@shipeasy/sdk 7.0.0 → 7.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.d.mts +38 -5
- package/dist/client/index.d.ts +38 -5
- package/dist/client/index.js +48 -11
- package/dist/client/index.mjs +48 -11
- package/dist/openfeature-server/index.d.mts +29 -8
- package/dist/openfeature-server/index.d.ts +29 -8
- package/dist/openfeature-web/index.d.mts +21 -3
- package/dist/openfeature-web/index.d.ts +21 -3
- package/dist/server/index.d.mts +38 -9
- package/dist/server/index.d.ts +38 -9
- package/dist/server/index.js +41 -10
- package/dist/server/index.mjs +41 -10
- package/docs/skill/SKILL.md +6 -0
- package/package.json +1 -1
package/dist/client/index.d.mts
CHANGED
|
@@ -280,10 +280,26 @@ interface EngineOptions {
|
|
|
280
280
|
/** Which published env to read values from. Defaults to "prod". */
|
|
281
281
|
env?: EngineEnv;
|
|
282
282
|
/**
|
|
283
|
-
*
|
|
283
|
+
* Master network switch — when `false`, the browser SDK makes NO outbound
|
|
284
|
+
* requests at all: identify()/init never call /sdk/evaluate (getters read code
|
|
285
|
+
* defaults / overrides), track() and exposure logging are no-ops, usage
|
|
286
|
+
* telemetry is off, and SDK-internal error self-monitoring is off.
|
|
287
|
+
*
|
|
288
|
+
* DEFAULT is environment-derived (see {@link isProductionEnv}): ON in
|
|
289
|
+
* production, OFF everywhere else. In the browser there is usually no native
|
|
290
|
+
* `NODE_ENV`, so production is taken from the `env` option above (default
|
|
291
|
+
* `"prod"`) — meaning it defaults ON. Pass `false` (or set `env: "dev"`) to
|
|
292
|
+
* keep a local build quiet.
|
|
293
|
+
*/
|
|
294
|
+
isNetworkEnabled?: boolean;
|
|
295
|
+
/**
|
|
296
|
+
* Per-evaluation usage telemetry ("tracking") — each getFlag/getConfig/
|
|
284
297
|
* getExperiment/getKillswitch call fires one fire-and-forget sendBeacon so
|
|
285
|
-
* usage is counted by Cloudflare's native per-path analytics.
|
|
286
|
-
*
|
|
298
|
+
* usage is counted by Cloudflare's native per-path analytics.
|
|
299
|
+
*
|
|
300
|
+
* DEFAULT is environment-derived: ON in production, OFF everywhere else (same
|
|
301
|
+
* inference as {@link isNetworkEnabled}). Pass `true` to force it off, `false`
|
|
302
|
+
* to force it on. Forced off whenever `isNetworkEnabled` is false.
|
|
287
303
|
*/
|
|
288
304
|
disableTelemetry?: boolean;
|
|
289
305
|
/** Override the telemetry beacon host. Defaults to {@link DEFAULT_TELEMETRY_URL}. */
|
|
@@ -365,6 +381,8 @@ declare class Engine {
|
|
|
365
381
|
private overrideListenerInstalled;
|
|
366
382
|
private identifySeq;
|
|
367
383
|
private readonly testMode;
|
|
384
|
+
private readonly networkEnabled;
|
|
385
|
+
private readonly offline;
|
|
368
386
|
private readonly flagOverrides;
|
|
369
387
|
private readonly configOverrides;
|
|
370
388
|
private readonly experimentOverrides;
|
|
@@ -517,6 +535,12 @@ interface ShipeasyClientConfig {
|
|
|
517
535
|
clientKey: string;
|
|
518
536
|
/** Override the ShipEasy CDN/edge base URL. Defaults to https://cdn.shipeasy.ai. */
|
|
519
537
|
baseUrl?: string;
|
|
538
|
+
/**
|
|
539
|
+
* Which published env to read values from — also the fallback signal for the
|
|
540
|
+
* environment-derived egress defaults (see {@link isNetworkEnabled}) when the
|
|
541
|
+
* browser exposes no native `NODE_ENV`. Defaults to `"prod"`.
|
|
542
|
+
*/
|
|
543
|
+
env?: EngineEnv;
|
|
520
544
|
/** Override the admin URL for the devtools overlay (dev use). */
|
|
521
545
|
adminUrl?: string;
|
|
522
546
|
/**
|
|
@@ -569,9 +593,18 @@ interface ShipeasyClientConfig {
|
|
|
569
593
|
*/
|
|
570
594
|
logLevel?: LogLevel;
|
|
571
595
|
/**
|
|
572
|
-
*
|
|
596
|
+
* Master network switch — `false` puts the browser SDK fully offline (no
|
|
597
|
+
* /sdk/evaluate, no /collect, no telemetry, no error self-monitoring).
|
|
598
|
+
* Defaults to environment-derived: ON in production, OFF everywhere else
|
|
599
|
+
* (in the browser, "production" comes from `env`, default `"prod"`). See
|
|
600
|
+
* {@link EngineOptions.isNetworkEnabled}.
|
|
601
|
+
*/
|
|
602
|
+
isNetworkEnabled?: boolean;
|
|
603
|
+
/**
|
|
604
|
+
* Disable per-evaluation usage telemetry ("tracking"). Defaults to
|
|
605
|
+
* environment-derived: ON in production, OFF everywhere else. Every
|
|
573
606
|
* flag/config/experiment/killswitch read fires one fire-and-forget beacon
|
|
574
|
-
* counted by Cloudflare's native per-path analytics. Pass `true` to
|
|
607
|
+
* counted by Cloudflare's native per-path analytics. Pass `true` to force off.
|
|
575
608
|
*/
|
|
576
609
|
disableTelemetry?: boolean;
|
|
577
610
|
/**
|
package/dist/client/index.d.ts
CHANGED
|
@@ -280,10 +280,26 @@ interface EngineOptions {
|
|
|
280
280
|
/** Which published env to read values from. Defaults to "prod". */
|
|
281
281
|
env?: EngineEnv;
|
|
282
282
|
/**
|
|
283
|
-
*
|
|
283
|
+
* Master network switch — when `false`, the browser SDK makes NO outbound
|
|
284
|
+
* requests at all: identify()/init never call /sdk/evaluate (getters read code
|
|
285
|
+
* defaults / overrides), track() and exposure logging are no-ops, usage
|
|
286
|
+
* telemetry is off, and SDK-internal error self-monitoring is off.
|
|
287
|
+
*
|
|
288
|
+
* DEFAULT is environment-derived (see {@link isProductionEnv}): ON in
|
|
289
|
+
* production, OFF everywhere else. In the browser there is usually no native
|
|
290
|
+
* `NODE_ENV`, so production is taken from the `env` option above (default
|
|
291
|
+
* `"prod"`) — meaning it defaults ON. Pass `false` (or set `env: "dev"`) to
|
|
292
|
+
* keep a local build quiet.
|
|
293
|
+
*/
|
|
294
|
+
isNetworkEnabled?: boolean;
|
|
295
|
+
/**
|
|
296
|
+
* Per-evaluation usage telemetry ("tracking") — each getFlag/getConfig/
|
|
284
297
|
* getExperiment/getKillswitch call fires one fire-and-forget sendBeacon so
|
|
285
|
-
* usage is counted by Cloudflare's native per-path analytics.
|
|
286
|
-
*
|
|
298
|
+
* usage is counted by Cloudflare's native per-path analytics.
|
|
299
|
+
*
|
|
300
|
+
* DEFAULT is environment-derived: ON in production, OFF everywhere else (same
|
|
301
|
+
* inference as {@link isNetworkEnabled}). Pass `true` to force it off, `false`
|
|
302
|
+
* to force it on. Forced off whenever `isNetworkEnabled` is false.
|
|
287
303
|
*/
|
|
288
304
|
disableTelemetry?: boolean;
|
|
289
305
|
/** Override the telemetry beacon host. Defaults to {@link DEFAULT_TELEMETRY_URL}. */
|
|
@@ -365,6 +381,8 @@ declare class Engine {
|
|
|
365
381
|
private overrideListenerInstalled;
|
|
366
382
|
private identifySeq;
|
|
367
383
|
private readonly testMode;
|
|
384
|
+
private readonly networkEnabled;
|
|
385
|
+
private readonly offline;
|
|
368
386
|
private readonly flagOverrides;
|
|
369
387
|
private readonly configOverrides;
|
|
370
388
|
private readonly experimentOverrides;
|
|
@@ -517,6 +535,12 @@ interface ShipeasyClientConfig {
|
|
|
517
535
|
clientKey: string;
|
|
518
536
|
/** Override the ShipEasy CDN/edge base URL. Defaults to https://cdn.shipeasy.ai. */
|
|
519
537
|
baseUrl?: string;
|
|
538
|
+
/**
|
|
539
|
+
* Which published env to read values from — also the fallback signal for the
|
|
540
|
+
* environment-derived egress defaults (see {@link isNetworkEnabled}) when the
|
|
541
|
+
* browser exposes no native `NODE_ENV`. Defaults to `"prod"`.
|
|
542
|
+
*/
|
|
543
|
+
env?: EngineEnv;
|
|
520
544
|
/** Override the admin URL for the devtools overlay (dev use). */
|
|
521
545
|
adminUrl?: string;
|
|
522
546
|
/**
|
|
@@ -569,9 +593,18 @@ interface ShipeasyClientConfig {
|
|
|
569
593
|
*/
|
|
570
594
|
logLevel?: LogLevel;
|
|
571
595
|
/**
|
|
572
|
-
*
|
|
596
|
+
* Master network switch — `false` puts the browser SDK fully offline (no
|
|
597
|
+
* /sdk/evaluate, no /collect, no telemetry, no error self-monitoring).
|
|
598
|
+
* Defaults to environment-derived: ON in production, OFF everywhere else
|
|
599
|
+
* (in the browser, "production" comes from `env`, default `"prod"`). See
|
|
600
|
+
* {@link EngineOptions.isNetworkEnabled}.
|
|
601
|
+
*/
|
|
602
|
+
isNetworkEnabled?: boolean;
|
|
603
|
+
/**
|
|
604
|
+
* Disable per-evaluation usage telemetry ("tracking"). Defaults to
|
|
605
|
+
* environment-derived: ON in production, OFF everywhere else. Every
|
|
573
606
|
* flag/config/experiment/killswitch read fires one fire-and-forget beacon
|
|
574
|
-
* counted by Cloudflare's native per-path analytics. Pass `true` to
|
|
607
|
+
* counted by Cloudflare's native per-path analytics. Pass `true` to force off.
|
|
575
608
|
*/
|
|
576
609
|
disableTelemetry?: boolean;
|
|
577
610
|
/**
|
package/dist/client/index.js
CHANGED
|
@@ -110,6 +110,24 @@ function send(url) {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
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";
|
|
118
|
+
}
|
|
119
|
+
function readNativeEnv() {
|
|
120
|
+
try {
|
|
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;
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
113
131
|
// src/see/core.ts
|
|
114
132
|
var SEE_MAX_MESSAGE = 500;
|
|
115
133
|
var SEE_MAX_STACK = 8e3;
|
|
@@ -497,6 +515,14 @@ function onDocument(type, handler, opts) {
|
|
|
497
515
|
} catch {
|
|
498
516
|
}
|
|
499
517
|
}
|
|
518
|
+
function dispatchWindow(type) {
|
|
519
|
+
try {
|
|
520
|
+
if (hasDomEvents() && typeof window.dispatchEvent === "function" && typeof CustomEvent === "function") {
|
|
521
|
+
window.dispatchEvent(new CustomEvent(type));
|
|
522
|
+
}
|
|
523
|
+
} catch {
|
|
524
|
+
}
|
|
525
|
+
}
|
|
500
526
|
var FLUSH_INTERVAL_MS = 5e3;
|
|
501
527
|
var MAX_BUFFER = 100;
|
|
502
528
|
var ANON_ID_KEY = "__se_anon_id";
|
|
@@ -1072,6 +1098,12 @@ var Engine = class _Engine {
|
|
|
1072
1098
|
// never fetches, track() is a no-op, telemetry is off, and the client is
|
|
1073
1099
|
// already ready with an empty eval result.
|
|
1074
1100
|
testMode;
|
|
1101
|
+
// Master network gate — false ⇒ the SDK never touches the network. Defaults to
|
|
1102
|
+
// environment-derived (prod-on), forced false by testMode.
|
|
1103
|
+
networkEnabled;
|
|
1104
|
+
// Convenience inverse of networkEnabled — the "no network at all" state every
|
|
1105
|
+
// /sdk/evaluate, /collect and telemetry gate keys on. testMode implies offline.
|
|
1106
|
+
offline;
|
|
1075
1107
|
// Programmatic overrides (Statsig-style). Set on any client via
|
|
1076
1108
|
// overrideFlag/overrideConfig/overrideExperiment; they win over BOTH the URL
|
|
1077
1109
|
// overrides and the fetched eval result. Cleared by clearOverrides().
|
|
@@ -1088,10 +1120,13 @@ var Engine = class _Engine {
|
|
|
1088
1120
|
this.baseUrl = (opts.baseUrl ?? "https://api.shipeasy.ai").replace(/\/$/, "");
|
|
1089
1121
|
this.env = opts.env ?? "prod";
|
|
1090
1122
|
this.testMode = opts.testMode === true;
|
|
1123
|
+
const prod = isProductionEnv(this.env);
|
|
1124
|
+
this.networkEnabled = this.testMode ? false : opts.isNetworkEnabled ?? prod;
|
|
1125
|
+
this.offline = !this.networkEnabled;
|
|
1091
1126
|
setInternalReportContext({
|
|
1092
1127
|
side: "client",
|
|
1093
1128
|
sdkVersion: version,
|
|
1094
|
-
enabled:
|
|
1129
|
+
enabled: this.networkEnabled && opts.disableInternalErrorReporting !== true
|
|
1095
1130
|
});
|
|
1096
1131
|
this.autoGuardrails = opts.autoGuardrails !== false;
|
|
1097
1132
|
this.autoCollectAlways = opts.autoCollectAlways === true;
|
|
@@ -1105,7 +1140,7 @@ var Engine = class _Engine {
|
|
|
1105
1140
|
engagement: g.engagement ?? this.autoGuardrails
|
|
1106
1141
|
};
|
|
1107
1142
|
this.anonId = getOrCreateAnonId();
|
|
1108
|
-
if (opts.anonymousStore && !this.
|
|
1143
|
+
if (opts.anonymousStore && !this.offline) {
|
|
1109
1144
|
this.anonReady = this.hydrateAnonFromStore(opts.anonymousStore);
|
|
1110
1145
|
}
|
|
1111
1146
|
this.buffer = new EventBuffer(`${this.baseUrl}/collect`, this.sdkKey);
|
|
@@ -1114,10 +1149,11 @@ var Engine = class _Engine {
|
|
|
1114
1149
|
sdkKey: this.sdkKey,
|
|
1115
1150
|
side: "client",
|
|
1116
1151
|
env: this.env,
|
|
1117
|
-
//
|
|
1118
|
-
|
|
1152
|
+
// Off when the network is disabled; otherwise honour an explicit
|
|
1153
|
+
// disableTelemetry, else default to prod-on (off outside production).
|
|
1154
|
+
disabled: this.offline || (opts.disableTelemetry ?? !prod)
|
|
1119
1155
|
});
|
|
1120
|
-
if (this.
|
|
1156
|
+
if (this.offline) {
|
|
1121
1157
|
this.evalResult = { flags: {}, configs: {}, experiments: {}, killswitches: {} };
|
|
1122
1158
|
} else {
|
|
1123
1159
|
void this.buffer.flushPendingAlias();
|
|
@@ -1162,7 +1198,7 @@ var Engine = class _Engine {
|
|
|
1162
1198
|
});
|
|
1163
1199
|
}
|
|
1164
1200
|
async identify(user) {
|
|
1165
|
-
if (this.
|
|
1201
|
+
if (this.offline) {
|
|
1166
1202
|
if (user.user_id !== void 0) this.userId = user.user_id;
|
|
1167
1203
|
this.notify();
|
|
1168
1204
|
return;
|
|
@@ -1224,6 +1260,7 @@ var Engine = class _Engine {
|
|
|
1224
1260
|
* the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
|
|
1225
1261
|
*/
|
|
1226
1262
|
reportError(problem, consequence, extras, kind, correlationId) {
|
|
1263
|
+
if (this.offline) return;
|
|
1227
1264
|
try {
|
|
1228
1265
|
const enriched = { ...collectSeeEnv(), ...extras };
|
|
1229
1266
|
const ev = buildSeeEvent(problem, consequence, enriched, {
|
|
@@ -1392,11 +1429,11 @@ var Engine = class _Engine {
|
|
|
1392
1429
|
getConfig: (n) => this.getConfig(n)
|
|
1393
1430
|
};
|
|
1394
1431
|
window.__shipeasy = bridge;
|
|
1395
|
-
|
|
1432
|
+
dispatchWindow("se:state:update");
|
|
1396
1433
|
return bridge;
|
|
1397
1434
|
}
|
|
1398
1435
|
track(eventName, props) {
|
|
1399
|
-
if (this.
|
|
1436
|
+
if (this.offline) return;
|
|
1400
1437
|
this.buffer.pushMetric(eventName, this.userId, this.anonId, this.stripPrivate(props));
|
|
1401
1438
|
}
|
|
1402
1439
|
/** Drop caller-marked private attributes from an outbound props bag. */
|
|
@@ -1549,10 +1586,12 @@ function shipeasy(opts) {
|
|
|
1549
1586
|
const client = configureShipeasy({
|
|
1550
1587
|
sdkKey: opts.clientKey,
|
|
1551
1588
|
baseUrl,
|
|
1589
|
+
env: opts.env,
|
|
1552
1590
|
logLevel: opts.logLevel,
|
|
1553
1591
|
autoGuardrails: blanket,
|
|
1554
1592
|
autoGuardrailGroups: groups,
|
|
1555
1593
|
autoCollectAlways: acObj?.always === true,
|
|
1594
|
+
isNetworkEnabled: opts.isNetworkEnabled,
|
|
1556
1595
|
disableTelemetry: opts.disableTelemetry,
|
|
1557
1596
|
disableInternalErrorReporting: opts.disableInternalErrorReporting,
|
|
1558
1597
|
disableAutoExposure: opts.disableAutoExposure,
|
|
@@ -1762,9 +1801,7 @@ var flags = {
|
|
|
1762
1801
|
*/
|
|
1763
1802
|
notifyMounted() {
|
|
1764
1803
|
_mountedAndReady = true;
|
|
1765
|
-
|
|
1766
|
-
window.dispatchEvent(new CustomEvent("se:override:change"));
|
|
1767
|
-
}
|
|
1804
|
+
dispatchWindow("se:override:change");
|
|
1768
1805
|
},
|
|
1769
1806
|
/** Subscribe for change notifications (identify/override). Used by framework adapters. */
|
|
1770
1807
|
subscribe(listener) {
|
package/dist/client/index.mjs
CHANGED
|
@@ -57,6 +57,24 @@ function send(url) {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
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";
|
|
65
|
+
}
|
|
66
|
+
function readNativeEnv() {
|
|
67
|
+
try {
|
|
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;
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
60
78
|
// src/see/core.ts
|
|
61
79
|
var SEE_MAX_MESSAGE = 500;
|
|
62
80
|
var SEE_MAX_STACK = 8e3;
|
|
@@ -444,6 +462,14 @@ function onDocument(type, handler, opts) {
|
|
|
444
462
|
} catch {
|
|
445
463
|
}
|
|
446
464
|
}
|
|
465
|
+
function dispatchWindow(type) {
|
|
466
|
+
try {
|
|
467
|
+
if (hasDomEvents() && typeof window.dispatchEvent === "function" && typeof CustomEvent === "function") {
|
|
468
|
+
window.dispatchEvent(new CustomEvent(type));
|
|
469
|
+
}
|
|
470
|
+
} catch {
|
|
471
|
+
}
|
|
472
|
+
}
|
|
447
473
|
var FLUSH_INTERVAL_MS = 5e3;
|
|
448
474
|
var MAX_BUFFER = 100;
|
|
449
475
|
var ANON_ID_KEY = "__se_anon_id";
|
|
@@ -1019,6 +1045,12 @@ var Engine = class _Engine {
|
|
|
1019
1045
|
// never fetches, track() is a no-op, telemetry is off, and the client is
|
|
1020
1046
|
// already ready with an empty eval result.
|
|
1021
1047
|
testMode;
|
|
1048
|
+
// Master network gate — false ⇒ the SDK never touches the network. Defaults to
|
|
1049
|
+
// environment-derived (prod-on), forced false by testMode.
|
|
1050
|
+
networkEnabled;
|
|
1051
|
+
// Convenience inverse of networkEnabled — the "no network at all" state every
|
|
1052
|
+
// /sdk/evaluate, /collect and telemetry gate keys on. testMode implies offline.
|
|
1053
|
+
offline;
|
|
1022
1054
|
// Programmatic overrides (Statsig-style). Set on any client via
|
|
1023
1055
|
// overrideFlag/overrideConfig/overrideExperiment; they win over BOTH the URL
|
|
1024
1056
|
// overrides and the fetched eval result. Cleared by clearOverrides().
|
|
@@ -1035,10 +1067,13 @@ var Engine = class _Engine {
|
|
|
1035
1067
|
this.baseUrl = (opts.baseUrl ?? "https://api.shipeasy.ai").replace(/\/$/, "");
|
|
1036
1068
|
this.env = opts.env ?? "prod";
|
|
1037
1069
|
this.testMode = opts.testMode === true;
|
|
1070
|
+
const prod = isProductionEnv(this.env);
|
|
1071
|
+
this.networkEnabled = this.testMode ? false : opts.isNetworkEnabled ?? prod;
|
|
1072
|
+
this.offline = !this.networkEnabled;
|
|
1038
1073
|
setInternalReportContext({
|
|
1039
1074
|
side: "client",
|
|
1040
1075
|
sdkVersion: version,
|
|
1041
|
-
enabled:
|
|
1076
|
+
enabled: this.networkEnabled && opts.disableInternalErrorReporting !== true
|
|
1042
1077
|
});
|
|
1043
1078
|
this.autoGuardrails = opts.autoGuardrails !== false;
|
|
1044
1079
|
this.autoCollectAlways = opts.autoCollectAlways === true;
|
|
@@ -1052,7 +1087,7 @@ var Engine = class _Engine {
|
|
|
1052
1087
|
engagement: g.engagement ?? this.autoGuardrails
|
|
1053
1088
|
};
|
|
1054
1089
|
this.anonId = getOrCreateAnonId();
|
|
1055
|
-
if (opts.anonymousStore && !this.
|
|
1090
|
+
if (opts.anonymousStore && !this.offline) {
|
|
1056
1091
|
this.anonReady = this.hydrateAnonFromStore(opts.anonymousStore);
|
|
1057
1092
|
}
|
|
1058
1093
|
this.buffer = new EventBuffer(`${this.baseUrl}/collect`, this.sdkKey);
|
|
@@ -1061,10 +1096,11 @@ var Engine = class _Engine {
|
|
|
1061
1096
|
sdkKey: this.sdkKey,
|
|
1062
1097
|
side: "client",
|
|
1063
1098
|
env: this.env,
|
|
1064
|
-
//
|
|
1065
|
-
|
|
1099
|
+
// Off when the network is disabled; otherwise honour an explicit
|
|
1100
|
+
// disableTelemetry, else default to prod-on (off outside production).
|
|
1101
|
+
disabled: this.offline || (opts.disableTelemetry ?? !prod)
|
|
1066
1102
|
});
|
|
1067
|
-
if (this.
|
|
1103
|
+
if (this.offline) {
|
|
1068
1104
|
this.evalResult = { flags: {}, configs: {}, experiments: {}, killswitches: {} };
|
|
1069
1105
|
} else {
|
|
1070
1106
|
void this.buffer.flushPendingAlias();
|
|
@@ -1109,7 +1145,7 @@ var Engine = class _Engine {
|
|
|
1109
1145
|
});
|
|
1110
1146
|
}
|
|
1111
1147
|
async identify(user) {
|
|
1112
|
-
if (this.
|
|
1148
|
+
if (this.offline) {
|
|
1113
1149
|
if (user.user_id !== void 0) this.userId = user.user_id;
|
|
1114
1150
|
this.notify();
|
|
1115
1151
|
return;
|
|
@@ -1171,6 +1207,7 @@ var Engine = class _Engine {
|
|
|
1171
1207
|
* the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
|
|
1172
1208
|
*/
|
|
1173
1209
|
reportError(problem, consequence, extras, kind, correlationId) {
|
|
1210
|
+
if (this.offline) return;
|
|
1174
1211
|
try {
|
|
1175
1212
|
const enriched = { ...collectSeeEnv(), ...extras };
|
|
1176
1213
|
const ev = buildSeeEvent(problem, consequence, enriched, {
|
|
@@ -1339,11 +1376,11 @@ var Engine = class _Engine {
|
|
|
1339
1376
|
getConfig: (n) => this.getConfig(n)
|
|
1340
1377
|
};
|
|
1341
1378
|
window.__shipeasy = bridge;
|
|
1342
|
-
|
|
1379
|
+
dispatchWindow("se:state:update");
|
|
1343
1380
|
return bridge;
|
|
1344
1381
|
}
|
|
1345
1382
|
track(eventName, props) {
|
|
1346
|
-
if (this.
|
|
1383
|
+
if (this.offline) return;
|
|
1347
1384
|
this.buffer.pushMetric(eventName, this.userId, this.anonId, this.stripPrivate(props));
|
|
1348
1385
|
}
|
|
1349
1386
|
/** Drop caller-marked private attributes from an outbound props bag. */
|
|
@@ -1496,10 +1533,12 @@ function shipeasy(opts) {
|
|
|
1496
1533
|
const client = configureShipeasy({
|
|
1497
1534
|
sdkKey: opts.clientKey,
|
|
1498
1535
|
baseUrl,
|
|
1536
|
+
env: opts.env,
|
|
1499
1537
|
logLevel: opts.logLevel,
|
|
1500
1538
|
autoGuardrails: blanket,
|
|
1501
1539
|
autoGuardrailGroups: groups,
|
|
1502
1540
|
autoCollectAlways: acObj?.always === true,
|
|
1541
|
+
isNetworkEnabled: opts.isNetworkEnabled,
|
|
1503
1542
|
disableTelemetry: opts.disableTelemetry,
|
|
1504
1543
|
disableInternalErrorReporting: opts.disableInternalErrorReporting,
|
|
1505
1544
|
disableAutoExposure: opts.disableAutoExposure,
|
|
@@ -1709,9 +1748,7 @@ var flags = {
|
|
|
1709
1748
|
*/
|
|
1710
1749
|
notifyMounted() {
|
|
1711
1750
|
_mountedAndReady = true;
|
|
1712
|
-
|
|
1713
|
-
window.dispatchEvent(new CustomEvent("se:override:change"));
|
|
1714
|
-
}
|
|
1751
|
+
dispatchWindow("se:override:change");
|
|
1715
1752
|
},
|
|
1716
1753
|
/** Subscribe for change notifications (identify/override). Used by framework adapters. */
|
|
1717
1754
|
subscribe(listener) {
|
|
@@ -201,12 +201,31 @@ interface EngineOptions {
|
|
|
201
201
|
*/
|
|
202
202
|
initialBlob?: FlagsBlob;
|
|
203
203
|
/**
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
204
|
+
* Master network switch — when `false`, the SDK makes NO outbound requests at
|
|
205
|
+
* all: init()/initOnce() never fetch (getters read code defaults / overrides),
|
|
206
|
+
* track() and exposure logging are no-ops, usage telemetry is off, and
|
|
207
|
+
* SDK-internal error self-monitoring is off. Think of it as a production-safe
|
|
208
|
+
* offline mode.
|
|
209
|
+
*
|
|
210
|
+
* DEFAULT is environment-derived (see {@link isProductionEnv}): ON in
|
|
211
|
+
* production, OFF everywhere else — so a local/dev/CI run never phones home
|
|
212
|
+
* unless you opt in. Production is inferred from `SHIPEASY_ENV`/`NODE_ENV`, or
|
|
213
|
+
* (when neither is set, e.g. on Cloudflare Workers) from the `env` option
|
|
214
|
+
* above, which defaults to `"prod"`. Pass an explicit value to override.
|
|
215
|
+
*/
|
|
216
|
+
isNetworkEnabled?: boolean;
|
|
217
|
+
/**
|
|
218
|
+
* Per-evaluation usage telemetry ("tracking"/outside logging). Each
|
|
219
|
+
* getFlag/getConfig/getExperiment/getKillswitch (and the per-key evaluate()
|
|
220
|
+
* loop) fires one fire-and-forget beacon counted by Cloudflare's native
|
|
221
|
+
* per-path analytics.
|
|
222
|
+
*
|
|
223
|
+
* DEFAULT is environment-derived: ON in production, OFF everywhere else (same
|
|
224
|
+
* inference as {@link isNetworkEnabled}). Pass `true` to force it off, `false`
|
|
225
|
+
* to force it on. NOTE: on Cloudflare Workers each beacon is an outbound
|
|
226
|
+
* subrequest (cap 50 free / 1000 paid per invocation), so disable this on hot
|
|
227
|
+
* request paths that evaluate many flags per request. Forced off whenever
|
|
228
|
+
* `isNetworkEnabled` is false.
|
|
210
229
|
*/
|
|
211
230
|
disableTelemetry?: boolean;
|
|
212
231
|
/** Override the telemetry beacon host. Defaults to {@link DEFAULT_TELEMETRY_URL}. */
|
|
@@ -258,6 +277,8 @@ declare class Engine {
|
|
|
258
277
|
private readonly env;
|
|
259
278
|
private readonly privateAttributes;
|
|
260
279
|
private readonly stickyStore;
|
|
280
|
+
private readonly networkEnabled;
|
|
281
|
+
private readonly offline;
|
|
261
282
|
private readonly telemetry;
|
|
262
283
|
private readonly seeLimiter;
|
|
263
284
|
private flagsBlob;
|
|
@@ -388,8 +409,8 @@ declare class Engine {
|
|
|
388
409
|
/**
|
|
389
410
|
* POST a single exposure for an enrolled `(user, experiment, group)`. Deduped
|
|
390
411
|
* per process (bounded set) so repeated `assign()` calls in one server don't
|
|
391
|
-
* spam `/collect`. Fire-and-forget; no-op
|
|
392
|
-
* `assignUniverse` auto-logs — the browser's auto-exposure parity for SSR.
|
|
412
|
+
* spam `/collect`. Fire-and-forget; no-op when the network is disabled. This
|
|
413
|
+
* is how `assignUniverse` auto-logs — the browser's auto-exposure parity for SSR.
|
|
393
414
|
*/
|
|
394
415
|
private postExposure;
|
|
395
416
|
/**
|
|
@@ -201,12 +201,31 @@ interface EngineOptions {
|
|
|
201
201
|
*/
|
|
202
202
|
initialBlob?: FlagsBlob;
|
|
203
203
|
/**
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
204
|
+
* Master network switch — when `false`, the SDK makes NO outbound requests at
|
|
205
|
+
* all: init()/initOnce() never fetch (getters read code defaults / overrides),
|
|
206
|
+
* track() and exposure logging are no-ops, usage telemetry is off, and
|
|
207
|
+
* SDK-internal error self-monitoring is off. Think of it as a production-safe
|
|
208
|
+
* offline mode.
|
|
209
|
+
*
|
|
210
|
+
* DEFAULT is environment-derived (see {@link isProductionEnv}): ON in
|
|
211
|
+
* production, OFF everywhere else — so a local/dev/CI run never phones home
|
|
212
|
+
* unless you opt in. Production is inferred from `SHIPEASY_ENV`/`NODE_ENV`, or
|
|
213
|
+
* (when neither is set, e.g. on Cloudflare Workers) from the `env` option
|
|
214
|
+
* above, which defaults to `"prod"`. Pass an explicit value to override.
|
|
215
|
+
*/
|
|
216
|
+
isNetworkEnabled?: boolean;
|
|
217
|
+
/**
|
|
218
|
+
* Per-evaluation usage telemetry ("tracking"/outside logging). Each
|
|
219
|
+
* getFlag/getConfig/getExperiment/getKillswitch (and the per-key evaluate()
|
|
220
|
+
* loop) fires one fire-and-forget beacon counted by Cloudflare's native
|
|
221
|
+
* per-path analytics.
|
|
222
|
+
*
|
|
223
|
+
* DEFAULT is environment-derived: ON in production, OFF everywhere else (same
|
|
224
|
+
* inference as {@link isNetworkEnabled}). Pass `true` to force it off, `false`
|
|
225
|
+
* to force it on. NOTE: on Cloudflare Workers each beacon is an outbound
|
|
226
|
+
* subrequest (cap 50 free / 1000 paid per invocation), so disable this on hot
|
|
227
|
+
* request paths that evaluate many flags per request. Forced off whenever
|
|
228
|
+
* `isNetworkEnabled` is false.
|
|
210
229
|
*/
|
|
211
230
|
disableTelemetry?: boolean;
|
|
212
231
|
/** Override the telemetry beacon host. Defaults to {@link DEFAULT_TELEMETRY_URL}. */
|
|
@@ -258,6 +277,8 @@ declare class Engine {
|
|
|
258
277
|
private readonly env;
|
|
259
278
|
private readonly privateAttributes;
|
|
260
279
|
private readonly stickyStore;
|
|
280
|
+
private readonly networkEnabled;
|
|
281
|
+
private readonly offline;
|
|
261
282
|
private readonly telemetry;
|
|
262
283
|
private readonly seeLimiter;
|
|
263
284
|
private flagsBlob;
|
|
@@ -388,8 +409,8 @@ declare class Engine {
|
|
|
388
409
|
/**
|
|
389
410
|
* POST a single exposure for an enrolled `(user, experiment, group)`. Deduped
|
|
390
411
|
* per process (bounded set) so repeated `assign()` calls in one server don't
|
|
391
|
-
* spam `/collect`. Fire-and-forget; no-op
|
|
392
|
-
* `assignUniverse` auto-logs — the browser's auto-exposure parity for SSR.
|
|
412
|
+
* spam `/collect`. Fire-and-forget; no-op when the network is disabled. This
|
|
413
|
+
* is how `assignUniverse` auto-logs — the browser's auto-exposure parity for SSR.
|
|
393
414
|
*/
|
|
394
415
|
private postExposure;
|
|
395
416
|
/**
|
|
@@ -158,10 +158,26 @@ interface EngineOptions {
|
|
|
158
158
|
/** Which published env to read values from. Defaults to "prod". */
|
|
159
159
|
env?: EngineEnv;
|
|
160
160
|
/**
|
|
161
|
-
*
|
|
161
|
+
* Master network switch — when `false`, the browser SDK makes NO outbound
|
|
162
|
+
* requests at all: identify()/init never call /sdk/evaluate (getters read code
|
|
163
|
+
* defaults / overrides), track() and exposure logging are no-ops, usage
|
|
164
|
+
* telemetry is off, and SDK-internal error self-monitoring is off.
|
|
165
|
+
*
|
|
166
|
+
* DEFAULT is environment-derived (see {@link isProductionEnv}): ON in
|
|
167
|
+
* production, OFF everywhere else. In the browser there is usually no native
|
|
168
|
+
* `NODE_ENV`, so production is taken from the `env` option above (default
|
|
169
|
+
* `"prod"`) — meaning it defaults ON. Pass `false` (or set `env: "dev"`) to
|
|
170
|
+
* keep a local build quiet.
|
|
171
|
+
*/
|
|
172
|
+
isNetworkEnabled?: boolean;
|
|
173
|
+
/**
|
|
174
|
+
* Per-evaluation usage telemetry ("tracking") — each getFlag/getConfig/
|
|
162
175
|
* getExperiment/getKillswitch call fires one fire-and-forget sendBeacon so
|
|
163
|
-
* usage is counted by Cloudflare's native per-path analytics.
|
|
164
|
-
*
|
|
176
|
+
* usage is counted by Cloudflare's native per-path analytics.
|
|
177
|
+
*
|
|
178
|
+
* DEFAULT is environment-derived: ON in production, OFF everywhere else (same
|
|
179
|
+
* inference as {@link isNetworkEnabled}). Pass `true` to force it off, `false`
|
|
180
|
+
* to force it on. Forced off whenever `isNetworkEnabled` is false.
|
|
165
181
|
*/
|
|
166
182
|
disableTelemetry?: boolean;
|
|
167
183
|
/** Override the telemetry beacon host. Defaults to {@link DEFAULT_TELEMETRY_URL}. */
|
|
@@ -243,6 +259,8 @@ declare class Engine {
|
|
|
243
259
|
private overrideListenerInstalled;
|
|
244
260
|
private identifySeq;
|
|
245
261
|
private readonly testMode;
|
|
262
|
+
private readonly networkEnabled;
|
|
263
|
+
private readonly offline;
|
|
246
264
|
private readonly flagOverrides;
|
|
247
265
|
private readonly configOverrides;
|
|
248
266
|
private readonly experimentOverrides;
|
|
@@ -158,10 +158,26 @@ interface EngineOptions {
|
|
|
158
158
|
/** Which published env to read values from. Defaults to "prod". */
|
|
159
159
|
env?: EngineEnv;
|
|
160
160
|
/**
|
|
161
|
-
*
|
|
161
|
+
* Master network switch — when `false`, the browser SDK makes NO outbound
|
|
162
|
+
* requests at all: identify()/init never call /sdk/evaluate (getters read code
|
|
163
|
+
* defaults / overrides), track() and exposure logging are no-ops, usage
|
|
164
|
+
* telemetry is off, and SDK-internal error self-monitoring is off.
|
|
165
|
+
*
|
|
166
|
+
* DEFAULT is environment-derived (see {@link isProductionEnv}): ON in
|
|
167
|
+
* production, OFF everywhere else. In the browser there is usually no native
|
|
168
|
+
* `NODE_ENV`, so production is taken from the `env` option above (default
|
|
169
|
+
* `"prod"`) — meaning it defaults ON. Pass `false` (or set `env: "dev"`) to
|
|
170
|
+
* keep a local build quiet.
|
|
171
|
+
*/
|
|
172
|
+
isNetworkEnabled?: boolean;
|
|
173
|
+
/**
|
|
174
|
+
* Per-evaluation usage telemetry ("tracking") — each getFlag/getConfig/
|
|
162
175
|
* getExperiment/getKillswitch call fires one fire-and-forget sendBeacon so
|
|
163
|
-
* usage is counted by Cloudflare's native per-path analytics.
|
|
164
|
-
*
|
|
176
|
+
* usage is counted by Cloudflare's native per-path analytics.
|
|
177
|
+
*
|
|
178
|
+
* DEFAULT is environment-derived: ON in production, OFF everywhere else (same
|
|
179
|
+
* inference as {@link isNetworkEnabled}). Pass `true` to force it off, `false`
|
|
180
|
+
* to force it on. Forced off whenever `isNetworkEnabled` is false.
|
|
165
181
|
*/
|
|
166
182
|
disableTelemetry?: boolean;
|
|
167
183
|
/** Override the telemetry beacon host. Defaults to {@link DEFAULT_TELEMETRY_URL}. */
|
|
@@ -243,6 +259,8 @@ declare class Engine {
|
|
|
243
259
|
private overrideListenerInstalled;
|
|
244
260
|
private identifySeq;
|
|
245
261
|
private readonly testMode;
|
|
262
|
+
private readonly networkEnabled;
|
|
263
|
+
private readonly offline;
|
|
246
264
|
private readonly flagOverrides;
|
|
247
265
|
private readonly configOverrides;
|
|
248
266
|
private readonly experimentOverrides;
|
package/dist/server/index.d.mts
CHANGED
|
@@ -309,12 +309,31 @@ interface EngineOptions {
|
|
|
309
309
|
*/
|
|
310
310
|
initialBlob?: FlagsBlob;
|
|
311
311
|
/**
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
312
|
+
* Master network switch — when `false`, the SDK makes NO outbound requests at
|
|
313
|
+
* all: init()/initOnce() never fetch (getters read code defaults / overrides),
|
|
314
|
+
* track() and exposure logging are no-ops, usage telemetry is off, and
|
|
315
|
+
* SDK-internal error self-monitoring is off. Think of it as a production-safe
|
|
316
|
+
* offline mode.
|
|
317
|
+
*
|
|
318
|
+
* DEFAULT is environment-derived (see {@link isProductionEnv}): ON in
|
|
319
|
+
* production, OFF everywhere else — so a local/dev/CI run never phones home
|
|
320
|
+
* unless you opt in. Production is inferred from `SHIPEASY_ENV`/`NODE_ENV`, or
|
|
321
|
+
* (when neither is set, e.g. on Cloudflare Workers) from the `env` option
|
|
322
|
+
* above, which defaults to `"prod"`. Pass an explicit value to override.
|
|
323
|
+
*/
|
|
324
|
+
isNetworkEnabled?: boolean;
|
|
325
|
+
/**
|
|
326
|
+
* Per-evaluation usage telemetry ("tracking"/outside logging). Each
|
|
327
|
+
* getFlag/getConfig/getExperiment/getKillswitch (and the per-key evaluate()
|
|
328
|
+
* loop) fires one fire-and-forget beacon counted by Cloudflare's native
|
|
329
|
+
* per-path analytics.
|
|
330
|
+
*
|
|
331
|
+
* DEFAULT is environment-derived: ON in production, OFF everywhere else (same
|
|
332
|
+
* inference as {@link isNetworkEnabled}). Pass `true` to force it off, `false`
|
|
333
|
+
* to force it on. NOTE: on Cloudflare Workers each beacon is an outbound
|
|
334
|
+
* subrequest (cap 50 free / 1000 paid per invocation), so disable this on hot
|
|
335
|
+
* request paths that evaluate many flags per request. Forced off whenever
|
|
336
|
+
* `isNetworkEnabled` is false.
|
|
318
337
|
*/
|
|
319
338
|
disableTelemetry?: boolean;
|
|
320
339
|
/** Override the telemetry beacon host. Defaults to {@link DEFAULT_TELEMETRY_URL}. */
|
|
@@ -366,6 +385,8 @@ declare class Engine {
|
|
|
366
385
|
private readonly env;
|
|
367
386
|
private readonly privateAttributes;
|
|
368
387
|
private readonly stickyStore;
|
|
388
|
+
private readonly networkEnabled;
|
|
389
|
+
private readonly offline;
|
|
369
390
|
private readonly telemetry;
|
|
370
391
|
private readonly seeLimiter;
|
|
371
392
|
private flagsBlob;
|
|
@@ -496,8 +517,8 @@ declare class Engine {
|
|
|
496
517
|
/**
|
|
497
518
|
* POST a single exposure for an enrolled `(user, experiment, group)`. Deduped
|
|
498
519
|
* per process (bounded set) so repeated `assign()` calls in one server don't
|
|
499
|
-
* spam `/collect`. Fire-and-forget; no-op
|
|
500
|
-
* `assignUniverse` auto-logs — the browser's auto-exposure parity for SSR.
|
|
520
|
+
* spam `/collect`. Fire-and-forget; no-op when the network is disabled. This
|
|
521
|
+
* is how `assignUniverse` auto-logs — the browser's auto-exposure parity for SSR.
|
|
501
522
|
*/
|
|
502
523
|
private postExposure;
|
|
503
524
|
/**
|
|
@@ -591,7 +612,15 @@ interface ShipeasyServerConfig {
|
|
|
591
612
|
*/
|
|
592
613
|
logLevel?: LogLevel;
|
|
593
614
|
/**
|
|
594
|
-
*
|
|
615
|
+
* Master network switch — `false` puts the SDK fully offline (no fetch, no
|
|
616
|
+
* track, no telemetry, no error self-monitoring). Defaults to
|
|
617
|
+
* environment-derived: ON in production, OFF everywhere else. See
|
|
618
|
+
* {@link EngineOptions.isNetworkEnabled}.
|
|
619
|
+
*/
|
|
620
|
+
isNetworkEnabled?: boolean;
|
|
621
|
+
/**
|
|
622
|
+
* Disable per-evaluation usage telemetry ("tracking"). Defaults to
|
|
623
|
+
* environment-derived: ON in production, OFF everywhere else. On Cloudflare
|
|
595
624
|
* Workers each beacon is an outbound subrequest, so disable on hot SSR paths
|
|
596
625
|
* that evaluate many flags per request. See {@link EngineOptions.disableTelemetry}.
|
|
597
626
|
*/
|
package/dist/server/index.d.ts
CHANGED
|
@@ -309,12 +309,31 @@ interface EngineOptions {
|
|
|
309
309
|
*/
|
|
310
310
|
initialBlob?: FlagsBlob;
|
|
311
311
|
/**
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
312
|
+
* Master network switch — when `false`, the SDK makes NO outbound requests at
|
|
313
|
+
* all: init()/initOnce() never fetch (getters read code defaults / overrides),
|
|
314
|
+
* track() and exposure logging are no-ops, usage telemetry is off, and
|
|
315
|
+
* SDK-internal error self-monitoring is off. Think of it as a production-safe
|
|
316
|
+
* offline mode.
|
|
317
|
+
*
|
|
318
|
+
* DEFAULT is environment-derived (see {@link isProductionEnv}): ON in
|
|
319
|
+
* production, OFF everywhere else — so a local/dev/CI run never phones home
|
|
320
|
+
* unless you opt in. Production is inferred from `SHIPEASY_ENV`/`NODE_ENV`, or
|
|
321
|
+
* (when neither is set, e.g. on Cloudflare Workers) from the `env` option
|
|
322
|
+
* above, which defaults to `"prod"`. Pass an explicit value to override.
|
|
323
|
+
*/
|
|
324
|
+
isNetworkEnabled?: boolean;
|
|
325
|
+
/**
|
|
326
|
+
* Per-evaluation usage telemetry ("tracking"/outside logging). Each
|
|
327
|
+
* getFlag/getConfig/getExperiment/getKillswitch (and the per-key evaluate()
|
|
328
|
+
* loop) fires one fire-and-forget beacon counted by Cloudflare's native
|
|
329
|
+
* per-path analytics.
|
|
330
|
+
*
|
|
331
|
+
* DEFAULT is environment-derived: ON in production, OFF everywhere else (same
|
|
332
|
+
* inference as {@link isNetworkEnabled}). Pass `true` to force it off, `false`
|
|
333
|
+
* to force it on. NOTE: on Cloudflare Workers each beacon is an outbound
|
|
334
|
+
* subrequest (cap 50 free / 1000 paid per invocation), so disable this on hot
|
|
335
|
+
* request paths that evaluate many flags per request. Forced off whenever
|
|
336
|
+
* `isNetworkEnabled` is false.
|
|
318
337
|
*/
|
|
319
338
|
disableTelemetry?: boolean;
|
|
320
339
|
/** Override the telemetry beacon host. Defaults to {@link DEFAULT_TELEMETRY_URL}. */
|
|
@@ -366,6 +385,8 @@ declare class Engine {
|
|
|
366
385
|
private readonly env;
|
|
367
386
|
private readonly privateAttributes;
|
|
368
387
|
private readonly stickyStore;
|
|
388
|
+
private readonly networkEnabled;
|
|
389
|
+
private readonly offline;
|
|
369
390
|
private readonly telemetry;
|
|
370
391
|
private readonly seeLimiter;
|
|
371
392
|
private flagsBlob;
|
|
@@ -496,8 +517,8 @@ declare class Engine {
|
|
|
496
517
|
/**
|
|
497
518
|
* POST a single exposure for an enrolled `(user, experiment, group)`. Deduped
|
|
498
519
|
* per process (bounded set) so repeated `assign()` calls in one server don't
|
|
499
|
-
* spam `/collect`. Fire-and-forget; no-op
|
|
500
|
-
* `assignUniverse` auto-logs — the browser's auto-exposure parity for SSR.
|
|
520
|
+
* spam `/collect`. Fire-and-forget; no-op when the network is disabled. This
|
|
521
|
+
* is how `assignUniverse` auto-logs — the browser's auto-exposure parity for SSR.
|
|
501
522
|
*/
|
|
502
523
|
private postExposure;
|
|
503
524
|
/**
|
|
@@ -591,7 +612,15 @@ interface ShipeasyServerConfig {
|
|
|
591
612
|
*/
|
|
592
613
|
logLevel?: LogLevel;
|
|
593
614
|
/**
|
|
594
|
-
*
|
|
615
|
+
* Master network switch — `false` puts the SDK fully offline (no fetch, no
|
|
616
|
+
* track, no telemetry, no error self-monitoring). Defaults to
|
|
617
|
+
* environment-derived: ON in production, OFF everywhere else. See
|
|
618
|
+
* {@link EngineOptions.isNetworkEnabled}.
|
|
619
|
+
*/
|
|
620
|
+
isNetworkEnabled?: boolean;
|
|
621
|
+
/**
|
|
622
|
+
* Disable per-evaluation usage telemetry ("tracking"). Defaults to
|
|
623
|
+
* environment-derived: ON in production, OFF everywhere else. On Cloudflare
|
|
595
624
|
* Workers each beacon is an outbound subrequest, so disable on hot SSR paths
|
|
596
625
|
* that evaluate many flags per request. See {@link EngineOptions.disableTelemetry}.
|
|
597
626
|
*/
|
package/dist/server/index.js
CHANGED
|
@@ -122,6 +122,24 @@ function send(url) {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
// src/env.ts
|
|
126
|
+
function isProductionEnv(configuredEnv) {
|
|
127
|
+
const native = readNativeEnv();
|
|
128
|
+
if (native !== null) return native === "production" || native === "prod";
|
|
129
|
+
return (configuredEnv ?? "prod").toLowerCase() === "prod";
|
|
130
|
+
}
|
|
131
|
+
function readNativeEnv() {
|
|
132
|
+
try {
|
|
133
|
+
const proc = globalThis.process;
|
|
134
|
+
const raw = proc?.env?.SHIPEASY_ENV ?? proc?.env?.NODE_ENV;
|
|
135
|
+
if (typeof raw !== "string") return null;
|
|
136
|
+
const v = raw.trim().toLowerCase();
|
|
137
|
+
return v.length ? v : null;
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
125
143
|
// src/see/core.ts
|
|
126
144
|
var SEE_MAX_MESSAGE = 500;
|
|
127
145
|
var SEE_MAX_STACK = 8e3;
|
|
@@ -724,6 +742,13 @@ var Engine = class _Engine {
|
|
|
724
742
|
env;
|
|
725
743
|
privateAttributes;
|
|
726
744
|
stickyStore;
|
|
745
|
+
// Master network gate — false ⇒ the SDK never touches the network (no fetch,
|
|
746
|
+
// no track, no telemetry, no internal error reports). Defaults to
|
|
747
|
+
// environment-derived (prod-on), forced false by testMode.
|
|
748
|
+
networkEnabled;
|
|
749
|
+
// Convenience inverse of networkEnabled — the "no network at all" state that
|
|
750
|
+
// every fetch/track/init gate keys on. `testMode` implies offline.
|
|
751
|
+
offline;
|
|
727
752
|
telemetry;
|
|
728
753
|
seeLimiter = new SeeLimiter();
|
|
729
754
|
flagsBlob = null;
|
|
@@ -757,20 +782,24 @@ var Engine = class _Engine {
|
|
|
757
782
|
this.privateAttributes = opts.privateAttributes ?? [];
|
|
758
783
|
this.stickyStore = opts.stickyStore;
|
|
759
784
|
this.testMode = opts.testMode === true;
|
|
785
|
+
const prod = isProductionEnv(this.env);
|
|
786
|
+
this.networkEnabled = this.testMode ? false : opts.isNetworkEnabled ?? prod;
|
|
787
|
+
this.offline = !this.networkEnabled;
|
|
760
788
|
setInternalReportContext({
|
|
761
789
|
side: "server",
|
|
762
790
|
sdkVersion: version,
|
|
763
|
-
enabled:
|
|
791
|
+
enabled: this.networkEnabled && opts.disableInternalErrorReporting !== true
|
|
764
792
|
});
|
|
765
793
|
this.telemetry = new Telemetry({
|
|
766
794
|
endpoint: opts.telemetryUrl ?? DEFAULT_TELEMETRY_URL,
|
|
767
795
|
sdkKey: this.apiKey,
|
|
768
796
|
side: "server",
|
|
769
797
|
env: this.env,
|
|
770
|
-
//
|
|
771
|
-
|
|
798
|
+
// Off when the network is disabled; otherwise honour an explicit
|
|
799
|
+
// disableTelemetry, else default to prod-on (off outside production).
|
|
800
|
+
disabled: this.offline || (opts.disableTelemetry ?? !prod)
|
|
772
801
|
});
|
|
773
|
-
if (opts.initialBlob || this.
|
|
802
|
+
if (opts.initialBlob || this.offline) {
|
|
774
803
|
this.flagsBlob = opts.initialBlob ?? { version: "test", plan: "free", gates: {}, configs: {}, killswitches: {} };
|
|
775
804
|
this.expsBlob = this.expsBlob ?? { version: "test", universes: {}, experiments: {} };
|
|
776
805
|
this.initialized = true;
|
|
@@ -821,7 +850,7 @@ var Engine = class _Engine {
|
|
|
821
850
|
return _Engine.fromSnapshot(snapshot);
|
|
822
851
|
}
|
|
823
852
|
async init() {
|
|
824
|
-
if (this.
|
|
853
|
+
if (this.offline) {
|
|
825
854
|
this.initialized = true;
|
|
826
855
|
return;
|
|
827
856
|
}
|
|
@@ -830,7 +859,7 @@ var Engine = class _Engine {
|
|
|
830
859
|
this.startPoll();
|
|
831
860
|
}
|
|
832
861
|
async initOnce() {
|
|
833
|
-
if (this.
|
|
862
|
+
if (this.offline || this.initialized) return;
|
|
834
863
|
await this.fetchAll();
|
|
835
864
|
this.initialized = true;
|
|
836
865
|
}
|
|
@@ -1054,7 +1083,7 @@ var Engine = class _Engine {
|
|
|
1054
1083
|
return out;
|
|
1055
1084
|
}
|
|
1056
1085
|
track(userId, eventName, props) {
|
|
1057
|
-
if (this.
|
|
1086
|
+
if (this.offline) return;
|
|
1058
1087
|
const safeProps = this.stripPrivate(props);
|
|
1059
1088
|
const body = JSON.stringify({
|
|
1060
1089
|
events: [
|
|
@@ -1076,11 +1105,11 @@ var Engine = class _Engine {
|
|
|
1076
1105
|
/**
|
|
1077
1106
|
* POST a single exposure for an enrolled `(user, experiment, group)`. Deduped
|
|
1078
1107
|
* per process (bounded set) so repeated `assign()` calls in one server don't
|
|
1079
|
-
* spam `/collect`. Fire-and-forget; no-op
|
|
1080
|
-
* `assignUniverse` auto-logs — the browser's auto-exposure parity for SSR.
|
|
1108
|
+
* spam `/collect`. Fire-and-forget; no-op when the network is disabled. This
|
|
1109
|
+
* is how `assignUniverse` auto-logs — the browser's auto-exposure parity for SSR.
|
|
1081
1110
|
*/
|
|
1082
1111
|
postExposure(user, experiment, group) {
|
|
1083
|
-
if (this.
|
|
1112
|
+
if (this.offline) return;
|
|
1084
1113
|
const uid = user.user_id ?? user.anonymous_id;
|
|
1085
1114
|
const dedupKey = `${uid ?? ""}:${experiment}:${group}`;
|
|
1086
1115
|
if (this.exposureSeen.has(dedupKey)) return;
|
|
@@ -1110,6 +1139,7 @@ var Engine = class _Engine {
|
|
|
1110
1139
|
* dedup window + per-process cap.
|
|
1111
1140
|
*/
|
|
1112
1141
|
reportError(problem, consequence, extras, kind) {
|
|
1142
|
+
if (this.offline) return;
|
|
1113
1143
|
try {
|
|
1114
1144
|
const correlationId = seeContext.getStore()?.correlationId;
|
|
1115
1145
|
const ev = buildSeeEvent(problem, consequence, extras, {
|
|
@@ -1326,6 +1356,7 @@ async function shipeasy(opts) {
|
|
|
1326
1356
|
const profile = opts.i18nDefaultProfile ?? "en:prod";
|
|
1327
1357
|
flags.configure({
|
|
1328
1358
|
apiKey: serverKey,
|
|
1359
|
+
isNetworkEnabled: opts.isNetworkEnabled,
|
|
1329
1360
|
disableTelemetry: opts.disableTelemetry,
|
|
1330
1361
|
disableInternalErrorReporting: opts.disableInternalErrorReporting,
|
|
1331
1362
|
privateAttributes: opts.privateAttributes,
|
package/dist/server/index.mjs
CHANGED
|
@@ -67,6 +67,24 @@ function send(url) {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// src/env.ts
|
|
71
|
+
function isProductionEnv(configuredEnv) {
|
|
72
|
+
const native = readNativeEnv();
|
|
73
|
+
if (native !== null) return native === "production" || native === "prod";
|
|
74
|
+
return (configuredEnv ?? "prod").toLowerCase() === "prod";
|
|
75
|
+
}
|
|
76
|
+
function readNativeEnv() {
|
|
77
|
+
try {
|
|
78
|
+
const proc = globalThis.process;
|
|
79
|
+
const raw = proc?.env?.SHIPEASY_ENV ?? proc?.env?.NODE_ENV;
|
|
80
|
+
if (typeof raw !== "string") return null;
|
|
81
|
+
const v = raw.trim().toLowerCase();
|
|
82
|
+
return v.length ? v : null;
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
70
88
|
// src/see/core.ts
|
|
71
89
|
var SEE_MAX_MESSAGE = 500;
|
|
72
90
|
var SEE_MAX_STACK = 8e3;
|
|
@@ -669,6 +687,13 @@ var Engine = class _Engine {
|
|
|
669
687
|
env;
|
|
670
688
|
privateAttributes;
|
|
671
689
|
stickyStore;
|
|
690
|
+
// Master network gate — false ⇒ the SDK never touches the network (no fetch,
|
|
691
|
+
// no track, no telemetry, no internal error reports). Defaults to
|
|
692
|
+
// environment-derived (prod-on), forced false by testMode.
|
|
693
|
+
networkEnabled;
|
|
694
|
+
// Convenience inverse of networkEnabled — the "no network at all" state that
|
|
695
|
+
// every fetch/track/init gate keys on. `testMode` implies offline.
|
|
696
|
+
offline;
|
|
672
697
|
telemetry;
|
|
673
698
|
seeLimiter = new SeeLimiter();
|
|
674
699
|
flagsBlob = null;
|
|
@@ -702,20 +727,24 @@ var Engine = class _Engine {
|
|
|
702
727
|
this.privateAttributes = opts.privateAttributes ?? [];
|
|
703
728
|
this.stickyStore = opts.stickyStore;
|
|
704
729
|
this.testMode = opts.testMode === true;
|
|
730
|
+
const prod = isProductionEnv(this.env);
|
|
731
|
+
this.networkEnabled = this.testMode ? false : opts.isNetworkEnabled ?? prod;
|
|
732
|
+
this.offline = !this.networkEnabled;
|
|
705
733
|
setInternalReportContext({
|
|
706
734
|
side: "server",
|
|
707
735
|
sdkVersion: version,
|
|
708
|
-
enabled:
|
|
736
|
+
enabled: this.networkEnabled && opts.disableInternalErrorReporting !== true
|
|
709
737
|
});
|
|
710
738
|
this.telemetry = new Telemetry({
|
|
711
739
|
endpoint: opts.telemetryUrl ?? DEFAULT_TELEMETRY_URL,
|
|
712
740
|
sdkKey: this.apiKey,
|
|
713
741
|
side: "server",
|
|
714
742
|
env: this.env,
|
|
715
|
-
//
|
|
716
|
-
|
|
743
|
+
// Off when the network is disabled; otherwise honour an explicit
|
|
744
|
+
// disableTelemetry, else default to prod-on (off outside production).
|
|
745
|
+
disabled: this.offline || (opts.disableTelemetry ?? !prod)
|
|
717
746
|
});
|
|
718
|
-
if (opts.initialBlob || this.
|
|
747
|
+
if (opts.initialBlob || this.offline) {
|
|
719
748
|
this.flagsBlob = opts.initialBlob ?? { version: "test", plan: "free", gates: {}, configs: {}, killswitches: {} };
|
|
720
749
|
this.expsBlob = this.expsBlob ?? { version: "test", universes: {}, experiments: {} };
|
|
721
750
|
this.initialized = true;
|
|
@@ -766,7 +795,7 @@ var Engine = class _Engine {
|
|
|
766
795
|
return _Engine.fromSnapshot(snapshot);
|
|
767
796
|
}
|
|
768
797
|
async init() {
|
|
769
|
-
if (this.
|
|
798
|
+
if (this.offline) {
|
|
770
799
|
this.initialized = true;
|
|
771
800
|
return;
|
|
772
801
|
}
|
|
@@ -775,7 +804,7 @@ var Engine = class _Engine {
|
|
|
775
804
|
this.startPoll();
|
|
776
805
|
}
|
|
777
806
|
async initOnce() {
|
|
778
|
-
if (this.
|
|
807
|
+
if (this.offline || this.initialized) return;
|
|
779
808
|
await this.fetchAll();
|
|
780
809
|
this.initialized = true;
|
|
781
810
|
}
|
|
@@ -999,7 +1028,7 @@ var Engine = class _Engine {
|
|
|
999
1028
|
return out;
|
|
1000
1029
|
}
|
|
1001
1030
|
track(userId, eventName, props) {
|
|
1002
|
-
if (this.
|
|
1031
|
+
if (this.offline) return;
|
|
1003
1032
|
const safeProps = this.stripPrivate(props);
|
|
1004
1033
|
const body = JSON.stringify({
|
|
1005
1034
|
events: [
|
|
@@ -1021,11 +1050,11 @@ var Engine = class _Engine {
|
|
|
1021
1050
|
/**
|
|
1022
1051
|
* POST a single exposure for an enrolled `(user, experiment, group)`. Deduped
|
|
1023
1052
|
* per process (bounded set) so repeated `assign()` calls in one server don't
|
|
1024
|
-
* spam `/collect`. Fire-and-forget; no-op
|
|
1025
|
-
* `assignUniverse` auto-logs — the browser's auto-exposure parity for SSR.
|
|
1053
|
+
* spam `/collect`. Fire-and-forget; no-op when the network is disabled. This
|
|
1054
|
+
* is how `assignUniverse` auto-logs — the browser's auto-exposure parity for SSR.
|
|
1026
1055
|
*/
|
|
1027
1056
|
postExposure(user, experiment, group) {
|
|
1028
|
-
if (this.
|
|
1057
|
+
if (this.offline) return;
|
|
1029
1058
|
const uid = user.user_id ?? user.anonymous_id;
|
|
1030
1059
|
const dedupKey = `${uid ?? ""}:${experiment}:${group}`;
|
|
1031
1060
|
if (this.exposureSeen.has(dedupKey)) return;
|
|
@@ -1055,6 +1084,7 @@ var Engine = class _Engine {
|
|
|
1055
1084
|
* dedup window + per-process cap.
|
|
1056
1085
|
*/
|
|
1057
1086
|
reportError(problem, consequence, extras, kind) {
|
|
1087
|
+
if (this.offline) return;
|
|
1058
1088
|
try {
|
|
1059
1089
|
const correlationId = seeContext.getStore()?.correlationId;
|
|
1060
1090
|
const ev = buildSeeEvent(problem, consequence, extras, {
|
|
@@ -1271,6 +1301,7 @@ async function shipeasy(opts) {
|
|
|
1271
1301
|
const profile = opts.i18nDefaultProfile ?? "en:prod";
|
|
1272
1302
|
flags.configure({
|
|
1273
1303
|
apiKey: serverKey,
|
|
1304
|
+
isNetworkEnabled: opts.isNetworkEnabled,
|
|
1274
1305
|
disableTelemetry: opts.disableTelemetry,
|
|
1275
1306
|
disableInternalErrorReporting: opts.disableInternalErrorReporting,
|
|
1276
1307
|
privateAttributes: opts.privateAttributes,
|
package/docs/skill/SKILL.md
CHANGED
|
@@ -42,6 +42,12 @@ flags.getFlagDetail("new_checkout"); // { value, reason }
|
|
|
42
42
|
`configure()` is first-config-wins and owns the fetch lifecycle (one-shot by
|
|
43
43
|
default; `poll: true` for a background refresh). Construct `new Client(user)` once
|
|
44
44
|
per user/request — it binds the user, so no method takes a user argument.
|
|
45
|
+
|
|
46
|
+
Egress is **environment-derived**: network + usage telemetry default ON in
|
|
47
|
+
production and OFF otherwise (prod is read from `SHIPEASY_ENV`/`NODE_ENV`, else
|
|
48
|
+
the `env` option, default `"prod"`), so the SDK never phones home from dev/CI.
|
|
49
|
+
Override with `isNetworkEnabled: false` (fully offline) or `disableTelemetry: true`
|
|
50
|
+
(keep fetching, no usage beacons).
|
|
45
51
|
Full reference: <https://shipeasy-ai.github.io/sdk-ts/pages/configuration.md> ·
|
|
46
52
|
<https://shipeasy-ai.github.io/sdk-ts/pages/flags.md> ·
|
|
47
53
|
snippets <https://shipeasy-ai.github.io/sdk-ts/snippets/release/flags.md> ·
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipeasy/sdk",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.1",
|
|
4
4
|
"description": "Shipeasy SDK — feature gates, runtime configs, experiments, and metrics for the Shipeasy hosted service.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"homepage": "https://shipeasy.ai",
|