/* eslint-disable consistent-return */ /* eslint-disable no-case-declarations */ import { BaseConfigWrapperNode, BlindingWrapperNode, ContactsConfigWrapperNode, ConvoInfoVolatileWrapperNode, GroupPubkeyType, MetaGroupWrapperNode, MultiEncryptWrapperNode, UserConfigWrapperNode, UserGroupsWrapperNode, } from 'libsession_util_nodejs'; import { isEmpty, isNull, isObject } from 'lodash'; import { BlindingConfig, ConfigWrapperGroup, ConfigWrapperObjectTypesMeta, ConfigWrapperUser, MetaGroupConfig, MultiEncryptConfig, isBlindingWrapperType, isMetaGroupWrapperType, isMultiEncryptWrapperType, isUserConfigWrapperType, } from '../../browser/libsession_worker_functions'; /* eslint-disable no-console */ /* eslint-disable strict */ /** * * @param _x Looks like we need to duplicate this function here as we cannot import the existing one from a webworker context */ function assertUnreachable(_x: never, message: string): never { console.info(`assertUnreachable: Didn't expect to get here with "${message}"`); throw new Error("Didn't expect to get here"); } // we can only have one of those so don't worry about storing them in a map for now let userProfileWrapper: UserConfigWrapperNode | undefined; let contactsConfigWrapper: ContactsConfigWrapperNode | undefined; let userGroupsConfigWrapper: UserGroupsWrapperNode | undefined; let convoInfoVolatileConfigWrapper: ConvoInfoVolatileWrapperNode | undefined; const metaGroupWrappers: Map<GroupPubkeyType, MetaGroupWrapperNode> = new Map(); function getUserWrapper(type: ConfigWrapperUser): BaseConfigWrapperNode | undefined { switch (type) { case 'UserConfig': return userProfileWrapper; case 'ContactsConfig': return contactsConfigWrapper; case 'UserGroupsConfig': return userGroupsConfigWrapper; case 'ConvoInfoVolatileConfig': return convoInfoVolatileConfigWrapper; default: assertUnreachable(type, `getUserWrapper: Missing case error "${type}"`); } } 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 (isMetaGroupWrapperType(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 (isMetaGroupWrapperType(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 getMultiEncryptWrapper(wrapperType: MultiEncryptConfig): MultiEncryptWrapperNode { if (isMultiEncryptWrapperType(wrapperType)) { return MultiEncryptWrapperNode; } assertUnreachable(wrapperType, `getMultiEncrypt missing global handling for "${wrapperType}"`); } function getBlindingWrapper(wrapperType: BlindingConfig): BlindingWrapperNode { if (isBlindingWrapperType(wrapperType)) { return BlindingWrapperNode; } assertUnreachable(wrapperType, `getBlindingWrapper missing global handling for "${wrapperType}"`); } function isUInt8Array(value: unknown): value is Uint8Array { return isObject(value) && value.constructor === Uint8Array; } function assertUserWrapperType(wrapperType: ConfigWrapperObjectTypesMeta): ConfigWrapperUser { if (!isUserConfigWrapperType(wrapperType)) { throw new Error(`wrapperType "${wrapperType} is not of type User"`); } return wrapperType; } function assertGroupWrapperType(wrapperType: ConfigWrapperObjectTypesMeta): ConfigWrapperGroup { if (!isMetaGroupWrapperType(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<unknown>, wrapperType: ConfigWrapperUser) { const userType = assertUserWrapperType(wrapperType); const wrapper = getUserWrapper(wrapperType); if (wrapper) { throw new Error(`${wrapperType} already init`); } if (options.length !== 2) { throw new Error(`${wrapperType} init needs two arguments`); } const [edSecretKey, dump] = options; if (isEmpty(edSecretKey) || !isUInt8Array(edSecretKey)) { throw new Error(`${wrapperType} init needs a valid edSecretKey`); } if (!isNull(dump) && !isUInt8Array(dump)) { throw new Error(`${wrapperType} init needs a valid dump`); } switch (userType) { case 'UserConfig': userProfileWrapper = new UserConfigWrapperNode(edSecretKey, dump); break; case 'ContactsConfig': contactsConfigWrapper = new ContactsConfigWrapperNode(edSecretKey, dump); break; case 'UserGroupsConfig': userGroupsConfigWrapper = new UserGroupsWrapperNode(edSecretKey, dump); break; case 'ConvoInfoVolatileConfig': convoInfoVolatileConfigWrapper = new ConvoInfoVolatileWrapperNode(edSecretKey, dump); break; default: assertUnreachable(userType, `initUserWrapper: Missing case error "${userType}"`); } } /** * * This function is used to free wrappers from memory only * * NOTE only use this function for wrappers that have not been saved to the database. * * EXAMPLE When restoring an account and fetching the display name of a user. We want to fetch a UserProfile config message and make a temporary wrapper for it in order to look up the display name. */ function freeUserWrapper(wrapperType: ConfigWrapperObjectTypesMeta) { const userWrapperType = assertUserWrapperType(wrapperType); switch (userWrapperType) { case 'UserConfig': userProfileWrapper = undefined; break; case 'ContactsConfig': contactsConfigWrapper = undefined; break; case 'UserGroupsConfig': userGroupsConfigWrapper = undefined; break; case 'ConvoInfoVolatileConfig': convoInfoVolatileConfigWrapper = undefined; break; default: assertUnreachable( userWrapperType, `freeUserWrapper: Missing case error "${userWrapperType}"` ); } } /* * This function can be used to initialize a group wrapper */ function initGroupWrapper(options: Array<unknown>, wrapperType: ConfigWrapperGroup) { const groupType = assertGroupWrapperType(wrapperType); const wrapper = getGroupWrapper(wrapperType); if (wrapper) { // console.warn(`group: "${wrapperType}" already init`); return; } if (options.length !== 1 || isObject(options[0])) { throw new Error(`group: "${wrapperType}" init needs 1 arguments`); } const firstArg = options[0]; if ( !isObject(firstArg) || !('groupEd25519Pubkey' in firstArg) || !('groupEd25519Secretkey' in firstArg) || !('metaDumped' in firstArg) || !('userEd25519Secretkey' in firstArg) ) { throw new Error( `group: "${wrapperType}" firstArg is not obj type, or missing some keys, or not the correct keys` ); } // 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 } = firstArg; if ( !isUInt8Array(groupEd25519Pubkey) || (!isUInt8Array(groupEd25519Secretkey) && !isNull(groupEd25519Secretkey)) || (!isUInt8Array(metaDumped) && !isNull(metaDumped)) || !isUInt8Array(userEd25519Secretkey) ) { throw new Error(`group: "${wrapperType}" type of keys is not correct`); } if (isMetaGroupWrapperType(groupType)) { const pk = getGroupPubkeyFromWrapperType(groupType); const justCreated = new MetaGroupWrapperNode({ groupEd25519Pubkey, groupEd25519Secretkey, metaDumped, userEd25519Secretkey, }); metaGroupWrappers.set(pk, justCreated); return; } assertUnreachable(groupType, `initGroupWrapper: Missing case error "${groupType}"`); } onmessage = async (e: { data: [number, ConfigWrapperObjectTypesMeta | 'Blinding', string, ...any]; }) => { const [jobId, config, action, ...args] = e.data; try { if (action === 'init') { if (config === 'Blinding' || config === 'MultiEncrypt') { // nothing to do for the blinding/multiEncrypt wrapper, all functions are static postMessage([jobId, null, null]); return; } if (isUserConfigWrapperType(config)) { initUserWrapper(args, config); postMessage([jobId, null, null]); return; } if (isMetaGroupWrapperType(config)) { initGroupWrapper(args, config); postMessage([jobId, null, null]); return; } assertUnreachable(config, `Unhandled init wrapper type: ${config}`); } if (action === 'free') { if (config === 'Blinding' || config === 'MultiEncrypt') { // nothing to do for the blinding/multiEncrypt wrapper, all functions are static postMessage([jobId, null, null]); return; } if (isUserConfigWrapperType(config)) { freeUserWrapper(config); postMessage([jobId, null, null]); return; } if (isMetaGroupWrapperType(config)) { const pk = getGroupPubkeyFromWrapperType(config); metaGroupWrappers.delete(pk); postMessage([jobId, null, null]); return; } assertUnreachable(config, `Unhandled free wrapper type: ${config}`); } const wrapper = isUserConfigWrapperType(config) ? getCorrespondingUserWrapper(config) : isMetaGroupWrapperType(config) ? getCorrespondingGroupWrapper(config) : isMultiEncryptWrapperType(config) ? getMultiEncryptWrapper(config) : isBlindingWrapperType(config) ? getBlindingWrapper(config) : undefined; if (!wrapper) { throw new Error(`did not find an already built (or static) wrapper for config: "${config}"`); } const fn = (wrapper as any)[action]; if (!fn) { throw new Error( `Worker: job "${jobId}" did not find function "${action}" on config "${config}"` ); } const result = await (wrapper as any)[action](...args); postMessage([jobId, null, result]); } catch (error) { const errorForDisplay = prepareErrorForPostMessage(error); postMessage([jobId, errorForDisplay]); } }; function prepareErrorForPostMessage(error: unknown) { if (!error) { return null; } // if (error.stack) { // return error.stack; // } return isObject(error) && 'message' in error ? error.message : 'prepareErrorForPostMessage: unknown error'; }