154 lines
		
	
	
	
		
			4.4 KiB
			
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			154 lines
		
	
	
	
		
			4.4 KiB
			
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
// Copyright 2017-2020 Signal Messenger, LLC
 | 
						|
// SPDX-License-Identifier: AGPL-3.0-only
 | 
						|
 | 
						|
/* eslint-disable max-classes-per-file */
 | 
						|
 | 
						|
import { Collection, Model } from 'backbone';
 | 
						|
 | 
						|
import { MessageModel } from '../models/messages';
 | 
						|
import { isIncoming } from '../state/selectors/message';
 | 
						|
import { isMessageUnread } from '../util/isMessageUnread';
 | 
						|
 | 
						|
type ReadSyncAttributesType = {
 | 
						|
  senderId: string;
 | 
						|
  sender: string;
 | 
						|
  senderUuid: string;
 | 
						|
  timestamp: number;
 | 
						|
  readAt: number;
 | 
						|
};
 | 
						|
 | 
						|
class ReadSyncModel extends Model<ReadSyncAttributesType> {}
 | 
						|
 | 
						|
let singleton: ReadSyncs | undefined;
 | 
						|
 | 
						|
async function maybeItIsAReactionReadSync(sync: ReadSyncModel): Promise<void> {
 | 
						|
  const readReaction = await window.Signal.Data.markReactionAsRead(
 | 
						|
    sync.get('senderUuid'),
 | 
						|
    Number(sync.get('timestamp'))
 | 
						|
  );
 | 
						|
 | 
						|
  if (!readReaction) {
 | 
						|
    window.log.info(
 | 
						|
      'Nothing found for read sync',
 | 
						|
      sync.get('senderId'),
 | 
						|
      sync.get('sender'),
 | 
						|
      sync.get('senderUuid'),
 | 
						|
      sync.get('timestamp')
 | 
						|
    );
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  window.Whisper.Notifications.removeBy({
 | 
						|
    conversationId: readReaction.conversationId,
 | 
						|
    emoji: readReaction.emoji,
 | 
						|
    targetAuthorUuid: readReaction.targetAuthorUuid,
 | 
						|
    targetTimestamp: readReaction.targetTimestamp,
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
export class ReadSyncs extends Collection {
 | 
						|
  static getSingleton(): ReadSyncs {
 | 
						|
    if (!singleton) {
 | 
						|
      singleton = new ReadSyncs();
 | 
						|
    }
 | 
						|
 | 
						|
    return singleton;
 | 
						|
  }
 | 
						|
 | 
						|
  forMessage(message: MessageModel): ReadSyncModel | null {
 | 
						|
    const senderId = window.ConversationController.ensureContactIds({
 | 
						|
      e164: message.get('source'),
 | 
						|
      uuid: message.get('sourceUuid'),
 | 
						|
    });
 | 
						|
    const sync = this.find(item => {
 | 
						|
      return (
 | 
						|
        item.get('senderId') === senderId &&
 | 
						|
        item.get('timestamp') === message.get('sent_at')
 | 
						|
      );
 | 
						|
    });
 | 
						|
    if (sync) {
 | 
						|
      window.log.info(
 | 
						|
        `Found early read sync for message ${sync.get('timestamp')}`
 | 
						|
      );
 | 
						|
      this.remove(sync);
 | 
						|
      return sync;
 | 
						|
    }
 | 
						|
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
 | 
						|
  async onSync(sync: ReadSyncModel): Promise<void> {
 | 
						|
    try {
 | 
						|
      const messages = await window.Signal.Data.getMessagesBySentAt(
 | 
						|
        sync.get('timestamp'),
 | 
						|
        {
 | 
						|
          MessageCollection: window.Whisper.MessageCollection,
 | 
						|
        }
 | 
						|
      );
 | 
						|
 | 
						|
      const found = messages.find(item => {
 | 
						|
        const senderId = window.ConversationController.ensureContactIds({
 | 
						|
          e164: item.get('source'),
 | 
						|
          uuid: item.get('sourceUuid'),
 | 
						|
        });
 | 
						|
 | 
						|
        return isIncoming(item.attributes) && senderId === sync.get('senderId');
 | 
						|
      });
 | 
						|
 | 
						|
      if (!found) {
 | 
						|
        await maybeItIsAReactionReadSync(sync);
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      window.Whisper.Notifications.removeBy({ messageId: found.id });
 | 
						|
 | 
						|
      const message = window.MessageController.register(found.id, found);
 | 
						|
      const readAt = Math.min(sync.get('readAt'), Date.now());
 | 
						|
 | 
						|
      // If message is unread, we mark it read. Otherwise, we update the expiration
 | 
						|
      //   timer to the time specified by the read sync if it's earlier than
 | 
						|
      //   the previous read time.
 | 
						|
      if (isMessageUnread(message.attributes)) {
 | 
						|
        // TODO DESKTOP-1509: use MessageUpdater.markRead once this is TS
 | 
						|
        message.markRead(readAt, { skipSave: true });
 | 
						|
 | 
						|
        const updateConversation = () => {
 | 
						|
          // onReadMessage may result in messages older than this one being
 | 
						|
          //   marked read. We want those messages to have the same expire timer
 | 
						|
          //   start time as this one, so we pass the readAt value through.
 | 
						|
          message.getConversation()?.onReadMessage(message, readAt);
 | 
						|
        };
 | 
						|
 | 
						|
        if (window.startupProcessingQueue) {
 | 
						|
          const conversation = message.getConversation();
 | 
						|
          if (conversation) {
 | 
						|
            window.startupProcessingQueue.add(
 | 
						|
              conversation.get('id'),
 | 
						|
              message.get('sent_at'),
 | 
						|
              updateConversation
 | 
						|
            );
 | 
						|
          }
 | 
						|
        } else {
 | 
						|
          updateConversation();
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        const now = Date.now();
 | 
						|
        const existingTimestamp = message.get('expirationStartTimestamp');
 | 
						|
        const expirationStartTimestamp = Math.min(
 | 
						|
          now,
 | 
						|
          Math.min(existingTimestamp || now, readAt || now)
 | 
						|
        );
 | 
						|
        message.set({ expirationStartTimestamp });
 | 
						|
      }
 | 
						|
 | 
						|
      window.Signal.Util.queueUpdateMessage(message.attributes);
 | 
						|
 | 
						|
      this.remove(sync);
 | 
						|
    } catch (error) {
 | 
						|
      window.log.error(
 | 
						|
        'ReadSyncs.onSync error:',
 | 
						|
        error && error.stack ? error.stack : error
 | 
						|
      );
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 |