@bugspotter/sdk 0.1.0-alpha.1

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.
Files changed (67) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/LICENSE +21 -0
  3. package/README.md +639 -0
  4. package/dist/bugspotter.min.js +2 -0
  5. package/dist/bugspotter.min.js.LICENSE.txt +14 -0
  6. package/dist/capture/base-capture.d.ts +34 -0
  7. package/dist/capture/base-capture.js +23 -0
  8. package/dist/capture/capture-lifecycle.d.ts +24 -0
  9. package/dist/capture/capture-lifecycle.js +2 -0
  10. package/dist/capture/console.d.ts +29 -0
  11. package/dist/capture/console.js +107 -0
  12. package/dist/capture/metadata.d.ts +21 -0
  13. package/dist/capture/metadata.js +76 -0
  14. package/dist/capture/network.d.ts +32 -0
  15. package/dist/capture/network.js +135 -0
  16. package/dist/capture/screenshot.d.ts +19 -0
  17. package/dist/capture/screenshot.js +52 -0
  18. package/dist/collectors/dom.d.ts +67 -0
  19. package/dist/collectors/dom.js +164 -0
  20. package/dist/collectors/index.d.ts +2 -0
  21. package/dist/collectors/index.js +5 -0
  22. package/dist/core/buffer.d.ts +50 -0
  23. package/dist/core/buffer.js +88 -0
  24. package/dist/core/circular-buffer.d.ts +42 -0
  25. package/dist/core/circular-buffer.js +77 -0
  26. package/dist/core/compress.d.ts +49 -0
  27. package/dist/core/compress.js +245 -0
  28. package/dist/core/offline-queue.d.ts +76 -0
  29. package/dist/core/offline-queue.js +301 -0
  30. package/dist/core/transport.d.ts +73 -0
  31. package/dist/core/transport.js +352 -0
  32. package/dist/core/upload-helpers.d.ts +32 -0
  33. package/dist/core/upload-helpers.js +79 -0
  34. package/dist/core/uploader.d.ts +70 -0
  35. package/dist/core/uploader.js +185 -0
  36. package/dist/index.d.ts +140 -0
  37. package/dist/index.esm.js +205 -0
  38. package/dist/index.js +244 -0
  39. package/dist/utils/logger.d.ts +28 -0
  40. package/dist/utils/logger.js +84 -0
  41. package/dist/utils/sanitize-patterns.d.ts +103 -0
  42. package/dist/utils/sanitize-patterns.js +282 -0
  43. package/dist/utils/sanitize.d.ts +73 -0
  44. package/dist/utils/sanitize.js +254 -0
  45. package/dist/widget/button.d.ts +33 -0
  46. package/dist/widget/button.js +143 -0
  47. package/dist/widget/components/dom-element-cache.d.ts +62 -0
  48. package/dist/widget/components/dom-element-cache.js +105 -0
  49. package/dist/widget/components/form-validator.d.ts +66 -0
  50. package/dist/widget/components/form-validator.js +115 -0
  51. package/dist/widget/components/pii-detection-display.d.ts +64 -0
  52. package/dist/widget/components/pii-detection-display.js +142 -0
  53. package/dist/widget/components/redaction-canvas.d.ts +95 -0
  54. package/dist/widget/components/redaction-canvas.js +230 -0
  55. package/dist/widget/components/screenshot-processor.d.ts +44 -0
  56. package/dist/widget/components/screenshot-processor.js +191 -0
  57. package/dist/widget/components/style-manager.d.ts +37 -0
  58. package/dist/widget/components/style-manager.js +296 -0
  59. package/dist/widget/components/template-manager.d.ts +66 -0
  60. package/dist/widget/components/template-manager.js +198 -0
  61. package/dist/widget/modal.d.ts +62 -0
  62. package/dist/widget/modal.js +299 -0
  63. package/docs/CDN.md +213 -0
  64. package/docs/FRAMEWORK_INTEGRATION.md +1104 -0
  65. package/docs/PUBLISHING.md +550 -0
  66. package/docs/SESSION_REPLAY.md +381 -0
  67. package/package.json +90 -0
package/README.md ADDED
@@ -0,0 +1,639 @@
1
+ # @bugspotter/sdk
2
+
3
+ > Core SDK for capturing and reporting bugs
4
+
5
+ The BugSpotter SDK provides a comprehensive solution for capturing bug reports in web applications, including screenshots, console logs, network requests, and browser metadata.
6
+
7
+ ## ๐Ÿ“ฆ Installation
8
+
9
+ ### NPM Package
10
+
11
+ ```bash
12
+ # npm
13
+ npm install @bugspotter/sdk
14
+
15
+ # yarn
16
+ yarn add @bugspotter/sdk
17
+
18
+ # pnpm
19
+ pnpm add @bugspotter/sdk
20
+ ```
21
+
22
+ ### CDN
23
+
24
+ ```html
25
+ <!-- BugSpotter CDN (versioned - recommended for production) -->
26
+ <script src="https://cdn.bugspotter.io/sdk/bugspotter-0.1.0.min.js"></script>
27
+
28
+ <!-- Latest version (for development only) -->
29
+ <script src="https://cdn.bugspotter.io/sdk/bugspotter-latest.min.js"></script>
30
+
31
+ <!-- Or unpkg -->
32
+ <script src="https://unpkg.com/@bugspotter/sdk@latest/dist/bugspotter.min.js"></script>
33
+ ```
34
+
35
+ ๐Ÿ“˜ **See [CDN Usage Guide](./docs/CDN.md) for detailed CDN integration, SRI hashes, and troubleshooting.**
36
+
37
+ ### From Source
38
+
39
+ ```bash
40
+ # Clone and build from source
41
+ git clone https://github.com/apexbridge-tech/bugspotter.git
42
+ cd bugspotter/packages/sdk
43
+ pnpm install
44
+ pnpm build
45
+ ```
46
+
47
+ The built SDK will be available at `dist/bugspotter.min.js` (~99 KB minified with session replay).
48
+
49
+ ## ๐Ÿš€ Quick Start
50
+
51
+ ### Basic Usage
52
+
53
+ **ES Modules (React, Vue, Angular, etc.)**
54
+
55
+ ```javascript
56
+ import BugSpotter from '@bugspotter/sdk';
57
+
58
+ // Initialize with auto-widget
59
+ const bugSpotter = BugSpotter.init({
60
+ apiKey: 'bgs_your_api_key',
61
+ endpoint: 'https://api.bugspotter.com',
62
+ showWidget: true,
63
+ });
64
+ ```
65
+
66
+ **CommonJS (Node.js)**
67
+
68
+ ```javascript
69
+ const BugSpotter = require('@bugspotter/sdk');
70
+
71
+ const bugSpotter = BugSpotter.init({
72
+ apiKey: 'bgs_your_api_key',
73
+ endpoint: 'https://api.bugspotter.com',
74
+ showWidget: true,
75
+ });
76
+ ```
77
+
78
+ **UMD (Browser script tag)**
79
+
80
+ ```html
81
+ <script src="https://cdn.bugspotter.io/sdk/bugspotter-latest.min.js"></script>
82
+ <script>
83
+ // Initialize with auto-widget
84
+ const bugSpotter = BugSpotter.init({
85
+ apiKey: 'bgs_your_api_key',
86
+ endpoint: 'https://api.bugspotter.com',
87
+ showWidget: true,
88
+ });
89
+ </script>
90
+ ```
91
+
92
+ ### Direct File Uploads (Presigned URLs)
93
+
94
+ For better performance with large files, use `DirectUploader` to upload files directly to storage:
95
+
96
+ ```javascript
97
+ import { DirectUploader, compressReplayEvents } from '@bugspotter/sdk';
98
+
99
+ // 1. Create bug report (metadata only)
100
+ const response = await fetch('https://api.example.com/api/v1/reports', {
101
+ method: 'POST',
102
+ headers: {
103
+ 'Content-Type': 'application/json',
104
+ 'x-api-key': 'bgs_your_api_key',
105
+ },
106
+ body: JSON.stringify({
107
+ project_id: 'project-uuid',
108
+ title: 'Bug title',
109
+ description: 'Bug description',
110
+ }),
111
+ });
112
+ const { id: bugId } = await response.json();
113
+
114
+ // 2. Initialize DirectUploader
115
+ const uploader = new DirectUploader({
116
+ apiEndpoint: 'https://api.example.com',
117
+ apiKey: 'bgs_your_api_key',
118
+ projectId: 'project-uuid',
119
+ bugId,
120
+ });
121
+
122
+ // 3. Upload screenshot with progress tracking
123
+ const screenshotBlob = await fetch(screenshotDataUrl).then((r) => r.blob());
124
+ await uploader.uploadScreenshot(screenshotBlob, (progress) => {
125
+ console.log(`Upload: ${progress.percentage}%`);
126
+ });
127
+
128
+ // 4. Compress and upload replay
129
+ const compressedReplay = await compressReplayEvents(replayEvents);
130
+ await uploader.uploadReplay(compressedReplay);
131
+
132
+ console.log('All uploads complete!');
133
+ ```
134
+
135
+ **Benefits:**
136
+
137
+ - 97% memory reduction (3.33MB โ†’ 100KB API payload)
138
+ - 3x faster uploads (direct to storage)
139
+ - Progress tracking for large files
140
+ - Automatic compression for replays
141
+
142
+ ### Manual Capture
143
+
144
+ ```javascript
145
+ // Initialize without widget
146
+ const bugSpotter = BugSpotter.init({
147
+ apiKey: 'your-api-key',
148
+ endpoint: 'https://api.example.com/bugs',
149
+ showWidget: false,
150
+ });
151
+
152
+ // Capture bug report manually
153
+ async function reportBug() {
154
+ const report = await bugSpotter.capture();
155
+ console.log('Captured:', report);
156
+ // report contains: screenshot, console, network, metadata
157
+ }
158
+ ```
159
+
160
+ ## ๐ŸŽจ Using the Widget
161
+
162
+ ### Automatic Widget
163
+
164
+ ```javascript
165
+ // Widget appears automatically with showWidget: true
166
+ const bugSpotter = BugSpotter.init({
167
+ apiKey: 'demo-key',
168
+ endpoint: 'http://localhost:4000/api/bugs',
169
+ showWidget: true,
170
+ widgetOptions: {
171
+ position: 'bottom-right',
172
+ icon: 'โšก',
173
+ backgroundColor: '#1a365d',
174
+ size: 48,
175
+ },
176
+ });
177
+ ```
178
+
179
+ ### Custom Widget
180
+
181
+ ```javascript
182
+ // Create custom floating button
183
+ const button = new BugSpotter.FloatingButton({
184
+ position: 'bottom-right',
185
+ icon: '๐Ÿ›',
186
+ backgroundColor: '#ff4444',
187
+ size: 56,
188
+ offset: { x: 24, y: 24 },
189
+ style: {
190
+ boxShadow: '0 2px 8px rgba(0,0,0,0.2)',
191
+ border: '2px solid white',
192
+ },
193
+ });
194
+
195
+ // Handle click
196
+ button.onClick(async () => {
197
+ const report = await bugSpotter.capture();
198
+
199
+ const modal = new BugSpotter.BugReportModal({
200
+ onSubmit: async (data) => {
201
+ // data.title, data.description
202
+ const response = await fetch('https://api.example.com/bugs', {
203
+ method: 'POST',
204
+ headers: {
205
+ 'Content-Type': 'application/json',
206
+ Authorization: `Bearer ${apiKey}`,
207
+ },
208
+ body: JSON.stringify({
209
+ ...data,
210
+ report,
211
+ }),
212
+ });
213
+
214
+ if (!response.ok) {
215
+ throw new Error('Submission failed');
216
+ }
217
+ },
218
+ });
219
+
220
+ modal.show(report.screenshot);
221
+ });
222
+
223
+ // Control button
224
+ button.show();
225
+ button.hide();
226
+ button.setIcon('โš ๏ธ');
227
+ button.setBackgroundColor('#00ff00');
228
+ ```
229
+
230
+ ## ๐Ÿ”’ PII Sanitization
231
+
232
+ Automatic detection and masking of sensitive data before submission.
233
+
234
+ **Built-in patterns:** Email, phone, credit card, SSN, Kazakhstan IIN, IP address
235
+
236
+ ```javascript
237
+ BugSpotter.init({
238
+ sanitize: {
239
+ enabled: true, // Default
240
+ patterns: ['email', 'phone', 'creditcard'],
241
+ customPatterns: [{ name: 'api-key', regex: /API[-_]KEY:\s*[\w-]{20,}/gi }],
242
+ excludeSelectors: ['.public-email'],
243
+ },
244
+ });
245
+ ```
246
+
247
+ **Performance:** <10ms overhead, supports Cyrillic text
248
+
249
+ ## ๏ฟฝ๐Ÿ“‹ API Reference
250
+
251
+ ### BugSpotter Class
252
+
253
+ #### `BugSpotter.init(config)`
254
+
255
+ Initialize the SDK.
256
+
257
+ **Parameters:**
258
+
259
+ ```typescript
260
+ interface BugSpotterConfig {
261
+ apiKey?: string; // API key for authentication
262
+ endpoint?: string; // Backend API URL
263
+ showWidget?: boolean; // Auto-show widget (default: true)
264
+ widgetOptions?: FloatingButtonOptions;
265
+ replay?: {
266
+ // Session replay configuration
267
+ enabled?: boolean; // Enable replay (default: true)
268
+ duration?: number; // Buffer duration in seconds (default: 15)
269
+ sampling?: {
270
+ mousemove?: number; // Mousemove throttle in ms (default: 50)
271
+ scroll?: number; // Scroll throttle in ms (default: 100)
272
+ };
273
+ };
274
+ sanitize?: {
275
+ // PII sanitization configuration
276
+ enabled?: boolean; // Enable PII sanitization (default: true)
277
+ patterns?: Array<
278
+ // PII patterns to detect
279
+ 'email' | 'phone' | 'creditcard' | 'ssn' | 'iin' | 'ip' | 'custom'
280
+ >;
281
+ customPatterns?: Array<{
282
+ // Custom regex patterns
283
+ name: string; // Pattern name for [REDACTED-NAME]
284
+ regex: RegExp; // Detection regex
285
+ }>;
286
+ excludeSelectors?: string[]; // CSS selectors to exclude from sanitization
287
+ };
288
+ }
289
+ ```
290
+
291
+ **Returns:** `BugSpotter` instance
292
+
293
+ #### `bugSpotter.capture()`
294
+
295
+ Capture current bug report data.
296
+
297
+ **Returns:** `Promise<BugReport>`
298
+
299
+ ```typescript
300
+ interface BugReport {
301
+ screenshot: string; // Base64 PNG data URL
302
+ console: ConsoleLog[]; // Array of console entries
303
+ network: NetworkRequest[]; // Array of network requests
304
+ metadata: BrowserMetadata; // Browser/system info
305
+ replay: eventWithTime[]; // Session replay events (rrweb format)
306
+ }
307
+
308
+ interface ConsoleLog {
309
+ level: string; // 'log', 'warn', 'error', 'info', 'debug'
310
+ message: string; // Formatted message
311
+ timestamp: number; // Unix timestamp
312
+ stack?: string; // Error stack trace (for errors)
313
+ }
314
+
315
+ interface NetworkRequest {
316
+ url: string; // Request URL
317
+ method: string; // HTTP method
318
+ status: number; // HTTP status code
319
+ duration: number; // Request duration in ms
320
+ timestamp: number; // Unix timestamp
321
+ error?: string; // Error message if failed
322
+ }
323
+
324
+ interface BrowserMetadata {
325
+ userAgent: string;
326
+ viewport: { width: number; height: number };
327
+ browser: string; // Detected browser name
328
+ os: string; // Detected OS
329
+ url: string; // Current page URL
330
+ timestamp: number; // Capture timestamp
331
+ }
332
+ ```
333
+
334
+ #### `bugSpotter.getConfig()`
335
+
336
+ Get current configuration.
337
+
338
+ **Returns:** `Readonly<BugSpotterConfig>`
339
+
340
+ #### `bugSpotter.destroy()`
341
+
342
+ Clean up and destroy the SDK instance.
343
+
344
+ ### FloatingButton Class
345
+
346
+ #### Constructor
347
+
348
+ ```typescript
349
+ new FloatingButton(options?: FloatingButtonOptions)
350
+
351
+ interface FloatingButtonOptions {
352
+ position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
353
+ icon?: string; // Emoji or text
354
+ backgroundColor?: string; // CSS color
355
+ size?: number; // Size in pixels
356
+ offset?: { x: number; y: number };
357
+ style?: Record<string, string>; // Additional CSS
358
+ }
359
+ ```
360
+
361
+ #### Methods
362
+
363
+ - `button.onClick(handler: () => void | Promise<void>)` - Set click handler
364
+ - `button.show()` - Show the button
365
+ - `button.hide()` - Hide the button
366
+ - `button.setIcon(icon: string)` - Change icon
367
+ - `button.setBackgroundColor(color: string)` - Change color
368
+ - `button.destroy()` - Remove button from DOM
369
+
370
+ ### BugReportModal Class
371
+
372
+ #### Constructor
373
+
374
+ ```typescript
375
+ new BugReportModal(options: BugReportModalOptions)
376
+
377
+ interface BugReportModalOptions {
378
+ onSubmit: (data: BugReportData) => void | Promise<void>;
379
+ onClose?: () => void;
380
+ }
381
+
382
+ interface BugReportData {
383
+ title: string;
384
+ description: string;
385
+ }
386
+ ```
387
+
388
+ #### Methods
389
+
390
+ - `modal.show(screenshot: string)` - Display modal with screenshot
391
+ - `modal.close()` - Close the modal
392
+ - `modal.destroy()` - Remove modal from DOM
393
+
394
+ **Features:**
395
+
396
+ - Form validation (title and description required)
397
+ - Loading state during async submission
398
+ - Error handling with user feedback
399
+ - Escape key to close
400
+ - Click X button to close
401
+ - Cannot close by clicking outside (prevents data loss)
402
+
403
+ ### DirectUploader Class
404
+
405
+ Direct client-to-storage uploads using presigned URLs (97% memory reduction, 3x faster).
406
+
407
+ #### Constructor
408
+
409
+ ```typescript
410
+ new DirectUploader(config: DirectUploadConfig)
411
+
412
+ interface DirectUploadConfig {
413
+ apiEndpoint: string; // Backend API URL
414
+ apiKey: string; // bgs_... API key
415
+ projectId: string; // Project UUID
416
+ bugId: string; // Bug report UUID
417
+ }
418
+ ```
419
+
420
+ #### Methods
421
+
422
+ **`uploadScreenshot(file, onProgress?)`**
423
+
424
+ Upload screenshot directly to storage.
425
+
426
+ ```typescript
427
+ const screenshotBlob = await fetch(dataUrl).then((r) => r.blob());
428
+
429
+ const result = await uploader.uploadScreenshot(screenshotBlob, (progress) => {
430
+ console.log(`Screenshot: ${progress.loaded}/${progress.total} (${progress.percentage}%)`);
431
+ });
432
+
433
+ // result: { success: true, storageKey: "screenshots/..." }
434
+ ```
435
+
436
+ **`uploadReplay(compressedBlob, onProgress?)`**
437
+
438
+ Upload compressed replay data to storage.
439
+
440
+ ```typescript
441
+ const compressed = await compressReplayEvents(events);
442
+ const result = await uploader.uploadReplay(compressed);
443
+ ```
444
+
445
+ **`uploadAttachment(file, onProgress?)`**
446
+
447
+ Upload attachment file to storage.
448
+
449
+ ```typescript
450
+ const file = document.querySelector('input[type="file"]').files[0];
451
+ const result = await uploader.uploadAttachment(file);
452
+ ```
453
+
454
+ #### Upload Progress
455
+
456
+ ```typescript
457
+ interface UploadProgress {
458
+ loaded: number; // Bytes uploaded
459
+ total: number; // Total bytes
460
+ percentage: number; // 0-100
461
+ }
462
+
463
+ type UploadProgressCallback = (progress: UploadProgress) => void;
464
+ ```
465
+
466
+ #### Upload Result
467
+
468
+ ```typescript
469
+ interface UploadResult {
470
+ success: boolean;
471
+ storageKey?: string; // Storage location (if successful)
472
+ error?: string; // Error message (if failed)
473
+ }
474
+ ```
475
+
476
+ ### Compression Utilities
477
+
478
+ Compress replay events before uploading (2MB JSON โ†’ ~200KB gzip).
479
+
480
+ **`compressReplayEvents(events)`**
481
+
482
+ ```typescript
483
+ import { compressReplayEvents } from '@bugspotter/sdk';
484
+
485
+ const events = [
486
+ /* rrweb events */
487
+ ];
488
+ const compressed = await compressReplayEvents(events);
489
+ console.log(`Compressed: ${(compressed.size / 1024).toFixed(2)} KB`);
490
+ ```
491
+
492
+ Uses native `CompressionStream` API (Chrome 80+, Firefox 113+, Safari 16.4+).
493
+
494
+ **`estimateCompressedReplaySize(events)`**
495
+
496
+ Estimate compressed size without actually compressing.
497
+
498
+ ```typescript
499
+ const estimatedSize = estimateCompressedReplaySize(events);
500
+ console.log(`Estimated: ${(estimatedSize / 1024).toFixed(2)} KB`);
501
+ ```
502
+
503
+ **`isWithinSizeLimit(blob, limitMB)`**
504
+
505
+ Check if blob is within size limit.
506
+
507
+ ```typescript
508
+ if (!isWithinSizeLimit(compressed, 10)) {
509
+ console.warn('File exceeds 10MB limit');
510
+ }
511
+ ```
512
+
513
+ ## ๐Ÿ“Š Capture Modules
514
+
515
+ The SDK automatically captures:
516
+
517
+ - **๐Ÿ“ธ Screenshots** - CSP-safe full page capture (~500ms)
518
+ - **๐ŸŽฅ Session Replay** - Last 15-30s of user interactions (rrweb)
519
+ - **๐Ÿ“ Console** - All log levels with stack traces
520
+ - **๐ŸŒ Network** - fetch/XHR timing and responses
521
+ - **๐Ÿ’ป Metadata** - Browser, OS, viewport, URL
522
+
523
+ See [Session Replay Documentation](docs/SESSION_REPLAY.md) for detailed configuration.
524
+
525
+ ### Screenshot Capture
526
+
527
+ ```typescript
528
+ import { ScreenshotCapture } from '@bugspotter/sdk';
529
+
530
+ const screenshotCapture = new ScreenshotCapture();
531
+ const screenshot = await screenshotCapture.capture();
532
+ // Returns: Base64 PNG data URL or 'SCREENSHOT_FAILED'
533
+ ```
534
+
535
+ **Features:**
536
+
537
+ - CSP-safe using `html-to-image`
538
+ - Full page capture
539
+ - Automatic error handling
540
+ - ~500ms average capture time
541
+
542
+ ### Console Capture
543
+
544
+ ```typescript
545
+ import { ConsoleCapture } from '@bugspotter/sdk';
546
+
547
+ const consoleCapture = new ConsoleCapture();
548
+ const logs = consoleCapture.getLogs();
549
+ ```
550
+
551
+ **Features:**
552
+
553
+ - Captures: log, warn, error, info, debug
554
+ - Stack traces for errors
555
+ - Timestamps for all entries
556
+ - Object stringification
557
+ - Circular reference handling
558
+ - Configurable max logs (default: 100)
559
+
560
+ ### Network Capture
561
+
562
+ ```typescript
563
+ import { NetworkCapture } from '@bugspotter/sdk';
564
+
565
+ const networkCapture = new NetworkCapture();
566
+ const requests = networkCapture.getRequests();
567
+ ```
568
+
569
+ **Features:**
570
+
571
+ - Captures fetch() and XMLHttpRequest
572
+ - Request/response timing
573
+ - HTTP status codes
574
+ - Error tracking
575
+ - Singleton pattern (one instance per page)
576
+
577
+ ### Metadata Capture
578
+
579
+ ```typescript
580
+ import { MetadataCapture } from '@bugspotter/sdk';
581
+
582
+ const metadataCapture = new MetadataCapture();
583
+ const metadata = metadataCapture.capture();
584
+ ```
585
+
586
+ **Features:**
587
+
588
+ - Browser detection (Chrome, Firefox, Safari, Edge, etc.)
589
+ - OS detection (Windows, macOS, Linux, iOS, Android)
590
+ - Viewport dimensions
591
+ - User agent string
592
+ - Current URL
593
+ - Timestamp
594
+
595
+ ## ๐Ÿงช Testing
596
+
597
+ ```bash
598
+ pnpm test # All tests
599
+ pnpm test --watch # Watch mode
600
+ pnpm test --coverage # Coverage report
601
+ ```
602
+
603
+ **345 tests** passing (unit + E2E + Playwright)
604
+
605
+ ## ๏ฟฝ๏ธ Building
606
+
607
+ ```bash
608
+ pnpm run dev # Development with watch
609
+ pnpm run build # Production build
610
+ ```
611
+
612
+ Output: `dist/bugspotter.min.js` (~99 KB)
613
+
614
+ ## ๐Ÿ“ˆ Performance
615
+
616
+ - **Bundle**: ~99 KB minified
617
+ - **Load**: < 100ms
618
+ - **Memory**: < 15 MB (30s replay buffer)
619
+ - **Screenshot**: ~500ms
620
+ - **PII sanitization**: <10ms
621
+
622
+ ## ๐Ÿ”’ Security
623
+
624
+ - CSP-safe (no eval, no inline scripts)
625
+ - Automatic PII detection and masking
626
+ - Input validation
627
+ - HTTPS recommended
628
+
629
+ ## ๐Ÿค Contributing
630
+
631
+ See the main [CONTRIBUTING.md](../../CONTRIBUTING.md) guide.
632
+
633
+ ## ๐Ÿ“„ License
634
+
635
+ MIT License - see [LICENSE](../../LICENSE)
636
+
637
+ ---
638
+
639
+ Part of the [BugSpotter](../../README.md) project