@mushi-mushi/web 0.7.0 → 0.8.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 +128 -5
- package/dist/index.cjs +876 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -7
- package/dist/index.d.ts +61 -7
- package/dist/index.js +877 -94
- package/dist/index.js.map +1 -1
- package/dist/test-utils.cjs +17 -0
- package/dist/test-utils.cjs.map +1 -1
- package/dist/test-utils.d.cts +21 -2
- package/dist/test-utils.d.ts +21 -2
- package/dist/test-utils.js +15 -1
- package/dist/test-utils.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -19,8 +19,16 @@ Browser SDK for Mushi Mushi — embeddable bug reporting widget with Shadow DOM
|
|
|
19
19
|
- Light/dark theme with auto-detection (`prefers-color-scheme`)
|
|
20
20
|
- **Trigger modes** (0.6+) — `auto` / `edge-tab` / `attach` (bring-your-own-button) / `manual` / `hidden`, plus `smartHide`, `hideOnSelector`, `hideOnRoutes`, configurable `inset` and `respectSafeArea`
|
|
21
21
|
- **Runtime trigger APIs** — `Mushi.show()`, `Mushi.hide()`, `Mushi.attachTo(selector)`, `Mushi.setTrigger(mode)`, `Mushi.openWith(category)`
|
|
22
|
+
- **Widget anchor** (0.9+) — `widget.anchor` accepts raw CSS (including `var()` and `env()`) so the launcher honours your app shell's tab bars, docks, mini-players, and cookie banners without Shadow-DOM patching
|
|
23
|
+
- **Presets** (0.9+) — `preset: 'production-calm' | 'beta-loud' | 'internal-debug' | 'manual-only'` flips a coherent bundle of widget / capture / proactive defaults so prod apps stay quiet and internal builds stay loud
|
|
22
24
|
- **Proactive triggers** — rage click, long task, API cascade failure detection
|
|
23
25
|
- **Report fatigue prevention** — session limits, cooldowns, permanent suppression
|
|
26
|
+
- **Privacy controls** (0.9.1+) — `privacy.maskSelectors`, `privacy.blockSelectors`, `privacy.allowUserRemoveScreenshot` for selector-level screenshot redaction and a one-tap "Remove screenshot" button in the panel
|
|
27
|
+
- **Repro timeline** (0.10+) — auto-captures route changes, clicks, and SDK lifecycle into a normalised `MushiReport.timeline`; pair with `Mushi.setScreen({ name, route, feature })` for screen-level grouping in the admin
|
|
28
|
+
- **Two-way replies** (0.11+) — the panel ships a "Your reports" view that polls comments authored by the dev team and lets the reporter reply, all signed with HMAC against the public API key (no auth user required)
|
|
29
|
+
- **SDK identity & freshness** (0.8+) — every report ships `sdkPackage` + `sdkVersion`; the widget polls `/v1/sdk/latest-version` and surfaces an outdated banner (configurable via `widget.outdatedBanner`)
|
|
30
|
+
- **Self-noise filters** (0.7.1+) — internal Mushi requests are tagged with `X-Mushi-Internal` and excluded from network capture + `apiCascade`; configurable `capture.ignoreUrls` and `proactive.apiCascade.ignoreUrls` for host-app endpoints you also don't want counted
|
|
31
|
+
- **`Mushi.diagnose()`** (0.7.1+) — one-call CSP / runtime-config / capture / widget health check (also runs without an init for pre-install smoke tests)
|
|
24
32
|
- Keyboard-first: `Esc` to close, `⌘/Ctrl + Enter` to submit, focus-trapped panel
|
|
25
33
|
- Honours `prefers-reduced-motion` (animations collapse to instant)
|
|
26
34
|
|
|
@@ -163,6 +171,118 @@ setupProactiveTriggers({
|
|
|
163
171
|
});
|
|
164
172
|
```
|
|
165
173
|
|
|
174
|
+
### Self-noise filters and CSP diagnostics
|
|
175
|
+
|
|
176
|
+
Out of the box the SDK tags every request it makes with `X-Mushi-Internal` and skips
|
|
177
|
+
those URLs in `capture.network` + `proactive.apiCascade`, so an unconfigured CSP
|
|
178
|
+
or a flaky local Supabase stack can no longer make Mushi report on Mushi:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
Mushi.init({
|
|
182
|
+
projectId: 'proj_xxx',
|
|
183
|
+
apiKey: 'mushi_xxx',
|
|
184
|
+
// 'auto' (default) skips the runtime-config fetch on localhost endpoints —
|
|
185
|
+
// pass `true` to force it everywhere, `false` to disable entirely.
|
|
186
|
+
runtimeConfig: 'auto',
|
|
187
|
+
capture: {
|
|
188
|
+
network: true,
|
|
189
|
+
ignoreUrls: [/\/api\/internal\//, 'https://posthog.example.com'],
|
|
190
|
+
},
|
|
191
|
+
proactive: {
|
|
192
|
+
apiCascade: {
|
|
193
|
+
enabled: true,
|
|
194
|
+
ignoreUrls: ['https://feature-flags.example.com'],
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const health = await Mushi.diagnose();
|
|
200
|
+
// → { apiEndpointReachable, cspAllowsEndpoint, widgetMounted, shadowDomAvailable,
|
|
201
|
+
// dialogSupported, runtimeConfigLoaded, captureScreenshotAvailable,
|
|
202
|
+
// captureNetworkIntercepting, sdkVersion }
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`Mushi.diagnose()` works **before** `Mushi.init()` too — call it from a debug
|
|
206
|
+
console or installer wizard to surface CSP / endpoint problems with zero risk
|
|
207
|
+
of accidentally booting the widget.
|
|
208
|
+
|
|
209
|
+
### Presets and widget anchor
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
Mushi.init({
|
|
213
|
+
projectId: 'proj_xxx',
|
|
214
|
+
apiKey: 'mushi_xxx',
|
|
215
|
+
// production-calm = manual trigger, screenshot only on report, no proactive prompts
|
|
216
|
+
// beta-loud = proactive triggers + console + network always on
|
|
217
|
+
// internal-debug = above + verbose debug + always-on screenshot
|
|
218
|
+
// manual-only = trigger only, every proactive surface off
|
|
219
|
+
preset: 'production-calm',
|
|
220
|
+
widget: {
|
|
221
|
+
// Raw CSS strings (including `var()` and `env()`) win over `position` /
|
|
222
|
+
// `inset` so the launcher tracks your app shell's tab bars or mini-player.
|
|
223
|
+
anchor: {
|
|
224
|
+
bottom: 'calc(var(--app-dock-h, 0px) + env(safe-area-inset-bottom))',
|
|
225
|
+
right: 'calc(0.75rem + env(safe-area-inset-right))',
|
|
226
|
+
},
|
|
227
|
+
brandFooter: true,
|
|
228
|
+
outdatedBanner: 'auto',
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Privacy and screenshot redaction
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
Mushi.init({
|
|
237
|
+
projectId: 'proj_xxx',
|
|
238
|
+
apiKey: 'mushi_xxx',
|
|
239
|
+
privacy: {
|
|
240
|
+
maskSelectors: ['[data-private]', 'input', '.thai-answer-draft'],
|
|
241
|
+
blockSelectors: ['[data-payment]', '[data-auth-token]'],
|
|
242
|
+
allowUserRemoveScreenshot: true,
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`maskSelectors` paints a solid block over matching elements before serialisation;
|
|
248
|
+
`blockSelectors` removes them entirely. `allowUserRemoveScreenshot` adds a
|
|
249
|
+
"Remove screenshot" affordance next to the attachment chip in the panel, so the
|
|
250
|
+
reporter can yank a screenshot they didn't realise contained sensitive data.
|
|
251
|
+
|
|
252
|
+
### Repro timeline and `setScreen()`
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
const mushi = Mushi.init({ /* ... */ });
|
|
256
|
+
mushi.setScreen({ name: 'Chat', route: '/chat', feature: 'roleplay' });
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The SDK auto-records `route` (initial + `pushState` / `popstate` / `hashchange`),
|
|
260
|
+
`click` (with selector + text snippet), and `screen` events into a 120-entry
|
|
261
|
+
ring buffer. Submissions ship the trail as `MushiReport.timeline` and the admin
|
|
262
|
+
console renders it as a chronological "what happened before the report" card on
|
|
263
|
+
`/reports/:id`.
|
|
264
|
+
|
|
265
|
+
### Two-way replies (Your reports)
|
|
266
|
+
|
|
267
|
+
The widget mounts a "Your reports" tab that lists this reporter's history,
|
|
268
|
+
unread admin replies (with a count badge on the trigger), and a reply input.
|
|
269
|
+
Calls are signed with an HMAC over `projectId.timestamp.sha256(reporterToken)`
|
|
270
|
+
using the public API key as the secret, so no Supabase auth is required.
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
Mushi.init({
|
|
274
|
+
projectId: 'proj_xxx',
|
|
275
|
+
apiKey: 'mushi_xxx',
|
|
276
|
+
widget: { brandFooter: true, outdatedBanner: 'auto' },
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Endpoints (Edge Function): `GET /v1/reporter/reports`,
|
|
281
|
+
`GET /v1/reporter/reports/:id/comments`, `POST /v1/reporter/reports/:id/reply`.
|
|
282
|
+
The DB-side `report_comments_fanout_to_reporter` trigger creates a
|
|
283
|
+
`reporter_notifications` row whenever a `visible_to_reporter` admin comment
|
|
284
|
+
lands, so the unread count stays in sync without polling.
|
|
285
|
+
|
|
166
286
|
## Test utilities (`./test-utils`)
|
|
167
287
|
|
|
168
288
|
Deterministic Playwright / jsdom helpers, published as a separate
|
|
@@ -172,11 +292,14 @@ entry-point so production bundles pay nothing for them:
|
|
|
172
292
|
import { triggerBug, openReport, waitForQueueDrain } from '@mushi-mushi/web/test-utils';
|
|
173
293
|
```
|
|
174
294
|
|
|
175
|
-
| Export
|
|
176
|
-
|
|
177
|
-
| `triggerBug(opts?)`
|
|
178
|
-
| `openReport(cat?)`
|
|
179
|
-
| `
|
|
295
|
+
| Export | Purpose |
|
|
296
|
+
|------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
|
|
297
|
+
| `triggerBug(opts?)` | Submit a report bypassing the widget. Returns the server-assigned id. |
|
|
298
|
+
| `openReport(cat?)` | Open the widget programmatically without submitting. |
|
|
299
|
+
| `openMushiWidget(cat?)` | Alias for `openReport` — Playwright-friendly name for the dogfood contract suite. |
|
|
300
|
+
| `waitForQueueDrain` | Resolve once the offline queue is empty (number remaining at timeout). |
|
|
301
|
+
| `expectMushiReady(opts?)` | Resolve with a `MushiDiagnosticsResult` once the SDK is initialised and reachable. Fails if `apiEndpointReachable === false`. |
|
|
302
|
+
| `expectNoMushiSelfCascade()` | Run an action and assert no internal Mushi request fired the `api_cascade` proactive trigger. Catches CSP / runtime-config self-noise. |
|
|
180
303
|
|
|
181
304
|
Every helper no-ops when `Mushi.getInstance()` returns `null`, so
|
|
182
305
|
conditional-wiring tests (e.g. cloud vs local targets) don't need to
|