@mushi-mushi/web 0.1.0 → 0.2.1
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 +34 -1
- package/dist/index.cjs +251 -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 +252 -146
- package/dist/index.js.map +1 -1
- package/package.json +89 -78
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createLogger, noopLogger, createApiClient, createPreFilter, createOfflineQueue, createRateLimiter, createPiiScrubber, getReporterToken, getSessionId, captureEnvironment } from '@mushi-mushi/core';
|
|
1
|
+
import { createLogger, noopLogger, createApiClient, createPreFilter, createOfflineQueue, createRateLimiter, createPiiScrubber, getDeviceFingerprintHash, getReporterToken, getSessionId, captureEnvironment } from '@mushi-mushi/core';
|
|
2
2
|
|
|
3
3
|
// src/mushi.ts
|
|
4
4
|
|
|
@@ -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,12 +1509,41 @@ 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;
|
|
1307
1543
|
}
|
|
1308
1544
|
const scrubbedDescription = piiScrubber.scrub(preFilter.truncate(description));
|
|
1309
1545
|
const sentryCtx = config.sentry ? captureSentryContext(config.sentry) : void 0;
|
|
1546
|
+
const fingerprintHash = await getDeviceFingerprintHash().catch(() => null);
|
|
1310
1547
|
const report = {
|
|
1311
1548
|
id: crypto.randomUUID?.() ?? `mushi_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
1312
1549
|
projectId: config.projectId,
|
|
@@ -1326,7 +1563,9 @@ function createInstance(config) {
|
|
|
1326
1563
|
},
|
|
1327
1564
|
sessionId: getSessionId(),
|
|
1328
1565
|
reporterToken: getReporterToken(),
|
|
1566
|
+
...fingerprintHash ? { fingerprintHash } : {},
|
|
1329
1567
|
appVersion: config.integrations?.vercel?.analyticsId,
|
|
1568
|
+
proactiveTrigger: pendingProactiveTrigger ?? void 0,
|
|
1330
1569
|
sentryEventId: sentryCtx?.eventId,
|
|
1331
1570
|
sentryReplayId: sentryCtx?.replayId,
|
|
1332
1571
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1363,10 +1602,11 @@ function createInstance(config) {
|
|
|
1363
1602
|
}
|
|
1364
1603
|
pendingScreenshot = null;
|
|
1365
1604
|
pendingElement = null;
|
|
1605
|
+
pendingProactiveTrigger = null;
|
|
1366
1606
|
}
|
|
1367
1607
|
const sdk = {
|
|
1368
|
-
report() {
|
|
1369
|
-
widget.open();
|
|
1608
|
+
report(options) {
|
|
1609
|
+
widget.open(options);
|
|
1370
1610
|
},
|
|
1371
1611
|
on(event, handler) {
|
|
1372
1612
|
if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
|
|
@@ -1389,6 +1629,8 @@ function createInstance(config) {
|
|
|
1389
1629
|
widget.close();
|
|
1390
1630
|
},
|
|
1391
1631
|
destroy() {
|
|
1632
|
+
proactiveTriggers?.destroy();
|
|
1633
|
+
proactiveManager?.reset();
|
|
1392
1634
|
widget.destroy();
|
|
1393
1635
|
consoleCap?.destroy();
|
|
1394
1636
|
networkCap?.destroy();
|
|
@@ -1423,142 +1665,6 @@ function createNoopInstance() {
|
|
|
1423
1665
|
};
|
|
1424
1666
|
}
|
|
1425
1667
|
|
|
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
1668
|
export { Mushi, MushiWidget, createConsoleCapture, createElementSelector, createNetworkCapture, createPerformanceCapture, createProactiveManager, createScreenshotCapture, getAvailableLocales, getLocale, setupProactiveTriggers };
|
|
1563
1669
|
//# sourceMappingURL=index.js.map
|
|
1564
1670
|
//# sourceMappingURL=index.js.map
|