@shipeasy/sdk 4.1.0 → 4.3.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 +43 -2
- package/dist/client/index.d.ts +43 -2
- package/dist/client/index.js +181 -11
- package/dist/client/index.mjs +179 -11
- package/dist/server/index.d.mts +41 -1
- package/dist/server/index.d.ts +41 -1
- package/dist/server/index.js +59 -3
- package/dist/server/index.mjs +57 -3
- package/package.json +1 -1
package/dist/client/index.d.mts
CHANGED
|
@@ -18,6 +18,20 @@ interface Violation {
|
|
|
18
18
|
/** Attach free-form detail. Variable data goes HERE (or in extras), never in the name. */
|
|
19
19
|
message(msg: string): Violation;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Identity of a problem that see() already reported, carried on the wire as
|
|
23
|
+
* the `caused_by` of a later occurrence. Holds exactly the fields the worker's
|
|
24
|
+
* fingerprint function consumes (raw `message`/`stack` — the server normalizes
|
|
25
|
+
* them) so the backend can recompute the prior issue's fingerprint and link
|
|
26
|
+
* the two issues. See `findCausedBy` for how the link is discovered.
|
|
27
|
+
*/
|
|
28
|
+
interface SeeCausedBy {
|
|
29
|
+
error_type: string;
|
|
30
|
+
message: string;
|
|
31
|
+
stack?: string;
|
|
32
|
+
subject: string;
|
|
33
|
+
outcome: string;
|
|
34
|
+
}
|
|
21
35
|
/** Wire shape — the `type:"error"` RawEvent variant accepted by POST /collect. */
|
|
22
36
|
interface SeeErrorEvent {
|
|
23
37
|
type: "error";
|
|
@@ -37,6 +51,24 @@ interface SeeErrorEvent {
|
|
|
37
51
|
env?: string;
|
|
38
52
|
sdk_version: string;
|
|
39
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;
|
|
64
|
+
/**
|
|
65
|
+
* The earlier reported problem this occurrence descends from — present when
|
|
66
|
+
* the same error was caught + reported at an inner boundary and then
|
|
67
|
+
* re-thrown (or wrapped via `{ cause }`) and reported again at an outer one.
|
|
68
|
+
* Lets the backend stitch the two issues into a cause chain instead of
|
|
69
|
+
* double-counting them as unrelated.
|
|
70
|
+
*/
|
|
71
|
+
caused_by?: SeeCausedBy;
|
|
40
72
|
}
|
|
41
73
|
interface SeeExtrasTail {
|
|
42
74
|
/** Attach debugging metadata. Callable repeatedly — keys merge, later wins. */
|
|
@@ -98,6 +130,15 @@ interface AutoCollectGroups {
|
|
|
98
130
|
errors: boolean;
|
|
99
131
|
engagement: boolean;
|
|
100
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>;
|
|
101
142
|
type FlagsClientBrowserEnv = "dev" | "staging" | "prod";
|
|
102
143
|
interface FlagsClientBrowserOptions {
|
|
103
144
|
sdkKey: string;
|
|
@@ -154,7 +195,7 @@ declare class FlagsClientBrowser {
|
|
|
154
195
|
* (beacon-first) — error occurrences are near-real-time, never queued behind
|
|
155
196
|
* the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
|
|
156
197
|
*/
|
|
157
|
-
reportError(problem: unknown, consequence: Consequence, extras?: SeeExtras, kind?: SeeKind): void;
|
|
198
|
+
reportError(problem: unknown, consequence: Consequence, extras?: SeeExtras, kind?: SeeKind, correlationId?: string): void;
|
|
158
199
|
get ready(): boolean;
|
|
159
200
|
private notify;
|
|
160
201
|
initFromBootstrap(data: EvalResponse): void;
|
|
@@ -456,4 +497,4 @@ interface I18nFacade {
|
|
|
456
497
|
}
|
|
457
498
|
declare const i18n: I18nFacade;
|
|
458
499
|
|
|
459
|
-
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
|
@@ -18,6 +18,20 @@ interface Violation {
|
|
|
18
18
|
/** Attach free-form detail. Variable data goes HERE (or in extras), never in the name. */
|
|
19
19
|
message(msg: string): Violation;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Identity of a problem that see() already reported, carried on the wire as
|
|
23
|
+
* the `caused_by` of a later occurrence. Holds exactly the fields the worker's
|
|
24
|
+
* fingerprint function consumes (raw `message`/`stack` — the server normalizes
|
|
25
|
+
* them) so the backend can recompute the prior issue's fingerprint and link
|
|
26
|
+
* the two issues. See `findCausedBy` for how the link is discovered.
|
|
27
|
+
*/
|
|
28
|
+
interface SeeCausedBy {
|
|
29
|
+
error_type: string;
|
|
30
|
+
message: string;
|
|
31
|
+
stack?: string;
|
|
32
|
+
subject: string;
|
|
33
|
+
outcome: string;
|
|
34
|
+
}
|
|
21
35
|
/** Wire shape — the `type:"error"` RawEvent variant accepted by POST /collect. */
|
|
22
36
|
interface SeeErrorEvent {
|
|
23
37
|
type: "error";
|
|
@@ -37,6 +51,24 @@ interface SeeErrorEvent {
|
|
|
37
51
|
env?: string;
|
|
38
52
|
sdk_version: string;
|
|
39
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;
|
|
64
|
+
/**
|
|
65
|
+
* The earlier reported problem this occurrence descends from — present when
|
|
66
|
+
* the same error was caught + reported at an inner boundary and then
|
|
67
|
+
* re-thrown (or wrapped via `{ cause }`) and reported again at an outer one.
|
|
68
|
+
* Lets the backend stitch the two issues into a cause chain instead of
|
|
69
|
+
* double-counting them as unrelated.
|
|
70
|
+
*/
|
|
71
|
+
caused_by?: SeeCausedBy;
|
|
40
72
|
}
|
|
41
73
|
interface SeeExtrasTail {
|
|
42
74
|
/** Attach debugging metadata. Callable repeatedly — keys merge, later wins. */
|
|
@@ -98,6 +130,15 @@ interface AutoCollectGroups {
|
|
|
98
130
|
errors: boolean;
|
|
99
131
|
engagement: boolean;
|
|
100
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>;
|
|
101
142
|
type FlagsClientBrowserEnv = "dev" | "staging" | "prod";
|
|
102
143
|
interface FlagsClientBrowserOptions {
|
|
103
144
|
sdkKey: string;
|
|
@@ -154,7 +195,7 @@ declare class FlagsClientBrowser {
|
|
|
154
195
|
* (beacon-first) — error occurrences are near-real-time, never queued behind
|
|
155
196
|
* the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
|
|
156
197
|
*/
|
|
157
|
-
reportError(problem: unknown, consequence: Consequence, extras?: SeeExtras, kind?: SeeKind): void;
|
|
198
|
+
reportError(problem: unknown, consequence: Consequence, extras?: SeeExtras, kind?: SeeKind, correlationId?: string): void;
|
|
158
199
|
get ready(): boolean;
|
|
159
200
|
private notify;
|
|
160
201
|
initFromBootstrap(data: EvalResponse): void;
|
|
@@ -456,4 +497,4 @@ interface I18nFacade {
|
|
|
456
497
|
}
|
|
457
498
|
declare const i18n: I18nFacade;
|
|
458
499
|
|
|
459
|
-
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) {
|
|
@@ -152,6 +155,44 @@ function isExpected(err) {
|
|
|
152
155
|
if (typeof err !== "object" || err === null) return false;
|
|
153
156
|
return err[EXPECTED_SYM] !== void 0;
|
|
154
157
|
}
|
|
158
|
+
var REPORTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-reported");
|
|
159
|
+
var SEE_MAX_CAUSE_DEPTH = 8;
|
|
160
|
+
function readReportStamp(err) {
|
|
161
|
+
if (typeof err !== "object" || err === null) return void 0;
|
|
162
|
+
const v = err[REPORTED_SYM];
|
|
163
|
+
return v !== void 0 && v !== null && typeof v === "object" ? v : void 0;
|
|
164
|
+
}
|
|
165
|
+
function findCausedBy(problem) {
|
|
166
|
+
let cur = problem;
|
|
167
|
+
const seen = /* @__PURE__ */ new Set();
|
|
168
|
+
for (let depth = 0; depth < SEE_MAX_CAUSE_DEPTH; depth++) {
|
|
169
|
+
if (typeof cur !== "object" || cur === null || seen.has(cur)) break;
|
|
170
|
+
seen.add(cur);
|
|
171
|
+
const stamp = readReportStamp(cur);
|
|
172
|
+
if (stamp) return stamp;
|
|
173
|
+
cur = cur.cause;
|
|
174
|
+
}
|
|
175
|
+
return void 0;
|
|
176
|
+
}
|
|
177
|
+
function markReported(problem, ev) {
|
|
178
|
+
if (!(problem instanceof Error)) return;
|
|
179
|
+
const stamp = {
|
|
180
|
+
error_type: ev.error_type,
|
|
181
|
+
message: ev.message,
|
|
182
|
+
subject: ev.subject,
|
|
183
|
+
outcome: ev.outcome
|
|
184
|
+
};
|
|
185
|
+
if (ev.stack !== void 0) stamp.stack = ev.stack;
|
|
186
|
+
try {
|
|
187
|
+
Object.defineProperty(problem, REPORTED_SYM, {
|
|
188
|
+
value: Object.freeze(stamp),
|
|
189
|
+
enumerable: false,
|
|
190
|
+
configurable: true,
|
|
191
|
+
writable: true
|
|
192
|
+
});
|
|
193
|
+
} catch {
|
|
194
|
+
}
|
|
195
|
+
}
|
|
155
196
|
function truncate(s, max) {
|
|
156
197
|
return s.length > max ? s.slice(0, max) : s;
|
|
157
198
|
}
|
|
@@ -177,7 +218,7 @@ function captureCallsiteStack() {
|
|
|
177
218
|
const kept = lines.slice(1).filter((l) => !/@shipeasy[\\/]sdk|see[\\/]core|captureCallsiteStack|\bsee\b\s*\(/.test(l));
|
|
178
219
|
return kept.length ? kept.join("\n") : void 0;
|
|
179
220
|
}
|
|
180
|
-
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
221
|
+
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride, correlationId) {
|
|
181
222
|
let errorType;
|
|
182
223
|
let message;
|
|
183
224
|
let stack;
|
|
@@ -210,12 +251,16 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
|
210
251
|
ts: Date.now()
|
|
211
252
|
};
|
|
212
253
|
if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
|
|
254
|
+
if (correlationId) ev.correlation_id = truncate(String(correlationId), SEE_MAX_CORRELATION);
|
|
255
|
+
const causedBy = findCausedBy(problem);
|
|
256
|
+
if (causedBy) ev.caused_by = causedBy;
|
|
213
257
|
const cleanExtras = sanitizeExtras(extras);
|
|
214
258
|
if (cleanExtras) ev.extras = cleanExtras;
|
|
215
259
|
if (ctx.url) ev.url = truncate(ctx.url, SEE_MAX_SUBJECT);
|
|
216
260
|
if (ctx.userId) ev.user_id = ctx.userId;
|
|
217
261
|
if (ctx.anonId) ev.anonymous_id = ctx.anonId;
|
|
218
262
|
if (ctx.env) ev.env = ctx.env;
|
|
263
|
+
markReported(problem, ev);
|
|
219
264
|
return ev;
|
|
220
265
|
}
|
|
221
266
|
function safeString(v) {
|
|
@@ -238,7 +283,9 @@ function startSeeChain(getProblem, dispatch) {
|
|
|
238
283
|
flushed = true;
|
|
239
284
|
dispatch(
|
|
240
285
|
getProblem(),
|
|
241
|
-
|
|
286
|
+
// Bare noun phrase — titles render as "… causes the {subject} …", so a
|
|
287
|
+
// leading article would double up ("causes the the app").
|
|
288
|
+
causesThe(subject ?? "app").to(outcome ?? "hit an error"),
|
|
242
289
|
collected
|
|
243
290
|
);
|
|
244
291
|
});
|
|
@@ -449,6 +496,43 @@ var EventBuffer = class {
|
|
|
449
496
|
});
|
|
450
497
|
}
|
|
451
498
|
};
|
|
499
|
+
function endpointTemplate(rawUrl) {
|
|
500
|
+
const isIdSegment = (seg) => /^\d+$/.test(seg) || /^0x[0-9a-f]+$/i.test(seg) || /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(seg) || /^[0-9a-f]{8,}$/i.test(seg) || seg.length >= 12 && /\d/.test(seg) && /[a-z]/i.test(seg);
|
|
501
|
+
let u;
|
|
502
|
+
try {
|
|
503
|
+
u = new URL(rawUrl, typeof location !== "undefined" ? location.href : void 0);
|
|
504
|
+
} catch {
|
|
505
|
+
return (rawUrl.split(/[?#]/)[0] ?? "").slice(0, 120);
|
|
506
|
+
}
|
|
507
|
+
const path = u.pathname.split("/").map((seg) => seg && isIdSegment(seg) ? ":id" : seg).join("/");
|
|
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
|
+
}
|
|
535
|
+
}
|
|
452
536
|
function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignoreUrlPrefixes, always = false) {
|
|
453
537
|
if (typeof window === "undefined" || typeof PerformanceObserver === "undefined") return;
|
|
454
538
|
const shouldEmit = () => always || buffer.hasExposures();
|
|
@@ -497,7 +581,7 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
497
581
|
const problem = err ?? (typeof msg === "string" && msg ? msg : "Unknown error");
|
|
498
582
|
reportSee(
|
|
499
583
|
problem,
|
|
500
|
-
causesThe("
|
|
584
|
+
causesThe("page").to("hit an unhandled error"),
|
|
501
585
|
{
|
|
502
586
|
source: typeof source === "string" ? source : void 0,
|
|
503
587
|
line: lineno ?? void 0
|
|
@@ -513,7 +597,7 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
513
597
|
if (isExpected(reason)) return;
|
|
514
598
|
reportSee(
|
|
515
599
|
reason ?? "Unhandled promise rejection",
|
|
516
|
-
causesThe("
|
|
600
|
+
causesThe("page").to("hit an unhandled promise rejection"),
|
|
517
601
|
void 0,
|
|
518
602
|
"unhandled_rejection"
|
|
519
603
|
);
|
|
@@ -524,6 +608,11 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
524
608
|
const url = typeof args[0] === "string" ? args[0] : args[0].toString();
|
|
525
609
|
const ignored = ignoreUrlPrefixes.some((p) => p && url.startsWith(p));
|
|
526
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
|
+
}
|
|
527
616
|
let res;
|
|
528
617
|
try {
|
|
529
618
|
res = await origFetch.apply(this, args);
|
|
@@ -531,7 +620,7 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
531
620
|
if (!ignored && !isExpected(err)) {
|
|
532
621
|
reportSee(
|
|
533
622
|
violation("NetworkError").message(`request to ${bareUrl} failed`),
|
|
534
|
-
causesThe(
|
|
623
|
+
causesThe(`request to ${endpointTemplate(url)}`).to("get no response"),
|
|
535
624
|
{ status: 0, url: url.slice(0, 200) },
|
|
536
625
|
"network"
|
|
537
626
|
);
|
|
@@ -542,9 +631,10 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
542
631
|
const elapsed = typeof performance !== "undefined" ? performance.now() - startedAt : 0;
|
|
543
632
|
reportSee(
|
|
544
633
|
violation("Http5xx").message(`request to ${bareUrl} returned ${res.status}`),
|
|
545
|
-
causesThe(
|
|
634
|
+
causesThe(`request to ${endpointTemplate(url)}`).to("fail with a server error"),
|
|
546
635
|
{ status: res.status, url: url.slice(0, 200), duration_ms: Math.round(elapsed) },
|
|
547
|
-
"network"
|
|
636
|
+
"network",
|
|
637
|
+
corr
|
|
548
638
|
);
|
|
549
639
|
}
|
|
550
640
|
return res;
|
|
@@ -687,6 +777,83 @@ function collectBrowserAttrs() {
|
|
|
687
777
|
}
|
|
688
778
|
return attrs;
|
|
689
779
|
}
|
|
780
|
+
function collectSeeEnv() {
|
|
781
|
+
const out = {};
|
|
782
|
+
if (typeof navigator === "undefined") return out;
|
|
783
|
+
const nav = navigator;
|
|
784
|
+
const ua = typeof nav.userAgent === "string" ? nav.userAgent : "";
|
|
785
|
+
const browser = parseUaBrowser(ua);
|
|
786
|
+
if (browser) out["env.browser"] = browser;
|
|
787
|
+
const os = parseUaOs(ua) ?? nav.userAgentData?.platform;
|
|
788
|
+
if (os) out["env.os"] = os;
|
|
789
|
+
out["env.device"] = typeof nav.userAgentData?.mobile === "boolean" ? nav.userAgentData.mobile ? "mobile" : "desktop" : /iPad|Tablet/.test(ua) ? "tablet" : /Mobi|iPhone|Android.*Mobile/.test(ua) ? "mobile" : "desktop";
|
|
790
|
+
try {
|
|
791
|
+
if (nav.language) out["env.lang"] = nav.language;
|
|
792
|
+
} catch {
|
|
793
|
+
}
|
|
794
|
+
try {
|
|
795
|
+
if (typeof nav.onLine === "boolean") out["env.online"] = nav.onLine;
|
|
796
|
+
} catch {
|
|
797
|
+
}
|
|
798
|
+
try {
|
|
799
|
+
if (typeof nav.hardwareConcurrency === "number") out["env.cores"] = nav.hardwareConcurrency;
|
|
800
|
+
} catch {
|
|
801
|
+
}
|
|
802
|
+
try {
|
|
803
|
+
if (typeof nav.deviceMemory === "number") out["env.memory_gb"] = nav.deviceMemory;
|
|
804
|
+
} catch {
|
|
805
|
+
}
|
|
806
|
+
try {
|
|
807
|
+
const et = nav.connection?.effectiveType;
|
|
808
|
+
if (et) out["env.connection"] = et;
|
|
809
|
+
} catch {
|
|
810
|
+
}
|
|
811
|
+
try {
|
|
812
|
+
if (typeof window !== "undefined" && window.innerWidth && window.innerHeight) {
|
|
813
|
+
out["env.viewport"] = `${window.innerWidth}\xD7${window.innerHeight}`;
|
|
814
|
+
}
|
|
815
|
+
if (typeof window !== "undefined" && typeof window.devicePixelRatio === "number") {
|
|
816
|
+
out["env.dpr"] = window.devicePixelRatio;
|
|
817
|
+
}
|
|
818
|
+
if (typeof screen !== "undefined" && screen.width && screen.height) {
|
|
819
|
+
out["env.screen"] = `${screen.width}\xD7${screen.height}`;
|
|
820
|
+
}
|
|
821
|
+
} catch {
|
|
822
|
+
}
|
|
823
|
+
try {
|
|
824
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
825
|
+
if (tz) out["env.tz"] = tz;
|
|
826
|
+
} catch {
|
|
827
|
+
}
|
|
828
|
+
return out;
|
|
829
|
+
}
|
|
830
|
+
function parseUaBrowser(ua) {
|
|
831
|
+
const tests = [
|
|
832
|
+
[/Edg(?:A|iOS)?\/(\d+)/, "Edge"],
|
|
833
|
+
[/(?:OPR|Opera)\/(\d+)/, "Opera"],
|
|
834
|
+
[/(?:Firefox|FxiOS)\/(\d+)/, "Firefox"],
|
|
835
|
+
[/(?:Chrome|CriOS)\/(\d+)/, "Chrome"],
|
|
836
|
+
[/Version\/(\d+)[.\d]* (?:Mobile.*)?Safari/, "Safari"]
|
|
837
|
+
];
|
|
838
|
+
for (const [re, name] of tests) {
|
|
839
|
+
const m = re.exec(ua);
|
|
840
|
+
if (m) return `${name} ${m[1]}`;
|
|
841
|
+
}
|
|
842
|
+
return void 0;
|
|
843
|
+
}
|
|
844
|
+
function parseUaOs(ua) {
|
|
845
|
+
if (/Windows NT 10/.test(ua)) return "Windows 10/11";
|
|
846
|
+
if (/Windows NT/.test(ua)) return "Windows";
|
|
847
|
+
let m = /Mac OS X (\d+)[._](\d+)/.exec(ua);
|
|
848
|
+
if (m) return `macOS ${m[1]}.${m[2]}`;
|
|
849
|
+
if (/Macintosh/.test(ua)) return "macOS";
|
|
850
|
+
m = /Android (\d+)/.exec(ua);
|
|
851
|
+
if (m) return `Android ${m[1]}`;
|
|
852
|
+
m = /(?:iPhone|iPad)[^)]* OS (\d+)/.exec(ua);
|
|
853
|
+
if (m) return `iOS ${m[1]}`;
|
|
854
|
+
if (/Linux/.test(ua)) return "Linux";
|
|
855
|
+
return void 0;
|
|
856
|
+
}
|
|
690
857
|
function readExperimentOverridesFromUrl() {
|
|
691
858
|
if (typeof window === "undefined") return {};
|
|
692
859
|
const out = {};
|
|
@@ -779,7 +946,7 @@ var FlagsClientBrowser = class {
|
|
|
779
946
|
this.userId,
|
|
780
947
|
this.anonId,
|
|
781
948
|
this.autoGuardrailGroups,
|
|
782
|
-
(problem, consequence, extras, kind) => this.reportError(problem, consequence, extras, kind),
|
|
949
|
+
(problem, consequence, extras, kind, correlationId) => this.reportError(problem, consequence, extras, kind, correlationId),
|
|
783
950
|
[`${this.baseUrl}/`, DEFAULT_TELEMETRY_URL],
|
|
784
951
|
this.autoCollectAlways
|
|
785
952
|
);
|
|
@@ -791,16 +958,17 @@ var FlagsClientBrowser = class {
|
|
|
791
958
|
* (beacon-first) — error occurrences are near-real-time, never queued behind
|
|
792
959
|
* the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
|
|
793
960
|
*/
|
|
794
|
-
reportError(problem, consequence, extras, kind) {
|
|
961
|
+
reportError(problem, consequence, extras, kind, correlationId) {
|
|
795
962
|
try {
|
|
796
|
-
const
|
|
963
|
+
const enriched = { ...collectSeeEnv(), ...extras };
|
|
964
|
+
const ev = buildSeeEvent(problem, consequence, enriched, {
|
|
797
965
|
side: "client",
|
|
798
966
|
sdkVersion: version,
|
|
799
967
|
env: this.env,
|
|
800
968
|
url: typeof window !== "undefined" && window.location ? window.location.href : void 0,
|
|
801
969
|
userId: this.userId || void 0,
|
|
802
970
|
anonId: this.anonId
|
|
803
|
-
}, kind);
|
|
971
|
+
}, kind, correlationId);
|
|
804
972
|
if (!this.seeLimiter.shouldSend(ev)) return;
|
|
805
973
|
this.buffer.sendNow([ev]);
|
|
806
974
|
} catch {
|
|
@@ -1467,12 +1635,14 @@ var i18n = {
|
|
|
1467
1635
|
flags,
|
|
1468
1636
|
getShipeasyClient,
|
|
1469
1637
|
i18n,
|
|
1638
|
+
injectCorrelationHeader,
|
|
1470
1639
|
isDevtoolsRequested,
|
|
1471
1640
|
labelAttrs,
|
|
1472
1641
|
loadDevtools,
|
|
1473
1642
|
readConfigOverride,
|
|
1474
1643
|
readExpOverride,
|
|
1475
1644
|
readGateOverride,
|
|
1645
|
+
sameOrigin,
|
|
1476
1646
|
see,
|
|
1477
1647
|
shipeasy,
|
|
1478
1648
|
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) {
|
|
@@ -106,6 +107,44 @@ function isExpected(err) {
|
|
|
106
107
|
if (typeof err !== "object" || err === null) return false;
|
|
107
108
|
return err[EXPECTED_SYM] !== void 0;
|
|
108
109
|
}
|
|
110
|
+
var REPORTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-reported");
|
|
111
|
+
var SEE_MAX_CAUSE_DEPTH = 8;
|
|
112
|
+
function readReportStamp(err) {
|
|
113
|
+
if (typeof err !== "object" || err === null) return void 0;
|
|
114
|
+
const v = err[REPORTED_SYM];
|
|
115
|
+
return v !== void 0 && v !== null && typeof v === "object" ? v : void 0;
|
|
116
|
+
}
|
|
117
|
+
function findCausedBy(problem) {
|
|
118
|
+
let cur = problem;
|
|
119
|
+
const seen = /* @__PURE__ */ new Set();
|
|
120
|
+
for (let depth = 0; depth < SEE_MAX_CAUSE_DEPTH; depth++) {
|
|
121
|
+
if (typeof cur !== "object" || cur === null || seen.has(cur)) break;
|
|
122
|
+
seen.add(cur);
|
|
123
|
+
const stamp = readReportStamp(cur);
|
|
124
|
+
if (stamp) return stamp;
|
|
125
|
+
cur = cur.cause;
|
|
126
|
+
}
|
|
127
|
+
return void 0;
|
|
128
|
+
}
|
|
129
|
+
function markReported(problem, ev) {
|
|
130
|
+
if (!(problem instanceof Error)) return;
|
|
131
|
+
const stamp = {
|
|
132
|
+
error_type: ev.error_type,
|
|
133
|
+
message: ev.message,
|
|
134
|
+
subject: ev.subject,
|
|
135
|
+
outcome: ev.outcome
|
|
136
|
+
};
|
|
137
|
+
if (ev.stack !== void 0) stamp.stack = ev.stack;
|
|
138
|
+
try {
|
|
139
|
+
Object.defineProperty(problem, REPORTED_SYM, {
|
|
140
|
+
value: Object.freeze(stamp),
|
|
141
|
+
enumerable: false,
|
|
142
|
+
configurable: true,
|
|
143
|
+
writable: true
|
|
144
|
+
});
|
|
145
|
+
} catch {
|
|
146
|
+
}
|
|
147
|
+
}
|
|
109
148
|
function truncate(s, max) {
|
|
110
149
|
return s.length > max ? s.slice(0, max) : s;
|
|
111
150
|
}
|
|
@@ -131,7 +170,7 @@ function captureCallsiteStack() {
|
|
|
131
170
|
const kept = lines.slice(1).filter((l) => !/@shipeasy[\\/]sdk|see[\\/]core|captureCallsiteStack|\bsee\b\s*\(/.test(l));
|
|
132
171
|
return kept.length ? kept.join("\n") : void 0;
|
|
133
172
|
}
|
|
134
|
-
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
173
|
+
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride, correlationId) {
|
|
135
174
|
let errorType;
|
|
136
175
|
let message;
|
|
137
176
|
let stack;
|
|
@@ -164,12 +203,16 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
|
164
203
|
ts: Date.now()
|
|
165
204
|
};
|
|
166
205
|
if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
|
|
206
|
+
if (correlationId) ev.correlation_id = truncate(String(correlationId), SEE_MAX_CORRELATION);
|
|
207
|
+
const causedBy = findCausedBy(problem);
|
|
208
|
+
if (causedBy) ev.caused_by = causedBy;
|
|
167
209
|
const cleanExtras = sanitizeExtras(extras);
|
|
168
210
|
if (cleanExtras) ev.extras = cleanExtras;
|
|
169
211
|
if (ctx.url) ev.url = truncate(ctx.url, SEE_MAX_SUBJECT);
|
|
170
212
|
if (ctx.userId) ev.user_id = ctx.userId;
|
|
171
213
|
if (ctx.anonId) ev.anonymous_id = ctx.anonId;
|
|
172
214
|
if (ctx.env) ev.env = ctx.env;
|
|
215
|
+
markReported(problem, ev);
|
|
173
216
|
return ev;
|
|
174
217
|
}
|
|
175
218
|
function safeString(v) {
|
|
@@ -192,7 +235,9 @@ function startSeeChain(getProblem, dispatch) {
|
|
|
192
235
|
flushed = true;
|
|
193
236
|
dispatch(
|
|
194
237
|
getProblem(),
|
|
195
|
-
|
|
238
|
+
// Bare noun phrase — titles render as "… causes the {subject} …", so a
|
|
239
|
+
// leading article would double up ("causes the the app").
|
|
240
|
+
causesThe(subject ?? "app").to(outcome ?? "hit an error"),
|
|
196
241
|
collected
|
|
197
242
|
);
|
|
198
243
|
});
|
|
@@ -403,6 +448,43 @@ var EventBuffer = class {
|
|
|
403
448
|
});
|
|
404
449
|
}
|
|
405
450
|
};
|
|
451
|
+
function endpointTemplate(rawUrl) {
|
|
452
|
+
const isIdSegment = (seg) => /^\d+$/.test(seg) || /^0x[0-9a-f]+$/i.test(seg) || /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(seg) || /^[0-9a-f]{8,}$/i.test(seg) || seg.length >= 12 && /\d/.test(seg) && /[a-z]/i.test(seg);
|
|
453
|
+
let u;
|
|
454
|
+
try {
|
|
455
|
+
u = new URL(rawUrl, typeof location !== "undefined" ? location.href : void 0);
|
|
456
|
+
} catch {
|
|
457
|
+
return (rawUrl.split(/[?#]/)[0] ?? "").slice(0, 120);
|
|
458
|
+
}
|
|
459
|
+
const path = u.pathname.split("/").map((seg) => seg && isIdSegment(seg) ? ":id" : seg).join("/");
|
|
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
|
+
}
|
|
487
|
+
}
|
|
406
488
|
function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignoreUrlPrefixes, always = false) {
|
|
407
489
|
if (typeof window === "undefined" || typeof PerformanceObserver === "undefined") return;
|
|
408
490
|
const shouldEmit = () => always || buffer.hasExposures();
|
|
@@ -451,7 +533,7 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
451
533
|
const problem = err ?? (typeof msg === "string" && msg ? msg : "Unknown error");
|
|
452
534
|
reportSee(
|
|
453
535
|
problem,
|
|
454
|
-
causesThe("
|
|
536
|
+
causesThe("page").to("hit an unhandled error"),
|
|
455
537
|
{
|
|
456
538
|
source: typeof source === "string" ? source : void 0,
|
|
457
539
|
line: lineno ?? void 0
|
|
@@ -467,7 +549,7 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
467
549
|
if (isExpected(reason)) return;
|
|
468
550
|
reportSee(
|
|
469
551
|
reason ?? "Unhandled promise rejection",
|
|
470
|
-
causesThe("
|
|
552
|
+
causesThe("page").to("hit an unhandled promise rejection"),
|
|
471
553
|
void 0,
|
|
472
554
|
"unhandled_rejection"
|
|
473
555
|
);
|
|
@@ -478,6 +560,11 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
478
560
|
const url = typeof args[0] === "string" ? args[0] : args[0].toString();
|
|
479
561
|
const ignored = ignoreUrlPrefixes.some((p) => p && url.startsWith(p));
|
|
480
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
|
+
}
|
|
481
568
|
let res;
|
|
482
569
|
try {
|
|
483
570
|
res = await origFetch.apply(this, args);
|
|
@@ -485,7 +572,7 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
485
572
|
if (!ignored && !isExpected(err)) {
|
|
486
573
|
reportSee(
|
|
487
574
|
violation("NetworkError").message(`request to ${bareUrl} failed`),
|
|
488
|
-
causesThe(
|
|
575
|
+
causesThe(`request to ${endpointTemplate(url)}`).to("get no response"),
|
|
489
576
|
{ status: 0, url: url.slice(0, 200) },
|
|
490
577
|
"network"
|
|
491
578
|
);
|
|
@@ -496,9 +583,10 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
496
583
|
const elapsed = typeof performance !== "undefined" ? performance.now() - startedAt : 0;
|
|
497
584
|
reportSee(
|
|
498
585
|
violation("Http5xx").message(`request to ${bareUrl} returned ${res.status}`),
|
|
499
|
-
causesThe(
|
|
586
|
+
causesThe(`request to ${endpointTemplate(url)}`).to("fail with a server error"),
|
|
500
587
|
{ status: res.status, url: url.slice(0, 200), duration_ms: Math.round(elapsed) },
|
|
501
|
-
"network"
|
|
588
|
+
"network",
|
|
589
|
+
corr
|
|
502
590
|
);
|
|
503
591
|
}
|
|
504
592
|
return res;
|
|
@@ -641,6 +729,83 @@ function collectBrowserAttrs() {
|
|
|
641
729
|
}
|
|
642
730
|
return attrs;
|
|
643
731
|
}
|
|
732
|
+
function collectSeeEnv() {
|
|
733
|
+
const out = {};
|
|
734
|
+
if (typeof navigator === "undefined") return out;
|
|
735
|
+
const nav = navigator;
|
|
736
|
+
const ua = typeof nav.userAgent === "string" ? nav.userAgent : "";
|
|
737
|
+
const browser = parseUaBrowser(ua);
|
|
738
|
+
if (browser) out["env.browser"] = browser;
|
|
739
|
+
const os = parseUaOs(ua) ?? nav.userAgentData?.platform;
|
|
740
|
+
if (os) out["env.os"] = os;
|
|
741
|
+
out["env.device"] = typeof nav.userAgentData?.mobile === "boolean" ? nav.userAgentData.mobile ? "mobile" : "desktop" : /iPad|Tablet/.test(ua) ? "tablet" : /Mobi|iPhone|Android.*Mobile/.test(ua) ? "mobile" : "desktop";
|
|
742
|
+
try {
|
|
743
|
+
if (nav.language) out["env.lang"] = nav.language;
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
try {
|
|
747
|
+
if (typeof nav.onLine === "boolean") out["env.online"] = nav.onLine;
|
|
748
|
+
} catch {
|
|
749
|
+
}
|
|
750
|
+
try {
|
|
751
|
+
if (typeof nav.hardwareConcurrency === "number") out["env.cores"] = nav.hardwareConcurrency;
|
|
752
|
+
} catch {
|
|
753
|
+
}
|
|
754
|
+
try {
|
|
755
|
+
if (typeof nav.deviceMemory === "number") out["env.memory_gb"] = nav.deviceMemory;
|
|
756
|
+
} catch {
|
|
757
|
+
}
|
|
758
|
+
try {
|
|
759
|
+
const et = nav.connection?.effectiveType;
|
|
760
|
+
if (et) out["env.connection"] = et;
|
|
761
|
+
} catch {
|
|
762
|
+
}
|
|
763
|
+
try {
|
|
764
|
+
if (typeof window !== "undefined" && window.innerWidth && window.innerHeight) {
|
|
765
|
+
out["env.viewport"] = `${window.innerWidth}\xD7${window.innerHeight}`;
|
|
766
|
+
}
|
|
767
|
+
if (typeof window !== "undefined" && typeof window.devicePixelRatio === "number") {
|
|
768
|
+
out["env.dpr"] = window.devicePixelRatio;
|
|
769
|
+
}
|
|
770
|
+
if (typeof screen !== "undefined" && screen.width && screen.height) {
|
|
771
|
+
out["env.screen"] = `${screen.width}\xD7${screen.height}`;
|
|
772
|
+
}
|
|
773
|
+
} catch {
|
|
774
|
+
}
|
|
775
|
+
try {
|
|
776
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
777
|
+
if (tz) out["env.tz"] = tz;
|
|
778
|
+
} catch {
|
|
779
|
+
}
|
|
780
|
+
return out;
|
|
781
|
+
}
|
|
782
|
+
function parseUaBrowser(ua) {
|
|
783
|
+
const tests = [
|
|
784
|
+
[/Edg(?:A|iOS)?\/(\d+)/, "Edge"],
|
|
785
|
+
[/(?:OPR|Opera)\/(\d+)/, "Opera"],
|
|
786
|
+
[/(?:Firefox|FxiOS)\/(\d+)/, "Firefox"],
|
|
787
|
+
[/(?:Chrome|CriOS)\/(\d+)/, "Chrome"],
|
|
788
|
+
[/Version\/(\d+)[.\d]* (?:Mobile.*)?Safari/, "Safari"]
|
|
789
|
+
];
|
|
790
|
+
for (const [re, name] of tests) {
|
|
791
|
+
const m = re.exec(ua);
|
|
792
|
+
if (m) return `${name} ${m[1]}`;
|
|
793
|
+
}
|
|
794
|
+
return void 0;
|
|
795
|
+
}
|
|
796
|
+
function parseUaOs(ua) {
|
|
797
|
+
if (/Windows NT 10/.test(ua)) return "Windows 10/11";
|
|
798
|
+
if (/Windows NT/.test(ua)) return "Windows";
|
|
799
|
+
let m = /Mac OS X (\d+)[._](\d+)/.exec(ua);
|
|
800
|
+
if (m) return `macOS ${m[1]}.${m[2]}`;
|
|
801
|
+
if (/Macintosh/.test(ua)) return "macOS";
|
|
802
|
+
m = /Android (\d+)/.exec(ua);
|
|
803
|
+
if (m) return `Android ${m[1]}`;
|
|
804
|
+
m = /(?:iPhone|iPad)[^)]* OS (\d+)/.exec(ua);
|
|
805
|
+
if (m) return `iOS ${m[1]}`;
|
|
806
|
+
if (/Linux/.test(ua)) return "Linux";
|
|
807
|
+
return void 0;
|
|
808
|
+
}
|
|
644
809
|
function readExperimentOverridesFromUrl() {
|
|
645
810
|
if (typeof window === "undefined") return {};
|
|
646
811
|
const out = {};
|
|
@@ -733,7 +898,7 @@ var FlagsClientBrowser = class {
|
|
|
733
898
|
this.userId,
|
|
734
899
|
this.anonId,
|
|
735
900
|
this.autoGuardrailGroups,
|
|
736
|
-
(problem, consequence, extras, kind) => this.reportError(problem, consequence, extras, kind),
|
|
901
|
+
(problem, consequence, extras, kind, correlationId) => this.reportError(problem, consequence, extras, kind, correlationId),
|
|
737
902
|
[`${this.baseUrl}/`, DEFAULT_TELEMETRY_URL],
|
|
738
903
|
this.autoCollectAlways
|
|
739
904
|
);
|
|
@@ -745,16 +910,17 @@ var FlagsClientBrowser = class {
|
|
|
745
910
|
* (beacon-first) — error occurrences are near-real-time, never queued behind
|
|
746
911
|
* the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
|
|
747
912
|
*/
|
|
748
|
-
reportError(problem, consequence, extras, kind) {
|
|
913
|
+
reportError(problem, consequence, extras, kind, correlationId) {
|
|
749
914
|
try {
|
|
750
|
-
const
|
|
915
|
+
const enriched = { ...collectSeeEnv(), ...extras };
|
|
916
|
+
const ev = buildSeeEvent(problem, consequence, enriched, {
|
|
751
917
|
side: "client",
|
|
752
918
|
sdkVersion: version,
|
|
753
919
|
env: this.env,
|
|
754
920
|
url: typeof window !== "undefined" && window.location ? window.location.href : void 0,
|
|
755
921
|
userId: this.userId || void 0,
|
|
756
922
|
anonId: this.anonId
|
|
757
|
-
}, kind);
|
|
923
|
+
}, kind, correlationId);
|
|
758
924
|
if (!this.seeLimiter.shouldSend(ev)) return;
|
|
759
925
|
this.buffer.sendNow([ev]);
|
|
760
926
|
} catch {
|
|
@@ -1420,12 +1586,14 @@ export {
|
|
|
1420
1586
|
flags,
|
|
1421
1587
|
getShipeasyClient,
|
|
1422
1588
|
i18n,
|
|
1589
|
+
injectCorrelationHeader,
|
|
1423
1590
|
isDevtoolsRequested,
|
|
1424
1591
|
labelAttrs,
|
|
1425
1592
|
loadDevtools,
|
|
1426
1593
|
readConfigOverride,
|
|
1427
1594
|
readExpOverride,
|
|
1428
1595
|
readGateOverride,
|
|
1596
|
+
sameOrigin,
|
|
1429
1597
|
see,
|
|
1430
1598
|
shipeasy,
|
|
1431
1599
|
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. */
|
|
@@ -18,6 +20,20 @@ interface Violation {
|
|
|
18
20
|
/** Attach free-form detail. Variable data goes HERE (or in extras), never in the name. */
|
|
19
21
|
message(msg: string): Violation;
|
|
20
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Identity of a problem that see() already reported, carried on the wire as
|
|
25
|
+
* the `caused_by` of a later occurrence. Holds exactly the fields the worker's
|
|
26
|
+
* fingerprint function consumes (raw `message`/`stack` — the server normalizes
|
|
27
|
+
* them) so the backend can recompute the prior issue's fingerprint and link
|
|
28
|
+
* the two issues. See `findCausedBy` for how the link is discovered.
|
|
29
|
+
*/
|
|
30
|
+
interface SeeCausedBy {
|
|
31
|
+
error_type: string;
|
|
32
|
+
message: string;
|
|
33
|
+
stack?: string;
|
|
34
|
+
subject: string;
|
|
35
|
+
outcome: string;
|
|
36
|
+
}
|
|
21
37
|
/** Wire shape — the `type:"error"` RawEvent variant accepted by POST /collect. */
|
|
22
38
|
interface SeeErrorEvent {
|
|
23
39
|
type: "error";
|
|
@@ -37,7 +53,26 @@ interface SeeErrorEvent {
|
|
|
37
53
|
env?: string;
|
|
38
54
|
sdk_version: string;
|
|
39
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;
|
|
66
|
+
/**
|
|
67
|
+
* The earlier reported problem this occurrence descends from — present when
|
|
68
|
+
* the same error was caught + reported at an inner boundary and then
|
|
69
|
+
* re-thrown (or wrapped via `{ cause }`) and reported again at an outer one.
|
|
70
|
+
* Lets the backend stitch the two issues into a cause chain instead of
|
|
71
|
+
* double-counting them as unrelated.
|
|
72
|
+
*/
|
|
73
|
+
caused_by?: SeeCausedBy;
|
|
40
74
|
}
|
|
75
|
+
declare function isExpected(err: unknown): boolean;
|
|
41
76
|
interface SeeExtrasTail {
|
|
42
77
|
/** Attach debugging metadata. Callable repeatedly — keys merge, later wins. */
|
|
43
78
|
extras(extras: SeeExtras): SeeExtrasTail;
|
|
@@ -165,6 +200,11 @@ declare class FlagsClient {
|
|
|
165
200
|
evaluate(user: User, rawUrl?: string): BootstrapPayload;
|
|
166
201
|
getKillswitch(name: string, switchKey?: string): boolean;
|
|
167
202
|
}
|
|
203
|
+
interface SeeCorrelationStore {
|
|
204
|
+
correlationId?: string;
|
|
205
|
+
}
|
|
206
|
+
declare const seeContext: AsyncLocalStorage<SeeCorrelationStore>;
|
|
207
|
+
|
|
168
208
|
interface I18nForRequest {
|
|
169
209
|
strings: Record<string, string>;
|
|
170
210
|
locale: string;
|
|
@@ -340,4 +380,4 @@ interface SeeApi {
|
|
|
340
380
|
*/
|
|
341
381
|
declare const see: SeeApi;
|
|
342
382
|
|
|
343
|
-
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 };
|
|
383
|
+
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, 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. */
|
|
@@ -18,6 +20,20 @@ interface Violation {
|
|
|
18
20
|
/** Attach free-form detail. Variable data goes HERE (or in extras), never in the name. */
|
|
19
21
|
message(msg: string): Violation;
|
|
20
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Identity of a problem that see() already reported, carried on the wire as
|
|
25
|
+
* the `caused_by` of a later occurrence. Holds exactly the fields the worker's
|
|
26
|
+
* fingerprint function consumes (raw `message`/`stack` — the server normalizes
|
|
27
|
+
* them) so the backend can recompute the prior issue's fingerprint and link
|
|
28
|
+
* the two issues. See `findCausedBy` for how the link is discovered.
|
|
29
|
+
*/
|
|
30
|
+
interface SeeCausedBy {
|
|
31
|
+
error_type: string;
|
|
32
|
+
message: string;
|
|
33
|
+
stack?: string;
|
|
34
|
+
subject: string;
|
|
35
|
+
outcome: string;
|
|
36
|
+
}
|
|
21
37
|
/** Wire shape — the `type:"error"` RawEvent variant accepted by POST /collect. */
|
|
22
38
|
interface SeeErrorEvent {
|
|
23
39
|
type: "error";
|
|
@@ -37,7 +53,26 @@ interface SeeErrorEvent {
|
|
|
37
53
|
env?: string;
|
|
38
54
|
sdk_version: string;
|
|
39
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;
|
|
66
|
+
/**
|
|
67
|
+
* The earlier reported problem this occurrence descends from — present when
|
|
68
|
+
* the same error was caught + reported at an inner boundary and then
|
|
69
|
+
* re-thrown (or wrapped via `{ cause }`) and reported again at an outer one.
|
|
70
|
+
* Lets the backend stitch the two issues into a cause chain instead of
|
|
71
|
+
* double-counting them as unrelated.
|
|
72
|
+
*/
|
|
73
|
+
caused_by?: SeeCausedBy;
|
|
40
74
|
}
|
|
75
|
+
declare function isExpected(err: unknown): boolean;
|
|
41
76
|
interface SeeExtrasTail {
|
|
42
77
|
/** Attach debugging metadata. Callable repeatedly — keys merge, later wins. */
|
|
43
78
|
extras(extras: SeeExtras): SeeExtrasTail;
|
|
@@ -165,6 +200,11 @@ declare class FlagsClient {
|
|
|
165
200
|
evaluate(user: User, rawUrl?: string): BootstrapPayload;
|
|
166
201
|
getKillswitch(name: string, switchKey?: string): boolean;
|
|
167
202
|
}
|
|
203
|
+
interface SeeCorrelationStore {
|
|
204
|
+
correlationId?: string;
|
|
205
|
+
}
|
|
206
|
+
declare const seeContext: AsyncLocalStorage<SeeCorrelationStore>;
|
|
207
|
+
|
|
168
208
|
interface I18nForRequest {
|
|
169
209
|
strings: Record<string, string>;
|
|
170
210
|
locale: string;
|
|
@@ -340,4 +380,4 @@ interface SeeApi {
|
|
|
340
380
|
*/
|
|
341
381
|
declare const see: SeeApi;
|
|
342
382
|
|
|
343
|
-
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 };
|
|
383
|
+
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, isExpected, see, seeContext, shipeasy, version };
|
package/dist/server/index.js
CHANGED
|
@@ -38,7 +38,9 @@ __export(server_exports, {
|
|
|
38
38
|
getBootstrapHtml: () => getBootstrapHtml,
|
|
39
39
|
getShipeasyServerClient: () => getShipeasyServerClient,
|
|
40
40
|
i18n: () => i18n,
|
|
41
|
+
isExpected: () => isExpected,
|
|
41
42
|
see: () => see,
|
|
43
|
+
seeContext: () => seeContext,
|
|
42
44
|
shipeasy: () => shipeasy,
|
|
43
45
|
version: () => version
|
|
44
46
|
});
|
|
@@ -110,6 +112,7 @@ var SEE_MAX_STACK = 8e3;
|
|
|
110
112
|
var SEE_MAX_SUBJECT = 200;
|
|
111
113
|
var SEE_MAX_EXTRA_VALUE = 200;
|
|
112
114
|
var SEE_MAX_EXTRA_KEYS = 20;
|
|
115
|
+
var SEE_MAX_CORRELATION = 64;
|
|
113
116
|
var SEE_DEDUP_WINDOW_MS = 3e4;
|
|
114
117
|
var SEE_MAX_PER_SESSION = 25;
|
|
115
118
|
function causesThe(subject) {
|
|
@@ -149,6 +152,48 @@ function markExpected(err, because) {
|
|
|
149
152
|
} catch {
|
|
150
153
|
}
|
|
151
154
|
}
|
|
155
|
+
function isExpected(err) {
|
|
156
|
+
if (typeof err !== "object" || err === null) return false;
|
|
157
|
+
return err[EXPECTED_SYM] !== void 0;
|
|
158
|
+
}
|
|
159
|
+
var REPORTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-reported");
|
|
160
|
+
var SEE_MAX_CAUSE_DEPTH = 8;
|
|
161
|
+
function readReportStamp(err) {
|
|
162
|
+
if (typeof err !== "object" || err === null) return void 0;
|
|
163
|
+
const v = err[REPORTED_SYM];
|
|
164
|
+
return v !== void 0 && v !== null && typeof v === "object" ? v : void 0;
|
|
165
|
+
}
|
|
166
|
+
function findCausedBy(problem) {
|
|
167
|
+
let cur = problem;
|
|
168
|
+
const seen = /* @__PURE__ */ new Set();
|
|
169
|
+
for (let depth = 0; depth < SEE_MAX_CAUSE_DEPTH; depth++) {
|
|
170
|
+
if (typeof cur !== "object" || cur === null || seen.has(cur)) break;
|
|
171
|
+
seen.add(cur);
|
|
172
|
+
const stamp = readReportStamp(cur);
|
|
173
|
+
if (stamp) return stamp;
|
|
174
|
+
cur = cur.cause;
|
|
175
|
+
}
|
|
176
|
+
return void 0;
|
|
177
|
+
}
|
|
178
|
+
function markReported(problem, ev) {
|
|
179
|
+
if (!(problem instanceof Error)) return;
|
|
180
|
+
const stamp = {
|
|
181
|
+
error_type: ev.error_type,
|
|
182
|
+
message: ev.message,
|
|
183
|
+
subject: ev.subject,
|
|
184
|
+
outcome: ev.outcome
|
|
185
|
+
};
|
|
186
|
+
if (ev.stack !== void 0) stamp.stack = ev.stack;
|
|
187
|
+
try {
|
|
188
|
+
Object.defineProperty(problem, REPORTED_SYM, {
|
|
189
|
+
value: Object.freeze(stamp),
|
|
190
|
+
enumerable: false,
|
|
191
|
+
configurable: true,
|
|
192
|
+
writable: true
|
|
193
|
+
});
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
196
|
+
}
|
|
152
197
|
function truncate(s, max) {
|
|
153
198
|
return s.length > max ? s.slice(0, max) : s;
|
|
154
199
|
}
|
|
@@ -174,7 +219,7 @@ function captureCallsiteStack() {
|
|
|
174
219
|
const kept = lines.slice(1).filter((l) => !/@shipeasy[\\/]sdk|see[\\/]core|captureCallsiteStack|\bsee\b\s*\(/.test(l));
|
|
175
220
|
return kept.length ? kept.join("\n") : void 0;
|
|
176
221
|
}
|
|
177
|
-
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
222
|
+
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride, correlationId) {
|
|
178
223
|
let errorType;
|
|
179
224
|
let message;
|
|
180
225
|
let stack;
|
|
@@ -207,12 +252,16 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
|
207
252
|
ts: Date.now()
|
|
208
253
|
};
|
|
209
254
|
if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
|
|
255
|
+
if (correlationId) ev.correlation_id = truncate(String(correlationId), SEE_MAX_CORRELATION);
|
|
256
|
+
const causedBy = findCausedBy(problem);
|
|
257
|
+
if (causedBy) ev.caused_by = causedBy;
|
|
210
258
|
const cleanExtras = sanitizeExtras(extras);
|
|
211
259
|
if (cleanExtras) ev.extras = cleanExtras;
|
|
212
260
|
if (ctx.url) ev.url = truncate(ctx.url, SEE_MAX_SUBJECT);
|
|
213
261
|
if (ctx.userId) ev.user_id = ctx.userId;
|
|
214
262
|
if (ctx.anonId) ev.anonymous_id = ctx.anonId;
|
|
215
263
|
if (ctx.env) ev.env = ctx.env;
|
|
264
|
+
markReported(problem, ev);
|
|
216
265
|
return ev;
|
|
217
266
|
}
|
|
218
267
|
function safeString(v) {
|
|
@@ -235,7 +284,9 @@ function startSeeChain(getProblem, dispatch) {
|
|
|
235
284
|
flushed = true;
|
|
236
285
|
dispatch(
|
|
237
286
|
getProblem(),
|
|
238
|
-
|
|
287
|
+
// Bare noun phrase — titles render as "… causes the {subject} …", so a
|
|
288
|
+
// leading article would double up ("causes the the app").
|
|
289
|
+
causesThe(subject ?? "app").to(outcome ?? "hit an error"),
|
|
239
290
|
collected
|
|
240
291
|
);
|
|
241
292
|
});
|
|
@@ -611,11 +662,12 @@ var FlagsClient = class {
|
|
|
611
662
|
*/
|
|
612
663
|
reportError(problem, consequence, extras, kind) {
|
|
613
664
|
try {
|
|
665
|
+
const correlationId = seeContext.getStore()?.correlationId;
|
|
614
666
|
const ev = buildSeeEvent(problem, consequence, extras, {
|
|
615
667
|
side: "server",
|
|
616
668
|
sdkVersion: version,
|
|
617
669
|
env: this.env
|
|
618
|
-
}, kind);
|
|
670
|
+
}, kind, correlationId);
|
|
619
671
|
if (!this.seeLimiter.shouldSend(ev)) return;
|
|
620
672
|
globalThis.fetch(`${this.baseUrl}/collect`, {
|
|
621
673
|
method: "POST",
|
|
@@ -710,6 +762,8 @@ Object.defineProperty(globalThis, _EDIT_MODE_SSR_SYM, {
|
|
|
710
762
|
},
|
|
711
763
|
configurable: true
|
|
712
764
|
});
|
|
765
|
+
var _SEE_CORR_ALS_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-correlation-als");
|
|
766
|
+
var seeContext = globalThis[_SEE_CORR_ALS_SYM] ?? (globalThis[_SEE_CORR_ALS_SYM] = new import_node_async_hooks.AsyncLocalStorage());
|
|
713
767
|
var i18n = {
|
|
714
768
|
/**
|
|
715
769
|
* Fetch translation labels for the current request and store them in an
|
|
@@ -957,7 +1011,9 @@ var see = Object.assign(
|
|
|
957
1011
|
getBootstrapHtml,
|
|
958
1012
|
getShipeasyServerClient,
|
|
959
1013
|
i18n,
|
|
1014
|
+
isExpected,
|
|
960
1015
|
see,
|
|
1016
|
+
seeContext,
|
|
961
1017
|
shipeasy,
|
|
962
1018
|
version
|
|
963
1019
|
});
|
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,48 @@ 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
|
+
}
|
|
113
|
+
var REPORTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-reported");
|
|
114
|
+
var SEE_MAX_CAUSE_DEPTH = 8;
|
|
115
|
+
function readReportStamp(err) {
|
|
116
|
+
if (typeof err !== "object" || err === null) return void 0;
|
|
117
|
+
const v = err[REPORTED_SYM];
|
|
118
|
+
return v !== void 0 && v !== null && typeof v === "object" ? v : void 0;
|
|
119
|
+
}
|
|
120
|
+
function findCausedBy(problem) {
|
|
121
|
+
let cur = problem;
|
|
122
|
+
const seen = /* @__PURE__ */ new Set();
|
|
123
|
+
for (let depth = 0; depth < SEE_MAX_CAUSE_DEPTH; depth++) {
|
|
124
|
+
if (typeof cur !== "object" || cur === null || seen.has(cur)) break;
|
|
125
|
+
seen.add(cur);
|
|
126
|
+
const stamp = readReportStamp(cur);
|
|
127
|
+
if (stamp) return stamp;
|
|
128
|
+
cur = cur.cause;
|
|
129
|
+
}
|
|
130
|
+
return void 0;
|
|
131
|
+
}
|
|
132
|
+
function markReported(problem, ev) {
|
|
133
|
+
if (!(problem instanceof Error)) return;
|
|
134
|
+
const stamp = {
|
|
135
|
+
error_type: ev.error_type,
|
|
136
|
+
message: ev.message,
|
|
137
|
+
subject: ev.subject,
|
|
138
|
+
outcome: ev.outcome
|
|
139
|
+
};
|
|
140
|
+
if (ev.stack !== void 0) stamp.stack = ev.stack;
|
|
141
|
+
try {
|
|
142
|
+
Object.defineProperty(problem, REPORTED_SYM, {
|
|
143
|
+
value: Object.freeze(stamp),
|
|
144
|
+
enumerable: false,
|
|
145
|
+
configurable: true,
|
|
146
|
+
writable: true
|
|
147
|
+
});
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
108
151
|
function truncate(s, max) {
|
|
109
152
|
return s.length > max ? s.slice(0, max) : s;
|
|
110
153
|
}
|
|
@@ -130,7 +173,7 @@ function captureCallsiteStack() {
|
|
|
130
173
|
const kept = lines.slice(1).filter((l) => !/@shipeasy[\\/]sdk|see[\\/]core|captureCallsiteStack|\bsee\b\s*\(/.test(l));
|
|
131
174
|
return kept.length ? kept.join("\n") : void 0;
|
|
132
175
|
}
|
|
133
|
-
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
176
|
+
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride, correlationId) {
|
|
134
177
|
let errorType;
|
|
135
178
|
let message;
|
|
136
179
|
let stack;
|
|
@@ -163,12 +206,16 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
|
163
206
|
ts: Date.now()
|
|
164
207
|
};
|
|
165
208
|
if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
|
|
209
|
+
if (correlationId) ev.correlation_id = truncate(String(correlationId), SEE_MAX_CORRELATION);
|
|
210
|
+
const causedBy = findCausedBy(problem);
|
|
211
|
+
if (causedBy) ev.caused_by = causedBy;
|
|
166
212
|
const cleanExtras = sanitizeExtras(extras);
|
|
167
213
|
if (cleanExtras) ev.extras = cleanExtras;
|
|
168
214
|
if (ctx.url) ev.url = truncate(ctx.url, SEE_MAX_SUBJECT);
|
|
169
215
|
if (ctx.userId) ev.user_id = ctx.userId;
|
|
170
216
|
if (ctx.anonId) ev.anonymous_id = ctx.anonId;
|
|
171
217
|
if (ctx.env) ev.env = ctx.env;
|
|
218
|
+
markReported(problem, ev);
|
|
172
219
|
return ev;
|
|
173
220
|
}
|
|
174
221
|
function safeString(v) {
|
|
@@ -191,7 +238,9 @@ function startSeeChain(getProblem, dispatch) {
|
|
|
191
238
|
flushed = true;
|
|
192
239
|
dispatch(
|
|
193
240
|
getProblem(),
|
|
194
|
-
|
|
241
|
+
// Bare noun phrase — titles render as "… causes the {subject} …", so a
|
|
242
|
+
// leading article would double up ("causes the the app").
|
|
243
|
+
causesThe(subject ?? "app").to(outcome ?? "hit an error"),
|
|
195
244
|
collected
|
|
196
245
|
);
|
|
197
246
|
});
|
|
@@ -567,11 +616,12 @@ var FlagsClient = class {
|
|
|
567
616
|
*/
|
|
568
617
|
reportError(problem, consequence, extras, kind) {
|
|
569
618
|
try {
|
|
619
|
+
const correlationId = seeContext.getStore()?.correlationId;
|
|
570
620
|
const ev = buildSeeEvent(problem, consequence, extras, {
|
|
571
621
|
side: "server",
|
|
572
622
|
sdkVersion: version,
|
|
573
623
|
env: this.env
|
|
574
|
-
}, kind);
|
|
624
|
+
}, kind, correlationId);
|
|
575
625
|
if (!this.seeLimiter.shouldSend(ev)) return;
|
|
576
626
|
globalThis.fetch(`${this.baseUrl}/collect`, {
|
|
577
627
|
method: "POST",
|
|
@@ -666,6 +716,8 @@ Object.defineProperty(globalThis, _EDIT_MODE_SSR_SYM, {
|
|
|
666
716
|
},
|
|
667
717
|
configurable: true
|
|
668
718
|
});
|
|
719
|
+
var _SEE_CORR_ALS_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-correlation-als");
|
|
720
|
+
var seeContext = globalThis[_SEE_CORR_ALS_SYM] ?? (globalThis[_SEE_CORR_ALS_SYM] = new AsyncLocalStorage());
|
|
669
721
|
var i18n = {
|
|
670
722
|
/**
|
|
671
723
|
* Fetch translation labels for the current request and store them in an
|
|
@@ -912,7 +964,9 @@ export {
|
|
|
912
964
|
getBootstrapHtml,
|
|
913
965
|
getShipeasyServerClient,
|
|
914
966
|
i18n,
|
|
967
|
+
isExpected,
|
|
915
968
|
see,
|
|
969
|
+
seeContext,
|
|
916
970
|
shipeasy,
|
|
917
971
|
version
|
|
918
972
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipeasy/sdk",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.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",
|