Skip to content

fix: sweep of firebase.json type config elements and related infrastructure #5597

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 33 additions & 10 deletions packages/app-check/e2e/appcheck.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@
const jwt = require('jsonwebtoken');

describe('appCheck()', function () {
describe('config', function () {
// This depends on firebase.json containing a false value for token auto refresh, we
// verify here that it was carried in to the Info.plist correctly
// it relies on token auto refresh being left false for local tests where the app is reused, since it is persistent
// but in CI it's fresh every time and would be true if not overridden in Info.plist
it('should configure token auto refresh in Info.plist on ios', async function () {
if (device.getPlatform() === 'ios') {
const tokenRefresh = await NativeModules.RNFBAppCheckModule.isTokenAutoRefreshEnabled(
'[DEFAULT]',
);
tokenRefresh.should.equal(false);
} else {
this.skip();
}
});
});

describe('setTokenAutoRefresh())', function () {
it('should set token refresh', function () {
firebase.appCheck().setTokenAutoRefreshEnabled(false);
Expand All @@ -38,21 +55,27 @@ describe('appCheck()', function () {
describe('activate())', function () {
it('should activate with default provider and default token refresh', async function () {
try {
await firebase.appCheck().activate('ignored', true);
await firebase.appCheck().activate('ignored', false);
} catch (e) {
return Promise.reject(e);
}
});
// Dynamic providers are not possible on iOS, so the debug provider is always working
android.it('token fetch attempt should work but fail attestation', async function () {
try {
// Activating on Android clobbers the shared secret in the debug provider shared secret, should fail now
await firebase.appCheck().getToken(true);
return Promise.reject('Should have thrown after resetting shared secret on debug provider');
} catch (e) {
e.message.should.containEql('[appCheck/token-error]');
e.message.should.containEql('App attestation failed');
return Promise.resolve();
it('token fetch attempt should work but fail attestation', async function () {
if (device.getPlatform() === 'android') {
try {
// Activating on Android clobbers the shared secret in the debug provider shared secret, should fail now
await firebase.appCheck().getToken(true);
return Promise.reject(
'Should have thrown after resetting shared secret on debug provider',
);
} catch (e) {
e.message.should.containEql('[appCheck/token-error]');
e.message.should.containEql('App attestation failed');
return Promise.resolve();
}
} else {
this.skip();
}
});
});
Expand Down
13 changes: 10 additions & 3 deletions packages/app-check/ios/RNFBAppcheck/RNFBAppCheckModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ - (dispatch_queue_t)methodQueue {
appCheck.isTokenAutoRefreshEnabled = isTokenAutoRefreshEnabled;
}

// Not present in JS or Android - it is iOS-specific so we only call this in testing - it is not in index.d.ts
RCT_EXPORT_METHOD(isTokenAutoRefreshEnabled
: (FIRApp *)firebaseApp
: (RCTPromiseResolveBlock)resolve rejecter
: (RCTPromiseRejectBlock)reject) {
FIRAppCheck *appCheck = [FIRAppCheck appCheckWithApp:firebaseApp];
BOOL isTokenAutoRefreshEnabled = appCheck.isTokenAutoRefreshEnabled;
resolve([NSNumber numberWithBool:isTokenAutoRefreshEnabled]);
}

RCT_EXPORT_METHOD(getToken
: (FIRApp *)firebaseApp
: (BOOL)forceRefresh
Expand Down Expand Up @@ -97,9 +107,6 @@ - (dispatch_queue_t)methodQueue {
// [[FIRAppCheckDebugProviderFactory alloc] init];
// [FIRAppCheck setAppCheckProviderFactory:providerFactory];

// - allow disable via firebase.json to override automatic data collection selection via
// FirebaseAppCheckTokenAutoRefreshEnabled is the plist value to put to NO or YES

// Write a custom provider factory, and allow the AppAttest provider

@end
3 changes: 2 additions & 1 deletion packages/app-check/lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ export namespace FirebaseAppCheckTypes {
export class Module extends FirebaseModule {
/**
* Activate App Check
* On iOS App Check is activated with DeviceCheck provider simply by including the module, but calling this does no harm.
* On iOS App Check is activated with DeviceCheck provider simply by including the module, using the token auto refresh default or
* the specific value (if configured) in firebase.json, but calling this does no harm.
* On Android you must call this and it will install the SafetyNet provider in release builds, the Debug provider if debuggable.
* On both platforms you may use this method to alter the token refresh setting after startup.
* On iOS if you want to set a specific AppCheckProviderFactory (for instance to FIRAppCheckDebugProviderFactory or
Expand Down
30 changes: 16 additions & 14 deletions packages/app/android/firebase-json.gradle
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
/* groovylint-disable CatchException, CompileStatic, DuplicateStringLiteral, JavaIoPackageAccess */
import groovy.json.JsonOutput
import groovy.json.JsonSlurper

String fileName = "firebase.json"
String jsonRoot = "react-native"
String fileName = 'firebase.json'
String jsonRoot = 'react-native'

File jsonFile = null
File parentDir = rootProject.projectDir

for (int i = 0; i <= 3; i++) {
if (parentDir == null) break
if (parentDir == null) { break }
parentDir = parentDir.parentFile
if (parentDir != null) {
jsonFile = new File(parentDir, fileName)
if (jsonFile.exists()) break
if (jsonFile.exists()) { break }
}
}

if (jsonFile != null && jsonFile.exists()) {
rootProject.logger.info ":${project.name} firebase.json found at ${jsonFile.toString()}"
if (jsonFile?.exists()) {
rootProject.logger.info ":${project.name} firebase.json found at ${jsonFile}"
Object json = null

try {
json = new JsonSlurper().parseText(jsonFile.text)
} catch (Exception ignored) {
rootProject.logger.warn ":${project.name} failed to parse firebase.json found at ${jsonFile.toString()}."
rootProject.logger.warn ":${project.name} failed to parse firebase.json found at ${jsonFile}."
rootProject.logger.warn ignored.toString()
}

Expand All @@ -33,27 +34,28 @@ if (jsonFile != null && jsonFile.exists()) {
rootProject.ext.firebaseJson = [
raw: json[jsonRoot],
isFlagEnabled: { key, defaultValue ->
if (json[jsonRoot] == null || json[jsonRoot][key] == null) return defaultValue
return json[jsonRoot][key] == true ? true : false
if (json[jsonRoot] == null || json[jsonRoot][key] == null) { return defaultValue }
return json[jsonRoot][key] == true
},
getStringValue: { key, defaultValue ->
if (json[jsonRoot] == null) return defaultValue
if (json[jsonRoot] == null) { return defaultValue }
return json[jsonRoot][key] ? json[jsonRoot][key] : defaultValue
}
]

rootProject.logger.info ":${project.name} found react-native json root in firebase.json, creating firebase build config"
rootProject.logger
.info ":${project.name} found react-native json root in firebase.json, creating firebase build config"
android {
defaultConfig {
buildConfigField "String", "FIREBASE_JSON_RAW", jsonStr
buildConfigField 'String', 'FIREBASE_JSON_RAW', jsonStr
}
}
} else {
rootProject.ext.firebaseJson = false
rootProject.logger.info ":${project.name} firebase.json found with no react-native config, skipping"
android {
defaultConfig {
buildConfigField "String", "FIREBASE_JSON_RAW", '"{}"'
buildConfigField 'String', 'FIREBASE_JSON_RAW', '"{}"'
}
}
}
Expand All @@ -62,7 +64,7 @@ if (jsonFile != null && jsonFile.exists()) {
rootProject.logger.info ":${project.name} no firebase.json found, skipping"
android {
defaultConfig {
buildConfigField "String", "FIREBASE_JSON_RAW", '"{}"'
buildConfigField 'String', 'FIREBASE_JSON_RAW', '"{}"'
}
}
}
8 changes: 4 additions & 4 deletions packages/app/firebase-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
"description": "Disable or enable auto collection of analytics data.\n This is useful for opt-in-first data flows, for example when dealing with GDPR compliance. This can be overridden in JavaScript. \n Re-enable analytics data collection, e.g. once user has granted permission.",
"type": "boolean"
},
"app_check_token_auto_refresh": {
"description": "If this flag is disabled then Firebase App Check will not periodically auto-refresh the app check token.\n This is useful for opt-in-first data flows, for example when dealing with GDPR compliance. \nIf unset it will default to the SDK-wide data collection default enabled setting. This may be overridden dynamically in Javascript.",
"type": "boolean"
},
"crashlytics_auto_collection_enabled": {
"description": "Additionally, you can configure whether Crashlytics sends out any reports through the auto_collection_enabled option in your firebase.json config. If you want users to opt-in, it is recommended that you disable this here and enable it later through the method once they opt-in.",
"type": "boolean"
Expand All @@ -29,10 +33,6 @@
"description": "React Native Firebase supports Crashlytics NDK reporting which is enabled by default. This allows Crashlytics to capture crashes originating from the Yoga layout engine used by React Native. You can disable Crashlytics NDK in your firebase.json config.",
"type": "boolean"
},
"database_persistence_enabled": {
"description": "Set whether database persistence is enabled or disabled.\n This can be overridden in JavaScript, e.g. when requesting permission or on a condition.",
"type": "boolean"
},
"in_app_messaging_auto_collection_enabled": {
"description": "In App Messaging can be further configured to enable or disable automatic data collection for Firebase In-App Messaging. This is useful for opt-in-first data flows, for example when dealing with GDPR compliance. This can be overridden in JavaScript.",
"type": "boolean"
Expand Down
16 changes: 16 additions & 0 deletions packages/app/ios_config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,22 @@ if [[ ${_SEARCH_RESULT} ]]; then
_PLIST_ENTRY_VALUES+=("$(jsonBoolToYesNo "$_MESSAGING_AUTO_INIT")")
fi

# config.in_app_messaging_auto_colllection_enabled
_FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue "$_JSON_OUTPUT_RAW" "in_app_messaging_auto_collection_enabled")
if [[ $_FIAM_AUTO_INIT ]]; then
_PLIST_ENTRY_KEYS+=("FirebaseInAppMessagingAutomaticDataCollectionEnabled")
_PLIST_ENTRY_TYPES+=("bool")
_PLIST_ENTRY_VALUES+=("$(jsonBoolToYesNo "$_FIAM_AUTO_INIT")")
fi

# config.app_check_token_auto_refresh
_APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue "$_JSON_OUTPUT_RAW" "app_check_token_auto_refresh")
if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then
_PLIST_ENTRY_KEYS+=("FirebaseAppCheckTokenAutoRefreshEnabled")
_PLIST_ENTRY_TYPES+=("bool")
_PLIST_ENTRY_VALUES+=("$(jsonBoolToYesNo "$_APP_CHECK_TOKEN_AUTO_REFRESH")")
fi

# config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful
_CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue "$_JSON_OUTPUT_RAW" "crashlytics_disable_auto_disabler")
if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == "true" ]]; then
Expand Down
106 changes: 57 additions & 49 deletions packages/database/e2e/query/on.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,58 +131,66 @@ describe('database().ref().on()', function () {
});

// FIXME super flaky on android emulator
ios.it('subscribe to child added events', async function () {
const successCallback = sinon.spy();
const cancelCallback = sinon.spy();
const ref = firebase.database().ref(`${TEST_PATH}/childAdded`);

ref.on(
'child_added',
$ => {
successCallback($.val());
},
() => {
cancelCallback();
},
);

await ref.child('child1').set('foo');
await ref.child('child2').set('bar');
await Utils.spyToBeCalledTimesAsync(successCallback, 2);
ref.off('child_added');
successCallback.getCall(0).args[0].should.equal('foo');
successCallback.getCall(1).args[0].should.equal('bar');
cancelCallback.should.be.callCount(0);
it('subscribe to child added events', async function () {
if (device.getPlatform() === 'ios') {
const successCallback = sinon.spy();
const cancelCallback = sinon.spy();
const ref = firebase.database().ref(`${TEST_PATH}/childAdded`);

ref.on(
'child_added',
$ => {
successCallback($.val());
},
() => {
cancelCallback();
},
);

await ref.child('child1').set('foo');
await ref.child('child2').set('bar');
await Utils.spyToBeCalledTimesAsync(successCallback, 2);
ref.off('child_added');
successCallback.getCall(0).args[0].should.equal('foo');
successCallback.getCall(1).args[0].should.equal('bar');
cancelCallback.should.be.callCount(0);
} else {
this.skip();
}
});

// FIXME super flaky on android emulator
ios.it('subscribe to child changed events', async function () {
const successCallback = sinon.spy();
const cancelCallback = sinon.spy();
const ref = firebase.database().ref(`${TEST_PATH}/childChanged`);
const child = ref.child('changeme');
await child.set('foo');

ref.on(
'child_changed',
$ => {
successCallback($.val());
},
() => {
cancelCallback();
},
);

const value1 = Date.now();
const value2 = Date.now() + 123;

await child.set(value1);
await child.set(value2);
await Utils.spyToBeCalledTimesAsync(successCallback, 2);
ref.off('child_changed');
successCallback.getCall(0).args[0].should.equal(value1);
successCallback.getCall(1).args[0].should.equal(value2);
cancelCallback.should.be.callCount(0);
it('subscribe to child changed events', async function () {
if (device.getPlatform() === 'ios') {
const successCallback = sinon.spy();
const cancelCallback = sinon.spy();
const ref = firebase.database().ref(`${TEST_PATH}/childChanged`);
const child = ref.child('changeme');
await child.set('foo');

ref.on(
'child_changed',
$ => {
successCallback($.val());
},
() => {
cancelCallback();
},
);

const value1 = Date.now();
const value2 = Date.now() + 123;

await child.set(value1);
await child.set(value2);
await Utils.spyToBeCalledTimesAsync(successCallback, 2);
ref.off('child_changed');
successCallback.getCall(0).args[0].should.equal(value1);
successCallback.getCall(1).args[0].should.equal(value2);
cancelCallback.should.be.callCount(0);
} else {
this.skip();
}
});

it('subscribe to child removed events', async function () {
Expand Down
30 changes: 17 additions & 13 deletions packages/database/e2e/query/once.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,19 +143,23 @@ describe('database().ref().once()', function () {
});

// FIXME too flaky against android in CI
ios.it('resolves when a child is removed', async function () {
const callbackAdd = sinon.spy();
const callbackRemove = sinon.spy();
const ref = firebase.database().ref(`${TEST_PATH}/childRemoved`);
ref.once('child_added').then($ => callbackAdd($.val()));
const child = ref.child('removeme');
await child.set('foo');
await Utils.spyToBeCalledOnceAsync(callbackAdd, 10000);

ref.once('child_removed').then($ => callbackRemove($.val()));
await child.remove();
await Utils.spyToBeCalledOnceAsync(callbackRemove, 10000);
callbackRemove.should.be.calledWith('foo');
it('resolves when a child is removed', async function () {
if (device.getPlatform() === 'ios') {
const callbackAdd = sinon.spy();
const callbackRemove = sinon.spy();
const ref = firebase.database().ref(`${TEST_PATH}/childRemoved`);
ref.once('child_added').then($ => callbackAdd($.val()));
const child = ref.child('removeme');
await child.set('foo');
await Utils.spyToBeCalledOnceAsync(callbackAdd, 10000);

ref.once('child_removed').then($ => callbackRemove($.val()));
await child.remove();
await Utils.spyToBeCalledOnceAsync(callbackRemove, 10000);
callbackRemove.should.be.calledWith('foo');
} else {
this.skip();
}
});

// https://github.com/firebase/firebase-js-sdk/blob/6b53e0058483c9002d2fe56119f86fc9fb96b56c/packages/database/test/order_by.test.ts#L104
Expand Down
Loading