From 332cd3005213a1293ee486c0e1893ee5f55774bf Mon Sep 17 00:00:00 2001 From: Beaudan Brown Date: Thu, 7 Nov 2019 11:30:29 +1100 Subject: [PATCH 1/4] Send contact sync message when becoming friends with contact and auto accept/send friend requests when receiving contact sync --- js/background.js | 8 ++++++++ js/models/conversations.js | 11 ++++++++--- libloki/api.js | 12 ++++++------ libtextsecure/sendmessage.js | 28 ++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/js/background.js b/js/background.js index 0a356c577..617aade94 100644 --- a/js/background.js +++ b/js/background.js @@ -1239,6 +1239,14 @@ await conversation.setSecondaryStatus(true); } + if (conversation.isFriendRequestStatusNone()) { + // Will be replaced with automatic friend request + libloki.api.sendBackgroundMessage(conversation.id); + } else { + // Accept any pending friend requests if there are any + conversation.onAcceptFriendRequest({ fromContactSync: true }); + } + if (details.profileKey) { const profileKey = window.Signal.Crypto.arrayBufferToBase64( details.profileKey diff --git a/js/models/conversations.js b/js/models/conversations.js index 6740514fd..1f562ab23 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -774,7 +774,8 @@ }); } }, - async setFriendRequestStatus(newStatus) { + async setFriendRequestStatus(newStatus, options = {}) { + const { fromContactSync } = options; // Ensure that the new status is a valid FriendStatusEnum value if (!(newStatus in Object.values(FriendRequestStatusEnum))) { return; @@ -791,6 +792,10 @@ Conversation: Whisper.Conversation, }); await this.updateTextInputState(); + if (!fromContactSync && newStatus === FriendRequestStatusEnum.friends) { + // Sync contact + this.wrapSend(textsecure.messaging.sendContactSyncMessage(this)); + } } }, async respondToAllFriendRequests(options) { @@ -837,12 +842,12 @@ await window.libloki.storage.removeContactPreKeyBundle(this.id); }, // We have accepted an incoming friend request - async onAcceptFriendRequest() { + async onAcceptFriendRequest(options = {}) { if (this.unlockTimer) { clearTimeout(this.unlockTimer); } if (this.hasReceivedFriendRequest()) { - this.setFriendRequestStatus(FriendRequestStatusEnum.friends); + this.setFriendRequestStatus(FriendRequestStatusEnum.friends, options); await this.respondToAllFriendRequests({ response: 'accepted', direction: 'incoming', diff --git a/libloki/api.js b/libloki/api.js index a41e3c34d..5d5949d0b 100644 --- a/libloki/api.js +++ b/libloki/api.js @@ -114,11 +114,7 @@ result.reset(); return result; } - async function createContactSyncProtoMessage() { - const conversations = await window.Signal.Data.getConversationsWithFriendStatus( - window.friends.friendRequestStatusEnum.friends, - { ConversationCollection: Whisper.ConversationCollection } - ); + async function createContactSyncProtoMessage(conversations) { // Extract required contacts information out of conversations const rawContacts = conversations.map(conversation => { const profile = conversation.getLokiProfile(); @@ -186,7 +182,11 @@ profile, }); // Attach contact list - const syncMessage = await createContactSyncProtoMessage(); + const conversations = await window.Signal.Data.getConversationsWithFriendStatus( + window.friends.friendRequestStatusEnum.friends, + { ConversationCollection: Whisper.ConversationCollection } + ); + const syncMessage = await createContactSyncProtoMessage(conversations); const content = new textsecure.protobuf.Content({ pairingAuthorisation, dataMessage, diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index 21e234967..31f354cee 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -563,6 +563,33 @@ MessageSender.prototype = { return Promise.resolve(); }, + async sendContactSyncMessage(contactConversation) { + const primaryDeviceKey = window.storage.get('primaryDevicePubKey'); + 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(); + } + + const syncMessage = await libloki.api.createContactSyncProtoMessage([ + contactConversation, + ]); + const contentMessage = new textsecure.protobuf.Content(); + contentMessage.syncMessage = syncMessage; + + const silent = true; + return this.sendIndividualProto( + primaryDeviceKey, + contentMessage, + Date.now(), + silent, + {} // options + ); + }, + sendRequestContactSyncMessage(options) { const myNumber = textsecure.storage.user.getNumber(); const myDevice = textsecure.storage.user.getDeviceId(); @@ -1160,6 +1187,7 @@ textsecure.MessageSender = function MessageSenderWrapper(username, password) { this.sendRequestContactSyncMessage = sender.sendRequestContactSyncMessage.bind( sender ); + this.sendContactSyncMessage = sender.sendContactSyncMessage.bind(sender); this.sendRequestConfigurationSyncMessage = sender.sendRequestConfigurationSyncMessage.bind( sender ); From c31535edb41eb18d890721d1c503027bbdc73d6b Mon Sep 17 00:00:00 2001 From: Beaudan Brown Date: Fri, 8 Nov 2019 11:00:32 +1100 Subject: [PATCH 2/4] Rename blockSync, only send contacts and profile if sending a GRANT request, do *not* always be friends with secondary devices because we still need to complete a friend request exchange --- js/background.js | 2 +- js/models/conversations.js | 4 +-- libloki/api.js | 43 ++++++++++++++----------------- libtextsecure/message_receiver.js | 9 ++++--- 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/js/background.js b/js/background.js index 617aade94..5aaec5be3 100644 --- a/js/background.js +++ b/js/background.js @@ -1244,7 +1244,7 @@ libloki.api.sendBackgroundMessage(conversation.id); } else { // Accept any pending friend requests if there are any - conversation.onAcceptFriendRequest({ fromContactSync: true }); + conversation.onAcceptFriendRequest({ blockSync: true }); } if (details.profileKey) { diff --git a/js/models/conversations.js b/js/models/conversations.js index 1f562ab23..40d459abb 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -775,7 +775,7 @@ } }, async setFriendRequestStatus(newStatus, options = {}) { - const { fromContactSync } = options; + const { blockSync } = options; // Ensure that the new status is a valid FriendStatusEnum value if (!(newStatus in Object.values(FriendRequestStatusEnum))) { return; @@ -792,7 +792,7 @@ Conversation: Whisper.Conversation, }); await this.updateTextInputState(); - if (!fromContactSync && newStatus === FriendRequestStatusEnum.friends) { + if (!blockSync && newStatus === FriendRequestStatusEnum.friends) { // Sync contact this.wrapSend(textsecure.messaging.sendContactSyncMessage(this)); } diff --git a/libloki/api.js b/libloki/api.js index 5d5949d0b..e606c554b 100644 --- a/libloki/api.js +++ b/libloki/api.js @@ -165,33 +165,28 @@ ourNumber, 'private' ); - 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( - lokiProfile - ); - const dataMessage = new textsecure.protobuf.DataMessage({ - profile, - }); - // Attach contact list - const conversations = await window.Signal.Data.getConversationsWithFriendStatus( - window.friends.friendRequestStatusEnum.friends, - { ConversationCollection: Whisper.ConversationCollection } - ); - const syncMessage = await createContactSyncProtoMessage(conversations); const content = new textsecure.protobuf.Content({ pairingAuthorisation, - dataMessage, - syncMessage, }); + const isGrant = authorisation.primaryDevicePubKey === ourNumber; + if (isGrant) { + // Send profile name to secondary device + const lokiProfile = ourConversation.getLokiProfile(); + const profile = new textsecure.protobuf.DataMessage.LokiProfile( + lokiProfile + ); + const dataMessage = new textsecure.protobuf.DataMessage({ + profile, + }); + // Attach contact list + const conversations = await window.Signal.Data.getConversationsWithFriendStatus( + window.friends.friendRequestStatusEnum.friends, + { ConversationCollection: Whisper.ConversationCollection } + ); + const syncMessage = await createContactSyncProtoMessage(conversations); + content.syncMessage = syncMessage; + content.dataMessage = dataMessage; + } // Send const options = { messageType: 'pairing-request' }; const p = new Promise((resolve, reject) => { diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index da2d7ff17..e62c8494e 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -1232,8 +1232,6 @@ MessageReceiver.prototype.extend({ return false; } await libloki.storage.savePairingAuthorisation(authorisation); - // sending a message back = accepting friend request - window.libloki.api.sendBackgroundMessage(pubKey); return true; }, @@ -1293,7 +1291,12 @@ MessageReceiver.prototype.extend({ deviceMapping ); if (autoAccepted) { - await conversation.onFriendRequestAccepted(); + // sending a message back = accepting friend request + // Directly setting friend request status to skip the pending state + await conversation.setFriendRequestStatus( + window.friends.friendRequestStatusEnum.friends + ); + window.libloki.api.sendBackgroundMessage(envelope.source); return this.removeFromCache(envelope); } } From 370dee5abbe4ffe370d165ccdc9e4e3aeaafc26c Mon Sep 17 00:00:00 2001 From: Beaudan Brown Date: Fri, 8 Nov 2019 14:28:43 +1100 Subject: [PATCH 3/4] Ensure we have the correct apis and listeners during the pairing process. Catch upnp error. Remove redundant friend requests --- js/background.js | 17 ++++++++++++----- libloki/api.js | 19 +++++++++++++------ libtextsecure/message_receiver.js | 8 -------- libtextsecure/sendmessage.js | 3 ++- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/js/background.js b/js/background.js index 5aaec5be3..ffa504b4b 100644 --- a/js/background.js +++ b/js/background.js @@ -244,10 +244,13 @@ // singleton to relay events to libtextsecure/message_receiver window.lokiPublicChatAPI = new window.LokiPublicChatAPI(ourKey); // singleton to interface the File server - window.lokiFileServerAPI = new window.LokiFileServerAPI(ourKey); - await window.lokiFileServerAPI.establishConnection( - window.getDefaultFileServer() - ); + // If already exists we registered as a secondary device + if (!window.lokiFileServerAPI) { + window.lokiFileServerAPI = new window.LokiFileServerAPI(ourKey); + await window.lokiFileServerAPI.establishConnection( + window.getDefaultFileServer() + ); + } // are there limits on tracking, is this unneeded? // window.mixpanel.track("Desktop boot"); window.lokiP2pAPI = new window.LokiP2pAPI(ourKey); @@ -262,7 +265,6 @@ if (storage.get('isSecondaryDevice')) { window.lokiFileServerAPI.updateOurDeviceMapping(); } - Whisper.events.trigger('apisReady'); }; function mapOldThemeToNew(theme) { @@ -957,6 +959,10 @@ if (Whisper.Registration.ongoingSecondaryDeviceRegistration()) { const ourKey = textsecure.storage.user.getNumber(); window.lokiMessageAPI = new window.LokiMessageAPI(ourKey); + window.lokiFileServerAPI = new window.LokiFileServerAPI(ourKey); + await window.lokiFileServerAPI.establishConnection( + window.getDefaultFileServer() + ); window.localLokiServer = null; window.lokiPublicChatAPI = null; window.feeds = []; @@ -967,6 +973,7 @@ options ); messageReceiver.addEventListener('message', onMessageReceived); + messageReceiver.addEventListener('contact', onContactReceived); window.textsecure.messaging = new textsecure.MessageSender( USERNAME, PASSWORD diff --git a/libloki/api.js b/libloki/api.js index e606c554b..a1b3f350f 100644 --- a/libloki/api.js +++ b/libloki/api.js @@ -38,15 +38,22 @@ let p2pPort = null; let type; - if (!window.localLokiServer || !window.localLokiServer.isListening()) { - type = textsecure.protobuf.LokiAddressMessage.Type.HOST_UNREACHABLE; - } else { - // clearnet change: getMyLokiAddress -> getMyClearIP - // const myLokiAddress = await window.lokiSnodeAPI.getMyLokiAddress(); - const myIp = await window.lokiSnodeAPI.getMyClearIp(); + let myIp; + if (window.localLokiServer && window.localLokiServer.isListening()) { + try { + // clearnet change: getMyLokiAddress -> getMyClearIP + // const myLokiAddress = await window.lokiSnodeAPI.getMyLokiAddress(); + myIp = await window.lokiSnodeAPI.getMyClearIp(); + } catch (e) { + log.warn(`Failed to get clear IP for local server ${e}`); + } + } + if (myIp) { p2pAddress = `https://${myIp}`; p2pPort = window.localLokiServer.getPublicPort(); type = textsecure.protobuf.LokiAddressMessage.Type.HOST_REACHABLE; + } else { + type = textsecure.protobuf.LokiAddressMessage.Type.HOST_UNREACHABLE; } const lokiAddressMessage = new textsecure.protobuf.LokiAddressMessage({ diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index e62c8494e..3a6ca8e69 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -1130,14 +1130,6 @@ MessageReceiver.prototype.extend({ // This call already removes the envelope from the cache await this.handleContacts(envelope, syncMessage.contacts); removedFromCache = true; - if (window.initialisedAPI) { - 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 { window.log.warn('Unimplemented pairing authorisation message type'); diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index 31f354cee..abf0705ad 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -570,7 +570,8 @@ MessageSender.prototype = { )) // Don't send to ourselves .filter(pubKey => pubKey !== textsecure.storage.user.getNumber()); - if (allOurDevices.length === 0) { + if (allOurDevices.includes(contactConversation.id) || !primaryDeviceKey || allOurDevices.length === 0) { + // If we havn't got a primaryDeviceKey then we are in the middle of pairing return Promise.resolve(); } From 1c022856b1ab08808834db247efe866a225db036 Mon Sep 17 00:00:00 2001 From: Beaudan Brown Date: Tue, 12 Nov 2019 09:28:30 +1100 Subject: [PATCH 4/4] Address review --- libtextsecure/sendmessage.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index abf0705ad..74a5d1656 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -452,7 +452,7 @@ MessageSender.prototype = { // Don't send to ourselves .filter(pubKey => pubKey !== textsecure.storage.user.getNumber()); if (allOurDevices.length === 0) { - return Promise.resolve(); + return null; } const dataMessage = textsecure.protobuf.DataMessage.decode( @@ -570,7 +570,11 @@ MessageSender.prototype = { )) // Don't send to ourselves .filter(pubKey => pubKey !== textsecure.storage.user.getNumber()); - if (allOurDevices.includes(contactConversation.id) || !primaryDeviceKey || allOurDevices.length === 0) { + if ( + allOurDevices.includes(contactConversation.id) || + !primaryDeviceKey || + allOurDevices.length === 0 + ) { // If we havn't got a primaryDeviceKey then we are in the middle of pairing return Promise.resolve(); }