diff --git a/js/background.js b/js/background.js index de8fc5e5f..cadca6300 100644 --- a/js/background.js +++ b/js/background.js @@ -102,6 +102,7 @@ messageReceiver.addEventListener('contactsync', onContactSyncComplete); + window.textsecure.messaging = new textsecure.MessageSender(SERVER_URL, USERNAME, PASSWORD); if (firstRun === true && textsecure.storage.user.getDeviceId() != '1') { textsecure.messaging.sendRequestContactSyncMessage().then(function() { textsecure.messaging.sendRequestGroupSyncMessage(); diff --git a/js/libtextsecure.js b/js/libtextsecure.js index 67848efdd..85b88caaf 100644 --- a/js/libtextsecure.js +++ b/js/libtextsecure.js @@ -39586,441 +39586,445 @@ var TextSecureServer = (function() { * vim: ts=4:sw=4:expandtab */ // sendMessage(numbers = [], message = PushMessageContentProto, callback(success/failure map)) -window.textsecure.messaging = function() { - 'use strict'; - - var self = {}; - - // message == DataMessage or ContentMessage proto - function sendMessageToDevices(timestamp, number, deviceObjectList, message) { - var relay = deviceObjectList[0].relay; - for (var i=1; i < deviceObjectList.length; ++i) { - if (deviceObjectList[i].relay !== relay) { - throw new Error("Mismatched relays for number " + number); +window.textsecure.MessageSender = function(url, username, password) { + 'use strict'; + var server = new TextSecureServer(url, username, password); + return (function(TextSecureServer) { + 'use strict'; + + var self = {}; + + // message == DataMessage or ContentMessage proto + function sendMessageToDevices(timestamp, number, deviceObjectList, message) { + var relay = deviceObjectList[0].relay; + for (var i=1; i < deviceObjectList.length; ++i) { + if (deviceObjectList[i].relay !== relay) { + throw new Error("Mismatched relays for number " + number); + } } - } - return Promise.all(deviceObjectList.map(function(device) { - return textsecure.protocol_wrapper.encryptMessageFor(device, message).then(function(encryptedMsg) { - return textsecure.protocol_wrapper.getRegistrationId(device.encodedNumber).then(function(registrationId) { - return textsecure.storage.devices.removeTempKeysFromDevice(device.encodedNumber).then(function() { - var json = { - type: encryptedMsg.type, - destinationDeviceId: textsecure.utils.unencodeNumber(device.encodedNumber)[1], - destinationRegistrationId: registrationId, - content: encryptedMsg.body, - timestamp: timestamp - }; + return Promise.all(deviceObjectList.map(function(device) { + return textsecure.protocol_wrapper.encryptMessageFor(device, message).then(function(encryptedMsg) { + return textsecure.protocol_wrapper.getRegistrationId(device.encodedNumber).then(function(registrationId) { + return textsecure.storage.devices.removeTempKeysFromDevice(device.encodedNumber).then(function() { + var json = { + type: encryptedMsg.type, + destinationDeviceId: textsecure.utils.unencodeNumber(device.encodedNumber)[1], + destinationRegistrationId: registrationId, + content: encryptedMsg.body, + timestamp: timestamp + }; - if (device.relay !== undefined) { - json.relay = device.relay; - } + if (device.relay !== undefined) { + json.relay = device.relay; + } - return json; + return json; + }); }); }); + })).then(function(jsonData) { + var legacy = (message instanceof textsecure.protobuf.DataMessage); + return TextSecureServer.sendMessages(number, jsonData, legacy); }); - })).then(function(jsonData) { - var legacy = (message instanceof textsecure.protobuf.DataMessage); - return TextSecureServer.sendMessages(number, jsonData, legacy); - }); - } + } - function makeAttachmentPointer(attachment) { - var proto = new textsecure.protobuf.AttachmentPointer(); - proto.key = textsecure.crypto.getRandomBytes(64); + function makeAttachmentPointer(attachment) { + var proto = new textsecure.protobuf.AttachmentPointer(); + proto.key = textsecure.crypto.getRandomBytes(64); - var iv = textsecure.crypto.getRandomBytes(16); - return textsecure.crypto.encryptAttachment(attachment.data, proto.key, iv).then(function(encryptedBin) { - return TextSecureServer.putAttachment(encryptedBin).then(function(id) { - proto.id = id; - proto.contentType = attachment.contentType; - return proto; + var iv = textsecure.crypto.getRandomBytes(16); + return textsecure.crypto.encryptAttachment(attachment.data, proto.key, iv).then(function(encryptedBin) { + return TextSecureServer.putAttachment(encryptedBin).then(function(id) { + proto.id = id; + proto.contentType = attachment.contentType; + return proto; + }); }); - }); - } + } - var tryMessageAgain = function(number, encodedMessage, timestamp) { - var proto = textsecure.protobuf.DataMessage.decode(encodedMessage); - return new Promise(function(resolve, reject) { - sendMessageProto(timestamp, [number], proto, function(res) { - if (res.failure.length > 0) - reject(res.failure); - else - resolve(); + var tryMessageAgain = function(number, encodedMessage, timestamp) { + var proto = textsecure.protobuf.DataMessage.decode(encodedMessage); + return new Promise(function(resolve, reject) { + sendMessageProto(timestamp, [number], proto, function(res) { + if (res.failure.length > 0) + reject(res.failure); + else + resolve(); + }); }); - }); - }; - textsecure.replay.registerFunction(tryMessageAgain, textsecure.replay.Type.SEND_MESSAGE); - - var sendMessageProto = function(timestamp, numbers, message, callback) { - var numbersCompleted = 0; - var errors = []; - var successfulNumbers = []; - - var numberCompleted = function() { - numbersCompleted++; - if (numbersCompleted >= numbers.length) - callback({success: successfulNumbers, failure: errors}); - }; - - var registerError = function(number, reason, error) { - if (!error) { - error = new Error(reason); - } - error.number = number; - error.reason = reason; - errors[errors.length] = error; - numberCompleted(); }; + textsecure.replay.registerFunction(tryMessageAgain, textsecure.replay.Type.SEND_MESSAGE); - var reloadDevicesAndSend = function(number, recurse) { - return function() { - return textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devicesForNumber) { - if (devicesForNumber.length == 0) - return registerError(number, "Got empty device list when loading device keys", null); - doSendMessage(number, devicesForNumber, recurse); - }); - } - }; + var sendMessageProto = function(timestamp, numbers, message, callback) { + var numbersCompleted = 0; + var errors = []; + var successfulNumbers = []; - function getKeysForNumber(number, updateDevices) { - var handleResult = function(response) { - return Promise.all(response.devices.map(function(device) { - if (updateDevices === undefined || updateDevices.indexOf(device.deviceId) > -1) - return textsecure.storage.devices.saveKeysToDeviceObject({ - encodedNumber: number + "." + device.deviceId, - identityKey: response.identityKey, - preKey: device.preKey.publicKey, - preKeyId: device.preKey.keyId, - signedKey: device.signedPreKey.publicKey, - signedKeyId: device.signedPreKey.keyId, - signedKeySignature: device.signedPreKey.signature, - registrationId: device.registrationId - }).catch(function(error) { - if (error.message === "Identity key changed") { - error = new textsecure.OutgoingIdentityKeyError(number, message.toArrayBuffer(), timestamp, error.identityKey); - registerError(number, "Identity key changed", error); - } - throw error; - }); - })); + var numberCompleted = function() { + numbersCompleted++; + if (numbersCompleted >= numbers.length) + callback({success: successfulNumbers, failure: errors}); }; - if (updateDevices === undefined) { - return TextSecureServer.getKeysForNumber(number).then(handleResult); - } else { - var promises = []; - for (var i in updateDevices) - promises[promises.length] = TextSecureServer.getKeysForNumber(number, updateDevices[i]).then(handleResult); - - return Promise.all(promises); - } - } - - var doSendMessage = function(number, devicesForNumber, recurse) { - return sendMessageToDevices(timestamp, number, devicesForNumber, message).then(function(result) { - successfulNumbers[successfulNumbers.length] = number; + var registerError = function(number, reason, error) { + if (!error) { + error = new Error(reason); + } + error.number = number; + error.reason = reason; + errors[errors.length] = error; numberCompleted(); - }).catch(function(error) { - if (error instanceof Error && error.name == "HTTPError" && (error.code == 410 || error.code == 409)) { - if (!recurse) - return registerError(number, "Hit retry limit attempting to reload device list", error); - - var p; - if (error.code == 409) { - p = textsecure.storage.devices.removeDeviceIdsForNumber(number, error.response.extraDevices); - } else { - p = Promise.all(error.response.staleDevices.map(function(deviceId) { - return textsecure.protocol_wrapper.closeOpenSessionForDevice(number + '.' + deviceId); - })); - } + }; - p.then(function() { - var resetDevices = ((error.code == 410) ? error.response.staleDevices : error.response.missingDevices); - getKeysForNumber(number, resetDevices) - .then(reloadDevicesAndSend(number, (error.code == 409))) - .catch(function(error) { - registerError(number, "Failed to reload device keys", error); - }); + var reloadDevicesAndSend = function(number, recurse) { + return function() { + return textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devicesForNumber) { + if (devicesForNumber.length == 0) + return registerError(number, "Got empty device list when loading device keys", null); + doSendMessage(number, devicesForNumber, recurse); }); + } + }; + + function getKeysForNumber(number, updateDevices) { + var handleResult = function(response) { + return Promise.all(response.devices.map(function(device) { + if (updateDevices === undefined || updateDevices.indexOf(device.deviceId) > -1) + return textsecure.storage.devices.saveKeysToDeviceObject({ + encodedNumber: number + "." + device.deviceId, + identityKey: response.identityKey, + preKey: device.preKey.publicKey, + preKeyId: device.preKey.keyId, + signedKey: device.signedPreKey.publicKey, + signedKeyId: device.signedPreKey.keyId, + signedKeySignature: device.signedPreKey.signature, + registrationId: device.registrationId + }).catch(function(error) { + if (error.message === "Identity key changed") { + error = new textsecure.OutgoingIdentityKeyError(number, message.toArrayBuffer(), timestamp, error.identityKey); + registerError(number, "Identity key changed", error); + } + throw error; + }); + })); + }; + + if (updateDevices === undefined) { + return TextSecureServer.getKeysForNumber(number).then(handleResult); } else { - registerError(number, "Failed to create or send message", error); + var promises = []; + for (var i in updateDevices) + promises[promises.length] = TextSecureServer.getKeysForNumber(number, updateDevices[i]).then(handleResult); + + return Promise.all(promises); } - }); - }; + } - numbers.forEach(function(number) { - textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devicesForNumber) { - return Promise.all(devicesForNumber.map(function(device) { - return textsecure.protocol_wrapper.hasOpenSession(device.encodedNumber).then(function(result) { - if (!result) - return getKeysForNumber(number, [parseInt(textsecure.utils.unencodeNumber(device.encodedNumber)[1])]); - }); - })).then(function() { - return textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devicesForNumber) { - if (devicesForNumber.length == 0) { - getKeysForNumber(number, [1]) - .then(reloadDevicesAndSend(number, true)) + var doSendMessage = function(number, devicesForNumber, recurse) { + return sendMessageToDevices(timestamp, number, devicesForNumber, message).then(function(result) { + successfulNumbers[successfulNumbers.length] = number; + numberCompleted(); + }).catch(function(error) { + if (error instanceof Error && error.name == "HTTPError" && (error.code == 410 || error.code == 409)) { + if (!recurse) + return registerError(number, "Hit retry limit attempting to reload device list", error); + + var p; + if (error.code == 409) { + p = textsecure.storage.devices.removeDeviceIdsForNumber(number, error.response.extraDevices); + } else { + p = Promise.all(error.response.staleDevices.map(function(deviceId) { + return textsecure.protocol_wrapper.closeOpenSessionForDevice(number + '.' + deviceId); + })); + } + + p.then(function() { + var resetDevices = ((error.code == 410) ? error.response.staleDevices : error.response.missingDevices); + getKeysForNumber(number, resetDevices) + .then(reloadDevicesAndSend(number, (error.code == 409))) .catch(function(error) { - registerError(number, "Failed to retreive new device keys for number " + number, error); + registerError(number, "Failed to reload device keys", error); }); - } else - doSendMessage(number, devicesForNumber, true); + }); + } else { + registerError(number, "Failed to create or send message", error); + } + }); + }; + + numbers.forEach(function(number) { + textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devicesForNumber) { + return Promise.all(devicesForNumber.map(function(device) { + return textsecure.protocol_wrapper.hasOpenSession(device.encodedNumber).then(function(result) { + if (!result) + return getKeysForNumber(number, [parseInt(textsecure.utils.unencodeNumber(device.encodedNumber)[1])]); + }); + })).then(function() { + return textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devicesForNumber) { + if (devicesForNumber.length == 0) { + getKeysForNumber(number, [1]) + .then(reloadDevicesAndSend(number, true)) + .catch(function(error) { + registerError(number, "Failed to retreive new device keys for number " + number, error); + }); + } else + doSendMessage(number, devicesForNumber, true); + }); }); }); }); - }); - } + } - var sendIndividualProto = function(number, proto, timestamp) { - return new Promise(function(resolve, reject) { - sendMessageProto(timestamp, [number], proto, function(res) { - if (res.failure.length > 0) - reject(res.failure); - else - resolve(); + var sendIndividualProto = function(number, proto, timestamp) { + return new Promise(function(resolve, reject) { + sendMessageProto(timestamp, [number], proto, function(res) { + if (res.failure.length > 0) + reject(res.failure); + else + resolve(); + }); }); - }); - } - - var sendSyncMessage = function(message, timestamp, destination) { - var myNumber = textsecure.storage.user.getNumber(); - var myDevice = textsecure.storage.user.getDeviceId(); - if (myDevice != 1) { - var sentMessage = new textsecure.protobuf.SyncMessage.Sent(); - sentMessage.timestamp = timestamp; - sentMessage.message = message; - if (destination) { - sentMessage.destination = destination; - } - var syncMessage = new textsecure.protobuf.SyncMessage(); - syncMessage.sent = sentMessage; - var contentMessage = new textsecure.protobuf.Content(); - contentMessage.syncMessage = syncMessage; - - return sendIndividualProto(myNumber, contentMessage, Date.now()); } - } - self.sendRequestGroupSyncMessage = function() { - var myNumber = textsecure.storage.user.getNumber(); - var myDevice = textsecure.storage.user.getDeviceId(); - if (myDevice != 1) { - var request = new textsecure.protobuf.SyncMessage.Request(); - request.type = textsecure.protobuf.SyncMessage.Request.Type.GROUPS; - var syncMessage = new textsecure.protobuf.SyncMessage(); - syncMessage.request = request; - var contentMessage = new textsecure.protobuf.Content(); - contentMessage.syncMessage = syncMessage; + var sendSyncMessage = function(message, timestamp, destination) { + var myNumber = textsecure.storage.user.getNumber(); + var myDevice = textsecure.storage.user.getDeviceId(); + if (myDevice != 1) { + var sentMessage = new textsecure.protobuf.SyncMessage.Sent(); + sentMessage.timestamp = timestamp; + sentMessage.message = message; + if (destination) { + sentMessage.destination = destination; + } + var syncMessage = new textsecure.protobuf.SyncMessage(); + syncMessage.sent = sentMessage; + var contentMessage = new textsecure.protobuf.Content(); + contentMessage.syncMessage = syncMessage; - return sendIndividualProto(myNumber, contentMessage, Date.now()); - } - }; - self.sendRequestContactSyncMessage = function() { - var myNumber = textsecure.storage.user.getNumber(); - var myDevice = textsecure.storage.user.getDeviceId(); - if (myDevice != 1) { - var request = new textsecure.protobuf.SyncMessage.Request(); - request.type = textsecure.protobuf.SyncMessage.Request.Type.CONTACTS; - var syncMessage = new textsecure.protobuf.SyncMessage(); - syncMessage.request = request; - var contentMessage = new textsecure.protobuf.Content(); - contentMessage.syncMessage = syncMessage; - - return sendIndividualProto(myNumber, contentMessage, Date.now()); + return sendIndividualProto(myNumber, contentMessage, Date.now()); + } } - }; - var sendGroupProto = function(numbers, proto, timestamp) { - timestamp = timestamp || Date.now(); - var me = textsecure.storage.user.getNumber(); - numbers = numbers.filter(function(number) { return number != me; }); + self.sendRequestGroupSyncMessage = function() { + var myNumber = textsecure.storage.user.getNumber(); + var myDevice = textsecure.storage.user.getDeviceId(); + if (myDevice != 1) { + var request = new textsecure.protobuf.SyncMessage.Request(); + request.type = textsecure.protobuf.SyncMessage.Request.Type.GROUPS; + var syncMessage = new textsecure.protobuf.SyncMessage(); + syncMessage.request = request; + var contentMessage = new textsecure.protobuf.Content(); + contentMessage.syncMessage = syncMessage; - return new Promise(function(resolve, reject) { - sendMessageProto(timestamp, numbers, proto, function(res) { - if (res.failure.length > 0) - reject(res.failure); - else - resolve(); - }); - }).then(function() { - return sendSyncMessage(proto, timestamp); - }); - } + return sendIndividualProto(myNumber, contentMessage, Date.now()); + } + }; + self.sendRequestContactSyncMessage = function() { + var myNumber = textsecure.storage.user.getNumber(); + var myDevice = textsecure.storage.user.getDeviceId(); + if (myDevice != 1) { + var request = new textsecure.protobuf.SyncMessage.Request(); + request.type = textsecure.protobuf.SyncMessage.Request.Type.CONTACTS; + var syncMessage = new textsecure.protobuf.SyncMessage(); + syncMessage.request = request; + var contentMessage = new textsecure.protobuf.Content(); + contentMessage.syncMessage = syncMessage; + + return sendIndividualProto(myNumber, contentMessage, Date.now()); + } + }; - self.sendMessageToNumber = function(number, messageText, attachments, timestamp) { - var proto = new textsecure.protobuf.DataMessage(); - proto.body = messageText; - - var promises = []; - for (var i in attachments) - promises.push(makeAttachmentPointer(attachments[i])); - return Promise.all(promises).then(function(attachmentsArray) { - proto.attachments = attachmentsArray; - return sendIndividualProto(number, proto, timestamp).then(function() { - return sendSyncMessage(proto, timestamp, number); - }); - }); - } + var sendGroupProto = function(numbers, proto, timestamp) { + timestamp = timestamp || Date.now(); + var me = textsecure.storage.user.getNumber(); + numbers = numbers.filter(function(number) { return number != me; }); - self.closeSession = function(number) { - var proto = new textsecure.protobuf.DataMessage(); - proto.body = "TERMINATE"; - proto.flags = textsecure.protobuf.DataMessage.Flags.END_SESSION; - return sendIndividualProto(number, proto, Date.now()).then(function(res) { - return textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devices) { - return Promise.all(devices.map(function(device) { - return textsecure.protocol_wrapper.closeOpenSessionForDevice(device.encodedNumber); - })).then(function() { - return res; + return new Promise(function(resolve, reject) { + sendMessageProto(timestamp, numbers, proto, function(res) { + if (res.failure.length > 0) + reject(res.failure); + else + resolve(); }); + }).then(function() { + return sendSyncMessage(proto, timestamp); }); - }); - } - - self.sendMessageToGroup = function(groupId, messageText, attachments, timestamp) { - var proto = new textsecure.protobuf.DataMessage(); - proto.body = messageText; - proto.group = new textsecure.protobuf.GroupContext(); - proto.group.id = toArrayBuffer(groupId); - proto.group.type = textsecure.protobuf.GroupContext.Type.DELIVER; + } - return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) { - if (numbers === undefined) - return Promise.reject(new Error("Unknown Group")); + self.sendMessageToNumber = function(number, messageText, attachments, timestamp) { + var proto = new textsecure.protobuf.DataMessage(); + proto.body = messageText; var promises = []; for (var i in attachments) promises.push(makeAttachmentPointer(attachments[i])); return Promise.all(promises).then(function(attachmentsArray) { proto.attachments = attachmentsArray; - return sendGroupProto(numbers, proto, timestamp); + return sendIndividualProto(number, proto, timestamp).then(function() { + return sendSyncMessage(proto, timestamp, number); + }); }); - }); - } + } - self.createGroup = function(numbers, name, avatar) { - var proto = new textsecure.protobuf.DataMessage(); - proto.group = new textsecure.protobuf.GroupContext(); + self.closeSession = function(number) { + var proto = new textsecure.protobuf.DataMessage(); + proto.body = "TERMINATE"; + proto.flags = textsecure.protobuf.DataMessage.Flags.END_SESSION; + return sendIndividualProto(number, proto, Date.now()).then(function(res) { + return textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devices) { + return Promise.all(devices.map(function(device) { + return textsecure.protocol_wrapper.closeOpenSessionForDevice(device.encodedNumber); + })).then(function() { + return res; + }); + }); + }); + } - return textsecure.storage.groups.createNewGroup(numbers).then(function(group) { - proto.group.id = toArrayBuffer(group.id); - var numbers = group.numbers; + self.sendMessageToGroup = function(groupId, messageText, attachments, timestamp) { + var proto = new textsecure.protobuf.DataMessage(); + proto.body = messageText; + proto.group = new textsecure.protobuf.GroupContext(); + proto.group.id = toArrayBuffer(groupId); + proto.group.type = textsecure.protobuf.GroupContext.Type.DELIVER; - proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; - proto.group.members = numbers; - proto.group.name = name; + return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) { + if (numbers === undefined) + return Promise.reject(new Error("Unknown Group")); - if (avatar !== undefined) { - return makeAttachmentPointer(avatar).then(function(attachment) { - proto.group.avatar = attachment; + var promises = []; + for (var i in attachments) + promises.push(makeAttachmentPointer(attachments[i])); + return Promise.all(promises).then(function(attachmentsArray) { + proto.attachments = attachmentsArray; + return sendGroupProto(numbers, proto, timestamp); + }); + }); + } + + self.createGroup = function(numbers, name, avatar) { + var proto = new textsecure.protobuf.DataMessage(); + proto.group = new textsecure.protobuf.GroupContext(); + + return textsecure.storage.groups.createNewGroup(numbers).then(function(group) { + proto.group.id = toArrayBuffer(group.id); + var numbers = group.numbers; + + proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; + proto.group.members = numbers; + proto.group.name = name; + + if (avatar !== undefined) { + return makeAttachmentPointer(avatar).then(function(attachment) { + proto.group.avatar = attachment; + return sendGroupProto(numbers, proto).then(function() { + return proto.group.id; + }); + }); + } else { return sendGroupProto(numbers, proto).then(function() { return proto.group.id; }); - }); - } else { - return sendGroupProto(numbers, proto).then(function() { - return proto.group.id; - }); - } - }); - } + } + }); + } - self.updateGroup = function(groupId, name, avatar, numbers) { - var proto = new textsecure.protobuf.DataMessage(); - proto.group = new textsecure.protobuf.GroupContext(); + self.updateGroup = function(groupId, name, avatar, numbers) { + var proto = new textsecure.protobuf.DataMessage(); + proto.group = new textsecure.protobuf.GroupContext(); - proto.group.id = toArrayBuffer(groupId); - proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; - proto.group.name = name; + proto.group.id = toArrayBuffer(groupId); + proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; + proto.group.name = name; - return textsecure.storage.groups.addNumbers(groupId, numbers).then(function(numbers) { - if (numbers === undefined) { - return Promise.reject(new Error("Unknown Group")); - } - proto.group.members = numbers; + return textsecure.storage.groups.addNumbers(groupId, numbers).then(function(numbers) { + if (numbers === undefined) { + return Promise.reject(new Error("Unknown Group")); + } + proto.group.members = numbers; - if (avatar !== undefined && avatar !== null) { - return makeAttachmentPointer(avatar).then(function(attachment) { - proto.group.avatar = attachment; + if (avatar !== undefined && avatar !== null) { + return makeAttachmentPointer(avatar).then(function(attachment) { + proto.group.avatar = attachment; + return sendGroupProto(numbers, proto).then(function() { + return proto.group.id; + }); + }); + } else { return sendGroupProto(numbers, proto).then(function() { return proto.group.id; }); - }); - } else { - return sendGroupProto(numbers, proto).then(function() { - return proto.group.id; - }); - } - }); - } + } + }); + } - self.addNumberToGroup = function(groupId, number) { - var proto = new textsecure.protobuf.DataMessage(); - proto.group = new textsecure.protobuf.GroupContext(); - proto.group.id = toArrayBuffer(groupId); - proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; + self.addNumberToGroup = function(groupId, number) { + var proto = new textsecure.protobuf.DataMessage(); + proto.group = new textsecure.protobuf.GroupContext(); + proto.group.id = toArrayBuffer(groupId); + proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; - return textsecure.storage.groups.addNumbers(groupId, [number]).then(function(numbers) { - if (numbers === undefined) - return Promise.reject(new Error("Unknown Group")); - proto.group.members = numbers; + return textsecure.storage.groups.addNumbers(groupId, [number]).then(function(numbers) { + if (numbers === undefined) + return Promise.reject(new Error("Unknown Group")); + proto.group.members = numbers; - return sendGroupProto(numbers, proto); - }); - } + return sendGroupProto(numbers, proto); + }); + } - self.setGroupName = function(groupId, name) { - var proto = new textsecure.protobuf.DataMessage(); - proto.group = new textsecure.protobuf.GroupContext(); - proto.group.id = toArrayBuffer(groupId); - proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; - proto.group.name = name; + self.setGroupName = function(groupId, name) { + var proto = new textsecure.protobuf.DataMessage(); + proto.group = new textsecure.protobuf.GroupContext(); + proto.group.id = toArrayBuffer(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(function(numbers) { + if (numbers === undefined) + return Promise.reject(new Error("Unknown Group")); + proto.group.members = numbers; - return sendGroupProto(numbers, proto); - }); - } + return sendGroupProto(numbers, proto); + }); + } - self.setGroupAvatar = function(groupId, avatar) { - var proto = new textsecure.protobuf.DataMessage(); - proto.group = new textsecure.protobuf.GroupContext(); - proto.group.id = toArrayBuffer(groupId); - proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; + self.setGroupAvatar = function(groupId, avatar) { + var proto = new textsecure.protobuf.DataMessage(); + proto.group = new textsecure.protobuf.GroupContext(); + proto.group.id = toArrayBuffer(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(function(numbers) { + if (numbers === undefined) + return Promise.reject(new Error("Unknown Group")); + proto.group.members = numbers; - return makeAttachmentPointer(avatar).then(function(attachment) { - proto.group.avatar = attachment; - return sendGroupProto(numbers, proto); + return makeAttachmentPointer(avatar).then(function(attachment) { + proto.group.avatar = attachment; + return sendGroupProto(numbers, proto); + }); }); - }); - } + } - self.leaveGroup = function(groupId) { - var proto = new textsecure.protobuf.DataMessage(); - proto.group = new textsecure.protobuf.GroupContext(); - proto.group.id = toArrayBuffer(groupId); - proto.group.type = textsecure.protobuf.GroupContext.Type.QUIT; + self.leaveGroup = function(groupId) { + var proto = new textsecure.protobuf.DataMessage(); + proto.group = new textsecure.protobuf.GroupContext(); + proto.group.id = toArrayBuffer(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 sendGroupProto(numbers, proto); + 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 sendGroupProto(numbers, proto); + }); }); - }); - } + } - return self; -}(); + return self; + })(server); +}; /* * vim: ts=4:sw=4:expandtab diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index 1befaba2c..7e3c5056c 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -2,438 +2,442 @@ * vim: ts=4:sw=4:expandtab */ // sendMessage(numbers = [], message = PushMessageContentProto, callback(success/failure map)) -window.textsecure.messaging = function() { - 'use strict'; - - var self = {}; - - // message == DataMessage or ContentMessage proto - function sendMessageToDevices(timestamp, number, deviceObjectList, message) { - var relay = deviceObjectList[0].relay; - for (var i=1; i < deviceObjectList.length; ++i) { - if (deviceObjectList[i].relay !== relay) { - throw new Error("Mismatched relays for number " + number); +window.textsecure.MessageSender = function(url, username, password) { + 'use strict'; + var server = new TextSecureServer(url, username, password); + return (function(TextSecureServer) { + 'use strict'; + + var self = {}; + + // message == DataMessage or ContentMessage proto + function sendMessageToDevices(timestamp, number, deviceObjectList, message) { + var relay = deviceObjectList[0].relay; + for (var i=1; i < deviceObjectList.length; ++i) { + if (deviceObjectList[i].relay !== relay) { + throw new Error("Mismatched relays for number " + number); + } } - } - return Promise.all(deviceObjectList.map(function(device) { - return textsecure.protocol_wrapper.encryptMessageFor(device, message).then(function(encryptedMsg) { - return textsecure.protocol_wrapper.getRegistrationId(device.encodedNumber).then(function(registrationId) { - return textsecure.storage.devices.removeTempKeysFromDevice(device.encodedNumber).then(function() { - var json = { - type: encryptedMsg.type, - destinationDeviceId: textsecure.utils.unencodeNumber(device.encodedNumber)[1], - destinationRegistrationId: registrationId, - content: encryptedMsg.body, - timestamp: timestamp - }; - - if (device.relay !== undefined) { - json.relay = device.relay; - } + return Promise.all(deviceObjectList.map(function(device) { + return textsecure.protocol_wrapper.encryptMessageFor(device, message).then(function(encryptedMsg) { + return textsecure.protocol_wrapper.getRegistrationId(device.encodedNumber).then(function(registrationId) { + return textsecure.storage.devices.removeTempKeysFromDevice(device.encodedNumber).then(function() { + var json = { + type: encryptedMsg.type, + destinationDeviceId: textsecure.utils.unencodeNumber(device.encodedNumber)[1], + destinationRegistrationId: registrationId, + content: encryptedMsg.body, + timestamp: timestamp + }; + + if (device.relay !== undefined) { + json.relay = device.relay; + } - return json; + return json; + }); }); }); + })).then(function(jsonData) { + var legacy = (message instanceof textsecure.protobuf.DataMessage); + return TextSecureServer.sendMessages(number, jsonData, legacy); }); - })).then(function(jsonData) { - var legacy = (message instanceof textsecure.protobuf.DataMessage); - return TextSecureServer.sendMessages(number, jsonData, legacy); - }); - } - - function makeAttachmentPointer(attachment) { - var proto = new textsecure.protobuf.AttachmentPointer(); - proto.key = textsecure.crypto.getRandomBytes(64); - - var iv = textsecure.crypto.getRandomBytes(16); - return textsecure.crypto.encryptAttachment(attachment.data, proto.key, iv).then(function(encryptedBin) { - return TextSecureServer.putAttachment(encryptedBin).then(function(id) { - proto.id = id; - proto.contentType = attachment.contentType; - return proto; - }); - }); - } - - var tryMessageAgain = function(number, encodedMessage, timestamp) { - var proto = textsecure.protobuf.DataMessage.decode(encodedMessage); - return new Promise(function(resolve, reject) { - sendMessageProto(timestamp, [number], proto, function(res) { - if (res.failure.length > 0) - reject(res.failure); - else - resolve(); - }); - }); - }; - textsecure.replay.registerFunction(tryMessageAgain, textsecure.replay.Type.SEND_MESSAGE); - - var sendMessageProto = function(timestamp, numbers, message, callback) { - var numbersCompleted = 0; - var errors = []; - var successfulNumbers = []; - - var numberCompleted = function() { - numbersCompleted++; - if (numbersCompleted >= numbers.length) - callback({success: successfulNumbers, failure: errors}); - }; + } - var registerError = function(number, reason, error) { - if (!error) { - error = new Error(reason); - } - error.number = number; - error.reason = reason; - errors[errors.length] = error; - numberCompleted(); - }; + function makeAttachmentPointer(attachment) { + var proto = new textsecure.protobuf.AttachmentPointer(); + proto.key = textsecure.crypto.getRandomBytes(64); - var reloadDevicesAndSend = function(number, recurse) { - return function() { - return textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devicesForNumber) { - if (devicesForNumber.length == 0) - return registerError(number, "Got empty device list when loading device keys", null); - doSendMessage(number, devicesForNumber, recurse); + var iv = textsecure.crypto.getRandomBytes(16); + return textsecure.crypto.encryptAttachment(attachment.data, proto.key, iv).then(function(encryptedBin) { + return TextSecureServer.putAttachment(encryptedBin).then(function(id) { + proto.id = id; + proto.contentType = attachment.contentType; + return proto; }); - } - }; + }); + } - function getKeysForNumber(number, updateDevices) { - var handleResult = function(response) { - return Promise.all(response.devices.map(function(device) { - if (updateDevices === undefined || updateDevices.indexOf(device.deviceId) > -1) - return textsecure.storage.devices.saveKeysToDeviceObject({ - encodedNumber: number + "." + device.deviceId, - identityKey: response.identityKey, - preKey: device.preKey.publicKey, - preKeyId: device.preKey.keyId, - signedKey: device.signedPreKey.publicKey, - signedKeyId: device.signedPreKey.keyId, - signedKeySignature: device.signedPreKey.signature, - registrationId: device.registrationId - }).catch(function(error) { - if (error.message === "Identity key changed") { - error = new textsecure.OutgoingIdentityKeyError(number, message.toArrayBuffer(), timestamp, error.identityKey); - registerError(number, "Identity key changed", error); - } - throw error; - }); - })); - }; + var tryMessageAgain = function(number, encodedMessage, timestamp) { + var proto = textsecure.protobuf.DataMessage.decode(encodedMessage); + return new Promise(function(resolve, reject) { + sendMessageProto(timestamp, [number], proto, function(res) { + if (res.failure.length > 0) + reject(res.failure); + else + resolve(); + }); + }); + }; + textsecure.replay.registerFunction(tryMessageAgain, textsecure.replay.Type.SEND_MESSAGE); - if (updateDevices === undefined) { - return TextSecureServer.getKeysForNumber(number).then(handleResult); - } else { - var promises = []; - for (var i in updateDevices) - promises[promises.length] = TextSecureServer.getKeysForNumber(number, updateDevices[i]).then(handleResult); + var sendMessageProto = function(timestamp, numbers, message, callback) { + var numbersCompleted = 0; + var errors = []; + var successfulNumbers = []; - return Promise.all(promises); - } - } + var numberCompleted = function() { + numbersCompleted++; + if (numbersCompleted >= numbers.length) + callback({success: successfulNumbers, failure: errors}); + }; - var doSendMessage = function(number, devicesForNumber, recurse) { - return sendMessageToDevices(timestamp, number, devicesForNumber, message).then(function(result) { - successfulNumbers[successfulNumbers.length] = number; + var registerError = function(number, reason, error) { + if (!error) { + error = new Error(reason); + } + error.number = number; + error.reason = reason; + errors[errors.length] = error; numberCompleted(); - }).catch(function(error) { - if (error instanceof Error && error.name == "HTTPError" && (error.code == 410 || error.code == 409)) { - if (!recurse) - return registerError(number, "Hit retry limit attempting to reload device list", error); - - var p; - if (error.code == 409) { - p = textsecure.storage.devices.removeDeviceIdsForNumber(number, error.response.extraDevices); - } else { - p = Promise.all(error.response.staleDevices.map(function(deviceId) { - return textsecure.protocol_wrapper.closeOpenSessionForDevice(number + '.' + deviceId); - })); - } + }; - p.then(function() { - var resetDevices = ((error.code == 410) ? error.response.staleDevices : error.response.missingDevices); - getKeysForNumber(number, resetDevices) - .then(reloadDevicesAndSend(number, (error.code == 409))) - .catch(function(error) { - registerError(number, "Failed to reload device keys", error); - }); + var reloadDevicesAndSend = function(number, recurse) { + return function() { + return textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devicesForNumber) { + if (devicesForNumber.length == 0) + return registerError(number, "Got empty device list when loading device keys", null); + doSendMessage(number, devicesForNumber, recurse); }); + } + }; + + function getKeysForNumber(number, updateDevices) { + var handleResult = function(response) { + return Promise.all(response.devices.map(function(device) { + if (updateDevices === undefined || updateDevices.indexOf(device.deviceId) > -1) + return textsecure.storage.devices.saveKeysToDeviceObject({ + encodedNumber: number + "." + device.deviceId, + identityKey: response.identityKey, + preKey: device.preKey.publicKey, + preKeyId: device.preKey.keyId, + signedKey: device.signedPreKey.publicKey, + signedKeyId: device.signedPreKey.keyId, + signedKeySignature: device.signedPreKey.signature, + registrationId: device.registrationId + }).catch(function(error) { + if (error.message === "Identity key changed") { + error = new textsecure.OutgoingIdentityKeyError(number, message.toArrayBuffer(), timestamp, error.identityKey); + registerError(number, "Identity key changed", error); + } + throw error; + }); + })); + }; + + if (updateDevices === undefined) { + return TextSecureServer.getKeysForNumber(number).then(handleResult); } else { - registerError(number, "Failed to create or send message", error); + var promises = []; + for (var i in updateDevices) + promises[promises.length] = TextSecureServer.getKeysForNumber(number, updateDevices[i]).then(handleResult); + + return Promise.all(promises); } - }); - }; + } - numbers.forEach(function(number) { - textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devicesForNumber) { - return Promise.all(devicesForNumber.map(function(device) { - return textsecure.protocol_wrapper.hasOpenSession(device.encodedNumber).then(function(result) { - if (!result) - return getKeysForNumber(number, [parseInt(textsecure.utils.unencodeNumber(device.encodedNumber)[1])]); - }); - })).then(function() { - return textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devicesForNumber) { - if (devicesForNumber.length == 0) { - getKeysForNumber(number, [1]) - .then(reloadDevicesAndSend(number, true)) + var doSendMessage = function(number, devicesForNumber, recurse) { + return sendMessageToDevices(timestamp, number, devicesForNumber, message).then(function(result) { + successfulNumbers[successfulNumbers.length] = number; + numberCompleted(); + }).catch(function(error) { + if (error instanceof Error && error.name == "HTTPError" && (error.code == 410 || error.code == 409)) { + if (!recurse) + return registerError(number, "Hit retry limit attempting to reload device list", error); + + var p; + if (error.code == 409) { + p = textsecure.storage.devices.removeDeviceIdsForNumber(number, error.response.extraDevices); + } else { + p = Promise.all(error.response.staleDevices.map(function(deviceId) { + return textsecure.protocol_wrapper.closeOpenSessionForDevice(number + '.' + deviceId); + })); + } + + p.then(function() { + var resetDevices = ((error.code == 410) ? error.response.staleDevices : error.response.missingDevices); + getKeysForNumber(number, resetDevices) + .then(reloadDevicesAndSend(number, (error.code == 409))) .catch(function(error) { - registerError(number, "Failed to retreive new device keys for number " + number, error); + registerError(number, "Failed to reload device keys", error); }); - } else - doSendMessage(number, devicesForNumber, true); + }); + } else { + registerError(number, "Failed to create or send message", error); + } + }); + }; + + numbers.forEach(function(number) { + textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devicesForNumber) { + return Promise.all(devicesForNumber.map(function(device) { + return textsecure.protocol_wrapper.hasOpenSession(device.encodedNumber).then(function(result) { + if (!result) + return getKeysForNumber(number, [parseInt(textsecure.utils.unencodeNumber(device.encodedNumber)[1])]); + }); + })).then(function() { + return textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devicesForNumber) { + if (devicesForNumber.length == 0) { + getKeysForNumber(number, [1]) + .then(reloadDevicesAndSend(number, true)) + .catch(function(error) { + registerError(number, "Failed to retreive new device keys for number " + number, error); + }); + } else + doSendMessage(number, devicesForNumber, true); + }); }); }); }); - }); - } - - var sendIndividualProto = function(number, proto, timestamp) { - return new Promise(function(resolve, reject) { - sendMessageProto(timestamp, [number], proto, function(res) { - if (res.failure.length > 0) - reject(res.failure); - else - resolve(); - }); - }); - } - - var sendSyncMessage = function(message, timestamp, destination) { - var myNumber = textsecure.storage.user.getNumber(); - var myDevice = textsecure.storage.user.getDeviceId(); - if (myDevice != 1) { - var sentMessage = new textsecure.protobuf.SyncMessage.Sent(); - sentMessage.timestamp = timestamp; - sentMessage.message = message; - if (destination) { - sentMessage.destination = destination; - } - var syncMessage = new textsecure.protobuf.SyncMessage(); - syncMessage.sent = sentMessage; - var contentMessage = new textsecure.protobuf.Content(); - contentMessage.syncMessage = syncMessage; - - return sendIndividualProto(myNumber, contentMessage, Date.now()); } - } - - self.sendRequestGroupSyncMessage = function() { - var myNumber = textsecure.storage.user.getNumber(); - var myDevice = textsecure.storage.user.getDeviceId(); - if (myDevice != 1) { - var request = new textsecure.protobuf.SyncMessage.Request(); - request.type = textsecure.protobuf.SyncMessage.Request.Type.GROUPS; - var syncMessage = new textsecure.protobuf.SyncMessage(); - syncMessage.request = request; - var contentMessage = new textsecure.protobuf.Content(); - contentMessage.syncMessage = syncMessage; - - return sendIndividualProto(myNumber, contentMessage, Date.now()); + + var sendIndividualProto = function(number, proto, timestamp) { + return new Promise(function(resolve, reject) { + sendMessageProto(timestamp, [number], proto, function(res) { + if (res.failure.length > 0) + reject(res.failure); + else + resolve(); + }); + }); } - }; - self.sendRequestContactSyncMessage = function() { - var myNumber = textsecure.storage.user.getNumber(); - var myDevice = textsecure.storage.user.getDeviceId(); - if (myDevice != 1) { - var request = new textsecure.protobuf.SyncMessage.Request(); - request.type = textsecure.protobuf.SyncMessage.Request.Type.CONTACTS; - var syncMessage = new textsecure.protobuf.SyncMessage(); - syncMessage.request = request; - var contentMessage = new textsecure.protobuf.Content(); - contentMessage.syncMessage = syncMessage; - - return sendIndividualProto(myNumber, contentMessage, Date.now()); + + var sendSyncMessage = function(message, timestamp, destination) { + var myNumber = textsecure.storage.user.getNumber(); + var myDevice = textsecure.storage.user.getDeviceId(); + if (myDevice != 1) { + var sentMessage = new textsecure.protobuf.SyncMessage.Sent(); + sentMessage.timestamp = timestamp; + sentMessage.message = message; + if (destination) { + sentMessage.destination = destination; + } + var syncMessage = new textsecure.protobuf.SyncMessage(); + syncMessage.sent = sentMessage; + var contentMessage = new textsecure.protobuf.Content(); + contentMessage.syncMessage = syncMessage; + + return sendIndividualProto(myNumber, contentMessage, Date.now()); + } } - }; - - var sendGroupProto = function(numbers, proto, timestamp) { - timestamp = timestamp || Date.now(); - var me = textsecure.storage.user.getNumber(); - numbers = numbers.filter(function(number) { return number != me; }); - - return new Promise(function(resolve, reject) { - sendMessageProto(timestamp, numbers, proto, function(res) { - if (res.failure.length > 0) - reject(res.failure); - else - resolve(); - }); - }).then(function() { - return sendSyncMessage(proto, timestamp); - }); - } - - self.sendMessageToNumber = function(number, messageText, attachments, timestamp) { - var proto = new textsecure.protobuf.DataMessage(); - proto.body = messageText; - - var promises = []; - for (var i in attachments) - promises.push(makeAttachmentPointer(attachments[i])); - return Promise.all(promises).then(function(attachmentsArray) { - proto.attachments = attachmentsArray; - return sendIndividualProto(number, proto, timestamp).then(function() { - return sendSyncMessage(proto, timestamp, number); - }); - }); - } - - self.closeSession = function(number) { - var proto = new textsecure.protobuf.DataMessage(); - proto.body = "TERMINATE"; - proto.flags = textsecure.protobuf.DataMessage.Flags.END_SESSION; - return sendIndividualProto(number, proto, Date.now()).then(function(res) { - return textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devices) { - return Promise.all(devices.map(function(device) { - return textsecure.protocol_wrapper.closeOpenSessionForDevice(device.encodedNumber); - })).then(function() { - return res; + + self.sendRequestGroupSyncMessage = function() { + var myNumber = textsecure.storage.user.getNumber(); + var myDevice = textsecure.storage.user.getDeviceId(); + if (myDevice != 1) { + var request = new textsecure.protobuf.SyncMessage.Request(); + request.type = textsecure.protobuf.SyncMessage.Request.Type.GROUPS; + var syncMessage = new textsecure.protobuf.SyncMessage(); + syncMessage.request = request; + var contentMessage = new textsecure.protobuf.Content(); + contentMessage.syncMessage = syncMessage; + + return sendIndividualProto(myNumber, contentMessage, Date.now()); + } + }; + self.sendRequestContactSyncMessage = function() { + var myNumber = textsecure.storage.user.getNumber(); + var myDevice = textsecure.storage.user.getDeviceId(); + if (myDevice != 1) { + var request = new textsecure.protobuf.SyncMessage.Request(); + request.type = textsecure.protobuf.SyncMessage.Request.Type.CONTACTS; + var syncMessage = new textsecure.protobuf.SyncMessage(); + syncMessage.request = request; + var contentMessage = new textsecure.protobuf.Content(); + contentMessage.syncMessage = syncMessage; + + return sendIndividualProto(myNumber, contentMessage, Date.now()); + } + }; + + var sendGroupProto = function(numbers, proto, timestamp) { + timestamp = timestamp || Date.now(); + var me = textsecure.storage.user.getNumber(); + numbers = numbers.filter(function(number) { return number != me; }); + + return new Promise(function(resolve, reject) { + sendMessageProto(timestamp, numbers, proto, function(res) { + if (res.failure.length > 0) + reject(res.failure); + else + resolve(); }); + }).then(function() { + return sendSyncMessage(proto, timestamp); }); - }); - } - - self.sendMessageToGroup = function(groupId, messageText, attachments, timestamp) { - var proto = new textsecure.protobuf.DataMessage(); - proto.body = messageText; - proto.group = new textsecure.protobuf.GroupContext(); - proto.group.id = toArrayBuffer(groupId); - proto.group.type = textsecure.protobuf.GroupContext.Type.DELIVER; + } - return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) { - if (numbers === undefined) - return Promise.reject(new Error("Unknown Group")); + self.sendMessageToNumber = function(number, messageText, attachments, timestamp) { + var proto = new textsecure.protobuf.DataMessage(); + proto.body = messageText; var promises = []; for (var i in attachments) promises.push(makeAttachmentPointer(attachments[i])); return Promise.all(promises).then(function(attachmentsArray) { proto.attachments = attachmentsArray; - return sendGroupProto(numbers, proto, timestamp); + return sendIndividualProto(number, proto, timestamp).then(function() { + return sendSyncMessage(proto, timestamp, number); + }); }); - }); - } + } - self.createGroup = function(numbers, name, avatar) { - var proto = new textsecure.protobuf.DataMessage(); - proto.group = new textsecure.protobuf.GroupContext(); + self.closeSession = function(number) { + var proto = new textsecure.protobuf.DataMessage(); + proto.body = "TERMINATE"; + proto.flags = textsecure.protobuf.DataMessage.Flags.END_SESSION; + return sendIndividualProto(number, proto, Date.now()).then(function(res) { + return textsecure.storage.devices.getDeviceObjectsForNumber(number).then(function(devices) { + return Promise.all(devices.map(function(device) { + return textsecure.protocol_wrapper.closeOpenSessionForDevice(device.encodedNumber); + })).then(function() { + return res; + }); + }); + }); + } - return textsecure.storage.groups.createNewGroup(numbers).then(function(group) { - proto.group.id = toArrayBuffer(group.id); - var numbers = group.numbers; + self.sendMessageToGroup = function(groupId, messageText, attachments, timestamp) { + var proto = new textsecure.protobuf.DataMessage(); + proto.body = messageText; + proto.group = new textsecure.protobuf.GroupContext(); + proto.group.id = toArrayBuffer(groupId); + proto.group.type = textsecure.protobuf.GroupContext.Type.DELIVER; - proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; - proto.group.members = numbers; - proto.group.name = name; + return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) { + if (numbers === undefined) + return Promise.reject(new Error("Unknown Group")); - if (avatar !== undefined) { - return makeAttachmentPointer(avatar).then(function(attachment) { - proto.group.avatar = attachment; + var promises = []; + for (var i in attachments) + promises.push(makeAttachmentPointer(attachments[i])); + return Promise.all(promises).then(function(attachmentsArray) { + proto.attachments = attachmentsArray; + return sendGroupProto(numbers, proto, timestamp); + }); + }); + } + + self.createGroup = function(numbers, name, avatar) { + var proto = new textsecure.protobuf.DataMessage(); + proto.group = new textsecure.protobuf.GroupContext(); + + return textsecure.storage.groups.createNewGroup(numbers).then(function(group) { + proto.group.id = toArrayBuffer(group.id); + var numbers = group.numbers; + + proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; + proto.group.members = numbers; + proto.group.name = name; + + if (avatar !== undefined) { + return makeAttachmentPointer(avatar).then(function(attachment) { + proto.group.avatar = attachment; + return sendGroupProto(numbers, proto).then(function() { + return proto.group.id; + }); + }); + } else { return sendGroupProto(numbers, proto).then(function() { return proto.group.id; }); - }); - } else { - return sendGroupProto(numbers, proto).then(function() { - return proto.group.id; - }); - } - }); - } + } + }); + } - self.updateGroup = function(groupId, name, avatar, numbers) { - var proto = new textsecure.protobuf.DataMessage(); - proto.group = new textsecure.protobuf.GroupContext(); + self.updateGroup = function(groupId, name, avatar, numbers) { + var proto = new textsecure.protobuf.DataMessage(); + proto.group = new textsecure.protobuf.GroupContext(); - proto.group.id = toArrayBuffer(groupId); - proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; - proto.group.name = name; + proto.group.id = toArrayBuffer(groupId); + proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; + proto.group.name = name; - return textsecure.storage.groups.addNumbers(groupId, numbers).then(function(numbers) { - if (numbers === undefined) { - return Promise.reject(new Error("Unknown Group")); - } - proto.group.members = numbers; + return textsecure.storage.groups.addNumbers(groupId, numbers).then(function(numbers) { + if (numbers === undefined) { + return Promise.reject(new Error("Unknown Group")); + } + proto.group.members = numbers; - if (avatar !== undefined && avatar !== null) { - return makeAttachmentPointer(avatar).then(function(attachment) { - proto.group.avatar = attachment; + if (avatar !== undefined && avatar !== null) { + return makeAttachmentPointer(avatar).then(function(attachment) { + proto.group.avatar = attachment; + return sendGroupProto(numbers, proto).then(function() { + return proto.group.id; + }); + }); + } else { return sendGroupProto(numbers, proto).then(function() { return proto.group.id; }); - }); - } else { - return sendGroupProto(numbers, proto).then(function() { - return proto.group.id; - }); - } - }); - } - - self.addNumberToGroup = function(groupId, number) { - var proto = new textsecure.protobuf.DataMessage(); - proto.group = new textsecure.protobuf.GroupContext(); - proto.group.id = toArrayBuffer(groupId); - proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; - - return textsecure.storage.groups.addNumbers(groupId, [number]).then(function(numbers) { - if (numbers === undefined) - return Promise.reject(new Error("Unknown Group")); - proto.group.members = numbers; - - return sendGroupProto(numbers, proto); - }); - } - - self.setGroupName = function(groupId, name) { - var proto = new textsecure.protobuf.DataMessage(); - proto.group = new textsecure.protobuf.GroupContext(); - proto.group.id = toArrayBuffer(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 sendGroupProto(numbers, proto); - }); - } - - self.setGroupAvatar = function(groupId, avatar) { - var proto = new textsecure.protobuf.DataMessage(); - proto.group = new textsecure.protobuf.GroupContext(); - proto.group.id = toArrayBuffer(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 makeAttachmentPointer(avatar).then(function(attachment) { - proto.group.avatar = attachment; + } + }); + } + + self.addNumberToGroup = function(groupId, number) { + var proto = new textsecure.protobuf.DataMessage(); + proto.group = new textsecure.protobuf.GroupContext(); + proto.group.id = toArrayBuffer(groupId); + proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; + + return textsecure.storage.groups.addNumbers(groupId, [number]).then(function(numbers) { + if (numbers === undefined) + return Promise.reject(new Error("Unknown Group")); + proto.group.members = numbers; + return sendGroupProto(numbers, proto); }); - }); - } - - self.leaveGroup = function(groupId) { - var proto = new textsecure.protobuf.DataMessage(); - proto.group = new textsecure.protobuf.GroupContext(); - proto.group.id = toArrayBuffer(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() { + } + + self.setGroupName = function(groupId, name) { + var proto = new textsecure.protobuf.DataMessage(); + proto.group = new textsecure.protobuf.GroupContext(); + proto.group.id = toArrayBuffer(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 sendGroupProto(numbers, proto); }); - }); - } + } + + self.setGroupAvatar = function(groupId, avatar) { + var proto = new textsecure.protobuf.DataMessage(); + proto.group = new textsecure.protobuf.GroupContext(); + proto.group.id = toArrayBuffer(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 makeAttachmentPointer(avatar).then(function(attachment) { + proto.group.avatar = attachment; + return sendGroupProto(numbers, proto); + }); + }); + } + + self.leaveGroup = function(groupId) { + var proto = new textsecure.protobuf.DataMessage(); + proto.group = new textsecure.protobuf.GroupContext(); + proto.group.id = toArrayBuffer(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 sendGroupProto(numbers, proto); + }); + }); + } - return self; -}(); + return self; + })(server); +};