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 incase 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);
 | 
						|
}
 |