Skip to content

refactor(remote-config): move to typescript#8896

Open
akashdeep931 wants to merge 37 commits intomainfrom
remote-config-typescript
Open

refactor(remote-config): move to typescript#8896
akashdeep931 wants to merge 37 commits intomainfrom
remote-config-typescript

Conversation

@akashdeep931
Copy link

Description

First TypeScript migration for the remote-config package. Source is now written in TypeScript and namespaced types are fully separated from modular types:

  • Namespaced types live in lib/types/namespaced.ts and are aligned with the previous index.d.ts surface. This keeps the migration low-risk and makes it straightforward to remove namespaced support later.
  • Modular types live in lib/types/modular.ts and are consumed only by the modular API.

The package continues to build with react-native-builder-bob (ESM + TypeScript declaration targets). No intentional changes to the public API or runtime behavior.

Breaking Changes

No consumer-facing breaking changes. Same public API and type surface; only the implementation and type file layout have changed (JS → TS, types split into types/namespaced.ts and types/modular.ts).

Related issues

Release Summary

  • @react-native-firebase/remote-config: TypeScript migration; namespaced and modular types separated into types/namespaced.ts and types/modular.ts. No API changes.

Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • My change supports the following platforms;
    • Android
    • iOS
    • Other (macOS, web)
  • My change includes tests;
    • e2e tests added or updated in packages/**/e2e (existing e2e unchanged; type-test and build validate TS)
    • jest tests added or updated in packages/**/__tests__ (existing tests updated for TS where applicable)
    • I have updated TypeScript types that are affected by my change.
  • This is a breaking change;
    • Yes
    • No

Test Plan

  • yarn compile at repo root (builds all packages including remote-config).
  • Run remote-config type-test: ensure packages/remote-config/type-test.ts is included in root tsc and passes.
  • Run existing remote-config e2e: packages/remote-config/e2e/config.e2e.js.
  • Run remote-config jest tests: packages/remote-config/__tests__/remote-config.test.ts.
  • Verify local test app (e.g. tests/local-tests/remote-config) runs and can use remote-config.

Think react-native-firebase is great? Please consider supporting the project with any of the below:

👉 Star this repo on GitHub ⭐️
👉 Follow React Native Firebase and Invertase on Twitter

@vercel
Copy link

vercel bot commented Feb 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-native-firebase Error Error Mar 2, 2026 3:18pm

Request Review

@CLAassistant
Copy link

CLAassistant commented Feb 19, 2026

CLA assistant check
All committers have signed the CLA.

Copy link
Member

@russellwheatley russellwheatley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job, @akashdeep931 - just needs some further work doing 😄 .

As another aside, I do prefer smaller commits rather than one big commit 🙏

}

/**
* Internal: Module instance methods accept optional deprecation arg (string).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal types need to be moved to their own types/internal.ts file. See storage typescript: https://github.com/invertase/react-native-firebase/blob/storage-typescript/packages/storage/lib/types/internal.ts

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit

Comment on lines +3 to +5
export type { FirebaseRemoteConfigTypes } from './types/namespaced';
export * from './modular';
export { default, firebase, SDK_VERSION } from './namespaced';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit

Comment on lines +370 to +373
const listener = parseListenerOrObserver(listenerOrObserver) as (
event?: { updatedKeys: string[] },
error?: { code: string; message: string; nativeErrorMessage: string },
) => void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal type

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit.

Comment on lines +426 to +432
if (typeof c.lastFetchTime === 'number') {
this._lastFetchTime = c.lastFetchTime;
}

// Wrapped this as we update using sync getters initially for `defaultConfig` & `settings`
if (constants.lastFetchStatus) {
this._lastFetchStatus = constants.lastFetchStatus;
if (typeof c.lastFetchStatus === 'string') {
this._lastFetchStatus = c.lastFetchStatus as FirebaseRemoteConfigTypes.LastFetchStatusType;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

behaviour change

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constants from native are loosely typed, so values can be strings, numbers, objects, or missing. The typeof checks act as a type-safety layer: we only assign to the instance fields when the value is actually the expected type (e.g. number for lastFetchTime, string for lastFetchStatus), so we don’t set invalid or mismatched data.

Comment on lines +452 to +454
if (c.values && typeof c.values === 'object' && !Array.isArray(c.values)) {
this._values = Object.freeze(c.values) as Record<string, { value: string; source: string }>;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

behaviour change?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reply. It's done mainly for type safety.

Comment on lines +32 to +41
type FirebaseRoot = {
app(name?: string): { name: string; remoteConfig(): ReturnType<typeof getRemoteConfig> };
remoteConfig: ((app?: { name: string }) => ReturnType<typeof getRemoteConfig>) & {
SDK_VERSION: string;
ValueSource: { REMOTE: string; DEFAULT: string; STATIC: string };
LastFetchStatus: { SUCCESS: string; FAILURE: string; THROTTLED: string; NO_FETCH_YET: string };
};
SDK_VERSION?: string;
};
const _firebase = firebase as unknown as FirebaseRoot;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this shouldn't be needed

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, linter was complaining when I didn't define the type of _firebase, but tsconfig is set up and isn't complaining anymore. Fixed in commit.

Comment on lines +77 to +91
"@react-native-firebase/remote-config": ["./packages/remote-config/lib/index.ts"],
"@react-native-firebase/app/dist/module/internal/web/utils": [
"./packages/app/lib/internal/web/utils"
],
"@react-native-firebase/app/dist/module/internal/web/firebaseRemoteConfig": [
"./packages/app/lib/internal/web/firebaseRemoteConfig"
],
"@react-native-firebase/app/dist/module/internal/nativeModule": [
"./packages/app/lib/internal/nativeModule"
],
"@react-native-firebase/app/dist/module/internal/*": ["./packages/app/lib/internal/*"],
"@react-native-firebase/app/dist/module/internal": ["./packages/app/lib/internal"],
"@react-native-firebase/app/dist/module/common/*": ["./packages/app/lib/common/*"],
"@react-native-firebase/app/dist/module/common": ["./packages/app/lib/common"],
"@react-native-firebase/app": ["./packages/app/lib/index.ts"],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fairly sure some if not all of these are not needed

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed some as they were reduntant. Fixed in commit.

@mikehardy
Copy link
Collaborator

mikehardy commented Feb 19, 2026

Appears to have caused some havoc with unexpected results on the Other platform - make sure to run yarn tests:macos:test (after running yarn tests:macos:build) from time to time to check work there and make sure it doesn't perturb that platform

https://github.com/invertase/react-native-firebase/actions/runs/22181126605/job/64142406453?pr=8896#step:23:2659

Copy link
Member

@russellwheatley russellwheatley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provided some more feedback 🙏

Comment on lines +102 to +187
export interface FirebaseExperimentDescription {
// A string of max length 22 characters and of format: _exp_<experiment_id>
experimentId: string;

// The variant of the experiment assigned to the app instance.
variantId: string;

// When the experiment was started.
experimentStartTime: string;

// How long the experiment can remain in STANDBY state. Valid range from 1 ms
// to 6 months.
triggerTimeoutMillis: string;

// How long the experiment can remain in ON state. Valid range from 1 ms to 6
// months.
timeToLiveMillis: string;

// Which all parameters are affected by this experiment.
affectedParameterKeys?: string[];
}

/**
* Defines a successful response (200 or 304).
*
* <p>Modeled after the native `Response` interface, but simplified for Remote Config's
* use case.
*
* @public
*/
export interface FetchResponse {
/**
* The HTTP status, which is useful for differentiating success responses with data from
* those without.
*
* <p>The Remote Config client is modeled after the native `Fetch` interface, so
* HTTP status is first-class.
*
* <p>Disambiguation: the fetch response returns a legacy "state" value that is redundant with the
* HTTP status code. The former is normalized into the latter.
*/
status: number;

/**
* Defines the ETag response header value.
*
* <p>Only defined for 200 and 304 responses.
*/
eTag?: string;

/**
* Defines the map of parameters returned as "entries" in the fetch response body.
*
* <p>Only defined for 200 responses.
*/
config?: FirebaseRemoteConfigObject;

/**
* The version number of the config template fetched from the server.
*/
templateVersion?: number;

/**
* Metadata for A/B testing and Remote Config Rollout experiments.
*
* @remarks Only defined for 200 responses.
*/
experiments?: FirebaseExperimentDescription[];
}

/**
* Options for Remote Config initialization.
*
* @public
*/
export interface RemoteConfigOptions {
/**
* The ID of the template to use. If not provided, defaults to "firebase".
*/
templateId?: string;

/**
* Hydrates the state with an initial fetch response.
*/
initialFetchResponse?: FetchResponse;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if we should provide types that don't currently do anything. See what @mikehardy thinks

Copy link
Member

@russellwheatley russellwheatley Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'll keep this type, but it should be annotated as web only, pretty sure it is only supported on firebase-js-sdk. Also - it does nothing at the moment, as it isn't wired up. I would also add a note that this isn't implemented currently and it is to be implemented on web in the future.

@@ -26,9 +28,9 @@ export type { CustomSignals };
*/
export function getRemoteConfig(app?: ReactNativeFirebase.FirebaseApp): RemoteConfig {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this should map to firebase-js-sdk getRemoteConfig(): https://firebase.google.com/docs/reference/js/remote-config.md#getremoteconfig_61d368f

Essentially needs RemoteConfigOptions as second arg. confirm that works for you, @mikehardy 🙏

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit. Let me know if that's what you expected.

* @param key - key for the given value
* @returns ConfigValue
*/
export function getValue(remoteConfig: RemoteConfig, key: string): ConfigValue {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit.

) => Promise<null>;

/** Alias for Value. */
export type ConfigValue = Value;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this alias and use Value

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit.

export type ConfigValues = { [key: string]: Value };

/** Alias for FetchStatus. */
export type LastFetchStatusType = FetchStatus;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LastFetchStatusType doesn't exist on firebase-js-sdk, remove and use FetchStatus

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit.

export type LastFetchStatusType = FetchStatus;

/** Alias for LogLevel. */
export type RemoteConfigLogLevel = LogLevel;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should also be public, it's on consumer facing api: https://firebase.google.com/docs/reference/js/remote-config.md#setloglevel_039a45b

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit.

*/
export function onConfigUpdated(
remoteConfig: RemoteConfig,
callback: CallbackOrObserver<OnConfigUpdatedListenerCallback>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type comes from internal, use the type from namespaced/types.ts instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the Modular API import namespaced types?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modular should not import namespaced types.

if anything, it should be:

modular -> namespaced.

We're modular first now, and namespaced will be removed soon. Same for web, any types for RNFBConfigModule.js, should be from modular or internal types.

*/
export function setConfigSettings(
remoteConfig: RemoteConfig,
settings: ConfigSettings,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use RemoteConfigSettings here instead

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit.

Copy link
Member

@russellwheatley russellwheatley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@akashdeep931 - getting really close with this! Just need to revert a couple of changes made to release docs. I'll be going over this tomorrow to double check the config options setup in the comparison script and whether they need to change as well 🙏


![image](https://user-images.githubusercontent.com/5347038/58876587-7c62b100-86c6-11e9-81f9-95c26e1485a1.png)

- [BREAKING] Module namespace has been renamed to `.remoteConfig()`, replace all usages of `firebase.config` with the new name.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert all changes to this file, these are the release notes for a previous release and should remain the same.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And commit.

`@react-native-firebase/remote-config`

- Module namespace has been renamed to `.remoteConfig()` from `.config()`.
- All Remote Config values can now be accessed synchronously in JS, see `getValue(key: string): ConfigValue` & `getAll(): ConfigValues` below.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert change for the same reason above

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And commit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still needs reverting. It should be exactly the same as before.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, my bad, here's the fix.

Copy link
Member

@russellwheatley russellwheatley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, now that I've spent a long time looking, there's a bunch of other changes I've missed. but these should really bring us closer to what we want 😄

`@react-native-firebase/remote-config`

- Module namespace has been renamed to `.remoteConfig()` from `.config()`.
- All Remote Config values can now be accessed synchronously in JS, see `getValue(key: string): ConfigValue` & `getAll(): ConfigValues` below.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still needs reverting. It should be exactly the same as before.


type RemoteConfigInstance = FirebaseRemoteConfigTypes.Module & {
settings: Record<string, number>;
defaultConfig: FirebaseRemoteConfigTypes.ConfigDefaults;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove all references to FirebaseRemoteConfigTypes in web. At some point we're going to remove those types. So types for modular and in here ought to come from types/internal or types/modular.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit.

{ minimumFetchIntervalMillis: number; fetchTimeoutMillis: number }
> = {};

const defaultConfigForInstance: Record<string, FirebaseRemoteConfigTypes.ConfigDefaults> = {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit.

});
},

setDefaults(appName: string, defaults: FirebaseRemoteConfigTypes.ConfigDefaults) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit.

Comment on lines +20 to +44
export type {
ConfigUpdate,
ConfigUpdateObserver,
ConfigValues,
CustomSignals,
FetchResponse,
FetchStatus,
FetchType,
FirebaseExperimentDescription,
FirebaseRemoteConfigObject,
LogLevel,
RemoteConfig,
RemoteConfigLogLevel,
RemoteConfigOptions,
RemoteConfigSettings,
Unsubscribe,
Value,
ValueSource as ModularValueSource,
} from './types/modular';

export * from './modular';

export type { FirebaseRemoteConfigTypes } from './types/namespaced';
export * from './namespaced';
export { default } from './namespaced';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update this. It needs to look similar to this:


// Export modular types from types/storage
export type * from './types/storage';

// Export modular API functions
export * from './modular';

// Export namespaced API
export type { FirebaseStorageTypes } from './types/namespaced';
export * from './namespaced';
export { default } from './namespaced';

A clear separation of modular and namespaced. It's also important to do this:

export type * from './types/storage';

It helps find out if we have duplicate type and value. If there is a type that is also a value, we should use the value as the type. I hope that makes sense.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense. Fixed in commit.

Comment on lines +91 to +97
{
name: 'settings',
reason:
'Getter function returning the current `ConfigSettings`. In the ' +
'firebase-js-sdk settings are accessed via the `settings` property ' +
'on the `RemoteConfig` object rather than a standalone function.',
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can also be removed from modular.ts, should be getting from remote config instance now.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit.

Comment on lines +162 to +166
reason:
'The RN Firebase implementation accepts `RemoteConfigLogLevel` (a type alias ' +
'for `LogLevel`) and returns it, whereas the firebase-js-sdk accepts `LogLevel` ' +
'and returns `void`. The parameter types are semantically identical; the return ' +
'type difference is a legacy artefact of the RN implementation.',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's update this to match firebase-js-sdk

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit.

Comment on lines +147 to +152
name: 'getAll',
reason:
'Returns `ConfigValues` (RN Firebase type alias for `{ [key: string]: Value }`) ' +
'instead of `Record<string, Value>`. The two types are structurally equivalent; ' +
'the alias is retained for backwards compatibility.',
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be removed when we remove ConfigValues and use raw { [key: string]: Value } type for getAll().

Copy link
Author

@akashdeep931 akashdeep931 Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit.

akashdeep931 and others added 28 commits March 2, 2026 14:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants