@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.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,12 +731,18 @@ 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 [];
|
|
446
738
|
}
|
|
447
|
-
const mapped = (data || []).
|
|
739
|
+
const mapped = (data || []).filter((item) => {
|
|
740
|
+
if (!item.test_case) {
|
|
741
|
+
console.warn("BugBear: Assignment returned without test_case", { id: item.id });
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
return true;
|
|
745
|
+
}).map((item) => ({
|
|
448
746
|
id: item.id,
|
|
449
747
|
status: item.status,
|
|
450
748
|
startedAt: item.started_at,
|
|
@@ -692,6 +990,7 @@ var BugBearClient = class {
|
|
|
692
990
|
* This empowers testers to shape better tests over time
|
|
693
991
|
*/
|
|
694
992
|
async submitTestFeedback(options) {
|
|
993
|
+
let feedbackPayload;
|
|
695
994
|
try {
|
|
696
995
|
const testerInfo = await this.getTesterInfo();
|
|
697
996
|
if (!testerInfo) {
|
|
@@ -701,7 +1000,17 @@ var BugBearClient = class {
|
|
|
701
1000
|
if (feedback.rating < 1 || feedback.rating > 5) {
|
|
702
1001
|
return { success: false, error: "Rating must be between 1 and 5" };
|
|
703
1002
|
}
|
|
704
|
-
const
|
|
1003
|
+
const optionalRatings = [
|
|
1004
|
+
{ name: "clarityRating", value: feedback.clarityRating },
|
|
1005
|
+
{ name: "stepsRating", value: feedback.stepsRating },
|
|
1006
|
+
{ name: "relevanceRating", value: feedback.relevanceRating }
|
|
1007
|
+
];
|
|
1008
|
+
for (const { name, value } of optionalRatings) {
|
|
1009
|
+
if (value !== void 0 && value !== null && (value < 1 || value > 5)) {
|
|
1010
|
+
return { success: false, error: `${name} must be between 1 and 5` };
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
feedbackPayload = {
|
|
705
1014
|
project_id: this.config.projectId,
|
|
706
1015
|
test_case_id: testCaseId,
|
|
707
1016
|
assignment_id: assignmentId || null,
|
|
@@ -719,8 +1028,13 @@ var BugBearClient = class {
|
|
|
719
1028
|
platform: this.getDeviceInfo().platform,
|
|
720
1029
|
time_to_complete_seconds: timeToCompleteSeconds || null,
|
|
721
1030
|
screenshot_urls: screenshotUrls || []
|
|
722
|
-
}
|
|
1031
|
+
};
|
|
1032
|
+
const { error: feedbackError } = await this.supabase.from("test_feedback").insert(feedbackPayload);
|
|
723
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
|
+
}
|
|
724
1038
|
console.error("BugBear: Failed to submit feedback", feedbackError);
|
|
725
1039
|
return { success: false, error: feedbackError.message };
|
|
726
1040
|
}
|
|
@@ -737,6 +1051,10 @@ var BugBearClient = class {
|
|
|
737
1051
|
return { success: true };
|
|
738
1052
|
} catch (err) {
|
|
739
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
|
+
}
|
|
740
1058
|
console.error("BugBear: Error submitting feedback", err);
|
|
741
1059
|
return { success: false, error: message };
|
|
742
1060
|
}
|
|
@@ -1371,6 +1689,7 @@ var BugBearClient = class {
|
|
|
1371
1689
|
* Send a message to a thread
|
|
1372
1690
|
*/
|
|
1373
1691
|
async sendMessage(threadId, content, attachments) {
|
|
1692
|
+
let insertData;
|
|
1374
1693
|
try {
|
|
1375
1694
|
const testerInfo = await this.getTesterInfo();
|
|
1376
1695
|
if (!testerInfo) {
|
|
@@ -1382,7 +1701,7 @@ var BugBearClient = class {
|
|
|
1382
1701
|
console.error("BugBear: Rate limit exceeded for messages");
|
|
1383
1702
|
return false;
|
|
1384
1703
|
}
|
|
1385
|
-
|
|
1704
|
+
insertData = {
|
|
1386
1705
|
thread_id: threadId,
|
|
1387
1706
|
sender_type: "tester",
|
|
1388
1707
|
sender_tester_id: testerInfo.id,
|
|
@@ -1397,12 +1716,21 @@ var BugBearClient = class {
|
|
|
1397
1716
|
}
|
|
1398
1717
|
const { error } = await this.supabase.from("discussion_messages").insert(insertData);
|
|
1399
1718
|
if (error) {
|
|
1719
|
+
if (this._queue && isNetworkError(error.message)) {
|
|
1720
|
+
await this._queue.enqueue("message", insertData);
|
|
1721
|
+
return false;
|
|
1722
|
+
}
|
|
1400
1723
|
console.error("BugBear: Failed to send message", formatPgError(error));
|
|
1401
1724
|
return false;
|
|
1402
1725
|
}
|
|
1403
1726
|
await this.markThreadAsRead(threadId);
|
|
1404
1727
|
return true;
|
|
1405
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
|
+
}
|
|
1406
1734
|
console.error("BugBear: Error sending message", err);
|
|
1407
1735
|
return false;
|
|
1408
1736
|
}
|
|
@@ -1738,8 +2066,11 @@ function createBugBear(config) {
|
|
|
1738
2066
|
export {
|
|
1739
2067
|
BUG_CATEGORIES,
|
|
1740
2068
|
BugBearClient,
|
|
2069
|
+
LocalStorageAdapter,
|
|
2070
|
+
OfflineQueue,
|
|
1741
2071
|
captureError,
|
|
1742
2072
|
contextCapture,
|
|
1743
2073
|
createBugBear,
|
|
1744
|
-
isBugCategory
|
|
2074
|
+
isBugCategory,
|
|
2075
|
+
isNetworkError
|
|
1745
2076
|
};
|