openreplay/spot/utils/networkTracking.ts

157 lines
4.4 KiB
TypeScript

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: ["<all_urls>"] },
["requestBody"], // allows capturing POST bodies
);
browser.webRequest.onBeforeSendHeaders.addListener(
trackOnHeaders,
{ urls: ["<all_urls>"] },
["requestHeaders"],
);
browser.webRequest.onCompleted.addListener(
trackOnCompleted,
{ urls: ["<all_urls>"] },
["responseHeaders"],
);
browser.webRequest.onErrorOccurred.addListener(trackOnError, {
urls: ["<all_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;
}