feat: add Web App Port management alongside API Server Port and fix square 512x512 app launcher icon

This commit is contained in:
Schwitzz
2026-10-30 14:23:12 -05:00
parent 1d209fedd2
commit 29dbc27c41
6 changed files with 161 additions and 18 deletions
+60
View File
@@ -8,6 +8,7 @@ import {
statSync,
writeFileSync,
} from "node:fs";
import { createServer, type Server as HttpServer } from "node:http";
import { homedir } from "node:os";
import { basename, dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
@@ -101,10 +102,14 @@ export async function buildApp() {
interface PersistedServerSysConfig {
enabled: boolean;
port: number;
webPort: number;
startOnBoot: boolean;
projectDirectory: string;
}
let secondaryWebServer: HttpServer | null = null;
let activeWebPort: number = 5173;
function loadPersistedServerSysConfig(): PersistedServerSysConfig {
try {
if (existsSync(sysConfigPath)) {
@@ -115,6 +120,10 @@ export async function buildApp() {
typeof raw.port === "number" && raw.port >= 1024 && raw.port <= 65535
? raw.port
: config.port,
webPort:
typeof raw.webPort === "number" && raw.webPort >= 1024 && raw.webPort <= 65535
? raw.webPort
: 5173,
startOnBoot:
typeof raw.startOnBoot === "boolean"
? raw.startOnBoot
@@ -131,11 +140,36 @@ export async function buildApp() {
return {
enabled: true,
port: config.port,
webPort: 5173,
startOnBoot: existsSync(autostartDesktopPath) || existsSync(systemdServicePath),
projectDirectory: defaultProjectDir,
};
}
async function bindSecondaryWebPort(targetWebPort: number, enabled: boolean): Promise<number> {
activeWebPort = targetWebPort;
if (secondaryWebServer) {
await new Promise<void>((res) => secondaryWebServer!.close(() => res()));
secondaryWebServer = null;
}
if (!enabled || targetWebPort === config.port) {
return targetWebPort;
}
return await new Promise<number>((res) => {
const srv = createServer((req, reply) => {
app.server.emit("request", req, reply);
});
srv.once("error", () => {
// Port may already be served by Vite dev server (e.g. :5173); keep activeWebPort
res(targetWebPort);
});
srv.listen(targetWebPort, "0.0.0.0", () => {
secondaryWebServer = srv;
res(targetWebPort);
});
});
}
function applySystemStartupFiles(cfg: PersistedServerSysConfig): boolean {
try {
mkdirSync(sysConfigDir, { recursive: true });
@@ -225,6 +259,8 @@ export async function buildApp() {
enabled: persisted.enabled,
port: persisted.port,
activePort: config.port,
webPort: persisted.webPort,
activeWebPort: activeWebPort || persisted.webPort,
startOnBoot: persisted.startOnBoot,
projectDirectory: persisted.projectDirectory,
autostartPath: autostartDesktopPath,
@@ -240,6 +276,11 @@ export async function buildApp() {
if (!Number.isFinite(nextPort) || nextPort < 1024 || nextPort > 65535) {
return reply.code(400).send({ error: "invalid_port" });
}
const nextWebPort =
typeof body.webPort === "number" ? Math.trunc(body.webPort) : current.webPort;
if (!Number.isFinite(nextWebPort) || nextWebPort < 1024 || nextWebPort > 65535) {
return reply.code(400).send({ error: "invalid_web_port" });
}
const nextProjectDir =
typeof body.projectDirectory === "string" && body.projectDirectory.trim()
? body.projectDirectory.trim()
@@ -247,16 +288,20 @@ export async function buildApp() {
const next: PersistedServerSysConfig = {
enabled: typeof body.enabled === "boolean" ? body.enabled : current.enabled,
port: nextPort,
webPort: nextWebPort,
startOnBoot:
typeof body.startOnBoot === "boolean" ? body.startOnBoot : current.startOnBoot,
projectDirectory: nextProjectDir,
};
const configured = applySystemStartupFiles(next);
const boundWebPort = await bindSecondaryWebPort(next.webPort, next.enabled);
return {
ok: true,
enabled: next.enabled,
port: next.port,
activePort: config.port,
webPort: next.webPort,
activeWebPort: boundWebPort,
startOnBoot: next.startOnBoot,
projectDirectory: next.projectDirectory,
autostartConfigured: configured,
@@ -355,6 +400,21 @@ export async function buildApp() {
});
}
app.addHook("onClose", async () => {
if (secondaryWebServer) {
await new Promise<void>((res) => secondaryWebServer!.close(() => res()));
secondaryWebServer = null;
}
});
if (
process.argv[1] !== undefined &&
fileURLToPath(import.meta.url) === process.argv[1]
) {
const persisted = loadPersistedServerSysConfig();
void bindSecondaryWebPort(persisted.webPort, persisted.enabled);
}
return app;
}