diff --git a/lib/modules/datafile-manager/datafileManager.ts b/lib/modules/datafile-manager/datafileManager.ts index abf11d8e9..5c9fdc6cd 100644 --- a/lib/modules/datafile-manager/datafileManager.ts +++ b/lib/modules/datafile-manager/datafileManager.ts @@ -44,6 +44,7 @@ export interface DatafileManagerConfig { updateInterval?: number; urlTemplate?: string; cache?: PersistentKeyValueCache; + customHeaders?: Record; } export interface NodeDatafileManagerConfig extends DatafileManagerConfig { diff --git a/lib/modules/datafile-manager/httpPollingDatafileManager.ts b/lib/modules/datafile-manager/httpPollingDatafileManager.ts index c3311997e..c9b4ad2f4 100644 --- a/lib/modules/datafile-manager/httpPollingDatafileManager.ts +++ b/lib/modules/datafile-manager/httpPollingDatafileManager.ts @@ -15,7 +15,7 @@ */ import { getLogger } from '../logging'; -import { sprintf } from '../../utils/fns'; +import { sprintf, assign } from '../../utils/fns'; import { DatafileManager, DatafileManagerConfig, DatafileUpdate } from './datafileManager'; import EventEmitter, { Disposer } from './eventEmitter'; import { AbortableRequest, Response, Headers } from './http'; @@ -96,6 +96,8 @@ export default abstract class HttpPollingDatafileManager implements DatafileMana private sdkKey: string; + private customHeaders?: Record; + // When true, this means the update interval timeout fired before the current // sync completed. In that case, we should sync again immediately upon // completion of the current request, instead of waiting another update @@ -114,11 +116,13 @@ export default abstract class HttpPollingDatafileManager implements DatafileMana updateInterval = DEFAULT_UPDATE_INTERVAL, urlTemplate = DEFAULT_URL_TEMPLATE, cache = noOpKeyValueCache, + customHeaders, } = configWithDefaultsApplied; this.cache = cache; this.cacheKey = 'opt-datafile-' + sdkKey; this.sdkKey = sdkKey; + this.customHeaders = customHeaders; this.isReadyPromiseSettled = false; this.readyPromiseResolver = (): void => { }; this.readyPromiseRejecter = (): void => { }; @@ -266,6 +270,11 @@ export default abstract class HttpPollingDatafileManager implements DatafileMana private syncDatafile(): void { const headers: Headers = {}; + + if (this.customHeaders) { + assign(headers, this.customHeaders); + } + if (this.lastResponseLastModified) { headers['if-modified-since'] = this.lastResponseLastModified; } diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 9e52c8a69..0dc03e41f 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -95,6 +95,7 @@ export interface DatafileOptions { updateInterval?: number; urlTemplate?: string; datafileAccessToken?: string; + customHeaders?: Record; } export interface OdpOptions { diff --git a/tests/nodeDatafileManager.spec.ts b/tests/nodeDatafileManager.spec.ts index 14fb49d05..91c1363c3 100644 --- a/tests/nodeDatafileManager.spec.ts +++ b/tests/nodeDatafileManager.spec.ts @@ -183,4 +183,49 @@ describe('nodeDatafileManager', () => { expect(makeGetRequestSpy).toBeCalledWith('https://myawesomeurl/', expect.anything()); await manager.stop(); }); + + it('passes custom headers to makeGetRequest and merges with system headers', async () => { + makeGetRequestSpy.mockReturnValue({ + abort: jest.fn(), + responsePromise: Promise.resolve({ + statusCode: 200, + body: '{"foo":"bar"}', + headers: { + 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT', + }, + }), + }); + const manager = new NodeDatafileManager({ + sdkKey: '1234', + autoUpdate: true, + customHeaders: { + 'X-Custom-Header': 'custom-value', + 'X-Environment': 'production', + }, + }); + manager.start(); + await manager.onReady(); + + // First request should have custom headers + expect(makeGetRequestSpy).toBeCalledTimes(1); + expect(makeGetRequestSpy.mock.calls[0][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); + expect(makeGetRequestSpy.mock.calls[0][1]).toEqual({ + 'X-Custom-Header': 'custom-value', + 'X-Environment': 'production', + }); + + // Advance time to trigger second request + await advanceTimersByTime(300000); + + // Second request should have both custom headers AND if-modified-since + expect(makeGetRequestSpy).toBeCalledTimes(2); + expect(makeGetRequestSpy.mock.calls[1][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); + expect(makeGetRequestSpy.mock.calls[1][1]).toEqual({ + 'X-Custom-Header': 'custom-value', + 'X-Environment': 'production', + 'if-modified-since': 'Fri, 08 Mar 2019 18:57:17 GMT', + }); + + await manager.stop(); + }); });