Fix conversation messages

Change-Id: I999839176bf523f1968dada3e3ebd8703f08656f
diff --git a/client/src/App.tsx b/client/src/App.tsx
index 86c226f..69dd466 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -20,6 +20,7 @@
 import { json, LoaderFunctionArgs, Outlet, redirect } from 'react-router-dom';
 
 import WelcomeAnimation from './components/welcome';
+import { getAccessToken } from './utils/auth';
 import { apiUrl } from './utils/constants';
 
 export async function checkSetupStatus(): Promise<boolean> {
@@ -38,7 +39,7 @@
 }
 
 const App = () => {
-  const [displayWelcome, setDisplayWelcome] = useState<boolean>(true);
+  const [displayWelcome, setDisplayWelcome] = useState<boolean>(getAccessToken() === undefined);
 
   console.log('App render');
 
diff --git a/client/src/components/ConversationListItem.tsx b/client/src/components/ConversationListItem.tsx
index 2861eb8..30dc7bf 100644
--- a/client/src/components/ConversationListItem.tsx
+++ b/client/src/components/ConversationListItem.tsx
@@ -50,7 +50,7 @@
   const pathId = conversationId || contactId;
   const isSelected = conversation.getDisplayUri() === pathId;
   const navigate = useNavigate();
-  const [userId] = useState(conversation?.getFirstMember()?.contact.getUri());
+  const userId = conversation?.getFirstMember()?.contact.getUri();
   const uri = conversation.getId() ? `/conversation/${conversation.getId()}` : `/add-contact/${userId}`;
   return (
     <Box onContextMenu={contextMenuHandler.handleAnchorPosition}>
diff --git a/client/src/pages/Messenger.tsx b/client/src/pages/Messenger.tsx
index fd8dc99..47567cf 100644
--- a/client/src/pages/Messenger.tsx
+++ b/client/src/pages/Messenger.tsx
@@ -16,8 +16,8 @@
  * <https://www.gnu.org/licenses/>.
  */
 import { Stack } from '@mui/material';
-import { Contact, Conversation } from 'jami-web-common';
-import { useEffect, useState } from 'react';
+import { Contact, Conversation, WebSocketMessageType } from 'jami-web-common';
+import { useContext, useEffect, useState } from 'react';
 
 //import Sound from 'react-sound';
 import ConversationList from '../components/ConversationList';
@@ -26,14 +26,18 @@
 import LoadingPage from '../components/Loading';
 import NewContactForm from '../components/NewContactForm';
 import { useAuthContext } from '../contexts/AuthProvider';
-import { useAppSelector } from '../redux/hooks';
+import { WebSocketContext } from '../contexts/WebSocketProvider';
+import { setRefreshFromSlice } from '../redux/appSlice';
+import { useAppDispatch, useAppSelector } from '../redux/hooks';
 import { MessengerRouteParams } from '../router';
 import { useUrlParams } from '../utils/hooks';
 import AddContactPage from './AddContactPage';
 
 const Messenger = () => {
   const { refresh } = useAppSelector((state) => state.userInfo);
+  const dispatch = useAppDispatch();
   const { account, axiosInstance } = useAuthContext();
+  const webSocket = useContext(WebSocketContext);
 
   const [conversations, setConversations] = useState<Conversation[] | undefined>(undefined);
   const [searchQuery, setSearchQuery] = useState('');
@@ -58,6 +62,16 @@
   }, [axiosInstance, accountId, refresh]);
 
   useEffect(() => {
+    if (!webSocket) {
+      return;
+    }
+
+    const conversationMessageListener = () => dispatch(setRefreshFromSlice());
+    webSocket.bind(WebSocketMessageType.ConversationMessage, conversationMessageListener);
+    return () => webSocket.unbind(WebSocketMessageType.ConversationMessage, conversationMessageListener);
+  }, [webSocket, dispatch]);
+
+  useEffect(() => {
     if (!searchQuery) return;
     const controller = new AbortController();
     // TODO: Type properly https://git.jami.net/savoirfairelinux/jami-web/-/issues/92
diff --git a/client/src/utils/auth.ts b/client/src/utils/auth.ts
index 822771c..73b5522 100644
--- a/client/src/utils/auth.ts
+++ b/client/src/utils/auth.ts
@@ -80,9 +80,8 @@
   }
 }
 
-export function getAccessToken(): string {
-  const accessToken: string | null = localStorage.getItem('accessToken');
-  return accessToken ?? '';
+export function getAccessToken(): string | undefined {
+  return localStorage.getItem('accessToken') ?? undefined;
 }
 
 export function setAccessToken(accessToken: string): void {
diff --git a/server/src/jamid/jami-signal-interfaces.ts b/server/src/jamid/jami-signal-interfaces.ts
index a839309..76025e1 100644
--- a/server/src/jamid/jami-signal-interfaces.ts
+++ b/server/src/jamid/jami-signal-interfaces.ts
@@ -17,6 +17,8 @@
  */
 import { AccountDetails, Message, VolatileDetails } from 'jami-web-common';
 
+import { Blob, StringMap } from './jami-swig.js';
+
 // 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
@@ -69,6 +71,14 @@
   state: number; // TODO: Replace state number with enum (see account_const.h)
 }
 
+export interface IncomingTrustRequest {
+  accountId: string;
+  conversationId: string;
+  from: string;
+  payload: Blob;
+  received: number;
+}
+
 export interface ContactAdded {
   accountId: string;
   contactId: string;
@@ -81,6 +91,12 @@
   banned: boolean;
 }
 
+export interface ConversationRequestReceived {
+  accountId: string;
+  conversationId: string;
+  metadata: StringMap;
+}
+
 export interface ConversationReady {
   accountId: string;
   conversationId: string;
@@ -98,6 +114,13 @@
   messages: Message[];
 }
 
+export interface ConversationMemberEvent {
+  accountId: string;
+  conversationId: string;
+  memberUri: string;
+  event: number;
+}
+
 export interface MessageReceived {
   accountId: string;
   conversationId: string;
diff --git a/server/src/jamid/jami-signal.ts b/server/src/jamid/jami-signal.ts
index 9455fcf..6a9b15b 100644
--- a/server/src/jamid/jami-signal.ts
+++ b/server/src/jamid/jami-signal.ts
@@ -25,6 +25,7 @@
   AccountsChanged = 'AccountsChanged',
   AccountDetailsChanged = 'AccountDetailsChanged',
   RegistrationStateChanged = 'RegistrationStateChanged',
+  IncomingTrustRequest = 'IncomingTrustRequest',
   ContactAdded = 'ContactAdded',
   ContactRemoved = 'ContactRemoved',
   ExportOnRingEnded = 'ExportOnRingEnded',
diff --git a/server/src/jamid/jami-swig.ts b/server/src/jamid/jami-swig.ts
index bb3d584..df9dd3d 100644
--- a/server/src/jamid/jami-swig.ts
+++ b/server/src/jamid/jami-swig.ts
@@ -28,14 +28,14 @@
   set(key: T, value: U): void;
 }
 
-// export type IntVect = SwigVec<number>;
-// export type UintVect = SwigVec<number>;
-// export type FloatVect = SwigVec<number>;
+// export type IntVect = SwigVect<number>;
+// export type UintVect = SwigVect<number>;
+// export type FloatVect = SwigVect<number>;
 export type StringVect = SwigVect<string>;
 // export type IntegerMap = SwigMap<string, number>;
 export type StringMap = SwigMap<string, string>;
 export type VectMap = SwigVect<StringMap>;
-// export type Blob = SwigVec<number>;
+export type Blob = SwigVect<number>;
 
 function* swigVectToIt<T>(swigVect: SwigVect<T>) {
   const size = swigVect.size();
@@ -106,6 +106,9 @@
   removeContact(accountId: string, contactId: string, ban: boolean): void;
   getContacts(accountId: string): VectMap;
   getContactDetails(accountId: string, contactId: string): StringMap;
+  sendTrustRequest(accountId: string, to: string, payload: Blob): void;
+  acceptTrustRequest(accountId: string, from: string): boolean;
+  discardTrustRequest(accountId: string, from: string): boolean;
 
   getDefaultModerators(accountId: string): StringVect;
   setDefaultModerator(accountId: string, uri: string, state: boolean): void;
@@ -113,6 +116,7 @@
   getConversations(accountId: string): StringVect;
   conversationInfos(accountId: string, conversationId: string): StringMap;
   getConversationMembers(accountId: string, conversationId: string): VectMap;
+  acceptConversationRequest(accountId: string, conversationId: string): void;
 
   sendMessage(accountId: string, conversationId: string, message: string, replyTo: string, flag: number): void;
   loadConversationMessages(accountId: string, conversationId: string, fromMessage: string, n: number): number;
@@ -128,4 +132,5 @@
   StringMap: Constructable<StringMap>;
   // VectMap: Constructable<VectMap>;
   // IntegerMap: Constructable<IntegerMap>;
+  Blob: Constructable<Blob>;
 }
diff --git a/server/src/jamid/jamid.ts b/server/src/jamid/jamid.ts
index 9122395..843d7cf 100644
--- a/server/src/jamid/jamid.ts
+++ b/server/src/jamid/jamid.ts
@@ -38,8 +38,10 @@
   ContactAdded,
   ContactRemoved,
   ConversationLoaded,
+  ConversationMemberEvent,
   ConversationReady,
   ConversationRemoved,
+  ConversationRequestReceived,
   IncomingAccountMessage,
   KnownDevicesChanged,
   MessageReceived,
@@ -118,6 +120,10 @@
     handlers.ContactRemoved = (accountId: string, contactId: string, banned: boolean) =>
       onContactRemoved.next({ accountId, contactId, banned });
 
+    const onConversationRequestReceived = new Subject<ConversationRequestReceived>();
+    handlers.ConversationRequestReceived = (accountId: string, conversationId: string, metadata: StringMap) =>
+      onConversationRequestReceived.next({ accountId, conversationId, metadata });
+
     const onConversationReady = new Subject<ConversationReady>();
     handlers.ConversationReady = (accountId: string, conversationId: string) =>
       onConversationReady.next({ accountId, conversationId });
@@ -130,6 +136,16 @@
     handlers.ConversationLoaded = (id: number, accountId: string, conversationId: string, messages: Message[]) =>
       onConversationLoaded.next({ id, accountId, conversationId, messages });
 
+    const onConversationMemberEvent = new Subject<ConversationMemberEvent>();
+    handlers.ConversationMemberEvent = (
+      accountId: string,
+      conversationId: string,
+      memberUri: string,
+      event: number
+    ) => {
+      onConversationMemberEvent.next({ accountId, conversationId, memberUri, event });
+    };
+
     const onMessageReceived = new Subject<MessageReceived>();
     handlers.MessageReceived = (accountId: string, conversationId: string, message: Message) =>
       onMessageReceived.next({ accountId, conversationId, message });
@@ -147,9 +163,11 @@
       onAccountMessageStatusChanged: onAccountMessageStatusChanged.asObservable(),
       onContactAdded: onContactAdded.asObservable(),
       onContactRemoved: onContactRemoved.asObservable(),
+      onConversationRequestReceived: onConversationRequestReceived.asObservable(),
       onConversationReady: onConversationReady.asObservable(),
       onConversationRemoved: onConversationRemoved.asObservable(),
       onConversationLoaded: onConversationLoaded.asObservable(),
+      onConversationMemberEvent: onConversationMemberEvent.asObservable(),
       onMessageReceived: onMessageReceived.asObservable(),
     };
 
@@ -266,6 +284,10 @@
     this.jamiSwig.addContact(accountId, contactId);
   }
 
+  sendTrustRequest(accountId: string, contactId: string): void {
+    this.jamiSwig.sendTrustRequest(accountId, contactId, new this.jamiSwig.Blob());
+  }
+
   removeContact(accountId: string, contactId: string): void {
     this.jamiSwig.removeContact(accountId, contactId, false);
   }
@@ -358,8 +380,8 @@
         `Received VolatileDetailsChanged: {"accountId":"${accountId}",` +
           `"details":{"Account.registeredName":"${username}", ...}}`
       );
-      // Keep map of usernames to account IDs
       if (username) {
+        // Keep map of usernames to account IDs
         this.usernamesToAccountIds.set(username, accountId);
       }
     });
@@ -420,6 +442,17 @@
       log.debug('Received ContactRemoved:', JSON.stringify(signal));
     });
 
+    this.events.onConversationRequestReceived.subscribe((signal) => {
+      log.debug('Received ConversationRequestReceived:', JSON.stringify(signal));
+
+      // TODO: Prompt user to accept conversation request on client
+      // Currently, we auto-accept all incoming conversation requests. In future, we
+      // need to ask the user if they accept the conversation request or not. Part of
+      // it can be done by sending a WebSocket event.
+      // See other implementations e.g. block contact / decline request / accept request.
+      this.jamiSwig.acceptConversationRequest(signal.accountId, signal.conversationId);
+    });
+
     this.events.onConversationReady.subscribe((signal) => {
       log.debug('Received ConversationReady:', JSON.stringify(signal));
     });
@@ -435,8 +468,13 @@
       );
     });
 
+    this.events.onConversationMemberEvent.subscribe((signal) => {
+      log.debug('Received onConversationMemberEvent:', JSON.stringify(signal));
+    });
+
     this.events.onMessageReceived.subscribe((signal) => {
       log.debug('Received MessageReceived:', JSON.stringify(signal));
+
       const data: ConversationMessage = {
         conversationId: signal.conversationId,
         message: signal.message,
diff --git a/server/src/routers/contacts-router.ts b/server/src/routers/contacts-router.ts
index d757d52..1b9772b 100644
--- a/server/src/routers/contacts-router.ts
+++ b/server/src/routers/contacts-router.ts
@@ -49,6 +49,8 @@
   const contactId = req.params.contactId;
 
   jamid.addContact(accountId, contactId);
+  // We need to manually send a conversation request
+  jamid.sendTrustRequest(accountId, contactId);
 
   const contactDetails = jamid.getContactDetails(accountId, contactId);
   if (Object.keys(contactDetails).length === 0) {
diff --git a/server/src/routers/conversation-router.ts b/server/src/routers/conversation-router.ts
index 224962c..b306bf1 100644
--- a/server/src/routers/conversation-router.ts
+++ b/server/src/routers/conversation-router.ts
@@ -111,6 +111,8 @@
 
     const contactId = members[0];
     jamid.addContact(accountId, contactId);
+    // We need to manually send a conversation request
+    jamid.sendTrustRequest(accountId, contactId);
 
     const contactDetails = jamid.getContactDetails(accountId, contactId);
     if (Object.keys(contactDetails).length === 0) {