254 lines
5.9 KiB
TypeScript
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")!);
|