let requestMaps = {}; const potentialActiveTabs: Array = []; export function resetMap(tabId?: string) { if (tabId) delete requestMaps[tabId]; else requestMaps = {}; } export async function attachDebuggerToTab(tabId: string | number) { if (requestMaps[tabId] && potentialActiveTabs.includes(tabId)) return; await new Promise((resolve, reject) => { chrome.debugger.attach({ tabId }, "1.3", () => { if (chrome.runtime.lastError) return reject(`${chrome.runtime.lastError.message}, ${tabId}`); if (!requestMaps[tabId]) requestMaps[tabId] = {}; potentialActiveTabs.push(tabId); chrome.debugger.sendCommand({ tabId }, "Network.enable", {}, resolve); }); chrome.debugger.onEvent.addListener(handleRequestIntercept); }); } export function stopDebugger(tabId?: string | number) { if (tabId) { chrome.debugger.detach({ tabId }); const index = potentialActiveTabs.indexOf(tabId); if (index > -1) potentialActiveTabs.splice(index, 1); } else { potentialActiveTabs.forEach((tabId) => { chrome.debugger.detach({ tabId }); }); potentialActiveTabs.length = 0; } } const getType = (requestType: string) => { switch (requestType.toLowerCase()) { case "fetch": case "xhr": case "xmlhttprequest": return 'xmlhttprequest' default: return requestType } } function handleRequestIntercept(source, method, params) { if (!source.tabId) return; const tabId = source.tabId; if (!requestMaps[tabId]) return; if (params.request && params.request.method === "OPTIONS") return; const reqId = `${tabId}_${params.requestId}`; switch (method) { case "Network.requestWillBeSent": requestMaps[tabId][reqId] = { encodedBodySize: 0, responseBodySize: 0, duration: 0, method: params.request.method, type: params.type ? getType(params.type) : "resource", statusCode: 0, url: params.request.url, body: params.request.postData || "", responseBody: "", fromCache: false, requestHeaders: params.request.headers || {}, responseHeaders: {}, timestamp: Date.now(), time: Date.now(), }; break; case "Network.responseReceived": if (!requestMaps[tabId][reqId]) return; requestMaps[tabId][reqId].statusCode = params.response.status; requestMaps[tabId][reqId].responseHeaders = params.response.headers || {}; if (params.response.fromDiskCache) requestMaps[tabId][reqId].fromCache = true; break; case "Network.dataReceived": if (!requestMaps[tabId][reqId]) return; requestMaps[tabId][reqId].encodedBodySize += params.dataLength; break; case "Network.loadingFinished": if (!requestMaps[tabId][reqId]) return; requestMaps[tabId][reqId].duration = Date.now() - requestMaps[tabId][reqId].timestamp; requestMaps[tabId][reqId].responseBodySize = requestMaps[tabId][reqId].encodedBodySize; chrome.debugger.sendCommand({ tabId }, "Network.getResponseBody", { requestId: params.requestId }, (res) => { if (!res || res.error) { requestMaps[tabId][reqId].error = res?.error || "Unknown"; } else { requestMaps[tabId][reqId].responseBody = res.base64Encoded ? 'base64 payload' : res.body; } }); break; case "Network.loadingFailed": if (!requestMaps[tabId][reqId]) return; requestMaps[tabId][reqId].error = params.errorText || "Unknown"; break; } } export function getRequests(tabId?: string) { if (tabId) { return Object.values(requestMaps[tabId] || {}); } return Object.values(requestMaps).reduce((acc, curr) => acc.concat(Object.values(curr)), []); }