mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 15:04:52 +09:00
Port basic lib sub-modules to TypeScript
This commit is contained in:
1 parent
e0c197b9bc
commit
15ea9a91dc
14 files changed
+636
-488
No files matched your search
@@ -5,7 +5,7 @@
|
||||
"repository": "https://github.com/clusterio/clusterio",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prepare": "pnpm run compile_validator && tsc || true",
|
||||
"prepare": "pnpm run compile_validator && tsc",
|
||||
"compile_validator": "node scripts/compile_validator.js"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
|
||||
@@ -162,6 +162,7 @@ export async function resolveHost(client: Link, hostName: string) {
|
||||
hostId = parseInt(hostName, 10);
|
||||
} else {
|
||||
let hosts = await client.sendTo("controller", new libData.HostListRequest());
|
||||
// @ts-ignore: TODO Remove when lib/data is migrated to ts
|
||||
for (let host of hosts) {
|
||||
if (host.name === hostName) {
|
||||
hostId = host.id;
|
||||
@@ -193,6 +194,7 @@ export async function resolveInstance(client: Link, instanceName: string) {
|
||||
instanceId = parseInt(instanceName, 10);
|
||||
} else {
|
||||
let instances = await client.sendTo("controller", new libData.InstanceDetailsListRequest());
|
||||
// @ts-ignore: TODO Remove when lib/data is migrated to ts
|
||||
for (let instance of instances) {
|
||||
if (instance.name === instanceName) {
|
||||
instanceId = instance.id;
|
||||
@@ -225,6 +227,7 @@ export async function resolveModPack(client: Link, modPackName: string) {
|
||||
modPackId = parseInt(modPackName, 10);
|
||||
} else {
|
||||
let modPacks = await client.sendTo("controller", new libData.ModPackListRequest());
|
||||
// @ts-ignore: TODO Remove when lib/data is migrated to ts
|
||||
for (let modPack of modPacks) {
|
||||
if (modPack.name === modPackName) {
|
||||
modPackId = modPack.id;
|
||||
@@ -256,6 +259,7 @@ export async function retrieveRole(client: Link, roleName: string) {
|
||||
let resolvedRole: libData.RawRole;
|
||||
if (/^-?\d+$/.test(roleName)) {
|
||||
let roleId = parseInt(roleName, 10);
|
||||
// @ts-ignore: TODO Remove when lib/data is migrated to ts
|
||||
for (let role of roles) {
|
||||
if (role.id === roleId) {
|
||||
resolvedRole = role;
|
||||
@@ -264,6 +268,7 @@ export async function retrieveRole(client: Link, roleName: string) {
|
||||
}
|
||||
|
||||
} else {
|
||||
// @ts-ignore: TODO Remove when lib/data is migrated to ts
|
||||
for (let role of roles) {
|
||||
if (role.name === roleName) {
|
||||
resolvedRole = role;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Configuration classes
|
||||
"use strict";
|
||||
import events from "events";
|
||||
|
||||
const events = require("events");
|
||||
import isDeepStrictEqual from "../is_deep_strict_equal";
|
||||
import { basicType } from"../helpers";
|
||||
|
||||
const isDeepStrictEqual = require("../is_deep_strict_equal");
|
||||
const { basicType } = require("../helpers");
|
||||
|
||||
export type ConfigLocation = "controller" | "host" | "instance" | "control";
|
||||
|
||||
/**
|
||||
* Invalid Access exception
|
||||
@@ -14,9 +14,8 @@ const { basicType } = require("../helpers");
|
||||
* does not permitt this access is done.
|
||||
*
|
||||
* @extends Error
|
||||
* @memberof module:lib
|
||||
*/
|
||||
class InvalidAccess extends Error { };
|
||||
export class InvalidAccess extends Error { };
|
||||
|
||||
/**
|
||||
* Invalid Value exception
|
||||
@@ -25,9 +24,8 @@ class InvalidAccess extends Error { };
|
||||
* field.
|
||||
*
|
||||
* @extends Error
|
||||
* @memberof module:lib
|
||||
*/
|
||||
class InvalidValue extends Error { };
|
||||
export class InvalidValue extends Error { };
|
||||
|
||||
/**
|
||||
* Invalid Field exception
|
||||
@@ -36,9 +34,8 @@ class InvalidValue extends Error { };
|
||||
* attempted to be accessed.
|
||||
*
|
||||
* @extends Error
|
||||
* @memberof module:lib
|
||||
*/
|
||||
class InvalidField extends Error { };
|
||||
export class InvalidField extends Error { };
|
||||
|
||||
/**
|
||||
* Split the given string on the first instance of separator
|
||||
@@ -48,14 +45,12 @@ class InvalidField extends Error { };
|
||||
* after the separator. Returns an array with the string and an empty
|
||||
* string if the separator is not present.
|
||||
*
|
||||
* @param {string} separator - Separator to split string by.
|
||||
* @param {string} string - String to split
|
||||
* @returns {Array} string split on separator.
|
||||
* @memberof module:lib
|
||||
* @private
|
||||
* @inner
|
||||
* @param separator - Separator to split string by.
|
||||
* @param string - String to split
|
||||
* @returns string split on separator.
|
||||
* @internal
|
||||
*/
|
||||
function splitOn(separator, string) {
|
||||
function splitOn(separator: string, string: string) {
|
||||
let index = string.indexOf(separator);
|
||||
if (index === -1) {
|
||||
return [string, ""];
|
||||
@@ -63,6 +58,9 @@ function splitOn(separator, string) {
|
||||
return [string.slice(0, index), string.slice(index + separator.length)];
|
||||
}
|
||||
|
||||
type SerializedConfig = ReturnType<Config["serialize"]>;
|
||||
type SerializedGroup = ReturnType<ConfigGroup["serialize"]>;
|
||||
|
||||
/**
|
||||
* Collection of config groups
|
||||
*
|
||||
@@ -76,34 +74,38 @@ function splitOn(separator, string) {
|
||||
* prev.
|
||||
*
|
||||
* @extends events.EventEmitter
|
||||
*
|
||||
* @memberof module:lib
|
||||
*/
|
||||
class Config extends events.EventEmitter {
|
||||
export class Config extends events.EventEmitter {
|
||||
declare static _finalized: boolean;
|
||||
declare static _initialized: boolean;
|
||||
declare static _groups: Map<string, typeof ConfigGroup>;
|
||||
|
||||
_initialized = false;
|
||||
_groups = new Map<string, ConfigGroup>();
|
||||
_unknownGroups = new Map<string, SerializedGroup>();
|
||||
|
||||
/**
|
||||
* Create a new instance of the given config
|
||||
*
|
||||
* @param {string} location -
|
||||
* @param location -
|
||||
* Location to evaluate access for this instance from
|
||||
*/
|
||||
constructor(location) {
|
||||
constructor(
|
||||
public location: ConfigLocation,
|
||||
) {
|
||||
super();
|
||||
|
||||
if (!this.constructor._finalized) {
|
||||
if (!(this.constructor as typeof Config)._finalized) {
|
||||
throw new Error(`Cannot instantiate incomplete Config class ${this.constructor.name}`);
|
||||
}
|
||||
|
||||
if (typeof location !== "string") {
|
||||
throw new Error("location must be a string");
|
||||
}
|
||||
|
||||
this.location = location;
|
||||
this._groups = new Map();
|
||||
this._unknownGroups = new Map();
|
||||
}
|
||||
|
||||
async init() {
|
||||
for (let [name, GroupClass] of this.constructor._groups) {
|
||||
for (let [name, GroupClass] of (this.constructor as typeof Config)._groups) {
|
||||
if (!this._groups.has(name)) {
|
||||
let group = new GroupClass(this);
|
||||
await group.init();
|
||||
@@ -113,7 +115,7 @@ class Config extends events.EventEmitter {
|
||||
this._initialized = true;
|
||||
}
|
||||
|
||||
_validate(serializedConfig) {
|
||||
_validate(serializedConfig: SerializedConfig) {
|
||||
if (basicType(serializedConfig) !== "object") {
|
||||
throw new Error(`Expected object, not ${basicType(serializedConfig)} for config`);
|
||||
}
|
||||
@@ -126,13 +128,16 @@ class Config extends events.EventEmitter {
|
||||
/**
|
||||
* Load config from a serialized object
|
||||
*
|
||||
* @param {object} serializedConfig - Serialized config to load.
|
||||
* @param {string=} location - Location used for access control.
|
||||
* @param serializedConfig - Serialized config to load.
|
||||
* @param location - Location used for access control.
|
||||
*/
|
||||
async load(serializedConfig, location = this.location) {
|
||||
async load(
|
||||
serializedConfig: SerializedConfig,
|
||||
location = this.location
|
||||
) {
|
||||
this._validate(serializedConfig);
|
||||
for (let serializedGroup of serializedConfig.groups) {
|
||||
let GroupClass = this.constructor._groups.get(serializedGroup.name);
|
||||
let GroupClass = (this.constructor as typeof Config)._groups.get(serializedGroup.name);
|
||||
if (!GroupClass) {
|
||||
this._unknownGroups.set(serializedGroup.name, serializedGroup);
|
||||
continue;
|
||||
@@ -149,11 +154,11 @@ class Config extends events.EventEmitter {
|
||||
/**
|
||||
* Serialize the config to a plain JavaScript object
|
||||
*
|
||||
* @param {string=} location - Location used for access control.
|
||||
* @returns {Object} JSON serializable representation of the config.
|
||||
* @param location - Location used for access control.
|
||||
* @returns JSON serializable representation of the config.
|
||||
*/
|
||||
serialize(location = this.location) {
|
||||
let groups = [...this._unknownGroups.values()];
|
||||
let groups: SerializedGroup[] = [...this._unknownGroups.values()];
|
||||
for (let group of this._groups.values()) {
|
||||
groups.push(group.serialize(location));
|
||||
}
|
||||
@@ -167,11 +172,15 @@ class Config extends events.EventEmitter {
|
||||
* Updates all groups in the config with groups in the serialized
|
||||
* config passed.
|
||||
*
|
||||
* @param {Object} serializedConfig - Output from .serialize().
|
||||
* @param {boolean} notify - Invoke fieldChanged events if true.
|
||||
* @param {string=} location - Location used for access control.
|
||||
* @param serializedConfig - Output from .serialize().
|
||||
* @param notify - Invoke fieldChanged events if true.
|
||||
* @param location - Location used for access control.
|
||||
*/
|
||||
update(serializedConfig, notify, location = this.location) {
|
||||
update(
|
||||
serializedConfig: SerializedConfig,
|
||||
notify: boolean,
|
||||
location = this.location
|
||||
) {
|
||||
this._validate(serializedConfig);
|
||||
for (let serializedGroup of serializedConfig.groups) {
|
||||
let group = this._groups.get(serializedGroup.name);
|
||||
@@ -186,11 +195,11 @@ class Config extends events.EventEmitter {
|
||||
/**
|
||||
* Check if field can be accessed from the given location
|
||||
*
|
||||
* @param {string} name - Name of the field to check.
|
||||
* @param {string=} location - Location used for access control.
|
||||
* @returns {boolean} true if field is accessible
|
||||
* @param name - Name of the field to check.
|
||||
* @param location - Location used for access control.
|
||||
* @returns true if field is accessible
|
||||
*/
|
||||
canAccess(name, location = this.location) {
|
||||
canAccess(name: string, location = this.location) {
|
||||
let [group, field] = splitOn(".", name);
|
||||
return this.group(group).canAccess(field, location);
|
||||
}
|
||||
@@ -198,11 +207,11 @@ class Config extends events.EventEmitter {
|
||||
/**
|
||||
* Get the value for a config field
|
||||
*
|
||||
* @param {string} name - Name of field to get.
|
||||
* @param {string=} location - Location used for access control.
|
||||
* @returns {*} the value for the field.
|
||||
* @param name - Name of field to get.
|
||||
* @param location - Location used for access control.
|
||||
* @returns the value for the field.
|
||||
*/
|
||||
get(name, location = this.location) {
|
||||
get(name: string, location = this.location) {
|
||||
let [group, field] = splitOn(".", name);
|
||||
return this.group(group).get(field, location);
|
||||
}
|
||||
@@ -210,11 +219,11 @@ class Config extends events.EventEmitter {
|
||||
/**
|
||||
* Set the value for a config field
|
||||
*
|
||||
* @param {string} name - Name of field to set.
|
||||
* @param {*} value - the value to set on field
|
||||
* @param {string=} location - Location used for access control.
|
||||
* @param name - Name of field to set.
|
||||
* @param value - the value to set on field
|
||||
* @param location - Location used for access control.
|
||||
*/
|
||||
set(name, value, location = this.location) {
|
||||
set(name: string, value: unknown, location = this.location) {
|
||||
let [group, field] = splitOn(".", name);
|
||||
this.group(group).set(field, value, location);
|
||||
}
|
||||
@@ -222,12 +231,12 @@ class Config extends events.EventEmitter {
|
||||
/**
|
||||
* Set property of an object value for a config field
|
||||
*
|
||||
* @param {string} name - Name of field to set property on.
|
||||
* @param {string} prop - Name of property to set on field.
|
||||
* @param {*} value - the value to set the property to.
|
||||
* @param {string=} location - Location used for access control.
|
||||
* @param name - Name of field to set property on.
|
||||
* @param prop - Name of property to set on field.
|
||||
* @param value - the value to set the property to.
|
||||
* @param location - Location used for access control.
|
||||
*/
|
||||
setProp(name, prop, value, location = this.location) {
|
||||
setProp(name: string, prop: string, value: unknown, location = this.location) {
|
||||
let [group, field] = splitOn(".", name);
|
||||
this.group(group).setProp(field, prop, value, location);
|
||||
}
|
||||
@@ -235,10 +244,10 @@ class Config extends events.EventEmitter {
|
||||
/**
|
||||
* Get the config group instance with the given name
|
||||
*
|
||||
* @param {string} name - Name of group to get.
|
||||
* @returns {module:lib.ConfigGroup} config group.
|
||||
* @param name - Name of group to get.
|
||||
* @returns config group.
|
||||
*/
|
||||
group(name) {
|
||||
group(name: string) {
|
||||
if (!this._initialized) {
|
||||
throw new Error(`${this.constructor.name} instance is uninitialized`);
|
||||
}
|
||||
@@ -266,7 +275,7 @@ class Config extends events.EventEmitter {
|
||||
* mave been registered with this config. Note that mutating this
|
||||
* object will lead to unexpected behaviour.
|
||||
*
|
||||
* @returns {Map<string, GroupClass>} config groups
|
||||
* @returns config groups
|
||||
*/
|
||||
static get groups() {
|
||||
this._initSubclass();
|
||||
@@ -287,9 +296,9 @@ class Config extends events.EventEmitter {
|
||||
* Adds the ConfigGroup to this config as a group that will get
|
||||
* initialized, loaded and serialized with this config class.
|
||||
*
|
||||
* @param {module:lib.ConfigGroup} group - class to register.
|
||||
* @param group - class to register.
|
||||
*/
|
||||
static registerGroup(group) {
|
||||
static registerGroup(group: typeof ConfigGroup) {
|
||||
this._initSubclass();
|
||||
|
||||
if (this._finalized) {
|
||||
@@ -308,6 +317,20 @@ class Config extends events.EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
type FieldType = "boolean" | "string" | "number" | "object";
|
||||
interface FieldDefinition {
|
||||
type: FieldType;
|
||||
name: string;
|
||||
fullName: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
optional: boolean;
|
||||
initial_value?: unknown | (() => unknown);
|
||||
enum?: unknown[];
|
||||
restartRequired: boolean;
|
||||
restartRequiredProps?: string[];
|
||||
access: ConfigLocation[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Collection of related config entries
|
||||
@@ -316,41 +339,47 @@ class Config extends events.EventEmitter {
|
||||
* to set invalid values for fields will result in an error thrown while
|
||||
* loading a config with invalid values will result in those being replaced
|
||||
* by the innitial value for those fields.
|
||||
*
|
||||
* @memberof module:lib
|
||||
*/
|
||||
class ConfigGroup {
|
||||
export class ConfigGroup {
|
||||
declare static _finalized: boolean;
|
||||
declare static _initialized: boolean;
|
||||
declare static _definitions: Map<string, FieldDefinition>;
|
||||
declare static groupName: string;
|
||||
declare static defaultAccess: ConfigLocation[];
|
||||
|
||||
_fields = new Map<string, unknown>();
|
||||
_unknownFields = new Map<string, unknown>();
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new config group.
|
||||
*
|
||||
* After the creation of the new config group you have to call
|
||||
* either .init() or .load() on it in order to fully initialize it.
|
||||
*
|
||||
* @param {module:lib.Config} config -
|
||||
* @param config -
|
||||
* Parent config for this group instance.
|
||||
*/
|
||||
constructor(config) {
|
||||
if (!this.constructor._finalized) {
|
||||
constructor(
|
||||
public config: Config
|
||||
) {
|
||||
if (!(this.constructor as typeof ConfigGroup)._finalized) {
|
||||
throw new Error(`Cannot instantiate incomplete ConfigGroup class ${this.constructor.name}`);
|
||||
}
|
||||
|
||||
this._fields = new Map();
|
||||
this._unknownFields = new Map();
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check access for a field
|
||||
*
|
||||
* @param {object} def - definition for for the field to check.
|
||||
* @param {string} remote - location of remote access to check for.
|
||||
* @param {boolean} error - throw on failure to pass check.
|
||||
* @throws {module:lib.InvalidAccess}
|
||||
* @param def - definition for for the field to check.
|
||||
* @param remote - location of remote access to check for.
|
||||
* @param error - throw on failure to pass check.
|
||||
* @throws {InvalidAccess}
|
||||
* if access is not granted and error is true.
|
||||
* @returns {boolean} true if access is granted
|
||||
* @returns true if access is granted
|
||||
* @private
|
||||
*/
|
||||
_checkAccess(def, remote, error) {
|
||||
_checkAccess(def: FieldDefinition, remote: ConfigLocation, error: boolean) {
|
||||
for (let loc of [remote, this.config.location]) {
|
||||
if (!def.access.includes(loc)) {
|
||||
if (error) {
|
||||
@@ -370,7 +399,7 @@ class ConfigGroup {
|
||||
* config group.
|
||||
*/
|
||||
async init() {
|
||||
for (let [name, def] of this.constructor._definitions) {
|
||||
for (let [name, def] of (this.constructor as typeof ConfigGroup)._definitions) {
|
||||
if (this._fields.has(name)) {
|
||||
continue;
|
||||
}
|
||||
@@ -397,11 +426,11 @@ class ConfigGroup {
|
||||
* group and then initializes any possible missing fields from it to
|
||||
* their default values.
|
||||
*
|
||||
* @param {Object} serializedGroup -
|
||||
* @param serializedGroup -
|
||||
* Result from a previous call to .serialize().
|
||||
* @param {string=} location - Location used for access control.
|
||||
* @param location - Location used for access control.
|
||||
*/
|
||||
async load(serializedGroup, location = this.config.location) {
|
||||
async load(serializedGroup: SerializedGroup, location = this.config.location) {
|
||||
this.update(serializedGroup, false, location);
|
||||
await this.init();
|
||||
}
|
||||
@@ -412,18 +441,18 @@ class ConfigGroup {
|
||||
* Shortcut for `group.constructor.groupName`
|
||||
*/
|
||||
get name() {
|
||||
return this.constructor.groupName;
|
||||
return (this.constructor as typeof ConfigGroup).groupName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if field can be accessed from the given location
|
||||
*
|
||||
* @param {string} name - Name of field to check.
|
||||
* @param {string=} location - Location used for access control.
|
||||
* @returns {boolean} true if field is accessible
|
||||
* @param name - Name of field to check.
|
||||
* @param location - Location used for access control.
|
||||
* @returns true if field is accessible
|
||||
*/
|
||||
canAccess(name, location = this.config.location) {
|
||||
let def = this.constructor._definitions.get(name);
|
||||
canAccess(name: string, location = this.config.location) {
|
||||
let def = (this.constructor as typeof ConfigGroup)._definitions.get(name);
|
||||
if (!def) {
|
||||
throw new InvalidField(`No field named '${name}'`);
|
||||
}
|
||||
@@ -433,12 +462,12 @@ class ConfigGroup {
|
||||
/**
|
||||
* Get value for field
|
||||
*
|
||||
* @param {string} name - Name of field to get.
|
||||
* @param {string=} location - Location used for access control.
|
||||
* @returns {*} Value stored for the field.
|
||||
* @param name - Name of field to get.
|
||||
* @param location - Location used for access control.
|
||||
* @returns Value stored for the field.
|
||||
*/
|
||||
get(name, location = this.config.location) {
|
||||
let def = this.constructor._definitions.get(name);
|
||||
get(name: string, location = this.config.location) {
|
||||
let def = (this.constructor as typeof ConfigGroup)._definitions.get(name);
|
||||
if (!def) {
|
||||
throw new InvalidField(`No field named '${name}'`);
|
||||
}
|
||||
@@ -453,14 +482,14 @@ class ConfigGroup {
|
||||
* The field must be defined for the config group and the value is
|
||||
* type checked against the field definition.
|
||||
*
|
||||
* @param {string} name - Name of field to set.
|
||||
* @param {*} value - Value to set for field.
|
||||
* @param {string=} location - Location used for access control.
|
||||
* @throws {module:lib.InvalidField} if field is not defined.
|
||||
* @throws {module:lib.InvalidValue} if value is not allowed for the field.
|
||||
* @param name - Name of field to set.
|
||||
* @param value - Value to set for field.
|
||||
* @param location - Location used for access control.
|
||||
* @throws {InvalidField} if field is not defined.
|
||||
* @throws {InvalidValue} if value is not allowed for the field.
|
||||
*/
|
||||
set(name, value, location = this.config.location) {
|
||||
let def = this.constructor._definitions.get(name);
|
||||
set(name: string, value: unknown, location = this.config.location) {
|
||||
let def = (this.constructor as typeof ConfigGroup)._definitions.get(name);
|
||||
if (!def) {
|
||||
throw new InvalidField(`No field named '${name}'`);
|
||||
}
|
||||
@@ -475,7 +504,7 @@ class ConfigGroup {
|
||||
throw new InvalidValue(`Expected one of [${def.enum.join(", ")}], not ${value}`);
|
||||
}
|
||||
|
||||
if (basicType(value) === "string") {
|
||||
if (typeof value === "string") {
|
||||
if (def.type === "boolean") {
|
||||
if (value === "true") {
|
||||
value = true;
|
||||
@@ -520,15 +549,15 @@ class ConfigGroup {
|
||||
* Update value of stored object field by setting the specified property
|
||||
* on it. The field must be defined as an object for the config group.
|
||||
*
|
||||
* @param {string} name - Name of field to set property on.
|
||||
* @param {string} prop - Name of property to set on field.
|
||||
* @param {*} value - the value to set the property to.
|
||||
* @param {string=} location - Location used for access control.
|
||||
* @throws {module:lib.InvalidField} if field is not defined.
|
||||
* @throws {module:lib.InvalidValue} if field is not an object.
|
||||
* @param name - Name of field to set property on.
|
||||
* @param prop - Name of property to set on field.
|
||||
* @param value - the value to set the property to.
|
||||
* @param location - Location used for access control.
|
||||
* @throws {InvalidField} if field is not defined.
|
||||
* @throws {InvalidValue} if field is not an object.
|
||||
*/
|
||||
setProp(name, prop, value, location = this.config.location) {
|
||||
let def = this.constructor._definitions.get(name);
|
||||
setProp(name: string, prop: string, value: unknown, location = this.config.location) {
|
||||
let def = (this.constructor as typeof ConfigGroup)._definitions.get(name);
|
||||
if (!def) {
|
||||
throw new InvalidField(`No field named '${name}'`);
|
||||
}
|
||||
@@ -538,7 +567,7 @@ class ConfigGroup {
|
||||
throw new InvalidValue(`Cannot set property on non-object field '${name}'`);
|
||||
}
|
||||
|
||||
let prev = this._fields.get(name);
|
||||
let prev = this._fields.get(name) as object;
|
||||
let updated = {...prev || {}};
|
||||
|
||||
if (value !== undefined) {
|
||||
@@ -558,12 +587,16 @@ class ConfigGroup {
|
||||
* Overwrites the values of all the fields in this config group that
|
||||
* has a valid value in the passed serialized group with that value.
|
||||
*
|
||||
* @param {Object} serializedGroup -
|
||||
* @param serializedGroup -
|
||||
* Result from a previous call to .serialize().
|
||||
* @param {boolean} notify - Invoke fieldChanged events if true.
|
||||
* @param {string=} location - Location used for access control.
|
||||
* @param notify - Invoke fieldChanged events if true.
|
||||
* @param location - Location used for access control.
|
||||
*/
|
||||
update(serializedGroup, notify, location = this.config.location) {
|
||||
update(
|
||||
serializedGroup: SerializedGroup,
|
||||
notify: boolean,
|
||||
location = this.config.location
|
||||
) {
|
||||
if (basicType(serializedGroup) !== "object") {
|
||||
throw new Error(`Expected object, not ${basicType(serializedGroup)} for ConfigGroup`);
|
||||
}
|
||||
@@ -577,7 +610,7 @@ class ConfigGroup {
|
||||
}
|
||||
|
||||
for (let [name, value] of Object.entries(serializedGroup.fields)) {
|
||||
let def = this.constructor._definitions.get(name);
|
||||
let def = (this.constructor as typeof ConfigGroup)._definitions.get(name);
|
||||
if (!def) {
|
||||
this._unknownFields.set(name, value);
|
||||
continue;
|
||||
@@ -611,13 +644,13 @@ class ConfigGroup {
|
||||
/**
|
||||
* Serialize the config to a plain JavaScript object
|
||||
*
|
||||
* @param {string=} location - Location used for access control.
|
||||
* @returns {Object} JSON serializable representation of the group.
|
||||
* @param location - Location used for access control.
|
||||
* @returns JSON serializable representation of the group.
|
||||
*/
|
||||
serialize(location = this.config.location) {
|
||||
serialize(location: ConfigLocation = this.config.location) {
|
||||
let fields = {};
|
||||
for (let [name, value] of this._fields) {
|
||||
let def = this.constructor._definitions.get(name);
|
||||
let def = (this.constructor as typeof ConfigGroup)._definitions.get(name);
|
||||
if (!this._checkAccess(def, location, false)) {
|
||||
continue;
|
||||
}
|
||||
@@ -649,8 +682,6 @@ class ConfigGroup {
|
||||
*
|
||||
* Returns the internal meta data for the fields of this class.
|
||||
* Note that mutating this object will lead to unexpected behaviour.
|
||||
*
|
||||
* @returns {Map<string, Object>} field meta data
|
||||
*/
|
||||
static get definitions() {
|
||||
this._initSubclass();
|
||||
@@ -660,37 +691,48 @@ class ConfigGroup {
|
||||
/**
|
||||
* Define a new configuration field
|
||||
*
|
||||
* @param {Object} item - Field definition.
|
||||
* @param {Array<string>=} item.access -
|
||||
* @param item - Field definition.
|
||||
* @param item.access -
|
||||
* Array of strings declaring where this config entry can be read
|
||||
* and modified from.
|
||||
* @param {string} item.type -
|
||||
* @param item.type -
|
||||
* string declaring the type of the config value, supports
|
||||
* boolean, string, number and object.
|
||||
* @param {string} item.name -
|
||||
* @param item.name -
|
||||
* Name of the configuration field. Should follow the
|
||||
* lower_case_underscore style.
|
||||
* @param {string} item.title -
|
||||
* @param item.title -
|
||||
* Text used to identify the config field in user interfaces.
|
||||
* @param {string} item.description -
|
||||
* @param item.description -
|
||||
* Text used to describe the config field in user interfaces.
|
||||
* @param {Array=} item.enum -
|
||||
* @param item.enum -
|
||||
* Array of all values that are acceptible. Useful for making
|
||||
* enumerated values.
|
||||
* @param {boolean=} item.restartRequired -
|
||||
* @param item.restartRequired -
|
||||
* True if a restart of the entity this config is attached to is
|
||||
* required for changes to take effect. Informative only, defaults
|
||||
* to false.
|
||||
* @param {Array=} item.restartRequiredProps -
|
||||
* @param item.restartRequiredProps -
|
||||
* Properties to invert the value of restartRequired for if present.
|
||||
* @param {boolean=} item.optional -
|
||||
* @param item.optional -
|
||||
* True if this field can be set to null. Defaults to false.
|
||||
* @param {*=} item.initial_value -
|
||||
* @param item.initial_value -
|
||||
* Value this config field should take in a newly initialized
|
||||
* config. This can also be an async function returning the
|
||||
* value to use.
|
||||
*/
|
||||
static define(item) {
|
||||
static define(item: {
|
||||
access?: ConfigLocation[],
|
||||
type: FieldType,
|
||||
name: string,
|
||||
title?: string,
|
||||
description?: string,
|
||||
enum?: unknown[],
|
||||
restartRequired?: boolean,
|
||||
restartRequiredProps?: string[],
|
||||
optional?: boolean,
|
||||
initial_value?: unknown | (() => unknown),
|
||||
}) {
|
||||
if (this._finalized) {
|
||||
throw new Error(`Cannot define field for ConfigGroup class ${this.name} after it has been finalized`);
|
||||
}
|
||||
@@ -786,7 +828,7 @@ class ConfigGroup {
|
||||
throw new Error(`Config field ${item.name} has already been defined`);
|
||||
}
|
||||
|
||||
let computed = {
|
||||
let computed: FieldDefinition = {
|
||||
access: this.defaultAccess,
|
||||
optional: false,
|
||||
fullName: `${this.groupName}.${item.name}`,
|
||||
@@ -829,10 +871,9 @@ class ConfigGroup {
|
||||
* plugins. This is currently only the enabled field. It is otherwise
|
||||
* identical to the ConfigGroup class.
|
||||
*
|
||||
* @extends module:lib.ConfigGroup
|
||||
* @memberof module:lib
|
||||
* @extends ConfigGroup
|
||||
*/
|
||||
class PluginConfigGroup extends ConfigGroup {
|
||||
export class PluginConfigGroup extends ConfigGroup {
|
||||
static _initSubclass() {
|
||||
if (!this._initialized) {
|
||||
super._initSubclass();
|
||||
@@ -846,14 +887,3 @@ class PluginConfigGroup extends ConfigGroup {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
InvalidAccess,
|
||||
InvalidField,
|
||||
InvalidValue,
|
||||
|
||||
Config,
|
||||
ConfigGroup,
|
||||
PluginConfigGroup,
|
||||
};
|
||||
@@ -1,13 +1,11 @@
|
||||
// Core definitions for the configuration system
|
||||
"use strict";
|
||||
|
||||
const classes = require("./classes");
|
||||
import * as classes from "./classes";
|
||||
import type { PluginInfo } from "../plugin";
|
||||
|
||||
|
||||
/**
|
||||
* Controller config group for {@link module:lib.ControllerConfig}
|
||||
* @extends module:lib.ConfigGroup
|
||||
* @memberof module:lib
|
||||
* Controller config group for {@link ControllerConfig}
|
||||
* @extends classes.ConfigGroup
|
||||
*/
|
||||
class ControllerGroup extends classes.ConfigGroup { }
|
||||
ControllerGroup.defaultAccess = ["controller", "host", "control"];
|
||||
@@ -140,17 +138,15 @@ ControllerGroup.finalize();
|
||||
|
||||
/**
|
||||
* Controller Config class
|
||||
* @extends module:lib.Config
|
||||
* @memberof module:lib
|
||||
* @extends classes.Config
|
||||
*/
|
||||
class ControllerConfig extends classes.Config { }
|
||||
ControllerConfig.registerGroup(ControllerGroup);
|
||||
|
||||
|
||||
/**
|
||||
* Host config group for {@link module:lib.HostConfig}
|
||||
* @extends module:lib.ConfigGroup
|
||||
* @memberof module:lib
|
||||
* Host config group for {@link HostConfig}
|
||||
* @extends classes.ConfigGroup
|
||||
*/
|
||||
class HostGroup extends classes.ConfigGroup {}
|
||||
HostGroup.defaultAccess = ["controller", "host", "control"];
|
||||
@@ -232,17 +228,15 @@ HostGroup.finalize();
|
||||
|
||||
/**
|
||||
* Host Config class
|
||||
* @extends module:lib.Config
|
||||
* @memberof module:lib
|
||||
* @extends classes.Config
|
||||
*/
|
||||
class HostConfig extends classes.Config { }
|
||||
HostConfig.registerGroup(HostGroup);
|
||||
|
||||
|
||||
/**
|
||||
* Instance config group for {@link module:lib.InstanceConfig}
|
||||
* @extends module:lib.ConfigGroup
|
||||
* @memberof module:lib
|
||||
* Instance config group for {@link classes.InstanceConfig}
|
||||
* @extends classes.ConfigGroup
|
||||
*/
|
||||
class InstanceGroup extends classes.ConfigGroup { }
|
||||
InstanceGroup.defaultAccess = ["controller", "host", "control"];
|
||||
@@ -272,9 +266,8 @@ InstanceGroup.define({
|
||||
InstanceGroup.finalize();
|
||||
|
||||
/**
|
||||
* Factorio config group for {@link module:lib.InstanceConfig}
|
||||
* @extends module:lib.ConfigGroup
|
||||
* @memberof module:lib
|
||||
* Factorio config group for {@link classes.InstanceConfig}
|
||||
* @extends classes.ConfigGroup
|
||||
*/
|
||||
class FactorioGroup extends classes.ConfigGroup { }
|
||||
FactorioGroup.defaultAccess = ["controller", "host", "control"];
|
||||
@@ -398,8 +391,7 @@ FactorioGroup.finalize();
|
||||
|
||||
/**
|
||||
* Instance config class
|
||||
* @extends module:lib.Config
|
||||
* @memberof module:lib
|
||||
* @extends classes.Config
|
||||
*/
|
||||
class InstanceConfig extends classes.Config { }
|
||||
InstanceConfig.registerGroup(InstanceGroup);
|
||||
@@ -407,9 +399,8 @@ InstanceConfig.registerGroup(FactorioGroup);
|
||||
|
||||
|
||||
/**
|
||||
* Control config group for {@link module:lib.ControlConfig}
|
||||
* @extends module:lib.ConfigGroup
|
||||
* @memberof module:lib
|
||||
* Control config group for {@link classes.ControlConfig}
|
||||
* @extends classes.ConfigGroup
|
||||
*/
|
||||
class ControlGroup extends classes.ConfigGroup {}
|
||||
ControlGroup.defaultAccess = ["control"];
|
||||
@@ -443,14 +434,13 @@ ControlGroup.finalize();
|
||||
|
||||
/**
|
||||
* Control config class
|
||||
* @extends module:lib.Config
|
||||
* @memberof module:lib
|
||||
* @extends classes.Config
|
||||
*/
|
||||
class ControlConfig extends classes.Config { }
|
||||
ControlConfig.registerGroup(ControlGroup);
|
||||
|
||||
|
||||
function validateGroup(pluginInfo, groupName) {
|
||||
function validateGroup(pluginInfo: PluginInfo, groupName: string) {
|
||||
if (!(pluginInfo[groupName].prototype instanceof classes.PluginConfigGroup)) {
|
||||
throw new Error(
|
||||
`Expected ${groupName} for ${pluginInfo.name} to be a subclass of PluginConfigGroup`
|
||||
@@ -468,9 +458,8 @@ function validateGroup(pluginInfo, groupName) {
|
||||
* Registers the config groups for the provided plugin infos
|
||||
*
|
||||
* @param {Array<Object>} pluginInfos - Array of plugin info objects.
|
||||
* @memberof module:lib
|
||||
*/
|
||||
function registerPluginConfigGroups(pluginInfos) {
|
||||
function registerPluginConfigGroups(pluginInfos: PluginInfo[]) {
|
||||
for (let pluginInfo of pluginInfos) {
|
||||
if (pluginInfo.ControllerConfigGroup) {
|
||||
validateGroup(pluginInfo, "ControllerConfigGroup");
|
||||
@@ -502,8 +491,6 @@ function registerPluginConfigGroups(pluginInfos) {
|
||||
|
||||
/**
|
||||
* Lock configs from adding more groups and make them usable
|
||||
*
|
||||
* @memberof module:lib
|
||||
*/
|
||||
function finalizeConfigs() {
|
||||
ControllerConfig.finalize();
|
||||
@@ -14,3 +14,40 @@ export * from "./messages_instance";
|
||||
export * from "./messages_mod";
|
||||
export * from "./messages_user";
|
||||
export * from "./version";
|
||||
|
||||
// TODO: Remove/migrate after lib/data is migrated to ts
|
||||
import type { User } from "../users";
|
||||
export type AddressType = "controller" | "host" | "instance" | "control" | "broadcast";
|
||||
export interface Serialisable {
|
||||
jsonSchema: object,
|
||||
fromJSON(json: any): object,
|
||||
}
|
||||
|
||||
import type { Address } from "./messages_core";
|
||||
export type AddressShorthand =
|
||||
| "controller"
|
||||
| { instanceId: number }
|
||||
| { hostId: number }
|
||||
| { controlId: number }
|
||||
| Address
|
||||
|
||||
export class Request {
|
||||
static type: "request";
|
||||
static src: AddressType | AddressType[];
|
||||
static dst: AddressType | AddressType[];
|
||||
static permission: null | "string" | ((user: User, message: Request) => void);
|
||||
static plugin?: string;
|
||||
static jsonSchema?: object;
|
||||
static fromJSON?: (json: any) => Request;
|
||||
static Response?: Serialisable;
|
||||
}
|
||||
|
||||
export class Event {
|
||||
static type: "event";
|
||||
static src: AddressType | AddressType[];
|
||||
static dst: AddressType | AddressType[];
|
||||
static permission: null | "string" | ((user: User, message: Event) => void);
|
||||
static plugin?: string;
|
||||
static jsonSchema?: object;
|
||||
static fromJSON?: (json: any) => Event;
|
||||
}
|
||||
@@ -19,10 +19,10 @@ class Address {
|
||||
/** @type {number} */
|
||||
type;
|
||||
|
||||
/** @type {string} */
|
||||
/** @type {number} */
|
||||
id;
|
||||
|
||||
/** @type {string|undefined} */
|
||||
/** @type {number|undefined} */
|
||||
requestId;
|
||||
|
||||
constructor(type, id, requestId) {
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
/**
|
||||
* Thrown by commands when they fail
|
||||
*/
|
||||
export class CommandError extends Error { }
|
||||
export class CommandError extends Error {
|
||||
code: "CommandError";
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown from requests sent when an error occured handling it
|
||||
@@ -14,7 +16,7 @@ export class CommandError extends Error { }
|
||||
export class RequestError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public code?: string,
|
||||
public code = "RequestError",
|
||||
public stack?: string,
|
||||
) {
|
||||
super(message);
|
||||
@@ -28,12 +30,22 @@ export class RequestError extends Error {
|
||||
* requests fail due to permission denied.
|
||||
*
|
||||
*/
|
||||
export class PermissionError extends RequestError { }
|
||||
export class PermissionError extends RequestError {
|
||||
constructor(
|
||||
message: string,
|
||||
code = "PermissionError",
|
||||
stack?: string,
|
||||
) {
|
||||
super(message, code, stack);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal for messages that fail validation
|
||||
*/
|
||||
export class InvalidMessage extends Error {
|
||||
code: "InvalidMessage";
|
||||
constructor(
|
||||
message: string,
|
||||
public errors: object = null,
|
||||
@@ -45,27 +57,36 @@ export class InvalidMessage extends Error {
|
||||
/**
|
||||
* Thrown from requests when the session it was sent on was lost
|
||||
*/
|
||||
export class SessionLost extends Error { }
|
||||
export class SessionLost extends Error {
|
||||
code: "SessionLost";
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when WebSocket authentication failed
|
||||
*/
|
||||
export class AuthenticationFailed extends Error { }
|
||||
export class AuthenticationFailed extends Error {
|
||||
code: "AuthenticationFailed";
|
||||
}
|
||||
|
||||
/**
|
||||
* Errror class for known errors occuring during startup
|
||||
*/
|
||||
export class StartupError extends Error { }
|
||||
export class StartupError extends Error {
|
||||
code: "StartupError";
|
||||
}
|
||||
|
||||
/**
|
||||
* Errors outside of our control
|
||||
*/
|
||||
export class EnvironmentError extends Error { }
|
||||
export class EnvironmentError extends Error {
|
||||
code: "EnvironmentError";
|
||||
}
|
||||
|
||||
/**
|
||||
* Errors caused by plugins
|
||||
*/
|
||||
export class PluginError extends Error {
|
||||
code: "PluginError";
|
||||
constructor(
|
||||
public pluginName: string,
|
||||
public original: Error
|
||||
|
||||
+64
-62
@@ -1,51 +1,50 @@
|
||||
"use strict";
|
||||
const zlib = require("zlib");
|
||||
|
||||
import zlib from "zlib";
|
||||
|
||||
class MapReaderState {
|
||||
constructor(buf) {
|
||||
this.pos = 0;
|
||||
this.buf = buf;
|
||||
this.last_position = { x: 0, y: 0 };
|
||||
}
|
||||
pos = 0;
|
||||
last_position = { x: 0, y: 0 };
|
||||
constructor(
|
||||
public buf: Buffer
|
||||
) { }
|
||||
}
|
||||
|
||||
function readUInt8(state) {
|
||||
function readUInt8(state: MapReaderState) {
|
||||
let value = state.buf.readUInt8(state.pos);
|
||||
state.pos += 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
function readBool(state) {
|
||||
function readBool(state: MapReaderState) {
|
||||
let value = readUInt8(state) !== 0;
|
||||
return value;
|
||||
}
|
||||
|
||||
function readInt16(state) {
|
||||
function readInt16(state: MapReaderState) {
|
||||
let value = state.buf.readInt16LE(state.pos);
|
||||
state.pos += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
function readUInt16(state) {
|
||||
function readUInt16(state: MapReaderState) {
|
||||
let value = state.buf.readUInt16LE(state.pos);
|
||||
state.pos += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
function readInt32(state) {
|
||||
function readInt32(state: MapReaderState) {
|
||||
let value = state.buf.readInt32LE(state.pos);
|
||||
state.pos += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
function readUInt32(state) {
|
||||
function readUInt32(state: MapReaderState) {
|
||||
let value = state.buf.readUInt32LE(state.pos);
|
||||
state.pos += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
function readUInt32so(state) {
|
||||
function readUInt32so(state: MapReaderState) {
|
||||
let value = readUInt8(state);
|
||||
if (value === 0xff) {
|
||||
return readUInt32(state);
|
||||
@@ -54,26 +53,26 @@ function readUInt32so(state) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function readFloat(state) {
|
||||
function readFloat(state: MapReaderState) {
|
||||
let value = state.buf.readFloatLE(state.pos);
|
||||
state.pos += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
function readDouble(state) {
|
||||
function readDouble(state: MapReaderState) {
|
||||
let value = state.buf.readDoubleLE(state.pos);
|
||||
state.pos += 8;
|
||||
return value;
|
||||
}
|
||||
|
||||
function readString(state) {
|
||||
function readString(state: MapReaderState) {
|
||||
let size = readUInt32so(state);
|
||||
let data = state.buf.slice(state.pos, state.pos + size).toString("utf-8");
|
||||
let data = state.buf.subarray(state.pos, state.pos + size).toString("utf-8");
|
||||
state.pos += size;
|
||||
return data;
|
||||
}
|
||||
|
||||
function readOptional(state, readValue) {
|
||||
function readOptional<T>(state: MapReaderState, readValue: (state: MapReaderState) => T) {
|
||||
let load = readUInt8(state) !== 0;
|
||||
if (!load) {
|
||||
return null;
|
||||
@@ -81,10 +80,10 @@ function readOptional(state, readValue) {
|
||||
return readValue(state);
|
||||
}
|
||||
|
||||
function readArray(state, readItem) {
|
||||
function readArray<T>(state: MapReaderState, readItem: (state: MapReaderState) => T) {
|
||||
let size = readUInt32so(state);
|
||||
|
||||
let array = [];
|
||||
let array: T[] = [];
|
||||
for (let i = 0; i < size; i++) {
|
||||
let item = readItem(state);
|
||||
array.push(item);
|
||||
@@ -93,10 +92,14 @@ function readArray(state, readItem) {
|
||||
return array;
|
||||
}
|
||||
|
||||
function readDict(state, readKey, readValue) {
|
||||
function readDict<K, V>(
|
||||
state: MapReaderState,
|
||||
readKey: (state: MapReaderState) => K,
|
||||
readValue: (state: MapReaderState) => V
|
||||
) {
|
||||
let size = readUInt32so(state);
|
||||
|
||||
let mapping = new Map();
|
||||
let mapping = new Map<K, V>();
|
||||
for (let i = 0; i < size; i++) {
|
||||
let key = readKey(state);
|
||||
let value = readValue(state);
|
||||
@@ -106,7 +109,7 @@ function readDict(state, readKey, readValue) {
|
||||
return mapping;
|
||||
}
|
||||
|
||||
function readVersion(state) {
|
||||
function readVersion(state: MapReaderState) {
|
||||
let major = readUInt16(state);
|
||||
let minor = readUInt16(state);
|
||||
let patch = readUInt16(state);
|
||||
@@ -114,7 +117,7 @@ function readVersion(state) {
|
||||
return [major, minor, patch, developer];
|
||||
}
|
||||
|
||||
function readFrequencySizeRichness(state) {
|
||||
function readFrequencySizeRichness(state: MapReaderState) {
|
||||
return {
|
||||
frequency: readFloat(state),
|
||||
size: readFloat(state),
|
||||
@@ -122,15 +125,15 @@ function readFrequencySizeRichness(state) {
|
||||
};
|
||||
}
|
||||
|
||||
function readAutoplaceSetting(state) {
|
||||
function readAutoplaceSetting(state: MapReaderState) {
|
||||
return {
|
||||
treat_missing_as_default: readBool(state),
|
||||
settings: Object.fromEntries(readDict(state, readString, readFrequencySizeRichness)),
|
||||
};
|
||||
}
|
||||
|
||||
function readMapPosition(state) {
|
||||
let x, y;
|
||||
function readMapPosition(state: MapReaderState) {
|
||||
let x: number, y: number;
|
||||
let x_diff = readInt16(state) / 256;
|
||||
if (x_diff === 0x7fff / 256) {
|
||||
x = readInt32(state) / 256;
|
||||
@@ -145,7 +148,7 @@ function readMapPosition(state) {
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
function readBoundingBox(state) {
|
||||
function readBoundingBox(state: MapReaderState) {
|
||||
return {
|
||||
left_top: readMapPosition(state),
|
||||
right_bottom: readMapPosition(state),
|
||||
@@ -156,7 +159,7 @@ function readBoundingBox(state) {
|
||||
};
|
||||
}
|
||||
|
||||
function readCliffSettings(state) {
|
||||
function readCliffSettings(state: MapReaderState) {
|
||||
return {
|
||||
name: readString(state),
|
||||
elevation_0: readFloat(state),
|
||||
@@ -165,7 +168,7 @@ function readCliffSettings(state) {
|
||||
};
|
||||
}
|
||||
|
||||
function readMapGenSettings(state) {
|
||||
function readMapGenSettings(state: MapReaderState) {
|
||||
return {
|
||||
terrain_segmentation: readFloat(state),
|
||||
water: readFloat(state),
|
||||
@@ -184,7 +187,7 @@ function readMapGenSettings(state) {
|
||||
};
|
||||
}
|
||||
|
||||
function readPollution(state) {
|
||||
function readPollution(state: MapReaderState) {
|
||||
return {
|
||||
enabled: readOptional(state, readBool),
|
||||
diffusion_ratio: readOptional(state, readDouble),
|
||||
@@ -201,24 +204,23 @@ function readPollution(state) {
|
||||
};
|
||||
}
|
||||
|
||||
function readSteeringValue(state) {
|
||||
function readSteeringValue(state: MapReaderState) {
|
||||
return {
|
||||
radius: readOptional(state, readDouble),
|
||||
separation_factor: readOptional(state, readDouble),
|
||||
separation_force: readOptional(state, readDouble),
|
||||
force_unit_fuzzy_goto_behavior: readOptional(state, readBool),
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
function readSteering(state) {
|
||||
function readSteering(state: MapReaderState) {
|
||||
return {
|
||||
default: readSteeringValue(state),
|
||||
moving: readSteeringValue(state),
|
||||
};
|
||||
}
|
||||
|
||||
function readEnemyEvolution(state) {
|
||||
function readEnemyEvolution(state: MapReaderState) {
|
||||
return {
|
||||
enabled: readOptional(state, readBool),
|
||||
time_factor: readOptional(state, readDouble),
|
||||
@@ -227,7 +229,7 @@ function readEnemyEvolution(state) {
|
||||
};
|
||||
}
|
||||
|
||||
function readEnemyExpansion(state) {
|
||||
function readEnemyExpansion(state: MapReaderState) {
|
||||
return {
|
||||
enabled: readOptional(state, readBool),
|
||||
max_expansion_distance: readOptional(state, readUInt32),
|
||||
@@ -245,7 +247,7 @@ function readEnemyExpansion(state) {
|
||||
};
|
||||
}
|
||||
|
||||
function readUnitGroup(state) {
|
||||
function readUnitGroup(state: MapReaderState) {
|
||||
return {
|
||||
min_group_gathering_time: readOptional(state, readUInt32),
|
||||
max_group_gathering_time: readOptional(state, readUInt32),
|
||||
@@ -263,7 +265,7 @@ function readUnitGroup(state) {
|
||||
};
|
||||
}
|
||||
|
||||
function readPathFinder(state) {
|
||||
function readPathFinder(state: MapReaderState) {
|
||||
return {
|
||||
fwd2bwd_ratio: readOptional(state, readInt32),
|
||||
goal_pressure_ratio: readOptional(state, readDouble),
|
||||
@@ -301,7 +303,7 @@ function readPathFinder(state) {
|
||||
};
|
||||
}
|
||||
|
||||
function readDifficultySettings(state) {
|
||||
function readDifficultySettings(state: MapReaderState) {
|
||||
return {
|
||||
recipe_difficulty: readUInt8(state),
|
||||
technology_difficulty: readUInt8(state),
|
||||
@@ -310,7 +312,7 @@ function readDifficultySettings(state) {
|
||||
};
|
||||
}
|
||||
|
||||
function readMapSettings(state) {
|
||||
function readMapSettings(state: MapReaderState) {
|
||||
return {
|
||||
pollution: readPollution(state),
|
||||
steering: readSteering(state),
|
||||
@@ -323,18 +325,23 @@ function readMapSettings(state) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} MapExchangeResult
|
||||
* @property {array} version -
|
||||
* Version of Factorio the string was created with.
|
||||
* @property {object} map_gen_settings -
|
||||
* Decoded map generator settings in the format the --map-gen-settings
|
||||
* command line option to Factorio expect.
|
||||
* @property {object} map_settings -
|
||||
* Decoded map settings in the format the --map-settings command line option
|
||||
* to Factorio expects.
|
||||
* @property {number} checksum - checksum for the exchange string.
|
||||
*/
|
||||
export interface MapExchangeData {
|
||||
/** Version of Factorio the string was created with. */
|
||||
version: ReturnType<typeof readVersion>;
|
||||
unknown: number;
|
||||
/**
|
||||
* Decoded map generator settings in the format the --map-gen-settings
|
||||
* command line option to Factorio expect.
|
||||
*/
|
||||
map_gen_settings: ReturnType<typeof readMapGenSettings>;
|
||||
/**
|
||||
* Decoded map settings in the format the --map-settings command line
|
||||
* option to Factorio expects.
|
||||
*/
|
||||
map_settings: ReturnType<typeof readMapSettings>;
|
||||
/** CRC32 checksum for the exchange string. */
|
||||
checksum: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a Map Exchange String
|
||||
@@ -343,11 +350,10 @@ function readMapSettings(state) {
|
||||
* data structures that can be fed into the Factorio server when creating a
|
||||
* save to set the map gen settings and the map settings for the save.
|
||||
*
|
||||
* @param {string} exchangeString - Max Exchange String to parse.
|
||||
* @returns {MapExchangeResult} Parsed result.
|
||||
* @memberof module:lib
|
||||
* @param exchangeString - Max Exchange String to parse.
|
||||
* @returns Parsed result.
|
||||
*/
|
||||
function readMapExchangeString(exchangeString) {
|
||||
export function readMapExchangeString(exchangeString: string) {
|
||||
exchangeString = exchangeString.replace(/[ \t\n\r]+/g, "");
|
||||
if (!/>>>[0-9a-zA-Z\/+]+={0,3}<<</.test(exchangeString)) {
|
||||
throw new Error("Not a map exchange string");
|
||||
@@ -364,7 +370,7 @@ function readMapExchangeString(exchangeString) {
|
||||
}
|
||||
|
||||
let state = new MapReaderState(buf);
|
||||
let data;
|
||||
let data: MapExchangeData;
|
||||
|
||||
try {
|
||||
data = {
|
||||
@@ -387,7 +393,3 @@ function readMapExchangeString(exchangeString) {
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
readMapExchangeString,
|
||||
};
|
||||
@@ -1,28 +1,30 @@
|
||||
// Connection adapters for Links
|
||||
"use strict";
|
||||
const assert = require("assert").strict;
|
||||
const events = require("events");
|
||||
import { strict as assert } from "assert";
|
||||
import events from "events";
|
||||
// eslint-disable-next-line node/no-process-env
|
||||
// @ts-ignore
|
||||
const WebSocket = process.env.APP_ENV === "browser" ? window.WebSocket : require("ws");
|
||||
|
||||
const libData = require("../data");
|
||||
const libErrors = require("../errors");
|
||||
const { logger } = require("../logging");
|
||||
const ExponentialBackoff = require("../ExponentialBackoff");
|
||||
import * as libData from "../data";
|
||||
import * as libErrors from "../errors";
|
||||
import { logger } from "../logging";
|
||||
import ExponentialBackoff from "../ExponentialBackoff";
|
||||
|
||||
|
||||
/**
|
||||
* Base connector for links
|
||||
*
|
||||
* @extends events.EventEmitter
|
||||
* @memberof module:lib
|
||||
*/
|
||||
class BaseConnector extends events.EventEmitter {
|
||||
constructor(src, dst) {
|
||||
export abstract class BaseConnector extends events.EventEmitter {
|
||||
protected _seq = 1;
|
||||
|
||||
constructor(
|
||||
public src: libData.Address,
|
||||
public dst: libData.Address,
|
||||
) {
|
||||
super();
|
||||
this._seq = 1;
|
||||
this.src = src;
|
||||
this.dst = dst;
|
||||
}
|
||||
|
||||
_reset() {
|
||||
@@ -33,11 +35,17 @@ class BaseConnector extends events.EventEmitter {
|
||||
this._seq = 1;
|
||||
}
|
||||
|
||||
sendRequest(request, requestId, dst) {
|
||||
abstract send(message: libData.Message): void;
|
||||
|
||||
sendRequest(
|
||||
request: libData.Request,
|
||||
requestId: number,
|
||||
dst: libData.Address
|
||||
) {
|
||||
let seq = this._seq;
|
||||
this._seq += 1;
|
||||
let src = new libData.Address(this.src.type, this.src.id, requestId);
|
||||
let Request = request.constructor;
|
||||
let Request = request.constructor as typeof libData.Request;
|
||||
let name = Request.plugin ? `${Request.plugin}:${Request.name}` : Request.name;
|
||||
let hasData = Boolean(Request.jsonSchema);
|
||||
const message = new libData.MessageRequest(seq, src, dst, name, hasData ? request : undefined);
|
||||
@@ -45,7 +53,7 @@ class BaseConnector extends events.EventEmitter {
|
||||
return seq;
|
||||
}
|
||||
|
||||
sendResponse(response, dst, src = this.src) {
|
||||
sendResponse(response: unknown, dst: libData.Address, src = this.src) {
|
||||
let seq = this._seq;
|
||||
this._seq += 1;
|
||||
const message = new libData.MessageResponse(seq, src, dst, response);
|
||||
@@ -53,7 +61,7 @@ class BaseConnector extends events.EventEmitter {
|
||||
return seq;
|
||||
}
|
||||
|
||||
sendResponseError(error, dst, src = this.src) {
|
||||
sendResponseError(error: libData.ResponseError, dst: libData.Address, src = this.src) {
|
||||
let seq = this._seq;
|
||||
this._seq += 1;
|
||||
const message = new libData.MessageResponseError(seq, src, dst, error);
|
||||
@@ -61,10 +69,10 @@ class BaseConnector extends events.EventEmitter {
|
||||
return seq;
|
||||
}
|
||||
|
||||
sendEvent(event, dst) {
|
||||
sendEvent(event: libData.Event, dst: libData.Address) {
|
||||
let seq = this._seq;
|
||||
this._seq += 1;
|
||||
let Event = event.constructor;
|
||||
let Event = event.constructor as typeof libData.Event;
|
||||
let name = Event.plugin ? `${Event.plugin}:${Event.name}` : Event.name;
|
||||
let hasData = Boolean(Event.jsonSchema);
|
||||
const message = new libData.MessageEvent(seq, this.src, dst, name, hasData ? event : undefined);
|
||||
@@ -73,25 +81,26 @@ class BaseConnector extends events.EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
type ConnectorState = "closed" | "connecting" | "connected" | "resuming";
|
||||
|
||||
/**
|
||||
* Base connector for links
|
||||
*
|
||||
* @extends module:lib.BaseConnector
|
||||
* @memberof module:lib
|
||||
*/
|
||||
class WebSocketBaseConnector extends BaseConnector {
|
||||
constructor(src, dst) {
|
||||
super(src, dst);
|
||||
export abstract class WebSocketBaseConnector extends BaseConnector {
|
||||
// One of closed, connecting (client only), connected and resuming.
|
||||
_state: ConnectorState = "closed";
|
||||
_closing = false;
|
||||
_socket = null;
|
||||
_lastHeartbeat = null;
|
||||
_heartbeatId: NodeJS.Timer = null;
|
||||
_heartbeatInterval: number | null = null;
|
||||
_lastReceivedSeq = undefined;
|
||||
_sendBuffer = [];
|
||||
|
||||
// One of closed, connecting (client only), connected and resuming.
|
||||
this._state = "closed";
|
||||
this._closing = false;
|
||||
this._socket = null;
|
||||
this._lastHeartbeat = null;
|
||||
this._heartbeatId = null;
|
||||
this._heartbeatInterval = null;
|
||||
this._lastReceivedSeq = undefined;
|
||||
this._sendBuffer = [];
|
||||
constructor(src: libData.Address, dst: libData.Address) {
|
||||
super(src, dst);
|
||||
}
|
||||
|
||||
_reset() {
|
||||
@@ -118,7 +127,7 @@ class WebSocketBaseConnector extends BaseConnector {
|
||||
super._invalidate();
|
||||
}
|
||||
|
||||
_check(...expectedStates) {
|
||||
_check(...expectedStates: ConnectorState[]) {
|
||||
if (!expectedStates.includes(this._state)) {
|
||||
throw new Error(
|
||||
`Expected state ${expectedStates} but state is ${this._state}`
|
||||
@@ -126,7 +135,7 @@ class WebSocketBaseConnector extends BaseConnector {
|
||||
}
|
||||
}
|
||||
|
||||
_dropSendBufferSeq(seq) {
|
||||
_dropSendBufferSeq(seq?: number) {
|
||||
if (seq === undefined) {
|
||||
return;
|
||||
}
|
||||
@@ -142,8 +151,8 @@ class WebSocketBaseConnector extends BaseConnector {
|
||||
this._sendBuffer.splice(0, dropCount);
|
||||
}
|
||||
|
||||
_parseMessage(text) {
|
||||
let json;
|
||||
_parseMessage(text: string) {
|
||||
let json: object;
|
||||
try {
|
||||
json = JSON.parse(text);
|
||||
} catch (err) {
|
||||
@@ -159,22 +168,24 @@ class WebSocketBaseConnector extends BaseConnector {
|
||||
return libData.Message.fromJSON(json);
|
||||
}
|
||||
|
||||
_processMessage(message) {
|
||||
_processMessage(message: libData.Message) {
|
||||
// TODO check if this can be inferred from Message type
|
||||
if (message.type === "heartbeat") {
|
||||
this._processHeartbeat(message);
|
||||
this._processHeartbeat(message as libData.MessageHeartbeat);
|
||||
|
||||
} else if (message.type === "disconnect") {
|
||||
if (message.data === "ready") {
|
||||
const data = (message as libData.MessageDisconnect).data;
|
||||
if (data === "ready") {
|
||||
this.emit("disconnectReady");
|
||||
|
||||
} else if (message.data === "prepare") {
|
||||
} else if (data === "prepare") {
|
||||
this.setClosing();
|
||||
this.emit("disconnectPrepare");
|
||||
}
|
||||
|
||||
} else {
|
||||
if (message.seq !== undefined) {
|
||||
this._lastReceivedSeq = message.seq;
|
||||
if ((message as any).seq !== undefined) {
|
||||
this._lastReceivedSeq = (message as any).seq;
|
||||
}
|
||||
|
||||
this.emit("message", message);
|
||||
@@ -197,7 +208,7 @@ class WebSocketBaseConnector extends BaseConnector {
|
||||
}
|
||||
}
|
||||
|
||||
_processHeartbeat(message) {
|
||||
_processHeartbeat(message: libData.MessageHeartbeat) {
|
||||
this._lastHeartbeat = Date.now();
|
||||
this._dropSendBufferSeq(message.seq);
|
||||
}
|
||||
@@ -231,9 +242,9 @@ class WebSocketBaseConnector extends BaseConnector {
|
||||
* This is a low level method that should only be used for implementing
|
||||
* links. See sendTo for sending requests and events.
|
||||
*
|
||||
* @param {module:lib.Message} message - Message to send.
|
||||
* @param message - Message to send.
|
||||
*/
|
||||
send(message) {
|
||||
send(message: libData.Message) {
|
||||
if (!["connected", "resuming"].includes(this._state)) {
|
||||
throw new libErrors.SessionLost("No session");
|
||||
}
|
||||
@@ -244,7 +255,7 @@ class WebSocketBaseConnector extends BaseConnector {
|
||||
}
|
||||
}
|
||||
|
||||
_sendInternal(message) {
|
||||
_sendInternal(message: libData.Message) {
|
||||
this._socket.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
@@ -254,9 +265,8 @@ class WebSocketBaseConnector extends BaseConnector {
|
||||
* Signal the connection is about to close and that once the close
|
||||
* frame is received it should be considered finished.
|
||||
*/
|
||||
setClosing() {
|
||||
throw new Error("Abstract function");
|
||||
}
|
||||
abstract setClosing(): void;
|
||||
abstract close(code: number, reason: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Gracefully disconnect
|
||||
@@ -272,7 +282,7 @@ class WebSocketBaseConnector extends BaseConnector {
|
||||
this.setClosing();
|
||||
this.send(new libData.MessageDisconnect("prepare"));
|
||||
|
||||
let timeout;
|
||||
let timeout: NodeJS.Timeout;
|
||||
let waitTimeout = new Promise(resolve => { timeout = setTimeout(resolve, 10000); }); // Make Configurable?
|
||||
try {
|
||||
await Promise.race([
|
||||
@@ -314,25 +324,27 @@ class WebSocketBaseConnector extends BaseConnector {
|
||||
* Connector for controller clients
|
||||
*
|
||||
* @extends module:lib.WebSocketBaseConnector
|
||||
* @memberof module:lib
|
||||
*/
|
||||
class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
constructor(url, maxReconnectDelay, tlsCa = null) {
|
||||
super(undefined, new libData.Address(libData.Address.controller, 0));
|
||||
export abstract class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
_reconnectId: NodeJS.Timeout | null = null;
|
||||
_sessionToken: string | null = null;
|
||||
_sessionTimeout: number | null = null;
|
||||
_startedResuming: number | null = null;
|
||||
_backoff: ExponentialBackoff;
|
||||
|
||||
this._url = url;
|
||||
constructor(
|
||||
protected _url: string,
|
||||
maxReconnectDelay: number,
|
||||
protected _tlsCa = null
|
||||
) {
|
||||
super(undefined, new libData.Address(libData.Address.controller, 0));
|
||||
this._backoff = new ExponentialBackoff({ max: maxReconnectDelay });
|
||||
this._tlsCa = tlsCa;
|
||||
|
||||
// The following states are used in the client connector
|
||||
// closed: Not connected
|
||||
// connecting: Attempting to connect to server.
|
||||
// resuming: Attempting to resume an existing connection to the server.
|
||||
// connected: Connection is online
|
||||
this._reconnectId = null;
|
||||
this._sessionToken = null;
|
||||
this._sessionTimeout = null;
|
||||
this._startedResuming = null;
|
||||
}
|
||||
|
||||
_reset() {
|
||||
@@ -360,9 +372,9 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
* Used in the register function in order to send the handshake messages
|
||||
* over the WebSocket.
|
||||
*
|
||||
* @param {module:lib.Message} message - Message to send.
|
||||
* @param message - Message to send.
|
||||
*/
|
||||
sendHandshake(message) {
|
||||
sendHandshake(message: libData.Message) {
|
||||
this._check("connecting", "resuming");
|
||||
this._sendInternal(message);
|
||||
}
|
||||
@@ -372,10 +384,10 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
*
|
||||
* Sends a close frame and disconnects the connector.
|
||||
*
|
||||
* @param {number} code - WebSocket close code.
|
||||
* @param {string} reason - WebSocket close reason.
|
||||
* @param code - WebSocket close code.
|
||||
* @param reason - WebSocket close reason.
|
||||
*/
|
||||
async close(code, reason) {
|
||||
async close(code: number, reason: string) {
|
||||
if (this._state === "closed") {
|
||||
return;
|
||||
}
|
||||
@@ -443,7 +455,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
this._socket = new WebSocket(url);
|
||||
|
||||
} else {
|
||||
let options = {};
|
||||
let options: { ca?: string } = {};
|
||||
if (this._tlsCa) { options.ca = this._tlsCa; }
|
||||
this._socket = new WebSocket(url, options);
|
||||
}
|
||||
@@ -470,7 +482,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
) {
|
||||
logger.error("Connector | Session timed out trying to resume");
|
||||
this._reset();
|
||||
if (this._disconnecting) {
|
||||
if (this._closing) {
|
||||
this._reset();
|
||||
this.emit("close");
|
||||
} else {
|
||||
@@ -485,11 +497,11 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
this._reconnectId = null;
|
||||
this._doConnect();
|
||||
}, delay);
|
||||
this._reconnectTime = Date.now() + delay;
|
||||
}
|
||||
|
||||
_attachSocketHandlers() {
|
||||
this._socket.onclose = event => {
|
||||
// TODO type event
|
||||
this._socket.onclose = (event: any) => {
|
||||
const previousState = this._state;
|
||||
|
||||
// Authentication failed
|
||||
@@ -532,7 +544,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
}
|
||||
};
|
||||
|
||||
this._socket.onerror = event => {
|
||||
this._socket.onerror = (event: any) => {
|
||||
// It's assumed that close is always called by ws
|
||||
let code = !event.error ? "" : `, code: ${event.error.code}`;
|
||||
let message = `Connector | Socket error: ${event.message || "unknown error"}${code}`;
|
||||
@@ -561,7 +573,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
};
|
||||
|
||||
// Handle messages
|
||||
this._socket.onmessage = event => {
|
||||
this._socket.onmessage = (event: any) => {
|
||||
let message = this._parseMessage(event.data);
|
||||
if (!message) {
|
||||
return;
|
||||
@@ -578,9 +590,9 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
};
|
||||
}
|
||||
|
||||
_processHandshake(message) {
|
||||
|
||||
let { type, data } = message;
|
||||
_processHandshake(message: libData.Message) {
|
||||
// TODO infer data from Message type.
|
||||
let { type, data } = message as libData.Message & { data: any };
|
||||
if (type === "hello") {
|
||||
logger.verbose(`Connector | received hello from controller version ${data.version}`);
|
||||
this.emit("hello", data);
|
||||
@@ -621,7 +633,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
|
||||
} else if (type === "invalidate") {
|
||||
logger.warn("Connector | session invalidated by controller");
|
||||
if (this._disconnecting) {
|
||||
if (this._closing) {
|
||||
this._reset();
|
||||
this.emit("close");
|
||||
} else {
|
||||
@@ -638,19 +650,18 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
* This function is expected to be overriden by sub-classes and send the
|
||||
* register message over the socket when invoked.
|
||||
*/
|
||||
register() {
|
||||
throw Error("Abstract function");
|
||||
}
|
||||
abstract register(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connector for in-memory local links
|
||||
*
|
||||
* @extends events.EventEmitter
|
||||
* @memberof module:lib
|
||||
*/
|
||||
class VirtualConnector extends BaseConnector {
|
||||
constructor(src, dst) {
|
||||
export class VirtualConnector extends BaseConnector {
|
||||
other: VirtualConnector;
|
||||
|
||||
constructor(src: libData.Address, dst: libData.Address) {
|
||||
super(src, dst);
|
||||
|
||||
this.other = this;
|
||||
@@ -662,11 +673,14 @@ class VirtualConnector extends BaseConnector {
|
||||
* Creates two virtual connectors that are liked to each othes such that
|
||||
* a message sent on one is received by the other.
|
||||
*
|
||||
* @param {module:lib.Address} src - Source for obverse connector
|
||||
* @param {module:lib.Address} dst - destination for obverse connector
|
||||
* @returns {Array} two virtuarl connectors.
|
||||
* @param src - Source for obverse connector
|
||||
* @param dst - destination for obverse connector
|
||||
* @returns two virtuarl connectors.
|
||||
*/
|
||||
static makePair(src, dst) {
|
||||
static makePair(
|
||||
src: libData.Address,
|
||||
dst: libData.Address,
|
||||
): [VirtualConnector, VirtualConnector] {
|
||||
let obverse = new this(src, dst);
|
||||
let reverse = new this(dst, src);
|
||||
obverse.other = reverse;
|
||||
@@ -677,16 +691,9 @@ class VirtualConnector extends BaseConnector {
|
||||
/**
|
||||
* Send a message to the other end of the connector
|
||||
*
|
||||
* @param {module:lib.Message} message - Message type to send.
|
||||
* @param message - Message type to send.
|
||||
*/
|
||||
send(message) {
|
||||
send(message: libData.Message) {
|
||||
this.other.emit("message", message);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
BaseConnector,
|
||||
WebSocketBaseConnector,
|
||||
WebSocketClientConnector,
|
||||
VirtualConnector,
|
||||
};
|
||||
@@ -5,7 +5,3 @@
|
||||
*/
|
||||
export * from "./link"
|
||||
export * from "./connectors"
|
||||
|
||||
// migrate: Allow info for plugins from before link refactor to load.
|
||||
export class Event {}
|
||||
export class Request {}
|
||||
@@ -1,11 +1,45 @@
|
||||
// Implementation of Link class
|
||||
"use strict";
|
||||
import * as libData from "../data";
|
||||
import * as libErrors from "../errors";
|
||||
import { logger } from "../logging";
|
||||
import * as libSchema from "../schema";
|
||||
import { dataClasses } from "./messages";
|
||||
import { BaseConnector, WebSocketBaseConnector } from "./connectors";
|
||||
import { strict as assert } from "assert";
|
||||
import type { PluginInfo } from "../plugin";
|
||||
|
||||
const libData = require("../data");
|
||||
const libErrors = require("../errors");
|
||||
const { logger } = require("../logging");
|
||||
const libSchema = require("../schema");
|
||||
const { dataClasses } = require("./messages");
|
||||
interface RequestEntry {
|
||||
Request: typeof libData.Request;
|
||||
name: string,
|
||||
allowedSrcTypes: Set<number>;
|
||||
allowedDstTypes: Set<number>;
|
||||
requestFromJSON: (json: any) => libData.Request;
|
||||
Response?: any;
|
||||
responseFromJSON: (json: any) => any;
|
||||
}
|
||||
|
||||
interface PendingRequest {
|
||||
promise: Promise<any>;
|
||||
request: RequestEntry;
|
||||
resolve: (result: any) => void;
|
||||
reject: (err: Error) => void;
|
||||
dst: libData.Address;
|
||||
}
|
||||
interface ForwardedRequest {
|
||||
origin: Link;
|
||||
src: libData.Address;
|
||||
dst: libData.Address;
|
||||
}
|
||||
|
||||
interface EventEntry {
|
||||
Event: typeof libData.Event;
|
||||
name: string,
|
||||
allowedSrcTypes: Set<number>;
|
||||
allowedDstTypes: Set<number>;
|
||||
eventFromJSON: (json: any) => libData.Event;
|
||||
}
|
||||
type RequestHandler = (request: libData.Request, src: libData.Address, dst: libData.Address) => any;
|
||||
type EventHandler = (request: libData.Event, src: libData.Address, dst: libData.Address) => any;
|
||||
|
||||
// Some definitions for the terminology used here:
|
||||
// link: Either side of a controller - client connection
|
||||
@@ -16,23 +50,23 @@ const { dataClasses } = require("./messages");
|
||||
|
||||
/**
|
||||
* Common interface for server and client connections
|
||||
*
|
||||
* @memberof module:lib
|
||||
*/
|
||||
class Link {
|
||||
constructor(connector) {
|
||||
this.connector = connector;
|
||||
this.router = undefined;
|
||||
this.validateSent = true;
|
||||
export class Link {
|
||||
router = undefined;
|
||||
validateSent = true;
|
||||
|
||||
this._requestHandlers = new Map();
|
||||
this._requestFallbacks = new Map();
|
||||
this._eventHandlers = new Map();
|
||||
this._eventSnoopers = new Map();
|
||||
this._pendingRequests = new Map();
|
||||
this._forwardedRequests = new Map();
|
||||
this._nextRequestId = 1;
|
||||
_requestHandlers = new Map();
|
||||
_requestFallbacks = new Map();
|
||||
_eventHandlers = new Map();
|
||||
_eventSnoopers = new Map();
|
||||
_pendingRequests = new Map<number, PendingRequest>();
|
||||
_forwardedRequests = new Map<string, ForwardedRequest>();
|
||||
_nextRequestId = 1;
|
||||
|
||||
constructor(
|
||||
public connector: BaseConnector
|
||||
) {
|
||||
// @ts-ignore: TODO Remove when lib/data is migrated to ts
|
||||
this.handle(libData.PingRequest, () => {});
|
||||
|
||||
// Process messages received by the connector
|
||||
@@ -74,11 +108,12 @@ class Link {
|
||||
});
|
||||
}
|
||||
|
||||
_clearPendingRequests(err) {
|
||||
_clearPendingRequests(err: Error & { code?: string }) {
|
||||
for (let pending of this._pendingRequests.values()) {
|
||||
pending.reject(err);
|
||||
}
|
||||
for (let pending of this._forwardedRequests.values()) {
|
||||
assert(pending.origin.connector instanceof WebSocketBaseConnector);
|
||||
if (pending.origin.connector.hasSession) {
|
||||
pending.origin.connector.sendResponseError(
|
||||
new libData.ResponseError(err.message, err.code), pending.src, pending.dst
|
||||
@@ -95,10 +130,12 @@ class Link {
|
||||
* Validates and invokes the handler and/or waiters for a message that has
|
||||
* been received. An unhandled message is considered to be an error.
|
||||
*
|
||||
* @param {module:lib.Message} message - Message to process.
|
||||
* @throws {module:lib.InvalidMessage} if the message is invalid or not handled.
|
||||
* @param message - Message to process.
|
||||
* @throws {libErrors.InvalidMessage} if the message is invalid or not handled.
|
||||
*/
|
||||
_processMessage(message) {
|
||||
_processMessage(
|
||||
message: libData.MessageRequest | libData.MessageResponse | libData.MessageResponseError | libData.MessageEvent
|
||||
) {
|
||||
if (!["request", "response", "responseError", "event"].includes(message.type)) {
|
||||
throw new libErrors.InvalidMessage(`Unhandled message type ${message.type}`);
|
||||
}
|
||||
@@ -115,10 +152,11 @@ class Link {
|
||||
}
|
||||
}
|
||||
|
||||
if (message.type === "event" && this._eventSnoopers.has(entry.Event)) {
|
||||
let handler = this._eventSnoopers.get(entry.Event);
|
||||
handler(entry.eventFromJSON(message.data), message.src, message.dst).catch(err => {
|
||||
logger.error(`Unexpected error snooping ${message.name}:\n${err.stack}`);
|
||||
if (message.type === "event" && this._eventSnoopers.has((entry as EventEntry).Event)) {
|
||||
let handler = this._eventSnoopers.get((entry as EventEntry).Event);
|
||||
let event = message as libData.MessageEvent;
|
||||
handler((entry as EventEntry).eventFromJSON(event.data), event.src, event.dst).catch((err: Error) => {
|
||||
logger.error(`Unexpected error snooping ${event.name}:\n${err.stack}`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -131,7 +169,11 @@ class Link {
|
||||
}
|
||||
|
||||
if (message.type === "request") {
|
||||
this._processRequest(message, entry, this._requestHandlers.get(entry.Request));
|
||||
this._processRequest(
|
||||
message as libData.MessageRequest,
|
||||
entry as RequestEntry,
|
||||
this._requestHandlers.get((entry as RequestEntry).Request)
|
||||
);
|
||||
|
||||
} else if (message.type === "response") {
|
||||
this._processResponse(message);
|
||||
@@ -140,7 +182,10 @@ class Link {
|
||||
this._processResponseError(message);
|
||||
|
||||
} else if (message.type === "event") {
|
||||
this._processEvent(message, entry);
|
||||
this._processEvent(
|
||||
message as libData.MessageEvent,
|
||||
entry as EventEntry
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,12 +194,14 @@ class Link {
|
||||
*
|
||||
* Should be overridden by sub-classes to validate messages received.
|
||||
*
|
||||
* @param {module:lib.Message} message - Message to check.
|
||||
* @throws {module:lib.InvalidMessage} if the message is invalid.
|
||||
* @param message - Message to check.
|
||||
* @throws {libErrors.InvalidMessage} if the message is invalid.
|
||||
*/
|
||||
validateIngress(message) { }
|
||||
validateIngress(message: libData.Message) { }
|
||||
|
||||
_validateMessage(message) {
|
||||
_validateMessage(
|
||||
message: libData.MessageRequest | libData.MessageResponse | libData.MessageResponseError | libData.MessageEvent
|
||||
) {
|
||||
try {
|
||||
if (message.src.type === libData.Address.broadcast) {
|
||||
throw new libErrors.InvalidMessage("Message src may not be broadcast");
|
||||
@@ -168,7 +215,8 @@ class Link {
|
||||
}
|
||||
|
||||
if (message.type === "request") {
|
||||
let entry = this.constructor._requestsByName.get(message.name);
|
||||
assert(message instanceof libData.MessageRequest);
|
||||
let entry = Link._requestsByName.get(message.name);
|
||||
if (!entry) {
|
||||
let err = new libErrors.InvalidMessage(`Unrecognized request ${message.name}`);
|
||||
this.connector.sendResponseError(new libData.ResponseError(err.message, err.code), message.src);
|
||||
@@ -193,7 +241,8 @@ class Link {
|
||||
return undefined;
|
||||
|
||||
} else if (message.type === "event") {
|
||||
let entry = this.constructor._eventsByName.get(message.name);
|
||||
assert(message instanceof libData.MessageEvent);
|
||||
let entry = Link._eventsByName.get(message.name);
|
||||
if (!entry) {
|
||||
throw new libErrors.InvalidMessage(`Unrecognized event ${message.name}`);
|
||||
}
|
||||
@@ -222,26 +271,29 @@ class Link {
|
||||
* Called when an request or event is received by a control connector.
|
||||
* Should be overridden by sub-classes to validate messages received.
|
||||
*
|
||||
* @param {module:lib.Message} message - Message to check.
|
||||
* @param {object} entry - Request or Event entry for this Message.
|
||||
* @throws {module:lib.PermissionError} if unauthorized.
|
||||
* @param message - Message to check.
|
||||
* @param entry - Request or Event entry for this Message.
|
||||
* @throws {libErrors.PermissionError} if unauthorized.
|
||||
*/
|
||||
validatePermission(message, entry) { }
|
||||
validatePermission(message: libData.Message, entry: RequestEntry | EventEntry) { }
|
||||
|
||||
_routeMessage(message, entry) {
|
||||
_routeMessage(message: libData.Message, entry?: RequestEntry | EventEntry) {
|
||||
if (!this.router) {
|
||||
let err = new libErrors.InvalidMessage(
|
||||
`Received message addressed to ${message.dst} but this link does not route messages`
|
||||
`Received message addressed to ${(message as libData.MessageRequest).dst} but this link `+
|
||||
`does not route messages`
|
||||
);
|
||||
if (message.type === "request") {
|
||||
this.connector.sendResponseError(
|
||||
new libData.ResponseError(err.message, err.code), message.src
|
||||
new libData.ResponseError(err.message, err.code), (message as libData.MessageRequest).src
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
let fallback = message.type === "request" ? this._requestFallbacks.get(entry.Request) : undefined;
|
||||
let fallback =
|
||||
message.type === "request" ? this._requestFallbacks.get((entry as RequestEntry).Request) : undefined
|
||||
;
|
||||
if (this.router.forwardMessage(this, message, Boolean(fallback))) {
|
||||
return;
|
||||
}
|
||||
@@ -251,10 +303,20 @@ class Link {
|
||||
if (message.type !== "request") {
|
||||
throw new Error(`Router requested fallback handling of unsupported message type ${message.type}`);
|
||||
}
|
||||
this._processRequest(message, entry, fallback, message.dst);
|
||||
this._processRequest(
|
||||
message as libData.MessageRequest,
|
||||
entry as RequestEntry,
|
||||
fallback,
|
||||
(message as libData.MessageRequest).dst
|
||||
);
|
||||
}
|
||||
|
||||
_processRequest(message, entry, handler, spoofedSrc) {
|
||||
_processRequest(
|
||||
message: libData.MessageRequest,
|
||||
entry: RequestEntry,
|
||||
handler: RequestHandler,
|
||||
spoofedSrc?: libData.Address
|
||||
) {
|
||||
if (!handler) {
|
||||
this.connector.sendResponseError(
|
||||
new libData.ResponseError(`No handler for ${entry.Request.name}`), message.src
|
||||
@@ -262,7 +324,7 @@ class Link {
|
||||
return;
|
||||
}
|
||||
|
||||
let response;
|
||||
let response: any;
|
||||
try {
|
||||
response = handler(entry.requestFromJSON(message.data), message.src, message.dst);
|
||||
} catch (err) {
|
||||
@@ -280,7 +342,7 @@ class Link {
|
||||
}
|
||||
|
||||
response.then(
|
||||
result => {
|
||||
(result: any) => {
|
||||
if (this.validateSent) {
|
||||
if (entry.Response) {
|
||||
entry.responseFromJSON(JSON.parse(JSON.stringify(result)));
|
||||
@@ -291,7 +353,7 @@ class Link {
|
||||
this.connector.sendResponse(result, message.src, spoofedSrc);
|
||||
}
|
||||
).catch(
|
||||
err => {
|
||||
(err: Error & { code?: string }) => {
|
||||
if (err instanceof libErrors.InvalidMessage) {
|
||||
logger.error(err.message);
|
||||
if (err.errors) {
|
||||
@@ -307,7 +369,7 @@ class Link {
|
||||
);
|
||||
}
|
||||
|
||||
_processResponse(message) {
|
||||
_processResponse(message: libData.MessageResponse) {
|
||||
let pending = this._pendingRequests.get(message.dst.requestId);
|
||||
if (!pending) {
|
||||
throw new libErrors.InvalidMessage(
|
||||
@@ -327,7 +389,7 @@ class Link {
|
||||
}
|
||||
}
|
||||
|
||||
_processResponseError(message) {
|
||||
_processResponseError(message: libData.MessageResponseError) {
|
||||
let pending = this._pendingRequests.get(message.dst.requestId);
|
||||
if (!pending) {
|
||||
throw new libErrors.InvalidMessage(
|
||||
@@ -338,13 +400,15 @@ class Link {
|
||||
pending.reject(new libErrors.RequestError(message.data.message, message.data.code, message.data.stack));
|
||||
}
|
||||
|
||||
_processEvent(message, entry) {
|
||||
_processEvent(message: libData.MessageEvent, entry: EventEntry) {
|
||||
let handler = this._eventHandlers.get(entry.Event);
|
||||
if (!handler) {
|
||||
throw new libErrors.InvalidMessage(`Unhandled event ${message.name}`);
|
||||
}
|
||||
|
||||
handler(entry.eventFromJSON(message.data), message.src, message.dst).catch(err => {
|
||||
handler(
|
||||
entry.eventFromJSON(message.data), message.src, message.dst
|
||||
).catch((err: Error) => {
|
||||
logger.error(`Unexpected error handling ${message.name}:\n${err.stack}`);
|
||||
});
|
||||
}
|
||||
@@ -368,38 +432,46 @@ class Link {
|
||||
|
||||
/**
|
||||
* Send a request or event to the other side of this link
|
||||
* @param {*} requestOrEvent - Request or event to send
|
||||
* @returns {Promise<*>|undefined}
|
||||
* @param requestOrEvent - Request or event to send
|
||||
* @returns
|
||||
* Promise that resolves to the response if a request was sent or
|
||||
* undefined if it was an event.
|
||||
*/
|
||||
send(requestOrEvent) {
|
||||
send(requestOrEvent: libData.Request): Promise<unknown>;
|
||||
send(requestOrEvent: libData.Event): void;
|
||||
send(requestOrEvent: libData.Request | libData.Event): Promise<unknown> | void {
|
||||
return this.sendTo(this.connector.dst, requestOrEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a request or event to the given address
|
||||
*
|
||||
* @param {*|module:lib.Address} destination - Where to send it
|
||||
* @param {*} requestOrEvent - Request or event to send
|
||||
* @returns {Promise<*>|undefined}
|
||||
* @param destination - Where to send it
|
||||
* @param requestOrEvent - Request or event to send
|
||||
* @returns
|
||||
* Promise that resolves to the response if a request was sent or
|
||||
* undefined if it was an event.
|
||||
*/
|
||||
sendTo(destination, requestOrEvent) {
|
||||
sendTo(destination: libData.AddressShorthand, requestOrEvent: libData.Request): Promise<unknown>;
|
||||
sendTo(destination: libData.AddressShorthand, requestOrEvent: libData.Event): void;
|
||||
sendTo(
|
||||
destination: libData.AddressShorthand,
|
||||
requestOrEvent: libData.Request | libData.Event,
|
||||
): Promise<unknown> | void {
|
||||
let dst = libData.Address.fromShorthand(destination);
|
||||
|
||||
if (requestOrEvent.constructor.type === "request") {
|
||||
// TODO investigate if these types can be inferred
|
||||
if ((requestOrEvent.constructor as any).type === "request") {
|
||||
return this.sendRequest(requestOrEvent, dst);
|
||||
}
|
||||
if (requestOrEvent.constructor.type === "event") {
|
||||
if ((requestOrEvent.constructor as any).type === "event") {
|
||||
return this.sendEvent(requestOrEvent, dst);
|
||||
}
|
||||
throw Error(`Expected request or event but got type ${requestOrEvent.constructor.type}`);
|
||||
throw Error(`Expected request or event but got type ${(requestOrEvent.constructor as any).type}`);
|
||||
}
|
||||
|
||||
sendRequest(request, dst) {
|
||||
let entry = this.constructor._requestsByClass.get(request.constructor);
|
||||
sendRequest(request: libData.Request, dst: libData.Address) {
|
||||
let entry = Link._requestsByClass.get(request.constructor as typeof libData.Request);
|
||||
if (!entry) {
|
||||
throw new Error(`Attempt to send unregistered Request ${request.constructor.name}`);
|
||||
}
|
||||
@@ -407,22 +479,22 @@ class Link {
|
||||
entry.requestFromJSON(JSON.parse(JSON.stringify(request)));
|
||||
}
|
||||
|
||||
let pending = {
|
||||
request: entry,
|
||||
dst,
|
||||
};
|
||||
pending.promise = new Promise((resolve, reject) => {
|
||||
pending.resolve = resolve;
|
||||
pending.reject = reject;
|
||||
let resolve: (value: unknown) => void;
|
||||
let reject: (reason: any) => void;
|
||||
let promise = new Promise((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
let requestId = this._nextRequestId;
|
||||
this._nextRequestId += 1;
|
||||
this._pendingRequests.set(requestId, pending);
|
||||
this._pendingRequests.set(
|
||||
requestId, { request: entry, promise, resolve, reject, dst, }
|
||||
);
|
||||
this.connector.sendRequest(request, requestId, dst);
|
||||
return pending.promise;
|
||||
return promise;
|
||||
}
|
||||
|
||||
forwardRequest(message, origin) {
|
||||
forwardRequest(message: libData.MessageRequest, origin: Link) {
|
||||
let pending = {
|
||||
origin,
|
||||
src: message.src,
|
||||
@@ -432,8 +504,8 @@ class Link {
|
||||
this.connector.send(message);
|
||||
}
|
||||
|
||||
sendEvent(event, dst) {
|
||||
let entry = this.constructor._eventsByClass.get(event.constructor);
|
||||
sendEvent(event: libData.Event, dst: libData.Address) {
|
||||
let entry = Link._eventsByClass.get(event.constructor as typeof libData.Event);
|
||||
if (!entry) {
|
||||
throw new Error(`Attempt to send unregistered Event ${event.constructor.name}`);
|
||||
}
|
||||
@@ -444,18 +516,23 @@ class Link {
|
||||
this.connector.sendEvent(event, dst);
|
||||
}
|
||||
|
||||
handle(Class, handler) {
|
||||
handle(Class: typeof libData.Request, handler: RequestHandler): void;
|
||||
handle(Class: typeof libData.Event, handler: EventHandler): void;
|
||||
handle(
|
||||
Class: typeof libData.Request | typeof libData.Event,
|
||||
handler: RequestHandler | EventHandler,
|
||||
) {
|
||||
if (Class.type === "request") {
|
||||
this.handleRequest(Class, handler);
|
||||
} else if (Class.type === "event") {
|
||||
this.handleEvent(Class, handler);
|
||||
} else {
|
||||
throw new Error(`Class ${Class.name} has unrecognized type ${Class.type}`);
|
||||
throw new Error(`Class ${(Class as any).name} has unrecognized type ${(Class as any).type}`);
|
||||
}
|
||||
}
|
||||
|
||||
handleRequest(Request, handler) {
|
||||
let entry = this.constructor._requestsByClass.get(Request);
|
||||
handleRequest(Request: typeof libData.Request, handler: RequestHandler) {
|
||||
let entry = Link._requestsByClass.get(Request);
|
||||
if (!entry) {
|
||||
throw new Error(`Unregistered Request class ${Request.name}`);
|
||||
}
|
||||
@@ -465,8 +542,8 @@ class Link {
|
||||
this._requestHandlers.set(Request, handler);
|
||||
}
|
||||
|
||||
fallbackRequest(Request, handler) {
|
||||
let entry = this.constructor._requestsByClass.get(Request);
|
||||
fallbackRequest(Request: typeof libData.Request, handler: RequestHandler) {
|
||||
let entry = Link._requestsByClass.get(Request);
|
||||
if (!entry) {
|
||||
throw new Error(`Unregistered Request class ${Request.name}`);
|
||||
}
|
||||
@@ -476,8 +553,8 @@ class Link {
|
||||
this._requestFallbacks.set(Request, handler);
|
||||
}
|
||||
|
||||
handleEvent(Event, handler) {
|
||||
let entry = this.constructor._eventsByClass.get(Event);
|
||||
handleEvent(Event: typeof libData.Event, handler: EventHandler) {
|
||||
let entry = Link._eventsByClass.get(Event);
|
||||
if (!entry) {
|
||||
throw new Error(`Unregistered Event class ${Event.name}`);
|
||||
}
|
||||
@@ -487,8 +564,8 @@ class Link {
|
||||
this._eventHandlers.set(Event, handler);
|
||||
}
|
||||
|
||||
snoopEvent(Event, handler) {
|
||||
let entry = this.constructor._eventsByClass.get(Event);
|
||||
snoopEvent(Event: typeof libData.Event, handler: EventHandler) {
|
||||
let entry = Link._eventsByClass.get(Event);
|
||||
if (!entry) {
|
||||
throw new Error(`Unregistered Event class ${Event.name}`);
|
||||
}
|
||||
@@ -498,23 +575,23 @@ class Link {
|
||||
this._eventSnoopers.set(Event, handler);
|
||||
}
|
||||
|
||||
static register(Class) {
|
||||
static register(Class: typeof libData.Request | typeof libData.Event) {
|
||||
if (Class.type === "request") {
|
||||
this.registerRequest(Class);
|
||||
} else if (Class.type === "event") {
|
||||
this.registerEvent(Class);
|
||||
} else {
|
||||
throw new Error(`Data class ${Class.name} has unknown type ${Class.type}`);
|
||||
throw new Error(`Data class ${(Class as any).name} has unknown type ${(Class as any).type}`);
|
||||
}
|
||||
}
|
||||
|
||||
static requestFromJSON(Request, name) {
|
||||
static requestFromJSON(Request: typeof libData.Request, name: string) {
|
||||
if (Request.fromJSON) {
|
||||
if (!Request.jsonSchema) {
|
||||
throw new Error(`Request ${name} has static fromJSON but is missing static jsonSchema`);
|
||||
}
|
||||
let validate = libSchema.compile(Request.jsonSchema);
|
||||
return (json) => {
|
||||
let validate = libSchema.compile(Request.jsonSchema as any);
|
||||
return (json: any) => {
|
||||
if (!validate(json)) {
|
||||
throw new libErrors.InvalidMessage(`Request ${name} failed validation`, validate.errors);
|
||||
}
|
||||
@@ -527,7 +604,7 @@ class Link {
|
||||
return () => new Request();
|
||||
}
|
||||
|
||||
static responseFromJSON(Response, name) {
|
||||
static responseFromJSON(Response: libData.Serialisable, name: string) {
|
||||
if (!Response.jsonSchema) {
|
||||
throw new Error(`Response for Request ${name} is missing static jsonSchema`);
|
||||
}
|
||||
@@ -535,8 +612,8 @@ class Link {
|
||||
throw new Error(`Response for Request ${name} is missing static fromJSON`);
|
||||
}
|
||||
|
||||
let validate = libSchema.compile(Response.jsonSchema);
|
||||
return (json) => {
|
||||
let validate = libSchema.compile(Response.jsonSchema as any);
|
||||
return (json: any) => {
|
||||
if (!validate(json)) {
|
||||
throw new libErrors.InvalidMessage(
|
||||
`Response for request ${name} failed validation`, validate.errors
|
||||
@@ -546,7 +623,7 @@ class Link {
|
||||
};
|
||||
}
|
||||
|
||||
static allowedTypes(types, name, side) {
|
||||
static allowedTypes(types: libData.AddressType | libData.AddressType[], name: string, side: "src" | "dst") {
|
||||
if (types === undefined) {
|
||||
throw new Error(`Missing ${side} specification in ${name}`);
|
||||
}
|
||||
@@ -555,7 +632,7 @@ class Link {
|
||||
types = [types];
|
||||
}
|
||||
|
||||
let allowed = new Set();
|
||||
let allowed = new Set<number>();
|
||||
for (let type of types) {
|
||||
let id = libData.Address[type];
|
||||
if (typeof id !== "number") {
|
||||
@@ -569,21 +646,22 @@ class Link {
|
||||
return allowed;
|
||||
}
|
||||
|
||||
static _requestsByName = new Map();
|
||||
static _requestsByClass = new Map();
|
||||
static _requestsByName = new Map<string, RequestEntry>();
|
||||
static _requestsByClass = new Map<typeof libData.Request, RequestEntry>();
|
||||
|
||||
static registerRequest(Request) {
|
||||
static registerRequest(Request: typeof libData.Request) {
|
||||
const name = Request.plugin ? `${Request.plugin}:${Request.name}` : Request.name;
|
||||
if (this._requestsByName.has(name)) {
|
||||
throw new Error(`Request ${name} is already registered`);
|
||||
}
|
||||
|
||||
let entry = {
|
||||
let entry: RequestEntry = {
|
||||
Request,
|
||||
name,
|
||||
requestFromJSON: this.requestFromJSON(Request, name),
|
||||
allowedSrcTypes: this.allowedTypes(Request.src, name, "src"),
|
||||
allowedDstTypes: this.allowedTypes(Request.dst, name, "dst"),
|
||||
responseFromJSON: (_json: any) => undefined,
|
||||
};
|
||||
|
||||
if (
|
||||
@@ -597,21 +675,19 @@ class Link {
|
||||
if (Response) {
|
||||
entry.Response = Response;
|
||||
entry.responseFromJSON = this.responseFromJSON(Response, name);
|
||||
} else {
|
||||
entry.responseFromJSON = () => undefined;
|
||||
}
|
||||
|
||||
this._requestsByName.set(name, entry);
|
||||
this._requestsByClass.set(Request, entry);
|
||||
}
|
||||
|
||||
static eventFromJSON(Event, name) {
|
||||
static eventFromJSON(Event: typeof libData.Event, name: string) {
|
||||
if (Event.fromJSON) {
|
||||
if (!Event.jsonSchema) {
|
||||
throw new Error(`Event ${name} has static fromJSON but is missing static jsonSchema`);
|
||||
}
|
||||
let validate = libSchema.compile(Event.jsonSchema);
|
||||
return (json) => {
|
||||
let validate = libSchema.compile(Event.jsonSchema as any);
|
||||
return (json: any) => {
|
||||
if (!validate(json)) {
|
||||
throw new libErrors.InvalidMessage(`Event ${name} failed validation`, validate.errors);
|
||||
}
|
||||
@@ -621,13 +697,13 @@ class Link {
|
||||
if (Event.jsonSchema) {
|
||||
throw new Error(`Event ${name} has static jsonSchema but is missing static fromJSON`);
|
||||
}
|
||||
return () => new Event();
|
||||
return (json: any) => new Event();
|
||||
}
|
||||
|
||||
static _eventsByName = new Map();
|
||||
static _eventsByClass = new Map();
|
||||
static _eventsByName = new Map<string, EventEntry>();
|
||||
static _eventsByClass = new Map<typeof libData.Event, EventEntry>();
|
||||
|
||||
static registerEvent(Event) {
|
||||
static registerEvent(Event: typeof libData.Event) {
|
||||
const name = Event.plugin ? `${Event.plugin}:${Event.name}` : Event.name;
|
||||
if (this._eventsByName.has(name)) {
|
||||
throw new Error(`Event ${name} is already registered`);
|
||||
@@ -654,10 +730,11 @@ class Link {
|
||||
}
|
||||
|
||||
for (let Class of dataClasses) {
|
||||
// @ts-ignore: TODO Remove when lib/data is migrated to ts
|
||||
Link.register(Class);
|
||||
}
|
||||
|
||||
function registerPluginMessages(pluginInfos) {
|
||||
export function registerPluginMessages(pluginInfos: PluginInfo[]) {
|
||||
for (let pluginInfo of pluginInfos) {
|
||||
for (let Class of pluginInfo.messages || []) {
|
||||
if (Class.plugin !== pluginInfo.name) {
|
||||
@@ -670,9 +747,3 @@ function registerPluginMessages(pluginInfos) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
Link,
|
||||
registerPluginMessages,
|
||||
};
|
||||
@@ -1,15 +1,18 @@
|
||||
"use strict";
|
||||
const core = require("../data/messages_core");
|
||||
const controller = require("../data/messages_controller");
|
||||
const host = require("../data/messages_host");
|
||||
const instance = require("../data/messages_instance");
|
||||
const mod = require("../data/messages_mod");
|
||||
const user = require("../data/messages_user");
|
||||
import * as core from "../data/messages_core";
|
||||
import * as controller from "../data/messages_controller";
|
||||
import * as host from "../data/messages_host";
|
||||
import * as instance from "../data/messages_instance";
|
||||
import * as mod from "../data/messages_mod";
|
||||
import * as user from "../data/messages_user";
|
||||
|
||||
// Event and Request payloads registered with the Link class. Used to decode
|
||||
// the data payload of incomming requsts and event messages into instances of
|
||||
// their proper classes.
|
||||
const dataClasses = [
|
||||
/**
|
||||
* Event and Request payloads registered with the Link class. Used to decode
|
||||
* the data payload of incomming requsts and event messages into instances of
|
||||
* their proper classes.
|
||||
* @internal
|
||||
*/
|
||||
export const dataClasses = [
|
||||
core.PingRequest,
|
||||
|
||||
controller.ControllerConfigGetRequest,
|
||||
@@ -107,5 +110,3 @@ const dataClasses = [
|
||||
user.AccountUpdateEvent,
|
||||
user.UserUpdateEvent,
|
||||
];
|
||||
|
||||
module.exports = { dataClasses };
|
||||
@@ -135,7 +135,7 @@ export async function handleConfigCommand(
|
||||
let command = args._[1];
|
||||
|
||||
if (command === "list") {
|
||||
for (let GroupClass of (instance as any).constructor.groups.values()) {
|
||||
for (let GroupClass of (instance.constructor as typeof libConfig.Config).groups.values()) {
|
||||
for (let def of GroupClass.definitions.values()) {
|
||||
let value = instance.get(def.fullName);
|
||||
print(`${def.fullName} ${JSON.stringify(value)}`);
|
||||
|
||||
@@ -37,15 +37,6 @@ describe("lib/link/connectors", function() {
|
||||
let testConnector = new lib.WebSocketClientConnector("url", 1);
|
||||
testConnector._socket = new mock.MockSocket();
|
||||
|
||||
describe(".register()", function() {
|
||||
it("is abstract", function() {
|
||||
assert.throws(
|
||||
() => testConnector.register(),
|
||||
new Error("Abstract function")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".sendHandshake()", function() {
|
||||
it("calls send on the socket", function() {
|
||||
testConnector._state = "connecting";
|
||||
|
||||
Reference in new issue
Block a user