about summary refs log tree commit diff
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
parentc3b05a72b7185112ece6e42c99e9a828c8298f04 (diff)
downloadrook-251cba56d9103c9ac38f9b98b69ee953ce23111e.tar.zst
rook-251cba56d9103c9ac38f9b98b69ee953ce23111e.zip
Started better typing for message handler
-rw-r--r--assets/src/network/channel/connection.ts38
-rw-r--r--assets/src/network/channel/messages/event_handler.ts153
-rw-r--r--assets/src/network/channel/messages/handler.ts112
-rw-r--r--assets/src/network/channel/messages/messages.ts21
-rw-r--r--assets/src/network/channel/share_connection.ts12
-rw-r--r--assets/src/network/transfer/request_transfer.ts6
-rw-r--r--assets/src/network/transfer/share_transfer.ts11
-rw-r--r--assets/src/network/transfer/transfer.ts4
-rw-r--r--lib/rook/request/request.ex2
-rw-r--r--lib/rook/share/share.ex2
10 files changed, 216 insertions, 145 deletions
diff --git a/assets/src/network/channel/connection.ts b/assets/src/network/channel/connection.ts
index 59e07e8..7e9b13c 100644
--- a/assets/src/network/channel/connection.ts
+++ b/assets/src/network/channel/connection.ts
@@ -2,11 +2,12 @@ import { Channel, Push, Socket } from "phoenix";
 import { get, Readable, writable, Writable } from "svelte/store";
 import {
     Handler,
-    Handlers,
-    registerTokenHandler,
-    UnregisterHandler,
-} from "./messages/handler";
-import type { AnyMessage } from "./messages/messages";
+    EventHandler,
+    registerHandlerForSpecificToken,
+    Unregister,
+    registerHandler,
+} from "./messages/event_handler";
+import type { AnyMessage, TokenizedMessage } from "./messages/messages";
 import { connectSocket, fetchToken } from "./socket";
 
 export enum ConnectionState {
@@ -22,7 +23,7 @@ export type Connection = {
     channel: Channel | null;
     token: string | null;
     state: Writable<ConnectionState>;
-    handlers: Handlers;
+    handlers: EventHandler;
 };
 
 const connection: Connection = {
@@ -50,12 +51,12 @@ export function send(event: string, data: any): Push {
     return connection.channel.push(event, data);
 }
 
-export function onWithToken<Message extends AnyMessage>(
-    event: string,
+export function onWithToken<M extends TokenizedMessage>(
+    event: M["event_name"],
     token: string | null,
-    handler: Handler<Message>
-): UnregisterHandler {
-    return registerTokenHandler(
+    handler: Handler<M>
+): Unregister {
+    return registerHandlerForSpecificToken(
         connection.handlers,
         connection.channel,
         event,
@@ -64,11 +65,16 @@ export function onWithToken<Message extends AnyMessage>(
     );
 }
 
-export function on<Message extends AnyMessage>(
-    event: string,
-    handler: Handler<Message>
-): UnregisterHandler {
-    return onWithToken(event, null, handler);
+export function on<M extends AnyMessage>(
+    event: M["event_name"],
+    handler: Handler<M>
+): Unregister {
+    return registerHandler(
+        connection.handlers,
+        connection.channel,
+        event,
+        handler
+    );
 }
 
 export function getOwnToken(): string {
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);
+    }
+}
diff --git a/assets/src/network/channel/messages/handler.ts b/assets/src/network/channel/messages/handler.ts
deleted file mode 100644
index cc8f005..0000000
--- a/assets/src/network/channel/messages/handler.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-import type { Channel } from "phoenix";
-import type { AnyMessage } from "./messages";
-
-export type Handlers = {
-    [event: string]: EventHandler<unknown>;
-};
-
-type EventHandler<Message extends AnyMessage> = {
-    tokenHandler: TokenHandler<Message>;
-    directHandlers: Handler<Message>[];
-};
-
-export type TokenHandler<Message extends AnyMessage> = {
-    [token: string]: Handler<Message>;
-};
-
-export type Handler<Message extends AnyMessage> = (
-    message?: Message,
-    unregister?: UnregisterHandler
-) => void;
-
-export type UnregisterHandler = () => void;
-
-export function registerTokenHandler<Message extends AnyMessage>(
-    handlers: Handlers,
-    channel: Channel,
-    event: string,
-    token: string | null,
-    handler: Handler<Message>
-): UnregisterHandler {
-    let eventHandler = handlers[event];
-
-    // If this event did not yet have any handlers registered we have to register it
-    if (eventHandler === undefined) {
-        eventHandler = {
-            tokenHandler: {},
-            directHandlers: [],
-        };
-
-        handlers[event] = eventHandler;
-
-        registerNewEvent<Message>(channel, eventHandler, event);
-    }
-
-    let unregister: UnregisterHandler;
-
-    if (token === null) {
-        const directHandlers = eventHandler.directHandlers;
-        directHandlers.push(handler);
-
-        unregister = makeDirectUnregister(directHandlers, handler);
-    } else {
-        const tokenHandler = eventHandler.tokenHandler;
-        tokenHandler[token] = handler;
-
-        unregister = makeTokenUnregister(tokenHandler, token);
-    }
-
-    return unregister;
-}
-
-function registerNewEvent<Message extends AnyMessage>(
-    channel: Channel,
-    eventHandler: EventHandler<Message>,
-    event: string
-) {
-    const callback = (data: Message) => {
-        handleEvent<Message>(eventHandler, data);
-    };
-
-    channel.on(event, callback);
-}
-
-function handleEvent<Message extends AnyMessage>(
-    eventHandler: EventHandler<Message>,
-    message: Message
-) {
-    if (message["token"] !== undefined) {
-        const token = message["token"];
-
-        const tokenHandler = eventHandler.tokenHandler;
-        const handler: Handler<Message> = tokenHandler[token];
-
-        if (handler !== undefined) {
-            handler(message, makeTokenUnregister(tokenHandler, token));
-        }
-    }
-
-    const directHandlers = eventHandler.directHandlers;
-    for (const handler of directHandlers) {
-        handler(message, makeDirectUnregister(directHandlers, handler));
-    }
-}
-
-function makeDirectUnregister<Message extends AnyMessage>(
-    directHandlers: Handler<Message>[],
-    handler: Handler<Message>
-): UnregisterHandler {
-    return () => {
-        const index = directHandlers.findIndex(h => h === handler);
-        directHandlers.splice(index, 1);
-    };
-}
-
-function makeTokenUnregister<Message extends AnyMessage>(
-    tokenHandler: TokenHandler<Message>,
-    token: string
-): UnregisterHandler {
-    return () => {
-        delete tokenHandler[token];
-    };
-}
diff --git a/assets/src/network/channel/messages/messages.ts b/assets/src/network/channel/messages/messages.ts
index 3f2253d..67f70ac 100644
--- a/assets/src/network/channel/messages/messages.ts
+++ b/assets/src/network/channel/messages/messages.ts
@@ -1,5 +1,12 @@
 export type AnyMessage = ShareMessage | RequestMessage;
 
+export type EventName = AnyMessage["event_name"];
+export type MessageForEvent<EN> = Extract<AnyMessage, { event_name: EN }>;
+
+export type TokenizedMessage = {
+    token: string;
+} & AnyMessage;
+
 // Messages for the sharer
 
 export type ShareMessage =
@@ -9,20 +16,24 @@ export type ShareMessage =
     | RequestIceCandidateMessage;
 
 export type NewRequestMessage = {
+    event_name: "new_request";
     token: string;
 };
 
 export type RequestCancelledMessage = {
+    event_name: "request_cancelled";
     token: string;
 };
 
 export type ShareAcceptedMessage = {
+    event_name: "share_accepted";
     token: string;
     sdp: string;
     type: RTCSdpType;
 };
 
 export type RequestIceCandidateMessage = {
+    event_name: "request_ice_candidate";
     token: string;
     candidate: RTCIceCandidateInit;
 };
@@ -35,15 +46,21 @@ export type RequestMessage =
     | ShareCancelledMessage
     | ShareIceCandidateMessage;
 
-export type RequestAcknowledgedMessage = {};
+export type RequestAcknowledgedMessage = {
+    event_name: "request_acknowledged";
+};
 
 export type RequestAcceptedMessage = {
+    event_name: "request_accepted";
     sdp: string;
     type: RTCSdpType;
 };
 
-export type ShareCancelledMessage = {};
+export type ShareCancelledMessage = {
+    event_name: "share_cancelled";
+};
 
 export type ShareIceCandidateMessage = {
+    event_name: "share_ice_candidate";
     candidate: RTCIceCandidateInit;
 };
diff --git a/assets/src/network/channel/share_connection.ts b/assets/src/network/channel/share_connection.ts
index 415cb7f..1bb4f14 100644
--- a/assets/src/network/channel/share_connection.ts
+++ b/assets/src/network/channel/share_connection.ts
@@ -7,7 +7,7 @@ import {
     start,
     updateState,
 } from "./connection";
-import type { UnregisterHandler } from "./messages/handler";
+import type { Unregister } from "./messages/event_handler";
 import type {
     NewRequestMessage,
     RequestCancelledMessage,
@@ -38,12 +38,18 @@ function onNewRequest(message: NewRequestMessage) {
     const request = newIncomingRequest(token);
     requests.addRequest(request);
 
-    onWithToken("request_cancelled", token, onRequestCancelled);
+    const unregister = onWithToken(
+        "request_cancelled",
+        token,
+        (message: RequestCancelledMessage) => {
+            onRequestCancelled(message, unregister);
+        }
+    );
 }
 
 function onRequestCancelled(
     message: RequestCancelledMessage,
-    unregister: UnregisterHandler
+    unregister: Unregister
 ) {
     const token = message.token;
     requests.removeRequest(token);
diff --git a/assets/src/network/transfer/request_transfer.ts b/assets/src/network/transfer/request_transfer.ts
index 317f5e5..e69a9a8 100644
--- a/assets/src/network/transfer/request_transfer.ts
+++ b/assets/src/network/transfer/request_transfer.ts
@@ -1,6 +1,6 @@
 import data from "../../stores/data";
 import { on, send } from "../channel/connection";
-import type { RequestIceCandidateMessage } from "../channel/messages/messages";
+import type { ShareIceCandidateMessage } from "../channel/messages/messages";
 import {
     createTransfer,
     onIncomingIceCandidate,
@@ -27,8 +27,8 @@ export async function createAnswerTransfer(
     };
 
     const unregisterIce = on(
-        "ice_candidate",
-        (message: RequestIceCandidateMessage) =>
+        "share_ice_candidate",
+        (message: ShareIceCandidateMessage) =>
             onIncomingIceCandidate(transfer, message)
     );
 
diff --git a/assets/src/network/transfer/share_transfer.ts b/assets/src/network/transfer/share_transfer.ts
index fc6df9c..16ad68a 100644
--- a/assets/src/network/transfer/share_transfer.ts
+++ b/assets/src/network/transfer/share_transfer.ts
@@ -1,11 +1,12 @@
 import { get } from "svelte/store";
 import dataStore from "../../stores/data";
 import { onWithToken, send } from "../channel/connection";
-import type { UnregisterHandler } from "../channel/messages/handler";
+import type { Unregister } from "../channel/messages/event_handler";
 import type {
     RequestIceCandidateMessage,
     ShareAcceptedMessage,
 } from "../channel/messages/messages";
+import { connectSocket } from "../channel/socket";
 import {
     createTransfer,
     onIncomingIceCandidate,
@@ -34,10 +35,10 @@ export async function createOfferTransfer(
         type: offer.type,
     });
 
-    onWithToken(
+    const unregister: Unregister = onWithToken(
         "share_accepted",
         request_token,
-        (message: ShareAcceptedMessage, unregister) =>
+        (message: ShareAcceptedMessage) =>
             onShareAccepted(transfer, message, unregister)
     );
 
@@ -47,7 +48,7 @@ export async function createOfferTransfer(
 function onShareAccepted(
     transfer: Transfer,
     message: ShareAcceptedMessage,
-    unregister: UnregisterHandler
+    unregister: Unregister
 ) {
     const token = message.token;
 
@@ -55,7 +56,7 @@ function onShareAccepted(
     transfer.pc.setRemoteDescription(answerDescription);
 
     const unregisterIce = onWithToken(
-        "ice_candidate",
+        "request_ice_candidate",
         token,
         (message: RequestIceCandidateMessage) =>
             onIncomingIceCandidate(transfer, message)
diff --git a/assets/src/network/transfer/transfer.ts b/assets/src/network/transfer/transfer.ts
index 7641c17..a0df5bb 100644
--- a/assets/src/network/transfer/transfer.ts
+++ b/assets/src/network/transfer/transfer.ts
@@ -1,7 +1,7 @@
 import { Writable, writable } from "svelte/store";
 import type { IncomingRequest } from "../../models/incoming_request";
 import type { OwnRequest } from "../../models/own_request";
-import type { UnregisterHandler } from "../channel/messages/handler";
+import type { Unregister } from "../channel/messages/event_handler";
 import type {
     RequestIceCandidateMessage,
     ShareIceCandidateMessage,
@@ -83,7 +83,7 @@ export function onIncomingIceCandidate(
 
 export function unregisterIceOnComplete(
     transfer: Transfer,
-    unregister: UnregisterHandler
+    unregister: Unregister
 ) {
     transfer.pc.onicegatheringstatechange = event => {
         const connection = event.target as any;
diff --git a/lib/rook/request/request.ex b/lib/rook/request/request.ex
index d0ee7c6..dabfd68 100644
--- a/lib/rook/request/request.ex
+++ b/lib/rook/request/request.ex
@@ -59,7 +59,7 @@ defmodule Rook.Request do
   end
 
   def handle_cast({:share_ice_candidate, candidate}, state) do
-    notify(state.token, "ice_candidate", %{candidate: candidate})
+    notify(state.token, "share_ice_candidate", %{candidate: candidate})
     {:noreply, state}
   end
 
diff --git a/lib/rook/share/share.ex b/lib/rook/share/share.ex
index fe626c2..e1760c7 100644
--- a/lib/rook/share/share.ex
+++ b/lib/rook/share/share.ex
@@ -61,7 +61,7 @@ defmodule Rook.Share do
   end
 
   def handle_cast({:request_ice_candidate, request_token, candidate}, state) do
-    notify(state.token, "ice_candidate", %{token: request_token, candidate: candidate})
+    notify(state.token, "request_ice_candidate", %{token: request_token, candidate: candidate})
     {:noreply, state}
   end