Page MenuHomeSealhub

No OneTemporary

diff --git a/android/code/index.mjs b/android/code/index.mjs
index 7ae0ad8..11531b5 100644
--- a/android/code/index.mjs
+++ b/android/code/index.mjs
@@ -1,51 +1,60 @@
import { WebSocketServer } from "ws";
import child_process from "child_process";
import fs from "fs";
+import { send_notification } from "./notifications.mjs"
async function spawnPromise(program, args) {
return new Promise((resolve, reject) => {
+ let output = "";
const process = child_process.spawn(program, args);
- process.on("close", (_) => {
- resolve();
+ process.stdout.on('data', (data) => {
+ output += data;
+ });
+ process.stderr.on('data', (data) => {
+ output += data;
+ });
+ process.on("close", (code) => {
+ resolve({output, code});
});
});
}
const wss = new WebSocketServer({ port: 3000 });
//maybe check output of child processes and send errors in some way
wss.on("connection", (ws) => {
ws.on("message", async (dataBuf) => {
let data = dataBuf.toString();
if (data === "screenshot") {
await spawnPromise("bash", ["/conf/screenshot.sh"]);
ws.send(fs.readFileSync("/screenshot.png"));
} else if (data.includes("touch")) {
const dataSplit = data.split(" ");
await spawnPromise("bash", [
"/conf/touch.sh",
dataSplit[1],
dataSplit[2],
]);
} else if (data === "back") {
await spawnPromise("bash", ["/conf/back.sh"]);
} else if (data === "home") {
await spawnPromise("bash", ["/conf/home.sh"]);
} else if (data === "install") {
- await spawnPromise("bash", ["/conf/install.sh"]);
+ const res = await spawnPromise("bash", ["/conf/install.sh"]);
+ send_notification(res.code === 0, "Installing the application", res.output);
} else if (data.includes("drag")) {
const dataSplit = data.split(" ");
await spawnPromise("bash", [
"/conf/drag.sh",
dataSplit[1],
dataSplit[2],
dataSplit[3],
dataSplit[4],
]);
}
});
ws.on("close", (_) => {
ws.close();
});
});
diff --git a/android/code/notifications.mjs b/android/code/notifications.mjs
new file mode 100644
index 0000000..200d5b2
--- /dev/null
+++ b/android/code/notifications.mjs
@@ -0,0 +1,36 @@
+import { WebSocket } from "ws";
+import { WebSocketServer } from "ws";
+
+const notification_proxy = new WebSocketServer({ port: 3001 });
+let notification_subs = [];
+
+notification_proxy.on('connection', (ws) => {
+ notification_subs.push(ws);
+});
+
+export function send_notification(is_ok, context, message)
+{
+ let updated_subs = [];
+
+ if (notification_subs.length === 0) {
+ console.log("WARNING: Got a notification, but nobody is subscribed");
+ }
+ for (const sub of notification_subs) {
+ if (sub.readyState == WebSocket.CONNECTING) {
+ console.log("WARNING: Unable to forward a notification to client that is still connecting");
+ updated_subs.push(sub);
+ } else {
+ try {
+ sub.send(JSON.stringify({
+ is_ok,
+ context,
+ message
+ }));
+ updated_subs.push(sub);
+ } catch {
+ sub.close();
+ console.log("WARNING: Fail to send a notification, closing the connection");
+ }
+ }
+ }
+}
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 661ca4b..1505ce7 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -1,57 +1,58 @@
services:
android:
build:
context: ./android/
args:
PROXY_PORT: "45459"
SERVER_PORT: "45456"
LOOPBACK_PORT: "10000"
MONITORING_API_PORT: "10001"
container_name: httptoolkit_server
container_name: android
sysctls:
- net.ipv6.conf.all.disable_ipv6=1
cap_add:
- NET_ADMIN
devices:
- /dev/kvm
networks:
- rent_gen_android
ports:
- 45456:45459 # This cannot change
- 45457:45459 # This cannot change
- 10001:10001 # api port
- 3000:3000 # android server port
+ - 3001:3001 # Notifications server
volumes:
- $PWD/shared_buffer:/shared_buffer
- $PWD/android/conf:/conf
- $PWD/certificates:/certificates
- $PWD/android/code:/code
httptoolkit_ui:
build:
context: ./httptoolkit_ui/
args:
# The ip / hostname using which,
# the browser can reach this docker session
DOCKER_HOST: "127.0.0.1"
container_name: httptoolkit_ui
networks:
- rent_gen_android
ports:
- 9080:9080
http_server:
build: ./http_server/
container_name: http_server
networks:
- rent_gen_android
volumes:
- $PWD/http_server/code:/code
- $PWD/shared_buffer:/shared_buffer
- $PWD/log:/log
ports:
- 8080:8080
networks:
rent_gen_android:
driver: bridge
diff --git a/http_server/code/index.html b/http_server/code/index.html
index 0780381..83ab631 100644
--- a/http_server/code/index.html
+++ b/http_server/code/index.html
@@ -1,320 +1,327 @@
<!DOCTYPE html>
<html lang="en">
<head style="height: 100vh">
<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-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;
}
.tab-section {
display: flex; flex-direction: column; flex-grow: 1;
}
<!-- TODO: A notification system -->
#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;
+ }
</style>
</head>
<body>
- <div id="resp"></div>
+ <div id="notifications"></div>
+ <div id="resp" style="display: none;"></div>
<main>
<section class="screen-section" >
<img
id="screen"
alt="android screen"
src=""
draggable="false"
class="screen"
style="flex-grow: 0"
/>
<div class="screen-buttons" style="flex-grow: 0">
<button class="screen-buttons-home">home</button>
<button class="screen-buttons-back">back</button>
</div>
<form id="upload_form" hx-post="/upload_apk" enctype="multipart/form-data" hx-target="#resp">
<label id="upload_input" for="app">Select file
<input type="file" id="app" name="app" accept=".apk" required multiple/>
</label>
<button type="submit">Install the app</button>
</form>
</section>
<div class="tab-section" >
<div class="tab">
<button class="tablinks active" onclick="open_tab(event, 'httptoolkit-tab')">HttpToolkit UI</button>
<button class="tablinks" onclick="open_tab(event, 'logs-tab')">Logs</button>
</div>
<div class="tabcontent" id="logs-tab">
<p id="clicks-log" class="log-section" ></p>
<p id="traffic-log" class="log-section"></p>
</div>
<div class="tabcontent active" id="httptoolkit-tab">
<iframe id="httptoolkit-frame" style="flex-grow: 1;" src="http://localhost:9080/" title="httptoolkit"></iframe>
</div>
</div>
</main>
<script>
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");
}
</script>
<script>
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);
fetch(path, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
...(body ? { body } : {}),
});
};
homeButton.addEventListener("click", () =>
registerClick({ path: "home", logText: "await homeButton();" })
);
backButton.addEventListener("click", () =>
registerClick({ path: "back", logText: "await backButton();" })
);
async function displayImage() {
try {
const response = await fetch("screen");
const blob = await response.blob();
screen.src = URL.createObjectURL(blob);
} catch (error) {
console.error("Error fetching image: ", error);
}
}
let isDragging = false;
let startDraggingPosX = 0;
let endDraggingPosX = 0;
let startDraggingPosY = 0;
let endDraggingPosY = 0;
const screenSize = [320, 640];
const handleDraggStart = (e) => {
e.preventDefault();
isDragging = true;
startDraggingPosX = e.offsetX;
startDraggingPosY = e.offsetY;
};
screen.addEventListener("mousedown", handleDraggStart);
document.addEventListener("mouseup", (e) => {
endDraggingPosX = e.offsetX;
endDraggingPosY = e.offsetY;
if (
isDragging &&
(Math.abs(endDraggingPosY - startDraggingPosY) > 10 ||
Math.abs(endDraggingPosX - startDraggingPosX) > 10)
) {
registerClick({
path: "drag",
logText: `await drag({x:${startDraggingPosX},y:${startDraggingPosY}},{x:${e.offsetX},y:${e.offsetY}});`,
body: `startX=${startDraggingPosX}&startY=${startDraggingPosY}&endX=${e.offsetX}&endY=${e.offsetY}`,
});
} else if (e.target === screen) {
const phoneX = event.offsetX;
const phoneY = event.offsetY;
if (phoneX <= screenSize[0] && phoneY <= screenSize[1])
registerClick({
path: "touch",
logText: `await click(${phoneX}, ${phoneY});`,
body: `x=${phoneX}&y=${phoneY}`,
});
}
isDragging = false;
});
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>
diff --git a/http_server/code/index.mjs b/http_server/code/index.mjs
index 32d82bf..1e5dc42 100644
--- a/http_server/code/index.mjs
+++ b/http_server/code/index.mjs
@@ -1,111 +1,113 @@
import express from "express";
import { readFile } from "node:fs/promises";
import {
guardedScreenshot,
android_websocket,
waitFullBoot,
} from "./screenshot.mjs";
import { execSync } from 'node:child_process';
import fileUpload from 'express-fileupload';
const device_size_x = 320;
const device_size_y = 640;
const app = express();
app.use(express.urlencoded({ extended: false }));
+app.use(express.static('/code/dist'))
+
console.log("Waiting for full boot...");
await waitFullBoot();
console.log("Boot detected! activating endpoints");
//GET
app.get("/favicon.ico", function (req, res) {
res.sendFile("/code/favicon.ico");
});
-app.get("/trafficLog.js", function (req, res) {
- res.sendFile("/code/dist/trafficLog.js");
-});
-
app.get("/htmx.js", function (req, res) {
res.sendFile("/code/node_modules/htmx.org/dist/htmx.min.js");
});
app.get("/trafficLog", async function (req, res) {
res.sendFile("/log/trafficLog");
});
app.get("/screen", async function (req, res) {
await guardedScreenshot();
res.sendFile("/code/screenshot.png");
});
app.get("/", async function (req, res) {
let fileData = (await readFile("/code/index.html")).toString();
fileData = fileData.replace(
"___screenshotDelayMs___",
process.env.screenshotDelayMs
);
res.setHeader("Content-Type", "text/html");
res.setHeader("Content-Disposition", "inline");
res.send(fileData);
});
//POST
app.post("/back", function (req, res) {
android_websocket.send(`back`);
res.sendStatus(200);
});
// default options
app.use(fileUpload());
app.post('/upload_apk', async function (req, res) {
if (!req.files || Object.keys(req.files).length === 0) {
return res.status(400).send('No files were uploaded.');
}
execSync("rm -rf /shared_buffer/*");
- console.log(req.files);
- for (const [idx, file] of req.files.app.entries()) {
- let uploadPath = '/shared_buffer/app' + idx + '.apk';
- await file.mv(uploadPath);
+ if (Array.isArray(req.files.app)) {
+ for (const [idx, file] of req.files.app.entries()) {
+ let uploadPath = '/shared_buffer/app' + idx + '.apk';
+ await file.mv(uploadPath);
+ }
+ } else {
+ let uploadPath = '/shared_buffer/app' + 0 + '.apk';
+ await req.files.app.mv(uploadPath);
}
android_websocket.send(`install`);
res.send('Files uploaded!');
})
app.post("/home", function (req, res) {
android_websocket.send(`home`);
res.sendStatus(200);
});
app.post("/touch", function (req, res) {
const x = parseInt(req.body.x);
const y = parseInt(req.body.y);
if (isNaN(x) || isNaN(y) || x > device_size_x || y > device_size_y) {
res.send(
`the query params must be x <= ${device_size_x}, y <= ${device_size_y}\n`
);
} else {
android_websocket.send(`touch ${x} ${y}`);
res.sendStatus(200);
}
});
app.post("/drag", function (req, res) {
const body = req.body;
const startX = Number(body.startX);
const startY = Number(body.startY);
const endX = Number(body.endX);
const endY = Number(body.endY);
android_websocket.send(`drag ${startX} ${startY} ${endX} ${endY}`);
res.sendStatus(200);
});
app.listen(8080, () => console.log("Listening in port 8080"));
diff --git a/http_server/code/package.json b/http_server/code/package.json
index ad975f7..4af3af4 100644
--- a/http_server/code/package.json
+++ b/http_server/code/package.json
@@ -1,15 +1,15 @@
{
"scripts": {
- "build": "esbuild --sourcemap --bundle src/trafficLog.jsx --outfile=dist/trafficLog.js --jsx-factory=h --jsx-fragment=Fragment"
+ "build": "esbuild --sourcemap --bundle src/trafficLog.jsx src/notifications.jsx --outdir=dist/ --jsx-factory=h --jsx-fragment=Fragment"
},
"dependencies": {
"express": "^4.18.2",
"express-fileupload": "^1.5.1",
"htmx.org": "^1.9.12",
"preact": "^10.18.1",
"ws": "^8.18.0"
},
"devDependencies": {
"esbuild": "^0.19.5"
}
}
diff --git a/http_server/code/src/notifications.jsx b/http_server/code/src/notifications.jsx
new file mode 100644
index 0000000..9ba6110
--- /dev/null
+++ b/http_server/code/src/notifications.jsx
@@ -0,0 +1,69 @@
+import { render, Component } from "preact";
+import { useState } from "preact/hooks";
+
+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
+ this.connection = new WebSocket("ws://127.0.0.1:3001");
+
+ this.connection.onmessage = (msg) => {
+ let new_id = rand_num();
+ this.setState({
+ notifications: [
+ { id: new_id, notification: JSON.parse(msg.data) },
+ ...this.state.notifications,
+ ],
+ });
+ // a 10 sec timeout
+ setTimeout(() => {
+ this.remove_notification(new_id)
+ }, 10000)
+ };
+ }
+
+ render() {
+ console.log("Render", this.state.notifications);
+ 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"));

File Metadata

Mime Type
text/x-diff
Expires
Fri, Jul 4, 00:14 (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
799745
Default Alt Text
(19 KB)

Event Timeline