From bd49d0551f36ebb9c0246b339b475388c61f0395 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 2 Dec 2025 10:36:20 +0000 Subject: [PATCH 01/28] refactor: first step to TS, package.json --- packages/app/package.json | 64 +++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 8a324870a4..e4e6ebf049 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -3,15 +3,17 @@ "version": "23.5.0", "author": "Invertase (http://invertase.io)", "description": "A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Admob, Analytics, Auth, Crash Reporting, Cloud Firestore, Database, Dynamic Links, Functions, Messaging (FCM), Remote Config, Storage and more.", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "./dist/commonjs/index.js", + "module": "./dist/module/index.js", + "types": "./dist/typescript/commonjs/lib/index.d.ts", "scripts": { - "build": "genversion --semi lib/version.js && npm run build:version", + "build": "genversion --esm --semi lib/version.ts && npm run build:version", "build:version": "node ./scripts/genversion-ios && node ./scripts/genversion-android", "build:clean": "rimraf android/build && rimraf ios/build", "build:plugin": "rimraf plugin/build && tsc --build plugin", "lint:plugin": "eslint plugin/src/*", - "prepare": "npm run build && npm run build:plugin" + "compile": "bob build", + "prepare": "npm run build && npm run build:plugin && npm run compile" }, "repository": { "type": "git", @@ -51,6 +53,27 @@ "dynamic-links", "crashlytics" ], + "exports": { + ".": { + "source": "./lib/index.js", + "import": { + "types": "./dist/typescript/module/lib/index.d.ts", + "default": "./dist/module/index.js" + }, + "require": { + "types": "./dist/typescript/commonjs/lib/index.d.ts", + "default": "./dist/commonjs/index.js" + } + }, + "./package.json": "./package.json" + }, + "files": [ + "lib", + "dist", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], "peerDependencies": { "expo": ">=47.0.0", "react": "*", @@ -61,7 +84,8 @@ }, "devDependencies": { "@react-native-async-storage/async-storage": "^2.2.0", - "expo": "^54.0.25" + "expo": "^54.0.25", + "react-native-builder-bob": "^0.40.13" }, "peerDependenciesMeta": { "expo": { @@ -90,5 +114,33 @@ "playServicesAuth": "21.4.0", "firebaseAppDistributionGradle": "5.2.0" } - } + }, + "react-native-builder-bob": { + "source": "lib", + "output": "dist", + "targets": [ + [ + "module", + { + "esm": true + } + ], + [ + "commonjs", + { + "esm": true + } + ], + [ + "typescript", + { + "tsc": "../../node_modules/.bin/tsc" + } + ] + ] + }, + "eslintIgnore": [ + "node_modules/", + "dist/" + ] } From e5a36728f7e6fa49397ca5c96b53f86a8342adb8 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 2 Dec 2025 10:38:12 +0000 Subject: [PATCH 02/28] refactor: create tsconfig.json for app package --- packages/app/tsconfig.json | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 packages/app/tsconfig.json diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json new file mode 100644 index 0000000000..33babab5a8 --- /dev/null +++ b/packages/app/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react-jsx", + "lib": ["ESNext", "DOM"], + "module": "ESNext", + "moduleResolution": "bundler", + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noImplicitUseStrict": false, + "noStrictGenericChecks": false, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "ESNext", + "verbatimModuleSyntax": true, + "baseUrl": ".", + "rootDir": "." + }, + "include": ["lib/**/*"], + "exclude": ["node_modules", "dist", "__tests__", "**/*.test.ts"] +} From 0fdeb1ee55f534212d92b068893e9f1b05011819 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 2 Dec 2025 14:07:27 +0000 Subject: [PATCH 03/28] refactor: move from JS to TS --- .../lib/{FirebaseApp.js => FirebaseApp.ts} | 43 +- .../app/lib/common/{Base64.js => Base64.ts} | 30 +- ...{MutatableParams.js => MutatableParams.ts} | 13 +- .../{ReferenceBase.js => ReferenceBase.ts} | 6 +- .../app/lib/common/{deeps.js => deeps.ts} | 22 +- packages/app/lib/common/{id.js => id.ts} | 27 +- packages/app/lib/common/index.d.ts | 10 - .../app/lib/common/{index.js => index.ts} | 114 ++-- packages/app/lib/common/{path.js => path.ts} | 20 +- .../app/lib/common/{promise.js => promise.ts} | 27 +- .../lib/common/{serialize.js => serialize.ts} | 9 +- packages/app/lib/common/validate.d.ts | 110 ---- .../lib/common/{validate.js => validate.ts} | 45 +- packages/app/lib/index.d.ts | 585 +---------------- packages/app/lib/{index.js => index.ts} | 7 +- .../{FirebaseModule.js => FirebaseModule.ts} | 37 +- ...irebaseError.js => NativeFirebaseError.ts} | 40 +- ...ntEmitter.js => RNFBNativeEventEmitter.ts} | 26 +- ...dEventEmitter.js => SharedEventEmitter.ts} | 0 packages/app/lib/internal/asyncStorage.d.ts | 47 -- packages/app/lib/internal/asyncStorage.js | 47 -- packages/app/lib/internal/asyncStorage.ts | 81 +++ .../internal/{constants.js => constants.ts} | 4 +- .../app/lib/internal/{index.js => index.ts} | 0 packages/app/lib/internal/logger.d.ts | 85 --- .../app/lib/internal/{logger.js => logger.ts} | 150 +++-- ...ule.android.js => nativeModule.android.ts} | 1 + ...ativeModule.ios.js => nativeModule.ios.ts} | 1 + .../{nativeModule.js => nativeModule.ts} | 1 + ...ndroidIos.js => nativeModuleAndroidIos.ts} | 23 +- ...{nativeModuleWeb.js => nativeModuleWeb.ts} | 26 +- .../lib/internal/registry/{app.js => app.ts} | 40 +- .../registry/{namespace.js => namespace.ts} | 49 +- .../{nativeModule.js => nativeModule.ts} | 45 +- packages/app/lib/modular/index.d.ts | 131 ---- packages/app/lib/modular/index.js | 150 ----- packages/app/lib/modular/index.ts | 187 ++++++ packages/app/lib/types/index.ts | 614 ++++++++++++++++++ .../{UtilsStatics.js => UtilsStatics.ts} | 27 +- packages/app/lib/utils/{index.js => index.ts} | 22 +- 40 files changed, 1434 insertions(+), 1468 deletions(-) rename packages/app/lib/{FirebaseApp.js => FirebaseApp.ts} (60%) rename packages/app/lib/common/{Base64.js => Base64.ts} (76%) rename packages/app/lib/common/{MutatableParams.js => MutatableParams.ts} (81%) rename packages/app/lib/common/{ReferenceBase.js => ReferenceBase.ts} (94%) rename packages/app/lib/common/{deeps.js => deeps.ts} (77%) rename packages/app/lib/common/{id.js => id.ts} (69%) delete mode 100644 packages/app/lib/common/index.d.ts rename packages/app/lib/common/{index.js => index.ts} (87%) rename packages/app/lib/common/{path.js => path.ts} (83%) rename packages/app/lib/common/{promise.js => promise.ts} (61%) rename packages/app/lib/common/{serialize.js => serialize.ts} (86%) delete mode 100644 packages/app/lib/common/validate.d.ts rename packages/app/lib/common/{validate.js => validate.ts} (75%) rename packages/app/lib/{index.js => index.ts} (82%) rename packages/app/lib/internal/{FirebaseModule.js => FirebaseModule.ts} (63%) rename packages/app/lib/internal/{NativeFirebaseError.js => NativeFirebaseError.ts} (70%) rename packages/app/lib/internal/{RNFBNativeEventEmitter.js => RNFBNativeEventEmitter.ts} (82%) rename packages/app/lib/internal/{SharedEventEmitter.js => SharedEventEmitter.ts} (100%) delete mode 100644 packages/app/lib/internal/asyncStorage.d.ts delete mode 100644 packages/app/lib/internal/asyncStorage.js create mode 100644 packages/app/lib/internal/asyncStorage.ts rename packages/app/lib/internal/{constants.js => constants.ts} (93%) rename packages/app/lib/internal/{index.js => index.ts} (100%) delete mode 100644 packages/app/lib/internal/logger.d.ts rename packages/app/lib/internal/{logger.js => logger.ts} (60%) rename packages/app/lib/internal/{nativeModule.android.js => nativeModule.android.ts} (99%) rename packages/app/lib/internal/{nativeModule.ios.js => nativeModule.ios.ts} (99%) rename packages/app/lib/internal/{nativeModule.js => nativeModule.ts} (99%) rename packages/app/lib/internal/{nativeModuleAndroidIos.js => nativeModuleAndroidIos.ts} (57%) rename packages/app/lib/internal/{nativeModuleWeb.js => nativeModuleWeb.ts} (56%) rename packages/app/lib/internal/registry/{app.js => app.ts} (84%) rename packages/app/lib/internal/registry/{namespace.js => namespace.ts} (82%) rename packages/app/lib/internal/registry/{nativeModule.js => nativeModule.ts} (84%) delete mode 100644 packages/app/lib/modular/index.d.ts delete mode 100644 packages/app/lib/modular/index.js create mode 100644 packages/app/lib/modular/index.ts create mode 100644 packages/app/lib/types/index.ts rename packages/app/lib/utils/{UtilsStatics.js => UtilsStatics.ts} (72%) rename packages/app/lib/utils/{index.js => index.ts} (80%) diff --git a/packages/app/lib/FirebaseApp.js b/packages/app/lib/FirebaseApp.ts similarity index 60% rename from packages/app/lib/FirebaseApp.js rename to packages/app/lib/FirebaseApp.ts index 841138702c..42d7769adb 100644 --- a/packages/app/lib/FirebaseApp.js +++ b/packages/app/lib/FirebaseApp.ts @@ -14,12 +14,26 @@ * limitations under the License. * */ -import { warnIfNotModularCall } from '@react-native-firebase/app/lib/common'; +import { warnIfNotModularCall } from './common'; import { getAppModule } from './internal/registry/nativeModule'; +import type { ReactNativeFirebase } from './types'; -export default class FirebaseApp { - constructor(options, appConfig, fromNative, deleteApp) { - const { name, automaticDataCollectionEnabled } = appConfig; +export default class FirebaseApp implements ReactNativeFirebase.FirebaseApp { + private _name: string; + private _deleted: boolean; + private _deleteApp: () => Promise; + private _options: ReactNativeFirebase.FirebaseAppOptions; + private _automaticDataCollectionEnabled: boolean; + _initialized: boolean; + _nativeInitialized: boolean; + + constructor( + options: ReactNativeFirebase.FirebaseAppOptions, + appConfig: ReactNativeFirebase.FirebaseAppConfig, + fromNative: boolean, + deleteApp: () => Promise, + ) { + const { name = '[DEFAULT]', automaticDataCollectionEnabled } = appConfig; this._name = name; this._deleted = false; @@ -36,44 +50,49 @@ export default class FirebaseApp { } } - get name() { + get name(): string { return this._name; } - get options() { + get options(): ReactNativeFirebase.FirebaseAppOptions { return Object.assign({}, this._options); } - get automaticDataCollectionEnabled() { + get automaticDataCollectionEnabled(): boolean { return this._automaticDataCollectionEnabled; } - set automaticDataCollectionEnabled(enabled) { + set automaticDataCollectionEnabled(enabled: boolean) { this._checkDestroyed(); getAppModule().setAutomaticDataCollectionEnabled(this.name, enabled); this._automaticDataCollectionEnabled = enabled; } - _checkDestroyed() { + private _checkDestroyed(): void { if (this._deleted) { throw new Error(`Firebase App named '${this._name}' already deleted`); } } - extendApp(extendedProps) { + extendApp(extendedProps: Record): void { warnIfNotModularCall(arguments); this._checkDestroyed(); Object.assign(this, extendedProps); } - delete() { + delete(): Promise { warnIfNotModularCall(arguments, 'deleteApp()'); this._checkDestroyed(); return this._deleteApp(); } - toString() { + toString(): string { warnIfNotModularCall(arguments, '.name property'); return this.name; } + + // For backward compatibility - utils method added by registry + utils(): any { + throw new Error('utils() should be added by registry'); + } } diff --git a/packages/app/lib/common/Base64.js b/packages/app/lib/common/Base64.ts similarity index 76% rename from packages/app/lib/common/Base64.js rename to packages/app/lib/common/Base64.ts index 6c12c6efb5..707d7f1796 100644 --- a/packages/app/lib/common/Base64.js +++ b/packages/app/lib/common/Base64.ts @@ -15,6 +15,7 @@ * */ +// @ts-expect-error - No type declarations available import binaryToBase64 from 'react-native/Libraries/Utilities/binaryToBase64'; import { promiseDefer } from './promise'; @@ -23,7 +24,7 @@ const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/= /** * window.btoa */ -function btoa(input) { +function btoa(input: string): string { let map; let i = 0; let block = 0; @@ -51,11 +52,11 @@ function btoa(input) { /** * window.atob */ -function atob(input) { +function atob(input: string): string { let i = 0; let bc = 0; let bs = 0; - let buffer; + let buffer: number | string; let output = ''; const str = input.replace(/[=]+$/, ''); @@ -69,7 +70,7 @@ function atob(input) { for ( bc = 0, bs = 0, i = 0; (buffer = str.charAt(i++)); - ~buffer && ((bs = bc % 4 ? bs * 64 + buffer : buffer), bc++ % 4) + ~buffer && ((bs = bc % 4 ? bs * 64 + (buffer as number) : buffer), bc++ % 4) ? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6)))) : 0 ) { @@ -79,23 +80,30 @@ function atob(input) { return output; } +export interface Base64Result { + string: string | ArrayBuffer | null; + format: 'data_url' | 'base64'; +} + /** * Converts a Blob, ArrayBuffer or Uint8Array to a base64 string. */ -function fromData(data) { +function fromData(data: Blob | ArrayBuffer | Uint8Array): Promise { if (data instanceof Blob) { const fileReader = new FileReader(); - const { resolve, reject, promise } = promiseDefer(); + const { resolve, reject, promise } = promiseDefer(); fileReader.readAsDataURL(data); - fileReader.onloadend = function onloadend() { - resolve({ string: fileReader.result, format: 'data_url' }); + fileReader.onloadend = () => { + if (fileReader?.result) { + resolve?.({ string: fileReader.result, format: 'data_url' }); + } }; - fileReader.onerror = function onerror(event) { - fileReader.abort(); - reject(event); + fileReader.onerror = event => { + fileReader?.abort(); + reject?.(event); }; return promise; diff --git a/packages/app/lib/common/MutatableParams.js b/packages/app/lib/common/MutatableParams.ts similarity index 81% rename from packages/app/lib/common/MutatableParams.js rename to packages/app/lib/common/MutatableParams.ts index 8b998dade5..00710b7867 100644 --- a/packages/app/lib/common/MutatableParams.js +++ b/packages/app/lib/common/MutatableParams.ts @@ -18,7 +18,10 @@ import { deepGet, deepSet } from './deeps'; export default class MutatableParams { - constructor(parentInstance) { + _mutatableParams: Record; + _parentInstance: MutatableParams; + + constructor(parentInstance?: MutatableParams) { if (parentInstance) { this._mutatableParams = parentInstance._mutatableParams; this._parentInstance = parentInstance; @@ -28,20 +31,20 @@ export default class MutatableParams { } } - set(param, value) { + set(param: string, value: any): MutatableParams { deepSet(this._mutatableParams, param, value); return this._parentInstance; } - get(param) { + get(param: string): any { return deepGet(this._mutatableParams, param, '.'); } - toJSON() { + toJSON(): Record { return Object.assign({}, this._mutatableParams); } - validate() { + validate(): void { // do nothing } } diff --git a/packages/app/lib/common/ReferenceBase.js b/packages/app/lib/common/ReferenceBase.ts similarity index 94% rename from packages/app/lib/common/ReferenceBase.js rename to packages/app/lib/common/ReferenceBase.ts index 0fe24f608a..5e73ba5e86 100644 --- a/packages/app/lib/common/ReferenceBase.js +++ b/packages/app/lib/common/ReferenceBase.ts @@ -16,7 +16,9 @@ */ export default class ReferenceBase { - constructor(_path) { + path: string; + + constructor(_path: string) { let path = _path; if (path) { @@ -37,7 +39,7 @@ export default class ReferenceBase { * @type {String} * {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#key} */ - get key() { + get key(): string | null { return this.path === '/' ? null : this.path.substring(this.path.lastIndexOf('/') + 1); } } diff --git a/packages/app/lib/common/deeps.js b/packages/app/lib/common/deeps.ts similarity index 77% rename from packages/app/lib/common/deeps.js rename to packages/app/lib/common/deeps.ts index cf235c376b..8c7660966a 100644 --- a/packages/app/lib/common/deeps.js +++ b/packages/app/lib/common/deeps.ts @@ -25,7 +25,7 @@ import { isArray, isObject } from './validate'; * @param joiner * @returns {*} */ -export function deepGet(object, path, joiner = '/') { +export function deepGet(object: any, path: string, joiner: string = '/'): any { if (!isObject(object) && !Array.isArray(object)) { return undefined; } @@ -36,11 +36,11 @@ export function deepGet(object, path, joiner = '/') { const len = keys.length; while (i < len) { - const key = keys[i++]; + const key = keys[i++]!; if (!tmp || !Object.hasOwnProperty.call(tmp, key)) { return undefined; } - tmp = tmp[key]; + tmp = (tmp as Record)[key]; } return tmp; @@ -54,7 +54,13 @@ export function deepGet(object, path, joiner = '/') { * @param initPaths * @param joiner */ -export function deepSet(object, path, value, initPaths = true, joiner = '.') { +export function deepSet( + object: any, + path: string, + value: any, + initPaths: boolean = true, + joiner: string = '.', +): boolean { if (!isObject(object)) { return false; } @@ -65,15 +71,15 @@ export function deepSet(object, path, value, initPaths = true, joiner = '.') { const len = keys.length - 1; while (i < len) { - const key = keys[i++]; + const key = keys[i++]!; if (initPaths && !Object.hasOwnProperty.call(object, key)) { - _object[key] = {}; + (_object as Record)[key] = {}; } - _object = _object[key]; + _object = (_object as Record)[key]; } if (isObject(_object) || (isArray(_object) && !Number.isNaN(keys[i]))) { - _object[keys[i]] = value; + (_object as Record)[keys[i]!] = value; } else { return false; } diff --git a/packages/app/lib/common/id.js b/packages/app/lib/common/id.ts similarity index 69% rename from packages/app/lib/common/id.js rename to packages/app/lib/common/id.ts index 1266cb815b..568f33ef52 100644 --- a/packages/app/lib/common/id.js +++ b/packages/app/lib/common/id.ts @@ -1,3 +1,20 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * 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. + * + */ + const PUSH_CHARS = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'; const AUTO_ID_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; @@ -9,14 +26,14 @@ let lastPushTime = 0; // timestamp to prevent collisions with other clients. We store the last characters we // generated because in the event of a collision, we'll use those same characters except // "incremented" by one. -const lastRandChars = []; +const lastRandChars: number[] = []; /** * Generate a firebase id - for use with ref().push(val, cb) - e.g. -KXMr7k2tXUFQqiaZRY4' * @param serverTimeOffset - pass in server time offset from native side * @returns {string} */ -export function generateDatabaseId(serverTimeOffset = 0) { +export function generateDatabaseId(serverTimeOffset: number = 0): string { const timeStampChars = new Array(8); let now = new Date().getTime() + serverTimeOffset; const duplicateTime = now === lastPushTime; @@ -46,11 +63,11 @@ export function generateDatabaseId(serverTimeOffset = 0) { lastRandChars[i] = 0; } - lastRandChars[i] += 1; + lastRandChars[i]! += 1; } for (let i = 0; i < 12; i++) { - id += PUSH_CHARS.charAt(lastRandChars[i]); + id += PUSH_CHARS.charAt(lastRandChars[i]!); } if (id.length !== 20) { @@ -64,7 +81,7 @@ export function generateDatabaseId(serverTimeOffset = 0) { * Generate a firestore auto id for use with collection/document .add() * @return {string} */ -export function generateFirestoreId() { +export function generateFirestoreId(): string { let autoId = ''; for (let i = 0; i < 20; i++) { diff --git a/packages/app/lib/common/index.d.ts b/packages/app/lib/common/index.d.ts deleted file mode 100644 index fc3b4a8963..0000000000 --- a/packages/app/lib/common/index.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const MODULAR_DEPRECATION_ARG: string; -export const isAndroid: boolean; -export const isNumber: (value: any) => value is number; - -export function createMessage( - nameSpace: string, - methodName: string, - instanceName?: string, - uniqueMessage?: string | null, -): string; diff --git a/packages/app/lib/common/index.js b/packages/app/lib/common/index.ts similarity index 87% rename from packages/app/lib/common/index.js rename to packages/app/lib/common/index.ts index 08afb367c2..0c3771d9a3 100644 --- a/packages/app/lib/common/index.js +++ b/packages/app/lib/common/index.ts @@ -26,7 +26,12 @@ export * from './validate'; export { default as Base64 } from './Base64'; export { default as ReferenceBase } from './ReferenceBase'; -export function getDataUrlParts(dataUrlString) { +export interface DataUrlParts { + base64String: string | undefined; + mediaType: string | undefined; +} + +export function getDataUrlParts(dataUrlString: string): DataUrlParts { const isBase64 = dataUrlString.includes(';base64'); let [mediaType, base64String] = dataUrlString.split(','); if (!mediaType || !base64String) { @@ -42,11 +47,14 @@ export function getDataUrlParts(dataUrlString) { return { base64String, mediaType }; } -export function once(fn, context) { - let onceResult; +export function once any>( + fn: T, + context?: any, +): (...args: Parameters) => ReturnType { + let onceResult: ReturnType; let ranOnce = false; - return function onceInner(...args) { + return function onceInner(this: any, ...args: Parameters): ReturnType { if (!ranOnce) { ranOnce = true; onceResult = fn.apply(context || this, args); @@ -56,7 +64,7 @@ export function once(fn, context) { }; } -export function isError(value) { +export function isError(value: any): value is Error { if (Object.prototype.toString.call(value) === '[object Error]') { return true; } @@ -64,7 +72,7 @@ export function isError(value) { return value instanceof Error; } -export function hasOwnProperty(target, property) { +export function hasOwnProperty(target: object, property: string | symbol): boolean { return Object.hasOwnProperty.call(target, property); } @@ -74,7 +82,7 @@ export function hasOwnProperty(target, property) { * @param string * @returns {*} */ -export function stripTrailingSlash(string) { +export function stripTrailingSlash(string: any): any { if (!isString(string)) { return string; } @@ -87,7 +95,7 @@ export const isAndroid = Platform.OS === 'android'; export const isOther = Platform.OS !== 'ios' && Platform.OS !== 'android'; -export function tryJSONParse(string) { +export function tryJSONParse(string: string | null | undefined): any { try { return string && JSON.parse(string); } catch (_) { @@ -95,7 +103,7 @@ export function tryJSONParse(string) { } } -export function tryJSONStringify(data) { +export function tryJSONStringify(data: any): string | null { try { return JSON.stringify(data); } catch (_) { @@ -103,14 +111,23 @@ export function tryJSONStringify(data) { } } -export function parseListenerOrObserver(listenerOrObserver) { +export interface Observer { + next: (value: T) => void; + error?: (error: Error) => void; + complete?: () => void; +} + +export function parseListenerOrObserver( + listenerOrObserver: ((value: T) => void) | Observer, +): (value: T) => void { if (!isFunction(listenerOrObserver) && !isObject(listenerOrObserver)) { + throw new Error("'listenerOrObserver' expected a function or an object with 'next' function."); } if (isFunction(listenerOrObserver)) { return listenerOrObserver; } - if (isObject(listenerOrObserver) && isFunction(listenerOrObserver.next)) { - return listenerOrObserver.next.bind(listenerOrObserver); + if (isObject(listenerOrObserver) && isFunction((listenerOrObserver as Observer).next)) { + return (listenerOrObserver as Observer).next!.bind(listenerOrObserver); } throw new Error("'listenerOrObserver' expected a function or an object with 'next' function."); @@ -119,7 +136,11 @@ export function parseListenerOrObserver(listenerOrObserver) { // Used to indicate if there is no corresponding modular function const NO_REPLACEMENT = true; -const mapOfDeprecationReplacements = { +type MethodMap = Record; +type InstanceMap = Record; +type DeprecationMap = Record; + +const mapOfDeprecationReplacements: DeprecationMap = { analytics: { default: { logEvent: 'logEvent()', @@ -523,18 +544,23 @@ const modularDeprecationMessage = 'This method is deprecated (as well as all React Native Firebase namespaced API) and will be removed in the next major release ' + 'as part of move to match Firebase Web modular SDK API. Please see migration guide for more details: https://rnfirebase.io/migrating-to-v22'; -export function deprecationConsoleWarning(nameSpace, methodName, instanceName, isModularMethod) { +export function deprecationConsoleWarning( + nameSpace: string, + methodName: string, + instanceName: string, + isModularMethod: boolean, +): void { if (!isModularMethod) { const moduleMap = mapOfDeprecationReplacements[nameSpace]; if (moduleMap) { const instanceMap = moduleMap[instanceName]; - const deprecatedMethod = instanceMap[methodName]; + const deprecatedMethod = instanceMap?.[methodName]; if (instanceMap && deprecatedMethod) { - if (!globalThis.RNFB_SILENCE_MODULAR_DEPRECATION_WARNINGS) { + if (!(globalThis as any).RNFB_SILENCE_MODULAR_DEPRECATION_WARNINGS) { // eslint-disable-next-line no-console console.warn(createMessage(nameSpace, methodName, instanceName)); - if (globalThis.RNFB_MODULAR_DEPRECATION_STRICT_MODE === true) { + if ((globalThis as any).RNFB_MODULAR_DEPRECATION_STRICT_MODE === true) { throw new Error('Deprecated API usage detected while in strict mode.'); } } @@ -544,11 +570,11 @@ export function deprecationConsoleWarning(nameSpace, methodName, instanceName, i } export function createMessage( - nameSpace, - methodName, - instanceName = 'default', - uniqueMessage = null, -) { + nameSpace: string, + methodName: string, + instanceName: string = 'default', + uniqueMessage: string | null = null, +): string | undefined { if (uniqueMessage) { // Unique deprecation message used for testing return uniqueMessage; @@ -570,9 +596,10 @@ export function createMessage( } } } + return undefined; } -function getNamespace(target) { +function getNamespace(target: any): string | undefined { if (target.constructor.name === 'DatabaseReference') { return 'database'; } @@ -588,13 +615,14 @@ function getNamespace(target) { } const className = target.name ? target.name : target.constructor.name; return Object.keys(mapOfDeprecationReplacements).find(key => { - if (mapOfDeprecationReplacements[key][className]) { + if (mapOfDeprecationReplacements[key]?.[className]) { return key; } + return false; }); } -function getInstanceName(target) { +function getInstanceName(target: any): string { if (target.GeoPoint || target.CustomProvider) { // target is statics object. GeoPoint - Firestore, CustomProvider - AppCheck return 'statics'; @@ -619,13 +647,13 @@ function getInstanceName(target) { return target.constructor.name; } -export function createDeprecationProxy(instance) { +export function createDeprecationProxy(instance: T): T { return new Proxy(instance, { - construct(target, args) { + construct(target: any, args: any[]) { // needed for Timestamp which we pass as static, when we construct new instance, we need to wrap it in proxy again. return createDeprecationProxy(new target(...args)); }, - get(target, prop, receiver) { + get(target: any, prop: string | symbol, receiver: any) { const originalMethod = target[prop]; if (prop === 'constructor') { @@ -633,7 +661,7 @@ export function createDeprecationProxy(instance) { } if (target && target.constructor && target.constructor.name === 'FirestoreTimestamp') { - deprecationConsoleWarning('firestore', prop, 'FirestoreTimestamp', false); + deprecationConsoleWarning('firestore', prop as string, 'FirestoreTimestamp', false); return Reflect.get(target, prop, receiver); } @@ -703,28 +731,30 @@ export function createDeprecationProxy(instance) { const instanceName = getInstanceName(target); const nameSpace = getNamespace(target); - if (descriptor.get) { + if (descriptor.get && nameSpace) { // Handle getter - call it and show deprecation warning - deprecationConsoleWarning(nameSpace, prop, instanceName, _isModularCall); + deprecationConsoleWarning(nameSpace, prop as string, instanceName, _isModularCall); return descriptor.get.call(target); } - if (descriptor.set) { + if (descriptor.set && nameSpace) { // Handle setter - return a function that calls the setter with deprecation warning - return function (value) { - deprecationConsoleWarning(nameSpace, prop, instanceName, _isModularCall); - descriptor.set.call(target, value); + return function (value: any) { + deprecationConsoleWarning(nameSpace, prop as string, instanceName, _isModularCall); + descriptor.set!.call(target, value); }; } } if (typeof originalMethod === 'function') { - return function (...args) { + return function (...args: any[]) { const isModularMethod = args.includes(MODULAR_DEPRECATION_ARG); const instanceName = getInstanceName(target); const nameSpace = getNamespace(target); - deprecationConsoleWarning(nameSpace, prop, instanceName, isModularMethod); + if (nameSpace) { + deprecationConsoleWarning(nameSpace, prop as string, instanceName, isModularMethod); + } return originalMethod.apply(target, filterModularArgument(args)); }; @@ -739,7 +769,7 @@ export const MODULAR_DEPRECATION_ARG = 'react-native-firebase-modular-method-cal // Flag to track if we're currently in a modular call let _isModularCall = false; -export function withModularFlag(fn) { +export function withModularFlag(fn: () => T): T { const previousFlag = _isModularCall; _isModularCall = true; try { @@ -749,11 +779,11 @@ export function withModularFlag(fn) { } } -export function filterModularArgument(list) { +export function filterModularArgument(list: any[]): any[] { return list.filter(arg => arg !== MODULAR_DEPRECATION_ARG); } -export function warnIfNotModularCall(args, replacementMethodName = '') { +export function warnIfNotModularCall(args: IArguments, replacementMethodName: string = ''): void { for (let i = 0; i < args.length; i++) { if (args[i] === MODULAR_DEPRECATION_ARG) { return; @@ -765,11 +795,11 @@ export function warnIfNotModularCall(args, replacementMethodName = '') { message += ` Please use \`${replacementMethodName}\` instead.`; } - if (!globalThis.RNFB_SILENCE_MODULAR_DEPRECATION_WARNINGS) { + if (!(globalThis as any).RNFB_SILENCE_MODULAR_DEPRECATION_WARNINGS) { // eslint-disable-next-line no-console console.warn(message); - if (globalThis.RNFB_MODULAR_DEPRECATION_STRICT_MODE === true) { + if ((globalThis as any).RNFB_MODULAR_DEPRECATION_STRICT_MODE === true) { throw new Error('Deprecated API usage detected while in strict mode.'); } } diff --git a/packages/app/lib/common/path.js b/packages/app/lib/common/path.ts similarity index 83% rename from packages/app/lib/common/path.js rename to packages/app/lib/common/path.ts index 3e276f8529..81fdbcddd4 100644 --- a/packages/app/lib/common/path.js +++ b/packages/app/lib/common/path.ts @@ -18,7 +18,7 @@ /** * Returns the next parent of the path e.g. /foo/bar/car -> /foo/bar */ -export function pathParent(path) { +export function pathParent(path: string): string | null { if (path.length === 0) { return null; } @@ -34,7 +34,7 @@ export function pathParent(path) { /** * Joins a parent and a child path */ -export function pathChild(path, childPath) { +export function pathChild(path: string, childPath: string): string { const canonicalChildPath = pathPieces(childPath).join('/'); if (path.length === 0) { @@ -47,7 +47,7 @@ export function pathChild(path, childPath) { /** * Returns the last component of a path, e.g /foo/bar.jpeg -> bar.jpeg */ -export function pathLastComponent(path) { +export function pathLastComponent(path: string): string { const index = path.lastIndexOf('/', path.length - 2); if (index === -1) { return path; @@ -61,7 +61,7 @@ export function pathLastComponent(path) { * @param path * @returns {*} */ -export function pathPieces(path) { +export function pathPieces(path: string): string[] { return path.split('/').filter($ => $.length > 0); } @@ -70,7 +70,7 @@ export function pathPieces(path) { * @param path * @returns {boolean} */ -export function pathIsEmpty(path) { +export function pathIsEmpty(path: string): boolean { return !pathPieces(path).length; } @@ -79,7 +79,7 @@ export function pathIsEmpty(path) { * @param path * @returns {string|string} */ -export function pathToUrlEncodedString(path) { +export function pathToUrlEncodedString(path: string): string { const pieces = pathPieces(path); let pathString = ''; for (let i = 0; i < pieces.length; i++) { @@ -95,7 +95,7 @@ export const INVALID_PATH_REGEX = /[[\].#$\u0000-\u001F\u007F]/; * @param path * @returns {boolean} */ -export function isValidPath(path) { +export function isValidPath(path: any): boolean { return typeof path === 'string' && path.length !== 0 && !INVALID_PATH_REGEX.test(path); } @@ -106,8 +106,8 @@ export const INVALID_KEY_REGEX = /[\[\].#$\/\u0000-\u001F\u007F]/; * @param key * @returns {boolean} */ -export function isValidKey(key) { - return typeof key === 'string' && key.length !== 0 && !INVALID_KEY_REGEX.test(path); +export function isValidKey(key: any): boolean { + return typeof key === 'string' && key.length !== 0 && !INVALID_KEY_REGEX.test(key); } /** @@ -115,7 +115,7 @@ export function isValidKey(key) { * @param path * @returns {*} */ -export function toFilePath(path) { +export function toFilePath(path: string): string { let _filePath = path.replace('file://', ''); if (_filePath.includes('%')) { _filePath = decodeURIComponent(_filePath); diff --git a/packages/app/lib/common/promise.js b/packages/app/lib/common/promise.ts similarity index 61% rename from packages/app/lib/common/promise.js rename to packages/app/lib/common/promise.ts index b1f9b86e4d..bfe160075d 100644 --- a/packages/app/lib/common/promise.js +++ b/packages/app/lib/common/promise.ts @@ -17,16 +17,23 @@ import { isFunction } from './validate'; +export interface Deferred { + promise: Promise; + resolve: ((value: T) => void) | null; + reject: ((reason?: any) => void) | null; +} + /** - * + * Creates a deferred promise */ -export function promiseDefer() { - const deferred = { +export function promiseDefer(): Deferred { + const deferred: Deferred = { + promise: null as any, resolve: null, reject: null, }; - deferred.promise = new Promise((resolve, reject) => { + deferred.promise = new Promise((resolve, reject) => { deferred.resolve = resolve; deferred.reject = reject; }); @@ -34,11 +41,17 @@ export function promiseDefer() { return deferred; } +type Callback = ((error: Error | null, result?: T) => void) | ((error: Error | null) => void); + /** + * Attaches an optional callback to a promise * @param promise * @param callback */ -export function promiseWithOptionalCallback(promise, callback) { +export function promiseWithOptionalCallback( + promise: Promise, + callback?: Callback, +): Promise { if (!isFunction(callback)) { return promise; } @@ -46,9 +59,9 @@ export function promiseWithOptionalCallback(promise, callback) { return promise .then(result => { if (callback && callback.length === 1) { - callback(null); + (callback as (error: Error | null) => void)(null); } else if (callback) { - callback(null, result); + (callback as (error: Error | null, result?: T) => void)(null, result); } return result; diff --git a/packages/app/lib/common/serialize.js b/packages/app/lib/common/serialize.ts similarity index 86% rename from packages/app/lib/common/serialize.js rename to packages/app/lib/common/serialize.ts index 2d92aecc73..404e02615b 100644 --- a/packages/app/lib/common/serialize.js +++ b/packages/app/lib/common/serialize.ts @@ -18,7 +18,12 @@ import { tryJSONParse, tryJSONStringify } from './index'; import { isObject } from './validate'; -export function serializeType(value) { +export interface SerializedValue { + type: string; + value: any; +} + +export function serializeType(value: any): SerializedValue { if (isObject(value)) { return { type: 'object', @@ -32,7 +37,7 @@ export function serializeType(value) { }; } -export function serializeObject(object) { +export function serializeObject(object: any): any { if (!isObject(object)) { return object; } diff --git a/packages/app/lib/common/validate.d.ts b/packages/app/lib/common/validate.d.ts deleted file mode 100644 index 1d1131c48a..0000000000 --- a/packages/app/lib/common/validate.d.ts +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2016-present Invertase Limited & Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this library except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - * - */ - -/** - * Validates that all key-value pairs in an object are strings. - */ -export function objectKeyValuesAreStrings(object: any): boolean; - -/** - * Simple is null check. - */ -export function isNull(value: any): value is null; - -/** - * Simple is object check. - */ -export function isObject(value: any): value is Record; - -/** - * Simple is date check - */ -export function isDate(value: any): value is Date; - -/** - * Simple is function check - */ -export function isFunction(value: any): value is (...args: any[]) => any; - -/** - * Simple is string check - */ -export function isString(value: any): value is string; - -/** - * Simple is number check - */ -export function isNumber(value: any): value is number; - -/** - * Simple is phone number check for E.164 format - */ -export function isE164PhoneNumber(value: any): boolean; - -/** - * Simple finite check - */ -export function isFinite(value: any): boolean; - -/** - * Simple integer check - */ -export function isInteger(value: any): boolean; - -/** - * Simple is boolean check - */ -export function isBoolean(value: any): value is boolean; - -/** - * Array check - */ -export function isArray(value: any): value is Array; - -/** - * Undefined check - */ -export function isUndefined(value: any): value is undefined; - -/** - * Alphanumeric underscore pattern check (/^[a-zA-Z0-9_]+$/) - */ -export function isAlphaNumericUnderscore(value: any): boolean; - -/** - * URL validation test - */ -export function isValidUrl(url: any): boolean; - -/** - * Array includes check - */ -export function isOneOf(value: any, oneOf?: any[]): boolean; - -/** - * No-operation function - */ -export function noop(): void; - -/** - * Validates that an optional native dependency exists - */ -export function validateOptionalNativeDependencyExists( - firebaseJsonKey: string, - apiName: string, - nativeFnExists: boolean, -): void; diff --git a/packages/app/lib/common/validate.js b/packages/app/lib/common/validate.ts similarity index 75% rename from packages/app/lib/common/validate.js rename to packages/app/lib/common/validate.ts index de974d40bc..03b02e9324 100644 --- a/packages/app/lib/common/validate.js +++ b/packages/app/lib/common/validate.ts @@ -19,7 +19,10 @@ import { Platform } from 'react-native'; const AlphaNumericUnderscore = /^[a-zA-Z0-9_]+$/; -export function objectKeyValuesAreStrings(object) { +/** + * Validates that all key-value pairs in an object are strings. + */ +export function objectKeyValuesAreStrings(object: any): boolean { if (!isObject(object)) { return false; } @@ -27,7 +30,7 @@ export function objectKeyValuesAreStrings(object) { const entries = Object.entries(object); for (let i = 0; i < entries.length; i++) { - const [key, value] = entries[i]; + const [key, value] = entries[i]!; if (!isString(key) || !isString(value)) { return false; } @@ -42,7 +45,7 @@ export function objectKeyValuesAreStrings(object) { * @param value * @returns {boolean} */ -export function isNull(value) { +export function isNull(value: any): value is null { return value === null; } @@ -52,7 +55,7 @@ export function isNull(value) { * @param value * @returns {boolean} */ -export function isObject(value) { +export function isObject(value: any): value is Record { return value ? typeof value === 'object' && !Array.isArray(value) && !isNull(value) : false; } @@ -62,7 +65,7 @@ export function isObject(value) { * @param value * @returns {boolean} */ -export function isDate(value) { +export function isDate(value: any): value is Date { // use the global isNaN() and not Number.isNaN() since it will validate an Invalid Date return value && Object.prototype.toString.call(value) === '[object Date]' && !isNaN(value); } @@ -73,7 +76,7 @@ export function isDate(value) { * @param value * @returns {*|boolean} */ -export function isFunction(value) { +export function isFunction(value: any): value is (...args: any[]) => any { return value ? typeof value === 'function' : false; } @@ -82,7 +85,7 @@ export function isFunction(value) { * @param value * @return {boolean} */ -export function isString(value) { +export function isString(value: any): value is string { return typeof value === 'string'; } @@ -91,7 +94,7 @@ export function isString(value) { * @param value * @return {boolean} */ -export function isNumber(value) { +export function isNumber(value: any): value is number { return typeof value === 'number'; } @@ -100,7 +103,7 @@ export function isNumber(value) { * @param value * @return {boolean} */ -export function isE164PhoneNumber(value) { +export function isE164PhoneNumber(value: any): boolean { const PHONE_NUMBER = /^\+[1-9]\d{1,14}$/; // E.164 return PHONE_NUMBER.test(value); } @@ -110,7 +113,7 @@ export function isE164PhoneNumber(value) { * @param value * @returns {boolean} */ -export function isFinite(value) { +export function isFinite(value: any): boolean { return Number.isFinite(value); } @@ -119,7 +122,7 @@ export function isFinite(value) { * @param value * @returns {boolean} */ -export function isInteger(value) { +export function isInteger(value: any): boolean { return Number.isInteger(value); } @@ -129,7 +132,7 @@ export function isInteger(value) { * @param value * @return {boolean} */ -export function isBoolean(value) { +export function isBoolean(value: any): value is boolean { return typeof value === 'boolean'; } @@ -138,7 +141,7 @@ export function isBoolean(value) { * @param value * @returns {arg is Array} */ -export function isArray(value) { +export function isArray(value: any): value is Array { return Array.isArray(value); } @@ -147,7 +150,7 @@ export function isArray(value) { * @param value * @returns {boolean} */ -export function isUndefined(value) { +export function isUndefined(value: any): value is undefined { return typeof value === 'undefined'; } @@ -157,7 +160,7 @@ export function isUndefined(value) { * @param value * @returns {boolean} */ -export function isAlphaNumericUnderscore(value) { +export function isAlphaNumericUnderscore(value: any): boolean { return AlphaNumericUnderscore.test(value); } @@ -167,7 +170,7 @@ export function isAlphaNumericUnderscore(value) { * @returns {boolean} */ const IS_VALID_URL_REGEX = /^(http|https):\/\/[^ "]+$/; -export function isValidUrl(url) { +export function isValidUrl(url: any): boolean { return IS_VALID_URL_REGEX.test(url); } @@ -178,18 +181,22 @@ export function isValidUrl(url) { * @param oneOf * @returns {boolean} */ -export function isOneOf(value, oneOf = []) { +export function isOneOf(value: any, oneOf: any[] = []): boolean { if (!isArray(oneOf)) { return false; } return oneOf.includes(value); } -export function noop() { +export function noop(): void { // noop-🐈 } -export function validateOptionalNativeDependencyExists(firebaseJsonKey, apiName, nativeFnExists) { +export function validateOptionalNativeDependencyExists( + firebaseJsonKey: string, + apiName: string, + nativeFnExists: boolean, +): void { if (nativeFnExists) { return; } diff --git a/packages/app/lib/index.d.ts b/packages/app/lib/index.d.ts index de97d89580..c20953ea11 100644 --- a/packages/app/lib/index.d.ts +++ b/packages/app/lib/index.d.ts @@ -30,575 +30,9 @@ * * @firebase app */ -export namespace ReactNativeFirebase { - export interface NativeFirebaseError extends Error { - /** - * Firebase error code, e.g. `auth/invalid-email` - */ - readonly code: string; - /** - * Firebase error message - */ - readonly message: string; - - /** - * The firebase module namespace that this error originated from, e.g. 'analytics' - */ - readonly namespace: string; - - /** - * The native sdks returned error code, different per platform - */ - readonly nativeErrorCode: string | number; - - /** - * The native sdks returned error message, different per platform - */ - readonly nativeErrorMessage: string; - } - - export type LogLevelString = 'debug' | 'verbose' | 'info' | 'warn' | 'error' | 'silent'; - - export interface FirebaseAppOptions { - /** - * The Google App ID that is used to uniquely identify an instance of an app. - */ - appId: string; - - /** - * An API key used for authenticating requests from your app, e.g. - * "AIzaSyDdVgKwhZl0sTTTLZ7iTmt1r3N2cJLnaDk", used to identify your app to Google servers. - */ - apiKey?: string; - - /** - * The database root URL, e.g. "http://abc-xyz-123.firebaseio.com". - */ - databaseURL?: string; - - /** - * The Project ID from the Firebase console, for example "abc-xyz-123". - */ - projectId: string; - - /** - * The tracking ID for Google Analytics, e.g. "UA-12345678-1", used to configure Google Analytics. - */ - measurementId?: string; - - /** - * The Google Cloud Storage bucket name, e.g. "abc-xyz-123.storage.firebase.com". - */ - storageBucket?: string; - - /** - * The Project Number from the Google Developer's console, for example "012345678901", used to - * configure Google Cloud Messaging. - */ - messagingSenderId?: string; - - /** - * iOS only - The OAuth2 client ID for iOS application used to authenticate Google users, for example - * "12345.apps.googleusercontent.com", used for signing in with Google. - */ - clientId?: string; - - /** - * iOS only - The Android client ID used in Google AppInvite when an iOS app has its Android version, for - * example "12345.apps.googleusercontent.com". - */ - androidClientId?: string; - - /** - * iOS only - The URL scheme used to set up Durable Deep Link service. - */ - deepLinkURLScheme?: string; - [name: string]: any; - } - - export interface FirebaseAppConfig { - /** - * The Firebase App name, defaults to [DEFAULT] if none provided. - */ - name?: string; - - /** - * Default setting for data collection on startup that affects all Firebase module startup data collection settings, - * in the absence of module-specific overrides. This will start as false if you set "app_data_collection_default_enabled" - * to false in firebase.json and may be used in opt-in flows, for example a GDPR-compliant app. - * If configured false initially, set to true after obtaining consent, then enable module-specific settings as needed afterwards. - */ - automaticDataCollectionEnabled?: boolean; - - /** - * If set to true it indicates that Firebase should close database connections - * automatically when the app is in the background. Disabled by default. - */ - automaticResourceManagement?: boolean; - } - - export interface FirebaseApp { - /** - * The name (identifier) for this App. '[DEFAULT]' is the default App. - */ - readonly name: string; - - /** - * The (read-only) configuration options from the app initialization. - */ - readonly options: FirebaseAppOptions; - - /** - * The settable config flag for GDPR opt-in/opt-out - */ - automaticDataCollectionEnabled: boolean; - - /** - * Make this app unusable and free up resources. - */ - delete(): Promise; - - utils(): Utils.Module; - } - - /** - * Interface for a supplied `AsyncStorage`. - */ - export interface ReactNativeAsyncStorage { - /** - * Persist an item in storage. - * - * @param key - storage key. - * @param value - storage value. - */ - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - setItem: Function; - /** - * Retrieve an item from storage. - * - * @param key - storage key. - */ - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - getItem: Function; - /** - * Remove an item from storage. - * - * @param key - storage key. - */ - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - removeItem: Function; - - [key: string]: any; - } - - export interface Module { - /** - * Create (and initialize) a FirebaseApp. - * - * @param options Options to configure the services used in the App. - * @param config The optional config for your firebase app - */ - initializeApp(options: FirebaseAppOptions, config?: FirebaseAppConfig): Promise; - - /** - * Create (and initialize) a FirebaseApp. - * - * @param options Options to configure the services used in the App. - * @param name The optional name of the app to initialize ('[DEFAULT]' if - * omitted) - */ - initializeApp(options: FirebaseAppOptions, name?: string): Promise; - - /** - * Retrieve an instance of a FirebaseApp. - * - * @example - * ```js - * const app = firebase.app('foo'); - * ``` - * - * @param name The optional name of the app to return ('[DEFAULT]' if omitted) - */ - app(name?: string): FirebaseApp; - - /** - * Set the log level across all modules. Only applies to iOS currently, has no effect on Android. - * Should be one of 'error', 'warn', 'info', or 'debug'. - * Logs messages at the configured level or lower (less verbose / more important). - * Note that if an app is running from AppStore, it will never log above info even if - * level is set to a higher (more verbose) setting. - * Note that iOS is missing firebase-js-sdk log levels 'verbose' and 'silent'. - * 'verbose' if used will map to 'debug', 'silent' has no valid mapping and will return an error if used. - * - * @ios - */ - setLogLevel(logLevel: LogLevelString): void; - - /** - * The `AsyncStorage` implementation to use for persisting data on 'Other' platforms. - * If not specified, in memory persistence is used. - * - * This is required if you want to persist things like Auth sessions, Analytics device IDs, etc. - */ - setReactNativeAsyncStorage(asyncStorage: ReactNativeAsyncStorage): void; - - /** - * A (read-only) array of all the initialized Apps. - */ - apps: FirebaseApp[]; - - /** - * The current React Native Firebase version. - */ - readonly SDK_VERSION: string; - - /** - * Utils provides a collection of utilities to aid in using Firebase - * and related services inside React Native, e.g. Test Lab helpers - * and Google Play Services version helpers. - */ - utils: typeof utils; - } - - /** - * A class that all React Native Firebase modules extend from to provide default behaviour. - */ - export class FirebaseModule { - /** - * The current `FirebaseApp` instance for this Firebase service. - */ - app: FirebaseApp; - } - - // eslint-disable-next-line @typescript-eslint/no-empty-object-type - export type FirebaseModuleWithStatics = { - (): M; - - /** - * This React Native Firebase module version. - */ - readonly SDK_VERSION: string; - } & S; - - // eslint-disable-next-line @typescript-eslint/no-empty-object-type - export type FirebaseModuleWithStaticsAndApp = { - (app?: FirebaseApp): M; - - /** - * This React Native Firebase module version. - */ - readonly SDK_VERSION: string; - } & S; -} - -/* - * @firebase utils - */ -export namespace Utils { - import FirebaseModule = ReactNativeFirebase.FirebaseModule; - - /** - * A collection of native device file paths to aid in the usage of file path based methods. - * - * Concatenate a file path with your target file name when using with Storage `putFile` or `writeToFile`. - * - * ```js - * firebase.utils.FilePath; - * ``` - */ - export interface FilePath { - /** - * Returns an absolute path to the applications main bundle. - * - * ```js - * firebase.utils.FilePath.MAIN_BUNDLE; - * ``` - * - * @ios iOS only - */ - MAIN_BUNDLE: string; - - /** - * Returns an absolute path to the application specific cache directory on the filesystem. - * - * The system will automatically delete files in this directory when disk space is needed elsewhere on the device, starting with the oldest files first. - * - * ```js - * firebase.utils.FilePath.CACHES_DIRECTORY; - * ``` - */ - CACHES_DIRECTORY: string; - - /** - * Returns an absolute path to the users Documents directory. - * - * Use this directory to place documents that have been created by the user. - * - * Normally this is the external files directory on Android but if no external storage directory found, - * e.g. removable media has been ejected by the user, it will fall back to internal storage. This may - * under rare circumstances where device storage environment changes cause the directory to be different - * between runs of the application - * - * ```js - * firebase.utils.FilePath.DOCUMENT_DIRECTORY; - * ``` - */ - DOCUMENT_DIRECTORY: string; - - /** - * Returns an absolute path to a temporary directory. - * - * Use this directory to create temporary files. The system will automatically delete files in this directory when disk space is needed elsewhere on the device, starting with the oldest files first. - * - * ```js - * firebase.utils.FilePath.TEMP_DIRECTORY; - * ``` - */ - TEMP_DIRECTORY: string; - - /** - * Returns an absolute path to the apps library/resources directory. - * - * E.g. this can be used for things like documentation, support files, and configuration files and generic resources. - * - * ```js - * firebase.utils.FilePath.LIBRARY_DIRECTORY; - * ``` - */ - LIBRARY_DIRECTORY: string; - - /** - * Returns an absolute path to the directory on the primary shared/external storage device. - * - * Here your application can place persistent files it owns. These files are internal to the application, and not typically visible to the user as media. - * - * Returns null if no external storage directory found, e.g. removable media has been ejected by the user. - * - * ```js - * firebase.utils.FilePath.EXTERNAL_DIRECTORY; - * ``` - * - * @android Android only - iOS returns null - */ - EXTERNAL_DIRECTORY: string | null; - - /** - * Returns an absolute path to the primary shared/external storage directory. - * - * Traditionally this is an SD card, but it may also be implemented as built-in storage on a device. - * - * Returns null if no external storage directory found, e.g. removable media has been ejected by the user. - * Requires special permission granted by Play Store review team on Android, is unlikely to be a valid path. - * - * ```js - * firebase.utils.FilePath.EXTERNAL_STORAGE_DIRECTORY; - * ``` - * - * @android Android only - iOS returns null - */ - EXTERNAL_STORAGE_DIRECTORY: string | null; - - /** - * Returns an absolute path to a directory in which to place pictures that are available to the user. - * Requires special permission granted by Play Store review team on Android, is unlikely to be a valid path. - * - * ```js - * firebase.utils.FilePath.PICTURES_DIRECTORY; - * ``` - */ - PICTURES_DIRECTORY: string; - - /** - * Returns an absolute path to a directory in which to place movies that are available to the user. - * Requires special permission granted by Play Store review team on Android, is unlikely to be a valid path. - * - * ```js - * firebase.utils.FilePath.MOVIES_DIRECTORY; - * ``` - */ - MOVIES_DIRECTORY: string; - } - - export interface Statics { - FilePath: FilePath; - } - - /** - * For further information on the status codes available & what they represent, please head over - * to ConnectionResult documentation: - * https://developers.google.com/android/reference/com/google/android/gms/common/ConnectionResult - */ - export enum PlayServicesAvailabilityStatusCodes { - API_UNAVAILABLE = 16, - CANCELED = 13, - DEVELOPER_ERROR = 10, - DRIVE_EXTERNAL_STORAGE_REQUIRED = 1500, - INTERNAL_ERROR = 8, - INTERRUPTED = 15, - INVALID_ACCOUNT = 5, - LICENSE_CHECK_FAILED = 11, - NETWORK_ERROR = 7, - RESOLUTION_REQUIRED = 6, - RESTRICTED_PROFILE = 20, - SERVICE_DISABLED = 3, - SERVICE_INVALID = 9, - SERVICE_MISSING = 1, - SERVICE_MISSING_PERMISSION = 19, - SERVICE_UPDATING = 18, - SERVICE_VERSION_UPDATE_REQUIRED = 2, - SIGN_IN_FAILED = 17, - SIGN_IN_REQUIRED = 4, - SUCCESS = 0, - TIMEOUT = 14, - } - - export interface PlayServicesAvailability { - /** - * Returns a numeric status code. Please refer to Android documentation - * for further information: - * https://developers.google.com/android/reference/com/google/android/gms/common/ConnectionResult - * - * ```js - * firebase.utils().playServicesAvailability.status; - * ``` - * - * @android Android only - iOS returns 0 - */ - status: PlayServicesAvailabilityStatusCodes; - - /** - * Returns a boolean indicating whether Play Store is available on the device - * - * ```js - * firebase.utils().playServicesAvailability.isAvailable; - * ``` - * - * @android Android only - iOS returns true - */ - isAvailable: boolean; - - /** - * If Play Services is not available on the device, hasResolution indicates - * whether it is possible to do something about it (e.g. install Play Services). - * - * ```js - * firebase.utils().playServicesAvailability.hasResolution; - * ``` - * @android Android only - iOS returns undefined - */ - hasResolution: boolean | undefined; - - /** - * If an error was received, this indicates whether the error is resolvable - * - * ```js - * firebase.utils().playServicesAvailability.isUserResolvableError; - * ``` - * @android Android only - iOS returns undefined - */ - isUserResolvableError: boolean | undefined; - - /** - * A human readable error string - * - * ```js - * firebase.utils().playServicesAvailability.error; - * ``` - * @android Android only - iOS returns undefined - */ - error: string | undefined; - } - - /** - * The React Native Firebase Utils service interface. - * - * > This module is available for the default app only. - * - * #### Example - * - * Get the Utils service for the default app: - * - * ```js - * const defaultAppUtils = firebase.utils(); - * ``` - */ - export class Module extends FirebaseModule { - /** - * Returns true if this app is running inside a Firebase Test Lab environment. - * - * #### Example - * - * ```js - * const isRunningInTestLab = await firebase.utils().isRunningInTestLab; - * ``` - * @android Android only - iOS returns false - */ - isRunningInTestLab: boolean; - /** - * Returns PlayServicesAvailability properties - * - * #### Example - * - * ```js - * const PlayServicesAvailability = await firebase.utils().playServicesAvailability; - * ``` - * - * @android Android only - iOS always returns { isAvailable: true, status: 0 } - */ - playServicesAvailability: PlayServicesAvailability; - - /** - * Returns PlayServicesAvailability properties - * - * #### Example - * - * ```js - * const PlayServicesAvailability = await firebase.utils().getPlayServicesStatus(); - * ``` - * - * @android Android only - iOS always returns { isAvailable: true, status: 0 } - */ - getPlayServicesStatus(): Promise; - - /** - * A prompt appears on the device to ask the user to update play services - * - * #### Example - * - * ```js - * await firebase.utils().promptForPlayServices(); - * ``` - * - * @android Android only - iOS returns undefined - */ - promptForPlayServices(): Promise; - /** - * Attempts to make Google Play services available on this device - * - * #### Example - * - * ```js - * await firebase.utils().makePlayServicesAvailable(); - * ``` - * - * @android Android only - iOS returns undefined - */ - makePlayServicesAvailable(): Promise; - /** - * Resolves an error by starting any intents requiring user interaction. - * - * #### Example - * - * ```js - * await firebase.utils().resolutionForPlayServices(); - * ``` - * - * @android Android only - iOS returns undefined - */ - resolutionForPlayServices(): Promise; - } -} +// Re-export all types from the types directory +export { ReactNativeFirebase, Utils, Internal } from './types'; /** * Add Utils module as a named export for `app`. @@ -607,20 +41,5 @@ export const utils: ReactNativeFirebase.FirebaseModuleWithStatics any, context?: any): EmitterSubscription { const RNFBAppModule = getReactNativeModule('RNFBAppModule'); if (!this.ready) { RNFBAppModule.eventsNotifyReady(true); @@ -41,7 +43,7 @@ class RNFBNativeEventEmitter extends NativeEventEmitter { // eslint-disable-next-line no-console console.debug(`[RNFB-->Event][👂] ${eventType} -> listening`); } - const listenerDebugger = (...args) => { + const listenerDebugger = (...args: any[]) => { if (globalThis.RNFBDebug) { // eslint-disable-next-line no-console console.debug(`[RNFB<--Event][📣] ${eventType} <-`, JSON.stringify(args[0])); @@ -68,16 +70,16 @@ class RNFBNativeEventEmitter extends NativeEventEmitter { // - addListener returns an unsubscriber instead of a more complex object with eventType etc // make sure eventType for backwards compatibility just in case - subscription.eventType = `rnfb_${eventType}`; + (subscription as any).eventType = `rnfb_${eventType}`; // New style is to return a remove function on the object, just in case people call that, // we will modify it to do our native unsubscription then call the original let originalRemove = subscription.remove; let newRemove = () => { RNFBAppModule.eventsRemoveListener(eventType, false); - if (super.removeSubscription != null) { + if ((super as any).removeSubscription != null) { // This is for RN <= 0.64 - 65 and greater no longer have removeSubscription - super.removeSubscription(subscription); + (super as any).removeSubscription(subscription); } else if (originalRemove != null) { // This is for RN >= 0.65 originalRemove(); @@ -87,20 +89,22 @@ class RNFBNativeEventEmitter extends NativeEventEmitter { return subscription; } - removeAllListeners(eventType) { + removeAllListeners(eventType: string): void { const RNFBAppModule = getReactNativeModule('RNFBAppModule'); RNFBAppModule.eventsRemoveListener(eventType, true); super.removeAllListeners(`rnfb_${eventType}`); } // This is likely no longer ever called, but it is here for backwards compatibility with RN <= 0.64 - removeSubscription(subscription) { + removeSubscription(subscription: EmitterSubscription & { eventType?: string }): void { const RNFBAppModule = getReactNativeModule('RNFBAppModule'); - RNFBAppModule.eventsRemoveListener(subscription.eventType.replace('rnfb_'), false); - if (super.removeSubscription) { - super.removeSubscription(subscription); + const eventType = subscription.eventType?.replace('rnfb_', '') || ''; + RNFBAppModule.eventsRemoveListener(eventType, false); + if ((super as any).removeSubscription) { + (super as any).removeSubscription(subscription); } } } export default new RNFBNativeEventEmitter(); + diff --git a/packages/app/lib/internal/SharedEventEmitter.js b/packages/app/lib/internal/SharedEventEmitter.ts similarity index 100% rename from packages/app/lib/internal/SharedEventEmitter.js rename to packages/app/lib/internal/SharedEventEmitter.ts diff --git a/packages/app/lib/internal/asyncStorage.d.ts b/packages/app/lib/internal/asyncStorage.d.ts deleted file mode 100644 index 5be94ee0fb..0000000000 --- a/packages/app/lib/internal/asyncStorage.d.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2016-present Invertase Limited & Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this library except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - * - */ - -// AsyncStorage interface compatible with React Native AsyncStorage -export interface AsyncStorageStatic { - setItem(key: string, value: string): Promise; - getItem(key: string): Promise; - removeItem(key: string): Promise; -} - -// Memory storage Map instance -export const memoryStorage: Map; - -// Storage key prefix -export const prefix: string; - -// Get the current AsyncStorage instance (either React Native AsyncStorage or memory storage) -export function getReactNativeAsyncStorageInternal(): Promise; - -// Set the AsyncStorage instance to use (React Native AsyncStorage or fallback to memory storage) -export function setReactNativeAsyncStorageInternal(asyncStorageInstance?: AsyncStorageStatic): void; - -// Check if currently using memory storage (fallback) -export function isMemoryStorage(): boolean; - -// Set an item in storage with the React Native Firebase prefix -export function setItem(key: string, value: string): Promise; - -// Get an item from storage with the React Native Firebase prefix -export function getItem(key: string): Promise; - -// Remove an item from storage with the React Native Firebase prefix -export function removeItem(key: string): Promise; diff --git a/packages/app/lib/internal/asyncStorage.js b/packages/app/lib/internal/asyncStorage.js deleted file mode 100644 index 3cd4448531..0000000000 --- a/packages/app/lib/internal/asyncStorage.js +++ /dev/null @@ -1,47 +0,0 @@ -export const memoryStorage = new Map(); - -export const prefix = '@react-native-firebase:'; - -const asyncStorageMemory = { - setItem(key, value) { - memoryStorage.set(key, value); - return Promise.resolve(); - }, - getItem(key) { - const hasValue = memoryStorage.has(key); - if (hasValue) { - return Promise.resolve(memoryStorage.get(key)); - } - return Promise.resolve(null); - }, - removeItem: function (key) { - memoryStorage.delete(key); - return Promise.resolve(); - }, -}; - -let asyncStorage = asyncStorageMemory; - -export async function getReactNativeAsyncStorageInternal() { - return asyncStorage; -} - -export function setReactNativeAsyncStorageInternal(asyncStorageInstance) { - asyncStorage = asyncStorageInstance || asyncStorageMemory; -} - -export function isMemoryStorage() { - return asyncStorage === asyncStorageMemory; -} - -export async function setItem(key, value) { - return await asyncStorage.setItem(prefix + key, value); -} - -export async function getItem(key) { - return await asyncStorage.getItem(prefix + key); -} - -export async function removeItem(key) { - return await asyncStorage.removeItem(prefix + key); -} diff --git a/packages/app/lib/internal/asyncStorage.ts b/packages/app/lib/internal/asyncStorage.ts new file mode 100644 index 0000000000..076074274f --- /dev/null +++ b/packages/app/lib/internal/asyncStorage.ts @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * 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. + * + */ + +// AsyncStorage interface compatible with React Native AsyncStorage +export interface AsyncStorageStatic { + setItem(key: string, value: string): Promise; + getItem(key: string): Promise; + removeItem(key: string): Promise; +} + +// Memory storage Map instance +export const memoryStorage = new Map(); + +// Storage key prefix +export const prefix = '@react-native-firebase:'; + +const asyncStorageMemory: AsyncStorageStatic = { + setItem(key: string, value: string): Promise { + memoryStorage.set(key, value); + return Promise.resolve(); + }, + getItem(key: string): Promise { + const hasValue = memoryStorage.has(key); + if (hasValue) { + return Promise.resolve(memoryStorage.get(key) || null); + } + return Promise.resolve(null); + }, + removeItem(key: string): Promise { + memoryStorage.delete(key); + return Promise.resolve(); + }, +}; + +let asyncStorage: AsyncStorageStatic = asyncStorageMemory; + +// Get the current AsyncStorage instance (either React Native AsyncStorage or memory storage) +export async function getReactNativeAsyncStorageInternal(): Promise { + return asyncStorage; +} + +// Set the AsyncStorage instance to use (React Native AsyncStorage or fallback to memory storage) +export function setReactNativeAsyncStorageInternal( + asyncStorageInstance?: AsyncStorageStatic, +): void { + asyncStorage = asyncStorageInstance || asyncStorageMemory; +} + +// Check if currently using memory storage (fallback) +export function isMemoryStorage(): boolean { + return asyncStorage === asyncStorageMemory; +} + +// Set an item in storage with the React Native Firebase prefix +export async function setItem(key: string, value: string): Promise { + return await asyncStorage.setItem(prefix + key, value); +} + +// Get an item from storage with the React Native Firebase prefix +export async function getItem(key: string): Promise { + return await asyncStorage.getItem(prefix + key); +} + +// Remove an item from storage with the React Native Firebase prefix +export async function removeItem(key: string): Promise { + return await asyncStorage.removeItem(prefix + key); +} diff --git a/packages/app/lib/internal/constants.js b/packages/app/lib/internal/constants.ts similarity index 93% rename from packages/app/lib/internal/constants.js rename to packages/app/lib/internal/constants.ts index 642ef31c92..4741b11345 100644 --- a/packages/app/lib/internal/constants.js +++ b/packages/app/lib/internal/constants.ts @@ -39,4 +39,6 @@ export const KNOWN_NAMESPACES = [ 'notifications', 'perf', 'utils', -]; +] as const; + +export type KnownNamespace = (typeof KNOWN_NAMESPACES)[number]; diff --git a/packages/app/lib/internal/index.js b/packages/app/lib/internal/index.ts similarity index 100% rename from packages/app/lib/internal/index.js rename to packages/app/lib/internal/index.ts diff --git a/packages/app/lib/internal/logger.d.ts b/packages/app/lib/internal/logger.d.ts deleted file mode 100644 index e48f506274..0000000000 --- a/packages/app/lib/internal/logger.d.ts +++ /dev/null @@ -1,85 +0,0 @@ -export type LogLevelString = 'debug' | 'verbose' | 'info' | 'warn' | 'error' | 'silent'; - -export interface LogOptions { - level: LogLevelString; -} - -export type LogCallback = (callbackParams: LogCallbackParams) => void; - -export interface LogCallbackParams { - level: LogLevelString; - message: string; - args: unknown[]; - type: string; -} - -/** - * A container for all of the Logger instances - */ -export const instances: Logger[] = []; - -/** - * The JS SDK supports 5 log levels and also allows a user the ability to - * silence the logs altogether. - * - * The order is a follows: - * DEBUG < VERBOSE < INFO < WARN < ERROR - * - * All of the log types above the current log level will be captured (i.e. if - * you set the log level to `INFO`, errors will still be logged, but `DEBUG` and - * `VERBOSE` logs will not) - */ -export enum LogLevel { - DEBUG, - VERBOSE, - INFO, - WARN, - ERROR, - SILENT, -} - -type LevelStringToEnum = { - debug: LogLevel.DEBUG; - verbose: LogLevel.VERBOSE; - info: LogLevel.INFO; - warn: LogLevel.WARN; - error: LogLevel.ERROR; - silent: LogLevel.SILENT; -}; - -/** - * The default log level - */ -type DefaultLogLevel = LogLevel.INFO; - -/** - * We allow users the ability to pass their own log handler. We will pass the - * type of log, the current log level, and any other arguments passed (i.e. the - * messages that the user wants to log) to this function. - */ -export type LogHandler = (loggerInstance: Logger, logType: LogLevel, ...args: unknown[]) => void; - -export class Logger { - constructor(name: string); - - get logLevel(): LogLevel; - set logLevel(val: LogLevel); - - setLogLevel(val: LogLevel | LogLevelString): void; - - get logHandler(): LogHandler; - set logHandler(val: LogHandler); - - get userLogHandler(): LogHandler | null; - set userLogHandler(val: LogHandler | null); - - debug(...args: unknown[]): void; - log(...args: unknown[]): void; - info(...args: unknown[]): void; - warn(...args: unknown[]): void; - error(...args: unknown[]): void; -} - -export const setLogLevel: (level: LogLevelString | LogLevel) => void; - -export const setUserLogHandler: (logCallback: LogCallback | null, options?: LogOptions) => void; diff --git a/packages/app/lib/internal/logger.js b/packages/app/lib/internal/logger.ts similarity index 60% rename from packages/app/lib/internal/logger.js rename to packages/app/lib/internal/logger.ts index b1b84ace55..73d4ccd84f 100644 --- a/packages/app/lib/internal/logger.js +++ b/packages/app/lib/internal/logger.ts @@ -15,37 +15,59 @@ * */ +export type LogLevelString = 'debug' | 'verbose' | 'info' | 'warn' | 'error' | 'silent'; + +export interface LogOptions { + level?: LogLevelString; +} + +export type LogCallback = (callbackParams: LogCallbackParams) => void; + +export interface LogCallbackParams { + level: LogLevelString; + message: string; + args: unknown[]; + type: string; +} + /** - * @typedef {import('./logger').Logger} Logger - * @typedef {import('./logger').setLogLevel} setLogLevel - * @typedef {import('./logger').setUserLogHandler} setUserLogHandler - * @typedef {import('./logger').LogHandler} LogHandler - * @typedef {import('./logger').LogLevel} LogLevel - * @typedef {import('./logger').LevelStringToEnum} LevelStringToEnum - * @typedef {import('./logger').DefaultLogLevel} DefaultLogLevel + * The JS SDK supports 5 log levels and also allows a user the ability to + * silence the logs altogether. + * + * The order is a follows: + * DEBUG < VERBOSE < INFO < WARN < ERROR + * + * All of the log types above the current log level will be captured (i.e. if + * you set the log level to `INFO`, errors will still be logged, but `DEBUG` and + * `VERBOSE` logs will not) */ - -const LogLevel = { - DEBUG: 0, - VERBOSE: 1, - INFO: 2, - WARN: 3, - ERROR: 4, - SILENT: 5, -}; +export enum LogLevel { + DEBUG = 0, + VERBOSE = 1, + INFO = 2, + WARN = 3, + ERROR = 4, + SILENT = 5, +} // mimic the LogLevel in firebase-js-sdk TS -const reverseLogLevel = obj => { - const reversed = {}; +const reverseLogLevel = (obj: typeof LogLevel): Record => { + const reversed: Record = {}; for (const [key, value] of Object.entries(obj)) { - reversed[value] = key; + if (typeof value === 'number') { + reversed[value] = key; + } } return reversed; }; const LogLevelReversed = reverseLogLevel(LogLevel); -const levelStringToEnum = { +type LevelStringToEnum = { + [K in LogLevelString]: LogLevel; +}; + +const levelStringToEnum: LevelStringToEnum = { debug: LogLevel.DEBUG, verbose: LogLevel.VERBOSE, info: LogLevel.INFO, @@ -60,21 +82,28 @@ const levelStringToEnum = { * (i.e. once for firebase, and once in the console), we are sending `DEBUG` * logs to the `console.log` function. */ -const ConsoleMethod = { +const ConsoleMethod: Record = { [LogLevel.DEBUG]: 'log', [LogLevel.VERBOSE]: 'log', [LogLevel.INFO]: 'info', [LogLevel.WARN]: 'warn', [LogLevel.ERROR]: 'error', + [LogLevel.SILENT]: 'error', // fallback, should never be used }; +/** + * We allow users the ability to pass their own log handler. We will pass the + * type of log, the current log level, and any other arguments passed (i.e. the + * messages that the user wants to log) to this function. + */ +export type LogHandler = (loggerInstance: Logger, logType: LogLevel, ...args: unknown[]) => void; + /** * The default log handler will forward DEBUG, VERBOSE, INFO, WARN, and ERROR * messages on to their corresponding console counterparts (if the log method * is supported by the current log level) - * @type {LogHandler} */ -const defaultLogHandler = (instance, logType, ...args) => { +const defaultLogHandler: LogHandler = (instance, logType, ...args) => { if (logType < instance.logLevel) { return; } @@ -82,8 +111,7 @@ const defaultLogHandler = (instance, logType, ...args) => { const method = ConsoleMethod[logType]; if (method) { // 'log' | 'info' | 'warn' | 'error' - // eslint-disable-next-line no-console - console[method](`[${now}] ${instance.name}:`, ...args); + (console as any)[method](`[${now}] ${instance.name}:`, ...args); } else { throw new Error(`Attempted to log a message with an invalid logType (value: ${logType})`); } @@ -91,20 +119,27 @@ const defaultLogHandler = (instance, logType, ...args) => { const defaultLogLevel = LogLevel.INFO; -export const instances = []; - /** - * @type {Logger} + * A container for all of the Logger instances */ +export const instances: Logger[] = []; +/** + * Logger class for Firebase + */ export class Logger { + name: string; + private _logLevel: LogLevel = defaultLogLevel; + private _logHandler: LogHandler = defaultLogHandler; + private _userLogHandler: LogHandler | null = null; + /** * Gives you an instance of a Logger to capture messages according to * Firebase's logging scheme. * * @param name The name that the logs will be associated with */ - constructor(name) { + constructor(name: string) { /** * Capture the current instance for later use */ @@ -115,13 +150,11 @@ export class Logger { /** * The log level of the given Logger instance. */ - _logLevel = defaultLogLevel; - - get logLevel() { + get logLevel(): LogLevel { return this._logLevel; } - set logLevel(val) { + set logLevel(val: LogLevel) { if (!(val in LogLevel)) { throw new TypeError(`Invalid value "${val}" assigned to \`logLevel\``); } @@ -129,7 +162,7 @@ export class Logger { } // Workaround for setter/getter having to be the same type. - setLogLevel(val) { + setLogLevel(val: LogLevel | LogLevelString): void { this._logLevel = typeof val === 'string' ? levelStringToEnum[val] : val; } @@ -137,11 +170,11 @@ export class Logger { * The main (internal) log handler for the Logger instance. * Can be set to a new function in internal package code but not by user. */ - _logHandler = defaultLogHandler; - get logHandler() { + get logHandler(): LogHandler { return this._logHandler; } - set logHandler(val) { + + set logHandler(val: LogHandler) { if (typeof val !== 'function') { throw new TypeError('Value assigned to `logHandler` must be a function'); } @@ -151,11 +184,11 @@ export class Logger { /** * The optional, additional, user-defined log handler for the Logger instance. */ - _userLogHandler = null; - get userLogHandler() { + get userLogHandler(): LogHandler | null { return this._userLogHandler; } - set userLogHandler(val) { + + set userLogHandler(val: LogHandler | null) { this._userLogHandler = val; } @@ -163,50 +196,57 @@ export class Logger { * The functions below are all based on the `console` interface */ - debug(...args) { + debug(...args: unknown[]): void { this._userLogHandler && this._userLogHandler(this, LogLevel.DEBUG, ...args); this._logHandler(this, LogLevel.DEBUG, ...args); } - log(...args) { + + log(...args: unknown[]): void { this._userLogHandler && this._userLogHandler(this, LogLevel.VERBOSE, ...args); this._logHandler(this, LogLevel.VERBOSE, ...args); } - info(...args) { + + info(...args: unknown[]): void { this._userLogHandler && this._userLogHandler(this, LogLevel.INFO, ...args); this._logHandler(this, LogLevel.INFO, ...args); } - warn(...args) { + + warn(...args: unknown[]): void { this._userLogHandler && this._userLogHandler(this, LogLevel.WARN, ...args); this._logHandler(this, LogLevel.WARN, ...args); } - error(...args) { + + error(...args: unknown[]): void { this._userLogHandler && this._userLogHandler(this, LogLevel.ERROR, ...args); this._logHandler(this, LogLevel.ERROR, ...args); } } /** - * @type {setLogLevel} + * Sets the log level for all Logger instances */ -export function setLogLevelInternal(level) { +export function setLogLevel(level: LogLevelString | LogLevel): void { instances.forEach(inst => { inst.setLogLevel(level); }); } +// Alias for compatibility +export const setLogLevelInternal = setLogLevel; + /** - * @type {setUserLogHandler} + * Sets a custom user log handler for all Logger instances */ -export function setUserLogHandler(logCallback, options) { +export function setUserLogHandler(logCallback: LogCallback | null, options?: LogOptions): void { for (const instance of instances) { - let customLogLevel = null; - if (options && options.level) { + let customLogLevel: LogLevel | null = null; + if (options?.level) { customLogLevel = levelStringToEnum[options.level]; } if (logCallback === null) { instance.userLogHandler = null; } else { - instance.userLogHandler = (instance, level, ...args) => { + instance.userLogHandler = (_instance, level, ...args) => { const message = args .map(arg => { if (arg == null) { @@ -227,12 +267,12 @@ export function setUserLogHandler(logCallback, options) { }) .filter(arg => arg) .join(' '); - if (level >= (customLogLevel ?? instance.logLevel)) { + if (level >= (customLogLevel ?? _instance.logLevel)) { logCallback({ - level: LogLevelReversed[level].toLowerCase(), + level: LogLevelReversed[level]!.toLowerCase() as LogLevelString, message, args, - type: instance.name, + type: _instance.name, }); } }; diff --git a/packages/app/lib/internal/nativeModule.android.js b/packages/app/lib/internal/nativeModule.android.ts similarity index 99% rename from packages/app/lib/internal/nativeModule.android.js rename to packages/app/lib/internal/nativeModule.android.ts index 95f31361ba..a6f72d6a97 100644 --- a/packages/app/lib/internal/nativeModule.android.js +++ b/packages/app/lib/internal/nativeModule.android.ts @@ -1,2 +1,3 @@ export { getReactNativeModule } from './nativeModuleAndroidIos'; export { setReactNativeModule } from './nativeModuleAndroidIos'; + diff --git a/packages/app/lib/internal/nativeModule.ios.js b/packages/app/lib/internal/nativeModule.ios.ts similarity index 99% rename from packages/app/lib/internal/nativeModule.ios.js rename to packages/app/lib/internal/nativeModule.ios.ts index 95f31361ba..a6f72d6a97 100644 --- a/packages/app/lib/internal/nativeModule.ios.js +++ b/packages/app/lib/internal/nativeModule.ios.ts @@ -1,2 +1,3 @@ export { getReactNativeModule } from './nativeModuleAndroidIos'; export { setReactNativeModule } from './nativeModuleAndroidIos'; + diff --git a/packages/app/lib/internal/nativeModule.js b/packages/app/lib/internal/nativeModule.ts similarity index 99% rename from packages/app/lib/internal/nativeModule.js rename to packages/app/lib/internal/nativeModule.ts index e1a9e708b6..e4e77b5c8a 100644 --- a/packages/app/lib/internal/nativeModule.js +++ b/packages/app/lib/internal/nativeModule.ts @@ -2,3 +2,4 @@ // In this case we use the web Firebase JS SDK. export { getReactNativeModule } from './nativeModuleWeb'; export { setReactNativeModule } from './nativeModuleWeb'; + diff --git a/packages/app/lib/internal/nativeModuleAndroidIos.js b/packages/app/lib/internal/nativeModuleAndroidIos.ts similarity index 57% rename from packages/app/lib/internal/nativeModuleAndroidIos.js rename to packages/app/lib/internal/nativeModuleAndroidIos.ts index 31a6a2e281..0f779ed135 100644 --- a/packages/app/lib/internal/nativeModuleAndroidIos.js +++ b/packages/app/lib/internal/nativeModuleAndroidIos.ts @@ -7,7 +7,7 @@ import { NativeModules } from 'react-native'; * and log them to the console for debugging purposes, if enabled. * @param moduleName */ -export function getReactNativeModule(moduleName) { +export function getReactNativeModule(moduleName: string): any { const nativeModule = NativeModules[moduleName]; if (!globalThis.RNFBDebug) { return nativeModule; @@ -17,29 +17,30 @@ export function getReactNativeModule(moduleName) { return Object.keys(target); }, get: (_, name) => { - if (typeof nativeModule[name] !== 'function') return nativeModule[name]; - return (...args) => { - console.debug(`[RNFB->Native][🔵] ${moduleName}.${name} -> ${JSON.stringify(args)}`); - const result = nativeModule[name](...args); + if (typeof nativeModule[name as string] !== 'function') return nativeModule[name as string]; + return (...args: any[]) => { + console.debug(`[RNFB->Native][🔵] ${moduleName}.${String(name)} -> ${JSON.stringify(args)}`); + const result = nativeModule[name as string](...args); if (result && result.then) { return result.then( - res => { - console.debug(`[RNFB<-Native][🟢] ${moduleName}.${name} <- ${JSON.stringify(res)}`); + (res: any) => { + console.debug(`[RNFB<-Native][🟢] ${moduleName}.${String(name)} <- ${JSON.stringify(res)}`); return res; }, - err => { - console.debug(`[RNFB<-Native][🔴] ${moduleName}.${name} <- ${JSON.stringify(err)}`); + (err: any) => { + console.debug(`[RNFB<-Native][🔴] ${moduleName}.${String(name)} <- ${JSON.stringify(err)}`); throw err; }, ); } - console.debug(`[RNFB<-Native][🟢] ${moduleName}.${name} <- ${JSON.stringify(result)}`); + console.debug(`[RNFB<-Native][🟢] ${moduleName}.${String(name)} <- ${JSON.stringify(result)}`); return result; }; }, }); } -export function setReactNativeModule() { +export function setReactNativeModule(): void { // No-op } + diff --git a/packages/app/lib/internal/nativeModuleWeb.js b/packages/app/lib/internal/nativeModuleWeb.ts similarity index 56% rename from packages/app/lib/internal/nativeModuleWeb.js rename to packages/app/lib/internal/nativeModuleWeb.ts index e39f939575..266fa762f5 100644 --- a/packages/app/lib/internal/nativeModuleWeb.js +++ b/packages/app/lib/internal/nativeModuleWeb.ts @@ -1,10 +1,11 @@ /* eslint-disable no-console */ +// @ts-expect-error import RNFBAppModule from './web/RNFBAppModule'; -const nativeModuleRegistry = {}; +const nativeModuleRegistry: Record = {}; -export function getReactNativeModule(moduleName) { +export function getReactNativeModule(moduleName: string): any { const nativeModule = nativeModuleRegistry[moduleName]; // Throw an error if the module is not registered. if (!nativeModule) { @@ -19,31 +20,32 @@ export function getReactNativeModule(moduleName) { return Object.keys(target); }, get: (_, name) => { - if (typeof nativeModule[name] !== 'function') return nativeModule[name]; - return (...args) => { - console.debug(`[RNFB->Native][🔵] ${moduleName}.${name} -> ${JSON.stringify(args)}`); - const result = nativeModule[name](...args); + if (typeof nativeModule[name as string] !== 'function') return nativeModule[name as string]; + return (...args: any[]) => { + console.debug(`[RNFB->Native][🔵] ${moduleName}.${String(name)} -> ${JSON.stringify(args)}`); + const result = nativeModule[name as string](...args); if (result && result.then) { return result.then( - res => { - console.debug(`[RNFB<-Native][🟢] ${moduleName}.${name} <- ${JSON.stringify(res)}`); + (res: any) => { + console.debug(`[RNFB<-Native][🟢] ${moduleName}.${String(name)} <- ${JSON.stringify(res)}`); return res; }, - err => { - console.debug(`[RNFB<-Native][🔴] ${moduleName}.${name} <- ${JSON.stringify(err)}`); + (err: any) => { + console.debug(`[RNFB<-Native][🔴] ${moduleName}.${String(name)} <- ${JSON.stringify(err)}`); throw err; }, ); } - console.debug(`[RNFB<-Native][🟢] ${moduleName}.${name} <- ${JSON.stringify(result)}`); + console.debug(`[RNFB<-Native][🟢] ${moduleName}.${String(name)} <- ${JSON.stringify(result)}`); return result; }; }, }); } -export function setReactNativeModule(moduleName, nativeModule) { +export function setReactNativeModule(moduleName: string, nativeModule: any): void { nativeModuleRegistry[moduleName] = nativeModule; } setReactNativeModule('RNFBAppModule', RNFBAppModule); + diff --git a/packages/app/lib/internal/registry/app.js b/packages/app/lib/internal/registry/app.ts similarity index 84% rename from packages/app/lib/internal/registry/app.js rename to packages/app/lib/internal/registry/app.ts index fda5741665..f020cc7b9f 100644 --- a/packages/app/lib/internal/registry/app.js +++ b/packages/app/lib/internal/registry/app.ts @@ -30,17 +30,18 @@ import { DEFAULT_APP_NAME } from '../constants'; import { setReactNativeAsyncStorageInternal } from '../asyncStorage'; import { getAppModule } from './nativeModule'; import { setLogLevelInternal } from '../logger'; +import { FirebaseAppConfig, FirebaseAppOptions, ReactNativeAsyncStorage } from '../../types'; -const APP_REGISTRY = {}; -let onAppCreateFn = null; -let onAppDestroyFn = null; +const APP_REGISTRY: Record = {}; +let onAppCreateFn: ((app: FirebaseApp) => void) | null = null; +let onAppDestroyFn: ((app: FirebaseApp) => void) | null = null; let initializedNativeApps = false; /** * This was needed to avoid metro require cycles... * @param fn */ -export function setOnAppCreate(fn) { +export function setOnAppCreate(fn: (app: FirebaseApp) => void): void { onAppCreateFn = fn; } @@ -48,7 +49,7 @@ export function setOnAppCreate(fn) { * This was needed to avoid metro require cycles... * @param fn */ -export function setOnAppDestroy(fn) { +export function setOnAppDestroy(fn: (app: FirebaseApp) => void): void { onAppDestroyFn = fn; } @@ -56,7 +57,7 @@ export function setOnAppDestroy(fn) { * Initializes all apps that were created natively (android/ios), * e.g. the Default firebase app from plist/json google services file. */ -export function initializeNativeApps() { +export function initializeNativeApps(): void { const nativeModule = getAppModule(); const { NATIVE_FIREBASE_APPS } = nativeModule; @@ -70,7 +71,7 @@ export function initializeNativeApps() { true, deleteApp.bind(null, name, true), ); - onAppCreateFn(APP_REGISTRY[name]); + onAppCreateFn?.(APP_REGISTRY[name]); } } @@ -85,7 +86,7 @@ export function initializeNativeApps() { * * @param name */ -export function getApp(name = DEFAULT_APP_NAME) { +export function getApp(name: string = DEFAULT_APP_NAME, _deprecationArg?: any): FirebaseApp { warnIfNotModularCall(arguments, 'getApp()'); if (!initializedNativeApps) { initializeNativeApps(); @@ -102,7 +103,7 @@ export function getApp(name = DEFAULT_APP_NAME) { /** * Gets all app instances, used for `firebase.apps` */ -export function getApps() { +export function getApps(_deprecationArg?: any): FirebaseApp[] { warnIfNotModularCall(arguments, 'getApps()'); if (!initializedNativeApps) { initializeNativeApps(); @@ -115,13 +116,17 @@ export function getApps() { * @param options * @param configOrName */ -export function initializeApp(options = {}, configOrName) { +export function initializeApp( + options: FirebaseAppOptions = {}, + configOrName?: string | FirebaseAppConfig, + _deprecationArg?: any, +): Promise { warnIfNotModularCall(arguments, 'initializeApp()'); - let appConfig = configOrName; + let appConfig: FirebaseAppConfig = configOrName as FirebaseAppConfig; if (!isObject(configOrName) || isNull(configOrName)) { appConfig = { - name: configOrName, + name: configOrName as string, automaticResourceManagement: false, automaticDataCollectionEnabled: true, }; @@ -184,7 +189,7 @@ export function initializeApp(options = {}, configOrName) { // Note these initialization actions with side effects are performed prior to knowledge of // successful initialization in the native code. Native code *may* throw an error. APP_REGISTRY[name] = app; - onAppCreateFn(APP_REGISTRY[name]); + onAppCreateFn?.(APP_REGISTRY[name]); return getAppModule() .initializeApp(options, appConfig) @@ -204,7 +209,7 @@ export function initializeApp(options = {}, configOrName) { }); } -export function setLogLevel(logLevel) { +export function setLogLevel(logLevel: string, _deprecationArg?: any): void { warnIfNotModularCall(arguments, 'setLogLevel()'); if (!['error', 'warn', 'info', 'debug', 'verbose'].includes(logLevel)) { throw new Error('LogLevel must be one of "error", "warn", "info", "debug", "verbose"'); @@ -217,7 +222,7 @@ export function setLogLevel(logLevel) { } } -export function setReactNativeAsyncStorage(asyncStorage) { +export function setReactNativeAsyncStorage(asyncStorage: ReactNativeAsyncStorage, _deprecationArg?: any): void { warnIfNotModularCall(arguments, 'setReactNativeAsyncStorage()'); if (!isObject(asyncStorage)) { @@ -242,7 +247,7 @@ export function setReactNativeAsyncStorage(asyncStorage) { /** * */ -export function deleteApp(name, nativeInitialized) { +export function deleteApp(name: string, nativeInitialized: boolean, _deprecationArg?: any): Promise { if (name === DEFAULT_APP_NAME && nativeInitialized) { return Promise.reject(new Error('Unable to delete the default native firebase app instance.')); } @@ -257,7 +262,8 @@ export function deleteApp(name, nativeInitialized) { return nativeModule.deleteApp(name).then(() => { app._deleted = true; - onAppDestroyFn(app); + onAppDestroyFn?.(app); delete APP_REGISTRY[name]; }); } + diff --git a/packages/app/lib/internal/registry/namespace.js b/packages/app/lib/internal/registry/namespace.ts similarity index 82% rename from packages/app/lib/internal/registry/namespace.js rename to packages/app/lib/internal/registry/namespace.ts index 76e3078666..2dd90461fa 100644 --- a/packages/app/lib/internal/registry/namespace.js +++ b/packages/app/lib/internal/registry/namespace.ts @@ -17,8 +17,8 @@ import { isString, createDeprecationProxy } from '../../common'; import FirebaseApp from '../../FirebaseApp'; -import SDK_VERSION from '../../version'; -import { DEFAULT_APP_NAME, KNOWN_NAMESPACES } from '../constants'; +import { version as SDK_VERSION } from '../../version'; +import { DEFAULT_APP_NAME, KNOWN_NAMESPACES, type KnownNamespace } from '../constants'; import FirebaseModule from '../FirebaseModule'; import { getApp, @@ -30,13 +30,20 @@ import { setOnAppDestroy, } from './app'; +interface NamespaceConfig { + hasCustomUrlOrRegionSupport?: boolean; + hasMultiAppSupport?: boolean; + ModuleClass: typeof FirebaseModule; + statics?: Record; +} + // firebase.X -let FIREBASE_ROOT = null; +let FIREBASE_ROOT: any = null; -const NAMESPACE_REGISTRY = {}; -const APP_MODULE_INSTANCE = {}; -const MODULE_GETTER_FOR_APP = {}; -const MODULE_GETTER_FOR_ROOT = {}; +const NAMESPACE_REGISTRY: Record = {}; +const APP_MODULE_INSTANCE: Record> = {}; +const MODULE_GETTER_FOR_APP: Record> = {}; +const MODULE_GETTER_FOR_ROOT: Record = {}; /** * Attaches module namespace getters on every newly created app. @@ -70,7 +77,7 @@ setOnAppDestroy(app => { * @param moduleNamespace * @returns {*} */ -function getOrCreateModuleForApp(app, moduleNamespace) { +function getOrCreateModuleForApp(app: FirebaseApp, moduleNamespace: KnownNamespace): any { if (MODULE_GETTER_FOR_APP[app.name] && MODULE_GETTER_FOR_APP[app.name][moduleNamespace]) { return MODULE_GETTER_FOR_APP[app.name][moduleNamespace]; } @@ -94,7 +101,7 @@ function getOrCreateModuleForApp(app, moduleNamespace) { } // e.g. firebase.storage(customUrlOrRegion), firebase.functions(customUrlOrRegion), firebase.firestore(databaseId), firebase.database(url) - function firebaseModuleWithArgs(customUrlOrRegionOrDatabaseId) { + function firebaseModuleWithArgs(customUrlOrRegionOrDatabaseId?: string): any { if (customUrlOrRegionOrDatabaseId !== undefined) { if (!hasCustomUrlOrRegionSupport) { // TODO throw Module does not support arguments error @@ -133,7 +140,7 @@ function getOrCreateModuleForApp(app, moduleNamespace) { * @param moduleNamespace * @returns {*} */ -function getOrCreateModuleForRoot(moduleNamespace) { +function getOrCreateModuleForRoot(moduleNamespace: KnownNamespace): any { if (MODULE_GETTER_FOR_ROOT[moduleNamespace]) { return MODULE_GETTER_FOR_ROOT[moduleNamespace]; } @@ -141,7 +148,7 @@ function getOrCreateModuleForRoot(moduleNamespace) { const { statics, hasMultiAppSupport, ModuleClass } = NAMESPACE_REGISTRY[moduleNamespace]; // e.g. firebase.storage(app) - function firebaseModuleWithApp(app) { + function firebaseModuleWithApp(app?: FirebaseApp): any { const _app = app || getApp(); if (!(_app instanceof FirebaseApp)) { @@ -193,12 +200,12 @@ function getOrCreateModuleForRoot(moduleNamespace) { * @param moduleNamespace * @returns {*} */ -function firebaseRootModuleProxy(_firebaseNamespace, moduleNamespace) { +function firebaseRootModuleProxy(_firebaseNamespace: any, moduleNamespace: string): any { if (NAMESPACE_REGISTRY[moduleNamespace]) { - return getOrCreateModuleForRoot(moduleNamespace); + return getOrCreateModuleForRoot(moduleNamespace as KnownNamespace); } - moduleWithDashes = moduleNamespace + const moduleWithDashes = moduleNamespace .split(/(?=[A-Z])/) .join('-') .toLowerCase(); @@ -218,13 +225,13 @@ function firebaseRootModuleProxy(_firebaseNamespace, moduleNamespace) { * @param moduleNamespace * @returns {*} */ -export function firebaseAppModuleProxy(app, moduleNamespace) { +export function firebaseAppModuleProxy(app: FirebaseApp, moduleNamespace: string): any { if (NAMESPACE_REGISTRY[moduleNamespace]) { app._checkDestroyed(); - return getOrCreateModuleForApp(app, moduleNamespace); + return getOrCreateModuleForApp(app, moduleNamespace as KnownNamespace); } - moduleWithDashes = moduleNamespace + const moduleWithDashes = moduleNamespace .split(/(?=[A-Z])/) .join('-') .toLowerCase(); @@ -242,7 +249,7 @@ export function firebaseAppModuleProxy(app, moduleNamespace) { * * @returns {*} */ -export function createFirebaseRoot() { +export function createFirebaseRoot(): any { FIREBASE_ROOT = { initializeApp, setReactNativeAsyncStorage, @@ -271,7 +278,7 @@ export function createFirebaseRoot() { * * @returns {*} */ -export function getFirebaseRoot() { +export function getFirebaseRoot(): any { if (FIREBASE_ROOT) { return FIREBASE_ROOT; } @@ -283,12 +290,12 @@ export function getFirebaseRoot() { * @param options * @returns {*} */ -export function createModuleNamespace(options = {}) { +export function createModuleNamespace(options: any = {}): any { const { namespace, ModuleClass } = options; if (!NAMESPACE_REGISTRY[namespace]) { // validation only for internal / module dev usage - if (FirebaseModule.__extended__ !== ModuleClass.__extended__) { + if ((FirebaseModule as any).__extended__ !== (ModuleClass as any).__extended__) { throw new Error('INTERNAL ERROR: ModuleClass must be an instance of FirebaseModule.'); } diff --git a/packages/app/lib/internal/registry/nativeModule.js b/packages/app/lib/internal/registry/nativeModule.ts similarity index 84% rename from packages/app/lib/internal/registry/nativeModule.js rename to packages/app/lib/internal/registry/nativeModule.ts index 2778b2dd75..ae16780564 100644 --- a/packages/app/lib/internal/registry/nativeModule.js +++ b/packages/app/lib/internal/registry/nativeModule.ts @@ -21,11 +21,18 @@ import RNFBNativeEventEmitter from '../RNFBNativeEventEmitter'; import SharedEventEmitter from '../SharedEventEmitter'; import { getReactNativeModule } from '../nativeModule'; import { isAndroid, isIOS } from '../../common'; +import FirebaseModule from '../FirebaseModule'; -const NATIVE_MODULE_REGISTRY = {}; -const NATIVE_MODULE_EVENT_SUBSCRIPTIONS = {}; +interface NativeEvent { + appName?: string; + databaseId?: string; + [key: string]: any; +} + +const NATIVE_MODULE_REGISTRY: Record = {}; +const NATIVE_MODULE_EVENT_SUBSCRIPTIONS: Record = {}; -function nativeModuleKey(module) { +function nativeModuleKey(module: FirebaseModule): string { return `${module._customUrlOrRegion || ''}:${module.app.name}:${module._config.namespace}`; } @@ -38,14 +45,18 @@ function nativeModuleKey(module) { * @param argToPrepend * @returns {Function} */ -function nativeModuleMethodWrapped(namespace, method, argToPrepend) { - return (...args) => { +function nativeModuleMethodWrapped( + namespace: string, + method: (...args: any[]) => any, + argToPrepend: any[], +): (...args: any[]) => any { + return (...args: any[]) => { const possiblePromise = method(...[...argToPrepend, ...args]); if (possiblePromise && possiblePromise.then) { const jsStack = new Error().stack; - return possiblePromise.catch(nativeError => - Promise.reject(new NativeFirebaseError(nativeError, jsStack, namespace)), + return possiblePromise.catch((nativeError: any) => + Promise.reject(new NativeFirebaseError(nativeError, jsStack as string, namespace)), ); } @@ -60,8 +71,8 @@ function nativeModuleMethodWrapped(namespace, method, argToPrepend) { * @param NativeModule * @param argToPrepend */ -function nativeModuleWrapped(namespace, NativeModule, argToPrepend) { - const native = {}; +function nativeModuleWrapped(namespace: string, NativeModule: any, argToPrepend: any[]): any { + const native: Record = {}; if (!NativeModule) { return NativeModule; } @@ -87,7 +98,7 @@ function nativeModuleWrapped(namespace, NativeModule, argToPrepend) { * @param module * @returns {*} */ -function initialiseNativeModule(module) { +function initialiseNativeModule(module: FirebaseModule): any { const config = module._config; const key = nativeModuleKey(module); const { @@ -98,7 +109,7 @@ function initialiseNativeModule(module) { hasCustomUrlOrRegionSupport, disablePrependCustomUrlOrRegion, } = config; - const multiModuleRoot = {}; + const multiModuleRoot: Record = {}; const multiModule = Array.isArray(nativeModuleName); const nativeModuleNames = multiModule ? nativeModuleName : [nativeModuleName]; @@ -115,7 +126,7 @@ function initialiseNativeModule(module) { multiModuleRoot[nativeModuleNames[i]] = !!nativeModule; } - const argToPrepend = []; + const argToPrepend: any[] = []; if (hasMultiAppSupport) { argToPrepend.push(module.app.name); @@ -150,9 +161,9 @@ function initialiseNativeModule(module) { * @param eventName * @private */ -function subscribeToNativeModuleEvent(eventName) { +function subscribeToNativeModuleEvent(eventName: string): void { if (!NATIVE_MODULE_EVENT_SUBSCRIPTIONS[eventName]) { - RNFBNativeEventEmitter.addListener(eventName, event => { + RNFBNativeEventEmitter.addListener(eventName, (event: NativeEvent) => { if (event.appName && event.databaseId) { // Firestore requires both appName and databaseId to prefix SharedEventEmitter.emit(`${event.appName}-${event.databaseId}-${eventName}`, event); @@ -175,7 +186,7 @@ function subscribeToNativeModuleEvent(eventName) { * @param namespace * @returns {string} */ -function getMissingModuleHelpText(namespace) { +function getMissingModuleHelpText(namespace: string): string { const snippet = `firebase.${namespace}()`; if (isIOS || isAndroid) { @@ -200,7 +211,7 @@ function getMissingModuleHelpText(namespace) { * @param module * @returns {*} */ -export function getNativeModule(module) { +export function getNativeModule(module: FirebaseModule): any { const key = nativeModuleKey(module); if (NATIVE_MODULE_REGISTRY[key]) { @@ -215,7 +226,7 @@ export function getNativeModule(module) { * * @returns {*} */ -export function getAppModule() { +export function getAppModule(): any { if (NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE]) { return NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE]; } diff --git a/packages/app/lib/modular/index.d.ts b/packages/app/lib/modular/index.d.ts deleted file mode 100644 index 49a8337d82..0000000000 --- a/packages/app/lib/modular/index.d.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { ReactNativeFirebase } from '..'; - -import FirebaseApp = ReactNativeFirebase.FirebaseApp; -import FirebaseAppOptions = ReactNativeFirebase.FirebaseAppOptions; -import LogLevelString = ReactNativeFirebase.LogLevelString; -import FirebaseAppConfig = ReactNativeFirebase.FirebaseAppConfig; - -/** - * Renders this app unusable and frees the resources of all associated services. - * @param app - FirebaseApp - The app to delete. - * @returns Promise - */ -export function deleteApp(app: FirebaseApp): Promise; - -/** - * Registers a library's name and version for platform logging purposes. - * @param libraryKeyOrName - Library name or key. - * @param version - Library version. - * @param variant - Library variant. Optional. - * @returns Promise - */ -export function registerVersion( - libraryKeyOrName: string, - version: string, - variant?: string, -): Promise; - -/** - * Sets log handler for all Firebase SDKs. Currently only supported on VertexAI. - * @param logCallback - The callback function to handle logs. - * @param options - Optional settings for log handling. - * @returns - */ - -interface LogCallbackParams { - level: LogLevelString; - message: string; - args: unknown[]; - type: string; -} - -export function onLog( - logCallback: (callbackParams: LogCallbackParams) => void, - options?: any, -): void; - -/** - * Gets the list of all initialized apps. - * @returns FirebaseApp[] - An array of all initialized Firebase apps. - */ -export function getApps(): FirebaseApp[]; - -/** - * Initializes a Firebase app with the provided options and name. - * @param options - Options to configure the services used in the app. - * @param name - The optional name of the app to initialize ('[DEFAULT]' if omitted). - * @returns Promise - The initialized Firebase app. - */ -export function initializeApp(options: FirebaseAppOptions, name?: string): Promise; - -/** - * Initializes a Firebase app with the provided options and config. - * @param options - Options to configure the services used in the app. - * @param config - The optional config for your firebase app. - * @returns Promise - The initialized Firebase app. - */ -export function initializeApp( - options: FirebaseAppOptions, - config?: FirebaseAppConfig, -): Promise; -/** - * Retrieves an instance of a Firebase app. - * @param name - The optional name of the app to return ('[DEFAULT]' if omitted). - * @returns FirebaseApp - The requested Firebase app instance. - */ -export function getApp(name?: string): FirebaseApp; - -/** - * Sets the log level across all Firebase SDKs. - * @param logLevel - The log level to set ('debug', 'verbose', 'info', 'warn', 'error', 'silent'). - * @returns void - */ -export function setLogLevel(logLevel: LogLevelString): void; - -/** - * Gets react-native-firebase specific "meta" data from native Info.plist / AndroidManifest.xml - * @returns map of key / value pairs containing native meta data - */ -export function metaGetAll(): Promise<{ [keyof: string]: string | boolean }>; - -/** - * Gets react-native-firebase specific "firebase.json" data - * @returns map of key / value pairs containing native firebase.json constants - */ -export function jsonGetAll(): Promise<{ [keyof: string]: string | boolean }>; - -/** - * Clears react-native-firebase specific native preferences - * @returns Promise - */ -export function preferencesClearAll(): Promise; - -/** - * Gets react-native-firebase specific native preferences - * @returns map of key / value pairs containing native preferences data - */ -export function preferencesGetAll(): Promise<{ [keyof: string]: string | boolean }>; - -/** - * Sets react-native-firebase specific native boolean preference - * @param key the name of the native preference to set - * @param value the value of the native preference to set - * @returns Promise - */ -export function preferencesSetBool(key: string, value: boolean): Promise; - -/** - * Sets react-native-firebase specific native string preference - * @param key the name of the native preference to set - * @param value the value of the native preference to set - * @returns Promise - */ -export function preferencesSetString(key: string, value: string): Promise; - -/** - * The `AsyncStorage` implementation to use for persisting data on 'Other' platforms. - * If not specified, in memory persistence is used. - * - * This is required if you want to persist things like Auth sessions, Analytics device IDs, etc. - */ -export function setReactNativeAsyncStorage(asyncStorage: ReactNativeAsyncStorage): void; diff --git a/packages/app/lib/modular/index.js b/packages/app/lib/modular/index.js deleted file mode 100644 index bc485ca7f3..0000000000 --- a/packages/app/lib/modular/index.js +++ /dev/null @@ -1,150 +0,0 @@ -import { MODULAR_DEPRECATION_ARG } from '@react-native-firebase/app/lib/common'; -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { - deleteApp as deleteAppCompat, - getApp as getAppCompat, - getApps as getAppsCompat, - initializeApp as initializeAppCompat, - setLogLevel as setLogLevelCompat, - setReactNativeAsyncStorage as setReactNativeAsyncStorageCompat, -} from '../internal'; -import { setUserLogHandler } from '../internal/logger'; -import sdkVersion from '../version'; - -/** - * @typedef {import('..').ReactNativeFirebase.FirebaseApp} FirebaseApp - * @typedef {import('..').ReactNativeFirebase.FirebaseAppOptions} FirebaseAppOptions - * @typedef {import('..').ReactNativeFirebase.LogLevelString} LogLevelString - * @typedef {import('../internal/logger').LogCallback} LogCallback - * @typedef {import('../internal/logger').LogOptions} LogOptions - */ - -/** - * Renders this app unusable and frees the resources of all associated services. - * @param {FirebaseApp} app - The app to delete. - * @returns {Promise} - */ -export function deleteApp(app) { - return deleteAppCompat.call(null, app.name, app._nativeInitialized, MODULAR_DEPRECATION_ARG); -} - -/** - * Registers a library's name and version for platform logging purposes. - @param {string} libraryKeyOrName - library name or key. - @param {string} version - library version. - @param {string | undefined} variant - library variant. Optional. - * @returns {Promise} - */ -export function registerVersion(libraryKeyOrName, version, variant) { - throw new Error('registerVersion is only supported on Web'); -} - -/** - * Sets log handler for VertexAI only currently. - * @param {LogCallback | null} logCallback - The callback function to handle logs. - * @param {LogOptions} [options] - Optional settings for log handling. - * @returns {void} - */ -export function onLog(logCallback, options) { - setUserLogHandler(logCallback, options); -} - -/** - * Gets the list of all initialized apps. - * @returns {FirebaseApp[]} - An array of all initialized Firebase apps. - */ -export function getApps() { - return getAppsCompat.call(null, MODULAR_DEPRECATION_ARG); -} - -/** - * Initializes a Firebase app with the provided options and name. - * @param {FirebaseAppOptions} options - Options to configure the services used in the app. - * @param {string | FirebaseAppConfig} [configOrName] - The optional name of the app, or config for the app to initialize (a name of '[DEFAULT]' will be used if omitted). - * @returns {FirebaseApp} - The initialized Firebase app. - */ -export function initializeApp(options, configOrName) { - return initializeAppCompat.call(null, options, configOrName, MODULAR_DEPRECATION_ARG); -} - -/** - * Retrieves an instance of a Firebase app. - * @param {string} [name] - The optional name of the app to return ('[DEFAULT]' if omitted). - * @returns {FirebaseApp} - The requested Firebase app instance. - */ -export function getApp(name) { - return getAppCompat.call(null, name, MODULAR_DEPRECATION_ARG); -} - -/** - * Sets the log level across all Firebase SDKs. - * @param {LogLevelString} logLevel - The log level to set ('debug', 'verbose', 'info', 'warn', 'error', 'silent'). - * @returns {void} - */ -export function setLogLevel(logLevel) { - return setLogLevelCompat.call(null, logLevel, MODULAR_DEPRECATION_ARG); -} - -/** - * The `AsyncStorage` implementation to use for persisting data on 'Other' platforms. - * If not specified, in memory persistence is used. - * - * This is required if you want to persist things like Auth sessions, Analytics device IDs, etc. - */ -export function setReactNativeAsyncStorage(asyncStorage) { - return setReactNativeAsyncStorageCompat.call(null, asyncStorage, MODULAR_DEPRECATION_ARG); -} - -/** - * Gets react-native-firebase specific "meta" data from native Info.plist / AndroidManifest.xml - * @returns map of key / value pairs containing native meta data - */ -export function metaGetAll() { - return NativeModules.RNFBAppModule.metaGetAll(); -} - -/** - * Gets react-native-firebase specific "firebase.json" data - * @returns map of key / value pairs containing native firebase.json constants - */ -export function jsonGetAll() { - return NativeModules.RNFBAppModule.jsonGetAll(); -} - -/** - * Clears react-native-firebase specific native preferences - * @returns Promise - */ -export function preferencesClearAll() { - return NativeModules.RNFBAppModule.preferencesClearAll(); -} - -/** - * Gets react-native-firebase specific native preferences - * @returns map of key / value pairs containing native preferences data - */ -export function preferencesGetAll() { - return NativeModules.RNFBAppModule.preferencesGetAll(); -} - -/** - * Sets react-native-firebase specific native boolean preference - * @param key the name of the native preference to set - * @param value the value of the native preference to set - * @returns Promise - */ -export function preferencesSetBool(key, value) { - return NativeModules.RNFBAppModule.preferencesSetBool(key, value); -} - -/** - * Sets react-native-firebase specific native string preference - * @param key the name of the native preference to set - * @param value the value of the native preference to set - * @returns Promise - */ -export function preferencesSetString(key, value) { - return NativeModules.RNFBAppModule.preferencesSetString(key, value); -} - -export const SDK_VERSION = sdkVersion; diff --git a/packages/app/lib/modular/index.ts b/packages/app/lib/modular/index.ts new file mode 100644 index 0000000000..b6c6404264 --- /dev/null +++ b/packages/app/lib/modular/index.ts @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * 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 { MODULAR_DEPRECATION_ARG } from '../common'; +import type { ReactNativeFirebase } from '../types'; +import { + deleteApp as deleteAppCompat, + getApp as getAppCompat, + getApps as getAppsCompat, + initializeApp as initializeAppCompat, + setLogLevel as setLogLevelCompat, + setReactNativeAsyncStorage as setReactNativeAsyncStorageCompat, +} from '../internal/registry/app'; +import { setUserLogHandler } from '../internal/logger'; +import { version as sdkVersion } from '../version'; +import { NativeModules } from 'react-native'; + +export interface LogCallbackParams { + level: ReactNativeFirebase.LogLevelString; + message: string; + args: unknown[]; + type: string; +} + +export type LogCallback = (callbackParams: LogCallbackParams) => void; + +export interface LogOptions { + level?: ReactNativeFirebase.LogLevelString; +} + +/** + * Renders this app unusable and frees the resources of all associated services. + * @param app - The app to delete. + * @returns Promise + */ +export function deleteApp(app: ReactNativeFirebase.FirebaseApp): Promise { + return deleteAppCompat.call( + null, + app.name, + (app as any)._nativeInitialized, + MODULAR_DEPRECATION_ARG, + ); +} + +/** + * Registers a library's name and version for platform logging purposes. + * @param _libraryKeyOrName - library name or key. + * @param _version - library version. + * @param _variant - library variant. Optional. + * @returns Promise + */ +export function registerVersion( + _libraryKeyOrName: string, + _version: string, + _variant?: string, +): Promise { + throw new Error('registerVersion is only supported on Web'); +} + +/** + * Sets log handler for VertexAI only currently. + * @param logCallback - The callback function to handle logs. + * @param options - Optional settings for log handling. + * @returns void + */ +export function onLog(logCallback: LogCallback | null, options?: LogOptions): void { + setUserLogHandler(logCallback, options); +} + +/** + * Gets the list of all initialized apps. + * @returns An array of all initialized Firebase apps. + */ +export function getApps(): ReactNativeFirebase.FirebaseApp[] { + return getAppsCompat.call(null, MODULAR_DEPRECATION_ARG); +} + +/** + * Initializes a Firebase app with the provided options and name. + * @param options - Options to configure the services used in the app. + * @param configOrName - The optional name of the app, or config for the app to initialize (a name of '[DEFAULT]' will be used if omitted). + * @returns The initialized Firebase app. + */ +export function initializeApp( + options: ReactNativeFirebase.FirebaseAppOptions, + configOrName?: string | ReactNativeFirebase.FirebaseAppConfig, +): Promise { + return initializeAppCompat.call(null, options, configOrName, MODULAR_DEPRECATION_ARG); +} + +/** + * Retrieves an instance of a Firebase app. + * @param name - The optional name of the app to return ('[DEFAULT]' if omitted). + * @returns The requested Firebase app instance. + */ +export function getApp(name?: string): ReactNativeFirebase.FirebaseApp { + return getAppCompat.call(null, name, MODULAR_DEPRECATION_ARG); +} + +/** + * Sets the log level across all Firebase SDKs. + * @param logLevel - The log level to set ('debug', 'verbose', 'info', 'warn', 'error', 'silent'). + * @returns void + */ +export function setLogLevel(logLevel: ReactNativeFirebase.LogLevelString): void { + return setLogLevelCompat.call(null, logLevel, MODULAR_DEPRECATION_ARG); +} + +/** + * The `AsyncStorage` implementation to use for persisting data on 'Other' platforms. + * If not specified, in memory persistence is used. + * + * This is required if you want to persist things like Auth sessions, Analytics device IDs, etc. + */ +export function setReactNativeAsyncStorage( + asyncStorage: ReactNativeFirebase.ReactNativeAsyncStorage, +): void { + return setReactNativeAsyncStorageCompat.call(null, asyncStorage, MODULAR_DEPRECATION_ARG); +} + +/** + * Gets react-native-firebase specific "meta" data from native Info.plist / AndroidManifest.xml + * @returns map of key / value pairs containing native meta data + */ +export function metaGetAll(): Promise<{ [key: string]: string | boolean }> { + return NativeModules.RNFBAppModule.metaGetAll(); +} + +/** + * Gets react-native-firebase specific "firebase.json" data + * @returns map of key / value pairs containing native firebase.json constants + */ +export function jsonGetAll(): Promise<{ [key: string]: string | boolean }> { + return NativeModules.RNFBAppModule.jsonGetAll(); +} + +/** + * Clears react-native-firebase specific native preferences + * @returns Promise + */ +export function preferencesClearAll(): Promise { + return NativeModules.RNFBAppModule.preferencesClearAll(); +} + +/** + * Gets react-native-firebase specific native preferences + * @returns map of key / value pairs containing native preferences data + */ +export function preferencesGetAll(): Promise<{ [key: string]: string | boolean }> { + return NativeModules.RNFBAppModule.preferencesGetAll(); +} + +/** + * Sets react-native-firebase specific native boolean preference + * @param key the name of the native preference to set + * @param value the value of the native preference to set + * @returns Promise + */ +export function preferencesSetBool(key: string, value: boolean): Promise { + return NativeModules.RNFBAppModule.preferencesSetBool(key, value); +} + +/** + * Sets react-native-firebase specific native string preference + * @param key the name of the native preference to set + * @param value the value of the native preference to set + * @returns Promise + */ +export function preferencesSetString(key: string, value: string): Promise { + return NativeModules.RNFBAppModule.preferencesSetString(key, value); +} + +export const SDK_VERSION = sdkVersion; diff --git a/packages/app/lib/types/index.ts b/packages/app/lib/types/index.ts new file mode 100644 index 0000000000..b9f1c58eb4 --- /dev/null +++ b/packages/app/lib/types/index.ts @@ -0,0 +1,614 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * 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. + * + */ + +/** + * Core React Native Firebase package types. + * + * @firebase app + */ +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace ReactNativeFirebase { + export interface NativeFirebaseError extends Error { + /** + * Firebase error code, e.g. `auth/invalid-email` + */ + readonly code: string; + + /** + * Firebase error message + */ + readonly message: string; + + /** + * The firebase module namespace that this error originated from, e.g. 'analytics' + */ + readonly namespace: string; + + /** + * The native sdks returned error code, different per platform + */ + readonly nativeErrorCode: string | number; + + /** + * The native sdks returned error message, different per platform + */ + readonly nativeErrorMessage: string; + } + + export type LogLevelString = 'debug' | 'verbose' | 'info' | 'warn' | 'error' | 'silent'; + + export interface FirebaseAppOptions { + /** + * The Google App ID that is used to uniquely identify an instance of an app. + */ + appId: string; + + /** + * An API key used for authenticating requests from your app, e.g. + * "AIzaSyDdVgKwhZl0sTTTLZ7iTmt1r3N2cJLnaDk", used to identify your app to Google servers. + */ + apiKey?: string; + + /** + * The database root URL, e.g. "http://abc-xyz-123.firebaseio.com". + */ + databaseURL?: string; + + /** + * The Project ID from the Firebase console, for example "abc-xyz-123". + */ + projectId: string; + + /** + * The tracking ID for Google Analytics, e.g. "UA-12345678-1", used to configure Google Analytics. + */ + measurementId?: string; + + /** + * The Google Cloud Storage bucket name, e.g. "abc-xyz-123.storage.firebase.com". + */ + storageBucket?: string; + + /** + * The Project Number from the Google Developer's console, for example "012345678901", used to + * configure Google Cloud Messaging. + */ + messagingSenderId?: string; + + /** + * iOS only - The OAuth2 client ID for iOS application used to authenticate Google users, for example + * "12345.apps.googleusercontent.com", used for signing in with Google. + */ + clientId?: string; + + /** + * iOS only - The Android client ID used in Google AppInvite when an iOS app has its Android version, for + * example "12345.apps.googleusercontent.com". + */ + androidClientId?: string; + + /** + * iOS only - The URL scheme used to set up Durable Deep Link service. + */ + deepLinkURLScheme?: string; + [name: string]: any; + } + + export interface FirebaseAppConfig { + /** + * The Firebase App name, defaults to [DEFAULT] if none provided. + */ + name?: string; + + /** + * Default setting for data collection on startup that affects all Firebase module startup data collection settings, + * in the absence of module-specific overrides. This will start as false if you set "app_data_collection_default_enabled" + * to false in firebase.json and may be used in opt-in flows, for example a GDPR-compliant app. + * If configured false initially, set to true after obtaining consent, then enable module-specific settings as needed afterwards. + */ + automaticDataCollectionEnabled?: boolean; + + /** + * If set to true it indicates that Firebase should close database connections + * automatically when the app is in the background. Disabled by default. + */ + automaticResourceManagement?: boolean; + } + + export interface FirebaseApp { + /** + * The name (identifier) for this App. '[DEFAULT]' is the default App. + */ + readonly name: string; + + /** + * The (read-only) configuration options from the app initialization. + */ + readonly options: FirebaseAppOptions; + + /** + * The settable config flag for GDPR opt-in/opt-out + */ + automaticDataCollectionEnabled: boolean; + + /** + * Make this app unusable and free up resources. + */ + delete(): Promise; + + utils(): Utils.Module; + } + + /** + * Interface for a supplied `AsyncStorage`. + */ + export interface ReactNativeAsyncStorage { + /** + * Persist an item in storage. + * + * @param key - storage key. + * @param value - storage value. + */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + setItem: Function; + /** + * Retrieve an item from storage. + * + * @param key - storage key. + */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + getItem: Function; + /** + * Remove an item from storage. + * + * @param key - storage key. + */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + removeItem: Function; + + [key: string]: any; + } + + export interface Module { + /** + * Create (and initialize) a FirebaseApp. + * + * @param options Options to configure the services used in the App. + * @param config The optional config for your firebase app + */ + initializeApp(options: FirebaseAppOptions, config?: FirebaseAppConfig): Promise; + + /** + * Create (and initialize) a FirebaseApp. + * + * @param options Options to configure the services used in the App. + * @param name The optional name of the app to initialize ('[DEFAULT]' if + * omitted) + */ + initializeApp(options: FirebaseAppOptions, name?: string): Promise; + + /** + * Retrieve an instance of a FirebaseApp. + * + * @example + * ```js + * const app = firebase.app('foo'); + * ``` + * + * @param name The optional name of the app to return ('[DEFAULT]' if omitted) + */ + app(name?: string): FirebaseApp; + + /** + * Set the log level across all modules. Only applies to iOS currently, has no effect on Android. + * Should be one of 'error', 'warn', 'info', or 'debug'. + * Logs messages at the configured level or lower (less verbose / more important). + * Note that if an app is running from AppStore, it will never log above info even if + * level is set to a higher (more verbose) setting. + * Note that iOS is missing firebase-js-sdk log levels 'verbose' and 'silent'. + * 'verbose' if used will map to 'debug', 'silent' has no valid mapping and will return an error if used. + * + * @ios + */ + setLogLevel(logLevel: LogLevelString): void; + + /** + * The `AsyncStorage` implementation to use for persisting data on 'Other' platforms. + * If not specified, in memory persistence is used. + * + * This is required if you want to persist things like Auth sessions, Analytics device IDs, etc. + */ + setReactNativeAsyncStorage(asyncStorage: ReactNativeAsyncStorage): void; + + /** + * A (read-only) array of all the initialized Apps. + */ + apps: FirebaseApp[]; + + /** + * The current React Native Firebase version. + */ + readonly SDK_VERSION: string; + + /** + * Utils provides a collection of utilities to aid in using Firebase + * and related services inside React Native, e.g. Test Lab helpers + * and Google Play Services version helpers. + */ + utils: Utils.Module; + } + + /** + * A class that all React Native Firebase modules extend from to provide default behaviour. + */ + export abstract class FirebaseModule { + /** + * The current `FirebaseApp` instance for this Firebase service. + */ + abstract app: FirebaseApp; + } + + // eslint-disable-next-line @typescript-eslint/no-empty-object-type + export type FirebaseModuleWithStatics = { + (): M; + + /** + * This React Native Firebase module version. + */ + readonly SDK_VERSION: string; + } & S; + + // eslint-disable-next-line @typescript-eslint/no-empty-object-type + export type FirebaseModuleWithStaticsAndApp = { + (app?: FirebaseApp): M; + + /** + * This React Native Firebase module version. + */ + readonly SDK_VERSION: string; + } & S; +} + +/** + * @firebase utils + */ +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Utils { + import FirebaseModule = ReactNativeFirebase.FirebaseModule; + + /** + * A collection of native device file paths to aid in the usage of file path based methods. + * + * Concatenate a file path with your target file name when using with Storage `putFile` or `writeToFile`. + * + * ```js + * firebase.utils.FilePath; + * ``` + */ + export interface FilePath { + /** + * Returns an absolute path to the applications main bundle. + * + * ```js + * firebase.utils.FilePath.MAIN_BUNDLE; + * ``` + * + * @ios iOS only + */ + MAIN_BUNDLE: string; + + /** + * Returns an absolute path to the application specific cache directory on the filesystem. + * + * The system will automatically delete files in this directory when disk space is needed elsewhere on the device, starting with the oldest files first. + * + * ```js + * firebase.utils.FilePath.CACHES_DIRECTORY; + * ``` + */ + CACHES_DIRECTORY: string; + + /** + * Returns an absolute path to the users Documents directory. + * + * Use this directory to place documents that have been created by the user. + * + * Normally this is the external files directory on Android but if no external storage directory found, + * e.g. removable media has been ejected by the user, it will fall back to internal storage. This may + * under rare circumstances where device storage environment changes cause the directory to be different + * between runs of the application + * + * ```js + * firebase.utils.FilePath.DOCUMENT_DIRECTORY; + * ``` + */ + DOCUMENT_DIRECTORY: string; + + /** + * Returns an absolute path to a temporary directory. + * + * Use this directory to create temporary files. The system will automatically delete files in this directory when disk space is needed elsewhere on the device, starting with the oldest files first. + * + * ```js + * firebase.utils.FilePath.TEMP_DIRECTORY; + * ``` + */ + TEMP_DIRECTORY: string; + + /** + * Returns an absolute path to the apps library/resources directory. + * + * E.g. this can be used for things like documentation, support files, and configuration files and generic resources. + * + * ```js + * firebase.utils.FilePath.LIBRARY_DIRECTORY; + * ``` + */ + LIBRARY_DIRECTORY: string; + + /** + * Returns an absolute path to the directory on the primary shared/external storage device. + * + * Here your application can place persistent files it owns. These files are internal to the application, and not typically visible to the user as media. + * + * Returns null if no external storage directory found, e.g. removable media has been ejected by the user. + * + * ```js + * firebase.utils.FilePath.EXTERNAL_DIRECTORY; + * ``` + * + * @android Android only - iOS returns null + */ + EXTERNAL_DIRECTORY: string | null; + + /** + * Returns an absolute path to the primary shared/external storage directory. + * + * Traditionally this is an SD card, but it may also be implemented as built-in storage on a device. + * + * Returns null if no external storage directory found, e.g. removable media has been ejected by the user. + * Requires special permission granted by Play Store review team on Android, is unlikely to be a valid path. + * + * ```js + * firebase.utils.FilePath.EXTERNAL_STORAGE_DIRECTORY; + * ``` + * + * @android Android only - iOS returns null + */ + EXTERNAL_STORAGE_DIRECTORY: string | null; + + /** + * Returns an absolute path to a directory in which to place pictures that are available to the user. + * Requires special permission granted by Play Store review team on Android, is unlikely to be a valid path. + * + * ```js + * firebase.utils.FilePath.PICTURES_DIRECTORY; + * ``` + */ + PICTURES_DIRECTORY: string; + + /** + * Returns an absolute path to a directory in which to place movies that are available to the user. + * Requires special permission granted by Play Store review team on Android, is unlikely to be a valid path. + * + * ```js + * firebase.utils.FilePath.MOVIES_DIRECTORY; + * ``` + */ + MOVIES_DIRECTORY: string; + } + + export interface Statics { + FilePath: FilePath; + } + + /** + * For further information on the status codes available & what they represent, please head over + * to ConnectionResult documentation: + * https://developers.google.com/android/reference/com/google/android/gms/common/ConnectionResult + */ + export enum PlayServicesAvailabilityStatusCodes { + API_UNAVAILABLE = 16, + CANCELED = 13, + DEVELOPER_ERROR = 10, + DRIVE_EXTERNAL_STORAGE_REQUIRED = 1500, + INTERNAL_ERROR = 8, + INTERRUPTED = 15, + INVALID_ACCOUNT = 5, + LICENSE_CHECK_FAILED = 11, + NETWORK_ERROR = 7, + RESOLUTION_REQUIRED = 6, + RESTRICTED_PROFILE = 20, + SERVICE_DISABLED = 3, + SERVICE_INVALID = 9, + SERVICE_MISSING = 1, + SERVICE_MISSING_PERMISSION = 19, + SERVICE_UPDATING = 18, + SERVICE_VERSION_UPDATE_REQUIRED = 2, + SIGN_IN_FAILED = 17, + SIGN_IN_REQUIRED = 4, + SUCCESS = 0, + TIMEOUT = 14, + } + + export interface PlayServicesAvailability { + /** + * Returns a numeric status code. Please refer to Android documentation + * for further information: + * https://developers.google.com/android/reference/com/google/android/gms/common/ConnectionResult + * + * ```js + * firebase.utils().playServicesAvailability.status; + * ``` + * + * @android Android only - iOS returns 0 + */ + status: PlayServicesAvailabilityStatusCodes; + + /** + * Returns a boolean indicating whether Play Store is available on the device + * + * ```js + * firebase.utils().playServicesAvailability.isAvailable; + * ``` + * + * @android Android only - iOS returns true + */ + isAvailable: boolean; + + /** + * If Play Services is not available on the device, hasResolution indicates + * whether it is possible to do something about it (e.g. install Play Services). + * + * ```js + * firebase.utils().playServicesAvailability.hasResolution; + * ``` + * @android Android only - iOS returns undefined + */ + hasResolution: boolean | undefined; + + /** + * If an error was received, this indicates whether the error is resolvable + * + * ```js + * firebase.utils().playServicesAvailability.isUserResolvableError; + * ``` + * @android Android only - iOS returns undefined + */ + isUserResolvableError: boolean | undefined; + + /** + * A human readable error string + * + * ```js + * firebase.utils().playServicesAvailability.error; + * ``` + * @android Android only - iOS returns undefined + */ + error: string | undefined; + } + + /** + * The React Native Firebase Utils service interface. + * + * > This module is available for the default app only. + * + * #### Example + * + * Get the Utils service for the default app: + * + * ```js + * const defaultAppUtils = firebase.utils(); + * ``` + */ + export abstract class Module extends FirebaseModule { + /** + * Returns true if this app is running inside a Firebase Test Lab environment. + * + * #### Example + * + * ```js + * const isRunningInTestLab = await firebase.utils().isRunningInTestLab; + * ``` + * @android Android only - iOS returns false + */ + abstract isRunningInTestLab: boolean; + /** + * Returns PlayServicesAvailability properties + * + * #### Example + * + * ```js + * const PlayServicesAvailability = await firebase.utils().playServicesAvailability; + * ``` + * + * @android Android only - iOS always returns { isAvailable: true, status: 0 } + */ + abstract playServicesAvailability: PlayServicesAvailability; + + /** + * Returns PlayServicesAvailability properties + * + * #### Example + * + * ```js + * const PlayServicesAvailability = await firebase.utils().getPlayServicesStatus(); + * ``` + * + * @android Android only - iOS always returns { isAvailable: true, status: 0 } + */ + abstract getPlayServicesStatus(): Promise; + + /** + * A prompt appears on the device to ask the user to update play services + * + * #### Example + * + * ```js + * await firebase.utils().promptForPlayServices(); + * ``` + * + * @android Android only - iOS returns undefined + */ + abstract promptForPlayServices(): Promise; + /** + * Attempts to make Google Play services available on this device + * + * #### Example + * + * ```js + * await firebase.utils().makePlayServicesAvailable(); + * ``` + * + * @android Android only - iOS returns undefined + */ + abstract makePlayServicesAvailable(): Promise; + /** + * Resolves an error by starting any intents requiring user interaction. + * + * #### Example + * + * ```js + * await firebase.utils().resolutionForPlayServices(); + * ``` + * + * @android Android only - iOS returns undefined + */ + abstract resolutionForPlayServices(): Promise; + } +} + +/** + * Internal module type definitions for consumption by other packages + */ +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Internal { + export declare function createModuleNamespace(config: any): any; + export interface FirebaseModule { + native: any; + firebaseJson: any; + _customUrlOrRegion: string | null; + } + export declare function getFirebaseRoot(): any; + export interface NativeFirebaseError { + code?: string; + message?: string; + } + export interface NativeFirebaseErrorStatic { + getStackWithMessage(message: string, jsStack?: string): string; + } +} diff --git a/packages/app/lib/utils/UtilsStatics.js b/packages/app/lib/utils/UtilsStatics.ts similarity index 72% rename from packages/app/lib/utils/UtilsStatics.js rename to packages/app/lib/utils/UtilsStatics.ts index addbd7f394..92061bef25 100644 --- a/packages/app/lib/utils/UtilsStatics.js +++ b/packages/app/lib/utils/UtilsStatics.ts @@ -17,6 +17,8 @@ import { NativeModules } from 'react-native'; import { stripTrailingSlash, isOther } from '../../lib/common'; +import { Utils } from '../types'; +import { version } from '../version'; const PATH_NAMES = [ 'MAIN_BUNDLE', @@ -28,38 +30,41 @@ const PATH_NAMES = [ 'LIBRARY_DIRECTORY', 'PICTURES_DIRECTORY', 'MOVIES_DIRECTORY', -]; +] as const; -const PATH_FILE_TYPES = ['FILE_TYPE_REGULAR', 'FILE_TYPE_DIRECTORY']; +const PATH_FILE_TYPES = ['FILE_TYPE_REGULAR', 'FILE_TYPE_DIRECTORY'] as const; -const paths = {}; +const paths: Partial = {}; let processedPathConstants = false; -function processPathConstants(nativeModule) { +function processPathConstants(nativeModule: any): Utils.FilePath { if (processedPathConstants || !nativeModule) { - return paths; + return paths as Utils.FilePath; } processedPathConstants = true; for (let i = 0; i < PATH_NAMES.length; i++) { const path = PATH_NAMES[i]; - paths[path] = nativeModule[path] ? stripTrailingSlash(nativeModule[path]) : null; + (paths as any)[path] = nativeModule[path] ? stripTrailingSlash(nativeModule[path]) : null; } for (let i = 0; i < PATH_FILE_TYPES.length; i++) { const pathFileType = PATH_FILE_TYPES[i]; - paths[pathFileType] = stripTrailingSlash(nativeModule[pathFileType]); + (paths as any)[pathFileType] = stripTrailingSlash(nativeModule[pathFileType]); } Object.freeze(paths); - return paths; + return paths as Utils.FilePath; } -export default { - SDK_VERSION: require('./../version'), - get FilePath() { +const statics: Utils.Statics = { + SDK_VERSION: version, + get FilePath(): Utils.FilePath { // We don't support path constants on non-native platforms. return processPathConstants(isOther ? {} : NativeModules.RNFBUtilsModule); }, }; + +export default statics; + diff --git a/packages/app/lib/utils/index.js b/packages/app/lib/utils/index.ts similarity index 80% rename from packages/app/lib/utils/index.js rename to packages/app/lib/utils/index.ts index 9f7befa923..68d2ae5452 100644 --- a/packages/app/lib/utils/index.js +++ b/packages/app/lib/utils/index.ts @@ -16,22 +16,25 @@ */ import { isIOS } from '../../lib/common'; +// @ts-expect-error import { createModuleNamespace, FirebaseModule } from '../../lib/internal'; import UtilsStatics from './UtilsStatics'; +import { Utils } from '../types'; +import FirebaseApp from '../FirebaseApp'; const namespace = 'utils'; const statics = UtilsStatics; const nativeModuleName = 'RNFBUtilsModule'; -class FirebaseUtilsModule extends FirebaseModule { - get isRunningInTestLab() { +class FirebaseUtilsModule extends FirebaseModule implements Utils.Module { + get isRunningInTestLab(): boolean { if (isIOS) { return false; } return this.native.isRunningInTestLab; } - get playServicesAvailability() { + get playServicesAvailability(): Utils.PlayServicesAvailability { if (isIOS) { return { isAvailable: true, @@ -41,7 +44,7 @@ class FirebaseUtilsModule extends FirebaseModule { return this.native.androidPlayServices; } - getPlayServicesStatus() { + getPlayServicesStatus(): Promise { if (isIOS) { return Promise.resolve({ isAvailable: true, @@ -51,30 +54,26 @@ class FirebaseUtilsModule extends FirebaseModule { return this.native.androidGetPlayServicesStatus(); } - promptForPlayServices() { + promptForPlayServices(): Promise { if (isIOS) { return Promise.resolve(); } return this.native.androidPromptForPlayServices(); } - makePlayServicesAvailable() { + makePlayServicesAvailable(): Promise { if (isIOS) { return Promise.resolve(); } return this.native.androidMakePlayServicesAvailable(); } - resolutionForPlayServices() { + resolutionForPlayServices(): Promise { if (isIOS) { return Promise.resolve(); } return this.native.androidResolutionForPlayServices(); } - - logInfo(...args) { - return logger.logInfo(...args); - } } // import { utils } from '@react-native-firebase/app'; @@ -89,3 +88,4 @@ export default createModuleNamespace({ hasCustomUrlOrRegionSupport: false, ModuleClass: FirebaseUtilsModule, }); + From bf7c589bd16d4186f4e5bec4623c9a26f2756fa1 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 2 Dec 2025 15:15:23 +0000 Subject: [PATCH 04/28] refactor: further internal TS changes --- packages/app/lib/common/index.ts | 5 +- packages/app/lib/internal/FirebaseModule.ts | 5 +- .../lib/internal/RNFBNativeEventEmitter.ts | 22 +++-- packages/app/lib/internal/global.d.ts | 33 ++++++++ .../lib/internal/nativeModuleAndroidIos.ts | 18 +++-- packages/app/lib/internal/nativeModuleWeb.ts | 18 +++-- .../app/lib/internal/registry/namespace.ts | 81 ++++++++++++------- .../app/lib/internal/registry/nativeModule.ts | 21 +++-- 8 files changed, 145 insertions(+), 58 deletions(-) create mode 100644 packages/app/lib/internal/global.d.ts diff --git a/packages/app/lib/common/index.ts b/packages/app/lib/common/index.ts index 0c3771d9a3..45a84556ae 100644 --- a/packages/app/lib/common/index.ts +++ b/packages/app/lib/common/index.ts @@ -17,6 +17,7 @@ import { Platform } from 'react-native'; import Base64 from './Base64'; import { isFunction, isObject, isString } from './validate'; +import '../internal/global'; export * from './id'; export * from './path'; @@ -795,11 +796,11 @@ export function warnIfNotModularCall(args: IArguments, replacementMethodName: st message += ` Please use \`${replacementMethodName}\` instead.`; } - if (!(globalThis as any).RNFB_SILENCE_MODULAR_DEPRECATION_WARNINGS) { + if (!globalThis.RNFB_SILENCE_MODULAR_DEPRECATION_WARNINGS) { // eslint-disable-next-line no-console console.warn(message); - if ((globalThis as any).RNFB_MODULAR_DEPRECATION_STRICT_MODE === true) { + if (globalThis.RNFB_MODULAR_DEPRECATION_STRICT_MODE === true) { throw new Error('Deprecated API usage detected while in strict mode.'); } } diff --git a/packages/app/lib/internal/FirebaseModule.ts b/packages/app/lib/internal/FirebaseModule.ts index 9ce6f0543c..bf29fbe67e 100644 --- a/packages/app/lib/internal/FirebaseModule.ts +++ b/packages/app/lib/internal/FirebaseModule.ts @@ -23,10 +23,11 @@ let firebaseJson: any = null; export interface ModuleConfig { namespace: string; - nativeModuleName?: string; + nativeModuleName?: string | string[]; hasMultiAppSupport?: boolean; hasCustomUrlOrRegionSupport?: boolean; - nativeEvents?: string[]; + nativeEvents?: boolean | string[]; + disablePrependCustomUrlOrRegion?: boolean; } export default class FirebaseModule { diff --git a/packages/app/lib/internal/RNFBNativeEventEmitter.ts b/packages/app/lib/internal/RNFBNativeEventEmitter.ts index 85bbec189e..c9d4629f9e 100644 --- a/packages/app/lib/internal/RNFBNativeEventEmitter.ts +++ b/packages/app/lib/internal/RNFBNativeEventEmitter.ts @@ -17,6 +17,7 @@ import { type EmitterSubscription, NativeEventEmitter } from 'react-native'; import { getReactNativeModule } from './nativeModule'; +import './global'; class RNFBNativeEventEmitter extends NativeEventEmitter { ready: boolean; @@ -32,7 +33,11 @@ class RNFBNativeEventEmitter extends NativeEventEmitter { this.ready = false; } - addListener(eventType: string, listener: (...args: any[]) => any, context?: any): EmitterSubscription { + addListener( + eventType: string, + listener: (...args: any[]) => any, + context?: any, + ): EmitterSubscription { const RNFBAppModule = getReactNativeModule('RNFBAppModule'); if (!this.ready) { RNFBAppModule.eventsNotifyReady(true); @@ -74,12 +79,13 @@ class RNFBNativeEventEmitter extends NativeEventEmitter { // New style is to return a remove function on the object, just in case people call that, // we will modify it to do our native unsubscription then call the original - let originalRemove = subscription.remove; - let newRemove = () => { + const originalRemove = subscription.remove; + const newRemove = () => { RNFBAppModule.eventsRemoveListener(eventType, false); - if ((super as any).removeSubscription != null) { + const superClass = Object.getPrototypeOf(Object.getPrototypeOf(this)); + if (superClass.removeSubscription != null) { // This is for RN <= 0.64 - 65 and greater no longer have removeSubscription - (super as any).removeSubscription(subscription); + superClass.removeSubscription(subscription); } else if (originalRemove != null) { // This is for RN >= 0.65 originalRemove(); @@ -100,11 +106,11 @@ class RNFBNativeEventEmitter extends NativeEventEmitter { const RNFBAppModule = getReactNativeModule('RNFBAppModule'); const eventType = subscription.eventType?.replace('rnfb_', '') || ''; RNFBAppModule.eventsRemoveListener(eventType, false); - if ((super as any).removeSubscription) { - (super as any).removeSubscription(subscription); + const superClass = Object.getPrototypeOf(Object.getPrototypeOf(this)); + if (superClass.removeSubscription) { + superClass.removeSubscription(subscription); } } } export default new RNFBNativeEventEmitter(); - diff --git a/packages/app/lib/internal/global.d.ts b/packages/app/lib/internal/global.d.ts new file mode 100644 index 0000000000..7112dd20c0 --- /dev/null +++ b/packages/app/lib/internal/global.d.ts @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * 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. + * + */ + +/** + * Global RNFB properties for debugging and testing + */ +declare global { + // Debug and testing flags + var RNFBDebug: boolean | undefined; + var RNFBTest: boolean | undefined; + var RNFBDebugInTestLeakDetection: boolean | undefined; + var RNFBDebugLastTest: string | undefined; + + // Modular API deprecation flags + var RNFB_SILENCE_MODULAR_DEPRECATION_WARNINGS: boolean | undefined; + var RNFB_MODULAR_DEPRECATION_STRICT_MODE: boolean | undefined; +} + +export {}; diff --git a/packages/app/lib/internal/nativeModuleAndroidIos.ts b/packages/app/lib/internal/nativeModuleAndroidIos.ts index 0f779ed135..578d054c8a 100644 --- a/packages/app/lib/internal/nativeModuleAndroidIos.ts +++ b/packages/app/lib/internal/nativeModuleAndroidIos.ts @@ -1,5 +1,6 @@ /* eslint-disable no-console */ import { NativeModules } from 'react-native'; +import './global'; /** * This is used by Android and iOS to get a native module. @@ -19,21 +20,29 @@ export function getReactNativeModule(moduleName: string): any { get: (_, name) => { if (typeof nativeModule[name as string] !== 'function') return nativeModule[name as string]; return (...args: any[]) => { - console.debug(`[RNFB->Native][🔵] ${moduleName}.${String(name)} -> ${JSON.stringify(args)}`); + console.debug( + `[RNFB->Native][🔵] ${moduleName}.${String(name)} -> ${JSON.stringify(args)}`, + ); const result = nativeModule[name as string](...args); if (result && result.then) { return result.then( (res: any) => { - console.debug(`[RNFB<-Native][🟢] ${moduleName}.${String(name)} <- ${JSON.stringify(res)}`); + console.debug( + `[RNFB<-Native][🟢] ${moduleName}.${String(name)} <- ${JSON.stringify(res)}`, + ); return res; }, (err: any) => { - console.debug(`[RNFB<-Native][🔴] ${moduleName}.${String(name)} <- ${JSON.stringify(err)}`); + console.debug( + `[RNFB<-Native][🔴] ${moduleName}.${String(name)} <- ${JSON.stringify(err)}`, + ); throw err; }, ); } - console.debug(`[RNFB<-Native][🟢] ${moduleName}.${String(name)} <- ${JSON.stringify(result)}`); + console.debug( + `[RNFB<-Native][🟢] ${moduleName}.${String(name)} <- ${JSON.stringify(result)}`, + ); return result; }; }, @@ -43,4 +52,3 @@ export function getReactNativeModule(moduleName: string): any { export function setReactNativeModule(): void { // No-op } - diff --git a/packages/app/lib/internal/nativeModuleWeb.ts b/packages/app/lib/internal/nativeModuleWeb.ts index 266fa762f5..44bad2c9b8 100644 --- a/packages/app/lib/internal/nativeModuleWeb.ts +++ b/packages/app/lib/internal/nativeModuleWeb.ts @@ -1,5 +1,6 @@ /* eslint-disable no-console */ +import './global'; // @ts-expect-error import RNFBAppModule from './web/RNFBAppModule'; @@ -22,21 +23,29 @@ export function getReactNativeModule(moduleName: string): any { get: (_, name) => { if (typeof nativeModule[name as string] !== 'function') return nativeModule[name as string]; return (...args: any[]) => { - console.debug(`[RNFB->Native][🔵] ${moduleName}.${String(name)} -> ${JSON.stringify(args)}`); + console.debug( + `[RNFB->Native][🔵] ${moduleName}.${String(name)} -> ${JSON.stringify(args)}`, + ); const result = nativeModule[name as string](...args); if (result && result.then) { return result.then( (res: any) => { - console.debug(`[RNFB<-Native][🟢] ${moduleName}.${String(name)} <- ${JSON.stringify(res)}`); + console.debug( + `[RNFB<-Native][🟢] ${moduleName}.${String(name)} <- ${JSON.stringify(res)}`, + ); return res; }, (err: any) => { - console.debug(`[RNFB<-Native][🔴] ${moduleName}.${String(name)} <- ${JSON.stringify(err)}`); + console.debug( + `[RNFB<-Native][🔴] ${moduleName}.${String(name)} <- ${JSON.stringify(err)}`, + ); throw err; }, ); } - console.debug(`[RNFB<-Native][🟢] ${moduleName}.${String(name)} <- ${JSON.stringify(result)}`); + console.debug( + `[RNFB<-Native][🟢] ${moduleName}.${String(name)} <- ${JSON.stringify(result)}`, + ); return result; }; }, @@ -48,4 +57,3 @@ export function setReactNativeModule(moduleName: string, nativeModule: any): voi } setReactNativeModule('RNFBAppModule', RNFBAppModule); - diff --git a/packages/app/lib/internal/registry/namespace.ts b/packages/app/lib/internal/registry/namespace.ts index 2dd90461fa..5997c29b10 100644 --- a/packages/app/lib/internal/registry/namespace.ts +++ b/packages/app/lib/internal/registry/namespace.ts @@ -19,7 +19,7 @@ import { isString, createDeprecationProxy } from '../../common'; import FirebaseApp from '../../FirebaseApp'; import { version as SDK_VERSION } from '../../version'; import { DEFAULT_APP_NAME, KNOWN_NAMESPACES, type KnownNamespace } from '../constants'; -import FirebaseModule from '../FirebaseModule'; +import FirebaseModule, { type ModuleConfig } from '../FirebaseModule'; import { getApp, getApps, @@ -30,17 +30,19 @@ import { setOnAppDestroy, } from './app'; -interface NamespaceConfig { - hasCustomUrlOrRegionSupport?: boolean; - hasMultiAppSupport?: boolean; +interface NamespaceConfig extends ModuleConfig { + nativeModuleName: string | string[]; + nativeEvents: boolean | string[]; + disablePrependCustomUrlOrRegion?: boolean; ModuleClass: typeof FirebaseModule; statics?: Record; + version?: string; } // firebase.X let FIREBASE_ROOT: any = null; -const NAMESPACE_REGISTRY: Record = {}; +const NAMESPACE_REGISTRY: Record = {}; const APP_MODULE_INSTANCE: Record> = {}; const MODULE_GETTER_FOR_APP: Record> = {}; const MODULE_GETTER_FOR_ROOT: Record = {}; @@ -53,10 +55,12 @@ const MODULE_GETTER_FOR_ROOT: Record = {}; setOnAppCreate(app => { for (let i = 0; i < KNOWN_NAMESPACES.length; i++) { const moduleNamespace = KNOWN_NAMESPACES[i]; - Object.defineProperty(app, moduleNamespace, { - enumerable: false, - get: firebaseAppModuleProxy.bind(null, app, moduleNamespace), - }); + if (moduleNamespace) { + Object.defineProperty(app, moduleNamespace, { + enumerable: false, + get: firebaseAppModuleProxy.bind(null, app, moduleNamespace), + }); + } } }); @@ -78,16 +82,19 @@ setOnAppDestroy(app => { * @returns {*} */ function getOrCreateModuleForApp(app: FirebaseApp, moduleNamespace: KnownNamespace): any { - if (MODULE_GETTER_FOR_APP[app.name] && MODULE_GETTER_FOR_APP[app.name][moduleNamespace]) { - return MODULE_GETTER_FOR_APP[app.name][moduleNamespace]; + if (MODULE_GETTER_FOR_APP[app.name] && MODULE_GETTER_FOR_APP[app.name]?.[moduleNamespace]) { + return MODULE_GETTER_FOR_APP[app.name]![moduleNamespace]; } if (!MODULE_GETTER_FOR_APP[app.name]) { MODULE_GETTER_FOR_APP[app.name] = {}; } - const { hasCustomUrlOrRegionSupport, hasMultiAppSupport, ModuleClass } = - NAMESPACE_REGISTRY[moduleNamespace]; + const config = NAMESPACE_REGISTRY[moduleNamespace]; + if (!config) { + throw new Error(`Module namespace '${moduleNamespace}' is not registered.`); + } + const { hasCustomUrlOrRegionSupport, hasMultiAppSupport, ModuleClass } = config; // modules such as analytics only run on the default app if (!hasMultiAppSupport && app.name !== DEFAULT_APP_NAME) { @@ -120,19 +127,23 @@ function getOrCreateModuleForApp(app: FirebaseApp, moduleNamespace: KnownNamespa APP_MODULE_INSTANCE[app.name] = {}; } - if (!APP_MODULE_INSTANCE[app.name][key]) { + if (!APP_MODULE_INSTANCE[app.name]?.[key]) { + const moduleConfig = NAMESPACE_REGISTRY[moduleNamespace]; + if (!moduleConfig) { + throw new Error(`Module namespace '${moduleNamespace}' is not registered.`); + } const module = createDeprecationProxy( - new ModuleClass(app, NAMESPACE_REGISTRY[moduleNamespace], customUrlOrRegionOrDatabaseId), + new ModuleClass(app, moduleConfig, customUrlOrRegionOrDatabaseId), ); - APP_MODULE_INSTANCE[app.name][key] = module; + APP_MODULE_INSTANCE[app.name]![key] = module; } - return APP_MODULE_INSTANCE[app.name][key]; + return APP_MODULE_INSTANCE[app.name]![key]; } - MODULE_GETTER_FOR_APP[app.name][moduleNamespace] = firebaseModuleWithArgs; - return MODULE_GETTER_FOR_APP[app.name][moduleNamespace]; + MODULE_GETTER_FOR_APP[app.name]![moduleNamespace] = firebaseModuleWithArgs; + return MODULE_GETTER_FOR_APP[app.name]![moduleNamespace]; } /** @@ -145,7 +156,11 @@ function getOrCreateModuleForRoot(moduleNamespace: KnownNamespace): any { return MODULE_GETTER_FOR_ROOT[moduleNamespace]; } - const { statics, hasMultiAppSupport, ModuleClass } = NAMESPACE_REGISTRY[moduleNamespace]; + const config = NAMESPACE_REGISTRY[moduleNamespace]; + if (!config) { + throw new Error(`Module namespace '${moduleNamespace}' is not registered.`); + } + const { statics, hasMultiAppSupport, ModuleClass } = config; // e.g. firebase.storage(app) function firebaseModuleWithApp(app?: FirebaseApp): any { @@ -176,14 +191,16 @@ function getOrCreateModuleForRoot(moduleNamespace: KnownNamespace): any { APP_MODULE_INSTANCE[_app.name] = {}; } - if (!APP_MODULE_INSTANCE[_app.name][moduleNamespace]) { - const module = createDeprecationProxy( - new ModuleClass(_app, NAMESPACE_REGISTRY[moduleNamespace]), - ); - APP_MODULE_INSTANCE[_app.name][moduleNamespace] = module; + if (!APP_MODULE_INSTANCE[_app.name]?.[moduleNamespace]) { + const moduleConfig = NAMESPACE_REGISTRY[moduleNamespace]; + if (!moduleConfig) { + throw new Error(`Module namespace '${moduleNamespace}' is not registered.`); + } + const module = createDeprecationProxy(new ModuleClass(_app, moduleConfig)); + APP_MODULE_INSTANCE[_app.name]![moduleNamespace] = module; } - return APP_MODULE_INSTANCE[_app.name][moduleNamespace]; + return APP_MODULE_INSTANCE[_app.name]![moduleNamespace]; } Object.assign(firebaseModuleWithApp, statics || {}); @@ -227,7 +244,7 @@ function firebaseRootModuleProxy(_firebaseNamespace: any, moduleNamespace: strin */ export function firebaseAppModuleProxy(app: FirebaseApp, moduleNamespace: string): any { if (NAMESPACE_REGISTRY[moduleNamespace]) { - app._checkDestroyed(); + (app as any)._checkDestroyed(); return getOrCreateModuleForApp(app, moduleNamespace as KnownNamespace); } @@ -265,10 +282,12 @@ export function createFirebaseRoot(): any { for (let i = 0; i < KNOWN_NAMESPACES.length; i++) { const namespace = KNOWN_NAMESPACES[i]; - Object.defineProperty(FIREBASE_ROOT, namespace, { - enumerable: false, - get: firebaseRootModuleProxy.bind(null, FIREBASE_ROOT, namespace), - }); + if (namespace) { + Object.defineProperty(FIREBASE_ROOT, namespace, { + enumerable: false, + get: firebaseRootModuleProxy.bind(null, FIREBASE_ROOT, namespace), + }); + } } return FIREBASE_ROOT; diff --git a/packages/app/lib/internal/registry/nativeModule.ts b/packages/app/lib/internal/registry/nativeModule.ts index ae16780564..90593c425e 100644 --- a/packages/app/lib/internal/registry/nativeModule.ts +++ b/packages/app/lib/internal/registry/nativeModule.ts @@ -82,6 +82,7 @@ function nativeModuleWrapped(namespace: string, NativeModule: any, argToPrepend: for (let i = 0, len = properties.length; i < len; i++) { const property = properties[i]; + if (!property) continue; if (typeof NativeModule[property] === 'function') { native[property] = nativeModuleMethodWrapped(namespace, NativeModule[property], argToPrepend); } else { @@ -111,10 +112,17 @@ function initialiseNativeModule(module: FirebaseModule): any { } = config; const multiModuleRoot: Record = {}; const multiModule = Array.isArray(nativeModuleName); - const nativeModuleNames = multiModule ? nativeModuleName : [nativeModuleName]; + const nativeModuleNames = multiModule + ? nativeModuleName + : nativeModuleName + ? [nativeModuleName] + : []; for (let i = 0; i < nativeModuleNames.length; i++) { - const nativeModule = getReactNativeModule(nativeModuleNames[i]); + const moduleName = nativeModuleNames[i]; + if (!moduleName) continue; + + const nativeModule = getReactNativeModule(moduleName); // only error if there's a single native module // as multi modules can mean some are optional @@ -123,7 +131,7 @@ function initialiseNativeModule(module: FirebaseModule): any { } if (multiModule) { - multiModuleRoot[nativeModuleNames[i]] = !!nativeModule; + multiModuleRoot[moduleName] = !!nativeModule; } const argToPrepend: any[] = []; @@ -139,9 +147,12 @@ function initialiseNativeModule(module: FirebaseModule): any { Object.assign(multiModuleRoot, nativeModuleWrapped(namespace, nativeModule, argToPrepend)); } - if (nativeEvents && nativeEvents.length) { + if (nativeEvents && Array.isArray(nativeEvents) && nativeEvents.length) { for (let i = 0, len = nativeEvents.length; i < len; i++) { - subscribeToNativeModuleEvent(nativeEvents[i]); + const eventName = nativeEvents[i]; + if (eventName) { + subscribeToNativeModuleEvent(eventName); + } } } From 790161060e6f9de08f23b7a75e2738b9c9a599e4 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 2 Dec 2025 16:20:38 +0000 Subject: [PATCH 05/28] refactor: internal TS implementation --- packages/app/lib/common/unitTestUtils.ts | 4 +- packages/app/lib/internal.d.ts | 79 ++++++++++++++++++++- packages/app/lib/internal/FirebaseModule.ts | 7 +- packages/app/lib/internal/NativeModules.ts | 77 ++++++++++++++++++++ packages/app/lib/internal/asyncStorage.ts | 6 +- packages/app/lib/internal/index.ts | 1 + packages/app/lib/internal/nativeModule.d.ts | 5 +- packages/app/lib/internal/registry/app.ts | 42 +++++++---- packages/app/lib/types/index.ts | 1 + packages/app/lib/utils/UtilsStatics.ts | 9 ++- packages/app/lib/utils/index.ts | 15 ++-- 11 files changed, 215 insertions(+), 31 deletions(-) create mode 100644 packages/app/lib/internal/NativeModules.ts diff --git a/packages/app/lib/common/unitTestUtils.ts b/packages/app/lib/common/unitTestUtils.ts index 98e7f6437e..f2315f06f8 100644 --- a/packages/app/lib/common/unitTestUtils.ts +++ b/packages/app/lib/common/unitTestUtils.ts @@ -52,7 +52,9 @@ export const createCheckV9Deprecation = (moduleNames: string[]): CheckV9Deprecat consoleWarnSpy.mockRestore(); const consoleWarnSpy2 = jest.spyOn(console, 'warn').mockImplementation(warnMessage => { const message = createMessage(moduleName, methodNameKey, instanceName, uniqueMessage); - expect(warnMessage).toMatch(message); + if (message) { + expect(warnMessage).toMatch(message); + } }); nonModularFunction(); diff --git a/packages/app/lib/internal.d.ts b/packages/app/lib/internal.d.ts index 0531871988..e863511830 100644 --- a/packages/app/lib/internal.d.ts +++ b/packages/app/lib/internal.d.ts @@ -20,8 +20,40 @@ import type { ReactNativeFirebase } from './index'; declare module '@react-native-firebase/app/lib/internal' { import BaseFirebaseModule = ReactNativeFirebase.FirebaseModule; - export class FirebaseModule extends BaseFirebaseModule { - native: any; + + export interface ReactNativeFirebaseNativeModules { + // Base interface - packages will augment this + } + + export interface RNFBUtilsModuleInterface { + isRunningInTestLab: boolean; + androidPlayServices: { + isAvailable: boolean; + status: number; + hasResolution: boolean; + isUserResolvableError: boolean; + error?: string; + }; + androidGetPlayServicesStatus(): Promise<{ + isAvailable: boolean; + status: number; + hasResolution: boolean; + isUserResolvableError: boolean; + error?: string; + }>; + androidPromptForPlayServices(): Promise; + androidMakePlayServicesAvailable(): Promise; + androidResolutionForPlayServices(): Promise; + } + + export interface RNFBAppModuleInterface { + FIREBASE_RAW_JSON: string; + } + + export class FirebaseModule< + NativeModuleName extends keyof ReactNativeFirebaseNativeModules = any, + > extends BaseFirebaseModule { + native: ReactNativeFirebaseNativeModules[NativeModuleName]; emitter: any; } export function createModuleNamespace(config: any): any; @@ -29,10 +61,53 @@ declare module '@react-native-firebase/app/lib/internal' { export class NativeFirebaseError { static getStackWithMessage(message: string, jsStack?: string): string; } + + export type GetNativeModule = + ReactNativeFirebaseNativeModules[T]; + export type AnyNativeModule = + ReactNativeFirebaseNativeModules[keyof ReactNativeFirebaseNativeModules]; +} + +declare module '@react-native-firebase/app/lib/internal/NativeModules' { + export interface ReactNativeFirebaseNativeModules { + RNFBUtilsModule: import('@react-native-firebase/app/lib/internal').RNFBUtilsModuleInterface; + RNFBAppModule: import('@react-native-firebase/app/lib/internal').RNFBAppModuleInterface; + } + + export interface RNFBUtilsModuleInterface { + isRunningInTestLab: boolean; + androidPlayServices: { + isAvailable: boolean; + status: number; + hasResolution: boolean; + isUserResolvableError: boolean; + error?: string; + }; + androidGetPlayServicesStatus(): Promise<{ + isAvailable: boolean; + status: number; + hasResolution: boolean; + isUserResolvableError: boolean; + error?: string; + }>; + androidPromptForPlayServices(): Promise; + androidMakePlayServicesAvailable(): Promise; + androidResolutionForPlayServices(): Promise; + } + + export interface RNFBAppModuleInterface { + FIREBASE_RAW_JSON: string; + } + + export type GetNativeModule = + ReactNativeFirebaseNativeModules[T]; + export type AnyNativeModule = + ReactNativeFirebaseNativeModules[keyof ReactNativeFirebaseNativeModules]; } declare module '@react-native-firebase/app/lib/internal/nativeModule' { export function setReactNativeModule(name: string, module: any): void; + export function getReactNativeModule(name: string): any; } declare module '@react-native-firebase/app/lib/common' { diff --git a/packages/app/lib/internal/FirebaseModule.ts b/packages/app/lib/internal/FirebaseModule.ts index bf29fbe67e..132ea00434 100644 --- a/packages/app/lib/internal/FirebaseModule.ts +++ b/packages/app/lib/internal/FirebaseModule.ts @@ -18,6 +18,7 @@ import { getAppModule, getNativeModule } from './registry/nativeModule'; import SharedEventEmitter from './SharedEventEmitter'; import type { ReactNativeFirebase } from '../types'; +import type { ReactNativeFirebaseNativeModules } from './NativeModules'; let firebaseJson: any = null; @@ -30,7 +31,9 @@ export interface ModuleConfig { disablePrependCustomUrlOrRegion?: boolean; } -export default class FirebaseModule { +export default class FirebaseModule< + NativeModuleName extends keyof ReactNativeFirebaseNativeModules = any, +> { _app: ReactNativeFirebase.FirebaseApp; _nativeModule: any; _customUrlOrRegion: string | null; @@ -67,7 +70,7 @@ export default class FirebaseModule { return `${this.app.name}-${args.join('-')}`; } - get native(): any { + get native(): ReactNativeFirebaseNativeModules[NativeModuleName] { if (this._nativeModule) { return this._nativeModule; } diff --git a/packages/app/lib/internal/NativeModules.ts b/packages/app/lib/internal/NativeModules.ts new file mode 100644 index 0000000000..6efe5c71c3 --- /dev/null +++ b/packages/app/lib/internal/NativeModules.ts @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * 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. + * + */ + +/** + * Base type for all React Native Firebase native modules. + * Each package can extend this interface via module augmentation to add their own native methods. + */ +export interface ReactNativeFirebaseNativeModules { + // Base interface - packages will augment this +} + +/** + * Utils Module native methods (from the app package) + */ +export interface RNFBUtilsModuleInterface { + // Android-only properties and methods + isRunningInTestLab: boolean; + androidPlayServices: { + isAvailable: boolean; + status: number; + hasResolution: boolean; + isUserResolvableError: boolean; + error?: string; + }; + androidGetPlayServicesStatus(): Promise<{ + isAvailable: boolean; + status: number; + hasResolution: boolean; + isUserResolvableError: boolean; + error?: string; + }>; + androidPromptForPlayServices(): Promise; + androidMakePlayServicesAvailable(): Promise; + androidResolutionForPlayServices(): Promise; +} + +/** + * Core app module native methods + */ +export interface RNFBAppModuleInterface { + FIREBASE_RAW_JSON: string; + // Add other app module methods here as needed +} + +// Augment the base interface with app package's native modules +declare module './NativeModules' { + interface ReactNativeFirebaseNativeModules { + RNFBUtilsModule: RNFBUtilsModuleInterface; + RNFBAppModule: RNFBAppModuleInterface; + } +} + +/** + * Helper type to get a specific native module type by name + */ +export type GetNativeModule = + ReactNativeFirebaseNativeModules[T]; + +/** + * Union type of all available native module types + */ +export type AnyNativeModule = + ReactNativeFirebaseNativeModules[keyof ReactNativeFirebaseNativeModules]; diff --git a/packages/app/lib/internal/asyncStorage.ts b/packages/app/lib/internal/asyncStorage.ts index 076074274f..ed2cc89591 100644 --- a/packages/app/lib/internal/asyncStorage.ts +++ b/packages/app/lib/internal/asyncStorage.ts @@ -17,9 +17,9 @@ // AsyncStorage interface compatible with React Native AsyncStorage export interface AsyncStorageStatic { - setItem(key: string, value: string): Promise; - getItem(key: string): Promise; - removeItem(key: string): Promise; + setItem: (key: string, value: string) => Promise; + getItem: (key: string) => Promise; + removeItem: (key: string) => Promise; } // Memory storage Map instance diff --git a/packages/app/lib/internal/index.ts b/packages/app/lib/internal/index.ts index 120bea13b6..4774df70c0 100644 --- a/packages/app/lib/internal/index.ts +++ b/packages/app/lib/internal/index.ts @@ -19,6 +19,7 @@ export { default as FirebaseApp } from '../FirebaseApp'; export * from './constants'; export { default as FirebaseModule } from './FirebaseModule'; export { default as NativeFirebaseError } from './NativeFirebaseError'; +export * from './NativeModules'; export * from './registry/app'; export * from './registry/namespace'; export * from './registry/nativeModule'; diff --git a/packages/app/lib/internal/nativeModule.d.ts b/packages/app/lib/internal/nativeModule.d.ts index b901315eb0..87889396f6 100644 --- a/packages/app/lib/internal/nativeModule.d.ts +++ b/packages/app/lib/internal/nativeModule.d.ts @@ -1,2 +1,3 @@ -export declare function getReactNativeModule(moduleName: string): any; -export declare function setReactNativeModule(moduleName: string, module: any): any; +export { getReactNativeModule } from './nativeModuleWeb'; +export { setReactNativeModule } from './nativeModuleWeb'; +//# sourceMappingURL=nativeModule.d.ts.map \ No newline at end of file diff --git a/packages/app/lib/internal/registry/app.ts b/packages/app/lib/internal/registry/app.ts index f020cc7b9f..498c025b01 100644 --- a/packages/app/lib/internal/registry/app.ts +++ b/packages/app/lib/internal/registry/app.ts @@ -24,13 +24,17 @@ import { isFunction, isString, isUndefined, -} from '@react-native-firebase/app/lib/common'; +} from '../../common'; import FirebaseApp from '../../FirebaseApp'; import { DEFAULT_APP_NAME } from '../constants'; import { setReactNativeAsyncStorageInternal } from '../asyncStorage'; import { getAppModule } from './nativeModule'; import { setLogLevelInternal } from '../logger'; -import { FirebaseAppConfig, FirebaseAppOptions, ReactNativeAsyncStorage } from '../../types'; +import type { ReactNativeFirebase } from '../../types'; + +type FirebaseAppConfig = ReactNativeFirebase.FirebaseAppConfig; +type FirebaseAppOptions = ReactNativeFirebase.FirebaseAppOptions; +type ReactNativeAsyncStorage = ReactNativeFirebase.ReactNativeAsyncStorage; const APP_REGISTRY: Record = {}; let onAppCreateFn: ((app: FirebaseApp) => void) | null = null; @@ -117,7 +121,7 @@ export function getApps(_deprecationArg?: any): FirebaseApp[] { * @param configOrName */ export function initializeApp( - options: FirebaseAppOptions = {}, + options: Partial = {}, configOrName?: string | FirebaseAppConfig, _deprecationArg?: any, ): Promise { @@ -184,7 +188,12 @@ export function initializeApp( ); } - const app = new FirebaseApp(options, appConfig, false, deleteApp.bind(null, name, true)); + const app = new FirebaseApp( + options as FirebaseAppOptions, + appConfig, + false, + deleteApp.bind(null, name, true), + ); // Note these initialization actions with side effects are performed prior to knowledge of // successful initialization in the native code. Native code *may* throw an error. @@ -192,12 +201,12 @@ export function initializeApp( onAppCreateFn?.(APP_REGISTRY[name]); return getAppModule() - .initializeApp(options, appConfig) + .initializeApp(options as FirebaseAppOptions, appConfig) .then(() => { app._initialized = true; return app; }) - .catch(e => { + .catch((e: any) => { // we need to clean the app entry from registry as the app does not actually exist // There are still possible side effects from `onAppCreateFn` to consider but as existing // code may rely on that function running prior to native create, re-ordering it is a semantic change @@ -209,7 +218,10 @@ export function initializeApp( }); } -export function setLogLevel(logLevel: string, _deprecationArg?: any): void { +export function setLogLevel( + logLevel: ReactNativeFirebase.LogLevelString, + _deprecationArg?: any, +): void { warnIfNotModularCall(arguments, 'setLogLevel()'); if (!['error', 'warn', 'info', 'debug', 'verbose'].includes(logLevel)) { throw new Error('LogLevel must be one of "error", "warn", "info", "debug", "verbose"'); @@ -222,7 +234,10 @@ export function setLogLevel(logLevel: string, _deprecationArg?: any): void { } } -export function setReactNativeAsyncStorage(asyncStorage: ReactNativeAsyncStorage, _deprecationArg?: any): void { +export function setReactNativeAsyncStorage( + asyncStorage: ReactNativeAsyncStorage, + _deprecationArg?: any, +): void { warnIfNotModularCall(arguments, 'setReactNativeAsyncStorage()'); if (!isObject(asyncStorage)) { @@ -241,13 +256,17 @@ export function setReactNativeAsyncStorage(asyncStorage: ReactNativeAsyncStorage throw new Error("setReactNativeAsyncStorage(*) 'asyncStorage.removeItem' must be a function."); } - setReactNativeAsyncStorageInternal(asyncStorage); + setReactNativeAsyncStorageInternal(asyncStorage as any); } /** * */ -export function deleteApp(name: string, nativeInitialized: boolean, _deprecationArg?: any): Promise { +export function deleteApp( + name: string, + nativeInitialized: boolean, + _deprecationArg?: any, +): Promise { if (name === DEFAULT_APP_NAME && nativeInitialized) { return Promise.reject(new Error('Unable to delete the default native firebase app instance.')); } @@ -261,9 +280,8 @@ export function deleteApp(name: string, nativeInitialized: boolean, _deprecation const nativeModule = getAppModule(); return nativeModule.deleteApp(name).then(() => { - app._deleted = true; + (app as any)._deleted = true; onAppDestroyFn?.(app); delete APP_REGISTRY[name]; }); } - diff --git a/packages/app/lib/types/index.ts b/packages/app/lib/types/index.ts index b9f1c58eb4..f9fba00eee 100644 --- a/packages/app/lib/types/index.ts +++ b/packages/app/lib/types/index.ts @@ -413,6 +413,7 @@ export namespace Utils { } export interface Statics { + SDK_VERSION: string; FilePath: FilePath; } diff --git a/packages/app/lib/utils/UtilsStatics.ts b/packages/app/lib/utils/UtilsStatics.ts index 92061bef25..7bd5c98bda 100644 --- a/packages/app/lib/utils/UtilsStatics.ts +++ b/packages/app/lib/utils/UtilsStatics.ts @@ -45,12 +45,16 @@ function processPathConstants(nativeModule: any): Utils.FilePath { for (let i = 0; i < PATH_NAMES.length; i++) { const path = PATH_NAMES[i]; - (paths as any)[path] = nativeModule[path] ? stripTrailingSlash(nativeModule[path]) : null; + if (path) { + (paths as any)[path] = nativeModule[path] ? stripTrailingSlash(nativeModule[path]) : null; + } } for (let i = 0; i < PATH_FILE_TYPES.length; i++) { const pathFileType = PATH_FILE_TYPES[i]; - (paths as any)[pathFileType] = stripTrailingSlash(nativeModule[pathFileType]); + if (pathFileType) { + (paths as any)[pathFileType] = stripTrailingSlash(nativeModule[pathFileType]); + } } Object.freeze(paths); @@ -67,4 +71,3 @@ const statics: Utils.Statics = { }; export default statics; - diff --git a/packages/app/lib/utils/index.ts b/packages/app/lib/utils/index.ts index 68d2ae5452..78242987b9 100644 --- a/packages/app/lib/utils/index.ts +++ b/packages/app/lib/utils/index.ts @@ -15,18 +15,16 @@ * */ -import { isIOS } from '../../lib/common'; -// @ts-expect-error -import { createModuleNamespace, FirebaseModule } from '../../lib/internal'; +import { isIOS } from '../common'; +import { createModuleNamespace, FirebaseModule } from '../internal'; import UtilsStatics from './UtilsStatics'; import { Utils } from '../types'; -import FirebaseApp from '../FirebaseApp'; const namespace = 'utils'; const statics = UtilsStatics; const nativeModuleName = 'RNFBUtilsModule'; -class FirebaseUtilsModule extends FirebaseModule implements Utils.Module { +class FirebaseUtilsModule extends FirebaseModule<'RNFBUtilsModule'> { get isRunningInTestLab(): boolean { if (isIOS) { return false; @@ -39,6 +37,9 @@ class FirebaseUtilsModule extends FirebaseModule implements Utils.Module { return { isAvailable: true, status: 0, + hasResolution: false, + isUserResolvableError: false, + error: undefined, }; } return this.native.androidPlayServices; @@ -49,6 +50,9 @@ class FirebaseUtilsModule extends FirebaseModule implements Utils.Module { return Promise.resolve({ isAvailable: true, status: 0, + hasResolution: false, + isUserResolvableError: false, + error: undefined, }); } return this.native.androidGetPlayServicesStatus(); @@ -88,4 +92,3 @@ export default createModuleNamespace({ hasCustomUrlOrRegionSupport: false, ModuleClass: FirebaseUtilsModule, }); - From 0787c4a2a87c543aa9d53330ef4fd4e4e701599e Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 2 Dec 2025 16:33:30 +0000 Subject: [PATCH 06/28] fix: script to get version for generating versions natively --- packages/app/scripts/genversion-android.js | 10 +++++++++- packages/app/scripts/genversion-ios.js | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/app/scripts/genversion-android.js b/packages/app/scripts/genversion-android.js index ccda19ed0a..12af85b744 100644 --- a/packages/app/scripts/genversion-android.js +++ b/packages/app/scripts/genversion-android.js @@ -1,7 +1,15 @@ const fs = require('fs'); const path = require('path'); -const version = require('../lib/version'); +// Read version from TypeScript file +const versionFilePath = path.resolve(__dirname, '..', 'lib', 'version.ts'); +const versionFileContent = fs.readFileSync(versionFilePath, 'utf8'); +const versionMatch = versionFileContent.match(/version = ['"](.+?)['"]/); +if (!versionMatch) { + throw new Error('Could not extract version from version.ts'); +} +const version = versionMatch[1]; + const outputPath = path.resolve( __dirname, '..', diff --git a/packages/app/scripts/genversion-ios.js b/packages/app/scripts/genversion-ios.js index 6292b7f264..049c6cb73e 100644 --- a/packages/app/scripts/genversion-ios.js +++ b/packages/app/scripts/genversion-ios.js @@ -1,7 +1,15 @@ const fs = require('fs'); const path = require('path'); -const version = require('../lib/version'); +// Read version from TypeScript file +const versionFilePath = path.resolve(__dirname, '..', 'lib', 'version.ts'); +const versionFileContent = fs.readFileSync(versionFilePath, 'utf8'); +const versionMatch = versionFileContent.match(/version = ['"](.+?)['"]/); +if (!versionMatch) { + throw new Error('Could not extract version from version.ts'); +} +const version = versionMatch[1]; + const outputPath = path.resolve(__dirname, '..', 'ios', 'RNFBApp', 'RNFBVersion.m'); const template = `/** * Copyright (c) 2016-present Invertase Limited & Contributors From 066f2d508300f1e85284a4769bb738bd68b5fadd Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 2 Dec 2025 16:54:11 +0000 Subject: [PATCH 07/28] yarn.lock --- yarn.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/yarn.lock b/yarn.lock index e8ce6b856e..82accd1114 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6053,6 +6053,7 @@ __metadata: "@react-native-async-storage/async-storage": "npm:^2.2.0" expo: "npm:^54.0.25" firebase: "npm:12.6.0" + react-native-builder-bob: "npm:^0.40.13" peerDependencies: expo: ">=47.0.0" react: "*" From 7bd77050d89a84f42703a81162a7cce44b483c18 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 2 Dec 2025 16:54:25 +0000 Subject: [PATCH 08/28] fix: keep internal types internal --- packages/app/lib/{internal.d.ts => internal-types.d.ts} | 8 ++++---- packages/app/lib/internal/NativeModules.ts | 4 ++-- packages/app/package.json | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) rename packages/app/lib/{internal.d.ts => internal-types.d.ts} (97%) diff --git a/packages/app/lib/internal.d.ts b/packages/app/lib/internal-types.d.ts similarity index 97% rename from packages/app/lib/internal.d.ts rename to packages/app/lib/internal-types.d.ts index e863511830..b363b389af 100644 --- a/packages/app/lib/internal.d.ts +++ b/packages/app/lib/internal-types.d.ts @@ -32,14 +32,14 @@ declare module '@react-native-firebase/app/lib/internal' { status: number; hasResolution: boolean; isUserResolvableError: boolean; - error?: string; + error: string | undefined; }; androidGetPlayServicesStatus(): Promise<{ isAvailable: boolean; status: number; hasResolution: boolean; isUserResolvableError: boolean; - error?: string; + error: string | undefined; }>; androidPromptForPlayServices(): Promise; androidMakePlayServicesAvailable(): Promise; @@ -81,14 +81,14 @@ declare module '@react-native-firebase/app/lib/internal/NativeModules' { status: number; hasResolution: boolean; isUserResolvableError: boolean; - error?: string; + error: string | undefined; }; androidGetPlayServicesStatus(): Promise<{ isAvailable: boolean; status: number; hasResolution: boolean; isUserResolvableError: boolean; - error?: string; + error: string | undefined; }>; androidPromptForPlayServices(): Promise; androidMakePlayServicesAvailable(): Promise; diff --git a/packages/app/lib/internal/NativeModules.ts b/packages/app/lib/internal/NativeModules.ts index 6efe5c71c3..b3a150b470 100644 --- a/packages/app/lib/internal/NativeModules.ts +++ b/packages/app/lib/internal/NativeModules.ts @@ -34,14 +34,14 @@ export interface RNFBUtilsModuleInterface { status: number; hasResolution: boolean; isUserResolvableError: boolean; - error?: string; + error: string | undefined; }; androidGetPlayServicesStatus(): Promise<{ isAvailable: boolean; status: number; hasResolution: boolean; isUserResolvableError: boolean; - error?: string; + error: string | undefined; }>; androidPromptForPlayServices(): Promise; androidMakePlayServicesAvailable(): Promise; diff --git a/packages/app/package.json b/packages/app/package.json index e4e6ebf049..817afa8551 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -118,6 +118,7 @@ "react-native-builder-bob": { "source": "lib", "output": "dist", + "exclude": "**/*.d.ts", "targets": [ [ "module", From f9c9dc62248dbd17c7a6a0e8352ecba0393a0281 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 3 Dec 2025 13:04:43 +0000 Subject: [PATCH 09/28] fix: other package TS config causing TS to output in app lib/ directory --- packages/ai/tsconfig.json | 12 ++++++------ packages/analytics/lib/namespaced.ts | 2 +- packages/analytics/tsconfig.json | 4 ++-- packages/vertexai/tsconfig.json | 6 ++---- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/ai/tsconfig.json b/packages/ai/tsconfig.json index f1d9865812..b8f113d3bf 100644 --- a/packages/ai/tsconfig.json +++ b/packages/ai/tsconfig.json @@ -6,9 +6,7 @@ "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "jsx": "react-jsx", - "lib": [ - "ESNext" - ], + "lib": ["ESNext"], "module": "ESNext", "target": "ESNext", "moduleResolution": "Bundler", @@ -24,9 +22,11 @@ "strict": true, "baseUrl": ".", "paths": { - "@react-native-firebase/app": ["../app/lib"], + "@react-native-firebase/app": ["../app/dist/typescript/commonjs/lib"], "@react-native-firebase/auth": ["../auth/lib"], - "@react-native-firebase/app-check": ["../app-check/lib"], + "@react-native-firebase/app-check": ["../app-check/lib"] } - } + }, + "include": ["lib/**/*"], + "exclude": ["node_modules", "dist", "__tests__", "**/*.test.ts"] } diff --git a/packages/analytics/lib/namespaced.ts b/packages/analytics/lib/namespaced.ts index b95bcc1fbc..c54ee23771 100644 --- a/packages/analytics/lib/namespaced.ts +++ b/packages/analytics/lib/namespaced.ts @@ -14,7 +14,7 @@ * limitations under the License. * */ -import { ReactNativeFirebase } from '@react-native-firebase/app'; +import type { ReactNativeFirebase } from '@react-native-firebase/app'; import { isAlphaNumericUnderscore, isE164PhoneNumber, diff --git a/packages/analytics/tsconfig.json b/packages/analytics/tsconfig.json index 8e4d4e23c0..6bf8f06222 100644 --- a/packages/analytics/tsconfig.json +++ b/packages/analytics/tsconfig.json @@ -23,8 +23,8 @@ "baseUrl": ".", "rootDir": ".", "paths": { - "@react-native-firebase/app/common/*": ["../app/lib/common/*"], - "@react-native-firebase/app": ["../app/lib"] + "@react-native-firebase/app/common/*": ["../app/dist/typescript/commonjs/lib/common/*"], + "@react-native-firebase/app": ["../app/dist/typescript/commonjs/lib"] } }, "include": ["lib/**/*"], diff --git a/packages/vertexai/tsconfig.json b/packages/vertexai/tsconfig.json index f371c6edc9..59193c9ff4 100644 --- a/packages/vertexai/tsconfig.json +++ b/packages/vertexai/tsconfig.json @@ -6,9 +6,7 @@ "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "jsx": "react-jsx", - "lib": [ - "ESNext" - ], + "lib": ["ESNext"], "module": "ESNext", "target": "ESNext", "moduleResolution": "Bundler", @@ -24,7 +22,7 @@ "strict": true, "baseUrl": ".", "paths": { - "@react-native-firebase/app": ["../app/lib"], + "@react-native-firebase/app": ["../app/dist/typescript/commonjs/lib"], "@react-native-firebase/auth": ["../auth/lib"], "@react-native-firebase/app-check": ["../app-check/lib"] } From 6ec38715c9727dcfb7999f460380408a773a931a Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 3 Dec 2025 13:44:27 +0000 Subject: [PATCH 10/28] fix: use types for app native module --- packages/app/lib/internal/NativeModules.ts | 41 +++++++++++++++---- packages/app/lib/internal/registry/app.ts | 8 ++-- .../app/lib/internal/registry/nativeModule.ts | 19 +++++---- 3 files changed, 50 insertions(+), 18 deletions(-) diff --git a/packages/app/lib/internal/NativeModules.ts b/packages/app/lib/internal/NativeModules.ts index b3a150b470..f5dd65820a 100644 --- a/packages/app/lib/internal/NativeModules.ts +++ b/packages/app/lib/internal/NativeModules.ts @@ -19,10 +19,43 @@ * Base type for all React Native Firebase native modules. * Each package can extend this interface via module augmentation to add their own native methods. */ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface ReactNativeFirebaseNativeModules { // Base interface - packages will augment this } +/** + * Interface for wrapped native modules returned by getAppModule() and getNativeModule() + * This represents the native module after wrapping with error handling + */ +export interface WrappedNativeModule { + [key: string]: any; +} + +/** + * App Module native methods that are always available + */ +export interface RNFBAppModuleInterface { + // Constants + NATIVE_FIREBASE_APPS: Array<{ + appConfig: { name: string; [key: string]: any }; + options: { [key: string]: any }; + }>; + FIREBASE_RAW_JSON: string; + + // Methods + initializeApp(options: any, appConfig: any): Promise; + deleteApp(name: string): Promise; + setLogLevel(logLevel: string): void; + metaGetAll(): Promise<{ [key: string]: string | boolean }>; + jsonGetAll(): Promise<{ [key: string]: string | boolean }>; + preferencesClearAll(): Promise; + preferencesGetAll(): Promise<{ [key: string]: string | boolean }>; + preferencesSetBool(key: string, value: boolean): Promise; + preferencesSetString(key: string, value: string): Promise; + setAutomaticDataCollectionEnabled(name: string, enabled: boolean): void; +} + /** * Utils Module native methods (from the app package) */ @@ -48,14 +81,6 @@ export interface RNFBUtilsModuleInterface { androidResolutionForPlayServices(): Promise; } -/** - * Core app module native methods - */ -export interface RNFBAppModuleInterface { - FIREBASE_RAW_JSON: string; - // Add other app module methods here as needed -} - // Augment the base interface with app package's native modules declare module './NativeModules' { interface ReactNativeFirebaseNativeModules { diff --git a/packages/app/lib/internal/registry/app.ts b/packages/app/lib/internal/registry/app.ts index 498c025b01..dd9f470f8a 100644 --- a/packages/app/lib/internal/registry/app.ts +++ b/packages/app/lib/internal/registry/app.ts @@ -67,11 +67,13 @@ export function initializeNativeApps(): void { if (NATIVE_FIREBASE_APPS && NATIVE_FIREBASE_APPS.length) { for (let i = 0; i < NATIVE_FIREBASE_APPS.length; i++) { - const { appConfig, options } = NATIVE_FIREBASE_APPS[i]; + const nativeApp = NATIVE_FIREBASE_APPS[i]; + if (!nativeApp) continue; + const { appConfig, options } = nativeApp; const { name } = appConfig; APP_REGISTRY[name] = new FirebaseApp( - options, - appConfig, + options as FirebaseAppOptions, + appConfig as FirebaseAppConfig, true, deleteApp.bind(null, name, true), ); diff --git a/packages/app/lib/internal/registry/nativeModule.ts b/packages/app/lib/internal/registry/nativeModule.ts index 90593c425e..cc659db0fa 100644 --- a/packages/app/lib/internal/registry/nativeModule.ts +++ b/packages/app/lib/internal/registry/nativeModule.ts @@ -22,6 +22,7 @@ import SharedEventEmitter from '../SharedEventEmitter'; import { getReactNativeModule } from '../nativeModule'; import { isAndroid, isIOS } from '../../common'; import FirebaseModule from '../FirebaseModule'; +import type { WrappedNativeModule, RNFBAppModuleInterface } from '../NativeModules'; interface NativeEvent { appName?: string; @@ -29,7 +30,7 @@ interface NativeEvent { [key: string]: any; } -const NATIVE_MODULE_REGISTRY: Record = {}; +const NATIVE_MODULE_REGISTRY: Record = {}; const NATIVE_MODULE_EVENT_SUBSCRIPTIONS: Record = {}; function nativeModuleKey(module: FirebaseModule): string { @@ -71,7 +72,11 @@ function nativeModuleMethodWrapped( * @param NativeModule * @param argToPrepend */ -function nativeModuleWrapped(namespace: string, NativeModule: any, argToPrepend: any[]): any { +function nativeModuleWrapped( + namespace: string, + NativeModule: any, + argToPrepend: any[], +): WrappedNativeModule { const native: Record = {}; if (!NativeModule) { return NativeModule; @@ -99,7 +104,7 @@ function nativeModuleWrapped(namespace: string, NativeModule: any, argToPrepend: * @param module * @returns {*} */ -function initialiseNativeModule(module: FirebaseModule): any { +function initialiseNativeModule(module: FirebaseModule): WrappedNativeModule { const config = module._config; const key = nativeModuleKey(module); const { @@ -222,7 +227,7 @@ function getMissingModuleHelpText(namespace: string): string { * @param module * @returns {*} */ -export function getNativeModule(module: FirebaseModule): any { +export function getNativeModule(module: FirebaseModule): WrappedNativeModule { const key = nativeModuleKey(module); if (NATIVE_MODULE_REGISTRY[key]) { @@ -237,9 +242,9 @@ export function getNativeModule(module: FirebaseModule): any { * * @returns {*} */ -export function getAppModule(): any { +export function getAppModule(): RNFBAppModuleInterface { if (NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE]) { - return NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE]; + return NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE] as RNFBAppModuleInterface; } const namespace = 'app'; @@ -251,5 +256,5 @@ export function getAppModule(): any { NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE] = nativeModuleWrapped(namespace, nativeModule, []); - return NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE]; + return NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE] as RNFBAppModuleInterface; } From 6ffeb5e9887bac39f5b6e4283dfb93c0e290eece Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 3 Dec 2025 14:04:00 +0000 Subject: [PATCH 11/28] fix: FirebaseModule types --- packages/app/lib/internal/FirebaseModule.ts | 23 +++++++++++++------ .../app/lib/internal/registry/nativeModule.ts | 2 +- packages/app/lib/types/index.ts | 6 +++-- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/packages/app/lib/internal/FirebaseModule.ts b/packages/app/lib/internal/FirebaseModule.ts index 132ea00434..f61d127009 100644 --- a/packages/app/lib/internal/FirebaseModule.ts +++ b/packages/app/lib/internal/FirebaseModule.ts @@ -19,8 +19,15 @@ import { getAppModule, getNativeModule } from './registry/nativeModule'; import SharedEventEmitter from './SharedEventEmitter'; import type { ReactNativeFirebase } from '../types'; import type { ReactNativeFirebaseNativeModules } from './NativeModules'; +import type EventEmitter from 'react-native/Libraries/vendor/emitter/EventEmitter'; -let firebaseJson: any = null; +/** + * Firebase JSON configuration from firebase.json file + * Structure: { "react-native": { [key: string]: boolean | string }, ... } + */ +export type FirebaseJsonConfig = Record; + +let firebaseJson: FirebaseJsonConfig | null = null; export interface ModuleConfig { namespace: string; @@ -35,7 +42,7 @@ export default class FirebaseModule< NativeModuleName extends keyof ReactNativeFirebaseNativeModules = any, > { _app: ReactNativeFirebase.FirebaseApp; - _nativeModule: any; + _nativeModule: ReactNativeFirebaseNativeModules[NativeModuleName] | null; _customUrlOrRegion: string | null; _config: ModuleConfig; @@ -54,19 +61,19 @@ export default class FirebaseModule< return this._app; } - get firebaseJson(): any { + get firebaseJson(): FirebaseJsonConfig { if (firebaseJson) { return firebaseJson; } firebaseJson = JSON.parse(getAppModule().FIREBASE_RAW_JSON); - return firebaseJson; + return firebaseJson as FirebaseJsonConfig; } - get emitter(): any { + get emitter(): EventEmitter { return SharedEventEmitter; } - eventNameForApp(...args: any[]): string { + eventNameForApp(...args: Array): string { return `${this.app.name}-${args.join('-')}`; } @@ -74,7 +81,9 @@ export default class FirebaseModule< if (this._nativeModule) { return this._nativeModule; } - this._nativeModule = getNativeModule(this); + this._nativeModule = getNativeModule( + this, + ) as ReactNativeFirebaseNativeModules[NativeModuleName]; return this._nativeModule; } } diff --git a/packages/app/lib/internal/registry/nativeModule.ts b/packages/app/lib/internal/registry/nativeModule.ts index cc659db0fa..409ccf0f61 100644 --- a/packages/app/lib/internal/registry/nativeModule.ts +++ b/packages/app/lib/internal/registry/nativeModule.ts @@ -115,7 +115,7 @@ function initialiseNativeModule(module: FirebaseModule): WrappedNativeModule { hasCustomUrlOrRegionSupport, disablePrependCustomUrlOrRegion, } = config; - const multiModuleRoot: Record = {}; + const multiModuleRoot: WrappedNativeModule = {}; const multiModule = Array.isArray(nativeModuleName); const nativeModuleNames = multiModule ? nativeModuleName diff --git a/packages/app/lib/types/index.ts b/packages/app/lib/types/index.ts index f9fba00eee..e6f11b3519 100644 --- a/packages/app/lib/types/index.ts +++ b/packages/app/lib/types/index.ts @@ -15,6 +15,8 @@ * */ +import type { WrappedNativeModule } from '../internal/NativeModules'; + /** * Core React Native Firebase package types. * @@ -600,8 +602,8 @@ export namespace Utils { export namespace Internal { export declare function createModuleNamespace(config: any): any; export interface FirebaseModule { - native: any; - firebaseJson: any; + native: WrappedNativeModule; + firebaseJson: Record; _customUrlOrRegion: string | null; } export declare function getFirebaseRoot(): any; From dbbf9af0ab05f5553671b1b8973bcd67463c688f Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 3 Dec 2025 14:06:16 +0000 Subject: [PATCH 12/28] fix: FirebaseApp types --- packages/app/lib/FirebaseApp.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/app/lib/FirebaseApp.ts b/packages/app/lib/FirebaseApp.ts index 42d7769adb..1a208e4afa 100644 --- a/packages/app/lib/FirebaseApp.ts +++ b/packages/app/lib/FirebaseApp.ts @@ -16,7 +16,7 @@ */ import { warnIfNotModularCall } from './common'; import { getAppModule } from './internal/registry/nativeModule'; -import type { ReactNativeFirebase } from './types'; +import type { ReactNativeFirebase, Utils } from './types'; export default class FirebaseApp implements ReactNativeFirebase.FirebaseApp { private _name: string; @@ -74,7 +74,7 @@ export default class FirebaseApp implements ReactNativeFirebase.FirebaseApp { } } - extendApp(extendedProps: Record): void { + extendApp(extendedProps: Record): void { warnIfNotModularCall(arguments); this._checkDestroyed(); Object.assign(this, extendedProps); @@ -92,7 +92,7 @@ export default class FirebaseApp implements ReactNativeFirebase.FirebaseApp { } // For backward compatibility - utils method added by registry - utils(): any { + utils(): Utils.Module { throw new Error('utils() should be added by registry'); } } From 0126b84f81a0479a1aaea2638833172bcb199e2c Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 3 Dec 2025 14:09:05 +0000 Subject: [PATCH 13/28] fix: NativeModules types --- packages/app/lib/internal/FirebaseModule.ts | 2 +- packages/app/lib/internal/NativeModules.ts | 13 +++++++++---- packages/app/lib/internal/registry/nativeModule.ts | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/app/lib/internal/FirebaseModule.ts b/packages/app/lib/internal/FirebaseModule.ts index f61d127009..e8ff766872 100644 --- a/packages/app/lib/internal/FirebaseModule.ts +++ b/packages/app/lib/internal/FirebaseModule.ts @@ -83,7 +83,7 @@ export default class FirebaseModule< } this._nativeModule = getNativeModule( this, - ) as ReactNativeFirebaseNativeModules[NativeModuleName]; + ) as unknown as ReactNativeFirebaseNativeModules[NativeModuleName]; return this._nativeModule; } } diff --git a/packages/app/lib/internal/NativeModules.ts b/packages/app/lib/internal/NativeModules.ts index f5dd65820a..e6833fc491 100644 --- a/packages/app/lib/internal/NativeModules.ts +++ b/packages/app/lib/internal/NativeModules.ts @@ -15,6 +15,8 @@ * */ +import type { ReactNativeFirebase } from '../types'; + /** * Base type for all React Native Firebase native modules. * Each package can extend this interface via module augmentation to add their own native methods. @@ -29,7 +31,7 @@ export interface ReactNativeFirebaseNativeModules { * This represents the native module after wrapping with error handling */ export interface WrappedNativeModule { - [key: string]: any; + [key: string]: unknown; } /** @@ -38,13 +40,16 @@ export interface WrappedNativeModule { export interface RNFBAppModuleInterface { // Constants NATIVE_FIREBASE_APPS: Array<{ - appConfig: { name: string; [key: string]: any }; - options: { [key: string]: any }; + appConfig: ReactNativeFirebase.FirebaseAppConfig; + options: ReactNativeFirebase.FirebaseAppOptions; }>; FIREBASE_RAW_JSON: string; // Methods - initializeApp(options: any, appConfig: any): Promise; + initializeApp( + options: ReactNativeFirebase.FirebaseAppOptions, + appConfig: ReactNativeFirebase.FirebaseAppConfig, + ): Promise; deleteApp(name: string): Promise; setLogLevel(logLevel: string): void; metaGetAll(): Promise<{ [key: string]: string | boolean }>; diff --git a/packages/app/lib/internal/registry/nativeModule.ts b/packages/app/lib/internal/registry/nativeModule.ts index 409ccf0f61..bc8c49375b 100644 --- a/packages/app/lib/internal/registry/nativeModule.ts +++ b/packages/app/lib/internal/registry/nativeModule.ts @@ -244,7 +244,7 @@ export function getNativeModule(module: FirebaseModule): WrappedNativeModule { */ export function getAppModule(): RNFBAppModuleInterface { if (NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE]) { - return NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE] as RNFBAppModuleInterface; + return NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE] as unknown as RNFBAppModuleInterface; } const namespace = 'app'; @@ -256,5 +256,5 @@ export function getAppModule(): RNFBAppModuleInterface { NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE] = nativeModuleWrapped(namespace, nativeModule, []); - return NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE] as RNFBAppModuleInterface; + return NATIVE_MODULE_REGISTRY[APP_NATIVE_MODULE] as unknown as RNFBAppModuleInterface; } From d5b7c3f4f5fa3c04ad60de35dfcd6f54d31e8dee Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 3 Dec 2025 14:18:30 +0000 Subject: [PATCH 14/28] fix: registry/nativeModule types --- packages/app/lib/internal/registry/app.ts | 2 +- .../app/lib/internal/registry/nativeModule.ts | 50 +++++++++++-------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/packages/app/lib/internal/registry/app.ts b/packages/app/lib/internal/registry/app.ts index dd9f470f8a..97f4b4aa33 100644 --- a/packages/app/lib/internal/registry/app.ts +++ b/packages/app/lib/internal/registry/app.ts @@ -70,7 +70,7 @@ export function initializeNativeApps(): void { const nativeApp = NATIVE_FIREBASE_APPS[i]; if (!nativeApp) continue; const { appConfig, options } = nativeApp; - const { name } = appConfig; + const name = appConfig.name as string; APP_REGISTRY[name] = new FirebaseApp( options as FirebaseAppOptions, appConfig as FirebaseAppConfig, diff --git a/packages/app/lib/internal/registry/nativeModule.ts b/packages/app/lib/internal/registry/nativeModule.ts index bc8c49375b..54a1c776b7 100644 --- a/packages/app/lib/internal/registry/nativeModule.ts +++ b/packages/app/lib/internal/registry/nativeModule.ts @@ -16,7 +16,7 @@ */ import { APP_NATIVE_MODULE } from '../constants'; -import NativeFirebaseError from '../NativeFirebaseError'; +import NativeFirebaseError, { type NativeError } from '../NativeFirebaseError'; import RNFBNativeEventEmitter from '../RNFBNativeEventEmitter'; import SharedEventEmitter from '../SharedEventEmitter'; import { getReactNativeModule } from '../nativeModule'; @@ -27,7 +27,7 @@ import type { WrappedNativeModule, RNFBAppModuleInterface } from '../NativeModul interface NativeEvent { appName?: string; databaseId?: string; - [key: string]: any; + [key: string]: unknown; } const NATIVE_MODULE_REGISTRY: Record = {}; @@ -48,15 +48,15 @@ function nativeModuleKey(module: FirebaseModule): string { */ function nativeModuleMethodWrapped( namespace: string, - method: (...args: any[]) => any, - argToPrepend: any[], -): (...args: any[]) => any { - return (...args: any[]) => { + method: (...args: unknown[]) => unknown, + argToPrepend: unknown[], +): (...args: unknown[]) => unknown { + return (...args: unknown[]) => { const possiblePromise = method(...[...argToPrepend, ...args]); - if (possiblePromise && possiblePromise.then) { + if (possiblePromise && typeof possiblePromise === 'object' && 'then' in possiblePromise) { const jsStack = new Error().stack; - return possiblePromise.catch((nativeError: any) => + return (possiblePromise as Promise).catch((nativeError: NativeError) => Promise.reject(new NativeFirebaseError(nativeError, jsStack as string, namespace)), ); } @@ -69,29 +69,39 @@ function nativeModuleMethodWrapped( * Prepends all arguments in prependArgs to all native method calls * * @param namespace - * @param NativeModule + * @param NativeModule - Raw native module from React Native (untyped) * @param argToPrepend */ function nativeModuleWrapped( namespace: string, - NativeModule: any, - argToPrepend: any[], + NativeModule: unknown, + argToPrepend: unknown[], ): WrappedNativeModule { - const native: Record = {}; + const native: Record = {}; if (!NativeModule) { - return NativeModule; + return NativeModule as WrappedNativeModule; } - let properties = Object.keys(Object.getPrototypeOf(NativeModule)); - if (!properties.length) properties = Object.keys(NativeModule); + // Type guard: ensure it's an object before accessing properties + if (typeof NativeModule !== 'object') { + return native; + } + + const nativeModuleObj = NativeModule as Record; + let properties = Object.keys(Object.getPrototypeOf(nativeModuleObj)); + if (!properties.length) properties = Object.keys(nativeModuleObj); for (let i = 0, len = properties.length; i < len; i++) { const property = properties[i]; if (!property) continue; - if (typeof NativeModule[property] === 'function') { - native[property] = nativeModuleMethodWrapped(namespace, NativeModule[property], argToPrepend); + if (typeof nativeModuleObj[property] === 'function') { + native[property] = nativeModuleMethodWrapped( + namespace, + nativeModuleObj[property] as (...args: unknown[]) => unknown, + argToPrepend, + ); } else { - native[property] = NativeModule[property]; + native[property] = nativeModuleObj[property]; } } @@ -139,14 +149,14 @@ function initialiseNativeModule(module: FirebaseModule): WrappedNativeModule { multiModuleRoot[moduleName] = !!nativeModule; } - const argToPrepend: any[] = []; + const argToPrepend: Array = []; if (hasMultiAppSupport) { argToPrepend.push(module.app.name); } if (hasCustomUrlOrRegionSupport && !disablePrependCustomUrlOrRegion) { - argToPrepend.push(module._customUrlOrRegion); + argToPrepend.push(module._customUrlOrRegion as string); } Object.assign(multiModuleRoot, nativeModuleWrapped(namespace, nativeModule, argToPrepend)); From 062218b3c0b47daaafa2d1b4869bfb8047ba9a2f Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 3 Dec 2025 15:00:54 +0000 Subject: [PATCH 15/28] fix: update namespace types --- .../app/lib/internal/registry/namespace.ts | 85 +++++++++++++------ 1 file changed, 60 insertions(+), 25 deletions(-) diff --git a/packages/app/lib/internal/registry/namespace.ts b/packages/app/lib/internal/registry/namespace.ts index 5997c29b10..9a7e19bcd9 100644 --- a/packages/app/lib/internal/registry/namespace.ts +++ b/packages/app/lib/internal/registry/namespace.ts @@ -30,22 +30,48 @@ import { setOnAppDestroy, } from './app'; +/** + * Type for a Firebase module getter function that can optionally accept + * a custom URL/region/databaseId parameter + */ +export type ModuleGetter = ((customUrlOrRegionOrDatabaseId?: string) => FirebaseModule) & { + [key: string]: unknown; +}; + +/** + * Type for Firebase root object with module getters + */ +export interface FirebaseRoot { + initializeApp: typeof initializeApp; + setReactNativeAsyncStorage: typeof setReactNativeAsyncStorage; + app: typeof getApp; + apps: FirebaseApp[]; + SDK_VERSION: string; + setLogLevel: typeof setLogLevel; + [key: string]: unknown; +} + interface NamespaceConfig extends ModuleConfig { nativeModuleName: string | string[]; nativeEvents: boolean | string[]; disablePrependCustomUrlOrRegion?: boolean; - ModuleClass: typeof FirebaseModule; - statics?: Record; + // ModuleClass can be FirebaseModule or any subclass of it + ModuleClass: new ( + app: FirebaseApp, + config: ModuleConfig, + customUrlOrRegion?: string | null, + ) => FirebaseModule; + statics?: object; version?: string; } // firebase.X -let FIREBASE_ROOT: any = null; +let FIREBASE_ROOT: FirebaseRoot | null = null; const NAMESPACE_REGISTRY: Record = {}; -const APP_MODULE_INSTANCE: Record> = {}; -const MODULE_GETTER_FOR_APP: Record> = {}; -const MODULE_GETTER_FOR_ROOT: Record = {}; +const APP_MODULE_INSTANCE: Record> = {}; +const MODULE_GETTER_FOR_APP: Record> = {}; +const MODULE_GETTER_FOR_ROOT: Record = {}; /** * Attaches module namespace getters on every newly created app. @@ -81,9 +107,9 @@ setOnAppDestroy(app => { * @param moduleNamespace * @returns {*} */ -function getOrCreateModuleForApp(app: FirebaseApp, moduleNamespace: KnownNamespace): any { +function getOrCreateModuleForApp(app: FirebaseApp, moduleNamespace: KnownNamespace): ModuleGetter { if (MODULE_GETTER_FOR_APP[app.name] && MODULE_GETTER_FOR_APP[app.name]?.[moduleNamespace]) { - return MODULE_GETTER_FOR_APP[app.name]![moduleNamespace]; + return MODULE_GETTER_FOR_APP[app.name]![moduleNamespace]!; } if (!MODULE_GETTER_FOR_APP[app.name]) { @@ -108,7 +134,7 @@ function getOrCreateModuleForApp(app: FirebaseApp, moduleNamespace: KnownNamespa } // e.g. firebase.storage(customUrlOrRegion), firebase.functions(customUrlOrRegion), firebase.firestore(databaseId), firebase.database(url) - function firebaseModuleWithArgs(customUrlOrRegionOrDatabaseId?: string): any { + function firebaseModuleWithArgs(customUrlOrRegionOrDatabaseId?: string): FirebaseModule { if (customUrlOrRegionOrDatabaseId !== undefined) { if (!hasCustomUrlOrRegionSupport) { // TODO throw Module does not support arguments error @@ -139,11 +165,11 @@ function getOrCreateModuleForApp(app: FirebaseApp, moduleNamespace: KnownNamespa APP_MODULE_INSTANCE[app.name]![key] = module; } - return APP_MODULE_INSTANCE[app.name]![key]; + return APP_MODULE_INSTANCE[app.name]![key]!; } - MODULE_GETTER_FOR_APP[app.name]![moduleNamespace] = firebaseModuleWithArgs; - return MODULE_GETTER_FOR_APP[app.name]![moduleNamespace]; + MODULE_GETTER_FOR_APP[app.name]![moduleNamespace] = firebaseModuleWithArgs as ModuleGetter; + return MODULE_GETTER_FOR_APP[app.name]![moduleNamespace]!; } /** @@ -151,7 +177,7 @@ function getOrCreateModuleForApp(app: FirebaseApp, moduleNamespace: KnownNamespa * @param moduleNamespace * @returns {*} */ -function getOrCreateModuleForRoot(moduleNamespace: KnownNamespace): any { +function getOrCreateModuleForRoot(moduleNamespace: KnownNamespace): ModuleGetter { if (MODULE_GETTER_FOR_ROOT[moduleNamespace]) { return MODULE_GETTER_FOR_ROOT[moduleNamespace]; } @@ -163,7 +189,7 @@ function getOrCreateModuleForRoot(moduleNamespace: KnownNamespace): any { const { statics, hasMultiAppSupport, ModuleClass } = config; // e.g. firebase.storage(app) - function firebaseModuleWithApp(app?: FirebaseApp): any { + function firebaseModuleWithApp(app?: FirebaseApp): FirebaseModule { const _app = app || getApp(); if (!(_app instanceof FirebaseApp)) { @@ -200,15 +226,17 @@ function getOrCreateModuleForRoot(moduleNamespace: KnownNamespace): any { APP_MODULE_INSTANCE[_app.name]![moduleNamespace] = module; } - return APP_MODULE_INSTANCE[_app.name]![moduleNamespace]; + return APP_MODULE_INSTANCE[_app.name]![moduleNamespace]!; } Object.assign(firebaseModuleWithApp, statics || {}); // Object.freeze(firebaseModuleWithApp); // Wrap around statics, e.g. firebase.firestore.FieldValue, removed freeze as it stops proxy working. it is deprecated anyway - MODULE_GETTER_FOR_ROOT[moduleNamespace] = createDeprecationProxy(firebaseModuleWithApp); + MODULE_GETTER_FOR_ROOT[moduleNamespace] = createDeprecationProxy( + firebaseModuleWithApp, + ) as ModuleGetter; - return MODULE_GETTER_FOR_ROOT[moduleNamespace]; + return MODULE_GETTER_FOR_ROOT[moduleNamespace]!; } /** @@ -217,7 +245,10 @@ function getOrCreateModuleForRoot(moduleNamespace: KnownNamespace): any { * @param moduleNamespace * @returns {*} */ -function firebaseRootModuleProxy(_firebaseNamespace: any, moduleNamespace: string): any { +function firebaseRootModuleProxy( + _firebaseNamespace: FirebaseRoot, + moduleNamespace: string, +): ModuleGetter { if (NAMESPACE_REGISTRY[moduleNamespace]) { return getOrCreateModuleForRoot(moduleNamespace as KnownNamespace); } @@ -242,9 +273,10 @@ function firebaseRootModuleProxy(_firebaseNamespace: any, moduleNamespace: strin * @param moduleNamespace * @returns {*} */ -export function firebaseAppModuleProxy(app: FirebaseApp, moduleNamespace: string): any { +export function firebaseAppModuleProxy(app: FirebaseApp, moduleNamespace: string): ModuleGetter { if (NAMESPACE_REGISTRY[moduleNamespace]) { - (app as any)._checkDestroyed(); + // Call private _checkDestroyed method + (app as unknown as { _checkDestroyed: () => void })._checkDestroyed(); return getOrCreateModuleForApp(app, moduleNamespace as KnownNamespace); } @@ -266,7 +298,7 @@ export function firebaseAppModuleProxy(app: FirebaseApp, moduleNamespace: string * * @returns {*} */ -export function createFirebaseRoot(): any { +export function createFirebaseRoot(): FirebaseRoot { FIREBASE_ROOT = { initializeApp, setReactNativeAsyncStorage, @@ -297,7 +329,7 @@ export function createFirebaseRoot(): any { * * @returns {*} */ -export function getFirebaseRoot(): any { +export function getFirebaseRoot(): FirebaseRoot { if (FIREBASE_ROOT) { return FIREBASE_ROOT; } @@ -309,17 +341,20 @@ export function getFirebaseRoot(): any { * @param options * @returns {*} */ -export function createModuleNamespace(options: any = {}): any { +export function createModuleNamespace(options: NamespaceConfig): ModuleGetter { const { namespace, ModuleClass } = options; if (!NAMESPACE_REGISTRY[namespace]) { // validation only for internal / module dev usage - if ((FirebaseModule as any).__extended__ !== (ModuleClass as any).__extended__) { + const firebaseModuleExtended = (FirebaseModule as unknown as { __extended__: object }) + .__extended__; + const moduleClassExtended = (ModuleClass as unknown as { __extended__: object }).__extended__; + if (firebaseModuleExtended !== moduleClassExtended) { throw new Error('INTERNAL ERROR: ModuleClass must be an instance of FirebaseModule.'); } NAMESPACE_REGISTRY[namespace] = Object.assign({}, options); } - return getFirebaseRoot()[namespace]; + return getFirebaseRoot()[namespace] as ModuleGetter; } From 0282cce853981bd372be7102b3fdf64c15b49841 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 3 Dec 2025 15:16:36 +0000 Subject: [PATCH 16/28] fix: native module types --- packages/app/lib/internal/NativeModules.ts | 9 ++++ .../lib/internal/RNFBNativeEventEmitter.ts | 42 +++++++++++++------ .../lib/internal/nativeModuleAndroidIos.ts | 18 ++++---- packages/app/lib/internal/nativeModuleWeb.ts | 24 ++++++----- .../app/lib/internal/registry/nativeModule.ts | 14 +++---- 5 files changed, 68 insertions(+), 39 deletions(-) diff --git a/packages/app/lib/internal/NativeModules.ts b/packages/app/lib/internal/NativeModules.ts index e6833fc491..76f07e4494 100644 --- a/packages/app/lib/internal/NativeModules.ts +++ b/packages/app/lib/internal/NativeModules.ts @@ -59,6 +59,15 @@ export interface RNFBAppModuleInterface { preferencesSetBool(key: string, value: boolean): Promise; preferencesSetString(key: string, value: string): Promise; setAutomaticDataCollectionEnabled(name: string, enabled: boolean): void; + + // Event emitter methods + eventsNotifyReady(ready: boolean): void; + eventsAddListener(eventType: string): void; + eventsRemoveListener(eventType: string, removeAll: boolean): void; + + // React Native EventEmitter compatibility + addListener?: (eventName: string) => void; + removeListeners?: (count: number) => void; } /** diff --git a/packages/app/lib/internal/RNFBNativeEventEmitter.ts b/packages/app/lib/internal/RNFBNativeEventEmitter.ts index c9d4629f9e..41b6ec81e5 100644 --- a/packages/app/lib/internal/RNFBNativeEventEmitter.ts +++ b/packages/app/lib/internal/RNFBNativeEventEmitter.ts @@ -17,6 +17,7 @@ import { type EmitterSubscription, NativeEventEmitter } from 'react-native'; import { getReactNativeModule } from './nativeModule'; +import type { RNFBAppModuleInterface } from './NativeModules'; import './global'; class RNFBNativeEventEmitter extends NativeEventEmitter { @@ -29,26 +30,29 @@ class RNFBNativeEventEmitter extends NativeEventEmitter { 'Native module RNFBAppModule not found. Re-check module install, linking, configuration, build and install steps.', ); } - super(RNFBAppModule); + // Cast to any for NativeEventEmitter constructor which expects React Native's NativeModule type + super(RNFBAppModule as any); this.ready = false; } addListener( eventType: string, - listener: (...args: any[]) => any, - context?: any, + listener: (...args: unknown[]) => unknown, + context?: object, ): EmitterSubscription { - const RNFBAppModule = getReactNativeModule('RNFBAppModule'); + const RNFBAppModule = getReactNativeModule( + 'RNFBAppModule', + ) as unknown as RNFBAppModuleInterface; if (!this.ready) { - RNFBAppModule.eventsNotifyReady(true); + (RNFBAppModule.eventsNotifyReady as (ready: boolean) => void)(true); this.ready = true; } - RNFBAppModule.eventsAddListener(eventType); + (RNFBAppModule.eventsAddListener as (eventType: string) => void)(eventType); if (globalThis.RNFBDebug) { // eslint-disable-next-line no-console console.debug(`[RNFB-->Event][👂] ${eventType} -> listening`); } - const listenerDebugger = (...args: any[]) => { + const listenerDebugger = (...args: unknown[]) => { if (globalThis.RNFBDebug) { // eslint-disable-next-line no-console console.debug(`[RNFB<--Event][📣] ${eventType} <-`, JSON.stringify(args[0])); @@ -81,7 +85,11 @@ class RNFBNativeEventEmitter extends NativeEventEmitter { // we will modify it to do our native unsubscription then call the original const originalRemove = subscription.remove; const newRemove = () => { - RNFBAppModule.eventsRemoveListener(eventType, false); + const module = getReactNativeModule('RNFBAppModule') as unknown as RNFBAppModuleInterface; + (module.eventsRemoveListener as (eventType: string, removeAll: boolean) => void)( + eventType, + false, + ); const superClass = Object.getPrototypeOf(Object.getPrototypeOf(this)); if (superClass.removeSubscription != null) { // This is for RN <= 0.64 - 65 and greater no longer have removeSubscription @@ -96,16 +104,26 @@ class RNFBNativeEventEmitter extends NativeEventEmitter { } removeAllListeners(eventType: string): void { - const RNFBAppModule = getReactNativeModule('RNFBAppModule'); - RNFBAppModule.eventsRemoveListener(eventType, true); + const RNFBAppModule = getReactNativeModule( + 'RNFBAppModule', + ) as unknown as RNFBAppModuleInterface; + (RNFBAppModule.eventsRemoveListener as (eventType: string, removeAll: boolean) => void)( + eventType, + true, + ); super.removeAllListeners(`rnfb_${eventType}`); } // This is likely no longer ever called, but it is here for backwards compatibility with RN <= 0.64 removeSubscription(subscription: EmitterSubscription & { eventType?: string }): void { - const RNFBAppModule = getReactNativeModule('RNFBAppModule'); + const RNFBAppModule = getReactNativeModule( + 'RNFBAppModule', + ) as unknown as RNFBAppModuleInterface; const eventType = subscription.eventType?.replace('rnfb_', '') || ''; - RNFBAppModule.eventsRemoveListener(eventType, false); + (RNFBAppModule.eventsRemoveListener as (eventType: string, removeAll: boolean) => void)( + eventType, + false, + ); const superClass = Object.getPrototypeOf(Object.getPrototypeOf(this)); if (superClass.removeSubscription) { superClass.removeSubscription(subscription); diff --git a/packages/app/lib/internal/nativeModuleAndroidIos.ts b/packages/app/lib/internal/nativeModuleAndroidIos.ts index 578d054c8a..5f67822925 100644 --- a/packages/app/lib/internal/nativeModuleAndroidIos.ts +++ b/packages/app/lib/internal/nativeModuleAndroidIos.ts @@ -7,8 +7,9 @@ import './global'; * We additionally add a Proxy to the module to intercept calls * and log them to the console for debugging purposes, if enabled. * @param moduleName + * @returns Raw native module from React Native (object with methods/properties or undefined) */ -export function getReactNativeModule(moduleName: string): any { +export function getReactNativeModule(moduleName: string): Record | undefined { const nativeModule = NativeModules[moduleName]; if (!globalThis.RNFBDebug) { return nativeModule; @@ -18,21 +19,22 @@ export function getReactNativeModule(moduleName: string): any { return Object.keys(target); }, get: (_, name) => { - if (typeof nativeModule[name as string] !== 'function') return nativeModule[name as string]; - return (...args: any[]) => { + const prop = nativeModule[name as string]; + if (typeof prop !== 'function') return prop; + return (...args: unknown[]) => { console.debug( `[RNFB->Native][🔵] ${moduleName}.${String(name)} -> ${JSON.stringify(args)}`, ); - const result = nativeModule[name as string](...args); - if (result && result.then) { - return result.then( - (res: any) => { + const result: unknown = (prop as (...args: unknown[]) => unknown)(...args); + if (result && typeof result === 'object' && 'then' in result) { + return (result as Promise).then( + (res: unknown) => { console.debug( `[RNFB<-Native][🟢] ${moduleName}.${String(name)} <- ${JSON.stringify(res)}`, ); return res; }, - (err: any) => { + (err: unknown) => { console.debug( `[RNFB<-Native][🔴] ${moduleName}.${String(name)} <- ${JSON.stringify(err)}`, ); diff --git a/packages/app/lib/internal/nativeModuleWeb.ts b/packages/app/lib/internal/nativeModuleWeb.ts index 44bad2c9b8..24918e0a5a 100644 --- a/packages/app/lib/internal/nativeModuleWeb.ts +++ b/packages/app/lib/internal/nativeModuleWeb.ts @@ -4,9 +4,9 @@ import './global'; // @ts-expect-error import RNFBAppModule from './web/RNFBAppModule'; -const nativeModuleRegistry: Record = {}; +const nativeModuleRegistry: Record> = {}; -export function getReactNativeModule(moduleName: string): any { +export function getReactNativeModule(moduleName: string): Record | undefined { const nativeModule = nativeModuleRegistry[moduleName]; // Throw an error if the module is not registered. if (!nativeModule) { @@ -21,21 +21,22 @@ export function getReactNativeModule(moduleName: string): any { return Object.keys(target); }, get: (_, name) => { - if (typeof nativeModule[name as string] !== 'function') return nativeModule[name as string]; - return (...args: any[]) => { + const prop = nativeModule[name as string]; + if (typeof prop !== 'function') return prop; + return (...args: unknown[]) => { console.debug( `[RNFB->Native][🔵] ${moduleName}.${String(name)} -> ${JSON.stringify(args)}`, ); - const result = nativeModule[name as string](...args); - if (result && result.then) { - return result.then( - (res: any) => { + const result: unknown = (prop as (...args: unknown[]) => unknown)(...args); + if (result && typeof result === 'object' && 'then' in result) { + return (result as Promise).then( + (res: unknown) => { console.debug( `[RNFB<-Native][🟢] ${moduleName}.${String(name)} <- ${JSON.stringify(res)}`, ); return res; }, - (err: any) => { + (err: unknown) => { console.debug( `[RNFB<-Native][🔴] ${moduleName}.${String(name)} <- ${JSON.stringify(err)}`, ); @@ -52,7 +53,10 @@ export function getReactNativeModule(moduleName: string): any { }); } -export function setReactNativeModule(moduleName: string, nativeModule: any): void { +export function setReactNativeModule( + moduleName: string, + nativeModule: Record, +): void { nativeModuleRegistry[moduleName] = nativeModule; } diff --git a/packages/app/lib/internal/registry/nativeModule.ts b/packages/app/lib/internal/registry/nativeModule.ts index 54a1c776b7..1bc80dc4dc 100644 --- a/packages/app/lib/internal/registry/nativeModule.ts +++ b/packages/app/lib/internal/registry/nativeModule.ts @@ -69,25 +69,20 @@ function nativeModuleMethodWrapped( * Prepends all arguments in prependArgs to all native method calls * * @param namespace - * @param NativeModule - Raw native module from React Native (untyped) + * @param NativeModule - Raw native module from React Native * @param argToPrepend */ function nativeModuleWrapped( namespace: string, - NativeModule: unknown, + NativeModule: Record | undefined, argToPrepend: unknown[], ): WrappedNativeModule { const native: Record = {}; if (!NativeModule) { - return NativeModule as WrappedNativeModule; - } - - // Type guard: ensure it's an object before accessing properties - if (typeof NativeModule !== 'object') { return native; } - const nativeModuleObj = NativeModule as Record; + const nativeModuleObj = NativeModule; let properties = Object.keys(Object.getPrototypeOf(nativeModuleObj)); if (!properties.length) properties = Object.keys(nativeModuleObj); @@ -189,7 +184,8 @@ function initialiseNativeModule(module: FirebaseModule): WrappedNativeModule { */ function subscribeToNativeModuleEvent(eventName: string): void { if (!NATIVE_MODULE_EVENT_SUBSCRIPTIONS[eventName]) { - RNFBNativeEventEmitter.addListener(eventName, (event: NativeEvent) => { + RNFBNativeEventEmitter.addListener(eventName, (...args: unknown[]) => { + const event = args[0] as NativeEvent; if (event.appName && event.databaseId) { // Firestore requires both appName and databaseId to prefix SharedEventEmitter.emit(`${event.appName}-${event.databaseId}-${eventName}`, event); From 423f30cf5275629ae92412bc446eac81656caef6 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 3 Dec 2025 15:20:15 +0000 Subject: [PATCH 17/28] fix: improve native module types --- .../lib/internal/RNFBNativeEventEmitter.ts | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/app/lib/internal/RNFBNativeEventEmitter.ts b/packages/app/lib/internal/RNFBNativeEventEmitter.ts index 41b6ec81e5..89c256c954 100644 --- a/packages/app/lib/internal/RNFBNativeEventEmitter.ts +++ b/packages/app/lib/internal/RNFBNativeEventEmitter.ts @@ -20,6 +20,21 @@ import { getReactNativeModule } from './nativeModule'; import type { RNFBAppModuleInterface } from './NativeModules'; import './global'; +/** + * Type for the eventsNotifyReady native method + */ +type EventsNotifyReadyMethod = (ready: boolean) => void; + +/** + * Type for the eventsAddListener native method + */ +type EventsAddListenerMethod = (eventType: string) => void; + +/** + * Type for the eventsRemoveListener native method + */ +type EventsRemoveListenerMethod = (eventType: string, removeAll: boolean) => void; + class RNFBNativeEventEmitter extends NativeEventEmitter { ready: boolean; @@ -44,10 +59,10 @@ class RNFBNativeEventEmitter extends NativeEventEmitter { 'RNFBAppModule', ) as unknown as RNFBAppModuleInterface; if (!this.ready) { - (RNFBAppModule.eventsNotifyReady as (ready: boolean) => void)(true); + (RNFBAppModule.eventsNotifyReady as EventsNotifyReadyMethod)(true); this.ready = true; } - (RNFBAppModule.eventsAddListener as (eventType: string) => void)(eventType); + (RNFBAppModule.eventsAddListener as EventsAddListenerMethod)(eventType); if (globalThis.RNFBDebug) { // eslint-disable-next-line no-console console.debug(`[RNFB-->Event][👂] ${eventType} -> listening`); @@ -86,10 +101,7 @@ class RNFBNativeEventEmitter extends NativeEventEmitter { const originalRemove = subscription.remove; const newRemove = () => { const module = getReactNativeModule('RNFBAppModule') as unknown as RNFBAppModuleInterface; - (module.eventsRemoveListener as (eventType: string, removeAll: boolean) => void)( - eventType, - false, - ); + (module.eventsRemoveListener as EventsRemoveListenerMethod)(eventType, false); const superClass = Object.getPrototypeOf(Object.getPrototypeOf(this)); if (superClass.removeSubscription != null) { // This is for RN <= 0.64 - 65 and greater no longer have removeSubscription @@ -107,10 +119,7 @@ class RNFBNativeEventEmitter extends NativeEventEmitter { const RNFBAppModule = getReactNativeModule( 'RNFBAppModule', ) as unknown as RNFBAppModuleInterface; - (RNFBAppModule.eventsRemoveListener as (eventType: string, removeAll: boolean) => void)( - eventType, - true, - ); + (RNFBAppModule.eventsRemoveListener as EventsRemoveListenerMethod)(eventType, true); super.removeAllListeners(`rnfb_${eventType}`); } @@ -120,10 +129,7 @@ class RNFBNativeEventEmitter extends NativeEventEmitter { 'RNFBAppModule', ) as unknown as RNFBAppModuleInterface; const eventType = subscription.eventType?.replace('rnfb_', '') || ''; - (RNFBAppModule.eventsRemoveListener as (eventType: string, removeAll: boolean) => void)( - eventType, - false, - ); + (RNFBAppModule.eventsRemoveListener as EventsRemoveListenerMethod)(eventType, false); const superClass = Object.getPrototypeOf(Object.getPrototypeOf(this)); if (superClass.removeSubscription) { superClass.removeSubscription(subscription); From 34bec7b491cc23c02258d94865aab6a8b9e5700b Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 3 Dec 2025 15:58:01 +0000 Subject: [PATCH 18/28] refactor: ensure internals are on internal path --- packages/app/lib/index.d.ts | 3 ++- packages/app/lib/index.ts | 13 ++----------- packages/app/lib/internal/index.ts | 3 +++ packages/app/lib/namespaced.ts | 29 +++++++++++++++++++++++++++++ packages/app/package.json | 22 ++++++++++++++++++++++ 5 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 packages/app/lib/namespaced.ts diff --git a/packages/app/lib/index.d.ts b/packages/app/lib/index.d.ts index c20953ea11..96a0bab5be 100644 --- a/packages/app/lib/index.d.ts +++ b/packages/app/lib/index.d.ts @@ -32,7 +32,8 @@ */ // Re-export all types from the types directory -export { ReactNativeFirebase, Utils, Internal } from './types'; +// Note: Internal types are available via '@react-native-firebase/app/lib/internal' +export { ReactNativeFirebase, Utils } from './types'; /** * Add Utils module as a named export for `app`. diff --git a/packages/app/lib/index.ts b/packages/app/lib/index.ts index 83b0ae0181..557d8f5b3b 100644 --- a/packages/app/lib/index.ts +++ b/packages/app/lib/index.ts @@ -15,15 +15,6 @@ * */ -import { getFirebaseRoot } from './internal/registry/namespace'; -import utils from './utils'; -import type { ReactNativeFirebase, Utils } from './types'; - -export const firebase = getFirebaseRoot(); +export { firebase, utils, default } from './namespaced'; export * from './modular'; -export { utils }; - -// Export types -export type { ReactNativeFirebase, Utils }; - -export default firebase; +export type { ReactNativeFirebase, Utils } from './types'; diff --git a/packages/app/lib/internal/index.ts b/packages/app/lib/internal/index.ts index 4774df70c0..539248cf94 100644 --- a/packages/app/lib/internal/index.ts +++ b/packages/app/lib/internal/index.ts @@ -25,3 +25,6 @@ export * from './registry/namespace'; export * from './registry/nativeModule'; export { default as SharedEventEmitter } from './SharedEventEmitter'; export { Logger } from './logger'; + +// Export Internal types for use by other @react-native-firebase packages +export type { Internal } from '../types'; diff --git a/packages/app/lib/namespaced.ts b/packages/app/lib/namespaced.ts new file mode 100644 index 0000000000..83b0ae0181 --- /dev/null +++ b/packages/app/lib/namespaced.ts @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * 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 { getFirebaseRoot } from './internal/registry/namespace'; +import utils from './utils'; +import type { ReactNativeFirebase, Utils } from './types'; + +export const firebase = getFirebaseRoot(); +export * from './modular'; +export { utils }; + +// Export types +export type { ReactNativeFirebase, Utils }; + +export default firebase; diff --git a/packages/app/package.json b/packages/app/package.json index 817afa8551..0401421a13 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -65,6 +65,28 @@ "default": "./dist/commonjs/index.js" } }, + "./internal": { + "source": "./lib/internal/index.ts", + "import": { + "types": "./dist/typescript/module/lib/internal/index.d.ts", + "default": "./dist/module/internal/index.js" + }, + "require": { + "types": "./dist/typescript/commonjs/lib/internal/index.d.ts", + "default": "./dist/commonjs/internal/index.js" + } + }, + "./lib/internal": { + "source": "./lib/internal/index.ts", + "import": { + "types": "./dist/typescript/module/lib/internal/index.d.ts", + "default": "./dist/module/internal/index.js" + }, + "require": { + "types": "./dist/typescript/commonjs/lib/internal/index.d.ts", + "default": "./dist/commonjs/internal/index.js" + } + }, "./package.json": "./package.json" }, "files": [ From 11e1e77a0069d5f3f725b3edb9a42220bf9ecda4 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 3 Dec 2025 16:01:48 +0000 Subject: [PATCH 19/28] refactor: move to modular.ts --- packages/app/lib/{modular/index.ts => modular.ts} | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename packages/app/lib/{modular/index.ts => modular.ts} (96%) diff --git a/packages/app/lib/modular/index.ts b/packages/app/lib/modular.ts similarity index 96% rename from packages/app/lib/modular/index.ts rename to packages/app/lib/modular.ts index b6c6404264..0acf3efe9e 100644 --- a/packages/app/lib/modular/index.ts +++ b/packages/app/lib/modular.ts @@ -15,8 +15,8 @@ * */ -import { MODULAR_DEPRECATION_ARG } from '../common'; -import type { ReactNativeFirebase } from '../types'; +import { MODULAR_DEPRECATION_ARG } from './common'; +import type { ReactNativeFirebase } from './types'; import { deleteApp as deleteAppCompat, getApp as getAppCompat, @@ -24,9 +24,9 @@ import { initializeApp as initializeAppCompat, setLogLevel as setLogLevelCompat, setReactNativeAsyncStorage as setReactNativeAsyncStorageCompat, -} from '../internal/registry/app'; -import { setUserLogHandler } from '../internal/logger'; -import { version as sdkVersion } from '../version'; +} from './internal/registry/app'; +import { setUserLogHandler } from './internal/logger'; +import { version as sdkVersion } from './version'; import { NativeModules } from 'react-native'; export interface LogCallbackParams { From 32e0b748ddd1ba7a4bc60ca335a165e48173631d Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 4 Dec 2025 10:18:36 +0000 Subject: [PATCH 20/28] fix: remove incorrect imports --- packages/app/lib/common/index.ts | 1 - packages/app/lib/internal/RNFBNativeEventEmitter.ts | 1 - packages/app/lib/internal/nativeModuleAndroidIos.ts | 1 - packages/app/lib/utils/UtilsStatics.ts | 2 +- 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/app/lib/common/index.ts b/packages/app/lib/common/index.ts index 45a84556ae..04b795b125 100644 --- a/packages/app/lib/common/index.ts +++ b/packages/app/lib/common/index.ts @@ -17,7 +17,6 @@ import { Platform } from 'react-native'; import Base64 from './Base64'; import { isFunction, isObject, isString } from './validate'; -import '../internal/global'; export * from './id'; export * from './path'; diff --git a/packages/app/lib/internal/RNFBNativeEventEmitter.ts b/packages/app/lib/internal/RNFBNativeEventEmitter.ts index 89c256c954..2abb338595 100644 --- a/packages/app/lib/internal/RNFBNativeEventEmitter.ts +++ b/packages/app/lib/internal/RNFBNativeEventEmitter.ts @@ -18,7 +18,6 @@ import { type EmitterSubscription, NativeEventEmitter } from 'react-native'; import { getReactNativeModule } from './nativeModule'; import type { RNFBAppModuleInterface } from './NativeModules'; -import './global'; /** * Type for the eventsNotifyReady native method diff --git a/packages/app/lib/internal/nativeModuleAndroidIos.ts b/packages/app/lib/internal/nativeModuleAndroidIos.ts index 5f67822925..ad6c02fb82 100644 --- a/packages/app/lib/internal/nativeModuleAndroidIos.ts +++ b/packages/app/lib/internal/nativeModuleAndroidIos.ts @@ -1,6 +1,5 @@ /* eslint-disable no-console */ import { NativeModules } from 'react-native'; -import './global'; /** * This is used by Android and iOS to get a native module. diff --git a/packages/app/lib/utils/UtilsStatics.ts b/packages/app/lib/utils/UtilsStatics.ts index 7bd5c98bda..1b942186cf 100644 --- a/packages/app/lib/utils/UtilsStatics.ts +++ b/packages/app/lib/utils/UtilsStatics.ts @@ -16,7 +16,7 @@ */ import { NativeModules } from 'react-native'; -import { stripTrailingSlash, isOther } from '../../lib/common'; +import { stripTrailingSlash, isOther } from '../common'; import { Utils } from '../types'; import { version } from '../version'; From 363723b6a4048b2a93fc051f0e50b2a374b03f21 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 4 Dec 2025 16:04:57 +0000 Subject: [PATCH 21/28] fix: metro updates to ensure compiled code is used --- tests/metro.config.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/metro.config.js b/tests/metro.config.js index 23a36c692d..a3de79d720 100644 --- a/tests/metro.config.js +++ b/tests/metro.config.js @@ -61,6 +61,24 @@ const config = { }, }, ), + resolveRequest: (context, moduleName, platform) => { + // For @react-native-firebase/app subpath imports, redirect lib/* to dist/commonjs/* + if (moduleName.startsWith('@react-native-firebase/app/lib/')) { + const subpath = moduleName.replace('@react-native-firebase/app/lib/', ''); + const newModuleName = `@react-native-firebase/app/dist/commonjs/${subpath}`; + return context.resolveRequest(context, newModuleName, platform); + } + + // For @react-native-firebase/app/common/*, redirect to dist/commonjs/common/* + if (moduleName.startsWith('@react-native-firebase/app/common/')) { + const subpath = moduleName.replace('@react-native-firebase/app/common/', ''); + const newModuleName = `@react-native-firebase/app/dist/commonjs/common/${subpath}`; + return context.resolveRequest(context, newModuleName, platform); + } + + // Let Metro resolve normally + return context.resolveRequest(context, moduleName, platform); + }, }, transformer: { unstable_allowRequireContext: true, From 6b22a1f8cada5e27d3513818711a16db7b37b273 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 4 Dec 2025 16:05:21 +0000 Subject: [PATCH 22/28] chore: remove internal types --- packages/app/lib/internal-types.d.ts | 124 --------------------------- 1 file changed, 124 deletions(-) delete mode 100644 packages/app/lib/internal-types.d.ts diff --git a/packages/app/lib/internal-types.d.ts b/packages/app/lib/internal-types.d.ts deleted file mode 100644 index b363b389af..0000000000 --- a/packages/app/lib/internal-types.d.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2016-present Invertase Limited & Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this library except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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 type { ReactNativeFirebase } from './index'; - -// Type definitions for internal modules used by other packages during TypeScript migration - -declare module '@react-native-firebase/app/lib/internal' { - import BaseFirebaseModule = ReactNativeFirebase.FirebaseModule; - - export interface ReactNativeFirebaseNativeModules { - // Base interface - packages will augment this - } - - export interface RNFBUtilsModuleInterface { - isRunningInTestLab: boolean; - androidPlayServices: { - isAvailable: boolean; - status: number; - hasResolution: boolean; - isUserResolvableError: boolean; - error: string | undefined; - }; - androidGetPlayServicesStatus(): Promise<{ - isAvailable: boolean; - status: number; - hasResolution: boolean; - isUserResolvableError: boolean; - error: string | undefined; - }>; - androidPromptForPlayServices(): Promise; - androidMakePlayServicesAvailable(): Promise; - androidResolutionForPlayServices(): Promise; - } - - export interface RNFBAppModuleInterface { - FIREBASE_RAW_JSON: string; - } - - export class FirebaseModule< - NativeModuleName extends keyof ReactNativeFirebaseNativeModules = any, - > extends BaseFirebaseModule { - native: ReactNativeFirebaseNativeModules[NativeModuleName]; - emitter: any; - } - export function createModuleNamespace(config: any): any; - export function getFirebaseRoot(): any; - export class NativeFirebaseError { - static getStackWithMessage(message: string, jsStack?: string): string; - } - - export type GetNativeModule = - ReactNativeFirebaseNativeModules[T]; - export type AnyNativeModule = - ReactNativeFirebaseNativeModules[keyof ReactNativeFirebaseNativeModules]; -} - -declare module '@react-native-firebase/app/lib/internal/NativeModules' { - export interface ReactNativeFirebaseNativeModules { - RNFBUtilsModule: import('@react-native-firebase/app/lib/internal').RNFBUtilsModuleInterface; - RNFBAppModule: import('@react-native-firebase/app/lib/internal').RNFBAppModuleInterface; - } - - export interface RNFBUtilsModuleInterface { - isRunningInTestLab: boolean; - androidPlayServices: { - isAvailable: boolean; - status: number; - hasResolution: boolean; - isUserResolvableError: boolean; - error: string | undefined; - }; - androidGetPlayServicesStatus(): Promise<{ - isAvailable: boolean; - status: number; - hasResolution: boolean; - isUserResolvableError: boolean; - error: string | undefined; - }>; - androidPromptForPlayServices(): Promise; - androidMakePlayServicesAvailable(): Promise; - androidResolutionForPlayServices(): Promise; - } - - export interface RNFBAppModuleInterface { - FIREBASE_RAW_JSON: string; - } - - export type GetNativeModule = - ReactNativeFirebaseNativeModules[T]; - export type AnyNativeModule = - ReactNativeFirebaseNativeModules[keyof ReactNativeFirebaseNativeModules]; -} - -declare module '@react-native-firebase/app/lib/internal/nativeModule' { - export function setReactNativeModule(name: string, module: any): void; - export function getReactNativeModule(name: string): any; -} - -declare module '@react-native-firebase/app/lib/common' { - export function isAlphaNumericUnderscore(value: any): boolean; - export function isE164PhoneNumber(value: any): boolean; - export function isIOS(): boolean; - export function isNull(value: any): boolean; - export function isNumber(value: any): boolean; - export function isObject(value: any): boolean; - export function isOneOf(value: any, array: any[]): boolean; - export function isString(value: any): boolean; - export function isUndefined(value: any): boolean; - export function isBoolean(value: any): boolean; -} From b3f4aae4b552101e8d18a1fdd7e81a56377f4cf5 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 4 Dec 2025 16:11:23 +0000 Subject: [PATCH 23/28] fix: update export paths from app --- packages/app/package.json | 46 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/packages/app/package.json b/packages/app/package.json index 0401421a13..4cd6f6a492 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -55,7 +55,7 @@ ], "exports": { ".": { - "source": "./lib/index.js", + "source": "./lib/index.ts", "import": { "types": "./dist/typescript/module/lib/index.d.ts", "default": "./dist/module/index.js" @@ -87,6 +87,50 @@ "default": "./dist/commonjs/internal/index.js" } }, + "./lib/internal/*": { + "source": "./lib/internal/*.ts", + "import": { + "types": "./dist/typescript/module/lib/internal/*.d.ts", + "default": "./dist/module/internal/*" + }, + "require": { + "types": "./dist/typescript/commonjs/lib/internal/*.d.ts", + "default": "./dist/commonjs/internal/*" + } + }, + "./lib/common": { + "source": "./lib/common/index.ts", + "import": { + "types": "./dist/typescript/module/lib/common/index.d.ts", + "default": "./dist/module/common/index.js" + }, + "require": { + "types": "./dist/typescript/commonjs/lib/common/index.d.ts", + "default": "./dist/commonjs/common/index.js" + } + }, + "./lib/common/*": { + "source": "./lib/common/*.ts", + "import": { + "types": "./dist/typescript/module/lib/common/*.d.ts", + "default": "./dist/module/common/*" + }, + "require": { + "types": "./dist/typescript/commonjs/lib/common/*.d.ts", + "default": "./dist/commonjs/common/*" + } + }, + "./common/*": { + "source": "./lib/common/*.ts", + "import": { + "types": "./dist/typescript/module/lib/common/*.d.ts", + "default": "./dist/module/common/*" + }, + "require": { + "types": "./dist/typescript/commonjs/lib/common/*.d.ts", + "default": "./dist/commonjs/common/*" + } + }, "./package.json": "./package.json" }, "files": [ From a32908e8a790bd8125236782ef2624be20ec0c59 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 4 Dec 2025 16:15:06 +0000 Subject: [PATCH 24/28] chore: fix test import --- tests/local-tests/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/local-tests/index.js b/tests/local-tests/index.js index a128b70e17..5737ebbb5d 100644 --- a/tests/local-tests/index.js +++ b/tests/local-tests/index.js @@ -26,7 +26,7 @@ import { AITestComponent } from './ai/ai'; import { DatabaseOnChildMovedTest } from './database'; import { FirestoreOnSnapshotInSyncTest } from './firestore/onSnapshotInSync'; import { VertexAITestComponent } from './vertexai/vertexai'; -import { AuthTOTPDemonstrator } from './auth/auth-mfa-demonstrator'; +import { AuthMFADemonstrator } from './auth/auth-mfa-demonstrator'; const testComponents = { // List your imported components here... From c847c2c0b904fa015ecb667dfd71d7e305f0165f Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 5 Dec 2025 10:28:32 +0000 Subject: [PATCH 25/28] chore: delete index.d.ts --- packages/app/lib/index.d.ts | 46 ------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 packages/app/lib/index.d.ts diff --git a/packages/app/lib/index.d.ts b/packages/app/lib/index.d.ts deleted file mode 100644 index 96a0bab5be..0000000000 --- a/packages/app/lib/index.d.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2016-present Invertase Limited & Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this library except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - * - */ - -/** - * Core React Native Firebase package. - * - * #### Example 1 - * - * Access the default firebase app from the `app` package: - * - * ```js - * import firebase from '@react-native-firebase/app'; - * - * console.log(firebase.app().name); - * ``` - * - * @firebase app - */ - -// Re-export all types from the types directory -// Note: Internal types are available via '@react-native-firebase/app/lib/internal' -export { ReactNativeFirebase, Utils } from './types'; - -/** - * Add Utils module as a named export for `app`. - */ -export const utils: ReactNativeFirebase.FirebaseModuleWithStatics; - -export * from './modular'; - -declare const module: ReactNativeFirebase.Module; -export default module; From a2012cd9994f45a9350774126c59c6007946fa22 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 5 Dec 2025 10:35:09 +0000 Subject: [PATCH 26/28] chore: remove unneeded types --- packages/app/lib/types/index.ts | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/packages/app/lib/types/index.ts b/packages/app/lib/types/index.ts index e6f11b3519..6e9fdbced9 100644 --- a/packages/app/lib/types/index.ts +++ b/packages/app/lib/types/index.ts @@ -15,8 +15,6 @@ * */ -import type { WrappedNativeModule } from '../internal/NativeModules'; - /** * Core React Native Firebase package types. * @@ -594,24 +592,3 @@ export namespace Utils { abstract resolutionForPlayServices(): Promise; } } - -/** - * Internal module type definitions for consumption by other packages - */ -// eslint-disable-next-line @typescript-eslint/no-namespace -export namespace Internal { - export declare function createModuleNamespace(config: any): any; - export interface FirebaseModule { - native: WrappedNativeModule; - firebaseJson: Record; - _customUrlOrRegion: string | null; - } - export declare function getFirebaseRoot(): any; - export interface NativeFirebaseError { - code?: string; - message?: string; - } - export interface NativeFirebaseErrorStatic { - getStackWithMessage(message: string, jsStack?: string): string; - } -} From 36992e19d2f54577ad4b4e94293e905f26391980 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 5 Dec 2025 10:35:50 +0000 Subject: [PATCH 27/28] chore: remove import --- packages/app/lib/internal/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/app/lib/internal/index.ts b/packages/app/lib/internal/index.ts index 539248cf94..4774df70c0 100644 --- a/packages/app/lib/internal/index.ts +++ b/packages/app/lib/internal/index.ts @@ -25,6 +25,3 @@ export * from './registry/namespace'; export * from './registry/nativeModule'; export { default as SharedEventEmitter } from './SharedEventEmitter'; export { Logger } from './logger'; - -// Export Internal types for use by other @react-native-firebase packages -export type { Internal } from '../types'; From deb281d7c0cce4cc05ed13e21ce56811924a8b14 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 5 Dec 2025 16:56:51 +0000 Subject: [PATCH 28/28] refactor: remove types and create internal types --- .../lib/internal-types/native-modules.d.ts | 61 +++++++++++++ .../lib/internal-types/web-modules.d.ts | 52 +++++++++++ packages/analytics/lib/namespaced.ts | 14 ++- packages/analytics/lib/types.d.ts | 91 ------------------- packages/analytics/tsconfig.json | 10 +- 5 files changed, 131 insertions(+), 97 deletions(-) create mode 100644 packages/analytics/lib/internal-types/native-modules.d.ts create mode 100644 packages/analytics/lib/internal-types/web-modules.d.ts delete mode 100644 packages/analytics/lib/types.d.ts diff --git a/packages/analytics/lib/internal-types/native-modules.d.ts b/packages/analytics/lib/internal-types/native-modules.d.ts new file mode 100644 index 0000000000..86ee43c01a --- /dev/null +++ b/packages/analytics/lib/internal-types/native-modules.d.ts @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * 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. + * + */ + +/** + * Internal type definitions for the Analytics native module. + * This file augments the base ReactNativeFirebaseNativeModules interface + * to add analytics-specific native methods. + * + * @internal - This file is not exposed to package consumers + */ + +import type { ConsentSettings } from './types/analytics'; + +/** + * Interface for the Analytics native module methods + */ +export interface RNFBAnalyticsModuleInterface { + logEvent(name: string, params: { [key: string]: any }): Promise; + setAnalyticsCollectionEnabled(enabled: boolean): Promise; + setSessionTimeoutDuration(milliseconds: number): Promise; + getAppInstanceId(): Promise; + getSessionId(): Promise; + setUserId(id: string | null): Promise; + setUserProperty(name: string, value: string | null): Promise; + setUserProperties(properties: { [key: string]: string | null }): Promise; + resetAnalyticsData(): Promise; + setConsent(consentSettings: ConsentSettings): Promise; + setDefaultEventParameters(params: { [key: string]: any } | null | undefined): Promise; + initiateOnDeviceConversionMeasurementWithEmailAddress(emailAddress: string): Promise; + initiateOnDeviceConversionMeasurementWithHashedEmailAddress( + hashedEmailAddress: string, + ): Promise; + initiateOnDeviceConversionMeasurementWithPhoneNumber(phoneNumber: string): Promise; + initiateOnDeviceConversionMeasurementWithHashedPhoneNumber( + hashedPhoneNumber: string, + ): Promise; +} + +/** + * Augment the base ReactNativeFirebaseNativeModules interface + * from the app package to include our analytics module + */ +declare module '@react-native-firebase/app/lib/internal/NativeModules' { + interface ReactNativeFirebaseNativeModules { + RNFBAnalyticsModule: RNFBAnalyticsModuleInterface; + } +} diff --git a/packages/analytics/lib/internal-types/web-modules.d.ts b/packages/analytics/lib/internal-types/web-modules.d.ts new file mode 100644 index 0000000000..d6b4c93b9a --- /dev/null +++ b/packages/analytics/lib/internal-types/web-modules.d.ts @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * 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. + * + */ + +/** + * Type declarations for web-only modules from the app package. + * These modules are JavaScript files with separate .d.ts declarations. + * + * @internal - This file is not exposed to package consumers + */ + +declare module '@react-native-firebase/app/lib/internal/web/firebaseApp' { + import type { ReactNativeFirebase } from '@react-native-firebase/app'; + + export function getApp(appName?: string): ReactNativeFirebase.FirebaseApp; +} + +declare module '@react-native-firebase/app/lib/internal/web/utils' { + export function guard(fn: () => T | Promise): Promise; +} + +declare module '@react-native-firebase/app/lib/internal/web/firebaseInstallations' { + import type { ReactNativeFirebase } from '@react-native-firebase/app'; + + export interface Installations { + app: ReactNativeFirebase.FirebaseApp; + } + + export type Unsubscribe = () => void; + + export function getApp(appName?: string): ReactNativeFirebase.FirebaseApp; + export function getInstallations(app?: ReactNativeFirebase.FirebaseApp): Installations; + export function getId(installations: Installations): Promise; + export function onIdChange( + installations: Installations, + callback: (installationId: string) => void, + ): Unsubscribe; + export function makeIDBAvailable(): void; +} diff --git a/packages/analytics/lib/namespaced.ts b/packages/analytics/lib/namespaced.ts index c54ee23771..b9208bcb12 100644 --- a/packages/analytics/lib/namespaced.ts +++ b/packages/analytics/lib/namespaced.ts @@ -37,6 +37,10 @@ import { import { setReactNativeModule } from '@react-native-firebase/app/lib/internal/nativeModule'; import { isBoolean } from '@react-native-firebase/app/lib/common'; +// Import internal types to register native module augmentation +import './internal-types/native-modules'; +import './internal-types/web-modules'; + import { validateStruct, validateCompound } from './struct'; import { RNFBAnalyticsModule } from './web/RNFBAnalyticsModule'; import { version } from './version'; @@ -120,7 +124,7 @@ const namespace = 'analytics'; const nativeModuleName = 'RNFBAnalyticsModule'; -class FirebaseAnalyticsModule extends FirebaseModule { +class FirebaseAnalyticsModule extends FirebaseModule<'RNFBAnalyticsModule'> { logEvent( name: string, params: { [key: string]: any } = {}, @@ -879,13 +883,13 @@ export default createModuleNamespace({ hasMultiAppSupport: false, hasCustomUrlOrRegionSupport: false, ModuleClass: FirebaseAnalyticsModule, -}) as typeof defaultExport; +}) as unknown as typeof defaultExport; // Register the interop module for non-native platforms. -setReactNativeModule(nativeModuleName, RNFBAnalyticsModule); +setReactNativeModule(nativeModuleName, RNFBAnalyticsModule as unknown as Record); -export const firebase: FirebaseAnalyticsModule & { +export const firebase = getFirebaseRoot() as unknown as typeof defaultExport & { analytics: typeof defaultExport; SDK_VERSION: string; app(name?: string): ReactNativeFirebase.FirebaseApp & { analytics(): FirebaseAnalyticsModule }; -} = getFirebaseRoot(); +}; diff --git a/packages/analytics/lib/types.d.ts b/packages/analytics/lib/types.d.ts deleted file mode 100644 index 71e0ff521d..0000000000 --- a/packages/analytics/lib/types.d.ts +++ /dev/null @@ -1,91 +0,0 @@ -declare module '@react-native-firebase/app/lib/common' { - export const MODULAR_DEPRECATION_ARG: string; - export const isAlphaNumericUnderscore: (value: any) => boolean; - export const isE164PhoneNumber: (value: any) => boolean; - export const isIOS: boolean; - export const isNull: (value: any) => value is null; - export const isNumber: (value: any) => value is number; - export const isObject: (value: any) => value is object; - export const isOneOf: (value: any, validValues: any[]) => boolean; - export const isString: (value: any) => value is string; - export const isUndefined: (value: any) => value is undefined; - export const isBoolean: (value: any) => value is boolean; -} - -declare module '@react-native-firebase/app/lib/common/unitTestUtils' { - export const getFirebaseApp: () => any; - export const getMockNativeModule: (name: string) => any; - export function createCheckV9Deprecation(moduleName: string): CheckV9DeprecationFunction; - export type CheckV9DeprecationFunction = (methodName: string) => void; -} - -declare module '@react-native-firebase/app/lib/common/validate' { - export const isUndefined: (value: any) => value is undefined; -} - -declare module '@react-native-firebase/app/lib/internal' { - export function createModuleNamespace(config: any): any; - export class FirebaseModule { - constructor(...args: any[]); - native: any; - app: any; - firebaseJson: any; - _customUrlOrRegion: string | null; - } - export function getFirebaseRoot(): any; - export class NativeFirebaseError { - static getStackWithMessage(message: string, jsStack?: string): string; - } -} - -declare module '@react-native-firebase/app/lib/internal/nativeModule' { - export function setReactNativeModule(moduleName: string, module: any): void; -} - -declare module '@react-native-firebase/app/lib/internal/web/firebaseInstallations' { - export function getApp(appName?: string): any; - export function getId(installations: any): Promise; - export function onIdChange( - installations: any, - callback: (installationId: string) => void, - ): () => void; - export function getInstallations(app: any): any; - export function makeIDBAvailable(): void; -} - -declare module '@react-native-firebase/app/lib/internal/asyncStorage' { - export function setItem(key: string, value: string): Promise; - export function getItem(key: string): Promise; - export function isMemoryStorage(): boolean; -} - -declare module '@react-native-firebase/app/lib/internal/web/firebaseApp' { - export function getApp(appName?: string): any; -} - -declare module '@react-native-firebase/app/lib/internal/web/utils' { - export function guard(fn: () => any): any; -} - -declare module '@react-native-firebase/app/lib' { - namespace ReactNativeFirebase { - import FirebaseModuleWithStaticsAndApp = ReactNativeFirebase.FirebaseModuleWithStaticsAndApp; - interface Module { - analytics: FirebaseModuleWithStaticsAndApp; - } - interface FirebaseApp { - analytics(): any; - readonly name: string; - } - } -} - -declare module './version' { - const version: string; - export default version; -} - -declare module './web/RNFBAnalyticsModule' { - const fallBackModule: any; - export default fallBackModule; -} diff --git a/packages/analytics/tsconfig.json b/packages/analytics/tsconfig.json index 6bf8f06222..01e51f7603 100644 --- a/packages/analytics/tsconfig.json +++ b/packages/analytics/tsconfig.json @@ -23,7 +23,15 @@ "baseUrl": ".", "rootDir": ".", "paths": { - "@react-native-firebase/app/common/*": ["../app/dist/typescript/commonjs/lib/common/*"], + "@react-native-firebase/app/lib/common/*": ["../app/dist/typescript/commonjs/lib/common/*"], + "@react-native-firebase/app/lib/common": ["../app/dist/typescript/commonjs/lib/common"], + "@react-native-firebase/app/lib/internal/web/*": [ + "../app/dist/typescript/commonjs/lib/internal/web/*" + ], + "@react-native-firebase/app/lib/internal/*": [ + "../app/dist/typescript/commonjs/lib/internal/*" + ], + "@react-native-firebase/app/lib/internal": ["../app/dist/typescript/commonjs/lib/internal"], "@react-native-firebase/app": ["../app/dist/typescript/commonjs/lib"] } },