diff --git a/ts/receiver/configMessage.ts b/ts/receiver/configMessage.ts index 150d6ea4d..99a95b8f5 100644 --- a/ts/receiver/configMessage.ts +++ b/ts/receiver/configMessage.ts @@ -24,7 +24,7 @@ import { SessionUtilConvoInfoVolatile } from '../session/utils/libsession/libses import { SessionUtilUserGroups } from '../session/utils/libsession/libsession_utils_user_groups'; import { configurationMessageReceived, trigger } from '../shims/events'; import { getCurrentlySelectedConversationOutsideRedux } from '../state/selectors/conversations'; -import { assertUnreachable } from '../types/sqlSharedTypes'; +import { assertUnreachable, stringify, toFixedUint8ArrayOfLength } from '../types/sqlSharedTypes'; import { BlockedNumberController } from '../util'; import { Storage, setLastProfileUpdateTimestamp } from '../util/storage'; // eslint-disable-next-line import/no-unresolved, import/extensions @@ -46,6 +46,7 @@ import { import { addKeyPairToCacheAndDBIfNeeded } from './closedGroups'; import { HexKeyPair } from './keypairs'; import { queueAllCachedFromSource } from './receiver'; +import { HexString } from '../node/hexStrings'; type IncomingUserResult = { needsPush: boolean; @@ -609,6 +610,42 @@ async function handleLegacyGroupUpdate(latestEnvelopeTimestamp: number) { } } +async function handleGroupUpdate(latestEnvelopeTimestamp: number) { + // first let's check which groups needs to be joined or left by doing a diff of what is in the wrapper and what is in the DB + const allGoupsInWrapper = await UserGroupsWrapperActions.getAllGroups(); + + const allGoupsIdsInWrapper = allGoupsInWrapper.map(m => m.pubkeyHex); + console.warn('allGoupsIdsInWrapper', stringify(allGoupsIdsInWrapper)); + + const userEdKeypair = await UserUtils.getUserED25519KeyPairBytes(); + if (!userEdKeypair) { + throw new Error('userEdKeypair is not set'); + } + + for (let index = 0; index < allGoupsInWrapper.length; index++) { + const groupInWrapper = allGoupsInWrapper[index]; + if (!getConversationController().get(groupInWrapper.pubkeyHex)) { + // dump is always empty when creating a new groupInfo + await MetaGroupWrapperActions.init(groupInWrapper.pubkeyHex, { + metaDumped: null, + userEd25519Secretkey: toFixedUint8ArrayOfLength(userEdKeypair.privKeyBytes, 64), + groupEd25519Secretkey: groupInWrapper.secretKey, + groupEd25519Pubkey: toFixedUint8ArrayOfLength( + HexString.fromHexString(groupInWrapper.pubkeyHex.slice(2)), + 32 + ), + }); + const created = await getConversationController().getOrCreateAndWait( + groupInWrapper.pubkeyHex, + ConversationTypeEnum.GROUPV3 + ); + created.set({ active_at: latestEnvelopeTimestamp }); + await created.commit(); + getSwarmPollingInstance().addGroupId(PubKey.cast(groupInWrapper.pubkeyHex)); + } + } +} + async function handleUserGroupsUpdate(result: IncomingUserResult) { const toHandle = SessionUtilUserGroups.getUserGroupTypes(); for (let index = 0; index < toHandle.length; index++) { @@ -621,6 +658,9 @@ async function handleUserGroupsUpdate(result: IncomingUserResult) { case 'LegacyGroup': await handleLegacyGroupUpdate(result.latestEnvelopeTimestamp); break; + case 'Group': + await handleGroupUpdate(result.latestEnvelopeTimestamp); + break; default: assertUnreachable(typeToHandle, `handleUserGroupsUpdate unhandled type "${typeToHandle}"`); @@ -731,6 +771,10 @@ async function handleConvoInfoVolatileUpdate() { } break; + case 'Group': + // debugger; // we need to update the current read messages of that group 03 with what we have in the wrapper // debugger + break; + default: assertUnreachable(type, `handleConvoInfoVolatileUpdate: unhandeld switch case: ${type}`); } diff --git a/ts/session/apis/snode_api/SnodeRequestTypes.ts b/ts/session/apis/snode_api/SnodeRequestTypes.ts index 43a58e978..aa7be98de 100644 --- a/ts/session/apis/snode_api/SnodeRequestTypes.ts +++ b/ts/session/apis/snode_api/SnodeRequestTypes.ts @@ -4,6 +4,7 @@ import { SnodeNamespaces, SnodeNamespacesGroup } from './namespaces'; export type SwarmForSubRequest = { method: 'get_swarm'; params: { pubkey: string } }; +type WithRetrieveMethod = { method: 'retrieve' }; type WithMaxCountSize = { max_count?: number; max_size?: number }; type WithPubkeyAsString = { pubkey: string }; type WithPubkeyAsGroupPubkey = { pubkey: GroupPubkeyType }; @@ -14,8 +15,7 @@ type RetrieveAlwaysNeeded = { timestamp?: number; }; -export type RetrievePubkeySubRequestType = { - method: 'retrieve'; +export type RetrievePubkeySubRequestType = WithRetrieveMethod & { params: { signature: string; pubkey_ed25519: string; @@ -33,8 +33,7 @@ export type RetrievePubkeySubRequestType = { */ // type UnauthenticatedStoreNamespaces = -30 | -20 | -10 | 0 | 10 | 20 | 30; -export type RetrieveLegacyClosedGroupSubRequestType = { - method: 'retrieve'; +export type RetrieveLegacyClosedGroupSubRequestType = WithRetrieveMethod & { params: { namespace: SnodeNamespaces.LegacyClosedGroup; // legacy closed groups retrieve are not authenticated because the clients do not have a shared key } & RetrieveAlwaysNeeded & @@ -42,8 +41,7 @@ export type RetrieveLegacyClosedGroupSubRequestType = { WithPubkeyAsString; }; -export type RetrieveGroupAdminSubRequestType = { - method: 'retrieve'; +export type RetrieveGroupAdminSubRequestType = WithRetrieveMethod & { params: { signature: string; namespace: SnodeNamespacesGroup; diff --git a/ts/session/apis/snode_api/batchRequest.ts b/ts/session/apis/snode_api/batchRequest.ts index 7e4b3198c..3e23b1ad6 100644 --- a/ts/session/apis/snode_api/batchRequest.ts +++ b/ts/session/apis/snode_api/batchRequest.ts @@ -23,8 +23,8 @@ export async function doSnodeBatchRequest( ): Promise { console.warn( `doSnodeBatchRequest "${method}":`, - JSON.stringify(subRequests.map(m => m.method)), - subRequests + JSON.stringify(subRequests.map(m => m.method)) + // subRequests ); const result = await snodeRpc({ method, diff --git a/ts/session/apis/snode_api/retrieveRequest.ts b/ts/session/apis/snode_api/retrieveRequest.ts index 6a36d2852..75833206f 100644 --- a/ts/session/apis/snode_api/retrieveRequest.ts +++ b/ts/session/apis/snode_api/retrieveRequest.ts @@ -102,7 +102,7 @@ async function retrieveRequestForGroup({ const group = await UserGroupsWrapperActions.getGroup(groupPk); const groupSecretKey = group?.secretKey; if (isNil(groupSecretKey) || isEmpty(groupSecretKey)) { - throw new Error(`sendMessagesDataToSnode: failed to find group admin secret key in wrapper`); + throw new Error(`retrieveRequestForGroup: failed to find group admin secret key in wrapper`); } const signatureBuilt = await SnodeSignature.getSnodeGroupSignatureParams({ ...retrieveParam, diff --git a/ts/session/apis/snode_api/swarmPolling.ts b/ts/session/apis/snode_api/swarmPolling.ts index b8b460585..8e9362888 100644 --- a/ts/session/apis/snode_api/swarmPolling.ts +++ b/ts/session/apis/snode_api/swarmPolling.ts @@ -26,7 +26,7 @@ import { SnodeNamespace, SnodeNamespaces } from './namespaces'; import { SnodeAPIRetrieve } from './retrieveRequest'; import { SwarmPollingGroupConfig } from './swarm_polling_config/SwarmPollingGroupConfig'; import { SwarmPollingUserConfig } from './swarm_polling_config/SwarmPollingUserConfig'; -import { RetrieveMessageItem, RetrieveMessagesResultsBatched } from './types'; +import { RetrieveMessageItem, RetrieveMessageItemWithNamespace, RetrieveMessagesResultsBatched, RetrieveRequestResult } from './types'; import { GroupPubkeyType } from 'libsession_util_nodejs'; import { assertUnreachable } from '../../../types/sqlSharedTypes'; @@ -547,27 +547,28 @@ export class SwarmPolling { } } +function retrieveItemWithNamespace(results: RetrieveRequestResult[] ) { + return flatten( + compact(results.map(result => result.messages.messages?.map(r => ({ ...r, namespace: result.namespace }))))); +} + function filterMessagesPerTypeOfConvo( type: T, retrieveResults: RetrieveMessagesResultsBatched -): { confMessages: Array | null; otherMessages: Array } { +): { confMessages: Array | null; otherMessages: Array } { switch (type) { case ConversationTypeEnum.PRIVATE: { - const confMessages = flatten( - compact( - retrieveResults - .filter(m => SnodeNamespace.isUserConfigNamespace(m.namespace)) - .map(r => r.messages.messages) - ) - ); + const userConfs = retrieveResults + .filter(m => SnodeNamespace.isUserConfigNamespace(m.namespace)); + const userOthers = retrieveResults + .filter(m => !SnodeNamespace.isUserConfigNamespace(m.namespace)); + + const confMessages = + retrieveItemWithNamespace(userConfs) + + const otherMessages = + retrieveItemWithNamespace(userOthers) - const otherMessages = flatten( - compact( - retrieveResults - .filter(m => !SnodeNamespace.isUserConfigNamespace(m.namespace)) - .map(r => r.messages.messages) - ) - ); return { confMessages, otherMessages: uniqBy(otherMessages, x => x.hash) }; } @@ -575,27 +576,25 @@ function filterMessagesPerTypeOfConvo( case ConversationTypeEnum.GROUP: return { confMessages: null, - otherMessages: flatten(compact(retrieveResults.map(m => m.messages.messages))), + otherMessages: retrieveItemWithNamespace(retrieveResults), }; case ConversationTypeEnum.GROUPV3: { - const confMessages = flatten( - compact( - retrieveResults - .filter(m => SnodeNamespace.isGroupConfigNamespace(m.namespace)) - .map(r => r.messages.messages) - ) - ); - const otherMessages = flatten( - compact( - retrieveResults - .filter(m => !SnodeNamespace.isGroupConfigNamespace(m.namespace)) - .map(r => r.messages.messages) - ) - ); + const groupConfs = retrieveResults + .filter(m => SnodeNamespace.isGroupConfigNamespace(m.namespace)); + const groupOthers = retrieveResults + .filter(m => !SnodeNamespace.isGroupConfigNamespace(m.namespace)); + + const groupConfMessages = + retrieveItemWithNamespace(groupConfs) + + const groupOtherMessages = + retrieveItemWithNamespace(groupOthers) + + + return { confMessages: groupConfMessages, otherMessages: uniqBy(groupOtherMessages, x => x.hash) }; - return { confMessages, otherMessages: uniqBy(otherMessages, x => x.hash) }; } default: diff --git a/ts/session/apis/snode_api/swarm_polling_config/SwarmPollingGroupConfig.ts b/ts/session/apis/snode_api/swarm_polling_config/SwarmPollingGroupConfig.ts index 66ff24b46..b35f8b890 100644 --- a/ts/session/apis/snode_api/swarm_polling_config/SwarmPollingGroupConfig.ts +++ b/ts/session/apis/snode_api/swarm_polling_config/SwarmPollingGroupConfig.ts @@ -1,11 +1,13 @@ import { GroupPubkeyType } from 'libsession_util_nodejs'; -import { EnvelopePlus } from '../../../../receiver/types'; +import { stringify } from '../../../../types/sqlSharedTypes'; +import { MetaGroupWrapperActions } from '../../../../webworker/workers/browser/libsession_worker_interface'; import { ed25519Str } from '../../../onions/onionPath'; -import { RetrieveMessageItem } from '../types'; -import { SwarmPollingConfigShared } from './SwarmPollingConfigShared'; +import { fromBase64ToArray } from '../../../utils/String'; +import { SnodeNamespaces } from '../namespaces'; +import { RetrieveMessageItemWithNamespace } from '../types'; async function handleGroupSharedConfigMessages( - groupConfigMessagesMerged: Array, + groupConfigMessagesMerged: Array, groupPk: GroupPubkeyType ) { window.log.info( @@ -14,34 +16,59 @@ async function handleGroupSharedConfigMessages( } for groupPk:${ed25519Str(groupPk)}` ); try { - const extractedConfigMessage = SwarmPollingConfigShared.extractWebSocketContents( - groupConfigMessagesMerged + const infos = groupConfigMessagesMerged + .filter(m => m.namespace === SnodeNamespaces.ClosedGroupInfo) + .map(info => { + return { data: fromBase64ToArray(info.data), hash: info.hash }; + }); + const members = groupConfigMessagesMerged + .filter(m => m.namespace === SnodeNamespaces.ClosedGroupMembers) + .map(info => { + return { data: fromBase64ToArray(info.data), hash: info.hash }; + }); + const keys = groupConfigMessagesMerged + .filter(m => m.namespace === SnodeNamespaces.ClosedGroupKeys) + .map(info => { + return { + data: fromBase64ToArray(info.data), + hash: info.hash, + timestampMs: info.timestamp, + }; + }); + const toMerge = { + groupInfo: infos, + groupKeys: keys, + groupMember: members, + }; + console.info(`About to merge ${stringify(toMerge)}`); + console.info(`dumps before ${stringify(await MetaGroupWrapperActions.metaDump(groupPk))}`); + console.info( + `groupInfo before merge: ${stringify(await MetaGroupWrapperActions.infoGet(groupPk))}` ); - - const allDecryptedConfigMessages = await SwarmPollingConfigShared.decryptSharedConfigMessages( - extractedConfigMessage, - async (_envelope: EnvelopePlus) => { - console.warn('decrypt closed group incoming shared message to do'); - return null; - } + const countMerged = await MetaGroupWrapperActions.metaMerge(groupPk, toMerge); + console.info( + `countMerged ${countMerged}, groupInfo after merge: ${stringify( + await MetaGroupWrapperActions.infoGet(groupPk) + )}` ); + console.info(`dumps after ${stringify(await MetaGroupWrapperActions.metaDump(groupPk))}`); - if (allDecryptedConfigMessages.length) { - try { - window.log.info( - `handleGroupSharedConfigMessages of "${allDecryptedConfigMessages.length}" messages with libsession` - ); - console.warn('HANDLING OF INCOMING GROUP TODO '); - // await ConfigMessageHandler.handleUserConfigMessagesViaLibSession( - // allDecryptedConfigMessages - // ); - } catch (e) { - const allMessageHases = allDecryptedConfigMessages.map(m => m.messageHash).join(','); - window.log.warn( - `failed to handle group messages hashes "${allMessageHases}" with libsession. Error: "${e.message}"` - ); - } - } + // if (allDecryptedConfigMessages.length) { + // try { + // window.log.info( + // `handleGroupSharedConfigMessages of "${allDecryptedConfigMessages.length}" messages with libsession` + // ); + // console.warn('HANDLING OF INCOMING GROUP TODO '); + // // await ConfigMessageHandler.handleUserConfigMessagesViaLibSession( + // // allDecryptedConfigMessages + // // ); + // } catch (e) { + // const allMessageHases = allDecryptedConfigMessages.map(m => m.messageHash).join(','); + // window.log.warn( + // `failed to handle group messages hashes "${allMessageHases}" with libsession. Error: "${e.message}"` + // ); + // } + // } } catch (e) { window.log.warn( `handleGroupSharedConfigMessages of ${groupConfigMessagesMerged.length} failed with ${e.message}` diff --git a/ts/session/apis/snode_api/types.ts b/ts/session/apis/snode_api/types.ts index 705c7b5fe..9e267cb2a 100644 --- a/ts/session/apis/snode_api/types.ts +++ b/ts/session/apis/snode_api/types.ts @@ -7,6 +7,10 @@ export type RetrieveMessageItem = { timestamp: number; }; +export type RetrieveMessageItemWithNamespace = RetrieveMessageItem & { + namespace: SnodeNamespaces; // the namespace from which this message was fetched +}; + export type RetrieveMessagesResultsContent = { hf?: Array; messages?: Array; diff --git a/ts/session/utils/job_runners/jobs/GroupConfigJob.ts b/ts/session/utils/job_runners/jobs/GroupConfigJob.ts index 6e45b9eb8..de9209037 100644 --- a/ts/session/utils/job_runners/jobs/GroupConfigJob.ts +++ b/ts/session/utils/job_runners/jobs/GroupConfigJob.ts @@ -29,6 +29,7 @@ import { PersistedJob, RunJobResult, } from '../PersistedJob'; +import { stringify } from '../../../../types/sqlSharedTypes'; const defaultMsBetweenRetries = 15000; // a long time between retries, to avoid running multiple jobs at the same time, when one was postponed at the same time as one already planned (5s) const defaultMaxAttempts = 2; @@ -210,12 +211,14 @@ class GroupSyncJob extends PersistedJob { data: item.data, }; }); + console.warn(`msgs ${stringify(msgs)}`); const result = await MessageSender.sendEncryptedDataToSnode( msgs, thisJobDestination, oldHashesToDelete ); + console.warn(`result ${stringify(result)}`); const expectedReplyLength = singleDestChanges.messages.length + (oldHashesToDelete.size ? 1 : 0); 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 a66ea2448..1cc1f1ae7 100644 --- a/ts/session/utils/libsession/libsession_utils_convo_info_volatile.ts +++ b/ts/session/utils/libsession/libsession_utils_convo_info_volatile.ts @@ -118,6 +118,9 @@ async function insertConvoFromDBIntoWrapperAndRefresh(convoId: string): Promise< ); } break; + case 'Group': + // we need to keep track of the convo volatile info for the new group now. // TODO AUDRIC debugger + break; case 'Community': try { const asOpengroup = foundConvo.toOpenGroupV2(); diff --git a/ts/session/utils/libsession/libsession_utils_user_groups.ts b/ts/session/utils/libsession/libsession_utils_user_groups.ts index 1de58fe5e..a59dec94b 100644 --- a/ts/session/utils/libsession/libsession_utils_user_groups.ts +++ b/ts/session/utils/libsession/libsession_utils_user_groups.ts @@ -10,6 +10,7 @@ import { } from '../../../types/sqlSharedTypes'; import { UserGroupsWrapperActions } from '../../../webworker/workers/browser/libsession_worker_interface'; import { getConversationController } from '../../conversations'; +import { PubKey } from '../../types'; /** * Returns true if that conversation is an active group @@ -33,6 +34,10 @@ function isLegacyGroupToStoreInWrapper(convo: ConversationModel): boolean { ); } +function isGroupToStoreInWrapper(convo: ConversationModel): boolean { + return convo.isGroup() && PubKey.isClosedGroupV3(convo.id) && convo.isActive(); // TODO should we filter by left/kicked or they are on the wrapper itself? +} + /** * We do not want to include groups left in the wrapper, but when receiving a list * of wrappers from the network we need to check against the one present locally @@ -73,6 +78,8 @@ async function insertGroupsFromDBIntoWrapperAndRefresh(convoId: string): Promise const convoType: UserGroupsType = isCommunityToStoreInWrapper(foundConvo) ? 'Community' + : PubKey.isClosedGroupV3(convoId) + ? 'Group' : 'LegacyGroup'; switch (convoType) { @@ -135,6 +142,9 @@ async function insertGroupsFromDBIntoWrapperAndRefresh(convoId: string): Promise // we still let this go through } break; + case 'Group': + // debugger; + break; default: assertUnreachable( @@ -175,7 +185,7 @@ async function removeCommunityFromWrapper(_convoId: string, fullUrlWithOrWithout * whole other bunch of issues because it is a native node module. */ function getUserGroupTypes(): Array { - return ['Community', 'LegacyGroup']; + return ['Community', 'LegacyGroup', 'Group']; } export const SessionUtilUserGroups = { @@ -193,4 +203,7 @@ export const SessionUtilUserGroups = { // legacy group isLegacyGroupToStoreInWrapper, isLegacyGroupToRemoveFromDBIfNotInWrapper, + + // group 03 + isGroupToStoreInWrapper, }; diff --git a/ts/state/ducks/groupInfos.ts b/ts/state/ducks/groupInfos.ts index a4b28e6e3..996edcb1e 100644 --- a/ts/state/ducks/groupInfos.ts +++ b/ts/state/ducks/groupInfos.ts @@ -1,17 +1,17 @@ import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; import { GroupInfoGet, GroupInfoShared, GroupPubkeyType } from 'libsession_util_nodejs'; +import { uniq } from 'lodash'; import { ConversationTypeEnum } from '../../models/conversationAttributes'; import { HexString } from '../../node/hexStrings'; import { ClosedGroup } from '../../session'; import { getConversationController } from '../../session/conversations'; import { UserUtils } from '../../session/utils'; +import { GroupSync } from '../../session/utils/job_runners/jobs/GroupConfigJob'; +import { toFixedUint8ArrayOfLength } from '../../types/sqlSharedTypes'; import { MetaGroupWrapperActions, UserGroupsWrapperActions, } from '../../webworker/workers/browser/libsession_worker_interface'; -import { toFixedUint8ArrayOfLength } from '../../types/sqlSharedTypes'; -import { uniq } from 'lodash'; -import { GroupSync } from '../../session/utils/job_runners/jobs/GroupConfigJob'; type GroupInfoGetWithId = GroupInfoGet & { id: GroupPubkeyType }; @@ -46,6 +46,9 @@ const initNewGroupInfoInWrapper = createAsyncThunk( }): Promise => { try { const newGroup = await UserGroupsWrapperActions.createGroup(); + + await UserGroupsWrapperActions.setGroup(newGroup); + const ourEd25519KeypairBytes = await UserUtils.getUserED25519KeyPairBytes(); if (!ourEd25519KeypairBytes) { throw new Error('Current user has no priv ed25519 key?'); @@ -61,12 +64,28 @@ const initNewGroupInfoInWrapper = createAsyncThunk( groupEd25519Pubkey: toFixedUint8ArrayOfLength(groupEd2519Pk, 32), }); + await Promise.all( + groupDetails.members.map(async member => { + const created = await MetaGroupWrapperActions.memberGetOrConstruct( + newGroup.pubkeyHex, + member + ); + await MetaGroupWrapperActions.memberSetInvited( + newGroup.pubkeyHex, + created.pubkeyHex, + false + ); + }) + ); const infos = await MetaGroupWrapperActions.infoGet(newGroup.pubkeyHex); + if (!infos) { throw new Error( `getInfos of ${newGroup.pubkeyHex} returned empty result even if it was just init.` ); } + infos.name = groupDetails.groupName; + await MetaGroupWrapperActions.infoSet(newGroup.pubkeyHex, infos); const convo = await getConversationController().getOrCreateAndWait( newGroup.pubkeyHex, diff --git a/ts/types/sqlSharedTypes.ts b/ts/types/sqlSharedTypes.ts index cc3a95fbd..7a5d2d7ab 100644 --- a/ts/types/sqlSharedTypes.ts +++ b/ts/types/sqlSharedTypes.ts @@ -12,7 +12,7 @@ import { isArray, isEmpty, isEqual } from 'lodash'; import { OpenGroupV2Room } from '../data/opengroups'; import { ConversationAttributes } from '../models/conversationAttributes'; import { OpenGroupRequestCommonType } from '../session/apis/open_group_api/opengroupV2/ApiUtil'; -import { fromHexToArray } from '../session/utils/String'; +import { fromHexToArray, toHex } from '../session/utils/String'; import { ConfigWrapperObjectTypesMeta } from '../webworker/workers/browser/libsession_worker_functions'; /** @@ -281,3 +281,9 @@ export function toFixedUint8ArrayOfLength( `toFixedUint8ArrayOfLength invalid. Expected length ${length} but got: ${data.length}` ); } + +export function stringify(obj: unknown) { + return JSON.stringify(obj, (_key, value) => + value instanceof Uint8Array ? `Uint8Array(${value.length}): ${toHex(value)}` : value + ); +} diff --git a/ts/webworker/workers/browser/libsession_worker_interface.ts b/ts/webworker/workers/browser/libsession_worker_interface.ts index a4be75217..9733aaa8b 100644 --- a/ts/webworker/workers/browser/libsession_worker_interface.ts +++ b/ts/webworker/workers/browser/libsession_worker_interface.ts @@ -14,6 +14,7 @@ import { UserConfigWrapperActionsCalls, UserGroupsWrapperActionsCalls, UserGroupsSet, + MergeSingle, } from 'libsession_util_nodejs'; import { join } from 'path'; @@ -76,7 +77,7 @@ export const GenericWrapperActions: GenericWrapperActionsCalls = { >, dump: async (wrapperId: ConfigWrapperUser) => callLibSessionWorker([wrapperId, 'dump']) as ReturnType, - merge: async (wrapperId: ConfigWrapperUser, toMerge: Array<{ hash: string; data: Uint8Array }>) => + merge: async (wrapperId: ConfigWrapperUser, toMerge: Array) => callLibSessionWorker([wrapperId, 'merge', toMerge]) as ReturnType< GenericWrapperActionsCalls['merge'] >, @@ -103,8 +104,7 @@ export const UserConfigWrapperActions: UserConfigWrapperActionsCalls = { confirmPushed: async (seqno: number, hash: string) => GenericWrapperActions.confirmPushed('UserConfig', seqno, hash), dump: async () => GenericWrapperActions.dump('UserConfig'), - merge: async (toMerge: Array<{ hash: string; data: Uint8Array }>) => - GenericWrapperActions.merge('UserConfig', toMerge), + merge: async (toMerge: Array) => GenericWrapperActions.merge('UserConfig', toMerge), needsDump: async () => GenericWrapperActions.needsDump('UserConfig'), needsPush: async () => GenericWrapperActions.needsPush('UserConfig'), push: async () => GenericWrapperActions.push('UserConfig'), @@ -149,7 +149,7 @@ export const ContactsWrapperActions: ContactsWrapperActionsCalls = { confirmPushed: async (seqno: number, hash: string) => GenericWrapperActions.confirmPushed('ContactsConfig', seqno, hash), dump: async () => GenericWrapperActions.dump('ContactsConfig'), - merge: async (toMerge: Array<{ hash: string; data: Uint8Array }>) => + merge: async (toMerge: Array) => GenericWrapperActions.merge('ContactsConfig', toMerge), needsDump: async () => GenericWrapperActions.needsDump('ContactsConfig'), needsPush: async () => GenericWrapperActions.needsPush('ContactsConfig'), @@ -184,7 +184,7 @@ export const UserGroupsWrapperActions: UserGroupsWrapperActionsCalls = { confirmPushed: async (seqno: number, hash: string) => GenericWrapperActions.confirmPushed('UserGroupsConfig', seqno, hash), dump: async () => GenericWrapperActions.dump('UserGroupsConfig'), - merge: async (toMerge: Array<{ hash: string; data: Uint8Array }>) => + merge: async (toMerge: Array) => GenericWrapperActions.merge('UserGroupsConfig', toMerge), needsDump: async () => GenericWrapperActions.needsDump('UserGroupsConfig'), needsPush: async () => GenericWrapperActions.needsPush('UserGroupsConfig'), @@ -281,7 +281,7 @@ export const ConvoInfoVolatileWrapperActions: ConvoInfoVolatileWrapperActionsCal confirmPushed: async (seqno: number, hash: string) => GenericWrapperActions.confirmPushed('ConvoInfoVolatileConfig', seqno, hash), dump: async () => GenericWrapperActions.dump('ConvoInfoVolatileConfig'), - merge: async (toMerge: Array<{ hash: string; data: Uint8Array }>) => + merge: async (toMerge: Array) => GenericWrapperActions.merge('ConvoInfoVolatileConfig', toMerge), needsDump: async () => GenericWrapperActions.needsDump('ConvoInfoVolatileConfig'), needsPush: async () => GenericWrapperActions.needsPush('ConvoInfoVolatileConfig'), @@ -396,6 +396,13 @@ export const MetaGroupWrapperActions: MetaGroupWrapperActionsCalls = { callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'metaConfirmPushed', args]) as Promise< ReturnType >, + metaMerge: async ( + groupPk: GroupPubkeyType, + args: Parameters[1] + ) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'metaMerge', args]) as Promise< + ReturnType + >, /** GroupInfo wrapper specific actions */ infoGet: async (groupPk: GroupPubkeyType) =>