import { SpotNetworkRequest, filterBody, filterHeaders, tryFilterUrl, TrackedRequest, } from "./networkTrackingUtils"; export const rawRequests: Array< TrackedRequest & { startTs: number; duration: number } > = []; function getRequestStatus(request: TrackedRequest): number { if (request.statusCode) return request.statusCode; if (request.error) return 0; return 200; } function modifyOnSpot(request: TrackedRequest) { const id = request.requestId; const index = rawRequests.findIndex((r) => r.requestId === id); const ts = Date.now(); const start = rawRequests[index]?.startTs ?? ts; rawRequests[index] = { ...rawRequests[index], ...request, duration: ts - start, }; } function trackOnBefore( details: browser.webRequest._OnBeforeRequestDetails & { reqBody?: string }, ) { if (details.method === "POST" && details.requestBody) { if (details.requestBody.formData) { details.reqBody = JSON.stringify(details.requestBody.formData); } else if (details.requestBody.raw) { const raw = details.requestBody.raw[0]?.bytes; if (raw) details.reqBody = new TextDecoder("utf-8").decode(raw); } } rawRequests.push({ ...details, startTs: Date.now(), duration: 0 }); } function trackOnHeaders( details: browser.webRequest._OnBeforeSendHeadersDetails, ) { modifyOnSpot(details); } function trackOnCompleted(details: browser.webRequest._OnCompletedDetails) { modifyOnSpot(details); } function trackOnError(details: browser.webRequest._OnErrorOccurredDetails) { modifyOnSpot(details); } // Build final SpotNetworkRequest objects function createSpotNetworkRequest( trackedRequest: TrackedRequest, trackedTab?: number, ): SpotNetworkRequest | undefined { if (trackedRequest.tabId === -1) return; if (trackedTab && trackedTab !== trackedRequest.tabId) return; if ( ["ping", "beacon", "image", "script", "font"].includes(trackedRequest.type) ) { if (!trackedRequest.statusCode || trackedRequest.statusCode < 400) return; } const type = ["stylesheet", "script", "image", "media", "font"].includes( trackedRequest.type, ) ? "resource" : trackedRequest.type; const requestHeaders = trackedRequest.requestHeaders ? filterHeaders(trackedRequest.requestHeaders) : {}; const responseHeaders = trackedRequest.responseHeaders ? filterHeaders(trackedRequest.responseHeaders) : {}; const reqSize = trackedRequest.reqBody ? trackedRequest.requestSize || trackedRequest.reqBody.length : 0; const status = getRequestStatus(trackedRequest); let body = ""; if (trackedRequest.reqBody) { try { body = filterBody(trackedRequest.reqBody); } catch (e) { body = "Error parsing body"; console.error(e); } } const request: SpotNetworkRequest = { method: trackedRequest.method, type, body, responseBody: "", requestHeaders, responseHeaders, time: trackedRequest.timeStamp, timestamp: trackedRequest.timeStamp, statusCode: status, error: trackedRequest.error, url: tryFilterUrl(trackedRequest.url), fromCache: trackedRequest.fromCache || false, encodedBodySize: reqSize, responseBodySize: trackedRequest.responseSize, duration: trackedRequest.duration, }; return request; } export function startTrackingNetwork() { rawRequests.length = 0; browser.webRequest.onBeforeRequest.addListener( trackOnBefore, { urls: [""] }, ["requestBody"], // allows capturing POST bodies ); browser.webRequest.onBeforeSendHeaders.addListener( trackOnHeaders, { urls: [""] }, ["requestHeaders"], ); browser.webRequest.onCompleted.addListener( trackOnCompleted, { urls: [""] }, ["responseHeaders"], ); browser.webRequest.onErrorOccurred.addListener(trackOnError, { urls: [""], }); } export function stopTrackingNetwork() { browser.webRequest.onBeforeRequest.removeListener(trackOnBefore); browser.webRequest.onBeforeSendHeaders.removeListener(trackOnHeaders); browser.webRequest.onCompleted.removeListener(trackOnCompleted); browser.webRequest.onErrorOccurred.removeListener(trackOnError); } export function getFinalRequests(tabId?: number): SpotNetworkRequest[] { const finalRequests = rawRequests .map((r) => createSpotNetworkRequest(r, tabId)) .filter((r) => r !== undefined) as SpotNetworkRequest[]; rawRequests.length = 0; return finalRequests; }