@shipeasy/sdk 3.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/README.md +52 -0
- package/dist/client/index.d.mts +171 -7
- package/dist/client/index.d.ts +171 -7
- package/dist/client/index.js +454 -50
- package/dist/client/index.mjs +453 -50
- package/dist/server/index.d.mts +134 -2
- package/dist/server/index.d.ts +134 -2
- package/dist/server/index.js +278 -1
- package/dist/server/index.mjs +277 -1
- package/package.json +1 -1
package/dist/client/index.js
CHANGED
|
@@ -38,6 +38,7 @@ __export(client_exports, {
|
|
|
38
38
|
readConfigOverride: () => readConfigOverride,
|
|
39
39
|
readExpOverride: () => readExpOverride,
|
|
40
40
|
readGateOverride: () => readGateOverride,
|
|
41
|
+
see: () => see,
|
|
41
42
|
shipeasy: () => shipeasy,
|
|
42
43
|
version: () => version
|
|
43
44
|
});
|
|
@@ -102,8 +103,251 @@ function send(url) {
|
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
// src/see/core.ts
|
|
107
|
+
var SEE_MAX_MESSAGE = 500;
|
|
108
|
+
var SEE_MAX_STACK = 8e3;
|
|
109
|
+
var SEE_MAX_SUBJECT = 200;
|
|
110
|
+
var SEE_MAX_EXTRA_VALUE = 200;
|
|
111
|
+
var SEE_MAX_EXTRA_KEYS = 20;
|
|
112
|
+
var SEE_DEDUP_WINDOW_MS = 3e4;
|
|
113
|
+
var SEE_MAX_PER_SESSION = 25;
|
|
114
|
+
function causesThe(subject) {
|
|
115
|
+
return {
|
|
116
|
+
to(outcome) {
|
|
117
|
+
return {
|
|
118
|
+
__seConsequence: true,
|
|
119
|
+
subject: truncate(String(subject), SEE_MAX_SUBJECT),
|
|
120
|
+
outcome: truncate(String(outcome), SEE_MAX_SUBJECT)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function violation(name) {
|
|
126
|
+
const make = (msg) => ({
|
|
127
|
+
__seViolation: true,
|
|
128
|
+
violationName: String(name),
|
|
129
|
+
...msg !== void 0 ? { violationMessage: msg } : {},
|
|
130
|
+
message(m) {
|
|
131
|
+
return make(String(m));
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return make();
|
|
135
|
+
}
|
|
136
|
+
function isViolation(p) {
|
|
137
|
+
return typeof p === "object" && p !== null && p.__seViolation === true;
|
|
138
|
+
}
|
|
139
|
+
var EXPECTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-expected");
|
|
140
|
+
function markExpected(err, because) {
|
|
141
|
+
if (typeof err !== "object" || err === null) return;
|
|
142
|
+
try {
|
|
143
|
+
Object.defineProperty(err, EXPECTED_SYM, {
|
|
144
|
+
value: String(because),
|
|
145
|
+
enumerable: false,
|
|
146
|
+
configurable: true
|
|
147
|
+
});
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function isExpected(err) {
|
|
152
|
+
if (typeof err !== "object" || err === null) return false;
|
|
153
|
+
return err[EXPECTED_SYM] !== void 0;
|
|
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
|
+
}
|
|
193
|
+
function truncate(s, max) {
|
|
194
|
+
return s.length > max ? s.slice(0, max) : s;
|
|
195
|
+
}
|
|
196
|
+
function sanitizeExtras(extras) {
|
|
197
|
+
if (!extras || typeof extras !== "object") return void 0;
|
|
198
|
+
const out = {};
|
|
199
|
+
let n = 0;
|
|
200
|
+
for (const [k, v] of Object.entries(extras)) {
|
|
201
|
+
if (v === null || v === void 0) continue;
|
|
202
|
+
if (n >= SEE_MAX_EXTRA_KEYS) break;
|
|
203
|
+
if (typeof v === "string") out[k] = truncate(v, SEE_MAX_EXTRA_VALUE);
|
|
204
|
+
else if (typeof v === "number" && Number.isFinite(v)) out[k] = v;
|
|
205
|
+
else if (typeof v === "boolean") out[k] = v;
|
|
206
|
+
else continue;
|
|
207
|
+
n += 1;
|
|
208
|
+
}
|
|
209
|
+
return n > 0 ? out : void 0;
|
|
210
|
+
}
|
|
211
|
+
function captureCallsiteStack() {
|
|
212
|
+
const raw = new Error().stack;
|
|
213
|
+
if (!raw) return void 0;
|
|
214
|
+
const lines = raw.split("\n");
|
|
215
|
+
const kept = lines.slice(1).filter((l) => !/@shipeasy[\\/]sdk|see[\\/]core|captureCallsiteStack|\bsee\b\s*\(/.test(l));
|
|
216
|
+
return kept.length ? kept.join("\n") : void 0;
|
|
217
|
+
}
|
|
218
|
+
function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
|
|
219
|
+
let errorType;
|
|
220
|
+
let message;
|
|
221
|
+
let stack;
|
|
222
|
+
let kind;
|
|
223
|
+
if (isViolation(problem)) {
|
|
224
|
+
errorType = problem.violationName;
|
|
225
|
+
message = problem.violationMessage ?? problem.violationName;
|
|
226
|
+
stack = captureCallsiteStack();
|
|
227
|
+
kind = kindOverride ?? "violation";
|
|
228
|
+
} else if (problem instanceof Error) {
|
|
229
|
+
errorType = problem.name || "Error";
|
|
230
|
+
message = problem.message || String(problem);
|
|
231
|
+
stack = problem.stack ?? void 0;
|
|
232
|
+
kind = kindOverride ?? "caught";
|
|
233
|
+
} else {
|
|
234
|
+
errorType = "Error";
|
|
235
|
+
message = typeof problem === "string" ? problem : safeString(problem);
|
|
236
|
+
stack = captureCallsiteStack();
|
|
237
|
+
kind = kindOverride ?? "caught";
|
|
238
|
+
}
|
|
239
|
+
const ev = {
|
|
240
|
+
type: "error",
|
|
241
|
+
kind,
|
|
242
|
+
error_type: truncate(errorType, SEE_MAX_SUBJECT),
|
|
243
|
+
message: truncate(message, SEE_MAX_MESSAGE),
|
|
244
|
+
subject: consequence.subject,
|
|
245
|
+
outcome: consequence.outcome,
|
|
246
|
+
side: ctx.side,
|
|
247
|
+
sdk_version: ctx.sdkVersion,
|
|
248
|
+
ts: Date.now()
|
|
249
|
+
};
|
|
250
|
+
if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
|
|
251
|
+
const causedBy = findCausedBy(problem);
|
|
252
|
+
if (causedBy) ev.caused_by = causedBy;
|
|
253
|
+
const cleanExtras = sanitizeExtras(extras);
|
|
254
|
+
if (cleanExtras) ev.extras = cleanExtras;
|
|
255
|
+
if (ctx.url) ev.url = truncate(ctx.url, SEE_MAX_SUBJECT);
|
|
256
|
+
if (ctx.userId) ev.user_id = ctx.userId;
|
|
257
|
+
if (ctx.anonId) ev.anonymous_id = ctx.anonId;
|
|
258
|
+
if (ctx.env) ev.env = ctx.env;
|
|
259
|
+
markReported(problem, ev);
|
|
260
|
+
return ev;
|
|
261
|
+
}
|
|
262
|
+
function safeString(v) {
|
|
263
|
+
try {
|
|
264
|
+
return typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
265
|
+
} catch {
|
|
266
|
+
return String(v);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
var scheduleMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : (cb) => {
|
|
270
|
+
void Promise.resolve().then(cb);
|
|
271
|
+
};
|
|
272
|
+
function startSeeChain(getProblem, dispatch) {
|
|
273
|
+
let subject;
|
|
274
|
+
let outcome;
|
|
275
|
+
let collected;
|
|
276
|
+
let flushed = false;
|
|
277
|
+
scheduleMicrotask(() => {
|
|
278
|
+
if (flushed) return;
|
|
279
|
+
flushed = true;
|
|
280
|
+
dispatch(
|
|
281
|
+
getProblem(),
|
|
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"),
|
|
285
|
+
collected
|
|
286
|
+
);
|
|
287
|
+
});
|
|
288
|
+
const tail = {
|
|
289
|
+
extras(x) {
|
|
290
|
+
if (x && typeof x === "object") collected = { ...collected, ...x };
|
|
291
|
+
return tail;
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
const step = {
|
|
295
|
+
to(o) {
|
|
296
|
+
outcome = String(o);
|
|
297
|
+
return tail;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
const start = (s) => {
|
|
301
|
+
subject = String(s);
|
|
302
|
+
return step;
|
|
303
|
+
};
|
|
304
|
+
return { causes_the: start, causesThe: start };
|
|
305
|
+
}
|
|
306
|
+
function startSeeViolationChain(name, dispatch) {
|
|
307
|
+
let msg;
|
|
308
|
+
const base = startSeeChain(
|
|
309
|
+
() => msg !== void 0 ? violation(name).message(msg) : violation(name),
|
|
310
|
+
dispatch
|
|
311
|
+
);
|
|
312
|
+
const chain = {
|
|
313
|
+
...base,
|
|
314
|
+
message(m) {
|
|
315
|
+
msg = String(m);
|
|
316
|
+
return chain;
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
return chain;
|
|
320
|
+
}
|
|
321
|
+
function topStackLine(stack) {
|
|
322
|
+
if (!stack) return "";
|
|
323
|
+
for (const line of stack.split("\n")) {
|
|
324
|
+
if (/^\s*at |@|:\d+:\d+/.test(line)) return line.trim().slice(0, 200);
|
|
325
|
+
}
|
|
326
|
+
return "";
|
|
327
|
+
}
|
|
328
|
+
var SeeLimiter = class {
|
|
329
|
+
constructor(maxPerSession = SEE_MAX_PER_SESSION, dedupWindowMs = SEE_DEDUP_WINDOW_MS) {
|
|
330
|
+
this.maxPerSession = maxPerSession;
|
|
331
|
+
this.dedupWindowMs = dedupWindowMs;
|
|
332
|
+
}
|
|
333
|
+
maxPerSession;
|
|
334
|
+
dedupWindowMs;
|
|
335
|
+
lastSent = /* @__PURE__ */ new Map();
|
|
336
|
+
sent = 0;
|
|
337
|
+
shouldSend(ev) {
|
|
338
|
+
if (this.sent >= this.maxPerSession) return false;
|
|
339
|
+
const key = `${ev.kind}|${ev.error_type}|${ev.message.slice(0, 200)}|${topStackLine(ev.stack)}`;
|
|
340
|
+
const now = Date.now();
|
|
341
|
+
const prev = this.lastSent.get(key);
|
|
342
|
+
if (prev !== void 0 && now - prev < this.dedupWindowMs) return false;
|
|
343
|
+
this.lastSent.set(key, now);
|
|
344
|
+
this.sent += 1;
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
105
349
|
// src/client/index.ts
|
|
106
|
-
var version = "
|
|
350
|
+
var version = "4.0.0";
|
|
107
351
|
var FLUSH_INTERVAL_MS = 5e3;
|
|
108
352
|
var MAX_BUFFER = 100;
|
|
109
353
|
var ANON_ID_KEY = "__se_anon_id";
|
|
@@ -137,6 +381,13 @@ var EventBuffer = class {
|
|
|
137
381
|
this.timer = null;
|
|
138
382
|
}
|
|
139
383
|
}
|
|
384
|
+
/** True once this visitor has been exposed to ≥1 experiment (this tab or a
|
|
385
|
+
* prior page in the session — the dedup set persists in sessionStorage).
|
|
386
|
+
* Gates auto-metric emission: vitals from non-participants are never read
|
|
387
|
+
* by the analysis pipeline and would be pure AE write cost (see cost.md). */
|
|
388
|
+
hasExposures() {
|
|
389
|
+
return this.exposureSeen.size > 0;
|
|
390
|
+
}
|
|
140
391
|
pushExposure(experiment, group, userId, anonId) {
|
|
141
392
|
const key = `${userId || anonId}:${experiment}`;
|
|
142
393
|
if (this.exposureSeen.has(key)) return;
|
|
@@ -202,16 +453,29 @@ var EventBuffer = class {
|
|
|
202
453
|
flush(useBeacon = false) {
|
|
203
454
|
if (!this.queue.length) return;
|
|
204
455
|
const batch = this.queue.splice(0);
|
|
205
|
-
|
|
456
|
+
this.send(batch, useBeacon);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Bypass the 5s queue and ship events immediately — used by see() error
|
|
460
|
+
* reporting so occurrences land near-real-time and survive page unload.
|
|
461
|
+
* Beacon-first (fire-and-forget, unload-safe), keepalive fetch fallback.
|
|
462
|
+
*/
|
|
463
|
+
sendNow(events) {
|
|
464
|
+
this.send(events, true);
|
|
465
|
+
}
|
|
466
|
+
send(batch, useBeacon) {
|
|
206
467
|
if (useBeacon && typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
207
468
|
const beaconBody = JSON.stringify({ k: this.sdkKey, events: batch });
|
|
208
|
-
|
|
209
|
-
|
|
469
|
+
try {
|
|
470
|
+
if (navigator.sendBeacon(this.collectUrl, new Blob([beaconBody], { type: "text/plain" })))
|
|
471
|
+
return;
|
|
472
|
+
} catch {
|
|
473
|
+
}
|
|
210
474
|
}
|
|
211
475
|
fetch(this.collectUrl, {
|
|
212
476
|
method: "POST",
|
|
213
477
|
headers: { "X-SDK-Key": this.sdkKey, "Content-Type": "application/json" },
|
|
214
|
-
body,
|
|
478
|
+
body: JSON.stringify({ events: batch }),
|
|
215
479
|
keepalive: true
|
|
216
480
|
}).catch(() => {
|
|
217
481
|
});
|
|
@@ -228,14 +492,24 @@ var EventBuffer = class {
|
|
|
228
492
|
});
|
|
229
493
|
}
|
|
230
494
|
};
|
|
231
|
-
|
|
232
|
-
|
|
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
|
+
}
|
|
507
|
+
function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignoreUrlPrefixes, always = false) {
|
|
233
508
|
if (typeof window === "undefined" || typeof PerformanceObserver === "undefined") return;
|
|
509
|
+
const shouldEmit = () => always || buffer.hasExposures();
|
|
234
510
|
let lcp = null;
|
|
235
511
|
let inp = null;
|
|
236
512
|
let clsBad = false;
|
|
237
|
-
let jsErrorCount = 0;
|
|
238
|
-
let netErrorCount = 0;
|
|
239
513
|
let navTimingFlushed = false;
|
|
240
514
|
if (groups.vitals) {
|
|
241
515
|
try {
|
|
@@ -274,68 +548,71 @@ function installAutoGuardrails(buffer, userId, anonId, groups) {
|
|
|
274
548
|
if (groups.errors) {
|
|
275
549
|
const origOnError = window.onerror;
|
|
276
550
|
window.onerror = (msg, source, lineno, _colno, err) => {
|
|
277
|
-
if (
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
551
|
+
if (!isExpected(err)) {
|
|
552
|
+
const problem = err ?? (typeof msg === "string" && msg ? msg : "Unknown error");
|
|
553
|
+
reportSee(
|
|
554
|
+
problem,
|
|
555
|
+
causesThe("page").to("hit an unhandled error"),
|
|
556
|
+
{
|
|
557
|
+
source: typeof source === "string" ? source : void 0,
|
|
558
|
+
line: lineno ?? void 0
|
|
559
|
+
},
|
|
560
|
+
"uncaught"
|
|
561
|
+
);
|
|
286
562
|
}
|
|
287
563
|
if (typeof origOnError === "function") return origOnError(msg, source, lineno, _colno, err);
|
|
288
564
|
return false;
|
|
289
565
|
};
|
|
290
566
|
window.addEventListener("unhandledrejection", (e) => {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
});
|
|
300
|
-
}
|
|
567
|
+
const reason = e.reason;
|
|
568
|
+
if (isExpected(reason)) return;
|
|
569
|
+
reportSee(
|
|
570
|
+
reason ?? "Unhandled promise rejection",
|
|
571
|
+
causesThe("page").to("hit an unhandled promise rejection"),
|
|
572
|
+
void 0,
|
|
573
|
+
"unhandled_rejection"
|
|
574
|
+
);
|
|
301
575
|
});
|
|
302
576
|
const origFetch = window.fetch;
|
|
303
577
|
window.fetch = async function(...args) {
|
|
304
578
|
const startedAt = typeof performance !== "undefined" ? performance.now() : 0;
|
|
305
579
|
const url = typeof args[0] === "string" ? args[0] : args[0].toString();
|
|
580
|
+
const ignored = ignoreUrlPrefixes.some((p) => p && url.startsWith(p));
|
|
581
|
+
const bareUrl = url.split("?")[0].slice(0, 200);
|
|
306
582
|
let res;
|
|
307
583
|
try {
|
|
308
584
|
res = await origFetch.apply(this, args);
|
|
309
585
|
} catch (err) {
|
|
310
|
-
if (
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
});
|
|
586
|
+
if (!ignored && !isExpected(err)) {
|
|
587
|
+
reportSee(
|
|
588
|
+
violation("NetworkError").message(`request to ${bareUrl} failed`),
|
|
589
|
+
causesThe(`request to ${endpointTemplate(url)}`).to("get no response"),
|
|
590
|
+
{ status: 0, url: url.slice(0, 200) },
|
|
591
|
+
"network"
|
|
592
|
+
);
|
|
318
593
|
}
|
|
319
594
|
throw err;
|
|
320
595
|
}
|
|
321
|
-
if (res.status >= 500
|
|
322
|
-
netErrorCount += 1;
|
|
596
|
+
if (!ignored && res.status >= 500) {
|
|
323
597
|
const elapsed = typeof performance !== "undefined" ? performance.now() - startedAt : 0;
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
status: res.status,
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
});
|
|
598
|
+
reportSee(
|
|
599
|
+
violation("Http5xx").message(`request to ${bareUrl} returned ${res.status}`),
|
|
600
|
+
causesThe(`request to ${endpointTemplate(url)}`).to("fail with a server error"),
|
|
601
|
+
{ status: res.status, url: url.slice(0, 200), duration_ms: Math.round(elapsed) },
|
|
602
|
+
"network"
|
|
603
|
+
);
|
|
331
604
|
}
|
|
332
605
|
return res;
|
|
333
606
|
};
|
|
334
607
|
}
|
|
335
608
|
const flushNavTiming = () => {
|
|
336
609
|
if (navTimingFlushed) return;
|
|
610
|
+
if (!groups.vitals) {
|
|
611
|
+
navTimingFlushed = true;
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (!shouldEmit()) return;
|
|
337
615
|
navTimingFlushed = true;
|
|
338
|
-
if (!groups.vitals) return;
|
|
339
616
|
try {
|
|
340
617
|
const navList = performance.getEntriesByType("navigation");
|
|
341
618
|
const nav = navList[0];
|
|
@@ -370,7 +647,7 @@ function installAutoGuardrails(buffer, userId, anonId, groups) {
|
|
|
370
647
|
};
|
|
371
648
|
if (groups.engagement) {
|
|
372
649
|
try {
|
|
373
|
-
buffer.pushMetric("__auto_session_active", userId, anonId, { value: 1 });
|
|
650
|
+
if (shouldEmit()) buffer.pushMetric("__auto_session_active", userId, anonId, { value: 1 });
|
|
374
651
|
} catch {
|
|
375
652
|
}
|
|
376
653
|
let lastEmit = Date.now();
|
|
@@ -378,6 +655,7 @@ function installAutoGuardrails(buffer, userId, anonId, groups) {
|
|
|
378
655
|
document.addEventListener("visibilitychange", () => {
|
|
379
656
|
if (document.visibilityState !== "visible") return;
|
|
380
657
|
if (Date.now() - lastEmit < SESSION_GAP_MS) return;
|
|
658
|
+
if (!shouldEmit()) return;
|
|
381
659
|
try {
|
|
382
660
|
buffer.pushMetric("__auto_session_active", userId, anonId, { value: 1 });
|
|
383
661
|
lastEmit = Date.now();
|
|
@@ -400,7 +678,7 @@ function installAutoGuardrails(buffer, userId, anonId, groups) {
|
|
|
400
678
|
}
|
|
401
679
|
const flushOnHide = () => {
|
|
402
680
|
flushNavTiming();
|
|
403
|
-
if (groups.vitals) {
|
|
681
|
+
if (groups.vitals && shouldEmit()) {
|
|
404
682
|
if (lcp !== null) buffer.pushMetric("__auto_lcp", userId, anonId, { value: lcp });
|
|
405
683
|
if (inp !== null) buffer.pushMetric("__auto_inp", userId, anonId, { value: inp });
|
|
406
684
|
if (clsBad) buffer.pushMetric("__auto_cls_binary", userId, anonId, { value: 1 });
|
|
@@ -464,6 +742,83 @@ function collectBrowserAttrs() {
|
|
|
464
742
|
}
|
|
465
743
|
return attrs;
|
|
466
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
|
+
}
|
|
467
822
|
function readExperimentOverridesFromUrl() {
|
|
468
823
|
if (typeof window === "undefined") return {};
|
|
469
824
|
const out = {};
|
|
@@ -483,12 +838,14 @@ var FlagsClientBrowser = class {
|
|
|
483
838
|
baseUrl;
|
|
484
839
|
autoGuardrails;
|
|
485
840
|
autoGuardrailGroups;
|
|
841
|
+
autoCollectAlways;
|
|
486
842
|
env;
|
|
487
843
|
evalResult = null;
|
|
488
844
|
anonId;
|
|
489
845
|
userId = "";
|
|
490
846
|
buffer;
|
|
491
847
|
telemetry;
|
|
848
|
+
seeLimiter = new SeeLimiter();
|
|
492
849
|
guardrailsInstalled = false;
|
|
493
850
|
listeners = /* @__PURE__ */ new Set();
|
|
494
851
|
overrideListenerInstalled = false;
|
|
@@ -504,6 +861,7 @@ var FlagsClientBrowser = class {
|
|
|
504
861
|
this.baseUrl = (opts.baseUrl ?? "https://edge.shipeasy.dev").replace(/\/$/, "");
|
|
505
862
|
this.env = opts.env ?? "prod";
|
|
506
863
|
this.autoGuardrails = opts.autoGuardrails !== false;
|
|
864
|
+
this.autoCollectAlways = opts.autoCollectAlways === true;
|
|
507
865
|
const g = opts.autoGuardrailGroups ?? {};
|
|
508
866
|
this.autoGuardrailGroups = {
|
|
509
867
|
vitals: g.vitals ?? this.autoGuardrails,
|
|
@@ -548,10 +906,39 @@ var FlagsClientBrowser = class {
|
|
|
548
906
|
const anyGroupOn = this.autoGuardrailGroups.vitals || this.autoGuardrailGroups.errors || this.autoGuardrailGroups.engagement;
|
|
549
907
|
if (anyGroupOn && !this.guardrailsInstalled) {
|
|
550
908
|
this.guardrailsInstalled = true;
|
|
551
|
-
installAutoGuardrails(
|
|
909
|
+
installAutoGuardrails(
|
|
910
|
+
this.buffer,
|
|
911
|
+
this.userId,
|
|
912
|
+
this.anonId,
|
|
913
|
+
this.autoGuardrailGroups,
|
|
914
|
+
(problem, consequence, extras, kind) => this.reportError(problem, consequence, extras, kind),
|
|
915
|
+
[`${this.baseUrl}/`, DEFAULT_TELEMETRY_URL],
|
|
916
|
+
this.autoCollectAlways
|
|
917
|
+
);
|
|
552
918
|
}
|
|
553
919
|
this.notify();
|
|
554
920
|
}
|
|
921
|
+
/**
|
|
922
|
+
* Report a structured error into the errors primitive. Flushes immediately
|
|
923
|
+
* (beacon-first) — error occurrences are near-real-time, never queued behind
|
|
924
|
+
* the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
|
|
925
|
+
*/
|
|
926
|
+
reportError(problem, consequence, extras, kind) {
|
|
927
|
+
try {
|
|
928
|
+
const enriched = { ...collectSeeEnv(), ...extras };
|
|
929
|
+
const ev = buildSeeEvent(problem, consequence, enriched, {
|
|
930
|
+
side: "client",
|
|
931
|
+
sdkVersion: version,
|
|
932
|
+
env: this.env,
|
|
933
|
+
url: typeof window !== "undefined" && window.location ? window.location.href : void 0,
|
|
934
|
+
userId: this.userId || void 0,
|
|
935
|
+
anonId: this.anonId
|
|
936
|
+
}, kind);
|
|
937
|
+
if (!this.seeLimiter.shouldSend(ev)) return;
|
|
938
|
+
this.buffer.sendNow([ev]);
|
|
939
|
+
} catch {
|
|
940
|
+
}
|
|
941
|
+
}
|
|
555
942
|
get ready() {
|
|
556
943
|
return this.evalResult !== null;
|
|
557
944
|
}
|
|
@@ -783,13 +1170,15 @@ var _client = null;
|
|
|
783
1170
|
function shipeasy(opts) {
|
|
784
1171
|
const ac = opts.autoCollect;
|
|
785
1172
|
const blanket = ac === false ? false : true;
|
|
786
|
-
const
|
|
1173
|
+
const acObj = ac && typeof ac === "object" ? ac : void 0;
|
|
1174
|
+
const groups = acObj ? { vitals: acObj.vitals, errors: acObj.errors, engagement: acObj.engagement } : void 0;
|
|
787
1175
|
const baseUrl = opts.baseUrl ?? "https://cdn.shipeasy.ai";
|
|
788
1176
|
const client = configureShipeasy({
|
|
789
1177
|
sdkKey: opts.clientKey,
|
|
790
1178
|
baseUrl,
|
|
791
1179
|
autoGuardrails: blanket,
|
|
792
1180
|
autoGuardrailGroups: groups,
|
|
1181
|
+
autoCollectAlways: acObj?.always === true,
|
|
793
1182
|
disableTelemetry: opts.disableTelemetry
|
|
794
1183
|
});
|
|
795
1184
|
injectI18nLoader(opts.clientKey, baseUrl, opts.i18nProfile);
|
|
@@ -951,6 +1340,20 @@ var flags = {
|
|
|
951
1340
|
return _client?.ready ?? false;
|
|
952
1341
|
}
|
|
953
1342
|
};
|
|
1343
|
+
function dispatchSee(problem, consequence, extras, kind) {
|
|
1344
|
+
if (!_client) {
|
|
1345
|
+
console.warn("[shipeasy] see() called before shipeasy({ clientKey }) \u2014 error dropped");
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
_client.reportError(problem, consequence, extras, kind);
|
|
1349
|
+
}
|
|
1350
|
+
var see = Object.assign(
|
|
1351
|
+
(problem) => startSeeChain(() => problem, dispatchSee),
|
|
1352
|
+
{
|
|
1353
|
+
Violation: (name) => startSeeViolationChain(name, dispatchSee),
|
|
1354
|
+
ControlFlowException: markExpected
|
|
1355
|
+
}
|
|
1356
|
+
);
|
|
954
1357
|
var LABEL_MARKER_START = "\uFFF9";
|
|
955
1358
|
var LABEL_MARKER_SEP = "\uFFFA";
|
|
956
1359
|
var LABEL_MARKER_END = "\uFFFB";
|
|
@@ -1203,6 +1606,7 @@ var i18n = {
|
|
|
1203
1606
|
readConfigOverride,
|
|
1204
1607
|
readExpOverride,
|
|
1205
1608
|
readGateOverride,
|
|
1609
|
+
see,
|
|
1206
1610
|
shipeasy,
|
|
1207
1611
|
version
|
|
1208
1612
|
});
|