@bbearai/core 0.1.5 → 0.2.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 +224 -1
- package/dist/index.d.ts +224 -1
- package/dist/index.js +185 -7
- package/dist/index.mjs +185 -7
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -217,9 +217,24 @@ interface TesterInfo {
|
|
|
217
217
|
id: string;
|
|
218
218
|
name: string;
|
|
219
219
|
email: string;
|
|
220
|
+
/** Additional email addresses for testing on different accounts */
|
|
221
|
+
additionalEmails: string[];
|
|
222
|
+
/** URL to profile photo/avatar */
|
|
223
|
+
avatarUrl?: string;
|
|
224
|
+
/** Testing platforms (ios, android, web) */
|
|
225
|
+
platforms: string[];
|
|
220
226
|
assignedTests: number;
|
|
221
227
|
completedTests: number;
|
|
222
228
|
}
|
|
229
|
+
interface TesterProfileUpdate {
|
|
230
|
+
name?: string;
|
|
231
|
+
/** Additional email addresses for testing */
|
|
232
|
+
additionalEmails?: string[];
|
|
233
|
+
/** URL to profile photo/avatar */
|
|
234
|
+
avatarUrl?: string;
|
|
235
|
+
/** Testing platforms */
|
|
236
|
+
platforms?: string[];
|
|
237
|
+
}
|
|
223
238
|
type ThreadType = 'announcement' | 'direct' | 'report' | 'general_note';
|
|
224
239
|
type ThreadPriority = 'low' | 'normal' | 'high' | 'urgent';
|
|
225
240
|
type MessageSenderType = 'admin' | 'tester';
|
|
@@ -248,6 +263,185 @@ interface TesterMessage {
|
|
|
248
263
|
name: string;
|
|
249
264
|
}>;
|
|
250
265
|
}
|
|
266
|
+
/** Priority factors breakdown for a route */
|
|
267
|
+
interface PriorityFactors {
|
|
268
|
+
bugFrequency: {
|
|
269
|
+
score: number;
|
|
270
|
+
openBugs: number;
|
|
271
|
+
bugs7d: number;
|
|
272
|
+
};
|
|
273
|
+
criticalSeverity: {
|
|
274
|
+
score: number;
|
|
275
|
+
critical: number;
|
|
276
|
+
high: number;
|
|
277
|
+
};
|
|
278
|
+
staleness: {
|
|
279
|
+
score: number;
|
|
280
|
+
daysSinceTest: number | null;
|
|
281
|
+
};
|
|
282
|
+
coverageGap: {
|
|
283
|
+
score: number;
|
|
284
|
+
testCount: number;
|
|
285
|
+
};
|
|
286
|
+
regressionRisk: {
|
|
287
|
+
score: number;
|
|
288
|
+
regressionCount: number;
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
/** Route priority with stats and recommendations */
|
|
292
|
+
interface RoutePriority {
|
|
293
|
+
rank: number;
|
|
294
|
+
route: string;
|
|
295
|
+
priorityScore: number;
|
|
296
|
+
urgency: 'critical' | 'high' | 'medium' | 'low';
|
|
297
|
+
stats: {
|
|
298
|
+
openBugs: number;
|
|
299
|
+
criticalBugs: number;
|
|
300
|
+
highBugs: number;
|
|
301
|
+
testCases: number;
|
|
302
|
+
daysSinceTest: number | null;
|
|
303
|
+
regressions: number;
|
|
304
|
+
recentBugs: number;
|
|
305
|
+
};
|
|
306
|
+
factors?: PriorityFactors;
|
|
307
|
+
recommendation?: string;
|
|
308
|
+
}
|
|
309
|
+
/** Coverage gap identification */
|
|
310
|
+
interface CoverageGap {
|
|
311
|
+
route: string;
|
|
312
|
+
severity: 'critical' | 'high' | 'medium';
|
|
313
|
+
type: 'untested' | 'missing_tracks' | 'stale';
|
|
314
|
+
details: {
|
|
315
|
+
missingTracks?: string[];
|
|
316
|
+
daysSinceTest?: number;
|
|
317
|
+
openBugs?: number;
|
|
318
|
+
criticalBugs?: number;
|
|
319
|
+
};
|
|
320
|
+
recommendation: string;
|
|
321
|
+
}
|
|
322
|
+
/** Regression event tracking */
|
|
323
|
+
interface RegressionEvent {
|
|
324
|
+
id: string;
|
|
325
|
+
route: string;
|
|
326
|
+
severity: 'critical' | 'high' | 'medium';
|
|
327
|
+
originalBug: {
|
|
328
|
+
id: string;
|
|
329
|
+
description: string;
|
|
330
|
+
resolvedAt: string;
|
|
331
|
+
};
|
|
332
|
+
newBugs: Array<{
|
|
333
|
+
id: string;
|
|
334
|
+
description: string;
|
|
335
|
+
severity: string;
|
|
336
|
+
createdAt: string;
|
|
337
|
+
}>;
|
|
338
|
+
daysSinceResolution: number;
|
|
339
|
+
regressionCount: number;
|
|
340
|
+
detectedAt: string;
|
|
341
|
+
}
|
|
342
|
+
/** Deploy checklist item */
|
|
343
|
+
interface ChecklistItem {
|
|
344
|
+
testCaseId?: string;
|
|
345
|
+
testKey?: string;
|
|
346
|
+
title: string;
|
|
347
|
+
route: string;
|
|
348
|
+
reason: string;
|
|
349
|
+
priority: 'P0' | 'P1' | 'P2' | 'P3';
|
|
350
|
+
lastTested?: string;
|
|
351
|
+
hasCriticalBugs?: boolean;
|
|
352
|
+
}
|
|
353
|
+
/** Deploy checklist grouped by urgency */
|
|
354
|
+
interface DeployChecklist {
|
|
355
|
+
critical: ChecklistItem[];
|
|
356
|
+
recommended: ChecklistItem[];
|
|
357
|
+
optional: ChecklistItem[];
|
|
358
|
+
gaps: ChecklistItem[];
|
|
359
|
+
}
|
|
360
|
+
/** QA Health metrics with trends */
|
|
361
|
+
interface QAHealthMetrics {
|
|
362
|
+
velocity: {
|
|
363
|
+
testsPerWeek: number;
|
|
364
|
+
testsCompleted: number;
|
|
365
|
+
trend: 'up' | 'down' | 'stable';
|
|
366
|
+
changePercent?: number;
|
|
367
|
+
};
|
|
368
|
+
bugDiscovery: {
|
|
369
|
+
bugsFound: number;
|
|
370
|
+
bugsPerTest: number;
|
|
371
|
+
criticalBugs: number;
|
|
372
|
+
trend: 'up' | 'down' | 'stable';
|
|
373
|
+
changePercent?: number;
|
|
374
|
+
};
|
|
375
|
+
resolution: {
|
|
376
|
+
bugsResolved: number;
|
|
377
|
+
avgResolutionDays: number;
|
|
378
|
+
trend: 'up' | 'down' | 'stable';
|
|
379
|
+
changePercent?: number;
|
|
380
|
+
};
|
|
381
|
+
coverage: {
|
|
382
|
+
routeCoverage: number;
|
|
383
|
+
routesWithTests: number;
|
|
384
|
+
totalRoutes: number;
|
|
385
|
+
};
|
|
386
|
+
testerHealth: {
|
|
387
|
+
activeTesters: number;
|
|
388
|
+
totalTesters: number;
|
|
389
|
+
utilizationPercent: number;
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
/** QA Health score with grade */
|
|
393
|
+
interface QAHealthScore {
|
|
394
|
+
score: number;
|
|
395
|
+
grade: 'A' | 'B' | 'C' | 'D' | 'F';
|
|
396
|
+
breakdown: {
|
|
397
|
+
coverage: number;
|
|
398
|
+
velocity: number;
|
|
399
|
+
resolution: number;
|
|
400
|
+
stability: number;
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
/** Aggregated route test statistics */
|
|
404
|
+
interface RouteTestStats {
|
|
405
|
+
id: string;
|
|
406
|
+
projectId: string;
|
|
407
|
+
route: string;
|
|
408
|
+
totalBugs: number;
|
|
409
|
+
openBugs: number;
|
|
410
|
+
criticalBugs: number;
|
|
411
|
+
highBugs: number;
|
|
412
|
+
resolvedBugs: number;
|
|
413
|
+
testCaseCount: number;
|
|
414
|
+
testsByTrack: Record<string, number>;
|
|
415
|
+
lastTestedAt: string | null;
|
|
416
|
+
totalTestExecutions: number;
|
|
417
|
+
passCount: number;
|
|
418
|
+
failCount: number;
|
|
419
|
+
passRate: number | null;
|
|
420
|
+
regressionCount: number;
|
|
421
|
+
lastRegressionAt: string | null;
|
|
422
|
+
bugsLast7Days: number;
|
|
423
|
+
bugsLast30Days: number;
|
|
424
|
+
priorityScore: number;
|
|
425
|
+
calculatedAt: string;
|
|
426
|
+
}
|
|
427
|
+
/** Coverage matrix cell */
|
|
428
|
+
interface CoverageMatrixCell {
|
|
429
|
+
testCount: number;
|
|
430
|
+
passCount: number;
|
|
431
|
+
failCount: number;
|
|
432
|
+
passRate: number | null;
|
|
433
|
+
lastTestedAt: string | null;
|
|
434
|
+
staleDays: number | null;
|
|
435
|
+
}
|
|
436
|
+
/** Coverage matrix row (route × tracks) */
|
|
437
|
+
interface CoverageMatrixRow {
|
|
438
|
+
route: string;
|
|
439
|
+
totalTests: number;
|
|
440
|
+
openBugs: number;
|
|
441
|
+
criticalBugs: number;
|
|
442
|
+
lastTestedAt: string | null;
|
|
443
|
+
tracks: Record<string, CoverageMatrixCell>;
|
|
444
|
+
}
|
|
251
445
|
|
|
252
446
|
/**
|
|
253
447
|
* BugBear Client
|
|
@@ -289,8 +483,37 @@ declare class BugBearClient {
|
|
|
289
483
|
/**
|
|
290
484
|
* Get current tester info
|
|
291
485
|
* Looks up tester by email from the host app's authenticated user
|
|
486
|
+
* Checks both primary email AND additional_emails array
|
|
487
|
+
* Uses parameterized RPC function to prevent SQL injection
|
|
292
488
|
*/
|
|
293
489
|
getTesterInfo(): Promise<TesterInfo | null>;
|
|
490
|
+
/**
|
|
491
|
+
* Basic email format validation (defense in depth)
|
|
492
|
+
*/
|
|
493
|
+
private isValidEmail;
|
|
494
|
+
/**
|
|
495
|
+
* Validate report input before submission
|
|
496
|
+
* Returns error message if invalid, null if valid
|
|
497
|
+
*/
|
|
498
|
+
private validateReport;
|
|
499
|
+
/**
|
|
500
|
+
* Validate profile update input
|
|
501
|
+
* Returns error message if invalid, null if valid
|
|
502
|
+
*/
|
|
503
|
+
private validateProfileUpdate;
|
|
504
|
+
/**
|
|
505
|
+
* Check rate limit for an action
|
|
506
|
+
* Returns { allowed: boolean, error?: string, remaining?: number }
|
|
507
|
+
*/
|
|
508
|
+
private checkRateLimit;
|
|
509
|
+
/**
|
|
510
|
+
* Update tester profile
|
|
511
|
+
* Allows testers to update their name, additional emails, avatar, and platforms
|
|
512
|
+
*/
|
|
513
|
+
updateTesterProfile(updates: TesterProfileUpdate): Promise<{
|
|
514
|
+
success: boolean;
|
|
515
|
+
error?: string;
|
|
516
|
+
}>;
|
|
294
517
|
/**
|
|
295
518
|
* Check if current user is a tester for this project
|
|
296
519
|
*/
|
|
@@ -444,4 +667,4 @@ declare function captureError(error: Error, errorInfo?: {
|
|
|
444
667
|
componentStack?: string;
|
|
445
668
|
};
|
|
446
669
|
|
|
447
|
-
export { type AppContext, BugBearClient, type BugBearConfig, type BugBearReport, type BugBearTheme, type ChecklistResult, type ConsoleLogEntry, type DeviceInfo, type EnhancedBugContext, type HostUserInfo, type MessageSenderType, type NetworkRequest, type QATrack, type ReportStatus, type ReportType, type RubricMode, type RubricResult, type Severity, type TestAssignment, type TestResult, type TestStep, type TestTemplate, type TesterInfo, type TesterMessage, type TesterThread, type ThreadPriority, type ThreadType, captureError, contextCapture, createBugBear };
|
|
670
|
+
export { type AppContext, BugBearClient, type BugBearConfig, type BugBearReport, type BugBearTheme, type ChecklistItem, type ChecklistResult, type ConsoleLogEntry, type CoverageGap, type CoverageMatrixCell, type CoverageMatrixRow, type DeployChecklist, type DeviceInfo, type EnhancedBugContext, type HostUserInfo, type MessageSenderType, type NetworkRequest, type PriorityFactors, type QAHealthMetrics, type QAHealthScore, type QATrack, type RegressionEvent, type ReportStatus, type ReportType, type RoutePriority, type RouteTestStats, type RubricMode, type RubricResult, type Severity, type TestAssignment, type TestResult, type TestStep, type TestTemplate, type TesterInfo, type TesterMessage, type TesterProfileUpdate, type TesterThread, type ThreadPriority, type ThreadType, captureError, contextCapture, createBugBear };
|
package/dist/index.d.ts
CHANGED
|
@@ -217,9 +217,24 @@ interface TesterInfo {
|
|
|
217
217
|
id: string;
|
|
218
218
|
name: string;
|
|
219
219
|
email: string;
|
|
220
|
+
/** Additional email addresses for testing on different accounts */
|
|
221
|
+
additionalEmails: string[];
|
|
222
|
+
/** URL to profile photo/avatar */
|
|
223
|
+
avatarUrl?: string;
|
|
224
|
+
/** Testing platforms (ios, android, web) */
|
|
225
|
+
platforms: string[];
|
|
220
226
|
assignedTests: number;
|
|
221
227
|
completedTests: number;
|
|
222
228
|
}
|
|
229
|
+
interface TesterProfileUpdate {
|
|
230
|
+
name?: string;
|
|
231
|
+
/** Additional email addresses for testing */
|
|
232
|
+
additionalEmails?: string[];
|
|
233
|
+
/** URL to profile photo/avatar */
|
|
234
|
+
avatarUrl?: string;
|
|
235
|
+
/** Testing platforms */
|
|
236
|
+
platforms?: string[];
|
|
237
|
+
}
|
|
223
238
|
type ThreadType = 'announcement' | 'direct' | 'report' | 'general_note';
|
|
224
239
|
type ThreadPriority = 'low' | 'normal' | 'high' | 'urgent';
|
|
225
240
|
type MessageSenderType = 'admin' | 'tester';
|
|
@@ -248,6 +263,185 @@ interface TesterMessage {
|
|
|
248
263
|
name: string;
|
|
249
264
|
}>;
|
|
250
265
|
}
|
|
266
|
+
/** Priority factors breakdown for a route */
|
|
267
|
+
interface PriorityFactors {
|
|
268
|
+
bugFrequency: {
|
|
269
|
+
score: number;
|
|
270
|
+
openBugs: number;
|
|
271
|
+
bugs7d: number;
|
|
272
|
+
};
|
|
273
|
+
criticalSeverity: {
|
|
274
|
+
score: number;
|
|
275
|
+
critical: number;
|
|
276
|
+
high: number;
|
|
277
|
+
};
|
|
278
|
+
staleness: {
|
|
279
|
+
score: number;
|
|
280
|
+
daysSinceTest: number | null;
|
|
281
|
+
};
|
|
282
|
+
coverageGap: {
|
|
283
|
+
score: number;
|
|
284
|
+
testCount: number;
|
|
285
|
+
};
|
|
286
|
+
regressionRisk: {
|
|
287
|
+
score: number;
|
|
288
|
+
regressionCount: number;
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
/** Route priority with stats and recommendations */
|
|
292
|
+
interface RoutePriority {
|
|
293
|
+
rank: number;
|
|
294
|
+
route: string;
|
|
295
|
+
priorityScore: number;
|
|
296
|
+
urgency: 'critical' | 'high' | 'medium' | 'low';
|
|
297
|
+
stats: {
|
|
298
|
+
openBugs: number;
|
|
299
|
+
criticalBugs: number;
|
|
300
|
+
highBugs: number;
|
|
301
|
+
testCases: number;
|
|
302
|
+
daysSinceTest: number | null;
|
|
303
|
+
regressions: number;
|
|
304
|
+
recentBugs: number;
|
|
305
|
+
};
|
|
306
|
+
factors?: PriorityFactors;
|
|
307
|
+
recommendation?: string;
|
|
308
|
+
}
|
|
309
|
+
/** Coverage gap identification */
|
|
310
|
+
interface CoverageGap {
|
|
311
|
+
route: string;
|
|
312
|
+
severity: 'critical' | 'high' | 'medium';
|
|
313
|
+
type: 'untested' | 'missing_tracks' | 'stale';
|
|
314
|
+
details: {
|
|
315
|
+
missingTracks?: string[];
|
|
316
|
+
daysSinceTest?: number;
|
|
317
|
+
openBugs?: number;
|
|
318
|
+
criticalBugs?: number;
|
|
319
|
+
};
|
|
320
|
+
recommendation: string;
|
|
321
|
+
}
|
|
322
|
+
/** Regression event tracking */
|
|
323
|
+
interface RegressionEvent {
|
|
324
|
+
id: string;
|
|
325
|
+
route: string;
|
|
326
|
+
severity: 'critical' | 'high' | 'medium';
|
|
327
|
+
originalBug: {
|
|
328
|
+
id: string;
|
|
329
|
+
description: string;
|
|
330
|
+
resolvedAt: string;
|
|
331
|
+
};
|
|
332
|
+
newBugs: Array<{
|
|
333
|
+
id: string;
|
|
334
|
+
description: string;
|
|
335
|
+
severity: string;
|
|
336
|
+
createdAt: string;
|
|
337
|
+
}>;
|
|
338
|
+
daysSinceResolution: number;
|
|
339
|
+
regressionCount: number;
|
|
340
|
+
detectedAt: string;
|
|
341
|
+
}
|
|
342
|
+
/** Deploy checklist item */
|
|
343
|
+
interface ChecklistItem {
|
|
344
|
+
testCaseId?: string;
|
|
345
|
+
testKey?: string;
|
|
346
|
+
title: string;
|
|
347
|
+
route: string;
|
|
348
|
+
reason: string;
|
|
349
|
+
priority: 'P0' | 'P1' | 'P2' | 'P3';
|
|
350
|
+
lastTested?: string;
|
|
351
|
+
hasCriticalBugs?: boolean;
|
|
352
|
+
}
|
|
353
|
+
/** Deploy checklist grouped by urgency */
|
|
354
|
+
interface DeployChecklist {
|
|
355
|
+
critical: ChecklistItem[];
|
|
356
|
+
recommended: ChecklistItem[];
|
|
357
|
+
optional: ChecklistItem[];
|
|
358
|
+
gaps: ChecklistItem[];
|
|
359
|
+
}
|
|
360
|
+
/** QA Health metrics with trends */
|
|
361
|
+
interface QAHealthMetrics {
|
|
362
|
+
velocity: {
|
|
363
|
+
testsPerWeek: number;
|
|
364
|
+
testsCompleted: number;
|
|
365
|
+
trend: 'up' | 'down' | 'stable';
|
|
366
|
+
changePercent?: number;
|
|
367
|
+
};
|
|
368
|
+
bugDiscovery: {
|
|
369
|
+
bugsFound: number;
|
|
370
|
+
bugsPerTest: number;
|
|
371
|
+
criticalBugs: number;
|
|
372
|
+
trend: 'up' | 'down' | 'stable';
|
|
373
|
+
changePercent?: number;
|
|
374
|
+
};
|
|
375
|
+
resolution: {
|
|
376
|
+
bugsResolved: number;
|
|
377
|
+
avgResolutionDays: number;
|
|
378
|
+
trend: 'up' | 'down' | 'stable';
|
|
379
|
+
changePercent?: number;
|
|
380
|
+
};
|
|
381
|
+
coverage: {
|
|
382
|
+
routeCoverage: number;
|
|
383
|
+
routesWithTests: number;
|
|
384
|
+
totalRoutes: number;
|
|
385
|
+
};
|
|
386
|
+
testerHealth: {
|
|
387
|
+
activeTesters: number;
|
|
388
|
+
totalTesters: number;
|
|
389
|
+
utilizationPercent: number;
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
/** QA Health score with grade */
|
|
393
|
+
interface QAHealthScore {
|
|
394
|
+
score: number;
|
|
395
|
+
grade: 'A' | 'B' | 'C' | 'D' | 'F';
|
|
396
|
+
breakdown: {
|
|
397
|
+
coverage: number;
|
|
398
|
+
velocity: number;
|
|
399
|
+
resolution: number;
|
|
400
|
+
stability: number;
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
/** Aggregated route test statistics */
|
|
404
|
+
interface RouteTestStats {
|
|
405
|
+
id: string;
|
|
406
|
+
projectId: string;
|
|
407
|
+
route: string;
|
|
408
|
+
totalBugs: number;
|
|
409
|
+
openBugs: number;
|
|
410
|
+
criticalBugs: number;
|
|
411
|
+
highBugs: number;
|
|
412
|
+
resolvedBugs: number;
|
|
413
|
+
testCaseCount: number;
|
|
414
|
+
testsByTrack: Record<string, number>;
|
|
415
|
+
lastTestedAt: string | null;
|
|
416
|
+
totalTestExecutions: number;
|
|
417
|
+
passCount: number;
|
|
418
|
+
failCount: number;
|
|
419
|
+
passRate: number | null;
|
|
420
|
+
regressionCount: number;
|
|
421
|
+
lastRegressionAt: string | null;
|
|
422
|
+
bugsLast7Days: number;
|
|
423
|
+
bugsLast30Days: number;
|
|
424
|
+
priorityScore: number;
|
|
425
|
+
calculatedAt: string;
|
|
426
|
+
}
|
|
427
|
+
/** Coverage matrix cell */
|
|
428
|
+
interface CoverageMatrixCell {
|
|
429
|
+
testCount: number;
|
|
430
|
+
passCount: number;
|
|
431
|
+
failCount: number;
|
|
432
|
+
passRate: number | null;
|
|
433
|
+
lastTestedAt: string | null;
|
|
434
|
+
staleDays: number | null;
|
|
435
|
+
}
|
|
436
|
+
/** Coverage matrix row (route × tracks) */
|
|
437
|
+
interface CoverageMatrixRow {
|
|
438
|
+
route: string;
|
|
439
|
+
totalTests: number;
|
|
440
|
+
openBugs: number;
|
|
441
|
+
criticalBugs: number;
|
|
442
|
+
lastTestedAt: string | null;
|
|
443
|
+
tracks: Record<string, CoverageMatrixCell>;
|
|
444
|
+
}
|
|
251
445
|
|
|
252
446
|
/**
|
|
253
447
|
* BugBear Client
|
|
@@ -289,8 +483,37 @@ declare class BugBearClient {
|
|
|
289
483
|
/**
|
|
290
484
|
* Get current tester info
|
|
291
485
|
* Looks up tester by email from the host app's authenticated user
|
|
486
|
+
* Checks both primary email AND additional_emails array
|
|
487
|
+
* Uses parameterized RPC function to prevent SQL injection
|
|
292
488
|
*/
|
|
293
489
|
getTesterInfo(): Promise<TesterInfo | null>;
|
|
490
|
+
/**
|
|
491
|
+
* Basic email format validation (defense in depth)
|
|
492
|
+
*/
|
|
493
|
+
private isValidEmail;
|
|
494
|
+
/**
|
|
495
|
+
* Validate report input before submission
|
|
496
|
+
* Returns error message if invalid, null if valid
|
|
497
|
+
*/
|
|
498
|
+
private validateReport;
|
|
499
|
+
/**
|
|
500
|
+
* Validate profile update input
|
|
501
|
+
* Returns error message if invalid, null if valid
|
|
502
|
+
*/
|
|
503
|
+
private validateProfileUpdate;
|
|
504
|
+
/**
|
|
505
|
+
* Check rate limit for an action
|
|
506
|
+
* Returns { allowed: boolean, error?: string, remaining?: number }
|
|
507
|
+
*/
|
|
508
|
+
private checkRateLimit;
|
|
509
|
+
/**
|
|
510
|
+
* Update tester profile
|
|
511
|
+
* Allows testers to update their name, additional emails, avatar, and platforms
|
|
512
|
+
*/
|
|
513
|
+
updateTesterProfile(updates: TesterProfileUpdate): Promise<{
|
|
514
|
+
success: boolean;
|
|
515
|
+
error?: string;
|
|
516
|
+
}>;
|
|
294
517
|
/**
|
|
295
518
|
* Check if current user is a tester for this project
|
|
296
519
|
*/
|
|
@@ -444,4 +667,4 @@ declare function captureError(error: Error, errorInfo?: {
|
|
|
444
667
|
componentStack?: string;
|
|
445
668
|
};
|
|
446
669
|
|
|
447
|
-
export { type AppContext, BugBearClient, type BugBearConfig, type BugBearReport, type BugBearTheme, type ChecklistResult, type ConsoleLogEntry, type DeviceInfo, type EnhancedBugContext, type HostUserInfo, type MessageSenderType, type NetworkRequest, type QATrack, type ReportStatus, type ReportType, type RubricMode, type RubricResult, type Severity, type TestAssignment, type TestResult, type TestStep, type TestTemplate, type TesterInfo, type TesterMessage, type TesterThread, type ThreadPriority, type ThreadType, captureError, contextCapture, createBugBear };
|
|
670
|
+
export { type AppContext, BugBearClient, type BugBearConfig, type BugBearReport, type BugBearTheme, type ChecklistItem, type ChecklistResult, type ConsoleLogEntry, type CoverageGap, type CoverageMatrixCell, type CoverageMatrixRow, type DeployChecklist, type DeviceInfo, type EnhancedBugContext, type HostUserInfo, type MessageSenderType, type NetworkRequest, type PriorityFactors, type QAHealthMetrics, type QAHealthScore, type QATrack, type RegressionEvent, type ReportStatus, type ReportType, type RoutePriority, type RouteTestStats, type RubricMode, type RubricResult, type Severity, type TestAssignment, type TestResult, type TestStep, type TestTemplate, type TesterInfo, type TesterMessage, type TesterProfileUpdate, type TesterThread, type ThreadPriority, type ThreadType, captureError, contextCapture, createBugBear };
|
package/dist/index.js
CHANGED
|
@@ -86,7 +86,16 @@ var BugBearClient = class {
|
|
|
86
86
|
*/
|
|
87
87
|
async submitReport(report) {
|
|
88
88
|
try {
|
|
89
|
+
const validationError = this.validateReport(report);
|
|
90
|
+
if (validationError) {
|
|
91
|
+
return { success: false, error: validationError };
|
|
92
|
+
}
|
|
89
93
|
const userInfo = await this.getCurrentUserInfo();
|
|
94
|
+
const rateLimitId = userInfo?.email || this.config.projectId;
|
|
95
|
+
const rateLimit = await this.checkRateLimit(rateLimitId, "report_submit");
|
|
96
|
+
if (!rateLimit.allowed) {
|
|
97
|
+
return { success: false, error: rateLimit.error };
|
|
98
|
+
}
|
|
90
99
|
if (!userInfo) {
|
|
91
100
|
console.error("BugBear: No user info available, cannot submit report");
|
|
92
101
|
return { success: false, error: "User not authenticated" };
|
|
@@ -192,25 +201,189 @@ var BugBearClient = class {
|
|
|
192
201
|
/**
|
|
193
202
|
* Get current tester info
|
|
194
203
|
* Looks up tester by email from the host app's authenticated user
|
|
204
|
+
* Checks both primary email AND additional_emails array
|
|
205
|
+
* Uses parameterized RPC function to prevent SQL injection
|
|
195
206
|
*/
|
|
196
207
|
async getTesterInfo() {
|
|
197
208
|
try {
|
|
198
209
|
const userInfo = await this.getCurrentUserInfo();
|
|
199
|
-
if (!userInfo) return null;
|
|
200
|
-
|
|
210
|
+
if (!userInfo?.email) return null;
|
|
211
|
+
if (!this.isValidEmail(userInfo.email)) {
|
|
212
|
+
console.warn("BugBear: Invalid email format");
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
const { data, error } = await this.supabase.rpc("lookup_tester_by_email", {
|
|
216
|
+
p_project_id: this.config.projectId,
|
|
217
|
+
p_email: userInfo.email
|
|
218
|
+
}).maybeSingle();
|
|
201
219
|
if (error || !data) return null;
|
|
220
|
+
const tester = data;
|
|
202
221
|
return {
|
|
203
|
-
id:
|
|
204
|
-
name:
|
|
205
|
-
email:
|
|
206
|
-
|
|
207
|
-
|
|
222
|
+
id: tester.id,
|
|
223
|
+
name: tester.name,
|
|
224
|
+
email: tester.email,
|
|
225
|
+
additionalEmails: tester.additional_emails || [],
|
|
226
|
+
avatarUrl: tester.avatar_url || void 0,
|
|
227
|
+
platforms: tester.platforms || [],
|
|
228
|
+
assignedTests: tester.assigned_count || 0,
|
|
229
|
+
completedTests: tester.completed_count || 0
|
|
208
230
|
};
|
|
209
231
|
} catch (err) {
|
|
210
232
|
console.error("BugBear: getTesterInfo error", err);
|
|
211
233
|
return null;
|
|
212
234
|
}
|
|
213
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Basic email format validation (defense in depth)
|
|
238
|
+
*/
|
|
239
|
+
isValidEmail(email) {
|
|
240
|
+
if (!email || email.length > 254) return false;
|
|
241
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
242
|
+
return emailRegex.test(email);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Validate report input before submission
|
|
246
|
+
* Returns error message if invalid, null if valid
|
|
247
|
+
*/
|
|
248
|
+
validateReport(report) {
|
|
249
|
+
const validTypes = ["bug", "feedback", "suggestion", "test_pass", "test_fail"];
|
|
250
|
+
if (report.type && !validTypes.includes(report.type)) {
|
|
251
|
+
return `Invalid report type: ${report.type}. Must be one of: ${validTypes.join(", ")}`;
|
|
252
|
+
}
|
|
253
|
+
const validSeverities = ["critical", "high", "medium", "low"];
|
|
254
|
+
if (report.severity && !validSeverities.includes(report.severity)) {
|
|
255
|
+
return `Invalid severity: ${report.severity}. Must be one of: ${validSeverities.join(", ")}`;
|
|
256
|
+
}
|
|
257
|
+
if (report.title && report.title.length > 500) {
|
|
258
|
+
return "Title must be 500 characters or less";
|
|
259
|
+
}
|
|
260
|
+
if (report.description && report.description.length > 1e4) {
|
|
261
|
+
return "Description must be 10,000 characters or less";
|
|
262
|
+
}
|
|
263
|
+
if (report.screenshots && Array.isArray(report.screenshots)) {
|
|
264
|
+
if (report.screenshots.length > 10) {
|
|
265
|
+
return "Maximum 10 screenshots allowed";
|
|
266
|
+
}
|
|
267
|
+
for (const url of report.screenshots) {
|
|
268
|
+
if (typeof url !== "string" || url.length > 2e3) {
|
|
269
|
+
return "Invalid screenshot URL";
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Validate profile update input
|
|
277
|
+
* Returns error message if invalid, null if valid
|
|
278
|
+
*/
|
|
279
|
+
validateProfileUpdate(updates) {
|
|
280
|
+
if (updates.name !== void 0) {
|
|
281
|
+
if (typeof updates.name !== "string" || updates.name.length > 100) {
|
|
282
|
+
return "Name must be 100 characters or less";
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (updates.additionalEmails !== void 0) {
|
|
286
|
+
if (!Array.isArray(updates.additionalEmails)) {
|
|
287
|
+
return "Additional emails must be an array";
|
|
288
|
+
}
|
|
289
|
+
if (updates.additionalEmails.length > 5) {
|
|
290
|
+
return "Maximum 5 additional emails allowed";
|
|
291
|
+
}
|
|
292
|
+
for (const email of updates.additionalEmails) {
|
|
293
|
+
if (!this.isValidEmail(email)) {
|
|
294
|
+
return `Invalid email format: ${email}`;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (updates.avatarUrl !== void 0 && updates.avatarUrl !== null) {
|
|
299
|
+
if (typeof updates.avatarUrl !== "string" || updates.avatarUrl.length > 2e3) {
|
|
300
|
+
return "Invalid avatar URL";
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (updates.platforms !== void 0) {
|
|
304
|
+
if (!Array.isArray(updates.platforms)) {
|
|
305
|
+
return "Platforms must be an array";
|
|
306
|
+
}
|
|
307
|
+
const validPlatforms = ["ios", "android", "web", "desktop", "other"];
|
|
308
|
+
for (const platform of updates.platforms) {
|
|
309
|
+
if (!validPlatforms.includes(platform)) {
|
|
310
|
+
return `Invalid platform: ${platform}. Must be one of: ${validPlatforms.join(", ")}`;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Check rate limit for an action
|
|
318
|
+
* Returns { allowed: boolean, error?: string, remaining?: number }
|
|
319
|
+
*/
|
|
320
|
+
async checkRateLimit(identifier, action) {
|
|
321
|
+
try {
|
|
322
|
+
const { data, error } = await this.supabase.rpc("check_rate_limit", {
|
|
323
|
+
p_identifier: identifier,
|
|
324
|
+
p_action: action,
|
|
325
|
+
p_project_id: this.config.projectId
|
|
326
|
+
});
|
|
327
|
+
if (error) {
|
|
328
|
+
console.warn("BugBear: Rate limit check failed, allowing request", error.message);
|
|
329
|
+
return { allowed: true };
|
|
330
|
+
}
|
|
331
|
+
if (!data.allowed) {
|
|
332
|
+
return {
|
|
333
|
+
allowed: false,
|
|
334
|
+
error: `Rate limit exceeded. Try again in ${Math.ceil((new Date(data.reset_at).getTime() - Date.now()) / 1e3)} seconds.`,
|
|
335
|
+
remaining: 0,
|
|
336
|
+
resetAt: data.reset_at
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
allowed: true,
|
|
341
|
+
remaining: data.remaining,
|
|
342
|
+
resetAt: data.reset_at
|
|
343
|
+
};
|
|
344
|
+
} catch (err) {
|
|
345
|
+
console.warn("BugBear: Rate limit check error", err);
|
|
346
|
+
return { allowed: true };
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Update tester profile
|
|
351
|
+
* Allows testers to update their name, additional emails, avatar, and platforms
|
|
352
|
+
*/
|
|
353
|
+
async updateTesterProfile(updates) {
|
|
354
|
+
try {
|
|
355
|
+
const validationError = this.validateProfileUpdate(updates);
|
|
356
|
+
if (validationError) {
|
|
357
|
+
return { success: false, error: validationError };
|
|
358
|
+
}
|
|
359
|
+
const userInfo = await this.getCurrentUserInfo();
|
|
360
|
+
if (!userInfo) {
|
|
361
|
+
return { success: false, error: "Not authenticated" };
|
|
362
|
+
}
|
|
363
|
+
const rateLimit = await this.checkRateLimit(userInfo.email, "profile_update");
|
|
364
|
+
if (!rateLimit.allowed) {
|
|
365
|
+
return { success: false, error: rateLimit.error };
|
|
366
|
+
}
|
|
367
|
+
const testerInfo = await this.getTesterInfo();
|
|
368
|
+
if (!testerInfo) {
|
|
369
|
+
return { success: false, error: "Not a registered tester" };
|
|
370
|
+
}
|
|
371
|
+
const updateData = {};
|
|
372
|
+
if (updates.name !== void 0) updateData.name = updates.name;
|
|
373
|
+
if (updates.additionalEmails !== void 0) updateData.additional_emails = updates.additionalEmails;
|
|
374
|
+
if (updates.avatarUrl !== void 0) updateData.avatar_url = updates.avatarUrl;
|
|
375
|
+
if (updates.platforms !== void 0) updateData.platforms = updates.platforms;
|
|
376
|
+
const { error } = await this.supabase.from("testers").update(updateData).eq("id", testerInfo.id);
|
|
377
|
+
if (error) {
|
|
378
|
+
console.error("BugBear: updateTesterProfile error", error);
|
|
379
|
+
return { success: false, error: error.message };
|
|
380
|
+
}
|
|
381
|
+
return { success: true };
|
|
382
|
+
} catch (err) {
|
|
383
|
+
console.error("BugBear: updateTesterProfile error", err);
|
|
384
|
+
return { success: false, error: "Failed to update profile" };
|
|
385
|
+
}
|
|
386
|
+
}
|
|
214
387
|
/**
|
|
215
388
|
* Check if current user is a tester for this project
|
|
216
389
|
*/
|
|
@@ -476,6 +649,11 @@ var BugBearClient = class {
|
|
|
476
649
|
console.error("BugBear: No tester info, cannot send message");
|
|
477
650
|
return false;
|
|
478
651
|
}
|
|
652
|
+
const rateLimit = await this.checkRateLimit(testerInfo.email, "message_send");
|
|
653
|
+
if (!rateLimit.allowed) {
|
|
654
|
+
console.error("BugBear: Rate limit exceeded for messages");
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
479
657
|
const { error } = await this.supabase.from("discussion_messages").insert({
|
|
480
658
|
thread_id: threadId,
|
|
481
659
|
sender_type: "tester",
|
package/dist/index.mjs
CHANGED
|
@@ -57,7 +57,16 @@ var BugBearClient = class {
|
|
|
57
57
|
*/
|
|
58
58
|
async submitReport(report) {
|
|
59
59
|
try {
|
|
60
|
+
const validationError = this.validateReport(report);
|
|
61
|
+
if (validationError) {
|
|
62
|
+
return { success: false, error: validationError };
|
|
63
|
+
}
|
|
60
64
|
const userInfo = await this.getCurrentUserInfo();
|
|
65
|
+
const rateLimitId = userInfo?.email || this.config.projectId;
|
|
66
|
+
const rateLimit = await this.checkRateLimit(rateLimitId, "report_submit");
|
|
67
|
+
if (!rateLimit.allowed) {
|
|
68
|
+
return { success: false, error: rateLimit.error };
|
|
69
|
+
}
|
|
61
70
|
if (!userInfo) {
|
|
62
71
|
console.error("BugBear: No user info available, cannot submit report");
|
|
63
72
|
return { success: false, error: "User not authenticated" };
|
|
@@ -163,25 +172,189 @@ var BugBearClient = class {
|
|
|
163
172
|
/**
|
|
164
173
|
* Get current tester info
|
|
165
174
|
* Looks up tester by email from the host app's authenticated user
|
|
175
|
+
* Checks both primary email AND additional_emails array
|
|
176
|
+
* Uses parameterized RPC function to prevent SQL injection
|
|
166
177
|
*/
|
|
167
178
|
async getTesterInfo() {
|
|
168
179
|
try {
|
|
169
180
|
const userInfo = await this.getCurrentUserInfo();
|
|
170
|
-
if (!userInfo) return null;
|
|
171
|
-
|
|
181
|
+
if (!userInfo?.email) return null;
|
|
182
|
+
if (!this.isValidEmail(userInfo.email)) {
|
|
183
|
+
console.warn("BugBear: Invalid email format");
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
const { data, error } = await this.supabase.rpc("lookup_tester_by_email", {
|
|
187
|
+
p_project_id: this.config.projectId,
|
|
188
|
+
p_email: userInfo.email
|
|
189
|
+
}).maybeSingle();
|
|
172
190
|
if (error || !data) return null;
|
|
191
|
+
const tester = data;
|
|
173
192
|
return {
|
|
174
|
-
id:
|
|
175
|
-
name:
|
|
176
|
-
email:
|
|
177
|
-
|
|
178
|
-
|
|
193
|
+
id: tester.id,
|
|
194
|
+
name: tester.name,
|
|
195
|
+
email: tester.email,
|
|
196
|
+
additionalEmails: tester.additional_emails || [],
|
|
197
|
+
avatarUrl: tester.avatar_url || void 0,
|
|
198
|
+
platforms: tester.platforms || [],
|
|
199
|
+
assignedTests: tester.assigned_count || 0,
|
|
200
|
+
completedTests: tester.completed_count || 0
|
|
179
201
|
};
|
|
180
202
|
} catch (err) {
|
|
181
203
|
console.error("BugBear: getTesterInfo error", err);
|
|
182
204
|
return null;
|
|
183
205
|
}
|
|
184
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Basic email format validation (defense in depth)
|
|
209
|
+
*/
|
|
210
|
+
isValidEmail(email) {
|
|
211
|
+
if (!email || email.length > 254) return false;
|
|
212
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
213
|
+
return emailRegex.test(email);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Validate report input before submission
|
|
217
|
+
* Returns error message if invalid, null if valid
|
|
218
|
+
*/
|
|
219
|
+
validateReport(report) {
|
|
220
|
+
const validTypes = ["bug", "feedback", "suggestion", "test_pass", "test_fail"];
|
|
221
|
+
if (report.type && !validTypes.includes(report.type)) {
|
|
222
|
+
return `Invalid report type: ${report.type}. Must be one of: ${validTypes.join(", ")}`;
|
|
223
|
+
}
|
|
224
|
+
const validSeverities = ["critical", "high", "medium", "low"];
|
|
225
|
+
if (report.severity && !validSeverities.includes(report.severity)) {
|
|
226
|
+
return `Invalid severity: ${report.severity}. Must be one of: ${validSeverities.join(", ")}`;
|
|
227
|
+
}
|
|
228
|
+
if (report.title && report.title.length > 500) {
|
|
229
|
+
return "Title must be 500 characters or less";
|
|
230
|
+
}
|
|
231
|
+
if (report.description && report.description.length > 1e4) {
|
|
232
|
+
return "Description must be 10,000 characters or less";
|
|
233
|
+
}
|
|
234
|
+
if (report.screenshots && Array.isArray(report.screenshots)) {
|
|
235
|
+
if (report.screenshots.length > 10) {
|
|
236
|
+
return "Maximum 10 screenshots allowed";
|
|
237
|
+
}
|
|
238
|
+
for (const url of report.screenshots) {
|
|
239
|
+
if (typeof url !== "string" || url.length > 2e3) {
|
|
240
|
+
return "Invalid screenshot URL";
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Validate profile update input
|
|
248
|
+
* Returns error message if invalid, null if valid
|
|
249
|
+
*/
|
|
250
|
+
validateProfileUpdate(updates) {
|
|
251
|
+
if (updates.name !== void 0) {
|
|
252
|
+
if (typeof updates.name !== "string" || updates.name.length > 100) {
|
|
253
|
+
return "Name must be 100 characters or less";
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (updates.additionalEmails !== void 0) {
|
|
257
|
+
if (!Array.isArray(updates.additionalEmails)) {
|
|
258
|
+
return "Additional emails must be an array";
|
|
259
|
+
}
|
|
260
|
+
if (updates.additionalEmails.length > 5) {
|
|
261
|
+
return "Maximum 5 additional emails allowed";
|
|
262
|
+
}
|
|
263
|
+
for (const email of updates.additionalEmails) {
|
|
264
|
+
if (!this.isValidEmail(email)) {
|
|
265
|
+
return `Invalid email format: ${email}`;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (updates.avatarUrl !== void 0 && updates.avatarUrl !== null) {
|
|
270
|
+
if (typeof updates.avatarUrl !== "string" || updates.avatarUrl.length > 2e3) {
|
|
271
|
+
return "Invalid avatar URL";
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (updates.platforms !== void 0) {
|
|
275
|
+
if (!Array.isArray(updates.platforms)) {
|
|
276
|
+
return "Platforms must be an array";
|
|
277
|
+
}
|
|
278
|
+
const validPlatforms = ["ios", "android", "web", "desktop", "other"];
|
|
279
|
+
for (const platform of updates.platforms) {
|
|
280
|
+
if (!validPlatforms.includes(platform)) {
|
|
281
|
+
return `Invalid platform: ${platform}. Must be one of: ${validPlatforms.join(", ")}`;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Check rate limit for an action
|
|
289
|
+
* Returns { allowed: boolean, error?: string, remaining?: number }
|
|
290
|
+
*/
|
|
291
|
+
async checkRateLimit(identifier, action) {
|
|
292
|
+
try {
|
|
293
|
+
const { data, error } = await this.supabase.rpc("check_rate_limit", {
|
|
294
|
+
p_identifier: identifier,
|
|
295
|
+
p_action: action,
|
|
296
|
+
p_project_id: this.config.projectId
|
|
297
|
+
});
|
|
298
|
+
if (error) {
|
|
299
|
+
console.warn("BugBear: Rate limit check failed, allowing request", error.message);
|
|
300
|
+
return { allowed: true };
|
|
301
|
+
}
|
|
302
|
+
if (!data.allowed) {
|
|
303
|
+
return {
|
|
304
|
+
allowed: false,
|
|
305
|
+
error: `Rate limit exceeded. Try again in ${Math.ceil((new Date(data.reset_at).getTime() - Date.now()) / 1e3)} seconds.`,
|
|
306
|
+
remaining: 0,
|
|
307
|
+
resetAt: data.reset_at
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
allowed: true,
|
|
312
|
+
remaining: data.remaining,
|
|
313
|
+
resetAt: data.reset_at
|
|
314
|
+
};
|
|
315
|
+
} catch (err) {
|
|
316
|
+
console.warn("BugBear: Rate limit check error", err);
|
|
317
|
+
return { allowed: true };
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Update tester profile
|
|
322
|
+
* Allows testers to update their name, additional emails, avatar, and platforms
|
|
323
|
+
*/
|
|
324
|
+
async updateTesterProfile(updates) {
|
|
325
|
+
try {
|
|
326
|
+
const validationError = this.validateProfileUpdate(updates);
|
|
327
|
+
if (validationError) {
|
|
328
|
+
return { success: false, error: validationError };
|
|
329
|
+
}
|
|
330
|
+
const userInfo = await this.getCurrentUserInfo();
|
|
331
|
+
if (!userInfo) {
|
|
332
|
+
return { success: false, error: "Not authenticated" };
|
|
333
|
+
}
|
|
334
|
+
const rateLimit = await this.checkRateLimit(userInfo.email, "profile_update");
|
|
335
|
+
if (!rateLimit.allowed) {
|
|
336
|
+
return { success: false, error: rateLimit.error };
|
|
337
|
+
}
|
|
338
|
+
const testerInfo = await this.getTesterInfo();
|
|
339
|
+
if (!testerInfo) {
|
|
340
|
+
return { success: false, error: "Not a registered tester" };
|
|
341
|
+
}
|
|
342
|
+
const updateData = {};
|
|
343
|
+
if (updates.name !== void 0) updateData.name = updates.name;
|
|
344
|
+
if (updates.additionalEmails !== void 0) updateData.additional_emails = updates.additionalEmails;
|
|
345
|
+
if (updates.avatarUrl !== void 0) updateData.avatar_url = updates.avatarUrl;
|
|
346
|
+
if (updates.platforms !== void 0) updateData.platforms = updates.platforms;
|
|
347
|
+
const { error } = await this.supabase.from("testers").update(updateData).eq("id", testerInfo.id);
|
|
348
|
+
if (error) {
|
|
349
|
+
console.error("BugBear: updateTesterProfile error", error);
|
|
350
|
+
return { success: false, error: error.message };
|
|
351
|
+
}
|
|
352
|
+
return { success: true };
|
|
353
|
+
} catch (err) {
|
|
354
|
+
console.error("BugBear: updateTesterProfile error", err);
|
|
355
|
+
return { success: false, error: "Failed to update profile" };
|
|
356
|
+
}
|
|
357
|
+
}
|
|
185
358
|
/**
|
|
186
359
|
* Check if current user is a tester for this project
|
|
187
360
|
*/
|
|
@@ -447,6 +620,11 @@ var BugBearClient = class {
|
|
|
447
620
|
console.error("BugBear: No tester info, cannot send message");
|
|
448
621
|
return false;
|
|
449
622
|
}
|
|
623
|
+
const rateLimit = await this.checkRateLimit(testerInfo.email, "message_send");
|
|
624
|
+
if (!rateLimit.allowed) {
|
|
625
|
+
console.error("BugBear: Rate limit exceeded for messages");
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
450
628
|
const { error } = await this.supabase.from("discussion_messages").insert({
|
|
451
629
|
thread_id: threadId,
|
|
452
630
|
sender_type: "tester",
|