@@ -16,6 +16,17 @@ import type { Hook } from './hooks';
1616import type { Provider } from './provider' ;
1717import { NOOP_PROVIDER , ProviderStatus } from './provider' ;
1818
19+ interface ProviderOptions {
20+ /**
21+ * If provided, will be used to check if the current context is valid during initialization and context changes.
22+ * When calling `setProvider`, returning `false` will skip provider initialization. Throwing will move the provider to the ERROR state.
23+ * When calling `setProviderAndWait`, returning `false` will skip provider initialization. Throwing will reject the promise.
24+ * TODO: When calling `setContext`, returning `false` will skip provider context change handling. Throwing will move the provider to the ERROR state.
25+ * @param context The evaluation context to validate.
26+ */
27+ validateContext ?: ( context : EvaluationContext ) => boolean ;
28+ }
29+
1930// use a symbol as a key for the global singleton
2031const GLOBAL_OPENFEATURE_API_KEY = Symbol . for ( '@openfeature/web-sdk/api' ) ;
2132
@@ -86,10 +97,11 @@ export class OpenFeatureAPI
8697 * Setting a provider supersedes the current provider used in new and existing unbound clients.
8798 * @param {Provider } provider The provider responsible for flag evaluations.
8899 * @param {EvaluationContext } context The evaluation context to use for flag evaluations.
100+ * @param {ProviderOptions } [options] Options for setting the provider.
89101 * @returns {Promise<void> }
90102 * @throws {Error } If the provider throws an exception during initialization.
91103 */
92- setProviderAndWait ( provider : Provider , context : EvaluationContext ) : Promise < void > ;
104+ setProviderAndWait ( provider : Provider , context : EvaluationContext , options ?: ProviderOptions ) : Promise < void > ;
93105 /**
94106 * Sets the provider that OpenFeature will use for flag evaluations on clients bound to the same domain.
95107 * A promise is returned that resolves when the provider is ready.
@@ -107,24 +119,41 @@ export class OpenFeatureAPI
107119 * @param {string } domain The name to identify the client
108120 * @param {Provider } provider The provider responsible for flag evaluations.
109121 * @param {EvaluationContext } context The evaluation context to use for flag evaluations.
122+ * @param {ProviderOptions } [options] Options for setting the provider.
110123 * @returns {Promise<void> }
111124 * @throws {Error } If the provider throws an exception during initialization.
112125 */
113- setProviderAndWait ( domain : string , provider : Provider , context : EvaluationContext ) : Promise < void > ;
126+ setProviderAndWait ( domain : string , provider : Provider , context : EvaluationContext , options ?: ProviderOptions ) : Promise < void > ;
114127 async setProviderAndWait (
115128 clientOrProvider ?: string | Provider ,
116129 providerContextOrUndefined ?: Provider | EvaluationContext ,
117- contextOrUndefined ?: EvaluationContext ,
130+ contextOptionsOrUndefined ?: EvaluationContext | ProviderOptions ,
131+ optionsOrUndefined ?: ProviderOptions ,
118132 ) : Promise < void > {
119133 const domain = stringOrUndefined ( clientOrProvider ) ;
120134 const provider = domain
121135 ? objectOrUndefined < Provider > ( providerContextOrUndefined )
122136 : objectOrUndefined < Provider > ( clientOrProvider ) ;
123137 const context = domain
124- ? objectOrUndefined < EvaluationContext > ( contextOrUndefined )
138+ ? objectOrUndefined < EvaluationContext > ( contextOptionsOrUndefined )
125139 : objectOrUndefined < EvaluationContext > ( providerContextOrUndefined ) ;
140+ const options = domain
141+ ? objectOrUndefined < ProviderOptions > ( optionsOrUndefined )
142+ : objectOrUndefined < ProviderOptions > ( contextOptionsOrUndefined ) ;
126143
144+ let skipInitialization = false ;
127145 if ( context ) {
146+ // validate the context to decide if we should initialize the provider with it.
147+ if ( typeof options ?. validateContext === 'function' ) {
148+ // allow any error to propagate here to reject the promise.
149+ skipInitialization = ! options . validateContext ( context ) ;
150+ if ( skipInitialization ) {
151+ this . _logger . debug (
152+ `Skipping provider initialization during setProviderAndWait for domain '${ domain ?? 'default' } ' due to validateContext returning false.` ,
153+ ) ;
154+ }
155+ }
156+
128157 // synonymously setting context prior to provider initialization.
129158 // No context change event will be emitted.
130159 if ( domain ) {
@@ -134,7 +163,7 @@ export class OpenFeatureAPI
134163 }
135164 }
136165
137- await this . setAwaitableProvider ( domain , provider ) ;
166+ await this . setAwaitableProvider ( domain , provider , skipInitialization ) ;
138167 }
139168
140169 /**
@@ -150,10 +179,11 @@ export class OpenFeatureAPI
150179 * This provider will be used by domainless clients and clients associated with domains to which no provider is bound.
151180 * Setting a provider supersedes the current provider used in new and existing unbound clients.
152181 * @param {Provider } provider The provider responsible for flag evaluations.
153- * @param context {EvaluationContext} The evaluation context to use for flag evaluations.
182+ * @param {EvaluationContext } context The evaluation context to use for flag evaluations. Ignored if 'delayed' option is set.
183+ * @param {ProviderOptions } [options] Options for setting the provider.
154184 * @returns {this } OpenFeature API
155185 */
156- setProvider ( provider : Provider , context : EvaluationContext ) : this;
186+ setProvider ( provider : Provider , context : EvaluationContext , options ?: ProviderOptions ) : this;
157187 /**
158188 * Sets the provider for flag evaluations of providers with the given name.
159189 * Setting a provider supersedes the current provider used in new and existing clients bound to the same domain.
@@ -167,24 +197,50 @@ export class OpenFeatureAPI
167197 * Setting a provider supersedes the current provider used in new and existing clients bound to the same domain.
168198 * @param {string } domain The name to identify the client
169199 * @param {Provider } provider The provider responsible for flag evaluations.
170- * @param context {EvaluationContext} The evaluation context to use for flag evaluations.
200+ * @param {EvaluationContext } context The evaluation context to use for flag evaluations. Ignored if 'delayed' option is set.
201+ * @param {ProviderOptions } [options] Options for setting the provider.
171202 * @returns {this } OpenFeature API
172203 */
173- setProvider ( domain : string , provider : Provider , context : EvaluationContext ) : this;
204+ setProvider ( domain : string , provider : Provider , context : EvaluationContext , options ?: ProviderOptions ) : this;
174205 setProvider (
175206 domainOrProvider ?: string | Provider ,
176207 providerContextOrUndefined ?: Provider | EvaluationContext ,
177- contextOrUndefined ?: EvaluationContext ,
208+ contextOptionsOrUndefined ?: EvaluationContext | ProviderOptions ,
209+ optionsOrUndefined ?: ProviderOptions ,
178210 ) : this {
179211 const domain = stringOrUndefined ( domainOrProvider ) ;
180212 const provider = domain
181213 ? objectOrUndefined < Provider > ( providerContextOrUndefined )
182214 : objectOrUndefined < Provider > ( domainOrProvider ) ;
183215 const context = domain
184- ? objectOrUndefined < EvaluationContext > ( contextOrUndefined )
216+ ? objectOrUndefined < EvaluationContext > ( contextOptionsOrUndefined )
185217 : objectOrUndefined < EvaluationContext > ( providerContextOrUndefined ) ;
218+ const options = domain
219+ ? objectOrUndefined < ProviderOptions > ( optionsOrUndefined )
220+ : objectOrUndefined < ProviderOptions > ( contextOptionsOrUndefined ) ;
186221
222+ let skipInitialization = false ;
223+ let validateContextError : unknown ;
187224 if ( context ) {
225+ // validate the context to decide if we should initialize the provider with it.
226+ if ( typeof options ?. validateContext === 'function' ) {
227+ try {
228+ skipInitialization = ! options . validateContext ( context ) ;
229+ if ( skipInitialization ) {
230+ this . _logger . debug (
231+ `Skipping provider initialization during setProvider for domain '${ domain ?? 'default' } ' due to validateContext returning false.` ,
232+ ) ;
233+ }
234+ } catch ( err ) {
235+ // capture the error to move the provider to ERROR state after setting it.
236+ validateContextError = err ;
237+ skipInitialization = true ;
238+ this . _logger . debug (
239+ `Skipping provider initialization during setProvider for domain '${ domain ?? 'default' } ' due to validateContext throwing an error.` ,
240+ ) ;
241+ }
242+ }
243+
188244 // synonymously setting context prior to provider initialization.
189245 // No context change event will be emitted.
190246 if ( domain ) {
@@ -194,7 +250,34 @@ export class OpenFeatureAPI
194250 }
195251 }
196252
197- const maybePromise = this . setAwaitableProvider ( domain , provider ) ;
253+ const maybePromise = this . setAwaitableProvider ( domain , provider , skipInitialization ) ;
254+
255+ // If there was a validation error with the context, move the newly created provider to ERROR state.
256+ // We know we've skipped initialization if this happens, so no need to worry about the promise changing the state later.
257+ if ( validateContextError ) {
258+ const wrapper = domain
259+ ? this . _domainScopedProviders . get ( domain )
260+ : this . _defaultProvider ;
261+ if ( wrapper ) {
262+ wrapper . status = this . _statusEnumType . ERROR ;
263+ const providerName = wrapper . provider ?. metadata ?. name || 'unnamed-provider' ;
264+ this . getAssociatedEventEmitters ( domain ) . forEach ( ( emitter ) => {
265+ emitter ?. emit ( ProviderEvents . Error , {
266+ clientName : domain ,
267+ domain,
268+ providerName,
269+ message : `Error validating context during setProvider: ${ validateContextError instanceof Error ? validateContextError . message : String ( validateContextError ) } ` ,
270+ } ) ;
271+ } ) ;
272+ this . _apiEmitter ?. emit ( ProviderEvents . Error , {
273+ clientName : domain ,
274+ domain,
275+ providerName,
276+ message : `Error validating context during setProvider: ${ validateContextError instanceof Error ? validateContextError . message : String ( validateContextError ) } ` ,
277+ } ) ;
278+ this . _logger . error ( 'Error validating context during setProvider:' , validateContextError ) ;
279+ }
280+ }
198281
199282 // The setProvider method doesn't return a promise so we need to catch and
200283 // log any errors that occur during provider initialization to avoid having
@@ -249,6 +332,8 @@ export class OpenFeatureAPI
249332 const domain = stringOrUndefined ( domainOrContext ) ;
250333 const context = objectOrUndefined < T > ( domainOrContext ) ?? objectOrUndefined ( contextOrUndefined ) ?? { } ;
251334
335+ // TODO: We need to store and call `validateContext` here if provided in `setProvider` options
336+
252337 if ( domain ) {
253338 const wrapper = this . _domainScopedProviders . get ( domain ) ;
254339 if ( wrapper ) {
0 commit comments