@mushi-mushi/core 1.28.0 → 1.30.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.ts 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;
@@ -198,9 +214,29 @@ interface MushiWidgetConfig {
198
214
  snapToEdge?: boolean;
199
215
  axis?: 'both' | 'x' | 'y';
200
216
  };
201
- /** 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
+ */
202
232
  brandFooter?: boolean;
203
- /** How the widget should surface SDK freshness warnings. Defaults to auto. */
233
+ /**
234
+ * How the widget surfaces the "update @mushi-mushi/web" notice. It is a
235
+ * developer instruction, so `'auto'` (default) shows it in the widget only on
236
+ * a dev host (localhost, loopback, `*.localhost`, `*.local`, `file:`) or with
237
+ * `debug: true` — never to an app's end users. `'banner'` always shows it;
238
+ * `'console-only'` and `'off'` never render it in the widget.
239
+ */
204
240
  outdatedBanner?: 'auto' | 'banner' | 'console-only' | 'off';
205
241
  /**
206
242
  * Privacy nudge shown beside an attached screenshot preview, reminding the
@@ -438,6 +474,24 @@ interface MushiCaptureConfig {
438
474
  screenshotProvider?: () => Promise<string | null>;
439
475
  elementSelector?: boolean;
440
476
  replay?: 'sentry' | 'rrweb' | 'lite' | 'off';
477
+ /**
478
+ * How to load rrweb for `replay: 'rrweb'`. A published SDK cannot import
479
+ * rrweb itself: its bare `import('rrweb')` is invisible to your bundler, so
480
+ * rrweb never makes it into your build. Hand the import over and your
481
+ * bundler code-splits it like any other dynamic import. Install `rrweb`
482
+ * yourself; the chunk loads only for sessions sampled into replay.
483
+ *
484
+ * Without a loader the SDK uses a global `rrweb` (the UMD build from a
485
+ * script tag) when one exists, and otherwise records lite replay (clicks)
486
+ * and warns once in the console. Recording masks every input and every
487
+ * text node.
488
+ *
489
+ * @example
490
+ * capture: { replay: 'rrweb', rrweb: () => import('rrweb') }
491
+ */
492
+ rrweb?: () => Promise<{
493
+ record?: unknown;
494
+ }>;
441
495
  /**
442
496
  * Mushi Mushi v2.1 (whitepaper §6 hybrid mode): passive inventory
443
497
  * discovery. When enabled the SDK observes navigations and emits a
@@ -546,9 +600,10 @@ interface MushiPrivacyConfig {
546
600
  * sensitive fields that should never appear in any form — passwords, PII,
547
601
  * financial data. Applied in addition to `maskSelectors`.
548
602
  *
549
- * Default: `['input[type="password"]', '[data-mushi-redact]']`
550
- *
551
- * To disable the default redaction, pass an empty array.
603
+ * Always redacted, whatever this is set to: `input[type="password"]`,
604
+ * `input[autocomplete^="cc-"]`, `[data-private]`, `[data-mushi-mask]`.
605
+ * This list is added on top; it defaults to `['[data-mushi-redact]']`,
606
+ * and an empty array drops only that default.
552
607
  */
553
608
  redactSelectors?: string[];
554
609
  /** Let reporters remove an attached screenshot before submitting. Defaults to true. */
@@ -1385,8 +1440,23 @@ interface MushiSDKInstance {
1385
1440
  * Manually record a host-defined activity event (e.g. 'lesson_completed').
1386
1441
  * The SDK batches these and flushes to POST /v1/sdk/activity.
1387
1442
  * No-op when rewards are disabled or the user has not opted in.
1443
+ * For product analytics (funnels, paths, people) use `track()` instead.
1388
1444
  */
1389
1445
  recordActivity(action: string, metadata?: Record<string, unknown>): void;
1446
+ /**
1447
+ * Record a product-analytics event, e.g. `track('checkout_started', { plan: 'pro' })`.
1448
+ * Names must match `^[a-z][a-z0-9_]{1,63}$`; properties are flat
1449
+ * (string | number | boolean | null), PII-looking keys are dropped, and
1450
+ * every string value runs through the PII scrubber. Batched to
1451
+ * POST /v1/sdk/events. No-op under DNT/GPC, `analytics.enabled: false`,
1452
+ * or before consent when `analytics.consent === 'required'`.
1453
+ * Returns true when the event was queued.
1454
+ */
1455
+ track(event: string, properties?: Record<string, MushiPropertyValue>): boolean;
1456
+ /** Grant or deny analytics consent (persisted per project in localStorage). */
1457
+ setConsent(state: 'granted' | 'denied'): void;
1458
+ /** The anonymous id analytics events are keyed on, or null when tracking is off. */
1459
+ getAnonymousId(): string | null;
1390
1460
  /**
1391
1461
  * Briefly animate the bug-report trigger button to draw the user's
1392
1462
  * attention without opening the full widget. Ideal for subtle "feedback
@@ -1496,6 +1566,11 @@ interface MushiApiClient {
1496
1566
  postSessionEvent(payload: MushiSessionEventPayload): Promise<MushiApiResponse<{
1497
1567
  accepted: boolean;
1498
1568
  }>>;
1569
+ /** POST /v1/sdk/events — batched product-analytics events (Mushi.track()). */
1570
+ postProductEvents(payload: MushiProductEventPayload): Promise<MushiApiResponse<{
1571
+ accepted: number;
1572
+ dropped: number;
1573
+ }>>;
1499
1574
  listReporterReports(reporterToken: string): Promise<MushiApiResponse<{
1500
1575
  reports: MushiReporterReport[];
1501
1576
  }>>;
@@ -1648,12 +1723,56 @@ interface MushiSessionEventPayload {
1648
1723
  user_agent?: string | null;
1649
1724
  sdk_version?: string;
1650
1725
  }
1726
+ type MushiPropertyValue = string | number | boolean | null;
1727
+ /** Configuration for `Mushi.track()` product analytics. */
1728
+ interface MushiAnalyticsConfig {
1729
+ /** Master switch (default true). */
1730
+ enabled?: boolean;
1731
+ /** 'implied' (default) sends immediately; 'required' buffers until setConsent('granted'). */
1732
+ consent?: 'implied' | 'required';
1733
+ /** 0..1, decided once per person (default 1). */
1734
+ sampleRate?: number;
1735
+ /** Honour navigator.doNotTrack / globalPrivacyControl (default true). */
1736
+ respectDoNotTrack?: boolean;
1737
+ /**
1738
+ * Skip tracking in WebDriver-controlled browsers (Playwright, Puppeteer,
1739
+ * Selenium), headless Chrome, Lighthouse and crawler user agents, so test
1740
+ * runs and bots never count as users (default true). Set false to exercise
1741
+ * analytics from an end-to-end test.
1742
+ */
1743
+ excludeBots?: boolean;
1744
+ /** Emit `pageview` on history navigation (default false; sessions already record page views). */
1745
+ autoPageviews?: boolean;
1746
+ /** Flush cadence in ms (default 5000, min 1000). */
1747
+ flushIntervalMs?: number;
1748
+ /** Property keys allowed through the PII key filter. */
1749
+ propertyAllowlist?: string[];
1750
+ /** Which surface these events come from (default 'web'). */
1751
+ surface?: 'web' | 'console' | 'docs' | 'cli' | 'mcp' | 'server' | 'mobile';
1752
+ }
1753
+ /** Wire shape for POST /v1/sdk/events. */
1754
+ interface MushiProductEventPayload {
1755
+ anon_id?: string | null;
1756
+ user_id?: string | null;
1757
+ user_traits?: Record<string, unknown> | null;
1758
+ session_id?: string | null;
1759
+ sdk_version?: string | null;
1760
+ surface?: MushiAnalyticsConfig['surface'];
1761
+ events: Array<{
1762
+ name: string;
1763
+ ts?: string;
1764
+ properties?: Record<string, MushiPropertyValue>;
1765
+ dedup_key?: string;
1766
+ }>;
1767
+ }
1651
1768
  interface MushiApiResponse<T> {
1652
1769
  ok: boolean;
1653
1770
  data?: T;
1771
+ /** `status` is the HTTP status when the server answered; absent for network errors. */
1654
1772
  error?: {
1655
1773
  code: string;
1656
1774
  message: string;
1775
+ status?: number;
1657
1776
  };
1658
1777
  }
1659
1778
  interface MushiRuntimeSdkConfig {
@@ -1679,8 +1798,30 @@ interface MushiSdkVersionInfo {
1679
1798
  interface MushiReporterReport {
1680
1799
  id: string;
1681
1800
  status: string;
1682
- category: string;
1801
+ /** @deprecated No longer sent to reporters (Plan 018); internal triage only. */
1802
+ category?: string;
1803
+ /** @deprecated No longer sent to reporters (Plan 018); internal triage only. */
1683
1804
  severity?: string | null;
1805
+ /** Reporter-safe title (the summary, or the first line of the description). */
1806
+ title?: string | null;
1807
+ /** The type the reporter picked (`user_category`), if any. */
1808
+ user_category?: string | null;
1809
+ /** Page path the report was filed on. */
1810
+ page?: string | null;
1811
+ app_version?: string | null;
1812
+ /** Signed, short-lived thumbnail URL; null without a screenshot. */
1813
+ screenshot_thumb_url?: string | null;
1814
+ /** Bucketed duplicate count: never a raw number. */
1815
+ group_bucket?: 'none' | 'few' | 'many';
1816
+ closed_reason?: string | null;
1817
+ fixed_in_version?: string | null;
1818
+ /** The developer asked a question and is waiting on the reporter. */
1819
+ awaiting_reporter?: boolean;
1820
+ /** True when this row is a report the reporter follows (theirs was a duplicate of it). */
1821
+ followed?: boolean;
1822
+ last_event_at?: string | null;
1823
+ /** Template-rendered (or verbatim developer) text of the latest event. */
1824
+ last_event_preview?: string | null;
1684
1825
  summary?: string | null;
1685
1826
  description?: string | null;
1686
1827
  created_at: string;
@@ -2159,31 +2300,62 @@ declare function getSessionId(): string;
2159
2300
  * no offline queue, fire-and-forget after visibility-change.
2160
2301
  *
2161
2302
  * Privacy:
2303
+ * - Passes the same gate as product events (analytics-gate.ts): nothing is
2304
+ * sent when `analytics.enabled` is false, under DNT / GPC, in automation
2305
+ * or crawler browsers, or before consent is granted. A later
2306
+ * setConsent('granted') starts the session; 'denied' stops it mid-page.
2162
2307
  * - No PII by default. `reporter_token_hash` is the same opaque device
2163
2308
  * fingerprint already used in reports. `user_id_hash` is only set when
2164
- * the host app calls Mushi.identify().
2309
+ * the host app identifies the user.
2310
+ * - Routes are sent as the pathname only and the referrer as its origin, so
2311
+ * query strings (reset tokens, emails in links) never leave the page.
2165
2312
  * - Respects the SDK-level `trackSessions: false` opt-out option.
2166
- * - Respects a DNT / Global Privacy Control header at the SDK init layer;
2167
- * callers should check `shouldRespectDnt()` before calling `initSession`.
2313
+ *
2314
+ * SPA page views:
2315
+ * The history patch below decides push-vs-replace at wrap time (comparing
2316
+ * against `history.pushState` inside the wrapper never matches, because by
2317
+ * then that property is the wrapper). Hosts that already own a history
2318
+ * patch (`@mushi-mushi/web`'s shared one) pass `patchHistory: false` and
2319
+ * call `trackPageView()` themselves — a second wrapper assigned to
2320
+ * `history.pushState` silently discards this one, which is how the web SDK
2321
+ * recorded zero SPA page views across >1,000 sessions per project.
2168
2322
  */
2169
2323
 
2170
2324
  interface SessionTrackerOptions {
2171
2325
  client: MushiApiClient;
2172
2326
  sdkVersion?: string;
2327
+ /** The raw per-project reporter token; the server stores only its sha256. */
2328
+ reporterToken?: string | null;
2329
+ /** @deprecated Renamed to `reporterToken` (it always carried the raw token, never a hash). */
2173
2330
  reporterTokenHash?: string | null;
2174
2331
  userIdHash?: string | null;
2332
+ /**
2333
+ * Install the built-in `history.pushState` / `popstate` patch that emits
2334
+ * `page_view` events. Default `true`. Pass `false` when the host already
2335
+ * owns a history patch and will call `trackPageView()` itself — stacking a
2336
+ * second wrapper on `history.pushState` is exactly how this tracker's page
2337
+ * views got overwritten and silently dropped in `@mushi-mushi/web`.
2338
+ */
2339
+ patchHistory?: boolean;
2340
+ /** Project the stored analytics consent is keyed on (the SDK passes its projectId). */
2341
+ projectId?: string;
2342
+ /** The SDK's `analytics` block: sessions pass the same gate as product events. */
2343
+ analytics?: MushiAnalyticsConfig;
2175
2344
  }
2176
2345
  /**
2177
2346
  * Initialise the session tracker. Call once at SDK init for web environments.
2178
2347
  * Subsequent calls on the same page are no-ops (idempotent).
2179
2348
  *
2180
- * Emits: session_start immediately, heartbeats every minute, session_end on
2181
- * visibilitychange/pagehide, and page_view on history API navigation.
2349
+ * Emits, once the analytics gate allows it: session_start, heartbeats every
2350
+ * minute, session_end on visibilitychange/pagehide, and page_view on history
2351
+ * API navigation. Under `consent: 'required'` nothing is sent until
2352
+ * setConsent('granted').
2182
2353
  */
2183
2354
  declare function initSessionTracker(opts: SessionTrackerOptions): void;
2184
2355
  /**
2185
2356
  * Record a page view manually — call from framework router hooks where the
2186
- * history patch may fire too early (e.g. React Router v7 loader transitions).
2357
+ * history patch may fire too early (e.g. React Router v7 loader transitions),
2358
+ * or from a host that initialised with `patchHistory: false`.
2187
2359
  */
2188
2360
  declare function trackPageView(route?: string): void;
2189
2361
  /** Update the user identity after a Mushi.identify() call. */
@@ -2191,6 +2363,295 @@ declare function updateSessionIdentity(userIdHash: string | null): void;
2191
2363
  /** Tear down timers (e.g. in tests or SSR environments). */
2192
2364
  declare function destroySessionTracker(): void;
2193
2365
 
2366
+ /**
2367
+ * FILE: packages/core/src/analytics-taxonomy.ts
2368
+ * PURPOSE: Single source of truth for Mushi's product-analytics vocabulary —
2369
+ * the events Mushi emits about its OWN funnel (dogfooding the SDK on
2370
+ * the landing site, docs, console, CLI and MCP) plus the validation
2371
+ * rules every `Mushi.track()` call obeys, for every project.
2372
+ *
2373
+ * Naming: `object_verb`, snake_case, past tense for completions, `_view` for
2374
+ * exposure, `_click` for intent. Every event carries `$surface`.
2375
+ *
2376
+ * Mirror: packages/server/supabase/functions/_shared/analytics-taxonomy.generated.ts
2377
+ * is generated from this file by `node scripts/gen-analytics-taxonomy.mjs`
2378
+ * (CI runs `--check`). Edge functions cannot import workspace packages.
2379
+ */
2380
+
2381
+ /** Event names: lowercase, start with a letter, 2–64 chars. */
2382
+ declare const EVENT_NAME_RE: RegExp;
2383
+ /** Hard caps applied client- and server-side. */
2384
+ declare const EVENT_PROPERTY_LIMITS: {
2385
+ readonly maxKeys: 40;
2386
+ readonly maxKeyLength: 64;
2387
+ readonly maxValueLength: 256;
2388
+ readonly maxBytes: 8192;
2389
+ /** Client batch size before an immediate flush. */
2390
+ readonly maxClientBatch: 20;
2391
+ /** Server-side per-request ceiling. */
2392
+ readonly maxServerBatch: 50;
2393
+ /** Distinct event names per project before the server returns 422. */
2394
+ readonly maxEventNamesPerProject: 200;
2395
+ };
2396
+ declare const MUSHI_SURFACES: readonly ["web", "console", "docs", "cli", "mcp", "server", "mobile"];
2397
+ type MushiSurface = (typeof MUSHI_SURFACES)[number];
2398
+ /**
2399
+ * Mushi's own funnel vocabulary. One entry per line — the generator parses
2400
+ * this block line by line, so keep the `name: { surface: '…', required: […] }`
2401
+ * shape. An event with more than one emitter lists every surface:
2402
+ * `surface: ['console', 'mcp']`.
2403
+ */
2404
+ declare const MUSHI_EVENTS: {
2405
+ readonly page_view: {
2406
+ readonly surface: readonly ["web", "docs", "console"];
2407
+ readonly required: readonly [];
2408
+ };
2409
+ readonly docs_page_view: {
2410
+ readonly surface: "docs";
2411
+ readonly required: readonly ["route"];
2412
+ };
2413
+ readonly landing_view: {
2414
+ readonly surface: "docs";
2415
+ readonly required: readonly [];
2416
+ };
2417
+ readonly cta_click: {
2418
+ readonly surface: "docs";
2419
+ readonly required: readonly ["cta_id", "location"];
2420
+ };
2421
+ readonly quickstart_view: {
2422
+ readonly surface: "docs";
2423
+ readonly required: readonly [];
2424
+ };
2425
+ readonly pricing_view: {
2426
+ readonly surface: "docs";
2427
+ readonly required: readonly [];
2428
+ };
2429
+ readonly connect_demo_click: {
2430
+ readonly surface: "docs";
2431
+ readonly required: readonly ["href"];
2432
+ };
2433
+ readonly signup_click: {
2434
+ readonly surface: "docs";
2435
+ readonly required: readonly ["href"];
2436
+ };
2437
+ readonly signup_completed: {
2438
+ readonly surface: "console";
2439
+ readonly required: readonly ["signup_source"];
2440
+ };
2441
+ readonly report_opened: {
2442
+ readonly surface: readonly ["console", "mcp"];
2443
+ readonly required: readonly ["report_id"];
2444
+ };
2445
+ readonly fix_context_pulled: {
2446
+ readonly surface: readonly ["console", "mcp"];
2447
+ readonly required: readonly ["report_id"];
2448
+ };
2449
+ readonly fix_dispatched: {
2450
+ readonly surface: readonly ["console", "mcp"];
2451
+ readonly required: readonly ["report_id", "agent"];
2452
+ };
2453
+ readonly invite_sent: {
2454
+ readonly surface: "console";
2455
+ readonly required: readonly [];
2456
+ };
2457
+ readonly upgrade_clicked: {
2458
+ readonly surface: "console";
2459
+ readonly required: readonly ["plan"];
2460
+ };
2461
+ readonly test_report_sent: {
2462
+ readonly surface: "console";
2463
+ readonly required: readonly ["project_id"];
2464
+ };
2465
+ readonly diagnosis_viewed: {
2466
+ readonly surface: "console";
2467
+ readonly required: readonly ["report_id", "project_id", "surface"];
2468
+ };
2469
+ readonly project_created: {
2470
+ readonly surface: "server";
2471
+ readonly required: readonly ["project_id"];
2472
+ };
2473
+ readonly key_minted: {
2474
+ readonly surface: "server";
2475
+ readonly required: readonly ["project_id"];
2476
+ };
2477
+ readonly first_report_received: {
2478
+ readonly surface: "server";
2479
+ readonly required: readonly ["project_id"];
2480
+ };
2481
+ readonly first_diagnosis_ready: {
2482
+ readonly surface: "server";
2483
+ readonly required: readonly ["project_id"];
2484
+ };
2485
+ readonly fix_merged: {
2486
+ readonly surface: "server";
2487
+ readonly required: readonly ["project_id"];
2488
+ };
2489
+ readonly upgrade_completed: {
2490
+ readonly surface: "server";
2491
+ readonly required: readonly ["project_id", "plan"];
2492
+ };
2493
+ readonly loop_impression: {
2494
+ readonly surface: "web";
2495
+ readonly required: readonly [];
2496
+ };
2497
+ readonly loop_click: {
2498
+ readonly surface: "web";
2499
+ readonly required: readonly [];
2500
+ };
2501
+ readonly loop_signup: {
2502
+ readonly surface: "console";
2503
+ readonly required: readonly ["ref"];
2504
+ };
2505
+ };
2506
+ type MushiEventName = keyof typeof MUSHI_EVENTS;
2507
+ declare const MUSHI_EVENT_NAMES: MushiEventName[];
2508
+ /** The aha event: a project receives its first SDK-originated report. */
2509
+ declare const ACTIVATION_EVENT: MushiEventName;
2510
+ /**
2511
+ * Any of these in a week counts as "diagnosis consumed" for the habit metric
2512
+ * (docs/plan-gtm.md: report opened, fix context pulled or fix dispatched, from
2513
+ * the console or an MCP client). company_funnel_weekly counts the same set.
2514
+ */
2515
+ declare const HABIT_EVENTS: readonly MushiEventName[];
2516
+ type MushiEventProperties = Record<string, MushiPropertyValue>;
2517
+ declare function isValidEventName(name: unknown): name is string;
2518
+ interface SanitizedProperties {
2519
+ properties: MushiEventProperties;
2520
+ dropped: string[];
2521
+ }
2522
+ /**
2523
+ * Enforce the property contract: depth 1, ≤ maxKeys, values ≤ maxValueLength,
2524
+ * PII-looking keys dropped unless allowlisted, `$`-prefixed keys dropped unless
2525
+ * `allowReserved` (the SDK sets those itself). Never throws.
2526
+ */
2527
+ declare function sanitizeEventProperties(input: unknown, opts?: {
2528
+ allowlist?: readonly string[];
2529
+ allowReserved?: boolean;
2530
+ scrub?: (s: string) => string;
2531
+ }): SanitizedProperties;
2532
+ /** Approximate serialized size guard (UTF-16 length is a fine upper bound here). */
2533
+ declare function propertiesWithinByteLimit(properties: MushiEventProperties): boolean;
2534
+
2535
+ /**
2536
+ * FILE: packages/core/src/event-tracker.ts
2537
+ * PURPOSE: Client-side product-analytics tracker behind `Mushi.track()`.
2538
+ *
2539
+ * Batches named events and flushes them to POST /v1/sdk/events. Modelled on
2540
+ * session-tracker.ts (module state, idempotent init) but with a batch buffer,
2541
+ * an interval flush, a pagehide flush (the API client already switches to
2542
+ * keepalive during unload) and a small localStorage spill for unsent batches.
2543
+ *
2544
+ * Privacy:
2545
+ * - `anon_id` is the same opaque per-project reporter token the SDK already
2546
+ * uses for reports and sessions; identify() attaches the host's user id.
2547
+ * - Everything passes the shared gate in analytics-gate.ts (also used by the
2548
+ * session tracker): analytics.enabled, DNT / GPC, automation, consent.
2549
+ * - Nothing leaves the page until consent is 'granted'. That covers the
2550
+ * identify marker and a previous page's spill, not just track() events.
2551
+ * - `consent: 'required'` buffers up to 50 events in memory until
2552
+ * setConsent('granted'); 'denied' drops the buffer, the pending identify
2553
+ * and the spill, and disables tracking.
2554
+ * - Every string property runs through the caller-supplied PII scrubber and
2555
+ * the shared property contract in analytics-taxonomy.ts.
2556
+ * - `sampleRate` is decided once per person (hash of anon_id), never per
2557
+ * event, so funnels stay consistent.
2558
+ */
2559
+
2560
+ interface EventTrackerOptions {
2561
+ client: MushiApiClient;
2562
+ projectId: string;
2563
+ /**
2564
+ * Anonymous id to key events on. Omit it: the tracker then uses its own
2565
+ * random per-project id, stored only after consent. Do not pass the reporter
2566
+ * token — it is a credential for the end user's report threads.
2567
+ */
2568
+ anonId?: string | null;
2569
+ sdkVersion?: string;
2570
+ userId?: string | null;
2571
+ config?: MushiAnalyticsConfig;
2572
+ /** PII scrubber for string values (the SDK passes createPiiScrubber().scrub). */
2573
+ scrub?: (s: string) => string;
2574
+ /** Optional session id resolver (the SDK passes getSessionId). */
2575
+ getSessionId?: () => string;
2576
+ }
2577
+ /**
2578
+ * True when tracking is active for this person (enabled, consented, sampled
2579
+ * in, no DNT / GPC, not automation). The gate leaves the tracker
2580
+ * uninitialised when it blocks, so `_initialized` covers the first part.
2581
+ */
2582
+ declare function isEventTrackingActive(): boolean;
2583
+ /** The anonymous id events are keyed on, or null when tracking is off. */
2584
+ declare function getEventAnonymousId(): string | null;
2585
+ /**
2586
+ * Initialise the tracker. Idempotent per page. Safe to call in SSR (no-op).
2587
+ */
2588
+ declare function initEventTracker(opts: EventTrackerOptions): void;
2589
+ /**
2590
+ * Record a named event. Invalid names are dropped; properties are sanitised
2591
+ * per analytics-taxonomy.ts. Returns true when the event was queued.
2592
+ */
2593
+ declare function trackEvent(name: string, properties?: Record<string, unknown>, opts?: {
2594
+ dedupKey?: string;
2595
+ ts?: string;
2596
+ reserved?: MushiEventProperties;
2597
+ }): boolean;
2598
+ /**
2599
+ * Grant or deny consent. Persisted per project and broadcast through the
2600
+ * shared gate, so the session tracker follows the same decision. Granting
2601
+ * releases the pending buffer and identify; denying drops them.
2602
+ */
2603
+ declare function setEventConsent(state: 'granted' | 'denied'): void;
2604
+ /**
2605
+ * Called from identify(): stitches anonymous history to the person. The
2606
+ * identify marker is held until consent is granted and dropped on denial.
2607
+ */
2608
+ declare function updateEventIdentity(userId: string | null, traits?: Record<string, unknown> | null): void;
2609
+ /** Flush immediately (tests, before navigation). */
2610
+ declare function flushEvents(): Promise<void>;
2611
+ /** Tear down timers (tests / SSR). */
2612
+ declare function destroyEventTracker(): void;
2613
+
2614
+ /**
2615
+ * FILE: packages/core/src/analytics-gate.ts
2616
+ * PURPOSE: The one privacy gate every analytics surface in the browser SDK
2617
+ * passes through: product events (`Mushi.track()`, `identify()`) and
2618
+ * session lifecycle tracking alike.
2619
+ *
2620
+ * A surface may send only when all of these hold:
2621
+ * - `analytics.enabled` is not false;
2622
+ * - Do Not Track / Global Privacy Control is off, or the host opted out of
2623
+ * honouring it (`respectDoNotTrack: false`);
2624
+ * - the browser is not automation or a crawler (`excludeBots: false` opts
2625
+ * back in, e.g. to exercise analytics from Playwright);
2626
+ * - consent resolves to 'granted'. 'implied' mode grants unless the visitor
2627
+ * stored a denial; 'required' mode waits for `setConsent('granted')`.
2628
+ *
2629
+ * The first three are fixed for the page, so `analyticsBlockReason()` is read
2630
+ * once at tracker init. Consent can change at any time, so trackers resolve it
2631
+ * from storage at init (the storage is the source of truth, which keeps the
2632
+ * result independent of which tracker starts first) and then follow
2633
+ * `onAnalyticsConsentChange()`.
2634
+ */
2635
+
2636
+ type AnalyticsConsentState = 'granted' | 'denied' | 'pending';
2637
+ type AnalyticsBlockReason = 'disabled' | 'dnt' | 'automation';
2638
+ type ConsentListener = (state: 'granted' | 'denied') => void;
2639
+ /** True when the browser signals Do Not Track or Global Privacy Control. */
2640
+ declare function dntActive(): boolean;
2641
+ /** True for WebDriver-controlled browsers (Playwright, Puppeteer, Selenium) and crawler user agents. */
2642
+ declare function isAutomatedBrowser(): boolean;
2643
+ /**
2644
+ * Why analytics may not run on this page at all, or null when only consent
2645
+ * stands between the tracker and the network.
2646
+ */
2647
+ declare function analyticsBlockReason(config?: MushiAnalyticsConfig): AnalyticsBlockReason | null;
2648
+ /** Consent for this page: a stored choice wins, otherwise the configured mode decides. */
2649
+ declare function resolveAnalyticsConsent(projectId: string, config?: MushiAnalyticsConfig): AnalyticsConsentState;
2650
+ /** Persist a consent decision and tell every subscribed tracker. */
2651
+ declare function setAnalyticsConsent(projectId: string, state: 'granted' | 'denied'): void;
2652
+ /** Follow consent changes. Returns the unsubscribe function. */
2653
+ declare function onAnalyticsConsentChange(listener: ConsentListener): () => void;
2654
+
2194
2655
  interface RateLimiterConfig {
2195
2656
  maxBurst?: number;
2196
2657
  refillRate?: number;
@@ -2753,4 +3214,4 @@ declare function projectInitialsThemeIndex(projectId: string): number;
2753
3214
  */
2754
3215
  declare function readPageFaviconHref(): string | null;
2755
3216
 
2756
- 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_BEACON_MAX_BYTES, 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 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 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, buildBeaconEnvelope, buildIdentityClaims, buildSdkIngestHeaders, captureEnvironment, checkReportPayloadSize, createApiClient, createBreadcrumbBuffer, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, destroySessionTracker, diagnoseEnvConfig, estimateJsonBytes, expandPreset, extractTunnelPath, faviconUrlCandidates, flushLastOutboundOnUnload, formatBytes, getBackoffDelay, getDeviceFingerprintHash, getReporterToken, getSessionId, githubRepoDomainHint, hmacSha256Hex, initSessionTracker, isLikelyGenericFavicon, isMushiBeaconEnvelope, isPageUnloading, isSameOriginOrRelative, isUntrustedFaviconUrl, markPageUnloading, matchesErrorFilter, mushiPalette, mushiTokens, newUuid, noopLogger, normaliseThrown, originToDomain, parseIdentityToken, parseRetryAfter, projectFaviconUrlCandidates, projectInitials, projectInitialsThemeIndex, readPageFaviconHref, resolveEnvConfig, resolveProjectDomain, resolveProjectFaviconDomains, resolveRegionEndpoint, resolveRequestBaseUrl, resolveWidgetAccent, safeWidgetHex, scrubPii, scrubUrl, sendOnUnload, sha256Hex, shouldDropCapturedError, trackPageView, updateSessionIdentity, validateConfig };
3217
+ 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 };