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); } }