@shipeasy/sdk 4.1.0 → 4.2.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 +22 -0
- package/dist/client/index.d.ts +22 -0
- package/dist/client/index.js +139 -6
- package/dist/client/index.mjs +139 -6
- package/dist/server/index.d.mts +22 -0
- package/dist/server/index.d.ts +22 -0
- package/dist/server/index.js +44 -1
- package/dist/server/index.mjs +44 -1
- 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,14 @@ interface SeeErrorEvent {
|
|
|
37
51
|
env?: string;
|
|
38
52
|
sdk_version: string;
|
|
39
53
|
ts: number;
|
|
54
|
+
/**
|
|
55
|
+
* The earlier reported problem this occurrence descends from — present when
|
|
56
|
+
* the same error was caught + reported at an inner boundary and then
|
|
57
|
+
* re-thrown (or wrapped via `{ cause }`) and reported again at an outer one.
|
|
58
|
+
* Lets the backend stitch the two issues into a cause chain instead of
|
|
59
|
+
* double-counting them as unrelated.
|
|
60
|
+
*/
|
|
61
|
+
caused_by?: SeeCausedBy;
|
|
40
62
|
}
|
|
41
63
|
interface SeeExtrasTail {
|
|
42
64
|
/** Attach debugging metadata. Callable repeatedly — keys merge, later wins. */
|
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,14 @@ interface SeeErrorEvent {
|
|
|
37
51
|
env?: string;
|
|
38
52
|
sdk_version: string;
|
|
39
53
|
ts: number;
|
|
54
|
+
/**
|
|
55
|
+
* The earlier reported problem this occurrence descends from — present when
|
|
56
|
+
* the same error was caught + reported at an inner boundary and then
|
|
57
|
+
* re-thrown (or wrapped via `{ cause }`) and reported again at an outer one.
|
|
58
|
+
* Lets the backend stitch the two issues into a cause chain instead of
|
|
59
|
+
* double-counting them as unrelated.
|
|
60
|
+
*/
|
|
61
|
+
caused_by?: SeeCausedBy;
|
|
40
62
|
}
|
|
41
63
|
interface SeeExtrasTail {
|
|
42
64
|
/** Attach debugging metadata. Callable repeatedly — keys merge, later wins. */
|
package/dist/client/index.js
CHANGED
|
@@ -152,6 +152,44 @@ function isExpected(err) {
|
|
|
152
152
|
if (typeof err !== "object" || err === null) return false;
|
|
153
153
|
return err[EXPECTED_SYM] !== void 0;
|
|
154
154
|
}
|
|
155
|
+
var REPORTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-reported");
|
|
156
|
+
var SEE_MAX_CAUSE_DEPTH = 8;
|
|
157
|
+
function readReportStamp(err) {
|
|
158
|
+
if (typeof err !== "object" || err === null) return void 0;
|
|
159
|
+
const v = err[REPORTED_SYM];
|
|
160
|
+
return v !== void 0 && v !== null && typeof v === "object" ? v : void 0;
|
|
161
|
+
}
|
|
162
|
+
function findCausedBy(problem) {
|
|
163
|
+
let cur = problem;
|
|
164
|
+
const seen = /* @__PURE__ */ new Set();
|
|
165
|
+
for (let depth = 0; depth < SEE_MAX_CAUSE_DEPTH; depth++) {
|
|
166
|
+
if (typeof cur !== "object" || cur === null || seen.has(cur)) break;
|
|
167
|
+
seen.add(cur);
|
|
168
|
+
const stamp = readReportStamp(cur);
|
|
169
|
+
if (stamp) return stamp;
|
|
170
|
+
cur = cur.cause;
|
|
171
|
+
}
|
|
172
|
+
return void 0;
|
|
173
|
+
}
|
|
174
|
+
function markReported(problem, ev) {
|
|
175
|
+
if (!(problem instanceof Error)) return;
|
|
176
|
+
const stamp = {
|
|
177
|
+
error_type: ev.error_type,
|
|
178
|
+
message: ev.message,
|
|
179
|
+
subject: ev.subject,
|
|
180
|
+
outcome: ev.outcome
|
|
181
|
+
};
|
|
182
|
+
if (ev.stack !== void 0) stamp.stack = ev.stack;
|
|
183
|
+
try {
|
|
184
|
+
Object.defineProperty(problem, REPORTED_SYM, {
|
|
185
|
+
value: Object.freeze(stamp),
|
|
186
|
+
enumerable: false,
|
|
187
|
+
configurable: true,
|
|
188
|
+
writable: true
|
|
189
|
+
});
|
|
190
|
+
} catch {
|
|
191
|
+
}
|
|
192
|
+
}
|
|
155
193
|
function truncate(s, max) {
|
|
156
194
|
return s.length > max ? s.slice(0, max) : s;
|
|
157
195
|
}
|
|
@@ -210,12 +248,15 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
|
210
248
|
ts: Date.now()
|
|
211
249
|
};
|
|
212
250
|
if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
|
|
251
|
+
const causedBy = findCausedBy(problem);
|
|
252
|
+
if (causedBy) ev.caused_by = causedBy;
|
|
213
253
|
const cleanExtras = sanitizeExtras(extras);
|
|
214
254
|
if (cleanExtras) ev.extras = cleanExtras;
|
|
215
255
|
if (ctx.url) ev.url = truncate(ctx.url, SEE_MAX_SUBJECT);
|
|
216
256
|
if (ctx.userId) ev.user_id = ctx.userId;
|
|
217
257
|
if (ctx.anonId) ev.anonymous_id = ctx.anonId;
|
|
218
258
|
if (ctx.env) ev.env = ctx.env;
|
|
259
|
+
markReported(problem, ev);
|
|
219
260
|
return ev;
|
|
220
261
|
}
|
|
221
262
|
function safeString(v) {
|
|
@@ -238,7 +279,9 @@ function startSeeChain(getProblem, dispatch) {
|
|
|
238
279
|
flushed = true;
|
|
239
280
|
dispatch(
|
|
240
281
|
getProblem(),
|
|
241
|
-
|
|
282
|
+
// Bare noun phrase — titles render as "… causes the {subject} …", so a
|
|
283
|
+
// leading article would double up ("causes the the app").
|
|
284
|
+
causesThe(subject ?? "app").to(outcome ?? "hit an error"),
|
|
242
285
|
collected
|
|
243
286
|
);
|
|
244
287
|
});
|
|
@@ -449,6 +492,18 @@ var EventBuffer = class {
|
|
|
449
492
|
});
|
|
450
493
|
}
|
|
451
494
|
};
|
|
495
|
+
function endpointTemplate(rawUrl) {
|
|
496
|
+
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);
|
|
497
|
+
let u;
|
|
498
|
+
try {
|
|
499
|
+
u = new URL(rawUrl, typeof location !== "undefined" ? location.href : void 0);
|
|
500
|
+
} catch {
|
|
501
|
+
return (rawUrl.split(/[?#]/)[0] ?? "").slice(0, 120);
|
|
502
|
+
}
|
|
503
|
+
const path = u.pathname.split("/").map((seg) => seg && isIdSegment(seg) ? ":id" : seg).join("/");
|
|
504
|
+
const sameOrigin = typeof location !== "undefined" && u.origin === location.origin;
|
|
505
|
+
return ((sameOrigin ? "" : u.host) + path).slice(0, 120);
|
|
506
|
+
}
|
|
452
507
|
function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignoreUrlPrefixes, always = false) {
|
|
453
508
|
if (typeof window === "undefined" || typeof PerformanceObserver === "undefined") return;
|
|
454
509
|
const shouldEmit = () => always || buffer.hasExposures();
|
|
@@ -497,7 +552,7 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
497
552
|
const problem = err ?? (typeof msg === "string" && msg ? msg : "Unknown error");
|
|
498
553
|
reportSee(
|
|
499
554
|
problem,
|
|
500
|
-
causesThe("
|
|
555
|
+
causesThe("page").to("hit an unhandled error"),
|
|
501
556
|
{
|
|
502
557
|
source: typeof source === "string" ? source : void 0,
|
|
503
558
|
line: lineno ?? void 0
|
|
@@ -513,7 +568,7 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
513
568
|
if (isExpected(reason)) return;
|
|
514
569
|
reportSee(
|
|
515
570
|
reason ?? "Unhandled promise rejection",
|
|
516
|
-
causesThe("
|
|
571
|
+
causesThe("page").to("hit an unhandled promise rejection"),
|
|
517
572
|
void 0,
|
|
518
573
|
"unhandled_rejection"
|
|
519
574
|
);
|
|
@@ -531,7 +586,7 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
531
586
|
if (!ignored && !isExpected(err)) {
|
|
532
587
|
reportSee(
|
|
533
588
|
violation("NetworkError").message(`request to ${bareUrl} failed`),
|
|
534
|
-
causesThe(
|
|
589
|
+
causesThe(`request to ${endpointTemplate(url)}`).to("get no response"),
|
|
535
590
|
{ status: 0, url: url.slice(0, 200) },
|
|
536
591
|
"network"
|
|
537
592
|
);
|
|
@@ -542,7 +597,7 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
542
597
|
const elapsed = typeof performance !== "undefined" ? performance.now() - startedAt : 0;
|
|
543
598
|
reportSee(
|
|
544
599
|
violation("Http5xx").message(`request to ${bareUrl} returned ${res.status}`),
|
|
545
|
-
causesThe(
|
|
600
|
+
causesThe(`request to ${endpointTemplate(url)}`).to("fail with a server error"),
|
|
546
601
|
{ status: res.status, url: url.slice(0, 200), duration_ms: Math.round(elapsed) },
|
|
547
602
|
"network"
|
|
548
603
|
);
|
|
@@ -687,6 +742,83 @@ function collectBrowserAttrs() {
|
|
|
687
742
|
}
|
|
688
743
|
return attrs;
|
|
689
744
|
}
|
|
745
|
+
function collectSeeEnv() {
|
|
746
|
+
const out = {};
|
|
747
|
+
if (typeof navigator === "undefined") return out;
|
|
748
|
+
const nav = navigator;
|
|
749
|
+
const ua = typeof nav.userAgent === "string" ? nav.userAgent : "";
|
|
750
|
+
const browser = parseUaBrowser(ua);
|
|
751
|
+
if (browser) out["env.browser"] = browser;
|
|
752
|
+
const os = parseUaOs(ua) ?? nav.userAgentData?.platform;
|
|
753
|
+
if (os) out["env.os"] = os;
|
|
754
|
+
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";
|
|
755
|
+
try {
|
|
756
|
+
if (nav.language) out["env.lang"] = nav.language;
|
|
757
|
+
} catch {
|
|
758
|
+
}
|
|
759
|
+
try {
|
|
760
|
+
if (typeof nav.onLine === "boolean") out["env.online"] = nav.onLine;
|
|
761
|
+
} catch {
|
|
762
|
+
}
|
|
763
|
+
try {
|
|
764
|
+
if (typeof nav.hardwareConcurrency === "number") out["env.cores"] = nav.hardwareConcurrency;
|
|
765
|
+
} catch {
|
|
766
|
+
}
|
|
767
|
+
try {
|
|
768
|
+
if (typeof nav.deviceMemory === "number") out["env.memory_gb"] = nav.deviceMemory;
|
|
769
|
+
} catch {
|
|
770
|
+
}
|
|
771
|
+
try {
|
|
772
|
+
const et = nav.connection?.effectiveType;
|
|
773
|
+
if (et) out["env.connection"] = et;
|
|
774
|
+
} catch {
|
|
775
|
+
}
|
|
776
|
+
try {
|
|
777
|
+
if (typeof window !== "undefined" && window.innerWidth && window.innerHeight) {
|
|
778
|
+
out["env.viewport"] = `${window.innerWidth}\xD7${window.innerHeight}`;
|
|
779
|
+
}
|
|
780
|
+
if (typeof window !== "undefined" && typeof window.devicePixelRatio === "number") {
|
|
781
|
+
out["env.dpr"] = window.devicePixelRatio;
|
|
782
|
+
}
|
|
783
|
+
if (typeof screen !== "undefined" && screen.width && screen.height) {
|
|
784
|
+
out["env.screen"] = `${screen.width}\xD7${screen.height}`;
|
|
785
|
+
}
|
|
786
|
+
} catch {
|
|
787
|
+
}
|
|
788
|
+
try {
|
|
789
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
790
|
+
if (tz) out["env.tz"] = tz;
|
|
791
|
+
} catch {
|
|
792
|
+
}
|
|
793
|
+
return out;
|
|
794
|
+
}
|
|
795
|
+
function parseUaBrowser(ua) {
|
|
796
|
+
const tests = [
|
|
797
|
+
[/Edg(?:A|iOS)?\/(\d+)/, "Edge"],
|
|
798
|
+
[/(?:OPR|Opera)\/(\d+)/, "Opera"],
|
|
799
|
+
[/(?:Firefox|FxiOS)\/(\d+)/, "Firefox"],
|
|
800
|
+
[/(?:Chrome|CriOS)\/(\d+)/, "Chrome"],
|
|
801
|
+
[/Version\/(\d+)[.\d]* (?:Mobile.*)?Safari/, "Safari"]
|
|
802
|
+
];
|
|
803
|
+
for (const [re, name] of tests) {
|
|
804
|
+
const m = re.exec(ua);
|
|
805
|
+
if (m) return `${name} ${m[1]}`;
|
|
806
|
+
}
|
|
807
|
+
return void 0;
|
|
808
|
+
}
|
|
809
|
+
function parseUaOs(ua) {
|
|
810
|
+
if (/Windows NT 10/.test(ua)) return "Windows 10/11";
|
|
811
|
+
if (/Windows NT/.test(ua)) return "Windows";
|
|
812
|
+
let m = /Mac OS X (\d+)[._](\d+)/.exec(ua);
|
|
813
|
+
if (m) return `macOS ${m[1]}.${m[2]}`;
|
|
814
|
+
if (/Macintosh/.test(ua)) return "macOS";
|
|
815
|
+
m = /Android (\d+)/.exec(ua);
|
|
816
|
+
if (m) return `Android ${m[1]}`;
|
|
817
|
+
m = /(?:iPhone|iPad)[^)]* OS (\d+)/.exec(ua);
|
|
818
|
+
if (m) return `iOS ${m[1]}`;
|
|
819
|
+
if (/Linux/.test(ua)) return "Linux";
|
|
820
|
+
return void 0;
|
|
821
|
+
}
|
|
690
822
|
function readExperimentOverridesFromUrl() {
|
|
691
823
|
if (typeof window === "undefined") return {};
|
|
692
824
|
const out = {};
|
|
@@ -793,7 +925,8 @@ var FlagsClientBrowser = class {
|
|
|
793
925
|
*/
|
|
794
926
|
reportError(problem, consequence, extras, kind) {
|
|
795
927
|
try {
|
|
796
|
-
const
|
|
928
|
+
const enriched = { ...collectSeeEnv(), ...extras };
|
|
929
|
+
const ev = buildSeeEvent(problem, consequence, enriched, {
|
|
797
930
|
side: "client",
|
|
798
931
|
sdkVersion: version,
|
|
799
932
|
env: this.env,
|
package/dist/client/index.mjs
CHANGED
|
@@ -106,6 +106,44 @@ function isExpected(err) {
|
|
|
106
106
|
if (typeof err !== "object" || err === null) return false;
|
|
107
107
|
return err[EXPECTED_SYM] !== void 0;
|
|
108
108
|
}
|
|
109
|
+
var REPORTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-reported");
|
|
110
|
+
var SEE_MAX_CAUSE_DEPTH = 8;
|
|
111
|
+
function readReportStamp(err) {
|
|
112
|
+
if (typeof err !== "object" || err === null) return void 0;
|
|
113
|
+
const v = err[REPORTED_SYM];
|
|
114
|
+
return v !== void 0 && v !== null && typeof v === "object" ? v : void 0;
|
|
115
|
+
}
|
|
116
|
+
function findCausedBy(problem) {
|
|
117
|
+
let cur = problem;
|
|
118
|
+
const seen = /* @__PURE__ */ new Set();
|
|
119
|
+
for (let depth = 0; depth < SEE_MAX_CAUSE_DEPTH; depth++) {
|
|
120
|
+
if (typeof cur !== "object" || cur === null || seen.has(cur)) break;
|
|
121
|
+
seen.add(cur);
|
|
122
|
+
const stamp = readReportStamp(cur);
|
|
123
|
+
if (stamp) return stamp;
|
|
124
|
+
cur = cur.cause;
|
|
125
|
+
}
|
|
126
|
+
return void 0;
|
|
127
|
+
}
|
|
128
|
+
function markReported(problem, ev) {
|
|
129
|
+
if (!(problem instanceof Error)) return;
|
|
130
|
+
const stamp = {
|
|
131
|
+
error_type: ev.error_type,
|
|
132
|
+
message: ev.message,
|
|
133
|
+
subject: ev.subject,
|
|
134
|
+
outcome: ev.outcome
|
|
135
|
+
};
|
|
136
|
+
if (ev.stack !== void 0) stamp.stack = ev.stack;
|
|
137
|
+
try {
|
|
138
|
+
Object.defineProperty(problem, REPORTED_SYM, {
|
|
139
|
+
value: Object.freeze(stamp),
|
|
140
|
+
enumerable: false,
|
|
141
|
+
configurable: true,
|
|
142
|
+
writable: true
|
|
143
|
+
});
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
}
|
|
109
147
|
function truncate(s, max) {
|
|
110
148
|
return s.length > max ? s.slice(0, max) : s;
|
|
111
149
|
}
|
|
@@ -164,12 +202,15 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
|
164
202
|
ts: Date.now()
|
|
165
203
|
};
|
|
166
204
|
if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
|
|
205
|
+
const causedBy = findCausedBy(problem);
|
|
206
|
+
if (causedBy) ev.caused_by = causedBy;
|
|
167
207
|
const cleanExtras = sanitizeExtras(extras);
|
|
168
208
|
if (cleanExtras) ev.extras = cleanExtras;
|
|
169
209
|
if (ctx.url) ev.url = truncate(ctx.url, SEE_MAX_SUBJECT);
|
|
170
210
|
if (ctx.userId) ev.user_id = ctx.userId;
|
|
171
211
|
if (ctx.anonId) ev.anonymous_id = ctx.anonId;
|
|
172
212
|
if (ctx.env) ev.env = ctx.env;
|
|
213
|
+
markReported(problem, ev);
|
|
173
214
|
return ev;
|
|
174
215
|
}
|
|
175
216
|
function safeString(v) {
|
|
@@ -192,7 +233,9 @@ function startSeeChain(getProblem, dispatch) {
|
|
|
192
233
|
flushed = true;
|
|
193
234
|
dispatch(
|
|
194
235
|
getProblem(),
|
|
195
|
-
|
|
236
|
+
// Bare noun phrase — titles render as "… causes the {subject} …", so a
|
|
237
|
+
// leading article would double up ("causes the the app").
|
|
238
|
+
causesThe(subject ?? "app").to(outcome ?? "hit an error"),
|
|
196
239
|
collected
|
|
197
240
|
);
|
|
198
241
|
});
|
|
@@ -403,6 +446,18 @@ var EventBuffer = class {
|
|
|
403
446
|
});
|
|
404
447
|
}
|
|
405
448
|
};
|
|
449
|
+
function endpointTemplate(rawUrl) {
|
|
450
|
+
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);
|
|
451
|
+
let u;
|
|
452
|
+
try {
|
|
453
|
+
u = new URL(rawUrl, typeof location !== "undefined" ? location.href : void 0);
|
|
454
|
+
} catch {
|
|
455
|
+
return (rawUrl.split(/[?#]/)[0] ?? "").slice(0, 120);
|
|
456
|
+
}
|
|
457
|
+
const path = u.pathname.split("/").map((seg) => seg && isIdSegment(seg) ? ":id" : seg).join("/");
|
|
458
|
+
const sameOrigin = typeof location !== "undefined" && u.origin === location.origin;
|
|
459
|
+
return ((sameOrigin ? "" : u.host) + path).slice(0, 120);
|
|
460
|
+
}
|
|
406
461
|
function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignoreUrlPrefixes, always = false) {
|
|
407
462
|
if (typeof window === "undefined" || typeof PerformanceObserver === "undefined") return;
|
|
408
463
|
const shouldEmit = () => always || buffer.hasExposures();
|
|
@@ -451,7 +506,7 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
451
506
|
const problem = err ?? (typeof msg === "string" && msg ? msg : "Unknown error");
|
|
452
507
|
reportSee(
|
|
453
508
|
problem,
|
|
454
|
-
causesThe("
|
|
509
|
+
causesThe("page").to("hit an unhandled error"),
|
|
455
510
|
{
|
|
456
511
|
source: typeof source === "string" ? source : void 0,
|
|
457
512
|
line: lineno ?? void 0
|
|
@@ -467,7 +522,7 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
467
522
|
if (isExpected(reason)) return;
|
|
468
523
|
reportSee(
|
|
469
524
|
reason ?? "Unhandled promise rejection",
|
|
470
|
-
causesThe("
|
|
525
|
+
causesThe("page").to("hit an unhandled promise rejection"),
|
|
471
526
|
void 0,
|
|
472
527
|
"unhandled_rejection"
|
|
473
528
|
);
|
|
@@ -485,7 +540,7 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
485
540
|
if (!ignored && !isExpected(err)) {
|
|
486
541
|
reportSee(
|
|
487
542
|
violation("NetworkError").message(`request to ${bareUrl} failed`),
|
|
488
|
-
causesThe(
|
|
543
|
+
causesThe(`request to ${endpointTemplate(url)}`).to("get no response"),
|
|
489
544
|
{ status: 0, url: url.slice(0, 200) },
|
|
490
545
|
"network"
|
|
491
546
|
);
|
|
@@ -496,7 +551,7 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
|
|
|
496
551
|
const elapsed = typeof performance !== "undefined" ? performance.now() - startedAt : 0;
|
|
497
552
|
reportSee(
|
|
498
553
|
violation("Http5xx").message(`request to ${bareUrl} returned ${res.status}`),
|
|
499
|
-
causesThe(
|
|
554
|
+
causesThe(`request to ${endpointTemplate(url)}`).to("fail with a server error"),
|
|
500
555
|
{ status: res.status, url: url.slice(0, 200), duration_ms: Math.round(elapsed) },
|
|
501
556
|
"network"
|
|
502
557
|
);
|
|
@@ -641,6 +696,83 @@ function collectBrowserAttrs() {
|
|
|
641
696
|
}
|
|
642
697
|
return attrs;
|
|
643
698
|
}
|
|
699
|
+
function collectSeeEnv() {
|
|
700
|
+
const out = {};
|
|
701
|
+
if (typeof navigator === "undefined") return out;
|
|
702
|
+
const nav = navigator;
|
|
703
|
+
const ua = typeof nav.userAgent === "string" ? nav.userAgent : "";
|
|
704
|
+
const browser = parseUaBrowser(ua);
|
|
705
|
+
if (browser) out["env.browser"] = browser;
|
|
706
|
+
const os = parseUaOs(ua) ?? nav.userAgentData?.platform;
|
|
707
|
+
if (os) out["env.os"] = os;
|
|
708
|
+
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";
|
|
709
|
+
try {
|
|
710
|
+
if (nav.language) out["env.lang"] = nav.language;
|
|
711
|
+
} catch {
|
|
712
|
+
}
|
|
713
|
+
try {
|
|
714
|
+
if (typeof nav.onLine === "boolean") out["env.online"] = nav.onLine;
|
|
715
|
+
} catch {
|
|
716
|
+
}
|
|
717
|
+
try {
|
|
718
|
+
if (typeof nav.hardwareConcurrency === "number") out["env.cores"] = nav.hardwareConcurrency;
|
|
719
|
+
} catch {
|
|
720
|
+
}
|
|
721
|
+
try {
|
|
722
|
+
if (typeof nav.deviceMemory === "number") out["env.memory_gb"] = nav.deviceMemory;
|
|
723
|
+
} catch {
|
|
724
|
+
}
|
|
725
|
+
try {
|
|
726
|
+
const et = nav.connection?.effectiveType;
|
|
727
|
+
if (et) out["env.connection"] = et;
|
|
728
|
+
} catch {
|
|
729
|
+
}
|
|
730
|
+
try {
|
|
731
|
+
if (typeof window !== "undefined" && window.innerWidth && window.innerHeight) {
|
|
732
|
+
out["env.viewport"] = `${window.innerWidth}\xD7${window.innerHeight}`;
|
|
733
|
+
}
|
|
734
|
+
if (typeof window !== "undefined" && typeof window.devicePixelRatio === "number") {
|
|
735
|
+
out["env.dpr"] = window.devicePixelRatio;
|
|
736
|
+
}
|
|
737
|
+
if (typeof screen !== "undefined" && screen.width && screen.height) {
|
|
738
|
+
out["env.screen"] = `${screen.width}\xD7${screen.height}`;
|
|
739
|
+
}
|
|
740
|
+
} catch {
|
|
741
|
+
}
|
|
742
|
+
try {
|
|
743
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
744
|
+
if (tz) out["env.tz"] = tz;
|
|
745
|
+
} catch {
|
|
746
|
+
}
|
|
747
|
+
return out;
|
|
748
|
+
}
|
|
749
|
+
function parseUaBrowser(ua) {
|
|
750
|
+
const tests = [
|
|
751
|
+
[/Edg(?:A|iOS)?\/(\d+)/, "Edge"],
|
|
752
|
+
[/(?:OPR|Opera)\/(\d+)/, "Opera"],
|
|
753
|
+
[/(?:Firefox|FxiOS)\/(\d+)/, "Firefox"],
|
|
754
|
+
[/(?:Chrome|CriOS)\/(\d+)/, "Chrome"],
|
|
755
|
+
[/Version\/(\d+)[.\d]* (?:Mobile.*)?Safari/, "Safari"]
|
|
756
|
+
];
|
|
757
|
+
for (const [re, name] of tests) {
|
|
758
|
+
const m = re.exec(ua);
|
|
759
|
+
if (m) return `${name} ${m[1]}`;
|
|
760
|
+
}
|
|
761
|
+
return void 0;
|
|
762
|
+
}
|
|
763
|
+
function parseUaOs(ua) {
|
|
764
|
+
if (/Windows NT 10/.test(ua)) return "Windows 10/11";
|
|
765
|
+
if (/Windows NT/.test(ua)) return "Windows";
|
|
766
|
+
let m = /Mac OS X (\d+)[._](\d+)/.exec(ua);
|
|
767
|
+
if (m) return `macOS ${m[1]}.${m[2]}`;
|
|
768
|
+
if (/Macintosh/.test(ua)) return "macOS";
|
|
769
|
+
m = /Android (\d+)/.exec(ua);
|
|
770
|
+
if (m) return `Android ${m[1]}`;
|
|
771
|
+
m = /(?:iPhone|iPad)[^)]* OS (\d+)/.exec(ua);
|
|
772
|
+
if (m) return `iOS ${m[1]}`;
|
|
773
|
+
if (/Linux/.test(ua)) return "Linux";
|
|
774
|
+
return void 0;
|
|
775
|
+
}
|
|
644
776
|
function readExperimentOverridesFromUrl() {
|
|
645
777
|
if (typeof window === "undefined") return {};
|
|
646
778
|
const out = {};
|
|
@@ -747,7 +879,8 @@ var FlagsClientBrowser = class {
|
|
|
747
879
|
*/
|
|
748
880
|
reportError(problem, consequence, extras, kind) {
|
|
749
881
|
try {
|
|
750
|
-
const
|
|
882
|
+
const enriched = { ...collectSeeEnv(), ...extras };
|
|
883
|
+
const ev = buildSeeEvent(problem, consequence, enriched, {
|
|
751
884
|
side: "client",
|
|
752
885
|
sdkVersion: version,
|
|
753
886
|
env: this.env,
|
package/dist/server/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,14 @@ interface SeeErrorEvent {
|
|
|
37
51
|
env?: string;
|
|
38
52
|
sdk_version: string;
|
|
39
53
|
ts: number;
|
|
54
|
+
/**
|
|
55
|
+
* The earlier reported problem this occurrence descends from — present when
|
|
56
|
+
* the same error was caught + reported at an inner boundary and then
|
|
57
|
+
* re-thrown (or wrapped via `{ cause }`) and reported again at an outer one.
|
|
58
|
+
* Lets the backend stitch the two issues into a cause chain instead of
|
|
59
|
+
* double-counting them as unrelated.
|
|
60
|
+
*/
|
|
61
|
+
caused_by?: SeeCausedBy;
|
|
40
62
|
}
|
|
41
63
|
interface SeeExtrasTail {
|
|
42
64
|
/** Attach debugging metadata. Callable repeatedly — keys merge, later wins. */
|
package/dist/server/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,14 @@ interface SeeErrorEvent {
|
|
|
37
51
|
env?: string;
|
|
38
52
|
sdk_version: string;
|
|
39
53
|
ts: number;
|
|
54
|
+
/**
|
|
55
|
+
* The earlier reported problem this occurrence descends from — present when
|
|
56
|
+
* the same error was caught + reported at an inner boundary and then
|
|
57
|
+
* re-thrown (or wrapped via `{ cause }`) and reported again at an outer one.
|
|
58
|
+
* Lets the backend stitch the two issues into a cause chain instead of
|
|
59
|
+
* double-counting them as unrelated.
|
|
60
|
+
*/
|
|
61
|
+
caused_by?: SeeCausedBy;
|
|
40
62
|
}
|
|
41
63
|
interface SeeExtrasTail {
|
|
42
64
|
/** Attach debugging metadata. Callable repeatedly — keys merge, later wins. */
|
package/dist/server/index.js
CHANGED
|
@@ -149,6 +149,44 @@ function markExpected(err, because) {
|
|
|
149
149
|
} catch {
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
+
var REPORTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-reported");
|
|
153
|
+
var SEE_MAX_CAUSE_DEPTH = 8;
|
|
154
|
+
function readReportStamp(err) {
|
|
155
|
+
if (typeof err !== "object" || err === null) return void 0;
|
|
156
|
+
const v = err[REPORTED_SYM];
|
|
157
|
+
return v !== void 0 && v !== null && typeof v === "object" ? v : void 0;
|
|
158
|
+
}
|
|
159
|
+
function findCausedBy(problem) {
|
|
160
|
+
let cur = problem;
|
|
161
|
+
const seen = /* @__PURE__ */ new Set();
|
|
162
|
+
for (let depth = 0; depth < SEE_MAX_CAUSE_DEPTH; depth++) {
|
|
163
|
+
if (typeof cur !== "object" || cur === null || seen.has(cur)) break;
|
|
164
|
+
seen.add(cur);
|
|
165
|
+
const stamp = readReportStamp(cur);
|
|
166
|
+
if (stamp) return stamp;
|
|
167
|
+
cur = cur.cause;
|
|
168
|
+
}
|
|
169
|
+
return void 0;
|
|
170
|
+
}
|
|
171
|
+
function markReported(problem, ev) {
|
|
172
|
+
if (!(problem instanceof Error)) return;
|
|
173
|
+
const stamp = {
|
|
174
|
+
error_type: ev.error_type,
|
|
175
|
+
message: ev.message,
|
|
176
|
+
subject: ev.subject,
|
|
177
|
+
outcome: ev.outcome
|
|
178
|
+
};
|
|
179
|
+
if (ev.stack !== void 0) stamp.stack = ev.stack;
|
|
180
|
+
try {
|
|
181
|
+
Object.defineProperty(problem, REPORTED_SYM, {
|
|
182
|
+
value: Object.freeze(stamp),
|
|
183
|
+
enumerable: false,
|
|
184
|
+
configurable: true,
|
|
185
|
+
writable: true
|
|
186
|
+
});
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
189
|
+
}
|
|
152
190
|
function truncate(s, max) {
|
|
153
191
|
return s.length > max ? s.slice(0, max) : s;
|
|
154
192
|
}
|
|
@@ -207,12 +245,15 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
|
207
245
|
ts: Date.now()
|
|
208
246
|
};
|
|
209
247
|
if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
|
|
248
|
+
const causedBy = findCausedBy(problem);
|
|
249
|
+
if (causedBy) ev.caused_by = causedBy;
|
|
210
250
|
const cleanExtras = sanitizeExtras(extras);
|
|
211
251
|
if (cleanExtras) ev.extras = cleanExtras;
|
|
212
252
|
if (ctx.url) ev.url = truncate(ctx.url, SEE_MAX_SUBJECT);
|
|
213
253
|
if (ctx.userId) ev.user_id = ctx.userId;
|
|
214
254
|
if (ctx.anonId) ev.anonymous_id = ctx.anonId;
|
|
215
255
|
if (ctx.env) ev.env = ctx.env;
|
|
256
|
+
markReported(problem, ev);
|
|
216
257
|
return ev;
|
|
217
258
|
}
|
|
218
259
|
function safeString(v) {
|
|
@@ -235,7 +276,9 @@ function startSeeChain(getProblem, dispatch) {
|
|
|
235
276
|
flushed = true;
|
|
236
277
|
dispatch(
|
|
237
278
|
getProblem(),
|
|
238
|
-
|
|
279
|
+
// Bare noun phrase — titles render as "… causes the {subject} …", so a
|
|
280
|
+
// leading article would double up ("causes the the app").
|
|
281
|
+
causesThe(subject ?? "app").to(outcome ?? "hit an error"),
|
|
239
282
|
collected
|
|
240
283
|
);
|
|
241
284
|
});
|
package/dist/server/index.mjs
CHANGED
|
@@ -105,6 +105,44 @@ function markExpected(err, because) {
|
|
|
105
105
|
} catch {
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
+
var REPORTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-reported");
|
|
109
|
+
var SEE_MAX_CAUSE_DEPTH = 8;
|
|
110
|
+
function readReportStamp(err) {
|
|
111
|
+
if (typeof err !== "object" || err === null) return void 0;
|
|
112
|
+
const v = err[REPORTED_SYM];
|
|
113
|
+
return v !== void 0 && v !== null && typeof v === "object" ? v : void 0;
|
|
114
|
+
}
|
|
115
|
+
function findCausedBy(problem) {
|
|
116
|
+
let cur = problem;
|
|
117
|
+
const seen = /* @__PURE__ */ new Set();
|
|
118
|
+
for (let depth = 0; depth < SEE_MAX_CAUSE_DEPTH; depth++) {
|
|
119
|
+
if (typeof cur !== "object" || cur === null || seen.has(cur)) break;
|
|
120
|
+
seen.add(cur);
|
|
121
|
+
const stamp = readReportStamp(cur);
|
|
122
|
+
if (stamp) return stamp;
|
|
123
|
+
cur = cur.cause;
|
|
124
|
+
}
|
|
125
|
+
return void 0;
|
|
126
|
+
}
|
|
127
|
+
function markReported(problem, ev) {
|
|
128
|
+
if (!(problem instanceof Error)) return;
|
|
129
|
+
const stamp = {
|
|
130
|
+
error_type: ev.error_type,
|
|
131
|
+
message: ev.message,
|
|
132
|
+
subject: ev.subject,
|
|
133
|
+
outcome: ev.outcome
|
|
134
|
+
};
|
|
135
|
+
if (ev.stack !== void 0) stamp.stack = ev.stack;
|
|
136
|
+
try {
|
|
137
|
+
Object.defineProperty(problem, REPORTED_SYM, {
|
|
138
|
+
value: Object.freeze(stamp),
|
|
139
|
+
enumerable: false,
|
|
140
|
+
configurable: true,
|
|
141
|
+
writable: true
|
|
142
|
+
});
|
|
143
|
+
} catch {
|
|
144
|
+
}
|
|
145
|
+
}
|
|
108
146
|
function truncate(s, max) {
|
|
109
147
|
return s.length > max ? s.slice(0, max) : s;
|
|
110
148
|
}
|
|
@@ -163,12 +201,15 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
|
163
201
|
ts: Date.now()
|
|
164
202
|
};
|
|
165
203
|
if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
|
|
204
|
+
const causedBy = findCausedBy(problem);
|
|
205
|
+
if (causedBy) ev.caused_by = causedBy;
|
|
166
206
|
const cleanExtras = sanitizeExtras(extras);
|
|
167
207
|
if (cleanExtras) ev.extras = cleanExtras;
|
|
168
208
|
if (ctx.url) ev.url = truncate(ctx.url, SEE_MAX_SUBJECT);
|
|
169
209
|
if (ctx.userId) ev.user_id = ctx.userId;
|
|
170
210
|
if (ctx.anonId) ev.anonymous_id = ctx.anonId;
|
|
171
211
|
if (ctx.env) ev.env = ctx.env;
|
|
212
|
+
markReported(problem, ev);
|
|
172
213
|
return ev;
|
|
173
214
|
}
|
|
174
215
|
function safeString(v) {
|
|
@@ -191,7 +232,9 @@ function startSeeChain(getProblem, dispatch) {
|
|
|
191
232
|
flushed = true;
|
|
192
233
|
dispatch(
|
|
193
234
|
getProblem(),
|
|
194
|
-
|
|
235
|
+
// Bare noun phrase — titles render as "… causes the {subject} …", so a
|
|
236
|
+
// leading article would double up ("causes the the app").
|
|
237
|
+
causesThe(subject ?? "app").to(outcome ?? "hit an error"),
|
|
195
238
|
collected
|
|
196
239
|
);
|
|
197
240
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipeasy/sdk",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.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",
|