feat: store info+members+keys on right namespaces for groups

make the signature work with the admin key, fetching it from the
usergroups wrapper
pull/2873/head
Audric Ackermann 2 years ago
parent f86b3689ba
commit d89ff59560

@ -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<Array<string> | 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<Array<string> | 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<Array<string> | 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<Array<string> | 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(

@ -21,11 +21,11 @@ export async function doSnodeBatchRequest(
associatedWith: string | null,
method: 'batch' | 'sequence' = 'batch'
): Promise<NotEmptyArrayOfBatchResults> {
// 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 },

@ -1085,7 +1085,6 @@ async function sendOnionRequestSnodeDest(
onionPath: Array<Snode>,
targetNode: Snode,
headers: Record<string, any>,
plaintext: string | null,
associatedWith?: string
) {

@ -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 },

@ -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<SnodeSignatureResult, 'signature' | 'timestamp'> & {
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<SnodeSignatureResult> {
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<SnodeSigParamsUs, 'method' | 'namespace'>): Promise<SnodeSignatureResult> {
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<SnodeGroupSignatureResult> {
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,
};

@ -47,7 +47,8 @@ function buildDeleteByHashesSubRequest(
async function storeOnNode(
targetNode: Snode,
params: Array<StoreOnNodeParams>,
toDeleteOnSequence: DeleteByHashesFromNodeParams | null
toDeleteOnSequence: DeleteByHashesFromNodeParams | null,
method: 'batch' | 'sequence'
): Promise<NotEmptyArrayOfBatchResults> {
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) {

@ -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;

@ -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<StoreOnNodeParamsNoSig>,
destination: string,
messagesHashesToDelete: Set<string> | null
messagesHashesToDelete: Set<string> | null,
method: 'batch' | 'sequence'
): Promise<NotEmptyArrayOfBatchResults> {
const rightDestination = params.filter(m => m.pubkey === destination);
const swarm = await getSwarmFor(destination);
const withSigWhenRequired: Array<StoreOnNodeParams> = 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'
);
},
{

@ -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;
}
}

@ -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<ConfigWrapperGroupDetailed, 'GroupInfo' | 'GroupMember'>;
};
type PendingChangesForGroupKey = Pick<
PendingChangesForGroupShared,
'data' | 'namespace' | 'timestamp'
> & { type: Extract<ConfigWrapperGroupDetailed, 'GroupKeys'> };
type PendingChangesForGroupKey = {
data: Uint8Array;
timestamp: number;
namespace: SnodeNamespaces.ClosedGroupKeys;
type: Extract<ConfigWrapperGroupDetailed, 'GroupKeys'>;
};
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) {

@ -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<string, BaseConvoInfoVolatile>();
*/
const mappedLegacyGroupWrapperValues = new Map<string, BaseConvoInfoVolatile>();
/**
* The key of this map is the convoId as stored in the database. So the group 03 pubkey
*/
const mappedGroupWrapperValues = new Map<GroupPubkeyType, BaseConvoInfoVolatile>();
/**
* 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,
};

@ -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
};

@ -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);

Loading…
Cancel
Save