@mushi-mushi/web 1.10.0 → 1.12.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 +28 -0
- package/dist/index.cjs +588 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +589 -6
- package/dist/index.js.map +1 -1
- package/dist/test-utils.cjs +1 -1
- package/dist/test-utils.js +1 -1
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
|
-
import { createLogger, noopLogger, createApiClient, createPreFilter, createOfflineQueue, createRateLimiter, createPiiScrubber, createBreadcrumbBuffer, getReporterToken, getDeviceFingerprintHash, getSessionId, captureEnvironment, newUuid, DEFAULT_API_ENDPOINT, MUSHI_INTERNAL_INIT_MARKER, MUSHI_INTERNAL_HEADER, normaliseThrown } from '@mushi-mushi/core';
|
|
1
|
+
import { createLogger, noopLogger, createApiClient, createPreFilter, createOfflineQueue, createRateLimiter, createPiiScrubber, createBreadcrumbBuffer, getReporterToken, getDeviceFingerprintHash, MAX_SCREENSHOT_DATA_URL_BYTES, getSessionId, captureEnvironment, newUuid, DEFAULT_API_ENDPOINT, MUSHI_INTERNAL_INIT_MARKER, MUSHI_INTERNAL_HEADER, normaliseThrown } from '@mushi-mushi/core';
|
|
2
2
|
|
|
3
|
-
// src/
|
|
3
|
+
// src/capture/compress-screenshot.ts
|
|
4
|
+
async function compressScreenshotDataUrl(dataUrl, maxBytes = MAX_SCREENSHOT_DATA_URL_BYTES) {
|
|
5
|
+
if (!dataUrl.startsWith("data:image/")) return dataUrl;
|
|
6
|
+
if (estimateDataUrlBytes(dataUrl) <= maxBytes) return dataUrl;
|
|
7
|
+
if (typeof document === "undefined") return null;
|
|
8
|
+
const img = new Image();
|
|
9
|
+
await new Promise((resolve, reject) => {
|
|
10
|
+
img.onload = () => resolve();
|
|
11
|
+
img.onerror = () => reject(new Error("screenshot_decode_failed"));
|
|
12
|
+
img.src = dataUrl;
|
|
13
|
+
});
|
|
14
|
+
const qualities = [0.65, 0.5, 0.35, 0.25];
|
|
15
|
+
const scales = [1, 0.85, 0.7, 0.55, 0.4];
|
|
16
|
+
for (const scale of scales) {
|
|
17
|
+
const canvas = document.createElement("canvas");
|
|
18
|
+
const w = Math.max(1, Math.round(img.naturalWidth * scale));
|
|
19
|
+
const h = Math.max(1, Math.round(img.naturalHeight * scale));
|
|
20
|
+
canvas.width = w;
|
|
21
|
+
canvas.height = h;
|
|
22
|
+
const ctx = canvas.getContext("2d");
|
|
23
|
+
if (!ctx) continue;
|
|
24
|
+
ctx.drawImage(img, 0, 0, w, h);
|
|
25
|
+
for (const q of qualities) {
|
|
26
|
+
const candidate = canvas.toDataURL("image/jpeg", q);
|
|
27
|
+
if (estimateDataUrlBytes(candidate) <= maxBytes) return candidate;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
function estimateDataUrlBytes(dataUrl) {
|
|
33
|
+
const comma = dataUrl.indexOf(",");
|
|
34
|
+
if (comma < 0) return dataUrl.length;
|
|
35
|
+
const b64 = dataUrl.slice(comma + 1);
|
|
36
|
+
return Math.ceil(b64.length * 3 / 4);
|
|
37
|
+
}
|
|
4
38
|
|
|
5
39
|
// src/i18n/en.ts
|
|
6
40
|
var en = {
|
|
@@ -1003,6 +1037,19 @@ function getWidgetStyles(theme) {
|
|
|
1003
1037
|
outline: 2px solid ${widgetAccent};
|
|
1004
1038
|
outline-offset: 2px;
|
|
1005
1039
|
}
|
|
1040
|
+
.mushi-annotate-host {
|
|
1041
|
+
flex-basis: 100%;
|
|
1042
|
+
margin-top: 8px;
|
|
1043
|
+
}
|
|
1044
|
+
.mushi-annotate-host:empty {
|
|
1045
|
+
margin-top: 0;
|
|
1046
|
+
}
|
|
1047
|
+
.mushi-annotate-toolbar {
|
|
1048
|
+
display: flex;
|
|
1049
|
+
flex-wrap: wrap;
|
|
1050
|
+
gap: 6px;
|
|
1051
|
+
margin-bottom: 8px;
|
|
1052
|
+
}
|
|
1006
1053
|
@keyframes mushi-spin {
|
|
1007
1054
|
to { transform: rotate(360deg); }
|
|
1008
1055
|
}
|
|
@@ -1832,7 +1879,11 @@ function reporterStatusLabel(status) {
|
|
|
1832
1879
|
case "fixed":
|
|
1833
1880
|
case "resolved":
|
|
1834
1881
|
case "completed":
|
|
1835
|
-
return "Fixed";
|
|
1882
|
+
return "Fixed \u2014 confirm?";
|
|
1883
|
+
case "verified":
|
|
1884
|
+
return "Verified";
|
|
1885
|
+
case "reopened":
|
|
1886
|
+
return "Reopened";
|
|
1836
1887
|
case "dismissed":
|
|
1837
1888
|
return "Closed";
|
|
1838
1889
|
default:
|
|
@@ -1857,6 +1908,10 @@ function reporterStatusTone(status) {
|
|
|
1857
1908
|
case "resolved":
|
|
1858
1909
|
case "completed":
|
|
1859
1910
|
return "fixed";
|
|
1911
|
+
case "verified":
|
|
1912
|
+
return "fixed";
|
|
1913
|
+
case "reopened":
|
|
1914
|
+
return "fixing";
|
|
1860
1915
|
case "dismissed":
|
|
1861
1916
|
return "closed";
|
|
1862
1917
|
default:
|
|
@@ -1976,6 +2031,7 @@ var MushiWidget = class _MushiWidget {
|
|
|
1976
2031
|
nudgeTimer = null;
|
|
1977
2032
|
sdkFreshness = null;
|
|
1978
2033
|
reporterReports = [];
|
|
2034
|
+
featureBoard = [];
|
|
1979
2035
|
reporterComments = [];
|
|
1980
2036
|
selectedReportId = null;
|
|
1981
2037
|
reporterLoading = false;
|
|
@@ -2695,6 +2751,8 @@ var MushiWidget = class _MushiWidget {
|
|
|
2695
2751
|
return this.renderReportDetailStep();
|
|
2696
2752
|
case "leaderboard":
|
|
2697
2753
|
return this.renderLeaderboardStep();
|
|
2754
|
+
case "roadmap":
|
|
2755
|
+
return this.renderRoadmapStep();
|
|
2698
2756
|
}
|
|
2699
2757
|
}
|
|
2700
2758
|
renderOutdatedBanner() {
|
|
@@ -2782,6 +2840,15 @@ var MushiWidget = class _MushiWidget {
|
|
|
2782
2840
|
<span class="mushi-option-arrow" aria-hidden="true">\u2192</span>
|
|
2783
2841
|
</button>
|
|
2784
2842
|
${this.renderFeatureRequestEntry()}
|
|
2843
|
+
${this.callbacks.onFeatureBoardRequest ? `
|
|
2844
|
+
<button type="button" class="mushi-option-btn" data-action="roadmap">
|
|
2845
|
+
<span class="mushi-option-icon" aria-hidden="true">\u{1F5F3}\uFE0F</span>
|
|
2846
|
+
<div class="mushi-option-text">
|
|
2847
|
+
<span class="mushi-option-label">Community ideas</span>
|
|
2848
|
+
<span class="mushi-option-desc">Vote on features and see what shipped</span>
|
|
2849
|
+
</div>
|
|
2850
|
+
<span class="mushi-option-arrow" aria-hidden="true">\u2192</span>
|
|
2851
|
+
</button>` : ""}
|
|
2785
2852
|
${categories}
|
|
2786
2853
|
${this.rewardsState ? this.renderRewardsNudge() : ""}
|
|
2787
2854
|
</div>
|
|
@@ -2892,6 +2959,38 @@ var MushiWidget = class _MushiWidget {
|
|
|
2892
2959
|
</div>
|
|
2893
2960
|
`;
|
|
2894
2961
|
}
|
|
2962
|
+
renderRoadmapStep() {
|
|
2963
|
+
const rows = this.featureBoard.map((ticket) => {
|
|
2964
|
+
const id = String(ticket.id ?? "");
|
|
2965
|
+
const subject = escapeHtml(String(ticket.subject ?? "Untitled idea"));
|
|
2966
|
+
const votes = Number(ticket.vote_count ?? 0);
|
|
2967
|
+
const shipped = Boolean(ticket.shipped_at);
|
|
2968
|
+
const voted = Boolean(ticket.my_vote);
|
|
2969
|
+
const status = shipped ? "Shipped" : String(ticket.status_label ?? ticket.status ?? "open");
|
|
2970
|
+
return `
|
|
2971
|
+
<div class="mushi-report-row mushi-roadmap-row">
|
|
2972
|
+
<div class="mushi-report-main">
|
|
2973
|
+
<span class="mushi-report-title">${subject}</span>
|
|
2974
|
+
<span class="mushi-report-meta">
|
|
2975
|
+
<span class="mushi-report-status">${escapeHtml(status)}</span>
|
|
2976
|
+
<span class="mushi-report-when">${votes} vote${votes === 1 ? "" : "s"}</span>
|
|
2977
|
+
</span>
|
|
2978
|
+
</div>
|
|
2979
|
+
${this.callbacks.onFeatureBoardVote ? `
|
|
2980
|
+
<button type="button" class="mushi-vote-btn" data-vote-id="${escapeHtml(id)}" aria-pressed="${voted}">
|
|
2981
|
+
${voted ? "Voted" : "Vote"}
|
|
2982
|
+
</button>` : ""}
|
|
2983
|
+
</div>`;
|
|
2984
|
+
}).join("");
|
|
2985
|
+
return `
|
|
2986
|
+
${this.renderHeader({ title: "Community ideas", showBack: true, eyebrow: "Mushi \xB7 Roadmap" })}
|
|
2987
|
+
<div class="mushi-body">
|
|
2988
|
+
${this.reporterLoading ? '<p class="mushi-muted">Loading ideas\u2026</p>' : ""}
|
|
2989
|
+
${this.reporterError ? `<p class="mushi-error-inline">${escapeHtml(this.reporterError)}</p>` : ""}
|
|
2990
|
+
${rows || (!this.reporterLoading ? '<p class="mushi-muted">No community ideas yet. Be the first to suggest one.</p>' : "")}
|
|
2991
|
+
</div>
|
|
2992
|
+
`;
|
|
2993
|
+
}
|
|
2895
2994
|
renderLeaderboardStep() {
|
|
2896
2995
|
const myRank = this.rewardsState && this.leaderboardEntries ? this.leaderboardEntries.findIndex((e) => e.display_name === "You") + 1 : 0;
|
|
2897
2996
|
const rows = (this.leaderboardEntries ?? []).map((e, i) => `
|
|
@@ -2937,6 +3036,12 @@ var MushiWidget = class _MushiWidget {
|
|
|
2937
3036
|
<div class="mushi-thread">
|
|
2938
3037
|
${this.reporterLoading ? '<p class="mushi-muted">Loading thread\u2026</p>' : comments || '<p class="mushi-muted">No developer replies yet.</p>'}
|
|
2939
3038
|
</div>
|
|
3039
|
+
${["fixed", "resolved", "verified"].includes(status) ? `
|
|
3040
|
+
<div class="mushi-verify-actions" role="group" aria-label="Fix verification">
|
|
3041
|
+
<button type="button" class="mushi-intent-btn" data-action="reporter-confirms">Yes, fixed for me</button>
|
|
3042
|
+
<button type="button" class="mushi-intent-btn" data-action="reporter-not-fixed">Not fixed yet</button>
|
|
3043
|
+
</div>
|
|
3044
|
+
` : ""}
|
|
2940
3045
|
<textarea class="mushi-textarea" data-role="reporter-reply" rows="3" placeholder="Reply to the developer\u2026"></textarea>
|
|
2941
3046
|
<button type="button" class="mushi-submit" data-action="reporter-reply">
|
|
2942
3047
|
<span>Reply</span><span class="mushi-submit-arrow" aria-hidden="true">\u2192</span>
|
|
@@ -3016,6 +3121,7 @@ var MushiWidget = class _MushiWidget {
|
|
|
3016
3121
|
${escapeHtml(screenshotLabel)}
|
|
3017
3122
|
</button>
|
|
3018
3123
|
${this.screenshotAttached && this.allowScreenshotRemove ? '<button type="button" class="mushi-attach-btn danger" data-action="remove-screenshot" aria-label="Remove screenshot">\u2715 Remove</button>' : ""}
|
|
3124
|
+
${this.screenshotAttached ? '<button type="button" class="mushi-attach-btn" data-action="annotate-screenshot" aria-label="Mark up screenshot">\u270F Mark up</button><div class="mushi-annotate-host" data-role="annotate-host"></div>' : ""}
|
|
3019
3125
|
<button type="button" class="${elementClass}"
|
|
3020
3126
|
data-action="element"
|
|
3021
3127
|
${this.elementCapturing ? "disabled" : ""}
|
|
@@ -3241,12 +3347,23 @@ var MushiWidget = class _MushiWidget {
|
|
|
3241
3347
|
this.selectedReportId = null;
|
|
3242
3348
|
} else if (this.step === "leaderboard") {
|
|
3243
3349
|
this.step = "reports";
|
|
3350
|
+
} else if (this.step === "roadmap") {
|
|
3351
|
+
this.step = "category";
|
|
3244
3352
|
}
|
|
3245
3353
|
this.render();
|
|
3246
3354
|
});
|
|
3247
3355
|
panel.querySelector('[data-action="reports"]')?.addEventListener("click", () => {
|
|
3248
3356
|
void this.loadReporterReports();
|
|
3249
3357
|
});
|
|
3358
|
+
panel.querySelector('[data-action="roadmap"]')?.addEventListener("click", () => {
|
|
3359
|
+
void this.loadFeatureBoard();
|
|
3360
|
+
});
|
|
3361
|
+
panel.querySelectorAll("[data-vote-id]").forEach((btn) => {
|
|
3362
|
+
btn.addEventListener("click", () => {
|
|
3363
|
+
const requestId = btn.dataset.voteId;
|
|
3364
|
+
if (requestId) void this.voteFeatureBoard(requestId);
|
|
3365
|
+
});
|
|
3366
|
+
});
|
|
3250
3367
|
panel.querySelector('[data-action="feature-request"]')?.addEventListener("click", () => {
|
|
3251
3368
|
this.selectedCategory = "other";
|
|
3252
3369
|
this.selectedIntent = FEATURE_REQUEST_INTENT;
|
|
@@ -3268,6 +3385,12 @@ var MushiWidget = class _MushiWidget {
|
|
|
3268
3385
|
panel.querySelector('[data-action="reporter-reply"]')?.addEventListener("click", () => {
|
|
3269
3386
|
void this.submitReporterReply(panel);
|
|
3270
3387
|
});
|
|
3388
|
+
panel.querySelector('[data-action="reporter-confirms"]')?.addEventListener("click", () => {
|
|
3389
|
+
void this.submitReporterFeedback("confirms");
|
|
3390
|
+
});
|
|
3391
|
+
panel.querySelector('[data-action="reporter-not-fixed"]')?.addEventListener("click", () => {
|
|
3392
|
+
void this.submitReporterReopen();
|
|
3393
|
+
});
|
|
3271
3394
|
panel.querySelector('[data-action="copy-report-id"]')?.addEventListener("click", (e) => {
|
|
3272
3395
|
const btn = e.currentTarget;
|
|
3273
3396
|
const id = btn.dataset.copyId;
|
|
@@ -3333,6 +3456,12 @@ var MushiWidget = class _MushiWidget {
|
|
|
3333
3456
|
panel.querySelector('[data-action="remove-screenshot"]')?.addEventListener("click", () => {
|
|
3334
3457
|
this.callbacks.onScreenshotRemove?.();
|
|
3335
3458
|
});
|
|
3459
|
+
panel.querySelector('[data-action="annotate-screenshot"]')?.addEventListener("click", () => {
|
|
3460
|
+
const host = panel.querySelector('[data-role="annotate-host"]');
|
|
3461
|
+
if (host && this.callbacks.onScreenshotAnnotateRequest) {
|
|
3462
|
+
void this.callbacks.onScreenshotAnnotateRequest(host);
|
|
3463
|
+
}
|
|
3464
|
+
});
|
|
3336
3465
|
panel.querySelector('[data-action="element"]')?.addEventListener("click", () => {
|
|
3337
3466
|
this.callbacks.onElementSelectorRequest?.();
|
|
3338
3467
|
});
|
|
@@ -3420,6 +3549,53 @@ var MushiWidget = class _MushiWidget {
|
|
|
3420
3549
|
unreadCount() {
|
|
3421
3550
|
return this.reporterReports.reduce((sum, report) => sum + (report.unread_count ?? 0), 0);
|
|
3422
3551
|
}
|
|
3552
|
+
async loadFeatureBoard() {
|
|
3553
|
+
this.step = "roadmap";
|
|
3554
|
+
this.reporterLoading = true;
|
|
3555
|
+
this.reporterError = null;
|
|
3556
|
+
this.render();
|
|
3557
|
+
try {
|
|
3558
|
+
this.featureBoard = await this.callbacks.onFeatureBoardRequest?.() ?? [];
|
|
3559
|
+
} catch (err) {
|
|
3560
|
+
this.reporterError = err instanceof Error ? err.message : "Could not load community ideas.";
|
|
3561
|
+
} finally {
|
|
3562
|
+
this.reporterLoading = false;
|
|
3563
|
+
this.render();
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
async voteFeatureBoard(requestId) {
|
|
3567
|
+
if (!this.callbacks.onFeatureBoardVote || this.reporterLoading) return;
|
|
3568
|
+
this.reporterLoading = true;
|
|
3569
|
+
this.render();
|
|
3570
|
+
try {
|
|
3571
|
+
await this.callbacks.onFeatureBoardVote(requestId);
|
|
3572
|
+
this.featureBoard = await this.callbacks.onFeatureBoardRequest?.() ?? this.featureBoard;
|
|
3573
|
+
} catch (err) {
|
|
3574
|
+
this.reporterError = err instanceof Error ? err.message : "Could not update vote.";
|
|
3575
|
+
} finally {
|
|
3576
|
+
this.reporterLoading = false;
|
|
3577
|
+
this.render();
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
async submitReporterReopen() {
|
|
3581
|
+
const reportId = this.selectedReportId;
|
|
3582
|
+
if (!reportId || this.reporterLoading) return;
|
|
3583
|
+
this.reporterLoading = true;
|
|
3584
|
+
this.render();
|
|
3585
|
+
try {
|
|
3586
|
+
if (this.callbacks.onReporterReopen) {
|
|
3587
|
+
await this.callbacks.onReporterReopen(reportId, "Not fixed for me");
|
|
3588
|
+
} else {
|
|
3589
|
+
await this.callbacks.onReporterFeedback?.(reportId, "not_fixed", "Not fixed for me");
|
|
3590
|
+
}
|
|
3591
|
+
await this.loadReporterReports();
|
|
3592
|
+
} catch (err) {
|
|
3593
|
+
this.reporterError = err instanceof Error ? err.message : "Could not reopen report.";
|
|
3594
|
+
} finally {
|
|
3595
|
+
this.reporterLoading = false;
|
|
3596
|
+
this.render();
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3423
3599
|
async loadReporterReports() {
|
|
3424
3600
|
this.step = "reports";
|
|
3425
3601
|
this.reporterLoading = true;
|
|
@@ -3449,6 +3625,21 @@ var MushiWidget = class _MushiWidget {
|
|
|
3449
3625
|
this.render();
|
|
3450
3626
|
}
|
|
3451
3627
|
}
|
|
3628
|
+
async submitReporterFeedback(signal) {
|
|
3629
|
+
const reportId = this.selectedReportId;
|
|
3630
|
+
if (!reportId || this.reporterLoading) return;
|
|
3631
|
+
this.reporterLoading = true;
|
|
3632
|
+
this.render();
|
|
3633
|
+
try {
|
|
3634
|
+
await this.callbacks.onReporterFeedback?.(reportId, signal);
|
|
3635
|
+
await this.loadReporterReports();
|
|
3636
|
+
if (reportId) await this.loadReporterComments(reportId);
|
|
3637
|
+
} catch (err) {
|
|
3638
|
+
this.reporterError = err instanceof Error ? err.message : "Could not send feedback.";
|
|
3639
|
+
this.reporterLoading = false;
|
|
3640
|
+
this.render();
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3452
3643
|
async submitReporterReply(panel) {
|
|
3453
3644
|
const reportId = this.selectedReportId;
|
|
3454
3645
|
const textarea = panel.querySelector('[data-role="reporter-reply"]');
|
|
@@ -4819,6 +5010,256 @@ function createDiscoveryCapture(opts) {
|
|
|
4819
5010
|
};
|
|
4820
5011
|
}
|
|
4821
5012
|
|
|
5013
|
+
// src/capture/replay.ts
|
|
5014
|
+
var DEFAULT_MAX_MS = 3e4;
|
|
5015
|
+
var MAX_EVENTS = 400;
|
|
5016
|
+
var RRWEB_META = 4;
|
|
5017
|
+
var RRWEB_FULL_SNAPSHOT = 2;
|
|
5018
|
+
function trimReplayBuffer(events, maxMs, maxEvents) {
|
|
5019
|
+
const ts = (e) => e.timestamp;
|
|
5020
|
+
const isFullSnapshot = (e) => e.type === RRWEB_FULL_SNAPSHOT;
|
|
5021
|
+
const isMeta = (e) => e.type === RRWEB_META;
|
|
5022
|
+
const cutoff = Date.now() - maxMs;
|
|
5023
|
+
let baseIndex = 0;
|
|
5024
|
+
for (let i = 0; i < events.length; i++) {
|
|
5025
|
+
const t = ts(events[i]);
|
|
5026
|
+
if (typeof t === "number" && t >= cutoff) break;
|
|
5027
|
+
if (isFullSnapshot(events[i])) baseIndex = isMeta(events[i - 1]) ? i - 1 : i;
|
|
5028
|
+
}
|
|
5029
|
+
if (events.length - baseIndex > maxEvents) {
|
|
5030
|
+
for (let i = baseIndex + 1; i < events.length; i++) {
|
|
5031
|
+
if (isFullSnapshot(events[i]) && events.length - i <= maxEvents) {
|
|
5032
|
+
baseIndex = isMeta(events[i - 1]) ? i - 1 : i;
|
|
5033
|
+
break;
|
|
5034
|
+
}
|
|
5035
|
+
}
|
|
5036
|
+
}
|
|
5037
|
+
if (baseIndex > 0) events.splice(0, baseIndex);
|
|
5038
|
+
}
|
|
5039
|
+
function createLiteReplay(maxMs) {
|
|
5040
|
+
const events = [];
|
|
5041
|
+
let active = false;
|
|
5042
|
+
const onClick = (ev) => {
|
|
5043
|
+
if (!active) return;
|
|
5044
|
+
const target = ev.target instanceof Element ? ev.target : null;
|
|
5045
|
+
const tag = target?.tagName?.toLowerCase() ?? "unknown";
|
|
5046
|
+
const testId = target?.closest("[data-testid]")?.getAttribute("data-testid") ?? void 0;
|
|
5047
|
+
events.push({ type: "lite_click", timestamp: Date.now(), data: { tag, testId } });
|
|
5048
|
+
if (events.length > MAX_EVENTS) events.shift();
|
|
5049
|
+
};
|
|
5050
|
+
const stop = () => {
|
|
5051
|
+
active = false;
|
|
5052
|
+
document.removeEventListener("click", onClick, true);
|
|
5053
|
+
};
|
|
5054
|
+
return {
|
|
5055
|
+
start() {
|
|
5056
|
+
if (active) return;
|
|
5057
|
+
active = true;
|
|
5058
|
+
document.addEventListener("click", onClick, true);
|
|
5059
|
+
},
|
|
5060
|
+
stop,
|
|
5061
|
+
flush() {
|
|
5062
|
+
const cutoff = Date.now() - maxMs;
|
|
5063
|
+
return events.filter((e) => e.timestamp >= cutoff);
|
|
5064
|
+
},
|
|
5065
|
+
destroy() {
|
|
5066
|
+
stop();
|
|
5067
|
+
events.length = 0;
|
|
5068
|
+
}
|
|
5069
|
+
};
|
|
5070
|
+
}
|
|
5071
|
+
var rrwebModule = null;
|
|
5072
|
+
async function loadRrweb() {
|
|
5073
|
+
if (rrwebModule) return rrwebModule;
|
|
5074
|
+
try {
|
|
5075
|
+
const specifier = "rrweb";
|
|
5076
|
+
rrwebModule = await import(
|
|
5077
|
+
/* @vite-ignore */
|
|
5078
|
+
specifier
|
|
5079
|
+
);
|
|
5080
|
+
return rrwebModule;
|
|
5081
|
+
} catch {
|
|
5082
|
+
return null;
|
|
5083
|
+
}
|
|
5084
|
+
}
|
|
5085
|
+
async function createReplayCapture(opts) {
|
|
5086
|
+
const maxMs = opts.maxDurationMs ?? DEFAULT_MAX_MS;
|
|
5087
|
+
const rrweb = await loadRrweb();
|
|
5088
|
+
if (!rrweb?.record) {
|
|
5089
|
+
return createLiteReplay(maxMs);
|
|
5090
|
+
}
|
|
5091
|
+
const record = rrweb.record;
|
|
5092
|
+
const events = [];
|
|
5093
|
+
let stopFn = null;
|
|
5094
|
+
let recording = false;
|
|
5095
|
+
const maskSelectors = ['input[type="password"]', ...opts.redactSelectors ?? []];
|
|
5096
|
+
const stop = () => {
|
|
5097
|
+
stopFn?.();
|
|
5098
|
+
stopFn = null;
|
|
5099
|
+
recording = false;
|
|
5100
|
+
};
|
|
5101
|
+
return {
|
|
5102
|
+
start() {
|
|
5103
|
+
if (recording) return;
|
|
5104
|
+
recording = true;
|
|
5105
|
+
stopFn = record({
|
|
5106
|
+
emit(event) {
|
|
5107
|
+
events.push(event);
|
|
5108
|
+
trimReplayBuffer(events, maxMs, MAX_EVENTS);
|
|
5109
|
+
},
|
|
5110
|
+
maskAllInputs: true,
|
|
5111
|
+
// Mask rendered DOM text too — without this, rrweb records every
|
|
5112
|
+
// visible label (emails, names) as plaintext. Hosts that need richer
|
|
5113
|
+
// capture can opt out via their own rrweb integration.
|
|
5114
|
+
maskAllText: true,
|
|
5115
|
+
maskTextSelector: maskSelectors.join(","),
|
|
5116
|
+
// Re-emit a full snapshot roughly once per retained window so trimming
|
|
5117
|
+
// never leaves incrementals without a base snapshot.
|
|
5118
|
+
checkoutEveryNms: maxMs,
|
|
5119
|
+
sampling: { mousemove: false, mouseInteraction: true, scroll: 150, media: 800 }
|
|
5120
|
+
}) ?? null;
|
|
5121
|
+
},
|
|
5122
|
+
stop,
|
|
5123
|
+
flush() {
|
|
5124
|
+
trimReplayBuffer(events, maxMs, MAX_EVENTS);
|
|
5125
|
+
return [...events];
|
|
5126
|
+
},
|
|
5127
|
+
destroy() {
|
|
5128
|
+
stop();
|
|
5129
|
+
events.length = 0;
|
|
5130
|
+
}
|
|
5131
|
+
};
|
|
5132
|
+
}
|
|
5133
|
+
|
|
5134
|
+
// src/capture/screenshot-annotation.ts
|
|
5135
|
+
function createScreenshotAnnotation(imageDataUrl, container) {
|
|
5136
|
+
return new Promise((resolve, reject) => {
|
|
5137
|
+
const img = new Image();
|
|
5138
|
+
img.onload = () => {
|
|
5139
|
+
const wrap = document.createElement("div");
|
|
5140
|
+
wrap.style.position = "relative";
|
|
5141
|
+
wrap.style.display = "inline-block";
|
|
5142
|
+
wrap.style.maxWidth = "100%";
|
|
5143
|
+
const base = document.createElement("canvas");
|
|
5144
|
+
const overlay = document.createElement("canvas");
|
|
5145
|
+
const scale = Math.min(1, 720 / img.width);
|
|
5146
|
+
const w = Math.round(img.width * scale);
|
|
5147
|
+
const h = Math.round(img.height * scale);
|
|
5148
|
+
for (const c of [base, overlay]) {
|
|
5149
|
+
c.width = w;
|
|
5150
|
+
c.height = h;
|
|
5151
|
+
c.style.width = "100%";
|
|
5152
|
+
c.style.height = "auto";
|
|
5153
|
+
c.style.display = "block";
|
|
5154
|
+
}
|
|
5155
|
+
overlay.style.position = "absolute";
|
|
5156
|
+
overlay.style.left = "0";
|
|
5157
|
+
overlay.style.top = "0";
|
|
5158
|
+
overlay.style.cursor = "crosshair";
|
|
5159
|
+
const bctx = base.getContext("2d");
|
|
5160
|
+
const octx = overlay.getContext("2d");
|
|
5161
|
+
if (!bctx || !octx) {
|
|
5162
|
+
reject(new Error("Canvas not supported"));
|
|
5163
|
+
return;
|
|
5164
|
+
}
|
|
5165
|
+
bctx.drawImage(img, 0, 0, w, h);
|
|
5166
|
+
wrap.appendChild(base);
|
|
5167
|
+
wrap.appendChild(overlay);
|
|
5168
|
+
container.appendChild(wrap);
|
|
5169
|
+
let tool = "highlight";
|
|
5170
|
+
let drawing = false;
|
|
5171
|
+
let start = null;
|
|
5172
|
+
const strokes = [];
|
|
5173
|
+
const toLocal = (ev) => {
|
|
5174
|
+
const rect = overlay.getBoundingClientRect();
|
|
5175
|
+
const touch = "touches" in ev ? ev.touches[0] ?? ev.changedTouches[0] : null;
|
|
5176
|
+
const clientX = touch ? touch.clientX : ev.clientX;
|
|
5177
|
+
const clientY = touch ? touch.clientY : ev.clientY;
|
|
5178
|
+
return {
|
|
5179
|
+
x: (clientX - rect.left) / rect.width * w,
|
|
5180
|
+
y: (clientY - rect.top) / rect.height * h
|
|
5181
|
+
};
|
|
5182
|
+
};
|
|
5183
|
+
const redraw = () => {
|
|
5184
|
+
octx.clearRect(0, 0, w, h);
|
|
5185
|
+
for (const s of strokes) {
|
|
5186
|
+
if (s.tool === "highlight" && s.points.length >= 2) {
|
|
5187
|
+
const [a, b] = s.points;
|
|
5188
|
+
octx.fillStyle = "rgba(255, 230, 0, 0.35)";
|
|
5189
|
+
octx.fillRect(
|
|
5190
|
+
Math.min(a.x, b.x),
|
|
5191
|
+
Math.min(a.y, b.y),
|
|
5192
|
+
Math.abs(b.x - a.x),
|
|
5193
|
+
Math.abs(b.y - a.y)
|
|
5194
|
+
);
|
|
5195
|
+
} else if (s.tool === "arrow" && s.points.length >= 2) {
|
|
5196
|
+
const [a, b] = s.points;
|
|
5197
|
+
octx.strokeStyle = "#ef4444";
|
|
5198
|
+
octx.lineWidth = 3;
|
|
5199
|
+
octx.beginPath();
|
|
5200
|
+
octx.moveTo(a.x, a.y);
|
|
5201
|
+
octx.lineTo(b.x, b.y);
|
|
5202
|
+
octx.stroke();
|
|
5203
|
+
}
|
|
5204
|
+
}
|
|
5205
|
+
};
|
|
5206
|
+
const onDown = (ev) => {
|
|
5207
|
+
drawing = true;
|
|
5208
|
+
start = toLocal(ev);
|
|
5209
|
+
};
|
|
5210
|
+
const onUp = (ev) => {
|
|
5211
|
+
if (!drawing || !start) return;
|
|
5212
|
+
drawing = false;
|
|
5213
|
+
const end = toLocal(ev);
|
|
5214
|
+
if (tool === "blur") {
|
|
5215
|
+
const x = Math.min(start.x, end.x);
|
|
5216
|
+
const y = Math.min(start.y, end.y);
|
|
5217
|
+
const bw = Math.abs(end.x - start.x);
|
|
5218
|
+
const bh = Math.abs(end.y - start.y);
|
|
5219
|
+
if (bw >= 1 && bh >= 1) {
|
|
5220
|
+
bctx.filter = "blur(8px)";
|
|
5221
|
+
bctx.drawImage(base, x, y, bw, bh, x, y, bw, bh);
|
|
5222
|
+
bctx.filter = "none";
|
|
5223
|
+
}
|
|
5224
|
+
} else {
|
|
5225
|
+
strokes.push({ tool, points: [start, end] });
|
|
5226
|
+
}
|
|
5227
|
+
start = null;
|
|
5228
|
+
redraw();
|
|
5229
|
+
};
|
|
5230
|
+
overlay.addEventListener("mousedown", onDown);
|
|
5231
|
+
overlay.addEventListener("mouseup", onUp);
|
|
5232
|
+
overlay.addEventListener("touchstart", onDown, { passive: true });
|
|
5233
|
+
overlay.addEventListener("touchend", onUp);
|
|
5234
|
+
resolve({
|
|
5235
|
+
canvas: base,
|
|
5236
|
+
setTool(t) {
|
|
5237
|
+
tool = t;
|
|
5238
|
+
},
|
|
5239
|
+
getDataUrl() {
|
|
5240
|
+
const out = document.createElement("canvas");
|
|
5241
|
+
out.width = w;
|
|
5242
|
+
out.height = h;
|
|
5243
|
+
const ctx = out.getContext("2d");
|
|
5244
|
+
if (!ctx) return imageDataUrl;
|
|
5245
|
+
ctx.drawImage(base, 0, 0);
|
|
5246
|
+
ctx.drawImage(overlay, 0, 0);
|
|
5247
|
+
return out.toDataURL("image/jpeg", 0.85);
|
|
5248
|
+
},
|
|
5249
|
+
destroy() {
|
|
5250
|
+
overlay.removeEventListener("mousedown", onDown);
|
|
5251
|
+
overlay.removeEventListener("mouseup", onUp);
|
|
5252
|
+
overlay.removeEventListener("touchstart", onDown);
|
|
5253
|
+
overlay.removeEventListener("touchend", onUp);
|
|
5254
|
+
wrap.remove();
|
|
5255
|
+
}
|
|
5256
|
+
});
|
|
5257
|
+
};
|
|
5258
|
+
img.onerror = () => reject(new Error("Failed to load screenshot"));
|
|
5259
|
+
img.src = imageDataUrl;
|
|
5260
|
+
});
|
|
5261
|
+
}
|
|
5262
|
+
|
|
4822
5263
|
// src/sentry.ts
|
|
4823
5264
|
function getSentryGlobal() {
|
|
4824
5265
|
try {
|
|
@@ -5302,7 +5743,7 @@ function createProactiveManager(config = {}) {
|
|
|
5302
5743
|
|
|
5303
5744
|
// src/version.ts
|
|
5304
5745
|
var MUSHI_SDK_PACKAGE = "@mushi-mushi/web";
|
|
5305
|
-
var MUSHI_SDK_VERSION = "1.
|
|
5746
|
+
var MUSHI_SDK_VERSION = "1.12.0" ;
|
|
5306
5747
|
|
|
5307
5748
|
// src/mushi.ts
|
|
5308
5749
|
var instance = null;
|
|
@@ -5381,6 +5822,8 @@ function createInstance(config) {
|
|
|
5381
5822
|
let elementSelector = null;
|
|
5382
5823
|
let discoveryCap = null;
|
|
5383
5824
|
const timelineCap = createTimelineCapture();
|
|
5825
|
+
let replayCap = null;
|
|
5826
|
+
let replayGeneration = 0;
|
|
5384
5827
|
let widget;
|
|
5385
5828
|
function syncCaptureModules() {
|
|
5386
5829
|
if (activeConfig.capture?.console !== false) {
|
|
@@ -5464,6 +5907,25 @@ function createInstance(config) {
|
|
|
5464
5907
|
discoveryCap?.destroy();
|
|
5465
5908
|
discoveryCap = null;
|
|
5466
5909
|
}
|
|
5910
|
+
const replayMode = activeConfig.capture?.replay ?? "off";
|
|
5911
|
+
if (replayMode === "rrweb" || replayMode === "lite") {
|
|
5912
|
+
const generation = ++replayGeneration;
|
|
5913
|
+
void createReplayCapture({
|
|
5914
|
+
redactSelectors: activeConfig.privacy?.redactSelectors
|
|
5915
|
+
}).then((cap) => {
|
|
5916
|
+
if (generation !== replayGeneration) {
|
|
5917
|
+
cap.destroy();
|
|
5918
|
+
return;
|
|
5919
|
+
}
|
|
5920
|
+
replayCap?.destroy();
|
|
5921
|
+
replayCap = cap;
|
|
5922
|
+
replayCap.start();
|
|
5923
|
+
});
|
|
5924
|
+
} else {
|
|
5925
|
+
replayGeneration++;
|
|
5926
|
+
replayCap?.destroy();
|
|
5927
|
+
replayCap = null;
|
|
5928
|
+
}
|
|
5467
5929
|
}
|
|
5468
5930
|
const listeners = /* @__PURE__ */ new Map();
|
|
5469
5931
|
function emit(type, data) {
|
|
@@ -5525,6 +5987,7 @@ function createInstance(config) {
|
|
|
5525
5987
|
onOpen: () => {
|
|
5526
5988
|
log.debug("Widget opened");
|
|
5527
5989
|
void autoCaptureScreenshot("open");
|
|
5990
|
+
replayCap?.start();
|
|
5528
5991
|
emit("widget:opened");
|
|
5529
5992
|
},
|
|
5530
5993
|
onClose: () => {
|
|
@@ -5549,6 +6012,55 @@ function createInstance(config) {
|
|
|
5549
6012
|
pendingScreenshot = null;
|
|
5550
6013
|
widget.setScreenshotAttached(false);
|
|
5551
6014
|
},
|
|
6015
|
+
onScreenshotAnnotateRequest: async (container) => {
|
|
6016
|
+
if (!pendingScreenshot) return;
|
|
6017
|
+
if (container.childElementCount > 0) return;
|
|
6018
|
+
let session;
|
|
6019
|
+
try {
|
|
6020
|
+
session = await createScreenshotAnnotation(pendingScreenshot, container);
|
|
6021
|
+
} catch (err) {
|
|
6022
|
+
log.warn("Screenshot annotation failed", {
|
|
6023
|
+
error: err instanceof Error ? err.message : String(err)
|
|
6024
|
+
});
|
|
6025
|
+
return;
|
|
6026
|
+
}
|
|
6027
|
+
const toolbar = document.createElement("div");
|
|
6028
|
+
toolbar.className = "mushi-annotate-toolbar";
|
|
6029
|
+
const finish = (commit) => {
|
|
6030
|
+
{
|
|
6031
|
+
pendingScreenshot = session.getDataUrl();
|
|
6032
|
+
widget.setScreenshotAttached(true);
|
|
6033
|
+
}
|
|
6034
|
+
session.destroy();
|
|
6035
|
+
toolbar.remove();
|
|
6036
|
+
};
|
|
6037
|
+
const tools = [
|
|
6038
|
+
{ id: "highlight", label: "\u270F\uFE0F Highlight" },
|
|
6039
|
+
{ id: "blur", label: "\u{1F512} Blur" },
|
|
6040
|
+
{ id: "arrow", label: "\u2197\uFE0F Arrow" }
|
|
6041
|
+
];
|
|
6042
|
+
for (const toolDef of tools) {
|
|
6043
|
+
const btn = document.createElement("button");
|
|
6044
|
+
btn.type = "button";
|
|
6045
|
+
btn.className = "mushi-attach-btn";
|
|
6046
|
+
btn.dataset.tool = toolDef.id;
|
|
6047
|
+
btn.textContent = toolDef.label;
|
|
6048
|
+
btn.addEventListener("click", () => {
|
|
6049
|
+
session.setTool(toolDef.id);
|
|
6050
|
+
toolbar.querySelectorAll("button[data-tool]").forEach((b) => b.classList.remove("active"));
|
|
6051
|
+
btn.classList.add("active");
|
|
6052
|
+
});
|
|
6053
|
+
toolbar.appendChild(btn);
|
|
6054
|
+
}
|
|
6055
|
+
toolbar.querySelector('button[data-tool="highlight"]')?.classList.add("active");
|
|
6056
|
+
const doneBtn = document.createElement("button");
|
|
6057
|
+
doneBtn.type = "button";
|
|
6058
|
+
doneBtn.className = "mushi-attach-btn";
|
|
6059
|
+
doneBtn.textContent = "\u2713 Done";
|
|
6060
|
+
doneBtn.addEventListener("click", () => finish());
|
|
6061
|
+
toolbar.appendChild(doneBtn);
|
|
6062
|
+
container.insertBefore(toolbar, container.firstChild);
|
|
6063
|
+
},
|
|
5552
6064
|
onElementSelectorRequest: async () => {
|
|
5553
6065
|
if (!elementSelector || activeConfig.capture?.elementSelector === false) return;
|
|
5554
6066
|
log.debug("Element selector activated");
|
|
@@ -5581,6 +6093,26 @@ function createInstance(config) {
|
|
|
5581
6093
|
const result = await apiClient2.replyToReporterReport(reportId, getReporterToken(), body);
|
|
5582
6094
|
if (!result.ok) throw new Error(result.error?.message ?? "Could not send reply");
|
|
5583
6095
|
},
|
|
6096
|
+
async onReporterFeedback(reportId, signal, note) {
|
|
6097
|
+
const result = await apiClient2.replyToReporterReport(reportId, getReporterToken(), note ?? "", signal);
|
|
6098
|
+
if (!result.ok) throw new Error(result.error?.message ?? "Could not send feedback");
|
|
6099
|
+
return result.data?.feedback ?? null;
|
|
6100
|
+
},
|
|
6101
|
+
async onReporterReopen(reportId, note) {
|
|
6102
|
+
const result = await apiClient2.reopenReporterReport(reportId, getReporterToken(), note);
|
|
6103
|
+
if (!result.ok) throw new Error(result.error?.message ?? "Could not reopen report");
|
|
6104
|
+
return result.data?.outcome ?? null;
|
|
6105
|
+
},
|
|
6106
|
+
async onFeatureBoardRequest() {
|
|
6107
|
+
const result = await apiClient2.listReporterFeatureBoard(getReporterToken());
|
|
6108
|
+
if (!result.ok) throw new Error(result.error?.message ?? "Could not load community ideas");
|
|
6109
|
+
return result.data?.tickets ?? [];
|
|
6110
|
+
},
|
|
6111
|
+
async onFeatureBoardVote(requestId) {
|
|
6112
|
+
const result = await apiClient2.voteReporterFeatureBoard(requestId, getReporterToken());
|
|
6113
|
+
if (!result.ok) throw new Error(result.error?.message ?? "Could not vote");
|
|
6114
|
+
return result.data ?? { voted: true, action: "added" };
|
|
6115
|
+
},
|
|
5584
6116
|
onLeaderboardOpen() {
|
|
5585
6117
|
widget.setLeaderboard(null, true);
|
|
5586
6118
|
void fetchLeaderboard(10).then((entries) => {
|
|
@@ -5758,6 +6290,10 @@ function createInstance(config) {
|
|
|
5758
6290
|
...sentryCtx.breadcrumbs ? { breadcrumbs: scrubBreadcrumbsForWire(sentryCtx.breadcrumbs) } : {},
|
|
5759
6291
|
...sentryCtx.tags ? { tags: scrubTagsForWire(sentryCtx.tags) } : {}
|
|
5760
6292
|
} : void 0;
|
|
6293
|
+
const screenshotForWire = pendingScreenshot ? await compressScreenshotDataUrl(pendingScreenshot).catch(() => null) : null;
|
|
6294
|
+
if (pendingScreenshot && !screenshotForWire) {
|
|
6295
|
+
log.warn("Screenshot dropped \u2014 could not compress under wire budget");
|
|
6296
|
+
}
|
|
5761
6297
|
const report = {
|
|
5762
6298
|
id: newUuid(),
|
|
5763
6299
|
projectId: config.projectId,
|
|
@@ -5769,8 +6305,9 @@ function createInstance(config) {
|
|
|
5769
6305
|
networkLogs,
|
|
5770
6306
|
performanceMetrics: activeConfig.capture?.performance === false ? void 0 : perfCap?.getMetrics(),
|
|
5771
6307
|
timeline: timelineCap.getEntries({ consoleLogs, networkLogs }),
|
|
5772
|
-
screenshotDataUrl:
|
|
6308
|
+
screenshotDataUrl: screenshotForWire ?? void 0,
|
|
5773
6309
|
selectedElement: pendingElement ?? void 0,
|
|
6310
|
+
...replayCap ? { replayEvents: replayCap.flush() } : {},
|
|
5774
6311
|
metadata: {
|
|
5775
6312
|
...customMetadata,
|
|
5776
6313
|
...userInfo ? { user: userInfo } : {},
|
|
@@ -5844,7 +6381,23 @@ function createInstance(config) {
|
|
|
5844
6381
|
emit("report:queued", { reportId: finalReport.id });
|
|
5845
6382
|
return { reportId: null, queuedOffline: true };
|
|
5846
6383
|
}
|
|
5847
|
-
|
|
6384
|
+
let result = await apiClient2.submitReport(finalReport);
|
|
6385
|
+
if (!result.ok && result.error?.code === "PAYLOAD_TOO_LARGE") {
|
|
6386
|
+
if (Array.isArray(finalReport.replayEvents) && finalReport.replayEvents.length > 0) {
|
|
6387
|
+
log.warn("Report too large \u2014 dropping replay buffer and retrying", {
|
|
6388
|
+
reportId: finalReport.id
|
|
6389
|
+
});
|
|
6390
|
+
finalReport = { ...finalReport, replayEvents: void 0 };
|
|
6391
|
+
result = await apiClient2.submitReport(finalReport);
|
|
6392
|
+
}
|
|
6393
|
+
if (!result.ok && result.error?.code === "PAYLOAD_TOO_LARGE" && finalReport.screenshotDataUrl) {
|
|
6394
|
+
log.warn("Report still too large \u2014 dropping screenshot and retrying", {
|
|
6395
|
+
reportId: finalReport.id
|
|
6396
|
+
});
|
|
6397
|
+
finalReport = { ...finalReport, screenshotDataUrl: void 0 };
|
|
6398
|
+
result = await apiClient2.submitReport(finalReport);
|
|
6399
|
+
}
|
|
6400
|
+
}
|
|
5848
6401
|
if (result.ok) {
|
|
5849
6402
|
log.info("Report sent", { reportId: result.data?.reportId });
|
|
5850
6403
|
emit("report:sent", { reportId: result.data?.reportId });
|
|
@@ -5867,6 +6420,17 @@ function createInstance(config) {
|
|
|
5867
6420
|
}
|
|
5868
6421
|
} catch {
|
|
5869
6422
|
}
|
|
6423
|
+
} else if (result.error?.code === "PAYLOAD_TOO_LARGE" || result.error?.code === "SERIALIZE_FAILED") {
|
|
6424
|
+
log.warn("Report exceeds size limit after degradation \u2014 dropping", {
|
|
6425
|
+
reportId: finalReport.id,
|
|
6426
|
+
error: result.error
|
|
6427
|
+
});
|
|
6428
|
+
emit("report:failed", { reportId: finalReport.id, error: result.error });
|
|
6429
|
+
breadcrumbs.add({
|
|
6430
|
+
category: "lifecycle",
|
|
6431
|
+
level: "error",
|
|
6432
|
+
message: `Mushi report dropped \u2014 payload too large (${finalReport.id})`
|
|
6433
|
+
});
|
|
5870
6434
|
} else {
|
|
5871
6435
|
log.warn("Report failed, queuing for retry", { reportId: finalReport.id, error: result.error });
|
|
5872
6436
|
await offlineQueue.enqueue(finalReport);
|
|
@@ -5952,6 +6516,8 @@ function createInstance(config) {
|
|
|
5952
6516
|
timelineCap.destroy();
|
|
5953
6517
|
discoveryCap?.destroy();
|
|
5954
6518
|
discoveryCap = null;
|
|
6519
|
+
replayCap?.destroy();
|
|
6520
|
+
replayCap = null;
|
|
5955
6521
|
offlineQueue.stopAutoSync();
|
|
5956
6522
|
detachAutoBreadcrumbs?.();
|
|
5957
6523
|
detachAutoBreadcrumbs = null;
|
|
@@ -6157,6 +6723,19 @@ function createInstance(config) {
|
|
|
6157
6723
|
if (!result.ok) return null;
|
|
6158
6724
|
return result.data?.comment ?? null;
|
|
6159
6725
|
},
|
|
6726
|
+
async submitFeedbackSignal(reportId, signal, note) {
|
|
6727
|
+
const result = await apiClient2.replyToReporterReport(reportId, getReporterToken(), note ?? "", signal);
|
|
6728
|
+
if (!result.ok) return null;
|
|
6729
|
+
return result.data?.feedback ?? null;
|
|
6730
|
+
},
|
|
6731
|
+
async reopenReport(reportId, note) {
|
|
6732
|
+
const result = await apiClient2.reopenReporterReport(reportId, getReporterToken(), note);
|
|
6733
|
+
if (!result.ok) return null;
|
|
6734
|
+
return result.data?.outcome ?? null;
|
|
6735
|
+
},
|
|
6736
|
+
openMyReports() {
|
|
6737
|
+
widget.recorderOpenMyReports();
|
|
6738
|
+
},
|
|
6160
6739
|
async getHallOfFame(limit = 20) {
|
|
6161
6740
|
const result = await apiClient2.getHallOfFame(limit);
|
|
6162
6741
|
if (!result.ok) return [];
|
|
@@ -6483,6 +7062,10 @@ function createNoopInstance() {
|
|
|
6483
7062
|
listMyReports: async () => [],
|
|
6484
7063
|
listMyComments: async () => [],
|
|
6485
7064
|
replyToReport: async () => null,
|
|
7065
|
+
submitFeedbackSignal: async () => null,
|
|
7066
|
+
reopenReport: async () => null,
|
|
7067
|
+
openMyReports: () => {
|
|
7068
|
+
},
|
|
6486
7069
|
getHallOfFame: async () => []
|
|
6487
7070
|
};
|
|
6488
7071
|
}
|