diff --git a/packages/datafile-manager/CHANGELOG.md b/packages/datafile-manager/CHANGELOG.md index 3d09ae6c0..317592071 100644 --- a/packages/datafile-manager/CHANGELOG.md +++ b/packages/datafile-manager/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] Changes that have landed but are not yet released. +### Changed +- Added support for authenticated datafiles. `NodeDatafileManager` now accepts `datafileAccessToken` to be able to fetch authenticated datafiles. + ## [0.5.0] - April 17, 2020 ### Breaking Changes diff --git a/packages/datafile-manager/__test__/nodeDatafileManager.spec.ts b/packages/datafile-manager/__test__/nodeDatafileManager.spec.ts index 72cad3893..f406e45a6 100644 --- a/packages/datafile-manager/__test__/nodeDatafileManager.spec.ts +++ b/packages/datafile-manager/__test__/nodeDatafileManager.spec.ts @@ -104,4 +104,80 @@ describe('nodeDatafileManager', () => { await manager.stop(); }); + + it('uses authenticated default datafile url when auth token is provided', async () => { + makeGetRequestSpy.mockReturnValue({ + abort: jest.fn(), + responsePromise: Promise.resolve({ + statusCode: 200, + body: '{"foo":"bar"}', + headers: {}, + }), + }); + const manager = new NodeDatafileManager({ + sdkKey: '1234', + datafileAccessToken: 'abcdefgh', + }); + manager.start(); + expect(makeGetRequestSpy).toBeCalledTimes(1); + expect(makeGetRequestSpy).toBeCalledWith('https://www.optimizely-cdn.com/datafiles/auth/1234.json', expect.anything()); + await manager.stop(); + }); + + it('uses public default datafile url when auth token is not provided', async () => { + makeGetRequestSpy.mockReturnValue({ + abort: jest.fn(), + responsePromise: Promise.resolve({ + statusCode: 200, + body: '{"foo":"bar"}', + headers: {}, + }), + }); + const manager = new NodeDatafileManager({ + sdkKey: '1234', + }); + manager.start(); + expect(makeGetRequestSpy).toBeCalledTimes(1); + expect(makeGetRequestSpy).toBeCalledWith('https://cdn.optimizely.com/datafiles/1234.json', expect.anything()); + await manager.stop(); + }); + + it('adds authorization header with bearer token when auth token is provided', async () => { + makeGetRequestSpy.mockReturnValue({ + abort: jest.fn(), + responsePromise: Promise.resolve({ + statusCode: 200, + body: '{"foo":"bar"}', + headers: {}, + }), + }); + const manager = new NodeDatafileManager({ + sdkKey: '1234', + datafileAccessToken: 'abcdefgh', + }); + manager.start(); + expect(makeGetRequestSpy).toBeCalledTimes(1); + expect(makeGetRequestSpy).toBeCalledWith(expect.anything(), { 'Authorization': 'Bearer abcdefgh'}); + await manager.stop(); + }); + + it('prefers user provided url template over defaults', async () => { + makeGetRequestSpy.mockReturnValue({ + abort: jest.fn(), + responsePromise: Promise.resolve({ + statusCode: 200, + body: '{"foo":"bar"}', + headers: {}, + }), + }); + const manager = new NodeDatafileManager({ + sdkKey: '1234', + datafileAccessToken: 'abcdefgh', + urlTemplate: 'https://myawesomeurl/' + }); + manager.start(); + expect(makeGetRequestSpy).toBeCalledTimes(1); + expect(makeGetRequestSpy).toBeCalledWith('https://myawesomeurl/', expect.anything()); + await manager.stop(); + }); }); diff --git a/packages/datafile-manager/src/config.ts b/packages/datafile-manager/src/config.ts index 4d7717a1c..aeea890ae 100644 --- a/packages/datafile-manager/src/config.ts +++ b/packages/datafile-manager/src/config.ts @@ -20,6 +20,8 @@ export const MIN_UPDATE_INTERVAL = 1000; export const DEFAULT_URL_TEMPLATE = `https://cdn.optimizely.com/datafiles/%s.json`; +export const DEFAULT_AUTHENTICATED_URL_TEMPLATE = `https://www.optimizely-cdn.com/datafiles/auth/%s.json`; + export const BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT = [0, 8, 16, 32, 64, 128, 256, 512]; export const REQUEST_TIMEOUT_MS = 60 * 1000; // 1 minute diff --git a/packages/datafile-manager/src/datafileManager.ts b/packages/datafile-manager/src/datafileManager.ts index 8e37aa912..9e415bfc6 100644 --- a/packages/datafile-manager/src/datafileManager.ts +++ b/packages/datafile-manager/src/datafileManager.ts @@ -44,3 +44,7 @@ export interface DatafileManagerConfig { urlTemplate?: string; cache?: PersistentKeyValueCache; } + +export interface NodeDatafileManagerConfig extends DatafileManagerConfig { + datafileAccessToken?: string; +} diff --git a/packages/datafile-manager/src/nodeDatafileManager.ts b/packages/datafile-manager/src/nodeDatafileManager.ts index 2120a0716..80944cc00 100644 --- a/packages/datafile-manager/src/nodeDatafileManager.ts +++ b/packages/datafile-manager/src/nodeDatafileManager.ts @@ -14,14 +14,41 @@ * limitations under the License. */ +import { getLogger } from '@optimizely/js-sdk-logging'; import { makeGetRequest } from './nodeRequest'; import HttpPollingDatafileManager from './httpPollingDatafileManager'; import { Headers, AbortableRequest } from './http'; -import { DatafileManagerConfig } from './datafileManager'; +import { + NodeDatafileManagerConfig, + DatafileManagerConfig, +} from './datafileManager'; +import { + DEFAULT_URL_TEMPLATE, + DEFAULT_AUTHENTICATED_URL_TEMPLATE, +} from './config'; + +const logger = getLogger('NodeDatafileManager'); export default class NodeDatafileManager extends HttpPollingDatafileManager { + + private accessToken?: string; + + constructor(config: NodeDatafileManagerConfig) { + const defaultUrlTemplate = config.datafileAccessToken ? DEFAULT_AUTHENTICATED_URL_TEMPLATE : DEFAULT_URL_TEMPLATE; + super({ + ... config, + urlTemplate: config.urlTemplate || defaultUrlTemplate, + }); + this.accessToken = config.datafileAccessToken; + } + protected makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - return makeGetRequest(reqUrl, headers); + let requestHeaders = Object.assign({}, headers); + if (this.accessToken) { + logger.debug('Adding Authorization header with Bearer Token'); + requestHeaders['Authorization'] = `Bearer ${this.accessToken}`; + } + return makeGetRequest(reqUrl, requestHeaders); } protected getConfigDefaults(): Partial {