From 1eeff81e5153f976290980c5e7bbcc7fe6146298 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 1 Oct 2025 11:39:56 -0300 Subject: [PATCH 1/8] Add method to retrieve client readiness status synchronously --- .../__tests__/sdkReadinessManager.spec.ts | 4 +- src/readiness/sdkReadinessManager.ts | 2 +- src/readiness/types.ts | 3 +- src/types.ts | 15 ------ types/splitio.d.ts | 52 +++++++++++++++++++ 5 files changed, 56 insertions(+), 20 deletions(-) diff --git a/src/readiness/__tests__/sdkReadinessManager.spec.ts b/src/readiness/__tests__/sdkReadinessManager.spec.ts index 9044fc72..35ee9d7a 100644 --- a/src/readiness/__tests__/sdkReadinessManager.spec.ts +++ b/src/readiness/__tests__/sdkReadinessManager.spec.ts @@ -51,8 +51,8 @@ describe('SDK Readiness Manager - Event emitter', () => { }); expect(typeof sdkStatus.ready).toBe('function'); // The sdkStatus exposes a .ready() function. - expect(typeof sdkStatus.__getStatus).toBe('function'); // The sdkStatus exposes a .__getStatus() function. - expect(sdkStatus.__getStatus()).toEqual({ + expect(typeof sdkStatus.getStatus).toBe('function'); // The sdkStatus exposes a .getStatus() function. + expect(sdkStatus.getStatus()).toEqual({ isReady: false, isReadyFromCache: false, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: false, lastUpdate: 0 }); diff --git a/src/readiness/sdkReadinessManager.ts b/src/readiness/sdkReadinessManager.ts index ee558d47..3f3de706 100644 --- a/src/readiness/sdkReadinessManager.ts +++ b/src/readiness/sdkReadinessManager.ts @@ -104,7 +104,7 @@ export function sdkReadinessManagerFactory( return readyPromise; }, - __getStatus() { + getStatus() { return { isReady: readinessManager.isReady(), isReadyFromCache: readinessManager.isReadyFromCache(), diff --git a/src/readiness/types.ts b/src/readiness/types.ts index df3c2603..2de99b43 100644 --- a/src/readiness/types.ts +++ b/src/readiness/types.ts @@ -1,4 +1,3 @@ -import { IStatusInterface } from '../types'; import SplitIO from '../../types/splitio'; /** Splits data emitter */ @@ -72,7 +71,7 @@ export interface IReadinessManager { export interface ISdkReadinessManager { readinessManager: IReadinessManager - sdkStatus: IStatusInterface + sdkStatus: SplitIO.IStatusInterface /** * Increment internalReadyCbCount, an offset value of SDK_READY listeners that are added/removed internally diff --git a/src/types.ts b/src/types.ts index ad3fa04c..5f6c7e39 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,21 +14,6 @@ export interface ISettings extends SplitIO.ISettings { readonly initialRolloutPlan?: RolloutPlan; } -/** - * SplitIO.IStatusInterface interface extended with private properties for internal use - */ -export interface IStatusInterface extends SplitIO.IStatusInterface { - // Expose status for internal purposes only. Not considered part of the public API, and might be updated eventually. - __getStatus(): { - isReady: boolean; - isReadyFromCache: boolean; - isTimedout: boolean; - hasTimedout: boolean; - isDestroyed: boolean; - isOperational: boolean; - lastUpdate: number; - }; -} /** * SplitIO.IBasicClient interface extended with private properties for internal use */ diff --git a/types/splitio.d.ts b/types/splitio.d.ts index eaa490f3..364c5208 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -668,6 +668,52 @@ declare namespace SplitIO { [status in ConsentStatus]: ConsentStatus; }; } + /** + * Readiness Status interface. It represents the readiness state of an SDK client. + */ + interface ReadinessStatus { + + /** + * `isReady` indicates if the client has triggered an `SDK_READY` event and + * thus is ready to evaluate with cached data synchronized with the backend. + */ + isReady: boolean; + + /** + * `isReadyFromCache` indicates if the client has triggered an `SDK_READY_FROM_CACHE` event and + * thus is ready to evaluate with cached data, although the data in cache might be stale. + */ + isReadyFromCache: boolean; + + /** + * `isTimedout` indicates if the client has triggered an `SDK_READY_TIMED_OUT` event and is not ready to evaluate. + * In other words, `isTimedout` is equivalent to `hasTimedout && !isReady`. + */ + isTimedout: boolean; + + /** + * `hasTimedout` indicates if the client has ever triggered an `SDK_READY_TIMED_OUT` event. + * It's meant to keep a reference that the SDK emitted a timeout at some point, not the current state. + */ + hasTimedout: boolean; + + /** + * `isDestroyed` indicates if the client has been destroyed, i.e., `destroy` method has been called. + */ + isDestroyed: boolean; + + /** + * `isOperational` indicates if the client can evaluate feature flags. + * In this state, `getTreatment` calls will not return `CONTROL` due to the SDK being unready or destroyed. + * It's equivalent to `(isReady || isReadyFromCache) && !isDestroyed`. + */ + isOperational: boolean; + + /** + * `lastUpdate` indicates the timestamp of the most recent status event. + */ + lastUpdate: number; + } /** * Common API for entities that expose status handlers. */ @@ -676,6 +722,12 @@ declare namespace SplitIO { * Constant object containing the SDK events for you to use. */ Event: EventConsts; + /** + * Gets the readiness status. + * + * @returns The current readiness status. + */ + getStatus(): ReadinessStatus; /** * Returns a promise that resolves once the SDK has finished loading (`SDK_READY` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready after a timeout event, the `ready` method will return a resolved promise once the SDK is ready. From 53cc6dbb7ff6249a12e7b5384e9a24785178d20b Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 21 Oct 2025 11:52:00 -0300 Subject: [PATCH 2/8] feat: add whenReady and whenReadyFromCache methods to replace deprecated ready method --- src/readiness/sdkReadinessManager.ts | 30 ++++++++++++++++++++++++++- types/splitio.d.ts | 31 +++++++++++++++++++++------- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/readiness/sdkReadinessManager.ts b/src/readiness/sdkReadinessManager.ts index 3f3de706..2bda4ed3 100644 --- a/src/readiness/sdkReadinessManager.ts +++ b/src/readiness/sdkReadinessManager.ts @@ -9,6 +9,7 @@ import { ERROR_CLIENT_LISTENER, CLIENT_READY_FROM_CACHE, CLIENT_READY, CLIENT_NO const NEW_LISTENER_EVENT = 'newListener'; const REMOVE_LISTENER_EVENT = 'removeListener'; +const TIMEOUT_ERROR = new Error('Split SDK has emitted SDK_READY_TIMED_OUT event.'); /** * SdkReadinessManager factory, which provides the public status API of SDK clients and manager: ready promise, readiness event emitter and constants (SDK_READY, etc). @@ -93,10 +94,11 @@ export function sdkReadinessManagerFactory( SDK_READY_TIMED_OUT, }, + // @TODO: remove in next major ready() { if (readinessManager.hasTimedout()) { if (!readinessManager.isReady()) { - return promiseWrapper(Promise.reject(new Error('Split SDK has emitted SDK_READY_TIMED_OUT event.')), defaultOnRejected); + return promiseWrapper(Promise.reject(TIMEOUT_ERROR), defaultOnRejected); } else { return Promise.resolve(); } @@ -104,6 +106,32 @@ export function sdkReadinessManagerFactory( return readyPromise; }, + whenReady() { + return new Promise((resolve, reject) => { + if (readinessManager.isReady()) { + resolve(); + } else if (readinessManager.hasTimedout()) { + reject(TIMEOUT_ERROR); + } else { + readinessManager.gate.once(SDK_READY, resolve); + readinessManager.gate.once(SDK_READY_TIMED_OUT, () => reject(TIMEOUT_ERROR)); + } + }); + }, + + whenReadyFromCache() { + return new Promise((resolve, reject) => { + if (readinessManager.isReadyFromCache()) { + resolve(); + } else if (readinessManager.hasTimedout()) { + reject(TIMEOUT_ERROR); + } else { + readinessManager.gate.once(SDK_READY_FROM_CACHE, resolve); + readinessManager.gate.once(SDK_READY_TIMED_OUT, () => reject(TIMEOUT_ERROR)); + } + }); + }, + getStatus() { return { isReady: readinessManager.isReady(), diff --git a/types/splitio.d.ts b/types/splitio.d.ts index 4a473fd8..58aca849 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -525,19 +525,19 @@ declare namespace SplitIO { */ type EventConsts = { /** - * The ready event. + * The ready event emitted once the SDK is ready to evaluate feature flags with cache synchronized with the backend. */ SDK_READY: 'init::ready'; /** - * The ready event when fired with cached data. + * The ready event emitted once the SDK is ready to evaluate feature flags with cache that could be stale. Use SDK_READY if you want to be sure the cache is in sync with the backend. */ SDK_READY_FROM_CACHE: 'init::cache-ready'; /** - * The timeout event. + * The timeout event emitted after `startup.readyTimeout` seconds if the SDK_READY event was not emitted. */ SDK_READY_TIMED_OUT: 'init::timeout'; /** - * The update event. + * The update event emitted when the SDK cache is updated with new data from the backend. */ SDK_UPDATE: 'state::update'; }; @@ -704,7 +704,7 @@ declare namespace SplitIO { /** * `isReadyFromCache` indicates if the client has triggered an `SDK_READY_FROM_CACHE` event and - * thus is ready to evaluate with cached data, although the data in cache might be stale. + * thus is ready to evaluate with cached data, although the data in cache might be stale, not synchronized with the backend. */ isReadyFromCache: boolean; @@ -728,7 +728,7 @@ declare namespace SplitIO { /** * `isOperational` indicates if the client can evaluate feature flags. * In this state, `getTreatment` calls will not return `CONTROL` due to the SDK being unready or destroyed. - * It's equivalent to `(isReady || isReadyFromCache) && !isDestroyed`. + * It's equivalent to `isReadyFromCache && !isDestroyed`. */ isOperational: boolean; @@ -752,7 +752,7 @@ declare namespace SplitIO { */ getStatus(): ReadinessStatus; /** - * Returns a promise that resolves once the SDK has finished loading (`SDK_READY` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). + * Returns a promise that resolves once the SDK has finished synchronizing with the backend (`SDK_READY` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready after a timeout event, the `ready` method will return a resolved promise once the SDK is ready. * * Caveats: the method was designed to avoid an unhandled Promise rejection if the rejection case is not handled, so that `onRejected` handler is optional when using promises. @@ -767,8 +767,23 @@ declare namespace SplitIO { * ``` * * @returns A promise that resolves once the SDK is ready or rejects if the SDK has timedout. + * @deprecated Use `whenReady` instead. */ ready(): Promise; + /** + * Returns a promise that resolves once the SDK is ready for evaluations using cached data synchronized with the backend (`SDK_READY` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). + * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready after a timeout event, the `whenReady` method will return a resolved promise once the SDK is ready. + * + * @returns A promise that resolves once the SDK is ready or rejects if the SDK has timedout. + */ + whenReady(): Promise; + /** + * Returns a promise that resolves once the SDK is ready for evaluations using cached data which might not yet be synchronized with the backend (`SDK_READY_FROM_CACHE` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). + * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready from cache after a timeout event, the `whenReadyFromCache` method will return a resolved promise once the SDK is ready from cache. + * + * @returns A promise that resolves once the SDK is ready from cache or rejects if the SDK has timedout. + */ + whenReadyFromCache(): Promise; } /** * Common definitions between clients for different environments interface. @@ -1702,7 +1717,7 @@ declare namespace SplitIO { * Wait for the SDK client to be ready before calling this method. * * ```js - * await factory.client().ready(); + * await factory.client().whenReady(); * const rolloutPlan = factory.getRolloutPlan(); * ``` * From 13eaec3e8026397d116e65a25b919aa606ef33be Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 22 Oct 2025 12:40:13 -0300 Subject: [PATCH 3/8] feat: update whenReadyFromCache to return boolean indicating SDK ready state --- src/readiness/sdkReadinessManager.ts | 6 +++--- types/splitio.d.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/readiness/sdkReadinessManager.ts b/src/readiness/sdkReadinessManager.ts index 2bda4ed3..03afd873 100644 --- a/src/readiness/sdkReadinessManager.ts +++ b/src/readiness/sdkReadinessManager.ts @@ -120,13 +120,13 @@ export function sdkReadinessManagerFactory( }, whenReadyFromCache() { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { if (readinessManager.isReadyFromCache()) { - resolve(); + resolve(readinessManager.isReady()); } else if (readinessManager.hasTimedout()) { reject(TIMEOUT_ERROR); } else { - readinessManager.gate.once(SDK_READY_FROM_CACHE, resolve); + readinessManager.gate.once(SDK_READY_FROM_CACHE, () => resolve(readinessManager.isReady())); readinessManager.gate.once(SDK_READY_TIMED_OUT, () => reject(TIMEOUT_ERROR)); } }); diff --git a/types/splitio.d.ts b/types/splitio.d.ts index 58aca849..f3981076 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -781,9 +781,9 @@ declare namespace SplitIO { * Returns a promise that resolves once the SDK is ready for evaluations using cached data which might not yet be synchronized with the backend (`SDK_READY_FROM_CACHE` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready from cache after a timeout event, the `whenReadyFromCache` method will return a resolved promise once the SDK is ready from cache. * - * @returns A promise that resolves once the SDK is ready from cache or rejects if the SDK has timedout. + * @returns A promise that resolves once the SDK is ready from cache or rejects if the SDK has timedout. The promise resolves with a boolean value that indicates whether the SDK is ready (synchronized with the backend) or not. */ - whenReadyFromCache(): Promise; + whenReadyFromCache(): Promise; } /** * Common definitions between clients for different environments interface. From e2179d7f2da0407ea20bc70140da21d55c414ebb Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 22 Oct 2025 15:32:39 -0300 Subject: [PATCH 4/8] Polishing --- src/readiness/readinessManager.ts | 4 ++-- types/splitio.d.ts | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/readiness/readinessManager.ts b/src/readiness/readinessManager.ts index 8a93d03c..319e843d 100644 --- a/src/readiness/readinessManager.ts +++ b/src/readiness/readinessManager.ts @@ -90,7 +90,7 @@ export function readinessManagerFactory( if (!isReady && !isDestroyed) { try { syncLastUpdate(); - gate.emit(SDK_READY_FROM_CACHE); + gate.emit(SDK_READY_FROM_CACHE, isReady); } catch (e) { // throws user callback exceptions in next tick setTimeout(() => { throw e; }, 0); @@ -116,7 +116,7 @@ export function readinessManagerFactory( syncLastUpdate(); if (!isReadyFromCache) { isReadyFromCache = true; - gate.emit(SDK_READY_FROM_CACHE); + gate.emit(SDK_READY_FROM_CACHE, isReady); } gate.emit(SDK_READY); } catch (e) { diff --git a/types/splitio.d.ts b/types/splitio.d.ts index f3981076..b3884694 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -772,16 +772,17 @@ declare namespace SplitIO { ready(): Promise; /** * Returns a promise that resolves once the SDK is ready for evaluations using cached data synchronized with the backend (`SDK_READY` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). - * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready after a timeout event, the `whenReady` method will return a resolved promise once the SDK is ready. + * As it's meant to provide similar flexibility than event listeners, given that the SDK might be ready after a timeout event, the `whenReady` method will return a resolved promise once the SDK is ready. * - * @returns A promise that resolves once the SDK is ready or rejects if the SDK has timedout. + * @returns A promise that resolves once the SDK_READY event is emitted or rejects if the SDK has timedout. */ whenReady(): Promise; /** * Returns a promise that resolves once the SDK is ready for evaluations using cached data which might not yet be synchronized with the backend (`SDK_READY_FROM_CACHE` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). - * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready from cache after a timeout event, the `whenReadyFromCache` method will return a resolved promise once the SDK is ready from cache. + * As it's meant to provide similar flexibility than event listeners, given that the SDK might be ready from cache after a timeout event, the `whenReadyFromCache` method will return a resolved promise once the SDK is ready from cache. * - * @returns A promise that resolves once the SDK is ready from cache or rejects if the SDK has timedout. The promise resolves with a boolean value that indicates whether the SDK is ready (synchronized with the backend) or not. + * @returns A promise that resolves once the SDK_READY_FROM_CACHE event is emitted or rejects if the SDK has timedout. The promise resolves with a boolean value that + * indicates whether the SDK_READY_FROM_CACHE event was emitted together with the SDK_READY event (i.e., the SDK is ready and synchronized with the backend) or not. */ whenReadyFromCache(): Promise; } From e49de68dda535708d7c9775514f998b3560ce2f7 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 22 Oct 2025 16:44:46 -0300 Subject: [PATCH 5/8] Revert "Add method to retrieve client readiness status synchronously" This reverts commit 1eeff81e5153f976290980c5e7bbcc7fe6146298. --- .../__tests__/sdkReadinessManager.spec.ts | 4 +- src/readiness/sdkReadinessManager.ts | 2 +- src/readiness/types.ts | 3 +- src/types.ts | 15 ++++++ types/splitio.d.ts | 52 ------------------- 5 files changed, 20 insertions(+), 56 deletions(-) diff --git a/src/readiness/__tests__/sdkReadinessManager.spec.ts b/src/readiness/__tests__/sdkReadinessManager.spec.ts index 35ee9d7a..9044fc72 100644 --- a/src/readiness/__tests__/sdkReadinessManager.spec.ts +++ b/src/readiness/__tests__/sdkReadinessManager.spec.ts @@ -51,8 +51,8 @@ describe('SDK Readiness Manager - Event emitter', () => { }); expect(typeof sdkStatus.ready).toBe('function'); // The sdkStatus exposes a .ready() function. - expect(typeof sdkStatus.getStatus).toBe('function'); // The sdkStatus exposes a .getStatus() function. - expect(sdkStatus.getStatus()).toEqual({ + expect(typeof sdkStatus.__getStatus).toBe('function'); // The sdkStatus exposes a .__getStatus() function. + expect(sdkStatus.__getStatus()).toEqual({ isReady: false, isReadyFromCache: false, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: false, lastUpdate: 0 }); diff --git a/src/readiness/sdkReadinessManager.ts b/src/readiness/sdkReadinessManager.ts index 03afd873..62f51571 100644 --- a/src/readiness/sdkReadinessManager.ts +++ b/src/readiness/sdkReadinessManager.ts @@ -132,7 +132,7 @@ export function sdkReadinessManagerFactory( }); }, - getStatus() { + __getStatus() { return { isReady: readinessManager.isReady(), isReadyFromCache: readinessManager.isReadyFromCache(), diff --git a/src/readiness/types.ts b/src/readiness/types.ts index 2de99b43..df3c2603 100644 --- a/src/readiness/types.ts +++ b/src/readiness/types.ts @@ -1,3 +1,4 @@ +import { IStatusInterface } from '../types'; import SplitIO from '../../types/splitio'; /** Splits data emitter */ @@ -71,7 +72,7 @@ export interface IReadinessManager { export interface ISdkReadinessManager { readinessManager: IReadinessManager - sdkStatus: SplitIO.IStatusInterface + sdkStatus: IStatusInterface /** * Increment internalReadyCbCount, an offset value of SDK_READY listeners that are added/removed internally diff --git a/src/types.ts b/src/types.ts index 5f6c7e39..ad3fa04c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,6 +14,21 @@ export interface ISettings extends SplitIO.ISettings { readonly initialRolloutPlan?: RolloutPlan; } +/** + * SplitIO.IStatusInterface interface extended with private properties for internal use + */ +export interface IStatusInterface extends SplitIO.IStatusInterface { + // Expose status for internal purposes only. Not considered part of the public API, and might be updated eventually. + __getStatus(): { + isReady: boolean; + isReadyFromCache: boolean; + isTimedout: boolean; + hasTimedout: boolean; + isDestroyed: boolean; + isOperational: boolean; + lastUpdate: number; + }; +} /** * SplitIO.IBasicClient interface extended with private properties for internal use */ diff --git a/types/splitio.d.ts b/types/splitio.d.ts index b3884694..0a8dfda2 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -691,52 +691,6 @@ declare namespace SplitIO { [status in ConsentStatus]: ConsentStatus; }; } - /** - * Readiness Status interface. It represents the readiness state of an SDK client. - */ - interface ReadinessStatus { - - /** - * `isReady` indicates if the client has triggered an `SDK_READY` event and - * thus is ready to evaluate with cached data synchronized with the backend. - */ - isReady: boolean; - - /** - * `isReadyFromCache` indicates if the client has triggered an `SDK_READY_FROM_CACHE` event and - * thus is ready to evaluate with cached data, although the data in cache might be stale, not synchronized with the backend. - */ - isReadyFromCache: boolean; - - /** - * `isTimedout` indicates if the client has triggered an `SDK_READY_TIMED_OUT` event and is not ready to evaluate. - * In other words, `isTimedout` is equivalent to `hasTimedout && !isReady`. - */ - isTimedout: boolean; - - /** - * `hasTimedout` indicates if the client has ever triggered an `SDK_READY_TIMED_OUT` event. - * It's meant to keep a reference that the SDK emitted a timeout at some point, not the current state. - */ - hasTimedout: boolean; - - /** - * `isDestroyed` indicates if the client has been destroyed, i.e., `destroy` method has been called. - */ - isDestroyed: boolean; - - /** - * `isOperational` indicates if the client can evaluate feature flags. - * In this state, `getTreatment` calls will not return `CONTROL` due to the SDK being unready or destroyed. - * It's equivalent to `isReadyFromCache && !isDestroyed`. - */ - isOperational: boolean; - - /** - * `lastUpdate` indicates the timestamp of the most recent status event. - */ - lastUpdate: number; - } /** * Common API for entities that expose status handlers. */ @@ -745,12 +699,6 @@ declare namespace SplitIO { * Constant object containing the SDK events for you to use. */ Event: EventConsts; - /** - * Gets the readiness status. - * - * @returns The current readiness status. - */ - getStatus(): ReadinessStatus; /** * Returns a promise that resolves once the SDK has finished synchronizing with the backend (`SDK_READY` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready after a timeout event, the `ready` method will return a resolved promise once the SDK is ready. From 36ca35e1d0b1aceb2407adfdc5e7cfb0e4a992d2 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 22 Oct 2025 17:17:05 -0300 Subject: [PATCH 6/8] Polishing --- CHANGES.txt | 1 + types/splitio.d.ts | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index fdab3d6d..8d931493 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 2.8.0 (October XX, 2025) + - Added `client.whenReady()` and `client.whenReadyFromCache()` methods to replace the deprecated `client.ready()` method, which had an issue causing the returned promise to hang when using async/await syntax if it was rejected. - Updated the SDK_READY_FROM_CACHE event to be emitted alongside the SDK_READY event if it hasn’t already been emitted. 2.7.1 (October 8, 2025) diff --git a/types/splitio.d.ts b/types/splitio.d.ts index 0a8dfda2..49f70c62 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -700,7 +700,7 @@ declare namespace SplitIO { */ Event: EventConsts; /** - * Returns a promise that resolves once the SDK has finished synchronizing with the backend (`SDK_READY` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). + * Returns a promise that resolves when the SDK has finished initial synchronization with the backend (`SDK_READY` event emitted), or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready after a timeout event, the `ready` method will return a resolved promise once the SDK is ready. * * Caveats: the method was designed to avoid an unhandled Promise rejection if the rejection case is not handled, so that `onRejected` handler is optional when using promises. @@ -719,15 +719,17 @@ declare namespace SplitIO { */ ready(): Promise; /** - * Returns a promise that resolves once the SDK is ready for evaluations using cached data synchronized with the backend (`SDK_READY` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). + * Returns a promise that resolves when the SDK has finished initial synchronization with the backend (`SDK_READY` event emitted), or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). * As it's meant to provide similar flexibility than event listeners, given that the SDK might be ready after a timeout event, the `whenReady` method will return a resolved promise once the SDK is ready. + * You must handle the promise rejection to avoid an unhandled promise rejection error, or set the `startup.readyTimeout` configuration option to 0 to avoid the timeout and thus the rejection. * * @returns A promise that resolves once the SDK_READY event is emitted or rejects if the SDK has timedout. */ whenReady(): Promise; /** - * Returns a promise that resolves once the SDK is ready for evaluations using cached data which might not yet be synchronized with the backend (`SDK_READY_FROM_CACHE` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). + * Returns a promise that resolves when the SDK is ready for evaluations using cached data, which might not yet be synchronized with the backend (`SDK_READY_FROM_CACHE` event emitted), or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). * As it's meant to provide similar flexibility than event listeners, given that the SDK might be ready from cache after a timeout event, the `whenReadyFromCache` method will return a resolved promise once the SDK is ready from cache. + * You must handle the promise rejection to avoid an unhandled promise rejection error, or set the `startup.readyTimeout` configuration option to 0 to avoid the timeout and thus the rejection. * * @returns A promise that resolves once the SDK_READY_FROM_CACHE event is emitted or rejects if the SDK has timedout. The promise resolves with a boolean value that * indicates whether the SDK_READY_FROM_CACHE event was emitted together with the SDK_READY event (i.e., the SDK is ready and synchronized with the backend) or not. From b8719b299df104bc2759861f22cb78b91958aec1 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 23 Oct 2025 11:20:33 -0300 Subject: [PATCH 7/8] Polishing --- CHANGES.txt | 2 +- .../__tests__/sdkReadinessManager.spec.ts | 72 +++++++++++++------ src/readiness/sdkReadinessManager.ts | 4 +- 3 files changed, 55 insertions(+), 23 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 8d931493..9163d52f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ 2.8.0 (October XX, 2025) - - Added `client.whenReady()` and `client.whenReadyFromCache()` methods to replace the deprecated `client.ready()` method, which had an issue causing the returned promise to hang when using async/await syntax if it was rejected. + - Added `client.whenReady()` and `client.whenReadyFromCache()` methods to replace the deprecated `client.ready()` method, which has an issue causing the returned promise to hang when using async/await syntax if it was rejected. - Updated the SDK_READY_FROM_CACHE event to be emitted alongside the SDK_READY event if it hasn’t already been emitted. 2.7.1 (October 8, 2025) diff --git a/src/readiness/__tests__/sdkReadinessManager.spec.ts b/src/readiness/__tests__/sdkReadinessManager.spec.ts index 9044fc72..d4de5124 100644 --- a/src/readiness/__tests__/sdkReadinessManager.spec.ts +++ b/src/readiness/__tests__/sdkReadinessManager.spec.ts @@ -1,11 +1,12 @@ // @ts-nocheck import { loggerMock } from '../../logger/__tests__/sdkLogger.mock'; import SplitIO from '../../../types/splitio'; -import { SDK_READY, SDK_READY_FROM_CACHE, SDK_READY_TIMED_OUT, SDK_UPDATE } from '../constants'; +import { SDK_READY, SDK_READY_FROM_CACHE, SDK_READY_TIMED_OUT, SDK_UPDATE, SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../constants'; import { sdkReadinessManagerFactory } from '../sdkReadinessManager'; import { IReadinessManager } from '../types'; import { ERROR_CLIENT_LISTENER, CLIENT_READY_FROM_CACHE, CLIENT_READY, CLIENT_NO_LISTENER } from '../../logger/constants'; import { fullSettings } from '../../utils/settingsValidation/__tests__/settings.mocks'; +import { EventEmitter } from '../../utils/MinEvents'; const EventEmitterMock = jest.fn(() => ({ on: jest.fn(), @@ -24,6 +25,7 @@ function emitReadyEvent(readinessManager: IReadinessManager) { readinessManager.segments.once.mock.calls[0][1](); readinessManager.segments.on.mock.calls[0][1](); readinessManager.gate.once.mock.calls[0][1](); + if (readinessManager.gate.once.mock.calls[3]) readinessManager.gate.once.mock.calls[3][1](); // whenReady promise } const timeoutErrorMessage = 'Split SDK emitted SDK_READY_TIMED_OUT event.'; @@ -32,6 +34,7 @@ const timeoutErrorMessage = 'Split SDK emitted SDK_READY_TIMED_OUT event.'; function emitTimeoutEvent(readinessManager: IReadinessManager) { readinessManager.gate.once.mock.calls[1][1](timeoutErrorMessage); readinessManager.hasTimedout = () => true; + if (readinessManager.gate.once.mock.calls[4]) readinessManager.gate.once.mock.calls[4][1](timeoutErrorMessage); // whenReady promise } describe('SDK Readiness Manager - Event emitter', () => { @@ -50,7 +53,8 @@ describe('SDK Readiness Manager - Event emitter', () => { expect(sdkStatus[propName]).toBeTruthy(); // The sdkStatus exposes all minimal EventEmitter functionality. }); - expect(typeof sdkStatus.ready).toBe('function'); // The sdkStatus exposes a .ready() function. + expect(typeof sdkStatus.whenReady).toBe('function'); // The sdkStatus exposes a .whenReady() function. + expect(typeof sdkStatus.whenReadyFromCache).toBe('function'); // The sdkStatus exposes a .whenReadyFromCache() function. expect(typeof sdkStatus.__getStatus).toBe('function'); // The sdkStatus exposes a .__getStatus() function. expect(sdkStatus.__getStatus()).toEqual({ isReady: false, isReadyFromCache: false, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: false, lastUpdate: 0 @@ -67,9 +71,9 @@ describe('SDK Readiness Manager - Event emitter', () => { const sdkReadyResolvePromiseCall = gateMock.once.mock.calls[0]; const sdkReadyRejectPromiseCall = gateMock.once.mock.calls[1]; const sdkReadyFromCacheListenersCheckCall = gateMock.once.mock.calls[2]; - expect(sdkReadyResolvePromiseCall[0]).toBe(SDK_READY); // A one time only subscription is on the SDK_READY event, for resolving the full blown ready promise and to check for callbacks warning. - expect(sdkReadyRejectPromiseCall[0]).toBe(SDK_READY_TIMED_OUT); // A one time only subscription is also on the SDK_READY_TIMED_OUT event, for rejecting the full blown ready promise. - expect(sdkReadyFromCacheListenersCheckCall[0]).toBe(SDK_READY_FROM_CACHE); // A one time only subscription is on the SDK_READY_FROM_CACHE event, to log the event and update internal state. + expect(sdkReadyResolvePromiseCall[0]).toBe(SDK_READY); // A one time only subscription is on the SDK_READY event + expect(sdkReadyRejectPromiseCall[0]).toBe(SDK_READY_TIMED_OUT); // A one time only subscription is also on the SDK_READY_TIMED_OUT event + expect(sdkReadyFromCacheListenersCheckCall[0]).toBe(SDK_READY_FROM_CACHE); // A one time only subscription is on the SDK_READY_FROM_CACHE event expect(gateMock.on).toBeCalledTimes(2); // It should also add two persistent listeners @@ -98,7 +102,7 @@ describe('SDK Readiness Manager - Event emitter', () => { emitReadyEvent(sdkReadinessManager.readinessManager); - expect(loggerMock.warn).toBeCalledTimes(1); // If the SDK_READY event fires and we have no callbacks for it (neither event nor ready promise) we get a warning. + expect(loggerMock.warn).toBeCalledTimes(1); // If the SDK_READY event fires and we have no callbacks for it (neither event nor whenReady promise) we get a warning. expect(loggerMock.warn).toBeCalledWith(CLIENT_NO_LISTENER); // Telling us there were no listeners and evaluations before this point may have been incorrect. expect(loggerMock.info).toBeCalledTimes(1); // If the SDK_READY event fires, we get a info message. @@ -199,15 +203,15 @@ describe('SDK Readiness Manager - Event emitter', () => { }); }); -describe('SDK Readiness Manager - Ready promise', () => { +describe('SDK Readiness Manager - whenReady promise', () => { - test('.ready() promise behavior for clients', async () => { + test('.whenReady() promise behavior for clients', async () => { const sdkReadinessManager = sdkReadinessManagerFactory(EventEmitterMock, fullSettings); - const ready = sdkReadinessManager.sdkStatus.ready(); + const ready = sdkReadinessManager.sdkStatus.whenReady(); expect(ready instanceof Promise).toBe(true); // It should return a promise. - // make the SDK "ready" + // make the SDK ready emitReadyEvent(sdkReadinessManager.readinessManager); let testPassedCount = 0; @@ -219,8 +223,8 @@ describe('SDK Readiness Manager - Ready promise', () => { () => { throw new Error('It should be resolved on ready event, not rejected.'); } ); - // any subsequent call to .ready() must be a resolved promise - await ready.then( + // any subsequent call to .whenReady() must be a resolved promise + await sdkReadinessManager.sdkStatus.whenReady().then( () => { expect('A subsequent call should be a resolved promise.'); testPassedCount++; @@ -233,9 +237,9 @@ describe('SDK Readiness Manager - Ready promise', () => { const sdkReadinessManagerForTimedout = sdkReadinessManagerFactory(EventEmitterMock, fullSettings); - const readyForTimeout = sdkReadinessManagerForTimedout.sdkStatus.ready(); + const readyForTimeout = sdkReadinessManagerForTimedout.sdkStatus.whenReady(); - emitTimeoutEvent(sdkReadinessManagerForTimedout.readinessManager); // make the SDK "timed out" + emitTimeoutEvent(sdkReadinessManagerForTimedout.readinessManager); // make the SDK timeout await readyForTimeout.then( () => { throw new Error('It should be a promise that was rejected on SDK_READY_TIMED_OUT, not resolved.'); }, @@ -245,8 +249,8 @@ describe('SDK Readiness Manager - Ready promise', () => { } ); - // any subsequent call to .ready() must be a rejected promise - await readyForTimeout.then( + // any subsequent call to .whenReady() must be a rejected promise until the SDK is ready + await sdkReadinessManagerForTimedout.sdkStatus.whenReady().then( () => { throw new Error('It should be a promise that was rejected on SDK_READY_TIMED_OUT, not resolved.'); }, () => { expect('A subsequent call should be a rejected promise.'); @@ -254,11 +258,11 @@ describe('SDK Readiness Manager - Ready promise', () => { } ); - // make the SDK "ready" + // make the SDK ready emitReadyEvent(sdkReadinessManagerForTimedout.readinessManager); - // once SDK_READY, `.ready()` returns a resolved promise - await ready.then( + // once SDK_READY, `.whenReady()` returns a resolved promise + await sdkReadinessManagerForTimedout.sdkStatus.whenReady().then( () => { expect('It should be a resolved promise when the SDK is ready, even after an SDK timeout.'); loggerMock.mockClear(); @@ -269,7 +273,35 @@ describe('SDK Readiness Manager - Ready promise', () => { ); }); - test('Full blown ready promise count as a callback and resolves on SDK_READY', (done) => { + test('whenReady promise count as a callback and resolves on SDK_READY', (done) => { + let sdkReadinessManager = sdkReadinessManagerFactory(EventEmitter, fullSettings); + + // Emit ready event + sdkReadinessManager.readinessManager.splits.emit(SDK_SPLITS_ARRIVED); + sdkReadinessManager.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); + + expect(loggerMock.warn).toBeCalledWith(CLIENT_NO_LISTENER); // We should get a warning if the SDK get's ready before calling the whenReady method or attaching a listener to the ready event + loggerMock.warn.mockClear(); + + sdkReadinessManager = sdkReadinessManagerFactory(EventEmitter, fullSettings); + sdkReadinessManager.sdkStatus.whenReady().then(() => { + expect('whenReady promise is resolved when the gate emits SDK_READY.'); + done(); + }, () => { + throw new Error('This should not be called as the promise is being resolved.'); + }); + + // Emit ready event + sdkReadinessManager.readinessManager.splits.emit(SDK_SPLITS_ARRIVED); + sdkReadinessManager.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); + + expect(loggerMock.warn).not.toBeCalled(); // But if we have a listener or call the whenReady method, we get no warnings. + }); +}); + +// @TODO: remove in next major +describe('SDK Readiness Manager - Ready promise', () => { + test('ready promise count as a callback and resolves on SDK_READY', (done) => { const sdkReadinessManager = sdkReadinessManagerFactory(EventEmitterMock, fullSettings); const readyPromise = sdkReadinessManager.sdkStatus.ready(); diff --git a/src/readiness/sdkReadinessManager.ts b/src/readiness/sdkReadinessManager.ts index 62f51571..857a9e84 100644 --- a/src/readiness/sdkReadinessManager.ts +++ b/src/readiness/sdkReadinessManager.ts @@ -9,7 +9,7 @@ import { ERROR_CLIENT_LISTENER, CLIENT_READY_FROM_CACHE, CLIENT_READY, CLIENT_NO const NEW_LISTENER_EVENT = 'newListener'; const REMOVE_LISTENER_EVENT = 'removeListener'; -const TIMEOUT_ERROR = new Error('Split SDK has emitted SDK_READY_TIMED_OUT event.'); +const TIMEOUT_ERROR = new Error(SDK_READY_TIMED_OUT); /** * SdkReadinessManager factory, which provides the public status API of SDK clients and manager: ready promise, readiness event emitter and constants (SDK_READY, etc). @@ -98,7 +98,7 @@ export function sdkReadinessManagerFactory( ready() { if (readinessManager.hasTimedout()) { if (!readinessManager.isReady()) { - return promiseWrapper(Promise.reject(TIMEOUT_ERROR), defaultOnRejected); + return promiseWrapper(Promise.reject(new Error('Split SDK has emitted SDK_READY_TIMED_OUT event.')), defaultOnRejected); } else { return Promise.resolve(); } From 94814df1f6630c28154778a5b58e6b630e4b28cd Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 23 Oct 2025 15:46:26 -0300 Subject: [PATCH 8/8] Update logs and tests --- src/logger/messages/warn.ts | 2 +- .../__tests__/sdkReadinessManager.spec.ts | 114 +++++++++--------- src/readiness/sdkReadinessManager.ts | 2 + 3 files changed, 62 insertions(+), 56 deletions(-) diff --git a/src/logger/messages/warn.ts b/src/logger/messages/warn.ts index 8f87babd..a0bf31a9 100644 --- a/src/logger/messages/warn.ts +++ b/src/logger/messages/warn.ts @@ -15,7 +15,7 @@ export const codesWarn: [number, string][] = codesError.concat([ [c.SUBMITTERS_PUSH_RETRY, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Failed to push %s, keeping data to retry on next iteration. Reason: %s.'], // client status [c.CLIENT_NOT_READY_FROM_CACHE, '%s: the SDK is not ready to evaluate. Results may be incorrect%s. Make sure to wait for SDK readiness before using this method.'], - [c.CLIENT_NO_LISTENER, 'No listeners for SDK Readiness detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet ready.'], + [c.CLIENT_NO_LISTENER, 'No listeners for SDK_READY event detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet synchronized with the backend.'], // input validation [c.WARN_SETTING_NULL, '%s: Property "%s" is of invalid type. Setting value to null.'], [c.WARN_TRIMMING_PROPERTIES, '%s: more than 300 properties were provided. Some of them will be trimmed when processed.'], diff --git a/src/readiness/__tests__/sdkReadinessManager.spec.ts b/src/readiness/__tests__/sdkReadinessManager.spec.ts index d4de5124..4d47d12f 100644 --- a/src/readiness/__tests__/sdkReadinessManager.spec.ts +++ b/src/readiness/__tests__/sdkReadinessManager.spec.ts @@ -1,7 +1,7 @@ // @ts-nocheck import { loggerMock } from '../../logger/__tests__/sdkLogger.mock'; import SplitIO from '../../../types/splitio'; -import { SDK_READY, SDK_READY_FROM_CACHE, SDK_READY_TIMED_OUT, SDK_UPDATE, SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../constants'; +import { SDK_READY, SDK_READY_FROM_CACHE, SDK_READY_TIMED_OUT, SDK_UPDATE, SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../constants'; import { sdkReadinessManagerFactory } from '../sdkReadinessManager'; import { IReadinessManager } from '../types'; import { ERROR_CLIENT_LISTENER, CLIENT_READY_FROM_CACHE, CLIENT_READY, CLIENT_NO_LISTENER } from '../../logger/constants'; @@ -20,6 +20,12 @@ const EventEmitterMock = jest.fn(() => ({ // Makes readinessManager emit SDK_READY & update isReady flag function emitReadyEvent(readinessManager: IReadinessManager) { + if (readinessManager.gate instanceof EventEmitter) { + readinessManager.splits.emit(SDK_SPLITS_ARRIVED); + readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); + return; + } + readinessManager.splits.once.mock.calls[0][1](); readinessManager.splits.on.mock.calls[0][1](); readinessManager.segments.once.mock.calls[0][1](); @@ -32,6 +38,11 @@ const timeoutErrorMessage = 'Split SDK emitted SDK_READY_TIMED_OUT event.'; // Makes readinessManager emit SDK_READY_TIMED_OUT & update hasTimedout flag function emitTimeoutEvent(readinessManager: IReadinessManager) { + if (readinessManager.gate instanceof EventEmitter) { + readinessManager.timeout(); + return; + } + readinessManager.gate.once.mock.calls[1][1](timeoutErrorMessage); readinessManager.hasTimedout = () => true; if (readinessManager.gate.once.mock.calls[4]) readinessManager.gate.once.mock.calls[4][1](timeoutErrorMessage); // whenReady promise @@ -39,7 +50,7 @@ function emitTimeoutEvent(readinessManager: IReadinessManager) { describe('SDK Readiness Manager - Event emitter', () => { - afterEach(() => { loggerMock.mockClear(); }); + beforeEach(() => { loggerMock.mockClear(); }); test('Providing the gate object to get the SDK status interface that manages events', () => { expect(typeof sdkReadinessManagerFactory).toBe('function'); // The module exposes a function. @@ -203,82 +214,74 @@ describe('SDK Readiness Manager - Event emitter', () => { }); }); -describe('SDK Readiness Manager - whenReady promise', () => { +describe('SDK Readiness Manager - Promises', () => { - test('.whenReady() promise behavior for clients', async () => { - const sdkReadinessManager = sdkReadinessManagerFactory(EventEmitterMock, fullSettings); + test('.whenReady() and .whenReadyFromCache() promises resolves when SDK_READY is emitted', async () => { + const sdkReadinessManager = sdkReadinessManagerFactory(EventEmitter, fullSettings); + + // make the SDK ready from cache + sdkReadinessManager.readinessManager.splits.emit(SDK_SPLITS_CACHE_LOADED); + expect(await sdkReadinessManager.sdkStatus.whenReadyFromCache()).toBe(false); + + // validate error log for SDK_READY_FROM_CACHE + expect(loggerMock.error).not.toBeCalled(); + sdkReadinessManager.readinessManager.gate.on(SDK_READY_FROM_CACHE, () => {}); + expect(loggerMock.error).toBeCalledWith(ERROR_CLIENT_LISTENER, ['SDK_READY_FROM_CACHE']); + const readyFromCache = sdkReadinessManager.sdkStatus.whenReadyFromCache(); const ready = sdkReadinessManager.sdkStatus.whenReady(); - expect(ready instanceof Promise).toBe(true); // It should return a promise. // make the SDK ready emitReadyEvent(sdkReadinessManager.readinessManager); + expect(await sdkReadinessManager.sdkStatus.whenReadyFromCache()).toBe(true); let testPassedCount = 0; - await ready.then( - () => { - expect('It should be a promise that will be resolved when the SDK is ready.'); - testPassedCount++; - }, - () => { throw new Error('It should be resolved on ready event, not rejected.'); } - ); + function incTestPassedCount() { testPassedCount++; } + function throwTestFailed() { throw new Error('It should be resolved, not rejected.'); } - // any subsequent call to .whenReady() must be a resolved promise - await sdkReadinessManager.sdkStatus.whenReady().then( - () => { - expect('A subsequent call should be a resolved promise.'); - testPassedCount++; - }, - () => { throw new Error('It should be resolved on ready event, not rejected.'); } - ); + await readyFromCache.then(incTestPassedCount, throwTestFailed); + await ready.then(incTestPassedCount, throwTestFailed); - // control assertion. stubs already reset. - expect(testPassedCount).toBe(2); + // any subsequent call to .whenReady() and .whenReadyFromCache() must be a resolved promise + await sdkReadinessManager.sdkStatus.whenReady().then(incTestPassedCount, throwTestFailed); + await sdkReadinessManager.sdkStatus.whenReadyFromCache().then(incTestPassedCount, throwTestFailed); - const sdkReadinessManagerForTimedout = sdkReadinessManagerFactory(EventEmitterMock, fullSettings); + expect(testPassedCount).toBe(4); + }); + test('.whenReady() and .whenReadyFromCache() promises reject when SDK_READY_TIMED_OUT is emitted before SDK_READY', async () => { + const sdkReadinessManagerForTimedout = sdkReadinessManagerFactory(EventEmitter, fullSettings); + + const readyFromCacheForTimeout = sdkReadinessManagerForTimedout.sdkStatus.whenReadyFromCache(); const readyForTimeout = sdkReadinessManagerForTimedout.sdkStatus.whenReady(); emitTimeoutEvent(sdkReadinessManagerForTimedout.readinessManager); // make the SDK timeout - await readyForTimeout.then( - () => { throw new Error('It should be a promise that was rejected on SDK_READY_TIMED_OUT, not resolved.'); }, - () => { - expect('It should be a promise that will be rejected when the SDK is timed out.'); - testPassedCount++; - } - ); + let testPassedCount = 0; + function incTestPassedCount() { testPassedCount++; } + function throwTestFailed() { throw new Error('It should rejected, not resolved.'); } - // any subsequent call to .whenReady() must be a rejected promise until the SDK is ready - await sdkReadinessManagerForTimedout.sdkStatus.whenReady().then( - () => { throw new Error('It should be a promise that was rejected on SDK_READY_TIMED_OUT, not resolved.'); }, - () => { - expect('A subsequent call should be a rejected promise.'); - testPassedCount++; - } - ); + await readyFromCacheForTimeout.then(throwTestFailed,incTestPassedCount); + await readyForTimeout.then(throwTestFailed,incTestPassedCount); + + // any subsequent call to .whenReady() and .whenReadyFromCache() must be a rejected promise until the SDK is ready + await sdkReadinessManagerForTimedout.sdkStatus.whenReadyFromCache().then(throwTestFailed,incTestPassedCount); + await sdkReadinessManagerForTimedout.sdkStatus.whenReady().then(throwTestFailed,incTestPassedCount); // make the SDK ready emitReadyEvent(sdkReadinessManagerForTimedout.readinessManager); // once SDK_READY, `.whenReady()` returns a resolved promise - await sdkReadinessManagerForTimedout.sdkStatus.whenReady().then( - () => { - expect('It should be a resolved promise when the SDK is ready, even after an SDK timeout.'); - loggerMock.mockClear(); - testPassedCount++; - expect(testPassedCount).toBe(5); - }, - () => { throw new Error('It should be resolved on ready event, not rejected.'); } - ); + await sdkReadinessManagerForTimedout.sdkStatus.whenReady().then(incTestPassedCount, throwTestFailed); + await sdkReadinessManagerForTimedout.sdkStatus.whenReadyFromCache().then(incTestPassedCount, throwTestFailed); + + expect(testPassedCount).toBe(6); }); - test('whenReady promise count as a callback and resolves on SDK_READY', (done) => { + test('whenReady promise counts as an SDK_READY listener', (done) => { let sdkReadinessManager = sdkReadinessManagerFactory(EventEmitter, fullSettings); - // Emit ready event - sdkReadinessManager.readinessManager.splits.emit(SDK_SPLITS_ARRIVED); - sdkReadinessManager.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); + emitReadyEvent(sdkReadinessManager.readinessManager); expect(loggerMock.warn).toBeCalledWith(CLIENT_NO_LISTENER); // We should get a warning if the SDK get's ready before calling the whenReady method or attaching a listener to the ready event loggerMock.warn.mockClear(); @@ -291,9 +294,7 @@ describe('SDK Readiness Manager - whenReady promise', () => { throw new Error('This should not be called as the promise is being resolved.'); }); - // Emit ready event - sdkReadinessManager.readinessManager.splits.emit(SDK_SPLITS_ARRIVED); - sdkReadinessManager.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); + emitReadyEvent(sdkReadinessManager.readinessManager); expect(loggerMock.warn).not.toBeCalled(); // But if we have a listener or call the whenReady method, we get no warnings. }); @@ -301,6 +302,9 @@ describe('SDK Readiness Manager - whenReady promise', () => { // @TODO: remove in next major describe('SDK Readiness Manager - Ready promise', () => { + + beforeEach(() => { loggerMock.mockClear(); }); + test('ready promise count as a callback and resolves on SDK_READY', (done) => { const sdkReadinessManager = sdkReadinessManagerFactory(EventEmitterMock, fullSettings); const readyPromise = sdkReadinessManager.sdkStatus.ready(); diff --git a/src/readiness/sdkReadinessManager.ts b/src/readiness/sdkReadinessManager.ts index 857a9e84..64e518b3 100644 --- a/src/readiness/sdkReadinessManager.ts +++ b/src/readiness/sdkReadinessManager.ts @@ -39,6 +39,8 @@ export function sdkReadinessManagerFactory( } else if (event === SDK_READY) { readyCbCount++; } + } else if (event === SDK_READY_FROM_CACHE && readinessManager.isReadyFromCache()) { + log.error(ERROR_CLIENT_LISTENER, ['SDK_READY_FROM_CACHE']); } });