mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 15:14:52 +09:00
Fix code style errors
Remove extra whitespace, use more consistent spacing, add missing semicolons, using dangling commas, break up long lines, consistent multiline operator placement, add missing JSDoc strings and so on.
This commit is contained in:
1 parent
97e05c0610
commit
7314dc99c8
71 files changed
+403
-290
No files matched your search
@@ -83,18 +83,16 @@ void new lib.Gauge(
|
||||
"clusterio_controller_websocket_active_connections",
|
||||
"How many WebSocket connections are currently open to the controller",
|
||||
{ callback: function(gauge:lib.Gauge) {
|
||||
gauge.set(controller.wsServer.activeConnectors.size);
|
||||
}
|
||||
}
|
||||
gauge.set(controller.wsServer.activeConnectors.size);
|
||||
}}
|
||||
);
|
||||
|
||||
void new lib.Gauge(
|
||||
"clusterio_controller_active_hosts",
|
||||
"How many hosts are currently connected to the controller",
|
||||
{ callback: function(gauge:lib.Gauge) {
|
||||
gauge.set(controller.wsServer.hostConnections.size);
|
||||
}
|
||||
}
|
||||
gauge.set(controller.wsServer.hostConnections.size);
|
||||
}}
|
||||
);
|
||||
|
||||
void new lib.Gauge(
|
||||
@@ -294,7 +292,7 @@ async function initialize(): Promise<InitializeParameters> {
|
||||
logger.info(`Loading available plugins from ${args.pluginList}`);
|
||||
let pluginList = new Map();
|
||||
try {
|
||||
pluginList = new Map(JSON.parse(await fs.readFile(args.pluginList, { encoding: 'utf8' })));
|
||||
pluginList = new Map(JSON.parse(await fs.readFile(args.pluginList, { encoding: "utf8" })));
|
||||
} catch (err: any) {
|
||||
if (err.code !== "ENOENT") {
|
||||
throw err;
|
||||
@@ -324,7 +322,7 @@ async function initialize(): Promise<InitializeParameters> {
|
||||
logger.info(`Loading config from ${controllerConfigPath}`);
|
||||
const controllerConfig = new lib.ControllerConfig("controller");
|
||||
try {
|
||||
let fileData = await fs.readFile(controllerConfigPath, { encoding: 'utf8' })
|
||||
let fileData = await fs.readFile(controllerConfigPath, { encoding: "utf8" });
|
||||
await controllerConfig.load(JSON.parse(fileData));
|
||||
|
||||
} catch (err: any) {
|
||||
@@ -363,7 +361,7 @@ async function initialize(): Promise<InitializeParameters> {
|
||||
controllerConfigPath,
|
||||
controllerConfig,
|
||||
shouldRun,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function startup() {
|
||||
@@ -377,7 +375,7 @@ async function startup() {
|
||||
clusterLogger,
|
||||
pluginInfos,
|
||||
controllerConfigPath,
|
||||
controllerConfig
|
||||
controllerConfig,
|
||||
} = await initialize();
|
||||
if (!shouldRun || !pluginInfos || !controllerConfigPath || !controllerConfig) {
|
||||
return;
|
||||
|
||||
@@ -254,7 +254,7 @@ export default class ControlConnection extends BaseConnection {
|
||||
assigned_host = undefined;
|
||||
}
|
||||
|
||||
let game_port: number|null|undefined = instance.game_port
|
||||
let game_port: number|null|undefined = instance.game_port;
|
||||
if (game_port === null) {
|
||||
game_port = undefined;
|
||||
}
|
||||
@@ -275,8 +275,8 @@ export default class ControlConnection extends BaseConnection {
|
||||
if (assigned_host === null) {
|
||||
assigned_host = undefined;
|
||||
}
|
||||
|
||||
let game_port: number|null|undefined = instance.game_port
|
||||
|
||||
let game_port: number|null|undefined = instance.game_port;
|
||||
if (game_port === null) {
|
||||
game_port = undefined;
|
||||
}
|
||||
@@ -434,7 +434,7 @@ export default class ControlConnection extends BaseConnection {
|
||||
async handleModPackCreateRequest(request: lib.ModPackCreateRequest) {
|
||||
let modPack = request.modPack;
|
||||
if (modPack.id === undefined) {
|
||||
throw new lib.RequestError(`Mod pack need an ID to be created`);
|
||||
throw new lib.RequestError("Mod pack need an ID to be created");
|
||||
}
|
||||
if (this._controller.modPacks!.has(modPack.id)) {
|
||||
throw new lib.RequestError(`Mod pack with ID ${modPack.id} already exist`);
|
||||
@@ -589,7 +589,7 @@ export default class ControlConnection extends BaseConnection {
|
||||
}
|
||||
|
||||
async handleLogSetSubscriptionsRequest(request: lib.LogSetSubscriptionsRequest) {
|
||||
this.logSubscriptions = {
|
||||
this.logSubscriptions = {
|
||||
all: request.all || false,
|
||||
controller: request.controller || false,
|
||||
hostIds: request.hostIds || [],
|
||||
@@ -622,7 +622,7 @@ export default class ControlConnection extends BaseConnection {
|
||||
controller: request.controller,
|
||||
hostIds: request.hostIds,
|
||||
instanceIds: request.instanceIds,
|
||||
}
|
||||
};
|
||||
|
||||
let observeDuration = queryLogTime.startTimer();
|
||||
let { all, controller, hostIds, instanceIds } = request;
|
||||
|
||||
@@ -77,7 +77,7 @@ export default class Controller {
|
||||
|
||||
/** Event subscription controller */
|
||||
subscriptions: lib.SubscriptionController;
|
||||
|
||||
|
||||
// Possible states are new, starting, running, stopping, stopped
|
||||
private _state: string = "new";
|
||||
private _shouldStop: boolean = false;
|
||||
@@ -87,7 +87,7 @@ export default class Controller {
|
||||
_snoopedEvents = new Map();
|
||||
|
||||
devMiddleware: any | null = null;
|
||||
|
||||
|
||||
logDirectory: string = "";
|
||||
clusterLogIndex: lib.LogIndex | null = null;
|
||||
clusterLogBuildInterval: ReturnType<typeof setInterval> | null = null;
|
||||
@@ -157,7 +157,7 @@ export default class Controller {
|
||||
async _startInternal(args: ControllerArgs) {
|
||||
this.logDirectory = args.logDirectory;
|
||||
this.clusterLogIndex = await lib.LogIndex.load(path.join(this.logDirectory, "cluster"));
|
||||
|
||||
|
||||
this.clusterLogBuildInterval = setInterval(() => {
|
||||
if (this.clusterLogIndex) {
|
||||
this.clusterLogIndex.buildIndex().catch(
|
||||
@@ -239,7 +239,7 @@ export default class Controller {
|
||||
Controller.addAppRoutes(this.app, this.pluginInfos);
|
||||
|
||||
if (!args.dev) {
|
||||
let manifestPath = path.join(__dirname, "..", "..", "web", "manifest.json")
|
||||
let manifestPath = path.join(__dirname, "..", "..", "web", "manifest.json");
|
||||
|
||||
let manifest = await Controller.loadJsonObject(manifestPath);
|
||||
if (!manifest["main.js"]) {
|
||||
@@ -365,7 +365,7 @@ export default class Controller {
|
||||
static async loadHosts(filePath: string): Promise<Map<number, HostInfo>> {
|
||||
let serialized: [number, HostInfo][];
|
||||
try {
|
||||
serialized = JSON.parse(await fs.readFile(filePath, { encoding: 'utf8' }));
|
||||
serialized = JSON.parse(await fs.readFile(filePath, { encoding: "utf8" }));
|
||||
|
||||
} catch (err: any) {
|
||||
if (err.code !== "ENOENT") {
|
||||
@@ -425,7 +425,7 @@ export default class Controller {
|
||||
static async loadModPacks(filePath: string): Promise<Map<number, lib.ModPack>> {
|
||||
let json;
|
||||
try {
|
||||
json = JSON.parse(await fs.readFile(filePath, { encoding : "utf8" }));
|
||||
json = JSON.parse(await fs.readFile(filePath, { encoding: "utf8" }));
|
||||
} catch (err: any) {
|
||||
if (err.code !== "ENOENT") {
|
||||
throw err;
|
||||
@@ -439,7 +439,7 @@ export default class Controller {
|
||||
await lib.safeOutputFile(filePath, JSON.stringify([...modPacks.values()], null, 4));
|
||||
}
|
||||
|
||||
static async loadModInfos(modsDirectory: string): Promise<Map<string,lib.ModInfo>> {
|
||||
static async loadModInfos(modsDirectory: string): Promise<Map<string, lib.ModInfo>> {
|
||||
let mods = new Map();
|
||||
for (let entry of await fs.readdir(modsDirectory, { withFileTypes: true })) {
|
||||
if (entry.isDirectory()) {
|
||||
@@ -476,7 +476,7 @@ export default class Controller {
|
||||
static async loadJsonObject(filePath: string, throwOnMissing: boolean = false): Promise<any> {
|
||||
let manifest = {};
|
||||
try {
|
||||
manifest = JSON.parse(await fs.readFile(filePath, { encoding: 'utf8' }));
|
||||
manifest = JSON.parse(await fs.readFile(filePath, { encoding: "utf8" }));
|
||||
} catch (err: any) {
|
||||
if (!throwOnMissing && err.code !== "ENOENT") {
|
||||
throw err;
|
||||
@@ -543,7 +543,9 @@ export default class Controller {
|
||||
|
||||
// Set folder to serve static content from (the website)
|
||||
const staticOptions = { immutable: true, maxAge: 1000 * 86400 * 365 };
|
||||
app.use("/static", express.static(path.join(__dirname, "..", "..", "..", "dist", "web", "static"), staticOptions));
|
||||
app.use("/static",
|
||||
express.static(path.join(__dirname, "..", "..", "..", "dist", "web", "static"), staticOptions)
|
||||
);
|
||||
app.use("/static", express.static("static", staticOptions)); // Used for data export files
|
||||
|
||||
// Add API routes
|
||||
@@ -595,9 +597,10 @@ export default class Controller {
|
||||
* Get instance by ID for a request
|
||||
*
|
||||
* @param instanceId - ID of instance to get.
|
||||
* @returns Info for the given instance if it exists
|
||||
* @throws {module:lib.RequestError} if the instance does not exist.
|
||||
*/
|
||||
getRequestInstance(instanceId:number):InstanceInfo {
|
||||
getRequestInstance(instanceId:number): InstanceInfo {
|
||||
let instance = this.instances!.get(instanceId);
|
||||
if (!instance) {
|
||||
throw new lib.RequestError(`Instance with ID ${instanceId} does not exist`);
|
||||
@@ -773,7 +776,7 @@ export default class Controller {
|
||||
assigned_host = undefined;
|
||||
}
|
||||
|
||||
let game_port: number|null|undefined = instance.game_port
|
||||
let game_port: number|null|undefined = instance.game_port;
|
||||
if (game_port === null) {
|
||||
game_port = undefined;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export default class HostConnection extends BaseConnection {
|
||||
this.plugins = new Map(Object.entries(registerData.plugins));
|
||||
this._checkPluginVersions();
|
||||
|
||||
let currentHostInfo = this._controller.hosts!.get(this._id)
|
||||
let currentHostInfo = this._controller.hosts!.get(this._id);
|
||||
|
||||
this._controller.hosts!.set(this._id, {
|
||||
agent: this._agent,
|
||||
@@ -223,7 +223,9 @@ export default class HostConnection extends BaseConnection {
|
||||
}
|
||||
|
||||
instanceConfig.set("instance.assigned_host", this._id);
|
||||
let newInstance = new InstanceInfo({ config: instanceConfig, status: instanceData.status as InstanceStatus });
|
||||
let newInstance = new InstanceInfo(
|
||||
{ config: instanceConfig, status: instanceData.status as InstanceStatus }
|
||||
);
|
||||
this._controller.instances!.set(instanceConfig.get("instance.id"), newInstance);
|
||||
this._controller.addInstanceHooks(newInstance);
|
||||
await this.send(
|
||||
|
||||
@@ -26,7 +26,8 @@ export default class HttpCloser {
|
||||
private _responses: Set<http.ServerResponse>;
|
||||
|
||||
/**
|
||||
* Server to attach this closer to.
|
||||
* Create HttpCloser attached to a server.
|
||||
* @param server - Node.js HTTP(S) server to attach to.
|
||||
*/
|
||||
constructor(server: http.Server | https.Server) {
|
||||
this._server = server;
|
||||
|
||||
@@ -14,7 +14,7 @@ import type * as lib from "@clusterio/lib";
|
||||
* - `exporting_data`: Instance is in the process of exporting game data.
|
||||
* - `deleted`: Instance has been deleted.
|
||||
*/
|
||||
export type InstanceStatus =
|
||||
export type InstanceStatus =
|
||||
"unknown" | "unassigned" | "stopped" | "starting" | "running" |
|
||||
"stopping" | "creating_save" | "exporting_data" | "deleted";
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export default class UserManager {
|
||||
|
||||
async load(filePath: string): Promise<void> {
|
||||
try {
|
||||
let content = JSON.parse(await fs.readFile(filePath, { encoding: 'utf8' }));
|
||||
let content = JSON.parse(await fs.readFile(filePath, { encoding: "utf8" }));
|
||||
for (let serializedRole of content.roles) {
|
||||
let role = new lib.Role(serializedRole);
|
||||
this.roles.set(role.id, role);
|
||||
@@ -65,6 +65,7 @@ export default class UserManager {
|
||||
/**
|
||||
* Creates a new user and add it to the user database.
|
||||
* @param name - Name of the user to create.
|
||||
* @returns The created user.
|
||||
*/
|
||||
createUser(name:string): lib.User {
|
||||
if (this.users.has(name)) {
|
||||
@@ -74,7 +75,7 @@ export default class UserManager {
|
||||
let roles = [];
|
||||
let defaultRoleId = this._config.get("controller.default_role_id");
|
||||
if (defaultRoleId !== null) {
|
||||
roles.push(defaultRoleId)
|
||||
roles.push(defaultRoleId);
|
||||
}
|
||||
|
||||
let user = new lib.User({ name, roles }, this.roles);
|
||||
|
||||
@@ -54,7 +54,9 @@ export default class WsServer {
|
||||
noServer: true,
|
||||
path: "/api/socket",
|
||||
});
|
||||
this.wss.on("connection", (socket: lib.WebSocketClusterio, req: IncomingMessage) => this.handleConnection(socket, req));
|
||||
this.wss.on("connection",
|
||||
(socket: lib.WebSocketClusterio, req: IncomingMessage) => this.handleConnection(socket, req),
|
||||
);
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
|
||||
@@ -12,4 +12,4 @@ endpointHitCounter.labels = util.deprecate(
|
||||
"incrementing endpointHitCounter is no longer needed and has no effect"
|
||||
);
|
||||
|
||||
export default endpointHitCounter
|
||||
export default endpointHitCounter;
|
||||
@@ -34,7 +34,7 @@ function mergeSamples(destinationResult: any, sourceResult: any) {
|
||||
|
||||
// Prometheus polling endpoint
|
||||
async function getMetrics(req: Request, res: Response, next: any) {
|
||||
const controller: Controller = req.app.locals.controller
|
||||
const controller: Controller = req.app.locals.controller;
|
||||
|
||||
let results: any[] = [];
|
||||
let pluginResults: any[] = await lib.invokeHook(controller.plugins, "onMetrics");
|
||||
@@ -93,7 +93,7 @@ async function getMetrics(req: Request, res: Response, next: any) {
|
||||
|
||||
// lib.exposition expect AsyncIterable<CollectorResult> as parameter.
|
||||
// Should wrap results into a Generator functions ?
|
||||
// @ts-ignore
|
||||
// @ts-ignore
|
||||
let text = await lib.exposition(results);
|
||||
res.set("Content-Type", lib.exposition.contentType);
|
||||
res.send(text);
|
||||
@@ -426,28 +426,29 @@ async function uploadSave(req: Request, res: Response) {
|
||||
|
||||
if (contentMime === "multipart/form-data") {
|
||||
await new Promise(resolve => {
|
||||
let instanceId:number | undefined;
|
||||
let parser = busboy({ headers: req.headers });
|
||||
parser.on("file", (name: string, stream: nodeStream.Readable, { filename, mimeType }: { filename: string, mimeType: string } ) => {
|
||||
let instanceId: number | undefined;
|
||||
function fileHandler(
|
||||
name: string,
|
||||
stream: nodeStream.Readable,
|
||||
{ filename, mimeType }: { filename: string, mimeType: string }
|
||||
) {
|
||||
if (instanceId === undefined) {
|
||||
requestErrors.push("instance_id must come before files uploaded");
|
||||
}
|
||||
|
||||
if (!zipMimes.includes(mimeType)) {
|
||||
requestErrors.push("invalid file Content-Type");
|
||||
}
|
||||
|
||||
if (!filename.endsWith(".zip")) {
|
||||
requestErrors.push("filename must end with .zip");
|
||||
}
|
||||
|
||||
if (errors.length || requestErrors.length) {
|
||||
stream.resume();
|
||||
return;
|
||||
}
|
||||
|
||||
tasks.push(handleFile(instanceId as number, stream, filename, mimeType));
|
||||
});
|
||||
}
|
||||
let parser = busboy({ headers: req.headers });
|
||||
parser.on("file", fileHandler);
|
||||
parser.on("field", (name, value, info) => {
|
||||
if (name === "instance_id") {
|
||||
instanceId = Number.parseInt(value, 10);
|
||||
@@ -593,23 +594,26 @@ async function uploadMod(req: Request, res: Response) {
|
||||
|
||||
if (contentMime === "multipart/form-data") {
|
||||
await new Promise(resolve => {
|
||||
let parser = busboy({ headers: req.headers });
|
||||
parser.on("file", (name:string, stream: nodeStream.Readable, { filename, mimeType }: { filename: string, mimeType: string }) => {
|
||||
function fileHandler(
|
||||
name: string,
|
||||
stream: nodeStream.Readable,
|
||||
{ filename, mimeType }: { filename: string, mimeType: string }
|
||||
) {
|
||||
if (!zipMimes.includes(mimeType)) {
|
||||
requestErrors.push("invalid file Content-Type");
|
||||
}
|
||||
|
||||
if (!filename.endsWith(".zip")) {
|
||||
requestErrors.push("filename must end with .zip");
|
||||
}
|
||||
|
||||
if (errors.length || requestErrors.length) {
|
||||
stream.resume();
|
||||
return;
|
||||
}
|
||||
|
||||
tasks.push(handleFile(stream, filename));
|
||||
});
|
||||
}
|
||||
|
||||
let parser = busboy({ headers: req.headers });
|
||||
parser.on("file", fileHandler);
|
||||
parser.on("close", resolve);
|
||||
parser.on("error", (err: any) => {
|
||||
logger.error(`Error parsing multipart request in upload-mod:\n${err.stack}`);
|
||||
|
||||
@@ -466,7 +466,7 @@ instanceConfigCommands.add(new lib.Command({
|
||||
});
|
||||
await events.once(doneEmitter, "dot_on_done");
|
||||
try {
|
||||
await fs.unlink(tmpFile)
|
||||
await fs.unlink(tmpFile);
|
||||
} catch (err) {
|
||||
print("err: temporary file", tmpFile, "could not be deleted.");
|
||||
print("This is not fatal, but they may build up over time if the issue persists.");
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
"use strict";
|
||||
import fs from "fs-extra";
|
||||
import path from "path";
|
||||
import events from "events";
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as lib from "@clusterio/lib";
|
||||
import { FactorioServer } from "./server";
|
||||
import { SaveModule, patch } from "./patch";
|
||||
import { exportData } from "./export";
|
||||
import type Host from "./Host"
|
||||
import type Host from "./Host";
|
||||
|
||||
|
||||
const instanceRconCommandDuration = new lib.Histogram(
|
||||
@@ -99,7 +99,7 @@ export default class Instance extends lib.Link {
|
||||
/**
|
||||
* ID of this instance, equivalenet to `instance.config.get("instance.id")`.
|
||||
*/
|
||||
readonly id: number
|
||||
readonly id: number;
|
||||
plugins: Map<string, lib.BaseInstancePlugin>;
|
||||
config: lib.InstanceConfig;
|
||||
logger: lib.Logger;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// Export of item icons and locale
|
||||
"use strict";
|
||||
import fs from "fs-extra";
|
||||
import path from "path";
|
||||
import Jimp from "jimp";
|
||||
|
||||
@@ -18,7 +18,7 @@ export class SaveModule {
|
||||
/** Module */
|
||||
public info: lib.ModuleInfo,
|
||||
/** Files and their content that will be patched into the save */
|
||||
public files = new Map<string, Buffer>,
|
||||
public files = new Map<string, Buffer>(),
|
||||
) { }
|
||||
|
||||
static moduleFilePath(filePath: string, moduleName: string) {
|
||||
@@ -47,7 +47,7 @@ export class SaveModule {
|
||||
if (entry.isFile()) {
|
||||
let savePath = SaveModule.moduleFilePath(relativePath, this.info.name);
|
||||
this.files.set(savePath, await fs.readFile(fsPath));
|
||||
if (relativePath == "module_exports.lua") {
|
||||
if (relativePath === "module_exports.lua") {
|
||||
this.files.set(
|
||||
`modules/${this.info.name}.lua`,
|
||||
Buffer.from(`return require("modules/${this.info.name}/module_exports")`, "utf-8")
|
||||
@@ -96,7 +96,7 @@ export class SaveModule {
|
||||
version: plugin.info.version,
|
||||
dependencies: { "clusterio": "*" },
|
||||
...JSON.parse(await fs.readFile(moduleJsonPath, "utf8")),
|
||||
}
|
||||
};
|
||||
} catch (err: any) {
|
||||
throw new Error(`Loading module/module.json in plugin ${plugin.info.name} failed: ${err.message}`);
|
||||
}
|
||||
@@ -173,7 +173,7 @@ export class PatchInfo {
|
||||
interface PatchInfoV0 {
|
||||
patch_number: number;
|
||||
scenario: ScenarioInfoV0;
|
||||
modules: { name: string, files: { path: string, load: boolean, require: boolean }[], }[];
|
||||
modules: { name: string, files: { path: string, load: boolean, require: boolean }[], }[];
|
||||
}
|
||||
|
||||
const info = json as unknown as PatchInfoV0;
|
||||
@@ -394,7 +394,7 @@ export async function patch(savePath: string, modules: SaveModule[]) {
|
||||
} else {
|
||||
for (let module of patchInfo.modules) {
|
||||
for (let file of module.files.keys()) {
|
||||
zip.remove(root.file(file)!.name)
|
||||
zip.remove(root.file(file)!.name);
|
||||
}
|
||||
}
|
||||
patchInfo.modules = [];
|
||||
|
||||
+51
-55
@@ -220,65 +220,61 @@ function parseOutput(line: string, source: "stdout" | "stderr"): lib.ParsedFacto
|
||||
level: secLogMatch[1],
|
||||
file: secLogMatch[2],
|
||||
message: secLogMatch[5],
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// The other possibility is that the content is a generic message
|
||||
} else {
|
||||
return {
|
||||
source,
|
||||
format,
|
||||
time,
|
||||
type: "generic",
|
||||
message: secContent,
|
||||
};
|
||||
}
|
||||
|
||||
// The second category of output starts with a date stamp of the format
|
||||
// "yyyy-mm-dd hh:mm:ss message"
|
||||
const dateRegex = /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d) (.*)$/;
|
||||
let dateMatch = dateRegex.exec(line);
|
||||
if (dateMatch) {
|
||||
const format = "date";
|
||||
const time = dateMatch[1];
|
||||
let dateContent = dateMatch[2];
|
||||
|
||||
// A date output has two general formats. The first is an action
|
||||
// followed by a message and has a format of "[ACTION] message"
|
||||
const dateActionRegex = /^\[(\w+)\] (.*)$/;
|
||||
let dateActionMatch = dateActionRegex.exec(dateContent);
|
||||
if (dateActionMatch) {
|
||||
return {
|
||||
source,
|
||||
format,
|
||||
time,
|
||||
type: "generic",
|
||||
message: secContent,
|
||||
}
|
||||
type: "action",
|
||||
action: dateActionMatch[1],
|
||||
message: dateActionMatch[2],
|
||||
};
|
||||
}
|
||||
|
||||
// The second category of output starts with a date stamp of the format
|
||||
// "yyyy-mm-dd hh:mm:ss message"
|
||||
} else {
|
||||
const dateRegex = /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d) (.*)$/;
|
||||
let dateMatch = dateRegex.exec(line);
|
||||
if (dateMatch) {
|
||||
const format = "date";
|
||||
const time = dateMatch[1];
|
||||
let dateContent = dateMatch[2];
|
||||
|
||||
// A date output has two general formats. The first is an action
|
||||
// followed by a message and has a format of "[ACTION] message"
|
||||
const dateActionRegex = /^\[(\w+)\] (.*)$/;
|
||||
let dateActionMatch = dateActionRegex.exec(dateContent);
|
||||
if (dateActionMatch) {
|
||||
return {
|
||||
source,
|
||||
format,
|
||||
time,
|
||||
type: "action",
|
||||
action: dateActionMatch[1],
|
||||
message: dateActionMatch[2],
|
||||
}
|
||||
|
||||
// The other format is a generic message
|
||||
} else {
|
||||
return {
|
||||
source,
|
||||
format,
|
||||
time,
|
||||
type: "generic",
|
||||
message: dateContent,
|
||||
}
|
||||
}
|
||||
|
||||
// The last category of output is simply a generic message with no
|
||||
// formating.
|
||||
} else {
|
||||
return {
|
||||
source,
|
||||
format: "none",
|
||||
type: "generic",
|
||||
message: line,
|
||||
}
|
||||
}
|
||||
// The other format is a generic message
|
||||
return {
|
||||
source,
|
||||
format,
|
||||
time,
|
||||
type: "generic",
|
||||
message: dateContent,
|
||||
};
|
||||
}
|
||||
|
||||
// The last category of output is simply a generic message with no
|
||||
// formating.
|
||||
return {
|
||||
source,
|
||||
format: "none",
|
||||
type: "generic",
|
||||
message: line,
|
||||
};
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/49402091
|
||||
@@ -465,7 +461,7 @@ export class FactorioServer extends events.EventEmitter {
|
||||
/** Enable Factorio.com based multiplayer bans **/
|
||||
enableAuthserverBans: boolean;
|
||||
/** Enable verbose logging */
|
||||
verboseLogging: boolean
|
||||
verboseLogging: boolean;
|
||||
/** Timeout in ms to wait for a response to a shutdown command */
|
||||
hangTimeout = 5000;
|
||||
|
||||
@@ -852,8 +848,8 @@ export class FactorioServer extends events.EventEmitter {
|
||||
"--config", this.writePath("config.ini"),
|
||||
"--create", this.writePath("saves", name),
|
||||
...(seed !== undefined ? ["--map-gen-seed", String(seed)] : []),
|
||||
...(mapGenSettings !== undefined ?
|
||||
["--map-gen-settings", this.writePath("map-gen-settings.json")] : []
|
||||
...(mapGenSettings !== undefined
|
||||
? ["--map-gen-settings", this.writePath("map-gen-settings.json")] : []
|
||||
),
|
||||
...(mapSettings !== undefined ? ["--map-settings", this.writePath("map-settings.json")] : []),
|
||||
...(this.verboseLogging ? ["--verbose"] : []),
|
||||
@@ -944,8 +940,8 @@ export class FactorioServer extends events.EventEmitter {
|
||||
"--config", this.writePath("config.ini"),
|
||||
"--start-server-load-scenario", scenario,
|
||||
...(seed !== undefined ? ["--map-gen-seed", String(seed)] : []),
|
||||
...(mapGenSettings !== undefined ?
|
||||
["--map-gen-settings", this.writePath("map-gen-settings.json")] : []
|
||||
...(mapGenSettings !== undefined
|
||||
? ["--map-gen-settings", this.writePath("map-gen-settings.json")] : []
|
||||
),
|
||||
...(mapSettings !== undefined ? ["--map-settings", this.writePath("map-settings.json")] : []),
|
||||
"--port", String(this.gamePort),
|
||||
|
||||
@@ -25,7 +25,7 @@ export * from "./src/shared_commands";
|
||||
export * from "./src/stream";
|
||||
export * from "./src/users";
|
||||
export * from "./src/zip_ops";
|
||||
export * from "./src/subscriptions"
|
||||
export * from "./src/subscriptions";
|
||||
|
||||
export { default as ExponentialBackoff } from "./src/ExponentialBackoff";
|
||||
export { default as PlayerStats } from "./src/PlayerStats";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Type, Static } from "@sinclair/typebox";
|
||||
import events from "events";
|
||||
|
||||
import isDeepStrictEqual from "../is_deep_strict_equal";
|
||||
import { basicType } from"../helpers";
|
||||
import { basicType } from "../helpers";
|
||||
|
||||
|
||||
export type ConfigLocation = "controller" | "host" | "instance" | "control";
|
||||
|
||||
@@ -264,7 +264,7 @@ export class HostConfig extends classes.Config {
|
||||
get(name: "host.public_address"): string;
|
||||
get(name: "host.max_reconnect_delay"): number;
|
||||
get(name: string): unknown;
|
||||
get(name: string) {
|
||||
get(name: string) {
|
||||
return super.get(name);
|
||||
}
|
||||
}
|
||||
@@ -503,7 +503,10 @@ export class ControlConfig extends classes.Config { }
|
||||
ControlConfig.registerGroup(ControlGroup);
|
||||
|
||||
|
||||
function validateGroup(pluginInfo: PluginNodeEnvInfo|PluginWebpackEnvInfo, groupName: "ControllerConfigGroup"|"InstanceConfigGroup") {
|
||||
function validateGroup(
|
||||
pluginInfo: PluginNodeEnvInfo | PluginWebpackEnvInfo,
|
||||
groupName: "ControllerConfigGroup"|"InstanceConfigGroup",
|
||||
) {
|
||||
if (!(pluginInfo[groupName]?.prototype instanceof classes.PluginConfigGroup)) {
|
||||
throw new Error(
|
||||
`Expected ${groupName} for ${pluginInfo.name} to be a subclass of PluginConfigGroup`
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
"use strict";
|
||||
import zlib from "zlib";
|
||||
import { Type, Static } from "@sinclair/typebox";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from "@sinclair/typebox";
|
||||
|
||||
export function StringEnum<T extends string[]>(values: [...T]) {
|
||||
return Type.Unsafe<T[number]>({ type: 'string', enum: values })
|
||||
return Type.Unsafe<T[number]>({ type: "string", enum: values });
|
||||
}
|
||||
|
||||
export interface JSONDeserialisable<T> {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { ValidateFunction } from "ajv";
|
||||
import type { Message } from "./messages_core";
|
||||
var message_validate: ValidateFunction<Message>;
|
||||
const message_validate: ValidateFunction<Message>;
|
||||
export default message_validate;
|
||||
@@ -166,6 +166,7 @@ export class LogQueryRequest {
|
||||
json.all, json.controller, json.hostIds, json.instanceIds, json.maxLevel, json.limit, json.order
|
||||
);
|
||||
}
|
||||
|
||||
static Response = class Response {
|
||||
constructor(
|
||||
public log: object[],
|
||||
|
||||
@@ -23,7 +23,15 @@ export class HostDetails {
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||
return new this(json.agent, json.version, json.name, json.id, json.connected, json.publicAddress, json.tokenValidAfter);
|
||||
return new this(
|
||||
json.agent,
|
||||
json.version,
|
||||
json.name,
|
||||
json.id,
|
||||
json.connected,
|
||||
json.publicAddress,
|
||||
json.tokenValidAfter,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ export class RawUser { // TODO refactor into lib/user.User
|
||||
isDeleted: this.isDeleted,
|
||||
};
|
||||
if (this.playerStats) {
|
||||
json.playerStats = this.playerStats.toJSON()
|
||||
json.playerStats = this.playerStats.toJSON();
|
||||
}
|
||||
if (this.instanceStats) {
|
||||
json.instanceStats = [...this.instanceStats].map(([k, v]) => [k, v.toJSON()]);
|
||||
|
||||
@@ -277,8 +277,9 @@ export class ItemDatabase {
|
||||
|
||||
/**
|
||||
* Allow to iterate through the items.
|
||||
* @returns array of items.
|
||||
*/
|
||||
getEntries(): IterableIterator<[string, number]> {
|
||||
return this._items.entries()
|
||||
return this._items.entries();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
"use strict";
|
||||
import zlib from "zlib";
|
||||
|
||||
class MapReaderState {
|
||||
@@ -72,7 +71,7 @@ function readString(state: MapReaderState) {
|
||||
return data;
|
||||
}
|
||||
|
||||
function readOptional<T>(state: MapReaderState, readValue: (state: MapReaderState) => T) {
|
||||
function readOptional<T>(state: MapReaderState, readValue: (s: MapReaderState) => T) {
|
||||
let load = readUInt8(state) !== 0;
|
||||
if (!load) {
|
||||
return null;
|
||||
@@ -80,7 +79,7 @@ function readOptional<T>(state: MapReaderState, readValue: (state: MapReaderStat
|
||||
return readValue(state);
|
||||
}
|
||||
|
||||
function readArray<T>(state: MapReaderState, readItem: (state: MapReaderState) => T) {
|
||||
function readArray<T>(state: MapReaderState, readItem: (s: MapReaderState) => T) {
|
||||
let size = readUInt32so(state);
|
||||
|
||||
let array: T[] = [];
|
||||
@@ -94,8 +93,8 @@ function readArray<T>(state: MapReaderState, readItem: (state: MapReaderState) =
|
||||
|
||||
function readDict<K, V>(
|
||||
state: MapReaderState,
|
||||
readKey: (state: MapReaderState) => K,
|
||||
readValue: (state: MapReaderState) => V
|
||||
readKey: (s: MapReaderState) => K,
|
||||
readValue: (s: MapReaderState) => V
|
||||
) {
|
||||
let size = readUInt32so(state);
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export default function isDeepStrictEqual(a: any, b: any): boolean
|
||||
export default function isDeepStrictEqual(a: any, b: any): boolean;
|
||||
@@ -1,13 +1,12 @@
|
||||
// Connection adapters for Links
|
||||
"use strict";
|
||||
import { strict as assert } from "assert";
|
||||
import events from "events";
|
||||
import WebSocket from "./WebSocket";
|
||||
|
||||
import * as libData from "../data";
|
||||
import * as libErrors from "../errors";
|
||||
import { logger } from "../logging";
|
||||
import ExponentialBackoff from "../ExponentialBackoff";
|
||||
import * as libErrors from "../errors";
|
||||
import { logger } from "../logging";
|
||||
import ExponentialBackoff from "../ExponentialBackoff";
|
||||
import type { Request, Event } from "./link";
|
||||
|
||||
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
* @module lib/link
|
||||
* @author Hornwitser
|
||||
*/
|
||||
export * from "./link"
|
||||
export * from "./connectors"
|
||||
export * from "./link";
|
||||
export * from "./connectors";
|
||||
@@ -316,7 +316,7 @@ export class Link {
|
||||
if (!this.router) {
|
||||
let err = new libErrors.InvalidMessage(
|
||||
`Received message addressed to ${(message as libData.MessageRequest).dst} but this link `+
|
||||
`does not route messages`
|
||||
"does not route messages"
|
||||
);
|
||||
if (message.type === "request") {
|
||||
this.connector.sendResponseError(
|
||||
@@ -401,8 +401,8 @@ export class Link {
|
||||
this.connector.sendResponseError(
|
||||
new libData.ResponseError(err.message, err.code, err.stack), message.src, spoofedSrc
|
||||
);
|
||||
} catch (err: any) {
|
||||
logger.error(`Unexpected error sending error response for ${message.name}:\n${err.stack}`);
|
||||
} catch (subErr: any) {
|
||||
logger.error(`Unexpected error sending error response for ${message.name}:\n${subErr.stack}`);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -527,7 +527,7 @@ export class Link {
|
||||
let requestId = this._nextRequestId;
|
||||
this._nextRequestId += 1;
|
||||
this._pendingRequests.set(
|
||||
requestId, { request: entry, promise, resolve: resolve!, reject: reject!, dst, }
|
||||
requestId, { request: entry, promise, resolve: resolve!, reject: reject!, dst }
|
||||
);
|
||||
this.connector.sendRequest(request, requestId, dst);
|
||||
return promise;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
"use strict";
|
||||
import * as core from "../data/messages_core";
|
||||
import * as controller from "../data/messages_controller";
|
||||
import * as host from "../data/messages_host";
|
||||
import * as instance from "../data/messages_instance";
|
||||
import * as mod from "../data/messages_mod";
|
||||
import * as user from "../data/messages_user";
|
||||
import * as subscriptions from "../subscriptions"
|
||||
import * as subscriptions from "../subscriptions";
|
||||
import type { RequestClass, EventClass } from "./link";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
"use strict";
|
||||
import winston from "winston";
|
||||
import Transport from "winston-transport";
|
||||
import { LEVEL, MESSAGE } from "triple-beam";
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* @author Hornwitser
|
||||
* @module lib/logging_utils
|
||||
*/
|
||||
"use strict";
|
||||
import fs from "fs-extra";
|
||||
import path from "path";
|
||||
import stream from "stream";
|
||||
|
||||
@@ -120,6 +120,7 @@ export class BaseInstancePlugin {
|
||||
message: string,
|
||||
expectEmpty: boolean,
|
||||
}[] = [];
|
||||
|
||||
private _sendingRconMessages = false;
|
||||
|
||||
constructor(
|
||||
@@ -655,7 +656,6 @@ export class BaseControlPlugin {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Plugin supplied login form
|
||||
*/
|
||||
|
||||
@@ -89,7 +89,7 @@ export async function loadControllerPluginClass(
|
||||
|
||||
/**
|
||||
* Load instance plugin class of a plugin
|
||||
|
||||
*
|
||||
* @param pluginInfo -
|
||||
* Plugin info object returned from {@link
|
||||
* loadPluginInfos} to load class from.
|
||||
|
||||
@@ -1188,7 +1188,7 @@ class HistogramChild {
|
||||
protected _bucketValues: Map<string, number>;
|
||||
protected _sumValues: Map<string, number>;
|
||||
protected _countValues: Map<string, number>;
|
||||
protected _key: string
|
||||
protected _key: string;
|
||||
protected _bucketKeys: Map<number, string>;
|
||||
|
||||
constructor(collector: Histogram, key: string) {
|
||||
@@ -1438,6 +1438,7 @@ export class Histogram extends LabeledCollector<HistogramChild> {
|
||||
* Observe a given value and increment matching buckets
|
||||
*
|
||||
* Note: Only works if this is an unlabeled collector.
|
||||
* @param value - number to count into histogram buckets.
|
||||
*/
|
||||
observe(value: number) {
|
||||
this._defaultChild!.observe(value);
|
||||
|
||||
@@ -22,13 +22,13 @@ export function compile<T>(schema: JSONSchemaType<T>) {
|
||||
}
|
||||
|
||||
let doValidate: ValidateFunction<T>;
|
||||
const validate = <ValidateFunction<T>>function (data) {
|
||||
const validate = <ValidateFunction<T>> function (data) {
|
||||
if (!doValidate) {
|
||||
doValidate = ajv.compile(schema);
|
||||
}
|
||||
let result = doValidate(data);
|
||||
validate.errors = doValidate.errors;
|
||||
return result;
|
||||
}
|
||||
return validate
|
||||
};
|
||||
return validate;
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
* Implementation of commands shared between controller/host/ctl.
|
||||
* @module lib/shared_commands
|
||||
*/
|
||||
"use strict";
|
||||
import path from "path";
|
||||
|
||||
import * as libConfig from "./config";
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
"use strict";
|
||||
import assert from "assert";
|
||||
import fs from "fs-extra";
|
||||
import stream from "stream";
|
||||
|
||||
@@ -33,16 +33,15 @@ export class SubscriptionResponse {
|
||||
Type.String(),
|
||||
Type.Unknown(),
|
||||
]),
|
||||
Type.Null()
|
||||
])
|
||||
Type.Null(),
|
||||
]);
|
||||
|
||||
toJSON() {
|
||||
if (this.eventReplay) {
|
||||
const entry = Link._eventsByClass.get(this.eventReplay.constructor)!;
|
||||
return [entry.name, this.eventReplay];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static fromJSON(json: Static<typeof SubscriptionResponse.jsonSchema>): SubscriptionResponse {
|
||||
@@ -67,7 +66,7 @@ export class SubscriptionResponse {
|
||||
export class SubscriptionRequest {
|
||||
declare ["constructor"]: typeof SubscriptionRequest;
|
||||
static type = "request" as const;
|
||||
static src = ["control", "instance"] as const;
|
||||
static src = ["control", "instance"] as const;
|
||||
static dst = "controller" as const;
|
||||
static Response = SubscriptionResponse;
|
||||
static permission(user: User, message: MessageRequest) {
|
||||
@@ -100,7 +99,7 @@ export class SubscriptionRequest {
|
||||
Type.Boolean(),
|
||||
Type.Array(Type.Union([Type.String(), Type.Number()])),
|
||||
Type.Number(),
|
||||
])
|
||||
]);
|
||||
|
||||
toJSON() {
|
||||
return [this.eventName, this.allChannels, this.channels, this.lastRequestTime];
|
||||
@@ -130,6 +129,9 @@ export class SubscriptionController {
|
||||
/**
|
||||
* Allow clients to subscribe to an event by telling the subscription controller to accept them
|
||||
* Has an optional subscription update handler which is called when any client updates their subscription
|
||||
* @param Event - Event class which is sent out as updates.
|
||||
* @param subscriptionUpdate -
|
||||
* Optional handler called when a client updates its subscriptions.
|
||||
*/
|
||||
handle<T>(Event: EventClass<T>, subscriptionUpdate?: SubscriptionRequestHandler<T>) {
|
||||
const entry = Link._eventsByClass.get(Event);
|
||||
@@ -147,6 +149,7 @@ export class SubscriptionController {
|
||||
|
||||
/**
|
||||
* Broadcast an event to all subscribers of that event, will be filtered by channel if a ChannelEvent is provided
|
||||
* @param event - Event to broadcast.
|
||||
*/
|
||||
broadcast<T>(event: Event<T> | ChannelEvent<T>) {
|
||||
const entry = Link._eventsByClass.get(event.constructor);
|
||||
@@ -169,6 +172,11 @@ export class SubscriptionController {
|
||||
|
||||
/**
|
||||
* Handle incoming event subscription requests
|
||||
* @param event - incomming event.
|
||||
* @param src - Source address of incomming request.
|
||||
* @param dst - destination address of incomming request.
|
||||
* @returns Response to subscription request.
|
||||
* @internal
|
||||
*/
|
||||
async _handleRequest(event: SubscriptionRequest, src: Address, dst: Address) {
|
||||
if (!Link._eventsByName.has(event.eventName)) {
|
||||
@@ -192,7 +200,9 @@ export class SubscriptionController {
|
||||
onceClose = () => eventData.subscriptions.delete(link);
|
||||
link.connector.once("close", onceClose);
|
||||
}
|
||||
eventData.subscriptions.set(link, { all: event.allChannels, channels: event.channels, onceClose: onceClose });
|
||||
eventData.subscriptions.set(
|
||||
link, { all: event.allChannels, channels: event.channels, onceClose: onceClose },
|
||||
);
|
||||
}
|
||||
return new SubscriptionResponse(eventReplay);
|
||||
}
|
||||
@@ -204,15 +214,15 @@ export class SubscriptionController {
|
||||
* Multiple handlers can be subscribed at the same time, on the same or different channels
|
||||
*/
|
||||
export class EventSubscriber<T, V=T> {
|
||||
_callbacks = new Array<EventSubscriberCallback<V>>()
|
||||
_channelCallbacks = new Map<string | number, Array<EventSubscriberCallback<V>>>()
|
||||
_callbacks = new Array<EventSubscriberCallback<V>>();
|
||||
_channelCallbacks = new Map<string | number, Array<EventSubscriberCallback<V>>>();
|
||||
lastResponse: Event<T> | null = null;
|
||||
lastResponseTime = 0;
|
||||
control?: Link
|
||||
control?: Link;
|
||||
|
||||
constructor(
|
||||
private event: EventClass<T> | ChannelEventClass<T>,
|
||||
private prehandler?: (event: T) => V,
|
||||
private prehandler?: (e: T) => V,
|
||||
control?: Link
|
||||
) {
|
||||
const entry = Link._eventsByClass.get(this.event);
|
||||
@@ -226,6 +236,8 @@ export class EventSubscriber<T, V=T> {
|
||||
|
||||
/**
|
||||
* Handle incoming events and distribute it to the correct callbacks
|
||||
* @param response - event from subscribed resource
|
||||
* @internal
|
||||
*/
|
||||
async _handle(response: Event<T> | ChannelEvent<T>) {
|
||||
this.lastResponse = response;
|
||||
@@ -246,10 +258,14 @@ export class EventSubscriber<T, V=T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* If a control link was not connected at creation, it can be connected here (normally during onControllerConnectionEvent)
|
||||
* If a control link was not connected at creation, it can be connected
|
||||
* here (normally during onControllerConnectionEvent)
|
||||
* @param control - Control link to associate with.
|
||||
*/
|
||||
async connectControl(control: Link) {
|
||||
if (this.control === control) return;
|
||||
if (this.control === control) {
|
||||
return;
|
||||
}
|
||||
this.control = control;
|
||||
this.control.handle(this.event, this._handle.bind(this));
|
||||
await this._updateSubscription();
|
||||
@@ -257,6 +273,8 @@ export class EventSubscriber<T, V=T> {
|
||||
|
||||
/**
|
||||
* Subscribe to receive all event notifications
|
||||
* @param handler -
|
||||
* callback invoked whenever the subscribed resource changes.
|
||||
*/
|
||||
async subscribe(handler: EventSubscriberCallback<V>) {
|
||||
this._callbacks.push(handler);
|
||||
@@ -265,6 +283,10 @@ export class EventSubscriber<T, V=T> {
|
||||
|
||||
/**
|
||||
* Subscribe to receive event notifications for the given channel
|
||||
* @param channel - Channel to subscribe to.
|
||||
* @param handler -
|
||||
* callback invoked whenever the subscribed resource changes on the
|
||||
* given channel.
|
||||
*/
|
||||
async subscribeToChannel(channel: string | number, handler: EventSubscriberCallback<V>) {
|
||||
if (!this._channelCallbacks.get(channel)) {
|
||||
@@ -275,8 +297,14 @@ export class EventSubscriber<T, V=T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from receiving all event notifications, does not effect the subscriptions of other handlers
|
||||
* Will throw an error if your handler is not subscribed to all channels, does not accept anonymous functions
|
||||
* Unsubscribe from receiving all event notifications, does not effect
|
||||
* the subscriptions of other handlers.
|
||||
*
|
||||
* @param handler - A callback previously passed to {@link subscribe}
|
||||
*
|
||||
* @throws {Error}
|
||||
* If your handler is not subscribed to all channels, does not
|
||||
* accept anonymous functions
|
||||
*/
|
||||
async unsubscribe(handler: EventSubscriberCallback<V>) {
|
||||
let index = this._callbacks.lastIndexOf(handler);
|
||||
@@ -289,8 +317,15 @@ export class EventSubscriber<T, V=T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from receiving event notifications for a given channel, does not effect the subscriptions of other handlers
|
||||
* Will throw an error if your handler is not subscribed to the channel, does not accept anonymous functions
|
||||
* Unsubscribe from receiving event notifications for a given channel,
|
||||
* does not effect the subscriptions of other handlers.
|
||||
*
|
||||
* @param channel - A channel previously passed to {@link subscribeToChannel}
|
||||
* @param handler - A callback previously passed to {@link subscribeToChannel}
|
||||
*
|
||||
* @throws {Error}
|
||||
* If your handler is not subscribed to the channel or does not
|
||||
* accept anonymous functions
|
||||
*/
|
||||
async unsubscribeFromChannel(channel: string | number, handler: EventSubscriberCallback<V>) {
|
||||
const channelCallbacks = this._channelCallbacks.get(channel);
|
||||
@@ -315,7 +350,9 @@ export class EventSubscriber<T, V=T> {
|
||||
* Update the subscription with the controller based on current handler counts
|
||||
*/
|
||||
async _updateSubscription() {
|
||||
if (!this.control || !(this.control.connector as WebSocketClientConnector).connected) return;
|
||||
if (!this.control || !(this.control.connector as WebSocketClientConnector).connected) {
|
||||
return;
|
||||
}
|
||||
const entry = Link._eventsByClass.get(this.event)!;
|
||||
|
||||
const response = await this.control.send(new SubscriptionRequest(
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* @author Hornwitser
|
||||
* @module lib/users
|
||||
*/
|
||||
"use strict";
|
||||
import * as libErrors from "./errors";
|
||||
|
||||
import PlayerStats from "./PlayerStats";
|
||||
@@ -328,6 +327,7 @@ export class User {
|
||||
}
|
||||
return playerStats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of users currently online in the cluster.
|
||||
*/
|
||||
|
||||
Vendored
+2
-2
@@ -1,5 +1,5 @@
|
||||
declare module '*.png' {
|
||||
const value: import('react-native').ImageSourcePropType;
|
||||
declare module "*.png" {
|
||||
const value: import("react-native").ImageSourcePropType;
|
||||
export default value;
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ export default function BaseConfigTree(props: BaseConfigTreeProps) {
|
||||
title="A restart is required for this setting to take effect"
|
||||
><ReloadOutlined/></Tooltip>;
|
||||
|
||||
|
||||
|
||||
type ChildNode = {
|
||||
key: string;
|
||||
children: any[];
|
||||
|
||||
@@ -41,8 +41,8 @@ export default function HostViewPage() {
|
||||
}
|
||||
|
||||
let hostButtons = <Space> {
|
||||
account.hasPermission("core.host.revoke_access") &&
|
||||
<Popconfirm
|
||||
account.hasPermission("core.host.revoke_access")
|
||||
&& <Popconfirm
|
||||
title={`Revoke tokens of ${host.name}?`}
|
||||
placement="bottomRight"
|
||||
okText="Revoke Tokens"
|
||||
@@ -50,15 +50,15 @@ export default function HostViewPage() {
|
||||
onConfirm={() => {
|
||||
control.send(new lib.HostRevokeTokensRequest(host.id!))
|
||||
.then(() => notify("Host tokens revoked"))
|
||||
.catch(notifyErrorHandler(`Error revoking tokens for host id:${host.id}`))
|
||||
.catch(notifyErrorHandler(`Error revoking tokens for host id:${host.id}`));
|
||||
}}
|
||||
>
|
||||
<Button danger>
|
||||
Revoke tokens
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
}
|
||||
</Space>
|
||||
}
|
||||
</Space>;
|
||||
|
||||
return <PageLayout nav={nav}>
|
||||
<PageHeader
|
||||
@@ -75,9 +75,9 @@ export default function HostViewPage() {
|
||||
<Descriptions.Item label="Agent">{host["agent"]}</Descriptions.Item>
|
||||
<Descriptions.Item label="Version">{host["version"]}</Descriptions.Item>
|
||||
{
|
||||
host.tokenValidAfter &&
|
||||
<Descriptions.Item label="Tokens valid after:">
|
||||
{formatTimestamp(host.tokenValidAfter*1000)}
|
||||
host.tokenValidAfter
|
||||
&& <Descriptions.Item label="Tokens valid after:">
|
||||
{formatTimestamp(host.tokenValidAfter*1000)}
|
||||
</Descriptions.Item>
|
||||
}
|
||||
</Descriptions>
|
||||
|
||||
@@ -37,7 +37,7 @@ function GenerateHostTokenButton() {
|
||||
}, []);
|
||||
|
||||
async function generateToken() {
|
||||
let id = undefined;
|
||||
let id;
|
||||
let values = form.getFieldsValue();
|
||||
if (values.hostId) {
|
||||
id = Number.parseInt(values.hostId, 10);
|
||||
@@ -114,12 +114,12 @@ function GenerateHostTokenButton() {
|
||||
<div className="codeblock">
|
||||
<CopyButton
|
||||
message={"Copied host setup commands to clipboard"}
|
||||
text={`
|
||||
mkdir clusterio
|
||||
cd clusterio
|
||||
`+
|
||||
// eslint-disable-next-line max-len
|
||||
`npm init "@clusterio" -- --controller-token ${token} --mode "host" --download-headless --controller-url ${document.location.origin}/ --host-name "Host ${hostId || "?"}" --public-address localhost ${pluginString.length ? "--plugins" : ""} ${pluginString}`
|
||||
text={`\
|
||||
mkdir clusterio
|
||||
cd clusterio
|
||||
npm init "@clusterio" -- --controller-token ${token} --mode "host" --download-headless \
|
||||
--controller-url ${document.location.origin}/ --host-name "Host ${hostId || "?"}" \
|
||||
--public-address localhost ${pluginString.length ? "--plugins" : ""} ${pluginString}`
|
||||
}/>
|
||||
<p>> mkdir clusterio</p>
|
||||
<p>> cd clusterio</p>
|
||||
@@ -166,7 +166,7 @@ function CopyButton({ text, message }: { text:string, message:string }) {
|
||||
return result.state === "granted";
|
||||
|
||||
} catch (err: any) {
|
||||
//If it fail because "clipboard-write" is not supported.
|
||||
// If it fail because "clipboard-write" is not supported.
|
||||
return err.name === "TypeError";
|
||||
}
|
||||
}
|
||||
@@ -229,7 +229,7 @@ export default function HostsPage() {
|
||||
{host.connected ? "Connected" : "Disconnected"}
|
||||
</Tag>,
|
||||
sorter: (a, b) => Number(a.connected) - Number(b.connected),
|
||||
}
|
||||
},
|
||||
]}
|
||||
dataSource={hostList}
|
||||
rowKey={host => host.id}
|
||||
|
||||
@@ -99,7 +99,7 @@ export default function InstanceList(props: InstanceListProps) {
|
||||
responsive: ["sm"],
|
||||
align: "right",
|
||||
width: 100,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (props.hideAssignedHost) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Alert, Button, Descriptions, Dropdown, Menu, MenuProps, Modal, Space, Spin, Typography } from "antd";
|
||||
import type { ItemType } from 'antd/es/menu/hooks/useItems';
|
||||
import type { ItemType } from "antd/es/menu/hooks/useItems";
|
||||
import DeleteOutlined from "@ant-design/icons/DeleteOutlined";
|
||||
import DownOutlined from "@ant-design/icons/DownOutlined";
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function ModDetails(props: ModDetailsProps) {
|
||||
{`https://mods.factorio.com/mod/${mod.name}`}
|
||||
</Typography.Link>
|
||||
</Descriptions.Item>
|
||||
|
||||
|
||||
{ (mod instanceof lib.ModInfo) && <>
|
||||
{mod.contact && <Descriptions.Item label="Contact" span={2}>{mod.contact}</Descriptions.Item>}
|
||||
{mod.homepage && <Descriptions.Item label="Homepage" span={2}>{mod.homepage}</Descriptions.Item>}
|
||||
|
||||
@@ -70,7 +70,7 @@ function SearchModsTable(props: SearchModsTableProps) {
|
||||
const [modResultPage, setModResultPage] = useState<number>(1);
|
||||
const [modResultPageSize, setModResultPageSize] = useState<number>(10);
|
||||
const [modResultCount, setModResultCount] = useState<number>(2);
|
||||
const [modResultSelectedVersion, setModResultSelectedVersion] = useState<Map<string,number>>(new Map());
|
||||
const [modResultSelectedVersion, setModResultSelectedVersion] = useState<Map<string, number>>(new Map());
|
||||
|
||||
const factorioVersion = props.modPack.factorioVersion.split(".").slice(0, 2).join(".");
|
||||
|
||||
@@ -216,7 +216,9 @@ function SearchModsTable(props: SearchModsTableProps) {
|
||||
title: "Action",
|
||||
key: "action",
|
||||
responsive: ["lg"],
|
||||
render: (result:ModResult) => actions(result.versions[modResultSelectedVersion.get(result.name) || 0]),
|
||||
render: (result:ModResult) => (
|
||||
actions(result.versions[modResultSelectedVersion.get(result.name) || 0])
|
||||
),
|
||||
},
|
||||
]}
|
||||
expandable={{
|
||||
@@ -294,11 +296,13 @@ function ModsTable(props: ModsTableProps) {
|
||||
>remove</Typography.Link>}
|
||||
{(deletedMods.has(mod.name) || changedMods.has(mod.name)) && <Typography.Link
|
||||
onClick={() => {
|
||||
props.onRevert && props.onRevert({
|
||||
type: deletedMods.has(mod.name) ? "mods.delete" : "mods.set",
|
||||
name: mod.name,
|
||||
value: mod,
|
||||
});
|
||||
if (props.onRevert) {
|
||||
props.onRevert({
|
||||
type: deletedMods.has(mod.name) ? "mods.delete" : "mods.set",
|
||||
name: mod.name,
|
||||
value: mod,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>revert</Typography.Link>}
|
||||
</Space>;
|
||||
@@ -337,7 +341,9 @@ function ModsTable(props: ModsTableProps) {
|
||||
});
|
||||
}
|
||||
}
|
||||
const added = selectedRowKeys.filter(key => !props.modPack.mods.get(key as string)?.enabled) as string[];
|
||||
const added = selectedRowKeys.filter(
|
||||
key => !props.modPack.mods.get(key as string)?.enabled
|
||||
) as string[];
|
||||
const removed = [...props.modPack.mods.values()].filter(
|
||||
mod => mod.enabled && selectedRowKeys.indexOf(mod.name) === -1
|
||||
).map(mod => mod.name);
|
||||
|
||||
@@ -14,9 +14,9 @@ import { notifyErrorHandler } from "../util/notify";
|
||||
|
||||
|
||||
export type RawRoleState = Partial<lib.RawRole> & {
|
||||
loading?: Boolean;
|
||||
missing?: Boolean;
|
||||
present?: Boolean;
|
||||
loading?: boolean;
|
||||
missing?: boolean;
|
||||
present?: boolean;
|
||||
};
|
||||
function useRole(id: number): [RawRoleState, () => void] {
|
||||
let control = useContext(ControlContext);
|
||||
|
||||
@@ -17,7 +17,6 @@ import { useSaves } from "../model/saves";
|
||||
import { notifyErrorHandler } from "../util/notify";
|
||||
|
||||
|
||||
|
||||
type ModalProps = {
|
||||
disabled: boolean;
|
||||
save: lib.SaveDetails;
|
||||
@@ -150,7 +149,9 @@ function TransferModal(props: ModalProps) {
|
||||
<Select
|
||||
autoFocus
|
||||
showSearch
|
||||
filterOption={(input, option) => (option?.title.toLowerCase().indexOf(input.toLowerCase())??-1) >= 0}
|
||||
filterOption={(input, option) => (
|
||||
(option?.title.toLowerCase().indexOf(input.toLowerCase())??-1) >= 0
|
||||
)}
|
||||
>
|
||||
{instanceList.filter(
|
||||
instance => instance["id"] !== props.instanceId
|
||||
|
||||
@@ -17,7 +17,7 @@ export default function StartStopInstanceButton(props: StartStopInstanceButtonPr
|
||||
let control = useContext(ControlContext);
|
||||
let [switching, setSwitching] = useState<boolean>(false);
|
||||
|
||||
const onClick: React.MouseEventHandler<HTMLElement> = function(event) {
|
||||
function onClick(event: React.MouseEvent<HTMLElement>) {
|
||||
event.stopPropagation();
|
||||
setSwitching(true);
|
||||
let action;
|
||||
@@ -48,7 +48,7 @@ export default function StartStopInstanceButton(props: StartStopInstanceButtonPr
|
||||
props.onFinish();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return <Button
|
||||
{...(props.buttonProps || {})}
|
||||
|
||||
@@ -96,7 +96,9 @@ export default function UsersPage() {
|
||||
{
|
||||
title: "Roles",
|
||||
key: "roles",
|
||||
render: (user: lib.RawUser) => user.roles.map(id => <Tag key={id}>{(roles.get(id) || { name: id }).name}</Tag>),
|
||||
render: (user: lib.RawUser) => (
|
||||
user.roles.map(id => <Tag key={id}>{(roles.get(id) || { name: id }).name}</Tag>)
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Online time",
|
||||
|
||||
@@ -16,8 +16,9 @@ export function useHost(id: number): [ HostState, () => void ] {
|
||||
|
||||
function updateHost() {
|
||||
// XXX optimize by requesting only the host in question
|
||||
control.send(new lib.HostListRequest())
|
||||
.then((hosts: lib.HostDetails[]) => {
|
||||
control.send(
|
||||
new lib.HostListRequest()
|
||||
).then((hosts: lib.HostDetails[]) => {
|
||||
let match = hosts.find(i => i.id === id);
|
||||
if (!match) {
|
||||
setHost({ missing: true });
|
||||
@@ -35,7 +36,7 @@ export function useHost(id: number): [ HostState, () => void ] {
|
||||
updateHost();
|
||||
|
||||
function updateHandler(newHost: lib.HostDetails) {
|
||||
setHost({ ...newHost, loading:false, missing:false, present:true });
|
||||
setHost({ ...newHost, loading: false, missing: false, present: true });
|
||||
}
|
||||
|
||||
control.hostUpdate.subscribeToChannel(id, updateHandler);
|
||||
@@ -52,8 +53,9 @@ export function useHostList() {
|
||||
let [hostList, setHostList] = useState<lib.HostDetails[]>([]);
|
||||
|
||||
function updateHostList() {
|
||||
control.send(new lib.HostListRequest())
|
||||
.then((hosts: lib.HostDetails[]) => {
|
||||
control.send(
|
||||
new lib.HostListRequest()
|
||||
).then((hosts: lib.HostDetails[]) => {
|
||||
setHostList(hosts);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,14 +29,14 @@ export function useItemMetadata() {
|
||||
let style = document.createElement("style");
|
||||
document.head.appendChild(style);
|
||||
for (let [name, meta] of itemMetadataCache) {
|
||||
style.sheet!.insertRule(
|
||||
`.item-${CSS.escape(name)} {
|
||||
background-image: url("${window.staticRoot}static/${exportManifest.assets["item-spritesheet"]}");
|
||||
background-repeat: no-repeat;
|
||||
background-position: -${meta.x}px -${meta.y}px;
|
||||
height: ${meta.size}px;
|
||||
width: ${meta.size}px;
|
||||
}`
|
||||
style.sheet!.insertRule(`\
|
||||
.item-${CSS.escape(name)} {
|
||||
background-image: url("${window.staticRoot}static/${exportManifest.assets["item-spritesheet"]}");
|
||||
background-repeat: no-repeat;
|
||||
background-position: -${meta.x}px -${meta.y}px;
|
||||
height: ${meta.size}px;
|
||||
width: ${meta.size}px;
|
||||
}`
|
||||
);
|
||||
}
|
||||
setItemMetadata(itemMetadataCache);
|
||||
|
||||
@@ -10,7 +10,7 @@ export function useSaves(instanceId?: number): lib.SaveDetails[] {
|
||||
let [saves, setSaves] = useState<lib.SaveDetails[]>([]);
|
||||
|
||||
function updateSaves() {
|
||||
control.sendTo({ instanceId:instanceId! }, new lib.InstanceListSavesRequest()).then(updatedSaves => {
|
||||
control.sendTo({ instanceId: instanceId! }, new lib.InstanceListSavesRequest()).then(updatedSaves => {
|
||||
setSaves(updatedSaves);
|
||||
}).catch(err => {
|
||||
logger.error(`Failed to list instance saves: ${err}`);
|
||||
|
||||
@@ -53,9 +53,9 @@ export function sortLastSeen(userA:RawUserState, userB:RawUserState, instanceIdA
|
||||
}
|
||||
|
||||
export type RawUserState = Partial<lib.RawUser> & {
|
||||
loading?: Boolean;
|
||||
present?: Boolean;
|
||||
missing?: Boolean;
|
||||
loading?: boolean;
|
||||
present?: boolean;
|
||||
missing?: boolean;
|
||||
};
|
||||
|
||||
export function useUser(name: string): [RawUserState, () => void] {
|
||||
|
||||
@@ -56,12 +56,29 @@ export class Control extends lib.Link {
|
||||
logHandlers: Map<lib.LogFilter, logHandler[]> = new Map();
|
||||
|
||||
/** Updates handled by the subscription service */
|
||||
hostUpdate = new lib.EventSubscriber<lib.HostUpdateEvent, lib.HostDetails>(lib.HostUpdateEvent, event => event.update);
|
||||
instanceUpdate = new lib.EventSubscriber<lib.InstanceDetailsUpdateEvent, lib.InstanceDetails>(lib.InstanceDetailsUpdateEvent, event => event.details);
|
||||
saveListUpdate = new lib.EventSubscriber<lib.InstanceSaveListUpdateEvent>(lib.InstanceSaveListUpdateEvent);
|
||||
modPackUpdate = new lib.EventSubscriber<lib.ModPackUpdateEvent, lib.ModPack>(lib.ModPackUpdateEvent, event => event.modPack);
|
||||
modUpdate = new lib.EventSubscriber<lib.ModUpdateEvent, lib.ModInfo>(lib.ModUpdateEvent, event => event.mod);
|
||||
userUpdate = new lib.EventSubscriber<lib.UserUpdateEvent, lib.RawUser>(lib.UserUpdateEvent, event => event.user);
|
||||
hostUpdate = new lib.EventSubscriber<lib.HostUpdateEvent, lib.HostDetails>(
|
||||
lib.HostUpdateEvent, event => event.update,
|
||||
);
|
||||
|
||||
instanceUpdate = new lib.EventSubscriber<lib.InstanceDetailsUpdateEvent, lib.InstanceDetails>(
|
||||
lib.InstanceDetailsUpdateEvent, event => event.details,
|
||||
);
|
||||
|
||||
saveListUpdate = new lib.EventSubscriber<lib.InstanceSaveListUpdateEvent>(
|
||||
lib.InstanceSaveListUpdateEvent,
|
||||
);
|
||||
|
||||
modPackUpdate = new lib.EventSubscriber<lib.ModPackUpdateEvent, lib.ModPack>(
|
||||
lib.ModPackUpdateEvent, event => event.modPack,
|
||||
);
|
||||
|
||||
modUpdate = new lib.EventSubscriber<lib.ModUpdateEvent, lib.ModInfo>(
|
||||
lib.ModUpdateEvent, event => event.mod,
|
||||
);
|
||||
|
||||
userUpdate = new lib.EventSubscriber<lib.UserUpdateEvent, lib.RawUser>(
|
||||
lib.UserUpdateEvent, event => event.user,
|
||||
);
|
||||
|
||||
declare connector: ControlConnector;
|
||||
|
||||
@@ -81,7 +98,7 @@ export class Control extends lib.Link {
|
||||
this.emitAccountUpdate();
|
||||
this.hostUpdate.connectControl(this).catch(err => logger.error(
|
||||
`Unexpected error updating host subscriptions:\n${err.stack}`
|
||||
));;
|
||||
));
|
||||
this.instanceUpdate.connectControl(this).catch(err => logger.error(
|
||||
`Unexpected error updating instance subscriptions:\n${err.stack}`
|
||||
));
|
||||
|
||||
@@ -12,6 +12,6 @@ const info: lib.PluginDeclaration = {
|
||||
messages: [
|
||||
ChatEvent,
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
export default info;
|
||||
@@ -60,6 +60,6 @@ const info: lib.PluginDeclaration = {
|
||||
],
|
||||
webEntrypoint: "./web",
|
||||
routes: ["/inventory"],
|
||||
}
|
||||
};
|
||||
|
||||
export default info;
|
||||
export default info;
|
||||
@@ -1,10 +1,14 @@
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { AcquireRequest, AcquireResponse, ReleaseRequest, UploadRequest, DownloadRequest, DownloadResponse } from "./messages";
|
||||
import {
|
||||
AcquireRequest, AcquireResponse, ReleaseRequest, UploadRequest, DownloadRequest, DownloadResponse,
|
||||
} from "./messages";
|
||||
|
||||
type IpcPlayerName = {
|
||||
player_name: string
|
||||
}
|
||||
export type IpcPlayerData = { //.\module\serialize.lua:serialize.serialize_player()
|
||||
|
||||
// .\module\serialize.lua:serialize.serialize_player()
|
||||
export type IpcPlayerData = {
|
||||
generation: number,
|
||||
controller: string,
|
||||
name: string,
|
||||
@@ -34,6 +38,7 @@ type IpcAcquireResponse = {
|
||||
* Splits string into array of strings with max of a certain length
|
||||
* @param chunkSize - Max length of each chunk
|
||||
* @param string - String to split into chunks
|
||||
* @returns array of substrings
|
||||
*/
|
||||
function chunkify(chunkSize: number, string: string): string[] {
|
||||
return string.match(new RegExp(`.{1,${chunkSize}}`, "g")) || [];
|
||||
@@ -48,21 +53,37 @@ export class InstancePlugin extends lib.BaseInstancePlugin {
|
||||
this.disconnecting = false;
|
||||
|
||||
// Handle IPC from scenario script
|
||||
this.instance.server.on("ipc-inventory_sync_acquire", (request: IpcPlayerName) => this.handleAcquire(request)
|
||||
.catch(err => this.logger.error(`Error handling ipc-inventory_sync_acquire:\n${err.stack}`)));
|
||||
this.instance.server.on("ipc-inventory_sync_release", (request: IpcPlayerName) => this.handleRelease(request)
|
||||
.catch(err => this.logger.error(`Error handling ipc-inventory_sync_release:\n${err.stack}`)));
|
||||
this.instance.server.on("ipc-inventory_sync_upload", (player_data: IpcPlayerData) => this.handleUpload(player_data)
|
||||
.catch(err => this.logger.error(`Error handling ipc-inventory_sync_upload:\n${err.stack}`)));
|
||||
this.instance.server.on("ipc-inventory_sync_download", (request: IpcPlayerName) => this.handleDownload(request)
|
||||
.catch(err => this.logger.error(`Error handling ipc-inventory_sync_download:\n${err.stack}`)));
|
||||
this.instance.server.on(
|
||||
"ipc-inventory_sync_acquire",
|
||||
(request: IpcPlayerName) => this.handleAcquire(request).catch(
|
||||
err => this.logger.error(`Error handling ipc-inventory_sync_acquire:\n${err.stack}`)
|
||||
),
|
||||
);
|
||||
this.instance.server.on(
|
||||
"ipc-inventory_sync_release",
|
||||
(request: IpcPlayerName) => this.handleRelease(request).catch(
|
||||
err => this.logger.error(`Error handling ipc-inventory_sync_release:\n${err.stack}`)
|
||||
),
|
||||
);
|
||||
this.instance.server.on(
|
||||
"ipc-inventory_sync_upload",
|
||||
(player_data: IpcPlayerData) => this.handleUpload(player_data).catch(
|
||||
err => this.logger.error(`Error handling ipc-inventory_sync_upload:\n${err.stack}`)
|
||||
),
|
||||
);
|
||||
this.instance.server.on(
|
||||
"ipc-inventory_sync_download",
|
||||
(request: IpcPlayerName) => this.handleDownload(request).catch(
|
||||
err => this.logger.error(`Error handling ipc-inventory_sync_download:\n${err.stack}`)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async onPrepareControllerDisconnect() {
|
||||
this.disconnecting = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
onControllerConnectionEvent(event: "connect" | "drop" | "resume" | "close") {
|
||||
if (event === "connect") {
|
||||
this.disconnecting = false;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { StringEnum } from "@clusterio/lib";
|
||||
const jsonPlayerData = Type.Object({
|
||||
generation: Type.Number(),
|
||||
name: Type.String(),
|
||||
})
|
||||
});
|
||||
|
||||
export class AcquireResponse {
|
||||
constructor(
|
||||
@@ -45,7 +45,7 @@ export class AcquireRequest {
|
||||
static jsonSchema = Type.Object({
|
||||
"instanceId": Type.Number(),
|
||||
"playerName": Type.String(),
|
||||
})
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof AcquireRequest.jsonSchema>): AcquireRequest {
|
||||
return new this(json.instanceId, json.playerName);
|
||||
@@ -68,7 +68,7 @@ export class ReleaseRequest {
|
||||
static jsonSchema = Type.Object({
|
||||
"instanceId": Type.Integer(),
|
||||
"playerName": Type.String(),
|
||||
})
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof ReleaseRequest.jsonSchema>) {
|
||||
return new this(json.instanceId, json.playerName);
|
||||
@@ -108,7 +108,7 @@ export class DownloadResponse {
|
||||
|
||||
static jsonSchema = Type.Object({
|
||||
"playerData": Type.Optional(jsonPlayerData),
|
||||
})
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof DownloadResponse.jsonSchema>): DownloadResponse {
|
||||
return new this(json.playerData as IpcPlayerData);
|
||||
@@ -132,7 +132,7 @@ export class DownloadRequest {
|
||||
static jsonSchema = Type.Object({
|
||||
"instanceId": Type.Integer(),
|
||||
"playerName": Type.String(),
|
||||
})
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof DownloadRequest.jsonSchema>) {
|
||||
return new this(json.instanceId, json.playerName);
|
||||
@@ -153,8 +153,8 @@ class DatabaseStatsResponse {
|
||||
"largestEntry": Type.Object({
|
||||
"name": Type.String(),
|
||||
"size": Type.Number(),
|
||||
})
|
||||
})
|
||||
}),
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof DatabaseStatsResponse.jsonSchema>): DatabaseStatsResponse {
|
||||
return new this(json.databaseSize, json.databaseEntries, json.largestEntry);
|
||||
|
||||
@@ -56,13 +56,21 @@ export class ControllerPlugin extends lib.BaseControllerPlugin {
|
||||
res.send(servers);
|
||||
});
|
||||
|
||||
this.controller.app.post("/api/player_auth/player_code", express.json(), (req: Request, res: Response, next: any) => {
|
||||
this.handlePlayerCode(req, res).catch(next);
|
||||
});
|
||||
this.controller.app.post(
|
||||
"/api/player_auth/player_code",
|
||||
express.json(),
|
||||
(req: Request, res: Response, next: any) => {
|
||||
this.handlePlayerCode(req, res).catch(next);
|
||||
}
|
||||
);
|
||||
|
||||
this.controller.app.post("/api/player_auth/verify", express.json(), (req: Request, res: Response, next: any) => {
|
||||
this.handleVerify(req, res).catch(next);
|
||||
});
|
||||
this.controller.app.post(
|
||||
"/api/player_auth/verify",
|
||||
express.json(),
|
||||
(req: Request, res: Response, next: any) => {
|
||||
this.handleVerify(req, res).catch(next);
|
||||
}
|
||||
);
|
||||
|
||||
this.controller.handle(FetchPlayerCodeRequest, this.handleFetchPlayerCodeRequest.bind(this));
|
||||
this.controller.handle(SetVerifyCodeRequest, this.handleSetVerifyCodeRequest.bind(this));
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as lib from "@clusterio/lib";
|
||||
|
||||
import { FetchPlayerCodeRequest, SetVerifyCodeRequest } from "./messages";
|
||||
|
||||
type ipcPlayerAuth = {
|
||||
type IpcPlayerAuth = {
|
||||
type: "open_dialog",
|
||||
player: string,
|
||||
} | {
|
||||
@@ -17,12 +17,15 @@ export class InstancePlugin extends lib.BaseInstancePlugin {
|
||||
throw new Error("player_auth plugin requires save patching.");
|
||||
}
|
||||
|
||||
this.instance.server.on("ipc-player_auth", (ipcPlayerAuth: ipcPlayerAuth) => this.handleEvent(ipcPlayerAuth).catch(
|
||||
err => this.logger.error(`Error handling event:\n${err.stack}`)
|
||||
));
|
||||
this.instance.server.on(
|
||||
"ipc-player_auth",
|
||||
(ipcPlayerAuth: IpcPlayerAuth) => this.handleEvent(ipcPlayerAuth).catch(
|
||||
err => this.logger.error(`Error handling event:\n${err.stack}`)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async handleEvent(event: ipcPlayerAuth) {
|
||||
async handleEvent(event: IpcPlayerAuth) {
|
||||
if (event.type === "open_dialog") {
|
||||
if (!this.host.connector.connected) {
|
||||
await this.sendRcon(`/web-login error ${event.player} login is temporarily unavailable`);
|
||||
|
||||
@@ -10,7 +10,7 @@ class FetchPlayerCodeResponse {
|
||||
static jsonSchema = Type.Object({
|
||||
"playerCode": Type.String(),
|
||||
"controllerUrl": Type.String(),
|
||||
})
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof FetchPlayerCodeResponse.jsonSchema>): FetchPlayerCodeResponse {
|
||||
return new this(json.playerCode, json.controllerUrl);
|
||||
@@ -32,7 +32,7 @@ export class FetchPlayerCodeRequest {
|
||||
|
||||
static jsonSchema = Type.Object({
|
||||
"player": Type.String(),
|
||||
})
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof FetchPlayerCodeRequest.jsonSchema>): FetchPlayerCodeRequest {
|
||||
return new this(json.player);
|
||||
@@ -55,7 +55,7 @@ export class SetVerifyCodeRequest {
|
||||
static jsonSchema = Type.Object({
|
||||
"player": Type.String(),
|
||||
"verifyCode": Type.String(),
|
||||
})
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof SetVerifyCodeRequest.jsonSchema>): SetVerifyCodeRequest {
|
||||
return new this(json.player, json.verifyCode);
|
||||
|
||||
@@ -19,7 +19,7 @@ export class ContributionEvent {
|
||||
"name": Type.String(),
|
||||
"level": Type.Integer(),
|
||||
"contribution": Type.Number(),
|
||||
})
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof ContributionEvent.jsonSchema>): ContributionEvent {
|
||||
return new this(json.name, json.level, json.contribution);
|
||||
@@ -39,7 +39,7 @@ export class TechnologyProgress {
|
||||
"name": Type.String(),
|
||||
"level": Type.Integer(),
|
||||
"progress": Type.Number(),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export class ProgressEvent {
|
||||
@@ -57,8 +57,8 @@ export class ProgressEvent {
|
||||
static jsonSchema = Type.Object({
|
||||
"technologies": Type.Array(
|
||||
TechnologyProgress.jsonSchema
|
||||
)
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof ProgressEvent.jsonSchema>): ProgressEvent {
|
||||
return new this(json.technologies);
|
||||
@@ -81,7 +81,7 @@ export class FinishedEvent {
|
||||
static jsonSchema = Type.Object({
|
||||
"name": Type.String(),
|
||||
"level": Type.Integer(),
|
||||
})
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof FinishedEvent.jsonSchema>): FinishedEvent {
|
||||
return new this(json.name, json.level);
|
||||
@@ -100,9 +100,10 @@ export class TechnologySync {
|
||||
static jsonSchema = Type.Tuple([
|
||||
Type.String(),
|
||||
Type.Integer(),
|
||||
Type.Union([ Type.Number(), Type.Null() ]),
|
||||
Type.Union([Type.Number(), Type.Null()]),
|
||||
Type.Boolean(),
|
||||
])
|
||||
]);
|
||||
|
||||
toJSON() {
|
||||
return [this.name, this.level, this.progress, this.researched];
|
||||
}
|
||||
|
||||
@@ -186,7 +186,9 @@ export class ControllerPlugin extends lib.BaseControllerPlugin {
|
||||
// use fancy neural net to calculate a "fair" dole division rate.
|
||||
if (method === "neural_dole") {
|
||||
for (let item of request.items) {
|
||||
let count = this.neuralDole.divider({ name: item.name, count: item.count, instanceId, instanceName });
|
||||
let count = this.neuralDole.divider(
|
||||
{ name: item.name, count: item.count, instanceId, instanceName }
|
||||
);
|
||||
if (count > 0) {
|
||||
itemsRemoved.push(new Item(item.name, count));
|
||||
}
|
||||
|
||||
@@ -99,7 +99,8 @@ export class NeuralDole {
|
||||
// If the server regularly can't fulfill requests, this number grows until
|
||||
// it can. Then it slowly shrinks back down.
|
||||
let _doleDivisionFactor: { [key:string]: number } = {};
|
||||
export function doleDivider({
|
||||
export function doleDivider(
|
||||
{
|
||||
object,
|
||||
items,
|
||||
logItemTransfers,
|
||||
|
||||
@@ -73,7 +73,8 @@ export class InstancePlugin extends lib.BaseInstancePlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
const fromIpcItems = items.map(item => new Item(item[0], item[1]))
|
||||
|
||||
const fromIpcItems = items.map(item => new Item(item[0], item[1]));
|
||||
this.instance.sendTo("controller", new PlaceEvent(fromIpcItems));
|
||||
|
||||
if (this.instance.config.get("subspace_storage.log_item_transfers")) {
|
||||
@@ -85,7 +86,7 @@ export class InstancePlugin extends lib.BaseInstancePlugin {
|
||||
// request items --------------------------------------------------------------
|
||||
async requestItems(requestItems: IpcItems) {
|
||||
// Request the items all at once
|
||||
let fromIpcItems = requestItems.map(item => new Item(item[0], item[1]))
|
||||
let fromIpcItems = requestItems.map(item => new Item(item[0], item[1]));
|
||||
let items = await this.instance.sendTo("controller", new RemoveRequest(fromIpcItems));
|
||||
|
||||
if (!items.length) {
|
||||
|
||||
@@ -37,8 +37,8 @@ export class PlaceEvent {
|
||||
}
|
||||
|
||||
static jsonSchema = Type.Object({
|
||||
"items": Type.Array(Item.jsonSchema)
|
||||
})
|
||||
"items": Type.Array(Item.jsonSchema),
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof PlaceEvent.jsonSchema>): PlaceEvent {
|
||||
return new this(json.items.map(item => Item.fromJSON(item)));
|
||||
@@ -58,8 +58,8 @@ export class RemoveRequest {
|
||||
}
|
||||
|
||||
static jsonSchema = Type.Object({
|
||||
"items": Type.Array(Item.jsonSchema)
|
||||
})
|
||||
"items": Type.Array(Item.jsonSchema),
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof RemoveRequest.jsonSchema>): RemoveRequest {
|
||||
return new this(json.items.map(item => Item.fromJSON(item)));
|
||||
@@ -91,8 +91,8 @@ export class UpdateStorageEvent {
|
||||
}
|
||||
|
||||
static jsonSchema = Type.Object({
|
||||
"items": Type.Array(Item.jsonSchema)
|
||||
})
|
||||
"items": Type.Array(Item.jsonSchema),
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof UpdateStorageEvent.jsonSchema>): UpdateStorageEvent {
|
||||
return new this(json.items.map(item => Item.fromJSON(item)));
|
||||
@@ -113,8 +113,8 @@ export class SetStorageSubscriptionRequest {
|
||||
}
|
||||
|
||||
static jsonSchema = Type.Object({
|
||||
"storage": Type.Boolean()
|
||||
})
|
||||
"storage": Type.Boolean(),
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof SetStorageSubscriptionRequest.jsonSchema>): SetStorageSubscriptionRequest {
|
||||
return new this(json.storage);
|
||||
|
||||
Reference in new issue
Block a user