@@ -8,13 +8,14 @@ import { version as PACKAGE_VERSION } from '../package.json';
88import { ExperimentConfig , Defaults , Source } from './config' ;
99import { LocalStorage } from './storage/localStorage' ;
1010import { FetchHttpClient } from './transport/http' ;
11- import { ExposureEvent } from './types/analytics' ;
11+ import { exposureEvent , isFallback , VariantSource } from './types/analytics' ;
1212import { Client } from './types/client' ;
1313import { ExperimentUserProvider } from './types/provider' ;
1414import { Storage } from './types/storage' ;
1515import { HttpClient } from './types/transport' ;
1616import { ExperimentUser } from './types/user' ;
1717import { Variant , Variants } from './types/variant' ;
18+ import { isNullOrUndefined } from './util' ;
1819import { Backoff } from './util/backoff' ;
1920import { urlSafeBase64Encode } from './util/base64' ;
2021import { randomString } from './util/randomstring' ;
@@ -114,37 +115,122 @@ export class ExperimentClient implements Client {
114115 * Access the variant from {@link Source}, falling back on the given
115116 * fallback, then the configured fallbackVariant.
116117 *
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.
118+ * If an {@link ExperimentAnalyticsProvider} is configured and trackExposure is
119+ * true, this function will call the provider with an {@link ExposureEvent}.
120+ * The exposure event does not count towards your event volume within Amplitude.
120121 *
121122 * @param key The key to get the variant for.
122123 * @param fallback The highest priority fallback.
124+ * @param trackExposure Sends a track event via the configured {@link ExperimentAnalyticsProvider}
123125 * @see ExperimentConfig
124126 * @see ExperimentAnalyticsProvider
125127 */
126- public variant ( key : string , fallback ?: string | Variant ) : Variant {
128+ public variant (
129+ key : string ,
130+ fallback ?: string | Variant ,
131+ trackExposure = true ,
132+ ) : Variant {
127133 if ( ! this . apiKey ) {
128134 return { value : undefined } ;
129135 }
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- ) ,
138- ) ;
136+ const { source, variant } = this . variantAndSource ( key , fallback ) ;
137+
138+ if ( trackExposure ) {
139+ if ( isFallback ( source ) || ! variant ?. value ) {
140+ // fallbacks indicate not being allocated into an experiment, so
141+ // we can unset the property
142+ this . config . analyticsProvider ?. unset (
143+ exposureEvent ( this . addContext ( this . getUser ( ) ) , key , variant , source ) ,
144+ ) ;
145+ } else {
146+ if ( variant ?. value ) {
147+ // only track when there's a value for a non fallback variant
148+ this . config . analyticsProvider ?. track (
149+ exposureEvent (
150+ this . addContext ( this . getUser ( ) ) ,
151+ key ,
152+ variant ,
153+ source ,
154+ ) ,
155+ ) ;
156+ }
157+ }
158+ }
159+
160+ this . debug ( `[Experiment] variant for ${ key } is ${ variant . value } ` ) ;
161+ return variant ;
162+ }
163+
164+ private variantAndSource (
165+ key : string ,
166+ fallback : string | Variant ,
167+ ) : {
168+ variant : Variant ;
169+ source : VariantSource ;
170+ } {
171+ if ( this . config . source === Source . InitialVariants ) {
172+ // for source = InitialVariants, fallback order goes:
173+ // 1. InitialFlags
174+ // 2. Local Storage
175+ // 3. Function fallback
176+ // 4. Config fallback
177+
178+ const sourceVariant = this . sourceVariants ( ) [ key ] ;
179+ if ( ! isNullOrUndefined ( sourceVariant ) ) {
180+ return {
181+ variant : this . convertVariant ( sourceVariant ) ,
182+ source : VariantSource . INITIAL_VARIANTS ,
183+ } ;
184+ }
185+ const secondaryVariant = this . secondaryVariants ( ) [ key ] ;
186+ if ( ! isNullOrUndefined ( secondaryVariant ) ) {
187+ return {
188+ variant : this . convertVariant ( secondaryVariant ) ,
189+ source : VariantSource . SECONDARY_LOCAL_STORAGE ,
190+ } ;
191+ }
192+ if ( ! isNullOrUndefined ( fallback ) ) {
193+ return {
194+ variant : this . convertVariant ( fallback ) ,
195+ source : VariantSource . FALLBACK_INLINE ,
196+ } ;
197+ }
198+ return {
199+ variant : this . convertVariant ( this . config . fallbackVariant ) ,
200+ source : VariantSource . FALLBACK_CONFIG ,
201+ } ;
202+ } else {
203+ // for source = LocalStorage, fallback order goes:
204+ // 1. Local Storage
205+ // 2. Function fallback
206+ // 3. InitialFlags
207+ // 4. Config fallback
208+
209+ const sourceVariant = this . sourceVariants ( ) [ key ] ;
210+ if ( ! isNullOrUndefined ( sourceVariant ) ) {
211+ return {
212+ variant : this . convertVariant ( sourceVariant ) ,
213+ source : VariantSource . LOCAL_STORAGE ,
214+ } ;
215+ }
216+ if ( ! isNullOrUndefined ( fallback ) ) {
217+ return {
218+ variant : this . convertVariant ( fallback ) ,
219+ source : VariantSource . FALLBACK_INLINE ,
220+ } ;
221+ }
222+ const secondaryVariant = this . secondaryVariants ( ) [ key ] ;
223+ if ( ! isNullOrUndefined ( secondaryVariant ) ) {
224+ return {
225+ variant : this . convertVariant ( secondaryVariant ) ,
226+ source : VariantSource . SECONDARY_INITIAL_VARIANTS ,
227+ } ;
228+ }
229+ return {
230+ variant : this . convertVariant ( this . config . fallbackVariant ) ,
231+ source : VariantSource . FALLBACK_CONFIG ,
232+ } ;
139233 }
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 ;
148234 }
149235
150236 /**
0 commit comments