Skip to content

Commit 4625961

Browse files
refactor(remote-config)!: migrate to TypeScript (#8972)
* refactor(remote-config): add typescript package scaffolding * refactor(remote-config): split modular and namespaced entrypoints * refactor(remote-config): split types for namespaced and modular * refactor(remote-config): type namespaced runtime and local helpers * refactor(remote-config): type modular wrapper adapters * test(remote-config): cover root type exports and drop source dts includes * chore(remote-config): initial compare script work * refactor(remote-config): switch compare-types to emitted dist declarations * fix(remote-config): align public config update instance typings * refactor(remote-config): remove source declaration shims * refactor(remote-config): remove legacy modular index wrapper * refacto(remote-config): convert web to TS * refactor(remote-config): reduce compare-types drift from firebase-js-sdk * refactor: remove legacy modular helpers that are not part of firebase-js-sdk * chore: rm unneeded typing * fix: modular deprecation warnings firing because we have instances that still have methods (such as StorageReference) and fire console warning incorrectly * fix: some remote config need to proxy setters as well (on RC instance) * fix: RC modular settings behaves same way as namespaced * test: update response from loglevel which matches firebase-js-sdk * fix: race condition due to using sync getters on remote config instance to match firebase-js-sdk * fix: revert back to previous implementation now we resolved race condition * fix: stop clobbering over non-default values in this._values * fix: ignore stale fetchTimeout & minimumFetchInterval before they have a chance to update * refactor:! remove deprecated api on RemoteConfigValue. value and source getters * refactor:align remote-config checks with modular-only exports * test: ios resolves to "undefined" as per previous TS declaration typings * refactor: align return types for fetch, reset, ensureInitialized and setCustomSignals * fix: remove path to non-existent remote-config TS declaration file * fix: update remote-config typedoc.json BREAKING CHANGE: remote-config types now match firebase-js-sdk as closely as possible Please see https://rnfirebase.io/migrating-to-v25 for help migrating if needed react-native-firebase has a goal to be a drop-in replacement for firebase-js-sdk, with native extensions and performance. It has always worked that way at the javascript level but the typescript types have been divergent We are fixing that as we refactor to typescript. Please bear with us as we get closer to our goal of react-native-firebase matching firebase-js-sdk both in functionality where possible, but also in exact typescript typing. Specifics for Remote Config: the primary modular remote-config types now use Firebase JS SDK names: LogLevel, FetchStatus, Value, and RemoteConfigSettings RemoteConfig.settings is now typed as RemoteConfigSettings, which uses fetchTimeoutMillis rather than the older RNFB-style fetchTimeMillis on the modular surface modular getAll() and getValue() now return SDK-aligned types: Record<string, Value> and Value modular setLogLevel() now matches the Firebase JS SDK signature and returns void the legacy modular helper exports fetchTimeMillis(), settings(), and lastFetchStatus() have been removed from @react-native-firebase/remote-config. Modular callers should read remoteConfig.fetchTimeMillis, remoteConfig.settings, and remoteConfig.lastFetchStatus from the RemoteConfig instance instead modular fetch() has been removed. Modular callers should use fetchConfig(remoteConfig) instead; the RNFB-only modular expirationDurationSeconds helper is no longer part of the public modular API modular setConfigSettings() and setDefaults() have been removed. Modular callers should use remoteConfig.settings = ... and remoteConfig.defaultConfig = ... on the RemoteConfig instance instead modular onConfigUpdated() has been removed. Modular callers should use onConfigUpdate(remoteConfig, observer) instead deprecated RemoteConfigValue.value and .source getters have been removed. Callers should use asString() and getSource() instead Remove LastFetchStatus, ValueSource, ConfigSettings, ConfigDefaults, ConfigValue, ConfigValues, LastFetchStatusType, and RemoteConfigLogLevel from modular exports --------- Co-authored-by: Mike Hardy <github@mikehardy.net>
1 parent 71e8eb5 commit 4625961

37 files changed

Lines changed: 1952 additions & 2583 deletions

.github/scripts/compare-types/packages/remote-config/config.ts

Lines changed: 27 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -16,113 +16,49 @@
1616
import type { PackageConfig } from '../../src/types';
1717

1818
const config: PackageConfig = {
19-
// ---------------------------------------------------------------------------
20-
// Name mapping
21-
// ---------------------------------------------------------------------------
22-
// ValueSource is defined as `ValueSource` in the source files of both
23-
// packages, but the RN Firebase index.d.ts re-exports it under the alias
24-
// `ModularValueSource` to avoid a naming clash with the namespaced API.
25-
// Since we compare source files directly, both sides see `ValueSource`
26-
// and they match — no mapping entry is needed here.
27-
nameMapping: {},
28-
29-
// ---------------------------------------------------------------------------
30-
// Missing in RN Firebase
31-
// ---------------------------------------------------------------------------
3219
missingInRN: [
33-
// Currently empty — all firebase-js-sdk remote-config exports are present
34-
// in the RN Firebase modular API (though some have different shapes; see below).
35-
],
36-
37-
// ---------------------------------------------------------------------------
38-
// Extra in RN Firebase
39-
// ---------------------------------------------------------------------------
40-
extraInRN: [
4120
{
42-
name: 'ConfigValues',
21+
name: 'FetchResponse',
4322
reason:
44-
'RN Firebase-specific type alias for `{ [key: string]: Value }`, ' +
45-
'semantically equivalent to `Record<string, Value>` used in the ' +
46-
'firebase-js-sdk. Kept for backwards compatibility.',
23+
'Part of the firebase-js-sdk initialization options surface. RN Firebase ' +
24+
'does not expose `RemoteConfigOptions` or the web fetch-response bootstrap path.',
4725
},
4826
{
49-
name: 'RemoteConfigLogLevel',
27+
name: 'FetchType',
5028
reason:
51-
'Type alias for `LogLevel` exposed for API clarity (mirrors the ' +
52-
'firebase-js-sdk internal RemoteConfigLogLevel). Not part of the ' +
53-
'firebase-js-sdk public API.',
29+
'Used by the firebase-js-sdk web fetch-response bootstrap path. RN Firebase ' +
30+
'does not expose that initialization surface.',
5431
},
5532
{
56-
name: 'LastFetchStatus',
33+
name: 'FirebaseExperimentDescription',
5734
reason:
58-
'Namespaced constant re-exported from statics.d.ts for backwards ' +
59-
'compatibility with the class-based (namespaced) API. Not part of ' +
60-
'the firebase-js-sdk modular API.',
35+
'Only used by the firebase-js-sdk fetch-response bootstrap types. RN Firebase ' +
36+
'does not expose that initialization surface.',
6137
},
6238
{
63-
name: 'ValueSource',
39+
name: 'FirebaseRemoteConfigObject',
6440
reason:
65-
'Namespaced constant re-exported from statics.d.ts for backwards ' +
66-
'compatibility with the class-based (namespaced) API. The modular ' +
67-
'`ValueSource` type alias (matching firebase-js-sdk) is also exported ' +
68-
'separately from types/modular.d.ts.',
41+
'Only used by the firebase-js-sdk fetch-response bootstrap types. RN Firebase ' +
42+
'does not expose that initialization surface.',
6943
},
7044
{
71-
name: 'fetch',
45+
name: 'RemoteConfigOptions',
7246
reason:
73-
'Legacy RN Firebase fetch API that accepts an optional ' +
74-
'`expirationDurationSeconds` parameter. Prefer `fetchConfig()` which ' +
75-
'matches the firebase-js-sdk API. Kept for backwards compatibility.',
76-
},
77-
{
78-
name: 'fetchTimeMillis',
79-
reason:
80-
'Getter function returning the last fetch timestamp. In the ' +
81-
'firebase-js-sdk this value is accessed as the `fetchTimeMillis` ' +
82-
'property on the `RemoteConfig` object rather than a standalone function.',
83-
},
84-
{
85-
name: 'lastFetchStatus',
86-
reason:
87-
'Getter function returning the last fetch status. In the ' +
88-
'firebase-js-sdk this value is accessed as the `lastFetchStatus` ' +
89-
'property on the `RemoteConfig` object rather than a standalone function.',
90-
},
91-
{
92-
name: 'settings',
93-
reason:
94-
'Getter function returning the current `ConfigSettings`. In the ' +
95-
'firebase-js-sdk settings are accessed via the `settings` property ' +
96-
'on the `RemoteConfig` object rather than a standalone function.',
97-
},
98-
{
99-
name: 'onConfigUpdated',
100-
reason:
101-
'Deprecated RN Firebase listener for real-time config updates. ' +
102-
'Replaced by `onConfigUpdate()` which matches the firebase-js-sdk API.',
47+
'The firebase-js-sdk supports optional initialization options when creating ' +
48+
'a Remote Config instance. RN Firebase does not expose this initialization API.',
10349
},
50+
],
51+
// ---------------------------------------------------------------------------
52+
// Extra in RN Firebase
53+
// ---------------------------------------------------------------------------
54+
extraInRN: [
10455
{
10556
name: 'reset',
10657
reason:
10758
'Android-only API that deletes all activated, fetched and default ' +
10859
'configs and resets all Remote Config settings. No equivalent exists ' +
10960
'in the firebase-js-sdk.',
11061
},
111-
{
112-
name: 'setConfigSettings',
113-
reason:
114-
'RN Firebase helper to update `minimumFetchIntervalMillis` and ' +
115-
'`fetchTimeoutMillis` asynchronously via the native module. In the ' +
116-
'firebase-js-sdk these properties are set by direct property assignment ' +
117-
'on the `RemoteConfig.settings` object.',
118-
},
119-
{
120-
name: 'setDefaults',
121-
reason:
122-
'RN Firebase API for setting default config values programmatically. ' +
123-
'The firebase-js-sdk uses direct assignment to `RemoteConfig.defaultConfig` ' +
124-
'instead.',
125-
},
12662
{
12763
name: 'setDefaultsFromResource',
12864
reason:
@@ -131,10 +67,6 @@ const config: PackageConfig = {
13167
'exists in the firebase-js-sdk web API.',
13268
},
13369
],
134-
135-
// ---------------------------------------------------------------------------
136-
// Different shape
137-
// ---------------------------------------------------------------------------
13870
differentShape: [
13971
{
14072
name: 'ConfigUpdateObserver',
@@ -144,33 +76,18 @@ const config: PackageConfig = {
14476
'errors but the RN type extends the native bridge error structure.',
14577
},
14678
{
147-
name: 'getAll',
79+
name: 'FetchStatus',
14880
reason:
149-
'Returns `ConfigValues` (RN Firebase type alias for `{ [key: string]: Value }`) ' +
150-
'instead of `Record<string, Value>`. The two types are structurally equivalent; ' +
151-
'the alias is retained for backwards compatibility.',
81+
'RN Firebase preserves the long-standing native string literals ' +
82+
'(`no_fetch_yet` / `throttled`) used by both the namespaced API and native bridge, ' +
83+
'instead of the firebase-js-sdk web literals (`no-fetch-yet` / `throttle`).',
15284
},
15385
{
15486
name: 'getRemoteConfig',
15587
reason:
156-
'The optional `app` parameter uses `ReactNativeFirebase.FirebaseApp` from ' +
157-
'`@react-native-firebase/app` instead of `FirebaseApp` from `@firebase/app`. ' +
158-
'Both types represent a Firebase app instance but come from different packages.',
159-
},
160-
{
161-
name: 'setLogLevel',
162-
reason:
163-
'The RN Firebase implementation accepts `RemoteConfigLogLevel` (a type alias ' +
164-
'for `LogLevel`) and returns it, whereas the firebase-js-sdk accepts `LogLevel` ' +
165-
'and returns `void`. The parameter types are semantically identical; the return ' +
166-
'type difference is a legacy artefact of the RN implementation.',
167-
},
168-
{
169-
name: 'setCustomSignals',
170-
reason:
171-
'Returns `Promise<null>` in RN Firebase vs `Promise<void>` in the ' +
172-
'firebase-js-sdk. The native module resolves with `null` rather than ' +
173-
'`undefined`; both signal successful completion.',
88+
'The firebase-js-sdk accepts an optional `RemoteConfigOptions` second argument. ' +
89+
'RN Firebase only accepts the optional app instance and does not expose the ' +
90+
'initialization-options surface.',
17491
},
17592
],
17693
};

.github/scripts/compare-types/src/registry.ts

Lines changed: 19 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import aiConfig from '../packages/ai/config';
1818
import appCheckConfig from '../packages/app-check/config';
1919
import firestoreConfig from '../packages/firestore/config';
2020
import firestorePipelinesConfig from '../packages/firestore-pipelines/config';
21+
import remoteConfigConfig from '../packages/remote-config/config';
2122

2223
const SCRIPT_DIR = path.resolve(__dirname, '..');
2324
const REPO_ROOT = path.resolve(SCRIPT_DIR, '..', '..', '..');
@@ -46,33 +47,10 @@ export interface PackageEntry {
4647
}
4748

4849
function rnDist(packageName: string): string {
49-
return path.join(
50-
REPO_ROOT,
51-
'packages',
52-
packageName,
53-
'dist',
54-
'typescript',
55-
'lib',
56-
);
50+
return path.join(REPO_ROOT, 'packages', packageName, 'dist', 'typescript', 'lib');
5751
}
5852

5953
export const packages: PackageEntry[] = [
60-
// {
61-
// name: 'remote-config',
62-
// firebaseSdkTypesPaths: [
63-
// path.join(SCRIPT_DIR, 'packages', 'remote-config', 'firebase-sdk.d.ts'),
64-
// ],
65-
// rnFirebaseModularFiles: [
66-
// path.join(rnDist('remote-config'), 'types', 'modular.d.ts'),
67-
// path.join(rnDist('remote-config'), 'modular.d.ts'),
68-
// ],
69-
// rnFirebaseSupportFiles: [
70-
// path.join(rnDist('remote-config'), 'statics.d.ts'),
71-
// path.join(rnDist('remote-config'), 'types', 'namespaced.d.ts'),
72-
// path.join(rnDist('remote-config'), 'types', 'internal.d.ts'),
73-
// ],
74-
// config: remoteConfigConfig,
75-
// },
7654
{
7755
name: 'storage',
7856
firebaseSdkTypesPaths: [path.join(
@@ -93,13 +71,24 @@ export const packages: PackageEntry[] = [
9371
config: storageConfig,
9472
},
9573
{
96-
name: 'ai',
74+
name: 'remote-config',
9775
firebaseSdkTypesPaths: [
98-
path.join(SCRIPT_DIR, 'packages', 'ai', 'ai-sdk.d.ts'),
76+
path.join(SCRIPT_DIR, 'packages', 'remote-config', 'firebase-sdk.d.ts'),
9977
],
10078
rnFirebaseModularFiles: [
101-
path.join(rnDist('ai'), 'index.d.ts'),
79+
path.join(rnDist('remote-config'), 'types', 'remote-config.d.ts'),
80+
path.join(rnDist('remote-config'), 'modular.d.ts'),
81+
],
82+
rnFirebaseSupportFiles: [
83+
path.join(rnDist('remote-config'), 'statics.d.ts'),
84+
path.join(rnDist('remote-config'), 'types', 'namespaced.d.ts'),
10285
],
86+
config: remoteConfigConfig,
87+
},
88+
{
89+
name: 'ai',
90+
firebaseSdkTypesPaths: [path.join(SCRIPT_DIR, 'packages', 'ai', 'ai-sdk.d.ts')],
91+
rnFirebaseModularFiles: [path.join(rnDist('ai'), 'index.d.ts')],
10392
rnFirebaseSupportFiles: [
10493
path.join(rnDist('ai'), 'backend.d.ts'),
10594
path.join(rnDist('ai'), 'errors.d.ts'),
@@ -148,12 +137,7 @@ export const packages: PackageEntry[] = [
148137
{
149138
name: 'firestore',
150139
firebaseSdkTypesPaths: [
151-
path.join(
152-
SCRIPT_DIR,
153-
'packages',
154-
'firestore',
155-
'firestore-js-sdk.d.ts',
156-
),
140+
path.join(SCRIPT_DIR, 'packages', 'firestore', 'firestore-js-sdk.d.ts'),
157141
],
158142
rnFirebaseModularFiles: [
159143
path.join(rnDist('firestore'), 'types', 'firestore.d.ts'),
@@ -190,16 +174,9 @@ export const packages: PackageEntry[] = [
190174
{
191175
name: 'firestore-pipelines',
192176
firebaseSdkTypesPaths: [
193-
path.join(
194-
SCRIPT_DIR,
195-
'packages',
196-
'firestore-pipelines',
197-
'pipelines.d.ts',
198-
),
199-
],
200-
rnFirebaseModularFiles: [
201-
path.join(rnDist('firestore'), 'pipelines', 'index.d.ts'),
177+
path.join(SCRIPT_DIR, 'packages', 'firestore-pipelines', 'pipelines.d.ts'),
202178
],
179+
rnFirebaseModularFiles: [path.join(rnDist('firestore'), 'pipelines', 'index.d.ts')],
203180
rnFirebaseSupportFiles: [
204181
path.join(rnDist('firestore'), 'pipelines', 'expressions.d.ts'),
205182
path.join(rnDist('firestore'), 'pipelines', 'pipeline.d.ts'),

packages/app/lib/common/index.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,11 @@ function isObjectLike(value: unknown): value is object | ((...args: any[]) => un
646646
}
647647

648648
function isModularInstance(value: unknown): boolean {
649-
return isObjectLike(value) && (value as any)[MODULAR_INSTANCE_SYMBOL] === true;
649+
if (!isObjectLike(value)) {
650+
return false;
651+
}
652+
653+
return Object.getOwnPropertyDescriptor(value, MODULAR_INSTANCE_SYMBOL)?.value === true;
650654
}
651655

652656
function markModularInstance<T>(value: T): T {
@@ -664,6 +668,10 @@ function markModularInstance<T>(value: T): T {
664668
return value;
665669
}
666670

671+
function isModularAccess(target: unknown, receiver?: unknown): boolean {
672+
return _isModularCall || isModularInstance(target) || isModularInstance(receiver);
673+
}
674+
667675
export function createDeprecationProxy<T extends object>(instance: T): T {
668676
return new Proxy(instance, {
669677
construct(target: any, args: any[]) {
@@ -672,7 +680,7 @@ export function createDeprecationProxy<T extends object>(instance: T): T {
672680
},
673681
get(target: any, prop: string | symbol, receiver: any) {
674682
const originalMethod = target[prop];
675-
const modularAccess = _isModularCall || isModularInstance(target);
683+
const modularAccess = isModularAccess(target, receiver);
676684

677685
if (prop === 'constructor') {
678686
return Reflect.get(target, prop, receiver);
@@ -781,6 +789,26 @@ export function createDeprecationProxy<T extends object>(instance: T): T {
781789
}
782790
return Reflect.get(target, prop, receiver);
783791
},
792+
set(target: any, prop: string | symbol, value: unknown, receiver: unknown) {
793+
const modularAccess = isModularAccess(target, receiver);
794+
const descriptor =
795+
Object.getOwnPropertyDescriptor(target, prop) ||
796+
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(target), prop);
797+
798+
if (descriptor?.set) {
799+
const instanceName = getInstanceName(target);
800+
const nameSpace = getNamespace(target);
801+
802+
if (nameSpace) {
803+
deprecationConsoleWarning(nameSpace, prop as string, instanceName, modularAccess);
804+
}
805+
806+
descriptor.set.call(target, value);
807+
return true;
808+
}
809+
810+
return Reflect.set(target, prop, value, receiver);
811+
},
784812
});
785813
}
786814

0 commit comments

Comments
 (0)