[v6] Implement Remote Config (#1896)

This commit is contained in:
Ilja Daderko
2019-03-26 15:03:57 +02:00
committed by Mike Diarmid
parent 8ef8038c94
commit fb7109e52a
51 changed files with 3013 additions and 837 deletions

View File

@@ -158,6 +158,20 @@ await analytics().setUserId('12345678');
- [NEW] Added support for `firebase.perf.Trace.removeMetric(metricName: string)`
- [NEW] Added support for `firebase.perf.Trace.getMetrics(): { [key: string]: number }`
## Remote Config (config)
- [NEW] Added a new `fetchAndActivate` method - this fetches the config and activate it without the need to call `activateFetch()` separately
- [NEW] Added a new `getConfigSettings` method - this provides the following properties; `lastFetchTime`, `lastFetchStatus` & `isDeveloperModeEnabled`
- [NEW] Added a new `setConfigSettings` method - this allows setting `isDeveloperModeEnabled`, replaces the `enableDeveloperMode` method
- [NEW] Added a new `getValuesByKeysPrefix` method - this will retrieve all values where the key matches the prefix provided, this saves having to call `getKeysByPrefix` and then `getValues` separately
- [BREAKING] `setDefaultsFromResource` now returns a Promise that resolves when completed, this will reject with code `config/resouce_not_found` if the file could not be found
- [BREAKING] `setDefaultsFromResource` now expects a resource file name for Android to match iOS, formerly this required a resource id (something you would not have in RN as this was generated at build time by Android)
- We're writing up a guide for this on the new documentation website, showing how to use the plist/xml defaults files on each platform
- [BREAKING] `enableDeveloperMode` has been removed, you can now use `setConfigSettings({ isDeveloperModeEnabled: boolean })` instead
- [BREAKING] `setDefaults` now returns a Promise that resolves when completed
## Messaging
- [NEW] Support `setAutoInitEnabled(enabled: boolean)` - this is useful for opt-in first flows

File diff suppressed because it is too large Load Diff

View File

@@ -15,18 +15,18 @@
"tests:packager:chrome": "cd tests && react-native start --platforms ios,android",
"tests:packager:jet": "REACT_DEBUGGER='echo nope' cd tests && react-native start",
"tests:packager:jet-reset-cache": "REACT_DEBUGGER='echo nope' cd tests && react-native start --reset-cache",
"tests:android:build": "cd tests && detox build --configuration android.emu.debug",
"tests:android:build-release": "cd tests && detox build --configuration android.emu.release",
"tests:android:test": "cd tests && detox test --configuration android.emu.debug",
"tests:android:test-reuse": "cd tests && detox test --configuration android.emu.debug --reuse",
"tests:android:build": "cd tests && ./node_modules/.bin/detox build --configuration android.emu.debug",
"tests:android:build-release": "cd tests && ./node_modules/.bin/detox build --configuration android.emu.release",
"tests:android:test": "cd tests && ./node_modules/.bin/detox test --configuration android.emu.debug",
"tests:android:test-reuse": "cd tests && ./node_modules/.bin/detox test --configuration android.emu.debug --reuse",
"tests:android:test-cover": "cd tests && nyc ./node_modules/.bin/detox test --configuration android.emu.debug",
"tests:android:test-cover-reuse": "cd tests && nyc detox test --configuration android.emu.debug --reuse",
"tests:ios:build": "cd tests && detox build --configuration ios.sim.debug",
"tests:ios:build-release": "cd tests && detox build --configuration ios.sim.release",
"tests:ios:test": "cd tests && detox test --configuration ios.sim.debug --loglevel warn",
"tests:ios:test-reuse": "cd tests && detox test --configuration ios.sim.debug --reuse --loglevel warn",
"tests:android:test-cover-reuse": "cd tests && nyc ./node_modules/.bin/detox test --configuration android.emu.debug --reuse",
"tests:ios:build": "cd tests && ./node_modules/.bin/detox build --configuration ios.sim.debug",
"tests:ios:build-release": "cd tests && ./node_modules/.bin/detox build --configuration ios.sim.release",
"tests:ios:test": "cd tests && ./node_modules/.bin/detox test --configuration ios.sim.debug --loglevel warn",
"tests:ios:test-reuse": "cd tests && ./node_modules/.bin/detox test --configuration ios.sim.debug --reuse --loglevel warn",
"tests:ios:test-cover": "cd tests && nyc ./node_modules/.bin/detox test --configuration ios.sim.debug",
"tests:ios:test-cover-reuse": "cd tests && nyc detox test --configuration ios.sim.debug --reuse --loglevel warn",
"tests:ios:test-cover-reuse": "cd tests && nyc ./node_modules/.bin/detox test --configuration ios.sim.debug --reuse --loglevel warn",
"tests:ios:pod:install": "cd tests && cd ios && rm -rf ReactNativeFirebaseDemo.xcworkspace && pod install && cd ..",
"npm:version:release:patch": "echo '!!🔴!! RELEASE !!🔴!!' && lerna version patch --exact --force-publish=*",
"npm:version:release:minor": "echo '!!🔴!! RELEASE !!🔴!!' && lerna version minor --exact --force-publish=*",

View File

@@ -55,7 +55,7 @@ repositories {
}
dependencies {
api project(':@react-native-firebase/app')
api project(':@react-native-firebase_app')
implementation "com.google.firebase:firebase-analytics:${ReactNative.ext.getVersion("firebase", "analytics")}"
implementation "com.google.android.gms:play-services-base:${ReactNative.ext.getVersion("googlePlayServices", "base")}"
}

View File

@@ -1 +1 @@
rootProject.name = '@react-native-firebase/analytics'
rootProject.name = '@react-native-firebase_analytics'

View File

@@ -17,6 +17,7 @@ package io.invertase.firebase.analytics;
*
*/
import android.annotation.SuppressLint;
import android.app.Activity;
import com.facebook.react.bridge.Arguments;
@@ -38,6 +39,7 @@ public class ReactNativeFirebaseAnalyticsModule extends ReactNativeFirebaseModul
super(reactContext, TAG);
}
@SuppressLint("MissingPermission")
@ReactMethod
public void logEvent(String name, @Nullable ReadableMap params, Promise promise) {
try {
@@ -48,6 +50,7 @@ public class ReactNativeFirebaseAnalyticsModule extends ReactNativeFirebaseModul
}
}
@SuppressLint("MissingPermission")
@ReactMethod
public void setAnalyticsCollectionEnabled(Boolean enabled, Promise promise) {
try {
@@ -63,6 +66,7 @@ public class ReactNativeFirebaseAnalyticsModule extends ReactNativeFirebaseModul
Activity activity = getActivity();
if (activity != null) {
activity.runOnUiThread(new Runnable() {
@SuppressLint("MissingPermission")
@Override
public void run() {
try {
@@ -81,6 +85,7 @@ public class ReactNativeFirebaseAnalyticsModule extends ReactNativeFirebaseModul
}
}
@SuppressLint("MissingPermission")
@ReactMethod
public void setMinimumSessionDuration(double milliseconds, Promise promise) {
try {
@@ -91,6 +96,7 @@ public class ReactNativeFirebaseAnalyticsModule extends ReactNativeFirebaseModul
}
}
@SuppressLint("MissingPermission")
@ReactMethod
public void setSessionTimeoutDuration(double milliseconds, Promise promise) {
try {
@@ -101,6 +107,7 @@ public class ReactNativeFirebaseAnalyticsModule extends ReactNativeFirebaseModul
}
}
@SuppressLint("MissingPermission")
@ReactMethod
public void setUserId(String id, Promise promise) {
try {
@@ -111,6 +118,7 @@ public class ReactNativeFirebaseAnalyticsModule extends ReactNativeFirebaseModul
}
}
@SuppressLint("MissingPermission")
@ReactMethod
public void setUserProperty(String name, String value, Promise promise) {
try {
@@ -125,7 +133,7 @@ public class ReactNativeFirebaseAnalyticsModule extends ReactNativeFirebaseModul
public void setUserProperties(ReadableMap properties, Promise promise) {
try {
ReadableMapKeySetIterator iterator = properties.keySetIterator();
FirebaseAnalytics firebaseAnalytics = FirebaseAnalytics.getInstance(getContext());
@SuppressLint("MissingPermission") FirebaseAnalytics firebaseAnalytics = FirebaseAnalytics.getInstance(getContext());
while (iterator.hasNextKey()) {
String name = iterator.nextKey();
@@ -139,6 +147,7 @@ public class ReactNativeFirebaseAnalyticsModule extends ReactNativeFirebaseModul
}
}
@SuppressLint("MissingPermission")
@ReactMethod
public void resetAnalyticsData(Promise promise) {
try {

View File

@@ -1 +1 @@
rootProject.name = '@react-native-firebase/app'
rootProject.name = '@react-native-firebase_app'

View File

@@ -30,6 +30,8 @@ import com.facebook.react.bridge.WritableMap;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nonnull;
import io.invertase.firebase.interfaces.ContextProvider;
public class ReactNativeFirebaseModule extends ReactContextBaseJavaModule implements ContextProvider {
@@ -72,10 +74,17 @@ public class ReactNativeFirebaseModule extends ReactContextBaseJavaModule implem
}
public void rejectPromiseWithExceptionMap(Promise promise, Exception exception) {
// TODO hook into crashlytics - report as handled exception?
promise.reject(exception, getExceptionMap(exception));
}
public void rejectPromiseWithCodeAndMessage(Promise promise, String code, String message) {
WritableMap userInfoMap = Arguments.createMap();
userInfoMap.putString("code", code);
userInfoMap.putString("message", message);
promise.reject(code, message, userInfoMap);
}
@Nonnull
@Override
public String getName() {
return "RNFB" + moduleName + "Module";

View File

@@ -72,16 +72,11 @@ NSString *const DEFAULT_APP_NAME = @"__FIRAPP_DEFAULT";
NSError *error = [NSError errorWithDomain:RNFBErrorDomain code:666 userInfo:userInfo];
// TODO hook into crashlytics - report as handled exception?
reject(exception.name, exception.reason, error);
}
+ (void)rejectPromiseWithUserInfo:(RCTPromiseRejectBlock)reject userInfo:(NSMutableDictionary *)userInfo; {
NSError *error = [NSError errorWithDomain:RNFBErrorDomain code:666 userInfo:userInfo];
// TODO hook into crashlytics - report as handled exception?
reject(userInfo[@"code"], userInfo[@"message"], error);
}
@end

View File

@@ -28,9 +28,9 @@ end
def react_native_firebase!(config = {})
react_native_firebase_path = config.fetch(:react_native_firebase_path, '../node_modules/@react-native-firebase')
known_firebase_modules = %w(app analytics crashlytics fiam functions firestore iid invites perf utils)
known_firebase_modules = %w(app analytics config crashlytics fiam functions firestore iid invites perf utils)
# TODO: validate versions / set pod versions
# TODO(salakar): validate versions / set pod versions
app_package = JSON.parse(File.read("#{react_native_firebase_path}/#{known_firebase_modules[0]}/package.json"))
app_package_version = app_package['version']

View File

@@ -55,7 +55,7 @@ repositories {
}
dependencies {
api project(':@react-native-firebase/app')
api project(':@react-native-firebase_app')
implementation "com.google.firebase:firebase-auth:${ReactNative.ext.getVersion("firebase", "auth")}"
implementation "com.google.android.gms:play-services-base:${ReactNative.ext.getVersion("googlePlayServices", "base")}"
}

View File

@@ -1 +1 @@
rootProject.name = '@react-native-firebase/auth'
rootProject.name = '@react-native-firebase_auth'

View File

@@ -55,7 +55,7 @@ repositories {
}
dependencies {
api project(':@react-native-firebase/app')
api project(':@react-native-firebase_app')
implementation "com.google.firebase:firebase-config:${ReactNative.ext.getVersion("firebase", "config")}"
implementation "com.google.android.gms:play-services-base:${ReactNative.ext.getVersion("googlePlayServices", "base")}"
}

View File

@@ -1 +1 @@
rootProject.name = '@react-native-firebase/config'
rootProject.name = '@react-native-firebase_config'

View File

@@ -17,24 +17,247 @@ package io.invertase.firebase.config;
*
*/
import android.app.Activity;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.google.android.gms.tasks.Task;
import com.google.firebase.remoteconfig.FirebaseRemoteConfig;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigFetchThrottledException;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigInfo;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Set;
import io.invertase.firebase.common.ReactNativeFirebaseModule;
import static com.google.firebase.remoteconfig.FirebaseRemoteConfig.LAST_FETCH_STATUS_FAILURE;
import static com.google.firebase.remoteconfig.FirebaseRemoteConfig.LAST_FETCH_STATUS_NO_FETCH_YET;
import static com.google.firebase.remoteconfig.FirebaseRemoteConfig.LAST_FETCH_STATUS_SUCCESS;
import static com.google.firebase.remoteconfig.FirebaseRemoteConfig.LAST_FETCH_STATUS_THROTTLED;
public class ReactNativeFirebaseConfigModule extends ReactNativeFirebaseModule {
private static final String TAG = "Config";
private static final String STRING_VALUE = "stringValue";
private static final String BOOL_VALUE = "boolValue";
private static final String NUMBER_VALUE = "numberValue";
private static final String SOURCE = "source";
ReactNativeFirebaseConfigModule(ReactApplicationContext reactContext) {
super(reactContext, TAG);
}
@ReactMethod
public void activateFetched(Promise promise) {
boolean activated = FirebaseRemoteConfig.getInstance().activateFetched();
promise.resolve(activated);
}
@ReactMethod
public void fetch(double cacheExpirationSeconds, boolean activate, Promise promise) {
Task<Void> fetchTask;
if (cacheExpirationSeconds == -1) {
fetchTask = FirebaseRemoteConfig.getInstance().fetch((long) cacheExpirationSeconds);
} else {
fetchTask = FirebaseRemoteConfig.getInstance().fetch();
}
fetchTask.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
if (activate) {
promise.resolve(FirebaseRemoteConfig.getInstance().activateFetched());
} else {
promise.resolve(null);
}
} else {
if (task.getException() instanceof FirebaseRemoteConfigFetchThrottledException) {
rejectPromiseWithCodeAndMessage(
promise,
"throttled",
"fetch() operation cannot be completed successfully, due to throttling."
);
} else {
rejectPromiseWithCodeAndMessage(
promise,
"failure",
"fetch() operation cannot be completed successfully."
);
}
}
});
}
@ReactMethod
public void getConfigSettings(Promise promise) {
WritableMap configSettingsMap = Arguments.createMap();
FirebaseRemoteConfigInfo remoteConfigInfo = FirebaseRemoteConfig.getInstance().getInfo();
FirebaseRemoteConfigSettings remoteConfigSettings = remoteConfigInfo.getConfigSettings();
configSettingsMap.putDouble("lastFetchTime", remoteConfigInfo.getFetchTimeMillis());
configSettingsMap.putString(
"lastFetchStatus",
lastFetchStatusToString(remoteConfigInfo.getLastFetchStatus())
);
configSettingsMap.putBoolean(
"isDeveloperModeEnabled",
remoteConfigSettings.isDeveloperModeEnabled()
);
promise.resolve(configSettingsMap);
}
@ReactMethod
public void setConfigSettings(ReadableMap configSettings, Promise promise) {
FirebaseRemoteConfigSettings.Builder configSettingsBuilder = new FirebaseRemoteConfigSettings.Builder();
configSettingsBuilder.setDeveloperModeEnabled(configSettings.getBoolean("isDeveloperModeEnabled"));
FirebaseRemoteConfig.getInstance().setConfigSettings(configSettingsBuilder.build());
getConfigSettings(promise);
}
@ReactMethod
public void setDefaults(ReadableMap defaults, Promise promise) {
FirebaseRemoteConfig.getInstance().setDefaults(defaults.toHashMap());
promise.resolve(null);
}
@ReactMethod
public void setDefaultsFromResource(String resourceName, Promise promise) {
int resourceId = getXmlResourceIdByName(resourceName);
XmlResourceParser xmlResourceParser = null;
try {
xmlResourceParser = getApplicationContext().getResources().getXml(resourceId);
} catch (Resources.NotFoundException nfe) {
// do nothing
}
if (xmlResourceParser != null) {
FirebaseRemoteConfig.getInstance().setDefaults(resourceId);
promise.resolve(null);
} else {
rejectPromiseWithCodeAndMessage(
promise,
"resource_not_found",
"The specified resource name was not found."
);
}
}
private int getXmlResourceIdByName(String name) {
String packageName = getApplicationContext().getPackageName();
return getApplicationContext().getResources().getIdentifier(name, "xml", packageName);
}
@ReactMethod
public void getValuesByKeysPrefix(String prefix, Promise promise) {
Set<String> keys = FirebaseRemoteConfig.getInstance().getKeysByPrefix(prefix);
WritableMap writableMap = Arguments.createMap();
for (String key : keys) {
FirebaseRemoteConfigValue configValue = FirebaseRemoteConfig.getInstance().getValue(key);
writableMap.putMap(key, convertRemoteConfigValue(configValue));
}
promise.resolve(writableMap);
}
@ReactMethod
public void getKeysByPrefix(String prefix, Promise promise) {
WritableArray keysByPrefix = Arguments.createArray();
Set<String> keys = FirebaseRemoteConfig.getInstance().getKeysByPrefix(prefix);
for (String key : keys) {
keysByPrefix.pushString(key);
}
promise.resolve(keysByPrefix);
}
@ReactMethod
public void getValue(String key, Promise promise) {
FirebaseRemoteConfigValue configValue = FirebaseRemoteConfig.getInstance().getValue(key);
promise.resolve(convertRemoteConfigValue(configValue));
}
@ReactMethod
public void getValues(ReadableArray keys, Promise promise) {
WritableArray valuesArray = Arguments.createArray();
ArrayList<Object> keysList = keys.toArrayList();
for (Object key : keysList) {
FirebaseRemoteConfigValue configValue = FirebaseRemoteConfig
.getInstance()
.getValue((String) key);
valuesArray.pushMap(convertRemoteConfigValue(configValue));
}
promise.resolve(valuesArray);
}
private WritableMap convertRemoteConfigValue(FirebaseRemoteConfigValue value) {
WritableMap map = Arguments.createMap();
map.putString(STRING_VALUE, value.asString());
try {
boolean booleanValue = value.asBoolean();
map.putBoolean(BOOL_VALUE, booleanValue);
} catch (Exception e) {
map.putNull(BOOL_VALUE);
}
try {
double numberValue = value.asDouble();
map.putDouble(NUMBER_VALUE, numberValue);
} catch (Exception e) {
map.putNull(NUMBER_VALUE);
}
switch (value.getSource()) {
case FirebaseRemoteConfig.VALUE_SOURCE_DEFAULT:
map.putString(SOURCE, "default");
break;
case FirebaseRemoteConfig.VALUE_SOURCE_REMOTE:
map.putString(SOURCE, "remote");
break;
default:
map.putString(SOURCE, "static");
}
return map;
}
private String lastFetchStatusToString(int fetchStatus) {
String status = "unknown";
switch (fetchStatus) {
case LAST_FETCH_STATUS_SUCCESS:
status = "success";
break;
case LAST_FETCH_STATUS_FAILURE:
status = "failure";
break;
case LAST_FETCH_STATUS_NO_FETCH_YET:
status = "no_fetch_yet";
break;
case LAST_FETCH_STATUS_THROTTLED:
status = "throttled";
break;
}
return status;
}
}

View File

@@ -22,23 +22,333 @@ describe('config()', () => {
should.exist(app.config);
app.config().app.should.equal(app);
});
});
// removing as pending if module.options.hasMultiAppSupport = true
xit('supports multiple apps', async () => {
firebase.config().app.name.should.equal('[DEFAULT]');
firebase
.config(firebase.app('secondaryFromNative'))
.app.name.should.equal('secondaryFromNative');
firebase
.app('secondaryFromNative')
.config()
.app.name.should.equal('secondaryFromNative');
describe('fetch()', () => {
it('with expiration provided', () => firebase.config().fetch(0));
it('without expiration provided', () => firebase.config().fetch());
it('it throws if expiration is not a number', () => {
try {
firebase.config().fetch('foo');
return Promise.reject(new Error('Did not throw'));
} catch (error) {
error.message.should.containEql('must be a number value');
return Promise.resolve();
}
});
});
describe('aMethod()', () => {
// TODO
describe('fetchAndActivate()', () => {
it('returns true/false if activated', async () => {
const activated = await firebase.config().fetchAndActivate(0);
activated.should.be.a.Boolean();
});
it('with expiration provided', () => firebase.config().fetchAndActivate(0));
it('without expiration provided', () => firebase.config().fetchAndActivate());
it('it throws if expiration is not a number', () => {
try {
firebase.config().fetchAndActivate('foo');
return Promise.reject(new Error('Did not throw'));
} catch (error) {
error.message.should.containEql('must be a number value');
return Promise.resolve();
}
});
});
describe('activateFetched()', () => {
it('with expiration provided', async () => {
await firebase.config().fetch(0);
const activated = await firebase.config().activateFetched();
activated.should.be.a.Boolean();
});
it('without expiration provided', async () => {
await firebase.config().fetch();
const activated = await firebase.config().activateFetched();
activated.should.be.a.Boolean();
});
});
describe('getConfigSettings()', () => {
it('gets settings', async () => {
const settings = await firebase.config().getConfigSettings();
settings.isDeveloperModeEnabled.should.be.a.Boolean();
settings.isDeveloperModeEnabled.should.equal(false);
settings.lastFetchStatus.should.be.a.String();
settings.lastFetchStatus.should.equal('success');
settings.lastFetchTime.should.be.a.Number();
});
});
describe('setConfigSettings()', () => {
it('isDeveloperModeEnabled sets correctly', async () => {
const settingsBefore = await firebase.config().getConfigSettings();
settingsBefore.isDeveloperModeEnabled.should.equal(false);
settingsBefore.isDeveloperModeEnabled.should.be.a.Boolean();
await firebase.config().setConfigSettings({ isDeveloperModeEnabled: true });
const settingsAfter = await firebase.config().getConfigSettings();
settingsAfter.isDeveloperModeEnabled.should.equal(true);
settingsAfter.isDeveloperModeEnabled.should.be.a.Boolean();
await firebase.config().setConfigSettings({ isDeveloperModeEnabled: false });
});
it('returns the new config settings', async () => {
const settings = await firebase.config().setConfigSettings({ isDeveloperModeEnabled: false });
settings.isDeveloperModeEnabled.should.be.a.Boolean();
settings.isDeveloperModeEnabled.should.equal(false);
settings.lastFetchStatus.should.be.a.String();
settings.lastFetchStatus.should.equal('success');
settings.lastFetchTime.should.be.a.Number();
});
it('it throws if no args', async () => {
try {
await firebase.config().setConfigSettings();
return Promise.reject(new Error('Did not throw'));
} catch (error) {
error.message.should.containEql('must be an object');
return Promise.resolve();
}
});
it('it throws if object does not contain isDeveloperModeEnabled key', async () => {
try {
await firebase.config().setConfigSettings({});
return Promise.reject(new Error('Did not throw'));
} catch (error) {
error.message.should.containEql(`'isDeveloperModeEnabled' key`);
return Promise.resolve();
}
});
it('it throws if isDeveloperModeEnabled key is not a boolean', async () => {
try {
await firebase.config().setConfigSettings({ isDeveloperModeEnabled: 'potato' });
return Promise.reject(new Error('Did not throw'));
} catch (error) {
error.message.should.containEql(
`'settings.isDeveloperModeEnabled' must be a boolean value`,
);
return Promise.resolve();
}
});
});
describe('getKeysByPrefix()', () => {
it('should return an object of all available values if no key prefix provided', async () => {
const config = await firebase.config().getValuesByKeysPrefix();
config.number.value.should.equal(1337);
config.number.source.should.equal('remote');
// firebase console stores as a string
config.float.value.should.equal(123.456);
config.float.source.should.equal('remote');
});
it('should return an object filtered by prefixed keys', async () => {
const config = await firebase.config().getValuesByKeysPrefix('prefix_');
Object.keys(config).length.should.equal(3);
config.prefix_1.value.should.equal(1);
config.prefix_1.source.should.equal('remote');
});
it('it throws if prefix is not a string', async () => {
try {
await firebase.config().getValuesByKeysPrefix(1337);
return Promise.reject(new Error('Did not throw'));
} catch (error) {
error.message.should.containEql('must be a string value');
return Promise.resolve();
}
});
});
describe('getKeysByPrefix()', () => {
it('should return an array of all available keys if no prefix provided', async () => {
const keys = await firebase.config().getKeysByPrefix();
keys.length.should.equal(9);
keys[0].should.be.a.String();
});
it('should return an array of prefixed keys', async () => {
const keys = await firebase.config().getKeysByPrefix('prefix_');
keys.length.should.equal(3);
keys[0].should.be.a.String();
});
it('it throws if prefix is not a string', () => {
try {
firebase.config().getKeysByPrefix(1337);
return Promise.reject(new Error('Did not throw'));
} catch (error) {
error.message.should.containEql('must be a string value');
return Promise.resolve();
}
});
});
describe('setDefaults()', () => {
it('sets default values from key values object', async () => {
await firebase.config().setDefaults({
some_key: 'I do not exist',
some_key_1: 1337,
some_key_2: true,
});
await firebase.config().fetch(0);
const values = await firebase.config().getValues(['some_key', 'some_key_1', 'some_key_2']);
values.some_key.value.should.equal('I do not exist');
values.some_key_1.value.should.equal(1337);
should.equal(values.some_key_2.value, true);
values.some_key.source.should.equal('default');
values.some_key_1.source.should.equal('default');
values.some_key_2.source.should.equal('default');
});
it('it throws if defaults object not provided', () => {
try {
firebase.config().setDefaults();
return Promise.reject(new Error('Did not throw'));
} catch (error) {
error.message.should.containEql('must be an object');
return Promise.resolve();
}
});
it('it throws if defaults arg is not an object', () => {
try {
firebase.config().setDefaults(1337);
return Promise.reject(new Error('Did not throw'));
} catch (error) {
error.message.should.containEql('must be an object');
return Promise.resolve();
}
});
});
describe('setDefaultsFromResource()', () => {
it('sets defaults from remote_config_resource_test file', async () => {
await Utils.sleep(10000);
await firebase.config().setDefaultsFromResource('remote_config_resource_test');
const config = await firebase.config().getValues(['company']);
config.company.source.should.equal('default');
config.company.value.should.equal('invertase');
});
it('it rejects if resource not found', async () => {
const [error] = await A2A(firebase.config().setDefaultsFromResource('i_do_not_exist'));
if (!error) throw new Error('Did not reject');
error.code.should.equal('config/resource_not_found');
error.message.should.containEql('was not found');
});
it('it throws if resourceName is not a string', () => {
try {
firebase.config().setDefaultsFromResource(1337);
return Promise.reject(new Error('Did not throw'));
} catch (error) {
error.message.should.containEql('must be a string value');
return Promise.resolve();
}
});
});
describe('getValue()', () => {
it('returns a value for the specified key', async () => {
const configValue = await firebase.config().getValue('string');
configValue.source.should.equal('remote');
configValue.value.should.equal('invertase');
});
it('errors if no key provided', async () => {
try {
await firebase.config().getValue();
return Promise.reject(new Error('Did not throw'));
} catch (error) {
error.message.should.containEql('must be a string');
return Promise.resolve();
}
});
it('errors if key not a string', async () => {
try {
await firebase.config().getValue(1234);
return Promise.reject(new Error('Did not throw'));
} catch (error) {
error.message.should.containEql('must be a string');
return Promise.resolve();
}
});
});
describe('getValues()', () => {
it('returns undefined for non existent keys', async () => {
const config = await firebase.config().getValues(['boopy', 'shoopy']);
should.equal(config.boopy.value, undefined);
should.equal(config.boopy.source, 'static');
should.equal(config.shoopy.value, undefined);
should.equal(config.shoopy.source, 'static');
});
it('get multiple values by an array of keys', async () => {
const config = await firebase.config().getValues(['bool', 'string', 'number']);
config.should.be.a.Object();
config.should.have.keys('bool', 'string', 'number');
const boolValue = config.bool.value;
const stringValue = config.string.value;
const numberValue = config.number.value;
boolValue.should.be.equal(true);
stringValue.should.be.equal('invertase');
numberValue.should.be.equal(1337);
});
it('errors if no args', async () => {
try {
await firebase.config().getValues();
return Promise.reject(new Error('Did not throw'));
} catch (error) {
error.message.should.containEql('must be an non empty array');
return Promise.resolve();
}
});
it('errors if not an array', async () => {
try {
await firebase.config().getValues({ foo: 'bar' });
return Promise.reject(new Error('Did not throw'));
} catch (error) {
error.message.should.containEql('must be an non empty array');
return Promise.resolve();
}
});
it('errors if array is empty', async () => {
try {
await firebase.config().getValues([]);
return Promise.reject(new Error('Did not throw'));
} catch (error) {
error.message.should.containEql('must be an non empty array');
return Promise.resolve();
}
});
it('errors if array values are not strings', async () => {
try {
await firebase.config().getValues([1, 2, 3]);
return Promise.reject(new Error('Did not throw'));
} catch (error) {
error.message.should.containEql('must be an array of strings');
return Promise.resolve();
}
});
});
});

View File

@@ -17,6 +17,7 @@ Pod::Spec.new do |s|
s.source_files = 'RNFBConfig/**/*.{h,m}'
s.dependency 'React'
s.dependency 'Firebase/Core', '~> 5.17.0'
s.dependency 'Firebase/RemoteConfig', '~> 5.17.0'
s.dependency 'RNFBApp'
s.static_framework = true
end

View File

@@ -16,13 +16,59 @@
*/
#import <React/RCTUtils.h>
#import <React/RCTConvert.h>
#import <Firebase/Firebase.h>
#import "RNFBConfigModule.h"
#import "RNFBApp/RNFBSharedUtils.h"
#import "RNFBSharedUtils.h"
@implementation RNFBConfigModule
#pragma mark -
# pragma mark Converters
NSString *convertFIRRemoteConfigFetchStatusToNSString(FIRRemoteConfigFetchStatus value) {
switch (value) {
case FIRRemoteConfigFetchStatusNoFetchYet:
return @"no_fetch_yet";
case FIRRemoteConfigFetchStatusSuccess:
return @"success";
case FIRRemoteConfigFetchStatusThrottled:
return @"throttled";
case FIRRemoteConfigFetchStatusFailure:
return @"failure";
default:
return @"unknown";
}
}
NSString *convertFIRRemoteConfigFetchStatusToNSStringDescription(FIRRemoteConfigFetchStatus value) {
switch (value) {
case FIRRemoteConfigFetchStatusThrottled:
return @"fetch() operation cannot be completed successfully, due to throttling.";
case FIRRemoteConfigFetchStatusNoFetchYet:
default:
return @"fetch() operation cannot be completed successfully.";
}
}
NSString *convertFIRRemoteConfigSourceToNSString(FIRRemoteConfigSource value) {
switch (value) {
case FIRRemoteConfigSourceDefault:
return @"default";
case FIRRemoteConfigSourceRemote:
return @"remote";
case FIRRemoteConfigSourceStatic:
return @"static";
default:
return @"unknown";
}
}
NSDictionary *convertFIRRemoteConfigValueToNSDictionary(FIRRemoteConfigValue *value) {
return @{@"stringValue": (id) value.stringValue ?: [NSNull null], @"numberValue": (id) value.numberValue ?: [NSNull null], @"boolValue": @(value.boolValue), @"source": convertFIRRemoteConfigSourceToNSString(value.source)};
}
#pragma mark -
#pragma mark Module Setup
@@ -32,7 +78,162 @@
return dispatch_get_main_queue();
}
+ (BOOL)requiresMainQueueSetup {
return NO;
}
#pragma mark -
#pragma mark Firebase Config Methods
RCT_EXPORT_METHOD(fetch:
(nonnull
NSNumber *)expirationDuration
activate: (BOOL) activate
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
FIRRemoteConfigFetchCompletion completionHandler = ^(FIRRemoteConfigFetchStatus status, NSError *__nullable error) {
if (error) {
[RNFBSharedUtils rejectPromiseWithUserInfo:reject userInfo:[@{@"code": convertFIRRemoteConfigFetchStatusToNSString(status), @"message": convertFIRRemoteConfigFetchStatusToNSStringDescription(status)} mutableCopy]];
} else {
if (activate) {
resolve(@([[FIRRemoteConfig remoteConfig] activateFetched]));
} else {
resolve([NSNull null]);
}
}
};
if (expirationDuration == @(-1)) {
[[FIRRemoteConfig remoteConfig] fetchWithExpirationDuration:expirationDuration.doubleValue completionHandler:completionHandler];
} else {
[[FIRRemoteConfig remoteConfig] fetchWithCompletionHandler:completionHandler];
}
}
RCT_EXPORT_METHOD(activateFetched:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject) {
BOOL status = [[FIRRemoteConfig remoteConfig] activateFetched];
resolve(@(status));
}
RCT_EXPORT_METHOD(getConfigSettings:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject) {
resolve([self getConfigSettings]);
}
RCT_EXPORT_METHOD(getValue:
(NSString *) key
resolver:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject) {
FIRRemoteConfigValue *value = [[FIRRemoteConfig remoteConfig] configValueForKey:key];
resolve(convertFIRRemoteConfigValueToNSDictionary(value));
}
RCT_EXPORT_METHOD(getValues:
(NSArray *) keys
resolver:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject) {
NSMutableArray *valuesArray = [[NSMutableArray alloc] init];
for (NSString *key in keys) {
FIRRemoteConfigValue *value = [[FIRRemoteConfig remoteConfig] configValueForKey:key];
[valuesArray addObject:convertFIRRemoteConfigValueToNSDictionary(value)];
}
resolve(valuesArray);
}
RCT_EXPORT_METHOD(setConfigSettings:
(NSDictionary *) configSettings
resolver:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject) {
FIRRemoteConfigSettings *remoteConfigSettings = [[FIRRemoteConfigSettings alloc] initWithDeveloperModeEnabled:[configSettings[@"isDeveloperModeEnabled"] boolValue]];
[FIRRemoteConfig remoteConfig].configSettings = remoteConfigSettings;
resolve([self getConfigSettings]);
}
RCT_EXPORT_METHOD(getKeysByPrefix:
(NSString *) prefix
resolver:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject) {
NSSet *keys = [[FIRRemoteConfig remoteConfig] keysWithPrefix:prefix];
NSMutableArray *keysArray = [[NSMutableArray alloc] init];
for (NSString *key in keys) {
[keysArray addObject:key];
}
resolve(keysArray);
}
RCT_EXPORT_METHOD(getValuesByKeysPrefix:
(NSString *) prefix
resolver:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject) {
NSSet *keys = [[FIRRemoteConfig remoteConfig] keysWithPrefix:prefix];
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary];
for (NSString *key in keys) {
FIRRemoteConfigValue *value = [[FIRRemoteConfig remoteConfig] configValueForKey:key];
mutableDictionary[key] = convertFIRRemoteConfigValueToNSDictionary(value);
}
resolve(mutableDictionary);
}
RCT_EXPORT_METHOD(setDefaults:
(NSDictionary *) defaults
resolver:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject
) {
[[FIRRemoteConfig remoteConfig] setDefaults:defaults];
resolve([NSNull null]);
}
RCT_EXPORT_METHOD(setDefaultsFromResource:
(NSString *) fileName
resolver:
(RCTPromiseResolveBlock) resolve
rejecter:
(RCTPromiseRejectBlock) reject) {
if ([[NSBundle mainBundle] pathForResource:fileName ofType:@"plist"] != nil) {
[[FIRRemoteConfig remoteConfig] setDefaultsFromPlistFileName:fileName];
resolve([NSNull null]);
} else {
[RNFBSharedUtils rejectPromiseWithUserInfo:reject userInfo:[@{@"code": @"resource_not_found", @"message": @"The specified resource name was not found."} mutableCopy]];
}
}
#pragma mark -
#pragma mark Internal Helper Methods
- (NSDictionary *)getConfigSettings {
FIRRemoteConfig *remoteConfig = [FIRRemoteConfig remoteConfig];
BOOL isDeveloperModeEnabled = [RCTConvert BOOL:@([remoteConfig configSettings].isDeveloperModeEnabled)];
NSString *lastFetchStatus = convertFIRRemoteConfigFetchStatusToNSString(remoteConfig.lastFetchStatus);
NSDate *lastFetchTime = remoteConfig.lastFetchTime;
return @{
@"isDeveloperModeEnabled": @(isDeveloperModeEnabled),
@"lastFetchTime": @(round([lastFetchTime timeIntervalSince1970])),
@"lastFetchStatus": lastFetchStatus
};
}
@end

View File

@@ -22,15 +22,129 @@ import {
} from '@react-native-firebase/app-types';
/**
* Config
* Firebase Remote Config is a cloud service that lets you change the behavior and appearance of your
* app without requiring users to download an app update. When using Remote Config, you create in-app default
* values that control the behavior and appearance of your app.
*
* @firebase config
*/
export namespace Config {
export interface Statics {}
export interface Module extends ReactNativeFirebaseModule {
/**
* An Interface representing a Remote Config value
*/
export interface ConfigValue {
/**
* Where the value was retrieved from
*/
source: 'remote' | 'default' | 'static';
/**
* The value
*/
value: undefined | number | boolean | string;
}
/**
* An Interface representing multiple Config Values
*/
export interface ConfigValues {
[key: string]: ConfigValue;
}
/**
* An Interface representing settable config settings.
*/
export interface ConfigSettingsWrite {
isDeveloperModeEnabled: boolean;
}
/**
* An Interface representing readable config settings.
*/
export interface ConfigSettingsRead {
lastFetchTime: number;
isDeveloperModeEnabled: boolean;
lastFetchStatus: 'success' | 'failure' | 'no_fetch_yet' | 'throttled';
}
/**
* An Interface representing a Config Defaults object.
*/
export interface ConfigDefaults {
[key: string]: number | string | boolean;
}
export interface Module extends ReactNativeFirebaseModule {
/**
* Moves fetched data to the apps active config.
* Always successfully resolves with a boolean value of whether the fetched config was moved successfully.
*/
activateFetched(): Promise<boolean>;
/**
* Fetches the remote config data from Firebase, defined in the dashboard. If duration is defined (seconds), data will be locally cached for this duration.
*
* @param cacheExpirationSeconds Duration in seconds to cache the data for. To force a cache use a duration of 0.
*/
fetch(cacheExpirationSeconds?: number): Promise<null>;
/**
* Fetches the remote config data from Firebase, defined in the dashboard. If duration is defined (seconds), data will be locally cached for this duration.
*
* Once fetching is completely this method immediately calls activateFetched on native and returns a boolean value of activation status.
*
* @param cacheExpirationSeconds Duration in seconds to cache the data for. To force a cache use a duration of 0.
*/
fetchAndActivate(cacheExpirationSeconds?: number): Promise<boolean>;
/**
* Retrieve the configuration settings and status for Remote Config.
*/
getConfigSettings(): Promise<ConfigSettingsRead>;
/**
* Returns all keys matching the prefix as an array. If no prefix is defined all keys are returned.
*
* @param prefix
*/
getKeysByPrefix(prefix?: string): Promise<string[]>;
/**
* Returns all config values for the keys matching the prefix provided. In no prefix is provided all values are returned.
*
* @param prefix
*/
getValuesByKeysPrefix(prefix?: string): Promise<ConfigValues>;
/**
* Gets a ConfigValue by key.
*
* @param key
*/
getValue(key: string): Promise<ConfigValue>;
/**
* Set the Remote Config settings, specifically the `isDeveloperModeEnabled` flag.
*/
setConfigSettings(configSettings: ConfigSettingsWrite): Promise<ConfigSettingsRead>;
/**
* Sets default values for the app to use when accessing values.
* Any data fetched and activated will override any default values. Any values in the defaults but not on Firebase will be untouched.
*
*/
setDefaults(defaults: ConfigDefaults): Promise<null>;
/**
* Sets the default values from a resource file.
* On iOS this is a plist file and on Android this is an XML defaultsMap file.
* TODO(ehesp): insert link to guide here somehow?
*
* @param resourceName The plist/xml file name with no extension.
*/
setDefaultsFromResource(resourceName: string): Promise<null>;
}
}
@@ -48,10 +162,7 @@ declare module '@react-native-firebase/config' {
*/
export const firebase = FirebaseNamespaceExport;
const ConfigDefaultExport: ReactNativeFirebaseModuleAndStatics<
Config.Module,
Config.Statics
>;
const ConfigDefaultExport: ReactNativeFirebaseModuleAndStatics<Config.Module, Config.Statics>;
/**
* @example
* ```js
@@ -68,17 +179,18 @@ declare module '@react-native-firebase/config' {
declare module '@react-native-firebase/app-types' {
interface ReactNativeFirebaseNamespace {
/**
* Config
* Firebase Remote Config is a cloud service that lets you change the behavior and appearance of your
* app without requiring users to download an app update. When using Remote Config, you create in-app default
* values that control the behavior and appearance of your app.
*/
config: ReactNativeFirebaseModuleAndStatics<
Config.Module,
Config.Statics
>;
config: ReactNativeFirebaseModuleAndStatics<Config.Module, Config.Statics>;
}
interface FirebaseApp {
/**
* Config
* Firebase Remote Config is a cloud service that lets you change the behavior and appearance of your
* app without requiring users to download an app update. When using Remote Config, you create in-app default
* values that control the behavior and appearance of your app.
*/
config(): Config.Module;
}

View File

@@ -20,7 +20,15 @@ import {
FirebaseModule,
getFirebaseRoot,
} from '@react-native-firebase/app/lib/internal';
import {
hasOwnProperty,
isNumber,
isString,
isBoolean,
isArray,
isUndefined,
isObject,
} from '@react-native-firebase/common';
import version from './version';
const statics = {};
@@ -29,8 +37,221 @@ const namespace = 'config';
const nativeModuleName = 'RNFBConfigModule';
class FirebaseConfigModule extends FirebaseModule {
/**
*
* @param nativeValue
* @returns {*}
*/
function nativeValueToJS(nativeValue) {
return {
source: nativeValue.source,
get value() {
const { boolValue, stringValue, numberValue } = nativeValue;
// undefined
if (boolValue === false && numberValue === 0 && !stringValue.length) {
return undefined;
}
// boolean
if (
boolValue !== null &&
(stringValue === 'true' || stringValue === 'false' || stringValue === null)
) {
return boolValue;
}
// number
if (
numberValue !== null &&
numberValue !== undefined &&
(stringValue == null || stringValue === '' || numberValue.toString() === stringValue || parseInt(stringValue, 10) === numberValue)
) {
return numberValue;
}
// string
return stringValue;
},
};
}
class FirebaseConfigModule extends FirebaseModule {
/**
* Activates the Fetched Config, so that the fetched key-values take effect.
* @returns {Promise<boolean>}
*/
activateFetched() {
return this.native.activateFetched();
}
/**
* Fetches parameter values for your app.
* @param {number} cacheExpirationSeconds
* @returns {Promise}
*/
fetch(cacheExpirationSeconds) {
if (!isUndefined(cacheExpirationSeconds) && !isNumber(cacheExpirationSeconds)) {
throw new Error(
`firebase.config().fetch(): 'cacheExpirationSeconds' must be a number value.`,
);
}
return this.native.fetch(cacheExpirationSeconds !== undefined ? cacheExpirationSeconds : -1, false);
}
/**
* TODO(salakar) return boolean always?
* @param cacheExpirationSeconds
* @returns {Promise|never|Promise<Response>}
*/
fetchAndActivate(cacheExpirationSeconds) {
if (!isUndefined(cacheExpirationSeconds) && !isNumber(cacheExpirationSeconds)) {
throw new Error(
`firebase.config().fetchAndActivate(): 'cacheExpirationSeconds' must be a number value.`,
);
}
return this.native.fetch(cacheExpirationSeconds !== undefined ? cacheExpirationSeconds : -1, true);
}
/**
* Returns FirebaseRemoteConfig singleton
* lastFetchTime,
* lastFetchStatus.
* isDeveloperModeEnabled
* @returns {Object}
*/
getConfigSettings() {
return this.native.getConfigSettings();
}
/**
* Gets the set of keys that start with the given prefix.
*
* @param {string} prefix
* @returns {string[]}
*/
getKeysByPrefix(prefix) {
if (!isUndefined(prefix) && !isString(prefix)) {
throw new Error(`firebase.config().getKeysByPrefix(): 'prefix' must be a string value.`);
}
return this.native.getKeysByPrefix(prefix);
}
/**
*
* @param prefix
* @returns {Promise<void>}
*/
async getValuesByKeysPrefix(prefix) {
if (!isUndefined(prefix) && !isString(prefix)) {
throw new Error(
`firebase.config().getValuesByKeysPrefix(): 'prefix' must be a string value.`,
);
}
const output = {};
const entries = Object.entries(await this.native.getValuesByKeysPrefix(prefix));
for (let i = 0; i < entries.length; i++) {
const [key, value] = entries[i];
output[key] = nativeValueToJS(value);
}
return output;
}
/**
* Gets the FirebaseRemoteConfigValue corresponding to the specified key.
*
* @param {string} key
*/
async getValue(key) {
if (!isString(key)) {
throw new Error(`firebase.config().getValue(): 'key' must be a string value.`);
}
return nativeValueToJS(await this.native.getValue(key));
}
/**
* Gets the FirebaseRemoteConfigValue array corresponding to the specified keys.
*
* @param keys
*/
async getValues(keys) {
if (!isArray(keys) || !keys.length) {
throw new Error(`firebase.config().getValues(): 'keys' must be an non empty array.`);
}
if (!isString(keys[0])) {
throw new Error(`firebase.config().getValues(): 'keys' must be an array of strings.`);
}
const valuesObject = {};
const keyValues = await this.native.getValues(keys);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
valuesObject[key] = nativeValueToJS(keyValues[i]);
}
return valuesObject;
}
/**
* Changes the settings for the FirebaseRemoteConfig object's operations,
* such as turning the developer mode on.
* @param {object} settings
* @description Android & iOS
*/
setConfigSettings(settings = {}) {
if (!isObject(settings) || !hasOwnProperty(settings, 'isDeveloperModeEnabled')) {
throw new Error(
`firebase.config().setConfigSettings(): 'settings' must be an object with a 'isDeveloperModeEnabled' key.`,
);
}
if (!isBoolean(settings.isDeveloperModeEnabled)) {
throw new Error(
`firebase.config().setConfigSettings(): 'settings.isDeveloperModeEnabled' must be a boolean value.`,
);
}
return this.native.setConfigSettings(settings);
}
/**
* Sets defaults.
*
* @param {object} defaults
*/
setDefaults(defaults) {
if (!isObject(defaults)) {
throw new Error(
`firebase.config().setDefaults(): 'defaults' must be an object.`,
);
}
return this.native.setDefaults(defaults);
}
/**
* Sets defaults based on resource.
* @param {string} resourceName
*/
setDefaultsFromResource(resourceName) {
if (!isString(resourceName)) {
throw new Error(
`firebase.config().setDefaultsFromResource(): 'resourceName' must be a string value.`,
);
}
return this.native.setDefaultsFromResource(resourceName);
}
}
// import { SDK_VERSION } from '@react-native-firebase/config';

View File

@@ -20,8 +20,120 @@ import type { ReactNativeFirebaseModule } from '@react-native-firebase/app-types
export interface Statics {}
export interface Module extends ReactNativeFirebaseModule {
/**
* An Interface representing a Remote Config value
*/
export interface ConfigValue {
/**
* Where the value was retrieved from
*/
source: 'remote' | 'default' | 'static';
/**
* The value
*/
value: undefined | number | boolean | string;
}
/**
* An Interface representing multiple Config Values
*/
export interface ConfigValues {
[key: string]: ConfigValue;
}
/**
* An Interface representing settable config settings.
*/
export interface ConfigSettingsWrite {
isDeveloperModeEnabled: boolean;
}
/**
* An Interface representing readable config settings.
*/
export interface ConfigSettingsRead {
lastFetchTime: number;
isDeveloperModeEnabled: boolean;
lastFetchStatus: 'success' | 'failure' | 'no_fetch_yet' | 'throttled';
}
/**
* An Interface representing a Config Defaults object.
*/
export interface ConfigDefaults {
[key: string]: number | string | boolean;
}
export interface Module extends ReactNativeFirebaseModule {
/**
* Moves fetched data to the apps active config.
* Always successfully resolves with a boolean value of whether the fetched config was moved successfully.
*/
activateFetched(): Promise<boolean>;
/**
* Fetches the remote config data from Firebase, defined in the dashboard. If duration is defined (seconds), data will be locally cached for this duration.
*
* @param cacheExpirationSeconds Duration in seconds to cache the data for. To force a cache use a duration of 0.
*/
fetch(cacheExpirationSeconds?: number): Promise<null>;
/**
* Fetches the remote config data from Firebase, defined in the dashboard. If duration is defined (seconds), data will be locally cached for this duration.
*
* Once fetching is completely this method immediately calls activateFetched on native and returns a boolean value of activation status.
*
* @param cacheExpirationSeconds Duration in seconds to cache the data for. To force a cache use a duration of 0.
*/
fetchAndActivate(cacheExpirationSeconds?: number): Promise<boolean>;
/**
* Retrieve the configuration settings and status for Remote Config.
*/
getConfigSettings(): Promise<ConfigSettingsRead>;
/**
* Returns all keys matching the prefix as an array. If no prefix is defined all keys are returned.
*
* @param prefix
*/
getKeysByPrefix(prefix?: string): Promise<string[]>;
/**
* Returns all config values for the keys matching the prefix provided. In no prefix is provided all values are returned.
*
* @param prefix
*/
getValuesByKeysPrefix(prefix?: string): Promise<ConfigValues>;
/**
* Gets a ConfigValue by key.
*
* @param key
*/
getValue(key: string): Promise<ConfigValue>;
/**
* Set the Remote Config settings, specifically the `isDeveloperModeEnabled` flag.
*/
setConfigSettings(configSettings: ConfigSettingsWrite): Promise<ConfigSettingsRead>;
/**
* Sets default values for the app to use when accessing values.
* Any data fetched and activated will override any default values. Any values in the defaults but not on Firebase will be untouched.
*
*/
setDefaults(defaults: ConfigDefaults): Promise<null>;
/**
* Sets the default values from a resource file.
* On iOS this is a plist file and on Android this is an XML defaultsMap file.
* TODO(ehesp): insert link to guide here somehow?
*
* @param resourceName The plist/xml file name with no extension.
*/
setDefaultsFromResource(resourceName: string): Promise<null>;
}
declare module '@react-native-firebase/config' {

View File

@@ -56,7 +56,7 @@ repositories {
}
dependencies {
api project(':@react-native-firebase/app')
api project(':@react-native-firebase_app')
implementation "com.crashlytics.sdk.android:crashlytics:${ReactNative.ext.getVersion("fabric", "crashlytics")}"
implementation "com.crashlytics.sdk.android:crashlytics-ndk:${ReactNative.ext.getVersion("fabric", "crashlyticsNdk")}"
implementation "com.google.android.gms:play-services-base:${ReactNative.ext.getVersion("googlePlayServices", "base")}"

View File

@@ -1 +1 @@
rootProject.name = '@react-native-firebase/crashlytics'
rootProject.name = '@react-native-firebase_crashlytics'

View File

@@ -55,7 +55,7 @@ repositories {
}
dependencies {
api project(':@react-native-firebase/app')
api project(':@react-native-firebase_app')
implementation "com.google.firebase:firebase-inappmessaging-display:${ReactNative.ext.getVersion("firebase", "fiam")}"
implementation "com.google.android.gms:play-services-base:${ReactNative.ext.getVersion("googlePlayServices", "base")}"
}

View File

@@ -1 +1 @@
rootProject.name = '@react-native-firebase/fiam'
rootProject.name = '@react-native-firebase_fiam'

View File

@@ -55,7 +55,7 @@ repositories {
}
dependencies {
api project(':@react-native-firebase/app')
api project(':@react-native-firebase_app')
implementation "com.google.firebase:firebase-firestore:${ReactNative.ext.getVersion("firebase", "firestore")}"
implementation "com.google.android.gms:play-services-base:${ReactNative.ext.getVersion("googlePlayServices", "base")}"
}

View File

@@ -1 +1 @@
rootProject.name = '@react-native-firebase/firestore'
rootProject.name = '@react-native-firebase_firestore'

View File

@@ -55,7 +55,7 @@ repositories {
}
dependencies {
api project(':@react-native-firebase/app')
api project(':@react-native-firebase_app')
implementation "com.google.firebase:firebase-functions:${ReactNative.ext.getVersion("firebase", "functions")}"
implementation "com.google.android.gms:play-services-base:${ReactNative.ext.getVersion("googlePlayServices", "base")}"
}

View File

@@ -1 +1 @@
rootProject.name = '@react-native-firebase/functions'
rootProject.name = '@react-native-firebase_functions'

View File

@@ -55,7 +55,7 @@ repositories {
}
dependencies {
api project(':@react-native-firebase/app')
api project(':@react-native-firebase_app')
implementation "com.google.firebase:firebase-iid:${ReactNative.ext.getVersion("firebase", "iid")}"
implementation "com.google.android.gms:play-services-base:${ReactNative.ext.getVersion("googlePlayServices", "base")}"
}

View File

@@ -1 +1 @@
rootProject.name = '@react-native-firebase/iid'
rootProject.name = '@react-native-firebase_iid'

View File

@@ -57,7 +57,7 @@ repositories {
}
dependencies {
api project(':@react-native-firebase/app')
api project(':@react-native-firebase_app')
implementation "com.google.firebase:firebase-ml-common:${ReactNative.ext.getVersion("firebase", "mlkitCommon")}"
implementation "com.google.firebase:firebase-ml-vision:${ReactNative.ext.getVersion("firebase", "mlkitVision")}"
implementation "com.google.firebase:firebase-ml-natural-language:${ReactNative.ext.getVersion("firebase", "mlkitNaturalLanguage")}"

View File

@@ -1 +1 @@
rootProject.name = '@react-native-firebase/mlkit'
rootProject.name = '@react-native-firebase_mlkit'

View File

@@ -55,7 +55,7 @@ repositories {
}
dependencies {
api project(':@react-native-firebase/app')
api project(':@react-native-firebase_app')
implementation "com.google.firebase:firebase-perf:${ReactNative.ext.getVersion("firebase", "perf")}"
implementation "com.google.android.gms:play-services-base:${ReactNative.ext.getVersion("googlePlayServices", "base")}"
}

View File

@@ -1 +1 @@
rootProject.name = '@react-native-firebase/perf'
rootProject.name = '@react-native-firebase_perf'

View File

@@ -51,7 +51,7 @@ repositories {
}
dependencies {
api project(':@react-native-firebase/app')
api project(':@react-native-firebase_app')
implementation "com.google.android.gms:play-services-base:${ReactNative.ext.getVersion("googlePlayServices", "base")}"
}

View File

@@ -1 +1 @@
rootProject.name = '@react-native-firebase/utils'
rootProject.name = '@react-native-firebase_utils'

View File

@@ -55,7 +55,7 @@ repositories {
}
dependencies {
api project(':@react-native-firebase/app')
api project(':@react-native-firebase_app')
implementation "com.google.firebase:firebase-_template_:${ReactNative.ext.getVersion("firebase", "_template_")}"
implementation "com.google.android.gms:play-services-base:${ReactNative.ext.getVersion("googlePlayServices", "base")}"
}

View File

@@ -1 +1 @@
rootProject.name = '@react-native-firebase/_template_'
rootProject.name = '@react-native-firebase__template_'

View File

@@ -74,6 +74,12 @@ android {
matchingFallbacks = ['debug']
}
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
@@ -109,7 +115,7 @@ dependencies {
* ---------------------------- */
firebasePackages.each { firebasePackage ->
implementation project(path: ":@react-native-firebase/${firebasePackage}")
implementation project(path: ":@react-native-firebase_${firebasePackage}")
}
/* ------------------------

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<defaultsMap>
<entry>
<key>company</key>
<value>invertase</value>
</entry>
</defaultsMap>

View File

@@ -9,7 +9,7 @@
# Default value: -Xmx10248m -XX:MaxPermSize=256m
org.gradle.daemon=true
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.parallel=false
org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.

View File

@@ -1,4 +1,4 @@
rootProject.name = '@react-native-firebase/tests'
rootProject.name = '@react-native-firebase_tests'
def firebasePackages = [
'app',
@@ -14,8 +14,8 @@ def firebasePackages = [
]
firebasePackages.each { firebasePackage ->
include ":@react-native-firebase/${firebasePackage}"
project(":@react-native-firebase/${firebasePackage}").projectDir = new File(rootProject.projectDir, "./../../packages/${firebasePackage}/android")
include ":@react-native-firebase_${firebasePackage}"
project(":@react-native-firebase_${firebasePackage}").projectDir = new File(rootProject.projectDir, "./../../packages/${firebasePackage}/android")
}
include ':jet'

View File

@@ -38,6 +38,12 @@ function requirePackageTests(packageName) {
}
}
Object.defineProperty(global, 'A2A', {
get() {
return require('a2a');
},
});
Object.defineProperty(global, 'firebase', {
get() {
return jet.module;

View File

@@ -2,7 +2,7 @@
--timeout 260000
--reporter spec
--slow 1000
--retries 3
--retries 1
--bail
--exit
--require jet/platform/node

View File

@@ -21,6 +21,9 @@ PODS:
- Firebase/Performance (5.17.0):
- Firebase/Core
- FirebasePerformance (= 2.2.3)
- Firebase/RemoteConfig (5.17.0):
- Firebase/Core
- FirebaseRemoteConfig (= 3.1.0)
- FirebaseABTesting (2.0.0):
- FirebaseCore (~> 5.0)
- Protobuf (~> 3.5)
@@ -170,6 +173,11 @@ PODS:
- Firebase/Auth (~> 5.17.0)
- Firebase/Core (~> 5.17.0)
- React
- RNFBConfig (6.0.0-alpha.5):
- Firebase/Core (~> 5.17.0)
- Firebase/RemoteConfig (~> 5.17.0)
- React
- RNFBApp
- RNFBCrashlytics (6.0.0-alpha.5):
- Crashlytics (~> 3.12.0)
- Fabric (~> 1.9.0)
@@ -218,6 +226,7 @@ DEPENDENCIES:
- React/RCTWebSocket (from `../node_modules/react-native`)
- "RNFBAnalytics (from `../node_modules/@react-native-firebase/analytics/ios`)"
- "RNFBApp (from `../node_modules/@react-native-firebase/app/ios`)"
- "RNFBConfig (from `../node_modules/@react-native-firebase/config/ios`)"
- "RNFBCrashlytics (from `../node_modules/@react-native-firebase/crashlytics/ios`)"
- "RNFBFiam (from `../node_modules/@react-native-firebase/fiam/ios`)"
- "RNFBFirestore (from `../node_modules/@react-native-firebase/firestore/ios`)"
@@ -266,6 +275,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-firebase/analytics/ios"
RNFBApp:
:path: "../node_modules/@react-native-firebase/app/ios"
RNFBConfig:
:path: "../node_modules/@react-native-firebase/config/ios"
RNFBCrashlytics:
:path: "../node_modules/@react-native-firebase/crashlytics/ios"
RNFBFiam:
@@ -312,6 +323,7 @@ SPEC CHECKSUMS:
React: 1d605e098d69bdf08960787f3446f0a9dc2e2ccf
RNFBAnalytics: 61b9d722deb136454425850860df910250a1874d
RNFBApp: ea2649b5993bec4ef4ff5ed4cb29454328e78b74
RNFBConfig: 7804a2113cb720f6819c3b289a1bee8342b82aad
RNFBCrashlytics: b7ec197ade3caaa6060525b506fd2ff65b529a4c
RNFBFiam: 7ea892c593296cfb74bced95d96717c991233c69
RNFBFirestore: 5cc04b0781abd2a0c339738dcc56fc76a5930ac0

View File

@@ -0,0 +1,8 @@
<?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>company</key>
<string>invertase</string>
</dict>
</plist>

View File

@@ -11,6 +11,7 @@
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
271CB185206AFCD300EBADF4 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 271CB184206AFCD300EBADF4 /* GoogleService-Info.plist */; };
27CE6A36224923D200222E16 /* remote_config_resource_test.plist in Resources */ = {isa = PBXBuildFile; fileRef = 27CE6A35224923D200222E16 /* remote_config_resource_test.plist */; };
3323F06104C7189BEC46D8B5 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3323FFA47718EA67C36AD776 /* Images.xcassets */; };
E31DA68013C367A4C7A4C7C7 /* Pods_testing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0468979958B59C09A7C97954 /* Pods_testing.framework */; };
/* End PBXBuildFile section */
@@ -40,6 +41,7 @@
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = testing/main.m; sourceTree = "<group>"; };
27034D93212869A1004B697E /* testing.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = testing.entitlements; path = testing/testing.entitlements; sourceTree = "<group>"; };
271CB184206AFCD300EBADF4 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
27CE6A35224923D200222E16 /* remote_config_resource_test.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = remote_config_resource_test.plist; sourceTree = "<group>"; };
30F8459A53F04DD0B22777D1 /* testing.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; path = testing.xcodeproj; sourceTree = "<group>"; };
3323FFA47718EA67C36AD776 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = testing/Images.xcassets; sourceTree = "<group>"; };
4C4B32475FBBBAC16708CB5B /* Pods-testing.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-testing.debug.xcconfig"; path = "Pods/Target Support Files/Pods-testing/Pods-testing.debug.xcconfig"; sourceTree = "<group>"; };
@@ -85,6 +87,7 @@
13B07FB61A68108700A75B9A /* Info.plist */,
13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
13B07FB71A68108700A75B9A /* main.m */,
27CE6A35224923D200222E16 /* remote_config_resource_test.plist */,
);
name = testing;
sourceTree = "<group>";
@@ -227,6 +230,7 @@
files = (
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
271CB185206AFCD300EBADF4 /* GoogleService-Info.plist in Resources */,
27CE6A36224923D200222E16 /* remote_config_resource_test.plist in Resources */,
3323F06104C7189BEC46D8B5 /* Images.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@@ -34,6 +34,7 @@
"sinon": "^6.2.0"
},
"devDependencies": {
"a2a": "^0.2.0",
"@react-native-firebase/private-tests-helpers": "^0.0.8",
"babel-plugin-istanbul": "^5.1.1",
"nyc": "^13.1.0",

View File

@@ -1,28 +1,17 @@
import firebase from 'react-native-firebase';
import '@react-native-firebase/iid';
import analytics, { Analytics } from '@react-native-firebase/analytics';
import functions, {
firebase as boopy,
Functions,
HttpsErrorCode,
} from '@react-native-firebase/functions';
import '@react-native-firebase/config';
import '@react-native-firebase/functions';
import { firebase } from '@react-native-firebase/analytics';
boopy.apps[0].options.projectId;
analytics.SDK_VERSION;
functions.SDK_VERSION;
const httpsCallable = firebase.functions(firebase.app()).httpsCallable('foo');
functions;
async () => {
await firebase.config().activateFetched();
await firebase.config().fetch(0);
await firebase.config().fetch();
firebase.iid().get();
firebase.analytics().resetAnalyticsData();
const settings = await firebase.config().getConfigSettings();
console.log(settings.isDeveloperModeEnabled);
console.log(settings.lastFetchStatus);
console.log(settings.lastFetchTime);
httpsCallable({ foo: 1 })
.then(result => {
result.data;
})
.catch((error: Functions.HttpsError) => {
const foo = {} as Analytics.Module;
error.details;
foo.logEvent('shoopy', {});
HttpsErrorCode.NOT_FOUND;
});
await firebase.config().setConfigSettings({ isDeveloperModeEnabled: false });
await firebase.config().setDefaults({ foo: null });
};

View File

@@ -1488,6 +1488,11 @@ JSONStream@^1.0.4, JSONStream@^1.3.4:
jsonparse "^1.2.0"
through ">=2.2.7 <3"
a2a@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/a2a/-/a2a-0.2.0.tgz#32bcbc13457636a9cd37f0c3674778120e3a847c"
integrity sha512-zacbQ73PcKwlC4dFqaD5GDvluv2I7owHYELi6PCDERcZVFm0LNuhWGm1CKwtxcxEF7SmQXVF342wBKO8xAayMg==
abbrev@1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"