@mushi-mushi/web 0.9.0 → 1.0.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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createLogger, noopLogger, createApiClient, createPreFilter, createOfflineQueue, createRateLimiter, createPiiScrubber, getReporterToken, getDeviceFingerprintHash, getSessionId, captureEnvironment, DEFAULT_API_ENDPOINT, MUSHI_INTERNAL_INIT_MARKER, MUSHI_INTERNAL_HEADER } from '@mushi-mushi/core';
1
+ import { createLogger, noopLogger, createApiClient, createPreFilter, createOfflineQueue, createRateLimiter, createPiiScrubber, createBreadcrumbBuffer, getReporterToken, getDeviceFingerprintHash, getSessionId, captureEnvironment, DEFAULT_API_ENDPOINT, MUSHI_INTERNAL_INIT_MARKER, MUSHI_INTERNAL_HEADER, normaliseThrown } from '@mushi-mushi/core';
2
2
 
3
3
  // src/mushi.ts
4
4
 
@@ -2466,42 +2466,211 @@ function createDiscoveryCapture(opts) {
2466
2466
  // src/sentry.ts
2467
2467
  function getSentryGlobal() {
2468
2468
  try {
2469
- const win = globalThis;
2470
- if (win.__SENTRY__) {
2471
- const sentry = win.__SENTRY__;
2472
- const hub = sentry.hub;
2473
- return hub;
2469
+ const w = globalThis;
2470
+ if (w.Sentry) return w.Sentry;
2471
+ return void 0;
2472
+ } catch {
2473
+ return void 0;
2474
+ }
2475
+ }
2476
+ function getSentryReplayGlobal() {
2477
+ try {
2478
+ const w = globalThis;
2479
+ return w.__SENTRY_REPLAY__;
2480
+ } catch {
2481
+ return void 0;
2482
+ }
2483
+ }
2484
+ function detectSentrySdkFamily() {
2485
+ try {
2486
+ const w = globalThis;
2487
+ const meta = w.__SENTRY__;
2488
+ const sentry = w.Sentry;
2489
+ if (meta?.version === "9" || sentry && typeof sentry.lastEventId === "function") {
2490
+ return meta?.version === "9" ? "v9" : "v8";
2491
+ }
2492
+ if (meta?.version === "8") return "v8";
2493
+ if (sentry && typeof sentry.getCurrentHub === "function") return "v7";
2494
+ return "unknown";
2495
+ } catch {
2496
+ return "unknown";
2497
+ }
2498
+ }
2499
+ function captureSentryContext(_config, options = {}) {
2500
+ const limit = Math.max(0, options.breadcrumbsLimit ?? 30);
2501
+ const out = {};
2502
+ const sentry = getSentryGlobal();
2503
+ if (!sentry) return out;
2504
+ out.sdk = detectSentrySdkFamily();
2505
+ try {
2506
+ const v8 = sentry;
2507
+ if (typeof v8.lastEventId === "function") {
2508
+ out.eventId = v8.lastEventId() ?? void 0;
2509
+ } else {
2510
+ const v7 = sentry;
2511
+ const scope2 = v7.getCurrentHub?.()?.getScope?.();
2512
+ out.eventId = scope2?.getLastEventId?.() ?? void 0;
2474
2513
  }
2475
- if (win.Sentry) {
2476
- return win.Sentry;
2514
+ } catch {
2515
+ }
2516
+ let scope;
2517
+ try {
2518
+ const v8 = sentry;
2519
+ if (typeof v8.getCurrentScope === "function") {
2520
+ scope = v8.getCurrentScope();
2521
+ } else {
2522
+ const v7 = sentry;
2523
+ scope = v7.getCurrentHub?.()?.getScope?.();
2477
2524
  }
2478
2525
  } catch {
2479
2526
  }
2480
- return void 0;
2527
+ if (scope) {
2528
+ try {
2529
+ const user = scope.getUser?.();
2530
+ if (user) {
2531
+ out.user = {
2532
+ id: typeof user.id === "string" ? user.id : void 0,
2533
+ email: typeof user.email === "string" ? user.email : void 0,
2534
+ username: typeof user.username === "string" ? user.username : void 0,
2535
+ ip_address: typeof user.ip_address === "string" ? user.ip_address : void 0
2536
+ };
2537
+ }
2538
+ } catch {
2539
+ }
2540
+ try {
2541
+ const tags = scope.getTags?.();
2542
+ if (tags && typeof tags === "object") {
2543
+ const pruned = {};
2544
+ for (const [k, v] of Object.entries(tags)) {
2545
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
2546
+ pruned[k] = v;
2547
+ }
2548
+ }
2549
+ if (Object.keys(pruned).length > 0) out.tags = pruned;
2550
+ }
2551
+ } catch {
2552
+ }
2553
+ try {
2554
+ out.transactionName = scope.getTransactionName?.() ?? scope.getTransaction?.()?.name ?? void 0;
2555
+ } catch {
2556
+ }
2557
+ try {
2558
+ out.sessionId = scope.getSession?.()?.sid ?? void 0;
2559
+ } catch {
2560
+ }
2561
+ try {
2562
+ const raw = scope.getBreadcrumbs?.() ?? scope._breadcrumbs ?? [];
2563
+ if (Array.isArray(raw) && raw.length > 0) {
2564
+ const sliced = raw.slice(-limit);
2565
+ out.breadcrumbs = sliced.map((b) => {
2566
+ const r = b;
2567
+ return {
2568
+ timestamp: typeof r.timestamp === "number" ? (
2569
+ // Sentry stores breadcrumb timestamps in seconds; convert
2570
+ // to ms so the field is comparable to Mushi's own.
2571
+ r.timestamp < 1e12 ? Math.round(r.timestamp * 1e3) : r.timestamp
2572
+ ) : void 0,
2573
+ category: typeof r.category === "string" ? r.category : void 0,
2574
+ level: typeof r.level === "string" ? r.level : void 0,
2575
+ message: typeof r.message === "string" ? r.message : void 0,
2576
+ type: typeof r.type === "string" ? r.type : void 0,
2577
+ data: r.data && typeof r.data === "object" ? r.data : void 0
2578
+ };
2579
+ });
2580
+ }
2581
+ } catch {
2582
+ }
2583
+ }
2584
+ try {
2585
+ const v8 = sentry;
2586
+ let span;
2587
+ if (typeof v8.getActiveSpan === "function") {
2588
+ span = v8.getActiveSpan();
2589
+ } else if (scope?.getSpan) {
2590
+ span = scope.getSpan();
2591
+ }
2592
+ if (span) {
2593
+ const ctx = span.spanContext?.();
2594
+ out.traceId = ctx?.traceId ?? span.traceId ?? void 0;
2595
+ out.spanId = ctx?.spanId ?? span.spanId ?? void 0;
2596
+ }
2597
+ } catch {
2598
+ }
2599
+ let client;
2600
+ try {
2601
+ const v8 = sentry;
2602
+ if (typeof v8.getClient === "function") {
2603
+ client = v8.getClient();
2604
+ } else {
2605
+ const v7 = sentry;
2606
+ client = v7.getCurrentHub?.()?.getClient?.();
2607
+ }
2608
+ } catch {
2609
+ }
2610
+ if (client) {
2611
+ try {
2612
+ const opts = client.getOptions?.();
2613
+ if (opts?.release) out.release = opts.release;
2614
+ if (opts?.environment) out.environment = opts.environment;
2615
+ } catch {
2616
+ }
2617
+ try {
2618
+ const dsn = client.getDsn?.();
2619
+ if (dsn?.host && dsn?.projectId && out.eventId) {
2620
+ const orgHost = dsn.host.replace(/^o\d+\./, "");
2621
+ out.issueUrl = `https://${orgHost}/issues/?query=${encodeURIComponent(out.eventId)}`;
2622
+ }
2623
+ } catch {
2624
+ }
2625
+ }
2626
+ try {
2627
+ const v8 = sentry;
2628
+ const replay = v8.getReplay?.() ?? getSentryReplayGlobal();
2629
+ out.replayId = replay?.getReplayId?.() ?? void 0;
2630
+ } catch {
2631
+ }
2632
+ return out;
2481
2633
  }
2482
- function captureSentryContext(_config) {
2483
- const context = {};
2634
+ function tagSentryScope(reportId, options = {}) {
2635
+ const sentry = getSentryGlobal();
2636
+ if (!sentry) return;
2484
2637
  try {
2485
- const hub = getSentryGlobal();
2486
- if (!hub) return context;
2487
- const scope = hub.getScope?.();
2488
- if (scope) {
2489
- context.eventId = scope.getLastEventId?.() ?? void 0;
2638
+ const v8 = sentry;
2639
+ if (typeof v8.setTag === "function") {
2640
+ v8.setTag("mushi.report_id", reportId);
2641
+ if (options.reportUrl) v8.setTag("mushi.report_url", options.reportUrl);
2642
+ }
2643
+ if (typeof v8.setContext === "function") {
2644
+ v8.setContext("mushi_report", {
2645
+ id: reportId,
2646
+ ...options.reportUrl ? { url: options.reportUrl } : {},
2647
+ captured_at: (/* @__PURE__ */ new Date()).toISOString()
2648
+ });
2490
2649
  }
2491
- const client = hub.getClient?.();
2492
- if (client) {
2493
- const options = client.getOptions?.();
2494
- context.release = options?.release;
2495
- context.environment = options?.environment;
2650
+ if (typeof v8.addBreadcrumb === "function") {
2651
+ v8.addBreadcrumb({
2652
+ category: "mushi",
2653
+ type: "info",
2654
+ level: "info",
2655
+ message: `Mushi report submitted (${reportId})`,
2656
+ data: { report_id: reportId, ...options.reportUrl ? { url: options.reportUrl } : {} }
2657
+ });
2496
2658
  }
2497
- const win = globalThis;
2498
- if (win.__SENTRY_REPLAY__) {
2499
- const replay = win.__SENTRY_REPLAY__;
2500
- context.replayId = replay.getReplayId?.() ?? void 0;
2659
+ } catch {
2660
+ }
2661
+ try {
2662
+ const v7 = sentry;
2663
+ const scope = v7.getCurrentHub?.()?.getScope?.();
2664
+ if (scope) {
2665
+ scope.setTag?.("mushi.report_id", reportId);
2666
+ if (options.reportUrl) scope.setTag?.("mushi.report_url", options.reportUrl);
2667
+ scope.setContext?.("mushi_report", {
2668
+ id: reportId,
2669
+ ...options.reportUrl ? { url: options.reportUrl } : {}
2670
+ });
2501
2671
  }
2502
2672
  } catch {
2503
2673
  }
2504
- return context;
2505
2674
  }
2506
2675
 
2507
2676
  // src/proactive-triggers.ts
@@ -2678,7 +2847,7 @@ function createProactiveManager(config = {}) {
2678
2847
 
2679
2848
  // src/version.ts
2680
2849
  var MUSHI_SDK_PACKAGE = "@mushi-mushi/web";
2681
- var MUSHI_SDK_VERSION = "0.9.0" ;
2850
+ var MUSHI_SDK_VERSION = "1.0.0" ;
2682
2851
 
2683
2852
  // src/mushi.ts
2684
2853
  var instance = null;
@@ -2726,6 +2895,30 @@ function createInstance(config) {
2726
2895
  const offlineQueue = createOfflineQueue(bootstrapConfig.offline);
2727
2896
  const rateLimiter = createRateLimiter({ maxBurst: 10, refillRate: 1, refillIntervalMs: 5e3 });
2728
2897
  const piiScrubber = createPiiScrubber();
2898
+ function scrubBreadcrumbsForWire(crumbs) {
2899
+ return crumbs.map((c) => {
2900
+ const next = { ...c };
2901
+ if (typeof c.message === "string") {
2902
+ next.message = piiScrubber.scrub(c.message);
2903
+ }
2904
+ if (c.data && typeof c.data === "object") {
2905
+ const cleaned = {};
2906
+ for (const [k, v] of Object.entries(c.data)) {
2907
+ cleaned[k] = typeof v === "string" ? piiScrubber.scrub(v) : v;
2908
+ }
2909
+ next.data = cleaned;
2910
+ }
2911
+ return next;
2912
+ });
2913
+ }
2914
+ function scrubTagsForWire(tags) {
2915
+ if (!tags) return void 0;
2916
+ const out = {};
2917
+ for (const [k, v] of Object.entries(tags)) {
2918
+ out[k] = typeof v === "string" ? piiScrubber.scrub(v) : v;
2919
+ }
2920
+ return out;
2921
+ }
2729
2922
  let consoleCap = null;
2730
2923
  let networkCap = null;
2731
2924
  let perfCap = null;
@@ -2825,6 +3018,16 @@ function createInstance(config) {
2825
3018
  let runtimeConfigLoaded = false;
2826
3019
  let userInfo = null;
2827
3020
  const customMetadata = {};
3021
+ const stickyTags = {};
3022
+ const breadcrumbs = createBreadcrumbBuffer({ max: 50 });
3023
+ breadcrumbs.add({
3024
+ category: "lifecycle",
3025
+ level: "info",
3026
+ message: "Mushi SDK init",
3027
+ data: { projectId: bootstrapConfig.projectId, sdkVersion: MUSHI_SDK_VERSION }
3028
+ });
3029
+ let detachAutoBreadcrumbs = null;
3030
+ detachAutoBreadcrumbs = installAutoBreadcrumbs(breadcrumbs);
2828
3031
  widget = new MushiWidget(bootstrapConfig.widget, {
2829
3032
  onSubmit: async ({ category, description, intent }) => {
2830
3033
  log.info("Report submitted", { category, intent });
@@ -3034,6 +3237,15 @@ function createInstance(config) {
3034
3237
  const fingerprintHash = await getDeviceFingerprintHash().catch(() => null);
3035
3238
  const consoleLogs = activeConfig.capture?.console === false ? void 0 : consoleCap?.getEntries();
3036
3239
  const networkLogs = activeConfig.capture?.network === false ? void 0 : networkCap?.getEntries();
3240
+ const reportBreadcrumbs = scrubBreadcrumbsForWire(breadcrumbs.getAll());
3241
+ const stickyTagSnapshot = scrubTagsForWire(
3242
+ Object.keys(stickyTags).length > 0 ? { ...stickyTags } : void 0
3243
+ );
3244
+ const sentryCtxScrubbed = sentryCtx ? {
3245
+ ...sentryCtx,
3246
+ ...sentryCtx.breadcrumbs ? { breadcrumbs: scrubBreadcrumbsForWire(sentryCtx.breadcrumbs) } : {},
3247
+ ...sentryCtx.tags ? { tags: scrubTagsForWire(sentryCtx.tags) } : {}
3248
+ } : void 0;
3037
3249
  const report = {
3038
3250
  id: crypto.randomUUID?.() ?? `mushi_${Date.now()}_${Math.random().toString(36).slice(2)}`,
3039
3251
  projectId: config.projectId,
@@ -3059,10 +3271,24 @@ function createInstance(config) {
3059
3271
  sdkPackage: MUSHI_SDK_PACKAGE,
3060
3272
  sdkVersion: MUSHI_SDK_VERSION,
3061
3273
  proactiveTrigger: pendingProactiveTrigger ?? void 0,
3274
+ // Top-level Sentry-grade observability fields. Breadcrumbs are
3275
+ // surfaced separately from `consoleLogs` because they're the
3276
+ // higher-signal "what just happened" trail (vs. the high-volume
3277
+ // raw console mirror), and the admin /reports drawer shows them
3278
+ // in different panes.
3279
+ ...reportBreadcrumbs.length > 0 ? { breadcrumbs: reportBreadcrumbs } : {},
3280
+ ...stickyTagSnapshot ? { tags: stickyTagSnapshot } : {},
3281
+ ...sentryCtxScrubbed ? { sentryContext: sentryCtxScrubbed } : {},
3062
3282
  sentryEventId: sentryCtx?.eventId,
3063
3283
  sentryReplayId: sentryCtx?.replayId,
3064
3284
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
3065
3285
  };
3286
+ breadcrumbs.add({
3287
+ category: "lifecycle",
3288
+ level: "info",
3289
+ message: `Mushi report submitting (${category})`,
3290
+ data: { reportId: report.id, category }
3291
+ });
3066
3292
  if (config.integrations?.custom) {
3067
3293
  const builder = {
3068
3294
  addMetadata(key, value) {
@@ -3088,10 +3314,26 @@ function createInstance(config) {
3088
3314
  if (result.ok) {
3089
3315
  log.info("Report sent", { reportId: result.data?.reportId });
3090
3316
  emit("report:sent", { reportId: result.data?.reportId });
3317
+ breadcrumbs.add({
3318
+ category: "lifecycle",
3319
+ level: "info",
3320
+ message: `Mushi report sent (${result.data?.reportId ?? report.id})`
3321
+ });
3322
+ try {
3323
+ if (config.sentry && result.data?.reportId) {
3324
+ tagSentryScope(result.data.reportId);
3325
+ }
3326
+ } catch {
3327
+ }
3091
3328
  } else {
3092
3329
  log.warn("Report failed, queuing for retry", { reportId: report.id, error: result.error });
3093
3330
  await offlineQueue.enqueue(report);
3094
3331
  emit("report:failed", { reportId: report.id, error: result.error });
3332
+ breadcrumbs.add({
3333
+ category: "lifecycle",
3334
+ level: "warning",
3335
+ message: `Mushi report queued for retry (${report.id})`
3336
+ });
3095
3337
  }
3096
3338
  pendingScreenshot = null;
3097
3339
  pendingElement = null;
@@ -3163,6 +3405,9 @@ function createInstance(config) {
3163
3405
  discoveryCap?.destroy();
3164
3406
  discoveryCap = null;
3165
3407
  offlineQueue.stopAutoSync();
3408
+ detachAutoBreadcrumbs?.();
3409
+ detachAutoBreadcrumbs = null;
3410
+ breadcrumbs.clear();
3166
3411
  listeners.clear();
3167
3412
  instance = null;
3168
3413
  log.debug("Destroyed");
@@ -3177,6 +3422,16 @@ function createInstance(config) {
3177
3422
  }
3178
3423
  const description = piiScrubber.scrub(preFilter.truncate(input.description));
3179
3424
  const category = input.category ?? "bug";
3425
+ const sentryCtx = config.sentry ? captureSentryContext(config.sentry) : void 0;
3426
+ const captureBreadcrumbs = scrubBreadcrumbsForWire(breadcrumbs.getAll());
3427
+ const mergedTags = scrubTagsForWire(
3428
+ Object.keys(stickyTags).length === 0 && !input.tags ? void 0 : { ...stickyTags, ...input.tags ?? {} }
3429
+ );
3430
+ const sentryCtxScrubbed = sentryCtx ? {
3431
+ ...sentryCtx,
3432
+ ...sentryCtx.breadcrumbs ? { breadcrumbs: scrubBreadcrumbsForWire(sentryCtx.breadcrumbs) } : {},
3433
+ ...sentryCtx.tags ? { tags: scrubTagsForWire(sentryCtx.tags) } : {}
3434
+ } : void 0;
3180
3435
  const report = {
3181
3436
  id: crypto.randomUUID?.() ?? `mushi_${Date.now()}_${Math.random().toString(36).slice(2)}`,
3182
3437
  projectId: config.projectId,
@@ -3187,16 +3442,20 @@ function createInstance(config) {
3187
3442
  metadata: {
3188
3443
  ...input.metadata ?? {},
3189
3444
  ...userInfo ? { user: userInfo } : {},
3190
- ...input.tags ? { tags: input.tags } : {},
3191
3445
  ...input.error ? { error: input.error } : {},
3192
3446
  ...input.severity ? { severity: input.severity } : {},
3193
3447
  ...input.component ? { component: input.component } : {},
3194
3448
  ...input.source ? { source: input.source } : { source: "captureEvent" }
3195
3449
  },
3450
+ ...captureBreadcrumbs.length > 0 ? { breadcrumbs: captureBreadcrumbs } : {},
3451
+ ...mergedTags && Object.keys(mergedTags).length > 0 ? { tags: mergedTags } : {},
3452
+ ...sentryCtxScrubbed ? { sentryContext: sentryCtxScrubbed } : {},
3196
3453
  sessionId: getSessionId(),
3197
3454
  reporterToken: getReporterToken(),
3198
3455
  sdkPackage: MUSHI_SDK_PACKAGE,
3199
3456
  sdkVersion: MUSHI_SDK_VERSION,
3457
+ sentryEventId: sentryCtx?.eventId,
3458
+ sentryReplayId: sentryCtx?.replayId,
3200
3459
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
3201
3460
  };
3202
3461
  emit("report:submitted", { reportId: report.id });
@@ -3208,12 +3467,43 @@ function createInstance(config) {
3208
3467
  const res = await apiClient.submitReport(report);
3209
3468
  if (res.ok) {
3210
3469
  emit("report:sent", { reportId: res.data?.reportId });
3470
+ try {
3471
+ if (config.sentry && res.data?.reportId) tagSentryScope(res.data.reportId);
3472
+ } catch {
3473
+ }
3211
3474
  return res.data?.reportId ?? null;
3212
3475
  }
3213
3476
  await offlineQueue.enqueue(report);
3214
3477
  emit("report:failed", { reportId: report.id, error: res.error });
3215
3478
  return null;
3216
3479
  },
3480
+ async captureException(error, options) {
3481
+ const normalised = normaliseThrown(error);
3482
+ breadcrumbs.add({
3483
+ category: "lifecycle",
3484
+ level: "error",
3485
+ message: `Mushi.captureException(${normalised.name}): ${normalised.message}`,
3486
+ ...normalised.stack ? { data: { stack: normalised.stack.slice(0, 500) } } : {}
3487
+ });
3488
+ const description = options?.description?.trim() || `${normalised.name}: ${normalised.message}` || "Uncaught exception";
3489
+ return sdk.captureEvent({
3490
+ description,
3491
+ category: options?.category ?? "bug",
3492
+ severity: options?.severity ?? "high",
3493
+ ...options?.component ? { component: options.component } : {},
3494
+ ...options?.tags ? { tags: options.tags } : {},
3495
+ source: options?.source ?? "captureException",
3496
+ error: {
3497
+ name: normalised.name,
3498
+ message: normalised.message,
3499
+ ...normalised.stack ? { stack: normalised.stack } : {}
3500
+ },
3501
+ metadata: {
3502
+ ...options?.metadata ?? {},
3503
+ ...normalised.cause ? { cause: normalised.cause } : {}
3504
+ }
3505
+ });
3506
+ },
3217
3507
  identify(userId, traits) {
3218
3508
  userInfo = { id: userId, ...traits?.email ? { email: traits.email } : {}, ...traits?.name ? { name: traits.name } : {} };
3219
3509
  if (traits) {
@@ -3221,6 +3511,36 @@ function createInstance(config) {
3221
3511
  if (k !== "email" && k !== "name") customMetadata[`user.${k}`] = v;
3222
3512
  }
3223
3513
  }
3514
+ breadcrumbs.add({
3515
+ category: "lifecycle",
3516
+ level: "info",
3517
+ message: `Mushi.identify(${userId})`
3518
+ });
3519
+ },
3520
+ addBreadcrumb(crumb) {
3521
+ breadcrumbs.add(crumb);
3522
+ },
3523
+ getBreadcrumbs() {
3524
+ return breadcrumbs.getAll();
3525
+ },
3526
+ setTag(key, value) {
3527
+ if (typeof key !== "string" || key.length === 0) return;
3528
+ stickyTags[key] = value;
3529
+ },
3530
+ setTags(tags) {
3531
+ if (!tags || typeof tags !== "object") return;
3532
+ for (const [k, v] of Object.entries(tags)) {
3533
+ if (typeof k === "string" && k.length > 0) {
3534
+ stickyTags[k] = v;
3535
+ }
3536
+ }
3537
+ },
3538
+ clearTag(key) {
3539
+ if (typeof key === "string" && key.length > 0) {
3540
+ delete stickyTags[key];
3541
+ return;
3542
+ }
3543
+ for (const k of Object.keys(stickyTags)) delete stickyTags[k];
3224
3544
  }
3225
3545
  };
3226
3546
  return sdk;
@@ -3458,10 +3778,144 @@ function createNoopInstance() {
3458
3778
  instance = null;
3459
3779
  },
3460
3780
  captureEvent: async () => null,
3781
+ captureException: async () => null,
3461
3782
  identify: () => {
3783
+ },
3784
+ addBreadcrumb: () => {
3785
+ },
3786
+ getBreadcrumbs: () => [],
3787
+ setTag: () => {
3788
+ },
3789
+ setTags: () => {
3790
+ },
3791
+ clearTag: () => {
3792
+ }
3793
+ };
3794
+ }
3795
+ function installAutoBreadcrumbs(buffer) {
3796
+ if (typeof window === "undefined") return () => {
3797
+ };
3798
+ const cleanups = [];
3799
+ try {
3800
+ const dispatchRouteChange = (kind) => {
3801
+ buffer.add({
3802
+ category: "navigation",
3803
+ level: "info",
3804
+ message: `${kind}: ${window.location.pathname}`,
3805
+ data: { url: window.location.href, kind }
3806
+ });
3807
+ };
3808
+ const onPop = () => dispatchRouteChange("popstate");
3809
+ window.addEventListener("popstate", onPop, { passive: true });
3810
+ cleanups.push(() => window.removeEventListener("popstate", onPop));
3811
+ const origPush = window.history.pushState;
3812
+ const origReplace = window.history.replaceState;
3813
+ window.history.pushState = function patched(...args) {
3814
+ const ret = origPush.apply(this, args);
3815
+ try {
3816
+ dispatchRouteChange("pushState");
3817
+ } catch {
3818
+ }
3819
+ return ret;
3820
+ };
3821
+ window.history.replaceState = function patched(...args) {
3822
+ const ret = origReplace.apply(this, args);
3823
+ try {
3824
+ dispatchRouteChange("replaceState");
3825
+ } catch {
3826
+ }
3827
+ return ret;
3828
+ };
3829
+ cleanups.push(() => {
3830
+ window.history.pushState = origPush;
3831
+ window.history.replaceState = origReplace;
3832
+ });
3833
+ } catch {
3834
+ }
3835
+ try {
3836
+ const origError = console.error;
3837
+ const origWarn = console.warn;
3838
+ console.error = function(...args) {
3839
+ try {
3840
+ buffer.add({
3841
+ category: "console",
3842
+ level: "error",
3843
+ message: args.map(stringifyConsoleArg).join(" ")
3844
+ });
3845
+ } catch {
3846
+ }
3847
+ return origError.apply(this, args);
3848
+ };
3849
+ console.warn = function(...args) {
3850
+ try {
3851
+ buffer.add({
3852
+ category: "console",
3853
+ level: "warning",
3854
+ message: args.map(stringifyConsoleArg).join(" ")
3855
+ });
3856
+ } catch {
3857
+ }
3858
+ return origWarn.apply(this, args);
3859
+ };
3860
+ cleanups.push(() => {
3861
+ console.error = origError;
3862
+ console.warn = origWarn;
3863
+ });
3864
+ } catch {
3865
+ }
3866
+ try {
3867
+ const onClick = (ev) => {
3868
+ try {
3869
+ const target = ev.target;
3870
+ if (!(target instanceof Element)) return;
3871
+ let cur = target;
3872
+ let hops = 0;
3873
+ while (cur && hops < 10) {
3874
+ const tid = cur.getAttribute("data-testid");
3875
+ if (tid) {
3876
+ const text = (cur.textContent ?? "").trim().slice(0, 80);
3877
+ buffer.add({
3878
+ category: "ui.click",
3879
+ level: "info",
3880
+ message: `clicked ${tid}${text ? ` \u2014 ${text}` : ""}`,
3881
+ data: { testid: tid, tag: cur.tagName.toLowerCase() }
3882
+ });
3883
+ return;
3884
+ }
3885
+ cur = cur.parentElement;
3886
+ hops++;
3887
+ }
3888
+ } catch {
3889
+ }
3890
+ };
3891
+ document.addEventListener("click", onClick, { passive: true, capture: true });
3892
+ cleanups.push(() => document.removeEventListener("click", onClick, true));
3893
+ } catch {
3894
+ }
3895
+ return () => {
3896
+ for (const c of cleanups) {
3897
+ try {
3898
+ c();
3899
+ } catch {
3900
+ }
3462
3901
  }
3463
3902
  };
3464
3903
  }
3904
+ function stringifyConsoleArg(arg) {
3905
+ try {
3906
+ if (arg instanceof Error) {
3907
+ return `${arg.name}: ${arg.message}`;
3908
+ }
3909
+ if (typeof arg === "object" && arg !== null) {
3910
+ const json = JSON.stringify(arg);
3911
+ return json.length > 200 ? `${json.slice(0, 200)}\u2026` : json;
3912
+ }
3913
+ const s = String(arg);
3914
+ return s.length > 200 ? `${s.slice(0, 200)}\u2026` : s;
3915
+ } catch {
3916
+ return `[${typeof arg}]`;
3917
+ }
3918
+ }
3465
3919
 
3466
3920
  export { Mushi, MushiWidget, createConsoleCapture, createElementSelector, createNetworkCapture, createPerformanceCapture, createProactiveManager, createScreenshotCapture, createTimelineCapture, getAvailableLocales, getLocale, setupProactiveTriggers };
3467
3921
  //# sourceMappingURL=index.js.map