55
66import { version as PACKAGE_VERSION } from '../package.json' ;
77
8- import { ExperimentConfig , Defaults , Source } from './config' ;
8+ import { ExperimentConfig , Defaults } from './config' ;
99import { LocalStorage } from './storage/localStorage' ;
1010import { FetchHttpClient } from './transport/http' ;
11- import { ExposureEvent } from './types/analytics' ;
11+ import { exposureEvent } from './types/analytics' ;
1212import { Client } from './types/client' ;
1313import { ExperimentUserProvider } from './types/provider' ;
14+ import { isFallback , Source , VariantSource } from './types/source' ;
1415import { Storage } from './types/storage' ;
1516import { HttpClient } from './types/transport' ;
1617import { ExperimentUser } from './types/user' ;
1718import { Variant , Variants } from './types/variant' ;
19+ import { isNullOrUndefined } from './util' ;
1820import { Backoff } from './util/backoff' ;
1921import { urlSafeBase64Encode } from './util/base64' ;
2022import { randomString } from './util/randomstring' ;
@@ -114,9 +116,9 @@ export class ExperimentClient implements Client {
114116 * Access the variant from {@link Source}, falling back on the given
115117 * fallback, then the configured fallbackVariant.
116118 *
117- * If an {@link ExperimentAnalyticsProvider} is configured, this function will
118- * call the provider with an {@link ExposureEvent}. The exposure event does
119- * not count towards your event volume within Amplitude.
119+ * If an {@link ExperimentAnalyticsProvider} is configured and trackExposure is
120+ * true, this function will call the provider with an {@link ExposureEvent}.
121+ * The exposure event does not count towards your event volume within Amplitude.
120122 *
121123 * @param key The key to get the variant for.
122124 * @param fallback The highest priority fallback.
@@ -127,24 +129,100 @@ export class ExperimentClient implements Client {
127129 if ( ! this . apiKey ) {
128130 return { value : undefined } ;
129131 }
130- const sourceVariant = this . sourceVariants ( ) [ key ] ;
131- if ( sourceVariant ?. value ) {
132- this . config . analyticsProvider ?. track (
133- new ExposureEvent (
134- this . addContext ( this . getUser ( ) ) ,
135- key ,
136- this . convertVariant ( sourceVariant ) ,
137- ) ,
132+ const { source, variant } = this . variantAndSource ( key , fallback ) ;
133+
134+ if ( isFallback ( source ) || ! variant ?. value ) {
135+ // fallbacks indicate not being allocated into an experiment, so
136+ // we can unset the property
137+ this . config . analyticsProvider ?. unsetUserProperty ?.(
138+ exposureEvent ( this . addContext ( this . getUser ( ) ) , key , variant , source ) ,
139+ ) ;
140+ } else if ( variant ?. value ) {
141+ // only track when there's a value for a non fallback variant
142+ const event = exposureEvent (
143+ this . addContext ( this . getUser ( ) ) ,
144+ key ,
145+ variant ,
146+ source ,
138147 ) ;
148+ this . config . analyticsProvider ?. setUserProperty ?.( event ) ;
149+ this . config . analyticsProvider ?. track ( event ) ;
150+ }
151+
152+ this . debug ( `[Experiment] variant for ${ key } is ${ variant . value } ` ) ;
153+ return variant ;
154+ }
155+
156+ private variantAndSource (
157+ key : string ,
158+ fallback : string | Variant ,
159+ ) : {
160+ variant : Variant ;
161+ source : VariantSource ;
162+ } {
163+ if ( this . config . source === Source . InitialVariants ) {
164+ // for source = InitialVariants, fallback order goes:
165+ // 1. InitialFlags
166+ // 2. Local Storage
167+ // 3. Function fallback
168+ // 4. Config fallback
169+
170+ const sourceVariant = this . sourceVariants ( ) [ key ] ;
171+ if ( ! isNullOrUndefined ( sourceVariant ) ) {
172+ return {
173+ variant : this . convertVariant ( sourceVariant ) ,
174+ source : VariantSource . InitialVariants ,
175+ } ;
176+ }
177+ const secondaryVariant = this . secondaryVariants ( ) [ key ] ;
178+ if ( ! isNullOrUndefined ( secondaryVariant ) ) {
179+ return {
180+ variant : this . convertVariant ( secondaryVariant ) ,
181+ source : VariantSource . SecondaryLocalStoraage ,
182+ } ;
183+ }
184+ if ( ! isNullOrUndefined ( fallback ) ) {
185+ return {
186+ variant : this . convertVariant ( fallback ) ,
187+ source : VariantSource . FallbackInline ,
188+ } ;
189+ }
190+ return {
191+ variant : this . convertVariant ( this . config . fallbackVariant ) ,
192+ source : VariantSource . FallbackConfig ,
193+ } ;
194+ } else {
195+ // for source = LocalStorage, fallback order goes:
196+ // 1. Local Storage
197+ // 2. Function fallback
198+ // 3. InitialFlags
199+ // 4. Config fallback
200+
201+ const sourceVariant = this . sourceVariants ( ) [ key ] ;
202+ if ( ! isNullOrUndefined ( sourceVariant ) ) {
203+ return {
204+ variant : this . convertVariant ( sourceVariant ) ,
205+ source : VariantSource . LocalStorage ,
206+ } ;
207+ }
208+ if ( ! isNullOrUndefined ( fallback ) ) {
209+ return {
210+ variant : this . convertVariant ( fallback ) ,
211+ source : VariantSource . FallbackInline ,
212+ } ;
213+ }
214+ const secondaryVariant = this . secondaryVariants ( ) [ key ] ;
215+ if ( ! isNullOrUndefined ( secondaryVariant ) ) {
216+ return {
217+ variant : this . convertVariant ( secondaryVariant ) ,
218+ source : VariantSource . SecondaryInitialVariants ,
219+ } ;
220+ }
221+ return {
222+ variant : this . convertVariant ( this . config . fallbackVariant ) ,
223+ source : VariantSource . FallbackConfig ,
224+ } ;
139225 }
140- const variant =
141- sourceVariant ??
142- fallback ??
143- this . secondaryVariants ( ) [ key ] ??
144- this . config . fallbackVariant ;
145- const converted = this . convertVariant ( variant ) ;
146- this . debug ( `[Experiment] variant for ${ key } is ${ converted . value } ` ) ;
147- return converted ;
148226 }
149227
150228 /**
0 commit comments