Decrypt any IncomingIdentityKeyError still sticking around
FREEBIE
This commit is contained in:
parent
67cb9bdf54
commit
8700112f6d
6 changed files with 351 additions and 31 deletions
|
@ -31,7 +31,9 @@
|
||||||
ReplayableError.prototype.constructor = ReplayableError;
|
ReplayableError.prototype.constructor = ReplayableError;
|
||||||
|
|
||||||
ReplayableError.prototype.replay = function() {
|
ReplayableError.prototype.replay = function() {
|
||||||
return registeredFunctions[this.functionCode].apply(window, this.args);
|
var argumentsAsArray = Array.prototype.slice.call(arguments, 0);
|
||||||
|
var args = this.args.concat(argumentsAsArray);
|
||||||
|
return registeredFunctions[this.functionCode].apply(window, args);
|
||||||
};
|
};
|
||||||
|
|
||||||
function IncomingIdentityKeyError(number, message, key) {
|
function IncomingIdentityKeyError(number, message, key) {
|
||||||
|
@ -38895,11 +38897,36 @@ MessageReceiver.prototype.extend({
|
||||||
.then(decryptAttachment)
|
.then(decryptAttachment)
|
||||||
.then(updateAttachment);
|
.then(updateAttachment);
|
||||||
},
|
},
|
||||||
tryMessageAgain: function(from, ciphertext) {
|
validateRetryContentMessage: function(content) {
|
||||||
|
// Today this is only called for incoming identity key errors. So it can't be a sync message.
|
||||||
|
if (content.syncMessage) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want at least one field set, but not more than one
|
||||||
|
var count = 0;
|
||||||
|
count += content.dataMessage ? 1 : 0;
|
||||||
|
count += content.callMessage ? 1 : 0;
|
||||||
|
count += content.nullMessage ? 1 : 0;
|
||||||
|
if (count !== 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's most likely that dataMessage will be populated, so we look at it in detail
|
||||||
|
var data = content.dataMessage;
|
||||||
|
if (data && !data.attachments.length && !data.body && !data.expireTimer && !data.flags && !data.group) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
tryMessageAgain: function(from, ciphertext, message) {
|
||||||
var address = libsignal.SignalProtocolAddress.fromString(from);
|
var address = libsignal.SignalProtocolAddress.fromString(from);
|
||||||
|
var sentAt = message.sent_at || Date.now();
|
||||||
|
|
||||||
var ourNumber = textsecure.storage.user.getNumber();
|
var ourNumber = textsecure.storage.user.getNumber();
|
||||||
var number = address.toString().split('.')[0];
|
var number = address.getName();
|
||||||
|
var device = address.getDeviceId();
|
||||||
var options = {};
|
var options = {};
|
||||||
|
|
||||||
// No limit on message keys if we're communicating with our other devices
|
// No limit on message keys if we're communicating with our other devices
|
||||||
|
@ -38910,19 +38937,42 @@ MessageReceiver.prototype.extend({
|
||||||
var sessionCipher = new libsignal.SessionCipher(textsecure.storage.protocol, address, options);
|
var sessionCipher = new libsignal.SessionCipher(textsecure.storage.protocol, address, options);
|
||||||
console.log('retrying prekey whisper message');
|
console.log('retrying prekey whisper message');
|
||||||
return this.decryptPreKeyWhisperMessage(ciphertext, sessionCipher, address).then(function(plaintext) {
|
return this.decryptPreKeyWhisperMessage(ciphertext, sessionCipher, address).then(function(plaintext) {
|
||||||
var finalMessage = textsecure.protobuf.DataMessage.decode(plaintext);
|
var envelope = {
|
||||||
|
source: number,
|
||||||
|
sourceDevice: device,
|
||||||
|
timestamp: {
|
||||||
|
toNumber: function() {
|
||||||
|
return sentAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var p = Promise.resolve();
|
// Before June, all incoming messages were still DataMessage:
|
||||||
if ((finalMessage.flags & textsecure.protobuf.DataMessage.Flags.END_SESSION)
|
// - iOS: Michael Kirk says that they were sending Legacy messages until June
|
||||||
== textsecure.protobuf.DataMessage.Flags.END_SESSION &&
|
// - Desktop: https://github.com/WhisperSystems/Signal-Desktop/commit/e8548879db405d9bcd78b82a456ad8d655592c0f
|
||||||
finalMessage.sync !== null) {
|
// - Android: https://github.com/WhisperSystems/libsignal-service-java/commit/61a75d023fba950ff9b4c75a249d1a3408e12958
|
||||||
var number = address.getName();
|
//
|
||||||
p = this.handleEndSession(number);
|
// var d = new Date('2017-06-01T07:00:00.000Z');
|
||||||
|
// d.getTime();
|
||||||
|
var startOfJune = 1496300400000;
|
||||||
|
if (sentAt < startOfJune) {
|
||||||
|
return this.innerHandleLegacyMessage(envelope, plaintext);
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.then(function() {
|
// This is ugly. But we don't know what kind of proto we need to decode...
|
||||||
return this.processDecrypted(finalMessage);
|
try {
|
||||||
}.bind(this));
|
// Simply decoding as a Content message may throw
|
||||||
|
var content = textsecure.protobuf.Content.decode(plaintext);
|
||||||
|
|
||||||
|
// But it might also result in an invalid object, so we try to detect that
|
||||||
|
if (this.validateRetryContentMessage(content)) {
|
||||||
|
return this.innerHandleContentMessage(envelope, plaintext);
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
return this.innerHandleLegacyMessage(envelope, plaintext);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.innerHandleLegacyMessage(envelope, plaintext);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
handleEndSession: function(number) {
|
handleEndSession: function(number) {
|
||||||
|
@ -39433,8 +39483,55 @@ MessageSender.prototype = {
|
||||||
return outgoing.transmitMessage(number, jsonData, timestamp);
|
return outgoing.transmitMessage(number, jsonData, timestamp);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
validateRetryContentMessage: function(content) {
|
||||||
|
// We want at least one field set, but not more than one
|
||||||
|
var count = 0;
|
||||||
|
count += content.syncMessage ? 1 : 0;
|
||||||
|
count += content.dataMessage ? 1 : 0;
|
||||||
|
count += content.callMessage ? 1 : 0;
|
||||||
|
count += content.nullMessage ? 1 : 0;
|
||||||
|
if (count !== 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's most likely that dataMessage will be populated, so we look at it in detail
|
||||||
|
var data = content.dataMessage;
|
||||||
|
if (data && !data.attachments.length && !data.body && !data.expireTimer && !data.flags && !data.group) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
getRetryProto: function(message, timestamp) {
|
||||||
|
// If message was sent before v0.41.3 was released on Aug 7, then it was most certainly a DataMessage
|
||||||
|
//
|
||||||
|
// var d = new Date('2017-08-07T07:00:00.000Z');
|
||||||
|
// d.getTime();
|
||||||
|
var august7 = 1502089200000;
|
||||||
|
if (timestamp < august7) {
|
||||||
|
return textsecure.protobuf.DataMessage.decode(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is ugly. But we don't know what kind of proto we need to decode...
|
||||||
|
try {
|
||||||
|
// Simply decoding as a Content message may throw
|
||||||
|
var proto = textsecure.protobuf.Content.decode(message);
|
||||||
|
|
||||||
|
// But it might also result in an invalid object, so we try to detect that
|
||||||
|
if (this.validateRetryContentMessage(proto)) {
|
||||||
|
return proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
return textsecure.protobuf.DataMessage.decode(message);
|
||||||
|
} catch(e) {
|
||||||
|
// If this call throws, something has really gone wrong, we'll fail to send
|
||||||
|
return textsecure.protobuf.DataMessage.decode(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
tryMessageAgain: function(number, encodedMessage, timestamp) {
|
tryMessageAgain: function(number, encodedMessage, timestamp) {
|
||||||
var proto = textsecure.protobuf.Content.decode(encodedMessage);
|
var proto = this.getRetryProto(encodedMessage, timestamp);
|
||||||
return this.sendIndividualProto(number, proto, timestamp);
|
return this.sendIndividualProto(number, proto, timestamp);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
// TODO: Factor out private and group subclasses of Conversation
|
// TODO: Factor out private and group subclasses of Conversation
|
||||||
|
|
||||||
var COLORS = [
|
var COLORS = [
|
||||||
'red',
|
'red',
|
||||||
'pink',
|
'pink',
|
||||||
'purple',
|
'purple',
|
||||||
|
@ -25,6 +25,22 @@
|
||||||
'blue_grey',
|
'blue_grey',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function constantTimeEqualArrayBuffers(ab1, ab2) {
|
||||||
|
if (!(ab1 instanceof ArrayBuffer && ab2 instanceof ArrayBuffer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ab1.byteLength !== ab2.byteLength) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var result = true;
|
||||||
|
var ta1 = new Uint8Array(ab1);
|
||||||
|
var ta2 = new Uint8Array(ab2);
|
||||||
|
for (var i = 0; i < ab1.byteLength; ++i) {
|
||||||
|
if (ta1[i] !== ta2[i]) { result = false; }
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
Whisper.Conversation = Backbone.Model.extend({
|
Whisper.Conversation = Backbone.Model.extend({
|
||||||
database: Whisper.Database,
|
database: Whisper.Database,
|
||||||
storeName: 'conversations',
|
storeName: 'conversations',
|
||||||
|
@ -169,6 +185,108 @@
|
||||||
return textsecure.messaging.syncVerification(number, state, key);
|
return textsecure.messaging.syncVerification(number, state, key);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
getIdentityKeys: function() {
|
||||||
|
var lookup = {};
|
||||||
|
|
||||||
|
if (this.isPrivate()) {
|
||||||
|
return textsecure.storage.protocol.loadIdentityKey(this.id).then(function(key) {
|
||||||
|
lookup[this.id] = key;
|
||||||
|
return lookup;
|
||||||
|
}.bind(this)).catch(function(error) {
|
||||||
|
console.log(
|
||||||
|
'getIdentityKeys error for conversation',
|
||||||
|
this.id,
|
||||||
|
error && error.stack ? error.stack : error
|
||||||
|
);
|
||||||
|
return lookup;
|
||||||
|
}.bind(this));
|
||||||
|
} else {
|
||||||
|
return Promise.all(this.contactCollection.map(function(contact) {
|
||||||
|
return textsecure.storage.protocol.loadIdentityKey(contact.id).then(function(key) {
|
||||||
|
lookup[contact.id] = key;
|
||||||
|
}).catch(function(error) {
|
||||||
|
console.log(
|
||||||
|
'getIdentityKeys error for group member',
|
||||||
|
contact.id,
|
||||||
|
error && error.stack ? error.stack : error
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})).then(function() {
|
||||||
|
return lookup;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
replay: function(error, message) {
|
||||||
|
var replayable = new textsecure.ReplayableError(error);
|
||||||
|
return replayable.replay(message.attributes).catch(function(error) {
|
||||||
|
console.log(
|
||||||
|
'replay error:',
|
||||||
|
error && error.stack ? error.stack : error
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
decryptOldIncomingKeyErrors: function() {
|
||||||
|
// We want to run just once per conversation
|
||||||
|
if (this.get('decryptedOldIncomingKeyErrors')) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
console.log('decryptOldIncomingKeyErrors start');
|
||||||
|
|
||||||
|
var messages = this.messageCollection.filter(function(message) {
|
||||||
|
var errors = message.get('errors');
|
||||||
|
if (!errors || !errors[0]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var error = _.find(errors, function(error) {
|
||||||
|
return error.name === 'IncomingIdentityKeyError';
|
||||||
|
});
|
||||||
|
|
||||||
|
return Boolean(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
var markComplete = function() {
|
||||||
|
console.log('decryptOldIncomingKeyErrors complete');
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
this.save({decryptedOldIncomingKeyErrors: true}).always(resolve);
|
||||||
|
}.bind(this));
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
if (!messages.length) {
|
||||||
|
return markComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('decryptOldIncomingKeyErrors found', messages.length, 'messages to process');
|
||||||
|
var safeDelete = function(message) {
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
message.destroy().always(resolve);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.getIdentityKeys().then(function(lookup) {
|
||||||
|
return Promise.all(_.map(messages, function(message) {
|
||||||
|
var source = message.get('source');
|
||||||
|
var error = _.find(message.get('errors'), function(error) {
|
||||||
|
return error.name === 'IncomingIdentityKeyError';
|
||||||
|
});
|
||||||
|
|
||||||
|
var key = lookup[source];
|
||||||
|
if (!key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (constantTimeEqualArrayBuffers(key, error.identityKey)) {
|
||||||
|
return this.replay(error, message).then(function() {
|
||||||
|
return safeDelete(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}.bind(this)));
|
||||||
|
}.bind(this)).catch(function(error) {
|
||||||
|
console.log(
|
||||||
|
'decryptOldIncomingKeyErrors error:',
|
||||||
|
error && error.stack ? error.stack : error
|
||||||
|
);
|
||||||
|
}).then(markComplete);
|
||||||
|
},
|
||||||
isVerified: function() {
|
isVerified: function() {
|
||||||
if (this.isPrivate()) {
|
if (this.isPrivate()) {
|
||||||
return this.get('verified') === this.verifiedEnum.VERIFIED;
|
return this.get('verified') === this.verifiedEnum.VERIFIED;
|
||||||
|
|
|
@ -429,14 +429,22 @@
|
||||||
this.lastActivity = Date.now();
|
this.lastActivity = Date.now();
|
||||||
|
|
||||||
this.statusFetch = this.throttledGetProfiles().then(function() {
|
this.statusFetch = this.throttledGetProfiles().then(function() {
|
||||||
this.model.updateVerified().then(function() {
|
return this.model.updateVerified().then(function() {
|
||||||
this.onVerifiedChange();
|
this.onVerifiedChange();
|
||||||
this.statusFetch = null;
|
this.statusFetch = null;
|
||||||
console.log('done with status fetch');
|
console.log('done with status fetch');
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
Promise.all([this.statusFetch, this.inProgressFetch])
|
// We schedule our catch-up decrypt right after any in-progress fetch of
|
||||||
|
// messages from the database, then ensure that the loading screen is only
|
||||||
|
// dismissed when that is complete.
|
||||||
|
var messagesLoaded = this.inProgressFetch || Promise.resolve();
|
||||||
|
messagesLoaded = messagesLoaded.then(function() {
|
||||||
|
return this.model.decryptOldIncomingKeyErrors();
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
Promise.all([this.statusFetch, messagesLoaded])
|
||||||
.then(this.onLoaded.bind(this), this.onLoaded.bind(this));
|
.then(this.onLoaded.bind(this), this.onLoaded.bind(this));
|
||||||
|
|
||||||
this.view.resetScrollPosition();
|
this.view.resetScrollPosition();
|
||||||
|
|
|
@ -30,7 +30,9 @@
|
||||||
ReplayableError.prototype.constructor = ReplayableError;
|
ReplayableError.prototype.constructor = ReplayableError;
|
||||||
|
|
||||||
ReplayableError.prototype.replay = function() {
|
ReplayableError.prototype.replay = function() {
|
||||||
return registeredFunctions[this.functionCode].apply(window, this.args);
|
var argumentsAsArray = Array.prototype.slice.call(arguments, 0);
|
||||||
|
var args = this.args.concat(argumentsAsArray);
|
||||||
|
return registeredFunctions[this.functionCode].apply(window, args);
|
||||||
};
|
};
|
||||||
|
|
||||||
function IncomingIdentityKeyError(number, message, key) {
|
function IncomingIdentityKeyError(number, message, key) {
|
||||||
|
|
|
@ -646,11 +646,36 @@ MessageReceiver.prototype.extend({
|
||||||
.then(decryptAttachment)
|
.then(decryptAttachment)
|
||||||
.then(updateAttachment);
|
.then(updateAttachment);
|
||||||
},
|
},
|
||||||
tryMessageAgain: function(from, ciphertext) {
|
validateRetryContentMessage: function(content) {
|
||||||
|
// Today this is only called for incoming identity key errors. So it can't be a sync message.
|
||||||
|
if (content.syncMessage) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want at least one field set, but not more than one
|
||||||
|
var count = 0;
|
||||||
|
count += content.dataMessage ? 1 : 0;
|
||||||
|
count += content.callMessage ? 1 : 0;
|
||||||
|
count += content.nullMessage ? 1 : 0;
|
||||||
|
if (count !== 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's most likely that dataMessage will be populated, so we look at it in detail
|
||||||
|
var data = content.dataMessage;
|
||||||
|
if (data && !data.attachments.length && !data.body && !data.expireTimer && !data.flags && !data.group) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
tryMessageAgain: function(from, ciphertext, message) {
|
||||||
var address = libsignal.SignalProtocolAddress.fromString(from);
|
var address = libsignal.SignalProtocolAddress.fromString(from);
|
||||||
|
var sentAt = message.sent_at || Date.now();
|
||||||
|
|
||||||
var ourNumber = textsecure.storage.user.getNumber();
|
var ourNumber = textsecure.storage.user.getNumber();
|
||||||
var number = address.toString().split('.')[0];
|
var number = address.getName();
|
||||||
|
var device = address.getDeviceId();
|
||||||
var options = {};
|
var options = {};
|
||||||
|
|
||||||
// No limit on message keys if we're communicating with our other devices
|
// No limit on message keys if we're communicating with our other devices
|
||||||
|
@ -661,19 +686,42 @@ MessageReceiver.prototype.extend({
|
||||||
var sessionCipher = new libsignal.SessionCipher(textsecure.storage.protocol, address, options);
|
var sessionCipher = new libsignal.SessionCipher(textsecure.storage.protocol, address, options);
|
||||||
console.log('retrying prekey whisper message');
|
console.log('retrying prekey whisper message');
|
||||||
return this.decryptPreKeyWhisperMessage(ciphertext, sessionCipher, address).then(function(plaintext) {
|
return this.decryptPreKeyWhisperMessage(ciphertext, sessionCipher, address).then(function(plaintext) {
|
||||||
var finalMessage = textsecure.protobuf.DataMessage.decode(plaintext);
|
var envelope = {
|
||||||
|
source: number,
|
||||||
|
sourceDevice: device,
|
||||||
|
timestamp: {
|
||||||
|
toNumber: function() {
|
||||||
|
return sentAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var p = Promise.resolve();
|
// Before June, all incoming messages were still DataMessage:
|
||||||
if ((finalMessage.flags & textsecure.protobuf.DataMessage.Flags.END_SESSION)
|
// - iOS: Michael Kirk says that they were sending Legacy messages until June
|
||||||
== textsecure.protobuf.DataMessage.Flags.END_SESSION &&
|
// - Desktop: https://github.com/WhisperSystems/Signal-Desktop/commit/e8548879db405d9bcd78b82a456ad8d655592c0f
|
||||||
finalMessage.sync !== null) {
|
// - Android: https://github.com/WhisperSystems/libsignal-service-java/commit/61a75d023fba950ff9b4c75a249d1a3408e12958
|
||||||
var number = address.getName();
|
//
|
||||||
p = this.handleEndSession(number);
|
// var d = new Date('2017-06-01T07:00:00.000Z');
|
||||||
|
// d.getTime();
|
||||||
|
var startOfJune = 1496300400000;
|
||||||
|
if (sentAt < startOfJune) {
|
||||||
|
return this.innerHandleLegacyMessage(envelope, plaintext);
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.then(function() {
|
// This is ugly. But we don't know what kind of proto we need to decode...
|
||||||
return this.processDecrypted(finalMessage);
|
try {
|
||||||
}.bind(this));
|
// Simply decoding as a Content message may throw
|
||||||
|
var content = textsecure.protobuf.Content.decode(plaintext);
|
||||||
|
|
||||||
|
// But it might also result in an invalid object, so we try to detect that
|
||||||
|
if (this.validateRetryContentMessage(content)) {
|
||||||
|
return this.innerHandleContentMessage(envelope, plaintext);
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
return this.innerHandleLegacyMessage(envelope, plaintext);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.innerHandleLegacyMessage(envelope, plaintext);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
handleEndSession: function(number) {
|
handleEndSession: function(number) {
|
||||||
|
|
|
@ -143,8 +143,55 @@ MessageSender.prototype = {
|
||||||
return outgoing.transmitMessage(number, jsonData, timestamp);
|
return outgoing.transmitMessage(number, jsonData, timestamp);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
validateRetryContentMessage: function(content) {
|
||||||
|
// We want at least one field set, but not more than one
|
||||||
|
var count = 0;
|
||||||
|
count += content.syncMessage ? 1 : 0;
|
||||||
|
count += content.dataMessage ? 1 : 0;
|
||||||
|
count += content.callMessage ? 1 : 0;
|
||||||
|
count += content.nullMessage ? 1 : 0;
|
||||||
|
if (count !== 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's most likely that dataMessage will be populated, so we look at it in detail
|
||||||
|
var data = content.dataMessage;
|
||||||
|
if (data && !data.attachments.length && !data.body && !data.expireTimer && !data.flags && !data.group) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
getRetryProto: function(message, timestamp) {
|
||||||
|
// If message was sent before v0.41.3 was released on Aug 7, then it was most certainly a DataMessage
|
||||||
|
//
|
||||||
|
// var d = new Date('2017-08-07T07:00:00.000Z');
|
||||||
|
// d.getTime();
|
||||||
|
var august7 = 1502089200000;
|
||||||
|
if (timestamp < august7) {
|
||||||
|
return textsecure.protobuf.DataMessage.decode(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is ugly. But we don't know what kind of proto we need to decode...
|
||||||
|
try {
|
||||||
|
// Simply decoding as a Content message may throw
|
||||||
|
var proto = textsecure.protobuf.Content.decode(message);
|
||||||
|
|
||||||
|
// But it might also result in an invalid object, so we try to detect that
|
||||||
|
if (this.validateRetryContentMessage(proto)) {
|
||||||
|
return proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
return textsecure.protobuf.DataMessage.decode(message);
|
||||||
|
} catch(e) {
|
||||||
|
// If this call throws, something has really gone wrong, we'll fail to send
|
||||||
|
return textsecure.protobuf.DataMessage.decode(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
tryMessageAgain: function(number, encodedMessage, timestamp) {
|
tryMessageAgain: function(number, encodedMessage, timestamp) {
|
||||||
var proto = textsecure.protobuf.Content.decode(encodedMessage);
|
var proto = this.getRetryProto(encodedMessage, timestamp);
|
||||||
return this.sendIndividualProto(number, proto, timestamp);
|
return this.sendIndividualProto(number, proto, timestamp);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue