From b6852df0188992462d6570e992fedd9e777c8d54 Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Wed, 5 Feb 2025 17:07:23 -0800 Subject: [PATCH] refactor: Use Gaxios Interceptors for default Auth headers --- src/auth/authclient.ts | 77 ++++++++++++++++++++++++----------------- test/test.authclient.ts | 28 +++++++++++++++ 2 files changed, 74 insertions(+), 31 deletions(-) diff --git a/src/auth/authclient.ts b/src/auth/authclient.ts index 5dc84517..fecb5cda 100644 --- a/src/auth/authclient.ts +++ b/src/auth/authclient.ts @@ -83,6 +83,8 @@ export interface AuthClientOptions /** * The {@link Gaxios `Gaxios`} instance used for making requests. + * + * @see {@link AuthClientOptions.useAuthRequestParameters} */ gaxios?: Gaxios; @@ -106,6 +108,19 @@ export interface AuthClientOptions * on the expiry_date. */ forceRefreshOnFailure?: boolean; + + /** + * Enables/disables the adding of the AuthClient's default interceptor. + * + * @see {@link AuthClientOptions.gaxios} + * + * @remarks + * + * Disabling is useful for debugging and experimentation. + * + * @default true + */ + useAuthRequestParameters?: boolean; } /** @@ -210,6 +225,12 @@ export abstract class AuthClient // Shared client options this.transporter = opts.gaxios ?? new Gaxios(opts.transporterOptions); + if (options.get('useAuthRequestParameters') !== false) { + this.transporter.interceptors.request.add( + AuthClient.DEFAULT_REQUEST_INTERCEPTOR + ); + } + if (opts.eagerRefreshThresholdMillis) { this.eagerRefreshThresholdMillis = opts.eagerRefreshThresholdMillis; } @@ -224,37 +245,6 @@ export abstract class AuthClient */ abstract request(options: GaxiosOptions): GaxiosPromise; - /** - * The internal request handler. - * - * @privateRemarks - * - * At this stage the credentials have been already prepared - passing it - * here adds the standard headers to the request, if not previously configured. - * - * @param options options for `gaxios` - */ - protected _request(options: GaxiosOptions): GaxiosPromise { - const headers = options.headers || {}; - - // Set `x-goog-api-client`, if not already set - if (!headers['x-goog-api-client']) { - const nodeVersion = process.version.replace(/^v/, ''); - headers['x-goog-api-client'] = `gl-node/${nodeVersion}`; - } - - // Set `User-Agent` - if (!headers['User-Agent']) { - headers['User-Agent'] = USER_AGENT; - } else if (!headers['User-Agent'].includes(`${PRODUCT_NAME}/`)) { - headers['User-Agent'] = `${headers['User-Agent']} ${USER_AGENT}`; - } - - options.headers = headers; - - return this.transporter.request(options); - } - /** * The main authentication interface. It takes an optional url which when * present is the endpoint being accessed, and returns a Promise which @@ -303,6 +293,31 @@ export abstract class AuthClient return headers; } + static readonly DEFAULT_REQUEST_INTERCEPTOR: Parameters< + Gaxios['interceptors']['request']['add'] + >[0] = { + resolved: async config => { + const headers = config.headers || {}; + + // Set `x-goog-api-client`, if not already set + if (!headers['x-goog-api-client']) { + const nodeVersion = process.version.replace(/^v/, ''); + headers['x-goog-api-client'] = `gl-node/${nodeVersion}`; + } + + // Set `User-Agent` + if (!headers['User-Agent']) { + headers['User-Agent'] = USER_AGENT; + } else if (!headers['User-Agent'].includes(`${PRODUCT_NAME}/`)) { + headers['User-Agent'] = `${headers['User-Agent']} ${USER_AGENT}`; + } + + config.headers = headers; + + return config; + }, + }; + /** * Retry config for Auth-related requests. * diff --git a/test/test.authclient.ts b/test/test.authclient.ts index 2faa0f15..28261eed 100644 --- a/test/test.authclient.ts +++ b/test/test.authclient.ts @@ -16,6 +16,7 @@ import {strict as assert} from 'assert'; import {PassThroughClient} from '../src'; import {snakeToCamel} from '../src/util'; +import {Gaxios} from 'gaxios'; describe('AuthClient', () => { it('should accept and normalize snake case options to camel case', () => { @@ -38,4 +39,31 @@ describe('AuthClient', () => { assert.equal(authClient[camelCased], value); } }); + + it('should allow disabling of the default interceptor', () => { + const gaxios = new Gaxios(); + const originalInterceptorCount = gaxios.interceptors.request.size; + + const authClient = new PassThroughClient({ + gaxios, + useAuthRequestParameters: false, + }); + + assert.equal(authClient.transporter, gaxios); + assert.equal( + authClient.transporter.interceptors.request.size, + originalInterceptorCount + ); + }); + + it('should add the default interceptor exactly once between instances', () => { + const gaxios = new Gaxios(); + const originalInterceptorCount = gaxios.interceptors.request.size; + const expectedInterceptorCount = originalInterceptorCount + 1; + + new PassThroughClient({gaxios}); + new PassThroughClient({gaxios}); + + assert.equal(gaxios.interceptors.request.size, expectedInterceptorCount); + }); });