@shipeasy/sdk 4.2.0 → 4.4.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 +21 -2
- package/dist/client/index.d.ts +21 -2
- package/dist/client/index.js +71 -11
- package/dist/client/index.mjs +69 -11
- package/dist/server/index.d.mts +28 -1
- package/dist/server/index.d.ts +28 -1
- package/dist/server/index.js +48 -5
- package/dist/server/index.mjs +45 -5
- package/package.json +1 -1
package/dist/client/index.d.mts
CHANGED
|
@@ -51,6 +51,16 @@ interface SeeErrorEvent {
|
|
|
51
51
|
env?: string;
|
|
52
52
|
sdk_version: string;
|
|
53
53
|
ts: number;
|
|
54
|
+
/**
|
|
55
|
+
* Per-request correlation token. The client mints one per same-origin fetch
|
|
56
|
+
* and ships it on both the request header (`X-SE-Correlation`) and any 5xx
|
|
57
|
+
* occurrence it reports; the server safety net reports the matching uncaught
|
|
58
|
+
* error under the same token. The backend joins the two issues by it —
|
|
59
|
+
* populating `caused_by` across the network boundary, where the in-process
|
|
60
|
+
* `.cause`-chain stamp (see `findCausedBy`) cannot reach. Join-only metadata,
|
|
61
|
+
* never persisted as an issue field.
|
|
62
|
+
*/
|
|
63
|
+
correlation_id?: string;
|
|
54
64
|
/**
|
|
55
65
|
* The earlier reported problem this occurrence descends from — present when
|
|
56
66
|
* the same error was caught + reported at an inner boundary and then
|
|
@@ -120,6 +130,15 @@ interface AutoCollectGroups {
|
|
|
120
130
|
errors: boolean;
|
|
121
131
|
engagement: boolean;
|
|
122
132
|
}
|
|
133
|
+
/** True when `rawUrl` resolves to the page's own origin (relative URLs included). */
|
|
134
|
+
declare function sameOrigin(rawUrl: string): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Return a new `fetch` args tuple with `X-SE-Correlation` added, preserving any
|
|
137
|
+
* existing headers across all three arg shapes (string / URL / Request). Never
|
|
138
|
+
* mutates the caller's objects. Best-effort: on any failure the original args
|
|
139
|
+
* pass through unchanged (correlation is optional, never breaks the fetch).
|
|
140
|
+
*/
|
|
141
|
+
declare function injectCorrelationHeader(args: Parameters<typeof fetch>, corr: string): Parameters<typeof fetch>;
|
|
123
142
|
type FlagsClientBrowserEnv = "dev" | "staging" | "prod";
|
|
124
143
|
interface FlagsClientBrowserOptions {
|
|
125
144
|
sdkKey: string;
|
|
@@ -176,7 +195,7 @@ declare class FlagsClientBrowser {
|
|
|
176
195
|
* (beacon-first) — error occurrences are near-real-time, never queued behind
|
|
177
196
|
* the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
|
|
178
197
|
*/
|
|
179
|
-
reportError(problem: unknown, consequence: Consequence, extras?: SeeExtras, kind?: SeeKind): void;
|
|
198
|
+
reportError(problem: unknown, consequence: Consequence, extras?: SeeExtras, kind?: SeeKind, correlationId?: string): void;
|
|
180
199
|
get ready(): boolean;
|
|
181
200
|
private notify;
|
|
182
201
|
initFromBootstrap(data: EvalResponse): void;
|
|
@@ -478,4 +497,4 @@ interface I18nFacade {
|
|
|
478
497
|
}
|
|
479
498
|
declare const i18n: I18nFacade;
|
|
480
499
|
|
|
481
|
-
export { type AutoCollectGroups, type BootstrapPayload, type Consequence, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type SeeApi, type SeeChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, type Violation, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, see, shipeasy, version };
|
|
500
|
+
export { type AutoCollectGroups, type BootstrapPayload, type Consequence, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type SeeApi, type SeeChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, type Violation, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, injectCorrelationHeader, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, sameOrigin, see, shipeasy, version };
|
package/dist/client/index.d.ts
CHANGED
|
@@ -51,6 +51,16 @@ interface SeeErrorEvent {
|
|
|
51
51
|
env?: string;
|
|
52
52
|
sdk_version: string;
|
|
53
53
|
ts: number;
|
|
54
|
+
/**
|
|
55
|
+
* Per-request correlation token. The client mints one per same-origin fetch
|
|
56
|
+
* and ships it on both the request header (`X-SE-Correlation`) and any 5xx
|
|
57
|
+
* occurrence it reports; the server safety net reports the matching uncaught
|
|
58
|
+
* error under the same token. The backend joins the two issues by it —
|
|
59
|
+
* populating `caused_by` across the network boundary, where the in-process
|
|
60
|
+
* `.cause`-chain stamp (see `findCausedBy`) cannot reach. Join-only metadata,
|
|
61
|
+
* never persisted as an issue field.
|
|
62
|
+
*/
|
|
63
|
+
correlation_id?: string;
|
|
54
64
|
/**
|
|
55
65
|
* The earlier reported problem this occurrence descends from — present when
|
|
56
66
|
* the same error was caught + reported at an inner boundary and then
|
|
@@ -120,6 +130,15 @@ interface AutoCollectGroups {
|
|
|
120
130
|
errors: boolean;
|
|
121
131
|
engagement: boolean;
|
|
122
132
|
}
|
|
133
|
+
/** True when `rawUrl` resolves to the page's own origin (relative URLs included). */
|
|
134
|
+
declare function sameOrigin(rawUrl: string): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Return a new `fetch` args tuple with `X-SE-Correlation` added, preserving any
|
|
137
|
+
* existing headers across all three arg shapes (string / URL / Request). Never
|
|
138
|
+
* mutates the caller's objects. Best-effort: on any failure the original args
|
|
139
|
+
* pass through unchanged (correlation is optional, never breaks the fetch).
|
|
140
|
+
*/
|
|
141
|
+
declare function injectCorrelationHeader(args: Parameters<typeof fetch>, corr: string): Parameters<typeof fetch>;
|
|
123
142
|
type FlagsClientBrowserEnv = "dev" | "staging" | "prod";
|
|
124
143
|
interface FlagsClientBrowserOptions {
|
|
125
144
|
sdkKey: string;
|
|
@@ -176,7 +195,7 @@ declare class FlagsClientBrowser {
|
|
|
176
195
|
* (beacon-first) — error occurrences are near-real-time, never queued behind
|
|
177
196
|
* the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
|
|
178
197
|
*/
|
|
179
|
-
reportError(problem: unknown, consequence: Consequence, extras?: SeeExtras, kind?: SeeKind): void;
|
|
198
|
+
reportError(problem: unknown, consequence: Consequence, extras?: SeeExtras, kind?: SeeKind, correlationId?: string): void;
|
|
180
199
|
get ready(): boolean;
|
|
181
200
|
private notify;
|
|
182
201
|
initFromBootstrap(data: EvalResponse): void;
|
|
@@ -478,4 +497,4 @@ interface I18nFacade {
|
|
|
478
497
|
}
|
|
479
498
|
declare const i18n: I18nFacade;
|
|
480
499
|
|
|
481
|
-
export { type AutoCollectGroups, type BootstrapPayload, type Consequence, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type SeeApi, type SeeChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, type Violation, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, see, shipeasy, version };
|
|
500
|
+
export { type AutoCollectGroups, type BootstrapPayload, type Consequence, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type SeeApi, type SeeChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, type Violation, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, injectCorrelationHeader, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, sameOrigin, see, shipeasy, version };
|
package/dist/client/index.js
CHANGED
|
@@ -32,12 +32,14 @@ __export(client_exports, {
|
|
|
32
32
|
flags: () => flags,
|
|
33
33
|
getShipeasyClient: () => getShipeasyClient,
|
|
34
34
|
i18n: () => i18n,
|
|
35
|
+
injectCorrelationHeader: () => injectCorrelationHeader,
|
|
35
36
|
isDevtoolsRequested: () => isDevtoolsRequested,
|
|
36
37
|
labelAttrs: () => labelAttrs,
|
|
37
38
|
loadDevtools: () => loadDevtools,
|
|
38
39
|
readConfigOverride: () => readConfigOverride,
|
|
39
40
|
readExpOverride: () => readExpOverride,
|
|
40
41
|
readGateOverride: () => readGateOverride,
|
|
42
|
+
sameOrigin: () => sameOrigin,
|
|
41
43
|
see: () => see,
|
|
42
44
|
shipeasy: () => shipeasy,
|
|
43
45
|
version: () => version
|
|
@@ -109,6 +111,7 @@ var SEE_MAX_STACK = 8e3;
|
|
|
109
111
|
var SEE_MAX_SUBJECT = 200;
|
|
110
112
|
var SEE_MAX_EXTRA_VALUE = 200;
|
|
111
113
|
var SEE_MAX_EXTRA_KEYS = 20;
|
|
114
|
+
var SEE_MAX_CORRELATION = 64;
|
|
112
115
|
var SEE_DEDUP_WINDOW_MS = 3e4;
|
|
113
116
|
var SEE_MAX_PER_SESSION = 25;
|
|
114
117
|
function causesThe(subject) {
|
|
@@ -215,7 +218,7 @@ function captureCallsiteStack() {
|
|
|
215
218
|
const kept = lines.slice(1).filter((l) => !/@shipeasy[\\/]sdk|see[\\/]core|captureCallsiteStack|\bsee\b\s*\(/.test(l));
|
|
216
219
|
return kept.length ? kept.join("\n") : void 0;
|
|
217
220
|
}
|
|
218
|
-
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
221
|
+
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride, correlationId) {
|
|
219
222
|
let errorType;
|
|
220
223
|
let message;
|
|
221
224
|
let stack;
|
|
@@ -248,6 +251,7 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
|
248
251
|
ts: Date.now()
|
|
249
252
|
};
|
|
250
253
|
if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
|
|
254
|
+
if (correlationId) ev.correlation_id = truncate(String(correlationId), SEE_MAX_CORRELATION);
|
|
251
255
|
const causedBy = findCausedBy(problem);
|
|
252
256
|
if (causedBy) ev.caused_by = causedBy;
|
|
253
257
|
const cleanExtras = sanitizeExtras(extras);
|
|
@@ -501,8 +505,33 @@ function endpointTemplate(rawUrl) {
|
|
|
501
505
|
return (rawUrl.split(/[?#]/)[0] ?? "").slice(0, 120);
|
|
502
506
|
}
|
|
503
507
|
const path = u.pathname.split("/").map((seg) => seg && isIdSegment(seg) ? ":id" : seg).join("/");
|
|
504
|
-
const
|
|
505
|
-
return ((
|
|
508
|
+
const sameOrigin2 = typeof location !== "undefined" && u.origin === location.origin;
|
|
509
|
+
return ((sameOrigin2 ? "" : u.host) + path).slice(0, 120);
|
|
510
|
+
}
|
|
511
|
+
function sameOrigin(rawUrl) {
|
|
512
|
+
if (typeof location === "undefined") return false;
|
|
513
|
+
try {
|
|
514
|
+
return new URL(rawUrl, location.href).origin === location.origin;
|
|
515
|
+
} catch {
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function injectCorrelationHeader(args, corr) {
|
|
520
|
+
try {
|
|
521
|
+
const input = args[0];
|
|
522
|
+
if (typeof Request !== "undefined" && input instanceof Request) {
|
|
523
|
+
const headers2 = new Headers(input.headers);
|
|
524
|
+
headers2.set("X-SE-Correlation", corr);
|
|
525
|
+
return [new Request(input, { headers: headers2 }), ...args.slice(1)];
|
|
526
|
+
}
|
|
527
|
+
const init = { ...args[1] ?? {} };
|
|
528
|
+
const headers = new Headers(init.headers ?? void 0);
|
|
529
|
+
headers.set("X-SE-Correlation", corr);
|
|
530
|
+
init.headers = headers;
|
|
531
|
+
return [input, init];
|
|
532
|
+
} catch {
|
|
533
|
+
return args;
|
|
534
|
+
}
|
|
506
535
|
}
|
|
507
536
|
function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignoreUrlPrefixes, always = false) {
|
|
508
537
|
if (typeof window === "undefined" || typeof PerformanceObserver === "undefined") return;
|
|
@@ -579,6 +608,11 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
579
608
|
const url = typeof args[0] === "string" ? args[0] : args[0].toString();
|
|
580
609
|
const ignored = ignoreUrlPrefixes.some((p) => p && url.startsWith(p));
|
|
581
610
|
const bareUrl = url.split("?")[0].slice(0, 200);
|
|
611
|
+
let corr;
|
|
612
|
+
if (!ignored && sameOrigin(url) && typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
613
|
+
corr = crypto.randomUUID();
|
|
614
|
+
args = injectCorrelationHeader(args, corr);
|
|
615
|
+
}
|
|
582
616
|
let res;
|
|
583
617
|
try {
|
|
584
618
|
res = await origFetch.apply(this, args);
|
|
@@ -599,7 +633,8 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
599
633
|
violation("Http5xx").message(`request to ${bareUrl} returned ${res.status}`),
|
|
600
634
|
causesThe(`request to ${endpointTemplate(url)}`).to("fail with a server error"),
|
|
601
635
|
{ status: res.status, url: url.slice(0, 200), duration_ms: Math.round(elapsed) },
|
|
602
|
-
"network"
|
|
636
|
+
"network",
|
|
637
|
+
corr
|
|
603
638
|
);
|
|
604
639
|
}
|
|
605
640
|
return res;
|
|
@@ -694,17 +729,40 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
694
729
|
});
|
|
695
730
|
}
|
|
696
731
|
}
|
|
697
|
-
function
|
|
732
|
+
function readAnonCookie() {
|
|
698
733
|
try {
|
|
699
|
-
const
|
|
700
|
-
|
|
734
|
+
const m = ("; " + document.cookie).match(/; __se_anon_id=([^;]+)/);
|
|
735
|
+
return m ? decodeURIComponent(m[1]) : null;
|
|
701
736
|
} catch {
|
|
737
|
+
return null;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
function writeAnonCookie(id) {
|
|
741
|
+
try {
|
|
742
|
+
const secure = location.protocol === "https:" ? ";secure" : "";
|
|
743
|
+
document.cookie = `${ANON_ID_KEY}=${id};path=/;max-age=31536000;samesite=lax${secure}`;
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
function getOrCreateAnonId() {
|
|
748
|
+
let id = readAnonCookie();
|
|
749
|
+
if (!id && typeof window !== "undefined") {
|
|
750
|
+
id = window.__SE_BOOTSTRAP?.anonId ?? null;
|
|
751
|
+
}
|
|
752
|
+
if (!id) {
|
|
753
|
+
try {
|
|
754
|
+
id = localStorage.getItem(ANON_ID_KEY);
|
|
755
|
+
} catch {
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
if (!id) {
|
|
759
|
+
id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `anon_${Math.random().toString(36).slice(2)}`;
|
|
702
760
|
}
|
|
703
|
-
const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `anon_${Math.random().toString(36).slice(2)}`;
|
|
704
761
|
try {
|
|
705
762
|
localStorage.setItem(ANON_ID_KEY, id);
|
|
706
763
|
} catch {
|
|
707
764
|
}
|
|
765
|
+
writeAnonCookie(id);
|
|
708
766
|
return id;
|
|
709
767
|
}
|
|
710
768
|
function collectBrowserAttrs() {
|
|
@@ -911,7 +969,7 @@ var FlagsClientBrowser = class {
|
|
|
911
969
|
this.userId,
|
|
912
970
|
this.anonId,
|
|
913
971
|
this.autoGuardrailGroups,
|
|
914
|
-
(problem, consequence, extras, kind) => this.reportError(problem, consequence, extras, kind),
|
|
972
|
+
(problem, consequence, extras, kind, correlationId) => this.reportError(problem, consequence, extras, kind, correlationId),
|
|
915
973
|
[`${this.baseUrl}/`, DEFAULT_TELEMETRY_URL],
|
|
916
974
|
this.autoCollectAlways
|
|
917
975
|
);
|
|
@@ -923,7 +981,7 @@ var FlagsClientBrowser = class {
|
|
|
923
981
|
* (beacon-first) — error occurrences are near-real-time, never queued behind
|
|
924
982
|
* the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
|
|
925
983
|
*/
|
|
926
|
-
reportError(problem, consequence, extras, kind) {
|
|
984
|
+
reportError(problem, consequence, extras, kind, correlationId) {
|
|
927
985
|
try {
|
|
928
986
|
const enriched = { ...collectSeeEnv(), ...extras };
|
|
929
987
|
const ev = buildSeeEvent(problem, consequence, enriched, {
|
|
@@ -933,7 +991,7 @@ var FlagsClientBrowser = class {
|
|
|
933
991
|
url: typeof window !== "undefined" && window.location ? window.location.href : void 0,
|
|
934
992
|
userId: this.userId || void 0,
|
|
935
993
|
anonId: this.anonId
|
|
936
|
-
}, kind);
|
|
994
|
+
}, kind, correlationId);
|
|
937
995
|
if (!this.seeLimiter.shouldSend(ev)) return;
|
|
938
996
|
this.buffer.sendNow([ev]);
|
|
939
997
|
} catch {
|
|
@@ -1600,12 +1658,14 @@ var i18n = {
|
|
|
1600
1658
|
flags,
|
|
1601
1659
|
getShipeasyClient,
|
|
1602
1660
|
i18n,
|
|
1661
|
+
injectCorrelationHeader,
|
|
1603
1662
|
isDevtoolsRequested,
|
|
1604
1663
|
labelAttrs,
|
|
1605
1664
|
loadDevtools,
|
|
1606
1665
|
readConfigOverride,
|
|
1607
1666
|
readExpOverride,
|
|
1608
1667
|
readGateOverride,
|
|
1668
|
+
sameOrigin,
|
|
1609
1669
|
see,
|
|
1610
1670
|
shipeasy,
|
|
1611
1671
|
version
|
package/dist/client/index.mjs
CHANGED
|
@@ -63,6 +63,7 @@ var SEE_MAX_STACK = 8e3;
|
|
|
63
63
|
var SEE_MAX_SUBJECT = 200;
|
|
64
64
|
var SEE_MAX_EXTRA_VALUE = 200;
|
|
65
65
|
var SEE_MAX_EXTRA_KEYS = 20;
|
|
66
|
+
var SEE_MAX_CORRELATION = 64;
|
|
66
67
|
var SEE_DEDUP_WINDOW_MS = 3e4;
|
|
67
68
|
var SEE_MAX_PER_SESSION = 25;
|
|
68
69
|
function causesThe(subject) {
|
|
@@ -169,7 +170,7 @@ function captureCallsiteStack() {
|
|
|
169
170
|
const kept = lines.slice(1).filter((l) => !/@shipeasy[\\/]sdk|see[\\/]core|captureCallsiteStack|\bsee\b\s*\(/.test(l));
|
|
170
171
|
return kept.length ? kept.join("\n") : void 0;
|
|
171
172
|
}
|
|
172
|
-
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
173
|
+
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride, correlationId) {
|
|
173
174
|
let errorType;
|
|
174
175
|
let message;
|
|
175
176
|
let stack;
|
|
@@ -202,6 +203,7 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
|
202
203
|
ts: Date.now()
|
|
203
204
|
};
|
|
204
205
|
if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
|
|
206
|
+
if (correlationId) ev.correlation_id = truncate(String(correlationId), SEE_MAX_CORRELATION);
|
|
205
207
|
const causedBy = findCausedBy(problem);
|
|
206
208
|
if (causedBy) ev.caused_by = causedBy;
|
|
207
209
|
const cleanExtras = sanitizeExtras(extras);
|
|
@@ -455,8 +457,33 @@ function endpointTemplate(rawUrl) {
|
|
|
455
457
|
return (rawUrl.split(/[?#]/)[0] ?? "").slice(0, 120);
|
|
456
458
|
}
|
|
457
459
|
const path = u.pathname.split("/").map((seg) => seg && isIdSegment(seg) ? ":id" : seg).join("/");
|
|
458
|
-
const
|
|
459
|
-
return ((
|
|
460
|
+
const sameOrigin2 = typeof location !== "undefined" && u.origin === location.origin;
|
|
461
|
+
return ((sameOrigin2 ? "" : u.host) + path).slice(0, 120);
|
|
462
|
+
}
|
|
463
|
+
function sameOrigin(rawUrl) {
|
|
464
|
+
if (typeof location === "undefined") return false;
|
|
465
|
+
try {
|
|
466
|
+
return new URL(rawUrl, location.href).origin === location.origin;
|
|
467
|
+
} catch {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function injectCorrelationHeader(args, corr) {
|
|
472
|
+
try {
|
|
473
|
+
const input = args[0];
|
|
474
|
+
if (typeof Request !== "undefined" && input instanceof Request) {
|
|
475
|
+
const headers2 = new Headers(input.headers);
|
|
476
|
+
headers2.set("X-SE-Correlation", corr);
|
|
477
|
+
return [new Request(input, { headers: headers2 }), ...args.slice(1)];
|
|
478
|
+
}
|
|
479
|
+
const init = { ...args[1] ?? {} };
|
|
480
|
+
const headers = new Headers(init.headers ?? void 0);
|
|
481
|
+
headers.set("X-SE-Correlation", corr);
|
|
482
|
+
init.headers = headers;
|
|
483
|
+
return [input, init];
|
|
484
|
+
} catch {
|
|
485
|
+
return args;
|
|
486
|
+
}
|
|
460
487
|
}
|
|
461
488
|
function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignoreUrlPrefixes, always = false) {
|
|
462
489
|
if (typeof window === "undefined" || typeof PerformanceObserver === "undefined") return;
|
|
@@ -533,6 +560,11 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
533
560
|
const url = typeof args[0] === "string" ? args[0] : args[0].toString();
|
|
534
561
|
const ignored = ignoreUrlPrefixes.some((p) => p && url.startsWith(p));
|
|
535
562
|
const bareUrl = url.split("?")[0].slice(0, 200);
|
|
563
|
+
let corr;
|
|
564
|
+
if (!ignored && sameOrigin(url) && typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
565
|
+
corr = crypto.randomUUID();
|
|
566
|
+
args = injectCorrelationHeader(args, corr);
|
|
567
|
+
}
|
|
536
568
|
let res;
|
|
537
569
|
try {
|
|
538
570
|
res = await origFetch.apply(this, args);
|
|
@@ -553,7 +585,8 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
553
585
|
violation("Http5xx").message(`request to ${bareUrl} returned ${res.status}`),
|
|
554
586
|
causesThe(`request to ${endpointTemplate(url)}`).to("fail with a server error"),
|
|
555
587
|
{ status: res.status, url: url.slice(0, 200), duration_ms: Math.round(elapsed) },
|
|
556
|
-
"network"
|
|
588
|
+
"network",
|
|
589
|
+
corr
|
|
557
590
|
);
|
|
558
591
|
}
|
|
559
592
|
return res;
|
|
@@ -648,17 +681,40 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
648
681
|
});
|
|
649
682
|
}
|
|
650
683
|
}
|
|
651
|
-
function
|
|
684
|
+
function readAnonCookie() {
|
|
652
685
|
try {
|
|
653
|
-
const
|
|
654
|
-
|
|
686
|
+
const m = ("; " + document.cookie).match(/; __se_anon_id=([^;]+)/);
|
|
687
|
+
return m ? decodeURIComponent(m[1]) : null;
|
|
655
688
|
} catch {
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function writeAnonCookie(id) {
|
|
693
|
+
try {
|
|
694
|
+
const secure = location.protocol === "https:" ? ";secure" : "";
|
|
695
|
+
document.cookie = `${ANON_ID_KEY}=${id};path=/;max-age=31536000;samesite=lax${secure}`;
|
|
696
|
+
} catch {
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
function getOrCreateAnonId() {
|
|
700
|
+
let id = readAnonCookie();
|
|
701
|
+
if (!id && typeof window !== "undefined") {
|
|
702
|
+
id = window.__SE_BOOTSTRAP?.anonId ?? null;
|
|
703
|
+
}
|
|
704
|
+
if (!id) {
|
|
705
|
+
try {
|
|
706
|
+
id = localStorage.getItem(ANON_ID_KEY);
|
|
707
|
+
} catch {
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (!id) {
|
|
711
|
+
id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `anon_${Math.random().toString(36).slice(2)}`;
|
|
656
712
|
}
|
|
657
|
-
const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `anon_${Math.random().toString(36).slice(2)}`;
|
|
658
713
|
try {
|
|
659
714
|
localStorage.setItem(ANON_ID_KEY, id);
|
|
660
715
|
} catch {
|
|
661
716
|
}
|
|
717
|
+
writeAnonCookie(id);
|
|
662
718
|
return id;
|
|
663
719
|
}
|
|
664
720
|
function collectBrowserAttrs() {
|
|
@@ -865,7 +921,7 @@ var FlagsClientBrowser = class {
|
|
|
865
921
|
this.userId,
|
|
866
922
|
this.anonId,
|
|
867
923
|
this.autoGuardrailGroups,
|
|
868
|
-
(problem, consequence, extras, kind) => this.reportError(problem, consequence, extras, kind),
|
|
924
|
+
(problem, consequence, extras, kind, correlationId) => this.reportError(problem, consequence, extras, kind, correlationId),
|
|
869
925
|
[`${this.baseUrl}/`, DEFAULT_TELEMETRY_URL],
|
|
870
926
|
this.autoCollectAlways
|
|
871
927
|
);
|
|
@@ -877,7 +933,7 @@ var FlagsClientBrowser = class {
|
|
|
877
933
|
* (beacon-first) — error occurrences are near-real-time, never queued behind
|
|
878
934
|
* the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
|
|
879
935
|
*/
|
|
880
|
-
reportError(problem, consequence, extras, kind) {
|
|
936
|
+
reportError(problem, consequence, extras, kind, correlationId) {
|
|
881
937
|
try {
|
|
882
938
|
const enriched = { ...collectSeeEnv(), ...extras };
|
|
883
939
|
const ev = buildSeeEvent(problem, consequence, enriched, {
|
|
@@ -887,7 +943,7 @@ var FlagsClientBrowser = class {
|
|
|
887
943
|
url: typeof window !== "undefined" && window.location ? window.location.href : void 0,
|
|
888
944
|
userId: this.userId || void 0,
|
|
889
945
|
anonId: this.anonId
|
|
890
|
-
}, kind);
|
|
946
|
+
}, kind, correlationId);
|
|
891
947
|
if (!this.seeLimiter.shouldSend(ev)) return;
|
|
892
948
|
this.buffer.sendNow([ev]);
|
|
893
949
|
} catch {
|
|
@@ -1553,12 +1609,14 @@ export {
|
|
|
1553
1609
|
flags,
|
|
1554
1610
|
getShipeasyClient,
|
|
1555
1611
|
i18n,
|
|
1612
|
+
injectCorrelationHeader,
|
|
1556
1613
|
isDevtoolsRequested,
|
|
1557
1614
|
labelAttrs,
|
|
1558
1615
|
loadDevtools,
|
|
1559
1616
|
readConfigOverride,
|
|
1560
1617
|
readExpOverride,
|
|
1561
1618
|
readGateOverride,
|
|
1619
|
+
sameOrigin,
|
|
1562
1620
|
see,
|
|
1563
1621
|
shipeasy,
|
|
1564
1622
|
version
|
package/dist/server/index.d.mts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
|
|
1
3
|
type SeeExtras = Record<string, string | number | boolean | null | undefined>;
|
|
2
4
|
type SeeKind = "caught" | "uncaught" | "unhandled_rejection" | "network" | "violation";
|
|
3
5
|
/** Built by `causesThe(subject).to(outcome)` — never constructed by hand. */
|
|
@@ -51,6 +53,16 @@ interface SeeErrorEvent {
|
|
|
51
53
|
env?: string;
|
|
52
54
|
sdk_version: string;
|
|
53
55
|
ts: number;
|
|
56
|
+
/**
|
|
57
|
+
* Per-request correlation token. The client mints one per same-origin fetch
|
|
58
|
+
* and ships it on both the request header (`X-SE-Correlation`) and any 5xx
|
|
59
|
+
* occurrence it reports; the server safety net reports the matching uncaught
|
|
60
|
+
* error under the same token. The backend joins the two issues by it —
|
|
61
|
+
* populating `caused_by` across the network boundary, where the in-process
|
|
62
|
+
* `.cause`-chain stamp (see `findCausedBy`) cannot reach. Join-only metadata,
|
|
63
|
+
* never persisted as an issue field.
|
|
64
|
+
*/
|
|
65
|
+
correlation_id?: string;
|
|
54
66
|
/**
|
|
55
67
|
* The earlier reported problem this occurrence descends from — present when
|
|
56
68
|
* the same error was caught + reported at an inner boundary and then
|
|
@@ -60,6 +72,7 @@ interface SeeErrorEvent {
|
|
|
60
72
|
*/
|
|
61
73
|
caused_by?: SeeCausedBy;
|
|
62
74
|
}
|
|
75
|
+
declare function isExpected(err: unknown): boolean;
|
|
63
76
|
interface SeeExtrasTail {
|
|
64
77
|
/** Attach debugging metadata. Callable repeatedly — keys merge, later wins. */
|
|
65
78
|
extras(extras: SeeExtras): SeeExtrasTail;
|
|
@@ -121,6 +134,7 @@ interface BootstrapPayload {
|
|
|
121
134
|
experiments: Record<string, ExperimentResult<Record<string, unknown>>>;
|
|
122
135
|
killswitches: Record<string, boolean | Record<string, boolean>>;
|
|
123
136
|
}
|
|
137
|
+
declare const ANON_ID_COOKIE = "__se_anon_id";
|
|
124
138
|
type FlagsClientEnv = "dev" | "staging" | "prod";
|
|
125
139
|
interface FlagsClientOptions {
|
|
126
140
|
apiKey: string;
|
|
@@ -187,6 +201,11 @@ declare class FlagsClient {
|
|
|
187
201
|
evaluate(user: User, rawUrl?: string): BootstrapPayload;
|
|
188
202
|
getKillswitch(name: string, switchKey?: string): boolean;
|
|
189
203
|
}
|
|
204
|
+
interface SeeCorrelationStore {
|
|
205
|
+
correlationId?: string;
|
|
206
|
+
}
|
|
207
|
+
declare const seeContext: AsyncLocalStorage<SeeCorrelationStore>;
|
|
208
|
+
|
|
190
209
|
interface I18nForRequest {
|
|
191
210
|
strings: Record<string, string>;
|
|
192
211
|
locale: string;
|
|
@@ -273,6 +292,14 @@ interface BootstrapHtmlOptions {
|
|
|
273
292
|
i18nProfile?: string;
|
|
274
293
|
/** When true, tEl() embeds label markers so the devtools can highlight them. */
|
|
275
294
|
editLabels?: boolean;
|
|
295
|
+
/**
|
|
296
|
+
* Stable anonymous bucketing id the server evaluated against. Emitted into
|
|
297
|
+
* window.__SE_BOOTSTRAP and persisted (pre-paint) to the first-party
|
|
298
|
+
* `__se_anon_id` cookie, so the browser SDK buckets identically to SSR.
|
|
299
|
+
* Normally minted by edge middleware; this write is the fallback for routes
|
|
300
|
+
* middleware doesn't cover. See experiment-platform/18-identity-bucketing.md.
|
|
301
|
+
*/
|
|
302
|
+
anonId?: string;
|
|
276
303
|
}
|
|
277
304
|
/**
|
|
278
305
|
* Returns a vanilla-JS string for a single inline <script> tag. Handles
|
|
@@ -362,4 +389,4 @@ interface SeeApi {
|
|
|
362
389
|
*/
|
|
363
390
|
declare const see: SeeApi;
|
|
364
391
|
|
|
365
|
-
export { type BootstrapHtmlOptions, type BootstrapPayload, type Consequence, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type I18nForRequest, type LabelFile, type SeeApi, type SeeChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyServerConfig, type ShipeasyServerHandle, type User, type Violation, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getBootstrapHtml, getShipeasyServerClient, i18n, see, shipeasy, version };
|
|
392
|
+
export { ANON_ID_COOKIE, type BootstrapHtmlOptions, type BootstrapPayload, type Consequence, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type I18nForRequest, type LabelFile, type SeeApi, type SeeChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyServerConfig, type ShipeasyServerHandle, type User, type Violation, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getBootstrapHtml, getShipeasyServerClient, i18n, isExpected, see, seeContext, shipeasy, version };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
|
|
1
3
|
type SeeExtras = Record<string, string | number | boolean | null | undefined>;
|
|
2
4
|
type SeeKind = "caught" | "uncaught" | "unhandled_rejection" | "network" | "violation";
|
|
3
5
|
/** Built by `causesThe(subject).to(outcome)` — never constructed by hand. */
|
|
@@ -51,6 +53,16 @@ interface SeeErrorEvent {
|
|
|
51
53
|
env?: string;
|
|
52
54
|
sdk_version: string;
|
|
53
55
|
ts: number;
|
|
56
|
+
/**
|
|
57
|
+
* Per-request correlation token. The client mints one per same-origin fetch
|
|
58
|
+
* and ships it on both the request header (`X-SE-Correlation`) and any 5xx
|
|
59
|
+
* occurrence it reports; the server safety net reports the matching uncaught
|
|
60
|
+
* error under the same token. The backend joins the two issues by it —
|
|
61
|
+
* populating `caused_by` across the network boundary, where the in-process
|
|
62
|
+
* `.cause`-chain stamp (see `findCausedBy`) cannot reach. Join-only metadata,
|
|
63
|
+
* never persisted as an issue field.
|
|
64
|
+
*/
|
|
65
|
+
correlation_id?: string;
|
|
54
66
|
/**
|
|
55
67
|
* The earlier reported problem this occurrence descends from — present when
|
|
56
68
|
* the same error was caught + reported at an inner boundary and then
|
|
@@ -60,6 +72,7 @@ interface SeeErrorEvent {
|
|
|
60
72
|
*/
|
|
61
73
|
caused_by?: SeeCausedBy;
|
|
62
74
|
}
|
|
75
|
+
declare function isExpected(err: unknown): boolean;
|
|
63
76
|
interface SeeExtrasTail {
|
|
64
77
|
/** Attach debugging metadata. Callable repeatedly — keys merge, later wins. */
|
|
65
78
|
extras(extras: SeeExtras): SeeExtrasTail;
|
|
@@ -121,6 +134,7 @@ interface BootstrapPayload {
|
|
|
121
134
|
experiments: Record<string, ExperimentResult<Record<string, unknown>>>;
|
|
122
135
|
killswitches: Record<string, boolean | Record<string, boolean>>;
|
|
123
136
|
}
|
|
137
|
+
declare const ANON_ID_COOKIE = "__se_anon_id";
|
|
124
138
|
type FlagsClientEnv = "dev" | "staging" | "prod";
|
|
125
139
|
interface FlagsClientOptions {
|
|
126
140
|
apiKey: string;
|
|
@@ -187,6 +201,11 @@ declare class FlagsClient {
|
|
|
187
201
|
evaluate(user: User, rawUrl?: string): BootstrapPayload;
|
|
188
202
|
getKillswitch(name: string, switchKey?: string): boolean;
|
|
189
203
|
}
|
|
204
|
+
interface SeeCorrelationStore {
|
|
205
|
+
correlationId?: string;
|
|
206
|
+
}
|
|
207
|
+
declare const seeContext: AsyncLocalStorage<SeeCorrelationStore>;
|
|
208
|
+
|
|
190
209
|
interface I18nForRequest {
|
|
191
210
|
strings: Record<string, string>;
|
|
192
211
|
locale: string;
|
|
@@ -273,6 +292,14 @@ interface BootstrapHtmlOptions {
|
|
|
273
292
|
i18nProfile?: string;
|
|
274
293
|
/** When true, tEl() embeds label markers so the devtools can highlight them. */
|
|
275
294
|
editLabels?: boolean;
|
|
295
|
+
/**
|
|
296
|
+
* Stable anonymous bucketing id the server evaluated against. Emitted into
|
|
297
|
+
* window.__SE_BOOTSTRAP and persisted (pre-paint) to the first-party
|
|
298
|
+
* `__se_anon_id` cookie, so the browser SDK buckets identically to SSR.
|
|
299
|
+
* Normally minted by edge middleware; this write is the fallback for routes
|
|
300
|
+
* middleware doesn't cover. See experiment-platform/18-identity-bucketing.md.
|
|
301
|
+
*/
|
|
302
|
+
anonId?: string;
|
|
276
303
|
}
|
|
277
304
|
/**
|
|
278
305
|
* Returns a vanilla-JS string for a single inline <script> tag. Handles
|
|
@@ -362,4 +389,4 @@ interface SeeApi {
|
|
|
362
389
|
*/
|
|
363
390
|
declare const see: SeeApi;
|
|
364
391
|
|
|
365
|
-
export { type BootstrapHtmlOptions, type BootstrapPayload, type Consequence, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type I18nForRequest, type LabelFile, type SeeApi, type SeeChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyServerConfig, type ShipeasyServerHandle, type User, type Violation, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getBootstrapHtml, getShipeasyServerClient, i18n, see, shipeasy, version };
|
|
392
|
+
export { ANON_ID_COOKIE, type BootstrapHtmlOptions, type BootstrapPayload, type Consequence, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type I18nForRequest, type LabelFile, type SeeApi, type SeeChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyServerConfig, type ShipeasyServerHandle, type User, type Violation, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getBootstrapHtml, getShipeasyServerClient, i18n, isExpected, see, seeContext, shipeasy, version };
|
package/dist/server/index.js
CHANGED
|
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/server/index.ts
|
|
31
31
|
var server_exports = {};
|
|
32
32
|
__export(server_exports, {
|
|
33
|
+
ANON_ID_COOKIE: () => ANON_ID_COOKIE,
|
|
33
34
|
FlagsClient: () => FlagsClient,
|
|
34
35
|
_resetShipeasyServerForTests: () => _resetShipeasyServerForTests,
|
|
35
36
|
configureShipeasyServer: () => configureShipeasyServer,
|
|
@@ -38,7 +39,9 @@ __export(server_exports, {
|
|
|
38
39
|
getBootstrapHtml: () => getBootstrapHtml,
|
|
39
40
|
getShipeasyServerClient: () => getShipeasyServerClient,
|
|
40
41
|
i18n: () => i18n,
|
|
42
|
+
isExpected: () => isExpected,
|
|
41
43
|
see: () => see,
|
|
44
|
+
seeContext: () => seeContext,
|
|
42
45
|
shipeasy: () => shipeasy,
|
|
43
46
|
version: () => version
|
|
44
47
|
});
|
|
@@ -110,6 +113,7 @@ var SEE_MAX_STACK = 8e3;
|
|
|
110
113
|
var SEE_MAX_SUBJECT = 200;
|
|
111
114
|
var SEE_MAX_EXTRA_VALUE = 200;
|
|
112
115
|
var SEE_MAX_EXTRA_KEYS = 20;
|
|
116
|
+
var SEE_MAX_CORRELATION = 64;
|
|
113
117
|
var SEE_DEDUP_WINDOW_MS = 3e4;
|
|
114
118
|
var SEE_MAX_PER_SESSION = 25;
|
|
115
119
|
function causesThe(subject) {
|
|
@@ -149,6 +153,10 @@ function markExpected(err, because) {
|
|
|
149
153
|
} catch {
|
|
150
154
|
}
|
|
151
155
|
}
|
|
156
|
+
function isExpected(err) {
|
|
157
|
+
if (typeof err !== "object" || err === null) return false;
|
|
158
|
+
return err[EXPECTED_SYM] !== void 0;
|
|
159
|
+
}
|
|
152
160
|
var REPORTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-reported");
|
|
153
161
|
var SEE_MAX_CAUSE_DEPTH = 8;
|
|
154
162
|
function readReportStamp(err) {
|
|
@@ -212,7 +220,7 @@ function captureCallsiteStack() {
|
|
|
212
220
|
const kept = lines.slice(1).filter((l) => !/@shipeasy[\\/]sdk|see[\\/]core|captureCallsiteStack|\bsee\b\s*\(/.test(l));
|
|
213
221
|
return kept.length ? kept.join("\n") : void 0;
|
|
214
222
|
}
|
|
215
|
-
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
223
|
+
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride, correlationId) {
|
|
216
224
|
let errorType;
|
|
217
225
|
let message;
|
|
218
226
|
let stack;
|
|
@@ -245,6 +253,7 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
|
245
253
|
ts: Date.now()
|
|
246
254
|
};
|
|
247
255
|
if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
|
|
256
|
+
if (correlationId) ev.correlation_id = truncate(String(correlationId), SEE_MAX_CORRELATION);
|
|
248
257
|
const causedBy = findCausedBy(problem);
|
|
249
258
|
if (causedBy) ev.caused_by = causedBy;
|
|
250
259
|
const cleanExtras = sanitizeExtras(extras);
|
|
@@ -434,6 +443,11 @@ function matchRule(rule, user) {
|
|
|
434
443
|
return false;
|
|
435
444
|
}
|
|
436
445
|
}
|
|
446
|
+
var ANON_ID_COOKIE = "__se_anon_id";
|
|
447
|
+
var ANON_ID_RX = /^[A-Za-z0-9_-]{1,64}$/;
|
|
448
|
+
function mintAnonId() {
|
|
449
|
+
return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `anon_${Math.random().toString(36).slice(2)}`;
|
|
450
|
+
}
|
|
437
451
|
function evalGateInternal(gate, user) {
|
|
438
452
|
if (isEnabled(gate.killswitch)) return false;
|
|
439
453
|
if (!isEnabled(gate.enabled)) return false;
|
|
@@ -441,7 +455,7 @@ function evalGateInternal(gate, user) {
|
|
|
441
455
|
if (!matchRule(rule, user)) return false;
|
|
442
456
|
}
|
|
443
457
|
const uid = user.user_id ?? user.anonymous_id;
|
|
444
|
-
if (!uid) return
|
|
458
|
+
if (!uid) return gate.rolloutPct >= 1e4;
|
|
445
459
|
return murmur3(`${gate.salt}:${uid}`) % 1e4 < gate.rolloutPct;
|
|
446
460
|
}
|
|
447
461
|
var TRUE_RX = /^(true|on|1|yes)$/i;
|
|
@@ -654,11 +668,12 @@ var FlagsClient = class {
|
|
|
654
668
|
*/
|
|
655
669
|
reportError(problem, consequence, extras, kind) {
|
|
656
670
|
try {
|
|
671
|
+
const correlationId = seeContext.getStore()?.correlationId;
|
|
657
672
|
const ev = buildSeeEvent(problem, consequence, extras, {
|
|
658
673
|
side: "server",
|
|
659
674
|
sdkVersion: version,
|
|
660
675
|
env: this.env
|
|
661
|
-
}, kind);
|
|
676
|
+
}, kind, correlationId);
|
|
662
677
|
if (!this.seeLimiter.shouldSend(ev)) return;
|
|
663
678
|
globalThis.fetch(`${this.baseUrl}/collect`, {
|
|
664
679
|
method: "POST",
|
|
@@ -753,6 +768,8 @@ Object.defineProperty(globalThis, _EDIT_MODE_SSR_SYM, {
|
|
|
753
768
|
},
|
|
754
769
|
configurable: true
|
|
755
770
|
});
|
|
771
|
+
var _SEE_CORR_ALS_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-correlation-als");
|
|
772
|
+
var seeContext = globalThis[_SEE_CORR_ALS_SYM] ?? (globalThis[_SEE_CORR_ALS_SYM] = new import_node_async_hooks.AsyncLocalStorage());
|
|
756
773
|
var i18n = {
|
|
757
774
|
/**
|
|
758
775
|
* Fetch translation labels for the current request and store them in an
|
|
@@ -874,7 +891,23 @@ async function shipeasy(opts) {
|
|
|
874
891
|
serverKey ? flags.initOnce() : Promise.resolve(),
|
|
875
892
|
serverKey ? i18n.init(serverKey, profile) : Promise.resolve()
|
|
876
893
|
]);
|
|
877
|
-
|
|
894
|
+
let anonId;
|
|
895
|
+
if (!opts.user?.user_id) {
|
|
896
|
+
if (opts.user?.anonymous_id) {
|
|
897
|
+
anonId = opts.user.anonymous_id;
|
|
898
|
+
} else {
|
|
899
|
+
try {
|
|
900
|
+
const { cookies } = await import("next/headers");
|
|
901
|
+
const c = await Promise.resolve(cookies());
|
|
902
|
+
const raw = c.get?.(ANON_ID_COOKIE)?.value;
|
|
903
|
+
if (raw && ANON_ID_RX.test(raw)) anonId = raw;
|
|
904
|
+
} catch {
|
|
905
|
+
}
|
|
906
|
+
if (!anonId) anonId = mintAnonId();
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
const effectiveUser = anonId ? { anonymous_id: anonId, ...opts.user } : { ...opts.user };
|
|
910
|
+
const bootstrap = flags.evaluate(effectiveUser, resolvedUrlOverrides);
|
|
878
911
|
const i18nData = i18n.getForRequest();
|
|
879
912
|
return {
|
|
880
913
|
flags: bootstrap.flags,
|
|
@@ -883,7 +916,8 @@ async function shipeasy(opts) {
|
|
|
883
916
|
getBootstrapHtml() {
|
|
884
917
|
return getBootstrapHtml(bootstrap, i18nData, {
|
|
885
918
|
editLabels,
|
|
886
|
-
i18nProfile: profile
|
|
919
|
+
i18nProfile: profile,
|
|
920
|
+
anonId
|
|
887
921
|
});
|
|
888
922
|
}
|
|
889
923
|
};
|
|
@@ -903,10 +937,16 @@ function getBootstrapHtml(bootstrap, i18nData, opts) {
|
|
|
903
937
|
};
|
|
904
938
|
if (i18nData) payload.i18n = i18nData;
|
|
905
939
|
if (opts.editLabels) payload.editLabels = true;
|
|
940
|
+
if (opts.anonId) payload.anonId = opts.anonId;
|
|
906
941
|
parts.push(
|
|
907
942
|
`(function(){var Q=new URLSearchParams(location.search).has('se_edit_labels');var C=/(?:^|;\\s*)se_edit_labels=1(?:;|$)/.test(document.cookie);if(!Q&&!C)return;if(Q){try{document.cookie='se_edit_labels=1;path=/;max-age=86400;samesite=lax';}catch(_){}}var R;function P(v){if(!v||typeof v.t!=='function'||v.__sePatched)return;var O=v.t.bind(v);v.__sePatched=true;window._sei18n_t=O;v.t=function(k,vars){var r=O(k,vars);if(r===k)return k;var V='';try{if(vars&&typeof vars==='object'){var hasKey=false;for(var _k in vars){hasKey=true;break;}if(hasKey)V=JSON.stringify(vars);}}catch(_){V='';}return '\\uFFF9'+k+'\\uFFFA'+V+'\\uFFFA'+r+'\\uFFFB';};}Object.defineProperty(window,'i18n',{configurable:true,get:function(){return R;},set:function(v){P(v);R=v;}});})();`
|
|
908
943
|
);
|
|
909
944
|
parts.push(`window.__SE_BOOTSTRAP=${JSON.stringify(payload)};`);
|
|
945
|
+
if (opts.anonId) {
|
|
946
|
+
parts.push(
|
|
947
|
+
`(function(){try{var k=${JSON.stringify(ANON_ID_COOKIE)},v=${JSON.stringify(opts.anonId)};if(('; '+document.cookie).indexOf('; '+k+'=')===-1){document.cookie=k+'='+v+';path=/;max-age=31536000;samesite=lax'+(location.protocol==='https:'?';secure':'');}}catch(_){}})();`
|
|
948
|
+
);
|
|
949
|
+
}
|
|
910
950
|
if (i18nData?.strings && Object.keys(i18nData.strings).length > 0) {
|
|
911
951
|
parts.push(
|
|
912
952
|
`(function(){var d=window.__SE_BOOTSTRAP.i18n;if(!d)return;window.i18n={locale:d.locale,t:function(k,v){var r=d.strings[k];if(!r)return k;return v?r.replace(/\\{\\{(\\w+)\\}\\}/g,function(_,p){return v[p]!==undefined?String(v[p]):'{{'+p+'}}'}):r;},on:function(){return function(){};}};})();`
|
|
@@ -992,6 +1032,7 @@ var see = Object.assign(
|
|
|
992
1032
|
);
|
|
993
1033
|
// Annotate the CommonJS export names for ESM import in node:
|
|
994
1034
|
0 && (module.exports = {
|
|
1035
|
+
ANON_ID_COOKIE,
|
|
995
1036
|
FlagsClient,
|
|
996
1037
|
_resetShipeasyServerForTests,
|
|
997
1038
|
configureShipeasyServer,
|
|
@@ -1000,7 +1041,9 @@ var see = Object.assign(
|
|
|
1000
1041
|
getBootstrapHtml,
|
|
1001
1042
|
getShipeasyServerClient,
|
|
1002
1043
|
i18n,
|
|
1044
|
+
isExpected,
|
|
1003
1045
|
see,
|
|
1046
|
+
seeContext,
|
|
1004
1047
|
shipeasy,
|
|
1005
1048
|
version
|
|
1006
1049
|
});
|
package/dist/server/index.mjs
CHANGED
|
@@ -66,6 +66,7 @@ var SEE_MAX_STACK = 8e3;
|
|
|
66
66
|
var SEE_MAX_SUBJECT = 200;
|
|
67
67
|
var SEE_MAX_EXTRA_VALUE = 200;
|
|
68
68
|
var SEE_MAX_EXTRA_KEYS = 20;
|
|
69
|
+
var SEE_MAX_CORRELATION = 64;
|
|
69
70
|
var SEE_DEDUP_WINDOW_MS = 3e4;
|
|
70
71
|
var SEE_MAX_PER_SESSION = 25;
|
|
71
72
|
function causesThe(subject) {
|
|
@@ -105,6 +106,10 @@ function markExpected(err, because) {
|
|
|
105
106
|
} catch {
|
|
106
107
|
}
|
|
107
108
|
}
|
|
109
|
+
function isExpected(err) {
|
|
110
|
+
if (typeof err !== "object" || err === null) return false;
|
|
111
|
+
return err[EXPECTED_SYM] !== void 0;
|
|
112
|
+
}
|
|
108
113
|
var REPORTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-reported");
|
|
109
114
|
var SEE_MAX_CAUSE_DEPTH = 8;
|
|
110
115
|
function readReportStamp(err) {
|
|
@@ -168,7 +173,7 @@ function captureCallsiteStack() {
|
|
|
168
173
|
const kept = lines.slice(1).filter((l) => !/@shipeasy[\\/]sdk|see[\\/]core|captureCallsiteStack|\bsee\b\s*\(/.test(l));
|
|
169
174
|
return kept.length ? kept.join("\n") : void 0;
|
|
170
175
|
}
|
|
171
|
-
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
176
|
+
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride, correlationId) {
|
|
172
177
|
let errorType;
|
|
173
178
|
let message;
|
|
174
179
|
let stack;
|
|
@@ -201,6 +206,7 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
|
201
206
|
ts: Date.now()
|
|
202
207
|
};
|
|
203
208
|
if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
|
|
209
|
+
if (correlationId) ev.correlation_id = truncate(String(correlationId), SEE_MAX_CORRELATION);
|
|
204
210
|
const causedBy = findCausedBy(problem);
|
|
205
211
|
if (causedBy) ev.caused_by = causedBy;
|
|
206
212
|
const cleanExtras = sanitizeExtras(extras);
|
|
@@ -390,6 +396,11 @@ function matchRule(rule, user) {
|
|
|
390
396
|
return false;
|
|
391
397
|
}
|
|
392
398
|
}
|
|
399
|
+
var ANON_ID_COOKIE = "__se_anon_id";
|
|
400
|
+
var ANON_ID_RX = /^[A-Za-z0-9_-]{1,64}$/;
|
|
401
|
+
function mintAnonId() {
|
|
402
|
+
return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `anon_${Math.random().toString(36).slice(2)}`;
|
|
403
|
+
}
|
|
393
404
|
function evalGateInternal(gate, user) {
|
|
394
405
|
if (isEnabled(gate.killswitch)) return false;
|
|
395
406
|
if (!isEnabled(gate.enabled)) return false;
|
|
@@ -397,7 +408,7 @@ function evalGateInternal(gate, user) {
|
|
|
397
408
|
if (!matchRule(rule, user)) return false;
|
|
398
409
|
}
|
|
399
410
|
const uid = user.user_id ?? user.anonymous_id;
|
|
400
|
-
if (!uid) return
|
|
411
|
+
if (!uid) return gate.rolloutPct >= 1e4;
|
|
401
412
|
return murmur3(`${gate.salt}:${uid}`) % 1e4 < gate.rolloutPct;
|
|
402
413
|
}
|
|
403
414
|
var TRUE_RX = /^(true|on|1|yes)$/i;
|
|
@@ -610,11 +621,12 @@ var FlagsClient = class {
|
|
|
610
621
|
*/
|
|
611
622
|
reportError(problem, consequence, extras, kind) {
|
|
612
623
|
try {
|
|
624
|
+
const correlationId = seeContext.getStore()?.correlationId;
|
|
613
625
|
const ev = buildSeeEvent(problem, consequence, extras, {
|
|
614
626
|
side: "server",
|
|
615
627
|
sdkVersion: version,
|
|
616
628
|
env: this.env
|
|
617
|
-
}, kind);
|
|
629
|
+
}, kind, correlationId);
|
|
618
630
|
if (!this.seeLimiter.shouldSend(ev)) return;
|
|
619
631
|
globalThis.fetch(`${this.baseUrl}/collect`, {
|
|
620
632
|
method: "POST",
|
|
@@ -709,6 +721,8 @@ Object.defineProperty(globalThis, _EDIT_MODE_SSR_SYM, {
|
|
|
709
721
|
},
|
|
710
722
|
configurable: true
|
|
711
723
|
});
|
|
724
|
+
var _SEE_CORR_ALS_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-correlation-als");
|
|
725
|
+
var seeContext = globalThis[_SEE_CORR_ALS_SYM] ?? (globalThis[_SEE_CORR_ALS_SYM] = new AsyncLocalStorage());
|
|
712
726
|
var i18n = {
|
|
713
727
|
/**
|
|
714
728
|
* Fetch translation labels for the current request and store them in an
|
|
@@ -830,7 +844,23 @@ async function shipeasy(opts) {
|
|
|
830
844
|
serverKey ? flags.initOnce() : Promise.resolve(),
|
|
831
845
|
serverKey ? i18n.init(serverKey, profile) : Promise.resolve()
|
|
832
846
|
]);
|
|
833
|
-
|
|
847
|
+
let anonId;
|
|
848
|
+
if (!opts.user?.user_id) {
|
|
849
|
+
if (opts.user?.anonymous_id) {
|
|
850
|
+
anonId = opts.user.anonymous_id;
|
|
851
|
+
} else {
|
|
852
|
+
try {
|
|
853
|
+
const { cookies } = await import("next/headers");
|
|
854
|
+
const c = await Promise.resolve(cookies());
|
|
855
|
+
const raw = c.get?.(ANON_ID_COOKIE)?.value;
|
|
856
|
+
if (raw && ANON_ID_RX.test(raw)) anonId = raw;
|
|
857
|
+
} catch {
|
|
858
|
+
}
|
|
859
|
+
if (!anonId) anonId = mintAnonId();
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
const effectiveUser = anonId ? { anonymous_id: anonId, ...opts.user } : { ...opts.user };
|
|
863
|
+
const bootstrap = flags.evaluate(effectiveUser, resolvedUrlOverrides);
|
|
834
864
|
const i18nData = i18n.getForRequest();
|
|
835
865
|
return {
|
|
836
866
|
flags: bootstrap.flags,
|
|
@@ -839,7 +869,8 @@ async function shipeasy(opts) {
|
|
|
839
869
|
getBootstrapHtml() {
|
|
840
870
|
return getBootstrapHtml(bootstrap, i18nData, {
|
|
841
871
|
editLabels,
|
|
842
|
-
i18nProfile: profile
|
|
872
|
+
i18nProfile: profile,
|
|
873
|
+
anonId
|
|
843
874
|
});
|
|
844
875
|
}
|
|
845
876
|
};
|
|
@@ -859,10 +890,16 @@ function getBootstrapHtml(bootstrap, i18nData, opts) {
|
|
|
859
890
|
};
|
|
860
891
|
if (i18nData) payload.i18n = i18nData;
|
|
861
892
|
if (opts.editLabels) payload.editLabels = true;
|
|
893
|
+
if (opts.anonId) payload.anonId = opts.anonId;
|
|
862
894
|
parts.push(
|
|
863
895
|
`(function(){var Q=new URLSearchParams(location.search).has('se_edit_labels');var C=/(?:^|;\\s*)se_edit_labels=1(?:;|$)/.test(document.cookie);if(!Q&&!C)return;if(Q){try{document.cookie='se_edit_labels=1;path=/;max-age=86400;samesite=lax';}catch(_){}}var R;function P(v){if(!v||typeof v.t!=='function'||v.__sePatched)return;var O=v.t.bind(v);v.__sePatched=true;window._sei18n_t=O;v.t=function(k,vars){var r=O(k,vars);if(r===k)return k;var V='';try{if(vars&&typeof vars==='object'){var hasKey=false;for(var _k in vars){hasKey=true;break;}if(hasKey)V=JSON.stringify(vars);}}catch(_){V='';}return '\\uFFF9'+k+'\\uFFFA'+V+'\\uFFFA'+r+'\\uFFFB';};}Object.defineProperty(window,'i18n',{configurable:true,get:function(){return R;},set:function(v){P(v);R=v;}});})();`
|
|
864
896
|
);
|
|
865
897
|
parts.push(`window.__SE_BOOTSTRAP=${JSON.stringify(payload)};`);
|
|
898
|
+
if (opts.anonId) {
|
|
899
|
+
parts.push(
|
|
900
|
+
`(function(){try{var k=${JSON.stringify(ANON_ID_COOKIE)},v=${JSON.stringify(opts.anonId)};if(('; '+document.cookie).indexOf('; '+k+'=')===-1){document.cookie=k+'='+v+';path=/;max-age=31536000;samesite=lax'+(location.protocol==='https:'?';secure':'');}}catch(_){}})();`
|
|
901
|
+
);
|
|
902
|
+
}
|
|
866
903
|
if (i18nData?.strings && Object.keys(i18nData.strings).length > 0) {
|
|
867
904
|
parts.push(
|
|
868
905
|
`(function(){var d=window.__SE_BOOTSTRAP.i18n;if(!d)return;window.i18n={locale:d.locale,t:function(k,v){var r=d.strings[k];if(!r)return k;return v?r.replace(/\\{\\{(\\w+)\\}\\}/g,function(_,p){return v[p]!==undefined?String(v[p]):'{{'+p+'}}'}):r;},on:function(){return function(){};}};})();`
|
|
@@ -947,6 +984,7 @@ var see = Object.assign(
|
|
|
947
984
|
}
|
|
948
985
|
);
|
|
949
986
|
export {
|
|
987
|
+
ANON_ID_COOKIE,
|
|
950
988
|
FlagsClient,
|
|
951
989
|
_resetShipeasyServerForTests,
|
|
952
990
|
configureShipeasyServer,
|
|
@@ -955,7 +993,9 @@ export {
|
|
|
955
993
|
getBootstrapHtml,
|
|
956
994
|
getShipeasyServerClient,
|
|
957
995
|
i18n,
|
|
996
|
+
isExpected,
|
|
958
997
|
see,
|
|
998
|
+
seeContext,
|
|
959
999
|
shipeasy,
|
|
960
1000
|
version
|
|
961
1001
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipeasy/sdk",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.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",
|