@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.
Files changed (67) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/LICENSE +21 -0
  3. package/README.md +639 -0
  4. package/dist/bugspotter.min.js +2 -0
  5. package/dist/bugspotter.min.js.LICENSE.txt +14 -0
  6. package/dist/capture/base-capture.d.ts +34 -0
  7. package/dist/capture/base-capture.js +23 -0
  8. package/dist/capture/capture-lifecycle.d.ts +24 -0
  9. package/dist/capture/capture-lifecycle.js +2 -0
  10. package/dist/capture/console.d.ts +29 -0
  11. package/dist/capture/console.js +107 -0
  12. package/dist/capture/metadata.d.ts +21 -0
  13. package/dist/capture/metadata.js +76 -0
  14. package/dist/capture/network.d.ts +32 -0
  15. package/dist/capture/network.js +135 -0
  16. package/dist/capture/screenshot.d.ts +19 -0
  17. package/dist/capture/screenshot.js +52 -0
  18. package/dist/collectors/dom.d.ts +67 -0
  19. package/dist/collectors/dom.js +164 -0
  20. package/dist/collectors/index.d.ts +2 -0
  21. package/dist/collectors/index.js +5 -0
  22. package/dist/core/buffer.d.ts +50 -0
  23. package/dist/core/buffer.js +88 -0
  24. package/dist/core/circular-buffer.d.ts +42 -0
  25. package/dist/core/circular-buffer.js +77 -0
  26. package/dist/core/compress.d.ts +49 -0
  27. package/dist/core/compress.js +245 -0
  28. package/dist/core/offline-queue.d.ts +76 -0
  29. package/dist/core/offline-queue.js +301 -0
  30. package/dist/core/transport.d.ts +73 -0
  31. package/dist/core/transport.js +352 -0
  32. package/dist/core/upload-helpers.d.ts +32 -0
  33. package/dist/core/upload-helpers.js +79 -0
  34. package/dist/core/uploader.d.ts +70 -0
  35. package/dist/core/uploader.js +185 -0
  36. package/dist/index.d.ts +140 -0
  37. package/dist/index.esm.js +205 -0
  38. package/dist/index.js +244 -0
  39. package/dist/utils/logger.d.ts +28 -0
  40. package/dist/utils/logger.js +84 -0
  41. package/dist/utils/sanitize-patterns.d.ts +103 -0
  42. package/dist/utils/sanitize-patterns.js +282 -0
  43. package/dist/utils/sanitize.d.ts +73 -0
  44. package/dist/utils/sanitize.js +254 -0
  45. package/dist/widget/button.d.ts +33 -0
  46. package/dist/widget/button.js +143 -0
  47. package/dist/widget/components/dom-element-cache.d.ts +62 -0
  48. package/dist/widget/components/dom-element-cache.js +105 -0
  49. package/dist/widget/components/form-validator.d.ts +66 -0
  50. package/dist/widget/components/form-validator.js +115 -0
  51. package/dist/widget/components/pii-detection-display.d.ts +64 -0
  52. package/dist/widget/components/pii-detection-display.js +142 -0
  53. package/dist/widget/components/redaction-canvas.d.ts +95 -0
  54. package/dist/widget/components/redaction-canvas.js +230 -0
  55. package/dist/widget/components/screenshot-processor.d.ts +44 -0
  56. package/dist/widget/components/screenshot-processor.js +191 -0
  57. package/dist/widget/components/style-manager.d.ts +37 -0
  58. package/dist/widget/components/style-manager.js +296 -0
  59. package/dist/widget/components/template-manager.d.ts +66 -0
  60. package/dist/widget/components/template-manager.js +198 -0
  61. package/dist/widget/modal.d.ts +62 -0
  62. package/dist/widget/modal.js +299 -0
  63. package/docs/CDN.md +213 -0
  64. package/docs/FRAMEWORK_INTEGRATION.md +1104 -0
  65. package/docs/PUBLISHING.md +550 -0
  66. package/docs/SESSION_REPLAY.md +381 -0
  67. package/package.json +90 -0
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DOMCollector = void 0;
4
+ const rrweb_1 = require("rrweb");
5
+ const buffer_1 = require("../core/buffer");
6
+ /**
7
+ * DOM Collector - Records user interactions and DOM mutations
8
+ * @packageDocumentation
9
+ */
10
+ const logger_1 = require("../utils/logger");
11
+ const logger = (0, logger_1.getLogger)();
12
+ class DOMCollector {
13
+ constructor(config = {}) {
14
+ var _a, _b, _c, _d, _e, _f, _g;
15
+ this.isRecording = false;
16
+ this.sanitizer = config.sanitizer;
17
+ this.config = {
18
+ duration: (_a = config.duration) !== null && _a !== void 0 ? _a : 15,
19
+ sampling: {
20
+ mousemove: (_c = (_b = config.sampling) === null || _b === void 0 ? void 0 : _b.mousemove) !== null && _c !== void 0 ? _c : 50,
21
+ scroll: (_e = (_d = config.sampling) === null || _d === void 0 ? void 0 : _d.scroll) !== null && _e !== void 0 ? _e : 100,
22
+ },
23
+ recordCanvas: (_f = config.recordCanvas) !== null && _f !== void 0 ? _f : false,
24
+ recordCrossOriginIframes: (_g = config.recordCrossOriginIframes) !== null && _g !== void 0 ? _g : false,
25
+ sanitizer: config.sanitizer,
26
+ };
27
+ this.buffer = new buffer_1.CircularBuffer({
28
+ duration: this.config.duration,
29
+ });
30
+ }
31
+ /**
32
+ * Start recording DOM events
33
+ */
34
+ startRecording() {
35
+ var _a, _b, _c, _d;
36
+ if (this.isRecording) {
37
+ (0, logger_1.getLogger)().warn('DOMCollector: Recording already in progress');
38
+ return;
39
+ }
40
+ try {
41
+ const recordConfig = {
42
+ emit: (event) => {
43
+ this.buffer.add(event);
44
+ },
45
+ sampling: {
46
+ mousemove: (_b = (_a = this.config.sampling) === null || _a === void 0 ? void 0 : _a.mousemove) !== null && _b !== void 0 ? _b : 50,
47
+ scroll: (_d = (_c = this.config.sampling) === null || _c === void 0 ? void 0 : _c.scroll) !== null && _d !== void 0 ? _d : 100,
48
+ // Also throttle mouse interactions slightly for better performance
49
+ mouseInteraction: {
50
+ MouseUp: false,
51
+ MouseDown: false,
52
+ Click: false,
53
+ ContextMenu: false,
54
+ DblClick: false,
55
+ Focus: false,
56
+ Blur: false,
57
+ TouchStart: false,
58
+ TouchEnd: false,
59
+ },
60
+ },
61
+ recordCanvas: this.config.recordCanvas,
62
+ recordCrossOriginIframes: this.config.recordCrossOriginIframes,
63
+ // PII sanitization for text content
64
+ maskTextFn: this.sanitizer
65
+ ? (text, element) => {
66
+ return this.sanitizer.sanitizeTextNode(text, element);
67
+ }
68
+ : undefined,
69
+ // Performance optimizations
70
+ slimDOMOptions: {
71
+ script: true, // Don't record script tags
72
+ comment: true, // Don't record comments
73
+ headFavicon: true, // Don't record favicon
74
+ headWhitespace: true, // Don't record whitespace in head
75
+ headMetaSocial: true, // Don't record social media meta tags
76
+ headMetaRobots: true, // Don't record robots meta tags
77
+ headMetaHttpEquiv: true, // Don't record http-equiv meta tags
78
+ headMetaAuthorship: true, // Don't record authorship meta tags
79
+ headMetaVerification: true, // Don't record verification meta tags
80
+ },
81
+ // Don't inline images to keep payload size down
82
+ inlineImages: false,
83
+ // Collect fonts for proper replay
84
+ collectFonts: false,
85
+ };
86
+ this.stopRecordingFn = (0, rrweb_1.record)(recordConfig);
87
+ this.isRecording = true;
88
+ (0, logger_1.getLogger)().debug('DOMCollector: Started recording');
89
+ }
90
+ catch (error) {
91
+ (0, logger_1.getLogger)().error('DOMCollector: Failed to start recording', error);
92
+ this.isRecording = false;
93
+ }
94
+ }
95
+ /**
96
+ * Stop recording DOM events
97
+ */
98
+ stopRecording() {
99
+ if (!this.isRecording || !this.stopRecordingFn) {
100
+ logger.warn('DOMCollector: No recording in progress');
101
+ return;
102
+ }
103
+ try {
104
+ this.stopRecordingFn();
105
+ this.isRecording = false;
106
+ this.stopRecordingFn = undefined;
107
+ logger.debug('DOMCollector: Stopped recording');
108
+ }
109
+ catch (error) {
110
+ logger.error('DOMCollector: Failed to stop recording', error);
111
+ }
112
+ }
113
+ /**
114
+ * Get all events from the buffer
115
+ */
116
+ getEvents() {
117
+ return this.buffer.getEvents();
118
+ }
119
+ /**
120
+ * Get compressed events from the buffer
121
+ */
122
+ getCompressedEvents() {
123
+ return this.buffer.getCompressedEvents();
124
+ }
125
+ /**
126
+ * Clear all events from the buffer
127
+ */
128
+ clearBuffer() {
129
+ this.buffer.clear();
130
+ }
131
+ /**
132
+ * Check if currently recording
133
+ */
134
+ isCurrentlyRecording() {
135
+ return this.isRecording;
136
+ }
137
+ /**
138
+ * Get the current buffer size
139
+ */
140
+ getBufferSize() {
141
+ return this.buffer.size();
142
+ }
143
+ /**
144
+ * Update the buffer duration
145
+ */
146
+ setDuration(seconds) {
147
+ this.config.duration = seconds;
148
+ this.buffer.setDuration(seconds);
149
+ }
150
+ /**
151
+ * Get the buffer duration
152
+ */
153
+ getDuration() {
154
+ return this.config.duration;
155
+ }
156
+ /**
157
+ * Destroy the collector and clean up resources
158
+ */
159
+ destroy() {
160
+ this.stopRecording();
161
+ this.clearBuffer();
162
+ }
163
+ }
164
+ exports.DOMCollector = DOMCollector;
@@ -0,0 +1,2 @@
1
+ export { DOMCollector } from './dom';
2
+ export type { DOMCollectorConfig } from './dom';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DOMCollector = void 0;
4
+ var dom_1 = require("./dom");
5
+ Object.defineProperty(exports, "DOMCollector", { enumerable: true, get: function () { return dom_1.DOMCollector; } });
@@ -0,0 +1,50 @@
1
+ import type { eventWithTime } from '@rrweb/types';
2
+ export interface CircularBufferConfig {
3
+ /** Duration in seconds to keep events */
4
+ duration: number;
5
+ }
6
+ /**
7
+ * Time-based circular buffer for storing replay events.
8
+ * Automatically prunes events older than the configured duration.
9
+ */
10
+ export declare class CircularBuffer<T extends eventWithTime = eventWithTime> {
11
+ private events;
12
+ private duration;
13
+ constructor(config: CircularBufferConfig);
14
+ /**
15
+ * Add an event to the buffer
16
+ */
17
+ add(event: T): void;
18
+ /**
19
+ * Add multiple events to the buffer
20
+ */
21
+ addBatch(events: T[]): void;
22
+ /**
23
+ * Remove events older than the configured duration
24
+ */
25
+ private prune;
26
+ /**
27
+ * Get all events in the buffer
28
+ */
29
+ getEvents(): T[];
30
+ /**
31
+ * Get compressed event data
32
+ */
33
+ getCompressedEvents(): T[];
34
+ /**
35
+ * Clear all events from the buffer
36
+ */
37
+ clear(): void;
38
+ /**
39
+ * Get the current size of the buffer
40
+ */
41
+ size(): number;
42
+ /**
43
+ * Update the buffer duration
44
+ */
45
+ setDuration(seconds: number): void;
46
+ /**
47
+ * Get the buffer duration in seconds
48
+ */
49
+ getDuration(): number;
50
+ }
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CircularBuffer = void 0;
4
+ /**
5
+ * Time-based circular buffer for storing replay events.
6
+ * Automatically prunes events older than the configured duration.
7
+ */
8
+ class CircularBuffer {
9
+ constructor(config) {
10
+ this.events = [];
11
+ this.duration = config.duration * 1000; // convert to milliseconds
12
+ }
13
+ /**
14
+ * Add an event to the buffer
15
+ */
16
+ add(event) {
17
+ this.events.push(event);
18
+ this.prune();
19
+ }
20
+ /**
21
+ * Add multiple events to the buffer
22
+ */
23
+ addBatch(events) {
24
+ this.events.push(...events);
25
+ this.prune();
26
+ }
27
+ /**
28
+ * Remove events older than the configured duration
29
+ */
30
+ prune() {
31
+ if (this.events.length === 0) {
32
+ return;
33
+ }
34
+ const now = Date.now();
35
+ const cutoffTime = now - this.duration;
36
+ // Find the first event that should be kept
37
+ let firstValidIndex = 0;
38
+ for (let i = 0; i < this.events.length; i++) {
39
+ if (this.events[i].timestamp >= cutoffTime) {
40
+ firstValidIndex = i;
41
+ break;
42
+ }
43
+ }
44
+ // Remove old events
45
+ if (firstValidIndex > 0) {
46
+ this.events = this.events.slice(firstValidIndex);
47
+ }
48
+ }
49
+ /**
50
+ * Get all events in the buffer
51
+ */
52
+ getEvents() {
53
+ this.prune(); // Ensure we don't return stale events
54
+ return [...this.events]; // Return a copy
55
+ }
56
+ /**
57
+ * Get compressed event data
58
+ */
59
+ getCompressedEvents() {
60
+ return this.getEvents();
61
+ }
62
+ /**
63
+ * Clear all events from the buffer
64
+ */
65
+ clear() {
66
+ this.events = [];
67
+ }
68
+ /**
69
+ * Get the current size of the buffer
70
+ */
71
+ size() {
72
+ return this.events.length;
73
+ }
74
+ /**
75
+ * Update the buffer duration
76
+ */
77
+ setDuration(seconds) {
78
+ this.duration = seconds * 1000;
79
+ this.prune();
80
+ }
81
+ /**
82
+ * Get the buffer duration in seconds
83
+ */
84
+ getDuration() {
85
+ return this.duration / 1000;
86
+ }
87
+ }
88
+ exports.CircularBuffer = CircularBuffer;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * A generic circular buffer implementation for storing a fixed number of items.
3
+ * When the buffer is full, new items overwrite the oldest items.
4
+ *
5
+ * @template T The type of items stored in the buffer
6
+ */
7
+ export declare class CircularBuffer<T> {
8
+ private maxSize;
9
+ private items;
10
+ private index;
11
+ private count;
12
+ constructor(maxSize: number);
13
+ /**
14
+ * Add an item to the buffer. If the buffer is full, the oldest item is overwritten.
15
+ */
16
+ add(item: T): void;
17
+ /**
18
+ * Get all items in chronological order (oldest to newest).
19
+ * Returns a copy of the internal array.
20
+ */
21
+ getAll(): T[];
22
+ /**
23
+ * Clear all items from the buffer.
24
+ */
25
+ clear(): void;
26
+ /**
27
+ * Get the current number of items in the buffer.
28
+ */
29
+ get size(): number;
30
+ /**
31
+ * Get the maximum capacity of the buffer.
32
+ */
33
+ get capacity(): number;
34
+ /**
35
+ * Check if the buffer is empty.
36
+ */
37
+ get isEmpty(): boolean;
38
+ /**
39
+ * Check if the buffer is full.
40
+ */
41
+ get isFull(): boolean;
42
+ }
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CircularBuffer = void 0;
4
+ /**
5
+ * A generic circular buffer implementation for storing a fixed number of items.
6
+ * When the buffer is full, new items overwrite the oldest items.
7
+ *
8
+ * @template T The type of items stored in the buffer
9
+ */
10
+ class CircularBuffer {
11
+ constructor(maxSize) {
12
+ this.maxSize = maxSize;
13
+ this.items = [];
14
+ this.index = 0;
15
+ this.count = 0;
16
+ if (maxSize <= 0) {
17
+ throw new Error('CircularBuffer maxSize must be greater than 0');
18
+ }
19
+ }
20
+ /**
21
+ * Add an item to the buffer. If the buffer is full, the oldest item is overwritten.
22
+ */
23
+ add(item) {
24
+ if (this.count < this.maxSize) {
25
+ this.items.push(item);
26
+ this.count++;
27
+ }
28
+ else {
29
+ this.items[this.index] = item;
30
+ }
31
+ this.index = (this.index + 1) % this.maxSize;
32
+ }
33
+ /**
34
+ * Get all items in chronological order (oldest to newest).
35
+ * Returns a copy of the internal array.
36
+ */
37
+ getAll() {
38
+ if (this.count < this.maxSize) {
39
+ return [...this.items];
40
+ }
41
+ // Return items in chronological order when buffer is full
42
+ return [...this.items.slice(this.index), ...this.items.slice(0, this.index)];
43
+ }
44
+ /**
45
+ * Clear all items from the buffer.
46
+ */
47
+ clear() {
48
+ this.items = [];
49
+ this.index = 0;
50
+ this.count = 0;
51
+ }
52
+ /**
53
+ * Get the current number of items in the buffer.
54
+ */
55
+ get size() {
56
+ return this.count;
57
+ }
58
+ /**
59
+ * Get the maximum capacity of the buffer.
60
+ */
61
+ get capacity() {
62
+ return this.maxSize;
63
+ }
64
+ /**
65
+ * Check if the buffer is empty.
66
+ */
67
+ get isEmpty() {
68
+ return this.count === 0;
69
+ }
70
+ /**
71
+ * Check if the buffer is full.
72
+ */
73
+ get isFull() {
74
+ return this.count >= this.maxSize;
75
+ }
76
+ }
77
+ exports.CircularBuffer = CircularBuffer;
@@ -0,0 +1,49 @@
1
+ export interface CompressionConfig {
2
+ gzipLevel?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -1;
3
+ imageMaxWidth?: number;
4
+ imageMaxHeight?: number;
5
+ webpQuality?: number;
6
+ jpegQuality?: number;
7
+ verbose?: boolean;
8
+ }
9
+ /**
10
+ * Compress JSON or string data using gzip
11
+ * @param data - Data to compress (will be JSON stringified if object)
12
+ * @param config - Optional compression configuration
13
+ * @returns Compressed data as Uint8Array
14
+ */
15
+ export declare function compressData(data: unknown, config?: CompressionConfig): Promise<Uint8Array>;
16
+ /**
17
+ * Decompress gzipped data back to original format
18
+ * Useful for testing and verification
19
+ * @param compressed - Compressed Uint8Array data
20
+ * @param config - Optional configuration
21
+ * @returns Decompressed and parsed data (or string if input was string)
22
+ */
23
+ export declare function decompressData(compressed: Uint8Array, config?: Pick<CompressionConfig, 'verbose'>): unknown;
24
+ /**
25
+ * Optimize and compress screenshot image
26
+ * Converts to WebP if supported, resizes if too large, then compresses
27
+ * @param base64 - Base64 encoded image (PNG or other format)
28
+ * @param config - Optional compression configuration
29
+ * @returns Optimized base64 image string
30
+ */
31
+ export declare function compressImage(base64: string, config?: CompressionConfig): Promise<string>;
32
+ /**
33
+ * Calculate compression ratio for analytics
34
+ * @param originalSize - Original data size in bytes
35
+ * @param compressedSize - Compressed data size in bytes
36
+ * @returns Compression ratio as percentage (0-100)
37
+ */
38
+ export declare function getCompressionRatio(originalSize: number, compressedSize: number): number;
39
+ /**
40
+ * Estimate payload size before compression
41
+ * @param data - Data to estimate
42
+ * @returns Estimated size in bytes
43
+ */
44
+ export declare function estimateSize(data: unknown): number;
45
+ /**
46
+ * Reset cached instances (useful for testing)
47
+ * @internal
48
+ */
49
+ export declare function resetCompressionCache(): void;