@mushi-mushi/core 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.
package/dist/index.js ADDED
@@ -0,0 +1,668 @@
1
+ // src/api-client.ts
2
+ var DEFAULT_API_ENDPOINT = "https://api.mushimushi.dev";
3
+ var DEFAULT_TIMEOUT = 1e4;
4
+ var DEFAULT_MAX_RETRIES = 2;
5
+ function createApiClient(options) {
6
+ const {
7
+ projectId,
8
+ apiKey,
9
+ apiEndpoint = DEFAULT_API_ENDPOINT,
10
+ timeout = DEFAULT_TIMEOUT,
11
+ maxRetries = DEFAULT_MAX_RETRIES
12
+ } = options;
13
+ const baseUrl = apiEndpoint.replace(/\/$/, "");
14
+ async function request(method, path, body, retries = maxRetries) {
15
+ const url = `${baseUrl}${path}`;
16
+ const controller = new AbortController();
17
+ const timer = setTimeout(() => controller.abort(), timeout);
18
+ try {
19
+ const response = await fetch(url, {
20
+ method,
21
+ headers: {
22
+ "Content-Type": "application/json",
23
+ "X-Mushi-Api-Key": apiKey,
24
+ "X-Mushi-Project": projectId
25
+ },
26
+ body: body ? JSON.stringify(body) : void 0,
27
+ signal: controller.signal
28
+ });
29
+ clearTimeout(timer);
30
+ if (!response.ok) {
31
+ const errorBody = await response.json().catch(() => ({}));
32
+ if (response.status >= 500 && retries > 0) {
33
+ await sleep(getBackoffDelay(maxRetries - retries));
34
+ return request(method, path, body, retries - 1);
35
+ }
36
+ return {
37
+ ok: false,
38
+ error: {
39
+ code: `HTTP_${response.status}`,
40
+ message: errorBody.message || `HTTP ${response.status} error`
41
+ }
42
+ };
43
+ }
44
+ const data = await response.json();
45
+ return { ok: true, data };
46
+ } catch (error) {
47
+ clearTimeout(timer);
48
+ if (retries > 0 && isRetryable(error)) {
49
+ await sleep(getBackoffDelay(maxRetries - retries));
50
+ return request(method, path, body, retries - 1);
51
+ }
52
+ return {
53
+ ok: false,
54
+ error: {
55
+ code: "NETWORK_ERROR",
56
+ message: error instanceof Error ? error.message : "Unknown network error"
57
+ }
58
+ };
59
+ }
60
+ }
61
+ return {
62
+ async submitReport(report) {
63
+ return request("POST", "/v1/reports", report);
64
+ },
65
+ async getReportStatus(reportId) {
66
+ return request("GET", `/v1/reports/${reportId}/status`);
67
+ }
68
+ };
69
+ }
70
+ function sleep(ms) {
71
+ return new Promise((resolve) => setTimeout(resolve, ms));
72
+ }
73
+ function getBackoffDelay(attempt) {
74
+ return Math.min(1e3 * 2 ** attempt + Math.random() * 500, 1e4);
75
+ }
76
+ function isRetryable(error) {
77
+ if (error instanceof DOMException && error.name === "AbortError") return true;
78
+ if (error instanceof TypeError) return true;
79
+ return false;
80
+ }
81
+
82
+ // src/pre-filter.ts
83
+ var DEFAULT_MIN_LENGTH = 10;
84
+ var DEFAULT_MAX_LENGTH = 2e3;
85
+ var SPAM_PATTERNS = [
86
+ /^(.)\1{10,}$/,
87
+ // repeated single character
88
+ /^[A-Z\s!?]{20,}$/,
89
+ // all caps shouting
90
+ /^[\d\s]+$/,
91
+ // numbers only
92
+ /^[^a-zA-Z\u00C0-\u024F\u4E00-\u9FFF\u3040-\u309F\u30A0-\u30FF]{10,}$/,
93
+ // no real letters
94
+ /\b(test|asdf|qwerty|lorem ipsum)\b/i
95
+ // common test strings
96
+ ];
97
+ var GIBBERISH_PATTERN = /^[bcdfghjklmnpqrstvwxz]{6,}/i;
98
+ function createPreFilter(config = {}) {
99
+ const {
100
+ enabled = true,
101
+ blockObviousSpam = true,
102
+ minDescriptionLength = DEFAULT_MIN_LENGTH,
103
+ maxDescriptionLength = DEFAULT_MAX_LENGTH
104
+ } = config;
105
+ function check(description) {
106
+ if (!enabled) {
107
+ return { passed: true };
108
+ }
109
+ const trimmed = description.trim();
110
+ if (trimmed.length < minDescriptionLength) {
111
+ return { passed: false, reason: `Too short (min ${minDescriptionLength} characters)` };
112
+ }
113
+ if (trimmed.length > maxDescriptionLength) {
114
+ return { passed: false, reason: `Too long (max ${maxDescriptionLength} characters)` };
115
+ }
116
+ if (blockObviousSpam) {
117
+ for (const pattern of SPAM_PATTERNS) {
118
+ if (pattern.test(trimmed)) {
119
+ return { passed: false, reason: "Detected as spam" };
120
+ }
121
+ }
122
+ if (GIBBERISH_PATTERN.test(trimmed)) {
123
+ return { passed: false, reason: "Detected as gibberish" };
124
+ }
125
+ const words = trimmed.split(/\s+/).filter((w) => w.length > 1);
126
+ if (words.length < 2) {
127
+ return { passed: false, reason: "Description needs at least 2 words" };
128
+ }
129
+ }
130
+ return { passed: true };
131
+ }
132
+ function truncate(description) {
133
+ const trimmed = description.trim();
134
+ if (trimmed.length <= maxDescriptionLength) return trimmed;
135
+ return trimmed.slice(0, maxDescriptionLength) + "...";
136
+ }
137
+ return { check, truncate };
138
+ }
139
+
140
+ // src/logger.ts
141
+ var LEVEL_VALUE = {
142
+ debug: 10,
143
+ info: 20,
144
+ warn: 30,
145
+ error: 40,
146
+ fatal: 50,
147
+ silent: 99
148
+ };
149
+ var LEVEL_LABEL = {
150
+ debug: "DBG",
151
+ info: "INF",
152
+ warn: "WRN",
153
+ error: "ERR",
154
+ fatal: "FTL"
155
+ };
156
+ var ANSI = {
157
+ reset: "\x1B[0m",
158
+ dim: "\x1B[2m",
159
+ bold: "\x1B[1m",
160
+ cyan: "\x1B[36m",
161
+ green: "\x1B[32m",
162
+ yellow: "\x1B[33m",
163
+ red: "\x1B[31m",
164
+ white: "\x1B[37m",
165
+ bgRed: "\x1B[41m"
166
+ };
167
+ var LEVEL_COLOR = {
168
+ debug: ANSI.dim,
169
+ info: ANSI.green,
170
+ warn: ANSI.yellow,
171
+ error: ANSI.red,
172
+ fatal: `${ANSI.bgRed}${ANSI.white}${ANSI.bold}`
173
+ };
174
+ function detectFormat() {
175
+ try {
176
+ if (typeof globalThis.Deno !== "undefined") return "json";
177
+ } catch {
178
+ }
179
+ const proc = typeof globalThis.process !== "undefined" ? globalThis.process : void 0;
180
+ if (proc?.env) {
181
+ if (proc.env.NODE_ENV === "production") return "json";
182
+ if (proc.env.LOG_FORMAT === "json") return "json";
183
+ if (proc.env.LOG_FORMAT === "pretty") return "pretty";
184
+ if (proc.stdout?.isTTY) return "pretty";
185
+ }
186
+ if (typeof globalThis.window !== "undefined") return "pretty";
187
+ return "json";
188
+ }
189
+ function flattenMeta(meta) {
190
+ const parts = [];
191
+ for (const [k, v] of Object.entries(meta)) {
192
+ if (v === void 0 || v === null) continue;
193
+ if (typeof v === "object") {
194
+ parts.push(`${k}=${JSON.stringify(v)}`);
195
+ } else {
196
+ parts.push(`${k}=${String(v)}`);
197
+ }
198
+ }
199
+ return parts.join(" ");
200
+ }
201
+ function formatPretty(entry) {
202
+ const { ts, level, scope, msg, ...rest } = entry;
203
+ const time = ts.slice(11, 23);
204
+ const color = LEVEL_COLOR[level] ?? "";
205
+ const label = LEVEL_LABEL[level] ?? level.toUpperCase();
206
+ const metaStr = Object.keys(rest).length > 0 ? ` ${ANSI.dim}${flattenMeta(rest)}${ANSI.reset}` : "";
207
+ return `${ANSI.dim}${time}${ANSI.reset} ${color}${label}${ANSI.reset} ${ANSI.cyan}[${scope}]${ANSI.reset} ${msg}${metaStr}`;
208
+ }
209
+ function formatJson(entry) {
210
+ return JSON.stringify(entry);
211
+ }
212
+ function emit(level, formatted) {
213
+ switch (level) {
214
+ case "error":
215
+ case "fatal":
216
+ console.error(formatted);
217
+ break;
218
+ case "warn":
219
+ console.warn(formatted);
220
+ break;
221
+ default:
222
+ console.log(formatted);
223
+ }
224
+ }
225
+ function buildLogger(scope, minLevel, baseMeta, formatter) {
226
+ let currentLevel = minLevel;
227
+ function log(level, msg, meta) {
228
+ if (LEVEL_VALUE[level] < LEVEL_VALUE[currentLevel]) return;
229
+ const entry = {
230
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
231
+ level,
232
+ scope,
233
+ msg,
234
+ ...baseMeta,
235
+ ...meta
236
+ };
237
+ emit(level, formatter(entry));
238
+ }
239
+ return {
240
+ debug: (msg, meta) => log("debug", msg, meta),
241
+ info: (msg, meta) => log("info", msg, meta),
242
+ warn: (msg, meta) => log("warn", msg, meta),
243
+ error: (msg, meta) => log("error", msg, meta),
244
+ fatal: (msg, meta) => log("fatal", msg, meta),
245
+ child(childScope, childMeta) {
246
+ return buildLogger(
247
+ `${scope}:${childScope}`,
248
+ currentLevel,
249
+ { ...baseMeta, ...childMeta },
250
+ formatter
251
+ );
252
+ },
253
+ setLevel(level) {
254
+ currentLevel = level;
255
+ }
256
+ };
257
+ }
258
+ function createLogger(options) {
259
+ const {
260
+ scope,
261
+ level = "info",
262
+ meta = {},
263
+ format = "auto"
264
+ } = options;
265
+ const resolvedFormat = format === "auto" ? detectFormat() : format;
266
+ const formatter = resolvedFormat === "json" ? formatJson : formatPretty;
267
+ return buildLogger(scope, level, meta, formatter);
268
+ }
269
+ var noopLogger = {
270
+ debug: () => {
271
+ },
272
+ info: () => {
273
+ },
274
+ warn: () => {
275
+ },
276
+ error: () => {
277
+ },
278
+ fatal: () => {
279
+ },
280
+ child: () => noopLogger,
281
+ setLevel: () => {
282
+ }
283
+ };
284
+
285
+ // src/queue.ts
286
+ var queueLog = createLogger({ scope: "mushi:queue", level: "warn" });
287
+ var DB_NAME = "mushi-mushi";
288
+ var STORE_NAME = "offline-reports";
289
+ var DB_VERSION = 1;
290
+ var LS_KEY = "mushi_offline_queue";
291
+ var BATCH_SIZE = 10;
292
+ var MAX_BACKOFF_MS = 6e4;
293
+ function createOfflineQueue(config = {}) {
294
+ const { enabled = true, maxQueueSize = 50, syncOnReconnect = true } = config;
295
+ let syncCleanup = null;
296
+ let backendType = null;
297
+ function detectBackend() {
298
+ if (backendType) return backendType;
299
+ if (typeof indexedDB !== "undefined") {
300
+ backendType = "indexeddb";
301
+ } else if (typeof localStorage !== "undefined") {
302
+ backendType = "localstorage";
303
+ } else {
304
+ backendType = "none";
305
+ }
306
+ return backendType;
307
+ }
308
+ function openDb() {
309
+ return new Promise((resolve, reject) => {
310
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
311
+ request.onupgradeneeded = () => {
312
+ const db = request.result;
313
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
314
+ db.createObjectStore(STORE_NAME, { keyPath: "id" });
315
+ }
316
+ };
317
+ request.onsuccess = () => resolve(request.result);
318
+ request.onerror = () => {
319
+ backendType = "localstorage";
320
+ reject(request.error);
321
+ };
322
+ });
323
+ }
324
+ async function idbEnqueue(report) {
325
+ const db = await openDb();
326
+ return new Promise((resolve, reject) => {
327
+ const tx = db.transaction(STORE_NAME, "readwrite");
328
+ tx.objectStore(STORE_NAME).put({ ...report, queuedAt: (/* @__PURE__ */ new Date()).toISOString() });
329
+ tx.oncomplete = () => resolve();
330
+ tx.onerror = () => reject(tx.error);
331
+ });
332
+ }
333
+ async function idbGetAll() {
334
+ const db = await openDb();
335
+ return new Promise((resolve, reject) => {
336
+ const tx = db.transaction(STORE_NAME, "readonly");
337
+ const request = tx.objectStore(STORE_NAME).getAll();
338
+ request.onsuccess = () => resolve(request.result);
339
+ request.onerror = () => reject(request.error);
340
+ });
341
+ }
342
+ async function idbDelete(id) {
343
+ const db = await openDb();
344
+ return new Promise((resolve, reject) => {
345
+ const tx = db.transaction(STORE_NAME, "readwrite");
346
+ tx.objectStore(STORE_NAME).delete(id);
347
+ tx.oncomplete = () => resolve();
348
+ tx.onerror = () => reject(tx.error);
349
+ });
350
+ }
351
+ async function idbSize() {
352
+ const db = await openDb();
353
+ return new Promise((resolve, reject) => {
354
+ const tx = db.transaction(STORE_NAME, "readonly");
355
+ const request = tx.objectStore(STORE_NAME).count();
356
+ request.onsuccess = () => resolve(request.result);
357
+ request.onerror = () => reject(request.error);
358
+ });
359
+ }
360
+ async function idbClear() {
361
+ const db = await openDb();
362
+ return new Promise((resolve, reject) => {
363
+ const tx = db.transaction(STORE_NAME, "readwrite");
364
+ tx.objectStore(STORE_NAME).clear();
365
+ tx.oncomplete = () => resolve();
366
+ tx.onerror = () => reject(tx.error);
367
+ });
368
+ }
369
+ function lsRead() {
370
+ try {
371
+ const raw = localStorage.getItem(LS_KEY);
372
+ return raw ? JSON.parse(raw) : [];
373
+ } catch {
374
+ return [];
375
+ }
376
+ }
377
+ function lsWrite(reports) {
378
+ try {
379
+ localStorage.setItem(LS_KEY, JSON.stringify(reports));
380
+ } catch {
381
+ }
382
+ }
383
+ function lsEnqueue(report) {
384
+ const reports = lsRead();
385
+ reports.push({ ...report, queuedAt: (/* @__PURE__ */ new Date()).toISOString() });
386
+ lsWrite(reports);
387
+ }
388
+ function lsDelete(id) {
389
+ const reports = lsRead().filter((r) => r.id !== id);
390
+ lsWrite(reports);
391
+ }
392
+ async function enqueue(report) {
393
+ if (!enabled) return;
394
+ const currentSize = await size();
395
+ if (currentSize >= maxQueueSize) {
396
+ queueLog.warn("Offline queue full \u2014 dropping report", { maxQueueSize });
397
+ return;
398
+ }
399
+ const backend = detectBackend();
400
+ if (backend === "indexeddb") {
401
+ try {
402
+ await idbEnqueue(report);
403
+ return;
404
+ } catch {
405
+ backendType = "localstorage";
406
+ }
407
+ }
408
+ if (backend === "localstorage" || backendType === "localstorage") {
409
+ lsEnqueue(report);
410
+ return;
411
+ }
412
+ }
413
+ function getBackoffDelay2(attempt) {
414
+ return Math.min(1e3 * 2 ** attempt + Math.random() * 500, MAX_BACKOFF_MS);
415
+ }
416
+ function sleep2(ms) {
417
+ return new Promise((resolve) => setTimeout(resolve, ms));
418
+ }
419
+ async function flush(client) {
420
+ if (!enabled) return { sent: 0, failed: 0 };
421
+ let reports;
422
+ const backend = detectBackend();
423
+ if (backend === "indexeddb") {
424
+ try {
425
+ reports = await idbGetAll();
426
+ } catch {
427
+ reports = lsRead();
428
+ }
429
+ } else {
430
+ reports = lsRead();
431
+ }
432
+ const batch = reports.slice(0, BATCH_SIZE);
433
+ let sent = 0;
434
+ let failed = 0;
435
+ for (let i = 0; i < batch.length; i++) {
436
+ const report = batch[i];
437
+ const result = await client.submitReport(report);
438
+ if (result.ok) {
439
+ try {
440
+ if (backend === "indexeddb") await idbDelete(report.id);
441
+ else lsDelete(report.id);
442
+ } catch {
443
+ lsDelete(report.id);
444
+ }
445
+ sent++;
446
+ } else {
447
+ failed++;
448
+ if (i < batch.length - 1) {
449
+ await sleep2(getBackoffDelay2(i));
450
+ }
451
+ }
452
+ }
453
+ return { sent, failed };
454
+ }
455
+ async function size() {
456
+ const backend = detectBackend();
457
+ if (backend === "indexeddb") {
458
+ try {
459
+ return await idbSize();
460
+ } catch {
461
+ return lsRead().length;
462
+ }
463
+ }
464
+ return lsRead().length;
465
+ }
466
+ async function clear() {
467
+ const backend = detectBackend();
468
+ if (backend === "indexeddb") {
469
+ try {
470
+ await idbClear();
471
+ } catch {
472
+ }
473
+ }
474
+ try {
475
+ localStorage.removeItem(LS_KEY);
476
+ } catch {
477
+ }
478
+ }
479
+ function startAutoSync(client) {
480
+ if (!enabled || !syncOnReconnect || typeof window === "undefined") return;
481
+ const handler = () => {
482
+ if (navigator.onLine) {
483
+ flush(client).catch(() => {
484
+ });
485
+ }
486
+ };
487
+ window.addEventListener("online", handler);
488
+ syncCleanup = () => window.removeEventListener("online", handler);
489
+ }
490
+ function stopAutoSync() {
491
+ syncCleanup?.();
492
+ syncCleanup = null;
493
+ }
494
+ return { enqueue, flush, size, clear, startAutoSync, stopAutoSync };
495
+ }
496
+
497
+ // src/environment.ts
498
+ function captureEnvironment() {
499
+ const nav = typeof navigator !== "undefined" ? navigator : void 0;
500
+ const win = typeof window !== "undefined" ? window : void 0;
501
+ const doc = typeof document !== "undefined" ? document : void 0;
502
+ const connection = nav && "connection" in nav ? nav.connection : void 0;
503
+ return {
504
+ userAgent: nav?.userAgent ?? "unknown",
505
+ platform: nav?.platform ?? "unknown",
506
+ language: nav?.language ?? "en",
507
+ viewport: {
508
+ width: win?.innerWidth ?? 0,
509
+ height: win?.innerHeight ?? 0
510
+ },
511
+ url: win?.location?.href ?? "",
512
+ referrer: doc?.referrer ?? "",
513
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
514
+ timezone: Intl?.DateTimeFormat?.()?.resolvedOptions?.()?.timeZone ?? "UTC",
515
+ connection: connection ? {
516
+ effectiveType: connection.effectiveType,
517
+ downlink: connection.downlink,
518
+ rtt: connection.rtt
519
+ } : void 0,
520
+ deviceMemory: nav?.deviceMemory,
521
+ hardwareConcurrency: nav?.hardwareConcurrency
522
+ };
523
+ }
524
+
525
+ // src/reporter-token.ts
526
+ var STORAGE_KEY = "mushi_reporter_token";
527
+ function getReporterToken() {
528
+ if (typeof localStorage !== "undefined") {
529
+ const existing = localStorage.getItem(STORAGE_KEY);
530
+ if (existing) return existing;
531
+ }
532
+ const token = generateToken();
533
+ if (typeof localStorage !== "undefined") {
534
+ try {
535
+ localStorage.setItem(STORAGE_KEY, token);
536
+ } catch {
537
+ }
538
+ }
539
+ return token;
540
+ }
541
+ function generateToken() {
542
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
543
+ return `mushi_${crypto.randomUUID()}`;
544
+ }
545
+ const bytes = new Uint8Array(16);
546
+ if (typeof crypto !== "undefined") {
547
+ crypto.getRandomValues(bytes);
548
+ } else {
549
+ for (let i = 0; i < bytes.length; i++) {
550
+ bytes[i] = Math.floor(Math.random() * 256);
551
+ }
552
+ }
553
+ const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
554
+ return `mushi_${hex}`;
555
+ }
556
+
557
+ // src/session.ts
558
+ var SESSION_KEY = "mushi_session_id";
559
+ var cachedSessionId = null;
560
+ function getSessionId() {
561
+ if (cachedSessionId) return cachedSessionId;
562
+ if (typeof sessionStorage !== "undefined") {
563
+ const existing = sessionStorage.getItem(SESSION_KEY);
564
+ if (existing) {
565
+ cachedSessionId = existing;
566
+ return existing;
567
+ }
568
+ }
569
+ const id = generateSessionId();
570
+ cachedSessionId = id;
571
+ if (typeof sessionStorage !== "undefined") {
572
+ try {
573
+ sessionStorage.setItem(SESSION_KEY, id);
574
+ } catch {
575
+ }
576
+ }
577
+ return id;
578
+ }
579
+ function generateSessionId() {
580
+ const timestamp = Date.now().toString(36);
581
+ const random = Math.random().toString(36).slice(2, 8);
582
+ return `ms_${timestamp}_${random}`;
583
+ }
584
+
585
+ // src/rate-limiter.ts
586
+ var DEFAULT_MAX_BURST = 10;
587
+ var DEFAULT_REFILL_RATE = 1;
588
+ var DEFAULT_REFILL_INTERVAL_MS = 5e3;
589
+ function createRateLimiter(config = {}) {
590
+ const {
591
+ maxBurst = DEFAULT_MAX_BURST,
592
+ refillRate = DEFAULT_REFILL_RATE,
593
+ refillIntervalMs = DEFAULT_REFILL_INTERVAL_MS
594
+ } = config;
595
+ let tokens = maxBurst;
596
+ let lastRefill = Date.now();
597
+ function refill() {
598
+ const now = Date.now();
599
+ const elapsed = now - lastRefill;
600
+ const refills = Math.floor(elapsed / refillIntervalMs);
601
+ if (refills > 0) {
602
+ tokens = Math.min(maxBurst, tokens + refills * refillRate);
603
+ lastRefill = now;
604
+ }
605
+ }
606
+ function tryConsume() {
607
+ refill();
608
+ if (tokens > 0) {
609
+ tokens--;
610
+ return true;
611
+ }
612
+ return false;
613
+ }
614
+ function reset() {
615
+ tokens = maxBurst;
616
+ lastRefill = Date.now();
617
+ }
618
+ function availableTokens() {
619
+ refill();
620
+ return tokens;
621
+ }
622
+ return { tryConsume, reset, availableTokens };
623
+ }
624
+
625
+ // src/pii-scrubber.ts
626
+ var ORDERED_PATTERNS = [
627
+ { key: "ssns", regex: /\b\d{3}-\d{2}-\d{4}\b/g, replacement: "[REDACTED_SSN]" },
628
+ { key: "creditCards", regex: /\b(?:\d[ -]*){12,18}\d\b/g, replacement: "[REDACTED_CC]" },
629
+ { key: "emails", regex: /\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b/g, replacement: "[REDACTED_EMAIL]" },
630
+ { key: "phones", regex: /(?:\+\d{1,3}[\s.-])?\(?\d{2,4}\)?[\s.-]\d{3,4}[\s.-]\d{3,4}\b/g, replacement: "[REDACTED_PHONE]" },
631
+ { key: "ipAddresses", regex: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g, replacement: "[REDACTED_IP]" }
632
+ ];
633
+ var DEFAULT_CONFIG = {
634
+ emails: true,
635
+ phones: true,
636
+ creditCards: true,
637
+ ssns: true,
638
+ ipAddresses: false
639
+ };
640
+ function createPiiScrubber(config = {}) {
641
+ const merged = { ...DEFAULT_CONFIG, ...config };
642
+ const activePatterns = ORDERED_PATTERNS.filter((p) => merged[p.key]);
643
+ function scrub(text) {
644
+ if (!text) return text;
645
+ let result = text;
646
+ for (const { regex, replacement } of activePatterns) {
647
+ result = result.replace(new RegExp(regex.source, regex.flags), replacement);
648
+ }
649
+ return result;
650
+ }
651
+ function scrubObject(obj, keys) {
652
+ const copy = { ...obj };
653
+ for (const key of keys) {
654
+ if (typeof copy[key] === "string") {
655
+ copy[key] = scrub(copy[key]);
656
+ }
657
+ }
658
+ return copy;
659
+ }
660
+ return { scrub, scrubObject };
661
+ }
662
+ function scrubPii(text, config) {
663
+ return createPiiScrubber(config).scrub(text);
664
+ }
665
+
666
+ export { captureEnvironment, createApiClient, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, getReporterToken, getSessionId, noopLogger, scrubPii };
667
+ //# sourceMappingURL=index.js.map
668
+ //# sourceMappingURL=index.js.map