@crashsense/utils 0.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.
@@ -0,0 +1,36 @@
1
+ import { EventBus, StackFrame, DeviceInfo } from '@crashsense/types';
2
+
3
+ declare class RingBuffer<T> {
4
+ private readonly _capacity;
5
+ private buffer;
6
+ private head;
7
+ private _size;
8
+ constructor(_capacity: number);
9
+ push(item: T): void;
10
+ drain(): T[];
11
+ peek(): T | undefined;
12
+ clear(): void;
13
+ get size(): number;
14
+ get capacity(): number;
15
+ }
16
+
17
+ declare function createEventBus(): EventBus;
18
+
19
+ declare function generateFingerprint(errorType: string, errorMessage: string, stackFrames: StackFrame[]): string;
20
+
21
+ interface RateLimiter {
22
+ tryAcquire(): boolean;
23
+ reset(): void;
24
+ }
25
+ declare function createRateLimiter(maxPerMinute: number): RateLimiter;
26
+
27
+ declare function scrubPII(text: string): string;
28
+ declare function scrubObject(obj: Record<string, unknown>): Record<string, unknown>;
29
+
30
+ declare function parseStackTrace(rawStack: string): StackFrame[];
31
+
32
+ declare function collectDeviceInfo(): DeviceInfo;
33
+
34
+ declare function generateId(): string;
35
+
36
+ export { type RateLimiter, RingBuffer, collectDeviceInfo, createEventBus, createRateLimiter, generateFingerprint, generateId, parseStackTrace, scrubObject, scrubPII };
@@ -0,0 +1,36 @@
1
+ import { EventBus, StackFrame, DeviceInfo } from '@crashsense/types';
2
+
3
+ declare class RingBuffer<T> {
4
+ private readonly _capacity;
5
+ private buffer;
6
+ private head;
7
+ private _size;
8
+ constructor(_capacity: number);
9
+ push(item: T): void;
10
+ drain(): T[];
11
+ peek(): T | undefined;
12
+ clear(): void;
13
+ get size(): number;
14
+ get capacity(): number;
15
+ }
16
+
17
+ declare function createEventBus(): EventBus;
18
+
19
+ declare function generateFingerprint(errorType: string, errorMessage: string, stackFrames: StackFrame[]): string;
20
+
21
+ interface RateLimiter {
22
+ tryAcquire(): boolean;
23
+ reset(): void;
24
+ }
25
+ declare function createRateLimiter(maxPerMinute: number): RateLimiter;
26
+
27
+ declare function scrubPII(text: string): string;
28
+ declare function scrubObject(obj: Record<string, unknown>): Record<string, unknown>;
29
+
30
+ declare function parseStackTrace(rawStack: string): StackFrame[];
31
+
32
+ declare function collectDeviceInfo(): DeviceInfo;
33
+
34
+ declare function generateId(): string;
35
+
36
+ export { type RateLimiter, RingBuffer, collectDeviceInfo, createEventBus, createRateLimiter, generateFingerprint, generateId, parseStackTrace, scrubObject, scrubPII };
package/dist/index.js ADDED
@@ -0,0 +1,258 @@
1
+ 'use strict';
2
+
3
+ // src/ring-buffer.ts
4
+ var RingBuffer = class {
5
+ constructor(_capacity) {
6
+ this._capacity = _capacity;
7
+ this.head = 0;
8
+ this._size = 0;
9
+ this.buffer = new Array(_capacity);
10
+ }
11
+ push(item) {
12
+ this.buffer[this.head] = item;
13
+ this.head = (this.head + 1) % this._capacity;
14
+ this._size = Math.min(this._size + 1, this._capacity);
15
+ }
16
+ drain() {
17
+ const result = [];
18
+ const start = this._size < this._capacity ? 0 : this.head;
19
+ for (let i = 0; i < this._size; i++) {
20
+ const item = this.buffer[(start + i) % this._capacity];
21
+ if (item !== void 0) {
22
+ result.push(item);
23
+ }
24
+ }
25
+ return result;
26
+ }
27
+ peek() {
28
+ if (this._size === 0) return void 0;
29
+ const idx = (this.head - 1 + this._capacity) % this._capacity;
30
+ return this.buffer[idx];
31
+ }
32
+ clear() {
33
+ this.buffer = new Array(this._capacity);
34
+ this.head = 0;
35
+ this._size = 0;
36
+ }
37
+ get size() {
38
+ return this._size;
39
+ }
40
+ get capacity() {
41
+ return this._capacity;
42
+ }
43
+ };
44
+
45
+ // src/event-bus.ts
46
+ function createEventBus() {
47
+ const handlers = /* @__PURE__ */ new Map();
48
+ return {
49
+ on(event, handler) {
50
+ if (!handlers.has(event)) {
51
+ handlers.set(event, /* @__PURE__ */ new Set());
52
+ }
53
+ handlers.get(event).add(handler);
54
+ },
55
+ off(event, handler) {
56
+ const set = handlers.get(event);
57
+ if (set) {
58
+ set.delete(handler);
59
+ }
60
+ },
61
+ emit(event, data) {
62
+ const set = handlers.get(event);
63
+ if (set) {
64
+ for (const handler of set) {
65
+ try {
66
+ handler(data);
67
+ } catch (_) {
68
+ }
69
+ }
70
+ }
71
+ }
72
+ };
73
+ }
74
+
75
+ // src/fingerprint.ts
76
+ function djb2Hash(str) {
77
+ let hash = 5381;
78
+ for (let i = 0; i < str.length; i++) {
79
+ hash = (hash << 5) + hash + str.charCodeAt(i) | 0;
80
+ }
81
+ return hash >>> 0;
82
+ }
83
+ function generateFingerprint(errorType, errorMessage, stackFrames) {
84
+ const topFrames = stackFrames.slice(0, 3);
85
+ const frameStr = topFrames.map((f) => `${f.filename}:${f.function}`).join("|");
86
+ const input = `${errorType}:${errorMessage}:${frameStr}`;
87
+ const hash = djb2Hash(input);
88
+ return hash.toString(16).padStart(8, "0");
89
+ }
90
+
91
+ // src/rate-limiter.ts
92
+ function createRateLimiter(maxPerMinute) {
93
+ let tokens = maxPerMinute;
94
+ let lastRefill = Date.now();
95
+ function refill() {
96
+ const now = Date.now();
97
+ const elapsed = now - lastRefill;
98
+ const newTokens = elapsed / 6e4 * maxPerMinute;
99
+ tokens = Math.min(maxPerMinute, tokens + newTokens);
100
+ lastRefill = now;
101
+ }
102
+ return {
103
+ tryAcquire() {
104
+ refill();
105
+ if (tokens >= 1) {
106
+ tokens -= 1;
107
+ return true;
108
+ }
109
+ return false;
110
+ },
111
+ reset() {
112
+ tokens = maxPerMinute;
113
+ lastRefill = Date.now();
114
+ }
115
+ };
116
+ }
117
+
118
+ // src/pii-scrubber.ts
119
+ var EMAIL_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
120
+ var IPV4_PATTERN = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g;
121
+ var BEARER_PATTERN = /Bearer\s+[A-Za-z0-9\-._~+\/]+/g;
122
+ var CARD_PATTERN = /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g;
123
+ function scrubPII(text) {
124
+ return text.replace(EMAIL_PATTERN, "[EMAIL]").replace(BEARER_PATTERN, "Bearer [TOKEN]").replace(CARD_PATTERN, "[CARD]").replace(IPV4_PATTERN, "[IP]");
125
+ }
126
+ function scrubObject(obj) {
127
+ const result = {};
128
+ for (const [key, value] of Object.entries(obj)) {
129
+ if (typeof value === "string") {
130
+ result[key] = scrubPII(value);
131
+ } else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
132
+ result[key] = scrubObject(value);
133
+ } else if (Array.isArray(value)) {
134
+ result[key] = value.map(
135
+ (item) => typeof item === "string" ? scrubPII(item) : item !== null && typeof item === "object" ? scrubObject(item) : item
136
+ );
137
+ } else {
138
+ result[key] = value;
139
+ }
140
+ }
141
+ return result;
142
+ }
143
+
144
+ // src/stack-parser.ts
145
+ var CHROME_PATTERN = /^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/;
146
+ var FIREFOX_PATTERN = /^(.+?)@(.+?):(\d+):(\d+)$/;
147
+ var NON_APP_PATTERNS = [
148
+ /node_modules/,
149
+ /^https?:\/\/cdn\./,
150
+ /^https?:\/\/unpkg\.com/,
151
+ /^https?:\/\/cdnjs\./,
152
+ /^chrome-extension:\/\//,
153
+ /^moz-extension:\/\//,
154
+ /^webpack\/runtime/,
155
+ /^\[native code\]/
156
+ ];
157
+ function isInApp(filename) {
158
+ return !NON_APP_PATTERNS.some((p) => p.test(filename));
159
+ }
160
+ function parseStackTrace(rawStack) {
161
+ const lines = rawStack.split("\n");
162
+ const frames = [];
163
+ for (const line of lines) {
164
+ const trimmed = line.trim();
165
+ if (!trimmed) continue;
166
+ let match = CHROME_PATTERN.exec(trimmed);
167
+ if (match) {
168
+ frames.push({
169
+ function: match[1] || "<anonymous>",
170
+ filename: match[2],
171
+ lineno: parseInt(match[3], 10),
172
+ colno: parseInt(match[4], 10),
173
+ inApp: isInApp(match[2])
174
+ });
175
+ continue;
176
+ }
177
+ match = FIREFOX_PATTERN.exec(trimmed);
178
+ if (match) {
179
+ frames.push({
180
+ function: match[1] || "<anonymous>",
181
+ filename: match[2],
182
+ lineno: parseInt(match[3], 10),
183
+ colno: parseInt(match[4], 10),
184
+ inApp: isInApp(match[2])
185
+ });
186
+ }
187
+ }
188
+ return frames;
189
+ }
190
+
191
+ // src/device-info.ts
192
+ var isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
193
+ function collectDeviceInfo() {
194
+ if (!isBrowser) {
195
+ return {
196
+ userAgent: "node",
197
+ platform: typeof process !== "undefined" ? process.platform : "unknown",
198
+ vendor: "",
199
+ deviceMemory: null,
200
+ hardwareConcurrency: null,
201
+ viewport: { width: 0, height: 0 },
202
+ devicePixelRatio: 1,
203
+ touchSupport: false,
204
+ colorScheme: "light",
205
+ reducedMotion: false,
206
+ language: "en",
207
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
208
+ };
209
+ }
210
+ const nav = navigator;
211
+ return {
212
+ userAgent: nav.userAgent,
213
+ platform: nav.platform,
214
+ vendor: nav.vendor || "",
215
+ deviceMemory: nav.deviceMemory ?? null,
216
+ hardwareConcurrency: nav.hardwareConcurrency ?? null,
217
+ viewport: {
218
+ width: window.innerWidth,
219
+ height: window.innerHeight
220
+ },
221
+ devicePixelRatio: window.devicePixelRatio || 1,
222
+ touchSupport: "ontouchstart" in window || nav.maxTouchPoints > 0,
223
+ colorScheme: window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light",
224
+ reducedMotion: window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false,
225
+ language: nav.language || "en",
226
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
227
+ };
228
+ }
229
+
230
+ // src/uuid.ts
231
+ function generateId() {
232
+ const hex = "0123456789abcdef";
233
+ const segments = [8, 4, 4, 4, 12];
234
+ const parts = [];
235
+ for (const len of segments) {
236
+ let segment = "";
237
+ for (let i = 0; i < len; i++) {
238
+ segment += hex[Math.floor(Math.random() * 16)];
239
+ }
240
+ parts.push(segment);
241
+ }
242
+ const p2 = "4" + parts[2].slice(1);
243
+ const variantChar = hex[8 + Math.floor(Math.random() * 4)];
244
+ const p3 = variantChar + parts[3].slice(1);
245
+ return `${parts[0]}-${parts[1]}-${p2}-${p3}-${parts[4]}`;
246
+ }
247
+
248
+ exports.RingBuffer = RingBuffer;
249
+ exports.collectDeviceInfo = collectDeviceInfo;
250
+ exports.createEventBus = createEventBus;
251
+ exports.createRateLimiter = createRateLimiter;
252
+ exports.generateFingerprint = generateFingerprint;
253
+ exports.generateId = generateId;
254
+ exports.parseStackTrace = parseStackTrace;
255
+ exports.scrubObject = scrubObject;
256
+ exports.scrubPII = scrubPII;
257
+ //# sourceMappingURL=index.js.map
258
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ring-buffer.ts","../src/event-bus.ts","../src/fingerprint.ts","../src/rate-limiter.ts","../src/pii-scrubber.ts","../src/stack-parser.ts","../src/device-info.ts","../src/uuid.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export class RingBuffer<T> {\n private buffer: (T | undefined)[];\n private head = 0;\n private _size = 0;\n\n constructor(private readonly _capacity: number) {\n this.buffer = new Array(_capacity);\n }\n\n push(item: T): void {\n this.buffer[this.head] = item;\n this.head = (this.head + 1) % this._capacity;\n this._size = Math.min(this._size + 1, this._capacity);\n }\n\n drain(): T[] {\n const result: T[] = [];\n const start = this._size < this._capacity ? 0 : this.head;\n for (let i = 0; i < this._size; i++) {\n const item = this.buffer[(start + i) % this._capacity];\n if (item !== undefined) {\n result.push(item);\n }\n }\n return result;\n }\n\n peek(): T | undefined {\n if (this._size === 0) return undefined;\n const idx = (this.head - 1 + this._capacity) % this._capacity;\n return this.buffer[idx];\n }\n\n clear(): void {\n this.buffer = new Array(this._capacity);\n this.head = 0;\n this._size = 0;\n }\n\n get size(): number {\n return this._size;\n }\n\n get capacity(): number {\n return this._capacity;\n }\n}\n","import type { EventBus, EventBusEventMap } from '@crashsense/types';\n\ntype Handler<T> = (data: T) => void;\n\nexport function createEventBus(): EventBus {\n const handlers = new Map<string, Set<Handler<unknown>>>();\n\n return {\n on<K extends keyof EventBusEventMap>(\n event: K,\n handler: (data: EventBusEventMap[K]) => void,\n ): void {\n if (!handlers.has(event as string)) {\n handlers.set(event as string, new Set());\n }\n handlers.get(event as string)!.add(handler as Handler<unknown>);\n },\n\n off<K extends keyof EventBusEventMap>(\n event: K,\n handler: (data: EventBusEventMap[K]) => void,\n ): void {\n const set = handlers.get(event as string);\n if (set) {\n set.delete(handler as Handler<unknown>);\n }\n },\n\n emit<K extends keyof EventBusEventMap>(\n event: K,\n data: EventBusEventMap[K],\n ): void {\n const set = handlers.get(event as string);\n if (set) {\n for (const handler of set) {\n try {\n handler(data);\n } catch (_) {\n // SDK errors must never crash the host application\n }\n }\n }\n },\n };\n}\n","import type { StackFrame } from '@crashsense/types';\n\nfunction djb2Hash(str: string): number {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0;\n }\n return hash >>> 0;\n}\n\nexport function generateFingerprint(\n errorType: string,\n errorMessage: string,\n stackFrames: StackFrame[],\n): string {\n const topFrames = stackFrames.slice(0, 3);\n const frameStr = topFrames\n .map((f) => `${f.filename}:${f.function}`)\n .join('|');\n const input = `${errorType}:${errorMessage}:${frameStr}`;\n const hash = djb2Hash(input);\n return hash.toString(16).padStart(8, '0');\n}\n","export interface RateLimiter {\n tryAcquire(): boolean;\n reset(): void;\n}\n\nexport function createRateLimiter(maxPerMinute: number): RateLimiter {\n let tokens = maxPerMinute;\n let lastRefill = Date.now();\n\n function refill(): void {\n const now = Date.now();\n const elapsed = now - lastRefill;\n const newTokens = (elapsed / 60000) * maxPerMinute;\n tokens = Math.min(maxPerMinute, tokens + newTokens);\n lastRefill = now;\n }\n\n return {\n tryAcquire(): boolean {\n refill();\n if (tokens >= 1) {\n tokens -= 1;\n return true;\n }\n return false;\n },\n\n reset(): void {\n tokens = maxPerMinute;\n lastRefill = Date.now();\n },\n };\n}\n","const EMAIL_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g;\nconst IPV4_PATTERN = /\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b/g;\nconst BEARER_PATTERN = /Bearer\\s+[A-Za-z0-9\\-._~+\\/]+/g;\nconst CARD_PATTERN = /\\b\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b/g;\n\nexport function scrubPII(text: string): string {\n return text\n .replace(EMAIL_PATTERN, '[EMAIL]')\n .replace(BEARER_PATTERN, 'Bearer [TOKEN]')\n .replace(CARD_PATTERN, '[CARD]')\n .replace(IPV4_PATTERN, '[IP]');\n}\n\nexport function scrubObject(\n obj: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (typeof value === 'string') {\n result[key] = scrubPII(value);\n } else if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n result[key] = scrubObject(value as Record<string, unknown>);\n } else if (Array.isArray(value)) {\n result[key] = value.map((item) =>\n typeof item === 'string'\n ? scrubPII(item)\n : item !== null && typeof item === 'object'\n ? scrubObject(item as Record<string, unknown>)\n : item,\n );\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n","import type { StackFrame } from '@crashsense/types';\n\n// Chrome/V8: \" at functionName (filename:line:col)\" or \" at filename:line:col\"\nconst CHROME_PATTERN = /^\\s*at\\s+(?:(.+?)\\s+\\()?(.+?):(\\d+):(\\d+)\\)?$/;\n\n// Firefox: \"functionName@filename:line:col\"\nconst FIREFOX_PATTERN = /^(.+?)@(.+?):(\\d+):(\\d+)$/;\n\nconst NON_APP_PATTERNS = [\n /node_modules/,\n /^https?:\\/\\/cdn\\./,\n /^https?:\\/\\/unpkg\\.com/,\n /^https?:\\/\\/cdnjs\\./,\n /^chrome-extension:\\/\\//,\n /^moz-extension:\\/\\//,\n /^webpack\\/runtime/,\n /^\\[native code\\]/,\n];\n\nfunction isInApp(filename: string): boolean {\n return !NON_APP_PATTERNS.some((p) => p.test(filename));\n}\n\nexport function parseStackTrace(rawStack: string): StackFrame[] {\n const lines = rawStack.split('\\n');\n const frames: StackFrame[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n let match = CHROME_PATTERN.exec(trimmed);\n if (match) {\n frames.push({\n function: match[1] || '<anonymous>',\n filename: match[2],\n lineno: parseInt(match[3], 10),\n colno: parseInt(match[4], 10),\n inApp: isInApp(match[2]),\n });\n continue;\n }\n\n match = FIREFOX_PATTERN.exec(trimmed);\n if (match) {\n frames.push({\n function: match[1] || '<anonymous>',\n filename: match[2],\n lineno: parseInt(match[3], 10),\n colno: parseInt(match[4], 10),\n inApp: isInApp(match[2]),\n });\n }\n }\n\n return frames;\n}\n","import type { DeviceInfo } from '@crashsense/types';\n\nconst isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';\n\nexport function collectDeviceInfo(): DeviceInfo {\n if (!isBrowser) {\n return {\n userAgent: 'node',\n platform: typeof process !== 'undefined' ? process.platform : 'unknown',\n vendor: '',\n deviceMemory: null,\n hardwareConcurrency: null,\n viewport: { width: 0, height: 0 },\n devicePixelRatio: 1,\n touchSupport: false,\n colorScheme: 'light',\n reducedMotion: false,\n language: 'en',\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n };\n }\n\n const nav = navigator as Navigator & {\n deviceMemory?: number;\n };\n\n return {\n userAgent: nav.userAgent,\n platform: nav.platform,\n vendor: nav.vendor || '',\n deviceMemory: nav.deviceMemory ?? null,\n hardwareConcurrency: nav.hardwareConcurrency ?? null,\n viewport: {\n width: window.innerWidth,\n height: window.innerHeight,\n },\n devicePixelRatio: window.devicePixelRatio || 1,\n touchSupport: 'ontouchstart' in window || nav.maxTouchPoints > 0,\n colorScheme: window.matchMedia?.('(prefers-color-scheme: dark)').matches\n ? 'dark'\n : 'light',\n reducedMotion: window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false,\n language: nav.language || 'en',\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n };\n}\n","export function generateId(): string {\n const hex = '0123456789abcdef';\n const segments = [8, 4, 4, 4, 12];\n const parts: string[] = [];\n\n for (const len of segments) {\n let segment = '';\n for (let i = 0; i < len; i++) {\n segment += hex[Math.floor(Math.random() * 16)];\n }\n parts.push(segment);\n }\n\n // Set version (4) and variant bits\n const p2 = '4' + parts[2].slice(1);\n const variantChar = hex[8 + Math.floor(Math.random() * 4)];\n const p3 = variantChar + parts[3].slice(1);\n\n return `${parts[0]}-${parts[1]}-${p2}-${p3}-${parts[4]}`;\n}\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,248 @@
1
+ // src/ring-buffer.ts
2
+ var RingBuffer = class {
3
+ constructor(_capacity) {
4
+ this._capacity = _capacity;
5
+ this.head = 0;
6
+ this._size = 0;
7
+ this.buffer = new Array(_capacity);
8
+ }
9
+ push(item) {
10
+ this.buffer[this.head] = item;
11
+ this.head = (this.head + 1) % this._capacity;
12
+ this._size = Math.min(this._size + 1, this._capacity);
13
+ }
14
+ drain() {
15
+ const result = [];
16
+ const start = this._size < this._capacity ? 0 : this.head;
17
+ for (let i = 0; i < this._size; i++) {
18
+ const item = this.buffer[(start + i) % this._capacity];
19
+ if (item !== void 0) {
20
+ result.push(item);
21
+ }
22
+ }
23
+ return result;
24
+ }
25
+ peek() {
26
+ if (this._size === 0) return void 0;
27
+ const idx = (this.head - 1 + this._capacity) % this._capacity;
28
+ return this.buffer[idx];
29
+ }
30
+ clear() {
31
+ this.buffer = new Array(this._capacity);
32
+ this.head = 0;
33
+ this._size = 0;
34
+ }
35
+ get size() {
36
+ return this._size;
37
+ }
38
+ get capacity() {
39
+ return this._capacity;
40
+ }
41
+ };
42
+
43
+ // src/event-bus.ts
44
+ function createEventBus() {
45
+ const handlers = /* @__PURE__ */ new Map();
46
+ return {
47
+ on(event, handler) {
48
+ if (!handlers.has(event)) {
49
+ handlers.set(event, /* @__PURE__ */ new Set());
50
+ }
51
+ handlers.get(event).add(handler);
52
+ },
53
+ off(event, handler) {
54
+ const set = handlers.get(event);
55
+ if (set) {
56
+ set.delete(handler);
57
+ }
58
+ },
59
+ emit(event, data) {
60
+ const set = handlers.get(event);
61
+ if (set) {
62
+ for (const handler of set) {
63
+ try {
64
+ handler(data);
65
+ } catch (_) {
66
+ }
67
+ }
68
+ }
69
+ }
70
+ };
71
+ }
72
+
73
+ // src/fingerprint.ts
74
+ function djb2Hash(str) {
75
+ let hash = 5381;
76
+ for (let i = 0; i < str.length; i++) {
77
+ hash = (hash << 5) + hash + str.charCodeAt(i) | 0;
78
+ }
79
+ return hash >>> 0;
80
+ }
81
+ function generateFingerprint(errorType, errorMessage, stackFrames) {
82
+ const topFrames = stackFrames.slice(0, 3);
83
+ const frameStr = topFrames.map((f) => `${f.filename}:${f.function}`).join("|");
84
+ const input = `${errorType}:${errorMessage}:${frameStr}`;
85
+ const hash = djb2Hash(input);
86
+ return hash.toString(16).padStart(8, "0");
87
+ }
88
+
89
+ // src/rate-limiter.ts
90
+ function createRateLimiter(maxPerMinute) {
91
+ let tokens = maxPerMinute;
92
+ let lastRefill = Date.now();
93
+ function refill() {
94
+ const now = Date.now();
95
+ const elapsed = now - lastRefill;
96
+ const newTokens = elapsed / 6e4 * maxPerMinute;
97
+ tokens = Math.min(maxPerMinute, tokens + newTokens);
98
+ lastRefill = now;
99
+ }
100
+ return {
101
+ tryAcquire() {
102
+ refill();
103
+ if (tokens >= 1) {
104
+ tokens -= 1;
105
+ return true;
106
+ }
107
+ return false;
108
+ },
109
+ reset() {
110
+ tokens = maxPerMinute;
111
+ lastRefill = Date.now();
112
+ }
113
+ };
114
+ }
115
+
116
+ // src/pii-scrubber.ts
117
+ var EMAIL_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
118
+ var IPV4_PATTERN = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g;
119
+ var BEARER_PATTERN = /Bearer\s+[A-Za-z0-9\-._~+\/]+/g;
120
+ var CARD_PATTERN = /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g;
121
+ function scrubPII(text) {
122
+ return text.replace(EMAIL_PATTERN, "[EMAIL]").replace(BEARER_PATTERN, "Bearer [TOKEN]").replace(CARD_PATTERN, "[CARD]").replace(IPV4_PATTERN, "[IP]");
123
+ }
124
+ function scrubObject(obj) {
125
+ const result = {};
126
+ for (const [key, value] of Object.entries(obj)) {
127
+ if (typeof value === "string") {
128
+ result[key] = scrubPII(value);
129
+ } else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
130
+ result[key] = scrubObject(value);
131
+ } else if (Array.isArray(value)) {
132
+ result[key] = value.map(
133
+ (item) => typeof item === "string" ? scrubPII(item) : item !== null && typeof item === "object" ? scrubObject(item) : item
134
+ );
135
+ } else {
136
+ result[key] = value;
137
+ }
138
+ }
139
+ return result;
140
+ }
141
+
142
+ // src/stack-parser.ts
143
+ var CHROME_PATTERN = /^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/;
144
+ var FIREFOX_PATTERN = /^(.+?)@(.+?):(\d+):(\d+)$/;
145
+ var NON_APP_PATTERNS = [
146
+ /node_modules/,
147
+ /^https?:\/\/cdn\./,
148
+ /^https?:\/\/unpkg\.com/,
149
+ /^https?:\/\/cdnjs\./,
150
+ /^chrome-extension:\/\//,
151
+ /^moz-extension:\/\//,
152
+ /^webpack\/runtime/,
153
+ /^\[native code\]/
154
+ ];
155
+ function isInApp(filename) {
156
+ return !NON_APP_PATTERNS.some((p) => p.test(filename));
157
+ }
158
+ function parseStackTrace(rawStack) {
159
+ const lines = rawStack.split("\n");
160
+ const frames = [];
161
+ for (const line of lines) {
162
+ const trimmed = line.trim();
163
+ if (!trimmed) continue;
164
+ let match = CHROME_PATTERN.exec(trimmed);
165
+ if (match) {
166
+ frames.push({
167
+ function: match[1] || "<anonymous>",
168
+ filename: match[2],
169
+ lineno: parseInt(match[3], 10),
170
+ colno: parseInt(match[4], 10),
171
+ inApp: isInApp(match[2])
172
+ });
173
+ continue;
174
+ }
175
+ match = FIREFOX_PATTERN.exec(trimmed);
176
+ if (match) {
177
+ frames.push({
178
+ function: match[1] || "<anonymous>",
179
+ filename: match[2],
180
+ lineno: parseInt(match[3], 10),
181
+ colno: parseInt(match[4], 10),
182
+ inApp: isInApp(match[2])
183
+ });
184
+ }
185
+ }
186
+ return frames;
187
+ }
188
+
189
+ // src/device-info.ts
190
+ var isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
191
+ function collectDeviceInfo() {
192
+ if (!isBrowser) {
193
+ return {
194
+ userAgent: "node",
195
+ platform: typeof process !== "undefined" ? process.platform : "unknown",
196
+ vendor: "",
197
+ deviceMemory: null,
198
+ hardwareConcurrency: null,
199
+ viewport: { width: 0, height: 0 },
200
+ devicePixelRatio: 1,
201
+ touchSupport: false,
202
+ colorScheme: "light",
203
+ reducedMotion: false,
204
+ language: "en",
205
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
206
+ };
207
+ }
208
+ const nav = navigator;
209
+ return {
210
+ userAgent: nav.userAgent,
211
+ platform: nav.platform,
212
+ vendor: nav.vendor || "",
213
+ deviceMemory: nav.deviceMemory ?? null,
214
+ hardwareConcurrency: nav.hardwareConcurrency ?? null,
215
+ viewport: {
216
+ width: window.innerWidth,
217
+ height: window.innerHeight
218
+ },
219
+ devicePixelRatio: window.devicePixelRatio || 1,
220
+ touchSupport: "ontouchstart" in window || nav.maxTouchPoints > 0,
221
+ colorScheme: window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light",
222
+ reducedMotion: window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false,
223
+ language: nav.language || "en",
224
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
225
+ };
226
+ }
227
+
228
+ // src/uuid.ts
229
+ function generateId() {
230
+ const hex = "0123456789abcdef";
231
+ const segments = [8, 4, 4, 4, 12];
232
+ const parts = [];
233
+ for (const len of segments) {
234
+ let segment = "";
235
+ for (let i = 0; i < len; i++) {
236
+ segment += hex[Math.floor(Math.random() * 16)];
237
+ }
238
+ parts.push(segment);
239
+ }
240
+ const p2 = "4" + parts[2].slice(1);
241
+ const variantChar = hex[8 + Math.floor(Math.random() * 4)];
242
+ const p3 = variantChar + parts[3].slice(1);
243
+ return `${parts[0]}-${parts[1]}-${p2}-${p3}-${parts[4]}`;
244
+ }
245
+
246
+ export { RingBuffer, collectDeviceInfo, createEventBus, createRateLimiter, generateFingerprint, generateId, parseStackTrace, scrubObject, scrubPII };
247
+ //# sourceMappingURL=index.mjs.map
248
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ring-buffer.ts","../src/event-bus.ts","../src/fingerprint.ts","../src/rate-limiter.ts","../src/pii-scrubber.ts","../src/stack-parser.ts","../src/device-info.ts","../src/uuid.ts"],"names":[],"mappings":";AAAO,IAAM,aAAN,MAAoB;AAAA,EAKzB,YAA6B,SAAA,EAAmB;AAAnB,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AAH7B,IAAA,IAAA,CAAQ,IAAA,GAAO,CAAA;AACf,IAAA,IAAA,CAAQ,KAAA,GAAQ,CAAA;AAGd,IAAA,IAAA,CAAK,MAAA,GAAS,IAAI,KAAA,CAAM,SAAS,CAAA;AAAA,EACnC;AAAA,EAEA,KAAK,IAAA,EAAe;AAClB,IAAA,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,GAAI,IAAA;AACzB,IAAA,IAAA,CAAK,IAAA,GAAA,CAAQ,IAAA,CAAK,IAAA,GAAO,CAAA,IAAK,IAAA,CAAK,SAAA;AACnC,IAAA,IAAA,CAAK,QAAQ,IAAA,CAAK,GAAA,CAAI,KAAK,KAAA,GAAQ,CAAA,EAAG,KAAK,SAAS,CAAA;AAAA,EACtD;AAAA,EAEA,KAAA,GAAa;AACX,IAAA,MAAM,SAAc,EAAC;AACrB,IAAA,MAAM,QAAQ,IAAA,CAAK,KAAA,GAAQ,IAAA,CAAK,SAAA,GAAY,IAAI,IAAA,CAAK,IAAA;AACrD,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,OAAO,CAAA,EAAA,EAAK;AACnC,MAAA,MAAM,OAAO,IAAA,CAAK,MAAA,CAAA,CAAQ,KAAA,GAAQ,CAAA,IAAK,KAAK,SAAS,CAAA;AACrD,MAAA,IAAI,SAAS,MAAA,EAAW;AACtB,QAAA,MAAA,CAAO,KAAK,IAAI,CAAA;AAAA,MAClB;AAAA,IACF;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA,EAEA,IAAA,GAAsB;AACpB,IAAA,IAAI,IAAA,CAAK,KAAA,KAAU,CAAA,EAAG,OAAO,MAAA;AAC7B,IAAA,MAAM,OAAO,IAAA,CAAK,IAAA,GAAO,CAAA,GAAI,IAAA,CAAK,aAAa,IAAA,CAAK,SAAA;AACpD,IAAA,OAAO,IAAA,CAAK,OAAO,GAAG,CAAA;AAAA,EACxB;AAAA,EAEA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,IAAI,KAAA,CAAM,IAAA,CAAK,SAAS,CAAA;AACtC,IAAA,IAAA,CAAK,IAAA,GAAO,CAAA;AACZ,IAAA,IAAA,CAAK,KAAA,GAAQ,CAAA;AAAA,EACf;AAAA,EAEA,IAAI,IAAA,GAAe;AACjB,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AAAA,EAEA,IAAI,QAAA,GAAmB;AACrB,IAAA,OAAO,IAAA,CAAK,SAAA;AAAA,EACd;AACF;;;AC1CO,SAAS,cAAA,GAA2B;AACzC,EAAA,MAAM,QAAA,uBAAe,GAAA,EAAmC;AAExD,EAAA,OAAO;AAAA,IACL,EAAA,CACE,OACA,OAAA,EACM;AACN,MAAA,IAAI,CAAC,QAAA,CAAS,GAAA,CAAI,KAAe,CAAA,EAAG;AAClC,QAAA,QAAA,CAAS,GAAA,CAAI,KAAA,kBAAiB,IAAI,GAAA,EAAK,CAAA;AAAA,MACzC;AACA,MAAA,QAAA,CAAS,GAAA,CAAI,KAAe,CAAA,CAAG,GAAA,CAAI,OAA2B,CAAA;AAAA,IAChE,CAAA;AAAA,IAEA,GAAA,CACE,OACA,OAAA,EACM;AACN,MAAA,MAAM,GAAA,GAAM,QAAA,CAAS,GAAA,CAAI,KAAe,CAAA;AACxC,MAAA,IAAI,GAAA,EAAK;AACP,QAAA,GAAA,CAAI,OAAO,OAA2B,CAAA;AAAA,MACxC;AAAA,IACF,CAAA;AAAA,IAEA,IAAA,CACE,OACA,IAAA,EACM;AACN,MAAA,MAAM,GAAA,GAAM,QAAA,CAAS,GAAA,CAAI,KAAe,CAAA;AACxC,MAAA,IAAI,GAAA,EAAK;AACP,QAAA,KAAA,MAAW,WAAW,GAAA,EAAK;AACzB,UAAA,IAAI;AACF,YAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,UACd,SAAS,CAAA,EAAG;AAAA,UAEZ;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,GACF;AACF;;;AC1CA,SAAS,SAAS,GAAA,EAAqB;AACrC,EAAA,IAAI,IAAA,GAAO,IAAA;AACX,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,CAAI,QAAQ,CAAA,EAAA,EAAK;AACnC,IAAA,IAAA,GAAA,CAAS,QAAQ,CAAA,IAAK,IAAA,GAAO,GAAA,CAAI,UAAA,CAAW,CAAC,CAAA,GAAK,CAAA;AAAA,EACpD;AACA,EAAA,OAAO,IAAA,KAAS,CAAA;AAClB;AAEO,SAAS,mBAAA,CACd,SAAA,EACA,YAAA,EACA,WAAA,EACQ;AACR,EAAA,MAAM,SAAA,GAAY,WAAA,CAAY,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA;AACxC,EAAA,MAAM,QAAA,GAAW,SAAA,CACd,GAAA,CAAI,CAAC,MAAM,CAAA,EAAG,CAAA,CAAE,QAAQ,CAAA,CAAA,EAAI,CAAA,CAAE,QAAQ,CAAA,CAAE,CAAA,CACxC,KAAK,GAAG,CAAA;AACX,EAAA,MAAM,QAAQ,CAAA,EAAG,SAAS,CAAA,CAAA,EAAI,YAAY,IAAI,QAAQ,CAAA,CAAA;AACtD,EAAA,MAAM,IAAA,GAAO,SAAS,KAAK,CAAA;AAC3B,EAAA,OAAO,KAAK,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAA;AAC1C;;;ACjBO,SAAS,kBAAkB,YAAA,EAAmC;AACnE,EAAA,IAAI,MAAA,GAAS,YAAA;AACb,EAAA,IAAI,UAAA,GAAa,KAAK,GAAA,EAAI;AAE1B,EAAA,SAAS,MAAA,GAAe;AACtB,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,MAAM,UAAU,GAAA,GAAM,UAAA;AACtB,IAAA,MAAM,SAAA,GAAa,UAAU,GAAA,GAAS,YAAA;AACtC,IAAA,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,YAAA,EAAc,MAAA,GAAS,SAAS,CAAA;AAClD,IAAA,UAAA,GAAa,GAAA;AAAA,EACf;AAEA,EAAA,OAAO;AAAA,IACL,UAAA,GAAsB;AACpB,MAAA,MAAA,EAAO;AACP,MAAA,IAAI,UAAU,CAAA,EAAG;AACf,QAAA,MAAA,IAAU,CAAA;AACV,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,OAAO,KAAA;AAAA,IACT,CAAA;AAAA,IAEA,KAAA,GAAc;AACZ,MAAA,MAAA,GAAS,YAAA;AACT,MAAA,UAAA,GAAa,KAAK,GAAA,EAAI;AAAA,IACxB;AAAA,GACF;AACF;;;AChCA,IAAM,aAAA,GAAgB,iDAAA;AACtB,IAAM,YAAA,GAAe,yCAAA;AACrB,IAAM,cAAA,GAAiB,gCAAA;AACvB,IAAM,YAAA,GAAe,6CAAA;AAEd,SAAS,SAAS,IAAA,EAAsB;AAC7C,EAAA,OAAO,IAAA,CACJ,OAAA,CAAQ,aAAA,EAAe,SAAS,EAChC,OAAA,CAAQ,cAAA,EAAgB,gBAAgB,CAAA,CACxC,QAAQ,YAAA,EAAc,QAAQ,CAAA,CAC9B,OAAA,CAAQ,cAAc,MAAM,CAAA;AACjC;AAEO,SAAS,YACd,GAAA,EACyB;AACzB,EAAA,MAAM,SAAkC,EAAC;AACzC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA,EAAG;AAC9C,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,QAAA,CAAS,KAAK,CAAA;AAAA,IAC9B,CAAA,MAAA,IAAW,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,YAAY,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC/E,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,WAAA,CAAY,KAAgC,CAAA;AAAA,IAC5D,CAAA,MAAA,IAAW,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC/B,MAAA,MAAA,CAAO,GAAG,IAAI,KAAA,CAAM,GAAA;AAAA,QAAI,CAAC,IAAA,KACvB,OAAO,IAAA,KAAS,WACZ,QAAA,CAAS,IAAI,CAAA,GACb,IAAA,KAAS,QAAQ,OAAO,IAAA,KAAS,QAAA,GAC/B,WAAA,CAAY,IAA+B,CAAA,GAC3C;AAAA,OACR;AAAA,IACF,CAAA,MAAO;AACL,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,KAAA;AAAA,IAChB;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;;;AChCA,IAAM,cAAA,GAAiB,+CAAA;AAGvB,IAAM,eAAA,GAAkB,2BAAA;AAExB,IAAM,gBAAA,GAAmB;AAAA,EACvB,cAAA;AAAA,EACA,mBAAA;AAAA,EACA,wBAAA;AAAA,EACA,qBAAA;AAAA,EACA,wBAAA;AAAA,EACA,qBAAA;AAAA,EACA,mBAAA;AAAA,EACA;AACF,CAAA;AAEA,SAAS,QAAQ,QAAA,EAA2B;AAC1C,EAAA,OAAO,CAAC,iBAAiB,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,IAAA,CAAK,QAAQ,CAAC,CAAA;AACvD;AAEO,SAAS,gBAAgB,QAAA,EAAgC;AAC9D,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,KAAA,CAAM,IAAI,CAAA;AACjC,EAAA,MAAM,SAAuB,EAAC;AAE9B,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,MAAM,OAAA,GAAU,KAAK,IAAA,EAAK;AAC1B,IAAA,IAAI,CAAC,OAAA,EAAS;AAEd,IAAA,IAAI,KAAA,GAAQ,cAAA,CAAe,IAAA,CAAK,OAAO,CAAA;AACvC,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,QAAA,EAAU,KAAA,CAAM,CAAC,CAAA,IAAK,aAAA;AAAA,QACtB,QAAA,EAAU,MAAM,CAAC,CAAA;AAAA,QACjB,MAAA,EAAQ,QAAA,CAAS,KAAA,CAAM,CAAC,GAAG,EAAE,CAAA;AAAA,QAC7B,KAAA,EAAO,QAAA,CAAS,KAAA,CAAM,CAAC,GAAG,EAAE,CAAA;AAAA,QAC5B,KAAA,EAAO,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAC;AAAA,OACxB,CAAA;AACD,MAAA;AAAA,IACF;AAEA,IAAA,KAAA,GAAQ,eAAA,CAAgB,KAAK,OAAO,CAAA;AACpC,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,QAAA,EAAU,KAAA,CAAM,CAAC,CAAA,IAAK,aAAA;AAAA,QACtB,QAAA,EAAU,MAAM,CAAC,CAAA;AAAA,QACjB,MAAA,EAAQ,QAAA,CAAS,KAAA,CAAM,CAAC,GAAG,EAAE,CAAA;AAAA,QAC7B,KAAA,EAAO,QAAA,CAAS,KAAA,CAAM,CAAC,GAAG,EAAE,CAAA;AAAA,QAC5B,KAAA,EAAO,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAC;AAAA,OACxB,CAAA;AAAA,IACH;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;;;ACtDA,IAAM,SAAA,GAAY,OAAO,MAAA,KAAW,WAAA,IAAe,OAAO,QAAA,KAAa,WAAA;AAEhE,SAAS,iBAAA,GAAgC;AAC9C,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,OAAO;AAAA,MACL,SAAA,EAAW,MAAA;AAAA,MACX,QAAA,EAAU,OAAO,OAAA,KAAY,WAAA,GAAc,QAAQ,QAAA,GAAW,SAAA;AAAA,MAC9D,MAAA,EAAQ,EAAA;AAAA,MACR,YAAA,EAAc,IAAA;AAAA,MACd,mBAAA,EAAqB,IAAA;AAAA,MACrB,QAAA,EAAU,EAAE,KAAA,EAAO,CAAA,EAAG,QAAQ,CAAA,EAAE;AAAA,MAChC,gBAAA,EAAkB,CAAA;AAAA,MAClB,YAAA,EAAc,KAAA;AAAA,MACd,WAAA,EAAa,OAAA;AAAA,MACb,aAAA,EAAe,KAAA;AAAA,MACf,QAAA,EAAU,IAAA;AAAA,MACV,QAAA,EAAU,IAAA,CAAK,cAAA,EAAe,CAAE,iBAAgB,CAAE;AAAA,KACpD;AAAA,EACF;AAEA,EAAA,MAAM,GAAA,GAAM,SAAA;AAIZ,EAAA,OAAO;AAAA,IACL,WAAW,GAAA,CAAI,SAAA;AAAA,IACf,UAAU,GAAA,CAAI,QAAA;AAAA,IACd,MAAA,EAAQ,IAAI,MAAA,IAAU,EAAA;AAAA,IACtB,YAAA,EAAc,IAAI,YAAA,IAAgB,IAAA;AAAA,IAClC,mBAAA,EAAqB,IAAI,mBAAA,IAAuB,IAAA;AAAA,IAChD,QAAA,EAAU;AAAA,MACR,OAAO,MAAA,CAAO,UAAA;AAAA,MACd,QAAQ,MAAA,CAAO;AAAA,KACjB;AAAA,IACA,gBAAA,EAAkB,OAAO,gBAAA,IAAoB,CAAA;AAAA,IAC7C,YAAA,EAAc,cAAA,IAAkB,MAAA,IAAU,GAAA,CAAI,cAAA,GAAiB,CAAA;AAAA,IAC/D,aAAa,MAAA,CAAO,UAAA,GAAa,8BAA8B,CAAA,CAAE,UAC7D,MAAA,GACA,OAAA;AAAA,IACJ,aAAA,EAAe,MAAA,CAAO,UAAA,GAAa,kCAAkC,EAAE,OAAA,IAAW,KAAA;AAAA,IAClF,QAAA,EAAU,IAAI,QAAA,IAAY,IAAA;AAAA,IAC1B,QAAA,EAAU,IAAA,CAAK,cAAA,EAAe,CAAE,iBAAgB,CAAE;AAAA,GACpD;AACF;;;AC7CO,SAAS,UAAA,GAAqB;AACnC,EAAA,MAAM,GAAA,GAAM,kBAAA;AACZ,EAAA,MAAM,WAAW,CAAC,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,GAAG,EAAE,CAAA;AAChC,EAAA,MAAM,QAAkB,EAAC;AAEzB,EAAA,KAAA,MAAW,OAAO,QAAA,EAAU;AAC1B,IAAA,IAAI,OAAA,GAAU,EAAA;AACd,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,EAAK,CAAA,EAAA,EAAK;AAC5B,MAAA,OAAA,IAAW,IAAI,IAAA,CAAK,KAAA,CAAM,KAAK,MAAA,EAAO,GAAI,EAAE,CAAC,CAAA;AAAA,IAC/C;AACA,IAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAAA,EACpB;AAGA,EAAA,MAAM,KAAK,GAAA,GAAM,KAAA,CAAM,CAAC,CAAA,CAAE,MAAM,CAAC,CAAA;AACjC,EAAA,MAAM,WAAA,GAAc,IAAI,CAAA,GAAI,IAAA,CAAK,MAAM,IAAA,CAAK,MAAA,EAAO,GAAI,CAAC,CAAC,CAAA;AACzD,EAAA,MAAM,KAAK,WAAA,GAAc,KAAA,CAAM,CAAC,CAAA,CAAE,MAAM,CAAC,CAAA;AAEzC,EAAA,OAAO,CAAA,EAAG,KAAA,CAAM,CAAC,CAAC,IAAI,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA,EAAI,EAAE,CAAA,CAAA,EAAI,EAAE,CAAA,CAAA,EAAI,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA;AACxD","file":"index.mjs","sourcesContent":["export class RingBuffer<T> {\n private buffer: (T | undefined)[];\n private head = 0;\n private _size = 0;\n\n constructor(private readonly _capacity: number) {\n this.buffer = new Array(_capacity);\n }\n\n push(item: T): void {\n this.buffer[this.head] = item;\n this.head = (this.head + 1) % this._capacity;\n this._size = Math.min(this._size + 1, this._capacity);\n }\n\n drain(): T[] {\n const result: T[] = [];\n const start = this._size < this._capacity ? 0 : this.head;\n for (let i = 0; i < this._size; i++) {\n const item = this.buffer[(start + i) % this._capacity];\n if (item !== undefined) {\n result.push(item);\n }\n }\n return result;\n }\n\n peek(): T | undefined {\n if (this._size === 0) return undefined;\n const idx = (this.head - 1 + this._capacity) % this._capacity;\n return this.buffer[idx];\n }\n\n clear(): void {\n this.buffer = new Array(this._capacity);\n this.head = 0;\n this._size = 0;\n }\n\n get size(): number {\n return this._size;\n }\n\n get capacity(): number {\n return this._capacity;\n }\n}\n","import type { EventBus, EventBusEventMap } from '@crashsense/types';\n\ntype Handler<T> = (data: T) => void;\n\nexport function createEventBus(): EventBus {\n const handlers = new Map<string, Set<Handler<unknown>>>();\n\n return {\n on<K extends keyof EventBusEventMap>(\n event: K,\n handler: (data: EventBusEventMap[K]) => void,\n ): void {\n if (!handlers.has(event as string)) {\n handlers.set(event as string, new Set());\n }\n handlers.get(event as string)!.add(handler as Handler<unknown>);\n },\n\n off<K extends keyof EventBusEventMap>(\n event: K,\n handler: (data: EventBusEventMap[K]) => void,\n ): void {\n const set = handlers.get(event as string);\n if (set) {\n set.delete(handler as Handler<unknown>);\n }\n },\n\n emit<K extends keyof EventBusEventMap>(\n event: K,\n data: EventBusEventMap[K],\n ): void {\n const set = handlers.get(event as string);\n if (set) {\n for (const handler of set) {\n try {\n handler(data);\n } catch (_) {\n // SDK errors must never crash the host application\n }\n }\n }\n },\n };\n}\n","import type { StackFrame } from '@crashsense/types';\n\nfunction djb2Hash(str: string): number {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0;\n }\n return hash >>> 0;\n}\n\nexport function generateFingerprint(\n errorType: string,\n errorMessage: string,\n stackFrames: StackFrame[],\n): string {\n const topFrames = stackFrames.slice(0, 3);\n const frameStr = topFrames\n .map((f) => `${f.filename}:${f.function}`)\n .join('|');\n const input = `${errorType}:${errorMessage}:${frameStr}`;\n const hash = djb2Hash(input);\n return hash.toString(16).padStart(8, '0');\n}\n","export interface RateLimiter {\n tryAcquire(): boolean;\n reset(): void;\n}\n\nexport function createRateLimiter(maxPerMinute: number): RateLimiter {\n let tokens = maxPerMinute;\n let lastRefill = Date.now();\n\n function refill(): void {\n const now = Date.now();\n const elapsed = now - lastRefill;\n const newTokens = (elapsed / 60000) * maxPerMinute;\n tokens = Math.min(maxPerMinute, tokens + newTokens);\n lastRefill = now;\n }\n\n return {\n tryAcquire(): boolean {\n refill();\n if (tokens >= 1) {\n tokens -= 1;\n return true;\n }\n return false;\n },\n\n reset(): void {\n tokens = maxPerMinute;\n lastRefill = Date.now();\n },\n };\n}\n","const EMAIL_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g;\nconst IPV4_PATTERN = /\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b/g;\nconst BEARER_PATTERN = /Bearer\\s+[A-Za-z0-9\\-._~+\\/]+/g;\nconst CARD_PATTERN = /\\b\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b/g;\n\nexport function scrubPII(text: string): string {\n return text\n .replace(EMAIL_PATTERN, '[EMAIL]')\n .replace(BEARER_PATTERN, 'Bearer [TOKEN]')\n .replace(CARD_PATTERN, '[CARD]')\n .replace(IPV4_PATTERN, '[IP]');\n}\n\nexport function scrubObject(\n obj: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (typeof value === 'string') {\n result[key] = scrubPII(value);\n } else if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n result[key] = scrubObject(value as Record<string, unknown>);\n } else if (Array.isArray(value)) {\n result[key] = value.map((item) =>\n typeof item === 'string'\n ? scrubPII(item)\n : item !== null && typeof item === 'object'\n ? scrubObject(item as Record<string, unknown>)\n : item,\n );\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n","import type { StackFrame } from '@crashsense/types';\n\n// Chrome/V8: \" at functionName (filename:line:col)\" or \" at filename:line:col\"\nconst CHROME_PATTERN = /^\\s*at\\s+(?:(.+?)\\s+\\()?(.+?):(\\d+):(\\d+)\\)?$/;\n\n// Firefox: \"functionName@filename:line:col\"\nconst FIREFOX_PATTERN = /^(.+?)@(.+?):(\\d+):(\\d+)$/;\n\nconst NON_APP_PATTERNS = [\n /node_modules/,\n /^https?:\\/\\/cdn\\./,\n /^https?:\\/\\/unpkg\\.com/,\n /^https?:\\/\\/cdnjs\\./,\n /^chrome-extension:\\/\\//,\n /^moz-extension:\\/\\//,\n /^webpack\\/runtime/,\n /^\\[native code\\]/,\n];\n\nfunction isInApp(filename: string): boolean {\n return !NON_APP_PATTERNS.some((p) => p.test(filename));\n}\n\nexport function parseStackTrace(rawStack: string): StackFrame[] {\n const lines = rawStack.split('\\n');\n const frames: StackFrame[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n let match = CHROME_PATTERN.exec(trimmed);\n if (match) {\n frames.push({\n function: match[1] || '<anonymous>',\n filename: match[2],\n lineno: parseInt(match[3], 10),\n colno: parseInt(match[4], 10),\n inApp: isInApp(match[2]),\n });\n continue;\n }\n\n match = FIREFOX_PATTERN.exec(trimmed);\n if (match) {\n frames.push({\n function: match[1] || '<anonymous>',\n filename: match[2],\n lineno: parseInt(match[3], 10),\n colno: parseInt(match[4], 10),\n inApp: isInApp(match[2]),\n });\n }\n }\n\n return frames;\n}\n","import type { DeviceInfo } from '@crashsense/types';\n\nconst isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';\n\nexport function collectDeviceInfo(): DeviceInfo {\n if (!isBrowser) {\n return {\n userAgent: 'node',\n platform: typeof process !== 'undefined' ? process.platform : 'unknown',\n vendor: '',\n deviceMemory: null,\n hardwareConcurrency: null,\n viewport: { width: 0, height: 0 },\n devicePixelRatio: 1,\n touchSupport: false,\n colorScheme: 'light',\n reducedMotion: false,\n language: 'en',\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n };\n }\n\n const nav = navigator as Navigator & {\n deviceMemory?: number;\n };\n\n return {\n userAgent: nav.userAgent,\n platform: nav.platform,\n vendor: nav.vendor || '',\n deviceMemory: nav.deviceMemory ?? null,\n hardwareConcurrency: nav.hardwareConcurrency ?? null,\n viewport: {\n width: window.innerWidth,\n height: window.innerHeight,\n },\n devicePixelRatio: window.devicePixelRatio || 1,\n touchSupport: 'ontouchstart' in window || nav.maxTouchPoints > 0,\n colorScheme: window.matchMedia?.('(prefers-color-scheme: dark)').matches\n ? 'dark'\n : 'light',\n reducedMotion: window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false,\n language: nav.language || 'en',\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n };\n}\n","export function generateId(): string {\n const hex = '0123456789abcdef';\n const segments = [8, 4, 4, 4, 12];\n const parts: string[] = [];\n\n for (const len of segments) {\n let segment = '';\n for (let i = 0; i < len; i++) {\n segment += hex[Math.floor(Math.random() * 16)];\n }\n parts.push(segment);\n }\n\n // Set version (4) and variant bits\n const p2 = '4' + parts[2].slice(1);\n const variantChar = hex[8 + Math.floor(Math.random() * 4)];\n const p3 = variantChar + parts[3].slice(1);\n\n return `${parts[0]}-${parts[1]}-${p2}-${p3}-${parts[4]}`;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@crashsense/utils",
3
+ "version": "0.1.0",
4
+ "description": "Internal utilities for CrashSense — ring buffer, fingerprint, rate limiter, event bus",
5
+ "author": "hoainho <nhoxtvt@gmail.com>",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/hoainho/crashsense.git",
10
+ "directory": "packages/utils"
11
+ },
12
+ "homepage": "https://github.com/hoainho/crashsense/tree/main/packages/utils",
13
+ "bugs": {
14
+ "url": "https://github.com/hoainho/crashsense/issues"
15
+ },
16
+ "keywords": [
17
+ "crashsense",
18
+ "utilities",
19
+ "ring-buffer",
20
+ "event-bus",
21
+ "fingerprint"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "main": "./dist/index.js",
27
+ "module": "./dist/index.mjs",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.mjs",
33
+ "require": "./dist/index.js"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "scripts": {
40
+ "build": "tsup",
41
+ "dev": "tsup --watch",
42
+ "test": "vitest run"
43
+ },
44
+ "dependencies": {
45
+ "@crashsense/types": "*"
46
+ },
47
+ "sideEffects": false
48
+ }