@mushi-mushi/web 0.8.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 +92 -0
- package/SECURITY.md +167 -4
- package/dist/index.cjs +724 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +725 -29
- 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
|
|
|
@@ -2050,6 +2050,17 @@ function createElementSelector() {
|
|
|
2050
2050
|
let active = false;
|
|
2051
2051
|
let overlay = null;
|
|
2052
2052
|
let resolvePromise = null;
|
|
2053
|
+
function findNearestTestid(el) {
|
|
2054
|
+
let cur = el;
|
|
2055
|
+
let hops = 0;
|
|
2056
|
+
while (cur && hops < 20) {
|
|
2057
|
+
const tid = cur.getAttribute?.("data-testid");
|
|
2058
|
+
if (tid) return tid;
|
|
2059
|
+
cur = cur.parentElement;
|
|
2060
|
+
hops++;
|
|
2061
|
+
}
|
|
2062
|
+
return null;
|
|
2063
|
+
}
|
|
2053
2064
|
function getXPath(el) {
|
|
2054
2065
|
const parts = [];
|
|
2055
2066
|
let current = el;
|
|
@@ -2079,7 +2090,13 @@ function createElementSelector() {
|
|
|
2079
2090
|
y: Math.round(rect.y),
|
|
2080
2091
|
width: Math.round(rect.width),
|
|
2081
2092
|
height: Math.round(rect.height)
|
|
2082
|
-
}
|
|
2093
|
+
},
|
|
2094
|
+
// v2 (whitepaper §4.7): the closest ancestor's `data-testid` lets the
|
|
2095
|
+
// server map this report → an Action node in the inventory graph
|
|
2096
|
+
// without a fuzzy NLP guess. We walk to the body so a deeply nested
|
|
2097
|
+
// span inside a button-with-testid still resolves correctly.
|
|
2098
|
+
nearestTestid: findNearestTestid(el) || void 0,
|
|
2099
|
+
route: typeof window !== "undefined" ? window.location.pathname : void 0
|
|
2083
2100
|
};
|
|
2084
2101
|
}
|
|
2085
2102
|
function createOverlay() {
|
|
@@ -2258,45 +2275,402 @@ function textSnippet(el) {
|
|
|
2258
2275
|
return text ? text.slice(0, 80) : void 0;
|
|
2259
2276
|
}
|
|
2260
2277
|
|
|
2278
|
+
// src/capture/discovery.ts
|
|
2279
|
+
var DEFAULT_THROTTLE_MS = 6e4;
|
|
2280
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
2281
|
+
var HEX24_RE = /^[0-9a-f]{20,}$/i;
|
|
2282
|
+
var NUMERIC_RE = /^\d+$/;
|
|
2283
|
+
var SLUG_HASHY_RE = /^[a-z0-9]{16,}$/i;
|
|
2284
|
+
function normalizeSegment(seg) {
|
|
2285
|
+
if (seg.length === 0) return seg;
|
|
2286
|
+
if (UUID_RE.test(seg)) return "[id]";
|
|
2287
|
+
if (HEX24_RE.test(seg)) return "[id]";
|
|
2288
|
+
if (NUMERIC_RE.test(seg)) return "[id]";
|
|
2289
|
+
if (SLUG_HASHY_RE.test(seg) && /\d/.test(seg)) return "[id]";
|
|
2290
|
+
return seg;
|
|
2291
|
+
}
|
|
2292
|
+
function normalizeRoute(pathname, templates) {
|
|
2293
|
+
const clean = pathname.length > 1 && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
2294
|
+
if (templates?.length) {
|
|
2295
|
+
const matched = matchTemplate(clean, templates);
|
|
2296
|
+
if (matched) return matched;
|
|
2297
|
+
}
|
|
2298
|
+
return "/" + clean.split("/").filter((s) => s.length > 0).map(normalizeSegment).join("/");
|
|
2299
|
+
}
|
|
2300
|
+
function matchTemplate(pathname, templates) {
|
|
2301
|
+
const segs = pathname.split("/").filter((s) => s.length > 0);
|
|
2302
|
+
const sorted = [...templates].sort(
|
|
2303
|
+
(a, b) => b.split("/").length - a.split("/").length
|
|
2304
|
+
);
|
|
2305
|
+
for (const tpl of sorted) {
|
|
2306
|
+
const tplSegs = tpl.split("/").filter((s) => s.length > 0);
|
|
2307
|
+
if (tplSegs.length !== segs.length) continue;
|
|
2308
|
+
let ok = true;
|
|
2309
|
+
for (let i = 0; i < tplSegs.length; i++) {
|
|
2310
|
+
const t = tplSegs[i];
|
|
2311
|
+
const s = segs[i];
|
|
2312
|
+
if (t.startsWith("[") && t.endsWith("]")) continue;
|
|
2313
|
+
if (t.startsWith(":")) continue;
|
|
2314
|
+
if (t === s) continue;
|
|
2315
|
+
ok = false;
|
|
2316
|
+
break;
|
|
2317
|
+
}
|
|
2318
|
+
if (ok) return "/" + tplSegs.join("/");
|
|
2319
|
+
}
|
|
2320
|
+
return null;
|
|
2321
|
+
}
|
|
2322
|
+
function readTestids() {
|
|
2323
|
+
if (typeof document === "undefined") return [];
|
|
2324
|
+
const out = /* @__PURE__ */ new Set();
|
|
2325
|
+
const els = document.querySelectorAll("[data-testid]");
|
|
2326
|
+
for (const el of Array.from(els)) {
|
|
2327
|
+
const v = el.getAttribute("data-testid");
|
|
2328
|
+
if (v && v.length > 0 && v.length < 120) out.add(v);
|
|
2329
|
+
}
|
|
2330
|
+
return Array.from(out).sort();
|
|
2331
|
+
}
|
|
2332
|
+
function readQueryParamKeys() {
|
|
2333
|
+
if (typeof window === "undefined") return [];
|
|
2334
|
+
try {
|
|
2335
|
+
const params = new URLSearchParams(window.location.search);
|
|
2336
|
+
const out = /* @__PURE__ */ new Set();
|
|
2337
|
+
params.forEach((_, key) => out.add(key));
|
|
2338
|
+
return Array.from(out).sort();
|
|
2339
|
+
} catch {
|
|
2340
|
+
return [];
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
function readDomSummary() {
|
|
2344
|
+
if (typeof document === "undefined") return null;
|
|
2345
|
+
const trim = (s) => (s ?? "").replace(/\s+/g, " ").trim().slice(0, 200);
|
|
2346
|
+
const h1 = trim(document.querySelector("h1")?.textContent);
|
|
2347
|
+
if (h1) return h1;
|
|
2348
|
+
const title = trim(document.title);
|
|
2349
|
+
if (title) return title;
|
|
2350
|
+
const main = trim(document.querySelector("main")?.textContent);
|
|
2351
|
+
return main || null;
|
|
2352
|
+
}
|
|
2353
|
+
async function hashUserId(input) {
|
|
2354
|
+
if (!input || typeof crypto === "undefined" || !crypto.subtle) return null;
|
|
2355
|
+
try {
|
|
2356
|
+
const data = new TextEncoder().encode(input);
|
|
2357
|
+
const buf = await crypto.subtle.digest("SHA-256", data);
|
|
2358
|
+
const bytes = new Uint8Array(buf);
|
|
2359
|
+
let hex = "";
|
|
2360
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
2361
|
+
hex += bytes[i].toString(16).padStart(2, "0");
|
|
2362
|
+
}
|
|
2363
|
+
return hex;
|
|
2364
|
+
} catch {
|
|
2365
|
+
return null;
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
function createDiscoveryCapture(opts) {
|
|
2369
|
+
const {
|
|
2370
|
+
config,
|
|
2371
|
+
getRecentNetworkPaths,
|
|
2372
|
+
getUserId,
|
|
2373
|
+
getSessionId: getSessionId2,
|
|
2374
|
+
onEvent
|
|
2375
|
+
} = opts;
|
|
2376
|
+
const throttleMs = config.throttleMs ?? DEFAULT_THROTTLE_MS;
|
|
2377
|
+
const captureSummary = config.captureDomSummary !== false;
|
|
2378
|
+
const userIdSource = config.userIdSource ?? "auto";
|
|
2379
|
+
const lastEmittedAt = /* @__PURE__ */ new Map();
|
|
2380
|
+
let lastPath = null;
|
|
2381
|
+
let pendingTimer = null;
|
|
2382
|
+
async function emitForCurrent() {
|
|
2383
|
+
if (typeof window === "undefined") return;
|
|
2384
|
+
const route = normalizeRoute(window.location.pathname, config.routeTemplates);
|
|
2385
|
+
const now = Date.now();
|
|
2386
|
+
const last = lastEmittedAt.get(route) ?? 0;
|
|
2387
|
+
if (now - last < throttleMs) return;
|
|
2388
|
+
lastEmittedAt.set(route, now);
|
|
2389
|
+
let userIdInput = null;
|
|
2390
|
+
if (userIdSource === "auto") {
|
|
2391
|
+
userIdInput = getUserId() ?? getSessionId2();
|
|
2392
|
+
} else if (userIdSource === "session-only") {
|
|
2393
|
+
userIdInput = getSessionId2();
|
|
2394
|
+
}
|
|
2395
|
+
const event = {
|
|
2396
|
+
route,
|
|
2397
|
+
page_title: typeof document !== "undefined" ? (document.title || "").slice(0, 300) || null : null,
|
|
2398
|
+
dom_summary: captureSummary ? readDomSummary() : null,
|
|
2399
|
+
testids: readTestids(),
|
|
2400
|
+
network_paths: getRecentNetworkPaths().slice(-50),
|
|
2401
|
+
query_param_keys: readQueryParamKeys(),
|
|
2402
|
+
user_id_hash: await hashUserId(userIdInput),
|
|
2403
|
+
observed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2404
|
+
};
|
|
2405
|
+
onEvent(event);
|
|
2406
|
+
}
|
|
2407
|
+
function scheduleEmit() {
|
|
2408
|
+
if (pendingTimer) return;
|
|
2409
|
+
pendingTimer = setTimeout(() => {
|
|
2410
|
+
pendingTimer = null;
|
|
2411
|
+
void emitForCurrent();
|
|
2412
|
+
}, 100);
|
|
2413
|
+
}
|
|
2414
|
+
function onMaybeNavigation() {
|
|
2415
|
+
if (typeof window === "undefined") return;
|
|
2416
|
+
const path = window.location.pathname + window.location.search;
|
|
2417
|
+
if (path === lastPath) return;
|
|
2418
|
+
lastPath = path;
|
|
2419
|
+
scheduleEmit();
|
|
2420
|
+
}
|
|
2421
|
+
if (typeof window === "undefined") {
|
|
2422
|
+
return {
|
|
2423
|
+
destroy: () => void 0,
|
|
2424
|
+
flushNow: () => void 0
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
const originalPush = window.history.pushState.bind(window.history);
|
|
2428
|
+
const originalReplace = window.history.replaceState.bind(window.history);
|
|
2429
|
+
const patchedPush = function patched(...args) {
|
|
2430
|
+
const out = originalPush(...args);
|
|
2431
|
+
onMaybeNavigation();
|
|
2432
|
+
return out;
|
|
2433
|
+
};
|
|
2434
|
+
const patchedReplace = function patched(...args) {
|
|
2435
|
+
const out = originalReplace(...args);
|
|
2436
|
+
onMaybeNavigation();
|
|
2437
|
+
return out;
|
|
2438
|
+
};
|
|
2439
|
+
window.history.pushState = patchedPush;
|
|
2440
|
+
window.history.replaceState = patchedReplace;
|
|
2441
|
+
const onPop = () => onMaybeNavigation();
|
|
2442
|
+
window.addEventListener("popstate", onPop);
|
|
2443
|
+
scheduleEmit();
|
|
2444
|
+
return {
|
|
2445
|
+
destroy() {
|
|
2446
|
+
window.removeEventListener("popstate", onPop);
|
|
2447
|
+
if (window.history.pushState === patchedPush) {
|
|
2448
|
+
window.history.pushState = originalPush;
|
|
2449
|
+
}
|
|
2450
|
+
if (window.history.replaceState === patchedReplace) {
|
|
2451
|
+
window.history.replaceState = originalReplace;
|
|
2452
|
+
}
|
|
2453
|
+
if (pendingTimer) {
|
|
2454
|
+
clearTimeout(pendingTimer);
|
|
2455
|
+
pendingTimer = null;
|
|
2456
|
+
}
|
|
2457
|
+
lastEmittedAt.clear();
|
|
2458
|
+
},
|
|
2459
|
+
flushNow() {
|
|
2460
|
+
lastEmittedAt.clear();
|
|
2461
|
+
void emitForCurrent();
|
|
2462
|
+
}
|
|
2463
|
+
};
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2261
2466
|
// src/sentry.ts
|
|
2262
2467
|
function getSentryGlobal() {
|
|
2263
2468
|
try {
|
|
2264
|
-
const
|
|
2265
|
-
if (
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
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;
|
|
2513
|
+
}
|
|
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?.();
|
|
2524
|
+
}
|
|
2525
|
+
} catch {
|
|
2526
|
+
}
|
|
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 {
|
|
2269
2560
|
}
|
|
2270
|
-
|
|
2271
|
-
|
|
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?.();
|
|
2272
2607
|
}
|
|
2273
2608
|
} catch {
|
|
2274
2609
|
}
|
|
2275
|
-
|
|
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;
|
|
2276
2633
|
}
|
|
2277
|
-
function
|
|
2278
|
-
const
|
|
2634
|
+
function tagSentryScope(reportId, options = {}) {
|
|
2635
|
+
const sentry = getSentryGlobal();
|
|
2636
|
+
if (!sentry) return;
|
|
2279
2637
|
try {
|
|
2280
|
-
const
|
|
2281
|
-
if (
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
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
|
+
});
|
|
2285
2649
|
}
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
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
|
+
});
|
|
2291
2658
|
}
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
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
|
+
});
|
|
2296
2671
|
}
|
|
2297
2672
|
} catch {
|
|
2298
2673
|
}
|
|
2299
|
-
return context;
|
|
2300
2674
|
}
|
|
2301
2675
|
|
|
2302
2676
|
// src/proactive-triggers.ts
|
|
@@ -2473,7 +2847,7 @@ function createProactiveManager(config = {}) {
|
|
|
2473
2847
|
|
|
2474
2848
|
// src/version.ts
|
|
2475
2849
|
var MUSHI_SDK_PACKAGE = "@mushi-mushi/web";
|
|
2476
|
-
var MUSHI_SDK_VERSION = "0.
|
|
2850
|
+
var MUSHI_SDK_VERSION = "1.0.0" ;
|
|
2477
2851
|
|
|
2478
2852
|
// src/mushi.ts
|
|
2479
2853
|
var instance = null;
|
|
@@ -2521,11 +2895,36 @@ function createInstance(config) {
|
|
|
2521
2895
|
const offlineQueue = createOfflineQueue(bootstrapConfig.offline);
|
|
2522
2896
|
const rateLimiter = createRateLimiter({ maxBurst: 10, refillRate: 1, refillIntervalMs: 5e3 });
|
|
2523
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
|
+
}
|
|
2524
2922
|
let consoleCap = null;
|
|
2525
2923
|
let networkCap = null;
|
|
2526
2924
|
let perfCap = null;
|
|
2527
2925
|
let screenshotCap = null;
|
|
2528
2926
|
let elementSelector = null;
|
|
2927
|
+
let discoveryCap = null;
|
|
2529
2928
|
const timelineCap = createTimelineCapture();
|
|
2530
2929
|
let widget;
|
|
2531
2930
|
function syncCaptureModules() {
|
|
@@ -2574,6 +2973,40 @@ function createInstance(config) {
|
|
|
2574
2973
|
elementSelector = null;
|
|
2575
2974
|
pendingElement = null;
|
|
2576
2975
|
}
|
|
2976
|
+
const discoveryRaw = activeConfig.capture?.discoverInventory;
|
|
2977
|
+
const discoveryConfig = discoveryRaw === true ? {} : discoveryRaw && typeof discoveryRaw === "object" ? discoveryRaw : null;
|
|
2978
|
+
const discoveryEnabled = discoveryConfig != null && discoveryConfig.enabled !== false;
|
|
2979
|
+
if (discoveryEnabled) {
|
|
2980
|
+
discoveryCap?.destroy();
|
|
2981
|
+
discoveryCap = createDiscoveryCapture({
|
|
2982
|
+
config: discoveryConfig,
|
|
2983
|
+
getRecentNetworkPaths: () => {
|
|
2984
|
+
if (!networkCap) return [];
|
|
2985
|
+
return networkCap.getEntries().map((e) => {
|
|
2986
|
+
try {
|
|
2987
|
+
const u = new URL(e.url, typeof window !== "undefined" ? window.location.href : "http://localhost");
|
|
2988
|
+
if (u.host && typeof window !== "undefined" && u.host !== window.location.host) return null;
|
|
2989
|
+
return u.pathname;
|
|
2990
|
+
} catch {
|
|
2991
|
+
return null;
|
|
2992
|
+
}
|
|
2993
|
+
}).filter((p) => p != null && p.length > 0 && p.length < 200);
|
|
2994
|
+
},
|
|
2995
|
+
getUserId: () => userInfo?.id ?? null,
|
|
2996
|
+
getSessionId,
|
|
2997
|
+
onEvent: (event) => {
|
|
2998
|
+
void apiClient.postDiscoveryEvent({
|
|
2999
|
+
...event,
|
|
3000
|
+
sdk_version: MUSHI_SDK_VERSION
|
|
3001
|
+
}).catch((err) => {
|
|
3002
|
+
log.debug("discovery emit failed", { err: String(err) });
|
|
3003
|
+
});
|
|
3004
|
+
}
|
|
3005
|
+
});
|
|
3006
|
+
} else {
|
|
3007
|
+
discoveryCap?.destroy();
|
|
3008
|
+
discoveryCap = null;
|
|
3009
|
+
}
|
|
2577
3010
|
}
|
|
2578
3011
|
const listeners = /* @__PURE__ */ new Map();
|
|
2579
3012
|
function emit(type, data) {
|
|
@@ -2585,6 +3018,16 @@ function createInstance(config) {
|
|
|
2585
3018
|
let runtimeConfigLoaded = false;
|
|
2586
3019
|
let userInfo = null;
|
|
2587
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);
|
|
2588
3031
|
widget = new MushiWidget(bootstrapConfig.widget, {
|
|
2589
3032
|
onSubmit: async ({ category, description, intent }) => {
|
|
2590
3033
|
log.info("Report submitted", { category, intent });
|
|
@@ -2794,6 +3237,15 @@ function createInstance(config) {
|
|
|
2794
3237
|
const fingerprintHash = await getDeviceFingerprintHash().catch(() => null);
|
|
2795
3238
|
const consoleLogs = activeConfig.capture?.console === false ? void 0 : consoleCap?.getEntries();
|
|
2796
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;
|
|
2797
3249
|
const report = {
|
|
2798
3250
|
id: crypto.randomUUID?.() ?? `mushi_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
2799
3251
|
projectId: config.projectId,
|
|
@@ -2819,10 +3271,24 @@ function createInstance(config) {
|
|
|
2819
3271
|
sdkPackage: MUSHI_SDK_PACKAGE,
|
|
2820
3272
|
sdkVersion: MUSHI_SDK_VERSION,
|
|
2821
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 } : {},
|
|
2822
3282
|
sentryEventId: sentryCtx?.eventId,
|
|
2823
3283
|
sentryReplayId: sentryCtx?.replayId,
|
|
2824
3284
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2825
3285
|
};
|
|
3286
|
+
breadcrumbs.add({
|
|
3287
|
+
category: "lifecycle",
|
|
3288
|
+
level: "info",
|
|
3289
|
+
message: `Mushi report submitting (${category})`,
|
|
3290
|
+
data: { reportId: report.id, category }
|
|
3291
|
+
});
|
|
2826
3292
|
if (config.integrations?.custom) {
|
|
2827
3293
|
const builder = {
|
|
2828
3294
|
addMetadata(key, value) {
|
|
@@ -2848,10 +3314,26 @@ function createInstance(config) {
|
|
|
2848
3314
|
if (result.ok) {
|
|
2849
3315
|
log.info("Report sent", { reportId: result.data?.reportId });
|
|
2850
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
|
+
}
|
|
2851
3328
|
} else {
|
|
2852
3329
|
log.warn("Report failed, queuing for retry", { reportId: report.id, error: result.error });
|
|
2853
3330
|
await offlineQueue.enqueue(report);
|
|
2854
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
|
+
});
|
|
2855
3337
|
}
|
|
2856
3338
|
pendingScreenshot = null;
|
|
2857
3339
|
pendingElement = null;
|
|
@@ -2920,7 +3402,12 @@ function createInstance(config) {
|
|
|
2920
3402
|
perfCap?.destroy();
|
|
2921
3403
|
elementSelector?.deactivate();
|
|
2922
3404
|
timelineCap.destroy();
|
|
3405
|
+
discoveryCap?.destroy();
|
|
3406
|
+
discoveryCap = null;
|
|
2923
3407
|
offlineQueue.stopAutoSync();
|
|
3408
|
+
detachAutoBreadcrumbs?.();
|
|
3409
|
+
detachAutoBreadcrumbs = null;
|
|
3410
|
+
breadcrumbs.clear();
|
|
2924
3411
|
listeners.clear();
|
|
2925
3412
|
instance = null;
|
|
2926
3413
|
log.debug("Destroyed");
|
|
@@ -2935,6 +3422,16 @@ function createInstance(config) {
|
|
|
2935
3422
|
}
|
|
2936
3423
|
const description = piiScrubber.scrub(preFilter.truncate(input.description));
|
|
2937
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;
|
|
2938
3435
|
const report = {
|
|
2939
3436
|
id: crypto.randomUUID?.() ?? `mushi_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
2940
3437
|
projectId: config.projectId,
|
|
@@ -2945,16 +3442,20 @@ function createInstance(config) {
|
|
|
2945
3442
|
metadata: {
|
|
2946
3443
|
...input.metadata ?? {},
|
|
2947
3444
|
...userInfo ? { user: userInfo } : {},
|
|
2948
|
-
...input.tags ? { tags: input.tags } : {},
|
|
2949
3445
|
...input.error ? { error: input.error } : {},
|
|
2950
3446
|
...input.severity ? { severity: input.severity } : {},
|
|
2951
3447
|
...input.component ? { component: input.component } : {},
|
|
2952
3448
|
...input.source ? { source: input.source } : { source: "captureEvent" }
|
|
2953
3449
|
},
|
|
3450
|
+
...captureBreadcrumbs.length > 0 ? { breadcrumbs: captureBreadcrumbs } : {},
|
|
3451
|
+
...mergedTags && Object.keys(mergedTags).length > 0 ? { tags: mergedTags } : {},
|
|
3452
|
+
...sentryCtxScrubbed ? { sentryContext: sentryCtxScrubbed } : {},
|
|
2954
3453
|
sessionId: getSessionId(),
|
|
2955
3454
|
reporterToken: getReporterToken(),
|
|
2956
3455
|
sdkPackage: MUSHI_SDK_PACKAGE,
|
|
2957
3456
|
sdkVersion: MUSHI_SDK_VERSION,
|
|
3457
|
+
sentryEventId: sentryCtx?.eventId,
|
|
3458
|
+
sentryReplayId: sentryCtx?.replayId,
|
|
2958
3459
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2959
3460
|
};
|
|
2960
3461
|
emit("report:submitted", { reportId: report.id });
|
|
@@ -2966,12 +3467,43 @@ function createInstance(config) {
|
|
|
2966
3467
|
const res = await apiClient.submitReport(report);
|
|
2967
3468
|
if (res.ok) {
|
|
2968
3469
|
emit("report:sent", { reportId: res.data?.reportId });
|
|
3470
|
+
try {
|
|
3471
|
+
if (config.sentry && res.data?.reportId) tagSentryScope(res.data.reportId);
|
|
3472
|
+
} catch {
|
|
3473
|
+
}
|
|
2969
3474
|
return res.data?.reportId ?? null;
|
|
2970
3475
|
}
|
|
2971
3476
|
await offlineQueue.enqueue(report);
|
|
2972
3477
|
emit("report:failed", { reportId: report.id, error: res.error });
|
|
2973
3478
|
return null;
|
|
2974
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
|
+
},
|
|
2975
3507
|
identify(userId, traits) {
|
|
2976
3508
|
userInfo = { id: userId, ...traits?.email ? { email: traits.email } : {}, ...traits?.name ? { name: traits.name } : {} };
|
|
2977
3509
|
if (traits) {
|
|
@@ -2979,6 +3511,36 @@ function createInstance(config) {
|
|
|
2979
3511
|
if (k !== "email" && k !== "name") customMetadata[`user.${k}`] = v;
|
|
2980
3512
|
}
|
|
2981
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];
|
|
2982
3544
|
}
|
|
2983
3545
|
};
|
|
2984
3546
|
return sdk;
|
|
@@ -3216,10 +3778,144 @@ function createNoopInstance() {
|
|
|
3216
3778
|
instance = null;
|
|
3217
3779
|
},
|
|
3218
3780
|
captureEvent: async () => null,
|
|
3781
|
+
captureException: async () => null,
|
|
3219
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
|
+
}
|
|
3220
3901
|
}
|
|
3221
3902
|
};
|
|
3222
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
|
+
}
|
|
3223
3919
|
|
|
3224
3920
|
export { Mushi, MushiWidget, createConsoleCapture, createElementSelector, createNetworkCapture, createPerformanceCapture, createProactiveManager, createScreenshotCapture, createTimelineCapture, getAvailableLocales, getLocale, setupProactiveTriggers };
|
|
3225
3921
|
//# sourceMappingURL=index.js.map
|