Add websocket logic server

- Websocket callback binding
- Websocket send message to specific connected socket

GitLab: #53

Change-Id: Ib52db5d99b0bc3ad4d1a775a62fecb9ea3b4d132
diff --git a/common/src/enums/websocket-message-type.ts b/common/src/enums/websocket-message-type.ts
new file mode 100644
index 0000000..9f95d60
--- /dev/null
+++ b/common/src/enums/websocket-message-type.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+export enum WebSocketMessageType {
+  WebRTCOffer = 'webrtc-offer',
+  WebRCTAnswer = 'webrtc-answer',
+}
diff --git a/common/src/index.ts b/common/src/index.ts
index 41e19b3..8dbab24 100644
--- a/common/src/index.ts
+++ b/common/src/index.ts
@@ -20,4 +20,6 @@
 export * from './Contact.js';
 export * from './Conversation.js';
 export * from './enums/http-status-code.js';
+export * from './enums/websocket-message-type.js';
+export * from './interfaces/websocket-message.js';
 export * from './util.js';
diff --git a/common/src/interfaces/websocket-message.ts b/common/src/interfaces/websocket-message.ts
new file mode 100644
index 0000000..f42b67b
--- /dev/null
+++ b/common/src/interfaces/websocket-message.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import { WebSocketMessageType } from '../enums/websocket-message-type';
+
+export interface WebSocketMessage {
+  type: WebSocketMessageType;
+  data: any;
+}
diff --git a/server/src/jamid/jamid.ts b/server/src/jamid/jamid.ts
index 27ff0e3..ee91ad4 100644
--- a/server/src/jamid/jamid.ts
+++ b/server/src/jamid/jamid.ts
@@ -138,6 +138,8 @@
     // 2. You cannot specify multiple handlers for the same event
     // 3. You cannot specify a default handler
     this.jamiSwig.init(handlers);
+
+    // TODO: Bind websocket callbacks for webrtc action on Incoming account message
   }
 
   stop() {
@@ -189,7 +191,7 @@
 
   sendAccountTextMessage(accountId: string, contactId: string, type: string, message: string) {
     const messageStringMap: StringMap = new this.jamiSwig.StringMap();
-    messageStringMap.set(type, message);
+    messageStringMap.set(type, JSON.stringify(message));
     this.jamiSwig.sendAccountTextMessage(accountId, contactId, messageStringMap);
   }
 
@@ -313,7 +315,7 @@
 
     this.events.onMessageReceived.subscribe((signal) => {
       log.debug('Received MessageReceived:', JSON.stringify(signal));
-      // TODO: Send message to client using WebSocket
+      // TODO: Send message to client using WS service
     });
   }
 }
diff --git a/server/src/ws.ts b/server/src/ws.ts
index eadbab0..0081e59 100644
--- a/server/src/ws.ts
+++ b/server/src/ws.ts
@@ -18,6 +18,7 @@
 import { IncomingMessage } from 'node:http';
 import { Duplex } from 'node:stream';
 
+import { WebSocketMessage, WebSocketMessageType } from 'jami-web-common';
 import { jwtVerify } from 'jose';
 import log from 'loglevel';
 import { Service } from 'typedi';
@@ -28,16 +29,34 @@
 
 @Service()
 export class Ws {
-  constructor(private readonly vault: Vault) {}
+  private sockets: Map<string, WebSocket[]>;
+  private callbacks: Map<WebSocketMessageType, ((message: WebSocketMessage) => void)[]>;
+
+  constructor(private readonly vault: Vault) {
+    this.sockets = new Map();
+    this.callbacks = new Map();
+  }
 
   async build() {
     const wss = new WebSocketServer({ noServer: true });
+
     wss.on('connection', (ws: WebSocket, _req: IncomingMessage, accountId: string) => {
       log.info('New connection', accountId);
-      // TODO: Add the account ID here to a map of accountId -> WebSocket connections
+      const accountSockets = this.sockets.get(accountId);
+      if (accountSockets) {
+        accountSockets.push(ws);
+      } else {
+        this.sockets.set(accountId, [ws]);
+      }
 
-      ws.on('message', (_data) => {
-        ws.send(JSON.stringify({ accountId }));
+      ws.on('message', (messageString: string) => {
+        const message: WebSocketMessage = JSON.parse(messageString);
+        const callbacks = this.callbacks.get(message.type);
+        if (callbacks) {
+          for (const callback of callbacks) {
+            callback(message);
+          }
+        }
       });
     });
 
@@ -67,4 +86,24 @@
         });
     };
   }
+
+  bind(messageType: WebSocketMessageType, callback: (message: WebSocketMessage) => void): void {
+    const messageTypeCallbacks = this.callbacks.get(messageType);
+    if (messageTypeCallbacks) {
+      messageTypeCallbacks.push(callback);
+    } else {
+      this.callbacks.set(messageType, [callback]);
+    }
+  }
+
+  send(accountId: string, message: WebSocketMessage): boolean {
+    const accountSockets = this.sockets.get(accountId);
+    if (!accountSockets) {
+      return false;
+    }
+    for (const accountSocket of accountSockets) {
+      accountSocket.send(message);
+    }
+    return true;
+  }
 }