@bugspotter/sdk 1.0.0 → 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/dist/bugspotter.min.js +1 -1
- package/dist/bugspotter.min.js.map +1 -1
- package/dist/capture/console.js +2 -2
- package/dist/capture/network.js +2 -2
- package/dist/index.esm.js +210 -313
- package/dist/index.esm.js.map +1 -1
- 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 -40
- package/dist/utils/url-helpers.js +10 -97
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
- package/release_notes.md +1 -16
- package/rollup.config.js +1 -1
- package/dist/core/circular-buffer.d.ts +0 -42
- package/dist/core/circular-buffer.js +0 -80
package/dist/capture/console.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ConsoleCapture = exports.SDK_LOG_PREFIX = void 0;
|
|
4
4
|
const base_capture_1 = require("./base-capture");
|
|
5
|
-
const
|
|
5
|
+
const common_1 = require("@bugspotter/common");
|
|
6
6
|
const CONSOLE_METHODS = [
|
|
7
7
|
'log',
|
|
8
8
|
'warn',
|
|
@@ -21,7 +21,7 @@ class ConsoleCapture extends base_capture_1.BaseCapture {
|
|
|
21
21
|
super(options);
|
|
22
22
|
this.originalMethods = new Map();
|
|
23
23
|
const maxLogs = (_a = options.maxLogs) !== null && _a !== void 0 ? _a : 100;
|
|
24
|
-
this.buffer = new
|
|
24
|
+
this.buffer = new common_1.CircularBuffer(maxLogs);
|
|
25
25
|
this.captureStackTrace = (_b = options.captureStackTrace) !== null && _b !== void 0 ? _b : true;
|
|
26
26
|
this.interceptConsole((_c = options.levels) !== null && _c !== void 0 ? _c : CONSOLE_METHODS);
|
|
27
27
|
}
|
package/dist/capture/network.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.NetworkCapture = void 0;
|
|
4
4
|
const base_capture_1 = require("./base-capture");
|
|
5
|
-
const
|
|
5
|
+
const common_1 = require("@bugspotter/common");
|
|
6
6
|
class NetworkCapture extends base_capture_1.BaseCapture {
|
|
7
7
|
constructor(options = {}) {
|
|
8
8
|
var _a;
|
|
9
9
|
super(options);
|
|
10
10
|
this.isIntercepting = false;
|
|
11
11
|
const maxRequests = (_a = options.maxRequests) !== null && _a !== void 0 ? _a : 50;
|
|
12
|
-
this.buffer = new
|
|
12
|
+
this.buffer = new common_1.CircularBuffer(maxRequests);
|
|
13
13
|
this.filterUrls = options.filterUrls;
|
|
14
14
|
this.originalFetch = window.fetch;
|
|
15
15
|
this.originalXHR = {
|
package/dist/index.esm.js
CHANGED
|
@@ -1940,106 +1940,124 @@ class ScreenshotProcessor {
|
|
|
1940
1940
|
}
|
|
1941
1941
|
|
|
1942
1942
|
/**
|
|
1943
|
-
*
|
|
1944
|
-
*
|
|
1943
|
+
* Generic circular buffer — fixed-size FIFO that overwrites oldest items.
|
|
1944
|
+
*
|
|
1945
|
+
* @template T The type of items stored in the buffer
|
|
1945
1946
|
*/
|
|
1947
|
+
let CircularBuffer$1 = class CircularBuffer {
|
|
1948
|
+
constructor(maxSize) {
|
|
1949
|
+
this.maxSize = maxSize;
|
|
1950
|
+
this.items = [];
|
|
1951
|
+
this.index = 0;
|
|
1952
|
+
this.count = 0;
|
|
1953
|
+
if (maxSize <= 0) {
|
|
1954
|
+
throw new Error('CircularBuffer maxSize must be greater than 0');
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
/**
|
|
1958
|
+
* Add an item to the buffer. If full, the oldest item is overwritten.
|
|
1959
|
+
*/
|
|
1960
|
+
add(item) {
|
|
1961
|
+
if (this.count < this.maxSize) {
|
|
1962
|
+
this.items.push(item);
|
|
1963
|
+
this.count++;
|
|
1964
|
+
}
|
|
1965
|
+
else {
|
|
1966
|
+
this.items[this.index] = item;
|
|
1967
|
+
}
|
|
1968
|
+
this.index = (this.index + 1) % this.maxSize;
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Get all items in chronological order (oldest to newest).
|
|
1972
|
+
* Returns a copy of the internal array.
|
|
1973
|
+
*/
|
|
1974
|
+
getAll() {
|
|
1975
|
+
if (this.count < this.maxSize) {
|
|
1976
|
+
return [...this.items];
|
|
1977
|
+
}
|
|
1978
|
+
return [
|
|
1979
|
+
...this.items.slice(this.index),
|
|
1980
|
+
...this.items.slice(0, this.index),
|
|
1981
|
+
];
|
|
1982
|
+
}
|
|
1983
|
+
/**
|
|
1984
|
+
* Clear all items from the buffer.
|
|
1985
|
+
*/
|
|
1986
|
+
clear() {
|
|
1987
|
+
this.items = [];
|
|
1988
|
+
this.index = 0;
|
|
1989
|
+
this.count = 0;
|
|
1990
|
+
}
|
|
1991
|
+
get size() {
|
|
1992
|
+
return this.count;
|
|
1993
|
+
}
|
|
1994
|
+
get capacity() {
|
|
1995
|
+
return this.maxSize;
|
|
1996
|
+
}
|
|
1997
|
+
get isEmpty() {
|
|
1998
|
+
return this.count === 0;
|
|
1999
|
+
}
|
|
2000
|
+
get isFull() {
|
|
2001
|
+
return this.count >= this.maxSize;
|
|
2002
|
+
}
|
|
2003
|
+
};
|
|
2004
|
+
|
|
1946
2005
|
/**
|
|
1947
|
-
*
|
|
2006
|
+
* PII pattern definitions for data sanitization.
|
|
1948
2007
|
*/
|
|
1949
2008
|
const DEFAULT_PATTERNS = {
|
|
1950
2009
|
email: {
|
|
1951
2010
|
name: 'email',
|
|
1952
2011
|
regex: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
|
|
1953
2012
|
description: 'Email addresses',
|
|
1954
|
-
|
|
1955
|
-
'user@example.com',
|
|
1956
|
-
'john.doe+tag@company.co.uk',
|
|
1957
|
-
'test_user@sub.domain.com',
|
|
1958
|
-
],
|
|
1959
|
-
priority: 1, // Highest priority - most specific
|
|
2013
|
+
priority: 1,
|
|
1960
2014
|
},
|
|
1961
2015
|
creditcard: {
|
|
1962
2016
|
name: 'creditcard',
|
|
1963
2017
|
regex: /\b(?:\d{4}[-\s]){3}\d{4}\b|\b\d{4}[-\s]\d{6}[-\s]\d{5}\b|\b\d{13,19}\b/g,
|
|
1964
|
-
description: 'Credit card numbers
|
|
1965
|
-
examples: [
|
|
1966
|
-
'4532-1488-0343-6467',
|
|
1967
|
-
'4532148803436467',
|
|
1968
|
-
'5425 2334 3010 9903',
|
|
1969
|
-
'3782 822463 10005',
|
|
1970
|
-
],
|
|
2018
|
+
description: 'Credit card numbers',
|
|
1971
2019
|
priority: 2,
|
|
1972
2020
|
},
|
|
1973
2021
|
ssn: {
|
|
1974
2022
|
name: 'ssn',
|
|
1975
2023
|
regex: /\b\d{3}-\d{2}-\d{4}\b|\b(?<!\d)\d{9}(?!\d)\b/g,
|
|
1976
2024
|
description: 'US Social Security Numbers',
|
|
1977
|
-
examples: ['123-45-6789', '987654321'],
|
|
1978
2025
|
priority: 3,
|
|
1979
2026
|
},
|
|
1980
2027
|
iin: {
|
|
1981
2028
|
name: 'iin',
|
|
1982
2029
|
regex: /\b[0-9]{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12][0-9]|3[01])\d{6}\b/g,
|
|
1983
|
-
description: 'Kazakhstan IIN/BIN
|
|
1984
|
-
examples: ['950315300123', '880612500456', '021225123456'],
|
|
2030
|
+
description: 'Kazakhstan IIN/BIN',
|
|
1985
2031
|
priority: 4,
|
|
1986
2032
|
},
|
|
1987
2033
|
ip: {
|
|
1988
2034
|
name: 'ip',
|
|
1989
2035
|
regex: /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b|(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\b/g,
|
|
1990
2036
|
description: 'IPv4 and IPv6 addresses',
|
|
1991
|
-
examples: [
|
|
1992
|
-
'192.168.1.100',
|
|
1993
|
-
'127.0.0.1',
|
|
1994
|
-
'2001:0db8:85a3:0000:0000:8a2e:0370:7334',
|
|
1995
|
-
],
|
|
1996
2037
|
priority: 5,
|
|
1997
2038
|
},
|
|
1998
2039
|
phone: {
|
|
1999
2040
|
name: 'phone',
|
|
2000
2041
|
regex: /\+\d{1,3}[-.\s]\d{3}[-.\s]\d{4}\b|\+\d{1,3}[-.\s]\d{3}[-.\s]\d{3}[-.\s]\d{4}\b|\(\d{3}\)\s*\d{3}[-.\s]\d{4}\b|\b\d{3}[-.\s]\d{3}[-.\s]\d{4}\b/g,
|
|
2001
|
-
description: '
|
|
2002
|
-
examples: [
|
|
2003
|
-
'+1-555-1234',
|
|
2004
|
-
'+1-555-123-4567',
|
|
2005
|
-
'(555) 123-4567',
|
|
2006
|
-
'555-123-4567',
|
|
2007
|
-
'+7 777 123 4567',
|
|
2008
|
-
],
|
|
2042
|
+
description: 'Phone numbers',
|
|
2009
2043
|
priority: 6,
|
|
2010
2044
|
},
|
|
2011
2045
|
apikey: {
|
|
2012
2046
|
name: 'apikey',
|
|
2013
2047
|
regex: /\b(?:sk|pk)_(?:live|test)_[a-zA-Z0-9]{24,}\b|AIza[a-zA-Z0-9_-]{35}|ya29\.[a-zA-Z0-9_-]+|AKIA[a-zA-Z0-9]{16}\b/g,
|
|
2014
|
-
description: 'API keys (Stripe, Google, AWS
|
|
2015
|
-
examples: [
|
|
2016
|
-
'sk_live_abc123def456ghi789jkl',
|
|
2017
|
-
'pk_test_xyz789abc123def456',
|
|
2018
|
-
'AIzaSyD1234567890abcdefghijklmnopqrst',
|
|
2019
|
-
'AKIAIOSFODNN7EXAMPLE',
|
|
2020
|
-
],
|
|
2048
|
+
description: 'API keys (Stripe, Google, AWS)',
|
|
2021
2049
|
priority: 7,
|
|
2022
2050
|
},
|
|
2023
2051
|
token: {
|
|
2024
2052
|
name: 'token',
|
|
2025
2053
|
regex: /\b(?:Bearer\s+)?[a-zA-Z0-9_-]{32,}\b|ghp_[a-zA-Z0-9]{36}|gho_[a-zA-Z0-9]{36}|github_pat_[a-zA-Z0-9_]{82}/g,
|
|
2026
|
-
description: '
|
|
2027
|
-
examples: [
|
|
2028
|
-
'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9',
|
|
2029
|
-
'ghp_abc123def456ghi789jkl012mno345pqr',
|
|
2030
|
-
'gho_xyz789abc123def456ghi789jkl012mno',
|
|
2031
|
-
],
|
|
2054
|
+
description: 'Auth tokens (Bearer, GitHub, JWT-like)',
|
|
2032
2055
|
priority: 8,
|
|
2033
2056
|
},
|
|
2034
2057
|
password: {
|
|
2035
2058
|
name: 'password',
|
|
2036
2059
|
regex: /(?:password|passwd|pwd)[\s:=]+[^\s]{6,}|(?:password|passwd|pwd)["']?\s*[:=]\s*["']?[^\s"']{6,}/gi,
|
|
2037
|
-
description: 'Password fields in text
|
|
2038
|
-
examples: [
|
|
2039
|
-
'password: MySecret123!',
|
|
2040
|
-
'passwd=SecurePass456',
|
|
2041
|
-
'pwd: "MyP@ssw0rd"',
|
|
2042
|
-
],
|
|
2060
|
+
description: 'Password fields in text',
|
|
2043
2061
|
priority: 9,
|
|
2044
2062
|
},
|
|
2045
2063
|
};
|
|
@@ -2055,13 +2073,147 @@ const PATTERN_CATEGORIES = {
|
|
|
2055
2073
|
kazakhstan: ['iin'],
|
|
2056
2074
|
};
|
|
2057
2075
|
/**
|
|
2058
|
-
*
|
|
2076
|
+
* Pre-configured pattern sets for common compliance/use cases
|
|
2059
2077
|
*/
|
|
2078
|
+
const PATTERN_PRESETS = {
|
|
2079
|
+
all: Object.keys(DEFAULT_PATTERNS),
|
|
2080
|
+
minimal: ['email', 'creditcard', 'ssn'],
|
|
2081
|
+
financial: ['creditcard', 'ssn'],
|
|
2082
|
+
contact: ['email', 'phone'],
|
|
2083
|
+
identification: ['ssn', 'iin'],
|
|
2084
|
+
credentials: ['apikey', 'token', 'password'],
|
|
2085
|
+
kazakhstan: ['email', 'phone', 'iin'],
|
|
2086
|
+
gdpr: ['email', 'phone', 'ip'],
|
|
2087
|
+
pci: ['creditcard'],
|
|
2088
|
+
security: [
|
|
2089
|
+
'email',
|
|
2090
|
+
'phone',
|
|
2091
|
+
'creditcard',
|
|
2092
|
+
'ssn',
|
|
2093
|
+
'apikey',
|
|
2094
|
+
'token',
|
|
2095
|
+
'password',
|
|
2096
|
+
],
|
|
2097
|
+
};
|
|
2060
2098
|
function getPatternsByPriority(patterns) {
|
|
2061
|
-
return [...patterns].sort((a, b) =>
|
|
2062
|
-
|
|
2063
|
-
|
|
2099
|
+
return [...patterns].sort((a, b) => a.priority - b.priority);
|
|
2100
|
+
}
|
|
2101
|
+
function createPatternConfig(preset) {
|
|
2102
|
+
const names = typeof preset === 'string' ? PATTERN_PRESETS[preset] : preset;
|
|
2103
|
+
return names.map((name) => DEFAULT_PATTERNS[name]);
|
|
2064
2104
|
}
|
|
2105
|
+
/**
|
|
2106
|
+
* Validate a custom pattern regex for performance issues
|
|
2107
|
+
*/
|
|
2108
|
+
function validatePattern(pattern) {
|
|
2109
|
+
const errors = [];
|
|
2110
|
+
if (!pattern.name)
|
|
2111
|
+
errors.push('Pattern must have a name');
|
|
2112
|
+
if (!pattern.regex) {
|
|
2113
|
+
errors.push('Pattern must have a regex');
|
|
2114
|
+
}
|
|
2115
|
+
else {
|
|
2116
|
+
if (!pattern.regex.global)
|
|
2117
|
+
errors.push('Pattern regex must have global flag');
|
|
2118
|
+
try {
|
|
2119
|
+
const testString = 'a'.repeat(1000);
|
|
2120
|
+
const start = Date.now();
|
|
2121
|
+
testString.match(pattern.regex);
|
|
2122
|
+
if (Date.now() - start > 100) {
|
|
2123
|
+
errors.push('Pattern regex may cause performance issues');
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
catch (e) {
|
|
2127
|
+
errors.push(`Pattern regex error: ${e.message}`);
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
return { valid: errors.length === 0, errors };
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
/**
|
|
2134
|
+
* URL helper utilities for endpoint validation and parsing.
|
|
2135
|
+
*/
|
|
2136
|
+
/**
|
|
2137
|
+
* Custom error for invalid endpoint URLs
|
|
2138
|
+
*/
|
|
2139
|
+
class InvalidEndpointError extends Error {
|
|
2140
|
+
constructor(endpoint, reason) {
|
|
2141
|
+
super(`Invalid endpoint URL: ${endpoint}. ${reason}`);
|
|
2142
|
+
this.endpoint = endpoint;
|
|
2143
|
+
this.reason = reason;
|
|
2144
|
+
this.name = 'InvalidEndpointError';
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
/**
|
|
2148
|
+
* Custom error for insecure endpoints
|
|
2149
|
+
*/
|
|
2150
|
+
class InsecureEndpointError extends Error {
|
|
2151
|
+
constructor(endpoint) {
|
|
2152
|
+
super(`Secure HTTPS connection required. Attempted to connect to insecure endpoint: "${endpoint}"`);
|
|
2153
|
+
this.endpoint = endpoint;
|
|
2154
|
+
this.name = 'InsecureEndpointError';
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
/**
|
|
2158
|
+
* Strip known endpoint suffixes from path.
|
|
2159
|
+
* Removes /api/v1/reports path.
|
|
2160
|
+
*/
|
|
2161
|
+
function stripEndpointSuffix(path) {
|
|
2162
|
+
const reportsIndex = path.lastIndexOf('/api/v1/reports');
|
|
2163
|
+
if (reportsIndex !== -1) {
|
|
2164
|
+
return path.substring(0, reportsIndex);
|
|
2165
|
+
}
|
|
2166
|
+
return path.replace(/\/$/, '') || '';
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* Extract base API URL from endpoint.
|
|
2170
|
+
* Returns scheme + host + base path (without /api/v1/reports suffix).
|
|
2171
|
+
*
|
|
2172
|
+
* @example
|
|
2173
|
+
* getApiBaseUrl('https://api.example.com/api/v1/reports')
|
|
2174
|
+
* // Returns: 'https://api.example.com'
|
|
2175
|
+
*
|
|
2176
|
+
* @throws InvalidEndpointError if endpoint is not a valid absolute URL
|
|
2177
|
+
*/
|
|
2178
|
+
function getApiBaseUrl(endpoint) {
|
|
2179
|
+
if (!endpoint) {
|
|
2180
|
+
throw new InvalidEndpointError('', 'No endpoint configured');
|
|
2181
|
+
}
|
|
2182
|
+
try {
|
|
2183
|
+
const url = new URL(endpoint);
|
|
2184
|
+
const basePath = stripEndpointSuffix(url.pathname);
|
|
2185
|
+
return url.origin + basePath;
|
|
2186
|
+
}
|
|
2187
|
+
catch {
|
|
2188
|
+
throw new InvalidEndpointError(endpoint, 'Must be a valid absolute URL (e.g., https://api.example.com/api/v1/reports)');
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
/**
|
|
2192
|
+
* Checks if the endpoint uses a secure protocol.
|
|
2193
|
+
* Uses the URL API for robust parsing.
|
|
2194
|
+
*
|
|
2195
|
+
* Allows HTTPS in production, HTTP only on localhost/127.0.0.1 for development.
|
|
2196
|
+
*/
|
|
2197
|
+
function isSecureEndpoint(endpoint) {
|
|
2198
|
+
if (!endpoint)
|
|
2199
|
+
return false;
|
|
2200
|
+
try {
|
|
2201
|
+
const url = new URL(endpoint.trim());
|
|
2202
|
+
return (url.protocol === 'https:' ||
|
|
2203
|
+
(url.protocol === 'http:' &&
|
|
2204
|
+
(url.hostname === 'localhost' || url.hostname === '127.0.0.1')));
|
|
2205
|
+
}
|
|
2206
|
+
catch {
|
|
2207
|
+
return false;
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
/**
|
|
2212
|
+
* PII Pattern Definitions
|
|
2213
|
+
* Re-exports from @bugspotter/common + SDK-specific extensions
|
|
2214
|
+
*/
|
|
2215
|
+
// Re-export everything from @bugspotter/common
|
|
2216
|
+
// SDK-specific extensions
|
|
2065
2217
|
/**
|
|
2066
2218
|
* Get pattern by name
|
|
2067
2219
|
*/
|
|
@@ -2076,12 +2228,6 @@ function getPatternsByCategory(category) {
|
|
|
2076
2228
|
return DEFAULT_PATTERNS[name];
|
|
2077
2229
|
});
|
|
2078
2230
|
}
|
|
2079
|
-
/**
|
|
2080
|
-
* Get all pattern names
|
|
2081
|
-
*/
|
|
2082
|
-
function getAllPatternNames() {
|
|
2083
|
-
return Object.keys(DEFAULT_PATTERNS);
|
|
2084
|
-
}
|
|
2085
2231
|
/**
|
|
2086
2232
|
* Custom pattern builder for advanced use cases
|
|
2087
2233
|
*/
|
|
@@ -2094,7 +2240,6 @@ class PatternBuilder {
|
|
|
2094
2240
|
return this;
|
|
2095
2241
|
}
|
|
2096
2242
|
regex(regex) {
|
|
2097
|
-
// Ensure global flag
|
|
2098
2243
|
if (!regex.global) {
|
|
2099
2244
|
const flags = regex.flags.includes('g') ? regex.flags : regex.flags + 'g';
|
|
2100
2245
|
this.pattern.regex = new RegExp(regex.source, flags);
|
|
@@ -2130,85 +2275,6 @@ class PatternBuilder {
|
|
|
2130
2275
|
};
|
|
2131
2276
|
}
|
|
2132
2277
|
}
|
|
2133
|
-
/**
|
|
2134
|
-
* Pre-configured pattern sets for common use cases
|
|
2135
|
-
*/
|
|
2136
|
-
const PATTERN_PRESETS = {
|
|
2137
|
-
/** All patterns enabled (PII + credentials) - default */
|
|
2138
|
-
all: getAllPatternNames(),
|
|
2139
|
-
/** Minimal - only most critical PII */
|
|
2140
|
-
minimal: ['email', 'creditcard', 'ssn'],
|
|
2141
|
-
/** Financial data only */
|
|
2142
|
-
financial: PATTERN_CATEGORIES.financial,
|
|
2143
|
-
/** Contact information only */
|
|
2144
|
-
contact: PATTERN_CATEGORIES.contact,
|
|
2145
|
-
/** Identification numbers only */
|
|
2146
|
-
identification: PATTERN_CATEGORIES.identification,
|
|
2147
|
-
/** Credentials and secrets only */
|
|
2148
|
-
credentials: PATTERN_CATEGORIES.credentials,
|
|
2149
|
-
/** Kazakhstan-specific patterns */
|
|
2150
|
-
kazakhstan: ['email', 'phone', 'iin'],
|
|
2151
|
-
/** GDPR compliance recommended set */
|
|
2152
|
-
gdpr: ['email', 'phone', 'ip'],
|
|
2153
|
-
/** PCI DSS compliance required */
|
|
2154
|
-
pci: ['creditcard'],
|
|
2155
|
-
/** Security-focused: PII + credentials */
|
|
2156
|
-
security: [
|
|
2157
|
-
'email',
|
|
2158
|
-
'phone',
|
|
2159
|
-
'creditcard',
|
|
2160
|
-
'ssn',
|
|
2161
|
-
'apikey',
|
|
2162
|
-
'token',
|
|
2163
|
-
'password',
|
|
2164
|
-
],
|
|
2165
|
-
};
|
|
2166
|
-
/**
|
|
2167
|
-
* Create custom pattern configuration
|
|
2168
|
-
*/
|
|
2169
|
-
function createPatternConfig(preset) {
|
|
2170
|
-
const names = typeof preset === 'string' ? PATTERN_PRESETS[preset] : preset;
|
|
2171
|
-
return names.map((name) => {
|
|
2172
|
-
return DEFAULT_PATTERNS[name];
|
|
2173
|
-
});
|
|
2174
|
-
}
|
|
2175
|
-
/**
|
|
2176
|
-
* Validate pattern regex
|
|
2177
|
-
*/
|
|
2178
|
-
function validatePattern(pattern) {
|
|
2179
|
-
const errors = [];
|
|
2180
|
-
if (!pattern.name) {
|
|
2181
|
-
errors.push('Pattern must have a name');
|
|
2182
|
-
}
|
|
2183
|
-
if (!pattern.regex) {
|
|
2184
|
-
errors.push('Pattern must have a regex');
|
|
2185
|
-
}
|
|
2186
|
-
else {
|
|
2187
|
-
if (!pattern.regex.global) {
|
|
2188
|
-
errors.push('Pattern regex must have global flag');
|
|
2189
|
-
}
|
|
2190
|
-
// Test regex doesn't cause catastrophic backtracking
|
|
2191
|
-
try {
|
|
2192
|
-
const testString = 'a'.repeat(1000);
|
|
2193
|
-
const start = Date.now();
|
|
2194
|
-
testString.match(pattern.regex);
|
|
2195
|
-
const duration = Date.now() - start;
|
|
2196
|
-
if (duration > 100) {
|
|
2197
|
-
errors.push(`Pattern regex may cause performance issues (took ${duration}ms on test)`);
|
|
2198
|
-
}
|
|
2199
|
-
}
|
|
2200
|
-
catch (error) {
|
|
2201
|
-
errors.push(`Pattern regex error: ${error.message}`);
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
if (pattern.priority < 0) {
|
|
2205
|
-
errors.push('Pattern priority must be non-negative');
|
|
2206
|
-
}
|
|
2207
|
-
return {
|
|
2208
|
-
valid: errors.length === 0,
|
|
2209
|
-
errors,
|
|
2210
|
-
};
|
|
2211
|
-
}
|
|
2212
2278
|
|
|
2213
2279
|
/**
|
|
2214
2280
|
* PII Detection and Sanitization Utility - REFACTORED
|
|
@@ -2530,7 +2596,7 @@ function createLogger(config) {
|
|
|
2530
2596
|
return new BugSpotterLogger(config);
|
|
2531
2597
|
}
|
|
2532
2598
|
|
|
2533
|
-
const logger$
|
|
2599
|
+
const logger$7 = getLogger();
|
|
2534
2600
|
/**
|
|
2535
2601
|
* BugReportModal
|
|
2536
2602
|
*
|
|
@@ -2810,7 +2876,7 @@ class BugReportModal {
|
|
|
2810
2876
|
finalScreenshot = yield this.screenshotProcessor.mergeRedactions(this.originalScreenshot, this.redactionCanvas.getCanvas());
|
|
2811
2877
|
}
|
|
2812
2878
|
catch (mergeError) {
|
|
2813
|
-
logger$
|
|
2879
|
+
logger$7.error('Failed to merge redactions:', mergeError);
|
|
2814
2880
|
finalScreenshot = this.originalScreenshot;
|
|
2815
2881
|
}
|
|
2816
2882
|
}
|
|
@@ -2871,98 +2937,6 @@ const DEFAULT_REPLAY_DURATION_SECONDS = 15;
|
|
|
2871
2937
|
*/
|
|
2872
2938
|
const MAX_RECOMMENDED_REPLAY_DURATION_SECONDS = 30;
|
|
2873
2939
|
|
|
2874
|
-
/**
|
|
2875
|
-
* URL Helper Utilities
|
|
2876
|
-
* Extract base API URL from endpoint configuration
|
|
2877
|
-
*/
|
|
2878
|
-
const logger$7 = getLogger();
|
|
2879
|
-
/**
|
|
2880
|
-
* Custom error for invalid endpoint URLs
|
|
2881
|
-
*/
|
|
2882
|
-
class InvalidEndpointError extends Error {
|
|
2883
|
-
constructor(endpoint, reason) {
|
|
2884
|
-
super(`Invalid endpoint URL: ${endpoint}. ${reason}`);
|
|
2885
|
-
this.endpoint = endpoint;
|
|
2886
|
-
this.reason = reason;
|
|
2887
|
-
this.name = 'InvalidEndpointError';
|
|
2888
|
-
}
|
|
2889
|
-
}
|
|
2890
|
-
/**
|
|
2891
|
-
* Strip known endpoint suffixes from path
|
|
2892
|
-
* Removes /api/v1/reports path
|
|
2893
|
-
*/
|
|
2894
|
-
function stripEndpointSuffix(path) {
|
|
2895
|
-
// Use lastIndexOf to handle paths like '/prefix/api/v1/reports'
|
|
2896
|
-
const reportsIndex = path.lastIndexOf('/api/v1/reports');
|
|
2897
|
-
if (reportsIndex !== -1) {
|
|
2898
|
-
return path.substring(0, reportsIndex);
|
|
2899
|
-
}
|
|
2900
|
-
// Remove trailing slash
|
|
2901
|
-
return path.replace(/\/$/, '') || '';
|
|
2902
|
-
}
|
|
2903
|
-
/**
|
|
2904
|
-
* Extract base API URL from endpoint
|
|
2905
|
-
* Returns scheme + host + base path (without /api/v1/reports suffix)
|
|
2906
|
-
*
|
|
2907
|
-
* @example
|
|
2908
|
-
* getApiBaseUrl('https://api.example.com/api/v1/reports')
|
|
2909
|
-
* // Returns: 'https://api.example.com'
|
|
2910
|
-
*
|
|
2911
|
-
* @throws InvalidEndpointError if endpoint is not a valid absolute URL
|
|
2912
|
-
*/
|
|
2913
|
-
function getApiBaseUrl(endpoint) {
|
|
2914
|
-
if (!endpoint) {
|
|
2915
|
-
throw new InvalidEndpointError('', 'No endpoint configured');
|
|
2916
|
-
}
|
|
2917
|
-
try {
|
|
2918
|
-
const url = new URL(endpoint);
|
|
2919
|
-
const basePath = stripEndpointSuffix(url.pathname);
|
|
2920
|
-
return url.origin + basePath;
|
|
2921
|
-
}
|
|
2922
|
-
catch (error) {
|
|
2923
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2924
|
-
logger$7.error('Invalid endpoint URL - must be a valid absolute URL', {
|
|
2925
|
-
endpoint,
|
|
2926
|
-
error: errorMessage,
|
|
2927
|
-
});
|
|
2928
|
-
throw new InvalidEndpointError(endpoint, 'Must be a valid absolute URL (e.g., https://api.example.com/api/v1/reports)');
|
|
2929
|
-
}
|
|
2930
|
-
}
|
|
2931
|
-
/**
|
|
2932
|
-
* specific error for insecure endpoints
|
|
2933
|
-
*/
|
|
2934
|
-
class InsecureEndpointError extends Error {
|
|
2935
|
-
constructor(endpoint) {
|
|
2936
|
-
super(`Secure HTTPS connection required. Attempted to connect to insecure endpoint: "${endpoint}"`);
|
|
2937
|
-
this.endpoint = endpoint;
|
|
2938
|
-
this.name = 'InsecureEndpointError';
|
|
2939
|
-
}
|
|
2940
|
-
}
|
|
2941
|
-
/**
|
|
2942
|
-
* Checks if the endpoint uses the secure HTTPS protocol.
|
|
2943
|
-
* Uses the URL API for robust parsing.
|
|
2944
|
-
*
|
|
2945
|
-
* @param endpoint The endpoint URL to check
|
|
2946
|
-
* @returns True if the endpoint uses HTTPS
|
|
2947
|
-
*/
|
|
2948
|
-
function isSecureEndpoint(endpoint) {
|
|
2949
|
-
if (!endpoint)
|
|
2950
|
-
return false;
|
|
2951
|
-
try {
|
|
2952
|
-
const url = new URL(endpoint.trim());
|
|
2953
|
-
// STRICT SECURITY:
|
|
2954
|
-
// 1. Production must use HTTPS
|
|
2955
|
-
// 2. Development allowed on localhost/127.0.0.1 via HTTP
|
|
2956
|
-
return (url.protocol === 'https:' ||
|
|
2957
|
-
(url.protocol === 'http:' &&
|
|
2958
|
-
(url.hostname === 'localhost' || url.hostname === '127.0.0.1')));
|
|
2959
|
-
}
|
|
2960
|
-
catch (_a) {
|
|
2961
|
-
// If it's not a valid URL, it's definitely not a secure endpoint
|
|
2962
|
-
return false;
|
|
2963
|
-
}
|
|
2964
|
-
}
|
|
2965
|
-
|
|
2966
2940
|
/**
|
|
2967
2941
|
* SDK version - auto-generated from package.json
|
|
2968
2942
|
* DO NOT EDIT THIS FILE MANUALLY
|
|
@@ -2970,7 +2944,7 @@ function isSecureEndpoint(endpoint) {
|
|
|
2970
2944
|
* This file is automatically generated during the build process.
|
|
2971
2945
|
* To update the version, modify package.json
|
|
2972
2946
|
*/
|
|
2973
|
-
const VERSION = '1.
|
|
2947
|
+
const VERSION = '1.1.0';
|
|
2974
2948
|
|
|
2975
2949
|
/**
|
|
2976
2950
|
* Configuration Validation Utilities
|
|
@@ -11089,83 +11063,6 @@ class ScreenshotCapture extends BaseCapture {
|
|
|
11089
11063
|
}
|
|
11090
11064
|
}
|
|
11091
11065
|
|
|
11092
|
-
/**
|
|
11093
|
-
* A generic circular buffer implementation for storing a fixed number of items.
|
|
11094
|
-
* When the buffer is full, new items overwrite the oldest items.
|
|
11095
|
-
*
|
|
11096
|
-
* @template T The type of items stored in the buffer
|
|
11097
|
-
*/
|
|
11098
|
-
let CircularBuffer$1 = class CircularBuffer {
|
|
11099
|
-
constructor(maxSize) {
|
|
11100
|
-
this.maxSize = maxSize;
|
|
11101
|
-
this.items = [];
|
|
11102
|
-
this.index = 0;
|
|
11103
|
-
this.count = 0;
|
|
11104
|
-
if (maxSize <= 0) {
|
|
11105
|
-
throw new Error('CircularBuffer maxSize must be greater than 0');
|
|
11106
|
-
}
|
|
11107
|
-
}
|
|
11108
|
-
/**
|
|
11109
|
-
* Add an item to the buffer. If the buffer is full, the oldest item is overwritten.
|
|
11110
|
-
*/
|
|
11111
|
-
add(item) {
|
|
11112
|
-
if (this.count < this.maxSize) {
|
|
11113
|
-
this.items.push(item);
|
|
11114
|
-
this.count++;
|
|
11115
|
-
}
|
|
11116
|
-
else {
|
|
11117
|
-
this.items[this.index] = item;
|
|
11118
|
-
}
|
|
11119
|
-
this.index = (this.index + 1) % this.maxSize;
|
|
11120
|
-
}
|
|
11121
|
-
/**
|
|
11122
|
-
* Get all items in chronological order (oldest to newest).
|
|
11123
|
-
* Returns a copy of the internal array.
|
|
11124
|
-
*/
|
|
11125
|
-
getAll() {
|
|
11126
|
-
if (this.count < this.maxSize) {
|
|
11127
|
-
return [...this.items];
|
|
11128
|
-
}
|
|
11129
|
-
// Return items in chronological order when buffer is full
|
|
11130
|
-
return [
|
|
11131
|
-
...this.items.slice(this.index),
|
|
11132
|
-
...this.items.slice(0, this.index),
|
|
11133
|
-
];
|
|
11134
|
-
}
|
|
11135
|
-
/**
|
|
11136
|
-
* Clear all items from the buffer.
|
|
11137
|
-
*/
|
|
11138
|
-
clear() {
|
|
11139
|
-
this.items = [];
|
|
11140
|
-
this.index = 0;
|
|
11141
|
-
this.count = 0;
|
|
11142
|
-
}
|
|
11143
|
-
/**
|
|
11144
|
-
* Get the current number of items in the buffer.
|
|
11145
|
-
*/
|
|
11146
|
-
get size() {
|
|
11147
|
-
return this.count;
|
|
11148
|
-
}
|
|
11149
|
-
/**
|
|
11150
|
-
* Get the maximum capacity of the buffer.
|
|
11151
|
-
*/
|
|
11152
|
-
get capacity() {
|
|
11153
|
-
return this.maxSize;
|
|
11154
|
-
}
|
|
11155
|
-
/**
|
|
11156
|
-
* Check if the buffer is empty.
|
|
11157
|
-
*/
|
|
11158
|
-
get isEmpty() {
|
|
11159
|
-
return this.count === 0;
|
|
11160
|
-
}
|
|
11161
|
-
/**
|
|
11162
|
-
* Check if the buffer is full.
|
|
11163
|
-
*/
|
|
11164
|
-
get isFull() {
|
|
11165
|
-
return this.count >= this.maxSize;
|
|
11166
|
-
}
|
|
11167
|
-
};
|
|
11168
|
-
|
|
11169
11066
|
const CONSOLE_METHODS = [
|
|
11170
11067
|
'log',
|
|
11171
11068
|
'warn',
|