Eslintify all of libtextsecure
This commit is contained in:
parent
4b3f9e969a
commit
0774ba2903
36 changed files with 1960 additions and 2128 deletions
|
@ -1,3 +1,7 @@
|
|||
/* global textsecure, libsignal, window, btoa */
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
|
||||
function OutgoingMessage(
|
||||
server,
|
||||
timestamp,
|
||||
|
@ -7,8 +11,9 @@ function OutgoingMessage(
|
|||
callback
|
||||
) {
|
||||
if (message instanceof textsecure.protobuf.DataMessage) {
|
||||
var content = new textsecure.protobuf.Content();
|
||||
const content = new textsecure.protobuf.Content();
|
||||
content.dataMessage = message;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
message = content;
|
||||
}
|
||||
this.server = server;
|
||||
|
@ -25,8 +30,8 @@ function OutgoingMessage(
|
|||
|
||||
OutgoingMessage.prototype = {
|
||||
constructor: OutgoingMessage,
|
||||
numberCompleted: function() {
|
||||
this.numbersCompleted++;
|
||||
numberCompleted() {
|
||||
this.numbersCompleted += 1;
|
||||
if (this.numbersCompleted >= this.numbers.length) {
|
||||
this.callback({
|
||||
successfulNumbers: this.successfulNumbers,
|
||||
|
@ -34,8 +39,9 @@ OutgoingMessage.prototype = {
|
|||
});
|
||||
}
|
||||
},
|
||||
registerError: function(number, reason, error) {
|
||||
registerError(number, reason, error) {
|
||||
if (!error || (error.name === 'HTTPError' && error.code !== 404)) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
error = new textsecure.OutgoingMessageError(
|
||||
number,
|
||||
this.message.toArrayBuffer(),
|
||||
|
@ -44,102 +50,94 @@ OutgoingMessage.prototype = {
|
|||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
error.number = number;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
error.reason = reason;
|
||||
this.errors[this.errors.length] = error;
|
||||
this.numberCompleted();
|
||||
},
|
||||
reloadDevicesAndSend: function(number, recurse) {
|
||||
return function() {
|
||||
return textsecure.storage.protocol.getDeviceIds(number).then(
|
||||
function(deviceIds) {
|
||||
if (deviceIds.length == 0) {
|
||||
return this.registerError(
|
||||
number,
|
||||
'Got empty device list when loading device keys',
|
||||
null
|
||||
);
|
||||
}
|
||||
return this.doSendMessage(number, deviceIds, recurse);
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this);
|
||||
reloadDevicesAndSend(number, recurse) {
|
||||
return () =>
|
||||
textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => {
|
||||
if (deviceIds.length === 0) {
|
||||
return this.registerError(
|
||||
number,
|
||||
'Got empty device list when loading device keys',
|
||||
null
|
||||
);
|
||||
}
|
||||
return this.doSendMessage(number, deviceIds, recurse);
|
||||
});
|
||||
},
|
||||
|
||||
getKeysForNumber: function(number, updateDevices) {
|
||||
var handleResult = function(response) {
|
||||
return Promise.all(
|
||||
response.devices.map(
|
||||
function(device) {
|
||||
device.identityKey = response.identityKey;
|
||||
if (
|
||||
updateDevices === undefined ||
|
||||
updateDevices.indexOf(device.deviceId) > -1
|
||||
) {
|
||||
var address = new libsignal.SignalProtocolAddress(
|
||||
number,
|
||||
device.deviceId
|
||||
);
|
||||
var builder = new libsignal.SessionBuilder(
|
||||
textsecure.storage.protocol,
|
||||
address
|
||||
);
|
||||
if (device.registrationId === 0) {
|
||||
window.log.info('device registrationId 0!');
|
||||
}
|
||||
return builder.processPreKey(device).catch(
|
||||
function(error) {
|
||||
if (error.message === 'Identity key changed') {
|
||||
error.timestamp = this.timestamp;
|
||||
error.originalMessage = this.message.toArrayBuffer();
|
||||
error.identityKey = device.identityKey;
|
||||
}
|
||||
throw error;
|
||||
}.bind(this)
|
||||
);
|
||||
getKeysForNumber(number, updateDevices) {
|
||||
const handleResult = response =>
|
||||
Promise.all(
|
||||
response.devices.map(device => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
device.identityKey = response.identityKey;
|
||||
if (
|
||||
updateDevices === undefined ||
|
||||
updateDevices.indexOf(device.deviceId) > -1
|
||||
) {
|
||||
const address = new libsignal.SignalProtocolAddress(
|
||||
number,
|
||||
device.deviceId
|
||||
);
|
||||
const builder = new libsignal.SessionBuilder(
|
||||
textsecure.storage.protocol,
|
||||
address
|
||||
);
|
||||
if (device.registrationId === 0) {
|
||||
window.log.info('device registrationId 0!');
|
||||
}
|
||||
}.bind(this)
|
||||
)
|
||||
return builder.processPreKey(device).catch(error => {
|
||||
if (error.message === 'Identity key changed') {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
error.timestamp = this.timestamp;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
error.originalMessage = this.message.toArrayBuffer();
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
error.identityKey = device.identityKey;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
);
|
||||
}.bind(this);
|
||||
|
||||
if (updateDevices === undefined) {
|
||||
return this.server.getKeysForNumber(number).then(handleResult);
|
||||
} else {
|
||||
var promise = Promise.resolve();
|
||||
updateDevices.forEach(
|
||||
function(device) {
|
||||
promise = promise.then(
|
||||
function() {
|
||||
return this.server
|
||||
.getKeysForNumber(number, device)
|
||||
.then(handleResult)
|
||||
.catch(
|
||||
function(e) {
|
||||
if (e.name === 'HTTPError' && e.code === 404) {
|
||||
if (device !== 1) {
|
||||
return this.removeDeviceIdsForNumber(number, [device]);
|
||||
} else {
|
||||
throw new textsecure.UnregisteredUserError(number, e);
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
return promise;
|
||||
}
|
||||
let promise = Promise.resolve();
|
||||
updateDevices.forEach(device => {
|
||||
promise = promise.then(() =>
|
||||
this.server
|
||||
.getKeysForNumber(number, device)
|
||||
.then(handleResult)
|
||||
.catch(e => {
|
||||
if (e.name === 'HTTPError' && e.code === 404) {
|
||||
if (device !== 1) {
|
||||
return this.removeDeviceIdsForNumber(number, [device]);
|
||||
}
|
||||
throw new textsecure.UnregisteredUserError(number, e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return promise;
|
||||
},
|
||||
|
||||
transmitMessage: function(number, jsonData, timestamp) {
|
||||
transmitMessage(number, jsonData, timestamp) {
|
||||
return this.server
|
||||
.sendMessages(number, jsonData, timestamp, this.silent)
|
||||
.catch(function(e) {
|
||||
.catch(e => {
|
||||
if (e.name === 'HTTPError' && (e.code !== 409 && e.code !== 410)) {
|
||||
// 409 and 410 should bubble and be handled by doSendMessage
|
||||
// 404 should throw UnregisteredUserError
|
||||
|
@ -158,20 +156,20 @@ OutgoingMessage.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
getPaddedMessageLength: function(messageLength) {
|
||||
var messageLengthWithTerminator = messageLength + 1;
|
||||
var messagePartCount = Math.floor(messageLengthWithTerminator / 160);
|
||||
getPaddedMessageLength(messageLength) {
|
||||
const messageLengthWithTerminator = messageLength + 1;
|
||||
let messagePartCount = Math.floor(messageLengthWithTerminator / 160);
|
||||
|
||||
if (messageLengthWithTerminator % 160 !== 0) {
|
||||
messagePartCount++;
|
||||
messagePartCount += 1;
|
||||
}
|
||||
|
||||
return messagePartCount * 160;
|
||||
},
|
||||
|
||||
getPlaintext: function() {
|
||||
getPlaintext() {
|
||||
if (!this.plaintext) {
|
||||
var messageBuffer = this.message.toArrayBuffer();
|
||||
const messageBuffer = this.message.toArrayBuffer();
|
||||
this.plaintext = new Uint8Array(
|
||||
this.getPaddedMessageLength(messageBuffer.byteLength + 1) - 1
|
||||
);
|
||||
|
@ -181,172 +179,154 @@ OutgoingMessage.prototype = {
|
|||
return this.plaintext;
|
||||
},
|
||||
|
||||
doSendMessage: function(number, deviceIds, recurse) {
|
||||
var ciphers = {};
|
||||
var plaintext = this.getPlaintext();
|
||||
doSendMessage(number, deviceIds, recurse) {
|
||||
const ciphers = {};
|
||||
const plaintext = this.getPlaintext();
|
||||
|
||||
return Promise.all(
|
||||
deviceIds.map(
|
||||
function(deviceId) {
|
||||
var address = new libsignal.SignalProtocolAddress(number, deviceId);
|
||||
deviceIds.map(deviceId => {
|
||||
const address = new libsignal.SignalProtocolAddress(number, deviceId);
|
||||
|
||||
var ourNumber = textsecure.storage.user.getNumber();
|
||||
var options = {};
|
||||
const ourNumber = textsecure.storage.user.getNumber();
|
||||
const options = {};
|
||||
|
||||
// No limit on message keys if we're communicating with our other devices
|
||||
if (ourNumber === number) {
|
||||
options.messageKeysLimit = false;
|
||||
}
|
||||
// No limit on message keys if we're communicating with our other devices
|
||||
if (ourNumber === number) {
|
||||
options.messageKeysLimit = false;
|
||||
}
|
||||
|
||||
var sessionCipher = new libsignal.SessionCipher(
|
||||
textsecure.storage.protocol,
|
||||
address,
|
||||
options
|
||||
);
|
||||
ciphers[address.getDeviceId()] = sessionCipher;
|
||||
return sessionCipher.encrypt(plaintext).then(function(ciphertext) {
|
||||
return {
|
||||
type: ciphertext.type,
|
||||
destinationDeviceId: address.getDeviceId(),
|
||||
destinationRegistrationId: ciphertext.registrationId,
|
||||
content: btoa(ciphertext.body),
|
||||
};
|
||||
});
|
||||
}.bind(this)
|
||||
)
|
||||
const sessionCipher = new libsignal.SessionCipher(
|
||||
textsecure.storage.protocol,
|
||||
address,
|
||||
options
|
||||
);
|
||||
ciphers[address.getDeviceId()] = sessionCipher;
|
||||
return sessionCipher.encrypt(plaintext).then(ciphertext => ({
|
||||
type: ciphertext.type,
|
||||
destinationDeviceId: address.getDeviceId(),
|
||||
destinationRegistrationId: ciphertext.registrationId,
|
||||
content: btoa(ciphertext.body),
|
||||
}));
|
||||
})
|
||||
)
|
||||
.then(
|
||||
function(jsonData) {
|
||||
return this.transmitMessage(number, jsonData, this.timestamp).then(
|
||||
function() {
|
||||
this.successfulNumbers[this.successfulNumbers.length] = number;
|
||||
this.numberCompleted();
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this)
|
||||
.then(jsonData =>
|
||||
this.transmitMessage(number, jsonData, this.timestamp).then(() => {
|
||||
this.successfulNumbers[this.successfulNumbers.length] = number;
|
||||
this.numberCompleted();
|
||||
})
|
||||
)
|
||||
.catch(
|
||||
function(error) {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
error.name == 'HTTPError' &&
|
||||
(error.code == 410 || error.code == 409)
|
||||
) {
|
||||
if (!recurse)
|
||||
return this.registerError(
|
||||
number,
|
||||
'Hit retry limit attempting to reload device list',
|
||||
error
|
||||
);
|
||||
|
||||
var p;
|
||||
if (error.code == 409) {
|
||||
p = this.removeDeviceIdsForNumber(
|
||||
number,
|
||||
error.response.extraDevices
|
||||
);
|
||||
} else {
|
||||
p = Promise.all(
|
||||
error.response.staleDevices.map(function(deviceId) {
|
||||
return ciphers[deviceId].closeOpenSessionForDevice();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return p.then(
|
||||
function() {
|
||||
var resetDevices =
|
||||
error.code == 410
|
||||
? error.response.staleDevices
|
||||
: error.response.missingDevices;
|
||||
return this.getKeysForNumber(number, resetDevices).then(
|
||||
this.reloadDevicesAndSend(number, error.code == 409)
|
||||
);
|
||||
}.bind(this)
|
||||
);
|
||||
} else if (error.message === 'Identity key changed') {
|
||||
error.timestamp = this.timestamp;
|
||||
error.originalMessage = this.message.toArrayBuffer();
|
||||
window.log.error(
|
||||
'Got "key changed" error from encrypt - no identityKey for application layer',
|
||||
.catch(error => {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
error.name === 'HTTPError' &&
|
||||
(error.code === 410 || error.code === 409)
|
||||
) {
|
||||
if (!recurse)
|
||||
return this.registerError(
|
||||
number,
|
||||
deviceIds
|
||||
);
|
||||
throw error;
|
||||
} else {
|
||||
this.registerError(
|
||||
number,
|
||||
'Failed to create or send message',
|
||||
'Hit retry limit attempting to reload device list',
|
||||
error
|
||||
);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
},
|
||||
|
||||
getStaleDeviceIdsForNumber: function(number) {
|
||||
return textsecure.storage.protocol
|
||||
.getDeviceIds(number)
|
||||
.then(function(deviceIds) {
|
||||
if (deviceIds.length === 0) {
|
||||
return [1];
|
||||
}
|
||||
var updateDevices = [];
|
||||
return Promise.all(
|
||||
deviceIds.map(function(deviceId) {
|
||||
var address = new libsignal.SignalProtocolAddress(number, deviceId);
|
||||
var sessionCipher = new libsignal.SessionCipher(
|
||||
textsecure.storage.protocol,
|
||||
address
|
||||
let p;
|
||||
if (error.code === 409) {
|
||||
p = this.removeDeviceIdsForNumber(
|
||||
number,
|
||||
error.response.extraDevices
|
||||
);
|
||||
return sessionCipher.hasOpenSession().then(function(hasSession) {
|
||||
if (!hasSession) {
|
||||
updateDevices.push(deviceId);
|
||||
}
|
||||
});
|
||||
})
|
||||
).then(function() {
|
||||
return updateDevices;
|
||||
});
|
||||
} else {
|
||||
p = Promise.all(
|
||||
error.response.staleDevices.map(deviceId =>
|
||||
ciphers[deviceId].closeOpenSessionForDevice()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return p.then(() => {
|
||||
const resetDevices =
|
||||
error.code === 410
|
||||
? error.response.staleDevices
|
||||
: error.response.missingDevices;
|
||||
return this.getKeysForNumber(number, resetDevices).then(
|
||||
this.reloadDevicesAndSend(number, error.code === 409)
|
||||
);
|
||||
});
|
||||
} else if (error.message === 'Identity key changed') {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
error.timestamp = this.timestamp;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
error.originalMessage = this.message.toArrayBuffer();
|
||||
window.log.error(
|
||||
'Got "key changed" error from encrypt - no identityKey for application layer',
|
||||
number,
|
||||
deviceIds
|
||||
);
|
||||
throw error;
|
||||
} else {
|
||||
this.registerError(number, 'Failed to create or send message', error);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
},
|
||||
|
||||
removeDeviceIdsForNumber: function(number, deviceIdsToRemove) {
|
||||
var promise = Promise.resolve();
|
||||
for (var j in deviceIdsToRemove) {
|
||||
promise = promise.then(function() {
|
||||
var encodedNumber = number + '.' + deviceIdsToRemove[j];
|
||||
getStaleDeviceIdsForNumber(number) {
|
||||
return textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => {
|
||||
if (deviceIds.length === 0) {
|
||||
return [1];
|
||||
}
|
||||
const updateDevices = [];
|
||||
return Promise.all(
|
||||
deviceIds.map(deviceId => {
|
||||
const address = new libsignal.SignalProtocolAddress(number, deviceId);
|
||||
const sessionCipher = new libsignal.SessionCipher(
|
||||
textsecure.storage.protocol,
|
||||
address
|
||||
);
|
||||
return sessionCipher.hasOpenSession().then(hasSession => {
|
||||
if (!hasSession) {
|
||||
updateDevices.push(deviceId);
|
||||
}
|
||||
});
|
||||
})
|
||||
).then(() => updateDevices);
|
||||
});
|
||||
},
|
||||
|
||||
removeDeviceIdsForNumber(number, deviceIdsToRemove) {
|
||||
let promise = Promise.resolve();
|
||||
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
||||
for (const j in deviceIdsToRemove) {
|
||||
promise = promise.then(() => {
|
||||
const encodedNumber = `${number}.${deviceIdsToRemove[j]}`;
|
||||
return textsecure.storage.protocol.removeSession(encodedNumber);
|
||||
});
|
||||
}
|
||||
return promise;
|
||||
},
|
||||
|
||||
sendToNumber: function(number) {
|
||||
return this.getStaleDeviceIdsForNumber(number).then(
|
||||
function(updateDevices) {
|
||||
return this.getKeysForNumber(number, updateDevices)
|
||||
.then(this.reloadDevicesAndSend(number, true))
|
||||
.catch(
|
||||
function(error) {
|
||||
if (error.message === 'Identity key changed') {
|
||||
error = new textsecure.OutgoingIdentityKeyError(
|
||||
number,
|
||||
error.originalMessage,
|
||||
error.timestamp,
|
||||
error.identityKey
|
||||
);
|
||||
this.registerError(number, 'Identity key changed', error);
|
||||
} else {
|
||||
this.registerError(
|
||||
number,
|
||||
'Failed to retrieve new device keys for number ' + number,
|
||||
error
|
||||
);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this)
|
||||
sendToNumber(number) {
|
||||
return this.getStaleDeviceIdsForNumber(number).then(updateDevices =>
|
||||
this.getKeysForNumber(number, updateDevices)
|
||||
.then(this.reloadDevicesAndSend(number, true))
|
||||
.catch(error => {
|
||||
if (error.message === 'Identity key changed') {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
error = new textsecure.OutgoingIdentityKeyError(
|
||||
number,
|
||||
error.originalMessage,
|
||||
error.timestamp,
|
||||
error.identityKey
|
||||
);
|
||||
this.registerError(number, 'Identity key changed', error);
|
||||
} else {
|
||||
this.registerError(
|
||||
number,
|
||||
`Failed to retrieve new device keys for number ${number}`,
|
||||
error
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue