Skip to content

Commit 4b35644

Browse files
authored
[FSSDK-12026] add customHeaders option to polling config manager (#1107)
1 parent dca1913 commit 4b35644

11 files changed

+104
-17
lines changed

lib/project_config/config_manager_factory.browser.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2024, Optimizely
2+
* Copyright 2024-2025, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -78,6 +78,7 @@ describe('createPollingConfigManager', () => {
7878
autoUpdate: true,
7979
urlTemplate: 'urlTemplate',
8080
datafileAccessToken: 'datafileAccessToken',
81+
customHeaders: { 'X-Test-Header': 'test-value' },
8182
cache: getMockSyncCache<string>(),
8283
};
8384

lib/project_config/config_manager_factory.browser.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2024, Optimizely
2+
* Copyright 2024-2025, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,9 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { getOpaquePollingConfigManager, OpaqueConfigManager, PollingConfigManagerConfig } from './config_manager_factory';
1817
import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser';
19-
import { ProjectConfigManager } from './project_config_manager';
18+
import { getOpaquePollingConfigManager, OpaqueConfigManager, PollingConfigManagerConfig } from './config_manager_factory';
2019

2120
export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): OpaqueConfigManager => {
2221
const defaultConfig = {

lib/project_config/config_manager_factory.node.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2024, Optimizely
2+
* Copyright 2024-2025, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -78,6 +78,7 @@ describe('createPollingConfigManager', () => {
7878
autoUpdate: false,
7979
urlTemplate: 'urlTemplate',
8080
datafileAccessToken: 'datafileAccessToken',
81+
customHeaders: { 'X-Test-Header': 'test-value' },
8182
cache: getMockSyncCache(),
8283
};
8384

lib/project_config/config_manager_factory.node.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2024, Optimizely
2+
* Copyright 2024-2025, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,10 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { getOpaquePollingConfigManager, OpaqueConfigManager, PollingConfigManagerConfig } from "./config_manager_factory";
1817
import { NodeRequestHandler } from "../utils/http_request_handler/request_handler.node";
19-
import { ProjectConfigManager } from "./project_config_manager";
20-
import { DEFAULT_URL_TEMPLATE, DEFAULT_AUTHENTICATED_URL_TEMPLATE } from './constant';
18+
import { getOpaquePollingConfigManager, OpaqueConfigManager, PollingConfigManagerConfig } from "./config_manager_factory";
2119

2220
export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): OpaqueConfigManager => {
2321
const defaultConfig = {

lib/project_config/config_manager_factory.react_native.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2024, Optimizely
2+
* Copyright 2024-2025, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -129,6 +129,7 @@ describe('createPollingConfigManager', () => {
129129
autoUpdate: false,
130130
urlTemplate: 'urlTemplate',
131131
datafileAccessToken: 'datafileAccessToken',
132+
customHeaders: { 'X-Test-Header': 'test-value' },
132133
cache: getMockSyncCache(),
133134
};
134135

lib/project_config/config_manager_factory.react_native.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2024, Optimizely
2+
* Copyright 2024-2025, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,12 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { getOpaquePollingConfigManager, PollingConfigManagerConfig } from "./config_manager_factory";
18-
import { BrowserRequestHandler } from "../utils/http_request_handler/request_handler.browser";
19-
import { ProjectConfigManager } from "./project_config_manager";
2017
import { AsyncStorageCache } from "../utils/cache/async_storage_cache.react_native";
21-
22-
import { OpaqueConfigManager } from "./config_manager_factory";
18+
import { BrowserRequestHandler } from "../utils/http_request_handler/request_handler.browser";
19+
import { getOpaquePollingConfigManager, PollingConfigManagerConfig, OpaqueConfigManager } from "./config_manager_factory";
2320

2421
export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): OpaqueConfigManager => {
2522
const defaultConfig = {

lib/project_config/config_manager_factory.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ describe('getPollingConfigManager', () => {
159159
autoUpdate: true,
160160
urlTemplate: 'urlTemplate',
161161
datafileAccessToken: 'datafileAccessToken',
162+
customHeaders: { 'X-Custom-Header': 'custom-value' },
162163
cache: getMockSyncCache<string>(),
163164
};
164165

@@ -171,6 +172,7 @@ describe('getPollingConfigManager', () => {
171172
autoUpdate: config.autoUpdate,
172173
urlTemplate: config.urlTemplate,
173174
datafileAccessToken: config.datafileAccessToken,
175+
customHeaders: config.customHeaders,
174176
requestHandler: config.requestHandler,
175177
repeater: MockIntervalRepeater.mock.instances[0],
176178
cache: config.cache,

lib/project_config/config_manager_factory.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export type PollingConfigManagerConfig = {
5656
updateInterval?: number;
5757
urlTemplate?: string;
5858
datafileAccessToken?: string;
59+
customHeaders?: Record<string, string>;
5960
cache?: Store<string>;
6061
};
6162

@@ -88,6 +89,7 @@ export const getPollingConfigManager = (
8889
autoUpdate: opt.autoUpdate,
8990
urlTemplate: opt.urlTemplate,
9091
datafileAccessToken: opt.datafileAccessToken,
92+
customHeaders: opt.customHeaders,
9193
requestHandler: opt.requestHandler,
9294
cache: opt.cache,
9395
repeater,

lib/project_config/datafile_manager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export type DatafileManagerConfig = {
3333
urlTemplate?: string;
3434
cache?: Store<string>;
3535
datafileAccessToken?: string;
36+
customHeaders?: Record<string, string>;
3637
initRetry?: number;
3738
repeater: Repeater;
3839
logger?: LoggerFacade;

lib/project_config/polling_datafile_manager.spec.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,85 @@ describe('PollingDatafileManager', () => {
789789
expect(requestHandler.makeRequest.mock.calls[0][1].Authorization).toBe('Bearer token123');
790790
});
791791

792+
it('sends customHeaders in the request headers', async () => {
793+
const repeater = getMockRepeater();
794+
const requestHandler = getMockRequestHandler();
795+
const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} }));
796+
requestHandler.makeRequest.mockReturnValueOnce(mockResponse);
797+
798+
const customHeaders = {
799+
'X-Custom-Header': 'custom-value',
800+
'X-Another-Header': 'another-value',
801+
};
802+
803+
const manager = new PollingDatafileManager({
804+
repeater,
805+
requestHandler,
806+
sdkKey: 'keyThatExists',
807+
customHeaders,
808+
});
809+
810+
manager.start();
811+
repeater.execute(0);
812+
813+
await expect(manager.onRunning()).resolves.not.toThrow();
814+
expect(requestHandler.makeRequest).toHaveBeenCalledOnce();
815+
const sentHeaders = requestHandler.makeRequest.mock.calls[0][1];
816+
expect(sentHeaders['X-Custom-Header']).toBe('custom-value');
817+
expect(sentHeaders['X-Another-Header']).toBe('another-value');
818+
});
819+
820+
it('merges customHeaders with other headers (access token and if-modified-since)', async () => {
821+
const repeater = getMockRepeater();
822+
const requestHandler = getMockRequestHandler();
823+
824+
// First request to set up last-modified header
825+
const mockResponse1 = getMockAbortableRequest(Promise.resolve({
826+
statusCode: 200,
827+
body: '{"foo": "bar"}',
828+
headers: { 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT' }
829+
}));
830+
831+
// Second request to test all headers together
832+
const mockResponse2 = getMockAbortableRequest(Promise.resolve({
833+
statusCode: 304,
834+
body: '',
835+
headers: {}
836+
}));
837+
838+
requestHandler.makeRequest.mockReturnValueOnce(mockResponse1)
839+
.mockReturnValueOnce(mockResponse2);
840+
841+
const customHeaders = {
842+
'X-Custom-Header': 'custom-value',
843+
};
844+
845+
const manager = new PollingDatafileManager({
846+
repeater,
847+
requestHandler,
848+
sdkKey: 'keyThatExists',
849+
datafileAccessToken: 'token123',
850+
customHeaders,
851+
autoUpdate: true,
852+
});
853+
854+
manager.start();
855+
856+
// First request
857+
await repeater.execute(0);
858+
859+
// Second request should have all headers
860+
await repeater.execute(0);
861+
862+
expect(requestHandler.makeRequest).toHaveBeenCalledTimes(2);
863+
864+
// Check second request headers include custom, auth, and if-modified-since
865+
const secondRequestHeaders = requestHandler.makeRequest.mock.calls[1][1];
866+
expect(secondRequestHeaders['X-Custom-Header']).toBe('custom-value');
867+
expect(secondRequestHeaders['Authorization']).toBe('Bearer token123');
868+
expect(secondRequestHeaders['if-modified-since']).toBe('Fri, 08 Mar 2019 18:57:17 GMT');
869+
});
870+
792871
it('uses the provided urlTemplate', async () => {
793872
const repeater = getMockRepeater();
794873
const requestHandler = getMockRequestHandler();

0 commit comments

Comments
 (0)