quick-bug-reporter-react 1.1.1 → 1.3.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 +266 -52
- package/dist/index.cjs +1489 -1380
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -207
- package/dist/index.d.ts +4 -207
- package/dist/index.js +1489 -1381
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +11 -10
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ Drop-in bug reporter for React apps — screenshot capture, video recording, ann
|
|
|
8
8
|
- **Video recording** — Screen + microphone via `MediaRecorder` API
|
|
9
9
|
- **Annotation** — Drag-to-highlight on captured screenshots
|
|
10
10
|
- **Network logging** — Automatic fetch interception during capture
|
|
11
|
+
- **Console capture** — Automatic console log and JS error capture
|
|
11
12
|
- **Integrations** — Linear and Jira (direct API or backend proxy)
|
|
12
13
|
- **Zero config UI** — Floating bug button + modal wizard, ships its own styles
|
|
13
14
|
|
|
@@ -56,18 +57,20 @@ import {
|
|
|
56
57
|
BugReporterProvider,
|
|
57
58
|
FloatingBugButton,
|
|
58
59
|
BugReporterModal,
|
|
59
|
-
|
|
60
|
+
JiraIntegration,
|
|
60
61
|
} from "quick-bug-reporter-react";
|
|
61
62
|
|
|
62
|
-
const
|
|
63
|
-
|
|
63
|
+
const jira = new JiraIntegration({
|
|
64
|
+
createIssueProxyEndpoint: "/api/jira/create-issue",
|
|
65
|
+
uploadAttachmentProxyEndpoint: "/api/jira/upload-attachment",
|
|
66
|
+
projectKey: "BUG",
|
|
64
67
|
});
|
|
65
68
|
|
|
66
69
|
export default function App({ children }) {
|
|
67
70
|
return (
|
|
68
71
|
<BugReporterProvider
|
|
69
|
-
integrations={{
|
|
70
|
-
defaultProvider="
|
|
72
|
+
integrations={{ jira }}
|
|
73
|
+
defaultProvider="jira"
|
|
71
74
|
>
|
|
72
75
|
{children}
|
|
73
76
|
<FloatingBugButton />
|
|
@@ -81,78 +84,288 @@ That's it — a floating "Report Bug" button appears in the bottom-right corner.
|
|
|
81
84
|
|
|
82
85
|
## Integrations
|
|
83
86
|
|
|
84
|
-
|
|
87
|
+
### Jira
|
|
85
88
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
```ts
|
|
90
|
+
import { JiraIntegration } from "quick-bug-reporter-react";
|
|
91
|
+
|
|
92
|
+
// ✅ Recommended: split proxy endpoints (parallel uploads, fastest)
|
|
93
|
+
const jira = new JiraIntegration({
|
|
94
|
+
createIssueProxyEndpoint: "/api/jira/create-issue",
|
|
95
|
+
uploadAttachmentProxyEndpoint: "/api/jira/upload-attachment",
|
|
96
|
+
projectKey: "BUG",
|
|
97
|
+
issueType: "Bug", // optional, defaults to "Bug"
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
| Option | Description |
|
|
102
|
+
|--------|-------------|
|
|
103
|
+
| `projectKey` | Jira project key (e.g. `"BUG"`) |
|
|
104
|
+
| `issueType` | Issue type name, defaults to `"Bug"` |
|
|
105
|
+
| `createIssueProxyEndpoint` | Proxy for issue creation (recommended) |
|
|
106
|
+
| `uploadAttachmentProxyEndpoint` | Proxy for attachment uploads (recommended) |
|
|
107
|
+
| `submitProxyEndpoint` | Single endpoint fallback (slower — see [below](#legacy-single-endpoint)) |
|
|
108
|
+
| `baseUrl` | Jira instance URL (direct mode only — not for browser SPAs) |
|
|
109
|
+
| `email` | Jira email (direct mode only) |
|
|
110
|
+
| `apiToken` | Jira API token (direct mode only) |
|
|
111
|
+
| `fetchImpl` | Custom fetch implementation |
|
|
90
112
|
|
|
91
113
|
### Linear
|
|
92
114
|
|
|
93
115
|
```ts
|
|
94
116
|
import { LinearIntegration } from "quick-bug-reporter-react";
|
|
95
117
|
|
|
96
|
-
// ✅ Recommended:
|
|
118
|
+
// ✅ Recommended: split proxy endpoints (parallel uploads, fastest)
|
|
97
119
|
const linear = new LinearIntegration({
|
|
98
|
-
|
|
120
|
+
createIssueProxyEndpoint: "/api/linear/create-issue",
|
|
121
|
+
uploadProxyEndpoint: "/api/linear/upload",
|
|
122
|
+
teamId: "TEAM_ID",
|
|
123
|
+
projectId: "PROJECT_ID", // optional — assigns issues to a Linear project
|
|
99
124
|
});
|
|
125
|
+
```
|
|
100
126
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
127
|
+
| Option | Description |
|
|
128
|
+
|--------|-------------|
|
|
129
|
+
| `teamId` | Linear team ID |
|
|
130
|
+
| `projectId` | Linear project ID — optional, assigns every issue to this project |
|
|
131
|
+
| `createIssueProxyEndpoint` | Proxy for issue creation (recommended) |
|
|
132
|
+
| `uploadProxyEndpoint` | Proxy for file uploads (recommended) |
|
|
133
|
+
| `submitProxyEndpoint` | Single endpoint fallback (slower — see [below](#legacy-single-endpoint)) |
|
|
134
|
+
| `apiKey` | Linear API key (direct mode only — not for browser SPAs) |
|
|
135
|
+
| `fetchImpl` | Custom fetch implementation |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### Proxy endpoint contract (split endpoints)
|
|
140
|
+
|
|
141
|
+
With split endpoints the library handles formatting and **uploads attachments in parallel** — your proxy only needs to forward auth. This is ~3× faster than a single endpoint.
|
|
142
|
+
|
|
143
|
+
#### `createIssueProxyEndpoint`
|
|
144
|
+
|
|
145
|
+
Receives a JSON POST:
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"summary": "Bug title", // Jira
|
|
150
|
+
"title": "Bug title", // Linear
|
|
151
|
+
"description": "Pre-formatted description (ready to use as-is)",
|
|
152
|
+
"issueType": "Bug", // Jira only
|
|
153
|
+
"projectKey": "BUG", // Jira only
|
|
154
|
+
"teamId": "...", // Linear only
|
|
155
|
+
"projectId": "..." // Linear only (if configured)
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Must return:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
// Jira
|
|
163
|
+
{ "id": "10001", "key": "BUG-42", "url": "https://you.atlassian.net/browse/BUG-42" }
|
|
164
|
+
|
|
165
|
+
// Linear
|
|
166
|
+
{ "id": "...", "identifier": "ENG-123", "url": "https://linear.app/..." }
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### `uploadAttachmentProxyEndpoint` (Jira)
|
|
170
|
+
|
|
171
|
+
Receives `FormData` with `issueKey` (string) + `file` (File). Returns `{ "ok": true }`.
|
|
172
|
+
|
|
173
|
+
#### `uploadProxyEndpoint` (Linear)
|
|
174
|
+
|
|
175
|
+
Receives `FormData` with `file` (File) + `filename` (string) + `contentType` (string). Returns `{ "assetUrl": "https://..." }`.
|
|
176
|
+
|
|
177
|
+
#### Example: Jira proxy (Node.js / Express)
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
// POST /api/jira/create-issue — forward issue creation with auth
|
|
181
|
+
app.post("/api/jira/create-issue", express.json(), async (req, res) => {
|
|
182
|
+
const { summary, description, issueType, projectKey } = req.body;
|
|
183
|
+
|
|
184
|
+
// Convert plain-text description to Jira ADF
|
|
185
|
+
const adf = {
|
|
186
|
+
type: "doc", version: 1,
|
|
187
|
+
content: description.split(/\n{2,}/).filter(Boolean).map(chunk => ({
|
|
188
|
+
type: "paragraph",
|
|
189
|
+
content: [{ type: "text", text: chunk.trim() }],
|
|
190
|
+
})),
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const jiraRes = await fetch(`${JIRA_BASE}/rest/api/3/issue`, {
|
|
194
|
+
method: "POST",
|
|
195
|
+
headers: {
|
|
196
|
+
Authorization: `Basic ${btoa(`${JIRA_EMAIL}:${JIRA_TOKEN}`)}`,
|
|
197
|
+
"Content-Type": "application/json",
|
|
198
|
+
},
|
|
199
|
+
body: JSON.stringify({
|
|
200
|
+
fields: {
|
|
201
|
+
project: { key: projectKey },
|
|
202
|
+
summary,
|
|
203
|
+
description: adf,
|
|
204
|
+
issuetype: { name: issueType || "Bug" },
|
|
205
|
+
},
|
|
206
|
+
}),
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const data = await jiraRes.json();
|
|
210
|
+
res.status(jiraRes.ok ? 201 : jiraRes.status).json(
|
|
211
|
+
jiraRes.ok
|
|
212
|
+
? { id: data.id, key: data.key, url: `${JIRA_BASE}/browse/${data.key}` }
|
|
213
|
+
: { error: data.errorMessages?.join("; ") || "Jira issue creation failed" }
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// POST /api/jira/upload-attachment — forward file upload with auth
|
|
218
|
+
app.post("/api/jira/upload-attachment", upload.single("file"), async (req, res) => {
|
|
219
|
+
const issueKey = req.body.issueKey;
|
|
220
|
+
const form = new FormData();
|
|
221
|
+
form.append("file", req.file.buffer, req.file.originalname);
|
|
222
|
+
|
|
223
|
+
const jiraRes = await fetch(`${JIRA_BASE}/rest/api/3/issue/${issueKey}/attachments`, {
|
|
224
|
+
method: "POST",
|
|
225
|
+
headers: {
|
|
226
|
+
Authorization: `Basic ${btoa(`${JIRA_EMAIL}:${JIRA_TOKEN}`)}`,
|
|
227
|
+
"X-Atlassian-Token": "no-check",
|
|
228
|
+
},
|
|
229
|
+
body: form,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
res.status(jiraRes.ok ? 200 : jiraRes.status).json(
|
|
233
|
+
jiraRes.ok ? { ok: true } : { error: "Upload failed" }
|
|
234
|
+
);
|
|
107
235
|
});
|
|
108
236
|
```
|
|
109
237
|
|
|
110
|
-
|
|
238
|
+
#### Example: Linear proxy (Node.js / Express)
|
|
239
|
+
|
|
240
|
+
Linear uses a **GraphQL API**. Your proxy forwards mutations to `https://api.linear.app/graphql` with the API key server-side.
|
|
111
241
|
|
|
112
242
|
```ts
|
|
113
|
-
|
|
243
|
+
const LINEAR_API = "https://api.linear.app/graphql";
|
|
244
|
+
const LINEAR_API_KEY = process.env.LINEAR_API_KEY; // e.g. "lin_api_..."
|
|
245
|
+
|
|
246
|
+
// Helper: execute a Linear GraphQL mutation
|
|
247
|
+
async function linearGql(query: string, variables: Record<string, unknown>) {
|
|
248
|
+
const res = await fetch(LINEAR_API, {
|
|
249
|
+
method: "POST",
|
|
250
|
+
headers: {
|
|
251
|
+
Authorization: LINEAR_API_KEY,
|
|
252
|
+
"Content-Type": "application/json",
|
|
253
|
+
},
|
|
254
|
+
body: JSON.stringify({ query, variables }),
|
|
255
|
+
});
|
|
256
|
+
const body = await res.json();
|
|
257
|
+
if (!res.ok || body.errors?.length) {
|
|
258
|
+
throw new Error(body.errors?.[0]?.message || `Linear API error (${res.status})`);
|
|
259
|
+
}
|
|
260
|
+
return body.data;
|
|
261
|
+
}
|
|
114
262
|
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
263
|
+
// POST /api/linear/create-issue — create issue via GraphQL
|
|
264
|
+
app.post("/api/linear/create-issue", express.json(), async (req, res) => {
|
|
265
|
+
const { title, description, teamId, projectId } = req.body;
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const data = await linearGql(
|
|
269
|
+
`mutation IssueCreate($input: IssueCreateInput!) {
|
|
270
|
+
issueCreate(input: $input) {
|
|
271
|
+
success
|
|
272
|
+
issue { id identifier url }
|
|
273
|
+
}
|
|
274
|
+
}`,
|
|
275
|
+
{
|
|
276
|
+
input: {
|
|
277
|
+
title,
|
|
278
|
+
description,
|
|
279
|
+
teamId,
|
|
280
|
+
...(projectId ? { projectId } : {}),
|
|
281
|
+
},
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const issue = data.issueCreate.issue;
|
|
286
|
+
res.status(201).json({
|
|
287
|
+
id: issue.id,
|
|
288
|
+
identifier: issue.identifier,
|
|
289
|
+
url: issue.url,
|
|
290
|
+
});
|
|
291
|
+
} catch (err) {
|
|
292
|
+
res.status(500).json({ error: err.message });
|
|
293
|
+
}
|
|
118
294
|
});
|
|
119
295
|
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
296
|
+
// POST /api/linear/upload — upload file via GraphQL fileUpload + PUT
|
|
297
|
+
app.post("/api/linear/upload", upload.single("file"), async (req, res) => {
|
|
298
|
+
const file = req.file;
|
|
299
|
+
const contentType = req.body.contentType || file.mimetype;
|
|
300
|
+
const filename = req.body.filename || file.originalname;
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
// 1. Request an upload URL from Linear
|
|
304
|
+
const data = await linearGql(
|
|
305
|
+
`mutation FileUpload($contentType: String!, $filename: String!, $size: Int!) {
|
|
306
|
+
fileUpload(contentType: $contentType, filename: $filename, size: $size) {
|
|
307
|
+
success
|
|
308
|
+
uploadFile { uploadUrl assetUrl headers { key value } }
|
|
309
|
+
}
|
|
310
|
+
}`,
|
|
311
|
+
{ contentType, filename, size: file.size }
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const { uploadUrl, assetUrl, headers } = data.fileUpload.uploadFile;
|
|
315
|
+
|
|
316
|
+
// 2. PUT the file to the upload URL with Linear's signed headers
|
|
317
|
+
const uploadHeaders: Record<string, string> = {};
|
|
318
|
+
for (const h of headers) uploadHeaders[h.key] = h.value;
|
|
319
|
+
|
|
320
|
+
const uploadRes = await fetch(uploadUrl, {
|
|
321
|
+
method: "PUT",
|
|
322
|
+
headers: uploadHeaders,
|
|
323
|
+
body: file.buffer,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
if (!uploadRes.ok) throw new Error(`Upload failed (${uploadRes.status})`);
|
|
327
|
+
|
|
328
|
+
res.json({ assetUrl });
|
|
329
|
+
} catch (err) {
|
|
330
|
+
res.status(500).json({ error: err.message });
|
|
331
|
+
}
|
|
127
332
|
});
|
|
128
333
|
```
|
|
129
334
|
|
|
130
|
-
|
|
335
|
+
> **Key points for Linear proxy implementers:**
|
|
336
|
+
> - The `create-issue` endpoint receives `{ title, description, teamId, projectId }` — use these directly in the `IssueCreateInput` GraphQL mutation. The `description` is already pre-formatted by the library.
|
|
337
|
+
> - The `upload` endpoint must: (1) call `fileUpload` mutation to get a signed upload URL, (2) `PUT` the file binary to that URL with the returned headers, (3) return the `assetUrl`.
|
|
338
|
+
> - `projectId` is optional — only include it in the mutation input if it's present in the request body.
|
|
131
339
|
|
|
132
|
-
|
|
340
|
+
---
|
|
133
341
|
|
|
134
|
-
|
|
135
|
-
|-------|------|-------------|
|
|
136
|
-
| `provider` | string | `"linear"` or `"jira"` |
|
|
137
|
-
| `title` | string | Bug title |
|
|
138
|
-
| `description` | string | Bug description |
|
|
139
|
-
| `pageUrl` | string | URL where the bug was reported |
|
|
140
|
-
| `userAgent` | string | Browser user agent |
|
|
141
|
-
| `captureMode` | string | `"screenshot"` or `"video"` |
|
|
142
|
-
| `clientMetadata` | JSON string | Device/browser metadata |
|
|
143
|
-
| `networkLogs` | string | Formatted network logs |
|
|
144
|
-
| `screenshotFile` | File | Screenshot PNG (if applicable) |
|
|
145
|
-
| `screenRecordingFile` | File | Screen recording WebM (if applicable) |
|
|
146
|
-
| `requestsLogFile` | File | Network logs as .txt |
|
|
342
|
+
### Legacy: single endpoint
|
|
147
343
|
|
|
148
|
-
The proxy
|
|
344
|
+
`submitProxyEndpoint` bundles everything into one request. The proxy must create the issue **and** upload all attachments server-side, which means uploads happen sequentially and are **~3× slower**. Use split endpoints above instead when possible.
|
|
149
345
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
346
|
+
<details>
|
|
347
|
+
<summary>Single endpoint FormData fields (click to expand)</summary>
|
|
348
|
+
|
|
349
|
+
| Field | Type | Always sent | Description |
|
|
350
|
+
|-------|------|:-----------:|-------------|
|
|
351
|
+
| `provider` | string | Yes | `"linear"` or `"jira"` |
|
|
352
|
+
| `title` | string | Yes | Issue title |
|
|
353
|
+
| `description` | string | Yes | **Pre-formatted** issue description (ready to use as-is) |
|
|
354
|
+
| `issueType` | string | Jira only | Issue type (e.g. `"Bug"`) |
|
|
355
|
+
| `projectKey` | string | Jira only | Jira project key (if configured) |
|
|
356
|
+
| `teamId` | string | Linear only | Linear team ID (if configured) |
|
|
357
|
+
| `projectId` | string | Linear only | Linear project ID (if configured) |
|
|
358
|
+
| `screenshotFile` | File | If screenshot | `bug-screenshot.png` |
|
|
359
|
+
| `screenRecordingFile` | File | If video | `bug-recording.webm` |
|
|
360
|
+
| `networkLogsFile` | File | Yes | `network-logs.txt` |
|
|
361
|
+
| `clientMetadataFile` | File | Yes | `client-metadata.json` |
|
|
362
|
+
| `consoleLogsFile` | File | If present | `console-logs.txt` (JS errors + console output) |
|
|
363
|
+
|
|
364
|
+
**Jira response:** `{ "jira": { "id": "...", "key": "BUG-42", "url": "..." }, "warnings": [] }`
|
|
365
|
+
|
|
366
|
+
**Linear response:** `{ "linear": { "id": "...", "identifier": "ENG-123", "url": "..." }, "warnings": [] }`
|
|
367
|
+
|
|
368
|
+
</details>
|
|
156
369
|
|
|
157
370
|
### Advanced: Custom fetch
|
|
158
371
|
|
|
@@ -185,6 +398,7 @@ const linear = new LinearIntegration({
|
|
|
185
398
|
- `ScreenshotCapturer` — HTML-to-canvas screenshot engine
|
|
186
399
|
- `ScreenRecorder` — Screen + mic recording via MediaRecorder
|
|
187
400
|
- `NetworkLogger` — Fetch interception logger
|
|
401
|
+
- `ConsoleCapture` — Console log and JS error capture
|
|
188
402
|
|
|
189
403
|
### Integrations
|
|
190
404
|
|