@mhosaic/feedback 0.14.0 → 0.15.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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  scrubCredentials
3
- } from "./chunk-DKE6ELJ4.mjs";
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 && /* @__PURE__ */ jsx2(
984
- "a",
985
- {
986
- class: "report-detail-screenshot",
987
- href: detail.screenshot_url,
988
- target: "_blank",
989
- rel: "noopener noreferrer",
990
- children: /* @__PURE__ */ jsx2("img", { src: detail.screenshot_url, alt: "", loading: "lazy" })
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 && /* @__PURE__ */ jsxs2("div", { class: "report-detail-context-line", title: detail.page_url, children: [
1052
- /* @__PURE__ */ jsx2("span", { class: "report-detail-context-label", children: strings["detail.context.page"] }),
1053
- /* @__PURE__ */ jsx2(
1054
- "a",
1055
- {
1056
- class: "report-detail-context-url",
1057
- href: detail.page_url,
1058
- target: "_blank",
1059
- rel: "noopener noreferrer",
1060
- children: truncateUrl(detail.page_url, 64)
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) {
@@ -4583,7 +4621,10 @@ function createFeedback(config) {
4583
4621
  async function buildAndSubmit(values) {
4584
4622
  const manualScreenshot = values.synthetic ? void 0 : values.screenshot;
4585
4623
  const technical_context = capture.snapshot();
4586
- if (user) technical_context.user = user;
4624
+ if (user) {
4625
+ const { userHash: _hash, exp: _exp, ...safeUser } = user;
4626
+ technical_context.user = safeUser;
4627
+ }
4587
4628
  if (metadata && Object.keys(metadata).length > 0) {
4588
4629
  technical_context.metadata = { ...metadata };
4589
4630
  }
@@ -4598,8 +4639,8 @@ function createFeedback(config) {
4598
4639
  capture_method: captureMethod,
4599
4640
  technical_context
4600
4641
  };
4601
- if ("0.14.0") {
4602
- payload.widget_version = "0.14.0";
4642
+ if ("0.15.0") {
4643
+ payload.widget_version = "0.15.0";
4603
4644
  }
4604
4645
  if (manualScreenshot) payload.screenshot = manualScreenshot;
4605
4646
  if (values.synthetic) payload.synthetic = true;
@@ -4685,4 +4726,4 @@ function createFeedback(config) {
4685
4726
  export {
4686
4727
  createFeedback
4687
4728
  };
4688
- //# sourceMappingURL=chunk-A4MBOFRR.mjs.map
4729
+ //# sourceMappingURL=chunk-67JM6XVT.mjs.map