feat: able to create a broken closedgroup v3

pull/2873/head
Audric Ackermann 2 years ago
parent 5b2580c48d
commit db98cc2812

@ -5,6 +5,11 @@ module.exports = {
react: {
version: 'detect',
},
'import/resolver': {
node: {
paths: [path.resolve(__dirname)],
},
},
},
extends: [

2
.gitignore vendored

@ -50,5 +50,5 @@ test-results/
coverage/
stylesheets/dist/
*.worker.js.LICENSE.txt
*.LICENSE.txt
ts/webworker/workers/node/**/*.node

@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');
const isProd = process.env.NODE_ENV === 'production';
module.exports = {
entry: './ts/webworker/workers/node/libsession/libsession.worker.ts',
@ -29,11 +30,11 @@ module.exports = {
},
},
output: {
filename: 'libsession.worker.js',
filename: 'libsession.worker.compiled.js',
path: path.resolve(__dirname, 'ts', 'webworker', 'workers', 'node', 'libsession'),
},
target: 'node',
optimization: {
minimize: true,
minimize: isProd,
},
};

@ -34,7 +34,7 @@ window.sessionFeatureFlags = {
integrationTestEnv: Boolean(
process.env.NODE_APP_INSTANCE && process.env.NODE_APP_INSTANCE.includes('test-integration')
),
useClosedGroupV3: false || process.env.USE_CLOSED_GROUP_V3,
useClosedGroupV3: true,
debug: {
debugLogging: !_.isEmpty(process.env.SESSION_DEBUG),
debugLibsessionDumps: !_.isEmpty(process.env.SESSION_DEBUG_LIBSESSION_DUMPS),

@ -39,6 +39,7 @@ import { useHasDeviceOutdatedSyncing } from '../state/selectors/settings';
import { Storage } from '../util/storage';
import { NoticeBanner } from './NoticeBanner';
import { Flex } from './basic/Flex';
import { initialGroupInfosState } from '../state/ducks/groupInfos';
function makeLookup<T>(items: Array<T>, key: string): { [key: string]: T } {
// Yep, we can't index into item without knowing what it is. True. But we want to.
@ -88,6 +89,7 @@ function createSessionInboxStore() {
call: initialCallState,
sogsRoomInfo: initialSogsRoomInfoState,
settings: getSettingsInitialState(),
groupInfos: initialGroupInfosState,
};
return createStore(initialState);

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

@ -41,11 +41,17 @@ import {
} from '../util/storage';
import { deleteAllMessagesByConvoIdNoConfirmation } from '../interactions/conversationInteractions';
// eslint-disable-next-line import/no-unresolved, import/extensions
import { ConfigWrapperObjectTypes } from '../../ts/webworker/workers/browser/libsession_worker_functions';
import {
ConfigWrapperObjectTypes,
getGroupPubkeyFromWrapperType,
isMetaWrapperType,
isUserConfigWrapperType,
} from '../../ts/webworker/workers/browser/libsession_worker_functions';
import {
ContactsWrapperActions,
ConvoInfoVolatileWrapperActions,
GenericWrapperActions,
MetaGroupWrapperActions,
UserConfigWrapperActions,
UserGroupsWrapperActions,
} from '../webworker/workers/browser/libsession_worker_interface';
@ -77,6 +83,29 @@ function groupByVariant(
return groupedByVariant;
}
async function printDumpForDebug(prefix: string, variant: ConfigWrapperObjectTypes) {
if (isUserConfigWrapperType(variant)) {
window.log.info(prefix, StringUtils.toHex(await GenericWrapperActions.dump(variant)));
return;
}
const metaGroupDumps = await MetaGroupWrapperActions.metaDump(
getGroupPubkeyFromWrapperType(variant)
);
window.log.info(prefix, StringUtils.toHex(metaGroupDumps));
}
async function variantNeedsDump(variant: ConfigWrapperObjectTypes) {
return isUserConfigWrapperType(variant)
? await GenericWrapperActions.needsDump(variant)
: await MetaGroupWrapperActions.needsDump(getGroupPubkeyFromWrapperType(variant));
}
async function variantNeedsPush(variant: ConfigWrapperObjectTypes) {
return isUserConfigWrapperType(variant)
? await GenericWrapperActions.needsPush(variant)
: await MetaGroupWrapperActions.needsPush(getGroupPubkeyFromWrapperType(variant));
}
async function mergeConfigsWithIncomingUpdates(
incomingConfigs: Array<IncomingMessage<SignalService.ISharedConfigMessage>>
): Promise<Map<ConfigWrapperObjectTypes, IncomingConfResult>> {
@ -100,25 +129,17 @@ async function mergeConfigsWithIncomingUpdates(
hash: msg.messageHash,
}));
if (window.sessionFeatureFlags.debug.debugLibsessionDumps) {
window.log.info(
`printDumpsForDebugging: before merge of ${variant}:`,
StringUtils.toHex(await GenericWrapperActions.dump(variant))
);
for (let dumpIndex = 0; dumpIndex < toMerge.length; dumpIndex++) {
const element = toMerge[dumpIndex];
window.log.info(
`printDumpsForDebugging: toMerge of ${dumpIndex}:${element.hash}: ${StringUtils.toHex(
element.data
)} `,
StringUtils.toHex(await GenericWrapperActions.dump(variant))
);
}
printDumpForDebug(`printDumpsForDebugging: before merge of ${variant}:`, variant);
}
if (!isUserConfigWrapperType(variant)) {
window.log.info('// TODO Audric');
continue;
}
const mergedCount = await GenericWrapperActions.merge(variant, toMerge);
const needsPush = await GenericWrapperActions.needsPush(variant);
const needsDump = await GenericWrapperActions.needsDump(variant);
const needsDump = await variantNeedsDump(variant);
const needsPush = await variantNeedsPush(variant);
const latestEnvelopeTimestamp = Math.max(...sameVariant.map(m => m.envelopeTimestamp));
window.log.debug(
@ -126,10 +147,7 @@ async function mergeConfigsWithIncomingUpdates(
);
if (window.sessionFeatureFlags.debug.debugLibsessionDumps) {
window.log.info(
`printDumpsForDebugging: after merge of ${variant}:`,
StringUtils.toHex(await GenericWrapperActions.dump(variant))
);
printDumpForDebug(`printDumpsForDebugging: after merge of ${variant}:`, variant);
}
const incomingConfResult: IncomingConfResult = {
needsDump,
@ -161,6 +179,10 @@ export function getSettingsKeyFromLibsessionWrapper(
case 'ConvoInfoVolatileConfig':
return null; // we don't really care about the convo info volatile one
default:
if (isMetaWrapperType(wrapperType)) {
// we don't care about the group updates as we don't need to drop older one for now
return null; // TODO maybe we do?
}
try {
assertUnreachable(
wrapperType,
@ -791,6 +813,10 @@ async function processMergingResults(results: Map<ConfigWrapperObjectTypes, Inco
if (incomingResult.needsDump) {
// The config data had changes so regenerate the dump and save it
if (!isUserConfigWrapperType(variant)) {
window.log.info('// TODO Audric');
return;
}
const dump = await GenericWrapperActions.dump(variant);
await ConfigDumpData.saveConfigDump({
data: dump,

@ -396,6 +396,11 @@ export class SwarmPolling {
);
}
}
throw new Error(
"todo audric make sure we don't handle shared config messages through the old pipeline since I cleanedup this part"
);
if (allDecryptedConfigMessages.length) {
try {
window.log.info(
@ -447,7 +452,7 @@ export class SwarmPolling {
}
window.log.debug(`configHashesToBump: ${configHashesToBump}`);
} else if (type === ConversationTypeEnum.GROUPV3) {
console.error('pollNodeForKey case bump of hashes closedgroup v3 to do ');
console.info('pollNodeForKey case bump of hashes closedgroup v3 to do ');
}
let results = await SnodeAPIRetrieve.retrieveNextMessages(

@ -179,15 +179,6 @@ export class ConversationController {
});
}
public async createGroupV3(id: string, privateIdentityKey: string): Promise<ConversationModel> {
if (!PubKey.isClosedGroupV3(id)) {
throw new Error('createGroupV3 invalid id given');
}
// FIXME we should save the key to the wrapper right away? or even to the DB idk
return getConversationController().getOrCreateAndWait(id, ConversationTypeEnum.GROUPV3);
}
/**
* Usually, we want to mark private contact deleted as inactive (active_at = undefined).
* That way we can still have the username and avatar for them, but they won't appear in search results etc.

@ -7,11 +7,7 @@ import { openConversationWithMessages } from '../../state/ducks/conversations';
import { updateConfirmModal } from '../../state/ducks/modalDialog';
import { getSwarmPollingInstance } from '../apis/snode_api';
import { SnodeNamespaces } from '../apis/snode_api/namespaces';
import {
generateClosedGroupPublicKey,
generateCurve25519KeyPairWithoutPrefix,
generateGroupV3Keypair,
} from '../crypto';
import { generateClosedGroupPublicKey, generateCurve25519KeyPairWithoutPrefix } from '../crypto';
import {
ClosedGroupNewMessage,
ClosedGroupNewMessageParams,
@ -20,6 +16,7 @@ import { PubKey } from '../types';
import { UserUtils } from '../utils';
import { forceSyncConfigurationNowIfNeeded } from '../utils/sync/syncUtils';
import { getConversationController } from './ConversationController';
import { groupInfoActions } from '../../state/ducks/groupInfos';
/**
* Creates a brand new closed group from user supplied details. This function generates a new identityKeyPair so cannot be used to restore a closed group.
@ -28,17 +25,26 @@ import { getConversationController } from './ConversationController';
* @param isV3 if this closed group is a v3 closed group or not (has a 03 prefix in the identity keypair)
*/
export async function createClosedGroup(groupName: string, members: Array<string>, isV3: boolean) {
const setOfMembers = new Set(members);
if (isV3) {
// 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.initNewGroupInfoInWrapper({ members, groupName }));
return;
}
// this is all legacy group logic.
// TODO: To be removed
const setOfMembers = new Set(members);
const us = UserUtils.getOurPubKeyStrFromCache();
const identityKeyPair = await generateGroupV3Keypair();
const identityKeyPair = await generateClosedGroupPublicKey();
if (!identityKeyPair) {
throw new Error('Could not create identity keypair for new closed group v3');
}
// a v3 pubkey starts with 03 and an old one starts with 05
const groupPublicKey = isV3 ? identityKeyPair.pubkey : await generateClosedGroupPublicKey();
const groupPublicKey = await generateClosedGroupPublicKey();
// the first encryption keypair is generated the same for all versions of closed group
const encryptionKeyPair = await generateCurve25519KeyPairWithoutPrefix();
@ -47,12 +53,10 @@ export async function createClosedGroup(groupName: string, members: Array<string
}
// Create the group
const convo = isV3
? await getConversationController().createGroupV3(groupPublicKey, identityKeyPair.privateKey)
: await getConversationController().getOrCreateAndWait(
groupPublicKey,
ConversationTypeEnum.GROUP
);
const convo = await getConversationController().getOrCreateAndWait(
groupPublicKey,
ConversationTypeEnum.GROUP
);
await convo.setIsApproved(true, false);
@ -81,12 +85,6 @@ export async function createClosedGroup(groupName: string, members: Array<string
await convo.commit();
convo.updateLastMessage();
if (isV3) {
// 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
return;
}
// Send a closed group update message to all members individually
const allInvitesSent = await sendToGroupMembers(
listOfMembers,

@ -55,23 +55,6 @@ export async function generateClosedGroupPublicKey() {
return toHex(prependedX25519PublicKey);
}
/**
* Returns a generated ed25519 hex with a public key being of length 66 and starting with 03.
*/
export async function generateGroupV3Keypair() {
const sodium = await getSodiumRenderer();
const ed25519KeyPair = sodium.crypto_sign_keypair();
const publicKey = new Uint8Array(ed25519KeyPair.publicKey);
const preprendedPubkey = new Uint8Array(33);
preprendedPubkey.set(publicKey, 1);
preprendedPubkey[0] = 3;
console.warn(`generateGroupV3Keypair: pubkey${toHex(preprendedPubkey)}`);
return { pubkey: toHex(preprendedPubkey), privateKey: toHex(ed25519KeyPair.privateKey) };
}
/**
* Returns a generated curve25519 keypair without the prefix on the public key.
* This should be used for the generation of encryption keypairs for a closed group

@ -1,4 +1,4 @@
import _ from 'lodash';
import _, { isNumber } from 'lodash';
import { v4 as uuidv4 } from 'uuid';
import { PubKey } from '../types';
@ -233,17 +233,16 @@ export async function updateOrCreateClosedGroup(details: GroupInfo) {
await conversation.commit();
if (expireTimer === undefined || typeof expireTimer !== 'number') {
return;
if (isNumber(expireTimer) && isFinite(expireTimer)) {
await conversation.updateExpireTimer(
expireTimer,
UserUtils.getOurPubKeyStrFromCache(),
Date.now(),
{
fromSync: true,
}
);
}
await conversation.updateExpireTimer(
expireTimer,
UserUtils.getOurPubKeyStrFromCache(),
Date.now(),
{
fromSync: true,
}
);
}
async function sendNewName(convo: ConversationModel, name: string, messageId: string) {

@ -1,3 +1,4 @@
import { GroupPubkeyType } from 'libsession_util_nodejs';
import { fromHexToArray } from '../utils/String';
export enum KeyPrefixType {
@ -240,7 +241,7 @@ export class PubKey {
return key.startsWith(KeyPrefixType.blinded15) || key.startsWith(KeyPrefixType.blinded25);
}
public static isClosedGroupV3(key: string) {
public static isClosedGroupV3(key: string): key is GroupPubkeyType {
const regex = new RegExp(`^${KeyPrefixType.groupV3}${PubKey.HEX}{64}$`);
return regex.test(key);
}

@ -20,6 +20,7 @@ import {
import { ReleasedFeatures } from '../../../../util/releaseFeature';
import { allowOnlyOneAtATime } from '../../Promise';
import { isSignInByLinking } from '../../../../util/storage';
import { isUserConfigWrapperType } from '../../../../webworker/workers/browser/libsession_worker_functions';
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;
@ -104,6 +105,10 @@ async function buildAndSaveDumpsToDB(
const change = changes[i];
const variant = LibSessionUtil.kindToVariant(change.message.kind);
if (!isUserConfigWrapperType(variant)) {
window.log.info('// TODO Audric');
continue;
}
const needsDump = await LibSessionUtil.markAsPushed(
variant,
destination,

@ -5,16 +5,28 @@ import { difference, omit } from 'lodash';
import Long from 'long';
import { UserUtils } from '..';
import { ConfigDumpData } from '../../../data/configDump/configDump';
import { HexString } from '../../../node/hexStrings';
import { SignalService } from '../../../protobuf';
import { assertUnreachable } from '../../../types/sqlSharedTypes';
import { ConfigWrapperObjectTypes } from '../../../webworker/workers/browser/libsession_worker_functions';
import { GenericWrapperActions } from '../../../webworker/workers/browser/libsession_worker_interface';
import { assertUnreachable, toFixedUint8ArrayOfLength } from '../../../types/sqlSharedTypes';
import {
ConfigWrapperObjectTypes,
ConfigWrapperUser,
getGroupPubkeyFromWrapperType,
isMetaWrapperType,
isUserConfigWrapperType,
} from '../../../webworker/workers/browser/libsession_worker_functions';
import {
GenericWrapperActions,
MetaGroupWrapperActions,
UserGroupsWrapperActions,
} from '../../../webworker/workers/browser/libsession_worker_interface';
import { GetNetworkTime } from '../../apis/snode_api/getNetworkTime';
import { SnodeNamespaces } from '../../apis/snode_api/namespaces';
import { SharedConfigMessage } from '../../messages/outgoing/controlMessage/SharedConfigMessage';
import { getUserED25519KeyPairBytes } from '../User';
import { ConfigurationSync } from '../job_runners/jobs/ConfigurationSyncJob';
const requiredUserVariants: Array<ConfigWrapperObjectTypes> = [
const requiredUserVariants: Array<ConfigWrapperUser> = [
'UserConfig',
'ContactsConfig',
'UserGroupsConfig',
@ -51,27 +63,31 @@ async function initializeLibSessionUtilWrappers() {
JSON.stringify(dumps.map(m => omit(m, 'data')))
);
const userVariantsBuildWithoutErrors = new Set<ConfigWrapperObjectTypes>();
const userVariantsBuildWithoutErrors = new Set<ConfigWrapperUser>();
// load the dumps retrieved from the database into their corresponding wrappers
for (let index = 0; index < dumps.length; index++) {
const dump = dumps[index];
window.log.debug('initializeLibSessionUtilWrappers initing from dump', dump.variant);
const variant = dump.variant;
if (!isUserConfigWrapperType(variant)) {
continue;
}
window.log.debug('initializeLibSessionUtilWrappers initing from dump', variant);
try {
await GenericWrapperActions.init(
dump.variant,
variant,
privateKeyEd25519,
dump.data.length ? dump.data : null
);
userVariantsBuildWithoutErrors.add(dump.variant);
userVariantsBuildWithoutErrors.add(variant);
} catch (e) {
window.log.warn(`init of UserConfig failed with ${e.message} `);
throw new Error(`initializeLibSessionUtilWrappers failed with ${e.message}`);
}
}
const missingRequiredVariants: Array<ConfigWrapperObjectTypes> = difference(
const missingRequiredVariants: Array<ConfigWrapperUser> = difference(
LibSessionUtil.requiredUserVariants,
[...userVariantsBuildWithoutErrors.values()]
);
@ -94,6 +110,39 @@ async function initializeLibSessionUtilWrappers() {
`initializeLibSessionUtilWrappers: missingRequiredVariants "${missingVariant}" created`
);
}
const ed25519KeyPairBytes = await getUserED25519KeyPairBytes();
if (!ed25519KeyPairBytes?.privKeyBytes) {
throw new Error('user has no ed25519KeyPairBytes.');
}
// TODO then load the Group wrappers (not handled yet) into memory
// load the dumps retrieved from the database into their corresponding wrappers
for (let index = 0; index < dumps.length; index++) {
const dump = dumps[index];
const { variant } = dump;
if (!isMetaWrapperType(variant)) {
continue;
}
const groupPk = getGroupPubkeyFromWrapperType(variant);
const groupPkNoPrefix = groupPk.substring(2);
const groupEd25519Pubkey = HexString.fromHexString(groupPkNoPrefix);
try {
const foundInUserGroups = await UserGroupsWrapperActions.getGroup(groupPk);
window.log.debug('initializeLibSessionUtilWrappers initing from dump', variant);
// TODO we need to fetch the admin key here if we have it, maybe from the usergroup wrapper?
await MetaGroupWrapperActions.init(groupPk, {
groupEd25519Pubkey: toFixedUint8ArrayOfLength(groupEd25519Pubkey, 32),
groupEd25519Secretkey: foundInUserGroups?.secretKey || null,
userEd25519Secretkey: toFixedUint8ArrayOfLength(ed25519KeyPairBytes.privKeyBytes, 64),
metaDumped: dump.data,
});
} catch (e) {
// TODO should not throw in this case? we should probably just try to load what we manage to load
window.log.warn(`initGroup of Group wrapper of variant ${variant} failed with ${e.message} `);
// throw new Error(`initializeLibSessionUtilWrappers failed with ${e.message}`);
}
}
}
async function pendingChangesForPubkey(pubkey: string): Promise<Array<OutgoingConfResult>> {
@ -119,6 +168,10 @@ async function pendingChangesForPubkey(pubkey: string): Promise<Array<OutgoingCo
for (let index = 0; index < dumps.length; index++) {
const dump = dumps[index];
const variant = dump.variant;
if (!isUserConfigWrapperType(variant)) {
window.log.info('// TODO Audric');
continue;
}
const needsPush = await GenericWrapperActions.needsPush(variant);
if (!needsPush) {
@ -183,13 +236,13 @@ function variantToKind(variant: ConfigWrapperObjectTypes): SignalService.SharedC
* Returns true if the config needs to be dumped afterwards
*/
async function markAsPushed(
variant: ConfigWrapperObjectTypes,
variant: ConfigWrapperUser,
pubkey: string,
seqno: number,
hash: string
) {
if (pubkey !== UserUtils.getOurPubKeyStrFromCache()) {
throw new Error('FIXME, generic case is to be done');
throw new Error('FIXME, generic case is to be done'); // TODO Audric
}
await GenericWrapperActions.confirmPushed(variant, seqno, hash);
return GenericWrapperActions.needsDump(variant);

@ -7,6 +7,7 @@ import { actions as sections } from './ducks/section';
import { actions as theme } from './ducks/theme';
import { actions as modalDialog } from './ducks/modalDialog';
import { actions as primaryColor } from './ducks/primaryColor';
import { groupInfoActions } from './ducks/groupInfos';
export function mapDispatchToProps(dispatch: Dispatch): object {
return {
@ -19,6 +20,7 @@ export function mapDispatchToProps(dispatch: Dispatch): object {
...sections,
...modalDialog,
...primaryColor,
...groupInfoActions,
},
dispatch
),

@ -16,7 +16,6 @@ const directConsole = {
group: console.group,
groupEnd: console.groupEnd,
warn: console.warn,
error: console.error,
};

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

@ -20,6 +20,7 @@ import {
} from './ducks/stagedAttachments';
import { PrimaryColorStateType, ThemeStateType } from '../themes/constants/colors';
import { settingsReducer, SettingsState } from './ducks/settings';
import { groupInfosReducer, GroupInfosState } from './ducks/groupInfos';
export type StateType = {
search: SearchStateType;
@ -37,6 +38,7 @@ export type StateType = {
call: CallStateType;
sogsRoomInfo: SogsRoomInfoState;
settings: SettingsState;
groupInfos: GroupInfosState;
};
export const reducers = {
@ -55,6 +57,7 @@ export const reducers = {
call,
sogsRoomInfo: ReduxSogsRoomInfos.sogsRoomInfoReducer,
settings: settingsReducer,
groupInfos: groupInfosReducer,
};
// Making this work would require that our reducer signature supported AnyAction, not

@ -1,7 +1,12 @@
/* eslint-disable import/extensions */
/* eslint-disable import/no-unresolved */
// eslint-disable-next-line camelcase
import { ContactInfoSet, LegacyGroupInfo, LegacyGroupMemberInfo } from 'libsession_util_nodejs';
import {
ContactInfoSet,
FixedSizeUint8Array,
LegacyGroupInfo,
LegacyGroupMemberInfo,
} from 'libsession_util_nodejs';
import { from_hex } from 'libsodium-wrappers-sumo';
import { isArray, isEmpty, isEqual } from 'lodash';
import { OpenGroupV2Room } from '../data/opengroups';
@ -265,3 +270,13 @@ export function capabilitiesListHasBlindEnabled(caps?: Array<string> | null) {
export function roomHasReactionsEnabled(openGroup?: OpenGroupV2Room) {
return Boolean(openGroup?.capabilities?.includes('reactions'));
}
export function toFixedUint8ArrayOfLength<T extends number>(
data: Uint8Array,
length: T
): FixedSizeUint8Array<T> {
if (data.length === length) return (data as any) as FixedSizeUint8Array<T>;
throw new Error(
`toFixedUint8ArrayOfLength invalid. Expected length ${length} but got: ${data.length}`
);
}

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

@ -1,19 +1,24 @@
/* eslint-disable import/extensions */
/* eslint-disable import/no-unresolved */
import { join } from 'path';
import {
BaseWrapperActionsCalls,
GroupWrapperConstructor,
ContactInfoSet,
ContactsWrapperActionsCalls,
ConvoInfoVolatileWrapperActionsCalls,
GenericWrapperActionsCall,
GroupInfoSet,
GroupPubkeyType,
LegacyGroupInfo,
MetaGroupWrapperActionsCalls,
ProfilePicture,
UserConfigWrapperActionsCalls,
UserGroupsWrapperActionsCalls,
} from 'libsession_util_nodejs';
import { join } from 'path';
import { getAppRootPath } from '../../../node/getRootPath';
import { WorkerInterface } from '../../worker_interface';
import { ConfigWrapperObjectTypes, LibSessionWorkerFunctions } from './libsession_worker_functions';
import { ConfigWrapperUser, LibSessionWorkerFunctions } from './libsession_worker_functions';
let libsessionWorkerInterface: WorkerInterface | undefined;
@ -30,56 +35,68 @@ const internalCallLibSessionWorker = async ([
'workers',
'node',
'libsession',
'libsession.worker.js'
'libsession.worker.compiled.js'
);
libsessionWorkerInterface = new WorkerInterface(libsessionWorkerPath, 1 * 60 * 1000);
}
return libsessionWorkerInterface?.callWorker(config, fnName, ...args);
const result = libsessionWorkerInterface?.callWorker(config, fnName, ...args);
return result;
};
export const GenericWrapperActions = {
init: async (
wrapperId: ConfigWrapperObjectTypes,
type GenericWrapperActionsCalls = {
init: (
wrapperId: ConfigWrapperUser,
ed25519Key: Uint8Array,
dump: Uint8Array | null
) =>
/** base wrapper generic actions */
callLibSessionWorker([wrapperId, 'init', ed25519Key, dump]) as Promise<void>,
confirmPushed: async (wrapperId: ConfigWrapperObjectTypes, seqno: number, hash: string) =>
callLibSessionWorker([wrapperId, 'confirmPushed', seqno, hash]) as ReturnType<
BaseWrapperActionsCalls['confirmPushed']
>,
dump: async (wrapperId: ConfigWrapperObjectTypes) =>
callLibSessionWorker([wrapperId, 'dump']) as Promise<
ReturnType<BaseWrapperActionsCalls['dump']>
) => Promise<void>;
confirmPushed: GenericWrapperActionsCall<ConfigWrapperUser, 'confirmPushed'>;
dump: GenericWrapperActionsCall<ConfigWrapperUser, 'dump'>;
merge: GenericWrapperActionsCall<ConfigWrapperUser, 'merge'>;
needsDump: GenericWrapperActionsCall<ConfigWrapperUser, 'needsDump'>;
needsPush: GenericWrapperActionsCall<ConfigWrapperUser, 'needsPush'>;
push: GenericWrapperActionsCall<ConfigWrapperUser, 'push'>;
storageNamespace: GenericWrapperActionsCall<ConfigWrapperUser, 'storageNamespace'>;
currentHashes: GenericWrapperActionsCall<ConfigWrapperUser, 'currentHashes'>;
};
// TODO rename this to a UserWrapperActions or UserGenericWrapperActions as those actions are only used for User Wrappers now
export const GenericWrapperActions: GenericWrapperActionsCalls = {
/** base wrapper generic actions */
init: async (wrapperId: ConfigWrapperUser, ed25519Key: Uint8Array, dump: Uint8Array | null) =>
callLibSessionWorker([wrapperId, 'init', ed25519Key, dump]) as ReturnType<
GenericWrapperActionsCalls['init']
>,
merge: async (
wrapperId: ConfigWrapperObjectTypes,
toMerge: Array<{ hash: string; data: Uint8Array }>
) =>
callLibSessionWorker([wrapperId, 'merge', toMerge]) as Promise<
ReturnType<BaseWrapperActionsCalls['merge']>
confirmPushed: async (wrapperId: ConfigWrapperUser, seqno: number, hash: string) =>
callLibSessionWorker([wrapperId, 'confirmPushed', seqno, hash]) as ReturnType<
GenericWrapperActionsCalls['confirmPushed']
>,
needsDump: async (wrapperId: ConfigWrapperObjectTypes) =>
callLibSessionWorker([wrapperId, 'needsDump']) as Promise<
ReturnType<BaseWrapperActionsCalls['needsDump']>
dump: async (wrapperId: ConfigWrapperUser) =>
callLibSessionWorker([wrapperId, 'dump']) as ReturnType<GenericWrapperActionsCalls['dump']>,
merge: async (wrapperId: ConfigWrapperUser, toMerge: Array<{ hash: string; data: Uint8Array }>) =>
callLibSessionWorker([wrapperId, 'merge', toMerge]) as ReturnType<
GenericWrapperActionsCalls['merge']
>,
needsPush: async (wrapperId: ConfigWrapperObjectTypes) =>
callLibSessionWorker([wrapperId, 'needsPush']) as Promise<
ReturnType<BaseWrapperActionsCalls['needsPush']>
needsDump: async (wrapperId: ConfigWrapperUser) =>
callLibSessionWorker([wrapperId, 'needsDump']) as ReturnType<
GenericWrapperActionsCalls['needsDump']
>,
push: async (wrapperId: ConfigWrapperObjectTypes) =>
callLibSessionWorker([wrapperId, 'push']) as Promise<
ReturnType<BaseWrapperActionsCalls['push']>
needsPush: async (wrapperId: ConfigWrapperUser) =>
callLibSessionWorker([wrapperId, 'needsPush']) as ReturnType<
GenericWrapperActionsCalls['needsPush']
>,
storageNamespace: async (wrapperId: ConfigWrapperObjectTypes) =>
callLibSessionWorker([wrapperId, 'storageNamespace']) as Promise<
ReturnType<BaseWrapperActionsCalls['storageNamespace']>
push: async (wrapperId: ConfigWrapperUser) =>
callLibSessionWorker([wrapperId, 'push']) as ReturnType<GenericWrapperActionsCalls['push']>,
storageNamespace: async (wrapperId: ConfigWrapperUser) =>
callLibSessionWorker([wrapperId, 'storageNamespace']) as ReturnType<
GenericWrapperActionsCalls['storageNamespace']
>,
currentHashes: async (wrapperId: ConfigWrapperObjectTypes) =>
callLibSessionWorker([wrapperId, 'currentHashes']) as Promise<
ReturnType<BaseWrapperActionsCalls['currentHashes']>
currentHashes: async (wrapperId: ConfigWrapperUser) =>
callLibSessionWorker([wrapperId, 'currentHashes']) as ReturnType<
GenericWrapperActionsCalls['currentHashes']
>,
};
@ -237,6 +254,16 @@ export const UserGroupsWrapperActions: UserGroupsWrapperActionsCalls = {
callLibSessionWorker(['UserGroupsConfig', 'eraseLegacyGroup', pubkeyHex]) as Promise<
ReturnType<UserGroupsWrapperActionsCalls['eraseLegacyGroup']>
>,
createGroup: async () =>
callLibSessionWorker(['UserGroupsConfig', 'createGroup']) as Promise<
ReturnType<UserGroupsWrapperActionsCalls['createGroup']>
>,
getGroup: async (pubkeyHex: GroupPubkeyType) =>
callLibSessionWorker(['UserGroupsConfig', 'getGroup', pubkeyHex]) as Promise<
ReturnType<UserGroupsWrapperActionsCalls['getGroup']>
>,
};
export const ConvoInfoVolatileWrapperActions: ConvoInfoVolatileWrapperActionsCalls = {
@ -333,6 +360,144 @@ export const ConvoInfoVolatileWrapperActions: ConvoInfoVolatileWrapperActionsCal
]) as Promise<ReturnType<ConvoInfoVolatileWrapperActionsCalls['eraseCommunityByFullUrl']>>,
};
export const MetaGroupWrapperActions: MetaGroupWrapperActionsCalls = {
/** Shared actions */
init: async (groupPk: GroupPubkeyType, options: GroupWrapperConstructor) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'init', options]) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['init']>
>,
needsPush: async (groupPk: GroupPubkeyType) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'needsPush']) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['needsPush']>
>,
push: async (groupPk: GroupPubkeyType) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'push']) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['push']>
>,
needsDump: async (groupPk: GroupPubkeyType) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'needsDump']) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['needsDump']>
>,
metaDump: async (groupPk: GroupPubkeyType) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'metaDump']) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['metaDump']>
>,
/** GroupInfo wrapper specific actions */
infoGet: async (groupPk: GroupPubkeyType) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'infoGet']) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['infoGet']>
>,
infoSet: async (groupPk: GroupPubkeyType, infos: GroupInfoSet) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'infoSet', infos]) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['infoSet']>
>,
infoDestroy: async (groupPk: GroupPubkeyType) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'infoDestroy']) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['infoDestroy']>
>,
/** GroupMembers wrapper specific actions */
memberGet: async (groupPk: GroupPubkeyType, pubkeyHex: string) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'memberGet', pubkeyHex]) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['memberGet']>
>,
memberGetOrConstruct: async (groupPk: GroupPubkeyType, pubkeyHex: string) =>
callLibSessionWorker([
`MetaGroupConfig-${groupPk}`,
'memberGetOrConstruct',
pubkeyHex,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['memberGetOrConstruct']>>,
memberGetAll: async (groupPk: GroupPubkeyType) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'memberGetAll']) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['memberGetAll']>
>,
memberErase: async (groupPk: GroupPubkeyType, pubkeyHex: string) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'memberErase', pubkeyHex]) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['memberErase']>
>,
memberSetAccepted: async (groupPk: GroupPubkeyType, pubkeyHex: string) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'memberSetAccepted', pubkeyHex]) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['memberSetAccepted']>
>,
memberSetPromoted: async (groupPk: GroupPubkeyType, pubkeyHex: string, failed: boolean) =>
callLibSessionWorker([
`MetaGroupConfig-${groupPk}`,
'memberSetPromoted',
pubkeyHex,
failed,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['memberSetPromoted']>>,
memberSetInvited: async (groupPk: GroupPubkeyType, pubkeyHex: string, failed: boolean) =>
callLibSessionWorker([
`MetaGroupConfig-${groupPk}`,
'memberSetInvited',
pubkeyHex,
failed,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['memberSetInvited']>>,
memberSetName: async (groupPk: GroupPubkeyType, pubkeyHex: string, name: string) =>
callLibSessionWorker([
`MetaGroupConfig-${groupPk}`,
'memberSetName',
pubkeyHex,
name,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['memberSetName']>>,
memberSetProfilePicture: async (
groupPk: GroupPubkeyType,
pubkeyHex: string,
profilePicture: ProfilePicture
) =>
callLibSessionWorker([
`MetaGroupConfig-${groupPk}`,
'memberSetProfilePicture',
pubkeyHex,
profilePicture,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['memberSetProfilePicture']>>,
/** GroupKeys wrapper specific actions */
keyRekey: async (groupPk: GroupPubkeyType) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'keyRekey']) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['keyRekey']>
>,
keysNeedsRekey: async (groupPk: GroupPubkeyType) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'keysNeedsRekey']) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['keysNeedsRekey']>
>,
groupKeys: async (groupPk: GroupPubkeyType) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'groupKeys']) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['groupKeys']>
>,
currentHashes: async (groupPk: GroupPubkeyType) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'currentHashes']) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['currentHashes']>
>,
loadKeyMessage: async (
groupPk: GroupPubkeyType,
hash: string,
data: Uint8Array,
timestampMs: number
) =>
callLibSessionWorker([
`MetaGroupConfig-${groupPk}`,
'loadKeyMessage',
hash,
data,
timestampMs,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['loadKeyMessage']>>,
encryptMessage: async (groupPk: GroupPubkeyType, plaintext: Uint8Array, compress: boolean) =>
callLibSessionWorker([
`MetaGroupConfig-${groupPk}`,
'encryptMessage',
plaintext,
compress,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['encryptMessage']>>,
decryptMessage: async (groupPk: GroupPubkeyType, ciphertext: Uint8Array) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'decryptMessage', ciphertext]) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['decryptMessage']>
>,
};
export const callLibSessionWorker = async (
callToMake: LibSessionWorkerFunctions
): Promise<unknown> => {

@ -25,7 +25,7 @@ const internalCallUtilsWorker = async (
'workers',
'node',
'util',
'util.worker.js'
'util.worker.compiled.js'
);
utilWorkerInterface = new WorkerInterface(utilWorkerPath, 3 * 60 * 1000);
}

@ -1,15 +1,25 @@
/* eslint-disable consistent-return */
/* eslint-disable no-case-declarations */
import { isEmpty, isNull } from 'lodash';
import {
BaseConfigWrapperNode,
ContactsConfigWrapperNode,
ConvoInfoVolatileWrapperNode,
GroupPubkeyType,
GroupWrapperConstructor,
MetaGroupWrapperNode,
UserConfigWrapperNode,
UserGroupsWrapperNode,
} from 'libsession_util_nodejs';
// eslint-disable-next-line import/no-unresolved, import/extensions
import { ConfigWrapperObjectTypes } from '../../browser/libsession_worker_functions';
import { isEmpty, isNull } from 'lodash';
import {
ConfigWrapperGroup,
ConfigWrapperObjectTypes,
ConfigWrapperUser,
MetaGroupConfig,
isMetaWrapperType,
isUserConfigWrapperType,
} from '../../browser/libsession_worker_functions';
/* eslint-disable no-console */
/* eslint-disable strict */
@ -29,7 +39,9 @@ let contactsConfigWrapper: ContactsConfigWrapperNode | undefined;
let userGroupsConfigWrapper: UserGroupsWrapperNode | undefined;
let convoInfoVolatileConfigWrapper: ConvoInfoVolatileWrapperNode | undefined;
function getUserWrapper(type: ConfigWrapperObjectTypes): BaseConfigWrapperNode | undefined {
const metaGroupWrappers: Map<GroupPubkeyType, MetaGroupWrapperNode> = new Map();
function getUserWrapper(type: ConfigWrapperUser): BaseConfigWrapperNode | undefined {
switch (type) {
case 'UserConfig':
return userProfileWrapper;
@ -44,45 +56,84 @@ function getUserWrapper(type: ConfigWrapperObjectTypes): BaseConfigWrapperNode |
}
}
function getCorrespondingWrapper(wrapperType: ConfigWrapperObjectTypes): BaseConfigWrapperNode {
switch (wrapperType) {
case 'UserConfig':
case 'ContactsConfig':
case 'UserGroupsConfig':
case 'ConvoInfoVolatileConfig':
const wrapper = getUserWrapper(wrapperType);
if (!wrapper) {
throw new Error(`${wrapperType} is not init yet`);
}
return wrapper;
default:
assertUnreachable(
wrapperType,
`getCorrespondingWrapper: Missing case error "${wrapperType}"`
);
function getGroupPubkeyFromWrapperType(type: ConfigWrapperGroup): GroupPubkeyType {
assertGroupWrapperType(type);
return type.substring(type.indexOf('-03') + 1) as GroupPubkeyType; // typescript is not yet smart enough
}
function getGroupWrapper(type: ConfigWrapperGroup): MetaGroupWrapperNode | undefined {
assertGroupWrapperType(type);
if (isMetaWrapperType(type)) {
const pk = getGroupPubkeyFromWrapperType(type);
return metaGroupWrappers.get(pk);
}
assertUnreachable(type, `getGroupWrapper: Missing case error "${type}"`);
}
function getCorrespondingUserWrapper(wrapperType: ConfigWrapperUser): BaseConfigWrapperNode {
if (isUserConfigWrapperType(wrapperType)) {
switch (wrapperType) {
case 'UserConfig':
case 'ContactsConfig':
case 'UserGroupsConfig':
case 'ConvoInfoVolatileConfig':
const wrapper = getUserWrapper(wrapperType);
if (!wrapper) {
throw new Error(`UserWrapper: ${wrapperType} is not init yet`);
}
return wrapper;
default:
assertUnreachable(
wrapperType,
`getCorrespondingUserWrapper: Missing case error "${wrapperType}"`
);
}
}
assertUnreachable(
wrapperType,
`getCorrespondingUserWrapper missing global handling for "${wrapperType}"`
);
}
function getCorrespondingGroupWrapper(wrapperType: MetaGroupConfig): MetaGroupWrapperNode {
if (isMetaWrapperType(wrapperType)) {
const wrapper = getGroupWrapper(wrapperType);
if (!wrapper) {
throw new Error(`GroupWrapper: ${wrapperType} is not init yet`);
}
return wrapper;
}
assertUnreachable(
wrapperType,
`getCorrespondingGroupWrapper missing global handling for "${wrapperType}"`
);
}
function isUInt8Array(value: any) {
return value.constructor === Uint8Array;
}
function assertUserWrapperType(wrapperType: ConfigWrapperObjectTypes): ConfigWrapperObjectTypes {
if (
wrapperType !== 'ContactsConfig' &&
wrapperType !== 'UserConfig' &&
wrapperType !== 'UserGroupsConfig' &&
wrapperType !== 'ConvoInfoVolatileConfig'
) {
function assertUserWrapperType(wrapperType: ConfigWrapperObjectTypes): ConfigWrapperUser {
if (!isUserConfigWrapperType(wrapperType)) {
throw new Error(`wrapperType "${wrapperType} is not of type User"`);
}
return wrapperType;
}
function assertGroupWrapperType(wrapperType: ConfigWrapperObjectTypes): ConfigWrapperGroup {
if (!isMetaWrapperType(wrapperType)) {
throw new Error(`wrapperType "${wrapperType} is not of type Group"`);
}
return wrapperType;
}
/**
* This function can be used to initialize a wrapper which takes the private ed25519 key of the user and a dump as argument.
*/
function initUserWrapper(options: Array<any>, wrapperType: ConfigWrapperObjectTypes) {
function initUserWrapper(options: Array<any>, wrapperType: ConfigWrapperUser) {
const userType = assertUserWrapperType(wrapperType);
const wrapper = getUserWrapper(wrapperType);
@ -119,16 +170,69 @@ function initUserWrapper(options: Array<any>, wrapperType: ConfigWrapperObjectTy
}
}
/**
* This function can be used to initialize a group wrapper
*/
function initGroupWrapper(options: Array<any>, wrapperType: ConfigWrapperGroup) {
const groupType = assertGroupWrapperType(wrapperType);
const wrapper = getGroupWrapper(wrapperType);
if (wrapper) {
throw new Error(`group: "${wrapperType}" already init`);
}
if (options.length !== 1) {
throw new Error(`group: "${wrapperType}" init needs 1 arguments`);
}
// we need all the fields defined in GroupWrapperConstructor, but the function in the wrapper will throw if we don't forward what's needed
const {
groupEd25519Pubkey,
groupEd25519Secretkey,
metaDumped,
userEd25519Secretkey,
}: GroupWrapperConstructor = options[0];
if (isMetaWrapperType(groupType)) {
const pk = getGroupPubkeyFromWrapperType(groupType);
const wrapper = new MetaGroupWrapperNode({
groupEd25519Pubkey,
groupEd25519Secretkey,
metaDumped,
userEd25519Secretkey,
});
metaGroupWrappers.set(pk, wrapper);
return;
}
assertUnreachable(groupType, `initGroupWrapper: Missing case error "${groupType}"`);
}
onmessage = async (e: { data: [number, ConfigWrapperObjectTypes, string, ...any] }) => {
const [jobId, config, action, ...args] = e.data;
try {
if (action === 'init') {
initUserWrapper(args, config);
postMessage([jobId, null, null]);
return;
if (isUserConfigWrapperType(config)) {
initUserWrapper(args, config);
postMessage([jobId, null, null]);
return;
} else if (isMetaWrapperType(config)) {
initGroupWrapper(args, config);
postMessage([jobId, null, null]);
return;
}
throw new Error('Unhandled init wrapper type:' + config);
}
const wrapper = isUserConfigWrapperType(config)
? getCorrespondingUserWrapper(config)
: isMetaWrapperType(config)
? getCorrespondingGroupWrapper(config)
: undefined;
if (!wrapper) {
throw new Error(`did not find an already built wrapper for config: "${config}"`);
}
const wrapper = getCorrespondingWrapper(config);
const fn = (wrapper as any)[action];
if (!fn) {

@ -26,8 +26,11 @@
"moduleResolution": "node", // Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6).
"resolveJsonModule": true,
// Module Resolution Options
// "baseUrl": "./", // Base directory to resolve non-absolute module names.
// "paths": {}, // A series of entries which re-map imports to lookup locations relative to the 'baseUrl'.
// "baseUrl": "./", // Base directory to resolve non-absolute module names.
// "paths": {
// // A series of entries which re-map imports to lookup locations relative to the 'baseUrl'.
// "@/ducks": ["ts/state/ducks/"]
// },
// "rootDirs": [], // List of root folders whose combined content represents the structure of the project at runtime.
// "typeRoots": [], // List of folders to include type definitions from.
// "types": [], // Type declaration files to be included in compilation.

@ -22,7 +22,7 @@ module.exports = {
},
},
output: {
filename: 'util.worker.js',
filename: 'util.worker.compiled.js',
path: path.resolve(__dirname, 'ts', 'webworker', 'workers', 'node', 'util'),
},
target: 'node',

Loading…
Cancel
Save