firebase log level
This commit is contained in:
96
Pods/FirebaseRemoteConfig/FirebaseABTesting/Sources/Private/ABTExperimentPayload.h
generated
Normal file
96
Pods/FirebaseRemoteConfig/FirebaseABTesting/Sources/Private/ABTExperimentPayload.h
generated
Normal file
@ -0,0 +1,96 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// Policy for handling the case where there's an overflow of experiments for an installation
|
||||
/// instance.
|
||||
typedef NS_ENUM(int32_t, ABTExperimentPayloadExperimentOverflowPolicy) {
|
||||
ABTExperimentPayloadExperimentOverflowPolicyUnrecognizedValue = 999,
|
||||
ABTExperimentPayloadExperimentOverflowPolicyUnspecified = 0,
|
||||
ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest = 1,
|
||||
ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest = 2,
|
||||
};
|
||||
|
||||
@interface ABTExperimentLite : NSObject
|
||||
@property(nonatomic, readonly, copy) NSString *experimentId;
|
||||
|
||||
- (instancetype)initWithExperimentId:(NSString *)experimentId NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
@end
|
||||
|
||||
@interface ABTExperimentPayload : NSObject
|
||||
|
||||
/// Unique identifier for this experiment.
|
||||
@property(nonatomic, readonly, copy) NSString *experimentId;
|
||||
|
||||
/// Unique identifier for the variant to which an installation instance has been assigned.
|
||||
@property(nonatomic, readonly, copy) NSString *variantId;
|
||||
|
||||
/// Epoch time that represents when the experiment was started.
|
||||
@property(nonatomic, readonly) int64_t experimentStartTimeMillis;
|
||||
|
||||
/// The event that triggers this experiment into ON state.
|
||||
@property(nonatomic, nullable, readonly, copy) NSString *triggerEvent;
|
||||
|
||||
/// Duration in milliseconds for which the experiment can stay in STANDBY state (un-triggered).
|
||||
@property(nonatomic, readonly) int64_t triggerTimeoutMillis;
|
||||
|
||||
/// Duration in milliseconds for which the experiment can stay in ON state (triggered).
|
||||
@property(nonatomic, readonly) int64_t timeToLiveMillis;
|
||||
|
||||
/// The event logged when impact service sets the experiment.
|
||||
@property(nonatomic, readonly, copy) NSString *setEventToLog;
|
||||
|
||||
/// The event logged when an experiment goes to the ON state.
|
||||
@property(nonatomic, readonly, copy) NSString *activateEventToLog;
|
||||
|
||||
/// The event logged when an experiment is cleared.
|
||||
@property(nonatomic, readonly, copy) NSString *clearEventToLog;
|
||||
|
||||
/// The event logged when an experiment times out after `triggerTimeoutMillis` milliseconds.
|
||||
@property(nonatomic, readonly, copy) NSString *timeoutEventToLog;
|
||||
|
||||
/// The event logged when an experiment times out after `timeToLiveMillis` milliseconds.
|
||||
@property(nonatomic, readonly, copy) NSString *ttlExpiryEventToLog;
|
||||
|
||||
@property(nonatomic, readonly) ABTExperimentPayloadExperimentOverflowPolicy overflowPolicy;
|
||||
|
||||
/// A list of all other ongoing (started, and not yet stopped) experiments at the time this
|
||||
/// experiment was started. Does not include this experiment; only the others.
|
||||
@property(nonatomic, readonly) NSArray<ABTExperimentLite *> *ongoingExperiments;
|
||||
|
||||
/// Parses an ABTExperimentPayload directly from JSON data.
|
||||
/// @param data JSON object as NSData. Must be reconstructible as an NSDictionary<NSString* , id>.
|
||||
+ (nullable instancetype)parseFromData:(NSData *)data;
|
||||
|
||||
/// Initializes an ABTExperimentPayload from a dictionary with experiment metadata.
|
||||
- (instancetype)initWithDictionary:(NSDictionary<NSString *, id> *)dictionary
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/// Clears the trigger event associated with this payload.
|
||||
- (void)clearTriggerEvent;
|
||||
|
||||
/// Checks if the overflow policy is a valid enum object.
|
||||
- (BOOL)overflowPolicyIsValid;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
20
Pods/FirebaseRemoteConfig/FirebaseABTesting/Sources/Private/FirebaseABTestingInternal.h
generated
Normal file
20
Pods/FirebaseRemoteConfig/FirebaseABTesting/Sources/Private/FirebaseABTestingInternal.h
generated
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// An umbrella header, for any other libraries in this repo to access Firebase Public and Private
|
||||
// headers. Any package manager complexity should be handled here.
|
||||
|
||||
#import <FirebaseABTesting/FirebaseABTesting.h>
|
||||
|
||||
#import "FirebaseABTesting/Sources/Private/ABTExperimentPayload.h"
|
||||
156
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FIRAppInternal.h
generated
Normal file
156
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FIRAppInternal.h
generated
Normal file
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2017 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <FirebaseCore/FIRApp.h>
|
||||
|
||||
@class FIRComponentContainer;
|
||||
@class FIRHeartbeatLogger;
|
||||
@protocol FIRLibrary;
|
||||
|
||||
/**
|
||||
* The internal interface to `FirebaseApp`. This is meant for first-party integrators, who need to
|
||||
* receive `FirebaseApp` notifications, log info about the success or failure of their
|
||||
* configuration, and access other internal functionality of `FirebaseApp`.
|
||||
*/
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, FIRConfigType) {
|
||||
FIRConfigTypeCore = 1,
|
||||
FIRConfigTypeSDK = 2,
|
||||
};
|
||||
|
||||
extern NSString *const kFIRDefaultAppName;
|
||||
extern NSString *const kFIRAppReadyToConfigureSDKNotification;
|
||||
extern NSString *const kFIRAppDeleteNotification;
|
||||
extern NSString *const kFIRAppIsDefaultAppKey;
|
||||
extern NSString *const kFIRAppNameKey;
|
||||
extern NSString *const kFIRGoogleAppIDKey;
|
||||
extern NSString *const kFirebaseCoreErrorDomain;
|
||||
|
||||
/**
|
||||
* The format string for the `UserDefaults` key used for storing the data collection enabled flag.
|
||||
* This includes formatting to append the `FirebaseApp`'s name.
|
||||
*/
|
||||
extern NSString *const kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat;
|
||||
|
||||
/**
|
||||
* The plist key used for storing the data collection enabled flag.
|
||||
*/
|
||||
extern NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey;
|
||||
|
||||
/** @var FirebaseAuthStateDidChangeInternalNotification
|
||||
@brief The name of the @c NotificationCenter notification which is posted when the auth state
|
||||
changes (e.g. a new token has been produced, a user logs in or out). The object parameter of
|
||||
the notification is a dictionary possibly containing the key:
|
||||
@c FirebaseAuthStateDidChangeInternalNotificationTokenKey (the new access token.) If it does not
|
||||
contain this key it indicates a sign-out event took place.
|
||||
*/
|
||||
extern NSString *const FIRAuthStateDidChangeInternalNotification;
|
||||
|
||||
/** @var FirebaseAuthStateDidChangeInternalNotificationTokenKey
|
||||
@brief A key present in the dictionary object parameter of the
|
||||
@c FirebaseAuthStateDidChangeInternalNotification notification. The value associated with this
|
||||
key will contain the new access token.
|
||||
*/
|
||||
extern NSString *const FIRAuthStateDidChangeInternalNotificationTokenKey;
|
||||
|
||||
/** @var FirebaseAuthStateDidChangeInternalNotificationAppKey
|
||||
@brief A key present in the dictionary object parameter of the
|
||||
@c FirebaseAuthStateDidChangeInternalNotification notification. The value associated with this
|
||||
key will contain the FirebaseApp associated with the auth instance.
|
||||
*/
|
||||
extern NSString *const FIRAuthStateDidChangeInternalNotificationAppKey;
|
||||
|
||||
/** @var FirebaseAuthStateDidChangeInternalNotificationUIDKey
|
||||
@brief A key present in the dictionary object parameter of the
|
||||
@c FirebaseAuthStateDidChangeInternalNotification notification. The value associated with this
|
||||
key will contain the new user's UID (or nil if there is no longer a user signed in).
|
||||
*/
|
||||
extern NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey;
|
||||
|
||||
@interface FIRApp ()
|
||||
|
||||
/**
|
||||
* A flag indicating if this is the default app (has the default app name).
|
||||
*/
|
||||
@property(nonatomic, readonly) BOOL isDefaultApp;
|
||||
|
||||
/**
|
||||
* The container of interop SDKs for this app.
|
||||
*/
|
||||
@property(nonatomic) FIRComponentContainer *container;
|
||||
|
||||
/**
|
||||
* The heartbeat logger associated with this app.
|
||||
*
|
||||
* Firebase apps have a 1:1 relationship with heartbeat loggers.
|
||||
*/
|
||||
@property(readonly) FIRHeartbeatLogger *heartbeatLogger;
|
||||
|
||||
/**
|
||||
* Checks if the default app is configured without trying to configure it.
|
||||
*/
|
||||
+ (BOOL)isDefaultAppConfigured;
|
||||
|
||||
/**
|
||||
* Registers a given third-party library with the given version number to be reported for
|
||||
* analytics.
|
||||
*
|
||||
* @param name Name of the library.
|
||||
* @param version Version of the library.
|
||||
*/
|
||||
+ (void)registerLibrary:(nonnull NSString *)name withVersion:(nonnull NSString *)version;
|
||||
|
||||
/**
|
||||
* Registers a given internal library to be reported for analytics.
|
||||
*
|
||||
* @param library Optional parameter for component registration.
|
||||
* @param name Name of the library.
|
||||
*/
|
||||
+ (void)registerInternalLibrary:(nonnull Class<FIRLibrary>)library
|
||||
withName:(nonnull NSString *)name;
|
||||
|
||||
/**
|
||||
* Registers a given internal library with the given version number to be reported for
|
||||
* analytics. This should only be used for non-Firebase libraries that have their own versioning
|
||||
* scheme.
|
||||
*
|
||||
* @param library Optional parameter for component registration.
|
||||
* @param name Name of the library.
|
||||
* @param version Version of the library.
|
||||
*/
|
||||
+ (void)registerInternalLibrary:(nonnull Class<FIRLibrary>)library
|
||||
withName:(nonnull NSString *)name
|
||||
withVersion:(nonnull NSString *)version;
|
||||
|
||||
/**
|
||||
* A concatenated string representing all the third-party libraries and version numbers.
|
||||
*/
|
||||
+ (NSString *)firebaseUserAgent;
|
||||
|
||||
/**
|
||||
* Can be used by the unit tests in each SDK to reset `FirebaseApp`. This method is thread unsafe.
|
||||
*/
|
||||
+ (void)resetApps;
|
||||
|
||||
/**
|
||||
* Can be used by the unit tests in each SDK to set customized options.
|
||||
*/
|
||||
- (instancetype)initInstanceWithName:(NSString *)name options:(FIROptions *)options;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
84
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FIRComponent.h
generated
Normal file
84
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FIRComponent.h
generated
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class FIRApp;
|
||||
@class FIRComponentContainer;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// Provides a system to clean up cached instances returned from the component system.
|
||||
NS_SWIFT_NAME(ComponentLifecycleMaintainer)
|
||||
@protocol FIRComponentLifecycleMaintainer
|
||||
/// The associated app will be deleted, clean up any resources as they are about to be deallocated.
|
||||
- (void)appWillBeDeleted:(FIRApp *)app;
|
||||
@end
|
||||
|
||||
typedef _Nullable id (^FIRComponentCreationBlock)(FIRComponentContainer *container,
|
||||
BOOL *isCacheable)
|
||||
NS_SWIFT_NAME(ComponentCreationBlock);
|
||||
|
||||
/// Describes the timing of instantiation. Note: new components should default to lazy unless there
|
||||
/// is a strong reason to be eager.
|
||||
typedef NS_ENUM(NSInteger, FIRInstantiationTiming) {
|
||||
FIRInstantiationTimingLazy,
|
||||
FIRInstantiationTimingAlwaysEager,
|
||||
FIRInstantiationTimingEagerInDefaultApp
|
||||
} NS_SWIFT_NAME(InstantiationTiming);
|
||||
|
||||
/// A component that can be used from other Firebase SDKs.
|
||||
NS_SWIFT_NAME(Component)
|
||||
@interface FIRComponent : NSObject
|
||||
|
||||
/// The protocol describing functionality provided from the `Component`.
|
||||
@property(nonatomic, strong, readonly) Protocol *protocol;
|
||||
|
||||
/// The timing of instantiation.
|
||||
@property(nonatomic, readonly) FIRInstantiationTiming instantiationTiming;
|
||||
|
||||
/// A block to instantiate an instance of the component with the appropriate dependencies.
|
||||
@property(nonatomic, copy, readonly) FIRComponentCreationBlock creationBlock;
|
||||
|
||||
// There's an issue with long NS_SWIFT_NAMES that causes compilation to fail, disable clang-format
|
||||
// for the next two methods.
|
||||
// clang-format off
|
||||
|
||||
/// Creates a component with no dependencies that will be lazily initialized.
|
||||
+ (instancetype)componentWithProtocol:(Protocol *)protocol
|
||||
creationBlock:(FIRComponentCreationBlock)creationBlock
|
||||
NS_SWIFT_NAME(init(_:creationBlock:));
|
||||
|
||||
/// Creates a component to be registered with the component container.
|
||||
///
|
||||
/// @param protocol - The protocol describing functionality provided by the component.
|
||||
/// @param instantiationTiming - When the component should be initialized. Use .lazy unless there's
|
||||
/// a good reason to be instantiated earlier.
|
||||
/// @param creationBlock - A block to instantiate the component with a container, and if
|
||||
/// @return A component that can be registered with the component container.
|
||||
+ (instancetype)componentWithProtocol:(Protocol *)protocol
|
||||
instantiationTiming:(FIRInstantiationTiming)instantiationTiming
|
||||
creationBlock:(FIRComponentCreationBlock)creationBlock
|
||||
NS_SWIFT_NAME(init(_:instantiationTiming:creationBlock:));
|
||||
|
||||
// clang-format on
|
||||
|
||||
/// Unavailable.
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
45
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FIRComponentContainer.h
generated
Normal file
45
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FIRComponentContainer.h
generated
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// A type-safe macro to retrieve a component from a container. This should be used to retrieve
|
||||
/// components instead of using the container directly.
|
||||
#define FIR_COMPONENT(type, container) \
|
||||
[FIRComponentType<id<type>> instanceForProtocol:@protocol(type) inContainer:container]
|
||||
|
||||
@class FIRApp;
|
||||
|
||||
/// A container that holds different components that are registered via the
|
||||
/// `registerAsComponentRegistrant` call. These classes should conform to `ComponentRegistrant`
|
||||
/// in order to properly register components for Core.
|
||||
NS_SWIFT_NAME(FirebaseComponentContainer)
|
||||
@interface FIRComponentContainer : NSObject
|
||||
|
||||
/// A weak reference to the app that an instance of the container belongs to.
|
||||
@property(nonatomic, weak, readonly) FIRApp *app;
|
||||
|
||||
// TODO: See if we can get improved type safety here.
|
||||
/// A Swift only API for fetching an instance since the top macro isn't available.
|
||||
- (nullable id)__instanceForProtocol:(Protocol *)protocol NS_SWIFT_NAME(instance(for:));
|
||||
|
||||
/// Unavailable. Use the `container` property on `FirebaseApp`.
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
35
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FIRComponentType.h
generated
Normal file
35
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FIRComponentType.h
generated
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class FIRComponentContainer;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// Do not use directly. A placeholder type in order to provide a macro that will warn users of
|
||||
/// mis-matched protocols.
|
||||
NS_SWIFT_NAME(ComponentType)
|
||||
@interface FIRComponentType<__covariant T> : NSObject
|
||||
|
||||
/// Do not use directly. A factory method to retrieve an instance that provides a specific
|
||||
/// functionality.
|
||||
+ (nullable T)instanceForProtocol:(Protocol *)protocol
|
||||
inContainer:(FIRComponentContainer *)container;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
90
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FIRHeartbeatLogger.h
generated
Normal file
90
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FIRHeartbeatLogger.h
generated
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#ifndef FIREBASE_BUILD_CMAKE
|
||||
@class FIRHeartbeatsPayload;
|
||||
#endif // FIREBASE_BUILD_CMAKE
|
||||
|
||||
/// Enum representing different daily heartbeat codes.
|
||||
/// This enum is only used by clients using platform logging V1. This is because
|
||||
/// the V1 payload only supports a single daily heartbeat.
|
||||
typedef NS_ENUM(NSInteger, FIRDailyHeartbeatCode) {
|
||||
/// Represents the absence of a daily heartbeat.
|
||||
FIRDailyHeartbeatCodeNone = 0,
|
||||
/// Represents the presence of a daily heartbeat.
|
||||
FIRDailyHeartbeatCodeSome = 2,
|
||||
};
|
||||
|
||||
@protocol FIRHeartbeatLoggerProtocol <NSObject>
|
||||
|
||||
/// Asynchronously logs a heartbeat.
|
||||
- (void)log;
|
||||
|
||||
#ifndef FIREBASE_BUILD_CMAKE
|
||||
/// Return the headerValue for the HeartbeatLogger.
|
||||
- (NSString *_Nullable)headerValue;
|
||||
#endif // FIREBASE_BUILD_CMAKE
|
||||
|
||||
/// Gets the heartbeat code for today.
|
||||
- (FIRDailyHeartbeatCode)heartbeatCodeForToday;
|
||||
|
||||
@end
|
||||
|
||||
#ifndef FIREBASE_BUILD_CMAKE
|
||||
/// Returns a nullable string header value from a given heartbeats payload.
|
||||
///
|
||||
/// This API returns `nil` when the given heartbeats payload is considered empty.
|
||||
///
|
||||
/// @param heartbeatsPayload The heartbeats payload.
|
||||
NSString *_Nullable FIRHeaderValueFromHeartbeatsPayload(FIRHeartbeatsPayload *heartbeatsPayload);
|
||||
#endif // FIREBASE_BUILD_CMAKE
|
||||
|
||||
/// A thread safe, synchronized object that logs and flushes platform logging info.
|
||||
@interface FIRHeartbeatLogger : NSObject <FIRHeartbeatLoggerProtocol>
|
||||
|
||||
/// Designated initializer.
|
||||
///
|
||||
/// @param appID The app ID that this heartbeat logger corresponds to.
|
||||
- (instancetype)initWithAppID:(NSString *)appID;
|
||||
|
||||
/// Asynchronously logs a new heartbeat corresponding to the Firebase User Agent, if needed.
|
||||
///
|
||||
/// @note This API is thread-safe.
|
||||
- (void)log;
|
||||
|
||||
#ifndef FIREBASE_BUILD_CMAKE
|
||||
/// Flushes heartbeats from storage into a structured payload of heartbeats.
|
||||
///
|
||||
/// This API is for clients using platform logging V2.
|
||||
///
|
||||
/// @note This API is thread-safe.
|
||||
/// @return A payload of heartbeats.
|
||||
- (FIRHeartbeatsPayload *)flushHeartbeatsIntoPayload;
|
||||
#endif // FIREBASE_BUILD_CMAKE
|
||||
|
||||
/// Gets today's corresponding heartbeat code.
|
||||
///
|
||||
/// This API is for clients using platform logging V1.
|
||||
///
|
||||
/// @note This API is thread-safe.
|
||||
/// @return Heartbeat code indicating whether or not there is an unsent global heartbeat.
|
||||
- (FIRDailyHeartbeatCode)heartbeatCodeForToday;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
39
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FIRLibrary.h
generated
Normal file
39
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FIRLibrary.h
generated
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FIRLibrary_h
|
||||
#define FIRLibrary_h
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class FIRApp;
|
||||
@class FIRComponent;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// Provide an interface to register a library for userAgent logging and availability to others.
|
||||
NS_SWIFT_NAME(Library)
|
||||
@protocol FIRLibrary
|
||||
|
||||
/// Returns one or more Components that will be registered in
|
||||
/// FirebaseApp and participate in dependency resolution and injection.
|
||||
+ (NSArray<FIRComponent *> *)componentsToRegister;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif /* FIRLibrary_h */
|
||||
148
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FIRLogger.h
generated
Normal file
148
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FIRLogger.h
generated
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2017 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <FirebaseCore/FIRLoggerLevel.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* The Firebase services used in Firebase logger.
|
||||
*/
|
||||
typedef NSString *const FIRLoggerService;
|
||||
|
||||
extern NSString *const kFIRLoggerAnalytics;
|
||||
extern NSString *const kFIRLoggerCrash;
|
||||
extern NSString *const kFIRLoggerCore;
|
||||
extern NSString *const kFIRLoggerRemoteConfig;
|
||||
|
||||
/**
|
||||
* The key used to store the logger's error count.
|
||||
*/
|
||||
extern NSString *const kFIRLoggerErrorCountKey;
|
||||
|
||||
/**
|
||||
* The key used to store the logger's warning count.
|
||||
*/
|
||||
extern NSString *const kFIRLoggerWarningCountKey;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
/**
|
||||
* Enables or disables Analytics debug mode.
|
||||
* If set to true, the logging level for Analytics will be set to FirebaseLoggerLevelDebug.
|
||||
* Enabling the debug mode has no effect if the app is running from App Store.
|
||||
* (required) analytics debug mode flag.
|
||||
*/
|
||||
void FIRSetAnalyticsDebugMode(BOOL analyticsDebugMode);
|
||||
|
||||
/**
|
||||
* Gets the current FIRLoggerLevel.
|
||||
*/
|
||||
FIRLoggerLevel FIRGetLoggerLevel(void);
|
||||
|
||||
/**
|
||||
* Changes the default logging level of FirebaseLoggerLevelNotice to a user-specified level.
|
||||
* The default level cannot be set above FirebaseLoggerLevelNotice if the app is running from App
|
||||
* Store. (required) log level (one of the FirebaseLoggerLevel enum values).
|
||||
*/
|
||||
void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel);
|
||||
|
||||
/**
|
||||
* Checks if the specified logger level is loggable given the current settings.
|
||||
* (required) log level (one of the FirebaseLoggerLevel enum values).
|
||||
* (required) whether or not this function is called from the Analytics component.
|
||||
*/
|
||||
BOOL FIRIsLoggableLevel(FIRLoggerLevel loggerLevel, BOOL analyticsComponent);
|
||||
|
||||
/**
|
||||
* Logs a message to the Xcode console and the device log. If running from AppStore, will
|
||||
* not log any messages with a level higher than FirebaseLoggerLevelNotice to avoid log spamming.
|
||||
* (required) log level (one of the FirebaseLoggerLevel enum values).
|
||||
* (required) service name of type FirebaseLoggerService.
|
||||
* (required) message code starting with "I-" which means iOS, followed by a capitalized
|
||||
* three-character service identifier and a six digit integer message ID that is unique
|
||||
* within the service.
|
||||
* An example of the message code is @"I-COR000001".
|
||||
* (required) message string which can be a format string.
|
||||
* (optional) variable arguments list obtained from calling va_start, used when message is a format
|
||||
* string.
|
||||
*/
|
||||
extern void FIRLogBasic(FIRLoggerLevel level,
|
||||
NSString *category,
|
||||
NSString *messageCode,
|
||||
NSString *message,
|
||||
// On 64-bit simulators, va_list is not a pointer, so cannot be marked nullable
|
||||
// See: http://stackoverflow.com/q/29095469
|
||||
#if __LP64__ && TARGET_OS_SIMULATOR || TARGET_OS_OSX
|
||||
va_list args_ptr
|
||||
#else
|
||||
va_list _Nullable args_ptr
|
||||
#endif
|
||||
);
|
||||
|
||||
/**
|
||||
* The following functions accept the following parameters in order:
|
||||
* (required) service name of type FirebaseLoggerService.
|
||||
* (required) message code starting from "I-" which means iOS, followed by a capitalized
|
||||
* three-character service identifier and a six digit integer message ID that is unique
|
||||
* within the service.
|
||||
* An example of the message code is @"I-COR000001".
|
||||
* See go/firebase-log-proposal for details.
|
||||
* (required) message string which can be a format string.
|
||||
* (optional) the list of arguments to substitute into the format string.
|
||||
* Example usage:
|
||||
* FirebaseLogError(kFirebaseLoggerCore, @"I-COR000001", @"Configuration of %@ failed.", app.name);
|
||||
*/
|
||||
extern void FIRLogError(NSString *category, NSString *messageCode, NSString *message, ...)
|
||||
NS_FORMAT_FUNCTION(3, 4);
|
||||
extern void FIRLogWarning(NSString *category, NSString *messageCode, NSString *message, ...)
|
||||
NS_FORMAT_FUNCTION(3, 4);
|
||||
extern void FIRLogNotice(NSString *category, NSString *messageCode, NSString *message, ...)
|
||||
NS_FORMAT_FUNCTION(3, 4);
|
||||
extern void FIRLogInfo(NSString *category, NSString *messageCode, NSString *message, ...)
|
||||
NS_FORMAT_FUNCTION(3, 4);
|
||||
extern void FIRLogDebug(NSString *category, NSString *messageCode, NSString *message, ...)
|
||||
NS_FORMAT_FUNCTION(3, 4);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif // __cplusplus
|
||||
|
||||
NS_SWIFT_NAME(FirebaseLogger)
|
||||
@interface FIRLoggerWrapper : NSObject
|
||||
|
||||
/// Logs a given message at a given log level.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - level: The log level to use (defined by `FirebaseLoggerLevel` enum values).
|
||||
/// - service: The service name of type `FirebaseLoggerService`.
|
||||
/// - code: The message code. Starting with "I-" which means iOS, followed by a capitalized
|
||||
/// three-character service identifier and a six digit integer message ID that is unique within
|
||||
/// the service. An example of the message code is @"I-COR000001".
|
||||
/// - message: Formatted string to be used as the log's message.
|
||||
+ (void)logWithLevel:(FIRLoggerLevel)level
|
||||
service:(NSString *)category
|
||||
code:(NSString *)code
|
||||
message:(NSString *)message
|
||||
__attribute__((__swift_name__("log(level:service:code:message:)")));
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
106
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FIROptionsInternal.h
generated
Normal file
106
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FIROptionsInternal.h
generated
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2017 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <FirebaseCore/FIROptions.h>
|
||||
|
||||
/**
|
||||
* Keys for the strings in the plist file.
|
||||
*/
|
||||
extern NSString *const kFIRAPIKey;
|
||||
extern NSString *const kFIRTrackingID;
|
||||
extern NSString *const kFIRGoogleAppID;
|
||||
extern NSString *const kFIRClientID;
|
||||
extern NSString *const kFIRGCMSenderID;
|
||||
extern NSString *const kFIRAndroidClientID;
|
||||
extern NSString *const kFIRDatabaseURL;
|
||||
extern NSString *const kFIRStorageBucket;
|
||||
extern NSString *const kFIRBundleID;
|
||||
extern NSString *const kFIRProjectID;
|
||||
|
||||
/**
|
||||
* Keys for the plist file name
|
||||
*/
|
||||
extern NSString *const kServiceInfoFileName;
|
||||
|
||||
extern NSString *const kServiceInfoFileType;
|
||||
|
||||
/**
|
||||
* This header file exposes the initialization of FirebaseOptions to internal use.
|
||||
*/
|
||||
@interface FIROptions ()
|
||||
|
||||
/**
|
||||
* `resetDefaultOptions` and `initInternalWithOptionsDictionary` are exposed only for unit tests.
|
||||
*/
|
||||
+ (void)resetDefaultOptions;
|
||||
|
||||
/**
|
||||
* Initializes the options with dictionary. The above strings are the keys of the dictionary.
|
||||
* This is the designated initializer.
|
||||
*/
|
||||
- (instancetype)initInternalWithOptionsDictionary:(NSDictionary *)serviceInfoDictionary
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* `defaultOptions` and `defaultOptionsDictionary` are exposed in order to be used in FirebaseApp
|
||||
* and other first party services.
|
||||
*/
|
||||
+ (FIROptions *)defaultOptions;
|
||||
|
||||
+ (NSDictionary *)defaultOptionsDictionary;
|
||||
|
||||
/**
|
||||
* Indicates whether or not Analytics collection was explicitly enabled via a plist flag or at
|
||||
* runtime.
|
||||
*/
|
||||
@property(nonatomic, readonly) BOOL isAnalyticsCollectionExplicitlySet;
|
||||
|
||||
/**
|
||||
* Whether or not Analytics Collection was enabled. Analytics Collection is enabled unless
|
||||
* explicitly disabled in GoogleService-Info.plist.
|
||||
*/
|
||||
@property(nonatomic, readonly) BOOL isAnalyticsCollectionEnabled;
|
||||
|
||||
/**
|
||||
* Whether or not Analytics Collection was completely disabled. If true, then
|
||||
* isAnalyticsCollectionEnabled will be false.
|
||||
*/
|
||||
@property(nonatomic, readonly) BOOL isAnalyticsCollectionDeactivated;
|
||||
|
||||
/**
|
||||
* The version ID of the client library, e.g. @"1100000".
|
||||
*/
|
||||
@property(nonatomic, readonly, copy) NSString *libraryVersionID;
|
||||
|
||||
/**
|
||||
* The flag indicating whether this object was constructed with the values in the default plist
|
||||
* file.
|
||||
*/
|
||||
@property(nonatomic) BOOL usingOptionsFromDefaultPlist;
|
||||
|
||||
/**
|
||||
* Whether or not Measurement was enabled. Measurement is enabled unless explicitly disabled in
|
||||
* GoogleService-Info.plist.
|
||||
*/
|
||||
@property(nonatomic, readonly) BOOL isMeasurementEnabled;
|
||||
|
||||
/**
|
||||
* Whether or not editing is locked. This should occur after `FirebaseOptions` has been set on a
|
||||
* `FirebaseApp`.
|
||||
*/
|
||||
@property(nonatomic, getter=isEditingLocked) BOOL editingLocked;
|
||||
|
||||
@end
|
||||
24
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FirebaseCoreInternal.h
generated
Normal file
24
Pods/FirebaseRemoteConfig/FirebaseCore/Extension/FirebaseCoreInternal.h
generated
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
@import FirebaseCore;
|
||||
|
||||
#import "FIRAppInternal.h"
|
||||
#import "FIRComponent.h"
|
||||
#import "FIRComponentContainer.h"
|
||||
#import "FIRComponentType.h"
|
||||
#import "FIRHeartbeatLogger.h"
|
||||
#import "FIRLibrary.h"
|
||||
#import "FIRLogger.h"
|
||||
#import "FIROptionsInternal.h"
|
||||
@ -0,0 +1,19 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// An umbrella header, for any other libraries in this repo to access Firebase
|
||||
// Installations Public headers. Any package manager complexity should be
|
||||
// handled here.
|
||||
|
||||
#import <FirebaseInstallations/FirebaseInstallations.h>
|
||||
91
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/FIRConfigValue.m
generated
Normal file
91
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/FIRConfigValue.m
generated
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
|
||||
|
||||
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
|
||||
|
||||
@implementation FIRRemoteConfigValue {
|
||||
/// Data backing the config value.
|
||||
NSData *_data;
|
||||
FIRRemoteConfigSource _source;
|
||||
}
|
||||
|
||||
/// Designated initializer
|
||||
- (instancetype)initWithData:(NSData *)data source:(FIRRemoteConfigSource)source {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_data = [data copy];
|
||||
_source = source;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Superclass's designated initializer
|
||||
- (instancetype)init {
|
||||
return [self initWithData:nil source:FIRRemoteConfigSourceStatic];
|
||||
}
|
||||
|
||||
/// The string is a UTF-8 representation of NSData.
|
||||
- (nonnull NSString *)stringValue {
|
||||
return [[NSString alloc] initWithData:_data encoding:NSUTF8StringEncoding] ?: @"";
|
||||
}
|
||||
|
||||
/// Number representation of a UTF-8 string.
|
||||
- (NSNumber *)numberValue {
|
||||
return [NSNumber numberWithDouble:self.stringValue.doubleValue];
|
||||
}
|
||||
|
||||
/// Internal representation of the FIRRemoteConfigValue as a NSData object.
|
||||
- (NSData *)dataValue {
|
||||
return _data;
|
||||
}
|
||||
|
||||
/// Boolean representation of a UTF-8 string.
|
||||
- (BOOL)boolValue {
|
||||
return self.stringValue.boolValue;
|
||||
}
|
||||
|
||||
/// Returns a foundation object (NSDictionary / NSArray) representation for JSON data.
|
||||
- (id)JSONValue {
|
||||
NSError *error;
|
||||
if (!_data) {
|
||||
return nil;
|
||||
}
|
||||
id JSONObject = [NSJSONSerialization JSONObjectWithData:_data options:kNilOptions error:&error];
|
||||
if (error) {
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000065", @"Error parsing data as JSON.");
|
||||
return nil;
|
||||
}
|
||||
return JSONObject;
|
||||
}
|
||||
|
||||
/// Debug description showing the representations of all types.
|
||||
- (NSString *)debugDescription {
|
||||
NSString *content = [NSString
|
||||
stringWithFormat:@"Boolean: %d, String: %@, Number: %@, JSON:%@, Data: %@, Source: %zd",
|
||||
self.boolValue, self.stringValue, self.numberValue, self.JSONValue, _data,
|
||||
(long)self.source];
|
||||
return [NSString stringWithFormat:@"<%@: %p, %@>", [self class], self, content];
|
||||
}
|
||||
|
||||
/// Copy method.
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
FIRRemoteConfigValue *value = [[[self class] allocWithZone:zone] initWithData:_data];
|
||||
return value;
|
||||
}
|
||||
@end
|
||||
698
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m
generated
Normal file
698
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m
generated
Normal file
@ -0,0 +1,698 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
|
||||
|
||||
#import "FirebaseABTesting/Sources/Private/FirebaseABTestingInternal.h"
|
||||
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
|
||||
#import "FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.h"
|
||||
#import "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h"
|
||||
#import "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h"
|
||||
#import "FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigContent.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigRealtime.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNDevice.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNPersonalization.h"
|
||||
|
||||
/// Remote Config Error Domain.
|
||||
/// TODO: Rename according to obj-c style for constants.
|
||||
NSString *const FIRRemoteConfigErrorDomain = @"com.google.remoteconfig.ErrorDomain";
|
||||
// Remote Config Realtime Error Domain
|
||||
NSString *const FIRRemoteConfigUpdateErrorDomain = @"com.google.remoteconfig.update.ErrorDomain";
|
||||
/// Remote Config Error Info End Time Seconds;
|
||||
NSString *const FIRRemoteConfigThrottledEndTimeInSecondsKey = @"error_throttled_end_time_seconds";
|
||||
/// Minimum required time interval between fetch requests made to the backend.
|
||||
static NSString *const kRemoteConfigMinimumFetchIntervalKey = @"_rcn_minimum_fetch_interval";
|
||||
/// Timeout value for waiting on a fetch response.
|
||||
static NSString *const kRemoteConfigFetchTimeoutKey = @"_rcn_fetch_timeout";
|
||||
/// Notification when config is successfully activated
|
||||
const NSNotificationName FIRRemoteConfigActivateNotification =
|
||||
@"FIRRemoteConfigActivateNotification";
|
||||
static NSNotificationName FIRRolloutsStateDidChangeNotificationName =
|
||||
@"FIRRolloutsStateDidChangeNotification";
|
||||
|
||||
/// Listener for the get methods.
|
||||
typedef void (^FIRRemoteConfigListener)(NSString *_Nonnull, NSDictionary *_Nonnull);
|
||||
|
||||
@implementation FIRRemoteConfigSettings
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_minimumFetchInterval = RCNDefaultMinimumFetchInterval;
|
||||
_fetchTimeout = RCNHTTPDefaultConnectionTimeout;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation FIRRemoteConfig {
|
||||
/// All the config content.
|
||||
RCNConfigContent *_configContent;
|
||||
RCNConfigDBManager *_DBManager;
|
||||
RCNConfigSettings *_settings;
|
||||
RCNConfigFetch *_configFetch;
|
||||
RCNConfigExperiment *_configExperiment;
|
||||
RCNConfigRealtime *_configRealtime;
|
||||
dispatch_queue_t _queue;
|
||||
NSString *_appName;
|
||||
NSMutableArray *_listeners;
|
||||
}
|
||||
|
||||
static NSMutableDictionary<NSString *, NSMutableDictionary<NSString *, FIRRemoteConfig *> *>
|
||||
*RCInstances;
|
||||
|
||||
+ (nonnull FIRRemoteConfig *)remoteConfigWithApp:(FIRApp *_Nonnull)firebaseApp {
|
||||
return [FIRRemoteConfig
|
||||
remoteConfigWithFIRNamespace:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform
|
||||
app:firebaseApp];
|
||||
}
|
||||
|
||||
+ (nonnull FIRRemoteConfig *)remoteConfigWithFIRNamespace:(NSString *_Nonnull)firebaseNamespace {
|
||||
if (![FIRApp isDefaultAppConfigured]) {
|
||||
[NSException raise:@"FIRAppNotConfigured"
|
||||
format:@"The default `FirebaseApp` instance must be configured before the "
|
||||
@"default Remote Config instance can be initialized. One way to ensure this "
|
||||
@"is to call `FirebaseApp.configure()` in the App Delegate's "
|
||||
@"`application(_:didFinishLaunchingWithOptions:)` or the `@main` struct's "
|
||||
@"initializer in SwiftUI."];
|
||||
}
|
||||
|
||||
return [FIRRemoteConfig remoteConfigWithFIRNamespace:firebaseNamespace app:[FIRApp defaultApp]];
|
||||
}
|
||||
|
||||
+ (nonnull FIRRemoteConfig *)remoteConfigWithFIRNamespace:(NSString *_Nonnull)firebaseNamespace
|
||||
app:(FIRApp *_Nonnull)firebaseApp {
|
||||
// Use the provider to generate and return instances of FIRRemoteConfig for this specific app and
|
||||
// namespace. This will ensure the app is configured before Remote Config can return an instance.
|
||||
id<FIRRemoteConfigProvider> provider =
|
||||
FIR_COMPONENT(FIRRemoteConfigProvider, firebaseApp.container);
|
||||
return [provider remoteConfigForNamespace:firebaseNamespace];
|
||||
}
|
||||
|
||||
+ (FIRRemoteConfig *)remoteConfig {
|
||||
// If the default app is not configured at this point, warn the developer.
|
||||
if (![FIRApp isDefaultAppConfigured]) {
|
||||
[NSException raise:@"FIRAppNotConfigured"
|
||||
format:@"The default `FirebaseApp` instance must be configured before the "
|
||||
@"default Remote Config instance can be initialized. One way to ensure this "
|
||||
@"is to call `FirebaseApp.configure()` in the App Delegate's "
|
||||
@"`application(_:didFinishLaunchingWithOptions:)` or the `@main` struct's "
|
||||
@"initializer in SwiftUI."];
|
||||
}
|
||||
|
||||
return [FIRRemoteConfig
|
||||
remoteConfigWithFIRNamespace:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform
|
||||
app:[FIRApp defaultApp]];
|
||||
}
|
||||
|
||||
/// Singleton instance of serial queue for queuing all incoming RC calls.
|
||||
+ (dispatch_queue_t)sharedRemoteConfigSerialQueue {
|
||||
static dispatch_once_t onceToken;
|
||||
static dispatch_queue_t sharedRemoteConfigQueue;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedRemoteConfigQueue =
|
||||
dispatch_queue_create(RCNRemoteConfigQueueLabel, DISPATCH_QUEUE_SERIAL);
|
||||
});
|
||||
return sharedRemoteConfigQueue;
|
||||
}
|
||||
|
||||
/// Designated initializer
|
||||
- (instancetype)initWithAppName:(NSString *)appName
|
||||
FIROptions:(FIROptions *)options
|
||||
namespace:(NSString *)FIRNamespace
|
||||
DBManager:(RCNConfigDBManager *)DBManager
|
||||
configContent:(RCNConfigContent *)configContent
|
||||
analytics:(nullable id<FIRAnalyticsInterop>)analytics {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_appName = appName;
|
||||
_DBManager = DBManager;
|
||||
// The fully qualified Firebase namespace is namespace:firappname.
|
||||
_FIRNamespace = [NSString stringWithFormat:@"%@:%@", FIRNamespace, appName];
|
||||
|
||||
// Initialize RCConfigContent if not already.
|
||||
_configContent = configContent;
|
||||
_settings = [[RCNConfigSettings alloc] initWithDatabaseManager:_DBManager
|
||||
namespace:_FIRNamespace
|
||||
firebaseAppName:appName
|
||||
googleAppID:options.googleAppID];
|
||||
|
||||
FIRExperimentController *experimentController = [FIRExperimentController sharedInstance];
|
||||
_configExperiment = [[RCNConfigExperiment alloc] initWithDBManager:_DBManager
|
||||
experimentController:experimentController];
|
||||
/// Serial queue for read and write lock.
|
||||
_queue = [FIRRemoteConfig sharedRemoteConfigSerialQueue];
|
||||
|
||||
// Initialize with default config settings.
|
||||
[self setDefaultConfigSettings];
|
||||
_configFetch = [[RCNConfigFetch alloc] initWithContent:_configContent
|
||||
DBManager:_DBManager
|
||||
settings:_settings
|
||||
analytics:analytics
|
||||
experiment:_configExperiment
|
||||
queue:_queue
|
||||
namespace:_FIRNamespace
|
||||
options:options];
|
||||
|
||||
_configRealtime = [[RCNConfigRealtime alloc] init:_configFetch
|
||||
settings:_settings
|
||||
namespace:_FIRNamespace
|
||||
options:options];
|
||||
|
||||
[_settings loadConfigFromMetadataTable];
|
||||
|
||||
if (analytics) {
|
||||
_listeners = [[NSMutableArray alloc] init];
|
||||
RCNPersonalization *personalization =
|
||||
[[RCNPersonalization alloc] initWithAnalytics:analytics];
|
||||
[self addListener:^(NSString *key, NSDictionary *config) {
|
||||
[personalization logArmActive:key config:config];
|
||||
}];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// Initialize with default config settings.
|
||||
- (void)setDefaultConfigSettings {
|
||||
// Set the default config settings.
|
||||
self->_settings.fetchTimeout = RCNHTTPDefaultConnectionTimeout;
|
||||
self->_settings.minimumFetchInterval = RCNDefaultMinimumFetchInterval;
|
||||
}
|
||||
|
||||
- (void)ensureInitializedWithCompletionHandler:
|
||||
(nonnull FIRRemoteConfigInitializationCompletion)completionHandler {
|
||||
__weak FIRRemoteConfig *weakSelf = self;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
|
||||
FIRRemoteConfig *strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
BOOL initializationSuccess = [self->_configContent initializationSuccessful];
|
||||
NSError *error = nil;
|
||||
if (!initializationSuccess) {
|
||||
error = [[NSError alloc]
|
||||
initWithDomain:FIRRemoteConfigErrorDomain
|
||||
code:FIRRemoteConfigErrorInternalError
|
||||
userInfo:@{NSLocalizedDescriptionKey : @"Timed out waiting for database load."}];
|
||||
}
|
||||
completionHandler(error);
|
||||
});
|
||||
}
|
||||
|
||||
/// Adds a listener that will be called whenever one of the get methods is called.
|
||||
/// @param listener Function that takes in the parameter key and the config.
|
||||
- (void)addListener:(nonnull FIRRemoteConfigListener)listener {
|
||||
@synchronized(_listeners) {
|
||||
[_listeners addObject:listener];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)callListeners:(NSString *)key config:(NSDictionary *)config {
|
||||
@synchronized(_listeners) {
|
||||
for (FIRRemoteConfigListener listener in _listeners) {
|
||||
dispatch_async(_queue, ^{
|
||||
listener(key, config);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - fetch
|
||||
|
||||
- (void)fetchWithCompletionHandler:(FIRRemoteConfigFetchCompletion)completionHandler {
|
||||
dispatch_async(_queue, ^{
|
||||
[self fetchWithExpirationDuration:self->_settings.minimumFetchInterval
|
||||
completionHandler:completionHandler];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)fetchWithExpirationDuration:(NSTimeInterval)expirationDuration
|
||||
completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler {
|
||||
FIRRemoteConfigFetchCompletion completionHandlerCopy = nil;
|
||||
if (completionHandler) {
|
||||
completionHandlerCopy = [completionHandler copy];
|
||||
}
|
||||
[_configFetch fetchConfigWithExpirationDuration:expirationDuration
|
||||
completionHandler:completionHandlerCopy];
|
||||
}
|
||||
|
||||
#pragma mark - fetchAndActivate
|
||||
|
||||
- (void)fetchAndActivateWithCompletionHandler:
|
||||
(FIRRemoteConfigFetchAndActivateCompletion)completionHandler {
|
||||
__weak FIRRemoteConfig *weakSelf = self;
|
||||
FIRRemoteConfigFetchCompletion fetchCompletion =
|
||||
^(FIRRemoteConfigFetchStatus fetchStatus, NSError *fetchError) {
|
||||
FIRRemoteConfig *strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
// Fetch completed. We are being called on the main queue.
|
||||
// If fetch is successful, try to activate the fetched config
|
||||
if (fetchStatus == FIRRemoteConfigFetchStatusSuccess && !fetchError) {
|
||||
[strongSelf activateWithCompletion:^(BOOL changed, NSError *_Nullable activateError) {
|
||||
if (completionHandler) {
|
||||
FIRRemoteConfigFetchAndActivateStatus status =
|
||||
activateError ? FIRRemoteConfigFetchAndActivateStatusSuccessUsingPreFetchedData
|
||||
: FIRRemoteConfigFetchAndActivateStatusSuccessFetchedFromRemote;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
completionHandler(status, nil);
|
||||
});
|
||||
}
|
||||
}];
|
||||
} else if (completionHandler) {
|
||||
FIRRemoteConfigFetchAndActivateStatus status =
|
||||
fetchStatus == FIRRemoteConfigFetchStatusSuccess
|
||||
? FIRRemoteConfigFetchAndActivateStatusSuccessUsingPreFetchedData
|
||||
: FIRRemoteConfigFetchAndActivateStatusError;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
completionHandler(status, fetchError);
|
||||
});
|
||||
}
|
||||
};
|
||||
[self fetchWithCompletionHandler:fetchCompletion];
|
||||
}
|
||||
|
||||
#pragma mark - activate
|
||||
|
||||
typedef void (^FIRRemoteConfigActivateChangeCompletion)(BOOL changed, NSError *_Nullable error);
|
||||
|
||||
- (void)activateWithCompletion:(FIRRemoteConfigActivateChangeCompletion)completion {
|
||||
__weak FIRRemoteConfig *weakSelf = self;
|
||||
void (^applyBlock)(void) = ^(void) {
|
||||
FIRRemoteConfig *strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
NSError *error = [NSError errorWithDomain:FIRRemoteConfigErrorDomain
|
||||
code:FIRRemoteConfigErrorInternalError
|
||||
userInfo:@{@"ActivationFailureReason" : @"Internal Error."}];
|
||||
if (completion) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
completion(NO, error);
|
||||
});
|
||||
}
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000068", @"Internal error activating config.");
|
||||
return;
|
||||
}
|
||||
// Check if the last fetched config has already been activated. Fetches with no data change are
|
||||
// ignored.
|
||||
if (strongSelf->_settings.lastETagUpdateTime == 0 ||
|
||||
strongSelf->_settings.lastETagUpdateTime <= strongSelf->_settings.lastApplyTimeInterval) {
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069",
|
||||
@"Most recently fetched config is already activated.");
|
||||
if (completion) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
completion(NO, nil);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
[strongSelf->_configContent copyFromDictionary:self->_configContent.fetchedConfig
|
||||
toSource:RCNDBSourceActive
|
||||
forNamespace:self->_FIRNamespace];
|
||||
strongSelf->_settings.lastApplyTimeInterval = [[NSDate date] timeIntervalSince1970];
|
||||
// New config has been activated at this point
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069", @"Config activated.");
|
||||
[strongSelf->_configContent activatePersonalization];
|
||||
// Update last active template version number in setting and userDefaults.
|
||||
[strongSelf->_settings updateLastActiveTemplateVersion];
|
||||
// Update activeRolloutMetadata
|
||||
[strongSelf->_configContent activateRolloutMetadata:^(BOOL success) {
|
||||
if (success) {
|
||||
[self notifyRolloutsStateChange:strongSelf->_configContent.activeRolloutMetadata
|
||||
versionNumber:strongSelf->_settings.lastActiveTemplateVersion];
|
||||
}
|
||||
}];
|
||||
|
||||
// Update experiments only for 3p namespace
|
||||
NSString *namespace = [strongSelf->_FIRNamespace
|
||||
substringToIndex:[strongSelf->_FIRNamespace rangeOfString:@":"].location];
|
||||
if ([namespace isEqualToString:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self notifyConfigHasActivated];
|
||||
});
|
||||
[strongSelf->_configExperiment updateExperimentsWithHandler:^(NSError *_Nullable error) {
|
||||
if (completion) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
completion(YES, nil);
|
||||
});
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
if (completion) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
completion(YES, nil);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
dispatch_async(_queue, applyBlock);
|
||||
}
|
||||
|
||||
- (void)notifyConfigHasActivated {
|
||||
// Need a valid google app name.
|
||||
if (!_appName) {
|
||||
return;
|
||||
}
|
||||
// The Remote Config Swift SDK will be listening for this notification so it can tell SwiftUI to
|
||||
// update the UI.
|
||||
NSDictionary *appInfoDict = @{kFIRAppNameKey : _appName};
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:FIRRemoteConfigActivateNotification
|
||||
object:self
|
||||
userInfo:appInfoDict];
|
||||
}
|
||||
|
||||
#pragma mark - helpers
|
||||
- (NSString *)fullyQualifiedNamespace:(NSString *)namespace {
|
||||
// If this is already a fully qualified namespace, return.
|
||||
if ([namespace rangeOfString:@":"].location != NSNotFound) {
|
||||
return namespace;
|
||||
}
|
||||
NSString *fullyQualifiedNamespace = [NSString stringWithFormat:@"%@:%@", namespace, _appName];
|
||||
return fullyQualifiedNamespace;
|
||||
}
|
||||
|
||||
- (FIRRemoteConfigValue *)defaultValueForFullyQualifiedNamespace:(NSString *)namespace
|
||||
key:(NSString *)key {
|
||||
FIRRemoteConfigValue *value = self->_configContent.defaultConfig[namespace][key];
|
||||
if (!value) {
|
||||
value = [[FIRRemoteConfigValue alloc]
|
||||
initWithData:[NSData data]
|
||||
source:(FIRRemoteConfigSource)FIRRemoteConfigSourceStatic];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
#pragma mark - Get Config Result
|
||||
|
||||
- (FIRRemoteConfigValue *)objectForKeyedSubscript:(NSString *)key {
|
||||
return [self configValueForKey:key];
|
||||
}
|
||||
|
||||
- (FIRRemoteConfigValue *)configValueForKey:(NSString *)key {
|
||||
if (!key) {
|
||||
return [[FIRRemoteConfigValue alloc] initWithData:[NSData data]
|
||||
source:FIRRemoteConfigSourceStatic];
|
||||
}
|
||||
NSString *FQNamespace = [self fullyQualifiedNamespace:_FIRNamespace];
|
||||
__block FIRRemoteConfigValue *value;
|
||||
dispatch_sync(_queue, ^{
|
||||
value = self->_configContent.activeConfig[FQNamespace][key];
|
||||
if (value) {
|
||||
if (value.source != FIRRemoteConfigSourceRemote) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000001",
|
||||
@"Key %@ should come from source:%zd instead coming from source: %zd.", key,
|
||||
(long)FIRRemoteConfigSourceRemote, (long)value.source);
|
||||
}
|
||||
[self callListeners:key
|
||||
config:[self->_configContent getConfigAndMetadataForNamespace:FQNamespace]];
|
||||
return;
|
||||
}
|
||||
value = [self defaultValueForFullyQualifiedNamespace:FQNamespace key:key];
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
- (FIRRemoteConfigValue *)configValueForKey:(NSString *)key source:(FIRRemoteConfigSource)source {
|
||||
if (!key) {
|
||||
return [[FIRRemoteConfigValue alloc] initWithData:[NSData data]
|
||||
source:FIRRemoteConfigSourceStatic];
|
||||
}
|
||||
NSString *FQNamespace = [self fullyQualifiedNamespace:_FIRNamespace];
|
||||
|
||||
__block FIRRemoteConfigValue *value;
|
||||
dispatch_sync(_queue, ^{
|
||||
if (source == FIRRemoteConfigSourceRemote) {
|
||||
value = self->_configContent.activeConfig[FQNamespace][key];
|
||||
} else if (source == FIRRemoteConfigSourceDefault) {
|
||||
value = self->_configContent.defaultConfig[FQNamespace][key];
|
||||
} else {
|
||||
value = [[FIRRemoteConfigValue alloc] initWithData:[NSData data]
|
||||
source:FIRRemoteConfigSourceStatic];
|
||||
}
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
|
||||
objects:(id __unsafe_unretained[])stackbuf
|
||||
count:(NSUInteger)len {
|
||||
__block NSUInteger localValue;
|
||||
dispatch_sync(_queue, ^{
|
||||
localValue =
|
||||
[self->_configContent.activeConfig[self->_FIRNamespace] countByEnumeratingWithState:state
|
||||
objects:stackbuf
|
||||
count:len];
|
||||
});
|
||||
return localValue;
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
/// Last fetch completion time.
|
||||
- (NSDate *)lastFetchTime {
|
||||
__block NSDate *fetchTime;
|
||||
dispatch_sync(_queue, ^{
|
||||
NSTimeInterval lastFetchTime = self->_settings.lastFetchTimeInterval;
|
||||
fetchTime = [NSDate dateWithTimeIntervalSince1970:lastFetchTime];
|
||||
});
|
||||
return fetchTime;
|
||||
}
|
||||
|
||||
- (FIRRemoteConfigFetchStatus)lastFetchStatus {
|
||||
__block FIRRemoteConfigFetchStatus currentStatus;
|
||||
dispatch_sync(_queue, ^{
|
||||
currentStatus = self->_settings.lastFetchStatus;
|
||||
});
|
||||
return currentStatus;
|
||||
}
|
||||
|
||||
- (NSArray *)allKeysFromSource:(FIRRemoteConfigSource)source {
|
||||
__block NSArray *keys = [[NSArray alloc] init];
|
||||
dispatch_sync(_queue, ^{
|
||||
NSString *FQNamespace = [self fullyQualifiedNamespace:self->_FIRNamespace];
|
||||
switch (source) {
|
||||
case FIRRemoteConfigSourceDefault:
|
||||
if (self->_configContent.defaultConfig[FQNamespace]) {
|
||||
keys = [[self->_configContent.defaultConfig[FQNamespace] allKeys] copy];
|
||||
}
|
||||
break;
|
||||
case FIRRemoteConfigSourceRemote:
|
||||
if (self->_configContent.activeConfig[FQNamespace]) {
|
||||
keys = [[self->_configContent.activeConfig[FQNamespace] allKeys] copy];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
return keys;
|
||||
}
|
||||
|
||||
- (nonnull NSSet *)keysWithPrefix:(nullable NSString *)prefix {
|
||||
__block NSMutableSet *keys = [[NSMutableSet alloc] init];
|
||||
dispatch_sync(_queue, ^{
|
||||
NSString *FQNamespace = [self fullyQualifiedNamespace:self->_FIRNamespace];
|
||||
if (self->_configContent.activeConfig[FQNamespace]) {
|
||||
NSArray *allKeys = [self->_configContent.activeConfig[FQNamespace] allKeys];
|
||||
if (!prefix.length) {
|
||||
keys = [NSMutableSet setWithArray:allKeys];
|
||||
} else {
|
||||
for (NSString *key in allKeys) {
|
||||
if ([key hasPrefix:prefix]) {
|
||||
[keys addObject:key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return [keys copy];
|
||||
}
|
||||
|
||||
#pragma mark - Defaults
|
||||
|
||||
- (void)setDefaults:(NSDictionary<NSString *, NSObject *> *)defaultConfig {
|
||||
NSString *FQNamespace = [self fullyQualifiedNamespace:_FIRNamespace];
|
||||
NSDictionary *defaultConfigCopy = [[NSDictionary alloc] init];
|
||||
if (defaultConfig) {
|
||||
defaultConfigCopy = [defaultConfig copy];
|
||||
}
|
||||
void (^setDefaultsBlock)(void) = ^(void) {
|
||||
NSDictionary *namespaceToDefaults = @{FQNamespace : defaultConfigCopy};
|
||||
[self->_configContent copyFromDictionary:namespaceToDefaults
|
||||
toSource:RCNDBSourceDefault
|
||||
forNamespace:FQNamespace];
|
||||
self->_settings.lastSetDefaultsTimeInterval = [[NSDate date] timeIntervalSince1970];
|
||||
};
|
||||
dispatch_async(_queue, setDefaultsBlock);
|
||||
}
|
||||
|
||||
- (FIRRemoteConfigValue *)defaultValueForKey:(NSString *)key {
|
||||
NSString *FQNamespace = [self fullyQualifiedNamespace:_FIRNamespace];
|
||||
__block FIRRemoteConfigValue *value;
|
||||
dispatch_sync(_queue, ^{
|
||||
NSDictionary *defaultConfig = self->_configContent.defaultConfig;
|
||||
value = defaultConfig[FQNamespace][key];
|
||||
if (value) {
|
||||
if (value.source != FIRRemoteConfigSourceDefault) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000002",
|
||||
@"Key %@ should come from source:%zd instead coming from source: %zd", key,
|
||||
(long)FIRRemoteConfigSourceDefault, (long)value.source);
|
||||
}
|
||||
}
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
- (void)setDefaultsFromPlistFileName:(nullable NSString *)fileName {
|
||||
if (!fileName || fileName.length == 0) {
|
||||
FIRLogWarning(kFIRLoggerRemoteConfig, @"I-RCN000037",
|
||||
@"The plist file '%@' could not be found by Remote Config.", fileName);
|
||||
return;
|
||||
}
|
||||
NSArray *bundles = @[ [NSBundle mainBundle], [NSBundle bundleForClass:[self class]] ];
|
||||
|
||||
for (NSBundle *bundle in bundles) {
|
||||
NSString *plistFile = [bundle pathForResource:fileName ofType:@"plist"];
|
||||
// Use the first one we find.
|
||||
if (plistFile) {
|
||||
NSDictionary *defaultConfig = [[NSDictionary alloc] initWithContentsOfFile:plistFile];
|
||||
if (defaultConfig) {
|
||||
[self setDefaults:defaultConfig];
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
FIRLogWarning(kFIRLoggerRemoteConfig, @"I-RCN000037",
|
||||
@"The plist file '%@' could not be found by Remote Config.", fileName);
|
||||
}
|
||||
|
||||
#pragma mark - custom variables
|
||||
|
||||
- (FIRRemoteConfigSettings *)configSettings {
|
||||
__block NSTimeInterval minimumFetchInterval = RCNDefaultMinimumFetchInterval;
|
||||
__block NSTimeInterval fetchTimeout = RCNHTTPDefaultConnectionTimeout;
|
||||
dispatch_sync(_queue, ^{
|
||||
minimumFetchInterval = self->_settings.minimumFetchInterval;
|
||||
fetchTimeout = self->_settings.fetchTimeout;
|
||||
});
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000066",
|
||||
@"Successfully read configSettings. Minimum Fetch Interval:%f, "
|
||||
@"Fetch timeout: %f",
|
||||
minimumFetchInterval, fetchTimeout);
|
||||
FIRRemoteConfigSettings *settings = [[FIRRemoteConfigSettings alloc] init];
|
||||
settings.minimumFetchInterval = minimumFetchInterval;
|
||||
settings.fetchTimeout = fetchTimeout;
|
||||
/// The NSURLSession needs to be recreated whenever the fetch timeout may be updated.
|
||||
[_configFetch recreateNetworkSession];
|
||||
return settings;
|
||||
}
|
||||
|
||||
- (void)setConfigSettings:(FIRRemoteConfigSettings *)configSettings {
|
||||
void (^setConfigSettingsBlock)(void) = ^(void) {
|
||||
if (!configSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->_settings.minimumFetchInterval = configSettings.minimumFetchInterval;
|
||||
self->_settings.fetchTimeout = configSettings.fetchTimeout;
|
||||
/// The NSURLSession needs to be recreated whenever the fetch timeout may be updated.
|
||||
[self->_configFetch recreateNetworkSession];
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000067",
|
||||
@"Successfully set configSettings. Minimum Fetch Interval:%f, "
|
||||
@"Fetch timeout:%f",
|
||||
configSettings.minimumFetchInterval, configSettings.fetchTimeout);
|
||||
};
|
||||
dispatch_async(_queue, setConfigSettingsBlock);
|
||||
}
|
||||
|
||||
#pragma mark - Realtime
|
||||
|
||||
- (FIRConfigUpdateListenerRegistration *)addOnConfigUpdateListener:
|
||||
(void (^_Nonnull)(FIRRemoteConfigUpdate *update, NSError *_Nullable error))listener {
|
||||
return [self->_configRealtime addConfigUpdateListener:listener];
|
||||
}
|
||||
|
||||
#pragma mark - Rollout
|
||||
|
||||
- (void)addRemoteConfigInteropSubscriber:(id<FIRRolloutsStateSubscriber>)subscriber {
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:FIRRolloutsStateDidChangeNotificationName
|
||||
object:self
|
||||
queue:nil
|
||||
usingBlock:^(NSNotification *_Nonnull notification) {
|
||||
FIRRolloutsState *rolloutsState =
|
||||
notification.userInfo[FIRRolloutsStateDidChangeNotificationName];
|
||||
[subscriber rolloutsStateDidChange:rolloutsState];
|
||||
}];
|
||||
// Send active rollout metadata stored in persistence while app launched if there is activeConfig
|
||||
NSString *fullyQualifiedNamespace = [self fullyQualifiedNamespace:_FIRNamespace];
|
||||
NSDictionary<NSString *, NSDictionary *> *activeConfig = self->_configContent.activeConfig;
|
||||
if (activeConfig[fullyQualifiedNamespace] && activeConfig[fullyQualifiedNamespace].count > 0) {
|
||||
[self notifyRolloutsStateChange:self->_configContent.activeRolloutMetadata
|
||||
versionNumber:self->_settings.lastActiveTemplateVersion];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)notifyRolloutsStateChange:(NSArray<NSDictionary *> *)rolloutMetadata
|
||||
versionNumber:(NSString *)versionNumber {
|
||||
NSArray<FIRRolloutAssignment *> *rolloutsAssignments =
|
||||
[self rolloutsAssignmentsWith:rolloutMetadata versionNumber:versionNumber];
|
||||
FIRRolloutsState *rolloutsState =
|
||||
[[FIRRolloutsState alloc] initWithAssignmentList:rolloutsAssignments];
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069",
|
||||
@"Send rollouts state notification with name %@ to RemoteConfigInterop.",
|
||||
FIRRolloutsStateDidChangeNotificationName);
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
postNotificationName:FIRRolloutsStateDidChangeNotificationName
|
||||
object:self
|
||||
userInfo:@{FIRRolloutsStateDidChangeNotificationName : rolloutsState}];
|
||||
}
|
||||
|
||||
- (NSArray<FIRRolloutAssignment *> *)rolloutsAssignmentsWith:
|
||||
(NSArray<NSDictionary *> *)rolloutMetadata
|
||||
versionNumber:(NSString *)versionNumber {
|
||||
NSMutableArray<FIRRolloutAssignment *> *rolloutsAssignments = [[NSMutableArray alloc] init];
|
||||
NSString *FQNamespace = [self fullyQualifiedNamespace:_FIRNamespace];
|
||||
for (NSDictionary *metadata in rolloutMetadata) {
|
||||
NSString *rolloutId = metadata[RCNFetchResponseKeyRolloutID];
|
||||
NSString *variantID = metadata[RCNFetchResponseKeyVariantID];
|
||||
NSArray<NSString *> *affectedParameterKeys = metadata[RCNFetchResponseKeyAffectedParameterKeys];
|
||||
if (rolloutId && variantID && affectedParameterKeys) {
|
||||
for (NSString *key in affectedParameterKeys) {
|
||||
FIRRemoteConfigValue *value = self->_configContent.activeConfig[FQNamespace][key];
|
||||
if (!value) {
|
||||
value = [self defaultValueForFullyQualifiedNamespace:FQNamespace key:key];
|
||||
}
|
||||
FIRRolloutAssignment *assignment =
|
||||
[[FIRRolloutAssignment alloc] initWithRolloutId:rolloutId
|
||||
variantId:variantID
|
||||
templateVersion:[versionNumber longLongValue]
|
||||
parameterKey:key
|
||||
parameterValue:value.stringValue];
|
||||
[rolloutsAssignments addObject:assignment];
|
||||
}
|
||||
}
|
||||
}
|
||||
return rolloutsAssignments;
|
||||
}
|
||||
@end
|
||||
64
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.h
generated
Normal file
64
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.h
generated
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
|
||||
@import FirebaseRemoteConfigInterop;
|
||||
|
||||
@class FIRApp;
|
||||
@class FIRRemoteConfig;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// Provides and creates instances of Remote Config based on the namespace provided. Used in the
|
||||
/// interop registration process to keep track of RC instances for each `FIRApp` instance.
|
||||
@protocol FIRRemoteConfigProvider
|
||||
|
||||
/// Cached instances of Remote Config objects.
|
||||
@property(nonatomic, strong) NSMutableDictionary<NSString *, FIRRemoteConfig *> *instances;
|
||||
|
||||
/// Default method for retrieving a Remote Config instance, or creating one if it doesn't exist.
|
||||
- (FIRRemoteConfig *)remoteConfigForNamespace:(NSString *)remoteConfigNamespace;
|
||||
|
||||
@end
|
||||
|
||||
/// A concrete implementation for FIRRemoteConfigInterop to create Remote Config instances and
|
||||
/// register with Core's component system.
|
||||
@interface FIRRemoteConfigComponent
|
||||
: NSObject <FIRRemoteConfigProvider, FIRLibrary, FIRRemoteConfigInterop>
|
||||
|
||||
/// The FIRApp that instances will be set up with.
|
||||
@property(nonatomic, weak, readonly) FIRApp *app;
|
||||
|
||||
/// Cached instances of Remote Config objects.
|
||||
@property(nonatomic, strong) NSMutableDictionary<NSString *, FIRRemoteConfig *> *instances;
|
||||
|
||||
/// Clear all the component instances from the singleton which created previously, this is for
|
||||
/// testing only
|
||||
+ (void)clearAllComponentInstances;
|
||||
|
||||
/// Default method for retrieving a Remote Config instance, or creating one if it doesn't exist.
|
||||
- (FIRRemoteConfig *)remoteConfigForNamespace:(NSString *)remoteConfigNamespace;
|
||||
|
||||
/// Default initializer.
|
||||
- (instancetype)initWithApp:(FIRApp *)app NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (instancetype)init __attribute__((unavailable("Use `initWithApp:`.")));
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
151
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.m
generated
Normal file
151
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.m
generated
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.h"
|
||||
|
||||
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
|
||||
#import "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigContent.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
|
||||
#import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
|
||||
|
||||
@implementation FIRRemoteConfigComponent
|
||||
|
||||
// Because Component now need to register two protocols (provider and interop), we need a way to
|
||||
// return the same component instance for both registered protocol, this singleton pattern allow us
|
||||
// to return the same component object for both registration callback.
|
||||
static NSMutableDictionary<NSString *, FIRRemoteConfigComponent *> *_componentInstances = nil;
|
||||
|
||||
+ (FIRRemoteConfigComponent *)getComponentForApp:(FIRApp *)app {
|
||||
@synchronized(_componentInstances) {
|
||||
// need to init the dictionary first
|
||||
if (!_componentInstances) {
|
||||
_componentInstances = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
if (![_componentInstances objectForKey:app.name]) {
|
||||
_componentInstances[app.name] = [[self alloc] initWithApp:app];
|
||||
}
|
||||
return _componentInstances[app.name];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (void)clearAllComponentInstances {
|
||||
@synchronized(_componentInstances) {
|
||||
[_componentInstances removeAllObjects];
|
||||
}
|
||||
}
|
||||
|
||||
/// Default method for retrieving a Remote Config instance, or creating one if it doesn't exist.
|
||||
- (FIRRemoteConfig *)remoteConfigForNamespace:(NSString *)remoteConfigNamespace {
|
||||
if (!remoteConfigNamespace) {
|
||||
// TODO: Throw an error? Return nil? What do we want to do?
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Validate the required information is available.
|
||||
FIROptions *options = self.app.options;
|
||||
NSString *errorPropertyName;
|
||||
if (options.googleAppID.length == 0) {
|
||||
errorPropertyName = @"googleAppID";
|
||||
} else if (options.GCMSenderID.length == 0) {
|
||||
errorPropertyName = @"GCMSenderID";
|
||||
} else if (options.projectID.length == 0) {
|
||||
errorPropertyName = @"projectID";
|
||||
}
|
||||
|
||||
if (errorPropertyName) {
|
||||
NSString *const kFirebaseConfigErrorDomain = @"com.firebase.config";
|
||||
[NSException
|
||||
raise:kFirebaseConfigErrorDomain
|
||||
format:@"%@",
|
||||
[NSString
|
||||
stringWithFormat:
|
||||
@"Firebase Remote Config is missing the required %@ property from the "
|
||||
@"configured FirebaseApp and will not be able to function properly. Please "
|
||||
@"fix this issue to ensure that Firebase is correctly configured.",
|
||||
errorPropertyName]];
|
||||
}
|
||||
|
||||
FIRRemoteConfig *instance = self.instances[remoteConfigNamespace];
|
||||
if (!instance) {
|
||||
FIRApp *app = self.app;
|
||||
id<FIRAnalyticsInterop> analytics =
|
||||
app.isDefaultApp ? FIR_COMPONENT(FIRAnalyticsInterop, app.container) : nil;
|
||||
instance = [[FIRRemoteConfig alloc] initWithAppName:app.name
|
||||
FIROptions:app.options
|
||||
namespace:remoteConfigNamespace
|
||||
DBManager:[RCNConfigDBManager sharedInstance]
|
||||
configContent:[RCNConfigContent sharedInstance]
|
||||
analytics:analytics];
|
||||
self.instances[remoteConfigNamespace] = instance;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// Default initializer.
|
||||
- (instancetype)initWithApp:(FIRApp *)app {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_app = app;
|
||||
_instances = [[NSMutableDictionary alloc] initWithCapacity:1];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
+ (void)load {
|
||||
// Register as an internal library to be part of the initialization process. The name comes from
|
||||
// go/firebase-sdk-platform-info.
|
||||
[FIRApp registerInternalLibrary:self withName:@"fire-rc"];
|
||||
}
|
||||
|
||||
#pragma mark - Interoperability
|
||||
|
||||
+ (NSArray<FIRComponent *> *)componentsToRegister {
|
||||
FIRComponent *rcProvider = [FIRComponent
|
||||
componentWithProtocol:@protocol(FIRRemoteConfigProvider)
|
||||
instantiationTiming:FIRInstantiationTimingAlwaysEager
|
||||
creationBlock:^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) {
|
||||
// Cache the component so instances of Remote Config are cached.
|
||||
*isCacheable = YES;
|
||||
return [FIRRemoteConfigComponent getComponentForApp:container.app];
|
||||
}];
|
||||
|
||||
// Unlike provider needs to setup a hard dependency on remote config, interop allows an optional
|
||||
// dependency on RC
|
||||
FIRComponent *rcInterop = [FIRComponent
|
||||
componentWithProtocol:@protocol(FIRRemoteConfigInterop)
|
||||
instantiationTiming:FIRInstantiationTimingAlwaysEager
|
||||
creationBlock:^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) {
|
||||
// Cache the component so instances of Remote Config are cached.
|
||||
*isCacheable = YES;
|
||||
return [FIRRemoteConfigComponent getComponentForApp:container.app];
|
||||
}];
|
||||
return @[ rcProvider, rcInterop ];
|
||||
}
|
||||
|
||||
#pragma mark - Remote Config Interop Protocol
|
||||
|
||||
- (void)registerRolloutsStateSubscriber:(id<FIRRolloutsStateSubscriber>)subscriber
|
||||
for:(NSString * _Nonnull)namespace {
|
||||
FIRRemoteConfig *instance = [self remoteConfigForNamespace:namespace];
|
||||
[instance addRemoteConfigInteropSubscriber:subscriber];
|
||||
}
|
||||
|
||||
@end
|
||||
33
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/FIRRemoteConfigUpdate.m
generated
Normal file
33
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/FIRRemoteConfigUpdate.m
generated
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2022 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
|
||||
|
||||
@implementation FIRRemoteConfigUpdate {
|
||||
NSSet<NSString *> *_updatedKeys;
|
||||
}
|
||||
|
||||
- (instancetype)initWithUpdatedKeys:(NSSet<NSString *> *)updatedKeys {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_updatedKeys = [updatedKeys copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
87
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h
generated
Normal file
87
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h
generated
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <FirebaseRemoteConfig/FIRRemoteConfig.h>
|
||||
#import "RCNConfigSettings.h" // This import is needed to expose settings for the Swift API tests.
|
||||
|
||||
@class FIROptions;
|
||||
@class RCNConfigContent;
|
||||
@class RCNConfigDBManager;
|
||||
@class RCNConfigFetch;
|
||||
@class RCNConfigRealtime;
|
||||
@protocol FIRAnalyticsInterop;
|
||||
@protocol FIRRolloutsStateSubscriber;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class RCNConfigSettings;
|
||||
|
||||
@interface FIRRemoteConfigUpdate ()
|
||||
|
||||
/// Designated initializer.
|
||||
- (instancetype)initWithUpdatedKeys:(NSSet<NSString *> *)updatedKeys;
|
||||
@end
|
||||
|
||||
@interface FIRRemoteConfig () {
|
||||
NSString *_FIRNamespace;
|
||||
}
|
||||
|
||||
/// Internal settings
|
||||
@property(nonatomic, readonly, strong) RCNConfigSettings *settings;
|
||||
|
||||
/// Config settings are custom settings.
|
||||
@property(nonatomic, readwrite, strong, nonnull) RCNConfigFetch *configFetch;
|
||||
|
||||
@property(nonatomic, readwrite, strong, nonnull) RCNConfigRealtime *configRealtime;
|
||||
|
||||
/// Returns the FIRRemoteConfig instance for your namespace and for the default Firebase App.
|
||||
/// This singleton object contains the complete set of Remote Config parameter values available to
|
||||
/// the app, including the Active Config and Default Config.. This object also caches values fetched
|
||||
/// from the Remote Config Server until they are copied to the Active Config by calling
|
||||
/// activateFetched. When you fetch values from the Remote Config Server using the default Firebase
|
||||
/// namespace service, you should use this class method to create a shared instance of the
|
||||
/// FIRRemoteConfig object to ensure that your app will function properly with the Remote Config
|
||||
/// Server and the Firebase service. This API is used internally by 2P teams.
|
||||
+ (FIRRemoteConfig *)remoteConfigWithFIRNamespace:(NSString *)remoteConfigNamespace
|
||||
NS_SWIFT_NAME(remoteConfig(FIRNamespace:));
|
||||
|
||||
/// Returns the FIRRemoteConfig instance for your namespace and for the default 3P developer's app.
|
||||
/// This singleton object contains the complete set of Remote Config parameter values available to
|
||||
/// the app, including the Active Config and Default Config. This object also caches values fetched
|
||||
/// from the Remote Config Server until they are copied to the Active Config by calling
|
||||
/// activateFetched. When you fetch values from the Remote Config Server using the default Firebase
|
||||
/// namespace service, you should use this class method to create a shared instance of the
|
||||
/// FIRRemoteConfig object to ensure that your app will function properly with the Remote Config
|
||||
/// Server and the Firebase service.
|
||||
+ (FIRRemoteConfig *)remoteConfigWithFIRNamespace:(NSString *)remoteConfigNamespace
|
||||
app:(FIRApp *)app
|
||||
NS_SWIFT_NAME(remoteConfig(FIRNamespace:app:));
|
||||
|
||||
/// Initialize a FIRRemoteConfig instance with all the required parameters directly. This exists so
|
||||
/// tests can create FIRRemoteConfig objects without needing FIRApp.
|
||||
- (instancetype)initWithAppName:(NSString *)appName
|
||||
FIROptions:(FIROptions *)options
|
||||
namespace:(NSString *)FIRNamespace
|
||||
DBManager:(RCNConfigDBManager *)DBManager
|
||||
configContent:(RCNConfigContent *)configContent
|
||||
analytics:(nullable id<FIRAnalyticsInterop>)analytics;
|
||||
|
||||
/// Register RolloutsStateSubcriber to FIRRemoteConfig instance
|
||||
- (void)addRemoteConfigInteropSubscriber:(id<FIRRolloutsStateSubscriber> _Nonnull)subscriber;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
76
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h
generated
Normal file
76
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h
generated
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
|
||||
#import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
|
||||
|
||||
@class FIROptions;
|
||||
@class RCNConfigContent;
|
||||
@class RCNConfigSettings;
|
||||
@class RCNConfigExperiment;
|
||||
@class RCNConfigDBManager;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// Completion handler invoked by NSSessionFetcher.
|
||||
typedef void (^RCNConfigFetcherCompletion)(NSData *data, NSURLResponse *response, NSError *error);
|
||||
|
||||
/// Completion handler invoked after a fetch that contains the updated keys
|
||||
typedef void (^RCNConfigFetchCompletion)(FIRRemoteConfigFetchStatus status,
|
||||
FIRRemoteConfigUpdate *update,
|
||||
NSError *error);
|
||||
|
||||
@interface RCNConfigFetch : NSObject
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/// Designated initializer
|
||||
- (instancetype)initWithContent:(RCNConfigContent *)content
|
||||
DBManager:(RCNConfigDBManager *)DBManager
|
||||
settings:(RCNConfigSettings *)settings
|
||||
analytics:(nullable id<FIRAnalyticsInterop>)analytics
|
||||
experiment:(nullable RCNConfigExperiment *)experiment
|
||||
queue:(dispatch_queue_t)queue
|
||||
namespace:(NSString *)firebaseNamespace
|
||||
options:(FIROptions *)firebaseOptions NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/// Fetches config data keyed by namespace. Completion block will be called on the main queue.
|
||||
/// @param expirationDuration Expiration duration, in seconds.
|
||||
/// @param completionHandler Callback handler.
|
||||
- (void)fetchConfigWithExpirationDuration:(NSTimeInterval)expirationDuration
|
||||
completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler;
|
||||
|
||||
/// Fetches config data immediately, keyed by namespace. Completion block will be called on the main
|
||||
/// queue.
|
||||
/// @param fetchAttemptNumber The number of the fetch attempt.
|
||||
/// @param completionHandler Callback handler.
|
||||
- (void)realtimeFetchConfigWithNoExpirationDuration:(NSInteger)fetchAttemptNumber
|
||||
completionHandler:(RCNConfigFetchCompletion)completionHandler;
|
||||
|
||||
/// Add the ability to update NSURLSession's timeout after a session has already been created.
|
||||
- (void)recreateNetworkSession;
|
||||
|
||||
/// Provide fetchSession for tests to override.
|
||||
@property(nonatomic, readwrite, strong, nonnull) NSURLSession *fetchSession;
|
||||
|
||||
/// Provide config template version number for Realtime config client.
|
||||
@property(nonatomic, copy, nonnull) NSString *templateVersionNumber;
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@end
|
||||
152
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h
generated
Normal file
152
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h
generated
Normal file
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <FirebaseRemoteConfig/FIRRemoteConfig.h>
|
||||
|
||||
@class RCNConfigDBManager;
|
||||
|
||||
/// This internal class contains a set of variables that are unique among all the config instances.
|
||||
/// It also handles all metadata and internal metadata. This class is not thread safe and does not
|
||||
/// inherently allow for synchronized access. Callers are responsible for synchronization
|
||||
/// (currently using serial dispatch queues).
|
||||
@interface RCNConfigSettings : NSObject
|
||||
|
||||
/// The time interval that config data stays fresh.
|
||||
@property(nonatomic, readwrite, assign) NSTimeInterval minimumFetchInterval;
|
||||
|
||||
/// The timeout to set for outgoing fetch requests.
|
||||
@property(nonatomic, readwrite, assign) NSTimeInterval fetchTimeout;
|
||||
// The Google App ID of the configured FIRApp.
|
||||
@property(nonatomic, readwrite, copy) NSString *googleAppID;
|
||||
#pragma mark - Data required by config request.
|
||||
/// Device authentication ID required by config request.
|
||||
@property(nonatomic, copy) NSString *deviceAuthID;
|
||||
/// Secret Token required by config request.
|
||||
@property(nonatomic, copy) NSString *secretToken;
|
||||
/// Device data version of checkin information.
|
||||
@property(nonatomic, copy) NSString *deviceDataVersion;
|
||||
/// InstallationsID.
|
||||
@property(nonatomic, copy) NSString *configInstallationsIdentifier;
|
||||
/// Installations token.
|
||||
@property(nonatomic, copy) NSString *configInstallationsToken;
|
||||
|
||||
/// A list of successful fetch timestamps in milliseconds.
|
||||
/// TODO Not used anymore. Safe to remove.
|
||||
@property(nonatomic, readonly, copy) NSArray *successFetchTimes;
|
||||
/// A list of failed fetch timestamps in milliseconds.
|
||||
@property(nonatomic, readonly, copy) NSArray *failureFetchTimes;
|
||||
/// Custom variable (aka App context digest). This is the pending custom variables request before
|
||||
/// fetching.
|
||||
@property(nonatomic, copy) NSDictionary *customVariables;
|
||||
/// Cached internal metadata from internal metadata table. It contains customized information such
|
||||
/// as HTTP connection timeout, HTTP read timeout, success/failure throttling rate and time
|
||||
/// interval. Client has the default value of each parameters, they are only saved in
|
||||
/// internalMetadata if they have been customize by developers.
|
||||
@property(nonatomic, readonly, copy) NSDictionary *internalMetadata;
|
||||
/// Device conditions since last successful fetch from the backend. Device conditions including
|
||||
/// app
|
||||
/// version, iOS version, device localte, language, GMP project ID and Game project ID. Used for
|
||||
/// determing whether to throttle.
|
||||
@property(nonatomic, readonly, copy) NSDictionary *deviceContext;
|
||||
/// Bundle Identifier
|
||||
@property(nonatomic, readonly, copy) NSString *bundleIdentifier;
|
||||
/// The time of last successful config fetch.
|
||||
@property(nonatomic, readonly, assign) NSTimeInterval lastFetchTimeInterval;
|
||||
/// Last fetch status.
|
||||
@property(nonatomic, readwrite, assign) FIRRemoteConfigFetchStatus lastFetchStatus;
|
||||
/// The reason that last fetch failed.
|
||||
@property(nonatomic, readwrite, assign) FIRRemoteConfigError lastFetchError;
|
||||
/// The time of last apply timestamp.
|
||||
@property(nonatomic, readwrite, assign) NSTimeInterval lastApplyTimeInterval;
|
||||
/// The time of last setDefaults timestamp.
|
||||
@property(nonatomic, readwrite, assign) NSTimeInterval lastSetDefaultsTimeInterval;
|
||||
/// The latest eTag value stored from the last successful response.
|
||||
@property(nonatomic, readwrite, assign) NSString *lastETag;
|
||||
/// The timestamp of the last eTag update.
|
||||
@property(nonatomic, readwrite, assign) NSTimeInterval lastETagUpdateTime;
|
||||
/// Last fetched template version.
|
||||
@property(nonatomic, readwrite, assign) NSString *lastFetchedTemplateVersion;
|
||||
/// Last active template version.
|
||||
@property(nonatomic, readwrite, assign) NSString *lastActiveTemplateVersion;
|
||||
|
||||
#pragma mark Throttling properties
|
||||
|
||||
/// Throttling intervals are based on https://cloud.google.com/storage/docs/exponential-backoff
|
||||
/// Returns true if client has fetched config and has not got back from server. This is used to
|
||||
/// determine whether there is another config task infight when fetching.
|
||||
@property(atomic, readwrite, assign) BOOL isFetchInProgress;
|
||||
/// Returns the current retry interval in seconds set for exponential backoff.
|
||||
@property(nonatomic, readwrite, assign) double exponentialBackoffRetryInterval;
|
||||
/// Returns the time in seconds until the next request is allowed while in exponential backoff mode.
|
||||
@property(nonatomic, readonly, assign) NSTimeInterval exponentialBackoffThrottleEndTime;
|
||||
/// Returns the current retry interval in seconds set for exponential backoff for the Realtime
|
||||
/// service.
|
||||
@property(nonatomic, readwrite, assign) double realtimeExponentialBackoffRetryInterval;
|
||||
/// Returns the time in seconds until the next request is allowed while in exponential backoff mode
|
||||
/// for the Realtime service.
|
||||
@property(nonatomic, readonly, assign) NSTimeInterval realtimeExponentialBackoffThrottleEndTime;
|
||||
/// Realtime connection attempts.
|
||||
@property(nonatomic, readwrite, assign) int realtimeRetryCount;
|
||||
|
||||
#pragma mark Throttling Methods
|
||||
|
||||
/// Designated initializer.
|
||||
- (instancetype)initWithDatabaseManager:(RCNConfigDBManager *)manager
|
||||
namespace:(NSString *)FIRNamespace
|
||||
firebaseAppName:(NSString *)appName
|
||||
googleAppID:(NSString *)googleAppID;
|
||||
|
||||
/// Returns a fetch request with the latest device and config change.
|
||||
/// Whenever user issues a fetch api call, collect the latest request.
|
||||
/// @param userProperties User properties to set to config request.
|
||||
/// @return Config fetch request string
|
||||
- (NSString *)nextRequestWithUserProperties:(NSDictionary *)userProperties;
|
||||
|
||||
/// Returns metadata from metadata table.
|
||||
- (NSDictionary *)loadConfigFromMetadataTable;
|
||||
|
||||
/// Updates internal content with the latest successful config response.
|
||||
- (void)updateInternalContentWithResponse:(NSDictionary *)response;
|
||||
|
||||
/// Updates the metadata table with the current fetch status.
|
||||
/// @param fetchSuccess True if fetch was successful.
|
||||
- (void)updateMetadataWithFetchSuccessStatus:(BOOL)fetchSuccess
|
||||
templateVersion:(NSString *)templateVersion;
|
||||
|
||||
/// Increases the throttling time. Should only be called if the fetch error indicates a server
|
||||
/// issue.
|
||||
- (void)updateExponentialBackoffTime;
|
||||
|
||||
/// Increases the throttling time for Realtime. Should only be called if the Realtime error
|
||||
/// indicates a server issue.
|
||||
- (void)updateRealtimeExponentialBackoffTime;
|
||||
|
||||
/// Update last active template version from last fetched template version.
|
||||
- (void)updateLastActiveTemplateVersion;
|
||||
|
||||
/// Returns the difference between the Realtime backoff end time and the current time in a
|
||||
/// NSTimeInterval format.
|
||||
- (NSTimeInterval)getRealtimeBackoffInterval;
|
||||
|
||||
/// Returns true if we are in exponential backoff mode and it is not yet the next request time.
|
||||
- (BOOL)shouldThrottle;
|
||||
|
||||
/// Returns true if the last fetch is outside the minimum fetch interval supplied.
|
||||
- (BOOL)hasMinimumFetchIntervalElapsed:(NSTimeInterval)minimumFetchInterval;
|
||||
|
||||
@end
|
||||
360
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h
generated
Normal file
360
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h
generated
Normal file
@ -0,0 +1,360 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class FIRApp;
|
||||
|
||||
/// The Firebase Remote Config service default namespace, to be used if the API method does not
|
||||
/// specify a different namespace. Use the default namespace if configuring from the Google Firebase
|
||||
/// service.
|
||||
extern NSString *const _Nonnull FIRNamespaceGoogleMobilePlatform NS_SWIFT_NAME(
|
||||
NamespaceGoogleMobilePlatform);
|
||||
|
||||
/// Key used to manage throttling in NSError user info when the refreshing of Remote Config
|
||||
/// parameter values (data) is throttled. The value of this key is the elapsed time since 1970,
|
||||
/// measured in seconds.
|
||||
extern NSString *const _Nonnull FIRRemoteConfigThrottledEndTimeInSecondsKey NS_SWIFT_NAME(
|
||||
RemoteConfigThrottledEndTimeInSecondsKey);
|
||||
|
||||
/**
|
||||
* Listener registration returned by `addOnConfigUpdateListener`. Calling its method `remove` stops
|
||||
* the associated listener from receiving config updates and unregisters itself.
|
||||
*
|
||||
* If remove is called and no other listener registrations remain, the connection to the real-time
|
||||
* RC backend is closed. Subsequently calling `addOnConfigUpdateListener` will re-open the
|
||||
* connection.
|
||||
*/
|
||||
NS_SWIFT_NAME(ConfigUpdateListenerRegistration)
|
||||
@interface FIRConfigUpdateListenerRegistration : NSObject
|
||||
/**
|
||||
* Removes the listener associated with this `ConfigUpdateListenerRegistration`. After the
|
||||
* initial call, subsequent calls have no effect.
|
||||
*/
|
||||
- (void)remove;
|
||||
@end
|
||||
|
||||
/// Indicates whether updated data was successfully fetched.
|
||||
typedef NS_ENUM(NSInteger, FIRRemoteConfigFetchStatus) {
|
||||
/// Config has never been fetched.
|
||||
FIRRemoteConfigFetchStatusNoFetchYet,
|
||||
/// Config fetch succeeded.
|
||||
FIRRemoteConfigFetchStatusSuccess,
|
||||
/// Config fetch failed.
|
||||
FIRRemoteConfigFetchStatusFailure,
|
||||
/// Config fetch was throttled.
|
||||
FIRRemoteConfigFetchStatusThrottled,
|
||||
} NS_SWIFT_NAME(RemoteConfigFetchStatus);
|
||||
|
||||
/// Indicates whether updated data was successfully fetched and activated.
|
||||
typedef NS_ENUM(NSInteger, FIRRemoteConfigFetchAndActivateStatus) {
|
||||
/// The remote fetch succeeded and fetched data was activated.
|
||||
FIRRemoteConfigFetchAndActivateStatusSuccessFetchedFromRemote,
|
||||
/// The fetch and activate succeeded from already fetched but yet unexpired config data. You can
|
||||
/// control this using minimumFetchInterval property in FIRRemoteConfigSettings.
|
||||
FIRRemoteConfigFetchAndActivateStatusSuccessUsingPreFetchedData,
|
||||
/// The fetch and activate failed.
|
||||
FIRRemoteConfigFetchAndActivateStatusError
|
||||
} NS_SWIFT_NAME(RemoteConfigFetchAndActivateStatus);
|
||||
|
||||
/// Remote Config error domain that handles errors when fetching data from the service.
|
||||
extern NSString *const _Nonnull FIRRemoteConfigErrorDomain NS_SWIFT_NAME(RemoteConfigErrorDomain);
|
||||
/// Firebase Remote Config service fetch error.
|
||||
typedef NS_ERROR_ENUM(FIRRemoteConfigErrorDomain, FIRRemoteConfigError){
|
||||
/// Unknown or no error.
|
||||
FIRRemoteConfigErrorUnknown = 8001,
|
||||
/// Frequency of fetch requests exceeds throttled limit.
|
||||
FIRRemoteConfigErrorThrottled = 8002,
|
||||
/// Internal error that covers all internal HTTP errors.
|
||||
FIRRemoteConfigErrorInternalError = 8003,
|
||||
} NS_SWIFT_NAME(RemoteConfigError);
|
||||
|
||||
/// Remote Config error domain that handles errors for the real-time config update service.
|
||||
extern NSString *const _Nonnull FIRRemoteConfigUpdateErrorDomain NS_SWIFT_NAME(RemoteConfigUpdateErrorDomain);
|
||||
/// Firebase Remote Config real-time config update service error.
|
||||
typedef NS_ERROR_ENUM(FIRRemoteConfigUpdateErrorDomain, FIRRemoteConfigUpdateError){
|
||||
/// Unable to make a connection to the Remote Config backend.
|
||||
FIRRemoteConfigUpdateErrorStreamError = 8001,
|
||||
/// Unable to fetch the latest version of the config.
|
||||
FIRRemoteConfigUpdateErrorNotFetched = 8002,
|
||||
/// The ConfigUpdate message was unparsable.
|
||||
FIRRemoteConfigUpdateErrorMessageInvalid = 8003,
|
||||
/// The Remote Config real-time config update service is unavailable.
|
||||
FIRRemoteConfigUpdateErrorUnavailable = 8004,
|
||||
} NS_SWIFT_NAME(RemoteConfigUpdateError);
|
||||
|
||||
/// Enumerated value that indicates the source of Remote Config data. Data can come from
|
||||
/// the Remote Config service, the DefaultConfig that is available when the app is first installed,
|
||||
/// or a static initialized value if data is not available from the service or DefaultConfig.
|
||||
typedef NS_ENUM(NSInteger, FIRRemoteConfigSource) {
|
||||
FIRRemoteConfigSourceRemote, ///< The data source is the Remote Config service.
|
||||
FIRRemoteConfigSourceDefault, ///< The data source is the DefaultConfig defined for this app.
|
||||
FIRRemoteConfigSourceStatic, ///< The data doesn't exist, return a static initialized value.
|
||||
} NS_SWIFT_NAME(RemoteConfigSource);
|
||||
|
||||
/// Completion handler invoked by fetch methods when they get a response from the server.
|
||||
///
|
||||
/// @param status Config fetching status.
|
||||
/// @param error Error message on failure.
|
||||
typedef void (^FIRRemoteConfigFetchCompletion)(FIRRemoteConfigFetchStatus status,
|
||||
NSError *_Nullable error)
|
||||
NS_SWIFT_UNAVAILABLE("Use Swift's closure syntax instead.");
|
||||
|
||||
/// Completion handler invoked by activate method upon completion.
|
||||
/// @param error Error message on failure. Nil if activation was successful.
|
||||
typedef void (^FIRRemoteConfigActivateCompletion)(NSError *_Nullable error)
|
||||
NS_SWIFT_UNAVAILABLE("Use Swift's closure syntax instead.");
|
||||
|
||||
/// Completion handler invoked upon completion of Remote Config initialization.
|
||||
///
|
||||
/// @param initializationError nil if initialization succeeded.
|
||||
typedef void (^FIRRemoteConfigInitializationCompletion)(NSError *_Nullable initializationError)
|
||||
NS_SWIFT_UNAVAILABLE("Use Swift's closure syntax instead.");
|
||||
|
||||
/// Completion handler invoked by the fetchAndActivate method. Used to convey status of fetch and,
|
||||
/// if successful, resultant activate call
|
||||
/// @param status Config fetching status.
|
||||
/// @param error Error message on failure of config fetch
|
||||
typedef void (^FIRRemoteConfigFetchAndActivateCompletion)(
|
||||
FIRRemoteConfigFetchAndActivateStatus status, NSError *_Nullable error)
|
||||
NS_SWIFT_UNAVAILABLE("Use Swift's closure syntax instead.");
|
||||
|
||||
#pragma mark - FIRRemoteConfigValue
|
||||
/// This class provides a wrapper for Remote Config parameter values, with methods to get parameter
|
||||
/// values as different data types.
|
||||
NS_SWIFT_NAME(RemoteConfigValue)
|
||||
@interface FIRRemoteConfigValue : NSObject <NSCopying>
|
||||
/// Gets the value as a string.
|
||||
@property(nonatomic, readonly, nonnull) NSString *stringValue;
|
||||
/// Gets the value as a number value.
|
||||
@property(nonatomic, readonly, nonnull) NSNumber *numberValue;
|
||||
/// Gets the value as a NSData object.
|
||||
@property(nonatomic, readonly, nonnull) NSData *dataValue;
|
||||
/// Gets the value as a boolean.
|
||||
@property(nonatomic, readonly) BOOL boolValue;
|
||||
/// Gets a foundation object (NSDictionary / NSArray) by parsing the value as JSON. This method uses
|
||||
/// NSJSONSerialization's JSONObjectWithData method with an options value of 0.
|
||||
@property(nonatomic, readonly, nullable) id JSONValue NS_SWIFT_NAME(jsonValue);
|
||||
/// Identifies the source of the fetched value.
|
||||
@property(nonatomic, readonly) FIRRemoteConfigSource source;
|
||||
@end
|
||||
|
||||
#pragma mark - FIRRemoteConfigSettings
|
||||
/// Firebase Remote Config settings.
|
||||
NS_SWIFT_NAME(RemoteConfigSettings)
|
||||
@interface FIRRemoteConfigSettings : NSObject
|
||||
/// Indicates the default value in seconds to set for the minimum interval that needs to elapse
|
||||
/// before a fetch request can again be made to the Remote Config backend. After a fetch request to
|
||||
/// the backend has succeeded, no additional fetch requests to the backend will be allowed until the
|
||||
/// minimum fetch interval expires. Note that you can override this default on a per-fetch request
|
||||
/// basis using `RemoteConfig.fetch(withExpirationDuration:)`. For example, setting
|
||||
/// the expiration duration to 0 in the fetch request will override the `minimumFetchInterval` and
|
||||
/// allow the request to proceed.
|
||||
@property(nonatomic, assign) NSTimeInterval minimumFetchInterval;
|
||||
/// Indicates the default value in seconds to abandon a pending fetch request made to the backend.
|
||||
/// This value is set for outgoing requests as the `timeoutIntervalForRequest` as well as the
|
||||
/// `timeoutIntervalForResource` on the `NSURLSession`'s configuration.
|
||||
@property(nonatomic, assign) NSTimeInterval fetchTimeout;
|
||||
@end
|
||||
|
||||
#pragma mark - FIRRemoteConfigUpdate
|
||||
/// Used by Remote Config real-time config update service, this class represents changes between the
|
||||
/// newly fetched config and the current one. An instance of this class is passed to
|
||||
/// `FIRRemoteConfigUpdateCompletion` when a new config version has been automatically fetched.
|
||||
NS_SWIFT_NAME(RemoteConfigUpdate)
|
||||
@interface FIRRemoteConfigUpdate : NSObject
|
||||
|
||||
/// Parameter keys whose values have been updated from the currently activated values. Includes
|
||||
/// keys that are added, deleted, and whose value, value source, or metadata has changed.
|
||||
@property(nonatomic, readonly, nonnull) NSSet<NSString *> *updatedKeys;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - FIRRemoteConfig
|
||||
/// Firebase Remote Config class. The class method `remoteConfig()` can be used
|
||||
/// to fetch, activate and read config results and set default config results on the default
|
||||
/// Remote Config instance.
|
||||
NS_SWIFT_NAME(RemoteConfig)
|
||||
@interface FIRRemoteConfig : NSObject <NSFastEnumeration>
|
||||
/// Last successful fetch completion time.
|
||||
@property(nonatomic, readonly, strong, nullable) NSDate *lastFetchTime;
|
||||
/// Last fetch status. The status can be any enumerated value from `RemoteConfigFetchStatus`.
|
||||
@property(nonatomic, readonly, assign) FIRRemoteConfigFetchStatus lastFetchStatus;
|
||||
/// Config settings are custom settings.
|
||||
@property(nonatomic, readwrite, strong, nonnull) FIRRemoteConfigSettings *configSettings;
|
||||
|
||||
/// Returns the `RemoteConfig` instance configured for the default Firebase app. This singleton
|
||||
/// object contains the complete set of Remote Config parameter values available to the app,
|
||||
/// including the Active Config and Default Config. This object also caches values fetched from the
|
||||
/// Remote Config server until they are copied to the Active Config by calling `activate()`. When
|
||||
/// you fetch values from the Remote Config server using the default Firebase app, you should use
|
||||
/// this class method to create and reuse a shared instance of `RemoteConfig`.
|
||||
+ (nonnull FIRRemoteConfig *)remoteConfig NS_SWIFT_NAME(remoteConfig());
|
||||
|
||||
/// Returns the `RemoteConfig` instance for your (non-default) Firebase appID. Note that Firebase
|
||||
/// analytics does not work for non-default app instances. This singleton object contains the
|
||||
/// complete set of Remote Config parameter values available to the app, including the Active Config
|
||||
/// and Default Config. This object also caches values fetched from the Remote Config Server until
|
||||
/// they are copied to the Active Config by calling `activate())`. When you fetch values
|
||||
/// from the Remote Config Server using the non-default Firebase app, you should use this
|
||||
/// class method to create and reuse shared instance of `RemoteConfig`.
|
||||
+ (nonnull FIRRemoteConfig *)remoteConfigWithApp:(nonnull FIRApp *)app
|
||||
NS_SWIFT_NAME(remoteConfig(app:));
|
||||
|
||||
/// Unavailable. Use +remoteConfig instead.
|
||||
- (nonnull instancetype)init __attribute__((unavailable("Use +remoteConfig instead.")));
|
||||
|
||||
/// Ensures initialization is complete and clients can begin querying for Remote Config values.
|
||||
/// @param completionHandler Initialization complete callback with error parameter.
|
||||
- (void)ensureInitializedWithCompletionHandler:
|
||||
(void (^_Nonnull)(NSError *_Nullable initializationError))completionHandler;
|
||||
#pragma mark - Fetch
|
||||
/// Fetches Remote Config data with a callback. Call `activate()` to make fetched data
|
||||
/// available to your app.
|
||||
///
|
||||
/// Note: This method uses a Firebase Installations token to identify the app instance, and once
|
||||
/// it's called, it periodically sends data to the Firebase backend. (see
|
||||
/// `Installations.authToken(completion:)`).
|
||||
/// To stop the periodic sync, call `Installations.delete(completion:)`
|
||||
/// and avoid calling this method again.
|
||||
///
|
||||
/// @param completionHandler Fetch operation callback with status and error parameters.
|
||||
- (void)fetchWithCompletionHandler:(void (^_Nullable)(FIRRemoteConfigFetchStatus status,
|
||||
NSError *_Nullable error))completionHandler;
|
||||
|
||||
/// Fetches Remote Config data and sets a duration that specifies how long config data lasts.
|
||||
/// Call `activateWithCompletion:` to make fetched data available to your app.
|
||||
///
|
||||
/// Note: This method uses a Firebase Installations token to identify the app instance, and once
|
||||
/// it's called, it periodically sends data to the Firebase backend. (see
|
||||
/// `Installations.authToken(completion:)`).
|
||||
/// To stop the periodic sync, call `Installations.delete(completion:)`
|
||||
/// and avoid calling this method again.
|
||||
///
|
||||
/// @param expirationDuration Override the (default or optionally set `minimumFetchInterval`
|
||||
/// property in RemoteConfigSettings) `minimumFetchInterval` for only the current request, in
|
||||
/// seconds. Setting a value of 0 seconds will force a fetch to the backend.
|
||||
/// @param completionHandler Fetch operation callback with status and error parameters.
|
||||
- (void)fetchWithExpirationDuration:(NSTimeInterval)expirationDuration
|
||||
completionHandler:(void (^_Nullable)(FIRRemoteConfigFetchStatus status,
|
||||
NSError *_Nullable error))completionHandler;
|
||||
|
||||
/// Fetches Remote Config data and if successful, activates fetched data. Optional completion
|
||||
/// handler callback is invoked after the attempted activation of data, if the fetch call succeeded.
|
||||
///
|
||||
/// Note: This method uses a Firebase Installations token to identify the app instance, and once
|
||||
/// it's called, it periodically sends data to the Firebase backend. (see
|
||||
/// `Installations.authToken(completion:)`).
|
||||
/// To stop the periodic sync, call `Installations.delete(completion:)`
|
||||
/// and avoid calling this method again.
|
||||
///
|
||||
/// @param completionHandler Fetch operation callback with status and error parameters.
|
||||
- (void)fetchAndActivateWithCompletionHandler:
|
||||
(void (^_Nullable)(FIRRemoteConfigFetchAndActivateStatus status,
|
||||
NSError *_Nullable error))completionHandler;
|
||||
|
||||
#pragma mark - Apply
|
||||
|
||||
/// Applies Fetched Config data to the Active Config, causing updates to the behavior and appearance
|
||||
/// of the app to take effect (depending on how config data is used in the app).
|
||||
/// @param completion Activate operation callback with changed and error parameters.
|
||||
- (void)activateWithCompletion:(void (^_Nullable)(BOOL changed,
|
||||
NSError *_Nullable error))completion;
|
||||
|
||||
#pragma mark - Get Config
|
||||
/// Enables access to configuration values by using object subscripting syntax.
|
||||
/// For example:
|
||||
/// let config = RemoteConfig.remoteConfig()
|
||||
/// let value = config["yourKey"]
|
||||
/// let boolValue = value.boolValue
|
||||
/// let number = config["yourKey"].numberValue
|
||||
- (nonnull FIRRemoteConfigValue *)objectForKeyedSubscript:(nonnull NSString *)key;
|
||||
|
||||
/// Gets the config value.
|
||||
/// @param key Config key.
|
||||
- (nonnull FIRRemoteConfigValue *)configValueForKey:(nullable NSString *)key;
|
||||
|
||||
/// Gets the config value of a given source from the default namespace.
|
||||
/// @param key Config key.
|
||||
/// @param source Config value source.
|
||||
- (nonnull FIRRemoteConfigValue *)configValueForKey:(nullable NSString *)key
|
||||
source:(FIRRemoteConfigSource)source;
|
||||
|
||||
/// Gets all the parameter keys of a given source from the default namespace.
|
||||
///
|
||||
/// @param source The config data source.
|
||||
/// @return An array of keys under the given source.
|
||||
- (nonnull NSArray<NSString *> *)allKeysFromSource:(FIRRemoteConfigSource)source;
|
||||
|
||||
/// Returns the set of parameter keys that start with the given prefix, from the default namespace
|
||||
/// in the active config.
|
||||
///
|
||||
/// @param prefix The key prefix to look for. If prefix is nil or empty, returns all the
|
||||
/// keys.
|
||||
/// @return The set of parameter keys that start with the specified prefix.
|
||||
- (nonnull NSSet<NSString *> *)keysWithPrefix:(nullable NSString *)prefix;
|
||||
|
||||
#pragma mark - Defaults
|
||||
/// Sets config defaults for parameter keys and values in the default namespace config.
|
||||
/// @param defaults A dictionary mapping a NSString * key to a NSObject * value.
|
||||
- (void)setDefaults:(nullable NSDictionary<NSString *, NSObject *> *)defaults;
|
||||
|
||||
/// Sets default configs from plist for default namespace.
|
||||
///
|
||||
/// @param fileName The plist file name, with no file name extension. For example, if the plist file
|
||||
/// is named `defaultSamples.plist`:
|
||||
/// `RemoteConfig.remoteConfig().setDefaults(fromPlist: "defaultSamples")`
|
||||
- (void)setDefaultsFromPlistFileName:(nullable NSString *)fileName
|
||||
NS_SWIFT_NAME(setDefaults(fromPlist:));
|
||||
|
||||
/// Returns the default value of a given key from the default config.
|
||||
///
|
||||
/// @param key The parameter key of default config.
|
||||
/// @return Returns the default value of the specified key. Returns
|
||||
/// nil if the key doesn't exist in the default config.
|
||||
- (nullable FIRRemoteConfigValue *)defaultValueForKey:(nullable NSString *)key;
|
||||
|
||||
#pragma mark - Real-time Config Updates
|
||||
|
||||
/// Completion handler invoked by `addOnConfigUpdateListener` when there is an update to
|
||||
/// the config from the backend.
|
||||
///
|
||||
/// @param configUpdate An instance of `FIRRemoteConfigUpdate` that contains information on which
|
||||
/// key's values have changed.
|
||||
/// @param error Error message on failure.
|
||||
typedef void (^FIRRemoteConfigUpdateCompletion)(FIRRemoteConfigUpdate *_Nullable configUpdate,
|
||||
NSError *_Nullable error)
|
||||
NS_SWIFT_UNAVAILABLE("Use Swift's closure syntax instead.");
|
||||
|
||||
/// Start listening for real-time config updates from the Remote Config backend and automatically
|
||||
/// fetch updates when they're available.
|
||||
///
|
||||
/// If a connection to the Remote Config backend is not already open, calling this method will
|
||||
/// open it. Multiple listeners can be added by calling this method again, but subsequent calls
|
||||
/// re-use the same connection to the backend.
|
||||
///
|
||||
/// Note: Real-time Remote Config requires the Firebase Remote Config Realtime API. See Get started
|
||||
/// with Firebase Remote Config at https://firebase.google.com/docs/remote-config/get-started for
|
||||
/// more information.
|
||||
///
|
||||
/// @param listener The configured listener that is called for every config update.
|
||||
/// @return Returns a registration representing the listener. The registration contains
|
||||
/// a remove method, which can be used to stop receiving updates for the provided listener.
|
||||
- (FIRConfigUpdateListenerRegistration *_Nonnull)addOnConfigUpdateListener:
|
||||
(FIRRemoteConfigUpdateCompletion _Nonnull)listener
|
||||
NS_SWIFT_NAME(addOnConfigUpdateListener(remoteConfigUpdateCompletion:));
|
||||
|
||||
@end
|
||||
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "FIRRemoteConfig.h"
|
||||
72
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigConstants.h
generated
Normal file
72
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigConstants.h
generated
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#define RCN_SEC_PER_MIN 60
|
||||
#define RCN_MSEC_PER_SEC 1000
|
||||
|
||||
/// Key prefix applied to all the packages (bundle IDs) in internal metadata.
|
||||
static NSString *const RCNInternalMetadataAllPackagesPrefix = @"all_packages";
|
||||
|
||||
/// HTTP connection default timeout in seconds.
|
||||
static const NSTimeInterval RCNHTTPDefaultConnectionTimeout = 60;
|
||||
/// Default duration of how long config data lasts to stay fresh.
|
||||
static const NSTimeInterval RCNDefaultMinimumFetchInterval = 43200;
|
||||
|
||||
/// Label for serial queue for read/write lock on ivars.
|
||||
static const char *RCNRemoteConfigQueueLabel = "com.google.GoogleConfigService.FIRRemoteConfig";
|
||||
|
||||
/// Constants for key names in the fetch response.
|
||||
/// Key that includes an array of template entries.
|
||||
static NSString *const RCNFetchResponseKeyEntries = @"entries";
|
||||
/// Key that includes data for experiment descriptions in ABT.
|
||||
static NSString *const RCNFetchResponseKeyExperimentDescriptions = @"experimentDescriptions";
|
||||
/// Key that includes data for Personalization metadata.
|
||||
static NSString *const RCNFetchResponseKeyPersonalizationMetadata = @"personalizationMetadata";
|
||||
/// Key that includes data for Rollout metadata.
|
||||
static NSString *const RCNFetchResponseKeyRolloutMetadata = @"rolloutMetadata";
|
||||
/// Key that indicates rollout id in Rollout metadata.
|
||||
static NSString *const RCNFetchResponseKeyRolloutID = @"rolloutId";
|
||||
/// Key that indicates variant id in Rollout metadata.
|
||||
static NSString *const RCNFetchResponseKeyVariantID = @"variantId";
|
||||
/// Key that indicates affected parameter keys in Rollout Metadata.
|
||||
static NSString *const RCNFetchResponseKeyAffectedParameterKeys = @"affectedParameterKeys";
|
||||
/// Error key.
|
||||
static NSString *const RCNFetchResponseKeyError = @"error";
|
||||
/// Error code.
|
||||
static NSString *const RCNFetchResponseKeyErrorCode = @"code";
|
||||
/// Error status.
|
||||
static NSString *const RCNFetchResponseKeyErrorStatus = @"status";
|
||||
/// Error message.
|
||||
static NSString *const RCNFetchResponseKeyErrorMessage = @"message";
|
||||
/// The current state of the backend template.
|
||||
static NSString *const RCNFetchResponseKeyState = @"state";
|
||||
/// Default state (when not set).
|
||||
static NSString *const RCNFetchResponseKeyStateUnspecified = @"INSTANCE_STATE_UNSPECIFIED";
|
||||
/// Config key/value map and/or ABT experiment list differs from last fetch.
|
||||
/// TODO: Migrate to the new HTTP error codes once available in the backend. b/117182055
|
||||
static NSString *const RCNFetchResponseKeyStateUpdate = @"UPDATE";
|
||||
/// No template fetched.
|
||||
static NSString *const RCNFetchResponseKeyStateNoTemplate = @"NO_TEMPLATE";
|
||||
/// Config key/value map and ABT experiment list both match last fetch.
|
||||
static NSString *const RCNFetchResponseKeyStateNoChange = @"NO_CHANGE";
|
||||
/// Template found, but evaluates to empty (e.g. all keys omitted).
|
||||
static NSString *const RCNFetchResponseKeyStateEmptyConfig = @"EMPTY_CONFIG";
|
||||
/// Fetched Template Version key
|
||||
static NSString *const RCNFetchResponseKeyTemplateVersion = @"templateVersion";
|
||||
/// Active Template Version key
|
||||
static NSString *const RCNActiveKeyTemplateVersion = @"activeTemplateVersion";
|
||||
76
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigContent.h
generated
Normal file
76
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigContent.h
generated
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
|
||||
|
||||
typedef NS_ENUM(NSInteger, RCNDBSource) {
|
||||
RCNDBSourceActive,
|
||||
RCNDBSourceDefault,
|
||||
RCNDBSourceFetched,
|
||||
};
|
||||
|
||||
@class RCNConfigDBManager;
|
||||
|
||||
/// This class handles all the config content that is fetched from the server, cached in local
|
||||
/// config or persisted in database.
|
||||
@interface RCNConfigContent : NSObject
|
||||
/// Shared Singleton Instance
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
/// Fetched config (aka pending config) data that is latest data from server that might or might
|
||||
/// not be applied.
|
||||
@property(nonatomic, readonly, copy) NSDictionary *fetchedConfig;
|
||||
/// Active config that is available to external users;
|
||||
@property(nonatomic, readonly, copy) NSDictionary *activeConfig;
|
||||
/// Local default config that is provided by external users;
|
||||
@property(nonatomic, readonly, copy) NSDictionary *defaultConfig;
|
||||
/// Active Rollout metadata that is currently used.
|
||||
@property(nonatomic, readonly, copy) NSArray<NSDictionary *> *activeRolloutMetadata;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/// Designated initializer;
|
||||
- (instancetype)initWithDBManager:(RCNConfigDBManager *)DBManager NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/// Returns true if initialization succeeded.
|
||||
- (BOOL)initializationSuccessful;
|
||||
|
||||
/// Update config content from fetch response in JSON format.
|
||||
- (void)updateConfigContentWithResponse:(NSDictionary *)response
|
||||
forNamespace:(NSString *)FIRNamespace;
|
||||
|
||||
/// Copy from a given dictionary to one of the data source.
|
||||
/// @param fromDictionary The data to copy from.
|
||||
/// @param source The data source to copy to(pending/active/default).
|
||||
- (void)copyFromDictionary:(NSDictionary *)fromDictionary
|
||||
toSource:(RCNDBSource)source
|
||||
forNamespace:(NSString *)FIRNamespace;
|
||||
|
||||
/// Sets the fetched Personalization metadata to active.
|
||||
- (void)activatePersonalization;
|
||||
|
||||
/// Gets the active config and Personalization metadata.
|
||||
- (NSDictionary *)getConfigAndMetadataForNamespace:(NSString *)FIRNamespace;
|
||||
|
||||
/// Sets the fetched rollout metadata to active with a success completion handler.
|
||||
- (void)activateRolloutMetadata:(void (^)(BOOL success))completionHandler;
|
||||
|
||||
/// Returns the updated parameters between fetched and active config.
|
||||
- (FIRRemoteConfigUpdate *)getConfigUpdateForNamespace:(NSString *)FIRNamespace;
|
||||
|
||||
@end
|
||||
526
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigContent.m
generated
Normal file
526
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigContent.m
generated
Normal file
@ -0,0 +1,526 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigContent.h"
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h"
|
||||
#import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigDefines.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
|
||||
|
||||
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
|
||||
|
||||
@implementation RCNConfigContent {
|
||||
/// Active config data that is currently used.
|
||||
NSMutableDictionary *_activeConfig;
|
||||
/// Pending config (aka Fetched config) data that is latest data from server that might or might
|
||||
/// not be applied.
|
||||
NSMutableDictionary *_fetchedConfig;
|
||||
/// Default config provided by user.
|
||||
NSMutableDictionary *_defaultConfig;
|
||||
/// Active Personalization metadata that is currently used.
|
||||
NSDictionary *_activePersonalization;
|
||||
/// Pending Personalization metadata that is latest data from server that might or might not be
|
||||
/// applied.
|
||||
NSDictionary *_fetchedPersonalization;
|
||||
/// Active Rollout metadata that is currently used.
|
||||
NSArray<NSDictionary *> *_activeRolloutMetadata;
|
||||
/// Pending Rollout metadata that is latest data from server that might or might not be applied.
|
||||
NSArray<NSDictionary *> *_fetchedRolloutMetadata;
|
||||
/// DBManager
|
||||
RCNConfigDBManager *_DBManager;
|
||||
/// Current bundle identifier;
|
||||
NSString *_bundleIdentifier;
|
||||
/// Blocks all config reads until we have read from the database. This only
|
||||
/// potentially blocks on the first read. Should be a no-wait for all subsequent reads once we
|
||||
/// have data read into memory from the database.
|
||||
dispatch_group_t _dispatch_group;
|
||||
/// Boolean indicating if initial DB load of fetched,active and default config has succeeded.
|
||||
BOOL _isConfigLoadFromDBCompleted;
|
||||
/// Boolean indicating that the load from database has initiated at least once.
|
||||
BOOL _isDatabaseLoadAlreadyInitiated;
|
||||
}
|
||||
|
||||
/// Default timeout when waiting to read data from database.
|
||||
const NSTimeInterval kDatabaseLoadTimeoutSecs = 30.0;
|
||||
|
||||
/// Singleton instance of RCNConfigContent.
|
||||
+ (instancetype)sharedInstance {
|
||||
static dispatch_once_t onceToken;
|
||||
static RCNConfigContent *sharedInstance;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedInstance =
|
||||
[[RCNConfigContent alloc] initWithDBManager:[RCNConfigDBManager sharedInstance]];
|
||||
});
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
NSAssert(NO, @"Invalid initializer.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
/// Designated initializer
|
||||
- (instancetype)initWithDBManager:(RCNConfigDBManager *)DBManager {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_activeConfig = [[NSMutableDictionary alloc] init];
|
||||
_fetchedConfig = [[NSMutableDictionary alloc] init];
|
||||
_defaultConfig = [[NSMutableDictionary alloc] init];
|
||||
_activePersonalization = [[NSDictionary alloc] init];
|
||||
_fetchedPersonalization = [[NSDictionary alloc] init];
|
||||
_activeRolloutMetadata = [[NSArray alloc] init];
|
||||
_fetchedRolloutMetadata = [[NSArray alloc] init];
|
||||
_bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
|
||||
if (!_bundleIdentifier) {
|
||||
FIRLogNotice(kFIRLoggerRemoteConfig, @"I-RCN000038",
|
||||
@"Main bundle identifier is missing. Remote Config might not work properly.");
|
||||
_bundleIdentifier = @"";
|
||||
}
|
||||
_DBManager = DBManager;
|
||||
// Waits for both config and Personalization data to load.
|
||||
_dispatch_group = dispatch_group_create();
|
||||
[self loadConfigFromMainTable];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// Blocking call that returns true/false once database load completes / times out.
|
||||
// @return Initialization status.
|
||||
- (BOOL)initializationSuccessful {
|
||||
RCN_MUST_NOT_BE_MAIN_THREAD();
|
||||
BOOL isDatabaseLoadSuccessful = [self checkAndWaitForInitialDatabaseLoad];
|
||||
return isDatabaseLoadSuccessful;
|
||||
}
|
||||
|
||||
#pragma mark - database
|
||||
|
||||
/// This method is only meant to be called at init time. The underlying logic will need to be
|
||||
/// reevaluated if the assumption changes at a later time.
|
||||
- (void)loadConfigFromMainTable {
|
||||
if (!_DBManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSAssert(!_isDatabaseLoadAlreadyInitiated, @"Database load has already been initiated");
|
||||
_isDatabaseLoadAlreadyInitiated = true;
|
||||
|
||||
dispatch_group_enter(_dispatch_group);
|
||||
[_DBManager loadMainWithBundleIdentifier:_bundleIdentifier
|
||||
completionHandler:^(
|
||||
BOOL success, NSDictionary *fetchedConfig, NSDictionary *activeConfig,
|
||||
NSDictionary *defaultConfig, NSDictionary *rolloutMetadata) {
|
||||
self->_fetchedConfig = [fetchedConfig mutableCopy];
|
||||
self->_activeConfig = [activeConfig mutableCopy];
|
||||
self->_defaultConfig = [defaultConfig mutableCopy];
|
||||
self->_fetchedRolloutMetadata =
|
||||
[rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata] copy];
|
||||
self->_activeRolloutMetadata =
|
||||
[rolloutMetadata[@RCNRolloutTableKeyActiveMetadata] copy];
|
||||
dispatch_group_leave(self->_dispatch_group);
|
||||
}];
|
||||
|
||||
// TODO(karenzeng): Refactor personalization to be returned in loadMainWithBundleIdentifier above
|
||||
dispatch_group_enter(_dispatch_group);
|
||||
[_DBManager
|
||||
loadPersonalizationWithCompletionHandler:^(
|
||||
BOOL success, NSDictionary *fetchedPersonalization, NSDictionary *activePersonalization,
|
||||
NSDictionary *defaultConfig, NSDictionary *rolloutMetadata) {
|
||||
self->_fetchedPersonalization = [fetchedPersonalization copy];
|
||||
self->_activePersonalization = [activePersonalization copy];
|
||||
dispatch_group_leave(self->_dispatch_group);
|
||||
}];
|
||||
}
|
||||
|
||||
/// Update the current config result to main table.
|
||||
/// @param values Values in a row to write to the table.
|
||||
/// @param source The source the config data is coming from. It determines which table to write to.
|
||||
- (void)updateMainTableWithValues:(NSArray *)values fromSource:(RCNDBSource)source {
|
||||
[_DBManager insertMainTableWithValues:values fromSource:source completionHandler:nil];
|
||||
}
|
||||
|
||||
#pragma mark - update
|
||||
/// This function is for copying dictionary when user set up a default config or when user clicks
|
||||
/// activate. For now the DBSource can only be Active or Default.
|
||||
- (void)copyFromDictionary:(NSDictionary *)fromDict
|
||||
toSource:(RCNDBSource)DBSource
|
||||
forNamespace:(NSString *)FIRNamespace {
|
||||
// Make sure database load has completed.
|
||||
[self checkAndWaitForInitialDatabaseLoad];
|
||||
NSMutableDictionary *toDict;
|
||||
if (!fromDict) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000007",
|
||||
@"The source dictionary to copy from does not exist.");
|
||||
return;
|
||||
}
|
||||
FIRRemoteConfigSource source = FIRRemoteConfigSourceRemote;
|
||||
switch (DBSource) {
|
||||
case RCNDBSourceDefault:
|
||||
toDict = _defaultConfig;
|
||||
source = FIRRemoteConfigSourceDefault;
|
||||
break;
|
||||
case RCNDBSourceFetched:
|
||||
FIRLogWarning(kFIRLoggerRemoteConfig, @"I-RCN000008",
|
||||
@"This shouldn't happen. Destination dictionary should never be pending type.");
|
||||
return;
|
||||
case RCNDBSourceActive:
|
||||
toDict = _activeConfig;
|
||||
source = FIRRemoteConfigSourceRemote;
|
||||
[toDict removeObjectForKey:FIRNamespace];
|
||||
break;
|
||||
default:
|
||||
toDict = _activeConfig;
|
||||
source = FIRRemoteConfigSourceRemote;
|
||||
[toDict removeObjectForKey:FIRNamespace];
|
||||
break;
|
||||
}
|
||||
|
||||
// Completely wipe out DB first.
|
||||
[_DBManager deleteRecordFromMainTableWithNamespace:FIRNamespace
|
||||
bundleIdentifier:_bundleIdentifier
|
||||
fromSource:DBSource];
|
||||
|
||||
toDict[FIRNamespace] = [[NSMutableDictionary alloc] init];
|
||||
NSDictionary *config = fromDict[FIRNamespace];
|
||||
for (NSString *key in config) {
|
||||
if (DBSource == FIRRemoteConfigSourceDefault) {
|
||||
NSObject *value = config[key];
|
||||
NSData *valueData;
|
||||
if ([value isKindOfClass:[NSData class]]) {
|
||||
valueData = (NSData *)value;
|
||||
} else if ([value isKindOfClass:[NSString class]]) {
|
||||
valueData = [(NSString *)value dataUsingEncoding:NSUTF8StringEncoding];
|
||||
} else if ([value isKindOfClass:[NSNumber class]]) {
|
||||
NSString *strValue = [(NSNumber *)value stringValue];
|
||||
valueData = [(NSString *)strValue dataUsingEncoding:NSUTF8StringEncoding];
|
||||
} else if ([value isKindOfClass:[NSDate class]]) {
|
||||
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
||||
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
|
||||
NSString *strValue = [dateFormatter stringFromDate:(NSDate *)value];
|
||||
valueData = [(NSString *)strValue dataUsingEncoding:NSUTF8StringEncoding];
|
||||
} else if ([value isKindOfClass:[NSArray class]]) {
|
||||
NSError *error;
|
||||
valueData = [NSJSONSerialization dataWithJSONObject:value options:0 error:&error];
|
||||
if (error) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000076", @"Invalid array value for key '%@'",
|
||||
key);
|
||||
}
|
||||
} else if ([value isKindOfClass:[NSDictionary class]]) {
|
||||
NSError *error;
|
||||
valueData = [NSJSONSerialization dataWithJSONObject:value options:0 error:&error];
|
||||
if (error) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000077",
|
||||
@"Invalid dictionary value for key '%@'", key);
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
toDict[FIRNamespace][key] = [[FIRRemoteConfigValue alloc] initWithData:valueData
|
||||
source:source];
|
||||
NSArray *values = @[ _bundleIdentifier, FIRNamespace, key, valueData ];
|
||||
[self updateMainTableWithValues:values fromSource:DBSource];
|
||||
} else {
|
||||
FIRRemoteConfigValue *value = config[key];
|
||||
toDict[FIRNamespace][key] = [[FIRRemoteConfigValue alloc] initWithData:value.dataValue
|
||||
source:source];
|
||||
NSArray *values = @[ _bundleIdentifier, FIRNamespace, key, value.dataValue ];
|
||||
[self updateMainTableWithValues:values fromSource:DBSource];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateConfigContentWithResponse:(NSDictionary *)response
|
||||
forNamespace:(NSString *)currentNamespace {
|
||||
// Make sure database load has completed.
|
||||
[self checkAndWaitForInitialDatabaseLoad];
|
||||
NSString *state = response[RCNFetchResponseKeyState];
|
||||
|
||||
if (!state) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000049", @"State field in fetch response is nil.");
|
||||
return;
|
||||
}
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000059",
|
||||
@"Updating config content from Response for namespace:%@ with state: %@",
|
||||
currentNamespace, response[RCNFetchResponseKeyState]);
|
||||
|
||||
if ([state isEqualToString:RCNFetchResponseKeyStateNoChange]) {
|
||||
[self handleNoChangeStateForConfigNamespace:currentNamespace];
|
||||
return;
|
||||
}
|
||||
|
||||
/// Handle empty config state
|
||||
if ([state isEqualToString:RCNFetchResponseKeyStateEmptyConfig]) {
|
||||
[self handleEmptyConfigStateForConfigNamespace:currentNamespace];
|
||||
return;
|
||||
}
|
||||
|
||||
/// Handle no template state.
|
||||
if ([state isEqualToString:RCNFetchResponseKeyStateNoTemplate]) {
|
||||
[self handleNoTemplateStateForConfigNamespace:currentNamespace];
|
||||
return;
|
||||
}
|
||||
|
||||
/// Handle update state
|
||||
if ([state isEqualToString:RCNFetchResponseKeyStateUpdate]) {
|
||||
[self handleUpdateStateForConfigNamespace:currentNamespace
|
||||
withEntries:response[RCNFetchResponseKeyEntries]];
|
||||
[self handleUpdatePersonalization:response[RCNFetchResponseKeyPersonalizationMetadata]];
|
||||
[self handleUpdateRolloutFetchedMetadata:response[RCNFetchResponseKeyRolloutMetadata]];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)activatePersonalization {
|
||||
_activePersonalization = _fetchedPersonalization;
|
||||
[_DBManager insertOrUpdatePersonalizationConfig:_activePersonalization
|
||||
fromSource:RCNDBSourceActive];
|
||||
}
|
||||
|
||||
- (void)activateRolloutMetadata:(void (^)(BOOL success))completionHandler {
|
||||
_activeRolloutMetadata = _fetchedRolloutMetadata;
|
||||
[_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyActiveMetadata
|
||||
value:_activeRolloutMetadata
|
||||
completionHandler:^(BOOL success, NSDictionary *result) {
|
||||
completionHandler(success);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark State handling
|
||||
- (void)handleNoChangeStateForConfigNamespace:(NSString *)currentNamespace {
|
||||
if (!_fetchedConfig[currentNamespace]) {
|
||||
_fetchedConfig[currentNamespace] = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleEmptyConfigStateForConfigNamespace:(NSString *)currentNamespace {
|
||||
if (_fetchedConfig[currentNamespace]) {
|
||||
[_fetchedConfig[currentNamespace] removeAllObjects];
|
||||
} else {
|
||||
// If namespace has empty status and it doesn't exist in _fetchedConfig, we will
|
||||
// still add an entry for that namespace. Even if it will not be persisted in database.
|
||||
// TODO: Add generics for all collection types.
|
||||
_fetchedConfig[currentNamespace] = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
[_DBManager deleteRecordFromMainTableWithNamespace:currentNamespace
|
||||
bundleIdentifier:_bundleIdentifier
|
||||
fromSource:RCNDBSourceFetched];
|
||||
}
|
||||
|
||||
- (void)handleNoTemplateStateForConfigNamespace:(NSString *)currentNamespace {
|
||||
// Remove the namespace.
|
||||
[_fetchedConfig removeObjectForKey:currentNamespace];
|
||||
[_DBManager deleteRecordFromMainTableWithNamespace:currentNamespace
|
||||
bundleIdentifier:_bundleIdentifier
|
||||
fromSource:RCNDBSourceFetched];
|
||||
}
|
||||
- (void)handleUpdateStateForConfigNamespace:(NSString *)currentNamespace
|
||||
withEntries:(NSDictionary *)entries {
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000058", @"Update config in DB for namespace:%@",
|
||||
currentNamespace);
|
||||
// Clear before updating
|
||||
[_DBManager deleteRecordFromMainTableWithNamespace:currentNamespace
|
||||
bundleIdentifier:_bundleIdentifier
|
||||
fromSource:RCNDBSourceFetched];
|
||||
if ([_fetchedConfig objectForKey:currentNamespace]) {
|
||||
[_fetchedConfig[currentNamespace] removeAllObjects];
|
||||
} else {
|
||||
_fetchedConfig[currentNamespace] = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
// Store the fetched config values.
|
||||
for (NSString *key in entries) {
|
||||
NSData *valueData = [entries[key] dataUsingEncoding:NSUTF8StringEncoding];
|
||||
if (!valueData) {
|
||||
continue;
|
||||
}
|
||||
_fetchedConfig[currentNamespace][key] =
|
||||
[[FIRRemoteConfigValue alloc] initWithData:valueData source:FIRRemoteConfigSourceRemote];
|
||||
NSArray *values = @[ _bundleIdentifier, currentNamespace, key, valueData ];
|
||||
[self updateMainTableWithValues:values fromSource:RCNDBSourceFetched];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleUpdatePersonalization:(NSDictionary *)metadata {
|
||||
if (!metadata) {
|
||||
return;
|
||||
}
|
||||
_fetchedPersonalization = metadata;
|
||||
[_DBManager insertOrUpdatePersonalizationConfig:metadata fromSource:RCNDBSourceFetched];
|
||||
}
|
||||
|
||||
- (void)handleUpdateRolloutFetchedMetadata:(NSArray<NSDictionary *> *)metadata {
|
||||
if (!metadata) {
|
||||
metadata = [[NSArray alloc] init];
|
||||
}
|
||||
_fetchedRolloutMetadata = metadata;
|
||||
[_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyFetchedMetadata
|
||||
value:metadata
|
||||
completionHandler:nil];
|
||||
}
|
||||
|
||||
#pragma mark - getter/setter
|
||||
- (NSDictionary *)fetchedConfig {
|
||||
/// If this is the first time reading the fetchedConfig, we might still be reading it from the
|
||||
/// database.
|
||||
[self checkAndWaitForInitialDatabaseLoad];
|
||||
return _fetchedConfig;
|
||||
}
|
||||
|
||||
- (NSDictionary *)activeConfig {
|
||||
/// If this is the first time reading the activeConfig, we might still be reading it from the
|
||||
/// database.
|
||||
[self checkAndWaitForInitialDatabaseLoad];
|
||||
return _activeConfig;
|
||||
}
|
||||
|
||||
- (NSDictionary *)defaultConfig {
|
||||
/// If this is the first time reading the fetchedConfig, we might still be reading it from the
|
||||
/// database.
|
||||
[self checkAndWaitForInitialDatabaseLoad];
|
||||
return _defaultConfig;
|
||||
}
|
||||
|
||||
- (NSDictionary *)activePersonalization {
|
||||
[self checkAndWaitForInitialDatabaseLoad];
|
||||
return _activePersonalization;
|
||||
}
|
||||
|
||||
- (NSArray<NSDictionary *> *)activeRolloutMetadata {
|
||||
[self checkAndWaitForInitialDatabaseLoad];
|
||||
return _activeRolloutMetadata;
|
||||
}
|
||||
|
||||
- (NSDictionary *)getConfigAndMetadataForNamespace:(NSString *)FIRNamespace {
|
||||
/// If this is the first time reading the active metadata, we might still be reading it from the
|
||||
/// database.
|
||||
[self checkAndWaitForInitialDatabaseLoad];
|
||||
return @{
|
||||
RCNFetchResponseKeyEntries : _activeConfig[FIRNamespace],
|
||||
RCNFetchResponseKeyPersonalizationMetadata : _activePersonalization
|
||||
};
|
||||
}
|
||||
|
||||
/// We load the database async at init time. Block all further calls to active/fetched/default
|
||||
/// configs until load is done.
|
||||
/// @return Database load completion status.
|
||||
- (BOOL)checkAndWaitForInitialDatabaseLoad {
|
||||
/// Wait until load is done. This should be a no-op for subsequent calls.
|
||||
if (!_isConfigLoadFromDBCompleted) {
|
||||
intptr_t isErrorOrTimeout = dispatch_group_wait(
|
||||
_dispatch_group,
|
||||
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDatabaseLoadTimeoutSecs * NSEC_PER_SEC)));
|
||||
if (isErrorOrTimeout) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000048",
|
||||
@"Timed out waiting for fetched config to be loaded from DB");
|
||||
return false;
|
||||
}
|
||||
_isConfigLoadFromDBCompleted = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Compare fetched config with active config and output what has changed
|
||||
- (FIRRemoteConfigUpdate *)getConfigUpdateForNamespace:(NSString *)FIRNamespace {
|
||||
// TODO: handle diff in experiment metadata
|
||||
|
||||
FIRRemoteConfigUpdate *configUpdate;
|
||||
NSMutableSet<NSString *> *updatedKeys = [[NSMutableSet alloc] init];
|
||||
|
||||
NSDictionary *fetchedConfig =
|
||||
_fetchedConfig[FIRNamespace] ? _fetchedConfig[FIRNamespace] : [[NSDictionary alloc] init];
|
||||
NSDictionary *activeConfig =
|
||||
_activeConfig[FIRNamespace] ? _activeConfig[FIRNamespace] : [[NSDictionary alloc] init];
|
||||
NSDictionary *fetchedP13n = _fetchedPersonalization;
|
||||
NSDictionary *activeP13n = _activePersonalization;
|
||||
NSArray<NSDictionary *> *fetchedRolloutMetadata = _fetchedRolloutMetadata;
|
||||
NSArray<NSDictionary *> *activeRolloutMetadata = _activeRolloutMetadata;
|
||||
|
||||
// add new/updated params
|
||||
for (NSString *key in [fetchedConfig allKeys]) {
|
||||
if (activeConfig[key] == nil ||
|
||||
![[activeConfig[key] stringValue] isEqualToString:[fetchedConfig[key] stringValue]]) {
|
||||
[updatedKeys addObject:key];
|
||||
}
|
||||
}
|
||||
// add deleted params
|
||||
for (NSString *key in [activeConfig allKeys]) {
|
||||
if (fetchedConfig[key] == nil) {
|
||||
[updatedKeys addObject:key];
|
||||
}
|
||||
}
|
||||
|
||||
// add params with new/updated p13n metadata
|
||||
for (NSString *key in [fetchedP13n allKeys]) {
|
||||
if (activeP13n[key] == nil || ![activeP13n[key] isEqualToDictionary:fetchedP13n[key]]) {
|
||||
[updatedKeys addObject:key];
|
||||
}
|
||||
}
|
||||
// add params with deleted p13n metadata
|
||||
for (NSString *key in [activeP13n allKeys]) {
|
||||
if (fetchedP13n[key] == nil) {
|
||||
[updatedKeys addObject:key];
|
||||
}
|
||||
}
|
||||
|
||||
NSDictionary<NSString *, NSDictionary *> *fetchedRollouts =
|
||||
[self getParameterKeyToRolloutMetadata:fetchedRolloutMetadata];
|
||||
NSDictionary<NSString *, NSDictionary *> *activeRollouts =
|
||||
[self getParameterKeyToRolloutMetadata:activeRolloutMetadata];
|
||||
|
||||
// add params with new/updated rollout metadata
|
||||
for (NSString *key in [fetchedRollouts allKeys]) {
|
||||
if (activeRollouts[key] == nil ||
|
||||
![activeRollouts[key] isEqualToDictionary:fetchedRollouts[key]]) {
|
||||
[updatedKeys addObject:key];
|
||||
}
|
||||
}
|
||||
// add params with deleted rollout metadata
|
||||
for (NSString *key in [activeRollouts allKeys]) {
|
||||
if (fetchedRollouts[key] == nil) {
|
||||
[updatedKeys addObject:key];
|
||||
}
|
||||
}
|
||||
|
||||
configUpdate = [[FIRRemoteConfigUpdate alloc] initWithUpdatedKeys:updatedKeys];
|
||||
return configUpdate;
|
||||
}
|
||||
|
||||
- (NSDictionary<NSString *, NSDictionary *> *)getParameterKeyToRolloutMetadata:
|
||||
(NSArray<NSDictionary *> *)rolloutMetadata {
|
||||
NSMutableDictionary<NSString *, NSMutableDictionary *> *result =
|
||||
[[NSMutableDictionary alloc] init];
|
||||
for (NSDictionary *metadata in rolloutMetadata) {
|
||||
NSString *rolloutId = metadata[RCNFetchResponseKeyRolloutID];
|
||||
NSString *variantId = metadata[RCNFetchResponseKeyVariantID];
|
||||
NSArray<NSString *> *affectedKeys = metadata[RCNFetchResponseKeyAffectedParameterKeys];
|
||||
if (rolloutId && variantId && affectedKeys) {
|
||||
for (NSString *key in affectedKeys) {
|
||||
if (result[key]) {
|
||||
NSMutableDictionary *rolloutIdToVariantId = result[key];
|
||||
[rolloutIdToVariantId setValue:variantId forKey:rolloutId];
|
||||
} else {
|
||||
NSMutableDictionary *rolloutIdToVariantId = [@{rolloutId : variantId} mutableCopy];
|
||||
[result setValue:rolloutIdToVariantId forKey:key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [result copy];
|
||||
}
|
||||
|
||||
@end
|
||||
142
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigDBManager.h
generated
Normal file
142
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigDBManager.h
generated
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigContent.h"
|
||||
|
||||
typedef NS_ENUM(NSInteger, RCNUpdateOption) {
|
||||
RCNUpdateOptionApplyTime,
|
||||
RCNUpdateOptionDefaultTime,
|
||||
RCNUpdateOptionFetchStatus,
|
||||
};
|
||||
|
||||
/// Column names in metadata table
|
||||
static NSString *const RCNKeyBundleIdentifier = @"bundle_identifier";
|
||||
static NSString *const RCNKeyNamespace = @"namespace";
|
||||
static NSString *const RCNKeyFetchTime = @"fetch_time";
|
||||
static NSString *const RCNKeyDigestPerNamespace = @"digest_per_ns";
|
||||
static NSString *const RCNKeyDeviceContext = @"device_context";
|
||||
static NSString *const RCNKeyAppContext = @"app_context";
|
||||
static NSString *const RCNKeySuccessFetchTime = @"success_fetch_time";
|
||||
static NSString *const RCNKeyFailureFetchTime = @"failure_fetch_time";
|
||||
static NSString *const RCNKeyLastFetchStatus = @"last_fetch_status";
|
||||
static NSString *const RCNKeyLastFetchError = @"last_fetch_error";
|
||||
static NSString *const RCNKeyLastApplyTime = @"last_apply_time";
|
||||
static NSString *const RCNKeyLastSetDefaultsTime = @"last_set_defaults_time";
|
||||
|
||||
/// Persist config data in sqlite database on device. Managing data read/write from/to database.
|
||||
@interface RCNConfigDBManager : NSObject
|
||||
/// Shared Singleton Instance
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
/// Database Operation Completion callback.
|
||||
/// @param success Decide whether the DB operation succeeds.
|
||||
/// @param result Return operation result data.
|
||||
typedef void (^RCNDBCompletion)(BOOL success, NSDictionary *result);
|
||||
|
||||
/// Database Load Operation Completion callback.
|
||||
/// @param success Decide whether the DB operation succeeds.
|
||||
/// @param fetchedConfig Return fetchedConfig loaded from DB
|
||||
/// @param activeConfig Return activeConfig loaded from DB
|
||||
/// @param defaultConfig Return defaultConfig loaded from DB
|
||||
/// @param rolloutMetadata Return fetched and active RolloutMetadata loaded from DB
|
||||
typedef void (^RCNDBLoadCompletion)(BOOL success,
|
||||
NSDictionary *fetchedConfig,
|
||||
NSDictionary *activeConfig,
|
||||
NSDictionary *defaultConfig,
|
||||
NSDictionary *rolloutMetadata);
|
||||
|
||||
/// Returns the current version of the Remote Config database.
|
||||
+ (NSString *)remoteConfigPathForDatabase;
|
||||
|
||||
/// Load config content from main table to cached memory during app start.
|
||||
- (void)loadMainWithBundleIdentifier:(NSString *)bundleIdentifier
|
||||
completionHandler:(RCNDBLoadCompletion)handler;
|
||||
/// Load config settings for a given namespace from metadata table to cached memory during app
|
||||
/// start. Config settings include success/failure fetch times, device contenxt, app context, etc.
|
||||
- (NSDictionary *)loadMetadataWithBundleIdentifier:(NSString *)bundleIdentifier
|
||||
namespace:(NSString *)namespace;
|
||||
/// Load internal metadata from internal metadata table, such as customized HTTP connection/read
|
||||
/// timeout, throttling time interval and number limit of throttling, etc.
|
||||
/// This call needs to be blocking to ensure throttling works during apps starts.
|
||||
- (NSDictionary *)loadInternalMetadataTable;
|
||||
/// Load experiment from experiment table.
|
||||
/// @param handler The callback when reading from DB is complete.
|
||||
- (void)loadExperimentWithCompletionHandler:(RCNDBCompletion)handler;
|
||||
/// Load Personalization from table.
|
||||
/// @param handler The callback when reading from DB is complete.
|
||||
- (void)loadPersonalizationWithCompletionHandler:(RCNDBLoadCompletion)handler;
|
||||
/// Insert a record in metadata table.
|
||||
/// @param columnNameToValue The column name and its value to be inserted in metadata table.
|
||||
/// @param handler The callback.
|
||||
- (void)insertMetadataTableWithValues:(NSDictionary *)columnNameToValue
|
||||
completionHandler:(RCNDBCompletion)handler;
|
||||
/// Insert a record in main table.
|
||||
/// @param values Values to be inserted.
|
||||
- (void)insertMainTableWithValues:(NSArray *)values
|
||||
fromSource:(RCNDBSource)source
|
||||
completionHandler:(RCNDBCompletion)handler;
|
||||
/// Insert a record in internal metadata table.
|
||||
/// @param values Values to be inserted.
|
||||
- (void)insertInternalMetadataTableWithValues:(NSArray *)values
|
||||
completionHandler:(RCNDBCompletion)handler;
|
||||
/// Insert experiment data in experiment table.
|
||||
/// @param key The key of experiment data belongs to, which are defined in
|
||||
/// RCNConfigDefines.h.
|
||||
/// @param value The value that experiment.
|
||||
/// @param handler The callback.
|
||||
- (void)insertExperimentTableWithKey:(NSString *)key
|
||||
value:(NSData *)value
|
||||
completionHandler:(RCNDBCompletion)handler;
|
||||
|
||||
- (void)updateMetadataWithOption:(RCNUpdateOption)option
|
||||
namespace:(NSString *)namespace
|
||||
values:(NSArray *)values
|
||||
completionHandler:(RCNDBCompletion)handler;
|
||||
|
||||
/// Insert or update the data in Personalization config.
|
||||
- (BOOL)insertOrUpdatePersonalizationConfig:(NSDictionary *)metadata fromSource:(RCNDBSource)source;
|
||||
|
||||
/// Insert rollout metadata in rollout table.
|
||||
/// @param key Key indicating whether rollout metadata is fetched or active and defined in
|
||||
/// RCNConfigDefines.h.
|
||||
/// @param metadataList The metadata info for each rollout entry .
|
||||
/// @param handler The callback.
|
||||
- (void)insertOrUpdateRolloutTableWithKey:(NSString *)key
|
||||
value:(NSArray<NSDictionary *> *)metadataList
|
||||
completionHandler:(RCNDBCompletion)handler;
|
||||
|
||||
/// Clear the record of given namespace and package name
|
||||
/// before updating the table.
|
||||
- (void)deleteRecordFromMainTableWithNamespace:(NSString *)namespace_p
|
||||
bundleIdentifier:(NSString *)bundleIdentifier
|
||||
fromSource:(RCNDBSource)source;
|
||||
/// Remove all the records of given package name and namespace from metadata/internal metadata DB
|
||||
/// before updating new values from response.
|
||||
- (void)deleteRecordWithBundleIdentifier:(NSString *)bundlerIdentifier
|
||||
namespace:(NSString *)namespace
|
||||
isInternalDB:(BOOL)isInternalDB;
|
||||
/// Remove all the records from a config content table.
|
||||
- (void)deleteAllRecordsFromTableWithSource:(RCNDBSource)source;
|
||||
|
||||
/// Remove all the records from experiment table with given key.
|
||||
/// @param key The key of experiment data belongs to, which are defined in RCNConfigDefines.h.
|
||||
- (void)deleteExperimentTableForKey:(NSString *)key;
|
||||
|
||||
/// Returns true if this a new install of the Config database.
|
||||
- (BOOL)isNewDatabase;
|
||||
@end
|
||||
1305
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigDBManager.m
generated
Normal file
1305
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigDBManager.m
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigDefines.h
generated
Normal file
37
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigDefines.h
generated
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef RCNConfigDefines_h
|
||||
#define RCNConfigDefines_h
|
||||
|
||||
#if defined(DEBUG)
|
||||
#define RCN_MUST_NOT_BE_MAIN_THREAD() \
|
||||
do { \
|
||||
NSAssert(![NSThread isMainThread], @"Must not be executing on the main thread."); \
|
||||
} while (0);
|
||||
#else
|
||||
#define RCN_MUST_NOT_BE_MAIN_THREAD() \
|
||||
do { \
|
||||
} while (0);
|
||||
#endif
|
||||
|
||||
#define RCNExperimentTableKeyPayload "experiment_payload"
|
||||
#define RCNExperimentTableKeyMetadata "experiment_metadata"
|
||||
#define RCNExperimentTableKeyActivePayload "experiment_active_payload"
|
||||
#define RCNRolloutTableKeyActiveMetadata "active_rollout_metadata"
|
||||
#define RCNRolloutTableKeyFetchedMetadata "fetched_rollout_metadata"
|
||||
|
||||
#endif
|
||||
38
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigExperiment.h
generated
Normal file
38
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigExperiment.h
generated
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class FIRExperimentController;
|
||||
@class RCNConfigDBManager;
|
||||
|
||||
/// Handles experiment information update and persistence.
|
||||
@interface RCNConfigExperiment : NSObject
|
||||
|
||||
/// Designated initializer;
|
||||
- (nonnull instancetype)initWithDBManager:(RCNConfigDBManager *_Nullable)DBManager
|
||||
experimentController:(FIRExperimentController *_Nullable)controller
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/// Use `initWithDBManager:` instead.
|
||||
- (nonnull instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/// Update/Persist experiment information from config fetch response.
|
||||
- (void)updateExperimentsWithResponse:(NSArray<NSDictionary<NSString *, id> *> *_Nullable)response;
|
||||
|
||||
/// Update experiments to Firebase Analytics when `activateWithCompletion:` happens.
|
||||
- (void)updateExperimentsWithHandler:(nullable void (^)(NSError *_Nullable error))handler;
|
||||
@end
|
||||
200
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigExperiment.m
generated
Normal file
200
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigExperiment.m
generated
Normal file
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h"
|
||||
|
||||
#import "FirebaseABTesting/Sources/Private/FirebaseABTestingInternal.h"
|
||||
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigDefines.h"
|
||||
|
||||
static NSString *const kExperimentMetadataKeyLastStartTime = @"last_experiment_start_time";
|
||||
|
||||
static NSString *const kServiceOrigin = @"frc";
|
||||
static NSString *const kMethodNameLatestStartTime =
|
||||
@"latestExperimentStartTimestampBetweenTimestamp:andPayloads:";
|
||||
|
||||
@interface RCNConfigExperiment ()
|
||||
@property(nonatomic, strong)
|
||||
NSMutableArray<NSData *> *experimentPayloads; ///< Experiment payloads.
|
||||
@property(nonatomic, strong)
|
||||
NSMutableDictionary<NSString *, id> *experimentMetadata; ///< Experiment metadata
|
||||
@property(nonatomic, strong)
|
||||
NSMutableArray<NSData *> *activeExperimentPayloads; ///< Activated experiment payloads.
|
||||
@property(nonatomic, strong) RCNConfigDBManager *DBManager; ///< Database Manager.
|
||||
@property(nonatomic, strong) FIRExperimentController *experimentController;
|
||||
@property(nonatomic, strong) NSDateFormatter *experimentStartTimeDateFormatter;
|
||||
@end
|
||||
|
||||
@implementation RCNConfigExperiment
|
||||
/// Designated initializer
|
||||
- (instancetype)initWithDBManager:(RCNConfigDBManager *)DBManager
|
||||
experimentController:(FIRExperimentController *)controller {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_experimentPayloads = [[NSMutableArray alloc] init];
|
||||
_experimentMetadata = [[NSMutableDictionary alloc] init];
|
||||
_activeExperimentPayloads = [[NSMutableArray alloc] init];
|
||||
_experimentStartTimeDateFormatter = [[NSDateFormatter alloc] init];
|
||||
[_experimentStartTimeDateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
|
||||
[_experimentStartTimeDateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
|
||||
// Locale needs to be hardcoded. See
|
||||
// https://developer.apple.com/library/ios/#qa/qa1480/_index.html for more details.
|
||||
[_experimentStartTimeDateFormatter
|
||||
setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
|
||||
[_experimentStartTimeDateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
|
||||
|
||||
_DBManager = DBManager;
|
||||
_experimentController = controller;
|
||||
[self loadExperimentFromTable];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)loadExperimentFromTable {
|
||||
if (!_DBManager) {
|
||||
return;
|
||||
}
|
||||
__weak RCNConfigExperiment *weakSelf = self;
|
||||
RCNDBCompletion completionHandler = ^(BOOL success, NSDictionary<NSString *, id> *result) {
|
||||
RCNConfigExperiment *strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
}
|
||||
if (result[@RCNExperimentTableKeyPayload]) {
|
||||
[strongSelf->_experimentPayloads removeAllObjects];
|
||||
for (NSData *experiment in result[@RCNExperimentTableKeyPayload]) {
|
||||
NSError *error;
|
||||
id experimentPayloadJSON = [NSJSONSerialization JSONObjectWithData:experiment
|
||||
options:kNilOptions
|
||||
error:&error];
|
||||
if (!experimentPayloadJSON || error) {
|
||||
FIRLogWarning(kFIRLoggerRemoteConfig, @"I-RCN000031",
|
||||
@"Experiment payload could not be parsed as JSON.");
|
||||
} else {
|
||||
[strongSelf->_experimentPayloads addObject:experiment];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result[@RCNExperimentTableKeyMetadata]) {
|
||||
strongSelf->_experimentMetadata = [result[@RCNExperimentTableKeyMetadata] mutableCopy];
|
||||
}
|
||||
|
||||
/// Load activated experiments payload and metadata.
|
||||
if (result[@RCNExperimentTableKeyActivePayload]) {
|
||||
[strongSelf->_activeExperimentPayloads removeAllObjects];
|
||||
for (NSData *experiment in result[@RCNExperimentTableKeyActivePayload]) {
|
||||
NSError *error;
|
||||
id experimentPayloadJSON = [NSJSONSerialization JSONObjectWithData:experiment
|
||||
options:kNilOptions
|
||||
error:&error];
|
||||
if (!experimentPayloadJSON || error) {
|
||||
FIRLogWarning(kFIRLoggerRemoteConfig, @"I-RCN000031",
|
||||
@"Activated experiment payload could not be parsed as JSON.");
|
||||
} else {
|
||||
[strongSelf->_activeExperimentPayloads addObject:experiment];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
[_DBManager loadExperimentWithCompletionHandler:completionHandler];
|
||||
}
|
||||
|
||||
- (void)updateExperimentsWithResponse:(NSArray<NSDictionary<NSString *, id> *> *)response {
|
||||
// cache fetched experiment payloads.
|
||||
[_experimentPayloads removeAllObjects];
|
||||
[_DBManager deleteExperimentTableForKey:@RCNExperimentTableKeyPayload];
|
||||
|
||||
for (NSDictionary<NSString *, id> *experiment in response) {
|
||||
NSError *error;
|
||||
NSData *JSONPayload = [NSJSONSerialization dataWithJSONObject:experiment
|
||||
options:kNilOptions
|
||||
error:&error];
|
||||
if (!JSONPayload || error) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000030",
|
||||
@"Invalid experiment payload to be serialized.");
|
||||
} else {
|
||||
[_experimentPayloads addObject:JSONPayload];
|
||||
[_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyPayload
|
||||
value:JSONPayload
|
||||
completionHandler:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateExperimentsWithHandler:(void (^)(NSError *_Nullable))handler {
|
||||
FIRLifecycleEvents *lifecycleEvent = [[FIRLifecycleEvents alloc] init];
|
||||
|
||||
// Get the last experiment start time prior to the latest payload.
|
||||
NSTimeInterval lastStartTime =
|
||||
[_experimentMetadata[kExperimentMetadataKeyLastStartTime] doubleValue];
|
||||
|
||||
// Update the last experiment start time with the latest payload.
|
||||
[self updateExperimentStartTime];
|
||||
[self.experimentController
|
||||
updateExperimentsWithServiceOrigin:kServiceOrigin
|
||||
events:lifecycleEvent
|
||||
policy:ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest
|
||||
lastStartTime:lastStartTime
|
||||
payloads:_experimentPayloads
|
||||
completionHandler:handler];
|
||||
|
||||
/// Update activated experiments payload and metadata in DB.
|
||||
[self updateActiveExperimentsInDB];
|
||||
}
|
||||
|
||||
- (void)updateExperimentStartTime {
|
||||
NSTimeInterval existingLastStartTime =
|
||||
[_experimentMetadata[kExperimentMetadataKeyLastStartTime] doubleValue];
|
||||
|
||||
NSTimeInterval latestStartTime =
|
||||
[self latestStartTimeWithExistingLastStartTime:existingLastStartTime];
|
||||
|
||||
_experimentMetadata[kExperimentMetadataKeyLastStartTime] = @(latestStartTime);
|
||||
|
||||
if (![NSJSONSerialization isValidJSONObject:_experimentMetadata]) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000028",
|
||||
@"Invalid fetched experiment metadata to be serialized.");
|
||||
return;
|
||||
}
|
||||
NSError *error;
|
||||
NSData *serializedExperimentMetadata =
|
||||
[NSJSONSerialization dataWithJSONObject:_experimentMetadata
|
||||
options:NSJSONWritingPrettyPrinted
|
||||
error:&error];
|
||||
[_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyMetadata
|
||||
value:serializedExperimentMetadata
|
||||
completionHandler:nil];
|
||||
}
|
||||
|
||||
- (void)updateActiveExperimentsInDB {
|
||||
/// Put current fetched experiment payloads into activated experiment DB.
|
||||
[_activeExperimentPayloads removeAllObjects];
|
||||
[_DBManager deleteExperimentTableForKey:@RCNExperimentTableKeyActivePayload];
|
||||
for (NSData *experiment in _experimentPayloads) {
|
||||
[_activeExperimentPayloads addObject:experiment];
|
||||
[_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyActivePayload
|
||||
value:experiment
|
||||
completionHandler:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTimeInterval)latestStartTimeWithExistingLastStartTime:(NSTimeInterval)existingLastStartTime {
|
||||
return [self.experimentController
|
||||
latestExperimentStartTimestampBetweenTimestamp:existingLastStartTime
|
||||
andPayloads:_experimentPayloads];
|
||||
}
|
||||
@end
|
||||
691
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigFetch.m
generated
Normal file
691
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigFetch.m
generated
Normal file
@ -0,0 +1,691 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h"
|
||||
#import "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h"
|
||||
|
||||
#import <GoogleUtilities/GULNSData+zlib.h>
|
||||
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
|
||||
#import "FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h"
|
||||
#import "FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigContent.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNDevice.h"
|
||||
@import FirebaseRemoteConfigInterop;
|
||||
|
||||
#ifdef RCN_STAGING_SERVER
|
||||
static NSString *const kServerURLDomain =
|
||||
@"https://staging-firebaseremoteconfig.sandbox.googleapis.com";
|
||||
#else
|
||||
static NSString *const kServerURLDomain = @"https://firebaseremoteconfig.googleapis.com";
|
||||
#endif
|
||||
|
||||
static NSString *const kServerURLVersion = @"/v1";
|
||||
static NSString *const kServerURLProjects = @"/projects/";
|
||||
static NSString *const kServerURLNamespaces = @"/namespaces/";
|
||||
static NSString *const kServerURLQuery = @":fetch?";
|
||||
static NSString *const kServerURLKey = @"key=";
|
||||
static NSString *const kRequestJSONKeyAppID = @"app_id";
|
||||
|
||||
static NSString *const kHTTPMethodPost = @"POST"; ///< HTTP request method config fetch using
|
||||
static NSString *const kContentTypeHeaderName = @"Content-Type"; ///< HTTP Header Field Name
|
||||
static NSString *const kContentEncodingHeaderName =
|
||||
@"Content-Encoding"; ///< HTTP Header Field Name
|
||||
static NSString *const kAcceptEncodingHeaderName = @"Accept-Encoding"; ///< HTTP Header Field Name
|
||||
static NSString *const kETagHeaderName = @"etag"; ///< HTTP Header Field Name
|
||||
static NSString *const kIfNoneMatchETagHeaderName = @"if-none-match"; ///< HTTP Header Field Name
|
||||
static NSString *const kInstallationsAuthTokenHeaderName = @"x-goog-firebase-installations-auth";
|
||||
// Sends the bundle ID. Refer to b/130301479 for details.
|
||||
static NSString *const kiOSBundleIdentifierHeaderName =
|
||||
@"X-Ios-Bundle-Identifier"; ///< HTTP Header Field Name
|
||||
|
||||
static NSString *const kFetchTypeHeaderName =
|
||||
@"X-Firebase-RC-Fetch-Type"; ///< Custom Http header key to identify the fetch type
|
||||
static NSString *const kBaseFetchType = @"BASE"; ///< Fetch identifier for Base Fetch
|
||||
static NSString *const kRealtimeFetchType = @"REALTIME"; ///< Fetch identifier for Realtime Fetch
|
||||
|
||||
/// Config HTTP request content type proto buffer
|
||||
static NSString *const kContentTypeValueJSON = @"application/json";
|
||||
|
||||
/// HTTP status codes. Ref: https://cloud.google.com/apis/design/errors#error_retries
|
||||
static NSInteger const kRCNFetchResponseHTTPStatusCodeOK = 200;
|
||||
static NSInteger const kRCNFetchResponseHTTPStatusTooManyRequests = 429;
|
||||
static NSInteger const kRCNFetchResponseHTTPStatusCodeInternalError = 500;
|
||||
static NSInteger const kRCNFetchResponseHTTPStatusCodeServiceUnavailable = 503;
|
||||
static NSInteger const kRCNFetchResponseHTTPStatusCodeGatewayTimeout = 504;
|
||||
|
||||
#pragma mark - RCNConfig
|
||||
|
||||
@implementation RCNConfigFetch {
|
||||
RCNConfigContent *_content;
|
||||
RCNConfigSettings *_settings;
|
||||
id<FIRAnalyticsInterop> _analytics;
|
||||
RCNConfigExperiment *_experiment;
|
||||
dispatch_queue_t _lockQueue; /// Guard the read/write operation.
|
||||
NSURLSession *_fetchSession; /// Managed internally by the fetch instance.
|
||||
NSString *_FIRNamespace;
|
||||
FIROptions *_options;
|
||||
NSString *_templateVersionNumber;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
NSAssert(NO, @"Invalid initializer.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
/// Designated initializer
|
||||
- (instancetype)initWithContent:(RCNConfigContent *)content
|
||||
DBManager:(RCNConfigDBManager *)DBManager
|
||||
settings:(RCNConfigSettings *)settings
|
||||
analytics:(nullable id<FIRAnalyticsInterop>)analytics
|
||||
experiment:(RCNConfigExperiment *)experiment
|
||||
queue:(dispatch_queue_t)queue
|
||||
namespace:(NSString *)FIRNamespace
|
||||
options:(FIROptions *)options {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_FIRNamespace = FIRNamespace;
|
||||
_settings = settings;
|
||||
_analytics = analytics;
|
||||
_experiment = experiment;
|
||||
_lockQueue = queue;
|
||||
_content = content;
|
||||
_fetchSession = [self newFetchSession];
|
||||
_options = options;
|
||||
_templateVersionNumber = [self->_settings lastFetchedTemplateVersion];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Force a new NSURLSession creation for updated config.
|
||||
- (void)recreateNetworkSession {
|
||||
if (_fetchSession) {
|
||||
[_fetchSession invalidateAndCancel];
|
||||
}
|
||||
_fetchSession = [self newFetchSession];
|
||||
}
|
||||
|
||||
/// Return the current session. (Tests).
|
||||
- (NSURLSession *)currentNetworkSession {
|
||||
return _fetchSession;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_fetchSession invalidateAndCancel];
|
||||
}
|
||||
|
||||
#pragma mark - Fetch Config API
|
||||
|
||||
- (void)fetchConfigWithExpirationDuration:(NSTimeInterval)expirationDuration
|
||||
completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler {
|
||||
// Note: We expect the googleAppID to always be available.
|
||||
BOOL hasDeviceContextChanged =
|
||||
FIRRemoteConfigHasDeviceContextChanged(_settings.deviceContext, _options.googleAppID);
|
||||
|
||||
__weak RCNConfigFetch *weakSelf = self;
|
||||
dispatch_async(_lockQueue, ^{
|
||||
RCNConfigFetch *strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check whether we are outside of the minimum fetch interval.
|
||||
if (![strongSelf->_settings hasMinimumFetchIntervalElapsed:expirationDuration] &&
|
||||
!hasDeviceContextChanged) {
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000051", @"Returning cached data.");
|
||||
return [strongSelf reportCompletionOnHandler:completionHandler
|
||||
withStatus:FIRRemoteConfigFetchStatusSuccess
|
||||
withError:nil];
|
||||
}
|
||||
|
||||
// Check if a fetch is already in progress.
|
||||
if (strongSelf->_settings.isFetchInProgress) {
|
||||
// Check if we have some fetched data.
|
||||
if (strongSelf->_settings.lastFetchTimeInterval > 0) {
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000052",
|
||||
@"A fetch is already in progress. Using previous fetch results.");
|
||||
return [strongSelf reportCompletionOnHandler:completionHandler
|
||||
withStatus:strongSelf->_settings.lastFetchStatus
|
||||
withError:nil];
|
||||
} else {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000053",
|
||||
@"A fetch is already in progress. Ignoring duplicate request.");
|
||||
return [strongSelf reportCompletionOnHandler:completionHandler
|
||||
withStatus:FIRRemoteConfigFetchStatusFailure
|
||||
withError:nil];
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether cache data is within throttle limit.
|
||||
if ([strongSelf->_settings shouldThrottle] && !hasDeviceContextChanged) {
|
||||
// Must set lastFetchStatus before FailReason.
|
||||
strongSelf->_settings.lastFetchStatus = FIRRemoteConfigFetchStatusThrottled;
|
||||
strongSelf->_settings.lastFetchError = FIRRemoteConfigErrorThrottled;
|
||||
NSTimeInterval throttledEndTime = strongSelf->_settings.exponentialBackoffThrottleEndTime;
|
||||
|
||||
NSError *error =
|
||||
[NSError errorWithDomain:FIRRemoteConfigErrorDomain
|
||||
code:FIRRemoteConfigErrorThrottled
|
||||
userInfo:@{
|
||||
FIRRemoteConfigThrottledEndTimeInSecondsKey : @(throttledEndTime)
|
||||
}];
|
||||
return [strongSelf reportCompletionOnHandler:completionHandler
|
||||
withStatus:strongSelf->_settings.lastFetchStatus
|
||||
withError:error];
|
||||
}
|
||||
strongSelf->_settings.isFetchInProgress = YES;
|
||||
NSString *fetchTypeHeader = [NSString stringWithFormat:@"%@/1", kBaseFetchType];
|
||||
[strongSelf refreshInstallationsTokenWithFetchHeader:fetchTypeHeader
|
||||
completionHandler:completionHandler
|
||||
updateCompletionHandler:nil];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Fetch helpers
|
||||
|
||||
- (void)realtimeFetchConfigWithNoExpirationDuration:(NSInteger)fetchAttemptNumber
|
||||
completionHandler:(RCNConfigFetchCompletion)completionHandler {
|
||||
// Note: We expect the googleAppID to always be available.
|
||||
BOOL hasDeviceContextChanged =
|
||||
FIRRemoteConfigHasDeviceContextChanged(_settings.deviceContext, _options.googleAppID);
|
||||
|
||||
__weak RCNConfigFetch *weakSelf = self;
|
||||
dispatch_async(_lockQueue, ^{
|
||||
RCNConfigFetch *strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
}
|
||||
// Check whether cache data is within throttle limit.
|
||||
if ([strongSelf->_settings shouldThrottle] && !hasDeviceContextChanged) {
|
||||
// Must set lastFetchStatus before FailReason.
|
||||
strongSelf->_settings.lastFetchStatus = FIRRemoteConfigFetchStatusThrottled;
|
||||
strongSelf->_settings.lastFetchError = FIRRemoteConfigErrorThrottled;
|
||||
NSTimeInterval throttledEndTime = strongSelf->_settings.exponentialBackoffThrottleEndTime;
|
||||
|
||||
NSError *error =
|
||||
[NSError errorWithDomain:FIRRemoteConfigErrorDomain
|
||||
code:FIRRemoteConfigErrorThrottled
|
||||
userInfo:@{
|
||||
FIRRemoteConfigThrottledEndTimeInSecondsKey : @(throttledEndTime)
|
||||
}];
|
||||
return [strongSelf reportCompletionWithStatus:FIRRemoteConfigFetchStatusFailure
|
||||
withUpdate:nil
|
||||
withError:error
|
||||
completionHandler:nil
|
||||
updateCompletionHandler:completionHandler];
|
||||
}
|
||||
strongSelf->_settings.isFetchInProgress = YES;
|
||||
|
||||
NSString *fetchTypeHeader =
|
||||
[NSString stringWithFormat:@"%@/%ld", kRealtimeFetchType, (long)fetchAttemptNumber];
|
||||
[strongSelf refreshInstallationsTokenWithFetchHeader:fetchTypeHeader
|
||||
completionHandler:nil
|
||||
updateCompletionHandler:completionHandler];
|
||||
});
|
||||
}
|
||||
|
||||
- (NSString *)FIRAppNameFromFullyQualifiedNamespace {
|
||||
return [[_FIRNamespace componentsSeparatedByString:@":"] lastObject];
|
||||
}
|
||||
/// Refresh installation ID token before fetching config. installation ID is now mandatory for fetch
|
||||
/// requests to work.(b/14751422).
|
||||
- (void)refreshInstallationsTokenWithFetchHeader:(NSString *)fetchTypeHeader
|
||||
completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler
|
||||
updateCompletionHandler:(RCNConfigFetchCompletion)updateCompletionHandler {
|
||||
FIRInstallations *installations = [FIRInstallations
|
||||
installationsWithApp:[FIRApp appNamed:[self FIRAppNameFromFullyQualifiedNamespace]]];
|
||||
if (!installations || !_options.GCMSenderID) {
|
||||
NSString *errorDescription = @"Failed to get GCMSenderID";
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000074", @"%@",
|
||||
[NSString stringWithFormat:@"%@", errorDescription]);
|
||||
self->_settings.isFetchInProgress = NO;
|
||||
return [self
|
||||
reportCompletionOnHandler:completionHandler
|
||||
withStatus:FIRRemoteConfigFetchStatusFailure
|
||||
withError:[NSError errorWithDomain:FIRRemoteConfigErrorDomain
|
||||
code:FIRRemoteConfigErrorInternalError
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : errorDescription
|
||||
}]];
|
||||
}
|
||||
|
||||
__weak RCNConfigFetch *weakSelf = self;
|
||||
FIRInstallationsTokenHandler installationsTokenHandler = ^(
|
||||
FIRInstallationsAuthTokenResult *tokenResult, NSError *error) {
|
||||
RCNConfigFetch *strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tokenResult || !tokenResult.authToken || error) {
|
||||
NSString *errorDescription =
|
||||
[NSString stringWithFormat:@"Failed to get installations token. Error : %@.", error];
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000073", @"%@",
|
||||
[NSString stringWithFormat:@"%@", errorDescription]);
|
||||
strongSelf->_settings.isFetchInProgress = NO;
|
||||
|
||||
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
|
||||
userInfo[NSLocalizedDescriptionKey] = errorDescription;
|
||||
userInfo[NSUnderlyingErrorKey] = error.userInfo[NSUnderlyingErrorKey];
|
||||
|
||||
return [strongSelf
|
||||
reportCompletionOnHandler:completionHandler
|
||||
withStatus:FIRRemoteConfigFetchStatusFailure
|
||||
withError:[NSError errorWithDomain:FIRRemoteConfigErrorDomain
|
||||
code:FIRRemoteConfigErrorInternalError
|
||||
userInfo:userInfo]];
|
||||
}
|
||||
|
||||
// We have a valid token. Get the backing installationID.
|
||||
[installations installationIDWithCompletion:^(NSString *_Nullable identifier,
|
||||
NSError *_Nullable error) {
|
||||
RCNConfigFetch *strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Dispatch to the RC serial queue to update settings on the queue.
|
||||
dispatch_async(strongSelf->_lockQueue, ^{
|
||||
RCNConfigFetch *strongSelfQueue = weakSelf;
|
||||
if (strongSelfQueue == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update config settings with the IID and token.
|
||||
strongSelfQueue->_settings.configInstallationsToken = tokenResult.authToken;
|
||||
strongSelfQueue->_settings.configInstallationsIdentifier = identifier;
|
||||
|
||||
if (!identifier || error) {
|
||||
NSString *errorDescription =
|
||||
[NSString stringWithFormat:@"Error getting iid : %@.", error];
|
||||
|
||||
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
|
||||
userInfo[NSLocalizedDescriptionKey] = errorDescription;
|
||||
userInfo[NSUnderlyingErrorKey] = error.userInfo[NSUnderlyingErrorKey];
|
||||
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000055", @"%@",
|
||||
[NSString stringWithFormat:@"%@", errorDescription]);
|
||||
strongSelfQueue->_settings.isFetchInProgress = NO;
|
||||
return [strongSelfQueue
|
||||
reportCompletionOnHandler:completionHandler
|
||||
withStatus:FIRRemoteConfigFetchStatusFailure
|
||||
withError:[NSError errorWithDomain:FIRRemoteConfigErrorDomain
|
||||
code:FIRRemoteConfigErrorInternalError
|
||||
userInfo:userInfo]];
|
||||
}
|
||||
|
||||
FIRLogInfo(kFIRLoggerRemoteConfig, @"I-RCN000022", @"Success to get iid : %@.",
|
||||
strongSelfQueue->_settings.configInstallationsIdentifier);
|
||||
[strongSelf doFetchCall:fetchTypeHeader
|
||||
completionHandler:completionHandler
|
||||
updateCompletionHandler:updateCompletionHandler];
|
||||
});
|
||||
}];
|
||||
};
|
||||
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000039", @"Starting requesting token.");
|
||||
[installations authTokenWithCompletion:installationsTokenHandler];
|
||||
}
|
||||
|
||||
- (void)doFetchCall:(NSString *)fetchTypeHeader
|
||||
completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler
|
||||
updateCompletionHandler:(RCNConfigFetchCompletion)updateCompletionHandler {
|
||||
[self getAnalyticsUserPropertiesWithCompletionHandler:^(NSDictionary *userProperties) {
|
||||
dispatch_async(self->_lockQueue, ^{
|
||||
[self fetchWithUserProperties:userProperties
|
||||
fetchTypeHeader:fetchTypeHeader
|
||||
completionHandler:completionHandler
|
||||
updateCompletionHandler:updateCompletionHandler];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)getAnalyticsUserPropertiesWithCompletionHandler:
|
||||
(FIRAInteropUserPropertiesCallback)completionHandler {
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000060", @"Fetch with user properties completed.");
|
||||
id<FIRAnalyticsInterop> analytics = self->_analytics;
|
||||
if (analytics == nil) {
|
||||
completionHandler(@{});
|
||||
} else {
|
||||
[analytics getUserPropertiesWithCallback:completionHandler];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reportCompletionOnHandler:(FIRRemoteConfigFetchCompletion)completionHandler
|
||||
withStatus:(FIRRemoteConfigFetchStatus)status
|
||||
withError:(NSError *)error {
|
||||
[self reportCompletionWithStatus:status
|
||||
withUpdate:nil
|
||||
withError:error
|
||||
completionHandler:completionHandler
|
||||
updateCompletionHandler:nil];
|
||||
}
|
||||
|
||||
- (void)reportCompletionWithStatus:(FIRRemoteConfigFetchStatus)status
|
||||
withUpdate:(FIRRemoteConfigUpdate *)update
|
||||
withError:(NSError *)error
|
||||
completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler
|
||||
updateCompletionHandler:(RCNConfigFetchCompletion)updateCompletionHandler {
|
||||
if (completionHandler) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
completionHandler(status, error);
|
||||
});
|
||||
}
|
||||
// if completion handler expects a config update response
|
||||
if (updateCompletionHandler) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
updateCompletionHandler(status, update, error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)fetchWithUserProperties:(NSDictionary *)userProperties
|
||||
fetchTypeHeader:(NSString *)fetchTypeHeader
|
||||
completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler
|
||||
updateCompletionHandler:(RCNConfigFetchCompletion)updateCompletionHandler {
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000061", @"Fetch with user properties initiated.");
|
||||
|
||||
NSString *postRequestString = [_settings nextRequestWithUserProperties:userProperties];
|
||||
|
||||
// Get POST request content.
|
||||
NSData *content = [postRequestString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSError *compressionError;
|
||||
NSData *compressedContent = [NSData gul_dataByGzippingData:content error:&compressionError];
|
||||
if (compressionError) {
|
||||
NSString *errString = [NSString stringWithFormat:@"Failed to compress the config request."];
|
||||
FIRLogWarning(kFIRLoggerRemoteConfig, @"I-RCN000033", @"%@", errString);
|
||||
NSError *error = [NSError errorWithDomain:FIRRemoteConfigErrorDomain
|
||||
code:FIRRemoteConfigErrorInternalError
|
||||
userInfo:@{NSLocalizedDescriptionKey : errString}];
|
||||
|
||||
self->_settings.isFetchInProgress = NO;
|
||||
return [self reportCompletionWithStatus:FIRRemoteConfigFetchStatusFailure
|
||||
withUpdate:nil
|
||||
withError:error
|
||||
completionHandler:completionHandler
|
||||
updateCompletionHandler:updateCompletionHandler];
|
||||
}
|
||||
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000040", @"Start config fetch.");
|
||||
__weak RCNConfigFetch *weakSelf = self;
|
||||
RCNConfigFetcherCompletion fetcherCompletion = ^(NSData *data, NSURLResponse *response,
|
||||
NSError *error) {
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000050",
|
||||
@"config fetch completed. Error: %@ StatusCode: %ld", (error ? error : @"nil"),
|
||||
(long)[((NSHTTPURLResponse *)response) statusCode]);
|
||||
|
||||
RCNConfigFetch *fetcherCompletionSelf = weakSelf;
|
||||
if (fetcherCompletionSelf == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The fetch has completed.
|
||||
fetcherCompletionSelf->_settings.isFetchInProgress = NO;
|
||||
|
||||
dispatch_async(fetcherCompletionSelf->_lockQueue, ^{
|
||||
RCNConfigFetch *strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSInteger statusCode = [((NSHTTPURLResponse *)response) statusCode];
|
||||
|
||||
if (error || (statusCode != kRCNFetchResponseHTTPStatusCodeOK)) {
|
||||
// Update metadata about fetch failure.
|
||||
[strongSelf->_settings updateMetadataWithFetchSuccessStatus:NO templateVersion:nil];
|
||||
if (error) {
|
||||
if (strongSelf->_settings.lastFetchStatus == FIRRemoteConfigFetchStatusSuccess) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000025",
|
||||
@"RCN Fetch failure: %@. Using cached config result.", error);
|
||||
} else {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000026",
|
||||
@"RCN Fetch failure: %@. No cached config result.", error);
|
||||
}
|
||||
}
|
||||
if (statusCode != kRCNFetchResponseHTTPStatusCodeOK) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000026",
|
||||
@"RCN Fetch failure. Response http error code: %ld", (long)statusCode);
|
||||
// Response error code 429, 500, 503 will trigger exponential backoff mode.
|
||||
// TODO: check error code in helper
|
||||
if (statusCode == kRCNFetchResponseHTTPStatusTooManyRequests ||
|
||||
statusCode == kRCNFetchResponseHTTPStatusCodeInternalError ||
|
||||
statusCode == kRCNFetchResponseHTTPStatusCodeServiceUnavailable ||
|
||||
statusCode == kRCNFetchResponseHTTPStatusCodeGatewayTimeout) {
|
||||
[strongSelf->_settings updateExponentialBackoffTime];
|
||||
if ([strongSelf->_settings shouldThrottle]) {
|
||||
// Must set lastFetchStatus before FailReason.
|
||||
strongSelf->_settings.lastFetchStatus = FIRRemoteConfigFetchStatusThrottled;
|
||||
strongSelf->_settings.lastFetchError = FIRRemoteConfigErrorThrottled;
|
||||
NSTimeInterval throttledEndTime =
|
||||
strongSelf->_settings.exponentialBackoffThrottleEndTime;
|
||||
|
||||
NSError *error = [NSError
|
||||
errorWithDomain:FIRRemoteConfigErrorDomain
|
||||
code:FIRRemoteConfigErrorThrottled
|
||||
userInfo:@{
|
||||
FIRRemoteConfigThrottledEndTimeInSecondsKey : @(throttledEndTime)
|
||||
}];
|
||||
return [strongSelf reportCompletionWithStatus:strongSelf->_settings.lastFetchStatus
|
||||
withUpdate:nil
|
||||
withError:error
|
||||
completionHandler:completionHandler
|
||||
updateCompletionHandler:updateCompletionHandler];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return back the received error.
|
||||
// Must set lastFetchStatus before setting Fetch Error.
|
||||
strongSelf->_settings.lastFetchStatus = FIRRemoteConfigFetchStatusFailure;
|
||||
strongSelf->_settings.lastFetchError = FIRRemoteConfigErrorInternalError;
|
||||
NSMutableDictionary<NSErrorUserInfoKey, id> *userInfo = [NSMutableDictionary dictionary];
|
||||
userInfo[NSUnderlyingErrorKey] = error;
|
||||
userInfo[NSLocalizedDescriptionKey] =
|
||||
error.localizedDescription
|
||||
?: [NSString
|
||||
stringWithFormat:@"Internal Error. Status code: %ld", (long)statusCode];
|
||||
|
||||
return [strongSelf
|
||||
reportCompletionWithStatus:FIRRemoteConfigFetchStatusFailure
|
||||
withUpdate:nil
|
||||
withError:[NSError errorWithDomain:FIRRemoteConfigErrorDomain
|
||||
code:FIRRemoteConfigErrorInternalError
|
||||
userInfo:userInfo]
|
||||
completionHandler:completionHandler
|
||||
updateCompletionHandler:updateCompletionHandler];
|
||||
}
|
||||
|
||||
// Fetch was successful. Check if we have data.
|
||||
NSError *retError;
|
||||
if (!data) {
|
||||
FIRLogInfo(kFIRLoggerRemoteConfig, @"I-RCN000043", @"RCN Fetch: No data in fetch response");
|
||||
// There may still be a difference between fetched and active config
|
||||
FIRRemoteConfigUpdate *update =
|
||||
[strongSelf->_content getConfigUpdateForNamespace:strongSelf->_FIRNamespace];
|
||||
return [strongSelf reportCompletionWithStatus:FIRRemoteConfigFetchStatusSuccess
|
||||
withUpdate:update
|
||||
withError:nil
|
||||
completionHandler:completionHandler
|
||||
updateCompletionHandler:updateCompletionHandler];
|
||||
}
|
||||
|
||||
// Config fetch succeeded.
|
||||
// JSONObjectWithData is always expected to return an NSDictionary in our case
|
||||
NSMutableDictionary *fetchedConfig =
|
||||
[NSJSONSerialization JSONObjectWithData:data
|
||||
options:NSJSONReadingMutableContainers
|
||||
error:&retError];
|
||||
if (retError) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000042",
|
||||
@"RCN Fetch failure: %@. Could not parse response data as JSON", error);
|
||||
}
|
||||
|
||||
// Check and log if we received an error from the server
|
||||
if (fetchedConfig && fetchedConfig.count == 1 && fetchedConfig[RCNFetchResponseKeyError]) {
|
||||
NSString *errStr = [NSString stringWithFormat:@"RCN Fetch Failure: Server returned error:"];
|
||||
NSDictionary *errDict = fetchedConfig[RCNFetchResponseKeyError];
|
||||
if (errDict[RCNFetchResponseKeyErrorCode]) {
|
||||
errStr = [errStr
|
||||
stringByAppendingString:[NSString
|
||||
stringWithFormat:@"code: %@",
|
||||
errDict[RCNFetchResponseKeyErrorCode]]];
|
||||
}
|
||||
if (errDict[RCNFetchResponseKeyErrorStatus]) {
|
||||
errStr = [errStr stringByAppendingString:
|
||||
[NSString stringWithFormat:@". Status: %@",
|
||||
errDict[RCNFetchResponseKeyErrorStatus]]];
|
||||
}
|
||||
if (errDict[RCNFetchResponseKeyErrorMessage]) {
|
||||
errStr =
|
||||
[errStr stringByAppendingString:
|
||||
[NSString stringWithFormat:@". Message: %@",
|
||||
errDict[RCNFetchResponseKeyErrorMessage]]];
|
||||
}
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000044", @"%@.", errStr);
|
||||
NSError *error = [NSError errorWithDomain:FIRRemoteConfigErrorDomain
|
||||
code:FIRRemoteConfigErrorInternalError
|
||||
userInfo:@{NSLocalizedDescriptionKey : errStr}];
|
||||
return [strongSelf reportCompletionWithStatus:FIRRemoteConfigFetchStatusFailure
|
||||
withUpdate:nil
|
||||
withError:error
|
||||
completionHandler:completionHandler
|
||||
updateCompletionHandler:updateCompletionHandler];
|
||||
}
|
||||
|
||||
// Add the fetched config to the database.
|
||||
if (fetchedConfig) {
|
||||
// Update config content to cache and DB.
|
||||
[strongSelf->_content updateConfigContentWithResponse:fetchedConfig
|
||||
forNamespace:strongSelf->_FIRNamespace];
|
||||
// Update experiments only for 3p namespace
|
||||
NSString *namespace = [strongSelf->_FIRNamespace
|
||||
substringToIndex:[strongSelf->_FIRNamespace rangeOfString:@":"].location];
|
||||
if ([namespace isEqualToString:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform]) {
|
||||
[strongSelf->_experiment updateExperimentsWithResponse:
|
||||
fetchedConfig[RCNFetchResponseKeyExperimentDescriptions]];
|
||||
}
|
||||
|
||||
strongSelf->_templateVersionNumber = [strongSelf getTemplateVersionNumber:fetchedConfig];
|
||||
} else {
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000063",
|
||||
@"Empty response with no fetched config.");
|
||||
}
|
||||
|
||||
// We had a successful fetch. Update the current eTag in settings if different.
|
||||
NSString *latestETag = ((NSHTTPURLResponse *)response).allHeaderFields[kETagHeaderName];
|
||||
if (!strongSelf->_settings.lastETag ||
|
||||
!([strongSelf->_settings.lastETag isEqualToString:latestETag])) {
|
||||
strongSelf->_settings.lastETag = latestETag;
|
||||
}
|
||||
// Compute config update after successful fetch
|
||||
FIRRemoteConfigUpdate *update =
|
||||
[strongSelf->_content getConfigUpdateForNamespace:strongSelf->_FIRNamespace];
|
||||
|
||||
[strongSelf->_settings
|
||||
updateMetadataWithFetchSuccessStatus:YES
|
||||
templateVersion:strongSelf->_templateVersionNumber];
|
||||
return [strongSelf reportCompletionWithStatus:FIRRemoteConfigFetchStatusSuccess
|
||||
withUpdate:update
|
||||
withError:nil
|
||||
completionHandler:completionHandler
|
||||
updateCompletionHandler:updateCompletionHandler];
|
||||
});
|
||||
};
|
||||
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000061", @"Making remote config fetch.");
|
||||
|
||||
NSURLSessionDataTask *dataTask = [self URLSessionDataTaskWithContent:compressedContent
|
||||
fetchTypeHeader:fetchTypeHeader
|
||||
completionHandler:fetcherCompletion];
|
||||
[dataTask resume];
|
||||
}
|
||||
|
||||
- (NSString *)constructServerURL {
|
||||
NSString *serverURLStr = [[NSString alloc] initWithString:kServerURLDomain];
|
||||
serverURLStr = [serverURLStr stringByAppendingString:kServerURLVersion];
|
||||
serverURLStr = [serverURLStr stringByAppendingString:kServerURLProjects];
|
||||
serverURLStr = [serverURLStr stringByAppendingString:_options.projectID];
|
||||
serverURLStr = [serverURLStr stringByAppendingString:kServerURLNamespaces];
|
||||
|
||||
// Get the namespace from the fully qualified namespace string of "namespace:FIRAppName".
|
||||
NSString *namespace =
|
||||
[_FIRNamespace substringToIndex:[_FIRNamespace rangeOfString:@":"].location];
|
||||
serverURLStr = [serverURLStr stringByAppendingString:namespace];
|
||||
serverURLStr = [serverURLStr stringByAppendingString:kServerURLQuery];
|
||||
|
||||
if (_options.APIKey) {
|
||||
serverURLStr = [serverURLStr stringByAppendingString:kServerURLKey];
|
||||
serverURLStr = [serverURLStr stringByAppendingString:_options.APIKey];
|
||||
} else {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000071",
|
||||
@"Missing `APIKey` from `FirebaseOptions`, please ensure the configured "
|
||||
@"`FirebaseApp` is configured with `FirebaseOptions` that contains an `APIKey`.");
|
||||
}
|
||||
|
||||
return serverURLStr;
|
||||
}
|
||||
|
||||
- (NSURLSession *)newFetchSession {
|
||||
NSURLSessionConfiguration *config =
|
||||
[[NSURLSessionConfiguration defaultSessionConfiguration] copy];
|
||||
config.timeoutIntervalForRequest = _settings.fetchTimeout;
|
||||
config.timeoutIntervalForResource = _settings.fetchTimeout;
|
||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
|
||||
return session;
|
||||
}
|
||||
|
||||
- (NSURLSessionDataTask *)URLSessionDataTaskWithContent:(NSData *)content
|
||||
fetchTypeHeader:(NSString *)fetchTypeHeader
|
||||
completionHandler:
|
||||
(RCNConfigFetcherCompletion)fetcherCompletion {
|
||||
NSURL *URL = [NSURL URLWithString:[self constructServerURL]];
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000046", @"%@",
|
||||
[NSString stringWithFormat:@"Making config request: %@", [URL absoluteString]]);
|
||||
|
||||
NSTimeInterval timeoutInterval = _fetchSession.configuration.timeoutIntervalForResource;
|
||||
NSMutableURLRequest *URLRequest =
|
||||
[[NSMutableURLRequest alloc] initWithURL:URL
|
||||
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
|
||||
timeoutInterval:timeoutInterval];
|
||||
URLRequest.HTTPMethod = kHTTPMethodPost;
|
||||
[URLRequest setValue:kContentTypeValueJSON forHTTPHeaderField:kContentTypeHeaderName];
|
||||
[URLRequest setValue:_settings.configInstallationsToken
|
||||
forHTTPHeaderField:kInstallationsAuthTokenHeaderName];
|
||||
[URLRequest setValue:[[NSBundle mainBundle] bundleIdentifier]
|
||||
forHTTPHeaderField:kiOSBundleIdentifierHeaderName];
|
||||
[URLRequest setValue:@"gzip" forHTTPHeaderField:kContentEncodingHeaderName];
|
||||
[URLRequest setValue:@"gzip" forHTTPHeaderField:kAcceptEncodingHeaderName];
|
||||
[URLRequest setValue:fetchTypeHeader forHTTPHeaderField:kFetchTypeHeaderName];
|
||||
// Set the eTag from the last successful fetch, if available.
|
||||
if (_settings.lastETag) {
|
||||
[URLRequest setValue:_settings.lastETag forHTTPHeaderField:kIfNoneMatchETagHeaderName];
|
||||
}
|
||||
[URLRequest setHTTPBody:content];
|
||||
|
||||
return [_fetchSession dataTaskWithRequest:URLRequest completionHandler:fetcherCompletion];
|
||||
}
|
||||
|
||||
- (NSString *)getTemplateVersionNumber:(NSDictionary *)fetchedConfig {
|
||||
if (fetchedConfig != nil && [fetchedConfig objectForKey:RCNFetchResponseKeyTemplateVersion] &&
|
||||
[[fetchedConfig objectForKey:RCNFetchResponseKeyTemplateVersion]
|
||||
isKindOfClass:[NSString class]]) {
|
||||
return (NSString *)[fetchedConfig objectForKey:RCNFetchResponseKeyTemplateVersion];
|
||||
}
|
||||
|
||||
return @"0";
|
||||
}
|
||||
|
||||
@end
|
||||
40
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigRealtime.h
generated
Normal file
40
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigRealtime.h
generated
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2022 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h"
|
||||
#import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
|
||||
|
||||
@class RCNConfigSettings;
|
||||
|
||||
@interface RCNConfigRealtime : NSObject <NSURLSessionDataDelegate>
|
||||
|
||||
/// Completion handler invoked by config update methods when they get a response from the server.
|
||||
///
|
||||
/// @param error Error message on failure.
|
||||
typedef void (^RCNConfigUpdateCompletion)(FIRRemoteConfigUpdate *_Nullable configUpdate,
|
||||
NSError *_Nullable error);
|
||||
|
||||
- (instancetype _Nonnull)init:(RCNConfigFetch *_Nonnull)configFetch
|
||||
settings:(RCNConfigSettings *_Nonnull)settings
|
||||
namespace:(NSString *_Nonnull)namespace
|
||||
options:(FIROptions *_Nonnull)options;
|
||||
|
||||
- (FIRConfigUpdateListenerRegistration *_Nonnull)addConfigUpdateListener:
|
||||
(RCNConfigUpdateCompletion _Nonnull)listener;
|
||||
- (void)removeConfigUpdateListener:(RCNConfigUpdateCompletion _Nonnull)listener;
|
||||
|
||||
@end
|
||||
732
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigRealtime.m
generated
Normal file
732
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigRealtime.m
generated
Normal file
@ -0,0 +1,732 @@
|
||||
/*
|
||||
* Copyright 2022 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigRealtime.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <GoogleUtilities/GULNSData+zlib.h>
|
||||
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
|
||||
#import "FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h"
|
||||
#import "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h"
|
||||
#import "FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNDevice.h"
|
||||
|
||||
/// URL params
|
||||
static NSString *const kServerURLDomain = @"https://firebaseremoteconfigrealtime.googleapis.com";
|
||||
static NSString *const kServerURLVersion = @"/v1";
|
||||
static NSString *const kServerURLProjects = @"/projects/";
|
||||
static NSString *const kServerURLNamespaces = @"/namespaces/";
|
||||
static NSString *const kServerURLQuery = @":streamFetchInvalidations?";
|
||||
static NSString *const kServerURLKey = @"key=";
|
||||
|
||||
/// Realtime API enablement
|
||||
static NSString *const kServerForbiddenStatusCode = @"\"code\": 403";
|
||||
|
||||
/// Header names
|
||||
static NSString *const kHTTPMethodPost = @"POST"; ///< HTTP request method config fetch using
|
||||
static NSString *const kContentTypeHeaderName = @"Content-Type"; ///< HTTP Header Field Name
|
||||
static NSString *const kContentEncodingHeaderName =
|
||||
@"Content-Encoding"; ///< HTTP Header Field Name
|
||||
static NSString *const kAcceptEncodingHeaderName = @"Accept"; ///< HTTP Header Field Name
|
||||
static NSString *const kETagHeaderName = @"etag"; ///< HTTP Header Field Name
|
||||
static NSString *const kIfNoneMatchETagHeaderName = @"if-none-match"; ///< HTTP Header Field Name
|
||||
static NSString *const kInstallationsAuthTokenHeaderName = @"x-goog-firebase-installations-auth";
|
||||
// Sends the bundle ID. Refer to b/130301479 for details.
|
||||
static NSString *const kiOSBundleIdentifierHeaderName =
|
||||
@"X-Ios-Bundle-Identifier"; ///< HTTP Header Field Name
|
||||
|
||||
/// Retryable HTTP status code.
|
||||
static NSInteger const kRCNFetchResponseHTTPStatusOk = 200;
|
||||
static NSInteger const kRCNFetchResponseHTTPStatusClientTimeout = 429;
|
||||
static NSInteger const kRCNFetchResponseHTTPStatusTooManyRequests = 429;
|
||||
static NSInteger const kRCNFetchResponseHTTPStatusCodeBadGateway = 502;
|
||||
static NSInteger const kRCNFetchResponseHTTPStatusCodeServiceUnavailable = 503;
|
||||
static NSInteger const kRCNFetchResponseHTTPStatusCodeGatewayTimeout = 504;
|
||||
|
||||
/// Invalidation message field names.
|
||||
static NSString *const kTemplateVersionNumberKey = @"latestTemplateVersionNumber";
|
||||
static NSString *const kIsFeatureDisabled = @"featureDisabled";
|
||||
|
||||
static NSTimeInterval gTimeoutSeconds = 330;
|
||||
static NSInteger const gFetchAttempts = 3;
|
||||
|
||||
// Retry parameters
|
||||
static NSInteger const gMaxRetries = 7;
|
||||
|
||||
@interface FIRConfigUpdateListenerRegistration ()
|
||||
@property(strong, atomic, nonnull) RCNConfigUpdateCompletion completionHandler;
|
||||
@end
|
||||
|
||||
@implementation FIRConfigUpdateListenerRegistration {
|
||||
RCNConfigRealtime *_realtimeClient;
|
||||
}
|
||||
|
||||
- (instancetype)initWithClient:(RCNConfigRealtime *)realtimeClient
|
||||
completionHandler:(RCNConfigUpdateCompletion)completionHandler {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_realtimeClient = realtimeClient;
|
||||
_completionHandler = completionHandler;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)remove {
|
||||
[self->_realtimeClient removeConfigUpdateListener:_completionHandler];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface RCNConfigRealtime ()
|
||||
|
||||
@property(strong, atomic, nonnull) NSMutableSet<RCNConfigUpdateCompletion> *listeners;
|
||||
@property(strong, atomic, nonnull) dispatch_queue_t realtimeLockQueue;
|
||||
@property(strong, atomic, nonnull) NSNotificationCenter *notificationCenter;
|
||||
|
||||
@property(strong, atomic) NSURLSession *session;
|
||||
@property(strong, atomic) NSURLSessionDataTask *dataTask;
|
||||
@property(strong, atomic) NSMutableURLRequest *request;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCNConfigRealtime {
|
||||
RCNConfigFetch *_configFetch;
|
||||
RCNConfigSettings *_settings;
|
||||
FIROptions *_options;
|
||||
NSString *_namespace;
|
||||
NSInteger _remainingRetryCount;
|
||||
bool _isRequestInProgress;
|
||||
bool _isInBackground;
|
||||
bool _isRealtimeDisabled;
|
||||
}
|
||||
|
||||
- (instancetype)init:(RCNConfigFetch *)configFetch
|
||||
settings:(RCNConfigSettings *)settings
|
||||
namespace:(NSString *)namespace
|
||||
options:(FIROptions *)options {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_listeners = [[NSMutableSet alloc] init];
|
||||
_realtimeLockQueue = [RCNConfigRealtime realtimeRemoteConfigSerialQueue];
|
||||
_notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
|
||||
_configFetch = configFetch;
|
||||
_settings = settings;
|
||||
_options = options;
|
||||
_namespace = namespace;
|
||||
|
||||
_remainingRetryCount = MAX(gMaxRetries - [_settings realtimeRetryCount], 1);
|
||||
_isRequestInProgress = false;
|
||||
_isRealtimeDisabled = false;
|
||||
_isInBackground = false;
|
||||
|
||||
[self setUpHttpRequest];
|
||||
[self setUpHttpSession];
|
||||
[self backgroundChangeListener];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Singleton instance of serial queue for queuing all incoming RC calls.
|
||||
+ (dispatch_queue_t)realtimeRemoteConfigSerialQueue {
|
||||
static dispatch_once_t onceToken;
|
||||
static dispatch_queue_t realtimeRemoteConfigQueue;
|
||||
dispatch_once(&onceToken, ^{
|
||||
realtimeRemoteConfigQueue =
|
||||
dispatch_queue_create(RCNRemoteConfigQueueLabel, DISPATCH_QUEUE_SERIAL);
|
||||
});
|
||||
return realtimeRemoteConfigQueue;
|
||||
}
|
||||
|
||||
- (void)propogateErrors:(NSError *)error {
|
||||
__weak RCNConfigRealtime *weakSelf = self;
|
||||
dispatch_async(_realtimeLockQueue, ^{
|
||||
__strong RCNConfigRealtime *strongSelf = weakSelf;
|
||||
for (RCNConfigUpdateCompletion listener in strongSelf->_listeners) {
|
||||
listener(nil, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Test Only Helpers
|
||||
|
||||
// TESTING ONLY
|
||||
- (void)triggerListenerForTesting:(void (^_Nonnull)(FIRRemoteConfigUpdate *configUpdate,
|
||||
NSError *_Nullable error))listener {
|
||||
listener([[FIRRemoteConfigUpdate alloc] init], nil);
|
||||
}
|
||||
|
||||
#pragma mark - Http Helpers
|
||||
|
||||
- (NSString *)constructServerURL {
|
||||
NSString *serverURLStr = [[NSString alloc] initWithString:kServerURLDomain];
|
||||
serverURLStr = [serverURLStr stringByAppendingString:kServerURLVersion];
|
||||
serverURLStr = [serverURLStr stringByAppendingString:kServerURLProjects];
|
||||
serverURLStr = [serverURLStr stringByAppendingString:_options.GCMSenderID];
|
||||
serverURLStr = [serverURLStr stringByAppendingString:kServerURLNamespaces];
|
||||
|
||||
/// Get the namespace from the fully qualified namespace string of "namespace:FIRAppName".
|
||||
NSString *namespace = [_namespace substringToIndex:[_namespace rangeOfString:@":"].location];
|
||||
serverURLStr = [serverURLStr stringByAppendingString:namespace];
|
||||
serverURLStr = [serverURLStr stringByAppendingString:kServerURLQuery];
|
||||
if (_options.APIKey) {
|
||||
serverURLStr = [serverURLStr stringByAppendingString:kServerURLKey];
|
||||
serverURLStr = [serverURLStr stringByAppendingString:_options.APIKey];
|
||||
} else {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000071",
|
||||
@"Missing `APIKey` from `FirebaseOptions`, please ensure the configured "
|
||||
@"`FirebaseApp` is configured with `FirebaseOptions` that contains an `APIKey`.");
|
||||
}
|
||||
|
||||
return serverURLStr;
|
||||
}
|
||||
|
||||
- (NSString *)FIRAppNameFromFullyQualifiedNamespace {
|
||||
return [[_namespace componentsSeparatedByString:@":"] lastObject];
|
||||
}
|
||||
|
||||
- (void)reportCompletionOnHandler:(FIRRemoteConfigFetchCompletion)completionHandler
|
||||
withStatus:(FIRRemoteConfigFetchStatus)status
|
||||
withError:(NSError *)error {
|
||||
if (completionHandler) {
|
||||
dispatch_async(_realtimeLockQueue, ^{
|
||||
completionHandler(status, error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Refresh installation ID token before fetching config. installation ID is now mandatory for fetch
|
||||
/// requests to work.(b/14751422).
|
||||
- (void)refreshInstallationsTokenWithCompletionHandler:
|
||||
(FIRRemoteConfigFetchCompletion)completionHandler {
|
||||
FIRInstallations *installations = [FIRInstallations
|
||||
installationsWithApp:[FIRApp appNamed:[self FIRAppNameFromFullyQualifiedNamespace]]];
|
||||
if (!installations || !_options.GCMSenderID) {
|
||||
NSString *errorDescription = @"Failed to get GCMSenderID";
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000074", @"%@",
|
||||
[NSString stringWithFormat:@"%@", errorDescription]);
|
||||
return [self
|
||||
reportCompletionOnHandler:completionHandler
|
||||
withStatus:FIRRemoteConfigFetchStatusFailure
|
||||
withError:[NSError errorWithDomain:FIRRemoteConfigErrorDomain
|
||||
code:FIRRemoteConfigErrorInternalError
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : errorDescription
|
||||
}]];
|
||||
}
|
||||
|
||||
__weak RCNConfigRealtime *weakSelf = self;
|
||||
FIRInstallationsTokenHandler installationsTokenHandler = ^(
|
||||
FIRInstallationsAuthTokenResult *tokenResult, NSError *error) {
|
||||
RCNConfigRealtime *strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tokenResult || !tokenResult.authToken || error) {
|
||||
NSString *errorDescription =
|
||||
[NSString stringWithFormat:@"Failed to get installations token. Error : %@.", error];
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000073", @"%@",
|
||||
[NSString stringWithFormat:@"%@", errorDescription]);
|
||||
return [strongSelf
|
||||
reportCompletionOnHandler:completionHandler
|
||||
withStatus:FIRRemoteConfigFetchStatusFailure
|
||||
withError:[NSError errorWithDomain:FIRRemoteConfigErrorDomain
|
||||
code:FIRRemoteConfigErrorInternalError
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : errorDescription
|
||||
}]];
|
||||
}
|
||||
|
||||
/// We have a valid token. Get the backing installationID.
|
||||
[installations installationIDWithCompletion:^(NSString *_Nullable identifier,
|
||||
NSError *_Nullable error) {
|
||||
RCNConfigRealtime *strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Dispatch to the RC serial queue to update settings on the queue.
|
||||
dispatch_async(strongSelf->_realtimeLockQueue, ^{
|
||||
RCNConfigRealtime *strongSelfQueue = weakSelf;
|
||||
if (strongSelfQueue == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
/// Update config settings with the IID and token.
|
||||
strongSelfQueue->_settings.configInstallationsToken = tokenResult.authToken;
|
||||
strongSelfQueue->_settings.configInstallationsIdentifier = identifier;
|
||||
|
||||
if (!identifier || error) {
|
||||
NSString *errorDescription =
|
||||
[NSString stringWithFormat:@"Error getting iid : %@.", error];
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000055", @"%@",
|
||||
[NSString stringWithFormat:@"%@", errorDescription]);
|
||||
strongSelfQueue->_settings.isFetchInProgress = NO;
|
||||
return [strongSelfQueue
|
||||
reportCompletionOnHandler:completionHandler
|
||||
withStatus:FIRRemoteConfigFetchStatusFailure
|
||||
withError:[NSError
|
||||
errorWithDomain:FIRRemoteConfigErrorDomain
|
||||
code:FIRRemoteConfigErrorInternalError
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : errorDescription
|
||||
}]];
|
||||
}
|
||||
|
||||
FIRLogInfo(kFIRLoggerRemoteConfig, @"I-RCN000022", @"Success to get iid : %@.",
|
||||
strongSelfQueue->_settings.configInstallationsIdentifier);
|
||||
return [strongSelfQueue reportCompletionOnHandler:completionHandler
|
||||
withStatus:FIRRemoteConfigFetchStatusNoFetchYet
|
||||
withError:nil];
|
||||
});
|
||||
}];
|
||||
};
|
||||
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000039", @"Starting requesting token.");
|
||||
[installations authTokenWithCompletion:installationsTokenHandler];
|
||||
}
|
||||
|
||||
- (void)createRequestBodyWithCompletion:(void (^)(NSData *_Nonnull requestBody))completion {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
[self refreshInstallationsTokenWithCompletionHandler:^(FIRRemoteConfigFetchStatus status,
|
||||
NSError *_Nullable error) {
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
if (!strongSelf) return;
|
||||
|
||||
if (![strongSelf->_settings.configInstallationsIdentifier length]) {
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000013",
|
||||
@"Installation token retrieval failed. Realtime connection will not include "
|
||||
@"valid installations token.");
|
||||
}
|
||||
|
||||
[strongSelf.request setValue:strongSelf->_settings.configInstallationsToken
|
||||
forHTTPHeaderField:kInstallationsAuthTokenHeaderName];
|
||||
if (strongSelf->_settings.lastETag) {
|
||||
[strongSelf.request setValue:strongSelf->_settings.lastETag
|
||||
forHTTPHeaderField:kIfNoneMatchETagHeaderName];
|
||||
}
|
||||
|
||||
NSString *namespace = [strongSelf->_namespace
|
||||
substringToIndex:[strongSelf->_namespace rangeOfString:@":"].location];
|
||||
NSString *postBody = [NSString
|
||||
stringWithFormat:@"{project:'%@', namespace:'%@', lastKnownVersionNumber:'%@', appId:'%@', "
|
||||
@"sdkVersion:'%@', appInstanceId:'%@'}",
|
||||
[strongSelf->_options GCMSenderID], namespace,
|
||||
strongSelf->_configFetch.templateVersionNumber,
|
||||
strongSelf->_options.googleAppID, FIRRemoteConfigPodVersion(),
|
||||
strongSelf->_settings.configInstallationsIdentifier];
|
||||
NSData *postData = [postBody dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSError *compressionError;
|
||||
completion([NSData gul_dataByGzippingData:postData error:&compressionError]);
|
||||
}];
|
||||
}
|
||||
|
||||
/// Creates request.
|
||||
- (void)setUpHttpRequest {
|
||||
NSString *address = [self constructServerURL];
|
||||
_request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:address]
|
||||
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
|
||||
timeoutInterval:gTimeoutSeconds];
|
||||
[_request setHTTPMethod:kHTTPMethodPost];
|
||||
[_request setValue:@"application/json" forHTTPHeaderField:kContentTypeHeaderName];
|
||||
[_request setValue:@"application/json" forHTTPHeaderField:kAcceptEncodingHeaderName];
|
||||
[_request setValue:@"gzip" forHTTPHeaderField:kContentEncodingHeaderName];
|
||||
[_request setValue:@"true" forHTTPHeaderField:@"X-Google-GFE-Can-Retry"];
|
||||
[_request setValue:[_options APIKey] forHTTPHeaderField:@"X-Goog-Api-Key"];
|
||||
[_request setValue:[[NSBundle mainBundle] bundleIdentifier]
|
||||
forHTTPHeaderField:kiOSBundleIdentifierHeaderName];
|
||||
}
|
||||
|
||||
/// Makes call to create session.
|
||||
- (void)setUpHttpSession {
|
||||
NSURLSessionConfiguration *sessionConfig =
|
||||
[[NSURLSessionConfiguration defaultSessionConfiguration] copy];
|
||||
[sessionConfig setTimeoutIntervalForResource:gTimeoutSeconds];
|
||||
[sessionConfig setTimeoutIntervalForRequest:gTimeoutSeconds];
|
||||
_session = [NSURLSession sessionWithConfiguration:sessionConfig
|
||||
delegate:self
|
||||
delegateQueue:[NSOperationQueue mainQueue]];
|
||||
}
|
||||
|
||||
#pragma mark - Retry Helpers
|
||||
|
||||
- (BOOL)canMakeConnection {
|
||||
BOOL noRunningConnection =
|
||||
self->_dataTask == nil || self->_dataTask.state != NSURLSessionTaskStateRunning;
|
||||
BOOL canMakeConnection = noRunningConnection && [self->_listeners count] > 0 &&
|
||||
!self->_isInBackground && !self->_isRealtimeDisabled;
|
||||
return canMakeConnection;
|
||||
}
|
||||
|
||||
// Retry mechanism for HTTP connections
|
||||
- (void)retryHTTPConnection {
|
||||
__weak RCNConfigRealtime *weakSelf = self;
|
||||
dispatch_async(_realtimeLockQueue, ^{
|
||||
__strong RCNConfigRealtime *strongSelf = weakSelf;
|
||||
if (!strongSelf || strongSelf->_isInBackground) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ([strongSelf canMakeConnection] && strongSelf->_remainingRetryCount > 0) {
|
||||
NSTimeInterval backOffInterval = self->_settings.getRealtimeBackoffInterval;
|
||||
|
||||
strongSelf->_remainingRetryCount--;
|
||||
[strongSelf->_settings setRealtimeRetryCount:[strongSelf->_settings realtimeRetryCount] + 1];
|
||||
dispatch_time_t executionDelay =
|
||||
dispatch_time(DISPATCH_TIME_NOW, (backOffInterval * NSEC_PER_SEC));
|
||||
dispatch_after(executionDelay, strongSelf->_realtimeLockQueue, ^{
|
||||
[strongSelf beginRealtimeStream];
|
||||
});
|
||||
} else {
|
||||
NSError *error = [NSError
|
||||
errorWithDomain:FIRRemoteConfigUpdateErrorDomain
|
||||
code:FIRRemoteConfigUpdateErrorStreamError
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey :
|
||||
@"Unable to connect to the server. Check your connection and try again."
|
||||
}];
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000014", @"Cannot establish connection. Error: %@",
|
||||
error);
|
||||
[self propogateErrors:error];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)backgroundChangeListener {
|
||||
[_notificationCenter addObserver:self
|
||||
selector:@selector(isInForeground)
|
||||
name:@"UIApplicationWillEnterForegroundNotification"
|
||||
object:nil];
|
||||
|
||||
[_notificationCenter addObserver:self
|
||||
selector:@selector(isInBackground)
|
||||
name:@"UIApplicationDidEnterBackgroundNotification"
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)isInForeground {
|
||||
__weak RCNConfigRealtime *weakSelf = self;
|
||||
dispatch_async(_realtimeLockQueue, ^{
|
||||
__strong RCNConfigRealtime *strongSelf = weakSelf;
|
||||
strongSelf->_isInBackground = false;
|
||||
[strongSelf beginRealtimeStream];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)isInBackground {
|
||||
__weak RCNConfigRealtime *weakSelf = self;
|
||||
dispatch_async(_realtimeLockQueue, ^{
|
||||
__strong RCNConfigRealtime *strongSelf = weakSelf;
|
||||
[strongSelf pauseRealtimeStream];
|
||||
strongSelf->_isInBackground = true;
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Autofetch Helpers
|
||||
|
||||
- (void)fetchLatestConfig:(NSInteger)remainingAttempts targetVersion:(NSInteger)targetVersion {
|
||||
__weak RCNConfigRealtime *weakSelf = self;
|
||||
dispatch_async(_realtimeLockQueue, ^{
|
||||
__strong RCNConfigRealtime *strongSelf = weakSelf;
|
||||
NSInteger attempts = remainingAttempts - 1;
|
||||
|
||||
[strongSelf->_configFetch
|
||||
realtimeFetchConfigWithNoExpirationDuration:gFetchAttempts - attempts
|
||||
completionHandler:^(FIRRemoteConfigFetchStatus status,
|
||||
FIRRemoteConfigUpdate *update,
|
||||
NSError *error) {
|
||||
if (error != nil) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000010",
|
||||
@"Failed to retrieve config due to fetch error. "
|
||||
@"Error: %@",
|
||||
error);
|
||||
return [self propogateErrors:error];
|
||||
}
|
||||
if (status == FIRRemoteConfigFetchStatusSuccess) {
|
||||
if ([strongSelf->_configFetch.templateVersionNumber
|
||||
integerValue] >= targetVersion) {
|
||||
// only notify listeners if there is a change
|
||||
if ([update updatedKeys].count > 0) {
|
||||
for (RCNConfigUpdateCompletion listener in strongSelf
|
||||
->_listeners) {
|
||||
listener(update, nil);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FIRLogDebug(
|
||||
kFIRLoggerRemoteConfig, @"I-RCN000016",
|
||||
@"Fetched config's template version is outdated, "
|
||||
@"re-fetching");
|
||||
[strongSelf autoFetch:attempts targetVersion:targetVersion];
|
||||
}
|
||||
} else {
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000016",
|
||||
@"Fetched config's template version is "
|
||||
@"outdated, re-fetching");
|
||||
[strongSelf autoFetch:attempts targetVersion:targetVersion];
|
||||
}
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)scheduleFetch:(NSInteger)remainingAttempts targetVersion:(NSInteger)targetVersion {
|
||||
/// Needs fetch to occur between 0 - 3 seconds. Randomize to not cause DDoS alerts in backend
|
||||
dispatch_time_t executionDelay =
|
||||
dispatch_time(DISPATCH_TIME_NOW, arc4random_uniform(4) * NSEC_PER_SEC);
|
||||
dispatch_after(executionDelay, _realtimeLockQueue, ^{
|
||||
[self fetchLatestConfig:remainingAttempts targetVersion:targetVersion];
|
||||
});
|
||||
}
|
||||
|
||||
/// Perform fetch and handle developers callbacks
|
||||
- (void)autoFetch:(NSInteger)remainingAttempts targetVersion:(NSInteger)targetVersion {
|
||||
__weak RCNConfigRealtime *weakSelf = self;
|
||||
dispatch_async(_realtimeLockQueue, ^{
|
||||
__strong RCNConfigRealtime *strongSelf = weakSelf;
|
||||
if (remainingAttempts == 0) {
|
||||
NSError *error = [NSError errorWithDomain:FIRRemoteConfigUpdateErrorDomain
|
||||
code:FIRRemoteConfigUpdateErrorNotFetched
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey :
|
||||
@"Unable to fetch the latest version of the template."
|
||||
}];
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000011",
|
||||
@"Ran out of fetch attempts, cannot find target config version.");
|
||||
[self propogateErrors:error];
|
||||
return;
|
||||
}
|
||||
|
||||
[strongSelf scheduleFetch:remainingAttempts targetVersion:targetVersion];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - NSURLSession Delegates
|
||||
|
||||
- (void)evaluateStreamResponse:(NSDictionary *)response error:(NSError *)dataError {
|
||||
NSInteger updateTemplateVersion = 1;
|
||||
if (dataError == nil) {
|
||||
if ([response objectForKey:kTemplateVersionNumberKey]) {
|
||||
updateTemplateVersion = [[response objectForKey:kTemplateVersionNumberKey] integerValue];
|
||||
}
|
||||
if ([response objectForKey:kIsFeatureDisabled]) {
|
||||
self->_isRealtimeDisabled = [response objectForKey:kIsFeatureDisabled];
|
||||
}
|
||||
|
||||
if (self->_isRealtimeDisabled) {
|
||||
[self pauseRealtimeStream];
|
||||
NSError *error = [NSError
|
||||
errorWithDomain:FIRRemoteConfigUpdateErrorDomain
|
||||
code:FIRRemoteConfigUpdateErrorUnavailable
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey :
|
||||
@"The server is temporarily unavailable. Try again in a few minutes."
|
||||
}];
|
||||
[self propogateErrors:error];
|
||||
} else {
|
||||
NSInteger clientTemplateVersion = [_configFetch.templateVersionNumber integerValue];
|
||||
if (updateTemplateVersion > clientTemplateVersion) {
|
||||
[self autoFetch:gFetchAttempts targetVersion:updateTemplateVersion];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NSError *error =
|
||||
[NSError errorWithDomain:FIRRemoteConfigUpdateErrorDomain
|
||||
code:FIRRemoteConfigUpdateErrorMessageInvalid
|
||||
userInfo:@{NSLocalizedDescriptionKey : @"Unable to parse ConfigUpdate."}];
|
||||
[self propogateErrors:error];
|
||||
}
|
||||
}
|
||||
|
||||
/// Delegate to asynchronously handle every new notification that comes over the wire. Auto-fetches
|
||||
/// and runs callback for each new notification
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
dataTask:(NSURLSessionDataTask *)dataTask
|
||||
didReceiveData:(NSData *)data {
|
||||
NSError *dataError;
|
||||
NSString *strData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
|
||||
/// If response data contains the API enablement link, return the entire message to the user in
|
||||
/// the form of a error.
|
||||
if ([strData containsString:kServerForbiddenStatusCode]) {
|
||||
NSError *error = [NSError errorWithDomain:FIRRemoteConfigUpdateErrorDomain
|
||||
code:FIRRemoteConfigUpdateErrorStreamError
|
||||
userInfo:@{NSLocalizedDescriptionKey : strData}];
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000021", @"Cannot establish connection. %@", error);
|
||||
[self propogateErrors:error];
|
||||
return;
|
||||
}
|
||||
|
||||
NSRange endRange = [strData rangeOfString:@"}"];
|
||||
NSRange beginRange = [strData rangeOfString:@"{"];
|
||||
if (beginRange.location != NSNotFound && endRange.location != NSNotFound) {
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000015",
|
||||
@"Received config update message on stream.");
|
||||
NSRange msgRange =
|
||||
NSMakeRange(beginRange.location, endRange.location - beginRange.location + 1);
|
||||
strData = [strData substringWithRange:msgRange];
|
||||
data = [strData dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:data
|
||||
options:NSJSONReadingMutableContainers
|
||||
error:&dataError];
|
||||
|
||||
[self evaluateStreamResponse:response error:dataError];
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if response code is retryable
|
||||
- (bool)isStatusCodeRetryable:(NSInteger)statusCode {
|
||||
return statusCode == kRCNFetchResponseHTTPStatusClientTimeout ||
|
||||
statusCode == kRCNFetchResponseHTTPStatusTooManyRequests ||
|
||||
statusCode == kRCNFetchResponseHTTPStatusCodeServiceUnavailable ||
|
||||
statusCode == kRCNFetchResponseHTTPStatusCodeBadGateway ||
|
||||
statusCode == kRCNFetchResponseHTTPStatusCodeGatewayTimeout;
|
||||
}
|
||||
|
||||
/// Delegate to handle initial reply from the server
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
dataTask:(NSURLSessionDataTask *)dataTask
|
||||
didReceiveResponse:(NSURLResponse *)response
|
||||
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
|
||||
_isRequestInProgress = false;
|
||||
NSHTTPURLResponse *_httpURLResponse = (NSHTTPURLResponse *)response;
|
||||
NSInteger statusCode = [_httpURLResponse statusCode];
|
||||
|
||||
if (statusCode == 403) {
|
||||
completionHandler(NSURLSessionResponseAllow);
|
||||
return;
|
||||
}
|
||||
|
||||
if (statusCode != kRCNFetchResponseHTTPStatusOk) {
|
||||
[self->_settings updateRealtimeExponentialBackoffTime];
|
||||
[self pauseRealtimeStream];
|
||||
|
||||
if ([self isStatusCodeRetryable:statusCode]) {
|
||||
[self retryHTTPConnection];
|
||||
} else {
|
||||
NSError *error = [NSError
|
||||
errorWithDomain:FIRRemoteConfigUpdateErrorDomain
|
||||
code:FIRRemoteConfigUpdateErrorStreamError
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey :
|
||||
[NSString stringWithFormat:@"Unable to connect to the server. Try again in "
|
||||
@"a few minutes. Http Status code: %@",
|
||||
[@(statusCode) stringValue]]
|
||||
}];
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000021", @"Cannot establish connection. Error: %@",
|
||||
error);
|
||||
}
|
||||
} else {
|
||||
/// on success reset retry parameters
|
||||
_remainingRetryCount = gMaxRetries;
|
||||
[self->_settings setRealtimeRetryCount:0];
|
||||
}
|
||||
|
||||
completionHandler(NSURLSessionResponseAllow);
|
||||
}
|
||||
|
||||
/// Delegate to handle data task completion
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
didCompleteWithError:(NSError *)error {
|
||||
_isRequestInProgress = false;
|
||||
if (error != nil && [error code] != NSURLErrorCancelled) {
|
||||
[self->_settings updateRealtimeExponentialBackoffTime];
|
||||
}
|
||||
[self pauseRealtimeStream];
|
||||
[self retryHTTPConnection];
|
||||
}
|
||||
|
||||
/// Delegate to handle session invalidation
|
||||
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
|
||||
if (!_isRequestInProgress) {
|
||||
if (error != nil) {
|
||||
[self->_settings updateRealtimeExponentialBackoffTime];
|
||||
}
|
||||
[self pauseRealtimeStream];
|
||||
[self retryHTTPConnection];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Top level methods
|
||||
|
||||
- (void)beginRealtimeStream {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
dispatch_async(_realtimeLockQueue, ^{
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
|
||||
if (strongSelf->_settings.getRealtimeBackoffInterval > 0) {
|
||||
[strongSelf retryHTTPConnection];
|
||||
return;
|
||||
}
|
||||
|
||||
if ([strongSelf canMakeConnection]) {
|
||||
__weak __typeof(self) weakSelf = strongSelf;
|
||||
[strongSelf createRequestBodyWithCompletion:^(NSData *_Nonnull requestBody) {
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
if (!strongSelf) return;
|
||||
strongSelf->_isRequestInProgress = true;
|
||||
[strongSelf->_request setHTTPBody:requestBody];
|
||||
strongSelf->_dataTask = [strongSelf->_session dataTaskWithRequest:strongSelf->_request];
|
||||
[strongSelf->_dataTask resume];
|
||||
}];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)pauseRealtimeStream {
|
||||
__weak RCNConfigRealtime *weakSelf = self;
|
||||
dispatch_async(_realtimeLockQueue, ^{
|
||||
__strong RCNConfigRealtime *strongSelf = weakSelf;
|
||||
if (strongSelf->_dataTask != nil) {
|
||||
[strongSelf->_dataTask cancel];
|
||||
strongSelf->_dataTask = nil;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (FIRConfigUpdateListenerRegistration *)addConfigUpdateListener:
|
||||
(void (^_Nonnull)(FIRRemoteConfigUpdate *configUpdate, NSError *_Nullable error))listener {
|
||||
if (listener == nil) {
|
||||
return nil;
|
||||
}
|
||||
__block id listenerCopy = listener;
|
||||
|
||||
__weak RCNConfigRealtime *weakSelf = self;
|
||||
dispatch_async(_realtimeLockQueue, ^{
|
||||
__strong RCNConfigRealtime *strongSelf = weakSelf;
|
||||
[strongSelf->_listeners addObject:listenerCopy];
|
||||
[strongSelf beginRealtimeStream];
|
||||
});
|
||||
|
||||
return [[FIRConfigUpdateListenerRegistration alloc] initWithClient:self
|
||||
completionHandler:listenerCopy];
|
||||
}
|
||||
|
||||
- (void)removeConfigUpdateListener:(void (^_Nonnull)(FIRRemoteConfigUpdate *configUpdate,
|
||||
NSError *_Nullable error))listener {
|
||||
__weak RCNConfigRealtime *weakSelf = self;
|
||||
dispatch_async(_realtimeLockQueue, ^{
|
||||
__strong RCNConfigRealtime *strongSelf = weakSelf;
|
||||
[strongSelf->_listeners removeObject:listener];
|
||||
if (strongSelf->_listeners.count == 0) {
|
||||
[strongSelf pauseRealtimeStream];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
537
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigSettings.m
generated
Normal file
537
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigSettings.m
generated
Normal file
@ -0,0 +1,537 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h"
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNDevice.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.h"
|
||||
|
||||
#import <GoogleUtilities/GULAppEnvironmentUtil.h>
|
||||
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
|
||||
|
||||
static NSString *const kRCNGroupPrefix = @"frc.group.";
|
||||
static NSString *const kRCNUserDefaultsKeyNamelastETag = @"lastETag";
|
||||
static NSString *const kRCNUserDefaultsKeyNameLastSuccessfulFetchTime = @"lastSuccessfulFetchTime";
|
||||
static NSString *const kRCNAnalyticsFirstOpenTimePropertyName = @"_fot";
|
||||
static const int kRCNExponentialBackoffMinimumInterval = 60 * 2; // 2 mins.
|
||||
static const int kRCNExponentialBackoffMaximumInterval = 60 * 60 * 4; // 4 hours.
|
||||
|
||||
@interface RCNConfigSettings () {
|
||||
/// A list of successful fetch timestamps in seconds.
|
||||
NSMutableArray *_successFetchTimes;
|
||||
/// A list of failed fetch timestamps in seconds.
|
||||
NSMutableArray *_failureFetchTimes;
|
||||
/// Device conditions since last successful fetch from the backend. Device conditions including
|
||||
/// app
|
||||
/// version, iOS version, device localte, language, GMP project ID and Game project ID. Used for
|
||||
/// determing whether to throttle.
|
||||
NSMutableDictionary *_deviceContext;
|
||||
/// Custom variables (aka App context digest). This is the pending custom variables request before
|
||||
/// fetching.
|
||||
NSMutableDictionary *_customVariables;
|
||||
/// Cached internal metadata from internal metadata table. It contains customized information such
|
||||
/// as HTTP connection timeout, HTTP read timeout, success/failure throttling rate and time
|
||||
/// interval. Client has the default value of each parameters, they are only saved in
|
||||
/// internalMetadata if they have been customize by developers.
|
||||
NSMutableDictionary *_internalMetadata;
|
||||
/// Last fetch status.
|
||||
FIRRemoteConfigFetchStatus _lastFetchStatus;
|
||||
/// Last fetch Error.
|
||||
FIRRemoteConfigError _lastFetchError;
|
||||
/// The time of last apply timestamp.
|
||||
NSTimeInterval _lastApplyTimeInterval;
|
||||
/// The time of last setDefaults timestamp.
|
||||
NSTimeInterval _lastSetDefaultsTimeInterval;
|
||||
/// The database manager.
|
||||
RCNConfigDBManager *_DBManager;
|
||||
// The namespace for this instance.
|
||||
NSString *_FIRNamespace;
|
||||
// The Google App ID of the configured FIRApp.
|
||||
NSString *_googleAppID;
|
||||
/// The user defaults manager scoped to this RC instance of FIRApp and namespace.
|
||||
RCNUserDefaultsManager *_userDefaultsManager;
|
||||
/// The timestamp of last eTag update.
|
||||
NSTimeInterval _lastETagUpdateTime;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation RCNConfigSettings
|
||||
|
||||
- (instancetype)initWithDatabaseManager:(RCNConfigDBManager *)manager
|
||||
namespace:(NSString *)FIRNamespace
|
||||
firebaseAppName:(NSString *)appName
|
||||
googleAppID:(NSString *)googleAppID {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_FIRNamespace = FIRNamespace;
|
||||
_googleAppID = googleAppID;
|
||||
_bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
|
||||
if (!_bundleIdentifier) {
|
||||
FIRLogNotice(kFIRLoggerRemoteConfig, @"I-RCN000038",
|
||||
@"Main bundle identifier is missing. Remote Config might not work properly.");
|
||||
_bundleIdentifier = @"";
|
||||
}
|
||||
_minimumFetchInterval = RCNDefaultMinimumFetchInterval;
|
||||
_deviceContext = [[NSMutableDictionary alloc] init];
|
||||
_customVariables = [[NSMutableDictionary alloc] init];
|
||||
_successFetchTimes = [[NSMutableArray alloc] init];
|
||||
_failureFetchTimes = [[NSMutableArray alloc] init];
|
||||
_DBManager = manager;
|
||||
|
||||
_internalMetadata = [[_DBManager loadInternalMetadataTable] mutableCopy];
|
||||
if (!_internalMetadata) {
|
||||
_internalMetadata = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
_userDefaultsManager = [[RCNUserDefaultsManager alloc] initWithAppName:appName
|
||||
bundleID:_bundleIdentifier
|
||||
namespace:_FIRNamespace];
|
||||
|
||||
// Check if the config database is new. If so, clear the configs saved in userDefaults.
|
||||
if ([_DBManager isNewDatabase]) {
|
||||
FIRLogNotice(kFIRLoggerRemoteConfig, @"I-RCN000072",
|
||||
@"New config database created. Resetting user defaults.");
|
||||
[_userDefaultsManager resetUserDefaults];
|
||||
}
|
||||
|
||||
_isFetchInProgress = NO;
|
||||
_lastFetchedTemplateVersion = [_userDefaultsManager lastFetchedTemplateVersion];
|
||||
_lastActiveTemplateVersion = [_userDefaultsManager lastActiveTemplateVersion];
|
||||
_realtimeExponentialBackoffRetryInterval =
|
||||
[_userDefaultsManager currentRealtimeThrottlingRetryIntervalSeconds];
|
||||
_realtimeExponentialBackoffThrottleEndTime = [_userDefaultsManager realtimeThrottleEndTime];
|
||||
_realtimeRetryCount = [_userDefaultsManager realtimeRetryCount];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - read from / update userDefaults
|
||||
- (NSString *)lastETag {
|
||||
return [_userDefaultsManager lastETag];
|
||||
}
|
||||
|
||||
- (void)setLastETag:(NSString *)lastETag {
|
||||
[self setLastETagUpdateTime:[[NSDate date] timeIntervalSince1970]];
|
||||
[_userDefaultsManager setLastETag:lastETag];
|
||||
}
|
||||
|
||||
- (void)setLastETagUpdateTime:(NSTimeInterval)lastETagUpdateTime {
|
||||
[_userDefaultsManager setLastETagUpdateTime:lastETagUpdateTime];
|
||||
}
|
||||
|
||||
- (NSTimeInterval)lastFetchTimeInterval {
|
||||
return _userDefaultsManager.lastFetchTime;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)lastETagUpdateTime {
|
||||
return _userDefaultsManager.lastETagUpdateTime;
|
||||
}
|
||||
|
||||
// TODO: Update logic for app extensions as required.
|
||||
- (void)updateLastFetchTimeInterval:(NSTimeInterval)lastFetchTimeInterval {
|
||||
_userDefaultsManager.lastFetchTime = lastFetchTimeInterval;
|
||||
}
|
||||
|
||||
#pragma mark - load from DB
|
||||
- (NSDictionary *)loadConfigFromMetadataTable {
|
||||
NSDictionary *metadata = [[_DBManager loadMetadataWithBundleIdentifier:_bundleIdentifier
|
||||
namespace:_FIRNamespace] copy];
|
||||
if (metadata) {
|
||||
// TODO: Remove (all metadata in general) once ready to
|
||||
// migrate to user defaults completely.
|
||||
if (metadata[RCNKeyDeviceContext]) {
|
||||
self->_deviceContext = [metadata[RCNKeyDeviceContext] mutableCopy];
|
||||
}
|
||||
if (metadata[RCNKeyAppContext]) {
|
||||
self->_customVariables = [metadata[RCNKeyAppContext] mutableCopy];
|
||||
}
|
||||
if (metadata[RCNKeySuccessFetchTime]) {
|
||||
self->_successFetchTimes = [metadata[RCNKeySuccessFetchTime] mutableCopy];
|
||||
}
|
||||
if (metadata[RCNKeyFailureFetchTime]) {
|
||||
self->_failureFetchTimes = [metadata[RCNKeyFailureFetchTime] mutableCopy];
|
||||
}
|
||||
if (metadata[RCNKeyLastFetchStatus]) {
|
||||
self->_lastFetchStatus =
|
||||
(FIRRemoteConfigFetchStatus)[metadata[RCNKeyLastFetchStatus] intValue];
|
||||
}
|
||||
if (metadata[RCNKeyLastFetchError]) {
|
||||
self->_lastFetchError = (FIRRemoteConfigError)[metadata[RCNKeyLastFetchError] intValue];
|
||||
}
|
||||
if (metadata[RCNKeyLastApplyTime]) {
|
||||
self->_lastApplyTimeInterval = [metadata[RCNKeyLastApplyTime] doubleValue];
|
||||
}
|
||||
if (metadata[RCNKeyLastFetchStatus]) {
|
||||
self->_lastSetDefaultsTimeInterval = [metadata[RCNKeyLastSetDefaultsTime] doubleValue];
|
||||
}
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
|
||||
#pragma mark - update DB/cached
|
||||
|
||||
// Update internal metadata content to cache and DB.
|
||||
- (void)updateInternalContentWithResponse:(NSDictionary *)response {
|
||||
// Remove all the keys with current package name.
|
||||
[_DBManager deleteRecordWithBundleIdentifier:_bundleIdentifier
|
||||
namespace:_FIRNamespace
|
||||
isInternalDB:YES];
|
||||
|
||||
for (NSString *key in _internalMetadata.allKeys) {
|
||||
if ([key hasPrefix:_bundleIdentifier]) {
|
||||
[_internalMetadata removeObjectForKey:key];
|
||||
}
|
||||
}
|
||||
|
||||
for (NSString *entry in response) {
|
||||
NSData *val = [response[entry] dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSArray *values = @[ entry, val ];
|
||||
_internalMetadata[entry] = response[entry];
|
||||
[self updateInternalMetadataTableWithValues:values];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateInternalMetadataTableWithValues:(NSArray *)values {
|
||||
[_DBManager insertInternalMetadataTableWithValues:values completionHandler:nil];
|
||||
}
|
||||
|
||||
/// If the last fetch was not successful, update the (exponential backoff) period that we wait until
|
||||
/// fetching again. Any subsequent fetch requests will be checked and allowed only if past this
|
||||
/// throttle end time.
|
||||
- (void)updateExponentialBackoffTime {
|
||||
// If not in exponential backoff mode, reset the retry interval.
|
||||
if (_lastFetchStatus == FIRRemoteConfigFetchStatusSuccess) {
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000057",
|
||||
@"Throttling: Entering exponential backoff mode.");
|
||||
_exponentialBackoffRetryInterval = kRCNExponentialBackoffMinimumInterval;
|
||||
} else {
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000057",
|
||||
@"Throttling: Updating throttling interval.");
|
||||
// Double the retry interval until we hit the truncated exponential backoff. More info here:
|
||||
// https://cloud.google.com/storage/docs/exponential-backoff
|
||||
_exponentialBackoffRetryInterval =
|
||||
((_exponentialBackoffRetryInterval * 2) < kRCNExponentialBackoffMaximumInterval)
|
||||
? _exponentialBackoffRetryInterval * 2
|
||||
: _exponentialBackoffRetryInterval;
|
||||
}
|
||||
|
||||
// Randomize the next retry interval.
|
||||
int randomPlusMinusInterval = ((arc4random() % 2) == 0) ? -1 : 1;
|
||||
NSTimeInterval randomizedRetryInterval =
|
||||
_exponentialBackoffRetryInterval +
|
||||
(0.5 * _exponentialBackoffRetryInterval * randomPlusMinusInterval);
|
||||
_exponentialBackoffThrottleEndTime =
|
||||
[[NSDate date] timeIntervalSince1970] + randomizedRetryInterval;
|
||||
}
|
||||
|
||||
/// If the last Realtime stream attempt was not successful, update the (exponential backoff) period
|
||||
/// that we wait until trying again. Any subsequent Realtime requests will be checked and allowed
|
||||
/// only if past this throttle end time.
|
||||
- (void)updateRealtimeExponentialBackoffTime {
|
||||
// If there was only one stream attempt before, reset the retry interval.
|
||||
if (_realtimeRetryCount == 0) {
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000058",
|
||||
@"Throttling: Entering exponential Realtime backoff mode.");
|
||||
_realtimeExponentialBackoffRetryInterval = kRCNExponentialBackoffMinimumInterval;
|
||||
} else {
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000058",
|
||||
@"Throttling: Updating Realtime throttling interval.");
|
||||
// Double the retry interval until we hit the truncated exponential backoff. More info here:
|
||||
// https://cloud.google.com/storage/docs/exponential-backoff
|
||||
_realtimeExponentialBackoffRetryInterval =
|
||||
((_realtimeExponentialBackoffRetryInterval * 2) < kRCNExponentialBackoffMaximumInterval)
|
||||
? _realtimeExponentialBackoffRetryInterval * 2
|
||||
: _realtimeExponentialBackoffRetryInterval;
|
||||
}
|
||||
|
||||
// Randomize the next retry interval.
|
||||
int randomPlusMinusInterval = ((arc4random() % 2) == 0) ? -1 : 1;
|
||||
NSTimeInterval randomizedRetryInterval =
|
||||
_realtimeExponentialBackoffRetryInterval +
|
||||
(0.5 * _realtimeExponentialBackoffRetryInterval * randomPlusMinusInterval);
|
||||
_realtimeExponentialBackoffThrottleEndTime =
|
||||
[[NSDate date] timeIntervalSince1970] + randomizedRetryInterval;
|
||||
|
||||
[_userDefaultsManager setRealtimeThrottleEndTime:_realtimeExponentialBackoffThrottleEndTime];
|
||||
[_userDefaultsManager
|
||||
setCurrentRealtimeThrottlingRetryIntervalSeconds:_realtimeExponentialBackoffRetryInterval];
|
||||
}
|
||||
|
||||
- (void)setRealtimeRetryCount:(int)realtimeRetryCount {
|
||||
_realtimeRetryCount = realtimeRetryCount;
|
||||
[_userDefaultsManager setRealtimeRetryCount:_realtimeRetryCount];
|
||||
}
|
||||
|
||||
- (NSTimeInterval)getRealtimeBackoffInterval {
|
||||
NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
|
||||
return _realtimeExponentialBackoffThrottleEndTime - now;
|
||||
}
|
||||
|
||||
- (void)updateMetadataWithFetchSuccessStatus:(BOOL)fetchSuccess
|
||||
templateVersion:(NSString *)templateVersion {
|
||||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000056", @"Updating metadata with fetch result.");
|
||||
[self updateFetchTimeWithSuccessFetch:fetchSuccess];
|
||||
_lastFetchStatus =
|
||||
fetchSuccess ? FIRRemoteConfigFetchStatusSuccess : FIRRemoteConfigFetchStatusFailure;
|
||||
_lastFetchError = fetchSuccess ? FIRRemoteConfigErrorUnknown : FIRRemoteConfigErrorInternalError;
|
||||
if (fetchSuccess) {
|
||||
[self updateLastFetchTimeInterval:[[NSDate date] timeIntervalSince1970]];
|
||||
// Note: We expect the googleAppID to always be available.
|
||||
_deviceContext = FIRRemoteConfigDeviceContextWithProjectIdentifier(_googleAppID);
|
||||
_lastFetchedTemplateVersion = templateVersion;
|
||||
[_userDefaultsManager setLastFetchedTemplateVersion:templateVersion];
|
||||
}
|
||||
|
||||
[self updateMetadataTable];
|
||||
}
|
||||
|
||||
- (void)updateFetchTimeWithSuccessFetch:(BOOL)isSuccessfulFetch {
|
||||
NSTimeInterval epochTimeInterval = [[NSDate date] timeIntervalSince1970];
|
||||
if (isSuccessfulFetch) {
|
||||
[_successFetchTimes addObject:@(epochTimeInterval)];
|
||||
} else {
|
||||
[_failureFetchTimes addObject:@(epochTimeInterval)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateMetadataTable {
|
||||
[_DBManager deleteRecordWithBundleIdentifier:_bundleIdentifier
|
||||
namespace:_FIRNamespace
|
||||
isInternalDB:NO];
|
||||
NSError *error;
|
||||
// Objects to be serialized cannot be invalid.
|
||||
if (!_bundleIdentifier) {
|
||||
return;
|
||||
}
|
||||
if (![NSJSONSerialization isValidJSONObject:_customVariables]) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000028",
|
||||
@"Invalid custom variables to be serialized.");
|
||||
return;
|
||||
}
|
||||
if (![NSJSONSerialization isValidJSONObject:_deviceContext]) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000029",
|
||||
@"Invalid device context to be serialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (![NSJSONSerialization isValidJSONObject:_successFetchTimes]) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000031",
|
||||
@"Invalid success fetch times to be serialized.");
|
||||
return;
|
||||
}
|
||||
if (![NSJSONSerialization isValidJSONObject:_failureFetchTimes]) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000032",
|
||||
@"Invalid failure fetch times to be serialized.");
|
||||
return;
|
||||
}
|
||||
NSData *serializedAppContext = [NSJSONSerialization dataWithJSONObject:_customVariables
|
||||
options:NSJSONWritingPrettyPrinted
|
||||
error:&error];
|
||||
NSData *serializedDeviceContext =
|
||||
[NSJSONSerialization dataWithJSONObject:_deviceContext
|
||||
options:NSJSONWritingPrettyPrinted
|
||||
error:&error];
|
||||
// The digestPerNamespace is not used and only meant for backwards DB compatibility.
|
||||
NSData *serializedDigestPerNamespace =
|
||||
[NSJSONSerialization dataWithJSONObject:@{} options:NSJSONWritingPrettyPrinted error:&error];
|
||||
NSData *serializedSuccessTime = [NSJSONSerialization dataWithJSONObject:_successFetchTimes
|
||||
options:NSJSONWritingPrettyPrinted
|
||||
error:&error];
|
||||
NSData *serializedFailureTime = [NSJSONSerialization dataWithJSONObject:_failureFetchTimes
|
||||
options:NSJSONWritingPrettyPrinted
|
||||
error:&error];
|
||||
|
||||
if (!serializedDigestPerNamespace || !serializedDeviceContext || !serializedAppContext ||
|
||||
!serializedSuccessTime || !serializedFailureTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *columnNameToValue = @{
|
||||
RCNKeyBundleIdentifier : _bundleIdentifier,
|
||||
RCNKeyNamespace : _FIRNamespace,
|
||||
RCNKeyFetchTime : @(self.lastFetchTimeInterval),
|
||||
RCNKeyDigestPerNamespace : serializedDigestPerNamespace,
|
||||
RCNKeyDeviceContext : serializedDeviceContext,
|
||||
RCNKeyAppContext : serializedAppContext,
|
||||
RCNKeySuccessFetchTime : serializedSuccessTime,
|
||||
RCNKeyFailureFetchTime : serializedFailureTime,
|
||||
RCNKeyLastFetchStatus : [NSString stringWithFormat:@"%ld", (long)_lastFetchStatus],
|
||||
RCNKeyLastFetchError : [NSString stringWithFormat:@"%ld", (long)_lastFetchError],
|
||||
RCNKeyLastApplyTime : @(_lastApplyTimeInterval),
|
||||
RCNKeyLastSetDefaultsTime : @(_lastSetDefaultsTimeInterval)
|
||||
};
|
||||
|
||||
[_DBManager insertMetadataTableWithValues:columnNameToValue completionHandler:nil];
|
||||
}
|
||||
|
||||
- (void)updateLastActiveTemplateVersion {
|
||||
_lastActiveTemplateVersion = _lastFetchedTemplateVersion;
|
||||
[_userDefaultsManager setLastActiveTemplateVersion:_lastActiveTemplateVersion];
|
||||
}
|
||||
|
||||
#pragma mark - fetch request
|
||||
|
||||
/// Returns a fetch request with the latest device and config change.
|
||||
/// Whenever user issues a fetch api call, collect the latest request.
|
||||
- (NSString *)nextRequestWithUserProperties:(NSDictionary *)userProperties {
|
||||
// Note: We only set user properties as mentioned in the new REST API Design doc
|
||||
NSString *ret = [NSString stringWithFormat:@"{"];
|
||||
ret = [ret stringByAppendingString:[NSString stringWithFormat:@"app_instance_id:'%@'",
|
||||
_configInstallationsIdentifier]];
|
||||
ret = [ret stringByAppendingString:[NSString stringWithFormat:@", app_instance_id_token:'%@'",
|
||||
_configInstallationsToken]];
|
||||
ret = [ret stringByAppendingString:[NSString stringWithFormat:@", app_id:'%@'", _googleAppID]];
|
||||
|
||||
ret = [ret stringByAppendingString:[NSString stringWithFormat:@", country_code:'%@'",
|
||||
FIRRemoteConfigDeviceCountry()]];
|
||||
ret = [ret stringByAppendingString:[NSString stringWithFormat:@", language_code:'%@'",
|
||||
FIRRemoteConfigDeviceLocale()]];
|
||||
ret = [ret
|
||||
stringByAppendingString:[NSString stringWithFormat:@", platform_version:'%@'",
|
||||
[GULAppEnvironmentUtil systemVersion]]];
|
||||
ret = [ret stringByAppendingString:[NSString stringWithFormat:@", time_zone:'%@'",
|
||||
FIRRemoteConfigTimezone()]];
|
||||
ret = [ret stringByAppendingString:[NSString stringWithFormat:@", package_name:'%@'",
|
||||
_bundleIdentifier]];
|
||||
ret = [ret stringByAppendingString:[NSString stringWithFormat:@", app_version:'%@'",
|
||||
FIRRemoteConfigAppVersion()]];
|
||||
ret = [ret stringByAppendingString:[NSString stringWithFormat:@", app_build:'%@'",
|
||||
FIRRemoteConfigAppBuildVersion()]];
|
||||
ret = [ret stringByAppendingString:[NSString stringWithFormat:@", sdk_version:'%@'",
|
||||
FIRRemoteConfigPodVersion()]];
|
||||
|
||||
if (userProperties && userProperties.count > 0) {
|
||||
NSError *error;
|
||||
|
||||
// Extract first open time from user properties and send as a separate field
|
||||
NSNumber *firstOpenTime = userProperties[kRCNAnalyticsFirstOpenTimePropertyName];
|
||||
NSMutableDictionary *remainingUserProperties = [userProperties mutableCopy];
|
||||
if (firstOpenTime != nil) {
|
||||
NSDate *date = [NSDate dateWithTimeIntervalSince1970:([firstOpenTime longValue] / 1000)];
|
||||
NSISO8601DateFormatter *formatter = [[NSISO8601DateFormatter alloc] init];
|
||||
NSString *firstOpenTimeISOString = [formatter stringFromDate:date];
|
||||
ret = [ret stringByAppendingString:[NSString stringWithFormat:@", first_open_time:'%@'",
|
||||
firstOpenTimeISOString]];
|
||||
|
||||
[remainingUserProperties removeObjectForKey:kRCNAnalyticsFirstOpenTimePropertyName];
|
||||
}
|
||||
if (remainingUserProperties.count > 0) {
|
||||
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:remainingUserProperties
|
||||
options:0
|
||||
error:&error];
|
||||
if (!error) {
|
||||
ret = [ret
|
||||
stringByAppendingString:[NSString
|
||||
stringWithFormat:@", analytics_user_properties:%@",
|
||||
[[NSString alloc]
|
||||
initWithData:jsonData
|
||||
encoding:NSUTF8StringEncoding]]];
|
||||
}
|
||||
}
|
||||
}
|
||||
ret = [ret stringByAppendingString:@"}"];
|
||||
return ret;
|
||||
}
|
||||
|
||||
#pragma mark - getter/setter
|
||||
|
||||
- (void)setLastFetchError:(FIRRemoteConfigError)lastFetchError {
|
||||
if (_lastFetchError != lastFetchError) {
|
||||
_lastFetchError = lastFetchError;
|
||||
[_DBManager updateMetadataWithOption:RCNUpdateOptionFetchStatus
|
||||
namespace:_FIRNamespace
|
||||
values:@[ @(_lastFetchStatus), @(_lastFetchError) ]
|
||||
completionHandler:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)successFetchTimes {
|
||||
return [_successFetchTimes copy];
|
||||
}
|
||||
|
||||
- (NSArray *)failureFetchTimes {
|
||||
return [_failureFetchTimes copy];
|
||||
}
|
||||
|
||||
- (NSDictionary *)customVariables {
|
||||
return [_customVariables copy];
|
||||
}
|
||||
|
||||
- (NSDictionary *)internalMetadata {
|
||||
return [_internalMetadata copy];
|
||||
}
|
||||
|
||||
- (NSDictionary *)deviceContext {
|
||||
return [_deviceContext copy];
|
||||
}
|
||||
|
||||
- (void)setCustomVariables:(NSDictionary *)customVariables {
|
||||
_customVariables = [[NSMutableDictionary alloc] initWithDictionary:customVariables];
|
||||
[self updateMetadataTable];
|
||||
}
|
||||
|
||||
- (void)setMinimumFetchInterval:(NSTimeInterval)minimumFetchInterval {
|
||||
if (minimumFetchInterval < 0) {
|
||||
_minimumFetchInterval = 0;
|
||||
} else {
|
||||
_minimumFetchInterval = minimumFetchInterval;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFetchTimeout:(NSTimeInterval)fetchTimeout {
|
||||
if (fetchTimeout <= 0) {
|
||||
_fetchTimeout = RCNHTTPDefaultConnectionTimeout;
|
||||
} else {
|
||||
_fetchTimeout = fetchTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setLastApplyTimeInterval:(NSTimeInterval)lastApplyTimestamp {
|
||||
_lastApplyTimeInterval = lastApplyTimestamp;
|
||||
[_DBManager updateMetadataWithOption:RCNUpdateOptionApplyTime
|
||||
namespace:_FIRNamespace
|
||||
values:@[ @(lastApplyTimestamp) ]
|
||||
completionHandler:nil];
|
||||
}
|
||||
|
||||
- (void)setLastSetDefaultsTimeInterval:(NSTimeInterval)lastSetDefaultsTimestamp {
|
||||
_lastSetDefaultsTimeInterval = lastSetDefaultsTimestamp;
|
||||
[_DBManager updateMetadataWithOption:RCNUpdateOptionDefaultTime
|
||||
namespace:_FIRNamespace
|
||||
values:@[ @(lastSetDefaultsTimestamp) ]
|
||||
completionHandler:nil];
|
||||
}
|
||||
|
||||
#pragma mark Throttling
|
||||
|
||||
- (BOOL)hasMinimumFetchIntervalElapsed:(NSTimeInterval)minimumFetchInterval {
|
||||
if (self.lastFetchTimeInterval == 0) return YES;
|
||||
|
||||
// Check if last config fetch is within minimum fetch interval in seconds.
|
||||
NSTimeInterval diffInSeconds = [[NSDate date] timeIntervalSince1970] - self.lastFetchTimeInterval;
|
||||
return diffInSeconds > minimumFetchInterval;
|
||||
}
|
||||
|
||||
- (BOOL)shouldThrottle {
|
||||
NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
|
||||
return ((self.lastFetchTimeInterval > 0) &&
|
||||
(_lastFetchStatus != FIRRemoteConfigFetchStatusSuccess) &&
|
||||
(_exponentialBackoffThrottleEndTime - now > 0));
|
||||
}
|
||||
|
||||
@end
|
||||
25
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h
generated
Normal file
25
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h
generated
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
|
||||
|
||||
@interface FIRRemoteConfigValue ()
|
||||
@property(nonatomic, readwrite, assign) FIRRemoteConfigSource source;
|
||||
|
||||
/// Designated initializer.
|
||||
- (instancetype)initWithData:(NSData *)data
|
||||
source:(FIRRemoteConfigSource)source NS_DESIGNATED_INITIALIZER;
|
||||
@end
|
||||
21
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConstants3P.m
generated
Normal file
21
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNConstants3P.m
generated
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
|
||||
|
||||
/// Firebase Remote Config service default namespace.
|
||||
/// TODO(doudounan): Change to use this namespace defined in RemoteConfigInterop.
|
||||
NSString *const FIRNamespaceGoogleMobilePlatform = @"firebase";
|
||||
57
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNDevice.h
generated
Normal file
57
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNDevice.h
generated
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, RCNDeviceModel) {
|
||||
RCNDeviceModelOther,
|
||||
RCNDeviceModelPhone,
|
||||
RCNDeviceModelTablet,
|
||||
RCNDeviceModelTV,
|
||||
RCNDeviceModelGlass,
|
||||
RCNDeviceModelCar,
|
||||
RCNDeviceModelWearable,
|
||||
};
|
||||
|
||||
/// CocoaPods SDK version
|
||||
NSString *FIRRemoteConfigPodVersion(void);
|
||||
|
||||
/// App version.
|
||||
NSString *FIRRemoteConfigAppVersion(void);
|
||||
|
||||
/// App build version
|
||||
NSString *FIRRemoteConfigAppBuildVersion(void);
|
||||
|
||||
/// Device country, in lowercase.
|
||||
NSString *FIRRemoteConfigDeviceCountry(void);
|
||||
|
||||
/// Device locale, in language_country format, e.g. en_US.
|
||||
NSString *FIRRemoteConfigDeviceLocale(void);
|
||||
|
||||
/// Device subtype.
|
||||
RCNDeviceModel FIRRemoteConfigDeviceSubtype(void);
|
||||
|
||||
/// Device timezone.
|
||||
NSString *FIRRemoteConfigTimezone(void);
|
||||
|
||||
/// Update device context to the given dictionary.
|
||||
NSMutableDictionary *FIRRemoteConfigDeviceContextWithProjectIdentifier(
|
||||
NSString *GMPProjectIdentifier);
|
||||
|
||||
/// Check whether client has changed device context, including app version,
|
||||
/// iOS version, device country etc. This is used to determine whether to throttle.
|
||||
BOOL FIRRemoteConfigHasDeviceContextChanged(NSDictionary *deviceContext,
|
||||
NSString *GMPProjectIdentifier);
|
||||
241
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNDevice.m
generated
Normal file
241
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNDevice.m
generated
Normal file
@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/RCNDevice.h"
|
||||
|
||||
#import <sys/utsname.h>
|
||||
|
||||
#import <GoogleUtilities/GULAppEnvironmentUtil.h>
|
||||
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
|
||||
#import "FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
|
||||
|
||||
#define STR(x) STR_EXPAND(x)
|
||||
#define STR_EXPAND(x) #x
|
||||
|
||||
static NSString *const RCNDeviceContextKeyVersion = @"app_version";
|
||||
static NSString *const RCNDeviceContextKeyBuild = @"app_build";
|
||||
static NSString *const RCNDeviceContextKeyOSVersion = @"os_version";
|
||||
static NSString *const RCNDeviceContextKeyDeviceLocale = @"device_locale";
|
||||
static NSString *const RCNDeviceContextKeyLocaleLanguage = @"locale_language";
|
||||
static NSString *const RCNDeviceContextKeyGMPProjectIdentifier = @"GMP_project_Identifier";
|
||||
|
||||
NSString *FIRRemoteConfigAppVersion(void) {
|
||||
return [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"];
|
||||
}
|
||||
|
||||
NSString *FIRRemoteConfigAppBuildVersion(void) {
|
||||
return [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"];
|
||||
}
|
||||
|
||||
NSString *FIRRemoteConfigPodVersion(void) {
|
||||
return FIRFirebaseVersion();
|
||||
}
|
||||
|
||||
RCNDeviceModel FIRRemoteConfigDeviceSubtype(void) {
|
||||
NSString *model = [GULAppEnvironmentUtil deviceModel];
|
||||
if ([model hasPrefix:@"iPhone"]) {
|
||||
return RCNDeviceModelPhone;
|
||||
}
|
||||
if ([model isEqualToString:@"iPad"]) {
|
||||
return RCNDeviceModelTablet;
|
||||
}
|
||||
return RCNDeviceModelOther;
|
||||
}
|
||||
|
||||
NSString *FIRRemoteConfigDeviceCountry(void) {
|
||||
return [[[NSLocale currentLocale] objectForKey:NSLocaleCountryCode] lowercaseString];
|
||||
}
|
||||
|
||||
NSDictionary<NSString *, NSArray *> *FIRRemoteConfigFirebaseLocaleMap(void) {
|
||||
return @{
|
||||
// Albanian
|
||||
@"sq" : @[ @"sq_AL" ],
|
||||
// Belarusian
|
||||
@"be" : @[ @"be_BY" ],
|
||||
// Bulgarian
|
||||
@"bg" : @[ @"bg_BG" ],
|
||||
// Catalan
|
||||
@"ca" : @[ @"ca", @"ca_ES" ],
|
||||
// Croatian
|
||||
@"hr" : @[ @"hr", @"hr_HR" ],
|
||||
// Czech
|
||||
@"cs" : @[ @"cs", @"cs_CZ" ],
|
||||
// Danish
|
||||
@"da" : @[ @"da", @"da_DK" ],
|
||||
// Estonian
|
||||
@"et" : @[ @"et_EE" ],
|
||||
// Finnish
|
||||
@"fi" : @[ @"fi", @"fi_FI" ],
|
||||
// Hebrew
|
||||
@"he" : @[ @"he", @"iw_IL" ],
|
||||
// Hindi
|
||||
@"hi" : @[ @"hi_IN" ],
|
||||
// Hungarian
|
||||
@"hu" : @[ @"hu", @"hu_HU" ],
|
||||
// Icelandic
|
||||
@"is" : @[ @"is_IS" ],
|
||||
// Indonesian
|
||||
@"id" : @[ @"id", @"in_ID", @"id_ID" ],
|
||||
// Irish
|
||||
@"ga" : @[ @"ga_IE" ],
|
||||
// Korean
|
||||
@"ko" : @[ @"ko", @"ko_KR", @"ko-KR" ],
|
||||
// Latvian
|
||||
@"lv" : @[ @"lv_LV" ],
|
||||
// Lithuanian
|
||||
@"lt" : @[ @"lt_LT" ],
|
||||
// Macedonian
|
||||
@"mk" : @[ @"mk_MK" ],
|
||||
// Malay
|
||||
@"ms" : @[ @"ms_MY" ],
|
||||
// Maltese
|
||||
@"mt" : @[ @"mt_MT" ],
|
||||
// Polish
|
||||
@"pl" : @[ @"pl", @"pl_PL", @"pl-PL" ],
|
||||
// Romanian
|
||||
@"ro" : @[ @"ro", @"ro_RO" ],
|
||||
// Russian
|
||||
@"ru" : @[ @"ru_RU", @"ru", @"ru_BY", @"ru_KZ", @"ru-RU" ],
|
||||
// Slovak
|
||||
@"sk" : @[ @"sk", @"sk_SK" ],
|
||||
// Slovenian
|
||||
@"sl" : @[ @"sl_SI" ],
|
||||
// Swedish
|
||||
@"sv" : @[ @"sv", @"sv_SE", @"sv-SE" ],
|
||||
// Turkish
|
||||
@"tr" : @[ @"tr", @"tr-TR", @"tr_TR" ],
|
||||
// Ukrainian
|
||||
@"uk" : @[ @"uk", @"uk_UA" ],
|
||||
// Vietnamese
|
||||
@"vi" : @[ @"vi", @"vi_VN" ],
|
||||
// The following are groups of locales or locales that sub-divide a
|
||||
// language).
|
||||
// Arabic
|
||||
@"ar" : @[
|
||||
@"ar", @"ar_DZ", @"ar_BH", @"ar_EG", @"ar_IQ", @"ar_JO", @"ar_KW",
|
||||
@"ar_LB", @"ar_LY", @"ar_MA", @"ar_OM", @"ar_QA", @"ar_SA", @"ar_SD",
|
||||
@"ar_SY", @"ar_TN", @"ar_AE", @"ar_YE", @"ar_GB", @"ar-IQ", @"ar_US"
|
||||
],
|
||||
// Simplified Chinese
|
||||
@"zh_Hans" : @[ @"zh_CN", @"zh_SG", @"zh-Hans" ],
|
||||
// Traditional Chinese
|
||||
// Remove zh_HK until console added to the list. Otherwise client sends
|
||||
// zh_HK and server/console falls back to zh.
|
||||
// @"zh_Hant" : @[ @"zh_HK", @"zh_TW", @"zh-Hant", @"zh-HK", @"zh-TW" ],
|
||||
@"zh_Hant" : @[ @"zh_TW", @"zh-Hant", @"zh-TW" ],
|
||||
// Dutch
|
||||
@"nl" : @[ @"nl", @"nl_BE", @"nl_NL", @"nl-NL" ],
|
||||
// English
|
||||
@"en" : @[
|
||||
@"en", @"en_AU", @"en_CA", @"en_IN", @"en_IE", @"en_MT", @"en_NZ", @"en_PH",
|
||||
@"en_SG", @"en_ZA", @"en_GB", @"en_US", @"en_AE", @"en-AE", @"en_AS", @"en-AU",
|
||||
@"en_BD", @"en-CA", @"en_EG", @"en_ES", @"en_GB", @"en-GB", @"en_HK", @"en_ID",
|
||||
@"en-IN", @"en_NG", @"en-PH", @"en_PK", @"en-SG", @"en-US"
|
||||
],
|
||||
// French
|
||||
@"fr" :
|
||||
@[ @"fr", @"fr_BE", @"fr_CA", @"fr_FR", @"fr_LU", @"fr_CH", @"fr-CA", @"fr-FR", @"fr_MA" ],
|
||||
// German
|
||||
@"de" : @[ @"de", @"de_AT", @"de_DE", @"de_LU", @"de_CH", @"de-DE" ],
|
||||
// Greek
|
||||
@"el" : @[ @"el", @"el_CY", @"el_GR" ],
|
||||
// Italian
|
||||
@"it" : @[ @"it", @"it_IT", @"it_CH", @"it-IT" ],
|
||||
// Japanese
|
||||
@"ja" : @[ @"ja", @"ja_JP", @"ja_JP_JP", @"ja-JP" ],
|
||||
// Norwegian
|
||||
@"no" : @[ @"nb", @"no_NO", @"no_NO_NY", @"nb_NO" ],
|
||||
// Brazilian Portuguese
|
||||
@"pt_BR" : @[ @"pt_BR", @"pt-BR" ],
|
||||
// European Portuguese
|
||||
@"pt_PT" : @[ @"pt", @"pt_PT", @"pt-PT" ],
|
||||
// Serbian
|
||||
@"sr" : @[ @"sr_BA", @"sr_ME", @"sr_RS", @"sr_Latn_BA", @"sr_Latn_ME", @"sr_Latn_RS" ],
|
||||
// European Spanish
|
||||
@"es_ES" : @[ @"es", @"es_ES", @"es-ES" ],
|
||||
// Mexican Spanish
|
||||
@"es_MX" : @[ @"es-MX", @"es_MX", @"es_US", @"es-US" ],
|
||||
// Latin American Spanish
|
||||
@"es_419" : @[
|
||||
@"es_AR", @"es_BO", @"es_CL", @"es_CO", @"es_CR", @"es_DO", @"es_EC",
|
||||
@"es_SV", @"es_GT", @"es_HN", @"es_NI", @"es_PA", @"es_PY", @"es_PE",
|
||||
@"es_PR", @"es_UY", @"es_VE", @"es-AR", @"es-CL", @"es-CO"
|
||||
],
|
||||
// Thai
|
||||
@"th" : @[ @"th", @"th_TH", @"th_TH_TH" ],
|
||||
};
|
||||
}
|
||||
|
||||
NSArray<NSString *> *FIRRemoteConfigAppManagerLocales(void) {
|
||||
NSMutableArray *locales = [NSMutableArray array];
|
||||
NSDictionary<NSString *, NSArray *> *localesMap = FIRRemoteConfigFirebaseLocaleMap();
|
||||
for (NSString *key in localesMap) {
|
||||
[locales addObjectsFromArray:localesMap[key]];
|
||||
}
|
||||
return locales;
|
||||
}
|
||||
NSString *FIRRemoteConfigDeviceLocale(void) {
|
||||
NSArray<NSString *> *locales = FIRRemoteConfigAppManagerLocales();
|
||||
NSArray<NSString *> *preferredLocalizations =
|
||||
[NSBundle preferredLocalizationsFromArray:locales
|
||||
forPreferences:[NSLocale preferredLanguages]];
|
||||
NSString *legalDocsLanguage = [preferredLocalizations firstObject];
|
||||
// Use en as the default language
|
||||
return legalDocsLanguage ? legalDocsLanguage : @"en";
|
||||
}
|
||||
|
||||
NSString *FIRRemoteConfigTimezone(void) {
|
||||
NSTimeZone *timezone = [NSTimeZone systemTimeZone];
|
||||
return timezone.name;
|
||||
}
|
||||
|
||||
NSMutableDictionary *FIRRemoteConfigDeviceContextWithProjectIdentifier(
|
||||
NSString *GMPProjectIdentifier) {
|
||||
NSMutableDictionary *deviceContext = [[NSMutableDictionary alloc] init];
|
||||
deviceContext[RCNDeviceContextKeyVersion] = FIRRemoteConfigAppVersion();
|
||||
deviceContext[RCNDeviceContextKeyBuild] = FIRRemoteConfigAppBuildVersion();
|
||||
deviceContext[RCNDeviceContextKeyOSVersion] = [GULAppEnvironmentUtil systemVersion];
|
||||
deviceContext[RCNDeviceContextKeyDeviceLocale] = FIRRemoteConfigDeviceLocale();
|
||||
// NSDictionary setObjectForKey will fail if there's no GMP project ID, must check ahead.
|
||||
if (GMPProjectIdentifier) {
|
||||
deviceContext[RCNDeviceContextKeyGMPProjectIdentifier] = GMPProjectIdentifier;
|
||||
}
|
||||
return deviceContext;
|
||||
}
|
||||
|
||||
BOOL FIRRemoteConfigHasDeviceContextChanged(NSDictionary *deviceContext,
|
||||
NSString *GMPProjectIdentifier) {
|
||||
if (![deviceContext[RCNDeviceContextKeyVersion] isEqual:FIRRemoteConfigAppVersion()]) {
|
||||
return YES;
|
||||
}
|
||||
if (![deviceContext[RCNDeviceContextKeyBuild] isEqual:FIRRemoteConfigAppBuildVersion()]) {
|
||||
return YES;
|
||||
}
|
||||
if (![deviceContext[RCNDeviceContextKeyOSVersion]
|
||||
isEqual:[GULAppEnvironmentUtil systemVersion]]) {
|
||||
return YES;
|
||||
}
|
||||
if (![deviceContext[RCNDeviceContextKeyDeviceLocale] isEqual:FIRRemoteConfigDeviceLocale()]) {
|
||||
return YES;
|
||||
}
|
||||
// GMP project id is optional.
|
||||
if (deviceContext[RCNDeviceContextKeyGMPProjectIdentifier] &&
|
||||
![deviceContext[RCNDeviceContextKeyGMPProjectIdentifier] isEqual:GMPProjectIdentifier]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
56
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNPersonalization.h
generated
Normal file
56
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNPersonalization.h
generated
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static NSString *const kAnalyticsOriginPersonalization = @"fp";
|
||||
|
||||
static NSString *const kExternalEvent = @"personalization_assignment";
|
||||
static NSString *const kExternalRcParameterParam = @"arm_key";
|
||||
static NSString *const kExternalArmValueParam = @"arm_value";
|
||||
static NSString *const kPersonalizationId = @"personalizationId";
|
||||
static NSString *const kExternalPersonalizationIdParam = @"personalization_id";
|
||||
static NSString *const kArmIndex = @"armIndex";
|
||||
static NSString *const kExternalArmIndexParam = @"arm_index";
|
||||
static NSString *const kGroup = @"group";
|
||||
static NSString *const kExternalGroupParam = @"group";
|
||||
|
||||
static NSString *const kInternalEvent = @"_fpc";
|
||||
static NSString *const kChoiceId = @"choiceId";
|
||||
static NSString *const kInternalChoiceIdParam = @"_fpid";
|
||||
|
||||
@interface RCNPersonalization : NSObject
|
||||
|
||||
/// Analytics connector
|
||||
@property(nonatomic, strong) id<FIRAnalyticsInterop> _Nullable analytics;
|
||||
|
||||
@property(atomic, strong) NSMutableDictionary *loggedChoiceIds;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/// Designated initializer.
|
||||
- (instancetype)initWithAnalytics:(id<FIRAnalyticsInterop> _Nullable)analytics
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/// Called when an arm is pulled from Remote Config. If the arm is personalized, log information to
|
||||
/// Google in another thread.
|
||||
- (void)logArmActive:(NSString *)rcParameter config:(NSDictionary *)config;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
72
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNPersonalization.m
generated
Normal file
72
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNPersonalization.m
generated
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/RCNPersonalization.h"
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
|
||||
|
||||
@implementation RCNPersonalization
|
||||
|
||||
- (instancetype)initWithAnalytics:(id<FIRAnalyticsInterop> _Nullable)analytics {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self->_analytics = analytics;
|
||||
self->_loggedChoiceIds = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)logArmActive:(NSString *)rcParameter config:(NSDictionary *)config {
|
||||
NSDictionary *ids = config[RCNFetchResponseKeyPersonalizationMetadata];
|
||||
NSDictionary<NSString *, FIRRemoteConfigValue *> *values = config[RCNFetchResponseKeyEntries];
|
||||
if (ids.count < 1 || values.count < 1 || !values[rcParameter]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *metadata = ids[rcParameter];
|
||||
if (!metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *choiceId = metadata[kChoiceId];
|
||||
if (choiceId == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Listeners like logArmActive() are dispatched to a serial queue, so loggedChoiceIds should
|
||||
// contain any previously logged RC parameter / choice ID pairs.
|
||||
if (self->_loggedChoiceIds[rcParameter] == choiceId) {
|
||||
return;
|
||||
}
|
||||
self->_loggedChoiceIds[rcParameter] = choiceId;
|
||||
|
||||
[self->_analytics logEventWithOrigin:kAnalyticsOriginPersonalization
|
||||
name:kExternalEvent
|
||||
parameters:@{
|
||||
kExternalRcParameterParam : rcParameter,
|
||||
kExternalArmValueParam : values[rcParameter].stringValue,
|
||||
kExternalPersonalizationIdParam : metadata[kPersonalizationId],
|
||||
kExternalArmIndexParam : metadata[kArmIndex],
|
||||
kExternalGroupParam : metadata[kGroup]
|
||||
}];
|
||||
|
||||
[self->_analytics logEventWithOrigin:kAnalyticsOriginPersonalization
|
||||
name:kInternalEvent
|
||||
parameters:@{kInternalChoiceIdParam : choiceId}];
|
||||
}
|
||||
|
||||
@end
|
||||
66
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.h
generated
Normal file
66
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.h
generated
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCNUserDefaultsManager : NSObject
|
||||
|
||||
/// The last eTag received from the backend.
|
||||
@property(nonatomic, assign) NSString *lastETag;
|
||||
/// The time of the last eTag update.
|
||||
@property(nonatomic, assign) NSTimeInterval lastETagUpdateTime;
|
||||
/// The time of the last successful fetch.
|
||||
@property(nonatomic, assign) NSTimeInterval lastFetchTime;
|
||||
/// The time of the last successful fetch.
|
||||
@property(nonatomic, assign) NSString *lastFetchStatus;
|
||||
/// Boolean indicating if the last (one or more) fetch(es) was/were unsuccessful, in which case we
|
||||
/// are in an exponential backoff mode.
|
||||
@property(nonatomic, assign) BOOL isClientThrottledWithExponentialBackoff;
|
||||
/// Time when the next request can be made while being throttled.
|
||||
@property(nonatomic, assign) NSTimeInterval throttleEndTime;
|
||||
/// The retry interval increases exponentially for cumulative fetch failures. Refer to
|
||||
/// go/rc-client-throttling for details.
|
||||
@property(nonatomic, assign) NSTimeInterval currentThrottlingRetryIntervalSeconds;
|
||||
/// Time when the next request can be made while being throttled.
|
||||
@property(nonatomic, assign) NSTimeInterval realtimeThrottleEndTime;
|
||||
/// The retry interval increases exponentially for cumulative Realtime failures. Refer to
|
||||
/// go/rc-client-throttling for details.
|
||||
@property(nonatomic, assign) NSTimeInterval currentRealtimeThrottlingRetryIntervalSeconds;
|
||||
/// Realtime retry count.
|
||||
@property(nonatomic, assign) int realtimeRetryCount;
|
||||
/// Last fetched template version.
|
||||
@property(nonatomic, assign) NSString *lastFetchedTemplateVersion;
|
||||
/// Last active template version.
|
||||
@property(nonatomic, assign) NSString *lastActiveTemplateVersion;
|
||||
|
||||
/// Designated initializer.
|
||||
- (instancetype)initWithAppName:(NSString *)appName
|
||||
bundleID:(NSString *)bundleIdentifier
|
||||
namespace:(NSString *)firebaseNamespace NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
// NOLINTBEGIN
|
||||
/// Use `initWithAppName:bundleID:namespace:` instead.
|
||||
- (instancetype)init
|
||||
__attribute__((unavailable("Use `initWithAppName:bundleID:namespace:` instead.")));
|
||||
// NOLINTEND
|
||||
|
||||
/// Delete all saved userdefaults for this instance.
|
||||
- (void)resetUserDefaults;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
313
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.m
generated
Normal file
313
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.m
generated
Normal file
@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.h"
|
||||
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
|
||||
#import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
|
||||
#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
|
||||
|
||||
static NSString *const kRCNGroupPrefix = @"group";
|
||||
static NSString *const kRCNGroupSuffix = @"firebase";
|
||||
static NSString *const kRCNUserDefaultsKeyNamelastETag = @"lastETag";
|
||||
static NSString *const kRCNUserDefaultsKeyNamelastETagUpdateTime = @"lastETagUpdateTime";
|
||||
static NSString *const kRCNUserDefaultsKeyNameLastSuccessfulFetchTime = @"lastSuccessfulFetchTime";
|
||||
static NSString *const kRCNUserDefaultsKeyNamelastFetchStatus = @"lastFetchStatus";
|
||||
static NSString *const kRCNUserDefaultsKeyNameIsClientThrottled =
|
||||
@"isClientThrottledWithExponentialBackoff";
|
||||
static NSString *const kRCNUserDefaultsKeyNameThrottleEndTime = @"throttleEndTime";
|
||||
static NSString *const kRCNUserDefaultsKeyNamecurrentThrottlingRetryInterval =
|
||||
@"currentThrottlingRetryInterval";
|
||||
static NSString *const kRCNUserDefaultsKeyNameRealtimeThrottleEndTime = @"throttleRealtimeEndTime";
|
||||
static NSString *const kRCNUserDefaultsKeyNameCurrentRealtimeThrottlingRetryInterval =
|
||||
@"currentRealtimeThrottlingRetryInterval";
|
||||
static NSString *const kRCNUserDefaultsKeyNameRealtimeRetryCount = @"realtimeRetryCount";
|
||||
|
||||
@interface RCNUserDefaultsManager () {
|
||||
/// User Defaults instance for this bundleID. NSUserDefaults is guaranteed to be thread-safe.
|
||||
NSUserDefaults *_userDefaults;
|
||||
/// The suite name for this user defaults instance. It is a combination of a prefix and the
|
||||
/// bundleID. This is because you cannot use just the bundleID of the current app as the suite
|
||||
/// name when initializing user defaults.
|
||||
NSString *_userDefaultsSuiteName;
|
||||
/// The FIRApp that this instance is scoped within.
|
||||
NSString *_firebaseAppName;
|
||||
/// The Firebase Namespace that this instance is scoped within.
|
||||
NSString *_firebaseNamespace;
|
||||
/// The bundleID of the app. In case of an extension, this will be the bundleID of the parent app.
|
||||
NSString *_bundleIdentifier;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCNUserDefaultsManager
|
||||
|
||||
#pragma mark Initializers.
|
||||
|
||||
/// Designated initializer.
|
||||
- (instancetype)initWithAppName:(NSString *)appName
|
||||
bundleID:(NSString *)bundleIdentifier
|
||||
namespace:(NSString *)firebaseNamespace {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_firebaseAppName = appName;
|
||||
_bundleIdentifier = bundleIdentifier;
|
||||
NSInteger location = [firebaseNamespace rangeOfString:@":"].location;
|
||||
if (location == NSNotFound) {
|
||||
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000064",
|
||||
@"Error: Namespace %@ is not fully qualified app:namespace.", firebaseNamespace);
|
||||
_firebaseNamespace = firebaseNamespace;
|
||||
} else {
|
||||
_firebaseNamespace = [firebaseNamespace substringToIndex:location];
|
||||
}
|
||||
|
||||
// Initialize the user defaults with a prefix and the bundleID. For app extensions, this will be
|
||||
// the bundleID of the app extension.
|
||||
_userDefaults =
|
||||
[RCNUserDefaultsManager sharedUserDefaultsForBundleIdentifier:_bundleIdentifier];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (NSUserDefaults *)sharedUserDefaultsForBundleIdentifier:(NSString *)bundleIdentifier {
|
||||
static dispatch_once_t onceToken;
|
||||
static NSUserDefaults *sharedInstance;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *userDefaultsSuiteName =
|
||||
[RCNUserDefaultsManager userDefaultsSuiteNameForBundleIdentifier:bundleIdentifier];
|
||||
sharedInstance = [[NSUserDefaults alloc] initWithSuiteName:userDefaultsSuiteName];
|
||||
});
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
+ (NSString *)userDefaultsSuiteNameForBundleIdentifier:(NSString *)bundleIdentifier {
|
||||
NSString *suiteName =
|
||||
[NSString stringWithFormat:@"%@.%@.%@", kRCNGroupPrefix, bundleIdentifier, kRCNGroupSuffix];
|
||||
return suiteName;
|
||||
}
|
||||
|
||||
#pragma mark Public properties.
|
||||
|
||||
- (NSString *)lastETag {
|
||||
return [[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNamelastETag];
|
||||
}
|
||||
|
||||
- (void)setLastETag:(NSString *)lastETag {
|
||||
if (lastETag) {
|
||||
[self setInstanceUserDefaultsValue:lastETag forKey:kRCNUserDefaultsKeyNamelastETag];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)lastFetchedTemplateVersion {
|
||||
NSDictionary *userDefaults = [self instanceUserDefaults];
|
||||
if ([userDefaults objectForKey:RCNFetchResponseKeyTemplateVersion]) {
|
||||
return [userDefaults objectForKey:RCNFetchResponseKeyTemplateVersion];
|
||||
}
|
||||
|
||||
return @"0";
|
||||
}
|
||||
|
||||
- (void)setLastFetchedTemplateVersion:(NSString *)templateVersion {
|
||||
if (templateVersion) {
|
||||
[self setInstanceUserDefaultsValue:templateVersion forKey:RCNFetchResponseKeyTemplateVersion];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)lastActiveTemplateVersion {
|
||||
NSDictionary *userDefaults = [self instanceUserDefaults];
|
||||
if ([userDefaults objectForKey:RCNActiveKeyTemplateVersion]) {
|
||||
return [userDefaults objectForKey:RCNActiveKeyTemplateVersion];
|
||||
}
|
||||
|
||||
return @"0";
|
||||
}
|
||||
|
||||
- (void)setLastActiveTemplateVersion:(NSString *)templateVersion {
|
||||
if (templateVersion) {
|
||||
[self setInstanceUserDefaultsValue:templateVersion forKey:RCNActiveKeyTemplateVersion];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTimeInterval)lastETagUpdateTime {
|
||||
NSNumber *lastETagUpdateTime =
|
||||
[[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNamelastETagUpdateTime];
|
||||
return lastETagUpdateTime.doubleValue;
|
||||
}
|
||||
|
||||
- (void)setLastETagUpdateTime:(NSTimeInterval)lastETagUpdateTime {
|
||||
if (lastETagUpdateTime) {
|
||||
[self setInstanceUserDefaultsValue:@(lastETagUpdateTime)
|
||||
forKey:kRCNUserDefaultsKeyNamelastETagUpdateTime];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTimeInterval)lastFetchTime {
|
||||
NSNumber *lastFetchTime =
|
||||
[[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNameLastSuccessfulFetchTime];
|
||||
return lastFetchTime.doubleValue;
|
||||
}
|
||||
|
||||
- (void)setLastFetchTime:(NSTimeInterval)lastFetchTime {
|
||||
[self setInstanceUserDefaultsValue:@(lastFetchTime)
|
||||
forKey:kRCNUserDefaultsKeyNameLastSuccessfulFetchTime];
|
||||
}
|
||||
|
||||
- (NSString *)lastFetchStatus {
|
||||
return [[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNamelastFetchStatus];
|
||||
}
|
||||
|
||||
- (void)setLastFetchStatus:(NSString *)lastFetchStatus {
|
||||
if (lastFetchStatus) {
|
||||
[self setInstanceUserDefaultsValue:lastFetchStatus
|
||||
forKey:kRCNUserDefaultsKeyNamelastFetchStatus];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isClientThrottledWithExponentialBackoff {
|
||||
NSNumber *isClientThrottled =
|
||||
[[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNameIsClientThrottled];
|
||||
return isClientThrottled.boolValue;
|
||||
}
|
||||
|
||||
- (void)setIsClientThrottledWithExponentialBackoff:(BOOL)isClientThrottled {
|
||||
[self setInstanceUserDefaultsValue:@(isClientThrottled)
|
||||
forKey:kRCNUserDefaultsKeyNameIsClientThrottled];
|
||||
}
|
||||
|
||||
- (NSTimeInterval)throttleEndTime {
|
||||
NSNumber *throttleEndTime =
|
||||
[[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNameThrottleEndTime];
|
||||
return throttleEndTime.doubleValue;
|
||||
}
|
||||
|
||||
- (void)setThrottleEndTime:(NSTimeInterval)throttleEndTime {
|
||||
[self setInstanceUserDefaultsValue:@(throttleEndTime)
|
||||
forKey:kRCNUserDefaultsKeyNameThrottleEndTime];
|
||||
}
|
||||
|
||||
- (NSTimeInterval)currentThrottlingRetryIntervalSeconds {
|
||||
NSNumber *throttleEndTime = [[self instanceUserDefaults]
|
||||
objectForKey:kRCNUserDefaultsKeyNamecurrentThrottlingRetryInterval];
|
||||
return throttleEndTime.doubleValue;
|
||||
}
|
||||
|
||||
- (void)setCurrentThrottlingRetryIntervalSeconds:(NSTimeInterval)throttlingRetryIntervalSeconds {
|
||||
[self setInstanceUserDefaultsValue:@(throttlingRetryIntervalSeconds)
|
||||
forKey:kRCNUserDefaultsKeyNamecurrentThrottlingRetryInterval];
|
||||
}
|
||||
|
||||
- (int)realtimeRetryCount {
|
||||
int realtimeRetryCount = 0;
|
||||
if ([[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNameRealtimeRetryCount]) {
|
||||
realtimeRetryCount = [[[self instanceUserDefaults]
|
||||
objectForKey:kRCNUserDefaultsKeyNameRealtimeRetryCount] intValue];
|
||||
}
|
||||
|
||||
return realtimeRetryCount;
|
||||
}
|
||||
|
||||
- (void)setRealtimeRetryCount:(int)realtimeRetryCount {
|
||||
[self setInstanceUserDefaultsValue:[NSNumber numberWithInt:realtimeRetryCount]
|
||||
forKey:kRCNUserDefaultsKeyNameRealtimeRetryCount];
|
||||
}
|
||||
|
||||
- (NSTimeInterval)realtimeThrottleEndTime {
|
||||
NSNumber *realtimeThrottleEndTime = 0;
|
||||
if ([[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNameRealtimeThrottleEndTime]) {
|
||||
realtimeThrottleEndTime =
|
||||
[[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNameRealtimeThrottleEndTime];
|
||||
}
|
||||
return realtimeThrottleEndTime.doubleValue;
|
||||
}
|
||||
|
||||
- (void)setRealtimeThrottleEndTime:(NSTimeInterval)throttleEndTime {
|
||||
[self setInstanceUserDefaultsValue:@(throttleEndTime)
|
||||
forKey:kRCNUserDefaultsKeyNameRealtimeThrottleEndTime];
|
||||
}
|
||||
|
||||
- (NSTimeInterval)currentRealtimeThrottlingRetryIntervalSeconds {
|
||||
NSNumber *realtimeThrottleEndTime = 0;
|
||||
if ([[self instanceUserDefaults]
|
||||
objectForKey:kRCNUserDefaultsKeyNameCurrentRealtimeThrottlingRetryInterval]) {
|
||||
realtimeThrottleEndTime = [[self instanceUserDefaults]
|
||||
objectForKey:kRCNUserDefaultsKeyNameCurrentRealtimeThrottlingRetryInterval];
|
||||
}
|
||||
return realtimeThrottleEndTime.doubleValue;
|
||||
}
|
||||
|
||||
- (void)setCurrentRealtimeThrottlingRetryIntervalSeconds:
|
||||
(NSTimeInterval)throttlingRetryIntervalSeconds {
|
||||
[self setInstanceUserDefaultsValue:@(throttlingRetryIntervalSeconds)
|
||||
forKey:kRCNUserDefaultsKeyNameCurrentRealtimeThrottlingRetryInterval];
|
||||
}
|
||||
|
||||
#pragma mark Public methods.
|
||||
- (void)resetUserDefaults {
|
||||
[self resetInstanceUserDefaults];
|
||||
}
|
||||
|
||||
#pragma mark Private methods.
|
||||
|
||||
// There is a nested hierarchy for the userdefaults as follows:
|
||||
// [FIRAppName][FIRNamespaceName][Key]
|
||||
- (nonnull NSDictionary *)appUserDefaults {
|
||||
NSString *appPath = _firebaseAppName;
|
||||
NSDictionary *appDict = [_userDefaults valueForKeyPath:appPath];
|
||||
if (!appDict) {
|
||||
appDict = [[NSDictionary alloc] init];
|
||||
}
|
||||
return appDict;
|
||||
}
|
||||
|
||||
// Search for the user defaults for this (app, namespace) instance using the valueForKeyPath method.
|
||||
- (nonnull NSDictionary *)instanceUserDefaults {
|
||||
NSString *appNamespacePath =
|
||||
[NSString stringWithFormat:@"%@.%@", _firebaseAppName, _firebaseNamespace];
|
||||
NSDictionary *appNamespaceDict = [_userDefaults valueForKeyPath:appNamespacePath];
|
||||
|
||||
if (!appNamespaceDict) {
|
||||
appNamespaceDict = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return appNamespaceDict;
|
||||
}
|
||||
|
||||
// Update users defaults for just this (app, namespace) instance.
|
||||
- (void)setInstanceUserDefaultsValue:(NSObject *)value forKey:(NSString *)key {
|
||||
@synchronized(_userDefaults) {
|
||||
NSMutableDictionary *appUserDefaults = [[self appUserDefaults] mutableCopy];
|
||||
NSMutableDictionary *appNamespaceUserDefaults = [[self instanceUserDefaults] mutableCopy];
|
||||
[appNamespaceUserDefaults setObject:value forKey:key];
|
||||
[appUserDefaults setObject:appNamespaceUserDefaults forKey:_firebaseNamespace];
|
||||
[_userDefaults setObject:appUserDefaults forKey:_firebaseAppName];
|
||||
// We need to synchronize to have this value updated for the extension.
|
||||
[_userDefaults synchronize];
|
||||
}
|
||||
}
|
||||
|
||||
// Delete any existing userdefaults for this instance.
|
||||
- (void)resetInstanceUserDefaults {
|
||||
@synchronized(_userDefaults) {
|
||||
NSMutableDictionary *appUserDefaults = [[self appUserDefaults] mutableCopy];
|
||||
NSMutableDictionary *appNamespaceUserDefaults = [[self instanceUserDefaults] mutableCopy];
|
||||
[appNamespaceUserDefaults removeAllObjects];
|
||||
[appUserDefaults setObject:appNamespaceUserDefaults forKey:_firebaseNamespace];
|
||||
[_userDefaults setObject:appUserDefaults forKey:_firebaseAppName];
|
||||
// We need to synchronize to have this value updated for the extension.
|
||||
[_userDefaults synchronize];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
69
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Swift/Codable.swift
generated
Normal file
69
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Swift/Codable.swift
generated
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
#if SWIFT_PACKAGE
|
||||
@_exported import FirebaseRemoteConfigInternal
|
||||
#endif // SWIFT_PACKAGE
|
||||
import FirebaseSharedSwift
|
||||
|
||||
public enum RemoteConfigValueCodableError: Error {
|
||||
case unsupportedType(String)
|
||||
}
|
||||
|
||||
public extension RemoteConfigValue {
|
||||
/// Extracts a RemoteConfigValue JSON-encoded object and decodes it to the requested type.
|
||||
///
|
||||
/// - Parameter asType: The type to decode the JSON-object to
|
||||
func decoded<Value: Decodable>(asType: Value.Type = Value.self) throws -> Value {
|
||||
if asType == Date.self {
|
||||
throw RemoteConfigValueCodableError
|
||||
.unsupportedType("Date type is not currently supported for " +
|
||||
" Remote Config Value decoding. Please file a feature request")
|
||||
}
|
||||
return try FirebaseDataDecoder()
|
||||
.decode(Value.self, from: FirebaseRemoteConfigValueDecoderHelper(value: self))
|
||||
}
|
||||
}
|
||||
|
||||
public enum RemoteConfigCodableError: Error {
|
||||
case invalidSetDefaultsInput(String)
|
||||
}
|
||||
|
||||
public extension RemoteConfig {
|
||||
/// Decodes a struct from the respective Remote Config values.
|
||||
///
|
||||
/// - Parameter asType: The type to decode to.
|
||||
func decoded<Value: Decodable>(asType: Value.Type = Value.self) throws -> Value {
|
||||
let keys = allKeys(from: RemoteConfigSource.default) + allKeys(from: RemoteConfigSource.remote)
|
||||
let config = keys.reduce(into: [String: FirebaseRemoteConfigValueDecoderHelper]()) {
|
||||
$0[$1] = FirebaseRemoteConfigValueDecoderHelper(value: configValue(forKey: $1))
|
||||
}
|
||||
return try FirebaseDataDecoder().decode(Value.self, from: config)
|
||||
}
|
||||
|
||||
/// Sets config defaults from an encodable struct.
|
||||
///
|
||||
/// - Parameter value: The object to use to set the defaults.
|
||||
func setDefaults<Value: Encodable>(from value: Value) throws {
|
||||
guard let encoded = try FirebaseDataEncoder().encode(value) as? [String: NSObject] else {
|
||||
throw RemoteConfigCodableError.invalidSetDefaultsInput(
|
||||
"The setDefaults input: \(value), must be a Struct that encodes to a Dictionary"
|
||||
)
|
||||
}
|
||||
setDefaults(encoded)
|
||||
}
|
||||
}
|
||||
58
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Swift/FirebaseRemoteConfigValueDecoderHelper.swift
generated
Normal file
58
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Swift/FirebaseRemoteConfigValueDecoderHelper.swift
generated
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
#if SWIFT_PACKAGE
|
||||
@_exported import FirebaseRemoteConfigInternal
|
||||
#endif // SWIFT_PACKAGE
|
||||
import FirebaseSharedSwift
|
||||
|
||||
/// Implement the FirebaseRemoteConfigValueDecoding protocol for the shared Firebase decoder to
|
||||
/// decode Remote Config Values. It returns the four different kinds of values from
|
||||
/// a RemoteConfigValue object.
|
||||
struct FirebaseRemoteConfigValueDecoderHelper: FirebaseRemoteConfigValueDecoding {
|
||||
let value: RemoteConfigValue
|
||||
|
||||
func numberValue() -> NSNumber {
|
||||
return value.numberValue
|
||||
}
|
||||
|
||||
func boolValue() -> Bool {
|
||||
return value.boolValue
|
||||
}
|
||||
|
||||
func stringValue() -> String {
|
||||
return value.stringValue
|
||||
}
|
||||
|
||||
func dataValue() -> Data {
|
||||
return value.dataValue
|
||||
}
|
||||
|
||||
func arrayValue() -> [AnyHashable]? {
|
||||
guard let value = value.jsonValue as? [AnyHashable] else {
|
||||
return nil
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func dictionaryValue() -> [String: AnyHashable]? {
|
||||
guard let value = value.jsonValue as? [String: AnyHashable] else {
|
||||
return nil
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
51
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Swift/PropertyWrapper/RemoteConfigProperty.swift
generated
Normal file
51
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Swift/PropertyWrapper/RemoteConfigProperty.swift
generated
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2022 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#if SWIFT_PACKAGE
|
||||
@_exported import FirebaseRemoteConfigInternal
|
||||
#endif // SWIFT_PACKAGE
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// A property wrapper that listens to a Remote Config value.
|
||||
@available(iOS 14.0, macOS 11.0, macCatalyst 14.0, tvOS 14.0, watchOS 7.0, *)
|
||||
@propertyWrapper
|
||||
public struct RemoteConfigProperty<T: Decodable>: DynamicProperty {
|
||||
@StateObject private var configValueObserver: RemoteConfigValueObservable<T>
|
||||
|
||||
/// Remote Config key name for this property
|
||||
public let key: String
|
||||
|
||||
public var wrappedValue: T {
|
||||
configValueObserver.configValue
|
||||
}
|
||||
|
||||
/// Creates an instance by providing a config key.
|
||||
///
|
||||
/// - Parameter key: key name
|
||||
/// - Parameter fallback: The value to fall back to if the key doesn't exist in remote or default
|
||||
/// configs
|
||||
public init(key: String, fallback: T) {
|
||||
self.key = key
|
||||
|
||||
_configValueObserver = StateObject(
|
||||
wrappedValue: RemoteConfigValueObservable<T>(
|
||||
key: key,
|
||||
fallbackValue: fallback
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2022 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#if SWIFT_PACKAGE
|
||||
@_exported import FirebaseRemoteConfigInternal
|
||||
#endif // SWIFT_PACKAGE
|
||||
import FirebaseCore
|
||||
import SwiftUI
|
||||
|
||||
extension Notification.Name {
|
||||
// Listens to FirebaseRemoteConfig SDK if new configs are activated.
|
||||
static let onRemoteConfigActivated = Notification.Name("FIRRemoteConfigActivateNotification")
|
||||
}
|
||||
|
||||
// Make sure this key is consistent with kFIRGoogleAppIDKey in FirebaseCore SDK
|
||||
let FirebaseRemoteConfigAppNameKey = "FIRAppNameKey"
|
||||
|
||||
@available(iOS 14.0, macOS 11.0, macCatalyst 14.0, tvOS 14.0, watchOS 7.0, *)
|
||||
class RemoteConfigValueObservable<T: Decodable>: ObservableObject {
|
||||
@Published var configValue: T
|
||||
private let key: String
|
||||
private let remoteConfig: RemoteConfig
|
||||
private let fallbackValue: T
|
||||
|
||||
init(key: String, fallbackValue: T) {
|
||||
self.key = key
|
||||
remoteConfig = RemoteConfig.remoteConfig()
|
||||
self.fallbackValue = fallbackValue
|
||||
// Initialize with fallback value
|
||||
configValue = fallbackValue
|
||||
// Check cached remote config value
|
||||
do {
|
||||
let configValue: RemoteConfigValue = remoteConfig[key]
|
||||
if configValue.source == .remote || configValue.source == .default {
|
||||
self.configValue = try remoteConfig[key].decoded()
|
||||
} else {
|
||||
self.configValue = fallbackValue
|
||||
}
|
||||
} catch {
|
||||
configValue = fallbackValue
|
||||
}
|
||||
NotificationCenter.default.addObserver(
|
||||
self, selector: #selector(configDidActivate), name: .onRemoteConfigActivated, object: nil
|
||||
)
|
||||
}
|
||||
|
||||
@objc func configDidActivate(notification: NSNotification) {
|
||||
// This feature is only available in the default app.
|
||||
let appName = notification.userInfo?[FirebaseRemoteConfigAppNameKey] as? String
|
||||
if FirebaseApp.app()?.name != appName {
|
||||
return
|
||||
}
|
||||
do {
|
||||
let configValue: RemoteConfigValue = remoteConfig[key]
|
||||
if configValue.source == .remote {
|
||||
self.configValue = try remoteConfig[key].decoded()
|
||||
}
|
||||
} catch {
|
||||
// Suppresses a hard failure if decoding failed.
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Swift/Resources/PrivacyInfo.xcprivacy
generated
Normal file
38
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Swift/Resources/PrivacyInfo.xcprivacy
generated
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeOtherDiagnosticData</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>1C8F.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
30
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Swift/SPMSwiftHeaderWorkaround.swift
generated
Normal file
30
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Swift/SPMSwiftHeaderWorkaround.swift
generated
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#if SWIFT_PACKAGE
|
||||
@_exported import FirebaseRemoteConfigInternal
|
||||
|
||||
// This is a trick to force generate a `FirebaseRemoteConfig-Swift.h` header
|
||||
// that re-exports `FirebaseRemoteConfigInternal` for Objective-C clients. It
|
||||
// is important for the below code to reference a Remote Config symbol defined
|
||||
// in Objective-C as that will import the symbol's module
|
||||
// (`FirebaseRemoteConfigInternal`) in the generated header. This allows
|
||||
// Objective-C clients to import Remote Config's Objective-C API using
|
||||
// `@import FirebaseRemoteConfig;`. This API is not needed for Swift clients
|
||||
// and is therefore unavailable in a Swift context.
|
||||
@available(*, unavailable)
|
||||
@objc public extension RemoteConfig {
|
||||
static var __no_op: () -> Void { {} }
|
||||
}
|
||||
#endif // SWIFT_PACKAGE
|
||||
42
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Swift/Value.swift
generated
Normal file
42
Pods/FirebaseRemoteConfig/FirebaseRemoteConfig/Swift/Value.swift
generated
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
#if SWIFT_PACKAGE
|
||||
@_exported import FirebaseRemoteConfigInternal
|
||||
#endif // SWIFT_PACKAGE
|
||||
|
||||
/// Implements subscript overloads to enable Remote Config values to be accessed
|
||||
/// in a type-safe way directly from the current config.
|
||||
public extension RemoteConfig {
|
||||
/// Return a typed RemoteConfigValue for a key.
|
||||
/// - Parameter key: A Remote Config key.
|
||||
/// - Returns: A typed RemoteConfigValue.
|
||||
subscript<T: Decodable>(decodedValue key: String) -> T? {
|
||||
return try? configValue(forKey: key).decoded()
|
||||
}
|
||||
|
||||
/// Return a Dictionary for a RemoteConfig JSON key.
|
||||
/// - Parameter key: A Remote Config key.
|
||||
/// - Returns: A Dictionary representing a RemoteConfig JSON value.
|
||||
subscript(jsonValue key: String) -> [String: AnyHashable]? {
|
||||
guard let value = configValue(forKey: key).jsonValue as? [String: AnyHashable] else {
|
||||
// nil is the historical behavior for failing to extract JSON.
|
||||
return nil
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
68
Pods/FirebaseRemoteConfig/Interop/Analytics/Public/FIRAnalyticsInterop.h
generated
Normal file
68
Pods/FirebaseRemoteConfig/Interop/Analytics/Public/FIRAnalyticsInterop.h
generated
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol FIRAnalyticsInteropListener;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// Block typedef callback parameter to `getUserProperties(with:)`.
|
||||
typedef void (^FIRAInteropUserPropertiesCallback)(NSDictionary<NSString *, id> *userProperties)
|
||||
NS_SWIFT_UNAVAILABLE("Use Swift's closure syntax instead.");
|
||||
|
||||
/// Connector for bridging communication between Firebase SDKs and FirebaseAnalytics APIs.
|
||||
@protocol FIRAnalyticsInterop
|
||||
|
||||
/// Sets user property when trigger event is logged. This API is only available in the SDK.
|
||||
- (void)setConditionalUserProperty:(NSDictionary<NSString *, id> *)conditionalUserProperty;
|
||||
|
||||
/// Clears user property if set.
|
||||
- (void)clearConditionalUserProperty:(NSString *)userPropertyName
|
||||
forOrigin:(NSString *)origin
|
||||
clearEventName:(NSString *)clearEventName
|
||||
clearEventParameters:(NSDictionary<NSString *, NSString *> *)clearEventParameters;
|
||||
|
||||
/// Returns currently set user properties.
|
||||
- (NSArray<NSDictionary<NSString *, NSString *> *> *)conditionalUserProperties:(NSString *)origin
|
||||
propertyNamePrefix:
|
||||
(NSString *)propertyNamePrefix;
|
||||
|
||||
/// Returns the maximum number of user properties.
|
||||
- (NSInteger)maxUserProperties:(NSString *)origin;
|
||||
|
||||
/// Returns the user properties to a callback function.
|
||||
- (void)getUserPropertiesWithCallback:
|
||||
(void (^)(NSDictionary<NSString *, id> *userProperties))callback;
|
||||
|
||||
/// Logs events.
|
||||
- (void)logEventWithOrigin:(NSString *)origin
|
||||
name:(NSString *)name
|
||||
parameters:(nullable NSDictionary<NSString *, id> *)parameters;
|
||||
|
||||
/// Sets user property.
|
||||
- (void)setUserPropertyWithOrigin:(NSString *)origin name:(NSString *)name value:(id)value;
|
||||
|
||||
/// Registers an Analytics listener for the given origin.
|
||||
- (void)registerAnalyticsListener:(id<FIRAnalyticsInteropListener>)listener
|
||||
withOrigin:(NSString *)origin;
|
||||
|
||||
/// Unregisters an Analytics listener for the given origin.
|
||||
- (void)unregisterAnalyticsListenerWithOrigin:(NSString *)origin;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
24
Pods/FirebaseRemoteConfig/Interop/Analytics/Public/FIRAnalyticsInteropListener.h
generated
Normal file
24
Pods/FirebaseRemoteConfig/Interop/Analytics/Public/FIRAnalyticsInteropListener.h
generated
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2019 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/// Handles events and messages from Analytics.
|
||||
@protocol FIRAnalyticsInteropListener <NSObject>
|
||||
|
||||
/// Triggers when an Analytics event happens for the registered origin with
|
||||
/// FirebaseAnalyticsInterop`s `registerAnalyticsListener(_:withOrigin:)`.
|
||||
- (void)messageTriggered:(NSString *)name parameters:(NSDictionary *)parameters;
|
||||
|
||||
@end
|
||||
28
Pods/FirebaseRemoteConfig/Interop/Analytics/Public/FIRInteropEventNames.h
generated
Normal file
28
Pods/FirebaseRemoteConfig/Interop/Analytics/Public/FIRInteropEventNames.h
generated
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/// @file FIRInteropEventNames.h
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/// Notification open event name.
|
||||
static NSString *const kFIRIEventNotificationOpen = @"_no";
|
||||
|
||||
/// Notification foreground event name.
|
||||
static NSString *const kFIRIEventNotificationForeground = @"_nf";
|
||||
|
||||
/// Campaign event name.
|
||||
static NSString *const kFIRIEventFirebaseCampaign = @"_cmp";
|
||||
73
Pods/FirebaseRemoteConfig/Interop/Analytics/Public/FIRInteropParameterNames.h
generated
Normal file
73
Pods/FirebaseRemoteConfig/Interop/Analytics/Public/FIRInteropParameterNames.h
generated
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2018 Google
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/// @file FIRInteropParameterNames.h
|
||||
///
|
||||
/// Predefined event parameter names used by Firebase. This file is a subset of the
|
||||
/// FirebaseAnalytics FIRParameterNames.h public header.
|
||||
///
|
||||
/// The origin of your traffic, such as an Ad network (for example, google) or partner (urban
|
||||
/// airship). Identify the advertiser, site, publication, etc. that is sending traffic to your
|
||||
/// property. Highly recommended (String).
|
||||
/// <pre>
|
||||
/// let params = [
|
||||
/// kFIRParameterSource : "InMobi",
|
||||
/// // ...
|
||||
/// ]
|
||||
/// </pre>
|
||||
static NSString *const kFIRIParameterSource NS_SWIFT_NAME(AnalyticsParameterSource) = @"source";
|
||||
|
||||
/// The advertising or marketing medium, for example: cpc, banner, email, push. Highly recommended
|
||||
/// (String).
|
||||
/// <pre>
|
||||
/// let params = [
|
||||
/// kFIRParameterMedium : "email",
|
||||
/// // ...
|
||||
/// ]
|
||||
/// </pre>
|
||||
static NSString *const kFIRIParameterMedium NS_SWIFT_NAME(AnalyticsParameterMedium) = @"medium";
|
||||
|
||||
/// The individual campaign name, slogan, promo code, etc. Some networks have pre-defined macro to
|
||||
/// capture campaign information, otherwise can be populated by developer. Highly Recommended
|
||||
/// (String).
|
||||
/// <pre>
|
||||
/// let params = [
|
||||
/// kFIRParameterCampaign : "winter_promotion",
|
||||
/// // ...
|
||||
/// ]
|
||||
/// </pre>
|
||||
static NSString *const kFIRIParameterCampaign NS_SWIFT_NAME(AnalyticsParameterCampaign) =
|
||||
@"campaign";
|
||||
|
||||
/// Message identifier.
|
||||
static NSString *const kFIRIParameterMessageIdentifier = @"_nmid";
|
||||
|
||||
/// Message name.
|
||||
static NSString *const kFIRIParameterMessageName = @"_nmn";
|
||||
|
||||
/// Message send time.
|
||||
static NSString *const kFIRIParameterMessageTime = @"_nmt";
|
||||
|
||||
/// Message device time.
|
||||
static NSString *const kFIRIParameterMessageDeviceTime = @"_ndt";
|
||||
|
||||
/// Topic message.
|
||||
static NSString *const kFIRIParameterTopic = @"_nt";
|
||||
|
||||
/// Stores the message_id of the last notification opened by the app.
|
||||
static NSString *const kFIRIUserPropertyLastNotification = @"_ln";
|
||||
202
Pods/FirebaseRemoteConfig/LICENSE
generated
Normal file
202
Pods/FirebaseRemoteConfig/LICENSE
generated
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
302
Pods/FirebaseRemoteConfig/README.md
generated
Normal file
302
Pods/FirebaseRemoteConfig/README.md
generated
Normal file
@ -0,0 +1,302 @@
|
||||
<p align="center">
|
||||
<a href="https://cocoapods.org/pods/Firebase">
|
||||
<img src="https://img.shields.io/github/v/release/Firebase/firebase-ios-sdk?style=flat&label=CocoaPods"/>
|
||||
</a>
|
||||
<a href="https://swiftpackageindex.com/firebase/firebase-ios-sdk">
|
||||
<img src="https://img.shields.io/github/v/release/Firebase/firebase-ios-sdk?style=flat&label=Swift%20Package%20Index&color=red"/>
|
||||
</a>
|
||||
<a href="https://cocoapods.org/pods/Firebase">
|
||||
<img src="https://img.shields.io/github/license/Firebase/firebase-ios-sdk?style=flat"/>
|
||||
</a><br/>
|
||||
<a href="https://swiftpackageindex.com/firebase/firebase-ios-sdk">
|
||||
<img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Ffirebase%2Ffirebase-ios-sdk%2Fbadge%3Ftype%3Dplatforms"/>
|
||||
</a>
|
||||
<a href="https://swiftpackageindex.com/firebase/firebase-ios-sdk">
|
||||
<img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Ffirebase%2Ffirebase-ios-sdk%2Fbadge%3Ftype%3Dswift-versions"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
# Firebase Apple Open Source Development
|
||||
|
||||
This repository contains the source code for all Apple platform Firebase SDKs except FirebaseAnalytics.
|
||||
|
||||
Firebase is an app development platform with tools to help you build, grow, and
|
||||
monetize your app. More information about Firebase can be found on the
|
||||
[official Firebase website](https://firebase.google.com).
|
||||
|
||||
## Installation
|
||||
|
||||
See the subsections below for details about the different installation methods. Where
|
||||
available, it's recommended to install any libraries with a `Swift` suffix to get the
|
||||
best experience when writing your app in Swift.
|
||||
|
||||
1. [Standard pod install](#standard-pod-install)
|
||||
2. [Swift Package Manager](#swift-package-manager)
|
||||
3. [Installing from the GitHub repo](#installing-from-github)
|
||||
4. [Experimental Carthage](#carthage-ios-only)
|
||||
|
||||
### Standard pod install
|
||||
|
||||
For instructions on the standard pod install, visit:
|
||||
[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup).
|
||||
|
||||
### Swift Package Manager
|
||||
|
||||
Instructions for [Swift Package Manager](https://swift.org/package-manager/) support can be
|
||||
found in the [SwiftPackageManager.md](SwiftPackageManager.md) Markdown file.
|
||||
|
||||
### Installing from GitHub
|
||||
|
||||
These instructions can be used to access the Firebase repo at other branches,
|
||||
tags, or commits.
|
||||
|
||||
#### Background
|
||||
|
||||
See [the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod)
|
||||
for instructions and options about overriding pod source locations.
|
||||
|
||||
#### Accessing Firebase Source Snapshots
|
||||
|
||||
All official releases are tagged in this repo and available via CocoaPods. To access a local
|
||||
source snapshot or unreleased branch, use Podfile directives like the following:
|
||||
|
||||
To access FirebaseFirestore via a branch:
|
||||
```ruby
|
||||
pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'main'
|
||||
pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'main'
|
||||
```
|
||||
|
||||
To access FirebaseMessaging via a checked-out version of the firebase-ios-sdk repo:
|
||||
```ruby
|
||||
pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk'
|
||||
pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk'
|
||||
```
|
||||
|
||||
### Carthage (iOS only)
|
||||
|
||||
Instructions for the experimental Carthage distribution can be found at
|
||||
[Carthage.md](Carthage.md).
|
||||
|
||||
### Using Firebase from a Framework or a library
|
||||
|
||||
For details on using Firebase from a Framework or a library, refer to [firebase_in_libraries.md](docs/firebase_in_libraries.md).
|
||||
|
||||
## Development
|
||||
|
||||
To develop Firebase software in this repository, ensure that you have at least
|
||||
the following software:
|
||||
|
||||
* Xcode 15.2 (or later)
|
||||
|
||||
CocoaPods is still the canonical way to develop, but much of the repo now supports
|
||||
development with Swift Package Manager.
|
||||
|
||||
### CocoaPods
|
||||
|
||||
Install the following:
|
||||
* CocoaPods 1.12.0 (or later)
|
||||
* [CocoaPods generate](https://github.com/square/cocoapods-generate)
|
||||
|
||||
For the pod that you want to develop:
|
||||
|
||||
```ruby
|
||||
pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios
|
||||
```
|
||||
|
||||
Note: If the CocoaPods cache is out of date, you may need to run
|
||||
`pod repo update` before the `pod gen` command.
|
||||
|
||||
Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for
|
||||
those platforms. Since 10.2, Xcode does not properly handle multi-platform
|
||||
CocoaPods workspaces.
|
||||
|
||||
Firestore has a self-contained Xcode project. See
|
||||
[Firestore/README](Firestore/README.md) Markdown file.
|
||||
|
||||
#### Development for Catalyst
|
||||
* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios`
|
||||
* Check the Mac box in the App-iOS Build Settings
|
||||
* Sign the App in the Settings Signing & Capabilities tab
|
||||
* Click Pods in the Project Manager
|
||||
* Add Signing to the iOS host app and unit test targets
|
||||
* Select the Unit-unit scheme
|
||||
* Run it to build and test
|
||||
|
||||
Alternatively, disable signing in each target:
|
||||
* Go to Build Settings tab
|
||||
* Click `+`
|
||||
* Select `Add User-Defined Setting`
|
||||
* Add `CODE_SIGNING_REQUIRED` setting with a value of `NO`
|
||||
|
||||
### Swift Package Manager
|
||||
* To enable test schemes: `./scripts/setup_spm_tests.sh`
|
||||
* `open Package.swift` or double click `Package.swift` in Finder.
|
||||
* Xcode will open the project
|
||||
* Choose a scheme for a library to build or test suite to run
|
||||
* Choose a target platform by selecting the run destination along with the scheme
|
||||
|
||||
### Adding a New Firebase Pod
|
||||
|
||||
Refer to [AddNewPod](AddNewPod.md) Markdown file for details.
|
||||
|
||||
### Managing Headers and Imports
|
||||
|
||||
For information about managing headers and imports, see [HeadersImports](HeadersImports.md) Markdown file.
|
||||
|
||||
### Code Formatting
|
||||
|
||||
To ensure that the code is formatted consistently, run the script
|
||||
[./scripts/check.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/check.sh)
|
||||
before creating a pull request (PR).
|
||||
|
||||
GitHub Actions will verify that any code changes are done in a style-compliant
|
||||
way. Install `clang-format` and `mint`:
|
||||
|
||||
```console
|
||||
brew install clang-format@18
|
||||
brew install mint
|
||||
```
|
||||
|
||||
### Running Unit Tests
|
||||
|
||||
Select a scheme and press Command-u to build a component and run its unit tests.
|
||||
|
||||
### Running Sample Apps
|
||||
To run the sample apps and integration tests, you'll need a valid
|
||||
`GoogleService-Info.plist
|
||||
` file. The Firebase Xcode project contains dummy plist
|
||||
files without real values, but they can be replaced with real plist files. To get your own
|
||||
`GoogleService-Info.plist` files:
|
||||
|
||||
1. Go to the [Firebase Console](https://console.firebase.google.com/)
|
||||
2. Create a new Firebase project, if you don't already have one
|
||||
3. For each sample app you want to test, create a new Firebase app with the sample app's bundle
|
||||
identifier (e.g., `com.google.Database-Example`)
|
||||
4. Download the resulting `GoogleService-Info.plist` and add it to the Xcode project.
|
||||
|
||||
### Coverage Report Generation
|
||||
|
||||
For coverage report generation instructions, see [scripts/code_coverage_report/README](scripts/code_coverage_report/README.md) Markdown file.
|
||||
|
||||
## Specific Component Instructions
|
||||
See the sections below for any special instructions for those components.
|
||||
|
||||
### Firebase Auth
|
||||
|
||||
For specific Firebase Auth development, refer to the [Auth Sample README](FirebaseAuth/Tests/Sample/README.md) for instructions about
|
||||
building and running the FirebaseAuth pod along with various samples and tests.
|
||||
|
||||
### Firebase Database
|
||||
|
||||
The Firebase Database Integration tests can be run against a locally running Database Emulator
|
||||
or against a production instance.
|
||||
|
||||
To run against a local emulator instance, invoke `./scripts/run_database_emulator.sh start` before
|
||||
running the integration test.
|
||||
|
||||
To run against a production instance, provide a valid `GoogleServices-Info.plist` and copy it to
|
||||
`FirebaseDatabase/Tests/Resources/GoogleService-Info.plist`. Your Security Rule must be set to
|
||||
[public](https://firebase.google.com/docs/database/security/quickstart) while your tests are
|
||||
running.
|
||||
|
||||
### Firebase Dynamic Links
|
||||
|
||||
Firebase Dynamic Links is **deprecated** and should not be used in new projects. The service will shut down on August 25, 2025.
|
||||
|
||||
Please see our [Dynamic Links Deprecation FAQ documentation](https://firebase.google.com/support/dynamic-links-faq) for more guidance.
|
||||
|
||||
### Firebase Performance Monitoring
|
||||
|
||||
For specific Firebase Performance Monitoring development, see
|
||||
[the Performance README](FirebasePerformance/README.md) for instructions about building the SDK
|
||||
and [the Performance TestApp README](FirebasePerformance/Tests/TestApp/README.md) for instructions about
|
||||
integrating Performance with the dev test App.
|
||||
|
||||
### Firebase Storage
|
||||
|
||||
To run the Storage Integration tests, follow the instructions in
|
||||
[StorageIntegration.swift](FirebaseStorage/Tests/Integration/StorageIntegration.swift).
|
||||
|
||||
#### Push Notifications
|
||||
|
||||
Push notifications can only be delivered to specially provisioned App IDs in the developer portal.
|
||||
In order to test receiving push notifications, you will need to:
|
||||
|
||||
1. Change the bundle identifier of the sample app to something you own in your Apple Developer
|
||||
account and enable that App ID for push notifications.
|
||||
2. You'll also need to
|
||||
[upload your APNs Provider Authentication Key or certificate to the
|
||||
Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs)
|
||||
at **Project Settings > Cloud Messaging > [Your Firebase App]**.
|
||||
3. Ensure your iOS device is added to your Apple Developer portal as a test device.
|
||||
|
||||
#### iOS Simulator
|
||||
|
||||
The iOS Simulator cannot register for remote notifications and will not receive push notifications.
|
||||
To receive push notifications, follow the steps above and run the app on a physical device.
|
||||
|
||||
### Vertex AI for Firebase
|
||||
|
||||
See the [Vertex AI for Firebase README](FirebaseVertexAI#development) for
|
||||
instructions about building and testing the SDK.
|
||||
|
||||
## Building with Firebase on Apple platforms
|
||||
|
||||
Firebase provides official beta support for macOS, Catalyst, and tvOS. visionOS and watchOS
|
||||
are community supported. Thanks to community contributions for many of the multi-platform PRs.
|
||||
|
||||
At this time, most of Firebase's products are available across Apple platforms. There are still
|
||||
a few gaps, especially on visionOS and watchOS. For details about the current support matrix, see
|
||||
[this chart](https://firebase.google.com/docs/ios/learn-more#firebase_library_support_by_platform)
|
||||
in Firebase's documentation.
|
||||
|
||||
### visionOS
|
||||
|
||||
Where supported, visionOS works as expected with the exception of Firestore via Swift Package
|
||||
Manager where it is required to use the source distribution.
|
||||
|
||||
To enable the Firestore source distribution, quit Xcode and open the desired
|
||||
project from the command line with the `FIREBASE_SOURCE_FIRESTORE` environment
|
||||
variable: `open --env FIREBASE_SOURCE_FIRESTORE /path/to/project.xcodeproj`.
|
||||
To go back to using the binary distribution of Firestore, quit Xcode and open
|
||||
Xcode like normal, without the environment variable.
|
||||
|
||||
### watchOS
|
||||
Thanks to contributions from the community, many of Firebase SDKs now compile, run unit tests, and
|
||||
work on watchOS. See the [Independent Watch App Sample](Example/watchOSSample).
|
||||
|
||||
Keep in mind that watchOS is not officially supported by Firebase. While we can catch basic unit
|
||||
test issues with GitHub Actions, there may be some changes where the SDK no longer works as expected
|
||||
on watchOS. If you encounter this, please
|
||||
[file an issue](https://github.com/firebase/firebase-ios-sdk/issues).
|
||||
|
||||
During app setup in the console, you may get to a step that mentions something like "Checking if the
|
||||
app has communicated with our servers". This relies on Analytics and will not work on watchOS.
|
||||
**It's safe to ignore the message and continue**, the rest of the SDKs will work as expected.
|
||||
|
||||
#### Additional Crashlytics Notes
|
||||
* watchOS has limited support. Due to watchOS restrictions, mach exceptions and signal crashes are
|
||||
not recorded. (Crashes in SwiftUI are generated as mach exceptions, so will not be recorded)
|
||||
|
||||
## Combine
|
||||
Thanks to contributions from the community, _FirebaseCombineSwift_ contains support for Apple's Combine
|
||||
framework. This module is currently under development and not yet supported for use in production
|
||||
environments. For more details, please refer to the [docs](FirebaseCombineSwift/README.md).
|
||||
|
||||
## Roadmap
|
||||
|
||||
See [Roadmap](ROADMAP.md) for more about the Firebase Apple SDK Open Source
|
||||
plans and directions.
|
||||
|
||||
## Contributing
|
||||
|
||||
See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase
|
||||
Apple SDK.
|
||||
|
||||
## License
|
||||
|
||||
The contents of this repository are licensed under the
|
||||
[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
|
||||
|
||||
Your use of Firebase is governed by the
|
||||
[Terms of Service for Firebase Services](https://firebase.google.com/terms/).
|
||||
Reference in New Issue
Block a user