@shipeasy/sdk 5.1.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.d.mts +86 -2
- package/dist/client/index.d.ts +86 -2
- package/dist/client/index.js +90 -7
- package/dist/client/index.mjs +90 -7
- package/dist/openfeature-server/index.d.mts +338 -0
- package/dist/openfeature-server/index.d.ts +338 -0
- package/dist/openfeature-server/index.js +125 -0
- package/dist/openfeature-server/index.mjs +100 -0
- package/dist/openfeature-web/index.d.mts +332 -0
- package/dist/openfeature-web/index.d.ts +332 -0
- package/dist/openfeature-web/index.js +120 -0
- package/dist/openfeature-web/index.mjs +95 -0
- package/dist/server/index.d.mts +58 -1
- package/dist/server/index.d.ts +58 -1
- package/dist/server/index.js +97 -15
- package/dist/server/index.mjs +96 -15
- package/package.json +23 -1
package/dist/client/index.d.mts
CHANGED
|
@@ -120,6 +120,20 @@ interface ExperimentResult<P> {
|
|
|
120
120
|
group: string;
|
|
121
121
|
params: P;
|
|
122
122
|
}
|
|
123
|
+
/** Options object form of `getExperiment` — the legacy `decode`/`variants`
|
|
124
|
+
* positional args plus per-call exposure control. */
|
|
125
|
+
interface GetExperimentOptions<P> {
|
|
126
|
+
/** Decode the raw stored params into the typed shape callers want. */
|
|
127
|
+
decode?: (raw: unknown) => P;
|
|
128
|
+
/** Variant-specific param overrides merged on top of group params. */
|
|
129
|
+
variants?: Record<string, Partial<P>>;
|
|
130
|
+
/**
|
|
131
|
+
* Override automatic exposure logging for this read. Defaults to the client's
|
|
132
|
+
* setting (`disableAutoExposure` flips it). `false` reads the variant without
|
|
133
|
+
* logging an exposure — pair with `logExposure(name)` at render time.
|
|
134
|
+
*/
|
|
135
|
+
logExposure?: boolean;
|
|
136
|
+
}
|
|
123
137
|
/**
|
|
124
138
|
* Why a flag evaluated the way it did (LaunchDarkly variationDetail parity).
|
|
125
139
|
* Computed at the client boundary:
|
|
@@ -162,6 +176,17 @@ interface EvalResponse {
|
|
|
162
176
|
* switch booleans.
|
|
163
177
|
*/
|
|
164
178
|
killswitches?: Record<string, boolean | Record<string, boolean>>;
|
|
179
|
+
/**
|
|
180
|
+
* Newly-assigned sticky entries (doc 20 §2). The worker returns these so the
|
|
181
|
+
* browser can merge them into the `__se_sticky` cookie. Present only when
|
|
182
|
+
* sticky bucketing is on and at least one assignment was made/refreshed.
|
|
183
|
+
*/
|
|
184
|
+
sticky?: Record<string, StickyCookieEntry>;
|
|
185
|
+
}
|
|
186
|
+
/** One persisted sticky assignment: group + 8-char salt prefix (reshuffle key). */
|
|
187
|
+
interface StickyCookieEntry {
|
|
188
|
+
g: string;
|
|
189
|
+
s: string;
|
|
165
190
|
}
|
|
166
191
|
interface AutoCollectGroups {
|
|
167
192
|
vitals: boolean;
|
|
@@ -207,6 +232,29 @@ interface FlagsClientBrowserOptions {
|
|
|
207
232
|
disableTelemetry?: boolean;
|
|
208
233
|
/** Override the telemetry beacon host. Defaults to {@link DEFAULT_TELEMETRY_URL}. */
|
|
209
234
|
telemetryUrl?: string;
|
|
235
|
+
/**
|
|
236
|
+
* Suppress automatic exposure logging in `getExperiment` (Statsig's
|
|
237
|
+
* `disableExposureLogging`). Default false — reading an enrolled variant
|
|
238
|
+
* auto-logs a deduped exposure. When true, no exposure fires unless you call
|
|
239
|
+
* `logExposure(name)` yourself, or pass `{ logExposure: true }` per call.
|
|
240
|
+
*/
|
|
241
|
+
disableAutoExposure?: boolean;
|
|
242
|
+
/**
|
|
243
|
+
* Attribute names usable for targeting but never persisted in analytics
|
|
244
|
+
* (LD/Statsig `privateAttributes`). They are sent to `/sdk/evaluate` under
|
|
245
|
+
* `private_attributes` so the edge can evaluate with them (unavoidable —
|
|
246
|
+
* the edge evaluates), but the worker never stores them, and the listed keys
|
|
247
|
+
* are stripped from any `track(props)` payload.
|
|
248
|
+
*/
|
|
249
|
+
privateAttributes?: string[];
|
|
250
|
+
/**
|
|
251
|
+
* Sticky bucketing (doc 20 §2). ON by default in the browser: a unit's
|
|
252
|
+
* first-assigned variant is locked in the `__se_sticky` cookie so changing an
|
|
253
|
+
* experiment's allocation % or group weights never silently re-buckets
|
|
254
|
+
* enrolled users. Changing the experiment salt is the deliberate reshuffle
|
|
255
|
+
* lever. Pass `false` to opt out (pure deterministic eval).
|
|
256
|
+
*/
|
|
257
|
+
stickyBucketing?: boolean;
|
|
210
258
|
/**
|
|
211
259
|
* Test mode — no network at all. identify()/init are no-ops (never call
|
|
212
260
|
* /sdk/evaluate), track() is a no-op, telemetry is forced off, and the client
|
|
@@ -221,6 +269,9 @@ declare class FlagsClientBrowser {
|
|
|
221
269
|
private readonly autoGuardrails;
|
|
222
270
|
private readonly autoGuardrailGroups;
|
|
223
271
|
private readonly autoCollectAlways;
|
|
272
|
+
private readonly disableAutoExposure;
|
|
273
|
+
private readonly privateAttributes;
|
|
274
|
+
private readonly stickyBucketing;
|
|
224
275
|
private readonly env;
|
|
225
276
|
private evalResult;
|
|
226
277
|
private anonId;
|
|
@@ -292,6 +343,15 @@ declare class FlagsClientBrowser {
|
|
|
292
343
|
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
293
344
|
getConfig<T = unknown>(name: string, opts: GetConfigOptions<T>): T;
|
|
294
345
|
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, decode?: (raw: unknown) => P, variants?: Record<string, Partial<P>>): ExperimentResult<P>;
|
|
346
|
+
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, opts: GetExperimentOptions<P>): ExperimentResult<P>;
|
|
347
|
+
/**
|
|
348
|
+
* Manually log an exposure for an enrolled experiment (Statsig's
|
|
349
|
+
* `manuallyLogExposure`). Reads the cached eval result; if the visitor is in
|
|
350
|
+
* the experiment, pushes the session-deduped exposure. Pair this with the
|
|
351
|
+
* render of the treatment when reading with `{ logExposure: false }` (or
|
|
352
|
+
* `disableAutoExposure: true`). No-op if the visitor isn't enrolled.
|
|
353
|
+
*/
|
|
354
|
+
logExposure(name: string): void;
|
|
295
355
|
/**
|
|
296
356
|
* Subscribe to state changes — fires after identify() completes and on
|
|
297
357
|
* `se:override:change` events from the devtools overlay. Returns an
|
|
@@ -304,6 +364,8 @@ declare class FlagsClientBrowser {
|
|
|
304
364
|
*/
|
|
305
365
|
installBridge(): ShipeasySdkBridge | null;
|
|
306
366
|
track(eventName: string, props?: Record<string, unknown>): void;
|
|
367
|
+
/** Drop caller-marked private attributes from an outbound props bag. */
|
|
368
|
+
private stripPrivate;
|
|
307
369
|
/**
|
|
308
370
|
* Read a killswitch from the server's evaluated state. Without `switchKey`,
|
|
309
371
|
* returns true when the killswitch is whole-killed. With `switchKey`, returns
|
|
@@ -412,6 +474,25 @@ interface ShipeasyClientConfig {
|
|
|
412
474
|
* counted by Cloudflare's native per-path analytics. Pass `true` to opt out.
|
|
413
475
|
*/
|
|
414
476
|
disableTelemetry?: boolean;
|
|
477
|
+
/**
|
|
478
|
+
* Suppress automatic exposure logging in `flags.getExperiment` (Statsig's
|
|
479
|
+
* `disableExposureLogging`). Default false. When true, call
|
|
480
|
+
* `flags.logExposure(name)` at the treatment's render to log the exposure.
|
|
481
|
+
*/
|
|
482
|
+
disableAutoExposure?: boolean;
|
|
483
|
+
/**
|
|
484
|
+
* Attribute names usable for targeting but never persisted in analytics
|
|
485
|
+
* (LD/Statsig `privateAttributes`). Sent to the edge for evaluation, never
|
|
486
|
+
* stored, and stripped from `flags.track(props)`. See
|
|
487
|
+
* {@link FlagsClientBrowserOptions.privateAttributes}.
|
|
488
|
+
*/
|
|
489
|
+
privateAttributes?: string[];
|
|
490
|
+
/**
|
|
491
|
+
* Sticky bucketing (doc 20 §2). ON by default — locks each enrolled unit to
|
|
492
|
+
* its first-assigned variant via the `__se_sticky` cookie. Pass `false` to
|
|
493
|
+
* opt out. See {@link FlagsClientBrowserOptions.stickyBucketing}.
|
|
494
|
+
*/
|
|
495
|
+
stickyBucketing?: boolean;
|
|
415
496
|
}
|
|
416
497
|
/**
|
|
417
498
|
* Initialise the ShipEasy client SDK and wire up lazy devtools.
|
|
@@ -471,7 +552,10 @@ declare const flags: {
|
|
|
471
552
|
/** Evaluate a gate and report why (value + reason). See {@link FlagDetail}. */
|
|
472
553
|
getDetail(name: string): FlagDetail;
|
|
473
554
|
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
474
|
-
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P,
|
|
555
|
+
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, decodeOrOpts?: ((raw: unknown) => P) | GetExperimentOptions<P>, variants?: Record<string, Partial<P>>): ExperimentResult<P>;
|
|
556
|
+
/** Manually log an exposure for an enrolled experiment. See
|
|
557
|
+
* {@link FlagsClientBrowser.logExposure}. No-op before configure(). */
|
|
558
|
+
logExposure(name: string): void;
|
|
475
559
|
track(eventName: string, props?: Record<string, unknown>): void;
|
|
476
560
|
/**
|
|
477
561
|
* Read a killswitch. Without `switchKey`, returns true when the killswitch is
|
|
@@ -590,4 +674,4 @@ interface I18nFacade {
|
|
|
590
674
|
}
|
|
591
675
|
declare const i18n: I18nFacade;
|
|
592
676
|
|
|
593
|
-
export { type AutoCollectGroups, type BootstrapPayload, type Consequence, type ExperimentResult, FLAG_REASONS, type FlagDetail, type FlagReason, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type GetConfigOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type SeeApi, type SeeChain, type SeeControlFlowChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, type Violation, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, injectCorrelationHeader, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, sameOrigin, see, shipeasy, version };
|
|
677
|
+
export { type AutoCollectGroups, type BootstrapPayload, type Consequence, type ExperimentResult, FLAG_REASONS, type FlagDetail, type FlagReason, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type GetConfigOptions, type GetExperimentOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type SeeApi, type SeeChain, type SeeControlFlowChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, type Violation, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, injectCorrelationHeader, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, sameOrigin, see, shipeasy, version };
|
package/dist/client/index.d.ts
CHANGED
|
@@ -120,6 +120,20 @@ interface ExperimentResult<P> {
|
|
|
120
120
|
group: string;
|
|
121
121
|
params: P;
|
|
122
122
|
}
|
|
123
|
+
/** Options object form of `getExperiment` — the legacy `decode`/`variants`
|
|
124
|
+
* positional args plus per-call exposure control. */
|
|
125
|
+
interface GetExperimentOptions<P> {
|
|
126
|
+
/** Decode the raw stored params into the typed shape callers want. */
|
|
127
|
+
decode?: (raw: unknown) => P;
|
|
128
|
+
/** Variant-specific param overrides merged on top of group params. */
|
|
129
|
+
variants?: Record<string, Partial<P>>;
|
|
130
|
+
/**
|
|
131
|
+
* Override automatic exposure logging for this read. Defaults to the client's
|
|
132
|
+
* setting (`disableAutoExposure` flips it). `false` reads the variant without
|
|
133
|
+
* logging an exposure — pair with `logExposure(name)` at render time.
|
|
134
|
+
*/
|
|
135
|
+
logExposure?: boolean;
|
|
136
|
+
}
|
|
123
137
|
/**
|
|
124
138
|
* Why a flag evaluated the way it did (LaunchDarkly variationDetail parity).
|
|
125
139
|
* Computed at the client boundary:
|
|
@@ -162,6 +176,17 @@ interface EvalResponse {
|
|
|
162
176
|
* switch booleans.
|
|
163
177
|
*/
|
|
164
178
|
killswitches?: Record<string, boolean | Record<string, boolean>>;
|
|
179
|
+
/**
|
|
180
|
+
* Newly-assigned sticky entries (doc 20 §2). The worker returns these so the
|
|
181
|
+
* browser can merge them into the `__se_sticky` cookie. Present only when
|
|
182
|
+
* sticky bucketing is on and at least one assignment was made/refreshed.
|
|
183
|
+
*/
|
|
184
|
+
sticky?: Record<string, StickyCookieEntry>;
|
|
185
|
+
}
|
|
186
|
+
/** One persisted sticky assignment: group + 8-char salt prefix (reshuffle key). */
|
|
187
|
+
interface StickyCookieEntry {
|
|
188
|
+
g: string;
|
|
189
|
+
s: string;
|
|
165
190
|
}
|
|
166
191
|
interface AutoCollectGroups {
|
|
167
192
|
vitals: boolean;
|
|
@@ -207,6 +232,29 @@ interface FlagsClientBrowserOptions {
|
|
|
207
232
|
disableTelemetry?: boolean;
|
|
208
233
|
/** Override the telemetry beacon host. Defaults to {@link DEFAULT_TELEMETRY_URL}. */
|
|
209
234
|
telemetryUrl?: string;
|
|
235
|
+
/**
|
|
236
|
+
* Suppress automatic exposure logging in `getExperiment` (Statsig's
|
|
237
|
+
* `disableExposureLogging`). Default false — reading an enrolled variant
|
|
238
|
+
* auto-logs a deduped exposure. When true, no exposure fires unless you call
|
|
239
|
+
* `logExposure(name)` yourself, or pass `{ logExposure: true }` per call.
|
|
240
|
+
*/
|
|
241
|
+
disableAutoExposure?: boolean;
|
|
242
|
+
/**
|
|
243
|
+
* Attribute names usable for targeting but never persisted in analytics
|
|
244
|
+
* (LD/Statsig `privateAttributes`). They are sent to `/sdk/evaluate` under
|
|
245
|
+
* `private_attributes` so the edge can evaluate with them (unavoidable —
|
|
246
|
+
* the edge evaluates), but the worker never stores them, and the listed keys
|
|
247
|
+
* are stripped from any `track(props)` payload.
|
|
248
|
+
*/
|
|
249
|
+
privateAttributes?: string[];
|
|
250
|
+
/**
|
|
251
|
+
* Sticky bucketing (doc 20 §2). ON by default in the browser: a unit's
|
|
252
|
+
* first-assigned variant is locked in the `__se_sticky` cookie so changing an
|
|
253
|
+
* experiment's allocation % or group weights never silently re-buckets
|
|
254
|
+
* enrolled users. Changing the experiment salt is the deliberate reshuffle
|
|
255
|
+
* lever. Pass `false` to opt out (pure deterministic eval).
|
|
256
|
+
*/
|
|
257
|
+
stickyBucketing?: boolean;
|
|
210
258
|
/**
|
|
211
259
|
* Test mode — no network at all. identify()/init are no-ops (never call
|
|
212
260
|
* /sdk/evaluate), track() is a no-op, telemetry is forced off, and the client
|
|
@@ -221,6 +269,9 @@ declare class FlagsClientBrowser {
|
|
|
221
269
|
private readonly autoGuardrails;
|
|
222
270
|
private readonly autoGuardrailGroups;
|
|
223
271
|
private readonly autoCollectAlways;
|
|
272
|
+
private readonly disableAutoExposure;
|
|
273
|
+
private readonly privateAttributes;
|
|
274
|
+
private readonly stickyBucketing;
|
|
224
275
|
private readonly env;
|
|
225
276
|
private evalResult;
|
|
226
277
|
private anonId;
|
|
@@ -292,6 +343,15 @@ declare class FlagsClientBrowser {
|
|
|
292
343
|
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
293
344
|
getConfig<T = unknown>(name: string, opts: GetConfigOptions<T>): T;
|
|
294
345
|
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, decode?: (raw: unknown) => P, variants?: Record<string, Partial<P>>): ExperimentResult<P>;
|
|
346
|
+
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, opts: GetExperimentOptions<P>): ExperimentResult<P>;
|
|
347
|
+
/**
|
|
348
|
+
* Manually log an exposure for an enrolled experiment (Statsig's
|
|
349
|
+
* `manuallyLogExposure`). Reads the cached eval result; if the visitor is in
|
|
350
|
+
* the experiment, pushes the session-deduped exposure. Pair this with the
|
|
351
|
+
* render of the treatment when reading with `{ logExposure: false }` (or
|
|
352
|
+
* `disableAutoExposure: true`). No-op if the visitor isn't enrolled.
|
|
353
|
+
*/
|
|
354
|
+
logExposure(name: string): void;
|
|
295
355
|
/**
|
|
296
356
|
* Subscribe to state changes — fires after identify() completes and on
|
|
297
357
|
* `se:override:change` events from the devtools overlay. Returns an
|
|
@@ -304,6 +364,8 @@ declare class FlagsClientBrowser {
|
|
|
304
364
|
*/
|
|
305
365
|
installBridge(): ShipeasySdkBridge | null;
|
|
306
366
|
track(eventName: string, props?: Record<string, unknown>): void;
|
|
367
|
+
/** Drop caller-marked private attributes from an outbound props bag. */
|
|
368
|
+
private stripPrivate;
|
|
307
369
|
/**
|
|
308
370
|
* Read a killswitch from the server's evaluated state. Without `switchKey`,
|
|
309
371
|
* returns true when the killswitch is whole-killed. With `switchKey`, returns
|
|
@@ -412,6 +474,25 @@ interface ShipeasyClientConfig {
|
|
|
412
474
|
* counted by Cloudflare's native per-path analytics. Pass `true` to opt out.
|
|
413
475
|
*/
|
|
414
476
|
disableTelemetry?: boolean;
|
|
477
|
+
/**
|
|
478
|
+
* Suppress automatic exposure logging in `flags.getExperiment` (Statsig's
|
|
479
|
+
* `disableExposureLogging`). Default false. When true, call
|
|
480
|
+
* `flags.logExposure(name)` at the treatment's render to log the exposure.
|
|
481
|
+
*/
|
|
482
|
+
disableAutoExposure?: boolean;
|
|
483
|
+
/**
|
|
484
|
+
* Attribute names usable for targeting but never persisted in analytics
|
|
485
|
+
* (LD/Statsig `privateAttributes`). Sent to the edge for evaluation, never
|
|
486
|
+
* stored, and stripped from `flags.track(props)`. See
|
|
487
|
+
* {@link FlagsClientBrowserOptions.privateAttributes}.
|
|
488
|
+
*/
|
|
489
|
+
privateAttributes?: string[];
|
|
490
|
+
/**
|
|
491
|
+
* Sticky bucketing (doc 20 §2). ON by default — locks each enrolled unit to
|
|
492
|
+
* its first-assigned variant via the `__se_sticky` cookie. Pass `false` to
|
|
493
|
+
* opt out. See {@link FlagsClientBrowserOptions.stickyBucketing}.
|
|
494
|
+
*/
|
|
495
|
+
stickyBucketing?: boolean;
|
|
415
496
|
}
|
|
416
497
|
/**
|
|
417
498
|
* Initialise the ShipEasy client SDK and wire up lazy devtools.
|
|
@@ -471,7 +552,10 @@ declare const flags: {
|
|
|
471
552
|
/** Evaluate a gate and report why (value + reason). See {@link FlagDetail}. */
|
|
472
553
|
getDetail(name: string): FlagDetail;
|
|
473
554
|
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
474
|
-
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P,
|
|
555
|
+
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, decodeOrOpts?: ((raw: unknown) => P) | GetExperimentOptions<P>, variants?: Record<string, Partial<P>>): ExperimentResult<P>;
|
|
556
|
+
/** Manually log an exposure for an enrolled experiment. See
|
|
557
|
+
* {@link FlagsClientBrowser.logExposure}. No-op before configure(). */
|
|
558
|
+
logExposure(name: string): void;
|
|
475
559
|
track(eventName: string, props?: Record<string, unknown>): void;
|
|
476
560
|
/**
|
|
477
561
|
* Read a killswitch. Without `switchKey`, returns true when the killswitch is
|
|
@@ -590,4 +674,4 @@ interface I18nFacade {
|
|
|
590
674
|
}
|
|
591
675
|
declare const i18n: I18nFacade;
|
|
592
676
|
|
|
593
|
-
export { type AutoCollectGroups, type BootstrapPayload, type Consequence, type ExperimentResult, FLAG_REASONS, type FlagDetail, type FlagReason, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type GetConfigOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type SeeApi, type SeeChain, type SeeControlFlowChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, type Violation, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, injectCorrelationHeader, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, sameOrigin, see, shipeasy, version };
|
|
677
|
+
export { type AutoCollectGroups, type BootstrapPayload, type Consequence, type ExperimentResult, FLAG_REASONS, type FlagDetail, type FlagReason, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type GetConfigOptions, type GetExperimentOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type SeeApi, type SeeChain, type SeeControlFlowChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, type Violation, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, injectCorrelationHeader, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, sameOrigin, see, shipeasy, version };
|
package/dist/client/index.js
CHANGED
|
@@ -731,6 +731,40 @@ function writeAnonCookie(id) {
|
|
|
731
731
|
} catch {
|
|
732
732
|
}
|
|
733
733
|
}
|
|
734
|
+
var STICKY_COOKIE = "__se_sticky";
|
|
735
|
+
var STICKY_MAX_BYTES = 3800;
|
|
736
|
+
function readStickyCookie() {
|
|
737
|
+
try {
|
|
738
|
+
const m = ("; " + document.cookie).match(/; __se_sticky=([^;]+)/);
|
|
739
|
+
if (!m) return {};
|
|
740
|
+
const parsed = JSON.parse(decodeURIComponent(m[1]));
|
|
741
|
+
if (!parsed || typeof parsed !== "object") return {};
|
|
742
|
+
const out = {};
|
|
743
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
744
|
+
if (v && typeof v === "object" && typeof v.g === "string") {
|
|
745
|
+
out[k] = { g: v.g, s: String(v.s ?? "") };
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return out;
|
|
749
|
+
} catch {
|
|
750
|
+
return {};
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
function writeStickyCookie(map) {
|
|
754
|
+
try {
|
|
755
|
+
let json = JSON.stringify(map);
|
|
756
|
+
if (json.length > STICKY_MAX_BYTES) {
|
|
757
|
+
const entries = Object.entries(map);
|
|
758
|
+
while (entries.length > 0 && JSON.stringify(Object.fromEntries(entries)).length > STICKY_MAX_BYTES) {
|
|
759
|
+
entries.shift();
|
|
760
|
+
}
|
|
761
|
+
json = JSON.stringify(Object.fromEntries(entries));
|
|
762
|
+
}
|
|
763
|
+
const secure = location.protocol === "https:" ? ";secure" : "";
|
|
764
|
+
document.cookie = `${STICKY_COOKIE}=${encodeURIComponent(json)};path=/;max-age=31536000;samesite=lax${secure}`;
|
|
765
|
+
} catch {
|
|
766
|
+
}
|
|
767
|
+
}
|
|
734
768
|
function getOrCreateAnonId() {
|
|
735
769
|
let id = readAnonCookie();
|
|
736
770
|
if (!id && typeof window !== "undefined") {
|
|
@@ -884,6 +918,9 @@ var FlagsClientBrowser = class _FlagsClientBrowser {
|
|
|
884
918
|
autoGuardrails;
|
|
885
919
|
autoGuardrailGroups;
|
|
886
920
|
autoCollectAlways;
|
|
921
|
+
disableAutoExposure;
|
|
922
|
+
privateAttributes;
|
|
923
|
+
stickyBucketing;
|
|
887
924
|
env;
|
|
888
925
|
evalResult = null;
|
|
889
926
|
anonId;
|
|
@@ -918,6 +955,9 @@ var FlagsClientBrowser = class _FlagsClientBrowser {
|
|
|
918
955
|
this.testMode = opts.testMode === true;
|
|
919
956
|
this.autoGuardrails = opts.autoGuardrails !== false;
|
|
920
957
|
this.autoCollectAlways = opts.autoCollectAlways === true;
|
|
958
|
+
this.disableAutoExposure = opts.disableAutoExposure === true;
|
|
959
|
+
this.privateAttributes = opts.privateAttributes ?? [];
|
|
960
|
+
this.stickyBucketing = opts.stickyBucketing !== false;
|
|
921
961
|
const g = opts.autoGuardrailGroups ?? {};
|
|
922
962
|
this.autoGuardrailGroups = {
|
|
923
963
|
vitals: g.vitals ?? this.autoGuardrails,
|
|
@@ -983,13 +1023,22 @@ var FlagsClientBrowser = class _FlagsClientBrowser {
|
|
|
983
1023
|
headers: { "X-SDK-Key": this.sdkKey, "Content-Type": "application/json" },
|
|
984
1024
|
body: JSON.stringify({
|
|
985
1025
|
user: userPayload,
|
|
986
|
-
experiment_overrides: readExperimentOverridesFromUrl()
|
|
1026
|
+
experiment_overrides: readExperimentOverridesFromUrl(),
|
|
1027
|
+
// Private attributes still reach the edge for evaluation (unavoidable —
|
|
1028
|
+
// the edge evaluates), but the worker never persists them. See doc 20 §5.
|
|
1029
|
+
...this.privateAttributes.length > 0 ? { private_attributes: [...this.privateAttributes] } : {},
|
|
1030
|
+
// Sticky state round-trip: send the current cookie map; the worker
|
|
1031
|
+
// applies the sticky short-circuit and returns any new assignments.
|
|
1032
|
+
...this.stickyBucketing ? { sticky: readStickyCookie() } : {}
|
|
987
1033
|
})
|
|
988
1034
|
});
|
|
989
1035
|
if (!res.ok) throw new Error(`/sdk/evaluate returned ${res.status}`);
|
|
990
1036
|
const data = await res.json();
|
|
991
1037
|
if (seq !== this.identifySeq) return;
|
|
992
1038
|
this.evalResult = data;
|
|
1039
|
+
if (this.stickyBucketing && data.sticky && Object.keys(data.sticky).length > 0) {
|
|
1040
|
+
writeStickyCookie({ ...readStickyCookie(), ...data.sticky });
|
|
1041
|
+
}
|
|
993
1042
|
const anyGroupOn = this.autoGuardrailGroups.vitals || this.autoGuardrailGroups.errors || this.autoGuardrailGroups.engagement;
|
|
994
1043
|
if (anyGroupOn && !this.guardrailsInstalled) {
|
|
995
1044
|
this.guardrailsInstalled = true;
|
|
@@ -1115,7 +1164,9 @@ var FlagsClientBrowser = class _FlagsClientBrowser {
|
|
|
1115
1164
|
return void 0;
|
|
1116
1165
|
}
|
|
1117
1166
|
}
|
|
1118
|
-
getExperiment(name, defaultParams,
|
|
1167
|
+
getExperiment(name, defaultParams, decodeOrOpts, variantsArg) {
|
|
1168
|
+
const opts = typeof decodeOrOpts === "function" ? { decode: decodeOrOpts, variants: variantsArg } : decodeOrOpts ?? {};
|
|
1169
|
+
const { decode, variants } = opts;
|
|
1119
1170
|
this.telemetry.emit("experiment", name);
|
|
1120
1171
|
const notIn = {
|
|
1121
1172
|
inExperiment: false,
|
|
@@ -1136,7 +1187,8 @@ var FlagsClientBrowser = class _FlagsClientBrowser {
|
|
|
1136
1187
|
}
|
|
1137
1188
|
const entry = this.evalResult?.experiments[name];
|
|
1138
1189
|
if (!entry || !entry.inExperiment) return notIn;
|
|
1139
|
-
|
|
1190
|
+
const shouldLog = opts.logExposure ?? !this.disableAutoExposure;
|
|
1191
|
+
if (shouldLog) this.buffer.pushExposure(name, entry.group, this.userId, this.anonId);
|
|
1140
1192
|
if (!decode) return { inExperiment: true, group: entry.group, params: entry.params };
|
|
1141
1193
|
try {
|
|
1142
1194
|
return { inExperiment: true, group: entry.group, params: decode(entry.params) };
|
|
@@ -1145,6 +1197,18 @@ var FlagsClientBrowser = class _FlagsClientBrowser {
|
|
|
1145
1197
|
return notIn;
|
|
1146
1198
|
}
|
|
1147
1199
|
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Manually log an exposure for an enrolled experiment (Statsig's
|
|
1202
|
+
* `manuallyLogExposure`). Reads the cached eval result; if the visitor is in
|
|
1203
|
+
* the experiment, pushes the session-deduped exposure. Pair this with the
|
|
1204
|
+
* render of the treatment when reading with `{ logExposure: false }` (or
|
|
1205
|
+
* `disableAutoExposure: true`). No-op if the visitor isn't enrolled.
|
|
1206
|
+
*/
|
|
1207
|
+
logExposure(name) {
|
|
1208
|
+
const entry = this.evalResult?.experiments[name];
|
|
1209
|
+
if (!entry || !entry.inExperiment) return;
|
|
1210
|
+
this.buffer.pushExposure(name, entry.group, this.userId, this.anonId);
|
|
1211
|
+
}
|
|
1148
1212
|
/**
|
|
1149
1213
|
* Subscribe to state changes — fires after identify() completes and on
|
|
1150
1214
|
* `se:override:change` events from the devtools overlay. Returns an
|
|
@@ -1180,7 +1244,16 @@ var FlagsClientBrowser = class _FlagsClientBrowser {
|
|
|
1180
1244
|
}
|
|
1181
1245
|
track(eventName, props) {
|
|
1182
1246
|
if (this.testMode) return;
|
|
1183
|
-
this.buffer.pushMetric(eventName, this.userId, this.anonId, props);
|
|
1247
|
+
this.buffer.pushMetric(eventName, this.userId, this.anonId, this.stripPrivate(props));
|
|
1248
|
+
}
|
|
1249
|
+
/** Drop caller-marked private attributes from an outbound props bag. */
|
|
1250
|
+
stripPrivate(props) {
|
|
1251
|
+
if (!props || this.privateAttributes.length === 0) return props;
|
|
1252
|
+
const out = {};
|
|
1253
|
+
for (const [k, v] of Object.entries(props)) {
|
|
1254
|
+
if (!this.privateAttributes.includes(k)) out[k] = v;
|
|
1255
|
+
}
|
|
1256
|
+
return out;
|
|
1184
1257
|
}
|
|
1185
1258
|
/**
|
|
1186
1259
|
* Read a killswitch from the server's evaluated state. Without `switchKey`,
|
|
@@ -1326,7 +1399,10 @@ function shipeasy(opts) {
|
|
|
1326
1399
|
autoGuardrails: blanket,
|
|
1327
1400
|
autoGuardrailGroups: groups,
|
|
1328
1401
|
autoCollectAlways: acObj?.always === true,
|
|
1329
|
-
disableTelemetry: opts.disableTelemetry
|
|
1402
|
+
disableTelemetry: opts.disableTelemetry,
|
|
1403
|
+
disableAutoExposure: opts.disableAutoExposure,
|
|
1404
|
+
privateAttributes: opts.privateAttributes,
|
|
1405
|
+
stickyBucketing: opts.stickyBucketing
|
|
1330
1406
|
});
|
|
1331
1407
|
injectI18nLoader(opts.clientKey, baseUrl, opts.i18nProfile);
|
|
1332
1408
|
flags.notifyMounted();
|
|
@@ -1434,12 +1510,19 @@ var flags = {
|
|
|
1434
1510
|
return void 0;
|
|
1435
1511
|
}
|
|
1436
1512
|
},
|
|
1437
|
-
getExperiment(name, defaultParams,
|
|
1438
|
-
|
|
1513
|
+
getExperiment(name, defaultParams, decodeOrOpts, variants) {
|
|
1514
|
+
const fallback = {
|
|
1439
1515
|
inExperiment: false,
|
|
1440
1516
|
group: "control",
|
|
1441
1517
|
params: defaultParams
|
|
1442
1518
|
};
|
|
1519
|
+
if (!_client) return fallback;
|
|
1520
|
+
return typeof decodeOrOpts === "function" ? _client.getExperiment(name, defaultParams, decodeOrOpts, variants) : _client.getExperiment(name, defaultParams, decodeOrOpts ?? {});
|
|
1521
|
+
},
|
|
1522
|
+
/** Manually log an exposure for an enrolled experiment. See
|
|
1523
|
+
* {@link FlagsClientBrowser.logExposure}. No-op before configure(). */
|
|
1524
|
+
logExposure(name) {
|
|
1525
|
+
_client?.logExposure(name);
|
|
1443
1526
|
},
|
|
1444
1527
|
track(eventName, props) {
|
|
1445
1528
|
_client?.track(eventName, props);
|
package/dist/client/index.mjs
CHANGED
|
@@ -682,6 +682,40 @@ function writeAnonCookie(id) {
|
|
|
682
682
|
} catch {
|
|
683
683
|
}
|
|
684
684
|
}
|
|
685
|
+
var STICKY_COOKIE = "__se_sticky";
|
|
686
|
+
var STICKY_MAX_BYTES = 3800;
|
|
687
|
+
function readStickyCookie() {
|
|
688
|
+
try {
|
|
689
|
+
const m = ("; " + document.cookie).match(/; __se_sticky=([^;]+)/);
|
|
690
|
+
if (!m) return {};
|
|
691
|
+
const parsed = JSON.parse(decodeURIComponent(m[1]));
|
|
692
|
+
if (!parsed || typeof parsed !== "object") return {};
|
|
693
|
+
const out = {};
|
|
694
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
695
|
+
if (v && typeof v === "object" && typeof v.g === "string") {
|
|
696
|
+
out[k] = { g: v.g, s: String(v.s ?? "") };
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return out;
|
|
700
|
+
} catch {
|
|
701
|
+
return {};
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
function writeStickyCookie(map) {
|
|
705
|
+
try {
|
|
706
|
+
let json = JSON.stringify(map);
|
|
707
|
+
if (json.length > STICKY_MAX_BYTES) {
|
|
708
|
+
const entries = Object.entries(map);
|
|
709
|
+
while (entries.length > 0 && JSON.stringify(Object.fromEntries(entries)).length > STICKY_MAX_BYTES) {
|
|
710
|
+
entries.shift();
|
|
711
|
+
}
|
|
712
|
+
json = JSON.stringify(Object.fromEntries(entries));
|
|
713
|
+
}
|
|
714
|
+
const secure = location.protocol === "https:" ? ";secure" : "";
|
|
715
|
+
document.cookie = `${STICKY_COOKIE}=${encodeURIComponent(json)};path=/;max-age=31536000;samesite=lax${secure}`;
|
|
716
|
+
} catch {
|
|
717
|
+
}
|
|
718
|
+
}
|
|
685
719
|
function getOrCreateAnonId() {
|
|
686
720
|
let id = readAnonCookie();
|
|
687
721
|
if (!id && typeof window !== "undefined") {
|
|
@@ -835,6 +869,9 @@ var FlagsClientBrowser = class _FlagsClientBrowser {
|
|
|
835
869
|
autoGuardrails;
|
|
836
870
|
autoGuardrailGroups;
|
|
837
871
|
autoCollectAlways;
|
|
872
|
+
disableAutoExposure;
|
|
873
|
+
privateAttributes;
|
|
874
|
+
stickyBucketing;
|
|
838
875
|
env;
|
|
839
876
|
evalResult = null;
|
|
840
877
|
anonId;
|
|
@@ -869,6 +906,9 @@ var FlagsClientBrowser = class _FlagsClientBrowser {
|
|
|
869
906
|
this.testMode = opts.testMode === true;
|
|
870
907
|
this.autoGuardrails = opts.autoGuardrails !== false;
|
|
871
908
|
this.autoCollectAlways = opts.autoCollectAlways === true;
|
|
909
|
+
this.disableAutoExposure = opts.disableAutoExposure === true;
|
|
910
|
+
this.privateAttributes = opts.privateAttributes ?? [];
|
|
911
|
+
this.stickyBucketing = opts.stickyBucketing !== false;
|
|
872
912
|
const g = opts.autoGuardrailGroups ?? {};
|
|
873
913
|
this.autoGuardrailGroups = {
|
|
874
914
|
vitals: g.vitals ?? this.autoGuardrails,
|
|
@@ -934,13 +974,22 @@ var FlagsClientBrowser = class _FlagsClientBrowser {
|
|
|
934
974
|
headers: { "X-SDK-Key": this.sdkKey, "Content-Type": "application/json" },
|
|
935
975
|
body: JSON.stringify({
|
|
936
976
|
user: userPayload,
|
|
937
|
-
experiment_overrides: readExperimentOverridesFromUrl()
|
|
977
|
+
experiment_overrides: readExperimentOverridesFromUrl(),
|
|
978
|
+
// Private attributes still reach the edge for evaluation (unavoidable —
|
|
979
|
+
// the edge evaluates), but the worker never persists them. See doc 20 §5.
|
|
980
|
+
...this.privateAttributes.length > 0 ? { private_attributes: [...this.privateAttributes] } : {},
|
|
981
|
+
// Sticky state round-trip: send the current cookie map; the worker
|
|
982
|
+
// applies the sticky short-circuit and returns any new assignments.
|
|
983
|
+
...this.stickyBucketing ? { sticky: readStickyCookie() } : {}
|
|
938
984
|
})
|
|
939
985
|
});
|
|
940
986
|
if (!res.ok) throw new Error(`/sdk/evaluate returned ${res.status}`);
|
|
941
987
|
const data = await res.json();
|
|
942
988
|
if (seq !== this.identifySeq) return;
|
|
943
989
|
this.evalResult = data;
|
|
990
|
+
if (this.stickyBucketing && data.sticky && Object.keys(data.sticky).length > 0) {
|
|
991
|
+
writeStickyCookie({ ...readStickyCookie(), ...data.sticky });
|
|
992
|
+
}
|
|
944
993
|
const anyGroupOn = this.autoGuardrailGroups.vitals || this.autoGuardrailGroups.errors || this.autoGuardrailGroups.engagement;
|
|
945
994
|
if (anyGroupOn && !this.guardrailsInstalled) {
|
|
946
995
|
this.guardrailsInstalled = true;
|
|
@@ -1066,7 +1115,9 @@ var FlagsClientBrowser = class _FlagsClientBrowser {
|
|
|
1066
1115
|
return void 0;
|
|
1067
1116
|
}
|
|
1068
1117
|
}
|
|
1069
|
-
getExperiment(name, defaultParams,
|
|
1118
|
+
getExperiment(name, defaultParams, decodeOrOpts, variantsArg) {
|
|
1119
|
+
const opts = typeof decodeOrOpts === "function" ? { decode: decodeOrOpts, variants: variantsArg } : decodeOrOpts ?? {};
|
|
1120
|
+
const { decode, variants } = opts;
|
|
1070
1121
|
this.telemetry.emit("experiment", name);
|
|
1071
1122
|
const notIn = {
|
|
1072
1123
|
inExperiment: false,
|
|
@@ -1087,7 +1138,8 @@ var FlagsClientBrowser = class _FlagsClientBrowser {
|
|
|
1087
1138
|
}
|
|
1088
1139
|
const entry = this.evalResult?.experiments[name];
|
|
1089
1140
|
if (!entry || !entry.inExperiment) return notIn;
|
|
1090
|
-
|
|
1141
|
+
const shouldLog = opts.logExposure ?? !this.disableAutoExposure;
|
|
1142
|
+
if (shouldLog) this.buffer.pushExposure(name, entry.group, this.userId, this.anonId);
|
|
1091
1143
|
if (!decode) return { inExperiment: true, group: entry.group, params: entry.params };
|
|
1092
1144
|
try {
|
|
1093
1145
|
return { inExperiment: true, group: entry.group, params: decode(entry.params) };
|
|
@@ -1096,6 +1148,18 @@ var FlagsClientBrowser = class _FlagsClientBrowser {
|
|
|
1096
1148
|
return notIn;
|
|
1097
1149
|
}
|
|
1098
1150
|
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Manually log an exposure for an enrolled experiment (Statsig's
|
|
1153
|
+
* `manuallyLogExposure`). Reads the cached eval result; if the visitor is in
|
|
1154
|
+
* the experiment, pushes the session-deduped exposure. Pair this with the
|
|
1155
|
+
* render of the treatment when reading with `{ logExposure: false }` (or
|
|
1156
|
+
* `disableAutoExposure: true`). No-op if the visitor isn't enrolled.
|
|
1157
|
+
*/
|
|
1158
|
+
logExposure(name) {
|
|
1159
|
+
const entry = this.evalResult?.experiments[name];
|
|
1160
|
+
if (!entry || !entry.inExperiment) return;
|
|
1161
|
+
this.buffer.pushExposure(name, entry.group, this.userId, this.anonId);
|
|
1162
|
+
}
|
|
1099
1163
|
/**
|
|
1100
1164
|
* Subscribe to state changes — fires after identify() completes and on
|
|
1101
1165
|
* `se:override:change` events from the devtools overlay. Returns an
|
|
@@ -1131,7 +1195,16 @@ var FlagsClientBrowser = class _FlagsClientBrowser {
|
|
|
1131
1195
|
}
|
|
1132
1196
|
track(eventName, props) {
|
|
1133
1197
|
if (this.testMode) return;
|
|
1134
|
-
this.buffer.pushMetric(eventName, this.userId, this.anonId, props);
|
|
1198
|
+
this.buffer.pushMetric(eventName, this.userId, this.anonId, this.stripPrivate(props));
|
|
1199
|
+
}
|
|
1200
|
+
/** Drop caller-marked private attributes from an outbound props bag. */
|
|
1201
|
+
stripPrivate(props) {
|
|
1202
|
+
if (!props || this.privateAttributes.length === 0) return props;
|
|
1203
|
+
const out = {};
|
|
1204
|
+
for (const [k, v] of Object.entries(props)) {
|
|
1205
|
+
if (!this.privateAttributes.includes(k)) out[k] = v;
|
|
1206
|
+
}
|
|
1207
|
+
return out;
|
|
1135
1208
|
}
|
|
1136
1209
|
/**
|
|
1137
1210
|
* Read a killswitch from the server's evaluated state. Without `switchKey`,
|
|
@@ -1277,7 +1350,10 @@ function shipeasy(opts) {
|
|
|
1277
1350
|
autoGuardrails: blanket,
|
|
1278
1351
|
autoGuardrailGroups: groups,
|
|
1279
1352
|
autoCollectAlways: acObj?.always === true,
|
|
1280
|
-
disableTelemetry: opts.disableTelemetry
|
|
1353
|
+
disableTelemetry: opts.disableTelemetry,
|
|
1354
|
+
disableAutoExposure: opts.disableAutoExposure,
|
|
1355
|
+
privateAttributes: opts.privateAttributes,
|
|
1356
|
+
stickyBucketing: opts.stickyBucketing
|
|
1281
1357
|
});
|
|
1282
1358
|
injectI18nLoader(opts.clientKey, baseUrl, opts.i18nProfile);
|
|
1283
1359
|
flags.notifyMounted();
|
|
@@ -1385,12 +1461,19 @@ var flags = {
|
|
|
1385
1461
|
return void 0;
|
|
1386
1462
|
}
|
|
1387
1463
|
},
|
|
1388
|
-
getExperiment(name, defaultParams,
|
|
1389
|
-
|
|
1464
|
+
getExperiment(name, defaultParams, decodeOrOpts, variants) {
|
|
1465
|
+
const fallback = {
|
|
1390
1466
|
inExperiment: false,
|
|
1391
1467
|
group: "control",
|
|
1392
1468
|
params: defaultParams
|
|
1393
1469
|
};
|
|
1470
|
+
if (!_client) return fallback;
|
|
1471
|
+
return typeof decodeOrOpts === "function" ? _client.getExperiment(name, defaultParams, decodeOrOpts, variants) : _client.getExperiment(name, defaultParams, decodeOrOpts ?? {});
|
|
1472
|
+
},
|
|
1473
|
+
/** Manually log an exposure for an enrolled experiment. See
|
|
1474
|
+
* {@link FlagsClientBrowser.logExposure}. No-op before configure(). */
|
|
1475
|
+
logExposure(name) {
|
|
1476
|
+
_client?.logExposure(name);
|
|
1394
1477
|
},
|
|
1395
1478
|
track(eventName, props) {
|
|
1396
1479
|
_client?.track(eventName, props);
|