From 6ee6204bc4fb840f0c5ee1734183daa1b39e4574 Mon Sep 17 00:00:00 2001 From: Jonathan Edey Date: Mon, 29 Jul 2024 10:48:12 -0400 Subject: [PATCH 1/2] fix: returns existing promise to a token if one exists instead of a new token. --- src/app/firebase-app.ts | 16 +++++++++++----- test/integration/messaging.spec.ts | 2 +- test/unit/app/firebase-app.spec.ts | 10 ++++++++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 4dd97cb33d..b0696dc590 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -39,19 +39,21 @@ export interface FirebaseAccessToken { */ export class FirebaseAppInternals { private cachedToken_: FirebaseAccessToken; + private promiseToCachedToken_: Promise; private tokenListeners_: Array<(token: string) => void>; + private isRefreshing: boolean; // eslint-disable-next-line @typescript-eslint/naming-convention constructor(private credential_: Credential) { this.tokenListeners_ = []; + this.isRefreshing = false; } public getToken(forceRefresh = false): Promise { if (forceRefresh || this.shouldRefresh()) { - return this.refreshToken(); + this.promiseToCachedToken_ = this.refreshToken(); } - - return Promise.resolve(this.cachedToken_); + return this.promiseToCachedToken_ } public getCachedToken(): FirebaseAccessToken | null { @@ -59,6 +61,7 @@ export class FirebaseAppInternals { } private refreshToken(): Promise { + this.isRefreshing = true; return Promise.resolve(this.credential_.getAccessToken()) .then((result) => { // Since the developer can provide the credential implementation, we want to weakly verify @@ -108,11 +111,14 @@ export class FirebaseAppInternals { } throw new FirebaseAppError(AppErrorCodes.INVALID_CREDENTIAL, errorMessage); - }); + }) + .finally(() => { + this.isRefreshing = false; + }) } private shouldRefresh(): boolean { - return !this.cachedToken_ || (this.cachedToken_.expirationTime - Date.now()) <= TOKEN_EXPIRY_THRESHOLD_MILLIS; + return (!this.cachedToken_ || (this.cachedToken_.expirationTime - Date.now()) <= TOKEN_EXPIRY_THRESHOLD_MILLIS) && !this.isRefreshing; } /** diff --git a/test/integration/messaging.spec.ts b/test/integration/messaging.spec.ts index d11b035861..d564e06f66 100644 --- a/test/integration/messaging.spec.ts +++ b/test/integration/messaging.spec.ts @@ -278,7 +278,7 @@ describe('admin.messaging', () => { }); }); - xit('sendToDeviceGroup() returns a response with success count', () => { + it('sendToDeviceGroup() returns a response with success count', () => { return getMessaging().sendToDeviceGroup(notificationKey, payload, options) .then((response) => { expect(typeof response.successCount).to.equal('number'); diff --git a/test/unit/app/firebase-app.spec.ts b/test/unit/app/firebase-app.spec.ts index 2211f4f457..df4fc84a67 100644 --- a/test/unit/app/firebase-app.spec.ts +++ b/test/unit/app/firebase-app.spec.ts @@ -808,6 +808,16 @@ describe('FirebaseApp', () => { }); }); + it('only refreshes the token once for concurrent calls', () => { + const promise1 = mockApp.INTERNAL.getToken(); + const promise2 = mockApp.INTERNAL.getToken(); + expect(getTokenStub).to.have.been.calledOnce; + return Promise.all([promise1, promise2]).then((tokens) => { + expect(tokens[0]).to.equal(tokens[1]); + expect(getTokenStub).to.have.been.calledOnce; + }) + }); + it('Includes the original error in exception', () => { getTokenStub.restore(); const mockError = new FirebaseAppError( From 43948a88a1bede92c2eebb77af6f2c59af7f5f44 Mon Sep 17 00:00:00 2001 From: Jonathan Edey Date: Mon, 29 Jul 2024 11:05:25 -0400 Subject: [PATCH 2/2] fix: lint --- src/app/firebase-app.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index b0696dc590..a0ee93e1fe 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -118,7 +118,8 @@ export class FirebaseAppInternals { } private shouldRefresh(): boolean { - return (!this.cachedToken_ || (this.cachedToken_.expirationTime - Date.now()) <= TOKEN_EXPIRY_THRESHOLD_MILLIS) && !this.isRefreshing; + return (!this.cachedToken_ || (this.cachedToken_.expirationTime - Date.now()) <= TOKEN_EXPIRY_THRESHOLD_MILLIS) + && !this.isRefreshing; } /**