Support for incoming expiring messages

When initialized, or when expiration-related attributes change, expiring
messages will set timers to self-destruct. On self-destruct they trigger
'expired' events so that frontend listeners can clean up any collections
and views referencing them.

At startup, load all messages pending expiration so they can start their
timers even if they haven't been loaded in the frontend yet.

Todo: Remove expired conversation snippets from the left pane.
pull/749/head
lilia 9 years ago
parent b888e01044
commit 96fd017890

@ -480,6 +480,7 @@
<script type='text/javascript' src='js/models/messages.js'></script> <script type='text/javascript' src='js/models/messages.js'></script>
<script type='text/javascript' src='js/models/conversations.js'></script> <script type='text/javascript' src='js/models/conversations.js'></script>
<script type='text/javascript' src='js/models/blockedNumbers.js'></script> <script type='text/javascript' src='js/models/blockedNumbers.js'></script>
<script type='text/javascript' src='js/expiring_messages.js'></script>
<script type='text/javascript' src='js/chromium.js'></script> <script type='text/javascript' src='js/chromium.js'></script>
<script type='text/javascript' src='js/registration.js'></script> <script type='text/javascript' src='js/registration.js'></script>

@ -169,7 +169,8 @@
received_at : now, received_at : now,
conversationId : data.destination, conversationId : data.destination,
type : 'outgoing', type : 'outgoing',
sent : true sent : true,
expirationStartTimestamp: data.expirationStartTimestamp,
}); });
message.handleDataMessage(data.message); message.handleDataMessage(data.message);

@ -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();
}
}))();
})();

@ -230,17 +230,20 @@
this.getUnread().then(function(unreadMessages) { this.getUnread().then(function(unreadMessages) {
var read = unreadMessages.map(function(m) { var read = unreadMessages.map(function(m) {
if (this.messageCollection.get(m.id)) {
m = this.messageCollection.get(m.id);
}
m.markRead(); m.markRead();
return { return {
sender : m.get('source'), sender : m.get('source'),
timestamp : m.get('sent_at') timestamp : m.get('sent_at')
}; };
}); }.bind(this));
if (read.length > 0) { if (read.length > 0) {
console.log('Sending', read.length, 'read receipts'); console.log('Sending', read.length, 'read receipts');
textsecure.messaging.syncReadMessages(read); textsecure.messaging.syncReadMessages(read);
} }
}); }.bind(this));
} }
}, },

@ -11,6 +11,9 @@
initialize: function() { initialize: function() {
this.on('change:attachments', this.updateImageUrl); this.on('change:attachments', this.updateImageUrl);
this.on('destroy', this.revokeImageUrl); this.on('destroy', this.revokeImageUrl);
this.on('change:expirationStartTimestamp', this.setToExpire);
this.on('change:expireTimer', this.setToExpire);
this.setToExpire();
}, },
defaults : function() { defaults : function() {
return { return {
@ -344,6 +347,10 @@
errors : [] errors : []
}); });
if (dataMessage.expireTimer) {
message.set({expireTimer: dataMessage.expireTimer});
}
var conversation_timestamp = conversation.get('timestamp'); var conversation_timestamp = conversation.get('timestamp');
if (!conversation_timestamp || message.get('sent_at') > conversation_timestamp) { if (!conversation_timestamp || message.get('sent_at') > conversation_timestamp) {
conversation.set({ conversation.set({
@ -367,12 +374,35 @@
}); });
}); });
}, },
markRead: function(sync) { markRead: function() {
this.unset('unread'); this.unset('unread');
if (this.get('expireTimer') && !this.get('expirationStartTimestamp')) {
this.set('expirationStartTimestamp', Date.now());
}
Whisper.Notifications.remove(Whisper.Notifications.where({ Whisper.Notifications.remove(Whisper.Notifications.where({
messageId: this.id messageId: this.id
})); }));
return this.save(); 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)); }.bind(this));
}, },
fetchExpiring: function() {
this.fetch({conditions: {expireTimer: {$gte: 0}}});
},
hasKeyConflicts: function() { hasKeyConflicts: function() {
return this.any(function(m) { return m.hasKeyConflicts(); }); return this.any(function(m) { return m.hasKeyConflicts(); });
} }

@ -43,6 +43,7 @@
this.listenTo(this.model, 'change:name', this.updateTitle); this.listenTo(this.model, 'change:name', this.updateTitle);
this.listenTo(this.model, 'newmessage', this.addMessage); this.listenTo(this.model, 'newmessage', this.addMessage);
this.listenTo(this.model, 'opened', this.onOpened); this.listenTo(this.model, 'opened', this.onOpened);
this.listenTo(this.model.messageCollection, 'expired', this.onExpired);
this.render(); this.render();
@ -166,8 +167,13 @@
// TODO catch? // TODO catch?
}, },
onExpired: function(message) {
this.model.messageCollection.remove(message.id);
},
addMessage: function(message) { addMessage: function(message) {
this.model.messageCollection.add(message, {merge: true}); this.model.messageCollection.add(message, {merge: true});
message.setToExpire();
if (!this.isHidden() && window.isFocused()) { if (!this.isHidden() && window.isFocused()) {
this.markRead(); this.markRead();

@ -35,7 +35,8 @@
this.listenTo(this.model, 'change:delivered', this.renderDelivered); this.listenTo(this.model, 'change:delivered', this.renderDelivered);
this.listenTo(this.model, 'change', this.renderSent); this.listenTo(this.model, 'change', this.renderSent);
this.listenTo(this.model, 'change:flags change:group_update', this.renderControl); 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, 'pending', this.renderPending);
this.listenTo(this.model, 'done', this.renderDone); this.listenTo(this.model, 'done', this.renderDone);
this.timeStampView = new Whisper.ExtendedTimestampView(); this.timeStampView = new Whisper.ExtendedTimestampView();
@ -62,6 +63,17 @@
this.model.resend(number); this.model.resend(number);
}.bind(this)); }.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) { select: function(e) {
this.$el.trigger('select', {message: this.model}); this.$el.trigger('select', {message: this.model});
e.stopPropagation(); e.stopPropagation();

@ -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 { .control {
.bubble { .bubble {
.content { .content {

@ -1207,6 +1207,20 @@ li.entry .error-icon-container {
.message-container .outgoing .bubble, .message-container .outgoing .bubble,
.message-list .outgoing .bubble { .message-list .outgoing .bubble {
clear: left; } 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-container .control .bubble .content,
.message-list .control .bubble .content { .message-list .control .bubble .content {
font-style: italic; } font-style: italic; }

Loading…
Cancel
Save