diff --git a/packages/app-check/__tests__/appcheck.test.ts b/packages/app-check/__tests__/appcheck.test.ts index aba3e65fd4..6e11364d57 100644 --- a/packages/app-check/__tests__/appcheck.test.ts +++ b/packages/app-check/__tests__/appcheck.test.ts @@ -1,4 +1,12 @@ -import { describe, expect, it } from '@jest/globals'; +import { jest, beforeEach, describe, expect, it } from '@jest/globals'; + +// @ts-ignore test +import FirebaseModule from '../../app/lib/internal/FirebaseModule'; + +import { + createCheckV9Deprecation, + CheckV9DeprecationFunction, +} from '../../app/lib/common/unitTestUtils'; import { firebase, @@ -7,6 +15,8 @@ import { getLimitedUseToken, addTokenListener, setTokenAutoRefreshEnabled, + onTokenChanged, + CustomProvider, } from '../lib'; describe('appCheck()', function () { @@ -54,5 +64,114 @@ describe('appCheck()', function () { it('`setTokenAutoRefreshEnabled` function is properly exposed to end user', function () { expect(setTokenAutoRefreshEnabled).toBeDefined(); }); + + it('`CustomProvider` function is properly exposed to end user', function () { + expect(CustomProvider).toBeDefined(); + }); + }); + + describe('test `console.warn` is called for RNFB v8 API & not called for v9 API', function () { + let appCheckRefV9Deprecation: CheckV9DeprecationFunction; + let staticsRefV9Deprecation: CheckV9DeprecationFunction; + + beforeEach(function () { + appCheckRefV9Deprecation = createCheckV9Deprecation(['appCheck']); + staticsRefV9Deprecation = createCheckV9Deprecation(['appCheck', 'statics']); + + // @ts-ignore test + jest.spyOn(FirebaseModule.prototype, 'native', 'get').mockImplementation(() => { + return new Proxy( + {}, + { + get: () => + jest.fn().mockResolvedValue({ + source: 'cache', + changes: [], + documents: [], + metadata: {}, + path: 'foo', + } as never), + }, + ); + }); + }); + + describe('AppCheck', function () { + it('appCheck.activate()', function () { + const app = firebase.app(); + const appCheck = firebase.appCheck(); + appCheckRefV9Deprecation( + () => + initializeAppCheck(app, { + provider: { + providerOptions: { + android: { + provider: 'playIntegrity', + }, + }, + }, + isTokenAutoRefreshEnabled: true, + }), + () => appCheck.activate('string'), + 'activate', + ); + }); + + it('appCheck.setTokenAutoRefreshEnabled()', function () { + const appCheck = firebase.appCheck(); + appCheckRefV9Deprecation( + () => setTokenAutoRefreshEnabled(appCheck, true), + () => appCheck.setTokenAutoRefreshEnabled(true), + 'setTokenAutoRefreshEnabled', + ); + }); + + it('appCheck.getToken()', function () { + const appCheck = firebase.appCheck(); + appCheckRefV9Deprecation( + () => getToken(appCheck, true), + () => appCheck.getToken(true), + 'getToken', + ); + }); + + it('appCheck.getLimitedUseToken()', function () { + const appCheck = firebase.appCheck(); + appCheckRefV9Deprecation( + () => getLimitedUseToken(appCheck), + () => appCheck.getLimitedUseToken(), + 'getLimitedUseToken', + ); + }); + + it('appCheck.onTokenChanged()', function () { + const appCheck = firebase.appCheck(); + appCheckRefV9Deprecation( + () => + onTokenChanged( + appCheck, + () => {}, + () => {}, + () => {}, + ), + () => + appCheck.onTokenChanged( + () => {}, + () => {}, + () => {}, + ), + 'onTokenChanged', + ); + }); + + it('CustomProvider', function () { + const appCheck = firebase.appCheck; + staticsRefV9Deprecation( + () => CustomProvider, + () => appCheck.CustomProvider, + 'CustomProvider', + ); + }); + }); }); }); diff --git a/packages/app-check/lib/modular/index.d.ts b/packages/app-check/lib/modular/index.d.ts index 516da2f211..cd536e19e4 100644 --- a/packages/app-check/lib/modular/index.d.ts +++ b/packages/app-check/lib/modular/index.d.ts @@ -6,6 +6,8 @@ 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. @@ -52,6 +54,43 @@ export function addTokenListener( 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. + * + * @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 @@ -61,3 +100,11 @@ export function setTokenAutoRefreshEnabled( appCheckInstance: AppCheck, isAutoRefreshEnabled: boolean, ): void; + +/** + * Custom provider class. + * @public + */ +export class CustomProvider implements AppCheckProvider { + constructor(customProviderOptions: CustomProviderOptions); +} diff --git a/packages/app-check/lib/modular/index.js b/packages/app-check/lib/modular/index.js index d882204e4d..12b53608b7 100644 --- a/packages/app-check/lib/modular/index.js +++ b/packages/app-check/lib/modular/index.js @@ -17,6 +17,8 @@ */ import { firebase } from '..'; +import { MODULAR_DEPRECATION_ARG } from '../../../app/lib/common'; +import CustomProvider from '../ReactNativeFirebaseAppCheckProvider'; /** * @typedef {import('@firebase/app').FirebaseApp} FirebaseApp @@ -35,11 +37,12 @@ import { firebase } from '..'; */ export async function initializeAppCheck(app, options) { if (app) { - await firebase.app(app.name).appCheck().initializeAppCheck(options); + const appCheck = firebase.app(app.name).appCheck(); + await appCheck.initializeAppCheck.call(appCheck, options, MODULAR_DEPRECATION_ARG); return { app: firebase.app(app.name) }; } - - await firebase.app().appCheck().initializeAppCheck(options); + const appCheck = firebase.app().appCheck(); + await appCheck.initializeAppCheck.call(appCheck, options, MODULAR_DEPRECATION_ARG); return { app: firebase.app() }; } @@ -51,7 +54,8 @@ export async function initializeAppCheck(app, options) { * @returns {Promise} */ export function getToken(appCheckInstance, forceRefresh) { - return appCheckInstance.app.appCheck().getToken(forceRefresh); + const appCheck = appCheckInstance.app.appCheck(); + return appCheck.getToken.call(appCheck, forceRefresh, MODULAR_DEPRECATION_ARG); } /** @@ -61,7 +65,8 @@ export function getToken(appCheckInstance, forceRefresh) { * @returns {Promise} */ export function getLimitedUseToken(appCheckInstance) { - return appCheckInstance.app.appCheck().getLimitedUseToken(); + const appCheck = appCheckInstance.app.appCheck(); + return appCheck.getLimitedUseToken.call(appCheck, MODULAR_DEPRECATION_ARG); } /** @@ -85,5 +90,33 @@ export function addTokenListener(appCheckInstance, listener) { * @param {boolean} isAutoRefreshEnabled - Whether to enable auto-refresh. */ export function setTokenAutoRefreshEnabled(appCheckInstance, isAutoRefreshEnabled) { - return appCheckInstance.app.appCheck().setTokenAutoRefreshEnabled(isAutoRefreshEnabled); + const appCheck = appCheckInstance.app.appCheck(); + return appCheck.setTokenAutoRefreshEnabled.call( + appCheck, + 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) { + const appCheck = appCheckInstance.app.appCheck(); + return appCheck.onTokenChanged.call( + appCheck, + onNextOrObserver, + onError, + onCompletion, + MODULAR_DEPRECATION_ARG, + ); } + +export { CustomProvider }; diff --git a/packages/app/lib/common/index.js b/packages/app/lib/common/index.js index 159ae0c40f..a9d0206be6 100644 --- a/packages/app/lib/common/index.js +++ b/packages/app/lib/common/index.js @@ -107,6 +107,18 @@ export function tryJSONStringify(data) { const NO_REPLACEMENT = true; const mapOfDeprecationReplacements = { + appCheck: { + default: { + activate: 'initializeAppCheck()', + setTokenAutoRefreshEnabled: 'setTokenAutoRefreshEnabled()', + getToken: 'getToken()', + getLimitedUseToken: 'getLimitedUseToken()', + onTokenChanged: 'onTokenChanged()', + }, + statics: { + CustomProvider: 'CustomProvider', + }, + }, crashlytics: { default: { checkForUnsentReports: 'checkForUnsentReports()', @@ -247,8 +259,8 @@ export function createMessage( } function getNamespace(target) { - if (target.GeoPoint) { - // target is statics object. GeoPoint is a static class on Firestore + if (target.GeoPoint || target.CustomProvider) { + // target is statics object. GeoPoint - Firestore, CustomProvider - AppCheck return 'firestore'; } if (target._config && target._config.namespace) { @@ -263,8 +275,8 @@ function getNamespace(target) { } function getInstanceName(target) { - if (target.GeoPoint) { - // target is statics object. GeoPoint is a static class on Firestore + if (target.GeoPoint || target.CustomProvider) { + // target is statics object. GeoPoint - Firestore, CustomProvider - AppCheck return 'statics'; } if (target._config) { @@ -309,6 +321,10 @@ export function createDeprecationProxy(instance) { ) { deprecationConsoleWarning('firestore', prop, 'statics', false); } + if (prop === 'CustomProvider') { + deprecationConsoleWarning('appCheck', prop, 'statics', false); + } + if (prop !== 'setLogLevel') { // we want to capture setLogLevel function call which we do below return Reflect.get(target, prop, receiver); diff --git a/packages/app/lib/common/unitTestUtils.ts b/packages/app/lib/common/unitTestUtils.ts index 9372074ad4..5ae9778f96 100644 --- a/packages/app/lib/common/unitTestUtils.ts +++ b/packages/app/lib/common/unitTestUtils.ts @@ -19,7 +19,8 @@ export type CheckV9DeprecationFunction = ( modularFunction: () => void, nonModularFunction: () => void, methodNameKey: string, - uniqueMessage: string = '', + uniqueMessage?: string | null, + ignoreFirebaseAppDeprecationWarning?: boolean, ) => void; export const createCheckV9Deprecation = (moduleNames: string[]): CheckV9DeprecationFunction => { @@ -28,14 +29,26 @@ export const createCheckV9Deprecation = (moduleNames: string[]): CheckV9Deprecat nonModularFunction: () => void, methodNameKey: string, uniqueMessage: string?, + checkFirebaseAppDeprecationWarning: boolean = false, ) => { const moduleName = moduleNames[0]; // firestore, database, etc const instanceName = moduleNames[1] || 'default'; // default, FirestoreCollectionReference, etc - const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); - // Do not call `mockRestore()` as it removes the spy + const consoleWarnSpy = jest.spyOn(console, 'warn'); consoleWarnSpy.mockReset(); + + consoleWarnSpy.mockImplementation(warnMessage => { + const firebaseAppDeprecationMessage = warnMessage.includes('Please use `getApp()` instead.'); + if (checkFirebaseAppDeprecationWarning) { + throw new Error(`Console warn was called unexpectedly with: ${warnMessage}`); + } + + if (!firebaseAppDeprecationMessage) { + // we want to ignore all firebase app deprecation warnings (e.g. "Please use `getApp()` instead.") unless actually testing for it which we do above + throw new Error(`Console warn was called unexpectedly with: ${warnMessage}`); + } + }); + // Do not call `mockRestore()` unless removing the spy modularFunction(); - expect(consoleWarnSpy).not.toHaveBeenCalled(); consoleWarnSpy.mockReset(); consoleWarnSpy.mockRestore(); const consoleWarnSpy2 = jest.spyOn(console, 'warn').mockImplementation(warnMessage => {