@shipeasy/sdk 6.3.1 → 7.1.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 +1 -4
- package/dist/client/index.d.mts +166 -27
- package/dist/client/index.d.ts +166 -27
- package/dist/client/index.js +268 -132
- package/dist/client/index.mjs +268 -132
- package/dist/openfeature-server/index.d.mts +144 -14
- package/dist/openfeature-server/index.d.ts +144 -14
- package/dist/openfeature-server/index.js +65 -33
- package/dist/openfeature-server/index.mjs +65 -33
- package/dist/openfeature-web/index.d.mts +110 -30
- package/dist/openfeature-web/index.d.ts +110 -30
- package/dist/server/index.d.mts +181 -28
- package/dist/server/index.d.ts +181 -28
- package/dist/server/index.js +337 -159
- package/dist/server/index.mjs +337 -159
- package/docs/skill/SKILL.md +20 -10
- package/package.json +3 -1
package/dist/client/index.mjs
CHANGED
|
@@ -57,50 +57,21 @@ function send(url) {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
// src/
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
warn: 2,
|
|
66
|
-
info: 3,
|
|
67
|
-
debug: 4
|
|
68
|
-
};
|
|
69
|
-
var currentLevel = "warn";
|
|
70
|
-
function setLogLevel(level) {
|
|
71
|
-
if (typeof level === "string" && Object.prototype.hasOwnProperty.call(RANK, level)) {
|
|
72
|
-
currentLevel = level;
|
|
73
|
-
}
|
|
60
|
+
// src/env.ts
|
|
61
|
+
function isProductionEnv(configuredEnv) {
|
|
62
|
+
const native = readNativeEnv();
|
|
63
|
+
if (native !== null) return native === "production" || native === "prod";
|
|
64
|
+
return (configuredEnv ?? "prod").toLowerCase() === "prod";
|
|
74
65
|
}
|
|
75
|
-
function
|
|
66
|
+
function readNativeEnv() {
|
|
76
67
|
try {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
68
|
+
const proc = globalThis.process;
|
|
69
|
+
const raw = proc?.env?.SHIPEASY_ENV ?? proc?.env?.NODE_ENV;
|
|
70
|
+
if (typeof raw !== "string") return null;
|
|
71
|
+
const v = raw.trim().toLowerCase();
|
|
72
|
+
return v.length ? v : null;
|
|
81
73
|
} catch {
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
var logger = {
|
|
85
|
-
error(...args) {
|
|
86
|
-
if (RANK[currentLevel] >= RANK.error) write("error", args);
|
|
87
|
-
},
|
|
88
|
-
warn(...args) {
|
|
89
|
-
if (RANK[currentLevel] >= RANK.warn) write("warn", args);
|
|
90
|
-
},
|
|
91
|
-
info(...args) {
|
|
92
|
-
if (RANK[currentLevel] >= RANK.info) write("info", args);
|
|
93
|
-
},
|
|
94
|
-
debug(...args) {
|
|
95
|
-
if (RANK[currentLevel] >= RANK.debug) write("debug", args);
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
function safeRun(label, fallback, fn) {
|
|
99
|
-
try {
|
|
100
|
-
return fn();
|
|
101
|
-
} catch (err) {
|
|
102
|
-
logger.error(`[shipeasy] ${label} failed \u2014 returning safe default:`, String(err));
|
|
103
|
-
return fallback;
|
|
74
|
+
return null;
|
|
104
75
|
}
|
|
105
76
|
}
|
|
106
77
|
|
|
@@ -221,7 +192,7 @@ function captureCallsiteStack() {
|
|
|
221
192
|
const kept = lines.slice(1).filter((l) => !/@shipeasy[\\/]sdk|see[\\/]core|captureCallsiteStack|\bsee\b\s*\(/.test(l));
|
|
222
193
|
return kept.length ? kept.join("\n") : void 0;
|
|
223
194
|
}
|
|
224
|
-
function buildSeeEvent(problem, consequence, extras,
|
|
195
|
+
function buildSeeEvent(problem, consequence, extras, ctx2, kindOverride, correlationId) {
|
|
225
196
|
let errorType;
|
|
226
197
|
let message;
|
|
227
198
|
let stack;
|
|
@@ -249,8 +220,8 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride, correlat
|
|
|
249
220
|
message: truncate(message, SEE_MAX_MESSAGE),
|
|
250
221
|
subject: consequence.subject,
|
|
251
222
|
outcome: consequence.outcome,
|
|
252
|
-
side:
|
|
253
|
-
sdk_version:
|
|
223
|
+
side: ctx2.side,
|
|
224
|
+
sdk_version: ctx2.sdkVersion,
|
|
254
225
|
ts: Date.now()
|
|
255
226
|
};
|
|
256
227
|
if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
|
|
@@ -259,10 +230,10 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride, correlat
|
|
|
259
230
|
if (causedBy) ev.caused_by = causedBy;
|
|
260
231
|
const cleanExtras = sanitizeExtras(extras);
|
|
261
232
|
if (cleanExtras) ev.extras = cleanExtras;
|
|
262
|
-
if (
|
|
263
|
-
if (
|
|
264
|
-
if (
|
|
265
|
-
if (
|
|
233
|
+
if (ctx2.url) ev.url = truncate(ctx2.url, SEE_MAX_SUBJECT);
|
|
234
|
+
if (ctx2.userId) ev.user_id = ctx2.userId;
|
|
235
|
+
if (ctx2.anonId) ev.anonymous_id = ctx2.anonId;
|
|
236
|
+
if (ctx2.env) ev.env = ctx2.env;
|
|
266
237
|
markReported(problem, ev);
|
|
267
238
|
return ev;
|
|
268
239
|
}
|
|
@@ -355,6 +326,98 @@ var SeeLimiter = class {
|
|
|
355
326
|
}
|
|
356
327
|
};
|
|
357
328
|
|
|
329
|
+
// src/internal-report.ts
|
|
330
|
+
var INGEST_URL = "https://api.shipeasy.ai/collect";
|
|
331
|
+
var PLACEHOLDER_KEY = "sdk_client_REPLACE_WITH_SHIPEASY_INTERNAL_ERROR_KEY";
|
|
332
|
+
var INGEST_KEY = "sdk_client_00bd4608a03e4084922978f9522614d5";
|
|
333
|
+
function keyConfigured() {
|
|
334
|
+
return !!INGEST_KEY && INGEST_KEY !== PLACEHOLDER_KEY;
|
|
335
|
+
}
|
|
336
|
+
var OUTCOME = "returned a safe default";
|
|
337
|
+
var SDK_ID = "ts";
|
|
338
|
+
var ctx = null;
|
|
339
|
+
var limiter = new SeeLimiter();
|
|
340
|
+
function setInternalReportContext(c) {
|
|
341
|
+
ctx = { side: c.side, sdkVersion: c.sdkVersion, enabled: c.enabled !== false };
|
|
342
|
+
}
|
|
343
|
+
function reportInternalError(label, err) {
|
|
344
|
+
try {
|
|
345
|
+
if (!ctx || !ctx.enabled || !keyConfigured()) return;
|
|
346
|
+
const ev = buildSeeEvent(
|
|
347
|
+
err,
|
|
348
|
+
causesThe(label).to(OUTCOME),
|
|
349
|
+
{ sdk: SDK_ID },
|
|
350
|
+
{ side: ctx.side, sdkVersion: ctx.sdkVersion },
|
|
351
|
+
"caught"
|
|
352
|
+
);
|
|
353
|
+
if (!limiter.shouldSend(ev)) return;
|
|
354
|
+
send2(INGEST_URL, INGEST_KEY, JSON.stringify({ events: [ev] }));
|
|
355
|
+
} catch {
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function send2(url, key, body) {
|
|
359
|
+
try {
|
|
360
|
+
const f = globalThis.fetch;
|
|
361
|
+
if (typeof f !== "function") return;
|
|
362
|
+
void f(url, {
|
|
363
|
+
method: "POST",
|
|
364
|
+
headers: { "X-SDK-Key": key, "Content-Type": "text/plain" },
|
|
365
|
+
body,
|
|
366
|
+
keepalive: true
|
|
367
|
+
}).catch(() => {
|
|
368
|
+
});
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/logger.ts
|
|
374
|
+
var LOG_LEVELS = ["silent", "error", "warn", "info", "debug"];
|
|
375
|
+
var RANK = {
|
|
376
|
+
silent: 0,
|
|
377
|
+
error: 1,
|
|
378
|
+
warn: 2,
|
|
379
|
+
info: 3,
|
|
380
|
+
debug: 4
|
|
381
|
+
};
|
|
382
|
+
var currentLevel = "warn";
|
|
383
|
+
function setLogLevel(level) {
|
|
384
|
+
if (typeof level === "string" && Object.prototype.hasOwnProperty.call(RANK, level)) {
|
|
385
|
+
currentLevel = level;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function write(method, args) {
|
|
389
|
+
try {
|
|
390
|
+
const c = globalThis.console;
|
|
391
|
+
if (!c) return;
|
|
392
|
+
const fn = c[method] ?? c.log;
|
|
393
|
+
if (typeof fn === "function") fn.call(c, ...args);
|
|
394
|
+
} catch {
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
var logger = {
|
|
398
|
+
error(...args) {
|
|
399
|
+
if (RANK[currentLevel] >= RANK.error) write("error", args);
|
|
400
|
+
},
|
|
401
|
+
warn(...args) {
|
|
402
|
+
if (RANK[currentLevel] >= RANK.warn) write("warn", args);
|
|
403
|
+
},
|
|
404
|
+
info(...args) {
|
|
405
|
+
if (RANK[currentLevel] >= RANK.info) write("info", args);
|
|
406
|
+
},
|
|
407
|
+
debug(...args) {
|
|
408
|
+
if (RANK[currentLevel] >= RANK.debug) write("debug", args);
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
function safeRun(label, fallback, fn) {
|
|
412
|
+
try {
|
|
413
|
+
return fn();
|
|
414
|
+
} catch (err) {
|
|
415
|
+
logger.error(`[shipeasy] ${label} failed \u2014 returning safe default:`, String(err));
|
|
416
|
+
reportInternalError(label, err);
|
|
417
|
+
return fallback;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
358
421
|
// src/client/index.ts
|
|
359
422
|
var version = "4.0.0";
|
|
360
423
|
var FLAG_REASONS = [
|
|
@@ -365,6 +428,40 @@ var FLAG_REASONS = [
|
|
|
365
428
|
"RULE_MATCH",
|
|
366
429
|
"DEFAULT"
|
|
367
430
|
];
|
|
431
|
+
var AssignmentImpl = class {
|
|
432
|
+
constructor(name, group, params) {
|
|
433
|
+
this.name = name;
|
|
434
|
+
this.group = group;
|
|
435
|
+
this.params = params;
|
|
436
|
+
}
|
|
437
|
+
name;
|
|
438
|
+
group;
|
|
439
|
+
params;
|
|
440
|
+
get enrolled() {
|
|
441
|
+
return this.group !== null;
|
|
442
|
+
}
|
|
443
|
+
get(field, fallback) {
|
|
444
|
+
const v = this.params[field];
|
|
445
|
+
return v === void 0 ? fallback : v;
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
function hasDomEvents() {
|
|
449
|
+
return typeof window !== "undefined" && typeof window.addEventListener === "function";
|
|
450
|
+
}
|
|
451
|
+
function onWindow(type, handler, opts) {
|
|
452
|
+
try {
|
|
453
|
+
if (hasDomEvents()) window.addEventListener(type, handler, opts);
|
|
454
|
+
} catch {
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function onDocument(type, handler, opts) {
|
|
458
|
+
try {
|
|
459
|
+
if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
|
|
460
|
+
document.addEventListener(type, handler, opts);
|
|
461
|
+
}
|
|
462
|
+
} catch {
|
|
463
|
+
}
|
|
464
|
+
}
|
|
368
465
|
var FLUSH_INTERVAL_MS = 5e3;
|
|
369
466
|
var MAX_BUFFER = 100;
|
|
370
467
|
var ANON_ID_KEY = "__se_anon_id";
|
|
@@ -376,8 +473,8 @@ var EventBuffer = class {
|
|
|
376
473
|
this.sdkKey = sdkKey;
|
|
377
474
|
if (typeof window !== "undefined") {
|
|
378
475
|
this.timer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
|
|
379
|
-
|
|
380
|
-
|
|
476
|
+
onWindow("beforeunload", () => this.flush());
|
|
477
|
+
onDocument("visibilitychange", () => {
|
|
381
478
|
if (document.visibilityState === "hidden") this.flush(true);
|
|
382
479
|
});
|
|
383
480
|
try {
|
|
@@ -922,6 +1019,10 @@ var Engine = class _Engine {
|
|
|
922
1019
|
env;
|
|
923
1020
|
evalResult = null;
|
|
924
1021
|
anonId;
|
|
1022
|
+
// Resolves once a caller-supplied anonymousStore has been consulted (persisted
|
|
1023
|
+
// id adopted, or the minted id written back). identify() awaits it so the
|
|
1024
|
+
// stable id is settled before the first /sdk/evaluate. null when no store.
|
|
1025
|
+
anonReady = null;
|
|
925
1026
|
userId = "";
|
|
926
1027
|
buffer;
|
|
927
1028
|
telemetry;
|
|
@@ -936,6 +1037,12 @@ var Engine = class _Engine {
|
|
|
936
1037
|
// never fetches, track() is a no-op, telemetry is off, and the client is
|
|
937
1038
|
// already ready with an empty eval result.
|
|
938
1039
|
testMode;
|
|
1040
|
+
// Master network gate — false ⇒ the SDK never touches the network. Defaults to
|
|
1041
|
+
// environment-derived (prod-on), forced false by testMode.
|
|
1042
|
+
networkEnabled;
|
|
1043
|
+
// Convenience inverse of networkEnabled — the "no network at all" state every
|
|
1044
|
+
// /sdk/evaluate, /collect and telemetry gate keys on. testMode implies offline.
|
|
1045
|
+
offline;
|
|
939
1046
|
// Programmatic overrides (Statsig-style). Set on any client via
|
|
940
1047
|
// overrideFlag/overrideConfig/overrideExperiment; they win over BOTH the URL
|
|
941
1048
|
// overrides and the fetched eval result. Cleared by clearOverrides().
|
|
@@ -949,9 +1056,17 @@ var Engine = class _Engine {
|
|
|
949
1056
|
constructor(opts) {
|
|
950
1057
|
setLogLevel(opts.logLevel);
|
|
951
1058
|
this.sdkKey = opts.sdkKey;
|
|
952
|
-
this.baseUrl = (opts.baseUrl ?? "https://
|
|
1059
|
+
this.baseUrl = (opts.baseUrl ?? "https://api.shipeasy.ai").replace(/\/$/, "");
|
|
953
1060
|
this.env = opts.env ?? "prod";
|
|
954
1061
|
this.testMode = opts.testMode === true;
|
|
1062
|
+
const prod = isProductionEnv(this.env);
|
|
1063
|
+
this.networkEnabled = this.testMode ? false : opts.isNetworkEnabled ?? prod;
|
|
1064
|
+
this.offline = !this.networkEnabled;
|
|
1065
|
+
setInternalReportContext({
|
|
1066
|
+
side: "client",
|
|
1067
|
+
sdkVersion: version,
|
|
1068
|
+
enabled: this.networkEnabled && opts.disableInternalErrorReporting !== true
|
|
1069
|
+
});
|
|
955
1070
|
this.autoGuardrails = opts.autoGuardrails !== false;
|
|
956
1071
|
this.autoCollectAlways = opts.autoCollectAlways === true;
|
|
957
1072
|
this.disableAutoExposure = opts.disableAutoExposure === true;
|
|
@@ -964,21 +1079,42 @@ var Engine = class _Engine {
|
|
|
964
1079
|
engagement: g.engagement ?? this.autoGuardrails
|
|
965
1080
|
};
|
|
966
1081
|
this.anonId = getOrCreateAnonId();
|
|
1082
|
+
if (opts.anonymousStore && !this.offline) {
|
|
1083
|
+
this.anonReady = this.hydrateAnonFromStore(opts.anonymousStore);
|
|
1084
|
+
}
|
|
967
1085
|
this.buffer = new EventBuffer(`${this.baseUrl}/collect`, this.sdkKey);
|
|
968
1086
|
this.telemetry = new Telemetry({
|
|
969
1087
|
endpoint: opts.telemetryUrl ?? DEFAULT_TELEMETRY_URL,
|
|
970
1088
|
sdkKey: this.sdkKey,
|
|
971
1089
|
side: "client",
|
|
972
1090
|
env: this.env,
|
|
973
|
-
//
|
|
974
|
-
|
|
1091
|
+
// Off when the network is disabled; otherwise honour an explicit
|
|
1092
|
+
// disableTelemetry, else default to prod-on (off outside production).
|
|
1093
|
+
disabled: this.offline || (opts.disableTelemetry ?? !prod)
|
|
975
1094
|
});
|
|
976
|
-
if (this.
|
|
1095
|
+
if (this.offline) {
|
|
977
1096
|
this.evalResult = { flags: {}, configs: {}, experiments: {}, killswitches: {} };
|
|
978
1097
|
} else {
|
|
979
1098
|
void this.buffer.flushPendingAlias();
|
|
980
1099
|
}
|
|
981
1100
|
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Consult the caller-supplied {@link AnonymousStore}: adopt a persisted id so
|
|
1103
|
+
* the visitor buckets identically across app launches, or persist the id just
|
|
1104
|
+
* minted by getOrCreateAnonId() on a fresh device. Best-effort — any store
|
|
1105
|
+
* failure leaves the in-memory id in place.
|
|
1106
|
+
*/
|
|
1107
|
+
async hydrateAnonFromStore(store) {
|
|
1108
|
+
try {
|
|
1109
|
+
const stored = await store.get(ANON_ID_KEY);
|
|
1110
|
+
if (typeof stored === "string" && stored) {
|
|
1111
|
+
this.anonId = stored;
|
|
1112
|
+
} else {
|
|
1113
|
+
await store.set(ANON_ID_KEY, this.anonId);
|
|
1114
|
+
}
|
|
1115
|
+
} catch {
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
982
1118
|
/**
|
|
983
1119
|
* Build a no-network, immediately-usable browser client for tests
|
|
984
1120
|
* (Statsig-style). identify() is a no-op (never calls /sdk/evaluate), track()
|
|
@@ -1001,11 +1137,15 @@ var Engine = class _Engine {
|
|
|
1001
1137
|
});
|
|
1002
1138
|
}
|
|
1003
1139
|
async identify(user) {
|
|
1004
|
-
if (this.
|
|
1140
|
+
if (this.offline) {
|
|
1005
1141
|
if (user.user_id !== void 0) this.userId = user.user_id;
|
|
1006
1142
|
this.notify();
|
|
1007
1143
|
return;
|
|
1008
1144
|
}
|
|
1145
|
+
if (this.anonReady) {
|
|
1146
|
+
await this.anonReady;
|
|
1147
|
+
this.anonReady = null;
|
|
1148
|
+
}
|
|
1009
1149
|
const seq = ++this.identifySeq;
|
|
1010
1150
|
const prevUserId = this.userId;
|
|
1011
1151
|
if (user.user_id !== void 0) this.userId = user.user_id;
|
|
@@ -1059,6 +1199,7 @@ var Engine = class _Engine {
|
|
|
1059
1199
|
* the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
|
|
1060
1200
|
*/
|
|
1061
1201
|
reportError(problem, consequence, extras, kind, correlationId) {
|
|
1202
|
+
if (this.offline) return;
|
|
1062
1203
|
try {
|
|
1063
1204
|
const enriched = { ...collectSeeEnv(), ...extras };
|
|
1064
1205
|
const ev = buildSeeEvent(problem, consequence, enriched, {
|
|
@@ -1163,50 +1304,37 @@ var Engine = class _Engine {
|
|
|
1163
1304
|
return void 0;
|
|
1164
1305
|
}
|
|
1165
1306
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1307
|
+
/**
|
|
1308
|
+
* Assign the visitor within a universe: `universe("checkout").assign()`. A
|
|
1309
|
+
* universe is a mutual-exclusion pool, so the visitor lands in ≤1 experiment;
|
|
1310
|
+
* the returned {@link Assignment} exposes `.group` / `.get(field, fallback)`
|
|
1311
|
+
* and auto-logs one exposure when enrolled (unless `disableAutoExposure` or
|
|
1312
|
+
* `assign({ logExposure: false })`). An un-enrolled visitor still resolves
|
|
1313
|
+
* `get()` to the universe defaults. This is the sole experiment read path —
|
|
1314
|
+
* there is no `getExperiment` (ask a universe, not an experiment).
|
|
1315
|
+
*/
|
|
1316
|
+
universe(name) {
|
|
1317
|
+
return { assign: (opts) => this.assignUniverse(name, opts) };
|
|
1318
|
+
}
|
|
1319
|
+
assignUniverse(name, opts) {
|
|
1169
1320
|
this.telemetry.emit("experiment", name);
|
|
1170
|
-
const
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
params
|
|
1174
|
-
};
|
|
1175
|
-
const pov = this.experimentOverrides.get(name);
|
|
1176
|
-
if (pov) {
|
|
1177
|
-
const variantParams = variants?.[pov.group];
|
|
1178
|
-
const params = { ...defaultParams, ...pov.params, ...variantParams ?? {} };
|
|
1179
|
-
return { inExperiment: true, group: pov.group, params };
|
|
1321
|
+
const defaults = this.evalResult?.universes?.[name]?.defaults ?? {};
|
|
1322
|
+
for (const [expName, pov] of this.experimentOverrides) {
|
|
1323
|
+
if (this.evalResult?.experiments[expName]?.universe !== name) continue;
|
|
1324
|
+
return new AssignmentImpl(expName, pov.group, { ...defaults, ...pov.params });
|
|
1180
1325
|
}
|
|
1181
|
-
const
|
|
1182
|
-
|
|
1183
|
-
const
|
|
1184
|
-
|
|
1185
|
-
return { inExperiment: true, group: ov, params };
|
|
1326
|
+
for (const [expName, entry] of Object.entries(this.evalResult?.experiments ?? {})) {
|
|
1327
|
+
if (entry.universe !== name) continue;
|
|
1328
|
+
const ov = readExpOverride(expName);
|
|
1329
|
+
if (ov !== null) return new AssignmentImpl(expName, ov, { ...defaults, ...entry.params });
|
|
1186
1330
|
}
|
|
1187
|
-
const entry
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
try {
|
|
1193
|
-
return { inExperiment: true, group: entry.group, params: decode(entry.params) };
|
|
1194
|
-
} catch (err) {
|
|
1195
|
-
logger.warn(`[shipeasy] getExperiment('${name}') decode failed:`, String(err));
|
|
1196
|
-
return notIn;
|
|
1331
|
+
for (const [expName, entry] of Object.entries(this.evalResult?.experiments ?? {})) {
|
|
1332
|
+
if (entry.universe !== name || !entry.inExperiment) continue;
|
|
1333
|
+
const shouldLog = opts?.logExposure ?? !this.disableAutoExposure;
|
|
1334
|
+
if (shouldLog) this.buffer.pushExposure(expName, entry.group, this.userId, this.anonId);
|
|
1335
|
+
return new AssignmentImpl(expName, entry.group, entry.params);
|
|
1197
1336
|
}
|
|
1198
|
-
|
|
1199
|
-
/**
|
|
1200
|
-
* Manually log an exposure for an enrolled experiment (Statsig's
|
|
1201
|
-
* `manuallyLogExposure`). Reads the cached eval result; if the visitor is in
|
|
1202
|
-
* the experiment, pushes the session-deduped exposure. Pair this with the
|
|
1203
|
-
* render of the treatment when reading with `{ logExposure: false }` (or
|
|
1204
|
-
* `disableAutoExposure: true`). No-op if the visitor isn't enrolled.
|
|
1205
|
-
*/
|
|
1206
|
-
logExposure(name) {
|
|
1207
|
-
const entry = this.evalResult?.experiments[name];
|
|
1208
|
-
if (!entry || !entry.inExperiment) return;
|
|
1209
|
-
this.buffer.pushExposure(name, entry.group, this.userId, this.anonId);
|
|
1337
|
+
return new AssignmentImpl(null, null, defaults);
|
|
1210
1338
|
}
|
|
1211
1339
|
/**
|
|
1212
1340
|
* Subscribe to state changes — fires after identify() completes and on
|
|
@@ -1215,7 +1343,7 @@ var Engine = class _Engine {
|
|
|
1215
1343
|
*/
|
|
1216
1344
|
subscribe(listener) {
|
|
1217
1345
|
this.listeners.add(listener);
|
|
1218
|
-
if (!this.overrideListenerInstalled &&
|
|
1346
|
+
if (!this.overrideListenerInstalled && hasDomEvents()) {
|
|
1219
1347
|
this.overrideListenerInstalled = true;
|
|
1220
1348
|
window.addEventListener("se:override:change", this.onOverrideChange);
|
|
1221
1349
|
}
|
|
@@ -1231,9 +1359,11 @@ var Engine = class _Engine {
|
|
|
1231
1359
|
if (typeof window === "undefined") return null;
|
|
1232
1360
|
const bridge = {
|
|
1233
1361
|
getFlag: (n) => this.getFlag(n),
|
|
1362
|
+
// Devtools reads assignments by experiment name straight off the eval
|
|
1363
|
+
// result (the public read path is universe-first; this is display-only).
|
|
1234
1364
|
getExperiment: (n) => {
|
|
1235
|
-
const
|
|
1236
|
-
return { inExperiment:
|
|
1365
|
+
const entry = this.evalResult?.experiments[n];
|
|
1366
|
+
return { inExperiment: entry?.inExperiment ?? false, group: entry?.group ?? "control" };
|
|
1237
1367
|
},
|
|
1238
1368
|
getConfig: (n) => this.getConfig(n)
|
|
1239
1369
|
};
|
|
@@ -1242,7 +1372,7 @@ var Engine = class _Engine {
|
|
|
1242
1372
|
return bridge;
|
|
1243
1373
|
}
|
|
1244
1374
|
track(eventName, props) {
|
|
1245
|
-
if (this.
|
|
1375
|
+
if (this.offline) return;
|
|
1246
1376
|
this.buffer.pushMetric(eventName, this.userId, this.anonId, this.stripPrivate(props));
|
|
1247
1377
|
}
|
|
1248
1378
|
/** Drop caller-marked private attributes from an outbound props bag. */
|
|
@@ -1356,7 +1486,7 @@ function loadDevtools(opts = {}) {
|
|
|
1356
1486
|
}
|
|
1357
1487
|
}
|
|
1358
1488
|
function attachDevtools(client, opts = {}) {
|
|
1359
|
-
if (
|
|
1489
|
+
if (!hasDomEvents()) return () => {
|
|
1360
1490
|
};
|
|
1361
1491
|
const hotkey = opts.hotkey ?? "Shift+Alt+S";
|
|
1362
1492
|
const parts = hotkey.split("+");
|
|
@@ -1395,14 +1525,18 @@ function shipeasy(opts) {
|
|
|
1395
1525
|
const client = configureShipeasy({
|
|
1396
1526
|
sdkKey: opts.clientKey,
|
|
1397
1527
|
baseUrl,
|
|
1528
|
+
env: opts.env,
|
|
1398
1529
|
logLevel: opts.logLevel,
|
|
1399
1530
|
autoGuardrails: blanket,
|
|
1400
1531
|
autoGuardrailGroups: groups,
|
|
1401
1532
|
autoCollectAlways: acObj?.always === true,
|
|
1533
|
+
isNetworkEnabled: opts.isNetworkEnabled,
|
|
1402
1534
|
disableTelemetry: opts.disableTelemetry,
|
|
1535
|
+
disableInternalErrorReporting: opts.disableInternalErrorReporting,
|
|
1403
1536
|
disableAutoExposure: opts.disableAutoExposure,
|
|
1404
1537
|
privateAttributes: opts.privateAttributes,
|
|
1405
|
-
stickyBucketing: opts.stickyBucketing
|
|
1538
|
+
stickyBucketing: opts.stickyBucketing,
|
|
1539
|
+
anonymousStore: opts.anonymousStore
|
|
1406
1540
|
});
|
|
1407
1541
|
injectI18nLoader(opts.clientKey, baseUrl, opts.i18nProfile);
|
|
1408
1542
|
flags.notifyMounted();
|
|
@@ -1484,7 +1618,7 @@ var _mountedAndReady = false;
|
|
|
1484
1618
|
var _standaloneListeners = /* @__PURE__ */ new Set();
|
|
1485
1619
|
var _standaloneOverrideWired = false;
|
|
1486
1620
|
function wireStandaloneOverride() {
|
|
1487
|
-
if (_standaloneOverrideWired ||
|
|
1621
|
+
if (_standaloneOverrideWired || !hasDomEvents()) return;
|
|
1488
1622
|
_standaloneOverrideWired = true;
|
|
1489
1623
|
window.addEventListener("se:override:change", () => {
|
|
1490
1624
|
for (const cb of _standaloneListeners) cb();
|
|
@@ -1550,21 +1684,22 @@ var flags = {
|
|
|
1550
1684
|
}
|
|
1551
1685
|
});
|
|
1552
1686
|
},
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1687
|
+
/**
|
|
1688
|
+
* Assign the visitor within a universe: `flags.universe("checkout").assign()`.
|
|
1689
|
+
* A universe is a mutual-exclusion pool, so the visitor lands in ≤1 experiment;
|
|
1690
|
+
* the returned {@link Assignment} exposes `.group` / `.get(field, fallback)`
|
|
1691
|
+
* and auto-logs one exposure when enrolled. Before configure() (or on error)
|
|
1692
|
+
* returns a safe not-enrolled handle. Replaces the removed `getExperiment` —
|
|
1693
|
+
* read experiments by universe, never by name.
|
|
1694
|
+
*/
|
|
1695
|
+
universe(name) {
|
|
1696
|
+
return {
|
|
1697
|
+
assign: (opts) => safeRun(
|
|
1698
|
+
"flags.universe.assign",
|
|
1699
|
+
new AssignmentImpl(null, null, {}),
|
|
1700
|
+
() => _client ? _client.universe(name).assign(opts) : new AssignmentImpl(null, null, {})
|
|
1701
|
+
)
|
|
1558
1702
|
};
|
|
1559
|
-
return safeRun("flags.getExperiment", fallback, () => {
|
|
1560
|
-
if (!_client) return fallback;
|
|
1561
|
-
return typeof decodeOrOpts === "function" ? _client.getExperiment(name, defaultParams, decodeOrOpts, variants) : _client.getExperiment(name, defaultParams, decodeOrOpts ?? {});
|
|
1562
|
-
});
|
|
1563
|
-
},
|
|
1564
|
-
/** Manually log an exposure for an enrolled experiment. See
|
|
1565
|
-
* {@link Engine.logExposure}. No-op before configure(). */
|
|
1566
|
-
logExposure(name) {
|
|
1567
|
-
safeRun("flags.logExposure", void 0, () => _client?.logExposure(name));
|
|
1568
1703
|
},
|
|
1569
1704
|
track(eventName, props) {
|
|
1570
1705
|
safeRun("flags.track", void 0, () => _client?.track(eventName, props));
|
|
@@ -1670,13 +1805,20 @@ var Client = class {
|
|
|
1670
1805
|
() => this.engine.getConfig(name, decodeOrOpts)
|
|
1671
1806
|
);
|
|
1672
1807
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1808
|
+
/**
|
|
1809
|
+
* Assign the visitor within a universe: `client.universe("checkout").assign()`.
|
|
1810
|
+
* A universe is a mutual-exclusion pool, so the visitor lands in ≤1 experiment;
|
|
1811
|
+
* returns an {@link Assignment} (`.group` / `.get(field, fallback)`) and
|
|
1812
|
+
* auto-logs one exposure when enrolled. Replaces the removed `getExperiment`.
|
|
1813
|
+
*/
|
|
1814
|
+
universe(name) {
|
|
1815
|
+
return {
|
|
1816
|
+
assign: (opts) => safeRun(
|
|
1817
|
+
"Client.universe.assign",
|
|
1818
|
+
new AssignmentImpl(null, null, {}),
|
|
1819
|
+
() => this.engine.universe(name).assign(opts)
|
|
1820
|
+
)
|
|
1821
|
+
};
|
|
1680
1822
|
}
|
|
1681
1823
|
/** Read a killswitch (not user-bound; mirrors {@link Engine.getKillswitch}). */
|
|
1682
1824
|
getKillswitch(name, switchKey) {
|
|
@@ -1691,15 +1833,6 @@ var Client = class {
|
|
|
1691
1833
|
track(eventName, props) {
|
|
1692
1834
|
safeRun("Client.track", void 0, () => this.engine.track(eventName, props));
|
|
1693
1835
|
}
|
|
1694
|
-
/**
|
|
1695
|
-
* Log an exposure for `name` at the treatment's render for the bound user.
|
|
1696
|
-
* Delegates to {@link Engine.logExposure} (no-op when the visitor isn't
|
|
1697
|
-
* enrolled). Pair with `getExperiment(name, …, { logExposure: false })` /
|
|
1698
|
-
* `disableAutoExposure` to log exposure exactly when you render.
|
|
1699
|
-
*/
|
|
1700
|
-
logExposure(name) {
|
|
1701
|
-
safeRun("Client.logExposure", void 0, () => this.engine.logExposure(name));
|
|
1702
|
-
}
|
|
1703
1836
|
};
|
|
1704
1837
|
function dispatchSee(problem, consequence, extras, kind) {
|
|
1705
1838
|
if (!_client) {
|
|
@@ -1925,6 +2058,7 @@ var i18n = {
|
|
|
1925
2058
|
whenReady() {
|
|
1926
2059
|
if (typeof window === "undefined") return Promise.resolve();
|
|
1927
2060
|
if (window.i18n?.locale) return Promise.resolve();
|
|
2061
|
+
if (!hasDomEvents()) return Promise.resolve();
|
|
1928
2062
|
return new Promise((resolve) => {
|
|
1929
2063
|
const handler = () => resolve();
|
|
1930
2064
|
window.addEventListener("se:i18n:ready", handler, { once: true });
|
|
@@ -1935,6 +2069,8 @@ var i18n = {
|
|
|
1935
2069
|
if (typeof window === "undefined") return () => {
|
|
1936
2070
|
};
|
|
1937
2071
|
if (window.i18n) return window.i18n.on("update", cb);
|
|
2072
|
+
if (!hasDomEvents()) return () => {
|
|
2073
|
+
};
|
|
1938
2074
|
let unsub = () => {
|
|
1939
2075
|
};
|
|
1940
2076
|
const handler = () => {
|