@bbearai/core 0.4.5 → 0.5.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/dist/index.d.mts +154 -6
- package/dist/index.d.ts +154 -6
- package/dist/index.js +358 -24
- package/dist/index.mjs +354 -23
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,10 +22,13 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
BUG_CATEGORIES: () => BUG_CATEGORIES,
|
|
24
24
|
BugBearClient: () => BugBearClient,
|
|
25
|
+
LocalStorageAdapter: () => LocalStorageAdapter,
|
|
26
|
+
OfflineQueue: () => OfflineQueue,
|
|
25
27
|
captureError: () => captureError,
|
|
26
28
|
contextCapture: () => contextCapture,
|
|
27
29
|
createBugBear: () => createBugBear,
|
|
28
|
-
isBugCategory: () => isBugCategory
|
|
30
|
+
isBugCategory: () => isBugCategory,
|
|
31
|
+
isNetworkError: () => isNetworkError
|
|
29
32
|
});
|
|
30
33
|
module.exports = __toCommonJS(index_exports);
|
|
31
34
|
|
|
@@ -280,32 +283,312 @@ function captureError(error, errorInfo) {
|
|
|
280
283
|
};
|
|
281
284
|
}
|
|
282
285
|
|
|
286
|
+
// src/offline-queue.ts
|
|
287
|
+
var LocalStorageAdapter = class {
|
|
288
|
+
constructor() {
|
|
289
|
+
this.fallback = /* @__PURE__ */ new Map();
|
|
290
|
+
}
|
|
291
|
+
get isAvailable() {
|
|
292
|
+
try {
|
|
293
|
+
const key = "__bugbear_test__";
|
|
294
|
+
localStorage.setItem(key, "1");
|
|
295
|
+
localStorage.removeItem(key);
|
|
296
|
+
return true;
|
|
297
|
+
} catch {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async getItem(key) {
|
|
302
|
+
if (this.isAvailable) return localStorage.getItem(key);
|
|
303
|
+
return this.fallback.get(key) ?? null;
|
|
304
|
+
}
|
|
305
|
+
async setItem(key, value) {
|
|
306
|
+
if (this.isAvailable) {
|
|
307
|
+
localStorage.setItem(key, value);
|
|
308
|
+
} else {
|
|
309
|
+
this.fallback.set(key, value);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
async removeItem(key) {
|
|
313
|
+
if (this.isAvailable) {
|
|
314
|
+
localStorage.removeItem(key);
|
|
315
|
+
} else {
|
|
316
|
+
this.fallback.delete(key);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
var OfflineQueue = class {
|
|
321
|
+
constructor(config) {
|
|
322
|
+
this.items = [];
|
|
323
|
+
this.storageKey = "bugbear_offline_queue";
|
|
324
|
+
this.flushing = false;
|
|
325
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
326
|
+
this.maxItems = config.maxItems ?? 50;
|
|
327
|
+
this.maxRetries = config.maxRetries ?? 5;
|
|
328
|
+
this.storage = config.storage ?? new LocalStorageAdapter();
|
|
329
|
+
}
|
|
330
|
+
// ── Flush handler registration ──────────────────────────────
|
|
331
|
+
/** Register a handler that replays a queued operation. */
|
|
332
|
+
registerHandler(type, handler) {
|
|
333
|
+
this.handlers.set(type, handler);
|
|
334
|
+
}
|
|
335
|
+
// ── Change listener ─────────────────────────────────────────
|
|
336
|
+
/** Subscribe to queue count changes (for UI badges). */
|
|
337
|
+
onChange(callback) {
|
|
338
|
+
this.listener = callback;
|
|
339
|
+
}
|
|
340
|
+
notify() {
|
|
341
|
+
this.listener?.(this.items.length);
|
|
342
|
+
}
|
|
343
|
+
// ── Persistence ─────────────────────────────────────────────
|
|
344
|
+
/** Load queue from persistent storage. Call once after construction. */
|
|
345
|
+
async load() {
|
|
346
|
+
try {
|
|
347
|
+
const raw = await this.storage.getItem(this.storageKey);
|
|
348
|
+
if (raw) {
|
|
349
|
+
this.items = JSON.parse(raw);
|
|
350
|
+
}
|
|
351
|
+
} catch {
|
|
352
|
+
this.items = [];
|
|
353
|
+
}
|
|
354
|
+
this.notify();
|
|
355
|
+
}
|
|
356
|
+
async save() {
|
|
357
|
+
try {
|
|
358
|
+
await this.storage.setItem(this.storageKey, JSON.stringify(this.items));
|
|
359
|
+
} catch (err) {
|
|
360
|
+
console.error("BugBear: Failed to persist offline queue", err);
|
|
361
|
+
}
|
|
362
|
+
this.notify();
|
|
363
|
+
}
|
|
364
|
+
// ── Enqueue ─────────────────────────────────────────────────
|
|
365
|
+
/** Add a failed operation to the queue. Returns the item ID. */
|
|
366
|
+
async enqueue(type, payload) {
|
|
367
|
+
if (this.items.length >= this.maxItems) {
|
|
368
|
+
this.items.shift();
|
|
369
|
+
}
|
|
370
|
+
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
371
|
+
this.items.push({ id, type, payload, createdAt: Date.now(), retries: 0 });
|
|
372
|
+
await this.save();
|
|
373
|
+
return id;
|
|
374
|
+
}
|
|
375
|
+
// ── Accessors ───────────────────────────────────────────────
|
|
376
|
+
/** Number of items waiting to be flushed. */
|
|
377
|
+
get count() {
|
|
378
|
+
return this.items.length;
|
|
379
|
+
}
|
|
380
|
+
/** Read-only snapshot of pending items. */
|
|
381
|
+
get pending() {
|
|
382
|
+
return [...this.items];
|
|
383
|
+
}
|
|
384
|
+
/** Whether a flush is currently in progress. */
|
|
385
|
+
get isFlushing() {
|
|
386
|
+
return this.flushing;
|
|
387
|
+
}
|
|
388
|
+
// ── Flush ───────────────────────────────────────────────────
|
|
389
|
+
/**
|
|
390
|
+
* Process all queued items in FIFO order.
|
|
391
|
+
* Stops early if a network error is encountered (still offline).
|
|
392
|
+
*/
|
|
393
|
+
async flush() {
|
|
394
|
+
if (this.flushing || this.items.length === 0) {
|
|
395
|
+
return { flushed: 0, failed: 0 };
|
|
396
|
+
}
|
|
397
|
+
this.flushing = true;
|
|
398
|
+
let flushed = 0;
|
|
399
|
+
let failed = 0;
|
|
400
|
+
const snapshot = [...this.items];
|
|
401
|
+
for (const item of snapshot) {
|
|
402
|
+
const handler = this.handlers.get(item.type);
|
|
403
|
+
if (!handler) {
|
|
404
|
+
failed++;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
try {
|
|
408
|
+
const result = await handler(item.payload);
|
|
409
|
+
if (result.success) {
|
|
410
|
+
this.items = this.items.filter((i) => i.id !== item.id);
|
|
411
|
+
flushed++;
|
|
412
|
+
} else if (isNetworkError(result.error)) {
|
|
413
|
+
break;
|
|
414
|
+
} else {
|
|
415
|
+
const idx = this.items.findIndex((i) => i.id === item.id);
|
|
416
|
+
if (idx !== -1) {
|
|
417
|
+
this.items[idx].retries++;
|
|
418
|
+
if (this.items[idx].retries >= this.maxRetries) {
|
|
419
|
+
this.items.splice(idx, 1);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
failed++;
|
|
423
|
+
}
|
|
424
|
+
} catch {
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
await this.save();
|
|
429
|
+
this.flushing = false;
|
|
430
|
+
return { flushed, failed };
|
|
431
|
+
}
|
|
432
|
+
// ── Clear ───────────────────────────────────────────────────
|
|
433
|
+
/** Drop all queued items. */
|
|
434
|
+
async clear() {
|
|
435
|
+
this.items = [];
|
|
436
|
+
await this.save();
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
function isNetworkError(error) {
|
|
440
|
+
if (!error) return false;
|
|
441
|
+
const msg = error.toLowerCase();
|
|
442
|
+
return msg.includes("failed to fetch") || msg.includes("networkerror") || msg.includes("network request failed") || msg.includes("timeout") || msg.includes("econnrefused") || msg.includes("enotfound") || msg.includes("load failed") || // Safari
|
|
443
|
+
msg.includes("the internet connection appears to be offline") || msg.includes("a]server with the specified hostname could not be found");
|
|
444
|
+
}
|
|
445
|
+
|
|
283
446
|
// src/client.ts
|
|
284
447
|
var formatPgError = (e) => {
|
|
285
448
|
if (!e || typeof e !== "object") return { raw: e };
|
|
286
449
|
const { message, code, details, hint } = e;
|
|
287
450
|
return { message, code, details, hint };
|
|
288
451
|
};
|
|
289
|
-
var DEFAULT_SUPABASE_URL = "https://kyxgzjnqgvapvlnvqawz.supabase.co";
|
|
290
|
-
var getEnvVar = (key) => {
|
|
291
|
-
try {
|
|
292
|
-
if (typeof process !== "undefined" && process.env) {
|
|
293
|
-
return process.env[key];
|
|
294
|
-
}
|
|
295
|
-
} catch {
|
|
296
|
-
}
|
|
297
|
-
return void 0;
|
|
298
|
-
};
|
|
299
|
-
var HOSTED_BUGBEAR_ANON_KEY = getEnvVar("BUGBEAR_ANON_KEY") || getEnvVar("NEXT_PUBLIC_BUGBEAR_ANON_KEY") || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imt5eGd6am5xZ3ZhcHZsbnZxYXd6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkyNjgwNDIsImV4cCI6MjA4NDg0NDA0Mn0.NUkAlCHLFjeRoisbmNUVoGb4R6uQ8xs5LAEIX1BWTwU";
|
|
300
452
|
var BugBearClient = class {
|
|
301
453
|
constructor(config) {
|
|
302
454
|
this.navigationHistory = [];
|
|
303
455
|
this.reportSubmitInFlight = false;
|
|
456
|
+
/** Offline queue — only created when config.offlineQueue.enabled is true. */
|
|
457
|
+
this._queue = null;
|
|
458
|
+
/** Active Realtime channel references for cleanup. */
|
|
459
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
460
|
+
this.realtimeChannels = [];
|
|
461
|
+
if (!config.supabaseUrl) {
|
|
462
|
+
throw new Error("BugBear: supabaseUrl is required. Get it from your BugBear project settings.");
|
|
463
|
+
}
|
|
464
|
+
if (!config.supabaseAnonKey) {
|
|
465
|
+
throw new Error("BugBear: supabaseAnonKey is required. Get it from your BugBear project settings.");
|
|
466
|
+
}
|
|
304
467
|
this.config = config;
|
|
305
|
-
this.supabase = (0, import_supabase_js.createClient)(
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
468
|
+
this.supabase = (0, import_supabase_js.createClient)(config.supabaseUrl, config.supabaseAnonKey);
|
|
469
|
+
if (config.offlineQueue?.enabled) {
|
|
470
|
+
this._queue = new OfflineQueue({
|
|
471
|
+
enabled: true,
|
|
472
|
+
maxItems: config.offlineQueue.maxItems,
|
|
473
|
+
maxRetries: config.offlineQueue.maxRetries
|
|
474
|
+
});
|
|
475
|
+
this.registerQueueHandlers();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
// ── Offline Queue ─────────────────────────────────────────
|
|
479
|
+
/**
|
|
480
|
+
* Access the offline queue (if enabled).
|
|
481
|
+
* Use this to check queue.count, subscribe to changes, or trigger flush.
|
|
482
|
+
*/
|
|
483
|
+
get queue() {
|
|
484
|
+
return this._queue;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Initialize the offline queue with a platform-specific storage adapter.
|
|
488
|
+
* Must be called after construction for React Native (which supplies AsyncStorage).
|
|
489
|
+
* Web callers can skip this — LocalStorageAdapter is the default.
|
|
490
|
+
*/
|
|
491
|
+
async initQueue(storage) {
|
|
492
|
+
if (!this._queue) return;
|
|
493
|
+
if (storage) {
|
|
494
|
+
this._queue = new OfflineQueue({
|
|
495
|
+
enabled: true,
|
|
496
|
+
maxItems: this.config.offlineQueue?.maxItems,
|
|
497
|
+
maxRetries: this.config.offlineQueue?.maxRetries,
|
|
498
|
+
storage
|
|
499
|
+
});
|
|
500
|
+
this.registerQueueHandlers();
|
|
501
|
+
}
|
|
502
|
+
await this._queue.load();
|
|
503
|
+
}
|
|
504
|
+
registerQueueHandlers() {
|
|
505
|
+
if (!this._queue) return;
|
|
506
|
+
this._queue.registerHandler("report", async (payload) => {
|
|
507
|
+
const { error } = await this.supabase.from("reports").insert(payload).select("id").single();
|
|
508
|
+
if (error) return { success: false, error: error.message };
|
|
509
|
+
return { success: true };
|
|
510
|
+
});
|
|
511
|
+
this._queue.registerHandler("message", async (payload) => {
|
|
512
|
+
const { error } = await this.supabase.from("discussion_messages").insert(payload);
|
|
513
|
+
if (error) return { success: false, error: error.message };
|
|
514
|
+
return { success: true };
|
|
515
|
+
});
|
|
516
|
+
this._queue.registerHandler("feedback", async (payload) => {
|
|
517
|
+
const { error } = await this.supabase.from("test_feedback").insert(payload);
|
|
518
|
+
if (error) return { success: false, error: error.message };
|
|
519
|
+
return { success: true };
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
// ── Realtime Subscriptions ─────────────────────────────────
|
|
523
|
+
/** Whether realtime is enabled in config. */
|
|
524
|
+
get realtimeEnabled() {
|
|
525
|
+
return !!this.config.realtime?.enabled;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Subscribe to postgres_changes on relevant tables.
|
|
529
|
+
* Each callback fires when the corresponding table has changes —
|
|
530
|
+
* the provider should call its refresh function in response.
|
|
531
|
+
* Returns a cleanup function that unsubscribes all channels.
|
|
532
|
+
*/
|
|
533
|
+
subscribeToChanges(callbacks) {
|
|
534
|
+
this.unsubscribeAll();
|
|
535
|
+
const projectId = this.config.projectId;
|
|
536
|
+
const debounce = (fn, ms = 500) => {
|
|
537
|
+
let timer;
|
|
538
|
+
return () => {
|
|
539
|
+
clearTimeout(timer);
|
|
540
|
+
timer = setTimeout(fn, ms);
|
|
541
|
+
};
|
|
542
|
+
};
|
|
543
|
+
if (callbacks.onAssignmentChange) {
|
|
544
|
+
const debouncedCb = debounce(callbacks.onAssignmentChange);
|
|
545
|
+
const channel = this.supabase.channel("bugbear-assignments").on("postgres_changes", {
|
|
546
|
+
event: "*",
|
|
547
|
+
schema: "public",
|
|
548
|
+
table: "test_assignments",
|
|
549
|
+
filter: `project_id=eq.${projectId}`
|
|
550
|
+
}, debouncedCb).subscribe((status) => {
|
|
551
|
+
if (status === "CHANNEL_ERROR") {
|
|
552
|
+
console.warn("BugBear: Realtime subscription failed for test_assignments");
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
this.realtimeChannels.push(channel);
|
|
556
|
+
}
|
|
557
|
+
if (callbacks.onMessageChange) {
|
|
558
|
+
const debouncedCb = debounce(callbacks.onMessageChange);
|
|
559
|
+
const channel = this.supabase.channel("bugbear-messages").on("postgres_changes", {
|
|
560
|
+
event: "INSERT",
|
|
561
|
+
schema: "public",
|
|
562
|
+
table: "discussion_messages"
|
|
563
|
+
}, debouncedCb).subscribe((status) => {
|
|
564
|
+
if (status === "CHANNEL_ERROR") {
|
|
565
|
+
console.warn("BugBear: Realtime subscription failed for discussion_messages");
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
this.realtimeChannels.push(channel);
|
|
569
|
+
}
|
|
570
|
+
if (callbacks.onReportChange) {
|
|
571
|
+
const debouncedCb = debounce(callbacks.onReportChange);
|
|
572
|
+
const channel = this.supabase.channel("bugbear-reports").on("postgres_changes", {
|
|
573
|
+
event: "UPDATE",
|
|
574
|
+
schema: "public",
|
|
575
|
+
table: "reports",
|
|
576
|
+
filter: `project_id=eq.${projectId}`
|
|
577
|
+
}, debouncedCb).subscribe((status) => {
|
|
578
|
+
if (status === "CHANNEL_ERROR") {
|
|
579
|
+
console.warn("BugBear: Realtime subscription failed for reports");
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
this.realtimeChannels.push(channel);
|
|
583
|
+
}
|
|
584
|
+
return () => this.unsubscribeAll();
|
|
585
|
+
}
|
|
586
|
+
/** Remove all active Realtime channels. */
|
|
587
|
+
unsubscribeAll() {
|
|
588
|
+
for (const channel of this.realtimeChannels) {
|
|
589
|
+
this.supabase.removeChannel(channel);
|
|
590
|
+
}
|
|
591
|
+
this.realtimeChannels = [];
|
|
309
592
|
}
|
|
310
593
|
/**
|
|
311
594
|
* Track navigation for context.
|
|
@@ -365,6 +648,7 @@ var BugBearClient = class {
|
|
|
365
648
|
return { success: false, error: "A report is already being submitted" };
|
|
366
649
|
}
|
|
367
650
|
this.reportSubmitInFlight = true;
|
|
651
|
+
let fullReport;
|
|
368
652
|
try {
|
|
369
653
|
const validationError = this.validateReport(report);
|
|
370
654
|
if (validationError) {
|
|
@@ -381,7 +665,7 @@ var BugBearClient = class {
|
|
|
381
665
|
return { success: false, error: "User not authenticated" };
|
|
382
666
|
}
|
|
383
667
|
const testerInfo = await this.getTesterInfo();
|
|
384
|
-
|
|
668
|
+
fullReport = {
|
|
385
669
|
project_id: this.config.projectId,
|
|
386
670
|
reporter_id: userInfo.id,
|
|
387
671
|
// User ID from host app (required)
|
|
@@ -408,6 +692,10 @@ var BugBearClient = class {
|
|
|
408
692
|
};
|
|
409
693
|
const { data, error } = await this.supabase.from("reports").insert(fullReport).select("id").single();
|
|
410
694
|
if (error) {
|
|
695
|
+
if (this._queue && isNetworkError(error.message)) {
|
|
696
|
+
await this._queue.enqueue("report", fullReport);
|
|
697
|
+
return { success: false, queued: true, error: "Queued \u2014 will send when online" };
|
|
698
|
+
}
|
|
411
699
|
console.error("BugBear: Failed to submit report", error.message);
|
|
412
700
|
return { success: false, error: error.message };
|
|
413
701
|
}
|
|
@@ -417,6 +705,10 @@ var BugBearClient = class {
|
|
|
417
705
|
return { success: true, reportId: data.id };
|
|
418
706
|
} catch (err) {
|
|
419
707
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
708
|
+
if (this._queue && fullReport && isNetworkError(message)) {
|
|
709
|
+
await this._queue.enqueue("report", fullReport);
|
|
710
|
+
return { success: false, queued: true, error: "Queued \u2014 will send when online" };
|
|
711
|
+
}
|
|
420
712
|
return { success: false, error: message };
|
|
421
713
|
} finally {
|
|
422
714
|
this.reportSubmitInFlight = false;
|
|
@@ -426,10 +718,13 @@ var BugBearClient = class {
|
|
|
426
718
|
* Get assigned tests for current user
|
|
427
719
|
* First looks up the tester by email, then fetches their assignments
|
|
428
720
|
*/
|
|
429
|
-
async getAssignedTests() {
|
|
721
|
+
async getAssignedTests(options) {
|
|
430
722
|
try {
|
|
431
723
|
const testerInfo = await this.getTesterInfo();
|
|
432
724
|
if (!testerInfo) return [];
|
|
725
|
+
const pageSize = Math.min(options?.pageSize ?? 100, 100);
|
|
726
|
+
const from = (options?.page ?? 0) * pageSize;
|
|
727
|
+
const to = from + pageSize - 1;
|
|
433
728
|
const { data, error } = await this.supabase.from("test_assignments").select(`
|
|
434
729
|
id,
|
|
435
730
|
status,
|
|
@@ -470,12 +765,18 @@ var BugBearClient = class {
|
|
|
470
765
|
login_hint
|
|
471
766
|
)
|
|
472
767
|
)
|
|
473
|
-
`).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).
|
|
768
|
+
`).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).range(from, to);
|
|
474
769
|
if (error) {
|
|
475
770
|
console.error("BugBear: Failed to fetch assignments", formatPgError(error));
|
|
476
771
|
return [];
|
|
477
772
|
}
|
|
478
|
-
const mapped = (data || []).
|
|
773
|
+
const mapped = (data || []).filter((item) => {
|
|
774
|
+
if (!item.test_case) {
|
|
775
|
+
console.warn("BugBear: Assignment returned without test_case", { id: item.id });
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
return true;
|
|
779
|
+
}).map((item) => ({
|
|
479
780
|
id: item.id,
|
|
480
781
|
status: item.status,
|
|
481
782
|
startedAt: item.started_at,
|
|
@@ -723,6 +1024,7 @@ var BugBearClient = class {
|
|
|
723
1024
|
* This empowers testers to shape better tests over time
|
|
724
1025
|
*/
|
|
725
1026
|
async submitTestFeedback(options) {
|
|
1027
|
+
let feedbackPayload;
|
|
726
1028
|
try {
|
|
727
1029
|
const testerInfo = await this.getTesterInfo();
|
|
728
1030
|
if (!testerInfo) {
|
|
@@ -732,7 +1034,17 @@ var BugBearClient = class {
|
|
|
732
1034
|
if (feedback.rating < 1 || feedback.rating > 5) {
|
|
733
1035
|
return { success: false, error: "Rating must be between 1 and 5" };
|
|
734
1036
|
}
|
|
735
|
-
const
|
|
1037
|
+
const optionalRatings = [
|
|
1038
|
+
{ name: "clarityRating", value: feedback.clarityRating },
|
|
1039
|
+
{ name: "stepsRating", value: feedback.stepsRating },
|
|
1040
|
+
{ name: "relevanceRating", value: feedback.relevanceRating }
|
|
1041
|
+
];
|
|
1042
|
+
for (const { name, value } of optionalRatings) {
|
|
1043
|
+
if (value !== void 0 && value !== null && (value < 1 || value > 5)) {
|
|
1044
|
+
return { success: false, error: `${name} must be between 1 and 5` };
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
feedbackPayload = {
|
|
736
1048
|
project_id: this.config.projectId,
|
|
737
1049
|
test_case_id: testCaseId,
|
|
738
1050
|
assignment_id: assignmentId || null,
|
|
@@ -750,8 +1062,13 @@ var BugBearClient = class {
|
|
|
750
1062
|
platform: this.getDeviceInfo().platform,
|
|
751
1063
|
time_to_complete_seconds: timeToCompleteSeconds || null,
|
|
752
1064
|
screenshot_urls: screenshotUrls || []
|
|
753
|
-
}
|
|
1065
|
+
};
|
|
1066
|
+
const { error: feedbackError } = await this.supabase.from("test_feedback").insert(feedbackPayload);
|
|
754
1067
|
if (feedbackError) {
|
|
1068
|
+
if (this._queue && isNetworkError(feedbackError.message)) {
|
|
1069
|
+
await this._queue.enqueue("feedback", feedbackPayload);
|
|
1070
|
+
return { success: false, queued: true, error: "Queued \u2014 will send when online" };
|
|
1071
|
+
}
|
|
755
1072
|
console.error("BugBear: Failed to submit feedback", feedbackError);
|
|
756
1073
|
return { success: false, error: feedbackError.message };
|
|
757
1074
|
}
|
|
@@ -768,6 +1085,10 @@ var BugBearClient = class {
|
|
|
768
1085
|
return { success: true };
|
|
769
1086
|
} catch (err) {
|
|
770
1087
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
1088
|
+
if (this._queue && feedbackPayload && isNetworkError(message)) {
|
|
1089
|
+
await this._queue.enqueue("feedback", feedbackPayload);
|
|
1090
|
+
return { success: false, queued: true, error: "Queued \u2014 will send when online" };
|
|
1091
|
+
}
|
|
771
1092
|
console.error("BugBear: Error submitting feedback", err);
|
|
772
1093
|
return { success: false, error: message };
|
|
773
1094
|
}
|
|
@@ -1402,6 +1723,7 @@ var BugBearClient = class {
|
|
|
1402
1723
|
* Send a message to a thread
|
|
1403
1724
|
*/
|
|
1404
1725
|
async sendMessage(threadId, content, attachments) {
|
|
1726
|
+
let insertData;
|
|
1405
1727
|
try {
|
|
1406
1728
|
const testerInfo = await this.getTesterInfo();
|
|
1407
1729
|
if (!testerInfo) {
|
|
@@ -1413,7 +1735,7 @@ var BugBearClient = class {
|
|
|
1413
1735
|
console.error("BugBear: Rate limit exceeded for messages");
|
|
1414
1736
|
return false;
|
|
1415
1737
|
}
|
|
1416
|
-
|
|
1738
|
+
insertData = {
|
|
1417
1739
|
thread_id: threadId,
|
|
1418
1740
|
sender_type: "tester",
|
|
1419
1741
|
sender_tester_id: testerInfo.id,
|
|
@@ -1428,12 +1750,21 @@ var BugBearClient = class {
|
|
|
1428
1750
|
}
|
|
1429
1751
|
const { error } = await this.supabase.from("discussion_messages").insert(insertData);
|
|
1430
1752
|
if (error) {
|
|
1753
|
+
if (this._queue && isNetworkError(error.message)) {
|
|
1754
|
+
await this._queue.enqueue("message", insertData);
|
|
1755
|
+
return false;
|
|
1756
|
+
}
|
|
1431
1757
|
console.error("BugBear: Failed to send message", formatPgError(error));
|
|
1432
1758
|
return false;
|
|
1433
1759
|
}
|
|
1434
1760
|
await this.markThreadAsRead(threadId);
|
|
1435
1761
|
return true;
|
|
1436
1762
|
} catch (err) {
|
|
1763
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
1764
|
+
if (this._queue && insertData && isNetworkError(message)) {
|
|
1765
|
+
await this._queue.enqueue("message", insertData);
|
|
1766
|
+
return false;
|
|
1767
|
+
}
|
|
1437
1768
|
console.error("BugBear: Error sending message", err);
|
|
1438
1769
|
return false;
|
|
1439
1770
|
}
|
|
@@ -1770,8 +2101,11 @@ function createBugBear(config) {
|
|
|
1770
2101
|
0 && (module.exports = {
|
|
1771
2102
|
BUG_CATEGORIES,
|
|
1772
2103
|
BugBearClient,
|
|
2104
|
+
LocalStorageAdapter,
|
|
2105
|
+
OfflineQueue,
|
|
1773
2106
|
captureError,
|
|
1774
2107
|
contextCapture,
|
|
1775
2108
|
createBugBear,
|
|
1776
|
-
isBugCategory
|
|
2109
|
+
isBugCategory,
|
|
2110
|
+
isNetworkError
|
|
1777
2111
|
});
|