|
|
|
@ -22,6 +22,7 @@ import { ECKeyPair } from './keypairs';
|
|
|
|
|
import { getOurNumber } from '../session/utils/User';
|
|
|
|
|
import { UserUtils } from '../session/utils';
|
|
|
|
|
import { ConversationModel } from '../../js/models/conversations';
|
|
|
|
|
import _ from 'lodash';
|
|
|
|
|
|
|
|
|
|
export async function handleClosedGroupControlMessage(
|
|
|
|
|
envelope: EnvelopePlus,
|
|
|
|
@ -40,7 +41,13 @@ export async function handleClosedGroupControlMessage(
|
|
|
|
|
await handleClosedGroupEncryptionKeyPair(envelope, groupUpdate);
|
|
|
|
|
} else if (type === Type.NEW) {
|
|
|
|
|
await handleNewClosedGroup(envelope, groupUpdate);
|
|
|
|
|
} else if (type === Type.NAME_CHANGE || type === Type.MEMBERS_REMOVED || type === Type.MEMBERS_ADDED || type === Type.MEMBER_LEFT || type === Type.UPDATE) {
|
|
|
|
|
} else if (
|
|
|
|
|
type === Type.NAME_CHANGE ||
|
|
|
|
|
type === Type.MEMBERS_REMOVED ||
|
|
|
|
|
type === Type.MEMBERS_ADDED ||
|
|
|
|
|
type === Type.MEMBER_LEFT ||
|
|
|
|
|
type === Type.UPDATE
|
|
|
|
|
) {
|
|
|
|
|
await performIfValid(envelope, groupUpdate);
|
|
|
|
|
} else {
|
|
|
|
|
window.log.error('Unknown group update type: ', type);
|
|
|
|
@ -230,7 +237,6 @@ async function handleUpdateClosedGroup(
|
|
|
|
|
|
|
|
|
|
const curAdmins = convo.get('groupAdmins');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// NOTE: admins cannot change with closed groups
|
|
|
|
|
const members = membersBinary.map(toHex);
|
|
|
|
|
const diff = ClosedGroup.buildGroupDiff(convo, { name, members });
|
|
|
|
@ -291,6 +297,7 @@ async function handleUpdateClosedGroup(
|
|
|
|
|
convo.set('members', members);
|
|
|
|
|
|
|
|
|
|
await convo.commit();
|
|
|
|
|
convo.updateLastMessage();
|
|
|
|
|
|
|
|
|
|
await removeFromCache(envelope);
|
|
|
|
|
}
|
|
|
|
@ -310,6 +317,9 @@ async function handleClosedGroupEncryptionKeyPair(
|
|
|
|
|
) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
window.log.info(
|
|
|
|
|
`Got a group update for group ${envelope.source}, type: ENCRYPTION_KEY_PAIR`
|
|
|
|
|
);
|
|
|
|
|
const ourNumber = await UserUtils.getOurNumber();
|
|
|
|
|
const groupPublicKey = envelope.source;
|
|
|
|
|
const ourKeyPair = await UserUtils.getIdentityKeyPair();
|
|
|
|
@ -410,9 +420,10 @@ async function handleClosedGroupEncryptionKeyPair(
|
|
|
|
|
await removeFromCache(envelope);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function performIfValid(envelope: EnvelopePlus,
|
|
|
|
|
groupUpdate: SignalService.DataMessage.ClosedGroupControlMessage) {
|
|
|
|
|
async function performIfValid(
|
|
|
|
|
envelope: EnvelopePlus,
|
|
|
|
|
groupUpdate: SignalService.DataMessage.ClosedGroupControlMessage
|
|
|
|
|
) {
|
|
|
|
|
const { Type } = SignalService.DataMessage.ClosedGroupControlMessage;
|
|
|
|
|
|
|
|
|
|
const groupPublicKey = envelope.source;
|
|
|
|
@ -470,7 +481,6 @@ async function performIfValid(envelope: EnvelopePlus,
|
|
|
|
|
await handleClosedGroupMemberLeft(envelope, groupUpdate, convo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -481,18 +491,20 @@ async function handleClosedGroupNameChanged(
|
|
|
|
|
) {
|
|
|
|
|
// Only add update message if we have something to show
|
|
|
|
|
const newName = groupUpdate.name;
|
|
|
|
|
if (
|
|
|
|
|
newName !== convo.get(('name'))
|
|
|
|
|
) {
|
|
|
|
|
window.log.info(
|
|
|
|
|
`Got a group update for group ${envelope.source}, type: NAME_CHANGED`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (newName !== convo.get('name')) {
|
|
|
|
|
const groupDiff: ClosedGroup.GroupDiff = {
|
|
|
|
|
newName,
|
|
|
|
|
};
|
|
|
|
|
await ClosedGroup.addUpdateMessage(convo, groupDiff, 'incoming');
|
|
|
|
|
convo.set({ name: newName });
|
|
|
|
|
convo.updateLastMessage();
|
|
|
|
|
await convo.commit();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await removeFromCache(envelope);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -504,11 +516,18 @@ async function handleClosedGroupMembersAdded(
|
|
|
|
|
const { members: addedMembersBinary } = groupUpdate;
|
|
|
|
|
const addedMembers = (addedMembersBinary || []).map(toHex);
|
|
|
|
|
const oldMembers = convo.get('members') || [];
|
|
|
|
|
const membersNotAlreadyPresent = addedMembers.filter(m => !oldMembers.includes(m));
|
|
|
|
|
const membersNotAlreadyPresent = addedMembers.filter(
|
|
|
|
|
m => !oldMembers.includes(m)
|
|
|
|
|
);
|
|
|
|
|
console.warn('membersNotAlreadyPresent', membersNotAlreadyPresent);
|
|
|
|
|
window.log.info(
|
|
|
|
|
`Got a group update for group ${envelope.source}, type: MEMBERS_ADDED`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (membersNotAlreadyPresent.length === 0) {
|
|
|
|
|
window.log.info('no new members in this group update compared to what we have already. Skipping update');
|
|
|
|
|
window.log.info(
|
|
|
|
|
'no new members in this group update compared to what we have already. Skipping update'
|
|
|
|
|
);
|
|
|
|
|
await removeFromCache(envelope);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
@ -522,6 +541,7 @@ async function handleClosedGroupMembersAdded(
|
|
|
|
|
await ClosedGroup.addUpdateMessage(convo, groupDiff, 'incoming');
|
|
|
|
|
|
|
|
|
|
convo.set({ members });
|
|
|
|
|
convo.updateLastMessage();
|
|
|
|
|
await convo.commit();
|
|
|
|
|
await removeFromCache(envelope);
|
|
|
|
|
}
|
|
|
|
@ -529,8 +549,77 @@ async function handleClosedGroupMembersAdded(
|
|
|
|
|
async function handleClosedGroupMembersRemoved(
|
|
|
|
|
envelope: EnvelopePlus,
|
|
|
|
|
groupUpdate: SignalService.DataMessage.ClosedGroupControlMessage,
|
|
|
|
|
convo: ConversationModel) {
|
|
|
|
|
convo: ConversationModel
|
|
|
|
|
) {
|
|
|
|
|
// Check that the admin wasn't removed
|
|
|
|
|
const currentMembers = convo.get('members');
|
|
|
|
|
// removedMembers are all members in the diff
|
|
|
|
|
const removedMembers = groupUpdate.members.map(toHex);
|
|
|
|
|
// effectivelyRemovedMembers are the members which where effectively on this group before the update
|
|
|
|
|
// and is used for the group update message only
|
|
|
|
|
const effectivelyRemovedMembers = removedMembers.filter(m =>
|
|
|
|
|
currentMembers.includes(m)
|
|
|
|
|
);
|
|
|
|
|
const groupPubKey = envelope.source;
|
|
|
|
|
window.log.info(
|
|
|
|
|
`Got a group update for group ${envelope.source}, type: MEMBERS_REMOVED`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const membersAfterUpdate = _.difference(currentMembers, removedMembers);
|
|
|
|
|
const groupAdmins = convo.get('groupAdmins');
|
|
|
|
|
if (!groupAdmins?.length) {
|
|
|
|
|
throw new Error('No admins found for closed group member removed update.');
|
|
|
|
|
}
|
|
|
|
|
const firstAdmin = groupAdmins[0];
|
|
|
|
|
|
|
|
|
|
if (removedMembers.includes(firstAdmin)) {
|
|
|
|
|
window.log.warn(
|
|
|
|
|
'Ignoring invalid closed group update: trying to remove the admin.'
|
|
|
|
|
);
|
|
|
|
|
await removeFromCache(envelope);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the current user was removed:
|
|
|
|
|
// • Stop polling for the group
|
|
|
|
|
// • Remove the key pairs associated with the group
|
|
|
|
|
const ourPubKey = await UserUtils.getOurNumber();
|
|
|
|
|
const wasCurrentUserRemoved = !membersAfterUpdate.includes(ourPubKey.key);
|
|
|
|
|
if (wasCurrentUserRemoved) {
|
|
|
|
|
await window.Signal.Data.removeAllClosedGroupEncryptionKeyPairs(
|
|
|
|
|
groupPubKey
|
|
|
|
|
);
|
|
|
|
|
// Disable typing:
|
|
|
|
|
convo.set('isKickedFromGroup', true);
|
|
|
|
|
window.SwarmPolling.removePubkey(groupPubKey);
|
|
|
|
|
}
|
|
|
|
|
// Generate and distribute a new encryption key pair if needed
|
|
|
|
|
const isCurrentUserAdmin = firstAdmin === ourPubKey.key;
|
|
|
|
|
if (isCurrentUserAdmin) {
|
|
|
|
|
try {
|
|
|
|
|
await ClosedGroup.generateAndSendNewEncryptionKeyPair(
|
|
|
|
|
groupPubKey,
|
|
|
|
|
membersAfterUpdate
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
window.log.warn('Could not distribute new encryption keypair.');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Only add update message if we have something to show
|
|
|
|
|
if (membersAfterUpdate.length !== currentMembers.length) {
|
|
|
|
|
const groupDiff: ClosedGroup.GroupDiff = {
|
|
|
|
|
leavingMembers: effectivelyRemovedMembers,
|
|
|
|
|
};
|
|
|
|
|
await ClosedGroup.addUpdateMessage(convo, groupDiff, 'incoming');
|
|
|
|
|
convo.updateLastMessage();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the group
|
|
|
|
|
convo.set({ members: membersAfterUpdate });
|
|
|
|
|
|
|
|
|
|
await convo.commit();
|
|
|
|
|
await removeFromCache(envelope);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function handleClosedGroupMemberLeft(
|
|
|
|
@ -545,7 +634,7 @@ async function handleClosedGroupMemberLeft(
|
|
|
|
|
// otherwise, we remove the sender from the list of current members in this group
|
|
|
|
|
const oldMembers = convo.get('members') || [];
|
|
|
|
|
const leftMemberWasPresent = oldMembers.includes(sender);
|
|
|
|
|
const members = didAdminLeave ? [] : (oldMembers).filter(s => s !== sender);
|
|
|
|
|
const members = didAdminLeave ? [] : oldMembers.filter(s => s !== sender);
|
|
|
|
|
// Guard against self-sends
|
|
|
|
|
const ourPubkey = await UserUtils.getCurrentDevicePubKey();
|
|
|
|
|
if (!ourPubkey) {
|
|
|
|
@ -558,9 +647,13 @@ async function handleClosedGroupMemberLeft(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate and distribute a new encryption key pair if needed
|
|
|
|
|
const isCurrentUserAdmin = convo.get('groupAdmins')?.includes(ourPubkey) || false;
|
|
|
|
|
const isCurrentUserAdmin =
|
|
|
|
|
convo.get('groupAdmins')?.includes(ourPubkey) || false;
|
|
|
|
|
if (isCurrentUserAdmin && !!members.length) {
|
|
|
|
|
await ClosedGroup.generateAndSendNewEncryptionKeyPair(groupPublicKey, members);
|
|
|
|
|
await ClosedGroup.generateAndSendNewEncryptionKeyPair(
|
|
|
|
|
groupPublicKey,
|
|
|
|
|
members
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (didAdminLeave) {
|
|
|
|
@ -571,16 +664,14 @@ async function handleClosedGroupMemberLeft(
|
|
|
|
|
convo.set('isKickedFromGroup', true);
|
|
|
|
|
window.SwarmPolling.removePubkey(groupPublicKey);
|
|
|
|
|
}
|
|
|
|
|
// Update the group
|
|
|
|
|
|
|
|
|
|
// Only add update message if we have something to show
|
|
|
|
|
if (
|
|
|
|
|
leftMemberWasPresent
|
|
|
|
|
) {
|
|
|
|
|
if (leftMemberWasPresent) {
|
|
|
|
|
const groupDiff: ClosedGroup.GroupDiff = {
|
|
|
|
|
leavingMembers: didAdminLeave ? oldMembers : [sender],
|
|
|
|
|
};
|
|
|
|
|
await ClosedGroup.addUpdateMessage(convo, groupDiff, 'incoming');
|
|
|
|
|
convo.updateLastMessage();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
convo.set('members', members);
|
|
|
|
@ -590,7 +681,6 @@ async function handleClosedGroupMemberLeft(
|
|
|
|
|
await removeFromCache(envelope);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function createClosedGroup(
|
|
|
|
|
groupName: string,
|
|
|
|
|
members: Array<string>
|
|
|
|
@ -644,7 +734,9 @@ export async function createClosedGroup(
|
|
|
|
|
// the sending pipeline needs to know from GroupUtils when a message is for a medium group
|
|
|
|
|
await ClosedGroup.updateOrCreateClosedGroup(groupDetails);
|
|
|
|
|
convo.set('lastJoinedTimestamp', Date.now());
|
|
|
|
|
convo.set('active_at', Date.now());
|
|
|
|
|
await convo.commit();
|
|
|
|
|
convo.updateLastMessage();
|
|
|
|
|
|
|
|
|
|
// Send a closed group update message to all members individually
|
|
|
|
|
const promises = listOfMembers.map(async m => {
|
|
|
|
|