import { Sockpuppet } from "puppet/client"; import { acceptEULA, checkEULA } from "../util/EULA.ts"; import { Loader } from "../types/mcgrizzconf.ts"; import { getConfFile, updateConfFile } from "../util/confFile.ts"; import { IS_BROWSER } from "$fresh/runtime.ts"; import { getServerStartCommands } from "../serverConfigs/start.ts"; type MCServerEvent = "message"; type MCServerEventCallback = (msg: string) => void; type status = "running" | "stopped"; class ServerState { private _status: status = "stopped"; private pid?: number; public get status() { return this._status; } private set status(s: status) { this._status = s; const statusEvent = new CustomEvent("serverstatuschange", { detail: this._status, }); globalThis.dispatchEvent(statusEvent); } private command!: Deno.Command; private process!: Deno.ChildProcess; private _eulaAccepted = false; private sockpuppet!: Sockpuppet; private _channelId; public get channelId() { return this._channelId; } public get eulaAccepted() { return this._eulaAccepted; } private stdin!: WritableStreamDefaultWriter; private _serverType: Loader = "unset"; public get serverType(): Loader { return this._serverType; } public set serverType(loader: Loader) { updateConfFile({ loader }); this._serverType = loader; } private _serverVersion: string; public get serverVersion(): string { return this._serverVersion; } public set serverVersion(version: string) { updateConfFile({ version }); this._serverVersion = version; } constructor() { this._channelId = crypto.randomUUID ? crypto.randomUUID() : ""; const conf = getConfFile(); this._serverType = conf.loader; this._serverVersion = conf.version; this._eulaAccepted = checkEULA(); } private configureSockpuppet(): Promise { return new Promise((res) => { this.sockpuppet = new Sockpuppet( "ws://sockpuppet.cyborggrizzly.com", async () => { await this.sockpuppet.createChannel(this._channelId); res(true); this.sockpuppet.joinChannel(this.channelId, async (msg) => { if (msg === "log" && !IS_BROWSER) { try { const log = await Deno.readTextFile("./server/logs/latest.log"); this.sendMessageToChannel(log); } catch { 1; } } else { console.log(msg); this.sendStdIn(msg); } }); }, ); }); } public get channel() { return this.sockpuppet.getChannel(this.channelId); } public async sendStdIn(message: string) { if (IS_BROWSER || !this.stdin) return; const msg = new TextEncoder().encode(message + "\n"); await this.stdin.write(msg); } // "instance" should be moved to a private member once multi-instance support is implemented public async startMCServer(instance = "server") { this.command = getServerStartCommands(this._serverType, instance); await this.configureSockpuppet(); this.sendMessageToChannel("clear"); this.process = this.command.spawn(); this.pid = this.process.pid; const { readable, writable } = new TransformStream(); readable.pipeTo(this.process.stdin); this.stdin = writable.getWriter(); this.status = "running"; this.startStream(); await this.process.status; this.status = "stopped"; } private async startStream() { await this.process.stdout.pipeThrough(new TextDecoderStream()).pipeTo( new WritableStream({ write: (chunk) => { this.sendMessageToChannel(chunk); const stdoutMsg = new CustomEvent("stdoutmsg", { detail: chunk, }); globalThis.dispatchEvent(stdoutMsg); }, }), ); } public gracefullyStopMCServer() { this.status = "stopped"; this.sendStdIn("stop"); } public async forceStopMCServer() { this.status = "stopped"; this.process.kill(); await Deno.remove('./server/session.lock'); } public async restartMCServer() { if (this.status === "running") { await this.sendStdIn("stop"); const statusEvent = new CustomEvent("serverstatuschange", { detail: "restarting", }); globalThis.dispatchEvent(statusEvent); await this.process.status; } this.startMCServer(); } public acceptEULA() { this._eulaAccepted = true; acceptEULA(); } private async sendMessageToChannel(msg: string, retries = 0) { if (retries > 4) return console.log('Unable to reconnect to socket after 5 tries. Is the puppet server ok?') try { this.channel?.send(msg); } catch (e) { console.log(e); console.log( "Error sending message via socket, likely closed. Reconnecting", ); await this.configureSockpuppet(); await this.sendMessageToChannel(msg, retries + 1); } } } export const SERVER_STATE = new ServerState();