From db98cc28125d8d023a1461d94b56caeba31a7a1f Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 7 Sep 2023 13:32:45 +1000 Subject: [PATCH] feat: able to create a broken closedgroup v3 --- .eslintrc.js | 5 + .gitignore | 2 +- libsession.worker.config.js | 5 +- preload.js | 2 +- ts/components/SessionInboxView.tsx | 2 + ts/node/hexStrings.ts | 37 +++ ts/receiver/configMessage.ts | 68 +++-- ts/session/apis/snode_api/swarmPolling.ts | 7 +- .../conversations/ConversationController.ts | 9 - ts/session/conversations/createClosedGroup.ts | 40 ++- ts/session/crypto/index.ts | 17 -- ts/session/group/closed-group.ts | 21 +- ts/session/types/PubKey.ts | 3 +- .../job_runners/jobs/ConfigurationSyncJob.ts | 5 + .../utils/libsession/libsession_utils.ts | 75 +++++- ts/state/actions.ts | 2 + ts/state/createStore.ts | 1 - ts/state/ducks/groupInfos.ts | 144 +++++++++++ ts/state/reducer.ts | 3 + ts/types/sqlSharedTypes.ts | 17 +- .../browser/libsession_worker_functions.d.ts | 38 --- .../browser/libsession_worker_functions.ts | 81 ++++++ .../browser/libsession_worker_interface.ts | 243 +++++++++++++++--- .../workers/browser/util_worker_interface.ts | 2 +- .../node/libsession/libsession.worker.ts | 168 +++++++++--- tsconfig.json | 7 +- utils.worker.config.js | 2 +- 27 files changed, 795 insertions(+), 211 deletions(-) create mode 100644 ts/node/hexStrings.ts create mode 100644 ts/state/ducks/groupInfos.ts delete mode 100644 ts/webworker/workers/browser/libsession_worker_functions.d.ts create mode 100644 ts/webworker/workers/browser/libsession_worker_functions.ts diff --git a/.eslintrc.js b/.eslintrc.js index 6b9ace6d5..9a1156569 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,6 +5,11 @@ module.exports = { react: { version: 'detect', }, + 'import/resolver': { + node: { + paths: [path.resolve(__dirname)], + }, + }, }, extends: [ diff --git a/.gitignore b/.gitignore index a57cb1a51..29385777f 100644 --- a/.gitignore +++ b/.gitignore @@ -50,5 +50,5 @@ test-results/ coverage/ stylesheets/dist/ -*.worker.js.LICENSE.txt +*.LICENSE.txt ts/webworker/workers/node/**/*.node diff --git a/libsession.worker.config.js b/libsession.worker.config.js index fe1741bea..7d546c90a 100644 --- a/libsession.worker.config.js +++ b/libsession.worker.config.js @@ -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, }, }; diff --git a/preload.js b/preload.js index 643e7938f..f8339fd4b 100644 --- a/preload.js +++ b/preload.js @@ -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), diff --git a/ts/components/SessionInboxView.tsx b/ts/components/SessionInboxView.tsx index fe3ced550..26c59411c 100644 --- a/ts/components/SessionInboxView.tsx +++ b/ts/components/SessionInboxView.tsx @@ -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(items: Array, 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); diff --git a/ts/node/hexStrings.ts b/ts/node/hexStrings.ts new file mode 100644 index 000000000..574e19178 --- /dev/null +++ b/ts/node/hexStrings.ts @@ -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, +}; diff --git a/ts/receiver/configMessage.ts b/ts/receiver/configMessage.ts index de5ab601a..5a20d93a7 100644 --- a/ts/receiver/configMessage.ts +++ b/ts/receiver/configMessage.ts @@ -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> ): Promise> { @@ -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 { - 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. diff --git a/ts/session/conversations/createClosedGroup.ts b/ts/session/conversations/createClosedGroup.ts index 8a6719353..fa7f48782 100644 --- a/ts/session/conversations/createClosedGroup.ts +++ b/ts/session/conversations/createClosedGroup.ts @@ -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, 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 = [ +const requiredUserVariants: Array = [ 'UserConfig', 'ContactsConfig', 'UserGroupsConfig', @@ -51,27 +63,31 @@ async function initializeLibSessionUtilWrappers() { JSON.stringify(dumps.map(m => omit(m, 'data'))) ); - const userVariantsBuildWithoutErrors = new Set(); + const userVariantsBuildWithoutErrors = new Set(); // 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 = difference( + const missingRequiredVariants: Array = 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> { @@ -119,6 +168,10 @@ async function pendingChangesForPubkey(pubkey: string): Promise; +}; + +export const initialGroupInfosState: GroupInfosState = { + infos: {}, +}; + +const updateGroupInfoInWrapper = createAsyncThunk( + 'groupInfos/updateGroupInfoInWrapper', + async ({ + id, + data, + }: { + id: GroupPubkeyType; + data: GroupInfoShared; + }): Promise => { + // 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; + }): Promise => { + 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>) { + // 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; diff --git a/ts/state/reducer.ts b/ts/state/reducer.ts index c6db627a7..0ddaf641a 100644 --- a/ts/state/reducer.ts +++ b/ts/state/reducer.ts @@ -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 diff --git a/ts/types/sqlSharedTypes.ts b/ts/types/sqlSharedTypes.ts index 35cd5c396..39756b6a2 100644 --- a/ts/types/sqlSharedTypes.ts +++ b/ts/types/sqlSharedTypes.ts @@ -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 | null) { export function roomHasReactionsEnabled(openGroup?: OpenGroupV2Room) { return Boolean(openGroup?.capabilities?.includes('reactions')); } + +export function toFixedUint8ArrayOfLength( + data: Uint8Array, + length: T +): FixedSizeUint8Array { + if (data.length === length) return (data as any) as FixedSizeUint8Array; + throw new Error( + `toFixedUint8ArrayOfLength invalid. Expected length ${length} but got: ${data.length}` + ); +} diff --git a/ts/webworker/workers/browser/libsession_worker_functions.d.ts b/ts/webworker/workers/browser/libsession_worker_functions.d.ts deleted file mode 100644 index 4cb004816..000000000 --- a/ts/webworker/workers/browser/libsession_worker_functions.d.ts +++ /dev/null @@ -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; diff --git a/ts/webworker/workers/browser/libsession_worker_functions.ts b/ts/webworker/workers/browser/libsession_worker_functions.ts new file mode 100644 index 000000000..d2301c631 --- /dev/null +++ b/ts/webworker/workers/browser/libsession_worker_functions.ts @@ -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 +} + + diff --git a/ts/webworker/workers/browser/libsession_worker_interface.ts b/ts/webworker/workers/browser/libsession_worker_interface.ts index 6ae6f734e..b8e2454b3 100644 --- a/ts/webworker/workers/browser/libsession_worker_interface.ts +++ b/ts/webworker/workers/browser/libsession_worker_interface.ts @@ -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, - 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 + ) => Promise; + confirmPushed: GenericWrapperActionsCall; + dump: GenericWrapperActionsCall; + merge: GenericWrapperActionsCall; + needsDump: GenericWrapperActionsCall; + needsPush: GenericWrapperActionsCall; + push: GenericWrapperActionsCall; + storageNamespace: GenericWrapperActionsCall; + currentHashes: GenericWrapperActionsCall; +}; + +// 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 + + 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 + dump: async (wrapperId: ConfigWrapperUser) => + callLibSessionWorker([wrapperId, 'dump']) as ReturnType, + 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 + needsDump: async (wrapperId: ConfigWrapperUser) => + callLibSessionWorker([wrapperId, 'needsDump']) as ReturnType< + GenericWrapperActionsCalls['needsDump'] >, - push: async (wrapperId: ConfigWrapperObjectTypes) => - callLibSessionWorker([wrapperId, 'push']) as Promise< - ReturnType + needsPush: async (wrapperId: ConfigWrapperUser) => + callLibSessionWorker([wrapperId, 'needsPush']) as ReturnType< + GenericWrapperActionsCalls['needsPush'] >, - storageNamespace: async (wrapperId: ConfigWrapperObjectTypes) => - callLibSessionWorker([wrapperId, 'storageNamespace']) as Promise< - ReturnType + push: async (wrapperId: ConfigWrapperUser) => + callLibSessionWorker([wrapperId, 'push']) as ReturnType, + storageNamespace: async (wrapperId: ConfigWrapperUser) => + callLibSessionWorker([wrapperId, 'storageNamespace']) as ReturnType< + GenericWrapperActionsCalls['storageNamespace'] >, - currentHashes: async (wrapperId: ConfigWrapperObjectTypes) => - callLibSessionWorker([wrapperId, 'currentHashes']) as Promise< - ReturnType + 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 >, + + createGroup: async () => + callLibSessionWorker(['UserGroupsConfig', 'createGroup']) as Promise< + ReturnType + >, + + getGroup: async (pubkeyHex: GroupPubkeyType) => + callLibSessionWorker(['UserGroupsConfig', 'getGroup', pubkeyHex]) as Promise< + ReturnType + >, }; export const ConvoInfoVolatileWrapperActions: ConvoInfoVolatileWrapperActionsCalls = { @@ -333,6 +360,144 @@ export const ConvoInfoVolatileWrapperActions: ConvoInfoVolatileWrapperActionsCal ]) as Promise>, }; +export const MetaGroupWrapperActions: MetaGroupWrapperActionsCalls = { + /** Shared actions */ + init: async (groupPk: GroupPubkeyType, options: GroupWrapperConstructor) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'init', options]) as Promise< + ReturnType + >, + needsPush: async (groupPk: GroupPubkeyType) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'needsPush']) as Promise< + ReturnType + >, + push: async (groupPk: GroupPubkeyType) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'push']) as Promise< + ReturnType + >, + needsDump: async (groupPk: GroupPubkeyType) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'needsDump']) as Promise< + ReturnType + >, + metaDump: async (groupPk: GroupPubkeyType) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'metaDump']) as Promise< + ReturnType + >, + + /** GroupInfo wrapper specific actions */ + infoGet: async (groupPk: GroupPubkeyType) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'infoGet']) as Promise< + ReturnType + >, + infoSet: async (groupPk: GroupPubkeyType, infos: GroupInfoSet) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'infoSet', infos]) as Promise< + ReturnType + >, + infoDestroy: async (groupPk: GroupPubkeyType) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'infoDestroy']) as Promise< + ReturnType + >, + + /** GroupMembers wrapper specific actions */ + memberGet: async (groupPk: GroupPubkeyType, pubkeyHex: string) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'memberGet', pubkeyHex]) as Promise< + ReturnType + >, + memberGetOrConstruct: async (groupPk: GroupPubkeyType, pubkeyHex: string) => + callLibSessionWorker([ + `MetaGroupConfig-${groupPk}`, + 'memberGetOrConstruct', + pubkeyHex, + ]) as Promise>, + memberGetAll: async (groupPk: GroupPubkeyType) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'memberGetAll']) as Promise< + ReturnType + >, + memberErase: async (groupPk: GroupPubkeyType, pubkeyHex: string) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'memberErase', pubkeyHex]) as Promise< + ReturnType + >, + memberSetAccepted: async (groupPk: GroupPubkeyType, pubkeyHex: string) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'memberSetAccepted', pubkeyHex]) as Promise< + ReturnType + >, + memberSetPromoted: async (groupPk: GroupPubkeyType, pubkeyHex: string, failed: boolean) => + callLibSessionWorker([ + `MetaGroupConfig-${groupPk}`, + 'memberSetPromoted', + pubkeyHex, + failed, + ]) as Promise>, + memberSetInvited: async (groupPk: GroupPubkeyType, pubkeyHex: string, failed: boolean) => + callLibSessionWorker([ + `MetaGroupConfig-${groupPk}`, + 'memberSetInvited', + pubkeyHex, + failed, + ]) as Promise>, + memberSetName: async (groupPk: GroupPubkeyType, pubkeyHex: string, name: string) => + callLibSessionWorker([ + `MetaGroupConfig-${groupPk}`, + 'memberSetName', + pubkeyHex, + name, + ]) as Promise>, + memberSetProfilePicture: async ( + groupPk: GroupPubkeyType, + pubkeyHex: string, + profilePicture: ProfilePicture + ) => + callLibSessionWorker([ + `MetaGroupConfig-${groupPk}`, + 'memberSetProfilePicture', + pubkeyHex, + profilePicture, + ]) as Promise>, + + /** GroupKeys wrapper specific actions */ + + keyRekey: async (groupPk: GroupPubkeyType) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'keyRekey']) as Promise< + ReturnType + >, + keysNeedsRekey: async (groupPk: GroupPubkeyType) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'keysNeedsRekey']) as Promise< + ReturnType + >, + groupKeys: async (groupPk: GroupPubkeyType) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'groupKeys']) as Promise< + ReturnType + >, + currentHashes: async (groupPk: GroupPubkeyType) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'currentHashes']) as Promise< + ReturnType + >, + + loadKeyMessage: async ( + groupPk: GroupPubkeyType, + hash: string, + data: Uint8Array, + timestampMs: number + ) => + callLibSessionWorker([ + `MetaGroupConfig-${groupPk}`, + 'loadKeyMessage', + hash, + data, + timestampMs, + ]) as Promise>, + encryptMessage: async (groupPk: GroupPubkeyType, plaintext: Uint8Array, compress: boolean) => + callLibSessionWorker([ + `MetaGroupConfig-${groupPk}`, + 'encryptMessage', + plaintext, + compress, + ]) as Promise>, + decryptMessage: async (groupPk: GroupPubkeyType, ciphertext: Uint8Array) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'decryptMessage', ciphertext]) as Promise< + ReturnType + >, +}; + export const callLibSessionWorker = async ( callToMake: LibSessionWorkerFunctions ): Promise => { diff --git a/ts/webworker/workers/browser/util_worker_interface.ts b/ts/webworker/workers/browser/util_worker_interface.ts index cafef5cd9..f2fb842a3 100644 --- a/ts/webworker/workers/browser/util_worker_interface.ts +++ b/ts/webworker/workers/browser/util_worker_interface.ts @@ -25,7 +25,7 @@ const internalCallUtilsWorker = async ( 'workers', 'node', 'util', - 'util.worker.js' + 'util.worker.compiled.js' ); utilWorkerInterface = new WorkerInterface(utilWorkerPath, 3 * 60 * 1000); } diff --git a/ts/webworker/workers/node/libsession/libsession.worker.ts b/ts/webworker/workers/node/libsession/libsession.worker.ts index 75100a511..1c0ad1f1b 100644 --- a/ts/webworker/workers/node/libsession/libsession.worker.ts +++ b/ts/webworker/workers/node/libsession/libsession.worker.ts @@ -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 = 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, wrapperType: ConfigWrapperObjectTypes) { +function initUserWrapper(options: Array, wrapperType: ConfigWrapperUser) { const userType = assertUserWrapperType(wrapperType); const wrapper = getUserWrapper(wrapperType); @@ -119,16 +170,69 @@ function initUserWrapper(options: Array, wrapperType: ConfigWrapperObjectTy } } +/** + * This function can be used to initialize a group wrapper + */ +function initGroupWrapper(options: Array, 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) { diff --git a/tsconfig.json b/tsconfig.json index a47a46a27..c28124169 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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. diff --git a/utils.worker.config.js b/utils.worker.config.js index 001dddb40..284dca0f5 100644 --- a/utils.worker.config.js +++ b/utils.worker.config.js @@ -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',