You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			213 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			213 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			TypeScript
		
	
/* eslint-disable no-case-declarations */
 | 
						|
import { CommunityInfo, UserGroupsType } from 'libsession_util_nodejs';
 | 
						|
import { Data } from '../../../data/data';
 | 
						|
import { OpenGroupData } from '../../../data/opengroups';
 | 
						|
import { ConversationModel } from '../../../models/conversation';
 | 
						|
import {
 | 
						|
  assertUnreachable,
 | 
						|
  getCommunityInfoFromDBValues,
 | 
						|
  getLegacyGroupInfoFromDBValues,
 | 
						|
} from '../../../types/sqlSharedTypes';
 | 
						|
import { UserGroupsWrapperActions } from '../../../webworker/workers/browser/libsession_worker_interface';
 | 
						|
import { getConversationController } from '../../conversations';
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns true if that conversation is an active group
 | 
						|
 */
 | 
						|
function isUserGroupToStoreInWrapper(convo: ConversationModel): boolean {
 | 
						|
  return isCommunityToStoreInWrapper(convo) || isLegacyGroupToStoreInWrapper(convo);
 | 
						|
}
 | 
						|
 | 
						|
function isCommunityToStoreInWrapper(convo: ConversationModel): boolean {
 | 
						|
  return convo.isGroup() && convo.isPublic() && convo.isActive();
 | 
						|
}
 | 
						|
 | 
						|
function isLegacyGroupToStoreInWrapper(convo: ConversationModel): boolean {
 | 
						|
  return (
 | 
						|
    convo.isGroup() &&
 | 
						|
    !convo.isPublic() &&
 | 
						|
    convo.id.startsWith('05') && // new closed groups won't start with 05
 | 
						|
    convo.isActive() &&
 | 
						|
    !convo.get('isKickedFromGroup') &&
 | 
						|
    !convo.get('left')
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * We do not want to include groups left in the wrapper, but when receiving a list
 | 
						|
 * of wrappers from the network we need to check against the one present locally
 | 
						|
 * but already left, to know we need to remove them.
 | 
						|
 *
 | 
						|
 * This is to take care of this case:
 | 
						|
 * - deviceA creates group
 | 
						|
 * - deviceB joins group
 | 
						|
 * - deviceA leaves the group
 | 
						|
 * - deviceB leaves the group
 | 
						|
 * - deviceA removes the group entirely from the wrapper
 | 
						|
 * - deviceB receives the wrapper update and needs to remove the group from the DB
 | 
						|
 *
 | 
						|
 * But, as the group was already left, it would not be accounted for by `isLegacyGroupToStoreInWrapper`
 | 
						|
 *
 | 
						|
 */
 | 
						|
function isLegacyGroupToRemoveFromDBIfNotInWrapper(convo: ConversationModel): boolean {
 | 
						|
  // this filter is based on `isLegacyGroupToStoreInWrapper`
 | 
						|
  return (
 | 
						|
    convo.isGroup() && !convo.isPublic() && convo.id.startsWith('05') // new closed groups won't start with 05
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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 insertGroupsFromDBIntoWrapperAndRefresh(convoId: string): Promise<void> {
 | 
						|
  const foundConvo = getConversationController().get(convoId);
 | 
						|
  if (!foundConvo) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!isUserGroupToStoreInWrapper(foundConvo)) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  const convoType: UserGroupsType = isCommunityToStoreInWrapper(foundConvo)
 | 
						|
    ? 'Community'
 | 
						|
    : 'LegacyGroup';
 | 
						|
 | 
						|
  switch (convoType) {
 | 
						|
    case 'Community':
 | 
						|
      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 fullUrl = await UserGroupsWrapperActions.buildFullUrlFromDetails(
 | 
						|
        roomDetails.serverUrl,
 | 
						|
        roomDetails.roomId,
 | 
						|
        roomDetails.serverPublicKey
 | 
						|
      );
 | 
						|
 | 
						|
      const wrapperComm = getCommunityInfoFromDBValues({
 | 
						|
        priority: foundConvo.get('priority'),
 | 
						|
        fullUrl,
 | 
						|
      });
 | 
						|
 | 
						|
      try {
 | 
						|
        window.log.debug(`inserting into usergroup wrapper "${JSON.stringify(wrapperComm)}"...`);
 | 
						|
        // this does the create or the update of the matching existing community
 | 
						|
        await UserGroupsWrapperActions.setCommunityByFullUrl(
 | 
						|
          wrapperComm.fullUrl,
 | 
						|
          wrapperComm.priority
 | 
						|
        );
 | 
						|
      } catch (e) {
 | 
						|
        window.log.warn(`UserGroupsWrapperActions.set of ${convoId} failed with ${e.message}`);
 | 
						|
        // we still let this go through
 | 
						|
      }
 | 
						|
      break;
 | 
						|
 | 
						|
    case 'LegacyGroup':
 | 
						|
      const encryptionKeyPair = await Data.getLatestClosedGroupEncryptionKeyPair(convoId);
 | 
						|
      const wrapperLegacyGroup = getLegacyGroupInfoFromDBValues({
 | 
						|
        id: foundConvo.id,
 | 
						|
        priority: foundConvo.get('priority'),
 | 
						|
        members: foundConvo.get('members') || [],
 | 
						|
        groupAdmins: foundConvo.get('groupAdmins') || [],
 | 
						|
        // expireTimer: foundConvo.get('expireTimer'), // FIXME WILL add expirationMode here
 | 
						|
        displayNameInProfile: foundConvo.get('displayNameInProfile'),
 | 
						|
        encPubkeyHex: encryptionKeyPair?.publicHex || '',
 | 
						|
        encSeckeyHex: encryptionKeyPair?.privateHex || '',
 | 
						|
        lastJoinedTimestamp: foundConvo.get('lastJoinedTimestamp') || 0,
 | 
						|
      });
 | 
						|
 | 
						|
      try {
 | 
						|
        window.log.debug(
 | 
						|
          `inserting into usergroup wrapper "${foundConvo.id}"... }`,
 | 
						|
          JSON.stringify(wrapperLegacyGroup)
 | 
						|
        );
 | 
						|
        // this does the create or the update of the matching existing legacy group
 | 
						|
        await UserGroupsWrapperActions.setLegacyGroup(wrapperLegacyGroup);
 | 
						|
      } catch (e) {
 | 
						|
        window.log.warn(`UserGroupsWrapperActions.set of ${convoId} failed with ${e.message}`);
 | 
						|
        // we still let this go through
 | 
						|
      }
 | 
						|
      break;
 | 
						|
 | 
						|
    default:
 | 
						|
      assertUnreachable(
 | 
						|
        convoType,
 | 
						|
        `insertGroupsFromDBIntoWrapperAndRefresh case not handeld "${convoType}"`
 | 
						|
      );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
async function getCommunityByConvoIdNotCached(convoId: string) {
 | 
						|
  return UserGroupsWrapperActions.getCommunityByFullUrl(convoId);
 | 
						|
}
 | 
						|
 | 
						|
async function getAllCommunitiesNotCached(): Promise<Array<CommunityInfo>> {
 | 
						|
  return UserGroupsWrapperActions.getAllCommunities();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Removes the matching community from the wrapper and from the cached list of communities
 | 
						|
 */
 | 
						|
async function removeCommunityFromWrapper(_convoId: string, fullUrlWithOrWithoutPubkey: string) {
 | 
						|
  const fromWrapper = await UserGroupsWrapperActions.getCommunityByFullUrl(
 | 
						|
    fullUrlWithOrWithoutPubkey
 | 
						|
  );
 | 
						|
 | 
						|
  if (fromWrapper) {
 | 
						|
    await UserGroupsWrapperActions.eraseCommunityByFullUrl(fromWrapper.fullUrlWithPubkey);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Remove the matching legacy group from the wrapper and from the cached list of legacy groups
 | 
						|
 */
 | 
						|
async function removeLegacyGroupFromWrapper(groupPk: string) {
 | 
						|
  try {
 | 
						|
    await UserGroupsWrapperActions.eraseLegacyGroup(groupPk);
 | 
						|
  } catch (e) {
 | 
						|
    window.log.warn(
 | 
						|
      `UserGroupsWrapperActions.eraseLegacyGroup with = ${groupPk} failed with`,
 | 
						|
      e.message
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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 getUserGroupTypes(): Array<UserGroupsType> {
 | 
						|
  return ['Community', 'LegacyGroup'];
 | 
						|
}
 | 
						|
 | 
						|
export const SessionUtilUserGroups = {
 | 
						|
  // shared
 | 
						|
  isUserGroupToStoreInWrapper,
 | 
						|
  insertGroupsFromDBIntoWrapperAndRefresh,
 | 
						|
  getUserGroupTypes,
 | 
						|
 | 
						|
  // communities
 | 
						|
  isCommunityToStoreInWrapper,
 | 
						|
  getAllCommunitiesNotCached,
 | 
						|
  getCommunityByConvoIdNotCached,
 | 
						|
  removeCommunityFromWrapper,
 | 
						|
 | 
						|
  // legacy group
 | 
						|
  isLegacyGroupToStoreInWrapper,
 | 
						|
  isLegacyGroupToRemoveFromDBIfNotInWrapper,
 | 
						|
 | 
						|
  removeLegacyGroupFromWrapper, // a group can be removed but also just marked hidden, so only call this function when the group is completely removed // TODOLATER
 | 
						|
};
 |