Eslintify all of libtextsecure
This commit is contained in:
parent
4b3f9e969a
commit
0774ba2903
36 changed files with 1960 additions and 2128 deletions
|
@ -4,7 +4,7 @@ coverage/**
|
|||
dist/**
|
||||
|
||||
# these aren't ready yet, pulling files in one-by-one
|
||||
libtextsecure/**
|
||||
libtextsecure/test/*.js
|
||||
test/*.js
|
||||
test/models/*.js
|
||||
test/views/*.js
|
||||
|
|
|
@ -1,47 +1,46 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
/* global libsignal, textsecure */
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
function ProvisioningCipher() {}
|
||||
|
||||
ProvisioningCipher.prototype = {
|
||||
decrypt: function(provisionEnvelope) {
|
||||
var masterEphemeral = provisionEnvelope.publicKey.toArrayBuffer();
|
||||
var message = provisionEnvelope.body.toArrayBuffer();
|
||||
if (new Uint8Array(message)[0] != 1) {
|
||||
decrypt(provisionEnvelope) {
|
||||
const masterEphemeral = provisionEnvelope.publicKey.toArrayBuffer();
|
||||
const message = provisionEnvelope.body.toArrayBuffer();
|
||||
if (new Uint8Array(message)[0] !== 1) {
|
||||
throw new Error('Bad version number on ProvisioningMessage');
|
||||
}
|
||||
|
||||
var iv = message.slice(1, 16 + 1);
|
||||
var mac = message.slice(message.byteLength - 32, message.byteLength);
|
||||
var ivAndCiphertext = message.slice(0, message.byteLength - 32);
|
||||
var ciphertext = message.slice(16 + 1, message.byteLength - 32);
|
||||
const iv = message.slice(1, 16 + 1);
|
||||
const mac = message.slice(message.byteLength - 32, message.byteLength);
|
||||
const ivAndCiphertext = message.slice(0, message.byteLength - 32);
|
||||
const ciphertext = message.slice(16 + 1, message.byteLength - 32);
|
||||
|
||||
return libsignal.Curve.async
|
||||
.calculateAgreement(masterEphemeral, this.keyPair.privKey)
|
||||
.then(function(ecRes) {
|
||||
return libsignal.HKDF.deriveSecrets(
|
||||
.then(ecRes =>
|
||||
libsignal.HKDF.deriveSecrets(
|
||||
ecRes,
|
||||
new ArrayBuffer(32),
|
||||
'TextSecure Provisioning Message'
|
||||
);
|
||||
})
|
||||
.then(function(keys) {
|
||||
return libsignal.crypto
|
||||
)
|
||||
)
|
||||
.then(keys =>
|
||||
libsignal.crypto
|
||||
.verifyMAC(ivAndCiphertext, keys[1], mac, 32)
|
||||
.then(function() {
|
||||
return libsignal.crypto.decrypt(keys[0], ciphertext, iv);
|
||||
});
|
||||
})
|
||||
.then(function(plaintext) {
|
||||
var provisionMessage = textsecure.protobuf.ProvisionMessage.decode(
|
||||
.then(() => libsignal.crypto.decrypt(keys[0], ciphertext, iv))
|
||||
)
|
||||
.then(plaintext => {
|
||||
const provisionMessage = textsecure.protobuf.ProvisionMessage.decode(
|
||||
plaintext
|
||||
);
|
||||
var privKey = provisionMessage.identityKeyPrivate.toArrayBuffer();
|
||||
const privKey = provisionMessage.identityKeyPrivate.toArrayBuffer();
|
||||
|
||||
return libsignal.Curve.async
|
||||
.createKeyPair(privKey)
|
||||
.then(function(keyPair) {
|
||||
var ret = {
|
||||
return libsignal.Curve.async.createKeyPair(privKey).then(keyPair => {
|
||||
const ret = {
|
||||
identityKeyPair: keyPair,
|
||||
number: provisionMessage.number,
|
||||
provisioningCode: provisionMessage.provisioningCode,
|
||||
|
@ -55,29 +54,23 @@
|
|||
});
|
||||
});
|
||||
},
|
||||
getPublicKey: function() {
|
||||
getPublicKey() {
|
||||
return Promise.resolve()
|
||||
.then(
|
||||
function() {
|
||||
.then(() => {
|
||||
if (!this.keyPair) {
|
||||
return libsignal.Curve.async.generateKeyPair().then(
|
||||
function(keyPair) {
|
||||
return libsignal.Curve.async.generateKeyPair().then(keyPair => {
|
||||
this.keyPair = keyPair;
|
||||
}.bind(this)
|
||||
);
|
||||
});
|
||||
}
|
||||
}.bind(this)
|
||||
)
|
||||
.then(
|
||||
function() {
|
||||
return this.keyPair.pubKey;
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
return null;
|
||||
})
|
||||
.then(() => this.keyPair.pubKey);
|
||||
},
|
||||
};
|
||||
|
||||
libsignal.ProvisioningCipher = function() {
|
||||
var cipher = new ProvisioningCipher();
|
||||
libsignal.ProvisioningCipher = function ProvisioningCipherWrapper() {
|
||||
const cipher = new ProvisioningCipher();
|
||||
|
||||
this.decrypt = cipher.decrypt.bind(cipher);
|
||||
this.getPublicKey = cipher.getPublicKey.bind(cipher);
|
||||
|
|
|
@ -1,8 +1,21 @@
|
|||
/* global
|
||||
window,
|
||||
textsecure,
|
||||
libsignal,
|
||||
WebSocketResource,
|
||||
btoa,
|
||||
getString,
|
||||
libphonenumber,
|
||||
Event
|
||||
*/
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
'use strict';
|
||||
window.textsecure = window.textsecure || {};
|
||||
|
||||
var ARCHIVE_AGE = 7 * 24 * 60 * 60 * 1000;
|
||||
const ARCHIVE_AGE = 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
function AccountManager(username, password) {
|
||||
this.server = window.WebAPI.connect({ username, password });
|
||||
|
@ -14,7 +27,7 @@
|
|||
return numberId;
|
||||
}
|
||||
|
||||
var parts = numberId.split('.');
|
||||
const parts = numberId.split('.');
|
||||
if (!parts.length) {
|
||||
return numberId;
|
||||
}
|
||||
|
@ -25,24 +38,22 @@
|
|||
AccountManager.prototype = new textsecure.EventTarget();
|
||||
AccountManager.prototype.extend({
|
||||
constructor: AccountManager,
|
||||
requestVoiceVerification: function(number) {
|
||||
requestVoiceVerification(number) {
|
||||
return this.server.requestVerificationVoice(number);
|
||||
},
|
||||
requestSMSVerification: function(number) {
|
||||
requestSMSVerification(number) {
|
||||
return this.server.requestVerificationSMS(number);
|
||||
},
|
||||
registerSingleDevice: function(number, verificationCode) {
|
||||
var registerKeys = this.server.registerKeys.bind(this.server);
|
||||
var createAccount = this.createAccount.bind(this);
|
||||
var clearSessionsAndPreKeys = this.clearSessionsAndPreKeys.bind(this);
|
||||
var generateKeys = this.generateKeys.bind(this, 100);
|
||||
var confirmKeys = this.confirmKeys.bind(this);
|
||||
var registrationDone = this.registrationDone.bind(this);
|
||||
return this.queueTask(function() {
|
||||
return libsignal.KeyHelper.generateIdentityKeyPair().then(function(
|
||||
identityKeyPair
|
||||
) {
|
||||
var profileKey = textsecure.crypto.getRandomBytes(32);
|
||||
registerSingleDevice(number, verificationCode) {
|
||||
const registerKeys = this.server.registerKeys.bind(this.server);
|
||||
const createAccount = this.createAccount.bind(this);
|
||||
const clearSessionsAndPreKeys = this.clearSessionsAndPreKeys.bind(this);
|
||||
const generateKeys = this.generateKeys.bind(this, 100);
|
||||
const confirmKeys = this.confirmKeys.bind(this);
|
||||
const registrationDone = this.registrationDone.bind(this);
|
||||
return this.queueTask(() =>
|
||||
libsignal.KeyHelper.generateIdentityKeyPair().then(identityKeyPair => {
|
||||
const profileKey = textsecure.crypto.getRandomBytes(32);
|
||||
return createAccount(
|
||||
number,
|
||||
verificationCode,
|
||||
|
@ -51,47 +62,40 @@
|
|||
)
|
||||
.then(clearSessionsAndPreKeys)
|
||||
.then(generateKeys)
|
||||
.then(function(keys) {
|
||||
return registerKeys(keys).then(function() {
|
||||
return confirmKeys(keys);
|
||||
});
|
||||
})
|
||||
.then(keys => registerKeys(keys).then(() => confirmKeys(keys)))
|
||||
.then(registrationDone);
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
},
|
||||
registerSecondDevice: function(
|
||||
setProvisioningUrl,
|
||||
confirmNumber,
|
||||
progressCallback
|
||||
) {
|
||||
var createAccount = this.createAccount.bind(this);
|
||||
var clearSessionsAndPreKeys = this.clearSessionsAndPreKeys.bind(this);
|
||||
var generateKeys = this.generateKeys.bind(this, 100, progressCallback);
|
||||
var confirmKeys = this.confirmKeys.bind(this);
|
||||
var registrationDone = this.registrationDone.bind(this);
|
||||
var registerKeys = this.server.registerKeys.bind(this.server);
|
||||
var getSocket = this.server.getProvisioningSocket.bind(this.server);
|
||||
var queueTask = this.queueTask.bind(this);
|
||||
var provisioningCipher = new libsignal.ProvisioningCipher();
|
||||
var gotProvisionEnvelope = false;
|
||||
return provisioningCipher.getPublicKey().then(function(pubKey) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var socket = getSocket();
|
||||
socket.onclose = function(event) {
|
||||
registerSecondDevice(setProvisioningUrl, confirmNumber, progressCallback) {
|
||||
const createAccount = this.createAccount.bind(this);
|
||||
const clearSessionsAndPreKeys = this.clearSessionsAndPreKeys.bind(this);
|
||||
const generateKeys = this.generateKeys.bind(this, 100, progressCallback);
|
||||
const confirmKeys = this.confirmKeys.bind(this);
|
||||
const registrationDone = this.registrationDone.bind(this);
|
||||
const registerKeys = this.server.registerKeys.bind(this.server);
|
||||
const getSocket = this.server.getProvisioningSocket.bind(this.server);
|
||||
const queueTask = this.queueTask.bind(this);
|
||||
const provisioningCipher = new libsignal.ProvisioningCipher();
|
||||
let gotProvisionEnvelope = false;
|
||||
return provisioningCipher.getPublicKey().then(
|
||||
pubKey =>
|
||||
new Promise((resolve, reject) => {
|
||||
const socket = getSocket();
|
||||
socket.onclose = event => {
|
||||
window.log.info('provisioning socket closed. Code:', event.code);
|
||||
if (!gotProvisionEnvelope) {
|
||||
reject(new Error('websocket closed'));
|
||||
}
|
||||
};
|
||||
socket.onopen = function(e) {
|
||||
socket.onopen = () => {
|
||||
window.log.info('provisioning socket open');
|
||||
};
|
||||
var wsr = new WebSocketResource(socket, {
|
||||
const wsr = new WebSocketResource(socket, {
|
||||
keepalive: { path: '/v1/keepalive/provisioning' },
|
||||
handleRequest: function(request) {
|
||||
handleRequest(request) {
|
||||
if (request.path === '/v1/address' && request.verb === 'PUT') {
|
||||
var proto = textsecure.protobuf.ProvisioningUuid.decode(
|
||||
const proto = textsecure.protobuf.ProvisioningUuid.decode(
|
||||
request.body
|
||||
);
|
||||
setProvisioningUrl(
|
||||
|
@ -107,7 +111,7 @@
|
|||
request.path === '/v1/message' &&
|
||||
request.verb === 'PUT'
|
||||
) {
|
||||
var envelope = textsecure.protobuf.ProvisionEnvelope.decode(
|
||||
const envelope = textsecure.protobuf.ProvisionEnvelope.decode(
|
||||
request.body,
|
||||
'binary'
|
||||
);
|
||||
|
@ -117,10 +121,10 @@
|
|||
resolve(
|
||||
provisioningCipher
|
||||
.decrypt(envelope)
|
||||
.then(function(provisionMessage) {
|
||||
return queueTask(function() {
|
||||
return confirmNumber(provisionMessage.number).then(
|
||||
function(deviceName) {
|
||||
.then(provisionMessage =>
|
||||
queueTask(() =>
|
||||
confirmNumber(provisionMessage.number).then(
|
||||
deviceName => {
|
||||
if (
|
||||
typeof deviceName !== 'string' ||
|
||||
deviceName.length === 0
|
||||
|
@ -138,72 +142,68 @@
|
|||
)
|
||||
.then(clearSessionsAndPreKeys)
|
||||
.then(generateKeys)
|
||||
.then(function(keys) {
|
||||
return registerKeys(keys).then(function() {
|
||||
return confirmKeys(keys);
|
||||
});
|
||||
})
|
||||
.then(keys =>
|
||||
registerKeys(keys).then(() =>
|
||||
confirmKeys(keys)
|
||||
)
|
||||
)
|
||||
.then(registrationDone);
|
||||
}
|
||||
);
|
||||
});
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
window.log.error('Unknown websocket message', request.path);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
},
|
||||
refreshPreKeys: function() {
|
||||
var generateKeys = this.generateKeys.bind(this, 100);
|
||||
var registerKeys = this.server.registerKeys.bind(this.server);
|
||||
refreshPreKeys() {
|
||||
const generateKeys = this.generateKeys.bind(this, 100);
|
||||
const registerKeys = this.server.registerKeys.bind(this.server);
|
||||
|
||||
return this.queueTask(
|
||||
function() {
|
||||
return this.server.getMyKeys().then(function(preKeyCount) {
|
||||
window.log.info('prekey count ' + preKeyCount);
|
||||
return this.queueTask(() =>
|
||||
this.server.getMyKeys().then(preKeyCount => {
|
||||
window.log.info(`prekey count ${preKeyCount}`);
|
||||
if (preKeyCount < 10) {
|
||||
return generateKeys().then(registerKeys);
|
||||
}
|
||||
});
|
||||
}.bind(this)
|
||||
return null;
|
||||
})
|
||||
);
|
||||
},
|
||||
rotateSignedPreKey: function() {
|
||||
return this.queueTask(
|
||||
function() {
|
||||
var signedKeyId = textsecure.storage.get('signedKeyId', 1);
|
||||
if (typeof signedKeyId != 'number') {
|
||||
rotateSignedPreKey() {
|
||||
return this.queueTask(() => {
|
||||
const signedKeyId = textsecure.storage.get('signedKeyId', 1);
|
||||
if (typeof signedKeyId !== 'number') {
|
||||
throw new Error('Invalid signedKeyId');
|
||||
}
|
||||
|
||||
var store = textsecure.storage.protocol;
|
||||
var server = this.server;
|
||||
var cleanSignedPreKeys = this.cleanSignedPreKeys;
|
||||
const store = textsecure.storage.protocol;
|
||||
const { server, cleanSignedPreKeys } = this;
|
||||
|
||||
// TODO: harden this against missing identity key? Otherwise, we get
|
||||
// retries every five seconds.
|
||||
return store
|
||||
.getIdentityKeyPair()
|
||||
.then(
|
||||
function(identityKey) {
|
||||
return libsignal.KeyHelper.generateSignedPreKey(
|
||||
identityKey =>
|
||||
libsignal.KeyHelper.generateSignedPreKey(
|
||||
identityKey,
|
||||
signedKeyId
|
||||
);
|
||||
},
|
||||
function(error) {
|
||||
),
|
||||
() => {
|
||||
window.log.error(
|
||||
'Failed to get identity key. Canceling key rotation.'
|
||||
);
|
||||
}
|
||||
)
|
||||
.then(function(res) {
|
||||
.then(res => {
|
||||
if (!res) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
window.log.info('Saving new signed prekey', res.keyId);
|
||||
return Promise.all([
|
||||
|
@ -215,19 +215,17 @@
|
|||
signature: res.signature,
|
||||
}),
|
||||
])
|
||||
.then(function() {
|
||||
var confirmed = true;
|
||||
.then(() => {
|
||||
const confirmed = true;
|
||||
window.log.info('Confirming new signed prekey', res.keyId);
|
||||
return Promise.all([
|
||||
textsecure.storage.remove('signedKeyRotationRejected'),
|
||||
store.storeSignedPreKey(res.keyId, res.keyPair, confirmed),
|
||||
]);
|
||||
})
|
||||
.then(function() {
|
||||
return cleanSignedPreKeys();
|
||||
});
|
||||
.then(() => cleanSignedPreKeys());
|
||||
})
|
||||
.catch(function(e) {
|
||||
.catch(e => {
|
||||
window.log.error(
|
||||
'rotateSignedPrekey error:',
|
||||
e && e.stack ? e.stack : e
|
||||
|
@ -235,11 +233,11 @@
|
|||
|
||||
if (
|
||||
e instanceof Error &&
|
||||
e.name == 'HTTPError' &&
|
||||
e.name === 'HTTPError' &&
|
||||
e.code >= 400 &&
|
||||
e.code <= 599
|
||||
) {
|
||||
var rejections =
|
||||
const rejections =
|
||||
1 + textsecure.storage.get('signedKeyRotationRejected', 0);
|
||||
textsecure.storage.put('signedKeyRotationRejected', rejections);
|
||||
window.log.error(
|
||||
|
@ -250,35 +248,27 @@
|
|||
throw e;
|
||||
}
|
||||
});
|
||||
}.bind(this)
|
||||
);
|
||||
});
|
||||
},
|
||||
queueTask: function(task) {
|
||||
var taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
||||
return (this.pending = this.pending.then(
|
||||
taskWithTimeout,
|
||||
taskWithTimeout
|
||||
));
|
||||
},
|
||||
cleanSignedPreKeys: function() {
|
||||
var MINIMUM_KEYS = 3;
|
||||
var store = textsecure.storage.protocol;
|
||||
return store.loadSignedPreKeys().then(function(allKeys) {
|
||||
allKeys.sort(function(a, b) {
|
||||
return (a.created_at || 0) - (b.created_at || 0);
|
||||
});
|
||||
allKeys.reverse(); // we want the most recent first
|
||||
var confirmed = allKeys.filter(function(key) {
|
||||
return key.confirmed;
|
||||
});
|
||||
var unconfirmed = allKeys.filter(function(key) {
|
||||
return !key.confirmed;
|
||||
});
|
||||
queueTask(task) {
|
||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
||||
this.pending = this.pending.then(taskWithTimeout, taskWithTimeout);
|
||||
|
||||
var recent = allKeys[0] ? allKeys[0].keyId : 'none';
|
||||
var recentConfirmed = confirmed[0] ? confirmed[0].keyId : 'none';
|
||||
window.log.info('Most recent signed key: ' + recent);
|
||||
window.log.info('Most recent confirmed signed key: ' + recentConfirmed);
|
||||
return this.pending;
|
||||
},
|
||||
cleanSignedPreKeys() {
|
||||
const MINIMUM_KEYS = 3;
|
||||
const store = textsecure.storage.protocol;
|
||||
return store.loadSignedPreKeys().then(allKeys => {
|
||||
allKeys.sort((a, b) => (a.created_at || 0) - (b.created_at || 0));
|
||||
allKeys.reverse(); // we want the most recent first
|
||||
let confirmed = allKeys.filter(key => key.confirmed);
|
||||
const unconfirmed = allKeys.filter(key => !key.confirmed);
|
||||
|
||||
const recent = allKeys[0] ? allKeys[0].keyId : 'none';
|
||||
const recentConfirmed = confirmed[0] ? confirmed[0].keyId : 'none';
|
||||
window.log.info(`Most recent signed key: ${recent}`);
|
||||
window.log.info(`Most recent confirmed signed key: ${recentConfirmed}`);
|
||||
window.log.info(
|
||||
'Total signed key count:',
|
||||
allKeys.length,
|
||||
|
@ -287,51 +277,52 @@
|
|||
'confirmed'
|
||||
);
|
||||
|
||||
var confirmedCount = confirmed.length;
|
||||
let confirmedCount = confirmed.length;
|
||||
|
||||
// Keep MINIMUM_KEYS confirmed keys, then drop if older than a week
|
||||
confirmed = confirmed.forEach(function(key, index) {
|
||||
confirmed = confirmed.forEach((key, index) => {
|
||||
if (index < MINIMUM_KEYS) {
|
||||
return;
|
||||
}
|
||||
var created_at = key.created_at || 0;
|
||||
var age = Date.now() - created_at;
|
||||
const createdAt = key.created_at || 0;
|
||||
const age = Date.now() - createdAt;
|
||||
|
||||
if (age > ARCHIVE_AGE) {
|
||||
window.log.info(
|
||||
'Removing confirmed signed prekey:',
|
||||
key.keyId,
|
||||
'with timestamp:',
|
||||
created_at
|
||||
createdAt
|
||||
);
|
||||
store.removeSignedPreKey(key.keyId);
|
||||
confirmedCount--;
|
||||
confirmedCount -= 1;
|
||||
}
|
||||
});
|
||||
|
||||
var stillNeeded = MINIMUM_KEYS - confirmedCount;
|
||||
const stillNeeded = MINIMUM_KEYS - confirmedCount;
|
||||
|
||||
// If we still don't have enough total keys, we keep as many unconfirmed
|
||||
// keys as necessary. If not necessary, and over a week old, we drop.
|
||||
unconfirmed.forEach(function(key, index) {
|
||||
unconfirmed.forEach((key, index) => {
|
||||
if (index < stillNeeded) {
|
||||
return;
|
||||
}
|
||||
|
||||
var created_at = key.created_at || 0;
|
||||
var age = Date.now() - created_at;
|
||||
const createdAt = key.created_at || 0;
|
||||
const age = Date.now() - createdAt;
|
||||
if (age > ARCHIVE_AGE) {
|
||||
window.log.info(
|
||||
'Removing unconfirmed signed prekey:',
|
||||
key.keyId,
|
||||
'with timestamp:',
|
||||
created_at
|
||||
createdAt
|
||||
);
|
||||
store.removeSignedPreKey(key.keyId);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
createAccount: function(
|
||||
createAccount(
|
||||
number,
|
||||
verificationCode,
|
||||
identityKeyPair,
|
||||
|
@ -340,12 +331,12 @@
|
|||
userAgent,
|
||||
readReceipts
|
||||
) {
|
||||
var signalingKey = libsignal.crypto.getRandomBytes(32 + 20);
|
||||
var password = btoa(getString(libsignal.crypto.getRandomBytes(16)));
|
||||
const signalingKey = libsignal.crypto.getRandomBytes(32 + 20);
|
||||
let password = btoa(getString(libsignal.crypto.getRandomBytes(16)));
|
||||
password = password.substring(0, password.length - 2);
|
||||
var registrationId = libsignal.KeyHelper.generateRegistrationId();
|
||||
const registrationId = libsignal.KeyHelper.generateRegistrationId();
|
||||
|
||||
var previousNumber = getNumber(textsecure.storage.get('number_id'));
|
||||
const previousNumber = getNumber(textsecure.storage.get('number_id'));
|
||||
|
||||
return this.server
|
||||
.confirmCode(
|
||||
|
@ -356,18 +347,18 @@
|
|||
registrationId,
|
||||
deviceName
|
||||
)
|
||||
.then(function(response) {
|
||||
.then(response => {
|
||||
if (previousNumber && previousNumber !== number) {
|
||||
window.log.warn(
|
||||
'New number is different from old number; deleting all previous data'
|
||||
);
|
||||
|
||||
return textsecure.storage.protocol.removeAllData().then(
|
||||
function() {
|
||||
() => {
|
||||
window.log.info('Successfully deleted previous data');
|
||||
return response;
|
||||
},
|
||||
function(error) {
|
||||
error => {
|
||||
window.log.error(
|
||||
'Something went wrong deleting data from previous number',
|
||||
error && error.stack ? error.stack : error
|
||||
|
@ -380,8 +371,7 @@
|
|||
|
||||
return response;
|
||||
})
|
||||
.then(
|
||||
function(response) {
|
||||
.then(response => {
|
||||
textsecure.storage.remove('identityKey');
|
||||
textsecure.storage.remove('signaling_key');
|
||||
textsecure.storage.remove('password');
|
||||
|
@ -429,11 +419,10 @@
|
|||
'regionCode',
|
||||
libphonenumber.util.getRegionCodeForNumber(number)
|
||||
);
|
||||
}.bind(this)
|
||||
);
|
||||
});
|
||||
},
|
||||
clearSessionsAndPreKeys: function() {
|
||||
var store = textsecure.storage.protocol;
|
||||
clearSessionsAndPreKeys() {
|
||||
const store = textsecure.storage.protocol;
|
||||
|
||||
window.log.info('clearing all sessions, prekeys, and signed prekeys');
|
||||
return Promise.all([
|
||||
|
@ -443,37 +432,37 @@
|
|||
]);
|
||||
},
|
||||
// Takes the same object returned by generateKeys
|
||||
confirmKeys: function(keys) {
|
||||
var store = textsecure.storage.protocol;
|
||||
var key = keys.signedPreKey;
|
||||
var confirmed = true;
|
||||
confirmKeys(keys) {
|
||||
const store = textsecure.storage.protocol;
|
||||
const key = keys.signedPreKey;
|
||||
const confirmed = true;
|
||||
|
||||
window.log.info('confirmKeys: confirming key', key.keyId);
|
||||
return store.storeSignedPreKey(key.keyId, key.keyPair, confirmed);
|
||||
},
|
||||
generateKeys: function(count, progressCallback) {
|
||||
if (typeof progressCallback !== 'function') {
|
||||
progressCallback = undefined;
|
||||
}
|
||||
var startId = textsecure.storage.get('maxPreKeyId', 1);
|
||||
var signedKeyId = textsecure.storage.get('signedKeyId', 1);
|
||||
generateKeys(count, providedProgressCallback) {
|
||||
const progressCallback =
|
||||
typeof providedProgressCallback === 'function'
|
||||
? providedProgressCallback
|
||||
: null;
|
||||
const startId = textsecure.storage.get('maxPreKeyId', 1);
|
||||
const signedKeyId = textsecure.storage.get('signedKeyId', 1);
|
||||
|
||||
if (typeof startId != 'number') {
|
||||
if (typeof startId !== 'number') {
|
||||
throw new Error('Invalid maxPreKeyId');
|
||||
}
|
||||
if (typeof signedKeyId != 'number') {
|
||||
if (typeof signedKeyId !== 'number') {
|
||||
throw new Error('Invalid signedKeyId');
|
||||
}
|
||||
|
||||
var store = textsecure.storage.protocol;
|
||||
return store.getIdentityKeyPair().then(
|
||||
function(identityKey) {
|
||||
var result = { preKeys: [], identityKey: identityKey.pubKey };
|
||||
var promises = [];
|
||||
const store = textsecure.storage.protocol;
|
||||
return store.getIdentityKeyPair().then(identityKey => {
|
||||
const result = { preKeys: [], identityKey: identityKey.pubKey };
|
||||
const promises = [];
|
||||
|
||||
for (var keyId = startId; keyId < startId + count; ++keyId) {
|
||||
for (let keyId = startId; keyId < startId + count; keyId += 1) {
|
||||
promises.push(
|
||||
libsignal.KeyHelper.generatePreKey(keyId).then(function(res) {
|
||||
libsignal.KeyHelper.generatePreKey(keyId).then(res => {
|
||||
store.storePreKey(res.keyId, res.keyPair);
|
||||
result.preKeys.push({
|
||||
keyId: res.keyId,
|
||||
|
@ -490,7 +479,7 @@
|
|||
libsignal.KeyHelper.generateSignedPreKey(
|
||||
identityKey,
|
||||
signedKeyId
|
||||
).then(function(res) {
|
||||
).then(res => {
|
||||
store.storeSignedPreKey(res.keyId, res.keyPair);
|
||||
result.signedPreKey = {
|
||||
keyId: res.keyId,
|
||||
|
@ -504,18 +493,13 @@
|
|||
|
||||
textsecure.storage.put('maxPreKeyId', startId + count);
|
||||
textsecure.storage.put('signedKeyId', signedKeyId + 1);
|
||||
return Promise.all(promises).then(
|
||||
function() {
|
||||
return Promise.all(promises).then(() =>
|
||||
// This is primarily for the signed prekey summary it logs out
|
||||
return this.cleanSignedPreKeys().then(function() {
|
||||
return result;
|
||||
this.cleanSignedPreKeys().then(() => result)
|
||||
);
|
||||
});
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this)
|
||||
);
|
||||
},
|
||||
registrationDone: function() {
|
||||
registrationDone() {
|
||||
window.log.info('registration done');
|
||||
this.dispatchEvent(new Event('registration'));
|
||||
},
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/* global dcodeIO, window, textsecure */
|
||||
|
||||
function ProtoParser(arrayBuffer, protobuf) {
|
||||
this.protobuf = protobuf;
|
||||
this.buffer = new dcodeIO.ByteBuffer();
|
||||
|
@ -7,23 +9,23 @@ function ProtoParser(arrayBuffer, protobuf) {
|
|||
}
|
||||
ProtoParser.prototype = {
|
||||
constructor: ProtoParser,
|
||||
next: function() {
|
||||
next() {
|
||||
try {
|
||||
if (this.buffer.limit === this.buffer.offset) {
|
||||
return undefined; // eof
|
||||
}
|
||||
var len = this.buffer.readVarint32();
|
||||
var nextBuffer = this.buffer
|
||||
const len = this.buffer.readVarint32();
|
||||
const nextBuffer = this.buffer
|
||||
.slice(this.buffer.offset, this.buffer.offset + len)
|
||||
.toArrayBuffer();
|
||||
// TODO: de-dupe ByteBuffer.js includes in libaxo/libts
|
||||
// then remove this toArrayBuffer call.
|
||||
|
||||
var proto = this.protobuf.decode(nextBuffer);
|
||||
const proto = this.protobuf.decode(nextBuffer);
|
||||
this.buffer.skip(len);
|
||||
|
||||
if (proto.avatar) {
|
||||
var attachmentLen = proto.avatar.length;
|
||||
const attachmentLen = proto.avatar.length;
|
||||
proto.avatar.data = this.buffer
|
||||
.slice(this.buffer.offset, this.buffer.offset + attachmentLen)
|
||||
.toArrayBuffer();
|
||||
|
@ -41,14 +43,16 @@ ProtoParser.prototype = {
|
|||
error && error.stack ? error.stack : error
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
};
|
||||
var GroupBuffer = function(arrayBuffer) {
|
||||
const GroupBuffer = function Constructor(arrayBuffer) {
|
||||
ProtoParser.call(this, arrayBuffer, textsecure.protobuf.GroupDetails);
|
||||
};
|
||||
GroupBuffer.prototype = Object.create(ProtoParser.prototype);
|
||||
GroupBuffer.prototype.constructor = GroupBuffer;
|
||||
var ContactBuffer = function(arrayBuffer) {
|
||||
const ContactBuffer = function Constructor(arrayBuffer) {
|
||||
ProtoParser.call(this, arrayBuffer, textsecure.protobuf.ContactDetails);
|
||||
};
|
||||
ContactBuffer.prototype = Object.create(ProtoParser.prototype);
|
||||
|
|
|
@ -1,25 +1,23 @@
|
|||
/* global libsignal, crypto, textsecure, dcodeIO, window */
|
||||
|
||||
/* eslint-disable more/no-then, no-bitwise */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
'use strict';
|
||||
const { encrypt, decrypt, calculateMAC, verifyMAC } = libsignal.crypto;
|
||||
|
||||
var encrypt = libsignal.crypto.encrypt;
|
||||
var decrypt = libsignal.crypto.decrypt;
|
||||
var calculateMAC = libsignal.crypto.calculateMAC;
|
||||
var verifyMAC = libsignal.crypto.verifyMAC;
|
||||
|
||||
var PROFILE_IV_LENGTH = 12; // bytes
|
||||
var PROFILE_KEY_LENGTH = 32; // bytes
|
||||
var PROFILE_TAG_LENGTH = 128; // bits
|
||||
var PROFILE_NAME_PADDED_LENGTH = 26; // bytes
|
||||
const PROFILE_IV_LENGTH = 12; // bytes
|
||||
const PROFILE_KEY_LENGTH = 32; // bytes
|
||||
const PROFILE_TAG_LENGTH = 128; // bits
|
||||
const PROFILE_NAME_PADDED_LENGTH = 26; // bytes
|
||||
|
||||
function verifyDigest(data, theirDigest) {
|
||||
return crypto.subtle
|
||||
.digest({ name: 'SHA-256' }, data)
|
||||
.then(function(ourDigest) {
|
||||
var a = new Uint8Array(ourDigest);
|
||||
var b = new Uint8Array(theirDigest);
|
||||
var result = 0;
|
||||
for (var i = 0; i < theirDigest.byteLength; ++i) {
|
||||
result = result | (a[i] ^ b[i]);
|
||||
return crypto.subtle.digest({ name: 'SHA-256' }, data).then(ourDigest => {
|
||||
const a = new Uint8Array(ourDigest);
|
||||
const b = new Uint8Array(theirDigest);
|
||||
let result = 0;
|
||||
for (let i = 0; i < theirDigest.byteLength; i += 1) {
|
||||
result |= a[i] ^ b[i];
|
||||
}
|
||||
if (result !== 0) {
|
||||
throw new Error('Bad digest');
|
||||
|
@ -33,127 +31,127 @@
|
|||
window.textsecure = window.textsecure || {};
|
||||
window.textsecure.crypto = {
|
||||
// Decrypts message into a raw string
|
||||
decryptWebsocketMessage: function(message, signaling_key) {
|
||||
var decodedMessage = message.toArrayBuffer();
|
||||
decryptWebsocketMessage(message, signalingKey) {
|
||||
const decodedMessage = message.toArrayBuffer();
|
||||
|
||||
if (signaling_key.byteLength != 52) {
|
||||
throw new Error('Got invalid length signaling_key');
|
||||
if (signalingKey.byteLength !== 52) {
|
||||
throw new Error('Got invalid length signalingKey');
|
||||
}
|
||||
if (decodedMessage.byteLength < 1 + 16 + 10) {
|
||||
throw new Error('Got invalid length message');
|
||||
}
|
||||
if (new Uint8Array(decodedMessage)[0] != 1) {
|
||||
throw new Error('Got bad version number: ' + decodedMessage[0]);
|
||||
if (new Uint8Array(decodedMessage)[0] !== 1) {
|
||||
throw new Error(`Got bad version number: ${decodedMessage[0]}`);
|
||||
}
|
||||
|
||||
var aes_key = signaling_key.slice(0, 32);
|
||||
var mac_key = signaling_key.slice(32, 32 + 20);
|
||||
const aesKey = signalingKey.slice(0, 32);
|
||||
const macKey = signalingKey.slice(32, 32 + 20);
|
||||
|
||||
var iv = decodedMessage.slice(1, 1 + 16);
|
||||
var ciphertext = decodedMessage.slice(
|
||||
const iv = decodedMessage.slice(1, 1 + 16);
|
||||
const ciphertext = decodedMessage.slice(
|
||||
1 + 16,
|
||||
decodedMessage.byteLength - 10
|
||||
);
|
||||
var ivAndCiphertext = decodedMessage.slice(
|
||||
const ivAndCiphertext = decodedMessage.slice(
|
||||
0,
|
||||
decodedMessage.byteLength - 10
|
||||
);
|
||||
var mac = decodedMessage.slice(
|
||||
const mac = decodedMessage.slice(
|
||||
decodedMessage.byteLength - 10,
|
||||
decodedMessage.byteLength
|
||||
);
|
||||
|
||||
return verifyMAC(ivAndCiphertext, mac_key, mac, 10).then(function() {
|
||||
return decrypt(aes_key, ciphertext, iv);
|
||||
});
|
||||
return verifyMAC(ivAndCiphertext, macKey, mac, 10).then(() =>
|
||||
decrypt(aesKey, ciphertext, iv)
|
||||
);
|
||||
},
|
||||
|
||||
decryptAttachment: function(encryptedBin, keys, theirDigest) {
|
||||
if (keys.byteLength != 64) {
|
||||
decryptAttachment(encryptedBin, keys, theirDigest) {
|
||||
if (keys.byteLength !== 64) {
|
||||
throw new Error('Got invalid length attachment keys');
|
||||
}
|
||||
if (encryptedBin.byteLength < 16 + 32) {
|
||||
throw new Error('Got invalid length attachment');
|
||||
}
|
||||
|
||||
var aes_key = keys.slice(0, 32);
|
||||
var mac_key = keys.slice(32, 64);
|
||||
const aesKey = keys.slice(0, 32);
|
||||
const macKey = keys.slice(32, 64);
|
||||
|
||||
var iv = encryptedBin.slice(0, 16);
|
||||
var ciphertext = encryptedBin.slice(16, encryptedBin.byteLength - 32);
|
||||
var ivAndCiphertext = encryptedBin.slice(0, encryptedBin.byteLength - 32);
|
||||
var mac = encryptedBin.slice(
|
||||
const iv = encryptedBin.slice(0, 16);
|
||||
const ciphertext = encryptedBin.slice(16, encryptedBin.byteLength - 32);
|
||||
const ivAndCiphertext = encryptedBin.slice(
|
||||
0,
|
||||
encryptedBin.byteLength - 32
|
||||
);
|
||||
const mac = encryptedBin.slice(
|
||||
encryptedBin.byteLength - 32,
|
||||
encryptedBin.byteLength
|
||||
);
|
||||
|
||||
return verifyMAC(ivAndCiphertext, mac_key, mac, 32)
|
||||
.then(function() {
|
||||
return verifyMAC(ivAndCiphertext, macKey, mac, 32)
|
||||
.then(() => {
|
||||
if (theirDigest !== null) {
|
||||
return verifyDigest(encryptedBin, theirDigest);
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.then(function() {
|
||||
return decrypt(aes_key, ciphertext, iv);
|
||||
});
|
||||
.then(() => decrypt(aesKey, ciphertext, iv));
|
||||
},
|
||||
|
||||
encryptAttachment: function(plaintext, keys, iv) {
|
||||
encryptAttachment(plaintext, keys, iv) {
|
||||
if (
|
||||
!(plaintext instanceof ArrayBuffer) &&
|
||||
!ArrayBuffer.isView(plaintext)
|
||||
) {
|
||||
throw new TypeError(
|
||||
'`plaintext` must be an `ArrayBuffer` or `ArrayBufferView`; got: ' +
|
||||
typeof plaintext
|
||||
`\`plaintext\` must be an \`ArrayBuffer\` or \`ArrayBufferView\`; got: ${typeof plaintext}`
|
||||
);
|
||||
}
|
||||
|
||||
if (keys.byteLength != 64) {
|
||||
if (keys.byteLength !== 64) {
|
||||
throw new Error('Got invalid length attachment keys');
|
||||
}
|
||||
if (iv.byteLength != 16) {
|
||||
if (iv.byteLength !== 16) {
|
||||
throw new Error('Got invalid length attachment iv');
|
||||
}
|
||||
var aes_key = keys.slice(0, 32);
|
||||
var mac_key = keys.slice(32, 64);
|
||||
const aesKey = keys.slice(0, 32);
|
||||
const macKey = keys.slice(32, 64);
|
||||
|
||||
return encrypt(aes_key, plaintext, iv).then(function(ciphertext) {
|
||||
var ivAndCiphertext = new Uint8Array(16 + ciphertext.byteLength);
|
||||
return encrypt(aesKey, plaintext, iv).then(ciphertext => {
|
||||
const ivAndCiphertext = new Uint8Array(16 + ciphertext.byteLength);
|
||||
ivAndCiphertext.set(new Uint8Array(iv));
|
||||
ivAndCiphertext.set(new Uint8Array(ciphertext), 16);
|
||||
|
||||
return calculateMAC(mac_key, ivAndCiphertext.buffer).then(function(
|
||||
mac
|
||||
) {
|
||||
var encryptedBin = new Uint8Array(16 + ciphertext.byteLength + 32);
|
||||
return calculateMAC(macKey, ivAndCiphertext.buffer).then(mac => {
|
||||
const encryptedBin = new Uint8Array(16 + ciphertext.byteLength + 32);
|
||||
encryptedBin.set(ivAndCiphertext);
|
||||
encryptedBin.set(new Uint8Array(mac), 16 + ciphertext.byteLength);
|
||||
return calculateDigest(encryptedBin.buffer).then(function(digest) {
|
||||
return { ciphertext: encryptedBin.buffer, digest: digest };
|
||||
});
|
||||
return calculateDigest(encryptedBin.buffer).then(digest => ({
|
||||
ciphertext: encryptedBin.buffer,
|
||||
digest,
|
||||
}));
|
||||
});
|
||||
});
|
||||
},
|
||||
encryptProfile: function(data, key) {
|
||||
var iv = libsignal.crypto.getRandomBytes(PROFILE_IV_LENGTH);
|
||||
if (key.byteLength != PROFILE_KEY_LENGTH) {
|
||||
encryptProfile(data, key) {
|
||||
const iv = libsignal.crypto.getRandomBytes(PROFILE_IV_LENGTH);
|
||||
if (key.byteLength !== PROFILE_KEY_LENGTH) {
|
||||
throw new Error('Got invalid length profile key');
|
||||
}
|
||||
if (iv.byteLength != PROFILE_IV_LENGTH) {
|
||||
if (iv.byteLength !== PROFILE_IV_LENGTH) {
|
||||
throw new Error('Got invalid length profile iv');
|
||||
}
|
||||
return crypto.subtle
|
||||
.importKey('raw', key, { name: 'AES-GCM' }, false, ['encrypt'])
|
||||
.then(function(key) {
|
||||
return crypto.subtle
|
||||
.then(keyForEncryption =>
|
||||
crypto.subtle
|
||||
.encrypt(
|
||||
{ name: 'AES-GCM', iv: iv, tagLength: PROFILE_TAG_LENGTH },
|
||||
key,
|
||||
{ name: 'AES-GCM', iv, tagLength: PROFILE_TAG_LENGTH },
|
||||
keyForEncryption,
|
||||
data
|
||||
)
|
||||
.then(function(ciphertext) {
|
||||
var ivAndCiphertext = new Uint8Array(
|
||||
.then(ciphertext => {
|
||||
const ivAndCiphertext = new Uint8Array(
|
||||
PROFILE_IV_LENGTH + ciphertext.byteLength
|
||||
);
|
||||
ivAndCiphertext.set(new Uint8Array(iv));
|
||||
|
@ -162,32 +160,32 @@
|
|||
PROFILE_IV_LENGTH
|
||||
);
|
||||
return ivAndCiphertext.buffer;
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
},
|
||||
decryptProfile: function(data, key) {
|
||||
decryptProfile(data, key) {
|
||||
if (data.byteLength < 12 + 16 + 1) {
|
||||
throw new Error('Got too short input: ' + data.byteLength);
|
||||
throw new Error(`Got too short input: ${data.byteLength}`);
|
||||
}
|
||||
var iv = data.slice(0, PROFILE_IV_LENGTH);
|
||||
var ciphertext = data.slice(PROFILE_IV_LENGTH, data.byteLength);
|
||||
if (key.byteLength != PROFILE_KEY_LENGTH) {
|
||||
const iv = data.slice(0, PROFILE_IV_LENGTH);
|
||||
const ciphertext = data.slice(PROFILE_IV_LENGTH, data.byteLength);
|
||||
if (key.byteLength !== PROFILE_KEY_LENGTH) {
|
||||
throw new Error('Got invalid length profile key');
|
||||
}
|
||||
if (iv.byteLength != PROFILE_IV_LENGTH) {
|
||||
if (iv.byteLength !== PROFILE_IV_LENGTH) {
|
||||
throw new Error('Got invalid length profile iv');
|
||||
}
|
||||
var error = new Error(); // save stack
|
||||
const error = new Error(); // save stack
|
||||
return crypto.subtle
|
||||
.importKey('raw', key, { name: 'AES-GCM' }, false, ['decrypt'])
|
||||
.then(function(key) {
|
||||
return crypto.subtle
|
||||
.then(keyForEncryption =>
|
||||
crypto.subtle
|
||||
.decrypt(
|
||||
{ name: 'AES-GCM', iv: iv, tagLength: PROFILE_TAG_LENGTH },
|
||||
key,
|
||||
{ name: 'AES-GCM', iv, tagLength: PROFILE_TAG_LENGTH },
|
||||
keyForEncryption,
|
||||
ciphertext
|
||||
)
|
||||
.catch(function(e) {
|
||||
.catch(e => {
|
||||
if (e.name === 'OperationError') {
|
||||
// bad mac, basically.
|
||||
error.message =
|
||||
|
@ -195,26 +193,24 @@
|
|||
error.name = 'ProfileDecryptError';
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
},
|
||||
encryptProfileName: function(name, key) {
|
||||
var padded = new Uint8Array(PROFILE_NAME_PADDED_LENGTH);
|
||||
encryptProfileName(name, key) {
|
||||
const padded = new Uint8Array(PROFILE_NAME_PADDED_LENGTH);
|
||||
padded.set(new Uint8Array(name));
|
||||
return textsecure.crypto.encryptProfile(padded.buffer, key);
|
||||
},
|
||||
decryptProfileName: function(encryptedProfileName, key) {
|
||||
var data = dcodeIO.ByteBuffer.wrap(
|
||||
decryptProfileName(encryptedProfileName, key) {
|
||||
const data = dcodeIO.ByteBuffer.wrap(
|
||||
encryptedProfileName,
|
||||
'base64'
|
||||
).toArrayBuffer();
|
||||
return textsecure.crypto
|
||||
.decryptProfile(data, key)
|
||||
.then(function(decrypted) {
|
||||
return textsecure.crypto.decryptProfile(data, key).then(decrypted => {
|
||||
// unpad
|
||||
var name = '';
|
||||
var padded = new Uint8Array(decrypted);
|
||||
for (var i = padded.length; i > 0; i--) {
|
||||
const padded = new Uint8Array(decrypted);
|
||||
let i;
|
||||
for (i = padded.length; i > 0; i -= 1) {
|
||||
if (padded[i - 1] !== 0x00) {
|
||||
break;
|
||||
}
|
||||
|
@ -226,7 +222,7 @@
|
|||
});
|
||||
},
|
||||
|
||||
getRandomBytes: function(size) {
|
||||
getRandomBytes(size) {
|
||||
return libsignal.crypto.getRandomBytes(size);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
/* global window */
|
||||
|
||||
var registeredFunctions = {};
|
||||
var Type = {
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
const registeredFunctions = {};
|
||||
const Type = {
|
||||
ENCRYPT_MESSAGE: 1,
|
||||
INIT_SESSION: 2,
|
||||
TRANSMIT_MESSAGE: 3,
|
||||
|
@ -11,13 +12,14 @@
|
|||
};
|
||||
window.textsecure = window.textsecure || {};
|
||||
window.textsecure.replay = {
|
||||
Type: Type,
|
||||
registerFunction: function(func, functionCode) {
|
||||
Type,
|
||||
registerFunction(func, functionCode) {
|
||||
registeredFunctions[functionCode] = func;
|
||||
},
|
||||
};
|
||||
|
||||
function inherit(Parent, Child) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
Child.prototype = Object.create(Parent.prototype, {
|
||||
constructor: {
|
||||
value: Child,
|
||||
|
@ -27,11 +29,11 @@
|
|||
});
|
||||
}
|
||||
function appendStack(newError, originalError) {
|
||||
newError.stack += '\nOriginal stack:\n' + originalError.stack;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
newError.stack += `\nOriginal stack:\n${originalError.stack}`;
|
||||
}
|
||||
|
||||
function ReplayableError(options) {
|
||||
options = options || {};
|
||||
function ReplayableError(options = {}) {
|
||||
this.name = options.name || 'ReplayableError';
|
||||
this.message = options.message;
|
||||
|
||||
|
@ -48,13 +50,13 @@
|
|||
}
|
||||
inherit(Error, ReplayableError);
|
||||
|
||||
ReplayableError.prototype.replay = function() {
|
||||
var argumentsAsArray = Array.prototype.slice.call(arguments, 0);
|
||||
var args = this.args.concat(argumentsAsArray);
|
||||
ReplayableError.prototype.replay = function replay(...argumentsAsArray) {
|
||||
const args = this.args.concat(argumentsAsArray);
|
||||
return registeredFunctions[this.functionCode].apply(window, args);
|
||||
};
|
||||
|
||||
function IncomingIdentityKeyError(number, message, key) {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
this.number = number.split('.')[0];
|
||||
this.identityKey = key;
|
||||
|
||||
|
@ -62,12 +64,13 @@
|
|||
functionCode: Type.INIT_SESSION,
|
||||
args: [number, message],
|
||||
name: 'IncomingIdentityKeyError',
|
||||
message: 'The identity of ' + this.number + ' has changed.',
|
||||
message: `The identity of ${this.number} has changed.`,
|
||||
});
|
||||
}
|
||||
inherit(ReplayableError, IncomingIdentityKeyError);
|
||||
|
||||
function OutgoingIdentityKeyError(number, message, timestamp, identityKey) {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
this.number = number.split('.')[0];
|
||||
this.identityKey = identityKey;
|
||||
|
||||
|
@ -75,7 +78,7 @@
|
|||
functionCode: Type.ENCRYPT_MESSAGE,
|
||||
args: [number, message, timestamp],
|
||||
name: 'OutgoingIdentityKeyError',
|
||||
message: 'The identity of ' + this.number + ' has changed.',
|
||||
message: `The identity of ${this.number} has changed.`,
|
||||
});
|
||||
}
|
||||
inherit(ReplayableError, OutgoingIdentityKeyError);
|
||||
|
|
|
@ -1,27 +1,29 @@
|
|||
/* global window, Event, textsecure */
|
||||
|
||||
/*
|
||||
* Implements EventTarget
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
|
||||
*/
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
'use strict';
|
||||
window.textsecure = window.textsecure || {};
|
||||
|
||||
function EventTarget() {}
|
||||
|
||||
EventTarget.prototype = {
|
||||
constructor: EventTarget,
|
||||
dispatchEvent: function(ev) {
|
||||
dispatchEvent(ev) {
|
||||
if (!(ev instanceof Event)) {
|
||||
throw new Error('Expects an event');
|
||||
}
|
||||
if (this.listeners === null || typeof this.listeners !== 'object') {
|
||||
this.listeners = {};
|
||||
}
|
||||
var listeners = this.listeners[ev.type];
|
||||
var results = [];
|
||||
const listeners = this.listeners[ev.type];
|
||||
const results = [];
|
||||
if (typeof listeners === 'object') {
|
||||
for (var i = 0, max = listeners.length; i < max; i += 1) {
|
||||
var listener = listeners[i];
|
||||
for (let i = 0, max = listeners.length; i < max; i += 1) {
|
||||
const listener = listeners[i];
|
||||
if (typeof listener === 'function') {
|
||||
results.push(listener.call(null, ev));
|
||||
}
|
||||
|
@ -29,7 +31,7 @@
|
|||
}
|
||||
return results;
|
||||
},
|
||||
addEventListener: function(eventName, callback) {
|
||||
addEventListener(eventName, callback) {
|
||||
if (typeof eventName !== 'string') {
|
||||
throw new Error('First argument expects a string');
|
||||
}
|
||||
|
@ -39,14 +41,14 @@
|
|||
if (this.listeners === null || typeof this.listeners !== 'object') {
|
||||
this.listeners = {};
|
||||
}
|
||||
var listeners = this.listeners[eventName];
|
||||
let listeners = this.listeners[eventName];
|
||||
if (typeof listeners !== 'object') {
|
||||
listeners = [];
|
||||
}
|
||||
listeners.push(callback);
|
||||
this.listeners[eventName] = listeners;
|
||||
},
|
||||
removeEventListener: function(eventName, callback) {
|
||||
removeEventListener(eventName, callback) {
|
||||
if (typeof eventName !== 'string') {
|
||||
throw new Error('First argument expects a string');
|
||||
}
|
||||
|
@ -56,9 +58,9 @@
|
|||
if (this.listeners === null || typeof this.listeners !== 'object') {
|
||||
this.listeners = {};
|
||||
}
|
||||
var listeners = this.listeners[eventName];
|
||||
const listeners = this.listeners[eventName];
|
||||
if (typeof listeners === 'object') {
|
||||
for (var i = 0; i < listeners.length; ++i) {
|
||||
for (let i = 0; i < listeners.length; i += 1) {
|
||||
if (listeners[i] === callback) {
|
||||
listeners.splice(i, 1);
|
||||
return;
|
||||
|
@ -67,8 +69,9 @@
|
|||
}
|
||||
this.listeners[eventName] = listeners;
|
||||
},
|
||||
extend: function(obj) {
|
||||
for (var prop in obj) {
|
||||
extend(obj) {
|
||||
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
||||
for (const prop in obj) {
|
||||
this[prop] = obj[prop];
|
||||
}
|
||||
return this;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* global window, dcodeIO */
|
||||
|
||||
/* eslint-disable no-proto, no-restricted-syntax, guard-for-in */
|
||||
|
||||
window.textsecure = window.textsecure || {};
|
||||
|
||||
/** *******************************
|
||||
|
@ -6,16 +10,16 @@ window.textsecure = window.textsecure || {};
|
|||
// Strings/arrays
|
||||
// TODO: Throw all this shit in favor of consistent types
|
||||
// TODO: Namespace
|
||||
var StaticByteBufferProto = new dcodeIO.ByteBuffer().__proto__;
|
||||
var StaticArrayBufferProto = new ArrayBuffer().__proto__;
|
||||
var StaticUint8ArrayProto = new Uint8Array().__proto__;
|
||||
const StaticByteBufferProto = new dcodeIO.ByteBuffer().__proto__;
|
||||
const StaticArrayBufferProto = new ArrayBuffer().__proto__;
|
||||
const StaticUint8ArrayProto = new Uint8Array().__proto__;
|
||||
function getString(thing) {
|
||||
if (thing === Object(thing)) {
|
||||
if (thing.__proto__ == StaticUint8ArrayProto)
|
||||
if (thing.__proto__ === StaticUint8ArrayProto)
|
||||
return String.fromCharCode.apply(null, thing);
|
||||
if (thing.__proto__ == StaticArrayBufferProto)
|
||||
if (thing.__proto__ === StaticArrayBufferProto)
|
||||
return getString(new Uint8Array(thing));
|
||||
if (thing.__proto__ == StaticByteBufferProto)
|
||||
if (thing.__proto__ === StaticByteBufferProto)
|
||||
return thing.toString('binary');
|
||||
}
|
||||
return thing;
|
||||
|
@ -23,26 +27,22 @@ function getString(thing) {
|
|||
|
||||
function getStringable(thing) {
|
||||
return (
|
||||
typeof thing == 'string' ||
|
||||
typeof thing == 'number' ||
|
||||
typeof thing == 'boolean' ||
|
||||
typeof thing === 'string' ||
|
||||
typeof thing === 'number' ||
|
||||
typeof thing === 'boolean' ||
|
||||
(thing === Object(thing) &&
|
||||
(thing.__proto__ == StaticArrayBufferProto ||
|
||||
thing.__proto__ == StaticUint8ArrayProto ||
|
||||
thing.__proto__ == StaticByteBufferProto))
|
||||
(thing.__proto__ === StaticArrayBufferProto ||
|
||||
thing.__proto__ === StaticUint8ArrayProto ||
|
||||
thing.__proto__ === StaticByteBufferProto))
|
||||
);
|
||||
}
|
||||
|
||||
// Number formatting utils
|
||||
window.textsecure.utils = (function() {
|
||||
var self = {};
|
||||
self.unencodeNumber = function(number) {
|
||||
return number.split('.');
|
||||
};
|
||||
|
||||
self.isNumberSane = function(number) {
|
||||
return number[0] == '+' && /^[0-9]+$/.test(number.substring(1));
|
||||
};
|
||||
window.textsecure.utils = (() => {
|
||||
const self = {};
|
||||
self.unencodeNumber = number => number.split('.');
|
||||
self.isNumberSane = number =>
|
||||
number[0] === '+' && /^[0-9]+$/.test(number.substring(1));
|
||||
|
||||
/** ************************
|
||||
*** JSON'ing Utilities ***
|
||||
|
@ -50,22 +50,21 @@ window.textsecure.utils = (function() {
|
|||
function ensureStringed(thing) {
|
||||
if (getStringable(thing)) return getString(thing);
|
||||
else if (thing instanceof Array) {
|
||||
var res = [];
|
||||
for (var i = 0; i < thing.length; i++) res[i] = ensureStringed(thing[i]);
|
||||
const res = [];
|
||||
for (let i = 0; i < thing.length; i += 1)
|
||||
res[i] = ensureStringed(thing[i]);
|
||||
return res;
|
||||
} else if (thing === Object(thing)) {
|
||||
var res = {};
|
||||
for (var key in thing) res[key] = ensureStringed(thing[key]);
|
||||
const res = {};
|
||||
for (const key in thing) res[key] = ensureStringed(thing[key]);
|
||||
return res;
|
||||
} else if (thing === null) {
|
||||
return null;
|
||||
}
|
||||
throw new Error('unsure of how to jsonify object of type ' + typeof thing);
|
||||
throw new Error(`unsure of how to jsonify object of type ${typeof thing}`);
|
||||
}
|
||||
|
||||
self.jsonThing = function(thing) {
|
||||
return JSON.stringify(ensureStringed(thing));
|
||||
};
|
||||
self.jsonThing = thing => JSON.stringify(ensureStringed(thing));
|
||||
|
||||
return self;
|
||||
})();
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
'use strict';
|
||||
/* global window, postMessage, textsecure, close */
|
||||
|
||||
/* eslint-disable more/no-then, no-global-assign, no-restricted-globals, no-unused-vars */
|
||||
|
||||
/*
|
||||
* Load this script in a Web Worker to generate new prekeys without
|
||||
|
@ -25,34 +27,34 @@
|
|||
}
|
||||
};
|
||||
*/
|
||||
var store = {};
|
||||
let store = {};
|
||||
window.textsecure.storage.impl = {
|
||||
/** ***************************
|
||||
*** Override Storage Routines ***
|
||||
**************************** */
|
||||
put: function(key, value) {
|
||||
put(key, value) {
|
||||
if (value === undefined) throw new Error('Tried to store undefined');
|
||||
store[key] = value;
|
||||
postMessage({ method: 'set', key: key, value: value });
|
||||
postMessage({ method: 'set', key, value });
|
||||
},
|
||||
|
||||
get: function(key, defaultValue) {
|
||||
get(key, defaultValue) {
|
||||
if (key in store) {
|
||||
return store[key];
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
},
|
||||
|
||||
remove: function(key) {
|
||||
remove(key) {
|
||||
delete store[key];
|
||||
postMessage({ method: 'remove', key: key });
|
||||
postMessage({ method: 'remove', key });
|
||||
},
|
||||
};
|
||||
onmessage = function(e) {
|
||||
// eslint-disable-next-line no-undef
|
||||
onmessage = e => {
|
||||
store = e.data;
|
||||
textsecure.protocol_wrapper.generateKeys().then(function(keys) {
|
||||
postMessage({ method: 'done', keys: keys });
|
||||
textsecure.protocol_wrapper.generateKeys().then(keys => {
|
||||
postMessage({ method: 'done', keys });
|
||||
close();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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,16 +50,17 @@ 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) {
|
||||
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',
|
||||
|
@ -61,85 +68,76 @@ OutgoingMessage.prototype = {
|
|||
);
|
||||
}
|
||||
return this.doSendMessage(number, deviceIds, recurse);
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this);
|
||||
});
|
||||
},
|
||||
|
||||
getKeysForNumber: function(number, updateDevices) {
|
||||
var handleResult = function(response) {
|
||||
return Promise.all(
|
||||
response.devices.map(
|
||||
function(device) {
|
||||
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
|
||||
) {
|
||||
var address = new libsignal.SignalProtocolAddress(
|
||||
const address = new libsignal.SignalProtocolAddress(
|
||||
number,
|
||||
device.deviceId
|
||||
);
|
||||
var builder = new libsignal.SessionBuilder(
|
||||
const 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) {
|
||||
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;
|
||||
}.bind(this)
|
||||
);
|
||||
});
|
||||
}
|
||||
}.bind(this)
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
let promise = Promise.resolve();
|
||||
updateDevices.forEach(device => {
|
||||
promise = promise.then(() =>
|
||||
this.server
|
||||
.getKeysForNumber(number, device)
|
||||
.then(handleResult)
|
||||
.catch(
|
||||
function(e) {
|
||||
.catch(e => {
|
||||
if (e.name === 'HTTPError' && e.code === 404) {
|
||||
if (device !== 1) {
|
||||
return this.removeDeviceIdsForNumber(number, [device]);
|
||||
} else {
|
||||
throw new textsecure.UnregisteredUserError(number, e);
|
||||
}
|
||||
throw new textsecure.UnregisteredUserError(number, e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
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,56 +179,47 @@ 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;
|
||||
}
|
||||
|
||||
var sessionCipher = new libsignal.SessionCipher(
|
||||
const sessionCipher = new libsignal.SessionCipher(
|
||||
textsecure.storage.protocol,
|
||||
address,
|
||||
options
|
||||
);
|
||||
ciphers[address.getDeviceId()] = sessionCipher;
|
||||
return sessionCipher.encrypt(plaintext).then(function(ciphertext) {
|
||||
return {
|
||||
return sessionCipher.encrypt(plaintext).then(ciphertext => ({
|
||||
type: ciphertext.type,
|
||||
destinationDeviceId: address.getDeviceId(),
|
||||
destinationRegistrationId: ciphertext.registrationId,
|
||||
content: btoa(ciphertext.body),
|
||||
};
|
||||
});
|
||||
}.bind(this)
|
||||
}));
|
||||
})
|
||||
)
|
||||
)
|
||||
.then(
|
||||
function(jsonData) {
|
||||
return this.transmitMessage(number, jsonData, this.timestamp).then(
|
||||
function() {
|
||||
.then(jsonData =>
|
||||
this.transmitMessage(number, jsonData, this.timestamp).then(() => {
|
||||
this.successfulNumbers[this.successfulNumbers.length] = number;
|
||||
this.numberCompleted();
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this)
|
||||
})
|
||||
)
|
||||
.catch(
|
||||
function(error) {
|
||||
.catch(error => {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
error.name == 'HTTPError' &&
|
||||
(error.code == 410 || error.code == 409)
|
||||
error.name === 'HTTPError' &&
|
||||
(error.code === 410 || error.code === 409)
|
||||
) {
|
||||
if (!recurse)
|
||||
return this.registerError(
|
||||
|
@ -239,33 +228,33 @@ OutgoingMessage.prototype = {
|
|||
error
|
||||
);
|
||||
|
||||
var p;
|
||||
if (error.code == 409) {
|
||||
let 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();
|
||||
})
|
||||
error.response.staleDevices.map(deviceId =>
|
||||
ciphers[deviceId].closeOpenSessionForDevice()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return p.then(
|
||||
function() {
|
||||
var resetDevices =
|
||||
error.code == 410
|
||||
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)
|
||||
);
|
||||
}.bind(this)
|
||||
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',
|
||||
|
@ -274,62 +263,55 @@ OutgoingMessage.prototype = {
|
|||
);
|
||||
throw error;
|
||||
} else {
|
||||
this.registerError(
|
||||
number,
|
||||
'Failed to create or send message',
|
||||
error
|
||||
);
|
||||
this.registerError(number, 'Failed to create or send message', error);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
return null;
|
||||
});
|
||||
},
|
||||
|
||||
getStaleDeviceIdsForNumber: function(number) {
|
||||
return textsecure.storage.protocol
|
||||
.getDeviceIds(number)
|
||||
.then(function(deviceIds) {
|
||||
getStaleDeviceIdsForNumber(number) {
|
||||
return textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => {
|
||||
if (deviceIds.length === 0) {
|
||||
return [1];
|
||||
}
|
||||
var updateDevices = [];
|
||||
const updateDevices = [];
|
||||
return Promise.all(
|
||||
deviceIds.map(function(deviceId) {
|
||||
var address = new libsignal.SignalProtocolAddress(number, deviceId);
|
||||
var sessionCipher = new libsignal.SessionCipher(
|
||||
deviceIds.map(deviceId => {
|
||||
const address = new libsignal.SignalProtocolAddress(number, deviceId);
|
||||
const sessionCipher = new libsignal.SessionCipher(
|
||||
textsecure.storage.protocol,
|
||||
address
|
||||
);
|
||||
return sessionCipher.hasOpenSession().then(function(hasSession) {
|
||||
return sessionCipher.hasOpenSession().then(hasSession => {
|
||||
if (!hasSession) {
|
||||
updateDevices.push(deviceId);
|
||||
}
|
||||
});
|
||||
})
|
||||
).then(function() {
|
||||
return updateDevices;
|
||||
});
|
||||
).then(() => updateDevices);
|
||||
});
|
||||
},
|
||||
|
||||
removeDeviceIdsForNumber: function(number, deviceIdsToRemove) {
|
||||
var promise = Promise.resolve();
|
||||
for (var j in deviceIdsToRemove) {
|
||||
promise = promise.then(function() {
|
||||
var encodedNumber = number + '.' + deviceIdsToRemove[j];
|
||||
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)
|
||||
sendToNumber(number) {
|
||||
return this.getStaleDeviceIdsForNumber(number).then(updateDevices =>
|
||||
this.getKeysForNumber(number, updateDevices)
|
||||
.then(this.reloadDevicesAndSend(number, true))
|
||||
.catch(
|
||||
function(error) {
|
||||
.catch(error => {
|
||||
if (error.message === 'Identity key changed') {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
error = new textsecure.OutgoingIdentityKeyError(
|
||||
number,
|
||||
error.originalMessage,
|
||||
|
@ -340,13 +322,11 @@ OutgoingMessage.prototype = {
|
|||
} else {
|
||||
this.registerError(
|
||||
number,
|
||||
'Failed to retrieve new device keys for number ' + number,
|
||||
`Failed to retrieve new device keys for number ${number}`,
|
||||
error
|
||||
);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this)
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,35 +1,31 @@
|
|||
/* global window, dcodeIO, textsecure */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
'use strict';
|
||||
window.textsecure = window.textsecure || {};
|
||||
window.textsecure.protobuf = {};
|
||||
|
||||
function loadProtoBufs(filename) {
|
||||
return dcodeIO.ProtoBuf.loadProtoFile(
|
||||
{ root: window.PROTO_ROOT, file: filename },
|
||||
function(error, result) {
|
||||
(error, result) => {
|
||||
if (error) {
|
||||
var text =
|
||||
'Error loading protos from ' +
|
||||
filename +
|
||||
' (root: ' +
|
||||
window.PROTO_ROOT +
|
||||
') ' +
|
||||
(error && error.stack ? error.stack : error);
|
||||
const text = `Error loading protos from ${filename} (root: ${
|
||||
window.PROTO_ROOT
|
||||
}) ${error && error.stack ? error.stack : error}`;
|
||||
window.log.error(text);
|
||||
throw error;
|
||||
}
|
||||
var protos = result.build('signalservice');
|
||||
const protos = result.build('signalservice');
|
||||
if (!protos) {
|
||||
var text =
|
||||
'Error loading protos from ' +
|
||||
filename +
|
||||
' (root: ' +
|
||||
window.PROTO_ROOT +
|
||||
')';
|
||||
const text = `Error loading protos from ${filename} (root: ${
|
||||
window.PROTO_ROOT
|
||||
})`;
|
||||
window.log.error(text);
|
||||
throw new Error(text);
|
||||
}
|
||||
for (var protoName in protos) {
|
||||
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
||||
for (const protoName in protos) {
|
||||
textsecure.protobuf[protoName] = protos[protoName];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/* global window, textsecure, SignalProtocolStore, libsignal */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
'use strict';
|
||||
window.textsecure = window.textsecure || {};
|
||||
window.textsecure.storage = window.textsecure.storage || {};
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,6 @@
|
|||
'use strict';
|
||||
/* global window, textsecure, localStorage */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
/** **********************************************
|
||||
*** Utilities to store data in local storage ***
|
||||
|
@ -12,31 +13,25 @@
|
|||
/** ***************************
|
||||
*** Base Storage Routines ***
|
||||
**************************** */
|
||||
put: function(key, value) {
|
||||
put(key, value) {
|
||||
if (value === undefined) throw new Error('Tried to store undefined');
|
||||
localStorage.setItem('' + key, textsecure.utils.jsonThing(value));
|
||||
localStorage.setItem(`${key}`, textsecure.utils.jsonThing(value));
|
||||
},
|
||||
|
||||
get: function(key, defaultValue) {
|
||||
var value = localStorage.getItem('' + key);
|
||||
get(key, defaultValue) {
|
||||
const value = localStorage.getItem(`${key}`);
|
||||
if (value === null) return defaultValue;
|
||||
return JSON.parse(value);
|
||||
},
|
||||
|
||||
remove: function(key) {
|
||||
localStorage.removeItem('' + key);
|
||||
remove(key) {
|
||||
localStorage.removeItem(`${key}`);
|
||||
},
|
||||
};
|
||||
|
||||
window.textsecure.storage.put = function(key, value) {
|
||||
return textsecure.storage.impl.put(key, value);
|
||||
};
|
||||
|
||||
window.textsecure.storage.get = function(key, defaultValue) {
|
||||
return textsecure.storage.impl.get(key, defaultValue);
|
||||
};
|
||||
|
||||
window.textsecure.storage.remove = function(key) {
|
||||
return textsecure.storage.impl.remove(key);
|
||||
};
|
||||
window.textsecure.storage.put = (key, value) =>
|
||||
textsecure.storage.impl.put(key, value);
|
||||
window.textsecure.storage.get = (key, defaultValue) =>
|
||||
textsecure.storage.impl.get(key, defaultValue);
|
||||
window.textsecure.storage.remove = key => textsecure.storage.impl.remove(key);
|
||||
})();
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
/* global window, getString, libsignal, textsecure */
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
/** *******************
|
||||
*** Group Storage ***
|
||||
******************** */
|
||||
|
@ -9,24 +12,22 @@
|
|||
|
||||
// create a random group id that we haven't seen before.
|
||||
function generateNewGroupId() {
|
||||
var groupId = getString(libsignal.crypto.getRandomBytes(16));
|
||||
return textsecure.storage.protocol.getGroup(groupId).then(function(group) {
|
||||
const groupId = getString(libsignal.crypto.getRandomBytes(16));
|
||||
return textsecure.storage.protocol.getGroup(groupId).then(group => {
|
||||
if (group === undefined) {
|
||||
return groupId;
|
||||
} else {
|
||||
console.warn('group id collision'); // probably a bad sign.
|
||||
return generateNewGroupId();
|
||||
}
|
||||
window.log.warn('group id collision'); // probably a bad sign.
|
||||
return generateNewGroupId();
|
||||
});
|
||||
}
|
||||
|
||||
window.textsecure.storage.groups = {
|
||||
createNewGroup: function(numbers, groupId) {
|
||||
var groupId = groupId;
|
||||
return new Promise(function(resolve) {
|
||||
createNewGroup(numbers, groupId) {
|
||||
return new Promise(resolve => {
|
||||
if (groupId !== undefined) {
|
||||
resolve(
|
||||
textsecure.storage.protocol.getGroup(groupId).then(function(group) {
|
||||
textsecure.storage.protocol.getGroup(groupId).then(group => {
|
||||
if (group !== undefined) {
|
||||
throw new Error('Tried to recreate group');
|
||||
}
|
||||
|
@ -34,128 +35,121 @@
|
|||
);
|
||||
} else {
|
||||
resolve(
|
||||
generateNewGroupId().then(function(newGroupId) {
|
||||
generateNewGroupId().then(newGroupId => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
groupId = newGroupId;
|
||||
})
|
||||
);
|
||||
}
|
||||
}).then(function() {
|
||||
var me = textsecure.storage.user.getNumber();
|
||||
var haveMe = false;
|
||||
var finalNumbers = [];
|
||||
for (var i in numbers) {
|
||||
var number = numbers[i];
|
||||
}).then(() => {
|
||||
const me = textsecure.storage.user.getNumber();
|
||||
let haveMe = false;
|
||||
const finalNumbers = [];
|
||||
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
||||
for (const i in numbers) {
|
||||
const number = numbers[i];
|
||||
if (!textsecure.utils.isNumberSane(number))
|
||||
throw new Error('Invalid number in group');
|
||||
if (number == me) haveMe = true;
|
||||
if (number === me) haveMe = true;
|
||||
if (finalNumbers.indexOf(number) < 0) finalNumbers.push(number);
|
||||
}
|
||||
|
||||
if (!haveMe) finalNumbers.push(me);
|
||||
|
||||
var groupObject = { numbers: finalNumbers, numberRegistrationIds: {} };
|
||||
for (var i in finalNumbers)
|
||||
const groupObject = {
|
||||
numbers: finalNumbers,
|
||||
numberRegistrationIds: {},
|
||||
};
|
||||
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
||||
for (const i in finalNumbers) {
|
||||
groupObject.numberRegistrationIds[finalNumbers[i]] = {};
|
||||
}
|
||||
|
||||
return textsecure.storage.protocol
|
||||
.putGroup(groupId, groupObject)
|
||||
.then(function() {
|
||||
return { id: groupId, numbers: finalNumbers };
|
||||
});
|
||||
.then(() => ({ id: groupId, numbers: finalNumbers }));
|
||||
});
|
||||
},
|
||||
|
||||
getNumbers: function(groupId) {
|
||||
return textsecure.storage.protocol
|
||||
.getGroup(groupId)
|
||||
.then(function(group) {
|
||||
getNumbers(groupId) {
|
||||
return textsecure.storage.protocol.getGroup(groupId).then(group => {
|
||||
if (group === undefined) return undefined;
|
||||
|
||||
return group.numbers;
|
||||
});
|
||||
},
|
||||
|
||||
removeNumber: function(groupId, number) {
|
||||
return textsecure.storage.protocol
|
||||
.getGroup(groupId)
|
||||
.then(function(group) {
|
||||
removeNumber(groupId, number) {
|
||||
return textsecure.storage.protocol.getGroup(groupId).then(group => {
|
||||
if (group === undefined) return undefined;
|
||||
|
||||
var me = textsecure.storage.user.getNumber();
|
||||
if (number == me)
|
||||
const me = textsecure.storage.user.getNumber();
|
||||
if (number === me)
|
||||
throw new Error(
|
||||
'Cannot remove ourselves from a group, leave the group instead'
|
||||
);
|
||||
|
||||
var i = group.numbers.indexOf(number);
|
||||
const i = group.numbers.indexOf(number);
|
||||
if (i > -1) {
|
||||
group.numbers.splice(i, 1);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
delete group.numberRegistrationIds[number];
|
||||
return textsecure.storage.protocol
|
||||
.putGroup(groupId, group)
|
||||
.then(function() {
|
||||
return group.numbers;
|
||||
});
|
||||
.then(() => group.numbers);
|
||||
}
|
||||
|
||||
return group.numbers;
|
||||
});
|
||||
},
|
||||
|
||||
addNumbers: function(groupId, numbers) {
|
||||
return textsecure.storage.protocol
|
||||
.getGroup(groupId)
|
||||
.then(function(group) {
|
||||
addNumbers(groupId, numbers) {
|
||||
return textsecure.storage.protocol.getGroup(groupId).then(group => {
|
||||
if (group === undefined) return undefined;
|
||||
|
||||
for (var i in numbers) {
|
||||
var number = numbers[i];
|
||||
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
||||
for (const i in numbers) {
|
||||
const number = numbers[i];
|
||||
if (!textsecure.utils.isNumberSane(number))
|
||||
throw new Error('Invalid number in set to add to group');
|
||||
if (group.numbers.indexOf(number) < 0) {
|
||||
group.numbers.push(number);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
group.numberRegistrationIds[number] = {};
|
||||
}
|
||||
}
|
||||
|
||||
return textsecure.storage.protocol
|
||||
.putGroup(groupId, group)
|
||||
.then(function() {
|
||||
return group.numbers;
|
||||
});
|
||||
.then(() => group.numbers);
|
||||
});
|
||||
},
|
||||
|
||||
deleteGroup: function(groupId) {
|
||||
deleteGroup(groupId) {
|
||||
return textsecure.storage.protocol.removeGroup(groupId);
|
||||
},
|
||||
|
||||
getGroup: function(groupId) {
|
||||
return textsecure.storage.protocol
|
||||
.getGroup(groupId)
|
||||
.then(function(group) {
|
||||
getGroup(groupId) {
|
||||
return textsecure.storage.protocol.getGroup(groupId).then(group => {
|
||||
if (group === undefined) return undefined;
|
||||
|
||||
return { id: groupId, numbers: group.numbers };
|
||||
});
|
||||
},
|
||||
|
||||
updateNumbers: function(groupId, numbers) {
|
||||
return textsecure.storage.protocol
|
||||
.getGroup(groupId)
|
||||
.then(function(group) {
|
||||
updateNumbers(groupId, numbers) {
|
||||
return textsecure.storage.protocol.getGroup(groupId).then(group => {
|
||||
if (group === undefined)
|
||||
throw new Error('Tried to update numbers for unknown group');
|
||||
|
||||
if (
|
||||
numbers.filter(textsecure.utils.isNumberSane).length <
|
||||
numbers.length
|
||||
numbers.filter(textsecure.utils.isNumberSane).length < numbers.length
|
||||
)
|
||||
throw new Error('Invalid number in new group members');
|
||||
|
||||
var added = numbers.filter(function(number) {
|
||||
return group.numbers.indexOf(number) < 0;
|
||||
});
|
||||
const added = numbers.filter(
|
||||
number => group.numbers.indexOf(number) < 0
|
||||
);
|
||||
|
||||
return textsecure.storage.groups.addNumbers(groupId, added);
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
/* global window, textsecure */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
/** ***************************************
|
||||
*** Not-yet-processed message storage ***
|
||||
**************************************** */
|
||||
|
@ -8,16 +9,16 @@
|
|||
window.textsecure.storage = window.textsecure.storage || {};
|
||||
|
||||
window.textsecure.storage.unprocessed = {
|
||||
getAll: function() {
|
||||
getAll() {
|
||||
return textsecure.storage.protocol.getAllUnprocessed();
|
||||
},
|
||||
add: function(data) {
|
||||
add(data) {
|
||||
return textsecure.storage.protocol.addUnprocessed(data);
|
||||
},
|
||||
update: function(id, updates) {
|
||||
update(id, updates) {
|
||||
return textsecure.storage.protocol.updateUnprocessed(id, updates);
|
||||
},
|
||||
remove: function(id) {
|
||||
remove(id) {
|
||||
return textsecure.storage.protocol.removeUnprocessed(id);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
'use strict';
|
||||
/* global textsecure, window */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
/** *******************************************
|
||||
*** Utilities to store data about the user ***
|
||||
|
@ -8,26 +9,26 @@
|
|||
window.textsecure.storage = window.textsecure.storage || {};
|
||||
|
||||
window.textsecure.storage.user = {
|
||||
setNumberAndDeviceId: function(number, deviceId, deviceName) {
|
||||
textsecure.storage.put('number_id', number + '.' + deviceId);
|
||||
setNumberAndDeviceId(number, deviceId, deviceName) {
|
||||
textsecure.storage.put('number_id', `${number}.${deviceId}`);
|
||||
if (deviceName) {
|
||||
textsecure.storage.put('device_name', deviceName);
|
||||
}
|
||||
},
|
||||
|
||||
getNumber: function(key, defaultValue) {
|
||||
var number_id = textsecure.storage.get('number_id');
|
||||
if (number_id === undefined) return undefined;
|
||||
return textsecure.utils.unencodeNumber(number_id)[0];
|
||||
getNumber() {
|
||||
const numberId = textsecure.storage.get('number_id');
|
||||
if (numberId === undefined) return undefined;
|
||||
return textsecure.utils.unencodeNumber(numberId)[0];
|
||||
},
|
||||
|
||||
getDeviceId: function(key) {
|
||||
var number_id = textsecure.storage.get('number_id');
|
||||
if (number_id === undefined) return undefined;
|
||||
return textsecure.utils.unencodeNumber(number_id)[1];
|
||||
getDeviceId() {
|
||||
const numberId = textsecure.storage.get('number_id');
|
||||
if (numberId === undefined) return undefined;
|
||||
return textsecure.utils.unencodeNumber(numberId)[1];
|
||||
},
|
||||
|
||||
getDeviceName: function(key) {
|
||||
getDeviceName() {
|
||||
return textsecure.storage.get('device_name');
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
/* global window, StringView */
|
||||
|
||||
/* eslint-disable no-bitwise, no-nested-ternary */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
window.StringView = {
|
||||
/*
|
||||
* These functions from the Mozilla Developer Network
|
||||
|
@ -9,7 +12,7 @@
|
|||
* https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses
|
||||
*/
|
||||
|
||||
b64ToUint6: function(nChr) {
|
||||
b64ToUint6(nChr) {
|
||||
return nChr > 64 && nChr < 91
|
||||
? nChr - 65
|
||||
: nChr > 96 && nChr < 123
|
||||
|
@ -23,25 +26,31 @@
|
|||
: 0;
|
||||
},
|
||||
|
||||
base64ToBytes: function(sBase64, nBlocksSize) {
|
||||
var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ''),
|
||||
nInLen = sB64Enc.length,
|
||||
nOutLen = nBlocksSize
|
||||
base64ToBytes(sBase64, nBlocksSize) {
|
||||
const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, '');
|
||||
const nInLen = sB64Enc.length;
|
||||
const nOutLen = nBlocksSize
|
||||
? Math.ceil(((nInLen * 3 + 1) >> 2) / nBlocksSize) * nBlocksSize
|
||||
: (nInLen * 3 + 1) >> 2;
|
||||
var aBBytes = new ArrayBuffer(nOutLen);
|
||||
var taBytes = new Uint8Array(aBBytes);
|
||||
const aBBytes = new ArrayBuffer(nOutLen);
|
||||
const taBytes = new Uint8Array(aBBytes);
|
||||
|
||||
let nMod3;
|
||||
let nMod4;
|
||||
for (
|
||||
var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0;
|
||||
let nUint24 = 0, nOutIdx = 0, nInIdx = 0;
|
||||
nInIdx < nInLen;
|
||||
nInIdx++
|
||||
nInIdx += 1
|
||||
) {
|
||||
nMod4 = nInIdx & 3;
|
||||
nUint24 |=
|
||||
StringView.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (18 - 6 * nMod4);
|
||||
if (nMod4 === 3 || nInLen - nInIdx === 1) {
|
||||
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
|
||||
for (
|
||||
nMod3 = 0;
|
||||
nMod3 < 3 && nOutIdx < nOutLen;
|
||||
nMod3 += 1, nOutIdx += 1
|
||||
) {
|
||||
taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
|
||||
}
|
||||
nUint24 = 0;
|
||||
|
@ -50,7 +59,7 @@
|
|||
return aBBytes;
|
||||
},
|
||||
|
||||
uint6ToB64: function(nUint6) {
|
||||
uint6ToB64(nUint6) {
|
||||
return nUint6 < 26
|
||||
? nUint6 + 65
|
||||
: nUint6 < 52
|
||||
|
@ -64,13 +73,13 @@
|
|||
: 65;
|
||||
},
|
||||
|
||||
bytesToBase64: function(aBytes) {
|
||||
var nMod3,
|
||||
sB64Enc = '';
|
||||
bytesToBase64(aBytes) {
|
||||
let nMod3;
|
||||
let sB64Enc = '';
|
||||
for (
|
||||
var nLen = aBytes.length, nUint24 = 0, nIdx = 0;
|
||||
let nLen = aBytes.length, nUint24 = 0, nIdx = 0;
|
||||
nIdx < nLen;
|
||||
nIdx++
|
||||
nIdx += 1
|
||||
) {
|
||||
nMod3 = nIdx % 3;
|
||||
if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) {
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
/* global Event, textsecure, window */
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
'use strict';
|
||||
window.textsecure = window.textsecure || {};
|
||||
|
||||
function SyncRequest(sender, receiver) {
|
||||
|
@ -22,11 +26,11 @@
|
|||
window.log.info('SyncRequest created. Sending contact sync message...');
|
||||
sender
|
||||
.sendRequestContactSyncMessage()
|
||||
.then(function() {
|
||||
.then(() => {
|
||||
window.log.info('SyncRequest now sending group sync messsage...');
|
||||
return sender.sendRequestGroupSyncMessage();
|
||||
})
|
||||
.catch(function(error) {
|
||||
.catch(error => {
|
||||
window.log.error(
|
||||
'SyncRequest error:',
|
||||
error && error.stack ? error.stack : error
|
||||
|
@ -38,21 +42,21 @@
|
|||
SyncRequest.prototype = new textsecure.EventTarget();
|
||||
SyncRequest.prototype.extend({
|
||||
constructor: SyncRequest,
|
||||
onContactSyncComplete: function() {
|
||||
onContactSyncComplete() {
|
||||
this.contactSync = true;
|
||||
this.update();
|
||||
},
|
||||
onGroupSyncComplete: function() {
|
||||
onGroupSyncComplete() {
|
||||
this.groupSync = true;
|
||||
this.update();
|
||||
},
|
||||
update: function() {
|
||||
update() {
|
||||
if (this.contactSync && this.groupSync) {
|
||||
this.dispatchEvent(new Event('success'));
|
||||
this.cleanup();
|
||||
}
|
||||
},
|
||||
onTimeout: function() {
|
||||
onTimeout() {
|
||||
if (this.contactSync || this.groupSync) {
|
||||
this.dispatchEvent(new Event('success'));
|
||||
} else {
|
||||
|
@ -60,7 +64,7 @@
|
|||
}
|
||||
this.cleanup();
|
||||
},
|
||||
cleanup: function() {
|
||||
cleanup() {
|
||||
clearTimeout(this.timeout);
|
||||
this.receiver.removeEventListener('contactsync', this.oncontact);
|
||||
this.receiver.removeEventListener('groupSync', this.ongroup);
|
||||
|
@ -68,8 +72,8 @@
|
|||
},
|
||||
});
|
||||
|
||||
textsecure.SyncRequest = function(sender, receiver) {
|
||||
var syncRequest = new SyncRequest(sender, receiver);
|
||||
textsecure.SyncRequest = function SyncRequestWrapper(sender, receiver) {
|
||||
const syncRequest = new SyncRequest(sender, receiver);
|
||||
this.addEventListener = syncRequest.addEventListener.bind(syncRequest);
|
||||
this.removeEventListener = syncRequest.removeEventListener.bind(
|
||||
syncRequest
|
||||
|
|
|
@ -1,31 +1,34 @@
|
|||
/* global window */
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
window.textsecure = window.textsecure || {};
|
||||
|
||||
window.textsecure.createTaskWithTimeout = function(task, id, options) {
|
||||
options = options || {};
|
||||
options.timeout = options.timeout || 1000 * 60 * 2; // two minutes
|
||||
window.textsecure.createTaskWithTimeout = (task, id, options = {}) => {
|
||||
const timeout = options.timeout || 1000 * 60 * 2; // two minutes
|
||||
|
||||
var errorForStack = new Error('for stack');
|
||||
return function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var complete = false;
|
||||
var timer = setTimeout(
|
||||
function() {
|
||||
const errorForStack = new Error('for stack');
|
||||
return () =>
|
||||
new Promise((resolve, reject) => {
|
||||
let complete = false;
|
||||
let timer = setTimeout(() => {
|
||||
if (!complete) {
|
||||
var message =
|
||||
(id || '') +
|
||||
' task did not complete in time. Calling stack: ' +
|
||||
errorForStack.stack;
|
||||
const message = `${id ||
|
||||
''} task did not complete in time. Calling stack: ${
|
||||
errorForStack.stack
|
||||
}`;
|
||||
|
||||
window.log.error(message);
|
||||
return reject(new Error(message));
|
||||
}
|
||||
}.bind(this),
|
||||
options.timeout
|
||||
);
|
||||
var clearTimer = function() {
|
||||
|
||||
return null;
|
||||
}, timeout);
|
||||
const clearTimer = () => {
|
||||
try {
|
||||
var localTimer = timer;
|
||||
const localTimer = timer;
|
||||
if (localTimer) {
|
||||
timer = null;
|
||||
clearTimeout(localTimer);
|
||||
|
@ -39,18 +42,18 @@
|
|||
}
|
||||
};
|
||||
|
||||
var success = function(result) {
|
||||
const success = result => {
|
||||
clearTimer();
|
||||
complete = true;
|
||||
return resolve(result);
|
||||
};
|
||||
var failure = function(error) {
|
||||
const failure = error => {
|
||||
clearTimer();
|
||||
complete = true;
|
||||
return reject(error);
|
||||
};
|
||||
|
||||
var promise;
|
||||
let promise;
|
||||
try {
|
||||
promise = task();
|
||||
} catch (error) {
|
||||
|
@ -66,5 +69,4 @@
|
|||
return promise.then(success, failure);
|
||||
});
|
||||
};
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -3,19 +3,19 @@ window.assert = chai.assert;
|
|||
window.PROTO_ROOT = '../../protos';
|
||||
|
||||
(function() {
|
||||
var OriginalReporter = mocha._reporter;
|
||||
const OriginalReporter = mocha._reporter;
|
||||
|
||||
var SauceReporter = function(runner) {
|
||||
var failedTests = [];
|
||||
const SauceReporter = function(runner) {
|
||||
const failedTests = [];
|
||||
|
||||
runner.on('end', function() {
|
||||
runner.on('end', () => {
|
||||
window.mochaResults = runner.stats;
|
||||
window.mochaResults.reports = failedTests;
|
||||
});
|
||||
|
||||
runner.on('fail', function(test, err) {
|
||||
var flattenTitles = function(test) {
|
||||
var titles = [];
|
||||
runner.on('fail', (test, err) => {
|
||||
const flattenTitles = function(test) {
|
||||
const titles = [];
|
||||
while (test.parent.title) {
|
||||
titles.push(test.parent.title);
|
||||
test = test.parent;
|
||||
|
@ -47,9 +47,9 @@ function assertEqualArrayBuffers(ab1, ab2) {
|
|||
}
|
||||
|
||||
function hexToArrayBuffer(str) {
|
||||
var ret = new ArrayBuffer(str.length / 2);
|
||||
var array = new Uint8Array(ret);
|
||||
for (var i = 0; i < str.length / 2; i++)
|
||||
const ret = new ArrayBuffer(str.length / 2);
|
||||
const array = new Uint8Array(ret);
|
||||
for (let i = 0; i < str.length / 2; i++)
|
||||
array[i] = parseInt(str.substr(i * 2, 2), 16);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -1,30 +1,28 @@
|
|||
'use strict';
|
||||
|
||||
describe('AccountManager', function() {
|
||||
describe('AccountManager', () => {
|
||||
let accountManager;
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(() => {
|
||||
accountManager = new window.textsecure.AccountManager();
|
||||
});
|
||||
|
||||
describe('#cleanSignedPreKeys', function() {
|
||||
describe('#cleanSignedPreKeys', () => {
|
||||
let originalProtocolStorage;
|
||||
let signedPreKeys;
|
||||
const DAY = 1000 * 60 * 60 * 24;
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(() => {
|
||||
originalProtocolStorage = window.textsecure.storage.protocol;
|
||||
window.textsecure.storage.protocol = {
|
||||
loadSignedPreKeys: function() {
|
||||
loadSignedPreKeys() {
|
||||
return Promise.resolve(signedPreKeys);
|
||||
},
|
||||
};
|
||||
});
|
||||
afterEach(function() {
|
||||
afterEach(() => {
|
||||
window.textsecure.storage.protocol = originalProtocolStorage;
|
||||
});
|
||||
|
||||
it('keeps three confirmed keys even if over a week old', function() {
|
||||
it('keeps three confirmed keys even if over a week old', () => {
|
||||
const now = Date.now();
|
||||
signedPreKeys = [
|
||||
{
|
||||
|
@ -48,7 +46,7 @@ describe('AccountManager', function() {
|
|||
return accountManager.cleanSignedPreKeys();
|
||||
});
|
||||
|
||||
it('eliminates confirmed keys over a week old, if more than three', function() {
|
||||
it('eliminates confirmed keys over a week old, if more than three', () => {
|
||||
const now = Date.now();
|
||||
signedPreKeys = [
|
||||
{
|
||||
|
@ -81,18 +79,18 @@ describe('AccountManager', function() {
|
|||
let count = 0;
|
||||
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) {
|
||||
if (keyId !== 1 && keyId !== 4) {
|
||||
throw new Error('Wrong keys were eliminated! ' + keyId);
|
||||
throw new Error(`Wrong keys were eliminated! ${keyId}`);
|
||||
}
|
||||
|
||||
count++;
|
||||
};
|
||||
|
||||
return accountManager.cleanSignedPreKeys().then(function() {
|
||||
return accountManager.cleanSignedPreKeys().then(() => {
|
||||
assert.strictEqual(count, 2);
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps at least three unconfirmed keys if no confirmed', function() {
|
||||
it('keeps at least three unconfirmed keys if no confirmed', () => {
|
||||
const now = Date.now();
|
||||
signedPreKeys = [
|
||||
{
|
||||
|
@ -116,18 +114,18 @@ describe('AccountManager', function() {
|
|||
let count = 0;
|
||||
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) {
|
||||
if (keyId !== 2) {
|
||||
throw new Error('Wrong keys were eliminated! ' + keyId);
|
||||
throw new Error(`Wrong keys were eliminated! ${keyId}`);
|
||||
}
|
||||
|
||||
count++;
|
||||
};
|
||||
|
||||
return accountManager.cleanSignedPreKeys().then(function() {
|
||||
return accountManager.cleanSignedPreKeys().then(() => {
|
||||
assert.strictEqual(count, 1);
|
||||
});
|
||||
});
|
||||
|
||||
it('if some confirmed keys, keeps unconfirmed to addd up to three total', function() {
|
||||
it('if some confirmed keys, keeps unconfirmed to addd up to three total', () => {
|
||||
const now = Date.now();
|
||||
signedPreKeys = [
|
||||
{
|
||||
|
@ -153,13 +151,13 @@ describe('AccountManager', function() {
|
|||
let count = 0;
|
||||
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) {
|
||||
if (keyId !== 3) {
|
||||
throw new Error('Wrong keys were eliminated! ' + keyId);
|
||||
throw new Error(`Wrong keys were eliminated! ${keyId}`);
|
||||
}
|
||||
|
||||
count++;
|
||||
};
|
||||
|
||||
return accountManager.cleanSignedPreKeys().then(function() {
|
||||
return accountManager.cleanSignedPreKeys().then(() => {
|
||||
assert.strictEqual(count, 1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
'use strict';
|
||||
|
||||
describe('ContactBuffer', function() {
|
||||
describe('ContactBuffer', () => {
|
||||
function getTestBuffer() {
|
||||
var buffer = new dcodeIO.ByteBuffer();
|
||||
var avatarBuffer = new dcodeIO.ByteBuffer();
|
||||
var avatarLen = 255;
|
||||
const buffer = new dcodeIO.ByteBuffer();
|
||||
const avatarBuffer = new dcodeIO.ByteBuffer();
|
||||
const avatarLen = 255;
|
||||
for (var i = 0; i < avatarLen; ++i) {
|
||||
avatarBuffer.writeUint8(i);
|
||||
}
|
||||
avatarBuffer.limit = avatarBuffer.offset;
|
||||
avatarBuffer.offset = 0;
|
||||
var contactInfo = new textsecure.protobuf.ContactDetails({
|
||||
const contactInfo = new textsecure.protobuf.ContactDetails({
|
||||
name: 'Zero Cool',
|
||||
number: '+10000000000',
|
||||
avatar: { contentType: 'image/jpeg', length: avatarLen },
|
||||
});
|
||||
var contactInfoBuffer = contactInfo.encode().toArrayBuffer();
|
||||
const contactInfoBuffer = contactInfo.encode().toArrayBuffer();
|
||||
|
||||
for (var i = 0; i < 3; ++i) {
|
||||
buffer.writeVarint32(contactInfoBuffer.byteLength);
|
||||
|
@ -28,11 +26,11 @@ describe('ContactBuffer', function() {
|
|||
return buffer.toArrayBuffer();
|
||||
}
|
||||
|
||||
it('parses an array buffer of contacts', function() {
|
||||
var arrayBuffer = getTestBuffer();
|
||||
var contactBuffer = new ContactBuffer(arrayBuffer);
|
||||
var contact = contactBuffer.next();
|
||||
var count = 0;
|
||||
it('parses an array buffer of contacts', () => {
|
||||
const arrayBuffer = getTestBuffer();
|
||||
const contactBuffer = new ContactBuffer(arrayBuffer);
|
||||
let contact = contactBuffer.next();
|
||||
let count = 0;
|
||||
while (contact !== undefined) {
|
||||
count++;
|
||||
assert.strictEqual(contact.name, 'Zero Cool');
|
||||
|
@ -40,8 +38,8 @@ describe('ContactBuffer', function() {
|
|||
assert.strictEqual(contact.avatar.contentType, 'image/jpeg');
|
||||
assert.strictEqual(contact.avatar.length, 255);
|
||||
assert.strictEqual(contact.avatar.data.byteLength, 255);
|
||||
var avatarBytes = new Uint8Array(contact.avatar.data);
|
||||
for (var j = 0; j < 255; ++j) {
|
||||
const avatarBytes = new Uint8Array(contact.avatar.data);
|
||||
for (let j = 0; j < 255; ++j) {
|
||||
assert.strictEqual(avatarBytes[j], j);
|
||||
}
|
||||
contact = contactBuffer.next();
|
||||
|
@ -50,23 +48,23 @@ describe('ContactBuffer', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('GroupBuffer', function() {
|
||||
describe('GroupBuffer', () => {
|
||||
function getTestBuffer() {
|
||||
var buffer = new dcodeIO.ByteBuffer();
|
||||
var avatarBuffer = new dcodeIO.ByteBuffer();
|
||||
var avatarLen = 255;
|
||||
const buffer = new dcodeIO.ByteBuffer();
|
||||
const avatarBuffer = new dcodeIO.ByteBuffer();
|
||||
const avatarLen = 255;
|
||||
for (var i = 0; i < avatarLen; ++i) {
|
||||
avatarBuffer.writeUint8(i);
|
||||
}
|
||||
avatarBuffer.limit = avatarBuffer.offset;
|
||||
avatarBuffer.offset = 0;
|
||||
var groupInfo = new textsecure.protobuf.GroupDetails({
|
||||
const groupInfo = new textsecure.protobuf.GroupDetails({
|
||||
id: new Uint8Array([1, 3, 3, 7]).buffer,
|
||||
name: 'Hackers',
|
||||
members: ['cereal', 'burn', 'phreak', 'joey'],
|
||||
avatar: { contentType: 'image/jpeg', length: avatarLen },
|
||||
});
|
||||
var groupInfoBuffer = groupInfo.encode().toArrayBuffer();
|
||||
const groupInfoBuffer = groupInfo.encode().toArrayBuffer();
|
||||
|
||||
for (var i = 0; i < 3; ++i) {
|
||||
buffer.writeVarint32(groupInfoBuffer.byteLength);
|
||||
|
@ -79,11 +77,11 @@ describe('GroupBuffer', function() {
|
|||
return buffer.toArrayBuffer();
|
||||
}
|
||||
|
||||
it('parses an array buffer of groups', function() {
|
||||
var arrayBuffer = getTestBuffer();
|
||||
var groupBuffer = new GroupBuffer(arrayBuffer);
|
||||
var group = groupBuffer.next();
|
||||
var count = 0;
|
||||
it('parses an array buffer of groups', () => {
|
||||
const arrayBuffer = getTestBuffer();
|
||||
const groupBuffer = new GroupBuffer(arrayBuffer);
|
||||
let group = groupBuffer.next();
|
||||
let count = 0;
|
||||
while (group !== undefined) {
|
||||
count++;
|
||||
assert.strictEqual(group.name, 'Hackers');
|
||||
|
@ -95,8 +93,8 @@ describe('GroupBuffer', function() {
|
|||
assert.strictEqual(group.avatar.contentType, 'image/jpeg');
|
||||
assert.strictEqual(group.avatar.length, 255);
|
||||
assert.strictEqual(group.avatar.data.byteLength, 255);
|
||||
var avatarBytes = new Uint8Array(group.avatar.data);
|
||||
for (var j = 0; j < 255; ++j) {
|
||||
const avatarBytes = new Uint8Array(group.avatar.data);
|
||||
for (let j = 0; j < 255; ++j) {
|
||||
assert.strictEqual(avatarBytes[j], j);
|
||||
}
|
||||
group = groupBuffer.next();
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
describe('encrypting and decrypting profile data', function() {
|
||||
var NAME_PADDED_LENGTH = 26;
|
||||
describe('encrypting and decrypting profile names', function() {
|
||||
it('pads, encrypts, decrypts, and unpads a short string', function() {
|
||||
var name = 'Alice';
|
||||
var buffer = dcodeIO.ByteBuffer.wrap(name).toArrayBuffer();
|
||||
var key = libsignal.crypto.getRandomBytes(32);
|
||||
describe('encrypting and decrypting profile data', () => {
|
||||
const NAME_PADDED_LENGTH = 26;
|
||||
describe('encrypting and decrypting profile names', () => {
|
||||
it('pads, encrypts, decrypts, and unpads a short string', () => {
|
||||
const name = 'Alice';
|
||||
const buffer = dcodeIO.ByteBuffer.wrap(name).toArrayBuffer();
|
||||
const key = libsignal.crypto.getRandomBytes(32);
|
||||
|
||||
return textsecure.crypto
|
||||
.encryptProfileName(buffer, key)
|
||||
.then(function(encrypted) {
|
||||
.then(encrypted => {
|
||||
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
||||
return textsecure.crypto
|
||||
.decryptProfileName(encrypted, key)
|
||||
.then(function(decrypted) {
|
||||
.then(decrypted => {
|
||||
assert.strictEqual(
|
||||
dcodeIO.ByteBuffer.wrap(decrypted).toString('utf8'),
|
||||
'Alice'
|
||||
|
@ -20,17 +20,17 @@ describe('encrypting and decrypting profile data', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
it('works for empty string', function() {
|
||||
var name = dcodeIO.ByteBuffer.wrap('').toArrayBuffer();
|
||||
var key = libsignal.crypto.getRandomBytes(32);
|
||||
it('works for empty string', () => {
|
||||
const name = dcodeIO.ByteBuffer.wrap('').toArrayBuffer();
|
||||
const key = libsignal.crypto.getRandomBytes(32);
|
||||
|
||||
return textsecure.crypto
|
||||
.encryptProfileName(name.buffer, key)
|
||||
.then(function(encrypted) {
|
||||
.then(encrypted => {
|
||||
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
||||
return textsecure.crypto
|
||||
.decryptProfileName(encrypted, key)
|
||||
.then(function(decrypted) {
|
||||
.then(decrypted => {
|
||||
assert.strictEqual(decrypted.byteLength, 0);
|
||||
assert.strictEqual(
|
||||
dcodeIO.ByteBuffer.wrap(decrypted).toString('utf8'),
|
||||
|
@ -40,34 +40,34 @@ describe('encrypting and decrypting profile data', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
describe('encrypting and decrypting profile avatars', function() {
|
||||
it('encrypts and decrypts', function() {
|
||||
var buffer = dcodeIO.ByteBuffer.wrap('This is an avatar').toArrayBuffer();
|
||||
var key = libsignal.crypto.getRandomBytes(32);
|
||||
describe('encrypting and decrypting profile avatars', () => {
|
||||
it('encrypts and decrypts', () => {
|
||||
const buffer = dcodeIO.ByteBuffer.wrap(
|
||||
'This is an avatar'
|
||||
).toArrayBuffer();
|
||||
const key = libsignal.crypto.getRandomBytes(32);
|
||||
|
||||
return textsecure.crypto
|
||||
.encryptProfile(buffer, key)
|
||||
.then(function(encrypted) {
|
||||
return textsecure.crypto.encryptProfile(buffer, key).then(encrypted => {
|
||||
assert(encrypted.byteLength === buffer.byteLength + 16 + 12);
|
||||
return textsecure.crypto
|
||||
.decryptProfile(encrypted, key)
|
||||
.then(function(decrypted) {
|
||||
.then(decrypted => {
|
||||
assertEqualArrayBuffers(buffer, decrypted);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('throws when decrypting with the wrong key', function() {
|
||||
var buffer = dcodeIO.ByteBuffer.wrap('This is an avatar').toArrayBuffer();
|
||||
var key = libsignal.crypto.getRandomBytes(32);
|
||||
var bad_key = libsignal.crypto.getRandomBytes(32);
|
||||
it('throws when decrypting with the wrong key', () => {
|
||||
const buffer = dcodeIO.ByteBuffer.wrap(
|
||||
'This is an avatar'
|
||||
).toArrayBuffer();
|
||||
const key = libsignal.crypto.getRandomBytes(32);
|
||||
const bad_key = libsignal.crypto.getRandomBytes(32);
|
||||
|
||||
return textsecure.crypto
|
||||
.encryptProfile(buffer, key)
|
||||
.then(function(encrypted) {
|
||||
return textsecure.crypto.encryptProfile(buffer, key).then(encrypted => {
|
||||
assert(encrypted.byteLength === buffer.byteLength + 16 + 12);
|
||||
return textsecure.crypto
|
||||
.decryptProfile(encrypted, bad_key)
|
||||
.catch(function(error) {
|
||||
.catch(error => {
|
||||
assert.strictEqual(error.name, 'ProfileDecryptError');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,17 +22,18 @@ const fakeAPI = {
|
|||
// sendMessages: fakeCall,
|
||||
setSignedPreKey: fakeCall,
|
||||
|
||||
getKeysForNumber: function(number, deviceId) {
|
||||
var res = getKeysForNumberMap[number];
|
||||
getKeysForNumber(number, deviceId) {
|
||||
const res = getKeysForNumberMap[number];
|
||||
if (res !== undefined) {
|
||||
delete getKeysForNumberMap[number];
|
||||
return Promise.resolve(res);
|
||||
} else throw new Error('getKeysForNumber of unknown/used number');
|
||||
}
|
||||
throw new Error('getKeysForNumber of unknown/used number');
|
||||
},
|
||||
|
||||
sendMessages: function(destination, messageArray) {
|
||||
sendMessages(destination, messageArray) {
|
||||
for (i in messageArray) {
|
||||
var msg = messageArray[i];
|
||||
const msg = messageArray[i];
|
||||
if (
|
||||
(msg.type != 1 && msg.type != 3) ||
|
||||
msg.destinationDeviceId === undefined ||
|
||||
|
@ -45,7 +46,7 @@ const fakeAPI = {
|
|||
throw new Error('Invalid message');
|
||||
|
||||
messagesSentMap[
|
||||
destination + '.' + messageArray[i].destinationDeviceId
|
||||
`${destination}.${messageArray[i].destinationDeviceId}`
|
||||
] = msg;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
describe('Key generation', function() {
|
||||
var count = 10;
|
||||
const count = 10;
|
||||
this.timeout(count * 2000);
|
||||
|
||||
function validateStoredKeyPair(keyPair) {
|
||||
|
@ -14,49 +12,41 @@ describe('Key generation', function() {
|
|||
assert.strictEqual(keyPair.privKey.byteLength, 32);
|
||||
}
|
||||
function itStoresPreKey(keyId) {
|
||||
it('prekey ' + keyId + ' is valid', function() {
|
||||
return textsecure.storage.protocol
|
||||
.loadPreKey(keyId)
|
||||
.then(function(keyPair) {
|
||||
it(`prekey ${keyId} is valid`, () =>
|
||||
textsecure.storage.protocol.loadPreKey(keyId).then(keyPair => {
|
||||
validateStoredKeyPair(keyPair);
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
function itStoresSignedPreKey(keyId) {
|
||||
it('signed prekey ' + keyId + ' is valid', function() {
|
||||
return textsecure.storage.protocol
|
||||
.loadSignedPreKey(keyId)
|
||||
.then(function(keyPair) {
|
||||
it(`signed prekey ${keyId} is valid`, () =>
|
||||
textsecure.storage.protocol.loadSignedPreKey(keyId).then(keyPair => {
|
||||
validateStoredKeyPair(keyPair);
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
function validateResultKey(resultKey) {
|
||||
return textsecure.storage.protocol
|
||||
.loadPreKey(resultKey.keyId)
|
||||
.then(function(keyPair) {
|
||||
.then(keyPair => {
|
||||
assertEqualArrayBuffers(resultKey.publicKey, keyPair.pubKey);
|
||||
});
|
||||
}
|
||||
function validateResultSignedKey(resultSignedKey) {
|
||||
return textsecure.storage.protocol
|
||||
.loadSignedPreKey(resultSignedKey.keyId)
|
||||
.then(function(keyPair) {
|
||||
.then(keyPair => {
|
||||
assertEqualArrayBuffers(resultSignedKey.publicKey, keyPair.pubKey);
|
||||
});
|
||||
}
|
||||
|
||||
before(function() {
|
||||
before(() => {
|
||||
localStorage.clear();
|
||||
return libsignal.KeyHelper.generateIdentityKeyPair().then(function(
|
||||
keyPair
|
||||
) {
|
||||
return textsecure.storage.protocol.put('identityKey', keyPair);
|
||||
});
|
||||
return libsignal.KeyHelper.generateIdentityKeyPair().then(keyPair =>
|
||||
textsecure.storage.protocol.put('identityKey', keyPair)
|
||||
);
|
||||
});
|
||||
|
||||
describe('the first time', function() {
|
||||
var result;
|
||||
describe('the first time', () => {
|
||||
let result;
|
||||
/* result should have this format
|
||||
* {
|
||||
* preKeys: [ { keyId, publicKey }, ... ],
|
||||
|
@ -64,101 +54,98 @@ describe('Key generation', function() {
|
|||
* identityKey: <ArrayBuffer>
|
||||
* }
|
||||
*/
|
||||
before(function() {
|
||||
var accountManager = new textsecure.AccountManager('');
|
||||
return accountManager.generateKeys(count).then(function(res) {
|
||||
before(() => {
|
||||
const accountManager = new textsecure.AccountManager('');
|
||||
return accountManager.generateKeys(count).then(res => {
|
||||
result = res;
|
||||
});
|
||||
});
|
||||
for (var i = 1; i <= count; i++) {
|
||||
for (let i = 1; i <= count; i++) {
|
||||
itStoresPreKey(i);
|
||||
}
|
||||
itStoresSignedPreKey(1);
|
||||
|
||||
it('result contains ' + count + ' preKeys', function() {
|
||||
it(`result contains ${count} preKeys`, () => {
|
||||
assert.isArray(result.preKeys);
|
||||
assert.lengthOf(result.preKeys, count);
|
||||
for (var i = 0; i < count; i++) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
assert.isObject(result.preKeys[i]);
|
||||
}
|
||||
});
|
||||
it('result contains the correct keyIds', function() {
|
||||
for (var i = 0; i < count; i++) {
|
||||
it('result contains the correct keyIds', () => {
|
||||
for (let i = 0; i < count; i++) {
|
||||
assert.strictEqual(result.preKeys[i].keyId, i + 1);
|
||||
}
|
||||
});
|
||||
it('result contains the correct public keys', function() {
|
||||
return Promise.all(result.preKeys.map(validateResultKey));
|
||||
});
|
||||
it('returns a signed prekey', function() {
|
||||
it('result contains the correct public keys', () =>
|
||||
Promise.all(result.preKeys.map(validateResultKey)));
|
||||
it('returns a signed prekey', () => {
|
||||
assert.strictEqual(result.signedPreKey.keyId, 1);
|
||||
assert.instanceOf(result.signedPreKey.signature, ArrayBuffer);
|
||||
return validateResultSignedKey(result.signedPreKey);
|
||||
});
|
||||
});
|
||||
describe('the second time', function() {
|
||||
var result;
|
||||
before(function() {
|
||||
var accountManager = new textsecure.AccountManager('');
|
||||
return accountManager.generateKeys(count).then(function(res) {
|
||||
describe('the second time', () => {
|
||||
let result;
|
||||
before(() => {
|
||||
const accountManager = new textsecure.AccountManager('');
|
||||
return accountManager.generateKeys(count).then(res => {
|
||||
result = res;
|
||||
});
|
||||
});
|
||||
for (var i = 1; i <= 2 * count; i++) {
|
||||
for (let i = 1; i <= 2 * count; i++) {
|
||||
itStoresPreKey(i);
|
||||
}
|
||||
itStoresSignedPreKey(1);
|
||||
itStoresSignedPreKey(2);
|
||||
it('result contains ' + count + ' preKeys', function() {
|
||||
it(`result contains ${count} preKeys`, () => {
|
||||
assert.isArray(result.preKeys);
|
||||
assert.lengthOf(result.preKeys, count);
|
||||
for (var i = 0; i < count; i++) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
assert.isObject(result.preKeys[i]);
|
||||
}
|
||||
});
|
||||
it('result contains the correct keyIds', function() {
|
||||
for (var i = 1; i <= count; i++) {
|
||||
it('result contains the correct keyIds', () => {
|
||||
for (let i = 1; i <= count; i++) {
|
||||
assert.strictEqual(result.preKeys[i - 1].keyId, i + count);
|
||||
}
|
||||
});
|
||||
it('result contains the correct public keys', function() {
|
||||
return Promise.all(result.preKeys.map(validateResultKey));
|
||||
});
|
||||
it('returns a signed prekey', function() {
|
||||
it('result contains the correct public keys', () =>
|
||||
Promise.all(result.preKeys.map(validateResultKey)));
|
||||
it('returns a signed prekey', () => {
|
||||
assert.strictEqual(result.signedPreKey.keyId, 2);
|
||||
assert.instanceOf(result.signedPreKey.signature, ArrayBuffer);
|
||||
return validateResultSignedKey(result.signedPreKey);
|
||||
});
|
||||
});
|
||||
describe('the third time', function() {
|
||||
var result;
|
||||
before(function() {
|
||||
var accountManager = new textsecure.AccountManager('');
|
||||
return accountManager.generateKeys(count).then(function(res) {
|
||||
describe('the third time', () => {
|
||||
let result;
|
||||
before(() => {
|
||||
const accountManager = new textsecure.AccountManager('');
|
||||
return accountManager.generateKeys(count).then(res => {
|
||||
result = res;
|
||||
});
|
||||
});
|
||||
for (var i = 1; i <= 3 * count; i++) {
|
||||
for (let i = 1; i <= 3 * count; i++) {
|
||||
itStoresPreKey(i);
|
||||
}
|
||||
itStoresSignedPreKey(2);
|
||||
itStoresSignedPreKey(3);
|
||||
it('result contains ' + count + ' preKeys', function() {
|
||||
it(`result contains ${count} preKeys`, () => {
|
||||
assert.isArray(result.preKeys);
|
||||
assert.lengthOf(result.preKeys, count);
|
||||
for (var i = 0; i < count; i++) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
assert.isObject(result.preKeys[i]);
|
||||
}
|
||||
});
|
||||
it('result contains the correct keyIds', function() {
|
||||
for (var i = 1; i <= count; i++) {
|
||||
it('result contains the correct keyIds', () => {
|
||||
for (let i = 1; i <= count; i++) {
|
||||
assert.strictEqual(result.preKeys[i - 1].keyId, i + 2 * count);
|
||||
}
|
||||
});
|
||||
it('result contains the correct public keys', function() {
|
||||
return Promise.all(result.preKeys.map(validateResultKey));
|
||||
});
|
||||
it('result contains a signed prekey', function() {
|
||||
it('result contains the correct public keys', () =>
|
||||
Promise.all(result.preKeys.map(validateResultKey)));
|
||||
it('result contains a signed prekey', () => {
|
||||
assert.strictEqual(result.signedPreKey.keyId, 3);
|
||||
assert.instanceOf(result.signedPreKey.signature, ArrayBuffer);
|
||||
return validateResultSignedKey(result.signedPreKey);
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
describe('Helpers', function() {
|
||||
describe('ArrayBuffer->String conversion', function() {
|
||||
it('works', function() {
|
||||
var b = new ArrayBuffer(3);
|
||||
var a = new Uint8Array(b);
|
||||
describe('Helpers', () => {
|
||||
describe('ArrayBuffer->String conversion', () => {
|
||||
it('works', () => {
|
||||
const b = new ArrayBuffer(3);
|
||||
const a = new Uint8Array(b);
|
||||
a[0] = 0;
|
||||
a[1] = 255;
|
||||
a[2] = 128;
|
||||
|
@ -12,18 +10,18 @@ describe('Helpers', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('stringToArrayBuffer', function() {
|
||||
it('returns ArrayBuffer when passed string', function() {
|
||||
var StaticArrayBufferProto = new ArrayBuffer().__proto__;
|
||||
var anArrayBuffer = new ArrayBuffer(1);
|
||||
var typedArray = new Uint8Array(anArrayBuffer);
|
||||
describe('stringToArrayBuffer', () => {
|
||||
it('returns ArrayBuffer when passed string', () => {
|
||||
const StaticArrayBufferProto = new ArrayBuffer().__proto__;
|
||||
const anArrayBuffer = new ArrayBuffer(1);
|
||||
const typedArray = new Uint8Array(anArrayBuffer);
|
||||
typedArray[0] = 'a'.charCodeAt(0);
|
||||
assertEqualArrayBuffers(stringToArrayBuffer('a'), anArrayBuffer);
|
||||
});
|
||||
it('throws an error when passed a non string', function() {
|
||||
var notStringable = [{}, undefined, null, new ArrayBuffer()];
|
||||
notStringable.forEach(function(notString) {
|
||||
assert.throw(function() {
|
||||
it('throws an error when passed a non string', () => {
|
||||
const notStringable = [{}, undefined, null, new ArrayBuffer()];
|
||||
notStringable.forEach(notString => {
|
||||
assert.throw(() => {
|
||||
stringToArrayBuffer(notString);
|
||||
}, Error);
|
||||
});
|
||||
|
|
|
@ -4,13 +4,13 @@ function SignalProtocolStore() {
|
|||
|
||||
SignalProtocolStore.prototype = {
|
||||
Direction: { SENDING: 1, RECEIVING: 2 },
|
||||
getIdentityKeyPair: function() {
|
||||
getIdentityKeyPair() {
|
||||
return Promise.resolve(this.get('identityKey'));
|
||||
},
|
||||
getLocalRegistrationId: function() {
|
||||
getLocalRegistrationId() {
|
||||
return Promise.resolve(this.get('registrationId'));
|
||||
},
|
||||
put: function(key, value) {
|
||||
put(key, value) {
|
||||
if (
|
||||
key === undefined ||
|
||||
value === undefined ||
|
||||
|
@ -20,165 +20,130 @@ SignalProtocolStore.prototype = {
|
|||
throw new Error('Tried to store undefined/null');
|
||||
this.store[key] = value;
|
||||
},
|
||||
get: function(key, defaultValue) {
|
||||
get(key, defaultValue) {
|
||||
if (key === null || key === undefined)
|
||||
throw new Error('Tried to get value for undefined/null key');
|
||||
if (key in this.store) {
|
||||
return this.store[key];
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
},
|
||||
remove: function(key) {
|
||||
remove(key) {
|
||||
if (key === null || key === undefined)
|
||||
throw new Error('Tried to remove value for undefined/null key');
|
||||
delete this.store[key];
|
||||
},
|
||||
|
||||
isTrustedIdentity: function(identifier, identityKey) {
|
||||
isTrustedIdentity(identifier, identityKey) {
|
||||
if (identifier === null || identifier === undefined) {
|
||||
throw new error('tried to check identity key for undefined/null key');
|
||||
}
|
||||
if (!(identityKey instanceof ArrayBuffer)) {
|
||||
throw new error('Expected identityKey to be an ArrayBuffer');
|
||||
}
|
||||
var trusted = this.get('identityKey' + identifier);
|
||||
const trusted = this.get(`identityKey${identifier}`);
|
||||
if (trusted === undefined) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
return Promise.resolve(identityKey === trusted);
|
||||
},
|
||||
loadIdentityKey: function(identifier) {
|
||||
loadIdentityKey(identifier) {
|
||||
if (identifier === null || identifier === undefined)
|
||||
throw new Error('Tried to get identity key for undefined/null key');
|
||||
return new Promise(
|
||||
function(resolve) {
|
||||
resolve(this.get('identityKey' + identifier));
|
||||
}.bind(this)
|
||||
);
|
||||
return new Promise(resolve => {
|
||||
resolve(this.get(`identityKey${identifier}`));
|
||||
});
|
||||
},
|
||||
saveIdentity: function(identifier, identityKey) {
|
||||
saveIdentity(identifier, identityKey) {
|
||||
if (identifier === null || identifier === undefined)
|
||||
throw new Error('Tried to put identity key for undefined/null key');
|
||||
return new Promise(
|
||||
function(resolve) {
|
||||
var existing = this.get('identityKey' + identifier);
|
||||
this.put('identityKey' + identifier, identityKey);
|
||||
return new Promise(resolve => {
|
||||
const existing = this.get(`identityKey${identifier}`);
|
||||
this.put(`identityKey${identifier}`, identityKey);
|
||||
if (existing && existing !== identityKey) {
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/* Returns a prekeypair object or undefined */
|
||||
loadPreKey: function(keyId) {
|
||||
return new Promise(
|
||||
function(resolve) {
|
||||
var res = this.get('25519KeypreKey' + keyId);
|
||||
loadPreKey(keyId) {
|
||||
return new Promise(resolve => {
|
||||
const res = this.get(`25519KeypreKey${keyId}`);
|
||||
resolve(res);
|
||||
}.bind(this)
|
||||
);
|
||||
});
|
||||
},
|
||||
storePreKey: function(keyId, keyPair) {
|
||||
return new Promise(
|
||||
function(resolve) {
|
||||
resolve(this.put('25519KeypreKey' + keyId, keyPair));
|
||||
}.bind(this)
|
||||
);
|
||||
storePreKey(keyId, keyPair) {
|
||||
return new Promise(resolve => {
|
||||
resolve(this.put(`25519KeypreKey${keyId}`, keyPair));
|
||||
});
|
||||
},
|
||||
removePreKey: function(keyId) {
|
||||
return new Promise(
|
||||
function(resolve) {
|
||||
resolve(this.remove('25519KeypreKey' + keyId));
|
||||
}.bind(this)
|
||||
);
|
||||
removePreKey(keyId) {
|
||||
return new Promise(resolve => {
|
||||
resolve(this.remove(`25519KeypreKey${keyId}`));
|
||||
});
|
||||
},
|
||||
|
||||
/* Returns a signed keypair object or undefined */
|
||||
loadSignedPreKey: function(keyId) {
|
||||
return new Promise(
|
||||
function(resolve) {
|
||||
var res = this.get('25519KeysignedKey' + keyId);
|
||||
loadSignedPreKey(keyId) {
|
||||
return new Promise(resolve => {
|
||||
const res = this.get(`25519KeysignedKey${keyId}`);
|
||||
resolve(res);
|
||||
}.bind(this)
|
||||
);
|
||||
});
|
||||
},
|
||||
loadSignedPreKeys: function() {
|
||||
return new Promise(
|
||||
function(resolve) {
|
||||
var res = [];
|
||||
for (var i in this.store) {
|
||||
loadSignedPreKeys() {
|
||||
return new Promise(resolve => {
|
||||
const res = [];
|
||||
for (const i in this.store) {
|
||||
if (i.startsWith('25519KeysignedKey')) {
|
||||
res.push(this.store[i]);
|
||||
}
|
||||
}
|
||||
resolve(res);
|
||||
}.bind(this)
|
||||
);
|
||||
});
|
||||
},
|
||||
storeSignedPreKey: function(keyId, keyPair) {
|
||||
return new Promise(
|
||||
function(resolve) {
|
||||
resolve(this.put('25519KeysignedKey' + keyId, keyPair));
|
||||
}.bind(this)
|
||||
);
|
||||
storeSignedPreKey(keyId, keyPair) {
|
||||
return new Promise(resolve => {
|
||||
resolve(this.put(`25519KeysignedKey${keyId}`, keyPair));
|
||||
});
|
||||
},
|
||||
removeSignedPreKey: function(keyId) {
|
||||
return new Promise(
|
||||
function(resolve) {
|
||||
resolve(this.remove('25519KeysignedKey' + keyId));
|
||||
}.bind(this)
|
||||
);
|
||||
removeSignedPreKey(keyId) {
|
||||
return new Promise(resolve => {
|
||||
resolve(this.remove(`25519KeysignedKey${keyId}`));
|
||||
});
|
||||
},
|
||||
|
||||
loadSession: function(identifier) {
|
||||
return new Promise(
|
||||
function(resolve) {
|
||||
resolve(this.get('session' + identifier));
|
||||
}.bind(this)
|
||||
);
|
||||
loadSession(identifier) {
|
||||
return new Promise(resolve => {
|
||||
resolve(this.get(`session${identifier}`));
|
||||
});
|
||||
},
|
||||
storeSession: function(identifier, record) {
|
||||
return new Promise(
|
||||
function(resolve) {
|
||||
resolve(this.put('session' + identifier, record));
|
||||
}.bind(this)
|
||||
);
|
||||
storeSession(identifier, record) {
|
||||
return new Promise(resolve => {
|
||||
resolve(this.put(`session${identifier}`, record));
|
||||
});
|
||||
},
|
||||
removeAllSessions: function(identifier) {
|
||||
return new Promise(
|
||||
function(resolve) {
|
||||
removeAllSessions(identifier) {
|
||||
return new Promise(resolve => {
|
||||
for (key in this.store) {
|
||||
if (
|
||||
key.match(
|
||||
RegExp('^session' + identifier.replace('+', '\\+') + '.+')
|
||||
)
|
||||
) {
|
||||
if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) {
|
||||
delete this.store[key];
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
}.bind(this)
|
||||
);
|
||||
});
|
||||
},
|
||||
getDeviceIds: function(identifier) {
|
||||
return new Promise(
|
||||
function(resolve) {
|
||||
var deviceIds = [];
|
||||
getDeviceIds(identifier) {
|
||||
return new Promise(resolve => {
|
||||
const deviceIds = [];
|
||||
for (key in this.store) {
|
||||
if (
|
||||
key.match(
|
||||
RegExp('^session' + identifier.replace('+', '\\+') + '.+')
|
||||
)
|
||||
) {
|
||||
if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) {
|
||||
deviceIds.push(parseInt(key.split('.')[1]));
|
||||
}
|
||||
}
|
||||
resolve(deviceIds);
|
||||
}.bind(this)
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,47 +1,47 @@
|
|||
describe('MessageReceiver', function() {
|
||||
describe('MessageReceiver', () => {
|
||||
textsecure.storage.impl = new SignalProtocolStore();
|
||||
var WebSocket = window.WebSocket;
|
||||
var number = '+19999999999';
|
||||
var deviceId = 1;
|
||||
var signalingKey = libsignal.crypto.getRandomBytes(32 + 20);
|
||||
before(function() {
|
||||
const WebSocket = window.WebSocket;
|
||||
const number = '+19999999999';
|
||||
const deviceId = 1;
|
||||
const signalingKey = libsignal.crypto.getRandomBytes(32 + 20);
|
||||
before(() => {
|
||||
window.WebSocket = MockSocket;
|
||||
textsecure.storage.user.setNumberAndDeviceId(number, deviceId, 'name');
|
||||
textsecure.storage.put('password', 'password');
|
||||
textsecure.storage.put('signaling_key', signalingKey);
|
||||
});
|
||||
after(function() {
|
||||
after(() => {
|
||||
window.WebSocket = WebSocket;
|
||||
});
|
||||
|
||||
describe('connecting', function() {
|
||||
var blob = null;
|
||||
var attrs = {
|
||||
describe('connecting', () => {
|
||||
const blob = null;
|
||||
const attrs = {
|
||||
type: textsecure.protobuf.Envelope.Type.CIPHERTEXT,
|
||||
source: number,
|
||||
sourceDevice: deviceId,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
var websocketmessage = new textsecure.protobuf.WebSocketMessage({
|
||||
const websocketmessage = new textsecure.protobuf.WebSocketMessage({
|
||||
type: textsecure.protobuf.WebSocketMessage.Type.REQUEST,
|
||||
request: { verb: 'PUT', path: '/messages' },
|
||||
});
|
||||
|
||||
before(function(done) {
|
||||
var signal = new textsecure.protobuf.Envelope(attrs).toArrayBuffer();
|
||||
var data = new textsecure.protobuf.DataMessage({ body: 'hello' });
|
||||
before(done => {
|
||||
const signal = new textsecure.protobuf.Envelope(attrs).toArrayBuffer();
|
||||
const data = new textsecure.protobuf.DataMessage({ body: 'hello' });
|
||||
|
||||
var signaling_key = signalingKey;
|
||||
var aes_key = signaling_key.slice(0, 32);
|
||||
var mac_key = signaling_key.slice(32, 32 + 20);
|
||||
const signaling_key = signalingKey;
|
||||
const aes_key = signaling_key.slice(0, 32);
|
||||
const mac_key = signaling_key.slice(32, 32 + 20);
|
||||
|
||||
window.crypto.subtle
|
||||
.importKey('raw', aes_key, { name: 'AES-CBC' }, false, ['encrypt'])
|
||||
.then(function(key) {
|
||||
var iv = libsignal.crypto.getRandomBytes(16);
|
||||
.then(key => {
|
||||
const iv = libsignal.crypto.getRandomBytes(16);
|
||||
window.crypto.subtle
|
||||
.encrypt({ name: 'AES-CBC', iv: new Uint8Array(iv) }, key, signal)
|
||||
.then(function(ciphertext) {
|
||||
.then(ciphertext => {
|
||||
window.crypto.subtle
|
||||
.importKey(
|
||||
'raw',
|
||||
|
@ -50,12 +50,12 @@ describe('MessageReceiver', function() {
|
|||
false,
|
||||
['sign']
|
||||
)
|
||||
.then(function(key) {
|
||||
.then(key => {
|
||||
window.crypto.subtle
|
||||
.sign({ name: 'HMAC', hash: 'SHA-256' }, key, signal)
|
||||
.then(function(mac) {
|
||||
var version = new Uint8Array([1]);
|
||||
var message = dcodeIO.ByteBuffer.concat([
|
||||
.then(mac => {
|
||||
const version = new Uint8Array([1]);
|
||||
const message = dcodeIO.ByteBuffer.concat([
|
||||
version,
|
||||
iv,
|
||||
ciphertext,
|
||||
|
@ -69,27 +69,27 @@ describe('MessageReceiver', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('connects', function(done) {
|
||||
var mockServer = new MockServer(
|
||||
'ws://localhost:8080/v1/websocket/?login=' +
|
||||
encodeURIComponent(number) +
|
||||
'.1&password=password'
|
||||
it('connects', done => {
|
||||
const mockServer = new MockServer(
|
||||
`ws://localhost:8080/v1/websocket/?login=${encodeURIComponent(
|
||||
number
|
||||
)}.1&password=password`
|
||||
);
|
||||
|
||||
mockServer.on('connection', function(server) {
|
||||
mockServer.on('connection', server => {
|
||||
server.send(new Blob([websocketmessage.toArrayBuffer()]));
|
||||
});
|
||||
|
||||
window.addEventListener('textsecure:message', function(ev) {
|
||||
var signal = ev.proto;
|
||||
for (var key in attrs) {
|
||||
window.addEventListener('textsecure:message', ev => {
|
||||
const signal = ev.proto;
|
||||
for (const key in attrs) {
|
||||
assert.strictEqual(attrs[key], signal[key]);
|
||||
}
|
||||
assert.strictEqual(signal.message.body, 'hello');
|
||||
server.close();
|
||||
done();
|
||||
});
|
||||
var messageReceiver = new textsecure.MessageReceiver(
|
||||
const messageReceiver = new textsecure.MessageReceiver(
|
||||
'ws://localhost:8080',
|
||||
window
|
||||
);
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
'use strict';
|
||||
describe('Protocol', function() {
|
||||
describe('Unencrypted PushMessageProto "decrypt"', function() {
|
||||
describe('Protocol', () => {
|
||||
describe('Unencrypted PushMessageProto "decrypt"', () => {
|
||||
// exclusive
|
||||
it('works', function(done) {
|
||||
it('works', done => {
|
||||
localStorage.clear();
|
||||
|
||||
var text_message = new textsecure.protobuf.DataMessage();
|
||||
const text_message = new textsecure.protobuf.DataMessage();
|
||||
text_message.body = 'Hi Mom';
|
||||
var server_message = {
|
||||
const server_message = {
|
||||
type: 4, // unencrypted
|
||||
source: '+19999999999',
|
||||
timestamp: 42,
|
||||
|
@ -21,7 +20,7 @@ describe('Protocol', function() {
|
|||
server_message.type,
|
||||
server_message.message
|
||||
)
|
||||
.then(function(message) {
|
||||
.then(message => {
|
||||
assert.equal(message.body, text_message.body);
|
||||
assert.equal(
|
||||
message.attachments.length,
|
||||
|
|
|
@ -1,37 +1,32 @@
|
|||
'use strict';
|
||||
|
||||
describe('Protocol Wrapper', function() {
|
||||
var store = textsecure.storage.protocol;
|
||||
var identifier = '+5558675309';
|
||||
var another_identifier = '+5555590210';
|
||||
var prekeys, identityKey, testKey;
|
||||
const store = textsecure.storage.protocol;
|
||||
const identifier = '+5558675309';
|
||||
const another_identifier = '+5555590210';
|
||||
let prekeys, identityKey, testKey;
|
||||
this.timeout(5000);
|
||||
before(function(done) {
|
||||
before(done => {
|
||||
localStorage.clear();
|
||||
libsignal.KeyHelper.generateIdentityKeyPair()
|
||||
.then(function(identityKey) {
|
||||
return textsecure.storage.protocol.saveIdentity(
|
||||
identifier,
|
||||
identityKey
|
||||
);
|
||||
})
|
||||
.then(function() {
|
||||
.then(identityKey =>
|
||||
textsecure.storage.protocol.saveIdentity(identifier, identityKey)
|
||||
)
|
||||
.then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
describe('processPreKey', function() {
|
||||
it('rejects if the identity key changes', function() {
|
||||
var address = new libsignal.SignalProtocolAddress(identifier, 1);
|
||||
var builder = new libsignal.SessionBuilder(store, address);
|
||||
describe('processPreKey', () => {
|
||||
it('rejects if the identity key changes', () => {
|
||||
const address = new libsignal.SignalProtocolAddress(identifier, 1);
|
||||
const builder = new libsignal.SessionBuilder(store, address);
|
||||
return builder
|
||||
.processPreKey({
|
||||
identityKey: textsecure.crypto.getRandomBytes(33),
|
||||
encodedNumber: address.toString(),
|
||||
})
|
||||
.then(function() {
|
||||
.then(() => {
|
||||
throw new Error('Allowed to overwrite identity key');
|
||||
})
|
||||
.catch(function(e) {
|
||||
.catch(e => {
|
||||
assert.strictEqual(e.message, 'Identity key changed');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,55 +1,53 @@
|
|||
'use strict';
|
||||
|
||||
describe('SignalProtocolStore', function() {
|
||||
before(function() {
|
||||
describe('SignalProtocolStore', () => {
|
||||
before(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
var store = textsecure.storage.protocol;
|
||||
var identifier = '+5558675309';
|
||||
var another_identifier = '+5555590210';
|
||||
var identityKey = {
|
||||
const store = textsecure.storage.protocol;
|
||||
const identifier = '+5558675309';
|
||||
const another_identifier = '+5555590210';
|
||||
const identityKey = {
|
||||
pubKey: libsignal.crypto.getRandomBytes(33),
|
||||
privKey: libsignal.crypto.getRandomBytes(32),
|
||||
};
|
||||
var testKey = {
|
||||
const testKey = {
|
||||
pubKey: libsignal.crypto.getRandomBytes(33),
|
||||
privKey: libsignal.crypto.getRandomBytes(32),
|
||||
};
|
||||
it('retrieves my registration id', function(done) {
|
||||
it('retrieves my registration id', done => {
|
||||
store.put('registrationId', 1337);
|
||||
store
|
||||
.getLocalRegistrationId()
|
||||
.then(function(reg) {
|
||||
.then(reg => {
|
||||
assert.strictEqual(reg, 1337);
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
it('retrieves my identity key', function(done) {
|
||||
it('retrieves my identity key', done => {
|
||||
store.put('identityKey', identityKey);
|
||||
store
|
||||
.getIdentityKeyPair()
|
||||
.then(function(key) {
|
||||
.then(key => {
|
||||
assertEqualArrayBuffers(key.pubKey, identityKey.pubKey);
|
||||
assertEqualArrayBuffers(key.privKey, identityKey.privKey);
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
it('stores identity keys', function(done) {
|
||||
it('stores identity keys', done => {
|
||||
store
|
||||
.saveIdentity(identifier, testKey.pubKey)
|
||||
.then(function() {
|
||||
return store.loadIdentityKey(identifier).then(function(key) {
|
||||
.then(() =>
|
||||
store.loadIdentityKey(identifier).then(key => {
|
||||
assertEqualArrayBuffers(key, testKey.pubKey);
|
||||
});
|
||||
})
|
||||
)
|
||||
.then(done, done);
|
||||
});
|
||||
it('returns whether a key is trusted', function(done) {
|
||||
var newIdentity = libsignal.crypto.getRandomBytes(33);
|
||||
store.saveIdentity(identifier, testKey.pubKey).then(function() {
|
||||
it('returns whether a key is trusted', done => {
|
||||
const newIdentity = libsignal.crypto.getRandomBytes(33);
|
||||
store.saveIdentity(identifier, testKey.pubKey).then(() => {
|
||||
store
|
||||
.isTrustedIdentity(identifier, newIdentity)
|
||||
.then(function(trusted) {
|
||||
.then(trusted => {
|
||||
if (trusted) {
|
||||
done(new Error('Allowed to overwrite identity key'));
|
||||
} else {
|
||||
|
@ -59,12 +57,12 @@ describe('SignalProtocolStore', function() {
|
|||
.catch(done);
|
||||
});
|
||||
});
|
||||
it('returns whether a key is untrusted', function(done) {
|
||||
var newIdentity = libsignal.crypto.getRandomBytes(33);
|
||||
store.saveIdentity(identifier, testKey.pubKey).then(function() {
|
||||
it('returns whether a key is untrusted', done => {
|
||||
const newIdentity = libsignal.crypto.getRandomBytes(33);
|
||||
store.saveIdentity(identifier, testKey.pubKey).then(() => {
|
||||
store
|
||||
.isTrustedIdentity(identifier, testKey.pubKey)
|
||||
.then(function(trusted) {
|
||||
.then(trusted => {
|
||||
if (trusted) {
|
||||
done();
|
||||
} else {
|
||||
|
@ -74,124 +72,117 @@ describe('SignalProtocolStore', function() {
|
|||
.catch(done);
|
||||
});
|
||||
});
|
||||
it('stores prekeys', function(done) {
|
||||
it('stores prekeys', done => {
|
||||
store
|
||||
.storePreKey(1, testKey)
|
||||
.then(function() {
|
||||
return store.loadPreKey(1).then(function(key) {
|
||||
.then(() =>
|
||||
store.loadPreKey(1).then(key => {
|
||||
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
|
||||
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
||||
});
|
||||
})
|
||||
)
|
||||
.then(done, done);
|
||||
});
|
||||
it('deletes prekeys', function(done) {
|
||||
before(function(done) {
|
||||
it('deletes prekeys', done => {
|
||||
before(done => {
|
||||
store.storePreKey(2, testKey).then(done);
|
||||
});
|
||||
store
|
||||
.removePreKey(2, testKey)
|
||||
.then(function() {
|
||||
return store.loadPreKey(2).then(function(key) {
|
||||
.then(() =>
|
||||
store.loadPreKey(2).then(key => {
|
||||
assert.isUndefined(key);
|
||||
});
|
||||
})
|
||||
)
|
||||
.then(done, done);
|
||||
});
|
||||
it('stores signed prekeys', function(done) {
|
||||
it('stores signed prekeys', done => {
|
||||
store
|
||||
.storeSignedPreKey(3, testKey)
|
||||
.then(function() {
|
||||
return store.loadSignedPreKey(3).then(function(key) {
|
||||
.then(() =>
|
||||
store.loadSignedPreKey(3).then(key => {
|
||||
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
|
||||
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
||||
});
|
||||
})
|
||||
)
|
||||
.then(done, done);
|
||||
});
|
||||
it('deletes signed prekeys', function(done) {
|
||||
before(function(done) {
|
||||
it('deletes signed prekeys', done => {
|
||||
before(done => {
|
||||
store.storeSignedPreKey(4, testKey).then(done);
|
||||
});
|
||||
store
|
||||
.removeSignedPreKey(4, testKey)
|
||||
.then(function() {
|
||||
return store.loadSignedPreKey(4).then(function(key) {
|
||||
.then(() =>
|
||||
store.loadSignedPreKey(4).then(key => {
|
||||
assert.isUndefined(key);
|
||||
});
|
||||
})
|
||||
)
|
||||
.then(done, done);
|
||||
});
|
||||
it('stores sessions', function(done) {
|
||||
var testRecord = 'an opaque string';
|
||||
var devices = [1, 2, 3].map(function(deviceId) {
|
||||
return [identifier, deviceId].join('.');
|
||||
});
|
||||
var promise = Promise.resolve();
|
||||
devices.forEach(function(encodedNumber) {
|
||||
promise = promise.then(function() {
|
||||
return store.storeSession(encodedNumber, testRecord + encodedNumber);
|
||||
});
|
||||
it('stores sessions', done => {
|
||||
const testRecord = 'an opaque string';
|
||||
const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.'));
|
||||
let promise = Promise.resolve();
|
||||
devices.forEach(encodedNumber => {
|
||||
promise = promise.then(() =>
|
||||
store.storeSession(encodedNumber, testRecord + encodedNumber)
|
||||
);
|
||||
});
|
||||
promise
|
||||
.then(function() {
|
||||
return Promise.all(devices.map(store.loadSession.bind(store))).then(
|
||||
function(records) {
|
||||
for (var i in records) {
|
||||
.then(() =>
|
||||
Promise.all(devices.map(store.loadSession.bind(store))).then(
|
||||
records => {
|
||||
for (const i in records) {
|
||||
assert.strictEqual(records[i], testRecord + devices[i]);
|
||||
}
|
||||
}
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
.then(done, done);
|
||||
});
|
||||
it('removes all sessions for a number', function(done) {
|
||||
var testRecord = 'an opaque string';
|
||||
var devices = [1, 2, 3].map(function(deviceId) {
|
||||
return [identifier, deviceId].join('.');
|
||||
});
|
||||
var promise = Promise.resolve();
|
||||
devices.forEach(function(encodedNumber) {
|
||||
promise = promise.then(function() {
|
||||
return store.storeSession(encodedNumber, testRecord + encodedNumber);
|
||||
});
|
||||
it('removes all sessions for a number', done => {
|
||||
const testRecord = 'an opaque string';
|
||||
const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.'));
|
||||
let promise = Promise.resolve();
|
||||
devices.forEach(encodedNumber => {
|
||||
promise = promise.then(() =>
|
||||
store.storeSession(encodedNumber, testRecord + encodedNumber)
|
||||
);
|
||||
});
|
||||
promise
|
||||
.then(function() {
|
||||
return store.removeAllSessions(identifier).then(function(record) {
|
||||
return Promise.all(devices.map(store.loadSession.bind(store))).then(
|
||||
function(records) {
|
||||
for (var i in records) {
|
||||
.then(() =>
|
||||
store.removeAllSessions(identifier).then(record =>
|
||||
Promise.all(devices.map(store.loadSession.bind(store))).then(
|
||||
records => {
|
||||
for (const i in records) {
|
||||
assert.isUndefined(records[i]);
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(done, done);
|
||||
});
|
||||
it('returns deviceIds for a number', done => {
|
||||
const testRecord = 'an opaque string';
|
||||
const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.'));
|
||||
let promise = Promise.resolve();
|
||||
devices.forEach(encodedNumber => {
|
||||
promise = promise.then(() =>
|
||||
store.storeSession(encodedNumber, testRecord + encodedNumber)
|
||||
);
|
||||
});
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
it('returns deviceIds for a number', function(done) {
|
||||
var testRecord = 'an opaque string';
|
||||
var devices = [1, 2, 3].map(function(deviceId) {
|
||||
return [identifier, deviceId].join('.');
|
||||
});
|
||||
var promise = Promise.resolve();
|
||||
devices.forEach(function(encodedNumber) {
|
||||
promise = promise.then(function() {
|
||||
return store.storeSession(encodedNumber, testRecord + encodedNumber);
|
||||
});
|
||||
});
|
||||
promise
|
||||
.then(function() {
|
||||
return store.getDeviceIds(identifier).then(function(deviceIds) {
|
||||
.then(() =>
|
||||
store.getDeviceIds(identifier).then(deviceIds => {
|
||||
assert.sameMembers(deviceIds, [1, 2, 3]);
|
||||
});
|
||||
})
|
||||
)
|
||||
.then(done, done);
|
||||
});
|
||||
it('returns empty array for a number with no device ids', function() {
|
||||
return store.getDeviceIds('foo').then(function(deviceIds) {
|
||||
it('returns empty array for a number with no device ids', () =>
|
||||
store.getDeviceIds('foo').then(deviceIds => {
|
||||
assert.sameMembers(deviceIds, []);
|
||||
});
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
|
|
@ -1,78 +1,76 @@
|
|||
'use strict';
|
||||
|
||||
describe('createTaskWithTimeout', function() {
|
||||
it('resolves when promise resolves', function() {
|
||||
var task = function() {
|
||||
describe('createTaskWithTimeout', () => {
|
||||
it('resolves when promise resolves', () => {
|
||||
const task = function() {
|
||||
return Promise.resolve('hi!');
|
||||
};
|
||||
var taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
||||
|
||||
return taskWithTimeout().then(function(result) {
|
||||
return taskWithTimeout().then(result => {
|
||||
assert.strictEqual(result, 'hi!');
|
||||
});
|
||||
});
|
||||
it('flows error from promise back', function() {
|
||||
var error = new Error('original');
|
||||
var task = function() {
|
||||
it('flows error from promise back', () => {
|
||||
const error = new Error('original');
|
||||
const task = function() {
|
||||
return Promise.reject(error);
|
||||
};
|
||||
var taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
||||
|
||||
return taskWithTimeout().catch(function(flowedError) {
|
||||
return taskWithTimeout().catch(flowedError => {
|
||||
assert.strictEqual(error, flowedError);
|
||||
});
|
||||
});
|
||||
it('rejects if promise takes too long (this one logs error to console)', function() {
|
||||
var error = new Error('original');
|
||||
var complete = false;
|
||||
var task = function() {
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(function() {
|
||||
const error = new Error('original');
|
||||
let complete = false;
|
||||
const task = function() {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
complete = true;
|
||||
resolve();
|
||||
}, 3000);
|
||||
});
|
||||
};
|
||||
var taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, {
|
||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, {
|
||||
timeout: 10,
|
||||
});
|
||||
|
||||
return taskWithTimeout().then(
|
||||
function() {
|
||||
() => {
|
||||
throw new Error('it was not supposed to resolve!');
|
||||
},
|
||||
function() {
|
||||
() => {
|
||||
assert.strictEqual(complete, false);
|
||||
}
|
||||
);
|
||||
});
|
||||
it('resolves if task returns something falsey', function() {
|
||||
var task = function() {};
|
||||
var taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
||||
it('resolves if task returns something falsey', () => {
|
||||
const task = function() {};
|
||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
||||
return taskWithTimeout();
|
||||
});
|
||||
it('resolves if task returns a non-promise', function() {
|
||||
var task = function() {
|
||||
it('resolves if task returns a non-promise', () => {
|
||||
const task = function() {
|
||||
return 'hi!';
|
||||
};
|
||||
var taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
||||
return taskWithTimeout().then(function(result) {
|
||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
||||
return taskWithTimeout().then(result => {
|
||||
assert.strictEqual(result, 'hi!');
|
||||
});
|
||||
});
|
||||
it('rejects if task throws (and does not log about taking too long)', function() {
|
||||
var error = new Error('Task is throwing!');
|
||||
var task = function() {
|
||||
const error = new Error('Task is throwing!');
|
||||
const task = function() {
|
||||
throw error;
|
||||
};
|
||||
var taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, {
|
||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, {
|
||||
timeout: 10,
|
||||
});
|
||||
return taskWithTimeout().then(
|
||||
function(result) {
|
||||
result => {
|
||||
throw new Error('Overall task should reject!');
|
||||
},
|
||||
function(flowedError) {
|
||||
flowedError => {
|
||||
assert.strictEqual(flowedError, error);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
describe('WebSocket-Resource', function() {
|
||||
describe('requests and responses', function() {
|
||||
it('receives requests and sends responses', function(done) {
|
||||
describe('WebSocket-Resource', () => {
|
||||
describe('requests and responses', () => {
|
||||
it('receives requests and sends responses', done => {
|
||||
// mock socket
|
||||
var request_id = '1';
|
||||
var socket = {
|
||||
send: function(data) {
|
||||
var message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||
const request_id = '1';
|
||||
const socket = {
|
||||
send(data) {
|
||||
const message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||
assert.strictEqual(
|
||||
message.type,
|
||||
textsecure.protobuf.WebSocketMessage.Type.RESPONSE
|
||||
|
@ -18,12 +16,12 @@
|
|||
assert.strictEqual(message.response.id.toString(), request_id);
|
||||
done();
|
||||
},
|
||||
addEventListener: function() {},
|
||||
addEventListener() {},
|
||||
};
|
||||
|
||||
// actual test
|
||||
var resource = new WebSocketResource(socket, {
|
||||
handleRequest: function(request) {
|
||||
const resource = new WebSocketResource(socket, {
|
||||
handleRequest(request) {
|
||||
assert.strictEqual(request.verb, 'PUT');
|
||||
assert.strictEqual(request.path, '/some/path');
|
||||
assertEqualArrayBuffers(
|
||||
|
@ -52,12 +50,12 @@
|
|||
});
|
||||
});
|
||||
|
||||
it('sends requests and receives responses', function(done) {
|
||||
it('sends requests and receives responses', done => {
|
||||
// mock socket and request handler
|
||||
var request_id;
|
||||
var socket = {
|
||||
send: function(data) {
|
||||
var message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||
let request_id;
|
||||
const socket = {
|
||||
send(data) {
|
||||
const message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||
assert.strictEqual(
|
||||
message.type,
|
||||
textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
||||
|
@ -70,17 +68,17 @@
|
|||
);
|
||||
request_id = message.request.id;
|
||||
},
|
||||
addEventListener: function() {},
|
||||
addEventListener() {},
|
||||
};
|
||||
|
||||
// actual test
|
||||
var resource = new WebSocketResource(socket);
|
||||
const resource = new WebSocketResource(socket);
|
||||
resource.sendRequest({
|
||||
verb: 'PUT',
|
||||
path: '/some/path',
|
||||
body: new Uint8Array([1, 2, 3]).buffer,
|
||||
error: done,
|
||||
success: function(message, status, request) {
|
||||
success(message, status, request) {
|
||||
assert.strictEqual(message, 'OK');
|
||||
assert.strictEqual(status, 200);
|
||||
done();
|
||||
|
@ -101,19 +99,19 @@
|
|||
});
|
||||
});
|
||||
|
||||
describe('close', function() {
|
||||
before(function() {
|
||||
describe('close', () => {
|
||||
before(() => {
|
||||
window.WebSocket = MockSocket;
|
||||
});
|
||||
after(function() {
|
||||
after(() => {
|
||||
window.WebSocket = WebSocket;
|
||||
});
|
||||
it('closes the connection', function(done) {
|
||||
var mockServer = new MockServer('ws://localhost:8081');
|
||||
mockServer.on('connection', function(server) {
|
||||
it('closes the connection', done => {
|
||||
const mockServer = new MockServer('ws://localhost:8081');
|
||||
mockServer.on('connection', server => {
|
||||
server.on('close', done);
|
||||
});
|
||||
var resource = new WebSocketResource(
|
||||
const resource = new WebSocketResource(
|
||||
new WebSocket('ws://localhost:8081')
|
||||
);
|
||||
resource.close();
|
||||
|
@ -121,18 +119,18 @@
|
|||
});
|
||||
|
||||
describe.skip('with a keepalive config', function() {
|
||||
before(function() {
|
||||
before(() => {
|
||||
window.WebSocket = MockSocket;
|
||||
});
|
||||
after(function() {
|
||||
after(() => {
|
||||
window.WebSocket = WebSocket;
|
||||
});
|
||||
this.timeout(60000);
|
||||
it('sends keepalives once a minute', function(done) {
|
||||
var mockServer = new MockServer('ws://localhost:8081');
|
||||
mockServer.on('connection', function(server) {
|
||||
server.on('message', function(data) {
|
||||
var message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||
it('sends keepalives once a minute', done => {
|
||||
const mockServer = new MockServer('ws://localhost:8081');
|
||||
mockServer.on('connection', server => {
|
||||
server.on('message', data => {
|
||||
const message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||
assert.strictEqual(
|
||||
message.type,
|
||||
textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
||||
|
@ -148,11 +146,11 @@
|
|||
});
|
||||
});
|
||||
|
||||
it('uses / as a default path', function(done) {
|
||||
var mockServer = new MockServer('ws://localhost:8081');
|
||||
mockServer.on('connection', function(server) {
|
||||
server.on('message', function(data) {
|
||||
var message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||
it('uses / as a default path', done => {
|
||||
const mockServer = new MockServer('ws://localhost:8081');
|
||||
mockServer.on('connection', server => {
|
||||
server.on('message', data => {
|
||||
const message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||
assert.strictEqual(
|
||||
message.type,
|
||||
textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
||||
|
@ -170,9 +168,9 @@
|
|||
|
||||
it('optionally disconnects if no response', function(done) {
|
||||
this.timeout(65000);
|
||||
var mockServer = new MockServer('ws://localhost:8081');
|
||||
var socket = new WebSocket('ws://localhost:8081');
|
||||
mockServer.on('connection', function(server) {
|
||||
const mockServer = new MockServer('ws://localhost:8081');
|
||||
const socket = new WebSocket('ws://localhost:8081');
|
||||
mockServer.on('connection', server => {
|
||||
server.on('close', done);
|
||||
});
|
||||
new WebSocketResource(socket, { keepalive: true });
|
||||
|
@ -180,12 +178,12 @@
|
|||
|
||||
it('allows resetting the keepalive timer', function(done) {
|
||||
this.timeout(65000);
|
||||
var mockServer = new MockServer('ws://localhost:8081');
|
||||
var socket = new WebSocket('ws://localhost:8081');
|
||||
var startTime = Date.now();
|
||||
mockServer.on('connection', function(server) {
|
||||
server.on('message', function(data) {
|
||||
var message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||
const mockServer = new MockServer('ws://localhost:8081');
|
||||
const socket = new WebSocket('ws://localhost:8081');
|
||||
const startTime = Date.now();
|
||||
mockServer.on('connection', server => {
|
||||
server.on('message', data => {
|
||||
const message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||
assert.strictEqual(
|
||||
message.type,
|
||||
textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
||||
|
@ -200,8 +198,8 @@
|
|||
done();
|
||||
});
|
||||
});
|
||||
var resource = new WebSocketResource(socket, { keepalive: true });
|
||||
setTimeout(function() {
|
||||
const resource = new WebSocketResource(socket, { keepalive: true });
|
||||
setTimeout(() => {
|
||||
resource.resetKeepAliveTimer();
|
||||
}, 5000);
|
||||
});
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
describe('TextSecureWebSocket', function() {
|
||||
var RealWebSocket = window.WebSocket;
|
||||
before(function() {
|
||||
describe('TextSecureWebSocket', () => {
|
||||
const RealWebSocket = window.WebSocket;
|
||||
before(() => {
|
||||
window.WebSocket = MockSocket;
|
||||
});
|
||||
after(function() {
|
||||
after(() => {
|
||||
window.WebSocket = RealWebSocket;
|
||||
});
|
||||
it('connects and disconnects', function(done) {
|
||||
var mockServer = new MockServer('ws://localhost:8080');
|
||||
mockServer.on('connection', function(server) {
|
||||
it('connects and disconnects', done => {
|
||||
const mockServer = new MockServer('ws://localhost:8080');
|
||||
mockServer.on('connection', server => {
|
||||
socket.close();
|
||||
server.close();
|
||||
done();
|
||||
|
@ -16,15 +16,15 @@ describe('TextSecureWebSocket', function() {
|
|||
var socket = new TextSecureWebSocket('ws://localhost:8080');
|
||||
});
|
||||
|
||||
it('sends and receives', function(done) {
|
||||
var mockServer = new MockServer('ws://localhost:8080');
|
||||
mockServer.on('connection', function(server) {
|
||||
server.on('message', function(data) {
|
||||
it('sends and receives', done => {
|
||||
const mockServer = new MockServer('ws://localhost:8080');
|
||||
mockServer.on('connection', server => {
|
||||
server.on('message', data => {
|
||||
server.send('ack');
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
var socket = new TextSecureWebSocket('ws://localhost:8080');
|
||||
const socket = new TextSecureWebSocket('ws://localhost:8080');
|
||||
socket.onmessage = function(response) {
|
||||
assert.strictEqual(response.data, 'ack');
|
||||
socket.close();
|
||||
|
@ -33,9 +33,9 @@ describe('TextSecureWebSocket', function() {
|
|||
socket.send('syn');
|
||||
});
|
||||
|
||||
it('exposes the socket status', function(done) {
|
||||
var mockServer = new MockServer('ws://localhost:8082');
|
||||
mockServer.on('connection', function(server) {
|
||||
it('exposes the socket status', done => {
|
||||
const mockServer = new MockServer('ws://localhost:8082');
|
||||
mockServer.on('connection', server => {
|
||||
assert.strictEqual(socket.getStatus(), WebSocket.OPEN);
|
||||
server.close();
|
||||
socket.close();
|
||||
|
@ -49,11 +49,11 @@ describe('TextSecureWebSocket', function() {
|
|||
|
||||
it('reconnects', function(done) {
|
||||
this.timeout(60000);
|
||||
var mockServer = new MockServer('ws://localhost:8082');
|
||||
var socket = new TextSecureWebSocket('ws://localhost:8082');
|
||||
const mockServer = new MockServer('ws://localhost:8082');
|
||||
const socket = new TextSecureWebSocket('ws://localhost:8082');
|
||||
socket.onclose = function() {
|
||||
var mockServer = new MockServer('ws://localhost:8082');
|
||||
mockServer.on('connection', function(server) {
|
||||
const mockServer = new MockServer('ws://localhost:8082');
|
||||
mockServer.on('connection', server => {
|
||||
socket.close();
|
||||
server.close();
|
||||
done();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
/* global window, dcodeIO, Event, textsecure, FileReader, WebSocketResource */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
/*
|
||||
* WebSocket-Resources
|
||||
*
|
||||
|
@ -23,7 +24,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
var Request = function(options) {
|
||||
const Request = function Request(options) {
|
||||
this.verb = options.verb || options.type;
|
||||
this.path = options.path || options.url;
|
||||
this.body = options.body || options.data;
|
||||
|
@ -32,7 +33,7 @@
|
|||
this.id = options.id;
|
||||
|
||||
if (this.id === undefined) {
|
||||
var bits = new Uint32Array(2);
|
||||
const bits = new Uint32Array(2);
|
||||
window.crypto.getRandomValues(bits);
|
||||
this.id = dcodeIO.Long.fromBits(bits[0], bits[1], true);
|
||||
}
|
||||
|
@ -42,19 +43,19 @@
|
|||
}
|
||||
};
|
||||
|
||||
var IncomingWebSocketRequest = function(options) {
|
||||
var request = new Request(options);
|
||||
var socket = options.socket;
|
||||
const IncomingWebSocketRequest = function IncomingWebSocketRequest(options) {
|
||||
const request = new Request(options);
|
||||
const { socket } = options;
|
||||
|
||||
this.verb = request.verb;
|
||||
this.path = request.path;
|
||||
this.body = request.body;
|
||||
|
||||
this.respond = function(status, message) {
|
||||
this.respond = (status, message) => {
|
||||
socket.send(
|
||||
new textsecure.protobuf.WebSocketMessage({
|
||||
type: textsecure.protobuf.WebSocketMessage.Type.RESPONSE,
|
||||
response: { id: request.id, message: message, status: status },
|
||||
response: { id: request.id, message, status },
|
||||
})
|
||||
.encode()
|
||||
.toArrayBuffer()
|
||||
|
@ -62,9 +63,12 @@
|
|||
};
|
||||
};
|
||||
|
||||
var outgoing = {};
|
||||
var OutgoingWebSocketRequest = function(options, socket) {
|
||||
var request = new Request(options);
|
||||
const outgoing = {};
|
||||
const OutgoingWebSocketRequest = function OutgoingWebSocketRequest(
|
||||
options,
|
||||
socket
|
||||
) {
|
||||
const request = new Request(options);
|
||||
outgoing[request.id] = request;
|
||||
socket.send(
|
||||
new textsecure.protobuf.WebSocketMessage({
|
||||
|
@ -81,22 +85,18 @@
|
|||
);
|
||||
};
|
||||
|
||||
window.WebSocketResource = function(socket, opts) {
|
||||
opts = opts || {};
|
||||
var handleRequest = opts.handleRequest;
|
||||
window.WebSocketResource = function WebSocketResource(socket, opts = {}) {
|
||||
let { handleRequest } = opts;
|
||||
if (typeof handleRequest !== 'function') {
|
||||
handleRequest = function(request) {
|
||||
request.respond(404, 'Not found');
|
||||
};
|
||||
handleRequest = request => request.respond(404, 'Not found');
|
||||
}
|
||||
this.sendRequest = function(options) {
|
||||
return new OutgoingWebSocketRequest(options, socket);
|
||||
};
|
||||
this.sendRequest = options => new OutgoingWebSocketRequest(options, socket);
|
||||
|
||||
socket.onmessage = function(socketMessage) {
|
||||
var blob = socketMessage.data;
|
||||
var handleArrayBuffer = function(buffer) {
|
||||
var message = textsecure.protobuf.WebSocketMessage.decode(buffer);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
socket.onmessage = socketMessage => {
|
||||
const blob = socketMessage.data;
|
||||
const handleArrayBuffer = buffer => {
|
||||
const message = textsecure.protobuf.WebSocketMessage.decode(buffer);
|
||||
if (
|
||||
message.type === textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
||||
) {
|
||||
|
@ -106,17 +106,17 @@
|
|||
path: message.request.path,
|
||||
body: message.request.body,
|
||||
id: message.request.id,
|
||||
socket: socket,
|
||||
socket,
|
||||
})
|
||||
);
|
||||
} else if (
|
||||
message.type === textsecure.protobuf.WebSocketMessage.Type.RESPONSE
|
||||
) {
|
||||
var response = message.response;
|
||||
var request = outgoing[response.id];
|
||||
const { response } = message;
|
||||
const request = outgoing[response.id];
|
||||
if (request) {
|
||||
request.response = response;
|
||||
var callback = request.error;
|
||||
let callback = request.error;
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
callback = request.success;
|
||||
}
|
||||
|
@ -125,8 +125,9 @@
|
|||
callback(response.message, response.status, request);
|
||||
}
|
||||
} else {
|
||||
throw 'Received response for unknown request ' +
|
||||
message.response.id;
|
||||
throw new Error(
|
||||
`Received response for unknown request ${message.response.id}`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -134,10 +135,8 @@
|
|||
if (blob instanceof ArrayBuffer) {
|
||||
handleArrayBuffer(blob);
|
||||
} else {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
handleArrayBuffer(reader.result);
|
||||
};
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => handleArrayBuffer(reader.result);
|
||||
reader.readAsArrayBuffer(blob);
|
||||
}
|
||||
};
|
||||
|
@ -147,7 +146,7 @@
|
|||
path: opts.keepalive.path,
|
||||
disconnect: opts.keepalive.disconnect,
|
||||
});
|
||||
var resetKeepAliveTimer = this.keepalive.reset.bind(this.keepalive);
|
||||
const resetKeepAliveTimer = this.keepalive.reset.bind(this.keepalive);
|
||||
socket.addEventListener('open', resetKeepAliveTimer);
|
||||
socket.addEventListener('message', resetKeepAliveTimer);
|
||||
socket.addEventListener(
|
||||
|
@ -156,54 +155,45 @@
|
|||
);
|
||||
}
|
||||
|
||||
socket.addEventListener(
|
||||
'close',
|
||||
function() {
|
||||
socket.addEventListener('close', () => {
|
||||
this.closed = true;
|
||||
}.bind(this)
|
||||
);
|
||||
});
|
||||
|
||||
this.close = function(code, reason) {
|
||||
this.close = (code = 3000, reason) => {
|
||||
if (this.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.log.info('WebSocketResource.close()');
|
||||
if (!code) {
|
||||
code = 3000;
|
||||
}
|
||||
if (this.keepalive) {
|
||||
this.keepalive.stop();
|
||||
}
|
||||
|
||||
socket.close(code, reason);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
socket.onmessage = null;
|
||||
|
||||
// On linux the socket can wait a long time to emit its close event if we've
|
||||
// lost the internet connection. On the order of minutes. This speeds that
|
||||
// process up.
|
||||
setTimeout(
|
||||
function() {
|
||||
setTimeout(() => {
|
||||
if (this.closed) {
|
||||
return;
|
||||
}
|
||||
this.closed = true;
|
||||
|
||||
window.log.warn('Dispatching our own socket close event');
|
||||
var ev = new Event('close');
|
||||
const ev = new Event('close');
|
||||
ev.code = code;
|
||||
ev.reason = reason;
|
||||
this.dispatchEvent(ev);
|
||||
}.bind(this),
|
||||
1000
|
||||
);
|
||||
}, 1000);
|
||||
};
|
||||
};
|
||||
window.WebSocketResource.prototype = new textsecure.EventTarget();
|
||||
|
||||
function KeepAlive(websocketResource, opts) {
|
||||
function KeepAlive(websocketResource, opts = {}) {
|
||||
if (websocketResource instanceof WebSocketResource) {
|
||||
opts = opts || {};
|
||||
this.path = opts.path;
|
||||
if (this.path === undefined) {
|
||||
this.path = '/';
|
||||
|
@ -220,24 +210,20 @@
|
|||
|
||||
KeepAlive.prototype = {
|
||||
constructor: KeepAlive,
|
||||
stop: function() {
|
||||
stop() {
|
||||
clearTimeout(this.keepAliveTimer);
|
||||
clearTimeout(this.disconnectTimer);
|
||||
},
|
||||
reset: function() {
|
||||
reset() {
|
||||
clearTimeout(this.keepAliveTimer);
|
||||
clearTimeout(this.disconnectTimer);
|
||||
this.keepAliveTimer = setTimeout(
|
||||
function() {
|
||||
this.keepAliveTimer = setTimeout(() => {
|
||||
if (this.disconnect) {
|
||||
// automatically disconnect if server doesn't ack
|
||||
this.disconnectTimer = setTimeout(
|
||||
function() {
|
||||
this.disconnectTimer = setTimeout(() => {
|
||||
clearTimeout(this.keepAliveTimer);
|
||||
this.wsr.close(3001, 'No response to keepalive request');
|
||||
}.bind(this),
|
||||
1000
|
||||
);
|
||||
}, 1000);
|
||||
} else {
|
||||
this.reset();
|
||||
}
|
||||
|
@ -247,9 +233,7 @@
|
|||
path: this.path,
|
||||
success: this.reset.bind(this),
|
||||
});
|
||||
}.bind(this),
|
||||
55000
|
||||
);
|
||||
}, 55000);
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
|
Loading…
Reference in a new issue