From 762cb687219480673a4c8d221fde3fb75c1b8cef Mon Sep 17 00:00:00 2001 From: lilia Date: Tue, 15 Mar 2016 13:09:06 -0700 Subject: [PATCH] Serialize sending and adding messages to a convo Previously, if a message was sent in between the receive time of an incoming message and the time it is actually added to the conversation's message collection (which only occurs later after several async callbacks), the incoming message would be inserted not-at-the-end of the collection since it is ordered by receive time. This tricked the front end into assuming the message was an older message instead of a new one. Fixes #490 // FREEBIE --- js/models/conversations.js | 69 +++++++++------ js/models/messages.js | 167 +++++++++++++++++++------------------ 2 files changed, 128 insertions(+), 108 deletions(-) diff --git a/js/models/conversations.js b/js/models/conversations.js index 8641dfd4..fc53d7f8 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -112,36 +112,51 @@ this.set({tokens: tokens}); }, + queueJob: function(callback) { + var previous = this.pending || Promise.resolve(); + var current = this.pending = previous.then(callback, callback); + + current.then(function() { + if (this.pending === current) { + delete this.pending; + } + }.bind(this)); + + return current; + }, + sendMessage: function(body, attachments) { - var now = Date.now(); - var message = this.messageCollection.add({ - body : body, - conversationId : this.id, - type : 'outgoing', - attachments : attachments, - sent_at : now, - received_at : now - }); - if (this.isPrivate()) { - message.set({destination: this.id}); - } - message.save(); + this.queueJob(function() { + var now = Date.now(); + var message = this.messageCollection.add({ + body : body, + conversationId : this.id, + type : 'outgoing', + attachments : attachments, + sent_at : now, + received_at : now + }); + if (this.isPrivate()) { + message.set({destination: this.id}); + } + message.save(); - this.save({ - unreadCount : 0, - active_at : now, - timestamp : now, - lastMessage : message.getNotificationText() - }); + this.save({ + unreadCount : 0, + active_at : now, + timestamp : now, + lastMessage : message.getNotificationText() + }); - var sendFunc; - if (this.get('type') == 'private') { - sendFunc = textsecure.messaging.sendMessageToNumber; - } - else { - sendFunc = textsecure.messaging.sendMessageToGroup; - } - message.send(sendFunc(this.get('id'), body, attachments, now)); + var sendFunc; + if (this.get('type') == 'private') { + sendFunc = textsecure.messaging.sendMessageToNumber; + } + else { + sendFunc = textsecure.messaging.sendMessageToGroup; + } + message.send(sendFunc(this.get('id'), body, attachments, now)); + }.bind(this)); }, isSearchable: function() { diff --git a/js/models/messages.js b/js/models/messages.js index deb10655..413c204c 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -267,92 +267,97 @@ conversationId = dataMessage.group.id; } var conversation = ConversationController.create({id: conversationId}); - conversation.fetch().always(function() { - var now = new Date().getTime(); - var attributes = { type: 'private' }; - if (dataMessage.group) { - var group_update = null; - attributes = { - type: 'group', - groupId: dataMessage.group.id, - }; - if (dataMessage.group.type === textsecure.protobuf.GroupContext.Type.UPDATE) { - attributes = { - type : 'group', - groupId : dataMessage.group.id, - name : dataMessage.group.name, - avatar : dataMessage.group.avatar, - members : dataMessage.group.members, - }; - group_update = conversation.changedAttributes(_.pick(dataMessage.group, 'name', 'avatar')) || {}; - var difference = _.difference(dataMessage.group.members, conversation.get('members')); - if (difference.length > 0) { - group_update.joined = difference; - } - } - else if (dataMessage.group.type === textsecure.protobuf.GroupContext.Type.QUIT) { - if (source == textsecure.storage.user.getNumber()) { - group_update = { left: "You" }; - } else { - group_update = { left: source }; - } - attributes.members = _.without(conversation.get('members'), source); - } + conversation.queueJob(function() { + return new Promise(function(resolve) { + conversation.fetch().always(function() { + var now = new Date().getTime(); + var attributes = { type: 'private' }; + if (dataMessage.group) { + var group_update = null; + attributes = { + type: 'group', + groupId: dataMessage.group.id, + }; + if (dataMessage.group.type === textsecure.protobuf.GroupContext.Type.UPDATE) { + attributes = { + type : 'group', + groupId : dataMessage.group.id, + name : dataMessage.group.name, + avatar : dataMessage.group.avatar, + members : dataMessage.group.members, + }; + group_update = conversation.changedAttributes(_.pick(dataMessage.group, 'name', 'avatar')) || {}; + var difference = _.difference(dataMessage.group.members, conversation.get('members')); + if (difference.length > 0) { + group_update.joined = difference; + } + } + else if (dataMessage.group.type === textsecure.protobuf.GroupContext.Type.QUIT) { + if (source == textsecure.storage.user.getNumber()) { + group_update = { left: "You" }; + } else { + group_update = { left: source }; + } + attributes.members = _.without(conversation.get('members'), source); + } + + if (group_update !== null) { + message.set({group_update: group_update}); + } + } + if (type === 'outgoing') { + // lazy hack - check for receipts that arrived early. + var recipients; + if (dataMessage.group && dataMessage.group.id) { // group sync + recipients = conversation.get('members') || []; + } else { + recipients = [ conversation.id ]; + } + window.receipts.filter(function(receipt) { + return (receipt.get('timestamp') === timestamp) && + (recipients.indexOf(receipt.get('source')) > -1); + }).forEach(function(receipt) { + window.receipts.remove(receipt); + message.set({ + delivered: (message.get('delivered') || 0) + 1 + }); + }); + } + attributes.active_at = now; + if (type === 'incoming') { + attributes.unreadCount = conversation.get('unreadCount') + 1; + } + conversation.set(attributes); - if (group_update !== null) { - message.set({group_update: group_update}); - } - } - if (type === 'outgoing') { - // lazy hack - check for receipts that arrived early. - var recipients; - if (dataMessage.group && dataMessage.group.id) { // group sync - recipients = conversation.get('members') || []; - } else { - recipients = [ conversation.id ]; - } - window.receipts.filter(function(receipt) { - return (receipt.get('timestamp') === timestamp) && - (recipients.indexOf(receipt.get('source')) > -1); - }).forEach(function(receipt) { - window.receipts.remove(receipt); message.set({ - delivered: (message.get('delivered') || 0) + 1 + body : dataMessage.body, + conversationId : conversation.id, + attachments : dataMessage.attachments, + decrypted_at : now, + flags : dataMessage.flags, + errors : [] }); - }); - } - attributes.active_at = now; - if (type === 'incoming') { - attributes.unreadCount = conversation.get('unreadCount') + 1; - } - conversation.set(attributes); - message.set({ - body : dataMessage.body, - conversationId : conversation.id, - attachments : dataMessage.attachments, - decrypted_at : now, - flags : dataMessage.flags, - errors : [] - }); + var conversation_timestamp = conversation.get('timestamp'); + if (!conversation_timestamp || message.get('sent_at') > conversation_timestamp) { + conversation.set({ + timestamp: message.get('sent_at'), + lastMessage: message.getNotificationText() + }); + } + else if (!conversation.get('lastMessage')) { + conversation.set({ + lastMessage: message.getNotificationText() + }); + } - var conversation_timestamp = conversation.get('timestamp'); - if (!conversation_timestamp || message.get('sent_at') > conversation_timestamp) { - conversation.set({ - timestamp: message.get('sent_at'), - lastMessage: message.getNotificationText() - }); - } - else if (!conversation.get('lastMessage')) { - conversation.set({ - lastMessage: message.getNotificationText() - }); - } - - message.save().then(function() { - conversation.save().then(function() { - conversation.trigger('newmessage', message); - conversation.notify(message); + message.save().then(function() { + conversation.save().then(function() { + conversation.trigger('newmessage', message); + conversation.notify(message); + resolve(); + }); + }); }); }); });