@@ -48,19 +48,21 @@ import { getInspectorHook } from './inspection/getInspectorHook';
4848import InspectorManager from './inspection/InspectorManager' ;
4949import LDEmitter , { EventName } from './LDEmitter' ;
5050import { createPluginEnvironmentMetadata } from './plugins/createPluginEnvironmentMetadata' ;
51+ import { ActiveContextTracker , createActiveContextTracker } from './context/createActiveContextTracker' ;
52+ import { ItemDescriptor } from './flag-manager/ItemDescriptor' ;
5153
5254const { ClientMessages, ErrorKinds } = internal ;
5355
5456const DEFAULT_IDENTIFY_TIMEOUT_SECONDS = 5 ;
5557
5658export default class LDClientImpl implements LDClient , LDClientIdentifyResult {
5759 private readonly _config : Configuration ;
58- private _uncheckedContext ?: LDContext ;
59- private _checkedContext ?: Context ;
6060 private readonly _diagnosticsManager ?: internal . DiagnosticsManager ;
6161 private _eventProcessor ?: internal . EventProcessor ;
6262 readonly logger : LDLogger ;
6363
64+ private _activeContextTracker : ActiveContextTracker = createActiveContextTracker ( )
65+
6466 private readonly _highTimeoutThreshold : number = 15 ;
6567
6668 private _eventFactoryDefault = new EventFactory ( false ) ;
@@ -200,27 +202,20 @@ export default class LDClientImpl implements LDClient, LDClientIdentifyResult {
200202 // code. We are returned the unchecked context so that if a consumer identifies with an invalid context
201203 // and then calls getContext, they get back the same context they provided, without any assertion about
202204 // validity.
203- return this . _uncheckedContext ? clone < LDContext > ( this . _uncheckedContext ) : undefined ;
205+ return this . _activeContextTracker . hasContext ( ) ? clone < LDContext > ( this . _activeContextTracker . getPristineContext ( ) ) : undefined ;
204206 }
205207
206208 protected getInternalContext ( ) : Context | undefined {
207- return this . _checkedContext ;
209+ return this . _activeContextTracker . getContext ( ) ;
208210 }
209211
210- private _createIdentifyPromise ( ) : {
211- identifyPromise : Promise < void > ;
212- identifyResolve : ( ) => void ;
213- identifyReject : ( err : Error ) => void ;
214- } {
215- let res : any ;
216- let rej : any ;
217-
218- const basePromise = new Promise < void > ( ( resolve , reject ) => {
219- res = resolve ;
220- rej = reject ;
221- } ) ;
222-
223- return { identifyPromise : basePromise , identifyResolve : res , identifyReject : rej } ;
212+ /**
213+ * Preset flags are used to set the flags before the client is initialized. This is useful for
214+ * when client has precached flags that are ready to evaluate without full initialization.
215+ * @param newFlags - The flags to preset.
216+ */
217+ protected presetFlags ( newFlags : { [ key : string ] : ItemDescriptor } ) {
218+ this . _flagManager . presetFlags ( newFlags ) ;
224219 }
225220
226221 /**
@@ -307,15 +302,14 @@ export default class LDClientImpl implements LDClient, LDClientIdentifyResult {
307302 this . emitter . emit ( 'error' , context , error ) ;
308303 return Promise . reject ( error ) ;
309304 }
310- this . _uncheckedContext = context ;
311- this . _checkedContext = checkedContext ;
305+ this . _activeContextTracker . set ( context , checkedContext )
312306
313307 this . _eventProcessor ?. sendEvent (
314- this . _eventFactoryDefault . identifyEvent ( this . _checkedContext ) ,
308+ this . _eventFactoryDefault . identifyEvent ( checkedContext ) ,
315309 ) ;
316310 const { identifyPromise, identifyResolve, identifyReject } =
317- this . _createIdentifyPromise ( ) ;
318- this . logger . debug ( `Identifying ${ JSON . stringify ( this . _checkedContext ) } ` ) ;
311+ this . _activeContextTracker . newIdentificationPromise ( ) ;
312+ this . logger . debug ( `Identifying ${ JSON . stringify ( checkedContext ) } ` ) ;
319313
320314 await this . dataManager . identify (
321315 identifyResolve ,
@@ -370,7 +364,7 @@ export default class LDClientImpl implements LDClient, LDClientIdentifyResult {
370364 }
371365
372366 track ( key : string , data ?: any , metricValue ?: number ) : void {
373- if ( ! this . _checkedContext || ! this . _checkedContext . valid ) {
367+ if ( ! this . _activeContextTracker . hasValidContext ( ) ) {
374368 this . logger . warn ( ClientMessages . MissingContextKeyNoEvent ) ;
375369 return ;
376370 }
@@ -382,14 +376,14 @@ export default class LDClientImpl implements LDClient, LDClientIdentifyResult {
382376
383377 this . _eventProcessor ?. sendEvent (
384378 this . _config . trackEventModifier (
385- this . _eventFactoryDefault . customEvent ( key , this . _checkedContext ! , data , metricValue ) ,
379+ this . _eventFactoryDefault . customEvent ( key , this . _activeContextTracker . getContext ( ) ! , data , metricValue ) ,
386380 ) ,
387381 ) ;
388382
389383 this . _hookRunner . afterTrack ( {
390384 key,
391385 // The context is pre-checked above, so we know it can be unwrapped.
392- context : this . _uncheckedContext ! ,
386+ context : this . _activeContextTracker . getPristineContext ( ) ! ,
393387 data,
394388 metricValue,
395389 } ) ;
@@ -401,21 +395,28 @@ export default class LDClientImpl implements LDClient, LDClientIdentifyResult {
401395 eventFactory : EventFactory ,
402396 typeChecker ?: ( value : any ) => [ boolean , string ] ,
403397 ) : LDEvaluationDetail {
404- if ( ! this . _uncheckedContext ) {
405- this . logger . debug ( ClientMessages . MissingContextKeyNoEvent ) ;
406- return createErrorEvaluationDetail ( ErrorKinds . UserNotSpecified , defaultValue ) ;
398+ // We are letting evaulations happen without a context. The main case for this
399+ // is when cached data is loaded, but the client is not fully initialized. In this
400+ // case, we will write out a warning for each evaluation attempt.
401+
402+ // NOTE: we will be changing this behavior soon once we have a tracker on the
403+ // client initialization state.
404+ const hasContext = this . _activeContextTracker . hasContext ( )
405+ if ( ! hasContext ) {
406+ this . logger ?. warn ( 'Flag evaluation called before client is fully initialized, data from this evaulation could be stale.' )
407407 }
408408
409- const evalContext = Context . fromLDContext ( this . _uncheckedContext ) ;
409+ const evalContext = this . _activeContextTracker . getContext ( ) ! ;
410410 const foundItem = this . _flagManager . get ( flagKey ) ;
411411
412412 if ( foundItem === undefined || foundItem . flag . deleted ) {
413413 const defVal = defaultValue ?? null ;
414414 const error = new LDClientError (
415415 `Unknown feature flag "${ flagKey } "; returning default value ${ defVal } .` ,
416416 ) ;
417- this . emitter . emit ( 'error' , this . _uncheckedContext , error ) ;
418- this . _eventProcessor ?. sendEvent (
417+
418+ this . emitter . emit ( 'error' , this . _activeContextTracker . getPristineContext ( ) , error ) ;
419+ hasContext && this . _eventProcessor ?. sendEvent (
419420 this . _eventFactoryDefault . unknownFlagEvent ( flagKey , defVal , evalContext ) ,
420421 ) ;
421422 return createErrorEvaluationDetail ( ErrorKinds . FlagNotFound , defaultValue ) ;
@@ -426,7 +427,7 @@ export default class LDClientImpl implements LDClient, LDClientIdentifyResult {
426427 if ( typeChecker ) {
427428 const [ matched , type ] = typeChecker ( value ) ;
428429 if ( ! matched ) {
429- this . _eventProcessor ?. sendEvent (
430+ hasContext && this . _eventProcessor ?. sendEvent (
430431 eventFactory . evalEventClient (
431432 flagKey ,
432433 defaultValue , // track default value on type errors
@@ -439,7 +440,7 @@ export default class LDClientImpl implements LDClient, LDClientIdentifyResult {
439440 const error = new LDClientError (
440441 `Wrong type "${ type } " for feature flag "${ flagKey } "; returning default value` ,
441442 ) ;
442- this . emitter . emit ( 'error' , this . _uncheckedContext , error ) ;
443+ this . emitter . emit ( 'error' , this . _activeContextTracker . getPristineContext ( ) , error ) ;
443444 return createErrorEvaluationDetail ( ErrorKinds . WrongType , defaultValue ) ;
444445 }
445446 }
@@ -453,7 +454,7 @@ export default class LDClientImpl implements LDClient, LDClientIdentifyResult {
453454 prerequisites ?. forEach ( ( prereqKey ) => {
454455 this . _variationInternal ( prereqKey , undefined , this . _eventFactoryDefault ) ;
455456 } ) ;
456- this . _eventProcessor ?. sendEvent (
457+ hasContext && this . _eventProcessor ?. sendEvent (
457458 eventFactory . evalEventClient (
458459 flagKey ,
459460 value ,
@@ -469,14 +470,14 @@ export default class LDClientImpl implements LDClient, LDClientIdentifyResult {
469470 variation ( flagKey : string , defaultValue ?: LDFlagValue ) : LDFlagValue {
470471 const { value } = this . _hookRunner . withEvaluation (
471472 flagKey ,
472- this . _uncheckedContext ,
473+ this . _activeContextTracker . getPristineContext ( ) ,
473474 defaultValue ,
474475 ( ) => this . _variationInternal ( flagKey , defaultValue , this . _eventFactoryDefault ) ,
475476 ) ;
476477 return value ;
477478 }
478479 variationDetail ( flagKey : string , defaultValue ?: LDFlagValue ) : LDEvaluationDetail {
479- return this . _hookRunner . withEvaluation ( flagKey , this . _uncheckedContext , defaultValue , ( ) =>
480+ return this . _hookRunner . withEvaluation ( flagKey , this . _activeContextTracker . getPristineContext ( ) , defaultValue , ( ) =>
480481 this . _variationInternal ( flagKey , defaultValue , this . _eventFactoryWithReasons ) ,
481482 ) ;
482483 }
@@ -487,7 +488,7 @@ export default class LDClientImpl implements LDClient, LDClientIdentifyResult {
487488 eventFactory : EventFactory ,
488489 typeChecker : ( value : unknown ) => [ boolean , string ] ,
489490 ) : LDEvaluationDetailTyped < T > {
490- return this . _hookRunner . withEvaluation ( key , this . _uncheckedContext , defaultValue , ( ) =>
491+ return this . _hookRunner . withEvaluation ( key , this . _activeContextTracker . getPristineContext ( ) , defaultValue , ( ) =>
491492 this . _variationInternal ( key , defaultValue , eventFactory , typeChecker ) ,
492493 ) ;
493494 }
0 commit comments