@mushi-mushi/core 1.27.1 → 1.28.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 +4 -1
- package/dist/index.cjs +282 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -1
- package/dist/index.d.ts +122 -1
- package/dist/index.js +269 -78
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -96,6 +96,9 @@ Top-level `MushiConfig` fields (consumed by `@mushi-mushi/web` and framework wra
|
|
|
96
96
|
|-------|---------|----------|
|
|
97
97
|
| `sampleRate` | `1` | Probabilistic gate for **automatic** error reports (`0`–`1`). User-initiated feedback always sends. |
|
|
98
98
|
| `replaySampleRate` | `1` | Probabilistic gate for session replay; decided once at session init. Independent of `sampleRate`. |
|
|
99
|
+
| `replaysOnErrorSampleRate` | `0` | Start a replay buffer on automatic errors when session sample missed. Keep `0` unless you explicitly want rrweb. |
|
|
100
|
+
| `tunnel` | — | Same-origin ingest base (e.g. `/api/mushi-tunnel`). Wins over `apiEndpoint` so browser calls stay first-party. Pagehide uses `fetch` keepalive, then a `{ mushiBeacon, path, headers, body }` envelope because `sendBeacon` cannot set headers. |
|
|
101
|
+
| `ignoreErrors` / `denyUrls` / `allowUrls` | — | Drop **automatic** captures only. Never applied to user widget feedback. |
|
|
99
102
|
| `beforeSend` | — | `(report) => report \| null \| Promise<…>` after built-in PII scrub. Return `null` to drop. |
|
|
100
103
|
| `beforeSendFeedback` | — | Deprecated feedback-only hook; ignored when `beforeSend` is set. |
|
|
101
104
|
|
|
@@ -138,4 +141,4 @@ MIT
|
|
|
138
141
|
<!-- mushi-readme-stats-footer -->
|
|
139
142
|
---
|
|
140
143
|
|
|
141
|
-
<sub>Monorepo scale (July 2026):
|
|
144
|
+
<sub>Monorepo scale (July 2026): 58 edge functions · 347 SQL migrations · 13 outbound plugins · 11 inbound adapters · 19 pipeline agents. Canonical counts: <a href="https://github.com/kensaurus/mushi-mushi/blob/master/docs/stats.md">docs/stats.md</a> · <code>pnpm docs-stats</code></sub>
|
package/dist/index.cjs
CHANGED
|
@@ -86,7 +86,92 @@ async function hmacSha256Hex(secret, value) {
|
|
|
86
86
|
return bytesToHex(result);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
// src/unload-transport.ts
|
|
90
|
+
var MUSHI_BEACON_MAX_BYTES = 60 * 1024;
|
|
91
|
+
function isMushiBeaconEnvelope(value) {
|
|
92
|
+
if (!value || typeof value !== "object") return false;
|
|
93
|
+
const rec = value;
|
|
94
|
+
return rec.mushiBeacon === true && typeof rec.path === "string" && rec.headers !== null && typeof rec.headers === "object";
|
|
95
|
+
}
|
|
96
|
+
function buildBeaconEnvelope(opts) {
|
|
97
|
+
return {
|
|
98
|
+
mushiBeacon: true,
|
|
99
|
+
path: opts.path,
|
|
100
|
+
headers: opts.headers,
|
|
101
|
+
body: opts.body
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function isSameOriginOrRelative(url, origin = typeof location !== "undefined" ? location.origin : "") {
|
|
105
|
+
if (!url) return false;
|
|
106
|
+
if (url.startsWith("/")) return true;
|
|
107
|
+
if (!origin) return false;
|
|
108
|
+
try {
|
|
109
|
+
return new URL(url, origin).origin === origin;
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function extractTunnelPath(url) {
|
|
115
|
+
try {
|
|
116
|
+
const parsed = url.startsWith("/") ? new URL(url, "https://tunnel.local") : new URL(url);
|
|
117
|
+
return `${parsed.pathname}${parsed.search}`;
|
|
118
|
+
} catch {
|
|
119
|
+
return url;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function sendOnUnload(opts) {
|
|
123
|
+
const fetchImpl = opts.fetchImpl ?? (typeof fetch === "function" ? fetch : void 0);
|
|
124
|
+
if (fetchImpl) {
|
|
125
|
+
try {
|
|
126
|
+
void fetchImpl(opts.url, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers: {
|
|
129
|
+
"Content-Type": "application/json",
|
|
130
|
+
...opts.headers
|
|
131
|
+
},
|
|
132
|
+
body: opts.body,
|
|
133
|
+
keepalive: true
|
|
134
|
+
});
|
|
135
|
+
return true;
|
|
136
|
+
} catch {
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const beacon = opts.sendBeacon ?? (typeof navigator !== "undefined" ? navigator.sendBeacon?.bind(navigator) : void 0);
|
|
140
|
+
if (!beacon || !isSameOriginOrRelative(opts.url)) return false;
|
|
141
|
+
let parsedBody = opts.body;
|
|
142
|
+
try {
|
|
143
|
+
parsedBody = JSON.parse(opts.body);
|
|
144
|
+
} catch {
|
|
145
|
+
parsedBody = opts.body;
|
|
146
|
+
}
|
|
147
|
+
const envelope = JSON.stringify(
|
|
148
|
+
buildBeaconEnvelope({
|
|
149
|
+
path: opts.path ?? extractTunnelPath(opts.url),
|
|
150
|
+
headers: opts.headers,
|
|
151
|
+
body: parsedBody
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
if (envelope.length > MUSHI_BEACON_MAX_BYTES) return false;
|
|
155
|
+
try {
|
|
156
|
+
return beacon(opts.url, new Blob([envelope], { type: "application/json" }));
|
|
157
|
+
} catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
var pageUnloading = false;
|
|
162
|
+
function markPageUnloading() {
|
|
163
|
+
pageUnloading = true;
|
|
164
|
+
}
|
|
165
|
+
function isPageUnloading() {
|
|
166
|
+
return pageUnloading;
|
|
167
|
+
}
|
|
168
|
+
|
|
89
169
|
// src/api-client.ts
|
|
170
|
+
var lastOutbound = null;
|
|
171
|
+
function flushLastOutboundOnUnload() {
|
|
172
|
+
if (!lastOutbound) return false;
|
|
173
|
+
return sendOnUnload(lastOutbound);
|
|
174
|
+
}
|
|
90
175
|
var _credWarningEmitted = false;
|
|
91
176
|
var MUSHI_USER_TOKEN_HEADER = "X-Mushi-User-Token";
|
|
92
177
|
var MUSHI_PROJECT_HEADER = "X-Mushi-Project";
|
|
@@ -107,15 +192,22 @@ function buildSdkIngestHeaders(opts) {
|
|
|
107
192
|
var DEFAULT_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
|
|
108
193
|
var MUSHI_INTERNAL_HEADER = "X-Mushi-Internal";
|
|
109
194
|
var MUSHI_INTERNAL_INIT_MARKER = "__mushiInternal";
|
|
195
|
+
function resolveRequestBaseUrl(apiEndpoint, tunnel) {
|
|
196
|
+
const trimmedTunnel = tunnel?.trim();
|
|
197
|
+
if (trimmedTunnel) return trimmedTunnel.replace(/\/$/, "");
|
|
198
|
+
return (apiEndpoint ?? DEFAULT_API_ENDPOINT).replace(/\/$/, "");
|
|
199
|
+
}
|
|
110
200
|
var DEFAULT_TIMEOUT = 1e4;
|
|
111
201
|
var DEFAULT_MAX_RETRIES = 2;
|
|
112
202
|
var DEFAULT_CIRCUIT_THRESHOLD = 4;
|
|
113
203
|
var DEFAULT_CIRCUIT_COOLDOWN_MS = 3e4;
|
|
204
|
+
var MAX_INLINE_RETRY_WAIT_MS = 1e4;
|
|
114
205
|
function createApiClient(options) {
|
|
115
206
|
const {
|
|
116
207
|
projectId,
|
|
117
208
|
apiKey,
|
|
118
209
|
apiEndpoint = DEFAULT_API_ENDPOINT,
|
|
210
|
+
tunnel,
|
|
119
211
|
timeout = DEFAULT_TIMEOUT,
|
|
120
212
|
maxRetries = DEFAULT_MAX_RETRIES,
|
|
121
213
|
getUserToken,
|
|
@@ -123,7 +215,7 @@ function createApiClient(options) {
|
|
|
123
215
|
sdkVersion,
|
|
124
216
|
circuitBreaker
|
|
125
217
|
} = options;
|
|
126
|
-
let baseUrl = apiEndpoint
|
|
218
|
+
let baseUrl = resolveRequestBaseUrl(apiEndpoint, tunnel);
|
|
127
219
|
const cbEnabled = circuitBreaker?.enabled !== false;
|
|
128
220
|
const cbThreshold = Math.max(1, circuitBreaker?.threshold ?? DEFAULT_CIRCUIT_THRESHOLD);
|
|
129
221
|
const cbCooldownMs = Math.max(1e3, circuitBreaker?.cooldownMs ?? DEFAULT_CIRCUIT_COOLDOWN_MS);
|
|
@@ -147,21 +239,27 @@ function createApiClient(options) {
|
|
|
147
239
|
const controller = new AbortController();
|
|
148
240
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
149
241
|
const userToken = getUserToken?.() ?? null;
|
|
242
|
+
const headers = {
|
|
243
|
+
...buildSdkIngestHeaders({
|
|
244
|
+
apiKey,
|
|
245
|
+
projectId,
|
|
246
|
+
sdkPackage,
|
|
247
|
+
sdkVersion,
|
|
248
|
+
userToken,
|
|
249
|
+
internalKind
|
|
250
|
+
}),
|
|
251
|
+
...extraHeaders
|
|
252
|
+
};
|
|
253
|
+
const serialized = body ? JSON.stringify(body) : void 0;
|
|
254
|
+
if (serialized && method !== "GET") {
|
|
255
|
+
lastOutbound = { url, headers, body: serialized, path };
|
|
256
|
+
}
|
|
150
257
|
try {
|
|
151
258
|
const response = await fetch(url, {
|
|
152
259
|
method,
|
|
153
|
-
headers
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
projectId,
|
|
157
|
-
sdkPackage,
|
|
158
|
-
sdkVersion,
|
|
159
|
-
userToken,
|
|
160
|
-
internalKind
|
|
161
|
-
}),
|
|
162
|
-
...extraHeaders
|
|
163
|
-
},
|
|
164
|
-
body: body ? JSON.stringify(body) : void 0,
|
|
260
|
+
headers,
|
|
261
|
+
body: serialized,
|
|
262
|
+
keepalive: isPageUnloading(),
|
|
165
263
|
signal: controller.signal,
|
|
166
264
|
...internalKind ? { [MUSHI_INTERNAL_INIT_MARKER]: internalKind } : {}
|
|
167
265
|
});
|
|
@@ -185,11 +283,15 @@ function createApiClient(options) {
|
|
|
185
283
|
`[Mushi] Credentials rejected (HTTP ${response.status}). Check your Project ID and API key scope (must be "report:write"). ` + where
|
|
186
284
|
);
|
|
187
285
|
}
|
|
188
|
-
|
|
189
|
-
|
|
286
|
+
const rateLimited = response.status === 429;
|
|
287
|
+
const unusable = response.status >= 500 || rateLimited;
|
|
288
|
+
const retryAfterMs = rateLimited ? parseRetryAfter(response.headers.get("Retry-After")) : null;
|
|
289
|
+
const canWaitInline = retryAfterMs === null || retryAfterMs <= MAX_INLINE_RETRY_WAIT_MS;
|
|
290
|
+
if (unusable && retries > 0 && canWaitInline) {
|
|
291
|
+
await sleep(retryAfterMs ?? getBackoffDelay(maxRetries - retries));
|
|
190
292
|
return request(method, path, body, retries - 1, internalKind, extraHeaders);
|
|
191
293
|
}
|
|
192
|
-
if (
|
|
294
|
+
if (unusable) cbRecordUnreachable();
|
|
193
295
|
else cbRecordReachable();
|
|
194
296
|
return {
|
|
195
297
|
ok: false,
|
|
@@ -207,7 +309,7 @@ function createApiClient(options) {
|
|
|
207
309
|
clearTimeout(timer);
|
|
208
310
|
if (retries > 0 && isRetryable(error)) {
|
|
209
311
|
await sleep(getBackoffDelay(maxRetries - retries));
|
|
210
|
-
return request(method, path, body, retries - 1, internalKind);
|
|
312
|
+
return request(method, path, body, retries - 1, internalKind, extraHeaders);
|
|
211
313
|
}
|
|
212
314
|
cbRecordUnreachable();
|
|
213
315
|
return {
|
|
@@ -238,6 +340,7 @@ function createApiClient(options) {
|
|
|
238
340
|
"X-Reporter-Hmac": hmac
|
|
239
341
|
},
|
|
240
342
|
body: body ? JSON.stringify(body) : void 0,
|
|
343
|
+
keepalive: isPageUnloading(),
|
|
241
344
|
[MUSHI_INTERNAL_INIT_MARKER]: "reporter-poll"
|
|
242
345
|
});
|
|
243
346
|
if (!response.ok) {
|
|
@@ -497,12 +600,48 @@ function sleep(ms) {
|
|
|
497
600
|
function getBackoffDelay(attempt) {
|
|
498
601
|
return Math.min(1e3 * 2 ** attempt + Math.random() * 500, 1e4);
|
|
499
602
|
}
|
|
603
|
+
function parseRetryAfter(value, now2 = Date.now()) {
|
|
604
|
+
if (!value) return null;
|
|
605
|
+
const raw = value.trim();
|
|
606
|
+
if (!raw) return null;
|
|
607
|
+
const seconds = Number(raw);
|
|
608
|
+
if (Number.isFinite(seconds)) return seconds < 0 ? null : seconds * 1e3;
|
|
609
|
+
const at = Date.parse(raw);
|
|
610
|
+
if (Number.isNaN(at)) return null;
|
|
611
|
+
return Math.max(at - now2, 0);
|
|
612
|
+
}
|
|
500
613
|
function isRetryable(error) {
|
|
501
614
|
if (error instanceof DOMException && error.name === "AbortError") return true;
|
|
502
615
|
if (error instanceof TypeError) return true;
|
|
503
616
|
return false;
|
|
504
617
|
}
|
|
505
618
|
|
|
619
|
+
// src/error-filters.ts
|
|
620
|
+
function matchesErrorFilter(value, filters) {
|
|
621
|
+
if (!value || !filters || filters.length === 0) return false;
|
|
622
|
+
for (const filter of filters) {
|
|
623
|
+
if (typeof filter === "string") {
|
|
624
|
+
if (value.includes(filter)) return true;
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
try {
|
|
628
|
+
if (filter.test(value)) return true;
|
|
629
|
+
} catch {
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
function shouldDropCapturedError(input) {
|
|
635
|
+
if (matchesErrorFilter(input.message, input.ignoreErrors)) return true;
|
|
636
|
+
const filename = input.filename?.trim() || "";
|
|
637
|
+
if (filename && matchesErrorFilter(filename, input.denyUrls)) return true;
|
|
638
|
+
if (input.allowUrls && input.allowUrls.length > 0) {
|
|
639
|
+
if (!filename) return true;
|
|
640
|
+
if (!matchesErrorFilter(filename, input.allowUrls)) return true;
|
|
641
|
+
}
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
|
|
506
645
|
// src/region.ts
|
|
507
646
|
var REGION_ENDPOINTS = {
|
|
508
647
|
us: DEFAULT_API_ENDPOINT,
|
|
@@ -681,6 +820,7 @@ var KNOWN_CONFIG_KEYS = [
|
|
|
681
820
|
"projectId",
|
|
682
821
|
"apiKey",
|
|
683
822
|
"apiEndpoint",
|
|
823
|
+
"tunnel",
|
|
684
824
|
"timeout",
|
|
685
825
|
"maxRetries",
|
|
686
826
|
"circuitBreaker",
|
|
@@ -700,8 +840,13 @@ var KNOWN_CONFIG_KEYS = [
|
|
|
700
840
|
"enabled",
|
|
701
841
|
"appVersion",
|
|
702
842
|
"sampleRate",
|
|
843
|
+
"replaySampleRate",
|
|
844
|
+
"replaysOnErrorSampleRate",
|
|
703
845
|
"beforeSend",
|
|
704
846
|
"beforeSendFeedback",
|
|
847
|
+
"ignoreErrors",
|
|
848
|
+
"denyUrls",
|
|
849
|
+
"allowUrls",
|
|
705
850
|
"onCrashedLastRun"
|
|
706
851
|
];
|
|
707
852
|
function presetDefaults(preset) {
|
|
@@ -1166,6 +1311,8 @@ function createOfflineQueue(config = {}) {
|
|
|
1166
1311
|
let syncCleanup = null;
|
|
1167
1312
|
let flushInterval = null;
|
|
1168
1313
|
let backendType = null;
|
|
1314
|
+
let inFlight = null;
|
|
1315
|
+
let followUp = null;
|
|
1169
1316
|
async function wrapForStorage(report) {
|
|
1170
1317
|
const queuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1171
1318
|
if (!encryptAtRest) {
|
|
@@ -1318,7 +1465,10 @@ function createOfflineQueue(config = {}) {
|
|
|
1318
1465
|
}
|
|
1319
1466
|
async function lsEnqueue(report) {
|
|
1320
1467
|
const rows = lsRead();
|
|
1321
|
-
|
|
1468
|
+
const row = await wrapForStorage(report);
|
|
1469
|
+
const idx = rows.findIndex((r) => r.id === report.id);
|
|
1470
|
+
if (idx >= 0) rows[idx] = row;
|
|
1471
|
+
else rows.push(row);
|
|
1322
1472
|
lsWrite(rows);
|
|
1323
1473
|
}
|
|
1324
1474
|
function lsDelete(id) {
|
|
@@ -1357,26 +1507,43 @@ function createOfflineQueue(config = {}) {
|
|
|
1357
1507
|
return false;
|
|
1358
1508
|
}
|
|
1359
1509
|
}
|
|
1360
|
-
async function
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
if (
|
|
1364
|
-
|
|
1365
|
-
|
|
1510
|
+
async function readRows() {
|
|
1511
|
+
const backend = detectBackend();
|
|
1512
|
+
if (backend === "none") return { backend, rows: [] };
|
|
1513
|
+
if (backend === "indexeddb") {
|
|
1514
|
+
try {
|
|
1515
|
+
return { backend, rows: await idbGetAll() };
|
|
1516
|
+
} catch {
|
|
1517
|
+
backendType = "localstorage";
|
|
1518
|
+
}
|
|
1366
1519
|
}
|
|
1520
|
+
return { backend: "localstorage", rows: lsRead() };
|
|
1521
|
+
}
|
|
1522
|
+
async function writeRow(report) {
|
|
1367
1523
|
const backend = detectBackend();
|
|
1524
|
+
if (backend === "none") {
|
|
1525
|
+
queueLog.debug("Offline queue: no storage backend, dropping report", { id: report.id });
|
|
1526
|
+
return backend;
|
|
1527
|
+
}
|
|
1368
1528
|
if (backend === "indexeddb") {
|
|
1369
1529
|
try {
|
|
1370
1530
|
await idbEnqueue(report);
|
|
1371
|
-
return;
|
|
1531
|
+
return backend;
|
|
1372
1532
|
} catch {
|
|
1373
1533
|
backendType = "localstorage";
|
|
1374
1534
|
}
|
|
1375
1535
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1536
|
+
await lsEnqueue(report);
|
|
1537
|
+
return "localstorage";
|
|
1538
|
+
}
|
|
1539
|
+
async function enqueue(report) {
|
|
1540
|
+
if (!enabled) return;
|
|
1541
|
+
const currentSize = await size();
|
|
1542
|
+
if (currentSize >= maxQueueSize) {
|
|
1543
|
+
queueLog.warn("Offline queue full \u2014 dropping report", { maxQueueSize });
|
|
1378
1544
|
return;
|
|
1379
1545
|
}
|
|
1546
|
+
await writeRow(report);
|
|
1380
1547
|
}
|
|
1381
1548
|
function getBackoffDelay2(attempt) {
|
|
1382
1549
|
return Math.min(1e3 * 2 ** attempt + Math.random() * 500, MAX_BACKOFF_MS);
|
|
@@ -1384,19 +1551,9 @@ function createOfflineQueue(config = {}) {
|
|
|
1384
1551
|
function sleep2(ms) {
|
|
1385
1552
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1386
1553
|
}
|
|
1387
|
-
async function
|
|
1388
|
-
|
|
1389
|
-
let rows;
|
|
1390
|
-
const backend = detectBackend();
|
|
1391
|
-
if (backend === "indexeddb") {
|
|
1392
|
-
try {
|
|
1393
|
-
rows = await idbGetAll();
|
|
1394
|
-
} catch {
|
|
1395
|
-
rows = lsRead();
|
|
1396
|
-
}
|
|
1397
|
-
} else {
|
|
1398
|
-
rows = lsRead();
|
|
1399
|
-
}
|
|
1554
|
+
async function runFlush(client) {
|
|
1555
|
+
const { backend, rows: storedRows } = await readRows();
|
|
1556
|
+
let rows = storedRows;
|
|
1400
1557
|
const now2 = Date.now();
|
|
1401
1558
|
const fresh = [];
|
|
1402
1559
|
for (const row of rows) {
|
|
@@ -1425,59 +1582,84 @@ function createOfflineQueue(config = {}) {
|
|
|
1425
1582
|
if (result.ok) {
|
|
1426
1583
|
await removeRow(backend, rowId);
|
|
1427
1584
|
sent++;
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
failed++;
|
|
1588
|
+
if (result.error?.code === "CIRCUIT_OPEN") {
|
|
1589
|
+
queueLog.debug("Offline queue: circuit open, deferring the rest of the batch", {
|
|
1590
|
+
id: rowId,
|
|
1591
|
+
remaining: batch.length - i
|
|
1592
|
+
});
|
|
1593
|
+
break;
|
|
1594
|
+
}
|
|
1595
|
+
const permanent = result.error?.code === "HTTP_400" || result.error?.code === "HTTP_413" || result.error?.code === "HTTP_422" || result.error?.code === "INGEST_ERROR" || result.error?.code === "VALIDATION_ERROR" || // A payload that exceeds the size guard will never shrink on its own;
|
|
1596
|
+
// retrying re-serialises the multi-MB body every sync tick and wedges
|
|
1597
|
+
// the queue (it matches neither permanent nor transient otherwise).
|
|
1598
|
+
// SERIALIZE_FAILED (circular ref) is likewise unrecoverable on retry.
|
|
1599
|
+
result.error?.code === "PAYLOAD_TOO_LARGE" || result.error?.code === "SERIALIZE_FAILED" || typeof result.error?.message === "string" && /invalid payload|description must be at least|validation/i.test(
|
|
1600
|
+
result.error.message
|
|
1601
|
+
);
|
|
1602
|
+
const transient = !permanent && (result.error?.code === "NETWORK_ERROR" || result.error?.code === "HTTP_403" || result.error?.code === "HTTP_429" || result.error?.code === "HTTP_502" || result.error?.code === "HTTP_503" || result.error?.code === "HTTP_504" || typeof result.error?.code === "string" && result.error.code.startsWith("HTTP_5"));
|
|
1603
|
+
if (permanent) {
|
|
1604
|
+
await removeRow(backend, rowId);
|
|
1428
1605
|
} else {
|
|
1429
|
-
const
|
|
1430
|
-
|
|
1431
|
-
// the queue (it matches neither permanent nor transient otherwise).
|
|
1432
|
-
// SERIALIZE_FAILED (circular ref) is likewise unrecoverable on retry.
|
|
1433
|
-
result.error?.code === "PAYLOAD_TOO_LARGE" || result.error?.code === "SERIALIZE_FAILED" || typeof result.error?.message === "string" && /invalid payload|description must be at least|validation/i.test(
|
|
1434
|
-
result.error.message
|
|
1435
|
-
);
|
|
1436
|
-
const transient = !permanent && (result.error?.code === "NETWORK_ERROR" || result.error?.code === "HTTP_403" || result.error?.code === "HTTP_429" || result.error?.code === "HTTP_502" || result.error?.code === "HTTP_503" || result.error?.code === "HTTP_504" || typeof result.error?.code === "string" && result.error.code.startsWith("HTTP_5"));
|
|
1437
|
-
if (permanent) {
|
|
1606
|
+
const nextAttempts = rowAttempts(row) + 1;
|
|
1607
|
+
if (nextAttempts >= MAX_DELIVERY_ATTEMPTS) {
|
|
1438
1608
|
await removeRow(backend, rowId);
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1609
|
+
queueLog.warn("Offline queue: giving up on report after repeated failures", {
|
|
1610
|
+
id: rowId,
|
|
1611
|
+
attempts: nextAttempts,
|
|
1612
|
+
code: result.error?.code
|
|
1613
|
+
});
|
|
1614
|
+
} else {
|
|
1615
|
+
row.attempts = nextAttempts;
|
|
1616
|
+
const persisted = await persistRow(backend, row);
|
|
1617
|
+
if (persisted) {
|
|
1618
|
+
queueLog.debug("Offline queue: delivery failed, will retry", {
|
|
1444
1619
|
id: rowId,
|
|
1445
1620
|
attempts: nextAttempts,
|
|
1446
|
-
code: result.error?.code
|
|
1621
|
+
code: result.error?.code,
|
|
1622
|
+
classified: transient ? "transient" : "unclassified"
|
|
1447
1623
|
});
|
|
1448
1624
|
} else {
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
attempts: nextAttempts,
|
|
1455
|
-
code: result.error?.code
|
|
1456
|
-
});
|
|
1457
|
-
} else {
|
|
1458
|
-
await removeRow(backend, rowId);
|
|
1459
|
-
queueLog.warn("Offline queue: dropping report (attempt counter unpersistable)", {
|
|
1460
|
-
id: rowId,
|
|
1461
|
-
code: result.error?.code
|
|
1462
|
-
});
|
|
1463
|
-
}
|
|
1625
|
+
await removeRow(backend, rowId);
|
|
1626
|
+
queueLog.warn("Offline queue: dropping report (attempt counter unpersistable)", {
|
|
1627
|
+
id: rowId,
|
|
1628
|
+
code: result.error?.code
|
|
1629
|
+
});
|
|
1464
1630
|
}
|
|
1465
1631
|
}
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
}
|
|
1632
|
+
}
|
|
1633
|
+
if (i < batch.length - 1) {
|
|
1634
|
+
await sleep2(getBackoffDelay2(i));
|
|
1470
1635
|
}
|
|
1471
1636
|
}
|
|
1472
1637
|
return { sent, failed };
|
|
1473
1638
|
}
|
|
1639
|
+
function flush(client) {
|
|
1640
|
+
if (!enabled) return Promise.resolve({ sent: 0, failed: 0 });
|
|
1641
|
+
if (inFlight) {
|
|
1642
|
+
if (!followUp) {
|
|
1643
|
+
followUp = inFlight.catch(() => void 0).then(() => {
|
|
1644
|
+
followUp = null;
|
|
1645
|
+
return flush(client);
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
return followUp;
|
|
1649
|
+
}
|
|
1650
|
+
const pass = runFlush(client).finally(() => {
|
|
1651
|
+
if (inFlight === pass) inFlight = null;
|
|
1652
|
+
});
|
|
1653
|
+
inFlight = pass;
|
|
1654
|
+
return pass;
|
|
1655
|
+
}
|
|
1474
1656
|
async function size() {
|
|
1475
1657
|
const backend = detectBackend();
|
|
1476
1658
|
if (backend === "indexeddb") {
|
|
1477
1659
|
try {
|
|
1478
1660
|
return await idbSize();
|
|
1479
1661
|
} catch {
|
|
1480
|
-
|
|
1662
|
+
backendType = "localstorage";
|
|
1481
1663
|
}
|
|
1482
1664
|
}
|
|
1483
1665
|
return lsRead().length;
|
|
@@ -2434,13 +2616,22 @@ var SLUG_DOMAIN_HINTS = {
|
|
|
2434
2616
|
"glot-it": "glot.it",
|
|
2435
2617
|
glotit: "glot.it",
|
|
2436
2618
|
"solo-boss-cloud": "solo-boss.kensaur.us",
|
|
2437
|
-
"mushi-mushi": "kensaur.us"
|
|
2619
|
+
"mushi-mushi": "kensaur.us",
|
|
2620
|
+
"how-to-talk-to-girls": "talk.kensaur.us",
|
|
2621
|
+
htttg: "talk.kensaur.us",
|
|
2622
|
+
tsumagoi: "tsumagoi.kensaur.us"
|
|
2438
2623
|
};
|
|
2439
2624
|
var SLUG_ICON_URL_HINTS = {
|
|
2440
2625
|
"mushi-mushi": "https://kensaur.us/mushi-mushi/admin/favicon.svg",
|
|
2441
2626
|
"solo-boss-cloud": "https://solo-boss.kensaur.us/favicon.svg",
|
|
2442
2627
|
"glot-it": "https://kensaur.us/glot-it/apple-touch-icon.png",
|
|
2443
|
-
glotit: "https://kensaur.us/glot-it/apple-touch-icon.png"
|
|
2628
|
+
glotit: "https://kensaur.us/glot-it/apple-touch-icon.png",
|
|
2629
|
+
"the-wanting-mind": "https://kensaur.us/the-wanting-mind/pwa-512x512.png",
|
|
2630
|
+
"how-to-talk-to-girls": "https://talk.kensaur.us/pwa-192.png",
|
|
2631
|
+
htttg: "https://talk.kensaur.us/pwa-192.png",
|
|
2632
|
+
tsumagoi: "https://tsumagoi.kensaur.us/apple-touch-icon.png",
|
|
2633
|
+
"help-her-take-photo": "https://kensaur.us/help-her-take-photo/assets/apple-touch-icon.png",
|
|
2634
|
+
"yen-yen": "https://kensaur.us/yen-yen/icon.svg"
|
|
2444
2635
|
};
|
|
2445
2636
|
var UNTRUSTED_FAVICON_HOSTS = [
|
|
2446
2637
|
"www.google.com",
|
|
@@ -2620,6 +2811,7 @@ exports.MAX_SCREENSHOT_DATA_URL_BYTES = MAX_SCREENSHOT_DATA_URL_BYTES;
|
|
|
2620
2811
|
exports.MUSHI_ACCENT_SHADOW = MUSHI_ACCENT_SHADOW;
|
|
2621
2812
|
exports.MUSHI_BANNER_BRAND_BORDER = MUSHI_BANNER_BRAND_BORDER;
|
|
2622
2813
|
exports.MUSHI_BANNER_NEON = MUSHI_BANNER_NEON;
|
|
2814
|
+
exports.MUSHI_BEACON_MAX_BYTES = MUSHI_BEACON_MAX_BYTES;
|
|
2623
2815
|
exports.MUSHI_BORDER = MUSHI_BORDER;
|
|
2624
2816
|
exports.MUSHI_COLORS_DARK = MUSHI_COLORS_DARK;
|
|
2625
2817
|
exports.MUSHI_COLORS_LIGHT = MUSHI_COLORS_LIGHT;
|
|
@@ -2647,6 +2839,7 @@ exports.MUSHI_TYPE = MUSHI_TYPE;
|
|
|
2647
2839
|
exports.MUSHI_USER_TOKEN_HEADER = MUSHI_USER_TOKEN_HEADER;
|
|
2648
2840
|
exports.MUSHI_Z = MUSHI_Z;
|
|
2649
2841
|
exports.REGION_ENDPOINTS = REGION_ENDPOINTS;
|
|
2842
|
+
exports.buildBeaconEnvelope = buildBeaconEnvelope;
|
|
2650
2843
|
exports.buildIdentityClaims = buildIdentityClaims;
|
|
2651
2844
|
exports.buildSdkIngestHeaders = buildSdkIngestHeaders;
|
|
2652
2845
|
exports.captureEnvironment = captureEnvironment;
|
|
@@ -2662,8 +2855,11 @@ exports.destroySessionTracker = destroySessionTracker;
|
|
|
2662
2855
|
exports.diagnoseEnvConfig = diagnoseEnvConfig;
|
|
2663
2856
|
exports.estimateJsonBytes = estimateJsonBytes;
|
|
2664
2857
|
exports.expandPreset = expandPreset;
|
|
2858
|
+
exports.extractTunnelPath = extractTunnelPath;
|
|
2665
2859
|
exports.faviconUrlCandidates = faviconUrlCandidates;
|
|
2860
|
+
exports.flushLastOutboundOnUnload = flushLastOutboundOnUnload;
|
|
2666
2861
|
exports.formatBytes = formatBytes;
|
|
2862
|
+
exports.getBackoffDelay = getBackoffDelay;
|
|
2667
2863
|
exports.getDeviceFingerprintHash = getDeviceFingerprintHash;
|
|
2668
2864
|
exports.getReporterToken = getReporterToken;
|
|
2669
2865
|
exports.getSessionId = getSessionId;
|
|
@@ -2671,7 +2867,12 @@ exports.githubRepoDomainHint = githubRepoDomainHint;
|
|
|
2671
2867
|
exports.hmacSha256Hex = hmacSha256Hex;
|
|
2672
2868
|
exports.initSessionTracker = initSessionTracker;
|
|
2673
2869
|
exports.isLikelyGenericFavicon = isLikelyGenericFavicon;
|
|
2870
|
+
exports.isMushiBeaconEnvelope = isMushiBeaconEnvelope;
|
|
2871
|
+
exports.isPageUnloading = isPageUnloading;
|
|
2872
|
+
exports.isSameOriginOrRelative = isSameOriginOrRelative;
|
|
2674
2873
|
exports.isUntrustedFaviconUrl = isUntrustedFaviconUrl;
|
|
2874
|
+
exports.markPageUnloading = markPageUnloading;
|
|
2875
|
+
exports.matchesErrorFilter = matchesErrorFilter;
|
|
2675
2876
|
exports.mushiPalette = mushiPalette;
|
|
2676
2877
|
exports.mushiTokens = mushiTokens;
|
|
2677
2878
|
exports.newUuid = newUuid;
|
|
@@ -2679,6 +2880,7 @@ exports.noopLogger = noopLogger;
|
|
|
2679
2880
|
exports.normaliseThrown = normaliseThrown;
|
|
2680
2881
|
exports.originToDomain = originToDomain;
|
|
2681
2882
|
exports.parseIdentityToken = parseIdentityToken;
|
|
2883
|
+
exports.parseRetryAfter = parseRetryAfter;
|
|
2682
2884
|
exports.projectFaviconUrlCandidates = projectFaviconUrlCandidates;
|
|
2683
2885
|
exports.projectInitials = projectInitials;
|
|
2684
2886
|
exports.projectInitialsThemeIndex = projectInitialsThemeIndex;
|
|
@@ -2687,11 +2889,14 @@ exports.resolveEnvConfig = resolveEnvConfig;
|
|
|
2687
2889
|
exports.resolveProjectDomain = resolveProjectDomain;
|
|
2688
2890
|
exports.resolveProjectFaviconDomains = resolveProjectFaviconDomains;
|
|
2689
2891
|
exports.resolveRegionEndpoint = resolveRegionEndpoint;
|
|
2892
|
+
exports.resolveRequestBaseUrl = resolveRequestBaseUrl;
|
|
2690
2893
|
exports.resolveWidgetAccent = resolveWidgetAccent;
|
|
2691
2894
|
exports.safeWidgetHex = safeWidgetHex;
|
|
2692
2895
|
exports.scrubPii = scrubPii;
|
|
2693
2896
|
exports.scrubUrl = scrubUrl;
|
|
2897
|
+
exports.sendOnUnload = sendOnUnload;
|
|
2694
2898
|
exports.sha256Hex = sha256Hex;
|
|
2899
|
+
exports.shouldDropCapturedError = shouldDropCapturedError;
|
|
2695
2900
|
exports.trackPageView = trackPageView;
|
|
2696
2901
|
exports.updateSessionIdentity = updateSessionIdentity;
|
|
2697
2902
|
exports.validateConfig = validateConfig;
|