@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/index.js
CHANGED
|
@@ -91,13 +91,20 @@ async function fetchReplaySettings(endpoint, apiKey) {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
class BugSpotter {
|
|
94
|
-
constructor(config) {
|
|
94
|
+
constructor(config, sampled = true) {
|
|
95
95
|
var _a, _b, _c, _d, _e, _f;
|
|
96
96
|
// Validate deduplication configuration if provided
|
|
97
97
|
if (config.deduplication) {
|
|
98
98
|
(0, config_validator_1.validateDeduplicationConfig)(config.deduplication);
|
|
99
99
|
}
|
|
100
100
|
this.config = config;
|
|
101
|
+
this._sampled = sampled;
|
|
102
|
+
this.bugReporter = new bug_reporter_1.BugReporter(config);
|
|
103
|
+
// If not sampled, skip all capture initialization — true zero overhead
|
|
104
|
+
// No console/network interception, no DOM recording, no widget
|
|
105
|
+
if (!sampled) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
101
108
|
// Initialize sanitizer (enabled by default)
|
|
102
109
|
const sanitizeEnabled = (_b = (_a = config.sanitize) === null || _a === void 0 ? void 0 : _a.enabled) !== null && _b !== void 0 ? _b : true;
|
|
103
110
|
if (sanitizeEnabled) {
|
|
@@ -110,8 +117,6 @@ class BugSpotter {
|
|
|
110
117
|
}
|
|
111
118
|
// Initialize capture manager
|
|
112
119
|
this.captureManager = new capture_manager_1.CaptureManager(Object.assign(Object.assign({ sanitizer: this.sanitizer }, (config.endpoint && { apiEndpoint: (0, url_helpers_1.getApiBaseUrl)(config.endpoint) })), { replay: config.replay }));
|
|
113
|
-
// Initialize bug reporter
|
|
114
|
-
this.bugReporter = new bug_reporter_1.BugReporter(config);
|
|
115
120
|
// Initialize widget (enabled by default)
|
|
116
121
|
const widgetEnabled = (_f = config.showWidget) !== null && _f !== void 0 ? _f : true;
|
|
117
122
|
if (widgetEnabled) {
|
|
@@ -149,17 +154,33 @@ class BugSpotter {
|
|
|
149
154
|
* Fetches replay settings from backend before initialization
|
|
150
155
|
*/
|
|
151
156
|
static async createInstance(config) {
|
|
152
|
-
var _a, _b
|
|
157
|
+
var _a, _b;
|
|
158
|
+
// Check sampling rate — if this session is not sampled, disable all capture
|
|
159
|
+
if (config.sampleRate !== undefined) {
|
|
160
|
+
if (typeof config.sampleRate !== 'number' ||
|
|
161
|
+
!Number.isFinite(config.sampleRate) ||
|
|
162
|
+
config.sampleRate < 0 ||
|
|
163
|
+
config.sampleRate > 1) {
|
|
164
|
+
throw new Error('sampleRate must be a finite number between 0 and 1');
|
|
165
|
+
}
|
|
166
|
+
if (Math.random() >= config.sampleRate) {
|
|
167
|
+
// Create a lightweight no-op instance — zero overhead (no console/network interception)
|
|
168
|
+
return new BugSpotter(config, /* sampled */ false);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
153
171
|
// Fetch replay quality settings from backend if replay is enabled
|
|
154
172
|
let backendSettings = null;
|
|
155
173
|
const replayEnabled = (_b = (_a = config.replay) === null || _a === void 0 ? void 0 : _a.enabled) !== null && _b !== void 0 ? _b : true;
|
|
156
174
|
if (replayEnabled && config.endpoint) {
|
|
157
|
-
//
|
|
158
|
-
if (!(
|
|
175
|
+
// SECURITY: Don't send API key over insecure connection
|
|
176
|
+
if (!(0, url_helpers_1.isSecureEndpoint)(config.endpoint)) {
|
|
177
|
+
logger.warn('Insecure endpoint — skipping backend settings fetch to protect API key.');
|
|
178
|
+
}
|
|
179
|
+
else if (!config.apiKey) {
|
|
159
180
|
logger.warn('Endpoint provided but no API key configured. Skipping backend settings fetch.');
|
|
160
181
|
}
|
|
161
182
|
else {
|
|
162
|
-
backendSettings = await fetchReplaySettings(config.endpoint, config.
|
|
183
|
+
backendSettings = await fetchReplaySettings(config.endpoint, config.apiKey);
|
|
163
184
|
}
|
|
164
185
|
}
|
|
165
186
|
// Merge backend settings with user config (user config takes precedence)
|
|
@@ -174,7 +195,28 @@ class BugSpotter {
|
|
|
174
195
|
* Note: Screenshot is captured for modal preview only (_screenshotPreview)
|
|
175
196
|
* File uploads use presigned URLs returned from the backend
|
|
176
197
|
*/
|
|
198
|
+
/** Whether this session was sampled for capture */
|
|
199
|
+
get isSampled() {
|
|
200
|
+
return this._sampled;
|
|
201
|
+
}
|
|
177
202
|
async capture() {
|
|
203
|
+
if (!this.captureManager) {
|
|
204
|
+
// Unsampled session — return minimal valid report
|
|
205
|
+
return {
|
|
206
|
+
console: [],
|
|
207
|
+
network: [],
|
|
208
|
+
metadata: {
|
|
209
|
+
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',
|
|
210
|
+
url: typeof window !== 'undefined' ? window.location.href : '',
|
|
211
|
+
timestamp: Date.now(),
|
|
212
|
+
viewport: typeof window !== 'undefined'
|
|
213
|
+
? { width: window.innerWidth, height: window.innerHeight }
|
|
214
|
+
: { width: 0, height: 0 },
|
|
215
|
+
browser: 'unknown',
|
|
216
|
+
os: 'unknown',
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
178
220
|
return await this.captureManager.captureAll();
|
|
179
221
|
}
|
|
180
222
|
async handleBugReport() {
|
|
@@ -213,9 +255,9 @@ class BugSpotter {
|
|
|
213
255
|
return Object.assign({}, this.config);
|
|
214
256
|
}
|
|
215
257
|
destroy() {
|
|
216
|
-
var _a;
|
|
217
|
-
this.captureManager.destroy();
|
|
218
|
-
(
|
|
258
|
+
var _a, _b;
|
|
259
|
+
(_a = this.captureManager) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
260
|
+
(_b = this.widget) === null || _b === void 0 ? void 0 : _b.destroy();
|
|
219
261
|
this.bugReporter.destroy();
|
|
220
262
|
BugSpotter.instance = undefined;
|
|
221
263
|
BugSpotter.initPromise = undefined;
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
* Configuration Validation Utilities
|
|
3
3
|
* Validates BugSpotter configuration before use
|
|
4
4
|
*/
|
|
5
|
-
import type { AuthConfig } from '../core/transport';
|
|
6
5
|
import type { DeduplicationConfig } from './deduplicator';
|
|
7
6
|
export interface ValidationContext {
|
|
8
7
|
endpoint?: string;
|
|
9
|
-
|
|
8
|
+
apiKey?: string;
|
|
10
9
|
}
|
|
11
10
|
/**
|
|
12
|
-
* Validate
|
|
11
|
+
* Validate configuration before submitting a bug report
|
|
13
12
|
* @throws Error if configuration is invalid
|
|
14
13
|
*/
|
|
15
14
|
export declare function validateAuthConfig(context: ValidationContext): void;
|
|
@@ -8,7 +8,7 @@ exports.validateAuthConfig = validateAuthConfig;
|
|
|
8
8
|
exports.validateDeduplicationConfig = validateDeduplicationConfig;
|
|
9
9
|
const url_helpers_1 = require("./url-helpers");
|
|
10
10
|
/**
|
|
11
|
-
* Validate
|
|
11
|
+
* Validate configuration before submitting a bug report
|
|
12
12
|
* @throws Error if configuration is invalid
|
|
13
13
|
*/
|
|
14
14
|
function validateAuthConfig(context) {
|
|
@@ -16,15 +16,11 @@ function validateAuthConfig(context) {
|
|
|
16
16
|
throw new Error('No endpoint configured for bug report submission');
|
|
17
17
|
}
|
|
18
18
|
// SECURITY: Ensure endpoint uses HTTPS
|
|
19
|
-
// This prevents credentials and sensitive data from being sent over plain HTTP
|
|
20
19
|
if (!(0, url_helpers_1.isSecureEndpoint)(context.endpoint)) {
|
|
21
20
|
throw new url_helpers_1.InsecureEndpointError(context.endpoint);
|
|
22
21
|
}
|
|
23
|
-
if (!context.
|
|
24
|
-
throw new Error('API key
|
|
25
|
-
}
|
|
26
|
-
if (!context.auth.apiKey) {
|
|
27
|
-
throw new Error('API key is required in auth configuration');
|
|
22
|
+
if (!context.apiKey) {
|
|
23
|
+
throw new Error('API key is required');
|
|
28
24
|
}
|
|
29
25
|
}
|
|
30
26
|
/**
|
package/dist/utils/sanitize.js
CHANGED
|
@@ -80,6 +80,7 @@ class StringSanitizer {
|
|
|
80
80
|
class ValueSanitizer {
|
|
81
81
|
constructor(stringSanitizer) {
|
|
82
82
|
this.stringSanitizer = stringSanitizer;
|
|
83
|
+
this.seen = new WeakSet();
|
|
83
84
|
}
|
|
84
85
|
sanitize(value) {
|
|
85
86
|
// Handle null/undefined
|
|
@@ -90,26 +91,40 @@ class ValueSanitizer {
|
|
|
90
91
|
if (typeof value === 'string') {
|
|
91
92
|
return this.stringSanitizer.sanitize(value);
|
|
92
93
|
}
|
|
93
|
-
// Handle arrays
|
|
94
|
+
// Handle arrays (with circular reference protection)
|
|
94
95
|
if (Array.isArray(value)) {
|
|
95
|
-
|
|
96
|
-
return
|
|
97
|
-
|
|
96
|
+
if (this.seen.has(value))
|
|
97
|
+
return '[Circular]';
|
|
98
|
+
this.seen.add(value);
|
|
99
|
+
try {
|
|
100
|
+
return value.map((item) => this.sanitize(item));
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
this.seen.delete(value);
|
|
104
|
+
}
|
|
98
105
|
}
|
|
99
|
-
// Handle objects
|
|
106
|
+
// Handle objects (with circular reference protection)
|
|
100
107
|
if (typeof value === 'object') {
|
|
108
|
+
if (this.seen.has(value))
|
|
109
|
+
return '[Circular]';
|
|
101
110
|
return this.sanitizeObject(value);
|
|
102
111
|
}
|
|
103
112
|
// Return primitives as-is
|
|
104
113
|
return value;
|
|
105
114
|
}
|
|
106
115
|
sanitizeObject(obj) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
116
|
+
this.seen.add(obj);
|
|
117
|
+
try {
|
|
118
|
+
const sanitized = {};
|
|
119
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
120
|
+
const sanitizedKey = this.stringSanitizer.sanitize(key);
|
|
121
|
+
sanitized[sanitizedKey] = this.sanitize(val);
|
|
122
|
+
}
|
|
123
|
+
return sanitized;
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
this.seen.delete(obj);
|
|
111
127
|
}
|
|
112
|
-
return sanitized;
|
|
113
128
|
}
|
|
114
129
|
}
|
|
115
130
|
/**
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/docs/CDN.md
CHANGED
|
@@ -140,7 +140,7 @@ If our primary CDN is unavailable, you can also use:
|
|
|
140
140
|
Check available versions:
|
|
141
141
|
|
|
142
142
|
- [npm package page](https://www.npmjs.com/package/@bugspotter/sdk?activeTab=versions)
|
|
143
|
-
- [GitHub releases](https://github.com/
|
|
143
|
+
- [GitHub releases](https://github.com/apex-bridge/bugspotter-sdk/releases)
|
|
144
144
|
|
|
145
145
|
## ⚙️ Configuration
|
|
146
146
|
|
|
@@ -208,6 +208,6 @@ If updates aren't showing:
|
|
|
208
208
|
|
|
209
209
|
## 🆘 Support
|
|
210
210
|
|
|
211
|
-
- **Issues:** [GitHub Issues](https://github.com/
|
|
212
|
-
- **Discussions:** [GitHub Discussions](https://github.com/
|
|
211
|
+
- **Issues:** [GitHub Issues](https://github.com/apex-bridge/bugspotter-sdk/issues)
|
|
212
|
+
- **Discussions:** [GitHub Discussions](https://github.com/apex-bridge/bugspotter-sdk/discussions)
|
|
213
213
|
- **Security:** security@bugspotter.io
|
|
@@ -1101,4 +1101,4 @@ script-src 'self';
|
|
|
1101
1101
|
|
|
1102
1102
|
---
|
|
1103
1103
|
|
|
1104
|
-
**Need Help?** Open an issue on [GitHub](https://github.com/
|
|
1104
|
+
**Need Help?** Open an issue on [GitHub](https://github.com/apex-bridge/bugspotter-sdk) or contact support.
|
package/eslint.config.js
CHANGED
|
@@ -62,6 +62,13 @@ export default [
|
|
|
62
62
|
Console: 'readonly',
|
|
63
63
|
BlobPart: 'readonly',
|
|
64
64
|
BodyInit: 'readonly',
|
|
65
|
+
RequestInfo: 'readonly',
|
|
66
|
+
RequestInit: 'readonly',
|
|
67
|
+
MutationObserver: 'readonly',
|
|
68
|
+
MutationRecord: 'readonly',
|
|
69
|
+
DocumentFragment: 'readonly',
|
|
70
|
+
performance: 'readonly',
|
|
71
|
+
atob: 'readonly',
|
|
65
72
|
},
|
|
66
73
|
},
|
|
67
74
|
plugins: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bugspotter/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Professional bug reporting SDK with screenshots, session replay, and automatic error capture for web applications",
|
|
5
5
|
"packageManager": "pnpm@9.15.0",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -56,12 +56,12 @@
|
|
|
56
56
|
"license": "MIT",
|
|
57
57
|
"repository": {
|
|
58
58
|
"type": "git",
|
|
59
|
-
"url": "https://github.com/
|
|
59
|
+
"url": "https://github.com/apex-bridge/bugspotter-sdk.git"
|
|
60
60
|
},
|
|
61
61
|
"bugs": {
|
|
62
|
-
"url": "https://github.com/
|
|
62
|
+
"url": "https://github.com/apex-bridge/bugspotter-sdk/issues"
|
|
63
63
|
},
|
|
64
|
-
"homepage": "https://github.com/
|
|
64
|
+
"homepage": "https://github.com/apex-bridge/bugspotter-sdk#readme",
|
|
65
65
|
"engines": {
|
|
66
66
|
"node": ">=16.0.0"
|
|
67
67
|
},
|
package/release_notes.md
CHANGED