From 0a4e3041dea0a97217d915e3ca1f6a83d6328430 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 20 Feb 2024 09:59:30 +1100 Subject: [PATCH] fix: leave group v2 as only admin mark it as deleted and pushes to swarm before removing the wrapper data --- _locales/en/messages.json | 2 +- ts/interactions/conversationInteractions.ts | 10 ++-- ts/models/conversation.ts | 4 +- .../conversations/ConversationController.ts | 36 ++++++------- ts/state/ducks/metaGroups.ts | 50 ++++++++++++++----- ts/types/LocalizerKeys.ts | 2 +- 6 files changed, 62 insertions(+), 42 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 496f4f457..cafa1c320 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -342,7 +342,7 @@ "leaveAndRemoveForEveryone": "Leave Group and Remove for Everyone", "leaveGroupConfirmation": "Are you sure you want to leave $name$?", "leaveGroupConfirmationAdmin": "As you are the admin of this group, if you leave it it will be removed for every current members. Are you sure you want to leave this group?", - "leaveGroupConrirmationOnlyAdminLegacy": "Because you are the creator of this group it will be deleted for everyone. This cannot be undone.", + "leaveGroupConfirmationOnlyAdminLegacy": "Because you are the creator of this group it will be deleted for everyone. This cannot be undone.", "leaveGroupConfirmationOnlyAdmin": "You are the only admin in $name$", "leaveGroupConfirmationOnlyAdminWarning": "Group settings and members cannot be changed without an admin", "leaveGroupFailed": "Failed to leave Group!", diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts index 55ec30d0f..59cfa1c20 100644 --- a/ts/interactions/conversationInteractions.ts +++ b/ts/interactions/conversationInteractions.ts @@ -397,9 +397,9 @@ export async function showLeaveGroupByConvoId(conversationId: string, name: stri const isClosedGroup = conversation.isClosedGroup() || false; const isPublic = conversation.isPublic() || false; - const admins = conversation.get('groupAdmins') || []; + const admins = conversation.getGroupAdmins(); const isAdmin = admins.includes(UserUtils.getOurPubKeyStrFromCache()); - const showOnlyGroupAdminWarning = isClosedGroup && isAdmin && admins.length === 1; + const showOnlyGroupAdminWarning = isClosedGroup && isAdmin; const lastMessageInteractionType = conversation.get('lastMessageInteractionType'); const lastMessageInteractionStatus = conversation.get('lastMessageInteractionStatus'); @@ -432,7 +432,9 @@ export async function showLeaveGroupByConvoId(conversationId: string, name: stri window?.inboxStore?.dispatch( updateConfirmModal({ title: window.i18n('leaveGroup'), - message: window.i18n('leaveGroupConrirmationOnlyAdminLegacy', name ? [name] : ['']), + message: window.i18n('leaveGroupConfirmationOnlyAdminLegacy', [ + name || window.i18n('unknown'), + ]), onClickOk, okText: window.i18n('leave'), okTheme: SessionButtonColor.Danger, @@ -440,7 +442,7 @@ export async function showLeaveGroupByConvoId(conversationId: string, name: stri conversationId, }) ); - // TODO Only to be used after the closed group rebuild + // TODO AUDRIC this is chunk3 stuff: Only to be used after the closed group rebuild chunk3 // const onClickOkLastAdmin = () => { // /* TODO */ // }; diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index f05c7d034..6acb8f743 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -1401,7 +1401,7 @@ export class ConversationModel extends Backbone.Model { if (!pubKey) { throw new Error('isAdmin() pubKey is falsy'); } - const groupAdmins = getLibGroupAdminsOutsideRedux(this.id) || this.getGroupAdmins(); + const groupAdmins = this.getGroupAdmins(); return Array.isArray(groupAdmins) && groupAdmins.includes(pubKey); } @@ -1956,7 +1956,7 @@ export class ConversationModel extends Backbone.Model { } public getGroupAdmins(): Array { - const groupAdmins = this.get('groupAdmins'); + const groupAdmins = getLibGroupAdminsOutsideRedux(this.id) || this.get('groupAdmins'); return groupAdmins && groupAdmins.length > 0 ? groupAdmins : []; } diff --git a/ts/session/conversations/ConversationController.ts b/ts/session/conversations/ConversationController.ts index 9c767ff0b..218951c9d 100644 --- a/ts/session/conversations/ConversationController.ts +++ b/ts/session/conversations/ConversationController.ts @@ -224,15 +224,14 @@ class ConvoController { await leaveClosedGroup(groupId, fromSyncMessage); window.log.info(`deleteClosedGroup: ${groupId}, sendLeaveMessage?:${sendLeaveMessage}`); } - - // if we were kicked or sent our left message, we have nothing to do more with that group. - // Just delete everything related to it, not trying to add update message or send a left message. - await this.removeGroupOrCommunityFromDBAndRedux(groupId); if (PubKey.is03Pubkey(groupId)) { await remove03GroupFromWrappers(groupId); } else { await removeLegacyGroupFromWrappers(groupId); } + // if we were kicked or sent our left message, we have nothing to do more with that group. + // Just delete everything related to it, not trying to add update message or send a left message. + await this.removeGroupOrCommunityFromDBAndRedux(groupId); if (!fromSyncMessage) { await UserSync.queueNewJobIfNeeded(); @@ -456,6 +455,7 @@ class ConvoController { /** * You most likely don't want to call this function directly, but instead use the deleteLegacyGroup() from the ConversationController as it will take care of more cleaningup. + * This throws if a leaveMessage needs to be sent, but fails to be sent. * * Note: `fromSyncMessage` is used to know if we need to send a leave group message to the group first. * So if the user made the action on this device, fromSyncMessage should be false, but if it happened from a linked device polled update, set this to true. @@ -512,20 +512,17 @@ async function leaveClosedGroup(groupPk: PubkeyType | GroupPubkeyType, fromSyncM // We might not be able to send our leaving messages (no encryption keypair, we were already removed, no network, etc). // If that happens, we should just remove everything from our current user. - try { - const wasSent = await getMessageQueue().sendToGroupV2NonDurably({ - message: ourLeavingMessage, - }); - if (!wasSent) { - throw new Error( - `Even with the retries, leaving message for group ${ed25519Str( - groupPk - )} failed to be sent... Still deleting everything` - ); - } - } catch (e) { - window.log.warn('leaving groupv2 error:', e.message); + const wasSent = await getMessageQueue().sendToGroupV2NonDurably({ + message: ourLeavingMessage, + }); + if (!wasSent) { + throw new Error( + `Even with the retries, leaving message for group ${ed25519Str( + groupPk + )} failed to be sent...` + ); } + // the rest of the cleaning of that conversation is done in the `deleteClosedGroup()` return; @@ -579,12 +576,7 @@ async function removeLegacyGroupFromWrappers(groupId: string) { } async function remove03GroupFromWrappers(groupPk: GroupPubkeyType) { - getSwarmPollingInstance().removePubkey(groupPk, 'remove03GroupFromWrappers'); - - await UserGroupsWrapperActions.eraseGroup(groupPk); - await SessionUtilConvoInfoVolatile.removeGroupFromWrapper(groupPk); window?.inboxStore?.dispatch(groupInfoActions.destroyGroupDetails({ groupPk })); - window.log.info(`removed 03 from metagroup wrapper ${ed25519Str(groupPk)}`); } async function removeCommunityFromWrappers(conversationId: string) { diff --git a/ts/state/ducks/metaGroups.ts b/ts/state/ducks/metaGroups.ts index 2bf0776d2..8b8b51de5 100644 --- a/ts/state/ducks/metaGroups.ts +++ b/ts/state/ducks/metaGroups.ts @@ -30,6 +30,7 @@ import { ClosedGroup } from '../../session/group/closed-group'; import { GroupUpdateInfoChangeMessage } from '../../session/messages/outgoing/controlMessage/group_v2/to_group/GroupUpdateInfoChangeMessage'; import { GroupUpdateMemberChangeMessage } from '../../session/messages/outgoing/controlMessage/group_v2/to_group/GroupUpdateMemberChangeMessage'; import { GroupUpdateDeleteMessage } from '../../session/messages/outgoing/controlMessage/group_v2/to_user/GroupUpdateDeleteMessage'; +import { ed25519Str } from '../../session/onions/onionPath'; import { PubKey } from '../../session/types'; import { UserUtils } from '../../session/utils'; import { PreConditionFailed } from '../../session/utils/errors'; @@ -38,6 +39,7 @@ import { GroupSync } from '../../session/utils/job_runners/jobs/GroupSyncJob'; import { UserSync } from '../../session/utils/job_runners/jobs/UserSyncJob'; import { RunJobResult } from '../../session/utils/job_runners/PersistedJob'; import { LibSessionUtil } from '../../session/utils/libsession/libsession_utils'; +import { SessionUtilConvoInfoVolatile } from '../../session/utils/libsession/libsession_utils_convo_info_volatile'; import { getUserED25519KeyPairBytes } from '../../session/utils/User'; import { stringify, toFixedUint8ArrayOfLength } from '../../types/sqlSharedTypes'; import { @@ -382,14 +384,36 @@ const refreshGroupDetailsFromWrapper = createAsyncThunk( const destroyGroupDetails = createAsyncThunk( 'group/destroyGroupDetails', async ({ groupPk }: { groupPk: GroupPubkeyType }) => { - try { - await UserGroupsWrapperActions.eraseGroup(groupPk); - await ConfigDumpData.deleteDumpFor(groupPk); + debugger; + const us = UserUtils.getOurPubKeyStrFromCache(); + const weAreAdmin = await checkWeAreAdmin(groupPk); + const allMembers = await MetaGroupWrapperActions.memberGetAll(groupPk); + const otherAdminsCount = allMembers + .filter(m => m.admin || m.promoted) + .filter(m => m.pubkeyHex !== us).length; + + // we are the last admin promoted + if (weAreAdmin && otherAdminsCount === 0) { + // this marks the group info as deleted. We need to push those details await MetaGroupWrapperActions.infoDestroy(groupPk); - getSwarmPollingInstance().removePubkey(groupPk, 'destroyGroupDetails'); - } catch (e) { - window.log.warn(`destroyGroupDetails for ${groupPk} failed with ${e.message}`); + const lastPushResult = await GroupSync.pushChangesToGroupSwarmIfNeeded({ + groupPk, + revokeSubRequest: null, + unrevokeSubRequest: null, + supplementKeys: [], + }); + if (lastPushResult !== RunJobResult.Success) { + throw new Error(`Failed to destroyGroupDetails for pk ${ed25519Str(groupPk)}`); + } } + + // this deletes the secretKey if we had it. If we need it for something, it has to be done before this call. + await UserGroupsWrapperActions.eraseGroup(groupPk); + await SessionUtilConvoInfoVolatile.removeGroupFromWrapper(groupPk); + await ConfigDumpData.deleteDumpFor(groupPk); + + getSwarmPollingInstance().removePubkey(groupPk, 'destroyGroupDetails'); + return { groupPk }; } ); @@ -1073,11 +1097,13 @@ const handleMemberLeftMessage = createAsyncThunk( ); } - await handleMemberRemovedFromUI({ - groupPk, - removeMembers: [memberLeft], - fromMemberLeftMessage: true, - }); + if (await checkWeAreAdmin(groupPk)) { + await handleMemberRemovedFromUI({ + groupPk, + removeMembers: [memberLeft], + fromMemberLeftMessage: true, + }); + } return { groupPk, @@ -1303,7 +1329,7 @@ const metaGroupSlice = createSlice({ }); builder.addCase(destroyGroupDetails.fulfilled, (state, action) => { const { groupPk } = action.payload; - // FIXME destroyGroupDetails marks the info as destroyed, but does not really remove the wrapper currently + window.log.info(`removed 03 from metagroup wrapper ${ed25519Str(groupPk)}`); deleteGroupPkEntriesFromState(state, groupPk); }); builder.addCase(destroyGroupDetails.rejected, (_state, action) => { diff --git a/ts/types/LocalizerKeys.ts b/ts/types/LocalizerKeys.ts index c7bb011f4..458be881c 100644 --- a/ts/types/LocalizerKeys.ts +++ b/ts/types/LocalizerKeys.ts @@ -291,7 +291,7 @@ export type LocalizerKeys = | 'leaveGroupConfirmationAdmin' | 'leaveGroupConfirmationOnlyAdmin' | 'leaveGroupConfirmationOnlyAdminWarning' - | 'leaveGroupConrirmationOnlyAdminLegacy' + | 'leaveGroupConfirmationOnlyAdminLegacy' | 'leaveGroupFailed' | 'leaveGroupFailedPleaseTryAgain' | 'leaving'