Skip to content

Commit 2a360eb

Browse files
Merge branch 'rb_segments_proxy_fallback' into prerequisites_refactors
2 parents d0a0986 + 1118e49 commit 2a360eb

File tree

4 files changed

+69
-14
lines changed

4 files changed

+69
-14
lines changed

src/services/splitApi.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ export function splitApiFactory(
2929
const urls = settings.urls;
3030
const filterQueryString = settings.sync.__splitFiltersValidation && settings.sync.__splitFiltersValidation.queryString;
3131
const SplitSDKImpressionsMode = settings.sync.impressionsMode;
32-
const flagSpecVersion = settings.sync.flagSpecVersion;
3332
const splitHttpClient = splitHttpClientFactory(settings, platform);
3433

3534
return {
@@ -45,7 +44,7 @@ export function splitApiFactory(
4544
},
4645

4746
fetchAuth(userMatchingKeys?: string[]) {
48-
let url = `${urls.auth}/v2/auth?s=${flagSpecVersion}`;
47+
let url = `${urls.auth}/v2/auth?s=${settings.sync.flagSpecVersion}`;
4948
if (userMatchingKeys) { // `userMatchingKeys` is undefined in server-side
5049
const queryParams = userMatchingKeys.map(userKeyToQueryParam).join('&');
5150
if (queryParams) url += '&' + queryParams;
@@ -54,7 +53,7 @@ export function splitApiFactory(
5453
},
5554

5655
fetchSplitChanges(since: number, noCache?: boolean, till?: number, rbSince?: number) {
57-
const url = `${urls.sdk}/splitChanges?s=${flagSpecVersion}&since=${since}${rbSince ? '&rbSince=' + rbSince : ''}${filterQueryString || ''}${till ? '&till=' + till : ''}`;
56+
const url = `${urls.sdk}/splitChanges?s=${settings.sync.flagSpecVersion}&since=${since}${rbSince ? '&rbSince=' + rbSince : ''}${filterQueryString || ''}${till ? '&till=' + till : ''}`;
5857
return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(SPLITS))
5958
.catch((err) => {
6059
if (err.statusCode === 414) settings.log.error(ERROR_TOO_MANY_SETS);
Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
1+
import { ISettings } from '../../../types';
2+
import { ISplitChangesResponse } from '../../../dtos/types';
13
import { IFetchSplitChanges, IResponse } from '../../../services/types';
4+
import { IStorageBase } from '../../../storages/types';
5+
import { FLAG_SPEC_VERSION } from '../../../utils/constants';
6+
import { base } from '../../../utils/settingsValidation';
27
import { ISplitChangesFetcher } from './types';
8+
import { LOG_PREFIX_SYNC_SPLITS } from '../../../logger/constants';
9+
10+
const PROXY_CHECK_INTERVAL_MILLIS_CS = 60 * 60 * 1000; // 1 hour in Client Side
11+
const PROXY_CHECK_INTERVAL_MILLIS_SS = 24 * PROXY_CHECK_INTERVAL_MILLIS_CS; // 24 hours in Server Side
12+
13+
function sdkEndpointOverriden(settings: ISettings) {
14+
return settings.urls.sdk !== base.urls.sdk;
15+
}
316

417
/**
518
* Factory of SplitChanges fetcher.
619
* SplitChanges fetcher is a wrapper around `splitChanges` API service that parses the response and handle errors.
720
*/
8-
export function splitChangesFetcherFactory(fetchSplitChanges: IFetchSplitChanges): ISplitChangesFetcher {
21+
// @TODO breaking: drop support for Split Proxy below v5.10.0 and simplify the implementation
22+
export function splitChangesFetcherFactory(fetchSplitChanges: IFetchSplitChanges, settings: ISettings, storage: Pick<IStorageBase, 'splits' | 'rbSegments'>): ISplitChangesFetcher {
23+
24+
const log = settings.log;
25+
const PROXY_CHECK_INTERVAL_MILLIS = settings.core.key !== undefined ? PROXY_CHECK_INTERVAL_MILLIS_CS : PROXY_CHECK_INTERVAL_MILLIS_SS;
26+
let lastProxyCheckTimestamp: number | undefined;
927

1028
return function splitChangesFetcher(
1129
since: number,
@@ -14,12 +32,51 @@ export function splitChangesFetcherFactory(fetchSplitChanges: IFetchSplitChanges
1432
rbSince?: number,
1533
// Optional decorator for `fetchSplitChanges` promise, such as timeout or time tracker
1634
decorator?: (promise: Promise<IResponse>) => Promise<IResponse>
17-
) {
35+
): Promise<ISplitChangesResponse> {
36+
37+
// Recheck proxy
38+
if (lastProxyCheckTimestamp && (Date.now() - lastProxyCheckTimestamp) > PROXY_CHECK_INTERVAL_MILLIS) {
39+
settings.sync.flagSpecVersion = FLAG_SPEC_VERSION;
40+
}
41+
42+
let splitsPromise = fetchSplitChanges(since, noCache, till, settings.sync.flagSpecVersion === FLAG_SPEC_VERSION ? rbSince : undefined)
43+
// Handle proxy error with spec 1.3
44+
.catch((err) => {
45+
if (err.statusCode === 400 && sdkEndpointOverriden(settings) && settings.sync.flagSpecVersion === FLAG_SPEC_VERSION) {
46+
log.error(LOG_PREFIX_SYNC_SPLITS + 'Proxy error detected. If you are using Split Proxy, please upgrade to latest version');
47+
lastProxyCheckTimestamp = Date.now();
48+
settings.sync.flagSpecVersion = '1.2'; // fallback to 1.2 spec
49+
return fetchSplitChanges(since, noCache, till); // retry request without rbSince
50+
}
51+
throw err;
52+
});
1853

19-
let splitsPromise = fetchSplitChanges(since, noCache, till, rbSince);
2054
if (decorator) splitsPromise = decorator(splitsPromise);
2155

22-
return splitsPromise.then(resp => resp.json());
56+
return splitsPromise
57+
.then(resp => resp.json())
58+
.then(data => {
59+
// Using flag spec version 1.2
60+
if (data.splits) {
61+
return {
62+
ff: {
63+
d: data.splits,
64+
s: data.since,
65+
t: data.till
66+
}
67+
};
68+
}
69+
70+
// Proxy recovery
71+
if (lastProxyCheckTimestamp) {
72+
log.info(LOG_PREFIX_SYNC_SPLITS + 'Proxy error recovered');
73+
lastProxyCheckTimestamp = undefined;
74+
return Promise.all([storage.splits.clear(), storage.rbSegments.clear()])
75+
.then(() => splitChangesFetcher(storage.splits.getChangeNumber() as number, undefined, undefined, storage.rbSegments.getChangeNumber() as number));
76+
}
77+
78+
return data;
79+
});
2380
};
2481

2582
}

src/sync/polling/syncTasks/splitsSyncTask.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export function splitsSyncTaskFactory(
2121
settings.log,
2222
splitChangesUpdaterFactory(
2323
settings.log,
24-
splitChangesFetcherFactory(fetchSplitChanges),
24+
splitChangesFetcherFactory(fetchSplitChanges, settings, storage),
2525
storage,
2626
settings.sync.__splitFiltersValidation,
2727
readiness.splits,

src/sync/polling/updaters/__tests__/splitChangesUpdater.spec.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,6 @@ test('splitChangesUpdater / compute splits mutation with filters', () => {
156156
});
157157

158158
describe('splitChangesUpdater', () => {
159-
160-
fetchMock.once('*', { status: 200, body: splitChangesMock1 }); // @ts-ignore
161-
const splitApi = splitApiFactory(settingsSplitApi, { getFetch: () => fetchMock }, telemetryTrackerFactory());
162-
const fetchSplitChanges = jest.spyOn(splitApi, 'fetchSplitChanges');
163-
const splitChangesFetcher = splitChangesFetcherFactory(splitApi.fetchSplitChanges);
164-
165159
const splits = new SplitsCacheInMemory();
166160
const updateSplits = jest.spyOn(splits, 'update');
167161

@@ -173,6 +167,11 @@ describe('splitChangesUpdater', () => {
173167

174168
const storage = { splits, rbSegments, segments };
175169

170+
fetchMock.once('*', { status: 200, body: splitChangesMock1 }); // @ts-ignore
171+
const splitApi = splitApiFactory(settingsSplitApi, { getFetch: () => fetchMock }, telemetryTrackerFactory());
172+
const fetchSplitChanges = jest.spyOn(splitApi, 'fetchSplitChanges');
173+
const splitChangesFetcher = splitChangesFetcherFactory(splitApi.fetchSplitChanges, fullSettings, storage);
174+
176175
const readinessManager = readinessManagerFactory(EventEmitter, fullSettings);
177176
const splitsEmitSpy = jest.spyOn(readinessManager.splits, 'emit');
178177

0 commit comments

Comments
 (0)