22 AutoEnvAttributes ,
33 base64UrlEncode ,
44 BasicLogger ,
5+ cancelableTimedPromise ,
56 Configuration ,
67 Encoding ,
78 FlagManager ,
@@ -15,6 +16,7 @@ import {
1516 LDHeaders ,
1617 LDIdentifyResult ,
1718 LDPluginEnvironmentMetadata ,
19+ LDTimeoutError ,
1820 Platform ,
1921} from '@launchdarkly/js-client-sdk-common' ;
2022
@@ -32,6 +34,9 @@ import BrowserPlatform from './platform/BrowserPlatform';
3234class BrowserClientImpl extends LDClientImpl {
3335 private readonly _goalManager ?: GoalManager ;
3436 private readonly _plugins ?: LDPlugin [ ] ;
37+ private _initializedPromise ?: Promise < void > ;
38+ private _initResolve ?: ( ) => void ;
39+ private _isInitialized : boolean = false ;
3540
3641 constructor (
3742 clientSideId : string ,
@@ -212,10 +217,60 @@ class BrowserClientImpl extends LDClientImpl {
212217 identifyOptionsWithUpdatedDefaults . sheddable = true ;
213218 }
214219 const res = await super . identifyResult ( context , identifyOptionsWithUpdatedDefaults ) ;
220+ if ( res . status === 'completed' ) {
221+ this . _isInitialized = true ;
222+ this . _initResolve ?.( ) ;
223+ }
215224 this . _goalManager ?. startTracking ( ) ;
216225 return res ;
217226 }
218227
228+ waitForInitialization ( timeout : number = 5 ) : Promise < void > {
229+ // An initialization promise is only created if someone is going to use that promise.
230+ // If we always created an initialization promise, and there was no call waitForInitialization
231+ // by the time the promise was rejected, then that would result in an unhandled promise
232+ // rejection.
233+
234+ // It waitForInitialization was previously called, then we can use that promise even if it has
235+ // been resolved or rejected.
236+ if ( this . _initializedPromise ) {
237+ return this . _promiseWithTimeout ( this . _initializedPromise , timeout ) ;
238+ }
239+
240+ if ( this . _isInitialized ) {
241+ return Promise . resolve ( ) ;
242+ }
243+
244+ if ( ! this . _initializedPromise ) {
245+ this . _initializedPromise = new Promise ( ( resolve ) => {
246+ this . _initResolve = resolve ;
247+ } ) ;
248+ }
249+
250+ return this . _promiseWithTimeout ( this . _initializedPromise , timeout ) ;
251+ }
252+
253+ /**
254+ * Apply a timeout promise to a base promise. This is for use with waitForInitialization.
255+ *
256+ * @param basePromise The promise to race against a timeout.
257+ * @param timeout The timeout in seconds.
258+ * @param logger A logger to log when the timeout expires.
259+ * @returns
260+ */
261+ private _promiseWithTimeout ( basePromise : Promise < void > , timeout : number ) : Promise < void > {
262+ const cancelableTimeout = cancelableTimedPromise ( timeout , 'waitForInitialization' ) ;
263+ return Promise . race ( [
264+ basePromise . then ( ( ) => cancelableTimeout . cancel ( ) ) ,
265+ cancelableTimeout . promise ,
266+ ] ) . catch ( ( reason ) => {
267+ if ( reason instanceof LDTimeoutError ) {
268+ this . logger ?. error ( reason . message ) ;
269+ }
270+ throw reason ;
271+ } ) ;
272+ }
273+
219274 setStreaming ( streaming ?: boolean ) : void {
220275 // With FDv2 we may want to consider if we support connection mode directly.
221276 // Maybe with an extension to connection mode for 'automatic'.
@@ -282,6 +337,7 @@ export function makeClient(
282337 close : ( ) => impl . close ( ) ,
283338 allFlags : ( ) => impl . allFlags ( ) ,
284339 addHook : ( hook : Hook ) => impl . addHook ( hook ) ,
340+ waitForInitialization : ( timeout : number ) => impl . waitForInitialization ( timeout ) ,
285341 logger : impl . logger ,
286342 } ;
287343
0 commit comments