Skip to content

Instantly share code, notes, and snippets.

@JonathanTurnock
Created January 20, 2024 12:19
Show Gist options
  • Save JonathanTurnock/3d2017ba4368e8cccc1ff10fbd26387f to your computer and use it in GitHub Desktop.
Save JonathanTurnock/3d2017ba4368e8cccc1ff10fbd26387f to your computer and use it in GitHub Desktop.
Electron subprocess
import { randomUUID } from "node:crypto";
import * as os from "os";
import Aigle from "aigle";
import { UtilityProcess, utilityProcess } from "electron";
import { compact, flatten, values } from "lodash";
import { z } from "zod";
import { trpc } from "../trpc";
type ServerEntry = {
id: string;
path: string;
started: number;
stopped?: number;
logs: {
log: string;
level: "VERBOSE" | "DEBUG" | "INFO" | "WARNING" | "ERROR";
}[];
process: UtilityProcess;
};
export type ServerStatus = Omit<ServerEntry, "process" | "logs"> & {
pid?: number;
urls?: string[];
};
const servers: Record<string, ServerEntry> = {};
export const getLogLevel = (log: string) => {
if (log.includes("VERBOSE")) return "VERBOSE";
if (log.includes("DEBUG")) return "DEBUG";
if (log.includes("LOG") || log.includes("INFO")) return "INFO";
if (log.includes("WARNING") || log.includes("WARN")) return "WARNING";
if (log.includes("ERROR")) return "ERROR";
return "INFO";
};
export const serverService = {
getStatus(): Array<ServerStatus> {
return Object.values(servers).map((server) => ({
id: server.id,
path: server.path,
started: server.started,
stopped: server.stopped,
pid: server.process.pid,
urls: server.process.pid
? compact(flatten(values(os.networkInterfaces())))
.filter((it) => it.family === "IPv4")
.map(({ address }) => `http://${address}:1620/`)
: undefined,
}));
},
async start(trustedClients: string[]) {
const id = randomUUID();
const path = require.resolve("@flying-dice/war-room-server");
const serverProcess = utilityProcess.fork(path, [], {
serviceName: "WarRoomServer",
stdio: "pipe",
env: {
...process.env,
NO_COLOR: "true",
WAR_ROOM__TRUSTED_CLIENTS: JSON.stringify(trustedClients),
},
});
const server: ServerEntry = {
id: id,
started: Date.now(),
path,
process: serverProcess,
logs: [],
};
serverProcess.stdout?.on("data", (data) => {
const logString = data.toString().trimEnd().split("\n");
server.logs.push(
...logString.map((log) => ({ log, level: getLogLevel(log) })),
);
});
serverProcess.stderr?.on("data", (data) => {
const logString = data.toString().trimEnd().split("\n");
server.logs.push(
...logString.map((log) => ({ log, level: getLogLevel(log) })),
);
});
serverProcess.on("exit", () => {
server.stopped = Date.now();
});
servers[server.id] = server;
await Aigle.delay(3000);
},
async stop(id: string) {
const server = servers[id];
if (!server) return;
console.log(`Stopping Server with ID: ${id}`);
const result = server.process.kill();
console.log("Server Process Killed", result);
await Aigle.delay(500);
},
};
export const serverRouter = trpc.router({
start: trpc.procedure
.input(z.object({ trustedClients: z.array(z.string()) }))
.mutation(({ input }) => serverService.start(input.trustedClients)),
stop: trpc.procedure
.input(z.object({ id: z.string().uuid() }))
.mutation(({ input }) => serverService.stop(input.id)),
getStatus: trpc.procedure.query(serverService.getStatus),
getLogs: trpc.procedure
.input(z.object({ id: z.string().uuid() }))
.query(({ input }) => servers[input.id].logs),
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment