From 948e32fbf7254ebef6ea0996f260419bc89899e7 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 13 Jul 2020 16:28:03 +1000 Subject: [PATCH 1/8] update our top left avatar on avatar changes when our primary device updates the avatar, we wait for a sync message to know about this update. Once this message comes in, it updates the avatar on the conversation, but nothing refresh the ActionPanel component. This commit makes sure we listen for changes on our conversation to trigger this refresh. --- ts/components/EditProfileDialog.tsx | 2 +- ts/components/session/ActionsPanel.tsx | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/ts/components/EditProfileDialog.tsx b/ts/components/EditProfileDialog.tsx index 54abe0337..0c21c7e9c 100644 --- a/ts/components/EditProfileDialog.tsx +++ b/ts/components/EditProfileDialog.tsx @@ -337,7 +337,7 @@ export class EditProfileDialog extends React.Component { setProfileName: this.state.profileName, }, () => { - // Update settinngs in dialog complete; + // Update settings in dialog complete; // now callback to reloadactions panel avatar this.props.callback(this.state.avatar); } diff --git a/ts/components/session/ActionsPanel.tsx b/ts/components/session/ActionsPanel.tsx index 296cd02b4..13633cacb 100644 --- a/ts/components/session/ActionsPanel.tsx +++ b/ts/components/session/ActionsPanel.tsx @@ -42,6 +42,16 @@ export class ActionsPanel extends React.Component { this.setState({ avatarPath: conversation.getAvatarPath(), }); + // When our primary device updates its avatar, we will need for a message sync to know about that. + // Once we get the avatar update, we need to refresh this react component. + // So we listen to changes on our profile avatar and use the updated avatarPath (done on message received). + conversation.on('change', () => { + if (conversation.changed?.profileAvatar) { + this.setState({ + avatarPath: conversation.getAvatarPath(), + }); + } + }); } ); } From f641b9436b67917ec1a488056099e2ed7fd60824 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 14 Jul 2020 08:48:48 +1000 Subject: [PATCH 2/8] fix our handling of snode sending errors so we have a real error shown when a message fails to send because of it. Also, fix the retry logic on the corresponding message --- js/models/messages.js | 5 ++++- js/modules/loki_message_api.js | 3 ++- ts/session/snode_api/serviceNodeAPI.ts | 2 -- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/js/models/messages.js b/js/models/messages.js index f913208fa..69102da1c 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -1087,6 +1087,9 @@ } const { body, attachments, preview, quote } = await this.uploadData(); + const ourNumber = window.storage.get('primaryDevicePubKey'); + const ourConversation = window.ConversationController.get(ourNumber); + const lokiProfile = ourConversation.getOurProfile(); const chatMessage = new libsession.Messages.Outgoing.ChatMessage({ identifier: this.id, @@ -1096,7 +1099,7 @@ attachments, preview, quote, - lokiProfile: this.conversation.getOurProfile(), + lokiProfile, }); // Special-case the self-send case - we send only a sync message diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index 3c7d9b7f7..6f7a81652 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -96,8 +96,9 @@ class LokiMessageAPI { // eslint-disable-next-line more/no-then snode = await primitives.firstTrue(promises); } catch (e) { + const snodeStr = snode ? `${snode.ip}:${snode.port}` : 'null'; log.warn( - `loki_message:::sendMessage - ${e.code} ${e.message} to ${pubKey} via ${snode.ip}:${snode.port}` + `loki_message:::sendMessage - ${e.code} ${e.message} to ${pubKey} via snode:${snodeStr}` ); if (e instanceof textsecure.WrongDifficultyError) { // Force nonce recalculation diff --git a/ts/session/snode_api/serviceNodeAPI.ts b/ts/session/snode_api/serviceNodeAPI.ts index 2742fa9a4..3d8aa459f 100644 --- a/ts/session/snode_api/serviceNodeAPI.ts +++ b/ts/session/snode_api/serviceNodeAPI.ts @@ -298,8 +298,6 @@ export async function storeOnNode( return false; } - const res = snodeRes as any; - const json = JSON.parse(snodeRes.body); // Make sure we aren't doing too much PoW const currentDifficulty = window.storage.get('PoWDifficulty', null); From 3fd260b7935bef79b37b7ae0366c1e82137e5096 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 14 Jul 2020 11:27:19 +1000 Subject: [PATCH 3/8] enforce type of profileKey and signature on linking messages --- js/models/conversations.js | 2 +- libtextsecure/account_manager.js | 16 ++++++---------- .../content/link/DeviceLinkGrantMessage.ts | 7 +++++++ .../content/link/DeviceLinkRequestMessage.ts | 10 ++++++++++ 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/js/models/conversations.js b/js/models/conversations.js index 60e70460e..b67d943e7 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -2713,7 +2713,7 @@ const ourConversation = window.ConversationController.get(ourNumber); let profileKey = null; if (this.get('profileSharing')) { - profileKey = storage.get('profileKey'); + profileKey = new Uint8Array(storage.get('profileKey')); } const avatarPointer = ourConversation.get('avatarPointer'); const { displayName } = ourConversation.getLokiProfile(); diff --git a/libtextsecure/account_manager.js b/libtextsecure/account_manager.js index e39ca1984..573e31caa 100644 --- a/libtextsecure/account_manager.js +++ b/libtextsecure/account_manager.js @@ -553,7 +553,7 @@ timestamp: Date.now(), primaryDevicePubKey, secondaryDevicePubKey: ourPubKey, - requestSignature, + requestSignature: new Uint8Array(requestSignature), } ); await window.libsession @@ -616,14 +616,7 @@ ); // We need to send the our profile to the secondary device - const { displayName } = ourConversation.getLokiProfile(); - const avatarPointer = ourConversation.get('avatarPointer'); - const profileKey = window.storage.get('profileKey'); - const lokiProfile = { - displayName, - profileKey, - avatarPointer, - }; + const lokiProfile = ourConversation.getOurProfile(); // Try to upload to the file server and then send a message try { @@ -631,7 +624,10 @@ const requestPairingMessage = new libsession.Messages.Outgoing.DeviceLinkGrantMessage( { timestamp: Date.now(), - ...authorisation, + primaryDevicePubKey: ourPubKey, + secondaryDevicePubKey: secondaryDeviceStr, + requestSignature: new Uint8Array(requestSignature), + grantSignature: new Uint8Array(grantSignature), lokiProfile, } ); diff --git a/ts/session/messages/outgoing/content/link/DeviceLinkGrantMessage.ts b/ts/session/messages/outgoing/content/link/DeviceLinkGrantMessage.ts index a07217104..af4750782 100644 --- a/ts/session/messages/outgoing/content/link/DeviceLinkGrantMessage.ts +++ b/ts/session/messages/outgoing/content/link/DeviceLinkGrantMessage.ts @@ -25,6 +25,13 @@ export class DeviceLinkGrantMessage extends DeviceLinkRequestMessage { requestSignature: params.requestSignature, }); + if (!(params.lokiProfile.profileKey instanceof Uint8Array)) { + throw new TypeError('profileKey must be of type Uint8Array'); + } + if (!(params.grantSignature instanceof Uint8Array)) { + throw new TypeError('grantSignature must be of type Uint8Array'); + } + this.displayName = params.lokiProfile.displayName; this.avatarPointer = params.lokiProfile.avatarPointer; this.profileKey = params.lokiProfile.profileKey; diff --git a/ts/session/messages/outgoing/content/link/DeviceLinkRequestMessage.ts b/ts/session/messages/outgoing/content/link/DeviceLinkRequestMessage.ts index 945f9f00e..58c071ca9 100644 --- a/ts/session/messages/outgoing/content/link/DeviceLinkRequestMessage.ts +++ b/ts/session/messages/outgoing/content/link/DeviceLinkRequestMessage.ts @@ -14,6 +14,16 @@ export class DeviceLinkRequestMessage extends ContentMessage { constructor(params: DeviceLinkMessageParams) { super({ timestamp: params.timestamp, identifier: params.identifier }); + + if (!(params.requestSignature instanceof Uint8Array)) { + throw new TypeError('requestSignature must be of type Uint8Array'); + } + if (typeof params.primaryDevicePubKey !== 'string') { + throw new TypeError('primaryDevicePubKey must be of type string'); + } + if (typeof params.secondaryDevicePubKey !== 'string') { + throw new TypeError('secondaryDevicePubKey must be of type string'); + } this.primaryDevicePubKey = params.primaryDevicePubKey; this.secondaryDevicePubKey = params.secondaryDevicePubKey; this.requestSignature = params.requestSignature; From 5eb2f35c458e1967722cfef0cad60be2d2009f9a Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 14 Jul 2020 11:27:43 +1000 Subject: [PATCH 4/8] remove toast on link request when the linking screen is opened --- ts/components/DevicePairingDialog.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ts/components/DevicePairingDialog.tsx b/ts/components/DevicePairingDialog.tsx index e56bba982..c15604d88 100644 --- a/ts/components/DevicePairingDialog.tsx +++ b/ts/components/DevicePairingDialog.tsx @@ -262,14 +262,6 @@ export class DevicePairingDialog extends React.Component { private requestReceived(secondaryDevicePubKey: string | EventHandlerNonNull) { // FIFO: push at the front of the array with unshift() this.state.pubKeyRequests.unshift(secondaryDevicePubKey); - window.pushToast({ - title: window.i18n('gotPairingRequest'), - description: `${window.shortenPubkey( - secondaryDevicePubKey - )} ${window.i18n( - 'showPairingWordsTitle' - )}: ${window.mnemonic.pubkey_to_secret_words(secondaryDevicePubKey)}`, - }); if (!this.state.currentPubKey) { this.nextPubKey(); this.stopReceivingRequests(); From d2e7462627695417c6fceed54a2a5c58f7da52a8 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 14 Jul 2020 11:28:10 +1000 Subject: [PATCH 5/8] unregister to change events for avatarUpdates on ActionPanel --- ts/components/session/ActionsPanel.tsx | 32 ++++++++++++++++++++------ 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/ts/components/session/ActionsPanel.tsx b/ts/components/session/ActionsPanel.tsx index 13633cacb..2664b6ba0 100644 --- a/ts/components/session/ActionsPanel.tsx +++ b/ts/components/session/ActionsPanel.tsx @@ -24,6 +24,7 @@ interface Props { } export class ActionsPanel extends React.Component { + private ourConversation: any; constructor(props: Props) { super(props); this.state = { @@ -31,6 +32,7 @@ export class ActionsPanel extends React.Component { }; this.editProfileHandle = this.editProfileHandle.bind(this); + this.refreshAvatarCallback = this.refreshAvatarCallback.bind(this); } public componentDidMount() { @@ -45,17 +47,33 @@ export class ActionsPanel extends React.Component { // When our primary device updates its avatar, we will need for a message sync to know about that. // Once we get the avatar update, we need to refresh this react component. // So we listen to changes on our profile avatar and use the updated avatarPath (done on message received). - conversation.on('change', () => { - if (conversation.changed?.profileAvatar) { - this.setState({ - avatarPath: conversation.getAvatarPath(), - }); - } - }); + this.ourConversation = conversation; + + this.ourConversation.on( + 'change', + () => { + this.refreshAvatarCallback(this.ourConversation); + }, + 'refreshAvatarCallback' + ); } ); } + public refreshAvatarCallback(conversation: any) { + if (conversation.changed?.profileAvatar) { + this.setState({ + avatarPath: conversation.getAvatarPath(), + }); + } + } + + public componentWillUnmount() { + if (this.ourConversation) { + this.ourConversation.off('change', null, 'refreshAvatarCallback'); + } + } + public Section = ({ isSelected, onSelect, From 839938bad704b6495b22bba009a28659a25c3367 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 14 Jul 2020 11:39:15 +1000 Subject: [PATCH 6/8] add missing timestamp field for some Outgoing messages --- js/background.js | 5 +++-- js/models/conversations.js | 2 +- libtextsecure/sendmessage.js | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/js/background.js b/js/background.js index 36741687e..48d63175f 100644 --- a/js/background.js +++ b/js/background.js @@ -1391,10 +1391,11 @@ pubKey ); await window.lokiFileServerAPI.updateOurDeviceMapping(); - // TODO: we should ensure the message was sent and retry automatically if not const device = new libsession.Types.PubKey(pubKey); const unlinkMessage = new libsession.Messages.Outgoing.DeviceUnlinkMessage( - pubKey + { + timestamp: Date.now(), + } ); await libsession.getMessageQueue().send(device, unlinkMessage); diff --git a/js/models/conversations.js b/js/models/conversations.js index 6611f9578..6cc5f6a0a 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -1382,7 +1382,7 @@ const groupInvitMessage = new libsession.Messages.Outgoing.GroupInvitationMessage( { identifier: id, - + timestamp: Date.now(), serverName: groupInvitation.name, channelId: groupInvitation.channelId, serverAddress: groupInvitation.address, diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index 1b45085e2..f09aaa36d 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -458,6 +458,7 @@ MessageSender.prototype = { if (myDevice !== 1 && myDevice !== '1') { const syncReadMessages = new libsession.Messages.Outgoing.SyncReadMessage( { + timestamp: Date.now(), readMessages: reads, } ); From ae1f86325f0881a1f9a2f15039cd1efe3ac9759c Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 14 Jul 2020 11:39:40 +1000 Subject: [PATCH 7/8] lint --- ts/test/session/crypto/MessageEncrypter_test.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ts/test/session/crypto/MessageEncrypter_test.ts b/ts/test/session/crypto/MessageEncrypter_test.ts index 2e1753512..6fcb3119e 100644 --- a/ts/test/session/crypto/MessageEncrypter_test.ts +++ b/ts/test/session/crypto/MessageEncrypter_test.ts @@ -52,10 +52,9 @@ describe('MessageEncrypter', () => { it('should return a MEDIUM_GROUP_CIPHERTEXT envelope type', async () => { const data = crypto.randomBytes(10); - sandbox.stub( - Ratchet, - 'encryptWithSenderKey' - ).resolves({ ciphertext: '' }); + sandbox + .stub(Ratchet, 'encryptWithSenderKey') + .resolves({ ciphertext: '' }); const result = await MessageEncrypter.encrypt( TestUtils.generateFakePubKey(), From 48e4862c8ce31c61d9a7c1262cd10be8160a74be Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 14 Jul 2020 11:59:17 +1000 Subject: [PATCH 8/8] be sure ourConversation is set in retrySend() --- js/models/messages.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/js/models/messages.js b/js/models/messages.js index 69102da1c..d2b9433a4 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -1089,9 +1089,8 @@ const { body, attachments, preview, quote } = await this.uploadData(); const ourNumber = window.storage.get('primaryDevicePubKey'); const ourConversation = window.ConversationController.get(ourNumber); - const lokiProfile = ourConversation.getOurProfile(); - const chatMessage = new libsession.Messages.Outgoing.ChatMessage({ + const chatParams = { identifier: this.id, body, timestamp: this.get('sent_at'), @@ -1099,8 +1098,14 @@ attachments, preview, quote, - lokiProfile, - }); + }; + if (ourConversation) { + chatParams.lokiProfile = ourConversation.getOurProfile(); + } + + const chatMessage = new libsession.Messages.Outgoing.ChatMessage( + chatParams + ); // Special-case the self-send case - we send only a sync message if (recipients.length === 1) {