From 68f10239462606bdfca713783ea57671ffd8930e Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Thu, 17 Jun 2021 10:15:10 -0700 Subject: [PATCH] Move message.getPropsForBubble and friends to selectors --- background.html | 9 - js/delivery_receipts.js | 121 -- js/message_requests.js | 118 -- js/modules/signal.js | 2 - js/read_receipts.js | 122 -- js/read_syncs.js | 140 -- js/view_syncs.js | 89 - preload.js | 1 - test/index.html | 1 - ts/ConversationController.ts | 2 +- ts/background.ts | 74 +- ts/components/ForwardMessageModal.stories.tsx | 1 + .../conversation/AttachmentList.stories.tsx | 2 + ts/components/conversation/AttachmentList.tsx | 2 +- .../conversation/ContactDetail.stories.tsx | 3 + .../conversation/EmbeddedContact.stories.tsx | 3 + .../conversation/EmbeddedContact.tsx | 102 +- .../conversation/ImageGrid.stories.tsx | 1 + .../conversation/Message.stories.tsx | 3 + ts/components/conversation/Message.tsx | 88 +- .../conversation/MessageDetail.stories.tsx | 34 +- ts/components/conversation/MessageDetail.tsx | 10 + ts/components/conversation/Quote.stories.tsx | 54 +- ts/components/conversation/Quote.tsx | 14 +- .../conversation/Timeline.stories.tsx | 3 + ts/components/conversation/Timeline.tsx | 19 +- .../conversation/TimelineItem.stories.tsx | 3 + ts/components/conversation/TimelineItem.tsx | 2 +- .../messageModifiers/AttachmentDownloads.ts | 140 +- ts/messageModifiers/Deletes.ts | 105 ++ ts/messageModifiers/DeliveryReceipts.ts | 137 ++ ts/messageModifiers/MessageRequests.ts | 132 ++ ts/messageModifiers/Reactions.ts | 167 ++ ts/messageModifiers/ReadReceipts.ts | 139 ++ ts/messageModifiers/ReadSyncs.ts | 162 ++ ts/messageModifiers/ViewSyncs.ts | 105 ++ ts/model-types.d.ts | 121 +- ts/models/conversations.ts | 68 +- ts/models/messages.ts | 1650 ++++------------- ts/services/MessageUpdater.ts | 6 +- ts/shims/Whisper.ts | 9 - ts/sql/Interface.ts | 19 +- ts/sql/Server.ts | 2 +- ts/state/actions.ts | 3 + ts/state/ducks/accounts.ts | 99 + ts/state/ducks/conversations.ts | 113 +- ts/state/ducks/search.ts | 5 +- ts/state/ducks/user.ts | 4 + ts/state/reducer.ts | 2 + ts/state/selectors/accounts.ts | 24 + ts/state/selectors/calling.ts | 33 +- ts/state/selectors/conversations.ts | 116 +- ts/state/selectors/items.ts | 5 + ts/state/selectors/message.ts | 1203 ++++++++++++ ts/state/selectors/search.ts | 4 +- ts/state/selectors/user.ts | 5 + ts/state/smart/CompositionArea.tsx | 14 +- ts/state/smart/ConversationHeader.tsx | 14 +- ts/state/smart/MessageDetail.tsx | 6 + ts/state/smart/Timeline.tsx | 2 + ts/state/types.ts | 4 +- ts/test-both/state/selectors/search_test.ts | 28 +- ts/test-electron/models/messages_test.ts | 22 +- .../state/ducks/conversations_test.ts | 16 +- ts/test-node/types/Contact_test.ts | 27 +- ts/textsecure/MessageReceiver.ts | 12 +- ts/textsecure/WebAPI.ts | 9 +- ts/types/Attachment.ts | 32 +- ts/types/Contact.tsx | 27 +- ts/util/deleteForEveryone.ts | 4 +- ts/util/markConversationRead.ts | 3 +- ts/views/conversation_view.ts | 63 +- ts/window.d.ts | 86 +- 73 files changed, 3394 insertions(+), 2576 deletions(-) delete mode 100644 js/delivery_receipts.js delete mode 100644 js/message_requests.js delete mode 100644 js/read_receipts.js delete mode 100644 js/read_syncs.js delete mode 100644 js/view_syncs.js rename js/modules/attachment_downloads.js => ts/messageModifiers/AttachmentDownloads.ts (74%) create mode 100644 ts/messageModifiers/Deletes.ts create mode 100644 ts/messageModifiers/DeliveryReceipts.ts create mode 100644 ts/messageModifiers/MessageRequests.ts create mode 100644 ts/messageModifiers/Reactions.ts create mode 100644 ts/messageModifiers/ReadReceipts.ts create mode 100644 ts/messageModifiers/ReadSyncs.ts create mode 100644 ts/messageModifiers/ViewSyncs.ts create mode 100644 ts/state/ducks/accounts.ts create mode 100644 ts/state/selectors/accounts.ts create mode 100644 ts/state/selectors/message.ts diff --git a/background.html b/background.html index c01692a93..bdbfdad53 100644 --- a/background.html +++ b/background.html @@ -337,19 +337,10 @@ - - - - - - - - - diff --git a/js/delivery_receipts.js b/js/delivery_receipts.js deleted file mode 100644 index 560ffd3ae..000000000 --- a/js/delivery_receipts.js +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2016-2021 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -/* global - Backbone, - Whisper, - ConversationController, - MessageController, - _ -*/ - -/* eslint-disable more/no-then */ - -// eslint-disable-next-line func-names -(function () { - window.Whisper = window.Whisper || {}; - - Whisper.DeliveryReceipts = new (Backbone.Collection.extend({ - forMessage(conversation, message) { - let recipients; - if (conversation.isPrivate()) { - recipients = [conversation.id]; - } else { - recipients = conversation.getMemberIds(); - } - const receipts = this.filter( - receipt => - receipt.get('timestamp') === message.get('sent_at') && - recipients.indexOf(receipt.get('deliveredTo')) > -1 - ); - this.remove(receipts); - return receipts; - }, - async getTargetMessage(sourceId, messages) { - if (messages.length === 0) { - return null; - } - const message = messages.find( - item => !item.isIncoming() && sourceId === item.get('conversationId') - ); - if (message) { - return MessageController.register(message.id, message); - } - - const groups = await window.Signal.Data.getAllGroupsInvolvingId( - sourceId, - { - ConversationCollection: Whisper.ConversationCollection, - } - ); - - const ids = groups.pluck('id'); - ids.push(sourceId); - - const target = messages.find( - item => - !item.isIncoming() && _.contains(ids, item.get('conversationId')) - ); - if (!target) { - return null; - } - - return MessageController.register(target.id, target); - }, - async onReceipt(receipt) { - try { - const messages = await window.Signal.Data.getMessagesBySentAt( - receipt.get('timestamp'), - { - MessageCollection: Whisper.MessageCollection, - } - ); - - const message = await this.getTargetMessage( - receipt.get('deliveredTo'), - messages - ); - if (!message) { - window.log.info( - 'No message for delivery receipt', - receipt.get('deliveredTo'), - receipt.get('timestamp') - ); - return; - } - - const deliveries = message.get('delivered') || 0; - const deliveredTo = message.get('delivered_to') || []; - const expirationStartTimestamp = message.get( - 'expirationStartTimestamp' - ); - message.set({ - delivered_to: _.union(deliveredTo, [receipt.get('deliveredTo')]), - delivered: deliveries + 1, - expirationStartTimestamp: expirationStartTimestamp || Date.now(), - sent: true, - }); - - window.Signal.Util.queueUpdateMessage(message.attributes); - - // notify frontend listeners - const conversation = ConversationController.get( - message.get('conversationId') - ); - const updateLeftPane = conversation - ? conversation.debouncedUpdateLastMessage - : undefined; - if (updateLeftPane) { - updateLeftPane(); - } - - this.remove(receipt); - } catch (error) { - window.log.error( - 'DeliveryReceipts.onReceipt error:', - error && error.stack ? error.stack : error - ); - } - }, - }))(); -})(); diff --git a/js/message_requests.js b/js/message_requests.js deleted file mode 100644 index 0e902a83d..000000000 --- a/js/message_requests.js +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2020 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -/* global - Backbone, - Whisper, - ConversationController, -*/ - -/* eslint-disable more/no-then */ - -// eslint-disable-next-line func-names -(function () { - window.Whisper = window.Whisper || {}; - Whisper.MessageRequests = new (Backbone.Collection.extend({ - forConversation(conversation) { - if (conversation.get('e164')) { - const syncByE164 = this.findWhere({ - threadE164: conversation.get('e164'), - }); - if (syncByE164) { - window.log.info( - `Found early message request response for E164 ${conversation.idForLogging()}` - ); - this.remove(syncByE164); - return syncByE164; - } - } - - if (conversation.get('uuid')) { - const syncByUuid = this.findWhere({ - threadUuid: conversation.get('uuid'), - }); - if (syncByUuid) { - window.log.info( - `Found early message request response for UUID ${conversation.idForLogging()}` - ); - this.remove(syncByUuid); - return syncByUuid; - } - } - - // V1 Group - if (conversation.get('groupId')) { - const syncByGroupId = this.findWhere({ - groupId: conversation.get('groupId'), - }); - if (syncByGroupId) { - window.log.info( - `Found early message request response for group v1 ID ${conversation.idForLogging()}` - ); - this.remove(syncByGroupId); - return syncByGroupId; - } - } - - // V2 group - if (conversation.get('groupId')) { - const syncByGroupId = this.findWhere({ - groupV2Id: conversation.get('groupId'), - }); - if (syncByGroupId) { - window.log.info( - `Found early message request response for group v2 ID ${conversation.idForLogging()}` - ); - this.remove(syncByGroupId); - return syncByGroupId; - } - } - - return null; - }, - async onResponse(sync) { - try { - const threadE164 = sync.get('threadE164'); - const threadUuid = sync.get('threadUuid'); - const groupId = sync.get('groupId'); - const groupV2Id = sync.get('groupV2Id'); - - let conversation; - - // We multiplex between GV1/GV2 groups here, but we don't kick off migrations - if (groupV2Id) { - conversation = ConversationController.get(groupV2Id); - } - if (!conversation && groupId) { - conversation = ConversationController.get(groupId); - } - if (!conversation && (threadE164 || threadUuid)) { - conversation = ConversationController.get( - ConversationController.ensureContactIds({ - e164: threadE164, - uuid: threadUuid, - }) - ); - } - - if (!conversation) { - window.log( - `Received message request response for unknown conversation: groupv2(${groupV2Id}) group(${groupId}) ${threadUuid} ${threadE164}` - ); - return; - } - - conversation.applyMessageRequestResponse(sync.get('type'), { - fromSync: true, - }); - - this.remove(sync); - } catch (error) { - window.log.error( - 'MessageRequests.onResponse error:', - error && error.stack ? error.stack : error - ); - } - }, - }))(); -})(); diff --git a/js/modules/signal.js b/js/modules/signal.js index 02a0b8d3e..faff94482 100644 --- a/js/modules/signal.js +++ b/js/modules/signal.js @@ -22,7 +22,6 @@ const Settings = require('./settings'); const RemoteConfig = require('../../ts/RemoteConfig'); const Util = require('../../ts/util'); const LinkPreviews = require('./link_previews'); -const AttachmentDownloads = require('./attachment_downloads'); // Components const { @@ -443,7 +442,6 @@ exports.setup = (options = {}) => { }; return { - AttachmentDownloads, Backbone, Components, Crypto, diff --git a/js/read_receipts.js b/js/read_receipts.js deleted file mode 100644 index 0aba0699e..000000000 --- a/js/read_receipts.js +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2016-2021 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -/* global - Whisper, - Backbone, - _, - ConversationController, - MessageController, -*/ - -/* eslint-disable more/no-then */ - -// eslint-disable-next-line func-names -(function () { - window.Whisper = window.Whisper || {}; - Whisper.ReadReceipts = new (Backbone.Collection.extend({ - forMessage(conversation, message) { - if (!message.isOutgoing()) { - return []; - } - let ids = []; - if (conversation.isPrivate()) { - ids = [conversation.id]; - } else { - ids = conversation.getMemberIds(); - } - const receipts = this.filter( - receipt => - receipt.get('timestamp') === message.get('sent_at') && - _.contains(ids, receipt.get('reader')) - ); - if (receipts.length) { - window.log.info('Found early read receipts for message'); - this.remove(receipts); - } - return receipts; - }, - async getTargetMessage(reader, messages) { - if (messages.length === 0) { - return null; - } - const message = messages.find( - item => item.isOutgoing() && reader === item.get('conversationId') - ); - if (message) { - return MessageController.register(message.id, message); - } - - const groups = await window.Signal.Data.getAllGroupsInvolvingId(reader, { - ConversationCollection: Whisper.ConversationCollection, - }); - const ids = groups.pluck('id'); - ids.push(reader); - - const target = messages.find( - item => item.isOutgoing() && _.contains(ids, item.get('conversationId')) - ); - if (!target) { - return null; - } - - return MessageController.register(target.id, target); - }, - async onReceipt(receipt) { - try { - const messages = await window.Signal.Data.getMessagesBySentAt( - receipt.get('timestamp'), - { - MessageCollection: Whisper.MessageCollection, - } - ); - - const message = await this.getTargetMessage( - receipt.get('reader'), - messages - ); - - if (!message) { - window.log.info( - 'No message for read receipt', - receipt.get('reader'), - receipt.get('timestamp') - ); - return; - } - - const readBy = message.get('read_by') || []; - const expirationStartTimestamp = message.get( - 'expirationStartTimestamp' - ); - - readBy.push(receipt.get('reader')); - message.set({ - read_by: readBy, - expirationStartTimestamp: expirationStartTimestamp || Date.now(), - sent: true, - }); - - window.Signal.Util.queueUpdateMessage(message.attributes); - - // notify frontend listeners - const conversation = ConversationController.get( - message.get('conversationId') - ); - const updateLeftPane = conversation - ? conversation.debouncedUpdateLastMessage - : undefined; - if (updateLeftPane) { - updateLeftPane(); - } - - this.remove(receipt); - } catch (error) { - window.log.error( - 'ReadReceipts.onReceipt error:', - error && error.stack ? error.stack : error - ); - } - }, - }))(); -})(); diff --git a/js/read_syncs.js b/js/read_syncs.js deleted file mode 100644 index 299446d62..000000000 --- a/js/read_syncs.js +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2017-2021 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -/* global - Backbone, - Whisper, - MessageController -*/ - -/* eslint-disable more/no-then */ - -// eslint-disable-next-line func-names -(function () { - async function maybeItIsAReactionReadSync(receipt) { - const readReaction = await window.Signal.Data.markReactionAsRead( - receipt.get('senderUuid'), - Number(receipt.get('timestamp')) - ); - - if (!readReaction) { - window.log.info( - 'Nothing found for read sync', - receipt.get('senderId'), - receipt.get('sender'), - receipt.get('senderUuid'), - receipt.get('timestamp') - ); - return; - } - - Whisper.Notifications.removeBy({ - conversationId: readReaction.conversationId, - emoji: readReaction.emoji, - targetAuthorUuid: readReaction.targetAuthorUuid, - targetTimestamp: readReaction.targetTimestamp, - }); - } - - window.Whisper = window.Whisper || {}; - Whisper.ReadSyncs = new (Backbone.Collection.extend({ - forMessage(message) { - const senderId = window.ConversationController.ensureContactIds({ - e164: message.get('source'), - uuid: message.get('sourceUuid'), - }); - const receipt = this.findWhere({ - senderId, - timestamp: message.get('sent_at'), - }); - if (receipt) { - window.log.info('Found early read sync for message'); - this.remove(receipt); - return receipt; - } - - return null; - }, - async onReceipt(receipt) { - try { - const messages = await window.Signal.Data.getMessagesBySentAt( - receipt.get('timestamp'), - { - MessageCollection: Whisper.MessageCollection, - } - ); - - const found = messages.find(item => { - const senderId = window.ConversationController.ensureContactIds({ - e164: item.get('source'), - uuid: item.get('sourceUuid'), - }); - - return item.isIncoming() && senderId === receipt.get('senderId'); - }); - - if (!found) { - await maybeItIsAReactionReadSync(receipt); - return; - } - - Whisper.Notifications.removeBy({ messageId: found.id }); - - const message = MessageController.register(found.id, found); - const readAt = receipt.get('read_at'); - - // 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 (message.isUnread()) { - // 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. - const conversation = message.getConversation(); - if (conversation) { - conversation.onReadMessage(message, readAt); - } - }; - - if (window.startupProcessingQueue) { - const conversation = message.getConversation(); - if (conversation) { - window.startupProcessingQueue.add( - conversation.get('id'), - 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 }); - - const conversation = message.getConversation(); - if (conversation) { - conversation.trigger('expiration-change', message); - } - } - - window.Signal.Util.queueUpdateMessage(message.attributes); - - this.remove(receipt); - } catch (error) { - window.log.error( - 'ReadSyncs.onReceipt error:', - error && error.stack ? error.stack : error - ); - } - }, - }))(); -})(); diff --git a/js/view_syncs.js b/js/view_syncs.js deleted file mode 100644 index 4346234e3..000000000 --- a/js/view_syncs.js +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2019-2020 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -/* global - Backbone, - Whisper, - MessageController -*/ - -/* eslint-disable more/no-then */ - -// eslint-disable-next-line func-names -(function () { - window.Whisper = window.Whisper || {}; - Whisper.ViewSyncs = new (Backbone.Collection.extend({ - forMessage(message) { - const syncBySourceUuid = this.findWhere({ - sourceUuid: message.get('sourceUuid'), - timestamp: message.get('sent_at'), - }); - if (syncBySourceUuid) { - window.log.info('Found early view sync for message'); - this.remove(syncBySourceUuid); - return syncBySourceUuid; - } - - const syncBySource = this.findWhere({ - source: message.get('source'), - timestamp: message.get('sent_at'), - }); - if (syncBySource) { - window.log.info('Found early view sync for message'); - this.remove(syncBySource); - return syncBySource; - } - - return null; - }, - async onSync(sync) { - try { - const messages = await window.Signal.Data.getMessagesBySentAt( - sync.get('timestamp'), - { - MessageCollection: Whisper.MessageCollection, - } - ); - - const found = messages.find(item => { - const itemSourceUuid = item.get('sourceUuid'); - const syncSourceUuid = sync.get('sourceUuid'); - const itemSource = item.get('source'); - const syncSource = sync.get('source'); - - return ( - (itemSourceUuid && - syncSourceUuid && - itemSourceUuid === syncSourceUuid) || - (itemSource && syncSource && itemSource === syncSource) - ); - }); - - const syncSource = sync.get('source'); - const syncSourceUuid = sync.get('sourceUuid'); - const syncTimestamp = sync.get('timestamp'); - const wasMessageFound = Boolean(found); - window.log.info('Receive view sync:', { - syncSource, - syncSourceUuid, - syncTimestamp, - wasMessageFound, - }); - - if (!found) { - return; - } - - const message = MessageController.register(found.id, found); - await message.markViewed({ fromSync: true }); - - this.remove(sync); - } catch (error) { - window.log.error( - 'ViewSyncs.onSync error:', - error && error.stack ? error.stack : error - ); - } - }, - }))(); -})(); diff --git a/preload.js b/preload.js index 2a00f1bd5..37d3ed5b5 100644 --- a/preload.js +++ b/preload.js @@ -489,7 +489,6 @@ try { window.dataURLToBlobSync = require('blueimp-canvas-to-blob'); window.imageToBlurHash = imageToBlurHash; window.emojiData = require('emoji-datasource'); - window.filesize = require('filesize'); window.libphonenumber = require('google-libphonenumber').PhoneNumberUtil.getInstance(); window.libphonenumber.PhoneNumberFormat = require('google-libphonenumber').PhoneNumberFormat; window.loadImage = require('blueimp-load-image'); diff --git a/test/index.html b/test/index.html index 69c621403..b74c8353f 100644 --- a/test/index.html +++ b/test/index.html @@ -347,7 +347,6 @@ - diff --git a/ts/ConversationController.ts b/ts/ConversationController.ts index 9bf97b093..bb028b199 100644 --- a/ts/ConversationController.ts +++ b/ts/ConversationController.ts @@ -715,7 +715,7 @@ export class ConversationController { async getConversationForTargetMessage( targetFromId: string, targetTimestamp: number - ): Promise { + ): Promise { const messages = await getMessagesBySentAt(targetTimestamp, { MessageCollection: window.Whisper.MessageCollection, }); diff --git a/ts/background.ts b/ts/background.ts index f94f47467..3b1298ac8 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -43,7 +43,16 @@ import { isDirectConversation, isGroupV2 } from './util/whatTypeOfConversation'; import { getSendOptions } from './util/getSendOptions'; import { BackOff } from './util/BackOff'; import { AppViewType } from './state/ducks/app'; +import { hasErrors, isIncoming } from './state/selectors/message'; import { actionCreators } from './state/actions'; +import { Deletes } from './messageModifiers/Deletes'; +import { DeliveryReceipts } from './messageModifiers/DeliveryReceipts'; +import { MessageRequests } from './messageModifiers/MessageRequests'; +import { Reactions } from './messageModifiers/Reactions'; +import { ReadReceipts } from './messageModifiers/ReadReceipts'; +import { ReadSyncs } from './messageModifiers/ReadSyncs'; +import { ViewSyncs } from './messageModifiers/ViewSyncs'; +import * as AttachmentDownloads from './messageModifiers/AttachmentDownloads'; const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000; @@ -337,13 +346,15 @@ export async function startApp(): Promise { PASSWORD ); accountManager.addEventListener('registration', () => { + const ourDeviceId = window.textsecure.storage.user.getDeviceId(); const ourNumber = window.textsecure.storage.user.getNumber(); const ourUuid = window.textsecure.storage.user.getUuid(); const user = { - regionCode: window.storage.get('regionCode'), + ourConversationId: window.ConversationController.getOurConversationId(), + ourDeviceId, ourNumber, ourUuid, - ourConversationId: window.ConversationController.getOurConversationId(), + regionCode: window.storage.get('regionCode'), }; window.Whisper.events.trigger('userChanged', user); @@ -548,7 +559,7 @@ export async function startApp(): Promise { shutdown: async () => { window.log.info('background/shutdown'); // Stop background processing - window.Signal.AttachmentDownloads.stop(); + AttachmentDownloads.stop(); if (idleDetector) { idleDetector.stop(); } @@ -957,6 +968,7 @@ export async function startApp(): Promise { // Binding these actions to our redux store and exposing them allows us to update // redux when things change in the backbone world. window.reduxActions = { + accounts: bindActionCreators(actionCreators.accounts, store.dispatch), app: bindActionCreators(actionCreators.app, store.dispatch), audioPlayer: bindActionCreators( actionCreators.audioPlayer, @@ -1659,7 +1671,7 @@ export async function startApp(): Promise { const delivered = message.get('delivered'); const sentAt = message.get('sent_at'); - if (message.hasErrors()) { + if (hasErrors(message.attributes)) { return; } @@ -1887,7 +1899,7 @@ export async function startApp(): Promise { // Clear timer, since we're only called when the timer is expired disconnectTimer = null; - window.Signal.AttachmentDownloads.stop(); + AttachmentDownloads.stop(); if (messageReceiver) { await messageReceiver.close(); } @@ -2063,7 +2075,7 @@ export async function startApp(): Promise { addQueuedEventListener('fetchLatest', onFetchLatestSync); addQueuedEventListener('keys', onKeysSync); - window.Signal.AttachmentDownloads.start({ + AttachmentDownloads.start({ getMessageReceiver: () => messageReceiver, logger: window.log, }); @@ -2859,7 +2871,10 @@ export async function startApp(): Promise { const message = initIncomingMessage(data, messageDescriptor); - if (message.isIncoming() && message.get('unidentifiedDeliveryReceived')) { + if ( + isIncoming(message.attributes) && + message.get('unidentifiedDeliveryReceived') + ) { const sender = message.getContact(); if (!sender) { @@ -2890,7 +2905,7 @@ export async function startApp(): Promise { 'Queuing incoming reaction for', reaction.targetTimestamp ); - const reactionModel = window.Whisper.Reactions.add({ + const reactionModel = Reactions.getSingleton().add({ emoji: reaction.emoji, remove: reaction.remove, targetAuthorUuid: reaction.targetAuthorUuid, @@ -2902,7 +2917,7 @@ export async function startApp(): Promise { }), }); // Note: We do not wait for completion here - window.Whisper.Reactions.onReaction(reactionModel); + Reactions.getSingleton().onReaction(reactionModel); confirm(); return Promise.resolve(); } @@ -2910,7 +2925,7 @@ export async function startApp(): Promise { if (data.message.delete) { const { delete: del } = data.message; window.log.info('Queuing incoming DOE for', del.targetSentTimestamp); - const deleteModel = window.Whisper.Deletes.add({ + const deleteModel = Deletes.getSingleton().add({ targetSentTimestamp: del.targetSentTimestamp, serverTimestamp: data.serverTimestamp, fromId: window.ConversationController.ensureContactIds({ @@ -2919,7 +2934,7 @@ export async function startApp(): Promise { }), }); // Note: We do not wait for completion here - window.Whisper.Deletes.onDelete(deleteModel); + Deletes.getSingleton().onDelete(deleteModel); confirm(); return Promise.resolve(); } @@ -3184,7 +3199,7 @@ export async function startApp(): Promise { } window.log.info('Queuing sent reaction for', reaction.targetTimestamp); - const reactionModel = window.Whisper.Reactions.add({ + const reactionModel = Reactions.getSingleton().add({ emoji: reaction.emoji, remove: reaction.remove, targetAuthorUuid: reaction.targetAuthorUuid, @@ -3194,7 +3209,7 @@ export async function startApp(): Promise { fromSync: true, }); // Note: We do not wait for completion here - window.Whisper.Reactions.onReaction(reactionModel); + Reactions.getSingleton().onReaction(reactionModel); event.confirm(); return Promise.resolve(); @@ -3203,13 +3218,13 @@ export async function startApp(): Promise { if (data.message.delete) { const { delete: del } = data.message; window.log.info('Queuing sent DOE for', del.targetSentTimestamp); - const deleteModel = window.Whisper.Deletes.add({ + const deleteModel = Deletes.getSingleton().add({ targetSentTimestamp: del.targetSentTimestamp, serverTimestamp: del.serverTimestamp, fromId: window.ConversationController.getOurConversationId(), }); // Note: We do not wait for completion here - window.Whisper.Deletes.onDelete(deleteModel); + Deletes.getSingleton().onDelete(deleteModel); confirm(); return Promise.resolve(); } @@ -3299,11 +3314,13 @@ export async function startApp(): Promise { window.Signal.Util.Registration.remove(); const NUMBER_ID_KEY = 'number_id'; + const UUID_ID_KEY = 'uuid_id'; const VERSION_KEY = 'version'; const LAST_PROCESSED_INDEX_KEY = 'attachmentMigration_lastProcessedIndex'; const IS_MIGRATION_COMPLETE_KEY = 'attachmentMigration_isComplete'; const previousNumberId = window.textsecure.storage.get(NUMBER_ID_KEY); + const previousUuidId = window.textsecure.storage.get(UUID_ID_KEY); const lastProcessedIndex = window.textsecure.storage.get( LAST_PROCESSED_INDEX_KEY ); @@ -3327,6 +3344,9 @@ export async function startApp(): Promise { if (previousNumberId !== undefined) { await window.textsecure.storage.put(NUMBER_ID_KEY, previousNumberId); } + if (previousUuidId !== undefined) { + await window.textsecure.storage.put(UUID_ID_KEY, previousUuidId); + } // These two are important to ensure we don't rip through every message // in the database attempting to upgrade it after starting up again. @@ -3782,13 +3802,13 @@ export async function startApp(): Promise { const { source, sourceUuid, timestamp } = ev; window.log.info(`view sync ${source} ${timestamp}`); - const sync = window.Whisper.ViewSyncs.add({ + const sync = ViewSyncs.getSingleton().add({ source, sourceUuid, timestamp, }); - window.Whisper.ViewSyncs.onSync(sync); + ViewSyncs.getSingleton().onSync(sync); } async function onFetchLatestSync(ev: WhatIsThis) { @@ -3855,7 +3875,7 @@ export async function startApp(): Promise { messageRequestResponseType, }); - const sync = window.Whisper.MessageRequests.add({ + const sync = MessageRequests.getSingleton().add({ threadE164, threadUuid, groupId, @@ -3863,7 +3883,7 @@ export async function startApp(): Promise { type: messageRequestResponseType, }); - window.Whisper.MessageRequests.onResponse(sync); + MessageRequests.getSingleton().onResponse(sync); } function onReadReceipt(ev: WhatIsThis) { @@ -3890,14 +3910,14 @@ export async function startApp(): Promise { return; } - const receipt = window.Whisper.ReadReceipts.add({ + const receipt = ReadReceipts.getSingleton().add({ reader, timestamp, - read_at: readAt, + readAt, }); // Note: We do not wait for completion here - window.Whisper.ReadReceipts.onReceipt(receipt); + ReadReceipts.getSingleton().onReceipt(receipt); } function onReadSync(ev: WhatIsThis) { @@ -3918,19 +3938,19 @@ export async function startApp(): Promise { timestamp ); - const receipt = window.Whisper.ReadSyncs.add({ + const receipt = ReadSyncs.getSingleton().add({ senderId, sender, senderUuid, timestamp, - read_at: readAt, + readAt, }); receipt.on('remove', ev.confirm); // Note: Here we wait, because we want read states to be in the database // before we move on. - return window.Whisper.ReadSyncs.onReceipt(receipt); + return ReadSyncs.getSingleton().onReceipt(receipt); } async function onVerified(ev: WhatIsThis) { @@ -4037,13 +4057,13 @@ export async function startApp(): Promise { return; } - const receipt = window.Whisper.DeliveryReceipts.add({ + const receipt = DeliveryReceipts.getSingleton().add({ timestamp, deliveredTo, }); // Note: We don't wait for completion here - window.Whisper.DeliveryReceipts.onReceipt(receipt); + DeliveryReceipts.getSingleton().onReceipt(receipt); } } diff --git a/ts/components/ForwardMessageModal.stories.tsx b/ts/components/ForwardMessageModal.stories.tsx index f9e78bf42..27b633767 100644 --- a/ts/components/ForwardMessageModal.stories.tsx +++ b/ts/components/ForwardMessageModal.stories.tsx @@ -109,6 +109,7 @@ story.add('media attachments', () => { width: 112, url: '/fixtures/kitten-4-112-112.jpg', contentType: IMAGE_JPEG, + path: 'originalPath', }, }), ], diff --git a/ts/components/conversation/AttachmentList.stories.tsx b/ts/components/conversation/AttachmentList.stories.tsx index 105e6b793..17edd30e5 100644 --- a/ts/components/conversation/AttachmentList.stories.tsx +++ b/ts/components/conversation/AttachmentList.stories.tsx @@ -60,6 +60,7 @@ story.add('Multiple Visual Attachments', () => { width: 112, url: '/fixtures/kitten-4-112-112.jpg', contentType: IMAGE_JPEG, + path: 'originalpath', }, }, { @@ -100,6 +101,7 @@ story.add('Multiple with Non-Visual Types', () => { width: 112, url: '/fixtures/kitten-4-112-112.jpg', contentType: IMAGE_JPEG, + path: 'originalpath', }, }, { diff --git a/ts/components/conversation/AttachmentList.tsx b/ts/components/conversation/AttachmentList.tsx index 96b992c0d..1a69e4430 100644 --- a/ts/components/conversation/AttachmentList.tsx +++ b/ts/components/conversation/AttachmentList.tsx @@ -77,7 +77,7 @@ export const AttachmentList = ({ {i18n('stagedImageAttachment', { contact: { avatar: { avatar: { + contentType: IMAGE_GIF, pending: true, }, isProfile: true, diff --git a/ts/components/conversation/EmbeddedContact.stories.tsx b/ts/components/conversation/EmbeddedContact.stories.tsx index 6a4ee9cbc..2ca89013f 100644 --- a/ts/components/conversation/EmbeddedContact.stories.tsx +++ b/ts/components/conversation/EmbeddedContact.stories.tsx @@ -11,6 +11,7 @@ import { EmbeddedContact, Props } from './EmbeddedContact'; import { setup as setupI18n } from '../../../js/modules/i18n'; import enMessages from '../../../_locales/en/messages.json'; import { ContactFormType } from '../../types/Contact'; +import { IMAGE_GIF } from '../../types/MIME'; const i18n = setupI18n('en', enMessages); @@ -36,6 +37,7 @@ const fullContact = { avatar: { avatar: { path: '/fixtures/giphy-GVNvOUpeYmI7e.gif', + contentType: IMAGE_GIF, }, isProfile: true, }, @@ -134,6 +136,7 @@ story.add('Loading Avatar', () => { avatar: { avatar: { pending: true, + contentType: IMAGE_GIF, }, isProfile: true, }, diff --git a/ts/components/conversation/EmbeddedContact.tsx b/ts/components/conversation/EmbeddedContact.tsx index 2d2ab4b19..56e85e860 100644 --- a/ts/components/conversation/EmbeddedContact.tsx +++ b/ts/components/conversation/EmbeddedContact.tsx @@ -23,61 +23,55 @@ export type Props = { onClick?: () => void; }; -export class EmbeddedContact extends React.Component { - public render(): JSX.Element { - const { - contact, - i18n, - isIncoming, - onClick, - tabIndex, - withContentAbove, - withContentBelow, - } = this.props; - const module = 'embedded-contact'; - const direction = isIncoming ? 'incoming' : 'outgoing'; +export const EmbeddedContact: React.FC = (props: Props) => { + const { + contact, + i18n, + isIncoming, + onClick, + tabIndex, + withContentAbove, + withContentBelow, + } = props; + const module = 'embedded-contact'; + const direction = isIncoming ? 'incoming' : 'outgoing'; - return ( - - ); - } -} + onClick(); + } + }} + tabIndex={tabIndex} + > + {renderAvatar({ contact, i18n, size: 52, direction })} +
+ {renderName({ contact, isIncoming, module })} + {renderContactShorthand({ contact, isIncoming, module })} +
+ + ); +}; diff --git a/ts/components/conversation/ImageGrid.stories.tsx b/ts/components/conversation/ImageGrid.stories.tsx index 4d485d272..39f265b81 100644 --- a/ts/components/conversation/ImageGrid.stories.tsx +++ b/ts/components/conversation/ImageGrid.stories.tsx @@ -260,6 +260,7 @@ story.add('Mixed Content Types', () => { width: 112, url: '/fixtures/kitten-4-112-112.jpg', contentType: IMAGE_JPEG, + path: 'originalpath', }, url: '/fixtures/pixabay-Soap-Bubble-7141.mp4', width: 112, diff --git a/ts/components/conversation/Message.stories.tsx b/ts/components/conversation/Message.stories.tsx index 54f529754..342837433 100644 --- a/ts/components/conversation/Message.stories.tsx +++ b/ts/components/conversation/Message.stories.tsx @@ -76,6 +76,7 @@ const createProps = (overrideProps: Partial = {}): Props => ({ canReply: true, canDownload: true, canDeleteForEveryone: overrideProps.canDeleteForEveryone || false, + checkForAccount: action('checkForAccount'), clearSelectedMessage: action('clearSelectedMessage'), collapseMetadata: overrideProps.collapseMetadata, conversationColor: @@ -90,6 +91,7 @@ const createProps = (overrideProps: Partial = {}): Props => ({ disableScroll: overrideProps.disableScroll, direction: overrideProps.direction || 'incoming', displayTapToViewMessage: action('displayTapToViewMessage'), + doubleCheckMissingQuoteReference: action('doubleCheckMissingQuoteReference'), downloadAttachment: action('downloadAttachment'), expirationLength: number('expirationLength', overrideProps.expirationLength || 0) || @@ -114,6 +116,7 @@ const createProps = (overrideProps: Partial = {}): Props => ({ isTapToViewExpired: overrideProps.isTapToViewExpired, kickOffAttachmentDownload: action('kickOffAttachmentDownload'), markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'), + onHeightChange: action('onHeightChange'), openConversation: action('openConversation'), openLink: action('openLink'), previews: overrideProps.previews || [], diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index 398260b1c..2c11fcb25 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -8,7 +8,11 @@ import { drop, groupBy, orderBy, take } from 'lodash'; import { ContextMenu, ContextMenuTrigger, MenuItem } from 'react-contextmenu'; import { Manager, Popper, Reference } from 'react-popper'; -import { ConversationType } from '../../state/ducks/conversations'; +import { + ConversationType, + ConversationTypeType, + InteractionModeType, +} from '../../state/ducks/conversations'; import { Avatar } from '../Avatar'; import { Spinner } from '../Spinner'; import { MessageBody } from './MessageBody'; @@ -80,15 +84,9 @@ export const MessageStatuses = [ ] as const; export type MessageStatusType = typeof MessageStatuses[number]; -export const InteractionModes = ['mouse', 'keyboard'] as const; -export type InteractionModeType = typeof InteractionModes[number]; - export const Directions = ['incoming', 'outgoing'] as const; export type DirectionType = typeof Directions[number]; -export const ConversationTypes = ['direct', 'group'] as const; -export type ConversationTypesType = typeof ConversationTypes[number]; - export type AudioAttachmentProps = { id: string; i18n: LocalizerType; @@ -133,7 +131,7 @@ export type PropsData = { | 'unblurredAvatarPath' >; reducedMotion?: boolean; - conversationType: ConversationTypesType; + conversationType: ConversationTypeType; attachments?: Array; quote?: { conversationColor: ConversationColorType; @@ -185,6 +183,9 @@ export type PropsHousekeeping = { export type PropsActions = { clearSelectedMessage: () => unknown; + doubleCheckMissingQuoteReference: (messageId: string) => unknown; + onHeightChange: () => unknown; + checkForAccount: (identifier: string) => unknown; reactToMessage: ( id: string, @@ -405,18 +406,21 @@ export class Message extends React.Component { } const { expirationLength } = this.props; - if (!expirationLength) { - return; + if (expirationLength) { + const increment = getIncrement(expirationLength); + const checkFrequency = Math.max(EXPIRATION_CHECK_MINIMUM, increment); + + this.checkExpired(); + + this.expirationCheckInterval = setInterval(() => { + this.checkExpired(); + }, checkFrequency); } - const increment = getIncrement(expirationLength); - const checkFrequency = Math.max(EXPIRATION_CHECK_MINIMUM, increment); - - this.checkExpired(); - - this.expirationCheckInterval = setInterval(() => { - this.checkExpired(); - }, checkFrequency); + const { contact, checkForAccount } = this.props; + if (contact && contact.firstNumber && !contact.isNumberOnSignal) { + checkForAccount(contact.firstNumber); + } } public componentWillUnmount(): void { @@ -448,12 +452,31 @@ export class Message extends React.Component { } this.checkExpired(); + this.checkForHeightChange(prevProps); if (canDeleteForEveryone !== prevProps.canDeleteForEveryone) { this.startDeleteForEveryoneTimer(); } } + public checkForHeightChange(prevProps: Props): void { + const { contact, onHeightChange } = this.props; + const willRenderSendMessageButton = Boolean( + contact && contact.firstNumber && contact.isNumberOnSignal + ); + + const { contact: previousContact } = prevProps; + const previouslyRenderedSendMessageButton = Boolean( + previousContact && + previousContact.firstNumber && + previousContact.isNumberOnSignal + ); + + if (willRenderSendMessageButton !== previouslyRenderedSendMessageButton) { + onHeightChange(); + } + } + public startSelectedTimer(): void { const { clearSelectedMessage, interactionMode } = this.props; const { isSelected } = this.state; @@ -1064,7 +1087,9 @@ export class Message extends React.Component { customColor, direction, disableScroll, + doubleCheckMissingQuoteReference, i18n, + id, quote, scrollToQuotedMessage, } = this.props; @@ -1104,6 +1129,9 @@ export class Message extends React.Component { referencedMessageNotFound={referencedMessageNotFound} isFromMe={quote.isFromMe} withContentAbove={withContentAbove} + doubleCheckMissingQuoteReference={() => + doubleCheckMissingQuoteReference(id) + } /> ); } @@ -1127,7 +1155,9 @@ export class Message extends React.Component { conversationType === 'group' && direction === 'incoming'; const withContentBelow = withCaption || !collapseMetadata; - const otherContent = (contact && contact.signalAccount) || withCaption; + const otherContent = + (contact && contact.firstNumber && contact.isNumberOnSignal) || + withCaption; const tabIndex = otherContent ? 0 : -1; return ( @@ -1136,7 +1166,7 @@ export class Message extends React.Component { isIncoming={direction === 'incoming'} i18n={i18n} onClick={() => { - showContactDetail({ contact, signalAccount: contact.signalAccount }); + showContactDetail({ contact, signalAccount: contact.firstNumber }); }} withContentAbove={withContentAbove} withContentBelow={withContentBelow} @@ -1147,18 +1177,18 @@ export class Message extends React.Component { public renderSendMessageButton(): JSX.Element | null { const { contact, openConversation, i18n } = this.props; - if (!contact || !contact.signalAccount) { + if (!contact) { + return null; + } + const { firstNumber, isNumberOnSignal } = contact; + if (!firstNumber || !isNumberOnSignal) { return null; } return (