You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-desktop/ts/state/ducks/groupInfos.ts

146 lines
5.1 KiB
TypeScript

import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { GroupInfoGet, GroupInfoShared, GroupPubkeyType } from 'libsession_util_nodejs';
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 {
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 };
export type GroupInfosState = {
infos: Record<GroupPubkeyType, GroupInfoGetWithId>;
};
export const initialGroupInfosState: GroupInfosState = {
infos: {},
};
const updateGroupInfoInWrapper = createAsyncThunk(
'groupInfos/updateGroupInfoInWrapper',
async ({
id,
data,
}: {
id: GroupPubkeyType;
data: GroupInfoShared;
}): Promise<GroupInfoGetWithId> => {
// TODO this will throw if the wrapper is not init yet... how to make sure it does exist?
const infos = await MetaGroupWrapperActions.infoSet(id, data);
return { id, ...infos };
}
);
const initNewGroupInfoInWrapper = createAsyncThunk(
'groupInfos/initNewGroupInfoInWrapper',
async (groupDetails: {
groupName: string;
members: Array<string>;
}): Promise<GroupInfoGetWithId> => {
try {
const newGroup = await UserGroupsWrapperActions.createGroup();
const ourEd25519KeypairBytes = await UserUtils.getUserED25519KeyPairBytes();
if (!ourEd25519KeypairBytes) {
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)
// dump is always empty when creating a new groupInfo
await MetaGroupWrapperActions.init(newGroup.pubkeyHex, {
metaDumped: null,
userEd25519Secretkey: toFixedUint8ArrayOfLength(userEd25519Secretkey, 64),
groupEd25519Secretkey: newGroup.secretKey,
groupEd25519Pubkey: toFixedUint8ArrayOfLength(groupEd2519Pk, 32),
});
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.`
);
}
const convo = await getConversationController().getOrCreateAndWait(
newGroup.pubkeyHex,
ConversationTypeEnum.GROUPV3
);
await convo.setIsApproved(true, false);
console.warn('store the v3 identityPrivatekeypair as part of the wrapper only?');
await GroupSync.queueNewJobIfNeeded(newGroup.pubkeyHex);
const us = UserUtils.getOurPubKeyStrFromCache();
// Ensure the current user is a member and admin
const members = uniq([...groupDetails.members, us]);
const updateGroupDetails: ClosedGroup.GroupInfo = {
id: newGroup.pubkeyHex,
name: groupDetails.groupName,
members,
admins: [us],
activeAt: Date.now(),
expireTimer: 0,
};
// be sure to call this before sending the message.
// the sending pipeline needs to know from GroupUtils when a message is for a medium group
await ClosedGroup.updateOrCreateClosedGroup(updateGroupDetails);
await convo.commit();
convo.updateLastMessage();
return { id: newGroup.pubkeyHex, ...infos };
} catch (e) {
throw e;
}
}
);
/**
* This slice is the one holding the default joinable rooms fetched once in a while from the default opengroup v2 server.
*/
const groupInfosSlice = createSlice({
name: 'groupInfos',
initialState: initialGroupInfosState,
reducers: {
updateGroupInfosFromMergeResults(state, action: PayloadAction<Array<GroupInfoGetWithId>>) {
// anything not in the results should not be in the state here
state.infos = {};
action.payload.forEach(infos => {
state.infos[infos.id] = infos;
});
return state;
},
},
extraReducers: builder => {
builder.addCase(updateGroupInfoInWrapper.fulfilled, (state, action) => {
state.infos[action.payload.id] = action.payload;
});
builder.addCase(initNewGroupInfoInWrapper.fulfilled, (state, action) => {
state.infos[action.payload.id] = action.payload;
});
builder.addCase(updateGroupInfoInWrapper.rejected, () => {
window.log.error('a updateGroupInfoInWrapper was rejected');
});
builder.addCase(initNewGroupInfoInWrapper.rejected, () => {
window.log.error('a initNewGroupInfoInWrapper was rejected');
});
},
});
export const groupInfoActions = {
initNewGroupInfoInWrapper,
updateGroupInfoInWrapper,
...groupInfosSlice.actions,
};
export const groupInfosReducer = groupInfosSlice.reducer;