diff --git a/.gitignore b/.gitignore index 945dd2a..3618117 100644 --- a/.gitignore +++ b/.gitignore @@ -134,4 +134,7 @@ dist *.d.ts .idea + +# default config file +config.json !src/types/*.d.ts diff --git a/config.json b/config.json deleted file mode 100644 index c249049..0000000 --- a/config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "port": "25565", - "logLevel": "DEBUG", - "shutdownKickReason": "Server closed" -} diff --git a/index.ts b/index.ts index 202543b..1363788 100644 --- a/index.ts +++ b/index.ts @@ -1,11 +1,11 @@ -import Config from "./src/Config.js"; +import { Config, ConfigLoader } from "./src/Config.js"; import Server from "./src/Server.js"; import LoginSuccessPacket from "./src/packet/server/LoginSuccessPacket.js"; import Connection from "./src/Connection.js"; -const config: Config = await Config.fromFile("config.json"); - +const config: Config = await ConfigLoader.fromFile("config.json"); const server = new Server(config); + server.start(); server.on("listening", (port) => server.logger.info(`Listening on port ${port}`)); diff --git a/src/Config.ts b/src/Config.ts index 27160b6..2c7c35e 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -1,32 +1,82 @@ -import { open } from "node:fs/promises"; +import { open, access, constants, FileHandle } from "node:fs/promises"; import Logger from "./Logger.js"; -export default class Config { - public port: number = 25565; - public logLevel: Logger.Level = Logger.Level.INFO; + +export interface Config { + /** + * Port to listen on + */ + port: number; + + /** + * The level to display logs at + */ + logLevel: Logger.Level; /** * Kick reason for when the server is shutting down */ - public shutdownKickReason: string = "Server closed"; + shutdownKickReason: ChatComponent; +} +export class ConfigLoader { /** * Get a Config instance from a json file * @param file The file to read from * @returns a promise that resolves to a Config instance + * @throws {Error & {CODE: "EACCESS"}} failed to read config + * @throws {SyntaxError} failed to parse config */ public static async fromFile(file: string): Promise { - let config: Config; + if (!(await ConfigLoader.exists(file))) { + await ConfigLoader.createDefault(file); + const config = ConfigLoader.getDefault(); + new Logger("Config", config.logLevel).warn("Config does not exist, creating default '%s'", file); + return config; + } + const fd: FileHandle = await open(file, "r"); + const data: string = await fd.readFile("utf-8"); + fd.close(); + + return JSON.parse(data) as Config; + } + + /** + * Get a default config instance + * @returns a default config instance + */ + public static getDefault(): Config { + return { + port: 25565, + logLevel: Logger.Level.INFO, + shutdownKickReason: { + text: "Server closed" + } + }; + + } + + /** + * Checks if a config exists + * @param file The file to check + * @returns a promise that resolves to a boolean + */ + public static async exists(file: string): Promise { try { - const fd = await open(file, "r"); - const data = await fd.readFile("utf-8"); - config = JSON.parse(data) as Config; - fd.close(); + await access(file, constants.F_OK); + return true; } catch { - config = new Config(); - new Logger("Config", config.logLevel).error("Failed to read config file, using default config"); + return false; } - return config; } + /** + * Create the default config file + */ + public static async createDefault(file: string): Promise { + const fd = await open(file, "w"); + await fd.writeFile(JSON.stringify(ConfigLoader.getDefault(), null, 4)); + fd.close(); + } } + diff --git a/src/ConnectionPool.ts b/src/ConnectionPool.ts index ae361e3..753c04b 100644 --- a/src/ConnectionPool.ts +++ b/src/ConnectionPool.ts @@ -27,7 +27,7 @@ export default class ConnectionPool { * @param [reason] The reason for the disconnect * @returns Whether all connections disconnected successfully */ - public async disconnectAll (reason?: string): Promise { + public async disconnectAll (reason?: string | ChatComponent): Promise { const promises: Promise[] = []; for (const connection of this.connections) promises.push(this.disconnect(connection.id, reason)); @@ -40,18 +40,19 @@ export default class ConnectionPool { * @param [reason] The reason for the disconnect * @returns Whether the connection was found and disconnected */ - public async disconnect(id: string, reason?: string): Promise { + public async disconnect(id: string, reason?: string | ChatComponent): Promise { const connection = this.get(id); if (!connection) return false; const index = this.connections.indexOf(connection); if (index === -1) return false; + const message = typeof reason === "string" ? {text: reason} : reason!; if (reason) switch (connection.state) { case Connection.State.LOGIN: { - await new DisconnectLoginPacket({text: reason}).send(connection); + await new DisconnectLoginPacket(message).send(connection); break; } case Connection.State.PLAY: { - await new DisconnectPlayPacket({text: reason}).send(connection); + await new DisconnectPlayPacket(message).send(connection); break; } default: { diff --git a/src/Server.ts b/src/Server.ts index 27badf1..4ab6348 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -2,7 +2,6 @@ import * as net from "node:net"; import EventEmitter from "node:events"; import path from "node:path"; import Packet from "./Packet.js"; -import Config from "./Config.js"; import Logger from "./Logger.js"; import {TypedClientPacket} from "./types/TypedPacket"; import TypedEventEmitter from "./types/TypedEventEmitter"; @@ -10,6 +9,7 @@ import ConnectionPool from "./ConnectionPool.js"; import Connection from "./Connection.js"; import HandshakePacket from "./packet/client/HandshakePacket"; import LoginPacket from "./packet/client/LoginPacket"; +import { Config } from "./Config.js"; type ServerEvents = { /**