diff --git a/js/sendmessage.js b/js/sendmessage.js index 25ebd713..842fac49 100644 --- a/js/sendmessage.js +++ b/js/sendmessage.js @@ -86,32 +86,39 @@ window.textsecure.messaging = function() { }); } - var sendGroupProto; var makeAttachmentPointer; - var refreshGroups = function(number) { - var groups = textsecure.storage.groups.getGroupListForNumber(number); - var promises = []; - for (var i in groups) { - var group = textsecure.storage.groups.getGroup(groups[i]); + var refreshGroup = function(number, groupId, devicesForNumber) { + groupId = getString(groupId); - var proto = new textsecure.protobuf.PushMessageContent(); - proto.group = new textsecure.protobuf.PushMessageContent.GroupContext(); - - proto.group.id = toArrayBuffer(group.id); - proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; - proto.group.members = group.numbers; - proto.group.name = group.name === undefined ? null : group.name; - - if (group.avatar !== undefined) { - return makeAttachmentPointer(group.avatar).then(function(attachment) { - proto.group.avatar = attachment; - promises.push(sendGroupProto([number], proto)); - }); - } else { - promises.push(sendGroupProto([number], proto)); - } + var doUpdate = false; + for (var i in devicesForNumber) { + if (textsecure.storage.groups.needUpdateByDeviceRegistrationId(groupId, number, devicesForNumber[i].encodedNumber, devicesForNumber[i].registrationId)) + doUpdate = true; + } + if (!doUpdate) + return Promise.resolve(true); + + var group = textsecure.storage.groups.getGroup(groupId); + var numberIndex = group.numbers.indexOf(number); + if (numberIndex < 0) // This is potentially a multi-message rare racing-AJAX race + return Promise.reject("Tried to refresh group to non-member"); + + var proto = new textsecure.protobuf.PushMessageContent(); + proto.group = new textsecure.protobuf.PushMessageContent.GroupContext(); + + proto.group.id = toArrayBuffer(group.id); + proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; + proto.group.members = group.numbers; + proto.group.name = group.name === undefined ? null : group.name; + + if (group.avatar !== undefined) { + return makeAttachmentPointer(group.avatar).then(function(attachment) { + proto.group.avatar = attachment; + return sendMessageToDevices(Date.now(), number, devicesForNumber, proto); + }); + } else { + return sendMessageToDevices(Date.now(), number, devicesForNumber, proto); } - return Promise.all(promises); } var tryMessageAgain = function(number, encodedMessage, message_id) { @@ -120,12 +127,10 @@ window.textsecure.messaging = function() { textsecure.storage.removeEncrypted("devices" + number); var proto = textsecure.protobuf.PushMessageContent.decode(encodedMessage, 'binary'); sendMessageProto(message.get('sent_at'), [number], proto, function(res) { - if (res.failure.length > 0) { + if (res.failure.length > 0) message.set('errors', res.failure); - } - else { + else message.set('errors', []); - } message.save().then(function(){ extension.trigger('message', message); // notify frontend listeners }); @@ -161,16 +166,19 @@ window.textsecure.messaging = function() { var devicesForNumber = textsecure.storage.devices.getDeviceObjectsForNumber(number); if (devicesForNumber.length == 0) return registerError(number, "Got empty device list when loading device keys", null); - refreshGroups(number).then(function() { - doSendMessage(number, devicesForNumber, recurse); - }); + doSendMessage(number, devicesForNumber, recurse); } } doSendMessage = function(number, devicesForNumber, recurse) { - return sendMessageToDevices(timestamp, number, devicesForNumber, message).then(function(result) { - successfulNumbers[successfulNumbers.length] = number; - numberCompleted(); + var groupUpdate = Promise.resolve(true); + if (message.group && message.group.id) + groupUpdate = refreshGroup(number, message.group.id, devicesForNumber); + return groupUpdate.then(function() { + return sendMessageToDevices(timestamp, number, devicesForNumber, message).then(function(result) { + successfulNumbers[successfulNumbers.length] = number; + numberCompleted(); + }); }).catch(function(error) { if (error instanceof Error && error.name == "HTTPError" && (error.message == 410 || error.message == 409)) { if (!recurse) @@ -243,7 +251,7 @@ window.textsecure.messaging = function() { }); } - sendGroupProto = function(numbers, proto, timestamp) { + var sendGroupProto = function(numbers, proto, timestamp) { timestamp = timestamp || Date.now(); var me = textsecure.utils.unencodeNumber(textsecure.storage.getUnencrypted("number_id"))[0]; numbers = numbers.filter(function(number) { return number != me; }); diff --git a/js/storage/groups.js b/js/storage/groups.js index 3de9c24b..e2f74514 100644 --- a/js/storage/groups.js +++ b/js/storage/groups.js @@ -24,18 +24,12 @@ window.textsecure.storage = window.textsecure.storage || {}; window.textsecure.storage.groups = { - getGroupListForNumber: function(number) { - return textsecure.storage.getEncrypted("groupMembership" + number, []); - }, - createNewGroup: function(numbers, groupId) { - if (groupId !== undefined && textsecure.storage.getEncrypted("group" + groupId) !== undefined) { + if (groupId !== undefined && textsecure.storage.getEncrypted("group" + groupId) !== undefined) throw new Error("Tried to recreate group"); - } - while (groupId === undefined || textsecure.storage.getEncrypted("group" + groupId) !== undefined) { + while (groupId === undefined || textsecure.storage.getEncrypted("group" + groupId) !== undefined) groupId = getString(textsecure.crypto.getRandomBytes(16)); - } var me = textsecure.utils.unencodeNumber(textsecure.storage.getUnencrypted("number_id"))[0]; var haveMe = false; @@ -44,16 +38,18 @@ var number = libphonenumber.util.verifyNumber(numbers[i]); if (number == me) haveMe = true; - if (finalNumbers.indexOf(number) < 0) { + if (finalNumbers.indexOf(number) < 0) finalNumbers.push(number); - addGroupToNumber(groupId, number); - } } if (!haveMe) finalNumbers.push(me); - textsecure.storage.putEncrypted("group" + groupId, {numbers: finalNumbers}); + var groupObject = {numbers: finalNumbers, numberRegistrationIds: {}}; + for (var i in finalNumbers) + groupObject.numberRegistrationIds[finalNumbers[i]] = {}; + + textsecure.storage.putEncrypted("group" + groupId, groupObject); return {id: groupId, numbers: finalNumbers}; }, @@ -84,8 +80,8 @@ var i = group.numbers.indexOf(number); if (i > -1) { group.numbers.slice(i, 1); + delete group.numberRegistrationIds[number]; textsecure.storage.putEncrypted("group" + groupId, group); - removeGroupFromNumber(groupId, number); } return group.numbers; @@ -100,7 +96,7 @@ var number = libphonenumber.util.verifyNumber(numbers[i]); if (group.numbers.indexOf(number) < 0) { group.numbers.push(number); - addGroupToNumber(groupId, number); + group.numberRegistrationIds[number] = {}; } } @@ -118,23 +114,24 @@ return undefined; return { id: groupId, numbers: group.numbers }; //TODO: avatar/name tracking - } + }, + + needUpdateByDeviceRegistrationId: function(groupId, number, encodedNumber, registrationId) { + var group = textsecure.storage.getEncrypted("group" + groupId); + if (group === undefined) + throw new Error("Unknown group for device registration id"); + + if (group.numberRegistrationIds[number] === undefined) + throw new Error("Unknown number in group for device registration id"); + + if (group.numberRegistrationIds[number][encodedNumber] == registrationId) + return false; + + var needUpdate = group.numberRegistrationIds[number][encodedNumber] !== undefined; + group.numberRegistrationIds[number][encodedNumber] = registrationId; + textsecure.storage.putEncrypted("group" + groupId, group); + return needUpdate; + }, }; - var addGroupToNumber = function(groupId, number) { - var membership = textsecure.storage.getEncrypted("groupMembership" + number, [groupId]); - if (membership.indexOf(groupId) < 0) - membership.push(groupId); - textsecure.storage.putEncrypted("groupMembership" + number, membership); - } - - var removeGroupFromNumber = function(groupId, number) { - var membership = textsecure.storage.getEncrypted("groupMembership" + number, [groupId]); - membership = membership.filter(function(group) { return group != groupId; }); - if (membership.length == 0) - textsecure.storage.removeEncrypted("groupMembership" + number); - else - textsecure.storage.putEncrypted("groupMembership" + number, membership); - } - })();