Port basic lib sub-modules to TypeScript

This commit is contained in:
Hornwitser committed 2023-08-06 18:38:20 +02:00
1 parent e0c197b9bc
commit 15ea9a91dc
14 files changed
+636 -488

No files matched your search

+1 -1
View File
@@ -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",
+5
View File
@@ -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();
+37
View File
@@ -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;
}
+2 -2
View File
@@ -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) {
+28 -7
View File
@@ -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
@@ -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,
};
-4
View File
@@ -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 };
+1 -1
View File
@@ -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)}`);
-9
View File
@@ -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";