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; }