@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/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, _c;
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
- // Validate auth is configured before attempting fetch
158
- if (!((_c = config.auth) === null || _c === void 0 ? void 0 : _c.apiKey)) {
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.auth.apiKey);
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
- (_a = this.widget) === null || _a === void 0 ? void 0 : _a.destroy();
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
- auth?: AuthConfig;
8
+ apiKey?: string;
10
9
  }
11
10
  /**
12
- * Validate authentication configuration
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 authentication configuration
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.auth) {
24
- throw new Error('API key authentication is required');
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
  /**
@@ -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
- return value.map((item) => {
96
- return this.sanitize(item);
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
- const sanitized = {};
108
- for (const [key, val] of Object.entries(obj)) {
109
- const sanitizedKey = this.stringSanitizer.sanitize(key);
110
- sanitized[sanitizedKey] = this.sanitize(val);
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
@@ -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 = "2.0.0";
8
+ export declare const VERSION = "2.1.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 = '2.0.0';
11
+ exports.VERSION = '2.1.0';
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/apexbridge-tech/bugspotter/releases)
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/apexbridge-tech/bugspotter/issues)
212
- - **Discussions:** [GitHub Discussions](https://github.com/apexbridge-tech/bugspotter/discussions)
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/apexbridge-tech/bugspotter) or contact support.
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.0.0",
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/apexbridge-tech/bugspotter-sdk.git"
59
+ "url": "https://github.com/apex-bridge/bugspotter-sdk.git"
60
60
  },
61
61
  "bugs": {
62
- "url": "https://github.com/apexbridge-tech/bugspotter-sdk/issues"
62
+ "url": "https://github.com/apex-bridge/bugspotter-sdk/issues"
63
63
  },
64
- "homepage": "https://github.com/apexbridge-tech/bugspotter-sdk#readme",
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
@@ -1,4 +1,4 @@
1
- ## Release 2.0.0
1
+ ## Release 2.1.0
2
2
 
3
3
  ### Changes
4
4