From c1cea3ca04a392a60cb2745130609dface009458 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 4 Feb 2020 14:33:29 -0800 Subject: [PATCH 01/22] turn off snode proxy logging --- js/modules/loki_rpc.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index d725f04ed..9bd613a4e 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -33,12 +33,6 @@ const sendToProxy = async (options = {}, targetNode) => { const url = `https://${randSnode.ip}:${randSnode.port}/proxy`; - log.info( - `Proxy snode request to ${targetNode.pubkey_ed25519} via ${ - randSnode.pubkey_ed25519 - }` - ); - const snPubkeyHex = StringView.hexToArrayBuffer(targetNode.pubkey_x25519); const myKeys = window.libloki.crypto.snodeCipher._ephemeralKeyPair; From 739ac4ce4e54e8bcd58613634ba9e0ed12cc7fcf Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 4 Feb 2020 14:33:47 -0800 Subject: [PATCH 02/22] include useful info on error --- js/modules/loki_snode_api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/modules/loki_snode_api.js b/js/modules/loki_snode_api.js index 14a536e53..edb19873b 100644 --- a/js/modules/loki_snode_api.js +++ b/js/modules/loki_snode_api.js @@ -151,7 +151,7 @@ class LokiSnodeAPI { const snodes = result.snodes.filter(tSnode => tSnode.ip !== '0.0.0.0'); return snodes; } catch (e) { - log.error('getSwarmNodes', JSON.stringify(e)); + log.error('getSwarmNodes error', e.code, e.message); // this.randomSnodePool = _.without( this.randomSnodePool, From 8767a57ddd2be9a4ed1efd7c9ce7e0bc375623e4 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 4 Feb 2020 14:34:25 -0800 Subject: [PATCH 03/22] actually validate URL before starting up a bunch of timers --- js/modules/loki_public_chat_api.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/js/modules/loki_public_chat_api.js b/js/modules/loki_public_chat_api.js index 20f68a17d..d0398f6dd 100644 --- a/js/modules/loki_public_chat_api.js +++ b/js/modules/loki_public_chat_api.js @@ -1,4 +1,4 @@ -/* global log, window */ +/* global log, window, process */ const EventEmitter = require('events'); const nodeFetch = require('node-fetch'); const LokiAppDotNetAPI = require('./loki_app_dot_net_api'); @@ -24,7 +24,25 @@ class LokiPublicChatFactoryAPI extends EventEmitter { ); if (!thisServer) { log.info(`LokiAppDotNetAPI creating ${serverUrl}`); + + // test to make sure it's online (and maybe has a valid SSL cert) + try { + // allow .loki (may only need an agent but not sure + // until we have a .loki to test with) + process.env.NODE_TLS_REJECT_UNAUTHORIZED = serverUrl.match(/\.loki\//) ? 0 : 1; + await nodeFetch(serverUrl); + process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; + // const txt = await res.text(); + } catch(e) { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; + log.warn(`failing to created ${serverUrl}`, e.code, e.message); + // bail out if not valid enough + return null; + } + + // after verification then we can start up all the pollers thisServer = new LokiAppDotNetAPI(this.ourKey, serverUrl); + const gotToken = await thisServer.getOrRefreshServerToken(); if (!gotToken) { log.warn(`Invalid server ${serverUrl}`); From 83e2404d8948e8f5a8d5cf7221aa61f8d34351aa Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 4 Feb 2020 15:24:09 -0800 Subject: [PATCH 04/22] move comments from connecting_to_server_dialog_view --- js/background.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/js/background.js b/js/background.js index 3c6330ebf..508da29d5 100644 --- a/js/background.js +++ b/js/background.js @@ -1056,6 +1056,7 @@ const sslServerURL = `https://${rawserverURL}`; const conversationId = `publicChat:${channelId}@${rawserverURL}`; + // quickly peak to make sure we don't already have it const conversationExists = window.ConversationController.get( conversationId ); @@ -1066,9 +1067,11 @@ }); } + // get server const serverAPI = await window.lokiPublicChatAPI.findOrCreateServer( sslServerURL ); + // SSL certificate failure or offline if (!serverAPI) { // Url incorrect or server not compatible return new Promise((_resolve, reject) => { @@ -1076,16 +1079,20 @@ }); } + // create conversation const conversation = await window.ConversationController.getOrCreateAndWait( conversationId, 'group' ); + // convert conversation to a public one await conversation.setPublicSource(sslServerURL, channelId); + // set friend and appropriate SYNC messages for multidevice await conversation.setFriendRequestStatus( window.friends.friendRequestStatusEnum.friends ); + // and finally activate it conversation.getPublicSendData(); // may want "await" if you want to use the API return conversation; From eecf2252d7993a0c85facbe1d8a87e3008fed489 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 4 Feb 2020 15:24:38 -0800 Subject: [PATCH 05/22] use attempt from window object to reduce code duplication --- js/views/connecting_to_server_dialog_view.js | 31 ++++---------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/js/views/connecting_to_server_dialog_view.js b/js/views/connecting_to_server_dialog_view.js index 2a491ff29..9973ed9a5 100644 --- a/js/views/connecting_to_server_dialog_view.js +++ b/js/views/connecting_to_server_dialog_view.js @@ -1,4 +1,4 @@ -/* global Whisper, i18n, ConversationController, friends */ +/* global Whisper, i18n, ConversationController, friends, lokiPublicChatAPI */ // eslint-disable-next-line func-names (function() { @@ -24,31 +24,12 @@ 'click .cancel': 'close', }, async attemptConnection(serverUrl, channelId) { - const rawServerUrl = serverUrl - .replace(/^https?:\/\//i, '') - .replace(/[/\\]+$/i, ''); - const sslServerUrl = `https://${rawServerUrl}`; - const conversationId = `publicChat:${channelId}@${rawServerUrl}`; - - const conversationExists = ConversationController.get(conversationId); - if (conversationExists) { - // We are already a member of this public chat - return this.resolveWith({ errorCode: i18n('publicChatExists') }); + try { + const conversion = await window.attemptConnection(serverUrl, channelId); + } catch(e) { + log.error('can not connect', e.message, e.code); + return this.resolveWith({ errorCode: e.message }); } - - // create conversation - const conversation = await ConversationController.getOrCreateAndWait( - conversationId, - 'group' - ); - // convert conversation to a public one - await conversation.setPublicSource(sslServerUrl, channelId); - // set friend and appropriate SYNC messages for multidevice - await conversation.setFriendRequestStatus( - friends.friendRequestStatusEnum.friends - ); - // and finally activate it - conversation.getPublicSendData(); // may want "await" if you want to use the API return this.resolveWith({ conversation }); }, resolveWith(result) { From 90de43e6ffa1f78c0e6771aa4d3a65dd6f37c8bf Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 4 Feb 2020 15:25:09 -0800 Subject: [PATCH 06/22] refactor out validServer() --- js/modules/loki_public_chat_api.js | 33 +++++++++++++++++++----------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/js/modules/loki_public_chat_api.js b/js/modules/loki_public_chat_api.js index d0398f6dd..8e4cd0f3d 100644 --- a/js/modules/loki_public_chat_api.js +++ b/js/modules/loki_public_chat_api.js @@ -17,6 +17,26 @@ class LokiPublicChatFactoryAPI extends EventEmitter { await Promise.all(this.servers.map(server => server.close())); } + static async validServer(serverUrl) { + // test to make sure it's online (and maybe has a valid SSL cert) + try { + // allow .loki (may only need an agent but not sure + // until we have a .loki to test with) + process.env.NODE_TLS_REJECT_UNAUTHORIZED = serverUrl.match(/\.loki\//) + ? 0 + : 1; + await nodeFetch(serverUrl); + process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; + // const txt = await res.text(); + } catch (e) { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; + log.warn(`failing to created ${serverUrl}`, e.code, e.message); + // bail out if not valid enough + return false; + } + return true; + } + // server getter/factory async findOrCreateServer(serverUrl) { let thisServer = this.servers.find( @@ -25,18 +45,7 @@ class LokiPublicChatFactoryAPI extends EventEmitter { if (!thisServer) { log.info(`LokiAppDotNetAPI creating ${serverUrl}`); - // test to make sure it's online (and maybe has a valid SSL cert) - try { - // allow .loki (may only need an agent but not sure - // until we have a .loki to test with) - process.env.NODE_TLS_REJECT_UNAUTHORIZED = serverUrl.match(/\.loki\//) ? 0 : 1; - await nodeFetch(serverUrl); - process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; - // const txt = await res.text(); - } catch(e) { - process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; - log.warn(`failing to created ${serverUrl}`, e.code, e.message); - // bail out if not valid enough + if (!await this.constructor.validServer(serverUrl)) { return null; } From 3abb691e2c7b6770be0bce491d48fd6948e4d731 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 4 Feb 2020 15:25:25 -0800 Subject: [PATCH 07/22] lint --- js/modules/loki_app_dot_net_api.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index cf66cd80d..a3d92cfb6 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -496,7 +496,9 @@ class LokiAppDotNetServerAPI { )); } else { // disable check for .loki - process.env.NODE_TLS_REJECT_UNAUTHORIZED = endpoint.match(/\.loki\//) ? 0 : 1; + process.env.NODE_TLS_REJECT_UNAUTHORIZED = endpoint.match(/\.loki\//) + ? 0 + : 1; result = await nodeFetch(url, fetchOptions); // always make sure this check is enabled process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; @@ -505,7 +507,12 @@ class LokiAppDotNetServerAPI { } } catch (e) { if (txtResponse) { - log.info(`serverRequest ${mode} error`, e.code, e.message, `json: ${txtResponse}`); + log.info( + `serverRequest ${mode} error`, + e.code, + e.message, + `json: ${txtResponse}` + ); } else { log.info(`serverRequest ${mode} error`, e.code, e.message); } From 09e133743d4cb96480ce3878e6af03032f91b83a Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 4 Feb 2020 15:27:49 -0800 Subject: [PATCH 08/22] lint caught typo --- js/views/connecting_to_server_dialog_view.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/js/views/connecting_to_server_dialog_view.js b/js/views/connecting_to_server_dialog_view.js index 9973ed9a5..b2d800f1b 100644 --- a/js/views/connecting_to_server_dialog_view.js +++ b/js/views/connecting_to_server_dialog_view.js @@ -1,4 +1,4 @@ -/* global Whisper, i18n, ConversationController, friends, lokiPublicChatAPI */ +/* global Whisper, i18n, log */ // eslint-disable-next-line func-names (function() { @@ -24,9 +24,10 @@ 'click .cancel': 'close', }, async attemptConnection(serverUrl, channelId) { + let conversation = null; try { - const conversion = await window.attemptConnection(serverUrl, channelId); - } catch(e) { + conversation = await window.attemptConnection(serverUrl, channelId); + } catch (e) { log.error('can not connect', e.message, e.code); return this.resolveWith({ errorCode: e.message }); } From aff22eb5f9c0af6f89caa4065a92055d17a04ab5 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 4 Feb 2020 22:29:20 -0800 Subject: [PATCH 09/22] fix grunt error --- ts/components/LeftPane.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/ts/components/LeftPane.tsx b/ts/components/LeftPane.tsx index 93ae06626..babd60a8e 100644 --- a/ts/components/LeftPane.tsx +++ b/ts/components/LeftPane.tsx @@ -177,7 +177,6 @@ export class LeftPane extends React.Component { private renderChannelSection() { const { - friends, openConversationInternal, conversations, searchResults, From 8f77c2e00bce18ea1544912bff2525c051968aec Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 4 Feb 2020 23:09:03 -0800 Subject: [PATCH 10/22] expose isRss, don't close uncloseable Rss conversation on deleteMessages --- js/models/conversations.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/js/models/conversations.js b/js/models/conversations.js index 52290e7a9..29c9ab317 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -561,6 +561,7 @@ type: this.isPrivate() ? 'direct' : 'group', isMe: this.isMe(), isPublic: this.isPublic(), + isRss: this.isRss(), isClosable: this.isClosable(), isTyping: typingKeys.length > 0, lastUpdated: this.get('timestamp'), @@ -2798,11 +2799,19 @@ this.messageCollection.reset([]); - this.set({ - lastMessage: null, - timestamp: null, - active_at: null, - }); + // let's try to keep the RSS conversation open just empty... + if (this.isRss()) { + this.set({ + lastMessage: null, + }); + } else { + // this will remove the conversation from conversation lists... + this.set({ + lastMessage: null, + timestamp: null, + active_at: null, + }); + } // Reset our friend status if we're not friends if (!this.isFriend()) { From 6ff5175a743eb48400f8f076b17eac03d3b573cb Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 4 Feb 2020 23:09:30 -0800 Subject: [PATCH 11/22] remove copyId and block user on RSS feeds --- ts/components/conversation/ConversationHeader.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index a2dc56545..a25367cc5 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -319,7 +319,9 @@ export class ConversationHeader extends React.Component { return ( {this.renderPublicMenuItems()} - {copyIdLabel} + {!isRss ? ( + {copyIdLabel} + ) : null} {i18n('deleteMessages')} {isPrivateGroup || amMod ? ( {i18n('updateGroup')} @@ -477,7 +479,8 @@ export class ConversationHeader extends React.Component { {i18n('resetSession')} ); const blockHandlerMenuItem = !isMe && - !isGroup && {blockTitle}; + !isGroup && + !isRss && {blockTitle}; const changeNicknameMenuItem = !isMe && !isGroup && ( {i18n('changeNickname')} From 79c4259a914ca1e2e0d700ebc672869948567360 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 4 Feb 2020 23:09:53 -0800 Subject: [PATCH 12/22] remove options from RSS feed that don't make any sense and don't work --- ts/components/ConversationListItem.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx index 8827853a4..0a3868527 100644 --- a/ts/components/ConversationListItem.tsx +++ b/ts/components/ConversationListItem.tsx @@ -23,6 +23,7 @@ export type PropsData = { avatarPath?: string; isMe: boolean; isPublic?: boolean; + isRss?: boolean; isClosable?: boolean; lastUpdated: number; @@ -176,6 +177,7 @@ export class ConversationListItem extends React.PureComponent { isBlocked, isMe, isClosable, + isRss, isPublic, hasNickname, onDeleteContact, @@ -192,18 +194,18 @@ export class ConversationListItem extends React.PureComponent { return ( - {!isPublic && !isMe ? ( + {!isPublic && !isRss && !isMe ? ( {blockTitle} ) : null} - {!isPublic && !isMe ? ( + {!isPublic && !isRss && !isMe ? ( {i18n('changeNickname')} ) : null} - {!isPublic && !isMe && hasNickname ? ( + {!isPublic && !isRss && !isMe && hasNickname ? ( {i18n('clearNickname')} ) : null} - {!isPublic ? ( + {!isPublic && !isRss ? ( {i18n('copyPublicKey')} ) : null} {i18n('deleteMessages')} From 5a440941eac63fcc4f930b99fe5434daf436050d Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 5 Feb 2020 01:03:53 -0800 Subject: [PATCH 13/22] squelch RSS duplicate messages --- js/background.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/js/background.js b/js/background.js index 05243b9c1..9f6b99e35 100644 --- a/js/background.js +++ b/js/background.js @@ -1973,7 +1973,10 @@ } const isDuplicate = await isMessageDuplicate(message); if (isDuplicate) { - window.log.warn('Received duplicate message', message.idForLogging()); + // RSS expects duplciates, so squelch log + if (!descriptorId.match(/^rss:/)) { + window.log.warn('Received duplicate message', message.idForLogging()); + } return event.confirm(); } From 6c73fa99e25b91e16dc41aeabdeef88d7df84fb5 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 5 Feb 2020 01:06:23 -0800 Subject: [PATCH 14/22] extension.expiredStatus(), adjustable timers, improve guards --- js/expire.js | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/js/expire.js b/js/expire.js index ece6938b4..32f9555fc 100644 --- a/js/expire.js +++ b/js/expire.js @@ -14,12 +14,19 @@ LokiFileServerAPI.secureRpcPubKey ); + let nextWaitSeconds = 1; const checkForUpgrades = async () => { - const response = await window.tokenlessFileServerAdnAPI.serverRequest( + const result = await window.tokenlessFileServerAdnAPI.serverRequest( 'loki/v1/version/client/desktop' ); - if (response && response.response) { - const latestVer = semver.clean(response.response.data[0][0]); + if ( + result && + result.response && + result.response.data && + result.response.data.length && + result.response.data[0].length + ) { + const latestVer = semver.clean(result.response.data[0][0]); if (semver.valid(latestVer)) { const ourVersion = window.getVersion(); if (latestVer === ourVersion) { @@ -36,10 +43,11 @@ } } else { // give it a minute - log.warn('Could not check to see if newer version is available'); + log.warn('Could not check to see if newer version is available', result); + nextWaitSeconds = 60; setTimeout(async () => { await checkForUpgrades(); - }, 60 * 1000); // wait a minute + }, nextWaitSeconds * 1000); // wait a minute } // no message logged means serverRequest never returned... }; @@ -56,22 +64,33 @@ if (expiredVersion !== null) { return res(expiredVersion); } - log.info('Delaying sending checks for 1s, no version yet'); - setTimeout(waitForVersion, 1000); + log.info( + 'Delaying sending checks for', + nextWaitSeconds, + 's, no version yet' + ); + setTimeout(waitForVersion, nextWaitSeconds * 1000); return true; } waitForVersion(); return true; }; + // just get current status + window.extension.expiredStatus = () => expiredVersion; + // actually wait until we know for sure window.extension.expiredPromise = () => new Promise(resolveWhenReady); window.extension.expired = cb => { if (expiredVersion === null) { // just give it another second - log.info('Delaying expire banner determination for 1s'); + log.info( + 'Delaying expire banner determination for', + nextWaitSeconds, + 's' + ); setTimeout(() => { window.extension.expired(cb); - }, 1000); + }, nextWaitSeconds * 1000); return; } // yes we know From 156c11d30fc9e0d251da2d74fb19338b157dd19c Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 5 Feb 2020 01:06:56 -0800 Subject: [PATCH 15/22] allowing sending of messages if we're still waiting to hear back --- js/views/conversation_view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index ab9e5a547..2e1fa720e 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -1860,8 +1860,8 @@ message = window.Signal.Emoji.replaceColons(message).trim(); const toastOptions = { type: 'info' }; - const expiredVersion = await extension.expiredPromise(); - if (expiredVersion) { + // let it pass if we're still trying to read it or it's false... + if (extension.expiredStatus() === true) { toastOptions.title = i18n('expiredWarning'); toastOptions.id = 'expiredWarning'; } From 3bba0571a00f96f9221b63f11bd26974554d8a21 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 5 Feb 2020 01:07:57 -0800 Subject: [PATCH 16/22] markRandomNodeUnreachable() refactor, notes/logging --- js/modules/loki_snode_api.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/js/modules/loki_snode_api.js b/js/modules/loki_snode_api.js index edb19873b..eb6bdc2f5 100644 --- a/js/modules/loki_snode_api.js +++ b/js/modules/loki_snode_api.js @@ -9,8 +9,8 @@ class LokiSnodeAPI { if (!is.string(serverUrl)) { throw new Error('WebAPI.initialize: Invalid server url'); } - this.serverUrl = serverUrl; - this.localUrl = localUrl; + this.serverUrl = serverUrl; // random.snode + this.localUrl = localUrl; // localhost.loki this.randomSnodePool = []; this.swarmsPendingReplenish = {}; } @@ -63,7 +63,7 @@ class LokiSnodeAPI { pubkey_ed25519: snode.pubkey_ed25519, })); } catch (e) { - log.warn('initialiseRandomPool error', JSON.stringify(e)); + log.warn('initialiseRandomPool error', e.code, e.message); if (seedNodes.length === 0) { throw new window.textsecure.SeedNodeError( 'Failed to contact seed node' @@ -73,6 +73,7 @@ class LokiSnodeAPI { } } + // nodeUrl is like 9hrje1bymy7hu6nmtjme9idyu3rm8gr3mkstakjyuw1997t7w4ny.snode async unreachableNode(pubKey, nodeUrl) { const conversation = ConversationController.get(pubKey); const swarmNodes = [...conversation.get('swarmNodes')]; @@ -82,6 +83,13 @@ class LokiSnodeAPI { await conversation.updateSwarmNodes(filteredNodes); } + markRandomNodeUnreachable(snode) { + this.randomSnodePool = _.without( + this.randomSnodePool, + _.find(this.randomSnodePool, { ip: snode.ip, port: snode.port }) + ); + } + async updateLastHash(snode, hash, expiresAt) { await window.Signal.Data.updateLastHash({ snode, hash, expiresAt }); } @@ -152,11 +160,7 @@ class LokiSnodeAPI { return snodes; } catch (e) { log.error('getSwarmNodes error', e.code, e.message); - // - this.randomSnodePool = _.without( - this.randomSnodePool, - _.find(this.randomSnodePool, { ip: snode.ip }) - ); + this.markRandomNodeUnreachable(snode); return this.getSwarmNodes(pubKey); } } From 99afd33fb44ad186fb7a78c8bbeb9aad1aea37a0 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 5 Feb 2020 01:08:24 -0800 Subject: [PATCH 17/22] improve logging --- js/modules/loki_rpc.js | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index 9bd613a4e..1cd99b9c7 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -13,16 +13,18 @@ const LOKI_EPHEMKEY_HEADER = 'X-Loki-EphemKey'; const endpointBase = '/storage_rpc/v1'; const decryptResponse = async (response, address) => { + let plaintext = false; try { const ciphertext = await response.text(); - const plaintext = await libloki.crypto.snodeCipher.decrypt( - address, - ciphertext - ); + plaintext = await libloki.crypto.snodeCipher.decrypt(address, ciphertext); const result = plaintext === '' ? {} : JSON.parse(plaintext); return result; } catch (e) { - log.warn(`Could not decrypt response from ${address}`, e); + log.warn( + `Could not decrypt response [${plaintext}] from [${address}],`, + e.code, + e.message + ); } return {}; }; @@ -80,11 +82,21 @@ const sendToProxy = async (options = {}, targetNode) => { const textDecoder = new TextDecoder(); const plaintext = textDecoder.decode(plaintextBuffer); - const jsonRes = JSON.parse(plaintext); - - jsonRes.json = () => JSON.parse(jsonRes.body); - - return jsonRes; + try { + const jsonRes = JSON.parse(plaintext); + // emulate nodeFetch response... + jsonRes.json = () => JSON.parse(jsonRes.body); + return jsonRes; + } catch (e) { + log.error( + 'lokiRpc sendToProxy error', + e.code, + e.message, + 'json', + plaintext + ); + } + return false; }; // A small wrapper around node-fetch which deserializes response From ede5258dd398b92cd631d4a34ac325a09627845a Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 5 Feb 2020 01:08:44 -0800 Subject: [PATCH 18/22] improve logging --- js/modules/loki_message_api.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index b581a16ff..9ff394f41 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -288,7 +288,7 @@ class LokiMessageAPI { // Execute callback even with empty array to signal online status callback(messages); } catch (e) { - log.warn('Loki retrieve messages:', e); + log.warn('Loki retrieve messages:', e.code, e.message); if (e instanceof textsecure.WrongSwarmError) { const { newSwarm } = e; await lokiSnodeAPI.updateSwarmNodes(this.ourKey, newSwarm); @@ -352,7 +352,6 @@ class LokiMessageAPI { const lastHash = await window.Signal.Data.getLastHashBySnode( nodes[i].address ); - this.ourSwarmNodes[nodes[i].address] = { ...nodes[i], lastHash, From 4c44265909d8d0602c928220a3ee87a5c4a42dbb Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 5 Feb 2020 01:10:18 -0800 Subject: [PATCH 19/22] no need to validate empty token, support lokinet/getession file domains, mark broken snodes as bad, improve logging --- js/modules/loki_app_dot_net_api.js | 96 ++++++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 10 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 752d37d93..7f2a5dc56 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -185,6 +185,15 @@ class LokiAppDotNetServerAPI { } this.token = token; + // if no token to verify, just bail now + if (!token) { + // + if (!forceRefresh) { + token = await this.getOrRefreshServerToken(true); + } + return token; + } + // verify token info const tokenRes = await this.serverRequest('token'); // if no problems and we have data @@ -270,7 +279,31 @@ class LokiAppDotNetServerAPI { res = await this.proxyFetch(url); } catch (e) { - log.error('requestToken request failed', e); + // should we retry here? + // no, this is the low level function + // not really an error, from a client's pov, network servers can fail... + if (e.code === 'ECONNREFUSED') { + // down + log.warn( + 'requestToken request can not connect', + this.baseServerUrl, + e.message + ); + } else if (e.code === 'ECONNRESET') { + // got disconnected + log.warn( + 'requestToken request lost connection', + this.baseServerUrl, + e.message + ); + } else { + log.error( + 'requestToken request failed', + this.baseServerUrl, + e.code, + e.message + ); + } return null; } if (!res.ok) { @@ -302,14 +335,17 @@ class LokiAppDotNetServerAPI { ); return res.ok; } catch (e) { + log.error('submitToken proxyFetch failure', e.code, e.message); return false; } } - async proxyFetch(urlObj, fetchOptions) { + async proxyFetch(urlObj, fetchOptions = { method: 'GET' }) { if ( window.lokiFeatureFlags.useSnodeProxy && (this.baseServerUrl === 'https://file-dev.lokinet.org' || + this.baseServerUrl === 'https://file.lokinet.org' || + this.baseServerUrl === 'https://file-dev.getsession.org' || this.baseServerUrl === 'https://file.getsession.org') ) { const finalOptions = { ...fetchOptions }; @@ -408,7 +444,21 @@ class LokiAppDotNetServerAPI { const result = await nodeFetch(url, firstHopOptions); const txtResponse = await result.text(); - let response = JSON.parse(txtResponse); + if (txtResponse === 'Service node is not ready: not in any swarm; \n') { + // mark snode bad + log.warn('Marking random snode bad', randSnode); + lokiSnodeAPI.markRandomNodeUnreachable(randSnode); + // retry (hopefully with new snode) + // FIXME: max number of retries... + return this._sendToProxy(endpoint, fetchOptions); + } + + let response = {}; + try { + response = JSON.parse(txtResponse); + } catch (e) { + log.warn(`_sendToProxy Could not parse outer JSON [${txtResponse}]`); + } if (response.meta && response.meta.code === 200) { // convert base64 in response to binary @@ -423,9 +473,17 @@ class LokiAppDotNetServerAPI { const textDecoder = new TextDecoder(); const json = textDecoder.decode(decrypted); // replace response - response = JSON.parse(json); + try { + response = JSON.parse(json); + } catch (e) { + log.warn(`_sendToProxy Could not parse inner JSON [${json}]`); + } } else { - log.warn('file server secure_rpc gave an non-200 response'); + log.warn( + 'file server secure_rpc gave an non-200 response: ', + response, + ` txtResponse[${txtResponse}]` + ); } return { result, txtResponse, response }; } @@ -469,7 +527,7 @@ class LokiAppDotNetServerAPI { fetchOptions.agent = snodeHttpsAgent; } } catch (e) { - log.info('serverRequest set up error:', JSON.stringify(e)); + log.info('serverRequest set up error:', e.code, e.message); return { err: e, }; @@ -483,6 +541,8 @@ class LokiAppDotNetServerAPI { if ( window.lokiFeatureFlags.useSnodeProxy && (this.baseServerUrl === 'https://file-dev.lokinet.org' || + this.baseServerUrl === 'https://file.lokinet.org' || + this.baseServerUrl === 'https://file-dev.getsession.org' || this.baseServerUrl === 'https://file.getsession.org') ) { mode = '_sendToProxy'; @@ -902,7 +962,11 @@ class LokiPublicChannelAPI { try { await this.pollOnceForModerators(); } catch (e) { - log.warn(`Error while polling for public chat moderators: ${e}`); + log.warn( + 'Error while polling for public chat moderators:', + e.code, + e.message + ); } if (this.running) { this.timers.moderator = setTimeout(() => { @@ -1052,7 +1116,11 @@ class LokiPublicChannelAPI { try { await this.pollForChannelOnce(); } catch (e) { - log.warn(`Error while polling for public chat room details: ${e}`); + log.warn( + 'Error while polling for public chat room details', + e.code, + e.message + ); } if (this.running) { this.timers.channel = setTimeout(() => { @@ -1103,7 +1171,11 @@ class LokiPublicChannelAPI { try { await this.pollOnceForDeletions(); } catch (e) { - log.warn(`Error while polling for public chat deletions: ${e}`); + log.warn( + 'Error while polling for public chat deletions:', + e.code, + e.message + ); } if (this.running) { this.timers.delete = setTimeout(() => { @@ -1278,7 +1350,11 @@ class LokiPublicChannelAPI { try { await this.pollOnceForMessages(); } catch (e) { - log.warn(`Error while polling for public chat messages: ${e}`); + log.warn( + 'Error while polling for public chat messages:', + e.code, + e.message + ); } if (this.running) { this.timers.message = setTimeout(() => { From 2486cc7eb29a0f5b98d9462741e87b263bbad2c9 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 5 Feb 2020 02:51:58 -0800 Subject: [PATCH 20/22] Catch a stray loki messenger --- _locales/en/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 0b9c9db1a..06e134777 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2225,7 +2225,7 @@ }, "setAccountPasswordDescription": { "message": - "Require password to unlock Session’s screen. You can still receive message notifications while Screen Lock is enabled. Loki Messenger’s notification settings allow you to customize information that is displayed", + "Require password to unlock Session’s screen. You can still receive message notifications while Screen Lock is enabled. Session’s notification settings allow you to customize information that is displayed", "description": "Description for set account password setting view" }, "changeAccountPasswordTitle": { From bd9f4cfa405d6655cc284d9cbc1df6a7531178c4 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 5 Feb 2020 02:52:50 -0800 Subject: [PATCH 21/22] fix stray loki messenger --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d852ffd13..216832649 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -19,4 +19,4 @@ If applicable, add screenshots or log files to help explain your problem. * Device: [e.g. PC, Mac] * OS: [e.g. Ubuntu 16.04, Windows 10] -* Loki messenger Version or Git commit hash: +* Session Version or Git commit hash: From 2888523c6d9b70b56fb9adbd3f6ebb4449c277ed Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 5 Feb 2020 02:54:28 -0800 Subject: [PATCH 22/22] loki messenger isnt a thing --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6175fa925..766d7db52 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ [![Build Status](https://travis-ci.org/loki-project/loki-messenger.svg?branch=development)](https://travis-ci.org/loki-project/loki-messenger) -Session allows for truly decentralized, end to end, and private encrypted chats. Session is built to handle both online and fully Asynchronous offline messages. Loki messenger implements the Signal protocol for message encryption. Our Client interface is a fork of [Signal Messenger](https://signal.org/). All communication that passes through Loki messenger is routed through [Lokinet](https://github.com/loki-project/loki-network). +Session allows for truly decentralized, end to end, and private encrypted chats. Session is built to handle both online and fully Asynchronous offline messages. Session implements the Signal protocol for message encryption. Our Client interface is a fork of [Signal Messenger](https://signal.org/). All communication that passes through Session is routed through [Lokinet](https://github.com/loki-project/loki-network). ## Summary -Loki messenger integrates directly with Loki [Service Nodes](https://lokidocs.com/ServiceNodes/SNOverview/), which are a set of distributed, decentralized and Sybil resistant nodes. Service Nodes act as both federated servers which store messages offline, and a set of nodes which allow for onion routing functionality obfuscating users IP Addresses. For a full understanding of how Loki messenger works, read the [Loki whitepaper](https://loki.network/whitepaper). +Session integrates directly with Loki [Service Nodes](https://lokidocs.com/ServiceNodes/SNOverview/), which are a set of distributed, decentralized and Sybil resistant nodes. Service Nodes act as both federated servers which store messages offline, and a set of nodes which allow for onion routing functionality obfuscating users IP Addresses. For a full understanding of how Session works, read the [Loki whitepaper](https://loki.network/whitepaper). **Online Messages**