@@ -8,25 +8,25 @@ import { IFeatureFlagProvider } from "./featureProvider.js";
88import { TargetingFilter } from "./filter/TargetingFilter.js" ;
99import { Variant } from "./variant/Variant.js" ;
1010import { IFeatureManager } from "./IFeatureManager.js" ;
11- import { ITargetingContext } from "./common/ITargetingContext .js" ;
11+ import { ITargetingContext , ITargetingContextAccessor } from "./common/targetingContext .js" ;
1212import { isTargetedGroup , isTargetedPercentile , isTargetedUser } from "./common/targetingEvaluator.js" ;
1313
1414export class FeatureManager implements IFeatureManager {
15- #provider: IFeatureFlagProvider ;
16- #featureFilters: Map < string , IFeatureFilter > = new Map ( ) ;
17- #onFeatureEvaluated?: ( event : EvaluationResult ) => void ;
15+ readonly #provider: IFeatureFlagProvider ;
16+ readonly #featureFilters: Map < string , IFeatureFilter > = new Map ( ) ;
17+ readonly #onFeatureEvaluated?: ( event : EvaluationResult ) => void ;
18+ readonly #targetingContextAccessor?: ITargetingContextAccessor ;
1819
1920 constructor ( provider : IFeatureFlagProvider , options ?: FeatureManagerOptions ) {
2021 this . #provider = provider ;
22+ this . #onFeatureEvaluated = options ?. onFeatureEvaluated ;
23+ this . #targetingContextAccessor = options ?. targetingContextAccessor ;
2124
22- const builtinFilters = [ new TimeWindowFilter ( ) , new TargetingFilter ( ) ] ;
23-
25+ const builtinFilters = [ new TimeWindowFilter ( ) , new TargetingFilter ( options ?. targetingContextAccessor ) ] ;
2426 // If a custom filter shares a name with an existing filter, the custom filter overrides the existing one.
2527 for ( const filter of [ ...builtinFilters , ...( options ?. customFilters ?? [ ] ) ] ) {
2628 this . #featureFilters. set ( filter . name , filter ) ;
2729 }
28-
29- this . #onFeatureEvaluated = options ?. onFeatureEvaluated ;
3030 }
3131
3232 async listFeatureNames ( ) : Promise < string [ ] > {
@@ -78,7 +78,7 @@ export class FeatureManager implements IFeatureManager {
7878 return { variant : undefined , reason : VariantAssignmentReason . None } ;
7979 }
8080
81- async #isEnabled( featureFlag : FeatureFlag , context ?: unknown ) : Promise < boolean > {
81+ async #isEnabled( featureFlag : FeatureFlag , appContext ?: unknown ) : Promise < boolean > {
8282 if ( featureFlag . enabled !== true ) {
8383 // If the feature is not explicitly enabled, then it is disabled by default.
8484 return false ;
@@ -106,7 +106,7 @@ export class FeatureManager implements IFeatureManager {
106106 console . warn ( `Feature filter ${ clientFilter . name } is not found.` ) ;
107107 return false ;
108108 }
109- if ( await matchedFeatureFilter . evaluate ( contextWithFeatureName , context ) === shortCircuitEvaluationResult ) {
109+ if ( await matchedFeatureFilter . evaluate ( contextWithFeatureName , appContext ) === shortCircuitEvaluationResult ) {
110110 return shortCircuitEvaluationResult ;
111111 }
112112 }
@@ -115,7 +115,7 @@ export class FeatureManager implements IFeatureManager {
115115 return ! shortCircuitEvaluationResult ;
116116 }
117117
118- async #evaluateFeature( featureName : string , context : unknown ) : Promise < EvaluationResult > {
118+ async #evaluateFeature( featureName : string , appContext : unknown ) : Promise < EvaluationResult > {
119119 const featureFlag = await this . #provider. getFeatureFlag ( featureName ) ;
120120 const result = new EvaluationResult ( featureFlag ) ;
121121
@@ -128,9 +128,10 @@ export class FeatureManager implements IFeatureManager {
128128 validateFeatureFlagFormat ( featureFlag ) ;
129129
130130 // Evaluate if the feature is enabled.
131- result . enabled = await this . #isEnabled( featureFlag , context ) ;
131+ result . enabled = await this . #isEnabled( featureFlag , appContext ) ;
132132
133- const targetingContext = context as ITargetingContext ;
133+ // Get targeting context from the app context or the targeting context accessor
134+ const targetingContext = this . #getTargetingContext( appContext ) ;
134135 result . targetingId = targetingContext ?. userId ;
135136
136137 // Determine Variant
@@ -151,7 +152,7 @@ export class FeatureManager implements IFeatureManager {
151152 }
152153 } else {
153154 // enabled, assign based on allocation
154- if ( context !== undefined && featureFlag . allocation !== undefined ) {
155+ if ( targetingContext !== undefined && featureFlag . allocation !== undefined ) {
155156 const variantAndReason = await this . #assignVariant( featureFlag , targetingContext ) ;
156157 variantDef = variantAndReason . variant ;
157158 reason = variantAndReason . reason ;
@@ -189,6 +190,16 @@ export class FeatureManager implements IFeatureManager {
189190
190191 return result ;
191192 }
193+
194+ #getTargetingContext( context : unknown ) : ITargetingContext | undefined {
195+ let targetingContext : ITargetingContext | undefined = context as ITargetingContext ;
196+ if ( targetingContext ?. userId === undefined &&
197+ targetingContext ?. groups === undefined &&
198+ this . #targetingContextAccessor !== undefined ) {
199+ targetingContext = this . #targetingContextAccessor. getTargetingContext ( ) ;
200+ }
201+ return targetingContext ;
202+ }
192203}
193204
194205export interface FeatureManagerOptions {
@@ -202,6 +213,11 @@ export interface FeatureManagerOptions {
202213 * The callback function is called only when telemetry is enabled for the feature flag.
203214 */
204215 onFeatureEvaluated ?: ( event : EvaluationResult ) => void ;
216+
217+ /**
218+ * The accessor function that provides the @see ITargetingContext for targeting evaluation.
219+ */
220+ targetingContextAccessor ?: ITargetingContextAccessor ;
205221}
206222
207223export class EvaluationResult {
0 commit comments