diff --git a/background.html b/background.html index eb902fcb..13e3ddf9 100644 --- a/background.html +++ b/background.html @@ -248,6 +248,10 @@ {{ #conflict }} {{ /conflict }} + {{ #retry }} + + {{message}} + {{ /retry }} {{ #errors }}
{{message}} diff --git a/js/libtextsecure.js b/js/libtextsecure.js index daebf462..addc3e40 100644 --- a/js/libtextsecure.js +++ b/js/libtextsecure.js @@ -7,9 +7,9 @@ var registeredFunctions = {}; var Type = { - SEND_MESSAGE: 1, + ENCRYPT_MESSAGE: 1, INIT_SESSION: 2, - NETWORK_REQUEST: 3, + TRANSMIT_MESSAGE: 3, }; window.textsecure = window.textsecure || {}; window.textsecure.replay = { @@ -48,7 +48,7 @@ function OutgoingIdentityKeyError(number, message, timestamp, identityKey) { ReplayableError.call(this, { - functionCode : Type.SEND_MESSAGE, + functionCode : Type.ENCRYPT_MESSAGE, args : [number, message, timestamp] }); this.name = 'OutgoingIdentityKeyError'; @@ -59,23 +59,40 @@ OutgoingIdentityKeyError.prototype = new ReplayableError(); OutgoingIdentityKeyError.prototype.constructor = OutgoingIdentityKeyError; - function NetworkError(number, jsonData, legacy, code) { + function OutgoingMessageError(number, message, timestamp, httpError) { ReplayableError.call(this, { - functionCode : Type.NETWORK_REQUEST, + functionCode : Type.ENCRYPT_MESSAGE, + args : [number, message, timestamp] + }); + this.name = 'OutgoingMessageError'; + if (httpError) { + this.code = httpError.code; + this.message = httpError.message; + this.stack = httpError.stack; + } + } + OutgoingMessageError.prototype = new ReplayableError(); + OutgoingMessageError.prototype.constructor = OutgoingMessageError; + + function SendMessageNetworkError(number, jsonData, legacy, httpError) { + ReplayableError.call(this, { + functionCode : Type.TRANSMIT_MESSAGE, args : [number, jsonData, legacy] }); - this.name = 'NetworkError'; - this.message = 'Network request failed' - this.code = code; + this.name = 'SendMessageNetworkError'; this.number = number; + this.code = httpError.code; + this.message = httpError.message; + this.stack = httpError.stack; } - NetworkError.prototype = new ReplayableError(); - NetworkError.prototype.constructor = NetworkError; + SendMessageNetworkError.prototype = new ReplayableError(); + SendMessageNetworkError.prototype.constructor = SendMessageNetworkError; - window.textsecure.NetworkError = NetworkError; + window.textsecure.SendMessageNetworkError = SendMessageNetworkError; window.textsecure.IncomingIdentityKeyError = IncomingIdentityKeyError; window.textsecure.OutgoingIdentityKeyError = OutgoingIdentityKeyError; window.textsecure.ReplayableError = ReplayableError; + window.textsecure.OutgoingMessageError = OutgoingMessageError; })(); @@ -39637,14 +39654,14 @@ MessageSender.prototype = { }); })).then(function(jsonData) { var legacy = (message instanceof textsecure.protobuf.DataMessage); - return this.sendRequest(number, jsonData, legacy); + return this.sendMessageRequest(number, jsonData, legacy); }.bind(this)); }, - sendRequest: function(number, jsonData, legacy) { + sendMessageRequest: function(number, jsonData, legacy) { return this.server.sendMessages(number, jsonData, legacy).catch(function(e) { - if (e.name === 'HTTPError' && e.code === -1) { - throw new NetworkError(number, jsonData, legacy); + if (e.name === 'HTTPError' && (e.code !== 409 && e.code !== 410)) { + throw new textsecure.SendMessageNetworkError(number, jsonData, legacy, e); } throw e; }); @@ -39684,9 +39701,10 @@ MessageSender.prototype = { }; var registerError = function(number, reason, error) { - if (!error) { - error = new Error(reason); + if (!error || error.name === 'HTTPError') { + error = new textsecure.OutgoingMessageError(number, message.toArrayBuffer(), timestamp, error); } + error.number = number; error.reason = reason; errors[errors.length] = error; @@ -40025,8 +40043,8 @@ window.textsecure = window.textsecure || {}; textsecure.MessageSender = function(url, username, password) { var sender = new MessageSender(url, username, password); - textsecure.replay.registerFunction(sender.tryMessageAgain.bind(sender), textsecure.replay.Type.SEND_MESSAGE); - textsecure.replay.registerFunction(sender.sendRequest.bind(sender), textsecure.replay.Type.NETWORK_REQUEST); + textsecure.replay.registerFunction(sender.tryMessageAgain.bind(sender), textsecure.replay.Type.ENCRYPT_MESSAGE); + textsecure.replay.registerFunction(sender.sendMessageRequest.bind(sender), textsecure.replay.Type.TRANSMIT_MESSAGE); this.sendRequestGroupSyncMessage = sender.sendRequestGroupSyncMessage .bind(sender); this.sendRequestContactSyncMessage = sender.sendRequestContactSyncMessage.bind(sender); diff --git a/js/models/messages.js b/js/models/messages.js index be7bcbbe..a2a9a4c2 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -175,8 +175,18 @@ this.set({errors: errors}); }, + removeOutgoingErrors: function(number) { + var errors = _.partition(this.get('errors'), function(e) { + return e.number === number && + (e.name === 'OutgoingMessageError' || + e.name === 'SendMessageNetworkError'); + }); + this.set({errors: errors[1]}); + return errors[0][0]; + }, + resend: function(number) { - var error = this.getSendError(number); + var error = this.removeOutgoingErrors(number); if (error) { var promise = new textsecure.ReplayableError(error).replay(); this.send(promise); diff --git a/js/views/message_detail_view.js b/js/views/message_detail_view.js index 2f0b8e9b..9a2298db 100644 --- a/js/views/message_detail_view.js +++ b/js/views/message_detail_view.js @@ -10,22 +10,33 @@ templateName: 'contact-detail', initialize: function(options) { this.conflict = options.conflict; + this.retry = _.find(options.errors, function(e) { + return (e.name === 'OutgoingMessageError' || + e.name === 'SendMessageNetworkError'); + }); this.errors = _.reject(options.errors, function(e) { return (e.name === 'IncomingIdentityKeyError' || - e.name === 'OutgoingIdentityKeyError'); + e.name === 'OutgoingIdentityKeyError' || + e.name === 'OutgoingMessageError' || + e.name === 'SendMessageNetworkError'); }); }, events: { - 'click .conflict': 'triggerConflict' + 'click .conflict': 'triggerConflict', + 'click .retry': 'triggerRetry' }, triggerConflict: function() { this.$el.trigger('conflict', {conflict: this.conflict}); }, + triggerRetry: function() { + this.$el.trigger('retry', {error: this.retry}); + }, render_attributes: function() { return { name : this.model.getTitle(), avatar : this.model.getAvatar(), conflict : this.conflict, + retry : this.retry, errors : this.errors }; } @@ -43,7 +54,8 @@ }, events: { 'click .back': 'goBack', - 'conflict': 'conflictDialogue' + 'conflict': 'conflictDialogue', + 'retry': 'retryMessage', }, goBack: function() { this.trigger('back'); @@ -82,6 +94,9 @@ this.render(); }); }, + retryMessage: function(e, data) { + this.model.resend(data.error.number); + }, renderContact: function(contact) { var v = new ContactView({ model: contact, diff --git a/libtextsecure/errors.js b/libtextsecure/errors.js index d0eb5f3c..4f1d2c6f 100644 --- a/libtextsecure/errors.js +++ b/libtextsecure/errors.js @@ -6,9 +6,9 @@ var registeredFunctions = {}; var Type = { - SEND_MESSAGE: 1, + ENCRYPT_MESSAGE: 1, INIT_SESSION: 2, - NETWORK_REQUEST: 3, + TRANSMIT_MESSAGE: 3, }; window.textsecure = window.textsecure || {}; window.textsecure.replay = { @@ -47,7 +47,7 @@ function OutgoingIdentityKeyError(number, message, timestamp, identityKey) { ReplayableError.call(this, { - functionCode : Type.SEND_MESSAGE, + functionCode : Type.ENCRYPT_MESSAGE, args : [number, message, timestamp] }); this.name = 'OutgoingIdentityKeyError'; @@ -58,22 +58,39 @@ OutgoingIdentityKeyError.prototype = new ReplayableError(); OutgoingIdentityKeyError.prototype.constructor = OutgoingIdentityKeyError; - function NetworkError(number, jsonData, legacy, code) { + function OutgoingMessageError(number, message, timestamp, httpError) { ReplayableError.call(this, { - functionCode : Type.NETWORK_REQUEST, + functionCode : Type.ENCRYPT_MESSAGE, + args : [number, message, timestamp] + }); + this.name = 'OutgoingMessageError'; + if (httpError) { + this.code = httpError.code; + this.message = httpError.message; + this.stack = httpError.stack; + } + } + OutgoingMessageError.prototype = new ReplayableError(); + OutgoingMessageError.prototype.constructor = OutgoingMessageError; + + function SendMessageNetworkError(number, jsonData, legacy, httpError) { + ReplayableError.call(this, { + functionCode : Type.TRANSMIT_MESSAGE, args : [number, jsonData, legacy] }); - this.name = 'NetworkError'; - this.message = 'Network request failed' - this.code = code; + this.name = 'SendMessageNetworkError'; this.number = number; + this.code = httpError.code; + this.message = httpError.message; + this.stack = httpError.stack; } - NetworkError.prototype = new ReplayableError(); - NetworkError.prototype.constructor = NetworkError; + SendMessageNetworkError.prototype = new ReplayableError(); + SendMessageNetworkError.prototype.constructor = SendMessageNetworkError; - window.textsecure.NetworkError = NetworkError; + window.textsecure.SendMessageNetworkError = SendMessageNetworkError; window.textsecure.IncomingIdentityKeyError = IncomingIdentityKeyError; window.textsecure.OutgoingIdentityKeyError = OutgoingIdentityKeyError; window.textsecure.ReplayableError = ReplayableError; + window.textsecure.OutgoingMessageError = OutgoingMessageError; })(); diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index bc70ab94..abd968e0 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -37,14 +37,14 @@ MessageSender.prototype = { }); })).then(function(jsonData) { var legacy = (message instanceof textsecure.protobuf.DataMessage); - return this.sendRequest(number, jsonData, legacy); + return this.sendMessageRequest(number, jsonData, legacy); }.bind(this)); }, - sendRequest: function(number, jsonData, legacy) { + sendMessageRequest: function(number, jsonData, legacy) { return this.server.sendMessages(number, jsonData, legacy).catch(function(e) { - if (e.name === 'HTTPError' && e.code === -1) { - throw new NetworkError(number, jsonData, legacy); + if (e.name === 'HTTPError' && (e.code !== 409 && e.code !== 410)) { + throw new textsecure.SendMessageNetworkError(number, jsonData, legacy, e); } throw e; }); @@ -84,9 +84,10 @@ MessageSender.prototype = { }; var registerError = function(number, reason, error) { - if (!error) { - error = new Error(reason); + if (!error || error.name === 'HTTPError') { + error = new textsecure.OutgoingMessageError(number, message.toArrayBuffer(), timestamp, error); } + error.number = number; error.reason = reason; errors[errors.length] = error; @@ -425,8 +426,8 @@ window.textsecure = window.textsecure || {}; textsecure.MessageSender = function(url, username, password) { var sender = new MessageSender(url, username, password); - textsecure.replay.registerFunction(sender.tryMessageAgain.bind(sender), textsecure.replay.Type.SEND_MESSAGE); - textsecure.replay.registerFunction(sender.sendRequest.bind(sender), textsecure.replay.Type.NETWORK_REQUEST); + textsecure.replay.registerFunction(sender.tryMessageAgain.bind(sender), textsecure.replay.Type.ENCRYPT_MESSAGE); + textsecure.replay.registerFunction(sender.sendMessageRequest.bind(sender), textsecure.replay.Type.TRANSMIT_MESSAGE); this.sendRequestGroupSyncMessage = sender.sendRequestGroupSyncMessage .bind(sender); this.sendRequestContactSyncMessage = sender.sendRequestContactSyncMessage.bind(sender); diff --git a/stylesheets/_conversation.scss b/stylesheets/_conversation.scss index 6b40562e..58ba0515 100644 --- a/stylesheets/_conversation.scss +++ b/stylesheets/_conversation.scss @@ -61,13 +61,18 @@ } } - .conflict { + .contact-detail button { border: none; border-radius: 5px; color: white; padding: 0.5em; font-weight: bold; - background: #d00; + background: $blue; + + span { + vertical-align: middle; + padding-left: 5px; + } &:before { content: ''; @@ -75,13 +80,15 @@ vertical-align: middle; width: 18px; height: 18px; - background: url('/images/error.png') no-repeat center center; + background: url('/images/refresh.png') no-repeat center center; background-size: 100%; } + } + .conflict { + background: #d00; - span { - vertical-align: middle; - padding-left: 5px; + &:before { + background: url('/images/error.png') no-repeat center center; } } } diff --git a/stylesheets/manifest.css b/stylesheets/manifest.css index 9787f87a..8234fc8a 100644 --- a/stylesheets/manifest.css +++ b/stylesheets/manifest.css @@ -550,24 +550,28 @@ input.search { font-weight: bold; padding-right: 1em; vertical-align: top; } - .message-detail .conflict { + .message-detail .contact-detail button { border: none; border-radius: 5px; color: white; padding: 0.5em; font-weight: bold; - background: #d00; } - .message-detail .conflict:before { + background: #2090ea; } + .message-detail .contact-detail button span { + vertical-align: middle; + padding-left: 5px; } + .message-detail .contact-detail button:before { content: ''; display: inline-block; vertical-align: middle; width: 18px; height: 18px; - background: url("/images/error.png") no-repeat center center; + background: url("/images/refresh.png") no-repeat center center; background-size: 100%; } - .message-detail .conflict span { - vertical-align: middle; - padding-left: 5px; } + .message-detail .conflict { + background: #d00; } + .message-detail .conflict:before { + background: url("/images/error.png") no-repeat center center; } .group-update { font-size: smaller; }