@mushi-mushi/web 0.7.0 → 0.9.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 +170 -5
- package/dist/index.cjs +1119 -93
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -7
- package/dist/index.d.ts +61 -7
- package/dist/index.js +1120 -95
- package/dist/index.js.map +1 -1
- package/dist/test-utils.cjs +17 -0
- package/dist/test-utils.cjs.map +1 -1
- package/dist/test-utils.d.cts +21 -2
- package/dist/test-utils.d.ts +21 -2
- package/dist/test-utils.js +15 -1
- package/dist/test-utils.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createLogger, noopLogger, createApiClient, createPreFilter, createOfflineQueue, createRateLimiter, createPiiScrubber,
|
|
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';
|
|
2
2
|
|
|
3
3
|
// src/mushi.ts
|
|
4
4
|
|
|
@@ -389,6 +389,29 @@ function getWidgetStyles(theme) {
|
|
|
389
389
|
left: var(--mushi-left, calc(24px + env(safe-area-inset-left, 0px)));
|
|
390
390
|
--mushi-origin: top left;
|
|
391
391
|
}
|
|
392
|
+
.mushi-outdated {
|
|
393
|
+
margin: 12px 14px 0;
|
|
394
|
+
padding: 10px 12px;
|
|
395
|
+
border: 1px solid ${vermillionWash};
|
|
396
|
+
background: ${vermillionWash};
|
|
397
|
+
color: ${vermillionInk};
|
|
398
|
+
font-family: ${fontBody};
|
|
399
|
+
font-size: 12px;
|
|
400
|
+
line-height: 1.4;
|
|
401
|
+
}
|
|
402
|
+
.mushi-outdated strong {
|
|
403
|
+
display: block;
|
|
404
|
+
font-family: ${fontMono};
|
|
405
|
+
font-size: 10px;
|
|
406
|
+
letter-spacing: 0.12em;
|
|
407
|
+
text-transform: uppercase;
|
|
408
|
+
margin-bottom: 2px;
|
|
409
|
+
}
|
|
410
|
+
.mushi-outdated span {
|
|
411
|
+
display: block;
|
|
412
|
+
margin-top: 3px;
|
|
413
|
+
color: ${inkMuted};
|
|
414
|
+
}
|
|
392
415
|
|
|
393
416
|
@keyframes mushi-stamp-in {
|
|
394
417
|
0% { opacity: 0; transform: scale(0.94) translateY(6px); }
|
|
@@ -543,6 +566,75 @@ function getWidgetStyles(theme) {
|
|
|
543
566
|
transform: translateX(-4px);
|
|
544
567
|
transition: opacity 220ms ${easeStamp}, transform 220ms ${easeStamp}, color 220ms ${easeStamp};
|
|
545
568
|
}
|
|
569
|
+
.mushi-report-row {
|
|
570
|
+
width: 100%;
|
|
571
|
+
display: grid;
|
|
572
|
+
grid-template-columns: auto 1fr auto;
|
|
573
|
+
gap: 8px;
|
|
574
|
+
align-items: center;
|
|
575
|
+
padding: 10px 0;
|
|
576
|
+
border: 0;
|
|
577
|
+
border-bottom: 1px solid ${rule};
|
|
578
|
+
background: transparent;
|
|
579
|
+
color: ${ink};
|
|
580
|
+
cursor: pointer;
|
|
581
|
+
text-align: left;
|
|
582
|
+
}
|
|
583
|
+
.mushi-report-status {
|
|
584
|
+
font-family: ${fontMono};
|
|
585
|
+
font-size: 10px;
|
|
586
|
+
color: ${vermillion};
|
|
587
|
+
text-transform: uppercase;
|
|
588
|
+
}
|
|
589
|
+
.mushi-report-title {
|
|
590
|
+
font-size: 13px;
|
|
591
|
+
overflow: hidden;
|
|
592
|
+
text-overflow: ellipsis;
|
|
593
|
+
white-space: nowrap;
|
|
594
|
+
}
|
|
595
|
+
.mushi-thread-summary {
|
|
596
|
+
border-bottom: 1px solid ${rule};
|
|
597
|
+
padding-bottom: 10px;
|
|
598
|
+
margin-bottom: 10px;
|
|
599
|
+
}
|
|
600
|
+
.mushi-thread-summary span {
|
|
601
|
+
font-family: ${fontMono};
|
|
602
|
+
font-size: 10px;
|
|
603
|
+
color: ${vermillion};
|
|
604
|
+
text-transform: uppercase;
|
|
605
|
+
}
|
|
606
|
+
.mushi-thread {
|
|
607
|
+
display: grid;
|
|
608
|
+
gap: 8px;
|
|
609
|
+
max-height: 180px;
|
|
610
|
+
overflow: auto;
|
|
611
|
+
margin-bottom: 12px;
|
|
612
|
+
}
|
|
613
|
+
.mushi-thread-comment {
|
|
614
|
+
padding: 8px 10px;
|
|
615
|
+
border: 1px solid ${rule};
|
|
616
|
+
background: ${isDark ? "rgba(242,235,221,0.04)" : "rgba(14,13,11,0.03)"};
|
|
617
|
+
}
|
|
618
|
+
.mushi-thread-comment.reporter {
|
|
619
|
+
border-color: ${vermillionWash};
|
|
620
|
+
background: ${vermillionWash};
|
|
621
|
+
}
|
|
622
|
+
.mushi-thread-comment strong {
|
|
623
|
+
display: block;
|
|
624
|
+
font-family: ${fontMono};
|
|
625
|
+
font-size: 10px;
|
|
626
|
+
letter-spacing: 0.08em;
|
|
627
|
+
text-transform: uppercase;
|
|
628
|
+
margin-bottom: 3px;
|
|
629
|
+
}
|
|
630
|
+
.mushi-thread-comment p,
|
|
631
|
+
.mushi-muted,
|
|
632
|
+
.mushi-error-inline {
|
|
633
|
+
font-size: 12px;
|
|
634
|
+
color: ${inkMuted};
|
|
635
|
+
line-height: 1.45;
|
|
636
|
+
}
|
|
637
|
+
.mushi-error-inline { color: ${vermillion}; }
|
|
546
638
|
|
|
547
639
|
.mushi-selected-category {
|
|
548
640
|
display: inline-flex;
|
|
@@ -651,6 +743,16 @@ function getWidgetStyles(theme) {
|
|
|
651
743
|
border-color: ${vermillion};
|
|
652
744
|
background: ${vermillionWash};
|
|
653
745
|
}
|
|
746
|
+
.mushi-attach-btn.danger {
|
|
747
|
+
color: ${vermillionInk};
|
|
748
|
+
border-color: ${vermillionWash};
|
|
749
|
+
background: transparent;
|
|
750
|
+
}
|
|
751
|
+
.mushi-attach-btn.danger:hover {
|
|
752
|
+
color: ${vermillion};
|
|
753
|
+
border-color: ${vermillion};
|
|
754
|
+
background: ${vermillionWash};
|
|
755
|
+
}
|
|
654
756
|
.mushi-attach-btn:focus-visible {
|
|
655
757
|
outline: 2px solid ${vermillion};
|
|
656
758
|
outline-offset: 2px;
|
|
@@ -720,6 +822,17 @@ function getWidgetStyles(theme) {
|
|
|
720
822
|
}
|
|
721
823
|
.mushi-submit:hover .mushi-submit-arrow { transform: translateX(3px); }
|
|
722
824
|
|
|
825
|
+
.mushi-brand-footer {
|
|
826
|
+
padding: 9px 14px 11px;
|
|
827
|
+
border-top: 1px solid ${rule};
|
|
828
|
+
color: ${inkFaint};
|
|
829
|
+
font-family: ${fontMono};
|
|
830
|
+
font-size: 9px;
|
|
831
|
+
letter-spacing: 0.16em;
|
|
832
|
+
text-align: center;
|
|
833
|
+
text-transform: uppercase;
|
|
834
|
+
}
|
|
835
|
+
|
|
723
836
|
.mushi-step-indicator {
|
|
724
837
|
display: flex;
|
|
725
838
|
align-items: center;
|
|
@@ -849,43 +962,19 @@ var TOTAL_STEPS = 3;
|
|
|
849
962
|
var STEP_NUMBER = {
|
|
850
963
|
category: 1,
|
|
851
964
|
intent: 2,
|
|
852
|
-
details: 3
|
|
853
|
-
};
|
|
965
|
+
details: 3};
|
|
854
966
|
function isSubmitShortcut(e) {
|
|
855
967
|
return (e.metaKey || e.ctrlKey) && e.key === "Enter";
|
|
856
968
|
}
|
|
969
|
+
function escapeHtml(value) {
|
|
970
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
971
|
+
}
|
|
857
972
|
var MushiWidget = class {
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
config;
|
|
861
|
-
callbacks;
|
|
862
|
-
locale;
|
|
863
|
-
isOpen = false;
|
|
864
|
-
step = "category";
|
|
865
|
-
selectedCategory = null;
|
|
866
|
-
selectedIntent = null;
|
|
867
|
-
screenshotAttached = false;
|
|
868
|
-
elementSelected = false;
|
|
869
|
-
submitting = false;
|
|
870
|
-
triggerVisible = true;
|
|
871
|
-
triggerShrunk = false;
|
|
872
|
-
triggerHiddenByScroll = false;
|
|
873
|
-
attachedLaunchers = [];
|
|
874
|
-
smartHideCleanup = null;
|
|
875
|
-
smartHideTimer = null;
|
|
876
|
-
/** Captured at the moment of submit so the success ledger metadata
|
|
877
|
-
* ("REPORT · 14:23:07 JST") doesn't drift while the success step
|
|
878
|
-
* is on screen. */
|
|
879
|
-
submittedAt = null;
|
|
880
|
-
/** Pending success-state + auto-close timers. Tracked so destroy()
|
|
881
|
-
* can clear them — otherwise a host that unmounts mid-submit leaks
|
|
882
|
-
* this MushiWidget reference (and re-renders into a detached shadow
|
|
883
|
-
* root) for up to ~3.3s after destroy. */
|
|
884
|
-
successTimer = null;
|
|
885
|
-
autoCloseTimer = null;
|
|
886
|
-
constructor(config = {}, callbacks) {
|
|
973
|
+
constructor(config = {}, callbacks, sdkVersion = "0.7.0") {
|
|
974
|
+
this.sdkVersion = sdkVersion;
|
|
887
975
|
this.config = {
|
|
888
976
|
position: config.position ?? "bottom-right",
|
|
977
|
+
anchor: config.anchor ?? {},
|
|
889
978
|
theme: config.theme ?? "auto",
|
|
890
979
|
// Falsy-OR (NOT `??`) on purpose: `triggerText: ''` is semantically
|
|
891
980
|
// nonsense — it would render a labelless, glyphless trigger button
|
|
@@ -907,7 +996,9 @@ var MushiWidget = class {
|
|
|
907
996
|
hideOnRoutes: config.hideOnRoutes ?? [],
|
|
908
997
|
environments: config.environments ?? {},
|
|
909
998
|
smartHide: config.smartHide ?? false,
|
|
910
|
-
draggable: config.draggable ?? false
|
|
999
|
+
draggable: config.draggable ?? false,
|
|
1000
|
+
brandFooter: config.brandFooter ?? true,
|
|
1001
|
+
outdatedBanner: config.outdatedBanner ?? "auto"
|
|
911
1002
|
};
|
|
912
1003
|
this.callbacks = callbacks;
|
|
913
1004
|
this.locale = getLocale(this.config.locale === "auto" ? void 0 : this.config.locale);
|
|
@@ -915,16 +1006,57 @@ var MushiWidget = class {
|
|
|
915
1006
|
this.host.id = "mushi-mushi-widget";
|
|
916
1007
|
this.shadow = this.host.attachShadow({ mode: "closed" });
|
|
917
1008
|
}
|
|
1009
|
+
sdkVersion;
|
|
1010
|
+
host;
|
|
1011
|
+
shadow;
|
|
1012
|
+
config;
|
|
1013
|
+
callbacks;
|
|
1014
|
+
locale;
|
|
1015
|
+
isOpen = false;
|
|
1016
|
+
step = "category";
|
|
1017
|
+
selectedCategory = null;
|
|
1018
|
+
selectedIntent = null;
|
|
1019
|
+
screenshotAttached = false;
|
|
1020
|
+
allowScreenshotRemove = true;
|
|
1021
|
+
elementSelected = false;
|
|
1022
|
+
submitting = false;
|
|
1023
|
+
triggerVisible = true;
|
|
1024
|
+
triggerShrunk = false;
|
|
1025
|
+
triggerHiddenByScroll = false;
|
|
1026
|
+
sdkFreshness = null;
|
|
1027
|
+
reporterReports = [];
|
|
1028
|
+
reporterComments = [];
|
|
1029
|
+
selectedReportId = null;
|
|
1030
|
+
reporterLoading = false;
|
|
1031
|
+
reporterError = null;
|
|
1032
|
+
attachedLaunchers = [];
|
|
1033
|
+
smartHideCleanup = null;
|
|
1034
|
+
smartHideTimer = null;
|
|
1035
|
+
/** Captured at the moment of submit so the success ledger metadata
|
|
1036
|
+
* ("REPORT · 14:23:07 JST") doesn't drift while the success step
|
|
1037
|
+
* is on screen. */
|
|
1038
|
+
submittedAt = null;
|
|
1039
|
+
/** Pending success-state + auto-close timers. Tracked so destroy()
|
|
1040
|
+
* can clear them — otherwise a host that unmounts mid-submit leaks
|
|
1041
|
+
* this MushiWidget reference (and re-renders into a detached shadow
|
|
1042
|
+
* root) for up to ~3.3s after destroy. */
|
|
1043
|
+
successTimer = null;
|
|
1044
|
+
autoCloseTimer = null;
|
|
918
1045
|
mount() {
|
|
1046
|
+
if (this.host.isConnected) return;
|
|
919
1047
|
document.body.appendChild(this.host);
|
|
920
1048
|
this.syncAttachedLaunchers();
|
|
921
1049
|
this.syncSmartHide();
|
|
922
1050
|
this.render();
|
|
923
1051
|
}
|
|
1052
|
+
getIsMounted() {
|
|
1053
|
+
return this.host.isConnected;
|
|
1054
|
+
}
|
|
924
1055
|
updateConfig(config = {}) {
|
|
925
1056
|
this.config = {
|
|
926
1057
|
...this.config,
|
|
927
1058
|
...config.position ? { position: config.position } : {},
|
|
1059
|
+
...config.anchor !== void 0 ? { anchor: config.anchor } : {},
|
|
928
1060
|
...config.theme ? { theme: config.theme } : {},
|
|
929
1061
|
...config.triggerText !== void 0 ? { triggerText: config.triggerText || "\u{1F41B}" } : {},
|
|
930
1062
|
...config.expandedTitle !== void 0 ? { expandedTitle: config.expandedTitle } : {},
|
|
@@ -939,7 +1071,9 @@ var MushiWidget = class {
|
|
|
939
1071
|
...config.hideOnRoutes !== void 0 ? { hideOnRoutes: config.hideOnRoutes } : {},
|
|
940
1072
|
...config.environments !== void 0 ? { environments: config.environments } : {},
|
|
941
1073
|
...config.smartHide !== void 0 ? { smartHide: config.smartHide } : {},
|
|
942
|
-
...config.draggable !== void 0 ? { draggable: config.draggable } : {}
|
|
1074
|
+
...config.draggable !== void 0 ? { draggable: config.draggable } : {},
|
|
1075
|
+
...config.brandFooter !== void 0 ? { brandFooter: config.brandFooter } : {},
|
|
1076
|
+
...config.outdatedBanner !== void 0 ? { outdatedBanner: config.outdatedBanner } : {}
|
|
943
1077
|
};
|
|
944
1078
|
this.locale = getLocale(this.config.locale === "auto" ? void 0 : this.config.locale);
|
|
945
1079
|
this.syncAttachedLaunchers();
|
|
@@ -1002,10 +1136,18 @@ var MushiWidget = class {
|
|
|
1002
1136
|
this.screenshotAttached = attached;
|
|
1003
1137
|
if (this.isOpen) this.render();
|
|
1004
1138
|
}
|
|
1139
|
+
setAllowScreenshotRemove(allow) {
|
|
1140
|
+
this.allowScreenshotRemove = allow;
|
|
1141
|
+
if (this.isOpen) this.render();
|
|
1142
|
+
}
|
|
1005
1143
|
setElementSelected(selected) {
|
|
1006
1144
|
this.elementSelected = selected;
|
|
1007
1145
|
if (this.isOpen) this.render();
|
|
1008
1146
|
}
|
|
1147
|
+
setSdkFreshness(info) {
|
|
1148
|
+
this.sdkFreshness = info;
|
|
1149
|
+
if (this.isOpen) this.render();
|
|
1150
|
+
}
|
|
1009
1151
|
destroy() {
|
|
1010
1152
|
if (this.successTimer !== null) {
|
|
1011
1153
|
clearTimeout(this.successTimer);
|
|
@@ -1131,13 +1273,22 @@ var MushiWidget = class {
|
|
|
1131
1273
|
panel.style.zIndex = String(this.config.zIndex + 1);
|
|
1132
1274
|
this.applyInsetVars(panel);
|
|
1133
1275
|
if (this.isOpen) {
|
|
1134
|
-
panel.innerHTML = this.renderStep()
|
|
1276
|
+
panel.innerHTML = `${this.renderOutdatedBanner()}${this.renderStep()}${this.renderBrandFooter()}`;
|
|
1135
1277
|
this.shadow.appendChild(panel);
|
|
1136
1278
|
this.attachHandlers(panel);
|
|
1137
1279
|
this.trapFocus(panel);
|
|
1138
1280
|
}
|
|
1139
1281
|
}
|
|
1140
1282
|
applyInsetVars(el) {
|
|
1283
|
+
const { anchor } = this.config;
|
|
1284
|
+
if (anchor && Object.keys(anchor).length > 0) {
|
|
1285
|
+
["top", "right", "bottom", "left"].forEach((edge) => {
|
|
1286
|
+
const value = anchor[edge];
|
|
1287
|
+
if (value !== void 0) el.style.setProperty(`--mushi-${edge}`, value);
|
|
1288
|
+
});
|
|
1289
|
+
el.style.setProperty("--mushi-safe-area", this.config.respectSafeArea ? "1" : "0");
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1141
1292
|
const { inset } = this.config;
|
|
1142
1293
|
if (!this.config.respectSafeArea) {
|
|
1143
1294
|
["top", "right", "bottom", "left"].forEach((edge) => {
|
|
@@ -1161,8 +1312,29 @@ var MushiWidget = class {
|
|
|
1161
1312
|
return this.renderDetailsStep();
|
|
1162
1313
|
case "success":
|
|
1163
1314
|
return this.renderSuccessStep();
|
|
1315
|
+
case "reports":
|
|
1316
|
+
return this.renderReportsStep();
|
|
1317
|
+
case "report-detail":
|
|
1318
|
+
return this.renderReportDetailStep();
|
|
1164
1319
|
}
|
|
1165
1320
|
}
|
|
1321
|
+
renderOutdatedBanner() {
|
|
1322
|
+
if (!this.sdkFreshness) return "";
|
|
1323
|
+
if (this.config.outdatedBanner === "off" || this.config.outdatedBanner === "console-only") return "";
|
|
1324
|
+
const { latest, current, deprecated, message } = this.sdkFreshness;
|
|
1325
|
+
if (!latest && !deprecated) return "";
|
|
1326
|
+
return `
|
|
1327
|
+
<div class="mushi-outdated" role="status">
|
|
1328
|
+
<strong>Mushi SDK ${escapeHtml(current)}</strong>
|
|
1329
|
+
${latest ? `latest is ${escapeHtml(latest)}.` : "needs attention."}
|
|
1330
|
+
${message ? `<span>${escapeHtml(message)}</span>` : ""}
|
|
1331
|
+
</div>
|
|
1332
|
+
`;
|
|
1333
|
+
}
|
|
1334
|
+
renderBrandFooter() {
|
|
1335
|
+
if (this.config.brandFooter === false) return "";
|
|
1336
|
+
return `<div class="mushi-brand-footer">Powered by Mushi v${escapeHtml(this.sdkVersion)}</div>`;
|
|
1337
|
+
}
|
|
1166
1338
|
/**
|
|
1167
1339
|
* Editorial masthead. Always carries:
|
|
1168
1340
|
* • the brand mark (虫 kanji on vermillion, "MUSHI" in mono above)
|
|
@@ -1221,11 +1393,61 @@ var MushiWidget = class {
|
|
|
1221
1393
|
return `
|
|
1222
1394
|
${this.renderHeader({ title: t.step1.heading, step: STEP_NUMBER.category })}
|
|
1223
1395
|
<div class="mushi-body" role="radiogroup" aria-label="${t.step1.heading}">
|
|
1396
|
+
<button type="button" class="mushi-option-btn mushi-reports-entry" data-action="reports">
|
|
1397
|
+
<span class="mushi-option-icon" aria-hidden="true">\u{1F4EC}</span>
|
|
1398
|
+
<div class="mushi-option-text">
|
|
1399
|
+
<span class="mushi-option-label">Your reports${this.unreadCount() ? ` (${this.unreadCount()} new)` : ""}</span>
|
|
1400
|
+
<span class="mushi-option-desc">See status, developer replies, and respond</span>
|
|
1401
|
+
</div>
|
|
1402
|
+
<span class="mushi-option-arrow" aria-hidden="true">\u2192</span>
|
|
1403
|
+
</button>
|
|
1224
1404
|
${categories}
|
|
1225
1405
|
</div>
|
|
1226
1406
|
${this.renderStepIndicator(STEP_NUMBER.category)}
|
|
1227
1407
|
`;
|
|
1228
1408
|
}
|
|
1409
|
+
renderReportsStep() {
|
|
1410
|
+
const reports = this.reporterReports.map((report) => `
|
|
1411
|
+
<button type="button" class="mushi-report-row" data-report-id="${escapeHtml(report.id)}">
|
|
1412
|
+
<span class="mushi-report-status">${escapeHtml(report.status)}</span>
|
|
1413
|
+
<span class="mushi-report-title">${escapeHtml(report.summary ?? report.description ?? `Report ${report.id.slice(0, 8)}`)}</span>
|
|
1414
|
+
${report.unread_count ? `<b>${report.unread_count}</b>` : ""}
|
|
1415
|
+
</button>
|
|
1416
|
+
`).join("");
|
|
1417
|
+
return `
|
|
1418
|
+
${this.renderHeader({ title: "Your reports", showBack: true, eyebrow: "Mushi \xB7 Inbox" })}
|
|
1419
|
+
<div class="mushi-body">
|
|
1420
|
+
${this.reporterLoading ? '<p class="mushi-muted">Loading reports\u2026</p>' : ""}
|
|
1421
|
+
${this.reporterError ? `<p class="mushi-error-inline">${escapeHtml(this.reporterError)}</p>` : ""}
|
|
1422
|
+
${reports || (!this.reporterLoading ? '<p class="mushi-muted">No reports from this browser yet.</p>' : "")}
|
|
1423
|
+
</div>
|
|
1424
|
+
`;
|
|
1425
|
+
}
|
|
1426
|
+
renderReportDetailStep() {
|
|
1427
|
+
const report = this.reporterReports.find((r) => r.id === this.selectedReportId);
|
|
1428
|
+
const comments = this.reporterComments.map((comment) => `
|
|
1429
|
+
<div class="mushi-thread-comment ${comment.author_kind}">
|
|
1430
|
+
<strong>${escapeHtml(comment.author_kind === "reporter" ? "You" : comment.author_name ?? "Developer")}</strong>
|
|
1431
|
+
<p>${escapeHtml(comment.body)}</p>
|
|
1432
|
+
</div>
|
|
1433
|
+
`).join("");
|
|
1434
|
+
return `
|
|
1435
|
+
${this.renderHeader({ title: "Report thread", showBack: true, eyebrow: "Mushi \xB7 Inbox" })}
|
|
1436
|
+
<div class="mushi-body">
|
|
1437
|
+
<div class="mushi-thread-summary">
|
|
1438
|
+
<span>${escapeHtml(report?.status ?? "unknown")}</span>
|
|
1439
|
+
<p>${escapeHtml(report?.summary ?? report?.description ?? "Report details")}</p>
|
|
1440
|
+
</div>
|
|
1441
|
+
<div class="mushi-thread">
|
|
1442
|
+
${this.reporterLoading ? '<p class="mushi-muted">Loading thread\u2026</p>' : comments || '<p class="mushi-muted">No developer replies yet.</p>'}
|
|
1443
|
+
</div>
|
|
1444
|
+
<textarea class="mushi-textarea" data-role="reporter-reply" rows="3" placeholder="Reply to the developer\u2026"></textarea>
|
|
1445
|
+
<button type="button" class="mushi-submit" data-action="reporter-reply">
|
|
1446
|
+
<span>Reply</span><span class="mushi-submit-arrow" aria-hidden="true">\u2192</span>
|
|
1447
|
+
</button>
|
|
1448
|
+
</div>
|
|
1449
|
+
`;
|
|
1450
|
+
}
|
|
1229
1451
|
renderIntentStep() {
|
|
1230
1452
|
const t = this.locale;
|
|
1231
1453
|
const cat = this.selectedCategory;
|
|
@@ -1265,6 +1487,7 @@ var MushiWidget = class {
|
|
|
1265
1487
|
<button type="button" class="mushi-attach-btn${this.screenshotAttached ? " active" : ""}" data-action="screenshot">
|
|
1266
1488
|
\u{1F4F8} ${this.screenshotAttached ? t.step3.screenshotAttached : t.step3.screenshotButton}
|
|
1267
1489
|
</button>
|
|
1490
|
+
${this.screenshotAttached && this.allowScreenshotRemove ? '<button type="button" class="mushi-attach-btn danger" data-action="remove-screenshot">\u2715 Remove screenshot</button>' : ""}
|
|
1268
1491
|
<button type="button" class="mushi-attach-btn${this.elementSelected ? " active" : ""}" data-action="element">
|
|
1269
1492
|
\u{1F3AF} ${this.elementSelected ? t.step3.elementSelected : t.step3.elementButton}
|
|
1270
1493
|
</button>
|
|
@@ -1317,9 +1540,26 @@ var MushiWidget = class {
|
|
|
1317
1540
|
} else if (this.step === "details") {
|
|
1318
1541
|
this.step = "intent";
|
|
1319
1542
|
this.selectedIntent = null;
|
|
1543
|
+
} else if (this.step === "reports") {
|
|
1544
|
+
this.step = "category";
|
|
1545
|
+
} else if (this.step === "report-detail") {
|
|
1546
|
+
this.step = "reports";
|
|
1547
|
+
this.selectedReportId = null;
|
|
1320
1548
|
}
|
|
1321
1549
|
this.render();
|
|
1322
1550
|
});
|
|
1551
|
+
panel.querySelector('[data-action="reports"]')?.addEventListener("click", () => {
|
|
1552
|
+
void this.loadReporterReports();
|
|
1553
|
+
});
|
|
1554
|
+
panel.querySelectorAll("[data-report-id]").forEach((btn) => {
|
|
1555
|
+
btn.addEventListener("click", () => {
|
|
1556
|
+
const reportId = btn.dataset.reportId;
|
|
1557
|
+
if (reportId) void this.loadReporterComments(reportId);
|
|
1558
|
+
});
|
|
1559
|
+
});
|
|
1560
|
+
panel.querySelector('[data-action="reporter-reply"]')?.addEventListener("click", () => {
|
|
1561
|
+
void this.submitReporterReply(panel);
|
|
1562
|
+
});
|
|
1323
1563
|
panel.querySelectorAll("[data-category]").forEach((btn) => {
|
|
1324
1564
|
btn.addEventListener("click", () => {
|
|
1325
1565
|
this.selectedCategory = btn.dataset.category;
|
|
@@ -1337,6 +1577,9 @@ var MushiWidget = class {
|
|
|
1337
1577
|
panel.querySelector('[data-action="screenshot"]')?.addEventListener("click", () => {
|
|
1338
1578
|
this.callbacks.onScreenshotRequest();
|
|
1339
1579
|
});
|
|
1580
|
+
panel.querySelector('[data-action="remove-screenshot"]')?.addEventListener("click", () => {
|
|
1581
|
+
this.callbacks.onScreenshotRemove?.();
|
|
1582
|
+
});
|
|
1340
1583
|
panel.querySelector('[data-action="element"]')?.addEventListener("click", () => {
|
|
1341
1584
|
this.callbacks.onElementSelectorRequest?.();
|
|
1342
1585
|
});
|
|
@@ -1394,6 +1637,57 @@ var MushiWidget = class {
|
|
|
1394
1637
|
if (focusable.length > 0) focusable[0].focus();
|
|
1395
1638
|
});
|
|
1396
1639
|
}
|
|
1640
|
+
unreadCount() {
|
|
1641
|
+
return this.reporterReports.reduce((sum, report) => sum + (report.unread_count ?? 0), 0);
|
|
1642
|
+
}
|
|
1643
|
+
async loadReporterReports() {
|
|
1644
|
+
this.step = "reports";
|
|
1645
|
+
this.reporterLoading = true;
|
|
1646
|
+
this.reporterError = null;
|
|
1647
|
+
this.render();
|
|
1648
|
+
try {
|
|
1649
|
+
this.reporterReports = await this.callbacks.onReporterReportsRequest?.() ?? [];
|
|
1650
|
+
} catch (err) {
|
|
1651
|
+
this.reporterError = err instanceof Error ? err.message : "Could not load reports.";
|
|
1652
|
+
} finally {
|
|
1653
|
+
this.reporterLoading = false;
|
|
1654
|
+
this.render();
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
async loadReporterComments(reportId) {
|
|
1658
|
+
this.selectedReportId = reportId;
|
|
1659
|
+
this.step = "report-detail";
|
|
1660
|
+
this.reporterLoading = true;
|
|
1661
|
+
this.reporterError = null;
|
|
1662
|
+
this.render();
|
|
1663
|
+
try {
|
|
1664
|
+
this.reporterComments = await this.callbacks.onReporterCommentsRequest?.(reportId) ?? [];
|
|
1665
|
+
} catch (err) {
|
|
1666
|
+
this.reporterError = err instanceof Error ? err.message : "Could not load thread.";
|
|
1667
|
+
} finally {
|
|
1668
|
+
this.reporterLoading = false;
|
|
1669
|
+
this.render();
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
async submitReporterReply(panel) {
|
|
1673
|
+
const reportId = this.selectedReportId;
|
|
1674
|
+
const textarea = panel.querySelector('[data-role="reporter-reply"]');
|
|
1675
|
+
const replyButton = panel.querySelector('[data-action="reporter-reply"]');
|
|
1676
|
+
const body = textarea?.value.trim() ?? "";
|
|
1677
|
+
if (!reportId || !body || this.reporterLoading) return;
|
|
1678
|
+
this.reporterLoading = true;
|
|
1679
|
+
if (replyButton) replyButton.disabled = true;
|
|
1680
|
+
this.render();
|
|
1681
|
+
try {
|
|
1682
|
+
await this.callbacks.onReporterReply?.(reportId, body);
|
|
1683
|
+
if (textarea) textarea.value = "";
|
|
1684
|
+
await this.loadReporterComments(reportId);
|
|
1685
|
+
} catch (err) {
|
|
1686
|
+
this.reporterError = err instanceof Error ? err.message : "Could not send reply.";
|
|
1687
|
+
this.reporterLoading = false;
|
|
1688
|
+
this.render();
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1397
1691
|
};
|
|
1398
1692
|
|
|
1399
1693
|
// src/capture/console.ts
|
|
@@ -1445,35 +1739,109 @@ function createConsoleCapture() {
|
|
|
1445
1739
|
}
|
|
1446
1740
|
};
|
|
1447
1741
|
}
|
|
1742
|
+
var DEFAULT_INTERNAL_URL_MATCHERS = [
|
|
1743
|
+
/\/v1\/sdk(?:\/|$)/,
|
|
1744
|
+
/\/v1\/reports(?:\/|$)/,
|
|
1745
|
+
/\/v1\/notifications(?:\/|$)/,
|
|
1746
|
+
/\/v1\/reputation(?:\/|$)/
|
|
1747
|
+
];
|
|
1748
|
+
function getRequestUrl(input) {
|
|
1749
|
+
if (typeof input === "string") return input;
|
|
1750
|
+
if (input instanceof URL) return input.href;
|
|
1751
|
+
return input.url;
|
|
1752
|
+
}
|
|
1753
|
+
function getInternalRequestKind(input, init) {
|
|
1754
|
+
const marker = init?.[MUSHI_INTERNAL_INIT_MARKER];
|
|
1755
|
+
if (marker) return marker;
|
|
1756
|
+
const initHeader = readHeader(init?.headers, MUSHI_INTERNAL_HEADER);
|
|
1757
|
+
if (initHeader) return initHeader;
|
|
1758
|
+
if (typeof Request !== "undefined" && input instanceof Request) {
|
|
1759
|
+
const requestHeader = input.headers.get(MUSHI_INTERNAL_HEADER);
|
|
1760
|
+
if (requestHeader) return requestHeader;
|
|
1761
|
+
}
|
|
1762
|
+
return null;
|
|
1763
|
+
}
|
|
1764
|
+
function shouldIgnoreMushiUrl(url, options = {}) {
|
|
1765
|
+
const matchers = [...DEFAULT_INTERNAL_URL_MATCHERS, ...options.ignoreUrls ?? []];
|
|
1766
|
+
if (matchers.some((matcher) => matchesUrl(url, matcher))) return true;
|
|
1767
|
+
const endpoint = normalizeUrlPrefix(options.apiEndpoint);
|
|
1768
|
+
return endpoint ? normalizeComparableUrl(url).startsWith(endpoint) : false;
|
|
1769
|
+
}
|
|
1770
|
+
function matchesUrl(url, matcher) {
|
|
1771
|
+
if (typeof matcher === "string") {
|
|
1772
|
+
return normalizeComparableUrl(url).includes(matcher);
|
|
1773
|
+
}
|
|
1774
|
+
matcher.lastIndex = 0;
|
|
1775
|
+
return matcher.test(url);
|
|
1776
|
+
}
|
|
1777
|
+
function normalizeUrlPrefix(url) {
|
|
1778
|
+
if (!url) return null;
|
|
1779
|
+
return normalizeComparableUrl(url).replace(/\/+$/, "");
|
|
1780
|
+
}
|
|
1781
|
+
function isLocalhostEndpoint(url) {
|
|
1782
|
+
if (!url) return false;
|
|
1783
|
+
try {
|
|
1784
|
+
const parsed = new URL(url);
|
|
1785
|
+
return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "::1" || parsed.hostname.endsWith(".localhost");
|
|
1786
|
+
} catch {
|
|
1787
|
+
return /\blocalhost\b|127\.0\.0\.1/.test(url);
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
function normalizeComparableUrl(url) {
|
|
1791
|
+
try {
|
|
1792
|
+
return new URL(url, typeof location !== "undefined" ? location.href : "http://localhost").href;
|
|
1793
|
+
} catch {
|
|
1794
|
+
return url;
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
function readHeader(headers, name) {
|
|
1798
|
+
if (!headers) return null;
|
|
1799
|
+
if (typeof Headers !== "undefined" && headers instanceof Headers) {
|
|
1800
|
+
return headers.get(name);
|
|
1801
|
+
}
|
|
1802
|
+
if (Array.isArray(headers)) {
|
|
1803
|
+
const found = headers.find(([key]) => key.toLowerCase() === name.toLowerCase());
|
|
1804
|
+
return found?.[1] ?? null;
|
|
1805
|
+
}
|
|
1806
|
+
const record = headers;
|
|
1807
|
+
return record[name] ?? record[name.toLowerCase()] ?? null;
|
|
1808
|
+
}
|
|
1448
1809
|
|
|
1449
1810
|
// src/capture/network.ts
|
|
1450
1811
|
var MAX_ENTRIES2 = 30;
|
|
1451
|
-
function createNetworkCapture() {
|
|
1812
|
+
function createNetworkCapture(options = {}) {
|
|
1452
1813
|
const entries = [];
|
|
1453
1814
|
const originalFetch = globalThis.fetch;
|
|
1815
|
+
let activeOptions = options;
|
|
1454
1816
|
globalThis.fetch = async function mushiFetchInterceptor(input, init) {
|
|
1455
1817
|
const startTime = Date.now();
|
|
1456
1818
|
const method = init?.method?.toUpperCase() ?? "GET";
|
|
1457
|
-
const url =
|
|
1819
|
+
const url = getRequestUrl(input);
|
|
1820
|
+
const internalKind = getInternalRequestKind(input, init);
|
|
1821
|
+
const shouldRecord = !internalKind && !shouldIgnoreMushiUrl(url, activeOptions);
|
|
1458
1822
|
try {
|
|
1459
1823
|
const response = await originalFetch.call(globalThis, input, init);
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1824
|
+
if (shouldRecord) {
|
|
1825
|
+
addEntry({
|
|
1826
|
+
method,
|
|
1827
|
+
url: truncateUrl(url),
|
|
1828
|
+
status: response.status,
|
|
1829
|
+
duration: Date.now() - startTime,
|
|
1830
|
+
timestamp: startTime
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1467
1833
|
return response;
|
|
1468
1834
|
} catch (error) {
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1835
|
+
if (shouldRecord) {
|
|
1836
|
+
addEntry({
|
|
1837
|
+
method,
|
|
1838
|
+
url: truncateUrl(url),
|
|
1839
|
+
status: 0,
|
|
1840
|
+
duration: Date.now() - startTime,
|
|
1841
|
+
timestamp: startTime,
|
|
1842
|
+
error: error instanceof Error ? error.message : "Network error"
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1477
1845
|
throw error;
|
|
1478
1846
|
}
|
|
1479
1847
|
};
|
|
@@ -1490,6 +1858,9 @@ function createNetworkCapture() {
|
|
|
1490
1858
|
clear() {
|
|
1491
1859
|
entries.length = 0;
|
|
1492
1860
|
},
|
|
1861
|
+
updateOptions(nextOptions) {
|
|
1862
|
+
activeOptions = nextOptions;
|
|
1863
|
+
},
|
|
1493
1864
|
destroy() {
|
|
1494
1865
|
globalThis.fetch = originalFetch;
|
|
1495
1866
|
}
|
|
@@ -1509,7 +1880,8 @@ function truncateUrl(url) {
|
|
|
1509
1880
|
}
|
|
1510
1881
|
|
|
1511
1882
|
// src/capture/screenshot.ts
|
|
1512
|
-
function createScreenshotCapture() {
|
|
1883
|
+
function createScreenshotCapture(options = {}) {
|
|
1884
|
+
let activeOptions = options;
|
|
1513
1885
|
async function take() {
|
|
1514
1886
|
try {
|
|
1515
1887
|
if (typeof document === "undefined") return null;
|
|
@@ -1522,11 +1894,12 @@ function createScreenshotCapture() {
|
|
|
1522
1894
|
canvas.width = width * dpr;
|
|
1523
1895
|
canvas.height = height * dpr;
|
|
1524
1896
|
ctx.scale(dpr, dpr);
|
|
1897
|
+
const safeDocument = buildPrivacySafeDocument(activeOptions.privacy);
|
|
1525
1898
|
const svgData = `
|
|
1526
1899
|
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
|
|
1527
1900
|
<foreignObject width="100%" height="100%">
|
|
1528
1901
|
<div xmlns="http://www.w3.org/1999/xhtml">
|
|
1529
|
-
${new XMLSerializer().serializeToString(
|
|
1902
|
+
${new XMLSerializer().serializeToString(safeDocument)}
|
|
1530
1903
|
</div>
|
|
1531
1904
|
</foreignObject>
|
|
1532
1905
|
</svg>
|
|
@@ -1555,7 +1928,46 @@ function createScreenshotCapture() {
|
|
|
1555
1928
|
return null;
|
|
1556
1929
|
}
|
|
1557
1930
|
}
|
|
1558
|
-
return {
|
|
1931
|
+
return {
|
|
1932
|
+
take,
|
|
1933
|
+
updateOptions(nextOptions) {
|
|
1934
|
+
activeOptions = nextOptions;
|
|
1935
|
+
}
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
function buildPrivacySafeDocument(privacy) {
|
|
1939
|
+
const clone = document.documentElement.cloneNode(true);
|
|
1940
|
+
for (const selector of privacy?.blockSelectors ?? []) {
|
|
1941
|
+
for (const el of safeQueryAll(clone, selector)) {
|
|
1942
|
+
el.remove();
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
for (const selector of privacy?.maskSelectors ?? []) {
|
|
1946
|
+
for (const el of safeQueryAll(clone, selector)) {
|
|
1947
|
+
maskElement(el);
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
return clone;
|
|
1951
|
+
}
|
|
1952
|
+
function safeQueryAll(root, selector) {
|
|
1953
|
+
try {
|
|
1954
|
+
return Array.from(root.querySelectorAll(selector));
|
|
1955
|
+
} catch {
|
|
1956
|
+
return [];
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
function maskElement(el) {
|
|
1960
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
|
|
1961
|
+
el.value = "";
|
|
1962
|
+
el.setAttribute("value", "");
|
|
1963
|
+
el.setAttribute("placeholder", "\u2022\u2022\u2022\u2022");
|
|
1964
|
+
}
|
|
1965
|
+
el.textContent = el.children.length === 0 ? "\u2022\u2022\u2022\u2022" : el.textContent;
|
|
1966
|
+
el.setAttribute(
|
|
1967
|
+
"style",
|
|
1968
|
+
`${el.getAttribute("style") ?? ""};background:#8f8f8f!important;color:transparent!important;text-shadow:none!important;`
|
|
1969
|
+
);
|
|
1970
|
+
el.setAttribute("data-mushi-masked", "true");
|
|
1559
1971
|
}
|
|
1560
1972
|
|
|
1561
1973
|
// src/capture/performance.ts
|
|
@@ -1638,6 +2050,17 @@ function createElementSelector() {
|
|
|
1638
2050
|
let active = false;
|
|
1639
2051
|
let overlay = null;
|
|
1640
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
|
+
}
|
|
1641
2064
|
function getXPath(el) {
|
|
1642
2065
|
const parts = [];
|
|
1643
2066
|
let current = el;
|
|
@@ -1667,7 +2090,13 @@ function createElementSelector() {
|
|
|
1667
2090
|
y: Math.round(rect.y),
|
|
1668
2091
|
width: Math.round(rect.width),
|
|
1669
2092
|
height: Math.round(rect.height)
|
|
1670
|
-
}
|
|
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
|
|
1671
2100
|
};
|
|
1672
2101
|
}
|
|
1673
2102
|
function createOverlay() {
|
|
@@ -1744,6 +2173,296 @@ function createElementSelector() {
|
|
|
1744
2173
|
return { activate, deactivate, isActive: () => active };
|
|
1745
2174
|
}
|
|
1746
2175
|
|
|
2176
|
+
// src/capture/timeline.ts
|
|
2177
|
+
var MAX_TIMELINE_ENTRIES = 120;
|
|
2178
|
+
function createTimelineCapture() {
|
|
2179
|
+
const entries = [];
|
|
2180
|
+
const originalPushState = history.pushState;
|
|
2181
|
+
const originalReplaceState = history.replaceState;
|
|
2182
|
+
const handlePopState = () => recordRoute("popstate");
|
|
2183
|
+
const handleHashChange = () => recordRoute("hashchange");
|
|
2184
|
+
recordRoute("initial");
|
|
2185
|
+
function record(entry) {
|
|
2186
|
+
entries.push(entry);
|
|
2187
|
+
if (entries.length > MAX_TIMELINE_ENTRIES) entries.shift();
|
|
2188
|
+
}
|
|
2189
|
+
function recordRoute(source) {
|
|
2190
|
+
if (typeof location === "undefined") return;
|
|
2191
|
+
record({
|
|
2192
|
+
ts: Date.now(),
|
|
2193
|
+
kind: "route",
|
|
2194
|
+
payload: {
|
|
2195
|
+
source,
|
|
2196
|
+
route: `${location.pathname}${location.search}${location.hash}`,
|
|
2197
|
+
href: location.href
|
|
2198
|
+
}
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
function handleClick(event) {
|
|
2202
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
2203
|
+
if (!target) return;
|
|
2204
|
+
const el = target.closest('button,a,[role="button"],input,textarea,select,[data-mushi-track]') ?? target;
|
|
2205
|
+
record({
|
|
2206
|
+
ts: Date.now(),
|
|
2207
|
+
kind: "click",
|
|
2208
|
+
payload: {
|
|
2209
|
+
tag: el.tagName.toLowerCase(),
|
|
2210
|
+
id: el.id || void 0,
|
|
2211
|
+
text: textSnippet(el)
|
|
2212
|
+
}
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2215
|
+
history.pushState = function mushiPushState(...args) {
|
|
2216
|
+
const result = originalPushState.apply(this, args);
|
|
2217
|
+
recordRoute("pushState");
|
|
2218
|
+
return result;
|
|
2219
|
+
};
|
|
2220
|
+
history.replaceState = function mushiReplaceState(...args) {
|
|
2221
|
+
const result = originalReplaceState.apply(this, args);
|
|
2222
|
+
recordRoute("replaceState");
|
|
2223
|
+
return result;
|
|
2224
|
+
};
|
|
2225
|
+
window.addEventListener("popstate", handlePopState);
|
|
2226
|
+
window.addEventListener("hashchange", handleHashChange);
|
|
2227
|
+
document.addEventListener("click", handleClick, true);
|
|
2228
|
+
return {
|
|
2229
|
+
setScreen(screen) {
|
|
2230
|
+
record({
|
|
2231
|
+
ts: Date.now(),
|
|
2232
|
+
kind: "screen",
|
|
2233
|
+
payload: screen
|
|
2234
|
+
});
|
|
2235
|
+
},
|
|
2236
|
+
getEntries(input = {}) {
|
|
2237
|
+
const merged = [
|
|
2238
|
+
...entries,
|
|
2239
|
+
...(input.consoleLogs ?? []).map((log) => ({
|
|
2240
|
+
ts: log.timestamp,
|
|
2241
|
+
kind: "log",
|
|
2242
|
+
payload: {
|
|
2243
|
+
level: log.level,
|
|
2244
|
+
message: log.message
|
|
2245
|
+
}
|
|
2246
|
+
})),
|
|
2247
|
+
...(input.networkLogs ?? []).map((network) => ({
|
|
2248
|
+
ts: network.timestamp,
|
|
2249
|
+
kind: "request",
|
|
2250
|
+
payload: {
|
|
2251
|
+
method: network.method,
|
|
2252
|
+
url: network.url,
|
|
2253
|
+
status: network.status,
|
|
2254
|
+
duration: network.duration,
|
|
2255
|
+
error: network.error
|
|
2256
|
+
}
|
|
2257
|
+
}))
|
|
2258
|
+
].sort((a, b) => a.ts - b.ts);
|
|
2259
|
+
return merged.slice(-MAX_TIMELINE_ENTRIES);
|
|
2260
|
+
},
|
|
2261
|
+
clear() {
|
|
2262
|
+
entries.length = 0;
|
|
2263
|
+
},
|
|
2264
|
+
destroy() {
|
|
2265
|
+
history.pushState = originalPushState;
|
|
2266
|
+
history.replaceState = originalReplaceState;
|
|
2267
|
+
window.removeEventListener("popstate", handlePopState);
|
|
2268
|
+
window.removeEventListener("hashchange", handleHashChange);
|
|
2269
|
+
document.removeEventListener("click", handleClick, true);
|
|
2270
|
+
}
|
|
2271
|
+
};
|
|
2272
|
+
}
|
|
2273
|
+
function textSnippet(el) {
|
|
2274
|
+
const text = (el.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
2275
|
+
return text ? text.slice(0, 80) : void 0;
|
|
2276
|
+
}
|
|
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
|
+
|
|
1747
2466
|
// src/sentry.ts
|
|
1748
2467
|
function getSentryGlobal() {
|
|
1749
2468
|
try {
|
|
@@ -1830,36 +2549,25 @@ function setupProactiveTriggers(callbacks, config = {}) {
|
|
|
1830
2549
|
} catch {
|
|
1831
2550
|
}
|
|
1832
2551
|
}
|
|
1833
|
-
|
|
2552
|
+
const apiCascade = normalizeApiCascadeConfig(config.apiCascade);
|
|
2553
|
+
if (apiCascade.enabled) {
|
|
1834
2554
|
const failedRequests = [];
|
|
1835
2555
|
const origFetch = globalThis.fetch;
|
|
1836
2556
|
globalThis.fetch = async function(...args) {
|
|
2557
|
+
const [input, init] = args;
|
|
2558
|
+
const url = getRequestUrl(input);
|
|
2559
|
+
const ignoreFailure = Boolean(getInternalRequestKind(input, init)) || shouldIgnoreMushiUrl(url, {
|
|
2560
|
+
apiEndpoint: config.apiEndpoint,
|
|
2561
|
+
ignoreUrls: apiCascade.ignoreUrls
|
|
2562
|
+
});
|
|
1837
2563
|
try {
|
|
1838
2564
|
const res = await origFetch.apply(this, args);
|
|
1839
|
-
if (!res.ok && res.status >= 400) {
|
|
1840
|
-
|
|
1841
|
-
failedRequests.push(now);
|
|
1842
|
-
const recentFailures = failedRequests.filter((t) => now - t < 1e4);
|
|
1843
|
-
if (recentFailures.length >= 3) {
|
|
1844
|
-
callbacks.onTrigger("api_cascade", {
|
|
1845
|
-
failureCount: recentFailures.length,
|
|
1846
|
-
windowMs: 1e4
|
|
1847
|
-
});
|
|
1848
|
-
failedRequests.length = 0;
|
|
1849
|
-
}
|
|
2565
|
+
if (!ignoreFailure && !res.ok && res.status >= 400) {
|
|
2566
|
+
recordApiFailure(failedRequests, callbacks);
|
|
1850
2567
|
}
|
|
1851
2568
|
return res;
|
|
1852
2569
|
} catch (err) {
|
|
1853
|
-
|
|
1854
|
-
failedRequests.push(now);
|
|
1855
|
-
const recentFailures = failedRequests.filter((t) => now - t < 1e4);
|
|
1856
|
-
if (recentFailures.length >= 3) {
|
|
1857
|
-
callbacks.onTrigger("api_cascade", {
|
|
1858
|
-
failureCount: recentFailures.length,
|
|
1859
|
-
windowMs: 1e4
|
|
1860
|
-
});
|
|
1861
|
-
failedRequests.length = 0;
|
|
1862
|
-
}
|
|
2570
|
+
if (!ignoreFailure) recordApiFailure(failedRequests, callbacks);
|
|
1863
2571
|
throw err;
|
|
1864
2572
|
}
|
|
1865
2573
|
};
|
|
@@ -1894,6 +2602,28 @@ function setupProactiveTriggers(callbacks, config = {}) {
|
|
|
1894
2602
|
}
|
|
1895
2603
|
};
|
|
1896
2604
|
}
|
|
2605
|
+
function normalizeApiCascadeConfig(config) {
|
|
2606
|
+
if (config === false) return { enabled: false, ignoreUrls: [] };
|
|
2607
|
+
if (config && typeof config === "object") {
|
|
2608
|
+
return {
|
|
2609
|
+
enabled: config.enabled !== false,
|
|
2610
|
+
ignoreUrls: config.ignoreUrls ?? []
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2613
|
+
return { enabled: true, ignoreUrls: [] };
|
|
2614
|
+
}
|
|
2615
|
+
function recordApiFailure(failedRequests, callbacks) {
|
|
2616
|
+
const now = Date.now();
|
|
2617
|
+
failedRequests.push(now);
|
|
2618
|
+
const recentFailures = failedRequests.filter((t) => now - t < 1e4);
|
|
2619
|
+
if (recentFailures.length >= 3) {
|
|
2620
|
+
callbacks.onTrigger("api_cascade", {
|
|
2621
|
+
failureCount: recentFailures.length,
|
|
2622
|
+
windowMs: 1e4
|
|
2623
|
+
});
|
|
2624
|
+
failedRequests.length = 0;
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
1897
2627
|
|
|
1898
2628
|
// src/proactive-manager.ts
|
|
1899
2629
|
var STORAGE_KEY_LAST_DISMISS = "mushi:lastDismiss";
|
|
@@ -1946,6 +2676,10 @@ function createProactiveManager(config = {}) {
|
|
|
1946
2676
|
return { shouldShow, recordDismissal, recordSubmission, reset };
|
|
1947
2677
|
}
|
|
1948
2678
|
|
|
2679
|
+
// src/version.ts
|
|
2680
|
+
var MUSHI_SDK_PACKAGE = "@mushi-mushi/web";
|
|
2681
|
+
var MUSHI_SDK_VERSION = "0.9.0" ;
|
|
2682
|
+
|
|
1949
2683
|
// src/mushi.ts
|
|
1950
2684
|
var instance = null;
|
|
1951
2685
|
var Mushi = class {
|
|
@@ -1975,18 +2709,21 @@ var Mushi = class {
|
|
|
1975
2709
|
instance?.destroy();
|
|
1976
2710
|
instance = null;
|
|
1977
2711
|
}
|
|
2712
|
+
static diagnose() {
|
|
2713
|
+
return instance?.diagnose() ?? diagnoseWithoutInstance();
|
|
2714
|
+
}
|
|
1978
2715
|
};
|
|
1979
2716
|
function createInstance(config) {
|
|
1980
|
-
const bootstrapConfig = config;
|
|
1981
|
-
let activeConfig =
|
|
2717
|
+
const bootstrapConfig = applyPresetConfig(config);
|
|
2718
|
+
let activeConfig = bootstrapConfig;
|
|
1982
2719
|
const log = config.debug ?? false ? createLogger({ scope: "mushi", level: "debug", format: "pretty" }) : noopLogger;
|
|
1983
2720
|
const apiClient = createApiClient({
|
|
1984
|
-
projectId:
|
|
1985
|
-
apiKey:
|
|
1986
|
-
...
|
|
2721
|
+
projectId: bootstrapConfig.projectId,
|
|
2722
|
+
apiKey: bootstrapConfig.apiKey,
|
|
2723
|
+
...bootstrapConfig.apiEndpoint ? { apiEndpoint: bootstrapConfig.apiEndpoint } : {}
|
|
1987
2724
|
});
|
|
1988
|
-
const preFilter = createPreFilter(
|
|
1989
|
-
const offlineQueue = createOfflineQueue(
|
|
2725
|
+
const preFilter = createPreFilter(bootstrapConfig.preFilter);
|
|
2726
|
+
const offlineQueue = createOfflineQueue(bootstrapConfig.offline);
|
|
1990
2727
|
const rateLimiter = createRateLimiter({ maxBurst: 10, refillRate: 1, refillIntervalMs: 5e3 });
|
|
1991
2728
|
const piiScrubber = createPiiScrubber();
|
|
1992
2729
|
let consoleCap = null;
|
|
@@ -1994,6 +2731,9 @@ function createInstance(config) {
|
|
|
1994
2731
|
let perfCap = null;
|
|
1995
2732
|
let screenshotCap = null;
|
|
1996
2733
|
let elementSelector = null;
|
|
2734
|
+
let discoveryCap = null;
|
|
2735
|
+
const timelineCap = createTimelineCapture();
|
|
2736
|
+
let widget;
|
|
1997
2737
|
function syncCaptureModules() {
|
|
1998
2738
|
if (activeConfig.capture?.console !== false) {
|
|
1999
2739
|
consoleCap ??= createConsoleCapture();
|
|
@@ -2002,7 +2742,15 @@ function createInstance(config) {
|
|
|
2002
2742
|
consoleCap = null;
|
|
2003
2743
|
}
|
|
2004
2744
|
if (activeConfig.capture?.network !== false) {
|
|
2005
|
-
|
|
2745
|
+
const networkOptions = {
|
|
2746
|
+
apiEndpoint: resolveApiEndpoint(activeConfig),
|
|
2747
|
+
ignoreUrls: activeConfig.capture?.ignoreUrls
|
|
2748
|
+
};
|
|
2749
|
+
if (networkCap) {
|
|
2750
|
+
networkCap.updateOptions(networkOptions);
|
|
2751
|
+
} else {
|
|
2752
|
+
networkCap = createNetworkCapture(networkOptions);
|
|
2753
|
+
}
|
|
2006
2754
|
} else {
|
|
2007
2755
|
networkCap?.destroy();
|
|
2008
2756
|
networkCap = null;
|
|
@@ -2013,8 +2761,18 @@ function createInstance(config) {
|
|
|
2013
2761
|
perfCap?.destroy();
|
|
2014
2762
|
perfCap = null;
|
|
2015
2763
|
}
|
|
2016
|
-
|
|
2764
|
+
if (activeConfig.capture?.screenshot !== "off") {
|
|
2765
|
+
const screenshotOptions = { privacy: activeConfig.privacy };
|
|
2766
|
+
if (screenshotCap) {
|
|
2767
|
+
screenshotCap.updateOptions(screenshotOptions);
|
|
2768
|
+
} else {
|
|
2769
|
+
screenshotCap = createScreenshotCapture(screenshotOptions);
|
|
2770
|
+
}
|
|
2771
|
+
} else {
|
|
2772
|
+
screenshotCap = null;
|
|
2773
|
+
}
|
|
2017
2774
|
if (!screenshotCap) pendingScreenshot = null;
|
|
2775
|
+
widget.setAllowScreenshotRemove(activeConfig.privacy?.allowUserRemoveScreenshot !== false);
|
|
2018
2776
|
if (activeConfig.capture?.elementSelector !== false) {
|
|
2019
2777
|
elementSelector ??= createElementSelector();
|
|
2020
2778
|
} else {
|
|
@@ -2022,6 +2780,40 @@ function createInstance(config) {
|
|
|
2022
2780
|
elementSelector = null;
|
|
2023
2781
|
pendingElement = null;
|
|
2024
2782
|
}
|
|
2783
|
+
const discoveryRaw = activeConfig.capture?.discoverInventory;
|
|
2784
|
+
const discoveryConfig = discoveryRaw === true ? {} : discoveryRaw && typeof discoveryRaw === "object" ? discoveryRaw : null;
|
|
2785
|
+
const discoveryEnabled = discoveryConfig != null && discoveryConfig.enabled !== false;
|
|
2786
|
+
if (discoveryEnabled) {
|
|
2787
|
+
discoveryCap?.destroy();
|
|
2788
|
+
discoveryCap = createDiscoveryCapture({
|
|
2789
|
+
config: discoveryConfig,
|
|
2790
|
+
getRecentNetworkPaths: () => {
|
|
2791
|
+
if (!networkCap) return [];
|
|
2792
|
+
return networkCap.getEntries().map((e) => {
|
|
2793
|
+
try {
|
|
2794
|
+
const u = new URL(e.url, typeof window !== "undefined" ? window.location.href : "http://localhost");
|
|
2795
|
+
if (u.host && typeof window !== "undefined" && u.host !== window.location.host) return null;
|
|
2796
|
+
return u.pathname;
|
|
2797
|
+
} catch {
|
|
2798
|
+
return null;
|
|
2799
|
+
}
|
|
2800
|
+
}).filter((p) => p != null && p.length > 0 && p.length < 200);
|
|
2801
|
+
},
|
|
2802
|
+
getUserId: () => userInfo?.id ?? null,
|
|
2803
|
+
getSessionId,
|
|
2804
|
+
onEvent: (event) => {
|
|
2805
|
+
void apiClient.postDiscoveryEvent({
|
|
2806
|
+
...event,
|
|
2807
|
+
sdk_version: MUSHI_SDK_VERSION
|
|
2808
|
+
}).catch((err) => {
|
|
2809
|
+
log.debug("discovery emit failed", { err: String(err) });
|
|
2810
|
+
});
|
|
2811
|
+
}
|
|
2812
|
+
});
|
|
2813
|
+
} else {
|
|
2814
|
+
discoveryCap?.destroy();
|
|
2815
|
+
discoveryCap = null;
|
|
2816
|
+
}
|
|
2025
2817
|
}
|
|
2026
2818
|
const listeners = /* @__PURE__ */ new Map();
|
|
2027
2819
|
function emit(type, data) {
|
|
@@ -2030,10 +2822,10 @@ function createInstance(config) {
|
|
|
2030
2822
|
let pendingScreenshot = null;
|
|
2031
2823
|
let pendingElement = null;
|
|
2032
2824
|
let pendingProactiveTrigger = null;
|
|
2825
|
+
let runtimeConfigLoaded = false;
|
|
2033
2826
|
let userInfo = null;
|
|
2034
2827
|
const customMetadata = {};
|
|
2035
|
-
|
|
2036
|
-
const widget = new MushiWidget(config.widget, {
|
|
2828
|
+
widget = new MushiWidget(bootstrapConfig.widget, {
|
|
2037
2829
|
onSubmit: async ({ category, description, intent }) => {
|
|
2038
2830
|
log.info("Report submitted", { category, intent });
|
|
2039
2831
|
proactiveManager?.recordSubmission();
|
|
@@ -2060,6 +2852,11 @@ function createInstance(config) {
|
|
|
2060
2852
|
pendingScreenshot = await screenshotCap.take();
|
|
2061
2853
|
widget.setScreenshotAttached(pendingScreenshot !== null);
|
|
2062
2854
|
},
|
|
2855
|
+
onScreenshotRemove: () => {
|
|
2856
|
+
log.debug("Screenshot attachment removed");
|
|
2857
|
+
pendingScreenshot = null;
|
|
2858
|
+
widget.setScreenshotAttached(false);
|
|
2859
|
+
},
|
|
2063
2860
|
onElementSelectorRequest: async () => {
|
|
2064
2861
|
if (!elementSelector || activeConfig.capture?.elementSelector === false) return;
|
|
2065
2862
|
log.debug("Element selector activated");
|
|
@@ -2069,8 +2866,23 @@ function createInstance(config) {
|
|
|
2069
2866
|
widget.setElementSelected(true);
|
|
2070
2867
|
log.debug("Element selected", { tagName: el.tagName, xpath: el.xpath });
|
|
2071
2868
|
}
|
|
2869
|
+
},
|
|
2870
|
+
async onReporterReportsRequest() {
|
|
2871
|
+
const result = await apiClient.listReporterReports(getReporterToken());
|
|
2872
|
+
if (!result.ok) throw new Error(result.error?.message ?? "Could not load reports");
|
|
2873
|
+
return result.data?.reports ?? [];
|
|
2874
|
+
},
|
|
2875
|
+
async onReporterCommentsRequest(reportId) {
|
|
2876
|
+
const result = await apiClient.listReporterComments(reportId, getReporterToken());
|
|
2877
|
+
if (!result.ok) throw new Error(result.error?.message ?? "Could not load thread");
|
|
2878
|
+
return result.data?.comments ?? [];
|
|
2879
|
+
},
|
|
2880
|
+
async onReporterReply(reportId, body) {
|
|
2881
|
+
const result = await apiClient.replyToReporterReport(reportId, getReporterToken(), body);
|
|
2882
|
+
if (!result.ok) throw new Error(result.error?.message ?? "Could not send reply");
|
|
2072
2883
|
}
|
|
2073
|
-
});
|
|
2884
|
+
}, MUSHI_SDK_VERSION);
|
|
2885
|
+
syncCaptureModules();
|
|
2074
2886
|
if (typeof document !== "undefined") {
|
|
2075
2887
|
if (document.readyState === "loading") {
|
|
2076
2888
|
document.addEventListener("DOMContentLoaded", () => widget.mount());
|
|
@@ -2080,7 +2892,7 @@ function createInstance(config) {
|
|
|
2080
2892
|
}
|
|
2081
2893
|
let proactiveTriggers = null;
|
|
2082
2894
|
let proactiveManager = null;
|
|
2083
|
-
const proactiveCfg =
|
|
2895
|
+
const proactiveCfg = activeConfig.proactive;
|
|
2084
2896
|
const hasAnyProactive = proactiveCfg && (proactiveCfg.rageClick !== false || proactiveCfg.longTask !== false || proactiveCfg.apiCascade !== false || proactiveCfg.errorBoundary === true);
|
|
2085
2897
|
if (hasAnyProactive && typeof document !== "undefined") {
|
|
2086
2898
|
proactiveManager = createProactiveManager(proactiveCfg?.cooldown);
|
|
@@ -2101,6 +2913,7 @@ function createInstance(config) {
|
|
|
2101
2913
|
rageClick: proactiveCfg?.rageClick,
|
|
2102
2914
|
longTask: proactiveCfg?.longTask,
|
|
2103
2915
|
apiCascade: proactiveCfg?.apiCascade,
|
|
2916
|
+
apiEndpoint: resolveApiEndpoint(activeConfig),
|
|
2104
2917
|
errorBoundary: proactiveCfg?.errorBoundary
|
|
2105
2918
|
}
|
|
2106
2919
|
);
|
|
@@ -2116,6 +2929,7 @@ function createInstance(config) {
|
|
|
2116
2929
|
if (result.sent > 0) log.info("Synced offline reports", { sent: result.sent });
|
|
2117
2930
|
});
|
|
2118
2931
|
function applyRuntimeConfig(runtime) {
|
|
2932
|
+
runtimeConfigLoaded = true;
|
|
2119
2933
|
if (runtime.enabled === false) {
|
|
2120
2934
|
activeConfig = bootstrapConfig;
|
|
2121
2935
|
clearCachedRuntimeConfig(config.projectId);
|
|
@@ -2129,7 +2943,7 @@ function createInstance(config) {
|
|
|
2129
2943
|
if (runtime.widget) widget.updateConfig(activeConfig.widget);
|
|
2130
2944
|
log.debug("Applied runtime SDK config", { version: runtime.version });
|
|
2131
2945
|
}
|
|
2132
|
-
if (config
|
|
2946
|
+
if (shouldUseRuntimeConfig(config)) {
|
|
2133
2947
|
const cached = readCachedRuntimeConfig(config.projectId);
|
|
2134
2948
|
if (cached) applyRuntimeConfig(cached);
|
|
2135
2949
|
apiClient.getSdkConfig().then((result) => {
|
|
@@ -2142,8 +2956,41 @@ function createInstance(config) {
|
|
|
2142
2956
|
}).catch((err) => {
|
|
2143
2957
|
log.debug("Runtime SDK config fetch failed", { error: err instanceof Error ? err.message : String(err) });
|
|
2144
2958
|
});
|
|
2959
|
+
} else if (config.runtimeConfig !== false && isLocalhostEndpoint(resolveApiEndpoint(config))) {
|
|
2960
|
+
log.debug("Runtime SDK config skipped for localhost apiEndpoint; set runtimeConfig: true to force it");
|
|
2145
2961
|
}
|
|
2962
|
+
void checkSdkFreshness();
|
|
2146
2963
|
log.info("Initialized", { projectId: config.projectId });
|
|
2964
|
+
async function checkSdkFreshness() {
|
|
2965
|
+
if (activeConfig.widget?.outdatedBanner === "off") return;
|
|
2966
|
+
const cached = readCachedSdkVersion(MUSHI_SDK_PACKAGE);
|
|
2967
|
+
if (cached) applySdkFreshness(cached);
|
|
2968
|
+
const result = await apiClient.getLatestSdkVersion(MUSHI_SDK_PACKAGE);
|
|
2969
|
+
if (!result.ok || !result.data) return;
|
|
2970
|
+
cacheSdkVersion(MUSHI_SDK_PACKAGE, result.data);
|
|
2971
|
+
applySdkFreshness(result.data);
|
|
2972
|
+
}
|
|
2973
|
+
function applySdkFreshness(info) {
|
|
2974
|
+
const latest = info.latest;
|
|
2975
|
+
const outdated = Boolean(latest && isVersionOlder(MUSHI_SDK_VERSION, latest));
|
|
2976
|
+
if (!outdated && !info.deprecated) return;
|
|
2977
|
+
const message = info.deprecationMessage ?? (outdated ? `Update ${MUSHI_SDK_PACKAGE} to ${latest}.` : null);
|
|
2978
|
+
log.warn("Mushi SDK is outdated", {
|
|
2979
|
+
package: MUSHI_SDK_PACKAGE,
|
|
2980
|
+
current: MUSHI_SDK_VERSION,
|
|
2981
|
+
latest,
|
|
2982
|
+
deprecated: info.deprecated,
|
|
2983
|
+
message
|
|
2984
|
+
});
|
|
2985
|
+
if (activeConfig.widget?.outdatedBanner !== "console-only") {
|
|
2986
|
+
widget.setSdkFreshness({
|
|
2987
|
+
latest,
|
|
2988
|
+
current: MUSHI_SDK_VERSION,
|
|
2989
|
+
deprecated: info.deprecated,
|
|
2990
|
+
message
|
|
2991
|
+
});
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2147
2994
|
async function submitReport(category, description, intent) {
|
|
2148
2995
|
const filterResult = preFilter.check(description);
|
|
2149
2996
|
if (!filterResult.passed) {
|
|
@@ -2185,6 +3032,8 @@ function createInstance(config) {
|
|
|
2185
3032
|
const scrubbedDescription = piiScrubber.scrub(preFilter.truncate(description));
|
|
2186
3033
|
const sentryCtx = config.sentry ? captureSentryContext(config.sentry) : void 0;
|
|
2187
3034
|
const fingerprintHash = await getDeviceFingerprintHash().catch(() => null);
|
|
3035
|
+
const consoleLogs = activeConfig.capture?.console === false ? void 0 : consoleCap?.getEntries();
|
|
3036
|
+
const networkLogs = activeConfig.capture?.network === false ? void 0 : networkCap?.getEntries();
|
|
2188
3037
|
const report = {
|
|
2189
3038
|
id: crypto.randomUUID?.() ?? `mushi_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
2190
3039
|
projectId: config.projectId,
|
|
@@ -2192,9 +3041,10 @@ function createInstance(config) {
|
|
|
2192
3041
|
description: scrubbedDescription,
|
|
2193
3042
|
userIntent: intent,
|
|
2194
3043
|
environment: captureEnvironment(),
|
|
2195
|
-
consoleLogs
|
|
2196
|
-
networkLogs
|
|
3044
|
+
consoleLogs,
|
|
3045
|
+
networkLogs,
|
|
2197
3046
|
performanceMetrics: activeConfig.capture?.performance === false ? void 0 : perfCap?.getMetrics(),
|
|
3047
|
+
timeline: timelineCap.getEntries({ consoleLogs, networkLogs }),
|
|
2198
3048
|
screenshotDataUrl: pendingScreenshot ?? void 0,
|
|
2199
3049
|
selectedElement: pendingElement ?? void 0,
|
|
2200
3050
|
metadata: {
|
|
@@ -2206,6 +3056,8 @@ function createInstance(config) {
|
|
|
2206
3056
|
reporterToken: getReporterToken(),
|
|
2207
3057
|
...fingerprintHash ? { fingerprintHash } : {},
|
|
2208
3058
|
appVersion: config.integrations?.vercel?.analyticsId,
|
|
3059
|
+
sdkPackage: MUSHI_SDK_PACKAGE,
|
|
3060
|
+
sdkVersion: MUSHI_SDK_VERSION,
|
|
2209
3061
|
proactiveTrigger: pendingProactiveTrigger ?? void 0,
|
|
2210
3062
|
sentryEventId: sentryCtx?.eventId,
|
|
2211
3063
|
sentryReplayId: sentryCtx?.replayId,
|
|
@@ -2260,6 +3112,9 @@ function createInstance(config) {
|
|
|
2260
3112
|
setMetadata(key, value) {
|
|
2261
3113
|
customMetadata[key] = value;
|
|
2262
3114
|
},
|
|
3115
|
+
setScreen(screen) {
|
|
3116
|
+
timelineCap.setScreen(screen);
|
|
3117
|
+
},
|
|
2263
3118
|
isOpen() {
|
|
2264
3119
|
return widget.getIsOpen();
|
|
2265
3120
|
},
|
|
@@ -2287,6 +3142,15 @@ function createInstance(config) {
|
|
|
2287
3142
|
updateConfig(runtimeConfig) {
|
|
2288
3143
|
applyRuntimeConfig(runtimeConfig);
|
|
2289
3144
|
},
|
|
3145
|
+
diagnose() {
|
|
3146
|
+
return runDiagnostics({
|
|
3147
|
+
apiEndpoint: resolveApiEndpoint(activeConfig),
|
|
3148
|
+
widgetMounted: widget.getIsMounted(),
|
|
3149
|
+
runtimeConfigLoaded,
|
|
3150
|
+
captureScreenshotAvailable: screenshotCap !== null,
|
|
3151
|
+
captureNetworkIntercepting: networkCap !== null
|
|
3152
|
+
});
|
|
3153
|
+
},
|
|
2290
3154
|
destroy() {
|
|
2291
3155
|
proactiveTriggers?.destroy();
|
|
2292
3156
|
proactiveManager?.reset();
|
|
@@ -2295,6 +3159,9 @@ function createInstance(config) {
|
|
|
2295
3159
|
networkCap?.destroy();
|
|
2296
3160
|
perfCap?.destroy();
|
|
2297
3161
|
elementSelector?.deactivate();
|
|
3162
|
+
timelineCap.destroy();
|
|
3163
|
+
discoveryCap?.destroy();
|
|
3164
|
+
discoveryCap = null;
|
|
2298
3165
|
offlineQueue.stopAutoSync();
|
|
2299
3166
|
listeners.clear();
|
|
2300
3167
|
instance = null;
|
|
@@ -2316,6 +3183,7 @@ function createInstance(config) {
|
|
|
2316
3183
|
category,
|
|
2317
3184
|
description,
|
|
2318
3185
|
environment: captureEnvironment(),
|
|
3186
|
+
timeline: timelineCap.getEntries(),
|
|
2319
3187
|
metadata: {
|
|
2320
3188
|
...input.metadata ?? {},
|
|
2321
3189
|
...userInfo ? { user: userInfo } : {},
|
|
@@ -2327,6 +3195,8 @@ function createInstance(config) {
|
|
|
2327
3195
|
},
|
|
2328
3196
|
sessionId: getSessionId(),
|
|
2329
3197
|
reporterToken: getReporterToken(),
|
|
3198
|
+
sdkPackage: MUSHI_SDK_PACKAGE,
|
|
3199
|
+
sdkVersion: MUSHI_SDK_VERSION,
|
|
2330
3200
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2331
3201
|
};
|
|
2332
3202
|
emit("report:submitted", { reportId: report.id });
|
|
@@ -2368,12 +3238,128 @@ function mergeRuntimeConfig(config, runtime) {
|
|
|
2368
3238
|
capture: {
|
|
2369
3239
|
...config.capture,
|
|
2370
3240
|
...runtime.capture
|
|
3241
|
+
},
|
|
3242
|
+
privacy: {
|
|
3243
|
+
...config.privacy
|
|
2371
3244
|
}
|
|
2372
3245
|
};
|
|
2373
3246
|
}
|
|
3247
|
+
function applyPresetConfig(config) {
|
|
3248
|
+
if (!config.preset) return config;
|
|
3249
|
+
const preset = presetDefaults(config.preset);
|
|
3250
|
+
return {
|
|
3251
|
+
...config,
|
|
3252
|
+
widget: {
|
|
3253
|
+
...preset.widget,
|
|
3254
|
+
...config.widget
|
|
3255
|
+
},
|
|
3256
|
+
capture: {
|
|
3257
|
+
...preset.capture,
|
|
3258
|
+
...config.capture
|
|
3259
|
+
},
|
|
3260
|
+
proactive: {
|
|
3261
|
+
...preset.proactive,
|
|
3262
|
+
...config.proactive,
|
|
3263
|
+
cooldown: {
|
|
3264
|
+
...preset.proactive?.cooldown,
|
|
3265
|
+
...config.proactive?.cooldown
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
};
|
|
3269
|
+
}
|
|
3270
|
+
function presetDefaults(preset) {
|
|
3271
|
+
switch (preset) {
|
|
3272
|
+
case "manual-only":
|
|
3273
|
+
return {
|
|
3274
|
+
widget: { trigger: "manual", outdatedBanner: "console-only" },
|
|
3275
|
+
capture: { console: true, network: true, performance: false, screenshot: "on-report", elementSelector: false },
|
|
3276
|
+
proactive: { rageClick: false, longTask: false, apiCascade: false, errorBoundary: false }
|
|
3277
|
+
};
|
|
3278
|
+
case "beta-loud":
|
|
3279
|
+
return {
|
|
3280
|
+
widget: { trigger: "auto", outdatedBanner: "banner" },
|
|
3281
|
+
capture: { console: true, network: true, performance: true, screenshot: "auto", elementSelector: true },
|
|
3282
|
+
proactive: { rageClick: true, longTask: true, apiCascade: true, errorBoundary: true }
|
|
3283
|
+
};
|
|
3284
|
+
case "internal-debug":
|
|
3285
|
+
return {
|
|
3286
|
+
widget: { trigger: "auto", outdatedBanner: "banner", brandFooter: true },
|
|
3287
|
+
capture: { console: true, network: true, performance: true, screenshot: "auto", elementSelector: true },
|
|
3288
|
+
proactive: {
|
|
3289
|
+
rageClick: true,
|
|
3290
|
+
longTask: true,
|
|
3291
|
+
apiCascade: true,
|
|
3292
|
+
errorBoundary: true,
|
|
3293
|
+
cooldown: { maxProactivePerSession: 10, dismissCooldownHours: 0, suppressAfterDismissals: 99 }
|
|
3294
|
+
}
|
|
3295
|
+
};
|
|
3296
|
+
case "production-calm":
|
|
3297
|
+
return {
|
|
3298
|
+
widget: { trigger: "auto", outdatedBanner: "console-only" },
|
|
3299
|
+
capture: { console: true, network: true, performance: false, screenshot: "on-report", elementSelector: false },
|
|
3300
|
+
proactive: { rageClick: false, longTask: false, apiCascade: false, errorBoundary: false }
|
|
3301
|
+
};
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
function resolveApiEndpoint(config) {
|
|
3305
|
+
return config.apiEndpoint ?? DEFAULT_API_ENDPOINT;
|
|
3306
|
+
}
|
|
3307
|
+
function shouldUseRuntimeConfig(config) {
|
|
3308
|
+
if (config.runtimeConfig === false) return false;
|
|
3309
|
+
if (config.runtimeConfig === true) return true;
|
|
3310
|
+
return !isLocalhostEndpoint(resolveApiEndpoint(config));
|
|
3311
|
+
}
|
|
3312
|
+
async function runDiagnostics(options) {
|
|
3313
|
+
const endpoint = await probeApiEndpoint(options.apiEndpoint);
|
|
3314
|
+
return {
|
|
3315
|
+
apiEndpointReachable: endpoint.reachable,
|
|
3316
|
+
cspAllowsEndpoint: endpoint.cspAllowed,
|
|
3317
|
+
widgetMounted: options.widgetMounted,
|
|
3318
|
+
shadowDomAvailable: typeof HTMLElement !== "undefined" && typeof HTMLElement.prototype.attachShadow === "function",
|
|
3319
|
+
dialogSupported: typeof HTMLDialogElement !== "undefined",
|
|
3320
|
+
runtimeConfigLoaded: options.runtimeConfigLoaded,
|
|
3321
|
+
captureScreenshotAvailable: options.captureScreenshotAvailable,
|
|
3322
|
+
captureNetworkIntercepting: options.captureNetworkIntercepting,
|
|
3323
|
+
sdkVersion: MUSHI_SDK_VERSION
|
|
3324
|
+
};
|
|
3325
|
+
}
|
|
3326
|
+
async function diagnoseWithoutInstance() {
|
|
3327
|
+
return {
|
|
3328
|
+
apiEndpointReachable: false,
|
|
3329
|
+
cspAllowsEndpoint: false,
|
|
3330
|
+
widgetMounted: false,
|
|
3331
|
+
shadowDomAvailable: typeof HTMLElement !== "undefined" && typeof HTMLElement.prototype.attachShadow === "function",
|
|
3332
|
+
dialogSupported: typeof HTMLDialogElement !== "undefined",
|
|
3333
|
+
runtimeConfigLoaded: false,
|
|
3334
|
+
captureScreenshotAvailable: false,
|
|
3335
|
+
captureNetworkIntercepting: false,
|
|
3336
|
+
sdkVersion: MUSHI_SDK_VERSION
|
|
3337
|
+
};
|
|
3338
|
+
}
|
|
3339
|
+
async function probeApiEndpoint(apiEndpoint) {
|
|
3340
|
+
if (typeof fetch === "undefined") return { reachable: false, cspAllowed: false };
|
|
3341
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
3342
|
+
const timer = controller ? setTimeout(() => controller.abort(), 3e3) : null;
|
|
3343
|
+
try {
|
|
3344
|
+
const response = await fetch(`${apiEndpoint.replace(/\/$/, "")}/health`, {
|
|
3345
|
+
method: "GET",
|
|
3346
|
+
cache: "no-store",
|
|
3347
|
+
...controller ? { signal: controller.signal } : {},
|
|
3348
|
+
[MUSHI_INTERNAL_INIT_MARKER]: "diagnose"
|
|
3349
|
+
});
|
|
3350
|
+
return { reachable: response.ok, cspAllowed: true };
|
|
3351
|
+
} catch {
|
|
3352
|
+
return { reachable: false, cspAllowed: false };
|
|
3353
|
+
} finally {
|
|
3354
|
+
if (timer) clearTimeout(timer);
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
2374
3357
|
function runtimeConfigCacheKey(projectId) {
|
|
2375
3358
|
return `mushi:sdk-config:${projectId}`;
|
|
2376
3359
|
}
|
|
3360
|
+
function sdkVersionCacheKey(packageName) {
|
|
3361
|
+
return `mushi:sdk-version:${packageName}`;
|
|
3362
|
+
}
|
|
2377
3363
|
function readCachedRuntimeConfig(projectId) {
|
|
2378
3364
|
if (typeof localStorage === "undefined") return null;
|
|
2379
3365
|
try {
|
|
@@ -2402,6 +3388,42 @@ function clearCachedRuntimeConfig(projectId) {
|
|
|
2402
3388
|
} catch {
|
|
2403
3389
|
}
|
|
2404
3390
|
}
|
|
3391
|
+
function readCachedSdkVersion(packageName) {
|
|
3392
|
+
if (typeof localStorage === "undefined") return null;
|
|
3393
|
+
try {
|
|
3394
|
+
const raw = localStorage.getItem(sdkVersionCacheKey(packageName));
|
|
3395
|
+
if (!raw) return null;
|
|
3396
|
+
const parsed = JSON.parse(raw);
|
|
3397
|
+
if (!parsed.data || !parsed.cachedAt || Date.now() - parsed.cachedAt > 864e5) return null;
|
|
3398
|
+
return parsed.data;
|
|
3399
|
+
} catch {
|
|
3400
|
+
return null;
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
function cacheSdkVersion(packageName, data) {
|
|
3404
|
+
if (typeof localStorage === "undefined") return;
|
|
3405
|
+
try {
|
|
3406
|
+
localStorage.setItem(sdkVersionCacheKey(packageName), JSON.stringify({
|
|
3407
|
+
cachedAt: Date.now(),
|
|
3408
|
+
data
|
|
3409
|
+
}));
|
|
3410
|
+
} catch {
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
function isVersionOlder(current, latest) {
|
|
3414
|
+
const currentParts = parseVersion(current);
|
|
3415
|
+
const latestParts = parseVersion(latest);
|
|
3416
|
+
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
|
|
3417
|
+
const cur = currentParts[i] ?? 0;
|
|
3418
|
+
const next = latestParts[i] ?? 0;
|
|
3419
|
+
if (cur < next) return true;
|
|
3420
|
+
if (cur > next) return false;
|
|
3421
|
+
}
|
|
3422
|
+
return false;
|
|
3423
|
+
}
|
|
3424
|
+
function parseVersion(version) {
|
|
3425
|
+
return version.split(/[.-]/).map((part) => Number.parseInt(part, 10)).filter((part) => Number.isFinite(part));
|
|
3426
|
+
}
|
|
2405
3427
|
function createNoopInstance() {
|
|
2406
3428
|
return {
|
|
2407
3429
|
report: () => {
|
|
@@ -2412,6 +3434,8 @@ function createNoopInstance() {
|
|
|
2412
3434
|
},
|
|
2413
3435
|
setMetadata: () => {
|
|
2414
3436
|
},
|
|
3437
|
+
setScreen: () => {
|
|
3438
|
+
},
|
|
2415
3439
|
isOpen: () => false,
|
|
2416
3440
|
open: () => {
|
|
2417
3441
|
},
|
|
@@ -2419,6 +3443,7 @@ function createNoopInstance() {
|
|
|
2419
3443
|
},
|
|
2420
3444
|
updateConfig: () => {
|
|
2421
3445
|
},
|
|
3446
|
+
diagnose: diagnoseWithoutInstance,
|
|
2422
3447
|
openWith: () => {
|
|
2423
3448
|
},
|
|
2424
3449
|
show: () => {
|
|
@@ -2438,6 +3463,6 @@ function createNoopInstance() {
|
|
|
2438
3463
|
};
|
|
2439
3464
|
}
|
|
2440
3465
|
|
|
2441
|
-
export { Mushi, MushiWidget, createConsoleCapture, createElementSelector, createNetworkCapture, createPerformanceCapture, createProactiveManager, createScreenshotCapture, getAvailableLocales, getLocale, setupProactiveTriggers };
|
|
3466
|
+
export { Mushi, MushiWidget, createConsoleCapture, createElementSelector, createNetworkCapture, createPerformanceCapture, createProactiveManager, createScreenshotCapture, createTimelineCapture, getAvailableLocales, getLocale, setupProactiveTriggers };
|
|
2442
3467
|
//# sourceMappingURL=index.js.map
|
|
2443
3468
|
//# sourceMappingURL=index.js.map
|