diff --git a/src/services/splitApi.ts b/src/services/splitApi.ts index b7163b93..6860b022 100644 --- a/src/services/splitApi.ts +++ b/src/services/splitApi.ts @@ -29,7 +29,6 @@ export function splitApiFactory( const urls = settings.urls; const filterQueryString = settings.sync.__splitFiltersValidation && settings.sync.__splitFiltersValidation.queryString; const SplitSDKImpressionsMode = settings.sync.impressionsMode; - const flagSpecVersion = settings.sync.flagSpecVersion; const splitHttpClient = splitHttpClientFactory(settings, platform); return { @@ -45,7 +44,7 @@ export function splitApiFactory( }, fetchAuth(userMatchingKeys?: string[]) { - let url = `${urls.auth}/v2/auth?s=${flagSpecVersion}`; + let url = `${urls.auth}/v2/auth?s=${settings.sync.flagSpecVersion}`; if (userMatchingKeys) { // `userMatchingKeys` is undefined in server-side const queryParams = userMatchingKeys.map(userKeyToQueryParam).join('&'); if (queryParams) url += '&' + queryParams; @@ -54,7 +53,7 @@ export function splitApiFactory( }, fetchSplitChanges(since: number, noCache?: boolean, till?: number, rbSince?: number) { - const url = `${urls.sdk}/splitChanges?s=${flagSpecVersion}&since=${since}${rbSince ? '&rbSince=' + rbSince : ''}${filterQueryString || ''}${till ? '&till=' + till : ''}`; + const url = `${urls.sdk}/splitChanges?s=${settings.sync.flagSpecVersion}&since=${since}${rbSince ? '&rbSince=' + rbSince : ''}${filterQueryString || ''}${till ? '&till=' + till : ''}`; return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(SPLITS)) .catch((err) => { if (err.statusCode === 414) settings.log.error(ERROR_TOO_MANY_SETS); diff --git a/src/sync/polling/fetchers/splitChangesFetcher.ts b/src/sync/polling/fetchers/splitChangesFetcher.ts index d134601b..58f87e9a 100644 --- a/src/sync/polling/fetchers/splitChangesFetcher.ts +++ b/src/sync/polling/fetchers/splitChangesFetcher.ts @@ -1,11 +1,29 @@ +import { ISettings } from '../../../types'; +import { ISplitChangesResponse } from '../../../dtos/types'; import { IFetchSplitChanges, IResponse } from '../../../services/types'; +import { IStorageBase } from '../../../storages/types'; +import { FLAG_SPEC_VERSION } from '../../../utils/constants'; +import { base } from '../../../utils/settingsValidation'; import { ISplitChangesFetcher } from './types'; +import { LOG_PREFIX_SYNC_SPLITS } from '../../../logger/constants'; + +const PROXY_CHECK_INTERVAL_MILLIS_CS = 60 * 60 * 1000; // 1 hour in Client Side +const PROXY_CHECK_INTERVAL_MILLIS_SS = 24 * PROXY_CHECK_INTERVAL_MILLIS_CS; // 24 hours in Server Side + +function sdkEndpointOverriden(settings: ISettings) { + return settings.urls.sdk !== base.urls.sdk; +} /** * Factory of SplitChanges fetcher. * SplitChanges fetcher is a wrapper around `splitChanges` API service that parses the response and handle errors. */ -export function splitChangesFetcherFactory(fetchSplitChanges: IFetchSplitChanges): ISplitChangesFetcher { +// @TODO breaking: drop support for Split Proxy below v5.10.0 and simplify the implementation +export function splitChangesFetcherFactory(fetchSplitChanges: IFetchSplitChanges, settings: ISettings, storage: Pick): ISplitChangesFetcher { + + const log = settings.log; + const PROXY_CHECK_INTERVAL_MILLIS = settings.core.key !== undefined ? PROXY_CHECK_INTERVAL_MILLIS_CS : PROXY_CHECK_INTERVAL_MILLIS_SS; + let lastProxyCheckTimestamp: number | undefined; return function splitChangesFetcher( since: number, @@ -14,12 +32,51 @@ export function splitChangesFetcherFactory(fetchSplitChanges: IFetchSplitChanges rbSince?: number, // Optional decorator for `fetchSplitChanges` promise, such as timeout or time tracker decorator?: (promise: Promise) => Promise - ) { + ): Promise { + + // Recheck proxy + if (lastProxyCheckTimestamp && (Date.now() - lastProxyCheckTimestamp) > PROXY_CHECK_INTERVAL_MILLIS) { + settings.sync.flagSpecVersion = FLAG_SPEC_VERSION; + } + + let splitsPromise = fetchSplitChanges(since, noCache, till, settings.sync.flagSpecVersion === FLAG_SPEC_VERSION ? rbSince : undefined) + // Handle proxy error with spec 1.3 + .catch((err) => { + if (err.statusCode === 400 && sdkEndpointOverriden(settings) && settings.sync.flagSpecVersion === FLAG_SPEC_VERSION) { + log.error(LOG_PREFIX_SYNC_SPLITS + 'Proxy error detected. If you are using Split Proxy, please upgrade to latest version'); + lastProxyCheckTimestamp = Date.now(); + settings.sync.flagSpecVersion = '1.2'; // fallback to 1.2 spec + return fetchSplitChanges(since, noCache, till); // retry request without rbSince + } + throw err; + }); - let splitsPromise = fetchSplitChanges(since, noCache, till, rbSince); if (decorator) splitsPromise = decorator(splitsPromise); - return splitsPromise.then(resp => resp.json()); + return splitsPromise + .then(resp => resp.json()) + .then(data => { + // Using flag spec version 1.2 + if (data.splits) { + return { + ff: { + d: data.splits, + s: data.since, + t: data.till + } + }; + } + + // Proxy recovery + if (lastProxyCheckTimestamp) { + log.info(LOG_PREFIX_SYNC_SPLITS + 'Proxy error recovered'); + lastProxyCheckTimestamp = undefined; + return Promise.all([storage.splits.clear(), storage.rbSegments.clear()]) + .then(() => splitChangesFetcher(storage.splits.getChangeNumber() as number, undefined, undefined, storage.rbSegments.getChangeNumber() as number)); + } + + return data; + }); }; } diff --git a/src/sync/polling/syncTasks/splitsSyncTask.ts b/src/sync/polling/syncTasks/splitsSyncTask.ts index d6fed5a2..d385bf77 100644 --- a/src/sync/polling/syncTasks/splitsSyncTask.ts +++ b/src/sync/polling/syncTasks/splitsSyncTask.ts @@ -21,7 +21,7 @@ export function splitsSyncTaskFactory( settings.log, splitChangesUpdaterFactory( settings.log, - splitChangesFetcherFactory(fetchSplitChanges), + splitChangesFetcherFactory(fetchSplitChanges, settings, storage), storage, settings.sync.__splitFiltersValidation, readiness.splits, diff --git a/src/sync/polling/updaters/__tests__/splitChangesUpdater.spec.ts b/src/sync/polling/updaters/__tests__/splitChangesUpdater.spec.ts index b1bc79d8..750f1c0d 100644 --- a/src/sync/polling/updaters/__tests__/splitChangesUpdater.spec.ts +++ b/src/sync/polling/updaters/__tests__/splitChangesUpdater.spec.ts @@ -170,7 +170,7 @@ describe('splitChangesUpdater', () => { fetchMock.once('*', { status: 200, body: splitChangesMock1 }); // @ts-ignore const splitApi = splitApiFactory(settingsSplitApi, { getFetch: () => fetchMock }, telemetryTrackerFactory()); const fetchSplitChanges = jest.spyOn(splitApi, 'fetchSplitChanges'); - const splitChangesFetcher = splitChangesFetcherFactory(splitApi.fetchSplitChanges); + const splitChangesFetcher = splitChangesFetcherFactory(splitApi.fetchSplitChanges, fullSettings, storage); const readinessManager = readinessManagerFactory(EventEmitter, fullSettings); const splitsEmitSpy = jest.spyOn(readinessManager.splits, 'emit');