diff --git a/packages/app/lib/common/index.js b/packages/app/lib/common/index.js index 68dcc755ed..9f7cb63bb6 100644 --- a/packages/app/lib/common/index.js +++ b/packages/app/lib/common/index.js @@ -103,6 +103,64 @@ export function tryJSONStringify(data) { } } +// Used to indicate if there is no corresponding modular function +const NO_REPLACEMENT = true; + +const mapOfDeprecationReplacements = { + crashlytics: { + checkForUnsentReports: 'checkForUnsentReports()', + crash: 'crash()', + deleteUnsentReports: 'deleteUnsentReports()', + didCrashOnPreviousExecution: 'didCrashOnPreviousExecution()', + log: 'log()', + setAttribute: 'setAttribute()', + setAttributes: 'setAttributes()', + setUserId: 'setUserId()', + recordError: 'recordError()', + sendUnsentReports: 'sendUnsentReports()', + setCrashlyticsCollectionEnabled: 'setCrashlyticsCollectionEnabled()', + }, +}; + +const v8deprecationMessage = + 'This v8 method is deprecated and will be removed in the next major release ' + + 'as part of move to match Firebase Web modular v9 SDK API.'; + +export function deprecationConsoleWarning(moduleName, methodName, isModularMethod) { + if (!isModularMethod) { + const moduleMap = mapOfDeprecationReplacements[moduleName]; + if (moduleMap) { + const replacementMethodName = moduleMap[methodName]; + // only warn if it is mapped and purposefully deprecated + if (replacementMethodName) { + const message = createMessage(moduleName, methodName); + + // eslint-disable-next-line no-console + console.warn(message); + } + } + } +} + +export function createMessage(moduleName, methodName, uniqueMessage = '') { + if (uniqueMessage.length > 0) { + // Unique deprecation message used for testing + return uniqueMessage; + } + + const moduleMap = mapOfDeprecationReplacements[moduleName]; + if (moduleMap) { + const replacementMethodName = moduleMap[methodName]; + if (replacementMethodName) { + if (replacementMethodName !== NO_REPLACEMENT) { + return v8deprecationMessage + ` Please use \`${replacementMethodName}\` instead.`; + } else { + return v8deprecationMessage; + } + } + } +} + export const MODULAR_DEPRECATION_ARG = 'react-native-firebase-modular-method-call'; export function warnIfNotModularCall(args, replacementMethodName, noAlternative) { diff --git a/packages/app/lib/common/unitTestUtils.ts b/packages/app/lib/common/unitTestUtils.ts index ce19cba3f6..7196fd99b4 100644 --- a/packages/app/lib/common/unitTestUtils.ts +++ b/packages/app/lib/common/unitTestUtils.ts @@ -1,10 +1,46 @@ +// @ts-nocheck import { expect, jest } from '@jest/globals'; +import { createMessage } from './index'; export const checkV9Deprecation = (modularFunction: () => void, nonModularFunction: () => void) => { const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + consoleWarnSpy.mockRestore(); modularFunction(); expect(consoleWarnSpy).not.toHaveBeenCalled(); + consoleWarnSpy.mockClear(); + const consoleWarnSpy2 = jest.spyOn(console, 'warn').mockImplementation(() => {}); nonModularFunction(); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - consoleWarnSpy.mockRestore(); + + expect(consoleWarnSpy2).toHaveBeenCalledTimes(1); + consoleWarnSpy2.mockClear(); +}; + +export type CheckV9DeprecationFunction = ( + modularFunction: () => void, + nonModularFunction: () => void, + methodName: string, + uniqueMessage: string = '', +) => void; + +export const createCheckV9Deprecation = (moduleName: string): CheckV9DeprecationFunction => { + return ( + modularFunction: () => void, + nonModularFunction: () => void, + methodName: string, + uniqueMessage = '', + ) => { + const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + consoleWarnSpy.mockReset(); + modularFunction(); + expect(consoleWarnSpy).not.toHaveBeenCalled(); + consoleWarnSpy.mockReset(); + const consoleWarnSpy2 = jest.spyOn(console, 'warn').mockImplementation(warnMessage => { + const message = createMessage(moduleName, methodName, uniqueMessage); + expect(message).toMatch(warnMessage); + }); + nonModularFunction(); + + expect(consoleWarnSpy2).toHaveBeenCalledTimes(1); + consoleWarnSpy2.mockReset(); + }; }; diff --git a/packages/app/lib/internal/registry/namespace.js b/packages/app/lib/internal/registry/namespace.js index d8a41903e6..6ee73bf20b 100644 --- a/packages/app/lib/internal/registry/namespace.js +++ b/packages/app/lib/internal/registry/namespace.js @@ -15,7 +15,7 @@ * */ -import { isString } from '../../common'; +import { isString, MODULAR_DEPRECATION_ARG, deprecationConsoleWarning } from '../../common'; import FirebaseApp from '../../FirebaseApp'; import SDK_VERSION from '../../version'; import { DEFAULT_APP_NAME, KNOWN_NAMESPACES } from '../constants'; @@ -170,10 +170,10 @@ function getOrCreateModuleForRoot(moduleNamespace) { } if (!APP_MODULE_INSTANCE[_app.name][moduleNamespace]) { - APP_MODULE_INSTANCE[_app.name][moduleNamespace] = new ModuleClass( - _app, - NAMESPACE_REGISTRY[moduleNamespace], + const module = createDeprecationProxy( + new ModuleClass(_app, NAMESPACE_REGISTRY[moduleNamespace]), ); + APP_MODULE_INSTANCE[_app.name][moduleNamespace] = module; } return APP_MODULE_INSTANCE[_app.name][moduleNamespace]; @@ -277,6 +277,28 @@ export function getFirebaseRoot() { return createFirebaseRoot(); } +function createDeprecationProxy(instance) { + return new Proxy(instance, { + get(target, prop, receiver) { + const originalMethod = target[prop]; + if (prop === 'constructor') { + return target.constructor; + } + if (typeof originalMethod === 'function') { + return function (...args) { + const isModularMethod = args.includes(MODULAR_DEPRECATION_ARG); + const moduleName = receiver._config.namespace; + + deprecationConsoleWarning(moduleName, prop, isModularMethod); + + return originalMethod.apply(target, args); + }; + } + return Reflect.get(target, prop, receiver); + }, + }); +} + /** * * @param options diff --git a/packages/crashlytics/__tests__/crashlytics.test.ts b/packages/crashlytics/__tests__/crashlytics.test.ts index 2867aecc2a..7e9775445e 100644 --- a/packages/crashlytics/__tests__/crashlytics.test.ts +++ b/packages/crashlytics/__tests__/crashlytics.test.ts @@ -1,5 +1,10 @@ -import { describe, expect, it } from '@jest/globals'; - +import { describe, expect, it, jest, beforeEach } from '@jest/globals'; +// @ts-ignore test +import FirebaseModule from '../../app/lib/internal/FirebaseModule'; +import { + createCheckV9Deprecation, + CheckV9DeprecationFunction, +} from '../../app/lib/common/unitTestUtils'; import { firebase, getCrashlytics, @@ -79,4 +84,132 @@ describe('Crashlytics', function () { expect(setCrashlyticsCollectionEnabled).toBeDefined(); }); }); + + describe('test `console.warn` is called for RNFB v8 API & not called for v9 API', function () { + let checkV9Deprecation: CheckV9DeprecationFunction; + + beforeEach(function () { + checkV9Deprecation = createCheckV9Deprecation('crashlytics'); + + // @ts-ignore test + jest.spyOn(FirebaseModule.prototype, 'native', 'get').mockImplementation(() => { + return new Proxy( + {}, + { + get: () => jest.fn(), + }, + ); + }); + }); + + it('checkForUnsentReports', function () { + const crashlytics = getCrashlytics(); + checkV9Deprecation( + () => checkForUnsentReports(crashlytics), + () => crashlytics.checkForUnsentReports(), + 'checkForUnsentReports', + ); + }); + + it('crash', function () { + const crashlytics = getCrashlytics(); + checkV9Deprecation( + () => crash(crashlytics), + () => crashlytics.crash(), + 'crash', + ); + }); + + it('deleteUnsentReports', function () { + const crashlytics = getCrashlytics(); + checkV9Deprecation( + () => deleteUnsentReports(crashlytics), + () => crashlytics.deleteUnsentReports(), + 'deleteUnsentReports', + ); + }); + + it('didCrashOnPreviousExecution', function () { + const crashlytics = getCrashlytics(); + checkV9Deprecation( + () => didCrashOnPreviousExecution(crashlytics), + () => crashlytics.didCrashOnPreviousExecution(), + 'didCrashOnPreviousExecution', + ); + }); + + it('log', function () { + const crashlytics = getCrashlytics(); + checkV9Deprecation( + () => log(crashlytics, 'message'), + () => crashlytics.log('message'), + 'log', + ); + }); + + it('setAttribute', function () { + const crashlytics = getCrashlytics(); + checkV9Deprecation( + () => setAttribute(crashlytics, 'name', 'value'), + () => crashlytics.setAttribute('name', 'value'), + 'setAttribute', + ); + }); + + it('setAttributes', function () { + const crashlytics = getCrashlytics(); + checkV9Deprecation( + () => setAttributes(crashlytics, {}), + () => crashlytics.setAttributes({}), + 'setAttributes', + ); + }); + + it('setUserId', function () { + const crashlytics = getCrashlytics(); + checkV9Deprecation( + () => setUserId(crashlytics, 'id'), + () => crashlytics.setUserId('id'), + 'setUserId', + ); + }); + + it('recordError', function () { + const crashlytics = getCrashlytics(); + checkV9Deprecation( + () => recordError(crashlytics, new Error(), 'name'), + () => crashlytics.recordError(new Error(), 'name'), + 'recordError', + ); + }); + + it('sendUnsentReports', function () { + const crashlytics = getCrashlytics(); + checkV9Deprecation( + () => sendUnsentReports(crashlytics), + () => crashlytics.sendUnsentReports(), + 'sendUnsentReports', + ); + }); + + it('setCrashlyticsCollectionEnabled', function () { + const crashlytics = getCrashlytics(); + checkV9Deprecation( + () => setCrashlyticsCollectionEnabled(crashlytics, true), + () => crashlytics.setCrashlyticsCollectionEnabled(true), + 'setCrashlyticsCollectionEnabled', + ); + }); + + it('isCrashlyticsCollectionEnabled', function () { + const crashlytics = getCrashlytics(); + checkV9Deprecation( + // swapped order here because we're deprecating the modular method and keeping the property on Crashlytics instance + () => crashlytics.isCrashlyticsCollectionEnabled, + () => isCrashlyticsCollectionEnabled(crashlytics), + '', + '`isCrashlyticsCollectionEnabled()` is deprecated, please use `Crashlytics.isCrashlyticsCollectionEnabled` property instead', + ); + }); + }); }); diff --git a/packages/crashlytics/lib/index.js b/packages/crashlytics/lib/index.js index 2f37ed8dd8..51b351dcdc 100644 --- a/packages/crashlytics/lib/index.js +++ b/packages/crashlytics/lib/index.js @@ -51,6 +51,7 @@ class FirebaseCrashlyticsModule extends FirebaseModule { } get isCrashlyticsCollectionEnabled() { + // Purposefully did not deprecate this as I think it should remain a property rather than a method. return this._isCrashlyticsCollectionEnabled; } diff --git a/packages/crashlytics/lib/modular/index.js b/packages/crashlytics/lib/modular/index.js index fc581bce29..1a4dcf8de6 100644 --- a/packages/crashlytics/lib/modular/index.js +++ b/packages/crashlytics/lib/modular/index.js @@ -1,3 +1,4 @@ +import { MODULAR_DEPRECATION_ARG } from '@react-native-firebase/app/lib/common'; import { firebase } from '..'; /** @@ -31,6 +32,11 @@ export function getCrashlytics() { * @returns {boolean} */ export function isCrashlyticsCollectionEnabled(crashlytics) { + // Unique. Deprecating modular method and allow it as a property on Crashlytics instance. + // eslint-disable-next-line no-console + console.warn( + '`isCrashlyticsCollectionEnabled()` is deprecated, please use `Crashlytics.isCrashlyticsCollectionEnabled` property instead', + ); return crashlytics.isCrashlyticsCollectionEnabled; } @@ -53,7 +59,7 @@ export function isCrashlyticsCollectionEnabled(crashlytics) { * @returns {Promise} */ export function checkForUnsentReports(crashlytics) { - return crashlytics.checkForUnsentReports(); + return crashlytics.checkForUnsentReports.call(crashlytics, MODULAR_DEPRECATION_ARG); } /** @@ -70,7 +76,7 @@ export function checkForUnsentReports(crashlytics) { * @returns {Promise} */ export function deleteUnsentReports(crashlytics) { - return crashlytics.deleteUnsentReports(); + return crashlytics.deleteUnsentReports.call(crashlytics, MODULAR_DEPRECATION_ARG); } /** @@ -91,7 +97,7 @@ export function deleteUnsentReports(crashlytics) { * @returns {Promise} */ export function didCrashOnPreviousExecution(crashlytics) { - return crashlytics.didCrashOnPreviousExecution(); + return crashlytics.didCrashOnPreviousExecution.call(crashlytics, MODULAR_DEPRECATION_ARG); } /** @@ -109,7 +115,7 @@ export function didCrashOnPreviousExecution(crashlytics) { * @returns {void} */ export function crash(crashlytics) { - return crashlytics.crash(); + return crashlytics.crash.call(crashlytics, MODULAR_DEPRECATION_ARG); } /** @@ -127,7 +133,7 @@ export function crash(crashlytics) { * @returns {void} */ export function log(crashlytics, message) { - return crashlytics.log(message); + return crashlytics.log.call(crashlytics, message, MODULAR_DEPRECATION_ARG); } /** @@ -152,7 +158,7 @@ export function log(crashlytics, message) { * @returns {void} */ export function recordError(crashlytics, error, jsErrorName) { - return crashlytics.recordError(error, jsErrorName); + return crashlytics.recordError.call(crashlytics, error, jsErrorName, MODULAR_DEPRECATION_ARG); } /** @@ -169,7 +175,7 @@ export function recordError(crashlytics, error, jsErrorName) { * @returns {void} */ export function sendUnsentReports(crashlytics) { - return crashlytics.sendUnsentReports(); + return crashlytics.sendUnsentReports.call(crashlytics, MODULAR_DEPRECATION_ARG); } /** @@ -196,7 +202,7 @@ export function sendUnsentReports(crashlytics) { * @returns {Promise} */ export function setUserId(crashlytics, userId) { - return crashlytics.setUserId(userId); + return crashlytics.setUserId.call(crashlytics, userId, MODULAR_DEPRECATION_ARG); } /** @@ -214,7 +220,7 @@ export function setUserId(crashlytics, userId) { * @returns {Promise} */ export function setAttribute(crashlytics, name, value) { - return crashlytics.setAttribute(name, value); + return crashlytics.setAttribute.call(crashlytics, name, value, MODULAR_DEPRECATION_ARG); } /** @@ -234,7 +240,7 @@ export function setAttribute(crashlytics, name, value) { * @returns {Promise} */ export function setAttributes(crashlytics, attributes) { - return crashlytics.setAttributes(attributes); + return crashlytics.setAttributes.call(crashlytics, attributes, MODULAR_DEPRECATION_ARG); } /** @@ -254,5 +260,9 @@ export function setAttributes(crashlytics, attributes) { * @returns {Promise} */ export function setCrashlyticsCollectionEnabled(crashlytics, enabled) { - return crashlytics.setCrashlyticsCollectionEnabled(enabled); + return crashlytics.setCrashlyticsCollectionEnabled.call( + crashlytics, + enabled, + MODULAR_DEPRECATION_ARG, + ); }