mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 23:04:52 +09:00
Port basic lib modules to TypeScript
This commit is contained in:
1 parent
2988cd962c
commit
e0c197b9bc
25 files changed
+1370
-1324
No files matched your search
@@ -36,6 +36,7 @@
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@types/node": "^20.4.5",
|
||||
"@types/triple-beam": "^1.3.2",
|
||||
"typescript": "^5.1.6"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
"use strict";
|
||||
|
||||
|
||||
/**
|
||||
* Helper class for implementing the exponential backoff algorithm
|
||||
*
|
||||
@@ -14,49 +11,48 @@
|
||||
* the passed value of `max`. The value for the delay will reset back to
|
||||
* `base` if more time than the value for `reset` has passed between two
|
||||
* invocations of delay.
|
||||
*
|
||||
* @alias module:lib.ExponentialBackoff
|
||||
*/
|
||||
class ExponentialBackoff {
|
||||
export default class ExponentialBackoff {
|
||||
/**
|
||||
* Base delay in seconds. The first delay will on average be this
|
||||
* long, but can be anywhere between 0 and 2 times this value.
|
||||
*/
|
||||
base = 1;
|
||||
/**
|
||||
* Maximum delay in seconds. A delay will not take more than this
|
||||
* value.
|
||||
*/
|
||||
max = 60;
|
||||
/**
|
||||
* Time in seconds between two invocations of delay which will cause
|
||||
* it to start over with `base` instead of doubling.
|
||||
*/
|
||||
reset: number;
|
||||
|
||||
private _exp = 0;
|
||||
private _lastInvocationTime = Date.now();
|
||||
|
||||
/**
|
||||
* Construct ExponentialBackoff helper
|
||||
*
|
||||
* The options passed may also be modified on the instance at runtime.
|
||||
*
|
||||
* @param {object=} options -
|
||||
* @param options -
|
||||
* Options for this exponential backoff helper
|
||||
* @param {number=} [options.base=1] -
|
||||
* @param [options.base=1] -
|
||||
* Base delay in seconds. The first delay will on average be this
|
||||
* long, but can be anywhere between 0 and 2 times this value.
|
||||
* @param {number=} [options.max=60] -
|
||||
* @param [options.max=60] -
|
||||
* Maximum delay in seconds. A delay will not take more than this
|
||||
* value.
|
||||
* @param {number=} [options.reset=2*max] -
|
||||
* @param [options.reset=2*max] -
|
||||
* Time in seconds between two invocations of delay which will cause
|
||||
* it to start over with `base` instead of doubling.
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
/**
|
||||
* Base delay in seconds. The first delay will on average be this
|
||||
* long, but can be anywhere between 0 and 2 times this value.
|
||||
* @type {number}
|
||||
*/
|
||||
constructor(options: { base?: number, max?: number, reset?: number } = {}) {
|
||||
this.base = options.base || 1;
|
||||
/**
|
||||
* Maximum delay in seconds. A delay will not take more than this
|
||||
* value.
|
||||
* @type {number}
|
||||
*/
|
||||
this.max = options.max || 60;
|
||||
/**
|
||||
* Time in seconds between two invocations of delay which will cause
|
||||
* it to start over with `base` instead of doubling.
|
||||
* @type {number}
|
||||
*/
|
||||
this.reset = options.reset || 2 * this.max;
|
||||
|
||||
this._exp = 0;
|
||||
this._lastInvocationTime = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,7 +62,7 @@ class ExponentialBackoff {
|
||||
* algorithm. This is a value between 0 and min(2 * options.base *
|
||||
* 2<sup>retries</sup>, optinos.max).
|
||||
*
|
||||
* @returns {number} time in milliseconds to wait.
|
||||
* @returns time in milliseconds to wait.
|
||||
*/
|
||||
delay() {
|
||||
let invocationTime = Date.now();
|
||||
@@ -1,39 +1,31 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Stastics collected about a player.
|
||||
* @alias module:lib.PlayerStats
|
||||
*/
|
||||
class PlayerStats {
|
||||
export default class PlayerStats {
|
||||
/**
|
||||
* Count of the number of times this player has been seen joining this server.
|
||||
* @type {number}
|
||||
*/
|
||||
joinCount = 0;
|
||||
|
||||
/**
|
||||
* Time in ms this player has been seen online on this instance.
|
||||
* @type {number}
|
||||
*/
|
||||
onlineTimeMs = 0;
|
||||
|
||||
/**
|
||||
* Timestamp the player was last seen joining.
|
||||
* @type {Date=}
|
||||
*/
|
||||
lastJoinAt;
|
||||
lastJoinAt?: Date;
|
||||
|
||||
/**
|
||||
* Timestamp the player was last seen leaving.
|
||||
* @type {Date=}
|
||||
*/
|
||||
lastLeaveAt;
|
||||
lastLeaveAt?: Date;
|
||||
|
||||
/**
|
||||
* Reason the player was last seen leaving with.
|
||||
* @type {string=}
|
||||
*/
|
||||
lastLeaveReason;
|
||||
lastLeaveReason?: string;
|
||||
|
||||
static jsonSchema = {
|
||||
type: "object",
|
||||
@@ -62,7 +54,7 @@ class PlayerStats {
|
||||
}
|
||||
}
|
||||
|
||||
static fromJSON(json) {
|
||||
static fromJSON(json: any) {
|
||||
return new this(json);
|
||||
}
|
||||
|
||||
@@ -84,5 +76,3 @@ class PlayerStats {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PlayerStats;
|
||||
@@ -1,6 +1,3 @@
|
||||
"use strict";
|
||||
|
||||
|
||||
/**
|
||||
* Helper class for rate limiting actions
|
||||
*
|
||||
@@ -8,6 +5,7 @@
|
||||
* limited on invocations.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const rateLimiter = RateLimiter({
|
||||
* action: () => { console.log("action"); },
|
||||
* maxRate: 1,
|
||||
@@ -19,36 +17,36 @@
|
||||
* // and one more time 1 second later for 99 other activations that were
|
||||
* // exceeding the rate limit.
|
||||
* }
|
||||
* @alias module:lib.RateLimiter
|
||||
* ```
|
||||
*/
|
||||
class RateLimiter {
|
||||
export default class RateLimiter {
|
||||
/**
|
||||
* Maximum rate of activations per second.
|
||||
*/
|
||||
maxRate: number;
|
||||
/**
|
||||
* Callback invoked at most `maxRate` per second on activation.
|
||||
*/
|
||||
action: Function | null;
|
||||
|
||||
private _lastRun = 0;
|
||||
private _runTimeout = null;
|
||||
private lastProgressBroadcast = Date.now();
|
||||
|
||||
/**
|
||||
* Construct RateLimiter helper
|
||||
*
|
||||
* The options passed may also be modified on the instance at runtime.
|
||||
*
|
||||
* @param {object=} options - Options for this rate limiter
|
||||
* @param {number=} options.maxRate -
|
||||
* @param options - Options for this rate limiter
|
||||
* @param options.maxRate -
|
||||
* Maximum rate of activations per second.
|
||||
* @param {function()=} options.action -
|
||||
* @param options.action -
|
||||
* Callback invoked at maste `maxRate` per second on activation.
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
/**
|
||||
* Maximum rate of activations per second.
|
||||
* @type {number}
|
||||
*/
|
||||
constructor(options: { maxRate?: number, action?: Function } = {}) {
|
||||
this.maxRate = options.maxRate || 1;
|
||||
|
||||
/**
|
||||
* Callback invoked at most `maxRate` per second on activation.
|
||||
* @type {function()}
|
||||
*/
|
||||
this.action = options.action || null;
|
||||
|
||||
this._lastRun = 0;
|
||||
this._runTimeout = null;
|
||||
this.lastProgressBroadcast = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,7 +57,7 @@ class RateLimiter {
|
||||
* invoked immediatly, otherwise a timeout is registered to invoke the
|
||||
* action at next point in time where it will be within the rate limit.
|
||||
*
|
||||
* @returns {boolean} true if activation was within the rate limit.
|
||||
* @returns true if activation was within the rate limit.
|
||||
*/
|
||||
activate() {
|
||||
let now = Date.now();
|
||||
@@ -100,5 +98,3 @@ class RateLimiter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RateLimiter;
|
||||
@@ -6,29 +6,37 @@
|
||||
* @author Hornwitser
|
||||
* @module lib/command
|
||||
*/
|
||||
"use strict";
|
||||
const libData = require("./data");
|
||||
const libLink = require("./link");
|
||||
const libErrors = require("./errors");
|
||||
import * as libData from "./data";
|
||||
import * as libErrors from "./errors";
|
||||
import type { Link } from "./link";
|
||||
|
||||
|
||||
export type CommandHandler = (args: Record<string, unknown>, control: Link) => Promise<void>;
|
||||
export type CommandDefinition = [string | string[], string?, Function?];
|
||||
|
||||
/**
|
||||
* Represents a command that can be runned by clusterioctl
|
||||
* @static
|
||||
*/
|
||||
class Command {
|
||||
export class Command {
|
||||
private _handler: CommandHandler;
|
||||
private _definition: CommandDefinition;
|
||||
|
||||
name: string;
|
||||
alias: string[];
|
||||
|
||||
/**
|
||||
* Define an executable command.
|
||||
*
|
||||
* @param {Object} cmd - Command definiton.
|
||||
* @param {Array} cmd.definiton -
|
||||
* @param cmd - Command definiton.
|
||||
* @param cmd.definiton -
|
||||
* Arguments to pass to yargs .command method to define this
|
||||
* command.
|
||||
* @param {function(args, control)} handler -
|
||||
* @param cmd.handler -
|
||||
* Async function invoked when the command is executed. Is given
|
||||
* the parsed args and a reference to the {@link
|
||||
* module:ctl/ctl.Control} instance.
|
||||
*/
|
||||
constructor({ definition, handler }) {
|
||||
constructor({ definition, handler }: { definition: CommandDefinition, handler: CommandHandler }) {
|
||||
if (definition[0] instanceof Array) {
|
||||
this.name = definition[0][0].split(" ")[0];
|
||||
this.alias = definition[0].slice(1);
|
||||
@@ -40,11 +48,11 @@ class Command {
|
||||
this._handler = handler;
|
||||
}
|
||||
|
||||
register(yargs) {
|
||||
register(yargs: any) {
|
||||
yargs.command(...this._definition);
|
||||
}
|
||||
|
||||
async run(args, control) {
|
||||
async run(args: Record<string, unknown>, control: Link) {
|
||||
await this._handler(args, control);
|
||||
}
|
||||
}
|
||||
@@ -56,20 +64,23 @@ class Command {
|
||||
* CommandTrees. This is used by clusterioctl to hold the full tree of
|
||||
* available commands, you may extend this tree by using a control plugin,
|
||||
* see {@link module:lib.BaseControlPlugin#addCommands}
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
class CommandTree {
|
||||
export class CommandTree {
|
||||
public name: string;
|
||||
public alias: string[];
|
||||
public description: string;
|
||||
public subCommands: Map<string, Command | CommandTree>;
|
||||
|
||||
/**
|
||||
* Define a command containing sub commands.
|
||||
*
|
||||
* @param {Object} cmd - Command definition
|
||||
* @param {string} cmd.name - Name of the command.
|
||||
* @param {Array<string>} cmd.alias - Aliases for this tree.
|
||||
* @param {description} cmd.description -
|
||||
* @param cmd - Command definition
|
||||
* @param cmd.name - Name of the command.
|
||||
* @param cmd.alias - Aliases for this tree.
|
||||
* @param cmd.description -
|
||||
* Descripton to provide for this command tree node.
|
||||
*/
|
||||
constructor({ name, alias, description }) {
|
||||
constructor({ name, alias, description }: { name: string, alias: string[], description: string }) {
|
||||
if (typeof name !== "string") {
|
||||
throw new Error("name must be a string");
|
||||
}
|
||||
@@ -83,10 +94,10 @@ class CommandTree {
|
||||
/**
|
||||
* Add a command or command tree to this tree
|
||||
*
|
||||
* @param {module:lib.Command|module:lib.CommandTree} command -
|
||||
* @param command -
|
||||
* The command to add to this command tree.
|
||||
*/
|
||||
add(command) {
|
||||
add(command: Command | CommandTree) {
|
||||
if (this.subCommands.has(command.name)) {
|
||||
throw new Error(`Command ${command.name} already exists`);
|
||||
}
|
||||
@@ -102,13 +113,13 @@ class CommandTree {
|
||||
/**
|
||||
* Get a command or command tree from this tree
|
||||
*
|
||||
* @param {string} name -
|
||||
* @param name -
|
||||
* The name of the command or command tree to retrieve.
|
||||
* @return {?module:lib.Command|module:lib.CommandTree}
|
||||
* @return
|
||||
* The command to add to this command tree.
|
||||
*/
|
||||
get(name) {
|
||||
return this.subCommands.get(name) || null;
|
||||
get(name: string) {
|
||||
return this.subCommands.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,10 +128,10 @@ class CommandTree {
|
||||
* Traverses the the tree of sub-commands recursively and adds all of
|
||||
* them to the given yargs parser.
|
||||
*
|
||||
* @param {Object} yargs - yargs parser to register with.
|
||||
* @param yargs - yargs parser to register with.
|
||||
*/
|
||||
register(yargs) {
|
||||
yargs.command([this.name].concat(this.alias), this.description, (yargs) => {
|
||||
register(yargs: any) {
|
||||
yargs.command([this.name].concat(this.alias), this.description, (yargs: any) => {
|
||||
for (let [name, command] of this.subCommands) {
|
||||
// Check if the entry is not an alias.
|
||||
if (name === command.name) {
|
||||
@@ -141,13 +152,12 @@ class CommandTree {
|
||||
* Resolves a string with either an host name or an id into an integer with
|
||||
* the host ID.
|
||||
*
|
||||
* @param {module:lib.Link} client - link to controller to query host on.
|
||||
* @param {string} hostName - string with name or id of host.
|
||||
* @returns {Promise<number>} host ID.
|
||||
* @static
|
||||
* @param client - link to controller to query host on.
|
||||
* @param hostName - string with name or id of host.
|
||||
* @returns host ID.
|
||||
*/
|
||||
async function resolveHost(client, hostName) {
|
||||
let hostId;
|
||||
export async function resolveHost(client: Link, hostName: string) {
|
||||
let hostId: number;
|
||||
if (/^-?\d+$/.test(hostName)) {
|
||||
hostId = parseInt(hostName, 10);
|
||||
} else {
|
||||
@@ -173,13 +183,12 @@ async function resolveHost(client, hostName) {
|
||||
* Resolves a string with either an instance name or an id into an integer
|
||||
* with the instance ID.
|
||||
*
|
||||
* @param {module:lib.Link} client - link to controller to query instance on.
|
||||
* @param {string} instanceName - string with name or id of instance.
|
||||
* @returns {Promise<number>} instance ID.
|
||||
* @static
|
||||
* @param client - link to controller to query instance on.
|
||||
* @param instanceName - string with name or id of instance.
|
||||
* @returns instance ID.
|
||||
*/
|
||||
async function resolveInstance(client, instanceName) {
|
||||
let instanceId;
|
||||
export async function resolveInstance(client: Link, instanceName: string) {
|
||||
let instanceId: number;
|
||||
if (/^-?\d+$/.test(instanceName)) {
|
||||
instanceId = parseInt(instanceName, 10);
|
||||
} else {
|
||||
@@ -205,14 +214,13 @@ async function resolveInstance(client, instanceName) {
|
||||
* Resolevs a string with either a mod pack name or an id into an integer
|
||||
* with the mod pack ID.
|
||||
*
|
||||
* @param {module:lib.Link} client -
|
||||
* @param client -
|
||||
* link to controller to query mod pack on.
|
||||
* @param {string} modPackName - string with name or id of mod pack.
|
||||
* @returns {Promise<number>} mod pack ID.
|
||||
* @static
|
||||
* @param modPackName - string with name or id of mod pack.
|
||||
* @returns mod pack ID.
|
||||
*/
|
||||
async function resolveModPack(client, modPackName) {
|
||||
let modPackId;
|
||||
export async function resolveModPack(client: Link, modPackName: string) {
|
||||
let modPackId: number;
|
||||
if (/^-?\d+/.test(modPackName)) {
|
||||
modPackId = parseInt(modPackName, 10);
|
||||
} else {
|
||||
@@ -238,15 +246,14 @@ async function resolveModPack(client, modPackName) {
|
||||
* Resolves a string with either a role name or an id into an object
|
||||
* representing the role.
|
||||
*
|
||||
* @param {module:lib.Link} client - link to controller to query role on.
|
||||
* @param {string} roleName - string with name or id of role.
|
||||
* @returns {Promise<object>} Role info.
|
||||
* @static
|
||||
* @param client - link to controller to query role on.
|
||||
* @param roleName - string with name or id of role.
|
||||
* @returns Role info.
|
||||
*/
|
||||
async function retrieveRole(client, roleName) {
|
||||
export async function retrieveRole(client: Link, roleName: string) {
|
||||
let roles = await client.sendTo("controller", new libData.RoleListRequest());
|
||||
|
||||
let resolvedRole;
|
||||
let resolvedRole: libData.RawRole;
|
||||
if (/^-?\d+$/.test(roleName)) {
|
||||
let roleId = parseInt(roleName, 10);
|
||||
for (let role of roles) {
|
||||
@@ -271,13 +278,3 @@ async function retrieveRole(client, roleName) {
|
||||
|
||||
return resolvedRole;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Command,
|
||||
CommandTree,
|
||||
|
||||
resolveHost,
|
||||
resolveInstance,
|
||||
resolveModPack,
|
||||
retrieveRole,
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
"use strict";
|
||||
const PlayerStats = require("../PlayerStats");
|
||||
const PlayerStats = require("../PlayerStats").default;
|
||||
const { JsonString, jsonArray } = require("./composites");
|
||||
|
||||
/** @memberof module:lib */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"use strict";
|
||||
const PlayerStats = require("../PlayerStats");
|
||||
const PlayerStats = require("../PlayerStats").default;
|
||||
const { jsonArray, JsonNumber } = require("./composites");
|
||||
|
||||
/* @memberof module:lib */
|
||||
|
||||
@@ -4,23 +4,21 @@
|
||||
* @module lib/database
|
||||
* @author Hornwitser
|
||||
*/
|
||||
import fs from "fs-extra";
|
||||
|
||||
"use strict";
|
||||
const fs = require("fs-extra");
|
||||
|
||||
const libFileOps = require("./file_ops");
|
||||
const { basicType } = require("./helpers");
|
||||
import * as libFileOps from "./file_ops";
|
||||
import { basicType } from "./helpers";
|
||||
|
||||
|
||||
/**
|
||||
* Converts a Map with only string keys to a JavaScript object.
|
||||
*
|
||||
* @param {Map} map - the Map to convert to an object.
|
||||
* @return {object} - Object with the mapping's key-values as properties.
|
||||
* @param map - the Map to convert to an object.
|
||||
* @return Object with the mapping's key-values as properties.
|
||||
* @throws {Error} if there are non-string keys in map.
|
||||
*/
|
||||
function mapToObject(map) {
|
||||
let obj = {};
|
||||
export function mapToObject<T>(map: Map<string, T>) {
|
||||
let obj: Record<string, T> = {};
|
||||
for (let [key, value] of map) {
|
||||
if (typeof key !== "string") {
|
||||
throw new Error(
|
||||
@@ -40,14 +38,15 @@ function mapToObject(map) {
|
||||
* properties as key value pairs and converts it into a JavaScript Map.
|
||||
* Returns an empty Map if the file does not exist.
|
||||
*
|
||||
* @param {string} filePath - The path to the JSON file to load.
|
||||
* @param filePath - The path to the JSON file to load.
|
||||
* @returns promise resolving to the loaded map
|
||||
* @throws {Error} If JSON file did not contain an object.
|
||||
* @throws {Error} If an error occured reading the file.
|
||||
*/
|
||||
async function loadJsonAsMap(filePath) {
|
||||
export async function loadJsonAsMap(filePath: string): Promise<Map<string, unknown>> {
|
||||
try {
|
||||
let content = await fs.readFile(filePath);
|
||||
let parsed = JSON.parse(content);
|
||||
let parsed = JSON.parse(content.toString());
|
||||
|
||||
if (basicType(parsed) !== "object") {
|
||||
throw new Error(`Expected object but got ${basicType(parsed)}`);
|
||||
@@ -72,12 +71,12 @@ async function loadJsonAsMap(filePath) {
|
||||
* If the directory the file is to be saved into doesn't exist it will be
|
||||
* created.
|
||||
*
|
||||
* @param {string} filePath - The path to the JSON file that will be saved.
|
||||
* @param {Map} map - Map with only string keys to save.
|
||||
* @param filePath - The path to the JSON file that will be saved.
|
||||
* @param map - Map with only string keys to save.
|
||||
* @throws {Error} if there are non-string keys in map.
|
||||
* @throws {Error} if an error occured writing to the file.
|
||||
*/
|
||||
async function saveMapAsJson(filePath, map) {
|
||||
export async function saveMapAsJson(filePath: string, map: Map<string, unknown>) {
|
||||
let obj = mapToObject(map);
|
||||
await libFileOps.safeOutputFile(filePath, JSON.stringify(obj, null, 4));
|
||||
}
|
||||
@@ -88,15 +87,15 @@ async function saveMapAsJson(filePath, map) {
|
||||
* Loads the JSON file specified by path containing an array of objects that
|
||||
* each have an id property into a Map from the id to the object.
|
||||
*
|
||||
* @param {string} filePath - The path to the JSON file to load.
|
||||
* @param filePath - The path to the JSON file to load.
|
||||
* @throws {Error} If JSON file did not contain an array.
|
||||
* @throws {Error} If there are objects in the array without an id property.
|
||||
* @throws {Error} If an error occured reading the file.
|
||||
*/
|
||||
async function loadJsonArrayAsMap(filePath) {
|
||||
export async function loadJsonArrayAsMap(filePath: string): Promise<Map<unknown, unknown>> {
|
||||
try {
|
||||
let content = await fs.readFile(filePath);
|
||||
let parsed = JSON.parse(content);
|
||||
let parsed = JSON.parse(content.toString());
|
||||
|
||||
if (basicType(parsed) !== "array") {
|
||||
throw new Error(`Expected array but got ${basicType(parsed)}`);
|
||||
@@ -132,24 +131,24 @@ async function loadJsonArrayAsMap(filePath) {
|
||||
* Save the values of a Map with objects containing an id property that is
|
||||
* equal to the key the objects are stored at into a JSON file.
|
||||
*
|
||||
* @param {string} filePath - The path to the JSON file that will be saved.
|
||||
* @param {Map} map -
|
||||
* @param filePath - The path to the JSON file that will be saved.
|
||||
* @param map -
|
||||
* Map with objects containing an id property equal to the key they are
|
||||
* stored under.
|
||||
* @throws {Error} if an error occured writing to the file.
|
||||
*/
|
||||
async function saveMapAsJsonArray(filePath, map) {
|
||||
export async function saveMapAsJsonArray(filePath: string, map: Map<unknown, { id: unknown }>) {
|
||||
await libFileOps.safeOutputFile(filePath, JSON.stringify([...map.values()], null, 4));
|
||||
}
|
||||
|
||||
|
||||
function checkName(name) {
|
||||
function checkName(name: unknown): asserts name is string {
|
||||
if (typeof name !== "string") {
|
||||
throw new Error("name must be a string");
|
||||
}
|
||||
}
|
||||
|
||||
function checkCount(count) {
|
||||
function checkCount(count: unknown): asserts count is number {
|
||||
if (typeof count !== "number" || isNaN(count)) {
|
||||
throw new Error("count must be a number");
|
||||
}
|
||||
@@ -164,7 +163,8 @@ function checkCount(count) {
|
||||
* serialized the database discards entries with a zero counts, and when
|
||||
* deserialized the content is verified.
|
||||
*/
|
||||
class ItemDatabase {
|
||||
export class ItemDatabase {
|
||||
private _items: Map<string, number> = new Map();
|
||||
|
||||
/**
|
||||
* Create a new item database
|
||||
@@ -174,9 +174,7 @@ class ItemDatabase {
|
||||
* database from. An empty database will be created if this parameter
|
||||
* is left undefined.
|
||||
*/
|
||||
constructor(serialized) {
|
||||
this._items = new Map();
|
||||
|
||||
constructor(serialized?: object) {
|
||||
// Verify the content of the serialized database
|
||||
if (serialized !== undefined) {
|
||||
for (let [name, count] of Object.entries(serialized)) {
|
||||
@@ -194,10 +192,10 @@ class ItemDatabase {
|
||||
* Serialize the item database into a plain JavaScript object that can be
|
||||
* turned inta a string with JSON.stringify().
|
||||
*
|
||||
* @returns {object} Serialized representation of the database
|
||||
* @returns Serialized representation of the database
|
||||
*/
|
||||
serialize() {
|
||||
let obj = {};
|
||||
let obj: Record<string, number> = {};
|
||||
for (let [name, count] of this._items) {
|
||||
if (count !== 0) {
|
||||
obj[name] = count;
|
||||
@@ -223,10 +221,10 @@ class ItemDatabase {
|
||||
* database. If the item has not been previously stored into the database
|
||||
* then 0 is returned.
|
||||
*
|
||||
* @param {string} name - The name of the item to get the count of.
|
||||
* @returns {number} The count of the item stored.
|
||||
* @param name - The name of the item to get the count of.
|
||||
* @returns The count of the item stored.
|
||||
*/
|
||||
getItemCount(name) {
|
||||
getItemCount(name: string) {
|
||||
checkName(name);
|
||||
|
||||
if (!this._items.has(name)) {
|
||||
@@ -241,10 +239,10 @@ class ItemDatabase {
|
||||
*
|
||||
* Add count copies of the item specified by name to the item database.
|
||||
*
|
||||
* @param {string} name - The name of the item to add.
|
||||
* @param {number} count - The count of item to remove.
|
||||
* @param name - The name of the item to add.
|
||||
* @param count - The count of item to remove.
|
||||
*/
|
||||
addItem(name, count) {
|
||||
addItem(name: string, count: number) {
|
||||
checkName(name);
|
||||
checkCount(count);
|
||||
|
||||
@@ -262,10 +260,10 @@ class ItemDatabase {
|
||||
* If count is greater than the currently stored count the stored item
|
||||
* count will become negative.
|
||||
*
|
||||
* @param {string} name - The name of the item to remove.
|
||||
* @param {number} count - The count of items to remove.
|
||||
* @param name - The name of the item to remove.
|
||||
* @param count - The count of items to remove.
|
||||
*/
|
||||
removeItem(name, count) {
|
||||
removeItem(name: string, count: number) {
|
||||
checkName(name);
|
||||
checkCount(count);
|
||||
|
||||
@@ -277,12 +275,3 @@ class ItemDatabase {
|
||||
this._items.set(name, count);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mapToObject,
|
||||
loadJsonAsMap,
|
||||
saveMapAsJson,
|
||||
loadJsonArrayAsMap,
|
||||
saveMapAsJsonArray,
|
||||
ItemDatabase,
|
||||
};
|
||||
@@ -1,92 +0,0 @@
|
||||
/**
|
||||
* Errors thrown in Clusterio
|
||||
* @module lib/errors
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Thrown by commands when they fail
|
||||
* @static
|
||||
*/
|
||||
class CommandError extends Error { }
|
||||
|
||||
/**
|
||||
* Thrown from requests sent when an error occured handling it
|
||||
* @static
|
||||
*/
|
||||
class RequestError extends Error {
|
||||
constructor(message, code, stack) {
|
||||
super(message);
|
||||
if (code) { this.code = code; }
|
||||
if (stack) { this.stack = stack; }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when a permission check fails.
|
||||
*
|
||||
* Is a subclass of RequestError to prevent logging stack traces when
|
||||
* requests fail due to permission denied.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
class PermissionError extends RequestError { }
|
||||
|
||||
/**
|
||||
* Signal for messages that fail validation
|
||||
* @static
|
||||
*/
|
||||
class InvalidMessage extends Error {
|
||||
constructor(msg, errors) {
|
||||
super(msg);
|
||||
this.errors = errors || null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown from requests when the session it was sent on was lost
|
||||
* @static
|
||||
*/
|
||||
class SessionLost extends Error { }
|
||||
|
||||
/**
|
||||
* Thrown when WebSocket authentication failed
|
||||
* @static
|
||||
*/
|
||||
class AuthenticationFailed extends Error { }
|
||||
|
||||
/**
|
||||
* Errror class for known errors occuring during startup
|
||||
* @static
|
||||
*/
|
||||
class StartupError extends Error { }
|
||||
|
||||
/**
|
||||
* Errors outside of our control
|
||||
* @static
|
||||
*/
|
||||
class EnvironmentError extends Error { }
|
||||
|
||||
/**
|
||||
* Errors caused by plugins
|
||||
* @static
|
||||
*/
|
||||
class PluginError extends Error {
|
||||
constructor(pluginName, original) {
|
||||
super(`PluginError: ${original.message}`);
|
||||
this.pluginName = pluginName;
|
||||
this.original = original;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
CommandError,
|
||||
RequestError,
|
||||
PermissionError,
|
||||
InvalidMessage,
|
||||
SessionLost,
|
||||
AuthenticationFailed,
|
||||
StartupError,
|
||||
EnvironmentError,
|
||||
PluginError,
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Errors thrown in Clusterio
|
||||
* @module lib/errors
|
||||
*/
|
||||
|
||||
/**
|
||||
* Thrown by commands when they fail
|
||||
*/
|
||||
export class CommandError extends Error { }
|
||||
|
||||
/**
|
||||
* Thrown from requests sent when an error occured handling it
|
||||
*/
|
||||
export class RequestError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public code?: string,
|
||||
public stack?: string,
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when a permission check fails.
|
||||
*
|
||||
* Is a subclass of RequestError to prevent logging stack traces when
|
||||
* requests fail due to permission denied.
|
||||
*
|
||||
*/
|
||||
export class PermissionError extends RequestError { }
|
||||
|
||||
/**
|
||||
* Signal for messages that fail validation
|
||||
*/
|
||||
export class InvalidMessage extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public errors: object = null,
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown from requests when the session it was sent on was lost
|
||||
*/
|
||||
export class SessionLost extends Error { }
|
||||
|
||||
/**
|
||||
* Thrown when WebSocket authentication failed
|
||||
*/
|
||||
export class AuthenticationFailed extends Error { }
|
||||
|
||||
/**
|
||||
* Errror class for known errors occuring during startup
|
||||
*/
|
||||
export class StartupError extends Error { }
|
||||
|
||||
/**
|
||||
* Errors outside of our control
|
||||
*/
|
||||
export class EnvironmentError extends Error { }
|
||||
|
||||
/**
|
||||
* Errors caused by plugins
|
||||
*/
|
||||
export class PluginError extends Error {
|
||||
constructor(
|
||||
public pluginName: string,
|
||||
public original: Error
|
||||
) {
|
||||
super(`PluginError: ${original.message}`);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,24 @@
|
||||
/**
|
||||
* @module lib/file_ops
|
||||
*/
|
||||
"use strict";
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const crypto = require("crypto"); // needed for getTempFile
|
||||
import fs from "fs-extra";
|
||||
import path from "path";
|
||||
import crypto from "crypto"; // needed for getTempFile
|
||||
|
||||
/**
|
||||
* Returns the newest file in a directory
|
||||
*
|
||||
* @param {string} directory - The directory to look for the newest file in.
|
||||
* @param {function(string): boolean} filter -
|
||||
* @param directory - The directory to look for the newest file in.
|
||||
* @param filter -
|
||||
* Optional function to filter out files to ignore. Should return true
|
||||
* if the file is to be considered.
|
||||
* @returns {Promise<string|undefined>}
|
||||
* @returns
|
||||
* Name of the file with the newest timestamp or null if the
|
||||
* directory contains no files.
|
||||
*/
|
||||
async function getNewestFile(directory, filter = (name) => true) {
|
||||
export async function getNewestFile(directory: string, filter = (_name: string) => true) {
|
||||
let newestTime = new Date(0);
|
||||
let newestFile;
|
||||
let newestFile: string | undefined;
|
||||
for (let entry of await fs.readdir(directory, { withFileTypes: true })) {
|
||||
if (entry.isFile()) {
|
||||
let stat = await fs.stat(path.join(directory, entry.name));
|
||||
@@ -40,11 +39,11 @@ async function getNewestFile(directory, filter = (name) => true) {
|
||||
* Error reading the size of files are ignored, and error reading the
|
||||
* directory will result in 0 being returned.
|
||||
*
|
||||
* @param {string} directory - The directory to sum files in.
|
||||
* @returns {Promise<number>} The size in bytes of all files in the directory.
|
||||
* @param directory - The directory to sum files in.
|
||||
* @returns The size in bytes of all files in the directory.
|
||||
*/
|
||||
async function directorySize(directory) {
|
||||
let dirEntries;
|
||||
export async function directorySize(directory: string) {
|
||||
let dirEntries: fs.Dirent[];
|
||||
try {
|
||||
dirEntries = await fs.readdir(directory, { withFileTypes: true });
|
||||
} catch (err) {
|
||||
@@ -53,7 +52,7 @@ async function directorySize(directory) {
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
let statTasks = [];
|
||||
let statTasks: Promise<number>[] = [];
|
||||
for (let entry of dirEntries) {
|
||||
if (entry.isFile()) {
|
||||
statTasks.push(fs
|
||||
@@ -77,13 +76,13 @@ async function directorySize(directory) {
|
||||
* racy and a file or folder may have been created in the folder by the time
|
||||
* this function returns.
|
||||
*
|
||||
* @param {string} directory - directory to check in.
|
||||
* @param {string} name - file name to check for, may have extension.
|
||||
* @param {string} extension - dot extension used for the file name.
|
||||
* @returns {Promise<string>} modified name with extension that likely does
|
||||
* @param directory - directory to check in.
|
||||
* @param name - file name to check for, may have extension.
|
||||
* @param extension - dot extension used for the file name.
|
||||
* @returns modified name with extension that likely does
|
||||
* not exist in the folder
|
||||
*/
|
||||
async function findUnusedName(directory, name, extension = "") {
|
||||
export async function findUnusedName(directory: string, name: string, extension = "") {
|
||||
if (extension && name.endsWith(extension)) {
|
||||
name = name.slice(0, -extension.length);
|
||||
}
|
||||
@@ -101,25 +100,27 @@ async function findUnusedName(directory, name, extension = "") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate collision resistante temporary file name
|
||||
*
|
||||
* Warning: this function should not be relied upon to be accurate for
|
||||
* security sensitive applications. The selection process is inherently
|
||||
* racy and a file or folder may have been created in the folder by the time
|
||||
* this function returns.
|
||||
*
|
||||
* @param {string} prefix - Prefix for file
|
||||
* @param {string} suffix - Suffix for file
|
||||
* @param {string} tmpdir - Directory for temp file
|
||||
* @param prefix - Prefix for file
|
||||
* @param suffix - Suffix for file
|
||||
* @param tmpdir - Directory for temp file
|
||||
* @returns Temporary file name
|
||||
*/
|
||||
async function getTempFile(prefix, suffix, tmpdir) {
|
||||
prefix = (typeof prefix !== "undefined") ? prefix : "tmp.";
|
||||
suffix = (typeof suffix !== "undefined") ? suffix : "";
|
||||
tmpdir = (typeof tmpdir !== "undefined") ? tmpdir : "./";
|
||||
export async function getTempFile(prefix = "tmp.", suffix = "", tmpdir = "./") {
|
||||
let fileName = path.join(prefix + crypto.randomBytes(16).toString("hex") + suffix);
|
||||
let freeFile = await findUnusedName(tmpdir, fileName);
|
||||
let fullPath = path.join(tmpdir, freeFile);
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely write data to a file
|
||||
*
|
||||
@@ -131,11 +132,11 @@ async function getTempFile(prefix, suffix, tmpdir) {
|
||||
* should not be too much of an issue as the next time the same file is
|
||||
* written the temporary will be overwritten and renamed to the target file.
|
||||
*
|
||||
* @param {string} file - Path to file to write.
|
||||
* @param {string|Buffer} data - Content to write.
|
||||
* @param {object|string} options - see fs.writeFile, `flag` must not be set.
|
||||
* @param file - Path to file to write.
|
||||
* @param data - Content to write.
|
||||
* @param options - see fs.writeFile, `flag` must not be set.
|
||||
*/
|
||||
async function safeOutputFile(file, data, options={}) {
|
||||
export async function safeOutputFile(file: string, data: string | Buffer, options: fs.WriteFileOptions ={}) {
|
||||
let directory = path.dirname(file);
|
||||
if (!await fs.pathExists(directory)) {
|
||||
await fs.mkdirs(directory);
|
||||
@@ -153,7 +154,7 @@ async function safeOutputFile(file, data, options={}) {
|
||||
}
|
||||
|
||||
|
||||
// Reserved names by allmost all filesystems
|
||||
// Reserved names by almost all filesystems
|
||||
const badNames = [".", ".."];
|
||||
|
||||
// Reserved namespaces in Windows
|
||||
@@ -167,10 +168,10 @@ const badWinNamespaces = [
|
||||
/**
|
||||
* Check if a string is a valid file name
|
||||
*
|
||||
* @param {string} name - Name to check
|
||||
* @param name - Name to check
|
||||
* @throws Error if the name is unsuitable.
|
||||
*/
|
||||
function checkFilename(name) {
|
||||
export function checkFilename(name: string) {
|
||||
// All of these are bad in Windows only, except for /, . and ..
|
||||
// See: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
|
||||
const badChars = /[<>:"\/\\|?*\x00-\x1f]/g;
|
||||
@@ -208,10 +209,10 @@ function checkFilename(name) {
|
||||
/**
|
||||
* Clean up string to be suitable for use as filename
|
||||
*
|
||||
* @param {string} name - Arbitrary name string
|
||||
* @returns {string} Filename suitable to use in the filesystem
|
||||
* @param name - Arbitrary name string
|
||||
* @returns Filename suitable to use in the filesystem
|
||||
*/
|
||||
function cleanFilename(name) {
|
||||
export function cleanFilename(name: string) {
|
||||
// copied from checkFilename due to RegExp with global flag containing state.
|
||||
const badChars = /[<>:"\/\\|?*\x00-\x1f]/g;
|
||||
const badEnd = /[. ]$/;
|
||||
@@ -233,14 +234,3 @@ function cleanFilename(name) {
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
getNewestFile,
|
||||
directorySize,
|
||||
findUnusedName,
|
||||
getTempFile,
|
||||
safeOutputFile,
|
||||
checkFilename,
|
||||
cleanFilename,
|
||||
};
|
||||
@@ -2,18 +2,17 @@
|
||||
* Hashing functions
|
||||
* @module lib/hash
|
||||
*/
|
||||
"use strict";
|
||||
const crypto = require("crypto");
|
||||
const fs = require("fs-extra");
|
||||
import crypto from "crypto";
|
||||
import fs from "fs-extra";
|
||||
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves to the SHA1 hash of the stream given
|
||||
*
|
||||
* @param {Stream} stream - Node stream of the content to hash.
|
||||
* @returns {Promise<string>} hash of the stream.
|
||||
* @param stream - Node stream of the content to hash.
|
||||
* @returns hash of the stream.
|
||||
*/
|
||||
function hashStream(stream) {
|
||||
export function hashStream(stream: NodeJS.ReadableStream): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let hasher = crypto.createHash("sha1");
|
||||
hasher.setEncoding("hex");
|
||||
@@ -33,14 +32,9 @@ function hashStream(stream) {
|
||||
/**
|
||||
* Returns a promise that resolves to the SHA1 hash of the file given by path
|
||||
*
|
||||
* @param {string} path - Path to the file to hash.
|
||||
* @returns {Promise<string>} hash of the file given.
|
||||
* @param path - Path to the file to hash.
|
||||
* @returns hash of the file given.
|
||||
*/
|
||||
function hashFile(path) {
|
||||
export function hashFile(path: string) {
|
||||
return hashStream(fs.createReadStream(path));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hashStream,
|
||||
hashFile,
|
||||
};
|
||||
@@ -5,17 +5,14 @@
|
||||
* @author Hornwitser
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Parse INI file into object of sections
|
||||
* @param {string} input - content of INI file to parse.
|
||||
* @returns {object<string, object<string, string>>}
|
||||
* sections parsed from the ini file.
|
||||
* @param input - content of INI file to parse.
|
||||
* @returns sections parsed from the ini file.
|
||||
*/
|
||||
function parse(input) {
|
||||
const sections = {};
|
||||
let currentSection = sections;
|
||||
export function parse(input: string) {
|
||||
const sections: Record<string, string | Record<string, string>> = {};
|
||||
let currentSection: Record<string, string> = sections as Record<string, string>;
|
||||
const lines = input.split(/\r\n|\n/);
|
||||
for (let number = 1; number <= lines.length; number++) {
|
||||
let line = lines[number - 1].replace(/^[\t ]+/, "");
|
||||
@@ -56,11 +53,11 @@ function parse(input) {
|
||||
|
||||
/**
|
||||
* Serialize an object of section definitions into an INI file
|
||||
* @param {object<string, object<string, string>>} sections -
|
||||
* @param sections -
|
||||
* Sections to encode into ini format
|
||||
* @returns {string} sections serialized into INI format
|
||||
* @returns sections serialized into INI format
|
||||
*/
|
||||
function stringify(sections) {
|
||||
export function stringify(sections: Record<string, string | Record<string, string>>) {
|
||||
let serialized = [];
|
||||
let first = true;
|
||||
|
||||
@@ -87,8 +84,3 @@ function stringify(sections) {
|
||||
if (serialized.length) { serialized.push(""); }
|
||||
return serialized.join("\n");
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parse,
|
||||
stringify,
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export default function isDeepStrictEqual(a: any, b: any): boolean
|
||||
@@ -1,9 +1,9 @@
|
||||
"use strict";
|
||||
const winston = require("winston");
|
||||
const Transport = require("winston-transport");
|
||||
const { LEVEL, MESSAGE } = require("triple-beam");
|
||||
import winston from "winston";
|
||||
import Transport from "winston-transport";
|
||||
import { LEVEL, MESSAGE } from "triple-beam";
|
||||
|
||||
const levels = Object.freeze({
|
||||
export const levels = Object.freeze({
|
||||
fatal: 0,
|
||||
error: 1,
|
||||
warn: 2,
|
||||
@@ -13,7 +13,7 @@ const levels = Object.freeze({
|
||||
verbose: 6,
|
||||
});
|
||||
|
||||
const colors = {
|
||||
export const colors = {
|
||||
fatal: "inverse red",
|
||||
error: "bold brightRed",
|
||||
warn: "bold brightYellow",
|
||||
@@ -21,21 +21,31 @@ const colors = {
|
||||
info: "bold brightBlue",
|
||||
server: "bold",
|
||||
verbose: "bold grey",
|
||||
};
|
||||
} as const;
|
||||
winston.addColors(colors);
|
||||
|
||||
// The Console Transport is replicated here because it's overly complicated
|
||||
// in winston, writes to stdout/err instead of console and doesn't work in
|
||||
// the browser.
|
||||
class ConsoleTransport extends Transport {
|
||||
constructor(options = {}) {
|
||||
export class ConsoleTransport extends Transport {
|
||||
errorLevels: Set<string>;
|
||||
warnLevels: Set<string>;
|
||||
filter: (info: object) => boolean;
|
||||
|
||||
constructor(
|
||||
options: Transport.TransportStreamOptions & {
|
||||
errorLevels?: string[],
|
||||
warnLevels?: string[],
|
||||
filter?: ConsoleTransport["filter"]
|
||||
} = {}
|
||||
) {
|
||||
super(options);
|
||||
this.errorLevels = new Set(options.errorLevels || ["fatal", "error"]);
|
||||
this.warnLevels = new Set(options.warnLevels || ["warn"]);
|
||||
this.filter = options.filter || (() => true);
|
||||
}
|
||||
|
||||
log(info, callback) {
|
||||
log(info: any, callback: Function) {
|
||||
if (!this.filter(info)) {
|
||||
return callback();
|
||||
}
|
||||
@@ -56,12 +66,12 @@ class ConsoleTransport extends Transport {
|
||||
/**
|
||||
* Formats winston log messages for the web console
|
||||
*/
|
||||
class WebConsoleFormat {
|
||||
constructor(options = {}) {
|
||||
this.options = options;
|
||||
}
|
||||
export class WebConsoleFormat {
|
||||
constructor(
|
||||
public options = {}
|
||||
) { }
|
||||
|
||||
transform(info, options) {
|
||||
transform(info: any, _options: {}) {
|
||||
let src = " ";
|
||||
if (info.host_id !== undefined) {
|
||||
src += `s:${info.host_name} - `;
|
||||
@@ -84,31 +94,41 @@ class WebConsoleFormat {
|
||||
|
||||
/**
|
||||
* Filter object for logs
|
||||
* @typedef {Object} LogFilter
|
||||
* @property {string} [maxLevel] -
|
||||
* Maximum log level to include. Higher levels are more verbose.
|
||||
* @property {boolean} [all] -
|
||||
* Include log entries from controller, all hosts and all instances.
|
||||
* @property {boolean} [controller] -
|
||||
* Include log entries from the controller.
|
||||
* @property {Array<number>} [hostIds] -
|
||||
* Include log entries for the given hosts and instances of those
|
||||
* hosts by id.
|
||||
* @property {Array<number>} [instanceIds] -
|
||||
* Include log entries for the given instances by id.
|
||||
*/
|
||||
export interface LogFilter {
|
||||
/**
|
||||
* Maximum log level to include. Higher levels are more verbose.
|
||||
*/
|
||||
maxLevel?: keyof typeof levels;
|
||||
/**
|
||||
* Include log entries from controller, all hosts and all instances.
|
||||
*/
|
||||
all?: boolean;
|
||||
/**
|
||||
* Include log entries from the controller.
|
||||
*/
|
||||
controller?: boolean;
|
||||
/**
|
||||
* Include log entries for the given hosts and instances of those
|
||||
* hosts by id.
|
||||
*/
|
||||
hostIds?: number[];
|
||||
/**
|
||||
* Include log entries for the given instances by id.
|
||||
*/
|
||||
instanceIds?: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create log filter by level and source.
|
||||
*
|
||||
* @param {LogFilter} filter -
|
||||
* @param filter -
|
||||
* Filter to filter log entries by.
|
||||
* @returns {function(object): boolean}
|
||||
* @returns
|
||||
* filter returning true for log entries that match it.
|
||||
* @static
|
||||
*/
|
||||
function logFilter({ all, controller, hostIds, instanceIds, maxLevel }) {
|
||||
return info => {
|
||||
export function logFilter({ all, controller, hostIds, instanceIds, maxLevel }: LogFilter) {
|
||||
return (info: any) => {
|
||||
// Note: reversed to filter out undefined levels
|
||||
if (maxLevel && !(levels[info.level] <= levels[maxLevel])) {
|
||||
return false;
|
||||
@@ -135,17 +155,23 @@ function logFilter({ all, controller, hostIds, instanceIds, maxLevel }) {
|
||||
};
|
||||
}
|
||||
|
||||
const logger = winston.createLogger({
|
||||
export const logger = winston.createLogger({
|
||||
level: "verbose",
|
||||
levels,
|
||||
format: winston.format.timestamp(),
|
||||
});
|
||||
}) as unknown as Logger;
|
||||
|
||||
module.exports = {
|
||||
ConsoleTransport,
|
||||
WebConsoleFormat,
|
||||
colors,
|
||||
levels,
|
||||
logFilter,
|
||||
logger,
|
||||
// Who in their right mind thought that log levels should be hard coded
|
||||
// into the type when you can define custom levels you want to support?
|
||||
export type Logger = Omit<winston.Logger,
|
||||
"error" | "warn" | "help" | "data" | "info" | "debug" | "prompt" | "http" | "verbose" | "input" | "silly" |
|
||||
"emerg" | "alert" | "crit" | "warning" | "notice"
|
||||
> & {
|
||||
fatal: winston.LeveledLogMethod,
|
||||
error: winston.LeveledLogMethod,
|
||||
warn: winston.LeveledLogMethod,
|
||||
audit: winston.LeveledLogMethod,
|
||||
info: winston.LeveledLogMethod,
|
||||
server: winston.LeveledLogMethod,
|
||||
verbose: winston.LeveledLogMethod,
|
||||
};
|
||||
@@ -5,24 +5,95 @@
|
||||
* @module lib/logging_utils
|
||||
*/
|
||||
"use strict";
|
||||
const events = require("events");
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const stream = require("stream");
|
||||
const util = require("util");
|
||||
const winston = require("winston");
|
||||
const Transport = require("winston-transport");
|
||||
const { LEVEL, MESSAGE } = require("triple-beam");
|
||||
const chalk = require("chalk");
|
||||
import fs from "fs-extra";
|
||||
import path from "path";
|
||||
import stream from "stream";
|
||||
import util from "util";
|
||||
import winston from "winston";
|
||||
import Transport from "winston-transport";
|
||||
import { LEVEL, MESSAGE } from "triple-beam";
|
||||
import chalk from "chalk";
|
||||
|
||||
const libData = require("./data");
|
||||
const libErrors = require("./errors");
|
||||
const libFileOps = require("./file_ops");
|
||||
const { levels, logFilter, logger } = require("./logging");
|
||||
const libStream = require("./stream");
|
||||
import * as libData from "./data";
|
||||
import * as libErrors from "./errors";
|
||||
import * as libFileOps from "./file_ops";
|
||||
import { type LogFilter, levels, logFilter, logger } from "./logging";
|
||||
import * as libStream from "./stream";
|
||||
import type { Link } from "./link";
|
||||
|
||||
const finished = util.promisify(stream.finished);
|
||||
|
||||
interface SecondsLogOutput {
|
||||
/** Where the message came from, one of "stdout" and "stderr". */
|
||||
source: "stdout" | "stderr";
|
||||
/** Timestamp format, one of "date", "seconds" and "none". */
|
||||
format: "seconds";
|
||||
/** Timestamp of the message. Not present if format is "none". */
|
||||
time: string;
|
||||
/** Type of message, one of "log", "action" and "generic". */
|
||||
type: "log";
|
||||
/** Log level for "log" type. i.e "Info" normally. */
|
||||
level: string;
|
||||
/** File reported for "log" type. */
|
||||
file: string;
|
||||
/** Main content of the line. */
|
||||
message: string;
|
||||
}
|
||||
interface SecondsGenericOutput {
|
||||
/** Where the message came from, one of "stdout" and "stderr". */
|
||||
source: "stdout" | "stderr";
|
||||
/** Timestamp format, one of "date", "seconds" and "none". */
|
||||
format: "seconds";
|
||||
/** Timestamp of the message. Not present if format is "none". */
|
||||
time: string;
|
||||
/** Type of message, one of "log", "action" and "generic". */
|
||||
type: "generic";
|
||||
/** Main content of the line. */
|
||||
message: string;
|
||||
}
|
||||
interface DateActionOutput {
|
||||
/** Where the message came from, one of "stdout" and "stderr". */
|
||||
source: "stdout" | "stderr";
|
||||
/** Timestamp format, one of "date", "seconds" and "none". */
|
||||
format: "date";
|
||||
/** Timestamp of the message. Not present if format is "none". */
|
||||
time: string;
|
||||
/** Type of message, one of "log", "action" and "generic". */
|
||||
type: "action";
|
||||
/** Kind of action for "action" type. i.e "CHAT" for chat. */
|
||||
action: string;
|
||||
/** Main content of the line. */
|
||||
message: string;
|
||||
}
|
||||
interface DateGenericOutput {
|
||||
/** Where the message came from, one of "stdout" and "stderr". */
|
||||
source: "stdout" | "stderr";
|
||||
/** Timestamp format, one of "date", "seconds" and "none". */
|
||||
format: "date";
|
||||
/** Timestamp of the message. Not present if format is "none". */
|
||||
time: string;
|
||||
/** Type of message, one of "log", "action" and "generic". */
|
||||
type: "generic";
|
||||
/** Main content of the line. */
|
||||
message: string;
|
||||
}
|
||||
interface UnformattedOutput {
|
||||
/** Where the message came from, one of "stdout" and "stderr". */
|
||||
source: "stdout" | "stderr";
|
||||
/** Timestamp format, one of "date", "seconds" and "none". */
|
||||
format: "none";
|
||||
/** Type of message, one of "log", "action" and "generic". */
|
||||
type: "generic";
|
||||
/** Main content of the line. */
|
||||
message: string;
|
||||
}
|
||||
export type ParsedFactorioOutput =
|
||||
| SecondsLogOutput
|
||||
| SecondsGenericOutput
|
||||
| DateActionOutput
|
||||
| DateGenericOutput
|
||||
| UnformattedOutput
|
||||
;
|
||||
|
||||
/**
|
||||
* Format a parsed Factorio message with colors
|
||||
@@ -30,11 +101,11 @@ const finished = util.promisify(stream.finished);
|
||||
* Formats a parsed Factorio output from lib/factorio into a readable
|
||||
* colorized output using terminal escape codes that can be printed.
|
||||
*
|
||||
* @param {Object} parsed - Parsed Factorio server output.
|
||||
* @returns {string} terminal colorized message.
|
||||
* @param parsed - Parsed Factorio server output.
|
||||
* @returns terminal colorized message.
|
||||
* @private
|
||||
*/
|
||||
function formatServerOutput(parsed) {
|
||||
function formatServerOutput(parsed: ParsedFactorioOutput) {
|
||||
let time = "";
|
||||
if (parsed.format === "seconds") {
|
||||
time = `${chalk.yellow(parsed.time.padStart(8))} `;
|
||||
@@ -71,15 +142,17 @@ function formatServerOutput(parsed) {
|
||||
|
||||
/**
|
||||
* Formats winston log messages for a character terminal.
|
||||
* @static
|
||||
*/
|
||||
class TerminalFormat {
|
||||
constructor(options = {}) {
|
||||
this.options = options;
|
||||
export class TerminalFormat {
|
||||
colorize: winston.Logform.Colorizer;
|
||||
|
||||
constructor(
|
||||
public options: object = {}
|
||||
) {
|
||||
this.colorize = winston.format.colorize(options);
|
||||
}
|
||||
|
||||
transform(info, options) {
|
||||
transform(info: any, options: { showTimestamp: boolean }) {
|
||||
info = this.colorize.transform(info, this.colorize.options);
|
||||
let ts = "";
|
||||
if (options.showTimestamp && info.timestamp) {
|
||||
@@ -112,17 +185,24 @@ class TerminalFormat {
|
||||
|
||||
/**
|
||||
* Sends logs over a lib/link connection.
|
||||
* @static
|
||||
*/
|
||||
class LinkTransport extends Transport {
|
||||
constructor(options) {
|
||||
export class LinkTransport extends Transport {
|
||||
link: Link;
|
||||
filter?: (info: any) => boolean;
|
||||
|
||||
constructor(
|
||||
options: Transport.TransportStreamOptions & {
|
||||
link: Link,
|
||||
filter?: (info: any) => boolean,
|
||||
}
|
||||
) {
|
||||
super(options);
|
||||
|
||||
this.link = options.link;
|
||||
this.filter = options.filter || null;
|
||||
this.filter = options.filter;
|
||||
}
|
||||
|
||||
log(info, callback) {
|
||||
log(info: any, callback: Function) {
|
||||
if (this.filter && !this.filter(info)) {
|
||||
return callback();
|
||||
}
|
||||
@@ -144,18 +224,26 @@ const logIndexVersion = 2;
|
||||
|
||||
/**
|
||||
* Keeps an index over a log directory to speed up queries to it
|
||||
* @static
|
||||
*/
|
||||
class LogIndex {
|
||||
constructor(logDirectory, serialized) {
|
||||
this.logDirectory = logDirectory;
|
||||
export class LogIndex {
|
||||
index: Map<string, {
|
||||
levels: Set<keyof typeof levels>,
|
||||
controller: boolean,
|
||||
hostIds: Set<number>,
|
||||
instanceIds: Set<number>,
|
||||
}>;
|
||||
|
||||
constructor(
|
||||
public logDirectory: string,
|
||||
serialized: any,
|
||||
) {
|
||||
this.index = new Map();
|
||||
|
||||
if (serialized.version !== logIndexVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let [file, serializedEntry] of Object.entries(serialized.files)) {
|
||||
for (let [file, serializedEntry] of Object.entries(serialized.files) as [string, any][]) {
|
||||
this.index.set(file, {
|
||||
levels: new Set(serializedEntry.levels),
|
||||
controller: serializedEntry.controller,
|
||||
@@ -190,13 +278,13 @@ class LogIndex {
|
||||
* data about previously indexed files saved with {@link
|
||||
* module:lib.LogIndex#save}.
|
||||
*
|
||||
* @param {string} logDirectory - Path to directory to load from.
|
||||
* @returns {Promise<module:lib.LogIndex>} loaded or created log index.
|
||||
* @param logDirectory - Path to directory to load from.
|
||||
* @returns loaded or created log index.
|
||||
*/
|
||||
static async load(logDirectory) {
|
||||
static async load(logDirectory: string) {
|
||||
try {
|
||||
let content = await fs.readFile(path.join(logDirectory, "index.json"));
|
||||
return new LogIndex(logDirectory, JSON.parse(content));
|
||||
return new LogIndex(logDirectory, JSON.parse(content.toString()));
|
||||
} catch (err) {
|
||||
if (err.code !== "ENOENT") {
|
||||
logger.warn(`Failed to load ${path.join(logDirectory, "index.json")}: ${err}`);
|
||||
@@ -218,19 +306,19 @@ class LogIndex {
|
||||
);
|
||||
}
|
||||
|
||||
async indexFile(file) {
|
||||
async indexFile(file: string) {
|
||||
let filePath = path.join(this.logDirectory, file);
|
||||
let lineStream = new libStream.LineSplitter({ readableObjectMode: true });
|
||||
let fileStream = fs.createReadStream(filePath);
|
||||
fileStream.pipe(lineStream);
|
||||
let entry = {
|
||||
levels: new Set(),
|
||||
levels: new Set() as Set<keyof typeof levels>,
|
||||
controller: false,
|
||||
hostIds: new Set(),
|
||||
instanceIds: new Set(),
|
||||
hostIds: new Set() as Set<number>,
|
||||
instanceIds: new Set() as Set<number>,
|
||||
};
|
||||
for await (let line of lineStream) {
|
||||
let info;
|
||||
let info: any;
|
||||
try {
|
||||
info = JSON.parse(line);
|
||||
} catch (err) {
|
||||
@@ -273,14 +361,14 @@ class LogIndex {
|
||||
/**
|
||||
* Check if a file includes entries for the given filter
|
||||
*
|
||||
* @param {string} file -
|
||||
* @param file -
|
||||
* Name of file in the log directory to check for.
|
||||
* @param {LogFilter} filter -
|
||||
* @param filter -
|
||||
* Filter to check index if file contains entries for.
|
||||
* @returns {boolean}
|
||||
* @returns
|
||||
* true if the file may contain entries included by the filter.
|
||||
*/
|
||||
filterIncludesFile(file, { maxLevel, all, controller, instanceIds, hostIds }) {
|
||||
filterIncludesFile(file: string, { maxLevel, all, controller, instanceIds, hostIds }: LogFilter) {
|
||||
let entry = this.index.get(file);
|
||||
if (!entry) {
|
||||
return true;
|
||||
@@ -314,35 +402,51 @@ class LogIndex {
|
||||
|
||||
/**
|
||||
* Filter object for querying logs
|
||||
* @typedef {Object} QueryLogFilter
|
||||
* @property {number} [limit=100] - Maximum number of entries to return.
|
||||
* @property {string} [order="asc"] -
|
||||
* Return entries in ascending ("asc") date order or desceding ("desc")
|
||||
* date order.
|
||||
* @property {string} [maxLevel] -
|
||||
* Maximum log level to include. Higher levels are more verbose.
|
||||
* @property {boolean} [all] -
|
||||
* Include log entries from controller, all hosts and all instances.
|
||||
* @property {boolean} [controller] -
|
||||
* Include log entries from the controller.
|
||||
* @property {Array<number>} [hostIds] -
|
||||
* Include log entries for the given hosts and instances of those
|
||||
* hosts by id.
|
||||
* @property {Array<number>} [instanceIds] -
|
||||
* Include log entries for the given instances by id.
|
||||
*/
|
||||
export interface QueryLogFilter {
|
||||
/**
|
||||
* Maximum number of entries to return. Defaults to 100.
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* Return entries in ascending ("asc") date order or desceding ("desc")
|
||||
* date order. Defaults to "asc".
|
||||
*/
|
||||
order: "asc" | "desc";
|
||||
/**
|
||||
* Maximum log level to include. Higher levels are more verbose.
|
||||
*/
|
||||
maxLevel: keyof typeof levels;
|
||||
/**
|
||||
* Include log entries from controller, all hosts and all instances.
|
||||
*/
|
||||
all: boolean;
|
||||
/**
|
||||
* Include log entries from the controller.
|
||||
*/
|
||||
controller: boolean;
|
||||
/**
|
||||
* Include log entries for the given hosts and instances of those
|
||||
* hosts by id.
|
||||
*/
|
||||
hostIds: number[];
|
||||
/**
|
||||
* Include log entries for the given instances by id.
|
||||
*/
|
||||
instanceIds: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Query log directory
|
||||
*
|
||||
* @param {string} logDirectory - path to directory with logs.
|
||||
* @param {QueryLogFilter} filter -
|
||||
* @param logDirectory - path to directory with logs.
|
||||
* @param filter -
|
||||
* Filter to limit logs by.
|
||||
* @param {module:lib.LogIndex=} index -
|
||||
* @param index -
|
||||
* Index to speed up query with.
|
||||
* @returns {Promise<Array<Object>>} log entries matching the filter
|
||||
* @returns log entries matching the filter
|
||||
*/
|
||||
async function queryLog(logDirectory, filter, index) {
|
||||
export async function queryLog(logDirectory: string, filter: QueryLogFilter, index: LogIndex) {
|
||||
let files = (await fs.readdir(logDirectory)).filter(entry => logFileGlob.test(entry));
|
||||
|
||||
filter = {
|
||||
@@ -363,8 +467,8 @@ async function queryLog(logDirectory, filter, index) {
|
||||
continue;
|
||||
}
|
||||
let filePath = path.join(logDirectory, file);
|
||||
let fileStream;
|
||||
let lineStream;
|
||||
let fileStream: fs.ReadStream;
|
||||
let lineStream: libStream.LineSplitter;
|
||||
if (filter.order === "asc") {
|
||||
lineStream = new libStream.LineSplitter({ readableObjectMode: true });
|
||||
fileStream = fs.createReadStream(filePath);
|
||||
@@ -373,7 +477,7 @@ async function queryLog(logDirectory, filter, index) {
|
||||
fileStream = await libStream.createReverseReadStream(filePath);
|
||||
}
|
||||
lineStream.on("data", line => {
|
||||
let info;
|
||||
let info: any;
|
||||
try {
|
||||
info = JSON.parse(line);
|
||||
} catch (err) {
|
||||
@@ -403,27 +507,18 @@ async function queryLog(logDirectory, filter, index) {
|
||||
return log;
|
||||
}
|
||||
|
||||
function handleUnhandledErrors() {
|
||||
export function handleUnhandledErrors() {
|
||||
/* eslint-disable node/no-process-exit */
|
||||
process.on("uncaughtException", err => {
|
||||
logger.fatal(`Uncaught exception:\n${err.stack}`);
|
||||
process.exit(1);
|
||||
});
|
||||
process.on("unhandledRejection", (reason, promise) => {
|
||||
process.on("unhandledRejection", (reason: Error, _promise) => {
|
||||
logger.fatal(`Unhandled rejection:\n${reason.stack ? reason.stack : reason}`);
|
||||
process.exit(1);
|
||||
});
|
||||
/* eslint-enable node/no-process-exit */
|
||||
}
|
||||
|
||||
const _formatServerOutput = formatServerOutput;
|
||||
module.exports = {
|
||||
TerminalFormat,
|
||||
LinkTransport,
|
||||
LogIndex,
|
||||
handleUnhandledErrors,
|
||||
queryLog,
|
||||
|
||||
// for testing only
|
||||
_formatServerOutput,
|
||||
};
|
||||
// for testing only
|
||||
export const _formatServerOutput = formatServerOutput;
|
||||
@@ -2,15 +2,14 @@
|
||||
* Utilities for dealing with Lua
|
||||
* @module lib/lua_tools
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Escapes a string for inclusion into a lua string
|
||||
*
|
||||
* @param {string} content - String to escape.
|
||||
* @returns {string} Escaped string.
|
||||
* @param content - String to escape.
|
||||
* @returns Escaped string.
|
||||
*/
|
||||
function escapeString(content) {
|
||||
export function escapeString(content: string) {
|
||||
return content
|
||||
.replace(/\\/g, "\\\\")
|
||||
.replace(/"/g, '\\"')
|
||||
@@ -20,7 +19,3 @@ function escapeString(content) {
|
||||
.replace(/\r/g, "\\r")
|
||||
;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
escapeString,
|
||||
};
|
||||
@@ -2,18 +2,61 @@
|
||||
* Plugin interfaces and utilities.
|
||||
* @module lib/plugin
|
||||
*/
|
||||
"use strict";
|
||||
import * as libHelpers from "./helpers";
|
||||
import type { Logger } from "./logging";
|
||||
import type { ConfigGroup } from "./config";
|
||||
import type { CollectorResult } from "./prometheus";
|
||||
import type { ParsedFactorioOutput } from "./logging_utils";
|
||||
import type { ModPack, ModInfo } from "./data";
|
||||
import type { Link } from "./link";
|
||||
import type { CommandTree } from "./command";
|
||||
|
||||
const helpers = require("./helpers");
|
||||
|
||||
/* TODO fix when converting to ts
|
||||
/**
|
||||
* Conceptual base for controller and instance plugins.
|
||||
* @typedef {
|
||||
* BaseControllerPlugin
|
||||
* |BaseInstancePlugin
|
||||
* |BaseControlPlugin
|
||||
* } BasePlugin
|
||||
*/
|
||||
export type BasePlugin =
|
||||
| BaseControllerPlugin
|
||||
| BaseInstancePlugin
|
||||
| BaseControlPlugin
|
||||
| BaseWebPlugin
|
||||
;
|
||||
|
||||
// TODO Add proper typing for plugins
|
||||
export type PluginInfo = any;
|
||||
type Controller = any;
|
||||
type Instance = any;
|
||||
type Host = any;
|
||||
type InstanceInfo = any;
|
||||
type HostConnection = any;
|
||||
type ControlConnection = any;
|
||||
type Event = any;
|
||||
type React = any;
|
||||
|
||||
/**
|
||||
* Information about the event.
|
||||
*/
|
||||
interface PlayerEvent {
|
||||
type: "join" | "leave" | "import";
|
||||
/** Name of the player that joined/left */
|
||||
name: string,
|
||||
/**
|
||||
* Only present for type "leave". Reason for player leaving the
|
||||
* game, one of the possible reasons in defines.disconnect_reason
|
||||
* or "server_quit" if the server exits while the player is online.
|
||||
*/
|
||||
reason?: string;
|
||||
}
|
||||
type InstanceStatus =
|
||||
| "unassigned"
|
||||
| "unknown"
|
||||
| "stopped"
|
||||
| "starting"
|
||||
| "running"
|
||||
| "stopping"
|
||||
| "creating_save"
|
||||
| "exporting_data"
|
||||
| "deleted"
|
||||
;
|
||||
|
||||
/**
|
||||
* Base class for instance plugins
|
||||
@@ -27,40 +70,43 @@ const helpers = require("./helpers");
|
||||
* Instances may be started and stopped many times, and many instances may
|
||||
* be running at the same time, each of which will have their own instance
|
||||
* of the InstancePlugin class.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
class BaseInstancePlugin {
|
||||
constructor(info, instance, host) {
|
||||
export class BaseInstancePlugin {
|
||||
/**
|
||||
* Logger for this plugin
|
||||
*
|
||||
* Instance of winston Logger for sending log messages from this
|
||||
* plugin. Supported methods and their corresponding log levels are
|
||||
* `error`, `warn`, `audit`, `info` and `verbose`.
|
||||
*/
|
||||
logger: Logger;
|
||||
|
||||
private _pendingRconMessages: {
|
||||
resolve: (result: string) => void,
|
||||
reject: (err: Error) => void,
|
||||
message: string,
|
||||
expectEmpty: boolean,
|
||||
}[] = [];
|
||||
private _sendingRconMessages = false;
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* The plugin's own info module
|
||||
*/
|
||||
this.info = info;
|
||||
|
||||
public info: PluginInfo,
|
||||
/**
|
||||
* Instance the plugin started for
|
||||
* @type {module:host/src/Instance}
|
||||
*/
|
||||
this.instance = instance;
|
||||
|
||||
public instance: Instance,
|
||||
/**
|
||||
* Host running the instance
|
||||
*
|
||||
* With the exepction of accessing the host's config you should
|
||||
* avoid ineracting with the host object directly.
|
||||
*
|
||||
* @type {module:host/src/Host}
|
||||
*/
|
||||
this.host = host;
|
||||
|
||||
/**
|
||||
* Logger for this plugin
|
||||
*
|
||||
* Instance of winston Logger for sending log messages from this
|
||||
* plugin. Supported methods and their corresponding log levels are
|
||||
* `error`, `warn`, `audit`, `info` and `verbose`.
|
||||
*/
|
||||
this.logger = instance.logger.child({ plugin: this.info.name });
|
||||
public host: Host,
|
||||
) {
|
||||
this.logger = instance.logger.child({ plugin: this.info.name }) as unknown as Logger;
|
||||
|
||||
this._pendingRconMessages = [];
|
||||
this._sendingRconMessages = false;
|
||||
@@ -77,12 +123,12 @@ class BaseInstancePlugin {
|
||||
* Invoked after the value of the config field given by `field` has
|
||||
* changed.
|
||||
*
|
||||
* @param {module:lib.ConfigGroup} group -
|
||||
* @param group -
|
||||
* The group who's field got changed.
|
||||
* @param {string} field - Name of the field that changed.
|
||||
* @param {*} prev - The previous value of the field.
|
||||
* @param field - Name of the field that changed.
|
||||
* @param prev - The previous value of the field.
|
||||
*/
|
||||
async onInstanceConfigFieldChanged(group, field, prev) { }
|
||||
async onInstanceConfigFieldChanged(group: ConfigGroup, field: string, prev: unknown) { }
|
||||
|
||||
/**
|
||||
* Called before collecting Prometheus metrics
|
||||
@@ -97,9 +143,9 @@ class BaseInstancePlugin {
|
||||
* the Gauges/Counters/etc in onMetric and let Clusterio deal will
|
||||
* collecting the values.
|
||||
*
|
||||
* @returns {*} an async iterator of prometheus metric results or undefined.
|
||||
* @returns an async iterator of prometheus metric results or undefined.
|
||||
*/
|
||||
async onMetrics() { }
|
||||
async onMetrics(): Promise<void | AsyncIterator<CollectorResult>> { }
|
||||
|
||||
/**
|
||||
* Called after the Factorio server is started
|
||||
@@ -130,24 +176,10 @@ class BaseInstancePlugin {
|
||||
* stderr. The output is a parsed form of the line and contains the
|
||||
* following properties:
|
||||
*
|
||||
* @param {Object} parsed - parsed server output.
|
||||
* @param {string} parsed.source -
|
||||
* Where the message came from, one of "stdout" and "stderr".
|
||||
* @param {string} parsed.format -
|
||||
* Timestamp format, one of "date", "seconds" and "none".
|
||||
* @param {string=} parsed.time -
|
||||
* Timestamp of the message. Not present if format is "none".
|
||||
* @param {string} parsed.type -
|
||||
* Type of message, one of "log", "action" and "generic".
|
||||
* @param {string=} parsed.level -
|
||||
* Log level for "log" type. i.e "Info" normally.
|
||||
* @param {string=} parsed.file - File reported for "log" type.
|
||||
* @param {string=} parsed.action -
|
||||
* Kind of action for "action" type. i.e "CHAT" for chat.
|
||||
* @param {string} parsed.message - Main content of the line.
|
||||
* @param {string} line - raw line of server output.
|
||||
* @param parsed - parsed server output.
|
||||
* @param line - raw line of server output.
|
||||
*/
|
||||
async onOutput(parsed, line) { }
|
||||
async onOutput(parsed: ParsedFactorioOutput, line: string) { }
|
||||
|
||||
/**
|
||||
* Called when an event on the controller connection happens
|
||||
@@ -181,9 +213,9 @@ class BaseInstancePlugin {
|
||||
* send any messages that goes to or via the controller after the
|
||||
* connection has been closed and before a new one is established.
|
||||
*
|
||||
* @param {string} event - one of connect, drop, resume and close
|
||||
* @param event - one of connect, drop, resume and close
|
||||
*/
|
||||
onControllerConnectionEvent(event) { }
|
||||
onControllerConnectionEvent(event: "connect" | "drop" | "resume" | "close") { }
|
||||
|
||||
/**
|
||||
* Called when the controller is preparing to disconnect from the host
|
||||
@@ -195,25 +227,19 @@ class BaseInstancePlugin {
|
||||
* Plugins must stop sending messages to the controller, or are forwarded
|
||||
* via the controller after the prepare disconnect has been handled.
|
||||
*
|
||||
* @param {module:controller/src/HostConnection} connection -
|
||||
* @param connection -
|
||||
* The connection to the host preparing to disconnect.
|
||||
*/
|
||||
async onPrepareControllerDisconnect(connection) { }
|
||||
async onPrepareControllerDisconnect(connection: HostConnection) { }
|
||||
|
||||
/**
|
||||
* Called when a player joins or leaves the game
|
||||
*
|
||||
* Invoked when a player either joins or leaves the instance.
|
||||
*
|
||||
* @param {Object} event - Information about the event.
|
||||
* @param {string} event.type - Either "join", "leave" or "import".
|
||||
* @param {string} event.name - Name of the player that joined/left.
|
||||
* @param {string=} event.reason -
|
||||
* Only present for type "leave". Reason for player leaving the
|
||||
* game, one of the possible reasons in defines.disconnect_reason
|
||||
* or "server_quit" if the server exits while the player is online.
|
||||
* @param event - Information about the event.
|
||||
*/
|
||||
async onPlayerEvent(event) { }
|
||||
async onPlayerEvent(event: PlayerEvent) { }
|
||||
|
||||
/**
|
||||
* Send RCON message to instance
|
||||
@@ -227,13 +253,13 @@ class BaseInstancePlugin {
|
||||
* running some time after `onStart` and some time before `onStop` is
|
||||
* invoked.
|
||||
*
|
||||
* @param {string} message - message to send to server over RCON.
|
||||
* @param {boolean=} [expectEmpty=false] -
|
||||
* @param message - message to send to server over RCON.
|
||||
* @param expectEmpty -
|
||||
* if true throw if the response is not empty. Useful for detecting
|
||||
* errors that might have been sent in response.
|
||||
* @returns {Promise<string>} response from server.
|
||||
* @returns response from server.
|
||||
*/
|
||||
async sendRcon(message, expectEmpty=false) {
|
||||
async sendRcon(message: string, expectEmpty=false): Promise<string> {
|
||||
return await this.instance.sendRcon(message, expectEmpty, this.info.name);
|
||||
}
|
||||
|
||||
@@ -252,14 +278,14 @@ class BaseInstancePlugin {
|
||||
* running some time after `onStart` and some time before `onStop` is
|
||||
* invoked.
|
||||
*
|
||||
* @param {string} message - message to send to server over RCON.
|
||||
* @param {boolean=} [expectEmpty=false] -
|
||||
* @param message - message to send to server over RCON.
|
||||
* @param [expectEmpty=false] -
|
||||
* if true throw if the response is not empty. Useful for detecting
|
||||
* errors that might have been sent in response.
|
||||
* @returns {Promise<string>} response from server.
|
||||
* @returns response from server.
|
||||
*/
|
||||
async sendOrderedRcon(message, expectEmpty=false) {
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
async sendOrderedRcon(message: string, expectEmpty=false) {
|
||||
let promise = new Promise<string>((resolve, reject) => {
|
||||
this._pendingRconMessages.push({resolve, reject, message, expectEmpty});
|
||||
});
|
||||
if (!this._sendingRconMessages) {
|
||||
@@ -291,28 +317,24 @@ class BaseInstancePlugin {
|
||||
* To be discovered the class must be exported under the name `ControllerPlugin`
|
||||
* in the module specified by the `controllerEntrypoint` in the plugin's info.js
|
||||
* file.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
class BaseControllerPlugin {
|
||||
constructor(info, controller, metrics, logger) {
|
||||
this.info = info;
|
||||
export class BaseControllerPlugin {
|
||||
/**
|
||||
* Logger for this plugin
|
||||
*
|
||||
* Instance of winston Logger for sending log messages from this
|
||||
* plugin. Supported methods and their corresponding log levels are
|
||||
* `error`, `warn`, `audit`, `info` and `verbose`.
|
||||
*/
|
||||
logger: Logger;
|
||||
|
||||
/**
|
||||
* Controller
|
||||
* @type {module:controller/src/Controller}
|
||||
*/
|
||||
this.controller = controller;
|
||||
this.metrics = metrics;
|
||||
|
||||
/**
|
||||
* Logger for this plugin
|
||||
*
|
||||
* Instance of winston Logger for sending log messages from this
|
||||
* plugin. Supported methods and their corresponding log levels are
|
||||
* `error`, `warn`, `audit`, `info` and `verbose`.
|
||||
*/
|
||||
this.logger = logger.child({ plugin: this.info.name });
|
||||
constructor(
|
||||
public info: PluginInfo,
|
||||
public controller: Controller,
|
||||
public metrics: any[],
|
||||
logger: Logger
|
||||
) {
|
||||
this.logger = logger.child({ plugin: this.info.name }) as unknown as Logger;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,11 +376,11 @@ class BaseControllerPlugin {
|
||||
* the case of network outages if reconnect fails to re-establish the
|
||||
* session between the controller and the host.
|
||||
*
|
||||
* @param {module:controller/src/InstanceInfo} instance -
|
||||
* @param instance -
|
||||
* The instance that changed.
|
||||
* @param {?string} prev - the previous status of the instance.
|
||||
* @param prev - the previous status of the instance.
|
||||
*/
|
||||
async onInstanceStatusChanged(instance, prev) { }
|
||||
async onInstanceStatusChanged(instance: InstanceInfo, prev?: InstanceStatus) { }
|
||||
|
||||
/**
|
||||
* Called when the value of a controller config field changed.
|
||||
@@ -366,12 +388,12 @@ class BaseControllerPlugin {
|
||||
* Invoked after the value of the config field given by `field` has
|
||||
* changed on the controller.
|
||||
*
|
||||
* @param {module:lib.ConfigGroup} group -
|
||||
* @param group -
|
||||
* The group who's field got changed.
|
||||
* @param {string} field - Name of the field that changed.
|
||||
* @param {*} prev - The previous value of the field.
|
||||
* @param field - Name of the field that changed.
|
||||
* @param prev - The previous value of the field.
|
||||
*/
|
||||
async onControllerConfigFieldChanged(group, field, prev) { }
|
||||
async onControllerConfigFieldChanged(group: ConfigGroup, field: string, prev: unknown) { }
|
||||
|
||||
/**
|
||||
* Called when the value of an instance config field changed.
|
||||
@@ -379,14 +401,14 @@ class BaseControllerPlugin {
|
||||
* Invoked after the value of the config field given by `field` has
|
||||
* changed on an instance.
|
||||
*
|
||||
* @param {module:controller/src/InstanceInfo} instance -
|
||||
* @param instance -
|
||||
* The instance the config changed on.
|
||||
* @param {module:lib.ConfigGroup} group -
|
||||
* @param group -
|
||||
* The group who's field got changed.
|
||||
* @param {string} field - Name of the field that changed.
|
||||
* @param {*} prev - The previous value of the field.
|
||||
* @param field - Name of the field that changed.
|
||||
* @param prev - The previous value of the field.
|
||||
*/
|
||||
async onInstanceConfigFieldChanged(instance, group, field, prev) { }
|
||||
async onInstanceConfigFieldChanged(instance: InstanceInfo, group: ConfigGroup, field: string, prev: unknown) { }
|
||||
|
||||
/**
|
||||
* Called before collecting Prometheus metrics
|
||||
@@ -401,9 +423,9 @@ class BaseControllerPlugin {
|
||||
* the Gauges/Counters/etc in onMetric and let Clusterio deal will
|
||||
* collecting the values.
|
||||
*
|
||||
* @returns {*} an async iterator of prometheus metric results or undefined.
|
||||
* @returns an async iterator of prometheus metric results or undefined.
|
||||
*/
|
||||
async onMetrics() { }
|
||||
async onMetrics(): Promise<void | AsyncIterator<CollectorResult>> { }
|
||||
|
||||
/**
|
||||
* Called when the controller is shutting down
|
||||
@@ -439,11 +461,11 @@ class BaseControllerPlugin {
|
||||
*
|
||||
* Invoked when the connection to the host has been closed.
|
||||
*
|
||||
* @param {module:controller/src/HostConnection} connection -
|
||||
* @param connection -
|
||||
* The connection the event occured on.
|
||||
* @param {string} event - one of connect, drop, resume and close
|
||||
* @param event - one of connect, drop, resume and close
|
||||
*/
|
||||
onHostConnectionEvent(connection, event) { }
|
||||
onHostConnectionEvent(connection: HostConnection, event: "connect" | "drop" | "resume" | "close") { }
|
||||
|
||||
/**
|
||||
* Called when an avent on a control connection happens
|
||||
@@ -475,11 +497,11 @@ class BaseControllerPlugin {
|
||||
*
|
||||
* Invoked when the connection to the control has been closed.
|
||||
*
|
||||
* @param {module:controller/src/ControlConnection} connection -
|
||||
* @param connection -
|
||||
* The connection the event occured on.
|
||||
* @param {string} event - one of connect, drop, resume, and close.
|
||||
* @param event - one of connect, drop, resume, and close.
|
||||
*/
|
||||
onControlConnectionEvent(connection, event) { }
|
||||
onControlConnectionEvent(connection: ControlConnection, event: "connect" | "drop" | "resume" | "close") { }
|
||||
|
||||
/**
|
||||
* Called when a host is preparing to disconnect from the controller
|
||||
@@ -490,10 +512,10 @@ class BaseControllerPlugin {
|
||||
* Plugins must stop sending messages to the host in question after the
|
||||
* prepare disconnect has been handled.
|
||||
*
|
||||
* @param {module:controller/src/HostConnection} connection -
|
||||
* @param connection -
|
||||
* The connection to the host preparing to disconnect.
|
||||
*/
|
||||
async onPrepareHostDisconnect(connection) { }
|
||||
async onPrepareHostDisconnect(connection: HostConnection) { }
|
||||
|
||||
/**
|
||||
* Called when a mod pack is updated
|
||||
@@ -504,9 +526,9 @@ class BaseControllerPlugin {
|
||||
* If the mod pack has been deleted its `.isDeleted` property will be
|
||||
* true.
|
||||
*
|
||||
* @param {module:lib.ModPack} modPack - Mod pack that updated.
|
||||
* @param modPack - Mod pack that updated.
|
||||
*/
|
||||
async onModPackUpdated(modPack) { }
|
||||
async onModPackUpdated(modPack: ModPack) { }
|
||||
|
||||
/**
|
||||
* Called when a mod stored on the controller is updated
|
||||
@@ -516,9 +538,9 @@ class BaseControllerPlugin {
|
||||
*
|
||||
* If a mod has been deleted its `.isDeleted` property will be true.
|
||||
*
|
||||
* @param {module:lib.ModInfo} mod - Mod that updated.
|
||||
* @param mod - Mod that updated.
|
||||
*/
|
||||
async onModUpdated(mod) { }
|
||||
async onModUpdated(mod: ModInfo) { }
|
||||
|
||||
/**
|
||||
* Called when a player joins or leaves an instance
|
||||
@@ -526,17 +548,11 @@ class BaseControllerPlugin {
|
||||
* Invoked when a player either joins or leaves an instance in the
|
||||
* cluster.
|
||||
*
|
||||
* @param {module:controller/src/InstanceInfo} instance -
|
||||
* @param instance -
|
||||
* The instance it occured on.
|
||||
* @param {Object} event - Information about the event.
|
||||
* @param {string} event.type - Either "join", "leave" or "import".
|
||||
* @param {string} event.name - Name of the player that joined/left.
|
||||
* @param {string=} event.reason -
|
||||
* Only present for type "leave". Reason for player leaving the
|
||||
* game, one of the possible reasons in defines.disconnect_reason
|
||||
* or "server_quit" if the server exits while the player is online.
|
||||
* @param event - Information about the event.
|
||||
*/
|
||||
async onPlayerEvent(instance, event) { }
|
||||
async onPlayerEvent(instance: InstanceInfo, event: PlayerEvent) { }
|
||||
|
||||
/**
|
||||
* Broadcast event to all connected hosts
|
||||
@@ -545,20 +561,15 @@ class BaseControllerPlugin {
|
||||
* This does not include hosts that are in the process of closing the
|
||||
* connection, which typically happens when they are shutting down.
|
||||
*
|
||||
* @param {*} event - Event to send
|
||||
* @param {Object} data - Data ta pass with the event.
|
||||
* @param event - Event to send
|
||||
*/
|
||||
broadcastEventToHosts(event, data={}) {
|
||||
if (event.type !== "event") {
|
||||
this.logger.warn(`Ignoring broadcast of ${event.type}`);
|
||||
return;
|
||||
}
|
||||
broadcastEventToHosts(event: Event) {
|
||||
for (let hostConnection of this.controller.wsServer.hostConnections.values()) {
|
||||
if (
|
||||
!hostConnection.connector.closing
|
||||
&& (!event.plugin || hostConnection.plugins.has(event.plugin))
|
||||
&& (!event.constructor.plugin || hostConnection.plugins.has(event.constructor.plugin))
|
||||
) {
|
||||
event.send(hostConnection, data);
|
||||
hostConnection.send(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -571,24 +582,25 @@ class BaseControllerPlugin {
|
||||
* clusterioctl in order to extend its functionallity. To be discovered the
|
||||
* class must be exported under the name `ControlPlugin` in the module
|
||||
* specified by the `controlEntrypoint` in the plugin's info.js file.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
class BaseControlPlugin {
|
||||
constructor(info, logger) {
|
||||
export class BaseControlPlugin {
|
||||
/**
|
||||
* Logger for this plugin
|
||||
*
|
||||
* Instance of winston Logger for sending log messages from this
|
||||
* plugin. Supported methods and their corresponding log levels are
|
||||
* `error`, `warn`, `audit`, `info` and `verbose`.
|
||||
*/
|
||||
logger: Logger;
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* The plugin's own info module
|
||||
*/
|
||||
this.info = info;
|
||||
|
||||
/**
|
||||
* Logger for this plugin
|
||||
*
|
||||
* Instance of winston Logger for sending log messages from this
|
||||
* plugin. Supported methods and their corresponding log levels are
|
||||
* `error`, `warn`, `audit`, `info` and `verbose`.
|
||||
*/
|
||||
this.logger = logger.child({ plugin: this.info.name });
|
||||
public info: PluginInfo,
|
||||
logger: Logger,
|
||||
) {
|
||||
this.logger = logger.child({ plugin: this.info.name }) as unknown as Logger;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -601,136 +613,153 @@ class BaseControlPlugin {
|
||||
*
|
||||
* Invoked by clusterioctl to let plugins add commands. `rootCommand` is
|
||||
* the top level command node which the plugin should add its own {@link
|
||||
* module:lib.CommandTree} to.
|
||||
* CommandTree} to.
|
||||
*
|
||||
* @param {module:lib.CommandTree} rootCommand -
|
||||
* @param rootCommand -
|
||||
* Root of the clusterioctl command tree.
|
||||
*/
|
||||
async addCommands(rootCommand) { }
|
||||
async addCommands(rootCommand: CommandTree) { }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Plugin supplied login form
|
||||
* @typedef {Object} LoginForm
|
||||
* @property {string} name -
|
||||
* Internal name of the login form, this should start with the
|
||||
* plugin name followed by a dot.
|
||||
* @property {string} title -
|
||||
* Name displayed above this form in the login window.
|
||||
* @property {React.Component} Component -
|
||||
* React component that's rendered for this login form. This is
|
||||
* supplied the setToken function via its props which should be
|
||||
* called when an authentication token is aquired via this
|
||||
* form.
|
||||
*/
|
||||
export interface PluginLoginForm {
|
||||
/**
|
||||
* Internal name of the login form, this should start with the
|
||||
* plugin name followed by a dot.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/** Name displayed above this form in the login window. */
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* React component that's rendered for this login form. This is
|
||||
* supplied the setToken function via its props which should be called
|
||||
* when an authentication token is aquired via this form.
|
||||
*/
|
||||
Component: React["Component"];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Plugin supplied pages
|
||||
* @typedef {Object} Page
|
||||
* @property {string} path - URL path to this page.
|
||||
* @property {string=} sidebarPath -
|
||||
* If present and this path matches one of the pages in the sidebar it
|
||||
* will cause that sidebar entry to be highlighted as active.
|
||||
* @property {string=} sidebarGroup -
|
||||
* If present group this entry under a group of the given name in the
|
||||
* sidebar.
|
||||
* @property {string=} sidebarName -
|
||||
* If present creates an entry in the sidebar for this page with the
|
||||
* given text.
|
||||
* @property {ReactNode} content -
|
||||
* A react node which is rendered when this page is navigated to.
|
||||
* Should render a PageLayout.
|
||||
*/
|
||||
export interface PluginPage {
|
||||
/** URL path to this page. */
|
||||
path: string;
|
||||
/**
|
||||
* If present and this path matches one of the pages in the sidebar it
|
||||
* will cause that sidebar entry to be highlighted as active.
|
||||
*/
|
||||
sidebarPath?: string;
|
||||
/**
|
||||
* If present group this entry under a group of the given name in the
|
||||
* sidebar.
|
||||
*/
|
||||
sidebarGroup?: string;
|
||||
/**
|
||||
* If present creates an entry in the sidebar for this page with the
|
||||
* given text.
|
||||
*/
|
||||
sidebarName?: string;
|
||||
/**
|
||||
* A react node which is rendered when this page is navigated to.
|
||||
* Should render a PageLayout.
|
||||
*/
|
||||
content?: React["ReactNode"];
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for web interface plugins
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
class BaseWebPlugin {
|
||||
constructor(container, packageData, info, logger) {
|
||||
export class BaseWebPlugin {
|
||||
/**
|
||||
* Contents of the plugin's package.json file
|
||||
*/
|
||||
package: any;
|
||||
/**
|
||||
* Control link to the controller, not available until the
|
||||
* connect event in onControllerConnectionEvent is signaled.
|
||||
*/
|
||||
control?: Link;
|
||||
/**
|
||||
* Logger for this plugin
|
||||
*
|
||||
* Instance of winston Logger for sending log messages from this
|
||||
* plugin. Supported methods and their corresponding log levels are
|
||||
* `error`, `warn`, `audit`, `info` and `verbose`.
|
||||
*/
|
||||
logger: Logger;
|
||||
/**
|
||||
* List of login forms provided by this plugin
|
||||
*/
|
||||
loginForms: PluginLoginForm[] = [];
|
||||
/**
|
||||
* List of pages provided by this plugin
|
||||
*/
|
||||
pages: PluginPage[] = [];
|
||||
/**
|
||||
* Extra react component to add to core components
|
||||
*
|
||||
* Interface to augment core components of the web UI. Setting a
|
||||
* component as one of the supported properties of this object will
|
||||
* cause the web UI to render it when displaying that component,
|
||||
* usually at the end. Each component will receive a `plugin` param
|
||||
* which is the instance of the web plugin that contained the
|
||||
* component extra.
|
||||
*/
|
||||
componentExtra: {
|
||||
/** Placed at the end of the controller page. */
|
||||
ControllerPage?: React["ComponentType"],
|
||||
/** Placed at the end of the hosts list page. */
|
||||
HostsPage?: React["ComponentType"],
|
||||
/**
|
||||
* Placed at the end of each host page. Takes a `host` param which
|
||||
* is the host the page is displayed for.
|
||||
*/
|
||||
HostViewPage?: React["ComponentType"],
|
||||
/** Placed at the end of the instance list page. */
|
||||
InstancesPage?: React["ComponentType"],
|
||||
/**
|
||||
* Placed at the end of each instance page. Takes an `instance`
|
||||
* param which is the instance the page is displayed for.
|
||||
*/
|
||||
InstanceViewPage?: React["ComponentType"],
|
||||
/** Placed at the end of the users list page. */
|
||||
UsersPage?: React["ComponentType"],
|
||||
/**
|
||||
* Placed at the end of each user page. Takes a `user` param which
|
||||
* is the user object the page is displayed for.
|
||||
*/
|
||||
UserViewPage?: React["ComponentType"],
|
||||
/** Placed at the end of the roles list page. */
|
||||
RolesPage?: React["ComponentType"],
|
||||
/**
|
||||
* Placed at the end of each role page. Takes a `role` param which
|
||||
* is the role object the page is displayed for.
|
||||
*/
|
||||
RoleViewPage?: React["ComponentType"],
|
||||
} = {};
|
||||
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* Webpack container for this plugin
|
||||
*/
|
||||
this.container = container;
|
||||
|
||||
/**
|
||||
* Contents of the plugin's package.json file
|
||||
* @type {Object}
|
||||
*/
|
||||
this.package = packageData;
|
||||
|
||||
public container: any,
|
||||
packageData: any,
|
||||
/**
|
||||
* The plugin's own info module
|
||||
*/
|
||||
this.info = info;
|
||||
|
||||
/**
|
||||
* Control link to the controller, not available until the
|
||||
* connect event in onControllerConnectionEvent is signaled.
|
||||
* @type {?module:lib.Link}
|
||||
*/
|
||||
this.control = null;
|
||||
|
||||
/**
|
||||
* Logger for this plugin
|
||||
*
|
||||
* Instance of winston Logger for sending log messages from this
|
||||
* plugin. Supported methods and their corresponding log levels are
|
||||
* `error`, `warn`, `audit`, `info` and `verbose`.
|
||||
*/
|
||||
this.logger = logger.child({ plugin: this.info.name });
|
||||
|
||||
/**
|
||||
* List of login forms provided by this plugin
|
||||
*
|
||||
* @type {Array<module:lib~LoginForm>}
|
||||
*/
|
||||
this.loginForms = [];
|
||||
|
||||
/**
|
||||
* List of pages provided by this plugin
|
||||
*
|
||||
* @type {Array<module:lib~Page>}
|
||||
*/
|
||||
this.pages = [];
|
||||
|
||||
/**
|
||||
* Extra react component to add to core components
|
||||
*
|
||||
* Interface to augment core components of the web UI. Setting a
|
||||
* component as one of the supported properties of this object will
|
||||
* cause the web UI to render it when displaying that component,
|
||||
* usually at the end. Each component will receive a `plugin` param
|
||||
* which is the instance of the web plugin that contained the
|
||||
* component extra.
|
||||
*
|
||||
* @type {object}
|
||||
* @property {React.ComponentType} ControllerPage -
|
||||
* Placed at the end of the controller page.
|
||||
* @property {React.ComponentType} HostsPage -
|
||||
* Placed at the end of the hosts list page.
|
||||
* @property {React.ComponentType} HostViewPage -
|
||||
* Placed at the end of each host page. Takes a `host` param
|
||||
* which is the host the page is displayed for.
|
||||
* @property {React.ComponentType} InstancesPage -
|
||||
* Placed at the end of the instance list page.
|
||||
* @property {React.ComponentType} InstanceViewPage -
|
||||
* Placed at the end of each instance page. Takes an `instance`
|
||||
* param which is the instance the page is displayed for.
|
||||
* @property {React.ComponentType} UsersPage -
|
||||
* Placed at the end of the users list page.
|
||||
* @property {React.ComponentType} UserViewPage -
|
||||
* Placed at the end of each user page. Takes a `user` param
|
||||
* which is the user object the page is displayed for.
|
||||
* @property {React.ComponentType} RolesPage -
|
||||
* Placed at the end of the roles list page.
|
||||
* @property {React.ComponentType} RoleViewPage -
|
||||
* Placed at the end of each role page. Takes a `role` param
|
||||
* which is the role object the page is displayed for.
|
||||
*/
|
||||
this.componentExtra = {};
|
||||
public info: PluginInfo,
|
||||
logger: Logger,
|
||||
) {
|
||||
this.package = packageData;
|
||||
this.logger = logger.child({ plugin: this.info.name }) as unknown as Logger;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -770,27 +799,27 @@ class BaseWebPlugin {
|
||||
* send any messages that goes to or via the controller after the
|
||||
* connection has been closed and before a new one is established.
|
||||
*
|
||||
* @param {string} event - one of connect, drop, resume and close
|
||||
* @param event - one of connect, drop, resume and close
|
||||
*/
|
||||
onControllerConnectionEvent(event) { }
|
||||
onControllerConnectionEvent(event: "connect" | "drop" | "resume" | "close") { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given hook on all plugins
|
||||
*
|
||||
* @param {Map<string, Object>} plugins -
|
||||
* @param plugins -
|
||||
* Mapping of plugin names to plugins to invoke the hook on.
|
||||
* @param {string} hook - Name of hook to invoke.
|
||||
* @param {...*} args - Arguments to pass on to the hook.
|
||||
* @returns {Promise<Array>} Non-empty return values from the hooks.
|
||||
* @param hook - Name of hook to invoke.
|
||||
* @param args - Arguments to pass on to the hook.
|
||||
* @returns Non-undefined return values from the hooks.
|
||||
*/
|
||||
async function invokeHook(plugins, hook, ...args) {
|
||||
let results = [];
|
||||
export async function invokeHook(plugins: Map<string, BasePlugin>, hook: string, ...args: any[]) {
|
||||
let results: unknown[] = [];
|
||||
for (let [name, plugin] of plugins) {
|
||||
try {
|
||||
// Use an object to detect if the hook failed on timeout or tried to return a value looking like an error
|
||||
const timeout = {};
|
||||
let result = await helpers.timeout(
|
||||
let result = await libHelpers.timeout(
|
||||
plugin[hook](...args),
|
||||
15000,
|
||||
timeout
|
||||
@@ -806,13 +835,3 @@ async function invokeHook(plugins, hook, ...args) {
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
BaseInstancePlugin,
|
||||
BaseControllerPlugin,
|
||||
BaseControlPlugin,
|
||||
BaseWebPlugin,
|
||||
|
||||
invokeHook,
|
||||
};
|
||||
@@ -3,12 +3,10 @@
|
||||
* Helpers for loading plugins in Node.js
|
||||
* @module lib/plugin_loader
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const libErrors = require("./errors");
|
||||
const libPlugin = require("./plugin");
|
||||
const { logger } = require("./logging");
|
||||
const path = require("path");
|
||||
import path from "path";
|
||||
import * as libErrors from "./errors";
|
||||
import * as libPlugin from "./plugin";
|
||||
import { logger } from "./logging";
|
||||
|
||||
|
||||
/**
|
||||
@@ -18,16 +16,15 @@ const path = require("path");
|
||||
* loaded the info modules will not be reloaded should this function be
|
||||
* called again.
|
||||
*
|
||||
* @param {Map<string, string>} pluginList -
|
||||
* @param pluginList -
|
||||
* Mapping of plugin name to require path for the plugins to load.
|
||||
* @returns {Promise<Array<Object>>} Array of plugin info modules.
|
||||
* @static
|
||||
* @returns Array of plugin info modules.
|
||||
*/
|
||||
async function loadPluginInfos(pluginList) {
|
||||
let plugins = [];
|
||||
export async function loadPluginInfos(pluginList: Map<string, string>) {
|
||||
let plugins: libPlugin.PluginInfo[] = [];
|
||||
for (let [pluginName, pluginPath] of pluginList) {
|
||||
let pluginInfo;
|
||||
let pluginPackage;
|
||||
let pluginInfo: libPlugin.PluginInfo;
|
||||
let pluginPackage: { version: string };
|
||||
|
||||
try {
|
||||
pluginInfo = require(path.posix.join(pluginPath, "info"));
|
||||
@@ -56,7 +53,12 @@ async function loadPluginInfos(pluginList) {
|
||||
return plugins;
|
||||
}
|
||||
|
||||
function loadPluginClass(entrypointName, className, pluginClass, pluginInfo) {
|
||||
function loadPluginClass(
|
||||
entrypointName: string,
|
||||
className: string,
|
||||
pluginClass: any,
|
||||
pluginInfo: libPlugin.PluginInfo
|
||||
) {
|
||||
let resolvedPath = path.posix.join(pluginInfo.requirePath, pluginInfo[entrypointName]);
|
||||
let entrypoint = require(resolvedPath);
|
||||
if (!entrypoint[className]) {
|
||||
@@ -75,45 +77,41 @@ function loadPluginClass(entrypointName, className, pluginClass, pluginInfo) {
|
||||
/**
|
||||
* Load controller plugin class of a plugin
|
||||
*
|
||||
* @param {Object} pluginInfo -
|
||||
* @param pluginInfo -
|
||||
* Plugin info object returned from {@link
|
||||
* module:lib.loadPluginInfos} to load class from.
|
||||
* @returns {Promise<function>} plugin class
|
||||
* @static
|
||||
* loadPluginInfos} to load class from.
|
||||
* @returns plugin class
|
||||
*/
|
||||
async function loadControllerPluginClass(pluginInfo) {
|
||||
export async function loadControllerPluginClass(
|
||||
pluginInfo: libPlugin.PluginInfo
|
||||
): Promise<libPlugin.BaseControllerPlugin> {
|
||||
return loadPluginClass("controllerEntrypoint", "ControllerPlugin", libPlugin.BaseControllerPlugin, pluginInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load instance plugin class of a plugin
|
||||
*
|
||||
* @param {Object} pluginInfo -
|
||||
|
||||
* @param pluginInfo -
|
||||
* Plugin info object returned from {@link
|
||||
* module:lib.loadPluginInfos} to load class from.
|
||||
* @returns {Promise<function>} plugin class
|
||||
* @static
|
||||
* loadPluginInfos} to load class from.
|
||||
* @returns plugin class
|
||||
*/
|
||||
async function loadInstancePluginClass(pluginInfo) {
|
||||
export async function loadInstancePluginClass(
|
||||
pluginInfo: libPlugin.PluginInfo
|
||||
): Promise<libPlugin.BaseInstancePlugin> {
|
||||
return loadPluginClass("instanceEntrypoint", "InstancePlugin", libPlugin.BaseInstancePlugin, pluginInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load control plugin class of a plugin
|
||||
*
|
||||
* @param {Object} pluginInfo -
|
||||
* @param pluginInfo -
|
||||
* Plugin info object returned from {@link
|
||||
* module:lib.loadPluginInfos} to load class from.
|
||||
* @returns {Promise<function>} plugin class
|
||||
* @static
|
||||
* loadPluginInfos} to load class from.
|
||||
* @returns plugin class
|
||||
*/
|
||||
async function loadControlPluginClass(pluginInfo) {
|
||||
export async function loadControlPluginClass(
|
||||
pluginInfo: libPlugin.PluginInfo
|
||||
): Promise<libPlugin.BaseControlPlugin> {
|
||||
return loadPluginClass("controlEntrypoint", "ControlPlugin", libPlugin.BaseControlPlugin, pluginInfo);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadPluginInfos,
|
||||
loadControllerPluginClass,
|
||||
loadInstancePluginClass,
|
||||
loadControlPluginClass,
|
||||
};
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2,8 +2,7 @@
|
||||
* JSON schemas used for validating link messages
|
||||
* @module lib/schema
|
||||
*/
|
||||
"use strict";
|
||||
const Ajv = require("ajv");
|
||||
import Ajv, { JSONSchemaType, ValidateFunction } from "ajv";
|
||||
const ajv = new Ajv({
|
||||
// These are duplicated in scripts/compile_validator
|
||||
allowUnionTypes: true,
|
||||
@@ -14,17 +13,16 @@ const ajv = new Ajv({
|
||||
|
||||
/**
|
||||
* Compile JSON schema into validator
|
||||
* @function
|
||||
* @param {Object} schema - JSON schema to create validator for.
|
||||
* @returns {Function} Validator for the schema.
|
||||
* @param schema - JSON schema to create validator for.
|
||||
* @returns Validator for the schema.
|
||||
*/
|
||||
function compile(schema) {
|
||||
export function compile<T>(schema: JSONSchemaType<T>) {
|
||||
if (typeof global !== "object" || !global.lazySchemaCompilation) {
|
||||
return ajv.compile(schema);
|
||||
return ajv.compile<T>(schema);
|
||||
}
|
||||
|
||||
let doValidate;
|
||||
function validate(data) {
|
||||
let doValidate: ValidateFunction<T>;
|
||||
const validate = <ValidateFunction<T>>function (data) {
|
||||
if (!doValidate) {
|
||||
doValidate = ajv.compile(schema);
|
||||
}
|
||||
@@ -32,9 +30,5 @@ function compile(schema) {
|
||||
validate.errors = doValidate.errors;
|
||||
return result;
|
||||
}
|
||||
return validate;
|
||||
return validate
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
compile,
|
||||
};
|
||||
@@ -3,15 +3,15 @@
|
||||
* @module lib/shared_commands
|
||||
*/
|
||||
"use strict";
|
||||
const path = require("path");
|
||||
import path from "path";
|
||||
|
||||
const libConfig = require("./config");
|
||||
const libFileOps = require("./file_ops");
|
||||
const { logger } = require("./logging");
|
||||
const libHelpers = require("./helpers");
|
||||
import * as libConfig from "./config";
|
||||
import * as libFileOps from "./file_ops";
|
||||
import { logger } from "./logging";
|
||||
import * as libHelpers from "./helpers";
|
||||
|
||||
|
||||
function print(...content) {
|
||||
function print(...content: any[]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(...content);
|
||||
}
|
||||
@@ -22,9 +22,9 @@ function print(...content) {
|
||||
* Can be passed to yargs.command to implement a plugin list management
|
||||
* command. Use handlePluginCommand to do the requested action.
|
||||
*
|
||||
* @param {Object} yargs - yargs command builder.
|
||||
* @param yargs - yargs command builder.
|
||||
*/
|
||||
function pluginCommand(yargs) {
|
||||
export function pluginCommand(yargs: any) {
|
||||
yargs
|
||||
.command("add <path>", "Add plugin by require path")
|
||||
.command("remove <name>", "Remove plugin by name")
|
||||
@@ -40,20 +40,24 @@ function pluginCommand(yargs) {
|
||||
*
|
||||
* Handle the actions that are made available by pluginCommand.
|
||||
*
|
||||
* @param {Object} args - yargs args object.
|
||||
* @param {Map<string, string>} pluginList - Current list of plugins.
|
||||
* @param {string} pluginListPath - Path to plugin list config file.
|
||||
* @param args - yargs args object.
|
||||
* @param pluginList - Current list of plugins.
|
||||
* @param pluginListPath - Path to plugin list config file.
|
||||
*/
|
||||
async function handlePluginCommand(args, pluginList, pluginListPath) {
|
||||
export async function handlePluginCommand(
|
||||
args: Record<string, unknown>,
|
||||
pluginList: Map<string, string>,
|
||||
pluginListPath: string
|
||||
) {
|
||||
let command = args._[1];
|
||||
|
||||
if (command === "add") {
|
||||
let pluginPath = args.path;
|
||||
let pluginPath = args.path as string;
|
||||
if (/^\.\.?[\/\\]/.test(pluginPath)) {
|
||||
pluginPath = path.resolve(pluginPath);
|
||||
}
|
||||
|
||||
let pluginInfo;
|
||||
let pluginInfo: { name: string };
|
||||
try {
|
||||
// eslint-disable-next-line node/global-require
|
||||
pluginInfo = require(path.posix.join(pluginPath, "info"));
|
||||
@@ -74,7 +78,7 @@ async function handlePluginCommand(args, pluginList, pluginListPath) {
|
||||
print(`Added ${pluginInfo.name}`);
|
||||
|
||||
} else if (command === "remove") {
|
||||
if (!pluginList.delete(args.name)) {
|
||||
if (!pluginList.delete(args.name as string)) {
|
||||
logger.error(`Plugin with name ${args.name} does not exist`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
@@ -97,11 +101,11 @@ async function handlePluginCommand(args, pluginList, pluginListPath) {
|
||||
* Can be passed to yargs.command to implement a config command. Use
|
||||
* handleConfigCommand to do the requested action.
|
||||
*
|
||||
* @param {Object} yargs - yargs command builder.
|
||||
* @param yargs - yargs command builder.
|
||||
*/
|
||||
function configCommand(yargs) {
|
||||
export function configCommand(yargs: any) {
|
||||
yargs
|
||||
.command("set <field> [value]", "Set config field", yargs => {
|
||||
.command("set <field> [value]", "Set config field", (yargs: any) => {
|
||||
yargs.options({
|
||||
"stdin": { describe: "read value from stdin", nargs: 0, type: "boolean" },
|
||||
});
|
||||
@@ -119,15 +123,19 @@ function configCommand(yargs) {
|
||||
*
|
||||
* Handle the actions that are made available by configCommand.
|
||||
*
|
||||
* @param {Object} args - yargs args object.
|
||||
* @param {module:lib.Config} instance - Config instance.
|
||||
* @param {string} configPath - Path to configuration file.
|
||||
* @param args - yargs args object.
|
||||
* @param instance - Config instance.
|
||||
* @param configPath - Path to configuration file.
|
||||
*/
|
||||
async function handleConfigCommand(args, instance, configPath) {
|
||||
export async function handleConfigCommand(
|
||||
args: Record<string, unknown>,
|
||||
instance: libConfig.Config,
|
||||
configPath: string
|
||||
) {
|
||||
let command = args._[1];
|
||||
|
||||
if (command === "list") {
|
||||
for (let GroupClass of instance.constructor.groups.values()) {
|
||||
for (let GroupClass of (instance as any).constructor.groups.values()) {
|
||||
for (let def of GroupClass.definitions.values()) {
|
||||
let value = instance.get(def.fullName);
|
||||
print(`${def.fullName} ${JSON.stringify(value)}`);
|
||||
@@ -136,7 +144,7 @@ async function handleConfigCommand(args, instance, configPath) {
|
||||
|
||||
} else if (command === "show") {
|
||||
try {
|
||||
print(instance.get(args.field));
|
||||
print(instance.get(args.field as string));
|
||||
} catch (err) {
|
||||
if (err instanceof libConfig.InvalidField) {
|
||||
logger.error(err.message);
|
||||
@@ -154,7 +162,7 @@ async function handleConfigCommand(args, instance, configPath) {
|
||||
}
|
||||
|
||||
try {
|
||||
instance.set(args.field, args.value);
|
||||
instance.set(args.field as string, args.value);
|
||||
await libFileOps.safeOutputFile(configPath, JSON.stringify(instance.serialize(), null, 4));
|
||||
} catch (err) {
|
||||
if (err instanceof libConfig.InvalidField || err instanceof libConfig.InvalidValue) {
|
||||
@@ -165,12 +173,3 @@ async function handleConfigCommand(args, instance, configPath) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
pluginCommand,
|
||||
handlePluginCommand,
|
||||
|
||||
configCommand,
|
||||
handleConfigCommand,
|
||||
};
|
||||
@@ -138,7 +138,7 @@ type ReadStreamOptions = Parameters<typeof fs.createReadStream>[1];
|
||||
* Options to pass to fs.createReadStream. Values passed for `fd` and
|
||||
* `fs` will be ignored.
|
||||
*/
|
||||
export async function createReverseReadStream(path: string, options: ReadStreamOptions) {
|
||||
export async function createReverseReadStream(path: string, options?: ReadStreamOptions) {
|
||||
if (typeof options === "string") {
|
||||
options = { encoding: options };
|
||||
}
|
||||
|
||||
@@ -7,24 +7,23 @@
|
||||
* @module lib/users
|
||||
*/
|
||||
"use strict";
|
||||
const libErrors = require("./errors");
|
||||
import * as libErrors from "./errors";
|
||||
|
||||
const PlayerStats = require("./PlayerStats");
|
||||
import PlayerStats from "./PlayerStats";
|
||||
|
||||
|
||||
/**
|
||||
* Represents a permission that can be granted
|
||||
* @static
|
||||
*/
|
||||
class Permission {
|
||||
constructor(name, title, description, grantByDefault) {
|
||||
this.name = name;
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.grantByDefault = grantByDefault;
|
||||
}
|
||||
export class Permission {
|
||||
constructor(
|
||||
public name: string,
|
||||
public title: string,
|
||||
public description: string,
|
||||
public grantByDefault: boolean,
|
||||
) { }
|
||||
}
|
||||
const permissions = new Map();
|
||||
export const permissions = new Map<string, Permission>();
|
||||
|
||||
/**
|
||||
* Define a new user permission for Clusterio
|
||||
@@ -33,15 +32,25 @@ const permissions = new Map();
|
||||
* granted to roles, and checked on users. Name should be `lower_case`
|
||||
* style and start with the plugin name followed by a dot.
|
||||
*
|
||||
* @param {Object} def - The definition for the permission.
|
||||
* @param {string} def.name - The internal name for the permission.
|
||||
* @param {string} def.title - User facing name for permission.
|
||||
* @param {string} def.description - User facing description.
|
||||
* @param {boolean} def.grantByDefault -
|
||||
* @param def - The definition for the permission.
|
||||
* @param def.name - The internal name for the permission.
|
||||
* @param def.title - User facing name for permission.
|
||||
* @param def.description - User facing description.
|
||||
* @param def.grantByDefault -
|
||||
* If true this role is granted by the generated Player role by default.
|
||||
*
|
||||
*/
|
||||
function definePermission({ name, title, description, grantByDefault=false }) {
|
||||
export function definePermission({
|
||||
name,
|
||||
title,
|
||||
description,
|
||||
grantByDefault = false,
|
||||
}: {
|
||||
name: string,
|
||||
title: string,
|
||||
description: string,
|
||||
grantByDefault?: boolean,
|
||||
}) {
|
||||
if (typeof name !== "string" || !name.length) {
|
||||
throw new Error("Expected name to be a non-empty string");
|
||||
}
|
||||
@@ -64,10 +73,14 @@ function definePermission({ name, title, description, grantByDefault=false }) {
|
||||
|
||||
/**
|
||||
* Represents a collection of granted permissions
|
||||
* @static
|
||||
*/
|
||||
class Role {
|
||||
constructor(serializedRole) {
|
||||
export class Role {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
permissions: Set<string>;
|
||||
|
||||
constructor(serializedRole: ReturnType<Role["serialize"]>) {
|
||||
this.id = serializedRole.id;
|
||||
this.name = serializedRole.name;
|
||||
this.description = serializedRole.description;
|
||||
@@ -98,14 +111,18 @@ class Role {
|
||||
*
|
||||
* Ensures the role with id 0 exisits and has the core.admin permission.
|
||||
*
|
||||
* @param {Map<number, module:lib.Role>} roles - role storage to modify.
|
||||
* @returns {module:lib.Role} the default admin role
|
||||
* @static
|
||||
* @param roles - role storage to modify.
|
||||
* @returns the default admin role
|
||||
*/
|
||||
function ensureDefaultAdminRole(roles) {
|
||||
export function ensureDefaultAdminRole(roles: Map<number, Role>) {
|
||||
let admin = roles.get(0);
|
||||
if (!admin) {
|
||||
admin = new Role({ id: 0, name: "Cluster Admin", description: "Cluster wide administrator." });
|
||||
admin = new Role({
|
||||
id: 0,
|
||||
name: "Cluster Admin",
|
||||
description: "Cluster wide administrator.",
|
||||
permissions: [],
|
||||
});
|
||||
roles.set(0, admin);
|
||||
}
|
||||
admin.permissions.add("core.admin");
|
||||
@@ -119,13 +136,17 @@ function ensureDefaultAdminRole(roles) {
|
||||
* assigned by default. Note that this may not be the same role as the one
|
||||
* that is assigned to new users by default.
|
||||
*
|
||||
* @param {Map<number, module:lib.Role>} roles - role storage to modify.
|
||||
* @static
|
||||
* @param roles - role storage to modify.
|
||||
*/
|
||||
function ensureDefaultPlayerRole(roles) {
|
||||
export function ensureDefaultPlayerRole(roles: Map<number, Role>) {
|
||||
let player = roles.get(1);
|
||||
if (!player) {
|
||||
player = new Role({ id: 1, name: "Player", description: "Default player role." });
|
||||
player = new Role({
|
||||
id: 1,
|
||||
name: "Player",
|
||||
description: "Default player role.",
|
||||
permissions: [],
|
||||
});
|
||||
roles.set(1, player);
|
||||
}
|
||||
player.grantDefaultPermissions();
|
||||
@@ -136,56 +157,42 @@ function ensureDefaultPlayerRole(roles) {
|
||||
* Represeents a user in the cluster
|
||||
*
|
||||
* Holds data about a Factorio user in the cluster.
|
||||
* @static
|
||||
*/
|
||||
class User {
|
||||
constructor(serializedUser, loadedRoles) {
|
||||
/**
|
||||
* Factorio user name.
|
||||
* @type {string}
|
||||
*/
|
||||
export class User {
|
||||
/** Factorio user name. */
|
||||
name: string;
|
||||
/** Instances this user is online on. */
|
||||
instances: Set<number>;
|
||||
/** Unix time in seconds the user token must be issued after to be valid. */
|
||||
tokenValidAfter: number;
|
||||
/** True if the user is promoted to admin on the Factorio instances. */
|
||||
isAdmin: boolean;
|
||||
/** True if the user is whitelisted on the Factorio instances. */
|
||||
isWhitelisted: boolean;
|
||||
/** True if the user is banned from Factorio instances. */
|
||||
isBanned: boolean;
|
||||
/** Reason for being banned. Ignored if isBanned is false. */
|
||||
banReason: string;
|
||||
/** Roles this user has */
|
||||
roles: Set<Role>;
|
||||
/** Per instance statistics for the player this user account is tied to. */
|
||||
instanceStats: Map<number, PlayerStats>;
|
||||
/** Combined statistics for the player this user account is tied to. */
|
||||
playerStats: PlayerStats;
|
||||
/** True if this user object has been removed from the cluster. */
|
||||
isDeleted: boolean;
|
||||
|
||||
constructor(
|
||||
serializedUser: ReturnType<User["serialize"]>,
|
||||
loadedRoles: Map<number, Role>
|
||||
) {
|
||||
this.name = serializedUser.name;
|
||||
|
||||
/**
|
||||
* Instances this user is online on.
|
||||
* @type {Set<number>}
|
||||
*/
|
||||
this.instances = new Set();
|
||||
|
||||
/**
|
||||
* Unix time in seconds the user token must be issued after to be valid.
|
||||
* @type {number}
|
||||
*/
|
||||
this.tokenValidAfter = serializedUser.token_valid_after || 0;
|
||||
|
||||
/**
|
||||
* True if the user is promoted to admin on the Factorio instances.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isAdmin = Boolean(serializedUser.is_admin);
|
||||
|
||||
/**
|
||||
* True if the user is whitelisted on the Factorio instances.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isWhitelisted = Boolean(serializedUser.is_whitelisted);
|
||||
|
||||
/**
|
||||
* True if the user is banned from Factorio instances.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isBanned = Boolean(serializedUser.is_banned);
|
||||
|
||||
/**
|
||||
* Reason for being banned. Ignored if isBanned is false.
|
||||
* @type {string}
|
||||
*/
|
||||
this.banReason = serializedUser.ban_reason || "";
|
||||
|
||||
/**
|
||||
* Roles this user has
|
||||
* @type {Set<module:lib.Role>}
|
||||
*/
|
||||
this.roles = new Set();
|
||||
if (serializedUser.roles) {
|
||||
for (let roleId of serializedUser.roles) {
|
||||
@@ -195,32 +202,26 @@ class User {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Per instance statistics for the player this user account is tied to.
|
||||
* @type {Map<number, module:lib.PlayerStats>}
|
||||
*/
|
||||
this.instanceStats = new Map(
|
||||
(serializedUser.instance_stats ? serializedUser.instance_stats : []).map(
|
||||
([id, stats]) => [id, new PlayerStats(stats)]
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Combined statistics for the player this user account is tied to.
|
||||
* @type {module:lib.PlayerStats}
|
||||
*/
|
||||
this.playerStats = this._calculatePlayerStats();
|
||||
|
||||
/**
|
||||
* True if this user object has been removed from the cluster.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isDeleted = false;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
let serialized = {
|
||||
let serialized: {
|
||||
name: string,
|
||||
roles?: number[],
|
||||
token_valid_after?: number,
|
||||
is_admin?: boolean,
|
||||
is_whitelisted?: boolean,
|
||||
is_banned?: boolean,
|
||||
ban_reason?: string,
|
||||
instance_stats?: [number, object][],
|
||||
} = {
|
||||
name: this.name,
|
||||
};
|
||||
|
||||
@@ -272,11 +273,11 @@ class User {
|
||||
* permission. If the permission is not granted for the user a
|
||||
* "Permission denied" error is thrown.
|
||||
*
|
||||
* @param {string} permission - The permission to check for.
|
||||
* @param permission - The permission to check for.
|
||||
* @throws {Error} If the given permission does not exist.
|
||||
* @throws {module:lib.PermissionError} if the user does noh have the given permission.
|
||||
* @throws {libErrors.PermissionError} if the user does noh have the given permission.
|
||||
*/
|
||||
checkPermission(permission) {
|
||||
checkPermission(permission: string) {
|
||||
if (!permissions.has(permission)) {
|
||||
throw new Error(`permission ${permission} does not exist`);
|
||||
}
|
||||
@@ -290,12 +291,12 @@ class User {
|
||||
throw new libErrors.PermissionError("Permission denied");
|
||||
}
|
||||
|
||||
notifyJoin(instance_id) {
|
||||
notifyJoin(instance_id: number) {
|
||||
this.instances.add(instance_id);
|
||||
User.onlineUsers.add(this);
|
||||
}
|
||||
|
||||
notifyLeave(instance_id) {
|
||||
notifyLeave(instance_id: number) {
|
||||
this.instances.delete(instance_id);
|
||||
if (!this.instances.size) {
|
||||
User.onlineUsers.delete(this);
|
||||
@@ -321,12 +322,11 @@ class User {
|
||||
}
|
||||
return playerStats;
|
||||
}
|
||||
/**
|
||||
* Set of users currently online in the cluster.
|
||||
*/
|
||||
static onlineUsers = new Set<User>();
|
||||
}
|
||||
/**
|
||||
* Set of users currently online in the cluster.
|
||||
* @type {module:lib.User}
|
||||
*/
|
||||
User.onlineUsers = new Set();
|
||||
|
||||
// Definitions for the built in permissions used in Clusterio.
|
||||
// description should answer "this permission allows you to ___"
|
||||
@@ -677,14 +677,3 @@ definePermission({
|
||||
title: "Dump WebSocket",
|
||||
description: "Dump all WebSocket communicatation from the controller.",
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
Permission,
|
||||
Role,
|
||||
User,
|
||||
|
||||
permissions,
|
||||
definePermission,
|
||||
ensureDefaultAdminRole,
|
||||
ensureDefaultPlayerRole,
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
"use strict";
|
||||
import type JSZip from "jszip";
|
||||
|
||||
/**
|
||||
* Returns the root folder in the zip file
|
||||
@@ -6,13 +6,10 @@
|
||||
* Returns the name of the folder that all files in the zip file is
|
||||
* contained in. Throws an error if there are multiple such folders.
|
||||
*
|
||||
* @param {JSZip} zip - Zip to search through.
|
||||
* @returns {string} name of the root folder.
|
||||
* @memberof module:lib
|
||||
* @private
|
||||
* @inner
|
||||
* @param zip - Zip to search through.
|
||||
* @returns name of the root folder.
|
||||
*/
|
||||
function findRoot(zip) {
|
||||
export function findRoot(zip: JSZip) {
|
||||
let root = null;
|
||||
zip.forEach((relativePath, file) => {
|
||||
let index = relativePath.indexOf("/");
|
||||
@@ -34,7 +31,3 @@ function findRoot(zip) {
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
findRoot,
|
||||
};
|
||||
Reference in new issue
Block a user