@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.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
|
|
2470
|
-
if (
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
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
|
-
|
|
2476
|
-
|
|
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
|
-
|
|
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
|
|
2483
|
-
const
|
|
2634
|
+
function tagSentryScope(reportId, options = {}) {
|
|
2635
|
+
const sentry = getSentryGlobal();
|
|
2636
|
+
if (!sentry) return;
|
|
2484
2637
|
try {
|
|
2485
|
-
const
|
|
2486
|
-
if (
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
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
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
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
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
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.
|
|
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
|