Merge pull request #121 from pocketgems/master

[ios][remoteconfig] Implement Firebase Remote Config for iOS
This commit is contained in:
Elliot Hesp 2017-05-22 09:57:40 +01:00 committed by GitHub
commit 07a7f863f6
6 changed files with 322 additions and 73 deletions

View File

@ -15,6 +15,7 @@
D96290851D6D28B80099A3EC /* RNFirebaseDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = D96290841D6D28B80099A3EC /* RNFirebaseDatabase.m */; }; D96290851D6D28B80099A3EC /* RNFirebaseDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = D96290841D6D28B80099A3EC /* RNFirebaseDatabase.m */; };
D9D62E7C1D6D86FD003D826D /* RNFirebaseStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = D9D62E7B1D6D86FD003D826D /* RNFirebaseStorage.m */; }; D9D62E7C1D6D86FD003D826D /* RNFirebaseStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = D9D62E7B1D6D86FD003D826D /* RNFirebaseStorage.m */; };
D9D62E801D6D8717003D826D /* RNFirebaseAuth.m in Sources */ = {isa = PBXBuildFile; fileRef = D9D62E7F1D6D8717003D826D /* RNFirebaseAuth.m */; }; D9D62E801D6D8717003D826D /* RNFirebaseAuth.m in Sources */ = {isa = PBXBuildFile; fileRef = D9D62E7F1D6D8717003D826D /* RNFirebaseAuth.m */; };
EC841F001ECE79D6001AD3D9 /* RNFirebaseRemoteConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = EC841EFF1ECE79D6001AD3D9 /* RNFirebaseRemoteConfig.m */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
@ -48,6 +49,8 @@
D9D62E7B1D6D86FD003D826D /* RNFirebaseStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNFirebaseStorage.m; path = RNFirebase/RNFirebaseStorage.m; sourceTree = "<group>"; }; D9D62E7B1D6D86FD003D826D /* RNFirebaseStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNFirebaseStorage.m; path = RNFirebase/RNFirebaseStorage.m; sourceTree = "<group>"; };
D9D62E7E1D6D8717003D826D /* RNFirebaseAuth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNFirebaseAuth.h; path = RNFirebase/RNFirebaseAuth.h; sourceTree = "<group>"; }; D9D62E7E1D6D8717003D826D /* RNFirebaseAuth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNFirebaseAuth.h; path = RNFirebase/RNFirebaseAuth.h; sourceTree = "<group>"; };
D9D62E7F1D6D8717003D826D /* RNFirebaseAuth.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNFirebaseAuth.m; path = RNFirebase/RNFirebaseAuth.m; sourceTree = "<group>"; }; D9D62E7F1D6D8717003D826D /* RNFirebaseAuth.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNFirebaseAuth.m; path = RNFirebase/RNFirebaseAuth.m; sourceTree = "<group>"; };
EC841EFE1ECE79D6001AD3D9 /* RNFirebaseRemoteConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNFirebaseRemoteConfig.h; path = RNFirebase/RNFirebaseRemoteConfig.h; sourceTree = "<group>"; };
EC841EFF1ECE79D6001AD3D9 /* RNFirebaseRemoteConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNFirebaseRemoteConfig.m; path = RNFirebase/RNFirebaseRemoteConfig.m; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -82,6 +85,8 @@
D96290351D6D145F0099A3EC /* Modules */ = { D96290351D6D145F0099A3EC /* Modules */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
EC841EFE1ECE79D6001AD3D9 /* RNFirebaseRemoteConfig.h */,
EC841EFF1ECE79D6001AD3D9 /* RNFirebaseRemoteConfig.m */,
D90882D41D89C18C00FB6742 /* RNFirebaseMessaging.h */, D90882D41D89C18C00FB6742 /* RNFirebaseMessaging.h */,
D90882D51D89C18C00FB6742 /* RNFirebaseMessaging.m */, D90882D51D89C18C00FB6742 /* RNFirebaseMessaging.m */,
D9D62E7E1D6D8717003D826D /* RNFirebaseAuth.h */, D9D62E7E1D6D8717003D826D /* RNFirebaseAuth.h */,
@ -163,6 +168,7 @@
D962903F1D6D15B00099A3EC /* RNFirebaseErrors.m in Sources */, D962903F1D6D15B00099A3EC /* RNFirebaseErrors.m in Sources */,
D950369E1D19C77400F7094D /* RNFirebase.m in Sources */, D950369E1D19C77400F7094D /* RNFirebase.m in Sources */,
D90882D61D89C18C00FB6742 /* RNFirebaseMessaging.m in Sources */, D90882D61D89C18C00FB6742 /* RNFirebaseMessaging.m in Sources */,
EC841F001ECE79D6001AD3D9 /* RNFirebaseRemoteConfig.m in Sources */,
29C199451EA7A851007B6BF8 /* RNFirebaseCrash.m in Sources */, 29C199451EA7A851007B6BF8 /* RNFirebaseCrash.m in Sources */,
D96290851D6D28B80099A3EC /* RNFirebaseDatabase.m in Sources */, D96290851D6D28B80099A3EC /* RNFirebaseDatabase.m in Sources */,
); );

View File

@ -243,82 +243,10 @@ RCT_EXPORT_METHOD(configure:(RCTResponseSenderBlock)callback)
callback:callback]; callback:callback];
} }
#pragma mark - Storage #pragma mark Storage
#pragma mark RemoteConfig #pragma mark RemoteConfig
// RCT_EXPORT_METHOD(setDefaultRemoteConfig:(NSDictionary *)props
// callback:(RCTResponseSenderBlock) callback)
// {
// if (!self.remoteConfigInstance) {
// // Create remote Config instance
// self.remoteConfigInstance = [FIRRemoteConfig remoteConfig];
// }
// [self.remoteConfigInstance setDefaults:props];
// callback(@[[NSNull null], props]);
// }
// RCT_EXPORT_METHOD(setDev:(RCTResponseSenderBlock) callback)
// {
// FIRRemoteConfigSettings *remoteConfigSettings = [[FIRRemoteConfigSettings alloc] initWithDeveloperModeEnabled:YES];
// self.remoteConfigInstance.configSettings = remoteConfigSettings;
// callback(@[[NSNull null], @"ok"]);
// }
// RCT_EXPORT_METHOD(configValueForKey:(NSString *)name
// callback:(RCTResponseSenderBlock) callback)
// {
// if (!self.remoteConfigInstance) {
// NSDictionary *err = @{
// @"error": @"No configuration instance",
// @"msg": @"No configuration instance set. Please call setDefaultRemoteConfig before using this feature"
// };
// callback(@[err]);
// }
// FIRRemoteConfigValue *value = [self.remoteConfigInstance configValueForKey:name];
// NSString *valueStr = value.stringValue;
// if (valueStr == nil) {
// valueStr = @"";
// }
// callback(@[[NSNull null], valueStr]);
// }
// RCT_EXPORT_METHOD(fetchWithExpiration:(NSNumber*)expirationSeconds
// callback:(RCTResponseSenderBlock) callback)
// {
// if (!self.remoteConfigInstance) {
// NSDictionary *err = @{
// @"error": @"No configuration instance",
// @"msg": @"No configuration instance set. Please call setDefaultRemoteConfig before using this feature"
// };
// callback(@[err]);
// }
// NSTimeInterval expirationDuration = [expirationSeconds doubleValue];
// [self.remoteConfigInstance fetchWithExpirationDuration:expirationDuration completionHandler:^(FIRRemoteConfigFetchStatus status, NSError *error) {
// if (status == FIRRemoteConfigFetchStatusSuccess) {
// NSLog(@"Config fetched!");
// [self.remoteConfigInstance activateFetched];
// callback(@[[NSNull null], @(YES)]);
// } else {
// NSLog(@"Error %@", error.localizedDescription);
// NSDictionary *err = @{
// @"error": @"No configuration instance",
// @"msg": [error localizedDescription]
// };
// callback(@[err]);
// }
// }];
// }
#pragma mark Database #pragma mark Database
#pragma mark Messaging #pragma mark Messaging

View File

@ -0,0 +1,14 @@
#ifndef RNFirebaseRemoteConfig_h
#define RNFirebaseRemoteConfig_h
#if __has_include(<React/RCTBridgeModule.h>)
#import <React/RCTBridgeModule.h>
#else // Compatibility for RN version < 0.40
#import "RCTBridgeModule.h"
#endif
@interface RNFirebaseRemoteConfig : NSObject <RCTBridgeModule>
@end
#endif

View File

@ -0,0 +1,161 @@
#import "RNFirebaseRemoteConfig.h"
#if __has_include(<React/RCTConvert.h>)
#import <React/RCTConvert.h>
#else // Compatibility for RN version < 0.40
#import "RCTConvert.h"
#endif
#if __has_include(<React/RCTUtils.h>)
#import <React/RCTUtils.h>
#else // Compatibility for RN version < 0.40
#import "RCTUtils.h"
#endif
#import "FirebaseRemoteConfig/FirebaseRemoteConfig.h"
NSString *convertFIRRemoteConfigFetchStatusToNSString(FIRRemoteConfigFetchStatus value)
{
switch(value){
case FIRRemoteConfigFetchStatusNoFetchYet:
return @"remoteConfitFetchStatusNoFetchYet";
case FIRRemoteConfigFetchStatusSuccess:
return @"remoteConfitFetchStatusSuccess";
case FIRRemoteConfigFetchStatusFailure:
return @"remoteConfitFetchStatusFailure";
case FIRRemoteConfigFetchStatusThrottled:
return @"remoteConfitFetchStatusThrottled";
default:
return @"remoteConfitFetchStatusFailure";
}
}
NSString *convertFIRRemoteConfigSourceToNSString(FIRRemoteConfigSource value)
{
switch(value) {
case FIRRemoteConfigSourceRemote:
return @"remoteConfigSourceRemote";
case FIRRemoteConfigSourceDefault:
return @"remoteConfigSourceDefault";
case FIRRemoteConfigSourceStatic:
return @"remoteConfigSourceStatic";
default:
return @"remoteConfigSourceStatic";
}
}
NSDictionary *convertFIRRemoteConfigValueToNSDictionary(FIRRemoteConfigValue *value)
{
return @{
@"stringValue" : value.stringValue ?: [NSNull null],
@"numberValue" : value.numberValue ?: [NSNull null],
@"dataValue" : value.dataValue ? [value.dataValue base64EncodedStringWithOptions:0] : [NSNull null],
@"boolValue" : @(value.boolValue),
@"source" : convertFIRRemoteConfigSourceToNSString(value.source)
};
}
@interface RNFirebaseRemoteConfig ()
@property (nonatomic, readwrite, weak) FIRRemoteConfig *remoteConfig;
@end
@implementation RNFirebaseRemoteConfig
RCT_EXPORT_MODULE(RNFirebaseRemoteConfig);
- (id)init
{
if (self = [super init]) {
_remoteConfig = [FIRRemoteConfig remoteConfig];
}
return self;
}
RCT_EXPORT_METHOD(enableDeveloperMode)
{
FIRRemoteConfigSettings *remoteConfigSettings = [[FIRRemoteConfigSettings alloc] initWithDeveloperModeEnabled:YES];
self.remoteConfig.configSettings = remoteConfigSettings;
}
RCT_EXPORT_METHOD(fetch:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
[self.remoteConfig fetchWithCompletionHandler:^(FIRRemoteConfigFetchStatus status, NSError *__nullable error) {
if (error) {
RCTLogError(@"\nError: %@", RCTJSErrorFromNSError(error));
reject(convertFIRRemoteConfigFetchStatusToNSString(status), error.localizedDescription, error);
} else {
resolve(convertFIRRemoteConfigFetchStatusToNSString(status));
}
}];
}
RCT_EXPORT_METHOD(fetchWithExpirationDuration:(nonnull NSNumber *)expirationDuration
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
[self.remoteConfig fetchWithExpirationDuration:expirationDuration.doubleValue completionHandler:^(FIRRemoteConfigFetchStatus status, NSError *__nullable error) {
if (error) {
RCTLogError(@"\nError: %@", RCTJSErrorFromNSError(error));
reject(convertFIRRemoteConfigFetchStatusToNSString(status), error.localizedDescription, error);
} else {
resolve(convertFIRRemoteConfigFetchStatusToNSString(status));
}
}];
}
RCT_EXPORT_METHOD(activateFetched:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
BOOL status = [self.remoteConfig activateFetched];
if (status) {
resolve(@(status));
} else {
reject(@"activate_failed", @"Did not activate remote config", nil);
}
}
RCT_EXPORT_METHOD(configValueForKey:(NSString *)key
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
FIRRemoteConfigValue *value = [self.remoteConfig configValueForKey:key];
resolve(convertFIRRemoteConfigValueToNSDictionary(value));
}
RCT_EXPORT_METHOD(configValuesForKeys:(NSArray *)keys
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSMutableDictionary *res = [[NSMutableDictionary alloc] init];
for (NSString *key in keys) {
FIRRemoteConfigValue *value = [self.remoteConfig configValueForKey:key];
res[key] = convertFIRRemoteConfigValueToNSDictionary(value);
}
resolve(res);
}
RCT_EXPORT_METHOD(keysWithPrefix:(NSString *)prefix
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSSet *keys = [self.remoteConfig keysWithPrefix:prefix];
if (keys.count) {
resolve(keys);
} else {
reject(@"no_keys_matching_prefix", @"There are no keys that match that prefix", nil);
}
}
RCT_EXPORT_METHOD(setDefaults:(NSDictionary *)defaults)
{
[self.remoteConfig setDefaults:defaults];
}
RCT_EXPORT_METHOD(setDefaultsFromPlistFileName:(NSString *)fileName)
{
[self.remoteConfig setDefaultsFromPlistFileName:fileName];
}
@end

View File

@ -14,6 +14,7 @@ import Database, { statics as DatabaseStatics } from './modules/database';
import Messaging, { statics as MessagingStatics } from './modules/messaging'; import Messaging, { statics as MessagingStatics } from './modules/messaging';
import Analytics from './modules/analytics'; import Analytics from './modules/analytics';
import Crash from './modules/crash'; import Crash from './modules/crash';
import RemoteConfig from './modules/remoteConfig';
const instances: Object = { default: null }; const instances: Object = { default: null };
const FirebaseModule = NativeModules.RNFirebase; const FirebaseModule = NativeModules.RNFirebase;
@ -40,6 +41,7 @@ export default class Firebase {
database: Function; database: Function;
analytics: Function; analytics: Function;
messaging: Function; messaging: Function;
remoteConfig: Function;
eventHandlers: Object; eventHandlers: Object;
debug: boolean; debug: boolean;
@ -83,6 +85,7 @@ export default class Firebase {
this.messaging = this._staticsOrInstance('messaging', MessagingStatics, Messaging); this.messaging = this._staticsOrInstance('messaging', MessagingStatics, Messaging);
this.analytics = this._staticsOrInstance('analytics', {}, Analytics); this.analytics = this._staticsOrInstance('analytics', {}, Analytics);
this.crash = this._staticsOrInstance('crash', {}, Crash); this.crash = this._staticsOrInstance('crash', {}, Crash);
this.remoteConfig = this._staticsOrInstance('remoteConfig', {}, RemoteConfig);
// init auth to start listeners // init auth to start listeners
this.auth(); this.auth();

View File

@ -0,0 +1,137 @@
/**
* @flow
*/
import { NativeModules } from 'react-native';
import { Base } from './../base';
const FirebaseRemoteConfig = NativeModules.RNFirebaseRemoteConfig;
type RemoteConfigOptions = {}
/**
* @class Config
*/
export default class RemoteConfig extends Base {
constructor(firebase: Object, options: RemoteConfigOptions = {}) {
super(firebase, options);
this.namespace = 'firebase:config';
this.developerModeEnabled = false;
}
/**
* Enable Remote Config developer mode to allow for frequent refreshes of the cache
*/
enableDeveloperMode() {
if (!this.developerModeEnabled) {
this.log.debug('Enabled developer mode');
FirebaseRemoteConfig.enableDeveloperMode();
this.developerModeEnabled = true
}
}
/**
* Fetches Remote Config data
* Call activateFetched to make fetched data available in app
* @returns {*|Promise.<String>}:
* One of
* - remoteConfitFetchStatusSuccess
* - remoteConfitFetchStatusFailure
* - remoteConfitFetchStatusThrottled
* rejects on remoteConfitFetchStatusFailure and remoteConfitFetchStatusThrottled
* resolves on remoteConfitFetchStatusSuccess
*/
fetch() {
this.log.debug('Fetching remote config data');
return FirebaseRemoteConfig.fetch();
}
/**
* Fetches Remote Config data and sets a duration that specifies how long config data lasts.
* Call activateFetched to make fetched data available
* @param expiration: Duration that defines how long fetched config data is available, in
* seconds. When the config data expires, a new fetch is required.
* @returns {*|Promise.<Bool>}
* One of
* - remoteConfitFetchStatusSuccess
* - remoteConfitFetchStatusFailure
* - remoteConfitFetchStatusThrottled
* rejects on remoteConfitFetchStatusFailure and remoteConfitFetchStatusThrottled
* resolves on remoteConfitFetchStatusSuccess
*/
fetchWithExpirationDuration(expiration: Number) {
this.log.debug(`Fetching remote config data with expiration ${expiration.toString()}`);
return FirebaseRemoteConfig.fetchWithExpirationDuration(expiration);
}
/**
* Applies Fetched Config data to the Active Config
* @returns {*|Promise.<Bool>}
* resolves if there was a Fetched Config, and it was activated,
* rejects if no Fetched Config was found, or the Fetched Config was already activated.
*/
activateFetched() {
this.log.debug('Activating remote config');
return FirebaseRemoteConfig.activateFetched();
}
/**
* Gets the config value of the default namespace.
* @param key: Config key
* @returns {*|Promise.<Object>}, will always resolve
* Object looks like
* {
* "stringValue" : stringValue,
* "numberValue" : numberValue,
* "dataValue" : dataValue,
* "boolValue" : boolValue,
* "source" : OneOf<String>(remoteConfigSourceRemote|remoteConfigSourceDefault|remoteConfigSourceStatic)
* }
*/
configValueForKey(key: String) {
return FirebaseRemoteConfig.configValueForKey(key);
}
/**
* Gets the config value of the default namespace.
* @param key: Config key
* @returns {*|Promise.<Object>}, will always resolve.
* Result will be a dictionary of key and config objects
* Object looks like
* {
* "stringValue" : stringValue,
* "numberValue" : numberValue,
* "dataValue" : dataValue,
* "boolValue" : boolValue,
* "source" : OneOf<String>(remoteConfigSourceRemote|remoteConfigSourceDefault|remoteConfigSourceStatic)
* }
*/
configValuesForKeys(keys: Array<String>) {
return FirebaseRemoteConfig.configValuesForKeys(keys);
}
/**
* Get the set of parameter keys that start with the given prefix, from the default namespace
* @param prefix: The key prefix to look for. If prefix is nil or empty, returns all the keys.
* @returns {*|Promise.<Array<String>>}
*/
keysWithPrefix(prefix: String) {
return FirebaseRemoteConfig.keysWithPrefix(prefix);
}
/**
* Sets config defaults for parameter keys and values in the default namespace config.
* @param defaults: A dictionary mapping a String key to a Object values.
*/
setDefaults(defaults: Object) {
FirebaseRemoteConfig.setDefaults(defaults);
}
/**
* Sets default configs from plist for default namespace;
* @param filename: The plist file name, with no file name extension
*/
setDefaultsFromPlistFileName(filename: String) {
FirebaseRemoteConfig.setDefaultsFromPlistFileName(filename);
}
}