Make libtextsecure group storage asynchronous

pull/749/head
lilia 10 years ago
parent c26c6fc317
commit f774047935

@ -38182,110 +38182,124 @@ axolotlInternal.RecipientRecord = function() {
window.textsecure.storage.groups = { window.textsecure.storage.groups = {
createNewGroup: function(numbers, groupId) { createNewGroup: function(numbers, groupId) {
if (groupId !== undefined && textsecure.storage.get("group" + groupId) !== undefined) return Promise.resolve(function() {
throw new Error("Tried to recreate group"); if (groupId !== undefined && textsecure.storage.get("group" + groupId) !== undefined)
throw new Error("Tried to recreate group");
while (groupId === undefined || textsecure.storage.get("group" + groupId) !== undefined)
groupId = getString(textsecure.crypto.getRandomBytes(16)); while (groupId === undefined || textsecure.storage.get("group" + groupId) !== undefined)
groupId = getString(textsecure.crypto.getRandomBytes(16));
var me = textsecure.storage.user.getNumber();
var haveMe = false; var me = textsecure.storage.user.getNumber();
var finalNumbers = []; var haveMe = false;
for (var i in numbers) { var finalNumbers = [];
var number = numbers[i]; for (var i in numbers) {
if (!textsecure.utils.isNumberSane(number)) var number = numbers[i];
throw new Error("Invalid number in group"); if (!textsecure.utils.isNumberSane(number))
if (number == me) throw new Error("Invalid number in group");
haveMe = true; if (number == me)
if (finalNumbers.indexOf(number) < 0) haveMe = true;
finalNumbers.push(number); if (finalNumbers.indexOf(number) < 0)
} finalNumbers.push(number);
}
if (!haveMe) if (!haveMe)
finalNumbers.push(me); finalNumbers.push(me);
var groupObject = {numbers: finalNumbers, numberRegistrationIds: {}}; var groupObject = {numbers: finalNumbers, numberRegistrationIds: {}};
for (var i in finalNumbers) for (var i in finalNumbers)
groupObject.numberRegistrationIds[finalNumbers[i]] = {}; groupObject.numberRegistrationIds[finalNumbers[i]] = {};
textsecure.storage.put("group" + groupId, groupObject); textsecure.storage.put("group" + groupId, groupObject);
return {id: groupId, numbers: finalNumbers}; return {id: groupId, numbers: finalNumbers};
}());
}, },
getNumbers: function(groupId) { getNumbers: function(groupId) {
var group = textsecure.storage.get("group" + groupId); return Promise.resolve(function() {
if (group === undefined) var group = textsecure.storage.get("group" + groupId);
return undefined; if (group === undefined)
return undefined;
return group.numbers; return group.numbers;
}());
}, },
removeNumber: function(groupId, number) { removeNumber: function(groupId, number) {
var group = textsecure.storage.get("group" + groupId); return Promise.resolve(function() {
if (group === undefined) var group = textsecure.storage.get("group" + groupId);
return undefined; if (group === undefined)
return undefined;
var me = textsecure.storage.user.getNumber(); var me = textsecure.storage.user.getNumber();
if (number == me) if (number == me)
throw new Error("Cannot remove ourselves from a group, leave the group instead"); throw new Error("Cannot remove ourselves from a group, leave the group instead");
var i = group.numbers.indexOf(number); var i = group.numbers.indexOf(number);
if (i > -1) { if (i > -1) {
group.numbers.slice(i, 1); group.numbers.slice(i, 1);
delete group.numberRegistrationIds[number]; delete group.numberRegistrationIds[number];
textsecure.storage.put("group" + groupId, group); textsecure.storage.put("group" + groupId, group);
} }
return group.numbers; return group.numbers;
}());
}, },
addNumbers: function(groupId, numbers) { addNumbers: function(groupId, numbers) {
var group = textsecure.storage.get("group" + groupId); return Promise.resolve(function() {
if (group === undefined) var group = textsecure.storage.get("group" + groupId);
return undefined; if (group === undefined)
return undefined;
for (var i in numbers) {
var number = numbers[i]; for (var i in numbers) {
if (!textsecure.utils.isNumberSane(number)) var number = numbers[i];
throw new Error("Invalid number in set to add to group"); if (!textsecure.utils.isNumberSane(number))
if (group.numbers.indexOf(number) < 0) { throw new Error("Invalid number in set to add to group");
group.numbers.push(number); if (group.numbers.indexOf(number) < 0) {
group.numberRegistrationIds[number] = {}; group.numbers.push(number);
group.numberRegistrationIds[number] = {};
}
} }
}
textsecure.storage.put("group" + groupId, group); textsecure.storage.put("group" + groupId, group);
return group.numbers; return group.numbers;
}());
}, },
deleteGroup: function(groupId) { deleteGroup: function(groupId) {
textsecure.storage.remove("group" + groupId); return Promise.resolve(function() {
textsecure.storage.remove("group" + groupId);
}());
}, },
getGroup: function(groupId) { getGroup: function(groupId) {
var group = textsecure.storage.get("group" + groupId); return Promise.resolve(function() {
if (group === undefined) var group = textsecure.storage.get("group" + groupId);
return undefined; if (group === undefined)
return undefined;
return { id: groupId, numbers: group.numbers }; //TODO: avatar/name tracking return { id: groupId, numbers: group.numbers }; //TODO: avatar/name tracking
}());
}, },
needUpdateByDeviceRegistrationId: function(groupId, number, encodedNumber, registrationId) { needUpdateByDeviceRegistrationId: function(groupId, number, encodedNumber, registrationId) {
var group = textsecure.storage.get("group" + groupId); return Promise.resolve(function() {
if (group === undefined) var group = textsecure.storage.get("group" + groupId);
throw new Error("Unknown group for device registration id"); if (group === undefined)
throw new Error("Unknown group for device registration id");
if (group.numberRegistrationIds[number] === undefined) if (group.numberRegistrationIds[number] === undefined)
throw new Error("Unknown number in group for device registration id"); throw new Error("Unknown number in group for device registration id");
if (group.numberRegistrationIds[number][encodedNumber] == registrationId) if (group.numberRegistrationIds[number][encodedNumber] == registrationId)
return false; return false;
var needUpdate = group.numberRegistrationIds[number][encodedNumber] !== undefined; var needUpdate = group.numberRegistrationIds[number][encodedNumber] !== undefined;
group.numberRegistrationIds[number][encodedNumber] = registrationId; group.numberRegistrationIds[number][encodedNumber] = registrationId;
textsecure.storage.put("group" + groupId, group); textsecure.storage.put("group" + groupId, group);
return needUpdate; return needUpdate;
}());
}, },
}; };
})(); })();
@ -38732,65 +38746,66 @@ textsecure.processDecrypted = function(decrypted, source) {
if (decrypted.group !== null) { if (decrypted.group !== null) {
decrypted.group.id = getString(decrypted.group.id); decrypted.group.id = getString(decrypted.group.id);
var existingGroup = textsecure.storage.groups.getNumbers(decrypted.group.id); promises.push(textsecure.storage.groups.getNumbers(decrypted.group.id).then(function(existingGroup) {
if (existingGroup === undefined) { if (existingGroup === undefined) {
if (decrypted.group.type != textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE) { if (decrypted.group.type != textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE) {
throw new Error("Got message for unknown group"); throw new Error("Got message for unknown group");
} }
textsecure.storage.groups.createNewGroup(decrypted.group.members, decrypted.group.id); if (decrypted.group.avatar !== null) {
if (decrypted.group.avatar !== null) {
promises.push(handleAttachment(decrypted.group.avatar));
}
} else {
var fromIndex = existingGroup.indexOf(source);
if (fromIndex < 0) {
//TODO: This could be indication of a race...
throw new Error("Sender was not a member of the group they were sending from");
}
switch(decrypted.group.type) {
case textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE:
if (decrypted.group.avatar !== null)
promises.push(handleAttachment(decrypted.group.avatar)); promises.push(handleAttachment(decrypted.group.avatar));
}
return textsecure.storage.groups.createNewGroup(decrypted.group.members, decrypted.group.id);
} else {
var fromIndex = existingGroup.indexOf(source);
if (decrypted.group.members.filter(function(number) { return !textsecure.utils.isNumberSane(number); }).length != 0) if (fromIndex < 0) {
throw new Error("Invalid number in new group members"); //TODO: This could be indication of a race...
throw new Error("Sender was not a member of the group they were sending from");
}
if (existingGroup.filter(function(number) { decrypted.group.members.indexOf(number) < 0 }).length != 0) switch(decrypted.group.type) {
throw new Error("Attempted to remove numbers from group with an UPDATE"); case textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE:
decrypted.group.added = decrypted.group.members.filter(function(number) { return existingGroup.indexOf(number) < 0; }); if (decrypted.group.avatar !== null)
promises.push(handleAttachment(decrypted.group.avatar));
var newGroup = textsecure.storage.groups.addNumbers(decrypted.group.id, decrypted.group.added); if (decrypted.group.members.filter(function(number) { return !textsecure.utils.isNumberSane(number); }).length != 0)
if (newGroup.length != decrypted.group.members.length || throw new Error("Invalid number in new group members");
newGroup.filter(function(number) { return decrypted.group.members.indexOf(number) < 0; }).length != 0) {
throw new Error("Error calculating group member difference");
}
//TODO: Also follow this path if avatar + name haven't changed (ie we should start storing those) if (existingGroup.filter(function(number) { decrypted.group.members.indexOf(number) < 0 }).length != 0)
if (decrypted.group.avatar === null && decrypted.group.added.length == 0 && decrypted.group.name === null) { throw new Error("Attempted to remove numbers from group with an UPDATE");
return; decrypted.group.added = decrypted.group.members.filter(function(number) { return existingGroup.indexOf(number) < 0; });
}
decrypted.body = null; return textsecure.storage.groups.addNumbers(decrypted.group.id, decrypted.group.added).then(function(newGroup) {
decrypted.attachments = []; if (newGroup.length != decrypted.group.members.length ||
newGroup.filter(function(number) { return decrypted.group.members.indexOf(number) < 0; }).length != 0) {
throw new Error("Error calculating group member difference");
}
break; //TODO: Also follow this path if avatar + name haven't changed (ie we should start storing those)
case textsecure.protobuf.PushMessageContent.GroupContext.Type.QUIT: if (decrypted.group.avatar === null && decrypted.group.added.length == 0 && decrypted.group.name === null) {
textsecure.storage.groups.removeNumber(decrypted.group.id, source); return;
}
decrypted.body = null; decrypted.body = null;
decrypted.attachments = []; decrypted.attachments = [];
case textsecure.protobuf.PushMessageContent.GroupContext.Type.DELIVER: });
decrypted.group.name = null;
decrypted.group.members = [];
decrypted.group.avatar = null;
break; break;
default: case textsecure.protobuf.PushMessageContent.GroupContext.Type.QUIT:
throw new Error("Unknown group message type"); decrypted.body = null;
decrypted.attachments = [];
return textsecure.storage.groups.removeNumber(decrypted.group.id, source);
case textsecure.protobuf.PushMessageContent.GroupContext.Type.DELIVER:
decrypted.group.name = null;
decrypted.group.members = [];
decrypted.group.avatar = null;
break;
default:
throw new Error("Unknown group message type");
}
} }
} }));
} }
for (var i in decrypted.attachments) { for (var i in decrypted.attachments) {
@ -39339,7 +39354,6 @@ TextSecureServer = function () {
}); });
} }
}; };
function createAccount(number, verificationCode, identityKeyPair, single_device) { function createAccount(number, verificationCode, identityKeyPair, single_device) {
textsecure.storage.put('identityKey', identityKeyPair); textsecure.storage.put('identityKey', identityKeyPair);
@ -39589,34 +39603,37 @@ window.textsecure.messaging = function() {
var doUpdate = false; var doUpdate = false;
Promise.all(devicesForNumber.map(function(device) { Promise.all(devicesForNumber.map(function(device) {
return textsecure.protocol_wrapper.getRegistrationId(device.encodedNumber).then(function(registrationId) { return textsecure.protocol_wrapper.getRegistrationId(device.encodedNumber).then(function(registrationId) {
if (textsecure.storage.groups.needUpdateByDeviceRegistrationId(groupId, number, devicesForNumber[i].encodedNumber, registrationId)) return textsecure.storage.groups.needUpdateByDeviceRegistrationId(
doUpdate = true; groupId, number, devicesForNumber[i].encodedNumber, registrationId
).then(function(needUpdate) {
if (needUpdate) doUpdate = true;
});
}); });
})).then(function() { })).then(function() {
if (!doUpdate) if (!doUpdate) return;
return Promise.resolve(true);
var group = textsecure.storage.groups.getGroup(groupId); return textsecure.storage.groups.getGroup(groupId).then(function(group) {
var numberIndex = group.numbers.indexOf(number); var numberIndex = group.numbers.indexOf(number);
if (numberIndex < 0) // This is potentially a multi-message rare racing-AJAX race if (numberIndex < 0) // This is potentially a multi-message rare racing-AJAX race
return Promise.reject("Tried to refresh group to non-member"); return Promise.reject("Tried to refresh group to non-member");
var proto = new textsecure.protobuf.PushMessageContent(); var proto = new textsecure.protobuf.PushMessageContent();
proto.group = new textsecure.protobuf.PushMessageContent.GroupContext(); proto.group = new textsecure.protobuf.PushMessageContent.GroupContext();
proto.group.id = toArrayBuffer(group.id); proto.group.id = toArrayBuffer(group.id);
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE;
proto.group.members = group.numbers; proto.group.members = group.numbers;
proto.group.name = group.name === undefined ? null : group.name; proto.group.name = group.name === undefined ? null : group.name;
if (group.avatar !== undefined) { if (group.avatar !== undefined) {
return makeAttachmentPointer(group.avatar).then(function(attachment) { return makeAttachmentPointer(group.avatar).then(function(attachment) {
proto.group.avatar = attachment; proto.group.avatar = attachment;
return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
});
} else {
return sendMessageToDevices(Date.now(), number, devicesForNumber, proto); return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
}); }
} else { });
return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
}
}); });
} }
@ -39821,16 +39838,17 @@ window.textsecure.messaging = function() {
proto.group.id = toArrayBuffer(groupId); proto.group.id = toArrayBuffer(groupId);
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.DELIVER; proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.DELIVER;
var numbers = textsecure.storage.groups.getNumbers(groupId); return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) {
if (numbers === undefined) if (numbers === undefined)
return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); }); return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); });
var promises = []; var promises = [];
for (var i in attachments) for (var i in attachments)
promises.push(makeAttachmentPointer(attachments[i])); promises.push(makeAttachmentPointer(attachments[i]));
return Promise.all(promises).then(function(attachmentsArray) { return Promise.all(promises).then(function(attachmentsArray) {
proto.attachments = attachmentsArray; proto.attachments = attachmentsArray;
return sendGroupProto(numbers, proto, timestamp); return sendGroupProto(numbers, proto, timestamp);
});
}); });
} }
@ -39838,26 +39856,27 @@ window.textsecure.messaging = function() {
var proto = new textsecure.protobuf.PushMessageContent(); var proto = new textsecure.protobuf.PushMessageContent();
proto.group = new textsecure.protobuf.PushMessageContent.GroupContext(); proto.group = new textsecure.protobuf.PushMessageContent.GroupContext();
var group = textsecure.storage.groups.createNewGroup(numbers); return textsecure.storage.groups.createNewGroup(numbers).then(function(group) {
proto.group.id = toArrayBuffer(group.id); proto.group.id = toArrayBuffer(group.id);
var numbers = group.numbers; var numbers = group.numbers;
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE;
proto.group.members = numbers; proto.group.members = numbers;
proto.group.name = name; proto.group.name = name;
if (avatar !== undefined) { if (avatar !== undefined) {
return makeAttachmentPointer(avatar).then(function(attachment) { return makeAttachmentPointer(avatar).then(function(attachment) {
proto.group.avatar = attachment; proto.group.avatar = attachment;
return sendGroupProto(numbers, proto).then(function() {
return proto.group.id;
});
});
} else {
return sendGroupProto(numbers, proto).then(function() { return sendGroupProto(numbers, proto).then(function() {
return proto.group.id; return proto.group.id;
}); });
}); }
} else { });
return sendGroupProto(numbers, proto).then(function() {
return proto.group.id;
});
}
} }
self.updateGroup = function(groupId, name, avatar, numbers) { self.updateGroup = function(groupId, name, avatar, numbers) {
@ -39868,24 +39887,25 @@ window.textsecure.messaging = function() {
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE;
proto.group.name = name; proto.group.name = name;
var numbers = textsecure.storage.groups.addNumbers(groupId, numbers); return textsecure.storage.groups.addNumbers(groupId, numbers).then(function(numbers) {
if (numbers === undefined) { if (numbers === undefined) {
return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); }); return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); });
} }
proto.group.members = numbers; proto.group.members = numbers;
if (avatar !== undefined) { if (avatar !== undefined) {
return makeAttachmentPointer(avatar).then(function(attachment) { return makeAttachmentPointer(avatar).then(function(attachment) {
proto.group.avatar = attachment; proto.group.avatar = attachment;
return sendGroupProto(numbers, proto).then(function() {
return proto.group.id;
});
});
} else {
return sendGroupProto(numbers, proto).then(function() { return sendGroupProto(numbers, proto).then(function() {
return proto.group.id; return proto.group.id;
}); });
}); }
} else { });
return sendGroupProto(numbers, proto).then(function() {
return proto.group.id;
});
}
} }
self.addNumberToGroup = function(groupId, number) { self.addNumberToGroup = function(groupId, number) {
@ -39894,12 +39914,13 @@ window.textsecure.messaging = function() {
proto.group.id = toArrayBuffer(groupId); proto.group.id = toArrayBuffer(groupId);
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE;
var numbers = textsecure.storage.groups.addNumbers(groupId, [number]); return textsecure.storage.groups.addNumbers(groupId, [number]).then(function(numbers) {
if (numbers === undefined) if (numbers === undefined)
return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); }); return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); });
proto.group.members = numbers; proto.group.members = numbers;
return sendGroupProto(numbers, proto); return sendGroupProto(numbers, proto);
});
} }
self.setGroupName = function(groupId, name) { self.setGroupName = function(groupId, name) {
@ -39909,12 +39930,13 @@ window.textsecure.messaging = function() {
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE;
proto.group.name = name; proto.group.name = name;
var numbers = textsecure.storage.groups.getNumbers(groupId); return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) {
if (numbers === undefined) if (numbers === undefined)
return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); }); return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); });
proto.group.members = numbers; proto.group.members = numbers;
return sendGroupProto(numbers, proto); return sendGroupProto(numbers, proto);
});
} }
self.setGroupAvatar = function(groupId, avatar) { self.setGroupAvatar = function(groupId, avatar) {
@ -39923,14 +39945,15 @@ window.textsecure.messaging = function() {
proto.group.id = toArrayBuffer(groupId); proto.group.id = toArrayBuffer(groupId);
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE;
var numbers = textsecure.storage.groups.getNumbers(groupId); return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) {
if (numbers === undefined) if (numbers === undefined)
return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); }); return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); });
proto.group.members = numbers; proto.group.members = numbers;
return makeAttachmentPointer(avatar).then(function(attachment) { return makeAttachmentPointer(avatar).then(function(attachment) {
proto.group.avatar = attachment; proto.group.avatar = attachment;
return sendGroupProto(numbers, proto); return sendGroupProto(numbers, proto);
});
}); });
} }
@ -39940,12 +39963,13 @@ window.textsecure.messaging = function() {
proto.group.id = toArrayBuffer(groupId); proto.group.id = toArrayBuffer(groupId);
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.QUIT; proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.QUIT;
var numbers = textsecure.storage.groups.getNumbers(groupId); return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) {
if (numbers === undefined) if (numbers === undefined)
return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); }); return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); });
textsecure.storage.groups.deleteGroup(groupId); return textsecure.storage.groups.deleteGroup(groupId).then(function() {
return sendGroupProto(numbers, proto);
return sendGroupProto(numbers, proto); });
});
} }
return self; return self;

@ -114,40 +114,43 @@
return this.avatarInput.getFile().then(function(avatarFile) { return this.avatarInput.getFile().then(function(avatarFile) {
var members = this.getRecipients().pluck('id'); var members = this.getRecipients().pluck('id');
var groupId = textsecure.storage.groups.createNewGroup(members).id; textsecure.storage.groups.createNewGroup(members).then(function(group) {
var attributes = { return group.id;
id: groupId, }).then(function(groupId) {
groupId: groupId, var attributes = {
type: 'group', id: groupId,
name: name, groupId: groupId,
avatar: avatarFile, type: 'group',
members: members name: name,
}; avatar: avatarFile,
var group = new Whisper.Conversation(attributes); members: members
group.save().then(function() { };
this.trigger('open', {modelId: groupId}); var group = new Whisper.Conversation(attributes);
group.save().then(function() {
this.trigger('open', {modelId: groupId});
}.bind(this));
var now = Date.now();
var message = group.messageCollection.add({
conversationId : group.id,
type : 'outgoing',
sent_at : now,
received_at : now,
group_update : {
name: group.get('name'),
avatar: group.get('avatar'),
joined: group.get('members')
}
});
message.save();
textsecure.messaging.updateGroup(
group.id,
group.get('name'),
group.get('avatar'),
group.get('members')
).catch(function(errors) {
message.save({errors: errors.map(function(e){return e.error;})});
});
}.bind(this)); }.bind(this));
var now = Date.now();
var message = group.messageCollection.add({
conversationId : group.id,
type : 'outgoing',
sent_at : now,
received_at : now,
group_update : {
name: group.get('name'),
avatar: group.get('avatar'),
joined: group.get('members')
}
});
message.save();
textsecure.messaging.updateGroup(
group.id,
group.get('name'),
group.get('avatar'),
group.get('members')
).catch(function(errors) {
message.save({errors: errors.map(function(e){return e.error;})});
});
}.bind(this)); }.bind(this));
}, },

@ -179,65 +179,66 @@ textsecure.processDecrypted = function(decrypted, source) {
if (decrypted.group !== null) { if (decrypted.group !== null) {
decrypted.group.id = getString(decrypted.group.id); decrypted.group.id = getString(decrypted.group.id);
var existingGroup = textsecure.storage.groups.getNumbers(decrypted.group.id); promises.push(textsecure.storage.groups.getNumbers(decrypted.group.id).then(function(existingGroup) {
if (existingGroup === undefined) { if (existingGroup === undefined) {
if (decrypted.group.type != textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE) { if (decrypted.group.type != textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE) {
throw new Error("Got message for unknown group"); throw new Error("Got message for unknown group");
} }
textsecure.storage.groups.createNewGroup(decrypted.group.members, decrypted.group.id); if (decrypted.group.avatar !== null) {
if (decrypted.group.avatar !== null) {
promises.push(handleAttachment(decrypted.group.avatar));
}
} else {
var fromIndex = existingGroup.indexOf(source);
if (fromIndex < 0) {
//TODO: This could be indication of a race...
throw new Error("Sender was not a member of the group they were sending from");
}
switch(decrypted.group.type) {
case textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE:
if (decrypted.group.avatar !== null)
promises.push(handleAttachment(decrypted.group.avatar)); promises.push(handleAttachment(decrypted.group.avatar));
if (decrypted.group.members.filter(function(number) { return !textsecure.utils.isNumberSane(number); }).length != 0)
throw new Error("Invalid number in new group members");
if (existingGroup.filter(function(number) { decrypted.group.members.indexOf(number) < 0 }).length != 0)
throw new Error("Attempted to remove numbers from group with an UPDATE");
decrypted.group.added = decrypted.group.members.filter(function(number) { return existingGroup.indexOf(number) < 0; });
var newGroup = textsecure.storage.groups.addNumbers(decrypted.group.id, decrypted.group.added);
if (newGroup.length != decrypted.group.members.length ||
newGroup.filter(function(number) { return decrypted.group.members.indexOf(number) < 0; }).length != 0) {
throw new Error("Error calculating group member difference");
} }
return textsecure.storage.groups.createNewGroup(decrypted.group.members, decrypted.group.id);
} else {
var fromIndex = existingGroup.indexOf(source);
//TODO: Also follow this path if avatar + name haven't changed (ie we should start storing those) if (fromIndex < 0) {
if (decrypted.group.avatar === null && decrypted.group.added.length == 0 && decrypted.group.name === null) { //TODO: This could be indication of a race...
return; throw new Error("Sender was not a member of the group they were sending from");
} }
decrypted.body = null; switch(decrypted.group.type) {
decrypted.attachments = []; case textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE:
if (decrypted.group.avatar !== null)
break; promises.push(handleAttachment(decrypted.group.avatar));
case textsecure.protobuf.PushMessageContent.GroupContext.Type.QUIT:
textsecure.storage.groups.removeNumber(decrypted.group.id, source); if (decrypted.group.members.filter(function(number) { return !textsecure.utils.isNumberSane(number); }).length != 0)
throw new Error("Invalid number in new group members");
decrypted.body = null;
decrypted.attachments = []; if (existingGroup.filter(function(number) { decrypted.group.members.indexOf(number) < 0 }).length != 0)
case textsecure.protobuf.PushMessageContent.GroupContext.Type.DELIVER: throw new Error("Attempted to remove numbers from group with an UPDATE");
decrypted.group.name = null; decrypted.group.added = decrypted.group.members.filter(function(number) { return existingGroup.indexOf(number) < 0; });
decrypted.group.members = [];
decrypted.group.avatar = null; return textsecure.storage.groups.addNumbers(decrypted.group.id, decrypted.group.added).then(function(newGroup) {
if (newGroup.length != decrypted.group.members.length ||
break; newGroup.filter(function(number) { return decrypted.group.members.indexOf(number) < 0; }).length != 0) {
default: throw new Error("Error calculating group member difference");
throw new Error("Unknown group message type"); }
//TODO: Also follow this path if avatar + name haven't changed (ie we should start storing those)
if (decrypted.group.avatar === null && decrypted.group.added.length == 0 && decrypted.group.name === null) {
return;
}
decrypted.body = null;
decrypted.attachments = [];
});
break;
case textsecure.protobuf.PushMessageContent.GroupContext.Type.QUIT:
decrypted.body = null;
decrypted.attachments = [];
return textsecure.storage.groups.removeNumber(decrypted.group.id, source);
case textsecure.protobuf.PushMessageContent.GroupContext.Type.DELIVER:
decrypted.group.name = null;
decrypted.group.members = [];
decrypted.group.avatar = null;
break;
default:
throw new Error("Unknown group message type");
}
} }
} }));
} }
for (var i in decrypted.attachments) { for (var i in decrypted.attachments) {

@ -99,34 +99,37 @@ window.textsecure.messaging = function() {
var doUpdate = false; var doUpdate = false;
Promise.all(devicesForNumber.map(function(device) { Promise.all(devicesForNumber.map(function(device) {
return textsecure.protocol_wrapper.getRegistrationId(device.encodedNumber).then(function(registrationId) { return textsecure.protocol_wrapper.getRegistrationId(device.encodedNumber).then(function(registrationId) {
if (textsecure.storage.groups.needUpdateByDeviceRegistrationId(groupId, number, devicesForNumber[i].encodedNumber, registrationId)) return textsecure.storage.groups.needUpdateByDeviceRegistrationId(
doUpdate = true; groupId, number, devicesForNumber[i].encodedNumber, registrationId
).then(function(needUpdate) {
if (needUpdate) doUpdate = true;
});
}); });
})).then(function() { })).then(function() {
if (!doUpdate) if (!doUpdate) return;
return Promise.resolve(true);
var group = textsecure.storage.groups.getGroup(groupId); return textsecure.storage.groups.getGroup(groupId).then(function(group) {
var numberIndex = group.numbers.indexOf(number); var numberIndex = group.numbers.indexOf(number);
if (numberIndex < 0) // This is potentially a multi-message rare racing-AJAX race if (numberIndex < 0) // This is potentially a multi-message rare racing-AJAX race
return Promise.reject("Tried to refresh group to non-member"); return Promise.reject("Tried to refresh group to non-member");
var proto = new textsecure.protobuf.PushMessageContent(); var proto = new textsecure.protobuf.PushMessageContent();
proto.group = new textsecure.protobuf.PushMessageContent.GroupContext(); proto.group = new textsecure.protobuf.PushMessageContent.GroupContext();
proto.group.id = toArrayBuffer(group.id); proto.group.id = toArrayBuffer(group.id);
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE;
proto.group.members = group.numbers; proto.group.members = group.numbers;
proto.group.name = group.name === undefined ? null : group.name; proto.group.name = group.name === undefined ? null : group.name;
if (group.avatar !== undefined) { if (group.avatar !== undefined) {
return makeAttachmentPointer(group.avatar).then(function(attachment) { return makeAttachmentPointer(group.avatar).then(function(attachment) {
proto.group.avatar = attachment; proto.group.avatar = attachment;
return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
});
} else {
return sendMessageToDevices(Date.now(), number, devicesForNumber, proto); return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
}); }
} else { });
return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
}
}); });
} }
@ -331,16 +334,17 @@ window.textsecure.messaging = function() {
proto.group.id = toArrayBuffer(groupId); proto.group.id = toArrayBuffer(groupId);
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.DELIVER; proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.DELIVER;
var numbers = textsecure.storage.groups.getNumbers(groupId); return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) {
if (numbers === undefined) if (numbers === undefined)
return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); }); return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); });
var promises = []; var promises = [];
for (var i in attachments) for (var i in attachments)
promises.push(makeAttachmentPointer(attachments[i])); promises.push(makeAttachmentPointer(attachments[i]));
return Promise.all(promises).then(function(attachmentsArray) { return Promise.all(promises).then(function(attachmentsArray) {
proto.attachments = attachmentsArray; proto.attachments = attachmentsArray;
return sendGroupProto(numbers, proto, timestamp); return sendGroupProto(numbers, proto, timestamp);
});
}); });
} }
@ -348,26 +352,27 @@ window.textsecure.messaging = function() {
var proto = new textsecure.protobuf.PushMessageContent(); var proto = new textsecure.protobuf.PushMessageContent();
proto.group = new textsecure.protobuf.PushMessageContent.GroupContext(); proto.group = new textsecure.protobuf.PushMessageContent.GroupContext();
var group = textsecure.storage.groups.createNewGroup(numbers); return textsecure.storage.groups.createNewGroup(numbers).then(function(group) {
proto.group.id = toArrayBuffer(group.id); proto.group.id = toArrayBuffer(group.id);
var numbers = group.numbers; var numbers = group.numbers;
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE;
proto.group.members = numbers; proto.group.members = numbers;
proto.group.name = name; proto.group.name = name;
if (avatar !== undefined) { if (avatar !== undefined) {
return makeAttachmentPointer(avatar).then(function(attachment) { return makeAttachmentPointer(avatar).then(function(attachment) {
proto.group.avatar = attachment; proto.group.avatar = attachment;
return sendGroupProto(numbers, proto).then(function() {
return proto.group.id;
});
});
} else {
return sendGroupProto(numbers, proto).then(function() { return sendGroupProto(numbers, proto).then(function() {
return proto.group.id; return proto.group.id;
}); });
}); }
} else { });
return sendGroupProto(numbers, proto).then(function() {
return proto.group.id;
});
}
} }
self.updateGroup = function(groupId, name, avatar, numbers) { self.updateGroup = function(groupId, name, avatar, numbers) {
@ -378,24 +383,25 @@ window.textsecure.messaging = function() {
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE;
proto.group.name = name; proto.group.name = name;
var numbers = textsecure.storage.groups.addNumbers(groupId, numbers); return textsecure.storage.groups.addNumbers(groupId, numbers).then(function(numbers) {
if (numbers === undefined) { if (numbers === undefined) {
return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); }); return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); });
} }
proto.group.members = numbers; proto.group.members = numbers;
if (avatar !== undefined) { if (avatar !== undefined) {
return makeAttachmentPointer(avatar).then(function(attachment) { return makeAttachmentPointer(avatar).then(function(attachment) {
proto.group.avatar = attachment; proto.group.avatar = attachment;
return sendGroupProto(numbers, proto).then(function() {
return proto.group.id;
});
});
} else {
return sendGroupProto(numbers, proto).then(function() { return sendGroupProto(numbers, proto).then(function() {
return proto.group.id; return proto.group.id;
}); });
}); }
} else { });
return sendGroupProto(numbers, proto).then(function() {
return proto.group.id;
});
}
} }
self.addNumberToGroup = function(groupId, number) { self.addNumberToGroup = function(groupId, number) {
@ -404,12 +410,13 @@ window.textsecure.messaging = function() {
proto.group.id = toArrayBuffer(groupId); proto.group.id = toArrayBuffer(groupId);
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE;
var numbers = textsecure.storage.groups.addNumbers(groupId, [number]); return textsecure.storage.groups.addNumbers(groupId, [number]).then(function(numbers) {
if (numbers === undefined) if (numbers === undefined)
return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); }); return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); });
proto.group.members = numbers; proto.group.members = numbers;
return sendGroupProto(numbers, proto); return sendGroupProto(numbers, proto);
});
} }
self.setGroupName = function(groupId, name) { self.setGroupName = function(groupId, name) {
@ -419,12 +426,13 @@ window.textsecure.messaging = function() {
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE;
proto.group.name = name; proto.group.name = name;
var numbers = textsecure.storage.groups.getNumbers(groupId); return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) {
if (numbers === undefined) if (numbers === undefined)
return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); }); return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); });
proto.group.members = numbers; proto.group.members = numbers;
return sendGroupProto(numbers, proto); return sendGroupProto(numbers, proto);
});
} }
self.setGroupAvatar = function(groupId, avatar) { self.setGroupAvatar = function(groupId, avatar) {
@ -433,14 +441,15 @@ window.textsecure.messaging = function() {
proto.group.id = toArrayBuffer(groupId); proto.group.id = toArrayBuffer(groupId);
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE;
var numbers = textsecure.storage.groups.getNumbers(groupId); return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) {
if (numbers === undefined) if (numbers === undefined)
return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); }); return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); });
proto.group.members = numbers; proto.group.members = numbers;
return makeAttachmentPointer(avatar).then(function(attachment) { return makeAttachmentPointer(avatar).then(function(attachment) {
proto.group.avatar = attachment; proto.group.avatar = attachment;
return sendGroupProto(numbers, proto); return sendGroupProto(numbers, proto);
});
}); });
} }
@ -450,12 +459,13 @@ window.textsecure.messaging = function() {
proto.group.id = toArrayBuffer(groupId); proto.group.id = toArrayBuffer(groupId);
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.QUIT; proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.QUIT;
var numbers = textsecure.storage.groups.getNumbers(groupId); return textsecure.storage.groups.getNumbers(groupId).then(function(numbers) {
if (numbers === undefined) if (numbers === undefined)
return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); }); return new Promise(function(resolve, reject) { reject(new Error("Unknown Group")); });
textsecure.storage.groups.deleteGroup(groupId); return textsecure.storage.groups.deleteGroup(groupId).then(function() {
return sendGroupProto(numbers, proto);
return sendGroupProto(numbers, proto); });
});
} }
return self; return self;

@ -25,110 +25,124 @@
window.textsecure.storage.groups = { window.textsecure.storage.groups = {
createNewGroup: function(numbers, groupId) { createNewGroup: function(numbers, groupId) {
if (groupId !== undefined && textsecure.storage.get("group" + groupId) !== undefined) return Promise.resolve(function() {
throw new Error("Tried to recreate group"); if (groupId !== undefined && textsecure.storage.get("group" + groupId) !== undefined)
throw new Error("Tried to recreate group");
while (groupId === undefined || textsecure.storage.get("group" + groupId) !== undefined)
groupId = getString(textsecure.crypto.getRandomBytes(16)); while (groupId === undefined || textsecure.storage.get("group" + groupId) !== undefined)
groupId = getString(textsecure.crypto.getRandomBytes(16));
var me = textsecure.storage.user.getNumber();
var haveMe = false; var me = textsecure.storage.user.getNumber();
var finalNumbers = []; var haveMe = false;
for (var i in numbers) { var finalNumbers = [];
var number = numbers[i]; for (var i in numbers) {
if (!textsecure.utils.isNumberSane(number)) var number = numbers[i];
throw new Error("Invalid number in group"); if (!textsecure.utils.isNumberSane(number))
if (number == me) throw new Error("Invalid number in group");
haveMe = true; if (number == me)
if (finalNumbers.indexOf(number) < 0) haveMe = true;
finalNumbers.push(number); if (finalNumbers.indexOf(number) < 0)
} finalNumbers.push(number);
}
if (!haveMe) if (!haveMe)
finalNumbers.push(me); finalNumbers.push(me);
var groupObject = {numbers: finalNumbers, numberRegistrationIds: {}}; var groupObject = {numbers: finalNumbers, numberRegistrationIds: {}};
for (var i in finalNumbers) for (var i in finalNumbers)
groupObject.numberRegistrationIds[finalNumbers[i]] = {}; groupObject.numberRegistrationIds[finalNumbers[i]] = {};
textsecure.storage.put("group" + groupId, groupObject); textsecure.storage.put("group" + groupId, groupObject);
return {id: groupId, numbers: finalNumbers}; return {id: groupId, numbers: finalNumbers};
}());
}, },
getNumbers: function(groupId) { getNumbers: function(groupId) {
var group = textsecure.storage.get("group" + groupId); return Promise.resolve(function() {
if (group === undefined) var group = textsecure.storage.get("group" + groupId);
return undefined; if (group === undefined)
return undefined;
return group.numbers; return group.numbers;
}());
}, },
removeNumber: function(groupId, number) { removeNumber: function(groupId, number) {
var group = textsecure.storage.get("group" + groupId); return Promise.resolve(function() {
if (group === undefined) var group = textsecure.storage.get("group" + groupId);
return undefined; if (group === undefined)
return undefined;
var me = textsecure.storage.user.getNumber();
if (number == me) var me = textsecure.storage.user.getNumber();
throw new Error("Cannot remove ourselves from a group, leave the group instead"); if (number == me)
throw new Error("Cannot remove ourselves from a group, leave the group instead");
var i = group.numbers.indexOf(number);
if (i > -1) {
group.numbers.slice(i, 1);
delete group.numberRegistrationIds[number];
textsecure.storage.put("group" + groupId, group);
}
return group.numbers; var i = group.numbers.indexOf(number);
if (i > -1) {
group.numbers.slice(i, 1);
delete group.numberRegistrationIds[number];
textsecure.storage.put("group" + groupId, group);
}
return group.numbers;
}());
}, },
addNumbers: function(groupId, numbers) { addNumbers: function(groupId, numbers) {
var group = textsecure.storage.get("group" + groupId); return Promise.resolve(function() {
if (group === undefined) var group = textsecure.storage.get("group" + groupId);
return undefined; if (group === undefined)
return undefined;
for (var i in numbers) {
var number = numbers[i]; for (var i in numbers) {
if (!textsecure.utils.isNumberSane(number)) var number = numbers[i];
throw new Error("Invalid number in set to add to group"); if (!textsecure.utils.isNumberSane(number))
if (group.numbers.indexOf(number) < 0) { throw new Error("Invalid number in set to add to group");
group.numbers.push(number); if (group.numbers.indexOf(number) < 0) {
group.numberRegistrationIds[number] = {}; group.numbers.push(number);
group.numberRegistrationIds[number] = {};
}
} }
}
textsecure.storage.put("group" + groupId, group); textsecure.storage.put("group" + groupId, group);
return group.numbers; return group.numbers;
}());
}, },
deleteGroup: function(groupId) { deleteGroup: function(groupId) {
textsecure.storage.remove("group" + groupId); return Promise.resolve(function() {
textsecure.storage.remove("group" + groupId);
}());
}, },
getGroup: function(groupId) { getGroup: function(groupId) {
var group = textsecure.storage.get("group" + groupId); return Promise.resolve(function() {
if (group === undefined) var group = textsecure.storage.get("group" + groupId);
return undefined; if (group === undefined)
return undefined;
return { id: groupId, numbers: group.numbers }; //TODO: avatar/name tracking return { id: groupId, numbers: group.numbers }; //TODO: avatar/name tracking
}());
}, },
needUpdateByDeviceRegistrationId: function(groupId, number, encodedNumber, registrationId) { needUpdateByDeviceRegistrationId: function(groupId, number, encodedNumber, registrationId) {
var group = textsecure.storage.get("group" + groupId); return Promise.resolve(function() {
if (group === undefined) var group = textsecure.storage.get("group" + groupId);
throw new Error("Unknown group for device registration id"); if (group === undefined)
throw new Error("Unknown group for device registration id");
if (group.numberRegistrationIds[number] === undefined) if (group.numberRegistrationIds[number] === undefined)
throw new Error("Unknown number in group for device registration id"); throw new Error("Unknown number in group for device registration id");
if (group.numberRegistrationIds[number][encodedNumber] == registrationId) if (group.numberRegistrationIds[number][encodedNumber] == registrationId)
return false; return false;
var needUpdate = group.numberRegistrationIds[number][encodedNumber] !== undefined; var needUpdate = group.numberRegistrationIds[number][encodedNumber] !== undefined;
group.numberRegistrationIds[number][encodedNumber] = registrationId; group.numberRegistrationIds[number][encodedNumber] = registrationId;
textsecure.storage.put("group" + groupId, group); textsecure.storage.put("group" + groupId, group);
return needUpdate; return needUpdate;
}());
}, },
}; };
})(); })();

Loading…
Cancel
Save