@bugspotter/sdk 0.2.5-alpha.5 → 0.3.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.
@@ -23,6 +23,7 @@ export declare class ConsoleCapture extends BaseCapture<LogEntry[], ConsoleCaptu
23
23
  /**
24
24
  * Check if log should be filtered (SDK internal logs)
25
25
  * Filters out SDK debug logs (prefix [BugSpotter]) except errors
26
+ * Errors are always captured even if they contain SDK prefix
26
27
  */
27
28
  private shouldFilterLog;
28
29
  private interceptConsole;
@@ -72,13 +72,15 @@ class ConsoleCapture extends base_capture_1.BaseCapture {
72
72
  /**
73
73
  * Check if log should be filtered (SDK internal logs)
74
74
  * Filters out SDK debug logs (prefix [BugSpotter]) except errors
75
+ * Errors are always captured even if they contain SDK prefix
75
76
  */
76
77
  shouldFilterLog(message, level) {
77
78
  // Always keep SDK errors for debugging
78
79
  if (level === 'error') {
79
80
  return false;
80
81
  }
81
- // Filter SDK internal logs (debug/info/warn only)
82
+ // Filter SDK internal logs (debug/info/warn/log only)
83
+ // Use startsWith to only match prefix, not substring anywhere in message
82
84
  return message.startsWith(exports.SDK_LOG_PREFIX);
83
85
  }
84
86
  interceptConsole(levels = CONSOLE_METHODS) {
@@ -2,6 +2,12 @@ import { BaseCapture, type CaptureOptions } from './base-capture';
2
2
  type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
3
3
  export interface NetworkCaptureOptions extends CaptureOptions {
4
4
  maxRequests?: number;
5
+ /**
6
+ * Optional filter function to exclude URLs from capture
7
+ * Returns true to CAPTURE the URL, false to FILTER it out
8
+ * Note: Error responses (non-2xx status) are always captured regardless of filter result
9
+ * Example: (url) => !url.startsWith('https://api.bugspotter.com') captures all except SDK API
10
+ */
5
11
  filterUrls?: (url: string) => boolean;
6
12
  }
7
13
  export declare class NetworkCapture extends BaseCapture<NetworkRequest[], NetworkCaptureOptions> {
@@ -54,8 +54,18 @@ class NetworkCapture extends base_capture_1.BaseCapture {
54
54
  return request;
55
55
  }
56
56
  addRequest(request) {
57
- if (this.filterUrls && !this.filterUrls(request.url)) {
58
- return; // Skip filtered URLs
57
+ // Check if URL should be filtered
58
+ // filterUrls returns true to CAPTURE, false to FILTER OUT
59
+ // Exception: Always capture error responses (non-2xx) for debugging purposes
60
+ // Rationale: Error responses from SDK API calls (4xx, 5xx) are invaluable for diagnosing
61
+ // connectivity issues, authentication failures, rate limiting, or backend problems that
62
+ // could affect bug report submission. Users need this visibility to troubleshoot SDK issues.
63
+ if (this.filterUrls) {
64
+ const shouldCapture = this.filterUrls(request.url);
65
+ const isError = request.status < 200 || request.status >= 300;
66
+ if (!shouldCapture && !isError) {
67
+ return; // Filter out successful requests from filtered URLs
68
+ }
59
69
  }
60
70
  this.buffer.add(request);
61
71
  }
package/dist/version.d.ts CHANGED
@@ -5,4 +5,4 @@
5
5
  * This file is automatically generated during the build process.
6
6
  * To update the version, modify package.json
7
7
  */
8
- export declare const VERSION = "0.2.5-alpha.5";
8
+ export declare const VERSION = "0.3.0";
package/dist/version.js CHANGED
@@ -8,4 +8,4 @@
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.VERSION = void 0;
11
- exports.VERSION = '0.2.5-alpha.5';
11
+ exports.VERSION = '0.3.0';
@@ -13,11 +13,28 @@ export interface StyleConfig {
13
13
  }
14
14
  export declare class StyleManager {
15
15
  private config;
16
+ private readonly SPACING;
17
+ private readonly BREAKPOINTS;
18
+ private readonly MODAL_SIZES;
19
+ private readonly FONT_SIZES;
20
+ private readonly BORDER_STYLES;
21
+ private readonly SHADOW_STYLES;
16
22
  constructor(config?: StyleConfig);
17
23
  /**
18
24
  * Generate complete CSS stylesheet for the modal
19
25
  */
20
26
  generateStyles(): string;
27
+ private generateOverlayStyles;
28
+ private generateModalStyles;
29
+ private generateHeaderStyles;
30
+ private generateBodyStyles;
31
+ private generateFormStyles;
32
+ private generateButtonStyles;
33
+ private generatePIIStyles;
34
+ private generateLoadingStyles;
35
+ private generateAccessibilityStyles;
36
+ private generateTabletResponsiveStyles;
37
+ private generateMobileResponsiveStyles;
21
38
  /**
22
39
  * Inject styles into document head
23
40
  */
@@ -9,6 +9,44 @@ Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.StyleManager = void 0;
10
10
  class StyleManager {
11
11
  constructor(config = {}) {
12
+ // ============================================================================
13
+ // SPACING & SIZING CONSTANTS
14
+ // ============================================================================
15
+ this.SPACING = {
16
+ xs: 8,
17
+ sm: 12,
18
+ md: 16,
19
+ lg: 20,
20
+ };
21
+ this.BREAKPOINTS = {
22
+ tablet: 768,
23
+ mobile: 480,
24
+ };
25
+ this.MODAL_SIZES = {
26
+ desktop: '600px',
27
+ tablet: '500px',
28
+ mobilePercent: '98%',
29
+ headerHeight: '30px',
30
+ };
31
+ // ============================================================================
32
+ // FONT & LAYOUT CONSTANTS
33
+ // ============================================================================
34
+ this.FONT_SIZES = {
35
+ h2: '20px',
36
+ h2Mobile: '18px',
37
+ label: '14px',
38
+ labelMobile: '13px',
39
+ body: '14px',
40
+ small: '12px',
41
+ sr: '13px',
42
+ };
43
+ this.BORDER_STYLES = {
44
+ primary: '1px solid #e0e0e0',
45
+ light: '1px solid #ddd',
46
+ };
47
+ this.SHADOW_STYLES = {
48
+ modal: '0 4px 6px rgba(0, 0, 0, 0.1)',
49
+ };
12
50
  this.config = {
13
51
  primaryColor: config.primaryColor || '#007bff',
14
52
  dangerColor: config.dangerColor || '#dc3545',
@@ -22,6 +60,24 @@ class StyleManager {
22
60
  */
23
61
  generateStyles() {
24
62
  return `
63
+ ${this.generateOverlayStyles()}
64
+ ${this.generateModalStyles()}
65
+ ${this.generateHeaderStyles()}
66
+ ${this.generateBodyStyles()}
67
+ ${this.generateFormStyles()}
68
+ ${this.generateButtonStyles()}
69
+ ${this.generatePIIStyles()}
70
+ ${this.generateLoadingStyles()}
71
+ ${this.generateAccessibilityStyles()}
72
+ ${this.generateTabletResponsiveStyles()}
73
+ ${this.generateMobileResponsiveStyles()}
74
+ `;
75
+ }
76
+ // ============================================================================
77
+ // COMPONENT STYLES - OVERLAY & MODAL
78
+ // ============================================================================
79
+ generateOverlayStyles() {
80
+ return `
25
81
  .overlay {
26
82
  position: fixed;
27
83
  top: 0;
@@ -35,20 +91,35 @@ class StyleManager {
35
91
  z-index: ${this.config.zIndex};
36
92
  font-family: ${this.config.fontFamily};
37
93
  }
38
-
94
+ `;
95
+ }
96
+ generateModalStyles() {
97
+ return `
39
98
  .modal {
40
99
  background: white;
41
100
  border-radius: 8px;
42
101
  width: 90%;
43
- max-width: 600px;
102
+ max-width: ${this.MODAL_SIZES.desktop};
44
103
  max-height: 90vh;
45
104
  overflow-y: auto;
46
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
105
+ box-shadow: ${this.SHADOW_STYLES.modal};
106
+ scrollbar-width: none;
107
+ -ms-overflow-style: none;
47
108
  }
48
109
 
110
+ .modal::-webkit-scrollbar {
111
+ display: none;
112
+ }
113
+ `;
114
+ }
115
+ // ============================================================================
116
+ // COMPONENT STYLES - HEADER
117
+ // ============================================================================
118
+ generateHeaderStyles() {
119
+ return `
49
120
  .header {
50
- padding: 20px;
51
- border-bottom: 1px solid #e0e0e0;
121
+ padding: ${this.SPACING.lg}px;
122
+ border-bottom: ${this.BORDER_STYLES.primary};
52
123
  display: flex;
53
124
  justify-content: space-between;
54
125
  align-items: center;
@@ -56,7 +127,7 @@ class StyleManager {
56
127
 
57
128
  .header h2 {
58
129
  margin: 0;
59
- font-size: 20px;
130
+ font-size: ${this.FONT_SIZES.h2};
60
131
  font-weight: 600;
61
132
  }
62
133
 
@@ -67,8 +138,8 @@ class StyleManager {
67
138
  cursor: pointer;
68
139
  color: #666;
69
140
  padding: 0;
70
- width: 30px;
71
- height: 30px;
141
+ width: ${this.MODAL_SIZES.headerHeight};
142
+ height: ${this.MODAL_SIZES.headerHeight};
72
143
  display: flex;
73
144
  align-items: center;
74
145
  justify-content: center;
@@ -78,29 +149,38 @@ class StyleManager {
78
149
  .close:hover {
79
150
  background: #f0f0f0;
80
151
  }
81
-
152
+ `;
153
+ }
154
+ // ============================================================================
155
+ // COMPONENT STYLES - BODY & FORM
156
+ // ============================================================================
157
+ generateBodyStyles() {
158
+ return `
82
159
  .body {
83
- padding: 20px;
160
+ padding: ${this.SPACING.lg}px;
84
161
  }
85
-
162
+ `;
163
+ }
164
+ generateFormStyles() {
165
+ return `
86
166
  .form-group {
87
- margin-bottom: 20px;
167
+ margin-bottom: ${this.SPACING.lg}px;
88
168
  }
89
169
 
90
170
  .label {
91
171
  display: block;
92
- margin-bottom: 8px;
172
+ margin-bottom: ${this.SPACING.xs}px;
93
173
  font-weight: 500;
94
- font-size: 14px;
174
+ font-size: ${this.FONT_SIZES.label};
95
175
  }
96
176
 
97
177
  .input,
98
178
  .textarea {
99
179
  width: 100%;
100
- padding: 10px;
101
- border: 1px solid #ddd;
180
+ padding: ${this.SPACING.xs}px;
181
+ border: ${this.BORDER_STYLES.light};
102
182
  border-radius: ${this.config.borderRadius};
103
- font-size: 14px;
183
+ font-size: ${this.FONT_SIZES.body};
104
184
  font-family: ${this.config.fontFamily};
105
185
  box-sizing: border-box;
106
186
  }
@@ -116,40 +196,92 @@ class StyleManager {
116
196
  resize: vertical;
117
197
  }
118
198
 
119
- .screenshot-container {
120
- margin-top: 10px;
121
- position: relative;
199
+ .checkbox-group {
200
+ display: flex;
201
+ align-items: center;
202
+ gap: ${this.SPACING.xs}px;
203
+ margin-top: ${this.SPACING.md}px;
122
204
  }
123
205
 
124
- .screenshot {
125
- max-width: 100%;
126
- border: 1px solid #ddd;
127
- border-radius: ${this.config.borderRadius};
206
+ .checkbox {
207
+ width: 18px;
208
+ height: 18px;
209
+ cursor: pointer;
128
210
  }
129
211
 
130
- .redaction-canvas {
131
- position: absolute;
132
- top: 0;
133
- left: 0;
134
- cursor: crosshair;
135
- border: 2px solid ${this.config.primaryColor};
212
+ .checkbox-label {
213
+ margin: 0;
214
+ font-size: ${this.FONT_SIZES.body};
215
+ cursor: pointer;
216
+ user-select: none;
217
+ }
218
+
219
+ .error {
220
+ color: ${this.config.dangerColor};
221
+ font-size: ${this.FONT_SIZES.small};
222
+ margin-top: 4px;
223
+ }
224
+ `;
225
+ }
226
+ // ============================================================================
227
+ // COMPONENT STYLES - BUTTONS & CONTROLS
228
+ // ============================================================================
229
+ generateButtonStyles() {
230
+ return `
231
+ .footer {
232
+ padding: ${this.SPACING.lg}px;
233
+ border-top: ${this.BORDER_STYLES.primary};
234
+ display: flex;
235
+ justify-content: flex-end;
236
+ gap: ${this.SPACING.xs}px;
237
+ }
238
+
239
+ .btn {
240
+ padding: ${this.SPACING.xs}px ${this.SPACING.md}px;
241
+ border: none;
136
242
  border-radius: ${this.config.borderRadius};
243
+ cursor: pointer;
244
+ font-size: ${this.FONT_SIZES.body};
245
+ font-weight: 500;
246
+ }
247
+
248
+ .btn-primary {
249
+ background: ${this.config.primaryColor};
250
+ color: white;
251
+ }
252
+
253
+ .btn-primary:hover {
254
+ opacity: 0.9;
255
+ }
256
+
257
+ .btn-primary:disabled {
258
+ opacity: 0.5;
259
+ cursor: not-allowed;
260
+ }
261
+
262
+ .btn-secondary {
263
+ background: #6c757d;
264
+ color: white;
265
+ }
266
+
267
+ .btn-secondary:hover {
268
+ opacity: 0.9;
137
269
  }
138
270
 
139
271
  .redaction-controls {
140
- margin-top: 10px;
272
+ margin-top: ${this.SPACING.xs}px;
141
273
  display: flex;
142
- gap: 10px;
274
+ gap: ${this.SPACING.xs}px;
143
275
  }
144
276
 
145
277
  .btn-redact,
146
278
  .btn-clear {
147
- padding: 8px 16px;
148
- border: 1px solid #ddd;
279
+ padding: ${this.SPACING.xs}px ${this.SPACING.md}px;
280
+ border: ${this.BORDER_STYLES.light};
149
281
  border-radius: ${this.config.borderRadius};
150
282
  background: white;
151
283
  cursor: pointer;
152
- font-size: 14px;
284
+ font-size: ${this.FONT_SIZES.body};
153
285
  }
154
286
 
155
287
  .btn-redact:hover,
@@ -163,17 +295,43 @@ class StyleManager {
163
295
  border-color: ${this.config.primaryColor};
164
296
  }
165
297
 
298
+ .screenshot-container {
299
+ margin-top: ${this.SPACING.xs}px;
300
+ position: relative;
301
+ }
302
+
303
+ .screenshot {
304
+ max-width: 100%;
305
+ border: ${this.BORDER_STYLES.light};
306
+ border-radius: ${this.config.borderRadius};
307
+ }
308
+
309
+ .redaction-canvas {
310
+ position: absolute;
311
+ top: 0;
312
+ left: 0;
313
+ cursor: crosshair;
314
+ border: 2px solid ${this.config.primaryColor};
315
+ border-radius: ${this.config.borderRadius};
316
+ }
317
+ `;
318
+ }
319
+ // ============================================================================
320
+ // COMPONENT STYLES - PII DETECTION
321
+ // ============================================================================
322
+ generatePIIStyles() {
323
+ return `
166
324
  .pii-section {
167
- margin-top: 20px;
168
- padding: 15px;
325
+ margin-top: ${this.SPACING.lg}px;
326
+ padding: ${this.SPACING.md}px;
169
327
  background: #fff3cd;
170
328
  border: 1px solid #ffc107;
171
329
  border-radius: ${this.config.borderRadius};
172
330
  }
173
331
 
174
332
  .pii-title {
175
- margin: 0 0 10px 0;
176
- font-size: 14px;
333
+ margin: 0 0 ${this.SPACING.xs}px 0;
334
+ font-size: ${this.FONT_SIZES.body};
177
335
  font-weight: 600;
178
336
  color: #856404;
179
337
  }
@@ -181,7 +339,7 @@ class StyleManager {
181
339
  .pii-list {
182
340
  margin: 0;
183
341
  padding-left: 20px;
184
- font-size: 13px;
342
+ font-size: ${this.FONT_SIZES.sr};
185
343
  color: #856404;
186
344
  }
187
345
 
@@ -192,61 +350,16 @@ class StyleManager {
192
350
  background: #ffc107;
193
351
  color: #856404;
194
352
  border-radius: 12px;
195
- font-size: 12px;
353
+ font-size: ${this.FONT_SIZES.small};
196
354
  font-weight: 500;
197
355
  }
198
-
199
- .checkbox-group {
200
- display: flex;
201
- align-items: center;
202
- gap: 8px;
203
- margin-top: 15px;
204
- }
205
-
206
- .checkbox {
207
- width: 18px;
208
- height: 18px;
209
- cursor: pointer;
210
- }
211
-
212
- .checkbox-label {
213
- margin: 0;
214
- font-size: 14px;
215
- cursor: pointer;
216
- user-select: none;
217
- }
218
-
219
- .footer {
220
- padding: 20px;
221
- border-top: 1px solid #e0e0e0;
222
- display: flex;
223
- justify-content: flex-end;
224
- gap: 10px;
225
- }
226
-
227
- .btn {
228
- padding: 10px 20px;
229
- border: none;
230
- border-radius: ${this.config.borderRadius};
231
- cursor: pointer;
232
- font-size: 14px;
233
- font-weight: 500;
234
- }
235
-
236
- .btn-primary {
237
- background: ${this.config.primaryColor};
238
- color: white;
239
- }
240
-
241
- .btn-primary:hover {
242
- opacity: 0.9;
243
- }
244
-
245
- .btn-primary:disabled {
246
- opacity: 0.5;
247
- cursor: not-allowed;
248
- }
249
-
356
+ `;
357
+ }
358
+ // ============================================================================
359
+ // COMPONENT STYLES - LOADING STATE
360
+ // ============================================================================
361
+ generateLoadingStyles() {
362
+ return `
250
363
  .btn.loading {
251
364
  position: relative;
252
365
  padding-left: 2.5rem;
@@ -269,7 +382,13 @@ class StyleManager {
269
382
  @keyframes spinner {
270
383
  to { transform: rotate(360deg); }
271
384
  }
272
-
385
+ `;
386
+ }
387
+ // ============================================================================
388
+ // COMPONENT STYLES - ACCESSIBILITY
389
+ // ============================================================================
390
+ generateAccessibilityStyles() {
391
+ return `
273
392
  .sr-only {
274
393
  position: absolute;
275
394
  width: 1px;
@@ -281,20 +400,110 @@ class StyleManager {
281
400
  white-space: nowrap;
282
401
  border-width: 0;
283
402
  }
284
-
285
- .btn-secondary {
286
- background: #6c757d;
287
- color: white;
288
- }
289
-
290
- .btn-secondary:hover {
291
- opacity: 0.9;
403
+ `;
404
+ }
405
+ // ============================================================================
406
+ // RESPONSIVE STYLES - TABLET (≤768px)
407
+ // ============================================================================
408
+ generateTabletResponsiveStyles() {
409
+ return `
410
+ @media (max-width: ${this.BREAKPOINTS.tablet}px) {
411
+ .modal {
412
+ width: 95%;
413
+ max-width: ${this.MODAL_SIZES.tablet};
414
+ }
415
+
416
+ .header {
417
+ padding: ${this.SPACING.md}px;
418
+ }
419
+
420
+ .body {
421
+ padding: ${this.SPACING.md}px;
422
+ }
423
+
424
+ .footer {
425
+ padding: ${this.SPACING.md}px;
426
+ }
427
+
428
+ /* Prevent iOS zoom on input focus (requires 16px minimum) */
429
+ .input,
430
+ .textarea {
431
+ padding: ${this.SPACING.xs}px;
432
+ font-size: 16px;
433
+ }
434
+
435
+ .textarea {
436
+ min-height: 80px;
437
+ }
292
438
  }
293
-
294
- .error {
295
- color: ${this.config.dangerColor};
296
- font-size: 12px;
297
- margin-top: 4px;
439
+ `;
440
+ }
441
+ // ============================================================================
442
+ // RESPONSIVE STYLES - MOBILE (≤480px)
443
+ // ============================================================================
444
+ generateMobileResponsiveStyles() {
445
+ return `
446
+ @media (max-width: ${this.BREAKPOINTS.mobile}px) {
447
+ .modal {
448
+ width: ${this.MODAL_SIZES.mobilePercent};
449
+ max-width: 100%;
450
+ max-height: 95vh;
451
+ }
452
+
453
+ .header {
454
+ padding: ${this.SPACING.sm}px;
455
+ }
456
+
457
+ .header h2 {
458
+ font-size: ${this.FONT_SIZES.h2Mobile};
459
+ }
460
+
461
+ .body {
462
+ padding: ${this.SPACING.sm}px;
463
+ }
464
+
465
+ .footer {
466
+ padding: ${this.SPACING.sm}px;
467
+ flex-direction: column;
468
+ }
469
+
470
+ .btn {
471
+ width: 100%;
472
+ padding: ${this.SPACING.md}px;
473
+ }
474
+
475
+ .input,
476
+ .textarea {
477
+ padding: ${this.SPACING.xs}px;
478
+ }
479
+
480
+ .textarea {
481
+ resize: none;
482
+ }
483
+
484
+ .redaction-controls {
485
+ flex-direction: column;
486
+ }
487
+
488
+ .btn-redact,
489
+ .btn-clear {
490
+ width: 100%;
491
+ }
492
+
493
+ .pii-section {
494
+ padding: ${this.SPACING.sm}px;
495
+ margin-top: ${this.SPACING.md}px;
496
+ }
497
+
498
+ .label {
499
+ font-size: ${this.FONT_SIZES.labelMobile};
500
+ }
501
+
502
+ .close {
503
+ width: 28px;
504
+ height: 28px;
505
+ font-size: 20px;
506
+ }
298
507
  }
299
508
  `;
300
509
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bugspotter/sdk",
3
- "version": "0.2.5-alpha.5",
3
+ "version": "0.3.0",
4
4
  "description": "Professional bug reporting SDK with screenshots, session replay, and automatic error capture for web applications",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",