@bugspotter/sdk 0.1.0-alpha.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/CHANGELOG.md +69 -0
- package/LICENSE +21 -0
- package/README.md +639 -0
- package/dist/bugspotter.min.js +2 -0
- package/dist/bugspotter.min.js.LICENSE.txt +14 -0
- package/dist/capture/base-capture.d.ts +34 -0
- package/dist/capture/base-capture.js +23 -0
- package/dist/capture/capture-lifecycle.d.ts +24 -0
- package/dist/capture/capture-lifecycle.js +2 -0
- package/dist/capture/console.d.ts +29 -0
- package/dist/capture/console.js +107 -0
- package/dist/capture/metadata.d.ts +21 -0
- package/dist/capture/metadata.js +76 -0
- package/dist/capture/network.d.ts +32 -0
- package/dist/capture/network.js +135 -0
- package/dist/capture/screenshot.d.ts +19 -0
- package/dist/capture/screenshot.js +52 -0
- package/dist/collectors/dom.d.ts +67 -0
- package/dist/collectors/dom.js +164 -0
- package/dist/collectors/index.d.ts +2 -0
- package/dist/collectors/index.js +5 -0
- package/dist/core/buffer.d.ts +50 -0
- package/dist/core/buffer.js +88 -0
- package/dist/core/circular-buffer.d.ts +42 -0
- package/dist/core/circular-buffer.js +77 -0
- package/dist/core/compress.d.ts +49 -0
- package/dist/core/compress.js +245 -0
- package/dist/core/offline-queue.d.ts +76 -0
- package/dist/core/offline-queue.js +301 -0
- package/dist/core/transport.d.ts +73 -0
- package/dist/core/transport.js +352 -0
- package/dist/core/upload-helpers.d.ts +32 -0
- package/dist/core/upload-helpers.js +79 -0
- package/dist/core/uploader.d.ts +70 -0
- package/dist/core/uploader.js +185 -0
- package/dist/index.d.ts +140 -0
- package/dist/index.esm.js +205 -0
- package/dist/index.js +244 -0
- package/dist/utils/logger.d.ts +28 -0
- package/dist/utils/logger.js +84 -0
- package/dist/utils/sanitize-patterns.d.ts +103 -0
- package/dist/utils/sanitize-patterns.js +282 -0
- package/dist/utils/sanitize.d.ts +73 -0
- package/dist/utils/sanitize.js +254 -0
- package/dist/widget/button.d.ts +33 -0
- package/dist/widget/button.js +143 -0
- package/dist/widget/components/dom-element-cache.d.ts +62 -0
- package/dist/widget/components/dom-element-cache.js +105 -0
- package/dist/widget/components/form-validator.d.ts +66 -0
- package/dist/widget/components/form-validator.js +115 -0
- package/dist/widget/components/pii-detection-display.d.ts +64 -0
- package/dist/widget/components/pii-detection-display.js +142 -0
- package/dist/widget/components/redaction-canvas.d.ts +95 -0
- package/dist/widget/components/redaction-canvas.js +230 -0
- package/dist/widget/components/screenshot-processor.d.ts +44 -0
- package/dist/widget/components/screenshot-processor.js +191 -0
- package/dist/widget/components/style-manager.d.ts +37 -0
- package/dist/widget/components/style-manager.js +296 -0
- package/dist/widget/components/template-manager.d.ts +66 -0
- package/dist/widget/components/template-manager.js +198 -0
- package/dist/widget/modal.d.ts +62 -0
- package/dist/widget/modal.js +299 -0
- package/docs/CDN.md +213 -0
- package/docs/FRAMEWORK_INTEGRATION.md +1104 -0
- package/docs/PUBLISHING.md +550 -0
- package/docs/SESSION_REPLAY.md +381 -0
- package/package.json +90 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BugReportModal = void 0;
|
|
4
|
+
const style_manager_1 = require("./components/style-manager");
|
|
5
|
+
const template_manager_1 = require("./components/template-manager");
|
|
6
|
+
const dom_element_cache_1 = require("./components/dom-element-cache");
|
|
7
|
+
const form_validator_1 = require("./components/form-validator");
|
|
8
|
+
const pii_detection_display_1 = require("./components/pii-detection-display");
|
|
9
|
+
const redaction_canvas_1 = require("./components/redaction-canvas");
|
|
10
|
+
const screenshot_processor_1 = require("./components/screenshot-processor");
|
|
11
|
+
const sanitize_1 = require("../utils/sanitize");
|
|
12
|
+
const logger_1 = require("../utils/logger");
|
|
13
|
+
const logger = (0, logger_1.getLogger)();
|
|
14
|
+
/**
|
|
15
|
+
* BugReportModal
|
|
16
|
+
*
|
|
17
|
+
* Refactored to follow SOLID principles
|
|
18
|
+
* Acts as a lightweight coordinator for specialized components
|
|
19
|
+
*/
|
|
20
|
+
class BugReportModal {
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.redactionCanvas = null;
|
|
23
|
+
// State
|
|
24
|
+
this.originalScreenshot = '';
|
|
25
|
+
this.piiDetections = [];
|
|
26
|
+
this.options = options;
|
|
27
|
+
this.container = document.createElement('div');
|
|
28
|
+
this.shadow = this.container.attachShadow({ mode: 'open' });
|
|
29
|
+
// Initialize components
|
|
30
|
+
this.styleManager = new style_manager_1.StyleManager();
|
|
31
|
+
this.templateManager = new template_manager_1.TemplateManager();
|
|
32
|
+
this.domCache = new dom_element_cache_1.DOMElementCache();
|
|
33
|
+
this.validator = new form_validator_1.FormValidator();
|
|
34
|
+
this.piiDisplay = new pii_detection_display_1.PIIDetectionDisplay();
|
|
35
|
+
this.screenshotProcessor = new screenshot_processor_1.ScreenshotProcessor();
|
|
36
|
+
// Bind event handler
|
|
37
|
+
this.handleEscapeKey = this.onEscapeKey.bind(this);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Show the modal with optional screenshot
|
|
41
|
+
*/
|
|
42
|
+
show(screenshotDataUrl) {
|
|
43
|
+
if (screenshotDataUrl) {
|
|
44
|
+
this.originalScreenshot = screenshotDataUrl;
|
|
45
|
+
}
|
|
46
|
+
// Generate and inject HTML (includes inline styles in shadow DOM)
|
|
47
|
+
this.shadow.innerHTML = `
|
|
48
|
+
<style>
|
|
49
|
+
${this.styleManager.generateStyles()}
|
|
50
|
+
</style>
|
|
51
|
+
${this.templateManager.generateModalHTML(this.originalScreenshot)}
|
|
52
|
+
`;
|
|
53
|
+
// Cache DOM elements
|
|
54
|
+
this.domCache.initialize(this.shadow);
|
|
55
|
+
// Initialize error display states
|
|
56
|
+
const elements = this.domCache.get();
|
|
57
|
+
elements.titleError.style.display = 'none';
|
|
58
|
+
elements.descriptionError.style.display = 'none';
|
|
59
|
+
// Setup components
|
|
60
|
+
this.setupRedactionCanvas();
|
|
61
|
+
this.attachEventListeners();
|
|
62
|
+
// Add to DOM
|
|
63
|
+
document.body.appendChild(this.container);
|
|
64
|
+
// Focus first input
|
|
65
|
+
elements.titleInput.focus();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Close and cleanup the modal
|
|
69
|
+
*/
|
|
70
|
+
close() {
|
|
71
|
+
// Remove keyboard listener
|
|
72
|
+
document.removeEventListener('keydown', this.handleEscapeKey);
|
|
73
|
+
// Cleanup components
|
|
74
|
+
if (this.redactionCanvas) {
|
|
75
|
+
this.redactionCanvas.destroy();
|
|
76
|
+
this.redactionCanvas = null;
|
|
77
|
+
}
|
|
78
|
+
// Remove from DOM
|
|
79
|
+
if (this.container.parentNode) {
|
|
80
|
+
this.container.parentNode.removeChild(this.container);
|
|
81
|
+
}
|
|
82
|
+
// Clear cache
|
|
83
|
+
this.domCache.clear();
|
|
84
|
+
// Call onClose callback
|
|
85
|
+
if (this.options.onClose) {
|
|
86
|
+
this.options.onClose();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Destroy the modal (alias for close)
|
|
91
|
+
*/
|
|
92
|
+
destroy() {
|
|
93
|
+
this.close();
|
|
94
|
+
}
|
|
95
|
+
// Private helper methods
|
|
96
|
+
setupRedactionCanvas() {
|
|
97
|
+
const elements = this.domCache.get();
|
|
98
|
+
if (!elements.redactionCanvas || !elements.screenshotImg) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
this.redactionCanvas = new redaction_canvas_1.RedactionCanvas(elements.redactionCanvas);
|
|
103
|
+
this.redactionCanvas.initializeCanvas(elements.screenshotImg);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
// Canvas 2D context not available (e.g., in test environment)
|
|
107
|
+
// Redaction features will be disabled
|
|
108
|
+
(0, logger_1.getLogger)().warn('Canvas redaction not available:', error);
|
|
109
|
+
this.redactionCanvas = null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
attachEventListeners() {
|
|
113
|
+
const elements = this.domCache.get();
|
|
114
|
+
// Close button
|
|
115
|
+
elements.closeButton.addEventListener('click', () => {
|
|
116
|
+
return this.close();
|
|
117
|
+
});
|
|
118
|
+
// Escape key to close
|
|
119
|
+
document.addEventListener('keydown', this.handleEscapeKey);
|
|
120
|
+
// Note: Overlay click does NOT close modal (improved UX to prevent accidental data loss)
|
|
121
|
+
// Form submission
|
|
122
|
+
elements.form.addEventListener('submit', (e) => {
|
|
123
|
+
return this.handleSubmit(e);
|
|
124
|
+
});
|
|
125
|
+
// Submit button click (manually trigger form submit for test compatibility)
|
|
126
|
+
elements.submitButton.addEventListener('click', () => {
|
|
127
|
+
const submitEvent = new Event('submit', { bubbles: true, cancelable: true });
|
|
128
|
+
elements.form.dispatchEvent(submitEvent);
|
|
129
|
+
});
|
|
130
|
+
// Cancel button
|
|
131
|
+
elements.cancelButton.addEventListener('click', () => {
|
|
132
|
+
return this.close();
|
|
133
|
+
});
|
|
134
|
+
// Real-time validation
|
|
135
|
+
elements.titleInput.addEventListener('input', () => {
|
|
136
|
+
return this.validateField('title');
|
|
137
|
+
});
|
|
138
|
+
elements.descriptionTextarea.addEventListener('input', () => {
|
|
139
|
+
this.validateField('description');
|
|
140
|
+
this.checkForPII();
|
|
141
|
+
});
|
|
142
|
+
// Redaction controls
|
|
143
|
+
if (elements.redactButton && this.redactionCanvas) {
|
|
144
|
+
elements.redactButton.addEventListener('click', () => {
|
|
145
|
+
return this.toggleRedactionMode();
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (elements.clearButton && this.redactionCanvas) {
|
|
149
|
+
elements.clearButton.addEventListener('click', () => {
|
|
150
|
+
return this.clearRedactions();
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// PII confirmation
|
|
154
|
+
elements.piiConfirmCheckbox.addEventListener('change', () => {
|
|
155
|
+
return this.updateSubmitButton();
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
validateField(fieldName) {
|
|
159
|
+
const elements = this.domCache.get();
|
|
160
|
+
const value = fieldName === 'title' ? elements.titleInput.value : elements.descriptionTextarea.value;
|
|
161
|
+
const error = this.validator.validateField(fieldName, value);
|
|
162
|
+
const errorElement = fieldName === 'title' ? elements.titleError : elements.descriptionError;
|
|
163
|
+
if (error) {
|
|
164
|
+
errorElement.textContent = error;
|
|
165
|
+
errorElement.style.display = 'block';
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
errorElement.textContent = '';
|
|
169
|
+
errorElement.style.display = 'none';
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
checkForPII() {
|
|
173
|
+
const elements = this.domCache.get();
|
|
174
|
+
const text = `${elements.titleInput.value} ${elements.descriptionTextarea.value}`;
|
|
175
|
+
// Create temporary sanitizer to detect PII
|
|
176
|
+
const sanitizer = (0, sanitize_1.createSanitizer)({ enabled: true });
|
|
177
|
+
const detections = sanitizer.detectPII(text);
|
|
178
|
+
// Convert to PIIDetection array
|
|
179
|
+
this.piiDetections = Array.from(detections.entries()).map(([type, count]) => {
|
|
180
|
+
return {
|
|
181
|
+
type,
|
|
182
|
+
count,
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
if (this.piiDetections.length > 0) {
|
|
186
|
+
elements.piiSection.style.display = 'block';
|
|
187
|
+
this.piiDisplay.render(this.piiDetections, elements.piiContent);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
elements.piiSection.style.display = 'none';
|
|
191
|
+
elements.piiConfirmCheckbox.checked = false;
|
|
192
|
+
}
|
|
193
|
+
this.updateSubmitButton();
|
|
194
|
+
}
|
|
195
|
+
updateSubmitButton() {
|
|
196
|
+
const elements = this.domCache.get();
|
|
197
|
+
const hasPII = this.piiDetections.length > 0;
|
|
198
|
+
const piiConfirmed = elements.piiConfirmCheckbox.checked;
|
|
199
|
+
elements.submitButton.disabled = hasPII && !piiConfirmed;
|
|
200
|
+
}
|
|
201
|
+
toggleRedactionMode() {
|
|
202
|
+
const elements = this.domCache.get();
|
|
203
|
+
if (!this.redactionCanvas || !elements.redactButton) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const isActive = this.redactionCanvas.toggleRedactionMode();
|
|
207
|
+
if (isActive) {
|
|
208
|
+
elements.redactButton.classList.add('active');
|
|
209
|
+
elements.redactButton.textContent = '✓ Redacting...';
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
elements.redactButton.classList.remove('active');
|
|
213
|
+
elements.redactButton.textContent = '✏️ Redact Area';
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
clearRedactions() {
|
|
217
|
+
if (this.redactionCanvas) {
|
|
218
|
+
this.redactionCanvas.clearRedactions();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async handleSubmit(e) {
|
|
222
|
+
e.preventDefault();
|
|
223
|
+
const elements = this.domCache.get();
|
|
224
|
+
const formData = {
|
|
225
|
+
title: elements.titleInput.value,
|
|
226
|
+
description: elements.descriptionTextarea.value,
|
|
227
|
+
piiDetected: this.piiDetections.length > 0,
|
|
228
|
+
piiConfirmed: elements.piiConfirmCheckbox.checked,
|
|
229
|
+
};
|
|
230
|
+
const validation = this.validator.validate(formData);
|
|
231
|
+
if (!validation.isValid) {
|
|
232
|
+
// Display errors
|
|
233
|
+
if (validation.errors.title) {
|
|
234
|
+
elements.titleError.textContent = validation.errors.title;
|
|
235
|
+
elements.titleError.style.display = 'block';
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
elements.titleError.textContent = '';
|
|
239
|
+
elements.titleError.style.display = 'none';
|
|
240
|
+
}
|
|
241
|
+
if (validation.errors.description) {
|
|
242
|
+
elements.descriptionError.textContent = validation.errors.description;
|
|
243
|
+
elements.descriptionError.style.display = 'block';
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
elements.descriptionError.textContent = '';
|
|
247
|
+
elements.descriptionError.style.display = 'none';
|
|
248
|
+
}
|
|
249
|
+
if (validation.errors.piiConfirmation) {
|
|
250
|
+
alert(validation.errors.piiConfirmation);
|
|
251
|
+
}
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
// Clear any previous error messages on successful validation
|
|
255
|
+
elements.titleError.style.display = 'none';
|
|
256
|
+
elements.descriptionError.style.display = 'none';
|
|
257
|
+
// Prepare screenshot with redactions
|
|
258
|
+
let finalScreenshot = this.originalScreenshot;
|
|
259
|
+
if (this.redactionCanvas && this.redactionCanvas.getRedactions().length > 0) {
|
|
260
|
+
try {
|
|
261
|
+
finalScreenshot = await this.screenshotProcessor.mergeRedactions(this.originalScreenshot, this.redactionCanvas.getCanvas());
|
|
262
|
+
}
|
|
263
|
+
catch (mergeError) {
|
|
264
|
+
logger.error('Failed to merge redactions:', mergeError);
|
|
265
|
+
finalScreenshot = this.originalScreenshot;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// Update original screenshot for submission
|
|
269
|
+
this.originalScreenshot = finalScreenshot;
|
|
270
|
+
// Submit
|
|
271
|
+
const bugReportData = {
|
|
272
|
+
title: formData.title.trim(),
|
|
273
|
+
description: formData.description.trim(),
|
|
274
|
+
};
|
|
275
|
+
try {
|
|
276
|
+
await this.options.onSubmit(bugReportData);
|
|
277
|
+
this.close();
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
(0, logger_1.getLogger)().error('Error submitting bug report:', error);
|
|
281
|
+
alert('Failed to submit bug report. Please try again.');
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Get the final screenshot (with redactions applied)
|
|
286
|
+
*/
|
|
287
|
+
getScreenshot() {
|
|
288
|
+
return this.originalScreenshot;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Handle Escape key press to close modal
|
|
292
|
+
*/
|
|
293
|
+
onEscapeKey(e) {
|
|
294
|
+
if (e.key === 'Escape') {
|
|
295
|
+
this.close();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
exports.BugReportModal = BugReportModal;
|
package/docs/CDN.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Using BugSpotter SDK via CDN
|
|
2
|
+
|
|
3
|
+
The BugSpotter SDK is available via CDN for easy integration without npm installation.
|
|
4
|
+
|
|
5
|
+
## 🚀 Quick Start
|
|
6
|
+
|
|
7
|
+
Add the SDK to your HTML file:
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<script src="https://cdn.bugspotter.io/sdk/bugspotter-latest.min.js"></script>
|
|
11
|
+
<script>
|
|
12
|
+
const bugSpotter = window.BugSpotter.init({
|
|
13
|
+
apiKey: 'bgs_your_api_key',
|
|
14
|
+
endpoint: 'https://api.bugspotter.io',
|
|
15
|
+
});
|
|
16
|
+
</script>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 📦 CDN URLs
|
|
20
|
+
|
|
21
|
+
### Production (Versioned)
|
|
22
|
+
|
|
23
|
+
**Recommended for production use** - immutable, cached for 1 year:
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<!-- Specific version (replace X.Y.Z with actual version) -->
|
|
27
|
+
<script src="https://cdn.bugspotter.io/sdk/bugspotter-X.Y.Z.min.js"></script>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Example:**
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<script src="https://cdn.bugspotter.io/sdk/bugspotter-0.1.0.min.js"></script>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Development (Latest)
|
|
37
|
+
|
|
38
|
+
**For development/testing only** - auto-updates to latest version:
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<script src="https://cdn.bugspotter.io/sdk/bugspotter-latest.min.js"></script>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
⚠️ **Warning:** The `latest` version updates automatically. Use versioned URLs in production to prevent breaking changes.
|
|
45
|
+
|
|
46
|
+
## 🔒 Subresource Integrity (SRI)
|
|
47
|
+
|
|
48
|
+
For enhanced security, use SRI hashes to verify file integrity:
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<script
|
|
52
|
+
src="https://cdn.bugspotter.io/sdk/bugspotter-0.1.0.min.js"
|
|
53
|
+
integrity="sha384-..."
|
|
54
|
+
crossorigin="anonymous"
|
|
55
|
+
></script>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
To generate SRI hash for a specific version:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
curl https://cdn.bugspotter.io/sdk/bugspotter-0.1.0.min.js | openssl dgst -sha384 -binary | openssl base64 -A
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 📝 Complete Example
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<!DOCTYPE html>
|
|
68
|
+
<html lang="en">
|
|
69
|
+
<head>
|
|
70
|
+
<meta charset="UTF-8" />
|
|
71
|
+
<title>BugSpotter Example</title>
|
|
72
|
+
</head>
|
|
73
|
+
<body>
|
|
74
|
+
<h1>My Application</h1>
|
|
75
|
+
<button id="trigger-error">Trigger Test Error</button>
|
|
76
|
+
|
|
77
|
+
<!-- Load BugSpotter SDK -->
|
|
78
|
+
<script src="https://cdn.bugspotter.io/sdk/bugspotter-0.1.0.min.js"></script>
|
|
79
|
+
|
|
80
|
+
<script>
|
|
81
|
+
// Initialize BugSpotter
|
|
82
|
+
const bugSpotter = window.BugSpotter.init({
|
|
83
|
+
apiKey: 'bgs_your_api_key',
|
|
84
|
+
endpoint: 'https://api.bugspotter.io',
|
|
85
|
+
sessionReplay: true,
|
|
86
|
+
captureConsole: true,
|
|
87
|
+
captureNetwork: true,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Test error reporting
|
|
91
|
+
document.getElementById('trigger-error').addEventListener('click', () => {
|
|
92
|
+
try {
|
|
93
|
+
throw new Error('Test error from CDN example');
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error('Caught error:', error);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
</script>
|
|
99
|
+
</body>
|
|
100
|
+
</html>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 🌐 Alternative CDN Options
|
|
104
|
+
|
|
105
|
+
If our primary CDN is unavailable, you can also use:
|
|
106
|
+
|
|
107
|
+
### unpkg
|
|
108
|
+
|
|
109
|
+
```html
|
|
110
|
+
<!-- Auto-resolves to browser field (recommended) -->
|
|
111
|
+
<script src="https://unpkg.com/@bugspotter/sdk@latest"></script>
|
|
112
|
+
|
|
113
|
+
<!-- Or explicit path with specific version -->
|
|
114
|
+
<script src="https://unpkg.com/@bugspotter/sdk@0.1.0/dist/bugspotter.min.js"></script>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### jsDelivr
|
|
118
|
+
|
|
119
|
+
```html
|
|
120
|
+
<!-- Auto-resolves to browser field (recommended) -->
|
|
121
|
+
<script src="https://cdn.jsdelivr.net/npm/@bugspotter/sdk@latest"></script>
|
|
122
|
+
|
|
123
|
+
<!-- Or explicit path with specific version -->
|
|
124
|
+
<script src="https://cdn.jsdelivr.net/npm/@bugspotter/sdk@0.1.0/dist/bugspotter.min.js"></script>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## 🆚 CDN vs npm
|
|
128
|
+
|
|
129
|
+
| Feature | CDN | npm |
|
|
130
|
+
| ------------------- | ----------------------- | ------------------------------- |
|
|
131
|
+
| **Setup** | Add `<script>` tag | `npm install @bugspotter/sdk` |
|
|
132
|
+
| **Bundle Size** | ~99KB (gzipped: ~35KB) | Tree-shakable, optimized |
|
|
133
|
+
| **Updates** | Change version in URL | `npm update` |
|
|
134
|
+
| **TypeScript** | No types | Full TypeScript support |
|
|
135
|
+
| **Best For** | Quick prototypes, demos | Production apps, React/Vue/etc. |
|
|
136
|
+
| **Browser Support** | ES2017+ (95% coverage) | Configurable via bundler |
|
|
137
|
+
|
|
138
|
+
## 📊 Version History
|
|
139
|
+
|
|
140
|
+
Check available versions:
|
|
141
|
+
|
|
142
|
+
- [npm package page](https://www.npmjs.com/package/@bugspotter/sdk?activeTab=versions)
|
|
143
|
+
- [GitHub releases](https://github.com/apexbridge-tech/bugspotter/releases)
|
|
144
|
+
|
|
145
|
+
## ⚙️ Configuration
|
|
146
|
+
|
|
147
|
+
All configuration options from npm package work with CDN:
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
window.BugSpotter.init({
|
|
151
|
+
apiKey: 'bgs_your_api_key',
|
|
152
|
+
endpoint: 'https://api.bugspotter.io',
|
|
153
|
+
|
|
154
|
+
// Session replay
|
|
155
|
+
sessionReplay: true,
|
|
156
|
+
|
|
157
|
+
// Data capture
|
|
158
|
+
captureConsole: true,
|
|
159
|
+
captureNetwork: true,
|
|
160
|
+
captureScreenshot: true,
|
|
161
|
+
|
|
162
|
+
// Privacy
|
|
163
|
+
sanitizePII: true,
|
|
164
|
+
|
|
165
|
+
// Performance
|
|
166
|
+
sampleRate: 1.0, // 100% of sessions
|
|
167
|
+
|
|
168
|
+
// Custom data
|
|
169
|
+
metadata: {
|
|
170
|
+
environment: 'production',
|
|
171
|
+
version: '1.0.0',
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
See [Configuration Guide](./README.md#configuration) for all options.
|
|
177
|
+
|
|
178
|
+
## 🔧 Troubleshooting
|
|
179
|
+
|
|
180
|
+
### Script Not Loading
|
|
181
|
+
|
|
182
|
+
1. **Check browser console** for CORS or loading errors
|
|
183
|
+
2. **Verify URL** - ensure version exists
|
|
184
|
+
3. **Try alternative CDN** (unpkg or jsDelivr)
|
|
185
|
+
|
|
186
|
+
### Global Not Found
|
|
187
|
+
|
|
188
|
+
If `window.BugSpotter` is undefined:
|
|
189
|
+
|
|
190
|
+
1. **Script loaded?** Check Network tab in DevTools
|
|
191
|
+
2. **Correct order?** SDK must load before initialization
|
|
192
|
+
3. **No conflicts?** Another script may override `window.BugSpotter`
|
|
193
|
+
|
|
194
|
+
### Old Version Cached
|
|
195
|
+
|
|
196
|
+
If updates aren't showing:
|
|
197
|
+
|
|
198
|
+
1. **Hard refresh:** Ctrl+Shift+R (Cmd+Shift+R on Mac)
|
|
199
|
+
2. **Use versioned URL** in production (not `latest`)
|
|
200
|
+
3. **Check cache headers** in Network tab
|
|
201
|
+
|
|
202
|
+
## 📚 Next Steps
|
|
203
|
+
|
|
204
|
+
- [Full SDK Documentation](./README.md)
|
|
205
|
+
- [Framework Integration](./FRAMEWORK_INTEGRATION.md)
|
|
206
|
+
- [Session Replay Guide](./SESSION_REPLAY.md)
|
|
207
|
+
- [API Reference](../../README.md#api-reference)
|
|
208
|
+
|
|
209
|
+
## 🆘 Support
|
|
210
|
+
|
|
211
|
+
- **Issues:** [GitHub Issues](https://github.com/apexbridge-tech/bugspotter/issues)
|
|
212
|
+
- **Discussions:** [GitHub Discussions](https://github.com/apexbridge-tech/bugspotter/discussions)
|
|
213
|
+
- **Security:** security@bugspotter.io
|