Refactor to use subsribers

This commit is contained in:
Cooldude2606 committed 2023-09-05 01:46:45 +01:00
1 parent 17943c48c3
commit da25196830
16 files changed
+120 -707

No files matched your search

@@ -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);
+48 -42
View File
@@ -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>(lib.HostUpdateEvent, undefined, event => event.update.id);
this.subscriptions.handle<lib.InstanceDetailsUpdateEvent>(lib.InstanceDetailsUpdateEvent, undefined, event => event.details.id);
this.subscriptions.handle<lib.InstanceSaveListUpdateEvent>(lib.InstanceSaveListUpdateEvent, undefined, event => event.instanceId);
this.subscriptions.handle<lib.ModPackUpdateEvent>(lib.ModPackUpdateEvent, undefined, event => event.modPack.id!);
this.subscriptions.handle<lib.ModUpdateEvent>(lib.ModUpdateEvent);
this.subscriptions.handle<lib.UserUpdateEvent>(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,
)
));
}
/**
+1
View File
@@ -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";
+1 -23
View File
@@ -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<typeof this.jsonSchema>) {
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,
+2 -44
View File
@@ -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<typeof this.jsonSchema>) {
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<typeof this.jsonSchema>) {
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,
+2 -44
View File
@@ -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<typeof this.jsonSchema>) {
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<typeof this.jsonSchema>) {
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,
+1 -22
View File
@@ -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<typeof this.jsonSchema>) {
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,
+3 -6
View File
@@ -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<unknown, unknown> | EventClass<unknown>)
controller.DebugDumpWsRequest,
controller.DebugWsMessageEvent,
subscriptions.SubscriptionRequest,
host.HostListRequest,
host.HostSetSubscriptionsRequest,
host.HostUpdateEvent,
host.HostMetricsRequest,
host.ControllerConnectionEvent,
@@ -37,7 +39,6 @@ export const dataClasses: (RequestClass<unknown, unknown> | EventClass<unknown>)
instance.InstanceDetailsGetRequest,
instance.InstanceDetailsListRequest,
instance.InstanceDetailsSetSubscriptionsRequest,
instance.InstanceDetailsUpdateEvent,
instance.InstanceCreateRequest,
instance.InstanceConfigGetRequest,
@@ -47,7 +48,6 @@ export const dataClasses: (RequestClass<unknown, unknown> | EventClass<unknown>)
instance.InstanceMetricsRequest,
instance.InstanceStartRequest,
instance.InstanceListSavesRequest,
instance.InstanceSetSaveListSubscriptionsRequest,
instance.InstanceSaveListUpdateEvent,
instance.InstanceCreateSaveRequest,
instance.InstanceRenameSaveRequest,
@@ -82,11 +82,9 @@ export const dataClasses: (RequestClass<unknown, unknown> | EventClass<unknown>)
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<unknown, unknown> | EventClass<unknown>)
user.RoleDeleteRequest,
user.UserGetRequest,
user.UserListRequest,
user.UserSetSubscriptionsRequest,
user.UserCreateRequest,
user.UserRevokeTokenRequest,
user.UserUpdateRolesRequest,
+25 -16
View File
@@ -3,9 +3,9 @@ import { Link, Event, EventClass, RequestHandler, WebSocketClientConnector, WebS
import { Address, MessageRequest } from "./data";
import { User } from "./users";
export type SubscriptionChannelCategoriser<T> = (event: Event<T>) => string | number;
export type SubscriptionChannelCategoriser<T> = (event: T) => string | number;
export type SubscriptionRequestHandler<T> = RequestHandler<SubscriptionRequest, Event<T> | null>;
export type EventSubscriberCallback<T> = (event: Event<T>) => Promise<void>;
export type EventSubscriberCallback<T> = (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<T> {
_callbacks = new Array<EventSubscriberCallback<T>>()
_channelCallbacks = new Map<string | number, Array<EventSubscriberCallback<T>>>()
lastResponse: Event<T> | null = null;
export class EventSubscriber<T, V=T> {
_callbacks = new Array<EventSubscriberCallback<V>>()
_channelCallbacks = new Map<string | number, Array<EventSubscriberCallback<V>>>()
lastResponse: T | null = null; // Does not work with Event<T>
lastResponseTime = 0;
constructor(
private event: EventClass<T>,
private prehandler?: (event: T) => V,
private control?: Link
) {
const entry = Link._eventsByClass.get(this.event);
@@ -212,26 +214,33 @@ export class EventSubscriber<T> {
}
}
async _handle(response: Event<T>) {
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<T>) {
subscribe(handler: EventSubscriberCallback<V>) {
this._callbacks.push(handler);
this._updateSubscription();
}
subscribeToChannel(channel: string | number, handler: EventSubscriberCallback<T>) {
subscribeToChannel(channel: string | number, handler: EventSubscriberCallback<V>) {
if (!this._channelCallbacks.get(channel)) {
this._channelCallbacks.set(channel, []);
}
@@ -239,7 +248,7 @@ export class EventSubscriber<T> {
this._updateSubscription();
}
unsubscribe(handler: EventSubscriberCallback<T>) {
unsubscribe(handler: EventSubscriberCallback<V>) {
let index = this._callbacks.lastIndexOf(handler);
if (index === -1) {
throw new Error("handler is not registered");
@@ -249,7 +258,7 @@ export class EventSubscriber<T> {
this._updateSubscription();
}
unsubscribeFromChannel(channel: string | number, handler: EventSubscriberCallback<T>) {
unsubscribeFromChannel(channel: string | number, handler: EventSubscriberCallback<V>) {
const channelCallbacks = this._channelCallbacks.get(channel);
if (!channelCallbacks) {
throw new Error("handler is not registered");
@@ -280,7 +289,7 @@ export class EventSubscriber<T> {
));
if (response.eventReplay) {
this._handle(response.eventReplay);
this._handle(response.eventReplay as T);
}
}
}
+4 -4
View File
@@ -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);
};
}, []);
+4 -4
View File
@@ -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);
};
}, []);
+4 -4
View File
@@ -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);
};
}, []);
+2 -2
View File
@@ -38,9 +38,9 @@ export function useModList() {
});
}
control.onModUpdate(null, updateHandler);
control.modUpdate.subscribe(updateHandler);
return () => {
control.offModUpdate(null, updateHandler);
control.modUpdate.unsubscribe(updateHandler);
};
}, []);
+2 -2
View File
@@ -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]);
+4 -4
View File
@@ -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);
};
}, []);
+17 -362
View File
@@ -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<number|null, hostHandler[]> = new Map();
instanceUpdateHandlers: Map<number|null, instanceHandler[]> = new Map();
saveListUpdateHandlers: Map<number|null, saveListHandler[]> = new Map();
modPackUpdateHandlers: Map<number|null, modPackHandler[]> = new Map();
modUpdateHandlers: Map<string|null, modInfoHandler[]> = new Map();
userUpdateHandlers: Map<string|null, userHandler[]> = new Map();
logHandlers: Map<lib.LogFilter, logHandler[]> = new Map();
/** Updates handled by the subscription service */
hostUpdate = new lib.EventSubscriber<lib.HostUpdateEvent, lib.HostDetails>(lib.HostUpdateEvent, event => event.update);
instanceUpdate = new lib.EventSubscriber<lib.InstanceDetailsUpdateEvent, lib.InstanceDetails>(lib.InstanceDetailsUpdateEvent, event => event.details);
saveListUpdate = new lib.EventSubscriber<lib.InstanceSaveListUpdateEvent>(lib.InstanceSaveListUpdateEvent);
modPackUpdate = new lib.EventSubscriber<lib.ModPackUpdateEvent, lib.ModPack>(lib.InstanceSaveListUpdateEvent, event => event.modPack);
modUpdate = new lib.EventSubscriber<lib.ModUpdateEvent, lib.ModInfo>(lib.InstanceSaveListUpdateEvent, event => event.mod);
userUpdate = new lib.EventSubscriber<lib.UserUpdateEvent, lib.RawUser>(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;