@bbearai/core 0.5.3 → 0.6.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 +65 -6
- package/dist/index.d.ts +65 -6
- package/dist/index.js +166 -27
- package/dist/index.mjs +166 -27
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -132,12 +132,39 @@ interface HostUserInfo {
|
|
|
132
132
|
name?: string;
|
|
133
133
|
}
|
|
134
134
|
interface BugBearConfig {
|
|
135
|
-
/**
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
/**
|
|
136
|
+
* BugBear API key (recommended).
|
|
137
|
+
* When provided, the SDK resolves projectId, supabaseUrl, and supabaseAnonKey
|
|
138
|
+
* automatically from the BugBear API. This is the simplest config path —
|
|
139
|
+
* only one env var needed.
|
|
140
|
+
*
|
|
141
|
+
* Get yours at https://app.bugbear.ai/settings/projects
|
|
142
|
+
*/
|
|
143
|
+
apiKey?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Base URL for the BugBear dashboard API.
|
|
146
|
+
* Defaults to 'https://app.bugbear.ai'. Only change this for
|
|
147
|
+
* self-hosted or local development environments.
|
|
148
|
+
*/
|
|
149
|
+
apiBaseUrl?: string;
|
|
150
|
+
/**
|
|
151
|
+
* Your BugBear project ID.
|
|
152
|
+
* Required when using the explicit config path (without apiKey).
|
|
153
|
+
* Automatically resolved when apiKey is provided.
|
|
154
|
+
*/
|
|
155
|
+
projectId?: string;
|
|
156
|
+
/**
|
|
157
|
+
* Supabase URL for the BugBear backend.
|
|
158
|
+
* Required when using the explicit config path (without apiKey).
|
|
159
|
+
* Automatically resolved when apiKey is provided.
|
|
160
|
+
*/
|
|
161
|
+
supabaseUrl?: string;
|
|
162
|
+
/**
|
|
163
|
+
* Supabase anon key for the BugBear backend.
|
|
164
|
+
* Required when using the explicit config path (without apiKey).
|
|
165
|
+
* Automatically resolved when apiKey is provided.
|
|
166
|
+
*/
|
|
167
|
+
supabaseAnonKey?: string;
|
|
141
168
|
/** Enable voice input */
|
|
142
169
|
enableVoice?: boolean;
|
|
143
170
|
/** Enable screenshot capture */
|
|
@@ -778,7 +805,38 @@ declare class BugBearClient {
|
|
|
778
805
|
private _queue;
|
|
779
806
|
/** Active Realtime channel references for cleanup. */
|
|
780
807
|
private realtimeChannels;
|
|
808
|
+
/**
|
|
809
|
+
* Resolves when the client is ready to make requests.
|
|
810
|
+
* For the explicit config path (supabaseUrl + supabaseAnonKey), this is immediate.
|
|
811
|
+
* For the apiKey path, this resolves after credentials are fetched from /api/v1/config.
|
|
812
|
+
*/
|
|
813
|
+
private pendingInit;
|
|
814
|
+
/** Whether the client has been successfully initialized. */
|
|
815
|
+
private initialized;
|
|
816
|
+
/** Initialization error, if any. */
|
|
817
|
+
private initError;
|
|
781
818
|
constructor(config: BugBearConfig);
|
|
819
|
+
/** Whether the client is ready for requests. */
|
|
820
|
+
get isReady(): boolean;
|
|
821
|
+
/** Wait until the client is ready. Throws if initialization failed. */
|
|
822
|
+
ready(): Promise<void>;
|
|
823
|
+
/**
|
|
824
|
+
* Resolve Supabase credentials from a BugBear API key.
|
|
825
|
+
* Checks localStorage cache first, falls back to /api/v1/config.
|
|
826
|
+
*/
|
|
827
|
+
private resolveFromApiKey;
|
|
828
|
+
/** Apply resolved credentials and create the Supabase client. */
|
|
829
|
+
private applyResolvedConfig;
|
|
830
|
+
/** Initialize offline queue if configured. Shared by both init paths. */
|
|
831
|
+
private initOfflineQueue;
|
|
832
|
+
/** Read cached config from localStorage if available and not expired. */
|
|
833
|
+
private readConfigCache;
|
|
834
|
+
/** Write resolved config to localStorage cache. */
|
|
835
|
+
private writeConfigCache;
|
|
836
|
+
/** Simple string hash for cache keys — avoids storing raw API keys. */
|
|
837
|
+
private hashKey;
|
|
838
|
+
/** Ensure the client is initialized before making requests. */
|
|
839
|
+
private ensureReady;
|
|
782
840
|
/**
|
|
783
841
|
* Access the offline queue (if enabled).
|
|
784
842
|
* Use this to check queue.count, subscribe to changes, or trigger flush.
|
|
@@ -1173,6 +1231,7 @@ declare class ContextCaptureManager {
|
|
|
1173
1231
|
private navigationHistory;
|
|
1174
1232
|
private originalConsole;
|
|
1175
1233
|
private originalFetch?;
|
|
1234
|
+
private fetchHost;
|
|
1176
1235
|
private originalPushState?;
|
|
1177
1236
|
private originalReplaceState?;
|
|
1178
1237
|
private popstateHandler?;
|
package/dist/index.d.ts
CHANGED
|
@@ -132,12 +132,39 @@ interface HostUserInfo {
|
|
|
132
132
|
name?: string;
|
|
133
133
|
}
|
|
134
134
|
interface BugBearConfig {
|
|
135
|
-
/**
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
/**
|
|
136
|
+
* BugBear API key (recommended).
|
|
137
|
+
* When provided, the SDK resolves projectId, supabaseUrl, and supabaseAnonKey
|
|
138
|
+
* automatically from the BugBear API. This is the simplest config path —
|
|
139
|
+
* only one env var needed.
|
|
140
|
+
*
|
|
141
|
+
* Get yours at https://app.bugbear.ai/settings/projects
|
|
142
|
+
*/
|
|
143
|
+
apiKey?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Base URL for the BugBear dashboard API.
|
|
146
|
+
* Defaults to 'https://app.bugbear.ai'. Only change this for
|
|
147
|
+
* self-hosted or local development environments.
|
|
148
|
+
*/
|
|
149
|
+
apiBaseUrl?: string;
|
|
150
|
+
/**
|
|
151
|
+
* Your BugBear project ID.
|
|
152
|
+
* Required when using the explicit config path (without apiKey).
|
|
153
|
+
* Automatically resolved when apiKey is provided.
|
|
154
|
+
*/
|
|
155
|
+
projectId?: string;
|
|
156
|
+
/**
|
|
157
|
+
* Supabase URL for the BugBear backend.
|
|
158
|
+
* Required when using the explicit config path (without apiKey).
|
|
159
|
+
* Automatically resolved when apiKey is provided.
|
|
160
|
+
*/
|
|
161
|
+
supabaseUrl?: string;
|
|
162
|
+
/**
|
|
163
|
+
* Supabase anon key for the BugBear backend.
|
|
164
|
+
* Required when using the explicit config path (without apiKey).
|
|
165
|
+
* Automatically resolved when apiKey is provided.
|
|
166
|
+
*/
|
|
167
|
+
supabaseAnonKey?: string;
|
|
141
168
|
/** Enable voice input */
|
|
142
169
|
enableVoice?: boolean;
|
|
143
170
|
/** Enable screenshot capture */
|
|
@@ -778,7 +805,38 @@ declare class BugBearClient {
|
|
|
778
805
|
private _queue;
|
|
779
806
|
/** Active Realtime channel references for cleanup. */
|
|
780
807
|
private realtimeChannels;
|
|
808
|
+
/**
|
|
809
|
+
* Resolves when the client is ready to make requests.
|
|
810
|
+
* For the explicit config path (supabaseUrl + supabaseAnonKey), this is immediate.
|
|
811
|
+
* For the apiKey path, this resolves after credentials are fetched from /api/v1/config.
|
|
812
|
+
*/
|
|
813
|
+
private pendingInit;
|
|
814
|
+
/** Whether the client has been successfully initialized. */
|
|
815
|
+
private initialized;
|
|
816
|
+
/** Initialization error, if any. */
|
|
817
|
+
private initError;
|
|
781
818
|
constructor(config: BugBearConfig);
|
|
819
|
+
/** Whether the client is ready for requests. */
|
|
820
|
+
get isReady(): boolean;
|
|
821
|
+
/** Wait until the client is ready. Throws if initialization failed. */
|
|
822
|
+
ready(): Promise<void>;
|
|
823
|
+
/**
|
|
824
|
+
* Resolve Supabase credentials from a BugBear API key.
|
|
825
|
+
* Checks localStorage cache first, falls back to /api/v1/config.
|
|
826
|
+
*/
|
|
827
|
+
private resolveFromApiKey;
|
|
828
|
+
/** Apply resolved credentials and create the Supabase client. */
|
|
829
|
+
private applyResolvedConfig;
|
|
830
|
+
/** Initialize offline queue if configured. Shared by both init paths. */
|
|
831
|
+
private initOfflineQueue;
|
|
832
|
+
/** Read cached config from localStorage if available and not expired. */
|
|
833
|
+
private readConfigCache;
|
|
834
|
+
/** Write resolved config to localStorage cache. */
|
|
835
|
+
private writeConfigCache;
|
|
836
|
+
/** Simple string hash for cache keys — avoids storing raw API keys. */
|
|
837
|
+
private hashKey;
|
|
838
|
+
/** Ensure the client is initialized before making requests. */
|
|
839
|
+
private ensureReady;
|
|
782
840
|
/**
|
|
783
841
|
* Access the offline queue (if enabled).
|
|
784
842
|
* Use this to check queue.count, subscribe to changes, or trigger flush.
|
|
@@ -1173,6 +1231,7 @@ declare class ContextCaptureManager {
|
|
|
1173
1231
|
private navigationHistory;
|
|
1174
1232
|
private originalConsole;
|
|
1175
1233
|
private originalFetch?;
|
|
1234
|
+
private fetchHost;
|
|
1176
1235
|
private originalPushState?;
|
|
1177
1236
|
private originalReplaceState?;
|
|
1178
1237
|
private popstateHandler?;
|
package/dist/index.js
CHANGED
|
@@ -52,6 +52,7 @@ var ContextCaptureManager = class {
|
|
|
52
52
|
this.networkRequests = [];
|
|
53
53
|
this.navigationHistory = [];
|
|
54
54
|
this.originalConsole = {};
|
|
55
|
+
this.fetchHost = null;
|
|
55
56
|
this.isCapturing = false;
|
|
56
57
|
}
|
|
57
58
|
/**
|
|
@@ -74,8 +75,9 @@ var ContextCaptureManager = class {
|
|
|
74
75
|
if (this.originalConsole.warn) console.warn = this.originalConsole.warn;
|
|
75
76
|
if (this.originalConsole.error) console.error = this.originalConsole.error;
|
|
76
77
|
if (this.originalConsole.info) console.info = this.originalConsole.info;
|
|
77
|
-
if (this.originalFetch &&
|
|
78
|
-
|
|
78
|
+
if (this.originalFetch && this.fetchHost) {
|
|
79
|
+
this.fetchHost.fetch = this.originalFetch;
|
|
80
|
+
this.fetchHost = null;
|
|
79
81
|
}
|
|
80
82
|
if (typeof window !== "undefined" && typeof history !== "undefined") {
|
|
81
83
|
if (this.originalPushState) {
|
|
@@ -184,15 +186,19 @@ var ContextCaptureManager = class {
|
|
|
184
186
|
});
|
|
185
187
|
}
|
|
186
188
|
captureFetch() {
|
|
187
|
-
if (typeof
|
|
188
|
-
|
|
189
|
+
if (typeof fetch === "undefined") return;
|
|
190
|
+
const host = typeof window !== "undefined" && typeof window.fetch === "function" ? window : typeof globalThis !== "undefined" && typeof globalThis.fetch === "function" ? globalThis : null;
|
|
191
|
+
if (!host) return;
|
|
192
|
+
const canCloneResponse = typeof document !== "undefined";
|
|
193
|
+
this.fetchHost = host;
|
|
194
|
+
this.originalFetch = host.fetch;
|
|
189
195
|
const self = this;
|
|
190
|
-
|
|
196
|
+
host.fetch = async function(input, init) {
|
|
191
197
|
const startTime = Date.now();
|
|
192
198
|
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
193
199
|
const method = init?.method || "GET";
|
|
194
200
|
try {
|
|
195
|
-
const response = await self.originalFetch.call(
|
|
201
|
+
const response = await self.originalFetch.call(host, input, init);
|
|
196
202
|
const requestEntry = {
|
|
197
203
|
method,
|
|
198
204
|
url: url.slice(0, 200),
|
|
@@ -201,7 +207,7 @@ var ContextCaptureManager = class {
|
|
|
201
207
|
duration: Date.now() - startTime,
|
|
202
208
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
203
209
|
};
|
|
204
|
-
if (response.status >= 400) {
|
|
210
|
+
if (canCloneResponse && response.status >= 400) {
|
|
205
211
|
try {
|
|
206
212
|
const cloned = response.clone();
|
|
207
213
|
const body = await cloned.text();
|
|
@@ -264,13 +270,13 @@ var ContextCaptureManager = class {
|
|
|
264
270
|
return Object.keys(metrics).length > 0 ? metrics : void 0;
|
|
265
271
|
}
|
|
266
272
|
getEnvironmentInfo() {
|
|
267
|
-
if (typeof window === "undefined" || typeof navigator === "undefined") return void 0;
|
|
273
|
+
if (typeof window === "undefined" || typeof navigator === "undefined" || typeof document === "undefined") return void 0;
|
|
268
274
|
return {
|
|
269
275
|
language: navigator.language,
|
|
270
276
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
271
|
-
cookiesEnabled: navigator.cookieEnabled,
|
|
277
|
+
cookiesEnabled: navigator.cookieEnabled ?? false,
|
|
272
278
|
localStorage: typeof localStorage !== "undefined",
|
|
273
|
-
online: navigator.onLine
|
|
279
|
+
online: navigator.onLine ?? true
|
|
274
280
|
};
|
|
275
281
|
}
|
|
276
282
|
};
|
|
@@ -449,6 +455,9 @@ var formatPgError = (e) => {
|
|
|
449
455
|
const { message, code, details, hint } = e;
|
|
450
456
|
return { message, code, details, hint };
|
|
451
457
|
};
|
|
458
|
+
var DEFAULT_API_BASE_URL = "https://app.bugbear.ai";
|
|
459
|
+
var CONFIG_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
460
|
+
var CONFIG_CACHE_PREFIX = "bugbear_config_";
|
|
452
461
|
var BugBearClient = class {
|
|
453
462
|
constructor(config) {
|
|
454
463
|
this.navigationHistory = [];
|
|
@@ -458,23 +467,133 @@ var BugBearClient = class {
|
|
|
458
467
|
/** Active Realtime channel references for cleanup. */
|
|
459
468
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
460
469
|
this.realtimeChannels = [];
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
throw new Error("BugBear: supabaseAnonKey is required. Get it from your BugBear project settings.");
|
|
466
|
-
}
|
|
470
|
+
/** Whether the client has been successfully initialized. */
|
|
471
|
+
this.initialized = false;
|
|
472
|
+
/** Initialization error, if any. */
|
|
473
|
+
this.initError = null;
|
|
467
474
|
this.config = config;
|
|
468
|
-
|
|
469
|
-
|
|
475
|
+
if (config.apiKey) {
|
|
476
|
+
this.pendingInit = this.resolveFromApiKey(config.apiKey);
|
|
477
|
+
} else if (config.supabaseUrl && config.supabaseAnonKey) {
|
|
478
|
+
if (!config.projectId) {
|
|
479
|
+
throw new Error(
|
|
480
|
+
"BugBear: projectId is required when using explicit Supabase credentials. Tip: Use apiKey instead for simpler setup \u2014 it resolves everything automatically."
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
this.supabase = (0, import_supabase_js.createClient)(config.supabaseUrl, config.supabaseAnonKey);
|
|
484
|
+
this.initialized = true;
|
|
485
|
+
this.pendingInit = Promise.resolve();
|
|
486
|
+
this.initOfflineQueue();
|
|
487
|
+
} else {
|
|
488
|
+
throw new Error(
|
|
489
|
+
"BugBear: Missing configuration. Provide either:\n \u2022 apiKey (recommended) \u2014 resolves everything automatically\n \u2022 projectId + supabaseUrl + supabaseAnonKey \u2014 explicit credentials\n\nGet your API key at https://app.bugbear.ai/settings/projects"
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
/** Whether the client is ready for requests. */
|
|
494
|
+
get isReady() {
|
|
495
|
+
return this.initialized;
|
|
496
|
+
}
|
|
497
|
+
/** Wait until the client is ready. Throws if initialization failed. */
|
|
498
|
+
async ready() {
|
|
499
|
+
await this.pendingInit;
|
|
500
|
+
if (this.initError) throw this.initError;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Resolve Supabase credentials from a BugBear API key.
|
|
504
|
+
* Checks localStorage cache first, falls back to /api/v1/config.
|
|
505
|
+
*/
|
|
506
|
+
async resolveFromApiKey(apiKey) {
|
|
507
|
+
try {
|
|
508
|
+
const cached = this.readConfigCache(apiKey);
|
|
509
|
+
if (cached) {
|
|
510
|
+
this.applyResolvedConfig(cached);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const baseUrl = (this.config.apiBaseUrl || DEFAULT_API_BASE_URL).replace(/\/$/, "");
|
|
514
|
+
const response = await fetch(`${baseUrl}/api/v1/config`, {
|
|
515
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
516
|
+
});
|
|
517
|
+
if (!response.ok) {
|
|
518
|
+
const body = await response.json().catch(() => ({}));
|
|
519
|
+
const message = body.error || `HTTP ${response.status}`;
|
|
520
|
+
throw new Error(
|
|
521
|
+
`BugBear: Invalid API key \u2014 ${message}. Get yours at https://app.bugbear.ai/settings/projects`
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
const data = await response.json();
|
|
525
|
+
this.writeConfigCache(apiKey, data);
|
|
526
|
+
this.applyResolvedConfig(data);
|
|
527
|
+
} catch (err) {
|
|
528
|
+
this.initError = err instanceof Error ? err : new Error(String(err));
|
|
529
|
+
this.config.onError?.(this.initError, { context: "apikey_resolution_failed" });
|
|
530
|
+
throw this.initError;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
/** Apply resolved credentials and create the Supabase client. */
|
|
534
|
+
applyResolvedConfig(resolved) {
|
|
535
|
+
this.config = {
|
|
536
|
+
...this.config,
|
|
537
|
+
projectId: resolved.projectId,
|
|
538
|
+
supabaseUrl: resolved.supabaseUrl,
|
|
539
|
+
supabaseAnonKey: resolved.supabaseAnonKey
|
|
540
|
+
};
|
|
541
|
+
this.supabase = (0, import_supabase_js.createClient)(resolved.supabaseUrl, resolved.supabaseAnonKey);
|
|
542
|
+
this.initialized = true;
|
|
543
|
+
this.initOfflineQueue();
|
|
544
|
+
}
|
|
545
|
+
/** Initialize offline queue if configured. Shared by both init paths. */
|
|
546
|
+
initOfflineQueue() {
|
|
547
|
+
if (this.config.offlineQueue?.enabled) {
|
|
470
548
|
this._queue = new OfflineQueue({
|
|
471
549
|
enabled: true,
|
|
472
|
-
maxItems: config.offlineQueue.maxItems,
|
|
473
|
-
maxRetries: config.offlineQueue.maxRetries
|
|
550
|
+
maxItems: this.config.offlineQueue.maxItems,
|
|
551
|
+
maxRetries: this.config.offlineQueue.maxRetries
|
|
474
552
|
});
|
|
475
553
|
this.registerQueueHandlers();
|
|
476
554
|
}
|
|
477
555
|
}
|
|
556
|
+
/** Read cached config from localStorage if available and not expired. */
|
|
557
|
+
readConfigCache(apiKey) {
|
|
558
|
+
if (typeof localStorage === "undefined") return null;
|
|
559
|
+
try {
|
|
560
|
+
const key = CONFIG_CACHE_PREFIX + this.hashKey(apiKey);
|
|
561
|
+
const raw = localStorage.getItem(key);
|
|
562
|
+
if (!raw) return null;
|
|
563
|
+
const cached = JSON.parse(raw);
|
|
564
|
+
if (Date.now() - cached.cachedAt > CONFIG_CACHE_TTL_MS) {
|
|
565
|
+
localStorage.removeItem(key);
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
return cached;
|
|
569
|
+
} catch {
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/** Write resolved config to localStorage cache. */
|
|
574
|
+
writeConfigCache(apiKey, data) {
|
|
575
|
+
if (typeof localStorage === "undefined") return;
|
|
576
|
+
try {
|
|
577
|
+
const key = CONFIG_CACHE_PREFIX + this.hashKey(apiKey);
|
|
578
|
+
const cached = { ...data, cachedAt: Date.now() };
|
|
579
|
+
localStorage.setItem(key, JSON.stringify(cached));
|
|
580
|
+
} catch {
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
/** Simple string hash for cache keys — avoids storing raw API keys. */
|
|
584
|
+
hashKey(apiKey) {
|
|
585
|
+
let hash = 0;
|
|
586
|
+
for (let i = 0; i < apiKey.length; i++) {
|
|
587
|
+
hash = (hash << 5) - hash + apiKey.charCodeAt(i) | 0;
|
|
588
|
+
}
|
|
589
|
+
return hash.toString(36);
|
|
590
|
+
}
|
|
591
|
+
/** Ensure the client is initialized before making requests. */
|
|
592
|
+
async ensureReady() {
|
|
593
|
+
if (this.initialized) return;
|
|
594
|
+
await this.pendingInit;
|
|
595
|
+
if (this.initError) throw this.initError;
|
|
596
|
+
}
|
|
478
597
|
// ── Offline Queue ─────────────────────────────────────────
|
|
479
598
|
/**
|
|
480
599
|
* Access the offline queue (if enabled).
|
|
@@ -630,6 +749,7 @@ var BugBearClient = class {
|
|
|
630
749
|
* Get current user info from host app or BugBear's own auth
|
|
631
750
|
*/
|
|
632
751
|
async getCurrentUserInfo() {
|
|
752
|
+
await this.ensureReady();
|
|
633
753
|
if (this.config.getCurrentUser) {
|
|
634
754
|
return await this.config.getCurrentUser();
|
|
635
755
|
}
|
|
@@ -849,6 +969,7 @@ var BugBearClient = class {
|
|
|
849
969
|
*/
|
|
850
970
|
async getAssignment(assignmentId) {
|
|
851
971
|
try {
|
|
972
|
+
await this.ensureReady();
|
|
852
973
|
const { data, error } = await this.supabase.from("test_assignments").select(`
|
|
853
974
|
id,
|
|
854
975
|
status,
|
|
@@ -920,6 +1041,7 @@ var BugBearClient = class {
|
|
|
920
1041
|
*/
|
|
921
1042
|
async updateAssignmentStatus(assignmentId, status, options) {
|
|
922
1043
|
try {
|
|
1044
|
+
await this.ensureReady();
|
|
923
1045
|
const { data: currentAssignment, error: fetchError } = await this.supabase.from("test_assignments").select("status, started_at").eq("id", assignmentId).single();
|
|
924
1046
|
if (fetchError || !currentAssignment) {
|
|
925
1047
|
console.error("BugBear: Assignment not found", {
|
|
@@ -1006,6 +1128,7 @@ var BugBearClient = class {
|
|
|
1006
1128
|
*/
|
|
1007
1129
|
async reopenAssignment(assignmentId) {
|
|
1008
1130
|
try {
|
|
1131
|
+
await this.ensureReady();
|
|
1009
1132
|
const { data: current, error: fetchError } = await this.supabase.from("test_assignments").select("status").eq("id", assignmentId).single();
|
|
1010
1133
|
if (fetchError || !current) {
|
|
1011
1134
|
return { success: false, error: "Assignment not found" };
|
|
@@ -1045,6 +1168,7 @@ var BugBearClient = class {
|
|
|
1045
1168
|
actualNotes = notes;
|
|
1046
1169
|
}
|
|
1047
1170
|
try {
|
|
1171
|
+
await this.ensureReady();
|
|
1048
1172
|
const updateData = {
|
|
1049
1173
|
status: "skipped",
|
|
1050
1174
|
skip_reason: actualReason,
|
|
@@ -1214,6 +1338,7 @@ var BugBearClient = class {
|
|
|
1214
1338
|
*/
|
|
1215
1339
|
async getTesterInfo() {
|
|
1216
1340
|
try {
|
|
1341
|
+
await this.ensureReady();
|
|
1217
1342
|
const userInfo = await this.getCurrentUserInfo();
|
|
1218
1343
|
if (!userInfo?.email) return null;
|
|
1219
1344
|
if (!this.isValidEmail(userInfo.email)) {
|
|
@@ -1505,6 +1630,7 @@ var BugBearClient = class {
|
|
|
1505
1630
|
*/
|
|
1506
1631
|
async isQAEnabled() {
|
|
1507
1632
|
try {
|
|
1633
|
+
await this.ensureReady();
|
|
1508
1634
|
const { data, error } = await this.supabase.rpc("check_qa_enabled", {
|
|
1509
1635
|
p_project_id: this.config.projectId
|
|
1510
1636
|
});
|
|
@@ -1536,6 +1662,7 @@ var BugBearClient = class {
|
|
|
1536
1662
|
*/
|
|
1537
1663
|
async uploadScreenshot(file, filename, bucket = "screenshots") {
|
|
1538
1664
|
try {
|
|
1665
|
+
await this.ensureReady();
|
|
1539
1666
|
const contentType = file.type || "image/png";
|
|
1540
1667
|
const ext = contentType.includes("png") ? "png" : "jpg";
|
|
1541
1668
|
const name = filename || `screenshot-${Date.now()}.${ext}`;
|
|
@@ -1576,6 +1703,7 @@ var BugBearClient = class {
|
|
|
1576
1703
|
*/
|
|
1577
1704
|
async uploadImageFromUri(uri, filename, bucket = "screenshots") {
|
|
1578
1705
|
try {
|
|
1706
|
+
await this.ensureReady();
|
|
1579
1707
|
const response = await fetch(uri);
|
|
1580
1708
|
const blob = await response.blob();
|
|
1581
1709
|
const contentType = blob.type || "image/jpeg";
|
|
@@ -1621,15 +1749,18 @@ var BugBearClient = class {
|
|
|
1621
1749
|
* Get device info (override in platform-specific implementations)
|
|
1622
1750
|
*/
|
|
1623
1751
|
getDeviceInfo() {
|
|
1624
|
-
if (typeof window !== "undefined") {
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
userAgent
|
|
1628
|
-
|
|
1752
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
1753
|
+
const info = { platform: "web" };
|
|
1754
|
+
if (typeof navigator !== "undefined" && navigator.userAgent) {
|
|
1755
|
+
info.userAgent = navigator.userAgent;
|
|
1756
|
+
}
|
|
1757
|
+
if (window.screen) {
|
|
1758
|
+
info.screenSize = {
|
|
1629
1759
|
width: window.screen.width,
|
|
1630
1760
|
height: window.screen.height
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
return info;
|
|
1633
1764
|
}
|
|
1634
1765
|
return { platform: "web" };
|
|
1635
1766
|
}
|
|
@@ -1667,6 +1798,7 @@ var BugBearClient = class {
|
|
|
1667
1798
|
*/
|
|
1668
1799
|
async getFixRequests(options) {
|
|
1669
1800
|
try {
|
|
1801
|
+
await this.ensureReady();
|
|
1670
1802
|
let query = this.supabase.from("fix_requests").select("*").eq("project_id", this.config.projectId).order("created_at", { ascending: false }).limit(options?.limit || 20);
|
|
1671
1803
|
if (options?.status) {
|
|
1672
1804
|
query = query.eq("status", options.status);
|
|
@@ -1738,6 +1870,7 @@ var BugBearClient = class {
|
|
|
1738
1870
|
*/
|
|
1739
1871
|
async getThreadMessages(threadId) {
|
|
1740
1872
|
try {
|
|
1873
|
+
await this.ensureReady();
|
|
1741
1874
|
const { data, error } = await this.supabase.from("discussion_messages").select(`
|
|
1742
1875
|
id,
|
|
1743
1876
|
thread_id,
|
|
@@ -1940,6 +2073,7 @@ var BugBearClient = class {
|
|
|
1940
2073
|
*/
|
|
1941
2074
|
async endSession(sessionId, options = {}) {
|
|
1942
2075
|
try {
|
|
2076
|
+
await this.ensureReady();
|
|
1943
2077
|
const { data, error } = await this.supabase.rpc("end_qa_session", {
|
|
1944
2078
|
p_session_id: sessionId,
|
|
1945
2079
|
p_notes: options.notes || null,
|
|
@@ -1977,6 +2111,7 @@ var BugBearClient = class {
|
|
|
1977
2111
|
*/
|
|
1978
2112
|
async getSession(sessionId) {
|
|
1979
2113
|
try {
|
|
2114
|
+
await this.ensureReady();
|
|
1980
2115
|
const { data, error } = await this.supabase.from("qa_sessions").select("*").eq("id", sessionId).single();
|
|
1981
2116
|
if (error || !data) return null;
|
|
1982
2117
|
return this.transformSession(data);
|
|
@@ -2008,6 +2143,7 @@ var BugBearClient = class {
|
|
|
2008
2143
|
*/
|
|
2009
2144
|
async addFinding(sessionId, options) {
|
|
2010
2145
|
try {
|
|
2146
|
+
await this.ensureReady();
|
|
2011
2147
|
const { data, error } = await this.supabase.rpc("add_session_finding", {
|
|
2012
2148
|
p_session_id: sessionId,
|
|
2013
2149
|
p_type: options.type,
|
|
@@ -2038,6 +2174,7 @@ var BugBearClient = class {
|
|
|
2038
2174
|
*/
|
|
2039
2175
|
async getSessionFindings(sessionId) {
|
|
2040
2176
|
try {
|
|
2177
|
+
await this.ensureReady();
|
|
2041
2178
|
const { data, error } = await this.supabase.from("qa_findings").select("*").eq("session_id", sessionId).order("created_at", { ascending: true }).limit(100);
|
|
2042
2179
|
if (error) {
|
|
2043
2180
|
console.error("BugBear: Failed to fetch findings", formatPgError(error));
|
|
@@ -2054,6 +2191,7 @@ var BugBearClient = class {
|
|
|
2054
2191
|
*/
|
|
2055
2192
|
async convertFindingToBug(findingId) {
|
|
2056
2193
|
try {
|
|
2194
|
+
await this.ensureReady();
|
|
2057
2195
|
const { data, error } = await this.supabase.rpc("convert_finding_to_bug", {
|
|
2058
2196
|
p_finding_id: findingId
|
|
2059
2197
|
});
|
|
@@ -2073,6 +2211,7 @@ var BugBearClient = class {
|
|
|
2073
2211
|
*/
|
|
2074
2212
|
async dismissFinding(findingId, reason) {
|
|
2075
2213
|
try {
|
|
2214
|
+
await this.ensureReady();
|
|
2076
2215
|
const { error } = await this.supabase.from("qa_findings").update({
|
|
2077
2216
|
dismissed: true,
|
|
2078
2217
|
dismissed_reason: reason || null,
|
package/dist/index.mjs
CHANGED
|
@@ -18,6 +18,7 @@ var ContextCaptureManager = class {
|
|
|
18
18
|
this.networkRequests = [];
|
|
19
19
|
this.navigationHistory = [];
|
|
20
20
|
this.originalConsole = {};
|
|
21
|
+
this.fetchHost = null;
|
|
21
22
|
this.isCapturing = false;
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
@@ -40,8 +41,9 @@ var ContextCaptureManager = class {
|
|
|
40
41
|
if (this.originalConsole.warn) console.warn = this.originalConsole.warn;
|
|
41
42
|
if (this.originalConsole.error) console.error = this.originalConsole.error;
|
|
42
43
|
if (this.originalConsole.info) console.info = this.originalConsole.info;
|
|
43
|
-
if (this.originalFetch &&
|
|
44
|
-
|
|
44
|
+
if (this.originalFetch && this.fetchHost) {
|
|
45
|
+
this.fetchHost.fetch = this.originalFetch;
|
|
46
|
+
this.fetchHost = null;
|
|
45
47
|
}
|
|
46
48
|
if (typeof window !== "undefined" && typeof history !== "undefined") {
|
|
47
49
|
if (this.originalPushState) {
|
|
@@ -150,15 +152,19 @@ var ContextCaptureManager = class {
|
|
|
150
152
|
});
|
|
151
153
|
}
|
|
152
154
|
captureFetch() {
|
|
153
|
-
if (typeof
|
|
154
|
-
|
|
155
|
+
if (typeof fetch === "undefined") return;
|
|
156
|
+
const host = typeof window !== "undefined" && typeof window.fetch === "function" ? window : typeof globalThis !== "undefined" && typeof globalThis.fetch === "function" ? globalThis : null;
|
|
157
|
+
if (!host) return;
|
|
158
|
+
const canCloneResponse = typeof document !== "undefined";
|
|
159
|
+
this.fetchHost = host;
|
|
160
|
+
this.originalFetch = host.fetch;
|
|
155
161
|
const self = this;
|
|
156
|
-
|
|
162
|
+
host.fetch = async function(input, init) {
|
|
157
163
|
const startTime = Date.now();
|
|
158
164
|
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
159
165
|
const method = init?.method || "GET";
|
|
160
166
|
try {
|
|
161
|
-
const response = await self.originalFetch.call(
|
|
167
|
+
const response = await self.originalFetch.call(host, input, init);
|
|
162
168
|
const requestEntry = {
|
|
163
169
|
method,
|
|
164
170
|
url: url.slice(0, 200),
|
|
@@ -167,7 +173,7 @@ var ContextCaptureManager = class {
|
|
|
167
173
|
duration: Date.now() - startTime,
|
|
168
174
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
169
175
|
};
|
|
170
|
-
if (response.status >= 400) {
|
|
176
|
+
if (canCloneResponse && response.status >= 400) {
|
|
171
177
|
try {
|
|
172
178
|
const cloned = response.clone();
|
|
173
179
|
const body = await cloned.text();
|
|
@@ -230,13 +236,13 @@ var ContextCaptureManager = class {
|
|
|
230
236
|
return Object.keys(metrics).length > 0 ? metrics : void 0;
|
|
231
237
|
}
|
|
232
238
|
getEnvironmentInfo() {
|
|
233
|
-
if (typeof window === "undefined" || typeof navigator === "undefined") return void 0;
|
|
239
|
+
if (typeof window === "undefined" || typeof navigator === "undefined" || typeof document === "undefined") return void 0;
|
|
234
240
|
return {
|
|
235
241
|
language: navigator.language,
|
|
236
242
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
237
|
-
cookiesEnabled: navigator.cookieEnabled,
|
|
243
|
+
cookiesEnabled: navigator.cookieEnabled ?? false,
|
|
238
244
|
localStorage: typeof localStorage !== "undefined",
|
|
239
|
-
online: navigator.onLine
|
|
245
|
+
online: navigator.onLine ?? true
|
|
240
246
|
};
|
|
241
247
|
}
|
|
242
248
|
};
|
|
@@ -415,6 +421,9 @@ var formatPgError = (e) => {
|
|
|
415
421
|
const { message, code, details, hint } = e;
|
|
416
422
|
return { message, code, details, hint };
|
|
417
423
|
};
|
|
424
|
+
var DEFAULT_API_BASE_URL = "https://app.bugbear.ai";
|
|
425
|
+
var CONFIG_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
426
|
+
var CONFIG_CACHE_PREFIX = "bugbear_config_";
|
|
418
427
|
var BugBearClient = class {
|
|
419
428
|
constructor(config) {
|
|
420
429
|
this.navigationHistory = [];
|
|
@@ -424,23 +433,133 @@ var BugBearClient = class {
|
|
|
424
433
|
/** Active Realtime channel references for cleanup. */
|
|
425
434
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
426
435
|
this.realtimeChannels = [];
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
throw new Error("BugBear: supabaseAnonKey is required. Get it from your BugBear project settings.");
|
|
432
|
-
}
|
|
436
|
+
/** Whether the client has been successfully initialized. */
|
|
437
|
+
this.initialized = false;
|
|
438
|
+
/** Initialization error, if any. */
|
|
439
|
+
this.initError = null;
|
|
433
440
|
this.config = config;
|
|
434
|
-
|
|
435
|
-
|
|
441
|
+
if (config.apiKey) {
|
|
442
|
+
this.pendingInit = this.resolveFromApiKey(config.apiKey);
|
|
443
|
+
} else if (config.supabaseUrl && config.supabaseAnonKey) {
|
|
444
|
+
if (!config.projectId) {
|
|
445
|
+
throw new Error(
|
|
446
|
+
"BugBear: projectId is required when using explicit Supabase credentials. Tip: Use apiKey instead for simpler setup \u2014 it resolves everything automatically."
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
this.supabase = createClient(config.supabaseUrl, config.supabaseAnonKey);
|
|
450
|
+
this.initialized = true;
|
|
451
|
+
this.pendingInit = Promise.resolve();
|
|
452
|
+
this.initOfflineQueue();
|
|
453
|
+
} else {
|
|
454
|
+
throw new Error(
|
|
455
|
+
"BugBear: Missing configuration. Provide either:\n \u2022 apiKey (recommended) \u2014 resolves everything automatically\n \u2022 projectId + supabaseUrl + supabaseAnonKey \u2014 explicit credentials\n\nGet your API key at https://app.bugbear.ai/settings/projects"
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
/** Whether the client is ready for requests. */
|
|
460
|
+
get isReady() {
|
|
461
|
+
return this.initialized;
|
|
462
|
+
}
|
|
463
|
+
/** Wait until the client is ready. Throws if initialization failed. */
|
|
464
|
+
async ready() {
|
|
465
|
+
await this.pendingInit;
|
|
466
|
+
if (this.initError) throw this.initError;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Resolve Supabase credentials from a BugBear API key.
|
|
470
|
+
* Checks localStorage cache first, falls back to /api/v1/config.
|
|
471
|
+
*/
|
|
472
|
+
async resolveFromApiKey(apiKey) {
|
|
473
|
+
try {
|
|
474
|
+
const cached = this.readConfigCache(apiKey);
|
|
475
|
+
if (cached) {
|
|
476
|
+
this.applyResolvedConfig(cached);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
const baseUrl = (this.config.apiBaseUrl || DEFAULT_API_BASE_URL).replace(/\/$/, "");
|
|
480
|
+
const response = await fetch(`${baseUrl}/api/v1/config`, {
|
|
481
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
482
|
+
});
|
|
483
|
+
if (!response.ok) {
|
|
484
|
+
const body = await response.json().catch(() => ({}));
|
|
485
|
+
const message = body.error || `HTTP ${response.status}`;
|
|
486
|
+
throw new Error(
|
|
487
|
+
`BugBear: Invalid API key \u2014 ${message}. Get yours at https://app.bugbear.ai/settings/projects`
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
const data = await response.json();
|
|
491
|
+
this.writeConfigCache(apiKey, data);
|
|
492
|
+
this.applyResolvedConfig(data);
|
|
493
|
+
} catch (err) {
|
|
494
|
+
this.initError = err instanceof Error ? err : new Error(String(err));
|
|
495
|
+
this.config.onError?.(this.initError, { context: "apikey_resolution_failed" });
|
|
496
|
+
throw this.initError;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/** Apply resolved credentials and create the Supabase client. */
|
|
500
|
+
applyResolvedConfig(resolved) {
|
|
501
|
+
this.config = {
|
|
502
|
+
...this.config,
|
|
503
|
+
projectId: resolved.projectId,
|
|
504
|
+
supabaseUrl: resolved.supabaseUrl,
|
|
505
|
+
supabaseAnonKey: resolved.supabaseAnonKey
|
|
506
|
+
};
|
|
507
|
+
this.supabase = createClient(resolved.supabaseUrl, resolved.supabaseAnonKey);
|
|
508
|
+
this.initialized = true;
|
|
509
|
+
this.initOfflineQueue();
|
|
510
|
+
}
|
|
511
|
+
/** Initialize offline queue if configured. Shared by both init paths. */
|
|
512
|
+
initOfflineQueue() {
|
|
513
|
+
if (this.config.offlineQueue?.enabled) {
|
|
436
514
|
this._queue = new OfflineQueue({
|
|
437
515
|
enabled: true,
|
|
438
|
-
maxItems: config.offlineQueue.maxItems,
|
|
439
|
-
maxRetries: config.offlineQueue.maxRetries
|
|
516
|
+
maxItems: this.config.offlineQueue.maxItems,
|
|
517
|
+
maxRetries: this.config.offlineQueue.maxRetries
|
|
440
518
|
});
|
|
441
519
|
this.registerQueueHandlers();
|
|
442
520
|
}
|
|
443
521
|
}
|
|
522
|
+
/** Read cached config from localStorage if available and not expired. */
|
|
523
|
+
readConfigCache(apiKey) {
|
|
524
|
+
if (typeof localStorage === "undefined") return null;
|
|
525
|
+
try {
|
|
526
|
+
const key = CONFIG_CACHE_PREFIX + this.hashKey(apiKey);
|
|
527
|
+
const raw = localStorage.getItem(key);
|
|
528
|
+
if (!raw) return null;
|
|
529
|
+
const cached = JSON.parse(raw);
|
|
530
|
+
if (Date.now() - cached.cachedAt > CONFIG_CACHE_TTL_MS) {
|
|
531
|
+
localStorage.removeItem(key);
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
return cached;
|
|
535
|
+
} catch {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
/** Write resolved config to localStorage cache. */
|
|
540
|
+
writeConfigCache(apiKey, data) {
|
|
541
|
+
if (typeof localStorage === "undefined") return;
|
|
542
|
+
try {
|
|
543
|
+
const key = CONFIG_CACHE_PREFIX + this.hashKey(apiKey);
|
|
544
|
+
const cached = { ...data, cachedAt: Date.now() };
|
|
545
|
+
localStorage.setItem(key, JSON.stringify(cached));
|
|
546
|
+
} catch {
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
/** Simple string hash for cache keys — avoids storing raw API keys. */
|
|
550
|
+
hashKey(apiKey) {
|
|
551
|
+
let hash = 0;
|
|
552
|
+
for (let i = 0; i < apiKey.length; i++) {
|
|
553
|
+
hash = (hash << 5) - hash + apiKey.charCodeAt(i) | 0;
|
|
554
|
+
}
|
|
555
|
+
return hash.toString(36);
|
|
556
|
+
}
|
|
557
|
+
/** Ensure the client is initialized before making requests. */
|
|
558
|
+
async ensureReady() {
|
|
559
|
+
if (this.initialized) return;
|
|
560
|
+
await this.pendingInit;
|
|
561
|
+
if (this.initError) throw this.initError;
|
|
562
|
+
}
|
|
444
563
|
// ── Offline Queue ─────────────────────────────────────────
|
|
445
564
|
/**
|
|
446
565
|
* Access the offline queue (if enabled).
|
|
@@ -596,6 +715,7 @@ var BugBearClient = class {
|
|
|
596
715
|
* Get current user info from host app or BugBear's own auth
|
|
597
716
|
*/
|
|
598
717
|
async getCurrentUserInfo() {
|
|
718
|
+
await this.ensureReady();
|
|
599
719
|
if (this.config.getCurrentUser) {
|
|
600
720
|
return await this.config.getCurrentUser();
|
|
601
721
|
}
|
|
@@ -815,6 +935,7 @@ var BugBearClient = class {
|
|
|
815
935
|
*/
|
|
816
936
|
async getAssignment(assignmentId) {
|
|
817
937
|
try {
|
|
938
|
+
await this.ensureReady();
|
|
818
939
|
const { data, error } = await this.supabase.from("test_assignments").select(`
|
|
819
940
|
id,
|
|
820
941
|
status,
|
|
@@ -886,6 +1007,7 @@ var BugBearClient = class {
|
|
|
886
1007
|
*/
|
|
887
1008
|
async updateAssignmentStatus(assignmentId, status, options) {
|
|
888
1009
|
try {
|
|
1010
|
+
await this.ensureReady();
|
|
889
1011
|
const { data: currentAssignment, error: fetchError } = await this.supabase.from("test_assignments").select("status, started_at").eq("id", assignmentId).single();
|
|
890
1012
|
if (fetchError || !currentAssignment) {
|
|
891
1013
|
console.error("BugBear: Assignment not found", {
|
|
@@ -972,6 +1094,7 @@ var BugBearClient = class {
|
|
|
972
1094
|
*/
|
|
973
1095
|
async reopenAssignment(assignmentId) {
|
|
974
1096
|
try {
|
|
1097
|
+
await this.ensureReady();
|
|
975
1098
|
const { data: current, error: fetchError } = await this.supabase.from("test_assignments").select("status").eq("id", assignmentId).single();
|
|
976
1099
|
if (fetchError || !current) {
|
|
977
1100
|
return { success: false, error: "Assignment not found" };
|
|
@@ -1011,6 +1134,7 @@ var BugBearClient = class {
|
|
|
1011
1134
|
actualNotes = notes;
|
|
1012
1135
|
}
|
|
1013
1136
|
try {
|
|
1137
|
+
await this.ensureReady();
|
|
1014
1138
|
const updateData = {
|
|
1015
1139
|
status: "skipped",
|
|
1016
1140
|
skip_reason: actualReason,
|
|
@@ -1180,6 +1304,7 @@ var BugBearClient = class {
|
|
|
1180
1304
|
*/
|
|
1181
1305
|
async getTesterInfo() {
|
|
1182
1306
|
try {
|
|
1307
|
+
await this.ensureReady();
|
|
1183
1308
|
const userInfo = await this.getCurrentUserInfo();
|
|
1184
1309
|
if (!userInfo?.email) return null;
|
|
1185
1310
|
if (!this.isValidEmail(userInfo.email)) {
|
|
@@ -1471,6 +1596,7 @@ var BugBearClient = class {
|
|
|
1471
1596
|
*/
|
|
1472
1597
|
async isQAEnabled() {
|
|
1473
1598
|
try {
|
|
1599
|
+
await this.ensureReady();
|
|
1474
1600
|
const { data, error } = await this.supabase.rpc("check_qa_enabled", {
|
|
1475
1601
|
p_project_id: this.config.projectId
|
|
1476
1602
|
});
|
|
@@ -1502,6 +1628,7 @@ var BugBearClient = class {
|
|
|
1502
1628
|
*/
|
|
1503
1629
|
async uploadScreenshot(file, filename, bucket = "screenshots") {
|
|
1504
1630
|
try {
|
|
1631
|
+
await this.ensureReady();
|
|
1505
1632
|
const contentType = file.type || "image/png";
|
|
1506
1633
|
const ext = contentType.includes("png") ? "png" : "jpg";
|
|
1507
1634
|
const name = filename || `screenshot-${Date.now()}.${ext}`;
|
|
@@ -1542,6 +1669,7 @@ var BugBearClient = class {
|
|
|
1542
1669
|
*/
|
|
1543
1670
|
async uploadImageFromUri(uri, filename, bucket = "screenshots") {
|
|
1544
1671
|
try {
|
|
1672
|
+
await this.ensureReady();
|
|
1545
1673
|
const response = await fetch(uri);
|
|
1546
1674
|
const blob = await response.blob();
|
|
1547
1675
|
const contentType = blob.type || "image/jpeg";
|
|
@@ -1587,15 +1715,18 @@ var BugBearClient = class {
|
|
|
1587
1715
|
* Get device info (override in platform-specific implementations)
|
|
1588
1716
|
*/
|
|
1589
1717
|
getDeviceInfo() {
|
|
1590
|
-
if (typeof window !== "undefined") {
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
userAgent
|
|
1594
|
-
|
|
1718
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
1719
|
+
const info = { platform: "web" };
|
|
1720
|
+
if (typeof navigator !== "undefined" && navigator.userAgent) {
|
|
1721
|
+
info.userAgent = navigator.userAgent;
|
|
1722
|
+
}
|
|
1723
|
+
if (window.screen) {
|
|
1724
|
+
info.screenSize = {
|
|
1595
1725
|
width: window.screen.width,
|
|
1596
1726
|
height: window.screen.height
|
|
1597
|
-
}
|
|
1598
|
-
}
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
return info;
|
|
1599
1730
|
}
|
|
1600
1731
|
return { platform: "web" };
|
|
1601
1732
|
}
|
|
@@ -1633,6 +1764,7 @@ var BugBearClient = class {
|
|
|
1633
1764
|
*/
|
|
1634
1765
|
async getFixRequests(options) {
|
|
1635
1766
|
try {
|
|
1767
|
+
await this.ensureReady();
|
|
1636
1768
|
let query = this.supabase.from("fix_requests").select("*").eq("project_id", this.config.projectId).order("created_at", { ascending: false }).limit(options?.limit || 20);
|
|
1637
1769
|
if (options?.status) {
|
|
1638
1770
|
query = query.eq("status", options.status);
|
|
@@ -1704,6 +1836,7 @@ var BugBearClient = class {
|
|
|
1704
1836
|
*/
|
|
1705
1837
|
async getThreadMessages(threadId) {
|
|
1706
1838
|
try {
|
|
1839
|
+
await this.ensureReady();
|
|
1707
1840
|
const { data, error } = await this.supabase.from("discussion_messages").select(`
|
|
1708
1841
|
id,
|
|
1709
1842
|
thread_id,
|
|
@@ -1906,6 +2039,7 @@ var BugBearClient = class {
|
|
|
1906
2039
|
*/
|
|
1907
2040
|
async endSession(sessionId, options = {}) {
|
|
1908
2041
|
try {
|
|
2042
|
+
await this.ensureReady();
|
|
1909
2043
|
const { data, error } = await this.supabase.rpc("end_qa_session", {
|
|
1910
2044
|
p_session_id: sessionId,
|
|
1911
2045
|
p_notes: options.notes || null,
|
|
@@ -1943,6 +2077,7 @@ var BugBearClient = class {
|
|
|
1943
2077
|
*/
|
|
1944
2078
|
async getSession(sessionId) {
|
|
1945
2079
|
try {
|
|
2080
|
+
await this.ensureReady();
|
|
1946
2081
|
const { data, error } = await this.supabase.from("qa_sessions").select("*").eq("id", sessionId).single();
|
|
1947
2082
|
if (error || !data) return null;
|
|
1948
2083
|
return this.transformSession(data);
|
|
@@ -1974,6 +2109,7 @@ var BugBearClient = class {
|
|
|
1974
2109
|
*/
|
|
1975
2110
|
async addFinding(sessionId, options) {
|
|
1976
2111
|
try {
|
|
2112
|
+
await this.ensureReady();
|
|
1977
2113
|
const { data, error } = await this.supabase.rpc("add_session_finding", {
|
|
1978
2114
|
p_session_id: sessionId,
|
|
1979
2115
|
p_type: options.type,
|
|
@@ -2004,6 +2140,7 @@ var BugBearClient = class {
|
|
|
2004
2140
|
*/
|
|
2005
2141
|
async getSessionFindings(sessionId) {
|
|
2006
2142
|
try {
|
|
2143
|
+
await this.ensureReady();
|
|
2007
2144
|
const { data, error } = await this.supabase.from("qa_findings").select("*").eq("session_id", sessionId).order("created_at", { ascending: true }).limit(100);
|
|
2008
2145
|
if (error) {
|
|
2009
2146
|
console.error("BugBear: Failed to fetch findings", formatPgError(error));
|
|
@@ -2020,6 +2157,7 @@ var BugBearClient = class {
|
|
|
2020
2157
|
*/
|
|
2021
2158
|
async convertFindingToBug(findingId) {
|
|
2022
2159
|
try {
|
|
2160
|
+
await this.ensureReady();
|
|
2023
2161
|
const { data, error } = await this.supabase.rpc("convert_finding_to_bug", {
|
|
2024
2162
|
p_finding_id: findingId
|
|
2025
2163
|
});
|
|
@@ -2039,6 +2177,7 @@ var BugBearClient = class {
|
|
|
2039
2177
|
*/
|
|
2040
2178
|
async dismissFinding(findingId, reason) {
|
|
2041
2179
|
try {
|
|
2180
|
+
await this.ensureReady();
|
|
2042
2181
|
const { error } = await this.supabase.from("qa_findings").update({
|
|
2043
2182
|
dismissed: true,
|
|
2044
2183
|
dismissed_reason: reason || null,
|