From 84d5cf4541919ad4e73752b355f7ddf3914ad7b2 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 27 Feb 2020 15:04:50 +1100 Subject: [PATCH 01/59] fix contextmenu caching on Message right click --- ts/components/conversation/Message.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index e841079c5..c06ad4161 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -1064,9 +1064,13 @@ export class Message extends React.PureComponent { // This id is what connects our triple-dot click with our associated pop-up menu. // It needs to be unique. - const triggerId = String(id || `${authorPhoneNumber}-${timestamp}`); - const rightClickTriggerId = `${authorPhoneNumber}-ctx-${timestamp}`; - + // The Date.now() is a workaround to be sure a single triggerID with this id exists + const triggerId = id + ? String(`${id}-${Date.now()}`) + : String(`${authorPhoneNumber}-${timestamp}`); + const rightClickTriggerId = id + ? String(`${id}-ctx-${Date.now()}`) + : String(`${authorPhoneNumber}-ctx-${timestamp}`); if (expired) { return null; } From 2d1739a1e74671e868e490916b5bc33ed75c0c31 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 27 Feb 2020 15:41:39 +1100 Subject: [PATCH 02/59] add key to a list of badges (cause error on console) --- ts/components/conversation/Message.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index c06ad4161..c8dd2a98d 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -213,7 +213,7 @@ export class Message extends React.PureComponent { } public renderMetadataBadges() { - const { direction, isPublic, senderIsModerator } = this.props; + const { direction, isPublic, senderIsModerator, id } = this.props; const badges = [isPublic && 'Public', senderIsModerator && 'Mod']; @@ -224,7 +224,7 @@ export class Message extends React.PureComponent { } return ( - <> +
 •  @@ -239,7 +239,7 @@ export class Message extends React.PureComponent { > {badgeText} - +
); }) .filter(i => !!i); From a52127de5d3c5aabb4dc36017c0daeb98f45bcce Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 27 Feb 2020 15:41:59 +1100 Subject: [PATCH 03/59] do not trigger change of unchanged messages on conversation reset --- js/models/conversations.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/js/models/conversations.js b/js/models/conversations.js index 1889996a7..fac026b1a 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -330,9 +330,13 @@ resetMessageSelection() { this.selectedMessages.clear(); this.messageCollection.forEach(m => { - // eslint-disable-next-line no-param-reassign - m.selected = false; - m.trigger('change'); + // on change for ALL messages without real changes is a really costly operation + // -> cause refresh of the whole conversation view even if not a single message was selected + if (m.selected) { + // eslint-disable-next-line no-param-reassign + m.selected = false; + m.trigger('change'); + } }); this.trigger('message-selection-changed'); From 738057587d932af59df99b4a6a5392182d43dc13 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Thu, 5 Mar 2020 21:53:12 -0800 Subject: [PATCH 04/59] open group avatar redo conversion properly --- js/background.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/js/background.js b/js/background.js index eef39319d..1ca333868 100644 --- a/js/background.js +++ b/js/background.js @@ -754,12 +754,10 @@ } // lets not allow ANY URLs, lets force it to be local to public chat server - const relativeFileUrl = fileObj.url.replace( - API.serverAPI.baseServerUrl, - '' - ); + const url = new URL(fileObj.url); + // write it to the channel - await API.setChannelAvatar(relativeFileUrl); + await API.setChannelAvatar(url.pathname); } if (await API.setChannelName(groupName)) { @@ -2029,7 +2027,7 @@ } const isDuplicate = await isMessageDuplicate(message); if (isDuplicate) { - // RSS expects duplciates, so squelch log + // RSS expects duplicates, so squelch log if (!descriptorId.match(/^rss:/)) { window.log.warn('Received duplicate message', message.idForLogging()); } From 837dfe1af59bc0ae0f6cd91d1fdf8e4c11207d85 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Thu, 5 Mar 2020 22:27:09 -0800 Subject: [PATCH 05/59] remove org.whispersystems from notifications to match package.json build appId, fixes #952 --- main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.js b/main.js index 48ca42d37..5678d71a5 100644 --- a/main.js +++ b/main.js @@ -27,7 +27,7 @@ const { shell, } = electron; -const appUserModelId = `org.whispersystems.${packageJson.name}`; +const appUserModelId = packageJson.build.appId; console.log('Set Windows Application User Model ID (AUMID)', { appUserModelId, }); From 8c32bb93f755bf1edf24b025244e1354a5c8f7a5 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Fri, 6 Mar 2020 17:36:55 +0100 Subject: [PATCH 06/59] Implement Advanced Search (from, before and after) --- ts/state/ducks/search.ts | 87 ++++++++++++++++++++++++++++++++++++++-- ts/types/Search.ts | 7 ++++ 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index 194823a79..d44fc4503 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -1,7 +1,7 @@ import { omit, reject } from 'lodash'; import { normalize } from '../../types/PhoneNumber'; -import { SearchOptions } from '../../types/Search'; +import { SearchOptions, AdvancedSearchOptions } from '../../types/Search'; import { trigger } from '../../shims/events'; import { getMessageModel } from '../../shims/Whisper'; import { cleanSearchTerm } from '../../util/cleanSearchTerm'; @@ -98,12 +98,28 @@ async function doSearch( ): Promise { const { regionCode } = options; + const advancedSearchOptions = getAdvancedSearchOptionsFromQuery(query); + const processedQuery = advancedSearchOptions['query']; + const isAdvancedQuery = query !== processedQuery; + const [discussions, messages] = await Promise.all([ - queryConversationsAndContacts(query, options), - queryMessages(query), + queryConversationsAndContacts(processedQuery, options), + queryMessages(processedQuery) ]); const { conversations, contacts } = discussions; - const filteredMessages = messages.filter(message => message !== undefined); + let filteredMessages = messages.filter(message => message !== undefined); + + if (isAdvancedQuery) { + let senderFilter: string[] = []; + if (advancedSearchOptions['from'] !== null && advancedSearchOptions['from'].length > 0) { + const senderFilterQuery = await queryConversationsAndContacts(advancedSearchOptions['from'], options); + senderFilter = senderFilterQuery.contacts; + console.log(senderFilter); + } + filteredMessages = filterMessages(filteredMessages, advancedSearchOptions, senderFilter); + console.log(filteredMessages); + console.log(advancedSearchOptions); + } return { query, @@ -146,6 +162,69 @@ function startNewConversation( // Helper functions for search +function filterMessages(messages: any[], filters: AdvancedSearchOptions, contacts: string[]) { + let filteredMessages = messages; + if (filters['from'] !== null && filters['from'].length > 0) { + if (filters['from'] === '@me') { + filteredMessages = filteredMessages.filter(message => message.sent); + } else { + filteredMessages = []; + for(let contact of contacts) { + for (const message of messages) { + if (message.source === contact) { + filteredMessages.push(message); + } + } + } + } + } + if (filters['before'] > 0) { + filteredMessages = filteredMessages.filter(message => message.received_at < filters['before']); + } + if (filters['after'] > 0) { + filteredMessages = filteredMessages.filter(message => message.received_at > filters['after']); + } + + return filteredMessages; +}; + +function getUnixTimestampParameter(timestamp: string): number { + if (!isNaN(parseInt(timestamp))) { + return parseInt(timestamp); + } else { + // ToDo: (konstantinullrich) Add Support for dateformats + return 0; + } +} + +function getAdvancedSearchOptionsFromQuery(query: string): AdvancedSearchOptions { + const filterSeperator = ':'; + const filters: any = { + query: null, + from: null, + before: null, + after: null + }; + + let newQuery = query; + const splitQuery = query.toLowerCase().split(' '); + const filtersList = Object.keys(filters); + for(let queryPart of splitQuery) { + for(let filter of filtersList) { + const filterMatcher = filter + filterSeperator; + if(queryPart.startsWith(filterMatcher)) { + filters[filter] = queryPart.replace(filterMatcher, ''); + newQuery = newQuery.replace(queryPart, '').trim(); + } + } + } + + filters['before'] = getUnixTimestampParameter(filters['before']); + filters['after'] = getUnixTimestampParameter(filters['after']); + filters['query'] = newQuery; + return filters; +}; + const getMessageProps = (messages: Array) => { if (!messages || !messages.length) { return []; diff --git a/ts/types/Search.ts b/ts/types/Search.ts index debd559a0..22cd091cb 100644 --- a/ts/types/Search.ts +++ b/ts/types/Search.ts @@ -4,3 +4,10 @@ export type SearchOptions = { noteToSelf: string; isSecondaryDevice: boolean; }; + +export type AdvancedSearchOptions = { + query: string; + from: string; + before: number; + after: number; +}; \ No newline at end of file From c11a96ad01672f0358733e8d49575662833db6f1 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Fri, 6 Mar 2020 17:43:00 +0100 Subject: [PATCH 07/59] lint --- ts/state/ducks/search.ts | 50 ++++++++++++++++++++++++++++------------ ts/types/Search.ts | 8 +++---- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index d44fc4503..b965c0dc3 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -104,19 +104,29 @@ async function doSearch( const [discussions, messages] = await Promise.all([ queryConversationsAndContacts(processedQuery, options), - queryMessages(processedQuery) + queryMessages(processedQuery), ]); const { conversations, contacts } = discussions; let filteredMessages = messages.filter(message => message !== undefined); if (isAdvancedQuery) { let senderFilter: string[] = []; - if (advancedSearchOptions['from'] !== null && advancedSearchOptions['from'].length > 0) { - const senderFilterQuery = await queryConversationsAndContacts(advancedSearchOptions['from'], options); + if ( + advancedSearchOptions['from'] !== null && + advancedSearchOptions['from'].length > 0 + ) { + const senderFilterQuery = await queryConversationsAndContacts( + advancedSearchOptions['from'], + options + ); senderFilter = senderFilterQuery.contacts; console.log(senderFilter); } - filteredMessages = filterMessages(filteredMessages, advancedSearchOptions, senderFilter); + filteredMessages = filterMessages( + filteredMessages, + advancedSearchOptions, + senderFilter + ); console.log(filteredMessages); console.log(advancedSearchOptions); } @@ -162,14 +172,18 @@ function startNewConversation( // Helper functions for search -function filterMessages(messages: any[], filters: AdvancedSearchOptions, contacts: string[]) { +function filterMessages( + messages: any[], + filters: AdvancedSearchOptions, + contacts: string[] +) { let filteredMessages = messages; if (filters['from'] !== null && filters['from'].length > 0) { if (filters['from'] === '@me') { filteredMessages = filteredMessages.filter(message => message.sent); } else { filteredMessages = []; - for(let contact of contacts) { + for (let contact of contacts) { for (const message of messages) { if (message.source === contact) { filteredMessages.push(message); @@ -179,14 +193,18 @@ function filterMessages(messages: any[], filters: AdvancedSearchOptions, contact } } if (filters['before'] > 0) { - filteredMessages = filteredMessages.filter(message => message.received_at < filters['before']); + filteredMessages = filteredMessages.filter( + message => message.received_at < filters['before'] + ); } if (filters['after'] > 0) { - filteredMessages = filteredMessages.filter(message => message.received_at > filters['after']); + filteredMessages = filteredMessages.filter( + message => message.received_at > filters['after'] + ); } return filteredMessages; -}; +} function getUnixTimestampParameter(timestamp: string): number { if (!isNaN(parseInt(timestamp))) { @@ -197,22 +215,24 @@ function getUnixTimestampParameter(timestamp: string): number { } } -function getAdvancedSearchOptionsFromQuery(query: string): AdvancedSearchOptions { +function getAdvancedSearchOptionsFromQuery( + query: string +): AdvancedSearchOptions { const filterSeperator = ':'; const filters: any = { query: null, from: null, before: null, - after: null + after: null, }; let newQuery = query; const splitQuery = query.toLowerCase().split(' '); const filtersList = Object.keys(filters); - for(let queryPart of splitQuery) { - for(let filter of filtersList) { + for (let queryPart of splitQuery) { + for (let filter of filtersList) { const filterMatcher = filter + filterSeperator; - if(queryPart.startsWith(filterMatcher)) { + if (queryPart.startsWith(filterMatcher)) { filters[filter] = queryPart.replace(filterMatcher, ''); newQuery = newQuery.replace(queryPart, '').trim(); } @@ -223,7 +243,7 @@ function getAdvancedSearchOptionsFromQuery(query: string): AdvancedSearchOptions filters['after'] = getUnixTimestampParameter(filters['after']); filters['query'] = newQuery; return filters; -}; +} const getMessageProps = (messages: Array) => { if (!messages || !messages.length) { diff --git a/ts/types/Search.ts b/ts/types/Search.ts index 22cd091cb..990372434 100644 --- a/ts/types/Search.ts +++ b/ts/types/Search.ts @@ -6,8 +6,8 @@ export type SearchOptions = { }; export type AdvancedSearchOptions = { - query: string; - from: string; - before: number; + query: string; + from: string; + before: number; after: number; -}; \ No newline at end of file +}; From 8f7083f5a2fc1003cb6313a553e6ac571910de1b Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Sat, 7 Mar 2020 19:29:46 +0100 Subject: [PATCH 08/59] Update Advanced Search by easier date formats --- ts/state/ducks/search.ts | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index b965c0dc3..0e8449965 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -120,15 +120,12 @@ async function doSearch( options ); senderFilter = senderFilterQuery.contacts; - console.log(senderFilter); } filteredMessages = filterMessages( filteredMessages, advancedSearchOptions, senderFilter ); - console.log(filteredMessages); - console.log(advancedSearchOptions); } return { @@ -206,13 +203,20 @@ function filterMessages( return filteredMessages; } -function getUnixTimestampParameter(timestamp: string): number { - if (!isNaN(parseInt(timestamp))) { - return parseInt(timestamp); - } else { - // ToDo: (konstantinullrich) Add Support for dateformats - return 0; +function getUnixMillisecondsTimestamp(timestamp: string): number { + if(!isNaN(parseInt(timestamp))) { + const timestampInt = parseInt(timestamp); + try { + if (timestampInt > 10000) { + return new Date(timestampInt).getTime(); + } + return new Date(timestamp).getTime(); + } catch (error) { + console.warn('Advanced Search: ' + error); + return 0; + } } + return 0; } function getAdvancedSearchOptionsFromQuery( @@ -239,8 +243,8 @@ function getAdvancedSearchOptionsFromQuery( } } - filters['before'] = getUnixTimestampParameter(filters['before']); - filters['after'] = getUnixTimestampParameter(filters['after']); + filters['before'] = getUnixMillisecondsTimestamp(filters['before']); + filters['after'] = getUnixMillisecondsTimestamp(filters['after']); filters['query'] = newQuery; return filters; } From f770d4ed448ddb63ea963d94ee7714793e4f1e36 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Sat, 7 Mar 2020 19:34:05 +0100 Subject: [PATCH 09/59] lint --- ts/state/ducks/search.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index 0e8449965..a85e2ba04 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -204,7 +204,7 @@ function filterMessages( } function getUnixMillisecondsTimestamp(timestamp: string): number { - if(!isNaN(parseInt(timestamp))) { + if (!isNaN(parseInt(timestamp))) { const timestampInt = parseInt(timestamp); try { if (timestampInt > 10000) { From 3eca4025e7edccabbcfd84e1047c323a438e1699 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Sat, 7 Mar 2020 20:30:43 +0100 Subject: [PATCH 10/59] Fix lint in search.ts --- ts/state/ducks/search.ts | 50 ++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index a85e2ba04..37a7b00ea 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -1,7 +1,7 @@ import { omit, reject } from 'lodash'; import { normalize } from '../../types/PhoneNumber'; -import { SearchOptions, AdvancedSearchOptions } from '../../types/Search'; +import { AdvancedSearchOptions, SearchOptions } from '../../types/Search'; import { trigger } from '../../shims/events'; import { getMessageModel } from '../../shims/Whisper'; import { cleanSearchTerm } from '../../util/cleanSearchTerm'; @@ -99,7 +99,7 @@ async function doSearch( const { regionCode } = options; const advancedSearchOptions = getAdvancedSearchOptionsFromQuery(query); - const processedQuery = advancedSearchOptions['query']; + const processedQuery = advancedSearchOptions.query; const isAdvancedQuery = query !== processedQuery; const [discussions, messages] = await Promise.all([ @@ -110,13 +110,13 @@ async function doSearch( let filteredMessages = messages.filter(message => message !== undefined); if (isAdvancedQuery) { - let senderFilter: string[] = []; + let senderFilter: Array = []; if ( - advancedSearchOptions['from'] !== null && - advancedSearchOptions['from'].length > 0 + advancedSearchOptions.from !== null && + advancedSearchOptions.from.length > 0 ) { const senderFilterQuery = await queryConversationsAndContacts( - advancedSearchOptions['from'], + advancedSearchOptions.from, options ); senderFilter = senderFilterQuery.contacts; @@ -170,17 +170,17 @@ function startNewConversation( // Helper functions for search function filterMessages( - messages: any[], + messages: Array, filters: AdvancedSearchOptions, - contacts: string[] + contacts: Array ) { let filteredMessages = messages; - if (filters['from'] !== null && filters['from'].length > 0) { - if (filters['from'] === '@me') { + if (filters.from !== null && filters.from.length > 0) { + if (filters.from=== '@me') { filteredMessages = filteredMessages.filter(message => message.sent); } else { filteredMessages = []; - for (let contact of contacts) { + for (const contact of contacts) { for (const message of messages) { if (message.source === contact) { filteredMessages.push(message); @@ -189,14 +189,14 @@ function filterMessages( } } } - if (filters['before'] > 0) { + if (filters.before > 0) { filteredMessages = filteredMessages.filter( - message => message.received_at < filters['before'] + message => message.received_at < filters.before ); } - if (filters['after'] > 0) { + if (filters.after > 0) { filteredMessages = filteredMessages.filter( - message => message.received_at > filters['after'] + message => message.received_at > filters.after ); } @@ -204,18 +204,21 @@ function filterMessages( } function getUnixMillisecondsTimestamp(timestamp: string): number { - if (!isNaN(parseInt(timestamp))) { - const timestampInt = parseInt(timestamp); + const timestampInt = parseInt(timestamp, 10); + if (!isNaN(timestampInt)) { try { if (timestampInt > 10000) { return new Date(timestampInt).getTime(); } + return new Date(timestamp).getTime(); } catch (error) { - console.warn('Advanced Search: ' + error); + console.warn('Advanced Search: ', error); + return 0; } } + return 0; } @@ -233,8 +236,8 @@ function getAdvancedSearchOptionsFromQuery( let newQuery = query; const splitQuery = query.toLowerCase().split(' '); const filtersList = Object.keys(filters); - for (let queryPart of splitQuery) { - for (let filter of filtersList) { + for (const queryPart of splitQuery) { + for (const filter of filtersList) { const filterMatcher = filter + filterSeperator; if (queryPart.startsWith(filterMatcher)) { filters[filter] = queryPart.replace(filterMatcher, ''); @@ -243,9 +246,10 @@ function getAdvancedSearchOptionsFromQuery( } } - filters['before'] = getUnixMillisecondsTimestamp(filters['before']); - filters['after'] = getUnixMillisecondsTimestamp(filters['after']); - filters['query'] = newQuery; + filters.before = getUnixMillisecondsTimestamp(filters.before); + filters.after = getUnixMillisecondsTimestamp(filters.after); + filters.query = newQuery; + return filters; } From 048ca2d405b39d8a789da256feb14dd3886a5d8f Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Sat, 7 Mar 2020 21:02:24 +0100 Subject: [PATCH 11/59] fix lint in search.ts --- ts/state/ducks/search.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index 37a7b00ea..6064cb66d 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -176,7 +176,7 @@ function filterMessages( ) { let filteredMessages = messages; if (filters.from !== null && filters.from.length > 0) { - if (filters.from=== '@me') { + if (filters.from === '@me') { filteredMessages = filteredMessages.filter(message => message.sent); } else { filteredMessages = []; From 8d684f7b1b69dc35b15520b7c21212441edf4372 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 9 Mar 2020 22:59:23 -0400 Subject: [PATCH 12/59] make .loki public chats "work" passing number zero to environmental variables UNSETS the variable, pass "0" instead. this commit makes it "work" but is probably not the right fix, that comes later. (tm) --- js/modules/loki_app_dot_net_api.js | 16 +++++++++++----- js/modules/loki_public_chat_api.js | 8 ++++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index c4945fd76..70ff7e8a1 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -390,7 +390,13 @@ class LokiAppDotNetServerAPI { json: () => response, }; } - return nodeFetch(urlObj, fetchOptions, options); + const urlStr = urlObj.toString(); + if (urlStr.match(/\.loki\//)) { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + } + const result = await nodeFetch(urlObj, fetchOptions, options); + process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; + return result; } async _sendToProxy(endpoint, pFetchOptions, options = {}) { @@ -565,7 +571,7 @@ class LokiAppDotNetServerAPI { fetchOptions.headers = headers; // domain ends in .loki - if (endpoint.match(/\.loki\//)) { + if (url.toString().match(/\.loki\//)) { fetchOptions.agent = snodeHttpsAgent; } } catch (e) { @@ -599,9 +605,9 @@ class LokiAppDotNetServerAPI { )); } else { // disable check for .loki - process.env.NODE_TLS_REJECT_UNAUTHORIZED = endpoint.match(/\.loki\//) - ? 0 - : 1; + if (url.toString().match(/\.loki/)) { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + } result = await nodeFetch(url, fetchOptions); // always make sure this check is enabled process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; diff --git a/js/modules/loki_public_chat_api.js b/js/modules/loki_public_chat_api.js index 0dff04c9b..89fbc7abf 100644 --- a/js/modules/loki_public_chat_api.js +++ b/js/modules/loki_public_chat_api.js @@ -29,11 +29,11 @@ class LokiPublicChatFactoryAPI extends EventEmitter { 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; + if (serverUrl.match(/\.loki$/)) { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + } await nodeFetch(serverUrl); - process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'; // const txt = await res.text(); } catch (e) { process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; From d10a8f0a6f8e17a133d410ec0aada0cc645e60c7 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 10 Mar 2020 09:55:29 +1100 Subject: [PATCH 13/59] Added setting to disable checking for auto updates. --- _locales/en/messages.json | 6 ++++++ app/base_config.d.ts | 11 +++++++++++ app/user_config.d.ts | 3 +++ main.js | 18 +++++++++++++++++- preload.js | 13 +++++++++++++ .../session/settings/SessionSettings.tsx | 13 +++++++++++++ ts/updater/index.ts | 18 ++++++++++++++++-- ts/updater/updater.ts | 16 ++++++++++++++-- 8 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 app/base_config.d.ts create mode 100644 app/user_config.d.ts diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 3364bed1e..b176ebefb 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1958,6 +1958,12 @@ "relink": { "message": "Relink" }, + "autoUpdateSettingTitle": { + "message": "Auto Update" + }, + "autoUpdateSettingDescription": { + "message": "Automatically check for updates on launch" + }, "autoUpdateNewVersionTitle": { "message": "Session update available" }, diff --git a/app/base_config.d.ts b/app/base_config.d.ts new file mode 100644 index 000000000..6d2c25304 --- /dev/null +++ b/app/base_config.d.ts @@ -0,0 +1,11 @@ +export interface BaseConfig { + set(keyPath: string, value: any): void + get(keyPath: string): any | undefined + remove(): void +} + +interface Options { + allowMalformedOnStartup: boolean +} + +export function start(name: string, targetPath: string, options: Options): BaseConfig; diff --git a/app/user_config.d.ts b/app/user_config.d.ts new file mode 100644 index 000000000..9c399c847 --- /dev/null +++ b/app/user_config.d.ts @@ -0,0 +1,3 @@ +import { BaseConfig } from "./base_config"; + +type UserConfig = BaseConfig diff --git a/main.js b/main.js index e0d0cb770..21373b622 100644 --- a/main.js +++ b/main.js @@ -427,7 +427,7 @@ async function readyForUpdates() { // Second, start checking for app updates try { - await updater.start(getMainWindow, locale.messages, logger); + await updater.start(getMainWindow, userConfig, locale.messages, logger); } catch (error) { const log = logger || console; log.error( @@ -1090,6 +1090,22 @@ ipc.on('set-media-permissions', (event, value) => { } }); +// Loki - Auto updating +ipc.on('get-auto-update-enabled', event => { + const configValue = userConfig.get('autoUpdate'); + // eslint-disable-next-line no-param-reassign + event.returnValue = typeof configValue !== 'boolean' ? true : configValue; +}); + +ipc.on('set-auto-update-enabled', (event, value) => { + userConfig.set('autoUpdate', !!value); + + // Stop updater if user turned it off + if (!value) { + updater.stop(); + } +}); + function getDataFromMainWindow(name, callback) { ipc.once(`get-success-${name}`, (_event, error, value) => callback(error, value) diff --git a/preload.js b/preload.js index 789d14113..e90590365 100644 --- a/preload.js +++ b/preload.js @@ -211,8 +211,11 @@ window.getSettingValue = (settingID, comparisonValue = null) => { // Eg. window.getSettingValue('theme', 'light') // returns 'false' when the value is 'dark'. + // We need to get specific settings from the main process if (settingID === 'media-permissions') { return window.getMediaPermissions(); + } else if (settingID === 'auto-update') { + return window.getAutoUpdateEnabled(); } const settingVal = window.storage.get(settingID); @@ -220,6 +223,12 @@ window.getSettingValue = (settingID, comparisonValue = null) => { }; window.setSettingValue = (settingID, value) => { + // For auto updating we need to pass the value to the main process + if (settingID === 'auto-update') { + window.setAutoUpdateEnabled(value); + return; + } + window.storage.put(settingID, value); if (settingID === 'zoom-factor-setting') { @@ -231,6 +240,10 @@ window.setSettingValue = (settingID, value) => { window.getMessageTTL = () => window.storage.get('message-ttl', 24); window.getMediaPermissions = () => ipc.sendSync('get-media-permissions'); +// Auto update setting +window.getAutoUpdateEnabled = () => ipc.sendSync('get-auto-update-enabled'); +window.setAutoUpdateEnabled = (value) => ipc.send('set-auto-update-enabled', !!value); + ipc.on('get-ready-for-shutdown', async () => { const { shutdown } = window.Events || {}; if (!shutdown) { diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 233ef340e..01ab4dd16 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -496,6 +496,19 @@ export class SettingsView extends React.Component { content: {}, confirmationDialogParams: undefined, }, + { + id: 'auto-update', + title: window.i18n('autoUpdateSettingTitle'), + description: window.i18n('autoUpdateSettingDescription'), + hidden: false, + type: SessionSettingType.Toggle, + category: SessionSettingCategory.Privacy, + setFn: undefined, + comparisonValue: undefined, + onClick: undefined, + content: {}, + confirmationDialogParams: undefined, + }, { id: 'set-password', title: window.i18n('setAccountPasswordTitle'), diff --git a/ts/updater/index.ts b/ts/updater/index.ts index dc1ec1310..036344388 100644 --- a/ts/updater/index.ts +++ b/ts/updater/index.ts @@ -1,12 +1,15 @@ import { get as getFromConfig } from 'config'; import { BrowserWindow } from 'electron'; -import { start as startUpdater } from './updater'; +import { start as startUpdater, stop as stopUpdater } from './updater'; import { LoggerType, MessagesType } from './common'; +import { UserConfig } from '../../app/user_config'; let initialized = false; +let config: UserConfig; export async function start( getMainWindow: () => BrowserWindow, + userConfig: UserConfig, messages?: MessagesType, logger?: LoggerType ) { @@ -14,6 +17,7 @@ export async function start( throw new Error('updater/start: Updates have already been initialized!'); } initialized = true; + config = userConfig; if (!messages) { throw new Error('updater/start: Must provide messages!'); @@ -40,8 +44,18 @@ export async function start( await startUpdater(getMainWindow, messages, logger); } +export function stop() { + if (initialized) { + stopUpdater(); + initialized = false; + } +} + function autoUpdateDisabled() { return ( - process.mas || !getFromConfig('updatesEnabled') // From Electron: Mac App Store build + process.mas || // From Electron: Mac App Store build + !getFromConfig('updatesEnabled') || // Hard coded config + // tslint:disable-next-line: no-backbone-get-set-outside-model + !config.get('autoUpdate') // User setting ); } diff --git a/ts/updater/updater.ts b/ts/updater/updater.ts index abde36b06..5540030b7 100644 --- a/ts/updater/updater.ts +++ b/ts/updater/updater.ts @@ -3,6 +3,8 @@ import * as fs from 'fs-extra'; import { autoUpdater, UpdateInfo } from 'electron-updater'; import { app, BrowserWindow } from 'electron'; import { markShouldQuit } from '../../app/window_state'; +import { UserConfig } from '../../app/user_config'; + import { getPrintableError, LoggerType, @@ -15,6 +17,8 @@ import { gt as isVersionGreaterThan, parse as parseVersion } from 'semver'; let isUpdating = false; let downloadIgnored = false; +let interval: NodeJS.Timeout; +let stopped = false; const SECOND = 1000; const MINUTE = SECOND * 60; @@ -30,23 +34,31 @@ export async function start( autoUpdater.logger = logger; autoUpdater.autoDownload = false; - setInterval(async () => { + interval = setInterval(async () => { try { await checkForUpdates(getMainWindow, messages, logger); } catch (error) { logger.error('auto-update: error:', getPrintableError(error)); } }, INTERVAL); + stopped = false; await checkForUpdates(getMainWindow, messages, logger); } +export function stop() { + if (interval) { + clearInterval(interval); + stopped = true; + } +} + async function checkForUpdates( getMainWindow: () => BrowserWindow, messages: MessagesType, logger: LoggerType ) { - if (isUpdating || downloadIgnored) { + if (stopped || isUpdating || downloadIgnored) { return; } From 86b427cc4ddf2c8d487e480886addc15aaaabdd6 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 10 Mar 2020 14:28:33 +1100 Subject: [PATCH 14/59] Lint --- app/base_config.d.ts | 14 +++++++++----- app/user_config.d.ts | 4 ++-- preload.js | 3 ++- ts/updater/updater.ts | 7 ++++++- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/app/base_config.d.ts b/app/base_config.d.ts index 6d2c25304..3aec7c607 100644 --- a/app/base_config.d.ts +++ b/app/base_config.d.ts @@ -1,11 +1,15 @@ export interface BaseConfig { - set(keyPath: string, value: any): void - get(keyPath: string): any | undefined - remove(): void + set(keyPath: string, value: any): void; + get(keyPath: string): any | undefined; + remove(): void; } interface Options { - allowMalformedOnStartup: boolean + allowMalformedOnStartup: boolean; } -export function start(name: string, targetPath: string, options: Options): BaseConfig; +export function start( + name: string, + targetPath: string, + options: Options +): BaseConfig; diff --git a/app/user_config.d.ts b/app/user_config.d.ts index 9c399c847..4b0a2282c 100644 --- a/app/user_config.d.ts +++ b/app/user_config.d.ts @@ -1,3 +1,3 @@ -import { BaseConfig } from "./base_config"; +import { BaseConfig } from './base_config'; -type UserConfig = BaseConfig +type UserConfig = BaseConfig; diff --git a/preload.js b/preload.js index e90590365..6ad2ee75f 100644 --- a/preload.js +++ b/preload.js @@ -242,7 +242,8 @@ window.getMediaPermissions = () => ipc.sendSync('get-media-permissions'); // Auto update setting window.getAutoUpdateEnabled = () => ipc.sendSync('get-auto-update-enabled'); -window.setAutoUpdateEnabled = (value) => ipc.send('set-auto-update-enabled', !!value); +window.setAutoUpdateEnabled = value => + ipc.send('set-auto-update-enabled', !!value); ipc.on('get-ready-for-shutdown', async () => { const { shutdown } = window.Events || {}; diff --git a/ts/updater/updater.ts b/ts/updater/updater.ts index 5540030b7..ea6856b19 100644 --- a/ts/updater/updater.ts +++ b/ts/updater/updater.ts @@ -3,7 +3,6 @@ import * as fs from 'fs-extra'; import { autoUpdater, UpdateInfo } from 'electron-updater'; import { app, BrowserWindow } from 'electron'; import { markShouldQuit } from '../../app/window_state'; -import { UserConfig } from '../../app/user_config'; import { getPrintableError, @@ -29,6 +28,12 @@ export async function start( messages: MessagesType, logger: LoggerType ) { + if (interval) { + logger.info('auto-update: Already running'); + + return; + } + logger.info('auto-update: starting checks...'); autoUpdater.logger = logger; From d31feed2c06b49592e377f3e96dea81a6e515586 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 10 Mar 2020 14:58:44 +1100 Subject: [PATCH 15/59] Review fixes --- main.js | 12 +++++++----- preload.js | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/main.js b/main.js index 21373b622..5ed65eac0 100644 --- a/main.js +++ b/main.js @@ -1091,18 +1091,20 @@ ipc.on('set-media-permissions', (event, value) => { }); // Loki - Auto updating -ipc.on('get-auto-update-enabled', event => { +ipc.on('get-auto-update-setting', event => { const configValue = userConfig.get('autoUpdate'); // eslint-disable-next-line no-param-reassign event.returnValue = typeof configValue !== 'boolean' ? true : configValue; }); -ipc.on('set-auto-update-enabled', (event, value) => { - userConfig.set('autoUpdate', !!value); +ipc.on('set-auto-update-setting', (event, enabled) => { + userConfig.set('autoUpdate', !!enabled); - // Stop updater if user turned it off - if (!value) { + if (enabled) { + readyForUpdates(); + } else { updater.stop(); + isReadyForUpdates = false; } }); diff --git a/preload.js b/preload.js index 6ad2ee75f..94fa47338 100644 --- a/preload.js +++ b/preload.js @@ -241,9 +241,9 @@ window.getMessageTTL = () => window.storage.get('message-ttl', 24); window.getMediaPermissions = () => ipc.sendSync('get-media-permissions'); // Auto update setting -window.getAutoUpdateEnabled = () => ipc.sendSync('get-auto-update-enabled'); +window.getAutoUpdateEnabled = () => ipc.sendSync('get-auto-update-setting'); window.setAutoUpdateEnabled = value => - ipc.send('set-auto-update-enabled', !!value); + ipc.send('set-auto-update-setting', !!value); ipc.on('get-ready-for-shutdown', async () => { const { shutdown } = window.Events || {}; From 87aee6286d2a0a51c302e9446713df0b4a941245 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Tue, 10 Mar 2020 06:43:34 +0100 Subject: [PATCH 16/59] Implement requested minor changes --- ts/state/ducks/search.ts | 2 +- ts/types/Search.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index 6064cb66d..56eb7b90d 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -175,7 +175,7 @@ function filterMessages( contacts: Array ) { let filteredMessages = messages; - if (filters.from !== null && filters.from.length > 0) { + if (filters.from && filters.from.length > 0) { if (filters.from === '@me') { filteredMessages = filteredMessages.filter(message => message.sent); } else { diff --git a/ts/types/Search.ts b/ts/types/Search.ts index 990372434..b24914d8e 100644 --- a/ts/types/Search.ts +++ b/ts/types/Search.ts @@ -7,7 +7,7 @@ export type SearchOptions = { export type AdvancedSearchOptions = { query: string; - from: string; + from?: string; before: number; after: number; }; From 366acae1cd493e2cdaa5355b9772db5e40679039 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Tue, 10 Mar 2020 06:58:36 +0100 Subject: [PATCH 17/59] Minor Fix check if from is invalid not just null --- ts/state/ducks/search.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index 56eb7b90d..37af10c04 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -112,7 +112,7 @@ async function doSearch( if (isAdvancedQuery) { let senderFilter: Array = []; if ( - advancedSearchOptions.from !== null && + advancedSearchOptions.from && advancedSearchOptions.from.length > 0 ) { const senderFilterQuery = await queryConversationsAndContacts( From 20feb23017e22318a24b5e5740da5c1ab6bba5e0 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Tue, 10 Mar 2020 07:07:23 +0100 Subject: [PATCH 18/59] small lint fix in search.ts --- ts/state/ducks/search.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index 37af10c04..11eb2eade 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -111,10 +111,7 @@ async function doSearch( if (isAdvancedQuery) { let senderFilter: Array = []; - if ( - advancedSearchOptions.from && - advancedSearchOptions.from.length > 0 - ) { + if (advancedSearchOptions.from && advancedSearchOptions.from.length > 0) { const senderFilterQuery = await queryConversationsAndContacts( advancedSearchOptions.from, options From d823850a9eaaa8b59a1a50e89533e5bf1003292c Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Tue, 10 Mar 2020 14:10:45 +0100 Subject: [PATCH 19/59] Add overflow-y auto to session-id-editable to be able see the whole sessionID --- stylesheets/_session.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index a3b282caa..0b3aa883e 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -1457,6 +1457,7 @@ label { resize: none; overflow: hidden; user-select: all; + overflow-y: auto; } input { From 26140e0ed0e8c3b699e7c900190ee0f30f9d6d49 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 11 Mar 2020 11:10:19 +1100 Subject: [PATCH 20/59] Send delivery receipts asynchronously --- js/background.js | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/js/background.js b/js/background.js index 7485c5831..2d7494b1e 100644 --- a/js/background.js +++ b/js/background.js @@ -2132,35 +2132,35 @@ const message = new Whisper.Message(messageData); - // If we don't return early here, we can get into infinite error loops. So, no - // delivery receipts for sealed sender errors. - + // Send a delivery receipt + // If we don't return early here, we can get into infinite error loops. So, no delivery receipts for sealed sender errors. // Note(LOKI): don't send receipt for FR as we don't have a session yet - if (isError || !data.unidentifiedDeliveryReceived || data.friendRequest) { - return message; - } - - try { + const isGroup = data && data.message && data.message.group; + const shouldSendReceipt = + !isError && + data.unidentifiedDeliveryReceived && + !data.isFriendRequest && + !isGroup; + + // Send the receipt async and hope that it succeeds + if (shouldSendReceipt) { const { wrap, sendOptions } = ConversationController.prepareForSend( data.source ); - const isGroup = data && data.message && data.message.group; - if (!isGroup) { - await wrap( - textsecure.messaging.sendDeliveryReceipt( - data.source, - data.timestamp, - sendOptions - ) + wrap( + textsecure.messaging.sendDeliveryReceipt( + data.source, + data.timestamp, + sendOptions + ) + ).catch(error => { + window.log.error( + `Failed to send delivery receipt to ${data.source} for message ${ + data.timestamp + }:`, + error && error.stack ? error.stack : error ); - } - } catch (error) { - window.log.error( - `Failed to send delivery receipt to ${data.source} for message ${ - data.timestamp - }:`, - error && error.stack ? error.stack : error - ); + }); } return message; From 8c91aa04f1366aaec4a1029ce5c92df2c348ad38 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 11 Mar 2020 13:18:35 +1100 Subject: [PATCH 21/59] Speed up message creation. We were fetching all of our devices everytime a new message was received. This was only used for when the message was a public chat message, so i moved it into the relevant if statement. In the future if we increase the device link limit, we'll need to fetch all our devices on application launch or something. --- js/background.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/js/background.js b/js/background.js index 2d7494b1e..105e8e71f 100644 --- a/js/background.js +++ b/js/background.js @@ -1964,21 +1964,23 @@ return handleProfileUpdate({ data, confirm, messageDescriptor }); } - const primaryDeviceKey = window.storage.get('primaryDevicePubKey'); - const allOurDevices = await libloki.storage.getAllDevicePubKeysForPrimaryPubKey( - primaryDeviceKey - ); const descriptorId = await textsecure.MessageReceiver.arrayBufferToString( messageDescriptor.id ); let message; if ( messageDescriptor.type === 'group' && - descriptorId.match(/^publicChat:/) && - allOurDevices.includes(data.source) + descriptorId.match(/^publicChat:/) ) { - // Public chat messages from ourselves should be outgoing - message = await createSentMessage(data); + // Note: This only works currently because we have a 1 device limit + // When we change that, the check below needs to change too + const ourNumber = textsecure.storage.user.getNumber(); + const primaryDevice = window.storage.get('primaryDevicePubKey'); + const { source } = data; + if (source && (source === ourNumber || source === primaryDevice)) { + // Public chat messages from ourselves should be outgoing + message = await createSentMessage(data); + } } else { message = await createMessage(data); } From 2ed7ed5de56cdca8dd411743ab8292a4f4e0963d Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Wed, 11 Mar 2020 17:23:42 +0100 Subject: [PATCH 22/59] Implement Blib's change --- js/views/conversation_view.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 93ce33d67..ef60f2a39 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -2408,7 +2408,7 @@ let direction = $input.selectionDirection; if (event.shiftKey) { - if (direction === 'none') { + if (direction === 'none' || direction === 'forward') { if (isLeft) { direction = 'backward'; } else { @@ -2419,6 +2419,7 @@ direction = 'none'; } + console.log(isLeft, direction, posStart, posEnd, newPosStart, newPosEnd); if (direction === 'forward') { newPosStart = posStart; } else if (direction === 'backward') { From c410ef5354009b847053e75c8ce779883a371b0a Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Wed, 11 Mar 2020 17:37:07 +0100 Subject: [PATCH 23/59] Remove Debuglog --- js/views/conversation_view.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index ef60f2a39..e32b56742 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -2419,7 +2419,6 @@ direction = 'none'; } - console.log(isLeft, direction, posStart, posEnd, newPosStart, newPosEnd); if (direction === 'forward') { newPosStart = posStart; } else if (direction === 'backward') { From 18a464fc58f93c3bc2e3391b1252efbdd4056b2d Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Thu, 12 Mar 2020 12:43:24 +0100 Subject: [PATCH 24/59] Add a numeric TTL Countdown if the time left is under 1 minute --- stylesheets/_modules.scss | 5 ++++- ts/components/conversation/ExpireTimer.tsx | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index ed30fc502..3af867ec2 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -653,11 +653,14 @@ // Module: Expire Timer +.module-expire-timer-margin { + margin-left: 6px; +} + .module-expire-timer { width: 12px; height: 12px; display: inline-block; - margin-left: 6px; margin-bottom: 2px; @include color-svg('../images/timer-60.svg', $color-gray-60); } diff --git a/ts/components/conversation/ExpireTimer.tsx b/ts/components/conversation/ExpireTimer.tsx index cd66055fd..929f386c9 100644 --- a/ts/components/conversation/ExpireTimer.tsx +++ b/ts/components/conversation/ExpireTimer.tsx @@ -47,11 +47,27 @@ export class ExpireTimer extends React.Component { } = this.props; const bucket = getTimerBucket(expirationTimestamp, expirationLength); + let timeLeft = Math.round((expirationTimestamp - Date.now()) / 1000); + timeLeft = timeLeft >= 0 ? timeLeft : 0; + if (timeLeft <= 60) { + return ( + + {timeLeft} + + ); + } return (
Date: Fri, 13 Mar 2020 12:04:32 +1100 Subject: [PATCH 25/59] Build binaries for win32 --- package.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index bfeb9b735..ec66c3259 100644 --- a/package.json +++ b/package.json @@ -235,7 +235,13 @@ "verifyUpdateCodeSignature": false, "icon": "build/icons/win/icon.ico", "target": [ - "nsis" + { + "target": "nsis", + "arch": [ + "x64", + "ia32" + ] + } ] }, "nsis": { From a285c091b3aed67d1b5b63e78c464e86bc564fbb Mon Sep 17 00:00:00 2001 From: Mikunj Varsani Date: Fri, 13 Mar 2020 12:47:24 +1100 Subject: [PATCH 26/59] Revert "Enable building win32 binaries." --- package.json | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/package.json b/package.json index ec66c3259..bfeb9b735 100644 --- a/package.json +++ b/package.json @@ -235,13 +235,7 @@ "verifyUpdateCodeSignature": false, "icon": "build/icons/win/icon.ico", "target": [ - { - "target": "nsis", - "arch": [ - "x64", - "ia32" - ] - } + "nsis" ] }, "nsis": { From de6dc26d6ca0bb698a628b7a7c8442683f617697 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Fri, 13 Mar 2020 16:14:30 +0100 Subject: [PATCH 27/59] Replace native Context Menu with a syleguided one in SearchInput --- ts/components/session/SessionSearchInput.tsx | 81 +++++++++++++++++--- 1 file changed, 69 insertions(+), 12 deletions(-) diff --git a/ts/components/session/SessionSearchInput.tsx b/ts/components/session/SessionSearchInput.tsx index c92b0cb1b..8cae48432 100644 --- a/ts/components/session/SessionSearchInput.tsx +++ b/ts/components/session/SessionSearchInput.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { SessionIconButton, SessionIconSize, SessionIconType } from './icon'; +import { ContextMenu, ContextMenuTrigger, MenuItem } from 'react-contextmenu'; interface Props { searchString: string; @@ -12,24 +13,56 @@ export class SessionSearchInput extends React.Component { public constructor(props: Props) { super(props); this.handleKeyDown = this.handleKeyDown.bind(this); + this.handleUndo = this.handleUndo.bind(this); + this.handleRedo = this.handleRedo.bind(this); + this.handleCut = this.handleCut.bind(this); + this.handleCopy = this.handleCopy.bind(this); + this.handlePast = this.handlePast.bind(this); + this.handleSelectAll = this.handleSelectAll.bind(this); } public render() { const { searchString } = this.props; + const triggerId = `session-search-input-context`; return ( -
- - this.props.onChange(e.target.value)} - onKeyDown={this.handleKeyDown} - placeholder={this.props.placeholder} - /> -
+ <> + +
+ + this.props.onChange(e.target.value)} + onKeyDown={this.handleKeyDown} + placeholder={this.props.placeholder} + /> +
+
+ + + {window.i18n('editMenuUndo')} + + + {window.i18n('editMenuRedo')} + +
+ + {window.i18n('editMenuCut')} + + + {window.i18n('editMenuCopy')} + + + {window.i18n('editMenuPaste')} + + + {window.i18n('editMenuSelectAll')} + +
+ ); } @@ -42,4 +75,28 @@ export class SessionSearchInput extends React.Component { } } } + + public handleUndo() { + document.execCommand('undo'); + } + + public handleRedo() { + document.execCommand('redo'); + } + + public handleCut() { + document.execCommand('cut'); + } + + public handleCopy() { + document.execCommand('copy'); + } + + public handlePast() { + document.execCommand('paste'); + } + + public handleSelectAll() { + document.execCommand('selectAll'); + } } From e9be9bcbc5a89d5753c63bd78ab6e4f46a383bfc Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Fri, 13 Mar 2020 16:23:27 +0100 Subject: [PATCH 28/59] lint quotemark ` should be ' --- ts/components/session/SessionSearchInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/components/session/SessionSearchInput.tsx b/ts/components/session/SessionSearchInput.tsx index 8cae48432..769092536 100644 --- a/ts/components/session/SessionSearchInput.tsx +++ b/ts/components/session/SessionSearchInput.tsx @@ -23,7 +23,7 @@ export class SessionSearchInput extends React.Component { public render() { const { searchString } = this.props; - const triggerId = `session-search-input-context`; + const triggerId = 'session-search-input-context'; return ( <> From d8b982e1949e35451649e4e4b81f907162fe4ab1 Mon Sep 17 00:00:00 2001 From: Mikunj Varsani Date: Wed, 18 Mar 2020 16:14:17 +1100 Subject: [PATCH 29/59] Fix messages not being created properly in open groups --- js/background.js | 6 +++++- js/modules/loki_file_server_api.js | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/js/background.js b/js/background.js index 105e8e71f..b2ff36189 100644 --- a/js/background.js +++ b/js/background.js @@ -1981,9 +1981,13 @@ // Public chat messages from ourselves should be outgoing message = await createSentMessage(data); } - } else { + } + + // All other messages should be incoming + if (!message) { message = await createMessage(data); } + const isDuplicate = await isMessageDuplicate(message); if (isDuplicate) { // RSS expects duplciates, so squelch log diff --git a/js/modules/loki_file_server_api.js b/js/modules/loki_file_server_api.js index 1bd5fbc51..571a8016b 100644 --- a/js/modules/loki_file_server_api.js +++ b/js/modules/loki_file_server_api.js @@ -105,6 +105,10 @@ class LokiFileServerInstance { } await Promise.all( authorisations.map(async auth => { + if (typeof auth !== 'object') { + return; + } + // only skip, if in secondary search mode if (isRequest && auth.secondaryDevicePubKey !== user.username) { // this is not the authorization we're looking for From 447ba9d3aa388e789654b97650e996feb1cbfd45 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Wed, 18 Mar 2020 08:13:44 +0100 Subject: [PATCH 30/59] Remove redundant code from SessionSearchInput --- ts/components/session/SessionSearchInput.tsx | 42 +++----------------- 1 file changed, 6 insertions(+), 36 deletions(-) diff --git a/ts/components/session/SessionSearchInput.tsx b/ts/components/session/SessionSearchInput.tsx index 769092536..ddbc71a53 100644 --- a/ts/components/session/SessionSearchInput.tsx +++ b/ts/components/session/SessionSearchInput.tsx @@ -13,12 +13,6 @@ export class SessionSearchInput extends React.Component { public constructor(props: Props) { super(props); this.handleKeyDown = this.handleKeyDown.bind(this); - this.handleUndo = this.handleUndo.bind(this); - this.handleRedo = this.handleRedo.bind(this); - this.handleCut = this.handleCut.bind(this); - this.handleCopy = this.handleCopy.bind(this); - this.handlePast = this.handlePast.bind(this); - this.handleSelectAll = this.handleSelectAll.bind(this); } public render() { @@ -42,23 +36,23 @@ export class SessionSearchInput extends React.Component {
- + document.execCommand('undo')}> {window.i18n('editMenuUndo')} - + document.execCommand('redo')}> {window.i18n('editMenuRedo')}
- + document.execCommand('cut')}> {window.i18n('editMenuCut')} - + document.execCommand('copy')}> {window.i18n('editMenuCopy')} - + document.execCommand('paste')}> {window.i18n('editMenuPaste')} - + document.execCommand('selectAll')}> {window.i18n('editMenuSelectAll')}
@@ -75,28 +69,4 @@ export class SessionSearchInput extends React.Component { } } } - - public handleUndo() { - document.execCommand('undo'); - } - - public handleRedo() { - document.execCommand('redo'); - } - - public handleCut() { - document.execCommand('cut'); - } - - public handleCopy() { - document.execCommand('copy'); - } - - public handlePast() { - document.execCommand('paste'); - } - - public handleSelectAll() { - document.execCommand('selectAll'); - } } From 66cec1f51e283c7af07c0b6fd75bfd880c5b00b3 Mon Sep 17 00:00:00 2001 From: Mikunj Varsani Date: Wed, 18 Mar 2020 18:09:34 +1100 Subject: [PATCH 31/59] Review fix. --- ts/updater/updater.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ts/updater/updater.ts b/ts/updater/updater.ts index ea6856b19..efd12b1bf 100644 --- a/ts/updater/updater.ts +++ b/ts/updater/updater.ts @@ -16,7 +16,7 @@ import { gt as isVersionGreaterThan, parse as parseVersion } from 'semver'; let isUpdating = false; let downloadIgnored = false; -let interval: NodeJS.Timeout; +let interval: NodeJS.Timeout | undefined; let stopped = false; const SECOND = 1000; @@ -54,6 +54,7 @@ export async function start( export function stop() { if (interval) { clearInterval(interval); + interval = undefined; stopped = true; } } From 33925d69e9ab5bb56c181a220f9f1b4ef846f5f6 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 19 Mar 2020 09:12:06 +1100 Subject: [PATCH 32/59] Cleanup --- js/background.js | 38 ++++++++++++++++-------------- js/modules/loki_file_server_api.js | 9 ++++--- libloki/storage.js | 8 ++++++- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/js/background.js b/js/background.js index b2ff36189..64b4a0f66 100644 --- a/js/background.js +++ b/js/background.js @@ -1946,6 +1946,10 @@ }) { return async event => { const { data, confirm } = event; + if (!data) { + window.log.warn('Invalid data passed to createMessageHandler.', event); + return confirm(); + } const messageDescriptor = getMessageDescriptor(data); @@ -1968,23 +1972,21 @@ messageDescriptor.id ); let message; - if ( + const { source } = data; + + // Note: This only works currently because we have a 1 device limit + // When we change that, the check below needs to change too + const ourNumber = textsecure.storage.user.getNumber(); + const primaryDevice = window.storage.get('primaryDevicePubKey'); + const isOurDevice = + source && (source === ourNumber || source === primaryDevice); + const isPublicChatMessage = messageDescriptor.type === 'group' && - descriptorId.match(/^publicChat:/) - ) { - // Note: This only works currently because we have a 1 device limit - // When we change that, the check below needs to change too - const ourNumber = textsecure.storage.user.getNumber(); - const primaryDevice = window.storage.get('primaryDevicePubKey'); - const { source } = data; - if (source && (source === ourNumber || source === primaryDevice)) { - // Public chat messages from ourselves should be outgoing - message = await createSentMessage(data); - } - } - - // All other messages should be incoming - if (!message) { + descriptorId.match(/^publicChat:/); + if (isPublicChatMessage && isOurDevice) { + // Public chat messages from ourselves should be outgoing + message = await createSentMessage(data); + } else { message = await createMessage(data); } @@ -1994,14 +1996,14 @@ if (!descriptorId.match(/^rss:/)) { window.log.warn('Received duplicate message', message.idForLogging()); } - return event.confirm(); + return confirm(); } await ConversationController.getOrCreateAndWait( messageDescriptor.id, messageDescriptor.type ); - return message.handleDataMessage(data.message, event.confirm, { + return message.handleDataMessage(data.message, confirm, { initialLoadComplete, }); }; diff --git a/js/modules/loki_file_server_api.js b/js/modules/loki_file_server_api.js index 571a8016b..5d3dfdde5 100644 --- a/js/modules/loki_file_server_api.js +++ b/js/modules/loki_file_server_api.js @@ -103,12 +103,11 @@ class LokiFileServerInstance { if (!Array.isArray(authorisations)) { return; } + const validAuthorisations = authorisations.filter( + a => a && typeof auth === 'object' + ); await Promise.all( - authorisations.map(async auth => { - if (typeof auth !== 'object') { - return; - } - + validAuthorisations.map(async auth => { // only skip, if in secondary search mode if (isRequest && auth.secondaryDevicePubKey !== user.username) { // this is not the authorization we're looking for diff --git a/libloki/storage.js b/libloki/storage.js index 0e4cd3dda..ff52e52c5 100644 --- a/libloki/storage.js +++ b/libloki/storage.js @@ -147,7 +147,9 @@ ({ authorisations } = primaryDeviceMapping); } } - return authorisations || []; + + // filter out any invalid authorisations + return authorisations.filter(a => a && typeof a === 'object') || []; } // if the device is a secondary device, @@ -168,6 +170,10 @@ } async function savePairingAuthorisation(authorisation) { + if (!authorisation) { + return; + } + // Ensure that we have a conversation for all the devices const conversation = await ConversationController.getOrCreateAndWait( authorisation.secondaryDevicePubKey, From 1c78e1a7012ab83042393307ebfa915ee069e71f Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 18 Mar 2020 16:41:26 -0700 Subject: [PATCH 33/59] sendToProxy use agent, handle 401/500s, retry system --- js/modules/loki_rpc.js | 102 +++++++++++++++++++++++++++++++++-------- 1 file changed, 84 insertions(+), 18 deletions(-) diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index 5f29f6c38..c7a709337 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -29,7 +29,9 @@ const decryptResponse = async (response, address) => { return {}; }; -const sendToProxy = async (options = {}, targetNode) => { +const timeoutDelay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); + +const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => { const randSnode = await lokiSnodeAPI.getRandomSnodeAddress(); // Don't allow arbitrary URLs, only snodes and loki servers @@ -60,36 +62,93 @@ const sendToProxy = async (options = {}, targetNode) => { 'X-Sender-Public-Key': StringView.arrayBufferToHex(myKeys.pubKey), 'X-Target-Snode-Key': targetNode.pubkey_ed25519, }, + agent: snodeHttpsAgent, }; // we only proxy to snodes... - process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; const response = await nodeFetch(url, firstHopOptions); - process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; + + if (response.status === 401) { + // decom or dereg + // remove + // but which the proxy or the target... + // we got a ton of randomPool nodes, let's just not worry about this one + const randomPoolRemainingCount = lokiSnodeAPI.markRandomNodeUnreachable(randSnode); + log.warn( + `lokiRpc sendToProxy`, + `snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${targetNode.port}`, + `snode is decom or dereg: `, + ciphertext, + // `marking random snode bad ${randomPoolRemainingCount} remaining` + `Try #${retryNumber}`, + `removing randSnode leaving ${randomPoolRemainingCount} in the random pool` + ); + // retry, just count it happening 5 times to be the target for now + return sendToProxy(options, targetNode, retryNumber + 1); + } // detect SNode is not ready (not in swarm; not done syncing) - if (response.status === 503) { + if (response.status === 503 || response.status === 500) { const ciphertext = await response.text(); - log.error( - `lokiRpc sendToProxy snode ${randSnode.ip}:${randSnode.port} error`, - ciphertext + // we shouldn't do these, + // it's seems to be not the random node that's always bad + // but the target node + + // we got a ton of randomPool nodes, let's just not worry about this one + const randomPoolRemainingCount = lokiSnodeAPI.markRandomNodeUnreachable(randSnode); + log.warn( + `lokiRpc sendToProxy`, + `snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${targetNode.port}`, + `code ${response.status} error`, + ciphertext, + // `marking random snode bad ${randomPoolRemainingCount} remaining` + `Try #${retryNumber}`, + `removing randSnode leaving ${randomPoolRemainingCount} in the random pool` ); // mark as bad for this round (should give it some time and improve success rates) - lokiSnodeAPI.markRandomNodeUnreachable(randSnode); // retry for a new working snode - return sendToProxy(options, targetNode); + const pRetryNumber = retryNumber + 1; + if (pRetryNumber > 5) { + // it's likely a net problem or an actual problem on the target node + // lets mark the target node bad for now + // we'll just rotate it back in if it's a net problem + log.warn(`Failing ${targetNode.ip}:${targetNode.port} after 5 retries`); + if (options.ourPubKey) { + lokiSnodeAPI.unreachableNode(options.ourPubKey, targetNode); + } + return false; + } + // 500 burns through a node too fast, + // let's slow the retry to give it more time to recover + if (response.status === 500) { + await timeoutDelay(5000); + } + return sendToProxy(options, targetNode, pRetryNumber); } + /* + if (response.status === 500) { + // usually when the server returns nothing... + } + */ // FIXME: handle nodeFetch errors/exceptions... if (response.status !== 200) { // let us know we need to create handlers for new unhandled codes - log.warn('lokiRpc sendToProxy fetch non-200 statusCode', response.status); + log.warn( + 'lokiRpc sendToProxy fetch non-200 statusCode', + response.status, + `from snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${targetNode.port}` + ); + return false; } const ciphertext = await response.text(); if (!ciphertext) { // avoid base64 decode failure - log.warn('Server did not return any data for', options); + // usually a 500 but not always + // could it be a timeout? + log.warn('Server did not return any data for', options, targetNode); + return false; } let plaintext; @@ -112,7 +171,7 @@ const sendToProxy = async (options = {}, targetNode) => { 'lokiRpc sendToProxy decode error', e.code, e.message, - `from ${randSnode.ip}:${randSnode.port} ciphertext:`, + `from ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${targetNode.port} ciphertext:`, ciphertext ); if (ciphertextBuffer) { @@ -138,6 +197,11 @@ const sendToProxy = async (options = {}, targetNode) => { } return false; }; + if (retryNumber) { + log.info(`lokiRpc sendToProxy request succeeded,`, + `snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${targetNode.port}`, + `on retry #${retryNumber}`); + } return jsonRes; } catch (e) { log.error( @@ -182,22 +246,24 @@ const lokiFetch = async (url, options = {}, targetNode = null) => { timeout, method, }; - if (url.match(/https:\/\//)) { - fetchOptions.agent = snodeHttpsAgent; - } try { if (window.lokiFeatureFlags.useSnodeProxy && targetNode) { const result = await sendToProxy(fetchOptions, targetNode); - return result ? result.json() : false; + // if not result, maybe we should throw?? + return result ? result.json() : {}; } if (url.match(/https:\/\//)) { - process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; + // import that this does not get set in sendToProxy fetchOptions + fetchOptions.agent = snodeHttpsAgent; + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + } else { + log.info ('lokiRpc http communication', url); } const response = await nodeFetch(url, fetchOptions); // restore TLS checking - process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'; let result; // Wrong swarm From e3545fa33847695ae7233ae5354b46b9a989109e Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 18 Mar 2020 17:14:55 -0700 Subject: [PATCH 34/59] change unreachableNode 2nd param to object, put lock around getRandomSnodeAddress so they can't stack, markRandomNodeUnreachable returns remaining count, adjust logging --- js/modules/loki_snode_api.js | 154 ++++++++++++++++++++++------------- 1 file changed, 97 insertions(+), 57 deletions(-) diff --git a/js/modules/loki_snode_api.js b/js/modules/loki_snode_api.js index 7d163a215..450cc03b7 100644 --- a/js/modules/loki_snode_api.js +++ b/js/modules/loki_snode_api.js @@ -15,6 +15,7 @@ class LokiSnodeAPI { this.localUrl = localUrl; // localhost.loki this.randomSnodePool = []; this.swarmsPendingReplenish = {}; + this.initialiseRandomPoolPromise = false; } async getRandomSnodeAddress() { @@ -35,70 +36,99 @@ class LokiSnodeAPI { seedNodes = [...window.seedNodeList], consecutiveErrors = 0 ) { - const params = { - limit: 20, - active_only: true, - fields: { - public_ip: true, - storage_port: true, - pubkey_x25519: true, - pubkey_ed25519: true, - }, - }; - const seedNode = seedNodes.splice( - Math.floor(Math.random() * seedNodes.length), - 1 - )[0]; - let snodes = []; - try { - const response = await lokiRpc( - `http://${seedNode.ip}`, - seedNode.port, - 'get_n_service_nodes', - params, - {}, // Options - '/json_rpc' // Seed request endpoint - ); - // Filter 0.0.0.0 nodes which haven't submitted uptime proofs - snodes = response.result.service_node_states.filter( - snode => snode.public_ip !== '0.0.0.0' - ); - this.randomSnodePool = snodes.map(snode => ({ - ip: snode.public_ip, - port: snode.storage_port, - pubkey_x25519: snode.pubkey_x25519, - pubkey_ed25519: snode.pubkey_ed25519, - })); - } catch (e) { - log.warn('initialiseRandomPool error', e.code, e.message); - if (consecutiveErrors < 3) { - // retry after a possible delay - setTimeout(() => { - log.info( - 'Retrying initialising random snode pool, try #', - consecutiveErrors + // if currently not in progress + if (this.initialiseRandomPoolPromise === false) { + // FIXME: add timeout + // set lock + this.initialiseRandomPoolPromise = new Promise(async resolve => { + const params = { + limit: 1024, + active_only: true, + fields: { + public_ip: true, + storage_port: true, + pubkey_x25519: true, + pubkey_ed25519: true, + }, + }; + const seedNode = seedNodes.splice( + Math.floor(Math.random() * seedNodes.length), + 1 + )[0]; + let snodes = []; + try { + log.info('loki_snodes: Refreshing random snode pool'); + const response = await lokiRpc( + `http://${seedNode.ip}`, + seedNode.port, + 'get_n_service_nodes', + params, + {}, // Options + '/json_rpc' // Seed request endpoint ); - this.initialiseRandomPool(seedNodes, consecutiveErrors + 1); - }, consecutiveErrors * consecutiveErrors * 5000); - } else { - log.error('Giving up trying to contact seed node'); - if (snodes.length === 0) { - throw new window.textsecure.SeedNodeError( - 'Failed to contact seed node' + // Filter 0.0.0.0 nodes which haven't submitted uptime proofs + snodes = response.result.service_node_states.filter( + snode => snode.public_ip !== '0.0.0.0' ); + this.randomSnodePool = snodes.map(snode => ({ + ip: snode.public_ip, + port: snode.storage_port, + pubkey_x25519: snode.pubkey_x25519, + pubkey_ed25519: snode.pubkey_ed25519, + })); + log.info('loki_snodes: Refreshed random snode pool with', this.randomSnodePool.length, 'snodes'); + } catch (e) { + log.warn('loki_snodes: initialiseRandomPool error', e.code, e.message); + if (consecutiveErrors < 3) { + // retry after a possible delay + setTimeout(() => { + log.info( + 'loki_snodes: Retrying initialising random snode pool, try #', + consecutiveErrors + ); + this.initialiseRandomPool(seedNodes, consecutiveErrors + 1); + }, consecutiveErrors * consecutiveErrors * 5000); + } else { + log.error('loki_snodes: Giving up trying to contact seed node'); + if (snodes.length === 0) { + throw new window.textsecure.SeedNodeError( + 'Failed to contact seed node' + ); + } + } } - } + // clear lock + this.initialiseRandomPoolPromise = null; + resolve(); + }) } + await this.initialiseRandomPoolPromise; } - // nodeUrl is like 9hrje1bymy7hu6nmtjme9idyu3rm8gr3mkstakjyuw1997t7w4ny.snode - async unreachableNode(pubKey, nodeUrl) { + // unreachableNode.url is like 9hrje1bymy7hu6nmtjme9idyu3rm8gr3mkstakjyuw1997t7w4ny.snode + async unreachableNode(pubKey, unreachableNode) { const conversation = ConversationController.get(pubKey); const swarmNodes = [...conversation.get('swarmNodes')]; + if (typeof(unreachableNode) === 'string') { + log.warn('loki_snodes::unreachableNode: String passed as unreachableNode to unreachableNode'); + return swarmNodes; + } + let found = false const filteredNodes = swarmNodes.filter( - node => node.address !== nodeUrl && node.ip !== nodeUrl + node => { + // keep all but thisNode + const thisNode = (node.address === unreachableNode.address && node.ip === unreachableNode.ip && node.port === unreachableNode.port) + if (thisNode) { + found = true + } + return !thisNode + } ); + if (!found) { + log.warn(`loki_snodes::unreachableNode snode ${unreachableNode.ip}:${unreachableNode.port} has already been marked as bad`); + } await conversation.updateSwarmNodes(filteredNodes); + return filteredNodes; } markRandomNodeUnreachable(snode) { @@ -106,6 +136,7 @@ class LokiSnodeAPI { this.randomSnodePool, _.find(this.randomSnodePool, { ip: snode.ip, port: snode.port }) ); + return this.randomSnodePool.length; } async updateLastHash(snode, hash, expiresAt) { @@ -150,7 +181,7 @@ class LokiSnodeAPI { try { newSwarmNodes = await this.getSwarmNodes(pubKey); } catch (e) { - log.error('getFreshSwarmNodes error', e.code, e.message); + log.error('loki_snodes: getFreshSwarmNodes error', e.code, e.message); // TODO: Handle these errors sensibly newSwarmNodes = []; } @@ -184,16 +215,25 @@ class LokiSnodeAPI { ); return []; } + if (!result.snodes) { + log.warn( + `getSnodesForPubkey lokiRpc on ${snode.ip}:${ + snode.port + } returned falsish value for snodes`, + result + ); + return []; + } const snodes = result.snodes.filter(tSnode => tSnode.ip !== '0.0.0.0'); return snodes; } catch (e) { + const randomPoolRemainingCount = this.markRandomNodeUnreachable(snode); log.error( - 'getSnodesForPubkey error', + 'loki_snodes: getSnodesForPubkey error', e.code, e.message, - `for ${snode.ip}:${snode.port}` + `for ${snode.ip}:${snode.port}. ${randomPoolRemainingCount} snodes remaining in randomPool` ); - this.markRandomNodeUnreachable(snode); return []; } } From 241e64b94b6796c70b9d9af535101b8e6a6cfda0 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 18 Mar 2020 17:17:46 -0700 Subject: [PATCH 35/59] lint --- js/modules/loki_rpc.js | 38 +++++++++++++++++++-------- js/modules/loki_snode_api.js | 51 ++++++++++++++++++++++++------------ 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index c7a709337..b27bca1a0 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -29,7 +29,7 @@ const decryptResponse = async (response, address) => { return {}; }; -const timeoutDelay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); +const timeoutDelay = ms => new Promise(resolve => setTimeout(resolve, ms)); const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => { const randSnode = await lokiSnodeAPI.getRandomSnodeAddress(); @@ -73,10 +73,14 @@ const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => { // remove // but which the proxy or the target... // we got a ton of randomPool nodes, let's just not worry about this one - const randomPoolRemainingCount = lokiSnodeAPI.markRandomNodeUnreachable(randSnode); + const randomPoolRemainingCount = lokiSnodeAPI.markRandomNodeUnreachable( + randSnode + ); log.warn( `lokiRpc sendToProxy`, - `snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${targetNode.port}`, + `snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${ + targetNode.port + }`, `snode is decom or dereg: `, ciphertext, // `marking random snode bad ${randomPoolRemainingCount} remaining` @@ -95,10 +99,14 @@ const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => { // but the target node // we got a ton of randomPool nodes, let's just not worry about this one - const randomPoolRemainingCount = lokiSnodeAPI.markRandomNodeUnreachable(randSnode); + const randomPoolRemainingCount = lokiSnodeAPI.markRandomNodeUnreachable( + randSnode + ); log.warn( `lokiRpc sendToProxy`, - `snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${targetNode.port}`, + `snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${ + targetNode.port + }`, `code ${response.status} error`, ciphertext, // `marking random snode bad ${randomPoolRemainingCount} remaining` @@ -137,7 +145,9 @@ const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => { log.warn( 'lokiRpc sendToProxy fetch non-200 statusCode', response.status, - `from snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${targetNode.port}` + `from snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${ + targetNode.port + }` ); return false; } @@ -171,7 +181,9 @@ const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => { 'lokiRpc sendToProxy decode error', e.code, e.message, - `from ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${targetNode.port} ciphertext:`, + `from ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${ + targetNode.port + } ciphertext:`, ciphertext ); if (ciphertextBuffer) { @@ -198,9 +210,13 @@ const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => { return false; }; if (retryNumber) { - log.info(`lokiRpc sendToProxy request succeeded,`, - `snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${targetNode.port}`, - `on retry #${retryNumber}`); + log.info( + `lokiRpc sendToProxy request succeeded,`, + `snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${ + targetNode.port + }`, + `on retry #${retryNumber}` + ); } return jsonRes; } catch (e) { @@ -259,7 +275,7 @@ const lokiFetch = async (url, options = {}, targetNode = null) => { fetchOptions.agent = snodeHttpsAgent; process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; } else { - log.info ('lokiRpc http communication', url); + log.info('lokiRpc http communication', url); } const response = await nodeFetch(url, fetchOptions); // restore TLS checking diff --git a/js/modules/loki_snode_api.js b/js/modules/loki_snode_api.js index 450cc03b7..9f3efcb76 100644 --- a/js/modules/loki_snode_api.js +++ b/js/modules/loki_snode_api.js @@ -76,9 +76,17 @@ class LokiSnodeAPI { pubkey_x25519: snode.pubkey_x25519, pubkey_ed25519: snode.pubkey_ed25519, })); - log.info('loki_snodes: Refreshed random snode pool with', this.randomSnodePool.length, 'snodes'); + log.info( + 'loki_snodes: Refreshed random snode pool with', + this.randomSnodePool.length, + 'snodes' + ); } catch (e) { - log.warn('loki_snodes: initialiseRandomPool error', e.code, e.message); + log.warn( + 'loki_snodes: initialiseRandomPool error', + e.code, + e.message + ); if (consecutiveErrors < 3) { // retry after a possible delay setTimeout(() => { @@ -100,7 +108,7 @@ class LokiSnodeAPI { // clear lock this.initialiseRandomPoolPromise = null; resolve(); - }) + }); } await this.initialiseRandomPoolPromise; } @@ -109,23 +117,30 @@ class LokiSnodeAPI { async unreachableNode(pubKey, unreachableNode) { const conversation = ConversationController.get(pubKey); const swarmNodes = [...conversation.get('swarmNodes')]; - if (typeof(unreachableNode) === 'string') { - log.warn('loki_snodes::unreachableNode: String passed as unreachableNode to unreachableNode'); + if (typeof unreachableNode === 'string') { + log.warn( + 'loki_snodes::unreachableNode: String passed as unreachableNode to unreachableNode' + ); return swarmNodes; } - let found = false - const filteredNodes = swarmNodes.filter( - node => { - // keep all but thisNode - const thisNode = (node.address === unreachableNode.address && node.ip === unreachableNode.ip && node.port === unreachableNode.port) - if (thisNode) { - found = true - } - return !thisNode + let found = false; + const filteredNodes = swarmNodes.filter(node => { + // keep all but thisNode + const thisNode = + node.address === unreachableNode.address && + node.ip === unreachableNode.ip && + node.port === unreachableNode.port; + if (thisNode) { + found = true; } - ); + return !thisNode; + }); if (!found) { - log.warn(`loki_snodes::unreachableNode snode ${unreachableNode.ip}:${unreachableNode.port} has already been marked as bad`); + log.warn( + `loki_snodes::unreachableNode snode ${unreachableNode.ip}:${ + unreachableNode.port + } has already been marked as bad` + ); } await conversation.updateSwarmNodes(filteredNodes); return filteredNodes; @@ -232,7 +247,9 @@ class LokiSnodeAPI { 'loki_snodes: getSnodesForPubkey error', e.code, e.message, - `for ${snode.ip}:${snode.port}. ${randomPoolRemainingCount} snodes remaining in randomPool` + `for ${snode.ip}:${ + snode.port + }. ${randomPoolRemainingCount} snodes remaining in randomPool` ); return []; } From 455bfa4ab754c33aef6ff46c2d0719c4fde89fea Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 18 Mar 2020 17:33:36 -0700 Subject: [PATCH 36/59] result guard, mark internal-only intended functions with _ prefix and simplify parameters, logging improvements --- js/modules/loki_message_api.js | 130 +++++++++++++++++++++------------ 1 file changed, 84 insertions(+), 46 deletions(-) diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index 88b02b7af..838bc5ef6 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -94,6 +94,7 @@ class LokiMessageAPI { await this.refreshSendingSwarm(pubKey, timestamp); } + // send parameters const params = { pubKey, ttl: ttl.toString(), @@ -104,7 +105,7 @@ class LokiMessageAPI { const promises = []; let completedConnections = 0; for (let i = 0; i < numConnections; i += 1) { - const connectionPromise = this.openSendConnection(params).finally(() => { + const connectionPromise = this._openSendConnection(params).finally(() => { completedConnections += 1; if (completedConnections >= numConnections) { delete this.sendingData[timestamp]; @@ -151,7 +152,7 @@ class LokiMessageAPI { 'Ran out of swarm nodes to query' ); } - log.info(`Successful storage message to ${pubKey}`); + log.info(`loki_message: Successfully stored message to ${pubKey}`); } async refreshSendingSwarm(pubKey, timestamp) { @@ -162,16 +163,11 @@ class LokiMessageAPI { return true; } - async openSendConnection(params) { + async _openSendConnection(params) { while (!_.isEmpty(this.sendingData[params.timestamp].swarm)) { const snode = this.sendingData[params.timestamp].swarm.shift(); // TODO: Revert back to using snode address instead of IP - const successfulSend = await this.sendToNode( - snode.ip, - snode.port, - snode, - params - ); + const successfulSend = await this._sendToNode(snode, params); if (successfulSend) { return true; } @@ -189,19 +185,19 @@ class LokiMessageAPI { } await this.sendingData[params.timestamp].refreshPromise; // Retry with a fresh list again - return this.openSendConnection(params); + return this._openSendConnection(params); } return false; } - async sendToNode(address, port, targetNode, params) { + async _sendToNode(targetNode, params) { let successiveFailures = 0; while (successiveFailures < MAX_ACCEPTABLE_FAILURES) { await sleepFor(successiveFailures * 500); try { const result = await lokiRpc( - `https://${address}`, - port, + `https://${targetNode.ip}`, + targetNode.port, 'store', params, {}, @@ -211,17 +207,19 @@ class LokiMessageAPI { // Make sure we aren't doing too much PoW const currentDifficulty = window.storage.get('PoWDifficulty', null); - const newDifficulty = result.difficulty; - if (newDifficulty != null && newDifficulty !== currentDifficulty) { - window.storage.put('PoWDifficulty', newDifficulty); - } + if (result && result.difficulty) { + const newDifficulty = result.difficulty; + if (newDifficulty != null && newDifficulty !== currentDifficulty) { + window.storage.put('PoWDifficulty', newDifficulty); + } + } // else should we return false? return true; } catch (e) { log.warn( - 'Loki send message error:', + 'loki_message: send error:', e.code, e.message, - `from ${address}` + `destination ${targetNode.ip}:${targetNode.port}` ); if (e instanceof textsecure.WrongSwarmError) { const { newSwarm } = e; @@ -238,26 +236,35 @@ class LokiMessageAPI { } else if (e instanceof textsecure.NotFoundError) { // TODO: Handle resolution error } else if (e instanceof textsecure.TimestampError) { - log.warn('Timestamp is invalid'); + log.warn('loki_message: Timestamp is invalid'); throw e; } else if (e instanceof textsecure.HTTPError) { // TODO: Handle working connection but error response const body = await e.response.text(); - log.warn('HTTPError body:', body); + log.warn('loki_message: HTTPError body:', body); } successiveFailures += 1; } } - log.error(`Failed to send to node: ${address}`); - await lokiSnodeAPI.unreachableNode(params.pubKey, address); + const remainingSwarmSnodes = await lokiSnodeAPI.unreachableNode( + params.pubKey, + targetNode + ); + log.error( + `loki_message: Too many successive failures trying to send to node ${ + targetNode.ip + }:${targetNode.port}, ${remainingSwarmSnodes.lengt} remaining swarm nodes` + ); return false; } - async openRetrieveConnection(stopPollingPromise, callback) { + async _openRetrieveConnection(stopPollingPromise, callback) { let stopPollingResult = false; + // When message_receiver restarts from onoffline/ononline events it closes // http-resources, which will then resolve the stopPollingPromise with true. We then // want to cancel these polling connections because new ones will be created + // eslint-disable-next-line more/no-then stopPollingPromise.then(result => { stopPollingResult = result; @@ -274,9 +281,13 @@ class LokiMessageAPI { ) { await sleepFor(successiveFailures * 1000); + // TODO: Revert back to using snode address instead of IP try { - // TODO: Revert back to using snode address instead of IP - let messages = await this.retrieveNextMessages(nodeData.ip, nodeData); + // in general, I think we want exceptions to bubble up + // so the user facing UI can report unhandled errors + // except in this case of living inside http-resource pollServer + // because it just restarts more connections... + let messages = await this._retrieveNextMessages(nodeData); // this only tracks retrieval failures // won't include parsing failures... successiveFailures = 0; @@ -296,7 +307,7 @@ class LokiMessageAPI { callback(messages); } catch (e) { log.warn( - 'Loki retrieve messages error:', + 'loki_message: retrieve error:', e.code, e.message, `on ${nodeData.ip}:${nodeData.port}` @@ -324,40 +335,49 @@ class LokiMessageAPI { } } if (successiveFailures >= MAX_ACCEPTABLE_FAILURES) { + const remainingSwarmSnodes = await lokiSnodeAPI.unreachableNode( + this.ourKey, + nodeData + ); log.warn( - `removing ${nodeData.ip}:${ + `loki_message: removing ${nodeData.ip}:${ nodeData.port } from our swarm pool. We have ${ Object.keys(this.ourSwarmNodes).length - } usable swarm nodes left` + } usable swarm nodes left (${ + remainingSwarmSnodes.length + } in local db)` ); - await lokiSnodeAPI.unreachableNode(this.ourKey, address); } } // if not stopPollingResult if (_.isEmpty(this.ourSwarmNodes)) { log.error( - 'We no longer have any swarm nodes available to try in pool, closing retrieve connection' + 'loki_message: We no longer have any swarm nodes available to try in pool, closing retrieve connection' ); return false; } return true; } - async retrieveNextMessages(nodeUrl, nodeData) { + // we don't throw or catch here + // mark private (_ prefix) since no error handling is done here... + async _retrieveNextMessages(nodeData) { const params = { pubKey: this.ourKey, lastHash: nodeData.lastHash || '', }; const options = { timeout: 40000, + ourPubKey: this.ourKey, headers: { [LOKI_LONGPOLL_HEADER]: true, }, }; + // let exceptions bubble up const result = await lokiRpc( - `https://${nodeUrl}`, + `https://${nodeData.ip}`, nodeData.port, 'retrieve', params, @@ -365,34 +385,39 @@ class LokiMessageAPI { '/storage_rpc/v1', nodeData ); + return result.messages || []; } + // we don't throw or catch here async startLongPolling(numConnections, stopPolling, callback) { - log.info('startLongPolling for', numConnections, 'connections'); this.ourSwarmNodes = {}; + // load from local DB let nodes = await lokiSnodeAPI.getSwarmNodesForPubKey(this.ourKey); - log.info('swarmNodes', nodes.length, 'for', this.ourKey); - Object.keys(nodes).forEach(j => { - const node = nodes[j]; - log.info(`${j} ${node.ip}:${node.port}`); - }); if (nodes.length < numConnections) { log.warn( - 'Not enough SwarmNodes for our pubkey in local database, getting current list from blockchain' + 'loki_message: Not enough SwarmNodes for our pubkey in local database, getting current list from blockchain' ); + // load from blockchain nodes = await lokiSnodeAPI.refreshSwarmNodesForPubKey(this.ourKey); if (nodes.length < numConnections) { log.error( - 'Could not get enough SwarmNodes for our pubkey from blockchain' + 'loki_message: Could not get enough SwarmNodes for our pubkey from blockchain' ); } } log.info( - `There are currently ${ - nodes.length - } swarmNodes for pubKey in our local database` + 'loki_message: startLongPolling for', + numConnections, + 'connections. We have swarmNodes', + nodes.length, + 'for', + this.ourKey ); + Object.keys(nodes).forEach(j => { + const node = nodes[j]; + log.info(`loki_message: ${j} ${node.ip}:${node.port}`); + }); for (let i = 0; i < nodes.length; i += 1) { const lastHash = await window.Signal.Data.getLastHashBySnode( @@ -406,15 +431,28 @@ class LokiMessageAPI { const promises = []; + let unresolved = numConnections; for (let i = 0; i < numConnections; i += 1) { - promises.push(this.openRetrieveConnection(stopPolling, callback)); + promises.push( + // eslint-disable-next-line more/no-then + this._openRetrieveConnection(stopPolling, callback).then(() => { + unresolved -= 1; + log.info( + 'loki_message: There are', + unresolved, + 'open retrieve connections left' + ); + }) + ); } // blocks until numConnections snodes in our swarms have been removed from the list // less than numConnections being active is fine, only need to restart if none per Niels 20/02/13 // or if there is network issues (ENOUTFOUND due to lokinet) await Promise.all(promises); - log.error('All our long poll swarm connections have been removed'); + log.error( + 'loki_message: All our long poll swarm connections have been removed' + ); // should we just call ourself again? // no, our caller already handles this... } From 9a01317e969fcb9eb837437f91c36d9110ef244d Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 18 Mar 2020 17:34:02 -0700 Subject: [PATCH 37/59] log error --- js/signal_protocol_store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/signal_protocol_store.js b/js/signal_protocol_store.js index 631433d81..a50a9f86f 100644 --- a/js/signal_protocol_store.js +++ b/js/signal_protocol_store.js @@ -223,7 +223,7 @@ if (item) { return item.value; } - + window.log.error('Could not load identityKey from SignalData'); return undefined; }, async getLocalRegistrationId() { From a00aa7371517b85839c7a044ab68a6b0992e330c Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 18 Mar 2020 17:38:13 -0700 Subject: [PATCH 38/59] disconnect even if it's been stopped before, logging improvements --- libtextsecure/http-resources.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/libtextsecure/http-resources.js b/libtextsecure/http-resources.js index 04de289cd..02d647ef5 100644 --- a/libtextsecure/http-resources.js +++ b/libtextsecure/http-resources.js @@ -84,6 +84,8 @@ }; this.pollServer = async () => { + // bg.connect calls mr connect after storage system is ready + window.log.info('http-resource pollServer start'); // This blocking call will return only when all attempts // at reaching snodes are exhausted or a DNS error occured try { @@ -93,25 +95,30 @@ messages => { connected = true; messages.forEach(message => { - const { data } = message; - this.handleMessage(data); + this.handleMessage(message.data); }); } ); } catch (e) { // we'll try again anyway - window.log.error('http-resource pollServer error', e.code, e.message); + window.log.error( + 'http-resource pollServer error', + e.code, + e.message, + e.stack + ); } + connected = false; if (this.calledStop) { + // don't restart return; } - connected = false; // Exhausted all our snodes urls, trying again later from scratch setTimeout(() => { window.log.info( - `Exhausted all our snodes urls, trying again in ${EXHAUSTED_SNODES_RETRY_DELAY / + `http-resource: Exhausted all our snodes urls, trying again in ${EXHAUSTED_SNODES_RETRY_DELAY / 1000}s from scratch` ); this.pollServer(); From 2d76b1eda9b7ec4c000b458a8dd451a089cbffeb Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 18 Mar 2020 17:38:42 -0700 Subject: [PATCH 39/59] .loki support improvement --- js/modules/loki_public_chat_api.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/js/modules/loki_public_chat_api.js b/js/modules/loki_public_chat_api.js index 89fbc7abf..3852a54cc 100644 --- a/js/modules/loki_public_chat_api.js +++ b/js/modules/loki_public_chat_api.js @@ -27,16 +27,18 @@ class LokiPublicChatFactoryAPI extends EventEmitter { static async validServer(serverUrl) { // test to make sure it's online (and maybe has a valid SSL cert) try { + const url = new URL(serverUrl); // allow .loki (may only need an agent but not sure // until we have a .loki to test with) - if (serverUrl.match(/\.loki$/)) { - process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; - } + process.env.NODE_TLS_REJECT_UNAUTHORIZED = url.host.match(/\.loki$/i) + ? '0' + : '1'; + // FIXME: use proxy when we have open groups that work with proxy await nodeFetch(serverUrl); process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'; // const txt = await res.text(); } catch (e) { - process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; + 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; From 07ce97aa560e803d761506c5002186d8cb1e3fa9 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 18 Mar 2020 17:40:48 -0700 Subject: [PATCH 40/59] serverRequest/sendToProxy refactor, start messagesPollLock implementation, improve logging --- js/modules/loki_app_dot_net_api.js | 600 ++++++++++++++++------------- 1 file changed, 333 insertions(+), 267 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 70ff7e8a1..d26ec16b3 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -14,6 +14,13 @@ const PUBLICCHAT_DELETION_POLL_EVERY = 5 * 1000; // 5s const PUBLICCHAT_MOD_POLL_EVERY = 30 * 1000; // 30s const PUBLICCHAT_MIN_TIME_BETWEEN_DUPLICATE_MESSAGES = 10 * 1000; // 10s +const FILESERVER_HOSTS = [ + 'file-dev.lokinet.org', + 'file.lokinet.org', + 'file-dev.getsession.org', + 'file.getsession.org', +]; + const HOMESERVER_USER_ANNOTATION_TYPE = 'network.loki.messenger.homeserver'; const AVATAR_USER_ANNOTATION_TYPE = 'network.loki.messenger.avatar'; const SETTINGS_CHANNEL_ANNOTATION_TYPE = 'net.patter-app.settings'; @@ -25,6 +32,290 @@ const snodeHttpsAgent = new https.Agent({ rejectUnauthorized: false, }); +const sendToProxy = async ( + srvPubKey, + endpoint, + pFetchOptions, + options = {} +) => { + if (!srvPubKey) { + log.error( + 'loki_app_dot_net: sendToProxy called without a server public key' + ); + return {}; + } + const randSnode = await lokiSnodeAPI.getRandomSnodeAddress(); + const url = `https://${randSnode.ip}:${randSnode.port}/file_proxy`; + + const fetchOptions = pFetchOptions; // make lint happy + // safety issue with file server, just safer to have this + if (fetchOptions.headers === undefined) { + fetchOptions.headers = {}; + } + + const payloadObj = { + body: fetchOptions.body, // might need to b64 if binary... + endpoint, + method: fetchOptions.method, + headers: fetchOptions.headers, + }; + + // from https://github.com/sindresorhus/is-stream/blob/master/index.js + if ( + payloadObj.body && + typeof payloadObj.body === 'object' && + typeof payloadObj.body.pipe === 'function' + ) { + const fData = payloadObj.body.getBuffer(); + const fHeaders = payloadObj.body.getHeaders(); + // update headers for boundary + payloadObj.headers = { ...payloadObj.headers, ...fHeaders }; + // update body with base64 chunk + payloadObj.body = { + fileUpload: fData.toString('base64'), + }; + } + + // convert our payload to binary buffer + const payloadData = Buffer.from( + dcodeIO.ByteBuffer.wrap(JSON.stringify(payloadObj)).toArrayBuffer() + ); + payloadObj.body = false; // free memory + + // make temporary key for this request/response + const ephemeralKey = libsignal.Curve.generateKeyPair(); + + // mix server pub key with our priv key + const symKey = libsignal.Curve.calculateAgreement( + srvPubKey, // server's pubkey + ephemeralKey.privKey // our privkey + ); + + const ivAndCiphertext = await libloki.crypto.DHEncrypt(symKey, payloadData); + + // convert final buffer to base64 + const cipherText64 = dcodeIO.ByteBuffer.wrap(ivAndCiphertext).toString( + 'base64' + ); + + const ephemeralPubKey64 = dcodeIO.ByteBuffer.wrap( + ephemeralKey.pubKey + ).toString('base64'); + + const finalRequestHeader = { + 'X-Loki-File-Server-Ephemeral-Key': ephemeralPubKey64, + }; + + const firstHopOptions = { + method: 'POST', + // not sure why I can't use anything but json... + // text/plain would be preferred... + body: JSON.stringify({ cipherText64 }), + headers: { + 'Content-Type': 'application/json', + 'X-Loki-File-Server-Target': '/loki/v1/secure_rpc', + 'X-Loki-File-Server-Verb': 'POST', + 'X-Loki-File-Server-Headers': JSON.stringify(finalRequestHeader), + }, + // we are talking to a snode... + agent: snodeHttpsAgent, + }; + // weird this doesn't need NODE_TLS_REJECT_UNAUTHORIZED = '0' + const result = await nodeFetch(url, firstHopOptions); + + const txtResponse = await result.text(); + if (txtResponse.match(/^Service node is not ready: not in any swarm/i)) { + // mark snode bad + const randomPoolRemainingCount = lokiSnodeAPI.markRandomNodeUnreachable( + randSnode + ); + log.warn( + `loki_app_dot_net: Marking random snode bad, internet address ${ + randSnode.ip + }:${ + randSnode.port + }. ${randomPoolRemainingCount} snodes remaining in randomPool` + ); + // retry (hopefully with new snode) + // FIXME: max number of retries... + return sendToProxy(srvPubKey, endpoint, fetchOptions); + } + + let response = {}; + try { + response = JSON.parse(txtResponse); + } catch (e) { + log.warn( + `loki_app_dot_net: sendToProxy Could not parse outer JSON [${txtResponse}]`, + endpoint, + 'on', + url + ); + } + + if (response.meta && response.meta.code === 200) { + // convert base64 in response to binary + const ivAndCiphertextResponse = dcodeIO.ByteBuffer.wrap( + response.data, + 'base64' + ).toArrayBuffer(); + const decrypted = await libloki.crypto.DHDecrypt( + symKey, + ivAndCiphertextResponse + ); + const textDecoder = new TextDecoder(); + const respStr = textDecoder.decode(decrypted); + // replace response + try { + response = options.textResponse ? respStr : JSON.parse(respStr); + } catch (e) { + log.warn( + `loki_app_dot_net: sendToProxy Could not parse inner JSON [${respStr}]`, + endpoint, + 'on', + url + ); + } + } else { + log.warn( + 'loki_app_dot_net: file server secure_rpc gave an non-200 response: ', + response, + ` txtResponse[${txtResponse}]`, + endpoint + ); + } + return { result, txtResponse, response }; +}; + +const serverRequest = async (endpoint, options = {}) => { + const { + params = {}, + method, + rawBody, + objBody, + token, + srvPubKey, + forceFreshToken = false, + } = options; + + const url = new URL(endpoint); + if (params) { + url.search = new URLSearchParams(params); + } + const fetchOptions = {}; + const headers = {}; + try { + if (token) { + headers.Authorization = `Bearer ${token}`; + } + if (method) { + fetchOptions.method = method; + } + if (objBody) { + headers['Content-Type'] = 'application/json'; + fetchOptions.body = JSON.stringify(objBody); + } else if (rawBody) { + fetchOptions.body = rawBody; + } + fetchOptions.headers = headers; + + // domain ends in .loki + if (url.host.match(/\.loki$/i)) { + fetchOptions.agent = snodeHttpsAgent; + } + } catch (e) { + log.info('serverRequest set up error:', e.code, e.message); + return { + err: e, + }; + } + + let response; + let result; + let txtResponse; + let mode = 'nodeFetch'; + try { + const host = url.host.toLowerCase(); + // log.info('host', host, FILESERVER_HOSTS); + if ( + window.lokiFeatureFlags.useSnodeProxy && + FILESERVER_HOSTS.includes(host) + ) { + mode = 'sendToProxy'; + // strip trailing slash + const endpointWithQS = ( + url.pathname + (url.search ? '?' + url.search : '') + ).replace(/^\//, ''); + // log.info('endpointWithQS', endpointWithQS) + ({ response, txtResponse, result } = await sendToProxy( + srvPubKey, + endpointWithQS, + fetchOptions, + options + )); + } else { + // disable check for .loki + process.env.NODE_TLS_REJECT_UNAUTHORIZED = url.host.match(/\.loki$/i) + ? '0' + : '1'; + result = await nodeFetch(url, fetchOptions); + // always make sure this check is enabled + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'; + txtResponse = await result.text(); + // cloudflare timeouts (504s) will be html... + response = options.textResponse ? txtResponse : JSON.parse(txtResponse); + } + } catch (e) { + if (txtResponse) { + log.info( + `serverRequest ${mode} error`, + e.code, + e.message, + `json: ${txtResponse}`, + 'attempting connection to', + url + ); + } else { + log.info( + `serverRequest ${mode} error`, + e.code, + e.message, + 'attempting connection to', + url + ); + } + if (mode === '_sendToProxy') { + // if we can detect, certain types of failures, we can retry... + if (e.code === 'ECONNRESET') { + // retry with counter? + } + } + return { + err: e, + }; + } + // if it's a response style with a meta + if (result.status !== 200) { + if (!forceFreshToken && (!response.meta || response.meta.code === 401)) { + // copy options because lint complains if we modify this directly + const updatedOptions = options; + // force it this time + updatedOptions.forceFreshToken = true; + // retry with updated options + return this.serverRequest(endpoint, updatedOptions); + } + return { + err: 'statusCode', + statusCode: result.status, + response, + }; + } + return { + statusCode: result.status, + response, + }; +}; + // the core ADN class that handles all communication with a specific server class LokiAppDotNetServerAPI { constructor(ourKey, url) { @@ -394,276 +685,19 @@ class LokiAppDotNetServerAPI { if (urlStr.match(/\.loki\//)) { process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; } - const result = await nodeFetch(urlObj, fetchOptions, options); - process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; + const result = nodeFetch(urlObj, fetchOptions, options); + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'; return result; } - async _sendToProxy(endpoint, pFetchOptions, options = {}) { - const randSnode = await lokiSnodeAPI.getRandomSnodeAddress(); - const url = `https://${randSnode.ip}:${randSnode.port}/file_proxy`; - - const fetchOptions = pFetchOptions; // make lint happy - // safety issue with file server, just safer to have this - if (fetchOptions.headers === undefined) { - fetchOptions.headers = {}; - } - - const payloadObj = { - body: fetchOptions.body, // might need to b64 if binary... - endpoint, - method: fetchOptions.method, - headers: fetchOptions.headers, - }; - - // from https://github.com/sindresorhus/is-stream/blob/master/index.js - if ( - payloadObj.body && - typeof payloadObj.body === 'object' && - typeof payloadObj.body.pipe === 'function' - ) { - log.info('detected body is a stream'); - const fData = payloadObj.body.getBuffer(); - const fHeaders = payloadObj.body.getHeaders(); - // update headers for boundary - payloadObj.headers = { ...payloadObj.headers, ...fHeaders }; - // update body with base64 chunk - payloadObj.body = { - fileUpload: fData.toString('base64'), - }; - } - - // convert our payload to binary buffer - const payloadData = Buffer.from( - dcodeIO.ByteBuffer.wrap(JSON.stringify(payloadObj)).toArrayBuffer() - ); - payloadObj.body = false; // free memory - - // make temporary key for this request/response - const ephemeralKey = libsignal.Curve.generateKeyPair(); - - // mix server pub key with our priv key - const symKey = libsignal.Curve.calculateAgreement( - this.pubKey, // server's pubkey - ephemeralKey.privKey // our privkey - ); - - const ivAndCiphertext = await libloki.crypto.DHEncrypt(symKey, payloadData); - - // convert final buffer to base64 - const cipherText64 = dcodeIO.ByteBuffer.wrap(ivAndCiphertext).toString( - 'base64' - ); - - const ephemeralPubKey64 = dcodeIO.ByteBuffer.wrap( - ephemeralKey.pubKey - ).toString('base64'); - - const finalRequestHeader = { - 'X-Loki-File-Server-Ephemeral-Key': ephemeralPubKey64, - }; - - const firstHopOptions = { - method: 'POST', - // not sure why I can't use anything but json... - // text/plain would be preferred... - body: JSON.stringify({ cipherText64 }), - headers: { - 'Content-Type': 'application/json', - 'X-Loki-File-Server-Target': '/loki/v1/secure_rpc', - 'X-Loki-File-Server-Verb': 'POST', - 'X-Loki-File-Server-Headers': JSON.stringify(finalRequestHeader), - }, - // we are talking to a snode... - agent: snodeHttpsAgent, - }; - // weird this doesn't need NODE_TLS_REJECT_UNAUTHORIZED = 0 - const result = await nodeFetch(url, firstHopOptions); - - const txtResponse = await result.text(); - if (txtResponse.match(/^Service node is not ready: not in any swarm/i)) { - // mark snode bad - log.warn( - `Marking random snode bad, internet address ${randSnode.ip}:${ - randSnode.port - }` - ); - 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}]`, - endpoint - ); - } - - if (response.meta && response.meta.code === 200) { - // convert base64 in response to binary - const ivAndCiphertextResponse = dcodeIO.ByteBuffer.wrap( - response.data, - 'base64' - ).toArrayBuffer(); - const decrypted = await libloki.crypto.DHDecrypt( - symKey, - ivAndCiphertextResponse - ); - const textDecoder = new TextDecoder(); - const respStr = textDecoder.decode(decrypted); - // replace response - try { - response = options.textResponse ? respStr : JSON.parse(respStr); - } catch (e) { - log.warn( - `_sendToProxy Could not parse inner JSON [${respStr}]`, - endpoint - ); - } - } else { - log.warn( - 'file server secure_rpc gave an non-200 response: ', - response, - ` txtResponse[${txtResponse}]`, - endpoint - ); - } - return { result, txtResponse, response }; - } - // make a request to the server async serverRequest(endpoint, options = {}) { - const { - params = {}, - method, - rawBody, - objBody, - forceFreshToken = false, - } = options; - - const url = new URL(`${this.baseServerUrl}/${endpoint}`); - if (params) { - url.search = new URLSearchParams(params); - } - const fetchOptions = {}; - const headers = {}; - try { - if (forceFreshToken) { - await this.getOrRefreshServerToken(true); - } - if (this.token) { - headers.Authorization = `Bearer ${this.token}`; - } - if (method) { - fetchOptions.method = method; - } - if (objBody) { - headers['Content-Type'] = 'application/json'; - fetchOptions.body = JSON.stringify(objBody); - } else if (rawBody) { - fetchOptions.body = rawBody; - } - fetchOptions.headers = headers; - - // domain ends in .loki - if (url.toString().match(/\.loki\//)) { - fetchOptions.agent = snodeHttpsAgent; - } - } catch (e) { - log.info('serverRequest set up error:', e.code, e.message); - return { - err: e, - }; - } - - let response; - let result; - let txtResponse; - let mode = 'nodeFetch'; - try { - 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'; - - const endpointWithQS = url - .toString() - .replace(`${this.baseServerUrl}/`, ''); - ({ response, txtResponse, result } = await this._sendToProxy( - endpointWithQS, - fetchOptions, - options - )); - } else { - // disable check for .loki - if (url.toString().match(/\.loki/)) { - process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; - } - result = await nodeFetch(url, fetchOptions); - // always make sure this check is enabled - process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; - txtResponse = await result.text(); - // hrm cloudflare timeouts (504s) will be html... - response = options.textResponse ? txtResponse : JSON.parse(txtResponse); - } - } catch (e) { - if (txtResponse) { - log.info( - `serverRequest ${mode} error`, - e.code, - e.message, - `json: ${txtResponse}`, - 'attempting connection to', - url - ); - } else { - log.info( - `serverRequest ${mode} error`, - e.code, - e.message, - 'attempting connection to', - url - ); - } - if (mode === '_sendToProxy') { - // if we can detect, certain types of failures, we can retry... - if (e.code === 'ECONNRESET') { - // retry with counter? - } - } - return { - err: e, - }; + options.token = this.token; + options.srvPubKey = this.pubKey; + if (options.forceFreshToken) { + await this.getOrRefreshServerToken(true); } - // if it's a response style with a meta - if (result.status !== 200) { - if (!forceFreshToken && (!response.meta || response.meta.code === 401)) { - // copy options because lint complains if we modify this directly - const updatedOptions = options; - // force it this time - updatedOptions.forceFreshToken = true; - // retry with updated options - return this.serverRequest(endpoint, updatedOptions); - } - return { - err: 'statusCode', - statusCode: result.status, - response, - }; - } - return { - statusCode: result.status, - response, - }; + return serverRequest(`${this.baseServerUrl}/${endpoint}`, options); } async getUserAnnotations(pubKey) { @@ -946,6 +980,8 @@ class LokiPublicChannelAPI { this.deleteLastId = 1; this.timers = {}; this.myPrivateKey = false; + this.messagesPollLock = false; + // can escalated to SQL if it start uses too much memory this.logMop = {}; @@ -1322,7 +1358,6 @@ class LokiPublicChannelAPI { Conversation: Whisper.Conversation, } ); - await this.pollForChannelOnce(); } // get moderation actions @@ -1523,6 +1558,20 @@ class LokiPublicChannelAPI { } async pollOnceForMessages() { + if (this.messagesPollLock) { + // TODO: check if lock is stale + log.warn( + 'pollOnceForModerators locked', + 'on', + this.channelId, + 'at', + this.serverAPI.baseServerUrl + ); + return; + } + // disable locking system for now as it's not quite perfect yet + // this.messagesPollLock = Date.now(); + const params = { include_annotations: 1, include_user_annotations: 1, // to get the home server @@ -1551,6 +1600,7 @@ class LokiPublicChannelAPI { if (res.err) { log.error('pollOnceForMessages receive error', res.err); } + this.messagesPollLock = false; return; } @@ -1706,6 +1756,7 @@ class LokiPublicChannelAPI { // do we really need this? if (!pendingMessages.length) { this.conversation.setLastRetrievedMessage(this.lastGot); + this.messagesPollLock = false; return; } @@ -1755,7 +1806,14 @@ class LokiPublicChannelAPI { ); sendNow.forEach(message => { // send them out now - log.info('emitting primary message', message.serverId); + log.info( + 'emitting primary message', + message.serverId, + 'on', + this.channelId, + 'at', + this.serverAPI.baseServerUrl + ); this.chatAPI.emit('publicMessage', { message, }); @@ -1832,7 +1890,14 @@ class LokiPublicChannelAPI { return; } } - log.info('emitting pending message', message.serverId); + log.info( + 'emitting pending message', + message.serverId, + 'on', + this.channelId, + 'at', + this.serverAPI.baseServerUrl + ); this.chatAPI.emit('publicMessage', { message, }); @@ -1854,6 +1919,7 @@ class LokiPublicChannelAPI { // finally update our position this.conversation.setLastRetrievedMessage(this.lastGot); + this.messagesPollLock = false; } static getPreviewFromAnnotation(annotation) { From e66e30bb0b3f374a394a5b662b8dc6f0c8520028 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 18 Mar 2020 17:52:09 -0700 Subject: [PATCH 41/59] URL for lint --- js/modules/loki_public_chat_api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/modules/loki_public_chat_api.js b/js/modules/loki_public_chat_api.js index 3852a54cc..a92a7b31d 100644 --- a/js/modules/loki_public_chat_api.js +++ b/js/modules/loki_public_chat_api.js @@ -1,4 +1,4 @@ -/* global log, window, process */ +/* global log, window, process, URL */ const EventEmitter = require('events'); const nodeFetch = require('node-fetch'); const LokiAppDotNetAPI = require('./loki_app_dot_net_api'); From 8d4be4cb0b4e69da9c2b9e79dd0865170fed0145 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 18 Mar 2020 17:52:25 -0700 Subject: [PATCH 42/59] lint --- js/modules/loki_app_dot_net_api.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index d26ec16b3..2f70a9a64 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -243,9 +243,8 @@ const serverRequest = async (endpoint, options = {}) => { ) { mode = 'sendToProxy'; // strip trailing slash - const endpointWithQS = ( - url.pathname + (url.search ? '?' + url.search : '') - ).replace(/^\//, ''); + const search = url.search ? `?${url.search}` : ''; + const endpointWithQS = `${url.pathname}${search}`.replace(/^\//, ''); // log.info('endpointWithQS', endpointWithQS) ({ response, txtResponse, result } = await sendToProxy( srvPubKey, @@ -691,7 +690,8 @@ class LokiAppDotNetServerAPI { } // make a request to the server - async serverRequest(endpoint, options = {}) { + async serverRequest(endpoint, pOptions = {}) { + const options = pOptions; options.token = this.token; options.srvPubKey = this.pubKey; if (options.forceFreshToken) { From 6bdc269081fa69783c436cc2693b304bbb8f7b44 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Thu, 19 Mar 2020 14:22:19 -0700 Subject: [PATCH 43/59] include function name in log message --- js/modules/loki_message_api.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index 838bc5ef6..a94f8a605 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -152,7 +152,7 @@ class LokiMessageAPI { 'Ran out of swarm nodes to query' ); } - log.info(`loki_message: Successfully stored message to ${pubKey}`); + log.info(`loki_message:::sendMessage - Successfully stored message to ${pubKey}`); } async refreshSendingSwarm(pubKey, timestamp) { @@ -216,7 +216,7 @@ class LokiMessageAPI { return true; } catch (e) { log.warn( - 'loki_message: send error:', + 'loki_message:::_sendToNode - send error:', e.code, e.message, `destination ${targetNode.ip}:${targetNode.port}` @@ -236,12 +236,12 @@ class LokiMessageAPI { } else if (e instanceof textsecure.NotFoundError) { // TODO: Handle resolution error } else if (e instanceof textsecure.TimestampError) { - log.warn('loki_message: Timestamp is invalid'); + log.warn('loki_message:::_sendToNode - Timestamp is invalid'); throw e; } else if (e instanceof textsecure.HTTPError) { // TODO: Handle working connection but error response const body = await e.response.text(); - log.warn('loki_message: HTTPError body:', body); + log.warn('loki_message:::_sendToNode - HTTPError body:', body); } successiveFailures += 1; } @@ -251,7 +251,7 @@ class LokiMessageAPI { targetNode ); log.error( - `loki_message: Too many successive failures trying to send to node ${ + `loki_message:::_sendToNode - Too many successive failures trying to send to node ${ targetNode.ip }:${targetNode.port}, ${remainingSwarmSnodes.lengt} remaining swarm nodes` ); @@ -307,7 +307,7 @@ class LokiMessageAPI { callback(messages); } catch (e) { log.warn( - 'loki_message: retrieve error:', + 'loki_message:::_openRetrieveConnection - retrieve error:', e.code, e.message, `on ${nodeData.ip}:${nodeData.port}` @@ -340,7 +340,7 @@ class LokiMessageAPI { nodeData ); log.warn( - `loki_message: removing ${nodeData.ip}:${ + `loki_message:::_openRetrieveConnection - too many successive failures, removing ${nodeData.ip}:${ nodeData.port } from our swarm pool. We have ${ Object.keys(this.ourSwarmNodes).length @@ -353,7 +353,7 @@ class LokiMessageAPI { // if not stopPollingResult if (_.isEmpty(this.ourSwarmNodes)) { log.error( - 'loki_message: We no longer have any swarm nodes available to try in pool, closing retrieve connection' + 'loki_message:::_openRetrieveConnection - We no longer have any swarm nodes available to try in pool, closing retrieve connection' ); return false; } @@ -396,18 +396,18 @@ class LokiMessageAPI { let nodes = await lokiSnodeAPI.getSwarmNodesForPubKey(this.ourKey); if (nodes.length < numConnections) { log.warn( - 'loki_message: Not enough SwarmNodes for our pubkey in local database, getting current list from blockchain' + 'loki_message:::startLongPolling - Not enough SwarmNodes for our pubkey in local database, getting current list from blockchain' ); // load from blockchain nodes = await lokiSnodeAPI.refreshSwarmNodesForPubKey(this.ourKey); if (nodes.length < numConnections) { log.error( - 'loki_message: Could not get enough SwarmNodes for our pubkey from blockchain' + 'loki_message:::startLongPolling - Could not get enough SwarmNodes for our pubkey from blockchain' ); } } log.info( - 'loki_message: startLongPolling for', + 'loki_message:::startLongPolling - start polling for', numConnections, 'connections. We have swarmNodes', nodes.length, @@ -438,7 +438,7 @@ class LokiMessageAPI { this._openRetrieveConnection(stopPolling, callback).then(() => { unresolved -= 1; log.info( - 'loki_message: There are', + 'loki_message:::startLongPolling - There are', unresolved, 'open retrieve connections left' ); @@ -451,7 +451,7 @@ class LokiMessageAPI { // or if there is network issues (ENOUTFOUND due to lokinet) await Promise.all(promises); log.error( - 'loki_message: All our long poll swarm connections have been removed' + 'loki_message:::startLongPolling - All our long poll swarm connections have been removed' ); // should we just call ourself again? // no, our caller already handles this... From 99679741c148d1ce653289fddf344bb27cb9fb87 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Thu, 19 Mar 2020 14:23:23 -0700 Subject: [PATCH 44/59] some renames, improve refresh random pool failures better --- js/modules/loki_snode_api.js | 192 ++++++++++++++++++++--------------- 1 file changed, 110 insertions(+), 82 deletions(-) diff --git a/js/modules/loki_snode_api.js b/js/modules/loki_snode_api.js index 9f3efcb76..1b78cd6a3 100644 --- a/js/modules/loki_snode_api.js +++ b/js/modules/loki_snode_api.js @@ -1,10 +1,12 @@ /* eslint-disable class-methods-use-this */ -/* global window, ConversationController, _, log */ +/* global window, ConversationController, _, log, clearTimeout */ const is = require('@sindresorhus/is'); const { lokiRpc } = require('./loki_rpc'); -const RANDOM_SNODES_TO_USE = 3; +const RANDOM_SNODES_TO_USE_FOR_PUBKEY_SWARM = 3; +const RANDOM_SNODES_POOL_SIZE = 1024; +const SEED_NODE_RETRIES = 3; class LokiSnodeAPI { constructor({ serverUrl, localUrl }) { @@ -15,14 +17,14 @@ class LokiSnodeAPI { this.localUrl = localUrl; // localhost.loki this.randomSnodePool = []; this.swarmsPendingReplenish = {}; - this.initialiseRandomPoolPromise = false; + this.refreshRandomPoolPromise = false; } async getRandomSnodeAddress() { /* resolve random snode */ if (this.randomSnodePool.length === 0) { // allow exceptions to pass through upwards - await this.initialiseRandomPool(); + await this.refreshRandomPool(); } if (this.randomSnodePool.length === 0) { throw new window.textsecure.SeedNodeError('Invalid seed node response'); @@ -32,85 +34,110 @@ class LokiSnodeAPI { ]; } - async initialiseRandomPool( - seedNodes = [...window.seedNodeList], - consecutiveErrors = 0 + + async refreshRandomPool( + seedNodes = [...window.seedNodeList] ) { // if currently not in progress - if (this.initialiseRandomPoolPromise === false) { - // FIXME: add timeout + if (this.refreshRandomPoolPromise === false) { // set lock - this.initialiseRandomPoolPromise = new Promise(async resolve => { - const params = { - limit: 1024, - active_only: true, - fields: { - public_ip: true, - storage_port: true, - pubkey_x25519: true, - pubkey_ed25519: true, - }, - }; - const seedNode = seedNodes.splice( - Math.floor(Math.random() * seedNodes.length), - 1 - )[0]; - let snodes = []; - try { - log.info('loki_snodes: Refreshing random snode pool'); - const response = await lokiRpc( - `http://${seedNode.ip}`, - seedNode.port, - 'get_n_service_nodes', - params, - {}, // Options - '/json_rpc' // Seed request endpoint - ); - // Filter 0.0.0.0 nodes which haven't submitted uptime proofs - snodes = response.result.service_node_states.filter( - snode => snode.public_ip !== '0.0.0.0' - ); - this.randomSnodePool = snodes.map(snode => ({ - ip: snode.public_ip, - port: snode.storage_port, - pubkey_x25519: snode.pubkey_x25519, - pubkey_ed25519: snode.pubkey_ed25519, - })); - log.info( - 'loki_snodes: Refreshed random snode pool with', - this.randomSnodePool.length, - 'snodes' - ); - } catch (e) { - log.warn( - 'loki_snodes: initialiseRandomPool error', - e.code, - e.message - ); - if (consecutiveErrors < 3) { - // retry after a possible delay - setTimeout(() => { - log.info( - 'loki_snodes: Retrying initialising random snode pool, try #', - consecutiveErrors - ); - this.initialiseRandomPool(seedNodes, consecutiveErrors + 1); - }, consecutiveErrors * consecutiveErrors * 5000); - } else { - log.error('loki_snodes: Giving up trying to contact seed node'); - if (snodes.length === 0) { - throw new window.textsecure.SeedNodeError( - 'Failed to contact seed node' - ); + this.refreshRandomPoolPromise = new Promise(async (resolve, reject) => { + let timeoutTimer = null + // private retry container + const trySeedNode = async (consecutiveErrors = 0) => { + const params = { + limit: RANDOM_SNODES_POOL_SIZE, + active_only: true, + fields: { + public_ip: true, + storage_port: true, + pubkey_x25519: true, + pubkey_ed25519: true, + }, + }; + const seedNode = seedNodes.splice( + Math.floor(Math.random() * seedNodes.length), + 1 + )[0]; + let snodes = []; + try { + log.info('loki_snodes:::refreshRandomPoolPromise - Refreshing random snode pool'); + const response = await lokiRpc( + `http://${seedNode.ip}`, + seedNode.port, + 'get_n_service_nodes', + params, + {}, // Options + '/json_rpc' // Seed request endpoint + ); + // Filter 0.0.0.0 nodes which haven't submitted uptime proofs + snodes = response.result.service_node_states.filter( + snode => snode.public_ip !== '0.0.0.0' + ); + this.randomSnodePool = snodes.map(snode => ({ + ip: snode.public_ip, + port: snode.storage_port, + pubkey_x25519: snode.pubkey_x25519, + pubkey_ed25519: snode.pubkey_ed25519, + })); + log.info( + 'loki_snodes:::refreshRandomPoolPromise - Refreshed random snode pool with', + this.randomSnodePool.length, + 'snodes' + ); + // clear lock + this.refreshRandomPoolPromise = null; + if (timeoutTimer !== null) { + clearTimeout(timeoutTimer); + timeoutTimer = null; + } + resolve(); + } catch (e) { + log.warn( + 'loki_snodes:::refreshRandomPoolPromise - error', + e.code, + e.message + ); + if (consecutiveErrors < SEED_NODE_RETRIES) { + // retry after a possible delay + setTimeout(() => { + log.info( + 'loki_snodes:::refreshRandomPoolPromise - Retrying initialising random snode pool, try #', + consecutiveErrors + ); + trySeedNode(consecutiveErrors + 1); + }, consecutiveErrors * consecutiveErrors * 5000); + } else { + log.error('loki_snodes:::refreshRandomPoolPromise - Giving up trying to contact seed node'); + if (snodes.length === 0) { + this.refreshRandomPoolPromise = null; // clear lock + if (timeoutTimer !== null) { + clearTimeout(timeoutTimer); + timeoutTimer = null; + } + reject() + } } } } - // clear lock - this.initialiseRandomPoolPromise = null; - resolve(); + const delay = (SEED_NODE_RETRIES + 1) * (SEED_NODE_RETRIES + 1) * 5000; + timeoutTimer = setTimeout(() => { + log.warn('loki_snodes:::refreshRandomPoolPromise - TIMEDOUT after', delay, 's'); + reject(); + }, delay); + trySeedNode() }); } - await this.initialiseRandomPoolPromise; + try { + await this.refreshRandomPoolPromise; + } catch(e) { + // we will throw for each time initialiseRandomPool has been called in parallel + log.error('loki_snodes:::refreshRandomPoolPromise - error', e.code, e.message); + throw new window.textsecure.SeedNodeError( + 'Failed to contact seed node' + ); + } + log.info('loki_snodes:::refreshRandomPoolPromise - RESOLVED') } // unreachableNode.url is like 9hrje1bymy7hu6nmtjme9idyu3rm8gr3mkstakjyuw1997t7w4ny.snode @@ -119,7 +146,7 @@ class LokiSnodeAPI { const swarmNodes = [...conversation.get('swarmNodes')]; if (typeof unreachableNode === 'string') { log.warn( - 'loki_snodes::unreachableNode: String passed as unreachableNode to unreachableNode' + 'loki_snodes:::unreachableNode - String passed as unreachableNode to unreachableNode' ); return swarmNodes; } @@ -137,7 +164,7 @@ class LokiSnodeAPI { }); if (!found) { log.warn( - `loki_snodes::unreachableNode snode ${unreachableNode.ip}:${ + `loki_snodes:::unreachableNode - snode ${unreachableNode.ip}:${ unreachableNode.port } has already been marked as bad` ); @@ -196,7 +223,7 @@ class LokiSnodeAPI { try { newSwarmNodes = await this.getSwarmNodes(pubKey); } catch (e) { - log.error('loki_snodes: getFreshSwarmNodes error', e.code, e.message); + log.error('loki_snodes:::getFreshSwarmNodes - error', e.code, e.message); // TODO: Handle these errors sensibly newSwarmNodes = []; } @@ -223,7 +250,7 @@ class LokiSnodeAPI { ); if (!result) { log.warn( - `getSnodesForPubkey lokiRpc on ${snode.ip}:${ + `loki_snode:::getSnodesForPubkey - lokiRpc on ${snode.ip}:${ snode.port } returned falsish value`, result @@ -231,8 +258,9 @@ class LokiSnodeAPI { return []; } if (!result.snodes) { + // we hit this when snode gives 500s log.warn( - `getSnodesForPubkey lokiRpc on ${snode.ip}:${ + `loki_snode:::getSnodesForPubkey - lokiRpc on ${snode.ip}:${ snode.port } returned falsish value for snodes`, result @@ -244,7 +272,7 @@ class LokiSnodeAPI { } catch (e) { const randomPoolRemainingCount = this.markRandomNodeUnreachable(snode); log.error( - 'loki_snodes: getSnodesForPubkey error', + 'loki_snodes:::getSnodesForPubkey - error', e.code, e.message, `for ${snode.ip}:${ @@ -257,7 +285,7 @@ class LokiSnodeAPI { async getSwarmNodes(pubKey) { const snodes = []; - const questions = [...Array(RANDOM_SNODES_TO_USE).keys()]; + const questions = [...Array(RANDOM_SNODES_TO_USE_FOR_PUBKEY_SWARM).keys()]; await Promise.all( questions.map(async () => { // allow exceptions to pass through upwards From 4bb2e83cb3d9eab00b314e499d80bd2e44865384 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Thu, 19 Mar 2020 15:04:36 -0700 Subject: [PATCH 45/59] lint --- js/modules/loki_message_api.js | 10 +++++--- js/modules/loki_snode_api.js | 45 +++++++++++++++++++++------------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index a94f8a605..3035fc7a9 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -152,7 +152,9 @@ class LokiMessageAPI { 'Ran out of swarm nodes to query' ); } - log.info(`loki_message:::sendMessage - Successfully stored message to ${pubKey}`); + log.info( + `loki_message:::sendMessage - Successfully stored message to ${pubKey}` + ); } async refreshSendingSwarm(pubKey, timestamp) { @@ -340,9 +342,9 @@ class LokiMessageAPI { nodeData ); log.warn( - `loki_message:::_openRetrieveConnection - too many successive failures, removing ${nodeData.ip}:${ - nodeData.port - } from our swarm pool. We have ${ + `loki_message:::_openRetrieveConnection - too many successive failures, removing ${ + nodeData.ip + }:${nodeData.port} from our swarm pool. We have ${ Object.keys(this.ourSwarmNodes).length } usable swarm nodes left (${ remainingSwarmSnodes.length diff --git a/js/modules/loki_snode_api.js b/js/modules/loki_snode_api.js index 1b78cd6a3..061ecf2de 100644 --- a/js/modules/loki_snode_api.js +++ b/js/modules/loki_snode_api.js @@ -34,15 +34,12 @@ class LokiSnodeAPI { ]; } - - async refreshRandomPool( - seedNodes = [...window.seedNodeList] - ) { + async refreshRandomPool(seedNodes = [...window.seedNodeList]) { // if currently not in progress if (this.refreshRandomPoolPromise === false) { // set lock this.refreshRandomPoolPromise = new Promise(async (resolve, reject) => { - let timeoutTimer = null + let timeoutTimer = null; // private retry container const trySeedNode = async (consecutiveErrors = 0) => { const params = { @@ -61,7 +58,9 @@ class LokiSnodeAPI { )[0]; let snodes = []; try { - log.info('loki_snodes:::refreshRandomPoolPromise - Refreshing random snode pool'); + log.info( + 'loki_snodes:::refreshRandomPoolPromise - Refreshing random snode pool' + ); const response = await lokiRpc( `http://${seedNode.ip}`, seedNode.port, @@ -108,36 +107,44 @@ class LokiSnodeAPI { trySeedNode(consecutiveErrors + 1); }, consecutiveErrors * consecutiveErrors * 5000); } else { - log.error('loki_snodes:::refreshRandomPoolPromise - Giving up trying to contact seed node'); + log.error( + 'loki_snodes:::refreshRandomPoolPromise - Giving up trying to contact seed node' + ); if (snodes.length === 0) { this.refreshRandomPoolPromise = null; // clear lock if (timeoutTimer !== null) { clearTimeout(timeoutTimer); timeoutTimer = null; } - reject() + reject(); } } } - } + }; const delay = (SEED_NODE_RETRIES + 1) * (SEED_NODE_RETRIES + 1) * 5000; timeoutTimer = setTimeout(() => { - log.warn('loki_snodes:::refreshRandomPoolPromise - TIMEDOUT after', delay, 's'); + log.warn( + 'loki_snodes:::refreshRandomPoolPromise - TIMEDOUT after', + delay, + 's' + ); reject(); }, delay); - trySeedNode() + trySeedNode(); }); } try { await this.refreshRandomPoolPromise; - } catch(e) { + } catch (e) { // we will throw for each time initialiseRandomPool has been called in parallel - log.error('loki_snodes:::refreshRandomPoolPromise - error', e.code, e.message); - throw new window.textsecure.SeedNodeError( - 'Failed to contact seed node' + log.error( + 'loki_snodes:::refreshRandomPoolPromise - error', + e.code, + e.message ); + throw new window.textsecure.SeedNodeError('Failed to contact seed node'); } - log.info('loki_snodes:::refreshRandomPoolPromise - RESOLVED') + log.info('loki_snodes:::refreshRandomPoolPromise - RESOLVED'); } // unreachableNode.url is like 9hrje1bymy7hu6nmtjme9idyu3rm8gr3mkstakjyuw1997t7w4ny.snode @@ -223,7 +230,11 @@ class LokiSnodeAPI { try { newSwarmNodes = await this.getSwarmNodes(pubKey); } catch (e) { - log.error('loki_snodes:::getFreshSwarmNodes - error', e.code, e.message); + log.error( + 'loki_snodes:::getFreshSwarmNodes - error', + e.code, + e.message + ); // TODO: Handle these errors sensibly newSwarmNodes = []; } From 67f94334c2c5ba755671804c8a19e1f7268f52d9 Mon Sep 17 00:00:00 2001 From: Vincent Date: Fri, 20 Mar 2020 10:25:15 +1100 Subject: [PATCH 46/59] hardcoded --- main.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.js b/main.js index 06ffcffd1..43bcc7e89 100644 --- a/main.js +++ b/main.js @@ -27,7 +27,8 @@ const { shell, } = electron; -const appUserModelId = packageJson.build.appId; +//const appUserModelId = packageJson.build.appId; +const appUserModelId = 'com.loki-project.messenger-desktop'; console.log('Set Windows Application User Model ID (AUMID)', { appUserModelId, }); From 458254d6b178522c830c7f749dfefe46ccefec5d Mon Sep 17 00:00:00 2001 From: Vince Date: Fri, 20 Mar 2020 10:28:59 +1100 Subject: [PATCH 47/59] Update main.js --- main.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.js b/main.js index 43bcc7e89..e0a54cf45 100644 --- a/main.js +++ b/main.js @@ -27,7 +27,8 @@ const { shell, } = electron; -//const appUserModelId = packageJson.build.appId; +// FIXME Hardcoding appId to prevent build failrues on release. +// const appUserModelId = packageJson.build.appId; const appUserModelId = 'com.loki-project.messenger-desktop'; console.log('Set Windows Application User Model ID (AUMID)', { appUserModelId, From a7def718403c1ddcb42f34bcc33ea7496f48205f Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 20 Mar 2020 14:28:32 +1100 Subject: [PATCH 48/59] multi device: do not sync contacts not friends --- libloki/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libloki/api.js b/libloki/api.js index b18746ef8..6456e2688 100644 --- a/libloki/api.js +++ b/libloki/api.js @@ -110,7 +110,7 @@ async function createContactSyncProtoMessage(conversations) { // Extract required contacts information out of conversations const sessionContacts = conversations.filter( - c => c.isPrivate() && !c.isSecondaryDevice() + c => c.isPrivate() && !c.isSecondaryDevice() && c.isFriend() ); if (sessionContacts.length === 0) { From d15445a1e07c5cb1204482d89b512024866a90e7 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Thu, 19 Mar 2020 21:09:42 -0700 Subject: [PATCH 49/59] don't create unneeded variables --- js/modules/loki_app_dot_net_api.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 2f70a9a64..fe2813df2 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -296,12 +296,11 @@ const serverRequest = async (endpoint, options = {}) => { // if it's a response style with a meta if (result.status !== 200) { if (!forceFreshToken && (!response.meta || response.meta.code === 401)) { - // copy options because lint complains if we modify this directly - const updatedOptions = options; - // force it this time - updatedOptions.forceFreshToken = true; - // retry with updated options - return this.serverRequest(endpoint, updatedOptions); + // retry with forcing a fresh token + return this.serverRequest(endpoint, { + ...options, + forceFreshToken: true, + }); } return { err: 'statusCode', @@ -690,14 +689,15 @@ class LokiAppDotNetServerAPI { } // make a request to the server - async serverRequest(endpoint, pOptions = {}) { - const options = pOptions; - options.token = this.token; - options.srvPubKey = this.pubKey; + async serverRequest(endpoint, options = {}) { if (options.forceFreshToken) { await this.getOrRefreshServerToken(true); } - return serverRequest(`${this.baseServerUrl}/${endpoint}`, options); + return serverRequest(`${this.baseServerUrl}/${endpoint}`, { + ...options, + token: this.token, + srvPubKey: this.pubKey, + }); } async getUserAnnotations(pubKey) { From 8bf77ce44f27b9f9efb179467ca7b7b6a43b3d40 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Thu, 19 Mar 2020 21:10:01 -0700 Subject: [PATCH 50/59] collapse difficulty branching --- js/modules/loki_message_api.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index 3035fc7a9..c3ccf6ae8 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -209,12 +209,14 @@ class LokiMessageAPI { // Make sure we aren't doing too much PoW const currentDifficulty = window.storage.get('PoWDifficulty', null); - if (result && result.difficulty) { - const newDifficulty = result.difficulty; - if (newDifficulty != null && newDifficulty !== currentDifficulty) { - window.storage.put('PoWDifficulty', newDifficulty); - } - } // else should we return false? + if ( + result && + result.difficulty && + result.difficulty !== currentDifficulty + ) { + window.storage.put('PoWDifficulty', result.difficulty); + // should we return false? + } return true; } catch (e) { log.warn( From 7a0e972ca4ef878eb80a3fd772249b2cbc500487 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Thu, 19 Mar 2020 21:19:20 -0700 Subject: [PATCH 51/59] refactor getRandomPoolLength out of markRandomNodeUnreachable --- js/modules/loki_rpc.js | 10 ++++------ js/modules/loki_snode_api.js | 6 +++++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index b27bca1a0..cb49b975b 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -73,9 +73,8 @@ const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => { // remove // but which the proxy or the target... // we got a ton of randomPool nodes, let's just not worry about this one - const randomPoolRemainingCount = lokiSnodeAPI.markRandomNodeUnreachable( - randSnode - ); + lokiSnodeAPI.markRandomNodeUnreachable(randSnode); + const randomPoolRemainingCount = lokiSnodeAPI.getRandomPoolLength(); log.warn( `lokiRpc sendToProxy`, `snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${ @@ -99,9 +98,8 @@ const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => { // but the target node // we got a ton of randomPool nodes, let's just not worry about this one - const randomPoolRemainingCount = lokiSnodeAPI.markRandomNodeUnreachable( - randSnode - ); + lokiSnodeAPI.markRandomNodeUnreachable(randSnode); + const randomPoolRemainingCount = lokiSnodeAPI.getRandomPoolLength(); log.warn( `lokiRpc sendToProxy`, `snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${ diff --git a/js/modules/loki_snode_api.js b/js/modules/loki_snode_api.js index 061ecf2de..46b18b9ee 100644 --- a/js/modules/loki_snode_api.js +++ b/js/modules/loki_snode_api.js @@ -185,6 +185,9 @@ class LokiSnodeAPI { this.randomSnodePool, _.find(this.randomSnodePool, { ip: snode.ip, port: snode.port }) ); + } + + getRandomPoolLength() { return this.randomSnodePool.length; } @@ -281,7 +284,8 @@ class LokiSnodeAPI { const snodes = result.snodes.filter(tSnode => tSnode.ip !== '0.0.0.0'); return snodes; } catch (e) { - const randomPoolRemainingCount = this.markRandomNodeUnreachable(snode); + this.markRandomNodeUnreachable(snode); + const randomPoolRemainingCount = this.getRandomPoolLength(); log.error( 'loki_snodes:::getSnodesForPubkey - error', e.code, From a6e54d5c5214bf1d16a56a474a447d3ea0a7377c Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 23 Mar 2020 09:05:40 +1100 Subject: [PATCH 52/59] Minor fix for auto-updating --- ts/updater/updater.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/updater/updater.ts b/ts/updater/updater.ts index efd12b1bf..f31ddfa12 100644 --- a/ts/updater/updater.ts +++ b/ts/updater/updater.ts @@ -55,8 +55,8 @@ export function stop() { if (interval) { clearInterval(interval); interval = undefined; - stopped = true; } + stopped = true; } async function checkForUpdates( From 99a5766592b242532da77feac7210df94d47002b Mon Sep 17 00:00:00 2001 From: Vincent Date: Mon, 23 Mar 2020 10:13:49 +1100 Subject: [PATCH 53/59] session nomenclature updates --- js/conversation_controller.js | 3 ++- js/models/conversations.js | 4 +++- package.json | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/js/conversation_controller.js b/js/conversation_controller.js index 5e102420d..852f2c46c 100644 --- a/js/conversation_controller.js +++ b/js/conversation_controller.js @@ -187,6 +187,7 @@ } await conversation.destroyMessages(); + await window.Signal.Data.removeConversation(id, { Conversation: Whisper.Conversation, }); @@ -195,7 +196,7 @@ getOrCreateAndWait(id, type) { return this._initialPromise.then(() => { const conversation = this.getOrCreate(id, type); - +5 if (conversation) { return conversation.initialPromise.then(() => conversation); } diff --git a/js/models/conversations.js b/js/models/conversations.js index 1889996a7..58d09b552 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -2763,7 +2763,9 @@ window.confirmationDialog({ title, message, - resolve: () => ConversationController.deleteContact(this.id), + resolve: () => { + ConversationController.deleteContact(this.id); + }, }); }, diff --git a/package.json b/package.json index bfeb9b735..c8d46a32a 100644 --- a/package.json +++ b/package.json @@ -210,7 +210,7 @@ "node": "^10.13.0" }, "build": { - "appId": "com.loki-project.messenger-desktop", + "appId": "org.getsession.session-desktop", "afterSign": "build/notarize.js", "artifactName": "${name}-${os}-${arch}-${version}.${ext}", "mac": { From 57b2840ed8355464fe9152b6d2c9c09c72036e6f Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Mon, 23 Mar 2020 15:41:38 -0700 Subject: [PATCH 54/59] serverRequest: fix retry, fix note --- js/modules/loki_app_dot_net_api.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index fe2813df2..945ec66a5 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -242,8 +242,8 @@ const serverRequest = async (endpoint, options = {}) => { FILESERVER_HOSTS.includes(host) ) { mode = 'sendToProxy'; - // strip trailing slash const search = url.search ? `?${url.search}` : ''; + // strip first slash const endpointWithQS = `${url.pathname}${search}`.replace(/^\//, ''); // log.info('endpointWithQS', endpointWithQS) ({ response, txtResponse, result } = await sendToProxy( @@ -297,7 +297,7 @@ const serverRequest = async (endpoint, options = {}) => { if (result.status !== 200) { if (!forceFreshToken && (!response.meta || response.meta.code === 401)) { // retry with forcing a fresh token - return this.serverRequest(endpoint, { + return serverRequest(endpoint, { ...options, forceFreshToken: true, }); From 4ed1e42caed262901fbe20dba0f35b48dbe1e291 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 24 Mar 2020 11:58:11 +1100 Subject: [PATCH 55/59] Message selection debug and onenter --- ts/components/conversation/Message.tsx | 12 +++++++++++- ts/components/session/SessionClosableOverlay.tsx | 4 ++-- ts/components/session/SessionIdEditable.tsx | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index c8dd2a98d..07b400580 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -1116,11 +1116,21 @@ export class Message extends React.PureComponent { expiring ? 'module-message--expired' : null )} role="button" - onClick={() => { + onClick={e => { const selection = window.getSelection(); + // Text is being selected if (selection && selection.type === 'Range') { return; } + + // User clicked on message body + // if (e.target === 'span.text-selectable'){ + + // } + console.log(`[event] EVENT: `, e); + console.log(`[event] Target: `, e.target); + console.log(`[event] Tagname: `, e.target); + this.props.onSelectMessage(); }} > diff --git a/ts/components/session/SessionClosableOverlay.tsx b/ts/components/session/SessionClosableOverlay.tsx index de9119337..67e34231b 100644 --- a/ts/components/session/SessionClosableOverlay.tsx +++ b/ts/components/session/SessionClosableOverlay.tsx @@ -176,9 +176,8 @@ export class SessionClosableOverlay extends React.Component { value={this.state.groupName} maxLength={window.CONSTANTS.MAX_GROUPNAME_LENGTH} onChange={this.onGroupNameChanged} + onPressEnter={onButtonClick ?? onButtonClick()} /> - - {/* */} ) : ( { groupName: event, }); } + } diff --git a/ts/components/session/SessionIdEditable.tsx b/ts/components/session/SessionIdEditable.tsx index cf2f8da9d..dc4bab8bc 100644 --- a/ts/components/session/SessionIdEditable.tsx +++ b/ts/components/session/SessionIdEditable.tsx @@ -61,7 +61,7 @@ export class SessionIdEditable extends React.PureComponent { private handleKeyDown(e: any) { const { editable, onPressEnter } = this.props; - if (editable && e.keyCode === 13) { + if (editable && e.key === 'Enter') { e.preventDefault(); // tslint:disable-next-line: no-unused-expression onPressEnter && onPressEnter(); From c27dd095fc385fcb090b1571dc436c43708b2ddb Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 24 Mar 2020 12:13:09 +1100 Subject: [PATCH 56/59] Join open group on enter --- ts/components/session/SessionClosableOverlay.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/components/session/SessionClosableOverlay.tsx b/ts/components/session/SessionClosableOverlay.tsx index 67e34231b..6b195366e 100644 --- a/ts/components/session/SessionClosableOverlay.tsx +++ b/ts/components/session/SessionClosableOverlay.tsx @@ -176,7 +176,7 @@ export class SessionClosableOverlay extends React.Component { value={this.state.groupName} maxLength={window.CONSTANTS.MAX_GROUPNAME_LENGTH} onChange={this.onGroupNameChanged} - onPressEnter={onButtonClick ?? onButtonClick()} + onPressEnter={() => onButtonClick(this.state.groupName)} /> ) : ( From 6135f4d33493377a299ee62a3bfa9bbf4436142c Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 24 Mar 2020 12:54:13 +1100 Subject: [PATCH 57/59] Message selection correction --- ts/components/conversation/Message.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index 07b400580..913f83568 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -1116,7 +1116,7 @@ export class Message extends React.PureComponent { expiring ? 'module-message--expired' : null )} role="button" - onClick={e => { + onClick={event => { const selection = window.getSelection(); // Text is being selected if (selection && selection.type === 'Range') { @@ -1124,12 +1124,8 @@ export class Message extends React.PureComponent { } // User clicked on message body - // if (e.target === 'span.text-selectable'){ - - // } - console.log(`[event] EVENT: `, e); - console.log(`[event] Target: `, e.target); - console.log(`[event] Tagname: `, e.target); + const target = (event.target as HTMLDivElement); + if(target.className === 'text-selectable') return; this.props.onSelectMessage(); }} From 6360b539a3fe3f91198dea3658ee7f80a56d4366 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 24 Mar 2020 13:32:12 +1100 Subject: [PATCH 58/59] linting --- js/conversation_controller.js | 2 +- ts/components/conversation/Message.tsx | 6 ++++-- ts/components/session/SessionClosableOverlay.tsx | 1 - ts/components/session/SessionGroupSettings.tsx | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/js/conversation_controller.js b/js/conversation_controller.js index 852f2c46c..171703716 100644 --- a/js/conversation_controller.js +++ b/js/conversation_controller.js @@ -196,7 +196,7 @@ getOrCreateAndWait(id, type) { return this._initialPromise.then(() => { const conversation = this.getOrCreate(id, type); -5 + if (conversation) { return conversation.initialPromise.then(() => conversation); } diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index 913f83568..99f1b3d97 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -1124,8 +1124,10 @@ export class Message extends React.PureComponent { } // User clicked on message body - const target = (event.target as HTMLDivElement); - if(target.className === 'text-selectable') return; + const target = event.target as HTMLDivElement; + if (target.className === 'text-selectable') { + return; + } this.props.onSelectMessage(); }} diff --git a/ts/components/session/SessionClosableOverlay.tsx b/ts/components/session/SessionClosableOverlay.tsx index 6b195366e..e86ed1ef0 100644 --- a/ts/components/session/SessionClosableOverlay.tsx +++ b/ts/components/session/SessionClosableOverlay.tsx @@ -285,5 +285,4 @@ export class SessionClosableOverlay extends React.Component { groupName: event, }); } - } diff --git a/ts/components/session/SessionGroupSettings.tsx b/ts/components/session/SessionGroupSettings.tsx index e514b633e..123f17d23 100644 --- a/ts/components/session/SessionGroupSettings.tsx +++ b/ts/components/session/SessionGroupSettings.tsx @@ -243,7 +243,7 @@ export class SessionGroupSettings extends React.Component {
{window.i18n('members', memberCount)}
-
+
)} Date: Tue, 24 Mar 2020 13:41:19 +1100 Subject: [PATCH 59/59] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c8d46a32a..bfeb9b735 100644 --- a/package.json +++ b/package.json @@ -210,7 +210,7 @@ "node": "^10.13.0" }, "build": { - "appId": "org.getsession.session-desktop", + "appId": "com.loki-project.messenger-desktop", "afterSign": "build/notarize.js", "artifactName": "${name}-${os}-${arch}-${version}.${ext}", "mac": {