Add more robust error handling to server

Changes:
- Use the proper RESTful HTTP status codes for all endpoints (e.g. 204 rather than 200 when the response body is empty)
- Add consistent and more helpful error messages
- Handle invalid route parameters by checking if jamid returns empty objects (e.g. invalid contact IDs)
- Use res.send() rather than res.json() for consistency
- Handle three new signals

GitLab: #111
Change-Id: I1d48dc4629995ab9a96bb2086a9aa91f81889598
diff --git a/server/src/jamid/jami-signal-interfaces.ts b/server/src/jamid/jami-signal-interfaces.ts
index 06dd6ed..a839309 100644
--- a/server/src/jamid/jami-signal-interfaces.ts
+++ b/server/src/jamid/jami-signal-interfaces.ts
@@ -59,7 +59,26 @@
 export interface IncomingAccountMessage {
   accountId: string;
   from: string;
-  message: Record<string, string>;
+  payload: Record<string, string>;
+}
+
+export interface AccountMessageStatusChanged {
+  accountId: string;
+  messageId: string;
+  peer: string;
+  state: number; // TODO: Replace state number with enum (see account_const.h)
+}
+
+export interface ContactAdded {
+  accountId: string;
+  contactId: string;
+  confirmed: boolean;
+}
+
+export interface ContactRemoved {
+  accountId: string;
+  contactId: string;
+  banned: boolean;
 }
 
 export interface ConversationReady {
diff --git a/server/src/jamid/jamid.ts b/server/src/jamid/jamid.ts
index 283e527..a3db747 100644
--- a/server/src/jamid/jamid.ts
+++ b/server/src/jamid/jamid.ts
@@ -24,6 +24,9 @@
 import { JamiSignal } from './jami-signal.js';
 import {
   AccountDetailsChanged,
+  AccountMessageStatusChanged,
+  ContactAdded,
+  ContactRemoved,
   ConversationLoaded,
   ConversationReady,
   ConversationRemoved,
@@ -93,8 +96,20 @@
       onKnownDevicesChanged.next({ accountId, devices });
 
     const onIncomingAccountMessage = new Subject<IncomingAccountMessage>();
-    handlers.IncomingAccountMessage = (accountId: string, from: string, message: Record<string, string>) =>
-      onIncomingAccountMessage.next({ accountId, from, message });
+    handlers.IncomingAccountMessage = (accountId: string, from: string, payload: Record<string, string>) =>
+      onIncomingAccountMessage.next({ accountId, from, payload });
+
+    const onAccountMessageStatusChanged = new Subject<AccountMessageStatusChanged>();
+    handlers.AccountMessageStatusChanged = (accountId: string, messageId: string, peer: string, state: number) =>
+      onAccountMessageStatusChanged.next({ accountId, messageId, peer, state });
+
+    const onContactAdded = new Subject<ContactAdded>();
+    handlers.ContactAdded = (accountId: string, contactId: string, confirmed: boolean) =>
+      onContactAdded.next({ accountId, contactId, confirmed });
+
+    const onContactRemoved = new Subject<ContactRemoved>();
+    handlers.ContactRemoved = (accountId: string, contactId: string, banned: boolean) =>
+      onContactRemoved.next({ accountId, contactId, banned });
 
     const onConversationReady = new Subject<ConversationReady>();
     handlers.ConversationReady = (accountId: string, conversationId: string) =>
@@ -122,6 +137,9 @@
       onRegisteredNameFound: onRegisteredNameFound.asObservable(),
       onKnownDevicesChanged: onKnownDevicesChanged.asObservable(),
       onIncomingAccountMessage: onIncomingAccountMessage.asObservable(),
+      onAccountMessageStatusChanged: onAccountMessageStatusChanged.asObservable(),
+      onContactAdded: onContactAdded.asObservable(),
+      onContactRemoved: onContactRemoved.asObservable(),
       onConversationReady: onConversationReady.asObservable(),
       onConversationRemoved: onConversationRemoved.asObservable(),
       onConversationLoaded: onConversationLoaded.asObservable(),
@@ -351,7 +369,7 @@
 
     this.events.onIncomingAccountMessage.subscribe((signal) => {
       log.debug('Received IncomingAccountMessage:', JSON.stringify(signal));
-      const message = JSON.parse(signal.message['application/json']);
+      const message = JSON.parse(signal.payload['application/json']);
 
       if (message === undefined) {
         log.debug('Undefined account message');
@@ -369,6 +387,18 @@
       this.ws.send(signal.accountId, message);
     });
 
+    this.events.onAccountMessageStatusChanged.subscribe((signal) => {
+      log.debug('Received AccountMessageStatusChanged:', JSON.stringify(signal));
+    });
+
+    this.events.onContactAdded.subscribe((signal) => {
+      log.debug('Received ContactAdded:', JSON.stringify(signal));
+    });
+
+    this.events.onContactRemoved.subscribe((signal) => {
+      log.debug('Received ContactRemoved:', JSON.stringify(signal));
+    });
+
     this.events.onConversationReady.subscribe((signal) => {
       log.debug('Received ConversationReady:', JSON.stringify(signal));
     });