mcgrizz/state/serverState.ts

191 lines
4.9 KiB
TypeScript

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<boolean> {
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();