rentgendroid/http_server/code/src/trafficLog.tsx
2025-07-26 10:57:35 +02:00

254 lines
5.9 KiB
TypeScript

import { Entry, Har, PostData, Request, Response } from "har-format";
import { render, Component } from "preact";
type MyState = {
finished_entries: Entry[];
unfinished_entries: Map<string, Entry>;
};
class TrafficLog extends Component {
connection: WebSocket | undefined;
state: MyState = { finished_entries: [], unfinished_entries: new Map() };
constructor() {
super();
}
componentDidMount() {
// This should also be dynamic
this.connection = new WebSocket("ws://localhost:10001");
this.connection.onmessage = (msg) => {
this.process_msg(msg.data);
this.setState({
finished_entries: this.state.finished_entries,
unfinished_entries: this.state.unfinished_entries,
});
};
this.connection.onclose = this.connection.onerror = () => {
window.location.reload();
};
}
render() {
const download_har = () => {
var tempLink = document.createElement("a");
var taBlob = new Blob([JSON.stringify(this.export_har())], {
type: "text/plain",
});
tempLink.setAttribute("href", URL.createObjectURL(taBlob));
tempLink.setAttribute("download", `rentgendroid-capture.har`);
tempLink.click();
URL.revokeObjectURL(tempLink.href);
};
const inspect_har = async () => {
const req_body = {
har: this.export_har(),
private_data: [
[
"adid",
document.getElementById("adid_priv_info_table")!
.textContent,
],
[
"latitude",
document.getElementById("lat_priv_info_table")!
.textContent,
],
[
"longitude",
document.getElementById("lon_priv_info_table")!
.textContent,
],
],
};
const resp = await fetch("/inspect_har", {
method: "POST",
body: JSON.stringify(req_body),
});
const resp_text = await resp.text();
const newWindow = window.open();
newWindow?.document.write(resp_text);
newWindow?.document.close();
};
const contentWithLineBreaks = this.state.finished_entries.map((req) => {
return (
<span>
{req.request.url}
<br />
</span>
);
});
return (
<div>
<button onClick={download_har}>Download HAR</button>
<button onClick={inspect_har}>Inspect HAR</button>
<div>
<h2>stats: </h2>
<p>
Request + responce pairs:{" "}
{this.state.finished_entries.length}
</p>
<p>
Waiting for the responce:{" "}
{this.state.unfinished_entries.size}
</p>
</div>
<div>{contentWithLineBreaks}</div>
</div>
);
}
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)
this.process_req(obj.payload.data.requestReceived);
if (
obj.payload &&
obj.payload.data &&
obj.payload.data.responseCompleted
)
this.process_res(obj.payload.data.responseCompleted);
}
process_res(res: any) {
let entry = this.state.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;
this.state.unfinished_entries.delete(res.id);
this.state.finished_entries.push(entry);
}
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,
},
};
this.state.unfinished_entries.set(req.id, entry);
}
export_har(): Har {
let ret: Har = {
log: {
version: "1.2",
creator: {
name: "Rentgendroid",
version: "0.0.1",
},
entries: [
...this.state.finished_entries,
...this.state.unfinished_entries.values(),
],
},
};
return ret;
}
}
render(<TrafficLog />, document.getElementById("traffic-log")!);