import { Entry, Har, PostData, Request, Response } from "har-format"; import { adid_priv_info_table, lat_priv_info_table, lon_priv_info_table, } from "./shared"; // Request + responce pairs: 0 const req_res_pairs = document.getElementById("traffic-log-req-res-pairs")!; // Waiting for the responce: 0 const waiting = document.getElementById("traffic-log-waiting")!; const traffic_log_lines = document.getElementById("traffic-log-lines")!; const inspect_har_form: HTMLFormElement = document.getElementById( "inspect-har-form" )! as HTMLFormElement; const connection = new WebSocket("ws://localhost:10001"); const unfinished_entries: Map = new Map(); const finished_entries: Entry[] = []; export function start_traffic_log() { update_stats(); connection.onmessage = (msg) => { process_msg(msg.data); update_stats(); }; connection.onclose = connection.onerror = () => { window.location.reload(); }; } export function download_har() { var tempLink = document.createElement("a"); var taBlob = new Blob([JSON.stringify(export_har())], { type: "text/plain", }); tempLink.setAttribute("href", URL.createObjectURL(taBlob)); tempLink.setAttribute("download", `rentgendroid-capture.har`); tempLink.click(); URL.revokeObjectURL(tempLink.href); } function launch_window_with_post(url: string, data: any) { let form = document.createElement("form"); form.target = "_blank"; form.method = "POST"; form.action = url; form.enctype = "multipart/form-data" form.style.display = "none"; for (var key in data) { var input = document.createElement("input"); input.type = "hidden"; input.name = key; input.value = data[key]; form.appendChild(input); } document.body.appendChild(form); form.submit(); document.body.removeChild(form); } export async function inspect_har() { let data = { har: JSON.stringify(export_har()), private_data: JSON.stringify([ {desc: "adid", data: adid_priv_info_table.textContent}, {desc: "latitude", data: lat_priv_info_table.textContent }, {desc: "longitude", data: lon_priv_info_table.textContent }, ]), }; launch_window_with_post("/inspect_har", data); } function update_stats() { req_res_pairs.innerText = `Request + responce pairs: ${finished_entries.length}`; waiting.innerText = `Waiting for the responce: ${unfinished_entries.size}`; } function process_msg(s: string) { let obj = JSON.parse(s); console.log(obj); if (obj.type !== "data") return; if (obj.payload && obj.payload.data && obj.payload.data.requestReceived) process_req(obj.payload.data.requestReceived); if (obj.payload && obj.payload.data && obj.payload.data.responseCompleted) process_res(obj.payload.data.responseCompleted); } function process_res(res: any) { let entry = unfinished_entries.get(res.id)!; let content_type = "application/text"; let headers = JSON.parse(res.rawHeaders).map((header: [string, string]) => { if (header[0].toLowerCase() === "content-type") content_type = header[1]; return { name: header[0], value: header[1], comment: "" }; }); //'{"startTime":1751745139334, // "startTimestamp":347666.762487, // "bodyReceivedTimestamp":347667.529477, // "headersSentTimestamp":347906.038202, // "responseSentTimestamp":347906.616067}' let timing_events = JSON.parse(res.timingEvents); let start_ts = timing_events.startTimestamp; let got_headers_ts = timing_events.headersSentTimestamp; let end_ts = timing_events.responseSentTimestamp; let wait_time = got_headers_ts - start_ts; let recieve_time = end_ts - got_headers_ts; let response: Response = { status: res.statusCode, statusText: res.statusMessage, httpVersion: entry.request.httpVersion, cookies: [], headers, content: { size: 0, mimeType: content_type, text: res.body, encoding: "base64", }, redirectURL: "", headersSize: -1, bodySize: -1, }; entry.response = response; entry.timings.wait = wait_time; entry.timings.receive = recieve_time; unfinished_entries.delete(res.id); finished_entries.push(entry); let el = document.createElement("span"); el.innerHTML = ` ${entry.request.url}
`; traffic_log_lines.appendChild(el); } function process_req(req: any) { let content_type = "application/text"; let headers = JSON.parse(req.rawHeaders).map((header: [string, string]) => { if (header[0].toLowerCase() === "Content-Type") content_type = header[1]; return { name: header[0], value: header[1], comment: "" }; }); let timing_events = JSON.parse(req.timingEvents); let start_time: number = timing_events.startTime!; let start_datetime = new Date(start_time).toISOString(); let request: Request = { method: req.method, url: req.url, httpVersion: req.httpVersion, cookies: [], headers, queryString: [], postData: req.body ? ({ text: req.body, mimeType: content_type } as PostData) : undefined, headersSize: -1, bodySize: -1, comment: "", }; //'{"startTime":1751745139334,"startTimestamp":347666.762487,"bodyReceivedTimestamp":347667.529477,"headersSentTimestamp":347906.038202,"responseSentTimestamp":347906.616067}' let entry: Entry = { startedDateTime: start_datetime, time: 0, request: request, response: { status: 0, statusText: "", httpVersion: "", cookies: [], headers: [], content: { size: 0, mimeType: "", }, redirectURL: "", headersSize: 0, bodySize: 0, }, cache: {}, timings: { wait: 0, receive: 0, }, }; unfinished_entries.set(req.id, entry); } function export_har(): Har { let ret: Har = { log: { version: "1.2", creator: { name: "Rentgendroid", version: "0.0.1", }, entries: [...finished_entries, ...unfinished_entries.values()], }, }; return ret; }