From d89ff59560a5d1bd9d64410c7960818a1b1bc7df Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 13 Sep 2023 13:40:12 +1000 Subject: [PATCH] feat: store info+members+keys on right namespaces for groups make the signature work with the admin key, fetching it from the usergroups wrapper --- ts/session/apis/snode_api/SNodeAPI.ts | 17 +-- ts/session/apis/snode_api/batchRequest.ts | 10 +- ts/session/apis/snode_api/onions.ts | 1 - ts/session/apis/snode_api/retrieveRequest.ts | 2 +- ts/session/apis/snode_api/snodeSignatures.ts | 124 +++++++++++++----- ts/session/apis/snode_api/storeMessage.ts | 5 +- .../conversations/ConversationController.ts | 34 +++-- ts/session/sending/MessageSender.ts | 66 +++++++--- .../utils/job_runners/jobs/GroupConfigJob.ts | 7 +- .../utils/libsession/libsession_utils.ts | 20 +-- .../libsession_utils_convo_info_volatile.ts | 20 ++- .../libsession_utils_user_groups.ts | 16 --- ts/state/ducks/groupInfos.ts | 2 + 13 files changed, 216 insertions(+), 108 deletions(-) diff --git a/ts/session/apis/snode_api/SNodeAPI.ts b/ts/session/apis/snode_api/SNodeAPI.ts index 2eeefde01..accd2faee 100644 --- a/ts/session/apis/snode_api/SNodeAPI.ts +++ b/ts/session/apis/snode_api/SNodeAPI.ts @@ -16,11 +16,11 @@ export const ERROR_CODE_NO_CONNECT = 'ENETUNREACH: No network connection.'; // TODOLATER we should merge those two functions together as they are almost exactly the same const forceNetworkDeletion = async (): Promise | null> => { const sodium = await getSodiumRenderer(); - const userX25519PublicKey = UserUtils.getOurPubKeyStrFromCache(); + const usPk = UserUtils.getOurPubKeyStrFromCache(); - const userED25519KeyPair = await UserUtils.getUserED25519KeyPair(); + const usED25519KeyPair = await UserUtils.getUserED25519KeyPairBytes(); - if (!userED25519KeyPair) { + if (!usED25519KeyPair) { window?.log?.warn('Cannot forceNetworkDeletion, did not find user ed25519 key.'); return null; } @@ -30,7 +30,7 @@ const forceNetworkDeletion = async (): Promise | null> => { try { const maliciousSnodes = await pRetry( async () => { - const userSwarm = await getSwarmFor(userX25519PublicKey); + const userSwarm = await getSwarmFor(usPk); const snodeToMakeRequestTo: Snode | undefined = sample(userSwarm); if (!snodeToMakeRequestTo) { @@ -40,17 +40,16 @@ const forceNetworkDeletion = async (): Promise | null> => { return pRetry( async () => { - const signOpts = await SnodeSignature.getSnodeSignatureParams({ + const signOpts = await SnodeSignature.getSnodeSignatureParamsUs({ method, namespace, - pubkey: userX25519PublicKey, }); const ret = await doSnodeBatchRequest( [{ method, params: { ...signOpts, namespace } }], snodeToMakeRequestTo, 10000, - userX25519PublicKey + usPk ); if (!ret || !ret?.[0].body || ret[0].code !== 200) { @@ -124,9 +123,7 @@ const forceNetworkDeletion = async (): Promise | null> => { const sortedHashes = hashes.sort(); const signatureSnode = snodeJson.signature as string; // The signature format is (with sortedHashes accross all namespaces) ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] ) - const dataToVerify = `${userX25519PublicKey}${ - signOpts.timestamp - }${sortedHashes.join('')}`; + const dataToVerify = `${usPk}${signOpts.timestamp}${sortedHashes.join('')}`; const dataToVerifyUtf8 = StringUtils.encode(dataToVerify, 'utf8'); const isValid = sodium.crypto_sign_verify_detached( diff --git a/ts/session/apis/snode_api/batchRequest.ts b/ts/session/apis/snode_api/batchRequest.ts index 86f6b7695..7e4b3198c 100644 --- a/ts/session/apis/snode_api/batchRequest.ts +++ b/ts/session/apis/snode_api/batchRequest.ts @@ -21,11 +21,11 @@ export async function doSnodeBatchRequest( associatedWith: string | null, method: 'batch' | 'sequence' = 'batch' ): Promise { - // console.warn( - // `doSnodeBatchRequest "${method}":`, - // subRequests.map(m => m.method), - // subRequests - // ); + console.warn( + `doSnodeBatchRequest "${method}":`, + JSON.stringify(subRequests.map(m => m.method)), + subRequests + ); const result = await snodeRpc({ method, params: { requests: subRequests }, diff --git a/ts/session/apis/snode_api/onions.ts b/ts/session/apis/snode_api/onions.ts index 58e272c7a..f7b1e58a5 100644 --- a/ts/session/apis/snode_api/onions.ts +++ b/ts/session/apis/snode_api/onions.ts @@ -1085,7 +1085,6 @@ async function sendOnionRequestSnodeDest( onionPath: Array, targetNode: Snode, headers: Record, - plaintext: string | null, associatedWith?: string ) { diff --git a/ts/session/apis/snode_api/retrieveRequest.ts b/ts/session/apis/snode_api/retrieveRequest.ts index 04f3d3175..17ec44fd1 100644 --- a/ts/session/apis/snode_api/retrieveRequest.ts +++ b/ts/session/apis/snode_api/retrieveRequest.ts @@ -64,7 +64,7 @@ async function buildRetrieveRequest( throw new Error('not a legacy closed group. pubkey can only be ours'); } const signatureArgs = { ...retrieveParam, method: 'retrieve' as const, ourPubkey }; - const signatureBuilt = await SnodeSignature.getSnodeSignatureParams(signatureArgs); + const signatureBuilt = await SnodeSignature.getSnodeSignatureParamsUs(signatureArgs); const retrieve: RetrieveSubRequestType = { method: 'retrieve', params: { ...retrieveParam, ...signatureBuilt }, diff --git a/ts/session/apis/snode_api/snodeSignatures.ts b/ts/session/apis/snode_api/snodeSignatures.ts index 0789180d7..91c09a168 100644 --- a/ts/session/apis/snode_api/snodeSignatures.ts +++ b/ts/session/apis/snode_api/snodeSignatures.ts @@ -1,16 +1,23 @@ +import { FixedSizeUint8Array, GroupPubkeyType } from 'libsession_util_nodejs'; import { getSodiumRenderer } from '../../crypto'; import { StringUtils, UserUtils } from '../../utils'; import { fromHexToArray, fromUInt8ArrayToBase64 } from '../../utils/String'; import { GetNetworkTime } from './getNetworkTime'; +import { SnodeNamespaces } from './namespaces'; +import { PubKey } from '../../types'; +import { toFixedUint8ArrayOfLength } from '../../../types/sqlSharedTypes'; export type SnodeSignatureResult = { timestamp: number; - // sig_timestamp: number; signature: string; pubkey_ed25519: string; pubkey: string; // this is the x25519 key of the pubkey we are doing the request to (ourself for our swarm usually) }; +export type SnodeGroupSignatureResult = Pick & { + pubkey: GroupPubkeyType; // this is the 03 pubkey of the corresponding group +}; + async function getSnodeSignatureByHashesParams({ messages, method, @@ -52,50 +59,106 @@ async function getSnodeSignatureByHashesParams({ } } -async function getSnodeSignatureParams(params: { - pubkey: string; +type SnodeSigParamsShared = { namespace: number | null | 'all'; // 'all' can be used to clear all namespaces (during account deletion) method: 'retrieve' | 'store' | 'delete_all'; -}): Promise { - const ourEd25519Key = await UserUtils.getUserED25519KeyPair(); - - if (!ourEd25519Key) { - const err = `getSnodeSignatureParams "${params.method}": User has no getUserED25519KeyPair()`; - window.log.warn(err); - throw new Error(err); - } - const namespace = params.namespace || 0; - const edKeyPrivBytes = fromHexToArray(ourEd25519Key?.privKey); - - const signatureTimestamp = GetNetworkTime.getNowWithNetworkOffset(); +}; - const withoutNamespace = `${params.method}${signatureTimestamp}`; - const withNamespace = `${params.method}${namespace}${signatureTimestamp}`; - const verificationData = - namespace === 0 - ? StringUtils.encode(withoutNamespace, 'utf8') - : StringUtils.encode(withNamespace, 'utf8'); +type SnodeSigParamsAdminGroup = SnodeSigParamsShared & { + groupPk: GroupPubkeyType; + privKey: Uint8Array; // our ed25519 key when we are signing with our pubkey +}; +type SnodeSigParamsUs = SnodeSigParamsShared & { + pubKey: string; + privKey: FixedSizeUint8Array<64>; +}; - const message = new Uint8Array(verificationData); +function isSigParamsForGroupAdmin( + sigParams: SnodeSigParamsAdminGroup | SnodeSigParamsUs +): sigParams is SnodeSigParamsAdminGroup { + const asGr = sigParams as SnodeSigParamsAdminGroup; + return PubKey.isClosedGroupV3(asGr.groupPk) && !!asGr.privKey; +} - const sodium = await getSodiumRenderer(); +async function getSnodeShared(params: SnodeSigParamsAdminGroup | SnodeSigParamsUs) { + const signatureTimestamp = GetNetworkTime.getNowWithNetworkOffset(); + const verificationData = StringUtils.encode( + `${params.method}${params.namespace === 0 ? '' : params.namespace}${signatureTimestamp}`, + 'utf8' + ); try { - const signature = sodium.crypto_sign_detached(message, edKeyPrivBytes); + const message = new Uint8Array(verificationData); + const sodium = await getSodiumRenderer(); + const signature = sodium.crypto_sign_detached(message, params.privKey as Uint8Array); const signatureBase64 = fromUInt8ArrayToBase64(signature); - + if (isSigParamsForGroupAdmin(params)) { + return { + timestamp: signatureTimestamp, + signature: signatureBase64, + pubkey: params.groupPk, + }; + } return { - // sig_timestamp: signatureTimestamp, timestamp: signatureTimestamp, signature: signatureBase64, - pubkey_ed25519: ourEd25519Key.pubKey, - pubkey: params.pubkey, }; } catch (e) { - window.log.warn('getSnodeSignatureParams failed with: ', e.message); + window.log.warn('getSnodeShared failed with: ', e.message); throw e; } } +async function getSnodeSignatureParamsUs({ + method, + namespace = 0, +}: Pick): Promise { + const ourEd25519Key = await UserUtils.getUserED25519KeyPairBytes(); + const ourEd25519PubKey = await UserUtils.getUserED25519KeyPair(); + + if (!ourEd25519Key || !ourEd25519PubKey) { + const err = `getSnodeSignatureParams "${method}": User has no getUserED25519KeyPairBytes()`; + window.log.warn(err); + throw new Error(err); + } + + const edKeyPrivBytes = ourEd25519Key.privKeyBytes; + + const lengthCheckedPrivKey = toFixedUint8ArrayOfLength(edKeyPrivBytes, 64); + const sigData = await getSnodeShared({ + pubKey: UserUtils.getOurPubKeyStrFromCache(), + method, + namespace, + privKey: lengthCheckedPrivKey, + }); + + const us = UserUtils.getOurPubKeyStrFromCache(); + return { + ...sigData, + pubkey_ed25519: ourEd25519PubKey.pubKey, + pubkey: us, + }; +} + +async function getSnodeGroupSignatureParams({ + groupIdentityPrivKey, + groupPk, + method, + namespace = 0, +}: { + groupPk: GroupPubkeyType; + groupIdentityPrivKey: FixedSizeUint8Array<64>; + namespace: SnodeNamespaces; + method: 'retrieve' | 'store'; +}): Promise { + const sigData = await getSnodeShared({ + pubKey: groupPk, + method, + namespace, + privKey: groupIdentityPrivKey, + }); + return { ...sigData, pubkey: groupPk }; +} + async function generateUpdateExpirySignature({ shortenOrExtend, timestamp, @@ -136,7 +199,8 @@ async function generateUpdateExpirySignature({ } export const SnodeSignature = { - getSnodeSignatureParams, + getSnodeSignatureParamsUs, + getSnodeGroupSignatureParams, getSnodeSignatureByHashesParams, generateUpdateExpirySignature, }; diff --git a/ts/session/apis/snode_api/storeMessage.ts b/ts/session/apis/snode_api/storeMessage.ts index 7517335f9..f4f449b97 100644 --- a/ts/session/apis/snode_api/storeMessage.ts +++ b/ts/session/apis/snode_api/storeMessage.ts @@ -47,7 +47,8 @@ function buildDeleteByHashesSubRequest( async function storeOnNode( targetNode: Snode, params: Array, - toDeleteOnSequence: DeleteByHashesFromNodeParams | null + toDeleteOnSequence: DeleteByHashesFromNodeParams | null, + method: 'batch' | 'sequence' ): Promise { try { const subRequests = buildStoreRequests(params, toDeleteOnSequence); @@ -56,7 +57,7 @@ async function storeOnNode( targetNode, 4000, params[0].pubkey, - toDeleteOnSequence ? 'sequence' : 'batch' + method ); if (!result || !result.length) { diff --git a/ts/session/conversations/ConversationController.ts b/ts/session/conversations/ConversationController.ts index 46ff72756..fc889bb08 100644 --- a/ts/session/conversations/ConversationController.ts +++ b/ts/session/conversations/ConversationController.ts @@ -1,6 +1,6 @@ /* eslint-disable no-await-in-loop */ /* eslint-disable more/no-then */ -import { ConvoVolatileType } from 'libsession_util_nodejs'; +import { ConvoVolatileType, GroupPubkeyType } from 'libsession_util_nodejs'; import { isEmpty, isNil } from 'lodash'; import { Data } from '../../data/data'; @@ -15,24 +15,24 @@ import { getOpenGroupManager } from '../apis/open_group_api/opengroupV2/OpenGrou import { getSwarmFor } from '../apis/snode_api/snodePool'; import { PubKey } from '../types'; +import { getMessageQueue } from '..'; import { deleteAllMessagesByConvoIdNoConfirmation } from '../../interactions/conversationInteractions'; import { CONVERSATION_PRIORITIES, ConversationTypeEnum } from '../../models/conversationAttributes'; +import { removeAllClosedGroupEncryptionKeyPairs } from '../../receiver/closedGroups'; +import { getCurrentlySelectedConversationOutsideRedux } from '../../state/selectors/conversations'; import { assertUnreachable } from '../../types/sqlSharedTypes'; import { UserGroupsWrapperActions } from '../../webworker/workers/browser/libsession_worker_interface'; +import { OpenGroupUtils } from '../apis/open_group_api/utils'; +import { getSwarmPollingInstance } from '../apis/snode_api'; +import { GetNetworkTime } from '../apis/snode_api/getNetworkTime'; +import { SnodeNamespaces } from '../apis/snode_api/namespaces'; +import { ClosedGroupMemberLeftMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupMemberLeftMessage'; +import { UserUtils } from '../utils'; import { ConfigurationSync } from '../utils/job_runners/jobs/ConfigurationSyncJob'; import { LibSessionUtil } from '../utils/libsession/libsession_utils'; import { SessionUtilContact } from '../utils/libsession/libsession_utils_contacts'; import { SessionUtilConvoInfoVolatile } from '../utils/libsession/libsession_utils_convo_info_volatile'; import { SessionUtilUserGroups } from '../utils/libsession/libsession_utils_user_groups'; -import { GetNetworkTime } from '../apis/snode_api/getNetworkTime'; -import { getMessageQueue } from '..'; -import { getSwarmPollingInstance } from '../apis/snode_api'; -import { SnodeNamespaces } from '../apis/snode_api/namespaces'; -import { ClosedGroupMemberLeftMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupMemberLeftMessage'; -import { UserUtils } from '../utils'; -import { getCurrentlySelectedConversationOutsideRedux } from '../../state/selectors/conversations'; -import { removeAllClosedGroupEncryptionKeyPairs } from '../../receiver/closedGroups'; -import { OpenGroupUtils } from '../apis/open_group_api/utils'; let instance: ConversationController | null; @@ -226,7 +226,11 @@ export class ConversationController { // 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); - await removeLegacyGroupFromWrappers(groupId); + if (PubKey.isClosedGroupV3(groupId)) { + await remove03GroupFromWrappers(groupId); + } else { + await removeLegacyGroupFromWrappers(groupId); + } if (!options.fromSyncMessage) { await ConfigurationSync.queueNewJobIfNeeded(); @@ -528,6 +532,14 @@ async function removeLegacyGroupFromWrappers(groupId: string) { await removeAllClosedGroupEncryptionKeyPairs(groupId); } +async function remove03GroupFromWrappers(groupId: GroupPubkeyType) { + getSwarmPollingInstance().removePubkey(groupId); + + await UserGroupsWrapperActions.eraseGroup(groupId); + await SessionUtilConvoInfoVolatile.removeGroupFromWrapper(groupId); + window.log.warn('remove 03 from metagroup wrapper'); +} + async function removeCommunityFromWrappers(conversationId: string) { if (!conversationId || !OpenGroupUtils.isOpenGroupV2(conversationId)) { return; diff --git a/ts/session/sending/MessageSender.ts b/ts/session/sending/MessageSender.ts index 1f4cbed11..2428efab4 100644 --- a/ts/session/sending/MessageSender.ts +++ b/ts/session/sending/MessageSender.ts @@ -23,7 +23,7 @@ import { import { GetNetworkTime } from '../apis/snode_api/getNetworkTime'; import { SnodeNamespace, SnodeNamespaces } from '../apis/snode_api/namespaces'; import { getSwarmFor } from '../apis/snode_api/snodePool'; -import { SnodeSignature, SnodeSignatureResult } from '../apis/snode_api/snodeSignatures'; +import { SnodeSignature } from '../apis/snode_api/snodeSignatures'; import { SnodeAPIStore } from '../apis/snode_api/storeMessage'; import { getConversationController } from '../conversations'; import { MessageEncrypter } from '../crypto'; @@ -37,8 +37,10 @@ import { OpenGroupVisibleMessage } from '../messages/outgoing/visibleMessage/Ope import { ed25519Str } from '../onions/onionPath'; import { PubKey } from '../types'; import { RawMessage } from '../types/RawMessage'; +import { UserUtils } from '../utils'; import { fromUInt8ArrayToBase64 } from '../utils/String'; import { EmptySwarmError } from '../utils/errors'; +import { UserGroupsWrapperActions } from '../../webworker/workers/browser/libsession_worker_interface'; // ================ SNODE STORE ================ @@ -140,7 +142,8 @@ async function send( }, ], recipient.key, - null + null, + 'batch' ); const isDestinationClosedGroup = getConversationController() @@ -181,33 +184,57 @@ async function send( ); } +async function getSignatureParamsFromNamespace(item: StoreOnNodeParamsNoSig, destination: string) { + if (SnodeNamespace.isUserConfigNamespace(item.namespace)) { + const ourPrivKey = (await UserUtils.getUserED25519KeyPairBytes())?.privKeyBytes; + if (!ourPrivKey) { + throw new Error('sendMessagesDataToSnode UserUtils.getUserED25519KeyPairBytes is empty'); + } + return SnodeSignature.getSnodeSignatureParamsUs({ + method: 'store' as const, + namespace: item.namespace, + }); + } + + if (SnodeNamespace.isGroupConfigNamespace(item.namespace)) { + if (!PubKey.isClosedGroupV3(destination)) { + throw new Error('sendMessagesDataToSnode: groupconfig namespace required a 03 pubkey'); + } + const group = await UserGroupsWrapperActions.getGroup(destination); + const groupSecretKey = group?.secretKey; + if (isNil(groupSecretKey) || isEmpty(groupSecretKey)) { + throw new Error(`sendMessagesDataToSnode: failed to find group admin secret key in wrapper`); + } + return SnodeSignature.getSnodeGroupSignatureParams({ + method: 'store' as const, + namespace: item.namespace, + groupPk: destination, + groupIdentityPrivKey: groupSecretKey, + }); + } + return {}; +} + async function sendMessagesDataToSnode( params: Array, destination: string, - messagesHashesToDelete: Set | null + messagesHashesToDelete: Set | null, + method: 'batch' | 'sequence' ): Promise { const rightDestination = params.filter(m => m.pubkey === destination); + const swarm = await getSwarmFor(destination); const withSigWhenRequired: Array = await Promise.all( rightDestination.map(async item => { // some namespaces require a signature to be added - let signOpts: SnodeSignatureResult | undefined; - if (SnodeNamespace.isUserConfigNamespace(item.namespace)) { - signOpts = await SnodeSignature.getSnodeSignatureParams({ - method: 'store' as const, - namespace: item.namespace, - pubkey: destination, - }); - } + const signOpts = await getSignatureParamsFromNamespace(item, destination); + const store: StoreOnNodeParams = { data: item.data64, namespace: item.namespace, pubkey: item.pubkey, - timestamp: item.timestamp, - // sig_timestamp: item.timestamp, - // sig_timestamp is currently not forwarded from the receiving snode to the other swarm members, and so their sig verify fail. - // This timestamp is not really needed so we just don't send it in the meantime (the timestamp value is used if the sig_timestamp is not present) + timestamp: item.timestamp, // sig_timestamp is unused and uneeded ttl: item.ttl, ...signOpts, }; @@ -234,7 +261,8 @@ async function sendMessagesDataToSnode( const storeResults = await SnodeAPIStore.storeOnNode( snode, withSigWhenRequired, - signedDeleteOldHashesRequest + signedDeleteOldHashesRequest, + method ); if (!isEmpty(storeResults)) { @@ -397,7 +425,8 @@ async function sendMessagesToSnode( namespace: wrapped.namespace, })), recipient.key, - messagesHashesToDelete + messagesHashesToDelete, + messagesHashesToDelete?.size ? 'sequence' : 'batch' ); }, { @@ -473,7 +502,8 @@ async function sendEncryptedDataToSnode( namespace: content.namespace, })), destination, - messagesHashesToDelete + messagesHashesToDelete, + 'sequence' ); }, { diff --git a/ts/session/utils/job_runners/jobs/GroupConfigJob.ts b/ts/session/utils/job_runners/jobs/GroupConfigJob.ts index 3eab28c95..6e45b9eb8 100644 --- a/ts/session/utils/job_runners/jobs/GroupConfigJob.ts +++ b/ts/session/utils/job_runners/jobs/GroupConfigJob.ts @@ -94,14 +94,15 @@ async function buildAndSaveDumpsToDB( groupPk, { groupInfo: null, groupKeys: null, groupMember: null }, ]; - debugger; for (let i = 0; i < changes.length; i++) { const change = changes[i]; switch (change.pushed.namespace) { case SnodeNamespaces.ClosedGroupInfo: { - toConfirm[1].groupInfo = [change.pushed.seqno.toNumber(), change.updatedHash]; + if ((change.pushed as any).seqno) { + toConfirm[1].groupInfo = [change.pushed.seqno.toNumber(), change.updatedHash]; + } break; } case SnodeNamespaces.ClosedGroupMembers: { @@ -109,7 +110,7 @@ async function buildAndSaveDumpsToDB( break; } case SnodeNamespaces.ClosedGroupKeys: { - toConfirm[1].groupKeys = [change.pushed.seqno.toNumber(), change.updatedHash]; + toConfirm[1].groupKeys = [change.pushed.data, change.updatedHash, change.pushed.timestamp]; break; } } diff --git a/ts/session/utils/libsession/libsession_utils.ts b/ts/session/utils/libsession/libsession_utils.ts index 175781e80..21391a77f 100644 --- a/ts/session/utils/libsession/libsession_utils.ts +++ b/ts/session/utils/libsession/libsession_utils.ts @@ -199,21 +199,22 @@ async function pendingChangesForUs(): Promise< return results; } -type PendingChangesForGroupShared = { +// we link the namespace to the type of what each wrapper needs + +type PendingChangesForGroupNonKey = { data: Uint8Array; seqno: Long; timestamp: number; - namespace: SnodeNamespaces; -}; - -type PendingChangesForGroupNonKey = PendingChangesForGroupShared & { + namespace: SnodeNamespaces.ClosedGroupInfo | SnodeNamespaces.ClosedGroupMembers; type: Extract; }; -type PendingChangesForGroupKey = Pick< - PendingChangesForGroupShared, - 'data' | 'namespace' | 'timestamp' -> & { type: Extract }; +type PendingChangesForGroupKey = { + data: Uint8Array; + timestamp: number; + namespace: SnodeNamespaces.ClosedGroupKeys; + type: Extract; +}; export type PendingChangesForGroup = PendingChangesForGroupNonKey | PendingChangesForGroupKey; @@ -239,7 +240,6 @@ async function pendingChangesForGroup( } const { groupInfo, groupMember, groupKeys } = await MetaGroupWrapperActions.push(groupPk); - debugger; // Note: We need the keys to be pushed first to avoid a race condition if (groupKeys) { diff --git a/ts/session/utils/libsession/libsession_utils_convo_info_volatile.ts b/ts/session/utils/libsession/libsession_utils_convo_info_volatile.ts index dbde50150..a66ea2448 100644 --- a/ts/session/utils/libsession/libsession_utils_convo_info_volatile.ts +++ b/ts/session/utils/libsession/libsession_utils_convo_info_volatile.ts @@ -1,5 +1,5 @@ /* eslint-disable no-case-declarations */ -import { BaseConvoInfoVolatile, ConvoVolatileType } from 'libsession_util_nodejs'; +import { BaseConvoInfoVolatile, ConvoVolatileType, GroupPubkeyType } from 'libsession_util_nodejs'; import { isEmpty, isFinite } from 'lodash'; import { Data } from '../../../data/data'; import { OpenGroupData } from '../../../data/opengroups'; @@ -25,6 +25,11 @@ const mapped1o1WrapperValues = new Map(); */ const mappedLegacyGroupWrapperValues = new Map(); +/** + * The key of this map is the convoId as stored in the database. So the group 03 pubkey + */ +const mappedGroupWrapperValues = new Map(); + /** * The key of this map is the convoId as stored in the database, so withoutpubkey */ @@ -243,6 +248,16 @@ async function removeLegacyGroupFromWrapper(convoId: string) { mappedLegacyGroupWrapperValues.delete(convoId); } +async function removeGroupFromWrapper(groupPk: GroupPubkeyType) { + // try { + // await ConvoInfoVolatileWrapperActions.eraseGroup(groupPk); + // } catch (e) { + // window.log.warn('removeGroupFromWrapper failed with ', e.message); + // } + window.log.warn('removeGroupFromWrapper TODO'); + mappedGroupWrapperValues.delete(groupPk); +} + /** * Removes the matching legacy group from the wrapper and from the cached list of legacy groups */ @@ -282,6 +297,9 @@ export const SessionUtilConvoInfoVolatile = { // legacy group removeLegacyGroupFromWrapper, // a group can be removed but also just marked hidden, so only call this function when the group is completely removed // TODOLATER + // group + removeGroupFromWrapper, // a group can be removed but also just marked hidden, so only call this function when the group is completely removed // TODOLATER + // communities removeCommunityFromWrapper, }; diff --git a/ts/session/utils/libsession/libsession_utils_user_groups.ts b/ts/session/utils/libsession/libsession_utils_user_groups.ts index 0186f5568..1de58fe5e 100644 --- a/ts/session/utils/libsession/libsession_utils_user_groups.ts +++ b/ts/session/utils/libsession/libsession_utils_user_groups.ts @@ -165,20 +165,6 @@ async function removeCommunityFromWrapper(_convoId: string, fullUrlWithOrWithout } } -/** - * Remove the matching legacy group from the wrapper and from the cached list of legacy groups - */ -async function removeLegacyGroupFromWrapper(groupPk: string) { - try { - await UserGroupsWrapperActions.eraseLegacyGroup(groupPk); - } catch (e) { - window.log.warn( - `UserGroupsWrapperActions.eraseLegacyGroup with = ${groupPk} failed with`, - e.message - ); - } -} - /** * This function can be used where there are things to do for all the types handled by this wrapper. * You can do a loop on all the types handled by this wrapper and have a switch using assertUnreachable to get errors when not every case is handled. @@ -207,6 +193,4 @@ export const SessionUtilUserGroups = { // legacy group isLegacyGroupToStoreInWrapper, isLegacyGroupToRemoveFromDBIfNotInWrapper, - - removeLegacyGroupFromWrapper, // a group can be removed but also just marked hidden, so only call this function when the group is completely removed // TODOLATER }; diff --git a/ts/state/ducks/groupInfos.ts b/ts/state/ducks/groupInfos.ts index 31c2b8333..a4b28e6e3 100644 --- a/ts/state/ducks/groupInfos.ts +++ b/ts/state/ducks/groupInfos.ts @@ -76,6 +76,8 @@ const initNewGroupInfoInWrapper = createAsyncThunk( await convo.setIsApproved(true, false); console.warn('store the v3 identityPrivatekeypair as part of the wrapper only?'); + // the sync below will need the secretKey of the group to be saved in the wrapper. So save it! + await UserGroupsWrapperActions.setGroup(newGroup); await GroupSync.queueNewJobIfNeeded(newGroup.pubkeyHex);