@bugspotter/sdk 0.2.5-alpha.5 → 0.3.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.
- package/.prettierrc +11 -0
- package/CHANGELOG.md +81 -149
- package/dist/bugspotter.min.js +1 -1
- package/dist/capture/console.d.ts +1 -0
- package/dist/capture/console.js +17 -4
- package/dist/capture/network.d.ts +6 -0
- package/dist/capture/network.js +12 -2
- package/dist/capture/screenshot.js +3 -2
- package/dist/core/buffer.js +2 -1
- package/dist/core/bug-reporter.js +16 -5
- package/dist/core/circular-buffer.js +4 -1
- package/dist/core/compress.js +2 -1
- package/dist/core/file-upload-handler.js +5 -2
- package/dist/core/offline-queue.js +5 -2
- package/dist/core/transport.js +4 -2
- package/dist/core/upload-helpers.js +3 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.esm.js +16889 -44
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +3 -1
- package/dist/utils/sanitize-patterns.js +15 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/widget/components/form-validator.js +2 -1
- package/dist/widget/components/style-manager.d.ts +17 -0
- package/dist/widget/components/style-manager.js +318 -108
- package/dist/widget/components/template-manager.js +2 -1
- package/dist/widget/modal.js +11 -4
- package/eslint.config.js +89 -0
- package/package.json +28 -14
- package/rollup.config.js +25 -0
|
@@ -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;
|
package/dist/capture/console.js
CHANGED
|
@@ -3,7 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ConsoleCapture = exports.SDK_LOG_PREFIX = void 0;
|
|
4
4
|
const base_capture_1 = require("./base-capture");
|
|
5
5
|
const circular_buffer_1 = require("../core/circular-buffer");
|
|
6
|
-
const CONSOLE_METHODS = [
|
|
6
|
+
const CONSOLE_METHODS = [
|
|
7
|
+
'log',
|
|
8
|
+
'warn',
|
|
9
|
+
'error',
|
|
10
|
+
'info',
|
|
11
|
+
'debug',
|
|
12
|
+
];
|
|
7
13
|
/**
|
|
8
14
|
* Prefix used for SDK internal log messages
|
|
9
15
|
* Exported for testing purposes
|
|
@@ -27,7 +33,9 @@ class ConsoleCapture extends base_capture_1.BaseCapture {
|
|
|
27
33
|
return '';
|
|
28
34
|
}
|
|
29
35
|
// Sanitize args if sanitizer is enabled
|
|
30
|
-
const sanitizedArgs = this.sanitizer
|
|
36
|
+
const sanitizedArgs = this.sanitizer
|
|
37
|
+
? this.sanitizer.sanitizeConsoleArgs(args)
|
|
38
|
+
: args;
|
|
31
39
|
return sanitizedArgs
|
|
32
40
|
.map((arg) => {
|
|
33
41
|
var _a;
|
|
@@ -57,7 +65,10 @@ class ConsoleCapture extends base_capture_1.BaseCapture {
|
|
|
57
65
|
};
|
|
58
66
|
if (this.captureStackTrace && method === 'error') {
|
|
59
67
|
const stack = this.captureStack();
|
|
60
|
-
log.stack =
|
|
68
|
+
log.stack =
|
|
69
|
+
this.sanitizer && stack
|
|
70
|
+
? this.sanitizer.sanitize(stack)
|
|
71
|
+
: stack;
|
|
61
72
|
}
|
|
62
73
|
return log;
|
|
63
74
|
}
|
|
@@ -72,13 +83,15 @@ class ConsoleCapture extends base_capture_1.BaseCapture {
|
|
|
72
83
|
/**
|
|
73
84
|
* Check if log should be filtered (SDK internal logs)
|
|
74
85
|
* Filters out SDK debug logs (prefix [BugSpotter]) except errors
|
|
86
|
+
* Errors are always captured even if they contain SDK prefix
|
|
75
87
|
*/
|
|
76
88
|
shouldFilterLog(message, level) {
|
|
77
89
|
// Always keep SDK errors for debugging
|
|
78
90
|
if (level === 'error') {
|
|
79
91
|
return false;
|
|
80
92
|
}
|
|
81
|
-
// Filter SDK internal logs (debug/info/warn only)
|
|
93
|
+
// Filter SDK internal logs (debug/info/warn/log only)
|
|
94
|
+
// Use startsWith to only match prefix, not substring anywhere in message
|
|
82
95
|
return message.startsWith(exports.SDK_LOG_PREFIX);
|
|
83
96
|
}
|
|
84
97
|
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> {
|
package/dist/capture/network.js
CHANGED
|
@@ -54,8 +54,18 @@ class NetworkCapture extends base_capture_1.BaseCapture {
|
|
|
54
54
|
return request;
|
|
55
55
|
}
|
|
56
56
|
addRequest(request) {
|
|
57
|
-
if
|
|
58
|
-
|
|
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
|
}
|
|
@@ -17,14 +17,15 @@ class ScreenshotCapture extends base_capture_1.BaseCapture {
|
|
|
17
17
|
}
|
|
18
18
|
getErrorPlaceholder() {
|
|
19
19
|
var _a;
|
|
20
|
-
return (_a = this.options.errorPlaceholder) !== null && _a !== void 0 ? _a : DEFAULT_SCREENSHOT_OPTIONS.errorPlaceholder;
|
|
20
|
+
return ((_a = this.options.errorPlaceholder) !== null && _a !== void 0 ? _a : DEFAULT_SCREENSHOT_OPTIONS.errorPlaceholder);
|
|
21
21
|
}
|
|
22
22
|
shouldIncludeNode(node) {
|
|
23
23
|
if (!('hasAttribute' in node)) {
|
|
24
24
|
return true;
|
|
25
25
|
}
|
|
26
26
|
const element = node;
|
|
27
|
-
const excludeAttr = this.options.excludeAttribute ||
|
|
27
|
+
const excludeAttr = this.options.excludeAttribute ||
|
|
28
|
+
DEFAULT_SCREENSHOT_OPTIONS.excludeAttribute;
|
|
28
29
|
return !element.hasAttribute(excludeAttr);
|
|
29
30
|
}
|
|
30
31
|
buildCaptureOptions() {
|
package/dist/core/buffer.js
CHANGED
|
@@ -57,7 +57,8 @@ class CircularBuffer {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
// Preserve full snapshot even if it's older than cutoff
|
|
60
|
-
if (this.lastFullSnapshotIndex >= 0 &&
|
|
60
|
+
if (this.lastFullSnapshotIndex >= 0 &&
|
|
61
|
+
this.lastFullSnapshotIndex < firstValidIndex) {
|
|
61
62
|
firstValidIndex = this.lastFullSnapshotIndex;
|
|
62
63
|
}
|
|
63
64
|
// Nothing to prune
|
|
@@ -40,12 +40,16 @@ function isBugReportResponse(obj) {
|
|
|
40
40
|
// When success is true, data.id must exist
|
|
41
41
|
if (response.success) {
|
|
42
42
|
const data = response.data;
|
|
43
|
-
if (!data ||
|
|
43
|
+
if (!data ||
|
|
44
|
+
typeof data !== 'object' ||
|
|
45
|
+
!('id' in data) ||
|
|
46
|
+
typeof data.id !== 'string') {
|
|
44
47
|
return false;
|
|
45
48
|
}
|
|
46
49
|
// If presignedUrls exists, it must be an object
|
|
47
50
|
if ('presignedUrls' in data && data.presignedUrls !== undefined) {
|
|
48
|
-
if (typeof data.presignedUrls !== 'object' ||
|
|
51
|
+
if (typeof data.presignedUrls !== 'object' ||
|
|
52
|
+
data.presignedUrls === null) {
|
|
49
53
|
return false;
|
|
50
54
|
}
|
|
51
55
|
}
|
|
@@ -183,7 +187,9 @@ class BugReporter {
|
|
|
183
187
|
success: result.success,
|
|
184
188
|
bugId: bugData.id,
|
|
185
189
|
hasPresignedUrls: !!bugData.presignedUrls,
|
|
186
|
-
presignedUrlKeys: bugData.presignedUrls
|
|
190
|
+
presignedUrlKeys: bugData.presignedUrls
|
|
191
|
+
? Object.keys(bugData.presignedUrls)
|
|
192
|
+
: [],
|
|
187
193
|
});
|
|
188
194
|
return bugData;
|
|
189
195
|
}
|
|
@@ -197,7 +203,9 @@ class BugReporter {
|
|
|
197
203
|
const { report } = payload;
|
|
198
204
|
const fileAnalysis = analyzeReportFiles(report);
|
|
199
205
|
if (!fileAnalysis.hasScreenshot && !fileAnalysis.hasReplay) {
|
|
200
|
-
logger.debug('No files to upload, bug report created successfully', {
|
|
206
|
+
logger.debug('No files to upload, bug report created successfully', {
|
|
207
|
+
bugId,
|
|
208
|
+
});
|
|
201
209
|
this.deduplicator.recordSubmission(dedupContext.title, dedupContext.description, dedupContext.errorStacks);
|
|
202
210
|
return;
|
|
203
211
|
}
|
|
@@ -217,7 +225,10 @@ class BugReporter {
|
|
|
217
225
|
this.deduplicator.recordSubmission(dedupContext.title, dedupContext.description, dedupContext.errorStacks);
|
|
218
226
|
}
|
|
219
227
|
catch (error) {
|
|
220
|
-
logger.error('File upload failed', {
|
|
228
|
+
logger.error('File upload failed', {
|
|
229
|
+
bugId,
|
|
230
|
+
error: formatSubmissionError('Upload', error),
|
|
231
|
+
});
|
|
221
232
|
throw new Error(formatSubmissionError(`Bug report created (ID: ${bugId}) but file upload failed`, error));
|
|
222
233
|
}
|
|
223
234
|
}
|
|
@@ -39,7 +39,10 @@ class CircularBuffer {
|
|
|
39
39
|
return [...this.items];
|
|
40
40
|
}
|
|
41
41
|
// Return items in chronological order when buffer is full
|
|
42
|
-
return [
|
|
42
|
+
return [
|
|
43
|
+
...this.items.slice(this.index),
|
|
44
|
+
...this.items.slice(0, this.index),
|
|
45
|
+
];
|
|
43
46
|
}
|
|
44
47
|
/**
|
|
45
48
|
* Clear all items from the buffer.
|
package/dist/core/compress.js
CHANGED
|
@@ -128,7 +128,8 @@ function supportsWebP() {
|
|
|
128
128
|
}
|
|
129
129
|
try {
|
|
130
130
|
const canvas = document.createElement('canvas');
|
|
131
|
-
webpSupportCache =
|
|
131
|
+
webpSupportCache =
|
|
132
|
+
canvas.toDataURL('image/webp').indexOf('data:image/webp') === 0;
|
|
132
133
|
}
|
|
133
134
|
catch (_a) {
|
|
134
135
|
webpSupportCache = false;
|
|
@@ -35,7 +35,8 @@ class FileUploadHandler {
|
|
|
35
35
|
async prepareFiles(report, presignedUrls) {
|
|
36
36
|
const files = [];
|
|
37
37
|
// Prepare screenshot
|
|
38
|
-
if (report._screenshotPreview &&
|
|
38
|
+
if (report._screenshotPreview &&
|
|
39
|
+
report._screenshotPreview.startsWith('data:image/')) {
|
|
39
40
|
const screenshotUrl = this.getPresignedUrl('screenshot', presignedUrls);
|
|
40
41
|
const screenshotBlob = await this.dataUrlToBlob(report._screenshotPreview);
|
|
41
42
|
files.push({
|
|
@@ -49,7 +50,9 @@ class FileUploadHandler {
|
|
|
49
50
|
if (report.replay && report.replay.length > 0) {
|
|
50
51
|
const replayUrl = this.getPresignedUrl('replay', presignedUrls);
|
|
51
52
|
const compressed = await (0, compress_1.compressData)(report.replay);
|
|
52
|
-
const replayBlob = new Blob([compressed], {
|
|
53
|
+
const replayBlob = new Blob([compressed], {
|
|
54
|
+
type: 'application/gzip',
|
|
55
|
+
});
|
|
53
56
|
files.push({
|
|
54
57
|
type: 'replay',
|
|
55
58
|
url: replayUrl.uploadUrl,
|
|
@@ -247,7 +247,9 @@ class OfflineQueue {
|
|
|
247
247
|
}
|
|
248
248
|
// Check error message as fallback (Firefox, Chrome variants)
|
|
249
249
|
const message = error.message.toLowerCase();
|
|
250
|
-
return message.includes('quota') ||
|
|
250
|
+
return (message.includes('quota') ||
|
|
251
|
+
message.includes('storage') ||
|
|
252
|
+
message.includes('exceeded'));
|
|
251
253
|
}
|
|
252
254
|
/**
|
|
253
255
|
* Clear oldest 50% of items and retry save
|
|
@@ -279,7 +281,8 @@ class OfflineQueue {
|
|
|
279
281
|
else {
|
|
280
282
|
// Fallback to Math.random for environments without crypto
|
|
281
283
|
randomPart =
|
|
282
|
-
Math.random().toString(36).substring(2, 9) +
|
|
284
|
+
Math.random().toString(36).substring(2, 9) +
|
|
285
|
+
Math.random().toString(36).substring(2, 9);
|
|
283
286
|
}
|
|
284
287
|
return `req_${Date.now()}_${this.requestCounter}_${randomPart}`;
|
|
285
288
|
}
|
package/dist/core/transport.js
CHANGED
|
@@ -75,7 +75,8 @@ class RetryHandler {
|
|
|
75
75
|
try {
|
|
76
76
|
const response = await operation();
|
|
77
77
|
// Check if we should retry based on status code
|
|
78
|
-
if (shouldRetryStatus(response.status) &&
|
|
78
|
+
if (shouldRetryStatus(response.status) &&
|
|
79
|
+
attempt < this.config.maxRetries) {
|
|
79
80
|
const delay = this.calculateDelay(attempt, response);
|
|
80
81
|
this.logger.warn(`Request failed with status ${response.status}, retrying in ${delay}ms (attempt ${attempt + 1}/${this.config.maxRetries})`);
|
|
81
82
|
await sleep(delay);
|
|
@@ -239,7 +240,8 @@ function isNetworkError(error) {
|
|
|
239
240
|
error.name === 'NetworkError' ||
|
|
240
241
|
error.name === 'AbortError' ||
|
|
241
242
|
// TypeError only if it mentions fetch or network
|
|
242
|
-
(error.name === 'TypeError' &&
|
|
243
|
+
(error.name === 'TypeError' &&
|
|
244
|
+
(message.includes('fetch') || message.includes('network'))));
|
|
243
245
|
}
|
|
244
246
|
// Re-export offline queue utilities
|
|
245
247
|
var offline_queue_2 = require("./offline-queue");
|
|
@@ -27,7 +27,9 @@ async function compressReplayEvents(events) {
|
|
|
27
27
|
try {
|
|
28
28
|
// Use modern streaming API: Blob → ReadableStream → CompressionStream → Response → Blob
|
|
29
29
|
const blob = new Blob([data]);
|
|
30
|
-
const compressedStream = blob
|
|
30
|
+
const compressedStream = blob
|
|
31
|
+
.stream()
|
|
32
|
+
.pipeThrough(new CompressionStream('gzip'));
|
|
31
33
|
return await new Response(compressedStream, {
|
|
32
34
|
headers: { 'Content-Type': 'application/gzip' },
|
|
33
35
|
}).blob();
|
package/dist/index.d.ts
CHANGED
|
@@ -132,8 +132,8 @@ export type { DOMCollectorConfig } from './collectors';
|
|
|
132
132
|
export { CircularBuffer } from './core/buffer';
|
|
133
133
|
export type { CircularBufferConfig } from './core/buffer';
|
|
134
134
|
export { compressData, decompressData, compressImage, estimateSize, getCompressionRatio, } from './core/compress';
|
|
135
|
-
export { submitWithAuth, getAuthHeaders, clearOfflineQueue } from './core/transport';
|
|
136
|
-
export type { AuthConfig, TransportOptions, RetryConfig } from './core/transport';
|
|
135
|
+
export { submitWithAuth, getAuthHeaders, clearOfflineQueue, } from './core/transport';
|
|
136
|
+
export type { AuthConfig, TransportOptions, RetryConfig, } from './core/transport';
|
|
137
137
|
export type { OfflineConfig } from './core/offline-queue';
|
|
138
138
|
export type { Logger, LogLevel, LoggerConfig } from './utils/logger';
|
|
139
139
|
export { getLogger, configureLogger, createLogger } from './utils/logger';
|
|
@@ -141,8 +141,8 @@ export { DirectUploader } from './core/uploader';
|
|
|
141
141
|
export type { UploadResult } from './core/uploader';
|
|
142
142
|
export { compressReplayEvents, canvasToBlob, estimateCompressedReplaySize, isWithinSizeLimit, } from './core/upload-helpers';
|
|
143
143
|
export { createSanitizer, Sanitizer } from './utils/sanitize';
|
|
144
|
-
export type { PIIPattern, CustomPattern, SanitizeConfig } from './utils/sanitize';
|
|
145
|
-
export { getApiBaseUrl, stripEndpointSuffix, InvalidEndpointError } from './utils/url-helpers';
|
|
144
|
+
export type { PIIPattern, CustomPattern, SanitizeConfig, } from './utils/sanitize';
|
|
145
|
+
export { getApiBaseUrl, stripEndpointSuffix, InvalidEndpointError, } from './utils/url-helpers';
|
|
146
146
|
export { validateAuthConfig } from './utils/config-validator';
|
|
147
147
|
export type { ValidationContext } from './utils/config-validator';
|
|
148
148
|
export { DEFAULT_PATTERNS, PATTERN_PRESETS, PATTERN_CATEGORIES, PatternBuilder, createPatternConfig, getPattern, getPatternsByCategory, validatePattern, } from './utils/sanitize';
|
|
@@ -150,7 +150,7 @@ export type { PIIPatternName, PatternDefinition } from './utils/sanitize';
|
|
|
150
150
|
export { FloatingButton } from './widget/button';
|
|
151
151
|
export type { FloatingButtonOptions } from './widget/button';
|
|
152
152
|
export { BugReportModal } from './widget/modal';
|
|
153
|
-
export type { BugReportData, BugReportModalOptions, PIIDetection } from './widget/modal';
|
|
153
|
+
export type { BugReportData, BugReportModalOptions, PIIDetection, } from './widget/modal';
|
|
154
154
|
export type { eventWithTime } from '@rrweb/types';
|
|
155
155
|
export { DEFAULT_REPLAY_DURATION_SECONDS, MAX_RECOMMENDED_REPLAY_DURATION_SECONDS, } from './constants';
|
|
156
156
|
/**
|