@mushi-mushi/core 1.32.0 → 1.32.3

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
@@ -48,13 +48,13 @@ const resolved = expandPreset(config); // preset → nested options, explicit va
48
48
 
49
49
  ## Sampling and filters
50
50
 
51
- Sampling and URL filters apply to **automatic** error reports only, so a report a user sends from the widget is never sampled or filtered out. Only your own `beforeSend` can change or drop it.
51
+ Sampling applies to **automatic** error reports, and the error filters to automatic reports and `captureException()`. A report a user sends from the widget is never sampled or filtered out. Only your own `beforeSend` can change or drop it.
52
52
 
53
53
  | Field | Default | What it does |
54
54
  | --- | --- | --- |
55
55
  | `sampleRate` | `1` | Share of automatic error reports to send (`0`–`1`) |
56
56
  | `replaySampleRate` | `1` | Share of sessions that record replay |
57
- | `ignoreErrors` / `denyUrls` / `allowUrls` | — | Drop matching automatic captures |
57
+ | `ignoreErrors` / `denyUrls` / `allowUrls` | — | Drop matching errors from automatic capture and `captureException()`. URLs match the frame that threw |
58
58
  | `beforeSend` | — | `(report) => report \| null` for every report, after the PII scrubber. Return `null` to drop |
59
59
  | `tunnel` | — | Same-origin ingest path (e.g. `/api/mushi-tunnel`) so ad blockers don't drop reports |
60
60
 
package/dist/index.cjs CHANGED
@@ -169,8 +169,9 @@ function isPageUnloading() {
169
169
  // src/api-client.ts
170
170
  var lastOutbound = null;
171
171
  function flushLastOutboundOnUnload() {
172
- if (!lastOutbound) return false;
173
- return sendOnUnload(lastOutbound);
172
+ const outbound = lastOutbound;
173
+ lastOutbound = null;
174
+ return !!outbound && sendOnUnload(outbound);
174
175
  }
175
176
  var _credWarningEmitted = false;
176
177
  var MUSHI_USER_TOKEN_HEADER = "X-Mushi-User-Token";
@@ -252,8 +253,10 @@ function createApiClient(options) {
252
253
  ...extraHeaders
253
254
  };
254
255
  const serialized = body ? JSON.stringify(body) : void 0;
256
+ let outbound = null;
255
257
  if (serialized && method !== "GET" && internalKind !== "reporter-poll") {
256
- lastOutbound = { url, headers, body: serialized, path };
258
+ outbound = { url, headers, body: serialized, path };
259
+ lastOutbound = outbound;
257
260
  }
258
261
  try {
259
262
  const response = await fetch(url, {
@@ -323,6 +326,8 @@ function createApiClient(options) {
323
326
  message: error instanceof Error ? error.message : "Unknown network error"
324
327
  }
325
328
  };
329
+ } finally {
330
+ if (lastOutbound === outbound) lastOutbound = null;
326
331
  }
327
332
  }
328
333
  async function requestForReporter(method, path, reporterToken, body) {
@@ -620,6 +625,7 @@ function matchesErrorFilter(value, filters) {
620
625
  continue;
621
626
  }
622
627
  try {
628
+ filter.lastIndex = 0;
623
629
  if (filter.test(value)) return true;
624
630
  } catch {
625
631
  }
@@ -1590,14 +1596,10 @@ function createOfflineQueue(config = {}) {
1590
1596
  });
1591
1597
  break;
1592
1598
  }
1593
- 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;
1594
- // retrying re-serialises the multi-MB body every sync tick and wedges
1595
- // the queue (it matches neither permanent nor transient otherwise).
1596
- // SERIALIZE_FAILED (circular ref) is likewise unrecoverable on retry.
1597
- 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(
1599
+ const permanent = result.error?.code === "HTTP_400" || result.error?.status === 401 || result.error?.status === 403 || result.error?.code === "HTTP_401" || result.error?.code === "HTTP_403" || result.error?.code === "HTTP_413" || result.error?.code === "HTTP_422" || result.error?.code === "INGEST_ERROR" || result.error?.code === "VALIDATION_ERROR" || 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(
1598
1600
  result.error.message
1599
1601
  );
1600
- 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"));
1602
+ const transient = !permanent && (result.error?.code === "NETWORK_ERROR" || 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"));
1601
1603
  if (permanent) {
1602
1604
  await removeRow(backend, rowId);
1603
1605
  } else {
@@ -2042,7 +2044,7 @@ function randomSuffix() {
2042
2044
  }
2043
2045
  let n = 0;
2044
2046
  for (const b of bytes) n = n * 256 + b >>> 0;
2045
- return n.toString(36).padStart(6, "0").slice(0, 6);
2047
+ return (n % 36 ** 6).toString(36).padStart(6, "0");
2046
2048
  }
2047
2049
 
2048
2050
  // src/analytics-gate.ts
@@ -2114,7 +2116,9 @@ var _sdkVersion;
2114
2116
  var _userIdHash = null;
2115
2117
  var _reporterToken = null;
2116
2118
  var _pageViewCount = 0;
2117
- var _heartbeatTimer = null;
2119
+ var _heartbeatTimer;
2120
+ var _lastRoute = null;
2121
+ var _endSent = false;
2118
2122
  var _initialized = false;
2119
2123
  var _active = false;
2120
2124
  var _started = false;
@@ -2156,30 +2160,62 @@ function send(payload) {
2156
2160
  if (!_client || !_active) return;
2157
2161
  _client.postSessionEvent(payload).catch(() => {
2158
2162
  });
2163
+ scheduleHeartbeat();
2164
+ }
2165
+ function clearHeartbeat() {
2166
+ clearTimeout(_heartbeatTimer);
2167
+ }
2168
+ function scheduleHeartbeat() {
2169
+ clearHeartbeat();
2170
+ if (!_active || globalThis.document?.visibilityState === "hidden") return;
2171
+ _heartbeatTimer = setTimeout(() => {
2172
+ send(buildPayload("session_heartbeat", { route: currentRoute() }));
2173
+ }, HEARTBEAT_INTERVAL_MS);
2159
2174
  }
2160
2175
  function recordPageView(route) {
2161
2176
  if (!_active) return;
2177
+ if (route === _lastRoute) return;
2178
+ _lastRoute = route;
2162
2179
  _pageViewCount += 1;
2163
2180
  send(buildPayload("page_view", { route }));
2164
2181
  }
2182
+ function sendSessionEnd() {
2183
+ if (_endSent || !_active) return;
2184
+ _endSent = true;
2185
+ send(buildPayload("session_end", { route: currentRoute() }));
2186
+ clearHeartbeat();
2187
+ }
2165
2188
  function activate() {
2166
2189
  if (_active || !_initialized) return;
2167
2190
  _active = true;
2168
2191
  if (!_started) {
2169
2192
  _started = true;
2170
2193
  _pageViewCount = 1;
2171
- send(buildPayload("session_start", { route: currentRoute(), referrer: referrerOrigin() }));
2194
+ _lastRoute = currentRoute();
2195
+ send(buildPayload("session_start", { route: _lastRoute, referrer: referrerOrigin() }));
2196
+ } else {
2197
+ scheduleHeartbeat();
2172
2198
  }
2173
- _heartbeatTimer = setInterval(() => {
2174
- send(buildPayload("session_heartbeat", { route: currentRoute() }));
2175
- }, HEARTBEAT_INTERVAL_MS);
2199
+ }
2200
+ function onHide() {
2201
+ if (document.visibilityState === "hidden") {
2202
+ sendSessionEnd();
2203
+ } else {
2204
+ _endSent = false;
2205
+ scheduleHeartbeat();
2206
+ }
2207
+ }
2208
+ function onPagehide() {
2209
+ sendSessionEnd();
2210
+ }
2211
+ function onPageshow(event) {
2212
+ if (!event.persisted) return;
2213
+ _endSent = false;
2214
+ scheduleHeartbeat();
2176
2215
  }
2177
2216
  function deactivate() {
2178
2217
  _active = false;
2179
- if (_heartbeatTimer != null) {
2180
- clearInterval(_heartbeatTimer);
2181
- _heartbeatTimer = null;
2182
- }
2218
+ clearHeartbeat();
2183
2219
  }
2184
2220
  function initSessionTracker(opts) {
2185
2221
  if (_initialized || typeof window === "undefined") return;
@@ -2189,17 +2225,11 @@ function initSessionTracker(opts) {
2189
2225
  _sdkVersion = opts.sdkVersion;
2190
2226
  _reporterToken = opts.reporterToken ?? opts.reporterTokenHash ?? null;
2191
2227
  _userIdHash = opts.userIdHash ?? null;
2192
- const onHide = () => {
2193
- if (typeof document !== "undefined" && document.visibilityState === "hidden") {
2194
- send(buildPayload("session_end", { route: currentRoute() }));
2195
- }
2196
- };
2197
2228
  if (typeof document !== "undefined") {
2198
2229
  document.addEventListener("visibilitychange", onHide, { passive: true });
2199
2230
  }
2200
- window.addEventListener("pagehide", () => {
2201
- send(buildPayload("session_end", { route: currentRoute() }));
2202
- }, { passive: true });
2231
+ window.addEventListener("pagehide", onPagehide, { passive: true });
2232
+ window.addEventListener("pageshow", onPageshow, { passive: true });
2203
2233
  if (opts.patchHistory !== false) {
2204
2234
  patchHistoryForPageViews();
2205
2235
  }
@@ -2214,6 +2244,7 @@ function trackPageView(route) {
2214
2244
  recordPageView(route ?? currentRoute());
2215
2245
  }
2216
2246
  function updateSessionIdentity(userIdHash) {
2247
+ if (userIdHash && userIdHash !== _userIdHash) _lastRoute = null;
2217
2248
  _userIdHash = userIdHash;
2218
2249
  }
2219
2250
  function destroySessionTracker() {
@@ -2222,6 +2253,8 @@ function destroySessionTracker() {
2222
2253
  _unsubscribeConsent = null;
2223
2254
  _initialized = false;
2224
2255
  _started = false;
2256
+ _lastRoute = null;
2257
+ _endSent = false;
2225
2258
  _client = null;
2226
2259
  }
2227
2260
  var _historyPatched = false;
@@ -2543,10 +2576,10 @@ function initEventTracker(opts) {
2543
2576
  _flushTimer = setInterval(() => {
2544
2577
  void flushNow();
2545
2578
  }, interval);
2546
- const onHide = () => {
2579
+ const onHide2 = () => {
2547
2580
  if (typeof document !== "undefined" && document.visibilityState === "hidden") void flushNow();
2548
2581
  };
2549
- if (typeof document !== "undefined") document.addEventListener("visibilitychange", onHide, { passive: true });
2582
+ if (typeof document !== "undefined") document.addEventListener("visibilitychange", onHide2, { passive: true });
2550
2583
  window.addEventListener("pagehide", () => {
2551
2584
  void flushNow();
2552
2585
  }, { passive: true });
@@ -3010,7 +3043,7 @@ function resolveWidgetAccent(mode, accent = "", accentText = "") {
3010
3043
  return { accent: resolvedAccent, accentWash, accentInk };
3011
3044
  }
3012
3045
  var MUSHI_COPY = {
3013
- bannerLabel: "Beta",
3046
+ bannerLabel: "Feedback",
3014
3047
  bannerMessage: "Found a bug? Tap to report it.",
3015
3048
  bugCta: "Report a bug",
3016
3049
  featureCta: "Feature request",