@bugspotter/sdk 2.0.0 → 2.1.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/CONTRIBUTING.md +3 -3
- package/README.md +125 -70
- package/dist/bugspotter.min.js +1 -1
- package/dist/bugspotter.min.js.map +1 -1
- package/dist/collectors/dom.d.ts +4 -0
- package/dist/collectors/dom.js +13 -14
- package/dist/core/bug-reporter.js +6 -4
- package/dist/core/capture-manager.d.ts +2 -0
- package/dist/core/capture-manager.js +3 -1
- package/dist/index.d.ts +31 -10
- package/dist/index.esm.js +103 -47
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +52 -10
- package/dist/utils/config-validator.d.ts +2 -3
- package/dist/utils/config-validator.js +3 -7
- package/dist/utils/sanitize.js +25 -10
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/CDN.md +3 -3
- package/docs/FRAMEWORK_INTEGRATION.md +1 -1
- package/eslint.config.js +7 -0
- package/package.json +4 -4
- package/release_notes.md +1 -1
package/dist/collectors/dom.d.ts
CHANGED
|
@@ -20,6 +20,10 @@ export interface DOMCollectorConfig {
|
|
|
20
20
|
recordCanvas?: boolean;
|
|
21
21
|
/** Whether to record cross-origin iframes (default: false) */
|
|
22
22
|
recordCrossOriginIframes?: boolean;
|
|
23
|
+
/** CSS selectors for elements to block from recording */
|
|
24
|
+
blockSelectors?: string[];
|
|
25
|
+
/** CSS class name to block elements from recording */
|
|
26
|
+
blockClass?: string;
|
|
23
27
|
/** Sanitizer for PII protection */
|
|
24
28
|
sanitizer?: Sanitizer;
|
|
25
29
|
}
|
package/dist/collectors/dom.js
CHANGED
|
@@ -26,6 +26,8 @@ class DOMCollector {
|
|
|
26
26
|
collectFonts: (_h = config.collectFonts) !== null && _h !== void 0 ? _h : false,
|
|
27
27
|
recordCanvas: (_j = config.recordCanvas) !== null && _j !== void 0 ? _j : false,
|
|
28
28
|
recordCrossOriginIframes: (_k = config.recordCrossOriginIframes) !== null && _k !== void 0 ? _k : false,
|
|
29
|
+
blockSelectors: config.blockSelectors,
|
|
30
|
+
blockClass: config.blockClass,
|
|
29
31
|
sanitizer: config.sanitizer,
|
|
30
32
|
};
|
|
31
33
|
this.buffer = new buffer_1.CircularBuffer({
|
|
@@ -36,30 +38,26 @@ class DOMCollector {
|
|
|
36
38
|
* Start recording DOM events
|
|
37
39
|
*/
|
|
38
40
|
startRecording() {
|
|
39
|
-
var _a, _b, _c, _d;
|
|
41
|
+
var _a, _b, _c, _d, _e;
|
|
40
42
|
if (this.isRecording) {
|
|
41
43
|
(0, logger_1.getLogger)().warn('DOMCollector: Recording already in progress');
|
|
42
44
|
return;
|
|
43
45
|
}
|
|
44
46
|
try {
|
|
45
|
-
const recordConfig = {
|
|
46
|
-
emit: (event) => {
|
|
47
|
+
const recordConfig = Object.assign(Object.assign({ emit: (event) => {
|
|
47
48
|
this.buffer.add(event);
|
|
48
|
-
},
|
|
49
|
-
sampling: {
|
|
49
|
+
}, sampling: {
|
|
50
50
|
mousemove: (_b = (_a = this.config.sampling) === null || _a === void 0 ? void 0 : _a.mousemove) !== null && _b !== void 0 ? _b : 50,
|
|
51
51
|
scroll: (_d = (_c = this.config.sampling) === null || _c === void 0 ? void 0 : _c.scroll) !== null && _d !== void 0 ? _d : 100,
|
|
52
52
|
// Record all mouse interactions for replay visibility
|
|
53
53
|
mouseInteraction: true,
|
|
54
|
-
},
|
|
55
|
-
recordCanvas: this.config.recordCanvas,
|
|
56
|
-
recordCrossOriginIframes: this.config.recordCrossOriginIframes,
|
|
54
|
+
}, recordCanvas: this.config.recordCanvas, recordCrossOriginIframes: this.config.recordCrossOriginIframes,
|
|
57
55
|
// PII sanitization for text content
|
|
58
56
|
maskTextFn: this.sanitizer
|
|
59
57
|
? (text, element) => {
|
|
60
58
|
return this.sanitizer.sanitizeTextNode(text, element);
|
|
61
59
|
}
|
|
62
|
-
: undefined,
|
|
60
|
+
: undefined,
|
|
63
61
|
// Performance optimizations
|
|
64
62
|
slimDOMOptions: {
|
|
65
63
|
script: true, // Don't record script tags
|
|
@@ -71,12 +69,13 @@ class DOMCollector {
|
|
|
71
69
|
headMetaHttpEquiv: true, // Don't record http-equiv meta tags
|
|
72
70
|
headMetaAuthorship: true, // Don't record authorship meta tags
|
|
73
71
|
headMetaVerification: true, // Don't record verification meta tags
|
|
74
|
-
},
|
|
72
|
+
},
|
|
75
73
|
// Quality settings (controlled by backend or user config)
|
|
76
|
-
inlineStylesheet: this.config.inlineStylesheet,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
inlineStylesheet: this.config.inlineStylesheet, inlineImages: this.config.inlineImages, collectFonts: this.config.collectFonts }, (((_e = this.config.blockSelectors) === null || _e === void 0 ? void 0 : _e.length) && {
|
|
75
|
+
blockSelector: this.config.blockSelectors.join(','),
|
|
76
|
+
})), (this.config.blockClass && {
|
|
77
|
+
blockClass: this.config.blockClass,
|
|
78
|
+
}));
|
|
80
79
|
this.stopRecordingFn = (0, rrweb_1.record)(recordConfig);
|
|
81
80
|
this.isRecording = true;
|
|
82
81
|
(0, logger_1.getLogger)().debug('DOMCollector: Started recording');
|
|
@@ -137,7 +137,7 @@ class BugReporter {
|
|
|
137
137
|
var _a;
|
|
138
138
|
(0, config_validator_1.validateAuthConfig)({
|
|
139
139
|
endpoint: this.config.endpoint,
|
|
140
|
-
|
|
140
|
+
apiKey: this.config.apiKey,
|
|
141
141
|
});
|
|
142
142
|
const dedupContext = createDeduplicationContext(payload);
|
|
143
143
|
if (this.deduplicator.isDuplicate(dedupContext.title, dedupContext.description, dedupContext.errorStacks)) {
|
|
@@ -165,8 +165,10 @@ class BugReporter {
|
|
|
165
165
|
network: report.network,
|
|
166
166
|
metadata: report.metadata,
|
|
167
167
|
}, hasScreenshot: fileAnalysis.hasScreenshot, hasReplay: fileAnalysis.hasReplay });
|
|
168
|
-
const
|
|
169
|
-
|
|
168
|
+
const apiBaseUrl = (0, url_helpers_1.getApiBaseUrl)(this.config.endpoint);
|
|
169
|
+
const submitUrl = `${apiBaseUrl}/api/v1/reports`;
|
|
170
|
+
const response = await (0, transport_1.submitWithAuth)(submitUrl, JSON.stringify(createPayload), { 'Content-Type': 'application/json' }, {
|
|
171
|
+
auth: { apiKey: this.config.apiKey },
|
|
170
172
|
retry: this.config.retry,
|
|
171
173
|
offline: this.config.offline,
|
|
172
174
|
});
|
|
@@ -218,7 +220,7 @@ class BugReporter {
|
|
|
218
220
|
throw new Error('Server did not provide presigned URLs for file uploads. Check backend configuration.');
|
|
219
221
|
}
|
|
220
222
|
const apiEndpoint = (0, url_helpers_1.getApiBaseUrl)(this.config.endpoint);
|
|
221
|
-
const uploadHandler = new file_upload_handler_1.FileUploadHandler(apiEndpoint, this.config.
|
|
223
|
+
const uploadHandler = new file_upload_handler_1.FileUploadHandler(apiEndpoint, this.config.apiKey);
|
|
222
224
|
try {
|
|
223
225
|
await uploadHandler.uploadFiles(bugId, report, bugReportData.presignedUrls);
|
|
224
226
|
logger.debug('File uploads completed successfully', { bugId });
|
|
@@ -17,7 +17,7 @@ const constants_1 = require("../constants");
|
|
|
17
17
|
*/
|
|
18
18
|
class CaptureManager {
|
|
19
19
|
constructor(config) {
|
|
20
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
20
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
21
21
|
// Initialize core capture modules
|
|
22
22
|
this.screenshot = new screenshot_1.ScreenshotCapture();
|
|
23
23
|
this.console = new console_1.ConsoleCapture({ sanitizer: config.sanitizer });
|
|
@@ -42,6 +42,8 @@ class CaptureManager {
|
|
|
42
42
|
collectFonts: (_h = config.replay) === null || _h === void 0 ? void 0 : _h.collectFonts,
|
|
43
43
|
recordCanvas: (_j = config.replay) === null || _j === void 0 ? void 0 : _j.recordCanvas,
|
|
44
44
|
recordCrossOriginIframes: (_k = config.replay) === null || _k === void 0 ? void 0 : _k.recordCrossOriginIframes,
|
|
45
|
+
blockSelectors: (_l = config.replay) === null || _l === void 0 ? void 0 : _l.blockSelectors,
|
|
46
|
+
blockClass: (_m = config.replay) === null || _m === void 0 ? void 0 : _m.blockClass,
|
|
45
47
|
sanitizer: config.sanitizer,
|
|
46
48
|
});
|
|
47
49
|
this.domCollector.startRecording();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { BrowserMetadata } from './capture/metadata';
|
|
2
2
|
import { type FloatingButtonOptions } from './widget/button';
|
|
3
3
|
import type { eventWithTime } from '@rrweb/types';
|
|
4
|
-
import type {
|
|
4
|
+
import type { RetryConfig } from './core/transport';
|
|
5
5
|
import type { OfflineConfig } from './core/offline-queue';
|
|
6
6
|
import { VERSION } from './version';
|
|
7
7
|
import { type DeduplicationConfig } from './utils/deduplicator';
|
|
@@ -12,9 +12,10 @@ export declare class BugSpotter {
|
|
|
12
12
|
private config;
|
|
13
13
|
private widget?;
|
|
14
14
|
private sanitizer?;
|
|
15
|
-
private captureManager
|
|
15
|
+
private captureManager?;
|
|
16
16
|
private bugReporter;
|
|
17
|
-
|
|
17
|
+
private _sampled;
|
|
18
|
+
constructor(config: BugSpotterConfig, sampled?: boolean);
|
|
18
19
|
static init(config: BugSpotterConfig): Promise<BugSpotter>;
|
|
19
20
|
/**
|
|
20
21
|
* Internal factory method to create a new BugSpotter instance
|
|
@@ -27,6 +28,8 @@ export declare class BugSpotter {
|
|
|
27
28
|
* Note: Screenshot is captured for modal preview only (_screenshotPreview)
|
|
28
29
|
* File uploads use presigned URLs returned from the backend
|
|
29
30
|
*/
|
|
31
|
+
/** Whether this session was sampled for capture */
|
|
32
|
+
get isSampled(): boolean;
|
|
30
33
|
capture(): Promise<BugReport>;
|
|
31
34
|
private handleBugReport;
|
|
32
35
|
/**
|
|
@@ -39,14 +42,19 @@ export declare class BugSpotter {
|
|
|
39
42
|
destroy(): void;
|
|
40
43
|
}
|
|
41
44
|
export interface BugSpotterConfig {
|
|
45
|
+
/** Base URL of BugSpotter API (e.g., https://api.example.com). SDK appends paths internally. */
|
|
42
46
|
endpoint?: string;
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
/** API key for authentication (starts with 'bgs_'). Required. */
|
|
48
|
+
apiKey: string;
|
|
45
49
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
50
|
+
* Session sampling rate (0 to 1). Controls what fraction of sessions activate capture.
|
|
51
|
+
* - 1 = capture all sessions (default)
|
|
52
|
+
* - 0.1 = capture 10% of sessions
|
|
53
|
+
* - 0 = capture nothing (SDK initializes but is inactive)
|
|
48
54
|
*/
|
|
49
|
-
|
|
55
|
+
sampleRate?: number;
|
|
56
|
+
showWidget?: boolean;
|
|
57
|
+
widgetOptions?: FloatingButtonOptions;
|
|
50
58
|
/** Retry configuration for failed requests */
|
|
51
59
|
retry?: RetryConfig;
|
|
52
60
|
/** Offline queue configuration */
|
|
@@ -76,6 +84,19 @@ export interface BugSpotterConfig {
|
|
|
76
84
|
recordCanvas?: boolean;
|
|
77
85
|
/** Whether to record cross-origin iframes (default: backend controlled) */
|
|
78
86
|
recordCrossOriginIframes?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* CSS selectors for DOM elements to exclude from session replay.
|
|
89
|
+
* Matched elements are replaced with a placeholder in the recording.
|
|
90
|
+
* Use for sensitive content that isn't PII (e.g., financial data, portfolios).
|
|
91
|
+
* Example: ['.portfolio-table', '#balance-widget', '[data-sensitive]']
|
|
92
|
+
*/
|
|
93
|
+
blockSelectors?: string[];
|
|
94
|
+
/**
|
|
95
|
+
* CSS class name to mark elements for blocking. Any element with this class
|
|
96
|
+
* will be excluded from replay. Alternative to blockSelectors.
|
|
97
|
+
* Example: 'bugspotter-block'
|
|
98
|
+
*/
|
|
99
|
+
blockClass?: string;
|
|
79
100
|
};
|
|
80
101
|
sanitize?: {
|
|
81
102
|
/** Enable PII sanitization (default: true) */
|
|
@@ -85,7 +106,7 @@ export interface BugSpotterConfig {
|
|
|
85
106
|
* - Can be a preset name: 'all', 'minimal', 'financial', 'contact', 'gdpr', 'pci', etc.
|
|
86
107
|
* - Or an array of pattern names: ['email', 'phone', 'ip']
|
|
87
108
|
*/
|
|
88
|
-
patterns?: 'all' | 'minimal' | 'financial' | 'contact' | 'identification' | 'kazakhstan' | 'gdpr' | 'pci' | Array<'email' | 'phone' | 'creditcard' | 'ssn' | 'iin' | 'ip' | 'custom'>;
|
|
109
|
+
patterns?: 'all' | 'minimal' | 'financial' | 'contact' | 'identification' | 'credentials' | 'kazakhstan' | 'gdpr' | 'pci' | Array<'email' | 'phone' | 'creditcard' | 'ssn' | 'iin' | 'ip' | 'apikey' | 'token' | 'password' | 'custom'>;
|
|
89
110
|
/** Custom regex patterns for PII detection */
|
|
90
111
|
customPatterns?: Array<{
|
|
91
112
|
name: string;
|
|
@@ -133,7 +154,7 @@ export { CircularBuffer } from './core/buffer';
|
|
|
133
154
|
export type { CircularBufferConfig } from './core/buffer';
|
|
134
155
|
export { compressData, decompressData, compressImage, estimateSize, getCompressionRatio, } from './core/compress';
|
|
135
156
|
export { submitWithAuth, getAuthHeaders, clearOfflineQueue, } from './core/transport';
|
|
136
|
-
export type {
|
|
157
|
+
export type { TransportOptions, RetryConfig } from './core/transport';
|
|
137
158
|
export type { OfflineConfig } from './core/offline-queue';
|
|
138
159
|
export type { Logger, LogLevel, LoggerConfig } from './utils/logger';
|
|
139
160
|
export { getLogger, configureLogger, createLogger } from './utils/logger';
|
package/dist/index.esm.js
CHANGED
|
@@ -2344,6 +2344,7 @@ class StringSanitizer {
|
|
|
2344
2344
|
class ValueSanitizer {
|
|
2345
2345
|
constructor(stringSanitizer) {
|
|
2346
2346
|
this.stringSanitizer = stringSanitizer;
|
|
2347
|
+
this.seen = new WeakSet();
|
|
2347
2348
|
}
|
|
2348
2349
|
sanitize(value) {
|
|
2349
2350
|
// Handle null/undefined
|
|
@@ -2354,26 +2355,40 @@ class ValueSanitizer {
|
|
|
2354
2355
|
if (typeof value === 'string') {
|
|
2355
2356
|
return this.stringSanitizer.sanitize(value);
|
|
2356
2357
|
}
|
|
2357
|
-
// Handle arrays
|
|
2358
|
+
// Handle arrays (with circular reference protection)
|
|
2358
2359
|
if (Array.isArray(value)) {
|
|
2359
|
-
|
|
2360
|
-
return
|
|
2361
|
-
|
|
2360
|
+
if (this.seen.has(value))
|
|
2361
|
+
return '[Circular]';
|
|
2362
|
+
this.seen.add(value);
|
|
2363
|
+
try {
|
|
2364
|
+
return value.map((item) => this.sanitize(item));
|
|
2365
|
+
}
|
|
2366
|
+
finally {
|
|
2367
|
+
this.seen.delete(value);
|
|
2368
|
+
}
|
|
2362
2369
|
}
|
|
2363
|
-
// Handle objects
|
|
2370
|
+
// Handle objects (with circular reference protection)
|
|
2364
2371
|
if (typeof value === 'object') {
|
|
2372
|
+
if (this.seen.has(value))
|
|
2373
|
+
return '[Circular]';
|
|
2365
2374
|
return this.sanitizeObject(value);
|
|
2366
2375
|
}
|
|
2367
2376
|
// Return primitives as-is
|
|
2368
2377
|
return value;
|
|
2369
2378
|
}
|
|
2370
2379
|
sanitizeObject(obj) {
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
const
|
|
2374
|
-
|
|
2380
|
+
this.seen.add(obj);
|
|
2381
|
+
try {
|
|
2382
|
+
const sanitized = {};
|
|
2383
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
2384
|
+
const sanitizedKey = this.stringSanitizer.sanitize(key);
|
|
2385
|
+
sanitized[sanitizedKey] = this.sanitize(val);
|
|
2386
|
+
}
|
|
2387
|
+
return sanitized;
|
|
2388
|
+
}
|
|
2389
|
+
finally {
|
|
2390
|
+
this.seen.delete(obj);
|
|
2375
2391
|
}
|
|
2376
|
-
return sanitized;
|
|
2377
2392
|
}
|
|
2378
2393
|
}
|
|
2379
2394
|
/**
|
|
@@ -2944,14 +2959,14 @@ const MAX_RECOMMENDED_REPLAY_DURATION_SECONDS = 30;
|
|
|
2944
2959
|
* This file is automatically generated during the build process.
|
|
2945
2960
|
* To update the version, modify package.json
|
|
2946
2961
|
*/
|
|
2947
|
-
const VERSION = '2.
|
|
2962
|
+
const VERSION = '2.1.0';
|
|
2948
2963
|
|
|
2949
2964
|
/**
|
|
2950
2965
|
* Configuration Validation Utilities
|
|
2951
2966
|
* Validates BugSpotter configuration before use
|
|
2952
2967
|
*/
|
|
2953
2968
|
/**
|
|
2954
|
-
* Validate
|
|
2969
|
+
* Validate configuration before submitting a bug report
|
|
2955
2970
|
* @throws Error if configuration is invalid
|
|
2956
2971
|
*/
|
|
2957
2972
|
function validateAuthConfig(context) {
|
|
@@ -2959,15 +2974,11 @@ function validateAuthConfig(context) {
|
|
|
2959
2974
|
throw new Error('No endpoint configured for bug report submission');
|
|
2960
2975
|
}
|
|
2961
2976
|
// SECURITY: Ensure endpoint uses HTTPS
|
|
2962
|
-
// This prevents credentials and sensitive data from being sent over plain HTTP
|
|
2963
2977
|
if (!isSecureEndpoint(context.endpoint)) {
|
|
2964
2978
|
throw new InsecureEndpointError(context.endpoint);
|
|
2965
2979
|
}
|
|
2966
|
-
if (!context.
|
|
2967
|
-
throw new Error('API key
|
|
2968
|
-
}
|
|
2969
|
-
if (!context.auth.apiKey) {
|
|
2970
|
-
throw new Error('API key is required in auth configuration');
|
|
2980
|
+
if (!context.apiKey) {
|
|
2981
|
+
throw new Error('API key is required');
|
|
2971
2982
|
}
|
|
2972
2983
|
}
|
|
2973
2984
|
/**
|
|
@@ -15455,6 +15466,8 @@ class DOMCollector {
|
|
|
15455
15466
|
collectFonts: (_h = config.collectFonts) !== null && _h !== void 0 ? _h : false,
|
|
15456
15467
|
recordCanvas: (_j = config.recordCanvas) !== null && _j !== void 0 ? _j : false,
|
|
15457
15468
|
recordCrossOriginIframes: (_k = config.recordCrossOriginIframes) !== null && _k !== void 0 ? _k : false,
|
|
15469
|
+
blockSelectors: config.blockSelectors,
|
|
15470
|
+
blockClass: config.blockClass,
|
|
15458
15471
|
sanitizer: config.sanitizer,
|
|
15459
15472
|
};
|
|
15460
15473
|
this.buffer = new CircularBuffer({
|
|
@@ -15465,30 +15478,26 @@ class DOMCollector {
|
|
|
15465
15478
|
* Start recording DOM events
|
|
15466
15479
|
*/
|
|
15467
15480
|
startRecording() {
|
|
15468
|
-
var _a, _b, _c, _d;
|
|
15481
|
+
var _a, _b, _c, _d, _e;
|
|
15469
15482
|
if (this.isRecording) {
|
|
15470
15483
|
getLogger().warn('DOMCollector: Recording already in progress');
|
|
15471
15484
|
return;
|
|
15472
15485
|
}
|
|
15473
15486
|
try {
|
|
15474
|
-
const recordConfig = {
|
|
15475
|
-
emit: (event) => {
|
|
15487
|
+
const recordConfig = Object.assign(Object.assign({ emit: (event) => {
|
|
15476
15488
|
this.buffer.add(event);
|
|
15477
|
-
},
|
|
15478
|
-
sampling: {
|
|
15489
|
+
}, sampling: {
|
|
15479
15490
|
mousemove: (_b = (_a = this.config.sampling) === null || _a === void 0 ? void 0 : _a.mousemove) !== null && _b !== void 0 ? _b : 50,
|
|
15480
15491
|
scroll: (_d = (_c = this.config.sampling) === null || _c === void 0 ? void 0 : _c.scroll) !== null && _d !== void 0 ? _d : 100,
|
|
15481
15492
|
// Record all mouse interactions for replay visibility
|
|
15482
15493
|
mouseInteraction: true,
|
|
15483
|
-
},
|
|
15484
|
-
recordCanvas: this.config.recordCanvas,
|
|
15485
|
-
recordCrossOriginIframes: this.config.recordCrossOriginIframes,
|
|
15494
|
+
}, recordCanvas: this.config.recordCanvas, recordCrossOriginIframes: this.config.recordCrossOriginIframes,
|
|
15486
15495
|
// PII sanitization for text content
|
|
15487
15496
|
maskTextFn: this.sanitizer
|
|
15488
15497
|
? (text, element) => {
|
|
15489
15498
|
return this.sanitizer.sanitizeTextNode(text, element);
|
|
15490
15499
|
}
|
|
15491
|
-
: undefined,
|
|
15500
|
+
: undefined,
|
|
15492
15501
|
// Performance optimizations
|
|
15493
15502
|
slimDOMOptions: {
|
|
15494
15503
|
script: true, // Don't record script tags
|
|
@@ -15500,12 +15509,13 @@ class DOMCollector {
|
|
|
15500
15509
|
headMetaHttpEquiv: true, // Don't record http-equiv meta tags
|
|
15501
15510
|
headMetaAuthorship: true, // Don't record authorship meta tags
|
|
15502
15511
|
headMetaVerification: true, // Don't record verification meta tags
|
|
15503
|
-
},
|
|
15512
|
+
},
|
|
15504
15513
|
// Quality settings (controlled by backend or user config)
|
|
15505
|
-
inlineStylesheet: this.config.inlineStylesheet,
|
|
15506
|
-
|
|
15507
|
-
|
|
15508
|
-
|
|
15514
|
+
inlineStylesheet: this.config.inlineStylesheet, inlineImages: this.config.inlineImages, collectFonts: this.config.collectFonts }, (((_e = this.config.blockSelectors) === null || _e === void 0 ? void 0 : _e.length) && {
|
|
15515
|
+
blockSelector: this.config.blockSelectors.join(','),
|
|
15516
|
+
})), (this.config.blockClass && {
|
|
15517
|
+
blockClass: this.config.blockClass,
|
|
15518
|
+
}));
|
|
15509
15519
|
this.stopRecordingFn = record(recordConfig);
|
|
15510
15520
|
this.isRecording = true;
|
|
15511
15521
|
getLogger().debug('DOMCollector: Started recording');
|
|
@@ -15595,7 +15605,7 @@ class DOMCollector {
|
|
|
15595
15605
|
*/
|
|
15596
15606
|
class CaptureManager {
|
|
15597
15607
|
constructor(config) {
|
|
15598
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
15608
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
15599
15609
|
// Initialize core capture modules
|
|
15600
15610
|
this.screenshot = new ScreenshotCapture();
|
|
15601
15611
|
this.console = new ConsoleCapture({ sanitizer: config.sanitizer });
|
|
@@ -15620,6 +15630,8 @@ class CaptureManager {
|
|
|
15620
15630
|
collectFonts: (_h = config.replay) === null || _h === void 0 ? void 0 : _h.collectFonts,
|
|
15621
15631
|
recordCanvas: (_j = config.replay) === null || _j === void 0 ? void 0 : _j.recordCanvas,
|
|
15622
15632
|
recordCrossOriginIframes: (_k = config.replay) === null || _k === void 0 ? void 0 : _k.recordCrossOriginIframes,
|
|
15633
|
+
blockSelectors: (_l = config.replay) === null || _l === void 0 ? void 0 : _l.blockSelectors,
|
|
15634
|
+
blockClass: (_m = config.replay) === null || _m === void 0 ? void 0 : _m.blockClass,
|
|
15623
15635
|
sanitizer: config.sanitizer,
|
|
15624
15636
|
});
|
|
15625
15637
|
this.domCollector.startRecording();
|
|
@@ -16742,7 +16754,7 @@ class BugReporter {
|
|
|
16742
16754
|
var _a;
|
|
16743
16755
|
validateAuthConfig({
|
|
16744
16756
|
endpoint: this.config.endpoint,
|
|
16745
|
-
|
|
16757
|
+
apiKey: this.config.apiKey,
|
|
16746
16758
|
});
|
|
16747
16759
|
const dedupContext = createDeduplicationContext(payload);
|
|
16748
16760
|
if (this.deduplicator.isDuplicate(dedupContext.title, dedupContext.description, dedupContext.errorStacks)) {
|
|
@@ -16771,8 +16783,10 @@ class BugReporter {
|
|
|
16771
16783
|
network: report.network,
|
|
16772
16784
|
metadata: report.metadata,
|
|
16773
16785
|
}, hasScreenshot: fileAnalysis.hasScreenshot, hasReplay: fileAnalysis.hasReplay });
|
|
16774
|
-
const
|
|
16775
|
-
|
|
16786
|
+
const apiBaseUrl = getApiBaseUrl(this.config.endpoint);
|
|
16787
|
+
const submitUrl = `${apiBaseUrl}/api/v1/reports`;
|
|
16788
|
+
const response = yield submitWithAuth(submitUrl, JSON.stringify(createPayload), { 'Content-Type': 'application/json' }, {
|
|
16789
|
+
auth: { apiKey: this.config.apiKey },
|
|
16776
16790
|
retry: this.config.retry,
|
|
16777
16791
|
offline: this.config.offline,
|
|
16778
16792
|
});
|
|
@@ -16826,7 +16840,7 @@ class BugReporter {
|
|
|
16826
16840
|
throw new Error('Server did not provide presigned URLs for file uploads. Check backend configuration.');
|
|
16827
16841
|
}
|
|
16828
16842
|
const apiEndpoint = getApiBaseUrl(this.config.endpoint);
|
|
16829
|
-
const uploadHandler = new FileUploadHandler(apiEndpoint, this.config.
|
|
16843
|
+
const uploadHandler = new FileUploadHandler(apiEndpoint, this.config.apiKey);
|
|
16830
16844
|
try {
|
|
16831
16845
|
yield uploadHandler.uploadFiles(bugId, report, bugReportData.presignedUrls);
|
|
16832
16846
|
logger$1.debug('File uploads completed successfully', { bugId });
|
|
@@ -17222,13 +17236,20 @@ function fetchReplaySettings(endpoint, apiKey) {
|
|
|
17222
17236
|
});
|
|
17223
17237
|
}
|
|
17224
17238
|
class BugSpotter {
|
|
17225
|
-
constructor(config) {
|
|
17239
|
+
constructor(config, sampled = true) {
|
|
17226
17240
|
var _a, _b, _c, _d, _e, _f;
|
|
17227
17241
|
// Validate deduplication configuration if provided
|
|
17228
17242
|
if (config.deduplication) {
|
|
17229
17243
|
validateDeduplicationConfig(config.deduplication);
|
|
17230
17244
|
}
|
|
17231
17245
|
this.config = config;
|
|
17246
|
+
this._sampled = sampled;
|
|
17247
|
+
this.bugReporter = new BugReporter(config);
|
|
17248
|
+
// If not sampled, skip all capture initialization — true zero overhead
|
|
17249
|
+
// No console/network interception, no DOM recording, no widget
|
|
17250
|
+
if (!sampled) {
|
|
17251
|
+
return;
|
|
17252
|
+
}
|
|
17232
17253
|
// Initialize sanitizer (enabled by default)
|
|
17233
17254
|
const sanitizeEnabled = (_b = (_a = config.sanitize) === null || _a === void 0 ? void 0 : _a.enabled) !== null && _b !== void 0 ? _b : true;
|
|
17234
17255
|
if (sanitizeEnabled) {
|
|
@@ -17241,8 +17262,6 @@ class BugSpotter {
|
|
|
17241
17262
|
}
|
|
17242
17263
|
// Initialize capture manager
|
|
17243
17264
|
this.captureManager = new CaptureManager(Object.assign(Object.assign({ sanitizer: this.sanitizer }, (config.endpoint && { apiEndpoint: getApiBaseUrl(config.endpoint) })), { replay: config.replay }));
|
|
17244
|
-
// Initialize bug reporter
|
|
17245
|
-
this.bugReporter = new BugReporter(config);
|
|
17246
17265
|
// Initialize widget (enabled by default)
|
|
17247
17266
|
const widgetEnabled = (_f = config.showWidget) !== null && _f !== void 0 ? _f : true;
|
|
17248
17267
|
if (widgetEnabled) {
|
|
@@ -17283,17 +17302,33 @@ class BugSpotter {
|
|
|
17283
17302
|
*/
|
|
17284
17303
|
static createInstance(config) {
|
|
17285
17304
|
return __awaiter$1(this, void 0, void 0, function* () {
|
|
17286
|
-
var _a, _b
|
|
17305
|
+
var _a, _b;
|
|
17306
|
+
// Check sampling rate — if this session is not sampled, disable all capture
|
|
17307
|
+
if (config.sampleRate !== undefined) {
|
|
17308
|
+
if (typeof config.sampleRate !== 'number' ||
|
|
17309
|
+
!Number.isFinite(config.sampleRate) ||
|
|
17310
|
+
config.sampleRate < 0 ||
|
|
17311
|
+
config.sampleRate > 1) {
|
|
17312
|
+
throw new Error('sampleRate must be a finite number between 0 and 1');
|
|
17313
|
+
}
|
|
17314
|
+
if (Math.random() >= config.sampleRate) {
|
|
17315
|
+
// Create a lightweight no-op instance — zero overhead (no console/network interception)
|
|
17316
|
+
return new BugSpotter(config, /* sampled */ false);
|
|
17317
|
+
}
|
|
17318
|
+
}
|
|
17287
17319
|
// Fetch replay quality settings from backend if replay is enabled
|
|
17288
17320
|
let backendSettings = null;
|
|
17289
17321
|
const replayEnabled = (_b = (_a = config.replay) === null || _a === void 0 ? void 0 : _a.enabled) !== null && _b !== void 0 ? _b : true;
|
|
17290
17322
|
if (replayEnabled && config.endpoint) {
|
|
17291
|
-
//
|
|
17292
|
-
if (!(
|
|
17323
|
+
// SECURITY: Don't send API key over insecure connection
|
|
17324
|
+
if (!isSecureEndpoint(config.endpoint)) {
|
|
17325
|
+
logger.warn('Insecure endpoint — skipping backend settings fetch to protect API key.');
|
|
17326
|
+
}
|
|
17327
|
+
else if (!config.apiKey) {
|
|
17293
17328
|
logger.warn('Endpoint provided but no API key configured. Skipping backend settings fetch.');
|
|
17294
17329
|
}
|
|
17295
17330
|
else {
|
|
17296
|
-
backendSettings = yield fetchReplaySettings(config.endpoint, config.
|
|
17331
|
+
backendSettings = yield fetchReplaySettings(config.endpoint, config.apiKey);
|
|
17297
17332
|
}
|
|
17298
17333
|
}
|
|
17299
17334
|
// Merge backend settings with user config (user config takes precedence)
|
|
@@ -17309,8 +17344,29 @@ class BugSpotter {
|
|
|
17309
17344
|
* Note: Screenshot is captured for modal preview only (_screenshotPreview)
|
|
17310
17345
|
* File uploads use presigned URLs returned from the backend
|
|
17311
17346
|
*/
|
|
17347
|
+
/** Whether this session was sampled for capture */
|
|
17348
|
+
get isSampled() {
|
|
17349
|
+
return this._sampled;
|
|
17350
|
+
}
|
|
17312
17351
|
capture() {
|
|
17313
17352
|
return __awaiter$1(this, void 0, void 0, function* () {
|
|
17353
|
+
if (!this.captureManager) {
|
|
17354
|
+
// Unsampled session — return minimal valid report
|
|
17355
|
+
return {
|
|
17356
|
+
console: [],
|
|
17357
|
+
network: [],
|
|
17358
|
+
metadata: {
|
|
17359
|
+
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',
|
|
17360
|
+
url: typeof window !== 'undefined' ? window.location.href : '',
|
|
17361
|
+
timestamp: Date.now(),
|
|
17362
|
+
viewport: typeof window !== 'undefined'
|
|
17363
|
+
? { width: window.innerWidth, height: window.innerHeight }
|
|
17364
|
+
: { width: 0, height: 0 },
|
|
17365
|
+
browser: 'unknown',
|
|
17366
|
+
os: 'unknown',
|
|
17367
|
+
},
|
|
17368
|
+
};
|
|
17369
|
+
}
|
|
17314
17370
|
return yield this.captureManager.captureAll();
|
|
17315
17371
|
});
|
|
17316
17372
|
}
|
|
@@ -17354,9 +17410,9 @@ class BugSpotter {
|
|
|
17354
17410
|
return Object.assign({}, this.config);
|
|
17355
17411
|
}
|
|
17356
17412
|
destroy() {
|
|
17357
|
-
var _a;
|
|
17358
|
-
this.captureManager.destroy();
|
|
17359
|
-
(
|
|
17413
|
+
var _a, _b;
|
|
17414
|
+
(_a = this.captureManager) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
17415
|
+
(_b = this.widget) === null || _b === void 0 ? void 0 : _b.destroy();
|
|
17360
17416
|
this.bugReporter.destroy();
|
|
17361
17417
|
BugSpotter.instance = undefined;
|
|
17362
17418
|
BugSpotter.initPromise = undefined;
|