-
Notifications
You must be signed in to change notification settings - Fork 31
feat: implement waitForInitialization for browser sdk 4.x
#1028
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
9a8ee5b
817200b
acf1ac7
201b2a5
ed10b6f
5952c48
8c3d52c
a80589f
1272b0b
0d640bf
4c64b0d
6445e26
262c6fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ import { | |
| AutoEnvAttributes, | ||
| base64UrlEncode, | ||
| BasicLogger, | ||
| cancelableTimedPromise, | ||
| Configuration, | ||
| Encoding, | ||
| FlagManager, | ||
|
|
@@ -24,14 +25,24 @@ import { BrowserIdentifyOptions as LDIdentifyOptions } from './BrowserIdentifyOp | |
| import { registerStateDetection } from './BrowserStateDetector'; | ||
| import GoalManager from './goals/GoalManager'; | ||
| import { Goal, isClick } from './goals/Goals'; | ||
| import { LDClient } from './LDClient'; | ||
| import { | ||
| LDClient, | ||
| LDWaitForInitializationComplete, | ||
| LDWaitForInitializationFailed, | ||
| LDWaitForInitializationOptions, | ||
| LDWaitForInitializationResult, | ||
| LDWaitForInitializationTimeout, | ||
| } from './LDClient'; | ||
| import { LDPlugin } from './LDPlugin'; | ||
| import validateBrowserOptions, { BrowserOptions, filterToBaseOptionsWithDefaults } from './options'; | ||
| import BrowserPlatform from './platform/BrowserPlatform'; | ||
|
|
||
| class BrowserClientImpl extends LDClientImpl { | ||
| private readonly _goalManager?: GoalManager; | ||
| private readonly _plugins?: LDPlugin[]; | ||
| private _initializedPromise?: Promise<LDWaitForInitializationResult>; | ||
| private _initResolve?: (result: LDWaitForInitializationResult) => void; | ||
| private _isInitialized: boolean = false; | ||
|
|
||
| constructor( | ||
| clientSideId: string, | ||
|
|
@@ -212,10 +223,78 @@ class BrowserClientImpl extends LDClientImpl { | |
| identifyOptionsWithUpdatedDefaults.sheddable = true; | ||
| } | ||
| const res = await super.identifyResult(context, identifyOptionsWithUpdatedDefaults); | ||
| if (res.status === 'completed') { | ||
| this._isInitialized = true; | ||
| this._initResolve?.({ status: 'complete' }); | ||
| } else if (res.status === 'error') { | ||
| this._initResolve?.({ status: 'failed', error: res.error }); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Stale initialization state persists across multiple identify callsThe Additional Locations (1) |
||
|
|
||
| this._goalManager?.startTracking(); | ||
| return res; | ||
| } | ||
|
|
||
| waitForInitialization( | ||
| options?: LDWaitForInitializationOptions, | ||
| ): Promise<LDWaitForInitializationResult> { | ||
| const timeout = options?.timeout ?? 5; | ||
|
|
||
| // An initialization promise is only created if someone is going to use that promise. | ||
| // If we always created an initialization promise, and there was no call waitForInitialization | ||
| // by the time the promise was rejected, then that would result in an unhandled promise | ||
| // rejection. | ||
|
|
||
| // It waitForInitialization was previously called, then we can use that promise even if it has | ||
| // been resolved or rejected. | ||
| if (this._initializedPromise) { | ||
| return this._promiseWithTimeout(this._initializedPromise, timeout); | ||
| } | ||
|
|
||
| if (this._isInitialized) { | ||
| return Promise.resolve({ | ||
| status: 'complete', | ||
| }); | ||
| } | ||
cursor[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if (!this._initializedPromise) { | ||
| this._initializedPromise = new Promise((resolve) => { | ||
| this._initResolve = resolve; | ||
| }); | ||
| } | ||
|
|
||
| return this._promiseWithTimeout(this._initializedPromise, timeout); | ||
| } | ||
|
|
||
| /** | ||
| * Apply a timeout promise to a base promise. This is for use with waitForInitialization. | ||
| * | ||
| * @param basePromise The promise to race against a timeout. | ||
| * @param timeout The timeout in seconds. | ||
| * @param logger A logger to log when the timeout expires. | ||
| * @returns | ||
| */ | ||
| private _promiseWithTimeout( | ||
| basePromise: Promise<LDWaitForInitializationResult>, | ||
| timeout: number, | ||
| ): Promise<LDWaitForInitializationResult> { | ||
| const cancelableTimeout = cancelableTimedPromise(timeout, 'waitForInitialization'); | ||
| return Promise.race([ | ||
| basePromise.then((res: LDWaitForInitializationResult) => { | ||
| cancelableTimeout.cancel(); | ||
| return res; | ||
| }), | ||
| cancelableTimeout.promise | ||
| // If the promise resolves without error, then the initialization completed successfully. | ||
| // NOTE: this should never return as the resolution would only be triggered by the basePromise | ||
| // being resolved. | ||
| .then(() => ({ status: 'complete' }) as LDWaitForInitializationComplete) | ||
| .catch(() => ({ status: 'timeout' }) as LDWaitForInitializationTimeout), | ||
| ]).catch((reason) => { | ||
| this.logger?.error(reason.message); | ||
| return { status: 'failed', error: reason as Error } as LDWaitForInitializationFailed; | ||
| }); | ||
| } | ||
|
|
||
| setStreaming(streaming?: boolean): void { | ||
| // With FDv2 we may want to consider if we support connection mode directly. | ||
| // Maybe with an extension to connection mode for 'automatic'. | ||
|
|
@@ -282,6 +361,8 @@ export function makeClient( | |
| close: () => impl.close(), | ||
| allFlags: () => impl.allFlags(), | ||
| addHook: (hook: Hook) => impl.addHook(hook), | ||
| waitForInitialization: (waitOptions?: LDWaitForInitializationOptions) => | ||
| impl.waitForInitialization(waitOptions), | ||
| logger: impl.logger, | ||
| }; | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.