You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			204 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			204 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			TypeScript
		
	
| import _ from 'lodash';
 | |
| import { Data } from '../../data/data';
 | |
| import { SignalService } from '../../protobuf';
 | |
| import { PnServer } from '../apis/push_notification_api';
 | |
| import { DisappearingMessages } from '../disappearing_messages';
 | |
| import { OpenGroupVisibleMessage } from '../messages/outgoing/visibleMessage/OpenGroupVisibleMessage';
 | |
| import { RawMessage } from '../types';
 | |
| import { UserUtils } from '../utils';
 | |
| 
 | |
| async function handlePublicMessageSentSuccess(
 | |
|   sentMessageIdentifier: string,
 | |
|   result: { serverId: number; serverTimestamp: number }
 | |
| ) {
 | |
|   const { serverId, serverTimestamp } = result;
 | |
| 
 | |
|   try {
 | |
|     const foundMessage = await fetchHandleMessageSentData(sentMessageIdentifier);
 | |
| 
 | |
|     if (!foundMessage) {
 | |
|       throw new Error(
 | |
|         'handlePublicMessageSentSuccess(): The message should be in memory for an openGroup message'
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     // serverTimestamp can be a fractional number where first part is seconds and second part is nanosecs depending on the pysogs version.
 | |
| 
 | |
|     foundMessage.set({
 | |
|       serverTimestamp,
 | |
|       serverId,
 | |
|       isPublic: true,
 | |
|       sent: true,
 | |
|       sent_at: serverTimestamp, // we quote by sent_at, so we MUST sent_at: serverTimestamp
 | |
|       sync: true,
 | |
|       synced: true,
 | |
|       sentSync: true,
 | |
|     });
 | |
|     await foundMessage.commit();
 | |
|     foundMessage.getConversation()?.updateLastMessage();
 | |
|   } catch (e) {
 | |
|     window?.log?.error('Error setting public on message');
 | |
|   }
 | |
| }
 | |
| 
 | |
| async function handleMessageSentSuccess(
 | |
|   sentMessage: RawMessage,
 | |
|   effectiveTimestamp: number,
 | |
|   wrappedEnvelope?: Uint8Array
 | |
| ) {
 | |
|   // The wrappedEnvelope will be set only if the message is not one of OpenGroupV2Message type.
 | |
|   let fetchedMessage = await fetchHandleMessageSentData(sentMessage.identifier);
 | |
|   if (!fetchedMessage) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   let sentTo = fetchedMessage.get('sent_to') || [];
 | |
| 
 | |
|   const isOurDevice = UserUtils.isUsFromCache(sentMessage.device);
 | |
| 
 | |
|   // FIXME this is not correct and will cause issues with syncing
 | |
|   // At this point the only way to check for medium
 | |
|   // group is by comparing the encryption type
 | |
|   const isClosedGroupMessage =
 | |
|     sentMessage.encryption === SignalService.Envelope.Type.CLOSED_GROUP_MESSAGE;
 | |
| 
 | |
|   // We trigger a sync message only when the message is not to one of our devices, AND
 | |
|   // the message is not for an open group (there is no sync for opengroups, each device pulls all messages), AND
 | |
|   // if we did not sync or trigger a sync message for this specific message already
 | |
|   const shouldTriggerSyncMessage =
 | |
|     !isOurDevice &&
 | |
|     !isClosedGroupMessage &&
 | |
|     !fetchedMessage.get('synced') &&
 | |
|     !fetchedMessage.get('sentSync');
 | |
| 
 | |
|   // A message is synced if we triggered a sync message (sentSync)
 | |
|   // and the current message was sent to our device (so a sync message)
 | |
|   const shouldMarkMessageAsSynced = isOurDevice && fetchedMessage.get('sentSync');
 | |
| 
 | |
|   const contentDecoded = SignalService.Content.decode(sentMessage.plainTextBuffer);
 | |
|   const { dataMessage } = contentDecoded;
 | |
| 
 | |
|   /**
 | |
|    * We should hit the notify endpoint for push notification only if:
 | |
|    *  • It's a one-to-one chat or a closed group
 | |
|    *  • The message has either text or attachments
 | |
|    */
 | |
|   const hasBodyOrAttachments = Boolean(
 | |
|     dataMessage && (dataMessage.body || (dataMessage.attachments && dataMessage.attachments.length))
 | |
|   );
 | |
|   const shouldNotifyPushServer = hasBodyOrAttachments && !isOurDevice;
 | |
| 
 | |
|   if (shouldNotifyPushServer) {
 | |
|     // notify the push notification server if needed
 | |
|     if (!wrappedEnvelope) {
 | |
|       window?.log?.warn('Should send PN notify but no wrapped envelope set.');
 | |
|     } else {
 | |
|       // we do not really care about the result, neither of waiting for it
 | |
|       void PnServer.notifyPnServer(wrappedEnvelope, sentMessage.device);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Handle the sync logic here
 | |
|   if (shouldTriggerSyncMessage) {
 | |
|     if (dataMessage) {
 | |
|       try {
 | |
|         await fetchedMessage.sendSyncMessage(contentDecoded, effectiveTimestamp);
 | |
|         const tempFetchMessage = await fetchHandleMessageSentData(sentMessage.identifier);
 | |
|         if (!tempFetchMessage) {
 | |
|           window?.log?.warn(
 | |
|             'Got an error while trying to sendSyncMessage(): fetchedMessage is null'
 | |
|           );
 | |
|           return;
 | |
|         }
 | |
|         fetchedMessage = tempFetchMessage;
 | |
|       } catch (e) {
 | |
|         window?.log?.warn('Got an error while trying to sendSyncMessage():', e);
 | |
|       }
 | |
|     }
 | |
|   } else if (shouldMarkMessageAsSynced) {
 | |
|     fetchedMessage.set({ synced: true });
 | |
|   }
 | |
| 
 | |
|   sentTo = _.union(sentTo, [sentMessage.device]);
 | |
| 
 | |
|   fetchedMessage.set({
 | |
|     sent_to: sentTo,
 | |
|     sent: true,
 | |
|     sent_at: effectiveTimestamp,
 | |
|   });
 | |
| 
 | |
|   DisappearingMessages.checkForExpiringOutgoingMessage(fetchedMessage, 'handleMessageSentSuccess');
 | |
| 
 | |
|   await fetchedMessage.commit();
 | |
|   fetchedMessage.getConversation()?.updateLastMessage();
 | |
| }
 | |
| 
 | |
| async function handleMessageSentFailure(
 | |
|   sentMessage: RawMessage | OpenGroupVisibleMessage,
 | |
|   error: any
 | |
| ) {
 | |
|   const fetchedMessage = await fetchHandleMessageSentData(sentMessage.identifier);
 | |
|   if (!fetchedMessage) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (error instanceof Error) {
 | |
|     await fetchedMessage.saveErrors(error);
 | |
|   }
 | |
| 
 | |
|   if (!(sentMessage instanceof OpenGroupVisibleMessage)) {
 | |
|     const isOurDevice = UserUtils.isUsFromCache(sentMessage.device);
 | |
|     // if this message was for ourself, and it was not already synced,
 | |
|     // it means that we failed to sync it.
 | |
|     // so just remove the flag saying that we are currently sending the sync message
 | |
|     if (isOurDevice && !fetchedMessage.get('sync')) {
 | |
|       fetchedMessage.set({ sentSync: false });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // always mark the message as sent.
 | |
|   // the fact that we have errors on the sent is based on the saveErrors()
 | |
|   fetchedMessage.set({
 | |
|     sent: true,
 | |
|   });
 | |
| 
 | |
|   // Disappeared messages that fail to send should not disappear
 | |
|   if (fetchedMessage.getExpirationType() && fetchedMessage.getExpireTimerSeconds() > 0) {
 | |
|     fetchedMessage.set({
 | |
|       expirationStartTimestamp: undefined,
 | |
|     });
 | |
|     window.log.warn(
 | |
|       `[handleMessageSentFailure] Stopping a message from disppearing until we retry the send operation. messageId: ${fetchedMessage.get(
 | |
|         'id'
 | |
|       )}`
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   await fetchedMessage.commit();
 | |
|   await fetchedMessage.getConversation()?.updateLastMessage();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * This function tries to find a message by messageId by first looking on the MessageController.
 | |
|  * The MessageController holds all messages being in memory.
 | |
|  * Those are the messages sent recently, received recently, or the one shown to the user.
 | |
|  *
 | |
|  * If the app restarted, it's very likely those messages won't be on the memory anymore.
 | |
|  * In this case, this function will look for it in the database and return it.
 | |
|  * If the message is found on the db, it will also register it to the MessageController so our subsequent calls are quicker.
 | |
|  */
 | |
| async function fetchHandleMessageSentData(messageIdentifier: string) {
 | |
|   const dbMessage = await Data.getMessageById(messageIdentifier);
 | |
| 
 | |
|   if (!dbMessage) {
 | |
|     return null;
 | |
|   }
 | |
|   return dbMessage;
 | |
| }
 | |
| 
 | |
| export const MessageSentHandler = {
 | |
|   handlePublicMessageSentSuccess,
 | |
|   handleMessageSentSuccess,
 | |
|   handleMessageSentFailure,
 | |
| };
 |