@shipeasy/sdk 5.0.0 → 5.2.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/README.md +158 -0
- package/dist/client/index.d.mts +169 -4
- package/dist/client/index.d.ts +169 -4
- package/dist/client/index.js +228 -26
- package/dist/client/index.mjs +227 -26
- 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 +198 -4
- package/dist/server/index.d.ts +198 -4
- package/dist/server/index.js +290 -36
- package/dist/server/index.mjs +294 -36
- package/package.json +23 -1
package/dist/client/index.mjs
CHANGED
|
@@ -310,6 +310,14 @@ var SeeLimiter = class {
|
|
|
310
310
|
|
|
311
311
|
// src/client/index.ts
|
|
312
312
|
var version = "4.0.0";
|
|
313
|
+
var FLAG_REASONS = [
|
|
314
|
+
"CLIENT_NOT_READY",
|
|
315
|
+
"FLAG_NOT_FOUND",
|
|
316
|
+
"OFF",
|
|
317
|
+
"OVERRIDE",
|
|
318
|
+
"RULE_MATCH",
|
|
319
|
+
"DEFAULT"
|
|
320
|
+
];
|
|
313
321
|
var FLUSH_INTERVAL_MS = 5e3;
|
|
314
322
|
var MAX_BUFFER = 100;
|
|
315
323
|
var ANON_ID_KEY = "__se_anon_id";
|
|
@@ -674,6 +682,40 @@ function writeAnonCookie(id) {
|
|
|
674
682
|
} catch {
|
|
675
683
|
}
|
|
676
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
|
+
}
|
|
677
719
|
function getOrCreateAnonId() {
|
|
678
720
|
let id = readAnonCookie();
|
|
679
721
|
if (!id && typeof window !== "undefined") {
|
|
@@ -821,12 +863,15 @@ function readExperimentOverridesFromUrl() {
|
|
|
821
863
|
}
|
|
822
864
|
return out;
|
|
823
865
|
}
|
|
824
|
-
var FlagsClientBrowser = class {
|
|
866
|
+
var FlagsClientBrowser = class _FlagsClientBrowser {
|
|
825
867
|
sdkKey;
|
|
826
868
|
baseUrl;
|
|
827
869
|
autoGuardrails;
|
|
828
870
|
autoGuardrailGroups;
|
|
829
871
|
autoCollectAlways;
|
|
872
|
+
disableAutoExposure;
|
|
873
|
+
privateAttributes;
|
|
874
|
+
stickyBucketing;
|
|
830
875
|
env;
|
|
831
876
|
evalResult = null;
|
|
832
877
|
anonId;
|
|
@@ -840,6 +885,16 @@ var FlagsClientBrowser = class {
|
|
|
840
885
|
// Monotonic counter so a later identify() always wins even if its /sdk/evaluate
|
|
841
886
|
// response races and lands before an earlier in-flight call's response.
|
|
842
887
|
identifySeq = 0;
|
|
888
|
+
// Test mode: built by `FlagsClientBrowser.forTesting()`. When set, identify()
|
|
889
|
+
// never fetches, track() is a no-op, telemetry is off, and the client is
|
|
890
|
+
// already ready with an empty eval result.
|
|
891
|
+
testMode;
|
|
892
|
+
// Programmatic overrides (Statsig-style). Set on any client via
|
|
893
|
+
// overrideFlag/overrideConfig/overrideExperiment; they win over BOTH the URL
|
|
894
|
+
// overrides and the fetched eval result. Cleared by clearOverrides().
|
|
895
|
+
flagOverrides = /* @__PURE__ */ new Map();
|
|
896
|
+
configOverrides = /* @__PURE__ */ new Map();
|
|
897
|
+
experimentOverrides = /* @__PURE__ */ new Map();
|
|
843
898
|
onOverrideChange = () => {
|
|
844
899
|
this.installBridge();
|
|
845
900
|
this.notify();
|
|
@@ -848,8 +903,12 @@ var FlagsClientBrowser = class {
|
|
|
848
903
|
this.sdkKey = opts.sdkKey;
|
|
849
904
|
this.baseUrl = (opts.baseUrl ?? "https://edge.shipeasy.dev").replace(/\/$/, "");
|
|
850
905
|
this.env = opts.env ?? "prod";
|
|
906
|
+
this.testMode = opts.testMode === true;
|
|
851
907
|
this.autoGuardrails = opts.autoGuardrails !== false;
|
|
852
908
|
this.autoCollectAlways = opts.autoCollectAlways === true;
|
|
909
|
+
this.disableAutoExposure = opts.disableAutoExposure === true;
|
|
910
|
+
this.privateAttributes = opts.privateAttributes ?? [];
|
|
911
|
+
this.stickyBucketing = opts.stickyBucketing !== false;
|
|
853
912
|
const g = opts.autoGuardrailGroups ?? {};
|
|
854
913
|
this.autoGuardrailGroups = {
|
|
855
914
|
vitals: g.vitals ?? this.autoGuardrails,
|
|
@@ -863,11 +922,42 @@ var FlagsClientBrowser = class {
|
|
|
863
922
|
sdkKey: this.sdkKey,
|
|
864
923
|
side: "client",
|
|
865
924
|
env: this.env,
|
|
866
|
-
|
|
925
|
+
// Test mode never talks to the network — telemetry off regardless of opt.
|
|
926
|
+
disabled: this.testMode || opts.disableTelemetry
|
|
927
|
+
});
|
|
928
|
+
if (this.testMode) {
|
|
929
|
+
this.evalResult = { flags: {}, configs: {}, experiments: {}, killswitches: {} };
|
|
930
|
+
} else {
|
|
931
|
+
void this.buffer.flushPendingAlias();
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Build a no-network, immediately-usable browser client for tests
|
|
936
|
+
* (Statsig-style). identify() is a no-op (never calls /sdk/evaluate), track()
|
|
937
|
+
* is a no-op, telemetry is disabled, and the client is already ready — seed
|
|
938
|
+
* every entity with overrideFlag/overrideConfig/overrideExperiment. No SDK
|
|
939
|
+
* key required.
|
|
940
|
+
*
|
|
941
|
+
* ```ts
|
|
942
|
+
* const client = FlagsClientBrowser.forTesting();
|
|
943
|
+
* client.overrideFlag("new_checkout", true);
|
|
944
|
+
* client.getFlag("new_checkout"); // true
|
|
945
|
+
* ```
|
|
946
|
+
*/
|
|
947
|
+
static forTesting(opts) {
|
|
948
|
+
return new _FlagsClientBrowser({
|
|
949
|
+
sdkKey: "",
|
|
950
|
+
autoGuardrails: false,
|
|
951
|
+
...opts,
|
|
952
|
+
testMode: true
|
|
867
953
|
});
|
|
868
|
-
void this.buffer.flushPendingAlias();
|
|
869
954
|
}
|
|
870
955
|
async identify(user) {
|
|
956
|
+
if (this.testMode) {
|
|
957
|
+
if (user.user_id !== void 0) this.userId = user.user_id;
|
|
958
|
+
this.notify();
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
871
961
|
const seq = ++this.identifySeq;
|
|
872
962
|
const prevUserId = this.userId;
|
|
873
963
|
if (user.user_id !== void 0) this.userId = user.user_id;
|
|
@@ -884,13 +974,22 @@ var FlagsClientBrowser = class {
|
|
|
884
974
|
headers: { "X-SDK-Key": this.sdkKey, "Content-Type": "application/json" },
|
|
885
975
|
body: JSON.stringify({
|
|
886
976
|
user: userPayload,
|
|
887
|
-
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() } : {}
|
|
888
984
|
})
|
|
889
985
|
});
|
|
890
986
|
if (!res.ok) throw new Error(`/sdk/evaluate returned ${res.status}`);
|
|
891
987
|
const data = await res.json();
|
|
892
988
|
if (seq !== this.identifySeq) return;
|
|
893
989
|
this.evalResult = data;
|
|
990
|
+
if (this.stickyBucketing && data.sticky && Object.keys(data.sticky).length > 0) {
|
|
991
|
+
writeStickyCookie({ ...readStickyCookie(), ...data.sticky });
|
|
992
|
+
}
|
|
894
993
|
const anyGroupOn = this.autoGuardrailGroups.vitals || this.autoGuardrailGroups.errors || this.autoGuardrailGroups.engagement;
|
|
895
994
|
if (anyGroupOn && !this.guardrailsInstalled) {
|
|
896
995
|
this.guardrailsInstalled = true;
|
|
@@ -942,34 +1041,95 @@ var FlagsClientBrowser = class {
|
|
|
942
1041
|
initFromBootstrap(data) {
|
|
943
1042
|
this.evalResult = data;
|
|
944
1043
|
}
|
|
945
|
-
|
|
1044
|
+
// ---- Local overrides (Statsig-style) ----
|
|
1045
|
+
//
|
|
1046
|
+
// Precedence (highest first): programmatic override (these methods) > URL
|
|
1047
|
+
// override (?se_gate_/?se_config_/?se_exp_) > fetched eval result. A
|
|
1048
|
+
// programmatic override is an explicit in-code decision, so it wins over the
|
|
1049
|
+
// ad-hoc URL/devtools overrides as well as the server's evaluation.
|
|
1050
|
+
/** Force `getFlag(name)` to return `value`, ignoring URL overrides + eval. */
|
|
1051
|
+
overrideFlag(name, value) {
|
|
1052
|
+
this.flagOverrides.set(name, value);
|
|
1053
|
+
}
|
|
1054
|
+
/** Force `getConfig(name)` to return `value`, ignoring URL overrides + eval. */
|
|
1055
|
+
overrideConfig(name, value) {
|
|
1056
|
+
this.configOverrides.set(name, value);
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Force `getExperiment(name, …)` to return `{ inExperiment: true, group, params }`,
|
|
1060
|
+
* ignoring URL overrides, the eval result, and exposure logging.
|
|
1061
|
+
*/
|
|
1062
|
+
overrideExperiment(name, group, params) {
|
|
1063
|
+
this.experimentOverrides.set(name, { group, params });
|
|
1064
|
+
}
|
|
1065
|
+
/** Remove every programmatic override set via the override* methods. */
|
|
1066
|
+
clearOverrides() {
|
|
1067
|
+
this.flagOverrides.clear();
|
|
1068
|
+
this.configOverrides.clear();
|
|
1069
|
+
this.experimentOverrides.clear();
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Evaluate a gate and report WHY (LaunchDarkly variationDetail parity). A
|
|
1073
|
+
* local override OR a ?se_gate_/?se_ks_ URL override short-circuits BEFORE
|
|
1074
|
+
* telemetry; otherwise exactly one "gate" beacon is emitted. The server
|
|
1075
|
+
* pre-evaluates a gate's enabled/killed state into a plain boolean, so OFF
|
|
1076
|
+
* folds into DEFAULT here (the browser can't tell "disabled" from "rule
|
|
1077
|
+
* denied").
|
|
1078
|
+
*/
|
|
1079
|
+
getFlagDetail(name) {
|
|
1080
|
+
const pov = this.flagOverrides.get(name);
|
|
1081
|
+
if (pov !== void 0) return { value: pov, reason: "OVERRIDE" };
|
|
1082
|
+
const urlOv = readGateOverride(name);
|
|
1083
|
+
if (urlOv !== null) return { value: urlOv, reason: "OVERRIDE" };
|
|
946
1084
|
this.telemetry.emit("gate", name);
|
|
947
|
-
if (this.evalResult === null) return false;
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
return
|
|
1085
|
+
if (this.evalResult === null) return { value: false, reason: "CLIENT_NOT_READY" };
|
|
1086
|
+
if (!(name in this.evalResult.flags)) return { value: false, reason: "FLAG_NOT_FOUND" };
|
|
1087
|
+
const value = this.evalResult.flags[name] ?? false;
|
|
1088
|
+
return { value, reason: value ? "RULE_MATCH" : "DEFAULT" };
|
|
951
1089
|
}
|
|
952
|
-
|
|
1090
|
+
/**
|
|
1091
|
+
* Read a feature gate. Returns `defaultValue` ONLY when the gate cannot be
|
|
1092
|
+
* evaluated (not ready or flag not found) — never for a gate that legitimately
|
|
1093
|
+
* evaluates to false. Plain `getFlag(name)` keeps returning false for a
|
|
1094
|
+
* missing flag.
|
|
1095
|
+
*/
|
|
1096
|
+
getFlag(name, defaultValue = false) {
|
|
1097
|
+
const d = this.getFlagDetail(name);
|
|
1098
|
+
if (d.reason === "CLIENT_NOT_READY" || d.reason === "FLAG_NOT_FOUND") return defaultValue;
|
|
1099
|
+
return d.value;
|
|
1100
|
+
}
|
|
1101
|
+
getConfig(name, decodeOrOpts) {
|
|
953
1102
|
this.telemetry.emit("config", name);
|
|
954
|
-
|
|
955
|
-
const
|
|
956
|
-
const
|
|
957
|
-
if (
|
|
958
|
-
|
|
1103
|
+
const opts = typeof decodeOrOpts === "function" ? { decode: decodeOrOpts } : decodeOrOpts ?? {};
|
|
1104
|
+
const fallback = "defaultValue" in opts ? opts.defaultValue : void 0;
|
|
1105
|
+
const hasProgrammatic = this.configOverrides.has(name);
|
|
1106
|
+
if (!hasProgrammatic && this.evalResult === null) return fallback;
|
|
1107
|
+
const urlOv = hasProgrammatic ? void 0 : readConfigOverride(name);
|
|
1108
|
+
const raw = hasProgrammatic ? this.configOverrides.get(name) : urlOv !== void 0 ? urlOv : this.evalResult?.configs?.[name];
|
|
1109
|
+
if (raw === void 0) return fallback;
|
|
1110
|
+
if (!opts.decode) return raw;
|
|
959
1111
|
try {
|
|
960
|
-
return decode(raw);
|
|
1112
|
+
return opts.decode(raw);
|
|
961
1113
|
} catch (err) {
|
|
962
1114
|
console.warn(`[shipeasy] getConfig('${name}') decode failed:`, String(err));
|
|
963
1115
|
return void 0;
|
|
964
1116
|
}
|
|
965
1117
|
}
|
|
966
|
-
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;
|
|
967
1121
|
this.telemetry.emit("experiment", name);
|
|
968
1122
|
const notIn = {
|
|
969
1123
|
inExperiment: false,
|
|
970
1124
|
group: "control",
|
|
971
1125
|
params: defaultParams
|
|
972
1126
|
};
|
|
1127
|
+
const pov = this.experimentOverrides.get(name);
|
|
1128
|
+
if (pov) {
|
|
1129
|
+
const variantParams = variants?.[pov.group];
|
|
1130
|
+
const params = { ...defaultParams, ...pov.params, ...variantParams ?? {} };
|
|
1131
|
+
return { inExperiment: true, group: pov.group, params };
|
|
1132
|
+
}
|
|
973
1133
|
const ov = readExpOverride(name);
|
|
974
1134
|
if (ov !== null) {
|
|
975
1135
|
const variantParams = variants?.[ov];
|
|
@@ -978,7 +1138,8 @@ var FlagsClientBrowser = class {
|
|
|
978
1138
|
}
|
|
979
1139
|
const entry = this.evalResult?.experiments[name];
|
|
980
1140
|
if (!entry || !entry.inExperiment) return notIn;
|
|
981
|
-
|
|
1141
|
+
const shouldLog = opts.logExposure ?? !this.disableAutoExposure;
|
|
1142
|
+
if (shouldLog) this.buffer.pushExposure(name, entry.group, this.userId, this.anonId);
|
|
982
1143
|
if (!decode) return { inExperiment: true, group: entry.group, params: entry.params };
|
|
983
1144
|
try {
|
|
984
1145
|
return { inExperiment: true, group: entry.group, params: decode(entry.params) };
|
|
@@ -987,6 +1148,18 @@ var FlagsClientBrowser = class {
|
|
|
987
1148
|
return notIn;
|
|
988
1149
|
}
|
|
989
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
|
+
}
|
|
990
1163
|
/**
|
|
991
1164
|
* Subscribe to state changes — fires after identify() completes and on
|
|
992
1165
|
* `se:override:change` events from the devtools overlay. Returns an
|
|
@@ -1021,7 +1194,17 @@ var FlagsClientBrowser = class {
|
|
|
1021
1194
|
return bridge;
|
|
1022
1195
|
}
|
|
1023
1196
|
track(eventName, props) {
|
|
1024
|
-
|
|
1197
|
+
if (this.testMode) return;
|
|
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;
|
|
1025
1208
|
}
|
|
1026
1209
|
/**
|
|
1027
1210
|
* Read a killswitch from the server's evaluated state. Without `switchKey`,
|
|
@@ -1167,7 +1350,10 @@ function shipeasy(opts) {
|
|
|
1167
1350
|
autoGuardrails: blanket,
|
|
1168
1351
|
autoGuardrailGroups: groups,
|
|
1169
1352
|
autoCollectAlways: acObj?.always === true,
|
|
1170
|
-
disableTelemetry: opts.disableTelemetry
|
|
1353
|
+
disableTelemetry: opts.disableTelemetry,
|
|
1354
|
+
disableAutoExposure: opts.disableAutoExposure,
|
|
1355
|
+
privateAttributes: opts.privateAttributes,
|
|
1356
|
+
stickyBucketing: opts.stickyBucketing
|
|
1171
1357
|
});
|
|
1172
1358
|
injectI18nLoader(opts.clientKey, baseUrl, opts.i18nProfile);
|
|
1173
1359
|
flags.notifyMounted();
|
|
@@ -1239,12 +1425,19 @@ var flags = {
|
|
|
1239
1425
|
* Everything else gates on _mountedAndReady to prevent hydration mismatches on
|
|
1240
1426
|
* force-static pages where SSR has no flag data.
|
|
1241
1427
|
*/
|
|
1242
|
-
get(name) {
|
|
1428
|
+
get(name, defaultValue = false) {
|
|
1243
1429
|
const bs = getBootstrap();
|
|
1244
1430
|
if (bs !== null && name in bs.flags) return bs.flags[name];
|
|
1245
|
-
if (!_mountedAndReady) return
|
|
1246
|
-
if (_client) return _client.getFlag(name);
|
|
1247
|
-
return readGateOverride(name) ??
|
|
1431
|
+
if (!_mountedAndReady) return defaultValue;
|
|
1432
|
+
if (_client) return _client.getFlag(name, defaultValue);
|
|
1433
|
+
return readGateOverride(name) ?? defaultValue;
|
|
1434
|
+
},
|
|
1435
|
+
/** Evaluate a gate and report why (value + reason). See {@link FlagDetail}. */
|
|
1436
|
+
getDetail(name) {
|
|
1437
|
+
if (_client) return _client.getFlagDetail(name);
|
|
1438
|
+
const ov = readGateOverride(name);
|
|
1439
|
+
if (ov !== null) return { value: ov, reason: "OVERRIDE" };
|
|
1440
|
+
return { value: false, reason: "CLIENT_NOT_READY" };
|
|
1248
1441
|
},
|
|
1249
1442
|
getConfig(name, decode) {
|
|
1250
1443
|
const bs = getBootstrap();
|
|
@@ -1268,12 +1461,19 @@ var flags = {
|
|
|
1268
1461
|
return void 0;
|
|
1269
1462
|
}
|
|
1270
1463
|
},
|
|
1271
|
-
getExperiment(name, defaultParams,
|
|
1272
|
-
|
|
1464
|
+
getExperiment(name, defaultParams, decodeOrOpts, variants) {
|
|
1465
|
+
const fallback = {
|
|
1273
1466
|
inExperiment: false,
|
|
1274
1467
|
group: "control",
|
|
1275
1468
|
params: defaultParams
|
|
1276
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);
|
|
1277
1477
|
},
|
|
1278
1478
|
track(eventName, props) {
|
|
1279
1479
|
_client?.track(eventName, props);
|
|
@@ -1575,6 +1775,7 @@ var i18n = {
|
|
|
1575
1775
|
}
|
|
1576
1776
|
};
|
|
1577
1777
|
export {
|
|
1778
|
+
FLAG_REASONS,
|
|
1578
1779
|
FlagsClientBrowser,
|
|
1579
1780
|
LABEL_MARKER_END,
|
|
1580
1781
|
LABEL_MARKER_RE,
|