Skip to content

Commit 49552e6

Browse files
chore(firestore): deprecation warnings for v8 API ahead of future major release (#8169)
Co-authored-by: Mike Hardy <[email protected]>
1 parent 3279cc1 commit 49552e6

18 files changed

+1153
-158
lines changed

packages/app/lib/FirebaseApp.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ export default class FirebaseApp {
6161
}
6262

6363
extendApp(extendedProps) {
64-
// this method has no modular alternative, send true for param 'noAlternative'
65-
warnIfNotModularCall(arguments, '', true);
64+
warnIfNotModularCall(arguments);
6665
this._checkDestroyed();
6766
Object.assign(this, extendedProps);
6867
}
@@ -74,8 +73,7 @@ export default class FirebaseApp {
7473
}
7574

7675
toString() {
77-
// this method has no modular alternative, send true for param 'noAlternative'
78-
warnIfNotModularCall(arguments, '', true);
76+
warnIfNotModularCall(arguments);
7977
return this.name;
8078
}
8179
}

packages/app/lib/common/index.js

+199-25
Original file line numberDiff line numberDiff line change
@@ -108,50 +108,135 @@ const NO_REPLACEMENT = true;
108108

109109
const mapOfDeprecationReplacements = {
110110
crashlytics: {
111-
checkForUnsentReports: 'checkForUnsentReports()',
112-
crash: 'crash()',
113-
deleteUnsentReports: 'deleteUnsentReports()',
114-
didCrashOnPreviousExecution: 'didCrashOnPreviousExecution()',
115-
log: 'log()',
116-
setAttribute: 'setAttribute()',
117-
setAttributes: 'setAttributes()',
118-
setUserId: 'setUserId()',
119-
recordError: 'recordError()',
120-
sendUnsentReports: 'sendUnsentReports()',
121-
setCrashlyticsCollectionEnabled: 'setCrashlyticsCollectionEnabled()',
111+
default: {
112+
checkForUnsentReports: 'checkForUnsentReports()',
113+
crash: 'crash()',
114+
deleteUnsentReports: 'deleteUnsentReports()',
115+
didCrashOnPreviousExecution: 'didCrashOnPreviousExecution()',
116+
log: 'log()',
117+
setAttribute: 'setAttribute()',
118+
setAttributes: 'setAttributes()',
119+
setUserId: 'setUserId()',
120+
recordError: 'recordError()',
121+
sendUnsentReports: 'sendUnsentReports()',
122+
setCrashlyticsCollectionEnabled: 'setCrashlyticsCollectionEnabled()',
123+
},
124+
},
125+
firestore: {
126+
default: {
127+
batch: 'writeBatch()',
128+
loadBundle: 'loadBundle()',
129+
namedQuery: 'namedQuery()',
130+
clearPersistence: 'clearIndexedDbPersistence()',
131+
waitForPendingWrites: 'waitForPendingWrites()',
132+
terminate: 'terminate()',
133+
useEmulator: 'connectFirestoreEmulator()',
134+
collection: 'collection()',
135+
collectionGroup: 'collectionGroup()',
136+
disableNetwork: 'disableNetwork()',
137+
doc: 'doc()',
138+
enableNetwork: 'enableNetwork()',
139+
runTransaction: 'runTransaction()',
140+
settings: 'settings()',
141+
persistentCacheIndexManager: 'getPersistentCacheIndexManager()',
142+
},
143+
statics: {
144+
setLogLevel: 'setLogLevel()',
145+
Filter: 'where()',
146+
FieldValue: 'FieldValue',
147+
Timestamp: 'Timestamp',
148+
GeoPoint: 'GeoPoint',
149+
Blob: 'Bytes',
150+
FieldPath: 'FieldPath',
151+
},
152+
FirestoreCollectionReference: {
153+
count: 'getCountFromServer()',
154+
countFromServer: 'getCountFromServer()',
155+
endAt: 'endAt()',
156+
endBefore: 'endBefore()',
157+
get: 'getDocs()',
158+
isEqual: NO_REPLACEMENT,
159+
limit: 'limit()',
160+
limitToLast: 'limitToLast()',
161+
onSnapshot: 'onSnapshot()',
162+
orderBy: 'orderBy()',
163+
startAfter: 'startAfter()',
164+
startAt: 'startAt()',
165+
where: 'where()',
166+
add: 'addDoc()',
167+
doc: 'doc()',
168+
},
169+
FirestoreDocumentReference: {
170+
collection: 'collection()',
171+
delete: 'deleteDoc()',
172+
get: 'getDoc()',
173+
isEqual: NO_REPLACEMENT,
174+
onSnapshot: 'onSnapshot()',
175+
set: 'setDoc()',
176+
update: 'updateDoc()',
177+
},
178+
FirestoreDocumentSnapshot: {
179+
isEqual: NO_REPLACEMENT,
180+
},
181+
FirestoreFieldValue: {
182+
arrayRemove: 'arrayRemove()',
183+
arrayUnion: 'arrayUnion()',
184+
delete: 'deleteField()',
185+
increment: 'increment()',
186+
serverTimestamp: 'serverTimestamp()',
187+
},
188+
Filter: {
189+
or: 'or()',
190+
and: 'and()',
191+
},
192+
FirestorePersistentCacheIndexManager: {
193+
enableIndexAutoCreation: 'enablePersistentCacheIndexAutoCreation()',
194+
disableIndexAutoCreation: 'disablePersistentCacheIndexAutoCreation()',
195+
deleteAllIndexes: 'deleteAllPersistentCacheIndexes()',
196+
},
197+
FirestoreTimestamp: {
198+
seconds: NO_REPLACEMENT,
199+
nanoseconds: NO_REPLACEMENT,
200+
},
122201
},
123202
};
124203

125204
const v8deprecationMessage =
126205
'This v8 method is deprecated and will be removed in the next major release ' +
127206
'as part of move to match Firebase Web modular v9 SDK API.';
128207

129-
export function deprecationConsoleWarning(moduleName, methodName, isModularMethod) {
208+
export function deprecationConsoleWarning(nameSpace, methodName, instanceName, isModularMethod) {
130209
if (!isModularMethod) {
131-
const moduleMap = mapOfDeprecationReplacements[moduleName];
210+
const moduleMap = mapOfDeprecationReplacements[nameSpace];
132211
if (moduleMap) {
133-
const replacementMethodName = moduleMap[methodName];
134-
// only warn if it is mapped and purposefully deprecated
135-
if (replacementMethodName) {
136-
const message = createMessage(moduleName, methodName);
137-
212+
const instanceMap = moduleMap[instanceName];
213+
const deprecatedMethod = instanceMap[methodName];
214+
if (instanceMap && deprecatedMethod) {
215+
const message = createMessage(nameSpace, methodName, instanceName);
138216
// eslint-disable-next-line no-console
139217
console.warn(message);
140218
}
141219
}
142220
}
143221
}
144222

145-
export function createMessage(moduleName, methodName, uniqueMessage = '') {
146-
if (uniqueMessage.length > 0) {
223+
export function createMessage(
224+
nameSpace,
225+
methodName,
226+
instanceName = 'default',
227+
uniqueMessage = null,
228+
) {
229+
if (uniqueMessage) {
147230
// Unique deprecation message used for testing
148231
return uniqueMessage;
149232
}
150233

151-
const moduleMap = mapOfDeprecationReplacements[moduleName];
234+
const moduleMap = mapOfDeprecationReplacements[nameSpace];
152235
if (moduleMap) {
153-
const replacementMethodName = moduleMap[methodName];
154-
if (replacementMethodName) {
236+
const instance = moduleMap[instanceName];
237+
if (instance) {
238+
const replacementMethodName = instance[methodName];
239+
155240
if (replacementMethodName !== NO_REPLACEMENT) {
156241
return v8deprecationMessage + ` Please use \`${replacementMethodName}\` instead.`;
157242
} else {
@@ -161,9 +246,98 @@ export function createMessage(moduleName, methodName, uniqueMessage = '') {
161246
}
162247
}
163248

249+
function getNamespace(target) {
250+
if (target.GeoPoint) {
251+
// target is statics object. GeoPoint is a static class on Firestore
252+
return 'firestore';
253+
}
254+
if (target._config && target._config.namespace) {
255+
return target._config.namespace;
256+
}
257+
const className = target.name ? target.name : target.constructor.name;
258+
return Object.keys(mapOfDeprecationReplacements).find(key => {
259+
if (mapOfDeprecationReplacements[key][className]) {
260+
return key;
261+
}
262+
});
263+
}
264+
265+
function getInstanceName(target) {
266+
if (target.GeoPoint) {
267+
// target is statics object. GeoPoint is a static class on Firestore
268+
return 'statics';
269+
}
270+
if (target._config) {
271+
// module class instance, we use default to store map of deprecated methods
272+
return 'default';
273+
}
274+
if (target.name) {
275+
// It's a function which has a name property unlike classes
276+
return target.name;
277+
}
278+
// It's a class instance
279+
return target.constructor.name;
280+
}
281+
282+
export function createDeprecationProxy(instance) {
283+
return new Proxy(instance, {
284+
construct(target, args) {
285+
// needed for Timestamp which we pass as static, when we construct new instance, we need to wrap it in proxy again.
286+
return createDeprecationProxy(new target(...args));
287+
},
288+
get(target, prop, receiver) {
289+
const originalMethod = target[prop];
290+
291+
if (prop === 'constructor') {
292+
return Reflect.get(target, prop, receiver);
293+
}
294+
295+
if (target && target.constructor && target.constructor.name === 'FirestoreTimestamp') {
296+
deprecationConsoleWarning('firestore', prop, 'FirestoreTimestamp', false);
297+
return Reflect.get(target, prop, receiver);
298+
}
299+
300+
if (target && target.name === 'firebaseModuleWithApp') {
301+
// statics
302+
if (
303+
prop === 'Filter' ||
304+
prop === 'FieldValue' ||
305+
prop === 'Timestamp' ||
306+
prop === 'GeoPoint' ||
307+
prop === 'Blob' ||
308+
prop === 'FieldPath'
309+
) {
310+
deprecationConsoleWarning('firestore', prop, 'statics', false);
311+
}
312+
if (prop !== 'setLogLevel') {
313+
// we want to capture setLogLevel function call which we do below
314+
return Reflect.get(target, prop, receiver);
315+
}
316+
}
317+
318+
if (typeof originalMethod === 'function') {
319+
return function (...args) {
320+
const isModularMethod = args.includes(MODULAR_DEPRECATION_ARG);
321+
const instanceName = getInstanceName(target);
322+
const nameSpace = getNamespace(target);
323+
324+
deprecationConsoleWarning(nameSpace, prop, instanceName, isModularMethod);
325+
326+
return originalMethod.apply(target, filterModularArgument(args));
327+
};
328+
}
329+
return Reflect.get(target, prop, receiver);
330+
},
331+
});
332+
}
333+
164334
export const MODULAR_DEPRECATION_ARG = 'react-native-firebase-modular-method-call';
165335

166-
export function warnIfNotModularCall(args, replacementMethodName, noAlternative) {
336+
export function filterModularArgument(list) {
337+
return list.filter(arg => arg !== MODULAR_DEPRECATION_ARG);
338+
}
339+
340+
export function warnIfNotModularCall(args, replacementMethodName = '') {
167341
for (let i = 0; i < args.length; i++) {
168342
if (args[i] === MODULAR_DEPRECATION_ARG) {
169343
return;
@@ -173,7 +347,7 @@ export function warnIfNotModularCall(args, replacementMethodName, noAlternative)
173347
'This v8 method is deprecated and will be removed in the next major release ' +
174348
'as part of move to match Firebase Web modular v9 SDK API.';
175349

176-
if (!noAlternative) {
350+
if (replacementMethodName.length > 0) {
177351
message += ` Please use \`${replacementMethodName}\` instead.`;
178352
}
179353

packages/app/lib/common/unitTestUtils.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,29 @@ export const checkV9Deprecation = (modularFunction: () => void, nonModularFuncti
1818
export type CheckV9DeprecationFunction = (
1919
modularFunction: () => void,
2020
nonModularFunction: () => void,
21-
methodName: string,
21+
methodNameKey: string,
2222
uniqueMessage: string = '',
2323
) => void;
2424

25-
export const createCheckV9Deprecation = (moduleName: string): CheckV9DeprecationFunction => {
25+
export const createCheckV9Deprecation = (moduleNames: string[]): CheckV9DeprecationFunction => {
2626
return (
2727
modularFunction: () => void,
2828
nonModularFunction: () => void,
29-
methodName: string,
30-
uniqueMessage = '',
29+
methodNameKey: string,
30+
uniqueMessage: string?,
3131
) => {
32+
const moduleName = moduleNames[0]; // firestore, database, etc
33+
const instanceName = moduleNames[1] || 'default'; // default, FirestoreCollectionReference, etc
3234
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
35+
// Do not call `mockRestore()` as it removes the spy
3336
consoleWarnSpy.mockReset();
3437
modularFunction();
3538
expect(consoleWarnSpy).not.toHaveBeenCalled();
3639
consoleWarnSpy.mockReset();
40+
consoleWarnSpy.mockRestore();
3741
const consoleWarnSpy2 = jest.spyOn(console, 'warn').mockImplementation(warnMessage => {
38-
const message = createMessage(moduleName, methodName, uniqueMessage);
39-
expect(message).toMatch(warnMessage);
42+
const message = createMessage(moduleName, methodNameKey, instanceName, uniqueMessage);
43+
expect(warnMessage).toMatch(message);
4044
});
4145
nonModularFunction();
4246

packages/app/lib/internal/registry/namespace.js

+8-29
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*
1616
*/
1717

18-
import { isString, MODULAR_DEPRECATION_ARG, deprecationConsoleWarning } from '../../common';
18+
import { isString, createDeprecationProxy } from '../../common';
1919
import FirebaseApp from '../../FirebaseApp';
2020
import SDK_VERSION from '../../version';
2121
import { DEFAULT_APP_NAME, KNOWN_NAMESPACES } from '../constants';
@@ -114,11 +114,11 @@ function getOrCreateModuleForApp(app, moduleNamespace) {
114114
}
115115

116116
if (!APP_MODULE_INSTANCE[app.name][key]) {
117-
APP_MODULE_INSTANCE[app.name][key] = new ModuleClass(
118-
app,
119-
NAMESPACE_REGISTRY[moduleNamespace],
120-
customUrlOrRegionOrDatabaseId,
117+
const module = createDeprecationProxy(
118+
new ModuleClass(app, NAMESPACE_REGISTRY[moduleNamespace], customUrlOrRegionOrDatabaseId),
121119
);
120+
121+
APP_MODULE_INSTANCE[app.name][key] = module;
122122
}
123123

124124
return APP_MODULE_INSTANCE[app.name][key];
@@ -180,8 +180,9 @@ function getOrCreateModuleForRoot(moduleNamespace) {
180180
}
181181

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

186187
return MODULE_GETTER_FOR_ROOT[moduleNamespace];
187188
}
@@ -277,28 +278,6 @@ export function getFirebaseRoot() {
277278
return createFirebaseRoot();
278279
}
279280

280-
function createDeprecationProxy(instance) {
281-
return new Proxy(instance, {
282-
get(target, prop, receiver) {
283-
const originalMethod = target[prop];
284-
if (prop === 'constructor') {
285-
return target.constructor;
286-
}
287-
if (typeof originalMethod === 'function') {
288-
return function (...args) {
289-
const isModularMethod = args.includes(MODULAR_DEPRECATION_ARG);
290-
const moduleName = receiver._config.namespace;
291-
292-
deprecationConsoleWarning(moduleName, prop, isModularMethod);
293-
294-
return originalMethod.apply(target, args);
295-
};
296-
}
297-
return Reflect.get(target, prop, receiver);
298-
},
299-
});
300-
}
301-
302281
/**
303282
*
304283
* @param options

packages/crashlytics/__tests__/crashlytics.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ describe('Crashlytics', function () {
8989
let checkV9Deprecation: CheckV9DeprecationFunction;
9090

9191
beforeEach(function () {
92-
checkV9Deprecation = createCheckV9Deprecation('crashlytics');
92+
checkV9Deprecation = createCheckV9Deprecation(['crashlytics']);
9393

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

0 commit comments

Comments
 (0)