@limekex/bugreport-widget-sdk 0.1.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 +47 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/types/widget.types.d.ts +21 -0
- package/dist/types/widget.types.d.ts.map +1 -0
- package/dist/types/widget.types.js +2 -0
- package/dist/types/widget.types.js.map +1 -0
- package/dist/utils/apiClient.d.ts +10 -0
- package/dist/utils/apiClient.d.ts.map +1 -0
- package/dist/utils/apiClient.js +19 -0
- package/dist/utils/apiClient.js.map +1 -0
- package/dist/utils/consoleCapture.d.ts +34 -0
- package/dist/utils/consoleCapture.d.ts.map +1 -0
- package/dist/utils/consoleCapture.js +82 -0
- package/dist/utils/consoleCapture.js.map +1 -0
- package/dist/utils/networkCapture.d.ts +44 -0
- package/dist/utils/networkCapture.d.ts.map +1 -0
- package/dist/utils/networkCapture.js +138 -0
- package/dist/utils/networkCapture.js.map +1 -0
- package/dist/utils/payloadBuilder.d.ts +15 -0
- package/dist/utils/payloadBuilder.d.ts.map +1 -0
- package/dist/utils/payloadBuilder.js +51 -0
- package/dist/utils/payloadBuilder.js.map +1 -0
- package/dist/utils/screenshotCapture.d.ts +21 -0
- package/dist/utils/screenshotCapture.d.ts.map +1 -0
- package/dist/utils/screenshotCapture.js +85 -0
- package/dist/utils/screenshotCapture.js.map +1 -0
- package/dist/widget/button.d.ts +11 -0
- package/dist/widget/button.d.ts.map +1 -0
- package/dist/widget/button.js +33 -0
- package/dist/widget/button.js.map +1 -0
- package/dist/widget/init.d.ts +28 -0
- package/dist/widget/init.d.ts.map +1 -0
- package/dist/widget/init.js +102 -0
- package/dist/widget/init.js.map +1 -0
- package/dist/widget/modal.d.ts +20 -0
- package/dist/widget/modal.d.ts.map +1 -0
- package/dist/widget/modal.js +493 -0
- package/dist/widget/modal.js.map +1 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @bugreport/widget-sdk
|
|
2
|
+
|
|
3
|
+
Reusable browser SDK for embedding the Stage Bug Reporter widget into staging/UAT applications.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { initBugReporter } from '@bugreport/widget-sdk';
|
|
9
|
+
|
|
10
|
+
initBugReporter({
|
|
11
|
+
apiBaseUrl: 'https://bugreport.betait.no',
|
|
12
|
+
environment: 'staging',
|
|
13
|
+
enabled: true,
|
|
14
|
+
appVersion: '1.2.3',
|
|
15
|
+
commitSha: process.env.VITE_COMMIT_SHA,
|
|
16
|
+
currentUser: { id: 'tester-001', email: 'tester@example.com', role: 'qa' },
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This injects a floating **🐛 Report bug** button into the page. Clicking it opens the bug report modal (placeholder).
|
|
21
|
+
|
|
22
|
+
## Config
|
|
23
|
+
|
|
24
|
+
See `BugReporterConfig` in `@bugreport/shared-types` for full config options.
|
|
25
|
+
|
|
26
|
+
## React example
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cd examples/react-example
|
|
30
|
+
pnpm dev
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Scripts
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pnpm build # compile TypeScript
|
|
37
|
+
pnpm typecheck # type-check without emit
|
|
38
|
+
pnpm dev # watch mode
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Architecture notes
|
|
42
|
+
|
|
43
|
+
- The `init.ts` entry point is framework-agnostic.
|
|
44
|
+
- The floating button is a plain DOM element — no React required for the trigger.
|
|
45
|
+
- The modal (`modal.ts`) is a **TODO** placeholder — replace with a real React component or Headless UI modal.
|
|
46
|
+
- `payloadBuilder.ts` auto-collects browser context (URL, viewport, locale, user-agent).
|
|
47
|
+
- `apiClient.ts` handles the multipart POST to the backend.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { initBugReporter } from './widget/init';
|
|
2
|
+
export type { BugReporterConfig, CurrentUser, TraceContext, WidgetTheme } from '@bugreport/shared-types';
|
|
3
|
+
export { installConsoleErrorHook, getCollectedErrors, clearCollectedErrors } from './utils/consoleCapture';
|
|
4
|
+
export type { CapturedError } from './utils/consoleCapture';
|
|
5
|
+
export { installNetworkCaptureHook, getFailedNetworkRequests, clearFailedNetworkRequests } from './utils/networkCapture';
|
|
6
|
+
export type { FailedNetworkEntry } from './utils/networkCapture';
|
|
7
|
+
export { capturePageScreenshot } from './utils/screenshotCapture';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,YAAY,EAAE,iBAAiB,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACzG,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC3G,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACzH,YAAY,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { initBugReporter } from './widget/init';
|
|
2
|
+
export { installConsoleErrorHook, getCollectedErrors, clearCollectedErrors } from './utils/consoleCapture';
|
|
3
|
+
export { installNetworkCaptureHook, getFailedNetworkRequests, clearFailedNetworkRequests } from './utils/networkCapture';
|
|
4
|
+
export { capturePageScreenshot } from './utils/screenshotCapture';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE3G,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAEzH,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BugReporterConfig } from '@bugreport/shared-types';
|
|
2
|
+
export type WidgetState = 'idle' | 'open' | 'submitting' | 'success' | 'error';
|
|
3
|
+
export type WidgetEventType = 'open' | 'close' | 'submit' | 'success' | 'error';
|
|
4
|
+
export interface WidgetEvent {
|
|
5
|
+
type: WidgetEventType;
|
|
6
|
+
payload?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export type WidgetEventListener = (event: WidgetEvent) => void;
|
|
9
|
+
/**
|
|
10
|
+
* Internal state container for the widget instance.
|
|
11
|
+
* Framework adapters (React, vanilla) wrap this state in their own reactivity model.
|
|
12
|
+
*/
|
|
13
|
+
export interface WidgetInstance {
|
|
14
|
+
config: BugReporterConfig;
|
|
15
|
+
state: WidgetState;
|
|
16
|
+
destroy: () => void;
|
|
17
|
+
open: () => void;
|
|
18
|
+
close: () => void;
|
|
19
|
+
on: (type: WidgetEventType, listener: WidgetEventListener) => void;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=widget.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget.types.d.ts","sourceRoot":"","sources":["../../src/types/widget.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,YAAY,GAAG,SAAS,GAAG,OAAO,CAAC;AAE/E,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;AAEhF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;AAE/D;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,EAAE,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,mBAAmB,KAAK,IAAI,CAAC;CACpE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget.types.js","sourceRoot":"","sources":["../../src/types/widget.types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BugReportSuccessResponse } from '@bugreport/shared-types';
|
|
2
|
+
export interface SubmitReportOptions {
|
|
3
|
+
apiBaseUrl: string;
|
|
4
|
+
formData: FormData;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Sends the bug report FormData to the backend API.
|
|
8
|
+
*/
|
|
9
|
+
export declare function submitReport(options: SubmitReportOptions): Promise<BugReportSuccessResponse>;
|
|
10
|
+
//# sourceMappingURL=apiClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiClient.d.ts","sourceRoot":"","sources":["../../src/utils/apiClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAA0B,MAAM,yBAAyB,CAAC;AAEhG,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,wBAAwB,CAAC,CAkBnC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sends the bug report FormData to the backend API.
|
|
3
|
+
*/
|
|
4
|
+
export async function submitReport(options) {
|
|
5
|
+
const { apiBaseUrl, formData } = options;
|
|
6
|
+
const url = `${apiBaseUrl.replace(/\/$/, '')}/api/reports/bug`;
|
|
7
|
+
const response = await fetch(url, {
|
|
8
|
+
method: 'POST',
|
|
9
|
+
body: formData,
|
|
10
|
+
// Note: do NOT set Content-Type manually — the browser sets it with the boundary.
|
|
11
|
+
credentials: 'omit',
|
|
12
|
+
});
|
|
13
|
+
const data = (await response.json());
|
|
14
|
+
if (!data.success) {
|
|
15
|
+
throw new Error(data.error ?? 'Unknown error from API');
|
|
16
|
+
}
|
|
17
|
+
return data;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=apiClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiClient.js","sourceRoot":"","sources":["../../src/utils/apiClient.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAA4B;IAE5B,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IACzC,MAAM,GAAG,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,kBAAkB,CAAC;IAE/D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAChC,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,QAAQ;QACd,kFAAkF;QAClF,WAAW,EAAE,MAAM;KACpB,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAsD,CAAC;IAE1F,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAE,IAA+B,CAAC,KAAK,IAAI,wBAAwB,CAAC,CAAC;IACtF,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight console error capture utility.
|
|
3
|
+
*
|
|
4
|
+
* Hooks into `console.error` for the lifetime of the current page to collect
|
|
5
|
+
* recent errors. This data is attached to bug reports as `optionalClientErrors`.
|
|
6
|
+
*
|
|
7
|
+
* Privacy rules (from docs/TECHNICAL_SPEC.md):
|
|
8
|
+
* - Only errors from the *current page lifetime* are captured.
|
|
9
|
+
* - A rolling buffer of MAX_ERRORS entries is kept — older entries are dropped.
|
|
10
|
+
* - No full session recording or persistent storage.
|
|
11
|
+
* - Error messages are trimmed to MAX_MESSAGE_LENGTH characters.
|
|
12
|
+
* - Stack traces are trimmed to MAX_STACK_LENGTH characters.
|
|
13
|
+
*/
|
|
14
|
+
export interface CapturedError {
|
|
15
|
+
message: string;
|
|
16
|
+
stack?: string;
|
|
17
|
+
timestamp: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Installs the console.error hook.
|
|
21
|
+
*
|
|
22
|
+
* Call once during SDK initialisation (`initBugReporter`).
|
|
23
|
+
* Safe to call multiple times — subsequent calls are no-ops.
|
|
24
|
+
*/
|
|
25
|
+
export declare function installConsoleErrorHook(): void;
|
|
26
|
+
/**
|
|
27
|
+
* Returns a JSON-serialised snapshot of the current error buffer.
|
|
28
|
+
*
|
|
29
|
+
* Returns `undefined` when the buffer is empty so callers can omit the field.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getCollectedErrors(): string | undefined;
|
|
32
|
+
/** Clears the in-memory buffer (useful for testing). */
|
|
33
|
+
export declare function clearCollectedErrors(): void;
|
|
34
|
+
//# sourceMappingURL=consoleCapture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consoleCapture.d.ts","sourceRoot":"","sources":["../../src/utils/consoleCapture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAKD;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAU9C;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,SAAS,CAGvD;AAED,wDAAwD;AACxD,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight console error capture utility.
|
|
3
|
+
*
|
|
4
|
+
* Hooks into `console.error` for the lifetime of the current page to collect
|
|
5
|
+
* recent errors. This data is attached to bug reports as `optionalClientErrors`.
|
|
6
|
+
*
|
|
7
|
+
* Privacy rules (from docs/TECHNICAL_SPEC.md):
|
|
8
|
+
* - Only errors from the *current page lifetime* are captured.
|
|
9
|
+
* - A rolling buffer of MAX_ERRORS entries is kept — older entries are dropped.
|
|
10
|
+
* - No full session recording or persistent storage.
|
|
11
|
+
* - Error messages are trimmed to MAX_MESSAGE_LENGTH characters.
|
|
12
|
+
* - Stack traces are trimmed to MAX_STACK_LENGTH characters.
|
|
13
|
+
*/
|
|
14
|
+
const MAX_ERRORS = 20;
|
|
15
|
+
const MAX_MESSAGE_LENGTH = 500;
|
|
16
|
+
const MAX_STACK_LENGTH = 1000;
|
|
17
|
+
const errorBuffer = [];
|
|
18
|
+
let hooked = false;
|
|
19
|
+
/**
|
|
20
|
+
* Installs the console.error hook.
|
|
21
|
+
*
|
|
22
|
+
* Call once during SDK initialisation (`initBugReporter`).
|
|
23
|
+
* Safe to call multiple times — subsequent calls are no-ops.
|
|
24
|
+
*/
|
|
25
|
+
export function installConsoleErrorHook() {
|
|
26
|
+
if (hooked || typeof console === 'undefined')
|
|
27
|
+
return;
|
|
28
|
+
hooked = true;
|
|
29
|
+
const originalError = console.error.bind(console);
|
|
30
|
+
console.error = (...args) => {
|
|
31
|
+
originalError(...args);
|
|
32
|
+
captureError(args);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Returns a JSON-serialised snapshot of the current error buffer.
|
|
37
|
+
*
|
|
38
|
+
* Returns `undefined` when the buffer is empty so callers can omit the field.
|
|
39
|
+
*/
|
|
40
|
+
export function getCollectedErrors() {
|
|
41
|
+
if (errorBuffer.length === 0)
|
|
42
|
+
return undefined;
|
|
43
|
+
return JSON.stringify(errorBuffer);
|
|
44
|
+
}
|
|
45
|
+
/** Clears the in-memory buffer (useful for testing). */
|
|
46
|
+
export function clearCollectedErrors() {
|
|
47
|
+
errorBuffer.length = 0;
|
|
48
|
+
}
|
|
49
|
+
// ── Internal helpers ──────────────────────────────────────────────────────────
|
|
50
|
+
function captureError(args) {
|
|
51
|
+
const raw = args
|
|
52
|
+
.map((a) => {
|
|
53
|
+
if (a instanceof Error)
|
|
54
|
+
return a.message;
|
|
55
|
+
if (typeof a === 'string')
|
|
56
|
+
return a;
|
|
57
|
+
try {
|
|
58
|
+
return JSON.stringify(a);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return String(a);
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
.join(' ');
|
|
65
|
+
const stack = args.find((a) => a instanceof Error && a.stack)
|
|
66
|
+
? (args.find((a) => a instanceof Error).stack ?? undefined)
|
|
67
|
+
: undefined;
|
|
68
|
+
const entry = {
|
|
69
|
+
message: raw.slice(0, MAX_MESSAGE_LENGTH),
|
|
70
|
+
stack: stack?.slice(0, MAX_STACK_LENGTH),
|
|
71
|
+
timestamp: new Date().toISOString(),
|
|
72
|
+
};
|
|
73
|
+
if (entry.stack === undefined) {
|
|
74
|
+
delete entry.stack;
|
|
75
|
+
}
|
|
76
|
+
errorBuffer.push(entry);
|
|
77
|
+
// Keep a rolling window — drop oldest when buffer is full
|
|
78
|
+
if (errorBuffer.length > MAX_ERRORS) {
|
|
79
|
+
errorBuffer.shift();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=consoleCapture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consoleCapture.js","sourceRoot":"","sources":["../../src/utils/consoleCapture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,UAAU,GAAG,EAAE,CAAC;AACtB,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAC/B,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAQ9B,MAAM,WAAW,GAAoB,EAAE,CAAC;AACxC,IAAI,MAAM,GAAG,KAAK,CAAC;AAEnB;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB;IACrC,IAAI,MAAM,IAAI,OAAO,OAAO,KAAK,WAAW;QAAE,OAAO;IACrD,MAAM,GAAG,IAAI,CAAC;IAEd,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAElD,OAAO,CAAC,KAAK,GAAG,CAAC,GAAG,IAAe,EAAE,EAAE;QACrC,aAAa,CAAC,GAAG,IAAI,CAAC,CAAC;QACvB,YAAY,CAAC,IAAI,CAAC,CAAC;IACrB,CAAC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC/C,OAAO,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;AACrC,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,oBAAoB;IAClC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;AACzB,CAAC;AAED,iFAAiF;AAEjF,SAAS,YAAY,CAAC,IAAe;IACnC,MAAM,GAAG,GAAG,IAAI;SACb,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,IAAI,CAAC,YAAY,KAAK;YAAE,OAAO,CAAC,CAAC,OAAO,CAAC;QACzC,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,OAAO,CAAC,CAAC;QACpC,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;SACD,IAAI,CAAC,GAAG,CAAC,CAAC;IAEb,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC;QAC3D,CAAC,CAAC,CAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,KAAK,CAAW,CAAC,KAAK,IAAI,SAAS,CAAC;QACtE,CAAC,CAAC,SAAS,CAAC;IAEd,MAAM,KAAK,GAAkB;QAC3B,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,kBAAkB,CAAC;QACzC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC;QACxC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;IAEF,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IAED,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAExB,0DAA0D;IAC1D,IAAI,WAAW,CAAC,MAAM,GAAG,UAAU,EAAE,CAAC;QACpC,WAAW,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Failed network request capture utility.
|
|
3
|
+
*
|
|
4
|
+
* Uses the `PerformanceObserver` API (where available) to passively observe
|
|
5
|
+
* `resource` entries and collect those with non-2xx status codes or that failed
|
|
6
|
+
* entirely (transferSize === 0 with duration > 0).
|
|
7
|
+
*
|
|
8
|
+
* The resulting summary is attached to bug reports as the
|
|
9
|
+
* `failedNetworkRequests` field so developers can see which API calls
|
|
10
|
+
* broke during the tester's session.
|
|
11
|
+
*
|
|
12
|
+
* Privacy rules:
|
|
13
|
+
* - Only the URL pathname + status are captured — no request/response bodies.
|
|
14
|
+
* - Authorization headers are never recorded.
|
|
15
|
+
* - A rolling buffer of MAX_ENTRIES is kept.
|
|
16
|
+
* - URLs are truncated to MAX_URL_LENGTH characters.
|
|
17
|
+
*/
|
|
18
|
+
export interface FailedNetworkEntry {
|
|
19
|
+
/** Request URL (pathname only for same-origin; full URL for cross-origin) */
|
|
20
|
+
url: string;
|
|
21
|
+
/** HTTP method when available (defaults to "GET" for resource entries) */
|
|
22
|
+
method: string;
|
|
23
|
+
/** HTTP status code, or 0 if the request failed entirely */
|
|
24
|
+
status: number;
|
|
25
|
+
/** Duration in milliseconds */
|
|
26
|
+
durationMs: number;
|
|
27
|
+
/** ISO 8601 timestamp */
|
|
28
|
+
timestamp: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Installs the PerformanceObserver hook + fetch/XHR interceptors.
|
|
32
|
+
*
|
|
33
|
+
* Call once during SDK initialisation. Safe to call multiple times.
|
|
34
|
+
*/
|
|
35
|
+
export declare function installNetworkCaptureHook(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Returns a JSON-serialised snapshot of captured failed requests.
|
|
38
|
+
*
|
|
39
|
+
* Returns `undefined` when the buffer is empty so callers can omit the field.
|
|
40
|
+
*/
|
|
41
|
+
export declare function getFailedNetworkRequests(): string | undefined;
|
|
42
|
+
/** Clears the in-memory buffer (useful for testing). */
|
|
43
|
+
export declare function clearFailedNetworkRequests(): void;
|
|
44
|
+
//# sourceMappingURL=networkCapture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"networkCapture.d.ts","sourceRoot":"","sources":["../../src/utils/networkCapture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,MAAM,WAAW,kBAAkB;IACjC,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CA8FhD;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,GAAG,SAAS,CAG7D;AAED,wDAAwD;AACxD,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Failed network request capture utility.
|
|
3
|
+
*
|
|
4
|
+
* Uses the `PerformanceObserver` API (where available) to passively observe
|
|
5
|
+
* `resource` entries and collect those with non-2xx status codes or that failed
|
|
6
|
+
* entirely (transferSize === 0 with duration > 0).
|
|
7
|
+
*
|
|
8
|
+
* The resulting summary is attached to bug reports as the
|
|
9
|
+
* `failedNetworkRequests` field so developers can see which API calls
|
|
10
|
+
* broke during the tester's session.
|
|
11
|
+
*
|
|
12
|
+
* Privacy rules:
|
|
13
|
+
* - Only the URL pathname + status are captured — no request/response bodies.
|
|
14
|
+
* - Authorization headers are never recorded.
|
|
15
|
+
* - A rolling buffer of MAX_ENTRIES is kept.
|
|
16
|
+
* - URLs are truncated to MAX_URL_LENGTH characters.
|
|
17
|
+
*/
|
|
18
|
+
const MAX_ENTRIES = 30;
|
|
19
|
+
const MAX_URL_LENGTH = 300;
|
|
20
|
+
const failedRequests = [];
|
|
21
|
+
let installed = false;
|
|
22
|
+
let observer = null;
|
|
23
|
+
/**
|
|
24
|
+
* Installs the PerformanceObserver hook + fetch/XHR interceptors.
|
|
25
|
+
*
|
|
26
|
+
* Call once during SDK initialisation. Safe to call multiple times.
|
|
27
|
+
*/
|
|
28
|
+
export function installNetworkCaptureHook() {
|
|
29
|
+
if (installed)
|
|
30
|
+
return;
|
|
31
|
+
installed = true;
|
|
32
|
+
// ── Fetch interceptor ─────────────────────────────────────────────────────
|
|
33
|
+
if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
|
|
34
|
+
const originalFetch = window.fetch.bind(window);
|
|
35
|
+
window.fetch = async (input, init) => {
|
|
36
|
+
const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
|
|
37
|
+
const method = init?.method?.toUpperCase() ?? 'GET';
|
|
38
|
+
const start = performance.now();
|
|
39
|
+
try {
|
|
40
|
+
const response = await originalFetch(input, init);
|
|
41
|
+
const durationMs = Math.round(performance.now() - start);
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
pushEntry({
|
|
44
|
+
url: truncateUrl(url),
|
|
45
|
+
method,
|
|
46
|
+
status: response.status,
|
|
47
|
+
durationMs,
|
|
48
|
+
timestamp: new Date().toISOString(),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return response;
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
const durationMs = Math.round(performance.now() - start);
|
|
55
|
+
pushEntry({
|
|
56
|
+
url: truncateUrl(url),
|
|
57
|
+
method,
|
|
58
|
+
status: 0,
|
|
59
|
+
durationMs,
|
|
60
|
+
timestamp: new Date().toISOString(),
|
|
61
|
+
});
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// ── XMLHttpRequest interceptor ────────────────────────────────────────────
|
|
67
|
+
if (typeof window !== 'undefined' && typeof XMLHttpRequest !== 'undefined') {
|
|
68
|
+
const OriginalXHR = XMLHttpRequest;
|
|
69
|
+
const originalOpen = OriginalXHR.prototype.open;
|
|
70
|
+
OriginalXHR.prototype.open = function (method, url, ...rest) {
|
|
71
|
+
this.__br_method = method;
|
|
72
|
+
this.__br_url = String(url);
|
|
73
|
+
this.addEventListener('loadend', function () {
|
|
74
|
+
if (this.status === 0 || this.status >= 400) {
|
|
75
|
+
pushEntry({
|
|
76
|
+
url: truncateUrl(String(url)),
|
|
77
|
+
method: method.toUpperCase(),
|
|
78
|
+
status: this.status,
|
|
79
|
+
durationMs: 0, // XHR doesn't expose timing directly
|
|
80
|
+
timestamp: new Date().toISOString(),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
+
return originalOpen.call(this, method, url, ...rest);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// ── PerformanceObserver (bonus: catches non-interceptable requests) ────────
|
|
89
|
+
if (typeof PerformanceObserver !== 'undefined') {
|
|
90
|
+
try {
|
|
91
|
+
observer = new PerformanceObserver((list) => {
|
|
92
|
+
for (const entry of list.getEntries()) {
|
|
93
|
+
const resource = entry;
|
|
94
|
+
// transferSize === 0 with duration > 0 often indicates a failed request
|
|
95
|
+
// (blocked, CORS error, etc.)
|
|
96
|
+
if (resource.transferSize === 0 && resource.duration > 50) {
|
|
97
|
+
pushEntry({
|
|
98
|
+
url: truncateUrl(resource.name),
|
|
99
|
+
method: 'GET', // PerformanceObserver doesn't expose the method
|
|
100
|
+
status: 0,
|
|
101
|
+
durationMs: Math.round(resource.duration),
|
|
102
|
+
timestamp: new Date().toISOString(),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
observer.observe({ entryTypes: ['resource'] });
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// PerformanceObserver not supported — silently skip
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Returns a JSON-serialised snapshot of captured failed requests.
|
|
116
|
+
*
|
|
117
|
+
* Returns `undefined` when the buffer is empty so callers can omit the field.
|
|
118
|
+
*/
|
|
119
|
+
export function getFailedNetworkRequests() {
|
|
120
|
+
if (failedRequests.length === 0)
|
|
121
|
+
return undefined;
|
|
122
|
+
return JSON.stringify(failedRequests);
|
|
123
|
+
}
|
|
124
|
+
/** Clears the in-memory buffer (useful for testing). */
|
|
125
|
+
export function clearFailedNetworkRequests() {
|
|
126
|
+
failedRequests.length = 0;
|
|
127
|
+
}
|
|
128
|
+
// ── Internal ──────────────────────────────────────────────────────────────────
|
|
129
|
+
function pushEntry(entry) {
|
|
130
|
+
failedRequests.push(entry);
|
|
131
|
+
if (failedRequests.length > MAX_ENTRIES) {
|
|
132
|
+
failedRequests.shift();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function truncateUrl(url) {
|
|
136
|
+
return url.length > MAX_URL_LENGTH ? url.slice(0, MAX_URL_LENGTH) + '…' : url;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=networkCapture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"networkCapture.js","sourceRoot":"","sources":["../../src/utils/networkCapture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,GAAG,EAAE,CAAC;AACvB,MAAM,cAAc,GAAG,GAAG,CAAC;AAe3B,MAAM,cAAc,GAAyB,EAAE,CAAC;AAChD,IAAI,SAAS,GAAG,KAAK,CAAC;AACtB,IAAI,QAAQ,GAA+B,IAAI,CAAC;AAEhD;;;;GAIG;AACH,MAAM,UAAU,yBAAyB;IACvC,IAAI,SAAS;QAAE,OAAO;IACtB,SAAS,GAAG,IAAI,CAAC;IAEjB,6EAA6E;IAC7E,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QACxE,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAChD,MAAM,CAAC,KAAK,GAAG,KAAK,EAAE,KAAwB,EAAE,IAAkB,EAAqB,EAAE;YACvF,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,YAAY,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;YAC9F,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,KAAK,CAAC;YACpD,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;YAEhC,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;gBAClD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC;gBAEzD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;oBACjB,SAAS,CAAC;wBACR,GAAG,EAAE,WAAW,CAAC,GAAG,CAAC;wBACrB,MAAM;wBACN,MAAM,EAAE,QAAQ,CAAC,MAAM;wBACvB,UAAU;wBACV,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;qBACpC,CAAC,CAAC;gBACL,CAAC;gBACD,OAAO,QAAQ,CAAC;YAClB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC;gBACzD,SAAS,CAAC;oBACR,GAAG,EAAE,WAAW,CAAC,GAAG,CAAC;oBACrB,MAAM;oBACN,MAAM,EAAE,CAAC;oBACT,UAAU;oBACV,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;iBACpC,CAAC,CAAC;gBACH,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,cAAc,KAAK,WAAW,EAAE,CAAC;QAC3E,MAAM,WAAW,GAAG,cAAc,CAAC;QACnC,MAAM,YAAY,GAAG,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC;QAEhD,WAAW,CAAC,SAAS,CAAC,IAAI,GAAG,UAC3B,MAAc,EACd,GAAiB,EACjB,GAAG,IAAe;YAEjB,IAAmE,CAAC,WAAW,GAAG,MAAM,CAAC;YACzF,IAA8C,CAAC,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;YAEvE,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE;gBAC/B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;oBAC5C,SAAS,CAAC;wBACR,GAAG,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;wBAC7B,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE;wBAC5B,MAAM,EAAE,IAAI,CAAC,MAAM;wBACnB,UAAU,EAAE,CAAC,EAAE,qCAAqC;wBACpD,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;qBACpC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,8DAA8D;YAC9D,OAAQ,YAA6C,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACzF,CAAC,CAAC;IACJ,CAAC;IAED,8EAA8E;IAC9E,IAAI,OAAO,mBAAmB,KAAK,WAAW,EAAE,CAAC;QAC/C,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,mBAAmB,CAAC,CAAC,IAAI,EAAE,EAAE;gBAC1C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;oBACtC,MAAM,QAAQ,GAAG,KAAkC,CAAC;oBACpD,wEAAwE;oBACxE,8BAA8B;oBAC9B,IAAI,QAAQ,CAAC,YAAY,KAAK,CAAC,IAAI,QAAQ,CAAC,QAAQ,GAAG,EAAE,EAAE,CAAC;wBAC1D,SAAS,CAAC;4BACR,GAAG,EAAE,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC;4BAC/B,MAAM,EAAE,KAAK,EAAE,gDAAgD;4BAC/D,MAAM,EAAE,CAAC;4BACT,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;4BACzC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;yBACpC,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAC;YACH,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,oDAAoD;QACtD,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB;IACtC,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAClD,OAAO,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;AACxC,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,0BAA0B;IACxC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,iFAAiF;AAEjF,SAAS,SAAS,CAAC,KAAyB;IAC1C,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,IAAI,cAAc,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC;QACxC,cAAc,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,GAAW;IAC9B,OAAO,GAAG,CAAC,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;AAChF,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { BugReportRequest } from '@bugreport/shared-types';
|
|
2
|
+
import type { BugReporterConfig } from '@bugreport/shared-types';
|
|
3
|
+
/**
|
|
4
|
+
* Builds the multipart FormData payload for POST /api/reports/bug.
|
|
5
|
+
*
|
|
6
|
+
* Text fields are taken from the report object; the screenshot File
|
|
7
|
+
* (if any) must be appended by the caller after this function returns.
|
|
8
|
+
*
|
|
9
|
+
* Automatically includes:
|
|
10
|
+
* - browser context (URL, viewport, locale, user-agent)
|
|
11
|
+
* - config values (environment, appVersion, commitSha, user info, traceId)
|
|
12
|
+
* - recent console errors collected by installConsoleErrorHook()
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildReportPayload(report: Omit<BugReportRequest, never>, config: BugReporterConfig, overrides?: Partial<BugReportRequest>): FormData;
|
|
15
|
+
//# sourceMappingURL=payloadBuilder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payloadBuilder.d.ts","sourceRoot":"","sources":["../../src/utils/payloadBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAIjE;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,EACrC,MAAM,EAAE,iBAAiB,EACzB,SAAS,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GACpC,QAAQ,CAyCV"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { getCollectedErrors } from './consoleCapture';
|
|
2
|
+
import { getFailedNetworkRequests } from './networkCapture';
|
|
3
|
+
/**
|
|
4
|
+
* Builds the multipart FormData payload for POST /api/reports/bug.
|
|
5
|
+
*
|
|
6
|
+
* Text fields are taken from the report object; the screenshot File
|
|
7
|
+
* (if any) must be appended by the caller after this function returns.
|
|
8
|
+
*
|
|
9
|
+
* Automatically includes:
|
|
10
|
+
* - browser context (URL, viewport, locale, user-agent)
|
|
11
|
+
* - config values (environment, appVersion, commitSha, user info, traceId)
|
|
12
|
+
* - recent console errors collected by installConsoleErrorHook()
|
|
13
|
+
*/
|
|
14
|
+
export function buildReportPayload(report, config, overrides) {
|
|
15
|
+
const contextDefaults = {
|
|
16
|
+
environment: config.environment,
|
|
17
|
+
appVersion: config.appVersion,
|
|
18
|
+
commitSha: config.commitSha,
|
|
19
|
+
buildNumber: config.buildNumber,
|
|
20
|
+
testerId: config.currentUser?.id,
|
|
21
|
+
testerRole: config.currentUser?.role,
|
|
22
|
+
contactEmail: config.currentUser?.email,
|
|
23
|
+
traceId: config.getTraceContext?.().traceId,
|
|
24
|
+
pageUrl: typeof window !== 'undefined' ? window.location.href : undefined,
|
|
25
|
+
route: typeof window !== 'undefined' ? window.location.pathname : undefined,
|
|
26
|
+
browser: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
|
|
27
|
+
locale: typeof navigator !== 'undefined' ? navigator.language : undefined,
|
|
28
|
+
viewport: typeof window !== 'undefined'
|
|
29
|
+
? `${window.innerWidth}x${window.innerHeight}`
|
|
30
|
+
: undefined,
|
|
31
|
+
// Automatically attach any console errors captured since page load
|
|
32
|
+
optionalClientErrors: getCollectedErrors(),
|
|
33
|
+
// Automatically attach any failed network requests captured since page load
|
|
34
|
+
failedNetworkRequests: getFailedNetworkRequests(),
|
|
35
|
+
};
|
|
36
|
+
// report fields take priority over context defaults; overrides take priority over everything
|
|
37
|
+
const payload = {
|
|
38
|
+
...contextDefaults,
|
|
39
|
+
...report,
|
|
40
|
+
...overrides,
|
|
41
|
+
};
|
|
42
|
+
const formData = new FormData();
|
|
43
|
+
Object.keys(payload).forEach((key) => {
|
|
44
|
+
const value = payload[key];
|
|
45
|
+
if (value !== undefined && value !== null) {
|
|
46
|
+
formData.append(key, String(value));
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return formData;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=payloadBuilder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payloadBuilder.js","sourceRoot":"","sources":["../../src/utils/payloadBuilder.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAqC,EACrC,MAAyB,EACzB,SAAqC;IAErC,MAAM,eAAe,GAA8B;QACjD,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,EAAE;QAChC,UAAU,EAAE,MAAM,CAAC,WAAW,EAAE,IAAI;QACpC,YAAY,EAAE,MAAM,CAAC,WAAW,EAAE,KAAK;QACvC,OAAO,EAAE,MAAM,CAAC,eAAe,EAAE,EAAE,CAAC,OAAO;QAC3C,OAAO,EAAE,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QACzE,KAAK,EAAE,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;QAC3E,OAAO,EAAE,OAAO,SAAS,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;QAC3E,MAAM,EAAE,OAAO,SAAS,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;QACzE,QAAQ,EACN,OAAO,MAAM,KAAK,WAAW;YAC3B,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,WAAW,EAAE;YAC9C,CAAC,CAAC,SAAS;QACf,mEAAmE;QACnE,oBAAoB,EAAE,kBAAkB,EAAE;QAC1C,4EAA4E;QAC5E,qBAAqB,EAAE,wBAAwB,EAAE;KAClD,CAAC;IAEF,6FAA6F;IAC7F,MAAM,OAAO,GAAqB;QAChC,GAAG,eAAe;QAClB,GAAG,MAAM;QACT,GAAG,SAAS;KACO,CAAC;IAEtB,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;IAE/B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAgC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QACnE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YAC1C,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACtC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full-page screenshot capture utility.
|
|
3
|
+
*
|
|
4
|
+
* Uses html2canvas when available to capture a client-side screenshot of the
|
|
5
|
+
* current page. html2canvas is loaded lazically from a CDN to keep the SDK
|
|
6
|
+
* bundle size small — it is only fetched when the user clicks "Capture screen".
|
|
7
|
+
*
|
|
8
|
+
* If html2canvas is not available (blocked by CSP, network error, etc.) the
|
|
9
|
+
* capture silently fails and the user can still attach a manual screenshot.
|
|
10
|
+
*
|
|
11
|
+
* @see https://html2canvas.hertzen.com/
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Captures a screenshot of the current page as a data URL.
|
|
15
|
+
*
|
|
16
|
+
* Returns `null` if html2canvas is unavailable or the capture fails.
|
|
17
|
+
*
|
|
18
|
+
* The overlay element (if any) is hidden during capture and restored after.
|
|
19
|
+
*/
|
|
20
|
+
export declare function capturePageScreenshot(): Promise<string | null>;
|
|
21
|
+
//# sourceMappingURL=screenshotCapture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshotCapture.d.ts","sourceRoot":"","sources":["../../src/utils/screenshotCapture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAkEH;;;;;;GAMG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmBpE"}
|