Refactor sendmessage for prototypality

// FREEBIE
pull/749/head
lilia 10 years ago
parent 868695558d
commit 0fc673f25f

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

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

Loading…
Cancel
Save