From 470346c9c4bb1b6a4bed5110136320e989974c14 Mon Sep 17 00:00:00 2001 From: lilia Date: Sun, 16 Nov 2014 13:19:51 -0800 Subject: [PATCH] Save incoming messages and pass to frontend asynchronously After a message is saved asynchronsly, fire an event and pass the message attributes to frontend listeners via the chrome-runtime API. This behavior is similar to the 'storage' event fired by localStorage. --- js/background.js | 4 +- js/chromium.js | 13 +++++- js/models/conversations.js | 14 +++--- js/views/conversation_view.js | 77 ++++++++++++++++--------------- js/views/message_list_view.js | 2 +- test/_test.js | 20 ++++++++ test/index.html | 5 +- test/models/conversations_test.js | 40 ++-------------- test/test.js | 20 ++++++++ 9 files changed, 110 insertions(+), 85 deletions(-) diff --git a/js/background.js b/js/background.js index cb533395e6c..8ac625d363f 100644 --- a/js/background.js +++ b/js/background.js @@ -25,7 +25,9 @@ if (textsecure.registration.isDone()) { var conversations = new Whisper.ConversationCollection(); textsecure.subscribeToPush(function(message) { - conversations.addIncomingMessage(message); + conversations.addIncomingMessage(message).then(function(message) { + extension.trigger('message', message); + }); console.log("Got message from " + message.pushMessage.source + "." + message.pushMessage.sourceDevice + ': "' + getString(message.message.body) + '"'); var newUnreadCount = textsecure.storage.getUnencrypted("unreadCount", 0) + 1; diff --git a/js/chromium.js b/js/chromium.js index d03d08eb7fc..42ac9738cb9 100644 --- a/js/chromium.js +++ b/js/chromium.js @@ -16,6 +16,7 @@ */ (function () { 'use strict'; + // Browser specific functions for Chrom* window.extension = window.extension || {}; window.extension.navigator = (function () { @@ -33,7 +34,17 @@ return self; }()); - // Random shared utilities that are used only by chromium things + window.extension.trigger = function (name, object) { + chrome.runtime.sendMessage(null, { name: name, data: object }); + }; + + window.extension.onMessage = function (name, callback) { + chrome.runtime.onMessage.addListener(function(e) { + if (e.name === name) { + callback(e.data); + } + }); + }; window.textsecure = window.textsecure || {}; window.textsecure.registration = { diff --git a/js/models/conversations.js b/js/models/conversations.js index 70d3cbd5961..b8317312230 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -86,7 +86,7 @@ receiveMessage: function(decrypted) { var conversation = this; - encodeAttachments(decrypted.message.attachments).then(function(base64_attachments) { + return encodeAttachments(decrypted.message.attachments).then(function(base64_attachments) { var timestamp = decrypted.pushMessage.timestamp.toNumber(); var m = this.messages().add({ body: decrypted.message.body, @@ -97,18 +97,20 @@ type: 'incoming', sender: decrypted.pushMessage.source }); - m.save(); if (timestamp > this.get('timestamp')) { this.set('timestamp', timestamp); } this.save({unreadCount: this.get('unreadCount') + 1, active: true}); - return m; + + return new Promise(function (resolve) { m.save().then(resolve) }); }.bind(this)); }, - fetch: function() { - return this.messageCollection.fetch({conditions: {conversationId: this.id }}); + fetch: function(options) { + options = options || {}; + options.conditions = {conversationId: this.id }; + return this.messageCollection.fetch(options); }, messages: function() { @@ -171,7 +173,7 @@ }; } var conversation = this.add(attributes, {merge: true}); - conversation.receiveMessage(decrypted); + return conversation.receiveMessage(decrypted); }, destroyAll: function () { diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 8cb3fde8018..92ff5ec2460 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -18,47 +18,50 @@ var Whisper = Whisper || {}; (function () { 'use strict'; - Whisper.ConversationView = Backbone.View.extend({ - className: 'conversation', - initialize: function() { - this.listenTo(this.model, 'destroy', this.stopListening); // auto update - this.template = $('#conversation').html(); - Mustache.parse(this.template); - this.$el.html(Mustache.render(this.template)); + Whisper.ConversationView = Backbone.View.extend({ + className: 'conversation', + initialize: function() { + this.listenTo(this.model, 'destroy', this.stopListening); // auto update + this.template = $('#conversation').html(); + Mustache.parse(this.template); + this.$el.html(Mustache.render(this.template)); - this.view = new Whisper.MessageListView({collection: this.model.messages()}); + this.fileInput = new Whisper.FileInputView({ + el: this.$el.find('.attachments') + }); - this.fileInput = new Whisper.FileInputView({el: this.$el.find('.attachments')}); + this.view = new Whisper.MessageListView({ + collection: this.model.messages() + }); + this.$el.find('.discussion-container').append(this.view.el); - this.model.messages().fetch({reset: true}); - this.$el.find('.discussion-container').append(this.view.el); - window.addEventListener('storage', (function(){ - this.model.messages().fetch(); - }).bind(this)); - }, - events: { - 'submit .send': 'sendMessage', - 'close': 'remove' - }, + this.model.fetch({reset: true}); + extension.onMessage('message', this.model.fetch.bind(this.model)); + }, - sendMessage: function(e) { - e.preventDefault(); - var input = this.$el.find('.send input'); - var message = input.val(); - var convo = this.model; + events: { + 'submit .send': 'sendMessage', + 'close': 'remove' + }, - if (message.length > 0 || this.fileInput.hasFiles()) { - this.fileInput.getFiles().then(function(attachments) { - convo.sendMessage(message, attachments); - }); - input.val(""); - } - }, + sendMessage: function(e) { + e.preventDefault(); + var input = this.$el.find('.send input'); + var message = input.val(); + var convo = this.model; - render: function() { - Whisper.Layout.setContent(this.$el.show()); - this.view.scrollToBottom(); - return this; - } - }); + if (message.length > 0 || this.fileInput.hasFiles()) { + this.fileInput.getFiles().then(function(attachments) { + convo.sendMessage(message, attachments); + }); + input.val(""); + } + }, + + render: function() { + Whisper.Layout.setContent(this.$el.show()); + this.view.scrollToBottom(); + return this; + } + }); })(); diff --git a/js/views/message_list_view.js b/js/views/message_list_view.js index 8e754ee3cb1..18f727918f7 100644 --- a/js/views/message_list_view.js +++ b/js/views/message_list_view.js @@ -18,7 +18,7 @@ var Whisper = Whisper || {}; this.collection.each(function(model) { var view = new this.itemView({model: model}); this.$el.prepend(view.render().el); - }); + }, this); }, }); })(); diff --git a/test/_test.js b/test/_test.js index 6c8233aeab3..a7386e31642 100644 --- a/test/_test.js +++ b/test/_test.js @@ -83,3 +83,23 @@ function hexToArrayBuffer(str) { array[i] = parseInt(str.substr(i*2, 2), 16); return ret; }; + +function deleteDatabase(done) { + indexedDB.deleteDatabase('test').then(done); +}; + +function clearDatabase(done) { + var convos = new Whisper.ConversationCollection(); + return convos.fetch().then(function() { + convos.destroyAll().then(function() { + var messages = new Whisper.MessageCollection(); + return messages.fetch().then(function() { + messages.destroyAll().then(function() { + if (done) { + done(); + } + }); + }); + }); + }); +} diff --git a/test/index.html b/test/index.html index 9d07a348e8d..715a2ae5816 100644 --- a/test/index.html +++ b/test/index.html @@ -135,8 +135,8 @@ - - + + @@ -162,6 +162,7 @@ + diff --git a/test/models/conversations_test.js b/test/models/conversations_test.js index c2e55e6062a..d5f3c175b47 100644 --- a/test/models/conversations_test.js +++ b/test/models/conversations_test.js @@ -16,20 +16,6 @@ (function () { 'use strict'; - function clear(done) { - var convos = new Whisper.ConversationCollection(); - return convos.fetch().then(function() { - convos.destroyAll().then(function() { - var messages = new Whisper.MessageCollection(); - return messages.fetch().then(function() { - messages.destroyAll().then(function() { - done(); - }); - }); - }); - }); - } - var attributes = { type: 'outgoing', body: 'hi', conversationId: 'foo', @@ -37,8 +23,8 @@ timestamp: new Date().getTime() }; describe('ConversationCollection', function() { - before(clear); - after(clear); + before(clearDatabase); + after(clearDatabase); it('adds without saving', function() { var convos = new Whisper.ConversationCollection(); @@ -107,7 +93,7 @@ message.save().then(done) }); }); - after(clear); + after(clearDatabase); it('contains its own messages', function (done) { var convo = new Whisper.ConversationCollection().add({id: 'foobar'}); @@ -124,26 +110,6 @@ done(); }); }); - - it('has most recent messages first', function(done) { - var convo = new Whisper.ConversationCollection().add({id: 'barfoo'}); - convo.messages().add({ - body: 'first message', - conversationId: convo.id, - timestamp: new Date().getTime() - 5000 - }).save().then(function() { - convo.messages().add({ - body: 'second message', - conversationId: convo.id - }).save().then(function() { - convo.fetch().then(function() { - assert.strictEqual(convo.messages().at(0).get('body'), 'second message'); - assert.strictEqual(convo.messages().at(1).get('body'), 'first message'); - done(); - }); - }); - }); - }); }); })();; diff --git a/test/test.js b/test/test.js index 12ff306f0d8..fac62ce7c0d 100644 --- a/test/test.js +++ b/test/test.js @@ -10957,3 +10957,23 @@ function hexToArrayBuffer(str) { array[i] = parseInt(str.substr(i*2, 2), 16); return ret; }; + +function deleteDatabase(done) { + indexedDB.deleteDatabase('test').then(done); +}; + +function clearDatabase(done) { + var convos = new Whisper.ConversationCollection(); + return convos.fetch().then(function() { + convos.destroyAll().then(function() { + var messages = new Whisper.MessageCollection(); + return messages.fetch().then(function() { + messages.destroyAll().then(function() { + if (done) { + done(); + } + }); + }); + }); + }); +}