@diegotsi/flint-core 1.9.0 → 1.10.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 +141 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -3
- package/dist/index.d.ts +31 -3
- package/dist/index.js +141 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -989,7 +989,8 @@ function captureHeaders(raw, max) {
|
|
|
989
989
|
count++;
|
|
990
990
|
}
|
|
991
991
|
} else {
|
|
992
|
-
const
|
|
992
|
+
const headersLike = raw;
|
|
993
|
+
const entries = typeof headersLike.entries === "function" && typeof headersLike.forEach === "function" ? headersLike.entries() : Array.isArray(raw) ? raw : Object.entries(raw);
|
|
993
994
|
for (const [k, v] of entries) {
|
|
994
995
|
total++;
|
|
995
996
|
if (count >= max) continue;
|
|
@@ -1007,7 +1008,7 @@ function captureHeaders(raw, max) {
|
|
|
1007
1008
|
function extractContentType(headers) {
|
|
1008
1009
|
if (!headers) return void 0;
|
|
1009
1010
|
try {
|
|
1010
|
-
if (typeof
|
|
1011
|
+
if (typeof headers.get === "function") return headers.get("content-type") ?? void 0;
|
|
1011
1012
|
if (Array.isArray(headers)) {
|
|
1012
1013
|
const found = headers.find(([k]) => k.toLowerCase() === "content-type");
|
|
1013
1014
|
return found?.[1];
|
|
@@ -1020,17 +1021,22 @@ function extractContentType(headers) {
|
|
|
1020
1021
|
}
|
|
1021
1022
|
return void 0;
|
|
1022
1023
|
}
|
|
1023
|
-
function createNetworkCollector(extraBlockedHosts = []) {
|
|
1024
|
+
function createNetworkCollector(extraBlockedHosts = [], options) {
|
|
1025
|
+
const maxEntries = options?.maxEntries ?? MAX_ENTRIES3;
|
|
1026
|
+
const maxResponseBody = options?.maxResponseBody ?? MAX_RESPONSE_BODY;
|
|
1024
1027
|
const entries = [];
|
|
1025
1028
|
const blocked = /* @__PURE__ */ new Set([...DEFAULT_BLOCKED_HOSTS, ...extraBlockedHosts]);
|
|
1026
1029
|
let origFetch = null;
|
|
1027
1030
|
let origXHROpen = null;
|
|
1028
1031
|
let origXHRSend = null;
|
|
1029
1032
|
let origXHRSetHeader = null;
|
|
1033
|
+
let perfObserver = null;
|
|
1030
1034
|
let active = false;
|
|
1035
|
+
const seenUrls = /* @__PURE__ */ new Set();
|
|
1031
1036
|
function push(entry) {
|
|
1037
|
+
seenUrls.add(entry.fullUrl ?? entry.url);
|
|
1032
1038
|
entries.push(entry);
|
|
1033
|
-
if (entries.length >
|
|
1039
|
+
if (entries.length > maxEntries) entries.shift();
|
|
1034
1040
|
}
|
|
1035
1041
|
return {
|
|
1036
1042
|
start() {
|
|
@@ -1038,19 +1044,50 @@ function createNetworkCollector(extraBlockedHosts = []) {
|
|
|
1038
1044
|
active = true;
|
|
1039
1045
|
origFetch = window.fetch;
|
|
1040
1046
|
window.fetch = async (input, init2) => {
|
|
1041
|
-
const
|
|
1042
|
-
const
|
|
1047
|
+
const reqObj = typeof Request !== "undefined" && input instanceof Request ? input : null;
|
|
1048
|
+
const method = (init2?.method ?? reqObj?.method ?? "GET").toUpperCase();
|
|
1049
|
+
const url = reqObj ? reqObj.url : typeof input === "string" ? input : input.href;
|
|
1043
1050
|
const startTime = Date.now();
|
|
1044
|
-
const
|
|
1045
|
-
const
|
|
1046
|
-
|
|
1047
|
-
|
|
1051
|
+
const reqHeaders = captureHeaders(init2?.headers ?? reqObj?.headers, MAX_HEADERS);
|
|
1052
|
+
const reqContentType = extractContentType(init2?.headers ?? reqObj?.headers);
|
|
1053
|
+
let reqBody = safeBodyPreview(init2?.body, MAX_REQUEST_BODY);
|
|
1054
|
+
if (reqBody === void 0 && reqObj && !shouldIgnore(url, blocked)) {
|
|
1055
|
+
try {
|
|
1056
|
+
const t = await reqObj.clone().text();
|
|
1057
|
+
if (t) reqBody = t.length > MAX_REQUEST_BODY ? t.slice(0, MAX_REQUEST_BODY) + "\u2026" : t;
|
|
1058
|
+
} catch {
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
let res;
|
|
1062
|
+
try {
|
|
1063
|
+
res = await origFetch.call(window, input, init2);
|
|
1064
|
+
} catch (err) {
|
|
1065
|
+
if (!shouldIgnore(url, blocked)) {
|
|
1066
|
+
push({
|
|
1067
|
+
method,
|
|
1068
|
+
url: truncateUrl(url),
|
|
1069
|
+
fullUrl: captureFullUrl(url),
|
|
1070
|
+
status: 0,
|
|
1071
|
+
duration: Date.now() - startTime,
|
|
1072
|
+
timestamp: startTime,
|
|
1073
|
+
source: "fetch",
|
|
1074
|
+
failed: true,
|
|
1075
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
1076
|
+
requestHeaders: reqHeaders,
|
|
1077
|
+
requestBody: reqBody,
|
|
1078
|
+
requestContentType: reqContentType
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
throw err;
|
|
1082
|
+
}
|
|
1048
1083
|
if (!shouldIgnore(url, blocked)) {
|
|
1049
1084
|
const entry = {
|
|
1050
1085
|
method,
|
|
1051
1086
|
url: truncateUrl(url),
|
|
1052
1087
|
fullUrl: captureFullUrl(url),
|
|
1053
1088
|
status: res.status,
|
|
1089
|
+
statusText: res.statusText || void 0,
|
|
1090
|
+
source: "fetch",
|
|
1054
1091
|
duration: Date.now() - startTime,
|
|
1055
1092
|
timestamp: startTime,
|
|
1056
1093
|
requestHeaders: reqHeaders,
|
|
@@ -1063,7 +1100,7 @@ function createNetworkCollector(extraBlockedHosts = []) {
|
|
|
1063
1100
|
try {
|
|
1064
1101
|
const clone = res.clone();
|
|
1065
1102
|
clone.text().then((text) => {
|
|
1066
|
-
entry.responseBody = text.length >
|
|
1103
|
+
entry.responseBody = text.length > maxResponseBody ? text.slice(0, maxResponseBody) + "\u2026" : text;
|
|
1067
1104
|
if (!entry.responseSize && text.length) entry.responseSize = text.length;
|
|
1068
1105
|
}).catch(() => {
|
|
1069
1106
|
});
|
|
@@ -1100,13 +1137,16 @@ function createNetworkCollector(extraBlockedHosts = []) {
|
|
|
1100
1137
|
XMLHttpRequest.prototype.open = function(method, url, async, username, password) {
|
|
1101
1138
|
const startTime = Date.now();
|
|
1102
1139
|
const urlStr = typeof url === "string" ? url : url.href;
|
|
1140
|
+
let captured = false;
|
|
1103
1141
|
this.addEventListener("load", () => {
|
|
1142
|
+
if (captured) return;
|
|
1143
|
+
captured = true;
|
|
1104
1144
|
if (!shouldIgnore(urlStr, blocked)) {
|
|
1105
1145
|
const meta = xhrMeta.get(this);
|
|
1106
1146
|
let resBody;
|
|
1107
1147
|
try {
|
|
1108
1148
|
const text = this.responseText;
|
|
1109
|
-
resBody = text && text.length >
|
|
1149
|
+
resBody = text && text.length > maxResponseBody ? text.slice(0, maxResponseBody) + "\u2026" : text || void 0;
|
|
1110
1150
|
} catch {
|
|
1111
1151
|
}
|
|
1112
1152
|
push({
|
|
@@ -1114,6 +1154,8 @@ function createNetworkCollector(extraBlockedHosts = []) {
|
|
|
1114
1154
|
url: truncateUrl(urlStr),
|
|
1115
1155
|
fullUrl: captureFullUrl(urlStr),
|
|
1116
1156
|
status: this.status,
|
|
1157
|
+
statusText: this.statusText || void 0,
|
|
1158
|
+
source: "xhr",
|
|
1117
1159
|
duration: Date.now() - startTime,
|
|
1118
1160
|
timestamp: startTime,
|
|
1119
1161
|
requestHeaders: meta?.reqHeaders && Object.keys(meta.reqHeaders).length > 0 ? meta.reqHeaders : void 0,
|
|
@@ -1126,8 +1168,58 @@ function createNetworkCollector(extraBlockedHosts = []) {
|
|
|
1126
1168
|
});
|
|
1127
1169
|
}
|
|
1128
1170
|
});
|
|
1171
|
+
const pushFailure = (errorMessage) => {
|
|
1172
|
+
if (captured) return;
|
|
1173
|
+
captured = true;
|
|
1174
|
+
if (shouldIgnore(urlStr, blocked)) return;
|
|
1175
|
+
const meta = xhrMeta.get(this);
|
|
1176
|
+
push({
|
|
1177
|
+
method: method.toUpperCase(),
|
|
1178
|
+
url: truncateUrl(urlStr),
|
|
1179
|
+
fullUrl: captureFullUrl(urlStr),
|
|
1180
|
+
status: 0,
|
|
1181
|
+
source: "xhr",
|
|
1182
|
+
failed: true,
|
|
1183
|
+
errorMessage,
|
|
1184
|
+
duration: Date.now() - startTime,
|
|
1185
|
+
timestamp: startTime,
|
|
1186
|
+
requestHeaders: meta?.reqHeaders && Object.keys(meta.reqHeaders).length > 0 ? meta.reqHeaders : void 0,
|
|
1187
|
+
requestBody: meta?.reqBody,
|
|
1188
|
+
requestContentType: meta?.reqContentType
|
|
1189
|
+
});
|
|
1190
|
+
};
|
|
1191
|
+
this.addEventListener("error", () => pushFailure("Network error"));
|
|
1192
|
+
this.addEventListener("timeout", () => pushFailure("Timeout"));
|
|
1193
|
+
this.addEventListener("abort", () => pushFailure("Aborted"));
|
|
1129
1194
|
return origXHROpen.apply(this, [method, url, async ?? true, username, password]);
|
|
1130
1195
|
};
|
|
1196
|
+
if (typeof PerformanceObserver !== "undefined") {
|
|
1197
|
+
try {
|
|
1198
|
+
perfObserver = new PerformanceObserver((list) => {
|
|
1199
|
+
for (const entry of list.getEntries()) {
|
|
1200
|
+
const res = entry;
|
|
1201
|
+
if (res.initiatorType !== "fetch" && res.initiatorType !== "xmlhttprequest") continue;
|
|
1202
|
+
const url = res.name;
|
|
1203
|
+
if (shouldIgnore(url, blocked)) continue;
|
|
1204
|
+
if (seenUrls.has(captureFullUrl(url)) || seenUrls.has(truncateUrl(url))) continue;
|
|
1205
|
+
push({
|
|
1206
|
+
// Resource timing does not expose the HTTP method; default to GET.
|
|
1207
|
+
method: "GET",
|
|
1208
|
+
url: truncateUrl(url),
|
|
1209
|
+
fullUrl: captureFullUrl(url),
|
|
1210
|
+
status: res.responseStatus ?? 0,
|
|
1211
|
+
source: "perf",
|
|
1212
|
+
duration: Math.round(res.duration),
|
|
1213
|
+
timestamp: Math.round(performance.timeOrigin + res.startTime),
|
|
1214
|
+
responseSize: res.transferSize || res.encodedBodySize || void 0
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
});
|
|
1218
|
+
perfObserver.observe({ type: "resource", buffered: true });
|
|
1219
|
+
} catch {
|
|
1220
|
+
perfObserver = null;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1131
1223
|
},
|
|
1132
1224
|
stop() {
|
|
1133
1225
|
if (!active) return;
|
|
@@ -1136,6 +1228,10 @@ function createNetworkCollector(extraBlockedHosts = []) {
|
|
|
1136
1228
|
if (origXHROpen) XMLHttpRequest.prototype.open = origXHROpen;
|
|
1137
1229
|
if (origXHRSend) XMLHttpRequest.prototype.send = origXHRSend;
|
|
1138
1230
|
if (origXHRSetHeader) XMLHttpRequest.prototype.setRequestHeader = origXHRSetHeader;
|
|
1231
|
+
if (perfObserver) {
|
|
1232
|
+
perfObserver.disconnect();
|
|
1233
|
+
perfObserver = null;
|
|
1234
|
+
}
|
|
1139
1235
|
},
|
|
1140
1236
|
getEntries() {
|
|
1141
1237
|
return [...entries];
|
|
@@ -1146,6 +1242,34 @@ function createNetworkCollector(extraBlockedHosts = []) {
|
|
|
1146
1242
|
// src/index.ts
|
|
1147
1243
|
init_datadog();
|
|
1148
1244
|
|
|
1245
|
+
// src/replayBuffer.ts
|
|
1246
|
+
function createReplayBuffer(replayBufferMs, now = Date.now) {
|
|
1247
|
+
const events = [];
|
|
1248
|
+
let checkoutSeen = false;
|
|
1249
|
+
let recentCheckoutIdx = 0;
|
|
1250
|
+
return {
|
|
1251
|
+
push(event, isCheckout) {
|
|
1252
|
+
if (isCheckout) {
|
|
1253
|
+
checkoutSeen = true;
|
|
1254
|
+
if (events.length > 0) {
|
|
1255
|
+
events.splice(0, recentCheckoutIdx);
|
|
1256
|
+
recentCheckoutIdx = events.length;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
events.push(event);
|
|
1260
|
+
if (!checkoutSeen) {
|
|
1261
|
+
const cutoff = now() - replayBufferMs;
|
|
1262
|
+
while (events.length > 0 && events[0].timestamp < cutoff) {
|
|
1263
|
+
events.shift();
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
},
|
|
1267
|
+
getEvents() {
|
|
1268
|
+
return [...events];
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1149
1273
|
// src/store.ts
|
|
1150
1274
|
var formErrorCollectorRef = null;
|
|
1151
1275
|
function _setFormErrorCollector(collector) {
|
|
@@ -1261,7 +1385,7 @@ function init(config) {
|
|
|
1261
1385
|
if (config.user) {
|
|
1262
1386
|
flint.setUser(config.user);
|
|
1263
1387
|
}
|
|
1264
|
-
const
|
|
1388
|
+
const replayBuffer = createReplayBuffer(replayBufferMs);
|
|
1265
1389
|
let stopReplay = null;
|
|
1266
1390
|
debugLog(config, "Collectors started", {
|
|
1267
1391
|
console: !!consoleCol,
|
|
@@ -1277,16 +1401,12 @@ function init(config) {
|
|
|
1277
1401
|
formErrors: formErrorsCol,
|
|
1278
1402
|
frustration: frustrationCol,
|
|
1279
1403
|
errorCapture: errorCaptureCol,
|
|
1280
|
-
|
|
1404
|
+
replayBuffer,
|
|
1281
1405
|
stopReplay: null
|
|
1282
1406
|
};
|
|
1283
1407
|
if (enableReplay && _replayRecorder) {
|
|
1284
|
-
_replayRecorder((event) => {
|
|
1285
|
-
|
|
1286
|
-
const cutoff = Date.now() - replayBufferMs;
|
|
1287
|
-
while (replayEvents.length > 0 && replayEvents[0].timestamp < cutoff) {
|
|
1288
|
-
replayEvents.shift();
|
|
1289
|
-
}
|
|
1408
|
+
_replayRecorder((event, isCheckout) => {
|
|
1409
|
+
replayBuffer.push(event, isCheckout);
|
|
1290
1410
|
}).then((stop) => {
|
|
1291
1411
|
stopReplay = stop ?? null;
|
|
1292
1412
|
if (instance) instance.stopReplay = stopReplay;
|
|
@@ -1378,7 +1498,7 @@ function getMeta(extraMeta) {
|
|
|
1378
1498
|
};
|
|
1379
1499
|
}
|
|
1380
1500
|
function getReplayEvents() {
|
|
1381
|
-
return instance ?
|
|
1501
|
+
return instance ? instance.replayBuffer.getEvents() : [];
|
|
1382
1502
|
}
|
|
1383
1503
|
function getConfig() {
|
|
1384
1504
|
return instance?.config ?? null;
|