upd
This commit is contained in:
parent
5b9866a8bd
commit
e23cac512e
@ -9,7 +9,7 @@ RUN node --version
|
||||
RUN git clone https://github.com/httptoolkit/httptoolkit-server /httptoolkit-server
|
||||
|
||||
WORKDIR /httptoolkit-server
|
||||
RUN git checkout 5c60a70b08d30126639484314f5b5619a388b026 \
|
||||
RUN git checkout 490b1b6f5180ad634b60997778c5f96b2f62bf0b \
|
||||
&& npm i && npm run build:src
|
||||
|
||||
# Set up proxy_cache_thing
|
||||
|
@ -73,12 +73,19 @@ io.on("connection", (socket) => {
|
||||
socket.on("back", async () => {
|
||||
if (gps_setting_in_progress) {
|
||||
send_notification(socket, false, "Interactions not allowed when setting gps coordinates", "");
|
||||
send_notification(socket, false, "Back", "");
|
||||
return ;
|
||||
}
|
||||
await spawnPromise("bash", ["/conf/back.sh"]);
|
||||
});
|
||||
|
||||
socket.on("recent", async () => {
|
||||
if (gps_setting_in_progress) {
|
||||
send_notification(socket, false, "Interactions not allowed when setting gps coordinates", "");
|
||||
return ;
|
||||
}
|
||||
await spawnPromise("bash", ["/conf/recent.sh"]);
|
||||
});
|
||||
|
||||
socket.on("home", async () => {
|
||||
if (gps_setting_in_progress) {
|
||||
send_notification(socket, false, "Interactions not allowed when setting gps coordinates", "");
|
||||
|
@ -1 +1 @@
|
||||
adb shell su root cat /data/data/com.google.android.gms/shared_prefs/adid_settings.xml | xmllint --xpath 'string(//map/string[@name="adid_key"])' -
|
||||
adb shell su root cat /data/data/com.google.android.gms/shared_prefs/adid_settings.xml | xmllint --xpath 'string(//map/string[@name="adid_key"])' - | tr -d $'\n'
|
||||
|
1
android/conf/recent.sh
Normal file
1
android/conf/recent.sh
Normal file
@ -0,0 +1 @@
|
||||
/opt/android-sdk-linux/platform-tools/adb shell input keyevent 187
|
@ -3,7 +3,7 @@ FROM node:22.14.0
|
||||
RUN git clone https://github.com/httptoolkit/httptoolkit-server
|
||||
|
||||
WORKDIR /httptoolkit-server
|
||||
RUN git checkout 5c60a70b08d30126639484314f5b5619a388b026 \
|
||||
RUN git checkout 490b1b6f5180ad634b60997778c5f96b2f62bf0b \
|
||||
&& npm i && npm run build:src
|
||||
|
||||
CMD /httptoolkit-server/bin/run start -c /certificates
|
||||
|
@ -2,5 +2,5 @@
|
||||
|
||||
npm i
|
||||
npm run build
|
||||
node --watch index.mjs
|
||||
#tail -f /dev/null
|
||||
node index.mjs &
|
||||
tail -f /dev/null
|
||||
|
@ -4,154 +4,8 @@
|
||||
<meta charset="UTF-8" />
|
||||
<title>Rentgen android</title>
|
||||
<script src="/htmx.js"></script>
|
||||
<style>
|
||||
main {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.log-section {
|
||||
height: auto;
|
||||
width: 400px;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.screen {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.screen-buttons {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-top: 5px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.screen-buttons button {
|
||||
font-size: 1.1rem;
|
||||
padding: 10px 20px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.screen-buttons button:hover {
|
||||
background-color: aqua;
|
||||
}
|
||||
|
||||
#clicks-log {
|
||||
font-family:
|
||||
Menlo,
|
||||
Consolas,
|
||||
Monaco,
|
||||
Liberation Mono,
|
||||
Lucida Console,
|
||||
monospace;
|
||||
}
|
||||
|
||||
.tab {
|
||||
border: 1px solid #ccc;
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
/* Style the buttons that are used to open the tab content */
|
||||
.tab button {
|
||||
background-color: inherit;
|
||||
float: left;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
padding: 14px 16px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
/* Change background color of buttons on hover */
|
||||
.tab button:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
/* Create an active/current tablink class */
|
||||
.tab button.active {
|
||||
background-color: #ccc;
|
||||
}
|
||||
.tabcontent.active {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* Style the tab content */
|
||||
.tabcontent {
|
||||
display: none;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-top: none;
|
||||
}
|
||||
html,
|
||||
body,
|
||||
main {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
}
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
}
|
||||
#logs-tab {
|
||||
overflow: auto;
|
||||
text-wrap: wrap;
|
||||
}
|
||||
.screen-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.screen-section #screen,
|
||||
.screen-section.screen_buttons {
|
||||
flex-grow: 0;
|
||||
}
|
||||
#screen {
|
||||
user-select: none;
|
||||
}
|
||||
.tab-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
#resp {
|
||||
display: none;
|
||||
}
|
||||
#upload_form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#upload_form button,
|
||||
#upload_form label {
|
||||
border: 2px solid #ccc;
|
||||
background-color: #f1f1f1;
|
||||
cursor: pointer;
|
||||
padding: 3px 10px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
#upload_form button:hover,
|
||||
#upload_form label:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
#notifications {
|
||||
width: 40%;
|
||||
margin-left: 60%;
|
||||
position: absolute;
|
||||
}
|
||||
#logs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
<script src="/main.js" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="notifications"></div>
|
||||
@ -168,6 +22,7 @@
|
||||
tabindex="0"
|
||||
/>
|
||||
<div class="screen-buttons" style="flex-grow: 0">
|
||||
<button class="screen-buttons-recent-apps">recent-apps</button>
|
||||
<button class="screen-buttons-home">home</button>
|
||||
<button class="screen-buttons-back">back</button>
|
||||
</div>
|
||||
@ -196,14 +51,17 @@
|
||||
<div class="tab">
|
||||
<button
|
||||
class="tablinks active"
|
||||
onclick="open_tab(event, 'httptoolkit-tab')"
|
||||
onclick="main.open_tab(event, 'httptoolkit-tab')"
|
||||
>
|
||||
HttpToolkit UI
|
||||
</button>
|
||||
<button class="tablinks" onclick="open_tab(event, 'logs-tab')">
|
||||
<button class="tablinks" onclick="main.open_tab(event, 'logs-tab')">
|
||||
Logs
|
||||
</button>
|
||||
<button class="tablinks" onclick="open_tab(event, 'controls-tab')">
|
||||
<button
|
||||
class="tablinks"
|
||||
onclick="main.open_tab(event, 'controls-tab')"
|
||||
>
|
||||
Device Controls
|
||||
</button>
|
||||
</div>
|
||||
@ -211,7 +69,23 @@
|
||||
<div class="tabcontent" id="logs-tab">
|
||||
<div id="logs">
|
||||
<p id="clicks-log" class="log-section"></p>
|
||||
<p id="traffic-log" class="log-section"></p>
|
||||
<div id="traffic-log" class="log-section">
|
||||
<div>
|
||||
<button onClick="main.download_har()">Download HAR</button>
|
||||
<button onClick="main.inspect_har()">Inspect HAR</button>
|
||||
<div>
|
||||
<h2>stats:</h2>
|
||||
<p id="traffic-log-req-res-pairs">
|
||||
Request + responce pairs: 0
|
||||
</p>
|
||||
<p id="traffic-log-waiting">
|
||||
Waiting for the responce: 0
|
||||
</p>
|
||||
</div>
|
||||
<div id="traffic-log-lines">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -224,7 +98,7 @@
|
||||
></iframe>
|
||||
</div>
|
||||
<div class="tabcontent" id="controls-tab">
|
||||
<form id="set_coords" onsubmit="coords_handler(event)">
|
||||
<form id="set_coords" onsubmit="main.coords_handler(event)">
|
||||
<label>
|
||||
Latitude:
|
||||
<input type="text" name="lat" />
|
||||
@ -235,7 +109,9 @@
|
||||
</label>
|
||||
<button type="submit">Submit coords</button>
|
||||
</form>
|
||||
<button id="reset_adid_btn" onclick="reset_adid_handler(event)">Reset ADID</button>
|
||||
<button id="reset_adid_btn" onclick="main.reset_adid_handler(event)">
|
||||
Reset ADID
|
||||
</button>
|
||||
<table>
|
||||
<thead></thead>
|
||||
<tbody>
|
||||
@ -256,219 +132,5 @@
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="/socket.io.js"></script>
|
||||
<script>
|
||||
var socket = io();
|
||||
|
||||
function reset_adid_handler(e) {
|
||||
socket.emit("reset_adid");
|
||||
}
|
||||
|
||||
function coords_handler(e) {
|
||||
e.preventDefault();
|
||||
const form_data = new FormData(e.target);
|
||||
console.log(form_data);
|
||||
socket.emit("setcoord", {
|
||||
lon: Number.parseFloat(form_data.get("lon")),
|
||||
lat: Number.parseFloat(form_data.get("lat")),
|
||||
});
|
||||
}
|
||||
|
||||
function open_tab(evt, tab_name) {
|
||||
let i, tabcontent, tablinks;
|
||||
|
||||
// Get all elements with class="tabcontent" and hide them
|
||||
tabcontent = document.getElementsByClassName("tabcontent");
|
||||
for (i = 0; i < tabcontent.length; i++) {
|
||||
if (tabcontent[i].id != tab_name) {
|
||||
tabcontent[i].classList.remove("active");
|
||||
} else {
|
||||
tabcontent[i].classList.add("active");
|
||||
}
|
||||
}
|
||||
|
||||
// Get all elements with class="tablinks" and remove the class "active"
|
||||
tablinks = document.getElementsByClassName("tablinks");
|
||||
for (i = 0; i < tablinks.length; i++) {
|
||||
tablinks[i].classList.remove("active");
|
||||
}
|
||||
|
||||
// Show the current tab, and add an "active" class to the button that opened the tab
|
||||
evt.currentTarget.classList.add("active");
|
||||
}
|
||||
var screen = document.getElementById("screen");
|
||||
var clicksLog = document.getElementById("clicks-log");
|
||||
const homeButton = document.querySelector(".screen-buttons-home");
|
||||
const backButton = document.querySelector(".screen-buttons-back");
|
||||
|
||||
let lastTouch = new Date().getTime();
|
||||
|
||||
const calculateElapsedTime = (last) => {
|
||||
const currentTouch = new Date().getTime();
|
||||
const elapsedTime = currentTouch - lastTouch;
|
||||
const elapsedSec = Math.round(elapsedTime / 1000);
|
||||
lastTouch = currentTouch;
|
||||
return elapsedSec;
|
||||
};
|
||||
|
||||
const waitToLog = (clickInfoText) => {
|
||||
const clickInfo = document.createElement("span");
|
||||
const waitInfo = document.createElement("span");
|
||||
waitInfo.textContent = `await wait(${calculateElapsedTime(
|
||||
lastTouch
|
||||
)});`;
|
||||
clicksLog.appendChild(waitInfo);
|
||||
clickInfo.textContent = clickInfoText;
|
||||
clicksLog.appendChild(clickInfo);
|
||||
};
|
||||
|
||||
const registerClick = ({ path, logText, body }) => {
|
||||
const clicksLog = document.getElementById("clicks-log");
|
||||
const span = document.createElement("span");
|
||||
|
||||
waitToLog(logText);
|
||||
socket.emit(path, body ? body : {});
|
||||
};
|
||||
|
||||
homeButton.addEventListener("click", () =>
|
||||
registerClick({ path: "home", logText: "await homeButton();" })
|
||||
);
|
||||
|
||||
backButton.addEventListener("click", () =>
|
||||
registerClick({ path: "back", logText: "await backButton();" })
|
||||
);
|
||||
|
||||
socket.on("screenshot_data", (data) => {
|
||||
try {
|
||||
const blob = new Blob([data]);
|
||||
screen.src = URL.createObjectURL(blob);
|
||||
} catch (error) {
|
||||
console.error("Error fetching image: ", error);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("private_info", (data) => {
|
||||
console.log("private_info");
|
||||
adid_priv_info_table.textContent = data.adid;
|
||||
lat_priv_info_table.textContent = data.latitude;
|
||||
lon_priv_info_table.textContent = data.longitude;
|
||||
});
|
||||
|
||||
socket.emit("private_info_req");
|
||||
|
||||
socket.onAny((ev, ...args) => {
|
||||
console.log("ev: ", ev, args);
|
||||
});
|
||||
|
||||
async function displayImage() {
|
||||
socket.emit("screenshot");
|
||||
}
|
||||
|
||||
let isDragging = false;
|
||||
const screenSize = [320, 640];
|
||||
|
||||
function calcMousePos(event) {
|
||||
let rect = screen.getBoundingClientRect();
|
||||
let x = ((event.clientX - rect.left) / rect.width) * screenSize[0];
|
||||
let y = ((event.clientY - rect.top) / rect.height) * screenSize[1];
|
||||
x = Math.min(Math.max(x, 0), screenSize[0]);
|
||||
y = Math.min(Math.max(y, 0), screenSize[1]);
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
screen.addEventListener(
|
||||
"mousemove",
|
||||
(event) => {
|
||||
if (!isDragging) return;
|
||||
let pos = calcMousePos(window.event);
|
||||
|
||||
if (isDragging) {
|
||||
registerClick({
|
||||
path: "motionevent",
|
||||
logText: `await motionevent({motionType: "MOVE", x:${pos.x},y:${pos.y}}});`,
|
||||
body: {
|
||||
motionType: "MOVE",
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
const handleDraggStart = (event) => {
|
||||
isDragging = true;
|
||||
let pos = calcMousePos(event);
|
||||
registerClick({
|
||||
path: "motionevent",
|
||||
logText: `await motionevent({motionType: "DOWN", x:${pos.x},y:${pos.y}}});`,
|
||||
body: {
|
||||
motionType: "DOWN",
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
screen.addEventListener("mousedown", handleDraggStart);
|
||||
|
||||
document.addEventListener("mouseup", (e) => {
|
||||
if (!isDragging) return;
|
||||
isDragging = false;
|
||||
let pos = calcMousePos(e);
|
||||
registerClick({
|
||||
path: "motionevent",
|
||||
logText: `await motionevent({motionType: "MOVE", x:${pos.x},y:${pos.y}}});`,
|
||||
body: {
|
||||
motionType: "MOVE",
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
},
|
||||
});
|
||||
registerClick({
|
||||
path: "motionevent",
|
||||
logText: `await motionevent({motionType: "UP", x:${pos.x},y:${pos.y}}});`,
|
||||
body: {
|
||||
motionType: "UP",
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener("keydown", (event) => {
|
||||
let key = event.key;
|
||||
if (key === "Space") key = " ";
|
||||
else if (key !== "Enter" && key !== "Backspace" && key.length !== 1)
|
||||
return;
|
||||
console.log(event.key, key);
|
||||
if (document.getElementById("screen").matches(":hover")) {
|
||||
registerClick({
|
||||
path: "key",
|
||||
logText: `await key(${event.key});`,
|
||||
body: { key },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
async function sleep(time) {
|
||||
return new Promise((resolve) => setTimeout(resolve, time));
|
||||
}
|
||||
|
||||
async function screenshot_loop() {
|
||||
var before;
|
||||
|
||||
while (true) {
|
||||
before = performance.now();
|
||||
await displayImage();
|
||||
while (performance.now() - before < ___screenshotDelayMs___)
|
||||
await sleep(50);
|
||||
}
|
||||
}
|
||||
screenshot_loop();
|
||||
</script>
|
||||
<script src="/trafficLog.js"></script>
|
||||
<script src="/notifications.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -4,11 +4,14 @@ import { execSync } from "node:child_process";
|
||||
import { Server } from "socket.io";
|
||||
|
||||
import { io } from "socket.io-client";
|
||||
import { build_html } from "./har-analyzer/build_html.js"
|
||||
import { build_html } from "har-analyzer"
|
||||
|
||||
import fileUpload from "express-fileupload";
|
||||
|
||||
const app = express();
|
||||
import multer from "multer"
|
||||
|
||||
const upload = multer();
|
||||
|
||||
async function sleep(time) {
|
||||
return new Promise((resolve) => setTimeout(resolve, time));
|
||||
@ -60,14 +63,15 @@ app.get("/", async function (req, res) {
|
||||
//POST
|
||||
app.use(express.text({limit: "100mb"}));
|
||||
|
||||
app.post("/inspect_har", function (req, res) {
|
||||
let body = JSON.parse(req.body);
|
||||
let har = JSON.stringify(body.har);
|
||||
app.post("/inspect_har", upload.none(), function (req, res) {
|
||||
let body = req.body;
|
||||
let har = body.har;
|
||||
let private_data;
|
||||
if (body.private_data)
|
||||
private_data = body.private_data;
|
||||
private_data = JSON.parse(body.private_data);
|
||||
res.setHeader("Content-Type", "text/html");
|
||||
res.send(build_html(har, private_data, "/code/har-analyzer/"));
|
||||
console.log(private_data);
|
||||
res.send(build_html(har, private_data));
|
||||
});
|
||||
|
||||
app.use(fileUpload());
|
||||
|
174
http_server/code/package-lock.json
generated
174
http_server/code/package-lock.json
generated
@ -8,7 +8,9 @@
|
||||
"@types/har-format": "^1.2.16",
|
||||
"express": "^4.18.2",
|
||||
"express-fileupload": "^1.5.1",
|
||||
"har-analyzer": "git+ssh://git@hub.sealcode.org/diffusion/171/har-parser.git#master",
|
||||
"htmx.org": "^1.9.12",
|
||||
"multer": "^2.0.2",
|
||||
"preact": "^10.18.1",
|
||||
"socket.io": "^4.8.1",
|
||||
"socket.io-client": "^4.8.1",
|
||||
@ -370,6 +372,18 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/util-hex-encoding": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz",
|
||||
"integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||
@ -412,11 +426,37 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/append-field": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
|
||||
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base64id": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||
@ -449,6 +489,12 @@
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
@ -480,6 +526,21 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
|
||||
"engines": [
|
||||
"node >= 6.0"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.0.2",
|
||||
"typedarray": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
@ -857,6 +918,18 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/har-analyzer": {
|
||||
"name": "module-starter",
|
||||
"version": "0.0.1",
|
||||
"resolved": "git+ssh://git@hub.sealcode.org/diffusion/171/har-parser.git#167d6bce983a30ac9699474c26ec6b0222a7309e",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@smithy/util-hex-encoding": "^4.0.0",
|
||||
"base64-js": "^1.5.1",
|
||||
"pako": "^2.1.0",
|
||||
"tabulator-tables": "^6.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
@ -986,11 +1059,50 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/multer": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz",
|
||||
"integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"append-field": "^1.0.0",
|
||||
"busboy": "^1.6.0",
|
||||
"concat-stream": "^2.0.0",
|
||||
"mkdirp": "^0.5.6",
|
||||
"object-assign": "^4.1.1",
|
||||
"type-is": "^1.6.18",
|
||||
"xtend": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
@ -1027,6 +1139,12 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
||||
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
@ -1097,6 +1215,20 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
@ -1366,6 +1498,21 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tabulator-tables": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tabulator-tables/-/tabulator-tables-6.3.1.tgz",
|
||||
"integrity": "sha512-qFW7kfadtcaISQIibKAIy0f3eeIXUVi8242Vly1iJfMD79kfEGzfczNuPBN/80hDxHzQJXYbmJ8VipI40hQtfA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
@ -1374,6 +1521,12 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
@ -1386,6 +1539,12 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/typedarray": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
@ -1400,6 +1559,12 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
@ -1443,6 +1608,15 @@
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,18 @@
|
||||
{
|
||||
"scripts": {
|
||||
"build": "esbuild --sourcemap --bundle src/trafficLog.tsx src/notifications.jsx --outdir=dist/ --jsx-factory=h --jsx-fragment=Fragment"
|
||||
"build": "esbuild --format=esm --sourcemap --bundle src/main.ts --outdir=dist/ --jsx-factory=h --jsx-fragment=Fragment"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/har-format": "^1.2.16",
|
||||
"express": "^4.18.2",
|
||||
"express-fileupload": "^1.5.1",
|
||||
"htmx.org": "^1.9.12",
|
||||
"multer": "^2.0.2",
|
||||
"preact": "^10.18.1",
|
||||
"socket.io": "^4.8.1",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"ws": "^8.18.0"
|
||||
"ws": "^8.18.0",
|
||||
"har-analyzer": "git+ssh://git@hub.sealcode.org/diffusion/171/har-parser.git#master"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.19.5"
|
||||
|
232
http_server/code/src/main.ts
Normal file
232
http_server/code/src/main.ts
Normal file
@ -0,0 +1,232 @@
|
||||
// make the export accessible from inline js
|
||||
import * as main from "./main";
|
||||
// for some reason doing the same with the window object doesn't work
|
||||
(globalThis as any).main = main;
|
||||
|
||||
import {
|
||||
backButton,
|
||||
clicksLog,
|
||||
homeButton,
|
||||
socket,
|
||||
screen,
|
||||
adid_priv_info_table,
|
||||
lat_priv_info_table,
|
||||
lon_priv_info_table,
|
||||
recentButton,
|
||||
} from "./shared";
|
||||
|
||||
import { start_notifications } from "./notifications";
|
||||
import { start_traffic_log } from "./traffic_log";
|
||||
|
||||
export { download_har, inspect_har } from "./traffic_log";
|
||||
|
||||
export function reset_adid_handler(_: Event) {
|
||||
socket.emit("reset_adid");
|
||||
}
|
||||
|
||||
export function coords_handler(e: FormDataEvent) {
|
||||
e.preventDefault();
|
||||
const form_data = new FormData(e.target as HTMLFormElement);
|
||||
socket.emit("setcoord", {
|
||||
lon: Number.parseFloat(form_data.get("lon") as string),
|
||||
lat: Number.parseFloat(form_data.get("lat") as string),
|
||||
});
|
||||
}
|
||||
|
||||
export function open_tab(evt: Event, tab_name: string) {
|
||||
let i, tabcontent, tablinks;
|
||||
|
||||
// Get all elements with class="tabcontent" and hide them
|
||||
tabcontent = document.getElementsByClassName("tabcontent");
|
||||
for (i = 0; i < tabcontent.length; i++) {
|
||||
if (tabcontent[i].id != tab_name) {
|
||||
tabcontent[i].classList.remove("active");
|
||||
} else {
|
||||
tabcontent[i].classList.add("active");
|
||||
}
|
||||
}
|
||||
|
||||
// Get all elements with class="tablinks" and remove the class "active"
|
||||
tablinks = document.getElementsByClassName("tablinks");
|
||||
for (i = 0; i < tablinks.length; i++) {
|
||||
tablinks[i].classList.remove("active");
|
||||
}
|
||||
|
||||
// Show the current tab, and add an "active" class to the button that opened the tab
|
||||
(evt.currentTarget as HTMLElement).classList.add("active");
|
||||
}
|
||||
|
||||
let lastTouch = new Date().getTime();
|
||||
|
||||
export const calculateElapsedTime = () => {
|
||||
const currentTouch = new Date().getTime();
|
||||
const elapsedTime = currentTouch - lastTouch;
|
||||
const elapsedSec = Math.round(elapsedTime / 1000);
|
||||
lastTouch = currentTouch;
|
||||
return elapsedSec;
|
||||
};
|
||||
|
||||
export const waitToLog = (clickInfoText: string) => {
|
||||
const clickInfo = document.createElement("span");
|
||||
const waitInfo = document.createElement("span");
|
||||
waitInfo.textContent = `await wait(${calculateElapsedTime()});`;
|
||||
clicksLog.appendChild(waitInfo);
|
||||
clickInfo.textContent = clickInfoText;
|
||||
clicksLog.appendChild(clickInfo);
|
||||
};
|
||||
|
||||
export const registerClick = ({
|
||||
path,
|
||||
logText,
|
||||
body,
|
||||
}: {
|
||||
path: string;
|
||||
logText: string;
|
||||
body?: any;
|
||||
}) => {
|
||||
waitToLog(logText);
|
||||
socket.emit(path, body ? body : {});
|
||||
};
|
||||
|
||||
homeButton.addEventListener("click", () =>
|
||||
registerClick({ path: "home", logText: "await homeButton();" })
|
||||
);
|
||||
|
||||
backButton.addEventListener("click", () =>
|
||||
registerClick({ path: "back", logText: "await backButton();" })
|
||||
);
|
||||
|
||||
recentButton.addEventListener("click", () =>
|
||||
registerClick({ path: "recent", logText: "await recentButton();" })
|
||||
);
|
||||
|
||||
socket.on("screenshot_data", (data) => {
|
||||
try {
|
||||
const blob = new Blob([data]);
|
||||
screen.src = URL.createObjectURL(blob);
|
||||
} catch (error) {
|
||||
console.error("Error fetching image: ", error);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("private_info", (data) => {
|
||||
console.log("private_info");
|
||||
adid_priv_info_table.textContent = data.adid;
|
||||
lat_priv_info_table.textContent = data.latitude;
|
||||
lon_priv_info_table.textContent = data.longitude;
|
||||
});
|
||||
|
||||
socket.emit("private_info_req");
|
||||
|
||||
socket.onAny((ev, ...args) => {
|
||||
console.log("ev: ", ev, args);
|
||||
});
|
||||
|
||||
async function displayImage() {
|
||||
socket.emit("screenshot");
|
||||
}
|
||||
|
||||
let isDragging = false;
|
||||
const screenSize = [320, 640];
|
||||
|
||||
export function calcMousePos(event: MouseEvent) {
|
||||
let rect = screen.getBoundingClientRect();
|
||||
let x = ((event.clientX - rect.left) / rect.width) * screenSize[0];
|
||||
let y = ((event.clientY - rect.top) / rect.height) * screenSize[1];
|
||||
x = Math.min(Math.max(x, 0), screenSize[0]);
|
||||
y = Math.min(Math.max(y, 0), screenSize[1]);
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
screen.addEventListener(
|
||||
"mousemove",
|
||||
(event) => {
|
||||
if (!isDragging) return;
|
||||
let pos = calcMousePos(event);
|
||||
|
||||
if (isDragging) {
|
||||
registerClick({
|
||||
path: "motionevent",
|
||||
logText: `await motionevent({motionType: "MOVE", x:${pos.x},y:${pos.y}}});`,
|
||||
body: {
|
||||
motionType: "MOVE",
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
export const handleDraggStart = (event: MouseEvent) => {
|
||||
isDragging = true;
|
||||
let pos = calcMousePos(event);
|
||||
registerClick({
|
||||
path: "motionevent",
|
||||
logText: `await motionevent({motionType: "DOWN", x:${pos.x},y:${pos.y}}});`,
|
||||
body: {
|
||||
motionType: "DOWN",
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
screen.addEventListener("mousedown", handleDraggStart);
|
||||
|
||||
document.addEventListener("mouseup", (e) => {
|
||||
if (!isDragging) return;
|
||||
isDragging = false;
|
||||
let pos = calcMousePos(e);
|
||||
registerClick({
|
||||
path: "motionevent",
|
||||
logText: `await motionevent({motionType: "MOVE", x:${pos.x},y:${pos.y}}});`,
|
||||
body: {
|
||||
motionType: "MOVE",
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
},
|
||||
});
|
||||
registerClick({
|
||||
path: "motionevent",
|
||||
logText: `await motionevent({motionType: "UP", x:${pos.x},y:${pos.y}}});`,
|
||||
body: {
|
||||
motionType: "UP",
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener("keydown", (event) => {
|
||||
let key = event.key;
|
||||
if (key === "Space") key = " ";
|
||||
else if (key !== "Enter" && key !== "Backspace" && key.length !== 1) return;
|
||||
console.log(event.key, key);
|
||||
if (screen.matches(":hover")) {
|
||||
registerClick({
|
||||
path: "key",
|
||||
logText: `await key(${event.key});`,
|
||||
body: { key },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export async function sleep(time: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, time));
|
||||
}
|
||||
|
||||
async function screenshot_loop() {
|
||||
var before;
|
||||
|
||||
while (true) {
|
||||
before = performance.now();
|
||||
await displayImage();
|
||||
// TODO: Make this dynamic again
|
||||
while (performance.now() - before < 100) await sleep(50);
|
||||
}
|
||||
}
|
||||
screenshot_loop();
|
||||
start_notifications();
|
||||
start_traffic_log();
|
@ -1,67 +0,0 @@
|
||||
import { render, Component } from "preact";
|
||||
import { io } from "socket.io-client";
|
||||
|
||||
function rand_num() {
|
||||
return Math.floor(Math.random() * Number.MAX_VALUE);
|
||||
}
|
||||
|
||||
class Notifications extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = { notifications: [] };
|
||||
}
|
||||
|
||||
remove_notification(id) {
|
||||
const newNotifications = this.state.notifications.filter(
|
||||
(notification) => notification.id !== id
|
||||
);
|
||||
this.setState({ notifications: newNotifications });
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// This should also be dynamic
|
||||
|
||||
io().on("notification", (data) => {
|
||||
let new_id = rand_num();
|
||||
this.setState({
|
||||
notifications: [
|
||||
{ id: new_id, notification: data },
|
||||
...this.state.notifications,
|
||||
],
|
||||
});
|
||||
// a 10 sec timeout
|
||||
setTimeout(() => {
|
||||
this.remove_notification(new_id);
|
||||
}, 10000);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.state.notifications.map(({ id, notification }) => (
|
||||
<div
|
||||
onClick={() => this.remove_notification(id)}
|
||||
style={`
|
||||
background-color: ${notification.is_ok ? "#66ff99" : "#ff5c33"};
|
||||
border-radius: 5px;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border-color: ${notification.is_ok ? "#369648" : "#a23915"};
|
||||
padding: 5px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
`}
|
||||
>
|
||||
<div>
|
||||
<b>{notification.context}</b>
|
||||
</div>
|
||||
<div>
|
||||
{notification.message.split("\n").map((line) => (
|
||||
<p>{line}</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
render(<Notifications />, document.getElementById("notifications"));
|
41
http_server/code/src/notifications.ts
Normal file
41
http_server/code/src/notifications.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import * as shared from "./shared";
|
||||
|
||||
const notifications = document.getElementById("notifications")!;
|
||||
|
||||
function create_notification(notification: {
|
||||
is_ok: boolean;
|
||||
message: string;
|
||||
context: string;
|
||||
}): HTMLElement {
|
||||
const el = document.createElement("div");
|
||||
|
||||
el.addEventListener("click", (_) => {
|
||||
el.remove();
|
||||
});
|
||||
|
||||
el.classList.add("notification");
|
||||
if (notification.is_ok) el.classList.add("notification_ok");
|
||||
else el.classList.add("notification_err");
|
||||
|
||||
// el.innerText = notification.message;
|
||||
el.innerHTML = `
|
||||
<div><b>${notification.context}</b></div>
|
||||
<div>
|
||||
${notification.message
|
||||
.split("\n")
|
||||
.map((line) => `<p>${line}</p>`)
|
||||
.join("")}
|
||||
</div>`;
|
||||
return el;
|
||||
}
|
||||
|
||||
export function start_notifications() {
|
||||
shared.socket.on("notification", (data) => {
|
||||
let el = create_notification(data);
|
||||
notifications.appendChild(el);
|
||||
// a 10 sec timeout
|
||||
setTimeout(() => {
|
||||
el.remove();
|
||||
}, 10000);
|
||||
});
|
||||
}
|
15
http_server/code/src/shared.ts
Normal file
15
http_server/code/src/shared.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { io } from "socket.io-client";
|
||||
|
||||
export const screen = document.getElementById("screen")! as HTMLImageElement;
|
||||
|
||||
export const clicksLog = document.getElementById("clicks-log")!;
|
||||
|
||||
export const recentButton = document.querySelector(".screen-buttons-recent-apps")!;
|
||||
export const homeButton = document.querySelector(".screen-buttons-home")!;
|
||||
export const backButton = document.querySelector(".screen-buttons-back")!;
|
||||
|
||||
export const adid_priv_info_table = document.getElementById("adid_priv_info_table")!;
|
||||
export const lat_priv_info_table = document.getElementById("lat_priv_info_table")!;
|
||||
export const lon_priv_info_table = document.getElementById("lon_priv_info_table")!;
|
||||
|
||||
export const socket = io();
|
@ -1,253 +0,0 @@
|
||||
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")!);
|
224
http_server/code/src/traffic_log.ts
Normal file
224
http_server/code/src/traffic_log.ts
Normal file
@ -0,0 +1,224 @@
|
||||
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;
|
||||
}
|
167
http_server/code/styles.css
Normal file
167
http_server/code/styles.css
Normal file
@ -0,0 +1,167 @@
|
||||
main {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.log-section {
|
||||
height: auto;
|
||||
width: 400px;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.screen {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.screen-buttons {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-top: 5px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.screen-buttons button {
|
||||
font-size: 1.1rem;
|
||||
padding: 10px 20px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.screen-buttons button:hover {
|
||||
background-color: aqua;
|
||||
}
|
||||
|
||||
#clicks-log {
|
||||
font-family:
|
||||
Menlo,
|
||||
Consolas,
|
||||
Monaco,
|
||||
Liberation Mono,
|
||||
Lucida Console,
|
||||
monospace;
|
||||
}
|
||||
|
||||
.tab {
|
||||
border: 1px solid #ccc;
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
/* Style the buttons that are used to open the tab content */
|
||||
.tab button {
|
||||
background-color: inherit;
|
||||
float: left;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
padding: 14px 16px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
/* Change background color of buttons on hover */
|
||||
.tab button:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
/* Create an active/current tablink class */
|
||||
.tab button.active {
|
||||
background-color: #ccc;
|
||||
}
|
||||
.tabcontent.active {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* Style the tab content */
|
||||
.tabcontent {
|
||||
display: none;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-top: none;
|
||||
}
|
||||
html,
|
||||
body,
|
||||
main {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
}
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
}
|
||||
#logs-tab {
|
||||
overflow: auto;
|
||||
text-wrap: wrap;
|
||||
}
|
||||
.screen-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.screen-section #screen,
|
||||
.screen-section.screen_buttons {
|
||||
flex-grow: 0;
|
||||
}
|
||||
#screen {
|
||||
user-select: none;
|
||||
}
|
||||
.tab-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
#resp {
|
||||
display: none;
|
||||
}
|
||||
#upload_form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#upload_form button,
|
||||
#upload_form label {
|
||||
border: 2px solid #ccc;
|
||||
background-color: #f1f1f1;
|
||||
cursor: pointer;
|
||||
padding: 3px 10px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
#upload_form button:hover,
|
||||
#upload_form label:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
#notifications {
|
||||
width: 40%;
|
||||
margin-left: 60%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.notification {
|
||||
border-radius: 5px;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
padding: 5px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.notification_ok {
|
||||
background-color: #66ff99;
|
||||
border-color: #369648;
|
||||
}
|
||||
|
||||
.notification_err {
|
||||
background-color: #ff5c33;
|
||||
border-color: #a23915;
|
||||
}
|
||||
|
||||
#logs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
15
http_server/code/test.html
Normal file
15
http_server/code/test.html
Normal file
@ -0,0 +1,15 @@
|
||||
<script type="text/javascript">
|
||||
function randomlinks(){
|
||||
var myrandom=Math.round(Math.random()*3)
|
||||
var links=new Array()
|
||||
links[0]="https://www.DemiCardGame.com"
|
||||
links[1]="https://www.games.xyphienllc.com"
|
||||
links[2]="https://www.htcg.news"
|
||||
links[3]="https://www.eTableCon.com"
|
||||
|
||||
window.open(links[myrandom], '_blank');
|
||||
}
|
||||
</script>
|
||||
<form>
|
||||
<input type="button" value="random link!" onClick="randomlinks()">
|
||||
</form>
|
@ -1,12 +1,15 @@
|
||||
FROM node:20.18.1
|
||||
|
||||
# Set up node
|
||||
RUN npm install -g n && n install 22.14.0 && n use 22.14.0 && hash -r
|
||||
|
||||
# If iproute2 is not installed,
|
||||
# node can't figure out what address to bind 0.0.0.0 to
|
||||
RUN apt-get update && apt-get install iproute2 python3 --yes \
|
||||
&& git clone https://github.com/httptoolkit/httptoolkit-ui
|
||||
|
||||
WORKDIR httptoolkit-ui
|
||||
RUN git checkout a0dbb8e1cd38b346fe411b03de0c5191ff06c669 \
|
||||
RUN git checkout ac44f8e6e1f5f41be988a32eb89c98f57723d005 \
|
||||
&& npm i && npm run server:setup
|
||||
|
||||
ARG DOCKER_HOST=localhost
|
||||
|
1
pre_android/preconf/AdIdreader/.idea/misc.xml
generated
1
pre_android/preconf/AdIdreader/.idea/misc.xml
generated
@ -1,4 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
|
Loading…
x
Reference in New Issue
Block a user