Require control in EventSubscriber.constructor

Reduce complexity in the implementation of EventSubscriber by requiring
a control link be passed to its constructor.  This is possible after the
change in 8c724e3 made the web interface control instance available to
plugins at init time.
This commit is contained in:
Hornwitser committed 2023-12-11 23:14:37 +01:00
1 parent 6cab1fa842
commit ddba58ca99
3 files changed
+30 -72

No files matched your search

+20 -31
View File
@@ -1,5 +1,6 @@
import { Type, Static } from "@sinclair/typebox";
import { Link, Event, EventClass, RequestHandler, WebSocketClientConnector, WebSocketBaseConnector } from "./link";
import { logger } from "./logging";
import { Address, MessageRequest, IControllerUser } from "./data";
export type SubscriptionRequestHandler<T> = RequestHandler<SubscriptionRequest, Event<T> | null>;
@@ -157,20 +158,16 @@ export class EventSubscriber<T extends Event<T>, V=T> {
_callbacks = new Array<EventSubscriberCallback<V>>();
lastResponse: Event<T> | null = null;
lastResponseTime = -1;
control?: Link;
constructor(
private event: EventClass<T>,
private Event: EventClass<T>,
public control: Link,
private prehandler?: (e: T) => V,
control?: Link
) {
const entry = Link._eventsByClass.get(this.event);
if (!entry) {
throw new Error(`Unregistered Event class ${this.event.name}`);
}
if (control) {
this.connectControl(control);
}
control.handle(Event, this._handle.bind(this));
control.connector.on("connect", () => {
this._updateSubscription();
});
}
/**
@@ -187,20 +184,6 @@ export class EventSubscriber<T extends Event<T>, V=T> {
}
}
/**
* If a control link was not connected at creation, it can be connected
* here (normally during onControllerConnectionEvent)
* @param control - Control link to associate with.
*/
async connectControl(control: Link) {
if (this.control === control) {
return;
}
this.control = control;
this.control.handle(this.event, this._handle.bind(this));
await this._updateSubscription();
}
/**
* Subscribe to receive all event notifications
* @param handler -
@@ -235,15 +218,21 @@ export class EventSubscriber<T extends Event<T>, V=T> {
* Update the subscription with the controller based on current handler counts
*/
async _updateSubscription() {
if (!this.control || !(this.control.connector as WebSocketClientConnector).connected) {
if (!(this.control.connector as WebSocketClientConnector).connected) {
return;
}
const entry = Link._eventsByClass.get(this.event)!;
const entry = Link._eventsByClass.get(this.Event)!;
await this.control.send(new SubscriptionRequest(
entry.name,
this._callbacks.length > 0,
this.lastResponseTime
));
try {
await this.control.send(new SubscriptionRequest(
entry.name,
this._callbacks.length > 0,
this.lastResponseTime
));
} catch (err: any) {
logger.error(
`Unexpected error updating ${entry.name} subscription:\n${err.stack}`
);
}
}
}
+6 -24
View File
@@ -61,27 +61,27 @@ export class Control extends lib.Link {
/** Updates handled by the subscription service */
hostUpdate = new lib.EventSubscriber<lib.HostUpdatesEvent, lib.HostDetails[]>(
lib.HostUpdatesEvent, event => event.updates,
lib.HostUpdatesEvent, this, event => event.updates,
);
instanceUpdate = new lib.EventSubscriber<lib.InstanceDetailsUpdatesEvent, lib.InstanceDetails[]>(
lib.InstanceDetailsUpdatesEvent, event => event.updates,
lib.InstanceDetailsUpdatesEvent, this, event => event.updates,
);
saveListUpdate = new lib.EventSubscriber<lib.InstanceSaveDetailsUpdatesEvent, lib.SaveDetails[]>(
lib.InstanceSaveDetailsUpdatesEvent, event => event.updates,
lib.InstanceSaveDetailsUpdatesEvent, this, event => event.updates,
);
modPackUpdate = new lib.EventSubscriber<lib.ModPackUpdatesEvent, lib.ModPack[]>(
lib.ModPackUpdatesEvent, event => event.updates,
lib.ModPackUpdatesEvent, this, event => event.updates,
);
modUpdate = new lib.EventSubscriber<lib.ModUpdatesEvent, lib.ModInfo[]>(
lib.ModUpdatesEvent, event => event.updates,
lib.ModUpdatesEvent, this, event => event.updates,
);
userUpdate = new lib.EventSubscriber<lib.UserUpdatesEvent, lib.User[]>(
lib.UserUpdatesEvent, event => event.updates,
lib.UserUpdatesEvent, this, event => event.updates,
);
declare connector: ControlConnector;
@@ -95,24 +95,6 @@ export class Control extends lib.Link {
this.accountName = data.account.name;
this.accountRoles = data.account.roles;
this.emitAccountUpdate();
this.hostUpdate.connectControl(this).catch(err => logger.error(
`Unexpected error updating host subscriptions:\n${err.stack}`
));
this.instanceUpdate.connectControl(this).catch(err => logger.error(
`Unexpected error updating instance subscriptions:\n${err.stack}`
));
this.saveListUpdate.connectControl(this).catch(err => logger.error(
`Unexpected error updating save list subscriptions:\n${err.stack}`
));
this.modPackUpdate.connectControl(this).catch(err => logger.error(
`Unexpected error updating mod pack subscriptions:\n${err.stack}`
));
this.modUpdate.connectControl(this).catch(err => logger.error(
`Unexpected error updating mod subscriptions:\n${err.stack}`
));
this.userUpdate.connectControl(this).catch(err => logger.error(
`Unexpected error updating user subscriptions:\n${err.stack}`
));
this.updateLogSubscriptions().catch(err => logger.error(
`Unexpected error updating log subscriptions:\n${err.stack}`
));
+4 -17
View File
@@ -303,7 +303,7 @@ describe("lib/subscriptions", function() {
addr({ controlId: 0 }),
addr("controller"),
));
registeredEvent = new lib.EventSubscriber(RegisteredEvent, undefined, mockControl);
registeredEvent = new lib.EventSubscriber(RegisteredEvent, mockControl);
});
function assertLastRequest(request) {
@@ -317,36 +317,23 @@ describe("lib/subscriptions", function() {
describe("constructor()", function() {
it("should not accept unregistered events", function() {
assert.throws(
() => new lib.EventSubscriber(UnregisteredEvent),
() => new lib.EventSubscriber(UnregisteredEvent, mockControl),
new Error(`Unregistered Event class ${UnregisteredEvent.name}`)
);
});
it("should handle the provided event, if a control link is provided", function() {
it("should handle the provided event", function() {
assert.equal(mockControl._eventHandlers.has(RegisteredEvent), true);
});
it("should call and use the return of a pre-handler, if provided", function() {
let calledWith = null;
function prehandler() { return "PreHandlerReturn"; }
const eventSubscriber = new lib.EventSubscriber(StringPermissionEvent, prehandler, mockControl);
const eventSubscriber = new lib.EventSubscriber(StringPermissionEvent, mockControl, prehandler);
eventSubscriber.subscribe(function(value) { calledWith = value; });
eventSubscriber._handle(new StringPermissionEvent());
assert.equal(calledWith, "PreHandlerReturn");
});
});
describe("connectControl()", function() {
it("should handle the provided event", function() {
const control = new MockControl(new MockConnector(addr({ controlId: 0 }), addr("controller")));
registeredEvent.connectControl(control);
assert.equal(control._eventHandlers.has(RegisteredEvent), true);
});
it("should do nothing if the control is already connected", function() {
assert.equal(mockControl._eventHandlers.has(RegisteredEvent), true);
registeredEvent.connectControl(mockControl);
assert.equal(mockControl._eventHandlers.has(RegisteredEvent), true);
});
});
describe("subscribe()", function() {
it("should allow subscriptions to an event", function() {
let calledWith = null;