You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
530 lines
16 KiB
TypeScript
530 lines
16 KiB
TypeScript
4 years ago
|
import { PubKey } from '../types';
|
||
4 years ago
|
|
||
4 years ago
|
import _ from 'lodash';
|
||
|
|
||
4 years ago
|
import { fromHexToArray, toHex } from '../utils/String';
|
||
4 years ago
|
import { BlockedNumberController } from '../../util/blockedNumberController';
|
||
4 years ago
|
import { getConversationController } from '../conversations';
|
||
3 years ago
|
import { getLatestClosedGroupEncryptionKeyPair } from '../../data/data';
|
||
4 years ago
|
import uuid from 'uuid';
|
||
|
import { SignalService } from '../../protobuf';
|
||
|
import { generateCurve25519KeyPairWithoutPrefix } from '../crypto';
|
||
|
import { encryptUsingSessionProtocol } from '../crypto/MessageEncrypter';
|
||
4 years ago
|
import { ECKeyPair } from '../../receiver/keypairs';
|
||
|
import { UserUtils } from '../utils';
|
||
4 years ago
|
import { ClosedGroupMemberLeftMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupMemberLeftMessage';
|
||
4 years ago
|
import { ConversationModel, ConversationTypeEnum } from '../../models/conversation';
|
||
4 years ago
|
import { MessageModel } from '../../models/message';
|
||
|
import { MessageModelType } from '../../models/messageType';
|
||
4 years ago
|
import {
|
||
4 years ago
|
addKeyPairToCacheAndDBIfNeeded,
|
||
4 years ago
|
distributingClosedGroupEncryptionKeyPairs,
|
||
|
markGroupAsLeftOrKicked,
|
||
|
} from '../../receiver/closedGroups';
|
||
4 years ago
|
import { getMessageQueue } from '..';
|
||
4 years ago
|
import { ClosedGroupAddedMembersMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupAddedMembersMessage';
|
||
|
import { ClosedGroupEncryptionPairMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupEncryptionPairMessage';
|
||
|
import { ClosedGroupNameChangeMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupNameChangeMessage';
|
||
|
import { ClosedGroupNewMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupNewMessage';
|
||
|
import { ClosedGroupRemovedMembersMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupRemovedMembersMessage';
|
||
3 years ago
|
import { getSwarmPollingInstance } from '../apis/snode_api';
|
||
|
import { getLatestTimestampOffset } from '../apis/snode_api/SNodeAPI';
|
||
4 years ago
|
|
||
4 years ago
|
export type GroupInfo = {
|
||
4 years ago
|
id: string;
|
||
|
name: string;
|
||
4 years ago
|
members: Array<string>;
|
||
|
zombies?: Array<string>;
|
||
4 years ago
|
activeAt?: number;
|
||
4 years ago
|
expireTimer?: number | null;
|
||
|
avatar?: any;
|
||
|
color?: any; // what is this???
|
||
|
blocked?: boolean;
|
||
|
admins?: Array<string>;
|
||
|
secretKey?: Uint8Array;
|
||
4 years ago
|
weWereJustAdded?: boolean;
|
||
4 years ago
|
};
|
||
4 years ago
|
|
||
|
export interface GroupDiff extends MemberChanges {
|
||
|
newName?: string;
|
||
|
}
|
||
|
|
||
|
export interface MemberChanges {
|
||
|
joiningMembers?: Array<string>;
|
||
|
leavingMembers?: Array<string>;
|
||
3 years ago
|
kickedMembers?: Array<string>;
|
||
4 years ago
|
}
|
||
|
|
||
3 years ago
|
/**
|
||
|
* This function is only called when the local user makes a change to a group.
|
||
|
* So this function is not called on group updates from the network, even from another of our devices.
|
||
|
*
|
||
|
* @param groupId the conversationID
|
||
|
* @param groupName the new name (or just pass the old one if nothing changed)
|
||
|
* @param members the new members (or just pass the old one if nothing changed)
|
||
|
* @param avatar the new avatar (or just pass the old one if nothing changed)
|
||
|
* @returns nothing
|
||
|
*/
|
||
3 years ago
|
export async function initiateClosedGroupUpdate(
|
||
4 years ago
|
groupId: string,
|
||
|
groupName: string,
|
||
3 years ago
|
members: Array<string>
|
||
4 years ago
|
) {
|
||
4 years ago
|
const convo = await getConversationController().getOrCreateAndWait(
|
||
4 years ago
|
groupId,
|
||
4 years ago
|
ConversationTypeEnum.GROUP
|
||
4 years ago
|
);
|
||
|
|
||
3 years ago
|
if (!convo.isMediumGroup()) {
|
||
4 years ago
|
throw new Error('Legacy group are not supported anymore.');
|
||
|
}
|
||
|
|
||
4 years ago
|
// do not give an admins field here. We don't want to be able to update admins and
|
||
|
// updateOrCreateClosedGroup() will update them if given the choice.
|
||
4 years ago
|
const groupDetails: GroupInfo = {
|
||
4 years ago
|
id: groupId,
|
||
|
name: groupName,
|
||
|
members,
|
||
4 years ago
|
// remove from the zombies list the zombies not which are not in the group anymore
|
||
4 years ago
|
zombies: convo.get('zombies')?.filter(z => members.includes(z)),
|
||
4 years ago
|
activeAt: Date.now(),
|
||
4 years ago
|
expireTimer: convo.get('expireTimer'),
|
||
3 years ago
|
avatar: null,
|
||
4 years ago
|
};
|
||
|
|
||
|
const diff = buildGroupDiff(convo, groupDetails);
|
||
|
|
||
4 years ago
|
await updateOrCreateClosedGroup(groupDetails);
|
||
4 years ago
|
|
||
|
const updateObj: GroupInfo = {
|
||
|
id: groupId,
|
||
|
name: groupName,
|
||
|
members,
|
||
|
admins: convo.get('groupAdmins'),
|
||
|
expireTimer: convo.get('expireTimer'),
|
||
|
};
|
||
|
|
||
4 years ago
|
if (diff.newName?.length) {
|
||
3 years ago
|
const nameOnlyDiff: GroupDiff = _.pick(diff, 'newName');
|
||
|
|
||
4 years ago
|
const dbMessageName = await addUpdateMessage(convo, nameOnlyDiff, 'outgoing', Date.now());
|
||
4 years ago
|
await sendNewName(convo, diff.newName, dbMessageName.id as string);
|
||
4 years ago
|
}
|
||
|
|
||
|
if (diff.joiningMembers?.length) {
|
||
3 years ago
|
const joiningOnlyDiff: GroupDiff = _.pick(diff, 'joiningMembers');
|
||
|
|
||
4 years ago
|
const dbMessageAdded = await addUpdateMessage(convo, joiningOnlyDiff, 'outgoing', Date.now());
|
||
4 years ago
|
await sendAddedMembers(convo, diff.joiningMembers, dbMessageAdded.id as string, updateObj);
|
||
4 years ago
|
}
|
||
4 years ago
|
|
||
4 years ago
|
if (diff.leavingMembers?.length) {
|
||
3 years ago
|
const leavingOnlyDiff: GroupDiff = { kickedMembers: diff.leavingMembers };
|
||
4 years ago
|
const dbMessageLeaving = await addUpdateMessage(convo, leavingOnlyDiff, 'outgoing', Date.now());
|
||
4 years ago
|
const stillMembers = members;
|
||
4 years ago
|
await sendRemovedMembers(
|
||
|
convo,
|
||
|
diff.leavingMembers,
|
||
|
stillMembers,
|
||
|
dbMessageLeaving.id as string
|
||
|
);
|
||
4 years ago
|
}
|
||
4 years ago
|
await convo.commit();
|
||
4 years ago
|
}
|
||
|
|
||
|
export async function addUpdateMessage(
|
||
|
convo: ConversationModel,
|
||
|
diff: GroupDiff,
|
||
4 years ago
|
type: MessageModelType,
|
||
|
sentAt: number
|
||
4 years ago
|
): Promise<MessageModel> {
|
||
|
const groupUpdate: any = {};
|
||
|
|
||
|
if (diff.newName) {
|
||
|
groupUpdate.name = diff.newName;
|
||
|
}
|
||
|
|
||
|
if (diff.joiningMembers) {
|
||
|
groupUpdate.joined = diff.joiningMembers;
|
||
|
}
|
||
|
|
||
|
if (diff.leavingMembers) {
|
||
|
groupUpdate.left = diff.leavingMembers;
|
||
|
}
|
||
|
|
||
3 years ago
|
if (diff.kickedMembers) {
|
||
|
groupUpdate.kicked = diff.kickedMembers;
|
||
|
}
|
||
|
|
||
4 years ago
|
const now = Date.now();
|
||
|
|
||
4 years ago
|
const unread = type === 'incoming';
|
||
4 years ago
|
|
||
4 years ago
|
const source = UserUtils.getOurPubKeyStrFromCache();
|
||
|
|
||
4 years ago
|
const message = await convo.addSingleMessage({
|
||
4 years ago
|
conversationId: convo.get('id'),
|
||
4 years ago
|
source,
|
||
4 years ago
|
type,
|
||
4 years ago
|
sent_at: sentAt,
|
||
4 years ago
|
received_at: now,
|
||
|
group_update: groupUpdate,
|
||
4 years ago
|
unread: unread ? 1 : 0,
|
||
4 years ago
|
expireTimer: 0,
|
||
4 years ago
|
});
|
||
|
|
||
4 years ago
|
if (unread) {
|
||
4 years ago
|
// update the unreadCount for this convo
|
||
|
const unreadCount = await convo.getUnreadCount();
|
||
|
convo.set({
|
||
|
unreadCount,
|
||
|
});
|
||
|
await convo.commit();
|
||
|
}
|
||
|
|
||
|
return message;
|
||
|
}
|
||
|
|
||
4 years ago
|
function buildGroupDiff(convo: ConversationModel, update: GroupInfo): GroupDiff {
|
||
4 years ago
|
const groupDiff: GroupDiff = {};
|
||
|
|
||
|
if (convo.get('name') !== update.name) {
|
||
|
groupDiff.newName = update.name;
|
||
|
}
|
||
|
|
||
|
const oldMembers = convo.get('members');
|
||
4 years ago
|
const oldZombies = convo.get('zombies');
|
||
|
const oldMembersWithZombies = _.uniq(oldMembers.concat(oldZombies));
|
||
|
|
||
|
const newMembersWithZombiesLeft = _.uniq(update.members.concat(update.zombies || []));
|
||
4 years ago
|
|
||
4 years ago
|
const addedMembers = _.difference(newMembersWithZombiesLeft, oldMembersWithZombies);
|
||
4 years ago
|
if (addedMembers.length > 0) {
|
||
|
groupDiff.joiningMembers = addedMembers;
|
||
|
}
|
||
|
// Check if anyone got kicked:
|
||
4 years ago
|
const removedMembers = _.difference(oldMembersWithZombies, newMembersWithZombiesLeft);
|
||
4 years ago
|
if (removedMembers.length > 0) {
|
||
|
groupDiff.leavingMembers = removedMembers;
|
||
|
}
|
||
|
|
||
|
return groupDiff;
|
||
|
}
|
||
|
|
||
4 years ago
|
export async function updateOrCreateClosedGroup(details: GroupInfo) {
|
||
4 years ago
|
const { id, weWereJustAdded } = details;
|
||
4 years ago
|
|
||
4 years ago
|
const conversation = await getConversationController().getOrCreateAndWait(
|
||
4 years ago
|
id,
|
||
4 years ago
|
ConversationTypeEnum.GROUP
|
||
4 years ago
|
);
|
||
|
|
||
|
const updates: any = {
|
||
|
name: details.name,
|
||
|
members: details.members,
|
||
|
type: 'group',
|
||
|
is_medium_group: true,
|
||
|
};
|
||
|
|
||
4 years ago
|
if (details.activeAt) {
|
||
|
updates.active_at = details.activeAt;
|
||
|
updates.timestamp = updates.active_at;
|
||
4 years ago
|
|
||
|
updates.left = false;
|
||
4 years ago
|
updates.lastJoinedTimestamp = weWereJustAdded ? Date.now() : updates.active_at;
|
||
4 years ago
|
} else {
|
||
|
updates.left = true;
|
||
|
}
|
||
|
|
||
4 years ago
|
if (details.zombies) {
|
||
|
updates.zombies = details.zombies;
|
||
|
}
|
||
|
|
||
4 years ago
|
conversation.set(updates);
|
||
|
|
||
|
const isBlocked = details.blocked || false;
|
||
|
if (conversation.isClosedGroup() || conversation.isMediumGroup()) {
|
||
4 years ago
|
await BlockedNumberController.setGroupBlocked(conversation.id as string, isBlocked);
|
||
4 years ago
|
}
|
||
|
|
||
|
if (details.admins?.length) {
|
||
|
await conversation.updateGroupAdmins(details.admins);
|
||
|
}
|
||
|
|
||
|
await conversation.commit();
|
||
|
|
||
|
const { expireTimer } = details;
|
||
|
|
||
|
if (expireTimer === undefined || typeof expireTimer !== 'number') {
|
||
|
return;
|
||
|
}
|
||
4 years ago
|
await conversation.updateExpireTimer(
|
||
4 years ago
|
expireTimer,
|
||
|
UserUtils.getOurPubKeyStrFromCache(),
|
||
|
Date.now(),
|
||
|
{
|
||
|
fromSync: true,
|
||
|
}
|
||
|
);
|
||
4 years ago
|
}
|
||
|
|
||
4 years ago
|
export async function leaveClosedGroup(groupId: string) {
|
||
4 years ago
|
const convo = getConversationController().get(groupId);
|
||
4 years ago
|
|
||
|
if (!convo) {
|
||
4 years ago
|
window?.log?.error('Cannot leave non-existing group');
|
||
4 years ago
|
return;
|
||
|
}
|
||
4 years ago
|
const ourNumber = UserUtils.getOurPubKeyFromCache();
|
||
4 years ago
|
const isCurrentUserAdmin = convo.get('groupAdmins')?.includes(ourNumber.key);
|
||
4 years ago
|
|
||
|
const now = Date.now();
|
||
|
let members: Array<string> = [];
|
||
4 years ago
|
let admins: Array<string> = [];
|
||
4 years ago
|
|
||
4 years ago
|
// if we are the admin, the group must be destroyed for every members
|
||
4 years ago
|
if (isCurrentUserAdmin) {
|
||
4 years ago
|
window?.log?.info('Admin left a closed group. We need to destroy it');
|
||
4 years ago
|
convo.set({ left: true });
|
||
4 years ago
|
members = [];
|
||
4 years ago
|
admins = [];
|
||
4 years ago
|
} else {
|
||
4 years ago
|
// otherwise, just the exclude ourself from the members and trigger an update with this
|
||
4 years ago
|
convo.set({ left: true });
|
||
4 years ago
|
members = (convo.get('members') || []).filter((m: string) => m !== ourNumber.key);
|
||
4 years ago
|
admins = convo.get('groupAdmins') || [];
|
||
4 years ago
|
}
|
||
|
convo.set({ members });
|
||
4 years ago
|
convo.set({ groupAdmins: admins });
|
||
4 years ago
|
await convo.commit();
|
||
|
|
||
4 years ago
|
const source = UserUtils.getOurPubKeyStrFromCache();
|
||
4 years ago
|
const diffTimestamp = Date.now() - getLatestTimestampOffset();
|
||
4 years ago
|
|
||
4 years ago
|
const dbMessage = await convo.addSingleMessage({
|
||
3 years ago
|
group_update: { left: [source] },
|
||
4 years ago
|
conversationId: groupId,
|
||
4 years ago
|
source,
|
||
4 years ago
|
type: 'outgoing',
|
||
4 years ago
|
sent_at: diffTimestamp,
|
||
4 years ago
|
received_at: now,
|
||
4 years ago
|
expireTimer: 0,
|
||
4 years ago
|
});
|
||
4 years ago
|
// Send the update to the group
|
||
4 years ago
|
const ourLeavingMessage = new ClosedGroupMemberLeftMessage({
|
||
|
timestamp: Date.now(),
|
||
|
groupId,
|
||
4 years ago
|
identifier: dbMessage.id as string,
|
||
4 years ago
|
});
|
||
4 years ago
|
|
||
4 years ago
|
window?.log?.info(`We are leaving the group ${groupId}. Sending our leaving message.`);
|
||
4 years ago
|
// sent the message to the group and once done, remove everything related to this group
|
||
4 years ago
|
getSwarmPollingInstance().removePubkey(groupId);
|
||
4 years ago
|
await getMessageQueue().sendToGroup(ourLeavingMessage, async () => {
|
||
4 years ago
|
window?.log?.info(
|
||
|
`Leaving message sent ${groupId}. Removing everything related to this group.`
|
||
|
);
|
||
4 years ago
|
await markGroupAsLeftOrKicked(groupId, convo, false);
|
||
4 years ago
|
});
|
||
4 years ago
|
}
|
||
|
|
||
4 years ago
|
async function sendNewName(convo: ConversationModel, name: string, messageId: string) {
|
||
4 years ago
|
if (name.length === 0) {
|
||
4 years ago
|
window?.log?.warn('No name given for group update. Skipping');
|
||
4 years ago
|
return;
|
||
|
}
|
||
4 years ago
|
|
||
4 years ago
|
const groupId = convo.get('id');
|
||
|
|
||
|
// Send the update to the group
|
||
|
const nameChangeMessage = new ClosedGroupNameChangeMessage({
|
||
|
timestamp: Date.now(),
|
||
4 years ago
|
groupId: groupId as string,
|
||
4 years ago
|
identifier: messageId,
|
||
|
name,
|
||
|
});
|
||
|
await getMessageQueue().sendToGroup(nameChangeMessage);
|
||
|
}
|
||
|
|
||
|
async function sendAddedMembers(
|
||
|
convo: ConversationModel,
|
||
|
addedMembers: Array<string>,
|
||
|
messageId: string,
|
||
|
groupUpdate: GroupInfo
|
||
|
) {
|
||
|
if (!addedMembers?.length) {
|
||
4 years ago
|
window?.log?.warn('No addedMembers given for group update. Skipping');
|
||
4 years ago
|
return;
|
||
|
}
|
||
4 years ago
|
|
||
4 years ago
|
const { id: groupId, members, name: groupName } = groupUpdate;
|
||
4 years ago
|
const admins = groupUpdate.admins || [];
|
||
|
|
||
|
// Check preconditions
|
||
4 years ago
|
const hexEncryptionKeyPair = await getLatestClosedGroupEncryptionKeyPair(groupId);
|
||
4 years ago
|
if (!hexEncryptionKeyPair) {
|
||
|
throw new Error("Couldn't get key pair for closed group");
|
||
|
}
|
||
|
|
||
|
const encryptionKeyPair = ECKeyPair.fromHexKeyPair(hexEncryptionKeyPair);
|
||
4 years ago
|
const existingExpireTimer = convo.get('expireTimer') || 0;
|
||
4 years ago
|
// Send the Added Members message to the group (only members already in the group will get it)
|
||
|
const closedGroupControlMessage = new ClosedGroupAddedMembersMessage({
|
||
4 years ago
|
timestamp: Date.now(),
|
||
|
groupId,
|
||
4 years ago
|
addedMembers,
|
||
|
identifier: messageId,
|
||
|
});
|
||
|
await getMessageQueue().sendToGroup(closedGroupControlMessage);
|
||
|
|
||
|
// Send closed group update messages to any new members individually
|
||
|
const newClosedGroupUpdate = new ClosedGroupNewMessage({
|
||
|
timestamp: Date.now(),
|
||
4 years ago
|
name: groupName,
|
||
4 years ago
|
groupId,
|
||
|
admins,
|
||
4 years ago
|
members,
|
||
4 years ago
|
keypair: encryptionKeyPair,
|
||
4 years ago
|
identifier: messageId || uuid(),
|
||
4 years ago
|
expireTimer: existingExpireTimer,
|
||
4 years ago
|
});
|
||
|
|
||
|
const promises = addedMembers.map(async m => {
|
||
4 years ago
|
await getConversationController().getOrCreateAndWait(m, ConversationTypeEnum.PRIVATE);
|
||
4 years ago
|
const memberPubKey = PubKey.cast(m);
|
||
|
await getMessageQueue().sendToPubKey(memberPubKey, newClosedGroupUpdate);
|
||
4 years ago
|
});
|
||
4 years ago
|
await Promise.all(promises);
|
||
|
}
|
||
4 years ago
|
|
||
4 years ago
|
export async function sendRemovedMembers(
|
||
4 years ago
|
convo: ConversationModel,
|
||
|
removedMembers: Array<string>,
|
||
4 years ago
|
stillMembers: Array<string>,
|
||
|
messageId?: string
|
||
4 years ago
|
) {
|
||
|
if (!removedMembers?.length) {
|
||
4 years ago
|
window?.log?.warn('No removedMembers given for group update. Skipping');
|
||
4 years ago
|
return;
|
||
|
}
|
||
|
const ourNumber = UserUtils.getOurPubKeyFromCache();
|
||
|
const admins = convo.get('groupAdmins') || [];
|
||
4 years ago
|
const groupId = convo.get('id') as string;
|
||
4 years ago
|
|
||
|
const isCurrentUserAdmin = admins.includes(ourNumber.key);
|
||
|
const isUserLeaving = removedMembers.includes(ourNumber.key);
|
||
4 years ago
|
if (isUserLeaving) {
|
||
4 years ago
|
throw new Error('Cannot remove members and leave the group at the same time');
|
||
4 years ago
|
}
|
||
|
if (removedMembers.includes(admins[0]) && stillMembers.length !== 0) {
|
||
4 years ago
|
throw new Error("Can't remove admin from closed group without removing everyone.");
|
||
4 years ago
|
}
|
||
|
// Send the update to the group and generate + distribute a new encryption key pair if needed
|
||
|
const mainClosedGroupControlMessage = new ClosedGroupRemovedMembersMessage({
|
||
|
timestamp: Date.now(),
|
||
|
groupId,
|
||
|
removedMembers,
|
||
|
identifier: messageId,
|
||
|
});
|
||
|
// Send the group update, and only once sent, generate and distribute a new encryption key pair if needed
|
||
4 years ago
|
await getMessageQueue().sendToGroup(mainClosedGroupControlMessage, async () => {
|
||
|
if (isCurrentUserAdmin) {
|
||
|
// we send the new encryption key only to members already here before the update
|
||
4 years ago
|
window?.log?.info(
|
||
4 years ago
|
`Sending group update: A user was removed from ${groupId} and we are the admin. Generating and sending a new EncryptionKeyPair`
|
||
|
);
|
||
|
|
||
|
await generateAndSendNewEncryptionKeyPair(groupId, stillMembers);
|
||
4 years ago
|
}
|
||
4 years ago
|
});
|
||
4 years ago
|
}
|
||
|
|
||
4 years ago
|
async function generateAndSendNewEncryptionKeyPair(
|
||
4 years ago
|
groupPublicKey: string,
|
||
|
targetMembers: Array<string>
|
||
|
) {
|
||
4 years ago
|
const groupConvo = getConversationController().get(groupPublicKey);
|
||
4 years ago
|
const groupId = fromHexToArray(groupPublicKey);
|
||
|
|
||
|
if (!groupConvo) {
|
||
4 years ago
|
window?.log?.warn(
|
||
|
'generateAndSendNewEncryptionKeyPair: conversation not found',
|
||
|
groupPublicKey
|
||
|
);
|
||
4 years ago
|
return;
|
||
|
}
|
||
|
if (!groupConvo.isMediumGroup()) {
|
||
4 years ago
|
window?.log?.warn(
|
||
4 years ago
|
'generateAndSendNewEncryptionKeyPair: conversation not a closed group',
|
||
4 years ago
|
groupPublicKey
|
||
|
);
|
||
|
return;
|
||
|
}
|
||
|
|
||
4 years ago
|
const ourNumber = UserUtils.getOurPubKeyFromCache();
|
||
4 years ago
|
if (!groupConvo.get('groupAdmins')?.includes(ourNumber.key)) {
|
||
4 years ago
|
window?.log?.warn('generateAndSendNewEncryptionKeyPair: cannot send it as a non admin');
|
||
4 years ago
|
return;
|
||
|
}
|
||
|
|
||
|
// Generate the new encryption key pair
|
||
|
const newKeyPair = await generateCurve25519KeyPairWithoutPrefix();
|
||
|
|
||
|
if (!newKeyPair) {
|
||
4 years ago
|
window?.log?.warn('generateAndSendNewEncryptionKeyPair: failed to generate new keypair');
|
||
4 years ago
|
return;
|
||
|
}
|
||
4 years ago
|
// Distribute it
|
||
4 years ago
|
const wrappers = await buildEncryptionKeyPairWrappers(targetMembers, newKeyPair);
|
||
4 years ago
|
|
||
|
const keypairsMessage = new ClosedGroupEncryptionPairMessage({
|
||
|
groupId: toHex(groupId),
|
||
|
timestamp: Date.now(),
|
||
|
encryptedKeyPairs: wrappers,
|
||
|
});
|
||
|
|
||
4 years ago
|
distributingClosedGroupEncryptionKeyPairs.set(toHex(groupId), newKeyPair);
|
||
|
|
||
4 years ago
|
const messageSentCallback = async () => {
|
||
4 years ago
|
window?.log?.info(
|
||
4 years ago
|
`KeyPairMessage for ClosedGroup ${groupPublicKey} is sent. Saving the new encryptionKeyPair.`
|
||
|
);
|
||
|
|
||
4 years ago
|
distributingClosedGroupEncryptionKeyPairs.delete(toHex(groupId));
|
||
|
|
||
4 years ago
|
await addKeyPairToCacheAndDBIfNeeded(toHex(groupId), newKeyPair.toHexKeyPair());
|
||
4 years ago
|
};
|
||
|
// this is to be sent to the group pubkey adress
|
||
|
await getMessageQueue().sendToGroup(keypairsMessage, messageSentCallback);
|
||
|
}
|
||
|
|
||
|
export async function buildEncryptionKeyPairWrappers(
|
||
|
targetMembers: Array<string>,
|
||
|
encryptionKeyPair: ECKeyPair
|
||
|
) {
|
||
|
if (
|
||
|
!encryptionKeyPair ||
|
||
|
!encryptionKeyPair.publicKeyData.length ||
|
||
|
!encryptionKeyPair.privateKeyData.length
|
||
|
) {
|
||
4 years ago
|
throw new Error('buildEncryptionKeyPairWrappers() needs a valid encryptionKeyPair set');
|
||
4 years ago
|
}
|
||
|
|
||
4 years ago
|
const proto = new SignalService.KeyPair({
|
||
4 years ago
|
privateKey: encryptionKeyPair?.privateKeyData,
|
||
|
publicKey: encryptionKeyPair?.publicKeyData,
|
||
4 years ago
|
});
|
||
|
const plaintext = SignalService.KeyPair.encode(proto).finish();
|
||
4 years ago
|
|
||
|
const wrappers = await Promise.all(
|
||
|
targetMembers.map(async pubkey => {
|
||
4 years ago
|
const ciphertext = await encryptUsingSessionProtocol(PubKey.cast(pubkey), plaintext);
|
||
|
return new SignalService.DataMessage.ClosedGroupControlMessage.KeyPairWrapper({
|
||
|
encryptedKeyPair: ciphertext,
|
||
|
publicKey: fromHexToArray(pubkey),
|
||
|
});
|
||
4 years ago
|
})
|
||
|
);
|
||
4 years ago
|
return wrappers;
|
||
|
}
|