@mushi-mushi/core
Shared types, HTTP client, offline queue helpers, and config used by every Mushi Mushi framework SDK. It is framework-agnostic, and you rarely install it directly.
Install
pnpm add @mushi-mushi/coreEvery SDK needs a project ID and an API key. Create a free account (no card) and create a project to get both; Project ID & API keys explains each one.
API
Mushi.init(config) (web) / MushiProvider (React Native)
interface MushiConfig {
projectId: string // UUID from the console Projects page
apiKey: string // report:write key — shown once at project creation
apiEndpoint?: string // override for self-hosted deployments
/** Automatic error reports only (0–1). User feedback always sends. Default 1. */
sampleRate?: number
/** Session-replay sampling (0–1). Decision at init. Default 1. */
replaySampleRate?: number
/** Drop or mutate any report after PII scrub. Prefer over beforeSendFeedback. */
beforeSend?: (report: MushiReport) => MushiReport | null | Promise<MushiReport | null>
}Get both values in one step by running mushi login or by creating a project in
the Mushi Mushi console . The ID and
key are shown together on the success screen after project creation.
Sampling + beforeSend examples: @mushi-mushi/web.
MushiWidgetConfig (selected keys)
interface MushiWidgetConfig {
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
theme?: 'auto' | 'light' | 'dark' | 'inherit'
trigger?: 'auto' | 'banner' | 'edge-tab' | 'attach' | 'manual' | 'hidden'
/** 1.19+ — privacy caption under screenshot preview */
screenshotSensitiveHint?: boolean | string
}See docs/SDK_SCREENSHOT_PREVIEW.md
for the full preview + consent flow.
captureEvent(input)
Send a report from code through the SDK instance (Mushi.getInstance() on
web). The input is MushiCaptureEventInput:
interface MushiCaptureEventInput {
description: string
category?: 'bug' | 'slow' | 'visual' | 'confusing' | 'other'
severity?: 'critical' | 'high' | 'medium' | 'low'
component?: string
tags?: Record<string, string | number | boolean>
source?: string
error?: { name?: string; message?: string; stack?: string }
metadata?: Record<string, unknown>
}The full wire shape the SDKs build for you is MushiReport (environment,
console and network logs, screenshotDataUrl, reporter token, and more).
Offline queue
createOfflineQueue() is the IndexedDB queue the web SDK uses. Queued
reports send automatically when the connection returns; there is no manual
flush call to make.
PII scrubbing utilities (1.27+)
The redactor that every SDK uses at capture time is exported so you can reuse
it in your own beforeSend hook, server code, or logs. It runs entirely in
your process, so scrubbed values never leave the client.
import { scrubPii, scrubUrl, createPiiScrubber } from '@mushi-mushi/core'
// Free text → typed placeholders
scrubPii('email jake@x.com or call +1 415 555 0100')
// → 'email [REDACTED_EMAIL] or call [REDACTED_PHONE]'
// URL query values → redacted; keys and path preserved for debuggability
scrubUrl('/articles?tag=dragons&token=eyJhbGciOiJIUzI1NiJ9.payload.sig')
// → '/articles?tag=dragons&token=[Scrubbed]'scrubPii(text, config?) replaces sensitive substrings anywhere in free
text. scrubUrl(url, config?) redacts only query-string values (both the
standard ?… part and any ?… inside a hash-router fragment, #/path?…) and
leaves keys and path segments intact. scrubUrl redacts values under
known-sensitive keys (any key containing token, jwt, passw, pwd,
secret, api_key/apikey, auth, session, signature, email, phone
or ssn, case-insensitive, plus the exact keys key / code / sig)
to [Scrubbed], then pattern-scrubs the remaining values so a JWT hiding under
an innocent key (?next=eyJ…) is still caught. Both accept absolute or relative
URLs and never throw.
Patterns and their placeholders (all matched case-sensitively unless noted):
| Data | Placeholder | Default |
|---|---|---|
[REDACTED_EMAIL] | on | |
| Phone number | [REDACTED_PHONE] | on |
| US SSN | [REDACTED_SSN] | on |
| Credit-card PAN | [REDACTED_CC] | on |
JWT (eyJ….….…) | [REDACTED_JWT] | on |
| AWS access key | [REDACTED_AWS_KEY] | on |
| AWS secret access key (case-insensitive key name) | aws_secret_access_key=[REDACTED_AWS_SECRET] | on |
| Stripe secret / restricted key | [REDACTED_STRIPE_KEY] | on |
| Stripe publishable key | [REDACTED_STRIPE_PK] | on |
| Slack token | [REDACTED_SLACK_TOKEN] | on |
| GitHub personal access token | [REDACTED_GITHUB_PAT] | on |
| OpenAI key | [REDACTED_OPENAI_KEY] | on |
| Anthropic key | [REDACTED_ANTHROPIC_KEY] | on |
| Google API key | [REDACTED_GOOGLE_KEY] | on |
| IPv4 address | [REDACTED_IP] | off |
| IPv6 address | [REDACTED_IPV6] | off |
IP scrubbing is off by default because IPs are frequently load-bearing for
debugging (rate-limit keys, geo). Toggle any pattern via PiiScrubberConfig:
interface PiiScrubberConfig {
emails?: boolean // default true
phones?: boolean // default true
creditCards?: boolean // default true
ssns?: boolean // default true
secretTokens?: boolean // default true — JWTs + vendor API keys
ipAddresses?: boolean // default false — IPv4
ipv6?: boolean // default false
}
// e.g. also redact IPs, keep everything else on
scrubPii(text, { ipAddresses: true })createPiiScrubber(config) returns { scrub, scrubObject } when you want to
build the scrubber once and reuse it across many strings or object fields:
const { scrub, scrubObject } = createPiiScrubber({ ipAddresses: true })
scrub(logLine)
scrubObject(record, ['description', 'url']) // scrubs only those string keysThe pattern set is the single source of truth shared with the Flutter SDK’s generated copy, so the two scrubbers can’t drift.
Region resolution
import { resolveRegionEndpoint, REGION_ENDPOINTS } from '@mushi-mushi/core'
const url = await resolveRegionEndpoint({
projectId: 'YOUR_PROJECT_ID',
apiEndpoint: REGION_ENDPOINTS.us,
region: 'eu',
})REGION_ENDPOINTS contains the canonical URLs:
const REGION_ENDPOINTS = {
us: 'https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api',
eu: 'https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api',
jp: 'https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api',
}