feat: able to create a broken closedgroup v3
parent
5b2580c48d
commit
db98cc2812
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Checks if a string is hex string. A hex string is a string like "0512ab".
|
||||
* @param maybeHex the string to test
|
||||
* @returns true if this string is a hex string.
|
||||
*/
|
||||
const isHexString = (maybeHex: string) =>
|
||||
maybeHex.length !== 0 && maybeHex.length % 2 === 0 && !/[^a-fA-F0-9]/u.test(maybeHex);
|
||||
|
||||
/**
|
||||
* Returns the Uint8Array corresponding to the given string.
|
||||
* Note: this is different than the libsodium.from_hex().
|
||||
* This takes a string like "0102" and converts it to an UIin8Array like [1, 2] whereare
|
||||
* the libsodium one returns [0, 1, 0, 2]
|
||||
*
|
||||
* Throws an error if this string is not a hex string.
|
||||
* @param hexString the string to convert from
|
||||
* @returns the Uint8Arraty
|
||||
*/
|
||||
const fromHexString = (hexString: string): Uint8Array => {
|
||||
if (!isHexString(hexString)) {
|
||||
throw new Error('Not a hex string');
|
||||
}
|
||||
const matches = hexString.match(/.{1,2}/g);
|
||||
if (!matches) {
|
||||
return new Uint8Array();
|
||||
}
|
||||
return Uint8Array.from(matches.map(byte => parseInt(byte, 16)));
|
||||
};
|
||||
|
||||
const toHexString = (bytes: Uint8Array) =>
|
||||
bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
|
||||
|
||||
export const HexString = {
|
||||
toHexString,
|
||||
fromHexString,
|
||||
isHexString,
|
||||
};
|
@ -0,0 +1,144 @@
|
||||
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';
|
||||
|
||||
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?');
|
||||
|
||||
const us = UserUtils.getOurPubKeyStrFromCache();
|
||||
const setOfMembers = new Set(...groupDetails.members);
|
||||
// Ensure the current user is a member
|
||||
setOfMembers.add(us);
|
||||
|
||||
const updateGroupDetails: ClosedGroup.GroupInfo = {
|
||||
id: newGroup.pubkeyHex,
|
||||
name: groupDetails.groupName,
|
||||
members: [...setOfMembers],
|
||||
admins: [us],
|
||||
activeAt: Date.now(),
|
||||
expireTimer: 0,
|
||||
};
|
||||
|
||||
// we don't want the initial "AAA and You joined the group"
|
||||
|
||||
// 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;
|
@ -1,38 +0,0 @@
|
||||
import {
|
||||
BaseConfigActions,
|
||||
ContactsConfigActionsType,
|
||||
UserConfigActionsType,
|
||||
UserGroupsConfigActionsType,
|
||||
ConvoInfoVolatileConfigActionsType,
|
||||
} from 'libsession_util_nodejs';
|
||||
|
||||
// we can only have one of those wrapper for our current user (but we can have a few configs for it to be merged into one)
|
||||
type UserConfig = 'UserConfig';
|
||||
type ContactsConfig = 'ContactsConfig';
|
||||
type UserGroupsConfig = 'UserGroupsConfig';
|
||||
type ConvoInfoVolatileConfig = 'ConvoInfoVolatileConfig';
|
||||
|
||||
export type ConfigWrapperObjectTypes =
|
||||
| UserConfig
|
||||
| ContactsConfig
|
||||
| UserGroupsConfig
|
||||
| ConvoInfoVolatileConfig;
|
||||
|
||||
type UserConfigFunctions =
|
||||
| [UserConfig, ...BaseConfigActions]
|
||||
| [UserConfig, ...UserConfigActionsType];
|
||||
type ContactsConfigFunctions =
|
||||
| [ContactsConfig, ...BaseConfigActions]
|
||||
| [ContactsConfig, ...ContactsConfigActionsType];
|
||||
type UserGroupsConfigFunctions =
|
||||
| [UserGroupsConfig, ...BaseConfigActions]
|
||||
| [UserGroupsConfig, ...UserGroupsConfigActionsType];
|
||||
type ConvoInfoVolatileConfigFunctions =
|
||||
| [ConvoInfoVolatileConfig, ...BaseConfigActions]
|
||||
| [ConvoInfoVolatileConfig, ...ConvoInfoVolatileConfigActionsType];
|
||||
|
||||
export type LibSessionWorkerFunctions =
|
||||
| UserConfigFunctions
|
||||
| ContactsConfigFunctions
|
||||
| UserGroupsConfigFunctions
|
||||
| ConvoInfoVolatileConfigFunctions;
|
@ -0,0 +1,81 @@
|
||||
import {
|
||||
BaseConfigActions,
|
||||
ContactsConfigActionsType,
|
||||
ConvoInfoVolatileConfigActionsType,
|
||||
GroupPubkeyType,
|
||||
MetaGroupActionsType,
|
||||
UserConfigActionsType,
|
||||
UserGroupsConfigActionsType,
|
||||
} from 'libsession_util_nodejs';
|
||||
|
||||
// we can only have one of those wrapper for our current user (but we can have a few configs for it to be merged into one)
|
||||
export type UserConfig = 'UserConfig';
|
||||
export type ContactsConfig = 'ContactsConfig';
|
||||
export type UserGroupsConfig = 'UserGroupsConfig';
|
||||
export type ConvoInfoVolatileConfig = 'ConvoInfoVolatileConfig';
|
||||
|
||||
export const MetaGroupConfigValue = 'MetaGroupConfig-';
|
||||
type MetaGroupConfigType = typeof MetaGroupConfigValue;
|
||||
export type MetaGroupConfig = `${MetaGroupConfigType}${GroupPubkeyType}`;
|
||||
|
||||
|
||||
export type ConfigWrapperUser =
|
||||
| UserConfig
|
||||
| ContactsConfig
|
||||
| UserGroupsConfig
|
||||
| ConvoInfoVolatileConfig;
|
||||
|
||||
|
||||
export type ConfigWrapperGroup = MetaGroupConfig;
|
||||
|
||||
export type ConfigWrapperObjectTypes =
|
||||
| ConfigWrapperUser
|
||||
| ConfigWrapperGroup;
|
||||
|
||||
type UserConfigFunctions =
|
||||
| [UserConfig, ...BaseConfigActions]
|
||||
| [UserConfig, ...UserConfigActionsType];
|
||||
type ContactsConfigFunctions =
|
||||
| [ContactsConfig, ...BaseConfigActions]
|
||||
| [ContactsConfig, ...ContactsConfigActionsType];
|
||||
type UserGroupsConfigFunctions =
|
||||
| [UserGroupsConfig, ...BaseConfigActions]
|
||||
| [UserGroupsConfig, ...UserGroupsConfigActionsType];
|
||||
type ConvoInfoVolatileConfigFunctions =
|
||||
| [ConvoInfoVolatileConfig, ...BaseConfigActions]
|
||||
| [ConvoInfoVolatileConfig, ...ConvoInfoVolatileConfigActionsType];
|
||||
|
||||
// Group-related calls
|
||||
type MetaGroupFunctions =
|
||||
| [MetaGroupConfig, ...MetaGroupActionsType]
|
||||
|
||||
|
||||
export type LibSessionWorkerFunctions =
|
||||
| UserConfigFunctions
|
||||
| ContactsConfigFunctions
|
||||
| UserGroupsConfigFunctions
|
||||
| ConvoInfoVolatileConfigFunctions
|
||||
| MetaGroupFunctions;
|
||||
|
||||
export function isUserConfigWrapperType(config: ConfigWrapperObjectTypes): config is ConfigWrapperUser {
|
||||
return (
|
||||
config === 'ContactsConfig' ||
|
||||
config === 'UserConfig' ||
|
||||
config === 'ConvoInfoVolatileConfig' ||
|
||||
config === 'UserGroupsConfig'
|
||||
);
|
||||
}
|
||||
|
||||
export function isMetaWrapperType(config: ConfigWrapperObjectTypes): config is MetaGroupConfig {
|
||||
return config.startsWith(MetaGroupConfigValue);
|
||||
}
|
||||
|
||||
|
||||
export function getGroupPubkeyFromWrapperType(type: ConfigWrapperGroup): GroupPubkeyType {
|
||||
if (!type.startsWith(MetaGroupConfigValue + '03')) {
|
||||
throw new Error(`not a metagroup variant: ${type}`)
|
||||
}
|
||||
return type.substring(type.indexOf('-03') + 1) as GroupPubkeyType; // typescript is not yet smart enough
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue