@mushi-mushi/web 0.7.0 → 0.8.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 +128 -5
- package/dist/index.cjs +876 -92
- 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 +877 -94
- 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
|
|
@@ -1744,6 +2156,108 @@ function createElementSelector() {
|
|
|
1744
2156
|
return { activate, deactivate, isActive: () => active };
|
|
1745
2157
|
}
|
|
1746
2158
|
|
|
2159
|
+
// src/capture/timeline.ts
|
|
2160
|
+
var MAX_TIMELINE_ENTRIES = 120;
|
|
2161
|
+
function createTimelineCapture() {
|
|
2162
|
+
const entries = [];
|
|
2163
|
+
const originalPushState = history.pushState;
|
|
2164
|
+
const originalReplaceState = history.replaceState;
|
|
2165
|
+
const handlePopState = () => recordRoute("popstate");
|
|
2166
|
+
const handleHashChange = () => recordRoute("hashchange");
|
|
2167
|
+
recordRoute("initial");
|
|
2168
|
+
function record(entry) {
|
|
2169
|
+
entries.push(entry);
|
|
2170
|
+
if (entries.length > MAX_TIMELINE_ENTRIES) entries.shift();
|
|
2171
|
+
}
|
|
2172
|
+
function recordRoute(source) {
|
|
2173
|
+
if (typeof location === "undefined") return;
|
|
2174
|
+
record({
|
|
2175
|
+
ts: Date.now(),
|
|
2176
|
+
kind: "route",
|
|
2177
|
+
payload: {
|
|
2178
|
+
source,
|
|
2179
|
+
route: `${location.pathname}${location.search}${location.hash}`,
|
|
2180
|
+
href: location.href
|
|
2181
|
+
}
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
2184
|
+
function handleClick(event) {
|
|
2185
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
2186
|
+
if (!target) return;
|
|
2187
|
+
const el = target.closest('button,a,[role="button"],input,textarea,select,[data-mushi-track]') ?? target;
|
|
2188
|
+
record({
|
|
2189
|
+
ts: Date.now(),
|
|
2190
|
+
kind: "click",
|
|
2191
|
+
payload: {
|
|
2192
|
+
tag: el.tagName.toLowerCase(),
|
|
2193
|
+
id: el.id || void 0,
|
|
2194
|
+
text: textSnippet(el)
|
|
2195
|
+
}
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
2198
|
+
history.pushState = function mushiPushState(...args) {
|
|
2199
|
+
const result = originalPushState.apply(this, args);
|
|
2200
|
+
recordRoute("pushState");
|
|
2201
|
+
return result;
|
|
2202
|
+
};
|
|
2203
|
+
history.replaceState = function mushiReplaceState(...args) {
|
|
2204
|
+
const result = originalReplaceState.apply(this, args);
|
|
2205
|
+
recordRoute("replaceState");
|
|
2206
|
+
return result;
|
|
2207
|
+
};
|
|
2208
|
+
window.addEventListener("popstate", handlePopState);
|
|
2209
|
+
window.addEventListener("hashchange", handleHashChange);
|
|
2210
|
+
document.addEventListener("click", handleClick, true);
|
|
2211
|
+
return {
|
|
2212
|
+
setScreen(screen) {
|
|
2213
|
+
record({
|
|
2214
|
+
ts: Date.now(),
|
|
2215
|
+
kind: "screen",
|
|
2216
|
+
payload: screen
|
|
2217
|
+
});
|
|
2218
|
+
},
|
|
2219
|
+
getEntries(input = {}) {
|
|
2220
|
+
const merged = [
|
|
2221
|
+
...entries,
|
|
2222
|
+
...(input.consoleLogs ?? []).map((log) => ({
|
|
2223
|
+
ts: log.timestamp,
|
|
2224
|
+
kind: "log",
|
|
2225
|
+
payload: {
|
|
2226
|
+
level: log.level,
|
|
2227
|
+
message: log.message
|
|
2228
|
+
}
|
|
2229
|
+
})),
|
|
2230
|
+
...(input.networkLogs ?? []).map((network) => ({
|
|
2231
|
+
ts: network.timestamp,
|
|
2232
|
+
kind: "request",
|
|
2233
|
+
payload: {
|
|
2234
|
+
method: network.method,
|
|
2235
|
+
url: network.url,
|
|
2236
|
+
status: network.status,
|
|
2237
|
+
duration: network.duration,
|
|
2238
|
+
error: network.error
|
|
2239
|
+
}
|
|
2240
|
+
}))
|
|
2241
|
+
].sort((a, b) => a.ts - b.ts);
|
|
2242
|
+
return merged.slice(-MAX_TIMELINE_ENTRIES);
|
|
2243
|
+
},
|
|
2244
|
+
clear() {
|
|
2245
|
+
entries.length = 0;
|
|
2246
|
+
},
|
|
2247
|
+
destroy() {
|
|
2248
|
+
history.pushState = originalPushState;
|
|
2249
|
+
history.replaceState = originalReplaceState;
|
|
2250
|
+
window.removeEventListener("popstate", handlePopState);
|
|
2251
|
+
window.removeEventListener("hashchange", handleHashChange);
|
|
2252
|
+
document.removeEventListener("click", handleClick, true);
|
|
2253
|
+
}
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
function textSnippet(el) {
|
|
2257
|
+
const text = (el.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
2258
|
+
return text ? text.slice(0, 80) : void 0;
|
|
2259
|
+
}
|
|
2260
|
+
|
|
1747
2261
|
// src/sentry.ts
|
|
1748
2262
|
function getSentryGlobal() {
|
|
1749
2263
|
try {
|
|
@@ -1830,36 +2344,25 @@ function setupProactiveTriggers(callbacks, config = {}) {
|
|
|
1830
2344
|
} catch {
|
|
1831
2345
|
}
|
|
1832
2346
|
}
|
|
1833
|
-
|
|
2347
|
+
const apiCascade = normalizeApiCascadeConfig(config.apiCascade);
|
|
2348
|
+
if (apiCascade.enabled) {
|
|
1834
2349
|
const failedRequests = [];
|
|
1835
2350
|
const origFetch = globalThis.fetch;
|
|
1836
2351
|
globalThis.fetch = async function(...args) {
|
|
2352
|
+
const [input, init] = args;
|
|
2353
|
+
const url = getRequestUrl(input);
|
|
2354
|
+
const ignoreFailure = Boolean(getInternalRequestKind(input, init)) || shouldIgnoreMushiUrl(url, {
|
|
2355
|
+
apiEndpoint: config.apiEndpoint,
|
|
2356
|
+
ignoreUrls: apiCascade.ignoreUrls
|
|
2357
|
+
});
|
|
1837
2358
|
try {
|
|
1838
2359
|
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
|
-
}
|
|
2360
|
+
if (!ignoreFailure && !res.ok && res.status >= 400) {
|
|
2361
|
+
recordApiFailure(failedRequests, callbacks);
|
|
1850
2362
|
}
|
|
1851
2363
|
return res;
|
|
1852
2364
|
} 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
|
-
}
|
|
2365
|
+
if (!ignoreFailure) recordApiFailure(failedRequests, callbacks);
|
|
1863
2366
|
throw err;
|
|
1864
2367
|
}
|
|
1865
2368
|
};
|
|
@@ -1894,6 +2397,28 @@ function setupProactiveTriggers(callbacks, config = {}) {
|
|
|
1894
2397
|
}
|
|
1895
2398
|
};
|
|
1896
2399
|
}
|
|
2400
|
+
function normalizeApiCascadeConfig(config) {
|
|
2401
|
+
if (config === false) return { enabled: false, ignoreUrls: [] };
|
|
2402
|
+
if (config && typeof config === "object") {
|
|
2403
|
+
return {
|
|
2404
|
+
enabled: config.enabled !== false,
|
|
2405
|
+
ignoreUrls: config.ignoreUrls ?? []
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
return { enabled: true, ignoreUrls: [] };
|
|
2409
|
+
}
|
|
2410
|
+
function recordApiFailure(failedRequests, callbacks) {
|
|
2411
|
+
const now = Date.now();
|
|
2412
|
+
failedRequests.push(now);
|
|
2413
|
+
const recentFailures = failedRequests.filter((t) => now - t < 1e4);
|
|
2414
|
+
if (recentFailures.length >= 3) {
|
|
2415
|
+
callbacks.onTrigger("api_cascade", {
|
|
2416
|
+
failureCount: recentFailures.length,
|
|
2417
|
+
windowMs: 1e4
|
|
2418
|
+
});
|
|
2419
|
+
failedRequests.length = 0;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
1897
2422
|
|
|
1898
2423
|
// src/proactive-manager.ts
|
|
1899
2424
|
var STORAGE_KEY_LAST_DISMISS = "mushi:lastDismiss";
|
|
@@ -1946,6 +2471,10 @@ function createProactiveManager(config = {}) {
|
|
|
1946
2471
|
return { shouldShow, recordDismissal, recordSubmission, reset };
|
|
1947
2472
|
}
|
|
1948
2473
|
|
|
2474
|
+
// src/version.ts
|
|
2475
|
+
var MUSHI_SDK_PACKAGE = "@mushi-mushi/web";
|
|
2476
|
+
var MUSHI_SDK_VERSION = "0.8.0" ;
|
|
2477
|
+
|
|
1949
2478
|
// src/mushi.ts
|
|
1950
2479
|
var instance = null;
|
|
1951
2480
|
var Mushi = class {
|
|
@@ -1975,18 +2504,21 @@ var Mushi = class {
|
|
|
1975
2504
|
instance?.destroy();
|
|
1976
2505
|
instance = null;
|
|
1977
2506
|
}
|
|
2507
|
+
static diagnose() {
|
|
2508
|
+
return instance?.diagnose() ?? diagnoseWithoutInstance();
|
|
2509
|
+
}
|
|
1978
2510
|
};
|
|
1979
2511
|
function createInstance(config) {
|
|
1980
|
-
const bootstrapConfig = config;
|
|
1981
|
-
let activeConfig =
|
|
2512
|
+
const bootstrapConfig = applyPresetConfig(config);
|
|
2513
|
+
let activeConfig = bootstrapConfig;
|
|
1982
2514
|
const log = config.debug ?? false ? createLogger({ scope: "mushi", level: "debug", format: "pretty" }) : noopLogger;
|
|
1983
2515
|
const apiClient = createApiClient({
|
|
1984
|
-
projectId:
|
|
1985
|
-
apiKey:
|
|
1986
|
-
...
|
|
2516
|
+
projectId: bootstrapConfig.projectId,
|
|
2517
|
+
apiKey: bootstrapConfig.apiKey,
|
|
2518
|
+
...bootstrapConfig.apiEndpoint ? { apiEndpoint: bootstrapConfig.apiEndpoint } : {}
|
|
1987
2519
|
});
|
|
1988
|
-
const preFilter = createPreFilter(
|
|
1989
|
-
const offlineQueue = createOfflineQueue(
|
|
2520
|
+
const preFilter = createPreFilter(bootstrapConfig.preFilter);
|
|
2521
|
+
const offlineQueue = createOfflineQueue(bootstrapConfig.offline);
|
|
1990
2522
|
const rateLimiter = createRateLimiter({ maxBurst: 10, refillRate: 1, refillIntervalMs: 5e3 });
|
|
1991
2523
|
const piiScrubber = createPiiScrubber();
|
|
1992
2524
|
let consoleCap = null;
|
|
@@ -1994,6 +2526,8 @@ function createInstance(config) {
|
|
|
1994
2526
|
let perfCap = null;
|
|
1995
2527
|
let screenshotCap = null;
|
|
1996
2528
|
let elementSelector = null;
|
|
2529
|
+
const timelineCap = createTimelineCapture();
|
|
2530
|
+
let widget;
|
|
1997
2531
|
function syncCaptureModules() {
|
|
1998
2532
|
if (activeConfig.capture?.console !== false) {
|
|
1999
2533
|
consoleCap ??= createConsoleCapture();
|
|
@@ -2002,7 +2536,15 @@ function createInstance(config) {
|
|
|
2002
2536
|
consoleCap = null;
|
|
2003
2537
|
}
|
|
2004
2538
|
if (activeConfig.capture?.network !== false) {
|
|
2005
|
-
|
|
2539
|
+
const networkOptions = {
|
|
2540
|
+
apiEndpoint: resolveApiEndpoint(activeConfig),
|
|
2541
|
+
ignoreUrls: activeConfig.capture?.ignoreUrls
|
|
2542
|
+
};
|
|
2543
|
+
if (networkCap) {
|
|
2544
|
+
networkCap.updateOptions(networkOptions);
|
|
2545
|
+
} else {
|
|
2546
|
+
networkCap = createNetworkCapture(networkOptions);
|
|
2547
|
+
}
|
|
2006
2548
|
} else {
|
|
2007
2549
|
networkCap?.destroy();
|
|
2008
2550
|
networkCap = null;
|
|
@@ -2013,8 +2555,18 @@ function createInstance(config) {
|
|
|
2013
2555
|
perfCap?.destroy();
|
|
2014
2556
|
perfCap = null;
|
|
2015
2557
|
}
|
|
2016
|
-
|
|
2558
|
+
if (activeConfig.capture?.screenshot !== "off") {
|
|
2559
|
+
const screenshotOptions = { privacy: activeConfig.privacy };
|
|
2560
|
+
if (screenshotCap) {
|
|
2561
|
+
screenshotCap.updateOptions(screenshotOptions);
|
|
2562
|
+
} else {
|
|
2563
|
+
screenshotCap = createScreenshotCapture(screenshotOptions);
|
|
2564
|
+
}
|
|
2565
|
+
} else {
|
|
2566
|
+
screenshotCap = null;
|
|
2567
|
+
}
|
|
2017
2568
|
if (!screenshotCap) pendingScreenshot = null;
|
|
2569
|
+
widget.setAllowScreenshotRemove(activeConfig.privacy?.allowUserRemoveScreenshot !== false);
|
|
2018
2570
|
if (activeConfig.capture?.elementSelector !== false) {
|
|
2019
2571
|
elementSelector ??= createElementSelector();
|
|
2020
2572
|
} else {
|
|
@@ -2030,10 +2582,10 @@ function createInstance(config) {
|
|
|
2030
2582
|
let pendingScreenshot = null;
|
|
2031
2583
|
let pendingElement = null;
|
|
2032
2584
|
let pendingProactiveTrigger = null;
|
|
2585
|
+
let runtimeConfigLoaded = false;
|
|
2033
2586
|
let userInfo = null;
|
|
2034
2587
|
const customMetadata = {};
|
|
2035
|
-
|
|
2036
|
-
const widget = new MushiWidget(config.widget, {
|
|
2588
|
+
widget = new MushiWidget(bootstrapConfig.widget, {
|
|
2037
2589
|
onSubmit: async ({ category, description, intent }) => {
|
|
2038
2590
|
log.info("Report submitted", { category, intent });
|
|
2039
2591
|
proactiveManager?.recordSubmission();
|
|
@@ -2060,6 +2612,11 @@ function createInstance(config) {
|
|
|
2060
2612
|
pendingScreenshot = await screenshotCap.take();
|
|
2061
2613
|
widget.setScreenshotAttached(pendingScreenshot !== null);
|
|
2062
2614
|
},
|
|
2615
|
+
onScreenshotRemove: () => {
|
|
2616
|
+
log.debug("Screenshot attachment removed");
|
|
2617
|
+
pendingScreenshot = null;
|
|
2618
|
+
widget.setScreenshotAttached(false);
|
|
2619
|
+
},
|
|
2063
2620
|
onElementSelectorRequest: async () => {
|
|
2064
2621
|
if (!elementSelector || activeConfig.capture?.elementSelector === false) return;
|
|
2065
2622
|
log.debug("Element selector activated");
|
|
@@ -2069,8 +2626,23 @@ function createInstance(config) {
|
|
|
2069
2626
|
widget.setElementSelected(true);
|
|
2070
2627
|
log.debug("Element selected", { tagName: el.tagName, xpath: el.xpath });
|
|
2071
2628
|
}
|
|
2629
|
+
},
|
|
2630
|
+
async onReporterReportsRequest() {
|
|
2631
|
+
const result = await apiClient.listReporterReports(getReporterToken());
|
|
2632
|
+
if (!result.ok) throw new Error(result.error?.message ?? "Could not load reports");
|
|
2633
|
+
return result.data?.reports ?? [];
|
|
2634
|
+
},
|
|
2635
|
+
async onReporterCommentsRequest(reportId) {
|
|
2636
|
+
const result = await apiClient.listReporterComments(reportId, getReporterToken());
|
|
2637
|
+
if (!result.ok) throw new Error(result.error?.message ?? "Could not load thread");
|
|
2638
|
+
return result.data?.comments ?? [];
|
|
2639
|
+
},
|
|
2640
|
+
async onReporterReply(reportId, body) {
|
|
2641
|
+
const result = await apiClient.replyToReporterReport(reportId, getReporterToken(), body);
|
|
2642
|
+
if (!result.ok) throw new Error(result.error?.message ?? "Could not send reply");
|
|
2072
2643
|
}
|
|
2073
|
-
});
|
|
2644
|
+
}, MUSHI_SDK_VERSION);
|
|
2645
|
+
syncCaptureModules();
|
|
2074
2646
|
if (typeof document !== "undefined") {
|
|
2075
2647
|
if (document.readyState === "loading") {
|
|
2076
2648
|
document.addEventListener("DOMContentLoaded", () => widget.mount());
|
|
@@ -2080,7 +2652,7 @@ function createInstance(config) {
|
|
|
2080
2652
|
}
|
|
2081
2653
|
let proactiveTriggers = null;
|
|
2082
2654
|
let proactiveManager = null;
|
|
2083
|
-
const proactiveCfg =
|
|
2655
|
+
const proactiveCfg = activeConfig.proactive;
|
|
2084
2656
|
const hasAnyProactive = proactiveCfg && (proactiveCfg.rageClick !== false || proactiveCfg.longTask !== false || proactiveCfg.apiCascade !== false || proactiveCfg.errorBoundary === true);
|
|
2085
2657
|
if (hasAnyProactive && typeof document !== "undefined") {
|
|
2086
2658
|
proactiveManager = createProactiveManager(proactiveCfg?.cooldown);
|
|
@@ -2101,6 +2673,7 @@ function createInstance(config) {
|
|
|
2101
2673
|
rageClick: proactiveCfg?.rageClick,
|
|
2102
2674
|
longTask: proactiveCfg?.longTask,
|
|
2103
2675
|
apiCascade: proactiveCfg?.apiCascade,
|
|
2676
|
+
apiEndpoint: resolveApiEndpoint(activeConfig),
|
|
2104
2677
|
errorBoundary: proactiveCfg?.errorBoundary
|
|
2105
2678
|
}
|
|
2106
2679
|
);
|
|
@@ -2116,6 +2689,7 @@ function createInstance(config) {
|
|
|
2116
2689
|
if (result.sent > 0) log.info("Synced offline reports", { sent: result.sent });
|
|
2117
2690
|
});
|
|
2118
2691
|
function applyRuntimeConfig(runtime) {
|
|
2692
|
+
runtimeConfigLoaded = true;
|
|
2119
2693
|
if (runtime.enabled === false) {
|
|
2120
2694
|
activeConfig = bootstrapConfig;
|
|
2121
2695
|
clearCachedRuntimeConfig(config.projectId);
|
|
@@ -2129,7 +2703,7 @@ function createInstance(config) {
|
|
|
2129
2703
|
if (runtime.widget) widget.updateConfig(activeConfig.widget);
|
|
2130
2704
|
log.debug("Applied runtime SDK config", { version: runtime.version });
|
|
2131
2705
|
}
|
|
2132
|
-
if (config
|
|
2706
|
+
if (shouldUseRuntimeConfig(config)) {
|
|
2133
2707
|
const cached = readCachedRuntimeConfig(config.projectId);
|
|
2134
2708
|
if (cached) applyRuntimeConfig(cached);
|
|
2135
2709
|
apiClient.getSdkConfig().then((result) => {
|
|
@@ -2142,8 +2716,41 @@ function createInstance(config) {
|
|
|
2142
2716
|
}).catch((err) => {
|
|
2143
2717
|
log.debug("Runtime SDK config fetch failed", { error: err instanceof Error ? err.message : String(err) });
|
|
2144
2718
|
});
|
|
2719
|
+
} else if (config.runtimeConfig !== false && isLocalhostEndpoint(resolveApiEndpoint(config))) {
|
|
2720
|
+
log.debug("Runtime SDK config skipped for localhost apiEndpoint; set runtimeConfig: true to force it");
|
|
2145
2721
|
}
|
|
2722
|
+
void checkSdkFreshness();
|
|
2146
2723
|
log.info("Initialized", { projectId: config.projectId });
|
|
2724
|
+
async function checkSdkFreshness() {
|
|
2725
|
+
if (activeConfig.widget?.outdatedBanner === "off") return;
|
|
2726
|
+
const cached = readCachedSdkVersion(MUSHI_SDK_PACKAGE);
|
|
2727
|
+
if (cached) applySdkFreshness(cached);
|
|
2728
|
+
const result = await apiClient.getLatestSdkVersion(MUSHI_SDK_PACKAGE);
|
|
2729
|
+
if (!result.ok || !result.data) return;
|
|
2730
|
+
cacheSdkVersion(MUSHI_SDK_PACKAGE, result.data);
|
|
2731
|
+
applySdkFreshness(result.data);
|
|
2732
|
+
}
|
|
2733
|
+
function applySdkFreshness(info) {
|
|
2734
|
+
const latest = info.latest;
|
|
2735
|
+
const outdated = Boolean(latest && isVersionOlder(MUSHI_SDK_VERSION, latest));
|
|
2736
|
+
if (!outdated && !info.deprecated) return;
|
|
2737
|
+
const message = info.deprecationMessage ?? (outdated ? `Update ${MUSHI_SDK_PACKAGE} to ${latest}.` : null);
|
|
2738
|
+
log.warn("Mushi SDK is outdated", {
|
|
2739
|
+
package: MUSHI_SDK_PACKAGE,
|
|
2740
|
+
current: MUSHI_SDK_VERSION,
|
|
2741
|
+
latest,
|
|
2742
|
+
deprecated: info.deprecated,
|
|
2743
|
+
message
|
|
2744
|
+
});
|
|
2745
|
+
if (activeConfig.widget?.outdatedBanner !== "console-only") {
|
|
2746
|
+
widget.setSdkFreshness({
|
|
2747
|
+
latest,
|
|
2748
|
+
current: MUSHI_SDK_VERSION,
|
|
2749
|
+
deprecated: info.deprecated,
|
|
2750
|
+
message
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2147
2754
|
async function submitReport(category, description, intent) {
|
|
2148
2755
|
const filterResult = preFilter.check(description);
|
|
2149
2756
|
if (!filterResult.passed) {
|
|
@@ -2185,6 +2792,8 @@ function createInstance(config) {
|
|
|
2185
2792
|
const scrubbedDescription = piiScrubber.scrub(preFilter.truncate(description));
|
|
2186
2793
|
const sentryCtx = config.sentry ? captureSentryContext(config.sentry) : void 0;
|
|
2187
2794
|
const fingerprintHash = await getDeviceFingerprintHash().catch(() => null);
|
|
2795
|
+
const consoleLogs = activeConfig.capture?.console === false ? void 0 : consoleCap?.getEntries();
|
|
2796
|
+
const networkLogs = activeConfig.capture?.network === false ? void 0 : networkCap?.getEntries();
|
|
2188
2797
|
const report = {
|
|
2189
2798
|
id: crypto.randomUUID?.() ?? `mushi_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
2190
2799
|
projectId: config.projectId,
|
|
@@ -2192,9 +2801,10 @@ function createInstance(config) {
|
|
|
2192
2801
|
description: scrubbedDescription,
|
|
2193
2802
|
userIntent: intent,
|
|
2194
2803
|
environment: captureEnvironment(),
|
|
2195
|
-
consoleLogs
|
|
2196
|
-
networkLogs
|
|
2804
|
+
consoleLogs,
|
|
2805
|
+
networkLogs,
|
|
2197
2806
|
performanceMetrics: activeConfig.capture?.performance === false ? void 0 : perfCap?.getMetrics(),
|
|
2807
|
+
timeline: timelineCap.getEntries({ consoleLogs, networkLogs }),
|
|
2198
2808
|
screenshotDataUrl: pendingScreenshot ?? void 0,
|
|
2199
2809
|
selectedElement: pendingElement ?? void 0,
|
|
2200
2810
|
metadata: {
|
|
@@ -2206,6 +2816,8 @@ function createInstance(config) {
|
|
|
2206
2816
|
reporterToken: getReporterToken(),
|
|
2207
2817
|
...fingerprintHash ? { fingerprintHash } : {},
|
|
2208
2818
|
appVersion: config.integrations?.vercel?.analyticsId,
|
|
2819
|
+
sdkPackage: MUSHI_SDK_PACKAGE,
|
|
2820
|
+
sdkVersion: MUSHI_SDK_VERSION,
|
|
2209
2821
|
proactiveTrigger: pendingProactiveTrigger ?? void 0,
|
|
2210
2822
|
sentryEventId: sentryCtx?.eventId,
|
|
2211
2823
|
sentryReplayId: sentryCtx?.replayId,
|
|
@@ -2260,6 +2872,9 @@ function createInstance(config) {
|
|
|
2260
2872
|
setMetadata(key, value) {
|
|
2261
2873
|
customMetadata[key] = value;
|
|
2262
2874
|
},
|
|
2875
|
+
setScreen(screen) {
|
|
2876
|
+
timelineCap.setScreen(screen);
|
|
2877
|
+
},
|
|
2263
2878
|
isOpen() {
|
|
2264
2879
|
return widget.getIsOpen();
|
|
2265
2880
|
},
|
|
@@ -2287,6 +2902,15 @@ function createInstance(config) {
|
|
|
2287
2902
|
updateConfig(runtimeConfig) {
|
|
2288
2903
|
applyRuntimeConfig(runtimeConfig);
|
|
2289
2904
|
},
|
|
2905
|
+
diagnose() {
|
|
2906
|
+
return runDiagnostics({
|
|
2907
|
+
apiEndpoint: resolveApiEndpoint(activeConfig),
|
|
2908
|
+
widgetMounted: widget.getIsMounted(),
|
|
2909
|
+
runtimeConfigLoaded,
|
|
2910
|
+
captureScreenshotAvailable: screenshotCap !== null,
|
|
2911
|
+
captureNetworkIntercepting: networkCap !== null
|
|
2912
|
+
});
|
|
2913
|
+
},
|
|
2290
2914
|
destroy() {
|
|
2291
2915
|
proactiveTriggers?.destroy();
|
|
2292
2916
|
proactiveManager?.reset();
|
|
@@ -2295,6 +2919,7 @@ function createInstance(config) {
|
|
|
2295
2919
|
networkCap?.destroy();
|
|
2296
2920
|
perfCap?.destroy();
|
|
2297
2921
|
elementSelector?.deactivate();
|
|
2922
|
+
timelineCap.destroy();
|
|
2298
2923
|
offlineQueue.stopAutoSync();
|
|
2299
2924
|
listeners.clear();
|
|
2300
2925
|
instance = null;
|
|
@@ -2316,6 +2941,7 @@ function createInstance(config) {
|
|
|
2316
2941
|
category,
|
|
2317
2942
|
description,
|
|
2318
2943
|
environment: captureEnvironment(),
|
|
2944
|
+
timeline: timelineCap.getEntries(),
|
|
2319
2945
|
metadata: {
|
|
2320
2946
|
...input.metadata ?? {},
|
|
2321
2947
|
...userInfo ? { user: userInfo } : {},
|
|
@@ -2327,6 +2953,8 @@ function createInstance(config) {
|
|
|
2327
2953
|
},
|
|
2328
2954
|
sessionId: getSessionId(),
|
|
2329
2955
|
reporterToken: getReporterToken(),
|
|
2956
|
+
sdkPackage: MUSHI_SDK_PACKAGE,
|
|
2957
|
+
sdkVersion: MUSHI_SDK_VERSION,
|
|
2330
2958
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2331
2959
|
};
|
|
2332
2960
|
emit("report:submitted", { reportId: report.id });
|
|
@@ -2368,12 +2996,128 @@ function mergeRuntimeConfig(config, runtime) {
|
|
|
2368
2996
|
capture: {
|
|
2369
2997
|
...config.capture,
|
|
2370
2998
|
...runtime.capture
|
|
2999
|
+
},
|
|
3000
|
+
privacy: {
|
|
3001
|
+
...config.privacy
|
|
3002
|
+
}
|
|
3003
|
+
};
|
|
3004
|
+
}
|
|
3005
|
+
function applyPresetConfig(config) {
|
|
3006
|
+
if (!config.preset) return config;
|
|
3007
|
+
const preset = presetDefaults(config.preset);
|
|
3008
|
+
return {
|
|
3009
|
+
...config,
|
|
3010
|
+
widget: {
|
|
3011
|
+
...preset.widget,
|
|
3012
|
+
...config.widget
|
|
3013
|
+
},
|
|
3014
|
+
capture: {
|
|
3015
|
+
...preset.capture,
|
|
3016
|
+
...config.capture
|
|
3017
|
+
},
|
|
3018
|
+
proactive: {
|
|
3019
|
+
...preset.proactive,
|
|
3020
|
+
...config.proactive,
|
|
3021
|
+
cooldown: {
|
|
3022
|
+
...preset.proactive?.cooldown,
|
|
3023
|
+
...config.proactive?.cooldown
|
|
3024
|
+
}
|
|
2371
3025
|
}
|
|
2372
3026
|
};
|
|
2373
3027
|
}
|
|
3028
|
+
function presetDefaults(preset) {
|
|
3029
|
+
switch (preset) {
|
|
3030
|
+
case "manual-only":
|
|
3031
|
+
return {
|
|
3032
|
+
widget: { trigger: "manual", outdatedBanner: "console-only" },
|
|
3033
|
+
capture: { console: true, network: true, performance: false, screenshot: "on-report", elementSelector: false },
|
|
3034
|
+
proactive: { rageClick: false, longTask: false, apiCascade: false, errorBoundary: false }
|
|
3035
|
+
};
|
|
3036
|
+
case "beta-loud":
|
|
3037
|
+
return {
|
|
3038
|
+
widget: { trigger: "auto", outdatedBanner: "banner" },
|
|
3039
|
+
capture: { console: true, network: true, performance: true, screenshot: "auto", elementSelector: true },
|
|
3040
|
+
proactive: { rageClick: true, longTask: true, apiCascade: true, errorBoundary: true }
|
|
3041
|
+
};
|
|
3042
|
+
case "internal-debug":
|
|
3043
|
+
return {
|
|
3044
|
+
widget: { trigger: "auto", outdatedBanner: "banner", brandFooter: true },
|
|
3045
|
+
capture: { console: true, network: true, performance: true, screenshot: "auto", elementSelector: true },
|
|
3046
|
+
proactive: {
|
|
3047
|
+
rageClick: true,
|
|
3048
|
+
longTask: true,
|
|
3049
|
+
apiCascade: true,
|
|
3050
|
+
errorBoundary: true,
|
|
3051
|
+
cooldown: { maxProactivePerSession: 10, dismissCooldownHours: 0, suppressAfterDismissals: 99 }
|
|
3052
|
+
}
|
|
3053
|
+
};
|
|
3054
|
+
case "production-calm":
|
|
3055
|
+
return {
|
|
3056
|
+
widget: { trigger: "auto", outdatedBanner: "console-only" },
|
|
3057
|
+
capture: { console: true, network: true, performance: false, screenshot: "on-report", elementSelector: false },
|
|
3058
|
+
proactive: { rageClick: false, longTask: false, apiCascade: false, errorBoundary: false }
|
|
3059
|
+
};
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
function resolveApiEndpoint(config) {
|
|
3063
|
+
return config.apiEndpoint ?? DEFAULT_API_ENDPOINT;
|
|
3064
|
+
}
|
|
3065
|
+
function shouldUseRuntimeConfig(config) {
|
|
3066
|
+
if (config.runtimeConfig === false) return false;
|
|
3067
|
+
if (config.runtimeConfig === true) return true;
|
|
3068
|
+
return !isLocalhostEndpoint(resolveApiEndpoint(config));
|
|
3069
|
+
}
|
|
3070
|
+
async function runDiagnostics(options) {
|
|
3071
|
+
const endpoint = await probeApiEndpoint(options.apiEndpoint);
|
|
3072
|
+
return {
|
|
3073
|
+
apiEndpointReachable: endpoint.reachable,
|
|
3074
|
+
cspAllowsEndpoint: endpoint.cspAllowed,
|
|
3075
|
+
widgetMounted: options.widgetMounted,
|
|
3076
|
+
shadowDomAvailable: typeof HTMLElement !== "undefined" && typeof HTMLElement.prototype.attachShadow === "function",
|
|
3077
|
+
dialogSupported: typeof HTMLDialogElement !== "undefined",
|
|
3078
|
+
runtimeConfigLoaded: options.runtimeConfigLoaded,
|
|
3079
|
+
captureScreenshotAvailable: options.captureScreenshotAvailable,
|
|
3080
|
+
captureNetworkIntercepting: options.captureNetworkIntercepting,
|
|
3081
|
+
sdkVersion: MUSHI_SDK_VERSION
|
|
3082
|
+
};
|
|
3083
|
+
}
|
|
3084
|
+
async function diagnoseWithoutInstance() {
|
|
3085
|
+
return {
|
|
3086
|
+
apiEndpointReachable: false,
|
|
3087
|
+
cspAllowsEndpoint: false,
|
|
3088
|
+
widgetMounted: false,
|
|
3089
|
+
shadowDomAvailable: typeof HTMLElement !== "undefined" && typeof HTMLElement.prototype.attachShadow === "function",
|
|
3090
|
+
dialogSupported: typeof HTMLDialogElement !== "undefined",
|
|
3091
|
+
runtimeConfigLoaded: false,
|
|
3092
|
+
captureScreenshotAvailable: false,
|
|
3093
|
+
captureNetworkIntercepting: false,
|
|
3094
|
+
sdkVersion: MUSHI_SDK_VERSION
|
|
3095
|
+
};
|
|
3096
|
+
}
|
|
3097
|
+
async function probeApiEndpoint(apiEndpoint) {
|
|
3098
|
+
if (typeof fetch === "undefined") return { reachable: false, cspAllowed: false };
|
|
3099
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
3100
|
+
const timer = controller ? setTimeout(() => controller.abort(), 3e3) : null;
|
|
3101
|
+
try {
|
|
3102
|
+
const response = await fetch(`${apiEndpoint.replace(/\/$/, "")}/health`, {
|
|
3103
|
+
method: "GET",
|
|
3104
|
+
cache: "no-store",
|
|
3105
|
+
...controller ? { signal: controller.signal } : {},
|
|
3106
|
+
[MUSHI_INTERNAL_INIT_MARKER]: "diagnose"
|
|
3107
|
+
});
|
|
3108
|
+
return { reachable: response.ok, cspAllowed: true };
|
|
3109
|
+
} catch {
|
|
3110
|
+
return { reachable: false, cspAllowed: false };
|
|
3111
|
+
} finally {
|
|
3112
|
+
if (timer) clearTimeout(timer);
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
2374
3115
|
function runtimeConfigCacheKey(projectId) {
|
|
2375
3116
|
return `mushi:sdk-config:${projectId}`;
|
|
2376
3117
|
}
|
|
3118
|
+
function sdkVersionCacheKey(packageName) {
|
|
3119
|
+
return `mushi:sdk-version:${packageName}`;
|
|
3120
|
+
}
|
|
2377
3121
|
function readCachedRuntimeConfig(projectId) {
|
|
2378
3122
|
if (typeof localStorage === "undefined") return null;
|
|
2379
3123
|
try {
|
|
@@ -2402,6 +3146,42 @@ function clearCachedRuntimeConfig(projectId) {
|
|
|
2402
3146
|
} catch {
|
|
2403
3147
|
}
|
|
2404
3148
|
}
|
|
3149
|
+
function readCachedSdkVersion(packageName) {
|
|
3150
|
+
if (typeof localStorage === "undefined") return null;
|
|
3151
|
+
try {
|
|
3152
|
+
const raw = localStorage.getItem(sdkVersionCacheKey(packageName));
|
|
3153
|
+
if (!raw) return null;
|
|
3154
|
+
const parsed = JSON.parse(raw);
|
|
3155
|
+
if (!parsed.data || !parsed.cachedAt || Date.now() - parsed.cachedAt > 864e5) return null;
|
|
3156
|
+
return parsed.data;
|
|
3157
|
+
} catch {
|
|
3158
|
+
return null;
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
function cacheSdkVersion(packageName, data) {
|
|
3162
|
+
if (typeof localStorage === "undefined") return;
|
|
3163
|
+
try {
|
|
3164
|
+
localStorage.setItem(sdkVersionCacheKey(packageName), JSON.stringify({
|
|
3165
|
+
cachedAt: Date.now(),
|
|
3166
|
+
data
|
|
3167
|
+
}));
|
|
3168
|
+
} catch {
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
function isVersionOlder(current, latest) {
|
|
3172
|
+
const currentParts = parseVersion(current);
|
|
3173
|
+
const latestParts = parseVersion(latest);
|
|
3174
|
+
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
|
|
3175
|
+
const cur = currentParts[i] ?? 0;
|
|
3176
|
+
const next = latestParts[i] ?? 0;
|
|
3177
|
+
if (cur < next) return true;
|
|
3178
|
+
if (cur > next) return false;
|
|
3179
|
+
}
|
|
3180
|
+
return false;
|
|
3181
|
+
}
|
|
3182
|
+
function parseVersion(version) {
|
|
3183
|
+
return version.split(/[.-]/).map((part) => Number.parseInt(part, 10)).filter((part) => Number.isFinite(part));
|
|
3184
|
+
}
|
|
2405
3185
|
function createNoopInstance() {
|
|
2406
3186
|
return {
|
|
2407
3187
|
report: () => {
|
|
@@ -2412,6 +3192,8 @@ function createNoopInstance() {
|
|
|
2412
3192
|
},
|
|
2413
3193
|
setMetadata: () => {
|
|
2414
3194
|
},
|
|
3195
|
+
setScreen: () => {
|
|
3196
|
+
},
|
|
2415
3197
|
isOpen: () => false,
|
|
2416
3198
|
open: () => {
|
|
2417
3199
|
},
|
|
@@ -2419,6 +3201,7 @@ function createNoopInstance() {
|
|
|
2419
3201
|
},
|
|
2420
3202
|
updateConfig: () => {
|
|
2421
3203
|
},
|
|
3204
|
+
diagnose: diagnoseWithoutInstance,
|
|
2422
3205
|
openWith: () => {
|
|
2423
3206
|
},
|
|
2424
3207
|
show: () => {
|
|
@@ -2438,6 +3221,6 @@ function createNoopInstance() {
|
|
|
2438
3221
|
};
|
|
2439
3222
|
}
|
|
2440
3223
|
|
|
2441
|
-
export { Mushi, MushiWidget, createConsoleCapture, createElementSelector, createNetworkCapture, createPerformanceCapture, createProactiveManager, createScreenshotCapture, getAvailableLocales, getLocale, setupProactiveTriggers };
|
|
3224
|
+
export { Mushi, MushiWidget, createConsoleCapture, createElementSelector, createNetworkCapture, createPerformanceCapture, createProactiveManager, createScreenshotCapture, createTimelineCapture, getAvailableLocales, getLocale, setupProactiveTriggers };
|
|
2442
3225
|
//# sourceMappingURL=index.js.map
|
|
2443
3226
|
//# sourceMappingURL=index.js.map
|