From 676fe8b5c5346f91cf0797d2d8de2a587d740b32 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 15 Nov 2018 13:58:21 +1100 Subject: [PATCH 1/7] Fixed new messages not showing when another message in the conversation is calculating its PoW --- js/models/conversations.js | 52 ++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/js/models/conversations.js b/js/models/conversations.js index 34384145a..167057077 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -909,6 +909,26 @@ return current; }, + queueMessageSend(callback) { + const previous = this.pendingSend || Promise.resolve(); + + const taskWithTimeout = textsecure.createTaskWithTimeout( + callback, + `conversation ${this.idForLogging()}` + ); + + this.pendingSend = previous.then(taskWithTimeout, taskWithTimeout); + const current = this.pendingSend; + + current.then(() => { + if (this.pendingSend === current) { + delete this.pendingSend; + } + }); + + return current; + }, + getRecipients() { if (this.isPrivate()) { return [this.id]; @@ -1078,20 +1098,26 @@ ); const options = this.getSendOptions(); - return message.send( - this.wrapSend( - sendFunction( - destination, - body, - attachmentsWithData, - quote, - now, - expireTimer, - profileKey, - options + + // Add the message sending on another queue so that our UI doesn't get blocked + this.queueMessageSend(async () => { + return message.send( + this.wrapSend( + sendFunction( + destination, + body, + attachmentsWithData, + quote, + now, + expireTimer, + profileKey, + options + ) ) - ) - ); + ); + }); + + return true; }); }, async updateTextInputState() { From 3789e943428a2c776c5d01f618654aaadb1fbbe8 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 15 Nov 2018 12:36:54 +1100 Subject: [PATCH 2/7] Added PoW loading icon. --- images/pow.svg | 6 ++++++ stylesheets/_ios.scss | 8 ++++++++ stylesheets/_modules.scss | 5 +++++ stylesheets/_theme_dark.scss | 4 ++++ 4 files changed, 23 insertions(+) create mode 100644 images/pow.svg diff --git a/images/pow.svg b/images/pow.svg new file mode 100644 index 000000000..830b9ada9 --- /dev/null +++ b/images/pow.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/stylesheets/_ios.scss b/stylesheets/_ios.scss index e10e76b25..6e895cb27 100644 --- a/stylesheets/_ios.scss +++ b/stylesheets/_ios.scss @@ -48,6 +48,10 @@ @include color-svg('../images/sending.svg', $color-white); } + .module-message__metadata__status-icon--pow { + @include color-svg('../images/pow.svg', $color-white); + } + .module-message__metadata__status-icon--sent { @include color-svg('../images/check-circle-outline.svg', $color-white-08); } @@ -153,6 +157,10 @@ @include color-svg('../images/sending.svg', $color-white); } + .module-message__metadata__status-icon--pow { + @include color-svg('../images/pow.svg', $color-white); + } + .module-message__metadata__status-icon--sent { @include color-svg('../images/check-circle-outline.svg', $color-white-08); } diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index 06796c453..5430952bd 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -535,6 +535,11 @@ animation: module-message__metdata__status-icon--spinning 4s linear infinite; } +.module-message__metadata__status-icon--pow { + @include color-svg('../images/pow.svg', $color-gray-60); + animation: module-message__metdata__status-icon--spinning 4s linear infinite; +} + @keyframes module-message__metdata__status-icon--spinning { 100% { -webkit-transform: rotate(360deg); diff --git a/stylesheets/_theme_dark.scss b/stylesheets/_theme_dark.scss index c29bb6312..190c077a9 100644 --- a/stylesheets/_theme_dark.scss +++ b/stylesheets/_theme_dark.scss @@ -770,6 +770,10 @@ body.dark-theme { @include color-svg('../images/sending.svg', $color-white-08); } + .module-message__metadata__status-icon--pow { + @include color-svg('../images/pow.svg', $color-white-08); + } + .module-message__metadata__status-icon--sent { @include color-svg('../images/check-circle-outline.svg', $color-white-08); } From ece266fffdafde9dfa023cf8e8b7314f11e2475b Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 15 Nov 2018 13:46:53 +1100 Subject: [PATCH 3/7] Added showing pow icon. --- js/background.js | 7 +++++++ js/models/conversations.js | 9 +++++++++ js/models/messages.js | 13 +++++++++++++ js/modules/loki_message_api.js | 6 +++++- libtextsecure/outgoing_message.js | 2 +- 5 files changed, 35 insertions(+), 2 deletions(-) diff --git a/js/background.js b/js/background.js index 99920f195..af12a9f45 100644 --- a/js/background.js +++ b/js/background.js @@ -562,6 +562,13 @@ appView.showFriendRequest(friendRequest); } }); + + Whisper.events.on('calculatingPoW', ({ pubKey, timestamp}) => { + try { + const conversation = ConversationController.get(pubKey); + conversation.onCalculatingPoW(pubKey, timestamp); + } catch (e) {} + }); } window.getSyncRequest = () => diff --git a/js/models/conversations.js b/js/models/conversations.js index 167057077..1d675e4fb 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -198,6 +198,15 @@ await this.inProgressFetch; removeMessage(); }, + async onCalculatingPoW(pubKey, timestamp) { + if (this.id !== pubKey) return; + + // Go through our messages and find the one that we need to update + const messages = this.messageCollection.models.filter(m => m.get('sent_at') === timestamp); + for (const message of messages) { + await message.setCalculatingPoW(); + } + }, addSingleMessage(message, setToExpire = true) { const model = this.messageCollection.add(message, { merge: true }); diff --git a/js/models/messages.js b/js/models/messages.js index e82b02d75..3b58ca934 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -93,6 +93,7 @@ return { timestamp: new Date().getTime(), attachments: [], + pow: false, }; }, validate(attributes) { @@ -439,6 +440,8 @@ if (sent || sentTo.length > 0) { return 'sent'; } + const calculatingPoW = this.get('calculatingPoW'); + if (calculatingPoW) return 'pow'; return 'sending'; }, @@ -930,7 +933,17 @@ return null; }, + async setCalculatingPoW() { + if (this.calculatingPoW) return; + this.set({ + calculatingPoW: true, + }); + + await window.Signal.Data.saveMessage(this.attributes, { + Message: Whisper.Message, + }); + }, send(promise) { this.trigger('pending'); return promise diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index af835e3cd..2224b3f20 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -46,7 +46,7 @@ class LokiServer { }); } - async sendMessage(pubKey, data, ttl) { + async sendMessage(pubKey, data, messageTimeStamp, ttl) { const data64 = dcodeIO.ByteBuffer.wrap(data).toString('base64'); // Hardcoded to use a single node/server for now const currentNode = this.nodes[0]; @@ -55,6 +55,10 @@ class LokiServer { // Nonce is returned as a base64 string to include in header let nonce; try { + window.Whisper.events.trigger('calculatingPoW', { + pubKey, + timestamp: messageTimeStamp, + }); nonce = await getPoWNonce(timestamp, ttl, pubKey, data64); } catch (err) { // Something went horribly wrong diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index 52b5129c8..fb8c949f2 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -177,7 +177,7 @@ OutgoingMessage.prototype = { async transmitMessage(number, data, timestamp, ttl = 24 * 60 * 60) { const pubKey = number; try { - const result = await this.lokiserver.sendMessage(pubKey, data, ttl); + const result = await this.lokiserver.sendMessage(pubKey, data, timestamp, ttl); return result; } catch (e) { if (e.name === 'HTTPError' && (e.code !== 409 && e.code !== 410)) { From 4148628e70fcc56e158f37e0d99f87eb4fd3e090 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 16 Nov 2018 10:03:43 +1100 Subject: [PATCH 4/7] Remove any unsent messages when app is started. --- app/sql.js | 15 +++++++++++++++ js/background.js | 13 ++++++++----- js/models/messages.js | 1 + js/modules/data.js | 7 +++++++ 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/app/sql.js b/app/sql.js index e87df9130..79d51f3b5 100644 --- a/app/sql.js +++ b/app/sql.js @@ -96,6 +96,7 @@ module.exports = { getMessageById, getAllMessages, getAllMessageIds, + getAllUnsentMessages, getMessagesBySentAt, getExpiredMessages, getOutgoingWithoutExpiresAt, @@ -203,6 +204,7 @@ async function updateToSchemaVersion1(currentVersion, instance) { unread INTEGER, expires_at INTEGER, + sent BOOLEAN, sent_at INTEGER, schemaVersion INTEGER, conversationId STRING, @@ -1115,6 +1117,7 @@ async function saveMessage(data, { forceSave } = {}) { received_at, schemaVersion, // eslint-disable-next-line camelcase + sent, sent_at, source, sourceDevice, @@ -1137,6 +1140,7 @@ async function saveMessage(data, { forceSave } = {}) { $hasVisualMediaAttachments: hasVisualMediaAttachments, $received_at: received_at, $schemaVersion: schemaVersion, + $sent: sent, $sent_at: sent_at, $source: source, $sourceDevice: sourceDevice, @@ -1158,6 +1162,7 @@ async function saveMessage(data, { forceSave } = {}) { id = $id, received_at = $received_at, schemaVersion = $schemaVersion, + sent = $sent, sent_at = $sent_at, source = $source, sourceDevice = $sourceDevice, @@ -1189,6 +1194,7 @@ async function saveMessage(data, { forceSave } = {}) { hasVisualMediaAttachments, received_at, schemaVersion, + sent, sent_at, source, sourceDevice, @@ -1207,6 +1213,7 @@ async function saveMessage(data, { forceSave } = {}) { $hasVisualMediaAttachments, $received_at, $schemaVersion, + $sent, $sent_at, $source, $sourceDevice, @@ -1293,6 +1300,14 @@ async function getMessageBySender({ source, sourceDevice, sent_at }) { return map(rows, row => jsonToObject(row.json)); } +async function getAllUnsentMessages() { + const rows = await db.all(` + SELECT json FROM messages WHERE NOT sent + ORDER BY sent_at DESC; + `); + return map(rows, row => jsonToObject(row.json)); +} + async function getUnreadByConversation(conversationId) { const rows = await db.all( `SELECT json FROM messages WHERE diff --git a/js/background.js b/js/background.js index af12a9f45..1c1afbc9d 100644 --- a/js/background.js +++ b/js/background.js @@ -318,11 +318,14 @@ Views.Initialization.setMessage(window.i18n('optimizingApplication')); window.log.info('Cleanup: starting...'); - const messagesForCleanup = await window.Signal.Data.getOutgoingWithoutExpiresAt( - { - MessageCollection: Whisper.MessageCollection, - } - ); + const results = await Promise.all([ + window.Signal.Data.getOutgoingWithoutExpiresAt({ MessageCollection: Whisper.MessageCollection }), + window.Signal.Data.getAllUnsentMessages({ MessageCollection: Whisper.MessageCollection }), + ]); + + // Combine the models + const messagesForCleanup = results.reduce((array, current) => array.concat(current.toArray()), []); + window.log.info( `Cleanup: Found ${messagesForCleanup.length} messages for cleanup` ); diff --git a/js/models/messages.js b/js/models/messages.js index 3b58ca934..247d4fe32 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -94,6 +94,7 @@ timestamp: new Date().getTime(), attachments: [], pow: false, + sent: false, }; }, validate(attributes) { diff --git a/js/modules/data.js b/js/modules/data.js index 5d4bfac86..0123eafe4 100644 --- a/js/modules/data.js +++ b/js/modules/data.js @@ -133,6 +133,7 @@ module.exports = { getMessageBySender, getMessageById, getAllMessages, + getAllUnsentMessages, getAllMessageIds, getMessagesBySentAt, getExpiredMessages, @@ -810,6 +811,12 @@ async function getAllMessages({ MessageCollection }) { return new MessageCollection(encoded); } +async function getAllUnsentMessages({ MessageCollection }) { + const messages = await channels.getAllUnsentMessages(); + const encoded = messages.map(m => keysToArrayBuffer(MESSAGE_PRE_KEYS, m)); + return new MessageCollection(encoded); +} + async function getAllMessageIds() { const ids = await channels.getAllMessageIds(); return ids; From 3943cbbc6e81c1799faf3679ec3300f50163435b Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 16 Nov 2018 11:33:26 +1100 Subject: [PATCH 5/7] Fix linting error. --- js/models/conversations.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/js/models/conversations.js b/js/models/conversations.js index 1d675e4fb..0a7d17081 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -203,9 +203,7 @@ // Go through our messages and find the one that we need to update const messages = this.messageCollection.models.filter(m => m.get('sent_at') === timestamp); - for (const message of messages) { - await message.setCalculatingPoW(); - } + await Promise.all(messages.map(m => m.setCalculatingPoW())) }, addSingleMessage(message, setToExpire = true) { From a9e3a64888a2d29ae5a6afe82b545b7c688d6fd4 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 16 Nov 2018 11:41:48 +1100 Subject: [PATCH 6/7] removed unused default param. --- js/models/messages.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/models/messages.js b/js/models/messages.js index 247d4fe32..996d6c5a1 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -93,7 +93,6 @@ return { timestamp: new Date().getTime(), attachments: [], - pow: false, sent: false, }; }, From c77f9967092d79ac32395ada46ccd786e19db5e2 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 16 Nov 2018 11:45:16 +1100 Subject: [PATCH 7/7] Forgot a ; --- js/models/conversations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/models/conversations.js b/js/models/conversations.js index 0a7d17081..83dbe8db3 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -203,7 +203,7 @@ // Go through our messages and find the one that we need to update const messages = this.messageCollection.models.filter(m => m.get('sent_at') === timestamp); - await Promise.all(messages.map(m => m.setCalculatingPoW())) + await Promise.all(messages.map(m => m.setCalculatingPoW())); }, addSingleMessage(message, setToExpire = true) {