diff --git a/background.html b/background.html index d394b459..00adb58e 100644 --- a/background.html +++ b/background.html @@ -480,6 +480,7 @@ + diff --git a/js/background.js b/js/background.js index 2eb48c04..c8f34aeb 100644 --- a/js/background.js +++ b/js/background.js @@ -169,7 +169,8 @@ received_at : now, conversationId : data.destination, type : 'outgoing', - sent : true + sent : true, + expirationStartTimestamp: data.expirationStartTimestamp, }); message.handleDataMessage(data.message); diff --git a/js/expiring_messages.js b/js/expiring_messages.js new file mode 100644 index 00000000..40fa5565 --- /dev/null +++ b/js/expiring_messages.js @@ -0,0 +1,14 @@ + +/* + * vim: ts=4:sw=4:expandtab + */ +;(function() { + 'use strict'; + window.Whisper = window.Whisper || {}; + Whisper.ExpiringMessages = new (Whisper.MessageCollection.extend({ + initialize: function() { + this.on('expired', this.remove); + this.fetchExpiring(); + } + }))(); +})(); diff --git a/js/models/conversations.js b/js/models/conversations.js index 909320d8..69e586c7 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -230,17 +230,20 @@ this.getUnread().then(function(unreadMessages) { var read = unreadMessages.map(function(m) { + if (this.messageCollection.get(m.id)) { + m = this.messageCollection.get(m.id); + } m.markRead(); return { sender : m.get('source'), timestamp : m.get('sent_at') }; - }); + }.bind(this)); if (read.length > 0) { console.log('Sending', read.length, 'read receipts'); textsecure.messaging.syncReadMessages(read); } - }); + }.bind(this)); } }, diff --git a/js/models/messages.js b/js/models/messages.js index ade716c4..aaa83e12 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -11,6 +11,9 @@ initialize: function() { this.on('change:attachments', this.updateImageUrl); this.on('destroy', this.revokeImageUrl); + this.on('change:expirationStartTimestamp', this.setToExpire); + this.on('change:expireTimer', this.setToExpire); + this.setToExpire(); }, defaults : function() { return { @@ -344,6 +347,10 @@ errors : [] }); + if (dataMessage.expireTimer) { + message.set({expireTimer: dataMessage.expireTimer}); + } + var conversation_timestamp = conversation.get('timestamp'); if (!conversation_timestamp || message.get('sent_at') > conversation_timestamp) { conversation.set({ @@ -367,12 +374,35 @@ }); }); }, - markRead: function(sync) { + markRead: function() { this.unset('unread'); + if (this.get('expireTimer') && !this.get('expirationStartTimestamp')) { + this.set('expirationStartTimestamp', Date.now()); + } Whisper.Notifications.remove(Whisper.Notifications.where({ messageId: this.id })); return this.save(); + }, + markExpired: function() { + console.log('message', this.get('sent_at'), 'expired'); + clearInterval(this.expirationTimeout); + this.expirationTimeout = null; + this.trigger('expired', this); + this.destroy(); + }, + setToExpire: function() { + if (this.get('expireTimer') && this.get('expirationStartTimestamp') && !this.expireTimer) { + var now = Date.now(); + var start = this.get('expirationStartTimestamp'); + var delta = this.get('expireTimer') * 1000; + var ms_from_now = start + delta - now; + if (ms_from_now < 0) { + ms_from_now = 0; + } + console.log('message', this.get('sent_at'), 'expires in', ms_from_now, 'ms'); + this.expirationTimeout = setTimeout(this.markExpired.bind(this), ms_from_now); + } } }); @@ -434,6 +464,10 @@ }.bind(this)); }, + fetchExpiring: function() { + this.fetch({conditions: {expireTimer: {$gte: 0}}}); + }, + hasKeyConflicts: function() { return this.any(function(m) { return m.hasKeyConflicts(); }); } diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 1b276630..72eeaf09 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -43,6 +43,7 @@ this.listenTo(this.model, 'change:name', this.updateTitle); this.listenTo(this.model, 'newmessage', this.addMessage); this.listenTo(this.model, 'opened', this.onOpened); + this.listenTo(this.model.messageCollection, 'expired', this.onExpired); this.render(); @@ -166,8 +167,13 @@ // TODO catch? }, + onExpired: function(message) { + this.model.messageCollection.remove(message.id); + }, + addMessage: function(message) { this.model.messageCollection.add(message, {merge: true}); + message.setToExpire(); if (!this.isHidden() && window.isFocused()) { this.markRead(); diff --git a/js/views/message_view.js b/js/views/message_view.js index 20dea9cd..3a206fb1 100644 --- a/js/views/message_view.js +++ b/js/views/message_view.js @@ -35,7 +35,8 @@ this.listenTo(this.model, 'change:delivered', this.renderDelivered); this.listenTo(this.model, 'change', this.renderSent); this.listenTo(this.model, 'change:flags change:group_update', this.renderControl); - this.listenTo(this.model, 'destroy', this.remove); + this.listenTo(this.model, 'destroy', this.onDestroy); + this.listenTo(this.model, 'expired', this.onExpired); this.listenTo(this.model, 'pending', this.renderPending); this.listenTo(this.model, 'done', this.renderDone); this.timeStampView = new Whisper.ExtendedTimestampView(); @@ -62,6 +63,17 @@ this.model.resend(number); }.bind(this)); }, + onExpired: function() { + this.$el.addClass('expired'); + this.$el.find('.bubble').one('webkitAnimationEnd animationend', + this.remove.bind(this)); + }, + onDestroy: function() { + if (this.$el.hasClass('expired')) { + return; + } + this.remove(); + }, select: function(e) { this.$el.trigger('select', {message: this.model}); e.stopPropagation(); diff --git a/stylesheets/_conversation.scss b/stylesheets/_conversation.scss index 24e32c6f..a13c1cd5 100644 --- a/stylesheets/_conversation.scss +++ b/stylesheets/_conversation.scss @@ -383,6 +383,18 @@ li.entry .error-icon-container { } } + @keyframes shake { + 0% { transform: translateX(0px); } + 25% { transform: translateX(-5px); } + 50% { transform: translateX(0px); } + 75% { transform: translateX(5px); } + 100% { transform: translateX(0px); } + } + + .expired .bubble { + animation: shake 0.2s linear 3; + } + .control { .bubble { .content { diff --git a/stylesheets/manifest.css b/stylesheets/manifest.css index 50628b44..61dea07f 100644 --- a/stylesheets/manifest.css +++ b/stylesheets/manifest.css @@ -1207,6 +1207,20 @@ li.entry .error-icon-container { .message-container .outgoing .bubble, .message-list .outgoing .bubble { clear: left; } +@keyframes shake { + 0% { + transform: translateX(0px); } + 25% { + transform: translateX(-5px); } + 50% { + transform: translateX(0px); } + 75% { + transform: translateX(5px); } + 100% { + transform: translateX(0px); } } + .message-container .expired .bubble, + .message-list .expired .bubble { + animation: shake 0.2s linear 3; } .message-container .control .bubble .content, .message-list .control .bubble .content { font-style: italic; }