@diegotsi/flint-core 1.9.1 → 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 CHANGED
@@ -989,7 +989,8 @@ function captureHeaders(raw, max) {
989
989
  count++;
990
990
  }
991
991
  } else {
992
- const entries = typeof Headers !== "undefined" && raw instanceof Headers ? raw.entries() : Array.isArray(raw) ? raw : Object.entries(raw);
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 Headers !== "undefined" && headers instanceof Headers) return headers.get("content-type") ?? void 0;
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 > MAX_ENTRIES3) entries.shift();
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 method = (init2?.method ?? "GET").toUpperCase();
1042
- const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
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 reqBody = safeBodyPreview(init2?.body, MAX_REQUEST_BODY);
1045
- const reqHeaders = captureHeaders(init2?.headers, MAX_HEADERS);
1046
- const reqContentType = extractContentType(init2?.headers);
1047
- const res = await origFetch.call(window, input, init2);
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 > MAX_RESPONSE_BODY ? text.slice(0, MAX_RESPONSE_BODY) + "\u2026" : text;
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 > MAX_RESPONSE_BODY ? text.slice(0, MAX_RESPONSE_BODY) + "\u2026" : text || void 0;
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];