From 6bc19ef5581f649a3acd5f8bce2e2acd7023bab0 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Sat, 17 May 2014 01:53:58 -0400 Subject: [PATCH] More namespacing --- js/api.js | 49 ++--- js/background.js | 4 +- js/crypto.js | 24 +-- js/fake_api.js | 1 + js/helpers.js | 458 +++++++++++++++++++++++++---------------------- js/options.js | 16 +- js/popup.js | 6 +- js/test.js | 2 +- 8 files changed, 296 insertions(+), 264 deletions(-) diff --git a/js/api.js b/js/api.js index 2d66e93d..5773a76c 100644 --- a/js/api.js +++ b/js/api.js @@ -14,24 +14,27 @@ * along with this program. If not, see . */ -/************************************************ - *** Utilities to communicate with the server *** - ************************************************/ -// WARNING: THIS SERVER LOGS KEY MATERIAL FOR TESTING -var URL_BASE = "http://sushiforeveryone.bluematt.me"; +window.textsecure = window.textsecure || {}; -// This is the real server -//var URL_BASE = "https://textsecure-service.whispersystems.org"; +window.textsecure.api = function() { + var self = {}; -var URL_CALLS = {}; -URL_CALLS['accounts'] = "/v1/accounts"; -URL_CALLS['devices'] = "/v1/devices"; -URL_CALLS['keys'] = "/v1/keys"; -URL_CALLS['push'] = "/v1/websocket"; -URL_CALLS['messages'] = "/v1/messages"; -URL_CALLS['attachment'] = "/v1/attachments"; + /************************************************ + *** Utilities to communicate with the server *** + ************************************************/ + // WARNING: THIS SERVER LOGS KEY MATERIAL FOR TESTING + var URL_BASE = "http://sushiforeveryone.bluematt.me"; -var API = new function() { + // This is the real server + //var URL_BASE = "https://textsecure-service.whispersystems.org"; + + var URL_CALLS = {}; + URL_CALLS['accounts'] = "/v1/accounts"; + URL_CALLS['devices'] = "/v1/devices"; + URL_CALLS['keys'] = "/v1/keys"; + URL_CALLS['push'] = "/v1/websocket"; + URL_CALLS['messages'] = "/v1/messages"; + URL_CALLS['attachment'] = "/v1/attachments"; /** * REQUIRED PARAMS: @@ -90,7 +93,7 @@ var API = new function() { }); }; - this.requestVerificationCode = function(number, success_callback, error_callback) { + self.requestVerificationCode = function(number, success_callback, error_callback) { doAjax({ call : 'accounts', httpType : 'GET', @@ -104,7 +107,7 @@ var API = new function() { }); }; - this.confirmCode = function(code, number, password, + self.confirmCode = function(code, number, password, signaling_key, registrationId, single_device, success_callback, error_callback) { var call = single_device ? 'accounts' : 'devices'; @@ -129,7 +132,7 @@ var API = new function() { }); }; - this.registerKeys = function(keys, success_callback, error_callback) { + self.registerKeys = function(keys, success_callback, error_callback) { //TODO: Do this conversion somewhere else? var identityKey = btoa(getString(keys.keys[0].identityKey)); for (var i = 0; i < keys.keys.length; i++) @@ -149,7 +152,7 @@ var API = new function() { }); }; - this.getKeysForNumber = function(number) { + self.getKeysForNumber = function(number) { return doAjax({ call : 'keys', httpType : 'GET', @@ -168,7 +171,7 @@ var API = new function() { }); }; - this.sendMessages = function(destination, messageArray) { + self.sendMessages = function(destination, messageArray) { //TODO: Do this conversion somewhere else? for (var i = 0; i < messageArray.length; i++) messageArray[i].body = btoa(messageArray[i].body); @@ -185,7 +188,7 @@ var API = new function() { }); }; - this.getAttachment = function(id) { + self.getAttachment = function(id) { return doAjax({ call : 'attachment', httpType : 'GET', @@ -218,4 +221,6 @@ var API = new function() { }); }); }; -}(); // API + + return self; +}(); diff --git a/js/background.js b/js/background.js index 8f659c9c..06e38db5 100644 --- a/js/background.js +++ b/js/background.js @@ -14,13 +14,13 @@ * along with this program. If not, see . */ -registerOnLoadFunction(function() { +textsecure.registerOnLoadFunction(function() { if (!localStorage.getItem('first_install_ran')) { localStorage.setItem('first_install_ran', 1); chrome.tabs.create({url: "options.html"}); } else { if (isRegistrationDone()) { - subscribeToPush(function(message) { + textsecure.subscribeToPush(function(message) { console.log("Got message from " + message.pushMessage.source + "." + message.pushMessage.sourceDevice + ': "' + getString(message.message.body) + '"'); var newUnreadCount = storage.getUnencrypted("unreadCount", 0) + 1; diff --git a/js/crypto.js b/js/crypto.js index 8fead8a8..5a70a108 100644 --- a/js/crypto.js +++ b/js/crypto.js @@ -14,9 +14,10 @@ * along with this program. If not, see . */ -var textsecure = textsecure || {}; +window.textsecure = window.textsecure || {}; -textsecure.crypto = new function() { +window.textsecure.crypto = new function() { + var self = {}; // functions exposed for replacement and direct calling in test code var testing_only = {}; @@ -38,7 +39,7 @@ textsecure.crypto = new function() { throw err; } } - this.getRandomBytes = getRandomBytes; + self.getRandomBytes = getRandomBytes; function intToArrayBuffer(nInt) { var res = new ArrayBuffer(16); @@ -67,7 +68,7 @@ textsecure.crypto = new function() { return pub; } - if (USE_NACL) { + if (textsecure.nacl.USE_NACL) { return postNaclMessage({command: "bytesToPriv", priv: privKey}).then(function(message) { var priv = message.res; if (!isIdentity) @@ -238,7 +239,7 @@ textsecure.crypto = new function() { console.error("WARNING: Expected pubkey of length 33, please report the ST and client that generated the pubkey"); return new Promise(function(resolve) { - if (USE_NACL) { + if (textsecure.nacl.USE_NACL) { postNaclMessage({command: "ECDHE", priv: privKey, pub: pubKey}).then(function(message) { resolve(message.res); }); @@ -556,7 +557,7 @@ textsecure.crypto = new function() { *** Public crypto API *** *************************/ // Decrypts message into a raw string - this.decryptWebsocketMessage = function(message) { + self.decryptWebsocketMessage = function(message) { var signaling_key = storage.getEncrypted("signaling_key"); //TODO: in crypto_storage var aes_key = toArrayBuffer(signaling_key.substring(0, 32)); var mac_key = toArrayBuffer(signaling_key.substring(32, 32 + 20)); @@ -575,7 +576,7 @@ textsecure.crypto = new function() { }); }; - this.decryptAttachment = function(encryptedBin, keys) { + self.decryptAttachment = function(encryptedBin, keys) { var aes_key = keys.slice(0, 32); var mac_key = keys.slice(32, 64); @@ -589,7 +590,7 @@ textsecure.crypto = new function() { }); }; - this.handleIncomingPushMessageProto = function(proto) { + self.handleIncomingPushMessageProto = function(proto) { switch(proto.type) { case 0: //TYPE_MESSAGE_PLAINTEXT return Promise.resolve({message: decodePushMessageContentProtobuf(getString(proto.message)), pushMessage:proto}); @@ -612,7 +613,7 @@ textsecure.crypto = new function() { } // return Promise(encoded [PreKey]WhisperMessage) - this.encryptMessageFor = function(deviceObject, pushMessageContent) { + self.encryptMessageFor = function(deviceObject, pushMessageContent) { var session = crypto_storage.getOpenSession(deviceObject.encodedNumber); var doEncryptPushMessageContent = function() { @@ -679,7 +680,7 @@ textsecure.crypto = new function() { } var GENERATE_KEYS_KEYS_GENERATED = 100; - this.generateKeys = function() { + self.generateKeys = function() { var identityKey = crypto_storage.getStoredPubKey("identityKey"); var identityKeyCalculated = function(pubKey) { identityKey = pubKey; @@ -721,5 +722,6 @@ textsecure.crypto = new function() { return identityKeyCalculated(identityKey); } - this.testing_only = testing_only; + self.testing_only = testing_only; + return self; }(); diff --git a/js/fake_api.js b/js/fake_api.js index 61691b43..182cf8ce 100644 --- a/js/fake_api.js +++ b/js/fake_api.js @@ -14,6 +14,7 @@ * along with this program. If not, see . */ +//TODO: Redo this (API has changed to textsecure.api and changed) var FakeWhisperAPI = function() { var doAjax = function(param) { if (param.success_callback) { diff --git a/js/helpers.js b/js/helpers.js index 8d6f5a68..584b1948 100644 --- a/js/helpers.js +++ b/js/helpers.js @@ -92,6 +92,8 @@ function base64EncArr (aBytes) { /* END CRAP TO BE DELETED */ +window.textsecure = window.textsecure || {}; + /********************************* *** Type conversion utilities *** *********************************/ @@ -176,7 +178,7 @@ function base64ToArrayBuffer(string) { return base64DecToArr(string); } -// Protobuf decodingA +// Protobuf decoding //TODO: throw on missing fields everywhere var IncomingPushMessageProtobuf = dcodeIO.ProtoBuf.loadProtoFile("protos/IncomingPushMessageSignal.proto").build("textsecure.IncomingPushMessageSignal"); function decodeIncomingPushMessageProtobuf(string) { @@ -224,13 +226,6 @@ function verifyNumber(string) { return getEncodedNumber(string.trim()); } -function getDeviceId(encodedNumber) { - var split = encodedNumber.split("."); - if (split.length > 1) - return split[1]; - return 1; -} - // Other function timestampToHumanReadable(timestamp) { @@ -251,18 +246,21 @@ function objectContainsKeys(object) { /************************************************ *** Utilities to store data in local storage *** ************************************************/ -var storage = new function() { +//TODO: textsecure.storage +window.storage = function() { + var self = {}; + /***************************** *** Base Storage Routines *** *****************************/ - this.putEncrypted = function(key, value) { + self.putEncrypted = function(key, value) { //TODO if (value === undefined) throw new Error("Tried to store undefined"); localStorage.setItem("e" + key, jsonThing(value)); } - this.getEncrypted = function(key, defaultValue) { + self.getEncrypted = function(key, defaultValue) { //TODO var value = localStorage.getItem("e" + key); if (value === null) @@ -270,40 +268,42 @@ var storage = new function() { return JSON.parse(value); } - this.removeEncrypted = function(key) { + self.removeEncrypted = function(key) { localStorage.removeItem("e" + key); } - this.putUnencrypted = function(key, value) { + self.putUnencrypted = function(key, value) { if (value === undefined) throw new Error("Tried to store undefined"); localStorage.setItem("u" + key, jsonThing(value)); } - this.getUnencrypted = function(key, defaultValue) { + self.getUnencrypted = function(key, defaultValue) { var value = localStorage.getItem("u" + key); if (value === null) return defaultValue; return JSON.parse(value); } - this.removeUnencrypted = function(key) { + self.removeUnencrypted = function(key) { localStorage.removeItem("u" + key); } /********************** *** Device Storage *** **********************/ - this.devices = new function() { - this.getDeviceObject = function(encodedNumber) { + self.devices = function() { + var self = {}; + + self.getDeviceObject = function(encodedNumber) { return storage.getEncrypted("deviceObject" + getEncodedNumber(encodedNumber)); } - this.getDeviceIdListFromNumber = function(number) { + self.getDeviceIdListFromNumber = function(number) { return storage.getEncrypted("deviceIdList" + getNumberFromString(number), []); } - this.addDeviceIdForNumber = function(number, deviceId) { + self.addDeviceIdForNumber = function(number, deviceId) { var deviceIdList = this.getDeviceIdListFromNumber(getNumberFromString(number)); for (var i = 0; i < deviceIdList.length; i++) { if (deviceIdList[i] == deviceId) @@ -313,8 +313,15 @@ var storage = new function() { storage.putEncrypted("deviceIdList" + getNumberFromString(number), deviceIdList); } + var getDeviceId = function(encodedNumber) { + var split = encodedNumber.split("."); + if (split.length > 1) + return split[1]; + return 1; + } + // throws "Identity key mismatch" - this.saveDeviceObject = function(deviceObject) { + self.saveDeviceObject = function(deviceObject) { var existing = this.getDeviceObject(deviceObject.encodedNumber); if (existing === undefined) existing = {encodedNumber: getEncodedNumber(deviceObject.encodedNumber)}; @@ -331,15 +338,19 @@ var storage = new function() { this.addDeviceIdForNumber(deviceObject.encodedNumber, getDeviceId(deviceObject.encodedNumber)); } - this.getDeviceObjectListFromNumber = function(number) { + self.getDeviceObjectListFromNumber = function(number) { var deviceObjectList = []; var deviceIdList = this.getDeviceIdListFromNumber(number); for (var i = 0; i < deviceIdList.length; i++) deviceObjectList[deviceObjectList.length] = this.getDeviceObject(getNumberFromString(number) + "." + deviceIdList[i]); return deviceObjectList; } - }; -}; + + return self; + }(); + + return self; +}(); function registrationDone() { storage.putUnencrypted("registration_done", ""); @@ -374,221 +385,232 @@ function storeMessage(messageObject) { /********************** *** NaCL Interface *** **********************/ -var USE_NACL = false; +window.textsecure.nacl = function() { + var self = {}; -var onLoadCallbacks = []; -var naclLoaded = 0; -function registerOnLoadFunction(func) { - if (naclLoaded || !USE_NACL) { - func(); - return; - } - onLoadCallbacks[onLoadCallbacks.length] = func; -} + self.USE_NACL = false; -var naclMessageNextId = 0; -var naclMessageIdCallbackMap = {}; -function moduleDidLoad() { - common.hideModule(); - naclLoaded = 1; - for (var i = 0; i < onLoadCallbacks.length; i++) - onLoadCallbacks[i](); - onLoadCallbacks = []; -} - -function handleMessage(message) { - naclMessageIdCallbackMap[message.data.call_id](message.data); -} - -function postNaclMessage(message) { - if (!USE_NACL) - throw new Error("Attempted to make NaCL call with !USE_NACL?"); - - return new Promise(function(resolve) { - naclMessageIdCallbackMap[naclMessageNextId] = resolve; - message.call_id = naclMessageNextId++; - - common.naclModule.postMessage(message); - }); -} - -// message_callback(decoded_protobuf) (use decodeMessage(proto)) -var subscribeToPushMessageSemaphore = 0; -function subscribeToPush(message_callback) { - subscribeToPushMessageSemaphore++; - if (subscribeToPushMessageSemaphore <= 0) - return; - - var user = storage.getUnencrypted("number_id"); - var password = storage.getEncrypted("password"); - var URL = URL_BASE.replace(/^http:/g, "ws:").replace(/^https:/g, "wss:") + URL_CALLS['push'] + "/?user=%2B" + getString(user).substring(1) + "&password=" + getString(password); - var socket = new WebSocket(URL); - - var pingInterval; - - //TODO: GUI - socket.onerror = function(socketEvent) { - console.log('Server is down :('); - clearInterval(pingInterval); - subscribeToPushMessageSemaphore--; - setTimeout(function() { subscribeToPush(message_callback); }, 60000); - }; - socket.onclose = function(socketEvent) { - console.log('Server closed :('); - clearInterval(pingInterval); - subscribeToPushMessageSemaphore--; - setTimeout(function() { subscribeToPush(message_callback); }, 60000); - }; - socket.onopen = function(socketEvent) { - console.log('Connected to server!'); - pingInterval = setInterval(function() { console.log("Sending server ping message."); socket.send(JSON.stringify({type: 2})); }, 30000); - }; - - socket.onmessage = function(response) { - try { - var message = JSON.parse(response.data); - } catch (e) { - console.log('Error parsing server JSON message: ' + response.responseBody.split("|")[1]); + var onLoadCallbacks = []; + var naclLoaded = 0; + self.registerOnLoadFunction = function(func) { + if (naclLoaded || !self.USE_NACL) { + func(); return; } + onLoadCallbacks[onLoadCallbacks.length] = func; + } - if (message.type == 3) { - console.log("Got pong message"); - } else if (message.type === undefined && message.id !== undefined) { - textsecure.crypto.decryptWebsocketMessage(message.message).then(function(plaintext) { - var proto = decodeIncomingPushMessageProtobuf(getString(plaintext)); - // After this point, a) decoding errors are not the server's fault, and - // b) we should handle them gracefully and tell the user they received an invalid message - console.log("Successfully decoded message with id: " + message.id); - socket.send(JSON.stringify({type: 1, id: message.id})); - return textsecure.crypto.handleIncomingPushMessageProto(proto).then(function(decrypted) { - var handleAttachment = function(attachment) { - return API.getAttachment(attachment.id).then(function(encryptedBin) { - return textsecure.crypto.decryptAttachment(encryptedBin, toArrayBuffer(attachment.key)).then(function(decryptedBin) { - attachment.decrypted = decryptedBin; + var naclMessageNextId = 0; + var naclMessageIdCallbackMap = {}; + window.moduleDidLoad = function() { + common.hideModule(); + naclLoaded = 1; + for (var i = 0; i < onLoadCallbacks.length; i++) + onLoadCallbacks[i](); + onLoadCallbacks = []; + } + + window.handleMessage = function(message) { + naclMessageIdCallbackMap[message.data.call_id](message.data); + } + + self.postNaclMessage = function(message) { + if (!self.USE_NACL) + throw new Error("Attempted to make NaCL call with !USE_NACL?"); + + return new Promise(function(resolve) { + naclMessageIdCallbackMap[naclMessageNextId] = resolve; + message.call_id = naclMessageNextId++; + + common.naclModule.postMessage(message); + }); + } + + return self; +}(); + +//TODO: Some kind of textsecure.init(use_nacl) +window.textsecure.registerOnLoadFunction = window.textsecure.nacl.registerOnLoadFunction; + +// message_callback({message: decryptedMessage, pushMessage: server-providedPushMessage}) +window.textsecure.subscribeToPush = function() { + var subscribeToPushMessageSemaphore = 0; + return function(message_callback) { + subscribeToPushMessageSemaphore++; + if (subscribeToPushMessageSemaphore <= 0) + return; + + var user = storage.getUnencrypted("number_id"); + var password = storage.getEncrypted("password"); + var URL = URL_BASE.replace(/^http:/g, "ws:").replace(/^https:/g, "wss:") + URL_CALLS['push'] + "/?user=%2B" + getString(user).substring(1) + "&password=" + getString(password); + var socket = new WebSocket(URL); + + var pingInterval; + + //TODO: GUI + socket.onerror = function(socketEvent) { + console.log('Server is down :('); + clearInterval(pingInterval); + subscribeToPushMessageSemaphore--; + setTimeout(function() { subscribeToPush(message_callback); }, 60000); + }; + socket.onclose = function(socketEvent) { + console.log('Server closed :('); + clearInterval(pingInterval); + subscribeToPushMessageSemaphore--; + setTimeout(function() { subscribeToPush(message_callback); }, 60000); + }; + socket.onopen = function(socketEvent) { + console.log('Connected to server!'); + pingInterval = setInterval(function() { console.log("Sending server ping message."); socket.send(JSON.stringify({type: 2})); }, 30000); + }; + + socket.onmessage = function(response) { + try { + var message = JSON.parse(response.data); + } catch (e) { + console.log('Error parsing server JSON message: ' + response.responseBody.split("|")[1]); + return; + } + + if (message.type == 3) { + console.log("Got pong message"); + } else if (message.type === undefined && message.id !== undefined) { + textsecure.crypto.decryptWebsocketMessage(message.message).then(function(plaintext) { + var proto = decodeIncomingPushMessageProtobuf(getString(plaintext)); + // After this point, a) decoding errors are not the server's fault, and + // b) we should handle them gracefully and tell the user they received an invalid message + console.log("Successfully decoded message with id: " + message.id); + socket.send(JSON.stringify({type: 1, id: message.id})); + return textsecure.crypto.handleIncomingPushMessageProto(proto).then(function(decrypted) { + var handleAttachment = function(attachment) { + return textsecure.api.getAttachment(attachment.id).then(function(encryptedBin) { + return textsecure.crypto.decryptAttachment(encryptedBin, toArrayBuffer(attachment.key)).then(function(decryptedBin) { + attachment.decrypted = decryptedBin; + }); }); + }; + + var promises = []; + for (var i = 0; i < decrypted.message.attachments.length; i++) { + promises[i] = handleAttachment(decrypted.message.attachments[i]); + } + return Promise.all(promises).then(function() { + storeMessage(decrypted); + message_callback(decrypted); }); - }; - - var promises = []; - for (var i = 0; i < decrypted.message.attachments.length; i++) { - promises[i] = handleAttachment(decrypted.message.attachments[i]); - } - return Promise.all(promises).then(function() { - storeMessage(decrypted); - message_callback(decrypted); - }); - }) - }).catch(function(e) { - console.log("Error handling incoming message: "); - console.log(e); - }); - } - }; -} - -// success_callback(identity_key), error_callback(error_msg) -function getKeysForNumber(number) { - return API.getKeysForNumber(number).then(function(response) { - for (var i = 0; i < response.length; i++) { - storage.devices.saveDeviceObject({ - encodedNumber: number + "." + response[i].deviceId, - identityKey: response[i].identityKey, - publicKey: response[i].publicKey, - preKeyId: response[i].keyId, - registrationId: response[i].registrationId - }); - } - return response[0].identityKey; - }); -} - -// success_callback(server success/failure map), error_callback(error_msg) -// message == PushMessageContentProto (NOT STRING) -function sendMessageToDevices(number, deviceObjectList, message, success_callback, error_callback) { - var jsonData = []; - var relay = undefined; - var promises = []; - - var addEncryptionFor = function(i) { - return textsecure.crypto.encryptMessageFor(deviceObjectList[i], message).then(function(encryptedMsg) { - jsonData[i] = { - type: encryptedMsg.type, - destination: deviceObjectList[i].encodedNumber, - destinationRegistrationId: deviceObjectList[i].registrationId, - body: encryptedMsg.body, - timestamp: new Date().getTime() - }; - - if (deviceObjectList[i].relay !== undefined) { - jsonData[i].relay = deviceObjectList[i].relay; - if (relay === undefined) - relay = jsonData[i].relay; - else if (relay != jsonData[i].relay) - throw new Error("Mismatched relays for number " + number); - } else { - if (relay === undefined) - relay = ""; - else if (relay != "") - throw new Error("Mismatched relays for number " + number); + }) + }).catch(function(e) { + console.log("Error handling incoming message: "); + console.log(e); + }); } + }; + } +}(); + +// sendMessage(numbers = [], message = PushMessageContentProto, callback(success/failure map)) +window.textsecure.sendMessage = function() { + function getKeysForNumber(number) { + return textsecure.api.getKeysForNumber(number).then(function(response) { + for (var i = 0; i < response.length; i++) { + storage.devices.saveDeviceObject({ + encodedNumber: number + "." + response[i].deviceId, + identityKey: response[i].identityKey, + publicKey: response[i].publicKey, + preKeyId: response[i].keyId, + registrationId: response[i].registrationId + }); + } + return response[0].identityKey; }); } - for (var i = 0; i < deviceObjectList.length; i++) - promises[i] = addEncryptionFor(i); - return Promise.all(promises).then(function() { - return API.sendMessages(number, jsonData); - }); -} -// callback(success/failure map, see code) -// message == PushMessageContentProto (NOT STRING) -function sendMessageToNumbers(numbers, message, callback) { - var numbersCompleted = 0; - var errors = []; - var successfulNumbers = []; + // success_callback(server success/failure map), error_callback(error_msg) + // message == PushMessageContentProto (NOT STRING) + function sendMessageToDevices(number, deviceObjectList, message, success_callback, error_callback) { + var jsonData = []; + var relay = undefined; + var promises = []; - var numberCompleted = function() { - numbersCompleted++; - if (numbersCompleted >= numbers.length) - callback({success: successfulNumbers, failure: errors}); + var addEncryptionFor = function(i) { + return textsecure.crypto.encryptMessageFor(deviceObjectList[i], message).then(function(encryptedMsg) { + jsonData[i] = { + type: encryptedMsg.type, + destination: deviceObjectList[i].encodedNumber, + destinationRegistrationId: deviceObjectList[i].registrationId, + body: encryptedMsg.body, + timestamp: new Date().getTime() + }; + + if (deviceObjectList[i].relay !== undefined) { + jsonData[i].relay = deviceObjectList[i].relay; + if (relay === undefined) + relay = jsonData[i].relay; + else if (relay != jsonData[i].relay) + throw new Error("Mismatched relays for number " + number); + } else { + if (relay === undefined) + relay = ""; + else if (relay != "") + throw new Error("Mismatched relays for number " + number); + } + }); + } + for (var i = 0; i < deviceObjectList.length; i++) + promises[i] = addEncryptionFor(i); + return Promise.all(promises).then(function() { + return textsecure.api.sendMessages(number, jsonData); + }); } - var registerError = function(number, message, error) { - errors[errors.length] = { number: number, reason: message, error: error }; - numberCompleted(); - } + return function(numbers, message, callback) { + var numbersCompleted = 0; + var errors = []; + var successfulNumbers = []; - var doSendMessage = function(number, devicesForNumber, message) { - return sendMessageToDevices(number, devicesForNumber, message).then(function(result) { - successfulNumbers[successfulNumbers.length] = number; + var numberCompleted = function() { + numbersCompleted++; + if (numbersCompleted >= numbers.length) + callback({success: successfulNumbers, failure: errors}); + } + + var registerError = function(number, message, error) { + errors[errors.length] = { number: number, reason: message, error: error }; numberCompleted(); - }).catch(function(error) { - if (error instanceof Error && error.name == "HTTPError" && (error.message == 410 || error.message == 409)) { - //TODO: Re-request keys for number here - } - registerError(number, "Failed to create or send message", error); - }); - } + } - for (var i = 0; i < numbers.length; i++) { - var number = numbers[i]; - var devicesForNumber = storage.devices.getDeviceObjectListFromNumber(number); - - if (devicesForNumber.length == 0) { - getKeysForNumber(number).then(function(identity_key) { - devicesForNumber = storage.devices.getDeviceObjectListFromNumber(number); - if (devicesForNumber.length == 0) - registerError(number, "Failed to retreive new device keys for number " + number, null); - else - doSendMessage(number, devicesForNumber, message); + var doSendMessage = function(number, devicesForNumber, message) { + return sendMessageToDevices(number, devicesForNumber, message).then(function(result) { + successfulNumbers[successfulNumbers.length] = number; + numberCompleted(); }).catch(function(error) { - registerError(number, "Failed to retreive new device keys for number " + number, error); + if (error instanceof Error && error.name == "HTTPError" && (error.message == 410 || error.message == 409)) { + //TODO: Re-request keys for number here + } + registerError(number, "Failed to create or send message", error); }); - } else - doSendMessage(number, devicesForNumber, message); + } + + for (var i = 0; i < numbers.length; i++) { + var number = numbers[i]; + var devicesForNumber = storage.devices.getDeviceObjectListFromNumber(number); + + if (devicesForNumber.length == 0) { + getKeysForNumber(number).then(function(identity_key) { + devicesForNumber = storage.devices.getDeviceObjectListFromNumber(number); + if (devicesForNumber.length == 0) + registerError(number, "Failed to retreive new device keys for number " + number, null); + else + doSendMessage(number, devicesForNumber, message); + }).catch(function(error) { + registerError(number, "Failed to retreive new device keys for number " + number, error); + }); + } else + doSendMessage(number, devicesForNumber, message); + } } -} +}(); function requestIdentityPrivKeyFromMasterDevice(number, identityKey) { sendMessageToDevices([storage.devices.getDeviceObject(getNumberFromString(number)) + ".1"], diff --git a/js/options.js b/js/options.js index beb4b4d1..2f0fabee 100644 --- a/js/options.js +++ b/js/options.js @@ -56,7 +56,7 @@ $('#init-go-single-client').click(function() { single_device = true; - API.requestVerificationCode(number, + textsecure.api.requestVerificationCode(number, function(response) { }, function(code) { alert("Failed to send key?" + code); //TODO @@ -76,7 +76,7 @@ $('#init-go').click(function() { $('#verify4done').html(''); $('#verify').show(); - API.confirmCode($('#code').val(), number, password, signaling_key, registrationId, single_device, + textsecure.api.confirmCode($('#code').val(), number, password, signaling_key, registrationId, single_device, function(response) { if (single_device) response = 1; @@ -91,7 +91,7 @@ $('#init-go').click(function() { $('#verify2done').html('done'); textsecure.crypto.generateKeys().then(function(keys) { $('#verify3done').html('done'); - API.registerKeys(keys, + textsecure.api.registerKeys(keys, function(response) { $('#complete-number').html(number); $('#verify').hide(); @@ -105,15 +105,17 @@ $('#init-go').click(function() { } if (!single_device) { - getKeysForNumber(number).then(function(identityKey) { - subscribeToPush(function(message) { + //TODO: Redo all this + /*getKeysForNumber(number).then(function(identityKey) { + textsecure.subscribeToPush(function(message) { //TODO receive shared identity key register_keys_func(); }); requestIdentityPrivKeyFromMasterDevice(number); }).catch(function(error) { alert(error); //TODO - }); + });*/ + register_keys_func(); } else { register_keys_func(); } @@ -136,7 +138,7 @@ $('#init-go').click(function() { } }); -registerOnLoadFunction(function() { +textsecure.registerOnLoadFunction(function() { if (!isRegistrationDone()) { $('#init-setup').show(); } else { diff --git a/js/popup.js b/js/popup.js index 341ee4e6..be2e90c6 100644 --- a/js/popup.js +++ b/js/popup.js @@ -23,7 +23,7 @@ $('#send_link').click(function() { $('#send').show(); }); -registerOnLoadFunction(function() { +textsecure.registerOnLoadFunction(function() { if (storage.getUnencrypted("number_id") === undefined) { chrome.tabs.create({url: "options.html"}); } else { @@ -73,7 +73,7 @@ registerOnLoadFunction(function() { var messageProto = new PushMessageContentProtobuf(); messageProto.body = input.val(); - sendMessageToNumbers(sendDestinations, messageProto, function(result) { + textsecure.sendMessage(sendDestinations, messageProto, function(result) { console.log(result); button.removeAttr("disabled"); button.text("Send"); @@ -109,7 +109,7 @@ registerOnLoadFunction(function() { } var messageProto = new PushMessageContentProtobuf(); messageProto.body = $("#popup_send_text").val(); - sendMessageToNumbers(numbers, messageProto, + textsecure.sendMessage(numbers, messageProto, //TODO: Handle result function(thing) {console.log(thing);}); }); diff --git a/js/test.js b/js/test.js index 2d57501c..d06ffa30 100644 --- a/js/test.js +++ b/js/test.js @@ -95,7 +95,7 @@ function hexToArrayBuffer(str) { return ret; } -registerOnLoadFunction(function() { +textsecure.registerOnLoadFunction(function() { localStorage.clear(); // Random tests to check my JS knowledge