quick-bug-reporter-react 1.1.1 → 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 +42 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +42 -28
- 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
|
@@ -1156,22 +1156,30 @@ var LinearIntegration = class {
|
|
|
1156
1156
|
const formData = new FormData();
|
|
1157
1157
|
formData.set("provider", "linear");
|
|
1158
1158
|
formData.set("title", payload.title);
|
|
1159
|
-
formData.set("description", payload
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
const formattedLogs = formatNetworkLogs(payload.networkLogs);
|
|
1166
|
-
formData.set("networkLogs", formattedLogs);
|
|
1167
|
-
formData.append("requestsLogFile", new Blob([formattedLogs], { type: "text/plain" }), "network-logs.txt");
|
|
1168
|
-
if (payload.videoBlob) {
|
|
1169
|
-
const file = toBlobFile(payload.videoBlob, "bug-recording.webm", "video/webm");
|
|
1170
|
-
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);
|
|
1171
1165
|
}
|
|
1172
1166
|
if (payload.screenshotBlob) {
|
|
1173
|
-
|
|
1174
|
-
|
|
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");
|
|
1175
1183
|
}
|
|
1176
1184
|
(onProgress ?? noop)("Submitting to Linear\u2026");
|
|
1177
1185
|
const response = await this.fetchImpl(this.submitProxyEndpoint, {
|
|
@@ -1502,22 +1510,28 @@ var JiraIntegration = class {
|
|
|
1502
1510
|
const formData = new FormData();
|
|
1503
1511
|
formData.set("provider", "jira");
|
|
1504
1512
|
formData.set("title", payload.title);
|
|
1505
|
-
formData.set("description", payload
|
|
1506
|
-
formData.set("
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
formData.set("captureMode", payload.captureMode);
|
|
1510
|
-
formData.set("clientMetadata", JSON.stringify(payload.metadata));
|
|
1511
|
-
const formattedLogs = formatNetworkLogs(payload.networkLogs);
|
|
1512
|
-
formData.append("requestsLogFile", new Blob([formattedLogs], { type: "text/plain" }), "network-logs.txt");
|
|
1513
|
-
formData.append("clientMetadataFile", new Blob([JSON.stringify(payload.metadata, null, 2)], { type: "application/json" }), "client-metadata.json");
|
|
1514
|
-
if (payload.videoBlob) {
|
|
1515
|
-
const file = toBlobFile(payload.videoBlob, "bug-recording.webm", "video/webm");
|
|
1516
|
-
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);
|
|
1517
1517
|
}
|
|
1518
1518
|
if (payload.screenshotBlob) {
|
|
1519
|
-
|
|
1520
|
-
|
|
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");
|
|
1521
1535
|
}
|
|
1522
1536
|
(onProgress ?? noop2)("Submitting to Jira\u2026");
|
|
1523
1537
|
const response = await this.fetchImpl(this.submitProxyEndpoint, {
|