session-desktop/ts/receiver/syncMessages.ts
2021-01-18 10:35:16 +11:00

307 lines
8.6 KiB
TypeScript

import { EnvelopePlus } from './types';
import { SignalService } from '../protobuf';
import { removeFromCache } from './cache';
import { getEnvelopeId } from './common';
import _ from 'lodash';
import ByteBuffer from 'bytebuffer';
import {
handleMessageEvent,
isMessageEmpty,
processDecrypted,
updateProfile,
} from './dataMessage';
import { handleContacts } from './multidevice';
import { MultiDeviceProtocol } from '../session/protocols';
import { BlockedNumberController } from '../util';
import { ConversationController } from '../session/conversations';
export async function handleSyncMessage(
envelope: EnvelopePlus,
syncMessage: SignalService.ISyncMessage
): Promise<void> {
const { textsecure } = window;
// We should only accept sync messages from our devices
const ourNumber = textsecure.storage.user.getNumber();
const ourDevices = await MultiDeviceProtocol.getAllDevices(ourNumber);
const validSyncSender = ourDevices.some(
device => device.key === envelope.source
);
if (!validSyncSender) {
throw new Error(
"Received sync message from a device we aren't paired with"
);
}
// remove empty fields (generated by ts even if they should be null)
if (syncMessage.openGroups && !syncMessage.openGroups.length) {
syncMessage.openGroups = null;
}
if (syncMessage.read && !syncMessage.read.length) {
syncMessage.read = null;
}
if (syncMessage.sent) {
const sentMessage = syncMessage.sent;
const message = sentMessage.message as SignalService.IDataMessage;
const to = message.group
? `group(${message.group.id})`
: sentMessage.destination;
window.log.info(
'sent message to',
to,
_.toNumber(sentMessage.timestamp),
'from',
getEnvelopeId(envelope)
);
await handleSentMessage(envelope, sentMessage);
} else if (syncMessage.contacts) {
return handleContacts(envelope, syncMessage.contacts);
} else if (syncMessage.groups) {
await handleGroupsSync(envelope, syncMessage.groups);
} else if (syncMessage.openGroups) {
await handleOpenGroups(envelope, syncMessage.openGroups);
} else if (syncMessage.blocked) {
await handleBlocked(envelope, syncMessage.blocked);
} else if (syncMessage.request) {
window.log.info('Got SyncMessage Request');
await removeFromCache(envelope);
} else if (syncMessage.read && syncMessage.read.length) {
window.log.info('read messages from', getEnvelopeId(envelope));
await handleRead(envelope, syncMessage.read);
} else if (syncMessage.verified) {
window.log.info('Got syncMessage.verified. Dropping it...');
await removeFromCache(envelope);
return;
} else if (syncMessage.configuration) {
window.log.info('Got syncMessage.configuration. Dropping it...');
await removeFromCache(envelope);
return;
}
throw new Error('Got empty SyncMessage');
}
// handle a SYNC message for a message
// sent by another device
async function handleSentMessage(
envelope: EnvelopePlus,
sentMessage: SignalService.SyncMessage.ISent
) {
const {
destination,
timestamp,
expirationStartTimestamp,
unidentifiedStatus,
message: msg,
} = sentMessage;
if (!msg) {
window.log('Inner message is missing in a sync message');
await removeFromCache(envelope);
return;
}
if (isMessageEmpty(msg as SignalService.DataMessage)) {
window.log.info('dropping empty message synced');
await removeFromCache(envelope);
return;
}
if (msg.mediumGroupUpdate) {
throw new Error('Got a medium group update. This should not happen');
}
const message = await processDecrypted(envelope, msg);
const primaryDevicePubKey = window.storage.get('primaryDevicePubKey');
// handle profileKey and avatar updates
if (envelope.source === primaryDevicePubKey) {
const { profileKey, profile } = message;
const primaryConversation = ConversationController.getInstance().get(
primaryDevicePubKey
);
if (profile) {
await updateProfile(primaryConversation, profile, profileKey);
}
}
const ev: any = new Event('sent');
ev.confirm = removeFromCache.bind(null, envelope);
ev.data = {
destination,
timestamp: _.toNumber(timestamp),
device: envelope.sourceDevice,
unidentifiedStatus,
message,
};
if (expirationStartTimestamp) {
ev.data.expirationStartTimestamp = _.toNumber(expirationStartTimestamp);
}
await handleMessageEvent(ev);
}
// This doesn't have to be async...
function handleAttachment(attachment: any) {
return {
...attachment,
data: ByteBuffer.wrap(attachment.data).toArrayBuffer(), // ByteBuffer to ArrayBuffer
};
}
async function handleOpenGroups(
envelope: EnvelopePlus,
openGroups: Array<SignalService.SyncMessage.IOpenGroupDetails>
) {
const groupsArray = openGroups.map(openGroup => openGroup.url);
openGroups.forEach(({ url, channelId }) => {
window.attemptConnection(url, channelId);
});
await removeFromCache(envelope);
}
async function handleBlocked(
envelope: EnvelopePlus,
blocked: SignalService.SyncMessage.IBlocked
) {
window.log.info('Setting these numbers as blocked:', blocked.numbers);
// blocked.numbers contains numbers
if (blocked.numbers) {
const currentlyBlockedNumbers = BlockedNumberController.getBlockedNumbers();
const toRemoveFromBlocked = _.difference(
currentlyBlockedNumbers,
blocked.numbers
);
const toAddToBlocked = _.difference(
blocked.numbers,
currentlyBlockedNumbers
);
async function markConvoBlocked(block: boolean, n: string) {
const conv = ConversationController.getInstance().get(n);
if (conv) {
if (conv.isPrivate()) {
await BlockedNumberController.setBlocked(n, block);
} else {
window.log.warn('Ignoring block/unblock for group:', n);
}
await conv.commit();
} else {
window.log.warn('Did not find corresponding conversation to block', n);
}
}
await Promise.all(toAddToBlocked.map(async n => markConvoBlocked(true, n)));
await Promise.all(
toRemoveFromBlocked.map(async n => markConvoBlocked(false, n))
);
}
await removeFromCache(envelope);
}
async function onReadSync(readAt: any, sender: any, timestamp: any) {
window.log.info('read sync', sender, timestamp);
const receipt = window.Whisper.ReadSyncs.add({
sender,
timestamp,
read_at: readAt,
});
await window.Whisper.ReadSyncs.onReceipt(receipt);
}
async function handleRead(
envelope: EnvelopePlus,
readArray: Array<SignalService.SyncMessage.IRead>
) {
const results = [];
for (const read of readArray) {
const promise = onReadSync(
_.toNumber(envelope.timestamp),
read.sender,
_.toNumber(read.timestamp)
);
results.push(promise);
}
await Promise.all(results);
await removeFromCache(envelope);
}
async function handleConfiguration(
envelope: EnvelopePlus,
configuration: SignalService.SyncMessage.IConfiguration
) {
window.log.info('got configuration sync message');
const { storage } = window;
const {
readReceipts,
typingIndicators,
unidentifiedDeliveryIndicators,
linkPreviews,
} = configuration;
storage.put('read-receipt-setting', readReceipts);
if (
unidentifiedDeliveryIndicators === true ||
unidentifiedDeliveryIndicators === false
) {
storage.put(
'unidentifiedDeliveryIndicators',
unidentifiedDeliveryIndicators
);
}
if (typingIndicators === true || typingIndicators === false) {
storage.put('typing-indicators-setting', typingIndicators);
}
if (linkPreviews === true || linkPreviews === false) {
storage.put('link-preview-setting', linkPreviews);
}
await removeFromCache(envelope);
}
async function handleGroupsSync(
envelope: EnvelopePlus,
groups: SignalService.SyncMessage.IGroups
) {
window.log.warn('FIXME group sync is not currently doing anything');
// const attachmentPointer = handleAttachment(groups);
// const groupBuffer = new window.GroupBuffer(attachmentPointer.data);
// let groupDetails = groupBuffer.next();
// const promises = [];
// while (groupDetails !== undefined) {
// groupDetails.id = groupDetails.id.toBinary();
// const promise = updateOrCreateGroupFromSync(groupDetails).catch(
// (e: any) => {
// window.log.error('error processing group', e);
// }
// );
// promises.push(promise);
// groupDetails = groupBuffer.next();
// }
// Note: we do not return here because we don't want to block the next message on
// this attachment download and a lot of processing of that attachment.
// void Promise.all(promises);
await removeFromCache(envelope);
}