@bbearai/core 0.4.6 → 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 +341 -23
- package/dist/index.mjs +337 -22
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -249,32 +249,312 @@ function captureError(error, errorInfo) {
|
|
|
249
249
|
};
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
+
// src/offline-queue.ts
|
|
253
|
+
var LocalStorageAdapter = class {
|
|
254
|
+
constructor() {
|
|
255
|
+
this.fallback = /* @__PURE__ */ new Map();
|
|
256
|
+
}
|
|
257
|
+
get isAvailable() {
|
|
258
|
+
try {
|
|
259
|
+
const key = "__bugbear_test__";
|
|
260
|
+
localStorage.setItem(key, "1");
|
|
261
|
+
localStorage.removeItem(key);
|
|
262
|
+
return true;
|
|
263
|
+
} catch {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async getItem(key) {
|
|
268
|
+
if (this.isAvailable) return localStorage.getItem(key);
|
|
269
|
+
return this.fallback.get(key) ?? null;
|
|
270
|
+
}
|
|
271
|
+
async setItem(key, value) {
|
|
272
|
+
if (this.isAvailable) {
|
|
273
|
+
localStorage.setItem(key, value);
|
|
274
|
+
} else {
|
|
275
|
+
this.fallback.set(key, value);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
async removeItem(key) {
|
|
279
|
+
if (this.isAvailable) {
|
|
280
|
+
localStorage.removeItem(key);
|
|
281
|
+
} else {
|
|
282
|
+
this.fallback.delete(key);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
var OfflineQueue = class {
|
|
287
|
+
constructor(config) {
|
|
288
|
+
this.items = [];
|
|
289
|
+
this.storageKey = "bugbear_offline_queue";
|
|
290
|
+
this.flushing = false;
|
|
291
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
292
|
+
this.maxItems = config.maxItems ?? 50;
|
|
293
|
+
this.maxRetries = config.maxRetries ?? 5;
|
|
294
|
+
this.storage = config.storage ?? new LocalStorageAdapter();
|
|
295
|
+
}
|
|
296
|
+
// ── Flush handler registration ──────────────────────────────
|
|
297
|
+
/** Register a handler that replays a queued operation. */
|
|
298
|
+
registerHandler(type, handler) {
|
|
299
|
+
this.handlers.set(type, handler);
|
|
300
|
+
}
|
|
301
|
+
// ── Change listener ─────────────────────────────────────────
|
|
302
|
+
/** Subscribe to queue count changes (for UI badges). */
|
|
303
|
+
onChange(callback) {
|
|
304
|
+
this.listener = callback;
|
|
305
|
+
}
|
|
306
|
+
notify() {
|
|
307
|
+
this.listener?.(this.items.length);
|
|
308
|
+
}
|
|
309
|
+
// ── Persistence ─────────────────────────────────────────────
|
|
310
|
+
/** Load queue from persistent storage. Call once after construction. */
|
|
311
|
+
async load() {
|
|
312
|
+
try {
|
|
313
|
+
const raw = await this.storage.getItem(this.storageKey);
|
|
314
|
+
if (raw) {
|
|
315
|
+
this.items = JSON.parse(raw);
|
|
316
|
+
}
|
|
317
|
+
} catch {
|
|
318
|
+
this.items = [];
|
|
319
|
+
}
|
|
320
|
+
this.notify();
|
|
321
|
+
}
|
|
322
|
+
async save() {
|
|
323
|
+
try {
|
|
324
|
+
await this.storage.setItem(this.storageKey, JSON.stringify(this.items));
|
|
325
|
+
} catch (err) {
|
|
326
|
+
console.error("BugBear: Failed to persist offline queue", err);
|
|
327
|
+
}
|
|
328
|
+
this.notify();
|
|
329
|
+
}
|
|
330
|
+
// ── Enqueue ─────────────────────────────────────────────────
|
|
331
|
+
/** Add a failed operation to the queue. Returns the item ID. */
|
|
332
|
+
async enqueue(type, payload) {
|
|
333
|
+
if (this.items.length >= this.maxItems) {
|
|
334
|
+
this.items.shift();
|
|
335
|
+
}
|
|
336
|
+
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
337
|
+
this.items.push({ id, type, payload, createdAt: Date.now(), retries: 0 });
|
|
338
|
+
await this.save();
|
|
339
|
+
return id;
|
|
340
|
+
}
|
|
341
|
+
// ── Accessors ───────────────────────────────────────────────
|
|
342
|
+
/** Number of items waiting to be flushed. */
|
|
343
|
+
get count() {
|
|
344
|
+
return this.items.length;
|
|
345
|
+
}
|
|
346
|
+
/** Read-only snapshot of pending items. */
|
|
347
|
+
get pending() {
|
|
348
|
+
return [...this.items];
|
|
349
|
+
}
|
|
350
|
+
/** Whether a flush is currently in progress. */
|
|
351
|
+
get isFlushing() {
|
|
352
|
+
return this.flushing;
|
|
353
|
+
}
|
|
354
|
+
// ── Flush ───────────────────────────────────────────────────
|
|
355
|
+
/**
|
|
356
|
+
* Process all queued items in FIFO order.
|
|
357
|
+
* Stops early if a network error is encountered (still offline).
|
|
358
|
+
*/
|
|
359
|
+
async flush() {
|
|
360
|
+
if (this.flushing || this.items.length === 0) {
|
|
361
|
+
return { flushed: 0, failed: 0 };
|
|
362
|
+
}
|
|
363
|
+
this.flushing = true;
|
|
364
|
+
let flushed = 0;
|
|
365
|
+
let failed = 0;
|
|
366
|
+
const snapshot = [...this.items];
|
|
367
|
+
for (const item of snapshot) {
|
|
368
|
+
const handler = this.handlers.get(item.type);
|
|
369
|
+
if (!handler) {
|
|
370
|
+
failed++;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
try {
|
|
374
|
+
const result = await handler(item.payload);
|
|
375
|
+
if (result.success) {
|
|
376
|
+
this.items = this.items.filter((i) => i.id !== item.id);
|
|
377
|
+
flushed++;
|
|
378
|
+
} else if (isNetworkError(result.error)) {
|
|
379
|
+
break;
|
|
380
|
+
} else {
|
|
381
|
+
const idx = this.items.findIndex((i) => i.id === item.id);
|
|
382
|
+
if (idx !== -1) {
|
|
383
|
+
this.items[idx].retries++;
|
|
384
|
+
if (this.items[idx].retries >= this.maxRetries) {
|
|
385
|
+
this.items.splice(idx, 1);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
failed++;
|
|
389
|
+
}
|
|
390
|
+
} catch {
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
await this.save();
|
|
395
|
+
this.flushing = false;
|
|
396
|
+
return { flushed, failed };
|
|
397
|
+
}
|
|
398
|
+
// ── Clear ───────────────────────────────────────────────────
|
|
399
|
+
/** Drop all queued items. */
|
|
400
|
+
async clear() {
|
|
401
|
+
this.items = [];
|
|
402
|
+
await this.save();
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
function isNetworkError(error) {
|
|
406
|
+
if (!error) return false;
|
|
407
|
+
const msg = error.toLowerCase();
|
|
408
|
+
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
|
|
409
|
+
msg.includes("the internet connection appears to be offline") || msg.includes("a]server with the specified hostname could not be found");
|
|
410
|
+
}
|
|
411
|
+
|
|
252
412
|
// src/client.ts
|
|
253
413
|
var formatPgError = (e) => {
|
|
254
414
|
if (!e || typeof e !== "object") return { raw: e };
|
|
255
415
|
const { message, code, details, hint } = e;
|
|
256
416
|
return { message, code, details, hint };
|
|
257
417
|
};
|
|
258
|
-
var DEFAULT_SUPABASE_URL = "https://kyxgzjnqgvapvlnvqawz.supabase.co";
|
|
259
|
-
var getEnvVar = (key) => {
|
|
260
|
-
try {
|
|
261
|
-
if (typeof process !== "undefined" && process.env) {
|
|
262
|
-
return process.env[key];
|
|
263
|
-
}
|
|
264
|
-
} catch {
|
|
265
|
-
}
|
|
266
|
-
return void 0;
|
|
267
|
-
};
|
|
268
|
-
var HOSTED_BUGBEAR_ANON_KEY = getEnvVar("BUGBEAR_ANON_KEY") || getEnvVar("NEXT_PUBLIC_BUGBEAR_ANON_KEY") || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imt5eGd6am5xZ3ZhcHZsbnZxYXd6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkyNjgwNDIsImV4cCI6MjA4NDg0NDA0Mn0.NUkAlCHLFjeRoisbmNUVoGb4R6uQ8xs5LAEIX1BWTwU";
|
|
269
418
|
var BugBearClient = class {
|
|
270
419
|
constructor(config) {
|
|
271
420
|
this.navigationHistory = [];
|
|
272
421
|
this.reportSubmitInFlight = false;
|
|
422
|
+
/** Offline queue — only created when config.offlineQueue.enabled is true. */
|
|
423
|
+
this._queue = null;
|
|
424
|
+
/** Active Realtime channel references for cleanup. */
|
|
425
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
426
|
+
this.realtimeChannels = [];
|
|
427
|
+
if (!config.supabaseUrl) {
|
|
428
|
+
throw new Error("BugBear: supabaseUrl is required. Get it from your BugBear project settings.");
|
|
429
|
+
}
|
|
430
|
+
if (!config.supabaseAnonKey) {
|
|
431
|
+
throw new Error("BugBear: supabaseAnonKey is required. Get it from your BugBear project settings.");
|
|
432
|
+
}
|
|
273
433
|
this.config = config;
|
|
274
|
-
this.supabase = createClient(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
434
|
+
this.supabase = createClient(config.supabaseUrl, config.supabaseAnonKey);
|
|
435
|
+
if (config.offlineQueue?.enabled) {
|
|
436
|
+
this._queue = new OfflineQueue({
|
|
437
|
+
enabled: true,
|
|
438
|
+
maxItems: config.offlineQueue.maxItems,
|
|
439
|
+
maxRetries: config.offlineQueue.maxRetries
|
|
440
|
+
});
|
|
441
|
+
this.registerQueueHandlers();
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// ── Offline Queue ─────────────────────────────────────────
|
|
445
|
+
/**
|
|
446
|
+
* Access the offline queue (if enabled).
|
|
447
|
+
* Use this to check queue.count, subscribe to changes, or trigger flush.
|
|
448
|
+
*/
|
|
449
|
+
get queue() {
|
|
450
|
+
return this._queue;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Initialize the offline queue with a platform-specific storage adapter.
|
|
454
|
+
* Must be called after construction for React Native (which supplies AsyncStorage).
|
|
455
|
+
* Web callers can skip this — LocalStorageAdapter is the default.
|
|
456
|
+
*/
|
|
457
|
+
async initQueue(storage) {
|
|
458
|
+
if (!this._queue) return;
|
|
459
|
+
if (storage) {
|
|
460
|
+
this._queue = new OfflineQueue({
|
|
461
|
+
enabled: true,
|
|
462
|
+
maxItems: this.config.offlineQueue?.maxItems,
|
|
463
|
+
maxRetries: this.config.offlineQueue?.maxRetries,
|
|
464
|
+
storage
|
|
465
|
+
});
|
|
466
|
+
this.registerQueueHandlers();
|
|
467
|
+
}
|
|
468
|
+
await this._queue.load();
|
|
469
|
+
}
|
|
470
|
+
registerQueueHandlers() {
|
|
471
|
+
if (!this._queue) return;
|
|
472
|
+
this._queue.registerHandler("report", async (payload) => {
|
|
473
|
+
const { error } = await this.supabase.from("reports").insert(payload).select("id").single();
|
|
474
|
+
if (error) return { success: false, error: error.message };
|
|
475
|
+
return { success: true };
|
|
476
|
+
});
|
|
477
|
+
this._queue.registerHandler("message", async (payload) => {
|
|
478
|
+
const { error } = await this.supabase.from("discussion_messages").insert(payload);
|
|
479
|
+
if (error) return { success: false, error: error.message };
|
|
480
|
+
return { success: true };
|
|
481
|
+
});
|
|
482
|
+
this._queue.registerHandler("feedback", async (payload) => {
|
|
483
|
+
const { error } = await this.supabase.from("test_feedback").insert(payload);
|
|
484
|
+
if (error) return { success: false, error: error.message };
|
|
485
|
+
return { success: true };
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
// ── Realtime Subscriptions ─────────────────────────────────
|
|
489
|
+
/** Whether realtime is enabled in config. */
|
|
490
|
+
get realtimeEnabled() {
|
|
491
|
+
return !!this.config.realtime?.enabled;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Subscribe to postgres_changes on relevant tables.
|
|
495
|
+
* Each callback fires when the corresponding table has changes —
|
|
496
|
+
* the provider should call its refresh function in response.
|
|
497
|
+
* Returns a cleanup function that unsubscribes all channels.
|
|
498
|
+
*/
|
|
499
|
+
subscribeToChanges(callbacks) {
|
|
500
|
+
this.unsubscribeAll();
|
|
501
|
+
const projectId = this.config.projectId;
|
|
502
|
+
const debounce = (fn, ms = 500) => {
|
|
503
|
+
let timer;
|
|
504
|
+
return () => {
|
|
505
|
+
clearTimeout(timer);
|
|
506
|
+
timer = setTimeout(fn, ms);
|
|
507
|
+
};
|
|
508
|
+
};
|
|
509
|
+
if (callbacks.onAssignmentChange) {
|
|
510
|
+
const debouncedCb = debounce(callbacks.onAssignmentChange);
|
|
511
|
+
const channel = this.supabase.channel("bugbear-assignments").on("postgres_changes", {
|
|
512
|
+
event: "*",
|
|
513
|
+
schema: "public",
|
|
514
|
+
table: "test_assignments",
|
|
515
|
+
filter: `project_id=eq.${projectId}`
|
|
516
|
+
}, debouncedCb).subscribe((status) => {
|
|
517
|
+
if (status === "CHANNEL_ERROR") {
|
|
518
|
+
console.warn("BugBear: Realtime subscription failed for test_assignments");
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
this.realtimeChannels.push(channel);
|
|
522
|
+
}
|
|
523
|
+
if (callbacks.onMessageChange) {
|
|
524
|
+
const debouncedCb = debounce(callbacks.onMessageChange);
|
|
525
|
+
const channel = this.supabase.channel("bugbear-messages").on("postgres_changes", {
|
|
526
|
+
event: "INSERT",
|
|
527
|
+
schema: "public",
|
|
528
|
+
table: "discussion_messages"
|
|
529
|
+
}, debouncedCb).subscribe((status) => {
|
|
530
|
+
if (status === "CHANNEL_ERROR") {
|
|
531
|
+
console.warn("BugBear: Realtime subscription failed for discussion_messages");
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
this.realtimeChannels.push(channel);
|
|
535
|
+
}
|
|
536
|
+
if (callbacks.onReportChange) {
|
|
537
|
+
const debouncedCb = debounce(callbacks.onReportChange);
|
|
538
|
+
const channel = this.supabase.channel("bugbear-reports").on("postgres_changes", {
|
|
539
|
+
event: "UPDATE",
|
|
540
|
+
schema: "public",
|
|
541
|
+
table: "reports",
|
|
542
|
+
filter: `project_id=eq.${projectId}`
|
|
543
|
+
}, debouncedCb).subscribe((status) => {
|
|
544
|
+
if (status === "CHANNEL_ERROR") {
|
|
545
|
+
console.warn("BugBear: Realtime subscription failed for reports");
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
this.realtimeChannels.push(channel);
|
|
549
|
+
}
|
|
550
|
+
return () => this.unsubscribeAll();
|
|
551
|
+
}
|
|
552
|
+
/** Remove all active Realtime channels. */
|
|
553
|
+
unsubscribeAll() {
|
|
554
|
+
for (const channel of this.realtimeChannels) {
|
|
555
|
+
this.supabase.removeChannel(channel);
|
|
556
|
+
}
|
|
557
|
+
this.realtimeChannels = [];
|
|
278
558
|
}
|
|
279
559
|
/**
|
|
280
560
|
* Track navigation for context.
|
|
@@ -334,6 +614,7 @@ var BugBearClient = class {
|
|
|
334
614
|
return { success: false, error: "A report is already being submitted" };
|
|
335
615
|
}
|
|
336
616
|
this.reportSubmitInFlight = true;
|
|
617
|
+
let fullReport;
|
|
337
618
|
try {
|
|
338
619
|
const validationError = this.validateReport(report);
|
|
339
620
|
if (validationError) {
|
|
@@ -350,7 +631,7 @@ var BugBearClient = class {
|
|
|
350
631
|
return { success: false, error: "User not authenticated" };
|
|
351
632
|
}
|
|
352
633
|
const testerInfo = await this.getTesterInfo();
|
|
353
|
-
|
|
634
|
+
fullReport = {
|
|
354
635
|
project_id: this.config.projectId,
|
|
355
636
|
reporter_id: userInfo.id,
|
|
356
637
|
// User ID from host app (required)
|
|
@@ -377,6 +658,10 @@ var BugBearClient = class {
|
|
|
377
658
|
};
|
|
378
659
|
const { data, error } = await this.supabase.from("reports").insert(fullReport).select("id").single();
|
|
379
660
|
if (error) {
|
|
661
|
+
if (this._queue && isNetworkError(error.message)) {
|
|
662
|
+
await this._queue.enqueue("report", fullReport);
|
|
663
|
+
return { success: false, queued: true, error: "Queued \u2014 will send when online" };
|
|
664
|
+
}
|
|
380
665
|
console.error("BugBear: Failed to submit report", error.message);
|
|
381
666
|
return { success: false, error: error.message };
|
|
382
667
|
}
|
|
@@ -386,6 +671,10 @@ var BugBearClient = class {
|
|
|
386
671
|
return { success: true, reportId: data.id };
|
|
387
672
|
} catch (err) {
|
|
388
673
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
674
|
+
if (this._queue && fullReport && isNetworkError(message)) {
|
|
675
|
+
await this._queue.enqueue("report", fullReport);
|
|
676
|
+
return { success: false, queued: true, error: "Queued \u2014 will send when online" };
|
|
677
|
+
}
|
|
389
678
|
return { success: false, error: message };
|
|
390
679
|
} finally {
|
|
391
680
|
this.reportSubmitInFlight = false;
|
|
@@ -395,10 +684,13 @@ var BugBearClient = class {
|
|
|
395
684
|
* Get assigned tests for current user
|
|
396
685
|
* First looks up the tester by email, then fetches their assignments
|
|
397
686
|
*/
|
|
398
|
-
async getAssignedTests() {
|
|
687
|
+
async getAssignedTests(options) {
|
|
399
688
|
try {
|
|
400
689
|
const testerInfo = await this.getTesterInfo();
|
|
401
690
|
if (!testerInfo) return [];
|
|
691
|
+
const pageSize = Math.min(options?.pageSize ?? 100, 100);
|
|
692
|
+
const from = (options?.page ?? 0) * pageSize;
|
|
693
|
+
const to = from + pageSize - 1;
|
|
402
694
|
const { data, error } = await this.supabase.from("test_assignments").select(`
|
|
403
695
|
id,
|
|
404
696
|
status,
|
|
@@ -439,7 +731,7 @@ var BugBearClient = class {
|
|
|
439
731
|
login_hint
|
|
440
732
|
)
|
|
441
733
|
)
|
|
442
|
-
`).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).
|
|
734
|
+
`).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).range(from, to);
|
|
443
735
|
if (error) {
|
|
444
736
|
console.error("BugBear: Failed to fetch assignments", formatPgError(error));
|
|
445
737
|
return [];
|
|
@@ -698,6 +990,7 @@ var BugBearClient = class {
|
|
|
698
990
|
* This empowers testers to shape better tests over time
|
|
699
991
|
*/
|
|
700
992
|
async submitTestFeedback(options) {
|
|
993
|
+
let feedbackPayload;
|
|
701
994
|
try {
|
|
702
995
|
const testerInfo = await this.getTesterInfo();
|
|
703
996
|
if (!testerInfo) {
|
|
@@ -717,7 +1010,7 @@ var BugBearClient = class {
|
|
|
717
1010
|
return { success: false, error: `${name} must be between 1 and 5` };
|
|
718
1011
|
}
|
|
719
1012
|
}
|
|
720
|
-
|
|
1013
|
+
feedbackPayload = {
|
|
721
1014
|
project_id: this.config.projectId,
|
|
722
1015
|
test_case_id: testCaseId,
|
|
723
1016
|
assignment_id: assignmentId || null,
|
|
@@ -735,8 +1028,13 @@ var BugBearClient = class {
|
|
|
735
1028
|
platform: this.getDeviceInfo().platform,
|
|
736
1029
|
time_to_complete_seconds: timeToCompleteSeconds || null,
|
|
737
1030
|
screenshot_urls: screenshotUrls || []
|
|
738
|
-
}
|
|
1031
|
+
};
|
|
1032
|
+
const { error: feedbackError } = await this.supabase.from("test_feedback").insert(feedbackPayload);
|
|
739
1033
|
if (feedbackError) {
|
|
1034
|
+
if (this._queue && isNetworkError(feedbackError.message)) {
|
|
1035
|
+
await this._queue.enqueue("feedback", feedbackPayload);
|
|
1036
|
+
return { success: false, queued: true, error: "Queued \u2014 will send when online" };
|
|
1037
|
+
}
|
|
740
1038
|
console.error("BugBear: Failed to submit feedback", feedbackError);
|
|
741
1039
|
return { success: false, error: feedbackError.message };
|
|
742
1040
|
}
|
|
@@ -753,6 +1051,10 @@ var BugBearClient = class {
|
|
|
753
1051
|
return { success: true };
|
|
754
1052
|
} catch (err) {
|
|
755
1053
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
1054
|
+
if (this._queue && feedbackPayload && isNetworkError(message)) {
|
|
1055
|
+
await this._queue.enqueue("feedback", feedbackPayload);
|
|
1056
|
+
return { success: false, queued: true, error: "Queued \u2014 will send when online" };
|
|
1057
|
+
}
|
|
756
1058
|
console.error("BugBear: Error submitting feedback", err);
|
|
757
1059
|
return { success: false, error: message };
|
|
758
1060
|
}
|
|
@@ -1387,6 +1689,7 @@ var BugBearClient = class {
|
|
|
1387
1689
|
* Send a message to a thread
|
|
1388
1690
|
*/
|
|
1389
1691
|
async sendMessage(threadId, content, attachments) {
|
|
1692
|
+
let insertData;
|
|
1390
1693
|
try {
|
|
1391
1694
|
const testerInfo = await this.getTesterInfo();
|
|
1392
1695
|
if (!testerInfo) {
|
|
@@ -1398,7 +1701,7 @@ var BugBearClient = class {
|
|
|
1398
1701
|
console.error("BugBear: Rate limit exceeded for messages");
|
|
1399
1702
|
return false;
|
|
1400
1703
|
}
|
|
1401
|
-
|
|
1704
|
+
insertData = {
|
|
1402
1705
|
thread_id: threadId,
|
|
1403
1706
|
sender_type: "tester",
|
|
1404
1707
|
sender_tester_id: testerInfo.id,
|
|
@@ -1413,12 +1716,21 @@ var BugBearClient = class {
|
|
|
1413
1716
|
}
|
|
1414
1717
|
const { error } = await this.supabase.from("discussion_messages").insert(insertData);
|
|
1415
1718
|
if (error) {
|
|
1719
|
+
if (this._queue && isNetworkError(error.message)) {
|
|
1720
|
+
await this._queue.enqueue("message", insertData);
|
|
1721
|
+
return false;
|
|
1722
|
+
}
|
|
1416
1723
|
console.error("BugBear: Failed to send message", formatPgError(error));
|
|
1417
1724
|
return false;
|
|
1418
1725
|
}
|
|
1419
1726
|
await this.markThreadAsRead(threadId);
|
|
1420
1727
|
return true;
|
|
1421
1728
|
} catch (err) {
|
|
1729
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
1730
|
+
if (this._queue && insertData && isNetworkError(message)) {
|
|
1731
|
+
await this._queue.enqueue("message", insertData);
|
|
1732
|
+
return false;
|
|
1733
|
+
}
|
|
1422
1734
|
console.error("BugBear: Error sending message", err);
|
|
1423
1735
|
return false;
|
|
1424
1736
|
}
|
|
@@ -1754,8 +2066,11 @@ function createBugBear(config) {
|
|
|
1754
2066
|
export {
|
|
1755
2067
|
BUG_CATEGORIES,
|
|
1756
2068
|
BugBearClient,
|
|
2069
|
+
LocalStorageAdapter,
|
|
2070
|
+
OfflineQueue,
|
|
1757
2071
|
captureError,
|
|
1758
2072
|
contextCapture,
|
|
1759
2073
|
createBugBear,
|
|
1760
|
-
isBugCategory
|
|
2074
|
+
isBugCategory,
|
|
2075
|
+
isNetworkError
|
|
1761
2076
|
};
|