diff --git a/backend/.env.example b/backend/.env.example
index 0438ec4..495ee33 100644
--- a/backend/.env.example
+++ b/backend/.env.example
@@ -1,5 +1,9 @@
-#
+# Config
JWT_SECRET=
AUTH_USERNAME=
AUTH_PASSWORD=
+
+# Apps
+PC_MAC_ADDR=
+TERMINAL_SHELL=
diff --git a/backend/bun.lockb b/backend/bun.lockb
deleted file mode 100755
index 05773cd..0000000
Binary files a/backend/bun.lockb and /dev/null differ
diff --git a/backend/global.d.ts b/backend/global.d.ts
new file mode 100644
index 0000000..3ce931b
--- /dev/null
+++ b/backend/global.d.ts
@@ -0,0 +1 @@
+declare module "wol";
diff --git a/backend/index.ts b/backend/index.ts
index ba5f0e7..d6986a3 100644
--- a/backend/index.ts
+++ b/backend/index.ts
@@ -1,8 +1,11 @@
+import "dotenv/config";
import { Hono } from "hono";
import { cors } from "hono/cors";
-import { serveStatic } from "hono/bun";
import { HTTPException } from "hono/http-exception";
+import { serveStatic } from "@hono/node-server/serve-static";
+import { serve } from "@hono/node-server";
import routes from "./routes/_routes";
+import createWsServer from "./websocket";
const app = new Hono()
.use(cors())
@@ -15,4 +18,8 @@ const app = new Hono()
return c.json({ message: err.message }, 500);
});
-export default app;
+const server = serve(app, (info) => {
+ console.log(`App listening on http://${info.address}:${info.port}`);
+});
+
+createWsServer(server);
diff --git a/backend/lib/terminal.ts b/backend/lib/terminal.ts
new file mode 100644
index 0000000..d936bf2
--- /dev/null
+++ b/backend/lib/terminal.ts
@@ -0,0 +1,46 @@
+import Pty from "node-pty";
+import type { WebSocket } from "ws";
+
+type TerminalClient = WebSocket & { tty?: Pty.IPty | null };
+
+export const createTerminalSession = (client: TerminalClient) => {
+ // Each client will have own terminal
+ const tty = Pty.spawn(process.env.TERMINAL_SHELL || "bash", [], {
+ name: "xterm-color",
+ cols: 80,
+ rows: 24,
+ cwd: process.env.PWD,
+ env: process.env,
+ });
+ client.tty = tty;
+
+ tty.onExit(() => {
+ client.tty = null;
+ client.close();
+ });
+
+ tty.onData(function (data) {
+ client.send(data);
+ });
+
+ client.on("close", function () {
+ if (client.tty) {
+ client.tty.kill("SIGINT");
+ client.tty = null;
+ }
+ });
+
+ client.on("message", function (data) {
+ const msg = data.toString("utf-8");
+
+ if (msg.startsWith("resize:")) {
+ const [cols, rows] = msg.split(":")[1].split(",");
+ if (client.tty) {
+ client.tty.resize(parseInt(cols), parseInt(rows));
+ }
+ return;
+ }
+
+ client.tty && client.tty.write(msg);
+ });
+};
diff --git a/backend/package-lock.json b/backend/package-lock.json
new file mode 100644
index 0000000..b665b1d
--- /dev/null
+++ b/backend/package-lock.json
@@ -0,0 +1,165 @@
+{
+ "name": "backend",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "backend",
+ "dependencies": {
+ "@hono/zod-validator": "^0.2.0",
+ "hono": "^4.1.0",
+ "nanoid": "^5.0.6",
+ "systeminformation": "^5.22.2",
+ "wol": "^1.0.7",
+ "zod": "^3.22.4"
+ },
+ "devDependencies": {
+ "@types/bun": "latest",
+ "@types/jsonwebtoken": "^9.0.6"
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0"
+ }
+ },
+ "node_modules/@hono/zod-validator": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@hono/zod-validator/-/zod-validator-0.2.0.tgz",
+ "integrity": "sha512-PC7akbA/DCFY406BL3+ogjYb+7Fgfs/6XPvyURBYMczo0M7kYsTUMnF8hA9mez1RORNCWPqXuFGfKrkoUVPvrQ==",
+ "peerDependencies": {
+ "hono": ">=3.9.0",
+ "zod": "^3.19.1"
+ }
+ },
+ "node_modules/@types/bun": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.0.8.tgz",
+ "integrity": "sha512-E6UWZuN4ymAxzUBWVIGDHJ3Zey7I8cMzDZ+cB1BqhZsmd1uPb9iAQzpWMruY1mKzsuD3R+dZPoBkZz8QL1KhSA==",
+ "dev": true,
+ "dependencies": {
+ "bun-types": "1.0.29"
+ }
+ },
+ "node_modules/@types/jsonwebtoken": {
+ "version": "9.0.6",
+ "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz",
+ "integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "20.11.28",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz",
+ "integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==",
+ "dev": true,
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/@types/ws": {
+ "version": "8.5.10",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
+ "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/bun-types": {
+ "version": "1.0.29",
+ "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.0.29.tgz",
+ "integrity": "sha512-Z+U1ORr/2UCwxelIZxE83pyPLclviYL9UewQCNEUmGeLObY8ao+3WF3D8N1+NMv2+S+hUWsdBJam+4GoPEz35g==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "~20.11.3",
+ "@types/ws": "~8.5.10"
+ }
+ },
+ "node_modules/hono": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.1.0.tgz",
+ "integrity": "sha512-9no6DCHb4ijB1tWdFXU6JnrnFgzwVZ1cnIcS1BjAFnMcjbtBTOMsQrDrPH3GXbkNEEEkj8kWqcYBy8Qc0bBkJQ==",
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.6.tgz",
+ "integrity": "sha512-rRq0eMHoGZxlvaFOUdK1Ev83Bd1IgzzR+WJ3IbDJ7QOSdAxYjlurSPqFs9s4lJg29RT6nPwizFtJhQS6V5xgiA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.js"
+ },
+ "engines": {
+ "node": "^18 || >=20"
+ }
+ },
+ "node_modules/systeminformation": {
+ "version": "5.22.3",
+ "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.22.3.tgz",
+ "integrity": "sha512-pTU7/kMQaYfUs929Uhl+C2heTNhKIgPuoiV5s2TMO3SLf10Zr7Rl/ZvVaiYWFbZVdFsZ+9tSXsybGdBQcr+xww==",
+ "os": [
+ "darwin",
+ "linux",
+ "win32",
+ "freebsd",
+ "openbsd",
+ "netbsd",
+ "sunos",
+ "android"
+ ],
+ "bin": {
+ "systeminformation": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "Buy me a coffee",
+ "url": "https://www.buymeacoffee.com/systeminfo"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.4.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
+ "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
+ "peer": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "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==",
+ "dev": true
+ },
+ "node_modules/wol": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/wol/-/wol-1.0.7.tgz",
+ "integrity": "sha512-kg7ETY8g3V5+3GVhUfWCVjeXuCmfrX6xfw4cw4c88+MtoxkbFmcs9Y5yhT1wwOL8inogFUQZ8JMzH9OekaaawQ==",
+ "bin": {
+ "wake": "bin/wake"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.22.4",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",
+ "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/backend/package.json b/backend/package.json
index d426a1f..117baf7 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -3,21 +3,28 @@
"module": "index.ts",
"type": "module",
"scripts": {
- "dev": "bun run --watch index.ts",
- "start": "bun run index.ts"
+ "dev": "tsx --watch index.ts",
+ "start": "tsx index.ts"
},
"devDependencies": {
- "@types/bun": "latest",
- "@types/jsonwebtoken": "^9.0.6"
+ "@types/jsonwebtoken": "^9.0.6",
+ "@types/node": "^20.11.28",
+ "@types/ws": "^8.5.10",
+ "tsx": "^4.7.1"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
+ "@hono/node-server": "^1.8.2",
"@hono/zod-validator": "^0.2.0",
+ "dotenv": "^16.4.5",
"hono": "^4.1.0",
"nanoid": "^5.0.6",
+ "node-pty": "^1.0.0",
"systeminformation": "^5.22.2",
+ "wol": "^1.0.7",
+ "ws": "^8.16.0",
"zod": "^3.22.4"
}
-}
\ No newline at end of file
+}
diff --git a/backend/routes/_routes.ts b/backend/routes/_routes.ts
index ef7e252..09c4916 100644
--- a/backend/routes/_routes.ts
+++ b/backend/routes/_routes.ts
@@ -2,13 +2,15 @@ import { Hono } from "hono";
import auth from "./auth";
import system from "./system";
import _process from "./process";
+import apps from "./apps";
import { authMiddleware } from "../lib/jwt";
const routes = new Hono()
.route("/auth", auth)
.use(authMiddleware)
.route("/system", system)
- .route("/process", _process);
+ .route("/process", _process)
+ .route("/apps", apps);
export type AppType = typeof routes;
diff --git a/backend/routes/apps.ts b/backend/routes/apps.ts
new file mode 100644
index 0000000..d75f5d0
--- /dev/null
+++ b/backend/routes/apps.ts
@@ -0,0 +1,22 @@
+import { Hono } from "hono";
+import wol from "wol";
+import { HTTPException } from "hono/http-exception";
+
+const route = new Hono().post("/wakepc", async (c) => {
+ const { PC_MAC_ADDR } = process.env;
+
+ try {
+ await new Promise((resolve, reject) => {
+ wol.wake(PC_MAC_ADDR || "", (err: any, res: any) =>
+ err ? reject(err) : resolve(res)
+ );
+ });
+ } catch (err) {
+ console.log(err);
+ throw new HTTPException(400, { message: "Cannot wake pc up!" });
+ }
+
+ return c.json({ message: "waking up..." });
+});
+
+export default route;
diff --git a/backend/websocket.ts b/backend/websocket.ts
new file mode 100644
index 0000000..41935ea
--- /dev/null
+++ b/backend/websocket.ts
@@ -0,0 +1,28 @@
+import { WebSocketServer } from "ws";
+import { verifyToken } from "./lib/jwt";
+import { createTerminalSession } from "./lib/terminal";
+
+const createWsServer = (server: any) => {
+ const wss = new WebSocketServer({ server: server as never });
+
+ wss.on("connection", async (ws, req) => {
+ const url = new URL(req.url || "", `http://${req.headers.host}`);
+ const token = url.searchParams.get("token");
+
+ try {
+ await verifyToken(token || "");
+ } catch (err) {
+ console.log(err);
+ ws.close();
+ return;
+ }
+
+ if (url.pathname === "/terminal") {
+ createTerminalSession(ws);
+ }
+
+ ws.on("error", console.error);
+ });
+};
+
+export default createWsServer;
diff --git a/backend/yarn.lock b/backend/yarn.lock
new file mode 100644
index 0000000..de3ed25
--- /dev/null
+++ b/backend/yarn.lock
@@ -0,0 +1,257 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@esbuild/aix-ppc64@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz#d1bc06aedb6936b3b6d313bf809a5a40387d2b7f"
+ integrity sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==
+
+"@esbuild/android-arm64@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz#7ad65a36cfdb7e0d429c353e00f680d737c2aed4"
+ integrity sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==
+
+"@esbuild/android-arm@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.12.tgz#b0c26536f37776162ca8bde25e42040c203f2824"
+ integrity sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==
+
+"@esbuild/android-x64@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.12.tgz#cb13e2211282012194d89bf3bfe7721273473b3d"
+ integrity sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==
+
+"@esbuild/darwin-arm64@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz#cbee41e988020d4b516e9d9e44dd29200996275e"
+ integrity sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==
+
+"@esbuild/darwin-x64@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz#e37d9633246d52aecf491ee916ece709f9d5f4cd"
+ integrity sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==
+
+"@esbuild/freebsd-arm64@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz#1ee4d8b682ed363b08af74d1ea2b2b4dbba76487"
+ integrity sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==
+
+"@esbuild/freebsd-x64@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz#37a693553d42ff77cd7126764b535fb6cc28a11c"
+ integrity sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==
+
+"@esbuild/linux-arm64@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz#be9b145985ec6c57470e0e051d887b09dddb2d4b"
+ integrity sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==
+
+"@esbuild/linux-arm@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz#207ecd982a8db95f7b5279207d0ff2331acf5eef"
+ integrity sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==
+
+"@esbuild/linux-ia32@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz#d0d86b5ca1562523dc284a6723293a52d5860601"
+ integrity sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==
+
+"@esbuild/linux-loong64@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz#9a37f87fec4b8408e682b528391fa22afd952299"
+ integrity sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==
+
+"@esbuild/linux-mips64el@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz#4ddebd4e6eeba20b509d8e74c8e30d8ace0b89ec"
+ integrity sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==
+
+"@esbuild/linux-ppc64@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz#adb67dadb73656849f63cd522f5ecb351dd8dee8"
+ integrity sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==
+
+"@esbuild/linux-riscv64@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz#11bc0698bf0a2abf8727f1c7ace2112612c15adf"
+ integrity sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==
+
+"@esbuild/linux-s390x@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz#e86fb8ffba7c5c92ba91fc3b27ed5a70196c3cc8"
+ integrity sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==
+
+"@esbuild/linux-x64@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz#5f37cfdc705aea687dfe5dfbec086a05acfe9c78"
+ integrity sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==
+
+"@esbuild/netbsd-x64@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz#29da566a75324e0d0dd7e47519ba2f7ef168657b"
+ integrity sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==
+
+"@esbuild/openbsd-x64@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz#306c0acbdb5a99c95be98bdd1d47c916e7dc3ff0"
+ integrity sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==
+
+"@esbuild/sunos-x64@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz#0933eaab9af8b9b2c930236f62aae3fc593faf30"
+ integrity sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==
+
+"@esbuild/win32-arm64@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz#773bdbaa1971b36db2f6560088639ccd1e6773ae"
+ integrity sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==
+
+"@esbuild/win32-ia32@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz#000516cad06354cc84a73f0943a4aa690ef6fd67"
+ integrity sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==
+
+"@esbuild/win32-x64@0.19.12":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz#c57c8afbb4054a3ab8317591a0b7320360b444ae"
+ integrity sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==
+
+"@hono/node-server@^1.8.2":
+ version "1.8.2"
+ resolved "https://registry.yarnpkg.com/@hono/node-server/-/node-server-1.8.2.tgz#940b3a0dbd7adbc510b79b626f3603258493354b"
+ integrity sha512-h8l2TBLCPHZBUrrkosZ6L5CpBLj6zdESyF4B+zngiCDF7aZFQJ0alVbLx7jn8PCVi9EyoFf8a4hOZFi1tD95EA==
+
+"@hono/zod-validator@^0.2.0":
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/@hono/zod-validator/-/zod-validator-0.2.0.tgz#77d8f1f167cba85b008a52f594ed823c841b2300"
+ integrity sha512-PC7akbA/DCFY406BL3+ogjYb+7Fgfs/6XPvyURBYMczo0M7kYsTUMnF8hA9mez1RORNCWPqXuFGfKrkoUVPvrQ==
+
+"@types/jsonwebtoken@^9.0.6":
+ version "9.0.6"
+ resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz#d1af3544d99ad992fb6681bbe60676e06b032bd3"
+ integrity sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==
+ dependencies:
+ "@types/node" "*"
+
+"@types/node@*", "@types/node@^20.11.28":
+ version "20.11.28"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.28.tgz#4fd5b2daff2e580c12316e457473d68f15ee6f66"
+ integrity sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==
+ dependencies:
+ undici-types "~5.26.4"
+
+"@types/ws@^8.5.10":
+ version "8.5.10"
+ resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787"
+ integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==
+ dependencies:
+ "@types/node" "*"
+
+dotenv@^16.4.5:
+ version "16.4.5"
+ resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
+ integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
+
+esbuild@~0.19.10:
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.12.tgz#dc82ee5dc79e82f5a5c3b4323a2a641827db3e04"
+ integrity sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==
+ optionalDependencies:
+ "@esbuild/aix-ppc64" "0.19.12"
+ "@esbuild/android-arm" "0.19.12"
+ "@esbuild/android-arm64" "0.19.12"
+ "@esbuild/android-x64" "0.19.12"
+ "@esbuild/darwin-arm64" "0.19.12"
+ "@esbuild/darwin-x64" "0.19.12"
+ "@esbuild/freebsd-arm64" "0.19.12"
+ "@esbuild/freebsd-x64" "0.19.12"
+ "@esbuild/linux-arm" "0.19.12"
+ "@esbuild/linux-arm64" "0.19.12"
+ "@esbuild/linux-ia32" "0.19.12"
+ "@esbuild/linux-loong64" "0.19.12"
+ "@esbuild/linux-mips64el" "0.19.12"
+ "@esbuild/linux-ppc64" "0.19.12"
+ "@esbuild/linux-riscv64" "0.19.12"
+ "@esbuild/linux-s390x" "0.19.12"
+ "@esbuild/linux-x64" "0.19.12"
+ "@esbuild/netbsd-x64" "0.19.12"
+ "@esbuild/openbsd-x64" "0.19.12"
+ "@esbuild/sunos-x64" "0.19.12"
+ "@esbuild/win32-arm64" "0.19.12"
+ "@esbuild/win32-ia32" "0.19.12"
+ "@esbuild/win32-x64" "0.19.12"
+
+fsevents@~2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
+ integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
+get-tsconfig@^4.7.2:
+ version "4.7.3"
+ resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.3.tgz#0498163d98f7b58484dd4906999c0c9d5f103f83"
+ integrity sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==
+ dependencies:
+ resolve-pkg-maps "^1.0.0"
+
+hono@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/hono/-/hono-4.1.0.tgz#62cef81df0dbf731643155e1e5c1b9dffb230dc4"
+ integrity sha512-9no6DCHb4ijB1tWdFXU6JnrnFgzwVZ1cnIcS1BjAFnMcjbtBTOMsQrDrPH3GXbkNEEEkj8kWqcYBy8Qc0bBkJQ==
+
+nan@^2.17.0:
+ version "2.19.0"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.19.0.tgz#bb58122ad55a6c5bc973303908d5b16cfdd5a8c0"
+ integrity sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==
+
+nanoid@^5.0.6:
+ version "5.0.6"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.6.tgz#7f99a033aa843e4dcf9778bdaec5eb02f4dc44d5"
+ integrity sha512-rRq0eMHoGZxlvaFOUdK1Ev83Bd1IgzzR+WJ3IbDJ7QOSdAxYjlurSPqFs9s4lJg29RT6nPwizFtJhQS6V5xgiA==
+
+node-pty@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.0.0.tgz#7daafc0aca1c4ca3de15c61330373af4af5861fd"
+ integrity sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==
+ dependencies:
+ nan "^2.17.0"
+
+resolve-pkg-maps@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f"
+ integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==
+
+systeminformation@^5.22.2:
+ version "5.22.3"
+ resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.22.3.tgz#33ef8bd045125d672f64e7000015fefae07a77cb"
+ integrity sha512-pTU7/kMQaYfUs929Uhl+C2heTNhKIgPuoiV5s2TMO3SLf10Zr7Rl/ZvVaiYWFbZVdFsZ+9tSXsybGdBQcr+xww==
+
+tsx@^4.7.1:
+ version "4.7.1"
+ resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.7.1.tgz#27af6cbf4e1cdfcb9b5425b1c61bb7e668eb5e84"
+ integrity sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==
+ dependencies:
+ esbuild "~0.19.10"
+ get-tsconfig "^4.7.2"
+ optionalDependencies:
+ fsevents "~2.3.3"
+
+undici-types@~5.26.4:
+ version "5.26.5"
+ resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
+ integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
+
+wol@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/wol/-/wol-1.0.7.tgz#a2e70efca2a28324a744a5d12331d359da470ff7"
+ integrity sha512-kg7ETY8g3V5+3GVhUfWCVjeXuCmfrX6xfw4cw4c88+MtoxkbFmcs9Y5yhT1wwOL8inogFUQZ8JMzH9OekaaawQ==
+
+ws@^8.16.0:
+ version "8.16.0"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4"
+ integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==
+
+zod@^3.22.4:
+ version "3.22.4"
+ resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"
+ integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==
diff --git a/package.json b/package.json
index 62f2192..f418ca6 100644
--- a/package.json
+++ b/package.json
@@ -31,10 +31,14 @@
"react-native-safe-area-context": "4.8.2",
"react-native-screens": "~3.29.0",
"react-native-svg": "14.1.0",
+ "react-native-toast-notifications": "^3.4.0",
"react-native-web": "~0.19.6",
"react-query": "^3.39.3",
"twrnc": "^4.1.0",
"typescript": "^5.3.0",
+ "xterm": "^5.3.0",
+ "xterm-addon-attach": "^0.9.0",
+ "xterm-addon-fit": "^0.8.0",
"zod": "^3.22.4",
"zustand": "^4.5.2"
},
diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx
index ec84d23..9d7b989 100644
--- a/src/app/_layout.tsx
+++ b/src/app/_layout.tsx
@@ -7,8 +7,10 @@ import { cn, tw } from "@/lib/utils";
import { useDeviceContext } from "twrnc";
import { StatusBar } from "expo-status-bar";
import { useSafeAreaInsets } from "react-native-safe-area-context";
+import Toast from "react-native-toast-notifications";
import { useStore } from "zustand";
import authStore from "@/stores/authStore";
+import { toastStore } from "@/stores/toastStore";
const RootLayout = () => {
const insets = useSafeAreaInsets();
@@ -39,6 +41,11 @@ const RootLayout = () => {
+ {
+ toastStore.setState(ref);
+ }}
+ />
);
};
diff --git a/src/app/apps/lib.ts b/src/app/apps/lib.ts
new file mode 100644
index 0000000..9b652e2
--- /dev/null
+++ b/src/app/apps/lib.ts
@@ -0,0 +1,13 @@
+// src/app/apps/lib.ts
+
+import api from "@/lib/api";
+import { showToast } from "@/stores/toastStore";
+
+export const wakePcUp = async () => {
+ try {
+ await api.apps.wakepc.$post();
+ showToast("Waking up PC...");
+ } catch (err) {
+ showToast("Cannot wake up the PC!", { type: "danger" });
+ }
+};
diff --git a/src/app/apps/terminal.tsx b/src/app/apps/terminal.tsx
new file mode 100644
index 0000000..dd0419f
--- /dev/null
+++ b/src/app/apps/terminal.tsx
@@ -0,0 +1,93 @@
+import { Platform } from "react-native";
+import { Terminal } from "xterm";
+import { FitAddon } from "xterm-addon-fit";
+import { AttachAddon } from "xterm-addon-attach";
+
+import Box from "@ui/Box";
+import Text from "@ui/Text";
+import React, { useEffect, useRef } from "react";
+import "xterm/css/xterm.css";
+import { API_BASEURL } from "@/lib/constants";
+import authStore, { useAuth } from "@/stores/authStore";
+
+const isWeb = Platform.OS === "web";
+
+const TerminalPage = () => {
+ const { token } = useAuth();
+ const terminalRef = useRef();
+ const fitRef = useRef(null);
+
+ useEffect(() => {
+ if (!isWeb || !token) {
+ return;
+ }
+
+ const term = new Terminal({ theme: { background: "#1d1e2b" } });
+ const fitAddon = new FitAddon();
+ term.loadAddon(fitAddon);
+
+ const baseUrl = API_BASEURL.replace("https://", "wss://").replace(
+ "http://",
+ "ws://"
+ );
+ const socket = new WebSocket(baseUrl + "/terminal?token=" + token);
+ const attachAddon = new AttachAddon(socket);
+
+ // Attach the socket to term
+ term.loadAddon(attachAddon);
+ term.open(terminalRef.current);
+
+ fitAddon.fit();
+ fitRef.current = fitAddon;
+
+ const sendResizeSignal = (data: { cols: number; rows: number }) => {
+ socket.send("resize:" + [data.cols, data.rows].join(","));
+ };
+
+ term.onResize(sendResizeSignal);
+ setTimeout(() => {
+ sendResizeSignal({ cols: term.cols, rows: term.rows });
+ }, 1000);
+
+ return () => {
+ attachAddon.dispose();
+ fitAddon.dispose();
+ term.dispose();
+ fitRef.current = null;
+ };
+ }, [token]);
+
+ useEffect(() => {
+ if (!isWeb) {
+ return;
+ }
+
+ const onResize = () => {
+ if (fitRef.current) {
+ fitRef.current.fit();
+ }
+ };
+
+ window.addEventListener("resize", onResize);
+ return () => {
+ window.removeEventListener("resize", onResize);
+ };
+ }, []);
+
+ if (!isWeb) {
+ return (
+
+ Only web platform suppported.
+
+ );
+ }
+
+ return (
+
+ );
+};
+
+export default TerminalPage;
diff --git a/src/app/index/_sections/Apps.tsx b/src/app/index/_sections/Apps.tsx
new file mode 100644
index 0000000..15fbddb
--- /dev/null
+++ b/src/app/index/_sections/Apps.tsx
@@ -0,0 +1,57 @@
+import React, { ComponentProps } from "react";
+import Text from "@ui/Text";
+import Box from "@ui/Box";
+import { Ionicons } from "@ui/Icons";
+import { HStack } from "@ui/Stack";
+import Button from "@ui/Button";
+import { useNavigation } from "expo-router";
+import { wakePcUp } from "@/app/apps/lib";
+
+type Props = ComponentProps;
+
+const Apps = (props: Props) => {
+ const navigation = useNavigation();
+
+ const appList = [
+ {
+ name: "Terminal",
+ icon: ,
+ path: "terminal",
+ },
+ {
+ name: "PC Control",
+ icon: ,
+ action: wakePcUp,
+ },
+ ];
+
+ return (
+
+ Apps
+
+ {appList.map((app, idx) => (
+
+
+ );
+};
+
+export default React.memo(Apps);
diff --git a/src/app/index/_sections/Summary.tsx b/src/app/index/_sections/Summary.tsx
index 7a5b207..b154ad3 100644
--- a/src/app/index/_sections/Summary.tsx
+++ b/src/app/index/_sections/Summary.tsx
@@ -11,7 +11,7 @@ type Props = {
const Summary = ({ data }: Props) => {
return (
-
+
{dayjs(data?.date).format("HH:mm")}
{dayjs(data?.date).format("dddd, DD MMM YYYY")}
diff --git a/src/app/index/index.tsx b/src/app/index/index.tsx
index fb5eb5e..5221dc9 100644
--- a/src/app/index/index.tsx
+++ b/src/app/index/index.tsx
@@ -7,6 +7,9 @@ import Summary from "./_sections/Summary";
import Storage from "./_sections/Storage";
import Container from "@ui/Container";
import { useAuth } from "@/stores/authStore";
+import { HStack } from "@ui/Stack";
+import Box from "@ui/Box";
+import Apps from "./_sections/Apps";
const HomePage = () => {
const { isLoggedIn } = useAuth();
@@ -22,12 +25,17 @@ const HomePage = () => {
}
return (
-
- Home Lab
-
-
-
-
+
+
+
+ Home Lab
+
+
+
+
+
+
+
);
};
diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx
index d5b3190..9a9d609 100644
--- a/src/components/ui/Button.tsx
+++ b/src/components/ui/Button.tsx
@@ -60,6 +60,7 @@ interface ButtonProps
labelClasses?: string;
className?: string;
icon?: React.ReactNode;
+ iconClassName?: string;
children?: string;
}
function Button({
@@ -69,12 +70,15 @@ function Button({
variant,
size,
icon,
+ iconClassName,
children,
...props
}: ButtonProps) {
- const textStyles = cn(
- buttonTextVariants({ variant, size, className: labelClasses })
- );
+ const textStyles = buttonTextVariants({
+ variant,
+ size,
+ className: labelClasses,
+ });
return (
- {icon ? {icon} : null}
+ {icon ? (
+ {icon}
+ ) : null}
{label || children ? (
- {label || children}
+ {label || children}
) : null}
);
diff --git a/src/stores/toastStore.ts b/src/stores/toastStore.ts
new file mode 100644
index 0000000..4759c50
--- /dev/null
+++ b/src/stores/toastStore.ts
@@ -0,0 +1,14 @@
+import { createStore } from "zustand";
+import { Toast, ToastOptions } from "react-native-toast-notifications";
+
+export const toastStore = createStore(() => null);
+
+export const showToast = (
+ message: string | JSX.Element,
+ toastOptions?: ToastOptions
+) => {
+ const toast = toastStore.getState();
+ if (toast) {
+ toast.show(message, toastOptions);
+ }
+};
diff --git a/yarn.lock b/yarn.lock
index 5e34491..3669b80 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6406,6 +6406,11 @@ react-native-svg@14.1.0:
css-select "^5.1.0"
css-tree "^1.1.3"
+react-native-toast-notifications@^3.4.0:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/react-native-toast-notifications/-/react-native-toast-notifications-3.4.0.tgz#9f2c79ec80fba4af6b788ecddf704f94cf21dbc2"
+ integrity sha512-ZvB//jLhRiBRemtcH7vGP1maiKCikqtW4aDqo+QYvEIOcX0y3GrjxRayVAqI4oh0qJgd/26DkbM8COobj+0MEQ==
+
react-native-web@~0.19.6:
version "0.19.10"
resolved "https://registry.yarnpkg.com/react-native-web/-/react-native-web-0.19.10.tgz#5f7205f8909c0889bc89c9fde7c6e287defa7c63"
@@ -7827,6 +7832,21 @@ xtend@~4.0.1:
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
+xterm-addon-attach@^0.9.0:
+ version "0.9.0"
+ resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.9.0.tgz#dd18057f147a402de1df852c1de4c8f3b63b37be"
+ integrity sha512-NykWWOsobVZPPK3P9eFkItrnBK9Lw0f94uey5zhqIVB1bhswdVBfl+uziEzSOhe2h0rT9wD0wOeAYsdSXeavPw==
+
+xterm-addon-fit@^0.8.0:
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.8.0.tgz#48ca99015385141918f955ca7819e85f3691d35f"
+ integrity sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw==
+
+xterm@^5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0.tgz#867daf9cc826f3d45b5377320aabd996cb0fce46"
+ integrity sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==
+
y18n@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"