Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(crashlytics): deprecation warning for v8 API ahead of future major release #8107

Merged
merged 9 commits into from
Nov 22, 2024
58 changes: 58 additions & 0 deletions packages/app/lib/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
40 changes: 38 additions & 2 deletions packages/app/lib/common/unitTestUtils.ts
Original file line number Diff line number Diff line change
@@ -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();
};
};
30 changes: 26 additions & 4 deletions packages/app/lib/internal/registry/namespace.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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
Expand Down
137 changes: 135 additions & 2 deletions packages/crashlytics/__tests__/crashlytics.test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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',
);
});
});
});
1 change: 1 addition & 0 deletions packages/crashlytics/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Loading
Loading