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.
307 lines
8.6 KiB
TypeScript
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);
|
|
}
|