@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.js
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/client/index.ts
|
|
21
21
|
var client_exports = {};
|
|
22
22
|
__export(client_exports, {
|
|
23
|
+
FLAG_REASONS: () => FLAG_REASONS,
|
|
23
24
|
FlagsClientBrowser: () => FlagsClientBrowser,
|
|
24
25
|
LABEL_MARKER_END: () => LABEL_MARKER_END,
|
|
25
26
|
LABEL_MARKER_RE: () => LABEL_MARKER_RE,
|
|
@@ -358,6 +359,14 @@ var SeeLimiter = class {
|
|
|
358
359
|
|
|
359
360
|
// src/client/index.ts
|
|
360
361
|
var version = "4.0.0";
|
|
362
|
+
var FLAG_REASONS = [
|
|
363
|
+
"CLIENT_NOT_READY",
|
|
364
|
+
"FLAG_NOT_FOUND",
|
|
365
|
+
"OFF",
|
|
366
|
+
"OVERRIDE",
|
|
367
|
+
"RULE_MATCH",
|
|
368
|
+
"DEFAULT"
|
|
369
|
+
];
|
|
361
370
|
var FLUSH_INTERVAL_MS = 5e3;
|
|
362
371
|
var MAX_BUFFER = 100;
|
|
363
372
|
var ANON_ID_KEY = "__se_anon_id";
|
|
@@ -722,6 +731,40 @@ function writeAnonCookie(id) {
|
|
|
722
731
|
} catch {
|
|
723
732
|
}
|
|
724
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
|
+
}
|
|
725
768
|
function getOrCreateAnonId() {
|
|
726
769
|
let id = readAnonCookie();
|
|
727
770
|
if (!id && typeof window !== "undefined") {
|
|
@@ -869,12 +912,15 @@ function readExperimentOverridesFromUrl() {
|
|
|
869
912
|
}
|
|
870
913
|
return out;
|
|
871
914
|
}
|
|
872
|
-
var FlagsClientBrowser = class {
|
|
915
|
+
var FlagsClientBrowser = class _FlagsClientBrowser {
|
|
873
916
|
sdkKey;
|
|
874
917
|
baseUrl;
|
|
875
918
|
autoGuardrails;
|
|
876
919
|
autoGuardrailGroups;
|
|
877
920
|
autoCollectAlways;
|
|
921
|
+
disableAutoExposure;
|
|
922
|
+
privateAttributes;
|
|
923
|
+
stickyBucketing;
|
|
878
924
|
env;
|
|
879
925
|
evalResult = null;
|
|
880
926
|
anonId;
|
|
@@ -888,6 +934,16 @@ var FlagsClientBrowser = class {
|
|
|
888
934
|
// Monotonic counter so a later identify() always wins even if its /sdk/evaluate
|
|
889
935
|
// response races and lands before an earlier in-flight call's response.
|
|
890
936
|
identifySeq = 0;
|
|
937
|
+
// Test mode: built by `FlagsClientBrowser.forTesting()`. When set, identify()
|
|
938
|
+
// never fetches, track() is a no-op, telemetry is off, and the client is
|
|
939
|
+
// already ready with an empty eval result.
|
|
940
|
+
testMode;
|
|
941
|
+
// Programmatic overrides (Statsig-style). Set on any client via
|
|
942
|
+
// overrideFlag/overrideConfig/overrideExperiment; they win over BOTH the URL
|
|
943
|
+
// overrides and the fetched eval result. Cleared by clearOverrides().
|
|
944
|
+
flagOverrides = /* @__PURE__ */ new Map();
|
|
945
|
+
configOverrides = /* @__PURE__ */ new Map();
|
|
946
|
+
experimentOverrides = /* @__PURE__ */ new Map();
|
|
891
947
|
onOverrideChange = () => {
|
|
892
948
|
this.installBridge();
|
|
893
949
|
this.notify();
|
|
@@ -896,8 +952,12 @@ var FlagsClientBrowser = class {
|
|
|
896
952
|
this.sdkKey = opts.sdkKey;
|
|
897
953
|
this.baseUrl = (opts.baseUrl ?? "https://edge.shipeasy.dev").replace(/\/$/, "");
|
|
898
954
|
this.env = opts.env ?? "prod";
|
|
955
|
+
this.testMode = opts.testMode === true;
|
|
899
956
|
this.autoGuardrails = opts.autoGuardrails !== false;
|
|
900
957
|
this.autoCollectAlways = opts.autoCollectAlways === true;
|
|
958
|
+
this.disableAutoExposure = opts.disableAutoExposure === true;
|
|
959
|
+
this.privateAttributes = opts.privateAttributes ?? [];
|
|
960
|
+
this.stickyBucketing = opts.stickyBucketing !== false;
|
|
901
961
|
const g = opts.autoGuardrailGroups ?? {};
|
|
902
962
|
this.autoGuardrailGroups = {
|
|
903
963
|
vitals: g.vitals ?? this.autoGuardrails,
|
|
@@ -911,11 +971,42 @@ var FlagsClientBrowser = class {
|
|
|
911
971
|
sdkKey: this.sdkKey,
|
|
912
972
|
side: "client",
|
|
913
973
|
env: this.env,
|
|
914
|
-
|
|
974
|
+
// Test mode never talks to the network — telemetry off regardless of opt.
|
|
975
|
+
disabled: this.testMode || opts.disableTelemetry
|
|
976
|
+
});
|
|
977
|
+
if (this.testMode) {
|
|
978
|
+
this.evalResult = { flags: {}, configs: {}, experiments: {}, killswitches: {} };
|
|
979
|
+
} else {
|
|
980
|
+
void this.buffer.flushPendingAlias();
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Build a no-network, immediately-usable browser client for tests
|
|
985
|
+
* (Statsig-style). identify() is a no-op (never calls /sdk/evaluate), track()
|
|
986
|
+
* is a no-op, telemetry is disabled, and the client is already ready — seed
|
|
987
|
+
* every entity with overrideFlag/overrideConfig/overrideExperiment. No SDK
|
|
988
|
+
* key required.
|
|
989
|
+
*
|
|
990
|
+
* ```ts
|
|
991
|
+
* const client = FlagsClientBrowser.forTesting();
|
|
992
|
+
* client.overrideFlag("new_checkout", true);
|
|
993
|
+
* client.getFlag("new_checkout"); // true
|
|
994
|
+
* ```
|
|
995
|
+
*/
|
|
996
|
+
static forTesting(opts) {
|
|
997
|
+
return new _FlagsClientBrowser({
|
|
998
|
+
sdkKey: "",
|
|
999
|
+
autoGuardrails: false,
|
|
1000
|
+
...opts,
|
|
1001
|
+
testMode: true
|
|
915
1002
|
});
|
|
916
|
-
void this.buffer.flushPendingAlias();
|
|
917
1003
|
}
|
|
918
1004
|
async identify(user) {
|
|
1005
|
+
if (this.testMode) {
|
|
1006
|
+
if (user.user_id !== void 0) this.userId = user.user_id;
|
|
1007
|
+
this.notify();
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
919
1010
|
const seq = ++this.identifySeq;
|
|
920
1011
|
const prevUserId = this.userId;
|
|
921
1012
|
if (user.user_id !== void 0) this.userId = user.user_id;
|
|
@@ -932,13 +1023,22 @@ var FlagsClientBrowser = class {
|
|
|
932
1023
|
headers: { "X-SDK-Key": this.sdkKey, "Content-Type": "application/json" },
|
|
933
1024
|
body: JSON.stringify({
|
|
934
1025
|
user: userPayload,
|
|
935
|
-
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() } : {}
|
|
936
1033
|
})
|
|
937
1034
|
});
|
|
938
1035
|
if (!res.ok) throw new Error(`/sdk/evaluate returned ${res.status}`);
|
|
939
1036
|
const data = await res.json();
|
|
940
1037
|
if (seq !== this.identifySeq) return;
|
|
941
1038
|
this.evalResult = data;
|
|
1039
|
+
if (this.stickyBucketing && data.sticky && Object.keys(data.sticky).length > 0) {
|
|
1040
|
+
writeStickyCookie({ ...readStickyCookie(), ...data.sticky });
|
|
1041
|
+
}
|
|
942
1042
|
const anyGroupOn = this.autoGuardrailGroups.vitals || this.autoGuardrailGroups.errors || this.autoGuardrailGroups.engagement;
|
|
943
1043
|
if (anyGroupOn && !this.guardrailsInstalled) {
|
|
944
1044
|
this.guardrailsInstalled = true;
|
|
@@ -990,34 +1090,95 @@ var FlagsClientBrowser = class {
|
|
|
990
1090
|
initFromBootstrap(data) {
|
|
991
1091
|
this.evalResult = data;
|
|
992
1092
|
}
|
|
993
|
-
|
|
1093
|
+
// ---- Local overrides (Statsig-style) ----
|
|
1094
|
+
//
|
|
1095
|
+
// Precedence (highest first): programmatic override (these methods) > URL
|
|
1096
|
+
// override (?se_gate_/?se_config_/?se_exp_) > fetched eval result. A
|
|
1097
|
+
// programmatic override is an explicit in-code decision, so it wins over the
|
|
1098
|
+
// ad-hoc URL/devtools overrides as well as the server's evaluation.
|
|
1099
|
+
/** Force `getFlag(name)` to return `value`, ignoring URL overrides + eval. */
|
|
1100
|
+
overrideFlag(name, value) {
|
|
1101
|
+
this.flagOverrides.set(name, value);
|
|
1102
|
+
}
|
|
1103
|
+
/** Force `getConfig(name)` to return `value`, ignoring URL overrides + eval. */
|
|
1104
|
+
overrideConfig(name, value) {
|
|
1105
|
+
this.configOverrides.set(name, value);
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Force `getExperiment(name, …)` to return `{ inExperiment: true, group, params }`,
|
|
1109
|
+
* ignoring URL overrides, the eval result, and exposure logging.
|
|
1110
|
+
*/
|
|
1111
|
+
overrideExperiment(name, group, params) {
|
|
1112
|
+
this.experimentOverrides.set(name, { group, params });
|
|
1113
|
+
}
|
|
1114
|
+
/** Remove every programmatic override set via the override* methods. */
|
|
1115
|
+
clearOverrides() {
|
|
1116
|
+
this.flagOverrides.clear();
|
|
1117
|
+
this.configOverrides.clear();
|
|
1118
|
+
this.experimentOverrides.clear();
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Evaluate a gate and report WHY (LaunchDarkly variationDetail parity). A
|
|
1122
|
+
* local override OR a ?se_gate_/?se_ks_ URL override short-circuits BEFORE
|
|
1123
|
+
* telemetry; otherwise exactly one "gate" beacon is emitted. The server
|
|
1124
|
+
* pre-evaluates a gate's enabled/killed state into a plain boolean, so OFF
|
|
1125
|
+
* folds into DEFAULT here (the browser can't tell "disabled" from "rule
|
|
1126
|
+
* denied").
|
|
1127
|
+
*/
|
|
1128
|
+
getFlagDetail(name) {
|
|
1129
|
+
const pov = this.flagOverrides.get(name);
|
|
1130
|
+
if (pov !== void 0) return { value: pov, reason: "OVERRIDE" };
|
|
1131
|
+
const urlOv = readGateOverride(name);
|
|
1132
|
+
if (urlOv !== null) return { value: urlOv, reason: "OVERRIDE" };
|
|
994
1133
|
this.telemetry.emit("gate", name);
|
|
995
|
-
if (this.evalResult === null) return false;
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
return
|
|
1134
|
+
if (this.evalResult === null) return { value: false, reason: "CLIENT_NOT_READY" };
|
|
1135
|
+
if (!(name in this.evalResult.flags)) return { value: false, reason: "FLAG_NOT_FOUND" };
|
|
1136
|
+
const value = this.evalResult.flags[name] ?? false;
|
|
1137
|
+
return { value, reason: value ? "RULE_MATCH" : "DEFAULT" };
|
|
999
1138
|
}
|
|
1000
|
-
|
|
1139
|
+
/**
|
|
1140
|
+
* Read a feature gate. Returns `defaultValue` ONLY when the gate cannot be
|
|
1141
|
+
* evaluated (not ready or flag not found) — never for a gate that legitimately
|
|
1142
|
+
* evaluates to false. Plain `getFlag(name)` keeps returning false for a
|
|
1143
|
+
* missing flag.
|
|
1144
|
+
*/
|
|
1145
|
+
getFlag(name, defaultValue = false) {
|
|
1146
|
+
const d = this.getFlagDetail(name);
|
|
1147
|
+
if (d.reason === "CLIENT_NOT_READY" || d.reason === "FLAG_NOT_FOUND") return defaultValue;
|
|
1148
|
+
return d.value;
|
|
1149
|
+
}
|
|
1150
|
+
getConfig(name, decodeOrOpts) {
|
|
1001
1151
|
this.telemetry.emit("config", name);
|
|
1002
|
-
|
|
1003
|
-
const
|
|
1004
|
-
const
|
|
1005
|
-
if (
|
|
1006
|
-
|
|
1152
|
+
const opts = typeof decodeOrOpts === "function" ? { decode: decodeOrOpts } : decodeOrOpts ?? {};
|
|
1153
|
+
const fallback = "defaultValue" in opts ? opts.defaultValue : void 0;
|
|
1154
|
+
const hasProgrammatic = this.configOverrides.has(name);
|
|
1155
|
+
if (!hasProgrammatic && this.evalResult === null) return fallback;
|
|
1156
|
+
const urlOv = hasProgrammatic ? void 0 : readConfigOverride(name);
|
|
1157
|
+
const raw = hasProgrammatic ? this.configOverrides.get(name) : urlOv !== void 0 ? urlOv : this.evalResult?.configs?.[name];
|
|
1158
|
+
if (raw === void 0) return fallback;
|
|
1159
|
+
if (!opts.decode) return raw;
|
|
1007
1160
|
try {
|
|
1008
|
-
return decode(raw);
|
|
1161
|
+
return opts.decode(raw);
|
|
1009
1162
|
} catch (err) {
|
|
1010
1163
|
console.warn(`[shipeasy] getConfig('${name}') decode failed:`, String(err));
|
|
1011
1164
|
return void 0;
|
|
1012
1165
|
}
|
|
1013
1166
|
}
|
|
1014
|
-
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;
|
|
1015
1170
|
this.telemetry.emit("experiment", name);
|
|
1016
1171
|
const notIn = {
|
|
1017
1172
|
inExperiment: false,
|
|
1018
1173
|
group: "control",
|
|
1019
1174
|
params: defaultParams
|
|
1020
1175
|
};
|
|
1176
|
+
const pov = this.experimentOverrides.get(name);
|
|
1177
|
+
if (pov) {
|
|
1178
|
+
const variantParams = variants?.[pov.group];
|
|
1179
|
+
const params = { ...defaultParams, ...pov.params, ...variantParams ?? {} };
|
|
1180
|
+
return { inExperiment: true, group: pov.group, params };
|
|
1181
|
+
}
|
|
1021
1182
|
const ov = readExpOverride(name);
|
|
1022
1183
|
if (ov !== null) {
|
|
1023
1184
|
const variantParams = variants?.[ov];
|
|
@@ -1026,7 +1187,8 @@ var FlagsClientBrowser = class {
|
|
|
1026
1187
|
}
|
|
1027
1188
|
const entry = this.evalResult?.experiments[name];
|
|
1028
1189
|
if (!entry || !entry.inExperiment) return notIn;
|
|
1029
|
-
|
|
1190
|
+
const shouldLog = opts.logExposure ?? !this.disableAutoExposure;
|
|
1191
|
+
if (shouldLog) this.buffer.pushExposure(name, entry.group, this.userId, this.anonId);
|
|
1030
1192
|
if (!decode) return { inExperiment: true, group: entry.group, params: entry.params };
|
|
1031
1193
|
try {
|
|
1032
1194
|
return { inExperiment: true, group: entry.group, params: decode(entry.params) };
|
|
@@ -1035,6 +1197,18 @@ var FlagsClientBrowser = class {
|
|
|
1035
1197
|
return notIn;
|
|
1036
1198
|
}
|
|
1037
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
|
+
}
|
|
1038
1212
|
/**
|
|
1039
1213
|
* Subscribe to state changes — fires after identify() completes and on
|
|
1040
1214
|
* `se:override:change` events from the devtools overlay. Returns an
|
|
@@ -1069,7 +1243,17 @@ var FlagsClientBrowser = class {
|
|
|
1069
1243
|
return bridge;
|
|
1070
1244
|
}
|
|
1071
1245
|
track(eventName, props) {
|
|
1072
|
-
|
|
1246
|
+
if (this.testMode) return;
|
|
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;
|
|
1073
1257
|
}
|
|
1074
1258
|
/**
|
|
1075
1259
|
* Read a killswitch from the server's evaluated state. Without `switchKey`,
|
|
@@ -1215,7 +1399,10 @@ function shipeasy(opts) {
|
|
|
1215
1399
|
autoGuardrails: blanket,
|
|
1216
1400
|
autoGuardrailGroups: groups,
|
|
1217
1401
|
autoCollectAlways: acObj?.always === true,
|
|
1218
|
-
disableTelemetry: opts.disableTelemetry
|
|
1402
|
+
disableTelemetry: opts.disableTelemetry,
|
|
1403
|
+
disableAutoExposure: opts.disableAutoExposure,
|
|
1404
|
+
privateAttributes: opts.privateAttributes,
|
|
1405
|
+
stickyBucketing: opts.stickyBucketing
|
|
1219
1406
|
});
|
|
1220
1407
|
injectI18nLoader(opts.clientKey, baseUrl, opts.i18nProfile);
|
|
1221
1408
|
flags.notifyMounted();
|
|
@@ -1287,12 +1474,19 @@ var flags = {
|
|
|
1287
1474
|
* Everything else gates on _mountedAndReady to prevent hydration mismatches on
|
|
1288
1475
|
* force-static pages where SSR has no flag data.
|
|
1289
1476
|
*/
|
|
1290
|
-
get(name) {
|
|
1477
|
+
get(name, defaultValue = false) {
|
|
1291
1478
|
const bs = getBootstrap();
|
|
1292
1479
|
if (bs !== null && name in bs.flags) return bs.flags[name];
|
|
1293
|
-
if (!_mountedAndReady) return
|
|
1294
|
-
if (_client) return _client.getFlag(name);
|
|
1295
|
-
return readGateOverride(name) ??
|
|
1480
|
+
if (!_mountedAndReady) return defaultValue;
|
|
1481
|
+
if (_client) return _client.getFlag(name, defaultValue);
|
|
1482
|
+
return readGateOverride(name) ?? defaultValue;
|
|
1483
|
+
},
|
|
1484
|
+
/** Evaluate a gate and report why (value + reason). See {@link FlagDetail}. */
|
|
1485
|
+
getDetail(name) {
|
|
1486
|
+
if (_client) return _client.getFlagDetail(name);
|
|
1487
|
+
const ov = readGateOverride(name);
|
|
1488
|
+
if (ov !== null) return { value: ov, reason: "OVERRIDE" };
|
|
1489
|
+
return { value: false, reason: "CLIENT_NOT_READY" };
|
|
1296
1490
|
},
|
|
1297
1491
|
getConfig(name, decode) {
|
|
1298
1492
|
const bs = getBootstrap();
|
|
@@ -1316,12 +1510,19 @@ var flags = {
|
|
|
1316
1510
|
return void 0;
|
|
1317
1511
|
}
|
|
1318
1512
|
},
|
|
1319
|
-
getExperiment(name, defaultParams,
|
|
1320
|
-
|
|
1513
|
+
getExperiment(name, defaultParams, decodeOrOpts, variants) {
|
|
1514
|
+
const fallback = {
|
|
1321
1515
|
inExperiment: false,
|
|
1322
1516
|
group: "control",
|
|
1323
1517
|
params: defaultParams
|
|
1324
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);
|
|
1325
1526
|
},
|
|
1326
1527
|
track(eventName, props) {
|
|
1327
1528
|
_client?.track(eventName, props);
|
|
@@ -1624,6 +1825,7 @@ var i18n = {
|
|
|
1624
1825
|
};
|
|
1625
1826
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1626
1827
|
0 && (module.exports = {
|
|
1828
|
+
FLAG_REASONS,
|
|
1627
1829
|
FlagsClientBrowser,
|
|
1628
1830
|
LABEL_MARKER_END,
|
|
1629
1831
|
LABEL_MARKER_RE,
|