diff --git a/android/code/index.mjs b/android/code/index.mjs
index bb5c961..c47b8d9 100644
--- a/android/code/index.mjs
+++ b/android/code/index.mjs
@@ -1,76 +1,128 @@
-import { WebSocketServer } from "ws";
 import child_process from "child_process";
 import fs from "fs";
-import { send_notification } from "./notifications.mjs";
+import { Server } from "socket.io";
 
 async function spawnPromise(program, args) {
-	return new Promise((resolve, reject) => {
+	return new Promise((resolve, _reject) => {
 		let output = "";
 		const process = child_process.spawn(program, args);
 		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 });
+let io = new Server();
 
+async function send_private_data() {
+	let adid = fs.readFileSync("/adid").toString();
+	let gps_coords = await spawnPromise("bash", ["/conf/get_location.sh"])
+	gps_coords = gps_coords.output;
+	gps_coords = gps_coords.trim().split(',');
+	io.emit("private_info", {adid, latitude: gps_coords[0], longitude: gps_coords[1]})
+}
+
+function send_notification(socket, is_ok, context, message) {
+	socket.emit("notification", {
+		is_ok,
+		context,
+		message,
+	});
+}
+
+let screenshot_in_flight = false;
 //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") {
-			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(" ");
+io.on("connection", (socket) => {
+	socket.onAny((ev, ...args) => {
+		console.log("server got: ", ev, ...args);
+	});
+	socket.on("screenshot", async () => {
+		if (screenshot_in_flight)
+			return;
+		screenshot_in_flight = true;
+		let screen = await fetch("http://localhost:9987/v2/uiDevice/screenshot");
+		let body = await screen.bytes();
+		socket.emit("screenshot_data", body);
+		screenshot_in_flight = false;
+	});
+
+	socket.on("private_info_req", async () => {
+		await send_private_data();
+	})
+
+	socket.on("reset_adid", async () => {
+		await spawnPromise("bash", ["/conf/reset_adid.sh"]);
+		await send_private_data();
+	})
 
-			await spawnPromise("bash", [
-				"/conf/drag.sh",
-				dataSplit[1],
-				dataSplit[2],
-				dataSplit[3],
-				dataSplit[4],
-			]);
-		} else if (data.startsWith("setcoord")) {
-			const dataSplit = data.split(" ");
-			const res = await spawnPromise("bash", [
-				"/conf/set_geo.sh",
-				dataSplit[1],
-				dataSplit[2],
-			]);
-			send_notification(
-				res.code === 0,
-				"Setting the moch location",
-				res.output
-			);
-		}
+	socket.on("back", async () => {
+		await spawnPromise("bash", ["/conf/back.sh"]);
 	});
-	ws.on("close", (_) => {
-		ws.close();
+
+	socket.on("home", async () => {
+		await spawnPromise("bash", ["/conf/home.sh"]);
+	});
+
+	socket.on("install", async () => {
+		const res = await spawnPromise("bash", ["/conf/install.sh"]);
+		send_notification(
+			socket,
+			res.code === 0,
+			"Installing the application",
+			res.output
+		);
+	});
+
+	// drag handles both drag and click
+	socket.on("motionevent", async (data) => {
+		await spawnPromise("bash", [
+			"/conf/motionevent.sh",
+			data.motionType,
+			data.x + "",
+			data.y + ""
+		]);
+	});
+
+	// drag handles both drag and click
+	socket.on("drag", async (data) => {
+		await spawnPromise("bash", [
+			"/conf/drag.sh",
+			data.startX + "",
+			data.startY + "",
+			data.endX + "",
+			data.endY + "",
+			data.dragTime + "",
+		]);
+	});
+
+	socket.on("key", async (data) => {
+		await spawnPromise("bash", [
+			"/conf/press_key.sh",
+			data.key
+		]);
+	});
+
+	socket.on("setcoord", async (data) => {
+		const res = await spawnPromise("bash", [
+			"/conf/set_geo.sh",
+			data.lon + "",
+			data.lat + "",
+		]);
+		send_notification(
+			socket,
+			res.code === 0,
+			"Setting the moch location",
+			res.output
+		);
+		await send_private_data();
 	});
 });
+
+io.listen(3000);
+console.log("listening on port 3000");
diff --git a/android/code/notifications.mjs b/android/code/notifications.mjs
deleted file mode 100644
index f1daa06..0000000
--- a/android/code/notifications.mjs
+++ /dev/null
@@ -1,42 +0,0 @@
-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"
-				);
-			}
-		}
-	}
-	notification_subs = updated_subs;
-}
diff --git a/android/code/package-lock.json b/android/code/package-lock.json
index b827a9c..3f7f07a 100644
--- a/android/code/package-lock.json
+++ b/android/code/package-lock.json
@@ -1,40 +1,462 @@
 {
   "name": "code",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "dependencies": {
+        "socket.io": "^4.8.1",
         "ws": "^8.18.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",
+      "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
+      "license": "MIT"
+    },
+    "node_modules/@types/cors": {
+      "version": "2.8.18",
+      "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.18.tgz",
+      "integrity": "sha512-nX3d0sxJW41CqQvfOzVG1NCTXfFDrDWIghCZncpHeWlVFd81zxB/DLhg7avFg6eHLCRX7ckBmoIIcqa++upvJA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/node": {
+      "version": "22.15.21",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz",
+      "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==",
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~6.21.0"
+      }
+    },
+    "node_modules/accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/base64id": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+      "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
+      "license": "MIT",
+      "engines": {
+        "node": "^4.5.0 || >= 5.9"
+      }
+    },
+    "node_modules/cookie": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+      "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cors": {
+      "version": "2.8.5",
+      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+      "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+      "license": "MIT",
+      "dependencies": {
+        "object-assign": "^4",
+        "vary": "^1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/engine.io": {
+      "version": "6.6.4",
+      "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
+      "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/cors": "^2.8.12",
+        "@types/node": ">=10.0.0",
+        "accepts": "~1.3.4",
+        "base64id": "2.0.0",
+        "cookie": "~0.7.2",
+        "cors": "~2.8.5",
+        "debug": "~4.3.1",
+        "engine.io-parser": "~5.2.1",
+        "ws": "~8.17.1"
+      },
+      "engines": {
+        "node": ">=10.2.0"
+      }
+    },
+    "node_modules/engine.io-parser": {
+      "version": "5.2.3",
+      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+      "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/engine.io/node_modules/ws": {
+      "version": "8.17.1",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+      "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/socket.io": {
+      "version": "4.8.1",
+      "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
+      "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
+      "license": "MIT",
+      "dependencies": {
+        "accepts": "~1.3.4",
+        "base64id": "~2.0.0",
+        "cors": "~2.8.5",
+        "debug": "~4.3.2",
+        "engine.io": "~6.6.0",
+        "socket.io-adapter": "~2.5.2",
+        "socket.io-parser": "~4.2.4"
+      },
+      "engines": {
+        "node": ">=10.2.0"
+      }
+    },
+    "node_modules/socket.io-adapter": {
+      "version": "2.5.5",
+      "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
+      "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "~4.3.4",
+        "ws": "~8.17.1"
+      }
+    },
+    "node_modules/socket.io-adapter/node_modules/ws": {
+      "version": "8.17.1",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+      "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/socket.io-parser": {
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+      "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+      "license": "MIT",
+      "dependencies": {
+        "@socket.io/component-emitter": "~3.1.0",
+        "debug": "~4.3.1"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/undici-types": {
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+      "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+      "license": "MIT"
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/ws": {
       "version": "8.18.0",
       "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
       "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
       "engines": {
         "node": ">=10.0.0"
       },
       "peerDependencies": {
         "bufferutil": "^4.0.1",
         "utf-8-validate": ">=5.0.2"
       },
       "peerDependenciesMeta": {
         "bufferutil": {
           "optional": true
         },
         "utf-8-validate": {
           "optional": true
         }
       }
     }
   },
   "dependencies": {
+    "@socket.io/component-emitter": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+      "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
+    },
+    "@types/cors": {
+      "version": "2.8.18",
+      "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.18.tgz",
+      "integrity": "sha512-nX3d0sxJW41CqQvfOzVG1NCTXfFDrDWIghCZncpHeWlVFd81zxB/DLhg7avFg6eHLCRX7ckBmoIIcqa++upvJA==",
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "@types/node": {
+      "version": "22.15.21",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz",
+      "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==",
+      "requires": {
+        "undici-types": "~6.21.0"
+      }
+    },
+    "accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "requires": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      }
+    },
+    "base64id": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+      "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
+    },
+    "cookie": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+      "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="
+    },
+    "cors": {
+      "version": "2.8.5",
+      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+      "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+      "requires": {
+        "object-assign": "^4",
+        "vary": "^1"
+      }
+    },
+    "debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "requires": {
+        "ms": "^2.1.3"
+      }
+    },
+    "engine.io": {
+      "version": "6.6.4",
+      "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
+      "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
+      "requires": {
+        "@types/cors": "^2.8.12",
+        "@types/node": ">=10.0.0",
+        "accepts": "~1.3.4",
+        "base64id": "2.0.0",
+        "cookie": "~0.7.2",
+        "cors": "~2.8.5",
+        "debug": "~4.3.1",
+        "engine.io-parser": "~5.2.1",
+        "ws": "~8.17.1"
+      },
+      "dependencies": {
+        "ws": {
+          "version": "8.17.1",
+          "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+          "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+          "requires": {}
+        }
+      }
+    },
+    "engine.io-parser": {
+      "version": "5.2.3",
+      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+      "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="
+    },
+    "mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
+    },
+    "mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "requires": {
+        "mime-db": "1.52.0"
+      }
+    },
+    "ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+    },
+    "negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
+    },
+    "object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
+    },
+    "socket.io": {
+      "version": "4.8.1",
+      "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
+      "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
+      "requires": {
+        "accepts": "~1.3.4",
+        "base64id": "~2.0.0",
+        "cors": "~2.8.5",
+        "debug": "~4.3.2",
+        "engine.io": "~6.6.0",
+        "socket.io-adapter": "~2.5.2",
+        "socket.io-parser": "~4.2.4"
+      }
+    },
+    "socket.io-adapter": {
+      "version": "2.5.5",
+      "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
+      "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
+      "requires": {
+        "debug": "~4.3.4",
+        "ws": "~8.17.1"
+      },
+      "dependencies": {
+        "ws": {
+          "version": "8.17.1",
+          "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+          "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+          "requires": {}
+        }
+      }
+    },
+    "socket.io-parser": {
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+      "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+      "requires": {
+        "@socket.io/component-emitter": "~3.1.0",
+        "debug": "~4.3.1"
+      }
+    },
+    "undici-types": {
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+      "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
+    },
+    "vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
+    },
     "ws": {
       "version": "8.18.0",
       "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
       "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
       "requires": {}
     }
   }
 }
diff --git a/android/code/package.json b/android/code/package.json
index 37c3082..a448b41 100644
--- a/android/code/package.json
+++ b/android/code/package.json
@@ -1,5 +1,6 @@
 {
   "dependencies": {
+    "socket.io": "^4.8.1",
     "ws": "^8.18.0"
   }
 }
diff --git a/android/conf/drag.sh b/android/conf/drag.sh
index f4c4a76..520d190 100644
--- a/android/conf/drag.sh
+++ b/android/conf/drag.sh
@@ -1 +1 @@
-/opt/android-sdk-linux/platform-tools/adb shell input swipe $1 $2 $3 $4 1000
\ No newline at end of file
+/opt/android-sdk-linux/platform-tools/adb shell input swipe $1 $2 $3 $4 $5
diff --git a/android/conf/get_location.sh b/android/conf/get_location.sh
new file mode 100644
index 0000000..f751fb5
--- /dev/null
+++ b/android/conf/get_location.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+adb shell dumpsys location | grep 'last location=' | head -n 1 | awk -F' ' '{ print $3 }'
diff --git a/android/conf/motionevent.sh b/android/conf/motionevent.sh
new file mode 100644
index 0000000..77887f1
--- /dev/null
+++ b/android/conf/motionevent.sh
@@ -0,0 +1 @@
+/opt/android-sdk-linux/platform-tools/adb shell input motionevent $1 $2 $3
diff --git a/android/conf/press_key.sh b/android/conf/press_key.sh
new file mode 100644
index 0000000..c250b53
--- /dev/null
+++ b/android/conf/press_key.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+# https://stackoverflow.com/questions/7789826/adb-shell-input-events
+if [ "$1" = "Enter" ]; then
+	/opt/android-sdk-linux/platform-tools/adb shell input keyevent 66
+elif [ "$1" = "Backspace" ]; then
+	/opt/android-sdk-linux/platform-tools/adb shell input keyevent 67
+else
+	/opt/android-sdk-linux/platform-tools/adb shell input text "'$1'"
+fi
diff --git a/android/conf/reset_adid.sh b/android/conf/reset_adid.sh
new file mode 100644
index 0000000..9e01789
--- /dev/null
+++ b/android/conf/reset_adid.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+set -xe
+
+# Change the file slightly
+echo | adb shell su root "tee /data/data/com.google.android.gms/shared_prefs/adid_settings.xml"
+
+# Ask android for the ADID. Android detects the changed file (somewhere), and regenerates it
+adb shell su root am start -W -n com.example.adidreader/com.example.adidreader.MainActivity
+
+sleep 0.1
+
+# Get the new ADID
+adb shell su root cat /data/data/com.google.android.gms/shared_prefs/adid_settings.xml | xmllint --xpath 'string(//map/string[@name="adid_key"])' - > /adid
diff --git a/android/conf/screenshot.sh b/android/conf/screenshot.sh
deleted file mode 100755
index 9f4f3c1..0000000
--- a/android/conf/screenshot.sh
+++ /dev/null
@@ -1 +0,0 @@
-curl http://localhost:9987/v2/uiDevice/screenshot -o /screenshot.png
\ No newline at end of file
diff --git a/android/conf/set_geo.sh b/android/conf/set_geo.sh
index 5de2ea8..ab65c9d 100644
--- a/android/conf/set_geo.sh
+++ b/android/conf/set_geo.sh
@@ -1,13 +1,21 @@
 #!/bin/bash
 ko=$((
 	echo "auth $(cat ~/.emulator_console_auth_token)"
 	echo "geo fix $1 $2 0.0"
 	sleep 0.2
-	) | telnet localhost 5554 | grep KO: );
+	) | telnet localhost 5554 2> /dev/null | grep KO: );
 
 if [ -z "${ko}" ]; then
+	# Check if the location was actually set, for some reason the location moching doesn't work,
+	# unless google maps is launched first, and is given access to the phone location
+	adb shell dumpsys location | grep 'last location=Location\[gps' > /dev/null
+	if [ $? -ne 0 ]; then
+		echo Failed to actually set the location, probably need to launch google maps first
+		exit 1
+	fi
+	echo Succesfully set the coordinates to: $(. /conf/get_location.sh)
 	exit 0
 else
 	echo "$ko"
 	exit 1;
 fi
diff --git a/android/conf/start_culebra.sh b/android/conf/start_culebra.sh
index d638ea7..8473c21 100644
--- a/android/conf/start_culebra.sh
+++ b/android/conf/start_culebra.sh
@@ -1,22 +1,26 @@
+set -xe
 rm -f /opt/android-sdk-linux/.android/avd/virtual_dev.avd/*.lock
 adb start-server
 cat > /simple.gpx << EOF
 <?xml version="1.0" encoding="UTF-8"?>
 <gpx version="1.1" creator="ChatGPT" xmlns="http://www.topografix.com/GPX/1/1">
   <wpt lat="37.422" lon="-122.084">
     <name>Simple Waypoint</name>
   </wpt>
 </gpx>
 EOF
 
-emulator -grpc 5556 -avd virtual_dev -snapshot configured -no-window -no-audio  -debug all,-adb,-gles1emu,-gles,-mtport,-metrics,-memory,-car,-tvremote &
+emulator -avd virtual_dev -writable-system -no-window -no-audio &
 adb wait-for-device
+adb emu avd snapshot load configured
+# emulator -grpc 5556 -avd virtual_dev -snapshot configured -no-window -no-audio -no-metrics -debug-no > /dev/null &
+# adb wait-for-device
 
 export PATH=$PATH:/root/culebraDependencies
 cd /root/culebra
 ./culebratester2 start-server &
 
 #wait for the server to start
 while ! curl http://localhost:9987/v2/uiDevice/screenshot > /dev/null 2> /dev/null; do   
   sleep 0.1
 done
diff --git a/http_server/Dockerfile b/http_server/Dockerfile
index 1fa58d2..777c583 100644
--- a/http_server/Dockerfile
+++ b/http_server/Dockerfile
@@ -1,11 +1,11 @@
 FROM alpine:3.18.2
 
 RUN apk add npm
 
 WORKDIR /code
 
 RUN mkdir /images
 
-ENV screenshotDelayMs 500
+ENV screenshotDelayMs 100
 
-CMD sh /code/docker-entrypoint.sh
\ No newline at end of file
+CMD sh /code/docker-entrypoint.sh
diff --git a/http_server/code/docker-entrypoint.sh b/http_server/code/docker-entrypoint.sh
index 55f3f67..8c58d63 100644
--- a/http_server/code/docker-entrypoint.sh
+++ b/http_server/code/docker-entrypoint.sh
@@ -1,8 +1,6 @@
 #!/bin/bash
 
 npm i
 npm run build
-node waitSocket.mjs
 node --watch index.mjs
-
 #tail -f /dev/null
diff --git a/http_server/code/index.html b/http_server/code/index.html
index ef48e0f..e46cfdb 100644
--- a/http_server/code/index.html
+++ b/http_server/code/index.html
@@ -1,332 +1,468 @@
-<!DOCTYPE html>
+<!doctype html>
 <html lang="en">
   <head style="height: 100vh">
     <meta charset="UTF-8" />
     <title>Rentgen android</title>
-	<script src="/htmx.js"></script>
+    <script src="/htmx.js"></script>
     <style>
       main {
         display: flex;
       }
 
       .log-section {
-		height: auto;
-		width: 400px;
+        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;
-	  }
+      #clicks-log {
+        font-family:
+          Menlo,
+          Consolas,
+          Monaco,
+          Liberation Mono,
+          Lucida Console,
+          monospace;
+      }
 
-	  .tab {
-		  border: 1px solid #ccc;
-		  background-color: #f1f1f1;
-	  }
+      .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;
-	  }
+      /* 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;
-	  }
+      /* 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;
-	  }
+      /* 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;
-	  }
-	  .tab-section {
-			display: flex; flex-direction: column; flex-grow: 1;
-	  }
-	  <!-- TODO: A notification system -->
+      /* 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;
-	  }
+        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="notifications"></div>
-	<div id="resp" style="display: none;"></div>
+    <div id="resp" style="display: none"></div>
     <main>
-      <section class="screen-section" >
+      <section class="screen-section">
         <img
           id="screen"
           alt="android screen"
           src=""
           draggable="false"
           class="screen"
-		  style="flex-grow: 0"
+          style="flex-grow: 0"
+          tabindex="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" >
-		<form id="set_coords" hx-post="/setcoord" hx-target="#resp">
-			<input type="text" name="lon"/>
-			<input type="text" name="lat"/>
-			<button type="submit">Submit coords</button>
-		</form>
-		  <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>
+        <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>
+          <button class="tablinks" onclick="open_tab(event, 'controls-tab')">
+            Device Controls
+          </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 class="tabcontent" id="controls-tab">
+          <form id="set_coords" onsubmit="coords_handler(event)">
+            <label>
+              Latitude:
+              <input type="text" name="lat" />
+            </label>
+            <label>
+              Longitude:
+              <input type="text" name="lon" />
+            </label>
+            <button type="submit">Submit coords</button>
+          </form>
+		  <button id="reset_adid_btn" onclick="reset_adid_handler(event)">Reset ADID</button>
+		  <table>
+			  <thead></thead>
+			  <tbody>
+				  <tr>
+					  <td>ADID:</td>
+					  <td id="adid_priv_info_table">UNKNOWN</td>
+				  </tr>
+				  <tr>
+					  <td>Longitude:</td>
+					  <td id="lon_priv_info_table">UNKNOWN</td>
+				  </tr>
+				  <tr>
+					  <td>Latitude:</td>
+					  <td id="lat_priv_info_table">UNKNOWN</td>
+				  </tr>
+			  </tbody>
+		  </table>
+        </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 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);
-
-        fetch(path, {
-          method: "POST",
-          headers: {
-            "Content-Type": "application/x-www-form-urlencoded",
-          },
-          ...(body ? { body } : {}),
-        });
+        socket.emit(path, body ? body : {});
       };
 
       homeButton.addEventListener("click", () =>
         registerClick({ path: "home", logText: "await homeButton();" })
       );
 
       backButton.addEventListener("click", () =>
         registerClick({ path: "back", logText: "await backButton();" })
       );
 
-      async function displayImage() {
+      socket.on("screenshot_data", (data) => {
         try {
-          const response = await fetch("screen");
-          const blob = await response.blob();
+          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;
-      let startDraggingPosX = 0;
-      let endDraggingPosX = 0;
-      let startDraggingPosY = 0;
-      let endDraggingPosY = 0;
-
       const screenSize = [320, 640];
 
-      const handleDraggStart = (e) => {
-        e.preventDefault();
+      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;
-        startDraggingPosX = e.offsetX;
-        startDraggingPosY = e.offsetY;
+        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) => {
-        endDraggingPosX = e.offsetX;
-        endDraggingPosY = e.offsetY;
-        if (
-          isDragging &&
-          (Math.abs(endDraggingPosY - startDraggingPosY) > 10 ||
-            Math.abs(endDraggingPosX - startDraggingPosX) > 10)
-        ) {
+        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: "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}`,
+            path: "key",
+            logText: `await key(${event.key});`,
+            body: { key },
           });
-        } 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 386054e..00ed30c 100644
--- a/http_server/code/index.mjs
+++ b/http_server/code/index.mjs
@@ -1,123 +1,96 @@
 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 { Server } from "socket.io";
 
-import fileUpload from "express-fileupload";
+import { io } from "socket.io-client";
 
-const device_size_x = 320;
-const device_size_y = 640;
+import fileUpload from "express-fileupload";
 
 const app = express();
-app.use(express.urlencoded({ extended: false }));
 
-app.use(express.static("/code/dist"));
+async function sleep(time) {
+	return new Promise((resolve) => setTimeout(resolve, time));
+}
 
 console.log("Waiting for full boot...");
-await waitFullBoot();
+// bidirectional forwarding
+// Wait untill the connection
+const back = io("ws://android:3000", {
+	reconnectionAttempts: 1000000,
+	reconnectionDelay: 100,
+});
+while (!back.connected) {
+	await sleep(100);
+}
 console.log("Boot detected! activating endpoints");
 
+app.use(express.urlencoded({ extended: false }));
+
+app.use(express.static("/code/dist"));
+
 //GET
 app.get("/favicon.ico", function (req, res) {
 	res.sendFile("/code/favicon.ico");
 });
 
 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("/socket.io.js", function (_req, res) {
+	res.sendFile("/code/node_modules/socket.io/client-dist/socket.io.js");
 });
 
 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/*");
 	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`);
+	back.emit("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);
+let server = app.listen(8080, () => console.log("Listening in port 8080"));
 
-	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);
-	}
-});
+const front = new Server(server);
 
-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);
+// forwarding the messages
+front.on("connection", (socket) => {
+	socket.onAny((event, ...args) => {
+		if (back.connected) {
+			back.emit(event, ...args);
+		} else {
+			console.log("Front tried to send: ", event, ...args);
+		}
+	});
 });
 
-app.post("/setcoord", function (req, res) {
-	console.log(req.body);
-	console.log(req.body.lat);
-	console.log(req.body.lon);
-	const lat = Number(req.body.lat);
-	const lon = Number(req.body.lon);
-	console.log(lat, lon);
-	android_websocket.send(`setcoord ${lat} ${lon}`);
-	res.sendStatus(200);
+back.onAny((event, ...args) => {
+	front.emit(event, ...args);
 });
-
-app.listen(8080, () => console.log("Listening in port 8080"));
diff --git a/http_server/code/package-lock.json b/http_server/code/package-lock.json
index 21386ed..b57ca33 100644
--- a/http_server/code/package-lock.json
+++ b/http_server/code/package-lock.json
@@ -1,1062 +1,1441 @@
 {
   "name": "code",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "dependencies": {
         "express": "^4.18.2",
         "express-fileupload": "^1.5.1",
         "htmx.org": "^1.9.12",
         "preact": "^10.18.1",
+        "socket.io": "^4.8.1",
+        "socket.io-client": "^4.8.1",
         "ws": "^8.18.0"
       },
       "devDependencies": {
         "esbuild": "^0.19.5"
       }
     },
     "node_modules/@esbuild/android-arm": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.5.tgz",
       "integrity": "sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==",
       "cpu": [
         "arm"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "android"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/android-arm64": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.5.tgz",
       "integrity": "sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==",
       "cpu": [
         "arm64"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "android"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/android-x64": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.5.tgz",
       "integrity": "sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==",
       "cpu": [
         "x64"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "android"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/darwin-arm64": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.5.tgz",
       "integrity": "sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==",
       "cpu": [
         "arm64"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "darwin"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/darwin-x64": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.5.tgz",
       "integrity": "sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==",
       "cpu": [
         "x64"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "darwin"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/freebsd-arm64": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.5.tgz",
       "integrity": "sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==",
       "cpu": [
         "arm64"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "freebsd"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/freebsd-x64": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.5.tgz",
       "integrity": "sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==",
       "cpu": [
         "x64"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "freebsd"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/linux-arm": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.5.tgz",
       "integrity": "sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==",
       "cpu": [
         "arm"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "linux"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/linux-arm64": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.5.tgz",
       "integrity": "sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==",
       "cpu": [
         "arm64"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "linux"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/linux-ia32": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.5.tgz",
       "integrity": "sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==",
       "cpu": [
         "ia32"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "linux"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/linux-loong64": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.5.tgz",
       "integrity": "sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==",
       "cpu": [
         "loong64"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "linux"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/linux-mips64el": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.5.tgz",
       "integrity": "sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==",
       "cpu": [
         "mips64el"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "linux"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/linux-ppc64": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.5.tgz",
       "integrity": "sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==",
       "cpu": [
         "ppc64"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "linux"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/linux-riscv64": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.5.tgz",
       "integrity": "sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==",
       "cpu": [
         "riscv64"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "linux"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/linux-s390x": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.5.tgz",
       "integrity": "sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==",
       "cpu": [
         "s390x"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "linux"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/linux-x64": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz",
       "integrity": "sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==",
       "cpu": [
         "x64"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "linux"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/netbsd-x64": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.5.tgz",
       "integrity": "sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==",
       "cpu": [
         "x64"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "netbsd"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/openbsd-x64": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.5.tgz",
       "integrity": "sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==",
       "cpu": [
         "x64"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "openbsd"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/sunos-x64": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.5.tgz",
       "integrity": "sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==",
       "cpu": [
         "x64"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "sunos"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/win32-arm64": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.5.tgz",
       "integrity": "sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==",
       "cpu": [
         "arm64"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "win32"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/win32-ia32": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.5.tgz",
       "integrity": "sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==",
       "cpu": [
         "ia32"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "win32"
       ],
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/@esbuild/win32-x64": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz",
       "integrity": "sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==",
       "cpu": [
         "x64"
       ],
       "dev": true,
       "optional": true,
       "os": [
         "win32"
       ],
       "engines": {
         "node": ">=12"
       }
     },
+    "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",
+      "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
+      "license": "MIT"
+    },
+    "node_modules/@types/cors": {
+      "version": "2.8.18",
+      "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.18.tgz",
+      "integrity": "sha512-nX3d0sxJW41CqQvfOzVG1NCTXfFDrDWIghCZncpHeWlVFd81zxB/DLhg7avFg6eHLCRX7ckBmoIIcqa++upvJA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/node": {
+      "version": "22.15.21",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz",
+      "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==",
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~6.21.0"
+      }
+    },
     "node_modules/accepts": {
       "version": "1.3.8",
       "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
       "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
       "dependencies": {
         "mime-types": "~2.1.34",
         "negotiator": "0.6.3"
       },
       "engines": {
         "node": ">= 0.6"
       }
     },
     "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/base64id": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+      "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
+      "license": "MIT",
+      "engines": {
+        "node": "^4.5.0 || >= 5.9"
+      }
+    },
     "node_modules/body-parser": {
       "version": "1.20.1",
       "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
       "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
       "dependencies": {
         "bytes": "3.1.2",
         "content-type": "~1.0.4",
         "debug": "2.6.9",
         "depd": "2.0.0",
         "destroy": "1.2.0",
         "http-errors": "2.0.0",
         "iconv-lite": "0.4.24",
         "on-finished": "2.4.1",
         "qs": "6.11.0",
         "raw-body": "2.5.1",
         "type-is": "~1.6.18",
         "unpipe": "1.0.0"
       },
       "engines": {
         "node": ">= 0.8",
         "npm": "1.2.8000 || >= 1.4.16"
       }
     },
     "node_modules/busboy": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
       "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
       "dependencies": {
         "streamsearch": "^1.1.0"
       },
       "engines": {
         "node": ">=10.16.0"
       }
     },
     "node_modules/bytes": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
       "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
       "engines": {
         "node": ">= 0.8"
       }
     },
     "node_modules/call-bind": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
       "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
       "dependencies": {
         "function-bind": "^1.1.1",
         "get-intrinsic": "^1.0.2"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
     },
     "node_modules/content-disposition": {
       "version": "0.5.4",
       "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
       "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
       "dependencies": {
         "safe-buffer": "5.2.1"
       },
       "engines": {
         "node": ">= 0.6"
       }
     },
     "node_modules/content-type": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
       "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
       "engines": {
         "node": ">= 0.6"
       }
     },
     "node_modules/cookie": {
       "version": "0.5.0",
       "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
       "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
       "engines": {
         "node": ">= 0.6"
       }
     },
     "node_modules/cookie-signature": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
       "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
     },
+    "node_modules/cors": {
+      "version": "2.8.5",
+      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+      "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+      "license": "MIT",
+      "dependencies": {
+        "object-assign": "^4",
+        "vary": "^1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
     "node_modules/debug": {
       "version": "2.6.9",
       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
       "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
       "dependencies": {
         "ms": "2.0.0"
       }
     },
     "node_modules/depd": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
       "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
       "engines": {
         "node": ">= 0.8"
       }
     },
     "node_modules/destroy": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
       "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
       "engines": {
         "node": ">= 0.8",
         "npm": "1.2.8000 || >= 1.4.16"
       }
     },
     "node_modules/ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
       "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
     },
     "node_modules/encodeurl": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
       "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
       "engines": {
         "node": ">= 0.8"
       }
     },
+    "node_modules/engine.io": {
+      "version": "6.6.4",
+      "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
+      "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/cors": "^2.8.12",
+        "@types/node": ">=10.0.0",
+        "accepts": "~1.3.4",
+        "base64id": "2.0.0",
+        "cookie": "~0.7.2",
+        "cors": "~2.8.5",
+        "debug": "~4.3.1",
+        "engine.io-parser": "~5.2.1",
+        "ws": "~8.17.1"
+      },
+      "engines": {
+        "node": ">=10.2.0"
+      }
+    },
+    "node_modules/engine.io-client": {
+      "version": "6.6.3",
+      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
+      "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
+      "license": "MIT",
+      "dependencies": {
+        "@socket.io/component-emitter": "~3.1.0",
+        "debug": "~4.3.1",
+        "engine.io-parser": "~5.2.1",
+        "ws": "~8.17.1",
+        "xmlhttprequest-ssl": "~2.1.1"
+      }
+    },
+    "node_modules/engine.io-client/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/engine.io-client/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/engine.io-client/node_modules/ws": {
+      "version": "8.17.1",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+      "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/engine.io-parser": {
+      "version": "5.2.3",
+      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+      "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/engine.io/node_modules/cookie": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+      "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/engine.io/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/engine.io/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/engine.io/node_modules/ws": {
+      "version": "8.17.1",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+      "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/esbuild": {
       "version": "0.19.5",
       "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.5.tgz",
       "integrity": "sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==",
       "dev": true,
       "hasInstallScript": true,
       "bin": {
         "esbuild": "bin/esbuild"
       },
       "engines": {
         "node": ">=12"
       },
       "optionalDependencies": {
         "@esbuild/android-arm": "0.19.5",
         "@esbuild/android-arm64": "0.19.5",
         "@esbuild/android-x64": "0.19.5",
         "@esbuild/darwin-arm64": "0.19.5",
         "@esbuild/darwin-x64": "0.19.5",
         "@esbuild/freebsd-arm64": "0.19.5",
         "@esbuild/freebsd-x64": "0.19.5",
         "@esbuild/linux-arm": "0.19.5",
         "@esbuild/linux-arm64": "0.19.5",
         "@esbuild/linux-ia32": "0.19.5",
         "@esbuild/linux-loong64": "0.19.5",
         "@esbuild/linux-mips64el": "0.19.5",
         "@esbuild/linux-ppc64": "0.19.5",
         "@esbuild/linux-riscv64": "0.19.5",
         "@esbuild/linux-s390x": "0.19.5",
         "@esbuild/linux-x64": "0.19.5",
         "@esbuild/netbsd-x64": "0.19.5",
         "@esbuild/openbsd-x64": "0.19.5",
         "@esbuild/sunos-x64": "0.19.5",
         "@esbuild/win32-arm64": "0.19.5",
         "@esbuild/win32-ia32": "0.19.5",
         "@esbuild/win32-x64": "0.19.5"
       }
     },
     "node_modules/escape-html": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
       "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
     },
     "node_modules/etag": {
       "version": "1.8.1",
       "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
       "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
       "engines": {
         "node": ">= 0.6"
       }
     },
     "node_modules/express": {
       "version": "4.18.2",
       "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
       "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
       "dependencies": {
         "accepts": "~1.3.8",
         "array-flatten": "1.1.1",
         "body-parser": "1.20.1",
         "content-disposition": "0.5.4",
         "content-type": "~1.0.4",
         "cookie": "0.5.0",
         "cookie-signature": "1.0.6",
         "debug": "2.6.9",
         "depd": "2.0.0",
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
         "etag": "~1.8.1",
         "finalhandler": "1.2.0",
         "fresh": "0.5.2",
         "http-errors": "2.0.0",
         "merge-descriptors": "1.0.1",
         "methods": "~1.1.2",
         "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
         "path-to-regexp": "0.1.7",
         "proxy-addr": "~2.0.7",
         "qs": "6.11.0",
         "range-parser": "~1.2.1",
         "safe-buffer": "5.2.1",
         "send": "0.18.0",
         "serve-static": "1.15.0",
         "setprototypeof": "1.2.0",
         "statuses": "2.0.1",
         "type-is": "~1.6.18",
         "utils-merge": "1.0.1",
         "vary": "~1.1.2"
       },
       "engines": {
         "node": ">= 0.10.0"
       }
     },
     "node_modules/express-fileupload": {
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.5.1.tgz",
       "integrity": "sha512-LsYG1ALXEB7vlmjuSw8ABeOctMp8a31aUC5ZF55zuz7O2jLFnmJYrCv10py357ky48aEoBQ/9bVXgFynjvaPmA==",
       "license": "MIT",
       "dependencies": {
         "busboy": "^1.6.0"
       },
       "engines": {
         "node": ">=12.0.0"
       }
     },
     "node_modules/finalhandler": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
       "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
       "dependencies": {
         "debug": "2.6.9",
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
         "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
         "statuses": "2.0.1",
         "unpipe": "~1.0.0"
       },
       "engines": {
         "node": ">= 0.8"
       }
     },
     "node_modules/forwarded": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
       "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
       "engines": {
         "node": ">= 0.6"
       }
     },
     "node_modules/fresh": {
       "version": "0.5.2",
       "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
       "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
       "engines": {
         "node": ">= 0.6"
       }
     },
     "node_modules/function-bind": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
       "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
     },
     "node_modules/get-intrinsic": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
       "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
       "dependencies": {
         "function-bind": "^1.1.1",
         "has": "^1.0.3",
         "has-proto": "^1.0.1",
         "has-symbols": "^1.0.3"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
     },
     "node_modules/has": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
       "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
       "dependencies": {
         "function-bind": "^1.1.1"
       },
       "engines": {
         "node": ">= 0.4.0"
       }
     },
     "node_modules/has-proto": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
       "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
       "engines": {
         "node": ">= 0.4"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
     },
     "node_modules/has-symbols": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
       "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
       "engines": {
         "node": ">= 0.4"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
     },
     "node_modules/htmx.org": {
       "version": "1.9.12",
       "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.12.tgz",
       "integrity": "sha512-VZAohXyF7xPGS52IM8d1T1283y+X4D+Owf3qY1NZ9RuBypyu9l8cGsxUMAG5fEAb/DhT7rDoJ9Hpu5/HxFD3cw==",
       "license": "0BSD"
     },
     "node_modules/http-errors": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
       "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
       "dependencies": {
         "depd": "2.0.0",
         "inherits": "2.0.4",
         "setprototypeof": "1.2.0",
         "statuses": "2.0.1",
         "toidentifier": "1.0.1"
       },
       "engines": {
         "node": ">= 0.8"
       }
     },
     "node_modules/iconv-lite": {
       "version": "0.4.24",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
       "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
       "dependencies": {
         "safer-buffer": ">= 2.1.2 < 3"
       },
       "engines": {
         "node": ">=0.10.0"
       }
     },
     "node_modules/inherits": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
       "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
     },
     "node_modules/ipaddr.js": {
       "version": "1.9.1",
       "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
       "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
       "engines": {
         "node": ">= 0.10"
       }
     },
     "node_modules/media-typer": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
       "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
       "engines": {
         "node": ">= 0.6"
       }
     },
     "node_modules/merge-descriptors": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
       "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
     },
     "node_modules/methods": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
       "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
       "engines": {
         "node": ">= 0.6"
       }
     },
     "node_modules/mime": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
       "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
       "bin": {
         "mime": "cli.js"
       },
       "engines": {
         "node": ">=4"
       }
     },
     "node_modules/mime-db": {
       "version": "1.52.0",
       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
       "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
       "engines": {
         "node": ">= 0.6"
       }
     },
     "node_modules/mime-types": {
       "version": "2.1.35",
       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
       "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
       "dependencies": {
         "mime-db": "1.52.0"
       },
       "engines": {
         "node": ">= 0.6"
       }
     },
     "node_modules/ms": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
       "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
     },
     "node_modules/negotiator": {
       "version": "0.6.3",
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
       "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
       "engines": {
         "node": ">= 0.6"
       }
     },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/object-inspect": {
       "version": "1.12.3",
       "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
       "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
     },
     "node_modules/on-finished": {
       "version": "2.4.1",
       "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
       "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
       "dependencies": {
         "ee-first": "1.1.1"
       },
       "engines": {
         "node": ">= 0.8"
       }
     },
     "node_modules/parseurl": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
       "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
       "engines": {
         "node": ">= 0.8"
       }
     },
     "node_modules/path-to-regexp": {
       "version": "0.1.7",
       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
       "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
     },
     "node_modules/preact": {
       "version": "10.18.1",
       "resolved": "https://registry.npmjs.org/preact/-/preact-10.18.1.tgz",
       "integrity": "sha512-mKUD7RRkQQM6s7Rkmi7IFkoEHjuFqRQUaXamO61E6Nn7vqF/bo7EZCmSyrUnp2UWHw0O7XjZ2eeXis+m7tf4lg==",
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/preact"
       }
     },
     "node_modules/proxy-addr": {
       "version": "2.0.7",
       "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
       "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
       "dependencies": {
         "forwarded": "0.2.0",
         "ipaddr.js": "1.9.1"
       },
       "engines": {
         "node": ">= 0.10"
       }
     },
     "node_modules/qs": {
       "version": "6.11.0",
       "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
       "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
       "dependencies": {
         "side-channel": "^1.0.4"
       },
       "engines": {
         "node": ">=0.6"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
     },
     "node_modules/range-parser": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
       "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
       "engines": {
         "node": ">= 0.6"
       }
     },
     "node_modules/raw-body": {
       "version": "2.5.1",
       "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
       "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
       "dependencies": {
         "bytes": "3.1.2",
         "http-errors": "2.0.0",
         "iconv-lite": "0.4.24",
         "unpipe": "1.0.0"
       },
       "engines": {
         "node": ">= 0.8"
       }
     },
     "node_modules/safe-buffer": {
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
       "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
       "funding": [
         {
           "type": "github",
           "url": "https://github.com/sponsors/feross"
         },
         {
           "type": "patreon",
           "url": "https://www.patreon.com/feross"
         },
         {
           "type": "consulting",
           "url": "https://feross.org/support"
         }
       ]
     },
     "node_modules/safer-buffer": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
     },
     "node_modules/send": {
       "version": "0.18.0",
       "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
       "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
       "dependencies": {
         "debug": "2.6.9",
         "depd": "2.0.0",
         "destroy": "1.2.0",
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
         "etag": "~1.8.1",
         "fresh": "0.5.2",
         "http-errors": "2.0.0",
         "mime": "1.6.0",
         "ms": "2.1.3",
         "on-finished": "2.4.1",
         "range-parser": "~1.2.1",
         "statuses": "2.0.1"
       },
       "engines": {
         "node": ">= 0.8.0"
       }
     },
     "node_modules/send/node_modules/ms": {
       "version": "2.1.3",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
       "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
     },
     "node_modules/serve-static": {
       "version": "1.15.0",
       "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
       "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
       "dependencies": {
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
         "parseurl": "~1.3.3",
         "send": "0.18.0"
       },
       "engines": {
         "node": ">= 0.8.0"
       }
     },
     "node_modules/setprototypeof": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
       "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
     },
     "node_modules/side-channel": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
       "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
       "dependencies": {
         "call-bind": "^1.0.0",
         "get-intrinsic": "^1.0.2",
         "object-inspect": "^1.9.0"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/socket.io": {
+      "version": "4.8.1",
+      "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
+      "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
+      "license": "MIT",
+      "dependencies": {
+        "accepts": "~1.3.4",
+        "base64id": "~2.0.0",
+        "cors": "~2.8.5",
+        "debug": "~4.3.2",
+        "engine.io": "~6.6.0",
+        "socket.io-adapter": "~2.5.2",
+        "socket.io-parser": "~4.2.4"
+      },
+      "engines": {
+        "node": ">=10.2.0"
+      }
+    },
+    "node_modules/socket.io-adapter": {
+      "version": "2.5.5",
+      "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
+      "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "~4.3.4",
+        "ws": "~8.17.1"
+      }
+    },
+    "node_modules/socket.io-adapter/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/socket.io-adapter/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/socket.io-adapter/node_modules/ws": {
+      "version": "8.17.1",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+      "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/socket.io-client": {
+      "version": "4.8.1",
+      "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
+      "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@socket.io/component-emitter": "~3.1.0",
+        "debug": "~4.3.2",
+        "engine.io-client": "~6.6.1",
+        "socket.io-parser": "~4.2.4"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/socket.io-client/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/socket.io-client/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/socket.io-parser": {
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+      "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+      "license": "MIT",
+      "dependencies": {
+        "@socket.io/component-emitter": "~3.1.0",
+        "debug": "~4.3.1"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/socket.io-parser/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/socket.io-parser/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/socket.io/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/socket.io/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
     "node_modules/statuses": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
       "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
       "engines": {
         "node": ">= 0.8"
       }
     },
     "node_modules/streamsearch": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
       "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
       "engines": {
         "node": ">=10.0.0"
       }
     },
     "node_modules/toidentifier": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
       "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
       "engines": {
         "node": ">=0.6"
       }
     },
     "node_modules/type-is": {
       "version": "1.6.18",
       "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
       "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
       "dependencies": {
         "media-typer": "0.3.0",
         "mime-types": "~2.1.24"
       },
       "engines": {
         "node": ">= 0.6"
       }
     },
+    "node_modules/undici-types": {
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+      "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+      "license": "MIT"
+    },
     "node_modules/unpipe": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
       "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
       "engines": {
         "node": ">= 0.8"
       }
     },
     "node_modules/utils-merge": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
       "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
       "engines": {
         "node": ">= 0.4.0"
       }
     },
     "node_modules/vary": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
       "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
       "engines": {
         "node": ">= 0.8"
       }
     },
     "node_modules/ws": {
       "version": "8.18.0",
       "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
       "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
       "engines": {
         "node": ">=10.0.0"
       },
       "peerDependencies": {
         "bufferutil": "^4.0.1",
         "utf-8-validate": ">=5.0.2"
       },
       "peerDependenciesMeta": {
         "bufferutil": {
           "optional": true
         },
         "utf-8-validate": {
           "optional": true
         }
       }
+    },
+    "node_modules/xmlhttprequest-ssl": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
+      "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
+      "engines": {
+        "node": ">=0.4.0"
+      }
     }
   }
 }
diff --git a/http_server/code/package.json b/http_server/code/package.json
index 4af3af4..a6176a7 100644
--- a/http_server/code/package.json
+++ b/http_server/code/package.json
@@ -1,15 +1,17 @@
 {
   "scripts": {
     "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",
+    "socket.io": "^4.8.1",
+    "socket.io-client": "^4.8.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
index 741523f..5105dc3 100644
--- a/http_server/code/src/notifications.jsx
+++ b/http_server/code/src/notifications.jsx
@@ -1,68 +1,68 @@
 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
-		this.connection = new WebSocket("ws://127.0.0.1:3001");
 
-		this.connection.onmessage = (msg) => {
+		io().on("notification", (data) => {
 			let new_id = rand_num();
 			this.setState({
 				notifications: [
-					{ id: new_id, notification: JSON.parse(msg.data) },
+					{ id: new_id, notification: 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"));
diff --git a/http_server/code/waitSocket.mjs b/http_server/code/waitSocket.mjs
deleted file mode 100644
index 1088ae2..0000000
--- a/http_server/code/waitSocket.mjs
+++ /dev/null
@@ -1,16 +0,0 @@
-import { exit } from "process";
-import { WebSocket } from "ws";
-
-async function sleep(time) {
-	return new Promise((resolve) => setTimeout(resolve, time));
-}
-
-while (true) {
-	let socket = new WebSocket("ws://android:3000");
-
-	socket.on("open", () => {
-		exit(0);
-	});
-	socket.on("error", () => {});
-	await sleep(200);
-}
diff --git a/package-lock.json b/package-lock.json
index ec5272d..9ef2514 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,668 +1,788 @@
 {
   "name": "rentgendroid",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "dependencies": {
         "zx": "^7.2.2"
+      },
+      "devDependencies": {
+        "socket.io-client": "^4.8.1"
       }
     },
     "node_modules/@nodelib/fs.scandir": {
       "version": "2.1.5",
       "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
       "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
       "license": "MIT",
       "dependencies": {
         "@nodelib/fs.stat": "2.0.5",
         "run-parallel": "^1.1.9"
       },
       "engines": {
         "node": ">= 8"
       }
     },
     "node_modules/@nodelib/fs.stat": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
       "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
       "license": "MIT",
       "engines": {
         "node": ">= 8"
       }
     },
     "node_modules/@nodelib/fs.walk": {
       "version": "1.2.8",
       "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
       "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
       "license": "MIT",
       "dependencies": {
         "@nodelib/fs.scandir": "2.1.5",
         "fastq": "^1.6.0"
       },
       "engines": {
         "node": ">= 8"
       }
     },
+    "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",
+      "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@types/fs-extra": {
       "version": "11.0.4",
       "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz",
       "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==",
       "license": "MIT",
       "dependencies": {
         "@types/jsonfile": "*",
         "@types/node": "*"
       }
     },
     "node_modules/@types/jsonfile": {
       "version": "6.1.4",
       "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz",
       "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==",
       "license": "MIT",
       "dependencies": {
         "@types/node": "*"
       }
     },
     "node_modules/@types/minimist": {
       "version": "1.2.5",
       "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz",
       "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==",
       "license": "MIT"
     },
     "node_modules/@types/node": {
       "version": "18.19.80",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.80.tgz",
       "integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==",
       "license": "MIT",
       "dependencies": {
         "undici-types": "~5.26.4"
       }
     },
     "node_modules/@types/ps-tree": {
       "version": "1.1.6",
       "resolved": "https://registry.npmjs.org/@types/ps-tree/-/ps-tree-1.1.6.tgz",
       "integrity": "sha512-PtrlVaOaI44/3pl3cvnlK+GxOM3re2526TJvPvh7W+keHIXdV4TE0ylpPBAcvFQCbGitaTXwL9u+RF7qtVeazQ==",
       "license": "MIT"
     },
     "node_modules/@types/which": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.4.tgz",
       "integrity": "sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w==",
       "license": "MIT"
     },
     "node_modules/braces": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
       "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
       "license": "MIT",
       "dependencies": {
         "fill-range": "^7.1.1"
       },
       "engines": {
         "node": ">=8"
       }
     },
     "node_modules/chalk": {
       "version": "5.4.1",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
       "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
       "license": "MIT",
       "engines": {
         "node": "^12.17.0 || ^14.13 || >=16.0.0"
       },
       "funding": {
         "url": "https://github.com/chalk/chalk?sponsor=1"
       }
     },
     "node_modules/data-uri-to-buffer": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
       "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
       "license": "MIT",
       "engines": {
         "node": ">= 12"
       }
     },
+    "node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/dir-glob": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
       "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
       "license": "MIT",
       "dependencies": {
         "path-type": "^4.0.0"
       },
       "engines": {
         "node": ">=8"
       }
     },
     "node_modules/duplexer": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
       "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
       "license": "MIT"
     },
+    "node_modules/engine.io-client": {
+      "version": "6.6.3",
+      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
+      "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@socket.io/component-emitter": "~3.1.0",
+        "debug": "~4.3.1",
+        "engine.io-parser": "~5.2.1",
+        "ws": "~8.17.1",
+        "xmlhttprequest-ssl": "~2.1.1"
+      }
+    },
+    "node_modules/engine.io-parser": {
+      "version": "5.2.3",
+      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+      "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
     "node_modules/event-stream": {
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
       "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==",
       "license": "MIT",
       "dependencies": {
         "duplexer": "~0.1.1",
         "from": "~0",
         "map-stream": "~0.1.0",
         "pause-stream": "0.0.11",
         "split": "0.3",
         "stream-combiner": "~0.0.4",
         "through": "~2.3.1"
       }
     },
     "node_modules/fast-glob": {
       "version": "3.3.3",
       "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
       "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
       "license": "MIT",
       "dependencies": {
         "@nodelib/fs.stat": "^2.0.2",
         "@nodelib/fs.walk": "^1.2.3",
         "glob-parent": "^5.1.2",
         "merge2": "^1.3.0",
         "micromatch": "^4.0.8"
       },
       "engines": {
         "node": ">=8.6.0"
       }
     },
     "node_modules/fastq": {
       "version": "1.19.1",
       "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
       "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
       "license": "ISC",
       "dependencies": {
         "reusify": "^1.0.4"
       }
     },
     "node_modules/fetch-blob": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
       "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
       "funding": [
         {
           "type": "github",
           "url": "https://github.com/sponsors/jimmywarting"
         },
         {
           "type": "paypal",
           "url": "https://paypal.me/jimmywarting"
         }
       ],
       "license": "MIT",
       "dependencies": {
         "node-domexception": "^1.0.0",
         "web-streams-polyfill": "^3.0.3"
       },
       "engines": {
         "node": "^12.20 || >= 14.13"
       }
     },
     "node_modules/fill-range": {
       "version": "7.1.1",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
       "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
       "license": "MIT",
       "dependencies": {
         "to-regex-range": "^5.0.1"
       },
       "engines": {
         "node": ">=8"
       }
     },
     "node_modules/formdata-polyfill": {
       "version": "4.0.10",
       "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
       "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
       "license": "MIT",
       "dependencies": {
         "fetch-blob": "^3.1.2"
       },
       "engines": {
         "node": ">=12.20.0"
       }
     },
     "node_modules/from": {
       "version": "0.1.7",
       "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
       "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==",
       "license": "MIT"
     },
     "node_modules/fs-extra": {
       "version": "11.3.0",
       "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
       "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==",
       "license": "MIT",
       "dependencies": {
         "graceful-fs": "^4.2.0",
         "jsonfile": "^6.0.1",
         "universalify": "^2.0.0"
       },
       "engines": {
         "node": ">=14.14"
       }
     },
     "node_modules/fx": {
       "version": "35.0.0",
       "resolved": "https://registry.npmjs.org/fx/-/fx-35.0.0.tgz",
       "integrity": "sha512-O07q+Lknrom5RUX/u53tjo2KTTLUnL0K703JbqMYb19ORijfJNvijzFqqYXEjdk25T9R14S6t6wHD8fCWXCM0g==",
       "license": "MIT",
       "bin": {
         "fx": "index.js"
       }
     },
     "node_modules/glob-parent": {
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
       "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
       "license": "ISC",
       "dependencies": {
         "is-glob": "^4.0.1"
       },
       "engines": {
         "node": ">= 6"
       }
     },
     "node_modules/globby": {
       "version": "13.2.2",
       "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz",
       "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==",
       "license": "MIT",
       "dependencies": {
         "dir-glob": "^3.0.1",
         "fast-glob": "^3.3.0",
         "ignore": "^5.2.4",
         "merge2": "^1.4.1",
         "slash": "^4.0.0"
       },
       "engines": {
         "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
       },
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
     "node_modules/graceful-fs": {
       "version": "4.2.11",
       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
       "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
       "license": "ISC"
     },
     "node_modules/ignore": {
       "version": "5.3.2",
       "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
       "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
       "license": "MIT",
       "engines": {
         "node": ">= 4"
       }
     },
     "node_modules/is-extglob": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
       "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
       "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
       }
     },
     "node_modules/is-glob": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
       "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
       "license": "MIT",
       "dependencies": {
         "is-extglob": "^2.1.1"
       },
       "engines": {
         "node": ">=0.10.0"
       }
     },
     "node_modules/is-number": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
       "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
       "license": "MIT",
       "engines": {
         "node": ">=0.12.0"
       }
     },
     "node_modules/isexe": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
       "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
       "license": "ISC"
     },
     "node_modules/jsonfile": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
       "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
       "license": "MIT",
       "dependencies": {
         "universalify": "^2.0.0"
       },
       "optionalDependencies": {
         "graceful-fs": "^4.1.6"
       }
     },
     "node_modules/map-stream": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
       "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g=="
     },
     "node_modules/merge2": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
       "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
       "license": "MIT",
       "engines": {
         "node": ">= 8"
       }
     },
     "node_modules/micromatch": {
       "version": "4.0.8",
       "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
       "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
       "license": "MIT",
       "dependencies": {
         "braces": "^3.0.3",
         "picomatch": "^2.3.1"
       },
       "engines": {
         "node": ">=8.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/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/node-domexception": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
       "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
       "funding": [
         {
           "type": "github",
           "url": "https://github.com/sponsors/jimmywarting"
         },
         {
           "type": "github",
           "url": "https://paypal.me/jimmywarting"
         }
       ],
       "license": "MIT",
       "engines": {
         "node": ">=10.5.0"
       }
     },
     "node_modules/node-fetch": {
       "version": "3.3.1",
       "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz",
       "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==",
       "license": "MIT",
       "dependencies": {
         "data-uri-to-buffer": "^4.0.0",
         "fetch-blob": "^3.1.4",
         "formdata-polyfill": "^4.0.10"
       },
       "engines": {
         "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
       },
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/node-fetch"
       }
     },
     "node_modules/path-type": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
       "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
       "license": "MIT",
       "engines": {
         "node": ">=8"
       }
     },
     "node_modules/pause-stream": {
       "version": "0.0.11",
       "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
       "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==",
       "license": [
         "MIT",
         "Apache2"
       ],
       "dependencies": {
         "through": "~2.3"
       }
     },
     "node_modules/picomatch": {
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
       "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
       "license": "MIT",
       "engines": {
         "node": ">=8.6"
       },
       "funding": {
         "url": "https://github.com/sponsors/jonschlinkert"
       }
     },
     "node_modules/ps-tree": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz",
       "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==",
       "license": "MIT",
       "dependencies": {
         "event-stream": "=3.3.4"
       },
       "bin": {
         "ps-tree": "bin/ps-tree.js"
       },
       "engines": {
         "node": ">= 0.10"
       }
     },
     "node_modules/queue-microtask": {
       "version": "1.2.3",
       "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
       "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
       "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/reusify": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
       "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
       "license": "MIT",
       "engines": {
         "iojs": ">=1.0.0",
         "node": ">=0.10.0"
       }
     },
     "node_modules/run-parallel": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
       "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
       "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",
       "dependencies": {
         "queue-microtask": "^1.2.2"
       }
     },
     "node_modules/slash": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
       "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==",
       "license": "MIT",
       "engines": {
         "node": ">=12"
       },
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/socket.io-client": {
+      "version": "4.8.1",
+      "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
+      "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@socket.io/component-emitter": "~3.1.0",
+        "debug": "~4.3.2",
+        "engine.io-client": "~6.6.1",
+        "socket.io-parser": "~4.2.4"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/socket.io-parser": {
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+      "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@socket.io/component-emitter": "~3.1.0",
+        "debug": "~4.3.1"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
     "node_modules/split": {
       "version": "0.3.3",
       "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz",
       "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==",
       "license": "MIT",
       "dependencies": {
         "through": "2"
       },
       "engines": {
         "node": "*"
       }
     },
     "node_modules/stream-combiner": {
       "version": "0.0.4",
       "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz",
       "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==",
       "license": "MIT",
       "dependencies": {
         "duplexer": "~0.1.1"
       }
     },
     "node_modules/through": {
       "version": "2.3.8",
       "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
       "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
       "license": "MIT"
     },
     "node_modules/to-regex-range": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
       "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
       "license": "MIT",
       "dependencies": {
         "is-number": "^7.0.0"
       },
       "engines": {
         "node": ">=8.0"
       }
     },
     "node_modules/undici-types": {
       "version": "5.26.5",
       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
       "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
       "license": "MIT"
     },
     "node_modules/universalify": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
       "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
       "license": "MIT",
       "engines": {
         "node": ">= 10.0.0"
       }
     },
     "node_modules/web-streams-polyfill": {
       "version": "3.3.3",
       "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
       "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
       "license": "MIT",
       "engines": {
         "node": ">= 8"
       }
     },
     "node_modules/webpod": {
       "version": "0.0.2",
       "resolved": "https://registry.npmjs.org/webpod/-/webpod-0.0.2.tgz",
       "integrity": "sha512-cSwwQIeg8v4i3p4ajHhwgR7N6VyxAf+KYSSsY6Pd3aETE+xEU4vbitz7qQkB0I321xnhDdgtxuiSfk5r/FVtjg==",
       "license": "MIT",
       "bin": {
         "webpod": "dist/index.js"
       }
     },
     "node_modules/which": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
       "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==",
       "license": "ISC",
       "dependencies": {
         "isexe": "^2.0.0"
       },
       "bin": {
         "node-which": "bin/which.js"
       },
       "engines": {
         "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
       }
     },
+    "node_modules/ws": {
+      "version": "8.17.1",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+      "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/xmlhttprequest-ssl": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
+      "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/yaml": {
       "version": "2.7.0",
       "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
       "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
       "license": "ISC",
       "bin": {
         "yaml": "bin.mjs"
       },
       "engines": {
         "node": ">= 14"
       }
     },
     "node_modules/zx": {
       "version": "7.2.3",
       "resolved": "https://registry.npmjs.org/zx/-/zx-7.2.3.tgz",
       "integrity": "sha512-QODu38nLlYXg/B/Gw7ZKiZrvPkEsjPN3LQ5JFXM7h0JvwhEdPNNl+4Ao1y4+o3CLNiDUNcwzQYZ4/Ko7kKzCMA==",
       "license": "Apache-2.0",
       "dependencies": {
         "@types/fs-extra": "^11.0.1",
         "@types/minimist": "^1.2.2",
         "@types/node": "^18.16.3",
         "@types/ps-tree": "^1.1.2",
         "@types/which": "^3.0.0",
         "chalk": "^5.2.0",
         "fs-extra": "^11.1.1",
         "fx": "*",
         "globby": "^13.1.4",
         "minimist": "^1.2.8",
         "node-fetch": "3.3.1",
         "ps-tree": "^1.2.0",
         "webpod": "^0",
         "which": "^3.0.0",
         "yaml": "^2.2.2"
       },
       "bin": {
         "zx": "build/cli.js"
       },
       "engines": {
         "node": ">= 16.0.0"
       }
     }
   }
 }
diff --git a/package.json b/package.json
index 762819c..debfbdf 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,12 @@
 {
   "scripts": {
     "start": "zx start.mjs up",
     "stop": "zx start.mjs down"
   },
-
   "dependencies": {
     "zx": "^7.2.2"
+  },
+  "devDependencies": {
+    "socket.io-client": "^4.8.1"
   }
 }
diff --git a/pre_android/Dockerfile b/pre_android/Dockerfile
index a74afaf..5520964 100644
--- a/pre_android/Dockerfile
+++ b/pre_android/Dockerfile
@@ -1,9 +1,9 @@
 FROM runmymind/docker-android-sdk:ubuntu-standalone-20230511
 
 ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/android-sdk-linux/cmdline-tools/latest/bin:/opt/android-sdk-linux/cmdline-tools/tools/bin:/opt/android-sdk-linux/tools/bin:/opt/android-sdk-linux/build-tools/32.0.0:/opt/android-sdk-linux/platform-tools:/opt/android-sdk-linux/emulator:/opt/android-sdk-linux/bin
 
-RUN sdkmanager --channel=2 "system-images;android-30;google_apis;x86_64" \
-	&& echo no | avdmanager create avd -n virtual_dev -b google_apis/x86_64 -k "system-images;android-30;google_apis;x86_64" \
+RUN sdkmanager --channel=2 "system-images;android-35;google_apis;x86_64" \
+	&& echo no | avdmanager create avd -n virtual_dev -b google_apis/x86_64 -k "system-images;android-35;google_apis;x86_64" \
 	&& apt-get update && apt-get install -y iproute2 iputils-ping npm git libxml2-utils telnet
 
 CMD bash /preconf/docker-entrypoint.sh