@diegotsi/flint-core 1.10.1 → 1.11.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 +129 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +129 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -180,8 +180,10 @@ function sanitize(str) {
|
|
|
180
180
|
|
|
181
181
|
// src/collectors/console.ts
|
|
182
182
|
var MAX_ENTRIES = 50;
|
|
183
|
+
var MAX_ERRORS = 10;
|
|
183
184
|
function createConsoleCollector() {
|
|
184
185
|
const entries = [];
|
|
186
|
+
const errors = [];
|
|
185
187
|
let active = false;
|
|
186
188
|
const originals = {
|
|
187
189
|
log: console.log.bind(console),
|
|
@@ -202,6 +204,15 @@ function createConsoleCollector() {
|
|
|
202
204
|
entries.push({ level, args: str, timestamp: Date.now() });
|
|
203
205
|
if (entries.length > MAX_ENTRIES) entries.shift();
|
|
204
206
|
}
|
|
207
|
+
function pushError(message, stack, errorClass) {
|
|
208
|
+
errors.push({
|
|
209
|
+
message: sanitize(message),
|
|
210
|
+
stack: stack ? sanitize(stack) : void 0,
|
|
211
|
+
errorClass,
|
|
212
|
+
timestamp: Date.now()
|
|
213
|
+
});
|
|
214
|
+
if (errors.length > MAX_ERRORS) errors.shift();
|
|
215
|
+
}
|
|
205
216
|
return {
|
|
206
217
|
start() {
|
|
207
218
|
if (active) return;
|
|
@@ -220,14 +231,22 @@ function createConsoleCollector() {
|
|
|
220
231
|
};
|
|
221
232
|
origOnerror = window.onerror;
|
|
222
233
|
window.onerror = (msg, src, line, col, err) => {
|
|
223
|
-
|
|
234
|
+
const message = err?.message ?? String(msg);
|
|
235
|
+
push("error", [message, `${src}:${line}:${col}`]);
|
|
236
|
+
pushError(message, err?.stack, err?.name);
|
|
224
237
|
if (typeof origOnerror === "function") return origOnerror(msg, src, line, col, err);
|
|
225
238
|
return false;
|
|
226
239
|
};
|
|
227
240
|
origUnhandled = window.onunhandledrejection;
|
|
228
241
|
window.onunhandledrejection = (event) => {
|
|
229
|
-
const
|
|
242
|
+
const isError = event.reason instanceof Error;
|
|
243
|
+
const reason = isError ? event.reason.message : JSON.stringify(event.reason);
|
|
230
244
|
push("error", ["UnhandledRejection:", reason]);
|
|
245
|
+
pushError(
|
|
246
|
+
reason,
|
|
247
|
+
isError ? event.reason.stack : void 0,
|
|
248
|
+
isError ? event.reason.name : void 0
|
|
249
|
+
);
|
|
231
250
|
if (typeof origUnhandled === "function") origUnhandled.call(window, event);
|
|
232
251
|
};
|
|
233
252
|
},
|
|
@@ -242,6 +261,9 @@ function createConsoleCollector() {
|
|
|
242
261
|
},
|
|
243
262
|
getEntries() {
|
|
244
263
|
return [...entries];
|
|
264
|
+
},
|
|
265
|
+
getErrors() {
|
|
266
|
+
return [...errors];
|
|
245
267
|
}
|
|
246
268
|
};
|
|
247
269
|
}
|
|
@@ -851,6 +873,100 @@ function createFrustrationCollector(opts) {
|
|
|
851
873
|
};
|
|
852
874
|
}
|
|
853
875
|
|
|
876
|
+
// src/collectors/sanitize.ts
|
|
877
|
+
var REDACTED = "[REDACTED]";
|
|
878
|
+
var DEFAULT_REDACT_KEYS = [
|
|
879
|
+
"password",
|
|
880
|
+
"passwd",
|
|
881
|
+
"pwd",
|
|
882
|
+
"secret",
|
|
883
|
+
"token",
|
|
884
|
+
"auth",
|
|
885
|
+
"credential",
|
|
886
|
+
"apikey",
|
|
887
|
+
"api_key",
|
|
888
|
+
"session",
|
|
889
|
+
"cookie",
|
|
890
|
+
"ssn",
|
|
891
|
+
"creditcard",
|
|
892
|
+
"credit_card",
|
|
893
|
+
"card_number",
|
|
894
|
+
"cardnumber",
|
|
895
|
+
"cvv",
|
|
896
|
+
"cvc",
|
|
897
|
+
"pin"
|
|
898
|
+
];
|
|
899
|
+
function createKeyMatcher(extra = []) {
|
|
900
|
+
const needles = [...DEFAULT_REDACT_KEYS, ...extra.map((k) => k.toLowerCase())];
|
|
901
|
+
return (key) => {
|
|
902
|
+
const lower = key.toLowerCase();
|
|
903
|
+
return needles.some((n) => lower.includes(n));
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
function redactValue(value, match) {
|
|
907
|
+
if (Array.isArray(value)) return value.map((v) => redactValue(v, match));
|
|
908
|
+
if (value && typeof value === "object") {
|
|
909
|
+
const out = {};
|
|
910
|
+
for (const [k, v] of Object.entries(value)) {
|
|
911
|
+
out[k] = match(k) ? REDACTED : redactValue(v, match);
|
|
912
|
+
}
|
|
913
|
+
return out;
|
|
914
|
+
}
|
|
915
|
+
return value;
|
|
916
|
+
}
|
|
917
|
+
function redactQueryString(qs, match) {
|
|
918
|
+
if (!qs) return qs;
|
|
919
|
+
return qs.split("&").map((pair) => {
|
|
920
|
+
const eq = pair.indexOf("=");
|
|
921
|
+
if (eq === -1) return pair;
|
|
922
|
+
const rawKey = pair.slice(0, eq);
|
|
923
|
+
let key = rawKey;
|
|
924
|
+
try {
|
|
925
|
+
key = decodeURIComponent(rawKey.replace(/\+/g, " "));
|
|
926
|
+
} catch {
|
|
927
|
+
}
|
|
928
|
+
return match(key) ? `${rawKey}=${REDACTED}` : pair;
|
|
929
|
+
}).join("&");
|
|
930
|
+
}
|
|
931
|
+
function sanitizeBody(body, match) {
|
|
932
|
+
if (!body) return body;
|
|
933
|
+
const trimmed = body.trimStart();
|
|
934
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
935
|
+
try {
|
|
936
|
+
const parsed = JSON.parse(body);
|
|
937
|
+
return JSON.stringify(redactValue(parsed, match));
|
|
938
|
+
} catch {
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (body.includes("=") && !/\s/.test(body)) {
|
|
942
|
+
const redacted = redactQueryString(body, match);
|
|
943
|
+
if (redacted !== body) return redacted;
|
|
944
|
+
}
|
|
945
|
+
return body.replace(
|
|
946
|
+
/("?)([\w.-]+)\1\s*:\s*"(?:[^"\\]|\\.)*"/gi,
|
|
947
|
+
(full, q, key) => match(key) ? `${q}${key}${q}:"${REDACTED}"` : full
|
|
948
|
+
).replace(/([\w.-]+)=([^&\s]+)/gi, (full, key) => match(key) ? `${key}=${REDACTED}` : full);
|
|
949
|
+
}
|
|
950
|
+
function sanitizeUrl(url, match) {
|
|
951
|
+
const qIdx = url.indexOf("?");
|
|
952
|
+
if (qIdx === -1) return url;
|
|
953
|
+
const base = url.slice(0, qIdx);
|
|
954
|
+
const query = url.slice(qIdx + 1);
|
|
955
|
+
const hashIdx = query.indexOf("#");
|
|
956
|
+
const qs = hashIdx === -1 ? query : query.slice(0, hashIdx);
|
|
957
|
+
const hash = hashIdx === -1 ? "" : query.slice(hashIdx);
|
|
958
|
+
const redacted = redactQueryString(qs, match);
|
|
959
|
+
return `${base}?${redacted}${hash}`;
|
|
960
|
+
}
|
|
961
|
+
function sanitizeHeaders(headers, match) {
|
|
962
|
+
if (!headers) return headers;
|
|
963
|
+
const out = {};
|
|
964
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
965
|
+
out[k] = match(k) ? REDACTED : v;
|
|
966
|
+
}
|
|
967
|
+
return out;
|
|
968
|
+
}
|
|
969
|
+
|
|
854
970
|
// src/collectors/network.ts
|
|
855
971
|
var MAX_ENTRIES3 = 50;
|
|
856
972
|
var MAX_FULL_URL = 2e3;
|
|
@@ -1026,6 +1142,7 @@ function extractContentType(headers) {
|
|
|
1026
1142
|
function createNetworkCollector(extraBlockedHosts = [], options) {
|
|
1027
1143
|
const maxEntries = options?.maxEntries ?? MAX_ENTRIES3;
|
|
1028
1144
|
const maxResponseBody = options?.maxResponseBody ?? MAX_RESPONSE_BODY;
|
|
1145
|
+
const matchSecret = createKeyMatcher(options?.redactKeys);
|
|
1029
1146
|
const entries = [];
|
|
1030
1147
|
const blocked = /* @__PURE__ */ new Set([...DEFAULT_BLOCKED_HOSTS, ...extraBlockedHosts]);
|
|
1031
1148
|
let origFetch = null;
|
|
@@ -1037,6 +1154,12 @@ function createNetworkCollector(extraBlockedHosts = [], options) {
|
|
|
1037
1154
|
const seenUrls = /* @__PURE__ */ new Set();
|
|
1038
1155
|
function push(entry) {
|
|
1039
1156
|
seenUrls.add(entry.fullUrl ?? entry.url);
|
|
1157
|
+
if (entry.fullUrl) entry.fullUrl = sanitizeUrl(entry.fullUrl, matchSecret);
|
|
1158
|
+
entry.url = sanitizeUrl(entry.url, matchSecret);
|
|
1159
|
+
entry.requestBody = sanitizeBody(entry.requestBody, matchSecret);
|
|
1160
|
+
entry.responseBody = sanitizeBody(entry.responseBody, matchSecret);
|
|
1161
|
+
entry.requestHeaders = sanitizeHeaders(entry.requestHeaders, matchSecret);
|
|
1162
|
+
entry.responseHeaders = sanitizeHeaders(entry.responseHeaders, matchSecret);
|
|
1040
1163
|
entries.push(entry);
|
|
1041
1164
|
if (entries.length > maxEntries) entries.shift();
|
|
1042
1165
|
}
|
|
@@ -1102,7 +1225,8 @@ function createNetworkCollector(extraBlockedHosts = [], options) {
|
|
|
1102
1225
|
try {
|
|
1103
1226
|
const clone = res.clone();
|
|
1104
1227
|
clone.text().then((text) => {
|
|
1105
|
-
|
|
1228
|
+
const truncated = text.length > maxResponseBody ? text.slice(0, maxResponseBody) + "\u2026" : text;
|
|
1229
|
+
entry.responseBody = sanitizeBody(truncated, matchSecret);
|
|
1106
1230
|
if (!entry.responseSize && text.length) entry.responseSize = text.length;
|
|
1107
1231
|
}).catch(() => {
|
|
1108
1232
|
});
|
|
@@ -1335,6 +1459,7 @@ function init(config) {
|
|
|
1335
1459
|
enableReplay = false,
|
|
1336
1460
|
replayBufferMs = DEFAULT_REPLAY_BUFFER_MS,
|
|
1337
1461
|
blockedHosts = [],
|
|
1462
|
+
redactKeys,
|
|
1338
1463
|
frustration: frustrationOpts,
|
|
1339
1464
|
onFrustration,
|
|
1340
1465
|
_replayRecorder,
|
|
@@ -1358,7 +1483,7 @@ function init(config) {
|
|
|
1358
1483
|
const allBlockedHosts = [...blockedHosts, ...flintHost ? [flintHost] : []];
|
|
1359
1484
|
const consoleCol = enableConsole ? _collectors?.console?.() ?? createConsoleCollector() : null;
|
|
1360
1485
|
consoleCol?.start();
|
|
1361
|
-
const networkCol = enableNetwork ? _collectors?.network?.(allBlockedHosts) ?? createNetworkCollector(allBlockedHosts) : null;
|
|
1486
|
+
const networkCol = enableNetwork ? _collectors?.network?.(allBlockedHosts, { redactKeys }) ?? createNetworkCollector(allBlockedHosts, { redactKeys }) : null;
|
|
1362
1487
|
networkCol?.start();
|
|
1363
1488
|
const formErrorsCol = enableFormErrors ? createFormErrorCollector() : null;
|
|
1364
1489
|
if (formErrorsCol) {
|