@mhosaic/feedback 0.14.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-A4MBOFRR.mjs → chunk-3YLD2AJI.mjs} +83 -31
- package/dist/chunk-3YLD2AJI.mjs.map +1 -0
- package/dist/{chunk-DKE6ELJ4.mjs → chunk-FGA63IEZ.mjs} +16 -4
- package/dist/chunk-FGA63IEZ.mjs.map +1 -0
- package/dist/embed.min.js +18 -7
- package/dist/embed.min.js.map +1 -1
- package/dist/error-tracking.d.ts +19 -0
- package/dist/error-tracking.mjs +23 -3
- package/dist/error-tracking.mjs.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/react.mjs +2 -2
- package/dist/webvitals.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-A4MBOFRR.mjs.map +0 -1
- package/dist/chunk-DKE6ELJ4.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
scrubCredentials
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-FGA63IEZ.mjs";
|
|
4
4
|
|
|
5
5
|
// src/api/client.ts
|
|
6
6
|
var SCALAR_FIELDS = [
|
|
@@ -12,6 +12,23 @@ var SCALAR_FIELDS = [
|
|
|
12
12
|
"user_agent",
|
|
13
13
|
"capture_method"
|
|
14
14
|
];
|
|
15
|
+
function assertSafeEndpoint(endpoint) {
|
|
16
|
+
let parsed;
|
|
17
|
+
try {
|
|
18
|
+
parsed = new URL(endpoint);
|
|
19
|
+
} catch {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`[mhosaic-feedback] \`endpoint\` is not a valid URL: ${endpoint}`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
if (parsed.protocol === "https:") return;
|
|
25
|
+
if (parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]")) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
throw new Error(
|
|
29
|
+
`[mhosaic-feedback] \`endpoint\` must use https:// (got ${parsed.protocol}//${parsed.hostname}). http:// is only allowed for localhost in dev.`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
15
32
|
function createApiClient(options) {
|
|
16
33
|
const endpoint = (options.endpoint ?? "").replace(/\/+$/, "");
|
|
17
34
|
if (!endpoint) {
|
|
@@ -19,6 +36,7 @@ function createApiClient(options) {
|
|
|
19
36
|
'[mhosaic-feedback] `endpoint` is required (e.g. "https://feedback.example.com").'
|
|
20
37
|
);
|
|
21
38
|
}
|
|
39
|
+
assertSafeEndpoint(endpoint);
|
|
22
40
|
const fetcher = options.fetch ?? globalThis.fetch;
|
|
23
41
|
async function submitReport(input) {
|
|
24
42
|
let payload = input;
|
|
@@ -872,6 +890,20 @@ function truncateUrl(url, maxLength = 80) {
|
|
|
872
890
|
const keepEnd = maxLength - 1 - keepStart;
|
|
873
891
|
return `${url.slice(0, keepStart)}\u2026${url.slice(url.length - keepEnd)}`;
|
|
874
892
|
}
|
|
893
|
+
function safeExternalHref(url) {
|
|
894
|
+
if (!url) return void 0;
|
|
895
|
+
let parsed;
|
|
896
|
+
try {
|
|
897
|
+
parsed = new URL(url);
|
|
898
|
+
} catch {
|
|
899
|
+
return void 0;
|
|
900
|
+
}
|
|
901
|
+
if (parsed.protocol === "https:") return parsed.toString();
|
|
902
|
+
if (parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]")) {
|
|
903
|
+
return parsed.toString();
|
|
904
|
+
}
|
|
905
|
+
return void 0;
|
|
906
|
+
}
|
|
875
907
|
|
|
876
908
|
// src/widget/ReportDetailView.tsx
|
|
877
909
|
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
|
|
@@ -980,16 +1012,19 @@ function ReportDetailView({
|
|
|
980
1012
|
/* @__PURE__ */ jsxs2("div", { class: "report-detail-body", children: [
|
|
981
1013
|
/* @__PURE__ */ jsx2(ContextBlock, { detail, strings }),
|
|
982
1014
|
/* @__PURE__ */ jsx2("p", { class: "report-detail-description", children: detail.description }),
|
|
983
|
-
detail.screenshot_url &&
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1015
|
+
detail.screenshot_url && (() => {
|
|
1016
|
+
const safeHref = safeExternalHref(detail.screenshot_url);
|
|
1017
|
+
return safeHref ? /* @__PURE__ */ jsx2(
|
|
1018
|
+
"a",
|
|
1019
|
+
{
|
|
1020
|
+
class: "report-detail-screenshot",
|
|
1021
|
+
href: safeHref,
|
|
1022
|
+
target: "_blank",
|
|
1023
|
+
rel: "noopener noreferrer",
|
|
1024
|
+
children: /* @__PURE__ */ jsx2("img", { src: detail.screenshot_url, alt: "", loading: "lazy" })
|
|
1025
|
+
}
|
|
1026
|
+
) : /* @__PURE__ */ jsx2("div", { class: "report-detail-screenshot", children: /* @__PURE__ */ jsx2("img", { src: detail.screenshot_url, alt: "", loading: "lazy" }) });
|
|
1027
|
+
})(),
|
|
993
1028
|
/* @__PURE__ */ jsx2("h3", { class: "report-detail-section", children: strings["detail.thread"] }),
|
|
994
1029
|
detail.comments.length === 0 ? /* @__PURE__ */ jsx2("p", { class: "report-detail-empty", children: strings["detail.no_replies"] }) : /* @__PURE__ */ jsx2("ul", { class: "report-comments", children: detail.comments.map((c) => /* @__PURE__ */ jsx2("li", { children: /* @__PURE__ */ jsx2(CommentBubble, { comment: c, strings }) })) }),
|
|
995
1030
|
detail.status_history && detail.status_history.length > 0 && /* @__PURE__ */ jsx2(StatusHistorySection, { rows: detail.status_history, strings }),
|
|
@@ -1048,19 +1083,22 @@ function ContextBlock({ detail, strings }) {
|
|
|
1048
1083
|
/* @__PURE__ */ jsx2("span", { class: "report-detail-context-label", children: strings["detail.context.submitted_at"] }),
|
|
1049
1084
|
/* @__PURE__ */ jsx2("span", { children: formatSubmittedAt(detail.created_at) })
|
|
1050
1085
|
] }),
|
|
1051
|
-
detail.page_url &&
|
|
1052
|
-
|
|
1053
|
-
/* @__PURE__ */
|
|
1054
|
-
"
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1086
|
+
detail.page_url && (() => {
|
|
1087
|
+
const safeHref = safeExternalHref(detail.page_url);
|
|
1088
|
+
return /* @__PURE__ */ jsxs2("div", { class: "report-detail-context-line", title: detail.page_url, children: [
|
|
1089
|
+
/* @__PURE__ */ jsx2("span", { class: "report-detail-context-label", children: strings["detail.context.page"] }),
|
|
1090
|
+
safeHref ? /* @__PURE__ */ jsx2(
|
|
1091
|
+
"a",
|
|
1092
|
+
{
|
|
1093
|
+
class: "report-detail-context-url",
|
|
1094
|
+
href: safeHref,
|
|
1095
|
+
target: "_blank",
|
|
1096
|
+
rel: "noopener noreferrer",
|
|
1097
|
+
children: truncateUrl(detail.page_url, 64)
|
|
1098
|
+
}
|
|
1099
|
+
) : /* @__PURE__ */ jsx2("span", { class: "report-detail-context-url", children: truncateUrl(detail.page_url, 64) })
|
|
1100
|
+
] });
|
|
1101
|
+
})()
|
|
1064
1102
|
] });
|
|
1065
1103
|
}
|
|
1066
1104
|
function formatSubmittedAt(iso) {
|
|
@@ -3934,9 +3972,16 @@ var WIDGET_STYLES = `
|
|
|
3934
3972
|
* Width/height/max-* are animated together; padding too, since the
|
|
3935
3973
|
* board's denser layout needs less of the modal's default padding. */
|
|
3936
3974
|
.modal.is-expanded {
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3975
|
+
/* Margin around the expanded board view. --mfb-space-7 (48px) matches
|
|
3976
|
+
* the breathing room of the default modal \u2014 24px top + 24px bottom +
|
|
3977
|
+
* 24px on each side \u2014 so the panel reads as "near-fullscreen with a
|
|
3978
|
+
* visible frame" instead of "the whole viewport, content cut off".
|
|
3979
|
+
* Was --mfb-space-5 (24px total = 12px each side), which felt
|
|
3980
|
+
* claustrophobic and tricked users into expecting more vertical
|
|
3981
|
+
* scroll under the viewport edge. */
|
|
3982
|
+
width: min(1280px, calc(100vw - var(--mfb-space-7)));
|
|
3983
|
+
max-height: calc(100vh - var(--mfb-space-7));
|
|
3984
|
+
height: calc(100vh - var(--mfb-space-7));
|
|
3940
3985
|
padding: var(--mfb-space-5);
|
|
3941
3986
|
transition:
|
|
3942
3987
|
width 320ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
@@ -3950,6 +3995,10 @@ var WIDGET_STYLES = `
|
|
|
3950
3995
|
.backdrop.is-expanded { /* hook for any backdrop-level overrides */ }
|
|
3951
3996
|
|
|
3952
3997
|
@media (max-width: 640px) {
|
|
3998
|
+
/* On mobile the sheet still snaps to the bottom edge and stretches
|
|
3999
|
+
* almost the full viewport \u2014 24px gap from the top is enough to
|
|
4000
|
+
* convey "this is a panel, not a takeover", but we don't pull margin
|
|
4001
|
+
* off the sides where users expect the sheet to fill the viewport. */
|
|
3953
4002
|
.modal.is-expanded {
|
|
3954
4003
|
width: 100vw;
|
|
3955
4004
|
height: calc(100vh - var(--mfb-space-5));
|
|
@@ -4583,7 +4632,10 @@ function createFeedback(config) {
|
|
|
4583
4632
|
async function buildAndSubmit(values) {
|
|
4584
4633
|
const manualScreenshot = values.synthetic ? void 0 : values.screenshot;
|
|
4585
4634
|
const technical_context = capture.snapshot();
|
|
4586
|
-
if (user)
|
|
4635
|
+
if (user) {
|
|
4636
|
+
const { userHash: _hash, exp: _exp, ...safeUser } = user;
|
|
4637
|
+
technical_context.user = safeUser;
|
|
4638
|
+
}
|
|
4587
4639
|
if (metadata && Object.keys(metadata).length > 0) {
|
|
4588
4640
|
technical_context.metadata = { ...metadata };
|
|
4589
4641
|
}
|
|
@@ -4598,8 +4650,8 @@ function createFeedback(config) {
|
|
|
4598
4650
|
capture_method: captureMethod,
|
|
4599
4651
|
technical_context
|
|
4600
4652
|
};
|
|
4601
|
-
if ("0.
|
|
4602
|
-
payload.widget_version = "0.
|
|
4653
|
+
if ("0.15.1") {
|
|
4654
|
+
payload.widget_version = "0.15.1";
|
|
4603
4655
|
}
|
|
4604
4656
|
if (manualScreenshot) payload.screenshot = manualScreenshot;
|
|
4605
4657
|
if (values.synthetic) payload.synthetic = true;
|
|
@@ -4685,4 +4737,4 @@ function createFeedback(config) {
|
|
|
4685
4737
|
export {
|
|
4686
4738
|
createFeedback
|
|
4687
4739
|
};
|
|
4688
|
-
//# sourceMappingURL=chunk-
|
|
4740
|
+
//# sourceMappingURL=chunk-3YLD2AJI.mjs.map
|