Merge pull request #601 from BeaudanBrown/sync-messages

[multi-device] Sync messages
pull/605/head
Beaudan Campbell-Brown 6 years ago committed by GitHub
commit 0f74a002df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -8,6 +8,7 @@
storage, storage,
textsecure, textsecure,
Whisper, Whisper,
libloki,
BlockedNumberController BlockedNumberController
*/ */
@ -233,9 +234,8 @@
specialConvInited = true; specialConvInited = true;
}; };
let initialisedAPI = false;
const initAPIs = async () => { const initAPIs = async () => {
if (initialisedAPI) { if (window.initialisedAPI) {
return; return;
} }
const ourKey = textsecure.storage.user.getNumber(); const ourKey = textsecure.storage.user.getNumber();
@ -253,15 +253,16 @@
window.lokiP2pAPI = new window.LokiP2pAPI(ourKey); window.lokiP2pAPI = new window.LokiP2pAPI(ourKey);
window.lokiP2pAPI.on('pingContact', pubKey => { window.lokiP2pAPI.on('pingContact', pubKey => {
const isPing = true; const isPing = true;
window.libloki.api.sendOnlineBroadcastMessage(pubKey, isPing); libloki.api.sendOnlineBroadcastMessage(pubKey, isPing);
}); });
window.lokiP2pAPI.on('online', ConversationController._handleOnline); window.lokiP2pAPI.on('online', ConversationController._handleOnline);
window.lokiP2pAPI.on('offline', ConversationController._handleOffline); window.lokiP2pAPI.on('offline', ConversationController._handleOffline);
initialisedAPI = true; window.initialisedAPI = true;
if (storage.get('isSecondaryDevice')) { if (storage.get('isSecondaryDevice')) {
window.lokiFileServerAPI.updateOurDeviceMapping(); window.lokiFileServerAPI.updateOurDeviceMapping();
} }
Whisper.events.trigger('apisReady');
}; };
function mapOldThemeToNew(theme) { function mapOldThemeToNew(theme) {
@ -851,8 +852,8 @@
}); });
Whisper.events.on('devicePairingRequestRejected', async pubKey => { Whisper.events.on('devicePairingRequestRejected', async pubKey => {
await window.libloki.storage.removeContactPreKeyBundle(pubKey); await libloki.storage.removeContactPreKeyBundle(pubKey);
await window.libloki.storage.removePairingAuthorisationForSecondaryPubKey( await libloki.storage.removePairingAuthorisationForSecondaryPubKey(
pubKey pubKey
); );
}); });
@ -1170,7 +1171,7 @@
} }
let primaryDevice = null; let primaryDevice = null;
const authorisation = await window.libloki.storage.getGrantAuthorisationForSecondaryPubKey( const authorisation = await libloki.storage.getGrantAuthorisationForSecondaryPubKey(
sender sender
); );
if (authorisation) { if (authorisation) {
@ -1227,6 +1228,16 @@
if (activeAt !== null) { if (activeAt !== null) {
activeAt = activeAt || Date.now(); activeAt = activeAt || Date.now();
} }
const ourAuthorisations = await libloki.storage.getPrimaryDeviceMapping(
window.storage.get('primaryDevicePubKey')
);
const isSecondaryDevice =
ourAuthorisations &&
ourAuthorisations.some(auth => auth.secondaryDevicePubKey === id);
if (isSecondaryDevice) {
await conversation.setSecondaryStatus(true);
}
if (details.profileKey) { if (details.profileKey) {
const profileKey = window.Signal.Crypto.arrayBufferToBase64( const profileKey = window.Signal.Crypto.arrayBufferToBase64(
@ -1401,7 +1412,7 @@
const messageDescriptor = getMessageDescriptor(data); const messageDescriptor = getMessageDescriptor(data);
// Funnel messages to primary device conversation if multi-device // Funnel messages to primary device conversation if multi-device
const authorisation = await window.libloki.storage.getGrantAuthorisationForSecondaryPubKey( const authorisation = await libloki.storage.getGrantAuthorisationForSecondaryPubKey(
messageDescriptor.id messageDescriptor.id
); );
if (authorisation) { if (authorisation) {

@ -192,7 +192,7 @@
}, },
isMe() { isMe() {
return this.id === this.ourNumber; return this.id === window.storage.get('primaryDevicePubKey');
}, },
isPublic() { isPublic() {
return this.id && this.id.match(/^publicChat:/); return this.id && this.id.match(/^publicChat:/);

@ -1269,7 +1269,9 @@
}); });
this.trigger('sent', this); this.trigger('sent', this);
if (this.get('type') !== 'friend-request') {
this.sendSyncMessage(); this.sendSyncMessage();
}
}) })
.catch(result => { .catch(result => {
this.trigger('done'); this.trigger('done');
@ -1710,6 +1712,7 @@
// 2. on a sent message sync'd from another device // 2. on a sent message sync'd from another device
// 3. in rare cases, an incoming message can be retried, though it will // 3. in rare cases, an incoming message can be retried, though it will
// still go through one of the previous two codepaths // still go through one of the previous two codepaths
const ourNumber = textsecure.storage.user.getNumber();
const message = this; const message = this;
const source = message.get('source'); const source = message.get('source');
const type = message.get('type'); const type = message.get('type');
@ -1719,7 +1722,8 @@
); );
if (initialMessage.group) { if (initialMessage.group) {
conversationId = initialMessage.group.id; conversationId = initialMessage.group.id;
} else if (authorisation) { } else if (source !== ourNumber && authorisation) {
// Ignore auth from our devices
conversationId = authorisation.primaryDevicePubKey; conversationId = authorisation.primaryDevicePubKey;
} }
@ -1916,8 +1920,6 @@
c.onReadMessage(message); c.onReadMessage(message);
} }
} else { } else {
const ourNumber = textsecure.storage.user.getNumber();
if ( if (
message.attributes.body && message.attributes.body &&
message.attributes.body.indexOf(`@${ourNumber}`) !== -1 message.attributes.body.indexOf(`@${ourNumber}`) !== -1
@ -2007,12 +2009,12 @@
autoAccept = true; autoAccept = true;
message.set({ friendStatus: 'accepted' }); message.set({ friendStatus: 'accepted' });
await sendingDeviceConversation.onFriendRequestAccepted(); await sendingDeviceConversation.onFriendRequestAccepted();
window.libloki.api.sendBackgroundMessage(message.get('source'));
} else { } else {
await sendingDeviceConversation.onFriendRequestReceived(); await sendingDeviceConversation.onFriendRequestReceived();
} }
} else { } else if (message.get('type') !== 'outgoing') {
await conversation.onFriendRequestAccepted(); // Ignore 'outgoing' messages because they are sync messages
await sendingDeviceConversation.onFriendRequestAccepted();
// We need to return for these types of messages because android struggles // We need to return for these types of messages because android struggles
if ( if (
!message.get('body') && !message.get('body') &&

@ -164,13 +164,21 @@
const pairingAuthorisation = createPairingAuthorisationProtoMessage( const pairingAuthorisation = createPairingAuthorisationProtoMessage(
authorisation authorisation
); );
// Send profile name to secondary device
const ourNumber = textsecure.storage.user.getNumber(); const ourNumber = textsecure.storage.user.getNumber();
const conversation = await ConversationController.getOrCreateAndWait( const ourConversation = await ConversationController.getOrCreateAndWait(
ourNumber, ourNumber,
'private' 'private'
); );
const lokiProfile = conversation.getLokiProfile(); const secondaryConversation = await ConversationController.getOrCreateAndWait(
recipientPubKey,
'private'
);
// Always be friends with secondary devices
secondaryConversation.setFriendRequestStatus(
window.friends.friendRequestStatusEnum.friends
);
// Send profile name to secondary device
const lokiProfile = ourConversation.getLokiProfile();
const profile = new textsecure.protobuf.DataMessage.LokiProfile( const profile = new textsecure.protobuf.DataMessage.LokiProfile(
lokiProfile lokiProfile
); );
@ -178,12 +186,11 @@
profile, profile,
}); });
// Attach contact list // Attach contact list
// TODO: Reenable sending of the syncmessage for pairing requests const syncMessage = await createContactSyncProtoMessage();
// const syncMessage = await createContactSyncProtoMessage();
const content = new textsecure.protobuf.Content({ const content = new textsecure.protobuf.Content({
pairingAuthorisation, pairingAuthorisation,
dataMessage, dataMessage,
// syncMessage, syncMessage,
}); });
// Send // Send
const options = { messageType: 'pairing-request' }; const options = { messageType: 'pairing-request' };

@ -239,6 +239,7 @@
getAuthorisationForSecondaryPubKey, getAuthorisationForSecondaryPubKey,
getAllDevicePubKeysForPrimaryPubKey, getAllDevicePubKeysForPrimaryPubKey,
getSecondaryDevicesFor, getSecondaryDevicesFor,
getPrimaryDeviceMapping,
}; };
// Libloki protocol store // Libloki protocol store

@ -546,6 +546,10 @@
async registrationDone(number, displayName) { async registrationDone(number, displayName) {
window.log.info('registration done'); window.log.info('registration done');
if (!textsecure.storage.get('secondaryDeviceStatus')) {
// We have registered as a primary device
textsecure.storage.put('primaryDevicePubKey', number);
}
// Ensure that we always have a conversation for ourself // Ensure that we always have a conversation for ourself
const conversation = await ConversationController.getOrCreateAndWait( const conversation = await ConversationController.getOrCreateAndWait(
number, number,

@ -21,6 +21,7 @@
/* global Whisper: false */ /* global Whisper: false */
/* global lokiFileServerAPI: false */ /* global lokiFileServerAPI: false */
/* global WebAPI: false */ /* global WebAPI: false */
/* global ConversationController: false */
/* eslint-disable more/no-then */ /* eslint-disable more/no-then */
/* eslint-disable no-unreachable */ /* eslint-disable no-unreachable */
@ -1018,7 +1019,9 @@ MessageReceiver.prototype.extend({
this.processDecrypted(envelope, msg).then(message => { this.processDecrypted(envelope, msg).then(message => {
const groupId = message.group && message.group.id; const groupId = message.group && message.group.id;
const isBlocked = this.isGroupBlocked(groupId); const isBlocked = this.isGroupBlocked(groupId);
const isMe = envelope.source === textsecure.storage.user.getNumber(); const isMe =
envelope.source === textsecure.storage.user.getNumber() ||
envelope.source === window.storage.get('primaryDevicePubKey');
const isLeavingGroup = Boolean( const isLeavingGroup = Boolean(
message.group && message.group &&
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
@ -1108,6 +1111,11 @@ MessageReceiver.prototype.extend({
window.storage.remove('secondaryDeviceStatus'); window.storage.remove('secondaryDeviceStatus');
window.storage.put('isSecondaryDevice', true); window.storage.put('isSecondaryDevice', true);
window.storage.put('primaryDevicePubKey', primaryDevicePubKey); window.storage.put('primaryDevicePubKey', primaryDevicePubKey);
const primaryConversation = await ConversationController.getOrCreateAndWait(
primaryDevicePubKey,
'private'
);
primaryConversation.trigger('change');
Whisper.events.trigger('secondaryDeviceRegistration'); Whisper.events.trigger('secondaryDeviceRegistration');
// Update profile name // Update profile name
if (dataMessage && dataMessage.profile) { if (dataMessage && dataMessage.profile) {
@ -1122,7 +1130,14 @@ MessageReceiver.prototype.extend({
// This call already removes the envelope from the cache // This call already removes the envelope from the cache
await this.handleContacts(envelope, syncMessage.contacts); await this.handleContacts(envelope, syncMessage.contacts);
removedFromCache = true; removedFromCache = true;
if (window.initialisedAPI) {
await this.sendFriendRequestsToSyncContacts(syncMessage.contacts); await this.sendFriendRequestsToSyncContacts(syncMessage.contacts);
} else {
// We need to wait here because initAPIs hasn't been called yet
Whisper.events.once('apisReady', async () => {
await this.sendFriendRequestsToSyncContacts(syncMessage.contacts);
});
}
} }
} else { } else {
window.log.warn('Unimplemented pairing authorisation message type'); window.log.warn('Unimplemented pairing authorisation message type');
@ -1451,13 +1466,23 @@ MessageReceiver.prototype.extend({
window.log.info('null message from', this.getEnvelopeId(envelope)); window.log.info('null message from', this.getEnvelopeId(envelope));
this.removeFromCache(envelope); this.removeFromCache(envelope);
}, },
handleSyncMessage(envelope, syncMessage) { async handleSyncMessage(envelope, syncMessage) {
if (envelope.source !== this.number) { const ourNumber = textsecure.storage.user.getNumber();
throw new Error('Received sync message from another number'); // NOTE: Maybe we should be caching this list?
} const ourAuthorisations = await libloki.storage.getPrimaryDeviceMapping(
// eslint-disable-next-line eqeqeq ourNumber
if (envelope.sourceDevice == this.deviceId) { );
throw new Error('Received sync message from our own device'); const validSyncSender =
ourAuthorisations &&
ourAuthorisations.some(
auth =>
auth.secondaryDevicePubKey === ourNumber ||
auth.primaryDevicePubKey === ourNumber
);
if (!validSyncSender) {
throw new Error(
"Received sync message from a device we aren't paired with"
);
} }
if (syncMessage.sent) { if (syncMessage.sent) {
const sentMessage = syncMessage.sent; const sentMessage = syncMessage.sent;

@ -95,18 +95,18 @@ OutgoingMessage.prototype = {
this.numberCompleted(); this.numberCompleted();
}, },
reloadDevicesAndSend(number, recurse) { reloadDevicesAndSend(number, recurse) {
const ourNumber = textsecure.storage.user.getNumber();
return () => return () =>
libloki.storage libloki.storage
.getAllDevicePubKeysForPrimaryPubKey(number) .getAllDevicePubKeysForPrimaryPubKey(number)
// Don't send to ourselves
.then(devicesPubKeys =>
devicesPubKeys.filter(pubKey => pubKey !== ourNumber)
)
.then(devicesPubKeys => { .then(devicesPubKeys => {
if (devicesPubKeys.length === 0) { if (devicesPubKeys.length === 0) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
devicesPubKeys = [number]; devicesPubKeys = [number];
// return this.registerError(
// number,
// 'Got empty device list when loading device keys',
// null
// );
} }
return this.doSendMessage(number, devicesPubKeys, recurse); return this.doSendMessage(number, devicesPubKeys, recurse);
}); });

@ -1,4 +1,4 @@
/* global _, textsecure, WebAPI, libsignal, OutgoingMessage, window */ /* global _, textsecure, WebAPI, libsignal, OutgoingMessage, window, libloki */
/* eslint-disable more/no-then, no-bitwise */ /* eslint-disable more/no-then, no-bitwise */
@ -434,7 +434,7 @@ MessageSender.prototype = {
return syncMessage; return syncMessage;
}, },
sendSyncMessage( async sendSyncMessage(
encodedDataMessage, encodedDataMessage,
timestamp, timestamp,
destination, destination,
@ -443,9 +443,15 @@ MessageSender.prototype = {
unidentifiedDeliveries = [], unidentifiedDeliveries = [],
options options
) { ) {
const myNumber = textsecure.storage.user.getNumber(); const primaryDeviceKey =
const myDevice = textsecure.storage.user.getDeviceId(); window.storage.get('primaryDevicePubKey') ||
if (myDevice === 1 || myDevice === '1') { textsecure.storage.user.getNumber();
const allOurDevices = (await libloki.storage.getAllDevicePubKeysForPrimaryPubKey(
primaryDeviceKey
))
// Don't send to ourselves
.filter(pubKey => pubKey !== textsecure.storage.user.getNumber());
if (allOurDevices.length === 0) {
return Promise.resolve(); return Promise.resolve();
} }
@ -489,7 +495,7 @@ MessageSender.prototype = {
const silent = true; const silent = true;
return this.sendIndividualProto( return this.sendIndividualProto(
myNumber, primaryDeviceKey,
contentMessage, contentMessage,
Date.now(), Date.now(),
silent, silent,
@ -588,7 +594,8 @@ MessageSender.prototype = {
// We don't want to send typing messages to our other devices, but we will // We don't want to send typing messages to our other devices, but we will
// in the group case. // in the group case.
const myNumber = textsecure.storage.user.getNumber(); const myNumber = textsecure.storage.user.getNumber();
if (recipientId && myNumber === recipientId) { const primaryDevicePubkey = window.storage.get('primaryDevicePubKey');
if (recipientId && primaryDevicePubkey === recipientId) {
return null; return null;
} }

@ -42,6 +42,7 @@ window.JobQueue = JobQueue;
window.getStoragePubKey = key => window.getStoragePubKey = key =>
window.isDev() ? key.substring(0, key.length - 2) : key; window.isDev() ? key.substring(0, key.length - 2) : key;
window.getDefaultFileServer = () => config.defaultFileServer; window.getDefaultFileServer = () => config.defaultFileServer;
window.initialisedAPI = false;
window.isBeforeVersion = (toCheck, baseVersion) => { window.isBeforeVersion = (toCheck, baseVersion) => {
try { try {

Loading…
Cancel
Save