Add webRTC handlers

- Bind webRCT callbacks from socket
- Handle incoming account message for webRTC

GitLab: #53
Change-Id: I4ed28e7ca41e8e3870968819c75ffad249a188d0
diff --git a/server/src/app.ts b/server/src/app.ts
index f7c860a..4c1914b 100644
--- a/server/src/app.ts
+++ b/server/src/app.ts
@@ -22,6 +22,7 @@
 import log from 'loglevel';
 import { Service } from 'typedi';
 
+import { bindWebRTCCallbacks } from './handlers/webrtc-handler.js';
 import { accountRouter } from './routers/account-router.js';
 import { authRouter } from './routers/auth-router.js';
 import { callRouter } from './routers/call-router.js';
@@ -49,6 +50,9 @@
     app.use('/calls', callRouter);
     app.use('/ns', nameserverRouter);
 
+    // Setup WebSocket callbacks
+    bindWebRTCCallbacks();
+
     // Setup 404 error handling
     app.use((_req, res) => {
       res.sendStatus(HttpStatusCode.NotFound);
diff --git a/server/src/handlers/webrtc-handler.ts b/server/src/handlers/webrtc-handler.ts
new file mode 100644
index 0000000..10de56f
--- /dev/null
+++ b/server/src/handlers/webrtc-handler.ts
@@ -0,0 +1,40 @@
+/*
+ * 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 { AccountTextMessage, WebSocketMessage, WebSocketMessageType } from 'jami-web-common';
+import log from 'loglevel';
+import { Container } from 'typedi';
+
+import { Jamid } from '../jamid/jamid.js';
+import { Ws } from '../ws.js';
+
+const jamid = Container.get(Jamid);
+const ws = Container.get(Ws);
+
+function sendWebRTCData(message: Partial<WebSocketMessage>) {
+  const data: AccountTextMessage = message.data;
+  if (!data.from || !data.to || !data.message) {
+    log.warn('Incorrect format for AccountTextMessage (require from, to and message):', data);
+    return;
+  }
+  jamid.sendAccountTextMessage(data.from, data.to, JSON.stringify(message));
+}
+
+export function bindWebRTCCallbacks() {
+  ws.bind(WebSocketMessageType.WebRTCOffer, sendWebRTCData);
+  ws.bind(WebSocketMessageType.WebRTCAnswer, sendWebRTCData);
+}
diff --git a/server/src/jamid/jamid.ts b/server/src/jamid/jamid.ts
index 78cd71a..283e527 100644
--- a/server/src/jamid/jamid.ts
+++ b/server/src/jamid/jamid.ts
@@ -15,11 +15,12 @@
  * License along with this program.  If not, see
  * <https://www.gnu.org/licenses/>.
  */
-import { AccountDetails, Message, VolatileDetails } from 'jami-web-common';
+import { AccountDetails, Message, VolatileDetails, WebSocketMessageType } from 'jami-web-common';
 import log from 'loglevel';
 import { filter, firstValueFrom, map, Subject } from 'rxjs';
 import { Service } from 'typedi';
 
+import { Ws } from '../ws.js';
 import { JamiSignal } from './jami-signal.js';
 import {
   AccountDetailsChanged,
@@ -46,7 +47,7 @@
   private readonly usernamesToAccountIds: Map<string, string>;
   private readonly events;
 
-  constructor() {
+  constructor(private ws: Ws) {
     // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
     this.jamiSwig = require('../../jamid.node') as JamiSwig; // TODO: we should put the path in the .env
 
@@ -134,8 +135,6 @@
     // 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(): void {
@@ -186,10 +185,10 @@
     return stringVectToArray(this.jamiSwig.getAccountList());
   }
 
-  sendAccountTextMessage(accountId: string, contactId: string, type: string, message: string): void {
+  sendAccountTextMessage(from: string, to: string, message: string): void {
     const messageStringMap: StringMap = new this.jamiSwig.StringMap();
-    messageStringMap.set(type, JSON.stringify(message));
-    this.jamiSwig.sendAccountTextMessage(accountId, contactId, messageStringMap);
+    messageStringMap.set('application/json', message);
+    this.jamiSwig.sendAccountTextMessage(from, to, messageStringMap);
   }
 
   // TODO: Add interface for returned type
@@ -352,6 +351,22 @@
 
     this.events.onIncomingAccountMessage.subscribe((signal) => {
       log.debug('Received IncomingAccountMessage:', JSON.stringify(signal));
+      const message = JSON.parse(signal.message['application/json']);
+
+      if (message === undefined) {
+        log.debug('Undefined account message');
+        return;
+      }
+
+      if (!Object.values(WebSocketMessageType).includes(message.type)) {
+        log.warn(`Unhandled account message type: ${message.type}`);
+        return;
+      }
+
+      message.data.from = signal.from;
+      message.data.to = signal.accountId;
+      log.info(`Sending ${JSON.stringify(message)} to ${signal.accountId}`);
+      this.ws.send(signal.accountId, message);
     });
 
     this.events.onConversationReady.subscribe((signal) => {
diff --git a/server/src/routers/account-router.ts b/server/src/routers/account-router.ts
index c870f83..6ef7aa6 100644
--- a/server/src/routers/account-router.ts
+++ b/server/src/routers/account-router.ts
@@ -18,19 +18,12 @@
 import { Request, Router } from 'express';
 import asyncHandler from 'express-async-handler';
 import { ParamsDictionary } from 'express-serve-static-core';
-import { AccountDetails, HttpStatusCode } from 'jami-web-common';
+import { AccountDetails, AccountTextMessage, HttpStatusCode } from 'jami-web-common';
 import { Container } from 'typedi';
 
 import { Jamid } from '../jamid/jamid.js';
 import { authenticateToken } from '../middleware/auth.js';
 
-interface SendAccountTextMessageApi {
-  from?: string;
-  to?: string;
-  type?: string;
-  data?: string;
-}
-
 const jamid = Container.get(Jamid);
 
 export const accountRouter = Router();
@@ -76,16 +69,12 @@
   res.sendStatus(HttpStatusCode.NoContent);
 });
 
-accountRouter.post(
-  '/send-account-message',
-  (req: Request<ParamsDictionary, string, SendAccountTextMessageApi>, res) => {
-    const { from, to, type, data } = req.body;
-    if (!from || !to || !type || !data) {
-      res.status(HttpStatusCode.BadRequest).send('Missing arguments in request');
-      return;
-    }
-
-    jamid.sendAccountTextMessage(from, to, type, data);
-    res.end();
+accountRouter.post('/send-account-message', (req: Request<ParamsDictionary, any, AccountTextMessage>, res) => {
+  const { from, to, message } = req.body;
+  if (!from || !to || !message) {
+    res.status(HttpStatusCode.BadRequest).send('Missing arguments in request');
+    return;
   }
-);
+  jamid.sendAccountTextMessage(from, to, JSON.stringify(message));
+  res.end();
+});
diff --git a/server/src/ws.ts b/server/src/ws.ts
index 0081e59..58cd3db 100644
--- a/server/src/ws.ts
+++ b/server/src/ws.ts
@@ -51,6 +51,10 @@
 
       ws.on('message', (messageString: string) => {
         const message: WebSocketMessage = JSON.parse(messageString);
+        if (!message.type || !message.data) {
+          ws.send('Incorrect format (require type and data)');
+          return;
+        }
         const callbacks = this.callbacks.get(message.type);
         if (callbacks) {
           for (const callback of callbacks) {