From 20330bc2c3fee16d825aa471b080f2d2ae369505 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Fri, 8 Dec 2023 22:01:46 +0100 Subject: [PATCH] Push multiple updates in each update event Refactor HostUpdatesEvent, InstanceDetailsUpdatesEvent, ModUpdatesEvent, ModPackUpdatesEvent and UserUpdatesEvent to have multiple updates of the entities they support in each event. This is intended to make the events capable of syncing the entire list that they represent. --- .../controller/src/BaseControllerPlugin.ts | 22 ++--- packages/controller/src/ControlConnection.ts | 22 ++--- packages/controller/src/Controller.ts | 86 +++++++++++-------- packages/controller/src/HostConnection.ts | 17 ++-- packages/controller/src/WsServer.ts | 4 +- packages/controller/src/routes.ts | 2 +- packages/lib/src/data/messages_host.ts | 10 +-- packages/lib/src/data/messages_instance.ts | 16 ++-- packages/lib/src/data/messages_mod.ts | 20 ++--- packages/lib/src/data/messages_user.ts | 10 +-- packages/lib/src/link/messages.ts | 10 +-- packages/web_ui/src/model/host.ts | 22 ++--- packages/web_ui/src/model/instance.ts | 28 +++--- packages/web_ui/src/model/mod_pack.ts | 28 +++--- packages/web_ui/src/model/mods.ts | 20 +++-- packages/web_ui/src/model/user.tsx | 28 +++--- packages/web_ui/src/util/websocket.ts | 20 ++--- test/integration/index.js | 40 ++++----- 18 files changed, 215 insertions(+), 190 deletions(-) diff --git a/packages/controller/src/BaseControllerPlugin.ts b/packages/controller/src/BaseControllerPlugin.ts index 659001d2..9aec578b 100644 --- a/packages/controller/src/BaseControllerPlugin.ts +++ b/packages/controller/src/BaseControllerPlugin.ts @@ -229,29 +229,29 @@ export default class BaseControllerPlugin { async onPrepareHostDisconnect(connection: HostConnection) { } /** - * Called when a mod pack is updated + * Called when one or more mod packs are updated * - * Invoked when a mod pack has been added, updated or deleted in the - * cluster. + * Invoked when one or more mod pack have been added, updated or deleted + * in the cluster. * - * If the mod pack has been deleted its `.isDeleted` property will be + * If a mod pack has been deleted its `.isDeleted` property will be * true. * - * @param modPack - Mod pack that updated. + * @param modPacks - Mod packs that updated. */ - async onModPackUpdated(modPack: ModPack) { } + async onModPacksUpdated(modPacks: ModPack[]) { } /** - * Called when a mod stored on the controller is updated + * Called when one or more mod stored on the controller are updated * - * Invoked when a mod has been added, updated or deleted from the pool - * of shared mods stored on the cluster. + * Invoked when one or more mods have been added, updated or deleted + * from the pool of shared mods stored on the cluster. * * If a mod has been deleted its `.isDeleted` property will be true. * - * @param mod - Mod that updated. + * @param mods - Mods that updated. */ - async onModUpdated(mod: ModInfo) { } + async onModsUpdated(mods: ModInfo[]) { } /** * Called when a player joins or leaves an instance diff --git a/packages/controller/src/ControlConnection.ts b/packages/controller/src/ControlConnection.ts index abd955b7..85fe45e1 100644 --- a/packages/controller/src/ControlConnection.ts +++ b/packages/controller/src/ControlConnection.ts @@ -195,7 +195,7 @@ export default class ControlConnection extends BaseConnection { hostConnection.connector.terminate(); } - this._controller.hostUpdated(host as HostInfo); + this._controller.hostsUpdated([host]); } async handleHostListRequest(): Promise { @@ -434,7 +434,7 @@ export default class ControlConnection extends BaseConnection { throw new lib.RequestError(`Mod pack with ID ${modPack.id} already exist`); } this._controller.modPacks.set(modPack.id, modPack); - this._controller.modPackUpdated(modPack); + this._controller.modPacksUpdated([modPack]); } async handleModPackUpdateRequest(request: lib.ModPackUpdateRequest) { @@ -443,7 +443,7 @@ export default class ControlConnection extends BaseConnection { throw new lib.RequestError(`Mod pack with ID ${modPack.id} does not exist`); } this._controller.modPacks.set(modPack.id, modPack); - this._controller.modPackUpdated(modPack); + this._controller.modPacksUpdated([modPack]); } async handleModPackDeleteRequest(request: lib.ModPackDeleteRequest) { @@ -454,7 +454,7 @@ export default class ControlConnection extends BaseConnection { } modPack.isDeleted = true; this._controller.modPacks.delete(id); - this._controller.modPackUpdated(modPack); + this._controller.modPacksUpdated([modPack]); } async handleModGetRequest(request: lib.ModGetRequest) { @@ -680,7 +680,7 @@ export default class ControlConnection extends BaseConnection { async handleUserCreateRequest(request: lib.UserCreateRequest) { let user = this._controller.userManager.createUser(request.name); - this._controller.userUpdated(user); + this._controller.usersUpdated([user]); } async handleUserRevokeTokenRequest(request: lib.UserRevokeTokenRequest) { @@ -698,7 +698,7 @@ export default class ControlConnection extends BaseConnection { controlConnection.connector.terminate(); } } - this._controller.userUpdated(user); + this._controller.usersUpdated([user]); } async handleUserUpdateRolesRequest(request: lib.UserUpdateRolesRequest) { @@ -715,7 +715,7 @@ export default class ControlConnection extends BaseConnection { user.roleIds = new Set(request.roles); this._controller.userPermissionsUpdated(user); - this._controller.userUpdated(user); + this._controller.usersUpdated([user]); } async handleUserSetAdminRequest(request: lib.UserSetAdminRequest) { @@ -731,7 +731,7 @@ export default class ControlConnection extends BaseConnection { } user.isAdmin = admin; - this._controller.userUpdated(user); + this._controller.usersUpdated([user]); this._controller.sendTo("allInstances", new lib.InstanceAdminlistUpdateEvent(name, admin)); } @@ -749,7 +749,7 @@ export default class ControlConnection extends BaseConnection { user.isBanned = banned; user.banReason = reason; - this._controller.userUpdated(user); + this._controller.usersUpdated([user]); this._controller.sendTo("allInstances", new lib.InstanceBanlistUpdateEvent(name, banned, reason)); } @@ -766,7 +766,7 @@ export default class ControlConnection extends BaseConnection { } user.isWhitelisted = whitelisted; - this._controller.userUpdated(user); + this._controller.usersUpdated([user]); this._controller.sendTo("allInstances", new lib.InstanceWhitelistUpdateEvent(name, whitelisted)); } @@ -779,7 +779,7 @@ export default class ControlConnection extends BaseConnection { user.isDeleted = true; this._controller.userManager.users.delete(name); - this._controller.userUpdated(user); + this._controller.usersUpdated([user]); if (user.isAdmin) { this._controller.sendTo("allInstances", new lib.InstanceAdminlistUpdateEvent(name, false)); diff --git a/packages/controller/src/Controller.ts b/packages/controller/src/Controller.ts index 8b1a466e..0f7d0fbc 100644 --- a/packages/controller/src/Controller.ts +++ b/packages/controller/src/Controller.ts @@ -143,16 +143,16 @@ export default class Controller { this.subscriptions = new lib.SubscriptionController(this); this.modStore.on("change", mod => { - this.modUpdated(mod); + this.modsUpdated([mod]); }); // Handle subscriptions for all internal properties - this.subscriptions.handle(lib.HostUpdateEvent); - this.subscriptions.handle(lib.InstanceDetailsUpdateEvent); + this.subscriptions.handle(lib.HostUpdatesEvent); + this.subscriptions.handle(lib.InstanceDetailsUpdatesEvent); this.subscriptions.handle(lib.InstanceSaveListUpdateEvent); - this.subscriptions.handle(lib.ModPackUpdateEvent); - this.subscriptions.handle(lib.ModUpdateEvent); - this.subscriptions.handle(lib.UserUpdateEvent); + this.subscriptions.handle(lib.ModPackUpdatesEvent); + this.subscriptions.handle(lib.ModUpdatesEvent); + this.subscriptions.handle(lib.UserUpdatesEvent); } async start(args: ControllerArgs) { @@ -607,8 +607,8 @@ export default class Controller { ); } - hostUpdated(host: HostInfo) { - let update = new lib.HostDetails( + hostsUpdated(hosts: HostInfo[]) { + let updates = hosts.map(host => new lib.HostDetails( host.agent, host.version, host.name, @@ -616,9 +616,9 @@ export default class Controller { this.wsServer.hostConnections.has(host.id), host.public_address, host.token_valid_after, - ); + )); - this.subscriptions.broadcast(new lib.HostUpdateEvent(update)); + this.subscriptions.broadcast(new lib.HostUpdatesEvent(updates)); } /** @@ -690,7 +690,7 @@ export default class Controller { this.instancesDirty = true; await lib.invokeHook(this.plugins, "onInstanceStatusChanged", instance); this.addInstanceHooks(instance); - this.instanceDetailsUpdated(instance); + this.instanceDetailsUpdated([instance]); return instance; } @@ -742,7 +742,7 @@ export default class Controller { ); } else { instance.status = "unassigned"; - this.instanceDetailsUpdated(instance); + this.instanceDetailsUpdated([instance]); } } @@ -765,7 +765,7 @@ export default class Controller { let prev = instance.status; instance.status = "deleted"; - this.instanceDetailsUpdated(instance); + this.instanceDetailsUpdated([instance]); await lib.invokeHook(this.plugins, "onInstanceStatusChanged", instance, prev); } @@ -793,7 +793,7 @@ export default class Controller { addInstanceHooks(instance: InstanceInfo) { instance.config.on("fieldChanged", (group: lib.ConfigGroup, field: string, prev: any) => { if (group.name === "instance" && field === "name") { - this.instanceDetailsUpdated(instance); + this.instanceDetailsUpdated([instance]); } this.instancesDirty = true; @@ -801,50 +801,60 @@ export default class Controller { }); } - instanceDetailsUpdated(instance: InstanceInfo) { - let assigned_host: number|null|undefined = instance.config.get("instance.assigned_host"); - if (assigned_host === null) { - assigned_host = undefined; - } + instanceDetailsUpdated(instances: InstanceInfo[]) { + const now = Date.now(); + const updates: lib.InstanceDetails[] = []; + for (const instance of instances) { + 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; - } + 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( + updates.push(new lib.InstanceDetails( instance.config.get("instance.name"), instance.id, assigned_host, game_port, instance.status, - Date.now(), - ) - )); + now, + )); + } + + this.subscriptions.broadcast(new lib.InstanceDetailsUpdatesEvent(updates)); } saveListUpdate(instanceId: number, saves: lib.SaveDetails[]) { this.subscriptions.broadcast(new lib.InstanceSaveListUpdateEvent(instanceId, saves)); } - modPackUpdated(modPack: lib.ModPack) { - modPack.updatedAt = Date.now(); + modPacksUpdated(modPacks: lib.ModPack[]) { + const now = Date.now(); + for (const modPack of modPacks) { + modPack.updatedAt = now; + } this.modPacksDirty = true; - this.subscriptions.broadcast(new lib.ModPackUpdateEvent(modPack)); - lib.invokeHook(this.plugins, "onModPackUpdated", modPack); + this.subscriptions.broadcast(new lib.ModPackUpdatesEvent(modPacks)); + lib.invokeHook(this.plugins, "onModPacksUpdated", modPacks); } - modUpdated(mod: lib.ModInfo) { + modsUpdated(mods: lib.ModInfo[]) { // ModStore sets updatedAt for mods - this.subscriptions.broadcast(new lib.ModUpdateEvent(mod)); - lib.invokeHook(this.plugins, "onModUpdated", mod); + this.subscriptions.broadcast(new lib.ModUpdatesEvent(mods)); + lib.invokeHook(this.plugins, "onModsUpdated", mods); } - userUpdated(user: ControllerUser) { - user.updatedAt = Date.now(); + usersUpdated(users: ControllerUser[]) { + const now = Date.now(); + for (const user of users) { + user.updatedAt = now; + } this.userManager.dirty = true; - this.subscriptions.broadcast(new lib.UserUpdateEvent(user)); + this.subscriptions.broadcast(new lib.UserUpdatesEvent(users)); } /** diff --git a/packages/controller/src/HostConnection.ts b/packages/controller/src/HostConnection.ts index 140bd124..02b6f131 100644 --- a/packages/controller/src/HostConnection.ts +++ b/packages/controller/src/HostConnection.ts @@ -69,16 +69,18 @@ export default class HostConnection extends BaseConnection { this.connector.on("close", () => { // Update status to unknown for instances on this host. + const instances: InstanceInfo[] = []; for (let instance of this._controller.instances.values()) { if (instance.config.get("instance.assigned_host") !== this._id) { continue; } + instances.push(instance); let prev = instance.status; instance.status = "unknown"; - this._controller.instanceDetailsUpdated(instance); lib.invokeHook(this._controller.plugins, "onInstanceStatusChanged", instance, prev); } + this._controller.instanceDetailsUpdated(instances); }); this.handle(lib.InstanceStatusChangedEvent, this.handleInstanceStatusChangedEvent.bind(this)); @@ -180,7 +182,7 @@ export default class HostConnection extends BaseConnection { instance.status = request.status as lib.InstanceStatus; instance.game_port = request.gamePort || null; logger.verbose(`Instance ${instance.config.get("instance.name")} State: ${instance.status}`); - this._controller.instanceDetailsUpdated(instance); + this._controller.instanceDetailsUpdated([instance]); await lib.invokeHook(this._controller.plugins, "onInstanceStatusChanged", instance, prev); } @@ -195,6 +197,7 @@ export default class HostConnection extends BaseConnection { } // Assign instances the host has but controller does not + const instanceUpdates: InstanceInfo[] = []; for (let instanceData of request.instances) { let instanceConfig = new lib.InstanceConfig("controller"); await instanceConfig.load(instanceData.config as lib.SerializedConfig, "host"); @@ -214,7 +217,7 @@ export default class HostConnection extends BaseConnection { let prev = controllerInstance.status; controllerInstance.status = instanceData.status as InstanceStatus; logger.verbose(`Instance ${instanceConfig.get("instance.name")} State: ${instanceData.status}`); - this._controller.instanceDetailsUpdated(controllerInstance); + instanceUpdates.push(controllerInstance); await lib.invokeHook( this._controller.plugins, "onInstanceStatusChanged", controllerInstance, prev ); @@ -234,10 +237,14 @@ export default class HostConnection extends BaseConnection { instanceConfig.get("instance.id"), instanceConfig.serialize("host") ) ); - this._controller.instanceDetailsUpdated(newInstance); + instanceUpdates.push(newInstance); await lib.invokeHook(this._controller.plugins, "onInstanceStatusChanged", newInstance); } + if (instanceUpdates.length) { + this._controller.instanceDetailsUpdated(instanceUpdates); + } + // Push lists to make sure they are in sync. let adminlist: Set = new Set(); let banlist: Map = new Map(); @@ -286,7 +293,7 @@ export default class HostConnection extends BaseConnection { user.instanceStats.set(instanceId, event.stats); user.recalculatePlayerStats(); - this._controller.userUpdated(user); + this._controller.usersUpdated([user]); let instance = this._controller.instances.get(instanceId)!; await lib.invokeHook(this._controller.plugins, "onPlayerEvent", instance, { diff --git a/packages/controller/src/WsServer.ts b/packages/controller/src/WsServer.ts index bc63a56f..bcc03be7 100644 --- a/packages/controller/src/WsServer.ts +++ b/packages/controller/src/WsServer.ts @@ -274,13 +274,13 @@ ${err.stack}` connector.on("close", () => { if (this.hostConnections.get(data.id) === connection) { this.hostConnections.delete(data.id); - this.controller.hostUpdated(this.controller.hosts.get(data.id) as HostInfo); + this.controller.hostsUpdated([this.controller.hosts.get(data.id)!]); } else { logger.warn("Unlisted HostConnection closed"); } }); this.hostConnections.set(data.id, connection); - this.controller.hostUpdated(this.controller.hosts.get(data.id) as HostInfo); + this.controller.hostsUpdated([this.controller.hosts.get(data.id)!]); let src = new lib.Address(lib.Address.host, data.id); connector.ready(socket, src, sessionToken, undefined); } diff --git a/packages/controller/src/routes.ts b/packages/controller/src/routes.ts index 99446069..66fc5763 100644 --- a/packages/controller/src/routes.ts +++ b/packages/controller/src/routes.ts @@ -256,7 +256,7 @@ async function uploadExport(req: Request, res: Response) { modPack.exportManifest = new lib.ExportManifest(assets); modPack.fillDefaultSettings(settingPrototypes, logger); - res.app.locals.controller.modPackUpdated(modPack); + res.app.locals.controller.modPacksUpdated([modPack]); res.sendStatus(200); } diff --git a/packages/lib/src/data/messages_host.ts b/packages/lib/src/data/messages_host.ts index 1aadc21e..674ff34e 100644 --- a/packages/lib/src/data/messages_host.ts +++ b/packages/lib/src/data/messages_host.ts @@ -52,23 +52,23 @@ export class HostListRequest { static Response = jsonArray(HostDetails); } -export class HostUpdateEvent { - declare ["constructor"]: typeof HostUpdateEvent; +export class HostUpdatesEvent { + declare ["constructor"]: typeof HostUpdatesEvent; 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, + public updates: HostDetails[], ) { } static jsonSchema = Type.Object({ - "update": HostDetails.jsonSchema, + "updates": Type.Array(HostDetails.jsonSchema), }); static fromJSON(json: Static) { - return new this(HostDetails.fromJSON(json.update)); + return new this(json.updates.map(update => HostDetails.fromJSON(update))); } } diff --git a/packages/lib/src/data/messages_instance.ts b/packages/lib/src/data/messages_instance.ts index 19e29ef6..ca7ac30c 100644 --- a/packages/lib/src/data/messages_instance.ts +++ b/packages/lib/src/data/messages_instance.ts @@ -81,25 +81,23 @@ export class InstanceDetailsListRequest { static Response = jsonArray(InstanceDetails); }; -export class InstanceDetailsUpdateEvent { - declare ["constructor"]: typeof InstanceDetailsUpdateEvent; +export class InstanceDetailsUpdatesEvent { + declare ["constructor"]: typeof InstanceDetailsUpdatesEvent; 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, + public updates: InstanceDetails[], ) { } - static jsonSchema = InstanceDetails.jsonSchema; - - toJSON() { - return this.details; - } + static jsonSchema = Type.Object({ + "updates": Type.Array(InstanceDetails.jsonSchema), + }); static fromJSON(json: Static) { - return new this(InstanceDetails.fromJSON(json)); + return new this(json.updates.map(update => InstanceDetails.fromJSON(update))); } }; diff --git a/packages/lib/src/data/messages_mod.ts b/packages/lib/src/data/messages_mod.ts index c522edae..60fc6563 100644 --- a/packages/lib/src/data/messages_mod.ts +++ b/packages/lib/src/data/messages_mod.ts @@ -245,42 +245,42 @@ export class ModDeleteRequest { } } -export class ModPackUpdateEvent { - declare ["constructor"]: typeof ModPackUpdateEvent; +export class ModPackUpdatesEvent { + declare ["constructor"]: typeof ModPackUpdatesEvent; 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, + public updates: ModPack[], ) { } static jsonSchema = Type.Object({ - "modPack": ModPack.jsonSchema, + "updates": Type.Array(ModPack.jsonSchema), }); static fromJSON(json: Static) { - return new this(ModPack.fromJSON(json.modPack)); + return new this(json.updates.map(update => ModPack.fromJSON(update))); } } -export class ModUpdateEvent { - declare ["constructor"]: typeof ModUpdateEvent; +export class ModUpdatesEvent { + declare ["constructor"]: typeof ModUpdatesEvent; 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, + public updates: ModInfo[], ) { } static jsonSchema = Type.Object({ - "mod": ModInfo.jsonSchema, + "updates": Type.Array(ModInfo.jsonSchema), }); static fromJSON(json: Static) { - return new this(ModInfo.fromJSON(json.mod)); + return new this(json.updates.map(update => ModInfo.fromJSON(update))); } } diff --git a/packages/lib/src/data/messages_user.ts b/packages/lib/src/data/messages_user.ts index 2b31bdb5..17fcae89 100644 --- a/packages/lib/src/data/messages_user.ts +++ b/packages/lib/src/data/messages_user.ts @@ -301,22 +301,22 @@ export class UserDeleteRequest { } } -export class UserUpdateEvent { - declare ["constructor"]: typeof UserUpdateEvent; +export class UserUpdatesEvent { + declare ["constructor"]: typeof UserUpdatesEvent; 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: User, + public updates: User[], ) { } static jsonSchema = Type.Object({ - "user": User.jsonSchema, + "updates": Type.Array(User.jsonSchema), }); static fromJSON(json: Static) { - return new this(User.fromJSON(json.user)); + return new this(json.updates.map(update => User.fromJSON(update))); } } diff --git a/packages/lib/src/link/messages.ts b/packages/lib/src/link/messages.ts index e9b1cc5e..4bd0f0ca 100644 --- a/packages/lib/src/link/messages.ts +++ b/packages/lib/src/link/messages.ts @@ -31,7 +31,7 @@ export const dataClasses: (RequestClass | EventClass) subscriptions.SubscriptionRequest, host.HostListRequest, - host.HostUpdateEvent, + host.HostUpdatesEvent, host.HostMetricsRequest, host.ControllerConnectionEvent, host.PrepareControllerDisconnectRequest, @@ -40,7 +40,7 @@ export const dataClasses: (RequestClass | EventClass) instance.InstanceDetailsGetRequest, instance.InstanceDetailsListRequest, - instance.InstanceDetailsUpdateEvent, + instance.InstanceDetailsUpdatesEvent, instance.InstanceCreateRequest, instance.InstanceConfigGetRequest, instance.InstanceConfigSetFieldRequest, @@ -88,8 +88,8 @@ export const dataClasses: (RequestClass | EventClass) mod.ModSearchRequest, mod.ModDownloadRequest, mod.ModDeleteRequest, - mod.ModPackUpdateEvent, - mod.ModUpdateEvent, + mod.ModPackUpdatesEvent, + mod.ModUpdatesEvent, user.PermissionListRequest, user.RoleListRequest, @@ -106,5 +106,5 @@ export const dataClasses: (RequestClass | EventClass) user.UserSetWhitelistedRequest, user.UserSetBannedRequest, user.UserDeleteRequest, - user.UserUpdateEvent, + user.UserUpdatesEvent, ]; diff --git a/packages/web_ui/src/model/host.ts b/packages/web_ui/src/model/host.ts index c13d09da..ec067f14 100644 --- a/packages/web_ui/src/model/host.ts +++ b/packages/web_ui/src/model/host.ts @@ -35,11 +35,11 @@ export function useHost(id: number): [ HostState, () => void ] { } updateHost(); - function updateHandler(newHost: lib.HostDetails) { - if (newHost.id !== id) { - return; + function updateHandler(newHosts: lib.HostDetails[]) { + const newHost = newHosts.find(h => h.id === id); + if (newHost) { + setHost({ ...newHost, loading: false, missing: false, present: true }); } - setHost({ ...newHost, loading: false, missing: false, present: true }); } control.hostUpdate.subscribe(updateHandler); @@ -66,14 +66,16 @@ export function useHostList() { useEffect(() => { updateHostList(); - function updateHandler(newHost: lib.HostDetails) { + function updateHandler(newHosts: lib.HostDetails[]) { setHostList(oldList => { let newList = oldList.concat(); - let index = newList.findIndex(s => s.id === newHost.id); - if (index !== -1) { - newList[index] = newHost; - } else { - newList.push(newHost); + for (const newHost of newHosts) { + let index = newList.findIndex(s => s.id === newHost.id); + if (index !== -1) { + newList[index] = newHost; + } else { + newList.push(newHost); + } } return newList; }); diff --git a/packages/web_ui/src/model/instance.ts b/packages/web_ui/src/model/instance.ts index e934f218..bd50b3a4 100644 --- a/packages/web_ui/src/model/instance.ts +++ b/packages/web_ui/src/model/instance.ts @@ -31,11 +31,11 @@ export function useInstance(id: number): [InstanceState, ()=>void] { } updateInstance(); - function updateHandler(newInstance: lib.InstanceDetails) { - if (newInstance.id !== id) { - return; + function updateHandler(newInstances: lib.InstanceDetails[]) { + const newInstance = newInstances.find(i => i.id === id); + if (newInstance) { + setInstance({ ...newInstance, present: true }); } - setInstance({ ...newInstance, present: true }); } control.instanceUpdate.subscribe(updateHandler); @@ -62,18 +62,20 @@ export function useInstanceList() { useEffect(() => { updateInstanceList(); - function updateHandler(newInstance: lib.InstanceDetails) { + function updateHandler(newInstances: lib.InstanceDetails[]) { setInstanceList(oldList => { let newList = oldList.concat(); - let index = newList.findIndex(s => s.id === newInstance.id); - if (newInstance.status !== "deleted") { - if (index !== -1) { - newList[index] = newInstance; - } else { - newList.push(newInstance); + for (const newInstance of newInstances) { + let index = newList.findIndex(s => s.id === newInstance.id); + if (newInstance.status !== "deleted") { + if (index !== -1) { + newList[index] = newInstance; + } else { + newList.push(newInstance); + } + } else if (index !== -1) { + newList.splice(index, 1); } - } else if (index !== -1) { - newList.splice(index, 1); } return newList; }); diff --git a/packages/web_ui/src/model/mod_pack.ts b/packages/web_ui/src/model/mod_pack.ts index c089d538..6b65b36b 100644 --- a/packages/web_ui/src/model/mod_pack.ts +++ b/packages/web_ui/src/model/mod_pack.ts @@ -33,11 +33,11 @@ export function useModPack(id: number) { } updateModPack(); - function updateHandler(newModPack: lib.ModPack) { - if (newModPack.id !== id) { - return; + function updateHandler(modPacks: lib.ModPack[]) { + const newModPack = modPacks.find(m => m.id === id); + if (newModPack) { + setModPack(newModPack); } - setModPack(newModPack); } control.modPackUpdate.subscribe(updateHandler); return () => { @@ -63,18 +63,20 @@ export function useModPackList() { useEffect(() => { updateModPackList(); - function updateHandler(newModPack: lib.ModPack) { + function updateHandler(newModPacks: lib.ModPack[]) { setModPackList(oldList => { let newList = oldList.concat(); - let index = newList.findIndex(u => u.id === newModPack.id); - if (!newModPack.isDeleted) { - if (index !== -1) { - newList[index] = newModPack; - } else { - newList.push(newModPack); + for (const newModPack of newModPacks) { + let index = newList.findIndex(u => u.id === newModPack.id); + if (!newModPack.isDeleted) { + if (index !== -1) { + newList[index] = newModPack; + } else { + newList.push(newModPack); + } + } else if (index !== -1) { + newList.splice(index, 1); } - } else if (index !== -1) { - newList.splice(index, 1); } return newList; }); diff --git a/packages/web_ui/src/model/mods.ts b/packages/web_ui/src/model/mods.ts index 0d131fae..6bbdfc16 100644 --- a/packages/web_ui/src/model/mods.ts +++ b/packages/web_ui/src/model/mods.ts @@ -21,18 +21,20 @@ export function useModList() { useEffect(() => { updateModList(); - function updateHandler(newMod: lib.ModInfo) { + function updateHandler(newMods: lib.ModInfo[]) { setModList(oldList => { let newList = oldList.concat(); - let index = newList.findIndex(mod => mod.name === newMod.name && mod.version === newMod.version); - if (!newMod.isDeleted) { - if (index !== -1) { - newList[index] = newMod; - } else { - newList.push(newMod); + for (const newMod of newMods) { + let index = newList.findIndex(mod => mod.name === newMod.name && mod.version === newMod.version); + if (!newMod.isDeleted) { + if (index !== -1) { + newList[index] = newMod; + } else { + newList.push(newMod); + } + } else if (index !== -1) { + newList.splice(index, 1); } - } else if (index !== -1) { - newList.splice(index, 1); } return newList; }); diff --git a/packages/web_ui/src/model/user.tsx b/packages/web_ui/src/model/user.tsx index 9e2d8dbc..9ad9e745 100644 --- a/packages/web_ui/src/model/user.tsx +++ b/packages/web_ui/src/model/user.tsx @@ -78,11 +78,11 @@ export function useUser(name: string): [RawUserState, () => void] { } updateUser(); - function updateHandler(newUser: lib.User) { - if (newUser.name !== name) { - return; + function updateHandler(newUsers: lib.User[]) { + const newUser = newUsers.find(u => u.name === name); + if (newUser) { + setUser({ ...newUser, present: true }); } - setUser({ ...newUser, present: true }); } control.userUpdate.subscribe(updateHandler); @@ -109,18 +109,20 @@ export function useUserList() { useEffect(() => { updateUserList(); - function updateHandler(newUser: lib.User) { + function updateHandler(newUsers: lib.User[]) { setUserList(oldList => { let newList = oldList.concat(); - let index = newList.findIndex(u => u.name === newUser.name); - if (!newUser.isDeleted) { - if (index !== -1) { - newList[index] = newUser; - } else { - newList.push(newUser); + for (const newUser of newUsers) { + let index = newList.findIndex(u => u.name === newUser.name); + if (!newUser.isDeleted) { + if (index !== -1) { + newList[index] = newUser; + } else { + newList.push(newUser); + } + } else if (index !== -1) { + newList.splice(index, 1); } - } else if (index !== -1) { - newList.splice(index, 1); } return newList; }); diff --git a/packages/web_ui/src/util/websocket.ts b/packages/web_ui/src/util/websocket.ts index 02b24fbd..1b50449f 100644 --- a/packages/web_ui/src/util/websocket.ts +++ b/packages/web_ui/src/util/websocket.ts @@ -60,28 +60,28 @@ export class Control extends lib.Link { logHandlers: Map = new Map(); /** Updates handled by the subscription service */ - hostUpdate = new lib.EventSubscriber( - lib.HostUpdateEvent, event => event.update, + hostUpdate = new lib.EventSubscriber( + lib.HostUpdatesEvent, event => event.updates, ); - instanceUpdate = new lib.EventSubscriber( - lib.InstanceDetailsUpdateEvent, event => event.details, + instanceUpdate = new lib.EventSubscriber( + lib.InstanceDetailsUpdatesEvent, event => event.updates, ); saveListUpdate = new lib.EventSubscriber( lib.InstanceSaveListUpdateEvent, ); - modPackUpdate = new lib.EventSubscriber( - lib.ModPackUpdateEvent, event => event.modPack, + modPackUpdate = new lib.EventSubscriber( + lib.ModPackUpdatesEvent, event => event.updates, ); - modUpdate = new lib.EventSubscriber( - lib.ModUpdateEvent, event => event.mod, + modUpdate = new lib.EventSubscriber( + lib.ModUpdatesEvent, event => event.updates, ); - userUpdate = new lib.EventSubscriber( - lib.UserUpdateEvent, event => event.user, + userUpdate = new lib.EventSubscriber( + lib.UserUpdatesEvent, event => event.updates, ); declare connector: ControlConnector; diff --git a/test/integration/index.js b/test/integration/index.js index 8fd2585e..d53768e5 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -36,56 +36,56 @@ class TestControl extends lib.Link { return; } this.send( - new lib.SubscriptionRequest(lib.HostUpdateEvent.name, true) + new lib.SubscriptionRequest(lib.HostUpdatesEvent.name, true) ).catch(err => logger.error(`Error setting host subscriptions:\n${err.stack}`)); this.send( - new lib.SubscriptionRequest(lib.InstanceDetailsUpdateEvent.name, true) + new lib.SubscriptionRequest(lib.InstanceDetailsUpdatesEvent.name, true) ).catch(err => logger.error(`Error setting instance subscriptions:\n${err.stack}`)); this.send( new lib.SubscriptionRequest(lib.InstanceSaveListUpdateEvent.name, true) ).catch(err => logger.error(`Error setting save list subscriptions:\n${err.stack}`)); this.send( - new lib.SubscriptionRequest(lib.ModUpdateEvent.name, true) + new lib.SubscriptionRequest(lib.ModUpdatesEvent.name, true) ).catch(err => logger.error(`Error setting mod subscriptions:\n${err.stack}`)); this.send( - new lib.SubscriptionRequest(lib.ModPackUpdateEvent.name, true) + new lib.SubscriptionRequest(lib.ModPackUpdatesEvent.name, true) ).catch(err => logger.error(`Error setting mod pack subscriptions:\n${err.stack}`)); this.send( - new lib.SubscriptionRequest(lib.UserUpdateEvent.name, true) + new lib.SubscriptionRequest(lib.UserUpdatesEvent.name, true) ).catch(err => logger.error(`Error setting user subscriptions:\n${err.stack}`)); }); this.handle(lib.AccountUpdateEvent); - this.handle(lib.HostUpdateEvent, this.handleHostUpdateEvent.bind(this)); - this.handle(lib.InstanceDetailsUpdateEvent, this.handleInstanceDetailsUpdateEvent.bind(this)); + this.handle(lib.HostUpdatesEvent, this.handleHostUpdatesEvent.bind(this)); + this.handle(lib.InstanceDetailsUpdatesEvent, this.handleInstanceDetailsUpdatesEvent.bind(this)); this.handle(lib.InstanceSaveListUpdateEvent, this.handleInstanceSaveListUpdateEvent.bind(this)); - this.handle(lib.ModUpdateEvent, this.handleModUpdateEvent.bind(this)); - this.handle(lib.ModPackUpdateEvent, this.handleModPackUpdateEvent.bind(this)); - this.handle(lib.UserUpdateEvent, this.handleUserUpdateEvent.bind(this)); + this.handle(lib.ModUpdatesEvent, this.handleModUpdatesEvent.bind(this)); + this.handle(lib.ModPackUpdatesEvent, this.handleModPackUpdatesEvent.bind(this)); + this.handle(lib.UserUpdatesEvent, this.handleUserUpdatesEvent.bind(this)); } - async handleHostUpdateEvent(event) { - this.hostUpdates.push(event.update); + async handleHostUpdatesEvent(event) { + this.hostUpdates.push(...event.updates); } - async handleInstanceDetailsUpdateEvent(event) { - this.instanceUpdates.push(event.details); + async handleInstanceDetailsUpdatesEvent(event) { + this.instanceUpdates.push(...event.updates); } async handleInstanceSaveListUpdateEvent(event) { this.saveListUpdates.push(event); } - async handleModUpdateEvent(event) { - this.modUpdates.push(event.mod); + async handleModUpdatesEvent(event) { + this.modUpdates.push(...event.updates); } - async handleModPackUpdateEvent(event) { - this.modPackUpdates.push(event.modPack); + async handleModPackUpdatesEvent(event) { + this.modPackUpdates.push(...event.updates); } - async handleUserUpdateEvent(event) { - this.userUpdates.push(event.user); + async handleUserUpdatesEvent(event) { + this.userUpdates.push(...event.updates); } }