Skip to content

Commit

Permalink
EventsSDK: Revise Authorization in AnalyticsConfig (#124)
Browse files Browse the repository at this point in the history
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 <[email protected]>
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Jan 26, 2024
1 parent 98a8795 commit e5bd3d6
Show file tree
Hide file tree
Showing 12 changed files with 110 additions and 74 deletions.
13 changes: 13 additions & 0 deletions docs/analytics.analyticsconfig.authorization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/analytics](./analytics.md) &gt; [AnalyticsConfig](./analytics.analyticsconfig.md) &gt; [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;
```
13 changes: 13 additions & 0 deletions docs/analytics.analyticsconfig.authorizationtype.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/analytics](./analytics.md) &gt; [AnalyticsConfig](./analytics.analyticsconfig.md) &gt; [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';
```
13 changes: 0 additions & 13 deletions docs/analytics.analyticsconfig.bearer.md

This file was deleted.

13 changes: 0 additions & 13 deletions docs/analytics.analyticsconfig.key.md

This file was deleted.

4 changes: 2 additions & 2 deletions docs/analytics.analyticsconfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |

4 changes: 2 additions & 2 deletions etc/analytics.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
12 changes: 4 additions & 8 deletions src/AnalyticsConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'. */
Expand Down
21 changes: 12 additions & 9 deletions src/AnalyticsEventReporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion src/convertStringToValue.ts
Original file line number Diff line number Diff line change
@@ -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 = [
Expand All @@ -18,7 +19,7 @@ const propertiesToCheck = [

export function convertStringToValue(
data: Record<string, unknown>
): EventPayload {
): EventPayload | AnalyticsConfig {
// Recursive function to traverse and convert nested objects
function recursiveConversion(obj: Record<string, unknown>) {
for (const property in obj) {
Expand Down
6 changes: 4 additions & 2 deletions test-site/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
77 changes: 54 additions & 23 deletions tests/AnalyticsEventReporter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -74,7 +94,8 @@ describe('Test report function', () => {
});

const config: AnalyticsConfig = {
key: 'validKey',
authorizationType: 'apiKey',
authorization: 'validKey',
region: RegionEnum.EU,
forceFetch: false
};
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -327,7 +352,8 @@ describe('Test report function', () => {
});

const config: AnalyticsConfig = {
key: 'validKey',
authorizationType: 'apiKey',
authorization: 'validKey',
region: RegionEnum.EU,
forceFetch: false
};
Expand Down Expand Up @@ -372,7 +398,8 @@ describe('Test report function', () => {
});

const config: AnalyticsConfig = {
key: 'validKey',
authorizationType: 'apiKey',
authorization: 'validKey',
region: RegionEnum.EU,
forceFetch: false
};
Expand Down Expand Up @@ -419,7 +446,8 @@ describe('Test report function', () => {
});

const config: AnalyticsConfig = {
key: 'validKey',
authorizationType: 'apiKey',
authorization: 'validKey',
region: RegionEnum.EU,
forceFetch: true
};
Expand Down Expand Up @@ -456,7 +484,8 @@ describe('Test report function', () => {
});

const config: AnalyticsConfig = {
key: 'validKey',
authorizationType: 'apiKey',
authorization: 'validKey',
region: RegionEnum.EU,
forceFetch: false
};
Expand Down Expand Up @@ -494,7 +523,8 @@ describe('Test report function', () => {
});

const config: AnalyticsConfig = {
key: 'validKey',
authorizationType: 'apiKey',
authorization: 'validKey',
region: RegionEnum.EU,
forceFetch: false
};
Expand Down Expand Up @@ -569,7 +599,8 @@ describe('Test report function', () => {
});

const config: AnalyticsConfig = {
key: 'validKey',
authorizationType: 'apiKey',
authorization: 'validKey',
region: RegionEnum.EU,
forceFetch: false,
sessionTrackingEnabled: true
Expand Down
5 changes: 4 additions & 1 deletion tests/reportBrowserAnalytics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit e5bd3d6

Please sign in to comment.