import { uniq } from 'lodash'; import { ConvoInfoVolatile1o1, ConvoInfoVolatileCommunity, ConvoInfoVolatileLegacyGroup, ConvoVolatileType, } from 'session_util_wrapper'; import { OpenGroupData } from '../../../data/opengroups'; import { ConversationModel } from '../../../models/conversation'; import { assertUnreachable } from '../../../types/sqlSharedTypes'; import { ConvoInfoVolatileWrapperActions, UserGroupsWrapperActions, } from '../../../webworker/workers/browser/libsession_worker_interface'; import { OpenGroupUtils } from '../../apis/open_group_api/utils'; import { getConversationController } from '../../conversations'; import { SessionUtilContact } from './libsession_utils_contacts'; import { SessionUtilUserGroups } from './libsession_utils_user_groups'; /** * The key of this map is the convoId as stored in the database. */ const mapped1o1WrapperValues = new Map(); /** * The key of this map is the convoId as stored in the database. So the legacy group 05 sessionID */ const mappedLegacyGroupWrapperValues = new Map(); /** * The key of this map is the convoId as stored in the database, so withoutpubkey */ const mappedCommunityWrapperValues = new Map(); /** * Update the ConvoInfoVolatileWrapper with all the data is cares about from the database. */ async function insertAllConvosIntoWrapper() { const convoIdsToInsert = uniq( getConversationController() .getConversations() .filter(isConvoToStoreInWrapper) .map(m => m.id) ); window.log.debug( `ConvoInfoVolatileWrapper keep tracks of ${convoIdsToInsert.length} convos in total.` ); for (let index = 0; index < convoIdsToInsert.length; index++) { const id = convoIdsToInsert[index]; await insertConvoFromDBIntoWrapperAndRefresh(id); } } /** * Returns true if that conversation should be stored in the conversation volatile info wrapper. * It actually relies on the two other wrappers to know what to store: * - Usergroups to know which communities and legacy group to store * - Contacts to know which contacts to store */ function isConvoToStoreInWrapper(convo: ConversationModel): boolean { return ( SessionUtilUserGroups.isUserGroupToStoreInWrapper(convo) || // this checks for community & legacy group SessionUtilContact.isContactToStoreInContactsWrapper(convo) // this checks for contacts ); } function getConvoType(convo: ConversationModel): ConvoVolatileType { const convoType: ConvoVolatileType = SessionUtilContact.isContactToStoreInContactsWrapper(convo) ? '1o1' : SessionUtilUserGroups.isCommunityToStoreInWrapper(convo) ? 'Community' : 'LegacyGroup'; return convoType; } /** * Fetches the specified convo and updates the required field in the wrapper. * If that community does not exist in the wrapper, it is created before being updated. * Same applies for a legacy group. */ async function insertConvoFromDBIntoWrapperAndRefresh(convoId: string): Promise { const foundConvo = getConversationController().get(convoId); if (!foundConvo) { return; } if (!isConvoToStoreInWrapper(foundConvo)) { return; } console.info(`inserting into convoInfoVolatile wrapper "${convoId}"...`); console.warn('ConvoInfoVolatileWrapperActions to finish with unread and readAt'); const convoType = getConvoType(foundConvo); switch (convoType) { case '1o1': try { await ConvoInfoVolatileWrapperActions.set1o1(convoId, 0, false); await refreshMappedValue(convoId, false, false); } catch (e) { window.log.warn( `ConvoInfoVolatileWrapperActions.set1o1 of ${convoId} failed with ${e.message}` ); } break; case 'LegacyGroup': try { await ConvoInfoVolatileWrapperActions.setLegacyGroup(convoId, 0, false); await refreshMappedValue(convoId, true, false); } catch (e) { window.log.warn( `ConvoInfoVolatileWrapperActions.setLegacyGroup of ${convoId} failed with ${e.message}` ); // we stil let this go through } break; case 'Community': try { const asOpengroup = foundConvo.toOpenGroupV2(); const roomDetails = OpenGroupData.getV2OpenGroupRoomByRoomId(asOpengroup); if (!roomDetails) { return; } // we need to build the full URL with the pubkey so we can add it to the wrapper. Let's reuse the exposed method from the wrapper for that const fullUrlWithPubkey = await UserGroupsWrapperActions.buildFullUrlFromDetails( roomDetails.serverUrl, roomDetails.roomId, roomDetails.serverPublicKey ); console.info(`inserting into convoInfoVolatile wrapper "${fullUrlWithPubkey}"...`); // this does the create or the update of the matching existing community await ConvoInfoVolatileWrapperActions.setCommunityByFullUrl(fullUrlWithPubkey, 0, false); await refreshMappedValue(convoId, false, false); } catch (e) { window.log.warn( `ConvoInfoVolatileWrapperActions.setCommunityByFullUrl of ${convoId} failed with ${e.message}` ); // we still let this go through } break; default: assertUnreachable( convoType, `insertConvoFromDBIntoWrapperAndRefresh unhandled case "${convoType}"` ); } } /** * @param isLegacyGroup we need this to know if the corresponding 05 starting pubkey is associated with a legacy group or not * @param duringAppStart set this to true if we should just fetch the cached value but not trigger a UI refresh of the corresponding conversation */ async function refreshMappedValue( convoId: string, isLegacyGroup: boolean, duringAppStart: boolean ) { try { let refreshed = false; if (OpenGroupUtils.isOpenGroupV2(convoId)) { const fromWrapper = await ConvoInfoVolatileWrapperActions.getCommunity(convoId); if (fromWrapper && fromWrapper.fullUrlWithPubkey) { mappedCommunityWrapperValues.set(convoId, fromWrapper); } refreshed = true; } else if (convoId.startsWith('05') && isLegacyGroup) { const fromWrapper = await ConvoInfoVolatileWrapperActions.getLegacyGroup(convoId); if (fromWrapper) { mappedLegacyGroupWrapperValues.set(convoId, fromWrapper); } refreshed = true; } else if (convoId.startsWith('05')) { const fromWrapper = await ConvoInfoVolatileWrapperActions.get1o1(convoId); if (fromWrapper) { mapped1o1WrapperValues.set(convoId, fromWrapper); } refreshed = true; } if (refreshed && !duringAppStart) { getConversationController() .get(convoId) ?.triggerUIRefresh(); } } catch (e) { window.log.info(`refreshMappedValue for volatile convoID: ${convoId}`, e.message); } // TODO handle the new closed groups once we got them ready } function get1o1(convoId: string): ConvoInfoVolatile1o1 | undefined { return mapped1o1WrapperValues.get(convoId); } function getAll1o1(): Array { return [...mapped1o1WrapperValues.values()]; } function getCommunityMappedValueByConvoId(convoId: string) { return mappedCommunityWrapperValues.get(convoId); } function getAllCommunities(): Array { return [...mappedCommunityWrapperValues.values()]; } function getLegacyGroupMappedValueByConvoId(convoId: string) { return mappedLegacyGroupWrapperValues.get(convoId); } function getAllLegacyGroups(): Array { return [...mappedLegacyGroupWrapperValues.values()]; } function getFromAny(convoId: string) { return ( mapped1o1WrapperValues.get(convoId) || mappedLegacyGroupWrapperValues.get(convoId) || mappedCommunityWrapperValues.get(convoId) ); } /** * This function can be used where there are things to do for all the types handled by this wrapper. * You can do a loop on all the types handled by this wrapper and have a switch using assertUnreachable to get errors when not every case is handled. * * * Note: Ideally, we'd like to have this type in the wrapper index.d.ts, but it would require it to be a index.ts instead, which causes a whole other bunch of issues because it is a native node module. */ function getConvoInfoVolatileTypes(): Array { return ['1o1', 'LegacyGroup', 'Community']; } export const SessionUtilConvoInfoVolatile = { // shared isConvoToStoreInWrapper, insertAllConvosIntoWrapper, insertConvoFromDBIntoWrapperAndRefresh, refreshMappedValue, getConvoInfoVolatileTypes, // 1o1 get1o1, getAll1o1, // removeCommunityFromWrapper, // legacy group getLegacyGroupMappedValueByConvoId, getAllLegacyGroups, // removeLegacyGroupFromWrapper, // a group can be removed but also just marked hidden, so only call this function when the group is completely removed // TODO // communities getAllCommunities, getCommunityMappedValueByConvoId, // removeCommunityFromWrapper, getFromAny, };