@mhosaic/feedback 0.16.2 → 0.18.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/dist/chunk-4QOIFIT5.mjs +50 -0
- package/dist/chunk-4QOIFIT5.mjs.map +1 -0
- package/dist/{chunk-I4GWTJ6Q.mjs → chunk-4QQI5ZYD.mjs} +60 -52
- package/dist/chunk-4QQI5ZYD.mjs.map +1 -0
- package/dist/{chunk-6JDT2KWQ.mjs → chunk-IP4Y5EZE.mjs} +14 -2
- package/dist/chunk-IP4Y5EZE.mjs.map +1 -0
- package/dist/chunk-QJ562NTG.mjs +145 -0
- package/dist/chunk-QJ562NTG.mjs.map +1 -0
- package/dist/embed.min.js +4 -4
- package/dist/embed.min.js.map +1 -1
- package/dist/error-tracking.d.ts +1 -1
- package/dist/error-tracking.mjs +3 -138
- package/dist/error-tracking.mjs.map +1 -1
- package/dist/{index-snrtK3_E.d.ts → index-Bq4QI0xa.d.ts} +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +2 -1
- package/dist/loader/react.d.ts +13 -2
- package/dist/loader/react.mjs +7 -3
- package/dist/loader/react.mjs.map +1 -1
- package/dist/loader.d.ts +16 -3
- package/dist/loader.min.js +1 -1
- package/dist/loader.min.js.map +1 -1
- package/dist/loader.mjs +1 -1
- package/dist/react.d.ts +11 -3
- package/dist/react.mjs +13 -2
- package/dist/react.mjs.map +1 -1
- package/dist/replay.d.ts +1 -1
- package/dist/telemetry.d.ts +53 -0
- package/dist/telemetry.mjs +166 -0
- package/dist/telemetry.mjs.map +1 -0
- package/dist/{types-BrsktaE1.d.ts → types-2dZ9bHAn.d.ts} +4 -0
- package/dist/webvitals.d.ts +1 -1
- package/dist/widget.min.js +6 -4
- package/dist/widget.min.js.map +1 -1
- package/package.json +5 -1
- package/dist/chunk-6JDT2KWQ.mjs.map +0 -1
- package/dist/chunk-I4GWTJ6Q.mjs.map +0 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/capture/urlSanitizer.ts
|
|
2
|
+
var SENSITIVE_NAME = /token|key|password|secret|auth|session|sig|credential|bearer|cookie/i;
|
|
3
|
+
var SENSITIVE_VALUE_PATTERNS = [
|
|
4
|
+
// JWT: three base64url segments separated by dots, header begins with eyJ.
|
|
5
|
+
/^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/,
|
|
6
|
+
// Mhosaic feedback keys (the project's own format).
|
|
7
|
+
/^(?:sk|pk)_proj_[A-Za-z0-9_-]{16,}$/,
|
|
8
|
+
// GitHub PATs (ghp/gho/ghu/ghs/ghr prefixes, 36+ chars).
|
|
9
|
+
/^gh[pousr]_[A-Za-z0-9]{30,}$/,
|
|
10
|
+
// Stripe live/test secret keys.
|
|
11
|
+
/^(?:sk|pk|rk|whsec)_(?:live|test)_[A-Za-z0-9]{16,}$/,
|
|
12
|
+
// AWS access key id.
|
|
13
|
+
/^AKIA[0-9A-Z]{12,}$/,
|
|
14
|
+
// Slack bot tokens.
|
|
15
|
+
/^xox[abprso]-[A-Za-z0-9-]{12,}$/,
|
|
16
|
+
// Generic high-entropy: hex 40+ chars (SHA-1, SHA-256) or long alnum 32+.
|
|
17
|
+
/^[a-f0-9]{40,}$/i,
|
|
18
|
+
// Long opaque base64-ish string (common for OAuth codes & session ids).
|
|
19
|
+
/^[A-Za-z0-9_-]{40,}$/
|
|
20
|
+
];
|
|
21
|
+
function looksLikeCredential(value) {
|
|
22
|
+
return SENSITIVE_VALUE_PATTERNS.some((re) => re.test(value));
|
|
23
|
+
}
|
|
24
|
+
function sanitizeUrl(url) {
|
|
25
|
+
try {
|
|
26
|
+
const isAbsolute = /^https?:\/\//i.test(url);
|
|
27
|
+
const isRootRelative = url.startsWith("/");
|
|
28
|
+
if (!isAbsolute && !isRootRelative) return url;
|
|
29
|
+
const base = typeof window !== "undefined" ? window.location.origin : "http://localhost";
|
|
30
|
+
const u = new URL(url, base);
|
|
31
|
+
const clean = new URLSearchParams();
|
|
32
|
+
u.searchParams.forEach((value, name) => {
|
|
33
|
+
if (SENSITIVE_NAME.test(name) || looksLikeCredential(value)) {
|
|
34
|
+
clean.set(name, "[redacted]");
|
|
35
|
+
} else {
|
|
36
|
+
clean.set(name, value);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
u.search = clean.toString();
|
|
40
|
+
u.hash = u.hash ? "#[redacted]" : "";
|
|
41
|
+
return u.toString();
|
|
42
|
+
} catch {
|
|
43
|
+
return url;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
sanitizeUrl
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=chunk-4QOIFIT5.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/capture/urlSanitizer.ts"],"sourcesContent":["/**\n * URL sanitizer for captured network request URLs.\n *\n * Two layers of defense:\n *\n * 1) Param NAME match — anything whose name suggests a credential is\n * redacted unconditionally (token, key, password, secret, auth, etc.).\n *\n * 2) Param VALUE shape match — even when the name is innocent (\"id\",\n * \"redirect_uri\", \"context\"), if the VALUE looks like a JWT, a\n * Bearer token, an mhosaic-feedback API key, or a long opaque hex/b64\n * string we redact it. This catches the realistic case where a backend\n * routes credentials through generic-named params or where a careless\n * developer puts a token in a URL fragment for \"convenience.\"\n *\n * The path portion is preserved as-is — its purpose is operator-side\n * \"which page was this report filed from\", and stripping it would\n * destroy that signal. Hosts that route secrets through path segments\n * (uncommon) should mark those routes for the widget to skip via the\n * sanitizeUrl hook in createFeedback().\n */\n\nconst SENSITIVE_NAME = /token|key|password|secret|auth|session|sig|credential|bearer|cookie/i\n\nconst SENSITIVE_VALUE_PATTERNS: RegExp[] = [\n // JWT: three base64url segments separated by dots, header begins with eyJ.\n /^eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$/,\n // Mhosaic feedback keys (the project's own format).\n /^(?:sk|pk)_proj_[A-Za-z0-9_-]{16,}$/,\n // GitHub PATs (ghp/gho/ghu/ghs/ghr prefixes, 36+ chars).\n /^gh[pousr]_[A-Za-z0-9]{30,}$/,\n // Stripe live/test secret keys.\n /^(?:sk|pk|rk|whsec)_(?:live|test)_[A-Za-z0-9]{16,}$/,\n // AWS access key id.\n /^AKIA[0-9A-Z]{12,}$/,\n // Slack bot tokens.\n /^xox[abprso]-[A-Za-z0-9-]{12,}$/,\n // Generic high-entropy: hex 40+ chars (SHA-1, SHA-256) or long alnum 32+.\n /^[a-f0-9]{40,}$/i,\n // Long opaque base64-ish string (common for OAuth codes & session ids).\n /^[A-Za-z0-9_-]{40,}$/,\n]\n\nfunction looksLikeCredential(value: string): boolean {\n return SENSITIVE_VALUE_PATTERNS.some((re) => re.test(value))\n}\n\nexport function sanitizeUrl(url: string): string {\n try {\n // Accept absolute URLs or root-relative paths; reject everything else\n const isAbsolute = /^https?:\\/\\//i.test(url)\n const isRootRelative = url.startsWith('/')\n if (!isAbsolute && !isRootRelative) return url\n\n const base = typeof window !== 'undefined' ? window.location.origin : 'http://localhost'\n const u = new URL(url, base)\n const clean = new URLSearchParams()\n u.searchParams.forEach((value, name) => {\n if (SENSITIVE_NAME.test(name) || looksLikeCredential(value)) {\n clean.set(name, '[redacted]')\n } else {\n clean.set(name, value)\n }\n })\n u.search = clean.toString()\n // Hash fragments occasionally carry credentials (OAuth implicit flow):\n // never log them.\n u.hash = u.hash ? '#[redacted]' : ''\n return u.toString()\n } catch {\n return url\n }\n}\n"],"mappings":";AAsBA,IAAM,iBAAiB;AAEvB,IAAM,2BAAqC;AAAA;AAAA,EAEzC;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AACF;AAEA,SAAS,oBAAoB,OAAwB;AACnD,SAAO,yBAAyB,KAAK,CAAC,OAAO,GAAG,KAAK,KAAK,CAAC;AAC7D;AAEO,SAAS,YAAY,KAAqB;AAC/C,MAAI;AAEF,UAAM,aAAa,gBAAgB,KAAK,GAAG;AAC3C,UAAM,iBAAiB,IAAI,WAAW,GAAG;AACzC,QAAI,CAAC,cAAc,CAAC,eAAgB,QAAO;AAE3C,UAAM,OAAO,OAAO,WAAW,cAAc,OAAO,SAAS,SAAS;AACtE,UAAM,IAAI,IAAI,IAAI,KAAK,IAAI;AAC3B,UAAM,QAAQ,IAAI,gBAAgB;AAClC,MAAE,aAAa,QAAQ,CAAC,OAAO,SAAS;AACtC,UAAI,eAAe,KAAK,IAAI,KAAK,oBAAoB,KAAK,GAAG;AAC3D,cAAM,IAAI,MAAM,YAAY;AAAA,MAC9B,OAAO;AACL,cAAM,IAAI,MAAM,KAAK;AAAA,MACvB;AAAA,IACF,CAAC;AACD,MAAE,SAAS,MAAM,SAAS;AAG1B,MAAE,OAAO,EAAE,OAAO,gBAAgB;AAClC,WAAO,EAAE,SAAS;AAAA,EACpB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sanitizeUrl
|
|
3
|
+
} from "./chunk-4QOIFIT5.mjs";
|
|
1
4
|
import {
|
|
2
5
|
scrubCredentials
|
|
3
6
|
} from "./chunk-FGA63IEZ.mjs";
|
|
@@ -206,8 +209,27 @@ function createApiClient(options) {
|
|
|
206
209
|
}
|
|
207
210
|
return response.json();
|
|
208
211
|
}
|
|
212
|
+
async function checkVisibility(externalId) {
|
|
213
|
+
const response = await fetcher(
|
|
214
|
+
`${endpoint}/api/feedback/v1/widget/visibility/`,
|
|
215
|
+
{
|
|
216
|
+
method: "POST",
|
|
217
|
+
headers: {
|
|
218
|
+
...widgetHeaders(externalId),
|
|
219
|
+
"Content-Type": "application/json"
|
|
220
|
+
},
|
|
221
|
+
body: JSON.stringify({ external_id: externalId })
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
if (!response.ok) {
|
|
225
|
+
throw new Error(`checkVisibility failed: ${response.status}`);
|
|
226
|
+
}
|
|
227
|
+
const data = await response.json();
|
|
228
|
+
return Boolean(data.show);
|
|
229
|
+
}
|
|
209
230
|
return {
|
|
210
231
|
submitReport,
|
|
232
|
+
checkVisibility,
|
|
211
233
|
listMine,
|
|
212
234
|
listChangelog,
|
|
213
235
|
getReport,
|
|
@@ -218,52 +240,6 @@ function createApiClient(options) {
|
|
|
218
240
|
};
|
|
219
241
|
}
|
|
220
242
|
|
|
221
|
-
// src/capture/urlSanitizer.ts
|
|
222
|
-
var SENSITIVE_NAME = /token|key|password|secret|auth|session|sig|credential|bearer|cookie/i;
|
|
223
|
-
var SENSITIVE_VALUE_PATTERNS = [
|
|
224
|
-
// JWT: three base64url segments separated by dots, header begins with eyJ.
|
|
225
|
-
/^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/,
|
|
226
|
-
// Mhosaic feedback keys (the project's own format).
|
|
227
|
-
/^(?:sk|pk)_proj_[A-Za-z0-9_-]{16,}$/,
|
|
228
|
-
// GitHub PATs (ghp/gho/ghu/ghs/ghr prefixes, 36+ chars).
|
|
229
|
-
/^gh[pousr]_[A-Za-z0-9]{30,}$/,
|
|
230
|
-
// Stripe live/test secret keys.
|
|
231
|
-
/^(?:sk|pk|rk|whsec)_(?:live|test)_[A-Za-z0-9]{16,}$/,
|
|
232
|
-
// AWS access key id.
|
|
233
|
-
/^AKIA[0-9A-Z]{12,}$/,
|
|
234
|
-
// Slack bot tokens.
|
|
235
|
-
/^xox[abprso]-[A-Za-z0-9-]{12,}$/,
|
|
236
|
-
// Generic high-entropy: hex 40+ chars (SHA-1, SHA-256) or long alnum 32+.
|
|
237
|
-
/^[a-f0-9]{40,}$/i,
|
|
238
|
-
// Long opaque base64-ish string (common for OAuth codes & session ids).
|
|
239
|
-
/^[A-Za-z0-9_-]{40,}$/
|
|
240
|
-
];
|
|
241
|
-
function looksLikeCredential(value) {
|
|
242
|
-
return SENSITIVE_VALUE_PATTERNS.some((re) => re.test(value));
|
|
243
|
-
}
|
|
244
|
-
function sanitizeUrl(url) {
|
|
245
|
-
try {
|
|
246
|
-
const isAbsolute = /^https?:\/\//i.test(url);
|
|
247
|
-
const isRootRelative = url.startsWith("/");
|
|
248
|
-
if (!isAbsolute && !isRootRelative) return url;
|
|
249
|
-
const base = typeof window !== "undefined" ? window.location.origin : "http://localhost";
|
|
250
|
-
const u = new URL(url, base);
|
|
251
|
-
const clean = new URLSearchParams();
|
|
252
|
-
u.searchParams.forEach((value, name) => {
|
|
253
|
-
if (SENSITIVE_NAME.test(name) || looksLikeCredential(value)) {
|
|
254
|
-
clean.set(name, "[redacted]");
|
|
255
|
-
} else {
|
|
256
|
-
clean.set(name, value);
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
u.search = clean.toString();
|
|
260
|
-
u.hash = u.hash ? "#[redacted]" : "";
|
|
261
|
-
return u.toString();
|
|
262
|
-
} catch {
|
|
263
|
-
return url;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
243
|
// src/capture/device.ts
|
|
268
244
|
function collectDevice() {
|
|
269
245
|
const nav = navigator;
|
|
@@ -844,7 +820,7 @@ function resolveStrings(overrides, options = {}) {
|
|
|
844
820
|
|
|
845
821
|
// src/widget/mount.tsx
|
|
846
822
|
import { h, render } from "preact";
|
|
847
|
-
import { useCallback } from "preact/hooks";
|
|
823
|
+
import { useCallback, useEffect as useEffect8, useState as useState7 } from "preact/hooks";
|
|
848
824
|
|
|
849
825
|
// src/widget/BoardView.tsx
|
|
850
826
|
import { useEffect as useEffect2, useMemo, useState as useState2 } from "preact/hooks";
|
|
@@ -4505,6 +4481,12 @@ var WIDGET_STYLES = `
|
|
|
4505
4481
|
|
|
4506
4482
|
// src/widget/mount.tsx
|
|
4507
4483
|
import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs12 } from "preact/jsx-runtime";
|
|
4484
|
+
function computeFabVisible(opts, externalId, visibilityAllowed) {
|
|
4485
|
+
if (!opts.showFAB) return false;
|
|
4486
|
+
if (opts.getExternalId !== void 0 && !externalId) return false;
|
|
4487
|
+
if (opts.requiresVisibilityCheck && !visibilityAllowed) return false;
|
|
4488
|
+
return true;
|
|
4489
|
+
}
|
|
4508
4490
|
function mountWidget(options) {
|
|
4509
4491
|
const shadow = options.host.attachShadow({ mode: "open" });
|
|
4510
4492
|
const style = document.createElement("style");
|
|
@@ -4553,7 +4535,29 @@ function mountWidget(options) {
|
|
|
4553
4535
|
}
|
|
4554
4536
|
}, []);
|
|
4555
4537
|
const externalId = options.getExternalId?.();
|
|
4556
|
-
const
|
|
4538
|
+
const [visibilityAllowed, setVisibilityAllowed] = useState7(
|
|
4539
|
+
!options.requiresVisibilityCheck
|
|
4540
|
+
);
|
|
4541
|
+
useEffect8(() => {
|
|
4542
|
+
if (!options.requiresVisibilityCheck) {
|
|
4543
|
+
setVisibilityAllowed(true);
|
|
4544
|
+
return;
|
|
4545
|
+
}
|
|
4546
|
+
if (!externalId || !options.checkVisibility) {
|
|
4547
|
+
setVisibilityAllowed(false);
|
|
4548
|
+
return;
|
|
4549
|
+
}
|
|
4550
|
+
let cancelled = false;
|
|
4551
|
+
options.checkVisibility(externalId).then((show) => {
|
|
4552
|
+
if (!cancelled) setVisibilityAllowed(show);
|
|
4553
|
+
}).catch(() => {
|
|
4554
|
+
if (!cancelled) setVisibilityAllowed(false);
|
|
4555
|
+
});
|
|
4556
|
+
return () => {
|
|
4557
|
+
cancelled = true;
|
|
4558
|
+
};
|
|
4559
|
+
}, [externalId]);
|
|
4560
|
+
const fabVisible = computeFabVisible(options, externalId, visibilityAllowed);
|
|
4557
4561
|
const showMineTab = Boolean(options.api && externalId);
|
|
4558
4562
|
return /* @__PURE__ */ jsxs12(Fragment3, { children: [
|
|
4559
4563
|
fabVisible && /* @__PURE__ */ jsx12(
|
|
@@ -4752,8 +4756,8 @@ function createFeedback(config) {
|
|
|
4752
4756
|
capture_method: captureMethod,
|
|
4753
4757
|
technical_context
|
|
4754
4758
|
};
|
|
4755
|
-
if ("0.
|
|
4756
|
-
payload.widget_version = "0.
|
|
4759
|
+
if ("0.18.0") {
|
|
4760
|
+
payload.widget_version = "0.18.0";
|
|
4757
4761
|
}
|
|
4758
4762
|
if (manualScreenshot) payload.screenshot = manualScreenshot;
|
|
4759
4763
|
if (values.synthetic) payload.synthetic = true;
|
|
@@ -4790,7 +4794,11 @@ function createFeedback(config) {
|
|
|
4790
4794
|
// Keep this a callback (not a snapshot) so the mount picks up identity
|
|
4791
4795
|
// changes that happen after createFeedback() — `notifyIdentityChanged()`
|
|
4792
4796
|
// is the trigger for a re-render.
|
|
4793
|
-
getExternalId: () => user?.id !== void 0 && user.id !== null && user.id !== "" ? String(user.id) : void 0
|
|
4797
|
+
getExternalId: () => user?.id !== void 0 && user.id !== null && user.id !== "" ? String(user.id) : void 0,
|
|
4798
|
+
// Phase 4: the manifest tells the loader whether this project gates the
|
|
4799
|
+
// widget per end-user; the loader forwards it as `requiresVisibilityCheck`.
|
|
4800
|
+
requiresVisibilityCheck: config.requiresVisibilityCheck ?? false,
|
|
4801
|
+
checkVisibility: (externalId) => api.checkVisibility(externalId)
|
|
4794
4802
|
});
|
|
4795
4803
|
const instance = {
|
|
4796
4804
|
show() {
|
|
@@ -4839,4 +4847,4 @@ function createFeedback(config) {
|
|
|
4839
4847
|
export {
|
|
4840
4848
|
createFeedback
|
|
4841
4849
|
};
|
|
4842
|
-
//# sourceMappingURL=chunk-
|
|
4850
|
+
//# sourceMappingURL=chunk-4QQI5ZYD.mjs.map
|