Skip to content

Commit 745c22e

Browse files
authored
fix: getToken() returns existing promise to a token if one exists instead of a new token. (#2648)
* fix: returns existing promise to a token if one exists instead of a new token. * fix: lint
1 parent d09e3bd commit 745c22e

File tree

3 files changed

+23
-6
lines changed

3 files changed

+23
-6
lines changed

src/app/firebase-app.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,26 +39,29 @@ export interface FirebaseAccessToken {
3939
*/
4040
export class FirebaseAppInternals {
4141
private cachedToken_: FirebaseAccessToken;
42+
private promiseToCachedToken_: Promise<FirebaseAccessToken>;
4243
private tokenListeners_: Array<(token: string) => void>;
44+
private isRefreshing: boolean;
4345

4446
// eslint-disable-next-line @typescript-eslint/naming-convention
4547
constructor(private credential_: Credential) {
4648
this.tokenListeners_ = [];
49+
this.isRefreshing = false;
4750
}
4851

4952
public getToken(forceRefresh = false): Promise<FirebaseAccessToken> {
5053
if (forceRefresh || this.shouldRefresh()) {
51-
return this.refreshToken();
54+
this.promiseToCachedToken_ = this.refreshToken();
5255
}
53-
54-
return Promise.resolve(this.cachedToken_);
56+
return this.promiseToCachedToken_
5557
}
5658

5759
public getCachedToken(): FirebaseAccessToken | null {
5860
return this.cachedToken_ || null;
5961
}
6062

6163
private refreshToken(): Promise<FirebaseAccessToken> {
64+
this.isRefreshing = true;
6265
return Promise.resolve(this.credential_.getAccessToken())
6366
.then((result) => {
6467
// Since the developer can provide the credential implementation, we want to weakly verify
@@ -108,11 +111,15 @@ export class FirebaseAppInternals {
108111
}
109112

110113
throw new FirebaseAppError(AppErrorCodes.INVALID_CREDENTIAL, errorMessage);
111-
});
114+
})
115+
.finally(() => {
116+
this.isRefreshing = false;
117+
})
112118
}
113119

114120
private shouldRefresh(): boolean {
115-
return !this.cachedToken_ || (this.cachedToken_.expirationTime - Date.now()) <= TOKEN_EXPIRY_THRESHOLD_MILLIS;
121+
return (!this.cachedToken_ || (this.cachedToken_.expirationTime - Date.now()) <= TOKEN_EXPIRY_THRESHOLD_MILLIS)
122+
&& !this.isRefreshing;
116123
}
117124

118125
/**

test/integration/messaging.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ describe('admin.messaging', () => {
278278
});
279279
});
280280

281-
xit('sendToDeviceGroup() returns a response with success count', () => {
281+
it('sendToDeviceGroup() returns a response with success count', () => {
282282
return getMessaging().sendToDeviceGroup(notificationKey, payload, options)
283283
.then((response) => {
284284
expect(typeof response.successCount).to.equal('number');

test/unit/app/firebase-app.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,16 @@ describe('FirebaseApp', () => {
808808
});
809809
});
810810

811+
it('only refreshes the token once for concurrent calls', () => {
812+
const promise1 = mockApp.INTERNAL.getToken();
813+
const promise2 = mockApp.INTERNAL.getToken();
814+
expect(getTokenStub).to.have.been.calledOnce;
815+
return Promise.all([promise1, promise2]).then((tokens) => {
816+
expect(tokens[0]).to.equal(tokens[1]);
817+
expect(getTokenStub).to.have.been.calledOnce;
818+
})
819+
});
820+
811821
it('Includes the original error in exception', () => {
812822
getTokenStub.restore();
813823
const mockError = new FirebaseAppError(

0 commit comments

Comments
 (0)