From 826b26456178c3e251636a77892837bdc0fcf253 Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Mon, 19 Aug 2024 14:05:37 -0700 Subject: [PATCH] feat: Group Concurrent `getClient` Requests --- src/auth/googleauth.ts | 21 ++++++++++++++++++++- test/test.googleauth.ts | 22 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/auth/googleauth.ts b/src/auth/googleauth.ts index 06ae466b..ae7a99a1 100644 --- a/src/auth/googleauth.ts +++ b/src/auth/googleauth.ts @@ -187,6 +187,10 @@ export class GoogleAuth { apiKey: string | null; cachedCredential: AnyAuthClient | T | null = null; + /** + * A pending {@link AuthClient}. Used for concurrent {@link GoogleAuth.getClient} calls. + */ + #pendingAuthClient: Promise | null = null; /** * Scopes populated by the client library by default. We differentiate between @@ -1007,7 +1011,22 @@ export class GoogleAuth { async getClient(): Promise { if (this.cachedCredential) { return this.cachedCredential; - } else if (this.jsonContent) { + } + + // Use an existing auth client request, or cache a new one + this.#pendingAuthClient = + this.#pendingAuthClient || this.#determineClient(); + + try { + return await this.#pendingAuthClient; + } finally { + // reset the pending auth client in case it is changed later + this.#pendingAuthClient = null; + } + } + + async #determineClient() { + if (this.jsonContent) { return this._cacheClientFromJSON(this.jsonContent, this.clientOptions); } else if (this.keyFilename) { const filePath = path.resolve(this.keyFilename); diff --git a/test/test.googleauth.ts b/test/test.googleauth.ts index afb56a3b..495585df 100644 --- a/test/test.googleauth.ts +++ b/test/test.googleauth.ts @@ -2617,6 +2617,28 @@ describe('googleauth', () => { assert(actualClient instanceof ExternalAccountAuthorizedUserClient); }); + + it('should return the same instance for concurrent requests', async () => { + // Set up a mock to return path to a valid credentials file. + mockEnvVar( + 'GOOGLE_APPLICATION_CREDENTIALS', + './test/fixtures/external-account-authorized-user-cred.json' + ); + const auth = new GoogleAuth(); + + let client: AuthClient | null = null; + const getClientCalls = await Promise.all([ + auth.getClient(), + auth.getClient(), + auth.getClient(), + ]); + + for (const resClient of getClientCalls) { + if (!client) client = resClient; + + assert(client === resClient); + } + }); }); describe('sign()', () => {