@outcode/bug-reporter-core 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/LICENSE +21 -0
- package/README.md +51 -0
- package/dist/index.d.mts +406 -0
- package/dist/index.d.ts +406 -0
- package/dist/index.js +570 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +554 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +51 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
// src/theme.ts
|
|
2
|
+
var THEMES = {
|
|
3
|
+
indigo: {
|
|
4
|
+
canvas: "#EBECF0",
|
|
5
|
+
surface: "#ffffff",
|
|
6
|
+
panel: "#ffffff",
|
|
7
|
+
border: "#E7E8EC",
|
|
8
|
+
borderStrong: "#D3D5DB",
|
|
9
|
+
text: "#15161A",
|
|
10
|
+
muted: "#6A6C75",
|
|
11
|
+
faint: "#A6A8B2",
|
|
12
|
+
accent: "#5E6AD2",
|
|
13
|
+
accentPress: "#4F58BE",
|
|
14
|
+
onAccent: "#ffffff",
|
|
15
|
+
ring: "rgba(94,106,210,0.14)",
|
|
16
|
+
ok: "#2E9E6B",
|
|
17
|
+
okBg: "rgba(46,158,107,0.10)",
|
|
18
|
+
shadow: "0 24px 60px rgba(20,22,40,0.18)",
|
|
19
|
+
dark: false
|
|
20
|
+
},
|
|
21
|
+
noir: {
|
|
22
|
+
canvas: "#08090B",
|
|
23
|
+
surface: "#161719",
|
|
24
|
+
panel: "#161719",
|
|
25
|
+
border: "#26282C",
|
|
26
|
+
borderStrong: "#34373C",
|
|
27
|
+
text: "#F3F4F6",
|
|
28
|
+
muted: "#9B9EA6",
|
|
29
|
+
faint: "#6C6F77",
|
|
30
|
+
accent: "#6E6AF0",
|
|
31
|
+
accentPress: "#5B57E0",
|
|
32
|
+
onAccent: "#ffffff",
|
|
33
|
+
ring: "rgba(110,106,240,0.20)",
|
|
34
|
+
ok: "#3FD08A",
|
|
35
|
+
okBg: "rgba(63,208,138,0.12)",
|
|
36
|
+
shadow: "0 24px 70px rgba(0,0,0,0.6)",
|
|
37
|
+
dark: true
|
|
38
|
+
},
|
|
39
|
+
mint: {
|
|
40
|
+
canvas: "#E8EFEC",
|
|
41
|
+
surface: "#ffffff",
|
|
42
|
+
panel: "#ffffff",
|
|
43
|
+
border: "#DCE6E1",
|
|
44
|
+
borderStrong: "#C5D3CC",
|
|
45
|
+
text: "#0E1A15",
|
|
46
|
+
muted: "#5C6B64",
|
|
47
|
+
faint: "#90A099",
|
|
48
|
+
accent: "#0E9D78",
|
|
49
|
+
accentPress: "#0B8266",
|
|
50
|
+
onAccent: "#ffffff",
|
|
51
|
+
ring: "rgba(14,157,120,0.14)",
|
|
52
|
+
ok: "#0E9D78",
|
|
53
|
+
okBg: "rgba(14,157,120,0.10)",
|
|
54
|
+
shadow: "0 24px 60px rgba(10,40,30,0.16)",
|
|
55
|
+
dark: false
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
function resolveTheme(theme) {
|
|
59
|
+
if (!theme) return THEMES.indigo;
|
|
60
|
+
if (typeof theme === "string") return THEMES[theme] ?? THEMES.indigo;
|
|
61
|
+
return { ...THEMES.indigo, ...theme };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/reportMeta.ts
|
|
65
|
+
function normalizeContext(input) {
|
|
66
|
+
if (!input) return [];
|
|
67
|
+
if (Array.isArray(input)) {
|
|
68
|
+
return input.filter((r) => r && r.label != null && String(r.value ?? "").trim() !== "");
|
|
69
|
+
}
|
|
70
|
+
return Object.entries(input).filter(([, v]) => v != null && String(v).trim() !== "").map(([label, value]) => ({ label, value: String(value) }));
|
|
71
|
+
}
|
|
72
|
+
var SEVERITIES = [
|
|
73
|
+
{ id: "low", label: "Low", color: "#3E63DD" },
|
|
74
|
+
{ id: "medium", label: "Medium", color: "#F5A524" },
|
|
75
|
+
{ id: "high", label: "High", color: "#F76B15" },
|
|
76
|
+
{ id: "critical", label: "Critical", color: "#E5484D" }
|
|
77
|
+
];
|
|
78
|
+
var REPORT_TYPES = [
|
|
79
|
+
{ id: "bug", label: "Bug" },
|
|
80
|
+
{ id: "crash", label: "Crash" },
|
|
81
|
+
{ id: "ui", label: "UI / Visual" },
|
|
82
|
+
{ id: "perf", label: "Performance" },
|
|
83
|
+
{ id: "other", label: "Other" }
|
|
84
|
+
];
|
|
85
|
+
var ANNOTATION_COLORS = [
|
|
86
|
+
"#E5484D",
|
|
87
|
+
"#F76B15",
|
|
88
|
+
"#F5A524",
|
|
89
|
+
"#2E9E6B",
|
|
90
|
+
"#3E63DD",
|
|
91
|
+
"#8E4EC6",
|
|
92
|
+
"#15161A",
|
|
93
|
+
"#FFFFFF"
|
|
94
|
+
];
|
|
95
|
+
var ANNOTATION_TOOLS = [
|
|
96
|
+
{ id: "pen", label: "Draw", iconPath: "M4 20l3.6-1L18 8.6 15.4 6 5 16.4 4 20z" },
|
|
97
|
+
{ id: "arrow", label: "Arrow", iconPath: "M6 18L18 6M18 6h-7M18 6v7" },
|
|
98
|
+
{ id: "rect", label: "Box", iconPath: "M4.5 6h15v12h-15z" },
|
|
99
|
+
{ id: "blur", label: "Blur", iconPath: "M12 3C8.6 8.4 6.5 11 6.5 14a5.5 5.5 0 0011 0c0-3-2.1-5.6-5.5-11z" },
|
|
100
|
+
{ id: "text", label: "Text", iconPath: "M6 6h12M12 6v12M9.5 18h5" }
|
|
101
|
+
];
|
|
102
|
+
function severityToPriority(severity) {
|
|
103
|
+
switch (severity) {
|
|
104
|
+
case "critical":
|
|
105
|
+
return "urgent";
|
|
106
|
+
case "high":
|
|
107
|
+
return "high";
|
|
108
|
+
case "medium":
|
|
109
|
+
return "normal";
|
|
110
|
+
case "low":
|
|
111
|
+
return "low";
|
|
112
|
+
default:
|
|
113
|
+
return void 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/api.ts
|
|
118
|
+
async function submitBugReport(request, config) {
|
|
119
|
+
const apiUrl = config.apiUrl;
|
|
120
|
+
if (!apiUrl) {
|
|
121
|
+
return { success: false, message: "apiUrl or repository is required" };
|
|
122
|
+
}
|
|
123
|
+
const body = {
|
|
124
|
+
title: request.title,
|
|
125
|
+
description: request.description,
|
|
126
|
+
screenshot_base64: request.screenshotBase64,
|
|
127
|
+
steps_to_reproduce: request.stepsToReproduce,
|
|
128
|
+
metadata: {
|
|
129
|
+
app_name: config.appName,
|
|
130
|
+
app_version: config.appVersion,
|
|
131
|
+
...request.metadata
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const response = await fetch(apiUrl, {
|
|
135
|
+
method: "POST",
|
|
136
|
+
headers: {
|
|
137
|
+
"Content-Type": "application/json",
|
|
138
|
+
...config.headers
|
|
139
|
+
},
|
|
140
|
+
body: JSON.stringify(body)
|
|
141
|
+
});
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
const text = await response.text();
|
|
144
|
+
return {
|
|
145
|
+
success: false,
|
|
146
|
+
message: text || `HTTP ${response.status}`
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const data = await response.json();
|
|
151
|
+
return { success: true, message: "Submitted", ...data };
|
|
152
|
+
} catch {
|
|
153
|
+
return { success: true, message: "Submitted" };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function buildCreateReportParams(config, options) {
|
|
157
|
+
return {
|
|
158
|
+
title: options.title,
|
|
159
|
+
description: options.description,
|
|
160
|
+
screenshotBase64: options.screenshotBase64,
|
|
161
|
+
isReportingProblem: options.isReportingProblem ?? true,
|
|
162
|
+
priority: options.priority ?? severityToPriority(options.severity) ?? config.defaultPriority,
|
|
163
|
+
severity: options.severity,
|
|
164
|
+
type: options.type,
|
|
165
|
+
tags: options.tags ?? (options.type ? [options.type] : void 0),
|
|
166
|
+
context: options.context,
|
|
167
|
+
diagnostics: config.collectDiagnostics?.(),
|
|
168
|
+
deviceInfo: {
|
|
169
|
+
platform: options.metadata?.platform ?? "web",
|
|
170
|
+
userAgent: options.metadata?.userAgent,
|
|
171
|
+
...config.deviceInfo,
|
|
172
|
+
...options.metadata
|
|
173
|
+
},
|
|
174
|
+
packageInfo: {
|
|
175
|
+
appName: config.appName,
|
|
176
|
+
appVersion: config.appVersion,
|
|
177
|
+
...config.packageInfo
|
|
178
|
+
},
|
|
179
|
+
userInfo: void 0
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async function submitReport(config, options) {
|
|
183
|
+
if (config.repository) {
|
|
184
|
+
return config.repository.createReport(buildCreateReportParams(config, options));
|
|
185
|
+
}
|
|
186
|
+
return submitBugReport(
|
|
187
|
+
{
|
|
188
|
+
title: options.title,
|
|
189
|
+
description: options.description,
|
|
190
|
+
screenshotBase64: options.screenshotBase64,
|
|
191
|
+
metadata: {
|
|
192
|
+
priority: options.priority ?? severityToPriority(options.severity) ?? config.defaultPriority,
|
|
193
|
+
severity: options.severity,
|
|
194
|
+
type: options.type,
|
|
195
|
+
...options.metadata
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
config
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/queue.ts
|
|
203
|
+
var DEFAULT_KEY = "outcode_bug_reporter_queue";
|
|
204
|
+
var MAX_ENTRIES = 10;
|
|
205
|
+
var ReportQueue = class {
|
|
206
|
+
constructor(storage, key = DEFAULT_KEY) {
|
|
207
|
+
this.storage = storage;
|
|
208
|
+
this.key = key;
|
|
209
|
+
}
|
|
210
|
+
async read() {
|
|
211
|
+
try {
|
|
212
|
+
const raw = await this.storage.getItem(this.key);
|
|
213
|
+
if (!raw) return [];
|
|
214
|
+
const parsed = JSON.parse(raw);
|
|
215
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
216
|
+
} catch {
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async write(items) {
|
|
221
|
+
try {
|
|
222
|
+
await this.storage.setItem(this.key, JSON.stringify(items));
|
|
223
|
+
} catch {
|
|
224
|
+
try {
|
|
225
|
+
const slim = items.map(({ screenshotBase64: _omit, ...rest }) => rest);
|
|
226
|
+
await this.storage.setItem(this.key, JSON.stringify(slim));
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/** Number of reports currently waiting to be retried. */
|
|
232
|
+
async size() {
|
|
233
|
+
return (await this.read()).length;
|
|
234
|
+
}
|
|
235
|
+
/** Persist a report that failed to send. Oldest entries are dropped past the cap. */
|
|
236
|
+
async enqueue(options) {
|
|
237
|
+
const items = await this.read();
|
|
238
|
+
items.push(options);
|
|
239
|
+
while (items.length > MAX_ENTRIES) items.shift();
|
|
240
|
+
await this.write(items);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Replay all queued reports using the current config. Reports that submit
|
|
244
|
+
* successfully are removed; failures stay queued for the next attempt.
|
|
245
|
+
*/
|
|
246
|
+
async flush(config) {
|
|
247
|
+
const items = await this.read();
|
|
248
|
+
if (items.length === 0) return { flushed: 0, remaining: 0 };
|
|
249
|
+
const remaining = [];
|
|
250
|
+
let flushed = 0;
|
|
251
|
+
for (const item of items) {
|
|
252
|
+
try {
|
|
253
|
+
const res = await submitReport(config, item);
|
|
254
|
+
if (res.success) flushed++;
|
|
255
|
+
else remaining.push(item);
|
|
256
|
+
} catch {
|
|
257
|
+
remaining.push(item);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (remaining.length > 0) await this.write(remaining);
|
|
261
|
+
else await this.storage.removeItem(this.key);
|
|
262
|
+
return { flushed, remaining: remaining.length };
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
function getReportQueue(config) {
|
|
266
|
+
return config.storage ? new ReportQueue(config.storage) : void 0;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/logCapture.ts
|
|
270
|
+
function stringifyArg(arg) {
|
|
271
|
+
if (typeof arg === "string") return arg;
|
|
272
|
+
if (arg instanceof Error) return `${arg.name}: ${arg.message}`;
|
|
273
|
+
try {
|
|
274
|
+
return JSON.stringify(arg);
|
|
275
|
+
} catch {
|
|
276
|
+
return String(arg);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function installLogCapture(options = {}) {
|
|
280
|
+
const levels = options.levels ?? ["warn", "error"];
|
|
281
|
+
const max = options.max ?? 25;
|
|
282
|
+
const captureFetch = options.captureFetch ?? true;
|
|
283
|
+
const breadcrumbs = [];
|
|
284
|
+
let lastFailedApiCall;
|
|
285
|
+
const push = (level, args) => {
|
|
286
|
+
breadcrumbs.push({
|
|
287
|
+
level,
|
|
288
|
+
message: args.map(stringifyArg).join(" "),
|
|
289
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
290
|
+
});
|
|
291
|
+
while (breadcrumbs.length > max) breadcrumbs.shift();
|
|
292
|
+
};
|
|
293
|
+
const g = globalThis;
|
|
294
|
+
const console = g.console;
|
|
295
|
+
const originalConsole = {};
|
|
296
|
+
if (console) {
|
|
297
|
+
for (const level of levels) {
|
|
298
|
+
const orig = console[level];
|
|
299
|
+
if (typeof orig === "function") {
|
|
300
|
+
const fn = orig;
|
|
301
|
+
originalConsole[level] = fn;
|
|
302
|
+
console[level] = (...args) => {
|
|
303
|
+
push(level, args);
|
|
304
|
+
fn.apply(console, args);
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
let originalFetch;
|
|
310
|
+
if (captureFetch && typeof g.fetch === "function") {
|
|
311
|
+
originalFetch = g.fetch.bind(g);
|
|
312
|
+
const orig = originalFetch;
|
|
313
|
+
g.fetch = async (input, init) => {
|
|
314
|
+
const reqLike = input;
|
|
315
|
+
const method = (init?.method ?? reqLike.method ?? "GET").toUpperCase();
|
|
316
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : reqLike.url ?? String(input);
|
|
317
|
+
try {
|
|
318
|
+
const res = await orig(input, init);
|
|
319
|
+
if (!res.ok) {
|
|
320
|
+
lastFailedApiCall = {
|
|
321
|
+
method,
|
|
322
|
+
url,
|
|
323
|
+
status: res.status,
|
|
324
|
+
message: res.statusText,
|
|
325
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
326
|
+
};
|
|
327
|
+
push("error", [`HTTP ${res.status} ${method} ${url}`]);
|
|
328
|
+
}
|
|
329
|
+
return res;
|
|
330
|
+
} catch (e) {
|
|
331
|
+
lastFailedApiCall = {
|
|
332
|
+
method,
|
|
333
|
+
url,
|
|
334
|
+
code: "NETWORK_ERROR",
|
|
335
|
+
message: e instanceof Error ? e.message : String(e),
|
|
336
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
337
|
+
};
|
|
338
|
+
push("error", [`Network error ${method} ${url}`]);
|
|
339
|
+
throw e;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
getBreadcrumbs: () => [...breadcrumbs],
|
|
345
|
+
getLastFailedApiCall: () => lastFailedApiCall,
|
|
346
|
+
collect: () => ({
|
|
347
|
+
breadcrumbs: [...breadcrumbs],
|
|
348
|
+
...lastFailedApiCall ? { lastFailedApiCall } : {}
|
|
349
|
+
}),
|
|
350
|
+
clear: () => {
|
|
351
|
+
breadcrumbs.length = 0;
|
|
352
|
+
lastFailedApiCall = void 0;
|
|
353
|
+
},
|
|
354
|
+
uninstall: () => {
|
|
355
|
+
if (console) {
|
|
356
|
+
for (const level of Object.keys(originalConsole)) {
|
|
357
|
+
console[level] = originalConsole[level];
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (originalFetch) g.fetch = originalFetch;
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/repositories/ClickUpBugReporterRepository.ts
|
|
366
|
+
var CLICKUP_API_BASE = "https://api.clickup.com/api/v2";
|
|
367
|
+
var PRIORITY_MAP = {
|
|
368
|
+
urgent: 1,
|
|
369
|
+
high: 2,
|
|
370
|
+
normal: 3,
|
|
371
|
+
low: 4
|
|
372
|
+
};
|
|
373
|
+
function isReactNative() {
|
|
374
|
+
const nav = globalThis.navigator;
|
|
375
|
+
return nav?.product === "ReactNative";
|
|
376
|
+
}
|
|
377
|
+
function base64ToBytes(base64) {
|
|
378
|
+
if (typeof atob !== "undefined") {
|
|
379
|
+
return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
|
380
|
+
}
|
|
381
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
|
382
|
+
const lookup = new Uint8Array(256);
|
|
383
|
+
for (let i = 0; i < chars.length; i++) lookup[chars.charCodeAt(i)] = i;
|
|
384
|
+
const len = base64.replace(/=+$/, "").length;
|
|
385
|
+
const bytes = new Uint8Array(len * 3 / 4);
|
|
386
|
+
let p = 0;
|
|
387
|
+
for (let i = 0; i < len; i += 4) {
|
|
388
|
+
const n = lookup[base64.charCodeAt(i)] << 18 | lookup[base64.charCodeAt(i + 1)] << 12 | lookup[base64.charCodeAt(i + 2)] << 6 | lookup[base64.charCodeAt(i + 3)];
|
|
389
|
+
bytes[p++] = n >> 16;
|
|
390
|
+
bytes[p++] = n >> 8 & 255;
|
|
391
|
+
bytes[p++] = n & 255;
|
|
392
|
+
}
|
|
393
|
+
return bytes.subarray(0, p);
|
|
394
|
+
}
|
|
395
|
+
var ClickUpBugReporterRepository = class {
|
|
396
|
+
constructor(options) {
|
|
397
|
+
this.options = options;
|
|
398
|
+
}
|
|
399
|
+
async createReport(params) {
|
|
400
|
+
const { apiKey, problemListId, suggestionListId, status } = this.options;
|
|
401
|
+
const listId = params.isReportingProblem !== false ? problemListId : suggestionListId;
|
|
402
|
+
const url = `${CLICKUP_API_BASE}/list/${listId}/task`;
|
|
403
|
+
const body = {
|
|
404
|
+
name: params.title,
|
|
405
|
+
markdown_content: this.buildDescription(params)
|
|
406
|
+
};
|
|
407
|
+
if (params.priority) body.priority = PRIORITY_MAP[params.priority];
|
|
408
|
+
if (status) body.status = status;
|
|
409
|
+
try {
|
|
410
|
+
const response = await fetch(url, {
|
|
411
|
+
method: "POST",
|
|
412
|
+
headers: {
|
|
413
|
+
"Content-Type": "application/json",
|
|
414
|
+
Authorization: apiKey
|
|
415
|
+
},
|
|
416
|
+
body: JSON.stringify(body)
|
|
417
|
+
});
|
|
418
|
+
if (!response.ok) {
|
|
419
|
+
const text = await response.text();
|
|
420
|
+
return {
|
|
421
|
+
success: false,
|
|
422
|
+
message: text || `ClickUp task creation failed: ${response.status}`
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
const data = await response.json();
|
|
426
|
+
const taskId = data?.id;
|
|
427
|
+
if (taskId && params.tags && params.tags.length > 0) {
|
|
428
|
+
await this.attachTags(params.tags, taskId);
|
|
429
|
+
}
|
|
430
|
+
if (taskId && params.screenshotBase64) {
|
|
431
|
+
await this.uploadAttachment(params.screenshotBase64, taskId);
|
|
432
|
+
}
|
|
433
|
+
return { success: true, id: taskId, message: "Submitted" };
|
|
434
|
+
} catch (e) {
|
|
435
|
+
const message = e instanceof Error ? e.message : "ClickUp error";
|
|
436
|
+
return { success: false, message };
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
buildDescription(params) {
|
|
440
|
+
const { description, deviceInfo, packageInfo, userInfo, diagnostics } = params;
|
|
441
|
+
const s = (v) => v === void 0 || v === null ? "\u2014" : String(v);
|
|
442
|
+
const lines = (pairs) => pairs.filter(([, v]) => v !== void 0 && v !== null && String(v).trim() !== "" && String(v) !== "\u2014").map(([k, v]) => `- ${k}: ${String(v)}`).join("\n");
|
|
443
|
+
let text = `${description}`;
|
|
444
|
+
if (params.type || params.severity) {
|
|
445
|
+
const bits = [];
|
|
446
|
+
if (params.type) bits.push(`Type: ${s(params.type)}`);
|
|
447
|
+
if (params.severity) bits.push(`Severity: ${s(params.severity)}`);
|
|
448
|
+
text += `
|
|
449
|
+
|
|
450
|
+
${bits.join(" \xB7 ")}`;
|
|
451
|
+
}
|
|
452
|
+
const contextPairs = params.context && params.context.length > 0 ? params.context.map((c) => [c.label, c.value]) : [
|
|
453
|
+
["App", packageInfo?.appName],
|
|
454
|
+
["Package", packageInfo?.packageName],
|
|
455
|
+
["Version", packageInfo?.appVersion ?? packageInfo?.versionName],
|
|
456
|
+
["Build", packageInfo?.buildNumber],
|
|
457
|
+
["OS version", deviceInfo?.deviceVersion ?? deviceInfo?.osVersion],
|
|
458
|
+
["Model", deviceInfo?.model],
|
|
459
|
+
["Platform", deviceInfo?.platform]
|
|
460
|
+
];
|
|
461
|
+
const contextLines = lines(contextPairs);
|
|
462
|
+
if (contextLines) {
|
|
463
|
+
text += `
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
**Context**
|
|
468
|
+
${contextLines}`;
|
|
469
|
+
}
|
|
470
|
+
if (userInfo && Object.keys(userInfo).length > 0) {
|
|
471
|
+
const userLines = lines([
|
|
472
|
+
["Id", userInfo?.id],
|
|
473
|
+
["Name", userInfo?.fullName ?? userInfo?.name],
|
|
474
|
+
["Email", userInfo?.email]
|
|
475
|
+
]);
|
|
476
|
+
if (userLines) text += `
|
|
477
|
+
|
|
478
|
+
**User**
|
|
479
|
+
${userLines}`;
|
|
480
|
+
}
|
|
481
|
+
const lastFailedApiCall = diagnostics?.lastFailedApiCall;
|
|
482
|
+
if (lastFailedApiCall) {
|
|
483
|
+
text += `
|
|
484
|
+
|
|
485
|
+
**Last Failed API Call**
|
|
486
|
+
- Method: ${s(lastFailedApiCall.method)}
|
|
487
|
+
- URL: ${s(lastFailedApiCall.url)}
|
|
488
|
+
- Status: ${s(lastFailedApiCall.status)}
|
|
489
|
+
- Code: ${s(lastFailedApiCall.code)}
|
|
490
|
+
- Message: ${s(lastFailedApiCall.message)}
|
|
491
|
+
- Time: ${s(lastFailedApiCall.timestamp)}`;
|
|
492
|
+
}
|
|
493
|
+
const breadcrumbs = diagnostics?.breadcrumbs;
|
|
494
|
+
if (Array.isArray(breadcrumbs) && breadcrumbs.length > 0) {
|
|
495
|
+
const lines2 = breadcrumbs.map((b) => `[${s(b.timestamp)}] ${s(b.level).toUpperCase()} ${s(b.message)}`).join("\n");
|
|
496
|
+
text += `
|
|
497
|
+
|
|
498
|
+
**Recent Logs**
|
|
499
|
+
\`\`\`
|
|
500
|
+
${lines2}
|
|
501
|
+
\`\`\``;
|
|
502
|
+
}
|
|
503
|
+
return text;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Attach tags to an already-created task. ClickUp rejects unknown tag names on
|
|
507
|
+
* the create-task call (failing the whole report), so we attach afterwards and
|
|
508
|
+
* swallow failures — a missing/uncreatable tag never costs the user their report.
|
|
509
|
+
* Tags must already exist in the Space to actually attach.
|
|
510
|
+
*/
|
|
511
|
+
async attachTags(tags, taskId) {
|
|
512
|
+
await Promise.all(
|
|
513
|
+
tags.map(async (tag) => {
|
|
514
|
+
try {
|
|
515
|
+
await fetch(`${CLICKUP_API_BASE}/task/${taskId}/tag/${encodeURIComponent(tag)}`, {
|
|
516
|
+
method: "POST",
|
|
517
|
+
headers: { Authorization: this.options.apiKey }
|
|
518
|
+
});
|
|
519
|
+
} catch {
|
|
520
|
+
}
|
|
521
|
+
})
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
async uploadAttachment(screenshotBase64, taskId) {
|
|
525
|
+
const url = `${CLICKUP_API_BASE}/task/${taskId}/attachment`;
|
|
526
|
+
const filename = `screenshot_${Date.now()}.png`;
|
|
527
|
+
try {
|
|
528
|
+
const formData = new FormData();
|
|
529
|
+
if (isReactNative()) {
|
|
530
|
+
formData.append("attachment", {
|
|
531
|
+
uri: `data:image/png;base64,${screenshotBase64}`,
|
|
532
|
+
name: filename,
|
|
533
|
+
type: "image/png"
|
|
534
|
+
});
|
|
535
|
+
} else {
|
|
536
|
+
const binary = base64ToBytes(screenshotBase64);
|
|
537
|
+
const buf = new ArrayBuffer(binary.byteLength);
|
|
538
|
+
new Uint8Array(buf).set(binary);
|
|
539
|
+
const blob = new Blob([buf], { type: "image/png" });
|
|
540
|
+
formData.append("attachment", blob, filename);
|
|
541
|
+
}
|
|
542
|
+
await fetch(url, {
|
|
543
|
+
method: "POST",
|
|
544
|
+
headers: { Authorization: this.options.apiKey },
|
|
545
|
+
body: formData
|
|
546
|
+
});
|
|
547
|
+
} catch {
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
export { ANNOTATION_COLORS, ANNOTATION_TOOLS, ClickUpBugReporterRepository, REPORT_TYPES, ReportQueue, SEVERITIES, THEMES, buildCreateReportParams, getReportQueue, installLogCapture, normalizeContext, resolveTheme, severityToPriority, submitBugReport, submitReport };
|
|
553
|
+
//# sourceMappingURL=index.mjs.map
|
|
554
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/theme.ts","../src/reportMeta.ts","../src/api.ts","../src/queue.ts","../src/logCapture.ts","../src/repositories/ClickUpBugReporterRepository.ts"],"names":["lines"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Theme tokens for the OC Bug Reporter UI, ported from the OC-BugReporter design.\n * Platform-agnostic colour tokens; web applies them as CSS variables, native\n * reads them directly. Three presets (indigo default, noir, mint); callers may\n * also pass a partial override object.\n */\n\nexport interface BugReporterTheme {\n canvas: string;\n surface: string;\n panel: string;\n border: string;\n borderStrong: string;\n text: string;\n muted: string;\n faint: string;\n accent: string;\n accentPress: string;\n onAccent: string;\n /** Translucent accent tint (selection / focus ring backgrounds). */\n ring: string;\n ok: string;\n okBg: string;\n /** CSS box-shadow string (web). Native composes its own elevation. */\n shadow: string;\n /** Whether this is a dark theme (affects native device frame / status bar). */\n dark: boolean;\n}\n\nexport type BugReporterThemeName = 'indigo' | 'noir' | 'mint';\n\nexport const THEMES: Record<BugReporterThemeName, BugReporterTheme> = {\n indigo: {\n canvas: '#EBECF0',\n surface: '#ffffff',\n panel: '#ffffff',\n border: '#E7E8EC',\n borderStrong: '#D3D5DB',\n text: '#15161A',\n muted: '#6A6C75',\n faint: '#A6A8B2',\n accent: '#5E6AD2',\n accentPress: '#4F58BE',\n onAccent: '#ffffff',\n ring: 'rgba(94,106,210,0.14)',\n ok: '#2E9E6B',\n okBg: 'rgba(46,158,107,0.10)',\n shadow: '0 24px 60px rgba(20,22,40,0.18)',\n dark: false,\n },\n noir: {\n canvas: '#08090B',\n surface: '#161719',\n panel: '#161719',\n border: '#26282C',\n borderStrong: '#34373C',\n text: '#F3F4F6',\n muted: '#9B9EA6',\n faint: '#6C6F77',\n accent: '#6E6AF0',\n accentPress: '#5B57E0',\n onAccent: '#ffffff',\n ring: 'rgba(110,106,240,0.20)',\n ok: '#3FD08A',\n okBg: 'rgba(63,208,138,0.12)',\n shadow: '0 24px 70px rgba(0,0,0,0.6)',\n dark: true,\n },\n mint: {\n canvas: '#E8EFEC',\n surface: '#ffffff',\n panel: '#ffffff',\n border: '#DCE6E1',\n borderStrong: '#C5D3CC',\n text: '#0E1A15',\n muted: '#5C6B64',\n faint: '#90A099',\n accent: '#0E9D78',\n accentPress: '#0B8266',\n onAccent: '#ffffff',\n ring: 'rgba(14,157,120,0.14)',\n ok: '#0E9D78',\n okBg: 'rgba(14,157,120,0.10)',\n shadow: '0 24px 60px rgba(10,40,30,0.16)',\n dark: false,\n },\n};\n\nexport type ThemeInput = BugReporterThemeName | Partial<BugReporterTheme>;\n\n/** Resolve a theme name or partial override into a full token set (indigo base). */\nexport function resolveTheme(theme?: ThemeInput): BugReporterTheme {\n if (!theme) return THEMES.indigo;\n if (typeof theme === 'string') return THEMES[theme] ?? THEMES.indigo;\n return { ...THEMES.indigo, ...theme };\n}\n","/**\n * Report taxonomy + annotation model shared by web and native, ported from the\n * OC-BugReporter design. Severity maps to backend priority; type becomes a tag.\n */\n\nimport type { ReportContextRow, ReportPriority, ReportSeverity, ReportType } from './types';\n\nexport type { ReportSeverity, ReportType, ReportContextRow } from './types';\n\n/** Normalize a collectContext() result (array or `{label: value}` object) into rows. */\nexport function normalizeContext(\n input?: ReportContextRow[] | Record<string, string> | null\n): ReportContextRow[] {\n if (!input) return [];\n if (Array.isArray(input)) {\n return input.filter(r => r && r.label != null && String(r.value ?? '').trim() !== '');\n }\n return Object.entries(input)\n .filter(([, v]) => v != null && String(v).trim() !== '')\n .map(([label, value]) => ({ label, value: String(value) }));\n}\n\nexport const SEVERITIES: { id: ReportSeverity; label: string; color: string }[] = [\n { id: 'low', label: 'Low', color: '#3E63DD' },\n { id: 'medium', label: 'Medium', color: '#F5A524' },\n { id: 'high', label: 'High', color: '#F76B15' },\n { id: 'critical', label: 'Critical', color: '#E5484D' },\n];\n\nexport const REPORT_TYPES: { id: ReportType; label: string }[] = [\n { id: 'bug', label: 'Bug' },\n { id: 'crash', label: 'Crash' },\n { id: 'ui', label: 'UI / Visual' },\n { id: 'perf', label: 'Performance' },\n { id: 'other', label: 'Other' },\n];\n\n/** Annotation palette (matches the design's 8-swatch toolbar). */\nexport const ANNOTATION_COLORS = [\n '#E5484D',\n '#F76B15',\n '#F5A524',\n '#2E9E6B',\n '#3E63DD',\n '#8E4EC6',\n '#15161A',\n '#FFFFFF',\n];\n\nexport type AnnotationTool = 'pen' | 'arrow' | 'rect' | 'blur' | 'text';\n\n/** Toolbar tools with their SVG icon paths (rendered identically on web + native). */\nexport const ANNOTATION_TOOLS: { id: AnnotationTool; label: string; iconPath: string }[] = [\n { id: 'pen', label: 'Draw', iconPath: 'M4 20l3.6-1L18 8.6 15.4 6 5 16.4 4 20z' },\n { id: 'arrow', label: 'Arrow', iconPath: 'M6 18L18 6M18 6h-7M18 6v7' },\n { id: 'rect', label: 'Box', iconPath: 'M4.5 6h15v12h-15z' },\n { id: 'blur', label: 'Blur', iconPath: 'M12 3C8.6 8.4 6.5 11 6.5 14a5.5 5.5 0 0011 0c0-3-2.1-5.6-5.5-11z' },\n { id: 'text', label: 'Text', iconPath: 'M6 6h12M12 6v12M9.5 18h5' },\n];\n\n/** Map design severity to backend (ClickUp) priority. */\nexport function severityToPriority(severity?: ReportSeverity): ReportPriority | undefined {\n switch (severity) {\n case 'critical':\n return 'urgent';\n case 'high':\n return 'high';\n case 'medium':\n return 'normal';\n case 'low':\n return 'low';\n default:\n return undefined;\n }\n}\n\n// ---- Shared annotation shape model ----\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport interface PenAnnotation {\n id: string;\n type: 'pen';\n color: string;\n points: Point[];\n}\n\nexport interface BoxAnnotation {\n id: string;\n type: 'arrow' | 'rect' | 'blur';\n color: string;\n x0: number;\n y0: number;\n x1: number;\n y1: number;\n}\n\nexport interface TextAnnotation {\n id: string;\n type: 'text';\n color: string;\n x: number;\n y: number;\n text: string;\n}\n\nexport type Annotation = PenAnnotation | BoxAnnotation | TextAnnotation;\n","/**\n * API client for submitting bug reports.\n * Platform-agnostic (no React/React Native).\n */\n\nimport { severityToPriority } from './reportMeta';\n\nimport type {\n BugReporterConfig,\n BugReportRequest,\n BugReportResponse,\n CreateReportParams,\n ReportPriority,\n ReportSeverity,\n ReportType,\n} from './types';\n\nexport async function submitBugReport(\n request: BugReportRequest,\n config: BugReporterConfig\n): Promise<BugReportResponse> {\n const apiUrl = config.apiUrl;\n if (!apiUrl) {\n return { success: false, message: 'apiUrl or repository is required' };\n }\n const body = {\n title: request.title,\n description: request.description,\n screenshot_base64: request.screenshotBase64,\n steps_to_reproduce: request.stepsToReproduce,\n metadata: {\n app_name: config.appName,\n app_version: config.appVersion,\n ...request.metadata,\n },\n };\n\n const response = await fetch(apiUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...config.headers,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const text = await response.text();\n return {\n success: false,\n message: text || `HTTP ${response.status}`,\n };\n }\n\n try {\n const data = (await response.json()) as Partial<BugReportResponse>;\n return { success: true, message: 'Submitted', ...data };\n } catch {\n return { success: true, message: 'Submitted' };\n }\n}\n\n/** Options accepted by {@link submitReport}; also the unit persisted by the offline queue. */\nexport interface SubmitReportOptions {\n title: string;\n description: string;\n screenshotBase64?: string;\n isReportingProblem?: boolean;\n priority?: ReportPriority;\n severity?: ReportSeverity;\n type?: ReportType;\n tags?: string[];\n /** Auto-captured context rows to include in the report body. */\n context?: { label: string; value: string }[];\n metadata?: Record<string, string | undefined>;\n}\n\n/**\n * Build the repository's CreateReportParams from config + submit options.\n * Diagnostics are collected fresh here (via config.collectDiagnostics) so a\n * replayed/queued report reflects the moment it is actually sent.\n */\nexport function buildCreateReportParams(\n config: BugReporterConfig,\n options: SubmitReportOptions\n): CreateReportParams {\n return {\n title: options.title,\n description: options.description,\n screenshotBase64: options.screenshotBase64,\n isReportingProblem: options.isReportingProblem ?? true,\n priority: options.priority ?? severityToPriority(options.severity) ?? config.defaultPriority,\n severity: options.severity,\n type: options.type,\n tags: options.tags ?? (options.type ? [options.type] : undefined),\n context: options.context,\n diagnostics: config.collectDiagnostics?.(),\n deviceInfo: {\n platform: options.metadata?.platform ?? 'web',\n userAgent: options.metadata?.userAgent,\n ...config.deviceInfo,\n ...options.metadata,\n },\n packageInfo: {\n appName: config.appName,\n appVersion: config.appVersion,\n ...config.packageInfo,\n },\n userInfo: undefined,\n };\n}\n\n/**\n * Submit a report using config.repository if set, otherwise default HTTP (apiUrl).\n * Builds CreateReportParams from title, description, screenshot, and config/metadata.\n */\nexport async function submitReport(\n config: BugReporterConfig,\n options: SubmitReportOptions\n): Promise<BugReportResponse> {\n if (config.repository) {\n return config.repository.createReport(buildCreateReportParams(config, options));\n }\n return submitBugReport(\n {\n title: options.title,\n description: options.description,\n screenshotBase64: options.screenshotBase64,\n metadata: {\n priority: options.priority ?? severityToPriority(options.severity) ?? config.defaultPriority,\n severity: options.severity,\n type: options.type,\n ...options.metadata,\n },\n },\n config\n );\n}\n","/**\n * Offline retry queue for bug reports.\n * When a submit fails (offline, server error), the report is persisted and\n * replayed later via the current config. Platform-agnostic: works with any\n * ReportQueueStorage (web localStorage, RN AsyncStorage).\n */\n\nimport { submitReport, type SubmitReportOptions } from './api';\n\nimport type { BugReporterConfig, ReportQueueStorage } from './types';\n\nconst DEFAULT_KEY = 'outcode_bug_reporter_queue';\n/** Cap stored entries so a long offline streak can't grow storage unbounded. */\nconst MAX_ENTRIES = 10;\n\nexport class ReportQueue {\n constructor(\n private readonly storage: ReportQueueStorage,\n private readonly key: string = DEFAULT_KEY\n ) {}\n\n private async read(): Promise<SubmitReportOptions[]> {\n try {\n const raw = await this.storage.getItem(this.key);\n if (!raw) return [];\n const parsed: unknown = JSON.parse(raw);\n return Array.isArray(parsed) ? (parsed as SubmitReportOptions[]) : [];\n } catch {\n return [];\n }\n }\n\n private async write(items: SubmitReportOptions[]): Promise<void> {\n try {\n await this.storage.setItem(this.key, JSON.stringify(items));\n } catch {\n // Likely a storage quota error from large screenshots (e.g. localStorage\n // ~5MB). Retry without the screenshot payloads rather than losing the text.\n try {\n const slim = items.map(({ screenshotBase64: _omit, ...rest }) => rest);\n await this.storage.setItem(this.key, JSON.stringify(slim));\n } catch {\n // Give up silently — never let queueing throw into the submit flow.\n }\n }\n }\n\n /** Number of reports currently waiting to be retried. */\n async size(): Promise<number> {\n return (await this.read()).length;\n }\n\n /** Persist a report that failed to send. Oldest entries are dropped past the cap. */\n async enqueue(options: SubmitReportOptions): Promise<void> {\n const items = await this.read();\n items.push(options);\n while (items.length > MAX_ENTRIES) items.shift();\n await this.write(items);\n }\n\n /**\n * Replay all queued reports using the current config. Reports that submit\n * successfully are removed; failures stay queued for the next attempt.\n */\n async flush(config: BugReporterConfig): Promise<{ flushed: number; remaining: number }> {\n const items = await this.read();\n if (items.length === 0) return { flushed: 0, remaining: 0 };\n\n const remaining: SubmitReportOptions[] = [];\n let flushed = 0;\n for (const item of items) {\n try {\n const res = await submitReport(config, item);\n if (res.success) flushed++;\n else remaining.push(item);\n } catch {\n remaining.push(item);\n }\n }\n\n if (remaining.length > 0) await this.write(remaining);\n else await this.storage.removeItem(this.key);\n return { flushed, remaining: remaining.length };\n }\n}\n\n/** Build a ReportQueue from config.storage, or undefined when no storage is configured. */\nexport function getReportQueue(config: BugReporterConfig): ReportQueue | undefined {\n return config.storage ? new ReportQueue(config.storage) : undefined;\n}\n","/**\n * Optional automatic diagnostics capture.\n *\n * `installLogCapture()` patches `console` (and optionally `fetch`) to keep a\n * rolling ring buffer of recent log lines plus the last failed network call.\n * Wire its `collect()` into `BugReporterConfig.collectDiagnostics` so every\n * report carries recent context automatically — turning \"it's broken\" tickets\n * into actionable ones.\n *\n * Platform-agnostic: `console` and `fetch` exist on web and React Native.\n */\n\nexport type LogLevel = 'log' | 'info' | 'warn' | 'error';\n\nexport interface Breadcrumb {\n level: LogLevel;\n message: string;\n /** ISO timestamp. */\n timestamp: string;\n}\n\nexport interface LastFailedApiCall {\n method?: string;\n url?: string;\n status?: number;\n code?: string;\n message?: string;\n timestamp?: string;\n}\n\nexport interface LogCaptureOptions {\n /** Console levels to record. Default ['warn', 'error']. */\n levels?: LogLevel[];\n /** Max breadcrumbs retained (ring buffer). Default 25. */\n max?: number;\n /** Also wrap global fetch to record failed requests. Default true. */\n captureFetch?: boolean;\n}\n\nexport interface LogCaptureHandle {\n getBreadcrumbs(): Breadcrumb[];\n getLastFailedApiCall(): LastFailedApiCall | undefined;\n /** Snapshot suitable to spread into diagnostics (`{ breadcrumbs, lastFailedApiCall }`). */\n collect(): Record<string, unknown>;\n clear(): void;\n /** Restore the original console/fetch. */\n uninstall(): void;\n}\n\nfunction stringifyArg(arg: unknown): string {\n if (typeof arg === 'string') return arg;\n if (arg instanceof Error) return `${arg.name}: ${arg.message}`;\n try {\n return JSON.stringify(arg);\n } catch {\n return String(arg);\n }\n}\n\ntype GlobalWithIO = {\n console?: Record<string, unknown>;\n fetch?: typeof fetch;\n};\n\n/**\n * Install console/fetch capture. Call once near app startup. The returned\n * handle exposes the collected data and an `uninstall()` to undo the patches.\n */\nexport function installLogCapture(options: LogCaptureOptions = {}): LogCaptureHandle {\n const levels = options.levels ?? ['warn', 'error'];\n const max = options.max ?? 25;\n const captureFetch = options.captureFetch ?? true;\n\n const breadcrumbs: Breadcrumb[] = [];\n let lastFailedApiCall: LastFailedApiCall | undefined;\n\n const push = (level: LogLevel, args: unknown[]): void => {\n breadcrumbs.push({\n level,\n message: args.map(stringifyArg).join(' '),\n timestamp: new Date().toISOString(),\n });\n while (breadcrumbs.length > max) breadcrumbs.shift();\n };\n\n const g = globalThis as unknown as GlobalWithIO;\n const console = g.console;\n const originalConsole: Partial<Record<LogLevel, (...a: unknown[]) => void>> = {};\n\n if (console) {\n for (const level of levels) {\n const orig = console[level];\n if (typeof orig === 'function') {\n const fn = orig as (...a: unknown[]) => void;\n originalConsole[level] = fn;\n console[level] = (...args: unknown[]) => {\n push(level, args);\n fn.apply(console, args);\n };\n }\n }\n }\n\n let originalFetch: typeof fetch | undefined;\n if (captureFetch && typeof g.fetch === 'function') {\n originalFetch = g.fetch.bind(g) as typeof fetch;\n const orig = originalFetch;\n type FetchInput = Parameters<typeof fetch>[0];\n type FetchInit = Parameters<typeof fetch>[1];\n g.fetch = async (input: FetchInput, init?: FetchInit): Promise<Response> => {\n const reqLike = input as { url?: string; method?: string };\n const method = (init?.method ?? reqLike.method ?? 'GET').toUpperCase();\n const url =\n typeof input === 'string'\n ? input\n : input instanceof URL\n ? input.toString()\n : (reqLike.url ?? String(input));\n try {\n const res = await orig(input, init);\n if (!res.ok) {\n lastFailedApiCall = {\n method,\n url,\n status: res.status,\n message: res.statusText,\n timestamp: new Date().toISOString(),\n };\n push('error', [`HTTP ${res.status} ${method} ${url}`]);\n }\n return res;\n } catch (e) {\n lastFailedApiCall = {\n method,\n url,\n code: 'NETWORK_ERROR',\n message: e instanceof Error ? e.message : String(e),\n timestamp: new Date().toISOString(),\n };\n push('error', [`Network error ${method} ${url}`]);\n throw e;\n }\n };\n }\n\n return {\n getBreadcrumbs: () => [...breadcrumbs],\n getLastFailedApiCall: () => lastFailedApiCall,\n collect: () => ({\n breadcrumbs: [...breadcrumbs],\n ...(lastFailedApiCall ? { lastFailedApiCall } : {}),\n }),\n clear: () => {\n breadcrumbs.length = 0;\n lastFailedApiCall = undefined;\n },\n uninstall: () => {\n if (console) {\n for (const level of Object.keys(originalConsole) as LogLevel[]) {\n console[level] = originalConsole[level] as (...a: unknown[]) => void;\n }\n }\n if (originalFetch) g.fetch = originalFetch;\n },\n };\n}\n","/**\n * ClickUp implementation of the bug reporter repository.\n * Creates bug/suggestion reports as tasks in ClickUp and uploads screenshot as attachment.\n * Aligned with Flutter ClickUpBugReporterRepository.\n */\n\nimport type {\n BugReportResponse,\n CreateReportParams,\n IBugReporterRepository,\n ReportPriority,\n} from '../types';\n\nexport interface ClickUpBugReporterRepositoryOptions {\n /** ClickUp API key (used in Authorization header) */\n apiKey: string;\n /** ClickUp list ID for problem/bug reports */\n problemListId: string;\n /** ClickUp list ID for suggestion reports */\n suggestionListId: string;\n /**\n * Optional initial status to set on created tasks (e.g. 'triage'). Must be a\n * valid status on the target list, or ClickUp rejects the task. Omit to use\n * the list's default status.\n */\n status?: string;\n}\n\nconst CLICKUP_API_BASE = 'https://api.clickup.com/api/v2';\n\n/** ClickUp priority is numeric: 1=urgent, 2=high, 3=normal, 4=low. */\nconst PRIORITY_MAP: Record<ReportPriority, number> = {\n urgent: 1,\n high: 2,\n normal: 3,\n low: 4,\n};\n\n/** True when running under React Native (Blob-from-ArrayBuffer is unsupported there). */\nfunction isReactNative(): boolean {\n const nav = (globalThis as { navigator?: { product?: string } }).navigator;\n return nav?.product === 'ReactNative';\n}\n\n/** Base64 decode that works in browser (atob) and React Native (no atob). */\nfunction base64ToBytes(base64: string): Uint8Array {\n if (typeof atob !== 'undefined') {\n return Uint8Array.from(atob(base64), c => c.charCodeAt(0));\n }\n const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';\n const lookup = new Uint8Array(256);\n for (let i = 0; i < chars.length; i++) lookup[chars.charCodeAt(i)] = i;\n const len = base64.replace(/=+$/, '').length;\n const bytes = new Uint8Array((len * 3) / 4);\n let p = 0;\n for (let i = 0; i < len; i += 4) {\n const n =\n (lookup[base64.charCodeAt(i)] << 18) |\n (lookup[base64.charCodeAt(i + 1)] << 12) |\n (lookup[base64.charCodeAt(i + 2)] << 6) |\n lookup[base64.charCodeAt(i + 3)];\n bytes[p++] = n >> 16;\n bytes[p++] = (n >> 8) & 0xff;\n bytes[p++] = n & 0xff;\n }\n return bytes.subarray(0, p);\n}\n\n/**\n * ClickUp bug reporter repository.\n * Use when your project uses ClickUp for bug/suggestion tracking.\n */\nexport class ClickUpBugReporterRepository implements IBugReporterRepository {\n constructor(private readonly options: ClickUpBugReporterRepositoryOptions) {}\n\n async createReport(params: CreateReportParams): Promise<BugReportResponse> {\n const { apiKey, problemListId, suggestionListId, status } = this.options;\n const listId = params.isReportingProblem !== false ? problemListId : suggestionListId;\n const url = `${CLICKUP_API_BASE}/list/${listId}/task`;\n\n // NOTE: Do not send `tags` here. ClickUp's create-task API rejects tag names\n // that don't already exist in the Space (400 \"Tag(s) not found\"), which would\n // fail the whole report. The platform is already captured in the description\n // via buildDescription (\"Device Platform: …\").\n // `markdown_content` renders the metadata block as formatted markdown.\n const body: Record<string, unknown> = {\n name: params.title,\n markdown_content: this.buildDescription(params),\n };\n if (params.priority) body.priority = PRIORITY_MAP[params.priority];\n if (status) body.status = status;\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: apiKey,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const text = await response.text();\n return {\n success: false,\n message: text || `ClickUp task creation failed: ${response.status}`,\n };\n }\n\n const data = (await response.json()) as { id?: string };\n const taskId = data?.id;\n if (taskId && params.tags && params.tags.length > 0) {\n await this.attachTags(params.tags, taskId);\n }\n if (taskId && params.screenshotBase64) {\n await this.uploadAttachment(params.screenshotBase64, taskId);\n }\n return { success: true, id: taskId, message: 'Submitted' };\n } catch (e) {\n const message = e instanceof Error ? e.message : 'ClickUp error';\n return { success: false, message };\n }\n }\n\n private buildDescription(params: CreateReportParams): string {\n const { description, deviceInfo, packageInfo, userInfo, diagnostics } = params;\n const s = (v: unknown): string => (v === undefined || v === null ? '—' : String(v));\n /** Build \"- Label: value\" lines, skipping blank/missing values. */\n const lines = (pairs: [string, unknown][]): string =>\n pairs\n .filter(([, v]) => v !== undefined && v !== null && String(v).trim() !== '' && String(v) !== '—')\n .map(([k, v]) => `- ${k}: ${String(v)}`)\n .join('\\n');\n\n let text = `${description}`;\n\n if (params.type || params.severity) {\n const bits: string[] = [];\n if (params.type) bits.push(`Type: ${s(params.type)}`);\n if (params.severity) bits.push(`Severity: ${s(params.severity)}`);\n text += `\\n\\n${bits.join(' · ')}`;\n }\n\n // Merged context: prefer the form's curated auto-captured rows; otherwise\n // fall back to raw app/device info. Missing fields are omitted (no \"—\" noise).\n const contextPairs: [string, unknown][] =\n params.context && params.context.length > 0\n ? params.context.map(c => [c.label, c.value] as [string, unknown])\n : [\n ['App', packageInfo?.appName],\n ['Package', packageInfo?.packageName],\n ['Version', packageInfo?.appVersion ?? packageInfo?.versionName],\n ['Build', packageInfo?.buildNumber],\n ['OS version', deviceInfo?.deviceVersion ?? deviceInfo?.osVersion],\n ['Model', deviceInfo?.model],\n ['Platform', deviceInfo?.platform],\n ];\n const contextLines = lines(contextPairs);\n if (contextLines) {\n text += `\\n\\n---\\n\\n**Context**\\n${contextLines}`;\n }\n\n if (userInfo && Object.keys(userInfo).length > 0) {\n const userLines = lines([\n ['Id', userInfo?.id],\n ['Name', userInfo?.fullName ?? userInfo?.name],\n ['Email', userInfo?.email],\n ]);\n if (userLines) text += `\\n\\n**User**\\n${userLines}`;\n }\n\n const lastFailedApiCall = diagnostics?.lastFailedApiCall as\n | {\n method?: string;\n url?: string;\n status?: number;\n code?: string;\n message?: string;\n timestamp?: string;\n }\n | undefined;\n if (lastFailedApiCall) {\n text += `\n\n**Last Failed API Call**\n- Method: ${s(lastFailedApiCall.method)}\n- URL: ${s(lastFailedApiCall.url)}\n- Status: ${s(lastFailedApiCall.status)}\n- Code: ${s(lastFailedApiCall.code)}\n- Message: ${s(lastFailedApiCall.message)}\n- Time: ${s(lastFailedApiCall.timestamp)}`;\n }\n\n const breadcrumbs = diagnostics?.breadcrumbs as\n | Array<{ level?: string; message?: string; timestamp?: string }>\n | undefined;\n if (Array.isArray(breadcrumbs) && breadcrumbs.length > 0) {\n const lines = breadcrumbs\n .map(b => `[${s(b.timestamp)}] ${s(b.level).toUpperCase()} ${s(b.message)}`)\n .join('\\n');\n text += `\n\n**Recent Logs**\n\\`\\`\\`\n${lines}\n\\`\\`\\``;\n }\n return text;\n }\n\n /**\n * Attach tags to an already-created task. ClickUp rejects unknown tag names on\n * the create-task call (failing the whole report), so we attach afterwards and\n * swallow failures — a missing/uncreatable tag never costs the user their report.\n * Tags must already exist in the Space to actually attach.\n */\n private async attachTags(tags: string[], taskId: string): Promise<void> {\n await Promise.all(\n tags.map(async tag => {\n try {\n await fetch(`${CLICKUP_API_BASE}/task/${taskId}/tag/${encodeURIComponent(tag)}`, {\n method: 'POST',\n headers: { Authorization: this.options.apiKey },\n });\n } catch {\n // ignore — tag attachment is best-effort\n }\n })\n );\n }\n\n private async uploadAttachment(screenshotBase64: string, taskId: string): Promise<void> {\n const url = `${CLICKUP_API_BASE}/task/${taskId}/attachment`;\n const filename = `screenshot_${Date.now()}.png`;\n try {\n const formData = new FormData();\n\n if (isReactNative()) {\n // React Native's Blob can't be built from an ArrayBuffer (non-Blob parts\n // are coerced to the string \"[object ArrayBuffer]\"), so a Blob upload would\n // attach garbage. RN's networking instead resolves a `data:` URI in the\n // multipart part, so hand it the data URI directly.\n formData.append('attachment', {\n uri: `data:image/png;base64,${screenshotBase64}`,\n name: filename,\n type: 'image/png',\n } as unknown as Blob);\n } else {\n // Web/Node: build a real Blob from the decoded bytes.\n const binary = base64ToBytes(screenshotBase64);\n const buf = new ArrayBuffer(binary.byteLength);\n new Uint8Array(buf).set(binary);\n const blob = new Blob([buf], { type: 'image/png' });\n formData.append('attachment', blob, filename);\n }\n\n await fetch(url, {\n method: 'POST',\n headers: { Authorization: this.options.apiKey },\n body: formData,\n });\n } catch {\n // Don't fail the report if attachment upload fails (same as Flutter)\n }\n }\n}\n"]}
|