Skip to content

Commit cb46f75

Browse files
bojeil-googlejshcrowthe
authored andcommitted
fix(app): fix issue with FirebaseApp extending INTERNAL (#38)
* fix(app): fix issue with FirebaseApp extending INTERNAL FirebaseAppImpl should not extend prototype.INTERNAL as this will apply to all Firebase app instances. To reproduce, do the following: var app1 = firebase.initializeApp(config); var app2 = firebase.initializeApp(config, 'app2'); console.log(app1.INTERNAL === app2.internal); The above incorrectly resolves to true. This means that if I sign in with app1.auth() and then call app1.INTERNAL.getToken(), it will resolve with null as it is getting app2.INTERNAL.getToken. The last initialized instance will basically overwrite all existing ones. This is currently breaking all FirebaseUI-web single page applications using Firebase real-time database with JS version >= 4.1.0: firebase/firebaseui-web#47 (comment) This should also be breaking any application using Auth and Database with multiple app instances. * fix(app): removed extra spaces and commented code
1 parent 8a90518 commit cb46f75

File tree

2 files changed

+49
-15
lines changed

2 files changed

+49
-15
lines changed

src/app/firebase_app.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,18 @@ class FirebaseAppImpl implements FirebaseApp {
246246
private firebase_: FirebaseNamespace) {
247247
this.name_ = name;
248248
this.options_ = deepCopy<FirebaseOptions>(options);
249+
this.INTERNAL = {
250+
'getUid': () => null,
251+
'getToken': () => LocalPromise.resolve(null),
252+
'addAuthTokenListener': (callback: (token: string|null) => void) => {
253+
tokenListeners.push(callback);
254+
// Make sure callback is called, asynchronously, in the absence of the auth module
255+
setTimeout(() => callback(null), 0);
256+
},
257+
'removeAuthTokenListener': (callback) => {
258+
tokenListeners = tokenListeners.filter(listener => listener !== callback);
259+
},
260+
};
249261
}
250262

251263
get name(): string {
@@ -321,7 +333,7 @@ class FirebaseAppImpl implements FirebaseApp {
321333
*/
322334
private extendApp(props: {[name: string]: any}): void {
323335
// Copy the object onto the FirebaseAppImpl prototype
324-
deepExtend(FirebaseAppImpl.prototype, props);
336+
deepExtend(this, props);
325337

326338
/**
327339
* If the app has overwritten the addAuthTokenListener stub, forward
@@ -351,19 +363,6 @@ class FirebaseAppImpl implements FirebaseApp {
351363
}
352364
};
353365

354-
FirebaseAppImpl.prototype.INTERNAL = {
355-
'getUid': () => null,
356-
'getToken': () => LocalPromise.resolve(null),
357-
'addAuthTokenListener': (callback: (token: string|null) => void) => {
358-
tokenListeners.push(callback);
359-
// Make sure callback is called, asynchronously, in the absence of the auth module
360-
setTimeout(() => callback(null), 0);
361-
},
362-
'removeAuthTokenListener': (callback) => {
363-
tokenListeners = tokenListeners.filter(listener => listener !== callback);
364-
},
365-
}
366-
367366
// Prevent dead-code elimination of these methods w/o invalid property
368367
// copying.
369368
FirebaseAppImpl.prototype.name &&
@@ -603,4 +602,4 @@ let errors: {[code: string]: string} = {
603602
'Firebase App instance.'
604603
};
605604

606-
let appErrors = new ErrorFactory<AppError>('app', 'Firebase', errors);
605+
let appErrors = new ErrorFactory<AppError>('app', 'Firebase', errors);

tests/app/unit/firebase_app.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,41 @@ describe("Firebase App Class", () => {
294294
const service = (firebase.app() as any).testService();
295295
});
296296

297+
it(`Should extend INTERNAL per app instance`, () => {
298+
let counter: number = 0;
299+
firebase.INTERNAL.registerService(
300+
'test',
301+
(app: FirebaseApp, extendApp: any) => {
302+
const service = new TestService(app);
303+
(service as any).token = 'tokenFor' + counter++;
304+
extendApp({
305+
'INTERNAL': {
306+
getToken: () => {
307+
return Promise.resolve({
308+
accessToken: (service as any).token,
309+
});
310+
},
311+
},
312+
});
313+
return service;
314+
});
315+
// Initialize 2 apps and their corresponding services.
316+
const app = firebase.initializeApp({});
317+
(app as any).test();
318+
const app2 = firebase.initializeApp({}, 'app2');
319+
(app2 as any).test();
320+
// Confirm extended INTERNAL getToken resolve with the corresponding
321+
// service's value.
322+
return app.INTERNAL.getToken()
323+
.then(token => {
324+
assert.equal('tokenFor0', token.accessToken);
325+
return app2.INTERNAL.getToken();
326+
})
327+
.then(token => {
328+
assert.equal('tokenFor1', token.accessToken);
329+
});
330+
});
331+
297332
describe("Check for bad app names", () => {
298333
let tests = ["", 123, false, null];
299334
for (let data of tests) {

0 commit comments

Comments
 (0)