@mushi-mushi/core 0.7.0 → 0.9.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
@@ -2,15 +2,18 @@ interface MushiConfig {
2
2
  projectId: string;
3
3
  apiKey: string;
4
4
  apiEndpoint?: string;
5
+ /** Opinionated defaults for common environments. Explicit config wins. */
6
+ preset?: MushiPreset;
5
7
  /**
6
8
  * Fetch non-secret widget/capture settings from the Mushi project at
7
9
  * startup. Defaults to true so console changes apply without rebuilding
8
10
  * host apps. Set false for fully static/offline deployments.
9
11
  */
10
- runtimeConfig?: boolean;
12
+ runtimeConfig?: boolean | 'auto';
11
13
  sentry?: MushiSentryConfig;
12
14
  widget?: MushiWidgetConfig;
13
15
  capture?: MushiCaptureConfig;
16
+ privacy?: MushiPrivacyConfig;
14
17
  proactive?: MushiProactiveConfig;
15
18
  preFilter?: MushiPreFilterConfig;
16
19
  integrations?: MushiIntegrationsConfig;
@@ -27,6 +30,11 @@ interface MushiSentryConfig {
27
30
  }
28
31
  interface MushiWidgetConfig {
29
32
  position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
33
+ /**
34
+ * Raw CSS anchors for app shells with bottom navs/chat composers/cookie
35
+ * banners. When set, these values win over `position` + numeric `inset`.
36
+ */
37
+ anchor?: MushiWidgetAnchor;
30
38
  theme?: 'auto' | 'light' | 'dark';
31
39
  triggerText?: string;
32
40
  expandedTitle?: string;
@@ -54,6 +62,10 @@ interface MushiWidgetConfig {
54
62
  /** Opt-in smart trigger behavior; planned to become the default in a later minor. */
55
63
  smartHide?: boolean | MushiWidgetSmartHideConfig;
56
64
  draggable?: boolean;
65
+ /** Show the tiny "Powered by Mushi vX" footer inside the widget panel. */
66
+ brandFooter?: boolean;
67
+ /** How the widget should surface SDK freshness warnings. Defaults to auto. */
68
+ outdatedBanner?: 'auto' | 'banner' | 'console-only' | 'off';
57
69
  }
58
70
  interface MushiWidgetInset {
59
71
  top?: number | 'auto';
@@ -61,6 +73,13 @@ interface MushiWidgetInset {
61
73
  bottom?: number | 'auto';
62
74
  left?: number | 'auto';
63
75
  }
76
+ interface MushiWidgetAnchor {
77
+ top?: string;
78
+ right?: string;
79
+ bottom?: string;
80
+ left?: string;
81
+ }
82
+ type MushiPreset = 'production-calm' | 'beta-loud' | 'internal-debug' | 'manual-only';
64
83
  interface MushiWidgetSmartHideConfig {
65
84
  onMobile?: 'edge-tab' | 'hide' | false;
66
85
  onScroll?: 'shrink' | 'hide' | false;
@@ -69,18 +88,104 @@ interface MushiWidgetSmartHideConfig {
69
88
  interface MushiCaptureConfig {
70
89
  console?: boolean;
71
90
  network?: boolean;
91
+ /**
92
+ * URLs that should never be captured as host-app traffic. Strings are
93
+ * substring matches; RegExp values are tested against the fully resolved URL.
94
+ */
95
+ ignoreUrls?: MushiUrlMatcher[];
72
96
  performance?: boolean;
73
97
  screenshot?: 'on-report' | 'auto' | 'off';
74
98
  elementSelector?: boolean;
75
99
  replay?: 'sentry' | 'rrweb' | 'lite' | 'off';
100
+ /**
101
+ * Mushi Mushi v2.1 (whitepaper §6 hybrid mode): passive inventory
102
+ * discovery. When enabled the SDK observes navigations and emits a
103
+ * tiny payload — `(route, title, testids[], outbound api paths[],
104
+ * dom_summary[≤200 chars])` — back to `/v1/sdk/discovery`. The
105
+ * server aggregates this over a 30-day rolling window and the
106
+ * admin can ask Claude to propose an `inventory.yaml` from it.
107
+ *
108
+ * Default: off. Recommended: enable in dev/preview/staging from the
109
+ * day you install the SDK so the proposer has data when you go to
110
+ * generate your first inventory.
111
+ *
112
+ * What is sent (per navigation, throttled to ≤1/route/min):
113
+ * - `location.pathname` template-normalized (`/practice/abc-123`
114
+ * → `/practice/[id]` via uuid/numeric/hex heuristics + an
115
+ * optional framework hint if the host app sets
116
+ * `discoverInventory.routeTemplates`)
117
+ * - `document.title`
118
+ * - All `[data-testid]` values currently in the DOM
119
+ * - Recent fetch/XHR paths the existing network capturer saw
120
+ * - The first text run of `<h1>` / `<title>` / `<main>` truncated
121
+ * to 200 chars (helps Claude name stories well)
122
+ * - Sanitized query-param keys (key names only, never values)
123
+ * - A SHA-256 of `userId || sessionId` so the server can dedupe
124
+ * distinct users without ever seeing identity
125
+ *
126
+ * Nothing else. No DOM beyond the summary, no query values, no PII.
127
+ */
128
+ discoverInventory?: boolean | MushiDiscoverInventoryConfig;
129
+ }
130
+ /**
131
+ * Fine-grained controls for `discoverInventory`. Defaults are tuned
132
+ * to be quiet enough for production but dense enough for the proposer
133
+ * to produce useful drafts.
134
+ */
135
+ interface MushiDiscoverInventoryConfig {
136
+ enabled?: boolean;
137
+ /**
138
+ * Minimum gap between two emissions for the same route (ms). Defaults
139
+ * to 60_000. Set to a larger value for high-traffic SPAs to keep the
140
+ * ingest volume manageable.
141
+ */
142
+ throttleMs?: number;
143
+ /**
144
+ * Static route templates the host framework knows about. When set,
145
+ * a visit to `/practice/abc-123` matches `/practice/[id]` and gets
146
+ * normalized to that template. Without this we fall back to a
147
+ * heuristic (uuid / numeric / 24-char-hex segments collapse to
148
+ * `[id]`). Provide this when you have a static manifest — Next.js'
149
+ * `next.config` route export, React Router's route config, etc.
150
+ */
151
+ routeTemplates?: string[];
152
+ /**
153
+ * Override the SHA-256 user-id-hash input. Defaults to
154
+ * `mushi.userId || sessionId`. Set to `null` to opt out of distinct-
155
+ * user counting entirely.
156
+ */
157
+ userIdSource?: 'auto' | 'session-only' | 'none';
158
+ /**
159
+ * If false, the DOM summary (≤200 chars from h1/title/main) is not
160
+ * captured. Default true.
161
+ */
162
+ captureDomSummary?: boolean;
163
+ }
164
+ interface MushiPrivacyConfig {
165
+ /** DOM nodes to visually mask in screenshots before upload. */
166
+ maskSelectors?: string[];
167
+ /** DOM subtrees to remove from screenshots before upload. */
168
+ blockSelectors?: string[];
169
+ /** Let reporters remove an attached screenshot before submitting. Defaults to true. */
170
+ allowUserRemoveScreenshot?: boolean;
76
171
  }
77
172
  interface MushiProactiveConfig {
78
173
  rageClick?: boolean;
79
174
  errorBoundary?: boolean;
80
175
  longTask?: boolean;
81
- apiCascade?: boolean;
176
+ apiCascade?: boolean | MushiApiCascadeConfig;
82
177
  cooldown?: MushiCooldownConfig;
83
178
  }
179
+ type MushiUrlMatcher = string | RegExp;
180
+ interface MushiApiCascadeConfig {
181
+ enabled?: boolean;
182
+ /**
183
+ * URLs ignored by the API cascade detector. The SDK always ignores its own
184
+ * gateway endpoints as well; this hook lets host apps exclude analytics,
185
+ * health probes, or third-party scripts that are noisy by design.
186
+ */
187
+ ignoreUrls?: MushiUrlMatcher[];
188
+ }
84
189
  interface MushiCooldownConfig {
85
190
  maxProactivePerSession?: number;
86
191
  dismissCooldownHours?: number;
@@ -167,6 +272,7 @@ interface MushiReport {
167
272
  consoleLogs?: MushiConsoleEntry[];
168
273
  networkLogs?: MushiNetworkEntry[];
169
274
  performanceMetrics?: MushiPerformanceMetrics;
275
+ timeline?: MushiTimelineEntry[];
170
276
  screenshotDataUrl?: string;
171
277
  selectedElement?: MushiSelectedElement;
172
278
  metadata?: Record<string, unknown>;
@@ -179,6 +285,10 @@ interface MushiReport {
179
285
  */
180
286
  fingerprintHash?: string;
181
287
  appVersion?: string;
288
+ /** SDK package that submitted the report, e.g. `@mushi-mushi/web`. */
289
+ sdkPackage?: string;
290
+ /** npm package version that submitted the report, e.g. `0.8.0`. */
291
+ sdkVersion?: string;
182
292
  proactiveTrigger?: string;
183
293
  sentryEventId?: string;
184
294
  sentryReplayId?: string;
@@ -209,6 +319,23 @@ interface MushiEnvironment {
209
319
  };
210
320
  deviceMemory?: number;
211
321
  hardwareConcurrency?: number;
322
+ /**
323
+ * v2 inventory hints (whitepaper §4.7).
324
+ *
325
+ * `route` — the bare pathname (no query / hash). Pinned to the
326
+ * inventory's `Page.path` so the Triage LLM can shortcut from a
327
+ * freeform "the streak counter is broken" report to the right page.
328
+ *
329
+ * `nearestTestid` — the closest ancestor of the active element with
330
+ * a `data-testid`, captured at widget-open time. This pins the
331
+ * report to one Action node when more than one page has the same
332
+ * page path (e.g. a shared "Buy Pro" CTA on landing + dashboard).
333
+ *
334
+ * Both are best-effort; freeform reports with no active element will
335
+ * have `route` only.
336
+ */
337
+ route?: string;
338
+ nearestTestid?: string;
212
339
  }
213
340
  interface MushiConsoleEntry {
214
341
  level: 'log' | 'warn' | 'error' | 'info' | 'debug';
@@ -247,12 +374,41 @@ interface MushiSelectedElement {
247
374
  width: number;
248
375
  height: number;
249
376
  };
377
+ /**
378
+ * `data-testid` of the closest ancestor that has one. Mushi v2 uses this
379
+ * to map a report → Action node in the bidirectional graph (whitepaper §4.7).
380
+ * Falls back to `undefined` when no ancestor declares a testid — the v2
381
+ * Triage LLM still classifies these reports, just without the inventory
382
+ * grounding shortcut.
383
+ */
384
+ nearestTestid?: string;
385
+ /** Path of the page the user reported from (`window.location.pathname`).
386
+ * Combined with `nearestTestid` it pins a report to one Action even when
387
+ * the same testid exists on multiple pages. */
388
+ route?: string;
389
+ }
390
+ type MushiTimelineKind = 'route' | 'click' | 'request' | 'log' | 'screen';
391
+ interface MushiTimelineEntry {
392
+ ts: number;
393
+ kind: MushiTimelineKind;
394
+ payload: Record<string, unknown>;
250
395
  }
251
396
  type MushiEventType = 'report:submitted' | 'report:queued' | 'report:sent' | 'report:failed' | 'widget:opened' | 'widget:closed' | 'proactive:triggered' | 'proactive:dismissed';
252
397
  type MushiEventHandler = (event: {
253
398
  type: MushiEventType;
254
399
  data?: unknown;
255
400
  }) => void;
401
+ interface MushiDiagnosticsResult {
402
+ apiEndpointReachable: boolean;
403
+ cspAllowsEndpoint: boolean;
404
+ widgetMounted: boolean;
405
+ shadowDomAvailable: boolean;
406
+ dialogSupported: boolean;
407
+ runtimeConfigLoaded: boolean;
408
+ captureScreenshotAvailable: boolean;
409
+ captureNetworkIntercepting: boolean;
410
+ sdkVersion: string;
411
+ }
256
412
  interface MushiSDKInstance {
257
413
  report(options?: {
258
414
  category?: MushiReportCategory;
@@ -264,6 +420,11 @@ interface MushiSDKInstance {
264
420
  name?: string;
265
421
  }): void;
266
422
  setMetadata(key: string, value: unknown): void;
423
+ setScreen(screen: {
424
+ name: string;
425
+ route?: string;
426
+ feature?: string;
427
+ }): void;
267
428
  isOpen(): boolean;
268
429
  open(): void;
269
430
  openWith(category: MushiReportCategory): void;
@@ -274,6 +435,7 @@ interface MushiSDKInstance {
274
435
  close(): void;
275
436
  destroy(): void;
276
437
  updateConfig(config: MushiRuntimeSdkConfig): void;
438
+ diagnose(): Promise<MushiDiagnosticsResult>;
277
439
  /**
278
440
  * Wave G4 — unified `captureEvent` API. Submits a bug report
279
441
  * programmatically without opening the widget. Useful for adapters
@@ -320,6 +482,42 @@ interface MushiApiClient {
320
482
  status: MushiReportStatus;
321
483
  }>>;
322
484
  getSdkConfig(): Promise<MushiApiResponse<MushiRuntimeSdkConfig>>;
485
+ getLatestSdkVersion(packageName: string): Promise<MushiApiResponse<MushiSdkVersionInfo>>;
486
+ /**
487
+ * Mushi v2.1: ship a single passive-discovery observation (route +
488
+ * testids + outbound APIs + DOM summary). Best-effort fire-and-forget;
489
+ * the caller should not block on the response. The server rate-limits
490
+ * per (project, route) to keep ingest cheap, so it's fine for clients
491
+ * to over-emit on the throttle window — the server picks the freshest.
492
+ */
493
+ postDiscoveryEvent(event: MushiDiscoveryEventPayload): Promise<MushiApiResponse<{
494
+ accepted: boolean;
495
+ }>>;
496
+ listReporterReports(reporterToken: string): Promise<MushiApiResponse<{
497
+ reports: MushiReporterReport[];
498
+ }>>;
499
+ listReporterComments(reportId: string, reporterToken: string): Promise<MushiApiResponse<{
500
+ comments: MushiReporterComment[];
501
+ }>>;
502
+ replyToReporterReport(reportId: string, reporterToken: string, body: string): Promise<MushiApiResponse<{
503
+ comment: MushiReporterComment;
504
+ }>>;
505
+ }
506
+ /**
507
+ * Wire shape of a single discovery event sent by the SDK to
508
+ * `POST /v1/sdk/discovery`. Mirrored server-side in
509
+ * `_shared/schemas.ts::discoveryEventSchema`.
510
+ */
511
+ interface MushiDiscoveryEventPayload {
512
+ route: string;
513
+ page_title?: string | null;
514
+ dom_summary?: string | null;
515
+ testids: string[];
516
+ network_paths: string[];
517
+ query_param_keys: string[];
518
+ user_id_hash?: string | null;
519
+ sdk_version?: string;
520
+ observed_at: string;
323
521
  }
324
522
  interface MushiApiResponse<T> {
325
523
  ok: boolean;
@@ -339,6 +537,34 @@ interface MushiRuntimeSdkConfig {
339
537
  minDescriptionLength?: number;
340
538
  };
341
539
  }
540
+ interface MushiSdkVersionInfo {
541
+ package: string;
542
+ latest: string | null;
543
+ current?: string;
544
+ deprecated: boolean;
545
+ deprecationMessage?: string | null;
546
+ releasedAt?: string | null;
547
+ }
548
+ interface MushiReporterReport {
549
+ id: string;
550
+ status: string;
551
+ category: string;
552
+ severity?: string | null;
553
+ summary?: string | null;
554
+ description?: string | null;
555
+ created_at: string;
556
+ last_admin_reply_at?: string | null;
557
+ last_reporter_reply_at?: string | null;
558
+ unread_count?: number;
559
+ }
560
+ interface MushiReporterComment {
561
+ id: number;
562
+ author_kind: 'admin' | 'reporter';
563
+ author_name?: string | null;
564
+ body: string;
565
+ visible_to_reporter?: boolean;
566
+ created_at: string;
567
+ }
342
568
 
343
569
  interface ApiClientOptions {
344
570
  projectId: string;
@@ -352,6 +578,9 @@ interface ApiClientOptions {
352
578
  maxRetries?: number;
353
579
  }
354
580
  declare const DEFAULT_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
581
+ declare const MUSHI_INTERNAL_HEADER = "X-Mushi-Internal";
582
+ declare const MUSHI_INTERNAL_INIT_MARKER = "__mushiInternal";
583
+ type MushiInternalRequestKind = 'sdk-config' | 'report-submit' | 'report-status' | 'reporter-poll' | 'diagnose' | 'discovery';
355
584
  declare function createApiClient(options: ApiClientOptions): MushiApiClient;
356
585
 
357
586
  /**
@@ -543,4 +772,4 @@ declare function createLogger(options: LoggerOptions): Logger;
543
772
  */
544
773
  declare const noopLogger: Logger;
545
774
 
546
- export { type ApiClientOptions, DEFAULT_API_ENDPOINT, type LogEntry, type LogFormat, type LogLevel, type Logger, type LoggerOptions, type MushiApiClient, type MushiApiResponse, type MushiCaptureConfig, type MushiCaptureEventInput, type MushiConfig, type MushiConsoleEntry, type MushiCooldownConfig, type MushiEnvironment, type MushiEventHandler, type MushiEventType, type MushiIntegrationsConfig, type MushiNetworkEntry, type MushiOfflineConfig, type MushiOnDeviceClassifier, type MushiOnDeviceClassifierInput, type MushiOnDeviceClassifierResult, type MushiPerformanceMetrics, type MushiPreFilterConfig, type MushiProactiveConfig, type MushiRegion, type MushiReport, type MushiReportBuilder, type MushiReportCategory, type MushiReportStatus, type MushiRewardsConfig, type MushiRuntimeSdkConfig, type MushiSDKInstance, type MushiSelectedElement, type MushiSentryConfig, type MushiWidgetConfig, type OfflineQueue, type PiiScrubberConfig, type PreFilterResult, REGION_ENDPOINTS, type RateLimiter, type RateLimiterConfig, captureEnvironment, createApiClient, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, getDeviceFingerprintHash, getReporterToken, getSessionId, noopLogger, resolveRegionEndpoint, scrubPii };
775
+ export { type ApiClientOptions, DEFAULT_API_ENDPOINT, type LogEntry, type LogFormat, type LogLevel, type Logger, type LoggerOptions, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, type MushiApiCascadeConfig, type MushiApiClient, type MushiApiResponse, type MushiCaptureConfig, type MushiCaptureEventInput, type MushiConfig, type MushiConsoleEntry, type MushiCooldownConfig, type MushiDiagnosticsResult, type MushiDiscoverInventoryConfig, type MushiDiscoveryEventPayload, type MushiEnvironment, type MushiEventHandler, type MushiEventType, type MushiIntegrationsConfig, type MushiInternalRequestKind, type MushiNetworkEntry, type MushiOfflineConfig, type MushiOnDeviceClassifier, type MushiOnDeviceClassifierInput, type MushiOnDeviceClassifierResult, 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 MushiRewardsConfig, type MushiRuntimeSdkConfig, type MushiSDKInstance, type MushiSdkVersionInfo, type MushiSelectedElement, type MushiSentryConfig, type MushiTimelineEntry, type MushiTimelineKind, type MushiUrlMatcher, type MushiWidgetAnchor, type MushiWidgetConfig, type OfflineQueue, type PiiScrubberConfig, type PreFilterResult, REGION_ENDPOINTS, type RateLimiter, type RateLimiterConfig, captureEnvironment, createApiClient, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, getDeviceFingerprintHash, getReporterToken, getSessionId, noopLogger, resolveRegionEndpoint, scrubPii };
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  // src/api-client.ts
2
2
  var DEFAULT_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
3
+ var MUSHI_INTERNAL_HEADER = "X-Mushi-Internal";
4
+ var MUSHI_INTERNAL_INIT_MARKER = "__mushiInternal";
3
5
  var DEFAULT_TIMEOUT = 1e4;
4
6
  var DEFAULT_MAX_RETRIES = 2;
5
7
  function createApiClient(options) {
@@ -11,7 +13,7 @@ function createApiClient(options) {
11
13
  maxRetries = DEFAULT_MAX_RETRIES
12
14
  } = options;
13
15
  let baseUrl = apiEndpoint.replace(/\/$/, "");
14
- async function request(method, path, body, retries = maxRetries) {
16
+ async function request(method, path, body, retries = maxRetries, internalKind) {
15
17
  const url = `${baseUrl}${path}`;
16
18
  const controller = new AbortController();
17
19
  const timer = setTimeout(() => controller.abort(), timeout);
@@ -21,10 +23,12 @@ function createApiClient(options) {
21
23
  headers: {
22
24
  "Content-Type": "application/json",
23
25
  "X-Mushi-Api-Key": apiKey,
24
- "X-Mushi-Project": projectId
26
+ "X-Mushi-Project": projectId,
27
+ ...internalKind ? { [MUSHI_INTERNAL_HEADER]: internalKind } : {}
25
28
  },
26
29
  body: body ? JSON.stringify(body) : void 0,
27
- signal: controller.signal
30
+ signal: controller.signal,
31
+ ...internalKind ? { [MUSHI_INTERNAL_INIT_MARKER]: internalKind } : {}
28
32
  });
29
33
  clearTimeout(timer);
30
34
  if (response.status === 307 || response.status === 308) {
@@ -33,7 +37,7 @@ function createApiClient(options) {
33
37
  const targetBase = target.replace(/\/v1\/.*$/, "").replace(/\/$/, "");
34
38
  if (targetBase !== baseUrl) {
35
39
  baseUrl = targetBase;
36
- return request(method, path, body, retries - 1);
40
+ return request(method, path, body, retries - 1, internalKind);
37
41
  }
38
42
  }
39
43
  }
@@ -41,7 +45,7 @@ function createApiClient(options) {
41
45
  const errorBody = await response.json().catch(() => ({}));
42
46
  if (response.status >= 500 && retries > 0) {
43
47
  await sleep(getBackoffDelay(maxRetries - retries));
44
- return request(method, path, body, retries - 1);
48
+ return request(method, path, body, retries - 1, internalKind);
45
49
  }
46
50
  return {
47
51
  ok: false,
@@ -58,7 +62,7 @@ function createApiClient(options) {
58
62
  clearTimeout(timer);
59
63
  if (retries > 0 && isRetryable(error)) {
60
64
  await sleep(getBackoffDelay(maxRetries - retries));
61
- return request(method, path, body, retries - 1);
65
+ return request(method, path, body, retries - 1, internalKind);
62
66
  }
63
67
  return {
64
68
  ok: false,
@@ -69,18 +73,96 @@ function createApiClient(options) {
69
73
  };
70
74
  }
71
75
  }
76
+ async function requestForReporter(method, path, reporterToken, body) {
77
+ const tokenHash = await sha256Hex(reporterToken);
78
+ const ts = String(Date.now());
79
+ const hmac = await hmacSha256Hex(apiKey, `${projectId}.${ts}.${tokenHash}`);
80
+ const url = `${baseUrl}${path}`;
81
+ const response = await fetch(url, {
82
+ method,
83
+ headers: {
84
+ "Content-Type": "application/json",
85
+ "X-Mushi-Api-Key": apiKey,
86
+ "X-Mushi-Project": projectId,
87
+ [MUSHI_INTERNAL_HEADER]: "reporter-poll",
88
+ "X-Reporter-Token-Hash": tokenHash,
89
+ "X-Reporter-Ts": ts,
90
+ "X-Reporter-Hmac": hmac
91
+ },
92
+ body: body ? JSON.stringify(body) : void 0,
93
+ [MUSHI_INTERNAL_INIT_MARKER]: "reporter-poll"
94
+ });
95
+ if (!response.ok) {
96
+ const errorBody = await response.json().catch(() => ({}));
97
+ return {
98
+ ok: false,
99
+ error: {
100
+ code: `HTTP_${response.status}`,
101
+ message: errorBody.error?.message ?? errorBody.message ?? `HTTP ${response.status} error`
102
+ }
103
+ };
104
+ }
105
+ const payload = await response.json();
106
+ return { ok: true, data: payload.data ?? payload };
107
+ }
72
108
  return {
73
109
  async submitReport(report) {
74
- return request("POST", "/v1/reports", report);
110
+ return request("POST", "/v1/reports", report, maxRetries, "report-submit");
75
111
  },
76
112
  async getReportStatus(reportId) {
77
- return request("GET", `/v1/reports/${reportId}/status`);
113
+ return request("GET", `/v1/reports/${reportId}/status`, void 0, maxRetries, "report-status");
78
114
  },
79
115
  async getSdkConfig() {
80
- return request("GET", "/v1/sdk/config");
116
+ return request("GET", "/v1/sdk/config", void 0, maxRetries, "sdk-config");
117
+ },
118
+ async getLatestSdkVersion(packageName) {
119
+ const query = new URLSearchParams({ package: packageName }).toString();
120
+ return request("GET", `/v1/sdk/latest-version?${query}`, void 0, maxRetries, "sdk-config");
121
+ },
122
+ async postDiscoveryEvent(event) {
123
+ return request(
124
+ "POST",
125
+ "/v1/sdk/discovery",
126
+ event,
127
+ 1,
128
+ "discovery"
129
+ );
130
+ },
131
+ async listReporterReports(reporterToken) {
132
+ return requestForReporter("GET", "/v1/reporter/reports", reporterToken);
133
+ },
134
+ async listReporterComments(reportId, reporterToken) {
135
+ return requestForReporter(
136
+ "GET",
137
+ `/v1/reporter/reports/${reportId}/comments`,
138
+ reporterToken
139
+ );
140
+ },
141
+ async replyToReporterReport(reportId, reporterToken, body) {
142
+ return requestForReporter(
143
+ "POST",
144
+ `/v1/reporter/reports/${reportId}/reply`,
145
+ reporterToken,
146
+ { body }
147
+ );
81
148
  }
82
149
  };
83
150
  }
151
+ async function sha256Hex(value) {
152
+ const buffer = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
153
+ return Array.from(new Uint8Array(buffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
154
+ }
155
+ async function hmacSha256Hex(secret, value) {
156
+ const key = await crypto.subtle.importKey(
157
+ "raw",
158
+ new TextEncoder().encode(secret),
159
+ { name: "HMAC", hash: "SHA-256" },
160
+ false,
161
+ ["sign"]
162
+ );
163
+ const buffer = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(value));
164
+ return Array.from(new Uint8Array(buffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
165
+ }
84
166
  function sleep(ms) {
85
167
  return new Promise((resolve) => setTimeout(resolve, ms));
86
168
  }
@@ -720,9 +802,23 @@ function captureEnvironment() {
720
802
  rtt: connection.rtt
721
803
  } : void 0,
722
804
  deviceMemory: nav?.deviceMemory,
723
- hardwareConcurrency: nav?.hardwareConcurrency
805
+ hardwareConcurrency: nav?.hardwareConcurrency,
806
+ route: win?.location?.pathname,
807
+ nearestTestid: findNearestTestidFromActive(doc)
724
808
  };
725
809
  }
810
+ function findNearestTestidFromActive(doc) {
811
+ if (!doc) return void 0;
812
+ let cur = doc.activeElement ?? null;
813
+ let hops = 0;
814
+ while (cur && hops < 20) {
815
+ const tid = cur.getAttribute?.("data-testid");
816
+ if (tid) return tid;
817
+ cur = cur.parentElement;
818
+ hops++;
819
+ }
820
+ return void 0;
821
+ }
726
822
 
727
823
  // src/reporter-token.ts
728
824
  var STORAGE_KEY = "mushi_reporter_token";
@@ -774,7 +870,7 @@ function collectInputs() {
774
870
  hardwareConcurrency: nav?.hardwareConcurrency
775
871
  };
776
872
  }
777
- async function sha256Hex(input) {
873
+ async function sha256Hex2(input) {
778
874
  if (typeof crypto !== "undefined" && crypto.subtle) {
779
875
  const buf = new TextEncoder().encode(input);
780
876
  const digest = await crypto.subtle.digest("SHA-256", buf);
@@ -794,7 +890,7 @@ async function getDeviceFingerprintHash() {
794
890
  }
795
891
  const inputs = collectInputs();
796
892
  const serialised = JSON.stringify(inputs);
797
- const hash = await sha256Hex(serialised);
893
+ const hash = await sha256Hex2(serialised);
798
894
  if (typeof localStorage !== "undefined") {
799
895
  try {
800
896
  localStorage.setItem(CACHE_KEY, hash);
@@ -930,6 +1026,6 @@ function scrubPii(text, config) {
930
1026
  return createPiiScrubber(config).scrub(text);
931
1027
  }
932
1028
 
933
- export { DEFAULT_API_ENDPOINT, REGION_ENDPOINTS, captureEnvironment, createApiClient, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, getDeviceFingerprintHash, getReporterToken, getSessionId, noopLogger, resolveRegionEndpoint, scrubPii };
1029
+ export { DEFAULT_API_ENDPOINT, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, REGION_ENDPOINTS, captureEnvironment, createApiClient, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, getDeviceFingerprintHash, getReporterToken, getSessionId, noopLogger, resolveRegionEndpoint, scrubPii };
934
1030
  //# sourceMappingURL=index.js.map
935
1031
  //# sourceMappingURL=index.js.map