@mushi-mushi/core 1.27.2 → 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 · 336 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,6 +192,11 @@ 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;
@@ -117,6 +207,7 @@ function createApiClient(options) {
117
207
  projectId,
118
208
  apiKey,
119
209
  apiEndpoint = DEFAULT_API_ENDPOINT,
210
+ tunnel,
120
211
  timeout = DEFAULT_TIMEOUT,
121
212
  maxRetries = DEFAULT_MAX_RETRIES,
122
213
  getUserToken,
@@ -124,7 +215,7 @@ function createApiClient(options) {
124
215
  sdkVersion,
125
216
  circuitBreaker
126
217
  } = options;
127
- let baseUrl = apiEndpoint.replace(/\/$/, "");
218
+ let baseUrl = resolveRequestBaseUrl(apiEndpoint, tunnel);
128
219
  const cbEnabled = circuitBreaker?.enabled !== false;
129
220
  const cbThreshold = Math.max(1, circuitBreaker?.threshold ?? DEFAULT_CIRCUIT_THRESHOLD);
130
221
  const cbCooldownMs = Math.max(1e3, circuitBreaker?.cooldownMs ?? DEFAULT_CIRCUIT_COOLDOWN_MS);
@@ -148,21 +239,27 @@ function createApiClient(options) {
148
239
  const controller = new AbortController();
149
240
  const timer = setTimeout(() => controller.abort(), timeout);
150
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
+ }
151
257
  try {
152
258
  const response = await fetch(url, {
153
259
  method,
154
- headers: {
155
- ...buildSdkIngestHeaders({
156
- apiKey,
157
- projectId,
158
- sdkPackage,
159
- sdkVersion,
160
- userToken,
161
- internalKind
162
- }),
163
- ...extraHeaders
164
- },
165
- body: body ? JSON.stringify(body) : void 0,
260
+ headers,
261
+ body: serialized,
262
+ keepalive: isPageUnloading(),
166
263
  signal: controller.signal,
167
264
  ...internalKind ? { [MUSHI_INTERNAL_INIT_MARKER]: internalKind } : {}
168
265
  });
@@ -243,6 +340,7 @@ function createApiClient(options) {
243
340
  "X-Reporter-Hmac": hmac
244
341
  },
245
342
  body: body ? JSON.stringify(body) : void 0,
343
+ keepalive: isPageUnloading(),
246
344
  [MUSHI_INTERNAL_INIT_MARKER]: "reporter-poll"
247
345
  });
248
346
  if (!response.ok) {
@@ -518,6 +616,32 @@ function isRetryable(error) {
518
616
  return false;
519
617
  }
520
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
+
521
645
  // src/region.ts
522
646
  var REGION_ENDPOINTS = {
523
647
  us: DEFAULT_API_ENDPOINT,
@@ -696,6 +820,7 @@ var KNOWN_CONFIG_KEYS = [
696
820
  "projectId",
697
821
  "apiKey",
698
822
  "apiEndpoint",
823
+ "tunnel",
699
824
  "timeout",
700
825
  "maxRetries",
701
826
  "circuitBreaker",
@@ -715,8 +840,13 @@ var KNOWN_CONFIG_KEYS = [
715
840
  "enabled",
716
841
  "appVersion",
717
842
  "sampleRate",
843
+ "replaySampleRate",
844
+ "replaysOnErrorSampleRate",
718
845
  "beforeSend",
719
846
  "beforeSendFeedback",
847
+ "ignoreErrors",
848
+ "denyUrls",
849
+ "allowUrls",
720
850
  "onCrashedLastRun"
721
851
  ];
722
852
  function presetDefaults(preset) {
@@ -2486,13 +2616,22 @@ var SLUG_DOMAIN_HINTS = {
2486
2616
  "glot-it": "glot.it",
2487
2617
  glotit: "glot.it",
2488
2618
  "solo-boss-cloud": "solo-boss.kensaur.us",
2489
- "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"
2490
2623
  };
2491
2624
  var SLUG_ICON_URL_HINTS = {
2492
2625
  "mushi-mushi": "https://kensaur.us/mushi-mushi/admin/favicon.svg",
2493
2626
  "solo-boss-cloud": "https://solo-boss.kensaur.us/favicon.svg",
2494
2627
  "glot-it": "https://kensaur.us/glot-it/apple-touch-icon.png",
2495
- 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"
2496
2635
  };
2497
2636
  var UNTRUSTED_FAVICON_HOSTS = [
2498
2637
  "www.google.com",
@@ -2672,6 +2811,7 @@ exports.MAX_SCREENSHOT_DATA_URL_BYTES = MAX_SCREENSHOT_DATA_URL_BYTES;
2672
2811
  exports.MUSHI_ACCENT_SHADOW = MUSHI_ACCENT_SHADOW;
2673
2812
  exports.MUSHI_BANNER_BRAND_BORDER = MUSHI_BANNER_BRAND_BORDER;
2674
2813
  exports.MUSHI_BANNER_NEON = MUSHI_BANNER_NEON;
2814
+ exports.MUSHI_BEACON_MAX_BYTES = MUSHI_BEACON_MAX_BYTES;
2675
2815
  exports.MUSHI_BORDER = MUSHI_BORDER;
2676
2816
  exports.MUSHI_COLORS_DARK = MUSHI_COLORS_DARK;
2677
2817
  exports.MUSHI_COLORS_LIGHT = MUSHI_COLORS_LIGHT;
@@ -2699,6 +2839,7 @@ exports.MUSHI_TYPE = MUSHI_TYPE;
2699
2839
  exports.MUSHI_USER_TOKEN_HEADER = MUSHI_USER_TOKEN_HEADER;
2700
2840
  exports.MUSHI_Z = MUSHI_Z;
2701
2841
  exports.REGION_ENDPOINTS = REGION_ENDPOINTS;
2842
+ exports.buildBeaconEnvelope = buildBeaconEnvelope;
2702
2843
  exports.buildIdentityClaims = buildIdentityClaims;
2703
2844
  exports.buildSdkIngestHeaders = buildSdkIngestHeaders;
2704
2845
  exports.captureEnvironment = captureEnvironment;
@@ -2714,8 +2855,11 @@ exports.destroySessionTracker = destroySessionTracker;
2714
2855
  exports.diagnoseEnvConfig = diagnoseEnvConfig;
2715
2856
  exports.estimateJsonBytes = estimateJsonBytes;
2716
2857
  exports.expandPreset = expandPreset;
2858
+ exports.extractTunnelPath = extractTunnelPath;
2717
2859
  exports.faviconUrlCandidates = faviconUrlCandidates;
2860
+ exports.flushLastOutboundOnUnload = flushLastOutboundOnUnload;
2718
2861
  exports.formatBytes = formatBytes;
2862
+ exports.getBackoffDelay = getBackoffDelay;
2719
2863
  exports.getDeviceFingerprintHash = getDeviceFingerprintHash;
2720
2864
  exports.getReporterToken = getReporterToken;
2721
2865
  exports.getSessionId = getSessionId;
@@ -2723,7 +2867,12 @@ exports.githubRepoDomainHint = githubRepoDomainHint;
2723
2867
  exports.hmacSha256Hex = hmacSha256Hex;
2724
2868
  exports.initSessionTracker = initSessionTracker;
2725
2869
  exports.isLikelyGenericFavicon = isLikelyGenericFavicon;
2870
+ exports.isMushiBeaconEnvelope = isMushiBeaconEnvelope;
2871
+ exports.isPageUnloading = isPageUnloading;
2872
+ exports.isSameOriginOrRelative = isSameOriginOrRelative;
2726
2873
  exports.isUntrustedFaviconUrl = isUntrustedFaviconUrl;
2874
+ exports.markPageUnloading = markPageUnloading;
2875
+ exports.matchesErrorFilter = matchesErrorFilter;
2727
2876
  exports.mushiPalette = mushiPalette;
2728
2877
  exports.mushiTokens = mushiTokens;
2729
2878
  exports.newUuid = newUuid;
@@ -2731,6 +2880,7 @@ exports.noopLogger = noopLogger;
2731
2880
  exports.normaliseThrown = normaliseThrown;
2732
2881
  exports.originToDomain = originToDomain;
2733
2882
  exports.parseIdentityToken = parseIdentityToken;
2883
+ exports.parseRetryAfter = parseRetryAfter;
2734
2884
  exports.projectFaviconUrlCandidates = projectFaviconUrlCandidates;
2735
2885
  exports.projectInitials = projectInitials;
2736
2886
  exports.projectInitialsThemeIndex = projectInitialsThemeIndex;
@@ -2739,11 +2889,14 @@ exports.resolveEnvConfig = resolveEnvConfig;
2739
2889
  exports.resolveProjectDomain = resolveProjectDomain;
2740
2890
  exports.resolveProjectFaviconDomains = resolveProjectFaviconDomains;
2741
2891
  exports.resolveRegionEndpoint = resolveRegionEndpoint;
2892
+ exports.resolveRequestBaseUrl = resolveRequestBaseUrl;
2742
2893
  exports.resolveWidgetAccent = resolveWidgetAccent;
2743
2894
  exports.safeWidgetHex = safeWidgetHex;
2744
2895
  exports.scrubPii = scrubPii;
2745
2896
  exports.scrubUrl = scrubUrl;
2897
+ exports.sendOnUnload = sendOnUnload;
2746
2898
  exports.sha256Hex = sha256Hex;
2899
+ exports.shouldDropCapturedError = shouldDropCapturedError;
2747
2900
  exports.trackPageView = trackPageView;
2748
2901
  exports.updateSessionIdentity = updateSessionIdentity;
2749
2902
  exports.validateConfig = validateConfig;