@jarve/bug-reporter 1.0.1 → 1.0.2
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/index.d.mts +28 -2
- package/dist/index.d.ts +28 -2
- package/dist/index.js +185 -5
- package/dist/index.mjs +185 -5
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -7,6 +7,15 @@ interface BugReporterUser {
|
|
|
7
7
|
name: string;
|
|
8
8
|
email: string;
|
|
9
9
|
}
|
|
10
|
+
type BugReporterClientContextValue = string | number | boolean | null | BugReporterClientContextValue[] | {
|
|
11
|
+
[key: string]: BugReporterClientContextValue;
|
|
12
|
+
};
|
|
13
|
+
type BugReporterClientContext = Record<string, BugReporterClientContextValue>;
|
|
14
|
+
interface BugReporterAccount {
|
|
15
|
+
id?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
plan?: string;
|
|
18
|
+
}
|
|
10
19
|
interface ClickedElement {
|
|
11
20
|
tagName: string;
|
|
12
21
|
textContent: string | null;
|
|
@@ -38,6 +47,11 @@ interface CaptureMetadata {
|
|
|
38
47
|
* derived from the secret API key.
|
|
39
48
|
*/
|
|
40
49
|
siteId: string;
|
|
50
|
+
widgetVersion?: string;
|
|
51
|
+
appVersion?: string;
|
|
52
|
+
appEnvironment?: string;
|
|
53
|
+
release?: string;
|
|
54
|
+
clientContext?: BugReporterClientContext;
|
|
41
55
|
screenshotCaptureFailed?: boolean;
|
|
42
56
|
clickedElement?: ClickedElement;
|
|
43
57
|
}
|
|
@@ -144,6 +158,18 @@ interface JarveBugReporterProps {
|
|
|
144
158
|
* See Issue E11.
|
|
145
159
|
*/
|
|
146
160
|
theme?: JarveTheme;
|
|
161
|
+
/** Optional host app version/release label to include in submitted reports. */
|
|
162
|
+
appVersion?: string;
|
|
163
|
+
/** Optional host environment label, e.g. "production", "preview", or "staging". */
|
|
164
|
+
environment?: string;
|
|
165
|
+
/** Optional deploy/release identifier, such as a git SHA or Vercel deployment URL. */
|
|
166
|
+
release?: string;
|
|
167
|
+
/** Optional account/tenant labels supplied by the host app. Values are redacted and size-capped. */
|
|
168
|
+
account?: BugReporterAccount;
|
|
169
|
+
/** Optional small tags supplied by the host app. Values are redacted and size-capped. */
|
|
170
|
+
tags?: string[];
|
|
171
|
+
/** Optional host debugging context. Values are redacted, depth-limited, and size-capped. */
|
|
172
|
+
context?: Record<string, unknown>;
|
|
147
173
|
/**
|
|
148
174
|
* Final chance to scrub the bug-report payload before it leaves the
|
|
149
175
|
* browser. The hook receives the full `/submit` payload and must return
|
|
@@ -155,6 +181,6 @@ interface JarveBugReporterProps {
|
|
|
155
181
|
onBeforeSubmit?: (payload: BugReportSubmitPayload) => BugReportSubmitPayload | null | false | Promise<BugReportSubmitPayload | null | false>;
|
|
156
182
|
children: React.ReactNode;
|
|
157
183
|
}
|
|
158
|
-
declare function JarveBugReporter({ apiUrl, apiKey, siteId, user, buttonPosition, zIndexBase, captureResponseBodies, theme, onBeforeSubmit, children, }: JarveBugReporterProps): react_jsx_runtime.JSX.Element;
|
|
184
|
+
declare function JarveBugReporter({ apiUrl, apiKey, siteId, user, buttonPosition, zIndexBase, captureResponseBodies, theme, appVersion, environment, release, account, tags, context, onBeforeSubmit, children, }: JarveBugReporterProps): react_jsx_runtime.JSX.Element;
|
|
159
185
|
|
|
160
|
-
export { type BugReportSubmitPayload, type BugReporterApiConfig, type BugReporterUser, type FloatingButtonPosition, JarveBugReporter, type JarveBugReporterProps };
|
|
186
|
+
export { type BugReportSubmitPayload, type BugReporterAccount, type BugReporterApiConfig, type BugReporterClientContext, type BugReporterUser, type FloatingButtonPosition, JarveBugReporter, type JarveBugReporterProps };
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,15 @@ interface BugReporterUser {
|
|
|
7
7
|
name: string;
|
|
8
8
|
email: string;
|
|
9
9
|
}
|
|
10
|
+
type BugReporterClientContextValue = string | number | boolean | null | BugReporterClientContextValue[] | {
|
|
11
|
+
[key: string]: BugReporterClientContextValue;
|
|
12
|
+
};
|
|
13
|
+
type BugReporterClientContext = Record<string, BugReporterClientContextValue>;
|
|
14
|
+
interface BugReporterAccount {
|
|
15
|
+
id?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
plan?: string;
|
|
18
|
+
}
|
|
10
19
|
interface ClickedElement {
|
|
11
20
|
tagName: string;
|
|
12
21
|
textContent: string | null;
|
|
@@ -38,6 +47,11 @@ interface CaptureMetadata {
|
|
|
38
47
|
* derived from the secret API key.
|
|
39
48
|
*/
|
|
40
49
|
siteId: string;
|
|
50
|
+
widgetVersion?: string;
|
|
51
|
+
appVersion?: string;
|
|
52
|
+
appEnvironment?: string;
|
|
53
|
+
release?: string;
|
|
54
|
+
clientContext?: BugReporterClientContext;
|
|
41
55
|
screenshotCaptureFailed?: boolean;
|
|
42
56
|
clickedElement?: ClickedElement;
|
|
43
57
|
}
|
|
@@ -144,6 +158,18 @@ interface JarveBugReporterProps {
|
|
|
144
158
|
* See Issue E11.
|
|
145
159
|
*/
|
|
146
160
|
theme?: JarveTheme;
|
|
161
|
+
/** Optional host app version/release label to include in submitted reports. */
|
|
162
|
+
appVersion?: string;
|
|
163
|
+
/** Optional host environment label, e.g. "production", "preview", or "staging". */
|
|
164
|
+
environment?: string;
|
|
165
|
+
/** Optional deploy/release identifier, such as a git SHA or Vercel deployment URL. */
|
|
166
|
+
release?: string;
|
|
167
|
+
/** Optional account/tenant labels supplied by the host app. Values are redacted and size-capped. */
|
|
168
|
+
account?: BugReporterAccount;
|
|
169
|
+
/** Optional small tags supplied by the host app. Values are redacted and size-capped. */
|
|
170
|
+
tags?: string[];
|
|
171
|
+
/** Optional host debugging context. Values are redacted, depth-limited, and size-capped. */
|
|
172
|
+
context?: Record<string, unknown>;
|
|
147
173
|
/**
|
|
148
174
|
* Final chance to scrub the bug-report payload before it leaves the
|
|
149
175
|
* browser. The hook receives the full `/submit` payload and must return
|
|
@@ -155,6 +181,6 @@ interface JarveBugReporterProps {
|
|
|
155
181
|
onBeforeSubmit?: (payload: BugReportSubmitPayload) => BugReportSubmitPayload | null | false | Promise<BugReportSubmitPayload | null | false>;
|
|
156
182
|
children: React.ReactNode;
|
|
157
183
|
}
|
|
158
|
-
declare function JarveBugReporter({ apiUrl, apiKey, siteId, user, buttonPosition, zIndexBase, captureResponseBodies, theme, onBeforeSubmit, children, }: JarveBugReporterProps): react_jsx_runtime.JSX.Element;
|
|
184
|
+
declare function JarveBugReporter({ apiUrl, apiKey, siteId, user, buttonPosition, zIndexBase, captureResponseBodies, theme, appVersion, environment, release, account, tags, context, onBeforeSubmit, children, }: JarveBugReporterProps): react_jsx_runtime.JSX.Element;
|
|
159
185
|
|
|
160
|
-
export { type BugReportSubmitPayload, type BugReporterApiConfig, type BugReporterUser, type FloatingButtonPosition, JarveBugReporter, type JarveBugReporterProps };
|
|
186
|
+
export { type BugReportSubmitPayload, type BugReporterAccount, type BugReporterApiConfig, type BugReporterClientContext, type BugReporterUser, type FloatingButtonPosition, JarveBugReporter, type JarveBugReporterProps };
|
package/dist/index.js
CHANGED
|
@@ -256,7 +256,7 @@ function collectElementInfo(target, section, coords) {
|
|
|
256
256
|
relativeClickY: coords.clientY - sectionRect.top
|
|
257
257
|
};
|
|
258
258
|
}
|
|
259
|
-
function collectMetadata(sectionElement, siteId, reporterName, reporterEmail, clickedElement) {
|
|
259
|
+
function collectMetadata(sectionElement, siteId, reporterName, reporterEmail, clickedElement, context) {
|
|
260
260
|
const { browser, os } = parseUserAgent();
|
|
261
261
|
const now = /* @__PURE__ */ new Date();
|
|
262
262
|
return {
|
|
@@ -270,6 +270,11 @@ function collectMetadata(sectionElement, siteId, reporterName, reporterEmail, cl
|
|
|
270
270
|
timestamp: now.toISOString(),
|
|
271
271
|
timestampUtc: now.toUTCString(),
|
|
272
272
|
siteId,
|
|
273
|
+
widgetVersion: context == null ? void 0 : context.widgetVersion,
|
|
274
|
+
appVersion: context == null ? void 0 : context.appVersion,
|
|
275
|
+
appEnvironment: context == null ? void 0 : context.appEnvironment,
|
|
276
|
+
release: context == null ? void 0 : context.release,
|
|
277
|
+
clientContext: context == null ? void 0 : context.clientContext,
|
|
273
278
|
reporterName,
|
|
274
279
|
reporterEmail,
|
|
275
280
|
clickedElement
|
|
@@ -571,6 +576,11 @@ function dataUrlToBlob(dataUrl) {
|
|
|
571
576
|
function CaptureOverlay({
|
|
572
577
|
isActive,
|
|
573
578
|
siteId,
|
|
579
|
+
widgetVersion,
|
|
580
|
+
appVersion,
|
|
581
|
+
appEnvironment,
|
|
582
|
+
release,
|
|
583
|
+
clientContext,
|
|
574
584
|
reporterName,
|
|
575
585
|
reporterEmail,
|
|
576
586
|
onCapture,
|
|
@@ -629,7 +639,14 @@ function CaptureOverlay({
|
|
|
629
639
|
siteId,
|
|
630
640
|
reporterName,
|
|
631
641
|
reporterEmail,
|
|
632
|
-
elementInfo
|
|
642
|
+
elementInfo,
|
|
643
|
+
{
|
|
644
|
+
widgetVersion,
|
|
645
|
+
appVersion,
|
|
646
|
+
appEnvironment,
|
|
647
|
+
release,
|
|
648
|
+
clientContext
|
|
649
|
+
}
|
|
633
650
|
);
|
|
634
651
|
const consoleErrors = getCapturedErrors();
|
|
635
652
|
const networkErrors = getCapturedNetworkErrors();
|
|
@@ -652,7 +669,14 @@ function CaptureOverlay({
|
|
|
652
669
|
siteId,
|
|
653
670
|
reporterName,
|
|
654
671
|
reporterEmail,
|
|
655
|
-
elementInfo
|
|
672
|
+
elementInfo,
|
|
673
|
+
{
|
|
674
|
+
widgetVersion,
|
|
675
|
+
appVersion,
|
|
676
|
+
appEnvironment,
|
|
677
|
+
release,
|
|
678
|
+
clientContext
|
|
679
|
+
}
|
|
656
680
|
);
|
|
657
681
|
const consoleErrors = getCapturedErrors();
|
|
658
682
|
const networkErrors = getCapturedNetworkErrors();
|
|
@@ -664,7 +688,14 @@ function CaptureOverlay({
|
|
|
664
688
|
siteId,
|
|
665
689
|
reporterName,
|
|
666
690
|
reporterEmail,
|
|
667
|
-
elementInfo
|
|
691
|
+
elementInfo,
|
|
692
|
+
{
|
|
693
|
+
widgetVersion,
|
|
694
|
+
appVersion,
|
|
695
|
+
appEnvironment,
|
|
696
|
+
release,
|
|
697
|
+
clientContext
|
|
698
|
+
}
|
|
668
699
|
);
|
|
669
700
|
const consoleErrors = getCapturedErrors();
|
|
670
701
|
const networkErrors = getCapturedNetworkErrors();
|
|
@@ -679,7 +710,17 @@ function CaptureOverlay({
|
|
|
679
710
|
setIsCapturing(false);
|
|
680
711
|
}
|
|
681
712
|
},
|
|
682
|
-
[
|
|
713
|
+
[
|
|
714
|
+
siteId,
|
|
715
|
+
reporterName,
|
|
716
|
+
reporterEmail,
|
|
717
|
+
onCapture,
|
|
718
|
+
widgetVersion,
|
|
719
|
+
appVersion,
|
|
720
|
+
appEnvironment,
|
|
721
|
+
release,
|
|
722
|
+
clientContext
|
|
723
|
+
]
|
|
683
724
|
);
|
|
684
725
|
const handleMouseMove = (0, import_react2.useCallback)(
|
|
685
726
|
(e) => {
|
|
@@ -1417,6 +1458,105 @@ function ReportModal({
|
|
|
1417
1458
|
|
|
1418
1459
|
// src/bug-reporter.tsx
|
|
1419
1460
|
var import_widget_shared6 = require("@jarve/widget-shared");
|
|
1461
|
+
|
|
1462
|
+
// src/context.ts
|
|
1463
|
+
var MAX_TOP_LEVEL_KEYS = 24;
|
|
1464
|
+
var MAX_ARRAY_ITEMS = 20;
|
|
1465
|
+
var MAX_DEPTH = 3;
|
|
1466
|
+
var MAX_STRING_LENGTH = 500;
|
|
1467
|
+
var MAX_SERIALIZED_BYTES = 8 * 1024;
|
|
1468
|
+
var SENSITIVE_KEY = /(token|password|secret|api[_-]?key|authorization|cookie|session)/i;
|
|
1469
|
+
function trimString(value) {
|
|
1470
|
+
return redactSensitive(value).slice(0, MAX_STRING_LENGTH);
|
|
1471
|
+
}
|
|
1472
|
+
function normalizeKey(key) {
|
|
1473
|
+
const trimmed = key.trim().slice(0, 80);
|
|
1474
|
+
if (!trimmed) return null;
|
|
1475
|
+
return trimmed;
|
|
1476
|
+
}
|
|
1477
|
+
function normalizeValue(key, value, depth) {
|
|
1478
|
+
if (value === void 0) return void 0;
|
|
1479
|
+
if (value === null) return null;
|
|
1480
|
+
if (SENSITIVE_KEY.test(key)) {
|
|
1481
|
+
return "[REDACTED]";
|
|
1482
|
+
}
|
|
1483
|
+
if (typeof value === "string") return trimString(value);
|
|
1484
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
1485
|
+
if (typeof value === "boolean") return value;
|
|
1486
|
+
if (Array.isArray(value)) {
|
|
1487
|
+
if (depth >= MAX_DEPTH) return "[TRUNCATED]";
|
|
1488
|
+
return value.slice(0, MAX_ARRAY_ITEMS).map((item, index) => normalizeValue(`${key}.${index}`, item, depth + 1)).filter((item) => item !== void 0);
|
|
1489
|
+
}
|
|
1490
|
+
if (typeof value === "object") {
|
|
1491
|
+
if (depth >= MAX_DEPTH) return "[TRUNCATED]";
|
|
1492
|
+
const output = {};
|
|
1493
|
+
for (const [childKey, childValue] of Object.entries(value).slice(
|
|
1494
|
+
0,
|
|
1495
|
+
MAX_TOP_LEVEL_KEYS
|
|
1496
|
+
)) {
|
|
1497
|
+
const normalizedKey = normalizeKey(childKey);
|
|
1498
|
+
if (!normalizedKey) continue;
|
|
1499
|
+
const normalizedValue = normalizeValue(normalizedKey, childValue, depth + 1);
|
|
1500
|
+
if (normalizedValue !== void 0) {
|
|
1501
|
+
output[normalizedKey] = normalizedValue;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
return output;
|
|
1505
|
+
}
|
|
1506
|
+
return trimString(String(value));
|
|
1507
|
+
}
|
|
1508
|
+
function serializedBytes(value) {
|
|
1509
|
+
try {
|
|
1510
|
+
return new TextEncoder().encode(JSON.stringify(value)).length;
|
|
1511
|
+
} catch (e) {
|
|
1512
|
+
return MAX_SERIALIZED_BYTES + 1;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
function trimToBudget(value) {
|
|
1516
|
+
const output = {};
|
|
1517
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1518
|
+
output[key] = entry;
|
|
1519
|
+
if (serializedBytes(output) > MAX_SERIALIZED_BYTES) {
|
|
1520
|
+
delete output[key];
|
|
1521
|
+
output._truncated = true;
|
|
1522
|
+
break;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
return output;
|
|
1526
|
+
}
|
|
1527
|
+
function normalizeClientContext({
|
|
1528
|
+
account,
|
|
1529
|
+
tags,
|
|
1530
|
+
context
|
|
1531
|
+
}) {
|
|
1532
|
+
const output = {};
|
|
1533
|
+
if (account) {
|
|
1534
|
+
const normalizedAccount = normalizeValue("account", account, 0);
|
|
1535
|
+
if (normalizedAccount !== void 0) {
|
|
1536
|
+
output.account = normalizedAccount;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
if (tags && tags.length > 0) {
|
|
1540
|
+
output.tags = tags.slice(0, MAX_ARRAY_ITEMS).map((tag) => trimString(tag));
|
|
1541
|
+
}
|
|
1542
|
+
if (context) {
|
|
1543
|
+
for (const [key, value] of Object.entries(context).slice(0, MAX_TOP_LEVEL_KEYS)) {
|
|
1544
|
+
const normalizedKey = normalizeKey(key);
|
|
1545
|
+
if (!normalizedKey) continue;
|
|
1546
|
+
const normalizedValue = normalizeValue(normalizedKey, value, 0);
|
|
1547
|
+
if (normalizedValue !== void 0) {
|
|
1548
|
+
output[normalizedKey] = normalizedValue;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
if (Object.keys(output).length === 0) return void 0;
|
|
1553
|
+
return trimToBudget(output);
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
// src/version.ts
|
|
1557
|
+
var BUG_REPORTER_WIDGET_VERSION = "1.0.2";
|
|
1558
|
+
|
|
1559
|
+
// src/bug-reporter.tsx
|
|
1420
1560
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1421
1561
|
function isValidApiUrl(raw) {
|
|
1422
1562
|
if (typeof raw !== "string" || raw.length === 0) return false;
|
|
@@ -1438,11 +1578,21 @@ function JarveBugReporter({
|
|
|
1438
1578
|
zIndexBase,
|
|
1439
1579
|
captureResponseBodies = false,
|
|
1440
1580
|
theme,
|
|
1581
|
+
appVersion,
|
|
1582
|
+
environment,
|
|
1583
|
+
release,
|
|
1584
|
+
account,
|
|
1585
|
+
tags,
|
|
1586
|
+
context,
|
|
1441
1587
|
onBeforeSubmit,
|
|
1442
1588
|
children
|
|
1443
1589
|
}) {
|
|
1444
1590
|
const mounted = (0, import_widget_shared6.useHasMounted)();
|
|
1445
1591
|
const apiUrlValid = (0, import_react4.useMemo)(() => isValidApiUrl(apiUrl), [apiUrl]);
|
|
1592
|
+
const clientContext = (0, import_react4.useMemo)(
|
|
1593
|
+
() => normalizeClientContext({ account, tags, context }),
|
|
1594
|
+
[account, tags, context]
|
|
1595
|
+
);
|
|
1446
1596
|
(0, import_react4.useEffect)(() => {
|
|
1447
1597
|
if (!apiUrlValid) {
|
|
1448
1598
|
console.error(
|
|
@@ -1463,6 +1613,31 @@ function JarveBugReporter({
|
|
|
1463
1613
|
stopNetworkCapture();
|
|
1464
1614
|
};
|
|
1465
1615
|
}, [apiUrlValid, isPrimary, captureResponseBodies]);
|
|
1616
|
+
(0, import_react4.useEffect)(() => {
|
|
1617
|
+
if (!apiUrlValid || !isPrimary) return;
|
|
1618
|
+
const controller = new AbortController();
|
|
1619
|
+
const timeout = window.setTimeout(() => controller.abort(), 8e3);
|
|
1620
|
+
void fetch(`${apiUrl.replace(/\/+$/, "")}/heartbeat`, {
|
|
1621
|
+
method: "POST",
|
|
1622
|
+
headers: {
|
|
1623
|
+
"Content-Type": "application/json",
|
|
1624
|
+
"X-Bug-Reporter-Key": apiKey.trim()
|
|
1625
|
+
},
|
|
1626
|
+
body: JSON.stringify({
|
|
1627
|
+
siteId,
|
|
1628
|
+
widgetVersion: BUG_REPORTER_WIDGET_VERSION,
|
|
1629
|
+
appVersion,
|
|
1630
|
+
environment,
|
|
1631
|
+
release
|
|
1632
|
+
}),
|
|
1633
|
+
signal: controller.signal
|
|
1634
|
+
}).catch(() => {
|
|
1635
|
+
});
|
|
1636
|
+
return () => {
|
|
1637
|
+
window.clearTimeout(timeout);
|
|
1638
|
+
controller.abort();
|
|
1639
|
+
};
|
|
1640
|
+
}, [apiUrl, apiKey, apiUrlValid, appVersion, environment, isPrimary, release, siteId]);
|
|
1466
1641
|
const toggleCaptureMode = (0, import_react4.useCallback)(() => {
|
|
1467
1642
|
setCaptureMode((prev) => !prev);
|
|
1468
1643
|
}, []);
|
|
@@ -1549,6 +1724,11 @@ function JarveBugReporter({
|
|
|
1549
1724
|
{
|
|
1550
1725
|
isActive: captureMode,
|
|
1551
1726
|
siteId,
|
|
1727
|
+
widgetVersion: BUG_REPORTER_WIDGET_VERSION,
|
|
1728
|
+
appVersion,
|
|
1729
|
+
appEnvironment: environment,
|
|
1730
|
+
release,
|
|
1731
|
+
clientContext,
|
|
1552
1732
|
reporterName,
|
|
1553
1733
|
reporterEmail,
|
|
1554
1734
|
onCapture: handleCapture,
|
package/dist/index.mjs
CHANGED
|
@@ -224,7 +224,7 @@ function collectElementInfo(target, section, coords) {
|
|
|
224
224
|
relativeClickY: coords.clientY - sectionRect.top
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
|
-
function collectMetadata(sectionElement, siteId, reporterName, reporterEmail, clickedElement) {
|
|
227
|
+
function collectMetadata(sectionElement, siteId, reporterName, reporterEmail, clickedElement, context) {
|
|
228
228
|
const { browser, os } = parseUserAgent();
|
|
229
229
|
const now = /* @__PURE__ */ new Date();
|
|
230
230
|
return {
|
|
@@ -238,6 +238,11 @@ function collectMetadata(sectionElement, siteId, reporterName, reporterEmail, cl
|
|
|
238
238
|
timestamp: now.toISOString(),
|
|
239
239
|
timestampUtc: now.toUTCString(),
|
|
240
240
|
siteId,
|
|
241
|
+
widgetVersion: context == null ? void 0 : context.widgetVersion,
|
|
242
|
+
appVersion: context == null ? void 0 : context.appVersion,
|
|
243
|
+
appEnvironment: context == null ? void 0 : context.appEnvironment,
|
|
244
|
+
release: context == null ? void 0 : context.release,
|
|
245
|
+
clientContext: context == null ? void 0 : context.clientContext,
|
|
241
246
|
reporterName,
|
|
242
247
|
reporterEmail,
|
|
243
248
|
clickedElement
|
|
@@ -539,6 +544,11 @@ function dataUrlToBlob(dataUrl) {
|
|
|
539
544
|
function CaptureOverlay({
|
|
540
545
|
isActive,
|
|
541
546
|
siteId,
|
|
547
|
+
widgetVersion,
|
|
548
|
+
appVersion,
|
|
549
|
+
appEnvironment,
|
|
550
|
+
release,
|
|
551
|
+
clientContext,
|
|
542
552
|
reporterName,
|
|
543
553
|
reporterEmail,
|
|
544
554
|
onCapture,
|
|
@@ -597,7 +607,14 @@ function CaptureOverlay({
|
|
|
597
607
|
siteId,
|
|
598
608
|
reporterName,
|
|
599
609
|
reporterEmail,
|
|
600
|
-
elementInfo
|
|
610
|
+
elementInfo,
|
|
611
|
+
{
|
|
612
|
+
widgetVersion,
|
|
613
|
+
appVersion,
|
|
614
|
+
appEnvironment,
|
|
615
|
+
release,
|
|
616
|
+
clientContext
|
|
617
|
+
}
|
|
601
618
|
);
|
|
602
619
|
const consoleErrors = getCapturedErrors();
|
|
603
620
|
const networkErrors = getCapturedNetworkErrors();
|
|
@@ -620,7 +637,14 @@ function CaptureOverlay({
|
|
|
620
637
|
siteId,
|
|
621
638
|
reporterName,
|
|
622
639
|
reporterEmail,
|
|
623
|
-
elementInfo
|
|
640
|
+
elementInfo,
|
|
641
|
+
{
|
|
642
|
+
widgetVersion,
|
|
643
|
+
appVersion,
|
|
644
|
+
appEnvironment,
|
|
645
|
+
release,
|
|
646
|
+
clientContext
|
|
647
|
+
}
|
|
624
648
|
);
|
|
625
649
|
const consoleErrors = getCapturedErrors();
|
|
626
650
|
const networkErrors = getCapturedNetworkErrors();
|
|
@@ -632,7 +656,14 @@ function CaptureOverlay({
|
|
|
632
656
|
siteId,
|
|
633
657
|
reporterName,
|
|
634
658
|
reporterEmail,
|
|
635
|
-
elementInfo
|
|
659
|
+
elementInfo,
|
|
660
|
+
{
|
|
661
|
+
widgetVersion,
|
|
662
|
+
appVersion,
|
|
663
|
+
appEnvironment,
|
|
664
|
+
release,
|
|
665
|
+
clientContext
|
|
666
|
+
}
|
|
636
667
|
);
|
|
637
668
|
const consoleErrors = getCapturedErrors();
|
|
638
669
|
const networkErrors = getCapturedNetworkErrors();
|
|
@@ -647,7 +678,17 @@ function CaptureOverlay({
|
|
|
647
678
|
setIsCapturing(false);
|
|
648
679
|
}
|
|
649
680
|
},
|
|
650
|
-
[
|
|
681
|
+
[
|
|
682
|
+
siteId,
|
|
683
|
+
reporterName,
|
|
684
|
+
reporterEmail,
|
|
685
|
+
onCapture,
|
|
686
|
+
widgetVersion,
|
|
687
|
+
appVersion,
|
|
688
|
+
appEnvironment,
|
|
689
|
+
release,
|
|
690
|
+
clientContext
|
|
691
|
+
]
|
|
651
692
|
);
|
|
652
693
|
const handleMouseMove = useCallback(
|
|
653
694
|
(e) => {
|
|
@@ -1392,6 +1433,105 @@ function ReportModal({
|
|
|
1392
1433
|
|
|
1393
1434
|
// src/bug-reporter.tsx
|
|
1394
1435
|
import { emit, useHasMounted } from "@jarve/widget-shared";
|
|
1436
|
+
|
|
1437
|
+
// src/context.ts
|
|
1438
|
+
var MAX_TOP_LEVEL_KEYS = 24;
|
|
1439
|
+
var MAX_ARRAY_ITEMS = 20;
|
|
1440
|
+
var MAX_DEPTH = 3;
|
|
1441
|
+
var MAX_STRING_LENGTH = 500;
|
|
1442
|
+
var MAX_SERIALIZED_BYTES = 8 * 1024;
|
|
1443
|
+
var SENSITIVE_KEY = /(token|password|secret|api[_-]?key|authorization|cookie|session)/i;
|
|
1444
|
+
function trimString(value) {
|
|
1445
|
+
return redactSensitive(value).slice(0, MAX_STRING_LENGTH);
|
|
1446
|
+
}
|
|
1447
|
+
function normalizeKey(key) {
|
|
1448
|
+
const trimmed = key.trim().slice(0, 80);
|
|
1449
|
+
if (!trimmed) return null;
|
|
1450
|
+
return trimmed;
|
|
1451
|
+
}
|
|
1452
|
+
function normalizeValue(key, value, depth) {
|
|
1453
|
+
if (value === void 0) return void 0;
|
|
1454
|
+
if (value === null) return null;
|
|
1455
|
+
if (SENSITIVE_KEY.test(key)) {
|
|
1456
|
+
return "[REDACTED]";
|
|
1457
|
+
}
|
|
1458
|
+
if (typeof value === "string") return trimString(value);
|
|
1459
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
1460
|
+
if (typeof value === "boolean") return value;
|
|
1461
|
+
if (Array.isArray(value)) {
|
|
1462
|
+
if (depth >= MAX_DEPTH) return "[TRUNCATED]";
|
|
1463
|
+
return value.slice(0, MAX_ARRAY_ITEMS).map((item, index) => normalizeValue(`${key}.${index}`, item, depth + 1)).filter((item) => item !== void 0);
|
|
1464
|
+
}
|
|
1465
|
+
if (typeof value === "object") {
|
|
1466
|
+
if (depth >= MAX_DEPTH) return "[TRUNCATED]";
|
|
1467
|
+
const output = {};
|
|
1468
|
+
for (const [childKey, childValue] of Object.entries(value).slice(
|
|
1469
|
+
0,
|
|
1470
|
+
MAX_TOP_LEVEL_KEYS
|
|
1471
|
+
)) {
|
|
1472
|
+
const normalizedKey = normalizeKey(childKey);
|
|
1473
|
+
if (!normalizedKey) continue;
|
|
1474
|
+
const normalizedValue = normalizeValue(normalizedKey, childValue, depth + 1);
|
|
1475
|
+
if (normalizedValue !== void 0) {
|
|
1476
|
+
output[normalizedKey] = normalizedValue;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
return output;
|
|
1480
|
+
}
|
|
1481
|
+
return trimString(String(value));
|
|
1482
|
+
}
|
|
1483
|
+
function serializedBytes(value) {
|
|
1484
|
+
try {
|
|
1485
|
+
return new TextEncoder().encode(JSON.stringify(value)).length;
|
|
1486
|
+
} catch (e) {
|
|
1487
|
+
return MAX_SERIALIZED_BYTES + 1;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
function trimToBudget(value) {
|
|
1491
|
+
const output = {};
|
|
1492
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1493
|
+
output[key] = entry;
|
|
1494
|
+
if (serializedBytes(output) > MAX_SERIALIZED_BYTES) {
|
|
1495
|
+
delete output[key];
|
|
1496
|
+
output._truncated = true;
|
|
1497
|
+
break;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
return output;
|
|
1501
|
+
}
|
|
1502
|
+
function normalizeClientContext({
|
|
1503
|
+
account,
|
|
1504
|
+
tags,
|
|
1505
|
+
context
|
|
1506
|
+
}) {
|
|
1507
|
+
const output = {};
|
|
1508
|
+
if (account) {
|
|
1509
|
+
const normalizedAccount = normalizeValue("account", account, 0);
|
|
1510
|
+
if (normalizedAccount !== void 0) {
|
|
1511
|
+
output.account = normalizedAccount;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
if (tags && tags.length > 0) {
|
|
1515
|
+
output.tags = tags.slice(0, MAX_ARRAY_ITEMS).map((tag) => trimString(tag));
|
|
1516
|
+
}
|
|
1517
|
+
if (context) {
|
|
1518
|
+
for (const [key, value] of Object.entries(context).slice(0, MAX_TOP_LEVEL_KEYS)) {
|
|
1519
|
+
const normalizedKey = normalizeKey(key);
|
|
1520
|
+
if (!normalizedKey) continue;
|
|
1521
|
+
const normalizedValue = normalizeValue(normalizedKey, value, 0);
|
|
1522
|
+
if (normalizedValue !== void 0) {
|
|
1523
|
+
output[normalizedKey] = normalizedValue;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
if (Object.keys(output).length === 0) return void 0;
|
|
1528
|
+
return trimToBudget(output);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// src/version.ts
|
|
1532
|
+
var BUG_REPORTER_WIDGET_VERSION = "1.0.2";
|
|
1533
|
+
|
|
1534
|
+
// src/bug-reporter.tsx
|
|
1395
1535
|
import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1396
1536
|
function isValidApiUrl(raw) {
|
|
1397
1537
|
if (typeof raw !== "string" || raw.length === 0) return false;
|
|
@@ -1413,11 +1553,21 @@ function JarveBugReporter({
|
|
|
1413
1553
|
zIndexBase,
|
|
1414
1554
|
captureResponseBodies = false,
|
|
1415
1555
|
theme,
|
|
1556
|
+
appVersion,
|
|
1557
|
+
environment,
|
|
1558
|
+
release,
|
|
1559
|
+
account,
|
|
1560
|
+
tags,
|
|
1561
|
+
context,
|
|
1416
1562
|
onBeforeSubmit,
|
|
1417
1563
|
children
|
|
1418
1564
|
}) {
|
|
1419
1565
|
const mounted = useHasMounted();
|
|
1420
1566
|
const apiUrlValid = useMemo2(() => isValidApiUrl(apiUrl), [apiUrl]);
|
|
1567
|
+
const clientContext = useMemo2(
|
|
1568
|
+
() => normalizeClientContext({ account, tags, context }),
|
|
1569
|
+
[account, tags, context]
|
|
1570
|
+
);
|
|
1421
1571
|
useEffect4(() => {
|
|
1422
1572
|
if (!apiUrlValid) {
|
|
1423
1573
|
console.error(
|
|
@@ -1438,6 +1588,31 @@ function JarveBugReporter({
|
|
|
1438
1588
|
stopNetworkCapture();
|
|
1439
1589
|
};
|
|
1440
1590
|
}, [apiUrlValid, isPrimary, captureResponseBodies]);
|
|
1591
|
+
useEffect4(() => {
|
|
1592
|
+
if (!apiUrlValid || !isPrimary) return;
|
|
1593
|
+
const controller = new AbortController();
|
|
1594
|
+
const timeout = window.setTimeout(() => controller.abort(), 8e3);
|
|
1595
|
+
void fetch(`${apiUrl.replace(/\/+$/, "")}/heartbeat`, {
|
|
1596
|
+
method: "POST",
|
|
1597
|
+
headers: {
|
|
1598
|
+
"Content-Type": "application/json",
|
|
1599
|
+
"X-Bug-Reporter-Key": apiKey.trim()
|
|
1600
|
+
},
|
|
1601
|
+
body: JSON.stringify({
|
|
1602
|
+
siteId,
|
|
1603
|
+
widgetVersion: BUG_REPORTER_WIDGET_VERSION,
|
|
1604
|
+
appVersion,
|
|
1605
|
+
environment,
|
|
1606
|
+
release
|
|
1607
|
+
}),
|
|
1608
|
+
signal: controller.signal
|
|
1609
|
+
}).catch(() => {
|
|
1610
|
+
});
|
|
1611
|
+
return () => {
|
|
1612
|
+
window.clearTimeout(timeout);
|
|
1613
|
+
controller.abort();
|
|
1614
|
+
};
|
|
1615
|
+
}, [apiUrl, apiKey, apiUrlValid, appVersion, environment, isPrimary, release, siteId]);
|
|
1441
1616
|
const toggleCaptureMode = useCallback3(() => {
|
|
1442
1617
|
setCaptureMode((prev) => !prev);
|
|
1443
1618
|
}, []);
|
|
@@ -1524,6 +1699,11 @@ function JarveBugReporter({
|
|
|
1524
1699
|
{
|
|
1525
1700
|
isActive: captureMode,
|
|
1526
1701
|
siteId,
|
|
1702
|
+
widgetVersion: BUG_REPORTER_WIDGET_VERSION,
|
|
1703
|
+
appVersion,
|
|
1704
|
+
appEnvironment: environment,
|
|
1705
|
+
release,
|
|
1706
|
+
clientContext,
|
|
1527
1707
|
reporterName,
|
|
1528
1708
|
reporterEmail,
|
|
1529
1709
|
onCapture: handleCapture,
|