diff --git a/ts/hooks/useParamSelector.ts b/ts/hooks/useParamSelector.ts index dc34f7123..8f5f51b80 100644 --- a/ts/hooks/useParamSelector.ts +++ b/ts/hooks/useParamSelector.ts @@ -12,6 +12,7 @@ import { StateType } from '../state/reducer'; import { getMessageReactsProps } from '../state/selectors/conversations'; import { useLibGroupAdmins, useLibGroupMembers, useLibGroupName } from '../state/selectors/groups'; import { isPrivateAndFriend } from '../state/selectors/selectedConversation'; +import { useOurPkStr } from '../state/selectors/user'; export function useAvatarPath(convoId: string | undefined) { const convoProps = useConversationPropsById(convoId); @@ -148,8 +149,9 @@ export function useIsKickedFromGroup(convoId?: string) { } export function useWeAreAdmin(convoId?: string) { - const convoProps = useConversationPropsById(convoId); - return Boolean(convoProps && convoProps.weAreAdmin); + const groupAdmins = useGroupAdmins(convoId); + const us = useOurPkStr(); + return Boolean(groupAdmins.includes(us)); } export function useGroupAdmins(convoId?: string) { @@ -221,7 +223,8 @@ export function useIsOutgoingRequest(convoId?: string) { } /** - * Not to be exported: This selector is too generic and needs to be broken node in individual fields selectors. + * Note: NOT to be exported: + * This selector is too generic and needs to be broken node in individual fields selectors. * Make sure when writing a selector that you fetch the data from libsession if needed. * (check useSortedGroupMembers() as an example) */ 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 9f0f20ef8..2fd1cb360 100644 --- a/ts/session/apis/snode_api/swarm_polling_config/SwarmPollingGroupConfig.ts +++ b/ts/session/apis/snode_api/swarm_polling_config/SwarmPollingGroupConfig.ts @@ -42,9 +42,6 @@ async function handleGroupSharedConfigMessages( groupKeys: keys, groupMember: members, }; - console.info( - `groupInfo before merge: ${stringify(await MetaGroupWrapperActions.infoGet(groupPk))}` - ); await MetaGroupWrapperActions.metaMerge(groupPk, toMerge); await LibSessionUtil.saveMetaGroupDumpToDb(groupPk); diff --git a/ts/session/conversations/createClosedGroup.ts b/ts/session/conversations/createClosedGroup.ts index 85f568238..0a46c480f 100644 --- a/ts/session/conversations/createClosedGroup.ts +++ b/ts/session/conversations/createClosedGroup.ts @@ -1,4 +1,4 @@ -import _ from 'lodash'; +import _, { concat } from 'lodash'; import { ClosedGroup, getMessageQueue } from '..'; import { ConversationTypeEnum } from '../../models/conversationAttributes'; import { addKeyPairToCacheAndDBIfNeeded } from '../../receiver/closedGroups'; @@ -26,10 +26,14 @@ import { groupInfoActions } from '../../state/ducks/groups'; */ export async function createClosedGroup(groupName: string, members: Array, isV3: boolean) { if (isV3) { + const us = UserUtils.getOurPubKeyStrFromCache(); + // we need to send a group info and encryption keys message to the batch endpoint with both seqno being 0 console.error('isV3 send invite to group TODO'); // FIXME // FIXME we should save the key to the wrapper right away? or even to the DB idk - window.inboxStore.dispatch(groupInfoActions.initNewGroupInWrapper({ members, groupName })); + window.inboxStore.dispatch( + groupInfoActions.initNewGroupInWrapper({ members: concat(members, [us]), groupName, us }) + ); return; } diff --git a/ts/state/ducks/groups.ts b/ts/state/ducks/groups.ts index 1ea7919ac..73123d2bb 100644 --- a/ts/state/ducks/groups.ts +++ b/ts/state/ducks/groups.ts @@ -1,6 +1,6 @@ import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import { GroupInfoGet, GroupMemberGet, GroupPubkeyType } from 'libsession_util_nodejs'; -import { isEmpty } from 'lodash'; +import { isEmpty, uniq } from 'lodash'; import { ConfigDumpData } from '../../data/configDump/configDump'; import { ConversationTypeEnum } from '../../models/conversationAttributes'; import { HexString } from '../../node/hexStrings'; @@ -16,6 +16,7 @@ import { MetaGroupWrapperActions, UserGroupsWrapperActions, } from '../../webworker/workers/browser/libsession_worker_interface'; +import { PreConditionFailed } from '../../session/utils/errors'; export type GroupState = { infos: Record; @@ -35,12 +36,23 @@ type GroupDetailsUpdate = { const initNewGroupInWrapper = createAsyncThunk( 'group/initNewGroupInWrapper', - async (groupDetails: { + async ({ + groupName, + members, + us, + }: { groupName: string; members: Array; + us: string; }): Promise => { + if (!members.includes(us)) { + throw new PreConditionFailed('initNewGroupInWrapper needs us to be a member'); + } + const uniqMembers = uniq(members); try { const newGroup = await UserGroupsWrapperActions.createGroup(); + const groupPk = newGroup.pubkeyHex; + newGroup.name = groupName; // this will be used by the linked devices until they fetch the info from the groups swarm await UserGroupsWrapperActions.setGroup(newGroup); @@ -49,47 +61,42 @@ const initNewGroupInWrapper = createAsyncThunk( throw new Error('Current user has no priv ed25519 key?'); } const userEd25519Secretkey = ourEd25519KeypairBytes.privKeyBytes; - const groupEd2519Pk = HexString.fromHexString(newGroup.pubkeyHex).slice(1); // remove the 03 prefix (single byte once in hex form) + const groupEd2519Pk = HexString.fromHexString(groupPk).slice(1); // remove the 03 prefix (single byte once in hex form) // dump is always empty when creating a new groupInfo - await MetaGroupWrapperActions.init(newGroup.pubkeyHex, { + await MetaGroupWrapperActions.init(groupPk, { metaDumped: null, userEd25519Secretkey: toFixedUint8ArrayOfLength(userEd25519Secretkey, 64), groupEd25519Secretkey: newGroup.secretKey, 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); + for (let index = 0; index < uniqMembers.length; index++) { + const member = uniqMembers[index]; + const created = await MetaGroupWrapperActions.memberGetOrConstruct(groupPk, member); + if (created.pubkeyHex === us) { + await MetaGroupWrapperActions.memberSetPromoted(groupPk, created.pubkeyHex, false); + } else { + await MetaGroupWrapperActions.memberSetInvited(groupPk, created.pubkeyHex, false); + } + } + + const infos = await MetaGroupWrapperActions.infoGet(groupPk); if (!infos) { - throw new Error( - `getInfos of ${newGroup.pubkeyHex} returned empty result even if it was just init.` - ); + throw new Error(`getInfos of ${groupPk} returned empty result even if it was just init.`); } - infos.name = groupDetails.groupName; - await MetaGroupWrapperActions.infoSet(newGroup.pubkeyHex, infos); + infos.name = groupName; + await MetaGroupWrapperActions.infoSet(groupPk, infos); - const members = await MetaGroupWrapperActions.memberGetAll(newGroup.pubkeyHex); - if (!members || isEmpty(members)) { + const membersFromWrapper = await MetaGroupWrapperActions.memberGetAll(groupPk); + if (!membersFromWrapper || isEmpty(membersFromWrapper)) { throw new Error( - `memberGetAll of ${newGroup.pubkeyHex} returned empty result even if it was just init.` + `memberGetAll of ${groupPk} returned empty result even if it was just init.` ); } const convo = await getConversationController().getOrCreateAndWait( - newGroup.pubkeyHex, + groupPk, ConversationTypeEnum.GROUPV3 ); @@ -99,11 +106,7 @@ const initNewGroupInWrapper = createAsyncThunk( // // 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); - - // const us = UserUtils.getOurPubKeyStrFromCache(); - // // Ensure the current user is a member and admin - // const members = uniq([...groupDetails.members, us]); + await GroupSync.queueNewJobIfNeeded(groupPk); // const updateGroupDetails: ClosedGroup.GroupInfo = { // id: newGroup.pubkeyHex, @@ -122,7 +125,7 @@ const initNewGroupInWrapper = createAsyncThunk( await convo.commit(); convo.updateLastMessage(); - return { groupPk: newGroup.pubkeyHex, infos, members }; + return { groupPk: newGroup.pubkeyHex, infos, members: membersFromWrapper }; } catch (e) { throw e; } diff --git a/ts/state/selectors/user.ts b/ts/state/selectors/user.ts index 69535cfda..0f9a22360 100644 --- a/ts/state/selectors/user.ts +++ b/ts/state/selectors/user.ts @@ -4,6 +4,7 @@ import { LocalizerType } from '../../types/Util'; import { StateType } from '../reducer'; import { UserStateType } from '../ducks/user'; +import { useSelector } from 'react-redux'; export const getUser = (state: StateType): UserStateType => state.user; @@ -13,3 +14,7 @@ export const getOurNumber = createSelector( ); export const getIntl = createSelector(getUser, (): LocalizerType => window.i18n); + +export function useOurPkStr() { + return useSelector((state: StateType) => getOurNumber(state)); +}