From 72b7e4ec34f06c890c8b6a907ad9c06da304d59e Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Mon, 29 Jan 2018 18:14:39 -0800 Subject: [PATCH] Process expireTimer and block status along with contact/group sync (#1980) * Mark group as left = false if it is active in contact sync * Handle expireTimer + blocked state along with contact/group sync --- _locales/en/messages.json | 10 +++ js/background.js | 93 ++++++++++++++++++-------- js/models/blockedNumbers.js | 23 ++++++- js/models/conversations.js | 51 +++++++++++--- js/views/message_view.js | 14 ++-- protos/IncomingPushMessageSignal.proto | 25 ++++--- 6 files changed, 162 insertions(+), 54 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 4f4564b4c..b13bbab2f 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -748,6 +748,16 @@ } } }, + "timerSetOnSync": { + "message": "Updating timer to $time$.", + "description": "Message displayed when timer is set on initial link of desktop device.", + "placeholders": { + "time": { + "content": "$1", + "example": "10m" + } + } + }, "theyChangedTheTimer": { "message": "$name$ set the timer to $time$.", "description": "Message displayed when someone else changes the message expiration timer in a conversation.", diff --git a/js/background.js b/js/background.js index 238394f34..8fbee9684 100644 --- a/js/background.js +++ b/js/background.js @@ -8,6 +8,12 @@ console.log(e); }; + window.wrapDeferred = function(deferred) { + return new Promise(function(resolve, reject) { + deferred.then(resolve, reject); + }); + }; + console.log('background page reloaded'); console.log('environment:', window.config.environment); @@ -377,38 +383,58 @@ return ConversationController.getOrCreateAndWait(id, 'private') .then(function(conversation) { - return new Promise(function(resolve, reject) { - var activeAt = conversation.get('active_at'); + var activeAt = conversation.get('active_at'); - // The idea is to make any new contact show up in the left pane. If - // activeAt is null, then this contact has been purposefully hidden. - if (activeAt !== null) { - activeAt = activeAt || Date.now(); - } + // The idea is to make any new contact show up in the left pane. If + // activeAt is null, then this contact has been purposefully hidden. + if (activeAt !== null) { + activeAt = activeAt || Date.now(); + } - if (details.profileKey) { - conversation.set({profileKey: details.profileKey}); + if (details.profileKey) { + conversation.set({profileKey: details.profileKey}); + } + + if (typeof details.blocked !== 'undefined') { + if (details.blocked) { + storage.addBlockedNumber(id); + } else { + storage.removeBlockedNumber(id); } - conversation.save({ - name: details.name, - avatar: details.avatar, - color: details.color, - active_at: activeAt, - }).then(resolve, reject); - }).then(function() { - if (details.verified) { - var verified = details.verified; - var ev = new Event('verified'); - ev.verified = { - state: verified.state, - destination: verified.destination, - identityKey: verified.identityKey.toArrayBuffer(), - }; - ev.viaContactSync = true; - return onVerified(ev); + } + + return wrapDeferred(conversation.save({ + name: details.name, + avatar: details.avatar, + color: details.color, + active_at: activeAt, + })).then(function() { + // this needs to be inline to get access to conversation model + if (typeof details.expireTimer !== 'undefined') { + var source = textsecure.storage.user.getNumber(); + var receivedAt = Date.now(); + return conversation.updateExpirationTimer( + details.expireTimer, + source, + receivedAt, + {fromSync: true} + ); } }); }) + .then(function() { + if (details.verified) { + var verified = details.verified; + var ev = new Event('verified'); + ev.verified = { + state: verified.state, + destination: verified.destination, + identityKey: verified.identityKey.toArrayBuffer(), + }; + ev.viaContactSync = true; + return onVerified(ev); + } + }) .then(ev.confirm) .catch(function(error) { console.log( @@ -437,11 +463,22 @@ if (activeAt !== null) { updates.active_at = activeAt || Date.now(); } + updates.left = false; } else { updates.left = true; } - return new Promise(function(resolve, reject) { - conversation.save(updates).then(resolve, reject); + + return wrapDeferred(conversation.save(updates)).then(function() { + if (typeof details.expireTimer !== 'undefined') { + var source = textsecure.storage.user.getNumber(); + var receivedAt = Date.now(); + return conversation.updateExpirationTimer( + details.expireTimer, + source, + receivedAt, + {fromSync: true} + ); + } }).then(ev.confirm); }); } diff --git a/js/models/blockedNumbers.js b/js/models/blockedNumbers.js index 76aa6a996..1eaa7af61 100644 --- a/js/models/blockedNumbers.js +++ b/js/models/blockedNumbers.js @@ -3,8 +3,27 @@ */ (function () { 'use strict'; - window.Whisper = window.Whisper || {}; storage.isBlocked = function(number) { - return storage.get('blocked', []).indexOf(number) >= 0; + var numbers = storage.get('blocked', []); + + return _.include(numbers, number); + }; + storage.addBlockedNumber = function(number) { + var numbers = storage.get('blocked', []); + if (_.include(numbers, number)) { + return; + } + + console.log('adding', number, 'to blocked list'); + storage.put('blocked', numbers.concat(number)); + }; + storage.removeBlockedNumber = function(number) { + var numbers = storage.get('blocked', []); + if (!_.include(numbers, number)) { + return; + } + + console.log('removing', number, 'from blocked list'); + storage.put('blocked', _.without(numbers, number)); }; })(); diff --git a/js/models/conversations.js b/js/models/conversations.js index fa5cfdf22..04a256c63 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -668,11 +668,28 @@ }.bind(this)); }, - updateExpirationTimer: function(expireTimer, source, received_at) { - if (!expireTimer) { expireTimer = null; } + updateExpirationTimer: function(expireTimer, source, received_at, options) { + options = options || {}; + _.defaults(options, {fromSync: false}); + + if (!expireTimer) { + expireTimer = null; + } + if (this.get('expireTimer') === expireTimer + || (!expireTimer && !this.get('expireTimer'))) { + + return; + } + + console.log( + 'Updating expireTimer for conversation', + this.idForLogging(), + 'via', + source + ); source = source || textsecure.storage.user.getNumber(); var timestamp = received_at || Date.now(); - this.save({ expireTimer: expireTimer }); + var message = this.messageCollection.add({ conversationId : this.id, type : received_at ? 'incoming' : 'outgoing', @@ -681,7 +698,8 @@ flags : textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE, expirationTimerUpdate : { expireTimer : expireTimer, - source : source + source : source, + fromSync : options.fromSync, } }); if (this.isPrivate()) { @@ -690,8 +708,16 @@ if (message.isOutgoing()) { message.set({recipients: this.getRecipients() }); } - message.save(); - if (message.isOutgoing()) { // outgoing update, send it to the number/group + + return Promise.all([ + wrapDeferred(message.save()), + wrapDeferred(this.save({ expireTimer: expireTimer })), + ]).then(function() { + if (message.isIncoming()) { + return message; + } + + // change was made locally, send it to the number/group var sendFunc; if (this.get('type') == 'private') { sendFunc = textsecure.messaging.sendExpirationTimerUpdateToNumber; @@ -703,9 +729,16 @@ if (this.get('profileSharing')) { profileKey = storage.get('profileKey'); } - message.send(sendFunc(this.get('id'), this.get('expireTimer'), message.get('sent_at'), profileKey)); - } - return message; + var promise = sendFunc(this.get('id'), + this.get('expireTimer'), + message.get('sent_at'), + profileKey + ); + + return message.send(promise).then(function() { + return message; + }); + }.bind(this)); }, isSearchable: function() { diff --git a/js/views/message_view.js b/js/views/message_view.js index 90cfca7bf..2811ead3a 100644 --- a/js/views/message_view.js +++ b/js/views/message_view.js @@ -82,13 +82,19 @@ render_attributes: function() { var seconds = this.model.get('expirationTimerUpdate').expireTimer; var timerMessage; - if (this.conversation.id === textsecure.storage.user.getNumber()) { - timerMessage = i18n('youChangedTheTimer', - Whisper.ExpirationTimerOptions.getName(seconds)); + + var timerUpdate = this.model.get('expirationTimerUpdate'); + var prettySeconds = Whisper.ExpirationTimerOptions.getName(seconds); + + if (timerUpdate && timerUpdate.fromSync) { + timerMessage = i18n('timerSetOnSync', prettySeconds); + } else if (this.conversation.id === textsecure.storage.user.getNumber()) { + timerMessage = i18n('youChangedTheTimer', prettySeconds); } else { timerMessage = i18n('theyChangedTheTimer', [ this.conversation.getTitle(), - Whisper.ExpirationTimerOptions.getName(seconds)]); + prettySeconds, + ]); } return { content: timerMessage }; } diff --git a/protos/IncomingPushMessageSignal.proto b/protos/IncomingPushMessageSignal.proto index 02160e5f3..75858caa5 100644 --- a/protos/IncomingPushMessageSignal.proto +++ b/protos/IncomingPushMessageSignal.proto @@ -194,12 +194,14 @@ message ContactDetails { optional uint32 length = 2; } - optional string number = 1; - optional string name = 2; - optional Avatar avatar = 3; - optional string color = 4; - optional Verified verified = 5; - optional bytes profileKey = 6; + optional string number = 1; + optional string name = 2; + optional Avatar avatar = 3; + optional string color = 4; + optional Verified verified = 5; + optional bytes profileKey = 6; + optional bool blocked = 7; + optional uint32 expireTimer = 8; } message GroupDetails { @@ -208,9 +210,10 @@ message GroupDetails { optional uint32 length = 2; } - optional bytes id = 1; - optional string name = 2; - repeated string members = 3; - optional Avatar avatar = 4; - optional bool active = 5 [default = true]; + optional bytes id = 1; + optional string name = 2; + repeated string members = 3; + optional Avatar avatar = 4; + optional bool active = 5 [default = true]; + optional uint32 expireTimer = 6; }