Refactor message registration and validation

Split up the action of registring an event or request data class to be
decoded by the Link class and the action of registring a handler for
those requests and events.  This moves the validation work from Link
instantiation time to startup initialisation which makes logic errors
fail on startup instead of failing instantiation of the link when a
connection attempt is made.

Additionally implement a partial validation logic for messages received
using the newly refactored message registration.  This unfortunately
ended up merged with the registration refactor.
This commit is contained in:
Hornwitser committed 2023-06-03 16:47:47 +02:00
1 parent bcd94709d2
commit 7f8a700716
39 files changed
+648 -332

No files matched your search

+2
View File
@@ -28,6 +28,7 @@ const jwt = require("jsonwebtoken");
// homebrew modules
const libErrors = require("@clusterio/lib/errors");
const libFileOps = require("@clusterio/lib/file_ops");
const libLink = require("@clusterio/lib/link");
const libPluginLoader = require("@clusterio/lib/plugin_loader");
const libPrometheus = require("@clusterio/lib/prometheus");
const libConfig = require("@clusterio/lib/config");
@@ -290,6 +291,7 @@ async function initialize() {
logger.info("Loading Plugin info");
parameters.pluginInfos = await libPluginLoader.loadPluginInfos(pluginList);
libLink.registerPluginMessages(parameters.pluginInfos);
libConfig.registerPluginConfigGroups(parameters.pluginInfos);
libConfig.finalizeConfigs();
+4 -4
View File
@@ -19,13 +19,13 @@ class BaseConnection extends libLink.Link {
super(connector);
this.router = new ControllerRouter(controller);
this._controller = controller;
for (let [Request, handler] of controller._registeredRequests) { this.register(Request, handler); }
for (let [Request, handler] of controller._registeredRequests) { this.handle(Request, handler); }
for (let [Request, handler] of controller._fallbackedRequests) { this.fallbackRequest(Request, handler); }
for (let [Event, handler] of controller._registeredEvents) { this.register(Event, handler); }
for (let [Event, handler] of controller._registeredEvents) { this.handle(Event, handler); }
for (let [Event, handler] of controller._snoopedEvents) { this.snoopEvent(Event, handler); }
this.register(libData.ModPackGetRequest, this.handleModPackGetRequest.bind(this));
this.register(libData.ModPackGetDefaultRequest, this.handleModPackGetDefaultRequest.bind(this));
this.handle(libData.ModPackGetRequest, this.handleModPackGetRequest.bind(this));
this.handle(libData.ModPackGetDefaultRequest, this.handleModPackGetDefaultRequest.bind(this));
}
async disconnect(code, reason) {
+52 -52
View File
@@ -104,69 +104,69 @@ class ControlConnection extends BaseConnection {
});
}
this.register(libData.ControllerConfigGetRequest, this.handleControllerConfigGetRequest.bind(this));
this.register(libData.ControllerConfigSetFieldRequest, this.handleControllerConfigSetFieldRequest.bind(this));
this.register(libData.ControllerConfigSetPropRequest, this.handleControllerConfigSetPropRequest.bind(this));
this.register(libData.HostListRequest, this.handleHostListRequest.bind(this));
this.register(libData.HostSetSubscriptionsRequest, this.handleHostSetSubscriptionsRequest.bind(this));
this.register(libData.HostGenerateTokenRequest, this.handleHostGenerateTokenRequest.bind(this));
this.register(libData.HostConfigCreateRequest, this.handleHostConfigCreateRequest.bind(this));
this.register(libData.InstanceDetailsGetRequest, this.handleInstanceDetailsGetRequest.bind(this));
this.register(libData.InstanceDetailsListRequest, this.handleInstanceDetailsListRequest.bind(this));
this.register(
this.handle(libData.ControllerConfigGetRequest, this.handleControllerConfigGetRequest.bind(this));
this.handle(libData.ControllerConfigSetFieldRequest, this.handleControllerConfigSetFieldRequest.bind(this));
this.handle(libData.ControllerConfigSetPropRequest, this.handleControllerConfigSetPropRequest.bind(this));
this.handle(libData.HostListRequest, this.handleHostListRequest.bind(this));
this.handle(libData.HostSetSubscriptionsRequest, this.handleHostSetSubscriptionsRequest.bind(this));
this.handle(libData.HostGenerateTokenRequest, this.handleHostGenerateTokenRequest.bind(this));
this.handle(libData.HostConfigCreateRequest, this.handleHostConfigCreateRequest.bind(this));
this.handle(libData.InstanceDetailsGetRequest, this.handleInstanceDetailsGetRequest.bind(this));
this.handle(libData.InstanceDetailsListRequest, this.handleInstanceDetailsListRequest.bind(this));
this.handle(
libData.InstanceDetailsSetSubscriptionsRequest, this.handleInstanceDetailsSetSubscriptionsRequest.bind(this)
);
this.register(libData.InstanceCreateRequest, this.handleInstanceCreateRequest.bind(this));
this.register(libData.InstanceDeleteRequest, this.handleInstanceDeleteRequest.bind(this));
this.register(libData.InstanceConfigGetRequest, this.handleInstanceConfigGetRequest.bind(this));
this.register(libData.InstanceConfigSetFieldRequest, this.handleInstanceConfigSetFieldRequest.bind(this));
this.register(libData.InstanceConfigSetPropRequest, this.handleInstanceConfigSetPropRequest.bind(this));
this.register(libData.InstanceAssignRequest, this.handleInstanceAssignRequest.bind(this));
this.register(
this.handle(libData.InstanceCreateRequest, this.handleInstanceCreateRequest.bind(this));
this.handle(libData.InstanceDeleteRequest, this.handleInstanceDeleteRequest.bind(this));
this.handle(libData.InstanceConfigGetRequest, this.handleInstanceConfigGetRequest.bind(this));
this.handle(libData.InstanceConfigSetFieldRequest, this.handleInstanceConfigSetFieldRequest.bind(this));
this.handle(libData.InstanceConfigSetPropRequest, this.handleInstanceConfigSetPropRequest.bind(this));
this.handle(libData.InstanceAssignRequest, this.handleInstanceAssignRequest.bind(this));
this.handle(
libData.InstanceSetSaveListSubscriptionsRequest,
this.handleInstanceSetSaveListSubscriptionsRequest.bind(this)
);
this.register(
this.handle(
libData.InstanceRenameSaveRequest, this._controller.sendToHostByInstanceId.bind(this._controller)
);
this.register(libData.InstanceCopySaveRequest, this._controller.sendToHostByInstanceId.bind(this._controller));
this.register(
this.handle(libData.InstanceCopySaveRequest, this._controller.sendToHostByInstanceId.bind(this._controller));
this.handle(
libData.InstanceDeleteSaveRequest, this._controller.sendToHostByInstanceId.bind(this._controller)
);
this.register(libData.InstanceDownloadSaveRequest, this.handleInstanceDownloadSaveRequest.bind(this));
this.register(libData.InstanceTransferSaveRequest, this.handleInstanceTransferSaveRequest.bind(this));
this.register(libData.ModPackListRequest, this.handleModPackListRequest.bind(this));
this.register(libData.ModPackSetSubscriptionsRequest, this.handleModPackSetSubscriptionsRequest.bind(this));
this.register(libData.ModPackCreateRequest, this.handleModPackCreateRequest.bind(this));
this.register(libData.ModPackUpdateRequest, this.handleModPackUpdateRequest.bind(this));
this.register(libData.ModPackDeleteRequest, this.handleModPackDeleteRequest.bind(this));
this.register(libData.ModGetRequest, this.handleModGetRequest.bind(this));
this.register(libData.ModListRequest, this.handleModListRequest.bind(this));
this.register(libData.ModSearchRequest, this.handleModSearchRequest.bind(this));
this.register(libData.ModSetSubscriptionsRequest, this.handleModSetSubscriptionsRequest.bind(this));
this.register(libData.ModDownloadRequest, this.handleModDownloadRequest.bind(this));
this.register(libData.ModDeleteRequest, this.handleModDeleteRequest.bind(this));
this.register(libData.LogSetSubscriptionsRequest, this.handleLogSetSubscriptionsRequest.bind(this));
this.register(libData.LogQueryRequest, this.handleLogQueryRequest.bind(this));
this.register(libData.PermissionListRequest, this.handlePermissionListRequest.bind(this));
this.register(libData.RoleListRequest, this.handleRoleListRequest.bind(this));
this.register(libData.RoleCreateRequest, this.handleRoleCreateRequest.bind(this));
this.register(libData.RoleUpdateRequest, this.handleRoleUpdateRequest.bind(this));
this.register(
this.handle(libData.InstanceDownloadSaveRequest, this.handleInstanceDownloadSaveRequest.bind(this));
this.handle(libData.InstanceTransferSaveRequest, this.handleInstanceTransferSaveRequest.bind(this));
this.handle(libData.ModPackListRequest, this.handleModPackListRequest.bind(this));
this.handle(libData.ModPackSetSubscriptionsRequest, this.handleModPackSetSubscriptionsRequest.bind(this));
this.handle(libData.ModPackCreateRequest, this.handleModPackCreateRequest.bind(this));
this.handle(libData.ModPackUpdateRequest, this.handleModPackUpdateRequest.bind(this));
this.handle(libData.ModPackDeleteRequest, this.handleModPackDeleteRequest.bind(this));
this.handle(libData.ModGetRequest, this.handleModGetRequest.bind(this));
this.handle(libData.ModListRequest, this.handleModListRequest.bind(this));
this.handle(libData.ModSearchRequest, this.handleModSearchRequest.bind(this));
this.handle(libData.ModSetSubscriptionsRequest, this.handleModSetSubscriptionsRequest.bind(this));
this.handle(libData.ModDownloadRequest, this.handleModDownloadRequest.bind(this));
this.handle(libData.ModDeleteRequest, this.handleModDeleteRequest.bind(this));
this.handle(libData.LogSetSubscriptionsRequest, this.handleLogSetSubscriptionsRequest.bind(this));
this.handle(libData.LogQueryRequest, this.handleLogQueryRequest.bind(this));
this.handle(libData.PermissionListRequest, this.handlePermissionListRequest.bind(this));
this.handle(libData.RoleListRequest, this.handleRoleListRequest.bind(this));
this.handle(libData.RoleCreateRequest, this.handleRoleCreateRequest.bind(this));
this.handle(libData.RoleUpdateRequest, this.handleRoleUpdateRequest.bind(this));
this.handle(
libData.RoleGrantDefaultPermissionsRequest, this.handleRoleGrantDefaultPermissionsRequest.bind(this)
);
this.register(libData.RoleDeleteRequest, this.handleRoleDeleteRequest.bind(this));
this.register(libData.UserGetRequest, this.handleUserGetRequest.bind(this));
this.register(libData.UserListRequest, this.handleUserListRequest.bind(this));
this.register(libData.UserSetSubscriptionsRequest, this.handleUserSetSubscriptionsRequest.bind(this));
this.register(libData.UserCreateRequest, this.handleUserCreateRequest.bind(this));
this.register(libData.UserRevokeTokenRequest, this.handleUserRevokeTokenRequest.bind(this));
this.register(libData.UserUpdateRolesRequest, this.handleUserUpdateRolesRequest.bind(this));
this.register(libData.UserSetAdminRequest, this.handleUserSetAdminRequest.bind(this));
this.register(libData.UserSetBannedRequest, this.handleUserSetBannedRequest.bind(this));
this.register(libData.UserSetWhitelistedRequest, this.handleUserSetWhitelistedRequest.bind(this));
this.register(libData.UserDeleteRequest, this.handleUserDeleteRequest.bind(this));
this.register(libData.DebugDumpWsRequest, this.handleDebugDumpWsRequest.bind(this));
this.handle(libData.RoleDeleteRequest, this.handleRoleDeleteRequest.bind(this));
this.handle(libData.UserGetRequest, this.handleUserGetRequest.bind(this));
this.handle(libData.UserListRequest, this.handleUserListRequest.bind(this));
this.handle(libData.UserSetSubscriptionsRequest, this.handleUserSetSubscriptionsRequest.bind(this));
this.handle(libData.UserCreateRequest, this.handleUserCreateRequest.bind(this));
this.handle(libData.UserRevokeTokenRequest, this.handleUserRevokeTokenRequest.bind(this));
this.handle(libData.UserUpdateRolesRequest, this.handleUserUpdateRolesRequest.bind(this));
this.handle(libData.UserSetAdminRequest, this.handleUserSetAdminRequest.bind(this));
this.handle(libData.UserSetBannedRequest, this.handleUserSetBannedRequest.bind(this));
this.handle(libData.UserSetWhitelistedRequest, this.handleUserSetWhitelistedRequest.bind(this));
this.handle(libData.UserDeleteRequest, this.handleUserDeleteRequest.bind(this));
this.handle(libData.DebugDumpWsRequest, this.handleDebugDumpWsRequest.bind(this));
}
async handleControllerConfigGetRequest() {
+18 -5
View File
@@ -13,6 +13,7 @@ const libConfig = require("@clusterio/lib/config");
const libData = require("@clusterio/lib/data");
const libErrors = require("@clusterio/lib/errors");
const libFileOps = require("@clusterio/lib/file_ops");
const libLink = require("@clusterio/lib/link");
const libLoggingUtils = require("@clusterio/lib/logging_utils");
const libPlugin = require("@clusterio/lib/plugin");
const libPluginLoader = require("@clusterio/lib/plugin_loader");
@@ -970,17 +971,20 @@ class Controller {
};
}
register(Class, handler) {
handle(Class, handler) {
if (Class.type === "request") {
this.registerRequest(Class, handler);
this.handleRequest(Class, handler);
} else if (Class.type === "event") {
this.registerEvent(Class, handler);
this.handleEvent(Class, handler);
} else {
throw new Error(`Class ${Class.name} has unrecognized type ${Class.type}`);
}
}
registerRequest(Request, handler) {
handleRequest(Request, handler) {
if (!libLink.Link._requestsByClass.has(Request)) {
throw new Error(`Unregistered Request class ${Request.name}`);
}
if (this._registeredRequests.has(Request)) {
throw new Error(`Request ${Request.name} is already registered`);
}
@@ -988,13 +992,19 @@ class Controller {
}
fallbackRequest(Request, handler) {
if (!libLink.Link._requestsByClass.has(Request)) {
throw new Error(`Unregistered Request class ${Request.name}`);
}
if (this._fallbackedRequests.has(Request)) {
throw new Error(`Request ${Request.name} is already fallbacked`);
}
this._fallbackedRequests.set(Request, handler);
}
registerEvent(Event, handler) {
handleEvent(Event, handler) {
if (!libLink.Link._eventsByClass.has(Event)) {
throw new Error(`Unregistered Event class ${Event.name}`);
}
if (this._registeredEvents.has(Event)) {
throw new Error(`Event ${Event.name} is already registered`);
}
@@ -1002,6 +1012,9 @@ class Controller {
}
snoopEvent(Event, handler) {
if (!libLink.Link._eventsByClass.has(Event)) {
throw new Error(`Unregistered Event class ${Event.name}`);
}
if (this._snoopedEvents.has(Event)) {
throw new Error(`Event ${Event.name} is already snooped`);
}
+5 -5
View File
@@ -58,11 +58,11 @@ class HostConnection extends BaseConnection {
}
});
this.register(libData.InstanceStatusChangedEvent, this.handleInstanceStatusChangedEvent.bind(this));
this.register(libData.InstancesUpdateRequest, this.handleInstancesUpdateRequest.bind(this));
this.register(libData.InstanceSaveListUpdateEvent, this.handleInstanceSaveListUpdateEvent.bind(this));
this.register(libData.LogMessageEvent, this.handleLogMessageEvent.bind(this));
this.register(libData.InstancePlayerUpdateEvent, this.handleInstancePlayerUpdateEvent.bind(this));
this.handle(libData.InstanceStatusChangedEvent, this.handleInstanceStatusChangedEvent.bind(this));
this.handle(libData.InstancesUpdateRequest, this.handleInstancesUpdateRequest.bind(this));
this.handle(libData.InstanceSaveListUpdateEvent, this.handleInstanceSaveListUpdateEvent.bind(this));
this.handle(libData.LogMessageEvent, this.handleLogMessageEvent.bind(this));
this.handle(libData.InstancePlayerUpdateEvent, this.handleInstancePlayerUpdateEvent.bind(this));
}
_checkPluginVersions() {
+3 -2
View File
@@ -76,8 +76,8 @@ class Control extends libLink.Link {
*/
this.keepOpen = false;
this.register(libData.LogMessageEvent, this.handleLogMessageEvent.bind(this));
this.register(libData.DebugWsMessageEvent, this.handleDebugWsMessageEvent.bind(this));
this.handle(libData.LogMessageEvent, this.handleLogMessageEvent.bind(this));
this.handle(libData.DebugWsMessageEvent, this.handleDebugWsMessageEvent.bind(this));
}
async setLogSubscriptions({
@@ -114,6 +114,7 @@ class Control extends libLink.Link {
async function loadPlugins(pluginList) {
let pluginInfos = await libPluginLoader.loadPluginInfos(pluginList);
libLink.registerPluginMessages(pluginInfos);
libConfig.registerPluginConfigGroups(pluginInfos);
libConfig.finalizeConfigs();
+1
View File
@@ -141,6 +141,7 @@ async function startHost() {
logger.info("Loading Plugin info");
let pluginInfos = await libPluginLoader.loadPluginInfos(pluginList);
libLink.registerPluginMessages(pluginInfos);
libConfig.registerPluginConfigGroups(pluginInfos);
libConfig.finalizeConfigs();
+11 -11
View File
@@ -307,21 +307,21 @@ class Host extends libLink.Link {
});
}
this.register(libData.SyncUserListsEvent, this.handleSyncUserListsEvent.bind(this));
this.handle(libData.SyncUserListsEvent, this.handleSyncUserListsEvent.bind(this));
this.snoopEvent(libData.InstanceAdminlistUpdateEvent, this.handleAdminlistUpdateEvent.bind(this));
this.snoopEvent(libData.InstanceBanlistUpdateEvent, this.handleBanlistUpdateEvent.bind(this));
this.snoopEvent(libData.InstanceWhitelistUpdateEvent, this.handleWhitelistUpdateEvent.bind(this));
this.register(libData.InstanceAssignInternalRequest, this.handleInstanceAssignInternalRequest.bind(this));
this.register(libData.InstanceUnassignInternalRequest, this.handleInstanceUnassignInternalRequest.bind(this));
this.register(libData.HostMetricsRequest, this.handleHostMetricsRequest.bind(this));
this.handle(libData.InstanceAssignInternalRequest, this.handleInstanceAssignInternalRequest.bind(this));
this.handle(libData.InstanceUnassignInternalRequest, this.handleInstanceUnassignInternalRequest.bind(this));
this.handle(libData.HostMetricsRequest, this.handleHostMetricsRequest.bind(this));
this.fallbackRequest(libData.InstanceListSavesRequest, this.fallbackInstanceListSavesRequest.bind(this));
this.register(libData.InstanceRenameSaveRequest, this.handleInstanceRenameSaveRequest.bind(this));
this.register(libData.InstanceCopySaveRequest, this.handleInstanceCopySaveRequest.bind(this));
this.register(libData.InstanceTransferSaveRequest, this.handleInstanceTransferSaveRequest.bind(this));
this.register(libData.InstanceDeleteSaveRequest, this.handleInstanceDeleteSaveRequest.bind(this));
this.register(libData.InstancePullSaveRequest, this.handleInstancePullSaveRequest.bind(this));
this.register(libData.InstancePushSaveRequest, this.handleInstancePushSaveRequest.bind(this));
this.register(libData.InstanceDeleteInternalRequest, this.handleInstanceDeleteInternalRequest.bind(this));
this.handle(libData.InstanceRenameSaveRequest, this.handleInstanceRenameSaveRequest.bind(this));
this.handle(libData.InstanceCopySaveRequest, this.handleInstanceCopySaveRequest.bind(this));
this.handle(libData.InstanceTransferSaveRequest, this.handleInstanceTransferSaveRequest.bind(this));
this.handle(libData.InstanceDeleteSaveRequest, this.handleInstanceDeleteSaveRequest.bind(this));
this.handle(libData.InstancePullSaveRequest, this.handleInstancePullSaveRequest.bind(this));
this.handle(libData.InstancePushSaveRequest, this.handleInstancePushSaveRequest.bind(this));
this.handle(libData.InstanceDeleteInternalRequest, this.handleInstanceDeleteInternalRequest.bind(this));
}
async _createNewInstanceDir(name) {
+15 -15
View File
@@ -211,23 +211,23 @@ class Instance extends libLink.Link {
this._hadPlayersOnline = false;
this._playerAutosaveSlot = 1;
this.register(libData.InstanceExtractPlayersRequest, this.handleInstanceExtractPlayersRequest.bind(this));
this.register(libData.InstanceAdminlistUpdateEvent, this.handleInstanceAdminlistUpdateEvent.bind(this));
this.register(libData.InstanceBanlistUpdateEvent, this.handleInstanceBanlistUpdateEvent.bind(this));
this.register(libData.InstanceWhitelistUpdateEvent, this.handleInstanceWhitelistUpdateEvent.bind(this));
this.register(libData.ControllerConnectionEvent, this.handleControllerConnectionEvent.bind(this));
this.register(
this.handle(libData.InstanceExtractPlayersRequest, this.handleInstanceExtractPlayersRequest.bind(this));
this.handle(libData.InstanceAdminlistUpdateEvent, this.handleInstanceAdminlistUpdateEvent.bind(this));
this.handle(libData.InstanceBanlistUpdateEvent, this.handleInstanceBanlistUpdateEvent.bind(this));
this.handle(libData.InstanceWhitelistUpdateEvent, this.handleInstanceWhitelistUpdateEvent.bind(this));
this.handle(libData.ControllerConnectionEvent, this.handleControllerConnectionEvent.bind(this));
this.handle(
libData.PrepareControllerDisconnectRequest, this.handlePrepareControllerDisconnectRequest.bind(this)
);
this.register(libData.InstanceMetricsRequest, this.handleInstanceMetricsRequest.bind(this));
this.register(libData.InstanceStartRequest, this.handleInstanceStartRequest.bind(this));
this.register(libData.InstanceLoadScenarioRequest, this.handleInstanceLoadScenarioRequest.bind(this));
this.register(libData.InstanceListSavesRequest, this.handleInstanceListSavesRequest.bind(this));
this.register(libData.InstanceCreateSaveRequest, this.handleInstanceCreateSaveRequest.bind(this));
this.register(libData.InstanceExportDataRequest, this.handleInstanceExportDataRequest.bind(this));
this.register(libData.InstanceStopRequest, this.handleInstanceStopRequest.bind(this));
this.register(libData.InstanceKillRequest, this.handleInstanceKillRequest.bind(this));
this.register(libData.InstanceSendRconRequest, this.handleInstanceSendRconRequest.bind(this));
this.handle(libData.InstanceMetricsRequest, this.handleInstanceMetricsRequest.bind(this));
this.handle(libData.InstanceStartRequest, this.handleInstanceStartRequest.bind(this));
this.handle(libData.InstanceLoadScenarioRequest, this.handleInstanceLoadScenarioRequest.bind(this));
this.handle(libData.InstanceListSavesRequest, this.handleInstanceListSavesRequest.bind(this));
this.handle(libData.InstanceCreateSaveRequest, this.handleInstanceCreateSaveRequest.bind(this));
this.handle(libData.InstanceExportDataRequest, this.handleInstanceExportDataRequest.bind(this));
this.handle(libData.InstanceStopRequest, this.handleInstanceStopRequest.bind(this));
this.handle(libData.InstanceKillRequest, this.handleInstanceKillRequest.bind(this));
this.handle(libData.InstanceSendRconRequest, this.handleInstanceSendRconRequest.bind(this));
}
_watchPlayerJoinsByChat() {
+1 -1
View File
@@ -14,7 +14,7 @@ class InstanceConnection extends libLink.Link {
this.plugins = new Map();
this.status = "stopped";
this.register(libData.InstanceInitialisedEvent, this.handleInstanceInitialisedEvent.bind(this));
this.handle(libData.InstanceInitialisedEvent, this.handleInstanceInitialisedEvent.bind(this));
this.snoopEvent(libData.InstanceStatusChangedEvent, this.snoopInstanceStatusChangedEvent.bind(this));
}
+2 -2
View File
@@ -301,8 +301,8 @@ LogQueryRequest.Response = class Response {
/** @memberof module:lib/data */
class LogMessageEvent {
static type = "event";
static src = "host";
static dst = "controller";
static src = ["host", "controller"];
static dst = ["controller", "control"];
/** @type {object} */
info;
+1 -1
View File
@@ -848,7 +848,7 @@ Message.validate = libSchema.compile(Message.jsonSchema);
class PingRequest {
static type = "request";
static src = ["controller", "host", "control"];
static dest = ["controller", "host", "control"];
static dst = ["controller", "host", "control"];
static permission = null;
}
+9 -9
View File
@@ -461,7 +461,7 @@ class InstanceSetSaveListSubscriptionsRequest {
/** @memberof module:lib/data */
class InstanceSaveListUpdateEvent {
static type = "event";
static src = ["instance", "host"];
static src = ["instance", "host", "controller"];
static dst = ["controller", "control"];
/** @type {number} */
@@ -533,8 +533,8 @@ class InstanceCreateSaveRequest {
/** @memberof module:lib/data */
class InstanceRenameSaveRequest {
static type = "request";
static src = "control";
static dst = "host";
static src = ["control", "controller"];
static dst = ["controller", "host"];
static permission = "core.instance.save.rename";
/** @type {number} */
@@ -568,8 +568,8 @@ class InstanceRenameSaveRequest {
/** @memberof module:lib/data */
class InstanceCopySaveRequest {
static type = "request";
static src = "control";
static dst = "host";
static src = ["control", "controller"];
static dst = ["controller", "host"];
static permission = "core.instance.save.copy";
/** @type {number} */
@@ -603,8 +603,8 @@ class InstanceCopySaveRequest {
/** @memberof module:lib/data */
class InstanceDeleteSaveRequest {
static type = "request";
static src = "control";
static dst = "host";
static src = ["control", "controller"];
static dst = ["controller", "host"];
static permission = "core.instance.save.delete";
/** @type {number} */
@@ -667,7 +667,7 @@ class InstanceDownloadSaveRequest {
/** @memberof module:lib/data */
class InstanceTransferSaveRequest {
static type = "request";
static src = "control";
static src = ["control", "controller"];
static dst = ["controller", "host"];
static permission = "core.instance.save.transfer";
@@ -1081,7 +1081,7 @@ class InstanceInitialisedEvent {
/** @memberof module:lib/data */
class InstanceStatusChangedEvent {
static type = "event";
static src = "instance";
static src = ["instance", "host"];
static dst = "controller";
/** @type {integer} */
+19 -1
View File
@@ -266,11 +266,11 @@ class RawUser { // TODO refactor into lib/user.User
properties: {
name: { type: "string" },
roles: { type: "array", items: { type: "integer" } },
instances: { type: "array", items: { type: "integer" } },
isAdmin: { type: "boolean" },
isBanned: { type: "boolean" },
isWhitelisted: { type: "boolean" },
banReason: { type: "string" },
instances: { type: "array", items: { type: "integer" } },
isDeleted: { type: "boolean" },
playerStats: PlayerStats.jsonSchema,
instanceStats: {
@@ -299,6 +299,24 @@ class RawUser { // TODO refactor into lib/user.User
json.isDeleted, playerStats, instanceStats
);
}
toJSON() {
const json = {
name: this.name,
roles: this.roles,
instances: this.instances,
isAdmin: this.isAdmin,
isBanned: this.isBanned,
isWhitelisted: this.isWhitelisted,
banReason: this.banReason,
isDeleted: this.isDeleted,
playerStats: this.playerStats,
};
if (this.instanceStats) {
json.instanceStats = [...this.instanceStats];
}
return json;
}
}
/* @memberof module:lib/data */
+6 -4
View File
@@ -36,8 +36,9 @@ class BaseConnector extends events.EventEmitter {
let seq = this._seq;
this._seq += 1;
let src = new libData.Address(this.src.type, this.src.id, requestId);
let name = request.constructor.name;
let hasData = Boolean(request.constructor.jsonSchema);
let Request = request.constructor;
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);
this.send(message);
return seq;
@@ -62,8 +63,9 @@ class BaseConnector extends events.EventEmitter {
sendEvent(event, dst) {
let seq = this._seq;
this._seq += 1;
let name = event.constructor.name;
let hasData = Boolean(event.constructor.jsonSchema);
let Event = event.constructor;
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);
this.send(message);
return seq;
+2
View File
@@ -10,6 +10,8 @@ const connectors = require("./connectors");
module.exports = {
...link,
...connectors,
// migrate: Allow info for plugins from before link refactor to load.
Event: class Event {},
Request: class Request {},
};
+258 -126
View File
@@ -5,6 +5,7 @@ const libData = require("../data");
const libErrors = require("../errors");
const { logger } = require("../logging");
const libSchema = require("../schema");
const { dataClasses } = require("./messages");
// Some definitions for the terminology used here:
// link: Either side of a controller - client connection
@@ -12,8 +13,6 @@ const libSchema = require("../schema");
// connection: the controller side of a link
// client: the side that is not the controller of a link
// message: the complete object sent using the 'message' event
// data: the data property of a message, essentially the payload.
// type: the type property of a message, identifies what to expect in data.
/**
* Common interface for server and client connections
@@ -26,15 +25,14 @@ class Link {
this.router = undefined;
this.validateSent = true;
this._registeredRequests = new Map();
this._fallbackedRequests = new Map();
this._registeredEvents = new Map();
this._snoopedEvents = new Map();
this._requestHandlers = new Map();
this._requestFallbacks = new Map();
this._eventHandlers = new Map();
this._eventSnoopers = new Map();
this._pendingRequests = new Map();
this._nextRequestId = 1;
this.register(libData.PingRequest, () => {});
this.handle(libData.PingRequest, () => {});
// Process messages received by the connector
connector.on("message", payload => {
@@ -97,28 +95,22 @@ class Link {
throw new libErrors.InvalidMessage(`Unhandled message type ${message.type}`);
}
if (message.type === "event" && this._snoopedEvents.has(message.name)) {
let event = this._snoopedEvents.get(message.name);
event.handler(event.eventFromJSON(message.data), message.src, message.dst).catch(err => {
let entry = this._validateMessage(message);
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.dst.addressedTo(this.connector.src)) {
this._routeMessage(message);
this._routeMessage(message, entry);
return;
}
if (message.type === "request") {
let request = this._registeredRequests.get(message.name);
if (!request) {
this.connector.sendResponseError(
new libData.ResponseError(`Unrecognized request ${message.name}`),
message.src,
);
return;
}
this._processRequest(message, request);
this._processRequest(message, entry, this._requestHandlers.get(entry.Request));
} else if (message.type === "response") {
this._processResponse(message);
@@ -127,11 +119,61 @@ class Link {
this._processResponseError(message);
} else if (message.type === "event") {
this._processEvent(message);
this._processEvent(message, entry);
}
}
_routeMessage(message) {
_validateMessage(message) {
if (message.type === "request") {
let entry = this.constructor._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);
throw err;
}
if (!entry.allowedSrcTypes.has(message.src.type)) {
let err = new libErrors.InvalidMessage(`Source ${message.src} is not allowed for ${message.name}`);
this.connector.sendResponseError(new libData.ResponseError(err.message, err.code), message.src);
throw err;
}
if (!entry.allowedDstTypes.has(message.dst.type)) {
let err = new libErrors.InvalidMessage(`Destination ${message.dst} is not allowed for ${message.name}`);
this.connector.sendResponseError(new libData.ResponseError(err.message, err.code), message.src);
throw err;
}
return entry;
} else if (message.type === "response") {
return undefined;
} else if (message.type === "responseError") {
return undefined;
} else if (message.type === "event") {
let entry = this.constructor._eventsByName.get(message.name);
if (!entry) {
throw new libErrors.InvalidMessage(`Unrecognized event ${message.name}`);
}
if (!entry.allowedSrcTypes.has(message.src.type)) {
throw new libErrors.InvalidMessage(`Source ${message.src} is not allowed for ${message.name}`);
}
if (message.dst.type === libData.Address.broadcast) {
if (!entry.allowedDstTypes.has(message.dst.id)) {
throw new libErrors.InvalidMessage(`Destination ${message.dst} is not allowed for ${message.name}`);
}
} else {
// eslint-disable-next-line no-lonely-if
if (!entry.allowedDstTypes.has(message.dst.type)) {
throw new libErrors.InvalidMessage(`Destination ${message.dst} is not allowed for ${message.name}`);
}
}
return entry;
}
throw new Error("Should be unreachable");
}
_routeMessage(message, entry) {
if (!this.router) {
let err = new libErrors.InvalidMessage(
`Received message addressed to ${message.dst} but this link does not route messages`
@@ -144,7 +186,7 @@ class Link {
throw err;
}
let fallback = this._fallbackedRequests.get(message.name);
let fallback = message.type === "request" ? this._requestFallbacks.get(entry.Request) : undefined;
if (this.router.forwardMessage(this, message, Boolean(fallback))) {
return;
}
@@ -154,20 +196,20 @@ class Link {
if (message.type !== "request") {
throw new Error(`Router requested fallback handling of unsupported message type ${message.type}`);
}
this._processRequest(message, fallback);
this._processRequest(message, entry, fallback);
}
_processRequest(message, request) {
if (!request.handler) {
_processRequest(message, entry, handler) {
if (!handler) {
this.connector.sendResponseError(
new libData.ResponseError(`No handler registered for ${request.Request.name}`), message.src
new libData.ResponseError(`No handler for ${entry.Request.name}`), message.src
);
return;
}
let response;
try {
response = request.handler(request.requestFromJSON(message.data), message.src, message.dst);
response = handler(entry.requestFromJSON(message.data), message.src, message.dst);
} catch (err) {
if (err.errors) {
logger.error(JSON.stringify(err.errors, null, 4));
@@ -185,17 +227,22 @@ class Link {
response.then(
result => {
if (this.validateSent) {
if (request.Response) {
request.responseFromJSON(JSON.parse(JSON.stringify(result)));
if (entry.Response) {
entry.responseFromJSON(JSON.parse(JSON.stringify(result)));
} else if (result !== undefined) {
throw new Error(`Expected empty response from ${request.Request.name} handler`);
throw new Error(`Expected empty response from ${entry.Request.name} handler`);
}
}
this.connector.sendResponse(result, message.src);
}
).catch(
err => {
if (!(err instanceof libErrors.RequestError)) {
if (err instanceof libErrors.InvalidMessage) {
logger.error(err.message);
if (err.errors) {
logger.error(JSON.stringify(err.errors, null, 4));
}
} else if (!(err instanceof libErrors.RequestError)) {
logger.error(`Unexpected error responding to ${message.name}:\n${err.stack}`);
}
this.connector.sendResponseError(
@@ -232,16 +279,13 @@ class Link {
pending.reject(new libErrors.RequestError(message.data.message, message.data.code, message.data.stack));
}
_processEvent(message) {
let event = this._registeredEvents.get(message.name);
if (!event) {
throw new libErrors.InvalidMessage(`Unrecognized event ${message.name}`);
_processEvent(message, entry) {
let handler = this._eventHandlers.get(entry.Event);
if (!handler) {
throw new libErrors.InvalidMessage(`Unhandled event ${message.name}`);
}
if (!event.handler) {
return;
}
event.handler(event.eventFromJSON(message.data), message.src, message.dst).catch(err => {
handler(entry.eventFromJSON(message.data), message.src, message.dst).catch(err => {
logger.error(`Unexpected error handling ${message.name}:\n${err.stack}`);
});
}
@@ -296,18 +340,16 @@ class Link {
}
sendRequest(request, dst) {
let name = request.constructor.name;
if (!this._registeredRequests.has(name)) {
// XXX should this be allowed?
this.registerRequest(request.constructor);
let entry = this.constructor._requestsByClass.get(request.constructor);
if (!entry) {
throw new Error(`Attempt to send unregistered Request ${request.constructor.name}`);
}
let registeredRequest = this._registeredRequests.get(name);
if (this.validateSent) {
registeredRequest.requestFromJSON(JSON.parse(JSON.stringify(request)));
entry.requestFromJSON(JSON.parse(JSON.stringify(request)));
}
let pending = {
request: registeredRequest,
request: entry,
};
pending.promise = new Promise((resolve, reject) => {
pending.resolve = resolve;
@@ -321,141 +363,231 @@ class Link {
}
sendEvent(event, dst) {
let entry = this.constructor._eventsByClass.get(event.constructor);
if (!entry) {
throw new Error(`Attempt to send unregistered Event ${event.constructor.name}`);
}
if (this.validateSent) {
let name = event.constructor.name;
if (!this._registeredEvents.has(name)) {
// XXX should this be allowed?
this.registerEvent(event.constructor);
}
this._registeredEvents.get(name).eventFromJSON(JSON.parse(JSON.stringify(event)));
entry.eventFromJSON(JSON.parse(JSON.stringify(event)));
}
this.connector.sendEvent(event, dst);
}
register(Class, handler) {
handle(Class, handler) {
if (Class.type === "request") {
this.registerRequest(Class, handler);
this.handleRequest(Class, handler);
} else if (Class.type === "event") {
this.registerEvent(Class, handler);
this.handleEvent(Class, handler);
} else {
throw new Error(`Class ${Class.name} has unrecognized type ${Class.type}`);
}
}
_requestEntry(Request, handler) {
let name = Request.name;
let entry = {
Request,
handler,
};
handleRequest(Request, handler) {
let entry = this.constructor._requestsByClass.get(Request);
if (!entry) {
throw new Error(`Unregistered Request class ${Request.name}`);
}
if (this._requestHandlers.has(Request)) {
throw new Error(`Request ${entry.name} is already registered`);
}
this._requestHandlers.set(Request, handler);
}
fallbackRequest(Request, handler) {
let entry = this.constructor._requestsByClass.get(Request);
if (!entry) {
throw new Error(`Unregistered Request class ${Request.name}`);
}
if (this._requestFallbacks.has(Request)) {
throw new Error(`Request ${entry.name} is already fallbacked`);
}
this._requestFallbacks.set(Request, handler);
}
handleEvent(Event, handler) {
let entry = this.constructor._eventsByClass.get(Event);
if (!entry) {
throw new Error(`Unregistered Event class ${Event.name}`);
}
if (this._eventHandlers.has(Event)) {
throw new Error(`Event ${entry.name} is already registered`);
}
this._eventHandlers.set(Event, handler);
}
snoopEvent(Event, handler) {
let entry = this.constructor._eventsByClass.get(Event);
if (!entry) {
throw new Error(`Unregistered Event class ${Event.name}`);
}
if (this._eventSnoopers.has(Event)) {
throw new Error(`Event ${entry.name} is already snooped`);
}
this._eventSnoopers.set(Event, handler);
}
static register(Class) {
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}`);
}
}
static requestFromJSON(Request, name) {
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);
entry.requestFromJSON = (json) => {
return (json) => {
if (!validate(json)) {
throw new libErrors.InvalidMessage(`Request ${name} failed validation`, validate.errors);
}
return Request.fromJSON(json);
};
} else if (Request.jsonSchema) {
throw new Error(`Request ${name} has static jsonSchema but is missing static fromJSON`);
} else {
entry.requestFromJSON = () => new Request();
}
if (Request.jsonSchema) {
throw new Error(`Request ${name} has static jsonSchema but is missing static fromJSON`);
}
return () => new Request();
}
static responseFromJSON(Response, name) {
if (!Response.jsonSchema) {
throw new Error(`Response for Request ${name} is missing static jsonSchema`);
}
if (!Response.fromJSON) {
throw new Error(`Response for Request ${name} is missing static fromJSON`);
}
let validate = libSchema.compile(Response.jsonSchema);
return (json) => {
if (!validate(json)) {
throw new libErrors.InvalidMessage(
`Response for request ${name} failed validation`, validate.errors
);
}
return Response.fromJSON(json);
};
}
static allowedTypes(types, name, side) {
if (types === undefined) {
throw new Error(`Missing ${side} specification in ${name}`);
}
if (!(types instanceof Array)) {
types = [types];
}
let allowed = new Set();
for (let type of types) {
let id = libData.Address[type];
if (typeof id !== "number") {
throw new Error(`Invalid type ${type} in ${side} specification of ${name}`);
}
if (id === libData.Address.broadcast) {
throw new Error(`${side} specification may not use the broadcast type in ${name}`);
}
allowed.add(id);
}
return allowed;
}
static _requestsByName = new Map();
static _requestsByClass = new Map();
static registerRequest(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 = {
Request,
name,
requestFromJSON: this.requestFromJSON(Request, name),
allowedSrcTypes: this.allowedTypes(Request.src, name, "src"),
allowedDstTypes: this.allowedTypes(Request.dst, name, "dst"),
};
let Response = Request.Response;
if (Response) {
if (!Response.jsonSchema) {
throw new Error(`Response for Request ${name} is missing static jsonSchema`);
}
if (!Response.fromJSON) {
throw new Error(`Response for Request ${name} is missing static fromJSON`);
}
let validate = libSchema.compile(Response.jsonSchema);
entry.responseFromJSON = (json) => {
if (!validate(json)) {
throw new libErrors.InvalidMessage(
`Response for request ${name} failed validation`, validate.errors
);
}
return Response.fromJSON(json);
};
entry.Response = Response;
entry.responseFromJSON = this.responseFromJSON(Response, name);
} else {
entry.responseFromJSON = () => undefined;
}
return entry;
this._requestsByName.set(name, entry);
this._requestsByClass.set(Request, entry);
}
registerRequest(Request, handler) {
let name = Request.name;
if (this._registeredRequests.has(name)) {
throw new Error(`Request ${name} is already registered`);
}
let request = this._requestEntry(Request, handler);
this._registeredRequests.set(name, request);
}
fallbackRequest(Request, handler) {
let name = Request.name;
if (this._fallbackedRequests.has(name)) {
throw new Error(`Request ${name} is already fallbacked`);
}
let request = this._requestEntry(Request, handler);
this._fallbackedRequests.set(name, request);
}
_createEventFromJSON(Event) {
static eventFromJSON(Event, name) {
if (Event.fromJSON) {
if (!Event.jsonSchema) {
throw new Error(`Event ${Event.name} has static fromJSON but is missing static jsonSchema`);
throw new Error(`Event ${name} has static fromJSON but is missing static jsonSchema`);
}
let validate = libSchema.compile(Event.jsonSchema);
return (json) => {
if (!validate(json)) {
throw new libErrors.InvalidMessage(`Event ${Event.name} failed validation`, validate.errors);
throw new libErrors.InvalidMessage(`Event ${name} failed validation`, validate.errors);
}
return Event.fromJSON(json);
};
}
if (Event.jsonSchema) {
throw new Error(`Event ${Event.name} has static jsonSchema but is missing static fromJSON`);
throw new Error(`Event ${name} has static jsonSchema but is missing static fromJSON`);
}
return () => new Event();
}
registerEvent(Event, handler) {
if (this._registeredEvents.has(Event.name)) {
throw new Error(`Event ${Event.name} is already registered`);
}
let event = {
Event,
handler,
eventFromJSON: this._createEventFromJSON(Event),
};
this._registeredEvents.set(Event.name, event);
}
static _eventsByName = new Map();
static _eventsByClass = new Map();
snoopEvent(Event, handler) {
let name = Event.name;
if (this._snoopedEvents.has(name)) {
throw new Error(`Event ${name} is already snooped`);
static registerEvent(Event) {
const name = Event.plugin ? `${Event.plugin}:${Event.name}` : Event.name;
if (this._eventsByName.has(name)) {
throw new Error(`Event ${name} is already registered`);
}
let event = {
let entry = {
Event,
handler,
eventFromJSON: this._createEventFromJSON(Event),
name,
eventFromJSON: this.eventFromJSON(Event, name),
allowedSrcTypes: this.allowedTypes(Event.src, name, "src"),
allowedDstTypes: this.allowedTypes(Event.dst, name, "dst"),
};
this._snoopedEvents.set(Event.name, event);
this._eventsByName.set(name, entry);
this._eventsByClass.set(Event, entry);
}
}
for (let Class of dataClasses) {
Link.register(Class);
}
function registerPluginMessages(pluginInfos) {
for (let pluginInfo of pluginInfos) {
for (let Class of pluginInfo.messages || []) {
if (Class.plugin !== pluginInfo.name) {
throw new Error(
`Expected message ${Class.name} from ${pluginInfo.name} to have a static ` +
`plugin property set to "${pluginInfo.name}"`
);
}
Link.register(Class);
}
}
}
module.exports = {
Link,
registerPluginMessages,
};
+111
View File
@@ -0,0 +1,111 @@
"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");
// 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 = [
core.PingRequest,
controller.ControllerConfigGetRequest,
controller.ControllerConfigSetFieldRequest,
controller.ControllerConfigSetPropRequest,
controller.HostGenerateTokenRequest,
controller.HostConfigCreateRequest,
controller.LogSetSubscriptionsRequest,
controller.LogQueryRequest,
controller.LogMessageEvent,
controller.DebugDumpWsRequest,
controller.DebugWsMessageEvent,
host.HostListRequest,
host.HostSetSubscriptionsRequest,
host.HostUpdateEvent,
host.HostMetricsRequest,
host.ControllerConnectionEvent,
host.PrepareControllerDisconnectRequest,
host.SyncUserListsEvent,
instance.InstanceDetailsGetRequest,
instance.InstanceDetailsListRequest,
instance.InstanceDetailsSetSubscriptionsRequest,
instance.InstanceDetailsUpdateEvent,
instance.InstanceCreateRequest,
instance.InstanceConfigGetRequest,
instance.InstanceConfigSetFieldRequest,
instance.InstanceConfigSetPropRequest,
instance.InstanceAssignRequest,
instance.InstanceMetricsRequest,
instance.InstanceStartRequest,
instance.InstanceListSavesRequest,
instance.InstanceSetSaveListSubscriptionsRequest,
instance.InstanceSaveListUpdateEvent,
instance.InstanceCreateSaveRequest,
instance.InstanceRenameSaveRequest,
instance.InstanceCopySaveRequest,
instance.InstanceDeleteSaveRequest,
instance.InstanceDownloadSaveRequest,
instance.InstanceTransferSaveRequest,
instance.InstancePullSaveRequest,
instance.InstancePushSaveRequest,
instance.InstanceLoadScenarioRequest,
instance.InstanceExportDataRequest,
instance.InstanceExtractPlayersRequest,
instance.InstanceStopRequest,
instance.InstanceKillRequest,
instance.InstanceDeleteRequest,
instance.InstanceDeleteInternalRequest,
instance.InstanceSendRconRequest,
instance.InstancesUpdateRequest,
instance.InstanceAssignInternalRequest,
instance.InstanceUnassignInternalRequest,
instance.InstanceInitialisedEvent,
instance.InstanceStatusChangedEvent,
instance.InstanceDetailsChangedEvent,
instance.InstanceBanlistUpdateEvent,
instance.InstanceAdminlistUpdateEvent,
instance.InstanceWhitelistUpdateEvent,
instance.InstancePlayerUpdateEvent,
mod.ModPackGetRequest,
mod.ModPackGetDefaultRequest,
mod.ModPackListRequest,
mod.ModPackCreateRequest,
mod.ModPackUpdateRequest,
mod.ModPackDeleteRequest,
mod.ModPackSetSubscriptionsRequest,
mod.ModGetRequest,
mod.ModListRequest,
mod.ModSearchRequest,
mod.ModSetSubscriptionsRequest,
mod.ModDownloadRequest,
mod.ModDeleteRequest,
mod.ModPackUpdateEvent,
mod.ModUpdateEvent,
user.PermissionListRequest,
user.RoleListRequest,
user.RoleCreateRequest,
user.RoleUpdateRequest,
user.RoleGrantDefaultPermissionsRequest,
user.RoleDeleteRequest,
user.UserGetRequest,
user.UserListRequest,
user.UserSetSubscriptionsRequest,
user.UserCreateRequest,
user.UserRevokeTokenRequest,
user.UserUpdateRolesRequest,
user.UserSetAdminRequest,
user.UserSetWhitelistedRequest,
user.UserSetBannedRequest,
user.UserDeleteRequest,
user.AccountUpdateEvent,
user.UserUpdateEvent,
];
module.exports = { dataClasses };
+7
View File
@@ -7,6 +7,7 @@
const libErrors = require("./errors");
const libPlugin = require("./plugin");
const { logger } = require("./logging");
const path = require("path");
@@ -42,6 +43,12 @@ async function loadPluginInfos(pluginList) {
);
}
// migrate: ignore incompatible old plugins
if (pluginInfo.messages && !(pluginInfo.messages instanceof Array)) {
logger.warn(`Ignoring incompatible pre alpha.14 plugin ${pluginName}`);
continue;
}
pluginInfo.requirePath = pluginPath;
pluginInfo.version = pluginPackage.version;
plugins.push(pluginInfo);
+2 -1
View File
@@ -3,7 +3,7 @@ import "./index.css";
import React from "react";
import ReactDOM from "react-dom";
import { libConfig, libLogging, libPlugin } from "@clusterio/lib";
import { libConfig, libLink, libLogging, libPlugin } from "@clusterio/lib";
const { ConsoleTransport, WebConsoleFormat, logger } = libLogging;
import App from "./components/App";
@@ -102,6 +102,7 @@ export default async function bootstrap() {
format: new WebConsoleFormat(),
}));
let pluginInfos = await loadPluginInfos();
libLink.registerPluginMessages(pluginInfos);
libConfig.registerPluginConfigGroups(pluginInfos);
libConfig.finalizeConfigs();
let plugins = await loadPlugins(pluginInfos);
+9 -9
View File
@@ -114,15 +114,15 @@ export class Control extends libLink.Link {
});
}
this.register(libData.AccountUpdateEvent, this.handleAccountUpdateEvent.bind(this));
this.register(libData.HostUpdateEvent, this.handleHostUpdateEvent.bind(this));
this.register(libData.InstanceDetailsUpdateEvent, this.handleInstanceDetailsUpdateEvent.bind(this));
this.register(libData.InstanceSaveListUpdateEvent, this.handleInstanceSaveListUpdateEvent.bind(this));
this.register(libData.ModPackUpdateEvent, this.handleModPackUpdateEvent.bind(this));
this.register(libData.ModUpdateEvent, this.handleModUpdateEvent.bind(this));
this.register(libData.UserUpdateEvent, this.handleUserUpdateEvent.bind(this));
this.register(libData.LogMessageEvent, this.handleLogMessageEvent.bind(this));
this.register(libData.DebugWsMessageEvent, this.handleDebugWsMessageEvent.bind(this));
this.handle(libData.AccountUpdateEvent, this.handleAccountUpdateEvent.bind(this));
this.handle(libData.HostUpdateEvent, this.handleHostUpdateEvent.bind(this));
this.handle(libData.InstanceDetailsUpdateEvent, this.handleInstanceDetailsUpdateEvent.bind(this));
this.handle(libData.InstanceSaveListUpdateEvent, this.handleInstanceSaveListUpdateEvent.bind(this));
this.handle(libData.ModPackUpdateEvent, this.handleModPackUpdateEvent.bind(this));
this.handle(libData.ModUpdateEvent, this.handleModUpdateEvent.bind(this));
this.handle(libData.UserUpdateEvent, this.handleUserUpdateEvent.bind(this));
this.handle(libData.LogMessageEvent, this.handleLogMessageEvent.bind(this));
this.handle(libData.DebugWsMessageEvent, this.handleDebugWsMessageEvent.bind(this));
}
async handleAccountUpdateEvent(event) {
+5
View File
@@ -1,4 +1,5 @@
"use strict";
const messages = require("./messages");
module.exports = {
name: "global_chat",
@@ -6,4 +7,8 @@ module.exports = {
description: "Forwards chat between instances.",
instanceEntrypoint: "instance",
controlEntrypoint: "control",
messages: [
messages.ChatEvent,
],
};
+1 -1
View File
@@ -20,7 +20,7 @@ function removeTags(content) {
class InstancePlugin extends libPlugin.BaseInstancePlugin {
async init() {
this.messageQueue = [];
this.instance.register(ChatEvent, this.handleChatEvent.bind(this));
this.instance.handle(ChatEvent, this.handleChatEvent.bind(this));
}
onControllerConnectionEvent(event) {
+2
View File
@@ -1,5 +1,6 @@
"use strict";
const assert = require("assert").strict;
const libLink = require("@clusterio/lib/link");
const mock = require("../../../test/mock");
const lines = require("../../../test/lib/factorio/lines");
@@ -25,6 +26,7 @@ describe("global_chat plugin", function() {
let instancePlugin;
before(async function() {
libLink.Link.register(ChatEvent);
instancePlugin = new instance.InstancePlugin(info, new mock.MockInstance(), new mock.MockHost());
await instancePlugin.init();
});
+5 -5
View File
@@ -48,11 +48,11 @@ class ControllerPlugin extends libPlugin.BaseControllerPlugin {
});
}, this.controller.config.get("inventory_sync.autosave_interval") * 1000);
this.controller.register(AcquireRequest, this.handleAcquireRequest.bind(this));
this.controller.register(ReleaseRequest, this.handleReleaseRequest.bind(this));
this.controller.register(UploadRequest, this.handleUploadRequest.bind(this));
this.controller.register(DownloadRequest, this.handleDownloadRequest.bind(this));
this.controller.register(DatabaseStatsRequest, this.handleDatabaseStatsRequest.bind(this));
this.controller.handle(AcquireRequest, this.handleAcquireRequest.bind(this));
this.controller.handle(ReleaseRequest, this.handleReleaseRequest.bind(this));
this.controller.handle(UploadRequest, this.handleUploadRequest.bind(this));
this.controller.handle(DownloadRequest, this.handleDownloadRequest.bind(this));
this.controller.handle(DatabaseStatsRequest, this.handleDatabaseStatsRequest.bind(this));
}
async onInstanceStatusChanged(instance) {
+8
View File
@@ -1,5 +1,6 @@
"use strict";
const { libConfig, libUsers } = require("@clusterio/lib");
let messages = require("./messages");
class ControllerConfigGroup extends libConfig.PluginConfigGroup { }
ControllerConfigGroup.defaultAccess = ["controller", "host", "control"];
@@ -51,6 +52,13 @@ module.exports = {
controllerEntrypoint: "controller",
ControllerConfigGroup,
messages: [
messages.AcquireRequest,
messages.ReleaseRequest,
messages.UploadRequest,
messages.DownloadRequest,
messages.DatabaseStatsRequest,
],
webEntrypoint: "./web",
routes: ["/inventory"],
};
+2 -2
View File
@@ -63,8 +63,8 @@ class ControllerPlugin extends libPlugin.BaseControllerPlugin {
this.handleVerify(req, res).catch(next);
});
this.controller.register(FetchPlayerCodeRequest, this.handleFetchPlayerCodeRequest.bind(this));
this.controller.register(SetVerifyCodeRequest, this.handleSetVerifyCodeRequest.bind(this));
this.controller.handle(FetchPlayerCodeRequest, this.handleFetchPlayerCodeRequest.bind(this));
this.controller.handle(SetVerifyCodeRequest, this.handleSetVerifyCodeRequest.bind(this));
}
async handlePlayerCode(req, res) {
+6
View File
@@ -1,5 +1,6 @@
"use strict";
let { libConfig } = require("@clusterio/lib");
let messages = require("./messages");
class ControllerConfigGroup extends libConfig.PluginConfigGroup {}
@@ -29,4 +30,9 @@ module.exports = {
instanceEntrypoint: "instance",
webEntrypoint: "./web",
ControllerConfigGroup,
messages: [
messages.FetchPlayerCodeRequest,
messages.SetVerifyCodeRequest,
],
};
+3 -3
View File
@@ -49,9 +49,9 @@ class ControllerPlugin extends libPlugin.BaseControllerPlugin {
this.progressBroadcastId = null;
this.progressToBroadcast = new Set();
this.controller.register(ContributionEvent, this.handleContributionEvent.bind(this));
this.controller.register(FinishedEvent, this.handleFinishedEvent.bind(this));
this.controller.register(SyncTechnologiesRequest, this.handleSyncTechnologiesRequest.bind(this));
this.controller.handle(ContributionEvent, this.handleContributionEvent.bind(this));
this.controller.handle(FinishedEvent, this.handleFinishedEvent.bind(this));
this.controller.handle(SyncTechnologiesRequest, this.handleSyncTechnologiesRequest.bind(this));
}
async onShutdown() {
+8
View File
@@ -1,4 +1,5 @@
"use strict";
const messages = require("./messages");
module.exports = {
name: "research_sync",
@@ -6,4 +7,11 @@ module.exports = {
description: "Synchronises technology research progress between instances.",
instanceEntrypoint: "instance",
controllerEntrypoint: "controller",
messages: [
messages.ContributionEvent,
messages.ProgressEvent,
messages.FinishedEvent,
messages.SyncTechnologiesRequest,
],
};
+2 -2
View File
@@ -28,8 +28,8 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
});
this.syncStarted = false;
this.instance.register(ProgressEvent, this.handleProgressEvent.bind(this));
this.instance.register(FinishedEvent, this.handleFinishedEvent.bind(this));
this.instance.handle(ProgressEvent, this.handleProgressEvent.bind(this));
this.instance.handle(FinishedEvent, this.handleFinishedEvent.bind(this));
}
async researchContribution(tech) {
+5 -5
View File
@@ -5,7 +5,7 @@ class ContributionEvent {
static type = "event";
static src = "instance";
static dst = "controller";
static json = "research_sync";
static plugin = "research_sync";
/** @type {string} */
name;
@@ -39,7 +39,7 @@ class ProgressEvent {
static type = "event";
static src = "controller";
static dst = "instance";
static json = "research_sync";
static plugin = "research_sync";
/** @type {Array<>} */
technologies;
@@ -74,9 +74,9 @@ class ProgressEvent {
class FinishedEvent {
static type = "event";
static src = "instance";
static src = ["instance", "controller"];
static dst = "instance";
static json = "research_sync";
static plugin = "research_sync";
/** @type {string} */
name;
@@ -144,7 +144,7 @@ class SyncTechnologiesRequest {
static type = "request";
static src = "instance";
static dst = "controller";
static json = "research_sync";
static plugin = "research_sync";
/** @type {Array<Technology>} */
technologies;
+4 -4
View File
@@ -97,10 +97,10 @@ class ControllerPlugin extends libPlugin.BaseControllerPlugin {
routes.addApiRoutes(this.controller.app, this.items);
this.controller.register(GetStorageRequest, this.handleGetStorageRequest.bind(this));
this.controller.register(PlaceEvent, this.handlePlaceEvent.bind(this));
this.controller.register(RemoveRequest, this.handleRemoveRequest.bind(this));
this.controller.register(SetStorageSubscriptionRequest, this.handleSetStorageSubscriptionRequest.bind(this));
this.controller.handle(GetStorageRequest, this.handleGetStorageRequest.bind(this));
this.controller.handle(PlaceEvent, this.handlePlaceEvent.bind(this));
this.controller.handle(RemoveRequest, this.handleRemoveRequest.bind(this));
this.controller.handle(SetStorageSubscriptionRequest, this.handleSetStorageSubscriptionRequest.bind(this));
}
updateStorage() {
+8
View File
@@ -1,5 +1,6 @@
"use strict";
const { libConfig, libUsers } = require("@clusterio/lib");
const messages = require("./messages");
class ControllerConfigGroup extends libConfig.PluginConfigGroup {}
ControllerConfigGroup.defaultAccess = ["controller", "host", "control"];
@@ -57,6 +58,13 @@ module.exports = {
controllerEntrypoint: "controller",
ControllerConfigGroup,
messages: [
messages.PlaceEvent,
messages.RemoveRequest,
messages.GetStorageRequest,
messages.UpdateStorageEvent,
messages.SetStorageSubscriptionRequest,
],
webEntrypoint: "./web",
routes: ["/storage"],
};
+1 -1
View File
@@ -31,7 +31,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
task.finally(() => { this.pendingTasks.delete(task); });
});
this.instance.register(UpdateStorageEvent, this.handleUpdateStorageEvent.bind(this));
this.instance.handle(UpdateStorageEvent, this.handleUpdateStorageEvent.bind(this));
}
async onStart() {
+7 -7
View File
@@ -53,13 +53,13 @@ class TestControl extends libLink.Link {
).catch(err => logger.error(`Error setting user subscriptions:\n${err.stack}`));
});
this.register(libData.AccountUpdateEvent);
this.register(libData.HostUpdateEvent, this.handleHostUpdateEvent.bind(this));
this.register(libData.InstanceDetailsUpdateEvent, this.handleInstanceDetailsUpdateEvent.bind(this));
this.register(libData.InstanceSaveListUpdateEvent, this.handleInstanceSaveListUpdateEvent.bind(this));
this.register(libData.ModUpdateEvent, this.handleModUpdateEvent.bind(this));
this.register(libData.ModPackUpdateEvent, this.handleModPackUpdateEvent.bind(this));
this.register(libData.UserUpdateEvent, this.handleUserUpdateEvent.bind(this));
this.handle(libData.AccountUpdateEvent);
this.handle(libData.HostUpdateEvent, this.handleHostUpdateEvent.bind(this));
this.handle(libData.InstanceDetailsUpdateEvent, this.handleInstanceDetailsUpdateEvent.bind(this));
this.handle(libData.InstanceSaveListUpdateEvent, this.handleInstanceSaveListUpdateEvent.bind(this));
this.handle(libData.ModUpdateEvent, this.handleModUpdateEvent.bind(this));
this.handle(libData.ModPackUpdateEvent, this.handleModPackUpdateEvent.bind(this));
this.handle(libData.UserUpdateEvent, this.handleUserUpdateEvent.bind(this));
}
async handleHostUpdateEvent(event) {
+8 -5
View File
@@ -13,15 +13,18 @@ const addr = libData.Address.fromShorthand;
describe("lib/command", function() {
let testRole = libData.RawRole.fromJSON({ id: 28, name: "Test Role", description: "Test", permissions: [] });
let loopbackConnector = new libLink.VirtualConnector(addr("controller"), addr("controller"));
let testControl = new mock.MockControl(loopbackConnector);
testControl.register(
let [controlConnector, controllerConnector] = libLink.VirtualConnector.makePair(
addr({ controlId: 1}), addr("controller")
);
let testControl = new mock.MockControl(controlConnector);
let testController = new libLink.Link(controllerConnector);
testController.handle(
libData.HostListRequest, () => [new libData.HostDetails("test", "0.1", "Test Host", 11, false)]
);
testControl.register(
testController.handle(
libData.InstanceDetailsListRequest, () => [new libData.InstanceDetails("Test Instance", 57, 4, null, "stopped")]
);
testControl.register(libData.RoleListRequest, () => [testRole]);
testController.handle(libData.RoleListRequest, () => [testRole]);
describe("resolveHost", function() {
it("should pass an integer like string back", async function() {
+33 -48
View File
@@ -27,6 +27,7 @@ describe("lib/link/link", function() {
static src = ["controller", "control"];
static dst = ["controller", "control"];
}
libLink.Link.register(SimpleRequest);
class NumberRequest {
static type = "request";
static src = ["controller", "control"];
@@ -42,11 +43,13 @@ describe("lib/link/link", function() {
toJSON() { return this.value; }
static fromJSON(json) { return new this(json); }
};
libLink.Link.register(NumberRequest);
class SimpleEvent {
static type = "event";
static src = ["controller", "control"];
static dst = ["controller", "control"];
}
libLink.Link.register(SimpleEvent);
class NumberEvent {
static type = "event";
static src = ["controller", "control"];
@@ -56,6 +59,7 @@ describe("lib/link/link", function() {
toJSON() { return this.value; }
static fromJSON(json) { return new this(json); }
}
libLink.Link.register(NumberEvent);
beforeEach(function() {
testConnector = new mock.MockConnector(src, dst);
@@ -82,14 +86,14 @@ describe("lib/link/link", function() {
static src = "controller";
static dst = "control";
}
testLink.register(UnhandledRequest);
libLink.Link.register(UnhandledRequest);
testConnector.emit("message", new libData.MessageRequest(1, dst, src, "UnhandledRequest"));
assert.deepEqual(testConnector.sentMessages, [
new libData.MessageResponseError(
testConnector._seq - 1,
src,
dst,
new libData.ResponseError("No handler registered for UnhandledRequest")
new libData.ResponseError("No handler for UnhandledRequest")
),
]);
});
@@ -190,18 +194,18 @@ describe("lib/link/link", function() {
);
});
});
describe(".register()", function() {
it("should register a request", function() {
describe(".handle()", function() {
it("should register a request handler", function() {
let handled = false;
testLink.register(SimpleRequest, async () => { handled = true; });
assert(testLink._registeredRequests.has("SimpleRequest"), "request was not registered");
testLink.handle(SimpleRequest, async () => { handled = true; });
assert(testLink._requestHandlers.has(SimpleRequest), "request handler was not registered");
testConnector.emit("message", new libData.MessageRequest(1, dst, src, "SimpleRequest"));
assert(handled, "request was not handled");
});
it("should send response error from request handler throwing", async function() {
let message = events.once(testConnector, "send");
message.catch(() => {});
testLink.register(SimpleRequest, async () => { throwSimple("Error"); });
testLink.handle(SimpleRequest, async () => { throwSimple("Error"); });
testConnector.emit("message", new libData.MessageRequest(1, dst, src, "SimpleRequest"));
assert.deepEqual(
await message,
@@ -213,7 +217,7 @@ describe("lib/link/link", function() {
it("should send response error on request validation failing", async function() {
let message = events.once(testConnector, "send");
message.catch(() => {});
testLink.register(NumberRequest, async () => 1);
testLink.handle(NumberRequest, async () => 1);
testConnector.emit("message", new libData.MessageRequest(1, dst, src, "NumberRequest", "not a number"));
let response = (await message)[0];
assert.deepEqual(
@@ -228,7 +232,7 @@ describe("lib/link/link", function() {
it("should send response error on response validation failing", async function() {
let message = events.once(testConnector, "send");
message.catch(() => {});
testLink.register(NumberRequest, async () => "not a number");
testLink.handle(NumberRequest, async () => "not a number");
testConnector.emit("message", new libData.MessageRequest(1, dst, src, "NumberRequest", 1));
let response = (await message)[0];
assert.deepEqual(
@@ -243,7 +247,7 @@ describe("lib/link/link", function() {
it("should send value returned from request handler", async function() {
let message = events.once(testConnector, "send");
message.catch(() => {});
testLink.register(NumberRequest, async (request) => request.value + 4);
testLink.handle(NumberRequest, async (request) => request.value + 4);
testConnector.emit("message", new libData.MessageRequest(1, dst, src, "NumberRequest", 1));
let response = (await message)[0];
assert.deepEqual(
@@ -251,41 +255,43 @@ describe("lib/link/link", function() {
new libData.MessageResponse(1, src, dst, 5)
);
});
it("should register an event", function() {
it("should register an event handler", function() {
let handled = false;
testLink.register(SimpleEvent, async () => { handled = true; });
assert(testLink._registeredEvents.has("SimpleEvent"), "event was not registered");
testLink.handle(SimpleEvent, async () => { handled = true; });
assert(testLink._eventHandlers.has(SimpleEvent), "event handler was not registered");
testConnector.emit("message", new libData.MessageEvent(1, dst, src, "SimpleEvent"));
assert(handled, "event was not handled");
});
it("should pass value to event handler", async function() {
let value;
testLink.register(NumberEvent, async (event) => { value = event.value; });
testLink.handle(NumberEvent, async (event) => { value = event.value; });
testConnector.emit("message", new libData.MessageEvent(1, dst, src, "NumberEvent", 9));
assert.deepEqual(value, 9);
});
it("should log errors from event handler", function() {
testLink.register(SimpleEvent, async () => { throwSimple("Error"); });
testLink.handle(SimpleEvent, async () => { throwSimple("Error"); });
testConnector.emit("message", new libData.MessageEvent(1, dst, src, "SimpleEvent"));
});
it("should throw on unknown type", function() {
assert.throws(
() => testLink.register({ name: "Bad", type: "bad" }),
() => testLink.handle({ name: "Bad", type: "bad" }),
{ message: "Class Bad has unrecognized type bad" }
);
});
it("should throw on double registration", function() {
testLink.register(SimpleRequest);
testLink.handle(SimpleRequest);
assert.throws(
() => testLink.register(SimpleRequest),
() => testLink.handle(SimpleRequest),
new Error("Request SimpleRequest is already registered")
);
testLink.register(SimpleEvent);
testLink.handle(SimpleEvent);
assert.throws(
() => testLink.register(SimpleEvent),
() => testLink.handle(SimpleEvent),
new Error("Event SimpleEvent is already registered")
);
});
});
describe("static .register()", function() {
it("should throw if Request has only one of jsonSchema and fromJSON", function() {
class BadRequest1 {
static type = "request";
@@ -294,7 +300,7 @@ describe("lib/link/link", function() {
static jsonSchema = {};
}
assert.throws(
() => testLink.register(BadRequest1),
() => libLink.Link.register(BadRequest1),
new Error("Request BadRequest1 has static jsonSchema but is missing static fromJSON")
);
class BadRequest2 {
@@ -304,7 +310,7 @@ describe("lib/link/link", function() {
static fromJSON() {};
}
assert.throws(
() => testLink.register(BadRequest2),
() => libLink.Link.register(BadRequest2),
new Error("Request BadRequest2 has static fromJSON but is missing static jsonSchema")
);
});
@@ -316,7 +322,7 @@ describe("lib/link/link", function() {
static jsonSchema = {};
}
assert.throws(
() => testLink.register(BadEvent1),
() => libLink.Link.register(BadEvent1),
new Error("Event BadEvent1 has static jsonSchema but is missing static fromJSON")
);
class BadEvent2 {
@@ -326,7 +332,7 @@ describe("lib/link/link", function() {
static fromJSON() {};
}
assert.throws(
() => testLink.register(BadEvent2),
() => libLink.Link.register(BadEvent2),
new Error("Event BadEvent2 has static fromJSON but is missing static jsonSchema")
);
});
@@ -348,7 +354,8 @@ describe("lib/link/link", function() {
static jsonSchema = { type: "string" };
static fromJSON(json) { return new this(json); };
}
testLink.register(StringEvent, () => {});
libLink.Link.register(StringEvent);
testLink.handle(StringEvent, () => {});
assert.throws(
() => testLink._processMessage(new libData.MessageEvent(1, dst, src, "StringEvent", 99)),
{ message: "Event StringEvent failed validation" }
@@ -360,7 +367,7 @@ describe("lib/link/link", function() {
it("should snoop an event", function() {
let handled = false;
testLink.snoopEvent(SimpleEvent, async () => { handled = true; });
assert(testLink._snoopedEvents.has("SimpleEvent"), "event was not snooped");
assert(testLink._eventSnoopers.has(SimpleEvent), "event was not snooped");
testConnector.emit("message", new libData.MessageEvent(1, dst, src, "SimpleEvent"));
assert(handled, "event was not handled");
});
@@ -375,28 +382,6 @@ describe("lib/link/link", function() {
new Error("Event SimpleEvent is already snooped")
);
});
it("should throw if Event has only one of jsonSchema and fromJSON", function() {
class BadEvent1 {
static type = "event";
static src = "controller";
static dst = "control";
static jsonSchema = {};
}
assert.throws(
() => testLink.snoopEvent(BadEvent1),
new Error("Event BadEvent1 has static jsonSchema but is missing static fromJSON")
);
class BadEvent2 {
static type = "event";
static src = "controller";
static dst = "control";
static fromJSON() {};
}
assert.throws(
() => testLink.snoopEvent(BadEvent2),
new Error("Event BadEvent2 has static fromJSON but is missing static jsonSchema")
);
});
});
});
});
+2 -1
View File
@@ -185,7 +185,7 @@ class MockController {
this.hosts = new Map();
}
register() {
handle() {
}
getControllerUrl() {
@@ -210,6 +210,7 @@ class MockController {
}
async function createControllerPlugin(ControllerPluginClass, info) {
libLink.registerPluginMessages([info]);
let controller = new MockController();
let metrics = {};
let logger = new MockLogger();