Skip to content
Draft
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
667 changes: 452 additions & 215 deletions packages/remote-config/e2e/config.e2e.js

Large diffs are not rendered by default.

62 changes: 61 additions & 1 deletion packages/remote-config/lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,45 @@ export namespace FirebaseRemoteConfigTypes {
*/
type LastFetchStatusType = 'success' | 'failure' | 'no_fetch_yet' | 'throttled';

/**
* Contains information about which keys have been updated.
*/
export interface ConfigUpdate {
/**
* Parameter keys whose values have been updated from the currently activated values.
* Includes keys that are added, deleted, or whose value, value source, or metadata has changed.
*/
getUpdatedKeys(): Set<string>;
}

/**
* Observer interface for receiving real-time Remote Config update notifications.
*
* NOTE: Although an `complete` callback can be provided, it will
* never be called because the ConfigUpdate stream is never-ending.
*/
export interface ConfigUpdateObserver {
/**
* Called when a new ConfigUpdate is available.
*/
next: (configUpdate: ConfigUpdate) => void;

/**
* Called if an error occurs during the stream.
*/
error: (error: FirebaseError) => void;

/**
* Called when the stream is gracefully terminated.
*/
complete: () => void;
}

/**
* A function that unsubscribes from a real-time event stream.
*/
export type Unsubscribe = () => void;

/**
* The Firebase Remote RemoteConfig service interface.
*
Expand Down Expand Up @@ -376,14 +415,33 @@ export namespace FirebaseRemoteConfigTypes {
*/
setDefaultsFromResource(resourceName: string): Promise<null>;

/**
* Starts listening for real-time config updates from the Remote Config backend and automatically
* fetches updates from the Remote Config backend when they are available.
*
* @remarks
* If a connection to the Remote Config backend is not already open, calling this method will
* open it. Multiple listeners can be added by calling this method again, but subsequent calls
* re-use the same connection to the backend.
*
* The list of updated keys passed to the callback will include all keys not currently active,
* and the config update process fetches the new config but does not automatically activate
* it for you. Typically you will activate the config in your callback to use the new values.
*
* @param remoteConfig - The {@link RemoteConfig} instance.
* @param observer - The {@link ConfigUpdateObserver} to be notified of config updates.
* @returns An {@link Unsubscribe} function to remove the listener.
*/
onConfigUpdate(remoteConfig: RemoteConfig, observer: ConfigUpdateObserver): Unsubscribe;

/**
* Start listening for real-time config updates from the Remote Config backend and
* automatically fetch updates when they’re available. Note that the list of updated keys
* passed to the callback will include all keys not currently active, and the config update
* process fetches the new config but does not automatically activate for you. Typically
* you will want to activate the config in your callback so the new values are in force.
*
* @param listener called with either array of updated keys or error arg when config changes
* @deprecated use official firebase-js-sdk onConfigUpdate now that web supports realtime
*/
onConfigUpdated(listener: CallbackOrObserver<OnConfigUpdatedListenerCallback>): () => void;

Expand Down Expand Up @@ -541,8 +599,10 @@ export namespace FirebaseRemoteConfigTypes {
reset(): Promise<void>;
}

// deprecated: from pre-Web realtime remote-config support - remove with onConfigUpdated
export type CallbackOrObserver<T extends (...args: any[]) => any> = T | { next: T };

// deprecated: from pre-Web realtime remote-config support - remove with onConfigUpdated
export type OnConfigUpdatedListenerCallback = (
event?: { updatedKeys: string[] },
error?: {
Expand Down
64 changes: 64 additions & 0 deletions packages/remote-config/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
isString,
isUndefined,
isIOS,
isOther,
isFunction,
parseListenerOrObserver,
} from '@react-native-firebase/app/lib/common';
import Value from './RemoteConfigValue';
Expand Down Expand Up @@ -241,11 +243,73 @@ class FirebaseConfigModule extends FirebaseModule {
return this._promiseWithConstants(this.native.setDefaultsFromResource(resourceName));
}

/**
* Registers an observer to changes in the configuration.
*
* @param observer - The {@link ConfigUpdateObserver} to be notified of config updates.
* @returns An {@link Unsubscribe} function to remove the listener.
*/
onConfigUpdate(observer) {
if (!isObject(observer) || !isFunction(observer.next) || !isFunction(observer.error)) {
throw new Error("'observer' expected an object with 'next' and 'error' functions.");
}

if (isOther) {
return this.native.onConfigUpdate(observer);
}

// For our native platforms, we will bend our pre-web-support interface to match
// the web interface by assuming the callback is an Observer, and sending it a ConfigUpdate
// compatible parameter that implements the `getUpdatedKeys` method
let unsubscribed = false;
const subscription = this.emitter.addListener(
this.eventNameForApp('on_config_updated'),
event => {
const { resultType } = event;
if (resultType === 'success') {
observer.next({
getUpdatedKeys: () => {
return new Set(event.updatedKeys);
},
});
return;
}

observer.error({
code: event.code,
message: event.message,
nativeErrorMessage: event.nativeErrorMessage,
});
},
);
if (this._configUpdateListenerCount === 0) {
this.native.onConfigUpdated();
}

this._configUpdateListenerCount++;

return () => {
if (unsubscribed) {
// there is no harm in calling this multiple times to unsubscribe,
// but anything after the first call is a no-op
return;
} else {
unsubscribed = true;
}
subscription.remove();
this._configUpdateListenerCount--;
if (this._configUpdateListenerCount === 0) {
this.native.removeConfigUpdateRegistration();
}
};
}

/**
* Registers a listener to changes in the configuration.
*
* @param listenerOrObserver - function called on config change
* @returns {function} unsubscribe listener
* @deprecated use official firebase-js-sdk onConfigUpdate now that web supports realtime
*/
onConfigUpdated(listenerOrObserver) {
const listener = parseListenerOrObserver(listenerOrObserver);
Expand Down
28 changes: 28 additions & 0 deletions packages/remote-config/lib/modular/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@
import FirebaseApp = ReactNativeFirebase.FirebaseApp;
import LastFetchStatusInterface = FirebaseRemoteConfigTypes.LastFetchStatus;
import ValueSourceInterface = FirebaseRemoteConfigTypes.ValueSource;
import ConfigUpdate = FirebaseRemoteConfigTypes.ConfigUpdate;

Check failure on line 30 in packages/remote-config/lib/modular/index.d.ts

View workflow job for this annotation

GitHub Actions / Lint

'ConfigUpdate' is defined but never used. Allowed unused vars must match /^_/u

Check failure on line 30 in packages/remote-config/lib/modular/index.d.ts

View workflow job for this annotation

GitHub Actions / Lint

'ConfigUpdate' is defined but never used. Allowed unused vars must match /^_/u

Check failure on line 30 in packages/remote-config/lib/modular/index.d.ts

View workflow job for this annotation

GitHub Actions / Lint

'ConfigUpdate' is defined but never used. Allowed unused vars must match /^_/u
import ConfigUpdateObserver = FirebaseRemoteConfigTypes.ConfigUpdateObserver;
import Unsubscribe = FirebaseRemoteConfigTypes.Unsubscribe;
// deprecated: from pre-Web realtime remote-config support - remove with onConfigUpdated
import CallbackOrObserver = FirebaseRemoteConfigTypes.CallbackOrObserver;
// deprecated: from pre-Web realtime remote-config support - remove with onConfigUpdated
import OnConfigUpdatedListenerCallback = FirebaseRemoteConfigTypes.OnConfigUpdatedListenerCallback;

export const LastFetchStatus: LastFetchStatusInterface;
Expand Down Expand Up @@ -203,12 +208,35 @@
resourceName: string,
): Promise<null>;

/**
* Starts listening for real-time config updates from the Remote Config backend and automatically
* fetches updates from the Remote Config backend when they are available.
*
* @remarks
* If a connection to the Remote Config backend is not already open, calling this method will
* open it. Multiple listeners can be added by calling this method again, but subsequent calls
* re-use the same connection to the backend.
*
* The list of updated keys passed to the callback will include all keys not currently active,
* and the config update process fetches the new config but does not automatically activate
* it for you. Typically you will activate the config in your callback to use the new values.
*
* @param remoteConfig - The {@link RemoteConfig} instance.
* @param observer - The {@link ConfigUpdateObserver} to be notified of config updates.
* @returns An {@link Unsubscribe} function to remove the listener.
*/
export function onConfigUpdate(
remoteConfig: RemoteConfig,
observer: ConfigUpdateObserver,
): Unsubscribe;

/**
* Registers a listener to changes in the configuration.
*
* @param remoteConfig - RemoteConfig instance
* @param callback - function called on config change
* @returns {function} unsubscribe listener
* @deprecated use official firebase-js-sdk onConfigUpdate now that web supports realtime
*/
export function onConfigUpdated(
remoteConfig: RemoteConfig,
Expand Down
15 changes: 15 additions & 0 deletions packages/remote-config/lib/modular/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import { MODULAR_DEPRECATION_ARG } from '@react-native-firebase/app/lib/common';
* @typedef {import('..').FirebaseRemoteConfigTypes.ConfigValues} ConfigValues
* @typedef {import('..').FirebaseRemoteConfigTypes.LastFetchStatusType} LastFetchStatusType
* @typedef {import('..').FirebaseRemoteConfigTypes.RemoteConfigLogLevel} RemoteConfigLogLevel
* @typedef {import('..').FirebaseRemoteConfigTypes.ConfigUpdateObserver} ConfigUpdateObserver
* @typedef {import('..').FirebaseRemoteConfigTypes.Unsubscribe} Unsubscribe
* @typedef {import('.').CustomSignals} CustomSignals
*/

Expand Down Expand Up @@ -239,12 +241,25 @@ export function setDefaultsFromResource(remoteConfig, resourceName) {
);
}

/**
* Registers a listener to changes in the configuration.
*
* @param {RemoteConfig} remoteConfig - RemoteConfig instance
* @param {ConfigUpdateObserver} observer - to be notified of config updates.
* @returns {Unsubscribe} function to remove the listener.
* @deprecated use official firebase-js-sdk onConfigUpdate now that web supports realtime
*/
export function onConfigUpdate(remoteConfig, observer) {
return remoteConfig.onConfigUpdate.call(remoteConfig, observer, MODULAR_DEPRECATION_ARG);
}

/**
* Registers a listener to changes in the configuration.
*
* @param {RemoteConfig} remoteConfig - RemoteConfig instance
* @param {CallbackOrObserver<OnConfigUpdatedListenerCallback>} callback - function called on config change
* @returns {function} unsubscribe listener
* @deprecated use official firebase-js-sdk onConfigUpdate now that web supports realtime
*/
export function onConfigUpdated(remoteConfig, callback) {
return remoteConfig.onConfigUpdated.call(remoteConfig, callback, MODULAR_DEPRECATION_ARG);
Expand Down
30 changes: 17 additions & 13 deletions packages/remote-config/lib/polyfills.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/*
* Copyright (c) 2016-present Invertase Limited & Contributors
/**
* @license
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this library except in compliance with the License.
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
Expand All @@ -12,17 +13,20 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

import { polyfillGlobal } from 'react-native/Libraries/Utilities/PolyfillFunctions';
import { ReadableStream } from 'web-streams-polyfill/dist/ponyfill';
import { fetch, Headers, Request, Response } from 'react-native-fetch-api';

// maybe this could be remote-config local install of text-encoding (similar to ai package)
import { TextEncoder, TextDecoder } from 'text-encoding';
polyfillGlobal(
'fetch',
() =>
(...args) =>
fetch(args[0], { ...args[1], reactNative: { textStreaming: true } }),
);
polyfillGlobal('Headers', () => Headers);
polyfillGlobal('Request', () => Request);
polyfillGlobal('Response', () => Response);
polyfillGlobal('ReadableStream', () => ReadableStream);

polyfillGlobal('TextEncoder', () => TextEncoder);
polyfillGlobal('TextDecoder', () => TextDecoder);
// Object.assign(global, {
// TextEncoder: TextEncoder,
// TextDecoder: TextDecoder,
// });
import 'text-encoding';
7 changes: 7 additions & 0 deletions packages/remote-config/lib/web/RNFBConfigModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
fetchConfig,
getAll,
makeIDBAvailable,
onConfigUpdate,
setCustomSignals,
} from '@react-native-firebase/app/lib/internal/web/firebaseRemoteConfig';
import { guard, getWebError } from '@react-native-firebase/app/lib/internal/web/utils';
Expand Down Expand Up @@ -122,6 +123,12 @@ export default {
return resultAndConstants(remoteConfig, null);
});
},
onConfigUpdate(appName, listener) {
const remoteConfig = getRemoteConfigInstanceForApp(appName);
return onConfigUpdate(remoteConfig, listener);
},
// This is the old API implemented for Android and iOS prior to web support.
// Remove when deprecated onConfigUpdated API is removed.
onConfigUpdated() {
throw getWebError({
code: 'unsupported',
Expand Down
4 changes: 3 additions & 1 deletion packages/remote-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
"provenance": true
},
"dependencies": {
"text-encoding": "^0.7.0"
"react-native-fetch-api": "^3.0.0",
"text-encoding": "^0.7.0",
"web-streams-polyfill": "^4.2.0"
},
"devDependencies": {
"@types/text-encoding": "^0.0.40"
Expand Down
2 changes: 2 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5767,7 +5767,9 @@ __metadata:
resolution: "@react-native-firebase/remote-config@workspace:packages/remote-config"
dependencies:
"@types/text-encoding": "npm:^0.0.40"
react-native-fetch-api: "npm:^3.0.0"
text-encoding: "npm:^0.7.0"
web-streams-polyfill: "npm:^4.2.0"
peerDependencies:
"@react-native-firebase/analytics": 23.4.1
"@react-native-firebase/app": 23.4.1
Expand Down
Loading