From 3c571770ed3ee26893453b93b702cda833d1a824 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 28 Apr 2020 11:43:34 +1000 Subject: [PATCH 01/17] Useful comments --- js/models/conversations.js | 18 ++++++++++++++++++ js/modules/loki_message_api.js | 2 ++ js/modules/signal.js | 2 ++ js/views/conversation_view.js | 13 +++++++++++++ libtextsecure/sendmessage.js | 2 ++ 5 files changed, 37 insertions(+) diff --git a/js/models/conversations.js b/js/models/conversations.js index 48c6781df..aec9987f9 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -649,6 +649,8 @@ ); }, async updateVerified() { + console.log('[vince] conversations.js --> updateVerified()'); + if (this.isPrivate()) { await this.initialPromise; const verified = await this.safeGetVerified(); @@ -850,9 +852,12 @@ return allDeviceStatus === FriendRequestStatusEnum.friends; }, getFriendRequestStatus() { + return this.get('friendRequestStatus'); }, async getPrimaryConversation() { + console.log('[vince] conversation.js --> getPrimaryConversation'); + if (!this.isSecondaryDevice()) { // This is already the primary conversation return this; @@ -870,6 +875,8 @@ return this; }, async updateTextInputState() { + console.log('[vince] conversation.js --> updateTextInputState'); + if (this.isRss()) { // or if we're an rss conversation, disable it this.trigger('disable:input', true); @@ -919,6 +926,8 @@ return this.get('primaryDevicePubKey') || this.id; }, async setSecondaryStatus(newStatus, primaryDevicePubKey) { + console.log('[vince] conversation.js --> setSecondaryStatus'); + if (this.get('secondaryStatus') !== newStatus) { this.set({ secondaryStatus: newStatus, @@ -930,6 +939,8 @@ } }, async setFriendRequestStatus(newStatus, options = {}) { + console.log('[vince] conversation.js --> setFriendRequestStatus'); + const { blockSync } = options; // Ensure that the new status is a valid FriendStatusEnum value if (!(newStatus in Object.values(FriendRequestStatusEnum))) { @@ -1536,6 +1547,13 @@ ) { this.clearTypingTimers(); + console.log(`[vince] conversations.js --> body:`, body); + console.log(`[vince] conversations.js --> attachments:`, attachments); + console.log(`[vince] conversations.js --> quote:`, quote); + console.log(`[vince] conversations.js --> preview:`, preview); + console.log(`[vince] conversations.js --> groupInvitation:`, groupInvitation); + console.log(`[vince] conversations.js --> otherOptions:`, otherOptions); + const destination = this.id; const expireTimer = this.get('expireTimer'); const recipients = this.getRecipients(); diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index 8c7de6553..dbd675e27 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -40,6 +40,8 @@ class LokiMessageAPI { } async sendMessage(pubKey, data, messageTimeStamp, ttl, options = {}) { + console.log('[vince] outgoing_message.js --> loki_message_api.js --> sendMessage'); + const { isPublic = false, numConnections = DEFAULT_CONNECTIONS, diff --git a/js/modules/signal.js b/js/modules/signal.js index c676c5845..eb719836e 100644 --- a/js/modules/signal.js +++ b/js/modules/signal.js @@ -235,6 +235,8 @@ function initializeMigrations({ logger, }), upgradeMessageSchema: (message, options = {}) => { + console.log('[vince] signal.js --> upgradeMessageSchema'); + const { maxVersion } = options; return MessageType.upgradeSchema(message, { diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 858cfccee..f7b958dba 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -1629,6 +1629,8 @@ }, showSendConfirmationDialog(e, contacts) { + console.log('[vince] conversation_view.js --> showSendConfirmationDialog'); + let message; const isUnverified = this.model.isUnverified(); @@ -1704,6 +1706,8 @@ }, async handleSubmitPressed(e, options = {}) { + console.log(`[vince] handleSubmitPressed e:`, e); + if (this.memberView.membersShown()) { const member = this.memberView.selectedMember(); this.selectMember(member); @@ -1713,6 +1717,8 @@ }, async checkUnverifiedSendMessage(e, options = {}) { + console.log(`[vince] checkUnverifiedSendMessage e:`, e); + e.preventDefault(); this.sendStart = Date.now(); this.$messageField.attr('disabled', true); @@ -1746,6 +1752,8 @@ }, async checkUntrustedSendMessage(e, options = {}) { + console.log(`[vince] checkUntrustedSendMessage e:`, e); + _.defaults(options, { force: false }); try { @@ -1907,6 +1915,8 @@ let message = this.memberView.replaceMentions(input.val()); message = window.Signal.Emoji.replaceColons(message).trim(); + + const toastOptions = { type: 'info' }; // let it pass if we're still trying to read it or it's false... if (extension.expiredStatus() === true) { @@ -2470,6 +2480,9 @@ !event.shiftKey && !event.ctrlKey ) { + + console.log('[vince] conversation_view.hs --> handleInputEvent:', Date.now()); + // enter pressed - submit the form now event.preventDefault(); this.$('.bottom-bar form').submit(); diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index a0bfe4267..d656f7304 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -514,6 +514,8 @@ MessageSender.prototype = { }, createSyncMessage() { + console.log('[vince] sendmessage.js --> createSyncMessage'); + const syncMessage = new textsecure.protobuf.SyncMessage(); // Generate a random int from 1 and 512 From b5f0f552a750ffb72980db74c14a2db5014bbcdd Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 30 Apr 2020 02:17:58 +1000 Subject: [PATCH 02/17] Changes --- js/models/conversations.js | 10 +++++----- js/modules/signal.js | 2 +- js/views/conversation_view.js | 4 ++-- libtextsecure/outgoing_message.js | 22 ++++++++++++++++++++-- libtextsecure/sendmessage.js | 2 +- package.json | 1 + 6 files changed, 30 insertions(+), 11 deletions(-) diff --git a/js/models/conversations.js b/js/models/conversations.js index aec9987f9..eac6a69af 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -649,7 +649,7 @@ ); }, async updateVerified() { - console.log('[vince] conversations.js --> updateVerified()'); + // console.log('[vince] conversations.js --> updateVerified()'); if (this.isPrivate()) { await this.initialPromise; @@ -856,7 +856,7 @@ return this.get('friendRequestStatus'); }, async getPrimaryConversation() { - console.log('[vince] conversation.js --> getPrimaryConversation'); + // console.log('[vince] conversation.js --> getPrimaryConversation'); if (!this.isSecondaryDevice()) { // This is already the primary conversation @@ -875,7 +875,7 @@ return this; }, async updateTextInputState() { - console.log('[vince] conversation.js --> updateTextInputState'); + // console.log('[vince] conversation.js --> updateTextInputState'); if (this.isRss()) { // or if we're an rss conversation, disable it @@ -926,7 +926,7 @@ return this.get('primaryDevicePubKey') || this.id; }, async setSecondaryStatus(newStatus, primaryDevicePubKey) { - console.log('[vince] conversation.js --> setSecondaryStatus'); + // console.log('[vince] conversation.js --> setSecondaryStatus'); if (this.get('secondaryStatus') !== newStatus) { this.set({ @@ -939,7 +939,7 @@ } }, async setFriendRequestStatus(newStatus, options = {}) { - console.log('[vince] conversation.js --> setFriendRequestStatus'); + // console.log('[vince] conversation.js --> setFriendRequestStatus'); const { blockSync } = options; // Ensure that the new status is a valid FriendStatusEnum value diff --git a/js/modules/signal.js b/js/modules/signal.js index eb719836e..04bcb5874 100644 --- a/js/modules/signal.js +++ b/js/modules/signal.js @@ -235,7 +235,7 @@ function initializeMigrations({ logger, }), upgradeMessageSchema: (message, options = {}) => { - console.log('[vince] signal.js --> upgradeMessageSchema'); + // console.log('[vince] signal.js --> upgradeMessageSchema'); const { maxVersion } = options; diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index f7b958dba..4180eae77 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -1629,7 +1629,7 @@ }, showSendConfirmationDialog(e, contacts) { - console.log('[vince] conversation_view.js --> showSendConfirmationDialog'); + // console.log('[vince] conversation_view.js --> showSendConfirmationDialog'); let message; const isUnverified = this.model.isUnverified(); @@ -2481,7 +2481,7 @@ !event.ctrlKey ) { - console.log('[vince] conversation_view.hs --> handleInputEvent:', Date.now()); + // console.log('[vince] conversation_view.hs --> handleInputEvent:', Date.now()); // enter pressed - submit the form now event.preventDefault(); diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index 5feb6d648..70fc43e3a 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -122,8 +122,13 @@ OutgoingMessage.prototype = { libloki.storage .getAllDevicePubKeysForPrimaryPubKey(number) // Don't send to ourselves - .then(devicesPubKeys => - devicesPubKeys.filter(pubKey => pubKey !== ourNumber) + .then(devicesPubKeys => { + console.log('[vince] devicesPubKeys should display for B1 and B2', devicesPubKeys); + console.log('[vince] devicesPubKeys:', devicesPubKeys); + + return devicesPubKeys.filter(pubKey => pubKey !== ourNumber) + + } ) .then(devicesPubKeys => { if (devicesPubKeys.length === 0) { @@ -315,6 +320,16 @@ OutgoingMessage.prototype = { return Promise.all( devicesPubKeys.map(async devicePubKey => { + console.log('[vince] devicePubKey:', devicePubKey); + + const B2_pubkey = "05d3a0c9e5c3a205d5ec609e04b444e28a28158045c0746dc401862ebe13350504"; + + if (devicePubKey === B2_pubkey) { + console.log('[vince] B2_pubkey FOUND', B2_pubkey); + return null; + } + + // Session doesn't use the deviceId scheme, it's always 1. // Instead, there are multiple device public keys. const deviceId = 1; @@ -643,6 +658,9 @@ OutgoingMessage.prototype = { } return this.reloadDevicesAndSend(number, true)().catch(error => { conversation.resetPendingSend(); + + console.log('[vince] error:', error); + if (error.message === 'Identity key changed') { // eslint-disable-next-line no-param-reassign error = new textsecure.OutgoingIdentityKeyError( diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index d656f7304..057bb6956 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -514,7 +514,7 @@ MessageSender.prototype = { }, createSyncMessage() { - console.log('[vince] sendmessage.js --> createSyncMessage'); + // console.log('[vince] sendmessage.js --> createSyncMessage'); const syncMessage = new textsecure.protobuf.SyncMessage(); diff --git a/package.json b/package.json index 3472e45d7..8f047f3cf 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "start-multi2": "cross-env NODE_APP_INSTANCE=2 electron .", "start-prod": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod electron .", "start-prod-multi": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod1 electron .", + "start-prod-multi2": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod2 electron .", "start-swarm-test": "cross-env NODE_ENV=swarm-testing NODE_APP_INSTANCE=1 electron .", "start-swarm-test-2": "cross-env NODE_ENV=swarm-testing NODE_APP_INSTANCE=2 electron .", "grunt": "grunt", From a708ce56ad523f9ba92fc7e168f5d6d08ca145d6 Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 30 Apr 2020 02:25:01 +1000 Subject: [PATCH 03/17] checkout outgoing --- libtextsecure/outgoing_message.js | 698 +----------------------------- 1 file changed, 4 insertions(+), 694 deletions(-) diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index 18c4fe21d..9b740ca43 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -1,694 +1,4 @@ -/* global - textsecure, - libsignal, - window, - ConversationController, - libloki, - StringView, - lokiMessageAPI, - i18n, -*/ - -/* eslint-disable more/no-then */ -/* eslint-disable no-unreachable */ -const NUM_SEND_CONNECTIONS = 3; - -const getTTLForType = type => { - switch (type) { - case 'friend-request': - return 4 * 24 * 60 * 60 * 1000; // 4 days for friend request message - case 'device-unpairing': - return 4 * 24 * 60 * 60 * 1000; // 4 days for device unpairing - case 'onlineBroadcast': - return 60 * 1000; // 1 minute for online broadcast message - case 'typing': - return 60 * 1000; // 1 minute for typing indicators - case 'pairing-request': - return 2 * 60 * 1000; // 2 minutes for pairing requests - default: - return (window.getMessageTTL() || 24) * 60 * 60 * 1000; // 1 day default for any other message - } -}; - -function OutgoingMessage( - server, - timestamp, - numbers, - message, - silent, - callback, - options = {} -) { - if (message instanceof textsecure.protobuf.DataMessage) { - const content = new textsecure.protobuf.Content(); - content.dataMessage = message; - // eslint-disable-next-line no-param-reassign - message = content; - } - this.server = server; - this.timestamp = timestamp; - this.numbers = numbers; - this.message = message; // ContentMessage proto - this.callback = callback; - this.silent = silent; - - this.numbersCompleted = 0; - this.errors = []; - this.successfulNumbers = []; - this.fallBackEncryption = false; - this.failoverNumbers = []; - this.unidentifiedDeliveries = []; - - const { - numberInfo, - senderCertificate, - online, - messageType, - isPing, - isPublic, - publicSendData, - } = - options || {}; - this.numberInfo = numberInfo; - this.isPublic = isPublic; - this.isGroup = !!( - this.message && - this.message.dataMessage && - this.message.dataMessage.group - ); - this.publicSendData = publicSendData; - this.senderCertificate = senderCertificate; - this.online = online; - this.messageType = messageType || 'outgoing'; - this.isPing = isPing || false; -} - -OutgoingMessage.prototype = { - constructor: OutgoingMessage, - numberCompleted() { - this.numbersCompleted += 1; - if (this.numbersCompleted >= this.numbers.length) { - this.callback({ - successfulNumbers: this.successfulNumbers, - failoverNumbers: this.failoverNumbers, - errors: this.errors, - unidentifiedDeliveries: this.unidentifiedDeliveries, - messageType: this.messageType, - }); - } - }, - registerError(number, reason, error) { - if (!error || (error.name === 'HTTPError' && error.code !== 404)) { - // eslint-disable-next-line no-param-reassign - error = new textsecure.OutgoingMessageError( - number, - this.message.toArrayBuffer(), - this.timestamp, - error - ); - } - - // eslint-disable-next-line no-param-reassign - error.number = number; - // eslint-disable-next-line no-param-reassign - error.reason = reason; - this.errors[this.errors.length] = error; - this.numberCompleted(); - }, - reloadDevicesAndSend(number, recurse) { - const ourNumber = textsecure.storage.user.getNumber(); - return () => - libloki.storage - .getAllDevicePubKeysForPrimaryPubKey(number) - // Don't send to ourselves - .then(devicesPubKeys => { - console.log('[vince] devicesPubKeys should display for B1 and B2', devicesPubKeys); - console.log('[vince] devicesPubKeys:', devicesPubKeys); - - return devicesPubKeys.filter(pubKey => pubKey !== ourNumber) - - } - ) - .then(devicesPubKeys => { - if (devicesPubKeys.length === 0) { - // eslint-disable-next-line no-param-reassign - devicesPubKeys = [number]; - } - return this.doSendMessage(number, devicesPubKeys, recurse); - }); - }, - - getKeysForNumber(number, updateDevices) { - const handleResult = response => - Promise.all( - response.devices.map(device => { - // eslint-disable-next-line no-param-reassign - device.identityKey = response.identityKey; - if ( - updateDevices === undefined || - updateDevices.indexOf(device.deviceId) > -1 - ) { - const address = new libsignal.SignalProtocolAddress( - number, - device.deviceId - ); - const builder = new libsignal.SessionBuilder( - textsecure.storage.protocol, - address - ); - if (device.registrationId === 0) { - window.log.info('device registrationId 0!'); - } - return builder - .processPreKey(device) - .then(async () => { - // TODO: only remove the keys that were used above! - await libloki.storage.removeContactPreKeyBundle(number); - return true; - }) - .catch(error => { - if (error.message === 'Identity key changed') { - // eslint-disable-next-line no-param-reassign - error.timestamp = this.timestamp; - // eslint-disable-next-line no-param-reassign - error.originalMessage = this.message.toArrayBuffer(); - // eslint-disable-next-line no-param-reassign - error.identityKey = device.identityKey; - } - throw error; - }); - } - - return false; - }) - ); - let promise = Promise.resolve(true); - updateDevices.forEach(device => { - promise = promise.then(() => - Promise.all([ - textsecure.storage.protocol.loadContactPreKey(number), - textsecure.storage.protocol.loadContactSignedPreKey(number), - ]) - .then(keys => { - const [preKey, signedPreKey] = keys; - if (preKey === undefined || signedPreKey === undefined) { - return false; - } - const identityKey = StringView.hexToArrayBuffer(number); - return handleResult({ - identityKey, - devices: [ - { deviceId: device, preKey, signedPreKey, registrationId: 0 }, - ], - }).then(results => results.every(value => value === true)); - }) - .catch(e => { - if (e.name === 'HTTPError' && e.code === 404) { - if (device !== 1) { - return this.removeDeviceIdsForNumber(number, [device]); - } - throw new textsecure.UnregisteredUserError(number, e); - } else { - throw e; - } - }) - ); - }); - - return promise; - }, - - // Default ttl to 24 hours if no value provided - async transmitMessage(number, data, timestamp, ttl = 24 * 60 * 60 * 1000) { - const pubKey = number; - try { - // TODO: Make NUM_CONCURRENT_CONNECTIONS a global constant - const options = { - numConnections: NUM_SEND_CONNECTIONS, - isPing: this.isPing, - }; - options.isPublic = this.isPublic; - if (this.isPublic) { - options.publicSendData = this.publicSendData; - } - await lokiMessageAPI.sendMessage(pubKey, data, timestamp, ttl, options); - } catch (e) { - if (e.name === 'HTTPError' && (e.code !== 409 && e.code !== 410)) { - // 409 and 410 should bubble and be handled by doSendMessage - // 404 should throw UnregisteredUserError - // all other network errors can be retried later. - if (e.code === 404) { - throw new textsecure.UnregisteredUserError(number, e); - } - throw new textsecure.SendMessageNetworkError(number, '', e, timestamp); - } else if (e.name === 'TimedOutError') { - throw new textsecure.PoWError(number, e); - } - throw e; - } - }, - - getPaddedMessageLength(messageLength) { - const messageLengthWithTerminator = messageLength + 1; - let messagePartCount = Math.floor(messageLengthWithTerminator / 160); - - if (messageLengthWithTerminator % 160 !== 0) { - messagePartCount += 1; - } - - return messagePartCount * 160; - }, - convertMessageToText(messageBuffer) { - const plaintext = new Uint8Array( - this.getPaddedMessageLength(messageBuffer.byteLength + 1) - 1 - ); - plaintext.set(new Uint8Array(messageBuffer)); - plaintext[messageBuffer.byteLength] = 0x80; - - return plaintext; - }, - getPlaintext(messageBuffer) { - return this.convertMessageToText(messageBuffer); - }, - async wrapInWebsocketMessage(outgoingObject) { - const source = - outgoingObject.type === - textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER - ? null - : outgoingObject.ourKey; - - const messageEnvelope = new textsecure.protobuf.Envelope({ - type: outgoingObject.type, - source, - sourceDevice: outgoingObject.sourceDevice, - timestamp: this.timestamp, - content: outgoingObject.content, - }); - const requestMessage = new textsecure.protobuf.WebSocketRequestMessage({ - id: new Uint8Array(libsignal.crypto.getRandomBytes(1))[0], // random ID for now - verb: 'PUT', - path: '/api/v1/message', - body: messageEnvelope.encode().toArrayBuffer(), - }); - const websocketMessage = new textsecure.protobuf.WebSocketMessage({ - type: textsecure.protobuf.WebSocketMessage.Type.REQUEST, - request: requestMessage, - }); - const bytes = new Uint8Array(websocketMessage.encode().toArrayBuffer()); - return bytes; - }, - doSendMessage(number, devicesPubKeys, recurse) { - const ciphers = {}; - if (this.isPublic) { - return this.transmitMessage( - number, - this.message.dataMessage, - this.timestamp, - 0 // ttl - ) - .then(() => { - this.successfulNumbers[this.successfulNumbers.length] = number; - this.numberCompleted(); - }) - .catch(error => { - throw error; - }); - } - - this.numbers = devicesPubKeys; - - return Promise.all( - devicesPubKeys.map(async devicePubKey => { - - // Session doesn't use the deviceId scheme, it's always 1. - // Instead, there are multiple device public keys. - const deviceId = 1; - const updatedDevices = await this.getStaleDeviceIdsForNumber( - devicePubKey - ); - const keysFound = await this.getKeysForNumber( - devicePubKey, - updatedDevices - ); - let enableFallBackEncryption = !keysFound; - - const address = new libsignal.SignalProtocolAddress( - devicePubKey, - deviceId - ); - const ourKey = textsecure.storage.user.getNumber(); - const options = {}; - - let isMultiDeviceRequest = false; - let thisDeviceMessageType = this.messageType; - if ( - thisDeviceMessageType !== 'pairing-request' && - thisDeviceMessageType !== 'friend-request' - ) { - let conversation; - try { - conversation = ConversationController.get(devicePubKey); - } catch (e) { - // do nothing - } - if (conversation && !this.isGroup) { - const isOurDevice = await conversation.isOurDevice(); - const isFriends = - conversation.isFriend() || - conversation.hasReceivedFriendRequest(); - // We should only send a friend request to our device if we don't have keys - const shouldSendAutomatedFR = isOurDevice ? !keysFound : !isFriends; - if (shouldSendAutomatedFR) { - // We want to send an automated friend request if: - // - We aren't already friends - // - We haven't received a friend request from this device - // - We haven't sent a friend request recently - if (conversation.friendRequestTimerIsExpired()) { - isMultiDeviceRequest = true; - thisDeviceMessageType = 'friend-request'; - } else { - // Throttle automated friend requests - this.successfulNumbers.push(devicePubKey); - return null; - } - } - - async buildMessage(devicePubKey) { - const updatedDevices = await this.getStaleDeviceIdsForNumber(devicePubKey); - const keysFound = await this.getKeysForNumber(devicePubKey, updatedDevices); - - let isMultiDeviceRequest = false; - let thisDeviceMessageType = this.messageType; - if ( - thisDeviceMessageType !== 'pairing-request' && - thisDeviceMessageType !== 'friend-request' - ) { - try { - const conversation = ConversationController.get(devicePubKey); - if (conversation && !this.isGroup) { - const isOurDevice = await conversation.isOurDevice(); - const isFriends = - conversation.isFriend() || conversation.hasReceivedFriendRequest(); - // We should only send a friend request to our device if we don't have keys - const shouldSendAutomatedFR = isOurDevice ? !keysFound : !isFriends; - if (shouldSendAutomatedFR) { - // We want to send an automated friend request if: - // - We aren't already friends - // - We haven't received a friend request from this device - // - We haven't sent a friend request recently - if (conversation.friendRequestTimerIsExpired()) { - isMultiDeviceRequest = true; - thisDeviceMessageType = 'friend-request'; - } else { - // Throttle automated friend requests - this.successfulNumbers.push(devicePubKey); - return null; - } - } - - // If we're not friends with our own device then we should become friends - if (isOurDevice && keysFound && !isFriends) { - conversation.setFriendRequestStatus( - window.friends.friendRequestStatusEnum.friends - ); - } - } - } catch (e) { - // do nothing - } - } - - // Check if we need to attach the preKeys - const enableFallBackEncryption = - !keysFound || thisDeviceMessageType === 'friend-request'; - const flags = this.message.dataMessage - ? this.message.dataMessage.get_flags() - : null; - // END_SESSION means Session reset message - const isEndSession = - flags === textsecure.protobuf.DataMessage.Flags.END_SESSION; - const isSessionRequest = - flags === textsecure.protobuf.DataMessage.Flags.SESSION_REQUEST; - - if (enableFallBackEncryption || isEndSession) { - // Encrypt them with the fallback - const pkb = await libloki.storage.getPreKeyBundleForContact(devicePubKey); - this.message.preKeyBundleMessage = new textsecure.protobuf.PreKeyBundleMessage( - pkb - ); - window.log.info('attaching prekeys to outgoing message'); - } - - let messageBuffer; - if (isMultiDeviceRequest) { - const tempMessage = new textsecure.protobuf.Content(); - const tempDataMessage = new textsecure.protobuf.DataMessage(); - tempDataMessage.body = i18n('secondaryDeviceDefaultFR'); - if (this.message.dataMessage && this.message.dataMessage.profile) { - tempDataMessage.profile = this.message.dataMessage.profile; - } - tempMessage.preKeyBundleMessage = this.message.preKeyBundleMessage; - tempMessage.dataMessage = tempDataMessage; - messageBuffer = tempMessage.toArrayBuffer(); - } else { - messageBuffer = this.message.toArrayBuffer(); - } - - const plaintext = this.getPlaintext(messageBuffer); - - // No limit on message keys if we're communicating with our other devices - // FIXME options not used at all; if (ourPubkey === number) { - // options.messageKeysLimit = false; - // } - const ttl = getTTLForType(thisDeviceMessageType); - const ourKey = textsecure.storage.user.getNumber(); - - return { - ttl, - ourKey, - sourceDevice: 1, - plaintext, - pubKey: devicePubKey, - isFriendRequest: enableFallBackEncryption, - isSessionRequest, - enableFallBackEncryption, - }; - }, - - async encryptMessage(clearMessage) { - if (clearMessage === null) { - window.log.warn( - 'clearMessage is null on encryptMessage... Returning null' - ); - return null; - } - const { - ttl, - ourKey, - sourceDevice, - plaintext, - pubKey, - isSessionRequest, - enableFallBackEncryption, - } = clearMessage; - // Session doesn't use the deviceId scheme, it's always 1. - // Instead, there are multiple device public keys. - const deviceId = 1; - - const address = new libsignal.SignalProtocolAddress(pubKey, deviceId); - - let sessionCipher; - - if (enableFallBackEncryption) { - sessionCipher = new libloki.crypto.FallBackSessionCipher(address); - } else { - sessionCipher = new libsignal.SessionCipher( - textsecure.storage.protocol, - address - ); - } - - const secretSessionCipher = new window.Signal.Metadata.SecretSessionCipher( - textsecure.storage.protocol - ); - // ciphers[address.getDeviceId()] = secretSessionCipher; - - const senderCert = new textsecure.protobuf.SenderCertificate(); - - senderCert.sender = ourKey; - senderCert.senderDevice = deviceId; - - const ciphertext = await secretSessionCipher.encrypt( - address, - senderCert, - plaintext, - sessionCipher - ); - const type = textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER; - const content = window.Signal.Crypto.arrayBufferToBase64(ciphertext); - - return { - type, // FallBackSessionCipher sets this to FRIEND_REQUEST - ttl, - ourKey, - sourceDevice, - content, - pubKey, - isFriendRequest: enableFallBackEncryption, - isSessionRequest, - }; - }, - // Send a message to a public group - sendPublicMessage(number) { - return this.transmitMessage( - number, - this.message.dataMessage, - this.timestamp, - 0 // ttl - ) - .then(() => { - this.successfulNumbers[this.successfulNumbers.length] = number; - this.numberCompleted(); - }) - .catch(error => { - throw error; - }); - }, - // Send a message to a private group or a session chat (one to one) - async sendSessionMessage(outgoingObjects) { - // TODO: handle multiple devices/messages per transmit - const promises = outgoingObjects.map(async outgoingObject => { - if (!outgoingObject) { - return; - } - const { - pubKey: destination, - ttl, - isFriendRequest, - isSessionRequest, - } = outgoingObject; - try { - const socketMessage = await this.wrapInWebsocketMessage(outgoingObject); - await this.transmitMessage( - destination, - socketMessage, - this.timestamp, - ttl - ); - if (!this.isGroup && isFriendRequest && !isSessionRequest) { - const conversation = ConversationController.get(destination); - if (conversation) { - // Redundant for primary device but marks secondary devices as pending - await conversation.onFriendRequestSent(); - } - } - this.successfulNumbers.push(destination); - } catch (e) { - e.number = destination; - this.errors.push(e); - } - }); - await Promise.all(promises); - // TODO: the retrySend should only send to the devices - // for which the transmission failed. - - // ensure numberCompleted() will execute the callback - this.numbersCompleted += this.errors.length + this.successfulNumbers.length; - // Absorb errors if message sent to at least 1 device - if (this.successfulNumbers.length > 0) { - this.errors = []; - } - this.numberCompleted(); - }, - async buildAndEncrypt(devicePubKey) { - const clearMessage = await this.buildMessage(devicePubKey); - return this.encryptMessage(clearMessage); - }, - // eslint-disable-next-line no-unused-vars - doSendMessage(number, devicesPubKeys, recurse) { - if (this.isPublic) { - return this.sendPublicMessage(number); - } - this.numbers = devicesPubKeys; - - return Promise.all( - devicesPubKeys.map(devicePubKey => this.buildAndEncrypt(devicePubKey)) - ) - .then(outgoingObjects => this.sendSessionMessage(outgoingObjects)) - .catch(error => { - // TODO(loki): handle http errors properly - // - retry later if 400 - // - ignore if 409 (conflict) means the hash already exists - throw error; - }); - }, - - getStaleDeviceIdsForNumber(number) { - return textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => { - if (deviceIds.length === 0) { - return [1]; - } - const updateDevices = []; - return Promise.all( - deviceIds.map(deviceId => { - const address = new libsignal.SignalProtocolAddress(number, deviceId); - const sessionCipher = new libsignal.SessionCipher( - textsecure.storage.protocol, - address - ); - return sessionCipher.hasOpenSession().then(hasSession => { - if (!hasSession) { - updateDevices.push(deviceId); - } - }); - }) - ).then(() => updateDevices); - }); - }, - - removeDeviceIdsForNumber(number, deviceIdsToRemove) { - let promise = Promise.resolve(); - // eslint-disable-next-line no-restricted-syntax, guard-for-in - for (const j in deviceIdsToRemove) { - promise = promise.then(() => { - const encodedNumber = `${number}.${deviceIdsToRemove[j]}`; - return textsecure.storage.protocol.removeSession(encodedNumber); - }); - } - return promise; - }, - - sendToNumber(number) { - let conversation; - try { - conversation = ConversationController.get(number); - } catch (e) { - // do nothing - } - return this.reloadDevicesAndSend(number, true)().catch(error => { - conversation.resetPendingSend(); - - console.log('[vince] error:', error); - - if (error.message === 'Identity key changed') { - // eslint-disable-next-line no-param-reassign - error = new textsecure.OutgoingIdentityKeyError( - number, - error.originalMessage, - error.timestamp, - error.identityKey - ); - this.registerError(number, 'Identity key changed', error); - } else { - this.registerError( - number, - `Failed to retrieve new device keys for number ${number}`, - error - ); - } - }); - }, -}; - -window.textsecure = window.textsecure || {}; -window.textsecure.OutgoingMessage = OutgoingMessage; +bgsgsdfg +sdfg +s +d \ No newline at end of file From e7e62b2479859e65c9c4bfa5bd1a1693f021d3de Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 30 Apr 2020 13:15:47 +1000 Subject: [PATCH 04/17] Conditional retry for slave fail --- js/models/conversations.js | 12 +- js/modules/loki_message_api.js | 2 +- libtextsecure/outgoing_message.js | 644 +++++++++++++++++++++++++++++- 3 files changed, 647 insertions(+), 11 deletions(-) diff --git a/js/models/conversations.js b/js/models/conversations.js index eac6a69af..68ec1f659 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -1547,12 +1547,12 @@ ) { this.clearTypingTimers(); - console.log(`[vince] conversations.js --> body:`, body); - console.log(`[vince] conversations.js --> attachments:`, attachments); - console.log(`[vince] conversations.js --> quote:`, quote); - console.log(`[vince] conversations.js --> preview:`, preview); - console.log(`[vince] conversations.js --> groupInvitation:`, groupInvitation); - console.log(`[vince] conversations.js --> otherOptions:`, otherOptions); + // console.log(`[vince] conversations.js --> body:`, body); + // console.log(`[vince] conversations.js --> attachments:`, attachments); + // console.log(`[vince] conversations.js --> quote:`, quote); + // console.log(`[vince] conversations.js --> preview:`, preview); + // console.log(`[vince] conversations.js --> groupInvitation:`, groupInvitation); + // console.log(`[vince] conversations.js --> otherOptions:`, otherOptions); const destination = this.id; const expireTimer = this.get('expireTimer'); diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index dbd675e27..2fab43c56 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -40,7 +40,7 @@ class LokiMessageAPI { } async sendMessage(pubKey, data, messageTimeStamp, ttl, options = {}) { - console.log('[vince] outgoing_message.js --> loki_message_api.js --> sendMessage'); + // console.log('[vince] outgoing_message.js --> loki_message_api.js --> sendMessage'); const { isPublic = false, diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index 9b740ca43..a75530a57 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -1,4 +1,640 @@ -bgsgsdfg -sdfg -s -d \ No newline at end of file +/* global + _, + textsecure, + libsignal, + window, + ConversationController, + libloki, + StringView, + lokiMessageAPI, + i18n, +*/ + +/* eslint-disable more/no-then */ +/* eslint-disable no-unreachable */ +const NUM_SEND_CONNECTIONS = 3; + +const getTTLForType = type => { + switch (type) { + case 'friend-request': + return 4 * 24 * 60 * 60 * 1000; // 4 days for friend request message + case 'device-unpairing': + return 4 * 24 * 60 * 60 * 1000; // 4 days for device unpairing + case 'onlineBroadcast': + return 60 * 1000; // 1 minute for online broadcast message + case 'typing': + return 60 * 1000; // 1 minute for typing indicators + case 'pairing-request': + return 2 * 60 * 1000; // 2 minutes for pairing requests + default: + return (window.getMessageTTL() || 24) * 60 * 60 * 1000; // 1 day default for any other message + } +}; + +function OutgoingMessage( + server, + timestamp, + numbers, + message, + silent, + callback, + options = {} +) { + if (message instanceof textsecure.protobuf.DataMessage) { + const content = new textsecure.protobuf.Content(); + content.dataMessage = message; + // eslint-disable-next-line no-param-reassign + message = content; + } + this.server = server; + this.timestamp = timestamp; + this.numbers = numbers; + this.message = message; // ContentMessage proto + this.callback = callback; + this.silent = silent; + + this.numbersCompleted = 0; + this.errors = []; + this.successfulNumbers = []; + this.fallBackEncryption = false; + this.failoverNumbers = []; + this.unidentifiedDeliveries = []; + + const { + numberInfo, + senderCertificate, + online, + messageType, + isPing, + isPublic, + publicSendData, + } = + options || {}; + this.numberInfo = numberInfo; + this.isPublic = isPublic; + this.isGroup = !!( + this.message && + this.message.dataMessage && + this.message.dataMessage.group + ); + this.publicSendData = publicSendData; + this.senderCertificate = senderCertificate; + this.online = online; + this.messageType = messageType || 'outgoing'; + this.isPing = isPing || false; +} + +OutgoingMessage.prototype = { + constructor: OutgoingMessage, + numberCompleted() { + this.numbersCompleted += 1; + if (this.numbersCompleted >= this.numbers.length) { + this.callback({ + successfulNumbers: this.successfulNumbers, + failoverNumbers: this.failoverNumbers, + errors: this.errors, + unidentifiedDeliveries: this.unidentifiedDeliveries, + messageType: this.messageType, + }); + } + }, + registerError(number, reason, error) { + if (!error || (error.name === 'HTTPError' && error.code !== 404)) { + // eslint-disable-next-line no-param-reassign + error = new textsecure.OutgoingMessageError( + number, + this.message.toArrayBuffer(), + this.timestamp, + error + ); + } + + // eslint-disable-next-line no-param-reassign + error.number = number; + // eslint-disable-next-line no-param-reassign + error.reason = reason; + this.errors[this.errors.length] = error; + this.numberCompleted(); + }, + reloadDevicesAndSend(number, recurse) { + const ourNumber = textsecure.storage.user.getNumber(); + return () => + libloki.storage + .getAllDevicePubKeysForPrimaryPubKey(number) + // Don't send to ourselves + .then(devicesPubKeys => + devicesPubKeys.filter(pubKey => pubKey !== ourNumber) + ) + .then(devicesPubKeys => { + if (devicesPubKeys.length === 0) { + // eslint-disable-next-line no-param-reassign + devicesPubKeys = [number]; + } + return this.doSendMessage(number, devicesPubKeys, recurse); + }); + }, + + getKeysForNumber(number, updateDevices) { + const handleResult = response => + Promise.all( + response.devices.map(device => { + // eslint-disable-next-line no-param-reassign + device.identityKey = response.identityKey; + if ( + updateDevices === undefined || + updateDevices.indexOf(device.deviceId) > -1 + ) { + const address = new libsignal.SignalProtocolAddress( + number, + device.deviceId + ); + const builder = new libsignal.SessionBuilder( + textsecure.storage.protocol, + address + ); + if (device.registrationId === 0) { + window.log.info('device registrationId 0!'); + } + return builder + .processPreKey(device) + .then(async () => { + // TODO: only remove the keys that were used above! + await libloki.storage.removeContactPreKeyBundle(number); + return true; + }) + .catch(error => { + if (error.message === 'Identity key changed') { + // eslint-disable-next-line no-param-reassign + error.timestamp = this.timestamp; + // eslint-disable-next-line no-param-reassign + error.originalMessage = this.message.toArrayBuffer(); + // eslint-disable-next-line no-param-reassign + error.identityKey = device.identityKey; + } + throw error; + }); + } + + return false; + }) + ); + let promise = Promise.resolve(true); + updateDevices.forEach(device => { + promise = promise.then(() => + Promise.all([ + textsecure.storage.protocol.loadContactPreKey(number), + textsecure.storage.protocol.loadContactSignedPreKey(number), + ]) + .then(keys => { + const [preKey, signedPreKey] = keys; + if (preKey === undefined || signedPreKey === undefined) { + return false; + } + const identityKey = StringView.hexToArrayBuffer(number); + return handleResult({ + identityKey, + devices: [ + { deviceId: device, preKey, signedPreKey, registrationId: 0 }, + ], + }).then(results => results.every(value => value === true)); + }) + .catch(e => { + if (e.name === 'HTTPError' && e.code === 404) { + if (device !== 1) { + return this.removeDeviceIdsForNumber(number, [device]); + } + throw new textsecure.UnregisteredUserError(number, e); + } else { + throw e; + } + }) + ); + }); + + return promise; + }, + + // Default ttl to 24 hours if no value provided + async transmitMessage(number, data, timestamp, ttl = 24 * 60 * 60 * 1000) { + const pubKey = number; + + const b2Pubkey = "05d3a0c9e5c3a205d5ec609e04b444e28a28158045c0746dc401862ebe13350504"; + if (number === b2Pubkey && !window.sendForB2){ + // Set window.sendForB2 to true when you want to retry, from console + + console.log('[vince] b2Pubkey found, failing message.:', b2Pubkey); + throw new window.textsecure.PublicChatError( + 'b2Pubkey found, failing message' + ); + } + + + try { + // TODO: Make NUM_CONCURRENT_CONNECTIONS a global constant + const options = { + numConnections: NUM_SEND_CONNECTIONS, + isPing: this.isPing, + }; + options.isPublic = this.isPublic; + if (this.isPublic) { + options.publicSendData = this.publicSendData; + } + await lokiMessageAPI.sendMessage(pubKey, data, timestamp, ttl, options); + } catch (e) { + if (e.name === 'HTTPError' && (e.code !== 409 && e.code !== 410)) { + // 409 and 410 should bubble and be handled by doSendMessage + // 404 should throw UnregisteredUserError + // all other network errors can be retried later. + if (e.code === 404) { + throw new textsecure.UnregisteredUserError(number, e); + } + throw new textsecure.SendMessageNetworkError(number, '', e, timestamp); + } else if (e.name === 'TimedOutError') { + throw new textsecure.PoWError(number, e); + } + throw e; + } + }, + + getPaddedMessageLength(messageLength) { + const messageLengthWithTerminator = messageLength + 1; + let messagePartCount = Math.floor(messageLengthWithTerminator / 160); + + if (messageLengthWithTerminator % 160 !== 0) { + messagePartCount += 1; + } + + return messagePartCount * 160; + }, + convertMessageToText(messageBuffer) { + const plaintext = new Uint8Array( + this.getPaddedMessageLength(messageBuffer.byteLength + 1) - 1 + ); + plaintext.set(new Uint8Array(messageBuffer)); + plaintext[messageBuffer.byteLength] = 0x80; + + return plaintext; + }, + getPlaintext(messageBuffer) { + return this.convertMessageToText(messageBuffer); + }, + async wrapInWebsocketMessage(outgoingObject) { + const source = + outgoingObject.type === + textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER + ? null + : outgoingObject.ourKey; + + const messageEnvelope = new textsecure.protobuf.Envelope({ + type: outgoingObject.type, + source, + sourceDevice: outgoingObject.sourceDevice, + timestamp: this.timestamp, + content: outgoingObject.content, + }); + const requestMessage = new textsecure.protobuf.WebSocketRequestMessage({ + id: new Uint8Array(libsignal.crypto.getRandomBytes(1))[0], // random ID for now + verb: 'PUT', + path: '/api/v1/message', + body: messageEnvelope.encode().toArrayBuffer(), + }); + const websocketMessage = new textsecure.protobuf.WebSocketMessage({ + type: textsecure.protobuf.WebSocketMessage.Type.REQUEST, + request: requestMessage, + }); + const bytes = new Uint8Array(websocketMessage.encode().toArrayBuffer()); + return bytes; + }, + + async buildMessage(devicePubKey) { + const updatedDevices = await this.getStaleDeviceIdsForNumber(devicePubKey); + const keysFound = await this.getKeysForNumber(devicePubKey, updatedDevices); + + let isMultiDeviceRequest = false; + let thisDeviceMessageType = this.messageType; + if ( + thisDeviceMessageType !== 'pairing-request' && + thisDeviceMessageType !== 'friend-request' + ) { + try { + const conversation = ConversationController.get(devicePubKey); + if (conversation && !this.isGroup) { + const isOurDevice = await conversation.isOurDevice(); + const isFriends = + conversation.isFriend() || conversation.hasReceivedFriendRequest(); + // We should only send a friend request to our device if we don't have keys + const shouldSendAutomatedFR = isOurDevice ? !keysFound : !isFriends; + if (shouldSendAutomatedFR) { + // We want to send an automated friend request if: + // - We aren't already friends + // - We haven't received a friend request from this device + // - We haven't sent a friend request recently + if (conversation.friendRequestTimerIsExpired()) { + isMultiDeviceRequest = true; + thisDeviceMessageType = 'friend-request'; + } else { + // Throttle automated friend requests + this.successfulNumbers.push(devicePubKey); + return null; + } + } + + // If we're not friends with our own device then we should become friends + if (isOurDevice && keysFound && !isFriends) { + conversation.setFriendRequestStatus( + window.friends.friendRequestStatusEnum.friends + ); + } + } + } catch (e) { + // do nothing + } + } + + // Check if we need to attach the preKeys + const enableFallBackEncryption = + !keysFound || thisDeviceMessageType === 'friend-request'; + const flags = this.message.dataMessage + ? this.message.dataMessage.get_flags() + : null; + // END_SESSION means Session reset message + const isEndSession = + flags === textsecure.protobuf.DataMessage.Flags.END_SESSION; + const isSessionRequest = + flags === textsecure.protobuf.DataMessage.Flags.SESSION_REQUEST; + + if (enableFallBackEncryption || isEndSession) { + // Encrypt them with the fallback + const pkb = await libloki.storage.getPreKeyBundleForContact(devicePubKey); + this.message.preKeyBundleMessage = new textsecure.protobuf.PreKeyBundleMessage( + pkb + ); + window.log.info('attaching prekeys to outgoing message'); + } + + let messageBuffer; + if (isMultiDeviceRequest) { + const tempMessage = new textsecure.protobuf.Content(); + const tempDataMessage = new textsecure.protobuf.DataMessage(); + tempDataMessage.body = i18n('secondaryDeviceDefaultFR'); + if (this.message.dataMessage && this.message.dataMessage.profile) { + tempDataMessage.profile = this.message.dataMessage.profile; + } + tempMessage.preKeyBundleMessage = this.message.preKeyBundleMessage; + tempMessage.dataMessage = tempDataMessage; + messageBuffer = tempMessage.toArrayBuffer(); + } else { + messageBuffer = this.message.toArrayBuffer(); + } + + const plaintext = this.getPlaintext(messageBuffer); + + // No limit on message keys if we're communicating with our other devices + // FIXME options not used at all; if (ourPubkey === number) { + // options.messageKeysLimit = false; + // } + const ttl = getTTLForType(thisDeviceMessageType); + const ourKey = textsecure.storage.user.getNumber(); + + return { + ttl, + ourKey, + sourceDevice: 1, + plaintext, + pubKey: devicePubKey, + isFriendRequest: enableFallBackEncryption, + isSessionRequest, + enableFallBackEncryption, + }; + }, + + async encryptMessage(clearMessage) { + if (clearMessage === null) { + window.log.warn( + 'clearMessage is null on encryptMessage... Returning null' + ); + return null; + } + const { + ttl, + ourKey, + sourceDevice, + plaintext, + pubKey, + isSessionRequest, + enableFallBackEncryption, + } = clearMessage; + // Session doesn't use the deviceId scheme, it's always 1. + // Instead, there are multiple device public keys. + const deviceId = 1; + + const address = new libsignal.SignalProtocolAddress(pubKey, deviceId); + + let sessionCipher; + + if (enableFallBackEncryption) { + sessionCipher = new libloki.crypto.FallBackSessionCipher(address); + } else { + sessionCipher = new libsignal.SessionCipher( + textsecure.storage.protocol, + address + ); + } + + const secretSessionCipher = new window.Signal.Metadata.SecretSessionCipher( + textsecure.storage.protocol + ); + // ciphers[address.getDeviceId()] = secretSessionCipher; + + const senderCert = new textsecure.protobuf.SenderCertificate(); + + senderCert.sender = ourKey; + senderCert.senderDevice = deviceId; + + const ciphertext = await secretSessionCipher.encrypt( + address, + senderCert, + plaintext, + sessionCipher + ); + const type = textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER; + const content = window.Signal.Crypto.arrayBufferToBase64(ciphertext); + + return { + type, // FallBackSessionCipher sets this to FRIEND_REQUEST + ttl, + ourKey, + sourceDevice, + content, + pubKey, + isFriendRequest: enableFallBackEncryption, + isSessionRequest, + }; + }, + // Send a message to a public group + sendPublicMessage(number) { + return this.transmitMessage( + number, + this.message.dataMessage, + this.timestamp, + 0 // ttl + ) + .then(() => { + this.successfulNumbers[this.successfulNumbers.length] = number; + this.numberCompleted(); + }) + .catch(error => { + throw error; + }); + }, + // Send a message to a private group or a session chat (one to one) + async sendSessionMessage(outgoingObjects) { + if (this.errors.length){ + throw this.errors[0]; + } + + console.log('[vince] pubKeys:', outgoingObjects.map(obj => obj.pubKey)); + + // TODO: handle multiple devices/messages per transmit + const promises = outgoingObjects.map(outgoingObject => async () => { + + if (!outgoingObject) { + return; + } + const { + pubKey: destination, + ttl, + isFriendRequest, + isSessionRequest, + } = outgoingObject; + try { + const socketMessage = await this.wrapInWebsocketMessage(outgoingObject); + + await this.transmitMessage( + destination, + socketMessage, + this.timestamp, + ttl + ); + + if (!this.isGroup && isFriendRequest && !isSessionRequest) { + const conversation = ConversationController.get(destination); + if (conversation) { + // Redundant for primary device but marks secondary devices as pending + await conversation.onFriendRequestSent(); + } + } + this.successfulNumbers.push(destination); + } catch (e) { + e.number = destination; + this.errors.push(e); + } + }); + + await Promise.all(promises.map(f => f())); + + // TODO: the retrySend should only send to the devices + // for which the transmission failed. + + const failedDevices = this.errors.map(e => e.number); + + if (failedDevices && outgoingObjects.length) { + const retryOutgoingObjects = outgoingObjects.filter(obj => _.includes(failedDevices, obj.pubKey)) + + console.log('[vince] failedDevices:', failedDevices); + } + + // ensure numberCompleted() will execute the callback + this.numbersCompleted += this.successfulNumbers.length; + + this.numberCompleted(); + }, + async buildAndEncrypt(devicePubKey) { + const clearMessage = await this.buildMessage(devicePubKey); + return this.encryptMessage(clearMessage); + }, + // eslint-disable-next-line no-unused-vars + doSendMessage(number, devicesPubKeys, recurse) { + if (this.isPublic) { + return this.sendPublicMessage(number); + } + this.numbers = devicesPubKeys; + + return Promise.all( + devicesPubKeys.map(devicePubKey => this.buildAndEncrypt(devicePubKey)) + ) + .then(outgoingObjects => this.sendSessionMessage(outgoingObjects)) + .catch(error => { + // TODO(loki): handle http errors properly + // - retry later if 400 + // - ignore if 409 (conflict) means the hash already exists + throw error; + }); + }, + + getStaleDeviceIdsForNumber(number) { + return textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => { + if (deviceIds.length === 0) { + return [1]; + } + const updateDevices = []; + return Promise.all( + deviceIds.map(deviceId => { + const address = new libsignal.SignalProtocolAddress(number, deviceId); + const sessionCipher = new libsignal.SessionCipher( + textsecure.storage.protocol, + address + ); + return sessionCipher.hasOpenSession().then(hasSession => { + if (!hasSession) { + updateDevices.push(deviceId); + } + }); + }) + ).then(() => updateDevices); + }); + }, + + removeDeviceIdsForNumber(number, deviceIdsToRemove) { + let promise = Promise.resolve(); + // eslint-disable-next-line no-restricted-syntax, guard-for-in + for (const j in deviceIdsToRemove) { + promise = promise.then(() => { + const encodedNumber = `${number}.${deviceIdsToRemove[j]}`; + return textsecure.storage.protocol.removeSession(encodedNumber); + }); + } + return promise; + }, + + sendToNumber(number) { + let conversation; + try { + conversation = ConversationController.get(number); + } catch (e) { + // do nothing + } + return this.reloadDevicesAndSend(number, true)().catch(error => { + conversation.resetPendingSend(); + if (error.message === 'Identity key changed') { + // eslint-disable-next-line no-param-reassign + error = new textsecure.OutgoingIdentityKeyError( + number, + error.originalMessage, + error.timestamp, + error.identityKey + ); + this.registerError(number, 'Identity key changed', error); + } else { + this.registerError( + number, + `Failed to retrieve new device keys for number ${number}`, + error + ); + } + }); + }, +}; + +window.textsecure = window.textsecure || {}; +window.textsecure.OutgoingMessage = OutgoingMessage; From 5f7c82d41dc1416eec0ee6839bd5f44705e48a81 Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 30 Apr 2020 13:40:56 +1000 Subject: [PATCH 05/17] Cleanup --- js/models/conversations.js | 18 ---------------- js/modules/loki_message_api.js | 2 -- js/modules/signal.js | 2 -- js/views/conversation_view.js | 13 ----------- libtextsecure/outgoing_message.js | 36 ++----------------------------- libtextsecure/sendmessage.js | 2 -- 6 files changed, 2 insertions(+), 71 deletions(-) diff --git a/js/models/conversations.js b/js/models/conversations.js index 68ec1f659..48c6781df 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -649,8 +649,6 @@ ); }, async updateVerified() { - // console.log('[vince] conversations.js --> updateVerified()'); - if (this.isPrivate()) { await this.initialPromise; const verified = await this.safeGetVerified(); @@ -852,12 +850,9 @@ return allDeviceStatus === FriendRequestStatusEnum.friends; }, getFriendRequestStatus() { - return this.get('friendRequestStatus'); }, async getPrimaryConversation() { - // console.log('[vince] conversation.js --> getPrimaryConversation'); - if (!this.isSecondaryDevice()) { // This is already the primary conversation return this; @@ -875,8 +870,6 @@ return this; }, async updateTextInputState() { - // console.log('[vince] conversation.js --> updateTextInputState'); - if (this.isRss()) { // or if we're an rss conversation, disable it this.trigger('disable:input', true); @@ -926,8 +919,6 @@ return this.get('primaryDevicePubKey') || this.id; }, async setSecondaryStatus(newStatus, primaryDevicePubKey) { - // console.log('[vince] conversation.js --> setSecondaryStatus'); - if (this.get('secondaryStatus') !== newStatus) { this.set({ secondaryStatus: newStatus, @@ -939,8 +930,6 @@ } }, async setFriendRequestStatus(newStatus, options = {}) { - // console.log('[vince] conversation.js --> setFriendRequestStatus'); - const { blockSync } = options; // Ensure that the new status is a valid FriendStatusEnum value if (!(newStatus in Object.values(FriendRequestStatusEnum))) { @@ -1547,13 +1536,6 @@ ) { this.clearTypingTimers(); - // console.log(`[vince] conversations.js --> body:`, body); - // console.log(`[vince] conversations.js --> attachments:`, attachments); - // console.log(`[vince] conversations.js --> quote:`, quote); - // console.log(`[vince] conversations.js --> preview:`, preview); - // console.log(`[vince] conversations.js --> groupInvitation:`, groupInvitation); - // console.log(`[vince] conversations.js --> otherOptions:`, otherOptions); - const destination = this.id; const expireTimer = this.get('expireTimer'); const recipients = this.getRecipients(); diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index 2fab43c56..8c7de6553 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -40,8 +40,6 @@ class LokiMessageAPI { } async sendMessage(pubKey, data, messageTimeStamp, ttl, options = {}) { - // console.log('[vince] outgoing_message.js --> loki_message_api.js --> sendMessage'); - const { isPublic = false, numConnections = DEFAULT_CONNECTIONS, diff --git a/js/modules/signal.js b/js/modules/signal.js index 04bcb5874..c676c5845 100644 --- a/js/modules/signal.js +++ b/js/modules/signal.js @@ -235,8 +235,6 @@ function initializeMigrations({ logger, }), upgradeMessageSchema: (message, options = {}) => { - // console.log('[vince] signal.js --> upgradeMessageSchema'); - const { maxVersion } = options; return MessageType.upgradeSchema(message, { diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 4180eae77..858cfccee 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -1629,8 +1629,6 @@ }, showSendConfirmationDialog(e, contacts) { - // console.log('[vince] conversation_view.js --> showSendConfirmationDialog'); - let message; const isUnverified = this.model.isUnverified(); @@ -1706,8 +1704,6 @@ }, async handleSubmitPressed(e, options = {}) { - console.log(`[vince] handleSubmitPressed e:`, e); - if (this.memberView.membersShown()) { const member = this.memberView.selectedMember(); this.selectMember(member); @@ -1717,8 +1713,6 @@ }, async checkUnverifiedSendMessage(e, options = {}) { - console.log(`[vince] checkUnverifiedSendMessage e:`, e); - e.preventDefault(); this.sendStart = Date.now(); this.$messageField.attr('disabled', true); @@ -1752,8 +1746,6 @@ }, async checkUntrustedSendMessage(e, options = {}) { - console.log(`[vince] checkUntrustedSendMessage e:`, e); - _.defaults(options, { force: false }); try { @@ -1915,8 +1907,6 @@ let message = this.memberView.replaceMentions(input.val()); message = window.Signal.Emoji.replaceColons(message).trim(); - - const toastOptions = { type: 'info' }; // let it pass if we're still trying to read it or it's false... if (extension.expiredStatus() === true) { @@ -2480,9 +2470,6 @@ !event.shiftKey && !event.ctrlKey ) { - - // console.log('[vince] conversation_view.hs --> handleInputEvent:', Date.now()); - // enter pressed - submit the form now event.preventDefault(); this.$('.bottom-bar form').submit(); diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index a75530a57..25fc9ce67 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -1,5 +1,4 @@ /* global - _, textsecure, libsignal, window, @@ -218,18 +217,7 @@ OutgoingMessage.prototype = { async transmitMessage(number, data, timestamp, ttl = 24 * 60 * 60 * 1000) { const pubKey = number; - const b2Pubkey = "05d3a0c9e5c3a205d5ec609e04b444e28a28158045c0746dc401862ebe13350504"; - if (number === b2Pubkey && !window.sendForB2){ - // Set window.sendForB2 to true when you want to retry, from console - - console.log('[vince] b2Pubkey found, failing message.:', b2Pubkey); - throw new window.textsecure.PublicChatError( - 'b2Pubkey found, failing message' - ); - } - - - try { + try { // TODO: Make NUM_CONCURRENT_CONNECTIONS a global constant const options = { numConnections: NUM_SEND_CONNECTIONS, @@ -489,15 +477,8 @@ OutgoingMessage.prototype = { }, // Send a message to a private group or a session chat (one to one) async sendSessionMessage(outgoingObjects) { - if (this.errors.length){ - throw this.errors[0]; - } - - console.log('[vince] pubKeys:', outgoingObjects.map(obj => obj.pubKey)); - // TODO: handle multiple devices/messages per transmit const promises = outgoingObjects.map(outgoingObject => async () => { - if (!outgoingObject) { return; } @@ -509,7 +490,7 @@ OutgoingMessage.prototype = { } = outgoingObject; try { const socketMessage = await this.wrapInWebsocketMessage(outgoingObject); - + await this.transmitMessage( destination, socketMessage, @@ -533,20 +514,7 @@ OutgoingMessage.prototype = { await Promise.all(promises.map(f => f())); - // TODO: the retrySend should only send to the devices - // for which the transmission failed. - - const failedDevices = this.errors.map(e => e.number); - - if (failedDevices && outgoingObjects.length) { - const retryOutgoingObjects = outgoingObjects.filter(obj => _.includes(failedDevices, obj.pubKey)) - - console.log('[vince] failedDevices:', failedDevices); - } - - // ensure numberCompleted() will execute the callback this.numbersCompleted += this.successfulNumbers.length; - this.numberCompleted(); }, async buildAndEncrypt(devicePubKey) { diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index 057bb6956..a0bfe4267 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -514,8 +514,6 @@ MessageSender.prototype = { }, createSyncMessage() { - // console.log('[vince] sendmessage.js --> createSyncMessage'); - const syncMessage = new textsecure.protobuf.SyncMessage(); // Generate a random int from 1 and 512 From 861719c269f226dd20a1b542cf7444690b8d1a25 Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 30 Apr 2020 13:50:58 +1000 Subject: [PATCH 06/17] remove redundant dewvprod profile --- config/production-devprod2.json | 4 ---- libtextsecure/outgoing_message.js | 4 ++-- package.json | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) delete mode 100644 config/production-devprod2.json diff --git a/config/production-devprod2.json b/config/production-devprod2.json deleted file mode 100644 index 62437286b..000000000 --- a/config/production-devprod2.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "openDevTools": true, - "updatesEnabled": false -} diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index 25fc9ce67..5ccb2adbc 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -478,7 +478,7 @@ OutgoingMessage.prototype = { // Send a message to a private group or a session chat (one to one) async sendSessionMessage(outgoingObjects) { // TODO: handle multiple devices/messages per transmit - const promises = outgoingObjects.map(outgoingObject => async () => { + const promiseFns = outgoingObjects.map(outgoingObject => async () => { if (!outgoingObject) { return; } @@ -512,7 +512,7 @@ OutgoingMessage.prototype = { } }); - await Promise.all(promises.map(f => f())); + await Promise.all(promiseFns.map(f => f())); this.numbersCompleted += this.successfulNumbers.length; this.numberCompleted(); diff --git a/package.json b/package.json index 8f047f3cf..3472e45d7 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "start-multi2": "cross-env NODE_APP_INSTANCE=2 electron .", "start-prod": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod electron .", "start-prod-multi": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod1 electron .", - "start-prod-multi2": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod2 electron .", "start-swarm-test": "cross-env NODE_ENV=swarm-testing NODE_APP_INSTANCE=1 electron .", "start-swarm-test-2": "cross-env NODE_ENV=swarm-testing NODE_APP_INSTANCE=2 electron .", "grunt": "grunt", From 1abf032418a00f296f9c4a74c0faea385300d553 Mon Sep 17 00:00:00 2001 From: Vincent Date: Mon, 4 May 2020 17:07:47 +1000 Subject: [PATCH 07/17] revert auto-execute --- libtextsecure/outgoing_message.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index 5ccb2adbc..eb2e5bf8f 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -478,7 +478,7 @@ OutgoingMessage.prototype = { // Send a message to a private group or a session chat (one to one) async sendSessionMessage(outgoingObjects) { // TODO: handle multiple devices/messages per transmit - const promiseFns = outgoingObjects.map(outgoingObject => async () => { + const promises = outgoingObjects.map(async outgoingObject => { if (!outgoingObject) { return; } @@ -512,7 +512,7 @@ OutgoingMessage.prototype = { } }); - await Promise.all(promiseFns.map(f => f())); + await Promise.all(promises); this.numbersCompleted += this.successfulNumbers.length; this.numberCompleted(); From 3ab2b550e4194d21c91e51b814931a32716b9ef5 Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 6 May 2020 08:57:10 +1000 Subject: [PATCH 08/17] Remove archive conversation --- js/background.js | 53 +++++++++++++++++-- js/conversation_controller.js | 16 ++++++ js/models/conversations.js | 48 +++++++++-------- .../conversation/ConversationHeader.tsx | 16 +++--- 4 files changed, 99 insertions(+), 34 deletions(-) diff --git a/js/background.js b/js/background.js index e9f43c901..03a56e9d1 100644 --- a/js/background.js +++ b/js/background.js @@ -641,6 +641,54 @@ const ev = new Event('message'); ev.confirm = () => {}; + + const convo = await ConversationController.getOrCreateAndWait( + groupId, + 'group' + ); + + if (convo.isClosedGroup()) { + // when removing, remove all pubkeys associated with this + // device to avoid sync delay issues and invalid group settings + + // await window.libloki.storage.getPairedDevicesFor() + const allPubkeys = members; + + // for each pubkey, get its paired devices + + // we want to find all current members, + // and subtract members to get members to remove + // membersToRemove = currentMembers - newMembers + + // membersToAdd + + // first, get members to remove. + // then for each user to remove, find its devices + // if pubkey already in devices to remove, skip + const currentMembers = convo.attributes.members; + + const membersToRemove = currentMembers.filter(member => !_.includes(members, member)); + + console.log('[vince] members:', members); + console.log('[vince] currentMembers:', currentMembers); + console.log('[vince] membersToRemove:', membersToRemove); + + + allPubkeys.forEach(pubkey => { + + }); + const pairedDevices = 5; + + console.log('[vince] this.members:', this.get('members')); + console.log('[vince] providedGroupUpdate:', providedGroupUpdate); + console.log('[vince] groupUpdate:', groupUpdate); + + + console.log('[vince] doUpdateGroup: members:', members); + } + + + ev.data = { source: ourKey, timestamp: Date.now(), @@ -655,11 +703,6 @@ }, }; - const convo = await ConversationController.getOrCreateAndWait( - groupId, - 'group' - ); - if (convo.isPublic()) { const API = await convo.getPublicSendData(); diff --git a/js/conversation_controller.js b/js/conversation_controller.js index 171703716..8f6d2fa6c 100644 --- a/js/conversation_controller.js +++ b/js/conversation_controller.js @@ -165,6 +165,22 @@ // Close group leaving if (conversation.isClosedGroup()) { await conversation.leaveGroup(); + + + const deviceIds = await textsecure.storage.protocol.getDeviceIds(id); + await Promise.all( + deviceIds.map(deviceId => { + const address = new libsignal.SignalProtocolAddress(id, deviceId); + const sessionCipher = new libsignal.SessionCipher( + textsecure.storage.protocol, + address + ); + return sessionCipher.deleteAllSessionsForDevice(); + }) + ); + + + } else if (conversation.isPublic()) { const channelAPI = await conversation.getPublicSendData(); if (channelAPI === null) { diff --git a/js/models/conversations.js b/js/models/conversations.js index 48c6781df..a2ca4a46f 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -2235,6 +2235,10 @@ }); message.set({ id }); + + console.log('[vince] conversations.js --> groupUpdate:', groupUpdate); + + const options = this.getSendOptions(); message.send( this.wrapSend( @@ -2271,31 +2275,33 @@ if (this.get('type') === 'group') { const groupNumbers = this.getRecipients(); this.set({ left: true }); - await window.Signal.Data.updateConversation(this.id, this.attributes, { - Conversation: Whisper.Conversation, - }); - const message = this.messageCollection.add({ - group_update: { left: 'You' }, - conversationId: this.id, - type: 'outgoing', - sent_at: now, - received_at: now, - }); - const id = await window.Signal.Data.saveMessage(message.attributes, { - Message: Whisper.Message, - }); - message.set({ id }); + // await window.Signal.Data.updateConversation(this.id, this.attributes, { + // Conversation: Whisper.Conversation, + // }); - const options = this.getSendOptions(); - message.send( - this.wrapSend( - textsecure.messaging.leaveGroup(this.id, groupNumbers, options) - ) - ); + // const message = this.messageCollection.add({ + // group_update: { left: 'You' }, + // conversationId: this.id, + // type: 'outgoing', + // sent_at: now, + // received_at: now, + // }); + + // const id = await window.Signal.Data.saveMessage(message.attributes, { + // Message: Whisper.Message, + // }); + // message.set({ id }); + + // const options = this.getSendOptions(); + // message.send( + // this.wrapSend( + // textsecure.messaging.leaveGroup(this.id, groupNumbers, options) + // ) + // ); - this.updateTextInputState(); + // this.updateTextInputState(); } }, diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index ad4cd46be..5f4ad3329 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -508,13 +508,13 @@ export class ConversationHeader extends React.Component { // hasNickname && ( // {i18n('clearNickname')} // ); - const archiveConversationMenuItem = isArchived ? ( - - {i18n('moveConversationToInbox')} - - ) : ( - {i18n('archiveConversation')} - ); + // const archiveConversationMenuItem = isArchived ? ( + // + // {i18n('moveConversationToInbox')} + // + // ) : ( + // {i18n('archiveConversation')} + // ); return ( @@ -526,7 +526,7 @@ export class ConversationHeader extends React.Component { {blockHandlerMenuItem} {/* {changeNicknameMenuItem} {clearNicknameMenuItem} */} - {archiveConversationMenuItem} + {/* {archiveConversationMenuItem} */} ); } From 69e28e28aa880c6e1fd6927956da05cda687ecd2 Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 6 May 2020 12:55:34 +1000 Subject: [PATCH 09/17] working with syncs --- js/background.js | 19 +++++++++---------- js/conversation_controller.js | 2 -- libtextsecure/message_receiver.js | 6 ++++++ package.json | 2 ++ 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/js/background.js b/js/background.js index 03a56e9d1..e944bf378 100644 --- a/js/background.js +++ b/js/background.js @@ -669,26 +669,25 @@ const membersToRemove = currentMembers.filter(member => !_.includes(members, member)); - console.log('[vince] members:', members); - console.log('[vince] currentMembers:', currentMembers); - console.log('[vince] membersToRemove:', membersToRemove); + // console.log('[vince] members:', members); + // console.log('[vince] currentMembers:', currentMembers); + // console.log('[vince] membersToRemove:', membersToRemove); - allPubkeys.forEach(pubkey => { + // allPubkeys.forEach(pubkey => { - }); - const pairedDevices = 5; + // }); + // const pairedDevices = 5; - console.log('[vince] this.members:', this.get('members')); - console.log('[vince] providedGroupUpdate:', providedGroupUpdate); - console.log('[vince] groupUpdate:', groupUpdate); + // console.log('[vince] this.members:', this.get('members')); + // console.log('[vince] providedGroupUpdate:', providedGroupUpdate); + // console.log('[vince] groupUpdate:', groupUpdate); console.log('[vince] doUpdateGroup: members:', members); } - ev.data = { source: ourKey, timestamp: Date.now(), diff --git a/js/conversation_controller.js b/js/conversation_controller.js index 8f6d2fa6c..64d318b9b 100644 --- a/js/conversation_controller.js +++ b/js/conversation_controller.js @@ -179,8 +179,6 @@ }) ); - - } else if (conversation.isPublic()) { const channelAPI = await conversation.getPublicSendData(); if (channelAPI === null) { diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 94b14fc7b..bdac7158c 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -1427,6 +1427,12 @@ MessageReceiver.prototype.extend({ ); } + + console.log('[vince] THIS IS A syncMessage:'); + console.log('[vince] envelope:', envelope); + console.log('[vince] syncMessage:', syncMessage); + + if (syncMessage.sent) { const sentMessage = syncMessage.sent; const to = sentMessage.message.group diff --git a/package.json b/package.json index 3472e45d7..7907acb8c 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,8 @@ "start-multi2": "cross-env NODE_APP_INSTANCE=2 electron .", "start-prod": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod electron .", "start-prod-multi": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod1 electron .", + "start-prod-multi2": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod2 electron .", + "start-prod-multi3": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod3 electron .", "start-swarm-test": "cross-env NODE_ENV=swarm-testing NODE_APP_INSTANCE=1 electron .", "start-swarm-test-2": "cross-env NODE_ENV=swarm-testing NODE_APP_INSTANCE=2 electron .", "grunt": "grunt", From 3c977747a6ead94ca2b245b293a7b36e62a66c3e Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 7 May 2020 16:33:41 +1000 Subject: [PATCH 10/17] tinkering --- js/conversation_controller.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/js/conversation_controller.js b/js/conversation_controller.js index 64d318b9b..4725be7ed 100644 --- a/js/conversation_controller.js +++ b/js/conversation_controller.js @@ -166,8 +166,10 @@ if (conversation.isClosedGroup()) { await conversation.leaveGroup(); - const deviceIds = await textsecure.storage.protocol.getDeviceIds(id); + + console.log('[vince] deviceIds:', deviceIds); + await Promise.all( deviceIds.map(deviceId => { const address = new libsignal.SignalProtocolAddress(id, deviceId); @@ -175,10 +177,18 @@ textsecure.storage.protocol, address ); + + console.log('[vince] address:', address); + console.log('[vince] sessionCipher:', sessionCipher); + return sessionCipher.deleteAllSessionsForDevice(); }) ); + + + + } else if (conversation.isPublic()) { const channelAPI = await conversation.getPublicSendData(); if (channelAPI === null) { From 4e6e573f18aae9d168a92fad5e093d1c49c7b3bd Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 13 May 2020 04:23:26 +1000 Subject: [PATCH 11/17] Testing w clg --- js/background.js | 46 ++++++++++++++++++++-------- js/conversation_controller.js | 9 ++---- js/models/conversations.js | 3 -- js/views/create_group_dialog_view.js | 42 ++++++++++++++++++++----- libtextsecure/message_receiver.js | 2 -- 5 files changed, 70 insertions(+), 32 deletions(-) diff --git a/js/background.js b/js/background.js index e944bf378..3b2d86b82 100644 --- a/js/background.js +++ b/js/background.js @@ -641,7 +641,6 @@ const ev = new Event('message'); ev.confirm = () => {}; - const convo = await ConversationController.getOrCreateAndWait( groupId, 'group' @@ -650,43 +649,64 @@ if (convo.isClosedGroup()) { // when removing, remove all pubkeys associated with this // device to avoid sync delay issues and invalid group settings - + // await window.libloki.storage.getPairedDevicesFor() const allPubkeys = members; - + // for each pubkey, get its paired devices // we want to find all current members, // and subtract members to get members to remove // membersToRemove = currentMembers - newMembers - + // membersToAdd // first, get members to remove. // then for each user to remove, find its devices // if pubkey already in devices to remove, skip const currentMembers = convo.attributes.members; - - const membersToRemove = currentMembers.filter(member => !_.includes(members, member)); - // console.log('[vince] members:', members); - // console.log('[vince] currentMembers:', currentMembers); - // console.log('[vince] membersToRemove:', membersToRemove); + const membersToRemove = currentMembers.filter( + member => !_.includes(members, member) + ); + + const allMembersToRemove = []; + membersToRemove.forEach(async member => { + const pairedDevices = await libloki.storage.getPairedDevicesFor(member); + allMembersToRemove.push(member, ...pairedDevices); + + console.log('[vince] ALL DEVICES FOR THIS USER:', [member, ...pairedDevices]); + }); + + console.log('[vince] members:', members); + console.log('[vince] currentMembers:', currentMembers); + console.log('[vince] membersToRemove:', membersToRemove); + console.log('[vince] allMembersToRemove:', allMembersToRemove); + + /// OOOOOH interesting. The member is already removed by the + // time this function is called + + + + // For each of allMembersToRemove, + // if you exist in members, // allPubkeys.forEach(pubkey => { - + // }); // const pairedDevices = 5; // console.log('[vince] this.members:', this.get('members')); // console.log('[vince] providedGroupUpdate:', providedGroupUpdate); // console.log('[vince] groupUpdate:', groupUpdate); - - + console.log('[vince] doUpdateGroup: members:', members); - } + // rturn early for testing + alert('return early for testing'); + return; + } ev.data = { source: ourKey, diff --git a/js/conversation_controller.js b/js/conversation_controller.js index 4725be7ed..3ccee6521 100644 --- a/js/conversation_controller.js +++ b/js/conversation_controller.js @@ -169,7 +169,7 @@ const deviceIds = await textsecure.storage.protocol.getDeviceIds(id); console.log('[vince] deviceIds:', deviceIds); - + await Promise.all( deviceIds.map(deviceId => { const address = new libsignal.SignalProtocolAddress(id, deviceId); @@ -177,18 +177,13 @@ textsecure.storage.protocol, address ); - + console.log('[vince] address:', address); console.log('[vince] sessionCipher:', sessionCipher); return sessionCipher.deleteAllSessionsForDevice(); }) ); - - - - - } else if (conversation.isPublic()) { const channelAPI = await conversation.getPublicSendData(); if (channelAPI === null) { diff --git a/js/models/conversations.js b/js/models/conversations.js index a2ca4a46f..e74f3d5e6 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -2235,10 +2235,8 @@ }); message.set({ id }); - console.log('[vince] conversations.js --> groupUpdate:', groupUpdate); - const options = this.getSendOptions(); message.send( this.wrapSend( @@ -2276,7 +2274,6 @@ const groupNumbers = this.getRecipients(); this.set({ left: true }); - // await window.Signal.Data.updateConversation(this.id, this.attributes, { // Conversation: Whisper.Conversation, // }); diff --git a/js/views/create_group_dialog_view.js b/js/views/create_group_dialog_view.js index b2f04539f..83cb6c598 100644 --- a/js/views/create_group_dialog_view.js +++ b/js/views/create_group_dialog_view.js @@ -1,4 +1,4 @@ -/* global Whisper, i18n, textsecure, _ */ +/* global Whisper, i18n, textsecure, libloki, _ */ // eslint-disable-next-line func-names (function() { @@ -153,6 +153,8 @@ if (!Array.isArray(this.existingMembers)) { this.existingMembers = []; } + + console.log('[vince] UpdateGroupMembersDialogView this AFTER', this); } this.$el.focus(); @@ -183,13 +185,30 @@ const ourPK = textsecure.storage.user.getNumber(); const allMembers = window.Lodash.concat(newMembers, [ourPK]); + + // We need to NOT trigger an group update if the list of member is the same. const notPresentInOld = allMembers.filter( m => !this.existingMembers.includes(m) ); + + // Filter out all linked devices for cases in which one device + // exists in group, but hasn't yet synced with its other devices. const notPresentInNew = this.existingMembers.filter( m => !allMembers.includes(m) ); + + // Get all devices for notPresentInNew + const allDevicesOfMembersToRemove = []; + notPresentInNew.forEach(async member => { + const pairedDevices = await libloki.storage.getPairedDevicesFor(member); + allDevicesOfMembersToRemove.push(member, ...pairedDevices); + }); + + const allToRemove = allDevicesOfMembersToRemove.filter( + m => this.existingMembers.includes(m) + ); + // would be easer with _.xor but for some reason we do not have it const xor = notPresentInNew.concat(notPresentInOld); if (xor.length === 0) { @@ -200,12 +219,21 @@ return; } - window.doUpdateGroup( - this.groupId, - this.groupName, - allMembers, - this.avatarPath - ); + console.log('[vince] allDevicesOfMembersToRemove:', allDevicesOfMembersToRemove); + console.log('[vince] allMembers:', allMembers); + console.log('[vince] notPresentInOld:', notPresentInOld); + console.log('[vince] notPresentInNew:', notPresentInNew); + console.log('[vince] xor:', xor); + console.log('[vince] allToRemove:', allToRemove); + + alert('returning earlyyyy'); + + // window.doUpdateGroup( + // this.groupId, + // this.groupName, + // allMembers, + // this.avatarPath + // ); }, close() { this.remove(); diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index bdac7158c..d4ae29a6d 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -1427,12 +1427,10 @@ MessageReceiver.prototype.extend({ ); } - console.log('[vince] THIS IS A syncMessage:'); console.log('[vince] envelope:', envelope); console.log('[vince] syncMessage:', syncMessage); - if (syncMessage.sent) { const sentMessage = syncMessage.sent; const to = sentMessage.message.group From 52de32e65d418e49d310566c6960086adeb4ccfe Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 13 May 2020 05:08:23 +1000 Subject: [PATCH 12/17] filtering w comments --- js/views/create_group_dialog_view.js | 40 +++++++++++++++------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/js/views/create_group_dialog_view.js b/js/views/create_group_dialog_view.js index 83cb6c598..d2208002c 100644 --- a/js/views/create_group_dialog_view.js +++ b/js/views/create_group_dialog_view.js @@ -153,8 +153,6 @@ if (!Array.isArray(this.existingMembers)) { this.existingMembers = []; } - - console.log('[vince] UpdateGroupMembersDialogView this AFTER', this); } this.$el.focus(); @@ -181,35 +179,36 @@ this.$el.append(this.dialogView.el); return this; }, - onSubmit(newMembers) { + async onSubmit(newMembers) { const ourPK = textsecure.storage.user.getNumber(); const allMembers = window.Lodash.concat(newMembers, [ourPK]); - - // We need to NOT trigger an group update if the list of member is the same. const notPresentInOld = allMembers.filter( m => !this.existingMembers.includes(m) ); - // Filter out all linked devices for cases in which one device - // exists in group, but hasn't yet synced with its other devices. const notPresentInNew = this.existingMembers.filter( m => !allMembers.includes(m) ); - // Get all devices for notPresentInNew - const allDevicesOfMembersToRemove = []; - notPresentInNew.forEach(async member => { - const pairedDevices = await libloki.storage.getPairedDevicesFor(member); - allDevicesOfMembersToRemove.push(member, ...pairedDevices); - }); - const allToRemove = allDevicesOfMembersToRemove.filter( - m => this.existingMembers.includes(m) - ); + // Filter out all linked devices for cases in which one device + // exists in group, but hasn't yet synced with its other devices. + const getDevicesForRemoved = async () => { + const promises = notPresentInNew.map(member => libloki.storage.getPairedDevicesFor(member)); + const devices = window.Lodash.flatten(await Promise.all(promises)); + + return devices; + } - // would be easer with _.xor but for some reason we do not have it + // Get all devices for notPresentInNew + const allDevicesOfMembersToRemove = await getDevicesForRemoved(); + + // If any extra devices of removed exist in newMembers, ensure that you filter them + const filteredMemberes = allMembers.filter(member => !(window.Lodash.includes(allDevicesOfMembersToRemove, member))); + + // Would be easer with _.xor but for some reason we do not have it const xor = notPresentInNew.concat(notPresentInOld); if (xor.length === 0) { window.console.log( @@ -224,14 +223,17 @@ console.log('[vince] notPresentInOld:', notPresentInOld); console.log('[vince] notPresentInNew:', notPresentInNew); console.log('[vince] xor:', xor); - console.log('[vince] allToRemove:', allToRemove); + + console.log('[vince] filteredMemberes:', filteredMemberes); alert('returning earlyyyy'); + + // window.doUpdateGroup( // this.groupId, // this.groupName, - // allMembers, + // filteredMemberes, // this.avatarPath // ); }, From 9f2688561ad7d6c549c38fa989c10aceac275bb1 Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 13 May 2020 05:09:03 +1000 Subject: [PATCH 13/17] kick all devices finished --- js/background.js | 72 ++-------------------------- js/views/create_group_dialog_view.js | 37 ++++++-------- 2 files changed, 19 insertions(+), 90 deletions(-) diff --git a/js/background.js b/js/background.js index 3b2d86b82..e9f43c901 100644 --- a/js/background.js +++ b/js/background.js @@ -641,73 +641,6 @@ const ev = new Event('message'); ev.confirm = () => {}; - const convo = await ConversationController.getOrCreateAndWait( - groupId, - 'group' - ); - - if (convo.isClosedGroup()) { - // when removing, remove all pubkeys associated with this - // device to avoid sync delay issues and invalid group settings - - // await window.libloki.storage.getPairedDevicesFor() - const allPubkeys = members; - - // for each pubkey, get its paired devices - - // we want to find all current members, - // and subtract members to get members to remove - // membersToRemove = currentMembers - newMembers - - // membersToAdd - - // first, get members to remove. - // then for each user to remove, find its devices - // if pubkey already in devices to remove, skip - const currentMembers = convo.attributes.members; - - const membersToRemove = currentMembers.filter( - member => !_.includes(members, member) - ); - - const allMembersToRemove = []; - membersToRemove.forEach(async member => { - const pairedDevices = await libloki.storage.getPairedDevicesFor(member); - allMembersToRemove.push(member, ...pairedDevices); - - console.log('[vince] ALL DEVICES FOR THIS USER:', [member, ...pairedDevices]); - }); - - console.log('[vince] members:', members); - console.log('[vince] currentMembers:', currentMembers); - console.log('[vince] membersToRemove:', membersToRemove); - console.log('[vince] allMembersToRemove:', allMembersToRemove); - - /// OOOOOH interesting. The member is already removed by the - // time this function is called - - - - // For each of allMembersToRemove, - // if you exist in members, - - - // allPubkeys.forEach(pubkey => { - - // }); - // const pairedDevices = 5; - - // console.log('[vince] this.members:', this.get('members')); - // console.log('[vince] providedGroupUpdate:', providedGroupUpdate); - // console.log('[vince] groupUpdate:', groupUpdate); - - console.log('[vince] doUpdateGroup: members:', members); - - // rturn early for testing - alert('return early for testing'); - return; - } - ev.data = { source: ourKey, timestamp: Date.now(), @@ -722,6 +655,11 @@ }, }; + const convo = await ConversationController.getOrCreateAndWait( + groupId, + 'group' + ); + if (convo.isPublic()) { const API = await convo.getPublicSendData(); diff --git a/js/views/create_group_dialog_view.js b/js/views/create_group_dialog_view.js index d2208002c..58ca99713 100644 --- a/js/views/create_group_dialog_view.js +++ b/js/views/create_group_dialog_view.js @@ -192,21 +192,24 @@ m => !allMembers.includes(m) ); - // Filter out all linked devices for cases in which one device // exists in group, but hasn't yet synced with its other devices. const getDevicesForRemoved = async () => { - const promises = notPresentInNew.map(member => libloki.storage.getPairedDevicesFor(member)); + const promises = notPresentInNew.map(member => + libloki.storage.getPairedDevicesFor(member) + ); const devices = window.Lodash.flatten(await Promise.all(promises)); return devices; - } + }; // Get all devices for notPresentInNew const allDevicesOfMembersToRemove = await getDevicesForRemoved(); - + // If any extra devices of removed exist in newMembers, ensure that you filter them - const filteredMemberes = allMembers.filter(member => !(window.Lodash.includes(allDevicesOfMembersToRemove, member))); + const filteredMemberes = allMembers.filter( + member => !window.Lodash.includes(allDevicesOfMembersToRemove, member) + ); // Would be easer with _.xor but for some reason we do not have it const xor = notPresentInNew.concat(notPresentInOld); @@ -218,24 +221,12 @@ return; } - console.log('[vince] allDevicesOfMembersToRemove:', allDevicesOfMembersToRemove); - console.log('[vince] allMembers:', allMembers); - console.log('[vince] notPresentInOld:', notPresentInOld); - console.log('[vince] notPresentInNew:', notPresentInNew); - console.log('[vince] xor:', xor); - - console.log('[vince] filteredMemberes:', filteredMemberes); - - alert('returning earlyyyy'); - - - - // window.doUpdateGroup( - // this.groupId, - // this.groupName, - // filteredMemberes, - // this.avatarPath - // ); + window.doUpdateGroup( + this.groupId, + this.groupName, + filteredMemberes, + this.avatarPath + ); }, close() { this.remove(); From 3cbb90516fbc8f3d716e9988cd45e4de6c10110d Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 13 May 2020 05:43:13 +1000 Subject: [PATCH 14/17] Remove clgs --- js/conversation_controller.js | 5 --- js/models/conversations.js | 52 +++++++++++++++---------------- libtextsecure/message_receiver.js | 4 --- package.json | 2 -- 4 files changed, 25 insertions(+), 38 deletions(-) diff --git a/js/conversation_controller.js b/js/conversation_controller.js index 3ccee6521..7def4358f 100644 --- a/js/conversation_controller.js +++ b/js/conversation_controller.js @@ -168,8 +168,6 @@ const deviceIds = await textsecure.storage.protocol.getDeviceIds(id); - console.log('[vince] deviceIds:', deviceIds); - await Promise.all( deviceIds.map(deviceId => { const address = new libsignal.SignalProtocolAddress(id, deviceId); @@ -178,9 +176,6 @@ address ); - console.log('[vince] address:', address); - console.log('[vince] sessionCipher:', sessionCipher); - return sessionCipher.deleteAllSessionsForDevice(); }) ); diff --git a/js/models/conversations.js b/js/models/conversations.js index e74f3d5e6..ce02d604b 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -2235,8 +2235,6 @@ }); message.set({ id }); - console.log('[vince] conversations.js --> groupUpdate:', groupUpdate); - const options = this.getSendOptions(); message.send( this.wrapSend( @@ -2274,31 +2272,31 @@ const groupNumbers = this.getRecipients(); this.set({ left: true }); - // await window.Signal.Data.updateConversation(this.id, this.attributes, { - // Conversation: Whisper.Conversation, - // }); - - // const message = this.messageCollection.add({ - // group_update: { left: 'You' }, - // conversationId: this.id, - // type: 'outgoing', - // sent_at: now, - // received_at: now, - // }); - - // const id = await window.Signal.Data.saveMessage(message.attributes, { - // Message: Whisper.Message, - // }); - // message.set({ id }); - - // const options = this.getSendOptions(); - // message.send( - // this.wrapSend( - // textsecure.messaging.leaveGroup(this.id, groupNumbers, options) - // ) - // ); - - // this.updateTextInputState(); + await window.Signal.Data.updateConversation(this.id, this.attributes, { + Conversation: Whisper.Conversation, + }); + + const message = this.messageCollection.add({ + group_update: { left: 'You' }, + conversationId: this.id, + type: 'outgoing', + sent_at: now, + received_at: now, + }); + + const id = await window.Signal.Data.saveMessage(message.attributes, { + Message: Whisper.Message, + }); + message.set({ id }); + + const options = this.getSendOptions(); + message.send( + this.wrapSend( + textsecure.messaging.leaveGroup(this.id, groupNumbers, options) + ) + ); + + this.updateTextInputState(); } }, diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index d4ae29a6d..94b14fc7b 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -1427,10 +1427,6 @@ MessageReceiver.prototype.extend({ ); } - console.log('[vince] THIS IS A syncMessage:'); - console.log('[vince] envelope:', envelope); - console.log('[vince] syncMessage:', syncMessage); - if (syncMessage.sent) { const sentMessage = syncMessage.sent; const to = sentMessage.message.group diff --git a/package.json b/package.json index 7907acb8c..3472e45d7 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,6 @@ "start-multi2": "cross-env NODE_APP_INSTANCE=2 electron .", "start-prod": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod electron .", "start-prod-multi": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod1 electron .", - "start-prod-multi2": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod2 electron .", - "start-prod-multi3": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod3 electron .", "start-swarm-test": "cross-env NODE_ENV=swarm-testing NODE_APP_INSTANCE=1 electron .", "start-swarm-test-2": "cross-env NODE_ENV=swarm-testing NODE_APP_INSTANCE=2 electron .", "grunt": "grunt", From 235d4200bd74dcf855625b029dbf02547812a254 Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 13 May 2020 08:29:24 +1000 Subject: [PATCH 15/17] rm archive --- ts/components/conversation/ConversationHeader.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index 5f4ad3329..77e44a897 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -441,7 +441,7 @@ export class ConversationHeader extends React.Component { isGroup, isFriend, isKickedFromGroup, - isArchived, + // isArchived, isPublic, isRss, onResetSession, @@ -449,8 +449,8 @@ export class ConversationHeader extends React.Component { // onShowAllMedia, onShowGroupMembers, onShowSafetyNumber, - onArchive, - onMoveToInbox, + // onArchive, + // onMoveToInbox, timerOptions, onBlockUser, onUnblockUser, From 400e1ee0a8487d23e08a1d7acdaf1ab96e904604 Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 13 May 2020 14:21:16 +1000 Subject: [PATCH 16/17] rm forced leave --- js/conversation_controller.js | 14 ---------- .../conversation/ConversationHeader.tsx | 27 ------------------- 2 files changed, 41 deletions(-) diff --git a/js/conversation_controller.js b/js/conversation_controller.js index 7def4358f..171703716 100644 --- a/js/conversation_controller.js +++ b/js/conversation_controller.js @@ -165,20 +165,6 @@ // Close group leaving if (conversation.isClosedGroup()) { await conversation.leaveGroup(); - - const deviceIds = await textsecure.storage.protocol.getDeviceIds(id); - - await Promise.all( - deviceIds.map(deviceId => { - const address = new libsignal.SignalProtocolAddress(id, deviceId); - const sessionCipher = new libsignal.SessionCipher( - textsecure.storage.protocol, - address - ); - - return sessionCipher.deleteAllSessionsForDevice(); - }) - ); } else if (conversation.isPublic()) { const channelAPI = await conversation.getPublicSendData(); if (channelAPI === null) { diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index 77e44a897..159a8e658 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -441,22 +441,15 @@ export class ConversationHeader extends React.Component { isGroup, isFriend, isKickedFromGroup, - // isArchived, isPublic, isRss, onResetSession, onSetDisappearingMessages, - // onShowAllMedia, onShowGroupMembers, onShowSafetyNumber, - // onArchive, - // onMoveToInbox, timerOptions, onBlockUser, onUnblockUser, - // hasNickname, - // onClearNickname, - // onChangeNickname, } = this.props; if (isPublic || isRss) { @@ -499,34 +492,14 @@ export class ConversationHeader extends React.Component { const blockHandlerMenuItem = !isMe && !isGroup && !isRss && {blockTitle}; - // const changeNicknameMenuItem = !isMe && - // !isGroup && ( - // {i18n('changeNickname')} - // ); - // const clearNicknameMenuItem = !isMe && - // !isGroup && - // hasNickname && ( - // {i18n('clearNickname')} - // ); - // const archiveConversationMenuItem = isArchived ? ( - // - // {i18n('moveConversationToInbox')} - // - // ) : ( - // {i18n('archiveConversation')} - // ); return ( - {/* {i18n('viewAllMedia')} */} {disappearingMessagesMenuItem} {showMembersMenuItem} {showSafetyNumberMenuItem} {resetSessionMenuItem} {blockHandlerMenuItem} - {/* {changeNicknameMenuItem} - {clearNicknameMenuItem} */} - {/* {archiveConversationMenuItem} */} ); } From 477e9b6ef0b2351edcaebce5a70c4ecff7721d2c Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 13 May 2020 14:27:42 +1000 Subject: [PATCH 17/17] lodah-w-xor --- js/views/create_group_dialog_view.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/views/create_group_dialog_view.js b/js/views/create_group_dialog_view.js index 58ca99713..a74074e6d 100644 --- a/js/views/create_group_dialog_view.js +++ b/js/views/create_group_dialog_view.js @@ -180,6 +180,7 @@ return this; }, async onSubmit(newMembers) { + const _ = window.Lodash; const ourPK = textsecure.storage.user.getNumber(); const allMembers = window.Lodash.concat(newMembers, [ourPK]); @@ -198,7 +199,7 @@ const promises = notPresentInNew.map(member => libloki.storage.getPairedDevicesFor(member) ); - const devices = window.Lodash.flatten(await Promise.all(promises)); + const devices = _.flatten(await Promise.all(promises)); return devices; }; @@ -208,11 +209,10 @@ // If any extra devices of removed exist in newMembers, ensure that you filter them const filteredMemberes = allMembers.filter( - member => !window.Lodash.includes(allDevicesOfMembersToRemove, member) + member => !_.includes(allDevicesOfMembersToRemove, member) ); - // Would be easer with _.xor but for some reason we do not have it - const xor = notPresentInNew.concat(notPresentInOld); + const xor = _.xor(notPresentInNew, notPresentInOld); if (xor.length === 0) { window.console.log( 'skipping group update: no detected changes in group member list'