Handle remaining important daemon signals

Changes:
- Create new Jami signal interfaces based on SWIG interface files
- Add handlers for signals which are currently used in the old server
- Add explanatory comments
- Remove unneeded methods from JamiSwig

Change-Id: Iecd7284a833238f790b8ee6f1987bb36eee140c8
diff --git a/server/src/jamid/jami-signal-interfaces.ts b/server/src/jamid/jami-signal-interfaces.ts
index f868150..189cb53 100644
--- a/server/src/jamid/jami-signal-interfaces.ts
+++ b/server/src/jamid/jami-signal-interfaces.ts
@@ -15,9 +15,20 @@
  * License along with this program.  If not, see
  * <https://www.gnu.org/licenses/>.
  */
+import { AccountDetails, VolatileDetails } from 'jami-web-common';
+
+// These interfaces are used to hold all the parameters for signal handlers
+// These parameters' names and types can be found in daemon/bin/nodejs/callback.h
+// or in their relevant SWIG interface files (.i) in daemon/bin/nodejs
+
+export interface AccountDetailsChanged {
+  accountId: string;
+  details: AccountDetails;
+}
+
 export interface VolatileDetailsChanged {
   accountId: string;
-  details: Map<string, string>;
+  details: VolatileDetails;
 }
 
 export interface RegistrationStateChanged {
@@ -40,8 +51,36 @@
   username: string;
 }
 
+export interface KnownDevicesChanged {
+  accountId: string;
+  devices: Record<string, string>;
+}
+
 export interface IncomingAccountMessage {
   accountId: string;
   from: string;
   message: Record<string, string>;
 }
+
+export interface ConversationReady {
+  accountId: string;
+  conversationId: string;
+}
+
+export interface ConversationRemoved {
+  accountId: string;
+  conversationId: string;
+}
+
+export interface ConversationLoaded {
+  id: number;
+  accountId: string;
+  conversationId: string;
+  messages: Record<string, string>[];
+}
+
+export interface MessageReceived {
+  accountId: string;
+  conversationId: string;
+  message: Record<string, string>;
+}
diff --git a/server/src/jamid/jami-signal.ts b/server/src/jamid/jami-signal.ts
index f76127b..9455fcf 100644
--- a/server/src/jamid/jami-signal.ts
+++ b/server/src/jamid/jami-signal.ts
@@ -15,8 +15,13 @@
  * License along with this program.  If not, see
  * <https://www.gnu.org/licenses/>.
  */
+/**
+ * Signals emitted by the Jami daemon.
+ *
+ * The definition of signals can be found in `daemon/bin/nodejs/callback.h`.
+ */
 export enum JamiSignal {
-  // using DRing::ConfigurationSignal;
+  // libjami::ConfigurationSignal
   AccountsChanged = 'AccountsChanged',
   AccountDetailsChanged = 'AccountDetailsChanged',
   RegistrationStateChanged = 'RegistrationStateChanged',
@@ -30,14 +35,14 @@
   IncomingAccountMessage = 'IncomingAccountMessage',
   AccountMessageStatusChanged = 'AccountMessageStatusChanged',
 
-  // using DRing::CallSignal;
+  // libjami::CallSignal
   StateChange = 'StateChange',
   IncomingMessage = 'IncomingMessage',
   IncomingCall = 'IncomingCall',
   IncomingCallWithMedia = 'IncomingCallWithMedia',
   MediaChangeRequested = 'MediaChangeRequested',
 
-  // using DRing::ConversationSignal;
+  // libjami::ConversationSignal
   ConversationLoaded = 'ConversationLoaded',
   MessagesFound = 'MessagesFound',
   MessageReceived = 'MessageReceived',
diff --git a/server/src/jamid/jami-swig.ts b/server/src/jamid/jami-swig.ts
index 26ac694..b072328 100644
--- a/server/src/jamid/jami-swig.ts
+++ b/server/src/jamid/jami-swig.ts
@@ -53,6 +53,11 @@
 export const stringMapToMap = (sm: StringMap) => itToMap(swigMapToIt(sm));
 // export const vectMapToArrayMap = (vm: VectMap) => itToArr(itMap(swigVecToIt(vm), stringMapToMap));
 
+/**
+ * Non-exhaustive list of properties for JamiSwig.
+ *
+ * The full list of methods can be found in SWIG interface files (`.i`) in `daemon/bin/nodejs`.
+ */
 export interface JamiSwig {
   init(args: Record<string, unknown>): void;
   fini(): void;
@@ -75,11 +80,6 @@
 
   getKnownRingDevices(accountId: string): StringMap;
 
-  getAudioOutputDeviceList(): StringVect;
-
-  getVolume(device: string): number;
-  setVolume(device: string, value: number): void;
-
   addContact(accountId: string, contactId: string): void;
   removeContact(accountId: string, contactId: string, ban: boolean): void;
   getContacts(accountId: string): VectMap;
diff --git a/server/src/jamid/jamid.ts b/server/src/jamid/jamid.ts
index ab1963f..8676875 100644
--- a/server/src/jamid/jamid.ts
+++ b/server/src/jamid/jamid.ts
@@ -22,7 +22,13 @@
 
 import { JamiSignal } from './jami-signal.js';
 import {
+  AccountDetailsChanged,
+  ConversationLoaded,
+  ConversationReady,
+  ConversationRemoved,
   IncomingAccountMessage,
+  KnownDevicesChanged,
+  MessageReceived,
   NameRegistrationEnded,
   RegisteredNameFound,
   RegistrationStateChanged,
@@ -31,6 +37,8 @@
 import { JamiSwig, StringMap, stringMapToRecord, stringVectToArray } from './jami-swig.js';
 import { require } from './utils.js';
 
+// TODO: Mechanism to map account IDs to a list of WebSockets
+
 @Service()
 export class Jamid {
   private readonly jamiSwig: JamiSwig;
@@ -41,15 +49,30 @@
     // 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
 
+    this.usernamesToAccountIds = new Map<string, string>();
+
+    // Setup signal handlers
     const handlers: Record<string, unknown> = {};
-    const handler = (sig: string) => {
-      return (...args: unknown[]) => log.warn('Unhandled', sig, args);
+
+    // Add default handler for all signals
+    const createDefaultHandler = (signal: string) => {
+      return (...args: unknown[]) => log.warn('Unhandled', signal, args);
     };
-    Object.keys(JamiSignal).forEach((sig) => (handlers[sig] = handler(sig)));
+    for (const signal in JamiSignal) {
+      handlers[signal] = createDefaultHandler(signal);
+    }
+
+    // Overwrite handlers for handled signals using RxJS Subjects, converting multiple arguments to objects
+    const onAccountsChanged = new Subject<void>();
+    handlers.AccountsChanged = () => onAccountsChanged.next();
+
+    const onAccountDetailsChanged = new Subject<AccountDetailsChanged>();
+    handlers.AccountDetailsChanged = (accountId: string, details: AccountDetails) =>
+      onAccountDetailsChanged.next({ accountId, details });
 
     const onVolatileDetailsChanged = new Subject<VolatileDetailsChanged>();
-    handlers.VolatileDetailsChanged = (accountId: string, details: Record<string, string>) =>
-      onVolatileDetailsChanged.next({ accountId, details: new Map(Object.entries(details)) });
+    handlers.VolatileDetailsChanged = (accountId: string, details: VolatileDetails) =>
+      onVolatileDetailsChanged.next({ accountId, details });
 
     const onRegistrationStateChanged = new Subject<RegistrationStateChanged>();
     handlers.RegistrationStateChanged = (accountId: string, state: string, code: number, details: string) =>
@@ -63,42 +86,56 @@
     handlers.RegisteredNameFound = (accountId: string, state: number, address: string, username: string) =>
       onRegisteredNameFound.next({ accountId, state, address, username });
 
+    const onKnownDevicesChanged = new Subject<KnownDevicesChanged>();
+    handlers.KnownDevicesChanged = (accountId: string, devices: Record<string, string>) =>
+      onKnownDevicesChanged.next({ accountId, devices });
+
     const onIncomingAccountMessage = new Subject<IncomingAccountMessage>();
     handlers.IncomingAccountMessage = (accountId: string, from: string, message: Record<string, string>) =>
       onIncomingAccountMessage.next({ accountId, from, message });
 
+    const onConversationReady = new Subject<ConversationReady>();
+    handlers.ConversationReady = (accountId: string, conversationId: string) =>
+      onConversationReady.next({ accountId, conversationId });
+
+    const onConversationRemoved = new Subject<ConversationRemoved>();
+    handlers.ConversationRemoved = (accountId: string, conversationId: string) =>
+      onConversationRemoved.next({ accountId, conversationId });
+
+    const onConversationLoaded = new Subject<ConversationLoaded>();
+    handlers.ConversationLoaded = (
+      id: number,
+      accountId: string,
+      conversationId: string,
+      messages: Record<string, string>[]
+    ) => onConversationLoaded.next({ id, accountId, conversationId, messages });
+
+    const onMessageReceived = new Subject<MessageReceived>();
+    handlers.MessageReceived = (accountId: string, conversationId: string, message: Record<string, string>) =>
+      onMessageReceived.next({ accountId, conversationId, message });
+
+    // Expose all signals in an events object to allow other handlers to subscribe after jamiSwig.init()
     this.events = {
+      onAccountsChanged: onAccountsChanged.asObservable(),
+      onAccountDetailsChanged: onAccountDetailsChanged.asObservable(),
       onVolatileDetailsChanged: onVolatileDetailsChanged.asObservable(),
       onRegistrationStateChanged: onRegistrationStateChanged.asObservable(),
       onNameRegistrationEnded: onNameRegistrationEnded.asObservable(),
       onRegisteredNameFound: onRegisteredNameFound.asObservable(),
+      onKnownDevicesChanged: onKnownDevicesChanged.asObservable(),
       onIncomingAccountMessage: onIncomingAccountMessage.asObservable(),
+      onConversationReady: onConversationReady.asObservable(),
+      onConversationRemoved: onConversationRemoved.asObservable(),
+      onConversationLoaded: onConversationLoaded.asObservable(),
+      onMessageReceived: onMessageReceived.asObservable(),
     };
 
-    this.events.onVolatileDetailsChanged.subscribe(({ accountId, details }) => {
-      log.debug('[1] Received onVolatileDetailsChanged with', { accountId, details });
-      // Keep map of usernames to account IDs as Jamid cannot do this by itself (AFAIK)
-      const username = details.get('Account.registeredName');
-      if (username) {
-        this.usernamesToAccountIds.set(username, accountId);
-      }
-    });
-    this.events.onRegistrationStateChanged.subscribe((ctx) =>
-      log.debug('[1] Received onRegistrationStateChanged with', ctx)
-    );
-    this.events.onNameRegistrationEnded.subscribe((ctx) => log.debug('[1] Received onNameRegistrationEnded with', ctx));
-    this.events.onRegisteredNameFound.subscribe((ctx) => log.debug('[1] Received onRegisteredNameFound with', ctx));
-    this.events.onIncomingAccountMessage.subscribe((ctx) =>
-      log.debug('[1] Received onIncomingAccountMessage with', ctx)
-    );
+    this.setupSignalHandlers();
 
-    this.usernamesToAccountIds = new Map<string, string>();
-
-    // 1. You cannot change event handlers after init
+    // RxJS Subjects are used as signal handlers for the following reasons:
+    // 1. You cannot change event handlers after calling jamiSwig.init()
     // 2. You cannot specify multiple handlers for the same event
     // 3. You cannot specify a default handler
-    // So we rely on Subject() instead of Observable()
-    // Also, handlers receive multiple argument instead of tuple or object!
     this.jamiSwig.init(handlers);
   }
 
@@ -202,4 +239,60 @@
   getAccountIdFromUsername(username: string): string | undefined {
     return this.usernamesToAccountIds.get(username);
   }
+
+  private setupSignalHandlers() {
+    this.events.onAccountsChanged.subscribe(() => {
+      log.debug('Received AccountsChanged');
+    });
+
+    this.events.onAccountDetailsChanged.subscribe((signal) => {
+      log.debug('Received AccountsDetailsChanged', JSON.stringify(signal));
+    });
+
+    this.events.onVolatileDetailsChanged.subscribe(({ accountId, details }) => {
+      log.debug(`Received VolatileDetailsChanged: {"accountId":"${accountId}", ...}`);
+      // Keep map of usernames to account IDs
+      const username = details['Account.registeredName'];
+      if (username) {
+        this.usernamesToAccountIds.set(username, accountId);
+      }
+    });
+
+    this.events.onRegistrationStateChanged.subscribe((signal) => {
+      log.debug('Received RegistrationStateChanged:', JSON.stringify(signal));
+    });
+
+    this.events.onNameRegistrationEnded.subscribe((signal) => {
+      log.debug('Received NameRegistrationEnded:', JSON.stringify(signal));
+    });
+
+    this.events.onRegisteredNameFound.subscribe((signal) => {
+      log.debug('Received RegisteredNameFound:', JSON.stringify(signal));
+    });
+
+    this.events.onKnownDevicesChanged.subscribe(({ accountId }) => {
+      log.debug(`Received KnownDevicesChanged: {"accountId":"${accountId}", ...}`);
+    });
+
+    this.events.onIncomingAccountMessage.subscribe((signal) => {
+      log.debug('Received IncomingAccountMessage:', JSON.stringify(signal));
+    });
+
+    this.events.onConversationReady.subscribe((signal) => {
+      log.debug('Received ConversationReady:', JSON.stringify(signal));
+    });
+
+    this.events.onConversationRemoved.subscribe((signal) => {
+      log.debug('Received ConversationRemoved:', JSON.stringify(signal));
+    });
+
+    this.events.onConversationLoaded.subscribe((signal) => {
+      log.debug('Received ConversationLoaded:', JSON.stringify(signal));
+    });
+
+    this.events.onMessageReceived.subscribe((signal) => {
+      log.debug('Received MessageReceived:', JSON.stringify(signal));
+      // TODO: Send message to client using WebSocket
+    });
+  }
 }
diff --git a/server/src/routers/auth-router.ts b/server/src/routers/auth-router.ts
index 14559fe..d0fc6f0 100644
--- a/server/src/routers/auth-router.ts
+++ b/server/src/routers/auth-router.ts
@@ -21,7 +21,6 @@
 import { ParamsDictionary, Request } from 'express-serve-static-core';
 import { HttpStatusCode } from 'jami-web-common';
 import { SignJWT } from 'jose';
-import log from 'loglevel';
 import { Container } from 'typedi';
 
 import { Creds } from '../creds.js';
@@ -104,8 +103,6 @@
       return;
     }
 
-    log.debug(jamid.getAccountDetails(accountId));
-
     const isPasswordVerified = await argon2.verify(hashedPassword, password);
     if (!isPasswordVerified) {
       res.sendStatus(HttpStatusCode.Unauthorized);