@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.js
CHANGED
|
@@ -110,50 +110,21 @@ function send(url) {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
// src/
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
warn: 2,
|
|
119
|
-
info: 3,
|
|
120
|
-
debug: 4
|
|
121
|
-
};
|
|
122
|
-
var currentLevel = "warn";
|
|
123
|
-
function setLogLevel(level) {
|
|
124
|
-
if (typeof level === "string" && Object.prototype.hasOwnProperty.call(RANK, level)) {
|
|
125
|
-
currentLevel = level;
|
|
126
|
-
}
|
|
113
|
+
// src/env.ts
|
|
114
|
+
function isProductionEnv(configuredEnv) {
|
|
115
|
+
const native = readNativeEnv();
|
|
116
|
+
if (native !== null) return native === "production" || native === "prod";
|
|
117
|
+
return (configuredEnv ?? "prod").toLowerCase() === "prod";
|
|
127
118
|
}
|
|
128
|
-
function
|
|
119
|
+
function readNativeEnv() {
|
|
129
120
|
try {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
121
|
+
const proc = globalThis.process;
|
|
122
|
+
const raw = proc?.env?.SHIPEASY_ENV ?? proc?.env?.NODE_ENV;
|
|
123
|
+
if (typeof raw !== "string") return null;
|
|
124
|
+
const v = raw.trim().toLowerCase();
|
|
125
|
+
return v.length ? v : null;
|
|
134
126
|
} catch {
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
var logger = {
|
|
138
|
-
error(...args) {
|
|
139
|
-
if (RANK[currentLevel] >= RANK.error) write("error", args);
|
|
140
|
-
},
|
|
141
|
-
warn(...args) {
|
|
142
|
-
if (RANK[currentLevel] >= RANK.warn) write("warn", args);
|
|
143
|
-
},
|
|
144
|
-
info(...args) {
|
|
145
|
-
if (RANK[currentLevel] >= RANK.info) write("info", args);
|
|
146
|
-
},
|
|
147
|
-
debug(...args) {
|
|
148
|
-
if (RANK[currentLevel] >= RANK.debug) write("debug", args);
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
function safeRun(label, fallback, fn) {
|
|
152
|
-
try {
|
|
153
|
-
return fn();
|
|
154
|
-
} catch (err) {
|
|
155
|
-
logger.error(`[shipeasy] ${label} failed \u2014 returning safe default:`, String(err));
|
|
156
|
-
return fallback;
|
|
127
|
+
return null;
|
|
157
128
|
}
|
|
158
129
|
}
|
|
159
130
|
|
|
@@ -274,7 +245,7 @@ function captureCallsiteStack() {
|
|
|
274
245
|
const kept = lines.slice(1).filter((l) => !/@shipeasy[\\/]sdk|see[\\/]core|captureCallsiteStack|\bsee\b\s*\(/.test(l));
|
|
275
246
|
return kept.length ? kept.join("\n") : void 0;
|
|
276
247
|
}
|
|
277
|
-
function buildSeeEvent(problem, consequence, extras,
|
|
248
|
+
function buildSeeEvent(problem, consequence, extras, ctx2, kindOverride, correlationId) {
|
|
278
249
|
let errorType;
|
|
279
250
|
let message;
|
|
280
251
|
let stack;
|
|
@@ -302,8 +273,8 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride, correlat
|
|
|
302
273
|
message: truncate(message, SEE_MAX_MESSAGE),
|
|
303
274
|
subject: consequence.subject,
|
|
304
275
|
outcome: consequence.outcome,
|
|
305
|
-
side:
|
|
306
|
-
sdk_version:
|
|
276
|
+
side: ctx2.side,
|
|
277
|
+
sdk_version: ctx2.sdkVersion,
|
|
307
278
|
ts: Date.now()
|
|
308
279
|
};
|
|
309
280
|
if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
|
|
@@ -312,10 +283,10 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride, correlat
|
|
|
312
283
|
if (causedBy) ev.caused_by = causedBy;
|
|
313
284
|
const cleanExtras = sanitizeExtras(extras);
|
|
314
285
|
if (cleanExtras) ev.extras = cleanExtras;
|
|
315
|
-
if (
|
|
316
|
-
if (
|
|
317
|
-
if (
|
|
318
|
-
if (
|
|
286
|
+
if (ctx2.url) ev.url = truncate(ctx2.url, SEE_MAX_SUBJECT);
|
|
287
|
+
if (ctx2.userId) ev.user_id = ctx2.userId;
|
|
288
|
+
if (ctx2.anonId) ev.anonymous_id = ctx2.anonId;
|
|
289
|
+
if (ctx2.env) ev.env = ctx2.env;
|
|
319
290
|
markReported(problem, ev);
|
|
320
291
|
return ev;
|
|
321
292
|
}
|
|
@@ -408,6 +379,98 @@ var SeeLimiter = class {
|
|
|
408
379
|
}
|
|
409
380
|
};
|
|
410
381
|
|
|
382
|
+
// src/internal-report.ts
|
|
383
|
+
var INGEST_URL = "https://api.shipeasy.ai/collect";
|
|
384
|
+
var PLACEHOLDER_KEY = "sdk_client_REPLACE_WITH_SHIPEASY_INTERNAL_ERROR_KEY";
|
|
385
|
+
var INGEST_KEY = "sdk_client_00bd4608a03e4084922978f9522614d5";
|
|
386
|
+
function keyConfigured() {
|
|
387
|
+
return !!INGEST_KEY && INGEST_KEY !== PLACEHOLDER_KEY;
|
|
388
|
+
}
|
|
389
|
+
var OUTCOME = "returned a safe default";
|
|
390
|
+
var SDK_ID = "ts";
|
|
391
|
+
var ctx = null;
|
|
392
|
+
var limiter = new SeeLimiter();
|
|
393
|
+
function setInternalReportContext(c) {
|
|
394
|
+
ctx = { side: c.side, sdkVersion: c.sdkVersion, enabled: c.enabled !== false };
|
|
395
|
+
}
|
|
396
|
+
function reportInternalError(label, err) {
|
|
397
|
+
try {
|
|
398
|
+
if (!ctx || !ctx.enabled || !keyConfigured()) return;
|
|
399
|
+
const ev = buildSeeEvent(
|
|
400
|
+
err,
|
|
401
|
+
causesThe(label).to(OUTCOME),
|
|
402
|
+
{ sdk: SDK_ID },
|
|
403
|
+
{ side: ctx.side, sdkVersion: ctx.sdkVersion },
|
|
404
|
+
"caught"
|
|
405
|
+
);
|
|
406
|
+
if (!limiter.shouldSend(ev)) return;
|
|
407
|
+
send2(INGEST_URL, INGEST_KEY, JSON.stringify({ events: [ev] }));
|
|
408
|
+
} catch {
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function send2(url, key, body) {
|
|
412
|
+
try {
|
|
413
|
+
const f = globalThis.fetch;
|
|
414
|
+
if (typeof f !== "function") return;
|
|
415
|
+
void f(url, {
|
|
416
|
+
method: "POST",
|
|
417
|
+
headers: { "X-SDK-Key": key, "Content-Type": "text/plain" },
|
|
418
|
+
body,
|
|
419
|
+
keepalive: true
|
|
420
|
+
}).catch(() => {
|
|
421
|
+
});
|
|
422
|
+
} catch {
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/logger.ts
|
|
427
|
+
var LOG_LEVELS = ["silent", "error", "warn", "info", "debug"];
|
|
428
|
+
var RANK = {
|
|
429
|
+
silent: 0,
|
|
430
|
+
error: 1,
|
|
431
|
+
warn: 2,
|
|
432
|
+
info: 3,
|
|
433
|
+
debug: 4
|
|
434
|
+
};
|
|
435
|
+
var currentLevel = "warn";
|
|
436
|
+
function setLogLevel(level) {
|
|
437
|
+
if (typeof level === "string" && Object.prototype.hasOwnProperty.call(RANK, level)) {
|
|
438
|
+
currentLevel = level;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function write(method, args) {
|
|
442
|
+
try {
|
|
443
|
+
const c = globalThis.console;
|
|
444
|
+
if (!c) return;
|
|
445
|
+
const fn = c[method] ?? c.log;
|
|
446
|
+
if (typeof fn === "function") fn.call(c, ...args);
|
|
447
|
+
} catch {
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
var logger = {
|
|
451
|
+
error(...args) {
|
|
452
|
+
if (RANK[currentLevel] >= RANK.error) write("error", args);
|
|
453
|
+
},
|
|
454
|
+
warn(...args) {
|
|
455
|
+
if (RANK[currentLevel] >= RANK.warn) write("warn", args);
|
|
456
|
+
},
|
|
457
|
+
info(...args) {
|
|
458
|
+
if (RANK[currentLevel] >= RANK.info) write("info", args);
|
|
459
|
+
},
|
|
460
|
+
debug(...args) {
|
|
461
|
+
if (RANK[currentLevel] >= RANK.debug) write("debug", args);
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
function safeRun(label, fallback, fn) {
|
|
465
|
+
try {
|
|
466
|
+
return fn();
|
|
467
|
+
} catch (err) {
|
|
468
|
+
logger.error(`[shipeasy] ${label} failed \u2014 returning safe default:`, String(err));
|
|
469
|
+
reportInternalError(label, err);
|
|
470
|
+
return fallback;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
411
474
|
// src/client/index.ts
|
|
412
475
|
var version = "4.0.0";
|
|
413
476
|
var FLAG_REASONS = [
|
|
@@ -418,6 +481,40 @@ var FLAG_REASONS = [
|
|
|
418
481
|
"RULE_MATCH",
|
|
419
482
|
"DEFAULT"
|
|
420
483
|
];
|
|
484
|
+
var AssignmentImpl = class {
|
|
485
|
+
constructor(name, group, params) {
|
|
486
|
+
this.name = name;
|
|
487
|
+
this.group = group;
|
|
488
|
+
this.params = params;
|
|
489
|
+
}
|
|
490
|
+
name;
|
|
491
|
+
group;
|
|
492
|
+
params;
|
|
493
|
+
get enrolled() {
|
|
494
|
+
return this.group !== null;
|
|
495
|
+
}
|
|
496
|
+
get(field, fallback) {
|
|
497
|
+
const v = this.params[field];
|
|
498
|
+
return v === void 0 ? fallback : v;
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
function hasDomEvents() {
|
|
502
|
+
return typeof window !== "undefined" && typeof window.addEventListener === "function";
|
|
503
|
+
}
|
|
504
|
+
function onWindow(type, handler, opts) {
|
|
505
|
+
try {
|
|
506
|
+
if (hasDomEvents()) window.addEventListener(type, handler, opts);
|
|
507
|
+
} catch {
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
function onDocument(type, handler, opts) {
|
|
511
|
+
try {
|
|
512
|
+
if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
|
|
513
|
+
document.addEventListener(type, handler, opts);
|
|
514
|
+
}
|
|
515
|
+
} catch {
|
|
516
|
+
}
|
|
517
|
+
}
|
|
421
518
|
var FLUSH_INTERVAL_MS = 5e3;
|
|
422
519
|
var MAX_BUFFER = 100;
|
|
423
520
|
var ANON_ID_KEY = "__se_anon_id";
|
|
@@ -429,8 +526,8 @@ var EventBuffer = class {
|
|
|
429
526
|
this.sdkKey = sdkKey;
|
|
430
527
|
if (typeof window !== "undefined") {
|
|
431
528
|
this.timer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
|
|
432
|
-
|
|
433
|
-
|
|
529
|
+
onWindow("beforeunload", () => this.flush());
|
|
530
|
+
onDocument("visibilitychange", () => {
|
|
434
531
|
if (document.visibilityState === "hidden") this.flush(true);
|
|
435
532
|
});
|
|
436
533
|
try {
|
|
@@ -975,6 +1072,10 @@ var Engine = class _Engine {
|
|
|
975
1072
|
env;
|
|
976
1073
|
evalResult = null;
|
|
977
1074
|
anonId;
|
|
1075
|
+
// Resolves once a caller-supplied anonymousStore has been consulted (persisted
|
|
1076
|
+
// id adopted, or the minted id written back). identify() awaits it so the
|
|
1077
|
+
// stable id is settled before the first /sdk/evaluate. null when no store.
|
|
1078
|
+
anonReady = null;
|
|
978
1079
|
userId = "";
|
|
979
1080
|
buffer;
|
|
980
1081
|
telemetry;
|
|
@@ -989,6 +1090,12 @@ var Engine = class _Engine {
|
|
|
989
1090
|
// never fetches, track() is a no-op, telemetry is off, and the client is
|
|
990
1091
|
// already ready with an empty eval result.
|
|
991
1092
|
testMode;
|
|
1093
|
+
// Master network gate — false ⇒ the SDK never touches the network. Defaults to
|
|
1094
|
+
// environment-derived (prod-on), forced false by testMode.
|
|
1095
|
+
networkEnabled;
|
|
1096
|
+
// Convenience inverse of networkEnabled — the "no network at all" state every
|
|
1097
|
+
// /sdk/evaluate, /collect and telemetry gate keys on. testMode implies offline.
|
|
1098
|
+
offline;
|
|
992
1099
|
// Programmatic overrides (Statsig-style). Set on any client via
|
|
993
1100
|
// overrideFlag/overrideConfig/overrideExperiment; they win over BOTH the URL
|
|
994
1101
|
// overrides and the fetched eval result. Cleared by clearOverrides().
|
|
@@ -1002,9 +1109,17 @@ var Engine = class _Engine {
|
|
|
1002
1109
|
constructor(opts) {
|
|
1003
1110
|
setLogLevel(opts.logLevel);
|
|
1004
1111
|
this.sdkKey = opts.sdkKey;
|
|
1005
|
-
this.baseUrl = (opts.baseUrl ?? "https://
|
|
1112
|
+
this.baseUrl = (opts.baseUrl ?? "https://api.shipeasy.ai").replace(/\/$/, "");
|
|
1006
1113
|
this.env = opts.env ?? "prod";
|
|
1007
1114
|
this.testMode = opts.testMode === true;
|
|
1115
|
+
const prod = isProductionEnv(this.env);
|
|
1116
|
+
this.networkEnabled = this.testMode ? false : opts.isNetworkEnabled ?? prod;
|
|
1117
|
+
this.offline = !this.networkEnabled;
|
|
1118
|
+
setInternalReportContext({
|
|
1119
|
+
side: "client",
|
|
1120
|
+
sdkVersion: version,
|
|
1121
|
+
enabled: this.networkEnabled && opts.disableInternalErrorReporting !== true
|
|
1122
|
+
});
|
|
1008
1123
|
this.autoGuardrails = opts.autoGuardrails !== false;
|
|
1009
1124
|
this.autoCollectAlways = opts.autoCollectAlways === true;
|
|
1010
1125
|
this.disableAutoExposure = opts.disableAutoExposure === true;
|
|
@@ -1017,21 +1132,42 @@ var Engine = class _Engine {
|
|
|
1017
1132
|
engagement: g.engagement ?? this.autoGuardrails
|
|
1018
1133
|
};
|
|
1019
1134
|
this.anonId = getOrCreateAnonId();
|
|
1135
|
+
if (opts.anonymousStore && !this.offline) {
|
|
1136
|
+
this.anonReady = this.hydrateAnonFromStore(opts.anonymousStore);
|
|
1137
|
+
}
|
|
1020
1138
|
this.buffer = new EventBuffer(`${this.baseUrl}/collect`, this.sdkKey);
|
|
1021
1139
|
this.telemetry = new Telemetry({
|
|
1022
1140
|
endpoint: opts.telemetryUrl ?? DEFAULT_TELEMETRY_URL,
|
|
1023
1141
|
sdkKey: this.sdkKey,
|
|
1024
1142
|
side: "client",
|
|
1025
1143
|
env: this.env,
|
|
1026
|
-
//
|
|
1027
|
-
|
|
1144
|
+
// Off when the network is disabled; otherwise honour an explicit
|
|
1145
|
+
// disableTelemetry, else default to prod-on (off outside production).
|
|
1146
|
+
disabled: this.offline || (opts.disableTelemetry ?? !prod)
|
|
1028
1147
|
});
|
|
1029
|
-
if (this.
|
|
1148
|
+
if (this.offline) {
|
|
1030
1149
|
this.evalResult = { flags: {}, configs: {}, experiments: {}, killswitches: {} };
|
|
1031
1150
|
} else {
|
|
1032
1151
|
void this.buffer.flushPendingAlias();
|
|
1033
1152
|
}
|
|
1034
1153
|
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Consult the caller-supplied {@link AnonymousStore}: adopt a persisted id so
|
|
1156
|
+
* the visitor buckets identically across app launches, or persist the id just
|
|
1157
|
+
* minted by getOrCreateAnonId() on a fresh device. Best-effort — any store
|
|
1158
|
+
* failure leaves the in-memory id in place.
|
|
1159
|
+
*/
|
|
1160
|
+
async hydrateAnonFromStore(store) {
|
|
1161
|
+
try {
|
|
1162
|
+
const stored = await store.get(ANON_ID_KEY);
|
|
1163
|
+
if (typeof stored === "string" && stored) {
|
|
1164
|
+
this.anonId = stored;
|
|
1165
|
+
} else {
|
|
1166
|
+
await store.set(ANON_ID_KEY, this.anonId);
|
|
1167
|
+
}
|
|
1168
|
+
} catch {
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1035
1171
|
/**
|
|
1036
1172
|
* Build a no-network, immediately-usable browser client for tests
|
|
1037
1173
|
* (Statsig-style). identify() is a no-op (never calls /sdk/evaluate), track()
|
|
@@ -1054,11 +1190,15 @@ var Engine = class _Engine {
|
|
|
1054
1190
|
});
|
|
1055
1191
|
}
|
|
1056
1192
|
async identify(user) {
|
|
1057
|
-
if (this.
|
|
1193
|
+
if (this.offline) {
|
|
1058
1194
|
if (user.user_id !== void 0) this.userId = user.user_id;
|
|
1059
1195
|
this.notify();
|
|
1060
1196
|
return;
|
|
1061
1197
|
}
|
|
1198
|
+
if (this.anonReady) {
|
|
1199
|
+
await this.anonReady;
|
|
1200
|
+
this.anonReady = null;
|
|
1201
|
+
}
|
|
1062
1202
|
const seq = ++this.identifySeq;
|
|
1063
1203
|
const prevUserId = this.userId;
|
|
1064
1204
|
if (user.user_id !== void 0) this.userId = user.user_id;
|
|
@@ -1112,6 +1252,7 @@ var Engine = class _Engine {
|
|
|
1112
1252
|
* the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
|
|
1113
1253
|
*/
|
|
1114
1254
|
reportError(problem, consequence, extras, kind, correlationId) {
|
|
1255
|
+
if (this.offline) return;
|
|
1115
1256
|
try {
|
|
1116
1257
|
const enriched = { ...collectSeeEnv(), ...extras };
|
|
1117
1258
|
const ev = buildSeeEvent(problem, consequence, enriched, {
|
|
@@ -1216,50 +1357,37 @@ var Engine = class _Engine {
|
|
|
1216
1357
|
return void 0;
|
|
1217
1358
|
}
|
|
1218
1359
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1360
|
+
/**
|
|
1361
|
+
* Assign the visitor within a universe: `universe("checkout").assign()`. A
|
|
1362
|
+
* universe is a mutual-exclusion pool, so the visitor lands in ≤1 experiment;
|
|
1363
|
+
* the returned {@link Assignment} exposes `.group` / `.get(field, fallback)`
|
|
1364
|
+
* and auto-logs one exposure when enrolled (unless `disableAutoExposure` or
|
|
1365
|
+
* `assign({ logExposure: false })`). An un-enrolled visitor still resolves
|
|
1366
|
+
* `get()` to the universe defaults. This is the sole experiment read path —
|
|
1367
|
+
* there is no `getExperiment` (ask a universe, not an experiment).
|
|
1368
|
+
*/
|
|
1369
|
+
universe(name) {
|
|
1370
|
+
return { assign: (opts) => this.assignUniverse(name, opts) };
|
|
1371
|
+
}
|
|
1372
|
+
assignUniverse(name, opts) {
|
|
1222
1373
|
this.telemetry.emit("experiment", name);
|
|
1223
|
-
const
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
params
|
|
1227
|
-
};
|
|
1228
|
-
const pov = this.experimentOverrides.get(name);
|
|
1229
|
-
if (pov) {
|
|
1230
|
-
const variantParams = variants?.[pov.group];
|
|
1231
|
-
const params = { ...defaultParams, ...pov.params, ...variantParams ?? {} };
|
|
1232
|
-
return { inExperiment: true, group: pov.group, params };
|
|
1374
|
+
const defaults = this.evalResult?.universes?.[name]?.defaults ?? {};
|
|
1375
|
+
for (const [expName, pov] of this.experimentOverrides) {
|
|
1376
|
+
if (this.evalResult?.experiments[expName]?.universe !== name) continue;
|
|
1377
|
+
return new AssignmentImpl(expName, pov.group, { ...defaults, ...pov.params });
|
|
1233
1378
|
}
|
|
1234
|
-
const
|
|
1235
|
-
|
|
1236
|
-
const
|
|
1237
|
-
|
|
1238
|
-
return { inExperiment: true, group: ov, params };
|
|
1379
|
+
for (const [expName, entry] of Object.entries(this.evalResult?.experiments ?? {})) {
|
|
1380
|
+
if (entry.universe !== name) continue;
|
|
1381
|
+
const ov = readExpOverride(expName);
|
|
1382
|
+
if (ov !== null) return new AssignmentImpl(expName, ov, { ...defaults, ...entry.params });
|
|
1239
1383
|
}
|
|
1240
|
-
const entry
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
try {
|
|
1246
|
-
return { inExperiment: true, group: entry.group, params: decode(entry.params) };
|
|
1247
|
-
} catch (err) {
|
|
1248
|
-
logger.warn(`[shipeasy] getExperiment('${name}') decode failed:`, String(err));
|
|
1249
|
-
return notIn;
|
|
1384
|
+
for (const [expName, entry] of Object.entries(this.evalResult?.experiments ?? {})) {
|
|
1385
|
+
if (entry.universe !== name || !entry.inExperiment) continue;
|
|
1386
|
+
const shouldLog = opts?.logExposure ?? !this.disableAutoExposure;
|
|
1387
|
+
if (shouldLog) this.buffer.pushExposure(expName, entry.group, this.userId, this.anonId);
|
|
1388
|
+
return new AssignmentImpl(expName, entry.group, entry.params);
|
|
1250
1389
|
}
|
|
1251
|
-
|
|
1252
|
-
/**
|
|
1253
|
-
* Manually log an exposure for an enrolled experiment (Statsig's
|
|
1254
|
-
* `manuallyLogExposure`). Reads the cached eval result; if the visitor is in
|
|
1255
|
-
* the experiment, pushes the session-deduped exposure. Pair this with the
|
|
1256
|
-
* render of the treatment when reading with `{ logExposure: false }` (or
|
|
1257
|
-
* `disableAutoExposure: true`). No-op if the visitor isn't enrolled.
|
|
1258
|
-
*/
|
|
1259
|
-
logExposure(name) {
|
|
1260
|
-
const entry = this.evalResult?.experiments[name];
|
|
1261
|
-
if (!entry || !entry.inExperiment) return;
|
|
1262
|
-
this.buffer.pushExposure(name, entry.group, this.userId, this.anonId);
|
|
1390
|
+
return new AssignmentImpl(null, null, defaults);
|
|
1263
1391
|
}
|
|
1264
1392
|
/**
|
|
1265
1393
|
* Subscribe to state changes — fires after identify() completes and on
|
|
@@ -1268,7 +1396,7 @@ var Engine = class _Engine {
|
|
|
1268
1396
|
*/
|
|
1269
1397
|
subscribe(listener) {
|
|
1270
1398
|
this.listeners.add(listener);
|
|
1271
|
-
if (!this.overrideListenerInstalled &&
|
|
1399
|
+
if (!this.overrideListenerInstalled && hasDomEvents()) {
|
|
1272
1400
|
this.overrideListenerInstalled = true;
|
|
1273
1401
|
window.addEventListener("se:override:change", this.onOverrideChange);
|
|
1274
1402
|
}
|
|
@@ -1284,9 +1412,11 @@ var Engine = class _Engine {
|
|
|
1284
1412
|
if (typeof window === "undefined") return null;
|
|
1285
1413
|
const bridge = {
|
|
1286
1414
|
getFlag: (n) => this.getFlag(n),
|
|
1415
|
+
// Devtools reads assignments by experiment name straight off the eval
|
|
1416
|
+
// result (the public read path is universe-first; this is display-only).
|
|
1287
1417
|
getExperiment: (n) => {
|
|
1288
|
-
const
|
|
1289
|
-
return { inExperiment:
|
|
1418
|
+
const entry = this.evalResult?.experiments[n];
|
|
1419
|
+
return { inExperiment: entry?.inExperiment ?? false, group: entry?.group ?? "control" };
|
|
1290
1420
|
},
|
|
1291
1421
|
getConfig: (n) => this.getConfig(n)
|
|
1292
1422
|
};
|
|
@@ -1295,7 +1425,7 @@ var Engine = class _Engine {
|
|
|
1295
1425
|
return bridge;
|
|
1296
1426
|
}
|
|
1297
1427
|
track(eventName, props) {
|
|
1298
|
-
if (this.
|
|
1428
|
+
if (this.offline) return;
|
|
1299
1429
|
this.buffer.pushMetric(eventName, this.userId, this.anonId, this.stripPrivate(props));
|
|
1300
1430
|
}
|
|
1301
1431
|
/** Drop caller-marked private attributes from an outbound props bag. */
|
|
@@ -1409,7 +1539,7 @@ function loadDevtools(opts = {}) {
|
|
|
1409
1539
|
}
|
|
1410
1540
|
}
|
|
1411
1541
|
function attachDevtools(client, opts = {}) {
|
|
1412
|
-
if (
|
|
1542
|
+
if (!hasDomEvents()) return () => {
|
|
1413
1543
|
};
|
|
1414
1544
|
const hotkey = opts.hotkey ?? "Shift+Alt+S";
|
|
1415
1545
|
const parts = hotkey.split("+");
|
|
@@ -1448,14 +1578,18 @@ function shipeasy(opts) {
|
|
|
1448
1578
|
const client = configureShipeasy({
|
|
1449
1579
|
sdkKey: opts.clientKey,
|
|
1450
1580
|
baseUrl,
|
|
1581
|
+
env: opts.env,
|
|
1451
1582
|
logLevel: opts.logLevel,
|
|
1452
1583
|
autoGuardrails: blanket,
|
|
1453
1584
|
autoGuardrailGroups: groups,
|
|
1454
1585
|
autoCollectAlways: acObj?.always === true,
|
|
1586
|
+
isNetworkEnabled: opts.isNetworkEnabled,
|
|
1455
1587
|
disableTelemetry: opts.disableTelemetry,
|
|
1588
|
+
disableInternalErrorReporting: opts.disableInternalErrorReporting,
|
|
1456
1589
|
disableAutoExposure: opts.disableAutoExposure,
|
|
1457
1590
|
privateAttributes: opts.privateAttributes,
|
|
1458
|
-
stickyBucketing: opts.stickyBucketing
|
|
1591
|
+
stickyBucketing: opts.stickyBucketing,
|
|
1592
|
+
anonymousStore: opts.anonymousStore
|
|
1459
1593
|
});
|
|
1460
1594
|
injectI18nLoader(opts.clientKey, baseUrl, opts.i18nProfile);
|
|
1461
1595
|
flags.notifyMounted();
|
|
@@ -1537,7 +1671,7 @@ var _mountedAndReady = false;
|
|
|
1537
1671
|
var _standaloneListeners = /* @__PURE__ */ new Set();
|
|
1538
1672
|
var _standaloneOverrideWired = false;
|
|
1539
1673
|
function wireStandaloneOverride() {
|
|
1540
|
-
if (_standaloneOverrideWired ||
|
|
1674
|
+
if (_standaloneOverrideWired || !hasDomEvents()) return;
|
|
1541
1675
|
_standaloneOverrideWired = true;
|
|
1542
1676
|
window.addEventListener("se:override:change", () => {
|
|
1543
1677
|
for (const cb of _standaloneListeners) cb();
|
|
@@ -1603,21 +1737,22 @@ var flags = {
|
|
|
1603
1737
|
}
|
|
1604
1738
|
});
|
|
1605
1739
|
},
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1740
|
+
/**
|
|
1741
|
+
* Assign the visitor within a universe: `flags.universe("checkout").assign()`.
|
|
1742
|
+
* A universe is a mutual-exclusion pool, so the visitor lands in ≤1 experiment;
|
|
1743
|
+
* the returned {@link Assignment} exposes `.group` / `.get(field, fallback)`
|
|
1744
|
+
* and auto-logs one exposure when enrolled. Before configure() (or on error)
|
|
1745
|
+
* returns a safe not-enrolled handle. Replaces the removed `getExperiment` —
|
|
1746
|
+
* read experiments by universe, never by name.
|
|
1747
|
+
*/
|
|
1748
|
+
universe(name) {
|
|
1749
|
+
return {
|
|
1750
|
+
assign: (opts) => safeRun(
|
|
1751
|
+
"flags.universe.assign",
|
|
1752
|
+
new AssignmentImpl(null, null, {}),
|
|
1753
|
+
() => _client ? _client.universe(name).assign(opts) : new AssignmentImpl(null, null, {})
|
|
1754
|
+
)
|
|
1611
1755
|
};
|
|
1612
|
-
return safeRun("flags.getExperiment", fallback, () => {
|
|
1613
|
-
if (!_client) return fallback;
|
|
1614
|
-
return typeof decodeOrOpts === "function" ? _client.getExperiment(name, defaultParams, decodeOrOpts, variants) : _client.getExperiment(name, defaultParams, decodeOrOpts ?? {});
|
|
1615
|
-
});
|
|
1616
|
-
},
|
|
1617
|
-
/** Manually log an exposure for an enrolled experiment. See
|
|
1618
|
-
* {@link Engine.logExposure}. No-op before configure(). */
|
|
1619
|
-
logExposure(name) {
|
|
1620
|
-
safeRun("flags.logExposure", void 0, () => _client?.logExposure(name));
|
|
1621
1756
|
},
|
|
1622
1757
|
track(eventName, props) {
|
|
1623
1758
|
safeRun("flags.track", void 0, () => _client?.track(eventName, props));
|
|
@@ -1723,13 +1858,20 @@ var Client = class {
|
|
|
1723
1858
|
() => this.engine.getConfig(name, decodeOrOpts)
|
|
1724
1859
|
);
|
|
1725
1860
|
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1861
|
+
/**
|
|
1862
|
+
* Assign the visitor within a universe: `client.universe("checkout").assign()`.
|
|
1863
|
+
* A universe is a mutual-exclusion pool, so the visitor lands in ≤1 experiment;
|
|
1864
|
+
* returns an {@link Assignment} (`.group` / `.get(field, fallback)`) and
|
|
1865
|
+
* auto-logs one exposure when enrolled. Replaces the removed `getExperiment`.
|
|
1866
|
+
*/
|
|
1867
|
+
universe(name) {
|
|
1868
|
+
return {
|
|
1869
|
+
assign: (opts) => safeRun(
|
|
1870
|
+
"Client.universe.assign",
|
|
1871
|
+
new AssignmentImpl(null, null, {}),
|
|
1872
|
+
() => this.engine.universe(name).assign(opts)
|
|
1873
|
+
)
|
|
1874
|
+
};
|
|
1733
1875
|
}
|
|
1734
1876
|
/** Read a killswitch (not user-bound; mirrors {@link Engine.getKillswitch}). */
|
|
1735
1877
|
getKillswitch(name, switchKey) {
|
|
@@ -1744,15 +1886,6 @@ var Client = class {
|
|
|
1744
1886
|
track(eventName, props) {
|
|
1745
1887
|
safeRun("Client.track", void 0, () => this.engine.track(eventName, props));
|
|
1746
1888
|
}
|
|
1747
|
-
/**
|
|
1748
|
-
* Log an exposure for `name` at the treatment's render for the bound user.
|
|
1749
|
-
* Delegates to {@link Engine.logExposure} (no-op when the visitor isn't
|
|
1750
|
-
* enrolled). Pair with `getExperiment(name, …, { logExposure: false })` /
|
|
1751
|
-
* `disableAutoExposure` to log exposure exactly when you render.
|
|
1752
|
-
*/
|
|
1753
|
-
logExposure(name) {
|
|
1754
|
-
safeRun("Client.logExposure", void 0, () => this.engine.logExposure(name));
|
|
1755
|
-
}
|
|
1756
1889
|
};
|
|
1757
1890
|
function dispatchSee(problem, consequence, extras, kind) {
|
|
1758
1891
|
if (!_client) {
|
|
@@ -1978,6 +2111,7 @@ var i18n = {
|
|
|
1978
2111
|
whenReady() {
|
|
1979
2112
|
if (typeof window === "undefined") return Promise.resolve();
|
|
1980
2113
|
if (window.i18n?.locale) return Promise.resolve();
|
|
2114
|
+
if (!hasDomEvents()) return Promise.resolve();
|
|
1981
2115
|
return new Promise((resolve) => {
|
|
1982
2116
|
const handler = () => resolve();
|
|
1983
2117
|
window.addEventListener("se:i18n:ready", handler, { once: true });
|
|
@@ -1988,6 +2122,8 @@ var i18n = {
|
|
|
1988
2122
|
if (typeof window === "undefined") return () => {
|
|
1989
2123
|
};
|
|
1990
2124
|
if (window.i18n) return window.i18n.on("update", cb);
|
|
2125
|
+
if (!hasDomEvents()) return () => {
|
|
2126
|
+
};
|
|
1991
2127
|
let unsub = () => {
|
|
1992
2128
|
};
|
|
1993
2129
|
const handler = () => {
|