quick-bug-reporter-react 1.0.6 → 1.2.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 +129 -23
- package/dist/index.cjs +243 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -1
- package/dist/index.d.ts +37 -1
- package/dist/index.js +242 -34
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,7 +86,7 @@ Both integrations support two modes:
|
|
|
86
86
|
| Mode | When to use | How it works |
|
|
87
87
|
|------|------------|--------------|
|
|
88
88
|
| **Backend proxy** (recommended) | Production apps | Your server-side endpoint receives the report and calls Linear/Jira. API keys stay on the server. |
|
|
89
|
-
| **Direct API** | Server-side only (Next.js API routes, etc.) | The library calls Linear/Jira APIs directly.
|
|
89
|
+
| **Direct API** | Server-side only (Next.js API routes, etc.) | The library calls Linear/Jira APIs directly. **Does NOT work from browser-only SPAs due to CORS.** |
|
|
90
90
|
|
|
91
91
|
### Linear
|
|
92
92
|
|
|
@@ -98,15 +98,30 @@ const linear = new LinearIntegration({
|
|
|
98
98
|
submitProxyEndpoint: "/api/bug-report",
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
+
// Or split proxy endpoints for finer control:
|
|
102
|
+
const linear = new LinearIntegration({
|
|
103
|
+
createIssueProxyEndpoint: "/api/linear/create-issue",
|
|
104
|
+
uploadProxyEndpoint: "/api/linear/upload",
|
|
105
|
+
});
|
|
106
|
+
|
|
101
107
|
// ⚠️ Direct API — server-side / Next.js API routes only
|
|
102
|
-
// Does NOT work from browser SPAs (CORS + exposes API key)
|
|
103
108
|
const linear = new LinearIntegration({
|
|
104
109
|
apiKey: "lin_api_...",
|
|
105
110
|
teamId: "TEAM_ID",
|
|
106
|
-
projectId: "PROJECT_ID", // optional
|
|
111
|
+
projectId: "PROJECT_ID", // optional — assigns issues to a Linear project
|
|
107
112
|
});
|
|
108
113
|
```
|
|
109
114
|
|
|
115
|
+
| Option | Description |
|
|
116
|
+
|--------|-------------|
|
|
117
|
+
| `apiKey` | Linear API key (direct mode only) |
|
|
118
|
+
| `teamId` | Linear team ID |
|
|
119
|
+
| `projectId` | Linear project ID — optional, assigns every issue to this project |
|
|
120
|
+
| `submitProxyEndpoint` | Single endpoint that handles the entire submission |
|
|
121
|
+
| `createIssueProxyEndpoint` | Proxy for issue creation only |
|
|
122
|
+
| `uploadProxyEndpoint` | Proxy for file uploads only |
|
|
123
|
+
| `fetchImpl` | Custom fetch implementation |
|
|
124
|
+
|
|
110
125
|
### Jira
|
|
111
126
|
|
|
112
127
|
```ts
|
|
@@ -117,6 +132,13 @@ const jira = new JiraIntegration({
|
|
|
117
132
|
submitProxyEndpoint: "/api/bug-report",
|
|
118
133
|
});
|
|
119
134
|
|
|
135
|
+
// Or split proxy endpoints:
|
|
136
|
+
const jira = new JiraIntegration({
|
|
137
|
+
createIssueProxyEndpoint: "/api/jira/create-issue",
|
|
138
|
+
uploadAttachmentProxyEndpoint: "/api/jira/upload-attachment",
|
|
139
|
+
projectKey: "BUG",
|
|
140
|
+
});
|
|
141
|
+
|
|
120
142
|
// ⚠️ Direct API — server-side only (CORS + exposes credentials)
|
|
121
143
|
const jira = new JiraIntegration({
|
|
122
144
|
baseUrl: "https://your-domain.atlassian.net",
|
|
@@ -127,33 +149,117 @@ const jira = new JiraIntegration({
|
|
|
127
149
|
});
|
|
128
150
|
```
|
|
129
151
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
|
135
|
-
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
152
|
+
| Option | Description |
|
|
153
|
+
|--------|-------------|
|
|
154
|
+
| `baseUrl` | Jira instance URL (direct mode only) |
|
|
155
|
+
| `email` | Jira email (direct mode only) |
|
|
156
|
+
| `apiToken` | Jira API token (direct mode only) |
|
|
157
|
+
| `projectKey` | Jira project key (e.g. `"BUG"`) |
|
|
158
|
+
| `issueType` | Issue type name, defaults to `"Bug"` |
|
|
159
|
+
| `submitProxyEndpoint` | Single endpoint that handles the entire submission |
|
|
160
|
+
| `createIssueProxyEndpoint` | Proxy for issue creation only |
|
|
161
|
+
| `uploadAttachmentProxyEndpoint` | Proxy for attachment uploads only |
|
|
162
|
+
| `fetchImpl` | Custom fetch implementation |
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
### Proxy endpoint contract
|
|
167
|
+
|
|
168
|
+
When using `submitProxyEndpoint`, the library sends a single `FormData` POST with a **pre-formatted description** and all attachment files. Your proxy just needs to create the issue and upload the files — no custom formatting required.
|
|
169
|
+
|
|
170
|
+
#### FormData fields
|
|
171
|
+
|
|
172
|
+
| Field | Type | Always sent | Description |
|
|
173
|
+
|-------|------|:-----------:|-------------|
|
|
174
|
+
| `provider` | string | Yes | `"linear"` or `"jira"` |
|
|
175
|
+
| `title` | string | Yes | Issue title |
|
|
176
|
+
| `description` | string | Yes | **Pre-formatted** issue description (ready to use as-is) |
|
|
177
|
+
| `issueType` | string | Jira only | Issue type (e.g. `"Bug"`) |
|
|
178
|
+
| `projectKey` | string | Jira only | Jira project key (if configured) |
|
|
179
|
+
| `teamId` | string | Linear only | Linear team ID (if configured) |
|
|
180
|
+
| `projectId` | string | Linear only | Linear project ID (if configured) |
|
|
181
|
+
| `screenshotFile` | File | If screenshot | `bug-screenshot.png` |
|
|
182
|
+
| `screenRecordingFile` | File | If video | `bug-recording.webm` |
|
|
183
|
+
| `networkLogsFile` | File | Yes | `network-logs.txt` |
|
|
184
|
+
| `clientMetadataFile` | File | Yes | `client-metadata.json` |
|
|
185
|
+
| `consoleLogsFile` | File | If present | `console-logs.txt` (JS errors + console output) |
|
|
186
|
+
|
|
187
|
+
#### Expected response
|
|
188
|
+
|
|
189
|
+
**Jira proxy:**
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"jira": { "id": "10001", "key": "BUG-42", "url": "https://you.atlassian.net/browse/BUG-42" },
|
|
193
|
+
"warnings": []
|
|
194
|
+
}
|
|
195
|
+
```
|
|
149
196
|
|
|
197
|
+
**Linear proxy:**
|
|
150
198
|
```json
|
|
151
199
|
{
|
|
152
|
-
"linear": { "id": "...", "identifier": "ENG-123", "url": "https
|
|
200
|
+
"linear": { "id": "...", "identifier": "ENG-123", "url": "https://linear.app/..." },
|
|
153
201
|
"warnings": []
|
|
154
202
|
}
|
|
155
203
|
```
|
|
156
204
|
|
|
205
|
+
#### Example Jira proxy (Node.js / Express)
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
app.post("/api/bug-report", upload.any(), async (req, res) => {
|
|
209
|
+
const { title, description, issueType, projectKey } = req.body;
|
|
210
|
+
|
|
211
|
+
// 1. Convert plain-text description to Jira ADF
|
|
212
|
+
const adf = {
|
|
213
|
+
type: "doc", version: 1,
|
|
214
|
+
content: description.split(/\n{2,}/).filter(Boolean).map(chunk => ({
|
|
215
|
+
type: "paragraph",
|
|
216
|
+
content: [{ type: "text", text: chunk.trim() }],
|
|
217
|
+
})),
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// 2. Create the issue
|
|
221
|
+
const issue = await fetch(`${JIRA_BASE}/rest/api/3/issue`, {
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: {
|
|
224
|
+
Authorization: `Basic ${btoa(`${JIRA_EMAIL}:${JIRA_TOKEN}`)}`,
|
|
225
|
+
"Content-Type": "application/json",
|
|
226
|
+
},
|
|
227
|
+
body: JSON.stringify({
|
|
228
|
+
fields: {
|
|
229
|
+
project: { key: projectKey },
|
|
230
|
+
summary: title,
|
|
231
|
+
description: adf,
|
|
232
|
+
issuetype: { name: issueType || "Bug" },
|
|
233
|
+
},
|
|
234
|
+
}),
|
|
235
|
+
}).then(r => r.json());
|
|
236
|
+
|
|
237
|
+
// 3. Upload all attachment files
|
|
238
|
+
for (const file of req.files) {
|
|
239
|
+
const form = new FormData();
|
|
240
|
+
form.append("file", file.buffer, file.originalname);
|
|
241
|
+
await fetch(`${JIRA_BASE}/rest/api/3/issue/${issue.key}/attachments`, {
|
|
242
|
+
method: "POST",
|
|
243
|
+
headers: {
|
|
244
|
+
Authorization: `Basic ${btoa(`${JIRA_EMAIL}:${JIRA_TOKEN}`)}`,
|
|
245
|
+
"X-Atlassian-Token": "no-check",
|
|
246
|
+
},
|
|
247
|
+
body: form,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
res.json({ jira: { id: issue.id, key: issue.key, url: `${JIRA_BASE}/browse/${issue.key}` } });
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Advanced: Split proxy endpoints
|
|
256
|
+
|
|
257
|
+
Instead of a single `submitProxyEndpoint`, you can use separate endpoints for issue creation and file uploads. This gives the **library** full control over formatting while your proxy only handles auth:
|
|
258
|
+
|
|
259
|
+
- **`createIssueProxyEndpoint`** — receives `{ title, description, issueType, projectKey }` as JSON, returns `{ id, key, url }`
|
|
260
|
+
- **`uploadAttachmentProxyEndpoint`** (Jira) — receives `FormData` with `issueKey` + `file`, returns `{ ok: true }`
|
|
261
|
+
- **`uploadProxyEndpoint`** (Linear) — receives `FormData` with `file` + `filename` + `contentType`, returns `{ assetUrl }`
|
|
262
|
+
|
|
157
263
|
### Advanced: Custom fetch
|
|
158
264
|
|
|
159
265
|
Both integrations accept a `fetchImpl` option to customize how HTTP requests are made (useful for adding auth headers, logging, or proxying):
|
package/dist/index.cjs
CHANGED
|
@@ -555,6 +555,31 @@ var ScreenshotCapturer = class {
|
|
|
555
555
|
|
|
556
556
|
// src/core/types.ts
|
|
557
557
|
var DEFAULT_MAX_RECORDING_MS = 2 * 60 * 1e3;
|
|
558
|
+
function formatConsoleLogs(logs) {
|
|
559
|
+
if (logs.length === 0) {
|
|
560
|
+
return "No console output captured.";
|
|
561
|
+
}
|
|
562
|
+
return logs.map((entry) => {
|
|
563
|
+
const tag = entry.level.toUpperCase().padEnd(5);
|
|
564
|
+
const args = entry.args.join(" ");
|
|
565
|
+
return `[${entry.timestamp}] ${tag} ${args}`;
|
|
566
|
+
}).join("\n");
|
|
567
|
+
}
|
|
568
|
+
function formatJsErrors(errors) {
|
|
569
|
+
if (errors.length === 0) {
|
|
570
|
+
return "No JavaScript errors captured.";
|
|
571
|
+
}
|
|
572
|
+
return errors.map((entry) => {
|
|
573
|
+
const lines = [`[${entry.timestamp}] ${entry.type}: ${entry.message}`];
|
|
574
|
+
if (entry.source) {
|
|
575
|
+
lines.push(` at ${entry.source}${entry.lineno ? `:${entry.lineno}` : ""}${entry.colno ? `:${entry.colno}` : ""}`);
|
|
576
|
+
}
|
|
577
|
+
if (entry.stack) {
|
|
578
|
+
lines.push(entry.stack.split("\n").map((l) => ` ${l}`).join("\n"));
|
|
579
|
+
}
|
|
580
|
+
return lines.join("\n");
|
|
581
|
+
}).join("\n\n");
|
|
582
|
+
}
|
|
558
583
|
function formatNetworkLogs(logs) {
|
|
559
584
|
if (logs.length === 0) {
|
|
560
585
|
return "No network requests captured.";
|
|
@@ -859,6 +884,8 @@ var BugReporter = class {
|
|
|
859
884
|
videoBlob: artifacts.videoBlob,
|
|
860
885
|
screenshotBlob: options.screenshotBlob ?? artifacts.screenshotBlob,
|
|
861
886
|
networkLogs: artifacts.networkLogs,
|
|
887
|
+
consoleLogs: options.consoleLogs ?? [],
|
|
888
|
+
jsErrors: options.jsErrors ?? [],
|
|
862
889
|
captureMode: artifacts.captureMode,
|
|
863
890
|
pageUrl: typeof window !== "undefined" ? window.location.href : "",
|
|
864
891
|
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "",
|
|
@@ -898,6 +925,124 @@ var BugReporter = class {
|
|
|
898
925
|
}
|
|
899
926
|
};
|
|
900
927
|
|
|
928
|
+
// src/core/ConsoleCapture.ts
|
|
929
|
+
var MAX_CONSOLE_ENTRIES = 200;
|
|
930
|
+
var MAX_ERROR_ENTRIES = 50;
|
|
931
|
+
var MAX_ARG_LENGTH = 1e3;
|
|
932
|
+
function serializeArg(arg) {
|
|
933
|
+
if (typeof arg === "string") {
|
|
934
|
+
return arg.length > MAX_ARG_LENGTH ? arg.slice(0, MAX_ARG_LENGTH) + "\u2026" : arg;
|
|
935
|
+
}
|
|
936
|
+
try {
|
|
937
|
+
const json = JSON.stringify(arg);
|
|
938
|
+
return json.length > MAX_ARG_LENGTH ? json.slice(0, MAX_ARG_LENGTH) + "\u2026" : json;
|
|
939
|
+
} catch {
|
|
940
|
+
return String(arg);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
var ConsoleCapture = class {
|
|
944
|
+
constructor() {
|
|
945
|
+
this.consoleLogs = [];
|
|
946
|
+
this.errors = [];
|
|
947
|
+
this.originals = {};
|
|
948
|
+
this.errorHandler = null;
|
|
949
|
+
this.rejectionHandler = null;
|
|
950
|
+
this.active = false;
|
|
951
|
+
}
|
|
952
|
+
start() {
|
|
953
|
+
if (this.active || typeof window === "undefined") {
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
this.active = true;
|
|
957
|
+
this.consoleLogs = [];
|
|
958
|
+
this.errors = [];
|
|
959
|
+
const levels = ["log", "info", "warn", "error"];
|
|
960
|
+
for (const level of levels) {
|
|
961
|
+
const original = console[level];
|
|
962
|
+
this.originals[level] = original;
|
|
963
|
+
const capture = this;
|
|
964
|
+
console[level] = (...args) => {
|
|
965
|
+
try {
|
|
966
|
+
capture.consoleLogs.push({
|
|
967
|
+
level,
|
|
968
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
969
|
+
args: args.map(serializeArg)
|
|
970
|
+
});
|
|
971
|
+
if (capture.consoleLogs.length > MAX_CONSOLE_ENTRIES) {
|
|
972
|
+
capture.consoleLogs.shift();
|
|
973
|
+
}
|
|
974
|
+
} catch {
|
|
975
|
+
}
|
|
976
|
+
original.apply(console, args);
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
this.errorHandler = (event) => {
|
|
980
|
+
try {
|
|
981
|
+
this.errors.push({
|
|
982
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
983
|
+
message: event.message || "Unknown error",
|
|
984
|
+
source: event.filename || void 0,
|
|
985
|
+
lineno: event.lineno || void 0,
|
|
986
|
+
colno: event.colno || void 0,
|
|
987
|
+
stack: event.error?.stack || void 0,
|
|
988
|
+
type: "error"
|
|
989
|
+
});
|
|
990
|
+
if (this.errors.length > MAX_ERROR_ENTRIES) {
|
|
991
|
+
this.errors.shift();
|
|
992
|
+
}
|
|
993
|
+
} catch {
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
window.addEventListener("error", this.errorHandler);
|
|
997
|
+
this.rejectionHandler = (event) => {
|
|
998
|
+
try {
|
|
999
|
+
const reason = event.reason;
|
|
1000
|
+
this.errors.push({
|
|
1001
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1002
|
+
message: reason instanceof Error ? reason.message : String(reason),
|
|
1003
|
+
stack: reason instanceof Error ? reason.stack || void 0 : void 0,
|
|
1004
|
+
type: "unhandledrejection"
|
|
1005
|
+
});
|
|
1006
|
+
if (this.errors.length > MAX_ERROR_ENTRIES) {
|
|
1007
|
+
this.errors.shift();
|
|
1008
|
+
}
|
|
1009
|
+
} catch {
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
window.addEventListener("unhandledrejection", this.rejectionHandler);
|
|
1013
|
+
}
|
|
1014
|
+
snapshot() {
|
|
1015
|
+
return {
|
|
1016
|
+
consoleLogs: [...this.consoleLogs],
|
|
1017
|
+
jsErrors: [...this.errors]
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
stop() {
|
|
1021
|
+
if (!this.active) {
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
for (const [level, original] of Object.entries(this.originals)) {
|
|
1025
|
+
if (original) {
|
|
1026
|
+
console[level] = original;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
this.originals = {};
|
|
1030
|
+
if (this.errorHandler) {
|
|
1031
|
+
window.removeEventListener("error", this.errorHandler);
|
|
1032
|
+
this.errorHandler = null;
|
|
1033
|
+
}
|
|
1034
|
+
if (this.rejectionHandler) {
|
|
1035
|
+
window.removeEventListener("unhandledrejection", this.rejectionHandler);
|
|
1036
|
+
this.rejectionHandler = null;
|
|
1037
|
+
}
|
|
1038
|
+
this.active = false;
|
|
1039
|
+
}
|
|
1040
|
+
clear() {
|
|
1041
|
+
this.consoleLogs = [];
|
|
1042
|
+
this.errors = [];
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
|
|
901
1046
|
// src/integrations/linear.ts
|
|
902
1047
|
var DEFAULT_GRAPHQL_ENDPOINT = "https://api.linear.app/graphql";
|
|
903
1048
|
var noop = () => {
|
|
@@ -979,12 +1124,22 @@ var LinearIntegration = class {
|
|
|
979
1124
|
const description = buildCleanDescription(payload, { screenshotUrl, recordingUrl });
|
|
980
1125
|
const issue = await this.createIssue(payload.title, description);
|
|
981
1126
|
progress("Attaching logs\u2026");
|
|
1127
|
+
const comments = [];
|
|
982
1128
|
const logsComment = "### Network Logs\n```text\n" + formatNetworkLogs(payload.networkLogs) + "\n```";
|
|
1129
|
+
comments.push(this.addComment(issue.id, logsComment));
|
|
1130
|
+
if (payload.jsErrors.length > 0 || payload.consoleLogs.length > 0) {
|
|
1131
|
+
const parts = [];
|
|
1132
|
+
if (payload.jsErrors.length > 0) {
|
|
1133
|
+
parts.push("### JavaScript Errors\n```text\n" + formatJsErrors(payload.jsErrors) + "\n```");
|
|
1134
|
+
}
|
|
1135
|
+
if (payload.consoleLogs.length > 0) {
|
|
1136
|
+
parts.push("### Console Output\n```text\n" + formatConsoleLogs(payload.consoleLogs) + "\n```");
|
|
1137
|
+
}
|
|
1138
|
+
comments.push(this.addComment(issue.id, parts.join("\n\n")));
|
|
1139
|
+
}
|
|
983
1140
|
const metadataComment = "### Client Metadata\n```json\n" + JSON.stringify(payload.metadata, null, 2) + "\n```";
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
this.addComment(issue.id, metadataComment)
|
|
987
|
-
]);
|
|
1141
|
+
comments.push(this.addComment(issue.id, metadataComment));
|
|
1142
|
+
await Promise.all(comments);
|
|
988
1143
|
progress("Done!");
|
|
989
1144
|
return {
|
|
990
1145
|
provider: this.provider,
|
|
@@ -1001,22 +1156,30 @@ var LinearIntegration = class {
|
|
|
1001
1156
|
const formData = new FormData();
|
|
1002
1157
|
formData.set("provider", "linear");
|
|
1003
1158
|
formData.set("title", payload.title);
|
|
1004
|
-
formData.set("description", payload
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
const formattedLogs = formatNetworkLogs(payload.networkLogs);
|
|
1011
|
-
formData.set("networkLogs", formattedLogs);
|
|
1012
|
-
formData.append("requestsLogFile", new Blob([formattedLogs], { type: "text/plain" }), "network-logs.txt");
|
|
1013
|
-
if (payload.videoBlob) {
|
|
1014
|
-
const file = toBlobFile(payload.videoBlob, "bug-recording.webm", "video/webm");
|
|
1015
|
-
formData.append("screenRecordingFile", file, file.name);
|
|
1159
|
+
formData.set("description", buildCleanDescription(payload, { screenshotUrl: null, recordingUrl: null }));
|
|
1160
|
+
if (this.teamId) {
|
|
1161
|
+
formData.set("teamId", this.teamId);
|
|
1162
|
+
}
|
|
1163
|
+
if (this.projectId) {
|
|
1164
|
+
formData.set("projectId", this.projectId);
|
|
1016
1165
|
}
|
|
1017
1166
|
if (payload.screenshotBlob) {
|
|
1018
|
-
|
|
1019
|
-
|
|
1167
|
+
formData.append("screenshotFile", toBlobFile(payload.screenshotBlob, "bug-screenshot.png", "image/png"), "bug-screenshot.png");
|
|
1168
|
+
}
|
|
1169
|
+
if (payload.videoBlob) {
|
|
1170
|
+
formData.append("screenRecordingFile", toBlobFile(payload.videoBlob, "bug-recording.webm", "video/webm"), "bug-recording.webm");
|
|
1171
|
+
}
|
|
1172
|
+
formData.append("networkLogsFile", new Blob([formatNetworkLogs(payload.networkLogs)], { type: "text/plain" }), "network-logs.txt");
|
|
1173
|
+
formData.append("clientMetadataFile", new Blob([JSON.stringify(payload.metadata, null, 2)], { type: "application/json" }), "client-metadata.json");
|
|
1174
|
+
if (payload.consoleLogs.length > 0 || payload.jsErrors.length > 0) {
|
|
1175
|
+
const consoleParts = [];
|
|
1176
|
+
if (payload.jsErrors.length > 0) {
|
|
1177
|
+
consoleParts.push("=== JavaScript Errors ===\n" + formatJsErrors(payload.jsErrors));
|
|
1178
|
+
}
|
|
1179
|
+
if (payload.consoleLogs.length > 0) {
|
|
1180
|
+
consoleParts.push("=== Console Output ===\n" + formatConsoleLogs(payload.consoleLogs));
|
|
1181
|
+
}
|
|
1182
|
+
formData.append("consoleLogsFile", new Blob([consoleParts.join("\n\n")], { type: "text/plain" }), "console-logs.txt");
|
|
1020
1183
|
}
|
|
1021
1184
|
(onProgress ?? noop)("Submitting to Linear\u2026");
|
|
1022
1185
|
const response = await this.fetchImpl(this.submitProxyEndpoint, {
|
|
@@ -1317,6 +1480,17 @@ var JiraIntegration = class {
|
|
|
1317
1480
|
}
|
|
1318
1481
|
const logsBlob = new Blob([formatNetworkLogs(payload.networkLogs)], { type: "text/plain" });
|
|
1319
1482
|
uploads.push(this.uploadAttachment(issue.key, logsBlob, "network-logs.txt", "text/plain"));
|
|
1483
|
+
if (payload.consoleLogs.length > 0 || payload.jsErrors.length > 0) {
|
|
1484
|
+
const consoleParts = [];
|
|
1485
|
+
if (payload.jsErrors.length > 0) {
|
|
1486
|
+
consoleParts.push("=== JavaScript Errors ===\n" + formatJsErrors(payload.jsErrors));
|
|
1487
|
+
}
|
|
1488
|
+
if (payload.consoleLogs.length > 0) {
|
|
1489
|
+
consoleParts.push("=== Console Output ===\n" + formatConsoleLogs(payload.consoleLogs));
|
|
1490
|
+
}
|
|
1491
|
+
const consoleBlob = new Blob([consoleParts.join("\n\n")], { type: "text/plain" });
|
|
1492
|
+
uploads.push(this.uploadAttachment(issue.key, consoleBlob, "console-logs.txt", "text/plain"));
|
|
1493
|
+
}
|
|
1320
1494
|
const metadataBlob = new Blob([JSON.stringify(payload.metadata, null, 2)], { type: "application/json" });
|
|
1321
1495
|
uploads.push(this.uploadAttachment(issue.key, metadataBlob, "client-metadata.json", "application/json"));
|
|
1322
1496
|
await Promise.all(uploads);
|
|
@@ -1336,22 +1510,28 @@ var JiraIntegration = class {
|
|
|
1336
1510
|
const formData = new FormData();
|
|
1337
1511
|
formData.set("provider", "jira");
|
|
1338
1512
|
formData.set("title", payload.title);
|
|
1339
|
-
formData.set("description", payload
|
|
1340
|
-
formData.set("
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
formData.set("captureMode", payload.captureMode);
|
|
1344
|
-
formData.set("clientMetadata", JSON.stringify(payload.metadata));
|
|
1345
|
-
const formattedLogs = formatNetworkLogs(payload.networkLogs);
|
|
1346
|
-
formData.append("requestsLogFile", new Blob([formattedLogs], { type: "text/plain" }), "network-logs.txt");
|
|
1347
|
-
formData.append("clientMetadataFile", new Blob([JSON.stringify(payload.metadata, null, 2)], { type: "application/json" }), "client-metadata.json");
|
|
1348
|
-
if (payload.videoBlob) {
|
|
1349
|
-
const file = toBlobFile(payload.videoBlob, "bug-recording.webm", "video/webm");
|
|
1350
|
-
formData.append("screenRecordingFile", file, file.name);
|
|
1513
|
+
formData.set("description", buildCleanDescription2(payload));
|
|
1514
|
+
formData.set("issueType", this.issueType);
|
|
1515
|
+
if (this.projectKey) {
|
|
1516
|
+
formData.set("projectKey", this.projectKey);
|
|
1351
1517
|
}
|
|
1352
1518
|
if (payload.screenshotBlob) {
|
|
1353
|
-
|
|
1354
|
-
|
|
1519
|
+
formData.append("screenshotFile", toBlobFile(payload.screenshotBlob, "bug-screenshot.png", "image/png"), "bug-screenshot.png");
|
|
1520
|
+
}
|
|
1521
|
+
if (payload.videoBlob) {
|
|
1522
|
+
formData.append("screenRecordingFile", toBlobFile(payload.videoBlob, "bug-recording.webm", "video/webm"), "bug-recording.webm");
|
|
1523
|
+
}
|
|
1524
|
+
formData.append("networkLogsFile", new Blob([formatNetworkLogs(payload.networkLogs)], { type: "text/plain" }), "network-logs.txt");
|
|
1525
|
+
formData.append("clientMetadataFile", new Blob([JSON.stringify(payload.metadata, null, 2)], { type: "application/json" }), "client-metadata.json");
|
|
1526
|
+
if (payload.consoleLogs.length > 0 || payload.jsErrors.length > 0) {
|
|
1527
|
+
const consoleParts = [];
|
|
1528
|
+
if (payload.jsErrors.length > 0) {
|
|
1529
|
+
consoleParts.push("=== JavaScript Errors ===\n" + formatJsErrors(payload.jsErrors));
|
|
1530
|
+
}
|
|
1531
|
+
if (payload.consoleLogs.length > 0) {
|
|
1532
|
+
consoleParts.push("=== Console Output ===\n" + formatConsoleLogs(payload.consoleLogs));
|
|
1533
|
+
}
|
|
1534
|
+
formData.append("consoleLogsFile", new Blob([consoleParts.join("\n\n")], { type: "text/plain" }), "console-logs.txt");
|
|
1355
1535
|
}
|
|
1356
1536
|
(onProgress ?? noop2)("Submitting to Jira\u2026");
|
|
1357
1537
|
const response = await this.fetchImpl(this.submitProxyEndpoint, {
|
|
@@ -1617,6 +1797,16 @@ function BugReporterProvider({
|
|
|
1617
1797
|
imageHeight: 0
|
|
1618
1798
|
});
|
|
1619
1799
|
const reporterRef = react.useRef(null);
|
|
1800
|
+
const consoleCaptureRef = react.useRef(null);
|
|
1801
|
+
react.useEffect(() => {
|
|
1802
|
+
const capture = new ConsoleCapture();
|
|
1803
|
+
capture.start();
|
|
1804
|
+
consoleCaptureRef.current = capture;
|
|
1805
|
+
return () => {
|
|
1806
|
+
capture.stop();
|
|
1807
|
+
consoleCaptureRef.current = null;
|
|
1808
|
+
};
|
|
1809
|
+
}, []);
|
|
1620
1810
|
const availableProviders = react.useMemo(() => {
|
|
1621
1811
|
return ["linear", "jira"].filter((provider) => Boolean(integrations[provider]));
|
|
1622
1812
|
}, [integrations]);
|
|
@@ -1740,6 +1930,18 @@ function BugReporterProvider({
|
|
|
1740
1930
|
window.clearInterval(interval);
|
|
1741
1931
|
};
|
|
1742
1932
|
}, [isRecording]);
|
|
1933
|
+
react.useEffect(() => {
|
|
1934
|
+
if (!isRecording) {
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
const onBeforeUnload = (event) => {
|
|
1938
|
+
event.preventDefault();
|
|
1939
|
+
};
|
|
1940
|
+
window.addEventListener("beforeunload", onBeforeUnload);
|
|
1941
|
+
return () => {
|
|
1942
|
+
window.removeEventListener("beforeunload", onBeforeUnload);
|
|
1943
|
+
};
|
|
1944
|
+
}, [isRecording]);
|
|
1743
1945
|
react.useEffect(() => {
|
|
1744
1946
|
return () => {
|
|
1745
1947
|
setScreenshotPreviewUrl((current) => {
|
|
@@ -1963,10 +2165,16 @@ function BugReporterProvider({
|
|
|
1963
2165
|
highlights: screenshotAnnotation.highlights
|
|
1964
2166
|
} : void 0
|
|
1965
2167
|
};
|
|
2168
|
+
const { consoleLogs, jsErrors } = consoleCaptureRef.current?.snapshot() ?? {
|
|
2169
|
+
consoleLogs: [],
|
|
2170
|
+
jsErrors: []
|
|
2171
|
+
};
|
|
1966
2172
|
try {
|
|
1967
2173
|
const result = await reporter.submit(title, description, {
|
|
1968
2174
|
screenshotBlob: screenshotBlobForSubmit,
|
|
1969
2175
|
metadata,
|
|
2176
|
+
consoleLogs,
|
|
2177
|
+
jsErrors,
|
|
1970
2178
|
onProgress: setSubmissionProgress
|
|
1971
2179
|
});
|
|
1972
2180
|
setSuccess(`Submitted to ${getProviderLabel(result.provider)} (${result.issueKey}).`);
|
|
@@ -3040,6 +3248,7 @@ exports.BugReporter = BugReporter;
|
|
|
3040
3248
|
exports.BugReporterModal = BugReporterModal;
|
|
3041
3249
|
exports.BugReporterProvider = BugReporterProvider;
|
|
3042
3250
|
exports.BugSession = BugSession;
|
|
3251
|
+
exports.ConsoleCapture = ConsoleCapture;
|
|
3043
3252
|
exports.DEFAULT_MAX_RECORDING_MS = DEFAULT_MAX_RECORDING_MS;
|
|
3044
3253
|
exports.FloatingBugButton = FloatingBugButton;
|
|
3045
3254
|
exports.JiraIntegration = JiraIntegration;
|
|
@@ -3048,6 +3257,8 @@ exports.NetworkLogger = NetworkLogger;
|
|
|
3048
3257
|
exports.ScreenRecorder = ScreenRecorder;
|
|
3049
3258
|
exports.ScreenshotCapturer = ScreenshotCapturer;
|
|
3050
3259
|
exports.collectClientEnvironmentMetadata = collectClientEnvironmentMetadata;
|
|
3260
|
+
exports.formatConsoleLogs = formatConsoleLogs;
|
|
3261
|
+
exports.formatJsErrors = formatJsErrors;
|
|
3051
3262
|
exports.formatNetworkLogs = formatNetworkLogs;
|
|
3052
3263
|
exports.toErrorMessage = toErrorMessage;
|
|
3053
3264
|
exports.useBugReporter = useBugReporter;
|