diff --git a/packages/ai/tsconfig.json b/packages/ai/tsconfig.json index b40f80b289..3bb2d31285 100644 --- a/packages/ai/tsconfig.json +++ b/packages/ai/tsconfig.json @@ -33,7 +33,7 @@ "@react-native-firebase/app/lib/internal": ["../app/dist/typescript/commonjs/lib/internal"], "@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/dist/typescript/commonjs/lib"] } }, "include": ["lib/**/*"], diff --git a/packages/app-check/__tests__/appcheck.test.ts b/packages/app-check/__tests__/appcheck.test.ts index 1a6867a42d..c70bfb3bc2 100644 --- a/packages/app-check/__tests__/appcheck.test.ts +++ b/packages/app-check/__tests__/appcheck.test.ts @@ -24,7 +24,6 @@ import { ReactNativeFirebaseAppCheckProviderAndroidOptions, ReactNativeFirebaseAppCheckProviderAppleOptions, ReactNativeFirebaseAppCheckProviderWebOptions, - ReactNativeFirebaseAppCheckProvider, } from '../lib'; describe('appCheck()', function () { @@ -84,8 +83,8 @@ describe('appCheck()', function () { }); it('`ReactNativeAppCheckProvider objects are properly exposed to end user', function () { - expect(ReactNativeFirebaseAppCheckProvider).toBeDefined(); - const provider = new ReactNativeFirebaseAppCheckProvider(); + expect(firebase.appCheck().newReactNativeFirebaseAppCheckProvider).toBeDefined(); + const provider = firebase.appCheck().newReactNativeFirebaseAppCheckProvider(); expect(provider.configure).toBeDefined(); const options = { debugToken: 'foo' } as ReactNativeFirebaseAppCheckProviderOptions; const appleOptions = { diff --git a/packages/app-check/lib/ReactNativeFirebaseAppCheckProvider.js b/packages/app-check/lib/ReactNativeFirebaseAppCheckProvider.js deleted file mode 100644 index b328d8aeba..0000000000 --- a/packages/app-check/lib/ReactNativeFirebaseAppCheckProvider.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class ReactNativeFirebaseAppCheckProvider { - providerOptions; - - constructor() {} - - configure(options) { - this.providerOptions = options; - } -} diff --git a/packages/app-check/lib/ReactNativeFirebaseAppCheckProvider.ts b/packages/app-check/lib/ReactNativeFirebaseAppCheckProvider.ts new file mode 100644 index 0000000000..bdc9887082 --- /dev/null +++ b/packages/app-check/lib/ReactNativeFirebaseAppCheckProvider.ts @@ -0,0 +1,21 @@ +import type { + AppCheckProvider, + AppCheckToken, + ReactNativeFirebaseAppCheckProviderOptionsMap as ProviderOptions, +} from './types/appcheck'; + +export default class ReactNativeFirebaseAppCheckProvider implements AppCheckProvider { + providerOptions?: ProviderOptions; + + constructor() {} + + configure(options: ProviderOptions): void { + this.providerOptions = options; + } + + async getToken(): Promise { + // This is a placeholder - the actual implementation is handled by the native modules + // This method exists to satisfy the AppCheckProvider interface + throw new Error('getToken() must be called after configure() and is handled by native modules'); + } +} diff --git a/packages/app-check/lib/index.d.ts b/packages/app-check/lib/index.d.ts deleted file mode 100644 index 6463612a84..0000000000 --- a/packages/app-check/lib/index.d.ts +++ /dev/null @@ -1,378 +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 { ReactNativeFirebase } from '@react-native-firebase/app'; - -/** - * Firebase App Check package for React Native. - * - * #### Example 1 - * - * Access the firebase export from the `appCheck` package: - * - * ```js - * import { firebase } from '@react-native-firebase/app-check'; - * - * // firebase.appCheck().X - * ``` - * - * #### Example 2 - * - * Using the default export from the `appCheck` package: - * - * ```js - * import appCheck from '@react-native-firebase/app-check'; - * - * // appCheck().X - * ``` - * - * #### Example 3 - * - * Using the default export from the `app` package: - * - * ```js - * import firebase from '@react-native-firebase/app'; - * import '@react-native-firebase/app-check'; - * - * // firebase.appCheck().X - * ``` - * - * @firebase app-check - */ -export namespace FirebaseAppCheckTypes { - import FirebaseModule = ReactNativeFirebase.FirebaseModule; - - /** - * An App Check provider. This can be either the built-in reCAPTCHA provider - * or a custom provider. For more on custom providers, see - * https://firebase.google.com/docs/app-check/web-custom-provider - */ - export interface AppCheckProvider { - /** - * Returns an AppCheck token. - */ - getToken(): Promise; - } - - /** - * Custom provider class. - * @public - */ - export class CustomProvider implements AppCheckProvider { - constructor(customProviderOptions: CustomProviderOptions); - } - - export interface CustomProviderOptions { - /** - * Function to get an App Check token through a custom provider - * service. - */ - getToken: () => Promise; - } - /** - * Options for App Check initialization. - */ - export interface AppCheckOptions { - /** - * The App Check provider to use. This can be either the built-in reCAPTCHA provider - * or a custom provider. - */ - provider: CustomProvider | ReactNativeFirebaseAppCheckProvider; - - /** - * If true, enables SDK to automatically - * refresh AppCheck token as needed. If undefined, the value will default - * to the value of `app.automaticDataCollectionEnabled`. That property - * defaults to false and can be set in the app config. - */ - isTokenAutoRefreshEnabled?: boolean; - } - - export type NextFn = (value: T) => void; - export type ErrorFn = (error: Error) => void; - export type CompleteFn = () => void; - - export interface Observer { - next: NextFn; - error: ErrorFn; - complete: CompleteFn; - } - - export type PartialObserver = Partial>; - - /** - * A function that unsubscribes from token changes. - */ - export type Unsubscribe = () => void; - - export interface ReactNativeFirebaseAppCheckProviderOptions { - /** - * debug token to use, if any. Defaults to undefined, pre-configure tokens in firebase web console if needed - */ - debugToken?: string; - } - - export interface ReactNativeFirebaseAppCheckProviderWebOptions extends ReactNativeFirebaseAppCheckProviderOptions { - /** - * The web provider to use, either `reCaptchaV3` or `reCaptchaEnterprise`, defaults to `reCaptchaV3` - */ - provider?: 'debug' | 'reCaptchaV3' | 'reCaptchaEnterprise'; - - /** - * siteKey for use in web queries, defaults to `none` - */ - siteKey?: string; - } - - export interface ReactNativeFirebaseAppCheckProviderAppleOptions extends ReactNativeFirebaseAppCheckProviderOptions { - /** - * The apple provider to use, either `deviceCheck` or `appAttest`, or `appAttestWithDeviceCheckFallback`, - * defaults to `DeviceCheck`. `appAttest` requires iOS 14+ or will fail, `appAttestWithDeviceCheckFallback` - * will use `appAttest` for iOS14+ and fallback to `deviceCheck` on devices with ios13 and lower - */ - provider?: 'debug' | 'deviceCheck' | 'appAttest' | 'appAttestWithDeviceCheckFallback'; - } - - export interface ReactNativeFirebaseAppCheckProviderAndroidOptions extends ReactNativeFirebaseAppCheckProviderOptions { - /** - * The android provider to use, either `debug` or `playIntegrity`. default is `playIntegrity`. - */ - provider?: 'debug' | 'playIntegrity'; - } - - export interface ReactNativeFirebaseAppCheckProvider extends AppCheckProvider { - /** - * Specify how the app check provider should be configured. The new configuration is - * in effect when this call returns. You must call `getToken()` - * after this call to get a token using the new configuration. - * This custom provider allows for delayed configuration and re-configuration on all platforms - * so AppCheck has the same experience across all platforms, with the only difference being the native - * providers you choose to use on each platform. - */ - configure(options: { - web?: ReactNativeFirebaseAppCheckProviderWebOptions; - android?: ReactNativeFirebaseAppCheckProviderAndroidOptions; - apple?: ReactNativeFirebaseAppCheckProviderAppleOptions; - isTokenAutoRefreshEnabled?: boolean; - }): void; - } - - /** - * Result returned by `getToken()`. - */ - export interface AppCheckTokenResult { - /** - * The token string in JWT format. - */ - readonly token: string; - } - /** - * The token returned from an `AppCheckProvider`. - */ - export interface AppCheckToken { - /** - * The token string in JWT format. - */ - readonly token: string; - /** - * The local timestamp after which the token will expire. - */ - readonly expireTimeMillis: number; - } - /** - * The result return from `onTokenChanged` - */ - export type AppCheckListenerResult = AppCheckToken & { readonly appName: string }; - - export interface Statics { - // firebase.appCheck.* static props go here - CustomProvider: typeof CustomProvider; - SDK_VERSION: string; - } - - /** - * The Firebase App Check service is available for the default app or a given app. - * - * #### Example 1 - * - * Get the appCheck instance for the **default app**: - * - * ```js - * const appCheckForDefaultApp = firebase.appCheck(); - * ``` - * - * #### Example 2 - * - * Get the appCheck instance for a **secondary app**: - *˚ - * ```js - * const otherApp = firebase.app('otherApp'); - * const appCheckForOtherApp = firebase.appCheck(otherApp); - * ``` - * - */ - export class Module extends FirebaseModule { - /** - * The current `FirebaseApp` instance for this Firebase service. - */ - app: ReactNativeFirebase.FirebaseApp; - - /** - * Create a ReactNativeFirebaseAppCheckProvider option for use in react-native-firebase - */ - newReactNativeFirebaseAppCheckProvider(): ReactNativeFirebaseAppCheckProvider; - - /** - * Initialize the AppCheck module. Note that in react-native-firebase AppCheckOptions must always - * be an object with a `provider` member containing `ReactNativeFirebaseAppCheckProvider` that has returned successfully - * from a call to the `configure` method, with sub-providers for the various platforms configured to meet your project - * requirements. This must be called prior to interacting with any firebase services protected by AppCheck - * - * @param options an AppCheckOptions with a configured ReactNativeFirebaseAppCheckProvider as the provider - */ - // TODO wrong types - initializeAppCheck(options: AppCheckOptions): Promise; - - /** - * Activate App Check - * On iOS App Check is activated with DeviceCheck provider simply by including the module, using the token auto refresh default or - * the specific value (if configured) in firebase.json, but calling this does no harm. - * On Android if you call this it will install the PlayIntegrity provider in release builds, the Debug provider if debuggable. - * On both platforms you may use this method to alter the token refresh setting after startup. - * On iOS if you want to set a specific AppCheckProviderFactory (for instance to FIRAppCheckDebugProviderFactory or - * FIRAppAttestProvider) you must manually do that in your AppDelegate.m prior to calling [FIRApp configure] - * - * @deprecated use initializeAppCheck to gain access to all platform providers and firebase-js-sdk v9 compatibility - * @param siteKeyOrProvider - This is ignored, Android uses DebugProviderFactory if the app is debuggable (https://firebase.google.com/docs/app-check/android/debug-provider) - * Android uses PlayIntegrityProviderFactory for release builds. - * iOS uses DeviceCheckProviderFactory by default unless altered in AppDelegate.m manually - * @param isTokenAutoRefreshEnabled - If true, enables SDK to automatically - * refresh AppCheck token as needed. If undefined, the value will default - * to the value of `app.automaticDataCollectionEnabled`. That property - * defaults to false and can be set in the app config. - */ - activate( - siteKeyOrProvider: string | AppCheckProvider, - isTokenAutoRefreshEnabled?: boolean, - ): Promise; - - /** - * Alter the token auto refresh setting. By default it will take the value of automaticDataCollectionEnabled from Info.plist / AndroidManifest.xml - * @param isTokenAutoRefreshEnabled - If true, the SDK automatically - * refreshes App Check tokens as needed. This overrides any value set - * during `activate()` or taken by default from automaticDataCollectionEnabled in plist / android manifest - */ - setTokenAutoRefreshEnabled(isTokenAutoRefreshEnabled: boolean): void; - - /** - * Requests Firebase App Check token. - * This method should only be used if you need to authorize requests to a non-Firebase backend. - * Requests to Firebase backend are authorized automatically if configured. - * - * @param forceRefresh - If true, a new Firebase App Check token is requested and the token cache is ignored. - * If false, the cached token is used if it exists and has not expired yet. - * In most cases, false should be used. True should only be used if the server explicitly returns an error, indicating a revoked token. - */ - getToken(forceRefresh?: boolean): Promise; - - /** - * Requests a Firebase App Check token. This method should be used only if you need to authorize requests - * to a non-Firebase backend. Returns limited-use tokens that are intended for use with your non-Firebase - * backend endpoints that are protected with Replay Protection (https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection). - * This method does not affect the token generation behavior of the getAppCheckToken() method. - */ - getLimitedUseToken(): Promise; - - /** - * Registers a listener to changes in the token state. There can be more - * than one listener registered at the same time for one or more - * App Check instances. The listeners call back on the UI thread whenever - * the current token associated with this App Check instance changes. - * - * @returns A function that unsubscribes this listener. - */ - // TODO wrong types - onTokenChanged(observer: PartialObserver): () => void; - - /** - * Registers a listener to changes in the token state. There can be more - * than one listener registered at the same time for one or more - * App Check instances. The listeners call back on the UI thread whenever - * the current token associated with this App Check instance changes. - * - * Token listeners do not exist in the native SDK for iOS, no token change events will be emitted on that platform. - * This is not yet implemented on Android, no token change events will be emitted until implemented. - * - * NOTE: Although an `onError` callback can be provided, it will - * never be called, Android sdk code doesn't provide handling for onError function - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the token stream is never-ending. - * - * @returns A function that unsubscribes this listener. - */ - // TODO wrong types - onTokenChanged( - onNext: (tokenResult: AppCheckListenerResult) => void, - onError?: (error: Error) => void, - onCompletion?: () => void, - ): () => void; - } -} - -type AppCheckNamespace = ReactNativeFirebase.FirebaseModuleWithStaticsAndApp< - FirebaseAppCheckTypes.Module, - FirebaseAppCheckTypes.Statics -> & { - appCheck: ReactNativeFirebase.FirebaseModuleWithStaticsAndApp< - FirebaseAppCheckTypes.Module, - FirebaseAppCheckTypes.Statics - >; - firebase: ReactNativeFirebase.Module; - app(name?: string): ReactNativeFirebase.FirebaseApp; -}; - -declare const defaultExport: AppCheckNamespace; - -export const firebase: ReactNativeFirebase.Module & { - appCheck: typeof defaultExport; - app( - name?: string, - ): ReactNativeFirebase.FirebaseApp & { appCheck(): FirebaseAppCheckTypes.Module }; -}; - -export default defaultExport; - -export * from './modular'; - -/** - * Attach namespace to `firebase.` and `FirebaseApp.`. - */ -declare module '@react-native-firebase/app' { - namespace ReactNativeFirebase { - import FirebaseModuleWithStaticsAndApp = ReactNativeFirebase.FirebaseModuleWithStaticsAndApp; - interface Module { - appCheck: FirebaseModuleWithStaticsAndApp< - FirebaseAppCheckTypes.Module, - FirebaseAppCheckTypes.Statics - >; - } - interface FirebaseApp { - appCheck(): FirebaseAppCheckTypes.Module; - } - } -} diff --git a/packages/app-check/lib/index.ts b/packages/app-check/lib/index.ts new file mode 100644 index 0000000000..6aaaf4f702 --- /dev/null +++ b/packages/app-check/lib/index.ts @@ -0,0 +1,46 @@ +/* + * 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. + * + */ + +// Export types from types/appcheck +export type { + AppCheckProvider, + CustomProviderOptions, + AppCheckOptions, + AppCheckToken, + AppCheckTokenResult, + AppCheckListenerResult, + PartialObserver, + Observer, + NextFn, + ErrorFn, + CompleteFn, + Unsubscribe, + ReactNativeFirebaseAppCheckProviderOptions, + ReactNativeFirebaseAppCheckProviderWebOptions, + ReactNativeFirebaseAppCheckProviderAppleOptions, + ReactNativeFirebaseAppCheckProviderAndroidOptions, + ReactNativeFirebaseAppCheckProvider, + AppCheck, + FirebaseAppCheckTypes, +} from './types/appcheck'; + +// Export modular API functions +export * from './modular'; + +// Export namespaced API +export * from './namespaced'; +export { default } from './namespaced'; diff --git a/packages/app-check/lib/modular.ts b/packages/app-check/lib/modular.ts new file mode 100644 index 0000000000..948f6c9004 --- /dev/null +++ b/packages/app-check/lib/modular.ts @@ -0,0 +1,157 @@ +/* + * 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 { getApp, type ReactNativeFirebase } from '@react-native-firebase/app'; +import { MODULAR_DEPRECATION_ARG } from '@react-native-firebase/app/lib/common'; +import ReactNativeFirebaseAppCheckProvider from './ReactNativeFirebaseAppCheckProvider'; + +import type { + AppCheck, + AppCheckOptions, + AppCheckTokenResult, + PartialObserver, + Unsubscribe, + AppCheckListenerResult, +} from './types/appcheck'; + +/** + * Activate App Check for the given app. Can be called only once per app. + * @param app - The app to initialize App Check for. Optional. + * @param options - App Check options. + * @returns Promise + */ +export async function initializeAppCheck( + app?: ReactNativeFirebase.FirebaseApp, + options?: AppCheckOptions, +): Promise { + if (app) { + const appInstance = getApp(app.name) as ReactNativeFirebase.FirebaseApp; + const appCheck = appInstance.appCheck(); + // @ts-ignore - Extra arg used by deprecation proxy to detect modular calls + await appCheck.initializeAppCheck.call(appCheck, options, MODULAR_DEPRECATION_ARG); + return appCheck; + } + const appInstance = getApp() as ReactNativeFirebase.FirebaseApp; + const appCheck = appInstance.appCheck(); + // @ts-ignore - Extra arg used by deprecation proxy to detect modular calls + await appCheck.initializeAppCheck.call(appCheck, options, MODULAR_DEPRECATION_ARG); + return appCheck; +} + +/** + * Get the current App Check token. Attaches to the most recent in-flight request if one is present. + * Returns null if no token is present and no token requests are in-flight. + * @param appCheckInstance - The App Check instance. + * @param forceRefresh - Whether to force refresh the token. Optional + * @returns Promise + */ +export function getToken( + appCheckInstance: AppCheck, + forceRefresh?: boolean, +): Promise { + return appCheckInstance.getToken.call( + appCheckInstance, + forceRefresh, + // @ts-ignore - Extra arg used by deprecation proxy to detect modular calls + MODULAR_DEPRECATION_ARG, + ) as Promise; +} + +/** + * Get a limited-use (consumable) App Check token. + * For use with server calls to firebase functions or custom backends using the firebase admin SDK. + * @param appCheckInstance - The App Check instance. + * @returns Promise + */ +export function getLimitedUseToken(appCheckInstance: AppCheck): Promise { + return appCheckInstance.getLimitedUseToken.call( + appCheckInstance, + // @ts-ignore - Extra arg used by deprecation proxy to detect modular calls + MODULAR_DEPRECATION_ARG, + ) as Promise; +} + +/** + * Set whether App Check will automatically refresh tokens as needed. + * @param appCheckInstance - The App Check instance. + * @param isAutoRefreshEnabled - Whether to enable auto-refresh. + */ +export function setTokenAutoRefreshEnabled( + appCheckInstance: AppCheck, + isAutoRefreshEnabled: boolean, +): void { + appCheckInstance.setTokenAutoRefreshEnabled.call( + appCheckInstance, + isAutoRefreshEnabled, + // @ts-ignore - Extra arg used by deprecation proxy to detect modular calls + MODULAR_DEPRECATION_ARG, + ); +} + +/** + * Registers a listener to changes in the token state. There can be more + * than one listener registered at the same time for one or more + * App Check instances. The listeners call back on the UI thread whenever + * the current token associated with this App Check instance changes. + * + * @param appCheckInstance - The App Check instance. + * @param listener - The listener to register. + * @returns Unsubscribe + */ +export function onTokenChanged( + appCheckInstance: AppCheck, + listener: PartialObserver, +): Unsubscribe; + +/** + * Registers a listener to changes in the token state. There can be more + * than one listener registered at the same time for one or more + * App Check instances. The listeners call back on the UI thread whenever + * the current token associated with this App Check instance changes. + * + * @param appCheckInstance - The App Check instance. + * @param onNext - The callback function for token changes. + * @param onError - Optional error callback. + * @param onCompletion - Optional completion callback. + * @returns Unsubscribe + */ +export function onTokenChanged( + appCheckInstance: AppCheck, + onNext: (tokenResult: AppCheckListenerResult) => void, + onError?: (error: Error) => void, + onCompletion?: () => void, +): Unsubscribe; + +export function onTokenChanged( + appCheckInstance: AppCheck, + onNextOrObserver: + | PartialObserver + | ((tokenResult: AppCheckListenerResult) => void), + onError?: (error: Error) => void, + onCompletion?: () => void, +): Unsubscribe { + return appCheckInstance.onTokenChanged.call( + appCheckInstance, + onNextOrObserver, + onError, + onCompletion, + // @ts-ignore - Extra arg used by deprecation proxy to detect modular calls + MODULAR_DEPRECATION_ARG, + ) as Unsubscribe; +} + +export { ReactNativeFirebaseAppCheckProvider }; diff --git a/packages/app-check/lib/modular/index.d.ts b/packages/app-check/lib/modular/index.d.ts deleted file mode 100644 index 338b6db50a..0000000000 --- a/packages/app-check/lib/modular/index.d.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { ReactNativeFirebase } from '@react-native-firebase/app'; -import { FirebaseAppCheckTypes } from '..'; - -import FirebaseApp = ReactNativeFirebase.FirebaseApp; -import AppCheck = FirebaseAppCheckTypes.Module; -import AppCheckOptions = FirebaseAppCheckTypes.AppCheckOptions; -import AppCheckTokenResult = FirebaseAppCheckTypes.AppCheckTokenResult; -import PartialObserver = FirebaseAppCheckTypes.PartialObserver; -import Unsubscribe = FirebaseAppCheckTypes.Unsubscribe; -import AppCheckProvider = FirebaseAppCheckTypes.AppCheckProvider; -import CustomProviderOptions = FirebaseAppCheckTypes.CustomProviderOptions; - -/** - * Activate App Check for the given app. Can be called only once per app. - * @param app - FirebaseApp. Optional. - * @param options - AppCheckOptions - * @returns {Promise} - */ -export function initializeAppCheck(app?: FirebaseApp, options?: AppCheckOptions): Promise; - -/** - * Get the current App Check token. Attaches to the most recent in-flight request if one is present. - * Returns null if no token is present and no token requests are in-flight. - * @param appCheckInstance - AppCheck - * @param forceRefresh - If true, will always try to fetch a fresh token. If false, will use a cached token if found in storage. - * @returns {Promise} - */ -export function getToken( - appCheckInstance: AppCheck, - forceRefresh?: boolean, -): Promise; - -/** - * Get a limited-use (consumable) App Check token. - * For use with server calls to firebase functions or custom backends using the firebase admin SDK - * @param appCheckInstance - AppCheck - * @returns {Promise} - */ -export function getLimitedUseToken(appCheckInstance: AppCheck): Promise; - -/** - * Registers a listener to changes in the token state. There can be more - * than one listener registered at the same time for one or more - * App Check instances. The listeners call back on the UI thread whenever - * the current token associated with this App Check instance changes. - * - * @returns A function that unsubscribes this listener. - */ -export function onTokenChanged( - appCheckInstance: AppCheck, - listener: PartialObserver, -): Unsubscribe; - -/** - * Registers a listener to changes in the token state. There can be more - * than one listener registered at the same time for one or more - * App Check instances. The listeners call back on the UI thread whenever - * the current token associated with this App Check instance changes. - * - * Token listeners do not exist in the native SDK for iOS, no token change events will be emitted on that platform. - * This is not yet implemented on Android, no token change events will be emitted until implemented. - * - * NOTE: Although an `onError` callback can be provided, it will - * never be called, Android sdk code doesn't provide handling for onError function - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the token stream is never-ending. - * - * @returns A function that unsubscribes this listener. - */ -export function onTokenChanged( - appCheckInstance: AppCheck, - onNext: (tokenResult: AppCheckListenerResult) => void, - onError?: (error: Error) => void, - onCompletion?: () => void, -): () => void; - -/** - * Set whether App Check will automatically refresh tokens as needed. - * @param appCheckInstance - AppCheck - * @param isAutoRefreshEnabled - boolean - */ -export function setTokenAutoRefreshEnabled( - appCheckInstance: AppCheck, - isAutoRefreshEnabled: boolean, -): void; - -/** - * Custom provider class. - * @public - */ -export class CustomProvider implements AppCheckProvider { - constructor(customProviderOptions: CustomProviderOptions); -} - -/** - * React-Native-Firebase AppCheckProvider that allows hot-swapping native AppCheck implementations - */ -export interface ReactNativeFirebaseAppCheckProviderOptions { - /** - * debug token to use, if any. Defaults to undefined, pre-configure tokens in firebase web console if needed - */ - debugToken?: string; -} - -export interface ReactNativeFirebaseAppCheckProviderWebOptions extends ReactNativeFirebaseAppCheckProviderOptions { - /** - * The web provider to use, either `reCaptchaV3` or `reCaptchaEnterprise`, defaults to `reCaptchaV3` - */ - provider?: 'debug' | 'reCaptchaV3' | 'reCaptchaEnterprise'; - - /** - * siteKey for use in web queries, defaults to `none` - */ - siteKey?: string; -} - -export interface ReactNativeFirebaseAppCheckProviderAppleOptions extends ReactNativeFirebaseAppCheckProviderOptions { - /** - * The apple provider to use, either `deviceCheck` or `appAttest`, or `appAttestWithDeviceCheckFallback`, - * defaults to `DeviceCheck`. `appAttest` requires iOS 14+ or will fail, `appAttestWithDeviceCheckFallback` - * will use `appAttest` for iOS14+ and fallback to `deviceCheck` on devices with ios13 and lower - */ - provider?: 'debug' | 'deviceCheck' | 'appAttest' | 'appAttestWithDeviceCheckFallback'; -} - -export interface ReactNativeFirebaseAppCheckProviderAndroidOptions extends ReactNativeFirebaseAppCheckProviderOptions { - /** - * The android provider to use, either `debug` or `playIntegrity`. default is `playIntegrity`. - */ - provider?: 'debug' | 'playIntegrity'; -} - -export class ReactNativeFirebaseAppCheckProvider extends AppCheckProvider { - /** - * Specify how the app check provider should be configured. The new configuration is - * in effect when this call returns. You must call `getToken()` - * after this call to get a token using the new configuration. - * This custom provider allows for delayed configuration and re-configuration on all platforms - * so AppCheck has the same experience across all platforms, with the only difference being the native - * providers you choose to use on each platform. - */ - configure(options: { - web?: ReactNativeFirebaseAppCheckProviderWebOptions; - android?: ReactNativeFirebaseAppCheckProviderAndroidOptions; - apple?: ReactNativeFirebaseAppCheckProviderAppleOptions; - isTokenAutoRefreshEnabled?: boolean; - }): void; -} diff --git a/packages/app-check/lib/modular/index.js b/packages/app-check/lib/modular/index.js deleted file mode 100644 index 649adcb182..0000000000 --- a/packages/app-check/lib/modular/index.js +++ /dev/null @@ -1,102 +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 { getApp } from '@react-native-firebase/app'; -import { MODULAR_DEPRECATION_ARG } from '../../../app/lib/common'; -import ReactNativeFirebaseAppCheckProvider from '../ReactNativeFirebaseAppCheckProvider'; - -/** - * @typedef {import('@firebase/app').FirebaseApp} FirebaseApp - * @typedef {import('..').FirebaseAppCheckTypes.Module} AppCheck - * @typedef {import('..').FirebaseAppCheckTypes.AppCheckTokenResult} AppCheckTokenResult - * @typedef {import('..').FirebaseAppCheckTypes.Unsubscribe} Unsubscribe - * @typedef {import('..').FirebaseAppCheckTypes.PartialObserver} PartialObserver - * @typedef {import('..').FirebaseAppCheckTypes.AppCheckOptions} AppCheckOptions - */ - -/** - * Activate App Check for the given app. Can be called only once per app. - * @param {FirebaseApp} [app] - The app to initialize App Check for. Optional. - * @param {AppCheckOptions} options - App Check options. - * @returns {Promise} - */ -export async function initializeAppCheck(app, options) { - if (app) { - const appCheck = getApp(app.name).appCheck(); - await appCheck.initializeAppCheck.call(appCheck, options, MODULAR_DEPRECATION_ARG); - return appCheck; - } - const appCheck = getApp().appCheck(); - await appCheck.initializeAppCheck.call(appCheck, options, MODULAR_DEPRECATION_ARG); - return appCheck; -} - -/** - * Get the current App Check token. Attaches to the most recent in-flight request if one is present. - * Returns null if no token is present and no token requests are in-flight. - * @param {AppCheck} appCheckInstance - The App Check instance. - * @param {boolean} forceRefresh - Whether to force refresh the token. Optional - * @returns {Promise} - */ -export function getToken(appCheckInstance, forceRefresh) { - return appCheckInstance.getToken.call(appCheckInstance, forceRefresh, MODULAR_DEPRECATION_ARG); -} - -/** - * Get a limited-use (consumable) App Check token. - * For use with server calls to firebase functions or custom backends using the firebase admin SDK. - * @param {AppCheck} appCheckInstance - The App Check instance. - * @returns {Promise} - */ -export function getLimitedUseToken(appCheckInstance) { - return appCheckInstance.getLimitedUseToken.call(appCheckInstance, MODULAR_DEPRECATION_ARG); -} - -/** - * Set whether App Check will automatically refresh tokens as needed. - * @param {AppCheck} appCheckInstance - The App Check instance. - * @param {boolean} isAutoRefreshEnabled - Whether to enable auto-refresh. - */ -export function setTokenAutoRefreshEnabled(appCheckInstance, isAutoRefreshEnabled) { - return appCheckInstance.setTokenAutoRefreshEnabled.call( - appCheckInstance, - isAutoRefreshEnabled, - MODULAR_DEPRECATION_ARG, - ); -} - -/** - * Registers a listener to changes in the token state. There can be more - * than one listener registered at the same time for one or more - * App Check instances. The listeners call back on the UI thread whenever - * the current token associated with this App Check instance changes. - * - * @param {AppCheck} appCheckInstance - The App Check instance. - * @param {PartialObserver} listener - The listener to register. - * @returns {Unsubscribe} - */ -export function onTokenChanged(appCheckInstance, onNextOrObserver, onError, onCompletion) { - return appCheckInstance.onTokenChanged.call( - appCheckInstance, - onNextOrObserver, - onError, - onCompletion, - MODULAR_DEPRECATION_ARG, - ); -} - -export { ReactNativeFirebaseAppCheckProvider }; diff --git a/packages/app-check/lib/index.js b/packages/app-check/lib/namespaced.ts similarity index 61% rename from packages/app-check/lib/index.js rename to packages/app-check/lib/namespaced.ts index c596fedc76..a1280cf4f1 100644 --- a/packages/app-check/lib/index.js +++ b/packages/app-check/lib/namespaced.ts @@ -30,19 +30,33 @@ import { FirebaseModule, getFirebaseRoot, } from '@react-native-firebase/app/lib/internal'; +import type { ModuleConfig } from '@react-native-firebase/app/lib/internal'; import { Platform } from 'react-native'; import ReactNativeFirebaseAppCheckProvider from './ReactNativeFirebaseAppCheckProvider'; import { setReactNativeModule } from '@react-native-firebase/app/lib/internal/nativeModule'; import fallBackModule from './web/RNFBAppCheckModule'; - -import version from './version'; +import { version } from './version'; +import type { + CustomProviderOptions, + AppCheckProvider, + AppCheckTokenResult, + AppCheckOptions, + AppCheckListenerResult, + PartialObserver, + AppCheck, + AppCheckStatics, + ProviderWithOptions, +} from './types/appcheck'; +import type { ReactNativeFirebase } from '@react-native-firebase/app'; const namespace = 'appCheck'; const nativeModuleName = 'RNFBAppCheckModule'; -export class CustomProvider { - constructor(_customProviderOptions) { +export class CustomProvider implements AppCheckProvider { + private _customProviderOptions: CustomProviderOptions; + + constructor(_customProviderOptions: CustomProviderOptions) { if (!isObject(_customProviderOptions)) { throw new Error('Invalid configuration: no provider options defined.'); } @@ -61,9 +75,29 @@ const statics = { CustomProvider, }; +/** + * Type guard to check if a provider has providerOptions. + * This provides proper type narrowing for providers that support platform-specific configuration. + */ +function hasProviderOptions( + provider: AppCheckOptions['provider'], +): provider is ProviderWithOptions { + return ( + provider !== undefined && + 'providerOptions' in provider && + provider.providerOptions !== undefined + ); +} + class FirebaseAppCheckModule extends FirebaseModule { - constructor(...args) { - super(...args); + _listenerCount: number; + + constructor( + app: ReactNativeFirebase.FirebaseAppBase, + config: ModuleConfig, + customUrlOrRegion?: string | null, + ) { + super(app, config, customUrlOrRegion); this.emitter.addListener(this.eventNameForApp('appCheck_token_changed'), event => { this.emitter.emit(this.eventNameForApp('onAppCheckTokenChanged'), event); @@ -72,18 +106,18 @@ class FirebaseAppCheckModule extends FirebaseModule { this._listenerCount = 0; } - getIsTokenRefreshEnabledDefault() { + getIsTokenRefreshEnabledDefault(): boolean | undefined { // no default to start - isTokenAutoRefreshEnabled = undefined; + let isTokenAutoRefreshEnabled: boolean | undefined = undefined; return isTokenAutoRefreshEnabled; } - newReactNativeFirebaseAppCheckProvider() { + newReactNativeFirebaseAppCheckProvider(): ReactNativeFirebaseAppCheckProvider { return new ReactNativeFirebaseAppCheckProvider(); } - initializeAppCheck(options) { + initializeAppCheck(options: AppCheckOptions): Promise { if (isOther) { if (!isObject(options)) { throw new Error('Invalid configuration: no options defined.'); @@ -95,12 +129,18 @@ class FirebaseAppCheckModule extends FirebaseModule { } // determine token refresh setting, if not specified if (!isBoolean(options.isTokenAutoRefreshEnabled)) { - options.isTokenAutoRefreshEnabled = this.firebaseJson.app_check_token_auto_refresh; + const tokenRefresh = this.firebaseJson.app_check_token_auto_refresh; + if (isBoolean(tokenRefresh)) { + options.isTokenAutoRefreshEnabled = tokenRefresh; + } } // If that was not defined, attempt to use app-wide data collection setting per docs: if (!isBoolean(options.isTokenAutoRefreshEnabled)) { - options.isTokenAutoRefreshEnabled = this.firebaseJson.app_data_collection_default_enabled; + const dataCollection = this.firebaseJson.app_data_collection_default_enabled; + if (isBoolean(dataCollection)) { + options.isTokenAutoRefreshEnabled = dataCollection; + } } // If that also was not defined, the default is documented as true. @@ -109,35 +149,39 @@ class FirebaseAppCheckModule extends FirebaseModule { } this.native.setTokenAutoRefreshEnabled(options.isTokenAutoRefreshEnabled); - if (options.provider === undefined || options.provider.providerOptions === undefined) { + if (!hasProviderOptions(options.provider)) { throw new Error('Invalid configuration: no provider or no provider options defined.'); } + const provider = options.provider; if (Platform.OS === 'android') { - if (!isString(options.provider.providerOptions.android.provider)) { + if (!isString(provider.providerOptions?.android?.provider)) { throw new Error( 'Invalid configuration: no android provider configured while on android platform.', ); } return this.native.configureProvider( - options.provider.providerOptions.android.provider, - options.provider.providerOptions.android.debugToken, + provider.providerOptions.android.provider, + provider.providerOptions.android.debugToken, ); } if (Platform.OS === 'ios' || Platform.OS === 'macos') { - if (!isString(options.provider.providerOptions.apple.provider)) { + if (!isString(provider.providerOptions?.apple?.provider)) { throw new Error( 'Invalid configuration: no apple provider configured while on apple platform.', ); } return this.native.configureProvider( - options.provider.providerOptions.apple.provider, - options.provider.providerOptions.apple.debugToken, + provider.providerOptions.apple.provider, + provider.providerOptions.apple.debugToken, ); } throw new Error('Unsupported platform: ' + Platform.OS); } - activate(siteKeyOrProvider, isTokenAutoRefreshEnabled) { + activate( + siteKeyOrProvider: string | AppCheckProvider, + isTokenAutoRefreshEnabled?: boolean, + ): Promise { if (isOther) { throw new Error('firebase.appCheck().activate(*) is not supported on other platforms'); } @@ -146,7 +190,7 @@ class FirebaseAppCheckModule extends FirebaseModule { } // We wrap our new flexible interface, with compatible defaults - rnfbProvider = new ReactNativeFirebaseAppCheckProvider(); + const rnfbProvider = new ReactNativeFirebaseAppCheckProvider(); rnfbProvider.configure({ android: { provider: 'playIntegrity', @@ -164,11 +208,11 @@ class FirebaseAppCheckModule extends FirebaseModule { } // TODO this is an async call - setTokenAutoRefreshEnabled(isTokenAutoRefreshEnabled) { + setTokenAutoRefreshEnabled(isTokenAutoRefreshEnabled: boolean): void { this.native.setTokenAutoRefreshEnabled(isTokenAutoRefreshEnabled); } - getToken(forceRefresh) { + getToken(forceRefresh?: boolean): Promise { if (!forceRefresh) { return this.native.getToken(false); } else { @@ -176,19 +220,28 @@ class FirebaseAppCheckModule extends FirebaseModule { } } - getLimitedUseToken() { + getLimitedUseToken(): Promise { return this.native.getLimitedUseToken(); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onTokenChanged(onNextOrObserver, onError, onCompletion) { + onTokenChanged( + onNextOrObserver: + | PartialObserver + | ((tokenResult: AppCheckListenerResult) => void), + _onError?: (error: Error) => void, + _onCompletion?: () => void, + ): () => void { // iOS does not provide any native listening feature if (isIOS) { // eslint-disable-next-line no-console console.warn('onTokenChanged is not implemented on IOS, only for Android'); return () => {}; } - const nextFn = parseListenerOrObserver(onNextOrObserver); + const nextFn = parseListenerOrObserver( + onNextOrObserver as + | ((value: AppCheckListenerResult) => void) + | { next: (value: AppCheckListenerResult) => void }, + ); // let errorFn = function () { }; // if (onNextOrObserver.error != null) { // errorFn = onNextOrObserver.error.bind(onNextOrObserver); @@ -211,12 +264,9 @@ class FirebaseAppCheckModule extends FirebaseModule { } } -// import { SDK_VERSION } from '@react-native-firebase/app-check'; export const SDK_VERSION = version; -// import appCheck from '@react-native-firebase/app-check'; -// appCheck().X(...); -export default createModuleNamespace({ +const appCheckNamespace = createModuleNamespace({ statics, version, namespace, @@ -227,12 +277,29 @@ export default createModuleNamespace({ ModuleClass: FirebaseAppCheckModule, }); -export * from './modular'; +type AppCheckNamespace = ReactNativeFirebase.FirebaseModuleWithStaticsAndApp< + AppCheck, + AppCheckStatics +> & { + appCheck: ReactNativeFirebase.FirebaseModuleWithStaticsAndApp; + firebase: ReactNativeFirebase.Module; + app(name?: string): ReactNativeFirebase.FirebaseApp; +}; + +// import appCheck from '@react-native-firebase/app-check'; +// appCheck().X(...); +export default appCheckNamespace as unknown as AppCheckNamespace; // import appCheck, { firebase } from '@react-native-firebase/app-check'; // appCheck().X(...); // firebase.appCheck().X(...); -export const firebase = getFirebaseRoot(); +export const firebase = + getFirebaseRoot() as unknown as ReactNativeFirebase.FirebaseNamespacedExport< + 'appCheck', + AppCheck, + AppCheckStatics, + false + >; // Register the interop module for non-native platforms. -setReactNativeModule(nativeModuleName, fallBackModule); +setReactNativeModule(nativeModuleName, fallBackModule as unknown as Record); diff --git a/packages/app-check/lib/types/appcheck.ts b/packages/app-check/lib/types/appcheck.ts new file mode 100644 index 0000000000..e26c72bb29 --- /dev/null +++ b/packages/app-check/lib/types/appcheck.ts @@ -0,0 +1,386 @@ +/* + * 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 '@react-native-firebase/app'; + +// ============ Provider Types ============ + +/** + * An App Check provider. This can be either the built-in reCAPTCHA provider + * or a custom provider. For more on custom providers, see + * https://firebase.google.com/docs/app-check/web-custom-provider + */ +export interface AppCheckProvider { + /** + * Returns an AppCheck token. + */ + getToken(): Promise; +} + +/** + * Function to get an App Check token through a custom provider service. + */ +export interface CustomProviderOptions { + getToken: () => Promise; +} + +// ============ Options & Result Types ============ + +/** + * Custom provider class. + * @public + */ +export declare class CustomProvider implements AppCheckProvider { + constructor(customProviderOptions: CustomProviderOptions); + getToken(): Promise; +} + +/** + * Options for App Check initialization. + */ +export interface AppCheckOptions { + /** + * The App Check provider to use. This can be either the built-in reCAPTCHA provider + * or a custom provider. For convenience, you can also pass an object with providerOptions + * directly, which will be accepted by the runtime. + */ + provider: + | CustomProvider + | ReactNativeFirebaseAppCheckProvider + | ReactNativeFirebaseAppCheckProviderConfig; + + /** + * If true, enables SDK to automatically + * refresh AppCheck token as needed. If undefined, the value will default + * to the value of `app.automaticDataCollectionEnabled`. That property + * defaults to false and can be set in the app config. + */ + isTokenAutoRefreshEnabled?: boolean; +} + +/** + * The token returned from an `AppCheckProvider`. + */ +export interface AppCheckToken { + /** + * The token string in JWT format. + */ + readonly token: string; + /** + * The local timestamp after which the token will expire. + */ + readonly expireTimeMillis: number; +} + +/** + * Result returned by `getToken()`. + */ +export interface AppCheckTokenResult { + /** + * The token string in JWT format. + */ + readonly token: string; +} + +/** + * The result return from `onTokenChanged` + */ +export type AppCheckListenerResult = AppCheckToken & { readonly appName: string }; + +// ============ Observer Types ============ + +export type NextFn = (value: T) => void; +export type ErrorFn = (error: Error) => void; +export type CompleteFn = () => void; + +export interface Observer { + next: NextFn; + error: ErrorFn; + complete: CompleteFn; +} + +export type PartialObserver = Partial>; + +/** + * A function that unsubscribes from token changes. + */ +export type Unsubscribe = () => void; + +// ============ ReactNativeFirebaseAppCheckProvider Types ============ + +export interface ReactNativeFirebaseAppCheckProviderOptions { + /** + * debug token to use, if any. Defaults to undefined, pre-configure tokens in firebase web console if needed + */ + debugToken?: string; +} + +export interface ReactNativeFirebaseAppCheckProviderWebOptions extends ReactNativeFirebaseAppCheckProviderOptions { + /** + * The web provider to use, either `reCaptchaV3` or `reCaptchaEnterprise`, defaults to `reCaptchaV3` + */ + provider?: 'debug' | 'reCaptchaV3' | 'reCaptchaEnterprise'; + + /** + * siteKey for use in web queries, defaults to `none` + */ + siteKey?: string; +} + +export interface ReactNativeFirebaseAppCheckProviderAppleOptions extends ReactNativeFirebaseAppCheckProviderOptions { + /** + * The apple provider to use, either `deviceCheck` or `appAttest`, or `appAttestWithDeviceCheckFallback`, + * defaults to `DeviceCheck`. `appAttest` requires iOS 14+ or will fail, `appAttestWithDeviceCheckFallback` + * will use `appAttest` for iOS14+ and fallback to `deviceCheck` on devices with ios13 and lower + */ + provider?: 'debug' | 'deviceCheck' | 'appAttest' | 'appAttestWithDeviceCheckFallback'; +} + +export interface ReactNativeFirebaseAppCheckProviderAndroidOptions extends ReactNativeFirebaseAppCheckProviderOptions { + /** + * The android provider to use, either `debug` or `playIntegrity`. default is `playIntegrity`. + */ + provider?: 'debug' | 'playIntegrity'; +} + +/** + * Platform-specific provider options configuration. + */ +export type ReactNativeFirebaseAppCheckProviderOptionsMap = { + web?: ReactNativeFirebaseAppCheckProviderWebOptions; + android?: ReactNativeFirebaseAppCheckProviderAndroidOptions; + apple?: ReactNativeFirebaseAppCheckProviderAppleOptions; + isTokenAutoRefreshEnabled?: boolean; +}; + +/** + * Configuration object for ReactNativeFirebaseAppCheckProvider + * that can be passed directly with providerOptions (for convenience in initialization). + * The runtime accepts objects with providerOptions even if they don't have + * getToken() and configure() methods. + */ +export interface ReactNativeFirebaseAppCheckProviderConfig { + providerOptions: ReactNativeFirebaseAppCheckProviderOptionsMap; +} + +export interface ReactNativeFirebaseAppCheckProvider extends AppCheckProvider { + /** + * Provider options for platform-specific configuration. + * This is set when configure() is called. + */ + providerOptions?: ReactNativeFirebaseAppCheckProviderOptionsMap; + + /** + * Specify how the app check provider should be configured. The new configuration is + * in effect when this call returns. You must call `getToken()` + * after this call to get a token using the new configuration. + * This custom provider allows for delayed configuration and re-configuration on all platforms + * so AppCheck has the same experience across all platforms, with the only difference being the native + * providers you choose to use on each platform. + */ + configure(options: ReactNativeFirebaseAppCheckProviderOptionsMap): void; +} + +/** + * Type representing providers that have providerOptions. + * Used for type narrowing in runtime code. + */ +export type ProviderWithOptions = + | ReactNativeFirebaseAppCheckProvider + | ReactNativeFirebaseAppCheckProviderConfig; + +// ============ Module Interface ============ + +/** + * App Check module instance - returned from firebase.appCheck() or firebase.app().appCheck() + */ +export interface AppCheck extends ReactNativeFirebase.FirebaseModule { + /** The FirebaseApp this module is associated with */ + app: ReactNativeFirebase.FirebaseApp; + + /** + * Create a ReactNativeFirebaseAppCheckProvider option for use in react-native-firebase + */ + newReactNativeFirebaseAppCheckProvider(): ReactNativeFirebaseAppCheckProvider; + + /** + * Initialize the AppCheck module. Note that in react-native-firebase AppCheckOptions must always + * be an object with a `provider` member containing `ReactNativeFirebaseAppCheckProvider` that has returned successfully + * from a call to the `configure` method, with sub-providers for the various platforms configured to meet your project + * requirements. This must be called prior to interacting with any firebase services protected by AppCheck + * + * @param options an AppCheckOptions with a configured ReactNativeFirebaseAppCheckProvider as the provider + */ + initializeAppCheck(options: AppCheckOptions): Promise; + + /** + * Activate App Check + * On iOS App Check is activated with DeviceCheck provider simply by including the module, using the token auto refresh default or + * the specific value (if configured) in firebase.json, but calling this does no harm. + * On Android if you call this it will install the PlayIntegrity provider in release builds, the Debug provider if debuggable. + * On both platforms you may use this method to alter the token refresh setting after startup. + * On iOS if you want to set a specific AppCheckProviderFactory (for instance to FIRAppCheckDebugProviderFactory or + * FIRAppAttestProvider) you must manually do that in your AppDelegate.m prior to calling [FIRApp configure] + * + * @deprecated use initializeAppCheck to gain access to all platform providers and firebase-js-sdk v9 compatibility + * @param siteKeyOrProvider - This is ignored, Android uses DebugProviderFactory if the app is debuggable (https://firebase.google.com/docs/app-check/android/debug-provider) + * Android uses PlayIntegrityProviderFactory for release builds. + * iOS uses DeviceCheckProviderFactory by default unless altered in AppDelegate.m manually + * @param isTokenAutoRefreshEnabled - If true, enables SDK to automatically + * refresh AppCheck token as needed. If undefined, the value will default + * to the value of `app.automaticDataCollectionEnabled`. That property + * defaults to false and can be set in the app config. + */ + activate( + siteKeyOrProvider: string | AppCheckProvider, + isTokenAutoRefreshEnabled?: boolean, + ): Promise; + + /** + * Alter the token auto refresh setting. By default it will take the value of automaticDataCollectionEnabled from Info.plist / AndroidManifest.xml + * @param isTokenAutoRefreshEnabled - If true, the SDK automatically + * refreshes App Check tokens as needed. This overrides any value set + * during `activate()` or taken by default from automaticDataCollectionEnabled in plist / android manifest + */ + setTokenAutoRefreshEnabled(isTokenAutoRefreshEnabled: boolean): void; + + /** + * Requests Firebase App Check token. + * This method should only be used if you need to authorize requests to a non-Firebase backend. + * Requests to Firebase backend are authorized automatically if configured. + * + * @param forceRefresh - If true, a new Firebase App Check token is requested and the token cache is ignored. + * If false, the cached token is used if it exists and has not expired yet. + * In most cases, false should be used. True should only be used if the server explicitly returns an error, indicating a revoked token. + */ + getToken(forceRefresh?: boolean): Promise; + + /** + * Requests a Firebase App Check token. This method should be used only if you need to authorize requests + * to a non-Firebase backend. Returns limited-use tokens that are intended for use with your non-Firebase + * backend endpoints that are protected with Replay Protection (https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection). + * This method does not affect the token generation behavior of the getAppCheckToken() method. + */ + getLimitedUseToken(): Promise; + + /** + * Registers a listener to changes in the token state. There can be more + * than one listener registered at the same time for one or more + * App Check instances. The listeners call back on the UI thread whenever + * the current token associated with this App Check instance changes. + * + * @returns A function that unsubscribes this listener. + */ + onTokenChanged(observer: PartialObserver): () => void; + + /** + * Registers a listener to changes in the token state. There can be more + * than one listener registered at the same time for one or more + * App Check instances. The listeners call back on the UI thread whenever + * the current token associated with this App Check instance changes. + * + * Token listeners do not exist in the native SDK for iOS, no token change events will be emitted on that platform. + * This is not yet implemented on Android, no token change events will be emitted until implemented. + * + * NOTE: Although an `onError` callback can be provided, it will + * never be called, Android sdk code doesn't provide handling for onError function + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the token stream is never-ending. + * + * @returns A function that unsubscribes this listener. + */ + onTokenChanged( + onNext: (tokenResult: AppCheckListenerResult) => void, + onError?: (error: Error) => void, + onCompletion?: () => void, + ): () => void; +} + +// ============ Statics Interface ============ + +/** + * Static properties available on firebase.appCheck + */ +export interface AppCheckStatics { + CustomProvider: new (customProviderOptions: CustomProviderOptions) => CustomProvider; + SDK_VERSION: string; +} + +/** + * FirebaseApp type with appCheck() method. + * @deprecated Import FirebaseApp from '@react-native-firebase/app' instead. + * The appCheck() method is added via module augmentation. + */ +export type FirebaseApp = ReactNativeFirebase.FirebaseApp; + +// ============ Module Augmentation ============ + +/* eslint-disable @typescript-eslint/no-namespace */ +declare module '@react-native-firebase/app' { + namespace ReactNativeFirebase { + interface Module { + appCheck: FirebaseModuleWithStaticsAndApp; + } + interface FirebaseApp { + appCheck(): AppCheck; + } + } +} +/* eslint-enable @typescript-eslint/no-namespace */ + +// ============ Backwards Compatibility Namespace ============ + +// Helper types to reference outer scope types within the namespace +// These are needed because TypeScript can't directly alias types with the same name +type _AppCheckProvider = AppCheckProvider; +type _CustomProviderOptions = CustomProviderOptions; +type _AppCheckOptions = AppCheckOptions; +type _AppCheckToken = AppCheckToken; +type _AppCheckTokenResult = AppCheckTokenResult; +type _AppCheckListenerResult = AppCheckListenerResult; +type _AppCheck = AppCheck; +type _AppCheckStatics = AppCheckStatics; + +/** + * @deprecated Use the exported types directly instead. + * FirebaseAppCheckTypes namespace is kept for backwards compatibility. + */ +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace FirebaseAppCheckTypes { + // Short name aliases referencing top-level types + export type Provider = AppCheckProvider; + export type ProviderOptions = CustomProviderOptions; + export type Options = AppCheckOptions; + export type Token = AppCheckToken; + export type TokenResult = AppCheckTokenResult; + export type ListenerResult = AppCheckListenerResult; + export type Statics = AppCheckStatics; + export type Module = AppCheck; + + // AppCheck* aliases that reference the exported types above via helper types + // These provide backwards compatibility for code using FirebaseAppCheckTypes.AppCheckProvider + export type AppCheckProvider = _AppCheckProvider; + export type CustomProviderOptions = _CustomProviderOptions; + export type AppCheckOptions = _AppCheckOptions; + export type AppCheckToken = _AppCheckToken; + export type AppCheckTokenResult = _AppCheckTokenResult; + export type AppCheckListenerResult = _AppCheckListenerResult; + export type AppCheck = _AppCheck; + export type AppCheckStatics = _AppCheckStatics; +} +/* eslint-enable @typescript-eslint/no-namespace */ diff --git a/packages/app-check/lib/web/RNFBAppCheckModule.android.ts b/packages/app-check/lib/web/RNFBAppCheckModule.android.ts new file mode 100644 index 0000000000..6945829a16 --- /dev/null +++ b/packages/app-check/lib/web/RNFBAppCheckModule.android.ts @@ -0,0 +1,4 @@ +// No-op for android. +const module: Record = {}; + +export default module; diff --git a/packages/app-check/lib/web/RNFBAppCheckModule.ios.ts b/packages/app-check/lib/web/RNFBAppCheckModule.ios.ts new file mode 100644 index 0000000000..055f3bce5b --- /dev/null +++ b/packages/app-check/lib/web/RNFBAppCheckModule.ios.ts @@ -0,0 +1,4 @@ +// No-op for ios. +const module: Record = {}; + +export default module; diff --git a/packages/app-check/lib/web/RNFBAppCheckModule.ts b/packages/app-check/lib/web/RNFBAppCheckModule.ts new file mode 100644 index 0000000000..ab634e007b --- /dev/null +++ b/packages/app-check/lib/web/RNFBAppCheckModule.ts @@ -0,0 +1,115 @@ +import { + getApp, + initializeAppCheck, + getToken, + getLimitedUseToken, + setTokenAutoRefreshEnabled, + CustomProvider, + onTokenChanged, + makeIDBAvailable, +} from '@react-native-firebase/app/lib/internal/web/firebaseAppCheck'; +import { guard, emitEvent } from '@react-native-firebase/app/lib/internal/web/utils'; +import type { FirebaseAppCheckTypes } from '../types/appcheck'; + +let appCheckInstances: Record = {}; +let listenersForApp: Record void> = {}; + +function getAppCheckInstanceForApp(appName: string): any { + if (!appCheckInstances[appName]) { + throw new Error( + `firebase AppCheck instance for app ${appName} has not been initialized, ensure you have called initializeAppCheck() first.`, + ); + } + return appCheckInstances[appName]; +} + +interface AppCheckModule { + initializeAppCheck( + appName: string, + options: FirebaseAppCheckTypes.AppCheckOptions, + ): Promise; + setTokenAutoRefreshEnabled(appName: string, isTokenAutoRefreshEnabled: boolean): Promise; + getLimitedUseToken(appName: string): Promise; + getToken( + appName: string, + forceRefresh: boolean, + ): Promise; + addAppCheckListener(appName: string): Promise; + removeAppCheckListener(appName: string): Promise; +} + +/** + * This is a 'NativeModule' for the web platform. + * Methods here are identical to the ones found in + * the native android/ios modules e.g. `@ReactMethod` annotated + * java methods on Android. + */ +const module: AppCheckModule = { + initializeAppCheck(appName: string, options: FirebaseAppCheckTypes.AppCheckOptions) { + makeIDBAvailable(); + return guard(async () => { + if (appCheckInstances[appName]) { + return; + } + const { provider, isTokenAutoRefreshEnabled } = options; + const _provider = new CustomProvider({ + getToken() { + if ('getToken' in provider && typeof provider.getToken === 'function') { + return provider.getToken(); + } + throw new Error('Provider does not have a getToken method'); + }, + }); + appCheckInstances[appName] = initializeAppCheck(getApp(appName), { + provider: _provider, + isTokenAutoRefreshEnabled, + }); + }); + }, + setTokenAutoRefreshEnabled(appName: string, isTokenAutoRefreshEnabled: boolean) { + return guard(async () => { + const instance = getAppCheckInstanceForApp(appName); + setTokenAutoRefreshEnabled(instance, isTokenAutoRefreshEnabled); + }); + }, + getLimitedUseToken(appName: string) { + return guard(async () => { + const instance = getAppCheckInstanceForApp(appName); + return getLimitedUseToken(instance); + }); + }, + getToken(appName: string, forceRefresh: boolean) { + return guard(async () => { + const instance = getAppCheckInstanceForApp(appName); + return getToken(instance, forceRefresh); + }); + }, + addAppCheckListener(appName: string) { + return guard(async () => { + if (listenersForApp[appName]) { + return; + } + const instance = getAppCheckInstanceForApp(appName); + listenersForApp[appName] = onTokenChanged( + instance, + (tokenResult: FirebaseAppCheckTypes.AppCheckTokenResult) => { + emitEvent('appCheck_token_changed', { + appName, + ...tokenResult, + }); + }, + ); + }); + }, + removeAppCheckListener(appName: string) { + return guard(async () => { + if (!listenersForApp[appName]) { + return; + } + listenersForApp[appName](); + delete listenersForApp[appName]; + }); + }, +}; + +export default module; diff --git a/packages/app-check/package.json b/packages/app-check/package.json index a17502d2cc..1b59433579 100644 --- a/packages/app-check/package.json +++ b/packages/app-check/package.json @@ -3,14 +3,16 @@ "version": "23.7.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - App Check", - "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", + "build": "genversion --esm --semi lib/version.ts", "build:clean": "rimraf android/build && rimraf ios/build", "build:plugin": "rimraf plugin/build && tsc --build plugin", "lint:plugin": "eslint plugin/src/*", - "prepare": "yarn run build && yarn run build:plugin" + "compile": "bob build", + "prepare": "yarn run build && yarn compile && yarn run build:plugin" }, "repository": { "type": "git", @@ -29,7 +31,9 @@ "expo": ">=47.0.0" }, "devDependencies": { - "expo": "^54.0.27" + "expo": "^54.0.27", + "react-native-builder-bob": "^0.40.12", + "typescript": "^5.8.3" }, "peerDependenciesMeta": { "expo": { @@ -39,5 +43,57 @@ "publishConfig": { "access": "public", "provenance": true - } + }, + "exports": { + ".": { + "source": "./lib/index.ts", + "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", + "plugin/src", + "tsconfig.json", + "plugin/tsconfig.json", + "dist", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], + "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/" + ] } diff --git a/packages/app-check/tsconfig.json b/packages/app-check/tsconfig.json new file mode 100644 index 0000000000..34992f7207 --- /dev/null +++ b/packages/app-check/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../tsconfig.packages.base.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": ".", + "paths": { + "@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"] + } + }, + "include": ["lib/**/*", "../app/lib/internal/global.d.ts"], + "exclude": ["node_modules", "dist", "__tests__", "**/*.test.ts"] +} diff --git a/packages/app-check/type-test.ts b/packages/app-check/type-test.ts index 1195fe3151..3314f4ca9d 100644 --- a/packages/app-check/type-test.ts +++ b/packages/app-check/type-test.ts @@ -7,7 +7,6 @@ import appCheck, { setTokenAutoRefreshEnabled, onTokenChanged, CustomProvider, - ReactNativeFirebaseAppCheckProvider, } from '.'; console.log(appCheck().app); @@ -187,7 +186,7 @@ initializeAppCheck(firebase.app(), { }); // checks modular ReactNativeFirebaseAppCheckProvider class can be instantiated directly from modular import -const rnfbProvider = new ReactNativeFirebaseAppCheckProvider(); +const rnfbProvider = appCheckInstance.newReactNativeFirebaseAppCheckProvider(); // Test that configure method exists and can be called (reusing modularProvider for initializeAppCheck above) rnfbProvider.configure({ android: { provider: 'playIntegrity' }, diff --git a/packages/functions/lib/namespaced.ts b/packages/functions/lib/namespaced.ts index 75cc2ae5a9..a580c2b1c1 100644 --- a/packages/functions/lib/namespaced.ts +++ b/packages/functions/lib/namespaced.ts @@ -21,6 +21,7 @@ import { FirebaseModule, getFirebaseRoot, } from '@react-native-firebase/app/lib/internal'; +import type { ModuleConfig } from '@react-native-firebase/app/lib/internal'; import { HttpsError, type NativeError } from './HttpsError'; import { version } from './version'; import { setReactNativeModule } from '@react-native-firebase/app/lib/internal/nativeModule'; @@ -82,7 +83,7 @@ class FirebaseFunctionsModule extends FirebaseModule { constructor( app: ReactNativeFirebase.FirebaseAppBase, - config: any, + config: ModuleConfig, customUrlOrRegion?: string | null, ) { super(app, config, customUrlOrRegion); diff --git a/packages/vertexai/tsconfig.json b/packages/vertexai/tsconfig.json index 3ae0a77732..3bb2d31285 100644 --- a/packages/vertexai/tsconfig.json +++ b/packages/vertexai/tsconfig.json @@ -33,7 +33,9 @@ "@react-native-firebase/app/lib/internal": ["../app/dist/typescript/commonjs/lib/internal"], "@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/dist/typescript/commonjs/lib"] } - } + }, + "include": ["lib/**/*"], + "exclude": ["node_modules", "dist", "__tests__", "**/*.test.ts"] } diff --git a/yarn.lock b/yarn.lock index 2f787c8dbc..068af2a8c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5058,6 +5058,8 @@ __metadata: resolution: "@react-native-firebase/app-check@workspace:packages/app-check" dependencies: expo: "npm:^54.0.27" + react-native-builder-bob: "npm:^0.40.12" + typescript: "npm:^5.8.3" peerDependencies: "@react-native-firebase/app": 23.7.0 expo: ">=47.0.0"