@diegotsi/flint-core 1.7.0 → 1.8.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.cjs +239 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -1
- package/dist/index.d.ts +65 -1
- package/dist/index.js +238 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -64,6 +64,7 @@ __export(index_exports, {
|
|
|
64
64
|
collectEnvironment: () => collectEnvironment,
|
|
65
65
|
createConsoleCollector: () => createConsoleCollector,
|
|
66
66
|
createDatadogReplayProvider: () => createDatadogReplayProvider,
|
|
67
|
+
createErrorCaptureCollector: () => createErrorCaptureCollector,
|
|
67
68
|
createFormErrorCollector: () => createFormErrorCollector,
|
|
68
69
|
createFrustrationCollector: () => createFrustrationCollector,
|
|
69
70
|
createNetworkCollector: () => createNetworkCollector,
|
|
@@ -141,8 +142,7 @@ async function submitReplay(serverUrl, projectKey, reportId, events) {
|
|
|
141
142
|
});
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
// src/
|
|
145
|
-
var MAX_ENTRIES = 50;
|
|
145
|
+
// src/sanitize.ts
|
|
146
146
|
var SENSITIVE_PATTERNS = [
|
|
147
147
|
/(?:password|passwd|pwd|secret|token|api[_-]?key|access[_-]?key|authorization|bearer)\s*[:=]\s*["']?[^\s"',]{4,}/gi,
|
|
148
148
|
/\b(sk-[a-zA-Z0-9_-]{20,})\b/g,
|
|
@@ -161,6 +161,9 @@ function sanitize(str) {
|
|
|
161
161
|
}
|
|
162
162
|
return result;
|
|
163
163
|
}
|
|
164
|
+
|
|
165
|
+
// src/collectors/console.ts
|
|
166
|
+
var MAX_ENTRIES = 50;
|
|
164
167
|
function createConsoleCollector() {
|
|
165
168
|
const entries = [];
|
|
166
169
|
let active = false;
|
|
@@ -280,6 +283,216 @@ function collectEnvironment() {
|
|
|
280
283
|
};
|
|
281
284
|
}
|
|
282
285
|
|
|
286
|
+
// src/collectors/errorCapture.ts
|
|
287
|
+
var MAX_MESSAGE = 1e3;
|
|
288
|
+
var MAX_STACK = 8e3;
|
|
289
|
+
var MAX_BATCH = 20;
|
|
290
|
+
var MAX_PAYLOAD_BYTES = 6e4;
|
|
291
|
+
var PER_KEY_PER_MINUTE = 10;
|
|
292
|
+
var GLOBAL_PAGE_CAP = 100;
|
|
293
|
+
var FLUSH_INTERVAL_MS = 5e3;
|
|
294
|
+
var THROTTLE_WINDOW_MS = 6e4;
|
|
295
|
+
var BREADCRUMB_CONSOLE = 10;
|
|
296
|
+
var BREADCRUMB_NETWORK = 10;
|
|
297
|
+
var DEFAULT_IGNORE = [/ResizeObserver loop/i, /^Script error\.?$/];
|
|
298
|
+
var EXTENSION_URL = /(chrome|moz|safari|safari-web)-extension:\/\//;
|
|
299
|
+
function normalizeForKey(message) {
|
|
300
|
+
return message.toLowerCase().replace(/\d+/g, "#").replace(/\s+/g, " ").trim().slice(0, 200);
|
|
301
|
+
}
|
|
302
|
+
function topFrame(stack) {
|
|
303
|
+
if (!stack) return "";
|
|
304
|
+
const lines = stack.split("\n").map((l) => l.trim());
|
|
305
|
+
return lines.find((l) => l.startsWith("at ") || /@/.test(l)) ?? "";
|
|
306
|
+
}
|
|
307
|
+
function createErrorCaptureCollector(options) {
|
|
308
|
+
const sampleRate = options.sampleRate ?? 1;
|
|
309
|
+
const ignoreList = [...DEFAULT_IGNORE, ...options.ignoreErrors ?? []];
|
|
310
|
+
const sessionId = `s_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
|
|
311
|
+
let active = false;
|
|
312
|
+
let queue = [];
|
|
313
|
+
let interval = null;
|
|
314
|
+
let sentCount = 0;
|
|
315
|
+
let environment;
|
|
316
|
+
const keyTimestamps = /* @__PURE__ */ new Map();
|
|
317
|
+
function debugLog2(...args) {
|
|
318
|
+
if (options.debug) console.log("[Flint]", ...args);
|
|
319
|
+
}
|
|
320
|
+
function isIgnored(message, stack, frameUrl) {
|
|
321
|
+
for (const entry of ignoreList) {
|
|
322
|
+
if (typeof entry === "string") {
|
|
323
|
+
if (message.includes(entry)) return true;
|
|
324
|
+
} else if (entry.test(message)) {
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (EXTENSION_URL.test(frameUrl ?? "") || EXTENSION_URL.test(stack ?? "")) return true;
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
function isThrottled(localKey) {
|
|
332
|
+
const now = Date.now();
|
|
333
|
+
const stamps = (keyTimestamps.get(localKey) ?? []).filter((t) => now - t < THROTTLE_WINDOW_MS);
|
|
334
|
+
if (stamps.length >= PER_KEY_PER_MINUTE) {
|
|
335
|
+
keyTimestamps.set(localKey, stamps);
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
stamps.push(now);
|
|
339
|
+
keyTimestamps.set(localKey, stamps);
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
function capture(input) {
|
|
343
|
+
try {
|
|
344
|
+
if (sentCount + queue.length >= GLOBAL_PAGE_CAP) return;
|
|
345
|
+
let message = input.message.slice(0, MAX_MESSAGE);
|
|
346
|
+
if (isIgnored(message, input.stack, input.frameUrl)) return;
|
|
347
|
+
if (sampleRate < 1 && Math.random() >= sampleRate) return;
|
|
348
|
+
const localKey = `${input.type}|${normalizeForKey(message)}|${topFrame(input.stack)}`;
|
|
349
|
+
if (isThrottled(localKey)) return;
|
|
350
|
+
const pending = queue.find((e) => e._localKey === localKey);
|
|
351
|
+
if (pending) {
|
|
352
|
+
pending.count += 1;
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
message = sanitize(message);
|
|
356
|
+
const stack = input.stack ? sanitize(input.stack.slice(0, MAX_STACK)) : void 0;
|
|
357
|
+
if (!environment && options.getEnvironment) {
|
|
358
|
+
try {
|
|
359
|
+
environment = options.getEnvironment();
|
|
360
|
+
} catch {
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const user = options.getUser?.();
|
|
364
|
+
let event = {
|
|
365
|
+
type: input.type,
|
|
366
|
+
message,
|
|
367
|
+
errorClass: input.errorClass,
|
|
368
|
+
stack,
|
|
369
|
+
url: typeof location !== "undefined" ? location.href : "",
|
|
370
|
+
timestamp: Date.now(),
|
|
371
|
+
release: options.release,
|
|
372
|
+
appVersion: options.appVersion,
|
|
373
|
+
userId: user?.id,
|
|
374
|
+
sessionId,
|
|
375
|
+
browser: environment?.browser,
|
|
376
|
+
os: environment?.os,
|
|
377
|
+
count: 1,
|
|
378
|
+
breadcrumbs: options.getBreadcrumbs ? {
|
|
379
|
+
consoleLogs: options.getBreadcrumbs().consoleLogs.slice(-BREADCRUMB_CONSOLE),
|
|
380
|
+
networkErrors: options.getBreadcrumbs().networkErrors.slice(-BREADCRUMB_NETWORK)
|
|
381
|
+
} : void 0,
|
|
382
|
+
_localKey: localKey
|
|
383
|
+
};
|
|
384
|
+
if (options.beforeSend) {
|
|
385
|
+
try {
|
|
386
|
+
const result = options.beforeSend(event);
|
|
387
|
+
if (!result) return;
|
|
388
|
+
event = { ...result, _localKey: localKey };
|
|
389
|
+
} catch {
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
queue.push(event);
|
|
393
|
+
debugLog2("Error captured", input.errorClass, message);
|
|
394
|
+
if (queue.length >= MAX_BATCH) flush(false);
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function serialize(events) {
|
|
399
|
+
const wire = events.map(({ _localKey, ...e }) => e);
|
|
400
|
+
let body = JSON.stringify(wire);
|
|
401
|
+
if (body.length > MAX_PAYLOAD_BYTES) {
|
|
402
|
+
body = JSON.stringify(wire.map((e) => ({ ...e, breadcrumbs: void 0 })));
|
|
403
|
+
if (body.length > MAX_PAYLOAD_BYTES) {
|
|
404
|
+
body = JSON.stringify(wire.map((e) => ({ ...e, breadcrumbs: void 0, stack: e.stack?.slice(0, 2e3) })));
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return body;
|
|
408
|
+
}
|
|
409
|
+
function flush(unloading) {
|
|
410
|
+
if (queue.length === 0) return;
|
|
411
|
+
const events = queue;
|
|
412
|
+
queue = [];
|
|
413
|
+
sentCount += events.length;
|
|
414
|
+
const base = options.serverUrl.replace(/\/$/, "");
|
|
415
|
+
const url = `${base}/api/v1/error-events?project_key=${encodeURIComponent(options.projectKey)}`;
|
|
416
|
+
const body = serialize(events);
|
|
417
|
+
if (unloading && typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
|
|
418
|
+
const ok = navigator.sendBeacon(url, body);
|
|
419
|
+
debugLog2("Flushed via beacon", events.length, ok);
|
|
420
|
+
if (ok) return;
|
|
421
|
+
}
|
|
422
|
+
fetch(url, {
|
|
423
|
+
method: "POST",
|
|
424
|
+
headers: { "Content-Type": "text/plain;charset=UTF-8" },
|
|
425
|
+
body,
|
|
426
|
+
keepalive: unloading
|
|
427
|
+
}).catch(() => {
|
|
428
|
+
});
|
|
429
|
+
debugLog2("Flushed via fetch", events.length);
|
|
430
|
+
}
|
|
431
|
+
function onError(event) {
|
|
432
|
+
const e = event;
|
|
433
|
+
if (typeof e.message !== "string" && !(e.error instanceof Error)) return;
|
|
434
|
+
const err = e.error instanceof Error ? e.error : void 0;
|
|
435
|
+
capture({
|
|
436
|
+
type: "error",
|
|
437
|
+
message: err?.message ?? (e.message || "Unknown error"),
|
|
438
|
+
errorClass: err?.name ?? "Error",
|
|
439
|
+
stack: err?.stack,
|
|
440
|
+
frameUrl: e.filename
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
function onRejection(event) {
|
|
444
|
+
const reason = event.reason;
|
|
445
|
+
const err = reason instanceof Error ? reason : void 0;
|
|
446
|
+
let message;
|
|
447
|
+
if (err) {
|
|
448
|
+
message = err.message;
|
|
449
|
+
} else {
|
|
450
|
+
try {
|
|
451
|
+
message = typeof reason === "string" ? reason : JSON.stringify(reason);
|
|
452
|
+
} catch {
|
|
453
|
+
message = String(reason);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
capture({
|
|
457
|
+
type: "unhandledrejection",
|
|
458
|
+
message: message || "Unhandled promise rejection",
|
|
459
|
+
errorClass: err?.name ?? "UnhandledRejection",
|
|
460
|
+
stack: err?.stack
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
function onPageHide() {
|
|
464
|
+
flush(true);
|
|
465
|
+
}
|
|
466
|
+
function onVisibilityChange() {
|
|
467
|
+
if (document.visibilityState === "hidden") flush(true);
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
start() {
|
|
471
|
+
if (active) return;
|
|
472
|
+
active = true;
|
|
473
|
+
window.addEventListener("error", onError, { capture: true });
|
|
474
|
+
window.addEventListener("unhandledrejection", onRejection, { capture: true });
|
|
475
|
+
window.addEventListener("pagehide", onPageHide);
|
|
476
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
477
|
+
interval = setInterval(() => flush(false), FLUSH_INTERVAL_MS);
|
|
478
|
+
},
|
|
479
|
+
stop() {
|
|
480
|
+
if (!active) return;
|
|
481
|
+
active = false;
|
|
482
|
+
window.removeEventListener("error", onError, { capture: true });
|
|
483
|
+
window.removeEventListener("unhandledrejection", onRejection, { capture: true });
|
|
484
|
+
window.removeEventListener("pagehide", onPageHide);
|
|
485
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
486
|
+
if (interval) clearInterval(interval);
|
|
487
|
+
interval = null;
|
|
488
|
+
flush(false);
|
|
489
|
+
},
|
|
490
|
+
flush() {
|
|
491
|
+
flush(false);
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
283
496
|
// src/collectors/formErrors.ts
|
|
284
497
|
var MAX_ENTRIES2 = 30;
|
|
285
498
|
var POST_SUBMIT_CHECK_MS = 300;
|
|
@@ -977,6 +1190,8 @@ function init(config) {
|
|
|
977
1190
|
enableFormErrors = true,
|
|
978
1191
|
enableFrustration = false,
|
|
979
1192
|
autoReportFrustration = false,
|
|
1193
|
+
enableErrorMonitoring = true,
|
|
1194
|
+
errorMonitoring: errorMonitoringOpts,
|
|
980
1195
|
enableReplay = false,
|
|
981
1196
|
replayBufferMs = DEFAULT_REPLAY_BUFFER_MS,
|
|
982
1197
|
blockedHosts = [],
|
|
@@ -1012,6 +1227,23 @@ function init(config) {
|
|
|
1012
1227
|
}
|
|
1013
1228
|
const frustrationCol = enableFrustration ? createFrustrationCollector(frustrationOpts) : null;
|
|
1014
1229
|
frustrationCol?.start();
|
|
1230
|
+
const errorCaptureCol = enableErrorMonitoring ? createErrorCaptureCollector({
|
|
1231
|
+
serverUrl: config.serverUrl,
|
|
1232
|
+
projectKey: config.projectKey,
|
|
1233
|
+
release: config.release,
|
|
1234
|
+
appVersion: config.appVersion,
|
|
1235
|
+
debug: config.debug,
|
|
1236
|
+
sampleRate: errorMonitoringOpts?.sampleRate,
|
|
1237
|
+
ignoreErrors: errorMonitoringOpts?.ignoreErrors,
|
|
1238
|
+
beforeSend: errorMonitoringOpts?.beforeSend,
|
|
1239
|
+
getUser: () => getSnapshot().user ?? config.user,
|
|
1240
|
+
getBreadcrumbs: () => ({
|
|
1241
|
+
consoleLogs: consoleCol?.getEntries() ?? [],
|
|
1242
|
+
networkErrors: networkCol?.getEntries() ?? []
|
|
1243
|
+
}),
|
|
1244
|
+
getEnvironment: _collectors?.environment ?? collectEnvironment
|
|
1245
|
+
}) : null;
|
|
1246
|
+
errorCaptureCol?.start();
|
|
1015
1247
|
if (config.user) {
|
|
1016
1248
|
flint.setUser(config.user);
|
|
1017
1249
|
}
|
|
@@ -1021,7 +1253,8 @@ function init(config) {
|
|
|
1021
1253
|
console: !!consoleCol,
|
|
1022
1254
|
network: !!networkCol,
|
|
1023
1255
|
formErrors: !!formErrorsCol,
|
|
1024
|
-
frustration: !!frustrationCol
|
|
1256
|
+
frustration: !!frustrationCol,
|
|
1257
|
+
errorCapture: !!errorCaptureCol
|
|
1025
1258
|
});
|
|
1026
1259
|
instance = {
|
|
1027
1260
|
config,
|
|
@@ -1029,6 +1262,7 @@ function init(config) {
|
|
|
1029
1262
|
network: networkCol,
|
|
1030
1263
|
formErrors: formErrorsCol,
|
|
1031
1264
|
frustration: frustrationCol,
|
|
1265
|
+
errorCapture: errorCaptureCol,
|
|
1032
1266
|
replayEvents,
|
|
1033
1267
|
stopReplay: null
|
|
1034
1268
|
};
|
|
@@ -1109,6 +1343,7 @@ function shutdown() {
|
|
|
1109
1343
|
instance.formErrors?.stop();
|
|
1110
1344
|
_setFormErrorCollector(null);
|
|
1111
1345
|
instance.frustration?.stop();
|
|
1346
|
+
instance.errorCapture?.stop();
|
|
1112
1347
|
instance.stopReplay?.();
|
|
1113
1348
|
instance = null;
|
|
1114
1349
|
}
|
|
@@ -1200,6 +1435,7 @@ function resolveTheme(theme) {
|
|
|
1200
1435
|
collectEnvironment,
|
|
1201
1436
|
createConsoleCollector,
|
|
1202
1437
|
createDatadogReplayProvider,
|
|
1438
|
+
createErrorCaptureCollector,
|
|
1203
1439
|
createFormErrorCollector,
|
|
1204
1440
|
createFrustrationCollector,
|
|
1205
1441
|
createNetworkCollector,
|