Skip to content

Commit

Permalink
chore(firestore): deprecation warnings for v8 API ahead of future maj…
Browse files Browse the repository at this point in the history
…or release (#8169)

Co-authored-by: Mike Hardy <[email protected]>
  • Loading branch information
russellwheatley and mikehardy authored Dec 20, 2024
1 parent 99de822 commit 86a128d
Show file tree
Hide file tree
Showing 18 changed files with 1,153 additions and 158 deletions.
6 changes: 2 additions & 4 deletions packages/app/lib/FirebaseApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ export default class FirebaseApp {
}

extendApp(extendedProps) {
// this method has no modular alternative, send true for param 'noAlternative'
warnIfNotModularCall(arguments, '', true);
warnIfNotModularCall(arguments);
this._checkDestroyed();
Object.assign(this, extendedProps);
}
Expand All @@ -74,8 +73,7 @@ export default class FirebaseApp {
}

toString() {
// this method has no modular alternative, send true for param 'noAlternative'
warnIfNotModularCall(arguments, '', true);
warnIfNotModularCall(arguments);
return this.name;
}
}
224 changes: 199 additions & 25 deletions packages/app/lib/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,50 +108,135 @@ 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()',
default: {
checkForUnsentReports: 'checkForUnsentReports()',
crash: 'crash()',
deleteUnsentReports: 'deleteUnsentReports()',
didCrashOnPreviousExecution: 'didCrashOnPreviousExecution()',
log: 'log()',
setAttribute: 'setAttribute()',
setAttributes: 'setAttributes()',
setUserId: 'setUserId()',
recordError: 'recordError()',
sendUnsentReports: 'sendUnsentReports()',
setCrashlyticsCollectionEnabled: 'setCrashlyticsCollectionEnabled()',
},
},
firestore: {
default: {
batch: 'writeBatch()',
loadBundle: 'loadBundle()',
namedQuery: 'namedQuery()',
clearPersistence: 'clearIndexedDbPersistence()',
waitForPendingWrites: 'waitForPendingWrites()',
terminate: 'terminate()',
useEmulator: 'connectFirestoreEmulator()',
collection: 'collection()',
collectionGroup: 'collectionGroup()',
disableNetwork: 'disableNetwork()',
doc: 'doc()',
enableNetwork: 'enableNetwork()',
runTransaction: 'runTransaction()',
settings: 'settings()',
persistentCacheIndexManager: 'getPersistentCacheIndexManager()',
},
statics: {
setLogLevel: 'setLogLevel()',
Filter: 'where()',
FieldValue: 'FieldValue',
Timestamp: 'Timestamp',
GeoPoint: 'GeoPoint',
Blob: 'Bytes',
FieldPath: 'FieldPath',
},
FirestoreCollectionReference: {
count: 'getCountFromServer()',
countFromServer: 'getCountFromServer()',
endAt: 'endAt()',
endBefore: 'endBefore()',
get: 'getDocs()',
isEqual: NO_REPLACEMENT,
limit: 'limit()',
limitToLast: 'limitToLast()',
onSnapshot: 'onSnapshot()',
orderBy: 'orderBy()',
startAfter: 'startAfter()',
startAt: 'startAt()',
where: 'where()',
add: 'addDoc()',
doc: 'doc()',
},
FirestoreDocumentReference: {
collection: 'collection()',
delete: 'deleteDoc()',
get: 'getDoc()',
isEqual: NO_REPLACEMENT,
onSnapshot: 'onSnapshot()',
set: 'setDoc()',
update: 'updateDoc()',
},
FirestoreDocumentSnapshot: {
isEqual: NO_REPLACEMENT,
},
FirestoreFieldValue: {
arrayRemove: 'arrayRemove()',
arrayUnion: 'arrayUnion()',
delete: 'deleteField()',
increment: 'increment()',
serverTimestamp: 'serverTimestamp()',
},
Filter: {
or: 'or()',
and: 'and()',
},
FirestorePersistentCacheIndexManager: {
enableIndexAutoCreation: 'enablePersistentCacheIndexAutoCreation()',
disableIndexAutoCreation: 'disablePersistentCacheIndexAutoCreation()',
deleteAllIndexes: 'deleteAllPersistentCacheIndexes()',
},
FirestoreTimestamp: {
seconds: NO_REPLACEMENT,
nanoseconds: NO_REPLACEMENT,
},
},
};

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) {
export function deprecationConsoleWarning(nameSpace, methodName, instanceName, isModularMethod) {
if (!isModularMethod) {
const moduleMap = mapOfDeprecationReplacements[moduleName];
const moduleMap = mapOfDeprecationReplacements[nameSpace];
if (moduleMap) {
const replacementMethodName = moduleMap[methodName];
// only warn if it is mapped and purposefully deprecated
if (replacementMethodName) {
const message = createMessage(moduleName, methodName);

const instanceMap = moduleMap[instanceName];
const deprecatedMethod = instanceMap[methodName];
if (instanceMap && deprecatedMethod) {
const message = createMessage(nameSpace, methodName, instanceName);
// eslint-disable-next-line no-console
console.warn(message);
}
}
}
}

export function createMessage(moduleName, methodName, uniqueMessage = '') {
if (uniqueMessage.length > 0) {
export function createMessage(
nameSpace,
methodName,
instanceName = 'default',
uniqueMessage = null,
) {
if (uniqueMessage) {
// Unique deprecation message used for testing
return uniqueMessage;
}

const moduleMap = mapOfDeprecationReplacements[moduleName];
const moduleMap = mapOfDeprecationReplacements[nameSpace];
if (moduleMap) {
const replacementMethodName = moduleMap[methodName];
if (replacementMethodName) {
const instance = moduleMap[instanceName];
if (instance) {
const replacementMethodName = instance[methodName];

if (replacementMethodName !== NO_REPLACEMENT) {
return v8deprecationMessage + ` Please use \`${replacementMethodName}\` instead.`;
} else {
Expand All @@ -161,9 +246,98 @@ export function createMessage(moduleName, methodName, uniqueMessage = '') {
}
}

function getNamespace(target) {
if (target.GeoPoint) {
// target is statics object. GeoPoint is a static class on Firestore
return 'firestore';
}
if (target._config && target._config.namespace) {
return target._config.namespace;
}
const className = target.name ? target.name : target.constructor.name;
return Object.keys(mapOfDeprecationReplacements).find(key => {
if (mapOfDeprecationReplacements[key][className]) {
return key;
}
});
}

function getInstanceName(target) {
if (target.GeoPoint) {
// target is statics object. GeoPoint is a static class on Firestore
return 'statics';
}
if (target._config) {
// module class instance, we use default to store map of deprecated methods
return 'default';
}
if (target.name) {
// It's a function which has a name property unlike classes
return target.name;
}
// It's a class instance
return target.constructor.name;
}

export function createDeprecationProxy(instance) {
return new Proxy(instance, {
construct(target, args) {
// needed for Timestamp which we pass as static, when we construct new instance, we need to wrap it in proxy again.
return createDeprecationProxy(new target(...args));
},
get(target, prop, receiver) {
const originalMethod = target[prop];

if (prop === 'constructor') {
return Reflect.get(target, prop, receiver);
}

if (target && target.constructor && target.constructor.name === 'FirestoreTimestamp') {
deprecationConsoleWarning('firestore', prop, 'FirestoreTimestamp', false);
return Reflect.get(target, prop, receiver);
}

if (target && target.name === 'firebaseModuleWithApp') {
// statics
if (
prop === 'Filter' ||
prop === 'FieldValue' ||
prop === 'Timestamp' ||
prop === 'GeoPoint' ||
prop === 'Blob' ||
prop === 'FieldPath'
) {
deprecationConsoleWarning('firestore', prop, 'statics', false);
}
if (prop !== 'setLogLevel') {
// we want to capture setLogLevel function call which we do below
return Reflect.get(target, prop, receiver);
}
}

if (typeof originalMethod === 'function') {
return function (...args) {
const isModularMethod = args.includes(MODULAR_DEPRECATION_ARG);
const instanceName = getInstanceName(target);
const nameSpace = getNamespace(target);

deprecationConsoleWarning(nameSpace, prop, instanceName, isModularMethod);

return originalMethod.apply(target, filterModularArgument(args));
};
}
return Reflect.get(target, prop, receiver);
},
});
}

export const MODULAR_DEPRECATION_ARG = 'react-native-firebase-modular-method-call';

export function warnIfNotModularCall(args, replacementMethodName, noAlternative) {
export function filterModularArgument(list) {
return list.filter(arg => arg !== MODULAR_DEPRECATION_ARG);
}

export function warnIfNotModularCall(args, replacementMethodName = '') {
for (let i = 0; i < args.length; i++) {
if (args[i] === MODULAR_DEPRECATION_ARG) {
return;
Expand All @@ -173,7 +347,7 @@ export function warnIfNotModularCall(args, replacementMethodName, noAlternative)
'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.';

if (!noAlternative) {
if (replacementMethodName.length > 0) {
message += ` Please use \`${replacementMethodName}\` instead.`;
}

Expand Down
16 changes: 10 additions & 6 deletions packages/app/lib/common/unitTestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,29 @@ export const checkV9Deprecation = (modularFunction: () => void, nonModularFuncti
export type CheckV9DeprecationFunction = (
modularFunction: () => void,
nonModularFunction: () => void,
methodName: string,
methodNameKey: string,
uniqueMessage: string = '',
) => void;

export const createCheckV9Deprecation = (moduleName: string): CheckV9DeprecationFunction => {
export const createCheckV9Deprecation = (moduleNames: string[]): CheckV9DeprecationFunction => {
return (
modularFunction: () => void,
nonModularFunction: () => void,
methodName: string,
uniqueMessage = '',
methodNameKey: string,
uniqueMessage: string?,
) => {
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
consoleWarnSpy.mockReset();
modularFunction();
expect(consoleWarnSpy).not.toHaveBeenCalled();
consoleWarnSpy.mockReset();
consoleWarnSpy.mockRestore();
const consoleWarnSpy2 = jest.spyOn(console, 'warn').mockImplementation(warnMessage => {
const message = createMessage(moduleName, methodName, uniqueMessage);
expect(message).toMatch(warnMessage);
const message = createMessage(moduleName, methodNameKey, instanceName, uniqueMessage);
expect(warnMessage).toMatch(message);
});
nonModularFunction();

Expand Down
37 changes: 8 additions & 29 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, MODULAR_DEPRECATION_ARG, deprecationConsoleWarning } from '../../common';
import { isString, createDeprecationProxy } from '../../common';
import FirebaseApp from '../../FirebaseApp';
import SDK_VERSION from '../../version';
import { DEFAULT_APP_NAME, KNOWN_NAMESPACES } from '../constants';
Expand Down Expand Up @@ -114,11 +114,11 @@ function getOrCreateModuleForApp(app, moduleNamespace) {
}

if (!APP_MODULE_INSTANCE[app.name][key]) {
APP_MODULE_INSTANCE[app.name][key] = new ModuleClass(
app,
NAMESPACE_REGISTRY[moduleNamespace],
customUrlOrRegionOrDatabaseId,
const module = createDeprecationProxy(
new ModuleClass(app, NAMESPACE_REGISTRY[moduleNamespace], customUrlOrRegionOrDatabaseId),
);

APP_MODULE_INSTANCE[app.name][key] = module;
}

return APP_MODULE_INSTANCE[app.name][key];
Expand Down Expand Up @@ -180,8 +180,9 @@ function getOrCreateModuleForRoot(moduleNamespace) {
}

Object.assign(firebaseModuleWithApp, statics || {});
Object.freeze(firebaseModuleWithApp);
MODULE_GETTER_FOR_ROOT[moduleNamespace] = firebaseModuleWithApp;
// Object.freeze(firebaseModuleWithApp);
// Wrap around statics, e.g. firebase.firestore.FieldValue, removed freeze as it stops proxy working. it is deprecated anyway
MODULE_GETTER_FOR_ROOT[moduleNamespace] = createDeprecationProxy(firebaseModuleWithApp);

return MODULE_GETTER_FOR_ROOT[moduleNamespace];
}
Expand Down Expand Up @@ -277,28 +278,6 @@ 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
2 changes: 1 addition & 1 deletion packages/crashlytics/__tests__/crashlytics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe('Crashlytics', function () {
let checkV9Deprecation: CheckV9DeprecationFunction;

beforeEach(function () {
checkV9Deprecation = createCheckV9Deprecation('crashlytics');
checkV9Deprecation = createCheckV9Deprecation(['crashlytics']);

// @ts-ignore test
jest.spyOn(FirebaseModule.prototype, 'native', 'get').mockImplementation(() => {
Expand Down
Loading

0 comments on commit 86a128d

Please sign in to comment.