@mushi-mushi/web 0.1.0 → 0.2.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 +30 -1
- package/dist/index.cjs +249 -145
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +249 -145
- package/dist/index.js.map +1 -1
- package/package.json +78 -78
package/dist/index.js
CHANGED
|
@@ -549,15 +549,21 @@ var MushiWidget = class {
|
|
|
549
549
|
document.body.appendChild(this.host);
|
|
550
550
|
this.render();
|
|
551
551
|
}
|
|
552
|
-
open() {
|
|
552
|
+
open(options) {
|
|
553
553
|
if (this.isOpen) return;
|
|
554
554
|
this.isOpen = true;
|
|
555
|
-
this.step = "category";
|
|
556
|
-
this.selectedCategory = null;
|
|
557
|
-
this.selectedIntent = null;
|
|
558
555
|
this.screenshotAttached = false;
|
|
559
556
|
this.elementSelected = false;
|
|
560
557
|
this.submitting = false;
|
|
558
|
+
if (options?.category) {
|
|
559
|
+
this.selectedCategory = options.category;
|
|
560
|
+
this.selectedIntent = null;
|
|
561
|
+
this.step = "intent";
|
|
562
|
+
} else {
|
|
563
|
+
this.selectedCategory = null;
|
|
564
|
+
this.selectedIntent = null;
|
|
565
|
+
this.step = "category";
|
|
566
|
+
}
|
|
561
567
|
this.render();
|
|
562
568
|
this.callbacks.onOpen();
|
|
563
569
|
}
|
|
@@ -773,9 +779,10 @@ var MushiWidget = class {
|
|
|
773
779
|
const textarea = panel.querySelector(".mushi-textarea");
|
|
774
780
|
const description = textarea?.value?.trim() ?? "";
|
|
775
781
|
const errorEl = panel.querySelector(".mushi-error");
|
|
776
|
-
|
|
782
|
+
const MIN_DESCRIPTION_LENGTH = 20;
|
|
783
|
+
if (description.length < MIN_DESCRIPTION_LENGTH) {
|
|
777
784
|
if (errorEl) {
|
|
778
|
-
errorEl.textContent = t.widget.error
|
|
785
|
+
errorEl.textContent = `${t.widget.error} (${description.length}/${MIN_DESCRIPTION_LENGTH})`;
|
|
779
786
|
errorEl.style.display = "block";
|
|
780
787
|
}
|
|
781
788
|
return;
|
|
@@ -1197,6 +1204,167 @@ function captureSentryContext(_config) {
|
|
|
1197
1204
|
return context;
|
|
1198
1205
|
}
|
|
1199
1206
|
|
|
1207
|
+
// src/proactive-triggers.ts
|
|
1208
|
+
function setupProactiveTriggers(callbacks, config = {}) {
|
|
1209
|
+
const cleanups = [];
|
|
1210
|
+
if (config.rageClick !== false) {
|
|
1211
|
+
let handleClick2 = function(e) {
|
|
1212
|
+
const now = Date.now();
|
|
1213
|
+
if (e.target === lastClickTarget) {
|
|
1214
|
+
clickTimes.push(now);
|
|
1215
|
+
clickTimes = clickTimes.filter((t) => now - t < 500);
|
|
1216
|
+
if (clickTimes.length >= 3) {
|
|
1217
|
+
const el = e.target;
|
|
1218
|
+
callbacks.onTrigger("rage_click", {
|
|
1219
|
+
element: el.tagName,
|
|
1220
|
+
id: el.id,
|
|
1221
|
+
text: el.textContent?.slice(0, 50)
|
|
1222
|
+
});
|
|
1223
|
+
clickTimes = [];
|
|
1224
|
+
}
|
|
1225
|
+
} else {
|
|
1226
|
+
lastClickTarget = e.target;
|
|
1227
|
+
clickTimes = [now];
|
|
1228
|
+
}
|
|
1229
|
+
};
|
|
1230
|
+
let clickTimes = [];
|
|
1231
|
+
let lastClickTarget = null;
|
|
1232
|
+
document.addEventListener("click", handleClick2, true);
|
|
1233
|
+
cleanups.push(() => document.removeEventListener("click", handleClick2, true));
|
|
1234
|
+
}
|
|
1235
|
+
if (config.longTask !== false && typeof PerformanceObserver !== "undefined") {
|
|
1236
|
+
try {
|
|
1237
|
+
const observer = new PerformanceObserver((list) => {
|
|
1238
|
+
for (const entry of list.getEntries()) {
|
|
1239
|
+
if (entry.duration > 5e3) {
|
|
1240
|
+
callbacks.onTrigger("long_task", {
|
|
1241
|
+
duration: Math.round(entry.duration),
|
|
1242
|
+
startTime: Math.round(entry.startTime)
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
observer.observe({ entryTypes: ["longtask"] });
|
|
1248
|
+
cleanups.push(() => observer.disconnect());
|
|
1249
|
+
} catch {
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
if (config.apiCascade !== false) {
|
|
1253
|
+
const failedRequests = [];
|
|
1254
|
+
const origFetch = globalThis.fetch;
|
|
1255
|
+
globalThis.fetch = async function(...args) {
|
|
1256
|
+
try {
|
|
1257
|
+
const res = await origFetch.apply(this, args);
|
|
1258
|
+
if (!res.ok && res.status >= 400) {
|
|
1259
|
+
const now = Date.now();
|
|
1260
|
+
failedRequests.push(now);
|
|
1261
|
+
const recentFailures = failedRequests.filter((t) => now - t < 1e4);
|
|
1262
|
+
if (recentFailures.length >= 3) {
|
|
1263
|
+
callbacks.onTrigger("api_cascade", {
|
|
1264
|
+
failureCount: recentFailures.length,
|
|
1265
|
+
windowMs: 1e4
|
|
1266
|
+
});
|
|
1267
|
+
failedRequests.length = 0;
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
return res;
|
|
1271
|
+
} catch (err) {
|
|
1272
|
+
const now = Date.now();
|
|
1273
|
+
failedRequests.push(now);
|
|
1274
|
+
const recentFailures = failedRequests.filter((t) => now - t < 1e4);
|
|
1275
|
+
if (recentFailures.length >= 3) {
|
|
1276
|
+
callbacks.onTrigger("api_cascade", {
|
|
1277
|
+
failureCount: recentFailures.length,
|
|
1278
|
+
windowMs: 1e4
|
|
1279
|
+
});
|
|
1280
|
+
failedRequests.length = 0;
|
|
1281
|
+
}
|
|
1282
|
+
throw err;
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
cleanups.push(() => {
|
|
1286
|
+
globalThis.fetch = origFetch;
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
if (config.errorBoundary) {
|
|
1290
|
+
let handleError2 = function(event) {
|
|
1291
|
+
callbacks.onTrigger("error_boundary", {
|
|
1292
|
+
message: event.message,
|
|
1293
|
+
filename: event.filename,
|
|
1294
|
+
lineno: event.lineno,
|
|
1295
|
+
colno: event.colno
|
|
1296
|
+
});
|
|
1297
|
+
}, handleUnhandledRejection2 = function(event) {
|
|
1298
|
+
callbacks.onTrigger("error_boundary", {
|
|
1299
|
+
message: event.reason instanceof Error ? event.reason.message : String(event.reason),
|
|
1300
|
+
type: "unhandled_rejection"
|
|
1301
|
+
});
|
|
1302
|
+
};
|
|
1303
|
+
window.addEventListener("error", handleError2);
|
|
1304
|
+
window.addEventListener("unhandledrejection", handleUnhandledRejection2);
|
|
1305
|
+
cleanups.push(() => {
|
|
1306
|
+
window.removeEventListener("error", handleError2);
|
|
1307
|
+
window.removeEventListener("unhandledrejection", handleUnhandledRejection2);
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
return {
|
|
1311
|
+
destroy() {
|
|
1312
|
+
cleanups.forEach((fn) => fn());
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
// src/proactive-manager.ts
|
|
1318
|
+
var STORAGE_KEY_LAST_DISMISS = "mushi:lastDismiss";
|
|
1319
|
+
var STORAGE_KEY_CONSEC_DISMISS = "mushi:consecDismiss";
|
|
1320
|
+
function readStorage(key) {
|
|
1321
|
+
try {
|
|
1322
|
+
return localStorage.getItem(key);
|
|
1323
|
+
} catch {
|
|
1324
|
+
return null;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
function writeStorage(key, value) {
|
|
1328
|
+
try {
|
|
1329
|
+
localStorage.setItem(key, value);
|
|
1330
|
+
} catch {
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
function createProactiveManager(config = {}) {
|
|
1334
|
+
const maxPerSession = config.maxProactivePerSession ?? 2;
|
|
1335
|
+
const cooldownHours = config.dismissCooldownHours ?? 24;
|
|
1336
|
+
const suppressThreshold = config.suppressAfterDismissals ?? 3;
|
|
1337
|
+
let sessionPromptCount = 0;
|
|
1338
|
+
const sessionTriggerTypes = /* @__PURE__ */ new Set();
|
|
1339
|
+
function shouldShow(triggerType) {
|
|
1340
|
+
const consecDismissals = parseInt(readStorage(STORAGE_KEY_CONSEC_DISMISS) ?? "0", 10);
|
|
1341
|
+
if (consecDismissals >= suppressThreshold) return false;
|
|
1342
|
+
const lastDismiss = readStorage(STORAGE_KEY_LAST_DISMISS);
|
|
1343
|
+
if (lastDismiss) {
|
|
1344
|
+
const elapsed = Date.now() - parseInt(lastDismiss, 10);
|
|
1345
|
+
if (elapsed < cooldownHours * 60 * 60 * 1e3) return false;
|
|
1346
|
+
}
|
|
1347
|
+
if (sessionPromptCount >= maxPerSession) return false;
|
|
1348
|
+
if (sessionTriggerTypes.has(triggerType)) return false;
|
|
1349
|
+
sessionTriggerTypes.add(triggerType);
|
|
1350
|
+
sessionPromptCount++;
|
|
1351
|
+
return true;
|
|
1352
|
+
}
|
|
1353
|
+
function recordDismissal() {
|
|
1354
|
+
writeStorage(STORAGE_KEY_LAST_DISMISS, String(Date.now()));
|
|
1355
|
+
const current = parseInt(readStorage(STORAGE_KEY_CONSEC_DISMISS) ?? "0", 10);
|
|
1356
|
+
writeStorage(STORAGE_KEY_CONSEC_DISMISS, String(current + 1));
|
|
1357
|
+
}
|
|
1358
|
+
function recordSubmission() {
|
|
1359
|
+
writeStorage(STORAGE_KEY_CONSEC_DISMISS, "0");
|
|
1360
|
+
}
|
|
1361
|
+
function reset() {
|
|
1362
|
+
sessionPromptCount = 0;
|
|
1363
|
+
sessionTriggerTypes.clear();
|
|
1364
|
+
}
|
|
1365
|
+
return { shouldShow, recordDismissal, recordSubmission, reset };
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1200
1368
|
// src/mushi.ts
|
|
1201
1369
|
var instance = null;
|
|
1202
1370
|
var Mushi = class {
|
|
@@ -1232,7 +1400,7 @@ function createInstance(config) {
|
|
|
1232
1400
|
const apiClient = createApiClient({
|
|
1233
1401
|
projectId: config.projectId,
|
|
1234
1402
|
apiKey: config.apiKey,
|
|
1235
|
-
apiEndpoint: config.apiEndpoint
|
|
1403
|
+
...config.apiEndpoint ? { apiEndpoint: config.apiEndpoint } : {}
|
|
1236
1404
|
});
|
|
1237
1405
|
const preFilter = createPreFilter(config.preFilter);
|
|
1238
1406
|
const offlineQueue = createOfflineQueue(config.offline);
|
|
@@ -1251,9 +1419,11 @@ function createInstance(config) {
|
|
|
1251
1419
|
const customMetadata = {};
|
|
1252
1420
|
let pendingScreenshot = null;
|
|
1253
1421
|
let pendingElement = null;
|
|
1422
|
+
let pendingProactiveTrigger = null;
|
|
1254
1423
|
const widget = new MushiWidget(config.widget, {
|
|
1255
1424
|
onSubmit: async ({ category, description, intent }) => {
|
|
1256
1425
|
log.info("Report submitted", { category, intent });
|
|
1426
|
+
proactiveManager?.recordSubmission();
|
|
1257
1427
|
await submitReport(category, description, intent);
|
|
1258
1428
|
},
|
|
1259
1429
|
onOpen: () => {
|
|
@@ -1262,8 +1432,13 @@ function createInstance(config) {
|
|
|
1262
1432
|
},
|
|
1263
1433
|
onClose: () => {
|
|
1264
1434
|
log.debug("Widget closed");
|
|
1435
|
+
if (pendingProactiveTrigger) {
|
|
1436
|
+
proactiveManager?.recordDismissal();
|
|
1437
|
+
emit("proactive:dismissed", { type: pendingProactiveTrigger });
|
|
1438
|
+
}
|
|
1265
1439
|
pendingScreenshot = null;
|
|
1266
1440
|
pendingElement = null;
|
|
1441
|
+
pendingProactiveTrigger = null;
|
|
1267
1442
|
emit("widget:closed");
|
|
1268
1443
|
},
|
|
1269
1444
|
onScreenshotRequest: async () => {
|
|
@@ -1290,6 +1465,39 @@ function createInstance(config) {
|
|
|
1290
1465
|
widget.mount();
|
|
1291
1466
|
}
|
|
1292
1467
|
}
|
|
1468
|
+
let proactiveTriggers = null;
|
|
1469
|
+
let proactiveManager = null;
|
|
1470
|
+
const proactiveCfg = config.proactive;
|
|
1471
|
+
const hasAnyProactive = proactiveCfg && (proactiveCfg.rageClick !== false || proactiveCfg.longTask !== false || proactiveCfg.apiCascade !== false || proactiveCfg.errorBoundary === true);
|
|
1472
|
+
if (hasAnyProactive && typeof document !== "undefined") {
|
|
1473
|
+
proactiveManager = createProactiveManager(proactiveCfg?.cooldown);
|
|
1474
|
+
proactiveTriggers = setupProactiveTriggers(
|
|
1475
|
+
{
|
|
1476
|
+
onTrigger: (type, context) => {
|
|
1477
|
+
if (!proactiveManager.shouldShow(type)) {
|
|
1478
|
+
log.debug("Proactive trigger suppressed by fatigue prevention", { type });
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
log.info("Proactive trigger fired", { type, context });
|
|
1482
|
+
pendingProactiveTrigger = type;
|
|
1483
|
+
emit("proactive:triggered", { type, context });
|
|
1484
|
+
widget.open();
|
|
1485
|
+
}
|
|
1486
|
+
},
|
|
1487
|
+
{
|
|
1488
|
+
rageClick: proactiveCfg?.rageClick,
|
|
1489
|
+
longTask: proactiveCfg?.longTask,
|
|
1490
|
+
apiCascade: proactiveCfg?.apiCascade,
|
|
1491
|
+
errorBoundary: proactiveCfg?.errorBoundary
|
|
1492
|
+
}
|
|
1493
|
+
);
|
|
1494
|
+
log.debug("Proactive triggers enabled", {
|
|
1495
|
+
rageClick: proactiveCfg?.rageClick !== false,
|
|
1496
|
+
longTask: proactiveCfg?.longTask !== false,
|
|
1497
|
+
apiCascade: proactiveCfg?.apiCascade !== false,
|
|
1498
|
+
errorBoundary: proactiveCfg?.errorBoundary === true
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1293
1501
|
offlineQueue.startAutoSync(apiClient);
|
|
1294
1502
|
offlineQueue.flush(apiClient).then((result) => {
|
|
1295
1503
|
if (result.sent > 0) log.info("Synced offline reports", { sent: result.sent });
|
|
@@ -1301,6 +1509,34 @@ function createInstance(config) {
|
|
|
1301
1509
|
log.info("Report blocked by pre-filter", { reason: filterResult.reason });
|
|
1302
1510
|
return;
|
|
1303
1511
|
}
|
|
1512
|
+
const wasm = config.preFilter?.wasmClassifier;
|
|
1513
|
+
if (wasm) {
|
|
1514
|
+
try {
|
|
1515
|
+
const verdict = await wasm.classify({
|
|
1516
|
+
description,
|
|
1517
|
+
category,
|
|
1518
|
+
url: typeof location !== "undefined" ? location.href : void 0,
|
|
1519
|
+
hasScreenshot: pendingScreenshot !== null,
|
|
1520
|
+
hasSelectedElement: pendingElement !== null,
|
|
1521
|
+
hasNetworkErrors: networkCap?.getEntries()?.some((e) => e.status >= 400 || !!e.error) ?? false,
|
|
1522
|
+
hasConsoleErrors: consoleCap?.getEntries()?.some((e) => e.level === "error") ?? false,
|
|
1523
|
+
proactiveTrigger: pendingProactiveTrigger ?? void 0
|
|
1524
|
+
});
|
|
1525
|
+
if (verdict.verdict === "block") {
|
|
1526
|
+
log.info("Report blocked by on-device classifier", {
|
|
1527
|
+
modelId: verdict.modelId,
|
|
1528
|
+
confidence: verdict.confidence,
|
|
1529
|
+
reason: verdict.reason
|
|
1530
|
+
});
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
log.debug("On-device classifier verdict", { ...verdict });
|
|
1534
|
+
} catch (err) {
|
|
1535
|
+
log.warn("On-device classifier threw \u2014 falling through to server", {
|
|
1536
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1304
1540
|
if (!rateLimiter.tryConsume()) {
|
|
1305
1541
|
log.warn("Report throttled \u2014 rate limit exceeded");
|
|
1306
1542
|
return;
|
|
@@ -1327,6 +1563,7 @@ function createInstance(config) {
|
|
|
1327
1563
|
sessionId: getSessionId(),
|
|
1328
1564
|
reporterToken: getReporterToken(),
|
|
1329
1565
|
appVersion: config.integrations?.vercel?.analyticsId,
|
|
1566
|
+
proactiveTrigger: pendingProactiveTrigger ?? void 0,
|
|
1330
1567
|
sentryEventId: sentryCtx?.eventId,
|
|
1331
1568
|
sentryReplayId: sentryCtx?.replayId,
|
|
1332
1569
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1363,10 +1600,11 @@ function createInstance(config) {
|
|
|
1363
1600
|
}
|
|
1364
1601
|
pendingScreenshot = null;
|
|
1365
1602
|
pendingElement = null;
|
|
1603
|
+
pendingProactiveTrigger = null;
|
|
1366
1604
|
}
|
|
1367
1605
|
const sdk = {
|
|
1368
|
-
report() {
|
|
1369
|
-
widget.open();
|
|
1606
|
+
report(options) {
|
|
1607
|
+
widget.open(options);
|
|
1370
1608
|
},
|
|
1371
1609
|
on(event, handler) {
|
|
1372
1610
|
if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
|
|
@@ -1389,6 +1627,8 @@ function createInstance(config) {
|
|
|
1389
1627
|
widget.close();
|
|
1390
1628
|
},
|
|
1391
1629
|
destroy() {
|
|
1630
|
+
proactiveTriggers?.destroy();
|
|
1631
|
+
proactiveManager?.reset();
|
|
1392
1632
|
widget.destroy();
|
|
1393
1633
|
consoleCap?.destroy();
|
|
1394
1634
|
networkCap?.destroy();
|
|
@@ -1423,142 +1663,6 @@ function createNoopInstance() {
|
|
|
1423
1663
|
};
|
|
1424
1664
|
}
|
|
1425
1665
|
|
|
1426
|
-
// src/proactive-manager.ts
|
|
1427
|
-
var STORAGE_KEY_LAST_DISMISS = "mushi:lastDismiss";
|
|
1428
|
-
var STORAGE_KEY_CONSEC_DISMISS = "mushi:consecDismiss";
|
|
1429
|
-
function readStorage(key) {
|
|
1430
|
-
try {
|
|
1431
|
-
return localStorage.getItem(key);
|
|
1432
|
-
} catch {
|
|
1433
|
-
return null;
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
function writeStorage(key, value) {
|
|
1437
|
-
try {
|
|
1438
|
-
localStorage.setItem(key, value);
|
|
1439
|
-
} catch {
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
function createProactiveManager(config = {}) {
|
|
1443
|
-
const maxPerSession = config.maxProactivePerSession ?? 2;
|
|
1444
|
-
const cooldownHours = config.dismissCooldownHours ?? 24;
|
|
1445
|
-
const suppressThreshold = config.suppressAfterDismissals ?? 3;
|
|
1446
|
-
let sessionPromptCount = 0;
|
|
1447
|
-
const sessionTriggerTypes = /* @__PURE__ */ new Set();
|
|
1448
|
-
function shouldShow(triggerType) {
|
|
1449
|
-
const consecDismissals = parseInt(readStorage(STORAGE_KEY_CONSEC_DISMISS) ?? "0", 10);
|
|
1450
|
-
if (consecDismissals >= suppressThreshold) return false;
|
|
1451
|
-
const lastDismiss = readStorage(STORAGE_KEY_LAST_DISMISS);
|
|
1452
|
-
if (lastDismiss) {
|
|
1453
|
-
const elapsed = Date.now() - parseInt(lastDismiss, 10);
|
|
1454
|
-
if (elapsed < cooldownHours * 60 * 60 * 1e3) return false;
|
|
1455
|
-
}
|
|
1456
|
-
if (sessionPromptCount >= maxPerSession) return false;
|
|
1457
|
-
if (sessionTriggerTypes.has(triggerType)) return false;
|
|
1458
|
-
sessionTriggerTypes.add(triggerType);
|
|
1459
|
-
sessionPromptCount++;
|
|
1460
|
-
return true;
|
|
1461
|
-
}
|
|
1462
|
-
function recordDismissal() {
|
|
1463
|
-
writeStorage(STORAGE_KEY_LAST_DISMISS, String(Date.now()));
|
|
1464
|
-
const current = parseInt(readStorage(STORAGE_KEY_CONSEC_DISMISS) ?? "0", 10);
|
|
1465
|
-
writeStorage(STORAGE_KEY_CONSEC_DISMISS, String(current + 1));
|
|
1466
|
-
}
|
|
1467
|
-
function recordSubmission() {
|
|
1468
|
-
writeStorage(STORAGE_KEY_CONSEC_DISMISS, "0");
|
|
1469
|
-
}
|
|
1470
|
-
function reset() {
|
|
1471
|
-
sessionPromptCount = 0;
|
|
1472
|
-
sessionTriggerTypes.clear();
|
|
1473
|
-
}
|
|
1474
|
-
return { shouldShow, recordDismissal, recordSubmission, reset };
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
// src/proactive-triggers.ts
|
|
1478
|
-
function setupProactiveTriggers(callbacks) {
|
|
1479
|
-
const cleanups = [];
|
|
1480
|
-
let clickTimes = [];
|
|
1481
|
-
let lastClickTarget = null;
|
|
1482
|
-
function handleClick(e) {
|
|
1483
|
-
const now = Date.now();
|
|
1484
|
-
if (e.target === lastClickTarget) {
|
|
1485
|
-
clickTimes.push(now);
|
|
1486
|
-
clickTimes = clickTimes.filter((t) => now - t < 500);
|
|
1487
|
-
if (clickTimes.length >= 3) {
|
|
1488
|
-
const el = e.target;
|
|
1489
|
-
callbacks.onTrigger("rage_click", {
|
|
1490
|
-
element: el.tagName,
|
|
1491
|
-
id: el.id,
|
|
1492
|
-
text: el.textContent?.slice(0, 50)
|
|
1493
|
-
});
|
|
1494
|
-
clickTimes = [];
|
|
1495
|
-
}
|
|
1496
|
-
} else {
|
|
1497
|
-
lastClickTarget = e.target;
|
|
1498
|
-
clickTimes = [now];
|
|
1499
|
-
}
|
|
1500
|
-
}
|
|
1501
|
-
document.addEventListener("click", handleClick, true);
|
|
1502
|
-
cleanups.push(() => document.removeEventListener("click", handleClick, true));
|
|
1503
|
-
if (typeof PerformanceObserver !== "undefined") {
|
|
1504
|
-
try {
|
|
1505
|
-
const observer = new PerformanceObserver((list) => {
|
|
1506
|
-
for (const entry of list.getEntries()) {
|
|
1507
|
-
if (entry.duration > 5e3) {
|
|
1508
|
-
callbacks.onTrigger("long_task", {
|
|
1509
|
-
duration: Math.round(entry.duration),
|
|
1510
|
-
startTime: Math.round(entry.startTime)
|
|
1511
|
-
});
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
});
|
|
1515
|
-
observer.observe({ entryTypes: ["longtask"] });
|
|
1516
|
-
cleanups.push(() => observer.disconnect());
|
|
1517
|
-
} catch {
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
const failedRequests = [];
|
|
1521
|
-
const origFetch = globalThis.fetch;
|
|
1522
|
-
globalThis.fetch = async function(...args) {
|
|
1523
|
-
try {
|
|
1524
|
-
const res = await origFetch.apply(this, args);
|
|
1525
|
-
if (!res.ok && res.status >= 400) {
|
|
1526
|
-
const now = Date.now();
|
|
1527
|
-
failedRequests.push(now);
|
|
1528
|
-
const recentFailures = failedRequests.filter((t) => now - t < 1e4);
|
|
1529
|
-
if (recentFailures.length >= 3) {
|
|
1530
|
-
callbacks.onTrigger("api_cascade", {
|
|
1531
|
-
failureCount: recentFailures.length,
|
|
1532
|
-
windowMs: 1e4
|
|
1533
|
-
});
|
|
1534
|
-
failedRequests.length = 0;
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
return res;
|
|
1538
|
-
} catch (err) {
|
|
1539
|
-
const now = Date.now();
|
|
1540
|
-
failedRequests.push(now);
|
|
1541
|
-
const recentFailures = failedRequests.filter((t) => now - t < 1e4);
|
|
1542
|
-
if (recentFailures.length >= 3) {
|
|
1543
|
-
callbacks.onTrigger("api_cascade", {
|
|
1544
|
-
failureCount: recentFailures.length,
|
|
1545
|
-
windowMs: 1e4
|
|
1546
|
-
});
|
|
1547
|
-
failedRequests.length = 0;
|
|
1548
|
-
}
|
|
1549
|
-
throw err;
|
|
1550
|
-
}
|
|
1551
|
-
};
|
|
1552
|
-
cleanups.push(() => {
|
|
1553
|
-
globalThis.fetch = origFetch;
|
|
1554
|
-
});
|
|
1555
|
-
return {
|
|
1556
|
-
destroy() {
|
|
1557
|
-
cleanups.forEach((fn) => fn());
|
|
1558
|
-
}
|
|
1559
|
-
};
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
1666
|
export { Mushi, MushiWidget, createConsoleCapture, createElementSelector, createNetworkCapture, createPerformanceCapture, createProactiveManager, createScreenshotCapture, getAvailableLocales, getLocale, setupProactiveTriggers };
|
|
1563
1667
|
//# sourceMappingURL=index.js.map
|
|
1564
1668
|
//# sourceMappingURL=index.js.map
|