From e5bd3d6bdb62ce18dd2e8d5453dfd4b83df6d500 Mon Sep 17 00:00:00 2001 From: Ethan Jaffee Date: Fri, 26 Jan 2024 14:43:39 -0500 Subject: [PATCH] EventsSDK: Revise Authorization in AnalyticsConfig (#124) Previously we had to fields in AnalyticsConfig that we're optional, `key` and `bearer`, where we throw an error if none or both are set. This change updates the interface so it is more clear that only one is required. There is now `authorizationType` which is either a string `apiKey` or `Bearer` and `authorization` which is the token itself. We need `authorizationType` so we can append the proper prefix of `KEY ` or `Bearer ` to the final authorization property to avoid passing this work onto the user. This also updates necessary test files to reflect this change, and removes logic for ensuring that only one of key or bearer is set. [J=FUS-6201](https://yexttest.atlassian.net/browse/FUS-6201) R=abenno, mtian, mkilpatrick TEST=auto --------- Co-authored-by: Ethan Jaffee Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- ...analytics.analyticsconfig.authorization.md | 13 ++++ ...ytics.analyticsconfig.authorizationtype.md | 13 ++++ docs/analytics.analyticsconfig.bearer.md | 13 ---- docs/analytics.analyticsconfig.key.md | 13 ---- docs/analytics.analyticsconfig.md | 4 +- etc/analytics.api.md | 4 +- src/AnalyticsConfig.ts | 12 +-- src/AnalyticsEventReporter.ts | 21 ++--- src/convertStringToValue.ts | 3 +- test-site/src/index.ts | 6 +- tests/AnalyticsEventReporter.test.ts | 77 +++++++++++++------ tests/reportBrowserAnalytics.test.ts | 5 +- 12 files changed, 110 insertions(+), 74 deletions(-) create mode 100644 docs/analytics.analyticsconfig.authorization.md create mode 100644 docs/analytics.analyticsconfig.authorizationtype.md delete mode 100644 docs/analytics.analyticsconfig.bearer.md delete mode 100644 docs/analytics.analyticsconfig.key.md diff --git a/docs/analytics.analyticsconfig.authorization.md b/docs/analytics.analyticsconfig.authorization.md new file mode 100644 index 00000000..0d9f3fa8 --- /dev/null +++ b/docs/analytics.analyticsconfig.authorization.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@yext/analytics](./analytics.md) > [AnalyticsConfig](./analytics.analyticsconfig.md) > [authorization](./analytics.analyticsconfig.authorization.md) + +## AnalyticsConfig.authorization property + +The API Key, OAuth, or bearer token for accessing the Analytics Events API. + +**Signature:** + +```typescript +authorization: string; +``` diff --git a/docs/analytics.analyticsconfig.authorizationtype.md b/docs/analytics.analyticsconfig.authorizationtype.md new file mode 100644 index 00000000..81408ced --- /dev/null +++ b/docs/analytics.analyticsconfig.authorizationtype.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@yext/analytics](./analytics.md) > [AnalyticsConfig](./analytics.analyticsconfig.md) > [authorizationType](./analytics.analyticsconfig.authorizationtype.md) + +## AnalyticsConfig.authorizationType property + +Used for specifying if an API Key or Bearer Token is used for the authorization property. + +**Signature:** + +```typescript +authorizationType: 'apiKey' | 'bearer'; +``` diff --git a/docs/analytics.analyticsconfig.bearer.md b/docs/analytics.analyticsconfig.bearer.md deleted file mode 100644 index a96cb4c8..00000000 --- a/docs/analytics.analyticsconfig.bearer.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@yext/analytics](./analytics.md) > [AnalyticsConfig](./analytics.analyticsconfig.md) > [bearer](./analytics.analyticsconfig.bearer.md) - -## AnalyticsConfig.bearer property - -The bearer token for accessing the Analytics Events API. Only one of key or bearer should be set. - -**Signature:** - -```typescript -bearer?: string; -``` diff --git a/docs/analytics.analyticsconfig.key.md b/docs/analytics.analyticsconfig.key.md deleted file mode 100644 index ad2f8dbf..00000000 --- a/docs/analytics.analyticsconfig.key.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@yext/analytics](./analytics.md) > [AnalyticsConfig](./analytics.analyticsconfig.md) > [key](./analytics.analyticsconfig.key.md) - -## AnalyticsConfig.key property - -The API Key or OAuth for accessing the Analytics Events API Only one of key or bearer should be set. - -**Signature:** - -```typescript -key?: string; -``` diff --git a/docs/analytics.analyticsconfig.md b/docs/analytics.analyticsconfig.md index 2b9d6f6a..72c31718 100644 --- a/docs/analytics.analyticsconfig.md +++ b/docs/analytics.analyticsconfig.md @@ -16,10 +16,10 @@ export interface AnalyticsConfig | Property | Modifiers | Type | Description | | --- | --- | --- | --- | -| [bearer?](./analytics.analyticsconfig.bearer.md) | | string | _(Optional)_ The bearer token for accessing the Analytics Events API. Only one of key or bearer should be set. | +| [authorization](./analytics.analyticsconfig.authorization.md) | | string | The API Key, OAuth, or bearer token for accessing the Analytics Events API. | +| [authorizationType](./analytics.analyticsconfig.authorizationtype.md) | | 'apiKey' \| 'bearer' | Used for specifying if an API Key or Bearer Token is used for the authorization property. | | [env?](./analytics.analyticsconfig.env.md) | | [Environment](./analytics.environment.md) | _(Optional)_ The Yext environment to send requests to. Defaults to 'PRODUCTION'. | | [forceFetch?](./analytics.analyticsconfig.forcefetch.md) | | boolean | _(Optional)_ Used to force sending the request with fetch even if the browser does not support fetch with the keepalive flag (like Firefox). If the browser does support it, fetch is used by default. | -| [key?](./analytics.analyticsconfig.key.md) | | string | _(Optional)_ The API Key or OAuth for accessing the Analytics Events API Only one of key or bearer should be set. | | [region?](./analytics.analyticsconfig.region.md) | | [Region](./analytics.region.md) | _(Optional)_ The region to send requests to. Defaults to 'US'. | | [sessionTrackingEnabled?](./analytics.analyticsconfig.sessiontrackingenabled.md) | | boolean | _(Optional)_ Whether to enable session tracking for analytics events. Defaults to true for both environments. If set to false, sessionId will automatically be set to undefined in the event payload. | diff --git a/etc/analytics.api.md b/etc/analytics.api.md index 26cb74f3..95dd4d58 100644 --- a/etc/analytics.api.md +++ b/etc/analytics.api.md @@ -12,10 +12,10 @@ export function analytics(config: AnalyticsConfig): AnalyticsEventService; // @public export interface AnalyticsConfig { - bearer?: string; + authorization: string; + authorizationType: 'apiKey' | 'bearer'; env?: Environment; forceFetch?: boolean; - key?: string; region?: Region; sessionTrackingEnabled?: boolean; } diff --git a/src/AnalyticsConfig.ts b/src/AnalyticsConfig.ts index 09f4f3d5..803521dd 100644 --- a/src/AnalyticsConfig.ts +++ b/src/AnalyticsConfig.ts @@ -7,14 +7,10 @@ import { Region } from './Region'; * @public */ export interface AnalyticsConfig { - /** The API Key or OAuth for accessing the Analytics Events API - * Only one of key or bearer should be set. - * */ - key?: string; - /** The bearer token for accessing the Analytics Events API. - * Only one of key or bearer should be set. - * */ - bearer?: string; + /** Used for specifying if an API Key or Bearer Token is used for the authorization property. */ + authorizationType: 'apiKey' | 'bearer'; + /** The API Key, OAuth, or bearer token for accessing the Analytics Events API. */ + authorization: string; /** The Yext environment to send requests to. Defaults to 'PRODUCTION'. */ env?: Environment; /** The region to send requests to. Defaults to 'US'. */ diff --git a/src/AnalyticsEventReporter.ts b/src/AnalyticsEventReporter.ts index 2cf3a088..83ca624b 100644 --- a/src/AnalyticsEventReporter.ts +++ b/src/AnalyticsEventReporter.ts @@ -18,12 +18,14 @@ export class AnalyticsEventReporter implements AnalyticsEventService { * @param payload - (optional) desired event values to report */ constructor(config: AnalyticsConfig, payload?: EventPayload) { - const apiKeyIsSet = config.key !== undefined; - const bearerTokenIsSet = config.bearer !== undefined; - const configIsValid = - (apiKeyIsSet || bearerTokenIsSet) && !(apiKeyIsSet && bearerTokenIsSet); - if (!configIsValid) { - throw new Error('Provide one and only one of API Key or Bearer Token.'); + if ( + config.authorizationType !== 'apiKey' && + config.authorizationType !== 'bearer' + ) { + throw new Error('Authorization type must be either apiKey or bearer.'); + } + if (!config.authorization) { + throw new Error('Authorization must be provided.'); } this.config = config; this.payload = payload; @@ -56,9 +58,10 @@ export class AnalyticsEventReporter implements AnalyticsEventService { packageinfo.version) : (finalPayload.clientSdk = { ['ANALYTICS']: packageinfo.version }); - finalPayload.authorization = this.config.key - ? 'KEY ' + this.config.key - : 'Bearer ' + this.config.bearer; + finalPayload.authorization = + this.config.authorizationType === 'apiKey' + ? 'KEY ' + this.config.authorization + : 'Bearer ' + this.config.authorization; const shouldUseBeacon = useBeacon(finalPayload, this.config.forceFetch); const requestUrl = setupRequestUrl(this.config.env, this.config.region); diff --git a/src/convertStringToValue.ts b/src/convertStringToValue.ts index fc43d580..317ac8b9 100644 --- a/src/convertStringToValue.ts +++ b/src/convertStringToValue.ts @@ -1,4 +1,5 @@ import { EventPayload } from './EventPayload'; +import { AnalyticsConfig } from './AnalyticsConfig'; // Define the list of possibly numerical properties to check and convert const propertiesToCheck = [ @@ -18,7 +19,7 @@ const propertiesToCheck = [ export function convertStringToValue( data: Record -): EventPayload { +): EventPayload | AnalyticsConfig { // Recursive function to traverse and convert nested objects function recursiveConversion(obj: Record) { for (const property in obj) { diff --git a/test-site/src/index.ts b/test-site/src/index.ts index f97cd078..5cf7031c 100644 --- a/test-site/src/index.ts +++ b/test-site/src/index.ts @@ -9,7 +9,8 @@ import { analytics } from '@yext/analytics'; * We will continue investigating this make a fix if necessary. */ const analyticsProvider = analytics({ - key: process.env.YEXT_API_KEY, + authorizationType: 'apiKey', + authorization: process.env.YEXT_API_KEY, sessionTrackingEnabled: false }).with({ action: 'CHAT_LINK_CLICK', @@ -53,7 +54,8 @@ const analyticsProvider = analytics({ }); const analyticsProvideWithSessionTracking = analytics({ - key: process.env.YEXT_API_KEY + authorizationType: 'apiKey', + authorization: process.env.YEXT_API_KEY }).with({ action: 'CHAT_LINK_CLICK', pageUrl: 'http://www.yext-test-pageurl.com', diff --git a/tests/AnalyticsEventReporter.test.ts b/tests/AnalyticsEventReporter.test.ts index 2208e42d..1f88fba6 100644 --- a/tests/AnalyticsEventReporter.test.ts +++ b/tests/AnalyticsEventReporter.test.ts @@ -9,39 +9,59 @@ import { AnalyticsEventService } from '../src/AnalyticsEventService'; jest.mock('../src/post'); jest.mock('../src/setupSessionId'); -it('Invalid config with no authorization field', () => { +it('Invalid config with niether authorization fields', () => { expect(() => new AnalyticsEventReporter({})).toThrowError( - 'Provide one and only one of API Key or Bearer Token.' + 'Authorization type must be either apiKey or bearer.' ); +}); +it('Invalid config with no authorizationType field', () => { const InvalidConfig: AnalyticsConfig = { - key: undefined + authorizationType: undefined, + authorization: 'myKey' }; expect(() => new AnalyticsEventReporter(InvalidConfig)).toThrowError( - 'Provide one and only one of API Key or Bearer Token.' + 'Authorization type must be either apiKey or bearer.' ); }); -it('Invalid config with both authorization field', () => { +it('Invalid config with invalid authorizationType field', () => { + const InvalidConfig: AnalyticsConfig = { + authorizationType: 'type5', + authorization: 'myKey' + }; + expect(() => new AnalyticsEventReporter(InvalidConfig)).toThrowError( + 'Authorization type must be either apiKey or bearer.' + ); +}); +it('Invalid config with no authorization field', () => { const InvalidConfig: AnalyticsConfig = { - key: 'mock-api-key', - bearer: 'mock-bearer-token' + authorizationType: 'apiKey', + authorization: undefined }; expect(() => new AnalyticsEventReporter(InvalidConfig)).toThrowError( - 'Provide one and only one of API Key or Bearer Token.' + 'Authorization must be provided.' + ); + const InvalidConfig2: AnalyticsConfig = { + authorizationType: 'apiKey' + }; + expect(() => new AnalyticsEventReporter(InvalidConfig2)).toThrowError( + 'Authorization must be provided.' ); }); it('Valid config will not throw error', () => { const configwithAPI: AnalyticsConfig = { - key: 'mock-api-key' + authorizationType: 'apiKey', + authorization: 'mock-api-key' }; expect(() => new AnalyticsEventReporter(configwithAPI)).not.toThrow(); - const configwithBearer: AnalyticsConfig = { - bearer: 'mock-bearer-token' + const configwithbearer: AnalyticsConfig = { + authorizationType: 'bearer', + authorization: 'BearerToken' }; - expect(() => new AnalyticsEventReporter(configwithBearer)).not.toThrow(); + expect(() => new AnalyticsEventReporter(configwithbearer)).not.toThrow(); }); describe('Test report function', () => { @@ -74,7 +94,8 @@ describe('Test report function', () => { }); const config: AnalyticsConfig = { - key: 'validKey', + authorizationType: 'apiKey', + authorization: 'validKey', region: RegionEnum.EU, forceFetch: false }; @@ -116,7 +137,8 @@ describe('Test report function', () => { mockUseBeacon.mockReturnValueOnce(false); const config: AnalyticsConfig = { - bearer: 'bearerToken', + authorizationType: 'bearer', + authorization: 'bearerToken', env: EnvironmentEnum.Sandbox }; const reporter = new AnalyticsEventReporter(config).with({ @@ -163,7 +185,8 @@ describe('Test report function', () => { mockUseBeacon.mockReturnValueOnce(false); const config: AnalyticsConfig = { - bearer: 'bearerToken', + authorizationType: 'bearer', + authorization: 'bearerToken', sessionTrackingEnabled: true }; const reporter = new AnalyticsEventReporter(config).with({ @@ -217,7 +240,8 @@ describe('Test report function', () => { mockUseBeacon.mockReturnValueOnce(false); const config: AnalyticsConfig = { - bearer: 'bearerToken', + authorizationType: 'bearer', + authorization: 'bearerToken', sessionTrackingEnabled: true }; const reporter = new AnalyticsEventReporter(config).with({ @@ -268,7 +292,8 @@ describe('Test report function', () => { mockPostWithFetch.mockResolvedValue({ id: 1111 }); const config: AnalyticsConfig = { - bearer: 'bearerToken', + authorizationType: 'bearer', + authorization: 'bearerToken', sessionTrackingEnabled: false }; const reporter = new AnalyticsEventReporter(config).with({ @@ -327,7 +352,8 @@ describe('Test report function', () => { }); const config: AnalyticsConfig = { - key: 'validKey', + authorizationType: 'apiKey', + authorization: 'validKey', region: RegionEnum.EU, forceFetch: false }; @@ -372,7 +398,8 @@ describe('Test report function', () => { }); const config: AnalyticsConfig = { - key: 'validKey', + authorizationType: 'apiKey', + authorization: 'validKey', region: RegionEnum.EU, forceFetch: false }; @@ -419,7 +446,8 @@ describe('Test report function', () => { }); const config: AnalyticsConfig = { - key: 'validKey', + authorizationType: 'apiKey', + authorization: 'validKey', region: RegionEnum.EU, forceFetch: true }; @@ -456,7 +484,8 @@ describe('Test report function', () => { }); const config: AnalyticsConfig = { - key: 'validKey', + authorizationType: 'apiKey', + authorization: 'validKey', region: RegionEnum.EU, forceFetch: false }; @@ -494,7 +523,8 @@ describe('Test report function', () => { }); const config: AnalyticsConfig = { - key: 'validKey', + authorizationType: 'apiKey', + authorization: 'validKey', region: RegionEnum.EU, forceFetch: false }; @@ -569,7 +599,8 @@ describe('Test report function', () => { }); const config: AnalyticsConfig = { - key: 'validKey', + authorizationType: 'apiKey', + authorization: 'validKey', region: RegionEnum.EU, forceFetch: false, sessionTrackingEnabled: true diff --git a/tests/reportBrowserAnalytics.test.ts b/tests/reportBrowserAnalytics.test.ts index 6a0e24e9..378bc443 100644 --- a/tests/reportBrowserAnalytics.test.ts +++ b/tests/reportBrowserAnalytics.test.ts @@ -20,7 +20,10 @@ describe('reportBrowserAnalytics', () => { it('should return a promise that resolves when payload is present', async () => { const mockPayload = [ - ['config', { key: 'apiKey' }], + [ + 'config', + { authorizationType: 'apiKey', authorization: 'test-api-key' } + ], ['payload', { bot: false }] ]; (global as any).window['analyticsEventPayload'] = mockPayload;