@bugspotter/sdk 0.3.1 → 1.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/.husky/pre-commit +1 -0
- package/CHANGELOG.md +15 -0
- package/CONTRIBUTING.md +200 -0
- package/README.md +18 -16
- package/SECURITY.md +65 -0
- package/dist/bugspotter.min.js +2 -1
- package/dist/bugspotter.min.js.map +1 -0
- package/dist/capture/console.js +2 -2
- package/dist/capture/network.js +2 -2
- package/dist/core/offline-queue.d.ts +13 -0
- package/dist/core/offline-queue.js +49 -4
- package/dist/core/transport.js +20 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +1460 -1178
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/utils/config-validator.js +6 -0
- package/dist/utils/sanitize-patterns.d.ts +3 -76
- package/dist/utils/sanitize-patterns.js +18 -216
- package/dist/utils/url-helpers.d.ts +2 -25
- package/dist/utils/url-helpers.js +10 -61
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/widget/button.d.ts +10 -0
- package/dist/widget/button.js +200 -3
- package/docs/CDN.md +5 -5
- package/eslint.config.js +10 -0
- package/package.json +15 -4
- package/release_notes.md +4 -0
- package/rollup.config.js +1 -1
- package/tsconfig.cjs.json +1 -1
- package/dist/core/circular-buffer.d.ts +0 -42
- package/dist/core/circular-buffer.js +0 -80
package/dist/widget/button.js
CHANGED
|
@@ -37,6 +37,104 @@ const BUTTON_STYLES = {
|
|
|
37
37
|
active: 'scale(0.95)',
|
|
38
38
|
},
|
|
39
39
|
};
|
|
40
|
+
// SVG sanitization whitelists (module-level for performance)
|
|
41
|
+
const SAFE_SVG_TAGS = new Set([
|
|
42
|
+
'svg',
|
|
43
|
+
'g',
|
|
44
|
+
'path',
|
|
45
|
+
'circle',
|
|
46
|
+
'rect',
|
|
47
|
+
'line',
|
|
48
|
+
'polyline',
|
|
49
|
+
'polygon',
|
|
50
|
+
'ellipse',
|
|
51
|
+
'text',
|
|
52
|
+
'tspan',
|
|
53
|
+
// SECURITY: 'use' deliberately excluded - requires href/xlink:href attributes which pose XSS risks
|
|
54
|
+
// and are not in the attribute whitelist, making <use> non-functional anyway
|
|
55
|
+
'symbol',
|
|
56
|
+
'defs',
|
|
57
|
+
'marker',
|
|
58
|
+
'lineargradient', // lowercase to match tagName.toLowerCase() in sanitizeSVGElement (parser preserves camelCase)
|
|
59
|
+
'radialgradient', // lowercase to match tagName.toLowerCase() in sanitizeSVGElement (parser preserves camelCase)
|
|
60
|
+
'stop',
|
|
61
|
+
'clippath', // lowercase to match tagName.toLowerCase() in sanitizeSVGElement (parser preserves camelCase)
|
|
62
|
+
'mask',
|
|
63
|
+
// SECURITY: 'image' deliberately excluded - requires href/xlink:href attributes which pose XSS risks
|
|
64
|
+
// and are not in the attribute whitelist, making <image> non-functional anyway
|
|
65
|
+
// SECURITY: foreignObject deliberately excluded - allows embedding arbitrary HTML/XML
|
|
66
|
+
// and can bypass SVG sanitization (e.g., <foreignObject><body><script>...</script></body></foreignObject>)
|
|
67
|
+
]);
|
|
68
|
+
const SAFE_SVG_ATTRIBUTES = new Set([
|
|
69
|
+
'id',
|
|
70
|
+
'class',
|
|
71
|
+
// SECURITY: 'style' deliberately excluded - can enable CSS-based attacks:
|
|
72
|
+
// - expression() in older browsers
|
|
73
|
+
// - url() with javascript:/data: URIs
|
|
74
|
+
// - @import with malicious stylesheets
|
|
75
|
+
// - CSS data exfiltration
|
|
76
|
+
// Use specific styling attributes (fill, stroke, opacity, etc.) instead
|
|
77
|
+
'd',
|
|
78
|
+
'cx',
|
|
79
|
+
'cy',
|
|
80
|
+
'r',
|
|
81
|
+
'rx',
|
|
82
|
+
'ry',
|
|
83
|
+
'x',
|
|
84
|
+
'y',
|
|
85
|
+
'x1',
|
|
86
|
+
'y1',
|
|
87
|
+
'x2',
|
|
88
|
+
'y2',
|
|
89
|
+
'width',
|
|
90
|
+
'height',
|
|
91
|
+
'viewbox', // lowercase to match attrName.toLowerCase() in sanitizeSVGElement (parser preserves camelCase)
|
|
92
|
+
'xmlns',
|
|
93
|
+
'fill',
|
|
94
|
+
'stroke',
|
|
95
|
+
'stroke-width',
|
|
96
|
+
'stroke-linecap',
|
|
97
|
+
'stroke-linejoin',
|
|
98
|
+
'opacity',
|
|
99
|
+
'fill-opacity',
|
|
100
|
+
'stroke-opacity',
|
|
101
|
+
'transform',
|
|
102
|
+
'points',
|
|
103
|
+
'text-anchor',
|
|
104
|
+
'font-size',
|
|
105
|
+
'font-family',
|
|
106
|
+
'font-weight',
|
|
107
|
+
'offset',
|
|
108
|
+
'stop-color',
|
|
109
|
+
'stop-opacity',
|
|
110
|
+
'clip-path',
|
|
111
|
+
'mask', // Used to reference mask definitions: mask="url(#maskId)"
|
|
112
|
+
]);
|
|
113
|
+
/**
|
|
114
|
+
* Check if an attribute value contains dangerous patterns
|
|
115
|
+
* Uses simple string matching instead of regex for better performance and clarity
|
|
116
|
+
*/
|
|
117
|
+
const isDangerousAttributeValue = (value) => {
|
|
118
|
+
const lowerValue = value.toLowerCase();
|
|
119
|
+
// Dangerous protocol checks
|
|
120
|
+
if (lowerValue.includes('javascript:'))
|
|
121
|
+
return true;
|
|
122
|
+
if (lowerValue.includes('vbscript:'))
|
|
123
|
+
return true;
|
|
124
|
+
// SECURITY: Block ALL data: URIs by default
|
|
125
|
+
// data:text/html, data:application/javascript, data:image/svg+xml can all execute scripts
|
|
126
|
+
// Even data:text/javascript or data URIs with embedded scripts are dangerous
|
|
127
|
+
if (lowerValue.includes('data:'))
|
|
128
|
+
return true;
|
|
129
|
+
// CSS-based attack patterns
|
|
130
|
+
if (lowerValue.includes('expression('))
|
|
131
|
+
return true; // IE CSS expressions
|
|
132
|
+
if (lowerValue.includes('@import'))
|
|
133
|
+
return true; // CSS imports
|
|
134
|
+
if (lowerValue.includes('-moz-binding'))
|
|
135
|
+
return true; // Firefox XBL binding
|
|
136
|
+
return false;
|
|
137
|
+
};
|
|
40
138
|
class FloatingButton {
|
|
41
139
|
constructor(options = {}) {
|
|
42
140
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
@@ -80,10 +178,12 @@ class FloatingButton {
|
|
|
80
178
|
const btn = document.createElement('button');
|
|
81
179
|
// Set button content (SVG or text)
|
|
82
180
|
if (this.options.customSvg) {
|
|
83
|
-
|
|
181
|
+
// Safely inject custom SVG by parsing and validating it
|
|
182
|
+
this.setSafeHTMLContent(btn, this.options.customSvg);
|
|
84
183
|
}
|
|
85
184
|
else if (this.options.icon === 'svg') {
|
|
86
|
-
|
|
185
|
+
// Safely inject default SVG
|
|
186
|
+
this.setSafeHTMLContent(btn, DEFAULT_SVG_ICON);
|
|
87
187
|
}
|
|
88
188
|
else {
|
|
89
189
|
btn.textContent = this.options.icon;
|
|
@@ -95,6 +195,103 @@ class FloatingButton {
|
|
|
95
195
|
this.addHoverEffects(btn);
|
|
96
196
|
return btn;
|
|
97
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Safely inject HTML content by parsing and validating SVG elements
|
|
200
|
+
* Prevents XSS attacks by only allowing safe SVG elements and attributes
|
|
201
|
+
*/
|
|
202
|
+
setSafeHTMLContent(element, htmlContent) {
|
|
203
|
+
try {
|
|
204
|
+
if (typeof window === 'undefined' ||
|
|
205
|
+
typeof window.DOMParser === 'undefined') {
|
|
206
|
+
element.textContent = htmlContent;
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
// SECURITY: Use DOMParser with image/svg+xml MIME type for strict SVG parsing
|
|
210
|
+
// This prevents HTML-specific parsing quirks from being exploited
|
|
211
|
+
const parser = new window.DOMParser();
|
|
212
|
+
const doc = parser.parseFromString(htmlContent, 'image/svg+xml');
|
|
213
|
+
// Check for parse errors
|
|
214
|
+
const parserError = doc.querySelector('parsererror');
|
|
215
|
+
if (parserError) {
|
|
216
|
+
element.textContent = htmlContent;
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (doc.documentElement &&
|
|
220
|
+
doc.documentElement.nodeType === Node.ELEMENT_NODE) {
|
|
221
|
+
const rootElement = doc.documentElement;
|
|
222
|
+
// SECURITY: Root element MUST be SVG - prevents wrapper element injection
|
|
223
|
+
// Reject structures like <div><svg>...</svg></div>
|
|
224
|
+
if (rootElement.tagName.toLowerCase() === 'svg') {
|
|
225
|
+
// SECURITY: Only proceed if there's exactly one root element
|
|
226
|
+
// This prevents attacks like: <svg></svg><script>alert('XSS')</script>
|
|
227
|
+
if (doc.children.length === 1) {
|
|
228
|
+
// Remove potentially dangerous attributes and event handlers
|
|
229
|
+
this.sanitizeSVGElement(rootElement);
|
|
230
|
+
// Clear the target element and append only the validated SVG element
|
|
231
|
+
element.innerHTML = '';
|
|
232
|
+
element.appendChild(rootElement);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// If not valid SVG, fall back to text content to prevent XSS
|
|
238
|
+
element.textContent = htmlContent;
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
// On any error, use text content for safety
|
|
242
|
+
// eslint-disable-next-line no-console
|
|
243
|
+
console.warn('[BugSpotter] Failed to inject custom SVG content:', error);
|
|
244
|
+
element.textContent = htmlContent;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Recursively sanitize SVG elements by removing dangerous tags and attributes
|
|
249
|
+
* Uses whitelists to ensure only safe SVG content is preserved
|
|
250
|
+
*/
|
|
251
|
+
sanitizeSVGElement(element) {
|
|
252
|
+
// Process all elements in the tree
|
|
253
|
+
const elementsToProcess = [element];
|
|
254
|
+
const processedElements = new WeakSet();
|
|
255
|
+
while (elementsToProcess.length > 0) {
|
|
256
|
+
const current = elementsToProcess.pop();
|
|
257
|
+
if (!current || processedElements.has(current))
|
|
258
|
+
continue;
|
|
259
|
+
processedElements.add(current);
|
|
260
|
+
// SECURITY: First, sanitize the current element's attributes (including root)
|
|
261
|
+
// This prevents attacks like <svg onload="alert('XSS')">
|
|
262
|
+
Array.from(current.attributes || []).forEach((attr) => {
|
|
263
|
+
const attrName = attr.name.toLowerCase();
|
|
264
|
+
// SECURITY: Explicitly reject all event handler attributes (on*)
|
|
265
|
+
// This provides defense-in-depth and prevents accidental whitelisting
|
|
266
|
+
if (attrName.startsWith('on')) {
|
|
267
|
+
current.removeAttribute(attr.name);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
// Only keep whitelisted attributes
|
|
271
|
+
if (!SAFE_SVG_ATTRIBUTES.has(attrName)) {
|
|
272
|
+
current.removeAttribute(attr.name);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
// Check attribute values for dangerous patterns
|
|
276
|
+
if (isDangerousAttributeValue(attr.value)) {
|
|
277
|
+
current.removeAttribute(attr.name);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
// Then, process children elements
|
|
282
|
+
const children = Array.from(current.children || []);
|
|
283
|
+
children.forEach((child) => {
|
|
284
|
+
const tagName = child.tagName.toLowerCase();
|
|
285
|
+
// SECURITY: Remove tags not in whitelist (blocks <script>, <style>, <iframe>, etc.)
|
|
286
|
+
if (!SAFE_SVG_TAGS.has(tagName)) {
|
|
287
|
+
child.remove();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
// Add to processing queue for recursive sanitization
|
|
291
|
+
elementsToProcess.push(child);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
98
295
|
getButtonStyles() {
|
|
99
296
|
const { position, size, offset, backgroundColor, zIndex } = this.options;
|
|
100
297
|
const positionStyles = this.getPositionStyles(position, offset);
|
|
@@ -159,7 +356,7 @@ class FloatingButton {
|
|
|
159
356
|
setIcon(icon) {
|
|
160
357
|
this.options.icon = icon;
|
|
161
358
|
if (icon === 'svg') {
|
|
162
|
-
this.button
|
|
359
|
+
this.setSafeHTMLContent(this.button, DEFAULT_SVG_ICON);
|
|
163
360
|
}
|
|
164
361
|
else {
|
|
165
362
|
this.button.textContent = icon;
|
package/docs/CDN.md
CHANGED
|
@@ -30,7 +30,7 @@ Add the SDK to your HTML file:
|
|
|
30
30
|
**Example:**
|
|
31
31
|
|
|
32
32
|
```html
|
|
33
|
-
<script src="https://cdn.bugspotter.io/sdk/bugspotter-
|
|
33
|
+
<script src="https://cdn.bugspotter.io/sdk/bugspotter-1.0.0.min.js"></script>
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
### Development (Latest)
|
|
@@ -49,8 +49,8 @@ For enhanced security, use SRI hashes to verify file integrity:
|
|
|
49
49
|
|
|
50
50
|
```html
|
|
51
51
|
<script
|
|
52
|
-
src="https://cdn.bugspotter.io/sdk/bugspotter-
|
|
53
|
-
integrity="sha384
|
|
52
|
+
src="https://cdn.bugspotter.io/sdk/bugspotter-1.0.0.min.js"
|
|
53
|
+
integrity="sha384-WmzRwRsJDYQTHnPU0mTuz+VqnCFn70GlSiGh6lsogKahPBEB48pTzfEEB71+uA7I"
|
|
54
54
|
crossorigin="anonymous"
|
|
55
55
|
></script>
|
|
56
56
|
```
|
|
@@ -58,7 +58,7 @@ For enhanced security, use SRI hashes to verify file integrity:
|
|
|
58
58
|
To generate SRI hash for a specific version:
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
|
-
curl https://cdn.bugspotter.io/sdk/bugspotter-
|
|
61
|
+
curl https://cdn.bugspotter.io/sdk/bugspotter-1.0.0.min.js | openssl dgst -sha384 -binary | openssl base64 -A
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
## 📝 Complete Example
|
|
@@ -75,7 +75,7 @@ curl https://cdn.bugspotter.io/sdk/bugspotter-0.1.0.min.js | openssl dgst -sha38
|
|
|
75
75
|
<button id="trigger-error">Trigger Test Error</button>
|
|
76
76
|
|
|
77
77
|
<!-- Load BugSpotter SDK -->
|
|
78
|
-
<script src="https://cdn.bugspotter.io/sdk/bugspotter-
|
|
78
|
+
<script src="https://cdn.bugspotter.io/sdk/bugspotter-1.0.0.min.js"></script>
|
|
79
79
|
|
|
80
80
|
<script>
|
|
81
81
|
// Initialize BugSpotter
|
package/eslint.config.js
CHANGED
|
@@ -80,6 +80,16 @@ export default [
|
|
|
80
80
|
},
|
|
81
81
|
{
|
|
82
82
|
files: ['**/*.test.{ts,js}', '**/*.spec.{ts,js}'],
|
|
83
|
+
languageOptions: {
|
|
84
|
+
globals: {
|
|
85
|
+
global: 'readonly',
|
|
86
|
+
Headers: 'readonly',
|
|
87
|
+
process: 'readonly',
|
|
88
|
+
ReadableStream: 'readonly',
|
|
89
|
+
WritableStream: 'readonly',
|
|
90
|
+
TransformStream: 'readonly',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
83
93
|
rules: {
|
|
84
94
|
'no-console': 'off',
|
|
85
95
|
'@typescript-eslint/no-explicit-any': 'off',
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bugspotter/sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Professional bug reporting SDK with screenshots, session replay, and automatic error capture for web applications",
|
|
5
|
+
"packageManager": "pnpm@9.15.0",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"module": "dist/index.esm.js",
|
|
7
8
|
"browser": "dist/bugspotter.min.js",
|
|
@@ -55,8 +56,7 @@
|
|
|
55
56
|
"license": "MIT",
|
|
56
57
|
"repository": {
|
|
57
58
|
"type": "git",
|
|
58
|
-
"url": "https://github.com/apexbridge-tech/bugspotter-sdk.git"
|
|
59
|
-
"directory": "packages/core"
|
|
59
|
+
"url": "https://github.com/apexbridge-tech/bugspotter-sdk.git"
|
|
60
60
|
},
|
|
61
61
|
"bugs": {
|
|
62
62
|
"url": "https://github.com/apexbridge-tech/bugspotter-sdk/issues"
|
|
@@ -69,7 +69,17 @@
|
|
|
69
69
|
"access": "public",
|
|
70
70
|
"registry": "https://registry.npmjs.org/"
|
|
71
71
|
},
|
|
72
|
+
"lint-staged": {
|
|
73
|
+
"*.{ts,js}": [
|
|
74
|
+
"eslint --fix",
|
|
75
|
+
"prettier --write"
|
|
76
|
+
],
|
|
77
|
+
"*.{json,md}": [
|
|
78
|
+
"prettier --write"
|
|
79
|
+
]
|
|
80
|
+
},
|
|
72
81
|
"dependencies": {
|
|
82
|
+
"@bugspotter/common": "^1.0.1",
|
|
73
83
|
"@rrweb/types": "2.0.0-alpha.18",
|
|
74
84
|
"html-to-image": "^1.11.13",
|
|
75
85
|
"pako": "^2.1.0",
|
|
@@ -90,8 +100,9 @@
|
|
|
90
100
|
"eslint": "^9.17.0",
|
|
91
101
|
"eslint-config-prettier": "^10.1.8",
|
|
92
102
|
"eslint-plugin-prettier": "^5.2.1",
|
|
93
|
-
"happy-dom": "^
|
|
103
|
+
"happy-dom": "^20.0.2",
|
|
94
104
|
"jsdom": "^24.1.0",
|
|
105
|
+
"lint-staged": "^15.2.11",
|
|
95
106
|
"prettier": "^3.4.2",
|
|
96
107
|
"rollup": "^4.55.1",
|
|
97
108
|
"rollup-plugin-dts": "^6.3.0",
|
package/release_notes.md
ADDED
package/rollup.config.js
CHANGED
package/tsconfig.cjs.json
CHANGED
|
@@ -1,42 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
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 [
|
|
43
|
-
...this.items.slice(this.index),
|
|
44
|
-
...this.items.slice(0, this.index),
|
|
45
|
-
];
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Clear all items from the buffer.
|
|
49
|
-
*/
|
|
50
|
-
clear() {
|
|
51
|
-
this.items = [];
|
|
52
|
-
this.index = 0;
|
|
53
|
-
this.count = 0;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Get the current number of items in the buffer.
|
|
57
|
-
*/
|
|
58
|
-
get size() {
|
|
59
|
-
return this.count;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Get the maximum capacity of the buffer.
|
|
63
|
-
*/
|
|
64
|
-
get capacity() {
|
|
65
|
-
return this.maxSize;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Check if the buffer is empty.
|
|
69
|
-
*/
|
|
70
|
-
get isEmpty() {
|
|
71
|
-
return this.count === 0;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Check if the buffer is full.
|
|
75
|
-
*/
|
|
76
|
-
get isFull() {
|
|
77
|
-
return this.count >= this.maxSize;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
exports.CircularBuffer = CircularBuffer;
|