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.
		
		
		
		
		
			
		
			
				
	
	
		
			219 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			219 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			TypeScript
		
	
| import _ from 'lodash';
 | |
| import { Data, hasSyncedInitialConfigurationItem } from '../data/data';
 | |
| import {
 | |
|   joinOpenGroupV2WithUIEvents,
 | |
|   parseOpenGroupV2,
 | |
| } from '../session/apis/open_group_api/opengroupV2/JoinOpenGroupV2';
 | |
| import { getOpenGroupV2ConversationId } from '../session/apis/open_group_api/utils/OpenGroupUtils';
 | |
| import { SignalService } from '../protobuf';
 | |
| import { getConversationController } from '../session/conversations';
 | |
| import { UserUtils } from '../session/utils';
 | |
| import { toHex } from '../session/utils/String';
 | |
| import { configurationMessageReceived, trigger } from '../shims/events';
 | |
| import { BlockedNumberController } from '../util';
 | |
| import { removeFromCache } from './cache';
 | |
| import { handleNewClosedGroup } from './closedGroups';
 | |
| import { EnvelopePlus } from './types';
 | |
| import { ConversationInteraction } from '../interactions';
 | |
| import { getLastProfileUpdateTimestamp, setLastProfileUpdateTimestamp } from '../util/storage';
 | |
| import { appendFetchAvatarAndProfileJob, updateOurProfileSync } from './userProfileImageUpdates';
 | |
| import { ConversationTypeEnum } from '../models/conversationAttributes';
 | |
| 
 | |
| async function handleOurProfileUpdate(
 | |
|   sentAt: number | Long,
 | |
|   configMessage: SignalService.ConfigurationMessage
 | |
| ) {
 | |
|   const latestProfileUpdateTimestamp = getLastProfileUpdateTimestamp();
 | |
|   if (!latestProfileUpdateTimestamp || sentAt > latestProfileUpdateTimestamp) {
 | |
|     window?.log?.info(
 | |
|       `Handling our profileUdpate ourLastUpdate:${latestProfileUpdateTimestamp}, envelope sent at: ${sentAt}`
 | |
|     );
 | |
|     const { profileKey, profilePicture, displayName } = configMessage;
 | |
| 
 | |
|     const lokiProfile = {
 | |
|       displayName,
 | |
|       profilePicture,
 | |
|     };
 | |
|     await updateOurProfileSync(lokiProfile, profileKey);
 | |
|     await setLastProfileUpdateTimestamp(_.toNumber(sentAt));
 | |
|     // do not trigger a signin by linking if the display name is empty
 | |
|     if (displayName) {
 | |
|       trigger(configurationMessageReceived, displayName);
 | |
|     } else {
 | |
|       window?.log?.warn('Got a configuration message but the display name is empty');
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| async function handleGroupsAndContactsFromConfigMessage(
 | |
|   envelope: EnvelopePlus,
 | |
|   configMessage: SignalService.ConfigurationMessage
 | |
| ) {
 | |
|   const envelopeTimestamp = _.toNumber(envelope.timestamp);
 | |
|   const lastConfigUpdate = await Data.getItemById(hasSyncedInitialConfigurationItem);
 | |
|   const lastConfigTimestamp = lastConfigUpdate?.timestamp;
 | |
|   const isNewerConfig =
 | |
|     !lastConfigTimestamp || (lastConfigTimestamp && lastConfigTimestamp < envelopeTimestamp);
 | |
| 
 | |
|   if (!isNewerConfig) {
 | |
|     window?.log?.info('Received outdated configuration message... Dropping message.');
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   await Data.createOrUpdateItem({
 | |
|     id: 'hasSyncedInitialConfigurationItem',
 | |
|     value: true,
 | |
|     timestamp: envelopeTimestamp,
 | |
|   });
 | |
| 
 | |
|   // we only want to apply changes to closed groups if we never got them
 | |
|   // new opengroups get added when we get a new closed group message from someone, or a sync'ed message from outself creating the group
 | |
|   if (!lastConfigTimestamp) {
 | |
|     await handleClosedGroupsFromConfig(configMessage.closedGroups, envelope);
 | |
|   }
 | |
| 
 | |
|   handleOpenGroupsFromConfig(configMessage.openGroups);
 | |
| 
 | |
|   if (configMessage.contacts?.length) {
 | |
|     await Promise.all(configMessage.contacts.map(async c => handleContactFromConfig(c, envelope)));
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Trigger a join for all open groups we are not already in.
 | |
|  * @param openGroups string array of open group urls
 | |
|  */
 | |
| const handleOpenGroupsFromConfig = (openGroups: Array<string>) => {
 | |
|   const numberOpenGroup = openGroups?.length || 0;
 | |
|   for (let i = 0; i < numberOpenGroup; i++) {
 | |
|     const currentOpenGroupUrl = openGroups[i];
 | |
|     const parsedRoom = parseOpenGroupV2(currentOpenGroupUrl);
 | |
|     if (!parsedRoom) {
 | |
|       continue;
 | |
|     }
 | |
|     const roomConvoId = getOpenGroupV2ConversationId(parsedRoom.serverUrl, parsedRoom.roomId);
 | |
|     if (!getConversationController().get(roomConvoId)) {
 | |
|       window?.log?.info(
 | |
|         `triggering join of public chat '${currentOpenGroupUrl}' from ConfigurationMessage`
 | |
|       );
 | |
|       void joinOpenGroupV2WithUIEvents(currentOpenGroupUrl, false, true);
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Trigger a join for all closed groups which doesn't exist yet
 | |
|  * @param openGroups string array of open group urls
 | |
|  */
 | |
| const handleClosedGroupsFromConfig = async (
 | |
|   closedGroups: Array<SignalService.ConfigurationMessage.IClosedGroup>,
 | |
|   envelope: EnvelopePlus
 | |
| ) => {
 | |
|   const numberClosedGroup = closedGroups?.length || 0;
 | |
| 
 | |
|   window?.log?.info(
 | |
|     `Received ${numberClosedGroup} closed group on configuration. Creating them... `
 | |
|   );
 | |
|   await Promise.all(
 | |
|     closedGroups.map(async c => {
 | |
|       const groupUpdate = new SignalService.DataMessage.ClosedGroupControlMessage({
 | |
|         type: SignalService.DataMessage.ClosedGroupControlMessage.Type.NEW,
 | |
|         encryptionKeyPair: c.encryptionKeyPair,
 | |
|         name: c.name,
 | |
|         admins: c.admins,
 | |
|         members: c.members,
 | |
|         publicKey: c.publicKey,
 | |
|       });
 | |
|       try {
 | |
|         // TODO we should not drop the envelope from cache as long as we are still handling a new closed group from that same envelope
 | |
|         // check the removeFromCache inside handleNewClosedGroup()
 | |
|         await handleNewClosedGroup(envelope, groupUpdate);
 | |
|       } catch (e) {
 | |
|         window?.log?.warn('failed to handle  a new closed group from configuration message');
 | |
|       }
 | |
|     })
 | |
|   );
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Handles adding of a contact and setting approval/block status
 | |
|  * @param contactReceived Contact to sync
 | |
|  */
 | |
| const handleContactFromConfig = async (
 | |
|   contactReceived: SignalService.ConfigurationMessage.IContact,
 | |
|   envelope: EnvelopePlus
 | |
| ) => {
 | |
|   try {
 | |
|     if (!contactReceived.publicKey?.length) {
 | |
|       return;
 | |
|     }
 | |
|     const contactConvo = await getConversationController().getOrCreateAndWait(
 | |
|       toHex(contactReceived.publicKey),
 | |
|       ConversationTypeEnum.PRIVATE
 | |
|     );
 | |
|     const profileInDataMessage: SignalService.DataMessage.ILokiProfile = {
 | |
|       displayName: contactReceived.name,
 | |
|       profilePicture: contactReceived.profilePicture,
 | |
|     };
 | |
| 
 | |
|     const existingActiveAt = contactConvo.get('active_at');
 | |
|     if (!existingActiveAt || existingActiveAt === 0) {
 | |
|       contactConvo.set('active_at', _.toNumber(envelope.timestamp));
 | |
|     }
 | |
| 
 | |
|     // checking for existence of field on protobuf
 | |
|     if (contactReceived.isApproved === true) {
 | |
|       if (!contactConvo.isApproved()) {
 | |
|         await contactConvo.setIsApproved(Boolean(contactReceived.isApproved));
 | |
|         await contactConvo.addOutgoingApprovalMessage(_.toNumber(envelope.timestamp));
 | |
|       }
 | |
| 
 | |
|       if (contactReceived.didApproveMe === true) {
 | |
|         // checking for existence of field on message
 | |
|         await contactConvo.setDidApproveMe(Boolean(contactReceived.didApproveMe));
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // only set for explicit true/false values in case outdated sender doesn't have the fields
 | |
|     if (contactReceived.isBlocked === true) {
 | |
|       if (contactConvo.isIncomingRequest()) {
 | |
|         // handling case where restored device's declined message requests were getting restored
 | |
|         await ConversationInteraction.deleteAllMessagesByConvoIdNoConfirmation(contactConvo.id);
 | |
|       }
 | |
|       await BlockedNumberController.block(contactConvo.id);
 | |
|     } else if (contactReceived.isBlocked === false) {
 | |
|       await BlockedNumberController.unblock(contactConvo.id);
 | |
|     }
 | |
| 
 | |
|     void appendFetchAvatarAndProfileJob(
 | |
|       contactConvo,
 | |
|       profileInDataMessage,
 | |
|       contactReceived.profileKey
 | |
|     );
 | |
|   } catch (e) {
 | |
|     window?.log?.warn('failed to handle  a new closed group from configuration message');
 | |
|   }
 | |
| };
 | |
| 
 | |
| export async function handleConfigurationMessage(
 | |
|   envelope: EnvelopePlus,
 | |
|   configurationMessage: SignalService.ConfigurationMessage
 | |
| ): Promise<void> {
 | |
|   window?.log?.info('Handling configuration message');
 | |
|   const ourPubkey = UserUtils.getOurPubKeyStrFromCache();
 | |
|   if (!ourPubkey) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (envelope.source !== ourPubkey) {
 | |
|     window?.log?.info('Dropping configuration change from someone else than us.');
 | |
|     return removeFromCache(envelope);
 | |
|   }
 | |
| 
 | |
|   await handleOurProfileUpdate(envelope.timestamp, configurationMessage);
 | |
| 
 | |
|   await handleGroupsAndContactsFromConfigMessage(envelope, configurationMessage);
 | |
| 
 | |
|   await removeFromCache(envelope);
 | |
| }
 |