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.
This commit is contained in:
parent
b888e01044
commit
96fd017890
9 changed files with 102 additions and 5 deletions
|
@ -480,6 +480,7 @@
|
|||
<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/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/registration.js'></script>
|
||||
|
|
|
@ -169,7 +169,8 @@
|
|||
received_at : now,
|
||||
conversationId : data.destination,
|
||||
type : 'outgoing',
|
||||
sent : true
|
||||
sent : true,
|
||||
expirationStartTimestamp: data.expirationStartTimestamp,
|
||||
});
|
||||
|
||||
message.handleDataMessage(data.message);
|
||||
|
|
14
js/expiring_messages.js
Normal file
14
js/expiring_messages.js
Normal file
|
@ -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) {
|
||||
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));
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -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(); });
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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; }
|
||||
|
|
Loading…
Reference in a new issue