@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 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): 55 edge functions · 331 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>
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.replace(/\/$/, "");
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
- ...buildSdkIngestHeaders({
155
- apiKey,
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
- if (response.status >= 500 && retries > 0) {
189
- await sleep(getBackoffDelay(maxRetries - retries));
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 (response.status >= 500) cbRecordUnreachable();
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
- rows.push(await wrapForStorage(report));
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 enqueue(report) {
1361
- if (!enabled) return;
1362
- const currentSize = await size();
1363
- if (currentSize >= maxQueueSize) {
1364
- queueLog.warn("Offline queue full \u2014 dropping report", { maxQueueSize });
1365
- return;
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
- if (backend === "localstorage" || backendType === "localstorage") {
1377
- await lsEnqueue(report);
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 flush(client) {
1388
- if (!enabled) return { sent: 0, failed: 0 };
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 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;
1430
- // retrying re-serialises the multi-MB body every sync tick and wedges
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
- } else if (transient) {
1440
- const nextAttempts = rowAttempts(row) + 1;
1441
- if (nextAttempts >= MAX_DELIVERY_ATTEMPTS) {
1442
- await removeRow(backend, rowId);
1443
- queueLog.warn("Offline queue: giving up on report after repeated failures", {
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
- row.attempts = nextAttempts;
1450
- const persisted = await persistRow(backend, row);
1451
- if (persisted) {
1452
- queueLog.debug("Offline queue: transient failure, will retry", {
1453
- id: rowId,
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
- failed++;
1467
- if (i < batch.length - 1) {
1468
- await sleep2(getBackoffDelay2(i));
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
- return lsRead().length;
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;