rentgendroid/http_server/code/src/traffic_log.ts
Andrii Dokhniak e23cac512e upd
2025-08-05 11:27:44 +02:00

225 lines
5.6 KiB
TypeScript

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<string, Entry> = 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}
<br />`;
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;
}