about summary refs log tree commit diff
path: root/assets/src/network/channel/messages/event_handler.ts
diff options
context:
space:
mode:
authorMelonai <einebeere@gmail.com>2021-07-14 19:36:29 +0200
committerMelonai <einebeere@gmail.com>2021-07-14 19:36:29 +0200
commit251cba56d9103c9ac38f9b98b69ee953ce23111e (patch)
treeebe7f48b99fd4d754084ebe416e6bbbc9f71092d /assets/src/network/channel/messages/event_handler.ts
parentc3b05a72b7185112ece6e42c99e9a828c8298f04 (diff)
downloadrook-251cba56d9103c9ac38f9b98b69ee953ce23111e.tar.zst
rook-251cba56d9103c9ac38f9b98b69ee953ce23111e.zip
Started better typing for message handler
Diffstat (limited to 'assets/src/network/channel/messages/event_handler.ts')
-rw-r--r--assets/src/network/channel/messages/event_handler.ts153
1 files changed, 153 insertions, 0 deletions
diff --git a/assets/src/network/channel/messages/event_handler.ts b/assets/src/network/channel/messages/event_handler.ts
new file mode 100644
index 0000000..749f289
--- /dev/null
+++ b/assets/src/network/channel/messages/event_handler.ts
@@ -0,0 +1,153 @@
+import type { Channel } from "phoenix";
+import type {
+    AnyMessage,
+    EventName,
+    MessageForEvent,
+    TokenizedMessage,
+} from "./messages";
+
+// The single handler for all events, which is used to dispatch to the correct
+// handler for each event and token.
+// Every event can only have either one single handler, or multiple handlers for different tokens.
+export type EventHandler = {
+    [EN in EventName]?: MessageHandler<MessageForEvent<EN>>;
+};
+
+// A handler for a specific event and message.
+// A message handler can either be a single handler or can have multiple handlers for different tokens
+type MessageHandler<M extends AnyMessage> =
+    // A single handler
+    | { type: "single"; handler: Handler<M> }
+    // A group of handlers for different tokens
+    // Can only be used for messages which have a "token" field.
+    | (M extends TokenizedMessage
+          ? {
+                type: "token";
+                handler: TokenHandler<M>;
+            }
+          : never);
+
+// A map of token to handler for a specific event.
+export type TokenHandler<M extends TokenizedMessage> = Map<string, Handler<M>>;
+
+// A function which handles a message for a specific event.
+export type Handler<M extends AnyMessage> = (message?: M) => void;
+
+// A function that unregisters a single event handler.
+export type Unregister = () => void;
+
+// Adds a single handler for a specific event.
+export function registerHandler<M extends AnyMessage>(
+    eventHandler: EventHandler,
+    channel: Channel,
+    event: M["event_name"],
+    handler: Handler<M>
+) {
+    const messageHandler: MessageHandler<M> = {
+        type: "single",
+        handler,
+    };
+
+    let unregisterChannelEvent: Unregister | null = null;
+    if (typeof eventHandler[event] === "undefined") {
+        // Register a new event handler, since this is the first handler for this event
+        unregisterChannelEvent = registerNewEvent<M>(
+            channel,
+            messageHandler,
+            event
+        );
+    } else {
+        throw new Error("Event already has a handler attached to it.");
+    }
+
+    // TODO: Check if we there is a possibility for TS to accept this.
+    // Technically this should work, because TS is afraid of the generic type parameter
+    // not matching the message type of the handler. But it should match, since
+    // the handler should accept only the exact message type from which the event_name was derived.
+    // @ts-ignore
+    eventHandler[event] = messageHandler;
+
+    // This could cause problems if we would allow to redefine the handler,
+    // as that would cause the Unsubscribe function to no longer apply to this specific handler,
+    // but as we throw an error on redefinition, this should be fine.
+    return () => {
+        delete eventHandler[event];
+
+        // If we registered a new event on the channel, we need to unregister it
+        if (unregisterChannelEvent !== null) {
+            unregisterChannelEvent();
+        }
+    };
+}
+
+export function registerHandlerForSpecificToken<M extends TokenizedMessage>(
+    eventHandler: EventHandler,
+    channel: Channel,
+    event: M["event_name"],
+    token: string,
+    handler: Handler<M>
+): Unregister {
+    if (typeof eventHandler[event] === "undefined") {
+        // TODO: Same as above, this should probably be valid.
+        // @ts-ignore
+        eventHandler[event] = {
+            type: "token",
+            handler: new Map<string, Handler<M>>(),
+        };
+        // @ts-ignore
+        registerNewEvent<M>(channel, eventHandler[event], event);
+    } else {
+        if (eventHandler[event].type === "single") {
+            throw new Error("Event already has a handler attached to it.");
+        }
+    }
+
+    // @ts-ignore This shoudl be valid, as we derive the event name from the message type.
+    const tokenHandler: TokenHandler<M> = eventHandler[event].handler;
+    tokenHandler.set(token, handler);
+
+    return () => {
+        // Unregister the handler for this specific token
+        tokenHandler.delete(token);
+        // If there are no more handlers for this event, we can unregister the event
+        if (tokenHandler.size === 0) {
+            // We should technically use the ref here, but we don't yet have a way to get it, as the event
+            // could have been registered outside of this function call.
+            channel.off(event);
+        }
+    };
+}
+
+// Adds a callback for a new event
+function registerNewEvent<M extends AnyMessage>(
+    channel: Channel,
+    messageHandler: MessageHandler<M>,
+    event: M["event_name"]
+): Unregister {
+    const callback = (data: M) => {
+        onEvent<M>(messageHandler, data);
+    };
+
+    const ref = channel.on(event, callback);
+
+    return () => channel.off(event, ref);
+}
+
+function onEvent<M extends AnyMessage>(
+    messageHandler: MessageHandler<M>,
+    message: M
+) {
+    if (messageHandler.type === "token") {
+        const token: string = message["token"];
+
+        const handler = messageHandler.handler.get(token);
+
+        if (typeof handler !== "undefined") {
+            (handler as Handler<M>)(message);
+        } else {
+            console.warn(`Received message for unknown token: ${token}`);
+        }
+    } else {
+        messageHandler.handler(message);
+    }
+}