UNPKG

error-flux

Version:

Network request interceptor and logger for web applications

207 lines 9.43 kB
/** * 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