@@ -246,14 +246,15 @@ export class FirestoreServices {
246246 this . memberIds ?. forEach ( ( memberId ) => {
247247 this . updateUserConversation (
248248 memberId ,
249- formatLatestMessage (
250- this . userId ,
251- this . userInfo ?. name || '' ,
252- '' ,
249+ formatLatestMessage ( {
250+ userId : this . userId ,
251+ name : this . userInfo ?. name || '' ,
252+ message : '' ,
253253 type,
254254 path,
255- extension
256- )
255+ extension,
256+ system : false ,
257+ } )
257258 ) ;
258259 } ) ;
259260 } catch ( error ) {
@@ -278,16 +279,30 @@ export class FirestoreServices {
278279 message . type === MessageTypes . image ||
279280 message . type === MessageTypes . video
280281 ) {
281- messageData = formatSendMessage ( this . userId , text , type , path , extension ) ;
282+ messageData = formatSendMessage ( {
283+ userId : this . userId ,
284+ text,
285+ type,
286+ path,
287+ extension,
288+ } ) ;
282289 this . sendMessageWithFile ( messageData ) ;
283290 } else {
284- /** Format message */
285- messageData = formatSendMessage ( this . userId , text ) ;
286- /** Encrypt the message before store to firestore */
287- if ( this . enableEncrypt && this . encryptKey ) {
288- messageData . text = this . encryptFunctionProp
289- ? await this . encryptFunctionProp ( text )
290- : await encryptData ( text , this . encryptKey ) ;
291+ if ( message . system ) {
292+ messageData = formatSendMessage ( {
293+ userId : this . userId ,
294+ text,
295+ system : true ,
296+ } ) ;
297+ } else {
298+ /** Format message text */
299+ messageData = formatSendMessage ( { userId : this . userId , text } ) ;
300+ /** Encrypt the message before store to firestore */
301+ if ( this . enableEncrypt && this . encryptKey ) {
302+ messageData . text = this . encryptFunctionProp
303+ ? await this . encryptFunctionProp ( text )
304+ : await encryptData ( text , this . encryptKey ) ;
305+ }
291306 }
292307
293308 try {
@@ -301,11 +316,12 @@ export class FirestoreServices {
301316 . add ( messageData ) ;
302317
303318 /** Format latest message data */
304- const latestMessageData = formatLatestMessage (
305- this . userId ,
306- this . userInfo ?. name || '' ,
307- messageData . text
308- ) ;
319+ const latestMessageData = formatLatestMessage ( {
320+ userId : this . userId ,
321+ name : this . userInfo ?. name || '' ,
322+ message : messageData . text ,
323+ system : messageData . system ,
324+ } ) ;
309325 this . memberIds ?. forEach ( ( memberId ) => {
310326 this . updateUserConversation ( memberId , latestMessageData ) ;
311327 } ) ;
@@ -324,6 +340,8 @@ export class FirestoreServices {
324340 'Please create conversation before send the first message!'
325341 ) ;
326342 }
343+ if ( userId === this . userId && latestMessageData . system ) return ;
344+
327345 const userConversationRef = firestore ( )
328346 . collection < Partial < ConversationProps > > (
329347 this . getUrlWithPrefix (
@@ -611,7 +629,9 @@ export class FirestoreServices {
611629 ) ;
612630 } ;
613631
614- listenConversationUpdate = ( callback : ( _ : ConversationProps ) => void ) => {
632+ listenConversationUpdate = (
633+ callback : ( _ : ConversationProps , type : string ) => void
634+ ) => {
615635 const regex = this . regexBlacklist ;
616636
617637 return firestore ( )
@@ -623,11 +643,11 @@ export class FirestoreServices {
623643 . onSnapshot ( async ( snapshot ) => {
624644 if ( snapshot ) {
625645 for ( const change of snapshot . docChanges ( ) ) {
646+ const data = {
647+ ...( change . doc . data ( ) as ConversationProps ) ,
648+ id : change . doc . id ,
649+ } ;
626650 if ( change . type === 'modified' ) {
627- const data = {
628- ...( change . doc . data ( ) as ConversationProps ) ,
629- id : change . doc . id ,
630- } ;
631651 const message = {
632652 ...data ,
633653 latestMessage : data . latestMessage
@@ -639,10 +659,139 @@ export class FirestoreServices {
639659 )
640660 : data . latestMessage ,
641661 } as ConversationProps ;
642- callback ?.( message ) ;
662+ callback ?.( message , change . type ) ;
663+ } else if ( change . type === 'removed' ) {
664+ callback ?.( data , change . type ) ;
643665 }
644666 }
645667 }
646668 } ) ;
647669 } ;
670+
671+ private deleteUnreadAndTypingUser = async ( userId : string ) => {
672+ if ( ! userId || ! this . conversationId ) {
673+ console . error ( 'User ID or conversation ID is missing' ) ;
674+ return ;
675+ }
676+
677+ const conversationRef = firestore ( )
678+ . collection < ConversationData > (
679+ this . getUrlWithPrefix ( `${ FireStoreCollection . conversations } ` )
680+ )
681+ . doc ( this . conversationId ) ;
682+
683+ try {
684+ const conversationDoc = await conversationRef . get ( ) ;
685+ if ( conversationDoc . exists ) {
686+ const conversationData = conversationDoc . data ( ) ;
687+ if (
688+ conversationData &&
689+ conversationData . unRead &&
690+ conversationData . unRead [ userId ]
691+ ) {
692+ const updatedUnread = { ...conversationData . unRead } ;
693+ delete updatedUnread [ userId ] ;
694+
695+ await conversationRef . update ( { unRead : updatedUnread } ) ;
696+ const updatedTyping = { ...conversationData . typing } ;
697+ delete updatedTyping [ userId ] ;
698+ await conversationRef . update ( { typing : updatedTyping } ) ;
699+ } else {
700+ console . log ( 'User not found in unRead or no unRead field present' ) ;
701+ }
702+ } else {
703+ console . log ( 'Conversation document does not exist' ) ;
704+ }
705+ } catch ( error ) {
706+ console . error ( 'Error removing user from unRead: ' , error ) ;
707+ }
708+ } ;
709+
710+ private updateConversationMembers = async (
711+ conversationId : string ,
712+ userId : string
713+ ) : Promise < ConversationProps | null > => {
714+ const leftConversation = firestore ( )
715+ . collection (
716+ this . getUrlWithPrefix (
717+ `${ FireStoreCollection . users } /${ userId } /${ FireStoreCollection . conversations } `
718+ )
719+ )
720+ . doc ( conversationId ) ;
721+
722+ try {
723+ const leftConversationDoc = await leftConversation . get ( ) ;
724+ if ( ! leftConversationDoc . exists ) {
725+ console . error ( 'Conversation document does not exist' ) ;
726+ return null ;
727+ }
728+
729+ const leftConversationData =
730+ leftConversationDoc . data ( ) as ConversationProps ;
731+ const newMembers = leftConversationData ?. members ?. filter (
732+ ( e ) => e !== userId
733+ ) ;
734+
735+ const batch = firestore ( ) . batch ( ) ;
736+ newMembers ?. forEach ( ( id ) => {
737+ if ( id ) {
738+ const doc = firestore ( )
739+ . collection (
740+ this . getUrlWithPrefix (
741+ `${ FireStoreCollection . users } /${ id } /${ FireStoreCollection . conversations } `
742+ )
743+ )
744+ . doc ( conversationId ) ;
745+ batch . set ( doc , { members : newMembers } , { merge : true } ) ;
746+ }
747+ } ) ;
748+
749+ await batch . commit ( ) ;
750+ return leftConversationData ;
751+ } catch ( error ) {
752+ console . error ( 'Error updating conversation members: ' , error ) ;
753+ return null ;
754+ }
755+ } ;
756+
757+ leaveConversation = async ( isSilent : boolean = false ) : Promise < boolean > => {
758+ if ( ! this . conversationId ) {
759+ throw new Error (
760+ 'Please create a conversation before sending the first message!'
761+ ) ;
762+ }
763+
764+ try {
765+ await this . deleteUnreadAndTypingUser ( this . userId ) ;
766+
767+ const leftConversationData = await this . updateConversationMembers (
768+ this . conversationId ,
769+ this . userId
770+ ) ;
771+
772+ if ( ! leftConversationData ) return false ;
773+
774+ if ( ! isSilent ) {
775+ await this . sendMessage ( {
776+ text : `${ this . userInfo ?. name } left the conversation` ,
777+ system : true ,
778+ } as MessageProps ) ;
779+ }
780+
781+ const leftConversation = firestore ( )
782+ . collection (
783+ this . getUrlWithPrefix (
784+ `${ FireStoreCollection . users } /${ this . userId } /${ FireStoreCollection . conversations } `
785+ )
786+ )
787+ . doc ( this . conversationId ) ;
788+
789+ await leftConversation . delete ( ) ;
790+
791+ return true ;
792+ } catch ( e ) {
793+ console . error ( 'Error leaving conversation: ' , e ) ;
794+ return false ;
795+ }
796+ } ;
648797}
0 commit comments