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.
session-desktop/ts/receiver/groupv2/handleGroupV2Message.ts

195 lines
6.2 KiB
TypeScript

import { PubkeyType, WithGroupPubkey } from 'libsession_util_nodejs';
import { isEmpty } from 'lodash';
import { ConversationTypeEnum } from '../../models/conversationAttributes';
import { HexString } from '../../node/hexStrings';
import { SignalService } from '../../protobuf';
import { getSwarmPollingInstance } from '../../session/apis/snode_api';
import { ConvoHub } from '../../session/conversations';
import { ClosedGroup } from '../../session/group/closed-group';
import { ed25519Str } from '../../session/onions/onionPath';
import { PubKey } from '../../session/types';
import { UserUtils } from '../../session/utils';
import { LibSessionUtil } from '../../session/utils/libsession/libsession_utils';
import { toFixedUint8ArrayOfLength } from '../../types/sqlSharedTypes';
import {
MetaGroupWrapperActions,
UserGroupsWrapperActions,
} from '../../webworker/workers/browser/libsession_worker_interface';
type WithEnvelopeTimestamp = { envelopeTimestamp: number };
type WithAuthor = { author: PubkeyType };
type WithUncheckedSource = { source: string };
type WithUncheckedSenderIdentity = { senderIdentity: string };
type GroupInviteDetails = {
inviteMessage: SignalService.GroupUpdateInviteMessage;
} & WithEnvelopeTimestamp &
WithAuthor;
type GroupMemberChangeDetails = {
memberChangeDetails: SignalService.GroupUpdateMemberChangeMessage;
} & WithEnvelopeTimestamp &
WithGroupPubkey &
WithAuthor;
type GroupUpdateDetails = {
updateMessage: SignalService.GroupUpdateMessage;
} & WithEnvelopeTimestamp;
async function handleGroupInviteMessage({
inviteMessage,
author,
envelopeTimestamp,
}: GroupInviteDetails) {
if (!PubKey.is03Pubkey(inviteMessage.groupSessionId)) {
// invite to a group which has not a 03 prefix, we can just drop it.
return;
}
window.log.debug(
`received invite to group ${ed25519Str(inviteMessage.groupSessionId)} by user:${ed25519Str(
author
)}`
);
// TODO verify sig invite adminSignature
const convo = await ConvoHub.use().getOrCreateAndWait(
inviteMessage.groupSessionId,
ConversationTypeEnum.GROUPV2
);
convo.set({
active_at: envelopeTimestamp,
didApproveMe: true,
});
if (inviteMessage.name && isEmpty(convo.getRealSessionUsername())) {
convo.set({
displayNameInProfile: inviteMessage.name,
});
}
await convo.commit();
let found = await UserGroupsWrapperActions.getGroup(inviteMessage.groupSessionId);
if (!found) {
found = {
authData: null,
joinedAtSeconds: Date.now(),
name: inviteMessage.name,
priority: 0,
pubkeyHex: inviteMessage.groupSessionId,
secretKey: null,
};
}
// not sure if we should drop it, or set it again? They should be the same anyway
found.authData = inviteMessage.memberAuthData;
const userEd25519Secretkey = (await UserUtils.getUserED25519KeyPairBytes()).privKeyBytes;
await UserGroupsWrapperActions.setGroup(found);
await MetaGroupWrapperActions.init(inviteMessage.groupSessionId, {
metaDumped: null,
groupEd25519Secretkey: null,
userEd25519Secretkey: toFixedUint8ArrayOfLength(userEd25519Secretkey, 64).buffer,
groupEd25519Pubkey: toFixedUint8ArrayOfLength(
HexString.fromHexString(inviteMessage.groupSessionId.slice(2)),
32
).buffer,
});
await LibSessionUtil.saveDumpsToDb(UserUtils.getOurPubKeyStrFromCache());
// TODO use the pending so we actually don't start polling here unless it is not in the pending state.
// once everything is ready, start polling using that authData to get the keys, members, details of that group, and its messages.
getSwarmPollingInstance().addGroupId(inviteMessage.groupSessionId);
}
async function handleGroupMemberChangeMessage({
memberChangeDetails,
groupPk,
envelopeTimestamp,
author,
}: GroupMemberChangeDetails) {
if (!PubKey.is03Pubkey(groupPk)) {
// invite to a group which has not a 03 prefix, we can just drop it.
return;
}
// TODO verify sig invite adminSignature
const convo = ConvoHub.use().get(groupPk);
if (!convo) {
return;
}
switch (memberChangeDetails.type) {
case SignalService.GroupUpdateMemberChangeMessage.Type.ADDED: {
await ClosedGroup.addUpdateMessage(
convo,
{ joiningMembers: memberChangeDetails.memberSessionIds },
author,
envelopeTimestamp
);
break;
}
case SignalService.GroupUpdateMemberChangeMessage.Type.REMOVED: {
await ClosedGroup.addUpdateMessage(
convo,
{ kickedMembers: memberChangeDetails.memberSessionIds },
author,
envelopeTimestamp
);
break;
}
case SignalService.GroupUpdateMemberChangeMessage.Type.PROMOTED: {
await ClosedGroup.addUpdateMessage(
convo,
{ promotedMembers: memberChangeDetails.memberSessionIds },
author,
envelopeTimestamp
);
break;
}
default:
return;
}
convo.set({
active_at: envelopeTimestamp,
didApproveMe: true,
});
}
async function handleGroupUpdateMessage(
details: GroupUpdateDetails & WithUncheckedSource & WithUncheckedSenderIdentity
) {
if (details.updateMessage.inviteMessage) {
// the invite message is received from our own swarm, so source is the sender, and senderIdentity is empty
const author = details.source;
if (!PubKey.is05Pubkey(author)) {
window.log.warn('received group inviteMessage with invalid author');
return;
}
await handleGroupInviteMessage({
inviteMessage: details.updateMessage.inviteMessage as SignalService.GroupUpdateInviteMessage,
...details,
author,
});
return;
}
// other messages are received from the groups swarm, so source is the groupPk, and senderIdentity is the author
const author = details.senderIdentity;
const groupPk = details.source;
if (!PubKey.is05Pubkey(author) || !PubKey.is03Pubkey(groupPk)) {
window.log.warn('received group update message with invalid author or groupPk');
return;
}
if (details.updateMessage.memberChangeMessage) {
await handleGroupMemberChangeMessage({
memberChangeDetails: details.updateMessage
.memberChangeMessage as SignalService.GroupUpdateMemberChangeMessage,
...details,
author,
groupPk,
});
return;
}
}
export const GroupV2Receiver = { handleGroupUpdateMessage };