import { SignalService } from '../protobuf'; import { removeFromCache } from './cache'; import { EnvelopePlus } from './types'; import { MediumGroupResponseKeysMessage } from '../session/messages/outgoing'; import { getMessageQueue } from '../session'; import { PubKey } from '../session/types'; import _ from 'lodash'; async function handleSenderKeyRequest( envelope: EnvelopePlus, groupUpdate: any ) { const { SenderKeyAPI, StringView, textsecure, log } = window; const senderIdentity = envelope.source; const ourIdentity = await textsecure.storage.user.getNumber(); const { groupId } = groupUpdate; log.debug('[sender key] sender key request from:', senderIdentity); // We reuse the same message type for sender keys const { chainKey, keyIdx } = await SenderKeyAPI.getSenderKeys( groupId, ourIdentity ); const chainKeyHex = StringView.arrayBufferToHex(chainKey); const responseParams = { timestamp: Date.now(), groupId, chainKey: chainKeyHex, keyIdx, }; const keysResponseMessage = new MediumGroupResponseKeysMessage( responseParams ); const senderPubKey = new PubKey(senderIdentity); await getMessageQueue().send(senderPubKey, keysResponseMessage); await removeFromCache(envelope); } async function handleSenderKey(envelope: EnvelopePlus, groupUpdate: any) { const { SenderKeyAPI, log } = window; const { groupId, senderKey } = groupUpdate; const senderIdentity = envelope.source; log.debug('[sender key] got a new sender key from:', senderIdentity); await SenderKeyAPI.saveSenderKeys( groupId, senderIdentity, senderKey.chainKey, senderKey.keyIdx ); await removeFromCache(envelope); } async function handleNewGroup(envelope: EnvelopePlus, groupUpdate: any) { const { SenderKeyAPI, StringView, Whisper, log, textsecure } = window; const senderIdentity = envelope.source; const ourIdentity = await textsecure.storage.user.getNumber(); const { groupId, members: membersBinary, groupSecretKey, groupName, senderKey, admins, } = groupUpdate; const maybeConvo = await window.ConversationController.get(groupId); const groupExists = !!maybeConvo; const members = membersBinary.map((pk: any) => StringView.arrayBufferToHex(pk.toArrayBuffer()) ); const convo = groupExists ? maybeConvo : await window.ConversationController.getOrCreateAndWait(groupId, 'group'); { // Add group update message const now = Date.now(); const message = convo.messageCollection.add({ conversationId: convo.id, type: 'incoming', sent_at: now, received_at: now, group_update: { name: groupName, members, }, }); const messageId = await window.Signal.Data.saveMessage(message.attributes, { Message: Whisper.Message, }); message.set({ id: messageId }); } if (groupExists) { // ***** Updating the group ***** log.info('Received a group update for medium group:', groupId); // Check that the sender is admin (make sure it words with multidevice) const isAdmin = convo.get('groupAdmins').includes(senderIdentity); if (!isAdmin) { log.warn('Rejected attempt to update a group by non-admin'); await removeFromCache(envelope); return; } convo.set('name', groupName); convo.set('members', members); // TODO: check that we are still in the group (when we enable deleting members) convo.saveChangesToDB(); // Update other fields. Add a corresponding "update" message to the conversation } else { // ***** Creating a new group ***** log.info('Received a new medium group:', groupId); // TODO: Check that we are even a part of this group? convo.set('is_medium_group', true); convo.set('active_at', Date.now()); convo.set('name', groupName); convo.set('groupAdmins', admins); const secretKeyHex = StringView.arrayBufferToHex( groupSecretKey.toArrayBuffer() ); await window.Signal.Data.createOrUpdateIdentityKey({ id: groupId, secretKey: secretKeyHex, }); // Save sender's key await SenderKeyAPI.saveSenderKeys( groupId, envelope.source, senderKey.chainKey, senderKey.keyIdx ); const ownSenderKeyHex = await SenderKeyAPI.createSenderKeyForGroup( groupId, ourIdentity ); { // Send own key to every member const otherMembers = _.without(members, ourIdentity); // We reuse the same message type for sender keys const responseParams = { timestamp: Date.now(), groupId, chainKey: ownSenderKeyHex, keyIdx: 0, }; const keysResponseMessage = new MediumGroupResponseKeysMessage( responseParams ); // send our senderKey to every other member otherMembers.forEach((member: string) => { const memberPubKey = new PubKey(member); getMessageQueue() .sendUsingMultiDevice(memberPubKey, keysResponseMessage) .ignore(); }); } window.SwarmPolling.addGroupId(groupId); } await removeFromCache(envelope); } export async function handleMediumGroupUpdate( envelope: EnvelopePlus, groupUpdate: any ) { const { type } = groupUpdate; const { Type } = SignalService.MediumGroupUpdate; if (type === Type.SENDER_KEY_REQUEST) { await handleSenderKeyRequest(envelope, groupUpdate); } else if (type === Type.SENDER_KEY) { await handleSenderKey(envelope, groupUpdate); } else if (type === Type.NEW_GROUP) { await handleNewGroup(envelope, groupUpdate); } }