From 2a52a82fb7d7f4eaeb3d0a2daf3601f4f1792b8c Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Thu, 10 Aug 2023 23:11:00 +0200 Subject: [PATCH] Port message types to TypeScript --- packages/controller/src/ControlConnection.js | 12 +- packages/ctl/ctl.js | 11 +- packages/ctl/src/commands.js | 12 +- packages/host/src/Host.js | 2 +- packages/host/src/Instance.js | 2 +- packages/host/src/server.js | 16 +- packages/lib/package.json | 3 +- packages/lib/scripts/compile_validator.js | 6 +- packages/lib/src/data/composites.js | 35 - packages/lib/src/data/composites.ts | 34 + packages/lib/src/data/index.ts | 10 +- packages/lib/src/data/message_validate.d.ts | 6 +- packages/lib/src/data/messages_controller.js | 384 ----- packages/lib/src/data/messages_controller.ts | 247 +++ packages/lib/src/data/messages_core.js | 890 ----------- packages/lib/src/data/messages_core.ts | 629 ++++++++ packages/lib/src/data/messages_host.js | 243 --- packages/lib/src/data/messages_host.ts | 157 ++ packages/lib/src/data/messages_instance.js | 1335 ----------------- packages/lib/src/data/messages_instance.ts | 916 +++++++++++ packages/lib/src/data/messages_mod.js | 474 ------ packages/lib/src/data/messages_mod.ts | 324 ++++ packages/lib/src/data/messages_user.js | 708 --------- packages/lib/src/data/messages_user.ts | 472 ++++++ packages/lib/src/link/link.ts | 14 +- .../web_ui/src/components/CreateSaveModal.jsx | 6 +- .../src/components/LoadScenarioModal.jsx | 6 +- packages/web_ui/src/components/LogConsole.jsx | 2 +- packages/web_ui/src/util/websocket.jsx | 2 +- test/integration/clusterio.js | 10 +- test/lib/command.js | 2 +- 31 files changed, 2846 insertions(+), 4124 deletions(-) delete mode 100644 packages/lib/src/data/composites.js create mode 100644 packages/lib/src/data/composites.ts delete mode 100644 packages/lib/src/data/messages_controller.js create mode 100644 packages/lib/src/data/messages_controller.ts delete mode 100644 packages/lib/src/data/messages_core.js create mode 100644 packages/lib/src/data/messages_core.ts delete mode 100644 packages/lib/src/data/messages_host.js create mode 100644 packages/lib/src/data/messages_host.ts delete mode 100644 packages/lib/src/data/messages_instance.js create mode 100644 packages/lib/src/data/messages_instance.ts delete mode 100644 packages/lib/src/data/messages_mod.js create mode 100644 packages/lib/src/data/messages_mod.ts delete mode 100644 packages/lib/src/data/messages_user.js create mode 100644 packages/lib/src/data/messages_user.ts diff --git a/packages/controller/src/ControlConnection.js b/packages/controller/src/ControlConnection.js index d14bb5e7..ee446c8f 100644 --- a/packages/controller/src/ControlConnection.js +++ b/packages/controller/src/ControlConnection.js @@ -248,7 +248,7 @@ class ControlConnection extends BaseConnection { async handleHostGenerateTokenRequest(message) { let hostId = message.host_id; - if (hostId === null) { + if (hostId === undefined) { hostId = Math.random() * 2**31 | 0; } return this._controller.generateHostToken(hostId); @@ -279,7 +279,7 @@ class ControlConnection extends BaseConnection { instance.config.get("instance.name"), instance.id, instance.config.get("instance.assigned_host"), - instance.game_port || null, + instance.game_port || undefined, instance.status, ); } @@ -290,8 +290,8 @@ class ControlConnection extends BaseConnection { list.push(new lib.InstanceDetails( instance.config.get("instance.name"), instance.id, - instance.config.get("instance.assigned_host"), - instance.game_port || null, + instance.config.get("instance.assigned_host") || undefined, + instance.game_port || undefined, instance.status, )); } @@ -311,8 +311,8 @@ class ControlConnection extends BaseConnection { new lib.InstanceDetails( instance.config.get("instance.name"), instance.id, - instance.config.get("instance.assigned_host"), - instance.game_port || null, + instance.config.get("instance.assigned_host") || undefined, + instance.game_port || undefined, instance.status, ) )); diff --git a/packages/ctl/ctl.js b/packages/ctl/ctl.js index 07c16fbc..4bdb33c5 100755 --- a/packages/ctl/ctl.js +++ b/packages/ctl/ctl.js @@ -83,7 +83,7 @@ class Control extends lib.Link { controller = false, hostIds = [], instanceIds = [], - maxLevel = null, + maxLevel = undefined, }) { await this.send( new lib.LogSetSubscriptionsRequest( @@ -286,7 +286,11 @@ async function startControl() { process.exitCode = 1; } else if (err instanceof lib.RequestError) { - logger.error(`Error sending request:\n${err.stack}`); + if (err.stack) { + logger.error(`Error sending request:\n${err.stack}`); + } else { + logger.error(`Error sending request: ${err.message}`); + } process.exitCode = 1; } else { @@ -315,6 +319,9 @@ I version of clusterio. Expect things to break. I ` ); startControl().catch(err => { + if (err.errors) { + logger.fatal(JSON.stringify(err.errors, null, 4)); + } if (!(err instanceof lib.StartupError)) { logger.fatal(` +------------------------------------------------------------+ diff --git a/packages/ctl/src/commands.js b/packages/ctl/src/commands.js index 35e675be..f3c5a9d1 100644 --- a/packages/ctl/src/commands.js +++ b/packages/ctl/src/commands.js @@ -237,9 +237,9 @@ hostCommands.add(new lib.Command({ yargs.option("id", { type: "number", nargs: 1, describe: "Host id" }); }], handler: async function(args, control) { - let hostId = typeof args.id === "number" ? args.id : null; + let hostId = typeof args.id === "number" ? args.id : undefined; let response = await control.send(new lib.HostGenerateTokenRequest(hostId)); - print(response.token); + print(response); }, })); @@ -480,9 +480,9 @@ instanceCommands.add(new lib.Command({ })); async function loadMapSettings(args) { - let seed = args.seed !== undefined ? args.seed : null; - let mapGenSettings = null; - let mapSettings = null; + let seed = args.seed; + let mapGenSettings; + let mapSettings; if (args.mapExchangeString) { let parsed = lib.readMapExchangeString(args.mapExchangeString); mapGenSettings = parsed.map_gen_settings; @@ -1551,7 +1551,7 @@ logCommands.add(new lib.Command({ "controller": { describe: "Query log of the controller", nargs: 0, type: "boolean", default: false }, "host": { describe: "Query log of given host", nargs: 1, type: "string", default: null }, "instance": { describe: "Query log of given instance", nargs: 1, type: "string", default: null }, - "max-level": { describe: "Maximum log level to return", nargs: 1, type: "string", default: null }, + "max-level": { describe: "Maximum log level to return", nargs: 1, type: "string", default: undefined }, "limit": { describe: "Max number of entries to return", nargs: 1, type: "number", default: 1000 }, "start": { describe: "Limit from the start instead of the end", nargs: 0, type: "boolean", default: false }, }); diff --git a/packages/host/src/Host.js b/packages/host/src/Host.js index c92ddc0b..01852403 100644 --- a/packages/host/src/Host.js +++ b/packages/host/src/Host.js @@ -457,7 +457,7 @@ class Host extends lib.Link { let instanceConnection = this.instanceConnections.get(instanceId); this.send( new lib.InstanceStatusChangedEvent( - instanceId, instanceConnection ? instanceConnection.status : "stopped", null + instanceId, instanceConnection ? instanceConnection.status : "stopped", undefined ) ); diff --git a/packages/host/src/Instance.js b/packages/host/src/Instance.js index 2d19f98f..132873fc 100644 --- a/packages/host/src/Instance.js +++ b/packages/host/src/Instance.js @@ -438,7 +438,7 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " "); new lib.InstanceStatusChangedEvent( this.id, status, - this.server && this.server.gamePort || this.config.get("factorio.game_port") || null, + this.server && this.server.gamePort || this.config.get("factorio.game_port") || undefined, ), ); } diff --git a/packages/host/src/server.js b/packages/host/src/server.js index 2237e7ae..73090b45 100644 --- a/packages/host/src/server.js +++ b/packages/host/src/server.js @@ -796,9 +796,11 @@ class FactorioServer extends events.EventEmitter { [ "--config", this.writePath("config.ini"), "--create", this.writePath("saves", name), - ...(seed !== null ? ["--map-gen-seed", seed] : []), - ...(mapGenSettings !== null ? ["--map-gen-settings", this.writePath("map-gen-settings.json")] : []), - ...(mapSettings !== null ? ["--map-settings", this.writePath("map-settings.json")] : []), + /* eslint-disable eqeqeq, no-eq-null */ + ...(seed != null ? ["--map-gen-seed", seed] : []), + ...(mapGenSettings != null ? ["--map-gen-settings", this.writePath("map-gen-settings.json")] : []), + ...(mapSettings != null ? ["--map-settings", this.writePath("map-settings.json")] : []), + /* eslint-enable eqeqeq, no-eq-null */ ...(this.verboseLogging ? ["--verbose"] : []), ], { @@ -886,9 +888,11 @@ class FactorioServer extends events.EventEmitter { [ "--config", this.writePath("config.ini"), "--start-server-load-scenario", scenario, - ...(seed !== null ? ["--map-gen-seed", seed] : []), - ...(mapGenSettings !== null ? ["--map-gen-settings", this.writePath("map-gen-settings.json")] : []), - ...(mapSettings !== null ? ["--map-settings", this.writePath("map-settings.json")] : []), + /* eslint-disable eqeqeq, no-eq-null */ + ...(seed != null ? ["--map-gen-seed", seed] : []), + ...(mapGenSettings != null ? ["--map-gen-settings", this.writePath("map-gen-settings.json")] : []), + ...(mapSettings != null ? ["--map-settings", this.writePath("map-settings.json")] : []), + /* eslint-enable eqeqeq, no-eq-null */ "--port", this.gamePort, "--rcon-port", this.rconPort, "--rcon-password", this.rconPassword, diff --git a/packages/lib/package.json b/packages/lib/package.json index 519495da..93696564 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -5,7 +5,7 @@ "repository": "https://github.com/clusterio/clusterio", "license": "MIT", "scripts": { - "prepare": "pnpm run compile_validator && tsc", + "prepare": "tsc && pnpm run compile_validator", "compile_validator": "node scripts/compile_validator.js" }, "main": "dist/index.js", @@ -17,6 +17,7 @@ "node": ">=12" }, "dependencies": { + "@sinclair/typebox": "^0.30.4", "ajv": "^8.12.0", "chalk": "^4.1.2", "fast-deep-equal": "^3.1.3", diff --git a/packages/lib/scripts/compile_validator.js b/packages/lib/scripts/compile_validator.js index 41745df1..61421e01 100644 --- a/packages/lib/scripts/compile_validator.js +++ b/packages/lib/scripts/compile_validator.js @@ -7,9 +7,9 @@ const standaloneCode = require("ajv/dist/standalone").default; /* eslint-disable node/no-sync */ // Provide an empty file for message_core to load -fs.outputFileSync(path.join(__dirname, "../src/data/message_validate.js"), ""); +fs.outputFileSync(path.join(__dirname, "../dist/src/data/message_validate.js"), ""); -const { Message } = require("../src/data/messages_core"); +const { Message } = require("../dist/src/data/messages_core"); const ajv = new Ajv({ allowUnionTypes: true, @@ -23,6 +23,6 @@ const ajv = new Ajv({ const validate = ajv.compile(Message.jsonSchema); let moduleCode = standaloneCode(ajv, validate); fs.outputFileSync( - path.join(__dirname, "../src/data/message_validate.js"), + path.join(__dirname, "../dist/src/data/message_validate.js"), `// generated by scripts/compile_validator.js\n// @ts-nocheck\n/* eslint-disable */\n${moduleCode}` ); diff --git a/packages/lib/src/data/composites.js b/packages/lib/src/data/composites.js deleted file mode 100644 index cdd80943..00000000 --- a/packages/lib/src/data/composites.js +++ /dev/null @@ -1,35 +0,0 @@ -"use strict"; - -function jsonArray(ItemClass) { - return { - jsonSchema: { - type: "array", - items: ItemClass.jsonSchema, - }, - - fromJSON(json) { - return json.map(i => ItemClass.fromJSON(i)); - }, - }; -} - -function jsonPrimitive(type) { - return { - jsonSchema: { type }, - fromJSON(json) { - return json; - }, - }; -} -const JsonBoolean = jsonPrimitive("boolean"); -const JsonNumber = jsonPrimitive("number"); -const JsonString = jsonPrimitive("string"); - -module.exports = { - JsonBoolean, - JsonNumber, - JsonString, - - jsonArray, - jsonPrimitive, -}; diff --git a/packages/lib/src/data/composites.ts b/packages/lib/src/data/composites.ts new file mode 100644 index 00000000..8566185f --- /dev/null +++ b/packages/lib/src/data/composites.ts @@ -0,0 +1,34 @@ +import { Type } from "@sinclair/typebox"; + +export function StringEnum(values: [...T]) { + return Type.Unsafe({ type: 'string', enum: values }) +} + +export interface JSONDeserialisable { + fromJSON(json: unknown): T; + jsonSchema: object; +} + +export function jsonArray(ItemClass: JSONDeserialisable) { + return { + jsonSchema: { + type: "array", + items: ItemClass.jsonSchema, + }, + fromJSON(json: Array) { + return json.map(i => ItemClass.fromJSON(i)); + }, + }; +} + +export function jsonPrimitive(type: U) { + return { + jsonSchema: { type }, + fromJSON(json: T) { + return json; + }, + }; +} +export const JsonBoolean = jsonPrimitive("boolean"); +export const JsonNumber = jsonPrimitive("number"); +export const JsonString = jsonPrimitive("string"); diff --git a/packages/lib/src/data/index.ts b/packages/lib/src/data/index.ts index 5fb50974..7f5e2349 100644 --- a/packages/lib/src/data/index.ts +++ b/packages/lib/src/data/index.ts @@ -17,19 +17,11 @@ export * from "./version"; // TODO: Remove/migrate after lib/data is migrated to ts import type { User } from "../users"; -export type AddressType = "controller" | "host" | "instance" | "control" | "broadcast"; export interface Serialisable { jsonSchema: object, fromJSON(json: any): object, } - -import type { Address } from "./messages_core"; -export type AddressShorthand = - | "controller" - | { instanceId: number } - | { hostId: number } - | { controlId: number } - | Address +import { AddressType } from "./messages_core"; export class Request { static type: "request"; diff --git a/packages/lib/src/data/message_validate.d.ts b/packages/lib/src/data/message_validate.d.ts index 177658ef..fdd40377 100644 --- a/packages/lib/src/data/message_validate.d.ts +++ b/packages/lib/src/data/message_validate.d.ts @@ -1,2 +1,4 @@ -import type { ValidateFunction } from "ajv" -export default ValidateFunction +import type { ValidateFunction } from "ajv"; +import type { Message } from "./messages_core"; +var message_validate: ValidateFunction; +export default message_validate; diff --git a/packages/lib/src/data/messages_controller.js b/packages/lib/src/data/messages_controller.js deleted file mode 100644 index 5fb97303..00000000 --- a/packages/lib/src/data/messages_controller.js +++ /dev/null @@ -1,384 +0,0 @@ -"use strict"; -const { JsonString } = require("./composites"); - -// TODO: remove after config refactor -/** @memberof module:lib */ -class RawConfig { - /** @type {object} */ - serializedConfig; - - constructor(serializedConfig) { - this.serializedConfig = serializedConfig; - } - - static jsonSchema = { - type: "object", - required: ["serializedConfig"], - properties: { - "serializedConfig": { type: "object" }, - }, - }; - - static fromJSON(json) { - return new this(json.serializedConfig); - } -} - -/* @memberof module:lib */ -class ControllerConfigGetRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.controller.get_config"; - static Response = RawConfig; -} - -/** @memberof module:lib */ -class ControllerConfigSetFieldRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.controller.update_config"; - - /** @type {string} */ - field; - /** @type {string} */ - value; - - constructor(field, value) { - this.field = field; - this.value = value; - } - - static jsonSchema = { - type: "object", - required: ["field", "value"], - properties: { - "field": { type: "string" }, - "value": { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.field, json.value); - } -} - -/* @memberof module:lib */ -class ControllerConfigSetPropRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.controller.update_config"; - - /** @type {string} */ - field; - /** @type {string} */ - prop; - /** @type {undefined} */ - value; - - constructor(field, prop, value) { - this.field = field; - this.prop = prop; - if (value) { this.value = value; } - } - - static jsonSchema = { - type: "object", - required: ["field", "prop"], - properties: { - field: { type: "string" }, - prop: { type: "string" }, - value: {}, - }, - }; - - static fromJSON(json) { - return new this(json.field, json.prop, json.value); - } -} - -/* @memberof module:lib */ -class HostGenerateTokenRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.host.generate_token"; - - /** @type {?number} */ - hostId; - - constructor(hostId) { - this.hostId = hostId; - } - - static jsonSchema = { - type: "object", - required: ["hostId"], - properties: { - hostId: { type: ["integer", "null"] }, - }, - }; - - static fromJSON(json) { - return new this(json.hostId); - } - - static Response = JsonString; -} - -/** @memberof module:lib */ -class HostConfigCreateRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.host.create_config"; - - /** @type {?number} */ - id; - /** @type {?string} */ - name; - /** @type {boolean} */ - generateToken; - - constructor(id, name, generateToken) { - this.id = id; - this.name = name; - this.generateToken = generateToken; - } - - static jsonSchema = { - type: "object", - required: ["id", "name", "generateToken"], - properties: { - "id": { type: ["integer", "null"] }, - "name": { type: ["string", "null"] }, - "generateToken": { type: "boolean" }, - }, - }; - - static fromJSON(json) { - return new this(json.id, json.name, json.generateToken); - } - - static Response = RawConfig; -} - -/* @memberof module:lib */ -class LogSetSubscriptionsRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.log.follow"; - - /** @type {boolean} */ - all; - /** @type {boolean} */ - controller; - /** @type {array} */ - hostIds; - /** @type {array} */ - instanceIds; - /** @type {?string} */ - maxLevel; - - constructor(all, controller, hostIds, instanceIds, maxLevel) { - this.all = all; - this.controller = controller; - this.hostIds = hostIds; - this.instanceIds = instanceIds; - this.maxLevel = maxLevel; - } - - static jsonSchema = { - type: "object", - required: ["all", "controller", "hostIds", "instanceIds", "maxLevel"], - properties: { - all: { type: "boolean" }, - controller: { type: "boolean" }, - hostIds: { - type: "array", - items: { type: "integer" }, - }, - instanceIds: { - type: "array", - items: { type: "integer" }, - }, - maxLevel: { type: ["string", "null"] }, - }, - }; - - static fromJSON(json) { - return new this(json.all, json.controller, json.hostIds, json.instanceIds, json.maxLevel); - } -} - -/* @memberof module:lib */ -class LogQueryRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.log.query"; - - /** @type {boolean} */ - all; - /** @type {boolean} */ - controller; - /** @type {Array} */ - hostIds; - /** @type {Array} */ - instanceIds; - /** @type {?string} */ - maxLevel; - /** @type {integer} */ - limit; - /** @type {undefined} */ - order; - - constructor(all, controller, hostIds, instanceIds, maxLevel, limit, order) { - this.all = all; - this.controller = controller; - this.hostIds = hostIds; - this.instanceIds = instanceIds; - this.maxLevel = maxLevel; - this.limit = limit; - this.order = order; - } - - static jsonSchema = { - type: "object", - required: ["all", "controller", "hostIds", "instanceIds", "maxLevel", "limit", "order"], - properties: { - all: { type: "boolean" }, - controller: { type: "boolean" }, - hostIds: { - type: "array", - items: { type: "integer" }, - }, - instanceIds: { - type: "array", - items: { type: "integer" }, - }, - maxLevel: { type: ["string", "null"] }, - limit: { type: "integer" }, - order: { - enum: ["asc", "desc"], - }, - }, - }; - - static fromJSON(json) { - return new this( - json.all, json.controller, json.hostIds, json.instanceIds, json.maxLevel, json.limit, json.order - ); - } -} -LogQueryRequest.Response = class Response { - /** @type {Array} */ - log; - - constructor(log) { - this.log = log; - } - - static jsonSchema = { - type: "object", - required: ["log"], - properties: { - log: { - type: "array", - items: { type: "object" }, - }, - }, - }; - - static fromJSON(json) { - return new this(json.log); - } -}; - -/** @memberof module:lib */ -class LogMessageEvent { - static type = "event"; - static src = ["host", "controller"]; - static dst = ["controller", "control"]; - - /** @type {object} */ - info; - - constructor(info) { - this.info = info; - } - - static jsonSchema = { - type: "object", - required: ["info"], - properties: { - "info": { - type: "object", - required: ["level", "message"], - properties: { - "level": { type: "string" }, - "message": { type: "string" }, - }, - }, - }, - }; - - static fromJSON(json) { - return new this(json.info); - } -} - -/* @memberof module:lib */ -class DebugDumpWsRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.debug.dump_ws"; -} - -/* @memberof module:lib */ -class DebugWsMessageEvent { - static type = "event"; - static src = "controller"; - static dst = "control"; - - /** @type {string} */ - direction; - /** @type {string} */ - content; - - constructor(direction, content) { - this.direction = direction; - this.content = content; - } - - static jsonSchema = { - type: "object", - required: ["direction", "content"], - properties: { - direction: { type: "string" }, - content: { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.direction, json.content); - } -} - -module.exports = { - RawConfig, - ControllerConfigGetRequest, - ControllerConfigSetFieldRequest, - ControllerConfigSetPropRequest, - HostGenerateTokenRequest, - HostConfigCreateRequest, - LogSetSubscriptionsRequest, - LogQueryRequest, - LogMessageEvent, - DebugDumpWsRequest, - DebugWsMessageEvent, -}; diff --git a/packages/lib/src/data/messages_controller.ts b/packages/lib/src/data/messages_controller.ts new file mode 100644 index 00000000..3ee14f7e --- /dev/null +++ b/packages/lib/src/data/messages_controller.ts @@ -0,0 +1,247 @@ +import { Type, Static } from "@sinclair/typebox"; +import { JsonString, StringEnum } from "./composites"; +import { levels } from "../logging"; + +// TODO: remove after config refactor +export class RawConfig { + constructor( + public serializedConfig: object + ) { } + + static jsonSchema = Type.Object({ + "serializedConfig": Type.Object({}), + }); + + static fromJSON(json: Static) { + return new this(json.serializedConfig); + } +} + +export class ControllerConfigGetRequest { + declare ["constructor"]: typeof ControllerConfigGetRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.controller.get_config"; + static Response = RawConfig; +} + +export class ControllerConfigSetFieldRequest { + declare ["constructor"]: typeof ControllerConfigSetFieldRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.controller.update_config"; + + constructor( + public field: string, + public value: string, + ) { } + + static jsonSchema = Type.Object({ + "field": Type.String(), + "value": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.field, json.value); + } +} + +export class ControllerConfigSetPropRequest { + declare ["constructor"]: typeof ControllerConfigSetPropRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.controller.update_config"; + + constructor( + public field: string, + public prop: string, + public value?: unknown, + ) { } + + static jsonSchema = Type.Object({ + "field": Type.String(), + "prop": Type.String(), + "value": Type.Optional(Type.Unknown()), + }); + + static fromJSON(json: Static) { + return new this(json.field, json.prop, json.value); + } +} + +export class HostGenerateTokenRequest { + declare ["constructor"]: typeof HostGenerateTokenRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.host.generate_token"; + + constructor( + public hostId?: number, + ) { } + + static jsonSchema = Type.Object({ + "hostId": Type.Optional(Type.Integer()), + }); + + static fromJSON(json: Static) { + return new this(json.hostId); + } + + static Response = JsonString; +} + +export class HostConfigCreateRequest { + declare ["constructor"]: typeof HostConfigCreateRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.host.create_config"; + + constructor( + public id: number | undefined, + public name: string | undefined, + public generateToken: boolean, + ) { } + + static jsonSchema = Type.Object({ + "id": Type.Optional(Type.Integer()), + "name": Type.Optional(Type.String()), + "generateToken": Type.Boolean(), + }); + + static fromJSON(json: Static) { + return new this(json.id, json.name, json.generateToken); + } + + static Response = RawConfig; +} + +export class LogSetSubscriptionsRequest { + declare ["constructor"]: typeof LogSetSubscriptionsRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.log.follow"; + + constructor( + public all?: boolean, + public controller?: boolean, + public hostIds?: number[], + public instanceIds?: number[], + public maxLevel?: keyof typeof levels, + ) { } + + static jsonSchema = Type.Object({ + all: Type.Optional(Type.Boolean()), + controller: Type.Optional(Type.Boolean()), + hostIds: Type.Array(Type.Integer()), + instanceIds: Type.Array(Type.Integer()), + maxLevel: Type.Optional(StringEnum(Object.keys(levels) as (keyof typeof levels)[])), + }); + + static fromJSON(json: Static) { + return new this(json.all, json.controller, json.hostIds, json.instanceIds, json.maxLevel); + } +} + +export class LogQueryRequest { + declare ["constructor"]: typeof LogQueryRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.log.query"; + + constructor( + public all: boolean, + public controller: boolean, + public hostIds: number[], + public instanceIds: number[], + public maxLevel: undefined | keyof typeof levels, + public limit: number, + public order: "asc" | "desc", + ) { } + + static jsonSchema = Type.Object({ + all: Type.Boolean(), + controller: Type.Boolean(), + hostIds: Type.Array(Type.Integer()), + instanceIds: Type.Array(Type.Integer()), + maxLevel: Type.Optional(StringEnum(Object.keys(levels) as (keyof typeof levels)[])), + limit: Type.Integer(), + order: StringEnum(["asc", "desc"]), + }); + + static fromJSON(json: Static) { + return new this( + json.all, json.controller, json.hostIds, json.instanceIds, json.maxLevel, json.limit, json.order + ); + } + static Response = class Response { + constructor( + public log: object[], + ) { } + + static jsonSchema = Type.Object({ + "log": Type.Array(Type.Object({})), + }); + + static fromJSON(json: Static) { + return new this(json.log); + } + }; +} + +export class LogMessageEvent { + declare ["constructor"]: typeof LogMessageEvent; + static type = "event"; + static src = ["host", "controller"]; + static dst = ["controller", "control"]; + + constructor( + public info: object, + ) { } + + static jsonSchema = Type.Object({ + "info": Type.Object({ + "level": Type.String(), + "message": Type.String(), + }), + }); + + static fromJSON(json: Static) { + return new this(json.info); + } +} + +export class DebugDumpWsRequest { + declare ["constructor"]: typeof DebugDumpWsRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.debug.dump_ws"; +} + +export class DebugWsMessageEvent { + declare ["constructor"]: typeof DebugWsMessageEvent; + static type = "event"; + static src = "controller"; + static dst = "control"; + + constructor( + public direction: string, + public content: string, + ) { } + + static jsonSchema = Type.Object({ + "direction": Type.String(), + "content": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.direction, json.content); + } +} diff --git a/packages/lib/src/data/messages_core.js b/packages/lib/src/data/messages_core.js deleted file mode 100644 index d418a60f..00000000 --- a/packages/lib/src/data/messages_core.js +++ /dev/null @@ -1,890 +0,0 @@ -// Note: Run compile_validator script after making changes to this file. -"use strict"; -const message_validate = require("./message_validate.js"); - - -/** @memberof module:lib */ -class Address { - /** Controller address */ - static controller = 0; - /** Instance address */ - static instance = 1; - /** Host address */ - static host = 2; - /** Control address */ - static control = 3; - /** Broadcast */ - static broadcast = 4; - - /** @type {number} */ - type; - - /** @type {number} */ - id; - - /** @type {number|undefined} */ - requestId; - - constructor(type, id, requestId) { - if (typeof type !== "number") { - throw Error(`Invalid type ${type}`); - } - this.type = type; - this.id = id; - this.requestId = requestId; - } - - /** - * Convert from convenient shorthand notation to address. - * - * Supported notations and their corresponding address: - * - `"controller"`: new Address(Address.controller, 0) - * - `{ hostId: id }`: new Address(Address.host, id) - * - `{ instanceId: id }`: new Address(Address.instance, id) - * - `{ controlId: id }`: new Address(Address.control, id) - * `"allHosts"`: new Address(Address.broadcast, Address.host) - * `"allInstances"`: new Address(Address.broadcast, Address.instance) - * `"allControls"`: new Address(Address.broadcast, Address.control) - * @param {module:lib.Address|string|object} shorthand - - * Shorthand to translate with. - * @returns {module:lib.Address} Translated address. - */ - static fromShorthand(shorthand) { - if (shorthand instanceof Address) { return shorthand; } - if (shorthand === "controller") { return new Address(Address.controller, 0); } - if (shorthand === "allHosts") { return new Address(Address.broadcast, Address.host); } - if (shorthand === "allInstances") { return new Address(Address.broadcast, Address.instance); } - if (shorthand === "allControls") { return new Address(Address.broadcast, Address.control); } - let entries = Object.entries(shorthand); - if (entries.length === 1) { - let [name, value] = entries[0]; - if (name === "hostId") { return new Address(Address.host, value); } - if (name === "instanceId") { return new Address(Address.instance, value); } - if (name === "controlId") { return new Address(Address.control, value); } - } - throw Error("Unrecognized shorthand"); - } - - static jsonSchema = { - type: "array", - minItems: 2, - items: [ - { type: "integer", minimum: 0, maximum: 4 }, - { type: "integer" }, - { type: "integer" }, - ], - }; - - static fromJSON(json) { - return new this(json[0], json[1], json[2]); - } - - toJSON() { - if (this.requestId !== undefined) { - return [this.type, this.id, this.requestId]; - } - return [this.type, this.id]; - } - - toString() { - const typeMap = ["controller", "instance", "host", "control"]; - const type = typeMap[this.type] || this.type; - if (this.type === Address.broadcast) { - return `[Address ${type}:${typeMap[this.id] || this.id}]`; - } - if (this.requestId === undefined) { - return `[Address ${type}:${this.id}]`; - } - return `[Address ${type}:${this.id}:${this.requestId}]`; - } - - /** - * Returns a string suitable for indexing the address in a Map - * @returns {string} unique string for this address - */ - index() { - return `${this.type}:${this.id}:${this.requestId}`; - } - - /** - * Returns true if this address targets the given destination - * @param {module:lib.Address} dst - Destination to check - * @returns {boolean} true if this address is addressed to the given destination - */ - addressedTo(dst) { - if (this.type !== Address.broadcast) { - return ( - this.type === dst.type - && this.id === dst.id - ); - } - return dst.type !== Address.broadcast && this.id === dst.type; - } - - equals(other) { - return ( - this.type === other.type - && this.id === other.id - && this.requestId === other.requestId - ); - } -} - -/** @memberof module:lib */ -class Message { - /** @type {string} */ - type; - - constructor(type) { - this.type = type; - } - - static jsonSchema; - static validate; - - static fromJSON(json) { - /* eslint-disable no-use-before-define */ - if (json.type === "hello") { return MessageHello.fromJSON(json); } - if (json.type === "registerHost") { return MessageRegisterHost.fromJSON(json); } - if (json.type === "registerControl") { return MessageRegisterControl.fromJSON(json); } - if (json.type === "ready") { return MessageReady.fromJSON(json); } - if (json.type === "resume") { return MessageResume.fromJSON(json); } - if (json.type === "continue") { return MessageContinue.fromJSON(json); } - if (json.type === "invalidate") { return MessageInvalidate.fromJSON(json); } - if (json.type === "heartbeat") { return MessageHeartbeat.fromJSON(json); } - if (json.type === "request") { return MessageRequest.fromJSON(json); } - if (json.type === "response") { return MessageResponse.fromJSON(json); } - if (json.type === "responseError") { return MessageResponseError.fromJSON(json); } - if (json.type === "event") { return MessageEvent.fromJSON(json); } - if (json.type === "disconnect") { return MessageDisconnect.fromJSON(json); } - /* eslint-enable no-use-before-define */ - throw new Error(`Unrecognized message type ${json.type}`); - } -} - -/** @memberof module:lib */ -class HelloData { - /** @type {string} */ - version; - /** @type {object} */ - plugins; - - constructor(version, plugins) { - this.version = version; - this.plugins = plugins; - } - - static jsonSchema = { - type: "object", - required: ["version", "plugins"], - properties: { - "version": { type: "string" }, - "plugins": { - type: "object", - additionalProperties: { type: "string" }, - }, - }, - }; - - static fromJSON(json) { - return new this(json.version, json.plugins); - } -} - -/** @memberof module:lib */ -class MessageHello extends Message { - /** @type {module:lib.HelloData} */ - data; - - constructor(data) { - super("hello"); - this.data = data; - } - - static jsonSchema = { - type: "object", - required: ["type", "data"], - properties: { - "type": { const: "hello" }, - "data": HelloData.jsonSchema, - }, - }; - - static fromJSON(json) { - return new this(HelloData.fromJSON(json.data)); - } -}; - -/** @memberof module:lib */ -class RegisterHostData { - /** @type {string} */ - token; - /** @type {string} */ - agent; - /** @type {string} */ - version; - /** @type {string} */ - name; - /** @type {string} */ - id; - /** @type {string|undefined} */ - publicAddress; - /** @type {Object} */ - plugins; - - constructor(token, agent, version, name, id, publicAddress, plugins) { - this.token = token; - this.agent = agent; - this.version = version; - this.name = name; - this.id = id; - this.publicAddress = publicAddress; - this.plugins = plugins; - } - - static jsonSchema = { - type: "object", - required: ["token", "agent", "version", "name", "id", "plugins"], - properties: { - "token": { type: "string" }, - "agent": { type: "string" }, - "version": { type: "string" }, - "name": { type: "string" }, - "id": { type: "integer" }, - "publicAddress": { type: "string" }, - "plugins": { - type: "object", - additionalProperties: { type: "string" }, - }, - }, - }; - - static fromJSON(json) { - return new this(json.token, json.agent, json.version, json.name, json.id, json.publicAddress, json.plugins); - } -} - -/** @memberof module:lib */ -class MessageRegisterHost extends Message { - /** @type {module:lib.RegisterHostData} */ - data; - - constructor(data) { - super("registerHost"); - this.data = data; - } - - static jsonSchema = { - type: "object", - required: ["type", "data"], - properties: { - "type": { const: "registerHost" }, - "data": RegisterHostData.jsonSchema, - }, - }; - - static fromJSON(json) { - return new this(RegisterHostData.fromJSON(json.data)); - } -}; - -/** @memberof module:lib */ -class RegisterControlData { - /** @type {string} */ - token; - /** @type {string} */ - agent; - /** @type {string} */ - version; - - constructor(token, agent, version) { - this.token = token; - this.agent = agent; - this.version = version; - } - - static jsonSchema = { - type: "object", - required: ["token", "agent", "version"], - properties: { - "token": { type: "string" }, - "agent": { type: "string" }, - "version": { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.token, json.agent, json.version); - } -} - -/** @memberof module:lib */ -class MessageRegisterControl extends Message { - /** @type {module:lib.RegisterControlData} */ - data; - - constructor(data) { - super("registerControl"); - this.data = data; - } - - static jsonSchema = { - type: "object", - required: ["data"], - properties: { - "type": { const: "registerControl" }, - "data": RegisterControlData.jsonSchema, - }, - }; - - static fromJSON(json) { - return new this(RegisterControlData.fromJSON(json.data)); - } -}; - -/** @memberof module:lib */ -class AccountDetails { - /** @type {string} */ - name; - /** @type {Array} */ - roles; - - constructor(name, roles) { - this.name = name; - this.roles = roles; - } - - static jsonSchema = { - type: "object", - required: ["name", "roles"], - properties: { - "name": { type: "string" }, - "roles": { - type: "array", - items: { - type: "object", - required: ["name", "id", "permissions"], - properties: { - "name": { type: "string" }, - "id": { type: "integer" }, - "permissions": { - type: "array", - items: { type: "string" }, - }, - }, - }, - }, - }, - }; - - static fromJSON(json) { - return new this(json.name, json.roles); - } -} - -/** @memberof module:lib */ -class ReadyData { - /** @type {module:lib.Address} */ - src; - /** @type {string} */ - sessionToken; - /** @type {number} */ - sessionTimeout; - /** @type {number} */ - heartbeatInterval; - /** @type {AccountDetails|undefined} */ - account; - - constructor(src, sessionToken, sessionTimeout, heartbeatInterval, account) { - this.src = src; - this.sessionToken = sessionToken; - this.sessionTimeout = sessionTimeout; - this.heartbeatInterval = heartbeatInterval; - if (account) { this.account = account; } - } - - static jsonSchema = { - type: "object", - required: ["src", "sessionToken", "sessionTimeout", "heartbeatInterval"], - properties: { - "src": Address.jsonSchema, - "sessionToken": { type: "string" }, - "sessionTimeout": { type: "number" }, - "heartbeatInterval": { type: "number" }, - "account": AccountDetails.jsonSchema, - }, - }; - - static fromJSON(json) { - let account; - if (json.account) { account = AccountDetails.fromJSON(json.account); } - return new this( - Address.fromJSON(json.src), - json.sessionToken, - json.sessionTimeout, - json.heartbeatInterval, - account - ); - } -} - -/** @memberof module:lib */ -class MessageReady extends Message { - /** @type {module:lib.ReadyData} */ - data; - - constructor(data) { - super("ready"); - this.data = data; - } - - static jsonSchema = { - type: "object", - required: ["data"], - properties: { - "type": { const: "ready" }, - "data": ReadyData.jsonSchema, - }, - }; - - static fromJSON(json) { - return new this(ReadyData.fromJSON(json.data)); - } -} - -/** @memberof module:lib */ -class ResumeData { - /** @type {string} */ - sessionToken; - /** @type {integer|undefined} */ - lastSeq; - - constructor(sessionToken, lastSeq) { - this.sessionToken = sessionToken; - if (lastSeq !== undefined) { this.lastSeq = lastSeq; } - } - - static jsonSchema = { - type: "object", - required: ["sessionToken"], - properties: { - "sessionToken": { type: "string" }, - "lastSeq": { type: "integer" }, - }, - }; - - static fromJSON(json) { - return new this(json.sessionToken, json.lastSeq); - } -} - - -/** @memberof module:lib */ -class MessageResume extends Message { - /** @type {module:lib.ResumeData} */ - data; - - constructor(data) { - super("resume"); - this.data = data; - } - - static jsonSchema = { - type: "object", - required: ["data"], - properties: { - "type": { const: "resume" }, - "data": ResumeData.jsonSchema, - }, - }; - - static fromJSON(json) { - return new this(ResumeData.fromJSON(json.data)); - } -} - -/** @memberof module:lib */ -class ContinueData { - /** @type {number} */ - sessionTimeout; - /** @type {number} */ - heartbeatInterval; - /** @type {integer|undefined} */ - lastSeq; - - constructor(sessionTimeout, heartbeatInterval, lastSeq) { - this.sessionTimeout = sessionTimeout; - this.heartbeatInterval = heartbeatInterval; - if (lastSeq !== undefined) { this.lastSeq = lastSeq; } - } - - static jsonSchema = { - type: "object", - required: ["sessionTimeout", "heartbeatInterval"], - properties: { - "sessionTimeout": { type: "number" }, - "heartbeatInterval": { type: "number" }, - "lastSeq": { type: "integer" }, - }, - }; - - static fromJSON(json) { - return new this(json.sessionTimeout, json.heartbeatInterval, json.lastSeq); - } -} - -/** @memberof module:lib */ -class MessageContinue extends Message { - /** @type {module:lib.ContinueData} */ - data; - - constructor(data) { - super("continue"); - this.data = data; - } - - static jsonSchema = { - type: "object", - required: ["data"], - properties: { - "type": { const: "continue" }, - "data": ContinueData.jsonSchema, - }, - }; - - static fromJSON(json) { - return new this(ContinueData.fromJSON(json.data)); - } -} - -/** @memberof module:lib */ -class MessageInvalidate extends Message { - constructor() { - super("invalidate"); - } - - static jsonSchema = { - type: "object", - properties: { - "type": { const: "invalidate" }, - }, - }; - - static fromJSON(json) { - return new this(); - } -} - -/** @memberof module:lib */ -class MessageHeartbeat extends Message { - /** @type {integer|undefined} */ - seq; - - constructor(seq) { - super("heartbeat"); - this.seq = seq; - } - - static jsonSchema = { - type: "object", - required: ["type"], - properties: { - "type": { const: "heartbeat" }, - "seq": { type: "integer" }, - }, - }; - - static fromJSON(json) { - return new this(json.seq); - } -} - -/** @memberof module:lib */ -class MessageRequest extends Message { - /** @type {number} */ - seq; - /** @type {Address} */ - src; - /** @type {Address} */ - dst; - /** @type {string} */ - name; - /** @type {*} */ - data; - - constructor(seq, src, dst, name, data) { - super("request"); - this.seq = seq; - this.src = src; - this.dst = dst; - this.name = name; - this.data = data; - } - - static jsonSchema = { - type: "object", - required: ["type", "seq", "src", "dst", "name"], - properties: { - "type": { const: "request" }, - "seq": { type: "integer" }, - "src": Address.jsonSchema, - "dst": Address.jsonSchema, - "name": { type: "string" }, - "data": {}, - }, - }; - - static fromJSON(json) { - return new this( - json.seq, - Address.fromJSON(json.src), - Address.fromJSON(json.dst), - json.name, - json.data, - ); - } -}; - -/** @memberof module:lib */ -class MessageResponse extends Message { - /** @type {number} */ - seq; - /** @type {Address} */ - src; - /** @type {Address} */ - dst; - /** @type {*} */ - data; - - constructor(seq, src, dst, data) { - super("response"); - this.seq = seq; - this.src = src; - this.dst = dst; - this.data = data; - } - - static jsonSchema = { - type: "object", - required: ["type", "seq", "src", "dst"], - properties: { - "type": { const: "response" }, - "seq": { type: "integer" }, - "src": Address.jsonSchema, - "dst": Address.jsonSchema, - "data": {}, - }, - }; - - static fromJSON(json) { - return new this( - json.seq, - Address.fromJSON(json.src), - Address.fromJSON(json.dst), - json.data, - ); - } -}; - -/** @memberof module:lib */ -class ResponseError { - /** @type {string} */ - message; - /** @type {string|undefined} */ - code; - /** @type {string|undefined} */ - stack; - - constructor(message, code, stack) { - this.message = message; - if (code) { this.code = code; } - if (stack) { this.stack = stack; } - } - - static jsonSchema = { - type: "object", - required: ["message"], - properties: { - "message": { type: "string" }, - "code": { type: "string" }, - "stack": { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.message, json.code, json.stack); - } -} - -/** @memberof module:lib */ -class MessageResponseError extends Message { - /** @type {number} */ - seq; - /** @type {Address} */ - src; - /** @type {Address} */ - dst; - /** @type {module:lib.ResponseError} */ - data; - - constructor(seq, src, dst, data) { - super("responseError"); - this.seq = seq; - this.src = src; - this.dst = dst; - this.data = data; - } - - static jsonSchema = { - type: "object", - required: ["type", "seq", "src", "dst", "data"], - properties: { - "type": { const: "responseError" }, - "seq": { type: "integer" }, - "src": Address.jsonSchema, - "dst": Address.jsonSchema, - "data": ResponseError.jsonSchema, - }, - }; - - static fromJSON(json) { - return new this( - json.seq, - Address.fromJSON(json.src), - Address.fromJSON(json.dst), - ResponseError.fromJSON(json.data), - ); - } -}; - -/** @memberof module:lib */ -class MessageEvent extends Message { - /** @type {number} */ - seq; - /** @type {Address} */ - src; - /** @type {Address} */ - dst; - /** @type {string} */ - name; - /** @type {*} */ - data; - - constructor(seq, src, dst, name, data) { - super("event"); - this.seq = seq; - this.src = src; - this.dst = dst; - this.name = name; - this.data = data; - } - - static jsonSchema = { - type: "object", - required: ["type", "seq", "src", "dst", "name"], - properties: { - "type": { const: "event" }, - "seq": { type: "integer" }, - "src": Address.jsonSchema, - "dst": Address.jsonSchema, - "name": { type: "string" }, - "data": {}, - }, - }; - - static fromJSON(json) { - return new this( - json.seq, - Address.fromJSON(json.src), - Address.fromJSON(json.dst), - json.name, - json.data, - ); - } -}; - -/** @memberof module:lib */ -class MessageDisconnect extends Message { - /** @type {string} */ - data; - - constructor(stage) { - super("disconnect"); - this.data = stage; - } - - static jsonSchema = { - type: "object", - required: ["type"], - properties: { - "type": { const: "disconnect" }, - "data": { enum: ["prepare", "ready"] }, - }, - }; - - static fromJSON(json) { - return new this(json.data); - } -}; - -Message.jsonSchema = { - type: "object", - required: ["type"], - properties: { - "type": { enum: [ - "hello", "registerHost", "registerControl", "ready", "resume", "continue", "invalidate", - "heartbeat", "request", "response", "responseError", "event", "disconnect", - ] }, - }, - oneOf: [ - MessageHello.jsonSchema, - MessageRegisterHost.jsonSchema, - MessageRegisterControl.jsonSchema, - MessageReady.jsonSchema, - MessageResume.jsonSchema, - MessageContinue.jsonSchema, - MessageInvalidate.jsonSchema, - MessageHeartbeat.jsonSchema, - MessageRequest.jsonSchema, - MessageResponse.jsonSchema, - MessageResponseError.jsonSchema, - MessageEvent.jsonSchema, - MessageDisconnect.jsonSchema, - ], -}; -Message.validate = message_validate; - -/** @memberof module:lib */ -class PingRequest { - static type = "request"; - static src = ["controller", "host", "control"]; - static dst = ["controller", "host", "control"]; - static permission = null; -} - -module.exports = { - Message, - HelloData, - MessageHello, - RegisterHostData, - MessageRegisterHost, - RegisterControlData, - MessageRegisterControl, - AccountDetails, - ReadyData, - MessageReady, - ResumeData, - MessageResume, - ContinueData, - MessageContinue, - MessageInvalidate, - MessageHeartbeat, - Address, - MessageRequest, - MessageResponse, - ResponseError, - MessageResponseError, - MessageEvent, - MessageDisconnect, - PingRequest, -}; diff --git a/packages/lib/src/data/messages_core.ts b/packages/lib/src/data/messages_core.ts new file mode 100644 index 00000000..5f6b8ac4 --- /dev/null +++ b/packages/lib/src/data/messages_core.ts @@ -0,0 +1,629 @@ +// Note: Run compile_validator script after making changes to this file. +import { TSchema, Type, Static } from "@sinclair/typebox"; +import messageValidate from "./message_validate"; +import { StringEnum } from "./composites"; + +export type AddressType = "controller" | "host" | "instance" | "control" | "broadcast"; +export type AddressShorthand = + | "controller" + | "allHosts" + | "allInstances" + | "allControls" + | { instanceId: number } + | { hostId: number } + | { controlId: number } + | Address +; + +export class Address { + /** Controller address */ + static controller = 0; + /** Instance address */ + static instance = 1; + /** Host address */ + static host = 2; + /** Control address */ + static control = 3; + /** Broadcast */ + static broadcast = 4; + + constructor( + public type: number, + public id: number, + public requestId?: number, + ) { } + + /** + * Convert from convenient shorthand notation to address. + * + * Supported notations and their corresponding address: + * - `"controller"`: new Address(Address.controller, 0) + * - `{ hostId: id }`: new Address(Address.host, id) + * - `{ instanceId: id }`: new Address(Address.instance, id) + * - `{ controlId: id }`: new Address(Address.control, id) + * `"allHosts"`: new Address(Address.broadcast, Address.host) + * `"allInstances"`: new Address(Address.broadcast, Address.instance) + * `"allControls"`: new Address(Address.broadcast, Address.control) + * @param shorthand - + * Shorthand to translate with. + * @returns Translated address. + */ + static fromShorthand(shorthand: AddressShorthand) { + if (shorthand instanceof Address) { return shorthand; } + if (shorthand === "controller") { return new Address(Address.controller, 0); } + if (shorthand === "allHosts") { return new Address(Address.broadcast, Address.host); } + if (shorthand === "allInstances") { return new Address(Address.broadcast, Address.instance); } + if (shorthand === "allControls") { return new Address(Address.broadcast, Address.control); } + let entries = Object.entries(shorthand); + if (entries.length === 1) { + let [name, value] = entries[0]; + if (name === "hostId") { return new Address(Address.host, value); } + if (name === "instanceId") { return new Address(Address.instance, value); } + if (name === "controlId") { return new Address(Address.control, value); } + } + throw Error("Unrecognized shorthand"); + } + + static jsonSchema = Type.Unsafe<[number, number, number?]>({ + type: "array", + minItems: 2, + items: [ + { type: "integer", minimum: 0, maximum: 4 }, + Type.Integer(), + Type.Integer(), + ], + }); + + static fromJSON(json: Static) { + return new this(json[0], json[1], json[2]); + } + + toJSON() { + if (this.requestId !== undefined) { + return [this.type, this.id, this.requestId]; + } + return [this.type, this.id]; + } + + toString() { + const typeMap = ["controller", "instance", "host", "control"]; + const type = typeMap[this.type] || this.type; + if (this.type === Address.broadcast) { + return `[Address ${type}:${typeMap[this.id] || this.id}]`; + } + if (this.requestId === undefined) { + return `[Address ${type}:${this.id}]`; + } + return `[Address ${type}:${this.id}:${this.requestId}]`; + } + + /** + * Returns a string suitable for indexing the address in a Map + * @returns unique string for this address + */ + index() { + return `${this.type}:${this.id}:${this.requestId}`; + } + + /** + * Returns true if this address targets the given destination + * @param dst - Destination to check + * @returns true if this address is addressed to the given destination + */ + addressedTo(dst: Address) { + if (this.type !== Address.broadcast) { + return ( + this.type === dst.type + && this.id === dst.id + ); + } + return dst.type !== Address.broadcast && this.id === dst.type; + } + + equals(other: Address) { + return ( + this.type === other.type + && this.id === other.id + && this.requestId === other.requestId + ); + } +} + +type MessageType = + | "hello" + | "registerHost" + | "registerControl" + | "ready" + | "resume" + | "continue" + | "invalidate" + | "heartbeat" + | "request" + | "response" + | "responseError" + | "event" + | "disconnect" +; +export class Message { + constructor( + public type: MessageType, + ) { } + + static jsonSchema: TSchema; + static validate = messageValidate; + + static fromJSON(json: any) { + /* eslint-disable no-use-before-define */ + if (json.type === "hello") { return MessageHello.fromJSON(json); } + if (json.type === "registerHost") { return MessageRegisterHost.fromJSON(json); } + if (json.type === "registerControl") { return MessageRegisterControl.fromJSON(json); } + if (json.type === "ready") { return MessageReady.fromJSON(json); } + if (json.type === "resume") { return MessageResume.fromJSON(json); } + if (json.type === "continue") { return MessageContinue.fromJSON(json); } + if (json.type === "invalidate") { return MessageInvalidate.fromJSON(json); } + if (json.type === "heartbeat") { return MessageHeartbeat.fromJSON(json); } + if (json.type === "request") { return MessageRequest.fromJSON(json); } + if (json.type === "response") { return MessageResponse.fromJSON(json); } + if (json.type === "responseError") { return MessageResponseError.fromJSON(json); } + if (json.type === "event") { return MessageEvent.fromJSON(json); } + if (json.type === "disconnect") { return MessageDisconnect.fromJSON(json); } + /* eslint-enable no-use-before-define */ + throw new Error(`Unrecognized message type ${json.type}`); + } +} + +export class HelloData { + constructor( + public version: string, + public plugins: Record, + ) { } + + static jsonSchema = Type.Object({ + "version": Type.String(), + "plugins": Type.Record(Type.String(), Type.String()), + }); + + static fromJSON(json: Static) { + return new this(json.version, json.plugins); + } +} + +export class MessageHello extends Message { + constructor( + public data: HelloData, + ) { + super("hello"); + } + + static jsonSchema = Type.Object({ + "type": Type.Literal("hello"), + "data": HelloData.jsonSchema, + }); + + static fromJSON(json: Static) { + return new this(HelloData.fromJSON(json.data)); + } +}; + +export class RegisterHostData { + constructor( + public token: string, + public agent: string, + public version: string, + public name: string, + public id: number, + public publicAddress: string | undefined, + public plugins: Record, + ) { } + + static jsonSchema = Type.Object({ + "token": Type.String(), + "agent": Type.String(), + "version": Type.String(), + "name": Type.String(), + "id": Type.Integer(), + "publicAddress": Type.Optional(Type.String()), + "plugins": Type.Record(Type.String(), Type.String()), + }); + + static fromJSON(json: Static) { + return new this(json.token, json.agent, json.version, json.name, json.id, json.publicAddress, json.plugins); + } +} + +export class MessageRegisterHost extends Message { + constructor( + public data: RegisterHostData, + ) { + super("registerHost"); + } + + static jsonSchema = Type.Object({ + "type": Type.Literal("registerHost"), + "data": RegisterHostData.jsonSchema, + }); + + static fromJSON(json: Static) { + return new this(RegisterHostData.fromJSON(json.data)); + } +}; + +export class RegisterControlData { + constructor( + public token: string, + public agent: string, + public version: string, + ) { } + + static jsonSchema = Type.Object({ + "token": Type.String(), + "agent": Type.String(), + "version": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.token, json.agent, json.version); + } +} + +export class MessageRegisterControl extends Message { + constructor( + public data: RegisterControlData, + ) { + super("registerControl"); + } + + static jsonSchema = Type.Object({ + "type": Type.Literal("registerControl"), + "data": RegisterControlData.jsonSchema, + }); + + static fromJSON(json: Static) { + return new this(RegisterControlData.fromJSON(json.data)); + } +}; + +export class AccountDetails { + constructor( + public name: string, + public roles: { name: string, id: number, permissions: string[] }[], + ) { } + + static jsonSchema = Type.Object({ + "name": Type.String(), + "roles": Type.Array( + Type.Object({ + "name": Type.String(), + "id": Type.Integer(), + "permissions": Type.Array(Type.String()), + }), + ), + }); + + static fromJSON(json: Static) { + return new this(json.name, json.roles); + } +} + +export class ReadyData { + constructor( + public src: Address, + public sessionToken: string, + public sessionTimeout: number, + public heartbeatInterval: number, + public account?: AccountDetails, + ) { } + + static jsonSchema = Type.Object({ + "src": Address.jsonSchema, + "sessionToken": Type.String(), + "sessionTimeout": Type.Number(), + "heartbeatInterval": Type.Number(), + "account": Type.Optional(AccountDetails.jsonSchema), + }); + + static fromJSON(json: Static) { + let account: AccountDetails; + if (json.account) { account = AccountDetails.fromJSON(json.account); } + return new this( + Address.fromJSON(json.src), + json.sessionToken, + json.sessionTimeout, + json.heartbeatInterval, + account + ); + } +} + +export class MessageReady extends Message { + constructor( + public data: ReadyData, + ) { + super("ready"); + } + + static jsonSchema = Type.Object({ + "type": Type.Literal("ready"), + "data": ReadyData.jsonSchema, + }); + + static fromJSON(json: Static) { + return new this(ReadyData.fromJSON(json.data)); + } +} + +export class ResumeData { + constructor( + public sessionToken: string, + public lastSeq?: number, + ) { } + + static jsonSchema = Type.Object({ + "sessionToken": Type.String(), + "lastSeq": Type.Optional(Type.Integer()), + }); + + static fromJSON(json: Static) { + return new this(json.sessionToken, json.lastSeq); + } +} + + +export class MessageResume extends Message { + constructor( + public data: ResumeData, + ) { + super("resume"); + } + + static jsonSchema = Type.Object({ + "type": Type.Literal("resume"), + "data": ResumeData.jsonSchema, + }); + + static fromJSON(json: Static) { + return new this(ResumeData.fromJSON(json.data)); + } +} + +export class ContinueData { + constructor( + public sessionTimeout: number, + public heartbeatInterval: number, + public lastSeq?: number, + ) { } + + static jsonSchema = Type.Object({ + "sessionTimeout": Type.Number(), + "heartbeatInterval": Type.Number(), + "lastSeq": Type.Optional(Type.Integer()), + }); + + static fromJSON(json: Static) { + return new this(json.sessionTimeout, json.heartbeatInterval, json.lastSeq); + } +} + +export class MessageContinue extends Message { + constructor( + public data: ContinueData, + ) { + super("continue"); + } + + static jsonSchema = Type.Object({ + "type": Type.Literal("continue"), + "data": ContinueData.jsonSchema, + }); + + static fromJSON(json: Static) { + return new this(ContinueData.fromJSON(json.data)); + } +} + +export class MessageInvalidate extends Message { + constructor() { + super("invalidate"); + } + + static jsonSchema = Type.Object({ + "type": Type.Literal("invalidate"), + }); + + static fromJSON(json: Static) { + return new this(); + } +} + +export class MessageHeartbeat extends Message { + constructor( + public seq?: number, + ) { + super("heartbeat"); + } + + static jsonSchema = Type.Object({ + "type": Type.Literal("heartbeat"), + "seq": Type.Optional(Type.Integer()), + }); + + static fromJSON(json: Static) { + return new this(json.seq); + } +} + +export class MessageRequest extends Message { + constructor( + public seq: number, + public src: Address, + public dst: Address, + public name: string, + public data?: T, + ) { + super("request"); + } + + static jsonSchema = Type.Object({ + "type": Type.Literal("request"), + "seq": Type.Integer(), + "src": Address.jsonSchema, + "dst": Address.jsonSchema, + "name": Type.String(), + "data": Type.Optional(Type.Unknown()), + }); + + static fromJSON(json: Static) { + return new this( + json.seq, + Address.fromJSON(json.src), + Address.fromJSON(json.dst), + json.name, + json.data, + ); + } +}; + +export class MessageResponse extends Message { + constructor( + public seq: number, + public src: Address, + public dst: Address, + public data?: T, + ) { + super("response"); + } + + static jsonSchema = Type.Object({ + "type": Type.Literal("response"), + "seq": Type.Integer(), + "src": Address.jsonSchema, + "dst": Address.jsonSchema, + "data": Type.Optional(Type.Unknown()), + }); + + static fromJSON(json: Static) { + return new this( + json.seq, + Address.fromJSON(json.src), + Address.fromJSON(json.dst), + json.data, + ); + } +}; + +export class ResponseError { + constructor( + public message: string, + public code?: string, + public stack?: string, + ) { } + + static jsonSchema = Type.Object({ + "message": Type.String(), + "code": Type.Optional(Type.String()), + "stack": Type.Optional(Type.String()), + }); + + static fromJSON(json: Static) { + return new this(json.message, json.code, json.stack); + } +} + +export class MessageResponseError extends Message { + constructor( + public seq: number, + public src: Address, + public dst: Address, + public data: ResponseError, + ) { + super("responseError"); + } + + static jsonSchema = Type.Object({ + "type": Type.Literal("responseError"), + "seq": Type.Integer(), + "src": Address.jsonSchema, + "dst": Address.jsonSchema, + "data": ResponseError.jsonSchema, + }); + + static fromJSON(json: Static) { + return new this( + json.seq, + Address.fromJSON(json.src), + Address.fromJSON(json.dst), + ResponseError.fromJSON(json.data), + ); + } +}; + +export class MessageEvent extends Message { + constructor( + public seq: number, + public src: Address, + public dst: Address, + public name: string, + public data?: T, + ) { + super("event"); + } + + static jsonSchema = Type.Object({ + "type": Type.Literal("event"), + "seq": Type.Integer(), + "src": Address.jsonSchema, + "dst": Address.jsonSchema, + "name": Type.String(), + "data": Type.Optional(Type.Unknown()), + }); + + static fromJSON(json: Static) { + return new this( + json.seq, + Address.fromJSON(json.src), + Address.fromJSON(json.dst), + json.name, + json.data, + ); + } +}; + +export class MessageDisconnect extends Message { + constructor( + public data: string, + ) { + super("disconnect"); + } + + static jsonSchema = Type.Object({ + "type": Type.Literal("disconnect"), + "data": StringEnum(["prepare", "ready"]), + }); + + static fromJSON(json: Static) { + return new this(json.data); + } +}; + +Message.jsonSchema = Type.Union([ + MessageHello.jsonSchema, + MessageRegisterHost.jsonSchema, + MessageRegisterControl.jsonSchema, + MessageReady.jsonSchema, + MessageResume.jsonSchema, + MessageContinue.jsonSchema, + MessageInvalidate.jsonSchema, + MessageHeartbeat.jsonSchema, + MessageRequest.jsonSchema, + MessageResponse.jsonSchema, + MessageResponseError.jsonSchema, + MessageEvent.jsonSchema, + MessageDisconnect.jsonSchema, +]); + +export class PingRequest { + static type = "request"; + static src = ["controller", "host", "control"]; + static dst = ["controller", "host", "control"]; + static permission = null; +} diff --git a/packages/lib/src/data/messages_host.js b/packages/lib/src/data/messages_host.js deleted file mode 100644 index d9d50174..00000000 --- a/packages/lib/src/data/messages_host.js +++ /dev/null @@ -1,243 +0,0 @@ -"use strict"; -const { jsonArray } = require("./composites"); - - -/** @memberof module:lib */ -class HostDetails { - /** @type {string} */ - agent; - /** @type {string} */ - version; - /** @type {string} */ - name; - /** @type {number} */ - id; - /** @type {boolean} */ - connected; - /** @type {string|undefined} */ - publicAddress; - - constructor(agent, version, name, id, connected, publicAddress) { - this.agent = agent; - this.version = version; - this.name = name; - this.id = id; - this.connected = connected; - if (publicAddress !== undefined) { this.publicAddress = publicAddress; } - } - - static jsonSchema = { - type: "object", - required: ["agent", "version", "name", "id", "connected"], - properties: { - "agent": { type: "string" }, - "version": { type: "string" }, - "name": { type: "string" }, - "id": { type: "integer" }, - "connected": { type: "boolean" }, - "publicAddress": { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.agent, json.version, json.name, json.id, json.connected, json.publicAddress); - } -} - -/** @memberof module:lib */ -class HostListRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.host.list"; - static Response = jsonArray(HostDetails); -} - -/* @memberof module:lib */ -class HostSetSubscriptionsRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.host.subscribe"; - - /** @type {boolean} */ - all; - /** @type {Array} */ - hostIds; - - constructor(all, hostIds) { - this.all = all; - this.hostIds = hostIds; - } - - static jsonSchema = { - type: "object", - required: ["all", "hostIds"], - properties: { - all: { type: "boolean" }, - hostIds: { - type: "array", - items: { type: "integer" }, - }, - }, - }; - - static fromJSON(json) { - return new this(json.all, json.hostIds); - } -} - - -/* @memberof module:lib */ -class HostUpdateEvent { - static type = "event"; - static src = "controller"; - static dst = "control"; - - /** @type {module:lib.HostDetails} */ - update; - - constructor(update) { - this.update = update; - } - - static jsonSchema = { - type: "object", - required: ["update"], - properties: { - update: HostDetails.jsonSchema, - }, - }; - - static fromJSON(json) { - return new this(HostDetails.fromJSON(json.update)); - } -} - -/** @memberof module:lib */ -class HostMetricsRequest { - static type = "request"; - static src = "controller"; - static dst = "host"; -} -HostMetricsRequest.Response = class Response { // TODO: Use JSON class pattern in Prometheus - /** @type {array} */ - results; - - constructor(results) { - this.results = results; - } - - static jsonSchema = { - type: "object", - required: ["results"], - properties: { - "results": { - type: "array", - }, - }, - }; - - static fromJSON(json) { - return new this(json.results); - } -}; - -/** @memberof module:lib */ -class ControllerConnectionEvent { - static type = "event"; - static src = "host"; - static dst = "instance"; - - /** @type {string} */ - event; - - constructor(event) { - this.event = event; - } - - static jsonSchema = { - type: "object", - required: ["event"], - properties: { - "event": { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.event); - } -} - -/* @memberof module:lib */ -class PrepareControllerDisconnectRequest { - static type = "request"; - static src = "host"; - static dst = "instance"; -} - -/** @memberof module:lib */ -class SyncUserListsEvent { - static type = "event"; - static src = "controller"; - static dst = "host"; - - /** @type {Set} */ - adminlist; - /** @type {Map} */ - banlist; - /** @type {Set} */ - whitelist; - - constructor(adminlist, banlist, whitelist) { - this.adminlist = adminlist; - this.banlist = banlist; - this.whitelist = whitelist; - } - - static jsonSchema = { - type: "object", - required: ["adminlist", "banlist", "whitelist"], - properties: { - "adminlist": { - type: "array", - items: { type: "string" }, - }, - "banlist": { - type: "array", - items: { - type: "array", - minItems: 2, - items: [{ type: "string" }, { type: "string" }], - }, - }, - "whitelist": { - type: "array", - items: { type: "string" }, - }, - }, - }; - - static fromJSON(json) { - return new this(new Set(json.adminlist), new Set(json.banlist), new Set(json.whitelist)); - } - - static toJSON() { - return { - adminlist: [...this.adminlist], - banlist: [...this.banlist], - whitelist: [...this.whitelist], - }; - } -} - -module.exports = { - HostDetails, - HostListRequest, - HostSetSubscriptionsRequest, - HostUpdateEvent, - HostMetricsRequest, - ControllerConnectionEvent, - PrepareControllerDisconnectRequest, - SyncUserListsEvent, -}; diff --git a/packages/lib/src/data/messages_host.ts b/packages/lib/src/data/messages_host.ts new file mode 100644 index 00000000..bbde6a6a --- /dev/null +++ b/packages/lib/src/data/messages_host.ts @@ -0,0 +1,157 @@ +import { Type, Static } from "@sinclair/typebox"; +import { jsonArray } from "./composites"; + + +export class HostDetails { + constructor( + public agent: string, + public version: string, + public name: string, + public id: number, + public connected: boolean, + public publicAddress?: string, + ) { } + + static jsonSchema = Type.Object({ + "agent": Type.String(), + "version": Type.String(), + "name": Type.String(), + "id": Type.Integer(), + "connected": Type.Boolean(), + "publicAddress": Type.Optional(Type.String()), + }); + + static fromJSON(json: Static) { + return new this(json.agent, json.version, json.name, json.id, json.connected, json.publicAddress); + } +} + +export class HostListRequest { + declare ["constructor"]: typeof HostListRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.host.list"; + static Response = jsonArray(HostDetails); +} + +export class HostSetSubscriptionsRequest { + declare ["constructor"]: typeof HostSetSubscriptionsRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.host.subscribe"; + + constructor( + public all: boolean, + public hostIds: number[], + ) { } + + static jsonSchema = Type.Object({ + "all": Type.Boolean(), + "hostIds": Type.Array(Type.Integer()), + }); + + static fromJSON(json: Static) { + return new this(json.all, json.hostIds); + } +} + + +export class HostUpdateEvent { + declare ["constructor"]: typeof HostUpdateEvent; + static type = "event"; + static src = "controller"; + static dst = "control"; + + constructor( + public update: HostDetails, + ) { } + + static jsonSchema = Type.Object({ + "update": HostDetails.jsonSchema, + }); + + static fromJSON(json: Static) { + return new this(HostDetails.fromJSON(json.update)); + } +} + +export class HostMetricsRequest { + declare ["constructor"]: typeof HostMetricsRequest; + static type = "request"; + static src = "controller"; + static dst = "host"; + static Response = class Response { // TODO: Use JSON class pattern in Prometheus + constructor( + public results: object[], + ) { } + + static jsonSchema = Type.Object({ + "results": Type.Array(Type.Object({})), + }); + + static fromJSON(json: Static) { + return new this(json.results); + } + }; +} + +export class ControllerConnectionEvent { + declare ["constructor"]: typeof ControllerConnectionEvent; + static type = "event"; + static src = "host"; + static dst = "instance"; + + constructor( + public event: string, + ) { } + + static jsonSchema = Type.Object({ + "event": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.event); + } +} + +export class PrepareControllerDisconnectRequest { + declare ["constructor"]: typeof PrepareControllerDisconnectRequest; + static type = "request"; + static src = "host"; + static dst = "instance"; +} + +export class SyncUserListsEvent { + declare ["constructor"]: typeof SyncUserListsEvent; + static type = "event"; + static src = "controller"; + static dst = "host"; + + constructor( + public adminlist: Set, + public banlist: Map, + public whitelist: Set, + ) { } + + static jsonSchema = Type.Object({ + "adminlist": Type.Array(Type.String()), + "banlist": Type.Array( + Type.Tuple([Type.String(), Type.String()]) + ), + "whitelist": Type.Array(Type.String()), + }); + + static fromJSON(json: Static) { + return new this(new Set(json.adminlist), new Map(json.banlist), new Set(json.whitelist)); + } + + toJSON() { + return { + adminlist: [...this.adminlist], + banlist: [...this.banlist], + whitelist: [...this.whitelist], + }; + } +} diff --git a/packages/lib/src/data/messages_instance.js b/packages/lib/src/data/messages_instance.js deleted file mode 100644 index da661418..00000000 --- a/packages/lib/src/data/messages_instance.js +++ /dev/null @@ -1,1335 +0,0 @@ -"use strict"; -const PlayerStats = require("../PlayerStats").default; -const { JsonString, jsonArray } = require("./composites"); - -/** @memberof module:lib */ -class InstanceDetails { - /** @type {string} */ - name; - /** @type {number} */ - id; - /** @type {?integer} */ - assignedHost; - /** @type {?integer} */ - gamePort; - /** @type {string} */ - status; - - constructor(name, id, assignedHost, gamePort, status) { - this.name = name; - this.id = id; - this.assignedHost = assignedHost; - this.gamePort = gamePort; - this.status = status; - } - - static jsonSchema = { - type: "object", - required: ["name", "id", "assignedHost", "gamePort", "status"], - properties: { - "name": { type: "string" }, - "id": { type: "integer" }, - "assignedHost": { type: ["null", "integer"] }, - "gamePort": { type: ["null", "integer"] }, - "status": { enum: [ - "unknown", "unassigned", "stopped", "starting", "running", "stopping", - "creating_save", "exporting_data", "deleted", - ]}, - }, - }; - - static fromJSON(json) { - return new this(json.name, json.id, json.assignedHost, json.gamePort, json.status); - } -} - -/** @memberof module:lib */ -class InstanceDetailsGetRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.instance.get"; - - /** @type {number} */ - instanceId; - - constructor(instanceId) { - this.instanceId = instanceId; - } - - static jsonSchema = { - type: "object", - required: ["instanceId"], - properties: { - "instanceId": { type: "integer" }, - }, - }; - - static fromJSON(json) { - return new this(json.instanceId); - } - - static Response = InstanceDetails; -} - -/** @memberof module:lib */ -class InstanceDetailsListRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.instance.list"; - static Response = jsonArray(InstanceDetails); -}; - -/** @memberof module:lib */ -class InstanceDetailsSetSubscriptionsRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.instance.subscribe"; - - /** @type {boolean} */ - all; - - /** @type {Array} */ - instanceIds; - - constructor(all, instanceIds) { - this.all = all || false; - this.instanceIds = instanceIds || []; - } - - static jsonSchema = { - type: "object", - properties: { - "all": { type: "boolean" }, - "instanceIds": { - type: "array", - items: { type: "integer" }, - }, - }, - }; - - static fromJSON(json) { - return new this(json.all, json.instanceIds); - } -}; - -/** @memberof module:lib */ -class InstanceDetailsUpdateEvent { - static type = "event"; - static src = "controller"; - static dst = "control"; - - /** @type {module:lib.InstanceDetails} */ - details; - - constructor(details) { - this.details = details; - } - - static jsonSchema = InstanceDetails.jsonSchema; - - toJSON() { - return this.details; - } - - static fromJSON(json) { - return new this(InstanceDetails.fromJSON(json)); - } -}; - -/** @memberof module:lib */ -class InstanceCreateRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.instance.create"; - - /** @type {object} */ - config; - - constructor(config) { - this.config = config; - } - - static jsonSchema = { - type: "object", - required: ["config"], - properties: { - "config": { type: "object" }, - }, - }; - - static fromJSON(json) { - // TODO deserialise config here after config refactor - return new this(json.config); - } -} - -/** @memberof module:lib */ -class InstanceConfigGetRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.instance.get_config"; - - /** @type {number} */ - instanceId; - - constructor(instanceId) { - this.instanceId = instanceId; - } - - static jsonSchema = { - type: "object", - required: ["instanceId"], - properties: { - "instanceId": { type: "integer" }, - }, - }; - - static fromJSON(json) { - return new this(json.instanceId); - } -} -// TODO replace with InstanceConfig after config refactor -InstanceConfigGetRequest.Response = class Response { - /** @type {object} */ - config; - - constructor(config) { - this.config = config; - } - - static jsonSchema = { - type: "object", - required: ["config"], - properties: { - "config": { type: "object" }, - }, - }; - - static fromJSON(json) { - return new this(json.config); - } -}; - -/** @memberof module:lib */ -class InstanceConfigSetFieldRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.instance.update_config"; - - /** @type {number} */ - instanceId; - /** @type {string} */ - field; - /** @type {string} */ - value; - - constructor(instanceId, field, value) { - this.instanceId = instanceId; - this.field = field; - this.value = value; - } - - static jsonSchema = { - type: "object", - required: ["instanceId", "field", "value"], - properties: { - "instanceId": { type: "integer" }, - "field": { type: "string" }, - "value": { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.instanceId, json.field, json.value); - } -} - -/** @memberof module:lib */ -class InstanceConfigSetPropRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.instance.update_config"; - - /** @type {number} */ - instanceId; - /** @type {string} */ - field; - /** @type {string} */ - prop; - /** @type {*} */ - value; - - constructor(instanceId, field, prop, value) { - this.instanceId = instanceId; - this.field = field; - this.prop = prop; - if (value !== undefined) { - this.value = value; - } - } - - static jsonSchema = { - type: "object", - required: ["instanceId", "field", "prop"], - properties: { - "instanceId": { type: "integer" }, - "field": { type: "string" }, - "prop": { type: "string" }, - "value": {}, - }, - }; - - static fromJSON(json) { - return new this(json.instanceId, json.field, json.prop, json.value); - } -} - -/** @memberof module:lib */ -class InstanceAssignRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.instance.assign"; - - /** @type {number} */ - instanceId; - /** @type {?number} */ - hostId; - - constructor(instanceId, hostId) { - this.instanceId = instanceId; - this.hostId = hostId; - } - - static jsonSchema = { - type: "object", - required: ["instanceId", "hostId"], - properties: { - "instanceId": { type: "number" }, - "hostId": { type: ["number", "null"] }, - }, - }; - - static fromJSON(json) { - return new this(json.instanceId, json.hostId); - } -} - -/** @memberof module:lib */ -class InstanceMetricsRequest { - static type = "request"; - static src = "host"; - static dst = "instance"; -} -InstanceMetricsRequest.Response = class Response { // TODO: Use JSON class pattern in Prometheus - /** @type {array} */ - results; - - constructor(results) { - this.results = results; - } - - static jsonSchema = { - type: "object", - required: ["results"], - properties: { - "results": { - type: "array", - }, - }, - }; - - static fromJSON(json) { - return new this(json.results); - } -}; - -/** @memberof module:lib */ -class InstanceStartRequest { - static type = "request"; - static src = ["control", "controller"]; - static dst = "instance"; - static permission = "core.instance.start"; - - /** @type {string|undefined} */ - save; - - constructor(save) { - if (save !== undefined) { this.save = save; }; - } - - static jsonSchema = { - type: "object", - properties: { - "save": { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.save); - } -} - -/** @memberof module:lib */ -class SaveDetails { - /** @type {string} */ - type; - /** @type {string} */ - name; - /** @type {number} */ - size; - /** @type {number} */ - mtimeMs; - /** @type {boolean} */ - loaded; - /** @type {boolean} */ - loadByDefault; - - constructor(type, name, size, mtimeMs, loaded, loadByDefault) { - this.type = type; - this.name = name; - this.size = size; - this.mtimeMs = mtimeMs; - this.loaded = loaded; - this.loadByDefault = loadByDefault; - } - - static jsonSchema = { - type: "object", - required: ["type", "name", "size", "mtimeMs", "loaded"], - properties: { - "type": { enum: ["file", "directory", "special"] }, - "name": { type: "string" }, - "size": { type: "integer" }, - "mtimeMs": { type: "number" }, - "loaded": { type: "boolean" }, - "loadByDefault": { type: "boolean" }, - }, - }; - - static fromJSON(json) { - return new this(json.type, json.name, json.size, json.mtimeMs, json.loaded, json.loadByDefault); - } -} - -/** @memberof module:lib */ -class InstanceListSavesRequest { - static type = "request"; - static src = "control"; - static dst = "instance"; - static permission = "core.instance.save.list"; - static Response = jsonArray(SaveDetails); -} - -/** @memberof module:lib */ -class InstanceSetSaveListSubscriptionsRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.instance.save.list_subscribe"; - - /** @type {boolean} */ - all; - - /** @type {Array} */ - instanceIds; - - constructor(all, instanceIds) { - this.all = all || false; - this.instanceIds = instanceIds || []; - } - - static jsonSchema = { - type: "object", - properties: { - "all": { type: "boolean" }, - "instanceIds": { - type: "array", - items: { type: "integer" }, - }, - }, - }; - - static fromJSON(json) { - return new this(json.all, json.instanceIds); - } -}; - -/** @memberof module:lib */ -class InstanceSaveListUpdateEvent { - static type = "event"; - static src = ["instance", "host", "controller"]; - static dst = ["controller", "control"]; - - /** @type {number} */ - instanceId; - /** @type {module:lib.SaveDetails} */ - saves; - - constructor(instanceId, saves) { - this.instanceId = instanceId; - this.saves = saves; - } - - static jsonSchema = { - type: "object", - required: ["instanceId", "saves"], - properties: { - "instanceId": { type: "integer" }, - "saves": { - type: "array", - items: SaveDetails.jsonSchema, - }, - }, - }; - - static fromJSON(json) { - return new this(json.instanceId, json.saves.map(i => SaveDetails.fromJSON(i))); - } -} - -/** @memberof module:lib */ -class InstanceCreateSaveRequest { - static type = "request"; - static src = "control"; - static dst = "instance"; - static permission = "core.instance.save.create"; - - /** @type {string} */ - name; - /** @type {?number} */ - seed; - /** @type {?object} */ - mapGenSettings; - /** @type {?object} */ - mapSettings; - - constructor(name, seed, mapGenSettings, mapSettings) { - this.name = name; - if (seed) { this.seed = seed; } - if (mapGenSettings) { this.mapGenSettings = mapGenSettings; } - if (mapSettings) { this.mapSettings = mapSettings; } - } - - static jsonSchema = { - type: "object", - required: ["name"], - properties: { - "name": { type: "string" }, - "seed": { type: "integer" }, - "mapGenSettings": { type: "object" }, - "mapSettings": { type: "object" }, - }, - }; - - static fromJSON(json) { - return new this(json.name, json.seed, json.mapGenSettings, json.mapSettings); - } -} - -/** @memberof module:lib */ -class InstanceRenameSaveRequest { - static type = "request"; - static src = ["control", "controller"]; - static dst = ["controller", "host"]; - static permission = "core.instance.save.rename"; - - /** @type {number} */ - instanceId; - /** @type {string} */ - oldName; - /** @type {string} */ - newName; - - constructor(instanceId, oldName, newName) { - this.instanceId = instanceId; - this.oldName = oldName; - this.newName = newName; - } - - static jsonSchema = { - type: "object", - required: ["instanceId", "oldName", "newName"], - properties: { - "instanceId": { type: "integer" }, - "oldName": { type: "string" }, - "newName": { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.instanceId, json.oldName, json.newName); - } -} - -/** @memberof module:lib */ -class InstanceCopySaveRequest { - static type = "request"; - static src = ["control", "controller"]; - static dst = ["controller", "host"]; - static permission = "core.instance.save.copy"; - - /** @type {number} */ - instanceId; - /** @type {string} */ - source; - /** @type {string} */ - destination; - - constructor(instanceId, source, destination) { - this.instanceId = instanceId; - this.source = source; - this.destination = destination; - } - - static jsonSchema = { - type: "object", - required: ["instanceId", "source", "destination"], - properties: { - "instanceId": { type: "integer" }, - "source": { type: "string" }, - "destination": { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.instanceId, json.source, json.destination); - } -} - -/** @memberof module:lib */ -class InstanceDeleteSaveRequest { - static type = "request"; - static src = ["control", "controller"]; - static dst = ["controller", "host"]; - static permission = "core.instance.save.delete"; - - /** @type {number} */ - instanceId; - /** @type {string} */ - name; - - constructor(instanceId, name) { - this.instanceId = instanceId; - this.name = name; - } - - static jsonSchema = { - type: "object", - required: ["instanceId", "name"], - properties: { - "instanceId": { type: "integer" }, - "name": { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.instanceId, json.name); - } -} - -/** @memberof module:lib */ -class InstanceDownloadSaveRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.instance.save.download"; - - /** @type {number} */ - instanceId; - /** @type {string} */ - name; - - constructor(instanceId, name) { - this.instanceId = instanceId; - this.name = name; - } - - static jsonSchema = { - type: "object", - required: ["name"], - properties: { - "instanceId": { type: "integer" }, - "name": { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.instanceId, json.name); - } - - static Response = JsonString; -} - -/** @memberof module:lib */ -class InstanceTransferSaveRequest { - static type = "request"; - static src = ["control", "controller"]; - static dst = ["controller", "host"]; - static permission(user, message) { - user.checkPermission("core.instance.save.transfer"); - if (message.data.copy) { - user.checkPermission("core.instance.save.copy"); - } else if (message.data.sourceName !== message.data.targetName) { - user.checkPermission("core.instance.save.rename"); - } - } - - - /** @type {number} */ - sourceInstanceId; - /** @type {string} */ - sourceName; - /** @type {number} */ - targetInstanceId; - /** @type {string} */ - targetName; - /** @type {boolean} */ - copy; - - constructor(sourceInstanceId, sourceName, targetInstanceId, targetName, copy) { - this.sourceInstanceId = sourceInstanceId; - this.sourceName = sourceName; - this.targetInstanceId = targetInstanceId; - this.targetName = targetName; - this.copy = copy; - } - - static jsonSchema = { - type: "object", - required: ["sourceInstanceId", "sourceName", "targetInstanceId", "targetName", "copy"], - properties: { - "sourceInstanceId": { type: "number" }, - "sourceName": { type: "string" }, - "targetInstanceId": { type: "number" }, - "targetName": { type: "string" }, - "copy": { type: "boolean" }, - }, - }; - - static fromJSON(json) { - return new this( - json.sourceInstanceId, json.sourceName, json.targetInstanceId, json.targetName, json.copy - ); - } - - static Response = JsonString; -} - - -/** @memberof module:lib */ -class InstancePullSaveRequest { - static type = "request"; - static src = "controller"; - static dst = "host"; - - /** @type {number} */ - instanceId; - /** @type {string} */ - streamId; - /** @type {string} */ - name; - - constructor(instanceId, streamId, name) { - this.instanceId = instanceId; - this.streamId = streamId; - this.name = name; - } - - static jsonSchema = { - type: "object", - required: ["instanceId", "streamId", "name"], - properties: { - "instanceId": { type: "integer" }, - "streamId": { type: "string" }, - "name": { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.instanceId, json.streamId, json.name); - } - - static Response = JsonString; -} - -/** @memberof module:lib */ -class InstancePushSaveRequest { - static type = "request"; - static src = "controller"; - static dst = "host"; - - /** @type {number} */ - instanceId; - /** @type {string} */ - streamId; - /** @type {string} */ - name; - - constructor(instanceId, streamId, name) { - this.instanceId = instanceId; - this.streamId = streamId; - this.name = name; - } - - static jsonSchema = { - type: "object", - required: ["instanceId", "streamId", "name"], - properties: { - "instanceId": { type: "integer" }, - "streamId": { type: "string" }, - "name": { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.instanceId, json.streamId, json.name); - } -} - -/** @memberof module:lib */ -class InstanceLoadScenarioRequest { - static type = "request"; - static src = "control"; - static dst = "instance"; - static permission = "core.instance.load_scenario"; - - /** @type {string} */ - scenario; - /** @type {?number} */ - seed; - /** @type {?object} */ - mapGenSettings; - /** @type {?object} */ - mapSettings; - - constructor(scenario, seed, mapGenSettings, mapSettings) { - this.scenario = scenario; - if (seed) { this.seed = seed; } - if (mapGenSettings) { this.mapGenSettings = mapGenSettings; } - if (mapSettings) { this.mapSettings = mapSettings; } - } - - static jsonSchema = { - type: "object", - required: ["scenario"], - properties: { - "scenario": { type: "string" }, - "seed": { type: "integer" }, - "mapGenSettings": { type: "object" }, - "mapSettings": { type: "object" }, - }, - }; - - static fromJSON(json) { - return new this(json.scenario, json.seed, json.mapGenSettings, json.mapSettings); - } -} - -/** @memberof module:lib */ -class InstanceExportDataRequest { - static type = "request"; - static src = "control"; - static dst = "instance"; - static permission = "core.instance.export_data"; -} - -/** @memberof module:lib */ -class InstanceExtractPlayersRequest { - static type = "request"; - static src = "control"; - static dst = "instance"; - static permission = "core.instance.extract_players"; -} - -/** @memberof module:lib */ -class InstanceStopRequest { - static type = "request"; - static src = "control"; - static dst = "instance"; - static permission = "core.instance.stop"; -} - -/** @memberof module:lib */ -class InstanceKillRequest { - static type = "request"; - static src = "control"; - static dst = "instance"; - static permission = "core.instance.kill"; -} - -/** @memberof module:lib */ -class InstanceDeleteRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.instance.delete"; - - /** @type {number} */ - instanceId; - - constructor(instanceId) { - this.instanceId = instanceId; - } - - static jsonSchema = { - type: "object", - required: ["instanceId"], - properties: { - instanceId: { type: "integer" }, - }, - }; - - static fromJSON(json) { - return new this(json.instanceId); - } -} - -/** @memberof module:lib */ -class InstanceDeleteInternalRequest { - static type = "request"; - static src = "controller"; - static dst = "host"; - - /** @type {number} */ - instanceId; - - constructor(instanceId) { - this.instanceId = instanceId; - } - - static jsonSchema = { - type: "object", - required: ["instanceId"], - properties: { - instanceId: { type: "integer" }, - }, - }; - - static fromJSON(json) { - return new this(json.instanceId); - } -} - -/** @memberof module:lib */ -class InstanceSendRconRequest { - static type = "request"; - static src = "control"; - static dst = "instance"; - static permission = "core.instance.send_rcon"; - - /** @type {string} */ - command; - - constructor(command) { - this.command = command; - } - - static jsonSchema = { - type: "object", - required: ["command"], - properties: { - "command": { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.command); - } - - static Response = JsonString; -} - -// TODO remove this after config refactor -/** @memberof module:lib */ -class RawInstanceInfo { - /** @type {object} */ - config; - - /** @type {string} */ - status; - - constructor(config, status) { - this.config = config; - this.status = status; - } - - static jsonSchema = { - type: "object", - required: ["config", "status"], - properties: { - "config": { type: "object" }, - "status": { enum: [ - "stopped", "starting", "running", "stopping", "creating_save", "exporting_data", - ]}, - }, - }; - - static fromJSON(json) { - return new this(json.config, json.status); - } -} - -/** @memberof module:lib */ -class InstancesUpdateRequest { - static type = "request"; - static src = "host"; - static dst = "controller"; - - /** @type {Array} */ - instances; - - constructor(instances) { - this.instances = instances; - } - - static jsonSchema = { - type: "array", - items: RawInstanceInfo.jsonSchema, - }; - - toJSON() { - return this.instances; - } - - static fromJSON(json) { - return new this(json.map(i => RawInstanceInfo.fromJSON(i))); - } -} - -/** @memberof module:lib */ -class InstanceAssignInternalRequest { - static type = "request"; - static src = "controller"; - static dst = "host"; - - /** @type {number} */ - instanceId; - /** @type {object} */ - config; - - constructor(instanceId, config) { - this.instanceId = instanceId; - this.config = config; - } - - static jsonSchema = { - type: "object", - required: ["instanceId", "config"], - properties: { - "instanceId": { type: "integer" }, - "config": { type: "object" }, - }, - }; - - static fromJSON(json) { - // TODO deserialise config here after config refactor - return new this(json.instanceId, json.config); - } -} - -/** @memberof module:lib */ -class InstanceUnassignInternalRequest { - static type = "request"; - static src = "controller"; - static dst = "host"; - - /** @type {number} */ - instanceId; - - constructor(instanceId) { - this.instanceId = instanceId; - } - - static jsonSchema = { - type: "object", - required: ["instanceId"], - properties: { - "instanceId": { type: "integer" }, - }, - }; - - static fromJSON(json) { - return new this(json.instanceId); - } -} - -/** @memberof module:lib */ -class InstanceInitialisedEvent { - static type = "event"; - static src = "instance"; - static dst = "host"; - - /** @type {Object} */ - plugins; - - constructor(plugins) { - this.plugins = plugins; - } - - static jsonSchema = { - type: "object", - required: ["plugins"], - properties: { - "plugins": { - type: "object", - additionalProperties: { type: "string" }, - }, - }, - }; - - static fromJSON(json) { - return new this(json.plugins); - } -} - -/** @memberof module:lib */ -class InstanceStatusChangedEvent { - static type = "event"; - static src = ["instance", "host"]; - static dst = "controller"; - - /** @type {integer} */ - instanceId; - /** @type {string} */ - status; - /** @type {?number} */ - gamePort; - - constructor(instanceId, status, gamePort) { - this.instanceId = instanceId; - this.status = status; - this.gamePort = gamePort; - } - - static jsonSchema = { - type: "object", - required: ["instanceId", "status", "gamePort"], - properties: { - "instanceId": { type: "integer" }, - "status": { - type: "string", - enum: [ - "stopped", "starting", "running", "stopping", "creating_save", "exporting_data", - ], - }, - "gamePort": { type: ["null", "integer"] }, - }, - }; - - static fromJSON(json) { - return new this(json.instanceId, json.status, json.gamePort); - } -} - -/** @memberof module:lib */ -class InstanceDetailsChangedEvent { - static type = "event"; - static src = "instance"; - static dst = "controller"; - - /** @type {module:lib.InstanceDetails} */ - details; - - constructor(details) { - this.details = details; - } - - static jsonSchema = InstanceDetails.jsonSchema; - - static fromJSON(json) { - return new this(InstanceDetails.fromJSON(json)); - } -} - -/** @memberof module:lib */ -class InstanceBanlistUpdateEvent { - static type = "event"; - static src = "controller"; - static dst = "instance"; - - /** @type {string} */ - name; - - /** @type {boolean} */ - banned; - - /** @type {string} */ - reason; - - constructor(name, banned, reason) { - this.name = name; - this.banned = banned; - this.reason = reason; - } - - static jsonSchema = { - type: "object", - required: ["name", "banned", "reason"], - properties: { - "name": { type: "string" }, - "banned": { type: "boolean" }, - "reason": { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.name, json.banned, json.reason); - } -} - -/** @memberof module:lib */ -class InstanceAdminlistUpdateEvent { - static type = "event"; - static src = "controller"; - static dst = "instance"; - - /** @type {string} */ - name; - - /** @type {boolean} */ - admin; - - constructor(name, admin) { - this.name = name; - this.admin = admin; - } - - static jsonSchema = { - type: "object", - required: ["name", "admin"], - properties: { - "name": { type: "string" }, - "admin": { type: "boolean" }, - }, - }; - - static fromJSON(json) { - return new this(json.name, json.admin); - } -} - -/** @memberof module:lib */ -class InstanceWhitelistUpdateEvent { - static type = "event"; - static src = "controller"; - static dst = "instance"; - - /** @type {string} */ - name; - - /** @type {boolean} */ - whitelisted; - - constructor(name, whitelisted) { - this.name = name; - this.whitelisted = whitelisted; - } - - static jsonSchema = { - type: "object", - required: ["name", "whitelisted"], - properties: { - "name": { type: "string" }, - "whitelisted": { type: "boolean" }, - }, - }; - - static fromJSON(json) { - return new this(json.name, json.whitelisted); - } -} - -/** @memberof module:lib */ -class InstancePlayerUpdateEvent { - static type = "event"; - static src = "instance"; - static dst = "controller"; - - /** @type {string} */ - type; - - /** @type {string} */ - name; - - /** @type {?boolean} */ - reason; - - /** @type {?string} */ - stats; - - constructor(type, name, reason, stats) { - this.type = type; - this.name = name; - if (reason !== undefined) { this.reason = reason; } - if (stats !== undefined) { this.stats = stats; } - } - - static jsonSchema = { - type: "object", - required: ["type", "name"], - properties: { - "type": { type: "string", enum: ["join", "leave", "import"] }, - "name": { type: "string" }, - "reason": { type: "string" }, - "stats": PlayerStats.jsonSchema, - }, - }; - - static fromJSON(json) { - return new this(json.name, json.whitelisted); - } -} - -module.exports = { - InstanceDetails, - InstanceDetailsGetRequest, - InstanceDetailsListRequest, - InstanceDetailsSetSubscriptionsRequest, - InstanceDetailsUpdateEvent, - InstanceCreateRequest, - InstanceConfigGetRequest, - InstanceConfigSetFieldRequest, - InstanceConfigSetPropRequest, - InstanceAssignRequest, - InstanceMetricsRequest, - InstanceStartRequest, - SaveDetails, - InstanceListSavesRequest, - InstanceSetSaveListSubscriptionsRequest, - InstanceSaveListUpdateEvent, - InstanceCreateSaveRequest, - InstanceRenameSaveRequest, - InstanceCopySaveRequest, - InstanceDeleteSaveRequest, - InstanceDownloadSaveRequest, - InstanceTransferSaveRequest, - InstancePullSaveRequest, - InstancePushSaveRequest, - InstanceLoadScenarioRequest, - InstanceExportDataRequest, - InstanceExtractPlayersRequest, - InstanceStopRequest, - InstanceKillRequest, - InstanceDeleteRequest, - InstanceDeleteInternalRequest, - InstanceSendRconRequest, - RawInstanceInfo, - InstancesUpdateRequest, - InstanceAssignInternalRequest, - InstanceUnassignInternalRequest, - InstanceInitialisedEvent, - InstanceStatusChangedEvent, - InstanceDetailsChangedEvent, - InstanceBanlistUpdateEvent, - InstanceAdminlistUpdateEvent, - InstanceWhitelistUpdateEvent, - InstancePlayerUpdateEvent, -}; diff --git a/packages/lib/src/data/messages_instance.ts b/packages/lib/src/data/messages_instance.ts new file mode 100644 index 00000000..214a665a --- /dev/null +++ b/packages/lib/src/data/messages_instance.ts @@ -0,0 +1,916 @@ +import { Type, Static } from "@sinclair/typebox"; +import PlayerStats from "../PlayerStats"; +import { JsonString, StringEnum, jsonArray } from "./composites"; +import type { User } from "../users"; +import type { MessageRequest } from "./messages_core"; + +export type InstanceStatus = + "unknown" | "unassigned" | "stopped" | "starting" | "running" + | "stopping" | "creating_save" | "exporting_data" | "deleted" +; +export class InstanceDetails { + constructor( + public name: string, + public id: number, + public assignedHost: number | undefined, + public gamePort: number | undefined, + public status: InstanceStatus, + ) { } + + static jsonSchema = Type.Object({ + "name": Type.String(), + "id": Type.Integer(), + "assignedHost": Type.Optional(Type.Integer()), + "gamePort": Type.Optional(Type.Integer()), + "status": StringEnum([ + "unknown", "unassigned", "stopped", "starting", "running", "stopping", + "creating_save", "exporting_data", "deleted", + ]), + }); + + static fromJSON(json: Static) { + return new this(json.name, json.id, json.assignedHost, json.gamePort, json.status); + } +} + +export class InstanceDetailsGetRequest { + declare ["constructor"]: typeof InstanceDetailsGetRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.instance.get"; + + constructor( + public instanceId: number, + ) { } + + static jsonSchema = Type.Object({ + "instanceId": Type.Integer(), + }); + + static fromJSON(json: Static) { + return new this(json.instanceId); + } + + static Response = InstanceDetails; +} + +export class InstanceDetailsListRequest { + declare ["constructor"]: typeof InstanceDetailsListRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.instance.list"; + static Response = jsonArray(InstanceDetails); +}; + +export class InstanceDetailsSetSubscriptionsRequest { + declare ["constructor"]: typeof InstanceDetailsSetSubscriptionsRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.instance.subscribe"; + + constructor( + public all: boolean = false, + public instanceIds: number[] = [], + ) { } + + static jsonSchema = Type.Object({ + "all": Type.Boolean(), + "instanceIds": Type.Array(Type.Integer()), + }); + + static fromJSON(json: Static) { + return new this(json.all, json.instanceIds); + } +}; + +export class InstanceDetailsUpdateEvent { + declare ["constructor"]: typeof InstanceDetailsUpdateEvent; + static type = "event"; + static src = "controller"; + static dst = "control"; + + constructor( + public details: InstanceDetails, + ) { } + + static jsonSchema = InstanceDetails.jsonSchema; + + toJSON() { + return this.details; + } + + static fromJSON(json: Static) { + return new this(InstanceDetails.fromJSON(json)); + } +}; + +export class InstanceCreateRequest { + declare ["constructor"]: typeof InstanceCreateRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.instance.create"; + + constructor( + public config: object, + ) { } + + static jsonSchema = Type.Object({ + "config": Type.Object({}), + }); + + static fromJSON(json: Static) { + // TODO deserialise config here after config refactor + return new this(json.config); + } +} + +export class InstanceConfigGetRequest { + declare ["constructor"]: typeof InstanceConfigGetRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.instance.get_config"; + + constructor( + public instanceId: number, + ) { } + + static jsonSchema = Type.Object({ + "instanceId": Type.Integer(), + }); + + static fromJSON(json: Static) { + return new this(json.instanceId); + } + + // TODO replace with InstanceConfig after config refactor + static Response = class Response { + constructor( + public config: object, + ) { } + + static jsonSchema = Type.Object({ + "config": Type.Object({}), + }); + + static fromJSON(json: Static) { + return new this(json.config); + } + }; +} + +export class InstanceConfigSetFieldRequest { + declare ["constructor"]: typeof InstanceConfigSetFieldRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.instance.update_config"; + + constructor( + public instanceId: number, + public field: string, + public value: string, + ) { } + + static jsonSchema = Type.Object({ + "instanceId": Type.Integer(), + "field": Type.String(), + "value": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.instanceId, json.field, json.value); + } +} + +export class InstanceConfigSetPropRequest { + declare ["constructor"]: typeof InstanceConfigSetPropRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.instance.update_config"; + + constructor( + public instanceId: number, + public field: string, + public prop: string, + public value?: unknown, + ) { } + + static jsonSchema = Type.Object({ + "instanceId": Type.Integer(), + "field": Type.String(), + "prop": Type.String(), + "value": Type.Unknown(), + }); + + static fromJSON(json: Static) { + return new this(json.instanceId, json.field, json.prop, json.value); + } +} + +export class InstanceAssignRequest { + declare ["constructor"]: typeof InstanceAssignRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.instance.assign"; + + constructor( + public instanceId: number, + public hostId?: number, + ) { } + + static jsonSchema = Type.Object({ + "instanceId": Type.Number(), + "hostId": Type.Optional(Type.Number()), + }); + + static fromJSON(json: Static) { + return new this(json.instanceId, json.hostId); + } +} + +export class InstanceMetricsRequest { + declare ["constructor"]: typeof InstanceMetricsRequest; + static type = "request"; + static src = "host"; + static dst = "instance"; + + static Response = class Response { // TODO: Use JSON class pattern in Prometheus + constructor( + public results: object[], + ) { } + + static jsonSchema = Type.Object({ + "results": Type.Array(Type.Object({})), + }); + + static fromJSON(json: Static) { + return new this(json.results); + } + }; +} + +export class InstanceStartRequest { + declare ["constructor"]: typeof InstanceStartRequest; + static type = "request"; + static src = ["control", "controller"]; + static dst = "instance"; + static permission = "core.instance.start"; + + constructor( + public save?: string, + ) { } + + static jsonSchema = Type.Object({ + "save": Type.Optional(Type.String()), + }); + + static fromJSON(json: Static) { + return new this(json.save); + } +} + +export class SaveDetails { + constructor( + public type: string, + public name: string, + public size: number, + public mtimeMs: number, + public loaded: boolean, + public loadByDefault: boolean, + ) { } + + static jsonSchema = Type.Object({ + "type": StringEnum(["file", "directory", "special"]), + "name": Type.String(), + "size": Type.Integer(), + "mtimeMs": Type.Number(), + "loaded": Type.Boolean(), + "loadByDefault": Type.Boolean(), + }); + + static fromJSON(json: Static) { + return new this(json.type, json.name, json.size, json.mtimeMs, json.loaded, json.loadByDefault); + } +} + +export class InstanceListSavesRequest { + declare ["constructor"]: typeof InstanceListSavesRequest; + static type = "request"; + static src = "control"; + static dst = "instance"; + static permission = "core.instance.save.list"; + static Response = jsonArray(SaveDetails); +} + +export class InstanceSetSaveListSubscriptionsRequest { + declare ["constructor"]: typeof InstanceSetSaveListSubscriptionsRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.instance.save.list_subscribe"; + + constructor( + public all: boolean = false, + public instanceIds: number[] = [], + ) { } + + static jsonSchema = Type.Object({ + "all": Type.Boolean(), + "instanceIds": Type.Array(Type.Integer()), + }); + + static fromJSON(json: Static) { + return new this(json.all, json.instanceIds); + } +}; + +export class InstanceSaveListUpdateEvent { + declare ["constructor"]: typeof InstanceSaveListUpdateEvent; + static type = "event"; + static src = ["instance", "host", "controller"]; + static dst = ["controller", "control"]; + + constructor( + public instanceId: number, + public saves: SaveDetails[], + ) { } + + static jsonSchema = Type.Object({ + "instanceId": Type.Integer(), + "saves": Type.Array(SaveDetails.jsonSchema), + }); + + static fromJSON(json: Static) { + return new this(json.instanceId, json.saves.map(i => SaveDetails.fromJSON(i))); + } +} + +export class InstanceCreateSaveRequest { + declare ["constructor"]: typeof InstanceCreateSaveRequest; + static type = "request"; + static src = "control"; + static dst = "instance"; + static permission = "core.instance.save.create"; + + constructor( + public name: string, + public seed?: number, + public mapGenSettings?: object, + public mapSettings?: object, + ) { } + + static jsonSchema = Type.Object({ + "name": Type.String(), + "seed": Type.Optional(Type.Integer()), + "mapGenSettings": Type.Optional(Type.Object({})), + "mapSettings": Type.Optional(Type.Object({})), + }); + + static fromJSON(json: Static) { + return new this(json.name, json.seed, json.mapGenSettings, json.mapSettings); + } +} + +export class InstanceRenameSaveRequest { + declare ["constructor"]: typeof InstanceRenameSaveRequest; + static type = "request"; + static src = ["control", "controller"]; + static dst = ["controller", "host"]; + static permission = "core.instance.save.rename"; + + constructor( + public instanceId: number, + public oldName: string, + public newName: string, + ) { } + + static jsonSchema = Type.Object({ + "instanceId": Type.Integer(), + "oldName": Type.String(), + "newName": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.instanceId, json.oldName, json.newName); + } +} + +export class InstanceCopySaveRequest { + declare ["constructor"]: typeof InstanceCopySaveRequest; + static type = "request"; + static src = ["control", "controller"]; + static dst = ["controller", "host"]; + static permission = "core.instance.save.copy"; + + constructor( + public instanceId: number, + public source: string, + public destination: string, + ) { } + + static jsonSchema = Type.Object({ + "instanceId": Type.Integer(), + "source": Type.String(), + "destination": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.instanceId, json.source, json.destination); + } +} + +export class InstanceDeleteSaveRequest { + declare ["constructor"]: typeof InstanceDeleteSaveRequest; + static type = "request"; + static src = ["control", "controller"]; + static dst = ["controller", "host"]; + static permission = "core.instance.save.delete"; + + constructor( + public instanceId: number, + public name: string, + ) { } + + static jsonSchema = Type.Object({ + "instanceId": Type.Integer(), + "name": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.instanceId, json.name); + } +} + +export class InstanceDownloadSaveRequest { + declare ["constructor"]: typeof InstanceDownloadSaveRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.instance.save.download"; + + constructor( + public instanceId: number, + public name: string, + ) { } + + static jsonSchema = Type.Object({ + "instanceId": Type.Integer(), + "name": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.instanceId, json.name); + } + + static Response = JsonString; +} + +export class InstanceTransferSaveRequest { + declare ["constructor"]: typeof InstanceTransferSaveRequest; + static type = "request"; + static src = ["control", "controller"]; + static dst = ["controller", "host"]; + static permission(user: User, message: MessageRequest) { + user.checkPermission("core.instance.save.transfer"); + if (typeof message.data === "object" && message.data !== null) { + const data = message.data as { copy: boolean, sourceName: string, targetName: string }; + if (data.copy) { + user.checkPermission("core.instance.save.copy"); + } else if (data.sourceName !== data.targetName) { + user.checkPermission("core.instance.save.rename"); + } + } + } + + constructor( + public sourceInstanceId: number, + public sourceName: string, + public targetInstanceId: number, + public targetName: string, + public copy: boolean, + ) { } + + static jsonSchema = Type.Object({ + "sourceInstanceId": Type.Number(), + "sourceName": Type.String(), + "targetInstanceId": Type.Number(), + "targetName": Type.String(), + "copy": Type.Boolean(), + }); + + static fromJSON(json: Static) { + return new this( + json.sourceInstanceId, json.sourceName, json.targetInstanceId, json.targetName, json.copy + ); + } + + static Response = JsonString; +} + + +export class InstancePullSaveRequest { + declare ["constructor"]: typeof InstancePullSaveRequest; + static type = "request"; + static src = "controller"; + static dst = "host"; + + constructor( + public instanceId: number, + public streamId: string, + public name: string, + ) { } + + static jsonSchema = Type.Object({ + "instanceId": Type.Integer(), + "streamId": Type.String(), + "name": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.instanceId, json.streamId, json.name); + } + + static Response = JsonString; +} + +export class InstancePushSaveRequest { + declare ["constructor"]: typeof InstancePushSaveRequest; + static type = "request"; + static src = "controller"; + static dst = "host"; + + constructor( + public instanceId: number, + public streamId: string, + public name: string, + ) { } + + static jsonSchema = Type.Object({ + "instanceId": Type.Integer(), + "streamId": Type.String(), + "name": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.instanceId, json.streamId, json.name); + } +} + +export class InstanceLoadScenarioRequest { + declare ["constructor"]: typeof InstanceLoadScenarioRequest; + static type = "request"; + static src = "control"; + static dst = "instance"; + static permission = "core.instance.load_scenario"; + + constructor( + public scenario: string, + public seed?: number, + public mapGenSettings?: object, + public mapSettings?: object, + ) { } + + static jsonSchema = Type.Object({ + "scenario": Type.String(), + "seed": Type.Optional(Type.Integer()), + "mapGenSettings": Type.Optional(Type.Object({})), + "mapSettings": Type.Optional(Type.Object({})), + }); + + static fromJSON(json: Static) { + return new this(json.scenario, json.seed, json.mapGenSettings, json.mapSettings); + } +} + +export class InstanceExportDataRequest { + declare ["constructor"]: typeof InstanceExportDataRequest; + static type = "request"; + static src = "control"; + static dst = "instance"; + static permission = "core.instance.export_data"; +} + +export class InstanceExtractPlayersRequest { + declare ["constructor"]: typeof InstanceExtractPlayersRequest; + static type = "request"; + static src = "control"; + static dst = "instance"; + static permission = "core.instance.extract_players"; +} + +export class InstanceStopRequest { + declare ["constructor"]: typeof InstanceStopRequest; + static type = "request"; + static src = "control"; + static dst = "instance"; + static permission = "core.instance.stop"; +} + +export class InstanceKillRequest { + declare ["constructor"]: typeof InstanceKillRequest; + static type = "request"; + static src = "control"; + static dst = "instance"; + static permission = "core.instance.kill"; +} + +export class InstanceDeleteRequest { + declare ["constructor"]: typeof InstanceDeleteRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.instance.delete"; + + constructor( + public instanceId: number, + ) { } + + static jsonSchema = Type.Object({ + "instanceId": Type.Integer(), + }); + + static fromJSON(json: Static) { + return new this(json.instanceId); + } +} + +export class InstanceDeleteInternalRequest { + declare ["constructor"]: typeof InstanceDeleteInternalRequest; + static type = "request"; + static src = "controller"; + static dst = "host"; + + constructor( + public instanceId: number, + ) { } + + static jsonSchema = Type.Object({ + "instanceId": Type.Integer(), + }); + + static fromJSON(json: Static) { + return new this(json.instanceId); + } +} + +export class InstanceSendRconRequest { + declare ["constructor"]: typeof InstanceSendRconRequest; + static type = "request"; + static src = "control"; + static dst = "instance"; + static permission = "core.instance.send_rcon"; + + constructor( + public command: string, + ) { } + + static jsonSchema = Type.Object({ + "command": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.command); + } + + static Response = JsonString; +} + +// TODO remove this after config refactor +export class RawInstanceInfo { + + constructor( + public config: object, + public status: string, + ) { } + + static jsonSchema = Type.Object({ + "config": Type.Object({}), + "status": StringEnum([ + "stopped", "starting", "running", "stopping", "creating_save", "exporting_data", + ]), + }); + + static fromJSON(json: Static) { + return new this(json.config, json.status); + } +} + +export class InstancesUpdateRequest { + declare ["constructor"]: typeof InstancesUpdateRequest; + static type = "request"; + static src = "host"; + static dst = "controller"; + + constructor( + public instances: RawInstanceInfo[], + ) { } + + static jsonSchema = Type.Array(RawInstanceInfo.jsonSchema); + + toJSON() { + return this.instances; + } + + static fromJSON(json: Static) { + return new this(json.map(i => RawInstanceInfo.fromJSON(i))); + } +} + +export class InstanceAssignInternalRequest { + declare ["constructor"]: typeof InstanceAssignInternalRequest; + static type = "request"; + static src = "controller"; + static dst = "host"; + + constructor( + public instanceId: number, + public config: object, + ) { } + + static jsonSchema = Type.Object({ + "instanceId": Type.Integer(), + "config": Type.Object({}), + }); + + static fromJSON(json: Static) { + // TODO deserialise config here after config refactor + return new this(json.instanceId, json.config); + } +} + +export class InstanceUnassignInternalRequest { + declare ["constructor"]: typeof InstanceUnassignInternalRequest; + static type = "request"; + static src = "controller"; + static dst = "host"; + + constructor( + public instanceId: number, + ) { } + + static jsonSchema = Type.Object({ + "instanceId": Type.Integer(), + }); + + static fromJSON(json: Static) { + return new this(json.instanceId); + } +} + +export class InstanceInitialisedEvent { + declare ["constructor"]: typeof InstanceInitialisedEvent; + static type = "event"; + static src = "instance"; + static dst = "host"; + + constructor( + public plugins: Record, + ) { } + + static jsonSchema = Type.Object({ + "plugins": Type.Record(Type.String(), Type.String()), + }); + + static fromJSON(json: Static) { + return new this(json.plugins); + } +} + +export class InstanceStatusChangedEvent { + declare ["constructor"]: typeof InstanceStatusChangedEvent; + static type = "event"; + static src = ["instance", "host"]; + static dst = "controller"; + + constructor( + public instanceId: number, + public status: string, + public gamePort?: number, + ) { } + + static jsonSchema = Type.Object({ + "instanceId": Type.Integer(), + "status": StringEnum([ + "stopped", "starting", "running", "stopping", "creating_save", "exporting_data", + ]), + "gamePort": Type.Optional(Type.Integer()), + }); + + static fromJSON(json: Static) { + return new this(json.instanceId, json.status, json.gamePort); + } +} + +export class InstanceDetailsChangedEvent { + declare ["constructor"]: typeof InstanceDetailsChangedEvent; + static type = "event"; + static src = "instance"; + static dst = "controller"; + + constructor( + public details: InstanceDetails, + ) { } + + static jsonSchema = InstanceDetails.jsonSchema; + + static fromJSON(json: Static) { + return new this(InstanceDetails.fromJSON(json)); + } +} + +export class InstanceBanlistUpdateEvent { + declare ["constructor"]: typeof InstanceBanlistUpdateEvent; + static type = "event"; + static src = "controller"; + static dst = "instance"; + + constructor( + public name: string, + public banned: boolean, + public reason: string, + ) { } + + static jsonSchema = Type.Object({ + "name": Type.String(), + "banned": Type.Boolean(), + "reason": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.name, json.banned, json.reason); + } +} + +export class InstanceAdminlistUpdateEvent { + declare ["constructor"]: typeof InstanceAdminlistUpdateEvent; + static type = "event"; + static src = "controller"; + static dst = "instance"; + + constructor( + public name: string, + public admin: boolean, + ) { } + + static jsonSchema = Type.Object({ + "name": Type.String(), + "admin": Type.Boolean(), + }); + + static fromJSON(json: Static) { + return new this(json.name, json.admin); + } +} + +export class InstanceWhitelistUpdateEvent { + declare ["constructor"]: typeof InstanceWhitelistUpdateEvent; + static type = "event"; + static src = "controller"; + static dst = "instance"; + + constructor( + public name: string, + public whitelisted: boolean, + ) { } + + static jsonSchema = Type.Object({ + "name": Type.String(), + "whitelisted": Type.Boolean(), + }); + + static fromJSON(json: Static) { + return new this(json.name, json.whitelisted); + } +} + +export class InstancePlayerUpdateEvent { + declare ["constructor"]: typeof InstancePlayerUpdateEvent; + static type = "event"; + static src = "instance"; + static dst = "controller"; + + constructor( + public type: string, + public name: string, + public reason?: string, + public stats?: PlayerStats, + ) { } + + static jsonSchema = Type.Object({ + "type": StringEnum(["join", "leave", "import"]), + "name": Type.String(), + "reason": Type.Optional(Type.String()), + "stats": Type.Unsafe(PlayerStats.jsonSchema), + }); + + static fromJSON(json: Static) { + return new this(json.name, json.name, json.reason, PlayerStats.fromJSON(json.stats)); + } +} diff --git a/packages/lib/src/data/messages_mod.js b/packages/lib/src/data/messages_mod.js deleted file mode 100644 index 7f4d34de..00000000 --- a/packages/lib/src/data/messages_mod.js +++ /dev/null @@ -1,474 +0,0 @@ -"use strict"; -const ModPack = require("./ModPack"); -const ModInfo = require("./ModInfo"); -const { jsonArray, JsonString } = require("./composites"); - - -/* @memberof module:lib */ -class ModPackGetRequest { - static type = "request"; - static src = ["instance", "control"]; - static dst = "controller"; - static permission = "core.mod_pack.get"; - - /** @type {number} */ - id; - - constructor(id) { - this.id = id; - } - - static jsonSchema = { - type: "object", - required: ["id"], - properties: { - id: { type: "integer" }, - }, - }; - - static fromJSON(json) { - return new this(json.id); - } - - static Response = ModPack; -} - -/* @memberof module:lib */ -class ModPackGetDefaultRequest { - static type = "request"; - static src = ["instance", "control"]; - static dst = "controller"; - static permission = "core.mod_pack.get"; - static Response = ModPack; -} - -/* @memberof module:lib */ -class ModPackListRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.mod_pack.list"; - static Response = jsonArray(ModPack); -} - -/** @memberof module:lib */ -class ModPackCreateRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.mod_pack.create"; - - /** @type {module:lib.ModPack} */ - modPack; - - constructor(modPack) { - this.modPack = modPack; - } - - static jsonSchema = { - type: "object", - required: ["modPack"], - properties: { - "modPack": ModPack.jsonSchema, - }, - }; - - static fromJSON(json) { - return new this(ModPack.fromJSON(json.modPack)); - } -} - -/* @memberof module:lib */ -class ModPackUpdateRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.mod_pack.update"; - - /** @type {module:lib.ModPack} */ - modPack; - - constructor(modPack) { - this.modPack = modPack; - } - - static jsonSchema = { - type: "object", - required: ["modPack"], - properties: { - "modPack": ModPack.jsonSchema, - }, - }; - - static fromJSON(json) { - return new this(ModPack.fromJSON(json.modPack)); - } -} - -/* @memberof module:lib */ -class ModPackDeleteRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.mod_pack.delete"; - - /** @type {number} */ - id; - - constructor(id) { - this.id = id; - } - - static jsonSchema = { - type: "object", - required: ["id"], - properties: { - id: { type: "integer" }, - }, - }; - - static fromJSON(json) { - return new this(json.id); - } -} - -/* @memberof module:lib */ -class ModPackSetSubscriptionsRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.mod_pack.subscribe"; - - /** @type {boolean} */ - all; - /** @type {Array} */ - modPackIds; - - constructor(all, modPackIds) { - this.all = all; - this.modPackIds = modPackIds; - } - - static jsonSchema = { - type: "object", - required: ["all", "modPackIds"], - properties: { - all: { type: "boolean" }, - modPackIds: { - type: "array", - items: { type: "integer" }, - }, - }, - }; - - static fromJSON(json) { - return new this(json.all, json.modPackIds); - } -} - -/* @memberof module:lib */ -class ModGetRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.mod.get"; - - /** @type {string} */ - name; - /** @type {string} */ - version; - - constructor(name, version) { - this.name = name; - this.version = version; - } - - static jsonSchema = { - type: "object", - required: ["name", "version"], - properties: { - name: { type: "string" }, - version: { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.name, json.version); - } - - static Response = ModInfo; -} - -/* @memberof module:lib */ -class ModListRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.mod.list"; - static Response = jsonArray(ModInfo); -} - -/* @memberof module:lib */ -class ModSearchRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.mod.search"; - - /** @type {string} */ - query; - /** @type {string} */ - factorioVersion; - /** @type {number} */ - page; - /** @type {number|undefined} */ - pageSize; - /** @type {string|undefined} */ - sort; - /** @type {string|undefined} */ - sortOrder; - - constructor(query, factorioVersion, page, pageSize, sort, sortOrder) { - this.query = query; - this.factorioVersion = factorioVersion; - this.page = page; - if (pageSize) { this.pageSize = pageSize; } - if (sort) { this.sort = sort; } - if (sortOrder) { this.sortOrder = sortOrder; } - } - - static jsonSchema = { - type: "object", - required: ["query", "factorioVersion", "page"], - properties: { - query: { type: "string" }, - factorioVersion: { type: "string" }, - pageSize: { type: "integer" }, - page: { type: "integer" }, - sort: { type: "string" }, - sortOrder: { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.query, json.factorioVersion, json.page, json.pageSize, json.sort, json.sortOrder); - } -} -ModSearchRequest.Response = class Response { - /** @type {Array} */ - queryIssues; - /** @type {number} */ - pageCount; - /** @type {number} */ - resultCount; - /** @type {Array} */ - results; - - constructor(queryIssues, pageCount, resultCount, results) { - this.queryIssues = queryIssues; - this.pageCount = pageCount; - this.resultCount = resultCount; - this.results = results; - } - - static jsonSchema = { - type: "object", - required: ["queryIssues", "pageCount", "resultCount", "results"], - properties: { - queryIssues: { - type: "array", - items: { type: "string" }, - }, - pageCount: { type: "integer" }, - resultCount: { type: "integer" }, - results: { - type: "array", - items: { - type: "object", - properties: { - name: { type: "string" }, - versions: { - type: "array", - items: ModInfo.jsonSchema, - }, - }, - }, - }, - }, - }; - - static fromJSON(json) { - let results = json.results.map( - ({ name, versions }) => ({ name, versions: versions.map(mod => ModInfo.fromJSON(mod)) }) - ); - return new this(json.queryIssues, json.pageCount, json.resultCount, results); - } -}; - -/* @memberof module:lib */ -class ModSetSubscriptionsRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.mod.subscribe"; - - /** @type {boolean} */ - all; - /** @type {Array} */ - modNames; - - constructor(all, modNames) { - this.all = all; - this.modNames = modNames; - } - - static jsonSchema = { - type: "object", - required: ["all", "modNames"], - properties: { - all: { type: "boolean" }, - modNames: { - type: "array", - items: { type: "string" }, - }, - }, - }; - - static fromJSON(json) { - return new this(json.all, json.modNames); - } -} - -/* @memberof module:lib */ -class ModDownloadRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.mod.download"; - - /** @type {string} */ - name; - /** @type {string} */ - version; - - constructor(name, version) { - this.name = name; - this.version = version; - } - - static jsonSchema = { - type: "object", - required: ["name", "version"], - properties: { - name: { type: "string" }, - version: { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.name, json.version); - } - - static Response = JsonString; -} - -/* @memberof module:lib */ -class ModDeleteRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.mod.delete"; - - /** @type {string} */ - name; - /** @type {string} */ - version; - - constructor(name, version) { - this.name = name; - this.version = version; - } - - static jsonSchema = { - type: "object", - required: ["name", "version"], - properties: { - name: { type: "string" }, - version: { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.name, json.version); - } -} - -/* @memberof module:lib */ -class ModPackUpdateEvent { - static type = "event"; - static src = "controller"; - static dst = "control"; - - /** @type {module:lib.ModPack} */ - modPack; - - constructor(modPack) { - this.modPack = modPack; - } - - static jsonSchema = { - type: "object", - required: ["modPack"], - properties: { - modPack: ModPack.jsonSchema, - }, - }; - - static fromJSON(json) { - return new this(ModPack.fromJSON(json.modPack)); - } -} - -/* @memberof module:lib */ -class ModUpdateEvent { - static type = "event"; - static src = "controller"; - static dst = "control"; - - /** @type {module:lib.ModInfo} */ - mod; - - constructor(mod) { - this.mod = mod; - } - - static jsonSchema = { - type: "object", - required: ["mod"], - properties: { - mod: ModInfo.jsonSchema, - }, - }; - - static fromJSON(json) { - return new this(ModInfo.fromJSON(json.mod)); - } -} - -module.exports = { - ModPackGetRequest, - ModPackGetDefaultRequest, - ModPackListRequest, - ModPackCreateRequest, - ModPackUpdateRequest, - ModPackDeleteRequest, - ModPackSetSubscriptionsRequest, - ModGetRequest, - ModListRequest, - ModSearchRequest, - ModSetSubscriptionsRequest, - ModDownloadRequest, - ModDeleteRequest, - ModPackUpdateEvent, - ModUpdateEvent, -}; diff --git a/packages/lib/src/data/messages_mod.ts b/packages/lib/src/data/messages_mod.ts new file mode 100644 index 00000000..a32aac3f --- /dev/null +++ b/packages/lib/src/data/messages_mod.ts @@ -0,0 +1,324 @@ +import { Type, Static } from "@sinclair/typebox"; +import ModInfo from "./ModInfo"; +import ModPack from "./ModPack"; +import { JsonString, jsonArray } from "./composites"; + + +export class ModPackGetRequest { + declare ["constructor"]: typeof ModPackGetRequest; + static type = "request"; + static src = ["instance", "control"]; + static dst = "controller"; + static permission = "core.mod_pack.get"; + + constructor( + public id: number, + ) { } + + static jsonSchema = Type.Object({ + "id": Type.Integer(), + }); + + static fromJSON(json: Static) { + return new this(json.id); + } + + static Response = ModPack; +} + +export class ModPackGetDefaultRequest { + declare ["constructor"]: typeof ModPackGetDefaultRequest; + static type = "request"; + static src = ["instance", "control"]; + static dst = "controller"; + static permission = "core.mod_pack.get"; + static Response = ModPack; +} + +export class ModPackListRequest { + declare ["constructor"]: typeof ModPackListRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.mod_pack.list"; + static Response = jsonArray(ModPack); +} + +export class ModPackCreateRequest { + declare ["constructor"]: typeof ModPackCreateRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.mod_pack.create"; + + constructor( + public modPack: ModPack, + ) { } + + static jsonSchema = Type.Object({ + "modPack": Type.Unsafe(ModPack.jsonSchema), + }); + + static fromJSON(json: Static) { + return new this(ModPack.fromJSON(json.modPack)); + } +} + +export class ModPackUpdateRequest { + declare ["constructor"]: typeof ModPackUpdateRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.mod_pack.update"; + + constructor( + public modPack: ModPack, + ) { } + + static jsonSchema = Type.Object({ + "modPack": Type.Unsafe(ModPack.jsonSchema), + }); + + static fromJSON(json: Static) { + return new this(ModPack.fromJSON(json.modPack)); + } +} + +export class ModPackDeleteRequest { + declare ["constructor"]: typeof ModPackDeleteRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.mod_pack.delete"; + + constructor( + public id: number, + ) { } + + static jsonSchema = Type.Object({ + "id": Type.Integer(), + }); + + static fromJSON(json: Static) { + return new this(json.id); + } +} + +export class ModPackSetSubscriptionsRequest { + declare ["constructor"]: typeof ModPackSetSubscriptionsRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.mod_pack.subscribe"; + + constructor( + public all: boolean, + public modPackIds: number[], + ) { } + + static jsonSchema = Type.Object({ + "all": Type.Boolean(), + "modPackIds": Type.Array(Type.Integer()), + }); + + static fromJSON(json: Static) { + return new this(json.all, json.modPackIds); + } +} + +export class ModGetRequest { + declare ["constructor"]: typeof ModGetRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.mod.get"; + + constructor( + public name: string, + public version: string, + ) { } + + static jsonSchema = Type.Object({ + "name": Type.String(), + "version": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.name, json.version); + } + + static Response = ModInfo; +} + +export class ModListRequest { + declare ["constructor"]: typeof ModListRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.mod.list"; + static Response = jsonArray(ModInfo); +} + +export class ModSearchRequest { + declare ["constructor"]: typeof ModSearchRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.mod.search"; + + constructor( + public query: string, + public factorioVersion: string, + public page: number, + public pageSize?: number, + public sort?: string, + public sortOrder?: string, + ) { } + + static jsonSchema = Type.Object({ + "query": Type.String(), + "factorioVersion": Type.String(), + "pageSize": Type.Integer(), + "page": Type.Optional(Type.Integer()), + "sort": Type.Optional(Type.String()), + "sortOrder": Type.Optional(Type.String()), + }); + + static fromJSON(json: Static) { + return new this(json.query, json.factorioVersion, json.page, json.pageSize, json.sort, json.sortOrder); + } + + static Response = class Response { + constructor( + public queryIssues: string[], + public pageCount: number, + public resultCount: number, + public results: object[], + ) { } + + static jsonSchema = Type.Object({ + "queryIssues": Type.Array(Type.String()), + "pageCount": Type.Integer(), + "resultCount": Type.Integer(), + "results": Type.Array( + Type.Object({ + "name": Type.String(), + "versions": Type.Array(Type.Unsafe(ModInfo.jsonSchema)), + }), + ), + }); + + static fromJSON(json: Static) { + let results = json.results.map( + ({ name, versions }) => ({ name, versions: versions.map(mod => ModInfo.fromJSON(mod)) }) + ); + return new this(json.queryIssues, json.pageCount, json.resultCount, results); + } + }; +} + +export class ModSetSubscriptionsRequest { + declare ["constructor"]: typeof ModSetSubscriptionsRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.mod.subscribe"; + + constructor( + public all: boolean, + public modNames: string[], + ) { } + + static jsonSchema = Type.Object({ + "all": Type.Boolean(), + "modNames": Type.Array(Type.String()), + }); + + static fromJSON(json: Static) { + return new this(json.all, json.modNames); + } +} + +export class ModDownloadRequest { + declare ["constructor"]: typeof ModDownloadRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.mod.download"; + + constructor( + public name: string, + public version: string, + ) { } + + static jsonSchema = Type.Object({ + "name": Type.String(), + "version": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.name, json.version); + } + + static Response = JsonString; +} + +export class ModDeleteRequest { + declare ["constructor"]: typeof ModDeleteRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.mod.delete"; + + constructor( + public name: string, + public version: string, + ) { } + + static jsonSchema = Type.Object({ + "name": Type.String(), + "version": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.name, json.version); + } +} + +export class ModPackUpdateEvent { + declare ["constructor"]: typeof ModPackUpdateEvent; + static type = "event"; + static src = "controller"; + static dst = "control"; + + constructor( + public modPack: ModPack, + ) { } + + static jsonSchema = Type.Object({ + "modPack": Type.Unsafe(ModPack.jsonSchema), + }); + + static fromJSON(json: Static) { + return new this(ModPack.fromJSON(json.modPack)); + } +} + +export class ModUpdateEvent { + declare ["constructor"]: typeof ModUpdateEvent; + static type = "event"; + static src = "controller"; + static dst = "control"; + + constructor( + public mod: ModInfo, + ) { } + + static jsonSchema = Type.Object({ + "mod": Type.Unsafe(ModInfo.jsonSchema), + }); + + static fromJSON(json: Static) { + return new this(ModInfo.fromJSON(json.mod)); + } +} diff --git a/packages/lib/src/data/messages_user.js b/packages/lib/src/data/messages_user.js deleted file mode 100644 index edd391cd..00000000 --- a/packages/lib/src/data/messages_user.js +++ /dev/null @@ -1,708 +0,0 @@ -"use strict"; -const PlayerStats = require("../PlayerStats").default; -const { jsonArray, JsonNumber } = require("./composites"); - -/* @memberof module:lib */ -class RawPermission { // TODO refactor into lib/user.Permission - /** @type {string} */ - name; - /** @type {string} */ - title; - /** @type {string} */ - description; - - constructor(name, title, description) { - this.name = name; - this.title = title; - this.description = description; - } - - static jsonSchema = { - type: "object", - required: ["name", "title", "description"], - properties: { - name: { type: "string" }, - title: { type: "string" }, - description: { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.name, json.title, json.description); - } -} - -/* @memberof module:lib */ -class PermissionListRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.permission.list"; - static Response = jsonArray(RawPermission); -} - -class RawRole { // TODO refactor into lib/user.Role - /** @type {number} */ - id; - /** @type {string} */ - name; - /** @type {string} */ - description; - /** @type {Array} */ - permissions; - - constructor(id, name, description, permissions) { - this.id = id; - this.name = name; - this.description = description; - this.permissions = permissions; - } - - static jsonSchema = { - type: "object", - required: ["id", "name", "description", "permissions"], - properties: { - id: { type: "integer" }, - name: { type: "string" }, - description: { type: "string" }, - permissions: { - type: "array", - items: { type: "string" }, - }, - }, - }; - - static fromJSON(json) { - return new this(json.id, json.name, json.description, json.permissions); - } -} - -/* @memberof module:lib */ -class RoleListRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.role.list"; - static Response = jsonArray(RawRole); -} - -/* @memberof module:lib */ -class RoleCreateRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.role.create"; - - /** @type {string} */ - name; - /** @type {string} */ - description; - /** @type {Array} */ - permissions; - - constructor(name, description, permissions) { - this.name = name; - this.description = description; - this.permissions = permissions; - } - - static jsonSchema = { - type: "object", - required: ["name", "description", "permissions"], - properties: { - name: { type: "string" }, - description: { type: "string" }, - permissions: { - type: "array", - items: { type: "string" }, - }, - }, - }; - - static fromJSON(json) { - return new this(json.name, json.description, json.permissions); - } - - static Response = JsonNumber; -} - -/* @memberof module:lib */ -class RoleUpdateRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.role.update"; - - /** @type {number} */ - id; - /** @type {string} */ - name; - /** @type {string} */ - description; - /** @type {Array} */ - permissions; - - constructor(id, name, description, permissions) { - this.id = id; - this.name = name; - this.description = description; - this.permissions = permissions; - } - - static jsonSchema = { - type: "object", - required: ["id", "name", "description", "permissions"], - properties: { - id: { type: "integer" }, - name: { type: "string" }, - description: { type: "string" }, - permissions: { - type: "array", - items: { type: "string" }, - }, - }, - }; - - static fromJSON(json) { - return new this(json.id, json.name, json.description, json.permissions); - } -} - -/* @memberof module:lib */ -class RoleGrantDefaultPermissionsRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.role.update"; - - /** @type {number} */ - id; - - constructor(id) { - this.id = id; - } - - static jsonSchema = { - type: "object", - required: ["id"], - properties: { - id: { type: "integer" }, - }, - }; - - static fromJSON(json) { - return new this(json.id); - } -} - -/* @memberof module:lib */ -class RoleDeleteRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.role.delete"; - - /** @type {number} */ - id; - - constructor(id) { - this.id = id; - } - - static jsonSchema = { - type: "object", - required: ["id"], - properties: { - id: { type: "integer" }, - }, - }; - - static fromJSON(json) { - return new this(json.id); - } -} - -/* @memberof module:lib */ -class RawUser { // TODO refactor into lib/user.User - /** @type {string} */ - name; - /** @type {Array} */ - roles; - /** @type {Array} */ - instances; - /** @type {boolean|undefined} */ - isAdmin; - /** @type {boolean|undefined} */ - isBanned; - /** @type {boolean|undefined} */ - isWhitelisted; - /** @type {string|undefined} */ - banReason; - /** @type {boolean|undefined} */ - isDeleted; - /** @type {module:lib.PlayerStats} */ - playerStats; - /** @type {Map|undefined} */ - instanceStats; - - constructor( - name, roles, instances, isAdmin, isBanned, isWhitelisted, banReason, - isDeleted, playerStats, instanceStats - ) { - this.name = name; - this.roles = roles; - this.instances = instances; - if (isAdmin !== undefined) { this.isAdmin = isAdmin; } - if (isBanned !== undefined) { this.isBanned = isBanned; } - if (isWhitelisted !== undefined) { this.isWhitelisted = isWhitelisted; } - if (banReason !== undefined) { this.banReason = banReason; } - if (isDeleted !== undefined) { this.isDeleted = isDeleted; } - if (playerStats !== undefined) { this.playerStats = playerStats; } - if (instanceStats !== undefined) { this.instanceStats = instanceStats; } - } - - static jsonSchema = { - type: "object", - required: ["name", "roles", "instances"], - properties: { - name: { type: "string" }, - roles: { type: "array", items: { type: "integer" } }, - instances: { type: "array", items: { type: "integer" } }, - isAdmin: { type: "boolean" }, - isBanned: { type: "boolean" }, - isWhitelisted: { type: "boolean" }, - banReason: { type: "string" }, - isDeleted: { type: "boolean" }, - playerStats: PlayerStats.jsonSchema, - instanceStats: { - type: "array", - items: { - type: "array", - minItems: 2, - items: [{ type: "integer" }, PlayerStats.jsonSchema], - }, - }, - }, - }; - - static fromJSON(json) { - let playerStats; - if (json.playerStats) { - playerStats = PlayerStats.fromJSON(json.playerStats); - } - let instanceStats; - if (json.instanceStats) { - instanceStats = new Map( - json.instanceStats.map(([id, stats]) => [id, PlayerStats.fromJSON(stats)]) - ); - } - return new this( - json.name, json.roles, json.instances, json.isAdmin, json.isBanned, json.isWhitelisted, json.banReason, - json.isDeleted, playerStats, instanceStats - ); - } - - toJSON() { - const json = { - name: this.name, - roles: this.roles, - instances: this.instances, - isAdmin: this.isAdmin, - isBanned: this.isBanned, - isWhitelisted: this.isWhitelisted, - banReason: this.banReason, - isDeleted: this.isDeleted, - playerStats: this.playerStats, - }; - if (this.instanceStats) { - json.instanceStats = [...this.instanceStats]; - } - return json; - } -} - -/* @memberof module:lib */ -class UserGetRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.user.get"; - - /** @type {string} */ - name; - - constructor(name) { - this.name = name; - } - - static jsonSchema = { - type: "object", - required: ["name"], - properties: { - name: { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.name); - } - - static Response = RawUser; -} - -/* @memberof module:lib */ -class UserListRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.user.list"; - static Response = jsonArray(RawUser); -} - -/* @memberof module:lib */ -class UserSetSubscriptionsRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.user.subscribe"; - - /** @type {boolean} */ - all; - /** @type {Array} */ - names; - - constructor(all, names) { - this.all = all; - this.names = names; - } - - static jsonSchema = { - type: "object", - required: ["all", "names"], - properties: { - all: { type: "boolean" }, - names: { - type: "array", - items: { type: "string" }, - }, - }, - }; - - static fromJSON(json) { - return new this(json.all, json.names); - } -} - -/* @memberof module:lib */ -class UserCreateRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.user.create"; - - /** @type {string} */ - name; - - constructor(name) { - this.name = name; - } - - static jsonSchema = { - type: "object", - required: ["name"], - properties: { - name: { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.name); - } -} - -/* @memberof module:lib */ -class UserRevokeTokenRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.user.revoke_token"; - - /** @type {string} */ - name; - - constructor(name) { - this.name = name; - } - - static jsonSchema = { - type: "object", - required: ["name"], - properties: { - name: { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.name); - } -} - -/* @memberof module:lib */ -class UserUpdateRolesRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.user.update_roles"; - - /** @type {string} */ - name; - /** @type {Array} */ - roles; - - constructor(name, roles) { - this.name = name; - this.roles = roles; - } - - static jsonSchema = { - type: "object", - required: ["name", "roles"], - properties: { - name: { type: "string" }, - roles: { - type: "array", - items: { type: "integer" }, - }, - }, - }; - - static fromJSON(json) { - return new this(json.name, json.roles); - } -} - -/* @memberof module:lib */ -class UserSetAdminRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.user.set_admin"; - - /** @type {string} */ - name; - /** @type {boolean} */ - create; - /** @type {boolean} */ - admin; - - constructor(name, create, admin) { - this.name = name; - this.create = create; - this.admin = admin; - } - - static jsonSchema = { - type: "object", - required: ["name", "create", "admin"], - properties: { - name: { type: "string" }, - create: { type: "boolean" }, - admin: { type: "boolean" }, - }, - }; - - static fromJSON(json) { - return new this(json.name, json.create, json.admin); - } -} - -/* @memberof module:lib */ -class UserSetWhitelistedRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.user.set_whitelisted"; - - /** @type {string} */ - name; - /** @type {boolean} */ - create; - /** @type {boolean} */ - whitelisted; - - constructor(name, create, whitelisted) { - this.name = name; - this.create = create; - this.whitelisted = whitelisted; - } - - static jsonSchema = { - type: "object", - required: ["name", "create", "whitelisted"], - properties: { - name: { type: "string" }, - create: { type: "boolean" }, - whitelisted: { type: "boolean" }, - }, - }; - - static fromJSON(json) { - return new this(json.name, json.create, json.whitelisted); - } -} - -/* @memberof module:lib */ -class UserSetBannedRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.user.set_banned"; - - /** @type {string} */ - name; - /** @type {boolean} */ - create; - /** @type {boolean} */ - banned; - /** @type {string} */ - reason; - - constructor(name, create, banned, reason) { - this.name = name; - this.create = create; - this.banned = banned; - this.reason = reason; - } - - static jsonSchema = { - type: "object", - required: ["name", "create", "banned", "reason"], - properties: { - name: { type: "string" }, - create: { type: "boolean" }, - banned: { type: "boolean" }, - reason: { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.name, json.create, json.banned, json.reason); - } -} - -/* @memberof module:lib */ -class UserDeleteRequest { - static type = "request"; - static src = "control"; - static dst = "controller"; - static permission = "core.user.delete"; - - /** @type {string} */ - name; - - constructor(name) { - this.name = name; - } - - static jsonSchema = { - type: "object", - required: ["name"], - properties: { - name: { type: "string" }, - }, - }; - - static fromJSON(json) { - return new this(json.name); - } -} - - -/* @memberof module:lib */ -class AccountUpdateEvent { - static type = "event"; - static src = "controller"; - static dst = "control"; - - /** @type {Array|undefined} */ - roles; - - constructor(roles) { - if (roles) { this.roles = roles; } - } - - static jsonSchema = { - type: "object", - properties: { - roles: { - type: "array", - items: { - type: "object", - required: ["name", "id", "permissions"], - properties: { - name: { type: "string" }, - id: { type: "integer" }, - permissions: { - type: "array", - items: { type: "string" }, - }, - }, - }, - }, - }, - }; - - static fromJSON(json) { - return new this(json.roles); - } -} - -/* @memberof module:lib */ -class UserUpdateEvent { - static type = "event"; - static src = "controller"; - static dst = "control"; - - /** @type {module:lib.RawUser} */ - user; - - constructor(user) { - this.user = user; - } - - static jsonSchema = { - type: "object", - required: ["user"], - properties: { - user: RawUser.jsonSchema, - }, - }; - - static fromJSON(json) { - return new this(RawUser.fromJSON(json.user)); - } -} - -module.exports = { - RawPermission, - PermissionListRequest, - RawRole, - RoleListRequest, - RoleCreateRequest, - RoleUpdateRequest, - RoleGrantDefaultPermissionsRequest, - RoleDeleteRequest, - RawUser, - UserGetRequest, - UserListRequest, - UserSetSubscriptionsRequest, - UserCreateRequest, - UserRevokeTokenRequest, - UserUpdateRolesRequest, - UserSetAdminRequest, - UserSetWhitelistedRequest, - UserSetBannedRequest, - UserDeleteRequest, - AccountUpdateEvent, - UserUpdateEvent, -}; diff --git a/packages/lib/src/data/messages_user.ts b/packages/lib/src/data/messages_user.ts new file mode 100644 index 00000000..a9b72769 --- /dev/null +++ b/packages/lib/src/data/messages_user.ts @@ -0,0 +1,472 @@ +import { Type, Static } from "@sinclair/typebox"; +import PlayerStats from "../PlayerStats"; +import { JsonNumber, jsonArray } from "./composites"; + +export class RawPermission { // TODO refactor into lib/user.Permission + constructor( + public name: string, + public title: string, + public description: string, + ) { } + + static jsonSchema = Type.Object({ + name: Type.String(), + title: Type.String(), + description: Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.name, json.title, json.description); + } +} + +export class PermissionListRequest { + declare ["constructor"]: typeof PermissionListRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.permission.list"; + static Response = jsonArray(RawPermission); +} + +export class RawRole { // TODO refactor into lib/user.Role + constructor( + public id: number, + public name: string, + public description: string, + public permissions: string[], + ) { } + + static jsonSchema = Type.Object({ + id: Type.Integer(), + name: Type.String(), + description: Type.String(), + permissions: Type.Array(Type.String()), + }); + + static fromJSON(json: Static) { + return new this(json.id, json.name, json.description, json.permissions); + } +} + +export class RoleListRequest { + declare ["constructor"]: typeof RoleListRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.role.list"; + static Response = jsonArray(RawRole); +} + +export class RoleCreateRequest { + declare ["constructor"]: typeof RoleCreateRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.role.create"; + + constructor( + public name: string, + public description: string, + public permissions: string[], + ) { } + + static jsonSchema = Type.Object({ + name: Type.String(), + description: Type.String(), + permissions: Type.Array(Type.String()), + }); + + static fromJSON(json: Static) { + return new this(json.name, json.description, json.permissions); + } + + static Response = JsonNumber; +} + +export class RoleUpdateRequest { + declare ["constructor"]: typeof RoleUpdateRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.role.update"; + + constructor( + public id: number, + public name: string, + public description: string, + public permissions: string[], + ) { } + + static jsonSchema = Type.Object({ + id: Type.Integer(), + name: Type.String(), + description: Type.String(), + permissions: Type.Array(Type.String()), + }); + + static fromJSON(json: Static) { + return new this(json.id, json.name, json.description, json.permissions); + } +} + +export class RoleGrantDefaultPermissionsRequest { + declare ["constructor"]: typeof RoleGrantDefaultPermissionsRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.role.update"; + + constructor( + public id: number, + ) { } + + static jsonSchema = Type.Object({ + id: Type.Integer(), + }); + + static fromJSON(json: Static) { + return new this(json.id); + } +} + +export class RoleDeleteRequest { + declare ["constructor"]: typeof RoleDeleteRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.role.delete"; + + constructor( + public id: number, + ) { } + + static jsonSchema = Type.Object({ + id: Type.Integer(), + }); + + static fromJSON(json: Static) { + return new this(json.id); + } +} + +export class RawUser { // TODO refactor into lib/user.User + constructor( + public name: string, + public roles: number[], + public instances: number[], + public isAdmin?: boolean, + public isBanned?: boolean, + public isWhitelisted?: boolean, + public banReason?: string, + public isDeleted?: boolean, + public playerStats?: PlayerStats, + public instanceStats?: Map, + ) { } + + static jsonSchema = Type.Object({ + name: Type.String(), + roles: Type.Array(Type.Integer()), + instances: Type.Array(Type.Integer()), + isAdmin: Type.Optional(Type.Boolean()), + isBanned: Type.Optional(Type.Boolean()), + isWhitelisted: Type.Optional(Type.Boolean()), + banReason: Type.Optional(Type.String()), + isDeleted: Type.Optional(Type.Boolean()), + playerStats: Type.Optional(Type.Unsafe(PlayerStats.jsonSchema)), + instanceStats: Type.Optional( + Type.Array( + Type.Tuple([Type.Integer(), Type.Unsafe(PlayerStats.jsonSchema)]), + ) + ), + }); + + static fromJSON(json: Static) { + let playerStats: PlayerStats; + if (json.playerStats) { + playerStats = PlayerStats.fromJSON(json.playerStats); + } + let instanceStats: Map; + if (json.instanceStats) { + instanceStats = new Map( + json.instanceStats.map(([id, stats]) => [id, PlayerStats.fromJSON(stats)]) + ); + } + return new this( + json.name, json.roles, json.instances, json.isAdmin, json.isBanned, json.isWhitelisted, json.banReason, + json.isDeleted, playerStats, instanceStats + ); + } + + toJSON() { + const json: Static = { + name: this.name, + roles: this.roles, + instances: this.instances, + isAdmin: this.isAdmin, + isBanned: this.isBanned, + isWhitelisted: this.isWhitelisted, + banReason: this.banReason, + isDeleted: this.isDeleted, + playerStats: this.playerStats, + }; + if (this.instanceStats) { + json.instanceStats = [...this.instanceStats]; + } + return json; + } +} + +export class UserGetRequest { + declare ["constructor"]: typeof UserGetRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.user.get"; + + constructor( + public name: string, + ) { } + + static jsonSchema = Type.Object({ + "name": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.name); + } + + static Response = RawUser; +} + +export class UserListRequest { + declare ["constructor"]: typeof UserListRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.user.list"; + static Response = jsonArray(RawUser); +} + +export class UserSetSubscriptionsRequest { + declare ["constructor"]: typeof UserSetSubscriptionsRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.user.subscribe"; + + constructor( + public all: boolean, + public names: string[], + ) { } + + static jsonSchema = Type.Object({ + "all": Type.Boolean(), + "names": Type.Array(Type.String()), + }); + + static fromJSON(json: Static) { + return new this(json.all, json.names); + } +} + +export class UserCreateRequest { + declare ["constructor"]: typeof UserCreateRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.user.create"; + + constructor( + public name: string, + ) { } + + static jsonSchema = Type.Object({ + "name": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.name); + } +} + +export class UserRevokeTokenRequest { + declare ["constructor"]: typeof UserRevokeTokenRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.user.revoke_token"; + + constructor( + public name: string, + ) { } + + static jsonSchema = Type.Object({ + "name": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.name); + } +} + +export class UserUpdateRolesRequest { + declare ["constructor"]: typeof UserUpdateRolesRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.user.update_roles"; + + constructor( + public name: string, + public roles: number[], + ) { } + + static jsonSchema = Type.Object({ + "name": Type.String(), + "roles": Type.Array(Type.Integer()), + }); + + static fromJSON(json: Static) { + return new this(json.name, json.roles); + } +} + +export class UserSetAdminRequest { + declare ["constructor"]: typeof UserSetAdminRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.user.set_admin"; + + constructor( + public name: string, + public create: boolean, + public admin: boolean, + ) { } + + static jsonSchema = Type.Object({ + "name": Type.String(), + "create": Type.Boolean(), + "admin": Type.Boolean(), + }); + + static fromJSON(json: Static) { + return new this(json.name, json.create, json.admin); + } +} + +export class UserSetWhitelistedRequest { + declare ["constructor"]: typeof UserSetWhitelistedRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.user.set_whitelisted"; + + constructor( + public name: string, + public create: boolean, + public whitelisted: boolean, + ) { } + + static jsonSchema = Type.Object({ + "name": Type.String(), + "create": Type.Boolean(), + "whitelisted": Type.Boolean(), + }); + + static fromJSON(json: Static) { + return new this(json.name, json.create, json.whitelisted); + } +} + +export class UserSetBannedRequest { + declare ["constructor"]: typeof UserSetBannedRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.user.set_banned"; + + constructor( + public name: string, + public create: boolean, + public banned: boolean, + public reason: string, + ) { } + + static jsonSchema = Type.Object({ + "name": Type.String(), + "create": Type.Boolean(), + "banned": Type.Boolean(), + "reason": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.name, json.create, json.banned, json.reason); + } +} + +export class UserDeleteRequest { + declare ["constructor"]: typeof UserDeleteRequest; + static type = "request"; + static src = "control"; + static dst = "controller"; + static permission = "core.user.delete"; + + constructor( + public name: string, + ) { } + + static jsonSchema = Type.Object({ + "name": Type.String(), + }); + + static fromJSON(json: Static) { + return new this(json.name); + } +} + + +export class AccountUpdateEvent { + declare ["constructor"]: typeof AccountUpdateEvent; + static type = "event"; + static src = "controller"; + static dst = "control"; + + constructor( + public roles?: { name: string, id: number, permissions: string[] }[], + ) { } + + static jsonSchema = Type.Object({ + roles: Type.Array( + Type.Object({ + name: Type.String(), + id: Type.Integer(), + permissions: Type.Array(Type.String()), + }), + ), + }); + + static fromJSON(json: Static) { + return new this(json.roles); + } +} + +export class UserUpdateEvent { + declare ["constructor"]: typeof UserUpdateEvent; + static type = "event"; + static src = "controller"; + static dst = "control"; + + constructor( + public user: RawUser, + ) { } + + static jsonSchema = Type.Object({ + "user": RawUser.jsonSchema, + }); + + static fromJSON(json: Static) { + return new this(RawUser.fromJSON(json.user)); + } +} diff --git a/packages/lib/src/link/link.ts b/packages/lib/src/link/link.ts index f62411a6..20d50713 100644 --- a/packages/lib/src/link/link.ts +++ b/packages/lib/src/link/link.ts @@ -176,10 +176,10 @@ export class Link { ); } else if (message.type === "response") { - this._processResponse(message); + this._processResponse(message as libData.MessageResponse); } else if (message.type === "responseError") { - this._processResponseError(message); + this._processResponseError(message as libData.MessageResponseError); } else if (message.type === "event") { this._processEvent( @@ -362,9 +362,13 @@ export class Link { } else if (!(err instanceof libErrors.RequestError)) { logger.error(`Unexpected error responding to ${message.name}:\n${err.stack}`); } - this.connector.sendResponseError( - new libData.ResponseError(err.message, err.code, err.stack), message.src, spoofedSrc - ); + try { + this.connector.sendResponseError( + new libData.ResponseError(err.message, err.code, err.stack), message.src, spoofedSrc + ); + } catch (err) { + logger.error(`Unexpected error sending error response for ${message.name}:\n${err.stack}`); + } } ); } diff --git a/packages/web_ui/src/components/CreateSaveModal.jsx b/packages/web_ui/src/components/CreateSaveModal.jsx index 5d7395f9..100fd4fe 100644 --- a/packages/web_ui/src/components/CreateSaveModal.jsx +++ b/packages/web_ui/src/components/CreateSaveModal.jsx @@ -16,9 +16,9 @@ export default function CreateSaveModal(props) { function createSave() { let values = form.getFieldsValue(); let name = values.saveName || "world.zip"; - let seed = values.seed === undefined ? null : Number.parseInt(values.seed, 10); - let mapGenSettings = null; - let mapSettings = null; + let seed = values.seed === undefined ? undefined : Number.parseInt(values.seed, 10); + let mapGenSettings; + let mapSettings; if (values.exchangeString && values.exchangeString.trim()) { let result; try { diff --git a/packages/web_ui/src/components/LoadScenarioModal.jsx b/packages/web_ui/src/components/LoadScenarioModal.jsx index 0da4a12b..0fda52ef 100644 --- a/packages/web_ui/src/components/LoadScenarioModal.jsx +++ b/packages/web_ui/src/components/LoadScenarioModal.jsx @@ -16,9 +16,9 @@ export default function LoadScenarioModal(props) { function loadScenario() { let values = form.getFieldsValue(); let scenario = values.scenario || "base/freeplay"; - let seed = values.seed === undefined ? null : Number.parseInt(values.seed, 10); - let mapGenSettings = null; - let mapSettings = null; + let seed = values.seed === undefined ? undefined : Number.parseInt(values.seed, 10); + let mapGenSettings; + let mapSettings; if (values.exchangeString && values.exchangeString.trim()) { let result; try { diff --git a/packages/web_ui/src/components/LogConsole.jsx b/packages/web_ui/src/components/LogConsole.jsx index 53c31ef0..820d8586 100644 --- a/packages/web_ui/src/components/LogConsole.jsx +++ b/packages/web_ui/src/components/LogConsole.jsx @@ -75,7 +75,7 @@ export default function LogConsole(props) { logFilter.controller, logFilter.hostIds, logFilter.instanceIds, - null, + undefined, 400, "desc", )).then(result => { diff --git a/packages/web_ui/src/util/websocket.jsx b/packages/web_ui/src/util/websocket.jsx index 3f06a68c..4bac6df1 100644 --- a/packages/web_ui/src/util/websocket.jsx +++ b/packages/web_ui/src/util/websocket.jsx @@ -565,7 +565,7 @@ export class Control extends lib.Link { } } - await this.send(new lib.LogSetSubscriptionsRequest(all, controller, hostIds, instanceIds, null)); + await this.send(new lib.LogSetSubscriptionsRequest(all, controller, hostIds, instanceIds, undefined)); } async handleDebugWsMessageEvent(message) { diff --git a/test/integration/clusterio.js b/test/integration/clusterio.js index 8e01758d..56620cd2 100644 --- a/test/integration/clusterio.js +++ b/test/integration/clusterio.js @@ -92,12 +92,14 @@ describe("Integration of Clusterio", function() { describe("queryLogRequestHandler", function() { it("should honnor the limit", async function() { - let result = await getControl().send(new lib.LogQueryRequest(true, false, [], [], null, 10, "asc")); + let result = await getControl().send( + new lib.LogQueryRequest(true, false, [], [], undefined, 10, "asc") + ); assert.equal(result.log.length, 10); }); it("should return entries by order", async function() { - let first = await getControl().send(new lib.LogQueryRequest(true, false, [], [], null, 1, "asc")); - let last = await getControl().send(new lib.LogQueryRequest(true, false, [], [], null, 1, "desc")); + let first = await getControl().send(new lib.LogQueryRequest(true, false, [], [], undefined, 1, "asc")); + let last = await getControl().send(new lib.LogQueryRequest(true, false, [], [], undefined, 1, "desc")); assert(first.log[0].timestamp < last.log[0].timestamp, "first log entry happened after last"); }); }); @@ -346,7 +348,7 @@ describe("Integration of Clusterio", function() { slowTest(this); await execCtl("instance send-rcon test technobabble"); let { log } = await getControl().send( - new lib.LogQueryRequest(false, false, [], [44], null, 10, "desc") + new lib.LogQueryRequest(false, false, [], [44], undefined, 10, "desc") ); assert(log.some(info => /technobabble/.test(info.message)), "Command was not sent"); }); diff --git a/test/lib/command.js b/test/lib/command.js index a78adf02..7fd94d91 100644 --- a/test/lib/command.js +++ b/test/lib/command.js @@ -19,7 +19,7 @@ describe("lib/command", function() { lib.HostListRequest, () => [new lib.HostDetails("test", "0.1", "Test Host", 11, false)] ); testController.handle( - lib.InstanceDetailsListRequest, () => [new lib.InstanceDetails("Test Instance", 57, 4, null, "stopped")] + lib.InstanceDetailsListRequest, () => [new lib.InstanceDetails("Test Instance", 57, 4, undefined, "stopped")] ); testController.handle(lib.RoleListRequest, () => [testRole]);