diff --git a/package.json b/package.json index 92f6ea8..feb7df2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@eppo/js-client-sdk-common", - "version": "4.15.1", + "version": "4.16.0-experimental.1", "description": "Common library for Eppo JavaScript SDKs (web, react native, and node)", "main": "dist/index.js", "files": [ diff --git a/src/client/eppo-client.ts b/src/client/eppo-client.ts index e7d9c6d..4412dc3 100644 --- a/src/client/eppo-client.ts +++ b/src/client/eppo-client.ts @@ -55,7 +55,7 @@ import { } from '../interfaces'; import { getMD5Hash } from '../obfuscation'; import { OverridePayload, OverrideValidator } from '../override-validator'; -import initPoller, { IPoller } from '../poller'; +import initPoller, { IPoller, PollInterceptor } from '../poller'; import SdkTokenDecoder from '../sdk-token-decoder'; import { Attributes, @@ -89,6 +89,7 @@ export type FlagConfigurationRequestParameters = { pollAfterFailedInitialization?: boolean; throwOnFailedInitialization?: boolean; skipInitialPoll?: boolean; + pollWrapper?: PollInterceptor; }; export interface IContainerExperiment { @@ -355,6 +356,7 @@ export default class EppoClient { pollAfterFailedStart: pollAfterFailedInitialization, errorOnFailedStart: throwOnFailedInitialization, skipInitialPoll: skipInitialPoll, + pollIntercept: this.configurationRequestParameters?.pollWrapper, }); await this.requestPoller.start(); diff --git a/src/index.ts b/src/index.ts index a9ebaee..12275e6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -67,6 +67,7 @@ import { Environment, } from './interfaces'; import { buildStorageKeySuffix } from './obfuscation'; +import { PollInterceptor } from './poller'; import { AttributeType, Attributes, @@ -96,6 +97,7 @@ export { FlagConfigRequestor, HttpClient, validation, + PollInterceptor as PollWrapper, // Precomputed Client EppoPrecomputedClient, diff --git a/src/poller.ts b/src/poller.ts index 386b0e7..2c4227f 100644 --- a/src/poller.ts +++ b/src/poller.ts @@ -10,6 +10,9 @@ export interface IPoller { start: () => Promise; stop: () => void; } +export type PollInterceptor = (pollAndProcess: () => Promise) => Promise; + +const defaultPollInterceptor: PollInterceptor = async (pollAndProcess) => pollAndProcess(); // TODO: change this to a class with methods instead of something that returns a function @@ -25,6 +28,7 @@ export default function initPoller( errorOnFailedStart?: boolean; pollAfterFailedStart?: boolean; skipInitialPoll?: boolean; + pollIntercept?: PollInterceptor; }, ): IPoller { let stopped = false; @@ -32,6 +36,7 @@ export default function initPoller( let nextPollMs = intervalMs; let previousPollFailed = false; let nextTimer: NodeJS.Timeout | undefined = undefined; + const pollIntercept = options?.pollIntercept ?? defaultPollInterceptor; const start = async () => { stopped = false; @@ -106,38 +111,45 @@ export default function initPoller( if (stopped) { return; } + const workFunction = async () => { + try { + await callback(); + // If no error, reset any retrying + failedAttempts = 0; + nextPollMs = intervalMs; + if (previousPollFailed) { + previousPollFailed = false; + logger.info('Eppo SDK poll successful; resuming normal polling'); + } + } catch (error: any) { + previousPollFailed = true; + logger.warn(`Eppo SDK encountered an error polling configurations: ${error.message}`); + const maxTries = 1 + (options?.maxPollRetries ?? DEFAULT_POLL_CONFIG_REQUEST_RETRIES); + if (++failedAttempts < maxTries) { + const failureWaitMultiplier = Math.pow(2, failedAttempts); + const jitterMs = randomJitterMs(intervalMs); + nextPollMs = failureWaitMultiplier * intervalMs + jitterMs; + logger.warn( + `Eppo SDK will try polling again in ${nextPollMs} ms (${ + maxTries - failedAttempts + } attempts remaining)`, + ); + } else { + logger.error( + `Eppo SDK reached maximum of ${failedAttempts} failed polling attempts. Stopping polling`, + ); + stop(); + } + } + }; try { - await callback(); - // If no error, reset any retrying - failedAttempts = 0; - nextPollMs = intervalMs; - if (previousPollFailed) { - previousPollFailed = false; - logger.info('Eppo SDK poll successful; resuming normal polling'); - } - } catch (error: any) { - previousPollFailed = true; - logger.warn(`Eppo SDK encountered an error polling configurations: ${error.message}`); - const maxTries = 1 + (options?.maxPollRetries ?? DEFAULT_POLL_CONFIG_REQUEST_RETRIES); - if (++failedAttempts < maxTries) { - const failureWaitMultiplier = Math.pow(2, failedAttempts); - const jitterMs = randomJitterMs(intervalMs); - nextPollMs = failureWaitMultiplier * intervalMs + jitterMs; - logger.warn( - `Eppo SDK will try polling again in ${nextPollMs} ms (${ - maxTries - failedAttempts - } attempts remaining)`, - ); - } else { - logger.error( - `Eppo SDK reached maximum of ${failedAttempts} failed polling attempts. Stopping polling`, - ); - stop(); - } + await pollIntercept(workFunction); + } catch (e: any) { + logger.error(`Eppo SDK encountered an error with the polling wrapper: ${e.message}`); } - setTimeout(poll, nextPollMs); + nextTimer = setTimeout(poll, nextPollMs); } return {