error-flux
Version:
Network request interceptor and logger for web applications
207 lines • 9.43 kB
JavaScript
/**
* ErrorFlux Network Interceptor (MonkeyPatch Approach)
* Captures all XHR & Fetch calls, logging success & error cases.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { getNetworkLogs, saveNetworkLogs } from "../db/index";
import store from "../state/store";
import { NetWorkClient, StorageTypes } from "../types";
import genUUID from "../utils/gen-uuid";
import { runLowPriorityTask } from "../utils/low-priority";
function getAllResponseHeaders(xhr) {
const headers = {};
const headerPairs = xhr.getAllResponseHeaders().trim().split("\n");
for (let i = 0; i < headerPairs.length; i++) {
const headerPair = headerPairs[i].trim().split(": ");
const key = headerPair[0];
const value = headerPair[1];
headers[key] = value;
}
return headers;
}
// Utility to generate unique request IDs
function generateRequestId() {
return genUUID();
}
const logs = {
push(log) {
return __awaiter(this, void 0, void 0, function* () {
runLowPriorityTask(() => __awaiter(this, void 0, void 0, function* () {
const { storeName: _storeName, dbName, storageType } = store.getState();
const storeName = _storeName.networkLogs;
switch (storageType) {
case StorageTypes.LocalStorage:
try {
const dbData = JSON.parse(localStorage.getItem(dbName) || "{}");
const existingLogs = dbData[storeName] || [];
existingLogs.push(log);
dbData[storeName] = existingLogs;
localStorage.setItem(dbName, JSON.stringify(dbData));
}
catch (err) {
console.warn("Failed to save to localStorage:", err);
}
break;
case StorageTypes.SessionStorage:
try {
const dbData = JSON.parse(sessionStorage.getItem(dbName) || "{}");
const existingLogs = dbData[storeName] || [];
existingLogs.push(log);
dbData[storeName] = existingLogs;
sessionStorage.setItem(dbName, JSON.stringify(dbData));
}
catch (err) {
console.warn("Failed to save to sessionStorage:", err);
}
break;
case StorageTypes.IndexedDB:
yield saveNetworkLogs(log);
break;
}
}));
});
},
};
const errorFluxNetworkInterceptor = ({ pattern, onlyFailures = false, }) => {
const originalFetch = window.fetch;
const originalXHR = window.XMLHttpRequest;
window.fetch = function (input, init) {
return __awaiter(this, void 0, void 0, function* () {
const requestId = generateRequestId();
const startTime = performance.now();
return originalFetch(input, init)
.then((response) => __awaiter(this, void 0, void 0, function* () {
const clonedResponse = response.clone();
const responseData = yield clonedResponse.text();
if (clonedResponse.url.match(pattern) &&
(!onlyFailures || (onlyFailures && !clonedResponse.ok))) {
logs.push({
id: requestId,
type: NetWorkClient.Fetch,
url: typeof input === "string" ? input : input.toString(),
method: (init === null || init === void 0 ? void 0 : init.method) || "GET",
requestHeaders: (init === null || init === void 0 ? void 0 : init.headers)
? Object.fromEntries(new Headers(init.headers).entries())
: {},
requestBody: (init === null || init === void 0 ? void 0 : init.body) || null,
responseHeaders: Object.fromEntries(clonedResponse.headers.entries()),
responseBody: responseData,
status: clonedResponse.status,
duration: performance.now() - startTime,
success: clonedResponse.ok,
cookies: document.cookie,
});
}
return response;
}))
.catch((error) => {
// For fetch API, we can determine network errors (status 0) vs HTTP errors (400-500)
const status = error instanceof TypeError
? 0
: error.status ||
(error.response && error.response.status) ||
500;
if (error.response.url.match(pattern)) {
logs.push({
id: requestId,
type: NetWorkClient.Fetch,
url: typeof input === "string" ? input : input.toString(),
method: (init === null || init === void 0 ? void 0 : init.method) || "GET",
error: error.message,
duration: performance.now() - startTime,
success: false,
requestBody: null,
status, // Actual HTTP error status or 500 as fallback
cookies: document.cookie,
});
}
throw error;
});
});
};
/**
* MonkeyPatch XMLHttpRequest
*/
class InterceptedXHR extends originalXHR {
constructor() {
super();
this.requestId = generateRequestId();
this.startTime = null;
this._url = "";
this._method = "";
this._error = null;
}
open(method, url, ...args) {
this._method = method;
this._url = url;
// @ts-ignore
super.open(method, url, ...args);
}
send(body) {
this.startTime = performance.now();
this.addEventListener("loadend", () => {
const isFailure = !(this.status >= 200 && this.status < 300);
if (!this._url.match(pattern) || (onlyFailures && !isFailure)) {
return;
}
logs.push({
id: this.requestId,
type: NetWorkClient.XHR,
url: this._url,
method: this._method,
requestBody: body || null,
responseHeaders: getAllResponseHeaders(this),
responseBody: this.responseText,
status: this.status,
duration: performance.now() - this.startTime,
success: !isFailure,
cookies: document.cookie,
});
});
this.addEventListener("error", () => {
if (this._url.match(pattern)) {
logs.push({
id: this.requestId,
type: NetWorkClient.XHR,
url: this._url,
method: this._method,
error: "Network error",
duration: performance.now() - this.startTime,
success: false,
requestBody: null,
cookies: document.cookie,
status: this.status,
});
}
});
super.send(body);
}
}
window.XMLHttpRequest = InterceptedXHR;
const getLogs = () => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b;
const { storeName: storeNameObj, dbName, storageType } = store.getState();
const storeName = storeNameObj.networkLogs;
switch (storageType) {
case StorageTypes.IndexedDB:
return yield getNetworkLogs();
case StorageTypes.LocalStorage:
return (((_a = JSON.parse(localStorage.getItem(dbName) || "{}")) === null || _a === void 0 ? void 0 : _a[storeName]) || []);
case StorageTypes.SessionStorage:
return (((_b = JSON.parse(sessionStorage.getItem(dbName) || "{}")) === null || _b === void 0 ? void 0 : _b[storeName]) || []);
}
});
return {
getLogs,
};
};
export default errorFluxNetworkInterceptor;
//# sourceMappingURL=network.js.map