@mushi-mushi/core 1.27.2 → 1.29.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/dist/index.d.cts CHANGED
@@ -26,6 +26,22 @@ interface MushiConfig {
26
26
  * host apps. Set false for fully static/offline deployments.
27
27
  */
28
28
  runtimeConfig?: boolean | 'auto';
29
+ /**
30
+ * Product analytics (`Mushi.track()`, Users & Funnels). Enabled by default
31
+ * and keyed on the same opaque per-project reporter token as sessions;
32
+ * honours DNT / GPC. Set `enabled: false` to opt out, or
33
+ * `consent: 'required'` to buffer until `setConsent('granted')`.
34
+ * Session tracking passes the same gate: `enabled`, DNT / GPC, bot
35
+ * exclusion and consent all apply to it too.
36
+ */
37
+ analytics?: MushiAnalyticsConfig;
38
+ /**
39
+ * Session lifecycle tracking for the console's Activity and Users views
40
+ * (session start / heartbeat / end, page views). Default true. Runs only
41
+ * while the `analytics` gate allows tracking; `false` turns it off even
42
+ * then.
43
+ */
44
+ trackSessions?: boolean;
29
45
  sentry?: MushiSentryConfig;
30
46
  widget?: MushiWidgetConfig;
31
47
  capture?: MushiCaptureConfig;
@@ -46,6 +62,31 @@ interface MushiConfig {
46
62
  * when omitted (legacy Vercel Analytics wiring).
47
63
  */
48
64
  appVersion?: string;
65
+ /**
66
+ * Same-origin (or absolute) ingest proxy. When set, every SDK HTTP call
67
+ * goes here instead of `apiEndpoint` so ad blockers that drop third-party
68
+ * `*.supabase.co/functions` hosts cannot silently eat reports.
69
+ * Relative URLs resolve against the page origin — that is the recommended
70
+ * form (no CORS preflight; matches Sentry `tunnel`).
71
+ *
72
+ * @example
73
+ * tunnel: '/api/mushi-tunnel'
74
+ */
75
+ tunnel?: string;
76
+ /**
77
+ * Drop automatic error reports whose message matches any string (substring)
78
+ * or RegExp. Never applied to user-submitted widget feedback.
79
+ */
80
+ ignoreErrors?: Array<string | RegExp>;
81
+ /**
82
+ * Drop automatic error reports whose script URL / stack filename matches.
83
+ */
84
+ denyUrls?: Array<string | RegExp>;
85
+ /**
86
+ * When set, drop automatic error reports unless the script URL matches
87
+ * at least one entry.
88
+ */
89
+ allowUrls?: Array<string | RegExp>;
49
90
  /**
50
91
  * Probabilistic sampling rate for session-replay recording (rrweb/lite).
51
92
  * Range 0–1, default 1 (record all sessions). The sampling decision is made
@@ -59,6 +100,13 @@ interface MushiConfig {
59
100
  * replaySampleRate: 0.2 // record replay for ~20% of sessions
60
101
  */
61
102
  replaySampleRate?: number;
103
+ /**
104
+ * When session replay was sampled out, still start the rolling buffer for
105
+ * this fraction of sessions (0–1, default 0) so an error/feedback report
106
+ * can attach replay. Keep at 0 unless you have accepted the CPU/egress
107
+ * cost — do not set to 1 with `capture.replay: 'rrweb'`.
108
+ */
109
+ replaysOnErrorSampleRate?: number;
62
110
  /**
63
111
  * Probabilistic sampling rate for automatic (non-user-initiated) error
64
112
  * reports. Range 0–1, default 1 (send all). User-initiated feedback reports
@@ -166,7 +214,21 @@ interface MushiWidgetConfig {
166
214
  snapToEdge?: boolean;
167
215
  axis?: 'both' | 'x' | 'y';
168
216
  };
169
- /** Show the tiny "Powered by Mushi vX" footer inside the widget panel. */
217
+ /**
218
+ * Show the "Bug reports by Mushi" mark at the bottom of the widget panel.
219
+ * It links to the Mushi site (`utm_source=widget&utm_medium=powered-by` plus
220
+ * an anonymous `ref` derived from a SHA-256 prefix of the project id) and
221
+ * feeds the `loop_impression` / `loop_click` analytics events.
222
+ *
223
+ * Precedence (most specific wins):
224
+ * 1. An explicit value here — the MIT SDK config is a hard override and
225
+ * beats anything the runtime config sends.
226
+ * 2. `widget.brandFooter` from the runtime config (`GET /v1/sdk/config`):
227
+ * Mushi Cloud sends `true` for Free Cloud projects and `false` for paid
228
+ * plans; self-hosted servers send nothing. Opt out with one toggle in the
229
+ * console project settings.
230
+ * 3. Default `false`.
231
+ */
170
232
  brandFooter?: boolean;
171
233
  /** How the widget should surface SDK freshness warnings. Defaults to auto. */
172
234
  outdatedBanner?: 'auto' | 'banner' | 'console-only' | 'off';
@@ -406,6 +468,24 @@ interface MushiCaptureConfig {
406
468
  screenshotProvider?: () => Promise<string | null>;
407
469
  elementSelector?: boolean;
408
470
  replay?: 'sentry' | 'rrweb' | 'lite' | 'off';
471
+ /**
472
+ * How to load rrweb for `replay: 'rrweb'`. A published SDK cannot import
473
+ * rrweb itself: its bare `import('rrweb')` is invisible to your bundler, so
474
+ * rrweb never makes it into your build. Hand the import over and your
475
+ * bundler code-splits it like any other dynamic import. Install `rrweb`
476
+ * yourself; the chunk loads only for sessions sampled into replay.
477
+ *
478
+ * Without a loader the SDK uses a global `rrweb` (the UMD build from a
479
+ * script tag) when one exists, and otherwise records lite replay (clicks)
480
+ * and warns once in the console. Recording masks every input and every
481
+ * text node.
482
+ *
483
+ * @example
484
+ * capture: { replay: 'rrweb', rrweb: () => import('rrweb') }
485
+ */
486
+ rrweb?: () => Promise<{
487
+ record?: unknown;
488
+ }>;
409
489
  /**
410
490
  * Mushi Mushi v2.1 (whitepaper §6 hybrid mode): passive inventory
411
491
  * discovery. When enabled the SDK observes navigations and emits a
@@ -1353,8 +1433,23 @@ interface MushiSDKInstance {
1353
1433
  * Manually record a host-defined activity event (e.g. 'lesson_completed').
1354
1434
  * The SDK batches these and flushes to POST /v1/sdk/activity.
1355
1435
  * No-op when rewards are disabled or the user has not opted in.
1436
+ * For product analytics (funnels, paths, people) use `track()` instead.
1356
1437
  */
1357
1438
  recordActivity(action: string, metadata?: Record<string, unknown>): void;
1439
+ /**
1440
+ * Record a product-analytics event, e.g. `track('checkout_started', { plan: 'pro' })`.
1441
+ * Names must match `^[a-z][a-z0-9_]{1,63}$`; properties are flat
1442
+ * (string | number | boolean | null), PII-looking keys are dropped, and
1443
+ * every string value runs through the PII scrubber. Batched to
1444
+ * POST /v1/sdk/events. No-op under DNT/GPC, `analytics.enabled: false`,
1445
+ * or before consent when `analytics.consent === 'required'`.
1446
+ * Returns true when the event was queued.
1447
+ */
1448
+ track(event: string, properties?: Record<string, MushiPropertyValue>): boolean;
1449
+ /** Grant or deny analytics consent (persisted per project in localStorage). */
1450
+ setConsent(state: 'granted' | 'denied'): void;
1451
+ /** The anonymous id analytics events are keyed on, or null when tracking is off. */
1452
+ getAnonymousId(): string | null;
1358
1453
  /**
1359
1454
  * Briefly animate the bug-report trigger button to draw the user's
1360
1455
  * attention without opening the full widget. Ideal for subtle "feedback
@@ -1464,6 +1559,11 @@ interface MushiApiClient {
1464
1559
  postSessionEvent(payload: MushiSessionEventPayload): Promise<MushiApiResponse<{
1465
1560
  accepted: boolean;
1466
1561
  }>>;
1562
+ /** POST /v1/sdk/events — batched product-analytics events (Mushi.track()). */
1563
+ postProductEvents(payload: MushiProductEventPayload): Promise<MushiApiResponse<{
1564
+ accepted: number;
1565
+ dropped: number;
1566
+ }>>;
1467
1567
  listReporterReports(reporterToken: string): Promise<MushiApiResponse<{
1468
1568
  reports: MushiReporterReport[];
1469
1569
  }>>;
@@ -1616,12 +1716,56 @@ interface MushiSessionEventPayload {
1616
1716
  user_agent?: string | null;
1617
1717
  sdk_version?: string;
1618
1718
  }
1719
+ type MushiPropertyValue = string | number | boolean | null;
1720
+ /** Configuration for `Mushi.track()` product analytics. */
1721
+ interface MushiAnalyticsConfig {
1722
+ /** Master switch (default true). */
1723
+ enabled?: boolean;
1724
+ /** 'implied' (default) sends immediately; 'required' buffers until setConsent('granted'). */
1725
+ consent?: 'implied' | 'required';
1726
+ /** 0..1, decided once per person (default 1). */
1727
+ sampleRate?: number;
1728
+ /** Honour navigator.doNotTrack / globalPrivacyControl (default true). */
1729
+ respectDoNotTrack?: boolean;
1730
+ /**
1731
+ * Skip tracking in WebDriver-controlled browsers (Playwright, Puppeteer,
1732
+ * Selenium), headless Chrome, Lighthouse and crawler user agents, so test
1733
+ * runs and bots never count as users (default true). Set false to exercise
1734
+ * analytics from an end-to-end test.
1735
+ */
1736
+ excludeBots?: boolean;
1737
+ /** Emit `pageview` on history navigation (default false; sessions already record page views). */
1738
+ autoPageviews?: boolean;
1739
+ /** Flush cadence in ms (default 5000, min 1000). */
1740
+ flushIntervalMs?: number;
1741
+ /** Property keys allowed through the PII key filter. */
1742
+ propertyAllowlist?: string[];
1743
+ /** Which surface these events come from (default 'web'). */
1744
+ surface?: 'web' | 'console' | 'docs' | 'cli' | 'mcp' | 'server' | 'mobile';
1745
+ }
1746
+ /** Wire shape for POST /v1/sdk/events. */
1747
+ interface MushiProductEventPayload {
1748
+ anon_id?: string | null;
1749
+ user_id?: string | null;
1750
+ user_traits?: Record<string, unknown> | null;
1751
+ session_id?: string | null;
1752
+ sdk_version?: string | null;
1753
+ surface?: MushiAnalyticsConfig['surface'];
1754
+ events: Array<{
1755
+ name: string;
1756
+ ts?: string;
1757
+ properties?: Record<string, MushiPropertyValue>;
1758
+ dedup_key?: string;
1759
+ }>;
1760
+ }
1619
1761
  interface MushiApiResponse<T> {
1620
1762
  ok: boolean;
1621
1763
  data?: T;
1764
+ /** `status` is the HTTP status when the server answered; absent for network errors. */
1622
1765
  error?: {
1623
1766
  code: string;
1624
1767
  message: string;
1768
+ status?: number;
1625
1769
  };
1626
1770
  }
1627
1771
  interface MushiRuntimeSdkConfig {
@@ -1711,6 +1855,8 @@ interface MushiTesterReputation {
1711
1855
  total_points: number;
1712
1856
  }
1713
1857
 
1858
+ /** Replay the last POST via keepalive/beacon during pagehide. */
1859
+ declare function flushLastOutboundOnUnload(): boolean;
1714
1860
  /** Header carrying the signed end-user identity token (verified server-side). */
1715
1861
  declare const MUSHI_USER_TOKEN_HEADER = "X-Mushi-User-Token";
1716
1862
  /** Project scope header — paired with the API key on every SDK request. */
@@ -1759,14 +1905,101 @@ interface ApiClientOptions {
1759
1905
  * backend can verify it and scope identity-bound features to that user.
1760
1906
  */
1761
1907
  getUserToken?: () => string | null | undefined;
1908
+ /**
1909
+ * Same-origin (or absolute) proxy. When set, replaces `apiEndpoint` as the
1910
+ * request base so browser calls stay first-party. See `MushiConfig.tunnel`.
1911
+ */
1912
+ tunnel?: string;
1762
1913
  }
1763
1914
  declare const DEFAULT_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
1764
1915
  declare const MUSHI_INTERNAL_HEADER = "X-Mushi-Internal";
1765
1916
  declare const MUSHI_INTERNAL_INIT_MARKER = "__mushiInternal";
1766
1917
  type MushiInternalRequestKind = 'sdk-config' | 'report-submit' | 'report-status' | 'reporter-poll' | 'diagnose' | 'discovery' | 'community';
1918
+ /** Resolve the HTTP base: tunnel wins so ingest stays first-party. */
1919
+ declare function resolveRequestBaseUrl(apiEndpoint: string | undefined, tunnel?: string): string;
1767
1920
  declare const DEFAULT_TIMEOUT = 10000;
1768
1921
  declare const DEFAULT_MAX_RETRIES = 2;
1769
1922
  declare function createApiClient(options: ApiClientOptions): MushiApiClient;
1923
+ declare function getBackoffDelay(attempt: number): number;
1924
+ /**
1925
+ * Parse a `Retry-After` header into a delay in milliseconds. The header comes
1926
+ * in two shapes (RFC 9110): delta-seconds, or an HTTP-date to wait until.
1927
+ * Returns null when the header is absent, malformed, or negative, so the
1928
+ * caller falls back to its own backoff curve.
1929
+ */
1930
+ declare function parseRetryAfter(value: string | null | undefined, now?: number): number | null;
1931
+
1932
+ /**
1933
+ * FILE: packages/core/src/error-filters.ts
1934
+ * PURPOSE: Sentry-grade drop filters for automatic error capture.
1935
+ *
1936
+ * OVERVIEW:
1937
+ * - ignoreErrors — drop when the error message matches a string or RegExp
1938
+ * - denyUrls — drop when the script URL / stack frame matches
1939
+ * - allowUrls — when set, drop unless the script URL matches at least one
1940
+ *
1941
+ * USAGE:
1942
+ * - captureException / window.onerror / unhandledrejection only
1943
+ * - Never applied to user-submitted widget feedback
1944
+ *
1945
+ * NOTES:
1946
+ * - String matchers are case-sensitive substrings (Sentry JS semantics)
1947
+ * - Invalid RegExp sources are treated as non-matches, not throws
1948
+ */
1949
+ type MushiErrorFilter = string | RegExp;
1950
+ declare function matchesErrorFilter(value: string | undefined | null, filters: readonly MushiErrorFilter[] | undefined): boolean;
1951
+ declare function shouldDropCapturedError(input: {
1952
+ message: string;
1953
+ filename?: string | null;
1954
+ ignoreErrors?: readonly MushiErrorFilter[];
1955
+ denyUrls?: readonly MushiErrorFilter[];
1956
+ allowUrls?: readonly MushiErrorFilter[];
1957
+ }): boolean;
1958
+
1959
+ /**
1960
+ * FILE: packages/core/src/unload-transport.ts
1961
+ * PURPOSE: Pagehide-safe report delivery (fetch keepalive + sendBeacon).
1962
+ *
1963
+ * OVERVIEW:
1964
+ * - fetch({ keepalive: true }) keeps custom X-Mushi-* headers (Sentry 2024+ primary)
1965
+ * - sendBeacon cannot set headers, so it wraps credentials in a beacon envelope
1966
+ * that only the same-origin tunnel unwraps
1967
+ *
1968
+ * NOTES:
1969
+ * - Beacon payloads over 60 KiB are skipped (browser sendBeacon limit ~64 KiB)
1970
+ * - Never put the API key in the query string
1971
+ */
1972
+ declare const MUSHI_BEACON_MAX_BYTES: number;
1973
+ interface MushiBeaconEnvelope {
1974
+ mushiBeacon: true;
1975
+ path: string;
1976
+ headers: Record<string, string>;
1977
+ body: unknown;
1978
+ }
1979
+ declare function isMushiBeaconEnvelope(value: unknown): value is MushiBeaconEnvelope;
1980
+ declare function buildBeaconEnvelope(opts: {
1981
+ path: string;
1982
+ headers: Record<string, string>;
1983
+ body: unknown;
1984
+ }): MushiBeaconEnvelope;
1985
+ /** True when the URL is same-origin or relative (safe for sendBeacon + tunnel). */
1986
+ declare function isSameOriginOrRelative(url: string, origin?: string): boolean;
1987
+ declare function extractTunnelPath(url: string): string;
1988
+ interface SendOnUnloadOptions {
1989
+ url: string;
1990
+ headers: Record<string, string>;
1991
+ body: string;
1992
+ path?: string;
1993
+ fetchImpl?: typeof fetch;
1994
+ sendBeacon?: (url: string, data: Blob) => boolean;
1995
+ }
1996
+ /**
1997
+ * Deliver one JSON POST during pagehide. Prefers fetch keepalive (headers).
1998
+ * Falls back to sendBeacon with a credential envelope on same-origin URLs.
1999
+ */
2000
+ declare function sendOnUnload(opts: SendOnUnloadOptions): boolean;
2001
+ declare function markPageUnloading(): void;
2002
+ declare function isPageUnloading(): boolean;
1770
2003
 
1771
2004
  /**
1772
2005
  * C7: Data residency region resolution.
@@ -2038,31 +2271,62 @@ declare function getSessionId(): string;
2038
2271
  * no offline queue, fire-and-forget after visibility-change.
2039
2272
  *
2040
2273
  * Privacy:
2274
+ * - Passes the same gate as product events (analytics-gate.ts): nothing is
2275
+ * sent when `analytics.enabled` is false, under DNT / GPC, in automation
2276
+ * or crawler browsers, or before consent is granted. A later
2277
+ * setConsent('granted') starts the session; 'denied' stops it mid-page.
2041
2278
  * - No PII by default. `reporter_token_hash` is the same opaque device
2042
2279
  * fingerprint already used in reports. `user_id_hash` is only set when
2043
- * the host app calls Mushi.identify().
2280
+ * the host app identifies the user.
2281
+ * - Routes are sent as the pathname only and the referrer as its origin, so
2282
+ * query strings (reset tokens, emails in links) never leave the page.
2044
2283
  * - Respects the SDK-level `trackSessions: false` opt-out option.
2045
- * - Respects a DNT / Global Privacy Control header at the SDK init layer;
2046
- * callers should check `shouldRespectDnt()` before calling `initSession`.
2284
+ *
2285
+ * SPA page views:
2286
+ * The history patch below decides push-vs-replace at wrap time (comparing
2287
+ * against `history.pushState` inside the wrapper never matches, because by
2288
+ * then that property is the wrapper). Hosts that already own a history
2289
+ * patch (`@mushi-mushi/web`'s shared one) pass `patchHistory: false` and
2290
+ * call `trackPageView()` themselves — a second wrapper assigned to
2291
+ * `history.pushState` silently discards this one, which is how the web SDK
2292
+ * recorded zero SPA page views across >1,000 sessions per project.
2047
2293
  */
2048
2294
 
2049
2295
  interface SessionTrackerOptions {
2050
2296
  client: MushiApiClient;
2051
2297
  sdkVersion?: string;
2298
+ /** The raw per-project reporter token; the server stores only its sha256. */
2299
+ reporterToken?: string | null;
2300
+ /** @deprecated Renamed to `reporterToken` (it always carried the raw token, never a hash). */
2052
2301
  reporterTokenHash?: string | null;
2053
2302
  userIdHash?: string | null;
2303
+ /**
2304
+ * Install the built-in `history.pushState` / `popstate` patch that emits
2305
+ * `page_view` events. Default `true`. Pass `false` when the host already
2306
+ * owns a history patch and will call `trackPageView()` itself — stacking a
2307
+ * second wrapper on `history.pushState` is exactly how this tracker's page
2308
+ * views got overwritten and silently dropped in `@mushi-mushi/web`.
2309
+ */
2310
+ patchHistory?: boolean;
2311
+ /** Project the stored analytics consent is keyed on (the SDK passes its projectId). */
2312
+ projectId?: string;
2313
+ /** The SDK's `analytics` block: sessions pass the same gate as product events. */
2314
+ analytics?: MushiAnalyticsConfig;
2054
2315
  }
2055
2316
  /**
2056
2317
  * Initialise the session tracker. Call once at SDK init for web environments.
2057
2318
  * Subsequent calls on the same page are no-ops (idempotent).
2058
2319
  *
2059
- * Emits: session_start immediately, heartbeats every minute, session_end on
2060
- * visibilitychange/pagehide, and page_view on history API navigation.
2320
+ * Emits, once the analytics gate allows it: session_start, heartbeats every
2321
+ * minute, session_end on visibilitychange/pagehide, and page_view on history
2322
+ * API navigation. Under `consent: 'required'` nothing is sent until
2323
+ * setConsent('granted').
2061
2324
  */
2062
2325
  declare function initSessionTracker(opts: SessionTrackerOptions): void;
2063
2326
  /**
2064
2327
  * Record a page view manually — call from framework router hooks where the
2065
- * history patch may fire too early (e.g. React Router v7 loader transitions).
2328
+ * history patch may fire too early (e.g. React Router v7 loader transitions),
2329
+ * or from a host that initialised with `patchHistory: false`.
2066
2330
  */
2067
2331
  declare function trackPageView(route?: string): void;
2068
2332
  /** Update the user identity after a Mushi.identify() call. */
@@ -2070,6 +2334,287 @@ declare function updateSessionIdentity(userIdHash: string | null): void;
2070
2334
  /** Tear down timers (e.g. in tests or SSR environments). */
2071
2335
  declare function destroySessionTracker(): void;
2072
2336
 
2337
+ /**
2338
+ * FILE: packages/core/src/analytics-taxonomy.ts
2339
+ * PURPOSE: Single source of truth for Mushi's product-analytics vocabulary —
2340
+ * the events Mushi emits about its OWN funnel (dogfooding the SDK on
2341
+ * the landing site, docs, console, CLI and MCP) plus the validation
2342
+ * rules every `Mushi.track()` call obeys, for every project.
2343
+ *
2344
+ * Naming: `object_verb`, snake_case, past tense for completions, `_view` for
2345
+ * exposure, `_click` for intent. Every event carries `$surface`.
2346
+ *
2347
+ * Mirror: packages/server/supabase/functions/_shared/analytics-taxonomy.generated.ts
2348
+ * is generated from this file by `node scripts/gen-analytics-taxonomy.mjs`
2349
+ * (CI runs `--check`). Edge functions cannot import workspace packages.
2350
+ */
2351
+
2352
+ /** Event names: lowercase, start with a letter, 2–64 chars. */
2353
+ declare const EVENT_NAME_RE: RegExp;
2354
+ /** Hard caps applied client- and server-side. */
2355
+ declare const EVENT_PROPERTY_LIMITS: {
2356
+ readonly maxKeys: 40;
2357
+ readonly maxKeyLength: 64;
2358
+ readonly maxValueLength: 256;
2359
+ readonly maxBytes: 8192;
2360
+ /** Client batch size before an immediate flush. */
2361
+ readonly maxClientBatch: 20;
2362
+ /** Server-side per-request ceiling. */
2363
+ readonly maxServerBatch: 50;
2364
+ /** Distinct event names per project before the server returns 422. */
2365
+ readonly maxEventNamesPerProject: 200;
2366
+ };
2367
+ declare const MUSHI_SURFACES: readonly ["web", "console", "docs", "cli", "mcp", "server", "mobile"];
2368
+ type MushiSurface = (typeof MUSHI_SURFACES)[number];
2369
+ /**
2370
+ * Mushi's own funnel vocabulary. One entry per line — the generator parses
2371
+ * this block line by line, so keep the `name: { surface: '…', required: […] }`
2372
+ * shape. An event with more than one emitter lists every surface:
2373
+ * `surface: ['console', 'mcp']`.
2374
+ */
2375
+ declare const MUSHI_EVENTS: {
2376
+ readonly page_view: {
2377
+ readonly surface: readonly ["web", "docs", "console"];
2378
+ readonly required: readonly [];
2379
+ };
2380
+ readonly docs_page_view: {
2381
+ readonly surface: "docs";
2382
+ readonly required: readonly ["route"];
2383
+ };
2384
+ readonly landing_view: {
2385
+ readonly surface: "docs";
2386
+ readonly required: readonly [];
2387
+ };
2388
+ readonly cta_click: {
2389
+ readonly surface: "docs";
2390
+ readonly required: readonly ["cta_id", "location"];
2391
+ };
2392
+ readonly quickstart_view: {
2393
+ readonly surface: "docs";
2394
+ readonly required: readonly [];
2395
+ };
2396
+ readonly pricing_view: {
2397
+ readonly surface: "docs";
2398
+ readonly required: readonly [];
2399
+ };
2400
+ readonly connect_demo_click: {
2401
+ readonly surface: "docs";
2402
+ readonly required: readonly ["href"];
2403
+ };
2404
+ readonly signup_click: {
2405
+ readonly surface: "docs";
2406
+ readonly required: readonly ["href"];
2407
+ };
2408
+ readonly signup_completed: {
2409
+ readonly surface: "console";
2410
+ readonly required: readonly ["signup_source"];
2411
+ };
2412
+ readonly report_opened: {
2413
+ readonly surface: readonly ["console", "mcp"];
2414
+ readonly required: readonly ["report_id"];
2415
+ };
2416
+ readonly fix_context_pulled: {
2417
+ readonly surface: readonly ["console", "mcp"];
2418
+ readonly required: readonly ["report_id"];
2419
+ };
2420
+ readonly fix_dispatched: {
2421
+ readonly surface: readonly ["console", "mcp"];
2422
+ readonly required: readonly ["report_id", "agent"];
2423
+ };
2424
+ readonly invite_sent: {
2425
+ readonly surface: "console";
2426
+ readonly required: readonly [];
2427
+ };
2428
+ readonly upgrade_clicked: {
2429
+ readonly surface: "console";
2430
+ readonly required: readonly ["plan"];
2431
+ };
2432
+ readonly test_report_sent: {
2433
+ readonly surface: "console";
2434
+ readonly required: readonly ["project_id"];
2435
+ };
2436
+ readonly project_created: {
2437
+ readonly surface: "server";
2438
+ readonly required: readonly ["project_id"];
2439
+ };
2440
+ readonly key_minted: {
2441
+ readonly surface: "server";
2442
+ readonly required: readonly ["project_id"];
2443
+ };
2444
+ readonly first_report_received: {
2445
+ readonly surface: "server";
2446
+ readonly required: readonly ["project_id"];
2447
+ };
2448
+ readonly fix_merged: {
2449
+ readonly surface: "server";
2450
+ readonly required: readonly ["project_id"];
2451
+ };
2452
+ readonly upgrade_completed: {
2453
+ readonly surface: "server";
2454
+ readonly required: readonly ["project_id", "plan"];
2455
+ };
2456
+ readonly loop_impression: {
2457
+ readonly surface: "web";
2458
+ readonly required: readonly [];
2459
+ };
2460
+ readonly loop_click: {
2461
+ readonly surface: "web";
2462
+ readonly required: readonly [];
2463
+ };
2464
+ readonly loop_signup: {
2465
+ readonly surface: "console";
2466
+ readonly required: readonly ["ref"];
2467
+ };
2468
+ };
2469
+ type MushiEventName = keyof typeof MUSHI_EVENTS;
2470
+ declare const MUSHI_EVENT_NAMES: MushiEventName[];
2471
+ /** The aha event: a project receives its first SDK-originated report. */
2472
+ declare const ACTIVATION_EVENT: MushiEventName;
2473
+ /**
2474
+ * Any of these in a week counts as "diagnosis consumed" for the habit metric
2475
+ * (docs/plan-gtm.md: report opened, fix context pulled or fix dispatched, from
2476
+ * the console or an MCP client). company_funnel_weekly counts the same set.
2477
+ */
2478
+ declare const HABIT_EVENTS: readonly MushiEventName[];
2479
+ type MushiEventProperties = Record<string, MushiPropertyValue>;
2480
+ declare function isValidEventName(name: unknown): name is string;
2481
+ interface SanitizedProperties {
2482
+ properties: MushiEventProperties;
2483
+ dropped: string[];
2484
+ }
2485
+ /**
2486
+ * Enforce the property contract: depth 1, ≤ maxKeys, values ≤ maxValueLength,
2487
+ * PII-looking keys dropped unless allowlisted, `$`-prefixed keys dropped unless
2488
+ * `allowReserved` (the SDK sets those itself). Never throws.
2489
+ */
2490
+ declare function sanitizeEventProperties(input: unknown, opts?: {
2491
+ allowlist?: readonly string[];
2492
+ allowReserved?: boolean;
2493
+ scrub?: (s: string) => string;
2494
+ }): SanitizedProperties;
2495
+ /** Approximate serialized size guard (UTF-16 length is a fine upper bound here). */
2496
+ declare function propertiesWithinByteLimit(properties: MushiEventProperties): boolean;
2497
+
2498
+ /**
2499
+ * FILE: packages/core/src/event-tracker.ts
2500
+ * PURPOSE: Client-side product-analytics tracker behind `Mushi.track()`.
2501
+ *
2502
+ * Batches named events and flushes them to POST /v1/sdk/events. Modelled on
2503
+ * session-tracker.ts (module state, idempotent init) but with a batch buffer,
2504
+ * an interval flush, a pagehide flush (the API client already switches to
2505
+ * keepalive during unload) and a small localStorage spill for unsent batches.
2506
+ *
2507
+ * Privacy:
2508
+ * - `anon_id` is the same opaque per-project reporter token the SDK already
2509
+ * uses for reports and sessions; identify() attaches the host's user id.
2510
+ * - Everything passes the shared gate in analytics-gate.ts (also used by the
2511
+ * session tracker): analytics.enabled, DNT / GPC, automation, consent.
2512
+ * - Nothing leaves the page until consent is 'granted'. That covers the
2513
+ * identify marker and a previous page's spill, not just track() events.
2514
+ * - `consent: 'required'` buffers up to 50 events in memory until
2515
+ * setConsent('granted'); 'denied' drops the buffer, the pending identify
2516
+ * and the spill, and disables tracking.
2517
+ * - Every string property runs through the caller-supplied PII scrubber and
2518
+ * the shared property contract in analytics-taxonomy.ts.
2519
+ * - `sampleRate` is decided once per person (hash of anon_id), never per
2520
+ * event, so funnels stay consistent.
2521
+ */
2522
+
2523
+ interface EventTrackerOptions {
2524
+ client: MushiApiClient;
2525
+ projectId: string;
2526
+ /**
2527
+ * Anonymous id to key events on. Omit it: the tracker then uses its own
2528
+ * random per-project id, stored only after consent. Do not pass the reporter
2529
+ * token — it is a credential for the end user's report threads.
2530
+ */
2531
+ anonId?: string | null;
2532
+ sdkVersion?: string;
2533
+ userId?: string | null;
2534
+ config?: MushiAnalyticsConfig;
2535
+ /** PII scrubber for string values (the SDK passes createPiiScrubber().scrub). */
2536
+ scrub?: (s: string) => string;
2537
+ /** Optional session id resolver (the SDK passes getSessionId). */
2538
+ getSessionId?: () => string;
2539
+ }
2540
+ /**
2541
+ * True when tracking is active for this person (enabled, consented, sampled
2542
+ * in, no DNT / GPC, not automation). The gate leaves the tracker
2543
+ * uninitialised when it blocks, so `_initialized` covers the first part.
2544
+ */
2545
+ declare function isEventTrackingActive(): boolean;
2546
+ /** The anonymous id events are keyed on, or null when tracking is off. */
2547
+ declare function getEventAnonymousId(): string | null;
2548
+ /**
2549
+ * Initialise the tracker. Idempotent per page. Safe to call in SSR (no-op).
2550
+ */
2551
+ declare function initEventTracker(opts: EventTrackerOptions): void;
2552
+ /**
2553
+ * Record a named event. Invalid names are dropped; properties are sanitised
2554
+ * per analytics-taxonomy.ts. Returns true when the event was queued.
2555
+ */
2556
+ declare function trackEvent(name: string, properties?: Record<string, unknown>, opts?: {
2557
+ dedupKey?: string;
2558
+ ts?: string;
2559
+ reserved?: MushiEventProperties;
2560
+ }): boolean;
2561
+ /**
2562
+ * Grant or deny consent. Persisted per project and broadcast through the
2563
+ * shared gate, so the session tracker follows the same decision. Granting
2564
+ * releases the pending buffer and identify; denying drops them.
2565
+ */
2566
+ declare function setEventConsent(state: 'granted' | 'denied'): void;
2567
+ /**
2568
+ * Called from identify(): stitches anonymous history to the person. The
2569
+ * identify marker is held until consent is granted and dropped on denial.
2570
+ */
2571
+ declare function updateEventIdentity(userId: string | null, traits?: Record<string, unknown> | null): void;
2572
+ /** Flush immediately (tests, before navigation). */
2573
+ declare function flushEvents(): Promise<void>;
2574
+ /** Tear down timers (tests / SSR). */
2575
+ declare function destroyEventTracker(): void;
2576
+
2577
+ /**
2578
+ * FILE: packages/core/src/analytics-gate.ts
2579
+ * PURPOSE: The one privacy gate every analytics surface in the browser SDK
2580
+ * passes through: product events (`Mushi.track()`, `identify()`) and
2581
+ * session lifecycle tracking alike.
2582
+ *
2583
+ * A surface may send only when all of these hold:
2584
+ * - `analytics.enabled` is not false;
2585
+ * - Do Not Track / Global Privacy Control is off, or the host opted out of
2586
+ * honouring it (`respectDoNotTrack: false`);
2587
+ * - the browser is not automation or a crawler (`excludeBots: false` opts
2588
+ * back in, e.g. to exercise analytics from Playwright);
2589
+ * - consent resolves to 'granted'. 'implied' mode grants unless the visitor
2590
+ * stored a denial; 'required' mode waits for `setConsent('granted')`.
2591
+ *
2592
+ * The first three are fixed for the page, so `analyticsBlockReason()` is read
2593
+ * once at tracker init. Consent can change at any time, so trackers resolve it
2594
+ * from storage at init (the storage is the source of truth, which keeps the
2595
+ * result independent of which tracker starts first) and then follow
2596
+ * `onAnalyticsConsentChange()`.
2597
+ */
2598
+
2599
+ type AnalyticsConsentState = 'granted' | 'denied' | 'pending';
2600
+ type AnalyticsBlockReason = 'disabled' | 'dnt' | 'automation';
2601
+ type ConsentListener = (state: 'granted' | 'denied') => void;
2602
+ /** True when the browser signals Do Not Track or Global Privacy Control. */
2603
+ declare function dntActive(): boolean;
2604
+ /** True for WebDriver-controlled browsers (Playwright, Puppeteer, Selenium) and crawler user agents. */
2605
+ declare function isAutomatedBrowser(): boolean;
2606
+ /**
2607
+ * Why analytics may not run on this page at all, or null when only consent
2608
+ * stands between the tracker and the network.
2609
+ */
2610
+ declare function analyticsBlockReason(config?: MushiAnalyticsConfig): AnalyticsBlockReason | null;
2611
+ /** Consent for this page: a stored choice wins, otherwise the configured mode decides. */
2612
+ declare function resolveAnalyticsConsent(projectId: string, config?: MushiAnalyticsConfig): AnalyticsConsentState;
2613
+ /** Persist a consent decision and tell every subscribed tracker. */
2614
+ declare function setAnalyticsConsent(projectId: string, state: 'granted' | 'denied'): void;
2615
+ /** Follow consent changes. Returns the unsubscribe function. */
2616
+ declare function onAnalyticsConsentChange(listener: ConsentListener): () => void;
2617
+
2073
2618
  interface RateLimiterConfig {
2074
2619
  maxBurst?: number;
2075
2620
  refillRate?: number;
@@ -2632,4 +3177,4 @@ declare function projectInitialsThemeIndex(projectId: string): number;
2632
3177
  */
2633
3178
  declare function readPageFaviconHref(): string | null;
2634
3179
 
2635
- export { type ApiClientOptions, type BreadcrumbBuffer, type BreadcrumbBufferOptions, DEFAULT_API_ENDPOINT, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, type EnvConfigDiagnostics, type EnvNearMiss, type LogEntry, type LogFormat, type LogLevel, type Logger, type LoggerOptions, MAX_REPORT_PAYLOAD_BYTES, MAX_SCREENSHOT_DATA_URL_BYTES, MUSHI_ACCENT_SHADOW, MUSHI_BANNER_BRAND_BORDER, MUSHI_BANNER_NEON, MUSHI_BORDER, MUSHI_COLORS_DARK, MUSHI_COLORS_LIGHT, MUSHI_CONTROL_DISABLED, MUSHI_COPY, MUSHI_DURATION, MUSHI_GEOMETRY, MUSHI_IDENTITY_TOKEN_PREFIX, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, MUSHI_INVERSE, MUSHI_LETTER_SPACING, MUSHI_MOTION, MUSHI_ON_ACCENT, MUSHI_OPACITY, MUSHI_PROJECT_HEADER, MUSHI_RADIUS, MUSHI_REPORTER_STATUS, MUSHI_SDK_PACKAGE_HEADER, MUSHI_SDK_VERSION_HEADER, MUSHI_SHADOW_INK, MUSHI_SPACING, MUSHI_TIER_COLORS, MUSHI_TYPE, MUSHI_USER_TOKEN_HEADER, MUSHI_Z, type MushiActivityEvent, type MushiApiCascadeConfig, type MushiApiClient, type MushiApiResponse, type MushiAssistantConfig, type MushiAssistantReply, type MushiAssistantStep, type MushiBannerConfig, type MushiBannerLink, type MushiBetaChangelogEntry, type MushiBetaModeConfig, type MushiBreadcrumb, type MushiCaptureConfig, type MushiCaptureEventInput, type MushiCaptureExceptionOptions, type MushiColorPalette, type MushiConfig, type MushiConsoleEntry, type MushiCooldownConfig, type MushiCrossAppReport, type MushiCustomCategory, type MushiDiagnosticsResult, type MushiDiscoverInventoryConfig, type MushiDiscoveryEventPayload, type MushiEnvironment, type MushiEventHandler, type MushiEventType, type MushiHallOfFameEntry, type MushiIdentityClaims, type MushiIntegrationsConfig, type MushiInternalRequestKind, type MushiLeaderboardEntry, type MushiNetworkEntry, type MushiOfflineConfig, type MushiOnDeviceClassifier, type MushiOnDeviceClassifierInput, type MushiOnDeviceClassifierResult, type MushiPageContext, type MushiPerformanceMetrics, type MushiPreFilterConfig, type MushiPreset, type MushiPrivacyConfig, type MushiProactiveConfig, type MushiRegion, type MushiReport, type MushiReportBuilder, type MushiReportCategory, type MushiReportStatus, type MushiReporterComment, type MushiReporterReport, type MushiReputationResult, type MushiRewardsConfig, type MushiRuntimeSdkConfig, type MushiSDKInstance, type MushiSdkVersionInfo, type MushiSelectedElement, type MushiSentryConfig, type MushiSentryContext, type MushiSessionEventPayload, type MushiTesterReputation, type MushiThemeMode, type MushiTierResult, type MushiTimelineEntry, type MushiTimelineKind, type MushiTokenSnapshot, type MushiTracePropagationConfig, type MushiUrlMatcher, type MushiWidgetAnchor, type MushiWidgetConfig, type NormalisedException, type OfflineQueue, type PayloadGuardResult, type PiiScrubberConfig, type PreFilterResult, type ProjectFaviconSource, REGION_ENDPOINTS, type RateLimiter, type RateLimiterConfig, type ResolvedEnvConfig, type SessionTrackerOptions, buildIdentityClaims, buildSdkIngestHeaders, captureEnvironment, checkReportPayloadSize, createApiClient, createBreadcrumbBuffer, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, destroySessionTracker, diagnoseEnvConfig, estimateJsonBytes, expandPreset, faviconUrlCandidates, formatBytes, getDeviceFingerprintHash, getReporterToken, getSessionId, githubRepoDomainHint, hmacSha256Hex, initSessionTracker, isLikelyGenericFavicon, isUntrustedFaviconUrl, mushiPalette, mushiTokens, newUuid, noopLogger, normaliseThrown, originToDomain, parseIdentityToken, projectFaviconUrlCandidates, projectInitials, projectInitialsThemeIndex, readPageFaviconHref, resolveEnvConfig, resolveProjectDomain, resolveProjectFaviconDomains, resolveRegionEndpoint, resolveWidgetAccent, safeWidgetHex, scrubPii, scrubUrl, sha256Hex, trackPageView, updateSessionIdentity, validateConfig };
3180
+ export { ACTIVATION_EVENT, type AnalyticsBlockReason, type AnalyticsConsentState, type ApiClientOptions, type BreadcrumbBuffer, type BreadcrumbBufferOptions, DEFAULT_API_ENDPOINT, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, EVENT_NAME_RE, EVENT_PROPERTY_LIMITS, type EnvConfigDiagnostics, type EnvNearMiss, type EventTrackerOptions, HABIT_EVENTS, type LogEntry, type LogFormat, type LogLevel, type Logger, type LoggerOptions, MAX_REPORT_PAYLOAD_BYTES, MAX_SCREENSHOT_DATA_URL_BYTES, MUSHI_ACCENT_SHADOW, MUSHI_BANNER_BRAND_BORDER, MUSHI_BANNER_NEON, MUSHI_BEACON_MAX_BYTES, MUSHI_BORDER, MUSHI_COLORS_DARK, MUSHI_COLORS_LIGHT, MUSHI_CONTROL_DISABLED, MUSHI_COPY, MUSHI_DURATION, MUSHI_EVENTS, MUSHI_EVENT_NAMES, MUSHI_GEOMETRY, MUSHI_IDENTITY_TOKEN_PREFIX, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, MUSHI_INVERSE, MUSHI_LETTER_SPACING, MUSHI_MOTION, MUSHI_ON_ACCENT, MUSHI_OPACITY, MUSHI_PROJECT_HEADER, MUSHI_RADIUS, MUSHI_REPORTER_STATUS, MUSHI_SDK_PACKAGE_HEADER, MUSHI_SDK_VERSION_HEADER, MUSHI_SHADOW_INK, MUSHI_SPACING, MUSHI_SURFACES, MUSHI_TIER_COLORS, MUSHI_TYPE, MUSHI_USER_TOKEN_HEADER, MUSHI_Z, type MushiActivityEvent, type MushiAnalyticsConfig, type MushiApiCascadeConfig, type MushiApiClient, type MushiApiResponse, type MushiAssistantConfig, type MushiAssistantReply, type MushiAssistantStep, type MushiBannerConfig, type MushiBannerLink, type MushiBeaconEnvelope, type MushiBetaChangelogEntry, type MushiBetaModeConfig, type MushiBreadcrumb, type MushiCaptureConfig, type MushiCaptureEventInput, type MushiCaptureExceptionOptions, type MushiColorPalette, type MushiConfig, type MushiConsoleEntry, type MushiCooldownConfig, type MushiCrossAppReport, type MushiCustomCategory, type MushiDiagnosticsResult, type MushiDiscoverInventoryConfig, type MushiDiscoveryEventPayload, type MushiEnvironment, type MushiErrorFilter, type MushiEventHandler, type MushiEventName, type MushiEventProperties, type MushiEventType, type MushiHallOfFameEntry, type MushiIdentityClaims, type MushiIntegrationsConfig, type MushiInternalRequestKind, type MushiLeaderboardEntry, type MushiNetworkEntry, type MushiOfflineConfig, type MushiOnDeviceClassifier, type MushiOnDeviceClassifierInput, type MushiOnDeviceClassifierResult, type MushiPageContext, type MushiPerformanceMetrics, type MushiPreFilterConfig, type MushiPreset, type MushiPrivacyConfig, type MushiProactiveConfig, type MushiProductEventPayload, type MushiPropertyValue, type MushiRegion, type MushiReport, type MushiReportBuilder, type MushiReportCategory, type MushiReportStatus, type MushiReporterComment, type MushiReporterReport, type MushiReputationResult, type MushiRewardsConfig, type MushiRuntimeSdkConfig, type MushiSDKInstance, type MushiSdkVersionInfo, type MushiSelectedElement, type MushiSentryConfig, type MushiSentryContext, type MushiSessionEventPayload, type MushiSurface, type MushiTesterReputation, type MushiThemeMode, type MushiTierResult, type MushiTimelineEntry, type MushiTimelineKind, type MushiTokenSnapshot, type MushiTracePropagationConfig, type MushiUrlMatcher, type MushiWidgetAnchor, type MushiWidgetConfig, type NormalisedException, type OfflineQueue, type PayloadGuardResult, type PiiScrubberConfig, type PreFilterResult, type ProjectFaviconSource, REGION_ENDPOINTS, type RateLimiter, type RateLimiterConfig, type ResolvedEnvConfig, type SessionTrackerOptions, analyticsBlockReason, buildBeaconEnvelope, buildIdentityClaims, buildSdkIngestHeaders, captureEnvironment, checkReportPayloadSize, createApiClient, createBreadcrumbBuffer, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, destroyEventTracker, destroySessionTracker, diagnoseEnvConfig, dntActive, estimateJsonBytes, expandPreset, extractTunnelPath, faviconUrlCandidates, flushEvents, flushLastOutboundOnUnload, formatBytes, getBackoffDelay, getDeviceFingerprintHash, getEventAnonymousId, getReporterToken, getSessionId, githubRepoDomainHint, hmacSha256Hex, initEventTracker, initSessionTracker, isAutomatedBrowser, isEventTrackingActive, isLikelyGenericFavicon, isMushiBeaconEnvelope, isPageUnloading, isSameOriginOrRelative, isUntrustedFaviconUrl, isValidEventName, markPageUnloading, matchesErrorFilter, mushiPalette, mushiTokens, newUuid, noopLogger, normaliseThrown, onAnalyticsConsentChange, originToDomain, parseIdentityToken, parseRetryAfter, projectFaviconUrlCandidates, projectInitials, projectInitialsThemeIndex, propertiesWithinByteLimit, readPageFaviconHref, resolveAnalyticsConsent, resolveEnvConfig, resolveProjectDomain, resolveProjectFaviconDomains, resolveRegionEndpoint, resolveRequestBaseUrl, resolveWidgetAccent, safeWidgetHex, sanitizeEventProperties, scrubPii, scrubUrl, sendOnUnload, setAnalyticsConsent, setEventConsent, sha256Hex, shouldDropCapturedError, trackEvent, trackPageView, updateEventIdentity, updateSessionIdentity, validateConfig };