diff --git a/.eslintignore b/.eslintignore index 23b77e9d57be..aa9a74971d3f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -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 diff --git a/libtextsecure/ProvisioningCipher.js b/libtextsecure/ProvisioningCipher.js index 6aebbf6f5163..1bd61d547fd4 100644 --- a/libtextsecure/ProvisioningCipher.js +++ b/libtextsecure/ProvisioningCipher.js @@ -1,83 +1,76 @@ -(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 = { - identityKeyPair: keyPair, - number: provisionMessage.number, - provisioningCode: provisionMessage.provisioningCode, - userAgent: provisionMessage.userAgent, - readReceipts: provisionMessage.readReceipts, - }; - if (provisionMessage.profileKey) { - ret.profileKey = provisionMessage.profileKey.toArrayBuffer(); - } - return ret; - }); + return libsignal.Curve.async.createKeyPair(privKey).then(keyPair => { + const ret = { + identityKeyPair: keyPair, + number: provisionMessage.number, + provisioningCode: provisionMessage.provisioningCode, + userAgent: provisionMessage.userAgent, + readReceipts: provisionMessage.readReceipts, + }; + if (provisionMessage.profileKey) { + ret.profileKey = provisionMessage.profileKey.toArrayBuffer(); + } + return ret; + }); }); }, - getPublicKey: function() { + getPublicKey() { return Promise.resolve() - .then( - function() { - if (!this.keyPair) { - return libsignal.Curve.async.generateKeyPair().then( - function(keyPair) { - this.keyPair = keyPair; - }.bind(this) - ); - } - }.bind(this) - ) - .then( - function() { - return this.keyPair.pubKey; - }.bind(this) - ); + .then(() => { + if (!this.keyPair) { + return libsignal.Curve.async.generateKeyPair().then(keyPair => { + this.keyPair = keyPair; + }); + } + + 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); diff --git a/libtextsecure/account_manager.js b/libtextsecure/account_manager.js index 16f4244c4665..c85e36c0462a 100644 --- a/libtextsecure/account_manager.js +++ b/libtextsecure/account_manager.js @@ -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,234 +62,213 @@ ) .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) { - window.log.info('provisioning socket closed. Code:', event.code); - if (!gotProvisionEnvelope) { - reject(new Error('websocket closed')); - } - }; - socket.onopen = function(e) { - window.log.info('provisioning socket open'); - }; - var wsr = new WebSocketResource(socket, { - keepalive: { path: '/v1/keepalive/provisioning' }, - handleRequest: function(request) { - if (request.path === '/v1/address' && request.verb === 'PUT') { - var proto = textsecure.protobuf.ProvisioningUuid.decode( - request.body - ); - setProvisioningUrl( - [ - 'tsdevice:/?uuid=', - proto.uuid, - '&pub_key=', - encodeURIComponent(btoa(getString(pubKey))), - ].join('') - ); - request.respond(200, 'OK'); - } else if ( - request.path === '/v1/message' && - request.verb === 'PUT' - ) { - var envelope = textsecure.protobuf.ProvisionEnvelope.decode( - request.body, - 'binary' - ); - request.respond(200, 'OK'); - gotProvisionEnvelope = true; - wsr.close(); - resolve( - provisioningCipher - .decrypt(envelope) - .then(function(provisionMessage) { - return queueTask(function() { - return confirmNumber(provisionMessage.number).then( - function(deviceName) { - if ( - typeof deviceName !== 'string' || - deviceName.length === 0 - ) { - throw new Error('Invalid device name'); + 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 = () => { + window.log.info('provisioning socket open'); + }; + const wsr = new WebSocketResource(socket, { + keepalive: { path: '/v1/keepalive/provisioning' }, + handleRequest(request) { + if (request.path === '/v1/address' && request.verb === 'PUT') { + const proto = textsecure.protobuf.ProvisioningUuid.decode( + request.body + ); + setProvisioningUrl( + [ + 'tsdevice:/?uuid=', + proto.uuid, + '&pub_key=', + encodeURIComponent(btoa(getString(pubKey))), + ].join('') + ); + request.respond(200, 'OK'); + } else if ( + request.path === '/v1/message' && + request.verb === 'PUT' + ) { + const envelope = textsecure.protobuf.ProvisionEnvelope.decode( + request.body, + 'binary' + ); + request.respond(200, 'OK'); + gotProvisionEnvelope = true; + wsr.close(); + resolve( + provisioningCipher + .decrypt(envelope) + .then(provisionMessage => + queueTask(() => + confirmNumber(provisionMessage.number).then( + deviceName => { + if ( + typeof deviceName !== 'string' || + deviceName.length === 0 + ) { + throw new Error('Invalid device name'); + } + return createAccount( + provisionMessage.number, + provisionMessage.provisioningCode, + provisionMessage.identityKeyPair, + provisionMessage.profileKey, + deviceName, + provisionMessage.userAgent, + provisionMessage.readReceipts + ) + .then(clearSessionsAndPreKeys) + .then(generateKeys) + .then(keys => + registerKeys(keys).then(() => + confirmKeys(keys) + ) + ) + .then(registrationDone); } - return createAccount( - provisionMessage.number, - provisionMessage.provisioningCode, - provisionMessage.identityKeyPair, - provisionMessage.profileKey, - deviceName, - provisionMessage.userAgent, - provisionMessage.readReceipts - ) - .then(clearSessionsAndPreKeys) - .then(generateKeys) - .then(function(keys) { - return registerKeys(keys).then(function() { - return confirmKeys(keys); - }); - }) - .then(registrationDone); - } - ); - }); - }) - ); - } else { - window.log.error('Unknown websocket message', request.path); - } - }, - }); - }); - }); + ) + ) + ) + ); + } 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); - if (preKeyCount < 10) { - return generateKeys().then(registerKeys); + return this.queueTask(() => + this.server.getMyKeys().then(preKeyCount => { + window.log.info(`prekey count ${preKeyCount}`); + if (preKeyCount < 10) { + return generateKeys().then(registerKeys); + } + return null; + }) + ); + }, + rotateSignedPreKey() { + return this.queueTask(() => { + const signedKeyId = textsecure.storage.get('signedKeyId', 1); + if (typeof signedKeyId !== 'number') { + throw new Error('Invalid signedKeyId'); + } + + 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( + identityKey => + libsignal.KeyHelper.generateSignedPreKey( + identityKey, + signedKeyId + ), + () => { + window.log.error( + 'Failed to get identity key. Canceling key rotation.' + ); + } + ) + .then(res => { + if (!res) { + return null; + } + window.log.info('Saving new signed prekey', res.keyId); + return Promise.all([ + textsecure.storage.put('signedKeyId', signedKeyId + 1), + store.storeSignedPreKey(res.keyId, res.keyPair), + server.setSignedPreKey({ + keyId: res.keyId, + publicKey: res.keyPair.pubKey, + signature: res.signature, + }), + ]) + .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(() => cleanSignedPreKeys()); + }) + .catch(e => { + window.log.error( + 'rotateSignedPrekey error:', + e && e.stack ? e.stack : e + ); + + if ( + e instanceof Error && + e.name === 'HTTPError' && + e.code >= 400 && + e.code <= 599 + ) { + const rejections = + 1 + textsecure.storage.get('signedKeyRotationRejected', 0); + textsecure.storage.put('signedKeyRotationRejected', rejections); + window.log.error( + 'Signed key rotation rejected count:', + rejections + ); + } else { + throw e; } }); - }.bind(this) - ); + }); }, - rotateSignedPreKey: function() { - return this.queueTask( - function() { - var signedKeyId = textsecure.storage.get('signedKeyId', 1); - if (typeof signedKeyId != 'number') { - throw new Error('Invalid signedKeyId'); - } + queueTask(task) { + const taskWithTimeout = textsecure.createTaskWithTimeout(task); + this.pending = this.pending.then(taskWithTimeout, taskWithTimeout); - var store = textsecure.storage.protocol; - var server = this.server; - var cleanSignedPreKeys = this.cleanSignedPreKeys; - - // 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, - signedKeyId - ); - }, - function(error) { - window.log.error( - 'Failed to get identity key. Canceling key rotation.' - ); - } - ) - .then(function(res) { - if (!res) { - return; - } - window.log.info('Saving new signed prekey', res.keyId); - return Promise.all([ - textsecure.storage.put('signedKeyId', signedKeyId + 1), - store.storeSignedPreKey(res.keyId, res.keyPair), - server.setSignedPreKey({ - keyId: res.keyId, - publicKey: res.keyPair.pubKey, - signature: res.signature, - }), - ]) - .then(function() { - var 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(); - }); - }) - .catch(function(e) { - window.log.error( - 'rotateSignedPrekey error:', - e && e.stack ? e.stack : e - ); - - if ( - e instanceof Error && - e.name == 'HTTPError' && - e.code >= 400 && - e.code <= 599 - ) { - var rejections = - 1 + textsecure.storage.get('signedKeyRotationRejected', 0); - textsecure.storage.put('signedKeyRotationRejected', rejections); - window.log.error( - 'Signed key rotation rejected count:', - rejections - ); - } else { - throw e; - } - }); - }.bind(this) - ); + return this.pending; }, - 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); - }); + 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 - var confirmed = allKeys.filter(function(key) { - return key.confirmed; - }); - var unconfirmed = allKeys.filter(function(key) { - return !key.confirmed; - }); + let confirmed = allKeys.filter(key => key.confirmed); + const unconfirmed = allKeys.filter(key => !key.confirmed); - 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); + 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,60 +371,58 @@ return response; }) - .then( - function(response) { - textsecure.storage.remove('identityKey'); - textsecure.storage.remove('signaling_key'); - textsecure.storage.remove('password'); - textsecure.storage.remove('registrationId'); - textsecure.storage.remove('number_id'); - textsecure.storage.remove('device_name'); - textsecure.storage.remove('regionCode'); - textsecure.storage.remove('userAgent'); - textsecure.storage.remove('profileKey'); - textsecure.storage.remove('read-receipts-setting'); + .then(response => { + textsecure.storage.remove('identityKey'); + textsecure.storage.remove('signaling_key'); + textsecure.storage.remove('password'); + textsecure.storage.remove('registrationId'); + textsecure.storage.remove('number_id'); + textsecure.storage.remove('device_name'); + textsecure.storage.remove('regionCode'); + textsecure.storage.remove('userAgent'); + textsecure.storage.remove('profileKey'); + textsecure.storage.remove('read-receipts-setting'); - // update our own identity key, which may have changed - // if we're relinking after a reinstall on the master device - textsecure.storage.protocol.saveIdentityWithAttributes(number, { - id: number, - publicKey: identityKeyPair.pubKey, - firstUse: true, - timestamp: Date.now(), - verified: textsecure.storage.protocol.VerifiedStatus.VERIFIED, - nonblockingApproval: true, - }); + // update our own identity key, which may have changed + // if we're relinking after a reinstall on the master device + textsecure.storage.protocol.saveIdentityWithAttributes(number, { + id: number, + publicKey: identityKeyPair.pubKey, + firstUse: true, + timestamp: Date.now(), + verified: textsecure.storage.protocol.VerifiedStatus.VERIFIED, + nonblockingApproval: true, + }); - textsecure.storage.put('identityKey', identityKeyPair); - textsecure.storage.put('signaling_key', signalingKey); - textsecure.storage.put('password', password); - textsecure.storage.put('registrationId', registrationId); - if (profileKey) { - textsecure.storage.put('profileKey', profileKey); - } - if (userAgent) { - textsecure.storage.put('userAgent', userAgent); - } - if (readReceipts) { - textsecure.storage.put('read-receipt-setting', true); - } else { - textsecure.storage.put('read-receipt-setting', false); - } + textsecure.storage.put('identityKey', identityKeyPair); + textsecure.storage.put('signaling_key', signalingKey); + textsecure.storage.put('password', password); + textsecure.storage.put('registrationId', registrationId); + if (profileKey) { + textsecure.storage.put('profileKey', profileKey); + } + if (userAgent) { + textsecure.storage.put('userAgent', userAgent); + } + if (readReceipts) { + textsecure.storage.put('read-receipt-setting', true); + } else { + textsecure.storage.put('read-receipt-setting', false); + } - textsecure.storage.user.setNumberAndDeviceId( - number, - response.deviceId || 1, - deviceName - ); - textsecure.storage.put( - 'regionCode', - libphonenumber.util.getRegionCodeForNumber(number) - ); - }.bind(this) - ); + textsecure.storage.user.setNumberAndDeviceId( + number, + response.deviceId || 1, + deviceName + ); + textsecure.storage.put( + 'regionCode', + libphonenumber.util.getRegionCodeForNumber(number) + ); + }); }, - 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,79 +432,74 @@ ]); }, // 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 = []; - - for (var keyId = startId; keyId < startId + count; ++keyId) { - promises.push( - libsignal.KeyHelper.generatePreKey(keyId).then(function(res) { - store.storePreKey(res.keyId, res.keyPair); - result.preKeys.push({ - keyId: res.keyId, - publicKey: res.keyPair.pubKey, - }); - if (progressCallback) { - progressCallback(); - } - }) - ); - } + const store = textsecure.storage.protocol; + return store.getIdentityKeyPair().then(identityKey => { + const result = { preKeys: [], identityKey: identityKey.pubKey }; + const promises = []; + for (let keyId = startId; keyId < startId + count; keyId += 1) { promises.push( - libsignal.KeyHelper.generateSignedPreKey( - identityKey, - signedKeyId - ).then(function(res) { - store.storeSignedPreKey(res.keyId, res.keyPair); - result.signedPreKey = { + libsignal.KeyHelper.generatePreKey(keyId).then(res => { + store.storePreKey(res.keyId, res.keyPair); + result.preKeys.push({ keyId: res.keyId, publicKey: res.keyPair.pubKey, - signature: res.signature, - // server.registerKeys doesn't use keyPair, confirmKeys does - keyPair: res.keyPair, - }; + }); + if (progressCallback) { + progressCallback(); + } }) ); + } - textsecure.storage.put('maxPreKeyId', startId + count); - textsecure.storage.put('signedKeyId', signedKeyId + 1); - return Promise.all(promises).then( - function() { - // This is primarily for the signed prekey summary it logs out - return this.cleanSignedPreKeys().then(function() { - return result; - }); - }.bind(this) - ); - }.bind(this) - ); + promises.push( + libsignal.KeyHelper.generateSignedPreKey( + identityKey, + signedKeyId + ).then(res => { + store.storeSignedPreKey(res.keyId, res.keyPair); + result.signedPreKey = { + keyId: res.keyId, + publicKey: res.keyPair.pubKey, + signature: res.signature, + // server.registerKeys doesn't use keyPair, confirmKeys does + keyPair: res.keyPair, + }; + }) + ); + + textsecure.storage.put('maxPreKeyId', startId + count); + textsecure.storage.put('signedKeyId', signedKeyId + 1); + return Promise.all(promises).then(() => + // This is primarily for the signed prekey summary it logs out + this.cleanSignedPreKeys().then(() => result) + ); + }); }, - registrationDone: function() { + registrationDone() { window.log.info('registration done'); this.dispatchEvent(new Event('registration')); }, diff --git a/libtextsecure/contacts_parser.js b/libtextsecure/contacts_parser.js index fac0d228b642..710faf6ae4a0 100644 --- a/libtextsecure/contacts_parser.js +++ b/libtextsecure/contacts_parser.js @@ -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); diff --git a/libtextsecure/crypto.js b/libtextsecure/crypto.js index a30954811f9a..9504bd119a4c 100644 --- a/libtextsecure/crypto.js +++ b/libtextsecure/crypto.js @@ -1,30 +1,28 @@ +/* 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]); - } - if (result !== 0) { - throw new Error('Bad digest'); - } - }); + 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'); + } + }); } function calculateDigest(data) { return crypto.subtle.digest({ name: 'SHA-256' }, data); @@ -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,38 +193,36 @@ 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) { - // unpad - var name = ''; - var padded = new Uint8Array(decrypted); - for (var i = padded.length; i > 0; i--) { - if (padded[i - 1] !== 0x00) { - break; - } + return textsecure.crypto.decryptProfile(data, key).then(decrypted => { + // unpad + const padded = new Uint8Array(decrypted); + let i; + for (i = padded.length; i > 0; i -= 1) { + if (padded[i - 1] !== 0x00) { + break; } + } - return dcodeIO.ByteBuffer.wrap(padded) - .slice(0, i) - .toArrayBuffer(); - }); + return dcodeIO.ByteBuffer.wrap(padded) + .slice(0, i) + .toArrayBuffer(); + }); }, - getRandomBytes: function(size) { + getRandomBytes(size) { return libsignal.crypto.getRandomBytes(size); }, }; diff --git a/libtextsecure/errors.js b/libtextsecure/errors.js index 0e2d97a52c06..7e6221277ad6 100644 --- a/libtextsecure/errors.js +++ b/libtextsecure/errors.js @@ -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); diff --git a/libtextsecure/event_target.js b/libtextsecure/event_target.js index 6594a5b52d34..0bfaa31c9738 100644 --- a/libtextsecure/event_target.js +++ b/libtextsecure/event_target.js @@ -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; diff --git a/libtextsecure/helpers.js b/libtextsecure/helpers.js index bd56e958b7c0..ffa4b59dcb40 100644 --- a/libtextsecure/helpers.js +++ b/libtextsecure/helpers.js @@ -1,21 +1,25 @@ +/* global window, dcodeIO */ + +/* eslint-disable no-proto, no-restricted-syntax, guard-for-in */ + window.textsecure = window.textsecure || {}; -/********************************* +/** ******************************* *** Type conversion utilities *** - *********************************/ + ******************************** */ // 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__; +// TODO: Throw all this shit in favor of consistent types +// TODO: Namespace +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,49 +27,44 @@ 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('.'); - }; +window.textsecure.utils = (() => { + const self = {}; + self.unencodeNumber = number => number.split('.'); + self.isNumberSane = number => + number[0] === '+' && /^[0-9]+$/.test(number.substring(1)); - self.isNumberSane = function(number) { - return number[0] == '+' && /^[0-9]+$/.test(number.substring(1)); - }; - - /************************** + /** ************************ *** JSON'ing Utilities *** - **************************/ + ************************* */ 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; })(); diff --git a/libtextsecure/key_worker.js b/libtextsecure/key_worker.js index d8cccd6f895e..ba8f32155ae5 100644 --- a/libtextsecure/key_worker.js +++ b/libtextsecure/key_worker.js @@ -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(); }); }; diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index d82134790cc5..15410be90c42 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -1,3 +1,7 @@ +/* global textsecure, libsignal, window, btoa */ + +/* eslint-disable more/no-then */ + function OutgoingMessage( server, timestamp, @@ -7,8 +11,9 @@ function OutgoingMessage( callback ) { if (message instanceof textsecure.protobuf.DataMessage) { - var content = new textsecure.protobuf.Content(); + const content = new textsecure.protobuf.Content(); content.dataMessage = message; + // eslint-disable-next-line no-param-reassign message = content; } this.server = server; @@ -25,8 +30,8 @@ function OutgoingMessage( OutgoingMessage.prototype = { constructor: OutgoingMessage, - numberCompleted: function() { - this.numbersCompleted++; + numberCompleted() { + this.numbersCompleted += 1; if (this.numbersCompleted >= this.numbers.length) { this.callback({ successfulNumbers: this.successfulNumbers, @@ -34,8 +39,9 @@ OutgoingMessage.prototype = { }); } }, - registerError: function(number, reason, error) { + registerError(number, reason, error) { if (!error || (error.name === 'HTTPError' && error.code !== 404)) { + // eslint-disable-next-line no-param-reassign error = new textsecure.OutgoingMessageError( number, this.message.toArrayBuffer(), @@ -44,102 +50,94 @@ OutgoingMessage.prototype = { ); } + // eslint-disable-next-line no-param-reassign error.number = number; + // eslint-disable-next-line no-param-reassign error.reason = reason; this.errors[this.errors.length] = error; this.numberCompleted(); }, - reloadDevicesAndSend: function(number, recurse) { - return function() { - return textsecure.storage.protocol.getDeviceIds(number).then( - function(deviceIds) { - if (deviceIds.length == 0) { - return this.registerError( - number, - 'Got empty device list when loading device keys', - null - ); - } - return this.doSendMessage(number, deviceIds, recurse); - }.bind(this) - ); - }.bind(this); + reloadDevicesAndSend(number, recurse) { + return () => + textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => { + if (deviceIds.length === 0) { + return this.registerError( + number, + 'Got empty device list when loading device keys', + null + ); + } + return this.doSendMessage(number, deviceIds, recurse); + }); }, - getKeysForNumber: function(number, updateDevices) { - var handleResult = function(response) { - return Promise.all( - response.devices.map( - function(device) { - device.identityKey = response.identityKey; - if ( - updateDevices === undefined || - updateDevices.indexOf(device.deviceId) > -1 - ) { - var address = new libsignal.SignalProtocolAddress( - number, - device.deviceId - ); - var builder = new libsignal.SessionBuilder( - textsecure.storage.protocol, - address - ); - if (device.registrationId === 0) { - window.log.info('device registrationId 0!'); - } - return builder.processPreKey(device).catch( - function(error) { - if (error.message === 'Identity key changed') { - error.timestamp = this.timestamp; - error.originalMessage = this.message.toArrayBuffer(); - error.identityKey = device.identityKey; - } - throw error; - }.bind(this) - ); + getKeysForNumber(number, updateDevices) { + const handleResult = response => + Promise.all( + response.devices.map(device => { + // eslint-disable-next-line no-param-reassign + device.identityKey = response.identityKey; + if ( + updateDevices === undefined || + updateDevices.indexOf(device.deviceId) > -1 + ) { + const address = new libsignal.SignalProtocolAddress( + number, + device.deviceId + ); + const builder = new libsignal.SessionBuilder( + textsecure.storage.protocol, + address + ); + if (device.registrationId === 0) { + window.log.info('device registrationId 0!'); } - }.bind(this) - ) + return builder.processPreKey(device).catch(error => { + if (error.message === 'Identity key changed') { + // eslint-disable-next-line no-param-reassign + error.timestamp = this.timestamp; + // eslint-disable-next-line no-param-reassign + error.originalMessage = this.message.toArrayBuffer(); + // eslint-disable-next-line no-param-reassign + error.identityKey = device.identityKey; + } + throw error; + }); + } + + return null; + }) ); - }.bind(this); if (updateDevices === undefined) { return this.server.getKeysForNumber(number).then(handleResult); - } else { - var promise = Promise.resolve(); - updateDevices.forEach( - function(device) { - promise = promise.then( - function() { - return this.server - .getKeysForNumber(number, device) - .then(handleResult) - .catch( - function(e) { - if (e.name === 'HTTPError' && e.code === 404) { - if (device !== 1) { - return this.removeDeviceIdsForNumber(number, [device]); - } else { - throw new textsecure.UnregisteredUserError(number, e); - } - } else { - throw e; - } - }.bind(this) - ); - }.bind(this) - ); - }.bind(this) - ); - - return promise; } + let promise = Promise.resolve(); + updateDevices.forEach(device => { + promise = promise.then(() => + this.server + .getKeysForNumber(number, device) + .then(handleResult) + .catch(e => { + if (e.name === 'HTTPError' && e.code === 404) { + if (device !== 1) { + return this.removeDeviceIdsForNumber(number, [device]); + } + throw new textsecure.UnregisteredUserError(number, e); + } else { + throw e; + } + }) + ); + }); + + return promise; }, - transmitMessage: function(number, jsonData, timestamp) { + transmitMessage(number, jsonData, timestamp) { return this.server .sendMessages(number, jsonData, timestamp, this.silent) - .catch(function(e) { + .catch(e => { if (e.name === 'HTTPError' && (e.code !== 409 && e.code !== 410)) { // 409 and 410 should bubble and be handled by doSendMessage // 404 should throw UnregisteredUserError @@ -158,20 +156,20 @@ OutgoingMessage.prototype = { }); }, - getPaddedMessageLength: function(messageLength) { - var messageLengthWithTerminator = messageLength + 1; - var messagePartCount = Math.floor(messageLengthWithTerminator / 160); + getPaddedMessageLength(messageLength) { + const messageLengthWithTerminator = messageLength + 1; + let messagePartCount = Math.floor(messageLengthWithTerminator / 160); if (messageLengthWithTerminator % 160 !== 0) { - messagePartCount++; + messagePartCount += 1; } return messagePartCount * 160; }, - getPlaintext: function() { + getPlaintext() { if (!this.plaintext) { - var messageBuffer = this.message.toArrayBuffer(); + const messageBuffer = this.message.toArrayBuffer(); this.plaintext = new Uint8Array( this.getPaddedMessageLength(messageBuffer.byteLength + 1) - 1 ); @@ -181,172 +179,154 @@ OutgoingMessage.prototype = { return this.plaintext; }, - doSendMessage: function(number, deviceIds, recurse) { - var ciphers = {}; - var plaintext = this.getPlaintext(); + doSendMessage(number, deviceIds, recurse) { + const ciphers = {}; + const plaintext = this.getPlaintext(); return Promise.all( - deviceIds.map( - function(deviceId) { - var address = new libsignal.SignalProtocolAddress(number, deviceId); + deviceIds.map(deviceId => { + const address = new libsignal.SignalProtocolAddress(number, deviceId); - var ourNumber = textsecure.storage.user.getNumber(); - var options = {}; + const ourNumber = textsecure.storage.user.getNumber(); + const options = {}; - // No limit on message keys if we're communicating with our other devices - if (ourNumber === number) { - options.messageKeysLimit = false; - } + // No limit on message keys if we're communicating with our other devices + if (ourNumber === number) { + options.messageKeysLimit = false; + } - var sessionCipher = new libsignal.SessionCipher( - textsecure.storage.protocol, - address, - options - ); - ciphers[address.getDeviceId()] = sessionCipher; - return sessionCipher.encrypt(plaintext).then(function(ciphertext) { - return { - type: ciphertext.type, - destinationDeviceId: address.getDeviceId(), - destinationRegistrationId: ciphertext.registrationId, - content: btoa(ciphertext.body), - }; - }); - }.bind(this) - ) + const sessionCipher = new libsignal.SessionCipher( + textsecure.storage.protocol, + address, + options + ); + ciphers[address.getDeviceId()] = sessionCipher; + return sessionCipher.encrypt(plaintext).then(ciphertext => ({ + type: ciphertext.type, + destinationDeviceId: address.getDeviceId(), + destinationRegistrationId: ciphertext.registrationId, + content: btoa(ciphertext.body), + })); + }) ) - .then( - function(jsonData) { - return this.transmitMessage(number, jsonData, this.timestamp).then( - function() { - this.successfulNumbers[this.successfulNumbers.length] = number; - this.numberCompleted(); - }.bind(this) - ); - }.bind(this) + .then(jsonData => + this.transmitMessage(number, jsonData, this.timestamp).then(() => { + this.successfulNumbers[this.successfulNumbers.length] = number; + this.numberCompleted(); + }) ) - .catch( - function(error) { - if ( - error instanceof Error && - error.name == 'HTTPError' && - (error.code == 410 || error.code == 409) - ) { - if (!recurse) - return this.registerError( - number, - 'Hit retry limit attempting to reload device list', - error - ); - - var p; - if (error.code == 409) { - p = this.removeDeviceIdsForNumber( - number, - error.response.extraDevices - ); - } else { - p = Promise.all( - error.response.staleDevices.map(function(deviceId) { - return ciphers[deviceId].closeOpenSessionForDevice(); - }) - ); - } - - return p.then( - function() { - var resetDevices = - error.code == 410 - ? error.response.staleDevices - : error.response.missingDevices; - return this.getKeysForNumber(number, resetDevices).then( - this.reloadDevicesAndSend(number, error.code == 409) - ); - }.bind(this) - ); - } else if (error.message === 'Identity key changed') { - error.timestamp = this.timestamp; - error.originalMessage = this.message.toArrayBuffer(); - window.log.error( - 'Got "key changed" error from encrypt - no identityKey for application layer', + .catch(error => { + if ( + error instanceof Error && + error.name === 'HTTPError' && + (error.code === 410 || error.code === 409) + ) { + if (!recurse) + return this.registerError( number, - deviceIds - ); - throw error; - } else { - this.registerError( - number, - 'Failed to create or send message', + 'Hit retry limit attempting to reload device list', error ); - } - }.bind(this) - ); - }, - getStaleDeviceIdsForNumber: function(number) { - return textsecure.storage.protocol - .getDeviceIds(number) - .then(function(deviceIds) { - if (deviceIds.length === 0) { - return [1]; - } - var updateDevices = []; - return Promise.all( - deviceIds.map(function(deviceId) { - var address = new libsignal.SignalProtocolAddress(number, deviceId); - var sessionCipher = new libsignal.SessionCipher( - textsecure.storage.protocol, - address + let p; + if (error.code === 409) { + p = this.removeDeviceIdsForNumber( + number, + error.response.extraDevices ); - return sessionCipher.hasOpenSession().then(function(hasSession) { - if (!hasSession) { - updateDevices.push(deviceId); - } - }); - }) - ).then(function() { - return updateDevices; - }); + } else { + p = Promise.all( + error.response.staleDevices.map(deviceId => + ciphers[deviceId].closeOpenSessionForDevice() + ) + ); + } + + return p.then(() => { + const resetDevices = + error.code === 410 + ? error.response.staleDevices + : error.response.missingDevices; + return this.getKeysForNumber(number, resetDevices).then( + this.reloadDevicesAndSend(number, error.code === 409) + ); + }); + } else if (error.message === 'Identity key changed') { + // eslint-disable-next-line no-param-reassign + error.timestamp = this.timestamp; + // eslint-disable-next-line no-param-reassign + error.originalMessage = this.message.toArrayBuffer(); + window.log.error( + 'Got "key changed" error from encrypt - no identityKey for application layer', + number, + deviceIds + ); + throw error; + } else { + this.registerError(number, 'Failed to create or send message', error); + } + + return null; }); }, - removeDeviceIdsForNumber: function(number, deviceIdsToRemove) { - var promise = Promise.resolve(); - for (var j in deviceIdsToRemove) { - promise = promise.then(function() { - var encodedNumber = number + '.' + deviceIdsToRemove[j]; + getStaleDeviceIdsForNumber(number) { + return textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => { + if (deviceIds.length === 0) { + return [1]; + } + const updateDevices = []; + return Promise.all( + deviceIds.map(deviceId => { + const address = new libsignal.SignalProtocolAddress(number, deviceId); + const sessionCipher = new libsignal.SessionCipher( + textsecure.storage.protocol, + address + ); + return sessionCipher.hasOpenSession().then(hasSession => { + if (!hasSession) { + updateDevices.push(deviceId); + } + }); + }) + ).then(() => updateDevices); + }); + }, + + removeDeviceIdsForNumber(number, deviceIdsToRemove) { + let promise = Promise.resolve(); + // eslint-disable-next-line no-restricted-syntax, guard-for-in + for (const j in deviceIdsToRemove) { + promise = promise.then(() => { + const encodedNumber = `${number}.${deviceIdsToRemove[j]}`; return textsecure.storage.protocol.removeSession(encodedNumber); }); } return promise; }, - sendToNumber: function(number) { - return this.getStaleDeviceIdsForNumber(number).then( - function(updateDevices) { - return this.getKeysForNumber(number, updateDevices) - .then(this.reloadDevicesAndSend(number, true)) - .catch( - function(error) { - if (error.message === 'Identity key changed') { - error = new textsecure.OutgoingIdentityKeyError( - number, - error.originalMessage, - error.timestamp, - error.identityKey - ); - this.registerError(number, 'Identity key changed', error); - } else { - this.registerError( - number, - 'Failed to retrieve new device keys for number ' + number, - error - ); - } - }.bind(this) - ); - }.bind(this) + sendToNumber(number) { + return this.getStaleDeviceIdsForNumber(number).then(updateDevices => + this.getKeysForNumber(number, updateDevices) + .then(this.reloadDevicesAndSend(number, true)) + .catch(error => { + if (error.message === 'Identity key changed') { + // eslint-disable-next-line no-param-reassign + error = new textsecure.OutgoingIdentityKeyError( + number, + error.originalMessage, + error.timestamp, + error.identityKey + ); + this.registerError(number, 'Identity key changed', error); + } else { + this.registerError( + number, + `Failed to retrieve new device keys for number ${number}`, + error + ); + } + }) ); }, }; diff --git a/libtextsecure/protobufs.js b/libtextsecure/protobufs.js index 3dbfb48efa6d..476280b5bc29 100644 --- a/libtextsecure/protobufs.js +++ b/libtextsecure/protobufs.js @@ -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]; } } diff --git a/libtextsecure/protocol_wrapper.js b/libtextsecure/protocol_wrapper.js index 8d12e1be8fcb..ddbf71ed861f 100644 --- a/libtextsecure/protocol_wrapper.js +++ b/libtextsecure/protocol_wrapper.js @@ -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 || {}; diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index a98838006de7..13ff05110a9e 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -1,10 +1,14 @@ +/* global textsecure, WebAPI, libsignal, OutgoingMessage, window */ + +/* eslint-disable more/no-then, no-bitwise */ + function stringToArrayBuffer(str) { if (typeof str !== 'string') { throw new Error('Passed non-string to stringToArrayBuffer'); } - var res = new ArrayBuffer(str.length); - var uint = new Uint8Array(res); - for (var i = 0; i < str.length; i++) { + const res = new ArrayBuffer(str.length); + const uint = new Uint8Array(res); + for (let i = 0; i < str.length; i += 1) { uint[i] = str.charCodeAt(i); } return res; @@ -78,14 +82,14 @@ function Message(options) { Message.prototype = { constructor: Message, - isEndSession: function() { + isEndSession() { return this.flags & textsecure.protobuf.DataMessage.Flags.END_SESSION; }, - toProto: function() { + toProto() { if (this.dataMessage instanceof textsecure.protobuf.DataMessage) { return this.dataMessage; } - var proto = new textsecure.protobuf.DataMessage(); + const proto = new textsecure.protobuf.DataMessage(); if (this.body) { proto.body = this.body; } @@ -99,20 +103,17 @@ Message.prototype = { proto.group.type = this.group.type; } if (this.quote) { - var QuotedAttachment = - textsecure.protobuf.DataMessage.Quote.QuotedAttachment; - var Quote = textsecure.protobuf.DataMessage.Quote; + const { QuotedAttachment } = textsecure.protobuf.DataMessage.Quote; + const { Quote } = textsecure.protobuf.DataMessage; proto.quote = new Quote(); - var quote = proto.quote; + const { quote } = proto; quote.id = this.quote.id; quote.author = this.quote.author; quote.text = this.quote.text; - quote.attachments = (this.quote.attachments || []).map(function( - attachment - ) { - var quotedAttachment = new QuotedAttachment(); + quote.attachments = (this.quote.attachments || []).map(attachment => { + const quotedAttachment = new QuotedAttachment(); quotedAttachment.contentType = attachment.contentType; quotedAttachment.fileName = attachment.fileName; @@ -134,7 +135,7 @@ Message.prototype = { this.dataMessage = proto; return proto; }, - toArrayBuffer: function() { + toArrayBuffer() { return this.toProto().toArrayBuffer(); }, }; @@ -148,7 +149,7 @@ MessageSender.prototype = { constructor: MessageSender, // makeAttachmentPointer :: Attachment -> Promise AttachmentPointerProto - makeAttachmentPointer: function(attachment) { + makeAttachmentPointer(attachment) { if (typeof attachment !== 'object' || attachment == null) { return Promise.resolve(undefined); } @@ -159,49 +160,44 @@ MessageSender.prototype = { ) { return Promise.reject( new TypeError( - '`attachment.data` must be an `ArrayBuffer` or `ArrayBufferView`; got: ' + - typeof attachment.data + `\`attachment.data\` must be an \`ArrayBuffer\` or \`ArrayBufferView\`; got: ${typeof attachment.data}` ) ); } - var proto = new textsecure.protobuf.AttachmentPointer(); + const proto = new textsecure.protobuf.AttachmentPointer(); proto.key = libsignal.crypto.getRandomBytes(64); - var iv = libsignal.crypto.getRandomBytes(16); + const iv = libsignal.crypto.getRandomBytes(16); return textsecure.crypto .encryptAttachment(attachment.data, proto.key, iv) - .then( - function(result) { - return this.server - .putAttachment(result.ciphertext) - .then(function(id) { - proto.id = id; - proto.contentType = attachment.contentType; - proto.digest = result.digest; - if (attachment.fileName) { - proto.fileName = attachment.fileName; - } - if (attachment.size) { - proto.size = attachment.size; - } - if (attachment.flags) { - proto.flags = attachment.flags; - } - return proto; - }); - }.bind(this) + .then(result => + this.server.putAttachment(result.ciphertext).then(id => { + proto.id = id; + proto.contentType = attachment.contentType; + proto.digest = result.digest; + if (attachment.fileName) { + proto.fileName = attachment.fileName; + } + if (attachment.size) { + proto.size = attachment.size; + } + if (attachment.flags) { + proto.flags = attachment.flags; + } + return proto; + }) ); }, - retransmitMessage: function(number, jsonData, timestamp) { - var outgoing = new OutgoingMessage(this.server); + retransmitMessage(number, jsonData, timestamp) { + const outgoing = new OutgoingMessage(this.server); return outgoing.transmitMessage(number, jsonData, timestamp); }, - validateRetryContentMessage: function(content) { + validateRetryContentMessage(content) { // We want at least one field set, but not more than one - var count = 0; + let count = 0; count += content.syncMessage ? 1 : 0; count += content.dataMessage ? 1 : 0; count += content.callMessage ? 1 : 0; @@ -211,7 +207,7 @@ MessageSender.prototype = { } // It's most likely that dataMessage will be populated, so we look at it in detail - var data = content.dataMessage; + const data = content.dataMessage; if ( data && !data.attachments.length && @@ -226,12 +222,13 @@ MessageSender.prototype = { return true; }, - getRetryProto: function(message, timestamp) { - // If message was sent before v0.41.3 was released on Aug 7, then it was most certainly a DataMessage + getRetryProto(message, timestamp) { + // If message was sent before v0.41.3 was released on Aug 7, then it was most + // certainly a DataMessage // // var d = new Date('2017-08-07T07:00:00.000Z'); // d.getTime(); - var august7 = 1502089200000; + const august7 = 1502089200000; if (timestamp < august7) { return textsecure.protobuf.DataMessage.decode(message); } @@ -239,7 +236,7 @@ MessageSender.prototype = { // This is ugly. But we don't know what kind of proto we need to decode... try { // Simply decoding as a Content message may throw - var proto = textsecure.protobuf.Content.decode(message); + const proto = textsecure.protobuf.Content.decode(message); // But it might also result in an invalid object, so we try to detect that if (this.validateRetryContentMessage(proto)) { @@ -253,39 +250,40 @@ MessageSender.prototype = { } }, - tryMessageAgain: function(number, encodedMessage, timestamp) { - var proto = this.getRetryProto(encodedMessage, timestamp); + tryMessageAgain(number, encodedMessage, timestamp) { + const proto = this.getRetryProto(encodedMessage, timestamp); return this.sendIndividualProto(number, proto, timestamp); }, - queueJobForNumber: function(number, runJob) { - var taskWithTimeout = textsecure.createTaskWithTimeout( + queueJobForNumber(number, runJob) { + const taskWithTimeout = textsecure.createTaskWithTimeout( runJob, - 'queueJobForNumber ' + number + `queueJobForNumber ${number}` ); - var runPrevious = this.pendingMessages[number] || Promise.resolve(); - var runCurrent = (this.pendingMessages[number] = runPrevious.then( + const runPrevious = this.pendingMessages[number] || Promise.resolve(); + this.pendingMessages[number] = runPrevious.then( taskWithTimeout, taskWithTimeout - )); - runCurrent.then( - function() { - if (this.pendingMessages[number] === runCurrent) { - delete this.pendingMessages[number]; - } - }.bind(this) ); + + const runCurrent = this.pendingMessages[number]; + runCurrent.then(() => { + if (this.pendingMessages[number] === runCurrent) { + delete this.pendingMessages[number]; + } + }); }, - uploadAttachments: function(message) { + uploadAttachments(message) { return Promise.all( message.attachments.map(this.makeAttachmentPointer.bind(this)) ) - .then(function(attachmentPointers) { + .then(attachmentPointers => { + // eslint-disable-next-line no-param-reassign message.attachmentPointers = attachmentPointers; }) - .catch(function(error) { + .catch(error => { if (error instanceof Error && error.name === 'HTTPError') { throw new textsecure.MessageError(message, error); } else { @@ -294,26 +292,27 @@ MessageSender.prototype = { }); }, - uploadThumbnails: function(message) { - var makePointer = this.makeAttachmentPointer.bind(this); - var quote = message.quote; + uploadThumbnails(message) { + const makePointer = this.makeAttachmentPointer.bind(this); + const { quote } = message; if (!quote || !quote.attachments || quote.attachments.length === 0) { return Promise.resolve(); } return Promise.all( - quote.attachments.map(function(attachment) { - const thumbnail = attachment.thumbnail; + quote.attachments.map(attachment => { + const { thumbnail } = attachment; if (!thumbnail) { - return; + return null; } - return makePointer(thumbnail).then(function(pointer) { + return makePointer(thumbnail).then(pointer => { + // eslint-disable-next-line no-param-reassign attachment.attachmentPointer = pointer; }); }) - ).catch(function(error) { + ).catch(error => { if (error instanceof Error && error.name === 'HTTPError') { throw new textsecure.MessageError(message, error); } else { @@ -322,35 +321,32 @@ MessageSender.prototype = { }); }, - sendMessage: function(attrs) { - var message = new Message(attrs); + sendMessage(attrs) { + const message = new Message(attrs); return Promise.all([ this.uploadAttachments(message), this.uploadThumbnails(message), ]).then( - function() { - return new Promise( - function(resolve, reject) { - this.sendMessageProto( - message.timestamp, - message.recipients, - message.toProto(), - function(res) { - res.dataMessage = message.toArrayBuffer(); - if (res.errors.length > 0) { - reject(res); - } else { - resolve(res); - } + () => + new Promise((resolve, reject) => { + this.sendMessageProto( + message.timestamp, + message.recipients, + message.toProto(), + res => { + res.dataMessage = message.toArrayBuffer(); + if (res.errors.length > 0) { + reject(res); + } else { + resolve(res); } - ); - }.bind(this) - ); - }.bind(this) + } + ); + }) ); }, - sendMessageProto: function(timestamp, numbers, message, callback, silent) { - var rejections = textsecure.storage.get('signedKeyRotationRejected', 0); + sendMessageProto(timestamp, numbers, message, callback, silent) { + const rejections = textsecure.storage.get('signedKeyRotationRejected', 0); if (rejections > 5) { throw new textsecure.SignedPreKeyRotationError( numbers, @@ -359,7 +355,7 @@ MessageSender.prototype = { ); } - var outgoing = new OutgoingMessage( + const outgoing = new OutgoingMessage( this.server, timestamp, numbers, @@ -368,51 +364,43 @@ MessageSender.prototype = { callback ); - numbers.forEach( - function(number) { - this.queueJobForNumber(number, function() { - return outgoing.sendToNumber(number); - }); - }.bind(this) - ); + numbers.forEach(number => { + this.queueJobForNumber(number, () => outgoing.sendToNumber(number)); + }); }, - retrySendMessageProto: function(numbers, encodedMessage, timestamp) { - var proto = textsecure.protobuf.DataMessage.decode(encodedMessage); - return new Promise( - function(resolve, reject) { - this.sendMessageProto(timestamp, numbers, proto, function(res) { - if (res.errors.length > 0) { - reject(res); - } else { - resolve(res); - } - }); - }.bind(this) - ); + retrySendMessageProto(numbers, encodedMessage, timestamp) { + const proto = textsecure.protobuf.DataMessage.decode(encodedMessage); + return new Promise((resolve, reject) => { + this.sendMessageProto(timestamp, numbers, proto, res => { + if (res.errors.length > 0) { + reject(res); + } else { + resolve(res); + } + }); + }); }, - sendIndividualProto: function(number, proto, timestamp, silent) { - return new Promise( - function(resolve, reject) { - var callback = function(res) { - if (res.errors.length > 0) { - reject(res); - } else { - resolve(res); - } - }; - this.sendMessageProto(timestamp, [number], proto, callback, silent); - }.bind(this) - ); + sendIndividualProto(number, proto, timestamp, silent) { + return new Promise((resolve, reject) => { + const callback = res => { + if (res.errors.length > 0) { + reject(res); + } else { + resolve(res); + } + }; + this.sendMessageProto(timestamp, [number], proto, callback, silent); + }); }, - createSyncMessage: function() { - var syncMessage = new textsecure.protobuf.SyncMessage(); + createSyncMessage() { + const syncMessage = new textsecure.protobuf.SyncMessage(); // Generate a random int from 1 and 512 - var buffer = libsignal.crypto.getRandomBytes(1); - var paddingLength = (new Uint8Array(buffer)[0] & 0x1ff) + 1; + const buffer = libsignal.crypto.getRandomBytes(1); + const paddingLength = (new Uint8Array(buffer)[0] & 0x1ff) + 1; // Generate a random padding buffer of the chosen size syncMessage.padding = libsignal.crypto.getRandomBytes(paddingLength); @@ -420,22 +408,22 @@ MessageSender.prototype = { return syncMessage; }, - sendSyncMessage: function( + sendSyncMessage( encodedDataMessage, timestamp, destination, expirationStartTimestamp ) { - var myNumber = textsecure.storage.user.getNumber(); - var myDevice = textsecure.storage.user.getDeviceId(); - if (myDevice == 1) { + const myNumber = textsecure.storage.user.getNumber(); + const myDevice = textsecure.storage.user.getDeviceId(); + if (myDevice === 1 || myDevice === '1') { return Promise.resolve(); } - var dataMessage = textsecure.protobuf.DataMessage.decode( + const dataMessage = textsecure.protobuf.DataMessage.decode( encodedDataMessage ); - var sentMessage = new textsecure.protobuf.SyncMessage.Sent(); + const sentMessage = new textsecure.protobuf.SyncMessage.Sent(); sentMessage.timestamp = timestamp; sentMessage.message = dataMessage; if (destination) { @@ -444,12 +432,12 @@ MessageSender.prototype = { if (expirationStartTimestamp) { sentMessage.expirationStartTimestamp = expirationStartTimestamp; } - var syncMessage = this.createSyncMessage(); + const syncMessage = this.createSyncMessage(); syncMessage.sent = sentMessage; - var contentMessage = new textsecure.protobuf.Content(); + const contentMessage = new textsecure.protobuf.Content(); contentMessage.syncMessage = syncMessage; - var silent = true; + const silent = true; return this.sendIndividualProto( myNumber, contentMessage, @@ -458,25 +446,25 @@ MessageSender.prototype = { ); }, - getProfile: function(number) { + getProfile(number) { return this.server.getProfile(number); }, - getAvatar: function(path) { + getAvatar(path) { return this.server.getAvatar(path); }, - sendRequestConfigurationSyncMessage: function() { - var myNumber = textsecure.storage.user.getNumber(); - var myDevice = textsecure.storage.user.getDeviceId(); - if (myDevice != 1) { - var request = new textsecure.protobuf.SyncMessage.Request(); + sendRequestConfigurationSyncMessage() { + const myNumber = textsecure.storage.user.getNumber(); + const myDevice = textsecure.storage.user.getDeviceId(); + if (myDevice !== 1 && myDevice !== '1') { + const request = new textsecure.protobuf.SyncMessage.Request(); request.type = textsecure.protobuf.SyncMessage.Request.Type.CONFIGURATION; - var syncMessage = this.createSyncMessage(); + const syncMessage = this.createSyncMessage(); syncMessage.request = request; - var contentMessage = new textsecure.protobuf.Content(); + const contentMessage = new textsecure.protobuf.Content(); contentMessage.syncMessage = syncMessage; - var silent = true; + const silent = true; return this.sendIndividualProto( myNumber, contentMessage, @@ -487,18 +475,18 @@ MessageSender.prototype = { return Promise.resolve(); }, - sendRequestGroupSyncMessage: function() { - var myNumber = textsecure.storage.user.getNumber(); - var myDevice = textsecure.storage.user.getDeviceId(); - if (myDevice != 1) { - var request = new textsecure.protobuf.SyncMessage.Request(); + sendRequestGroupSyncMessage() { + const myNumber = textsecure.storage.user.getNumber(); + const myDevice = textsecure.storage.user.getDeviceId(); + if (myDevice !== 1 && myDevice !== '1') { + const request = new textsecure.protobuf.SyncMessage.Request(); request.type = textsecure.protobuf.SyncMessage.Request.Type.GROUPS; - var syncMessage = this.createSyncMessage(); + const syncMessage = this.createSyncMessage(); syncMessage.request = request; - var contentMessage = new textsecure.protobuf.Content(); + const contentMessage = new textsecure.protobuf.Content(); contentMessage.syncMessage = syncMessage; - var silent = true; + const silent = true; return this.sendIndividualProto( myNumber, contentMessage, @@ -510,18 +498,18 @@ MessageSender.prototype = { return Promise.resolve(); }, - sendRequestContactSyncMessage: function() { - var myNumber = textsecure.storage.user.getNumber(); - var myDevice = textsecure.storage.user.getDeviceId(); - if (myDevice != 1) { - var request = new textsecure.protobuf.SyncMessage.Request(); + sendRequestContactSyncMessage() { + const myNumber = textsecure.storage.user.getNumber(); + const myDevice = textsecure.storage.user.getDeviceId(); + if (myDevice !== 1 && myDevice !== '1') { + const request = new textsecure.protobuf.SyncMessage.Request(); request.type = textsecure.protobuf.SyncMessage.Request.Type.CONTACTS; - var syncMessage = this.createSyncMessage(); + const syncMessage = this.createSyncMessage(); syncMessage.request = request; - var contentMessage = new textsecure.protobuf.Content(); + const contentMessage = new textsecure.protobuf.Content(); contentMessage.syncMessage = syncMessage; - var silent = true; + const silent = true; return this.sendIndividualProto( myNumber, contentMessage, @@ -532,33 +520,33 @@ MessageSender.prototype = { return Promise.resolve(); }, - sendReadReceipts: function(sender, timestamps) { - var receiptMessage = new textsecure.protobuf.ReceiptMessage(); + sendReadReceipts(sender, timestamps) { + const receiptMessage = new textsecure.protobuf.ReceiptMessage(); receiptMessage.type = textsecure.protobuf.ReceiptMessage.Type.READ; receiptMessage.timestamp = timestamps; - var contentMessage = new textsecure.protobuf.Content(); + const contentMessage = new textsecure.protobuf.Content(); contentMessage.receiptMessage = receiptMessage; - var silent = true; + const silent = true; return this.sendIndividualProto(sender, contentMessage, Date.now(), silent); }, - syncReadMessages: function(reads) { - var myNumber = textsecure.storage.user.getNumber(); - var myDevice = textsecure.storage.user.getDeviceId(); - if (myDevice != 1) { - var syncMessage = this.createSyncMessage(); + syncReadMessages(reads) { + const myNumber = textsecure.storage.user.getNumber(); + const myDevice = textsecure.storage.user.getDeviceId(); + if (myDevice !== 1 && myDevice !== '1') { + const syncMessage = this.createSyncMessage(); syncMessage.read = []; - for (var i = 0; i < reads.length; ++i) { - var read = new textsecure.protobuf.SyncMessage.Read(); + for (let i = 0; i < reads.length; i += 1) { + const read = new textsecure.protobuf.SyncMessage.Read(); read.timestamp = reads[i].timestamp; read.sender = reads[i].sender; syncMessage.read.push(read); } - var contentMessage = new textsecure.protobuf.Content(); + const contentMessage = new textsecure.protobuf.Content(); contentMessage.syncMessage = syncMessage; - var silent = true; + const silent = true; return this.sendIndividualProto( myNumber, contentMessage, @@ -569,79 +557,72 @@ MessageSender.prototype = { return Promise.resolve(); }, - syncVerification: function(destination, state, identityKey) { - var myNumber = textsecure.storage.user.getNumber(); - var myDevice = textsecure.storage.user.getDeviceId(); - var now = Date.now(); + syncVerification(destination, state, identityKey) { + const myNumber = textsecure.storage.user.getNumber(); + const myDevice = textsecure.storage.user.getDeviceId(); + const now = Date.now(); - if (myDevice == 1) { + if (myDevice === 1 || myDevice === '1') { return Promise.resolve(); } // First send a null message to mask the sync message. - var nullMessage = new textsecure.protobuf.NullMessage(); + const nullMessage = new textsecure.protobuf.NullMessage(); // Generate a random int from 1 and 512 - var buffer = libsignal.crypto.getRandomBytes(1); - var paddingLength = (new Uint8Array(buffer)[0] & 0x1ff) + 1; + const buffer = libsignal.crypto.getRandomBytes(1); + const paddingLength = (new Uint8Array(buffer)[0] & 0x1ff) + 1; // Generate a random padding buffer of the chosen size nullMessage.padding = libsignal.crypto.getRandomBytes(paddingLength); - var contentMessage = new textsecure.protobuf.Content(); + const contentMessage = new textsecure.protobuf.Content(); contentMessage.nullMessage = nullMessage; // We want the NullMessage to look like a normal outgoing message; not silent const promise = this.sendIndividualProto(destination, contentMessage, now); - return promise.then( - function() { - var verified = new textsecure.protobuf.Verified(); - verified.state = state; - verified.destination = destination; - verified.identityKey = identityKey; - verified.nullMessage = nullMessage.padding; + return promise.then(() => { + const verified = new textsecure.protobuf.Verified(); + verified.state = state; + verified.destination = destination; + verified.identityKey = identityKey; + verified.nullMessage = nullMessage.padding; - var syncMessage = this.createSyncMessage(); - syncMessage.verified = verified; + const syncMessage = this.createSyncMessage(); + syncMessage.verified = verified; - var contentMessage = new textsecure.protobuf.Content(); - contentMessage.syncMessage = syncMessage; + const secondMessage = new textsecure.protobuf.Content(); + secondMessage.syncMessage = syncMessage; - var silent = true; - return this.sendIndividualProto(myNumber, contentMessage, now, silent); - }.bind(this) - ); + const silent = true; + return this.sendIndividualProto(myNumber, secondMessage, now, silent); + }); }, - sendGroupProto: function(numbers, proto, timestamp) { - timestamp = timestamp || Date.now(); - var me = textsecure.storage.user.getNumber(); - numbers = numbers.filter(function(number) { - return number != me; - }); + sendGroupProto(providedNumbers, proto, timestamp = Date.now()) { + const me = textsecure.storage.user.getNumber(); + const numbers = providedNumbers.filter(number => number !== me); if (numbers.length === 0) { return Promise.reject(new Error('No other members in the group')); } - return new Promise( - function(resolve, reject) { - var silent = true; - var callback = function(res) { - res.dataMessage = proto.toArrayBuffer(); - if (res.errors.length > 0) { - reject(res); - } else { - resolve(res); - } - }.bind(this); + return new Promise((resolve, reject) => { + const silent = true; + const callback = res => { + res.dataMessage = proto.toArrayBuffer(); + if (res.errors.length > 0) { + reject(res); + } else { + resolve(res); + } + }; - this.sendMessageProto(timestamp, numbers, proto, callback, silent); - }.bind(this) - ); + this.sendMessageProto(timestamp, numbers, proto, callback, silent); + }); }, - sendMessageToNumber: function( + sendMessageToNumber( number, messageText, attachments, @@ -653,75 +634,68 @@ MessageSender.prototype = { return this.sendMessage({ recipients: [number], body: messageText, - timestamp: timestamp, - attachments: attachments, - quote: quote, + timestamp, + attachments, + quote, needsSync: true, - expireTimer: expireTimer, - profileKey: profileKey, + expireTimer, + profileKey, }); }, - resetSession: function(number, timestamp) { + resetSession(number, timestamp) { window.log.info('resetting secure session'); - var proto = new textsecure.protobuf.DataMessage(); + const proto = new textsecure.protobuf.DataMessage(); proto.body = 'TERMINATE'; proto.flags = textsecure.protobuf.DataMessage.Flags.END_SESSION; - var logError = function(prefix) { - return function(error) { - window.log.error(prefix, error && error.stack ? error.stack : error); - throw error; - }; - }; - var deleteAllSessions = function(number) { - return textsecure.storage.protocol - .getDeviceIds(number) - .then(function(deviceIds) { - return Promise.all( - deviceIds.map(function(deviceId) { - var address = new libsignal.SignalProtocolAddress( - number, - deviceId - ); - window.log.info('deleting sessions for', address.toString()); - var sessionCipher = new libsignal.SessionCipher( - textsecure.storage.protocol, - address - ); - return sessionCipher.deleteAllSessionsForDevice(); - }) - ); - }); + const logError = prefix => error => { + window.log.error(prefix, error && error.stack ? error.stack : error); + throw error; }; + const deleteAllSessions = targetNumber => + textsecure.storage.protocol.getDeviceIds(targetNumber).then(deviceIds => + Promise.all( + deviceIds.map(deviceId => { + const address = new libsignal.SignalProtocolAddress( + targetNumber, + deviceId + ); + window.log.info('deleting sessions for', address.toString()); + const sessionCipher = new libsignal.SessionCipher( + textsecure.storage.protocol, + address + ); + return sessionCipher.deleteAllSessionsForDevice(); + }) + ) + ); - var sendToContact = deleteAllSessions(number) + const sendToContact = deleteAllSessions(number) .catch(logError('resetSession/deleteAllSessions1 error:')) - .then( - function() { - window.log.info( - 'finished closing local sessions, now sending to contact' - ); - return this.sendIndividualProto(number, proto, timestamp).catch( - logError('resetSession/sendToContact error:') - ); - }.bind(this) - ) - .then(function() { - return deleteAllSessions(number).catch( - logError('resetSession/deleteAllSessions2 error:') + .then(() => { + window.log.info( + 'finished closing local sessions, now sending to contact' ); - }); + return this.sendIndividualProto(number, proto, timestamp).catch( + logError('resetSession/sendToContact error:') + ); + }) + .then(() => + deleteAllSessions(number).catch( + logError('resetSession/deleteAllSessions2 error:') + ) + ); - var buffer = proto.toArrayBuffer(); - var sendSync = this.sendSyncMessage(buffer, timestamp, number).catch( + const buffer = proto.toArrayBuffer(); + const sendSync = this.sendSyncMessage(buffer, timestamp, number).catch( logError('resetSession/sendSync error:') ); return Promise.all([sendToContact, sendSync]); }, - sendMessageToGroup: function( + sendMessageToGroup( groupId, messageText, attachments, @@ -730,210 +704,183 @@ MessageSender.prototype = { expireTimer, profileKey ) { - return textsecure.storage.groups.getNumbers(groupId).then( - function(numbers) { - if (numbers === undefined) - return Promise.reject(new Error('Unknown Group')); + return textsecure.storage.groups.getNumbers(groupId).then(targetNumbers => { + if (targetNumbers === undefined) + return Promise.reject(new Error('Unknown Group')); - var me = textsecure.storage.user.getNumber(); - numbers = numbers.filter(function(number) { - return number != me; - }); - if (numbers.length === 0) { - return Promise.reject(new Error('No other members in the group')); - } + const me = textsecure.storage.user.getNumber(); + const numbers = targetNumbers.filter(number => number !== me); + if (numbers.length === 0) { + return Promise.reject(new Error('No other members in the group')); + } - return this.sendMessage({ - recipients: numbers, - body: messageText, - timestamp: timestamp, - attachments: attachments, - quote: quote, - needsSync: true, - expireTimer: expireTimer, - profileKey: profileKey, - group: { - id: groupId, - type: textsecure.protobuf.GroupContext.Type.DELIVER, - }, - }); - }.bind(this) - ); + return this.sendMessage({ + recipients: numbers, + body: messageText, + timestamp, + attachments, + quote, + needsSync: true, + expireTimer, + profileKey, + group: { + id: groupId, + type: textsecure.protobuf.GroupContext.Type.DELIVER, + }, + }); + }); }, - createGroup: function(numbers, name, avatar) { - var proto = new textsecure.protobuf.DataMessage(); + createGroup(targetNumbers, name, avatar) { + const proto = new textsecure.protobuf.DataMessage(); proto.group = new textsecure.protobuf.GroupContext(); - return textsecure.storage.groups.createNewGroup(numbers).then( - function(group) { + return textsecure.storage.groups + .createNewGroup(targetNumbers) + .then(group => { proto.group.id = stringToArrayBuffer(group.id); - var numbers = group.numbers; + const { numbers } = group; proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; proto.group.members = numbers; proto.group.name = name; - return this.makeAttachmentPointer(avatar).then( - function(attachment) { - proto.group.avatar = attachment; - return this.sendGroupProto(numbers, proto).then(function() { - return proto.group.id; - }); - }.bind(this) - ); - }.bind(this) - ); + return this.makeAttachmentPointer(avatar).then(attachment => { + proto.group.avatar = attachment; + return this.sendGroupProto(numbers, proto).then(() => proto.group.id); + }); + }); }, - updateGroup: function(groupId, name, avatar, numbers) { - var proto = new textsecure.protobuf.DataMessage(); + updateGroup(groupId, name, avatar, targetNumbers) { + const proto = new textsecure.protobuf.DataMessage(); proto.group = new textsecure.protobuf.GroupContext(); proto.group.id = stringToArrayBuffer(groupId); proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; proto.group.name = name; - return textsecure.storage.groups.addNumbers(groupId, numbers).then( - function(numbers) { + return textsecure.storage.groups + .addNumbers(groupId, targetNumbers) + .then(numbers => { if (numbers === undefined) { return Promise.reject(new Error('Unknown Group')); } proto.group.members = numbers; - return this.makeAttachmentPointer(avatar).then( - function(attachment) { - proto.group.avatar = attachment; - return this.sendGroupProto(numbers, proto).then(function() { - return proto.group.id; - }); - }.bind(this) - ); - }.bind(this) - ); + return this.makeAttachmentPointer(avatar).then(attachment => { + proto.group.avatar = attachment; + return this.sendGroupProto(numbers, proto).then(() => proto.group.id); + }); + }); }, - addNumberToGroup: function(groupId, number) { - var proto = new textsecure.protobuf.DataMessage(); + addNumberToGroup(groupId, number) { + const proto = new textsecure.protobuf.DataMessage(); proto.group = new textsecure.protobuf.GroupContext(); proto.group.id = stringToArrayBuffer(groupId); proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; - return textsecure.storage.groups.addNumbers(groupId, [number]).then( - function(numbers) { + return textsecure.storage.groups + .addNumbers(groupId, [number]) + .then(numbers => { if (numbers === undefined) return Promise.reject(new Error('Unknown Group')); proto.group.members = numbers; return this.sendGroupProto(numbers, proto); - }.bind(this) - ); + }); }, - setGroupName: function(groupId, name) { - var proto = new textsecure.protobuf.DataMessage(); + setGroupName(groupId, name) { + const proto = new textsecure.protobuf.DataMessage(); proto.group = new textsecure.protobuf.GroupContext(); proto.group.id = stringToArrayBuffer(groupId); proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; proto.group.name = name; - return textsecure.storage.groups.getNumbers(groupId).then( - function(numbers) { - if (numbers === undefined) - return Promise.reject(new Error('Unknown Group')); - proto.group.members = numbers; + return textsecure.storage.groups.getNumbers(groupId).then(numbers => { + if (numbers === undefined) + return Promise.reject(new Error('Unknown Group')); + proto.group.members = numbers; - return this.sendGroupProto(numbers, proto); - }.bind(this) - ); + return this.sendGroupProto(numbers, proto); + }); }, - setGroupAvatar: function(groupId, avatar) { - var proto = new textsecure.protobuf.DataMessage(); + setGroupAvatar(groupId, avatar) { + const proto = new textsecure.protobuf.DataMessage(); proto.group = new textsecure.protobuf.GroupContext(); proto.group.id = stringToArrayBuffer(groupId); proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; - return textsecure.storage.groups.getNumbers(groupId).then( - function(numbers) { - if (numbers === undefined) - return Promise.reject(new Error('Unknown Group')); - proto.group.members = numbers; + return textsecure.storage.groups.getNumbers(groupId).then(numbers => { + if (numbers === undefined) + return Promise.reject(new Error('Unknown Group')); + proto.group.members = numbers; - return this.makeAttachmentPointer(avatar).then( - function(attachment) { - proto.group.avatar = attachment; - return this.sendGroupProto(numbers, proto); - }.bind(this) - ); - }.bind(this) - ); + return this.makeAttachmentPointer(avatar).then(attachment => { + proto.group.avatar = attachment; + return this.sendGroupProto(numbers, proto); + }); + }); }, - leaveGroup: function(groupId) { - var proto = new textsecure.protobuf.DataMessage(); + leaveGroup(groupId) { + const proto = new textsecure.protobuf.DataMessage(); proto.group = new textsecure.protobuf.GroupContext(); proto.group.id = stringToArrayBuffer(groupId); proto.group.type = textsecure.protobuf.GroupContext.Type.QUIT; - return textsecure.storage.groups - .getNumbers(groupId) - .then(function(numbers) { - if (numbers === undefined) - return Promise.reject(new Error('Unknown Group')); - return textsecure.storage.groups.deleteGroup(groupId).then( - function() { - return this.sendGroupProto(numbers, proto); - }.bind(this) - ); - }); + return textsecure.storage.groups.getNumbers(groupId).then(numbers => { + if (numbers === undefined) + return Promise.reject(new Error('Unknown Group')); + return textsecure.storage.groups + .deleteGroup(groupId) + .then(() => this.sendGroupProto(numbers, proto)); + }); }, - sendExpirationTimerUpdateToGroup: function( + sendExpirationTimerUpdateToGroup( groupId, expireTimer, timestamp, profileKey ) { - return textsecure.storage.groups.getNumbers(groupId).then( - function(numbers) { - if (numbers === undefined) - return Promise.reject(new Error('Unknown Group')); + return textsecure.storage.groups.getNumbers(groupId).then(targetNumbers => { + if (targetNumbers === undefined) + return Promise.reject(new Error('Unknown Group')); - var me = textsecure.storage.user.getNumber(); - numbers = numbers.filter(function(number) { - return number != me; - }); - if (numbers.length === 0) { - return Promise.reject(new Error('No other members in the group')); - } - return this.sendMessage({ - recipients: numbers, - timestamp: timestamp, - needsSync: true, - expireTimer: expireTimer, - profileKey: profileKey, - flags: textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE, - group: { - id: groupId, - type: textsecure.protobuf.GroupContext.Type.DELIVER, - }, - }); - }.bind(this) - ); + const me = textsecure.storage.user.getNumber(); + const numbers = targetNumbers.filter(number => number !== me); + if (numbers.length === 0) { + return Promise.reject(new Error('No other members in the group')); + } + return this.sendMessage({ + recipients: numbers, + timestamp, + needsSync: true, + expireTimer, + profileKey, + flags: textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE, + group: { + id: groupId, + type: textsecure.protobuf.GroupContext.Type.DELIVER, + }, + }); + }); }, - sendExpirationTimerUpdateToNumber: function( + sendExpirationTimerUpdateToNumber( number, expireTimer, timestamp, profileKey ) { - var proto = new textsecure.protobuf.DataMessage(); return this.sendMessage({ recipients: [number], - timestamp: timestamp, + timestamp, needsSync: true, - expireTimer: expireTimer, - profileKey: profileKey, + expireTimer, + profileKey, flags: textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE, }); }, @@ -941,8 +888,13 @@ MessageSender.prototype = { window.textsecure = window.textsecure || {}; -textsecure.MessageSender = function(url, username, password, cdn_url) { - var sender = new MessageSender(url, username, password, cdn_url); +textsecure.MessageSender = function MessageSenderWrapper( + url, + username, + password, + cdnUrl +) { + const sender = new MessageSender(url, username, password, cdnUrl); textsecure.replay.registerFunction( sender.tryMessageAgain.bind(sender), textsecure.replay.Type.ENCRYPT_MESSAGE diff --git a/libtextsecure/storage.js b/libtextsecure/storage.js index b339e72512a1..dff324cbf365 100644 --- a/libtextsecure/storage.js +++ b/libtextsecure/storage.js @@ -1,42 +1,37 @@ -'use strict'; +/* global window, textsecure, localStorage */ +// eslint-disable-next-line func-names (function() { - /************************************************ + /** ********************************************** *** Utilities to store data in local storage *** - ************************************************/ + *********************************************** */ window.textsecure = window.textsecure || {}; window.textsecure.storage = window.textsecure.storage || {}; // Overrideable storage implementation window.textsecure.storage.impl = window.textsecure.storage.impl || { - /***************************** + /** *************************** *** 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); })(); diff --git a/libtextsecure/storage/groups.js b/libtextsecure/storage/groups.js index f2d5a23fcb15..9be9414c1cbb 100644 --- a/libtextsecure/storage/groups.js +++ b/libtextsecure/storage/groups.js @@ -1,32 +1,33 @@ -(function() { - 'use strict'; +/* global window, getString, libsignal, textsecure */ - /********************* +/* eslint-disable more/no-then */ + +// eslint-disable-next-line func-names +(function() { + /** ******************* *** Group Storage *** - *********************/ + ******************** */ window.textsecure = window.textsecure || {}; window.textsecure.storage = window.textsecure.storage || {}; // 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,131 +35,124 @@ ); } 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) { - if (group === undefined) return undefined; + getNumbers(groupId) { + return textsecure.storage.protocol.getGroup(groupId).then(group => { + if (group === undefined) return undefined; - return group.numbers; - }); + return group.numbers; + }); }, - removeNumber: function(groupId, number) { - return textsecure.storage.protocol - .getGroup(groupId) - .then(function(group) { - if (group === undefined) return undefined; + 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) - throw new Error( - 'Cannot remove ourselves from a group, leave the group instead' - ); - - var i = group.numbers.indexOf(number); - if (i > -1) { - group.numbers.splice(i, 1); - delete group.numberRegistrationIds[number]; - return textsecure.storage.protocol - .putGroup(groupId, group) - .then(function() { - return group.numbers; - }); - } - - return group.numbers; - }); - }, - - addNumbers: function(groupId, numbers) { - return textsecure.storage.protocol - .getGroup(groupId) - .then(function(group) { - if (group === undefined) return undefined; - - for (var i in numbers) { - var 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); - group.numberRegistrationIds[number] = {}; - } - } + const me = textsecure.storage.user.getNumber(); + if (number === me) + throw new Error( + 'Cannot remove ourselves from a group, leave the group instead' + ); + 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; + }); }, - deleteGroup: function(groupId) { + addNumbers(groupId, numbers) { + return textsecure.storage.protocol.getGroup(groupId).then(group => { + if (group === undefined) return undefined; + + // 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(() => group.numbers); + }); + }, + + deleteGroup(groupId) { return textsecure.storage.protocol.removeGroup(groupId); }, - getGroup: function(groupId) { - return textsecure.storage.protocol - .getGroup(groupId) - .then(function(group) { - if (group === undefined) return undefined; + getGroup(groupId) { + return textsecure.storage.protocol.getGroup(groupId).then(group => { + if (group === undefined) return undefined; - return { id: groupId, numbers: group.numbers }; - }); + return { id: groupId, numbers: group.numbers }; + }); }, - updateNumbers: function(groupId, numbers) { - return textsecure.storage.protocol - .getGroup(groupId) - .then(function(group) { - if (group === undefined) - throw new Error('Tried to update numbers for unknown 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 - ) - throw new Error('Invalid number in new group members'); + if ( + 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); - }); + return textsecure.storage.groups.addNumbers(groupId, added); + }); }, }; })(); diff --git a/libtextsecure/storage/unprocessed.js b/libtextsecure/storage/unprocessed.js index 16dd6a6dcc1d..8793feeb2b5c 100644 --- a/libtextsecure/storage/unprocessed.js +++ b/libtextsecure/storage/unprocessed.js @@ -1,23 +1,24 @@ -(function() { - 'use strict'; +/* global window, textsecure */ - /***************************************** +// eslint-disable-next-line func-names +(function() { + /** *************************************** *** Not-yet-processed message storage *** - *****************************************/ + **************************************** */ window.textsecure = window.textsecure || {}; 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); }, }; diff --git a/libtextsecure/storage/user.js b/libtextsecure/storage/user.js index bb8398a95ace..efbfd8e2db06 100644 --- a/libtextsecure/storage/user.js +++ b/libtextsecure/storage/user.js @@ -1,33 +1,34 @@ -'use strict'; +/* global textsecure, window */ +// eslint-disable-next-line func-names (function() { - /********************************************* + /** ******************************************* *** Utilities to store data about the user *** - **********************************************/ + ********************************************* */ window.textsecure = window.textsecure || {}; 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'); }, }; diff --git a/libtextsecure/stringview.js b/libtextsecure/stringview.js index 7d1e5e4b6a88..749b2eafe90e 100644 --- a/libtextsecure/stringview.js +++ b/libtextsecure/stringview.js @@ -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 - ? Math.ceil(((nInLen * 3 + 1) >> 2) / nBlocksSize) * nBlocksSize - : (nInLen * 3 + 1) >> 2; - var aBBytes = new ArrayBuffer(nOutLen); - var taBytes = new Uint8Array(aBBytes); + 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; + 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) { diff --git a/libtextsecure/sync_request.js b/libtextsecure/sync_request.js index 290e0fff15bb..73c153164ca0 100644 --- a/libtextsecure/sync_request.js +++ b/libtextsecure/sync_request.js @@ -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 diff --git a/libtextsecure/task_with_timeout.js b/libtextsecure/task_with_timeout.js index d966ab4c99dd..119d03f11c3d 100644 --- a/libtextsecure/task_with_timeout.js +++ b/libtextsecure/task_with_timeout.js @@ -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() { - if (!complete) { - var message = - (id || '') + - ' task did not complete in time. Calling stack: ' + - errorForStack.stack; + const errorForStack = new Error('for stack'); + return () => + new Promise((resolve, reject) => { + let complete = false; + let timer = setTimeout(() => { + if (!complete) { + 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() { + window.log.error(message); + return reject(new Error(message)); + } + + 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) { @@ -65,6 +68,5 @@ return promise.then(success, failure); }); - }; }; })(); diff --git a/libtextsecure/test/_test.js b/libtextsecure/test/_test.js index e0903b56d81b..95125e41937a 100644 --- a/libtextsecure/test/_test.js +++ b/libtextsecure/test/_test.js @@ -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; } diff --git a/libtextsecure/test/account_manager_test.js b/libtextsecure/test/account_manager_test.js index ebb03fc23b83..6269fbc0a4d5 100644 --- a/libtextsecure/test/account_manager_test.js +++ b/libtextsecure/test/account_manager_test.js @@ -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); }); }); diff --git a/libtextsecure/test/contacts_parser_test.js b/libtextsecure/test/contacts_parser_test.js index 158a93babb68..9c1beef0e496 100644 --- a/libtextsecure/test/contacts_parser_test.js +++ b/libtextsecure/test/contacts_parser_test.js @@ -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(); diff --git a/libtextsecure/test/crypto_test.js b/libtextsecure/test/crypto_test.js index 1081a0c2c9c7..1dace255254b 100644 --- a/libtextsecure/test/crypto_test.js +++ b/libtextsecure/test/crypto_test.js @@ -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,37 +40,37 @@ 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) { - assert(encrypted.byteLength === buffer.byteLength + 16 + 12); - return textsecure.crypto - .decryptProfile(encrypted, key) - .then(function(decrypted) { - assertEqualArrayBuffers(buffer, decrypted); - }); - }); + return textsecure.crypto.encryptProfile(buffer, key).then(encrypted => { + assert(encrypted.byteLength === buffer.byteLength + 16 + 12); + return textsecure.crypto + .decryptProfile(encrypted, key) + .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) { - assert(encrypted.byteLength === buffer.byteLength + 16 + 12); - return textsecure.crypto - .decryptProfile(encrypted, bad_key) - .catch(function(error) { - assert.strictEqual(error.name, 'ProfileDecryptError'); - }); - }); + return textsecure.crypto.encryptProfile(buffer, key).then(encrypted => { + assert(encrypted.byteLength === buffer.byteLength + 16 + 12); + return textsecure.crypto + .decryptProfile(encrypted, bad_key) + .catch(error => { + assert.strictEqual(error.name, 'ProfileDecryptError'); + }); + }); }); }); }); diff --git a/libtextsecure/test/fake_web_api.js b/libtextsecure/test/fake_web_api.js index fb252493270d..af5305b09e4f 100644 --- a/libtextsecure/test/fake_web_api.js +++ b/libtextsecure/test/fake_web_api.js @@ -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; } }, diff --git a/libtextsecure/test/generate_keys_test.js b/libtextsecure/test/generate_keys_test.js index f5d742ecbe16..93a83be89dcd 100644 --- a/libtextsecure/test/generate_keys_test.js +++ b/libtextsecure/test/generate_keys_test.js @@ -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) { - validateStoredKeyPair(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) { - validateStoredKeyPair(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: * } */ - 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); diff --git a/libtextsecure/test/helpers_test.js b/libtextsecure/test/helpers_test.js index c2995ca6d4f4..b15ee28bb5c6 100644 --- a/libtextsecure/test/helpers_test.js +++ b/libtextsecure/test/helpers_test.js @@ -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); }); diff --git a/libtextsecure/test/in_memory_signal_protocol_store.js b/libtextsecure/test/in_memory_signal_protocol_store.js index a1da3f7aefa5..50e98c4192ff 100644 --- a/libtextsecure/test/in_memory_signal_protocol_store.js +++ b/libtextsecure/test/in_memory_signal_protocol_store.js @@ -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); - if (existing && existing !== identityKey) { - resolve(true); - } else { - resolve(false); - } - }.bind(this) - ); + return new Promise(resolve => { + const existing = this.get(`identityKey${identifier}`); + this.put(`identityKey${identifier}`, identityKey); + if (existing && existing !== identityKey) { + resolve(true); + } else { + resolve(false); + } + }); }, /* Returns a prekeypair object or undefined */ - loadPreKey: function(keyId) { - return new Promise( - function(resolve) { - var res = this.get('25519KeypreKey' + keyId); - resolve(res); - }.bind(this) - ); + loadPreKey(keyId) { + return new Promise(resolve => { + const res = this.get(`25519KeypreKey${keyId}`); + resolve(res); + }); }, - 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); - resolve(res); - }.bind(this) - ); + loadSignedPreKey(keyId) { + return new Promise(resolve => { + const res = this.get(`25519KeysignedKey${keyId}`); + resolve(res); + }); }, - loadSignedPreKeys: function() { - return new Promise( - function(resolve) { - var res = []; - for (var i in this.store) { - if (i.startsWith('25519KeysignedKey')) { - res.push(this.store[i]); - } + 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) - ); + } + resolve(res); + }); }, - 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) { - for (key in this.store) { - if ( - key.match( - RegExp('^session' + identifier.replace('+', '\\+') + '.+') - ) - ) { - delete this.store[key]; - } + removeAllSessions(identifier) { + return new Promise(resolve => { + for (key in this.store) { + if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) { + delete this.store[key]; } - resolve(); - }.bind(this) - ); + } + resolve(); + }); }, - getDeviceIds: function(identifier) { - return new Promise( - function(resolve) { - var deviceIds = []; - for (key in this.store) { - if ( - key.match( - RegExp('^session' + identifier.replace('+', '\\+') + '.+') - ) - ) { - deviceIds.push(parseInt(key.split('.')[1])); - } + getDeviceIds(identifier) { + return new Promise(resolve => { + const deviceIds = []; + for (key in this.store) { + if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) { + deviceIds.push(parseInt(key.split('.')[1])); } - resolve(deviceIds); - }.bind(this) - ); + } + resolve(deviceIds); + }); }, }; diff --git a/libtextsecure/test/message_receiver_test.js b/libtextsecure/test/message_receiver_test.js index 2da31fa5be66..f25b0f6dd781 100644 --- a/libtextsecure/test/message_receiver_test.js +++ b/libtextsecure/test/message_receiver_test.js @@ -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 ); diff --git a/libtextsecure/test/protocol_test.js b/libtextsecure/test/protocol_test.js index fb4f5a3355ce..539731d076cb 100644 --- a/libtextsecure/test/protocol_test.js +++ b/libtextsecure/test/protocol_test.js @@ -1,13 +1,12 @@ -'use strict'; -describe('Protocol', function() { - describe('Unencrypted PushMessageProto "decrypt"', function() { - //exclusive - it('works', function(done) { +describe('Protocol', () => { + describe('Unencrypted PushMessageProto "decrypt"', () => { + // exclusive + 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, diff --git a/libtextsecure/test/protocol_wrapper_test.js b/libtextsecure/test/protocol_wrapper_test.js index 2ff16f96ed11..f43ee589b3ac 100644 --- a/libtextsecure/test/protocol_wrapper_test.js +++ b/libtextsecure/test/protocol_wrapper_test.js @@ -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'); }); }); diff --git a/libtextsecure/test/storage_test.js b/libtextsecure/test/storage_test.js index ea3dd2d5b5c9..8dca194108fa 100644 --- a/libtextsecure/test/storage_test.js +++ b/libtextsecure/test/storage_test.js @@ -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', 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('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) + ); }); 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, []); - }); - }); + })); }); diff --git a/libtextsecure/test/task_with_timeout_test.js b/libtextsecure/test/task_with_timeout_test.js index f98beec0f6f6..81c12a9fd496 100644 --- a/libtextsecure/test/task_with_timeout_test.js +++ b/libtextsecure/test/task_with_timeout_test.js @@ -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); } ); diff --git a/libtextsecure/test/websocket-resources_test.js b/libtextsecure/test/websocket-resources_test.js index 9015fdf9f8eb..7ed4c91d5a20 100644 --- a/libtextsecure/test/websocket-resources_test.js +++ b/libtextsecure/test/websocket-resources_test.js @@ -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); }); diff --git a/libtextsecure/test/websocket_test.js b/libtextsecure/test/websocket_test.js index f880d2e6b7e6..3ba7ca5e3e65 100644 --- a/libtextsecure/test/websocket_test.js +++ b/libtextsecure/test/websocket_test.js @@ -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(); diff --git a/libtextsecure/websocket-resources.js b/libtextsecure/websocket-resources.js index 1e736491e252..aa4f869275da 100644 --- a/libtextsecure/websocket-resources.js +++ b/libtextsecure/websocket-resources.js @@ -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() { - this.closed = true; - }.bind(this) - ); + socket.addEventListener('close', () => { + this.closed = true; + }); - 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() { - if (this.closed) { - return; - } - this.closed = true; + setTimeout(() => { + if (this.closed) { + return; + } + this.closed = true; - window.log.warn('Dispatching our own socket close event'); - var ev = new Event('close'); - ev.code = code; - ev.reason = reason; - this.dispatchEvent(ev); - }.bind(this), - 1000 - ); + window.log.warn('Dispatching our own socket close event'); + const ev = new Event('close'); + ev.code = code; + ev.reason = reason; + this.dispatchEvent(ev); + }, 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,36 +210,30 @@ 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() { - if (this.disconnect) { - // automatically disconnect if server doesn't ack - this.disconnectTimer = setTimeout( - function() { - clearTimeout(this.keepAliveTimer); - this.wsr.close(3001, 'No response to keepalive request'); - }.bind(this), - 1000 - ); - } else { - this.reset(); - } - window.log.info('Sending a keepalive message'); - this.wsr.sendRequest({ - verb: 'GET', - path: this.path, - success: this.reset.bind(this), - }); - }.bind(this), - 55000 - ); + this.keepAliveTimer = setTimeout(() => { + if (this.disconnect) { + // automatically disconnect if server doesn't ack + this.disconnectTimer = setTimeout(() => { + clearTimeout(this.keepAliveTimer); + this.wsr.close(3001, 'No response to keepalive request'); + }, 1000); + } else { + this.reset(); + } + window.log.info('Sending a keepalive message'); + this.wsr.sendRequest({ + verb: 'GET', + path: this.path, + success: this.reset.bind(this), + }); + }, 55000); }, }; })();