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