From da25196830c98362c2e7fe8a160647274c20f188 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Tue, 5 Sep 2023 01:46:45 +0100 Subject: [PATCH] Refactor to use subsribers --- packages/controller/src/ControlConnection.ts | 128 ------- packages/controller/src/Controller.ts | 90 +++-- packages/lib/index.ts | 1 + packages/lib/src/data/messages_host.ts | 24 +- packages/lib/src/data/messages_instance.ts | 46 +-- packages/lib/src/data/messages_mod.ts | 46 +-- packages/lib/src/data/messages_user.ts | 23 +- packages/lib/src/link/messages.ts | 9 +- packages/lib/src/subscriptions.ts | 41 +- packages/web_ui/src/model/host.ts | 8 +- packages/web_ui/src/model/instance.ts | 8 +- packages/web_ui/src/model/mod_pack.ts | 8 +- packages/web_ui/src/model/mods.ts | 4 +- packages/web_ui/src/model/saves.ts | 4 +- packages/web_ui/src/model/user.tsx | 8 +- packages/web_ui/src/util/websocket.ts | 379 +------------------ 16 files changed, 120 insertions(+), 707 deletions(-) diff --git a/packages/controller/src/ControlConnection.ts b/packages/controller/src/ControlConnection.ts index b87e0255..b02e6f03 100644 --- a/packages/controller/src/ControlConnection.ts +++ b/packages/controller/src/ControlConnection.ts @@ -1,5 +1,4 @@ import type WsServerConnector from "./WsServerConnector"; -import type { HostInfo } from "./HostConnection"; import events from "events"; import fs from "fs-extra"; @@ -11,8 +10,6 @@ const { logFilter, logger } = lib; import BaseConnection from "./BaseConnection"; import * as routes from "./routes"; import Controller from "./Controller"; -import { promises } from "dns"; -import InstanceInfo from "./InstanceInfo"; const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }).compare; @@ -30,12 +27,6 @@ const queryLogTime = new lib.Summary( export default class ControlConnection extends BaseConnection { private _agent: string; private _version: string; - hostSubscriptions: { all:boolean, hostIds:number[] } = { all:false, hostIds:[] }; - instanceSubscriptions: { all:boolean, instanceIds:number[] } = { all:false, instanceIds:[] }; - saveListSubscriptions: { all:boolean, instanceIds:number[] } = { all:false, instanceIds:[] }; - modPackSubscriptions: { all:boolean, modPackIds:number[] } = { all:false, modPackIds:[] }; - modSubscriptions: { all:boolean, modNames:string[] } = { all:false, modNames:[] }; - userSubscriptions: { all:boolean, names:string[]} = { all:false, names:[] }; logTransport: lib.LinkTransport | null = null; logSubscriptions: { all: boolean, @@ -88,33 +79,28 @@ export default class ControlConnection extends BaseConnection { this.handle(lib.ControllerConfigSetFieldRequest, this.handleControllerConfigSetFieldRequest.bind(this)); this.handle(lib.ControllerConfigSetPropRequest, this.handleControllerConfigSetPropRequest.bind(this)); this.handle(lib.HostListRequest, this.handleHostListRequest.bind(this)); - this.handle(lib.HostSetSubscriptionsRequest, this.handleHostSetSubscriptionsRequest.bind(this)); this.handle(lib.HostGenerateTokenRequest, this.handleHostGenerateTokenRequest.bind(this)); this.handle(lib.HostConfigCreateRequest, this.handleHostConfigCreateRequest.bind(this)); this.handle(lib.InstanceDetailsGetRequest, this.handleInstanceDetailsGetRequest.bind(this)); this.handle(lib.InstanceDetailsListRequest, this.handleInstanceDetailsListRequest.bind(this)); - this.handle(lib.InstanceDetailsSetSubscriptionsRequest, this.handleInstanceDetailsSetSubscriptionsRequest.bind(this)); this.handle(lib.InstanceCreateRequest, this.handleInstanceCreateRequest.bind(this)); this.handle(lib.InstanceDeleteRequest, this.handleInstanceDeleteRequest.bind(this)); this.handle(lib.InstanceConfigGetRequest, this.handleInstanceConfigGetRequest.bind(this)); this.handle(lib.InstanceConfigSetFieldRequest, this.handleInstanceConfigSetFieldRequest.bind(this)); this.handle(lib.InstanceConfigSetPropRequest, this.handleInstanceConfigSetPropRequest.bind(this)); this.handle(lib.InstanceAssignRequest, this.handleInstanceAssignRequest.bind(this)); - this.handle(lib.InstanceSetSaveListSubscriptionsRequest, this.handleInstanceSetSaveListSubscriptionsRequest.bind(this)); this.handle(lib.InstanceRenameSaveRequest, this._controller.sendToHostByInstanceId.bind(this._controller)); this.handle(lib.InstanceCopySaveRequest, this._controller.sendToHostByInstanceId.bind(this._controller)); this.handle(lib.InstanceDeleteSaveRequest, this._controller.sendToHostByInstanceId.bind(this._controller)); this.handle(lib.InstanceDownloadSaveRequest, this.handleInstanceDownloadSaveRequest.bind(this)); this.handle(lib.InstanceTransferSaveRequest, this.handleInstanceTransferSaveRequest.bind(this)); this.handle(lib.ModPackListRequest, this.handleModPackListRequest.bind(this)); - this.handle(lib.ModPackSetSubscriptionsRequest, this.handleModPackSetSubscriptionsRequest.bind(this)); this.handle(lib.ModPackCreateRequest, this.handleModPackCreateRequest.bind(this)); this.handle(lib.ModPackUpdateRequest, this.handleModPackUpdateRequest.bind(this)); this.handle(lib.ModPackDeleteRequest, this.handleModPackDeleteRequest.bind(this)); this.handle(lib.ModGetRequest, this.handleModGetRequest.bind(this)); this.handle(lib.ModListRequest, this.handleModListRequest.bind(this)); this.handle(lib.ModSearchRequest, this.handleModSearchRequest.bind(this)); - this.handle(lib.ModSetSubscriptionsRequest, this.handleModSetSubscriptionsRequest.bind(this)); this.handle(lib.ModDownloadRequest, this.handleModDownloadRequest.bind(this)); this.handle(lib.ModDeleteRequest, this.handleModDeleteRequest.bind(this)); this.handle(lib.LogSetSubscriptionsRequest, this.handleLogSetSubscriptionsRequest.bind(this)); @@ -127,7 +113,6 @@ export default class ControlConnection extends BaseConnection { this.handle(lib.RoleDeleteRequest, this.handleRoleDeleteRequest.bind(this)); this.handle(lib.UserGetRequest, this.handleUserGetRequest.bind(this)); this.handle(lib.UserListRequest, this.handleUserListRequest.bind(this)); - this.handle(lib.UserSetSubscriptionsRequest, this.handleUserSetSubscriptionsRequest.bind(this)); this.handle(lib.UserCreateRequest, this.handleUserCreateRequest.bind(this)); this.handle(lib.UserRevokeTokenRequest, this.handleUserRevokeTokenRequest.bind(this)); this.handle(lib.UserUpdateRolesRequest, this.handleUserUpdateRolesRequest.bind(this)); @@ -216,22 +201,6 @@ export default class ControlConnection extends BaseConnection { return list; } - async handleHostSetSubscriptionsRequest(request: lib.HostSetSubscriptionsRequest) { - this.hostSubscriptions = { ...request }; - } - - hostUpdated( - host: HostInfo, - update: lib.HostDetails - ) { - if ( - this.hostSubscriptions.all - || this.hostSubscriptions.hostIds.includes(host.id) - ) { - this.send(new lib.HostUpdateEvent(update)); - } - } - async handleHostGenerateTokenRequest(message: lib.HostGenerateTokenRequest) { let hostId = message.hostId; if (hostId === undefined) { @@ -304,38 +273,6 @@ export default class ControlConnection extends BaseConnection { return list; } - async handleInstanceDetailsSetSubscriptionsRequest(request: lib.InstanceDetailsSetSubscriptionsRequest) { - this.instanceSubscriptions = { ...request }; - } - - instanceUpdated(instance: InstanceInfo) { - if ( - this.instanceSubscriptions.all - || this.instanceSubscriptions.instanceIds.includes(instance.id) - ) { - let assigned_host: number|null|undefined = instance.config.get("instance.assigned_host"); - if (assigned_host === null) { - assigned_host = undefined; - } - - let game_port: number|null|undefined = instance.game_port - if (game_port === null) { - game_port = undefined; - } - - - this.send(new lib.InstanceDetailsUpdateEvent( - new lib.InstanceDetails( - instance.config.get("instance.name"), - instance.id, - assigned_host, - game_port, - instance.status, - ) - )); - } - } - // XXX should probably add a hook for host reuqests? async handleInstanceCreateRequest(request: lib.InstanceCreateRequest) { let instanceConfig = new lib.InstanceConfig("controller"); @@ -378,19 +315,6 @@ export default class ControlConnection extends BaseConnection { await this._controller.instanceAssign(request.instanceId, request.hostId); } - async handleInstanceSetSaveListSubscriptionsRequest(request: lib.InstanceSetSaveListSubscriptionsRequest) { - this.saveListSubscriptions = { ...request }; - } - - saveListUpdate(instanceId: number, saves: lib.SaveDetails[]) { - if ( - this.saveListSubscriptions.all - || this.saveListSubscriptions.instanceIds.includes(instanceId) - ) { - this.send(new lib.InstanceSaveListUpdateEvent(instanceId, saves)); - } - } - async handleInstanceDownloadSaveRequest(request: lib.InstanceDownloadSaveRequest) { let { instanceId, name } = request; let stream = await routes.createProxyStream(this._controller.app); @@ -488,10 +412,6 @@ export default class ControlConnection extends BaseConnection { return [...this._controller.modPacks!.values()]; } - async handleModPackSetSubscriptionsRequest(request: lib.ModPackSetSubscriptionsRequest) { - this.modPackSubscriptions = { ...request }; - } - async handleModPackCreateRequest(request: lib.ModPackCreateRequest) { let modPack = request.modPack; if (modPack.id === undefined) { @@ -524,15 +444,6 @@ export default class ControlConnection extends BaseConnection { this._controller.modPackUpdated(modPack); } - modPackUpdated(modPack: lib.ModPack) { - if ( - this.modPackSubscriptions.all - || this.modPackSubscriptions.modPackIds.includes(modPack.id!) - ) { - this.send(new lib.ModPackUpdateEvent(modPack)); - } - } - async handleModGetRequest(request: lib.ModGetRequest) { let { name, version } = request; let filename = `${name}_${version}.zip`; @@ -636,10 +547,6 @@ export default class ControlConnection extends BaseConnection { }; } - async handleModSetSubscriptionsRequest(request: lib.ModSetSubscriptionsRequest) { - this.modSubscriptions = { ...request }; - } - async handleModDownloadRequest(request: lib.ModDownloadRequest) { let { name, version } = request; let filename = `${name}_${version}.zip`; @@ -672,15 +579,6 @@ export default class ControlConnection extends BaseConnection { this.updateLogSubscriptions(); } - modUpdated(mod: lib.ModInfo) { - if ( - this.modSubscriptions.all - || this.modSubscriptions.modNames.includes(mod.name) - ) { - this.send(new lib.ModUpdateEvent(mod)); - } - } - updateLogSubscriptions() { let { all, controller, hostIds, instanceIds } = this.logSubscriptions; if (all || controller || hostIds.length || instanceIds.length) { @@ -832,32 +730,6 @@ export default class ControlConnection extends BaseConnection { return list; } - async handleUserSetSubscriptionsRequest(request: lib.UserSetSubscriptionsRequest) { - this.userSubscriptions = { ...request }; - } - - userUpdated(user: any) { - if ( - this.userSubscriptions.all - || this.userSubscriptions.names.includes(user.name) - ) { - this.send(new lib.UserUpdateEvent( - new lib.RawUser( - user.name, - [...user.roles].map(role => role.id), - [...user.instances], - user.isAdmin, - user.isBanned, - user.isWhitelisted, - user.banReason, - user.isDeleted, - user.playerStats, - user.instanceStats, - ) - )); - } - } - async handleUserCreateRequest(request: lib.UserCreateRequest) { let user = this._controller.userManager.createUser(request.name); this._controller.userUpdated(user); diff --git a/packages/controller/src/Controller.ts b/packages/controller/src/Controller.ts index f69b102b..9092a744 100644 --- a/packages/controller/src/Controller.ts +++ b/packages/controller/src/Controller.ts @@ -74,6 +74,9 @@ export default class Controller { wsServer: WsServer; debugEvents: events.EventEmitter = new events.EventEmitter(); private _events: events.EventEmitter = new events.EventEmitter(); + + /** Event subscription controller */ + subscriptions: lib.SubscriptionController; // Possible states are new, starting, running, stopping, stopped private _state: string = "new"; @@ -106,6 +109,7 @@ export default class Controller { this.wsServer = new WsServer(this); this.userManager = new UserManager(this.config); + this.subscriptions = new lib.SubscriptionController(this); } async start(args: ControllerArgs) { @@ -236,6 +240,14 @@ export default class Controller { this.app.locals.mainBundle = manifest["main.js"] || "no_web_build"; } + // Handle subscriptions for all internal properties + this.subscriptions.handle(lib.HostUpdateEvent, undefined, event => event.update.id); + this.subscriptions.handle(lib.InstanceDetailsUpdateEvent, undefined, event => event.details.id); + this.subscriptions.handle(lib.InstanceSaveListUpdateEvent, undefined, event => event.instanceId); + this.subscriptions.handle(lib.ModPackUpdateEvent, undefined, event => event.modPack.id!); + this.subscriptions.handle(lib.ModUpdateEvent); + this.subscriptions.handle(lib.UserUpdateEvent, undefined, event => event.user.name); + // Load plugins await this.loadPlugins(); @@ -575,13 +587,7 @@ export default class Controller { host.public_address === null ? undefined : host.public_address, ); - for (let controlConnection of this.wsServer.controlConnections.values()) { - if (controlConnection.connector.closing) { - continue; - } - - controlConnection.hostUpdated(host, update); - } + this.subscriptions.broadcast(new lib.HostUpdateEvent(update)); } /** @@ -761,56 +767,56 @@ export default class Controller { } instanceUpdated(instance: InstanceInfo) { - for (let controlConnection of this.wsServer.controlConnections.values()) { - if (controlConnection.connector.closing) { - continue; - } - - controlConnection.instanceUpdated(instance); + let assigned_host: number|null|undefined = instance.config.get("instance.assigned_host"); + if (assigned_host === null) { + assigned_host = undefined; } + + let game_port: number|null|undefined = instance.game_port + if (game_port === null) { + game_port = undefined; + } + + this.subscriptions.broadcast(new lib.InstanceDetailsUpdateEvent( + new lib.InstanceDetails( + instance.config.get("instance.name"), + instance.id, + assigned_host, + game_port, + instance.status, + ) + )); } saveListUpdate(instanceId: number, saves: lib.SaveDetails[]) { - for (let controlConnection of this.wsServer.controlConnections.values()) { - if (controlConnection.connector.closing) { - continue; - } - - controlConnection.saveListUpdate(instanceId, saves); - } + this.subscriptions.broadcast(new lib.InstanceSaveListUpdateEvent(instanceId, saves)); } modPackUpdated(modPack: lib.ModPack) { - for (let controlConnection of this.wsServer.controlConnections.values()) { - if (controlConnection.connector.closing) { - continue; - } - controlConnection.modPackUpdated(modPack); - } - + this.subscriptions.broadcast(new lib.ModPackUpdateEvent(modPack)); lib.invokeHook(this.plugins, "onModPackUpdated", modPack); } modUpdated(mod: lib.ModInfo) { - for (let controlConnection of this.wsServer.controlConnections.values()) { - if (controlConnection.connector.closing) { - continue; - } - - controlConnection.modUpdated(mod); - } - + this.subscriptions.broadcast(new lib.ModUpdateEvent(mod)); lib.invokeHook(this.plugins, "onModUpdated", mod); } userUpdated(user: lib.User) { - for (let controlConnection of this.wsServer.controlConnections.values()) { - if (controlConnection.connector.closing) { - continue; - } - - controlConnection.userUpdated(user); - } + this.subscriptions.broadcast(new lib.UserUpdateEvent( + new lib.RawUser( + user.name, + [...user.roles].map(role => role.id), + [...user.instances], + user.isAdmin, + user.isBanned, + user.isWhitelisted, + user.banReason, + user.isDeleted, + user.playerStats, + user.instanceStats, + ) + )); } /** diff --git a/packages/lib/index.ts b/packages/lib/index.ts index 14b567e0..19583966 100644 --- a/packages/lib/index.ts +++ b/packages/lib/index.ts @@ -25,6 +25,7 @@ export * from "./src/shared_commands"; export * from "./src/stream"; export * from "./src/users"; export * from "./src/zip_ops"; +export * from "./src/subscriptions" export { default as ExponentialBackoff } from "./src/ExponentialBackoff"; export { default as PlayerStats } from "./src/PlayerStats"; diff --git a/packages/lib/src/data/messages_host.ts b/packages/lib/src/data/messages_host.ts index c271a3b9..b5540ff0 100644 --- a/packages/lib/src/data/messages_host.ts +++ b/packages/lib/src/data/messages_host.ts @@ -35,34 +35,12 @@ export class HostListRequest { static Response = jsonArray(HostDetails); } -export class HostSetSubscriptionsRequest { - declare ["constructor"]: typeof HostSetSubscriptionsRequest; - static type = "request" as const; - static src = "control" as const; - static dst = "controller" as const; - static permission = "core.host.subscribe" as const; - - constructor( - public all: boolean, - public hostIds: number[], - ) { } - - static jsonSchema = Type.Object({ - "all": Type.Boolean(), - "hostIds": Type.Array(Type.Integer()), - }); - - static fromJSON(json: Static) { - return new this(json.all, json.hostIds); - } -} - - export class HostUpdateEvent { declare ["constructor"]: typeof HostUpdateEvent; static type = "event" as const; static src = "controller" as const; static dst = "control" as const; + static permission = "core.host.subscribe" as const; constructor( public update: HostDetails, diff --git a/packages/lib/src/data/messages_instance.ts b/packages/lib/src/data/messages_instance.ts index 42376ae6..8df53810 100644 --- a/packages/lib/src/data/messages_instance.ts +++ b/packages/lib/src/data/messages_instance.ts @@ -67,33 +67,12 @@ export class InstanceDetailsListRequest { static Response = jsonArray(InstanceDetails); }; -export class InstanceDetailsSetSubscriptionsRequest { - declare ["constructor"]: typeof InstanceDetailsSetSubscriptionsRequest; - static type = "request" as const; - static src = "control" as const; - static dst = "controller" as const; - static permission = "core.instance.subscribe" as const; - - constructor( - public all: boolean = false, - public instanceIds: number[] = [], - ) { } - - static jsonSchema = Type.Object({ - "all": Type.Boolean(), - "instanceIds": Type.Array(Type.Integer()), - }); - - static fromJSON(json: Static) { - return new this(json.all, json.instanceIds); - } -}; - export class InstanceDetailsUpdateEvent { declare ["constructor"]: typeof InstanceDetailsUpdateEvent; static type = "event" as const; static src = "controller" as const; static dst = "control" as const; + static permission = "core.instance.subscribe" as const; constructor( public details: InstanceDetails, @@ -299,33 +278,12 @@ export class InstanceListSavesRequest { static Response = jsonArray(SaveDetails); } -export class InstanceSetSaveListSubscriptionsRequest { - declare ["constructor"]: typeof InstanceSetSaveListSubscriptionsRequest; - static type = "request" as const; - static src = "control" as const; - static dst = "controller" as const; - static permission = "core.instance.save.list_subscribe" as const; - - constructor( - public all: boolean = false, - public instanceIds: number[] = [], - ) { } - - static jsonSchema = Type.Object({ - "all": Type.Boolean(), - "instanceIds": Type.Array(Type.Integer()), - }); - - static fromJSON(json: Static) { - return new this(json.all, json.instanceIds); - } -}; - export class InstanceSaveListUpdateEvent { declare ["constructor"]: typeof InstanceSaveListUpdateEvent; static type = "event" as const; static src = ["instance", "host", "controller"] as const; static dst = ["controller", "control"] as const; + static permission = "core.instance.save.list_subscribe" as const; constructor( public instanceId: number, diff --git a/packages/lib/src/data/messages_mod.ts b/packages/lib/src/data/messages_mod.ts index 0c4fe7ce..07d2b999 100644 --- a/packages/lib/src/data/messages_mod.ts +++ b/packages/lib/src/data/messages_mod.ts @@ -104,28 +104,6 @@ export class ModPackDeleteRequest { } } -export class ModPackSetSubscriptionsRequest { - declare ["constructor"]: typeof ModPackSetSubscriptionsRequest; - static type = "request" as const; - static src = "control" as const; - static dst = "controller" as const; - static permission = "core.mod_pack.subscribe" as const; - - constructor( - public all: boolean, - public modPackIds: number[], - ) { } - - static jsonSchema = Type.Object({ - "all": Type.Boolean(), - "modPackIds": Type.Array(Type.Integer()), - }); - - static fromJSON(json: Static) { - return new this(json.all, json.modPackIds); - } -} - export class ModGetRequest { declare ["constructor"]: typeof ModGetRequest; static type = "request" as const; @@ -217,28 +195,6 @@ export class ModSearchRequest { }; } -export class ModSetSubscriptionsRequest { - declare ["constructor"]: typeof ModSetSubscriptionsRequest; - static type = "request" as const; - static src = "control" as const; - static dst = "controller" as const; - static permission = "core.mod.subscribe" as const; - - constructor( - public all: boolean, - public modNames: string[], - ) { } - - static jsonSchema = Type.Object({ - "all": Type.Boolean(), - "modNames": Type.Array(Type.String()), - }); - - static fromJSON(json: Static) { - return new this(json.all, json.modNames); - } -} - export class ModDownloadRequest { declare ["constructor"]: typeof ModDownloadRequest; static type = "request" as const; @@ -290,6 +246,7 @@ export class ModPackUpdateEvent { static type = "event" as const; static src = "controller" as const; static dst = "control" as const; + static permission = "core.mod_pack.subscribe" as const; constructor( public modPack: ModPack, @@ -309,6 +266,7 @@ export class ModUpdateEvent { static type = "event" as const; static src = "controller" as const; static dst = "control" as const; + static permission = "core.mod.subscribe" as const; constructor( public mod: ModInfo, diff --git a/packages/lib/src/data/messages_user.ts b/packages/lib/src/data/messages_user.ts index 210254dd..c7e0a62d 100644 --- a/packages/lib/src/data/messages_user.ts +++ b/packages/lib/src/data/messages_user.ts @@ -251,28 +251,6 @@ export class UserListRequest { static Response = jsonArray(RawUser); } -export class UserSetSubscriptionsRequest { - declare ["constructor"]: typeof UserSetSubscriptionsRequest; - static type = "request" as const; - static src = "control" as const; - static dst = "controller" as const; - static permission = "core.user.subscribe" as const; - - constructor( - public all: boolean, - public names: string[], - ) { } - - static jsonSchema = Type.Object({ - "all": Type.Boolean(), - "names": Type.Array(Type.String()), - }); - - static fromJSON(json: Static) { - return new this(json.all, json.names); - } -} - export class UserCreateRequest { declare ["constructor"]: typeof UserCreateRequest; static type = "request" as const; @@ -460,6 +438,7 @@ export class UserUpdateEvent { static type = "event" as const; static src = "controller" as const; static dst = "control" as const; + static permission = "core.user.subscribe" as const; constructor( public user: RawUser, diff --git a/packages/lib/src/link/messages.ts b/packages/lib/src/link/messages.ts index b6161e10..37e2c249 100644 --- a/packages/lib/src/link/messages.ts +++ b/packages/lib/src/link/messages.ts @@ -5,6 +5,7 @@ import * as host from "../data/messages_host"; import * as instance from "../data/messages_instance"; import * as mod from "../data/messages_mod"; import * as user from "../data/messages_user"; +import * as subscriptions from "../subscriptions" import type { RequestClass, EventClass } from "./link"; /** @@ -27,8 +28,9 @@ export const dataClasses: (RequestClass | EventClass) controller.DebugDumpWsRequest, controller.DebugWsMessageEvent, + subscriptions.SubscriptionRequest, + host.HostListRequest, - host.HostSetSubscriptionsRequest, host.HostUpdateEvent, host.HostMetricsRequest, host.ControllerConnectionEvent, @@ -37,7 +39,6 @@ export const dataClasses: (RequestClass | EventClass) instance.InstanceDetailsGetRequest, instance.InstanceDetailsListRequest, - instance.InstanceDetailsSetSubscriptionsRequest, instance.InstanceDetailsUpdateEvent, instance.InstanceCreateRequest, instance.InstanceConfigGetRequest, @@ -47,7 +48,6 @@ export const dataClasses: (RequestClass | EventClass) instance.InstanceMetricsRequest, instance.InstanceStartRequest, instance.InstanceListSavesRequest, - instance.InstanceSetSaveListSubscriptionsRequest, instance.InstanceSaveListUpdateEvent, instance.InstanceCreateSaveRequest, instance.InstanceRenameSaveRequest, @@ -82,11 +82,9 @@ export const dataClasses: (RequestClass | EventClass) mod.ModPackCreateRequest, mod.ModPackUpdateRequest, mod.ModPackDeleteRequest, - mod.ModPackSetSubscriptionsRequest, mod.ModGetRequest, mod.ModListRequest, mod.ModSearchRequest, - mod.ModSetSubscriptionsRequest, mod.ModDownloadRequest, mod.ModDeleteRequest, mod.ModPackUpdateEvent, @@ -100,7 +98,6 @@ export const dataClasses: (RequestClass | EventClass) user.RoleDeleteRequest, user.UserGetRequest, user.UserListRequest, - user.UserSetSubscriptionsRequest, user.UserCreateRequest, user.UserRevokeTokenRequest, user.UserUpdateRolesRequest, diff --git a/packages/lib/src/subscriptions.ts b/packages/lib/src/subscriptions.ts index 32cb28f4..e8dff008 100644 --- a/packages/lib/src/subscriptions.ts +++ b/packages/lib/src/subscriptions.ts @@ -3,9 +3,9 @@ import { Link, Event, EventClass, RequestHandler, WebSocketClientConnector, WebS import { Address, MessageRequest } from "./data"; import { User } from "./users"; -export type SubscriptionChannelCategoriser = (event: Event) => string | number; +export type SubscriptionChannelCategoriser = (event: T) => string | number; export type SubscriptionRequestHandler = RequestHandler | null>; -export type EventSubscriberCallback = (event: Event) => Promise; +export type EventSubscriberCallback = (value: T) => void; /** * Response received by the subscriber after a request @@ -192,15 +192,17 @@ export class SubscriptionController { /** * A class component to handle incoming subscription requests and offers a method to broadcast to subscribers * After creation, no other handler can be registered for SubscriptionRequest + * An optional prehandler can be given to extract data from the event to be used by the subscribed handlers */ -export class EventSubscriber { - _callbacks = new Array>() - _channelCallbacks = new Map>>() - lastResponse: Event | null = null; +export class EventSubscriber { + _callbacks = new Array>() + _channelCallbacks = new Map>>() + lastResponse: T | null = null; // Does not work with Event lastResponseTime = 0; constructor( private event: EventClass, + private prehandler?: (event: T) => V, private control?: Link ) { const entry = Link._eventsByClass.get(this.event); @@ -212,26 +214,33 @@ export class EventSubscriber { } } - async _handle(response: Event) { + async _handle(response: T) { this.lastResponse = response; this.lastResponseTime = Date.now(); - for (let callback of this._callbacks) { - callback(response); - } + if (this.prehandler) { + for (let callback of this._callbacks) { + callback(this.prehandler(response)); + } + } else { + for (let callback of this._callbacks) { + callback(response as any); // Default behaviour is to pass the event unchanged + } + } } connectControl(control: Link) { - if (this.control === control) return; + if (this.control === control) return Promise.resolve(); this.control = control; this.control.handle(this.event, this._handle.bind(this)); + return this._updateSubscription(); } - subscribe(handler: EventSubscriberCallback) { + subscribe(handler: EventSubscriberCallback) { this._callbacks.push(handler); this._updateSubscription(); } - subscribeToChannel(channel: string | number, handler: EventSubscriberCallback) { + subscribeToChannel(channel: string | number, handler: EventSubscriberCallback) { if (!this._channelCallbacks.get(channel)) { this._channelCallbacks.set(channel, []); } @@ -239,7 +248,7 @@ export class EventSubscriber { this._updateSubscription(); } - unsubscribe(handler: EventSubscriberCallback) { + unsubscribe(handler: EventSubscriberCallback) { let index = this._callbacks.lastIndexOf(handler); if (index === -1) { throw new Error("handler is not registered"); @@ -249,7 +258,7 @@ export class EventSubscriber { this._updateSubscription(); } - unsubscribeFromChannel(channel: string | number, handler: EventSubscriberCallback) { + unsubscribeFromChannel(channel: string | number, handler: EventSubscriberCallback) { const channelCallbacks = this._channelCallbacks.get(channel); if (!channelCallbacks) { throw new Error("handler is not registered"); @@ -280,7 +289,7 @@ export class EventSubscriber { )); if (response.eventReplay) { - this._handle(response.eventReplay); + this._handle(response.eventReplay as T); } } } \ No newline at end of file diff --git a/packages/web_ui/src/model/host.ts b/packages/web_ui/src/model/host.ts index 7338572b..84dbe7e2 100644 --- a/packages/web_ui/src/model/host.ts +++ b/packages/web_ui/src/model/host.ts @@ -38,9 +38,9 @@ export function useHost(id: number): [ HostState, () => void ] { setHost({ ...newHost, loading:false, missing:false, present:true }); } - control.onHostUpdate(id, updateHandler); + control.hostUpdate.subscribeToChannel(id, updateHandler); return () => { - control.offHostUpdate(id, updateHandler); + control.hostUpdate.unsubscribeFromChannel(id, updateHandler); }; }, [id]); @@ -74,9 +74,9 @@ export function useHostList() { }); } - control.onHostUpdate(null, updateHandler); + control.hostUpdate.subscribe(updateHandler); return () => { - control.offHostUpdate(null, updateHandler); + control.hostUpdate.unsubscribe(updateHandler); }; }, []); diff --git a/packages/web_ui/src/model/instance.ts b/packages/web_ui/src/model/instance.ts index 60093453..c262c2b9 100644 --- a/packages/web_ui/src/model/instance.ts +++ b/packages/web_ui/src/model/instance.ts @@ -35,9 +35,9 @@ export function useInstance(id: number): [InstanceState, ()=>void] { setInstance({ ...newInstance, present: true }); } - control.onInstanceUpdate(id, updateHandler); + control.instanceUpdate.subscribeToChannel(id, updateHandler); return () => { - control.offInstanceUpdate(id, updateHandler); + control.instanceUpdate.unsubscribeFromChannel(id, updateHandler); }; }, [id]); @@ -76,9 +76,9 @@ export function useInstanceList() { }); } - control.onInstanceUpdate(null, updateHandler); + control.instanceUpdate.subscribe(updateHandler); return () => { - control.offInstanceUpdate(null, updateHandler); + control.instanceUpdate.unsubscribe(updateHandler); }; }, []); diff --git a/packages/web_ui/src/model/mod_pack.ts b/packages/web_ui/src/model/mod_pack.ts index f30ce27e..0d610f29 100644 --- a/packages/web_ui/src/model/mod_pack.ts +++ b/packages/web_ui/src/model/mod_pack.ts @@ -33,9 +33,9 @@ export function useModPack(id: number) { } updateModPack(); - control.onModPackUpdate(id, setModPack); + control.modPackUpdate.subscribeToChannel(id, setModPack); return () => { - control.offModPackUpdate(id, setModPack); + control.modPackUpdate.unsubscribeFromChannel(id, setModPack); }; }, [id]); @@ -74,9 +74,9 @@ export function useModPackList() { }); } - control.onModPackUpdate(null, updateHandler); + control.modPackUpdate.subscribe(updateHandler); return () => { - control.offModPackUpdate(null, updateHandler); + control.modPackUpdate.unsubscribe(updateHandler); }; }, []); diff --git a/packages/web_ui/src/model/mods.ts b/packages/web_ui/src/model/mods.ts index 6c0497b4..0d131fae 100644 --- a/packages/web_ui/src/model/mods.ts +++ b/packages/web_ui/src/model/mods.ts @@ -38,9 +38,9 @@ export function useModList() { }); } - control.onModUpdate(null, updateHandler); + control.modUpdate.subscribe(updateHandler); return () => { - control.offModUpdate(null, updateHandler); + control.modUpdate.unsubscribe(updateHandler); }; }, []); diff --git a/packages/web_ui/src/model/saves.ts b/packages/web_ui/src/model/saves.ts index 49d6d79b..fdb2c88e 100644 --- a/packages/web_ui/src/model/saves.ts +++ b/packages/web_ui/src/model/saves.ts @@ -29,9 +29,9 @@ export function useSaves(instanceId?: number): lib.SaveDetails[] { setSaves(data.saves); } - control.onSaveListUpdate(instanceId!, updateHandler); + control.saveListUpdate.subscribeToChannel(instanceId!, updateHandler); return () => { - control.offSaveListUpdate(instanceId!, updateHandler); + control.saveListUpdate.unsubscribeFromChannel(instanceId!, updateHandler); }; }, [instanceId]); diff --git a/packages/web_ui/src/model/user.tsx b/packages/web_ui/src/model/user.tsx index 792f8113..3a7cccfb 100644 --- a/packages/web_ui/src/model/user.tsx +++ b/packages/web_ui/src/model/user.tsx @@ -82,9 +82,9 @@ export function useUser(name: string): [RawUserState, () => void] { setUser({ ...newUser, present: true }); } - control.onUserUpdate(name, updateHandler); + control.userUpdate.subscribeToChannel(name, updateHandler); return () => { - control.offUserUpdate(name, updateHandler); + control.userUpdate.unsubscribeFromChannel(name, updateHandler); }; }, [name]); @@ -123,9 +123,9 @@ export function useUserList() { }); } - control.onUserUpdate(null, updateHandler); + control.userUpdate.subscribe(updateHandler); return () => { - control.offUserUpdate(null, updateHandler); + control.userUpdate.unsubscribe(updateHandler); }; }, []); diff --git a/packages/web_ui/src/util/websocket.ts b/packages/web_ui/src/util/websocket.ts index 86602cd8..f503e6e0 100644 --- a/packages/web_ui/src/util/websocket.ts +++ b/packages/web_ui/src/util/websocket.ts @@ -50,15 +50,19 @@ export class Control extends lib.Link { accountName: string | null = null; /** Roles of the account this control link is connected as. */ accountRoles: lib.AccountRole[] | null = null; + + /** Updates not handled by the subscription service */ accountUpdateHandlers: Function[] = []; - hostUpdateHandlers: Map = new Map(); - instanceUpdateHandlers: Map = new Map(); - saveListUpdateHandlers: Map = new Map(); - modPackUpdateHandlers: Map = new Map(); - modUpdateHandlers: Map = new Map(); - userUpdateHandlers: Map = new Map(); logHandlers: Map = new Map(); + /** Updates handled by the subscription service */ + hostUpdate = new lib.EventSubscriber(lib.HostUpdateEvent, event => event.update); + instanceUpdate = new lib.EventSubscriber(lib.InstanceDetailsUpdateEvent, event => event.details); + saveListUpdate = new lib.EventSubscriber(lib.InstanceSaveListUpdateEvent); + modPackUpdate = new lib.EventSubscriber(lib.InstanceSaveListUpdateEvent, event => event.modPack); + modUpdate = new lib.EventSubscriber(lib.InstanceSaveListUpdateEvent, event => event.mod); + userUpdate = new lib.EventSubscriber(lib.InstanceSaveListUpdateEvent, event => event.user); + declare connector: ControlConnector; constructor( @@ -75,22 +79,22 @@ export class Control extends lib.Link { this.accountName = data.account.name; this.accountRoles = data.account.roles; this.emitAccountUpdate(); - this.updateHostSubscriptions().catch(err => logger.error( + this.hostUpdate.connectControl(this).catch(err => logger.error( `Unexpected error updating host subscriptions:\n${err.stack}` - )); - this.updateInstanceSubscriptions().catch(err => logger.error( + ));; + this.instanceUpdate.connectControl(this).catch(err => logger.error( `Unexpected error updating instance subscriptions:\n${err.stack}` )); - this.updateSaveListSubscriptions().catch(err => logger.error( + this.saveListUpdate.connectControl(this).catch(err => logger.error( `Unexpected error updating save list subscriptions:\n${err.stack}` )); - this.updateModPackSubscriptions().catch(err => logger.error( + this.modPackUpdate.connectControl(this).catch(err => logger.error( `Unexpected error updating mod pack subscriptions:\n${err.stack}` )); - this.updateModSubscriptions().catch(err => logger.error( + this.modUpdate.connectControl(this).catch(err => logger.error( `Unexpected error updating mod subscriptions:\n${err.stack}` )); - this.updateUserSubscriptions().catch(err => logger.error( + this.userUpdate.connectControl(this).catch(err => logger.error( `Unexpected error updating user subscriptions:\n${err.stack}` )); this.updateLogSubscriptions().catch(err => logger.error( @@ -113,12 +117,6 @@ export class Control extends lib.Link { } this.handle(lib.AccountUpdateEvent, this.handleAccountUpdateEvent.bind(this)); - this.handle(lib.HostUpdateEvent, this.handleHostUpdateEvent.bind(this)); - this.handle(lib.InstanceDetailsUpdateEvent, this.handleInstanceDetailsUpdateEvent.bind(this)); - this.handle(lib.InstanceSaveListUpdateEvent, this.handleInstanceSaveListUpdateEvent.bind(this)); - this.handle(lib.ModPackUpdateEvent, this.handleModPackUpdateEvent.bind(this)); - this.handle(lib.ModUpdateEvent, this.handleModUpdateEvent.bind(this)); - this.handle(lib.UserUpdateEvent, this.handleUserUpdateEvent.bind(this)); this.handle(lib.LogMessageEvent, this.handleLogMessageEvent.bind(this)); this.handle(lib.DebugWsMessageEvent, this.handleDebugWsMessageEvent.bind(this)); } @@ -149,349 +147,6 @@ export class Control extends lib.Link { this.accountUpdateHandlers.splice(index, 1); } - async handleHostUpdateEvent(event: lib.HostUpdateEvent) { - let handlers: Function[] = ([] as Function[]).concat( - this.hostUpdateHandlers.get(null) || [], - this.hostUpdateHandlers.get(event.update.id) || [], - ); - for (let handler of handlers) { - handler(event.update); - } - } - - async onHostUpdate(id: number|null, handler: hostHandler) { - if (id !== null && !Number.isInteger(id)) { - throw new Error("Invalid host id"); - } - - let handlers = this.hostUpdateHandlers.get(id); - if (!handlers) { - handlers = []; - this.hostUpdateHandlers.set(id, handlers); - } - - handlers.push(handler); - - if (handlers.length === 1) { - await this.updateHostSubscriptions(); - } - } - - async offHostUpdate(id: number|null, handler: hostHandler) { - let handlers = this.hostUpdateHandlers.get(id); - if (!handlers || !handlers.length) { - throw new Error(`No handlers for host ${id} exists`); - } - - let index = handlers.lastIndexOf(handler); - if (index === -1) { - throw new Error(`Given handler is not registered for host ${id}`); - } - - handlers.splice(index, 1); - if (!handlers.length) { - this.hostUpdateHandlers.delete(id); - await this.updateHostSubscriptions(); - } - } - - async updateHostSubscriptions() { - if (!this.connector.connected) { - return; - } - - await this.send(new lib.HostSetSubscriptionsRequest( - this.hostUpdateHandlers.has(null), - [...this.hostUpdateHandlers.keys()].filter(e => e !== null) as number[], - )); - } - - async handleInstanceDetailsUpdateEvent(event: lib.InstanceDetailsUpdateEvent) { - let handlers = ([] as Function[]).concat( - this.instanceUpdateHandlers.get(null) || [], - this.instanceUpdateHandlers.get(event.details.id) || [], - ); - for (let handler of handlers) { - handler(event.details); - } - }; - - async onInstanceUpdate(id: number|null, handler: instanceHandler) { - if (id !== null && !Number.isInteger(id)) { - throw new Error("Invalid instance id"); - } - - let handlers = this.instanceUpdateHandlers.get(id); - if (!handlers) { - handlers = []; - this.instanceUpdateHandlers.set(id, handlers); - } - - handlers.push(handler); - - if (handlers.length === 1) { - await this.updateInstanceSubscriptions(); - } - } - - async offInstanceUpdate(id: number|null, handler: instanceHandler) { - let handlers = this.instanceUpdateHandlers.get(id); - if (!handlers || !handlers.length) { - throw new Error(`No handlers for instance ${id} exist`); - } - - let index = handlers.lastIndexOf(handler); - if (index === -1) { - throw new Error(`Given handler is not registered for instance ${id}`); - } - - handlers.splice(index, 1); - if (!handlers.length) { - this.instanceUpdateHandlers.delete(id); - await this.updateInstanceSubscriptions(); - } - } - - async updateInstanceSubscriptions() { - if (!this.connector.connected) { - return; - } - - await this.send(new lib.InstanceDetailsSetSubscriptionsRequest( - this.instanceUpdateHandlers.has(null), - [...this.instanceUpdateHandlers.keys()].filter(e => e !== null) as number[], - )); - } - - async handleInstanceSaveListUpdateEvent(event: lib.InstanceSaveListUpdateEvent) { - let handlers = this.saveListUpdateHandlers.get(event.instanceId); - for (let handler of handlers || []) { - handler(event); - } - }; - - async onSaveListUpdate(id: number, handler: saveListHandler) { - if (!Number.isInteger(id)) { - throw new Error("Invalid instance id"); - } - - let handlers = this.saveListUpdateHandlers.get(id); - if (!handlers) { - handlers = []; - this.saveListUpdateHandlers.set(id, handlers); - } - - handlers.push(handler); - - if (handlers.length === 1) { - await this.updateSaveListSubscriptions(); - } - } - - async offSaveListUpdate(id: number, handler: saveListHandler) { - let handlers = this.saveListUpdateHandlers.get(id); - if (!handlers) { - throw new Error(`No handlers for instance ${id} exists`); - } - - let index = handlers.lastIndexOf(handler); - if (index === -1) { - throw new Error(`Given handler is not registered for instance ${id}`); - } - - handlers.splice(index, 1); - if (!handlers.length) { - this.saveListUpdateHandlers.delete(id); - await this.updateSaveListSubscriptions(); - } - } - - async updateSaveListSubscriptions() { - if (!this.connector.connected) { - return; - } - - await this.send(new lib.InstanceSetSaveListSubscriptionsRequest( - false, - [...this.saveListUpdateHandlers.keys()].filter(e => e !== null) as number[], - )); - } - - async handleModPackUpdateEvent(event: lib.ModPackUpdateEvent) { - let modPack = event.modPack; - - let handlers = ([] as modPackHandler[]).concat( - this.modPackUpdateHandlers.get(null) || [], - this.modPackUpdateHandlers.get(modPack.id??null) || [], - ); - for (let handler of handlers) { - handler(modPack); - } - } - - async onModPackUpdate(id: number|null, handler: modPackHandler) { - if (id !== null && typeof id !== "number") { - throw new Error("Invalid mod pack id"); - } - - let handlers = this.modPackUpdateHandlers.get(id); - if (!handlers) { - handlers = []; - this.modPackUpdateHandlers.set(id, handlers); - } - - handlers.push(handler); - - if (handlers.length === 1) { - await this.updateModPackSubscriptions(); - } - } - - async offModPackUpdate(id: number|null, handler: modPackHandler) { - let handlers = this.modPackUpdateHandlers.get(id); - if (!handlers || !handlers.length) { - throw new Error(`No handlers for mod pack ${id} exist`); - } - - let index = handlers.lastIndexOf(handler); - if (index === -1) { - throw new Error(`Given handler is not registered for mod pack ${id}`); - } - - handlers.splice(index, 1); - if (!handlers.length) { - this.modPackUpdateHandlers.delete(id); - await this.updateModPackSubscriptions(); - } - } - - async updateModPackSubscriptions() { - if (!this.connector.connected) { - return; - } - - await this.send(new lib.ModPackSetSubscriptionsRequest( - this.modPackUpdateHandlers.has(null), - [...this.modPackUpdateHandlers.keys()] - .filter(k => k !== null && k !== undefined) as number[], - )); - } - - async handleModUpdateEvent(event: lib.ModUpdateEvent) { - let mod = event.mod; - let handlers = ([] as modInfoHandler[]).concat( - this.modUpdateHandlers.get(null) || [], - this.modUpdateHandlers.get(mod.name) || [] - ); - for (let handler of handlers) { - handler(mod); - } - } - - async onModUpdate(name: string|null, handler: modInfoHandler) { - if (name !== null && typeof name !== "string") { - throw new Error("Invalid mod name"); - } - - let handlers = this.modUpdateHandlers.get(name); - if (!handlers) { - handlers = []; - this.modUpdateHandlers.set(name, handlers); - } - - handlers.push(handler); - - if (handlers.length === 1) { - await this.updateModSubscriptions(); - } - } - - async offModUpdate(name: string|null, handler: modInfoHandler) { - let handlers = this.modUpdateHandlers.get(name); - if (!handlers || !handlers.length) { - throw new Error(`No handlers for mod ${name} exist`); - } - - let index = handlers.lastIndexOf(handler); - if (index === -1) { - throw new Error(`Given handler is not registered for mod ${name}`); - } - - handlers.splice(index, 1); - if (!handlers.length) { - this.modUpdateHandlers.delete(name); - await this.updateModSubscriptions(); - } - } - - async updateModSubscriptions() { - if (!this.connector.connected) { - return; - } - - await this.send(new lib.ModSetSubscriptionsRequest( - this.modUpdateHandlers.has(null), - [...this.modUpdateHandlers.keys()].filter(k => k !== null) as string[], - )); - } - - async handleUserUpdateEvent(event: lib.UserUpdateEvent) { - let handlers = ([] as userHandler[]).concat( - this.userUpdateHandlers.get(null) || [], - this.userUpdateHandlers.get(event.user.name) || [], - ); - for (let handler of handlers) { - handler(event.user); - } - }; - - async onUserUpdate(name: string|null, handler: userHandler) { - if (name !== null && typeof name !== "string") { - throw new Error("Invalid user name"); - } - - let handlers = this.userUpdateHandlers.get(name); - if (!handlers) { - handlers = []; - this.userUpdateHandlers.set(name, handlers); - } - - handlers.push(handler); - - if (handlers.length === 1) { - await this.updateUserSubscriptions(); - } - } - - async offUserUpdate(name: string|null, handler: userHandler) { - let handlers = this.userUpdateHandlers.get(name); - if (!handlers || !handlers.length) { - throw new Error(`No handlers for user ${name} exist`); - } - - let index = handlers.lastIndexOf(handler); - if (index === -1) { - throw new Error(`Given handler is not registered for user ${name}`); - } - - handlers.splice(index, 1); - if (!handlers.length) { - this.userUpdateHandlers.delete(name); - await this.updateUserSubscriptions(); - } - } - - async updateUserSubscriptions() { - if (!this.connector.connected) { - return; - } - - await this.send(new lib.UserSetSubscriptionsRequest( - this.userUpdateHandlers.has(null), - [...this.userUpdateHandlers.keys()].filter(e => e !== null) as string[], - )); - } - async handleLogMessageEvent(event: lib.LogMessageEvent) { let info = event.info;