From f3a05ca497546469e2d4a3d55adeea158f467a5a Mon Sep 17 00:00:00 2001 From: Nick Richmond <5732000+NWRichmond@users.noreply.github.com> Date: Mon, 1 May 2023 18:34:03 -0400 Subject: [PATCH] refactor: request headers include `x-nacelle-sdk-version` (#385) * refactor: use static method to set request headers * docs: add changeset --- .changeset/kind-frogs-chew.md | 5 ++ .../src/client/createClient.test.ts | 5 +- .../storefront-sdk/src/client/index.test.ts | 26 ++++++- packages/storefront-sdk/src/client/index.ts | 77 +++++++++++++------ .../storefront-sdk/src/utils/constants.ts | 1 + 5 files changed, 88 insertions(+), 26 deletions(-) create mode 100644 .changeset/kind-frogs-chew.md diff --git a/.changeset/kind-frogs-chew.md b/.changeset/kind-frogs-chew.md new file mode 100644 index 00000000..3ac1a82e --- /dev/null +++ b/.changeset/kind-frogs-chew.md @@ -0,0 +1,5 @@ +--- +'@nacelle/storefront-sdk': patch +--- + +Adds an `x-nacelle-sdk-version` header to all requests. This improves tracing and observability in the event of a support request. diff --git a/packages/storefront-sdk/src/client/createClient.test.ts b/packages/storefront-sdk/src/client/createClient.test.ts index ea8834bc..1f8fe028 100644 --- a/packages/storefront-sdk/src/client/createClient.test.ts +++ b/packages/storefront-sdk/src/client/createClient.test.ts @@ -1,6 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { createClient } from '@urql/core'; import { StorefrontClient } from './index.js'; +import { version as packageVersion } from '../../package.json'; vi.mock('@urql/core'); @@ -23,7 +24,9 @@ describe('`createClient`', () => { url: storefrontEndpoint, fetch: globalThis.fetch, fetchOptions: { - headers: {} + headers: { + 'x-nacelle-sdk-version': packageVersion + } } }) ); diff --git a/packages/storefront-sdk/src/client/index.test.ts b/packages/storefront-sdk/src/client/index.test.ts index fb0e8140..02c0494c 100644 --- a/packages/storefront-sdk/src/client/index.test.ts +++ b/packages/storefront-sdk/src/client/index.test.ts @@ -13,7 +13,11 @@ import { StorefrontClient, retryExchange, retryStatusCodes } from './index.js'; import { NavigationDocument } from '../../__mocks__/gql/operations.js'; import getFetchPayload from '../../__mocks__/utils/getFetchPayload.js'; import NavigationResult from '../../__mocks__/gql/navigation.js'; -import { errorMessages, X_NACELLE_PREVIEW_TOKEN } from '../utils/index.js'; +import { + errorMessages, + X_NACELLE_SDK_VERSION, + X_NACELLE_PREVIEW_TOKEN +} from '../utils/index.js'; import type { StorefrontResponse } from './index.js'; import type { NavigationGroup, @@ -575,6 +579,26 @@ it('sets the expected header and query param when initialized with a `previewTok expect(requestHeaders[X_NACELLE_PREVIEW_TOKEN]).toBe(myPreviewToken); }); +it("sends an 'x-nacelle-sdk-version' header", async () => { + mockedFetch.mockImplementationOnce(() => + Promise.resolve(getFetchPayload({ data: NavigationResult })) + ); + const variables = { filter: { groupId: 'abc' } }; + await client.query({ + query: NavigationDocument, + variables + }); + + const lastFetch = mockedFetch.mock.lastCall as mockRequestArgs; + const requestHeaders = lastFetch[1]?.headers as HeadersInit & { + [X_NACELLE_SDK_VERSION]?: string; + }; + + expect(requestHeaders[X_NACELLE_SDK_VERSION]).toMatch( + /\b\d{1,2}.\d{1,2}.\d{1,2}\b/ // three sequences of 1-2 digits separated by periods + ); +}); + it('`getConfig` retrieves config', () => { const client = new StorefrontClient({ storefrontEndpoint, diff --git a/packages/storefront-sdk/src/client/index.ts b/packages/storefront-sdk/src/client/index.ts index a6d177c0..c77bcf55 100644 --- a/packages/storefront-sdk/src/client/index.ts +++ b/packages/storefront-sdk/src/client/index.ts @@ -1,7 +1,12 @@ import { createClient, fetchExchange } from '@urql/core'; import { retryExchange as urqlRetryExchange } from '@urql/exchange-retry'; import { persistedExchange as urqlPersistedExchange } from '@urql/exchange-persisted'; -import { errorMessages, X_NACELLE_PREVIEW_TOKEN } from '../utils/index.js'; +import { + errorMessages, + X_NACELLE_SDK_VERSION, + X_NACELLE_PREVIEW_TOKEN +} from '../utils/index.js'; +import { version as packageVersion } from '../../package.json'; import type { Client as UrqlClient, TypedDocumentNode, @@ -97,18 +102,13 @@ export class StorefrontClient { throw new Error(errorMessages.missingEndpoint); } - const storefrontEndpointUrl = new URL(params.storefrontEndpoint); - let headers = {}; - - if (params.previewToken) { - headers = { [X_NACELLE_PREVIEW_TOKEN]: params.previewToken }; - storefrontEndpointUrl.searchParams.set('preview', 'true'); - } - this.#config = { exchanges: params.exchanges ?? defaultExchanges, fetchClient: params.fetchClient ?? globalThis.fetch, - storefrontEndpoint: storefrontEndpointUrl.toString(), + storefrontEndpoint: StorefrontClient.getStorefrontEndpoint( + params.storefrontEndpoint, + params.previewToken + ), previewToken: params.previewToken, locale: params.locale ?? 'en-US' }; @@ -117,7 +117,7 @@ export class StorefrontClient { url: this.#config.storefrontEndpoint, fetch: this.#config.fetchClient, fetchOptions: { - headers + headers: StorefrontClient.getHeaders(params.previewToken) }, exchanges: this.#config.exchanges }); @@ -155,24 +155,22 @@ export class StorefrontClient { * ``` */ setConfig(setConfigParams: SetConfigParams): SetConfigResponse { - const currentEndpoint = new URL(this.#config.storefrontEndpoint); - - let headers = {}; - - if (setConfigParams.previewToken) { - this.#config.previewToken = setConfigParams.previewToken; - currentEndpoint.searchParams.set('preview', 'true'); - headers = { [X_NACELLE_PREVIEW_TOKEN]: this.#config.previewToken }; - } else { - this.#config.previewToken = undefined; - currentEndpoint.searchParams.delete('preview'); + this.#config.storefrontEndpoint = StorefrontClient.getStorefrontEndpoint( + this.#config.storefrontEndpoint, + setConfigParams.previewToken + ); + + if (typeof setConfigParams.previewToken !== 'undefined') { + // set to `undefined` if `null` or empty string + this.#config.previewToken = setConfigParams.previewToken || undefined; } - this.#config.storefrontEndpoint = currentEndpoint.toString(); this.#graphqlClient = createClient({ url: this.#config.storefrontEndpoint, fetch: this.#config.fetchClient, - fetchOptions: { headers }, + fetchOptions: { + headers: StorefrontClient.getHeaders(setConfigParams.previewToken) + }, exchanges: this.#config.exchanges }); @@ -300,4 +298,35 @@ export class StorefrontClient { }) .then(({ data, error }) => this.applyAfter('query', { data, error })); } + + private static getHeaders( + previewToken?: string | null + ): Record { + const headers: Record = { + [X_NACELLE_SDK_VERSION]: packageVersion + }; + + if (previewToken) { + headers[X_NACELLE_PREVIEW_TOKEN] = previewToken; + } + + return headers; + } + + private static getStorefrontEndpoint( + endpoint: string, + previewToken?: string | null + ): string { + const endpointUrl = new URL(endpoint); + + if (previewToken) { + endpointUrl.searchParams.set('preview', 'true'); + } else if (typeof previewToken !== 'undefined') { + // `previewToken` is `null`, an empty string, + // or some other falsey, non-`undefined` value + endpointUrl.searchParams.delete('preview'); + } + + return endpointUrl.toString(); + } } diff --git a/packages/storefront-sdk/src/utils/constants.ts b/packages/storefront-sdk/src/utils/constants.ts index 492e618d..67f17754 100644 --- a/packages/storefront-sdk/src/utils/constants.ts +++ b/packages/storefront-sdk/src/utils/constants.ts @@ -1 +1,2 @@ export const X_NACELLE_PREVIEW_TOKEN = 'x-nacelle-preview-token'; +export const X_NACELLE_SDK_VERSION = 'x-nacelle-sdk-version';