@shipeasy/sdk 7.0.0 → 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/dist/client/index.d.mts +38 -5
- package/dist/client/index.d.ts +38 -5
- package/dist/client/index.js +38 -7
- package/dist/client/index.mjs +38 -7
- 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;
|
|
@@ -1072,6 +1090,12 @@ var Engine = class _Engine {
|
|
|
1072
1090
|
// never fetches, track() is a no-op, telemetry is off, and the client is
|
|
1073
1091
|
// already ready with an empty eval result.
|
|
1074
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;
|
|
1075
1099
|
// Programmatic overrides (Statsig-style). Set on any client via
|
|
1076
1100
|
// overrideFlag/overrideConfig/overrideExperiment; they win over BOTH the URL
|
|
1077
1101
|
// overrides and the fetched eval result. Cleared by clearOverrides().
|
|
@@ -1088,10 +1112,13 @@ var Engine = class _Engine {
|
|
|
1088
1112
|
this.baseUrl = (opts.baseUrl ?? "https://api.shipeasy.ai").replace(/\/$/, "");
|
|
1089
1113
|
this.env = opts.env ?? "prod";
|
|
1090
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;
|
|
1091
1118
|
setInternalReportContext({
|
|
1092
1119
|
side: "client",
|
|
1093
1120
|
sdkVersion: version,
|
|
1094
|
-
enabled:
|
|
1121
|
+
enabled: this.networkEnabled && opts.disableInternalErrorReporting !== true
|
|
1095
1122
|
});
|
|
1096
1123
|
this.autoGuardrails = opts.autoGuardrails !== false;
|
|
1097
1124
|
this.autoCollectAlways = opts.autoCollectAlways === true;
|
|
@@ -1105,7 +1132,7 @@ var Engine = class _Engine {
|
|
|
1105
1132
|
engagement: g.engagement ?? this.autoGuardrails
|
|
1106
1133
|
};
|
|
1107
1134
|
this.anonId = getOrCreateAnonId();
|
|
1108
|
-
if (opts.anonymousStore && !this.
|
|
1135
|
+
if (opts.anonymousStore && !this.offline) {
|
|
1109
1136
|
this.anonReady = this.hydrateAnonFromStore(opts.anonymousStore);
|
|
1110
1137
|
}
|
|
1111
1138
|
this.buffer = new EventBuffer(`${this.baseUrl}/collect`, this.sdkKey);
|
|
@@ -1114,10 +1141,11 @@ var Engine = class _Engine {
|
|
|
1114
1141
|
sdkKey: this.sdkKey,
|
|
1115
1142
|
side: "client",
|
|
1116
1143
|
env: this.env,
|
|
1117
|
-
//
|
|
1118
|
-
|
|
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)
|
|
1119
1147
|
});
|
|
1120
|
-
if (this.
|
|
1148
|
+
if (this.offline) {
|
|
1121
1149
|
this.evalResult = { flags: {}, configs: {}, experiments: {}, killswitches: {} };
|
|
1122
1150
|
} else {
|
|
1123
1151
|
void this.buffer.flushPendingAlias();
|
|
@@ -1162,7 +1190,7 @@ var Engine = class _Engine {
|
|
|
1162
1190
|
});
|
|
1163
1191
|
}
|
|
1164
1192
|
async identify(user) {
|
|
1165
|
-
if (this.
|
|
1193
|
+
if (this.offline) {
|
|
1166
1194
|
if (user.user_id !== void 0) this.userId = user.user_id;
|
|
1167
1195
|
this.notify();
|
|
1168
1196
|
return;
|
|
@@ -1224,6 +1252,7 @@ var Engine = class _Engine {
|
|
|
1224
1252
|
* the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
|
|
1225
1253
|
*/
|
|
1226
1254
|
reportError(problem, consequence, extras, kind, correlationId) {
|
|
1255
|
+
if (this.offline) return;
|
|
1227
1256
|
try {
|
|
1228
1257
|
const enriched = { ...collectSeeEnv(), ...extras };
|
|
1229
1258
|
const ev = buildSeeEvent(problem, consequence, enriched, {
|
|
@@ -1396,7 +1425,7 @@ var Engine = class _Engine {
|
|
|
1396
1425
|
return bridge;
|
|
1397
1426
|
}
|
|
1398
1427
|
track(eventName, props) {
|
|
1399
|
-
if (this.
|
|
1428
|
+
if (this.offline) return;
|
|
1400
1429
|
this.buffer.pushMetric(eventName, this.userId, this.anonId, this.stripPrivate(props));
|
|
1401
1430
|
}
|
|
1402
1431
|
/** Drop caller-marked private attributes from an outbound props bag. */
|
|
@@ -1549,10 +1578,12 @@ function shipeasy(opts) {
|
|
|
1549
1578
|
const client = configureShipeasy({
|
|
1550
1579
|
sdkKey: opts.clientKey,
|
|
1551
1580
|
baseUrl,
|
|
1581
|
+
env: opts.env,
|
|
1552
1582
|
logLevel: opts.logLevel,
|
|
1553
1583
|
autoGuardrails: blanket,
|
|
1554
1584
|
autoGuardrailGroups: groups,
|
|
1555
1585
|
autoCollectAlways: acObj?.always === true,
|
|
1586
|
+
isNetworkEnabled: opts.isNetworkEnabled,
|
|
1556
1587
|
disableTelemetry: opts.disableTelemetry,
|
|
1557
1588
|
disableInternalErrorReporting: opts.disableInternalErrorReporting,
|
|
1558
1589
|
disableAutoExposure: opts.disableAutoExposure,
|
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;
|
|
@@ -1019,6 +1037,12 @@ var Engine = class _Engine {
|
|
|
1019
1037
|
// never fetches, track() is a no-op, telemetry is off, and the client is
|
|
1020
1038
|
// already ready with an empty eval result.
|
|
1021
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;
|
|
1022
1046
|
// Programmatic overrides (Statsig-style). Set on any client via
|
|
1023
1047
|
// overrideFlag/overrideConfig/overrideExperiment; they win over BOTH the URL
|
|
1024
1048
|
// overrides and the fetched eval result. Cleared by clearOverrides().
|
|
@@ -1035,10 +1059,13 @@ var Engine = class _Engine {
|
|
|
1035
1059
|
this.baseUrl = (opts.baseUrl ?? "https://api.shipeasy.ai").replace(/\/$/, "");
|
|
1036
1060
|
this.env = opts.env ?? "prod";
|
|
1037
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;
|
|
1038
1065
|
setInternalReportContext({
|
|
1039
1066
|
side: "client",
|
|
1040
1067
|
sdkVersion: version,
|
|
1041
|
-
enabled:
|
|
1068
|
+
enabled: this.networkEnabled && opts.disableInternalErrorReporting !== true
|
|
1042
1069
|
});
|
|
1043
1070
|
this.autoGuardrails = opts.autoGuardrails !== false;
|
|
1044
1071
|
this.autoCollectAlways = opts.autoCollectAlways === true;
|
|
@@ -1052,7 +1079,7 @@ var Engine = class _Engine {
|
|
|
1052
1079
|
engagement: g.engagement ?? this.autoGuardrails
|
|
1053
1080
|
};
|
|
1054
1081
|
this.anonId = getOrCreateAnonId();
|
|
1055
|
-
if (opts.anonymousStore && !this.
|
|
1082
|
+
if (opts.anonymousStore && !this.offline) {
|
|
1056
1083
|
this.anonReady = this.hydrateAnonFromStore(opts.anonymousStore);
|
|
1057
1084
|
}
|
|
1058
1085
|
this.buffer = new EventBuffer(`${this.baseUrl}/collect`, this.sdkKey);
|
|
@@ -1061,10 +1088,11 @@ var Engine = class _Engine {
|
|
|
1061
1088
|
sdkKey: this.sdkKey,
|
|
1062
1089
|
side: "client",
|
|
1063
1090
|
env: this.env,
|
|
1064
|
-
//
|
|
1065
|
-
|
|
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)
|
|
1066
1094
|
});
|
|
1067
|
-
if (this.
|
|
1095
|
+
if (this.offline) {
|
|
1068
1096
|
this.evalResult = { flags: {}, configs: {}, experiments: {}, killswitches: {} };
|
|
1069
1097
|
} else {
|
|
1070
1098
|
void this.buffer.flushPendingAlias();
|
|
@@ -1109,7 +1137,7 @@ var Engine = class _Engine {
|
|
|
1109
1137
|
});
|
|
1110
1138
|
}
|
|
1111
1139
|
async identify(user) {
|
|
1112
|
-
if (this.
|
|
1140
|
+
if (this.offline) {
|
|
1113
1141
|
if (user.user_id !== void 0) this.userId = user.user_id;
|
|
1114
1142
|
this.notify();
|
|
1115
1143
|
return;
|
|
@@ -1171,6 +1199,7 @@ var Engine = class _Engine {
|
|
|
1171
1199
|
* the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
|
|
1172
1200
|
*/
|
|
1173
1201
|
reportError(problem, consequence, extras, kind, correlationId) {
|
|
1202
|
+
if (this.offline) return;
|
|
1174
1203
|
try {
|
|
1175
1204
|
const enriched = { ...collectSeeEnv(), ...extras };
|
|
1176
1205
|
const ev = buildSeeEvent(problem, consequence, enriched, {
|
|
@@ -1343,7 +1372,7 @@ var Engine = class _Engine {
|
|
|
1343
1372
|
return bridge;
|
|
1344
1373
|
}
|
|
1345
1374
|
track(eventName, props) {
|
|
1346
|
-
if (this.
|
|
1375
|
+
if (this.offline) return;
|
|
1347
1376
|
this.buffer.pushMetric(eventName, this.userId, this.anonId, this.stripPrivate(props));
|
|
1348
1377
|
}
|
|
1349
1378
|
/** Drop caller-marked private attributes from an outbound props bag. */
|
|
@@ -1496,10 +1525,12 @@ function shipeasy(opts) {
|
|
|
1496
1525
|
const client = configureShipeasy({
|
|
1497
1526
|
sdkKey: opts.clientKey,
|
|
1498
1527
|
baseUrl,
|
|
1528
|
+
env: opts.env,
|
|
1499
1529
|
logLevel: opts.logLevel,
|
|
1500
1530
|
autoGuardrails: blanket,
|
|
1501
1531
|
autoGuardrailGroups: groups,
|
|
1502
1532
|
autoCollectAlways: acObj?.always === true,
|
|
1533
|
+
isNetworkEnabled: opts.isNetworkEnabled,
|
|
1503
1534
|
disableTelemetry: opts.disableTelemetry,
|
|
1504
1535
|
disableInternalErrorReporting: opts.disableInternalErrorReporting,
|
|
1505
1536
|
disableAutoExposure: opts.disableAutoExposure,
|
|
@@ -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.0",
|
|
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",
|