From 774c468c3956f24ccc3ac3cdba2930157a8ad4e9 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Sun, 16 Feb 2020 21:47:34 -0800 Subject: [PATCH 001/107] handle non-base64 responses appropriately, include which server failed in logs --- js/modules/loki_rpc.js | 56 +++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index 2579d27f0..1e806db45 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -29,10 +29,10 @@ const decryptResponse = async (response, address) => { return {}; }; -// TODO: Don't allow arbitrary URLs, only snodes and loki servers const sendToProxy = async (options = {}, targetNode) => { const randSnode = await lokiSnodeAPI.getRandomSnodeAddress(); + // Don't allow arbitrary URLs, only snodes and loki servers const url = `https://${randSnode.ip}:${randSnode.port}/proxy`; const snPubkeyHex = StringView.hexToArrayBuffer(targetNode.pubkey_x25519); @@ -67,20 +67,42 @@ const sendToProxy = async (options = {}, targetNode) => { const response = await nodeFetch(url, firstHopOptions); process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; - const ciphertext = await response.text(); - - const ciphertextBuffer = dcodeIO.ByteBuffer.wrap( - ciphertext, - 'base64' - ).toArrayBuffer(); + // FIXME: handle nodeFetch errors/exceptions... - const plaintextBuffer = await window.libloki.crypto.DHDecrypt( - symmetricKey, - ciphertextBuffer - ); + const ciphertext = await response.text(); + let plaintext + try { + // removed the following part to match the check we have in loki_app_dot_net_api.js + // ; not done syncing; + if (ciphertext.match(/Service node is not ready: not in any swarm/)) { + log.error(`lokiRpc sendToProxy snode ${randSnode.ip}:${randSnode.port} error`, ciphertext); + // mark as bad + lokiSnodeAPI.markRandomNodeUnreachable(randSnode); + // retry + return sendToProxy(options, targetNode); + } + const ciphertextBuffer = dcodeIO.ByteBuffer.wrap( + ciphertext, + 'base64' + ).toArrayBuffer(); + + const plaintextBuffer = await window.libloki.crypto.DHDecrypt( + symmetricKey, + ciphertextBuffer + ); - const textDecoder = new TextDecoder(); - const plaintext = textDecoder.decode(plaintextBuffer); + const textDecoder = new TextDecoder(); + plaintext = textDecoder.decode(plaintextBuffer); + } catch(e) { + log.error( + 'lokiRpc sendToProxy decode error', + e.code, + e.message, + `from ${randSnode.ip}:${randSnode.port} ciphertext:`, + ciphertext + ); + return false; + } try { const jsonRes = JSON.parse(plaintext); @@ -90,10 +112,10 @@ const sendToProxy = async (options = {}, targetNode) => { return JSON.parse(jsonRes.body); } catch (e) { log.error( - 'lokiRpc sendToProxy error', + 'lokiRpc sendToProxy parse error', e.code, e.message, - 'json', + `from ${randSnode.ip}:${randSnode.port} json:`, jsonRes.body ); } @@ -102,10 +124,10 @@ const sendToProxy = async (options = {}, targetNode) => { return jsonRes; } catch (e) { log.error( - 'lokiRpc sendToProxy error', + 'lokiRpc sendToProxy parse error', e.code, e.message, - 'json', + `from ${randSnode.ip}:${randSnode.port} json:`, plaintext ); } From 69dcfa2845cb791560de1090ef074659b55440f6 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Sun, 16 Feb 2020 21:50:43 -0800 Subject: [PATCH 002/107] getSwarmNodes refactor to include results from RANDOM_SNODES_TO_USE nodes, make refreshSwarmNodesForPubKey return filteredNodes, initialiseRandomPool() retries 3 times with delays --- js/modules/loki_snode_api.js | 67 +++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/js/modules/loki_snode_api.js b/js/modules/loki_snode_api.js index 3418c827f..60f89b562 100644 --- a/js/modules/loki_snode_api.js +++ b/js/modules/loki_snode_api.js @@ -4,6 +4,8 @@ const is = require('@sindresorhus/is'); const { lokiRpc } = require('./loki_rpc'); +const RANDOM_SNODES_TO_USE = 3; + class LokiSnodeAPI { constructor({ serverUrl, localUrl }) { if (!is.string(serverUrl)) { @@ -18,6 +20,7 @@ class LokiSnodeAPI { async getRandomSnodeAddress() { /* resolve random snode */ if (this.randomSnodePool.length === 0) { + // allow exceptions to pass through upwards await this.initialiseRandomPool(); } if (this.randomSnodePool.length === 0) { @@ -28,7 +31,7 @@ class LokiSnodeAPI { ]; } - async initialiseRandomPool(seedNodes = [...window.seedNodeList]) { + async initialiseRandomPool(seedNodes = [...window.seedNodeList], consecutiveErrors = 0) { const params = { limit: 20, active_only: true, @@ -43,8 +46,9 @@ class LokiSnodeAPI { Math.floor(Math.random() * seedNodes.length), 1 )[0]; + let snodes = []; try { - const result = await lokiRpc( + const response = await lokiRpc( `http://${seedNode.ip}`, seedNode.port, 'get_n_service_nodes', @@ -53,7 +57,7 @@ class LokiSnodeAPI { '/json_rpc' // Seed request endpoint ); // Filter 0.0.0.0 nodes which haven't submitted uptime proofs - const snodes = result.result.service_node_states.filter( + snodes = response.result.service_node_states.filter( snode => snode.public_ip !== '0.0.0.0' ); this.randomSnodePool = snodes.map(snode => ({ @@ -64,12 +68,20 @@ class LokiSnodeAPI { })); } catch (e) { log.warn('initialiseRandomPool error', e.code, e.message); - if (seedNodes.length === 0) { - throw new window.textsecure.SeedNodeError( - 'Failed to contact seed node' - ); + if (consecutiveErrors < 3) { + // retry after a possible delay + setTimeout(() => { + log.info('Retrying initialising random snode pool, try #', consecutiveErrors); + 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' + ); + } } - this.initialiseRandomPool(seedNodes); } } @@ -107,8 +119,9 @@ class LokiSnodeAPI { } async updateSwarmNodes(pubKey, newNodes) { + let filteredNodes = []; try { - const filteredNodes = newNodes.filter(snode => snode.ip !== '0.0.0.0'); + filteredNodes = newNodes.filter(snode => snode.ip !== '0.0.0.0'); const conversation = ConversationController.get(pubKey); await conversation.updateSwarmNodes(filteredNodes); } catch (e) { @@ -116,11 +129,13 @@ class LokiSnodeAPI { message: 'Could not get conversation', }); } + return filteredNodes; } async refreshSwarmNodesForPubKey(pubKey) { const newNodes = await this.getFreshSwarmNodes(pubKey); - this.updateSwarmNodes(pubKey, newNodes); + const filteredNodes = this.updateSwarmNodes(pubKey, newNodes); + return filteredNodes; } async getFreshSwarmNodes(pubKey) { @@ -130,6 +145,7 @@ class LokiSnodeAPI { try { newSwarmNodes = await this.getSwarmNodes(pubKey); } catch (e) { + log.error('getFreshSwarmNodes error', e.code, e.message); // TODO: Handle these errors sensibly newSwarmNodes = []; } @@ -141,9 +157,7 @@ class LokiSnodeAPI { return newSwarmNodes; } - async getSwarmNodes(pubKey) { - // TODO: Hit multiple random nodes and merge lists? - const snode = await this.getRandomSnodeAddress(); + async getSnodesForPubkey(snode, pubKey) { try { const result = await lokiRpc( `https://${snode.ip}`, @@ -158,7 +172,7 @@ class LokiSnodeAPI { ); if (!result) { log.warn( - `getSwarmNodes lokiRpc on ${snode.ip}:${ + `getSnodesForPubkey lokiRpc on ${snode.ip}:${ snode.port } returned falsish value`, result @@ -168,11 +182,32 @@ class LokiSnodeAPI { const snodes = result.snodes.filter(tSnode => tSnode.ip !== '0.0.0.0'); return snodes; } catch (e) { - log.error('getSwarmNodes error', e.code, e.message); + log.error('getSnodesForPubkey error', e.code, e.message, `for ${snode.ip}:${snode.port}`); this.markRandomNodeUnreachable(snode); - return this.getSwarmNodes(pubKey); + return []; } } + + async getSwarmNodes(pubKey) { + const snodes = []; + const questions = [...Array(RANDOM_SNODES_TO_USE).keys()]; + await Promise.all( + questions.map(async () => { + // allow exceptions to pass through upwards + const rSnode = await this.getRandomSnodeAddress(); + const resList = await this.getSnodesForPubkey(rSnode, pubKey); + // should we only activate entries that are in all results? + resList.map(item => { + const hasItem = snodes.some(hItem => item.ip === hItem.ip && item.port === hItem.port); + if (!hasItem) { + snodes.push(item); + } + return true; + }); + }) + ); + return snodes; + } } module.exports = LokiSnodeAPI; From c404d1c7297826a4584cbb11750bd2be3e396468 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Sun, 16 Feb 2020 21:53:13 -0800 Subject: [PATCH 003/107] log exception and when we exhausted long polling pool --- libtextsecure/http-resources.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libtextsecure/http-resources.js b/libtextsecure/http-resources.js index 2e2b42a19..04de289cd 100644 --- a/libtextsecure/http-resources.js +++ b/libtextsecure/http-resources.js @@ -100,6 +100,7 @@ ); } catch (e) { // we'll try again anyway + window.log.error('http-resource pollServer error', e.code, e.message); } if (this.calledStop) { @@ -109,6 +110,10 @@ 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 / + 1000}s from scratch` + ); this.pollServer(); }, EXHAUSTED_SNODES_RETRY_DELAY); }; From 4ba4b8bb541f4d09c00c8035d2fa6e70d3c2f429 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Sun, 16 Feb 2020 21:56:37 -0800 Subject: [PATCH 004/107] improve logging, add one retry if not enough snodes in the swarm on long poll start --- js/modules/loki_message_api.js | 55 ++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index 9ff394f41..98411fd85 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -217,7 +217,7 @@ class LokiMessageAPI { } return true; } catch (e) { - log.warn('Loki send message:', e); + log.warn('Loki send message error:', e.code, e.message, `from ${address}`); if (e instanceof textsecure.WrongSwarmError) { const { newSwarm } = e; await lokiSnodeAPI.updateSwarmNodes(params.pubKey, newSwarm); @@ -272,6 +272,8 @@ class LokiMessageAPI { try { // TODO: Revert back to using snode address instead of IP let messages = await this.retrieveNextMessages(nodeData.ip, nodeData); + // this only tracks retrieval failures + // won't include parsing failures... successiveFailures = 0; if (messages.length) { const lastMessage = _.last(messages); @@ -288,7 +290,12 @@ class LokiMessageAPI { // Execute callback even with empty array to signal online status callback(messages); } catch (e) { - log.warn('Loki retrieve messages:', e.code, e.message); + log.warn( + 'Loki retrieve messages error:', + e.code, + e.message, + `on ${nodeData.ip}:${nodeData.port}` + ); if (e instanceof textsecure.WrongSwarmError) { const { newSwarm } = e; await lokiSnodeAPI.updateSwarmNodes(this.ourKey, newSwarm); @@ -312,9 +319,24 @@ class LokiMessageAPI { } } if (successiveFailures >= MAX_ACCEPTABLE_FAILURES) { + log.warn( + `removing ${nodeData.ip}:${ + nodeData.port + } from our swarm pool. We have ${ + Object.keys(this.ourSwarmNodes).length + } usable swarm nodes left` + ); 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' + ); + return false; + } + return true; } async retrieveNextMessages(nodeUrl, nodeData) { @@ -342,12 +364,31 @@ class LokiMessageAPI { } async startLongPolling(numConnections, stopPolling, callback) { + log.info('startLongPolling for', numConnections, 'connections'); this.ourSwarmNodes = {}; 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) { - await lokiSnodeAPI.refreshSwarmNodesForPubKey(this.ourKey); - nodes = await lokiSnodeAPI.getSwarmNodesForPubKey(this.ourKey); + log.warn( + 'Not enough SwarmNodes for our pubkey in local database, getting current list from blockchain' + ); + nodes = await lokiSnodeAPI.refreshSwarmNodesForPubKey(this.ourKey); + if (nodes.length < numConnections) { + log.error( + 'Could not get enough SwarmNodes for our pubkey from blockchain' + ); + } } + log.info( + `There are currently ${ + nodes.length + } swarmNodes for pubKey in our local database` + ); + for (let i = 0; i < nodes.length; i += 1) { const lastHash = await window.Signal.Data.getLastHashBySnode( nodes[i].address @@ -364,9 +405,13 @@ class LokiMessageAPI { promises.push(this.openRetrieveConnection(stopPolling, callback)); } - // blocks until all snodes in our swarms have been removed from the list + // 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'); + // should we just call ourself again? + // no, our caller already handles this... } } From 99133437d640a66ca1a8bdf71df91e3be060fdb5 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 17 Feb 2020 17:25:02 +1100 Subject: [PATCH 005/107] close all dialogs on ESC or click outside --- js/views/session_confirm_view.js | 17 ++++++++++++++++- ts/components/session/SessionModal.tsx | 21 ++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/js/views/session_confirm_view.js b/js/views/session_confirm_view.js index ce03a8ed2..f5be20610 100644 --- a/js/views/session_confirm_view.js +++ b/js/views/session_confirm_view.js @@ -24,6 +24,17 @@ }; }, + registerEvents() { + this.unregisterEvents(); + document.addEventListener('mousedown', this.props.onClickClose, false); + document.addEventListener('keyup', this.props.onClickClose, false); + }, + + unregisterEvents() { + document.removeEventListener('mousedown', this.props.onClickClose, false); + document.removeEventListener('keyup', this.props.onClickClose, false); + }, + render() { this.$('.session-confirm-wrapper').remove(); @@ -32,25 +43,29 @@ Component: window.Signal.Components.SessionConfirm, props: this.props, }); + this.registerEvents(); this.$el.prepend(this.confirmView.el); }, ok() { this.$('.session-confirm-wrapper').remove(); + this.unregisterEvents(); if (this.props.resolve) { this.props.resolve(); } }, cancel() { this.$('.session-confirm-wrapper').remove(); + this.unregisterEvents(); if (this.props.reject) { this.props.reject(); } }, onKeyup(event) { if (event.key === 'Escape' || event.key === 'Esc') { - this.cancel(); + this.unregisterEvents(); + this.props.onClickClose(); } }, }); diff --git a/ts/components/session/SessionModal.tsx b/ts/components/session/SessionModal.tsx index 1d4c45808..d4345fb76 100644 --- a/ts/components/session/SessionModal.tsx +++ b/ts/components/session/SessionModal.tsx @@ -36,6 +36,8 @@ export class SessionModal extends React.PureComponent { headerReverse: false, }; + private node: HTMLDivElement | null; + constructor(props: any) { super(props); this.state = { @@ -44,10 +46,27 @@ export class SessionModal extends React.PureComponent { this.close = this.close.bind(this); this.onKeyUp = this.onKeyUp.bind(this); + this.node = null; window.addEventListener('keyup', this.onKeyUp); } + public componentWillMount() { + document.addEventListener('mousedown', this.handleClick, false); + } + + public componentWillUnmount() { + document.removeEventListener('mousedown', this.handleClick, false); + } + + public handleClick = (e: any) => { + if (this.node && this.node.contains(e.target)) { + return; + } + + this.close(); + }; + public render() { const { title, @@ -59,7 +78,7 @@ export class SessionModal extends React.PureComponent { const { isVisible } = this.state; return isVisible ? ( -
+
(this.node = node)} className={'session-modal'}> {showHeader ? ( <>
Date: Mon, 17 Feb 2020 17:25:54 +1100 Subject: [PATCH 006/107] lint --- js/views/invite_friends_dialog_view.js | 5 +++- .../session/LeftPaneChannelSection.tsx | 24 ++++++++++++------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/js/views/invite_friends_dialog_view.js b/js/views/invite_friends_dialog_view.js index 1cc7c0ec0..ddf8d5745 100644 --- a/js/views/invite_friends_dialog_view.js +++ b/js/views/invite_friends_dialog_view.js @@ -74,7 +74,10 @@ newMembers.length + existingMembers.length > window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT ) { - const msg = window.i18n('maxGroupMembersError', window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT); + const msg = window.i18n( + 'maxGroupMembersError', + window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT + ); window.pushToast({ title: msg, diff --git a/ts/components/session/LeftPaneChannelSection.tsx b/ts/components/session/LeftPaneChannelSection.tsx index 569d9bec1..fd543ebef 100644 --- a/ts/components/session/LeftPaneChannelSection.tsx +++ b/ts/components/session/LeftPaneChannelSection.tsx @@ -399,13 +399,18 @@ export class LeftPaneChannelSection extends React.Component { groupMembers: Array ) { // Validate groupName and groupMembers length - if (groupName.length === 0 || - groupName.length > window.CONSTANTS.MAX_GROUP_NAME_LENGTH) { - window.pushToast({ - title: window.i18n('invalidGroupName', window.CONSTANTS.MAX_GROUP_NAME_LENGTH), - type: 'error', - id: 'invalidGroupName', - }); + if ( + groupName.length === 0 || + groupName.length > window.CONSTANTS.MAX_GROUP_NAME_LENGTH + ) { + window.pushToast({ + title: window.i18n( + 'invalidGroupName', + window.CONSTANTS.MAX_GROUP_NAME_LENGTH + ), + type: 'error', + id: 'invalidGroupName', + }); return; } @@ -416,7 +421,10 @@ export class LeftPaneChannelSection extends React.Component { groupMembers.length >= window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT ) { window.pushToast({ - title: window.i18n('invalidGroupSize', window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT), + title: window.i18n( + 'invalidGroupSize', + window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT + ), type: 'error', id: 'invalidGroupSize', }); From 4a55040688f9112268f66898c7ebd66ce61b3b29 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Mon, 17 Feb 2020 15:09:44 -0800 Subject: [PATCH 007/107] improve code quality --- js/modules/loki_snode_api.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/js/modules/loki_snode_api.js b/js/modules/loki_snode_api.js index 60f89b562..7bc2944ed 100644 --- a/js/modules/loki_snode_api.js +++ b/js/modules/loki_snode_api.js @@ -119,17 +119,16 @@ class LokiSnodeAPI { } async updateSwarmNodes(pubKey, newNodes) { - let filteredNodes = []; try { - filteredNodes = newNodes.filter(snode => snode.ip !== '0.0.0.0'); + const filteredNodes = newNodes.filter(snode => snode.ip !== '0.0.0.0'); const conversation = ConversationController.get(pubKey); await conversation.updateSwarmNodes(filteredNodes); + return filteredNodes; } catch (e) { throw new window.textsecure.ReplayableError({ message: 'Could not get conversation', }); } - return filteredNodes; } async refreshSwarmNodesForPubKey(pubKey) { From a02fe9555603b977741b9dec9462f85d6556ae06 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Mon, 17 Feb 2020 15:45:16 -0800 Subject: [PATCH 008/107] detect not ready through statusCode instead of string, log any non-200 statusCode, warn if no reply at all, try to debug iv errors, don't call .json() on falsish values --- js/modules/loki_rpc.js | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index 1e806db45..61fb83926 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -67,21 +67,32 @@ const sendToProxy = async (options = {}, targetNode) => { const response = await nodeFetch(url, firstHopOptions); process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; + // detect SNode is not ready (not in swarm; not done syncing) + if (response.status === 503) { + const ciphertext = await response.text(); + log.error(`lokiRpc sendToProxy snode ${randSnode.ip}:${randSnode.port} error`, ciphertext); + // 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); + } + // 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); + } const ciphertext = await response.text(); - let plaintext + if (!ciphertext) { + // avoid base64 decode failure + log.warn('Server did not return any data for', options); + } + + let plaintext; + let ciphertextBuffer; try { - // removed the following part to match the check we have in loki_app_dot_net_api.js - // ; not done syncing; - if (ciphertext.match(/Service node is not ready: not in any swarm/)) { - log.error(`lokiRpc sendToProxy snode ${randSnode.ip}:${randSnode.port} error`, ciphertext); - // mark as bad - lokiSnodeAPI.markRandomNodeUnreachable(randSnode); - // retry - return sendToProxy(options, targetNode); - } - const ciphertextBuffer = dcodeIO.ByteBuffer.wrap( + ciphertextBuffer = dcodeIO.ByteBuffer.wrap( ciphertext, 'base64' ).toArrayBuffer(); @@ -101,6 +112,9 @@ const sendToProxy = async (options = {}, targetNode) => { `from ${randSnode.ip}:${randSnode.port} ciphertext:`, ciphertext ); + if (ciphertextBuffer) { + log.error('ciphertextBuffer', ciphertextBuffer); + } return false; } @@ -172,7 +186,7 @@ const lokiFetch = async (url, options = {}, targetNode = null) => { try { if (window.lokiFeatureFlags.useSnodeProxy && targetNode) { const result = await sendToProxy(fetchOptions, targetNode); - return result.json(); + return result ? result.json() : false; } if (url.match(/https:\/\//)) { From cef644b637e08d9cc9a6d14aded04942f06dbab8 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Mon, 17 Feb 2020 16:46:46 -0800 Subject: [PATCH 009/107] f5 isn't a valid accelerator fix WARNING: f5 is not a valid accelerator on MacOS --- main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.js b/main.js index 487363f3b..b13c1be9a 100644 --- a/main.js +++ b/main.js @@ -286,7 +286,7 @@ function createWindow() { // Disable system main menu mainWindow.setMenu(null); - electronLocalshortcut.register(mainWindow, 'f5', () => { + electronLocalshortcut.register(mainWindow, 'F5', () => { mainWindow.reload(); }); electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => { From a85fc9d0bad5ff97f269ec3ef40dbf75bdc63338 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Mon, 17 Feb 2020 17:02:21 -0800 Subject: [PATCH 010/107] loki-messenger => session-desktop --- CONTRIBUTING.md | 8 ++++---- bower.json | 4 ++-- debug_log.html | 2 +- js/modules/debuglogs.js | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9795e48b9..5dbc997e0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ for it or creating a new one yourself. You can use also that issue as a place to your intentions and get feedback from the users most likely to appreciate your changes. You're most likely to have your pull request accepted easily if it addresses bugs already -in the [Next Steps project](https://github.com/loki-project/loki-messenger/projects/1), +in the [Next Steps project](https://github.com/loki-project/session-desktop/projects/1), especially if they are near the top of the Backlog column. Those are what we'll be looking at next, so it would be great if you helped us out! @@ -22,7 +22,7 @@ ounce of prevention, as they say!](https://www.goodreads.com/quotes/247269-an-ou ## Developer Setup First, you'll need [Node.js](https://nodejs.org/) which matches our current version. -You can check [`.nvmrc` in the `development` branch](https://github.com/loki-project/loki-messenger/blob/development/.nvmrc) to see what the current version is. If you have [nvm](https://github.com/creationix/nvm) +You can check [`.nvmrc` in the `development` branch](https://github.com/loki-project/session-desktop/blob/development/.nvmrc) to see what the current version is. If you have [nvm](https://github.com/creationix/nvm) you can just run `nvm use` in the project directory and it will switch to the project's desired Node.js version. [nvm for windows](https://github.com/coreybutler/nvm-windows) is still useful, but it doesn't support `.nvmrc` files. @@ -56,8 +56,8 @@ Then you need `git`, if you don't have that yet: https://git-scm.com/ Now, run these commands in your preferred terminal in a good directory for development: ``` -git clone https://github.com/loki-project/loki-messenger.git -cd loki-messenger +git clone https://github.com/loki-project/session-desktop.git +cd session-desktop npm install --global yarn # (only if you don’t already have `yarn`) yarn install --frozen-lockfile # Install and build dependencies (this will take a while) yarn grunt # Generate final JS and CSS assets diff --git a/bower.json b/bower.json index 04a267455..8b2fbba06 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { - "name": "loki-messenger", + "name": "session-desktop", "version": "0.0.0", - "homepage": "https://github.com/loki-project/loki-messenger", + "homepage": "https://github.com/loki-project/session-desktop", "license": "GPLV3", "private": true, "dependencies": { diff --git a/debug_log.html b/debug_log.html index 414473fcf..f0b7b0262 100644 --- a/debug_log.html +++ b/debug_log.html @@ -41,7 +41,7 @@

+ href='https://github.com/loki-project/session-desktop/issues/new/'> {{ reportIssue }}

diff --git a/js/modules/debuglogs.js b/js/modules/debuglogs.js index 6bed2f0e4..81b00a5b8 100644 --- a/js/modules/debuglogs.js +++ b/js/modules/debuglogs.js @@ -57,7 +57,7 @@ exports.upload = async content => { form.append('Content-Type', contentType); form.append('file', contentBuffer, { contentType, - filename: `loki-messenger-debug-log-${VERSION}.txt`, + filename: `session-desktop-debug-log-${VERSION}.txt`, }); // WORKAROUND: See comment on `submitFormData`: From 2fd279e5146f7a5c3c2f31e2875658935d8e4c65 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Mon, 17 Feb 2020 17:04:31 -0800 Subject: [PATCH 011/107] Update loki-messenger links Update loki-messenger => session-desktop and fix support page --- main.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.js b/main.js index b13c1be9a..2295cbd9d 100644 --- a/main.js +++ b/main.js @@ -428,19 +428,19 @@ ipc.on('ready-for-updates', async () => { function openReleaseNotes() { shell.openExternal( - `https://github.com/loki-project/loki-messenger/releases/tag/v${app.getVersion()}` + `https://github.com/loki-project/session-desktop/releases/tag/v${app.getVersion()}` ); } function openNewBugForm() { shell.openExternal( - 'https://github.com/loki-project/loki-messenger/issues/new' + 'https://github.com/loki-project/session-desktop/issues/new' ); } function openSupportPage() { shell.openExternal( - 'https://loki-project.github.io/loki-docs/LokiServices/Messenger/' + 'https://docs.loki.network/LokiServices/Messenger/Messenger/' ); } From ec34f50c48a417171e42831a0cc641429393b2f3 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Mon, 17 Feb 2020 17:05:57 -0800 Subject: [PATCH 012/107] update windows path --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5dbc997e0..04557db73 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -115,7 +115,7 @@ NODE_APP_INSTANCE=alice yarn run start ``` This changes the [userData](https://electron.atom.io/docs/all/#appgetpathname) -directory from `%appData%/Loki-Messenger` to `%appData%/Loki-Messenger-aliceProfile`. +directory from `%appData%/Session` to `%appData%/Session-aliceProfile`. # Making changes From 2f73836f85a14ff59363ad034ac463d52ba20413 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Mon, 17 Feb 2020 18:06:28 -0800 Subject: [PATCH 013/107] Update Messenger/Messenger to Messenger/Session per Jimmy --- main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.js b/main.js index 2295cbd9d..3912076af 100644 --- a/main.js +++ b/main.js @@ -440,7 +440,7 @@ function openNewBugForm() { function openSupportPage() { shell.openExternal( - 'https://docs.loki.network/LokiServices/Messenger/Messenger/' + 'https://docs.loki.network/LokiServices/Messenger/Session/' ); } From 0daf41a339f37da612bc7e86e658ababf96a786b Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Tue, 18 Feb 2020 14:47:59 +1100 Subject: [PATCH 014/107] check the state when the SessionSettings get rendered --- ts/components/session/settings/SessionSettingListItem.tsx | 4 ++++ ts/components/session/settings/SessionSettings.tsx | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/ts/components/session/settings/SessionSettingListItem.tsx b/ts/components/session/settings/SessionSettingListItem.tsx index 0713a6068..db80bb296 100644 --- a/ts/components/session/settings/SessionSettingListItem.tsx +++ b/ts/components/session/settings/SessionSettingListItem.tsx @@ -100,12 +100,16 @@ export class SessionSettingListItem extends React.Component { this.handleSlider(sliderValue); }} /> +

{`${currentSliderValue} Hours`}

)}
+
+ See me in here +
); } diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 692a0efbd..5a4623619 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -83,6 +83,7 @@ export class SettingsView extends React.Component { }, 1000); }); this.refreshLinkedDevice(); + console.log(this.state, 'from SessionSettings'); } public componentWillUnmount() { @@ -151,6 +152,10 @@ export class SettingsView extends React.Component { ); })} + +
+ +
); } From 9c35659c6e8b9ef85e525730ea7150fa2f07dd50 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 18 Feb 2020 16:08:56 +1100 Subject: [PATCH 015/107] Only enable signing if we have a certificate on Mac --- .github/workflows/build-binaries.yml | 9 +++++++-- .github/workflows/release.yml | 9 +++++++-- build/setup-mac-certificate.sh | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100755 build/setup-mac-certificate.sh diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index e3dfd3a8e..01a6a515c 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -52,12 +52,17 @@ jobs: if: runner.os == 'Windows' run: node_modules\.bin\electron-builder --config.extraMetadata.environment=%SIGNAL_ENV% --publish=never --config.directories.output=release + - name: Setup mac certificate + if: runner.os == 'macOS' + run: ./build/setup-mac-certificate.sh + env: + MAC_CERTIFICATE: ${{ secrets.MAC_CERTIFICATE }} + MAC_CERTIFICATE_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }} + - name: Build mac production binaries if: runner.os == 'macOS' run: $(yarn bin)/electron-builder --config.extraMetadata.environment=$SIGNAL_ENV --config.mac.bundleVersion=${{ github.ref }} --publish=never --config.directories.output=release env: - CSC_LINK: ${{ secrets.MAC_CERTIFICATE }} - CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }} SIGNING_APPLE_ID: ${{ secrets.SIGNING_APPLE_ID }} SIGNING_APP_PASSWORD: ${{ secrets.SIGNING_APP_PASSWORD }} SIGNING_TEAM_ID: ${{ secrets.SIGNING_TEAM_ID }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ea6585df..700b52a50 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,12 +49,17 @@ jobs: if: runner.os == 'Windows' run: node_modules\.bin\electron-builder --config.extraMetadata.environment=%SIGNAL_ENV% --publish=always + - name: Setup mac certificate + if: runner.os == 'macOS' + run: ./build/setup-mac-certificate.sh + env: + MAC_CERTIFICATE: ${{ secrets.MAC_CERTIFICATE }} + MAC_CERTIFICATE_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }} + - name: Build mac production binaries if: runner.os == 'macOS' run: $(yarn bin)/electron-builder --config.extraMetadata.environment=$SIGNAL_ENV --config.mac.bundleVersion=${{ github.ref }} --publish=always env: - CSC_LINK: ${{ secrets.MAC_CERTIFICATE }} - CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }} SIGNING_APPLE_ID: ${{ secrets.SIGNING_APPLE_ID }} SIGNING_APP_PASSWORD: ${{ secrets.SIGNING_APP_PASSWORD }} SIGNING_TEAM_ID: ${{ secrets.SIGNING_TEAM_ID }} diff --git a/build/setup-mac-certificate.sh b/build/setup-mac-certificate.sh new file mode 100755 index 000000000..a8ec903c2 --- /dev/null +++ b/build/setup-mac-certificate.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +if [ -z "$MAC_CERTIFICATE" ]; then + export CSC_LINK="$MAC_CERTIFICATE" + echo "MAC_CERTIFICATE found." +else + echo "MAC_CERTIFICATE not set. Ignoring." +fi + +if [ -z "$MAC_CERTIFICATE_PASSWORD" ]; then + export CSC_KEY_PASSWORD="$MAC_CERTIFICATE_PASSWORD" + echo "MAC_CERTIFICATE_PASSWORD found." +else + echo "MAC_CERTIFICATE_PASSWORD not set. Ignoring." +fi From cc915fadbc02948af8cdd18dda3e1ca30f0ef58a Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Tue, 18 Feb 2020 16:15:10 +1100 Subject: [PATCH 016/107] add sliderbar and state control in fe --- .../session/settings/SessionSettings.tsx | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 5a4623619..8dfe17652 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -8,6 +8,7 @@ import { SessionButtonType, } from '../SessionButton'; + export enum SessionSettingCategory { Appearance = 'appearance', Account = 'account', @@ -34,6 +35,7 @@ interface State { pwdLockError: string | null; shouldLockSettings: boolean | null; linkedPubKeys: Array; + scaleValue: number; } interface LocalSettingType { @@ -61,6 +63,7 @@ export class SettingsView extends React.Component { pwdLockError: null, shouldLockSettings: true, linkedPubKeys: new Array(), + scaleValue: 200 }; this.settingsViewRef = React.createRef(); @@ -71,10 +74,12 @@ export class SettingsView extends React.Component { this.refreshLinkedDevice = this.refreshLinkedDevice.bind(this); this.onKeyUp = this.onKeyUp.bind(this); + this.handleScaleChange = this.handleScaleChange.bind(this) window.addEventListener('keyup', this.onKeyUp); } public componentDidMount() { + console.log(this.state, 'Print state of SessionSettings'); setTimeout(() => $('#password-lock-input').focus(), 100); window.Whisper.events.on('refreshLinkedDeviceList', async () => { @@ -83,7 +88,8 @@ export class SettingsView extends React.Component { }, 1000); }); this.refreshLinkedDevice(); - console.log(this.state, 'from SessionSettings'); + + } public componentWillUnmount() { @@ -107,6 +113,8 @@ export class SettingsView extends React.Component { settings = this.getLocalSettings(); } + + return ( <> {this.state.hasPassword !== null && @@ -154,8 +162,9 @@ export class SettingsView extends React.Component { })}
- +
+
Scale: {this.state.scaleValue}
); } @@ -250,6 +259,16 @@ export class SettingsView extends React.Component { ); } + + public handleScaleChange(event:any):any { + const {value} = event.target; + let scaleVal:number = parseInt(value,10); + this.setState({ + scaleValue:scaleVal + }) + } + + public renderSessionInfo(): JSX.Element { return (
@@ -307,6 +326,9 @@ export class SettingsView extends React.Component { } } + + + private getPubkeyName(pubKey: string | null) { if (!pubKey) { return {}; From 5bd7b1254e8e5b7943400c3fe6a55fa0d42813e8 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 18 Feb 2020 16:23:32 +1100 Subject: [PATCH 017/107] Fix incorrect bash script --- .github/workflows/build-binaries.yml | 2 +- build/setup-mac-certificate.sh | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 01a6a515c..062f50bf1 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -54,10 +54,10 @@ jobs: - name: Setup mac certificate if: runner.os == 'macOS' - run: ./build/setup-mac-certificate.sh env: MAC_CERTIFICATE: ${{ secrets.MAC_CERTIFICATE }} MAC_CERTIFICATE_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }} + run: ./build/setup-mac-certificate.sh - name: Build mac production binaries if: runner.os == 'macOS' diff --git a/build/setup-mac-certificate.sh b/build/setup-mac-certificate.sh index a8ec903c2..60d964b0c 100755 --- a/build/setup-mac-certificate.sh +++ b/build/setup-mac-certificate.sh @@ -1,15 +1,15 @@ #!/usr/bin/env bash if [ -z "$MAC_CERTIFICATE" ]; then + echo "MAC_CERTIFICATE not set. Ignoring." +else export CSC_LINK="$MAC_CERTIFICATE" echo "MAC_CERTIFICATE found." -else - echo "MAC_CERTIFICATE not set. Ignoring." fi if [ -z "$MAC_CERTIFICATE_PASSWORD" ]; then + echo "MAC_CERTIFICATE_PASSWORD not set. Ignoring." +else export CSC_KEY_PASSWORD="$MAC_CERTIFICATE_PASSWORD" echo "MAC_CERTIFICATE_PASSWORD found." -else - echo "MAC_CERTIFICATE_PASSWORD not set. Ignoring." fi From 9dd1e0f945d2b386339dfb257300ff502098e8ec Mon Sep 17 00:00:00 2001 From: Mikunj Varsani Date: Tue, 18 Feb 2020 19:10:50 +1100 Subject: [PATCH 018/107] Update build-binaries.yml --- .github/workflows/build-binaries.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 062f50bf1..bbb0c4a10 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -57,7 +57,7 @@ jobs: env: MAC_CERTIFICATE: ${{ secrets.MAC_CERTIFICATE }} MAC_CERTIFICATE_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }} - run: ./build/setup-mac-certificate.sh + run: source ./build/setup-mac-certificate.sh - name: Build mac production binaries if: runner.os == 'macOS' From 5f0c7a2416e14a84f8d4fcc198975bc318d5ad0c Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 19 Feb 2020 08:47:30 +1100 Subject: [PATCH 019/107] Trying a different approach --- .github/workflows/build-binaries.yml | 11 ++++------- .github/workflows/release.yml | 11 ++++------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index bbb0c4a10..d4da15e86 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -52,17 +52,14 @@ jobs: if: runner.os == 'Windows' run: node_modules\.bin\electron-builder --config.extraMetadata.environment=%SIGNAL_ENV% --publish=never --config.directories.output=release - - name: Setup mac certificate + - name: Build mac production binaries if: runner.os == 'macOS' + run: | + source ./build/setup-mac-certificate.sh + $(yarn bin)/electron-builder --config.extraMetadata.environment=$SIGNAL_ENV --config.mac.bundleVersion=${{ github.ref }} --publish=never --config.directories.output=release env: MAC_CERTIFICATE: ${{ secrets.MAC_CERTIFICATE }} MAC_CERTIFICATE_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }} - run: source ./build/setup-mac-certificate.sh - - - name: Build mac production binaries - if: runner.os == 'macOS' - run: $(yarn bin)/electron-builder --config.extraMetadata.environment=$SIGNAL_ENV --config.mac.bundleVersion=${{ github.ref }} --publish=never --config.directories.output=release - env: SIGNING_APPLE_ID: ${{ secrets.SIGNING_APPLE_ID }} SIGNING_APP_PASSWORD: ${{ secrets.SIGNING_APP_PASSWORD }} SIGNING_TEAM_ID: ${{ secrets.SIGNING_TEAM_ID }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 700b52a50..5d61ca569 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,17 +49,14 @@ jobs: if: runner.os == 'Windows' run: node_modules\.bin\electron-builder --config.extraMetadata.environment=%SIGNAL_ENV% --publish=always - - name: Setup mac certificate + - name: Build mac production binaries if: runner.os == 'macOS' - run: ./build/setup-mac-certificate.sh + run: | + source ./build/setup-mac-certificate.sh + $(yarn bin)/electron-builder --config.extraMetadata.environment=$SIGNAL_ENV --config.mac.bundleVersion=${{ github.ref }} --publish=always env: MAC_CERTIFICATE: ${{ secrets.MAC_CERTIFICATE }} MAC_CERTIFICATE_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }} - - - name: Build mac production binaries - if: runner.os == 'macOS' - run: $(yarn bin)/electron-builder --config.extraMetadata.environment=$SIGNAL_ENV --config.mac.bundleVersion=${{ github.ref }} --publish=always - env: SIGNING_APPLE_ID: ${{ secrets.SIGNING_APPLE_ID }} SIGNING_APP_PASSWORD: ${{ secrets.SIGNING_APP_PASSWORD }} SIGNING_TEAM_ID: ${{ secrets.SIGNING_TEAM_ID }} From c6be28909296147091d30057ad537f63f17acad4 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 7 Feb 2020 15:53:40 +1100 Subject: [PATCH 020/107] Fix leaving closed groups --- js/models/conversations.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/js/models/conversations.js b/js/models/conversations.js index 7471c5798..bdadd6a9b 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -221,9 +221,7 @@ return !!(this.id && this.id.match(/^publicChat:/)); }, isClosedGroup() { - return ( - this.get('type') === Message.GROUP && !this.isPublic() && !this.isRss() - ); + return this.get('type') === Message.GROUP && !this.isPublic() && !this.isRss(); }, isClosable() { return !this.isRss() || this.get('closable'); From d20d31b57426d4be8e75189bc62d4283fee914ab Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 7 Feb 2020 16:40:25 +1100 Subject: [PATCH 021/107] Linting --- js/models/conversations.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/models/conversations.js b/js/models/conversations.js index bdadd6a9b..7471c5798 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -221,7 +221,9 @@ return !!(this.id && this.id.match(/^publicChat:/)); }, isClosedGroup() { - return this.get('type') === Message.GROUP && !this.isPublic() && !this.isRss(); + return ( + this.get('type') === Message.GROUP && !this.isPublic() && !this.isRss() + ); }, isClosable() { return !this.isRss() || this.get('closable'); From fc6ca57e1e6b1669855eb45cd103b2df84d6d11c Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 10 Feb 2020 12:06:05 +1100 Subject: [PATCH 022/107] Added support for group request info --- js/background.js | 3 +- js/models/conversations.js | 17 ++++ js/models/messages.js | 128 ++++++++++++++++-------------- libtextsecure/message_receiver.js | 4 + libtextsecure/sendmessage.js | 11 ++- 5 files changed, 102 insertions(+), 61 deletions(-) diff --git a/js/background.js b/js/background.js index 859909b61..0a3b4f3be 100644 --- a/js/background.js +++ b/js/background.js @@ -787,6 +787,7 @@ 'group' ); + convo.updateGroupAdmins([primaryDeviceKey]); convo.updateGroup(ev.groupDetails); // Group conversations are automatically 'friends' @@ -795,8 +796,6 @@ window.friends.friendRequestStatusEnum.friends ); - convo.updateGroupAdmins([primaryDeviceKey]); - appView.openConversation(groupId, {}); }; diff --git a/js/models/conversations.js b/js/models/conversations.js index 7471c5798..b4f2a2485 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -2232,6 +2232,7 @@ this.get('name'), this.get('avatar'), this.get('members'), + this.get('groupAdmins'), groupUpdate.recipients, options ) @@ -2239,6 +2240,21 @@ ); }, + sendGroupInfo(recipients) { + if (this.isClosedGroup()) { + const options = this.getSendOptions(); + textsecure.messaging.updateGroup( + this.id, + this.get('name'), + this.get('avatar'), + this.get('members'), + this.get('groupAdmins'), + recipients, + options + ); + } + }, + async leaveGroup() { const now = Date.now(); if (this.get('type') === 'group') { @@ -2323,6 +2339,7 @@ const ourNumber = textsecure.storage.user.getNumber(); return !stillUnread.some( m => + m.propsForMessage && m.propsForMessage.text && m.propsForMessage.text.indexOf(`@${ourNumber}`) !== -1 ); diff --git a/js/models/messages.js b/js/models/messages.js index c79ca9c11..4aef00b64 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -1929,78 +1929,90 @@ } } - if ( - initialMessage.group && - initialMessage.group.members && - initialMessage.group.type === GROUP_TYPES.UPDATE - ) { - if (newGroup) { - conversation.updateGroupAdmins(initialMessage.group.admins); + if (initialMessage.group) { + if ( + initialMessage.group.type === GROUP_TYPES.REQUEST_INFO && + !newGroup + ) { + conversation.sendGroupInfo([source]); + return null; + } else if ( + initialMessage.group.members && + initialMessage.group.type === GROUP_TYPES.UPDATE + ) { + if (newGroup) { + conversation.updateGroupAdmins(initialMessage.group.admins); - conversation.setFriendRequestStatus( - window.friends.friendRequestStatusEnum.friends - ); - } + conversation.setFriendRequestStatus( + window.friends.friendRequestStatusEnum.friends + ); + } const fromAdmin = conversation .get('groupAdmins') .includes(primarySource); - if (!fromAdmin) { - // Make sure the message is not removing members / renaming the group - const nameChanged = - conversation.get('name') !== initialMessage.group.name; + if (!fromAdmin) { + // Make sure the message is not removing members / renaming the group + const nameChanged = + conversation.get('name') !== initialMessage.group.name; - if (nameChanged) { - window.log.warn( - 'Non-admin attempts to change the name of the group' - ); - } + if (nameChanged) { + window.log.warn( + 'Non-admin attempts to change the name of the group' + ); + } - const membersMissing = - _.difference( - conversation.get('members'), - initialMessage.group.members - ).length > 0; + const membersMissing = + _.difference( + conversation.get('members'), + initialMessage.group.members + ).length > 0; - if (membersMissing) { - window.log.warn('Non-admin attempts to remove group members'); - } + if (membersMissing) { + window.log.warn('Non-admin attempts to remove group members'); + } - const messageAllowed = !nameChanged && !membersMissing; + const messageAllowed = !nameChanged && !membersMissing; - if (!messageAllowed) { - confirm(); - return null; + if (!messageAllowed) { + confirm(); + return null; + } } - } - // For every member, see if we need to establish a session: - initialMessage.group.members.forEach(memberPubKey => { - const haveSession = _.some( - textsecure.storage.protocol.sessions, - s => s.number === memberPubKey - ); + // For every member, see if we need to establish a session: + initialMessage.group.members.forEach(memberPubKey => { + const haveSession = _.some( + textsecure.storage.protocol.sessions, + s => s.number === memberPubKey + ); - const ourPubKey = textsecure.storage.user.getNumber(); - if (!haveSession && memberPubKey !== ourPubKey) { - ConversationController.getOrCreateAndWait( - memberPubKey, - 'private' - ).then(() => { - textsecure.messaging.sendMessageToNumber( + const ourPubKey = textsecure.storage.user.getNumber(); + if (!haveSession && memberPubKey !== ourPubKey) { + ConversationController.getOrCreateAndWait( memberPubKey, - '(If you see this message, you must be using an out-of-date client)', - [], - undefined, - [], - Date.now(), - undefined, - undefined, - { messageType: 'friend-request', sessionRequest: true } - ); - }); - } - }); + 'private' + ).then(() => { + textsecure.messaging.sendMessageToNumber( + memberPubKey, + '(If you see this message, you must be using an out-of-date client)', + [], + undefined, + [], + Date.now(), + undefined, + undefined, + { messageType: 'friend-request', sessionRequest: true } + ); + }); + } + }); + } else if (newGroup) { + // We have an unknown group, we should request info + textsecure.messaging.requestGroupInfo(conversationId, [ + primarySource, + ]); + } } const isSessionRequest = diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 757cfbcfa..893d549cc 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -1786,6 +1786,10 @@ MessageReceiver.prototype.extend({ decrypted.group.members = []; decrypted.group.avatar = null; break; + case textsecure.protobuf.GroupContext.Type.REQUEST_INFO: + decrypted.body = null; + decrypted.attachments = []; + break; default: this.removeFromCache(envelope); throw new Error('Unknown group message type'); diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index 57ebb6a6a..9386d2409 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -1107,7 +1107,7 @@ MessageSender.prototype = { return this.sendMessage(attrs, options); }, - updateGroup(groupId, name, avatar, members, recipients, options) { + updateGroup(groupId, name, avatar, members, admins, recipients, options) { const proto = new textsecure.protobuf.DataMessage(); proto.group = new textsecure.protobuf.GroupContext(); @@ -1164,6 +1164,14 @@ MessageSender.prototype = { }); }, + requestGroupInfo(groupId, groupNumbers, options) { + const proto = new textsecure.protobuf.DataMessage(); + proto.group = new textsecure.protobuf.GroupContext(); + proto.group.id = stringToArrayBuffer(groupId); + proto.group.type = textsecure.protobuf.GroupContext.Type.REQUEST_INFO; + return this.sendGroupProto(groupNumbers, proto, Date.now(), options); + }, + leaveGroup(groupId, groupNumbers, options) { const proto = new textsecure.protobuf.DataMessage(); proto.group = new textsecure.protobuf.GroupContext(); @@ -1263,6 +1271,7 @@ textsecure.MessageSender = function MessageSenderWrapper(username, password) { this.addNumberToGroup = sender.addNumberToGroup.bind(sender); this.setGroupName = sender.setGroupName.bind(sender); this.setGroupAvatar = sender.setGroupAvatar.bind(sender); + this.requestGroupInfo = sender.requestGroupInfo.bind(sender); this.leaveGroup = sender.leaveGroup.bind(sender); this.sendSyncMessage = sender.sendSyncMessage.bind(sender); this.getProfile = sender.getProfile.bind(sender); From 62825faa6119c1025c2a2b5a60e1bb05ac255cfd Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 10 Feb 2020 13:01:27 +1100 Subject: [PATCH 023/107] Don't perform admin check if it's a new group that we are creating --- js/models/messages.js | 56 +++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/js/models/messages.js b/js/models/messages.js index 4aef00b64..c9455c098 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -1946,38 +1946,38 @@ conversation.setFriendRequestStatus( window.friends.friendRequestStatusEnum.friends ); - } - - const fromAdmin = conversation - .get('groupAdmins') - .includes(primarySource); - - if (!fromAdmin) { - // Make sure the message is not removing members / renaming the group - const nameChanged = - conversation.get('name') !== initialMessage.group.name; - - if (nameChanged) { - window.log.warn( - 'Non-admin attempts to change the name of the group' - ); - } + } else { + const fromAdmin = conversation + .get('groupAdmins') + .includes(primarySource); + + if (!fromAdmin) { + // Make sure the message is not removing members / renaming the group + const nameChanged = + conversation.get('name') !== initialMessage.group.name; + + if (nameChanged) { + window.log.warn( + 'Non-admin attempts to change the name of the group' + ); + } - const membersMissing = - _.difference( - conversation.get('members'), - initialMessage.group.members - ).length > 0; + const membersMissing = + _.difference( + conversation.get('members'), + initialMessage.group.members + ).length > 0; - if (membersMissing) { - window.log.warn('Non-admin attempts to remove group members'); - } + if (membersMissing) { + window.log.warn('Non-admin attempts to remove group members'); + } - const messageAllowed = !nameChanged && !membersMissing; + const messageAllowed = !nameChanged && !membersMissing; - if (!messageAllowed) { - confirm(); - return null; + if (!messageAllowed) { + confirm(); + return null; + } } } // For every member, see if we need to establish a session: From b61dd6a83914b56f58b5ece90e7569f8ddadb228 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 10 Feb 2020 15:20:34 +1100 Subject: [PATCH 024/107] Don't send groups in contact sync message --- js/models/messages.js | 2 +- libloki/api.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/js/models/messages.js b/js/models/messages.js index c9455c098..7b024886b 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -2008,7 +2008,7 @@ } }); } else if (newGroup) { - // We have an unknown group, we should request info + // We have an unknown group, we should request info from the sender textsecure.messaging.requestGroupInfo(conversationId, [ primarySource, ]); diff --git a/libloki/api.js b/libloki/api.js index 9b658a07f..431ff22ee 100644 --- a/libloki/api.js +++ b/libloki/api.js @@ -109,8 +109,9 @@ } async function createContactSyncProtoMessage(conversations) { // Extract required contacts information out of conversations + const sessionContacts = conversations.filter(c => c.isPrivate()); const rawContacts = await Promise.all( - conversations.map(async conversation => { + sessionContacts.map(async conversation => { const profile = conversation.getLokiProfile(); const number = conversation.getNumber(); const name = profile From abf298ba25c0c51cdbd3bbfbb57558e5bac4d223 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 19 Feb 2020 10:32:30 +1100 Subject: [PATCH 025/107] Added sending of group sync message --- libloki/api.js | 29 +++++++++++++++++++++++++++++ libtextsecure/account_manager.js | 2 ++ libtextsecure/message_receiver.js | 3 +-- libtextsecure/sendmessage.js | 16 ++++++++++++++++ protos/SignalService.proto | 2 ++ 5 files changed, 50 insertions(+), 2 deletions(-) diff --git a/libloki/api.js b/libloki/api.js index 431ff22ee..efbd6891b 100644 --- a/libloki/api.js +++ b/libloki/api.js @@ -152,6 +152,34 @@ }); return syncMessage; } + async function createGroupSyncProtoMessage(conversations) { + // We only want to sync across closed groups that we haven't left + const sessionGroups = conversations.filter(c => c.isClosedGroup() && !c.get('left') && c.isFriend()); + const rawGroups = await Promise.all( + sessionGroups.map(async conversation => ({ + id: conversation.id, + name: conversation.get('name'), + members: conversation.get('members') || [], + blocked: conversation.isBlocked(), + expireTimer: conversation.get('expireTimer'), + admins: conversation.get('groupAdmins') || [], + })) + ); + // Convert raw groups to an array of buffers + const groupDetails = rawGroups + .map(x => new textsecure.protobuf.GroupDetails(x)) + .map(x => x.encode()); + // Serialise array of byteBuffers into 1 byteBuffer + const byteBuffer = serialiseByteBuffers(groupDetails); + const data = new Uint8Array(byteBuffer.toArrayBuffer()); + const groups = new textsecure.protobuf.SyncMessage.Groups({ + data, + }); + const syncMessage = new textsecure.protobuf.SyncMessage({ + groups, + }); + return syncMessage; + } async function sendPairingAuthorisation(authorisation, recipientPubKey) { const pairingAuthorisation = createPairingAuthorisationProtoMessage( authorisation @@ -222,5 +250,6 @@ createPairingAuthorisationProtoMessage, sendUnpairingMessageToSecondary, createContactSyncProtoMessage, + createGroupSyncProtoMessage, }; })(); diff --git a/libtextsecure/account_manager.js b/libtextsecure/account_manager.js index 1dfca9417..d43b39ca3 100644 --- a/libtextsecure/account_manager.js +++ b/libtextsecure/account_manager.js @@ -634,6 +634,8 @@ blockSync: true, } ); + // Send group sync message + await textsecure.messaging.sendGroupSyncMessage(window.getConversations()) }, validatePubKeyHex(pubKey) { const c = new Whisper.Conversation({ diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 893d549cc..39cc611d6 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -1574,11 +1574,10 @@ MessageReceiver.prototype.extend({ }, handleGroups(envelope, groups) { window.log.info('group sync'); - const { blob } = groups; // Note: we do not return here because we don't want to block the next message on // this attachment download and a lot of processing of that attachment. - this.handleAttachment(blob).then(attachmentPointer => { + this.handleAttachment(groups).then(attachmentPointer => { const groupBuffer = new GroupBuffer(attachmentPointer.data); let groupDetails = groupBuffer.next(); const promises = []; diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index 9386d2409..40393a314 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -700,6 +700,22 @@ MessageSender.prototype = { ); }, + async sendGroupSyncMessage(conversations) { + const ourNumber = textsecure.storage.user.getNumber(); + const syncMessage = await libloki.api.createGroupSyncProtoMessage(conversations); + const contentMessage = new textsecure.protobuf.Content(); + contentMessage.syncMessage = syncMessage; + + const silent = true; + return this.sendIndividualProto( + ourNumber, + contentMessage, + Date.now(), + silent, + {} // options + ); + }, + sendRequestContactSyncMessage(options) { const myNumber = textsecure.storage.user.getNumber(); const myDevice = textsecure.storage.user.getDeviceId(); diff --git a/protos/SignalService.proto b/protos/SignalService.proto index 41f6ed5ab..f383448fe 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -282,6 +282,7 @@ message SyncMessage { message Groups { optional AttachmentPointer blob = 1; + optional bytes data = 101; } message Blocked { @@ -390,4 +391,5 @@ message GroupDetails { optional uint32 expireTimer = 6; optional string color = 7; optional bool blocked = 8; + repeated string admins = 9; } From f35493ce9fe562bd09737f17488254f8cdc5e265 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 19 Feb 2020 10:39:30 +1100 Subject: [PATCH 026/107] Linting --- js/views/invite_friends_dialog_view.js | 5 +++- libloki/api.js | 18 +++++++------- libtextsecure/account_manager.js | 4 +++- libtextsecure/sendmessage.js | 4 +++- .../session/LeftPaneChannelSection.tsx | 24 ++++++++++++------- 5 files changed, 36 insertions(+), 19 deletions(-) diff --git a/js/views/invite_friends_dialog_view.js b/js/views/invite_friends_dialog_view.js index 1cc7c0ec0..ddf8d5745 100644 --- a/js/views/invite_friends_dialog_view.js +++ b/js/views/invite_friends_dialog_view.js @@ -74,7 +74,10 @@ newMembers.length + existingMembers.length > window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT ) { - const msg = window.i18n('maxGroupMembersError', window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT); + const msg = window.i18n( + 'maxGroupMembersError', + window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT + ); window.pushToast({ title: msg, diff --git a/libloki/api.js b/libloki/api.js index efbd6891b..4df0ec2be 100644 --- a/libloki/api.js +++ b/libloki/api.js @@ -154,16 +154,18 @@ } async function createGroupSyncProtoMessage(conversations) { // We only want to sync across closed groups that we haven't left - const sessionGroups = conversations.filter(c => c.isClosedGroup() && !c.get('left') && c.isFriend()); + const sessionGroups = conversations.filter( + c => c.isClosedGroup() && !c.get('left') && c.isFriend() + ); const rawGroups = await Promise.all( sessionGroups.map(async conversation => ({ - id: conversation.id, - name: conversation.get('name'), - members: conversation.get('members') || [], - blocked: conversation.isBlocked(), - expireTimer: conversation.get('expireTimer'), - admins: conversation.get('groupAdmins') || [], - })) + id: conversation.id, + name: conversation.get('name'), + members: conversation.get('members') || [], + blocked: conversation.isBlocked(), + expireTimer: conversation.get('expireTimer'), + admins: conversation.get('groupAdmins') || [], + })) ); // Convert raw groups to an array of buffers const groupDetails = rawGroups diff --git a/libtextsecure/account_manager.js b/libtextsecure/account_manager.js index d43b39ca3..4775bdd5e 100644 --- a/libtextsecure/account_manager.js +++ b/libtextsecure/account_manager.js @@ -635,7 +635,9 @@ } ); // Send group sync message - await textsecure.messaging.sendGroupSyncMessage(window.getConversations()) + await textsecure.messaging.sendGroupSyncMessage( + window.getConversations() + ); }, validatePubKeyHex(pubKey) { const c = new Whisper.Conversation({ diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index 40393a314..4e557ba93 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -702,7 +702,9 @@ MessageSender.prototype = { async sendGroupSyncMessage(conversations) { const ourNumber = textsecure.storage.user.getNumber(); - const syncMessage = await libloki.api.createGroupSyncProtoMessage(conversations); + const syncMessage = await libloki.api.createGroupSyncProtoMessage( + conversations + ); const contentMessage = new textsecure.protobuf.Content(); contentMessage.syncMessage = syncMessage; diff --git a/ts/components/session/LeftPaneChannelSection.tsx b/ts/components/session/LeftPaneChannelSection.tsx index 569d9bec1..fd543ebef 100644 --- a/ts/components/session/LeftPaneChannelSection.tsx +++ b/ts/components/session/LeftPaneChannelSection.tsx @@ -399,13 +399,18 @@ export class LeftPaneChannelSection extends React.Component { groupMembers: Array ) { // Validate groupName and groupMembers length - if (groupName.length === 0 || - groupName.length > window.CONSTANTS.MAX_GROUP_NAME_LENGTH) { - window.pushToast({ - title: window.i18n('invalidGroupName', window.CONSTANTS.MAX_GROUP_NAME_LENGTH), - type: 'error', - id: 'invalidGroupName', - }); + if ( + groupName.length === 0 || + groupName.length > window.CONSTANTS.MAX_GROUP_NAME_LENGTH + ) { + window.pushToast({ + title: window.i18n( + 'invalidGroupName', + window.CONSTANTS.MAX_GROUP_NAME_LENGTH + ), + type: 'error', + id: 'invalidGroupName', + }); return; } @@ -416,7 +421,10 @@ export class LeftPaneChannelSection extends React.Component { groupMembers.length >= window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT ) { window.pushToast({ - title: window.i18n('invalidGroupSize', window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT), + title: window.i18n( + 'invalidGroupSize', + window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT + ), type: 'error', id: 'invalidGroupSize', }); From 6d5aed7de8cdb59db850e6bfc344c03604970a7a Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 19 Feb 2020 11:27:37 +1100 Subject: [PATCH 027/107] make upload of group picture work --- _locales/en/messages.json | 2 +- js/background.js | 48 ++++++++- js/modules/loki_app_dot_net_api.js | 34 +++++- js/views/conversation_view.js | 3 + js/views/create_group_dialog_view.js | 102 +++++++----------- js/views/invite_friends_dialog_view.js | 5 +- stylesheets/_index.scss | 1 + .../conversation/UpdateGroupMembersDialog.tsx | 12 --- .../conversation/UpdateGroupNameDialog.tsx | 86 +++++++++++++-- .../session/LeftPaneChannelSection.tsx | 24 +++-- .../session/SessionGroupSettings.tsx | 41 +++---- 11 files changed, 243 insertions(+), 115 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 5ee80af32..03ff620bb 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2205,7 +2205,7 @@ "description": "Button action that the user can click to edit their profile" }, "editGroupName": { - "message": "Edit group name", + "message": "Edit group name or picture", "description": "Button action that the user can click to edit a group name" }, "createGroupDialogTitle": { diff --git a/js/background.js b/js/background.js index 859909b61..5478fbffc 100644 --- a/js/background.js +++ b/js/background.js @@ -702,7 +702,7 @@ } }); - window.doUpdateGroup = async (groupId, groupName, members) => { + window.doUpdateGroup = async (groupId, groupName, members, avatar) => { const ourKey = textsecure.storage.user.getNumber(); const ev = new Event('message'); @@ -729,6 +729,44 @@ if (convo.isPublic()) { const API = await convo.getPublicSendData(); + + if (avatar) { + // I hate duplicating this... + const readFile = attachment => + new Promise((resolve, reject) => { + const fileReader = new FileReader(); + fileReader.onload = e => { + const data = e.target.result; + resolve({ + ...attachment, + data, + size: data.byteLength, + }); + }; + fileReader.onerror = reject; + fileReader.onabort = reject; + fileReader.readAsArrayBuffer(attachment.file); + }); + const attachment = await readFile({ file: avatar }); + // const tempUrl = window.URL.createObjectURL(avatar); + + // Get file onto public chat server + const fileObj = await API.serverAPI.putAttachment(attachment.data); + if (fileObj === null) { + // problem + log.warn('File upload failed'); + return; + } + + // lets not allow ANY URLs, lets force it to be local to public chat server + const relativeFileUrl = fileObj.url.replace( + API.serverAPI.baseServerUrl, + '' + ); + // write it to the channel + const changeRes = await API.setChannelAvatar(relativeFileUrl); + } + if (await API.setChannelName(groupName)) { // queue update from server // and let that set the conversation @@ -741,7 +779,11 @@ return; } - const avatar = ''; + const nullAvatar = ''; + if (avatar) { + // would get to download this file on each client in the group + // and reference the local file + } const options = {}; const recipients = _.union(convo.get('members'), members); @@ -750,7 +792,7 @@ convo.updateGroup({ groupId, groupName, - avatar, + nullAvatar, recipients, members, options, diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index c9664abef..ddb201663 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -877,6 +877,7 @@ class LokiAppDotNetServerAPI { }; } + // for avatar async uploadData(data) { const endpoint = 'files'; const options = { @@ -901,6 +902,7 @@ class LokiAppDotNetServerAPI { }; } + // for files putAttachment(attachmentBin) { const formData = new FormData(); const buffer = Buffer.from(attachmentBin); @@ -1246,7 +1248,37 @@ class LokiPublicChannelAPI { this.conversation.setGroupName(note.value.name); } if (note.value && note.value.avatar) { - this.conversation.setProfileAvatar(note.value.avatar); + const avatarAbsUrl = this.serverAPI.baseServerUrl + note.value.avatar; + console.log('setting', avatarAbsUrl); + const { + upgradeMessageSchema, + writeNewAttachmentData, + deleteAttachmentData, + } = window.Signal.Migrations; + // do we already have this image? no, then + + // download a copy and save it + const imageData = await nodeFetch(avatarAbsUrl); + function toArrayBuffer(buf) { + var ab = new ArrayBuffer(buf.length); + var view = new Uint8Array(ab); + for (var i = 0; i < buf.length; ++i) { + view[i] = buf[i]; + } + return ab; + } + const newAttributes = await window.Signal.Types.Conversation.maybeUpdateAvatar( + this.conversation.attributes, + toArrayBuffer(imageData), + { + writeNewAttachmentData, + deleteAttachmentData, + } + ); + console.log('newAttributes.avatar', newAttributes.avatar); + // update group + this.conversation.set(newAttributes); + //this.conversation.setProfileAvatar(newAttributes.avatar); } // is it mutable? // who are the moderators? diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 0550fa63e..9f28d86c7 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -287,6 +287,9 @@ isAdmin: this.model.get('groupAdmins').includes(ourPK), isRss: this.model.isRss(), memberCount: members.length, + amMod: this.model.isModerator( + window.storage.get('primaryDevicePubKey') + ), timerOptions: Whisper.ExpirationTimerOptions.map(item => ({ name: item.getName(), diff --git a/js/views/create_group_dialog_view.js b/js/views/create_group_dialog_view.js index befbf3b3b..193fd1ed0 100644 --- a/js/views/create_group_dialog_view.js +++ b/js/views/create_group_dialog_view.js @@ -59,31 +59,12 @@ this.close = this.close.bind(this); this.onSubmit = this.onSubmit.bind(this); this.isPublic = groupConvo.isPublic(); + this.groupId = groupConvo.id; const ourPK = textsecure.storage.user.getNumber(); this.isAdmin = groupConvo.get('groupAdmins').includes(ourPK); - const convos = window.getConversations().models.filter(d => !!d); - - let existingMembers = groupConvo.get('members') || []; - - // Show a contact if they are our friend or if they are a member - const friendsAndMembers = convos.filter( - d => - (d.isFriend() || existingMembers.includes(d.id)) && - d.isPrivate() && - !d.isMe() - ); - this.friendsAndMembers = _.uniq(friendsAndMembers, true, d => d.id); - - // at least make sure it's an array - if (!Array.isArray(existingMembers)) { - existingMembers = []; - } - - this.existingMembers = existingMembers; - // public chat settings overrides if (this.isPublic) { // fix the title @@ -98,6 +79,24 @@ // zero out friendList for now this.friendsAndMembers = []; this.existingMembers = []; + } else { + const convos = window.getConversations().models.filter(d => !!d); + + this.existingMembers = groupConvo.get('members') || []; + // Show a contact if they are our friend or if they are a member + this.friendsAndMembers = convos.filter( + d => this.existingMembers.includes(d.id) && d.isPrivate() && !d.isMe() + ); + this.friendsAndMembers = _.uniq( + this.friendsAndMembers, + true, + d => d.id + ); + + // at least make sure it's an array + if (!Array.isArray(this.existingMembers)) { + this.existingMembers = []; + } } this.$el.focus(); @@ -109,24 +108,22 @@ Component: window.Signal.Components.UpdateGroupNameDialog, props: { titleText: this.titleText, - groupName: this.groupName, - okText: this.okText, isPublic: this.isPublic, - cancelText: this.cancelText, - existingMembers: this.existingMembers, + groupName: this.groupName, + okText: i18n('ok'), + cancelText: i18n('cancel'), isAdmin: this.isAdmin, - onClose: this.close, + i18n, onSubmit: this.onSubmit, + onClose: this.close, }, }); this.$el.append(this.dialogView.el); return this; }, - onSubmit(newGroupName, members) { - const groupId = this.conversation.get('id'); - - window.doUpdateGroup(groupId, newGroupName, members); + onSubmit(groupName, avatar) { + window.doUpdateGroup(this.groupId, groupName, this.members, avatar); }, close() { this.remove(); @@ -136,40 +133,16 @@ Whisper.UpdateGroupMembersDialogView = Whisper.View.extend({ className: 'loki-dialog modal', initialize(groupConvo) { + const ourPK = textsecure.storage.user.getNumber(); this.groupName = groupConvo.get('name'); - - this.conversation = groupConvo; - this.titleText = i18n('updateGroupDialogTitle'); - this.okText = i18n('ok'); - this.cancelText = i18n('cancel'); this.close = this.close.bind(this); this.onSubmit = this.onSubmit.bind(this); this.isPublic = groupConvo.isPublic(); + this.groupId = groupConvo.id; + this.avatarPath = groupConvo.getAvatarPath(); + this.members = groupConvo.get('members') || []; - const ourPK = textsecure.storage.user.getNumber(); - - this.isAdmin = groupConvo.get('groupAdmins').includes(ourPK); - - const convos = window.getConversations().models.filter(d => !!d); - - let existingMembers = groupConvo.get('members') || []; - - // Show a contact if they are our friend or if they are a member - const friendsAndMembers = convos.filter( - d => existingMembers.includes(d.id) && d.isPrivate() && !d.isMe() - ); - this.friendsAndMembers = _.uniq(friendsAndMembers, true, d => d.id); - - // at least make sure it's an array - if (!Array.isArray(existingMembers)) { - existingMembers = []; - } - - this.existingMembers = existingMembers; - - // public chat settings overrides if (this.isPublic) { - // fix the title this.titleText = `${i18n('updatePublicGroupDialogTitle')}: ${ this.groupName }`; @@ -178,9 +151,9 @@ this.isAdmin = groupConvo.isModerator( window.storage.get('primaryDevicePubKey') ); - // zero out friendList for now - this.friendsAndMembers = []; - this.existingMembers = []; + } else { + this.titleText = i18n('updateGroupDialogTitle'); + this.isAdmin = groupConvo.get('groupAdmins').includes(ourPK); } this.$el.focus(); @@ -201,6 +174,7 @@ isAdmin: this.isAdmin, onClose: this.close, onSubmit: this.onSubmit, + groupId: this.groupId, }, }); @@ -210,9 +184,13 @@ onSubmit(groupName, newMembers) { const ourPK = textsecure.storage.user.getNumber(); const allMembers = window.Lodash.concat(newMembers, [ourPK]); - const groupId = this.conversation.get('id'); - window.doUpdateGroup(groupId, groupName, allMembers); + window.doUpdateGroup( + this.groupId, + groupName, + allMembers, + this.avatarPath + ); }, close() { this.remove(); diff --git a/js/views/invite_friends_dialog_view.js b/js/views/invite_friends_dialog_view.js index 1cc7c0ec0..ddf8d5745 100644 --- a/js/views/invite_friends_dialog_view.js +++ b/js/views/invite_friends_dialog_view.js @@ -74,7 +74,10 @@ newMembers.length + existingMembers.length > window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT ) { - const msg = window.i18n('maxGroupMembersError', window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT); + const msg = window.i18n( + 'maxGroupMembersError', + window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT + ); window.pushToast({ title: msg, diff --git a/stylesheets/_index.scss b/stylesheets/_index.scss index f5156f5dc..672319a6a 100644 --- a/stylesheets/_index.scss +++ b/stylesheets/_index.scss @@ -11,6 +11,7 @@ } .edit-profile-dialog, +.create-group-dialog, .user-details-dialog { .content { max-width: 100% !important; diff --git a/ts/components/conversation/UpdateGroupMembersDialog.tsx b/ts/components/conversation/UpdateGroupMembersDialog.tsx index 27de62c49..7c3f77775 100644 --- a/ts/components/conversation/UpdateGroupMembersDialog.tsx +++ b/ts/components/conversation/UpdateGroupMembersDialog.tsx @@ -34,7 +34,6 @@ export class UpdateGroupMembersDialog extends React.Component { this.onClickOK = this.onClickOK.bind(this); this.onKeyUp = this.onKeyUp.bind(this); this.closeDialog = this.closeDialog.bind(this); - this.onGroupNameChanged = this.onGroupNameChanged.bind(this); let friends = this.props.friendList; friends = friends.map(d => { @@ -209,15 +208,4 @@ export class UpdateGroupMembersDialog extends React.Component { }; }); } - - private onGroupNameChanged(event: any) { - event.persist(); - - this.setState(state => { - return { - ...state, - groupName: event.target.value, - }; - }); - } } diff --git a/ts/components/conversation/UpdateGroupNameDialog.tsx b/ts/components/conversation/UpdateGroupNameDialog.tsx index ba44a2497..bc4a1a95d 100644 --- a/ts/components/conversation/UpdateGroupNameDialog.tsx +++ b/ts/components/conversation/UpdateGroupNameDialog.tsx @@ -3,9 +3,11 @@ import classNames from 'classnames'; import { SessionModal } from '../session/SessionModal'; import { SessionButton } from '../session/SessionButton'; +import { Avatar } from '../Avatar'; interface Props { titleText: string; + isPublic: boolean; groupName: string; okText: string; cancelText: string; @@ -13,30 +15,35 @@ interface Props { i18n: any; onSubmit: any; onClose: any; - existingMembers: Array; + // avatar stuff + avatarPath: string; } interface State { groupName: string; errorDisplayed: boolean; errorMessage: string; + avatar: string; } export class UpdateGroupNameDialog extends React.Component { + private readonly inputEl: any; + constructor(props: any) { super(props); this.onClickOK = this.onClickOK.bind(this); this.onKeyUp = this.onKeyUp.bind(this); this.closeDialog = this.closeDialog.bind(this); - this.onGroupNameChanged = this.onGroupNameChanged.bind(this); + this.onFileSelected = this.onFileSelected.bind(this); this.state = { groupName: this.props.groupName, errorDisplayed: false, errorMessage: 'placeholder', + avatar: this.props.avatarPath, }; - + this.inputEl = React.createRef(); window.addEventListener('keyup', this.onKeyUp); } @@ -47,18 +54,30 @@ export class UpdateGroupNameDialog extends React.Component { return; } - this.props.onSubmit(this.state.groupName, this.props.existingMembers); + const avatar = + this.inputEl && + this.inputEl.current && + this.inputEl.current.files && + this.inputEl.current.files.length > 0 + ? this.inputEl.current.files[0] + : this.props.avatarPath; // otherwise use the current avatar + + this.props.onSubmit(this.props.groupName, avatar); this.closeDialog(); } public render() { - const okText = this.props.okText; - const cancelText = this.props.cancelText; + const { isPublic, okText, cancelText } = this.props; - let titleText; + const titleText = `${this.props.titleText}`; + let noAvatarClasses; - titleText = `${this.props.titleText}`; + if (isPublic) { + noAvatarClasses = classNames('avatar-center'); + } else { + noAvatarClasses = classNames('hidden'); + } const errorMsg = this.state.errorMessage; const errorMessageClasses = classNames( @@ -77,6 +96,33 @@ export class UpdateGroupNameDialog extends React.Component {

{errorMsg}

+
+
+ {this.renderAvatar()} +
+ +
{ + const el = this.inputEl.current; + if (el) { + el.click(); + } + }} + /> +
+
+
+
+ { }; }); } + + private renderAvatar() { + const avatarPath = this.state.avatar; + const color = '#00ff00'; + + return ( + + ); + } + + private onFileSelected() { + const file = this.inputEl.current.files[0]; + const url = window.URL.createObjectURL(file); + + this.setState({ + avatar: url, + }); + } } diff --git a/ts/components/session/LeftPaneChannelSection.tsx b/ts/components/session/LeftPaneChannelSection.tsx index 569d9bec1..fd543ebef 100644 --- a/ts/components/session/LeftPaneChannelSection.tsx +++ b/ts/components/session/LeftPaneChannelSection.tsx @@ -399,13 +399,18 @@ export class LeftPaneChannelSection extends React.Component { groupMembers: Array ) { // Validate groupName and groupMembers length - if (groupName.length === 0 || - groupName.length > window.CONSTANTS.MAX_GROUP_NAME_LENGTH) { - window.pushToast({ - title: window.i18n('invalidGroupName', window.CONSTANTS.MAX_GROUP_NAME_LENGTH), - type: 'error', - id: 'invalidGroupName', - }); + if ( + groupName.length === 0 || + groupName.length > window.CONSTANTS.MAX_GROUP_NAME_LENGTH + ) { + window.pushToast({ + title: window.i18n( + 'invalidGroupName', + window.CONSTANTS.MAX_GROUP_NAME_LENGTH + ), + type: 'error', + id: 'invalidGroupName', + }); return; } @@ -416,7 +421,10 @@ export class LeftPaneChannelSection extends React.Component { groupMembers.length >= window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT ) { window.pushToast({ - title: window.i18n('invalidGroupSize', window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT), + title: window.i18n( + 'invalidGroupSize', + window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT + ), type: 'error', id: 'invalidGroupSize', }); diff --git a/ts/components/session/SessionGroupSettings.tsx b/ts/components/session/SessionGroupSettings.tsx index 8f831c577..cff0c7245 100644 --- a/ts/components/session/SessionGroupSettings.tsx +++ b/ts/components/session/SessionGroupSettings.tsx @@ -20,6 +20,7 @@ interface Props { timerOptions: Array; isPublic: boolean; isAdmin: boolean; + amMod: boolean; onGoBack: () => void; onInviteFriends: () => void; @@ -211,6 +212,7 @@ export class SessionGroupSettings extends React.Component { onLeaveGroup, isPublic, isAdmin, + amMod, } = this.props; const { documents, media, onItemClick } = this.state; const showMemberCount = !!(memberCount && memberCount > 0); @@ -228,6 +230,9 @@ export class SessionGroupSettings extends React.Component { }; }); + const showUpdateGroupNameButton = isPublic ? amMod : isAdmin; + const showUpdateGroupMembersButton = !isPublic && isAdmin; + return (
{this.renderHeader()} @@ -245,25 +250,23 @@ export class SessionGroupSettings extends React.Component { className="description" placeholder={window.i18n('description')} /> - {!isPublic && ( - <> - {isAdmin && ( -
- {window.i18n('editGroupName')} -
- )} -
- {window.i18n('showMembers')} -
- + {showUpdateGroupNameButton && ( +
+ {window.i18n('editGroupName')} +
+ )} + {showUpdateGroupMembersButton && ( +
+ {window.i18n('showMembers')} +
)} {/*
{window.i18n('notifications')} From 0eaebcbcac1eef86df5ab076ba66c1cb097669fa Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 19 Feb 2020 12:31:01 +1100 Subject: [PATCH 028/107] Don't send contact sync message with pairing authorisation. Don't send secondary devices in contact sync messages. --- js/background.js | 2 + js/models/conversations.js | 2 +- js/modules/loki_app_dot_net_api.js | 2 +- libloki/api.js | 44 ++++++++------ libtextsecure/account_manager.js | 8 +-- libtextsecure/sendmessage.js | 98 ++++++++++++++++-------------- 6 files changed, 86 insertions(+), 70 deletions(-) diff --git a/js/background.js b/js/background.js index 0a3b4f3be..5ce84a8fc 100644 --- a/js/background.js +++ b/js/background.js @@ -1371,6 +1371,8 @@ await window.lokiFileServerAPI.updateOurDeviceMapping(); // TODO: we should ensure the message was sent and retry automatically if not await libloki.api.sendUnpairingMessageToSecondary(pubKey); + // Remove all traces of the device + ConversationController.deleteContact(pubKey); Whisper.events.trigger('refreshLinkedDeviceList'); }); } diff --git a/js/models/conversations.js b/js/models/conversations.js index b4f2a2485..1889996a7 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -935,7 +935,7 @@ if (newStatus === FriendRequestStatusEnum.friends) { if (!blockSync) { // Sync contact - this.wrapSend(textsecure.messaging.sendContactSyncMessage(this)); + this.wrapSend(textsecure.messaging.sendContactSyncMessage([this])); } // Only enable sending profileKey after becoming friends this.set({ profileSharing: true }); diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index c9664abef..8158a5bc2 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -229,7 +229,7 @@ class LokiAppDotNetServerAPI { window.storage.get('primaryDevicePubKey') || textsecure.storage.user.getNumber(); const profileConvo = ConversationController.get(ourNumber); - const profile = profileConvo.getLokiProfile(); + const profile = profileConvo && profileConvo.getLokiProfile(); const profileName = profile && profile.displayName; // if doesn't match, write it to the network if (tokenRes.response.data.user.name !== profileName) { diff --git a/libloki/api.js b/libloki/api.js index 4df0ec2be..ef1baccf4 100644 --- a/libloki/api.js +++ b/libloki/api.js @@ -1,4 +1,4 @@ -/* global window, textsecure, Whisper, dcodeIO, StringView, ConversationController */ +/* global window, textsecure, dcodeIO, StringView, ConversationController */ // eslint-disable-next-line func-names (function() { @@ -109,7 +109,14 @@ } async function createContactSyncProtoMessage(conversations) { // Extract required contacts information out of conversations - const sessionContacts = conversations.filter(c => c.isPrivate()); + const sessionContacts = conversations.filter( + c => c.isPrivate() && !c.isSecondaryDevice() + ); + + if (sessionContacts.length === 0) { + return null; + } + const rawContacts = await Promise.all( sessionContacts.map(async conversation => { const profile = conversation.getLokiProfile(); @@ -152,21 +159,25 @@ }); return syncMessage; } - async function createGroupSyncProtoMessage(conversations) { + function createGroupSyncProtoMessage(conversations) { // We only want to sync across closed groups that we haven't left const sessionGroups = conversations.filter( c => c.isClosedGroup() && !c.get('left') && c.isFriend() ); - const rawGroups = await Promise.all( - sessionGroups.map(async conversation => ({ - id: conversation.id, - name: conversation.get('name'), - members: conversation.get('members') || [], - blocked: conversation.isBlocked(), - expireTimer: conversation.get('expireTimer'), - admins: conversation.get('groupAdmins') || [], - })) - ); + + if (sessionGroups.length === 0) { + return null; + } + + const rawGroups = sessionGroups.map(conversation => ({ + id: window.Signal.Crypto.bytesFromString(conversation.id), + name: conversation.get('name'), + members: conversation.get('members') || [], + blocked: conversation.isBlocked(), + expireTimer: conversation.get('expireTimer'), + admins: conversation.get('groupAdmins') || [], + })); + // Convert raw groups to an array of buffers const groupDetails = rawGroups .map(x => new textsecure.protobuf.GroupDetails(x)) @@ -210,13 +221,6 @@ profile, profileKey, }); - // Attach contact list - const conversations = await window.Signal.Data.getConversationsWithFriendStatus( - window.friends.friendRequestStatusEnum.friends, - { ConversationCollection: Whisper.ConversationCollection } - ); - const syncMessage = await createContactSyncProtoMessage(conversations); - content.syncMessage = syncMessage; content.dataMessage = dataMessage; } // Send diff --git a/libtextsecure/account_manager.js b/libtextsecure/account_manager.js index 4775bdd5e..088422630 100644 --- a/libtextsecure/account_manager.js +++ b/libtextsecure/account_manager.js @@ -634,10 +634,10 @@ blockSync: true, } ); - // Send group sync message - await textsecure.messaging.sendGroupSyncMessage( - window.getConversations() - ); + // Send sync messages + const conversations = window.getConversations().models; + textsecure.messaging.sendContactSyncMessage(conversations); + textsecure.messaging.sendGroupSyncMessage(conversations); }, validatePubKeyHex(pubKey) { const c = new Whisper.Conversation({ diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index 4e557ba93..3d39d472d 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -664,58 +664,67 @@ MessageSender.prototype = { return Promise.resolve(); }, - async sendContactSyncMessage(contactConversation) { - if (!contactConversation.isPrivate()) { - return Promise.resolve(); - } - + async sendContactSyncMessage(conversations) { + // If we havn't got a primaryDeviceKey then we are in the middle of pairing + // primaryDevicePubKey is set to our own number if we are the master device const primaryDeviceKey = window.storage.get('primaryDevicePubKey'); - const allOurDevices = (await libloki.storage.getAllDevicePubKeysForPrimaryPubKey( - primaryDeviceKey - )) - // Don't send to ourselves - .filter(pubKey => pubKey !== textsecure.storage.user.getNumber()); - if ( - allOurDevices.includes(contactConversation.id) || - !primaryDeviceKey || - allOurDevices.length === 0 - ) { - // If we havn't got a primaryDeviceKey then we are in the middle of pairing + if (!primaryDeviceKey) { return Promise.resolve(); } - const syncMessage = await libloki.api.createContactSyncProtoMessage([ - contactConversation, - ]); - const contentMessage = new textsecure.protobuf.Content(); - contentMessage.syncMessage = syncMessage; - - const silent = true; - return this.sendIndividualProto( - primaryDeviceKey, - contentMessage, - Date.now(), - silent, - {} // options + // We need to sync across 3 contacts at a time + // This is to avoid hitting storage server limit + const chunked = _.chunk(conversations, 3); + const syncMessages = await Promise.all( + chunked.map(c => libloki.api.createContactSyncProtoMessage(c)) ); + const syncPromises = syncMessages + .filter(message => message != null) + .map(syncMessage => { + const contentMessage = new textsecure.protobuf.Content(); + contentMessage.syncMessage = syncMessage; + + const silent = true; + return this.sendIndividualProto( + primaryDeviceKey, + contentMessage, + Date.now(), + silent, + {} // options + ); + }); + + return Promise.all(syncPromises); }, - async sendGroupSyncMessage(conversations) { - const ourNumber = textsecure.storage.user.getNumber(); - const syncMessage = await libloki.api.createGroupSyncProtoMessage( - conversations - ); - const contentMessage = new textsecure.protobuf.Content(); - contentMessage.syncMessage = syncMessage; + sendGroupSyncMessage(conversations) { + // If we havn't got a primaryDeviceKey then we are in the middle of pairing + // primaryDevicePubKey is set to our own number if we are the master device + const primaryDeviceKey = window.storage.get('primaryDevicePubKey'); + if (!primaryDeviceKey) { + return Promise.resolve(); + } - const silent = true; - return this.sendIndividualProto( - ourNumber, - contentMessage, - Date.now(), - silent, - {} // options - ); + // We need to sync across 1 group at a time + // This is because we could hit the storage server limit with one group + const syncPromises = conversations + .map(c => libloki.api.createGroupSyncProtoMessage([c])) + .filter(message => message != null) + .map(syncMessage => { + const contentMessage = new textsecure.protobuf.Content(); + contentMessage.syncMessage = syncMessage; + + const silent = true; + return this.sendIndividualProto( + primaryDeviceKey, + contentMessage, + Date.now(), + silent, + {} // options + ); + }); + + return Promise.all(syncPromises); }, sendRequestContactSyncMessage(options) { @@ -1277,6 +1286,7 @@ textsecure.MessageSender = function MessageSenderWrapper(username, password) { sender ); this.sendContactSyncMessage = sender.sendContactSyncMessage.bind(sender); + this.sendGroupSyncMessage = sender.sendGroupSyncMessage.bind(sender); this.sendRequestConfigurationSyncMessage = sender.sendRequestConfigurationSyncMessage.bind( sender ); From a03185248c660cee536a45ebc8d070064c83a7c0 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 19 Feb 2020 13:28:26 +1100 Subject: [PATCH 029/107] Fix check for valid sender when handling sync message --- libloki/storage.js | 2 +- libtextsecure/message_receiver.js | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/libloki/storage.js b/libloki/storage.js index 3a2d084e2..a5b5c27de 100644 --- a/libloki/storage.js +++ b/libloki/storage.js @@ -131,7 +131,7 @@ if (deviceMapping.isPrimary === '0') { const { primaryDevicePubKey } = authorisations.find( - authorisation => authorisation.secondaryDevicePubKey === pubKey + authorisation => authorisation && authorisation.secondaryDevicePubKey === pubKey ) || {}; if (primaryDevicePubKey) { // do NOT call getprimaryDeviceMapping recursively diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 39cc611d6..a33eca0eb 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -1469,18 +1469,20 @@ MessageReceiver.prototype.extend({ this.removeFromCache(envelope); }, async handleSyncMessage(envelope, syncMessage) { + // We should only accept sync messages from our devices const ourNumber = textsecure.storage.user.getNumber(); - // NOTE: Maybe we should be caching this list? - const ourDevices = await libloki.storage.getAllDevicePubKeysForPrimaryPubKey( + const ourPrimaryNumber = window.storage.get('primaryDevicePubKey'); + const ourOtherDevices = await libloki.storage.getAllDevicePubKeysForPrimaryPubKey( window.storage.get('primaryDevicePubKey') ); - const validSyncSender = - ourDevices && ourDevices.some(devicePubKey => devicePubKey === ourNumber); + const ourDevices = new Set([ourNumber, ourPrimaryNumber, ...ourOtherDevices]); + const validSyncSender = ourDevices.has(envelope.source); if (!validSyncSender) { throw new Error( "Received sync message from a device we aren't paired with" ); } + if (syncMessage.sent) { const sentMessage = syncMessage.sent; const to = sentMessage.message.group From d00abed7da61e42ca3a1dd146d77d075012411ea Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 19 Feb 2020 13:30:10 +1100 Subject: [PATCH 030/107] Linting --- libloki/storage.js | 3 ++- libtextsecure/message_receiver.js | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/libloki/storage.js b/libloki/storage.js index a5b5c27de..0e4cd3dda 100644 --- a/libloki/storage.js +++ b/libloki/storage.js @@ -131,7 +131,8 @@ if (deviceMapping.isPrimary === '0') { const { primaryDevicePubKey } = authorisations.find( - authorisation => authorisation && authorisation.secondaryDevicePubKey === pubKey + authorisation => + authorisation && authorisation.secondaryDevicePubKey === pubKey ) || {}; if (primaryDevicePubKey) { // do NOT call getprimaryDeviceMapping recursively diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index a33eca0eb..81adb351f 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -1475,7 +1475,11 @@ MessageReceiver.prototype.extend({ const ourOtherDevices = await libloki.storage.getAllDevicePubKeysForPrimaryPubKey( window.storage.get('primaryDevicePubKey') ); - const ourDevices = new Set([ourNumber, ourPrimaryNumber, ...ourOtherDevices]); + const ourDevices = new Set([ + ourNumber, + ourPrimaryNumber, + ...ourOtherDevices, + ]); const validSyncSender = ourDevices.has(envelope.source); if (!validSyncSender) { throw new Error( From 60ed8f297236f24693716207c3589dca462ac83b Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 19 Feb 2020 13:41:08 +1100 Subject: [PATCH 031/107] make download of group avatar work --- js/modules/loki_app_dot_net_api.js | 14 ++++++++------ .../conversation/UpdateGroupNameDialog.tsx | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index ddb201663..320e16192 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -1249,9 +1249,7 @@ class LokiPublicChannelAPI { } if (note.value && note.value.avatar) { const avatarAbsUrl = this.serverAPI.baseServerUrl + note.value.avatar; - console.log('setting', avatarAbsUrl); const { - upgradeMessageSchema, writeNewAttachmentData, deleteAttachmentData, } = window.Signal.Migrations; @@ -1267,18 +1265,22 @@ class LokiPublicChannelAPI { } return ab; } + const buffer = await imageData.buffer(); const newAttributes = await window.Signal.Types.Conversation.maybeUpdateAvatar( this.conversation.attributes, - toArrayBuffer(imageData), + toArrayBuffer(buffer), { writeNewAttachmentData, deleteAttachmentData, } ); - console.log('newAttributes.avatar', newAttributes.avatar); // update group - this.conversation.set(newAttributes); - //this.conversation.setProfileAvatar(newAttributes.avatar); + this.conversation.set('avatar', newAttributes.avatar); + + await window.Signal.Data.updateConversation(this.conversation.id, this.conversation.attributes, { + Conversation: Whisper.Conversation, + }); + this.conversation.trigger('change'); } // is it mutable? // who are the moderators? diff --git a/ts/components/conversation/UpdateGroupNameDialog.tsx b/ts/components/conversation/UpdateGroupNameDialog.tsx index bc4a1a95d..7e923a848 100644 --- a/ts/components/conversation/UpdateGroupNameDialog.tsx +++ b/ts/components/conversation/UpdateGroupNameDialog.tsx @@ -36,6 +36,7 @@ export class UpdateGroupNameDialog extends React.Component { this.onKeyUp = this.onKeyUp.bind(this); this.closeDialog = this.closeDialog.bind(this); this.onFileSelected = this.onFileSelected.bind(this); + this.onGroupNameChanged = this.onGroupNameChanged.bind(this); this.state = { groupName: this.props.groupName, From b756332f89a76fdc7b7e7149c636406e5c5967a1 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 19 Feb 2020 14:00:20 +1100 Subject: [PATCH 032/107] fix group member dialog and refresh right after group avatar update --- js/modules/loki_app_dot_net_api.js | 11 +++---- js/views/create_group_dialog_view.js | 43 ++++++++++++++-------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 320e16192..6a92618fe 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -1276,11 +1276,6 @@ class LokiPublicChannelAPI { ); // update group this.conversation.set('avatar', newAttributes.avatar); - - await window.Signal.Data.updateConversation(this.conversation.id, this.conversation.attributes, { - Conversation: Whisper.Conversation, - }); - this.conversation.trigger('change'); } // is it mutable? // who are the moderators? @@ -1290,6 +1285,12 @@ class LokiPublicChannelAPI { if (data.counts && Number.isInteger(data.counts.subscribers)) { this.conversation.setSubscriberCount(data.counts.subscribers); } + + await window.Signal.Data.updateConversation(this.conversation.id, this.conversation.attributes, { + Conversation: Whisper.Conversation, + }); + await this.pollForChannelOnce(); + this.conversation.trigger('change'); } // get moderation actions diff --git a/js/views/create_group_dialog_view.js b/js/views/create_group_dialog_view.js index 193fd1ed0..639711ee3 100644 --- a/js/views/create_group_dialog_view.js +++ b/js/views/create_group_dialog_view.js @@ -60,6 +60,7 @@ this.onSubmit = this.onSubmit.bind(this); this.isPublic = groupConvo.isPublic(); this.groupId = groupConvo.id; + this.members = groupConvo.get('members') || []; const ourPK = textsecure.storage.user.getNumber(); @@ -76,27 +77,6 @@ this.isAdmin = groupConvo.isModerator( window.storage.get('primaryDevicePubKey') ); - // zero out friendList for now - this.friendsAndMembers = []; - this.existingMembers = []; - } else { - const convos = window.getConversations().models.filter(d => !!d); - - this.existingMembers = groupConvo.get('members') || []; - // Show a contact if they are our friend or if they are a member - this.friendsAndMembers = convos.filter( - d => this.existingMembers.includes(d.id) && d.isPrivate() && !d.isMe() - ); - this.friendsAndMembers = _.uniq( - this.friendsAndMembers, - true, - d => d.id - ); - - // at least make sure it's an array - if (!Array.isArray(this.existingMembers)) { - this.existingMembers = []; - } } this.$el.focus(); @@ -140,7 +120,6 @@ this.isPublic = groupConvo.isPublic(); this.groupId = groupConvo.id; this.avatarPath = groupConvo.getAvatarPath(); - this.members = groupConvo.get('members') || []; if (this.isPublic) { this.titleText = `${i18n('updatePublicGroupDialogTitle')}: ${ @@ -151,9 +130,29 @@ this.isAdmin = groupConvo.isModerator( window.storage.get('primaryDevicePubKey') ); + // zero out friendList for now + this.friendsAndMembers = []; + this.existingMembers = []; } else { this.titleText = i18n('updateGroupDialogTitle'); this.isAdmin = groupConvo.get('groupAdmins').includes(ourPK); + const convos = window.getConversations().models.filter(d => !!d); + + this.existingMembers = groupConvo.get('members') || []; + // Show a contact if they are our friend or if they are a member + this.friendsAndMembers = convos.filter( + d => this.existingMembers.includes(d.id) && d.isPrivate() && !d.isMe() + ); + this.friendsAndMembers = _.uniq( + this.friendsAndMembers, + true, + d => d.id + ); + + // at least make sure it's an array + if (!Array.isArray(this.existingMembers)) { + this.existingMembers = []; + } } this.$el.focus(); From 1428cfe1dd11151592e7bfbad8202f23dfeac21e Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 19 Feb 2020 15:17:20 +1100 Subject: [PATCH 033/107] fix download profile image open groups --- js/modules/loki_app_dot_net_api.js | 11 +++++++---- js/views/create_group_dialog_view.js | 4 ++-- .../conversation/UpdateGroupNameDialog.tsx | 15 +++++---------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 6a92618fe..75b2ebb43 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -1257,14 +1257,18 @@ class LokiPublicChannelAPI { // download a copy and save it const imageData = await nodeFetch(avatarAbsUrl); + // eslint-disable-next-line no-inner-declarations function toArrayBuffer(buf) { - var ab = new ArrayBuffer(buf.length); - var view = new Uint8Array(ab); - for (var i = 0; i < buf.length; ++i) { + const ab = new ArrayBuffer(buf.length); + const view = new Uint8Array(ab); + // eslint-disable-next-line no-plusplus + for (let i = 0; i < buf.length; i++) { view[i] = buf[i]; } return ab; } + // eslint-enable-next-line no-inner-declarations + const buffer = await imageData.buffer(); const newAttributes = await window.Signal.Types.Conversation.maybeUpdateAvatar( this.conversation.attributes, @@ -1290,7 +1294,6 @@ class LokiPublicChannelAPI { Conversation: Whisper.Conversation, }); await this.pollForChannelOnce(); - this.conversation.trigger('change'); } // get moderation actions diff --git a/js/views/create_group_dialog_view.js b/js/views/create_group_dialog_view.js index 639711ee3..8a5cb9e30 100644 --- a/js/views/create_group_dialog_view.js +++ b/js/views/create_group_dialog_view.js @@ -54,13 +54,12 @@ this.conversation = groupConvo; this.titleText = i18n('updateGroupDialogTitle'); - this.okText = i18n('ok'); - this.cancelText = i18n('cancel'); this.close = this.close.bind(this); this.onSubmit = this.onSubmit.bind(this); this.isPublic = groupConvo.isPublic(); this.groupId = groupConvo.id; this.members = groupConvo.get('members') || []; + this.avatarPath = groupConvo.getAvatarPath(); const ourPK = textsecure.storage.user.getNumber(); @@ -96,6 +95,7 @@ i18n, onSubmit: this.onSubmit, onClose: this.close, + avatarPath: this.avatarPath, }, }); diff --git a/ts/components/conversation/UpdateGroupNameDialog.tsx b/ts/components/conversation/UpdateGroupNameDialog.tsx index 7e923a848..53fcf648e 100644 --- a/ts/components/conversation/UpdateGroupNameDialog.tsx +++ b/ts/components/conversation/UpdateGroupNameDialog.tsx @@ -61,24 +61,21 @@ export class UpdateGroupNameDialog extends React.Component { this.inputEl.current.files && this.inputEl.current.files.length > 0 ? this.inputEl.current.files[0] - : this.props.avatarPath; // otherwise use the current avatar + : null; // otherwise use the current avatar - this.props.onSubmit(this.props.groupName, avatar); + this.props.onSubmit(this.state.groupName, avatar); this.closeDialog(); } public render() { - const { isPublic, okText, cancelText } = this.props; + const { okText, cancelText } = this.props; const titleText = `${this.props.titleText}`; let noAvatarClasses; - if (isPublic) { - noAvatarClasses = classNames('avatar-center'); - } else { - noAvatarClasses = classNames('hidden'); - } + noAvatarClasses = classNames('avatar-center'); + const errorMsg = this.state.errorMessage; const errorMessageClasses = classNames( @@ -195,12 +192,10 @@ export class UpdateGroupNameDialog extends React.Component { private renderAvatar() { const avatarPath = this.state.avatar; - const color = '#00ff00'; return ( Date: Wed, 19 Feb 2020 16:21:52 +1100 Subject: [PATCH 034/107] disable profile image upload for closed group --- _locales/en/messages.json | 8 ++- js/background.js | 4 +- .../conversation/UpdateGroupNameDialog.tsx | 72 +++++++++---------- .../session/SessionGroupSettings.tsx | 2 +- 4 files changed, 45 insertions(+), 41 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 03ff620bb..a7e6d2766 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2204,9 +2204,13 @@ "message": "Edit Profile", "description": "Button action that the user can click to edit their profile" }, - "editGroupName": { + "editGroupNameOrPicture": { "message": "Edit group name or picture", - "description": "Button action that the user can click to edit a group name" + "description": "Button action that the user can click to edit a group name (open)" + }, + "editGroupName": { + "message": "Edit group name", + "description": "Button action that the user can click to edit a group name (closed)" }, "createGroupDialogTitle": { "message": "Creating a Closed Group", diff --git a/js/background.js b/js/background.js index 5478fbffc..bd3ced3d5 100644 --- a/js/background.js +++ b/js/background.js @@ -754,7 +754,7 @@ const fileObj = await API.serverAPI.putAttachment(attachment.data); if (fileObj === null) { // problem - log.warn('File upload failed'); + window.warn('File upload failed'); return; } @@ -764,7 +764,7 @@ '' ); // write it to the channel - const changeRes = await API.setChannelAvatar(relativeFileUrl); + await API.setChannelAvatar(relativeFileUrl); } if (await API.setChannelName(groupName)) { diff --git a/ts/components/conversation/UpdateGroupNameDialog.tsx b/ts/components/conversation/UpdateGroupNameDialog.tsx index 53fcf648e..1f0d033ff 100644 --- a/ts/components/conversation/UpdateGroupNameDialog.tsx +++ b/ts/components/conversation/UpdateGroupNameDialog.tsx @@ -72,10 +72,6 @@ export class UpdateGroupNameDialog extends React.Component { const { okText, cancelText } = this.props; const titleText = `${this.props.titleText}`; - let noAvatarClasses; - - noAvatarClasses = classNames('avatar-center'); - const errorMsg = this.state.errorMessage; const errorMessageClasses = classNames( @@ -93,32 +89,7 @@ export class UpdateGroupNameDialog extends React.Component {

{errorMsg}

- -
-
- {this.renderAvatar()} -
- -
{ - const el = this.inputEl.current; - if (el) { - el.click(); - } - }} - /> -
-
-
+ {this.renderAvatar()}
{ private renderAvatar() { const avatarPath = this.state.avatar; + const isPublic = this.props.isPublic; + + if (!isPublic) { + return undefined; + } return ( - +
+
+ +
+ +
{ + const el = this.inputEl.current; + if (el) { + el.click(); + } + }} + /> +
+
+
); } diff --git a/ts/components/session/SessionGroupSettings.tsx b/ts/components/session/SessionGroupSettings.tsx index cff0c7245..c5f50bef7 100644 --- a/ts/components/session/SessionGroupSettings.tsx +++ b/ts/components/session/SessionGroupSettings.tsx @@ -256,7 +256,7 @@ export class SessionGroupSettings extends React.Component { role="button" onClick={this.props.onUpdateGroupName} > - {window.i18n('editGroupName')} + {isPublic ? window.i18n('editGroupNameOrPicture') : window.i18n('editGroupName')}
)} {showUpdateGroupMembersButton && ( From ad682b588a2255bef6c645df0d3e21fbed2d5074 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 19 Feb 2020 16:35:38 +1100 Subject: [PATCH 035/107] make group image upload look same as edit profile upload --- stylesheets/_session.scss | 46 +++++++++---------- .../conversation/UpdateGroupNameDialog.tsx | 40 ++++++++-------- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index caae396ac..a3b282caa 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -1020,29 +1020,6 @@ label { } } - .image-upload-section { - display: flex; - align-items: center; - justify-content: center; - position: absolute; - cursor: pointer; - width: 80px; - height: 80px; - border-radius: 100%; - background-color: rgba($session-color-black, 0.72); - box-shadow: 0px 0px 3px 0.5px rgba(0, 0, 0, 0.75); - opacity: 0; - transition: $session-transition-duration; - - &:after { - content: 'Edit'; - } - - &:hover { - opacity: 1; - } - } - .session-id-section { display: flex; align-items: center; @@ -1099,6 +1076,29 @@ label { } } +.image-upload-section { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + cursor: pointer; + width: 80px; + height: 80px; + border-radius: 100%; + background-color: rgba($session-color-black, 0.72); + box-shadow: 0px 0px 3px 0.5px rgba(0, 0, 0, 0.75); + opacity: 0; + transition: $session-transition-duration; + + &:after { + content: 'Edit'; + } + + &:hover { + opacity: 1; + } +} + .qr-image { display: flex; justify-content: center; diff --git a/ts/components/conversation/UpdateGroupNameDialog.tsx b/ts/components/conversation/UpdateGroupNameDialog.tsx index 1f0d033ff..c6e9aa8f2 100644 --- a/ts/components/conversation/UpdateGroupNameDialog.tsx +++ b/ts/components/conversation/UpdateGroupNameDialog.tsx @@ -178,27 +178,25 @@ export class UpdateGroupNameDialog extends React.Component { i18n={this.props.i18n} size={80} /> -
- -
{ - const el = this.inputEl.current; - if (el) { - el.click(); - } - }} - /> -
-
+
{ + const el = this.inputEl.current; + if (el) { + el.click(); + } + }} + /> + +
); } From 1038a306ce56ed11fd94d8ef91909f4633c9083c Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Wed, 19 Feb 2020 20:47:37 +1100 Subject: [PATCH 036/107] add comment to view logic --- ts/components/session/SessionToggle.tsx | 13 +++++++++++++ .../session/settings/SessionSettings.tsx | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/ts/components/session/SessionToggle.tsx b/ts/components/session/SessionToggle.tsx index b9df7c319..54083e151 100644 --- a/ts/components/session/SessionToggle.tsx +++ b/ts/components/session/SessionToggle.tsx @@ -34,9 +34,17 @@ export class SessionToggle extends React.PureComponent { this.state = { active: active, }; + console.log('it is the constructor runs the first') } + + + + public render() { + + console.log(this.props, 'from Session Toggle') + return (
{ } }; + + //what does the following piece of code do? // + + //what is the window.comfirmationDialog doing in here? + if ( this.props.confirmationDialogParams && this.props.confirmationDialogParams.shouldShowConfirm() diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 8dfe17652..0d6d6c3b2 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -140,6 +140,10 @@ export class SettingsView extends React.Component { this.updateSetting(setting); }); + //define a bunch of function that will be used in the setting list. + //since setting elem itself is an array elem, this either becomes, onlick function + // or a function returned by others. + return (
{shouldRenderSettings && @@ -235,24 +239,38 @@ export class SettingsView extends React.Component { } public render() { + + console.log(this.props, 'From SessionSettings'); const { category } = this.props; const shouldRenderPasswordLock = this.state.shouldLockSettings && this.state.hasPassword; return ( + + //this is the section where the actually setting group of components gets render!
+ + {/* header is always rendered */} + +
+ + {/* some show lock logic is put in here to make sure that every time if you want to change the appearance */} {shouldRenderPasswordLock ? ( this.renderPasswordLock() + // ) : (
{this.renderSettingInCategory()} + {/* what gets rendered back from calling renderSettingInCategory */}
)} + + {/* session info is always shown in here */} {this.renderSessionInfo()}
@@ -266,6 +284,7 @@ export class SettingsView extends React.Component { this.setState({ scaleValue:scaleVal }) + window.setSettingValue('') } From 96e9a68abdaa1438d94cd442d3b1dd9e986a4cd6 Mon Sep 17 00:00:00 2001 From: Brian Date: Thu, 20 Feb 2020 00:08:47 +1100 Subject: [PATCH 037/107] add zoomfactor to main --- js/background.js | 9 +++++++++ main.js | 3 +++ preload.js | 5 +++++ .../session/settings/SessionSettings.tsx | 17 ++++++++++++++++- 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/js/background.js b/js/background.js index 123dca978..983afc0c0 100644 --- a/js/background.js +++ b/js/background.js @@ -324,6 +324,15 @@ storage.put('message-ttl', ttl); }, + + getZoomFactor: () => storage.get('zoom-factor-setting', 48), + setZoomFactor: value => { + // Make sure the ttl is between a given range and is valid + const intValue = parseInt(value, 10); + const factor = Number.isNaN(intValue) ? 24 : intValue; + storage.put('zoom-factor-setting', factor); + }, + getReadReceiptSetting: () => storage.get('read-receipt-setting'), setReadReceiptSetting: value => storage.put('read-receipt-setting', value), diff --git a/main.js b/main.js index 2eb625db8..86866b1bd 100644 --- a/main.js +++ b/main.js @@ -577,6 +577,7 @@ let settingsWindow; async function showSettingsWindow() { if (settingsWindow) { settingsWindow.show(); + console.log(window.getSettingValue('zoom-factor-setting'), 'from settingsWindow') return; } if (!mainWindow) { @@ -836,6 +837,7 @@ async function showMainWindow(sqlKey, passwordAttempt = false) { createWindow(); + if (usingTrayIcon) { tray = createTrayIcon(getMainWindow, locale.messages); } @@ -1025,6 +1027,7 @@ ipc.on('password-window-login', async (event, passPhrase) => { const passwordAttempt = true; await showMainWindow(passPhrase, passwordAttempt); sendResponse(); + if (passwordWindow) { passwordWindow.close(); passwordWindow = null; diff --git a/preload.js b/preload.js index 8fb3936d1..78ec9a2a6 100644 --- a/preload.js +++ b/preload.js @@ -228,6 +228,11 @@ window.getMessageTTL = () => window.storage.get('message-ttl', 24); installGetter('message-ttl', 'getMessageTTL'); installSetter('message-ttl', 'setMessageTTL'); +// Get the zoom Factor setting +window.getZoomFactor = () => window.storage.get('zoom-factor-setting',50) +installGetter('zoom-factor-setting', 'getZoomFactor'); +installSetter('zoom-factor-setting', 'setZoomFactor'); + installGetter('read-receipt-setting', 'getReadReceiptSetting'); installSetter('read-receipt-setting', 'setReadReceiptSetting'); diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 0d6d6c3b2..4524fd167 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -490,6 +490,21 @@ export class SettingsView extends React.Component { }, confirmationDialogParams: undefined, }, + { + id: 'zoom-factor-setting', + title: window.i18n('zoomFactorSettingTitle'), + description: window.i18n('zoomFactorSettingTitleDescription'), + hidden: false, + type: SessionSettingType.Slider, + category: SessionSettingCategory.Appearance, + setFn: undefined, + comparisonValue: undefined, + onClick: undefined, + content: { + defaultValue: 24, + }, + confirmationDialogParams: undefined, + }, { id: 'read-receipt-setting', title: window.i18n('readReceiptSettingTitle'), @@ -575,7 +590,7 @@ export class SettingsView extends React.Component { onSuccess: this.onPasswordUpdated, }), confirmationDialogParams: undefined, - }, + } ]; } From e0ff1755acf6f5435bcbc1308026c5ea325caa27 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 20 Feb 2020 09:24:07 +1100 Subject: [PATCH 038/107] lint --- _locales/en/messages.json | 6 ++++-- js/modules/loki_app_dot_net_api.js | 10 +++++++--- ts/components/conversation/UpdateGroupNameDialog.tsx | 2 +- ts/components/session/SessionGroupSettings.tsx | 4 +++- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index a7e6d2766..6d93306bb 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2206,11 +2206,13 @@ }, "editGroupNameOrPicture": { "message": "Edit group name or picture", - "description": "Button action that the user can click to edit a group name (open)" + "description": + "Button action that the user can click to edit a group name (open)" }, "editGroupName": { "message": "Edit group name", - "description": "Button action that the user can click to edit a group name (closed)" + "description": + "Button action that the user can click to edit a group name (closed)" }, "createGroupDialogTitle": { "message": "Creating a Closed Group", diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 75b2ebb43..ff63a3980 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -1290,9 +1290,13 @@ class LokiPublicChannelAPI { this.conversation.setSubscriberCount(data.counts.subscribers); } - await window.Signal.Data.updateConversation(this.conversation.id, this.conversation.attributes, { - Conversation: Whisper.Conversation, - }); + await window.Signal.Data.updateConversation( + this.conversation.id, + this.conversation.attributes, + { + Conversation: Whisper.Conversation, + } + ); await this.pollForChannelOnce(); } diff --git a/ts/components/conversation/UpdateGroupNameDialog.tsx b/ts/components/conversation/UpdateGroupNameDialog.tsx index c6e9aa8f2..facdef321 100644 --- a/ts/components/conversation/UpdateGroupNameDialog.tsx +++ b/ts/components/conversation/UpdateGroupNameDialog.tsx @@ -197,7 +197,7 @@ export class UpdateGroupNameDialog extends React.Component { onChange={this.onFileSelected} />
-
+
); } diff --git a/ts/components/session/SessionGroupSettings.tsx b/ts/components/session/SessionGroupSettings.tsx index c5f50bef7..123f17d23 100644 --- a/ts/components/session/SessionGroupSettings.tsx +++ b/ts/components/session/SessionGroupSettings.tsx @@ -256,7 +256,9 @@ export class SessionGroupSettings extends React.Component { role="button" onClick={this.props.onUpdateGroupName} > - {isPublic ? window.i18n('editGroupNameOrPicture') : window.i18n('editGroupName')} + {isPublic + ? window.i18n('editGroupNameOrPicture') + : window.i18n('editGroupName')}
)} {showUpdateGroupMembersButton && ( From 15738c352567bd2a3546a8e09496ea3bca3be96c Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 20 Feb 2020 13:14:12 +1100 Subject: [PATCH 039/107] display continue session signin with seed --- _locales/en/messages.json | 3 +++ js/views/invite_friends_dialog_view.js | 5 +++- .../session/LeftPaneChannelSection.tsx | 24 ++++++++++++------- ts/components/session/RegistrationTabs.tsx | 4 +++- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 5ee80af32..a666d6f66 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2597,6 +2597,9 @@ "message": "Enter other device’s Session ID here" }, "continueYourSession": { + "message": "Continue Your Session" + }, + "linkDevice": { "message": "Link Device" }, "restoreSessionID": { diff --git a/js/views/invite_friends_dialog_view.js b/js/views/invite_friends_dialog_view.js index 1cc7c0ec0..ddf8d5745 100644 --- a/js/views/invite_friends_dialog_view.js +++ b/js/views/invite_friends_dialog_view.js @@ -74,7 +74,10 @@ newMembers.length + existingMembers.length > window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT ) { - const msg = window.i18n('maxGroupMembersError', window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT); + const msg = window.i18n( + 'maxGroupMembersError', + window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT + ); window.pushToast({ title: msg, diff --git a/ts/components/session/LeftPaneChannelSection.tsx b/ts/components/session/LeftPaneChannelSection.tsx index 569d9bec1..fd543ebef 100644 --- a/ts/components/session/LeftPaneChannelSection.tsx +++ b/ts/components/session/LeftPaneChannelSection.tsx @@ -399,13 +399,18 @@ export class LeftPaneChannelSection extends React.Component { groupMembers: Array ) { // Validate groupName and groupMembers length - if (groupName.length === 0 || - groupName.length > window.CONSTANTS.MAX_GROUP_NAME_LENGTH) { - window.pushToast({ - title: window.i18n('invalidGroupName', window.CONSTANTS.MAX_GROUP_NAME_LENGTH), - type: 'error', - id: 'invalidGroupName', - }); + if ( + groupName.length === 0 || + groupName.length > window.CONSTANTS.MAX_GROUP_NAME_LENGTH + ) { + window.pushToast({ + title: window.i18n( + 'invalidGroupName', + window.CONSTANTS.MAX_GROUP_NAME_LENGTH + ), + type: 'error', + id: 'invalidGroupName', + }); return; } @@ -416,7 +421,10 @@ export class LeftPaneChannelSection extends React.Component { groupMembers.length >= window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT ) { window.pushToast({ - title: window.i18n('invalidGroupSize', window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT), + title: window.i18n( + 'invalidGroupSize', + window.CONSTANTS.SMALL_GROUP_SIZE_LIMIT + ), type: 'error', id: 'invalidGroupSize', }); diff --git a/ts/components/session/RegistrationTabs.tsx b/ts/components/session/RegistrationTabs.tsx index eee4c1a5e..f5ef06b68 100644 --- a/ts/components/session/RegistrationTabs.tsx +++ b/ts/components/session/RegistrationTabs.tsx @@ -585,6 +585,7 @@ export class RegistrationTabs extends React.Component<{}, State> { } = this.state; let enableContinue = true; + let text = window.i18n('continueYourSession'); const displayNameOK = !displayNameError && !!displayName; //display name required const mnemonicOK = !mnemonicError && !!mnemonicSeed; //Mnemonic required const passwordsOK = @@ -593,6 +594,7 @@ export class RegistrationTabs extends React.Component<{}, State> { enableContinue = displayNameOK && mnemonicOK && passwordsOK; } else if (signInMode === SignInMode.LinkingDevice) { enableContinue = !!primaryDevicePubKey; + text = window.i18n('linkDevice'); } else if (signUpMode === SignUpMode.EnterDetails) { enableContinue = displayNameOK && passwordsOK; } @@ -604,7 +606,7 @@ export class RegistrationTabs extends React.Component<{}, State> { }} buttonType={SessionButtonType.Brand} buttonColor={SessionButtonColor.Green} - text={window.i18n('continueYourSession')} + text={text} disabled={!enableContinue} /> ); From b2322bae022c2a0f8ff4854e8fc7ef94f69658ed Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Thu, 20 Feb 2020 14:46:54 +1100 Subject: [PATCH 040/107] check to whether the zoomFactor is shown in preload.js --- js/background.js | 14 +++++++------- preload.js | 19 ++++++++++++++++--- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/js/background.js b/js/background.js index 983afc0c0..c690013fc 100644 --- a/js/background.js +++ b/js/background.js @@ -325,13 +325,13 @@ }, - getZoomFactor: () => storage.get('zoom-factor-setting', 48), - setZoomFactor: value => { - // Make sure the ttl is between a given range and is valid - const intValue = parseInt(value, 10); - const factor = Number.isNaN(intValue) ? 24 : intValue; - storage.put('zoom-factor-setting', factor); - }, + // getZoomFactor: () => storage.get('zoom-factor-setting', 48), + // setZoomFactor: value => { + // // Make sure the ttl is between a given range and is valid + // const intValue = parseInt(value, 10); + // const factor = Number.isNaN(intValue) ? 24 : intValue; + // storage.put('zoom-factor-setting', factor); + // }, getReadReceiptSetting: () => storage.get('read-receipt-setting'), setReadReceiptSetting: value => diff --git a/preload.js b/preload.js index 78ec9a2a6..7f2c54bbb 100644 --- a/preload.js +++ b/preload.js @@ -81,6 +81,11 @@ window.wrapDeferred = deferredToPromise; const ipc = electron.ipcRenderer; const localeMessages = ipc.sendSync('locale-data'); + +Window.updateScaling = (number) => { + ipc.s +} + window.setBadgeCount = count => ipc.send('set-badge-count', count); // Set the password for the database @@ -160,6 +165,9 @@ ipc.on('on-unblock-number', (event, number) => { window.closeAbout = () => ipc.send('close-about'); window.readyForUpdates = () => ipc.send('ready-for-updates'); + + + window.updateTrayIcon = unreadCount => ipc.send('update-tray-icon', unreadCount); @@ -177,6 +185,9 @@ ipc.on('set-up-as-standalone', () => { // Settings-related events +console.log(window.getSettingValue('zoom-factor-setting'), 'from preloadJS and get the setting value'); + + window.showSettings = () => ipc.send('show-settings'); window.showPermissionsPopup = () => ipc.send('show-permissions-popup'); @@ -212,6 +223,8 @@ window.getSettingValue = (settingID, comparisonValue = null) => { return comparisonValue ? !!settingVal === comparisonValue : settingVal; }; + + window.setSettingValue = (settingID, value) => { window.storage.put(settingID, value); }; @@ -229,9 +242,9 @@ installGetter('message-ttl', 'getMessageTTL'); installSetter('message-ttl', 'setMessageTTL'); // Get the zoom Factor setting -window.getZoomFactor = () => window.storage.get('zoom-factor-setting',50) -installGetter('zoom-factor-setting', 'getZoomFactor'); -installSetter('zoom-factor-setting', 'setZoomFactor'); +// window.getZoomFactor = () => window.storage.get('zoom-factor-setting',50) +// installGetter('zoom-factor-setting', 'getZoomFactor'); +// installSetter('zoom-factor-setting', 'setZoomFactor'); installGetter('read-receipt-setting', 'getReadReceiptSetting'); installSetter('read-receipt-setting', 'setReadReceiptSetting'); From 7a0f2c9ce314f3f9ebef95434ab4cae34e8e29fe Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Thu, 20 Feb 2020 17:15:39 +1100 Subject: [PATCH 041/107] add electron webFrame API to preloadJS --- preload.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/preload.js b/preload.js index 7f2c54bbb..927ac7b32 100644 --- a/preload.js +++ b/preload.js @@ -2,6 +2,7 @@ /* global window: false */ const path = require('path'); const electron = require('electron'); +const {webFrame} = electron; const semver = require('semver'); const { deferredToPromise } = require('./js/modules/deferred_to_promise'); @@ -69,6 +70,7 @@ window.CONSTANTS = { MAX_CONNECTION_DURATION: 5000, }; + window.versionInfo = { environment: window.getEnvironment(), version: window.getVersion(), @@ -82,10 +84,15 @@ const ipc = electron.ipcRenderer; const localeMessages = ipc.sendSync('locale-data'); -Window.updateScaling = (number) => { - ipc.s +window.setScaling = (number) => { + return webFrame.setZoomFactor(number); +} + +window.getScaling = () => { + return webFrame.getZoomFactor(); } + window.setBadgeCount = count => ipc.send('set-badge-count', count); // Set the password for the database @@ -185,7 +192,7 @@ ipc.on('set-up-as-standalone', () => { // Settings-related events -console.log(window.getSettingValue('zoom-factor-setting'), 'from preloadJS and get the setting value'); + window.showSettings = () => ipc.send('show-settings'); @@ -229,6 +236,9 @@ window.setSettingValue = (settingID, value) => { window.storage.put(settingID, value); }; + + + installGetter('device-name', 'getDeviceName'); installGetter('theme-setting', 'getThemeSetting'); From cb7289d3d868c813fedaf9d2572d98798d581eb1 Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Fri, 21 Feb 2020 10:37:58 +1100 Subject: [PATCH 042/107] working but buggy --- preload.js | 4 +-- .../settings/SessionSettingListItem.tsx | 31 ++++++++++++++++--- .../session/settings/SessionSettings.tsx | 19 ------------ ts/global.d.ts | 2 ++ 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/preload.js b/preload.js index 927ac7b32..3637bf4ea 100644 --- a/preload.js +++ b/preload.js @@ -84,11 +84,11 @@ const ipc = electron.ipcRenderer; const localeMessages = ipc.sendSync('locale-data'); -window.setScaling = (number) => { +window.setZoomFactor = (number) => { return webFrame.setZoomFactor(number); } -window.getScaling = () => { +window.getZoomFactor = () => { return webFrame.getZoomFactor(); } diff --git a/ts/components/session/settings/SessionSettingListItem.tsx b/ts/components/session/settings/SessionSettingListItem.tsx index db80bb296..667928d5c 100644 --- a/ts/components/session/settings/SessionSettingListItem.tsx +++ b/ts/components/session/settings/SessionSettingListItem.tsx @@ -88,7 +88,7 @@ export class SessionSettingListItem extends React.Component { /> )} - {type === SessionSettingType.Slider && ( + {type === SessionSettingType.Slider && title === 'messageTTL' ? (
{

{`${currentSliderValue} Hours`}

- )} -
-
- See me in here + ):type === SessionSettingType.Slider ? ( +
+ + { + this.handleSlider(sliderValue); + }} + /> +
+

{`% ${currentSliderValue} Zoom Level`}

+
+
+ ): + null}
); @@ -128,5 +143,11 @@ export class SessionSettingListItem extends React.Component { this.setState({ sliderValue: value, }); + + if(this.props.title !== 'messageTTL' && this.state.sliderValue!==null) { + window.setZoomFactor(this.state.sliderValue/100) + } + + } } diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 4524fd167..1a88ada2d 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -35,7 +35,6 @@ interface State { pwdLockError: string | null; shouldLockSettings: boolean | null; linkedPubKeys: Array; - scaleValue: number; } interface LocalSettingType { @@ -63,7 +62,6 @@ export class SettingsView extends React.Component { pwdLockError: null, shouldLockSettings: true, linkedPubKeys: new Array(), - scaleValue: 200 }; this.settingsViewRef = React.createRef(); @@ -74,7 +72,6 @@ export class SettingsView extends React.Component { this.refreshLinkedDevice = this.refreshLinkedDevice.bind(this); this.onKeyUp = this.onKeyUp.bind(this); - this.handleScaleChange = this.handleScaleChange.bind(this) window.addEventListener('keyup', this.onKeyUp); } @@ -164,11 +161,6 @@ export class SettingsView extends React.Component {
); })} - -
- -
-
Scale: {this.state.scaleValue}
); } @@ -277,17 +269,6 @@ export class SettingsView extends React.Component { ); } - - public handleScaleChange(event:any):any { - const {value} = event.target; - let scaleVal:number = parseInt(value,10); - this.setState({ - scaleValue:scaleVal - }) - window.setSettingValue('') - } - - public renderSessionInfo(): JSX.Element { return (
diff --git a/ts/global.d.ts b/ts/global.d.ts index 882a32e3f..fbc071720 100644 --- a/ts/global.d.ts +++ b/ts/global.d.ts @@ -11,6 +11,7 @@ interface Window { mnemonic: any; clipboard: any; attemptConnection: any; + setZoomFactor: any; passwordUtil: any; userConfig: any; @@ -58,6 +59,7 @@ interface Window { lokiFeatureFlags: any; resetDatabase: any; + } interface Promise { From a80c6e30e20fd1e8b10530b344ceff8c0b262f44 Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Fri, 21 Feb 2020 10:53:57 +1100 Subject: [PATCH 043/107] add slider title to message.json --- _locales/en/messages.json | 4 ++++ ts/components/session/settings/SessionSettingListItem.tsx | 2 +- ts/components/session/settings/SessionSettings.tsx | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 34a519aa4..b7155a8b0 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1471,6 +1471,10 @@ "Warning! Lowering the TTL could result in messages being lost if the recipient doesn't collect them in time!", "description": "Warning for the time to live setting" }, + "zoomFactorSettingTitle": { + "message": "Zoom Factor", + "description": "Title of the Zoom Factor setting" + }, "notificationSettingsDialog": { "message": "When messages arrive, display notifications that reveal...", "description": "Explain the purpose of the notification settings" diff --git a/ts/components/session/settings/SessionSettingListItem.tsx b/ts/components/session/settings/SessionSettingListItem.tsx index 667928d5c..c84c047e5 100644 --- a/ts/components/session/settings/SessionSettingListItem.tsx +++ b/ts/components/session/settings/SessionSettingListItem.tsx @@ -114,7 +114,7 @@ export class SessionSettingListItem extends React.Component { min={60} max={200} defaultValue={currentSliderValue} - onAfterChange={sliderValue => { + onChange={sliderValue => { this.handleSlider(sliderValue); }} /> diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 1a88ada2d..8d493ef5a 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -474,7 +474,7 @@ export class SettingsView extends React.Component { { id: 'zoom-factor-setting', title: window.i18n('zoomFactorSettingTitle'), - description: window.i18n('zoomFactorSettingTitleDescription'), + description: undefined, hidden: false, type: SessionSettingType.Slider, category: SessionSettingCategory.Appearance, From ff45fb27f2f6a85103f58c566095fb475cd7d942 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 20 Feb 2020 15:35:15 +1100 Subject: [PATCH 044/107] Set a bg color to the mainwindow Might improve sharpness of font rendering @see https://github.com/electron/electron/blob/master/docs/faq.md#the-font-looks-blurry-what-is-this-and-what-can-i-do --- main.js | 1 + 1 file changed, 1 insertion(+) diff --git a/main.js b/main.js index 3912076af..07cf10bbd 100644 --- a/main.js +++ b/main.js @@ -227,6 +227,7 @@ function createWindow() { minWidth: MIN_WIDTH, minHeight: MIN_HEIGHT, autoHideMenuBar: false, + backgroundColor: '#fff', webPreferences: { nodeIntegration: false, nodeIntegrationInWorker: false, From 6d03a63d4a796e73df62af782729faa632260eb8 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 21 Feb 2020 15:09:16 +1100 Subject: [PATCH 045/107] add sync of open groups on device link --- libloki/api.js | 24 +++++++++++++++++++++ libtextsecure/account_manager.js | 1 + libtextsecure/message_receiver.js | 8 +++++++ libtextsecure/sendmessage.js | 35 +++++++++++++++++++++++++++++++ protos/SignalService.proto | 24 +++++++++++++-------- 5 files changed, 83 insertions(+), 9 deletions(-) diff --git a/libloki/api.js b/libloki/api.js index ef1baccf4..b18746ef8 100644 --- a/libloki/api.js +++ b/libloki/api.js @@ -193,6 +193,29 @@ }); return syncMessage; } + function createOpenGroupsSyncProtoMessage(conversations) { + // We only want to sync across open groups that we haven't left + const sessionOpenGroups = conversations.filter( + c => c.isPublic() && !c.isRss() && !c.get('left') + ); + + if (sessionOpenGroups.length === 0) { + return null; + } + + const openGroups = sessionOpenGroups.map( + conversation => + new textsecure.protobuf.SyncMessage.OpenGroupDetails({ + url: conversation.id.split('@').pop(), + channelId: conversation.get('channelId'), + }) + ); + + const syncMessage = new textsecure.protobuf.SyncMessage({ + openGroups, + }); + return syncMessage; + } async function sendPairingAuthorisation(authorisation, recipientPubKey) { const pairingAuthorisation = createPairingAuthorisationProtoMessage( authorisation @@ -257,5 +280,6 @@ sendUnpairingMessageToSecondary, createContactSyncProtoMessage, createGroupSyncProtoMessage, + createOpenGroupsSyncProtoMessage, }; })(); diff --git a/libtextsecure/account_manager.js b/libtextsecure/account_manager.js index 088422630..b26ceda99 100644 --- a/libtextsecure/account_manager.js +++ b/libtextsecure/account_manager.js @@ -638,6 +638,7 @@ const conversations = window.getConversations().models; textsecure.messaging.sendContactSyncMessage(conversations); textsecure.messaging.sendGroupSyncMessage(conversations); + textsecure.messaging.sendOpenGroupsSyncMessage(conversations); }, validatePubKeyHex(pubKey) { const c = new Whisper.Conversation({ diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 81adb351f..8397df5f8 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -1505,6 +1505,8 @@ MessageReceiver.prototype.extend({ return this.handleContacts(envelope, syncMessage.contacts); } else if (syncMessage.groups) { return this.handleGroups(envelope, syncMessage.groups); + } else if (syncMessage.openGroups) { + return this.handleOpenGroups(envelope, syncMessage.openGroups); } else if (syncMessage.blocked) { return this.handleBlocked(envelope, syncMessage.blocked); } else if (syncMessage.request) { @@ -1606,6 +1608,12 @@ MessageReceiver.prototype.extend({ }); }); }, + handleOpenGroups(envelope, openGroups) { + openGroups.forEach(({ url, channelId }) => { + window.attemptConnection(url, channelId); + }); + return this.removeFromCache(envelope); + }, handleBlocked(envelope, blocked) { window.log.info('Setting these numbers as blocked:', blocked.numbers); textsecure.storage.put('blocked', blocked.numbers); diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index 3d39d472d..8775c9987 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -727,6 +727,38 @@ MessageSender.prototype = { return Promise.all(syncPromises); }, + sendOpenGroupsSyncMessage(conversations) { + // If we havn't got a primaryDeviceKey then we are in the middle of pairing + // primaryDevicePubKey is set to our own number if we are the master device + const primaryDeviceKey = window.storage.get('primaryDevicePubKey'); + if (!primaryDeviceKey) { + return Promise.resolve(); + } + + // Send the whole list of open groups in a single message + + const openGroupsSyncMessage = libloki.api.createOpenGroupsSyncProtoMessage( + conversations + ); + + if (!openGroupsSyncMessage) { + window.log.info('No open groups to sync'); + return Promise.resolve(); + } + + const contentMessage = new textsecure.protobuf.Content(); + contentMessage.syncMessage = openGroupsSyncMessage; + + const silent = true; + return this.sendIndividualProto( + primaryDeviceKey, + contentMessage, + Date.now(), + silent, + {} // options + ); + }, + sendRequestContactSyncMessage(options) { const myNumber = textsecure.storage.user.getNumber(); const myDevice = textsecure.storage.user.getDeviceId(); @@ -1287,6 +1319,9 @@ textsecure.MessageSender = function MessageSenderWrapper(username, password) { ); this.sendContactSyncMessage = sender.sendContactSyncMessage.bind(sender); this.sendGroupSyncMessage = sender.sendGroupSyncMessage.bind(sender); + this.sendOpenGroupsSyncMessage = sender.sendOpenGroupsSyncMessage.bind( + sender + ); this.sendRequestConfigurationSyncMessage = sender.sendRequestConfigurationSyncMessage.bind( sender ); diff --git a/protos/SignalService.proto b/protos/SignalService.proto index f383448fe..e8f96edfe 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -314,15 +314,21 @@ message SyncMessage { optional bool linkPreviews = 4; } - optional Sent sent = 1; - optional Contacts contacts = 2; - optional Groups groups = 3; - optional Request request = 4; - repeated Read read = 5; - optional Blocked blocked = 6; - optional Verified verified = 7; - optional Configuration configuration = 9; - optional bytes padding = 8; + message OpenGroupDetails { + optional string url = 1; + optional uint32 channelId = 2; + } + + optional Sent sent = 1; + optional Contacts contacts = 2; + optional Groups groups = 3; + optional Request request = 4; + repeated Read read = 5; + optional Blocked blocked = 6; + optional Verified verified = 7; + optional Configuration configuration = 9; + optional bytes padding = 8; + repeated OpenGroupDetails openGroups = 100; } message AttachmentPointer { From b877dab7de8145ae552d2746ae1bdde8ba5cadd7 Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Mon, 24 Feb 2020 10:46:27 +1100 Subject: [PATCH 046/107] refactor the code related to checking slider type --- .../session/settings/SessionSettingListItem.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/ts/components/session/settings/SessionSettingListItem.tsx b/ts/components/session/settings/SessionSettingListItem.tsx index c84c047e5..e7bc66e4a 100644 --- a/ts/components/session/settings/SessionSettingListItem.tsx +++ b/ts/components/session/settings/SessionSettingListItem.tsx @@ -88,7 +88,7 @@ export class SessionSettingListItem extends React.Component { /> )} - {type === SessionSettingType.Slider && title === 'messageTTL' ? ( + {type === SessionSettingType.Slider && title === 'Message TTL' ? (
{

{`${currentSliderValue} Hours`}

- ):type === SessionSettingType.Slider ? ( + ):type === SessionSettingType.Slider && title === "Zoom Factor" ? (
- { this.handleSlider(sliderValue); }} /> +
-

{`% ${currentSliderValue} Zoom Level`}

-
-
+

{`% ${currentSliderValue} Zoom Level`}

+
+
): + null}
@@ -144,7 +145,9 @@ export class SessionSettingListItem extends React.Component { sliderValue: value, }); - if(this.props.title !== 'messageTTL' && this.state.sliderValue!==null) { + console.log(this.props.title, 'from here') + + if(this.props.title === 'Zoom Factor' && this.state.sliderValue!==null) { window.setZoomFactor(this.state.sliderValue/100) } From d19be4568556cef1e4e4f8ab447cc0cfa83513fa Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Sun, 23 Feb 2020 18:17:17 -0800 Subject: [PATCH 047/107] add simple lock around lokiPublicChatAPI binding (on top of the clearing, only one is probably needed) --- libtextsecure/message_receiver.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 8397df5f8..0e6204016 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -24,6 +24,8 @@ /* eslint-disable more/no-then */ /* eslint-disable no-unreachable */ +let openGroupBound = false; + function MessageReceiver(username, password, signalingKey, options = {}) { this.count = 0; @@ -51,11 +53,15 @@ function MessageReceiver(username, password, signalingKey, options = {}) { // only do this once to prevent duplicates if (lokiPublicChatAPI) { - // bind events - lokiPublicChatAPI.on( - 'publicMessage', - this.handleUnencryptedMessage.bind(this) - ); + window.log.info('Binding open group events handler', openGroupBound); + if (!openGroupBound) { + // clear any previous binding + lokiPublicChatAPI.removeAllListeners('publicMessage'); + // we only need one MR in the system handling these + // bind events + lokiPublicChatAPI.on('publicMessage', this.handleUnencryptedMessage.bind(this)); + openGroupBound = true; + } } else { window.log.error('Can not handle open group data, API is not available'); } From f0760b22eaf6c3c8dacc9b96eee2bf86ff30cf31 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 24 Feb 2020 16:21:10 +1100 Subject: [PATCH 048/107] Make closed groups show up in contacts left panel --- js/background.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/background.js b/js/background.js index 5ce84a8fc..e1edcb968 100644 --- a/js/background.js +++ b/js/background.js @@ -993,7 +993,9 @@ let friendList = contacts; if (friendList !== undefined) { friendList = friendList.filter( - friend => friend.type === 'direct' && !friend.isMe + friend => + (friend.type === 'direct' && !friend.isMe) || + (friend.type === 'group' && !friend.isPublic && !friend.isRss) ); } return friendList; From c2517adc8997d36d8736ce0b36c033a2c572afcd Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 24 Feb 2020 16:40:26 +1100 Subject: [PATCH 049/107] No underline under contacts title --- stylesheets/_session_left_pane.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/stylesheets/_session_left_pane.scss b/stylesheets/_session_left_pane.scss index 4887ffad3..c5fc424a9 100644 --- a/stylesheets/_session_left_pane.scss +++ b/stylesheets/_session_left_pane.scss @@ -215,7 +215,6 @@ $session-compose-margin: 20px; &__list { height: -webkit-fill-available; - border-top: 1px solid rgba(255, 255, 255, 0.05); &-popup { width: -webkit-fill-available; From f2f4b4be7034feaf83771fbe13d3e41002ccf585 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 24 Feb 2020 17:11:43 +1100 Subject: [PATCH 050/107] move pill divider in react and fix the bg --- stylesheets/_session_left_pane.scss | 12 +++++++++--- ts/components/EditProfileDialog.tsx | 5 ++--- ts/components/session/PillDivider.tsx | 16 ++++++++++++++++ ts/components/session/SessionClosableOverlay.tsx | 5 ++--- 4 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 ts/components/session/PillDivider.tsx diff --git a/stylesheets/_session_left_pane.scss b/stylesheets/_session_left_pane.scss index 4887ffad3..af3a5098a 100644 --- a/stylesheets/_session_left_pane.scss +++ b/stylesheets/_session_left_pane.scss @@ -642,14 +642,20 @@ $session-compose-margin: 20px; .panel-text-divider { width: 100%; text-align: center; - border-bottom: 1px solid $session-color-dark-grey; - line-height: 0.1em; + display: flex; margin: 50px 0 50px; + .panel-text-divider-line { + border-bottom: 1px solid $session-color-dark-grey; + line-height: 0.1em; + flex-grow: 1; + height: 1px; + align-self: center; + } + span { padding: 5px 10px; border-radius: 50px; - background-color: $session-background; color: $session-color-light-grey; border: 1px solid $session-color-dark-grey; font-family: 'SF Pro Text'; diff --git a/ts/components/EditProfileDialog.tsx b/ts/components/EditProfileDialog.tsx index 8cf9f333a..54abe0337 100644 --- a/ts/components/EditProfileDialog.tsx +++ b/ts/components/EditProfileDialog.tsx @@ -16,6 +16,7 @@ import { SessionIconType, } from './session/icon'; import { SessionModal } from './session/SessionModal'; +import { PillDivider } from './session/PillDivider'; declare global { interface Window { @@ -107,9 +108,7 @@ export class EditProfileDialog extends React.Component { {viewEdit && this.renderEditView()}
-
- {window.i18n('yourSessionID')} -
+

= props => { + return ( +

+
+ {props.text} +
+
+ ); +}; diff --git a/ts/components/session/SessionClosableOverlay.tsx b/ts/components/session/SessionClosableOverlay.tsx index dc72290e3..de9119337 100644 --- a/ts/components/session/SessionClosableOverlay.tsx +++ b/ts/components/session/SessionClosableOverlay.tsx @@ -12,6 +12,7 @@ import { } from './SessionButton'; import { SessionSpinner } from './SessionSpinner'; import { SessionGroupType } from './LeftPaneChannelSection'; +import { PillDivider } from './PillDivider'; interface Props { overlayMode: 'message' | 'contact' | SessionGroupType; @@ -223,9 +224,7 @@ export class SessionClosableOverlay extends React.Component { )} {isAddContactView && ( -
- {window.i18n('yourPublicKey')} -
+ )} {isAddContactView && ( From cb18a48d5013c3cde543acef2b4fe6704b0d4656 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 24 Feb 2020 17:29:07 +1100 Subject: [PATCH 051/107] fix event propogation on links in messages --- ts/components/conversation/Linkify.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ts/components/conversation/Linkify.tsx b/ts/components/conversation/Linkify.tsx index 37313d478..558bad7ff 100644 --- a/ts/components/conversation/Linkify.tsx +++ b/ts/components/conversation/Linkify.tsx @@ -67,7 +67,7 @@ export class Linkify extends React.Component { !HAS_AT.test(url) ) { results.push( - + {originalText} ); @@ -85,4 +85,10 @@ export class Linkify extends React.Component { return results; } + + // disable click on elements so clicking a message containing a link doesn't + // select the message.The link will still be opened in the browser. + public handleClick = (e: any) => { + e.stopPropagation(); + }; } From c731241689e7807d24e922d4c0bf2edee83c7a8a Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 24 Feb 2020 19:23:30 +0100 Subject: [PATCH 052/107] Remove search icon in message view --- .../conversation/ConversationHeader.tsx | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index ad5a59e56..678226871 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -254,19 +254,6 @@ export class ConversationHeader extends React.Component { ); } - public renderSearch() { - return ( -
- -
- ); - } - public renderOptions(triggerId: string) { const { showBackButton } = this.props; @@ -394,7 +381,6 @@ export class ConversationHeader extends React.Component { {!this.props.isRss && ( <> - {this.renderSearch()} {this.renderAvatar()} )} @@ -411,12 +397,6 @@ export class ConversationHeader extends React.Component { } } - public highlightMessageSearch() { - // This is a temporary fix. In future we want to search - // messages in the current conversation - $('.session-search-input input').focus(); - } - private renderPublicMenuItems() { const { i18n, From 05a15dc9cdc7b740c1b3ead8f2a50782d5fbb2e3 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 24 Feb 2020 19:50:31 +0100 Subject: [PATCH 053/107] Small Code formating --- ts/components/conversation/ConversationHeader.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index 678226871..9ffd481b8 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -379,11 +379,7 @@ export class ConversationHeader extends React.Component {
{this.renderExpirationLength()} - {!this.props.isRss && ( - <> - {this.renderAvatar()} - - )} + {!this.props.isRss && <>{this.renderAvatar()}} {this.renderMenu(triggerId)}
From 8e0f2807246d60e9261e491eead2e97df9f2337b Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Mon, 24 Feb 2020 15:41:09 -0800 Subject: [PATCH 054/107] convert more Signal references to Session --- _locales/en/messages.json | 42 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 5ee80af32..5b765cac3 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -227,7 +227,7 @@ }, "loadDataDescription": { "message": - "You've just gone through the export process, and your contacts and messages are waiting patiently on your computer. Select the folder that contains your saved Signal data.", + "You've just gone through the export process, and your contacts and messages are waiting patiently on your computer. Select the folder that contains your saved Session data.", "description": "Introduction to the process of importing messages and contacts from disk" }, @@ -246,7 +246,7 @@ }, "importErrorFirst": { "message": - "Make sure you have chosen the correct directory that contains your saved Signal data. Its name should begin with 'Signal Export.' You can also save a new copy of your data from the Chrome App.", + "Make sure you have chosen the correct directory that contains your saved Session data. Its name should begin with 'Session Export.' You can also save a new copy of your data from the Chrome App.", "description": "Message shown if the import went wrong; first paragraph" }, "importErrorSecond": { @@ -403,13 +403,13 @@ }, "changedSinceVerifiedMultiple": { "message": - "Your safety numbers with multiple group members have changed since you last verified. This could mean that someone is trying to intercept your communication or that they have simply reinstalled Signal.", + "Your safety numbers with multiple group members have changed since you last verified. This could mean that someone is trying to intercept your communication or that they have simply reinstalled Session.", "description": "Shown on confirmation dialog when user attempts to send a message" }, "changedSinceVerified": { "message": - "Your safety number with $name$ has changed since you last verified. This could mean that someone is trying to intercept your communication or that $name$ has simply reinstalled Signal.", + "Your safety number with $name$ has changed since you last verified. This could mean that someone is trying to intercept your communication or that $name$ has simply reinstalled Session.", "description": "Shown on confirmation dialog when user attempts to send a message", "placeholders": { @@ -421,7 +421,7 @@ }, "changedRightAfterVerify": { "message": - "The safety number you are trying to verify has changed. Please review your new safety number with $name$. Remember, this change could mean that someone is trying to intercept your communication or that $name$ has simply reinstalled Signal.", + "The safety number you are trying to verify has changed. Please review your new safety number with $name$. Remember, this change could mean that someone is trying to intercept your communication or that $name$ has simply reinstalled Session.", "description": "Shown on the safety number screen when the user has selected to verify/unverify a contact's safety number, and we immediately discover a safety number change", "placeholders": { @@ -433,13 +433,13 @@ }, "changedRecentlyMultiple": { "message": - "Your safety numbers with multiple group members have changed recently. This could mean that someone is trying to intercept your communication or that they have simply reinstalled Signal.", + "Your safety numbers with multiple group members have changed recently. This could mean that someone is trying to intercept your communication or that they have simply reinstalled Session.", "description": "Shown on confirmation dialog when user attempts to send a message" }, "changedRecently": { "message": - "Your safety number with $name$ has changed recently. This could mean that someone is trying to intercept your communication or that $name$ has simply reinstalled Signal.", + "Your safety number with $name$ has changed recently. This could mean that someone is trying to intercept your communication or that $name$ has simply reinstalled Session.", "description": "Shown on confirmation dialog when user attempts to send a message", "placeholders": { @@ -451,7 +451,7 @@ }, "identityKeyErrorOnSend": { "message": - "Your safety number with $name$ has changed. This could either mean that someone is trying to intercept your communication or that $name$ has simply reinstalled Signal. You may wish to verify your saftey number with this contact.", + "Your safety number with $name$ has changed. This could either mean that someone is trying to intercept your communication or that $name$ has simply reinstalled Session. You may wish to verify your saftey number with this contact.", "description": "Shown when user clicks on a failed recipient in the message detail view after an identity key change", "placeholders": { @@ -545,7 +545,7 @@ }, "identityChanged": { "message": - "Your safety number with this contact has changed. This could either mean that someone is trying to intercept your communication, or this contact simply reinstalled Signal. You may wish to verify the new safety number below." + "Your safety number with this contact has changed. This could either mean that someone is trying to intercept your communication, or this contact simply reinstalled Session. You may wish to verify the new safety number below." }, "incomingError": { "message": "Error handling incoming message" @@ -610,12 +610,12 @@ "loadingPreview": { "message": "Loading Preview...", "description": - "Shown while Signal Desktop is fetching metadata for a url in composition area" + "Shown while Session Desktop is fetching metadata for a url in composition area" }, "stagedPreviewThumbnail": { "message": "Draft thumbnail link preview for $domain$", "description": - "Shown while Signal Desktop is fetching metadata for a url in composition area", + "Shown while Session Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { "content": "$1", @@ -626,7 +626,7 @@ "previewThumbnail": { "message": "Thumbnail link preview for $domain$", "description": - "Shown while Signal Desktop is fetching metadata for a url in composition area", + "Shown while Session Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { "content": "$1", @@ -726,7 +726,7 @@ "signalDesktopPreferences": { "message": "Session Preferences", "description": - "Title of the window that pops up with Signal Desktop preferences in it" + "Title of the window that pops up with Session Desktop preferences in it" }, "aboutSignalDesktop": { "message": "About Session", @@ -809,7 +809,7 @@ "sendMessageToContact": { "message": "Send Message", "description": - "Shown when you are sent a contact and that contact has a signal account" + "Shown when you are sent a contact and that contact has a session" }, "home": { "message": "home", @@ -935,13 +935,13 @@ }, "cannotUpdateDetail": { "message": - "Signal Desktop failed to update, but there is a new version available. Please go to https://getsession.org/ and install the new version manually, then either contact support or file a bug about this problem.", + "Session Desktop failed to update, but there is a new version available. Please go to https://getsession.org/ and install the new version manually, then either contact support or file a bug about this problem.", "description": "Shown if a general error happened while trying to install update package" }, "readOnlyVolume": { "message": - "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "Session Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Session.app to /Applications with Finder.", "description": "Shown on MacOS if running on a read-only volume and we cannot update" }, @@ -1946,7 +1946,7 @@ }, "unlinkedWarning": { "message": - "Relink Signal Desktop to your mobile device to continue messaging." + "Relink Session Desktop to your mobile device to continue messaging." }, "unlinked": { "message": "Unlinked" @@ -1955,16 +1955,16 @@ "message": "Relink" }, "autoUpdateNewVersionTitle": { - "message": "Signal update available" + "message": "Session update available" }, "autoUpdateNewVersionMessage": { - "message": "There is a new version of Signal available." + "message": "There is a new version of Session available." }, "autoUpdateNewVersionInstructions": { - "message": "Press Restart Signal to apply the updates." + "message": "Press Restart Session to apply the updates." }, "autoUpdateRestartButtonLabel": { - "message": "Restart Signal" + "message": "Restart Session" }, "autoUpdateLaterButtonLabel": { "message": "Later" From daec39b94ddf291438040b6c0973c3083417dc06 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 25 Feb 2020 12:26:11 +1100 Subject: [PATCH 055/107] fix catch of mouse events on forgotten modals --- js/views/session_confirm_view.js | 2 -- ts/components/ConfirmDialog.tsx | 2 +- ts/components/conversation/CreateGroupDialog.tsx | 2 +- ts/components/session/SessionConfirm.tsx | 2 +- ts/components/session/SessionModal.tsx | 3 ++- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/js/views/session_confirm_view.js b/js/views/session_confirm_view.js index f5be20610..ae31cec29 100644 --- a/js/views/session_confirm_view.js +++ b/js/views/session_confirm_view.js @@ -26,12 +26,10 @@ registerEvents() { this.unregisterEvents(); - document.addEventListener('mousedown', this.props.onClickClose, false); document.addEventListener('keyup', this.props.onClickClose, false); }, unregisterEvents() { - document.removeEventListener('mousedown', this.props.onClickClose, false); document.removeEventListener('keyup', this.props.onClickClose, false); }, diff --git a/ts/components/ConfirmDialog.tsx b/ts/components/ConfirmDialog.tsx index 77f902680..78c02f62f 100644 --- a/ts/components/ConfirmDialog.tsx +++ b/ts/components/ConfirmDialog.tsx @@ -21,7 +21,7 @@ export class ConfirmDialog extends React.Component { return ( null} + onClose={this.props.onClose} onOk={() => null} >
diff --git a/ts/components/conversation/CreateGroupDialog.tsx b/ts/components/conversation/CreateGroupDialog.tsx index 57ce634c9..ce853d4ce 100644 --- a/ts/components/conversation/CreateGroupDialog.tsx +++ b/ts/components/conversation/CreateGroupDialog.tsx @@ -95,7 +95,7 @@ export class CreateGroupDialog extends React.Component { ); return ( - null} onOk={() => null}> + null}>

{this.state.errorMessage}

diff --git a/ts/components/session/SessionConfirm.tsx b/ts/components/session/SessionConfirm.tsx index 561f2127e..1e3595499 100644 --- a/ts/components/session/SessionConfirm.tsx +++ b/ts/components/session/SessionConfirm.tsx @@ -53,7 +53,7 @@ export class SessionConfirm extends React.Component { return ( null} + onClose={onClickClose} onOk={() => null} showExitIcon={false} showHeader={showHeader} diff --git a/ts/components/session/SessionModal.tsx b/ts/components/session/SessionModal.tsx index d4345fb76..7fd8f559c 100644 --- a/ts/components/session/SessionModal.tsx +++ b/ts/components/session/SessionModal.tsx @@ -51,7 +51,7 @@ export class SessionModal extends React.PureComponent { window.addEventListener('keyup', this.onKeyUp); } - public componentWillMount() { + public componentDidMount() { document.addEventListener('mousedown', this.handleClick, false); } @@ -127,6 +127,7 @@ export class SessionModal extends React.PureComponent { }); window.removeEventListener('keyup', this.onKeyUp); + document.removeEventListener('mousedown', this.handleClick, false); if (this.props.onClose) { this.props.onClose(); From d6a9038198a76ab9b289b274fd6311ff82361dd2 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 25 Feb 2020 15:19:42 +1100 Subject: [PATCH 056/107] enable back notifications --- js/background.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/background.js b/js/background.js index 5ce84a8fc..3ea8d48cc 100644 --- a/js/background.js +++ b/js/background.js @@ -1470,7 +1470,8 @@ serverTrustRoot: window.getServerTrustRoot(), }; - Whisper.Notifications.disable(); // avoid notification flood until empty + // Whisper.Notifications.disable(); // avoid notification flood until empty + Whisper.Notifications.enable(); if (Whisper.Registration.ongoingSecondaryDeviceRegistration()) { const ourKey = textsecure.storage.user.getNumber(); @@ -1642,7 +1643,7 @@ // scenarios where we're coming back from sleep, we can get offline/online events // very fast, and it looks like a network blip. But we need to suppress // notifications in these scenarios too. So we listen for 'reconnect' events. - Whisper.Notifications.disable(); + // Whisper.Notifications.disable(); } function onProgress(ev) { const { count } = ev; From f5b3eb394c9b0dcce9c86d316063f0d04d7be575 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 25 Feb 2020 16:08:56 +1100 Subject: [PATCH 057/107] enable notifications after 10seconds of start or reconnect --- js/background.js | 13 ++++++++++--- preload.js | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/js/background.js b/js/background.js index 3ea8d48cc..910c4b1bd 100644 --- a/js/background.js +++ b/js/background.js @@ -1470,8 +1470,10 @@ serverTrustRoot: window.getServerTrustRoot(), }; - // Whisper.Notifications.disable(); // avoid notification flood until empty - Whisper.Notifications.enable(); + Whisper.Notifications.disable(); // avoid notification flood until empty + setTimeout(() => { + Whisper.Notifications.enable(); + }, window.CONSTANTS.NOTIFICATION_ENABLE_TIMEOUT_SECONDS * 1000); if (Whisper.Registration.ongoingSecondaryDeviceRegistration()) { const ourKey = textsecure.storage.user.getNumber(); @@ -1643,7 +1645,12 @@ // scenarios where we're coming back from sleep, we can get offline/online events // very fast, and it looks like a network blip. But we need to suppress // notifications in these scenarios too. So we listen for 'reconnect' events. - // Whisper.Notifications.disable(); + Whisper.Notifications.disable(); + + // Enable back notifications once most messages have been fetched + setTimeout(() => { + Whisper.Notifications.enable(); + }, window.CONSTANTS.NOTIFICATION_ENABLE_TIMEOUT_SECONDS * 1000); } function onProgress(ev) { const { count } = ev; diff --git a/preload.js b/preload.js index d701ff174..c9383e136 100644 --- a/preload.js +++ b/preload.js @@ -70,6 +70,7 @@ window.CONSTANTS = { MAX_MESSAGE_BODY_LENGTH: 64 * 1024, // Limited due to the proof-of-work requirement SMALL_GROUP_SIZE_LIMIT: 10, + NOTIFICATION_ENABLE_TIMEOUT_SECONDS: 10, // number of seconds to turn on notifications after reconnect/start of app }; window.versionInfo = { From ff8c2fc050732a8ac864ff355e8b4e63e7914f87 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 25 Feb 2020 16:09:14 +1100 Subject: [PATCH 058/107] debounce notifications update to 2sec --- js/notifications.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/js/notifications.js b/js/notifications.js index d053ea717..da6935725 100644 --- a/js/notifications.js +++ b/js/notifications.js @@ -34,8 +34,6 @@ Whisper.Notifications = new (Backbone.Collection.extend({ initialize() { this.isEnabled = false; - this.on('add', this.update); - this.on('remove', this.onRemove); this.lastNotification = null; @@ -45,7 +43,11 @@ // and batches up the quick successive update() calls we get from an incoming // read sync, which might have a number of messages referenced inside of it. this.fastUpdate = this.update; - this.update = _.debounce(this.update, 1000); + this.update = _.debounce(this.update, 2000); + + // make those calls use the debounced function + this.on('add', this.update); + this.on('remove', this.onRemove); }, update() { if (this.lastNotification) { From 99bb9a2c309e43242cd8fb760abbef586fa55669 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Tue, 25 Feb 2020 09:12:39 +0100 Subject: [PATCH 059/107] Remove redundant braces Co-Authored-By: Mikunj Varsani --- ts/components/conversation/ConversationHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index 9ffd481b8..5f90e7920 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -379,7 +379,7 @@ export class ConversationHeader extends React.Component {
{this.renderExpirationLength()} - {!this.props.isRss && <>{this.renderAvatar()}} + {!this.props.isRss && this.renderAvatar()} {this.renderMenu(triggerId)}
From 23da1831c29855c8b150ebc7c7995372d14f5a7b Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Wed, 26 Feb 2020 11:54:44 +1100 Subject: [PATCH 060/107] removed unnecessary comments after review --- js/background.js | 11 +---------- main.js | 1 - preload.js | 6 +----- ts/components/session/SessionToggle.tsx | 9 --------- .../session/settings/SessionSettingListItem.tsx | 2 -- ts/components/session/settings/SessionSettings.tsx | 8 -------- 6 files changed, 2 insertions(+), 35 deletions(-) diff --git a/js/background.js b/js/background.js index c690013fc..6e87a7062 100644 --- a/js/background.js +++ b/js/background.js @@ -323,16 +323,7 @@ const ttl = Number.isNaN(intValue) ? 24 : intValue; storage.put('message-ttl', ttl); }, - - - // getZoomFactor: () => storage.get('zoom-factor-setting', 48), - // setZoomFactor: value => { - // // Make sure the ttl is between a given range and is valid - // const intValue = parseInt(value, 10); - // const factor = Number.isNaN(intValue) ? 24 : intValue; - // storage.put('zoom-factor-setting', factor); - // }, - + getReadReceiptSetting: () => storage.get('read-receipt-setting'), setReadReceiptSetting: value => storage.put('read-receipt-setting', value), diff --git a/main.js b/main.js index 86866b1bd..9f774569a 100644 --- a/main.js +++ b/main.js @@ -577,7 +577,6 @@ let settingsWindow; async function showSettingsWindow() { if (settingsWindow) { settingsWindow.show(); - console.log(window.getSettingValue('zoom-factor-setting'), 'from settingsWindow') return; } if (!mainWindow) { diff --git a/preload.js b/preload.js index 3637bf4ea..144a02b2a 100644 --- a/preload.js +++ b/preload.js @@ -2,6 +2,7 @@ /* global window: false */ const path = require('path'); const electron = require('electron'); + const {webFrame} = electron; const semver = require('semver'); @@ -251,11 +252,6 @@ window.getMessageTTL = () => window.storage.get('message-ttl', 24); installGetter('message-ttl', 'getMessageTTL'); installSetter('message-ttl', 'setMessageTTL'); -// Get the zoom Factor setting -// window.getZoomFactor = () => window.storage.get('zoom-factor-setting',50) -// installGetter('zoom-factor-setting', 'getZoomFactor'); -// installSetter('zoom-factor-setting', 'setZoomFactor'); - installGetter('read-receipt-setting', 'getReadReceiptSetting'); installSetter('read-receipt-setting', 'setReadReceiptSetting'); diff --git a/ts/components/session/SessionToggle.tsx b/ts/components/session/SessionToggle.tsx index 54083e151..038129681 100644 --- a/ts/components/session/SessionToggle.tsx +++ b/ts/components/session/SessionToggle.tsx @@ -34,7 +34,6 @@ export class SessionToggle extends React.PureComponent { this.state = { active: active, }; - console.log('it is the constructor runs the first') } @@ -42,9 +41,6 @@ export class SessionToggle extends React.PureComponent { public render() { - - console.log(this.props, 'from Session Toggle') - return (
{ } }; - - //what does the following piece of code do? // - - //what is the window.comfirmationDialog doing in here? - if ( this.props.confirmationDialogParams && this.props.confirmationDialogParams.shouldShowConfirm() diff --git a/ts/components/session/settings/SessionSettingListItem.tsx b/ts/components/session/settings/SessionSettingListItem.tsx index e7bc66e4a..56ebe9c94 100644 --- a/ts/components/session/settings/SessionSettingListItem.tsx +++ b/ts/components/session/settings/SessionSettingListItem.tsx @@ -145,8 +145,6 @@ export class SessionSettingListItem extends React.Component { sliderValue: value, }); - console.log(this.props.title, 'from here') - if(this.props.title === 'Zoom Factor' && this.state.sliderValue!==null) { window.setZoomFactor(this.state.sliderValue/100) } diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 8d493ef5a..2ac98361a 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -137,10 +137,6 @@ export class SettingsView extends React.Component { this.updateSetting(setting); }); - //define a bunch of function that will be used in the setting list. - //since setting elem itself is an array elem, this either becomes, onlick function - // or a function returned by others. - return (
{shouldRenderSettings && @@ -238,11 +234,8 @@ export class SettingsView extends React.Component { this.state.shouldLockSettings && this.state.hasPassword; return ( - - //this is the section where the actually setting group of components gets render!
- {/* header is always rendered */} {
- {/* some show lock logic is put in here to make sure that every time if you want to change the appearance */} {shouldRenderPasswordLock ? ( this.renderPasswordLock() // From 203d3142548bf0bbc29ee1fef03d948171727e54 Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Wed, 26 Feb 2020 13:30:56 +1100 Subject: [PATCH 061/107] fix the slider bug --- js/background.js | 5 ++- js/modules/loki_app_dot_net_api.js | 4 ++- main.js | 3 +- preload.js | 33 ++++++++----------- ts/components/session/SessionToggle.tsx | 4 --- .../settings/SessionSettingListItem.tsx | 24 +++++--------- .../session/settings/SessionSettings.tsx | 18 ++-------- ts/global.d.ts | 1 - 8 files changed, 33 insertions(+), 59 deletions(-) diff --git a/js/background.js b/js/background.js index 6e87a7062..933d9d21c 100644 --- a/js/background.js +++ b/js/background.js @@ -286,6 +286,9 @@ } first = false; + // Update zoom + window.updateZoomFactor(); + const currentPoWDifficulty = storage.get('PoWDifficulty', null); if (!currentPoWDifficulty) { storage.put('PoWDifficulty', window.getDefaultPoWDifficulty()); @@ -323,7 +326,7 @@ const ttl = Number.isNaN(intValue) ? 24 : intValue; storage.put('message-ttl', ttl); }, - + getReadReceiptSetting: () => storage.get('read-receipt-setting'), setReadReceiptSetting: value => storage.put('read-receipt-setting', value), diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 293d6a522..c9664abef 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -614,7 +614,9 @@ class LokiAppDotNetServerAPI { `serverRequest ${mode} error`, e.code, e.message, - `json: ${txtResponse}`, 'attempting connection to', url + `json: ${txtResponse}`, + 'attempting connection to', + url ); } else { log.info( diff --git a/main.js b/main.js index 9f774569a..3fba1821e 100644 --- a/main.js +++ b/main.js @@ -836,7 +836,6 @@ async function showMainWindow(sqlKey, passwordAttempt = false) { createWindow(); - if (usingTrayIcon) { tray = createTrayIcon(getMainWindow, locale.messages); } @@ -1026,7 +1025,7 @@ ipc.on('password-window-login', async (event, passPhrase) => { const passwordAttempt = true; await showMainWindow(passPhrase, passwordAttempt); sendResponse(); - + if (passwordWindow) { passwordWindow.close(); passwordWindow = null; diff --git a/preload.js b/preload.js index 144a02b2a..bfc96b3c4 100644 --- a/preload.js +++ b/preload.js @@ -3,7 +3,7 @@ const path = require('path'); const electron = require('electron'); -const {webFrame} = electron; +const { webFrame } = electron; const semver = require('semver'); const { deferredToPromise } = require('./js/modules/deferred_to_promise'); @@ -71,7 +71,6 @@ window.CONSTANTS = { MAX_CONNECTION_DURATION: 5000, }; - window.versionInfo = { environment: window.getEnvironment(), version: window.getVersion(), @@ -84,15 +83,18 @@ window.wrapDeferred = deferredToPromise; const ipc = electron.ipcRenderer; const localeMessages = ipc.sendSync('locale-data'); - -window.setZoomFactor = (number) => { - return webFrame.setZoomFactor(number); +window.updateZoomFactor = () => { + const zoomFactor = window.getSettingValue('zoom-factor-setting') || 100; + window.setZoomFactor(zoomFactor / 100); } -window.getZoomFactor = () => { - return webFrame.getZoomFactor(); -} +window.setZoomFactor = number => { + webFrame.setZoomFactor(number); +}; +window.getZoomFactor = () => { + webFrame.getZoomFactor(); +}; window.setBadgeCount = count => ipc.send('set-badge-count', count); @@ -173,9 +175,6 @@ ipc.on('on-unblock-number', (event, number) => { window.closeAbout = () => ipc.send('close-about'); window.readyForUpdates = () => ipc.send('ready-for-updates'); - - - window.updateTrayIcon = unreadCount => ipc.send('update-tray-icon', unreadCount); @@ -193,9 +192,6 @@ ipc.on('set-up-as-standalone', () => { // Settings-related events - - - window.showSettings = () => ipc.send('show-settings'); window.showPermissionsPopup = () => ipc.send('show-permissions-popup'); @@ -231,14 +227,13 @@ window.getSettingValue = (settingID, comparisonValue = null) => { return comparisonValue ? !!settingVal === comparisonValue : settingVal; }; - - window.setSettingValue = (settingID, value) => { window.storage.put(settingID, value); -}; - - + if (settingID === 'zoom-factor-setting') { + window.updateZoomFactor(); + } +}; installGetter('device-name', 'getDeviceName'); diff --git a/ts/components/session/SessionToggle.tsx b/ts/components/session/SessionToggle.tsx index 038129681..b9df7c319 100644 --- a/ts/components/session/SessionToggle.tsx +++ b/ts/components/session/SessionToggle.tsx @@ -36,10 +36,6 @@ export class SessionToggle extends React.PureComponent { }; } - - - - public render() { return (
{ this.handleSlider(sliderValue); }} /> - +

{`${currentSliderValue} Hours`}

- ):type === SessionSettingType.Slider && title === "Zoom Factor" ? ( + ) : type === SessionSettingType.Slider && title === 'Zoom Factor' ? (
- { onChange={sliderValue => { this.handleSlider(sliderValue); }} - /> + /> -
-

{`% ${currentSliderValue} Zoom Level`}

-
+
+

{`% ${currentSliderValue} Zoom Level`}

+
- ): - - null} + ) : null}
); @@ -145,10 +143,6 @@ export class SessionSettingListItem extends React.Component { sliderValue: value, }); - if(this.props.title === 'Zoom Factor' && this.state.sliderValue!==null) { - window.setZoomFactor(this.state.sliderValue/100) - } - - + } } diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 2ac98361a..1cae11c29 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -8,7 +8,6 @@ import { SessionButtonType, } from '../SessionButton'; - export enum SessionSettingCategory { Appearance = 'appearance', Account = 'account', @@ -76,7 +75,6 @@ export class SettingsView extends React.Component { } public componentDidMount() { - console.log(this.state, 'Print state of SessionSettings'); setTimeout(() => $('#password-lock-input').focus(), 100); window.Whisper.events.on('refreshLinkedDeviceList', async () => { @@ -85,8 +83,6 @@ export class SettingsView extends React.Component { }, 1000); }); this.refreshLinkedDevice(); - - } public componentWillUnmount() { @@ -110,8 +106,6 @@ export class SettingsView extends React.Component { settings = this.getLocalSettings(); } - - return ( <> {this.state.hasPassword !== null && @@ -227,27 +221,22 @@ export class SettingsView extends React.Component { } public render() { - - console.log(this.props, 'From SessionSettings'); const { category } = this.props; const shouldRenderPasswordLock = this.state.shouldLockSettings && this.state.hasPassword; return (
- -
- {shouldRenderPasswordLock ? ( this.renderPasswordLock() - // ) : ( + //
{this.renderSettingInCategory()} {/* what gets rendered back from calling renderSettingInCategory */} @@ -318,9 +307,6 @@ export class SettingsView extends React.Component { } } - - - private getPubkeyName(pubKey: string | null) { if (!pubKey) { return {}; @@ -563,7 +549,7 @@ export class SettingsView extends React.Component { onSuccess: this.onPasswordUpdated, }), confirmationDialogParams: undefined, - } + }, ]; } diff --git a/ts/global.d.ts b/ts/global.d.ts index fbc071720..74c71bb6e 100644 --- a/ts/global.d.ts +++ b/ts/global.d.ts @@ -59,7 +59,6 @@ interface Window { lokiFeatureFlags: any; resetDatabase: any; - } interface Promise { From 797ccdc125a9a6929ece3eb46625743319291036 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 26 Feb 2020 14:44:56 +1100 Subject: [PATCH 062/107] Use the new member list UI for the update group dialog (closed) --- .../conversation/UpdateGroupMembersDialog.tsx | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/ts/components/conversation/UpdateGroupMembersDialog.tsx b/ts/components/conversation/UpdateGroupMembersDialog.tsx index 27de62c49..8acde7336 100644 --- a/ts/components/conversation/UpdateGroupMembersDialog.tsx +++ b/ts/components/conversation/UpdateGroupMembersDialog.tsx @@ -1,9 +1,13 @@ import React from 'react'; import classNames from 'classnames'; -import { Contact, MemberList } from './MemberList'; +import { Contact } from './MemberList'; import { SessionModal } from '../session/SessionModal'; import { SessionButton } from '../session/SessionButton'; +import { + ContactType, + SessionMemberListItem, +} from '../session/SessionMemberListItem'; interface Props { titleText: string; @@ -114,13 +118,8 @@ export class UpdateGroupMembersDialog extends React.Component {

{errorMsg}

-
- +
+ {this.renderMemberList()}

{`(${this.props.i18n( 'noMembersInThisGroup' @@ -135,6 +134,19 @@ export class UpdateGroupMembersDialog extends React.Component { ); } + private renderMemberList() { + const members = this.state.friendList; + + return members.map((member: ContactType) => ( + + )); + } + private onShowError(msg: string) { if (this.state.errorDisplayed) { return; From ccb470207a3db93802074836691c270d916e90f0 Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Wed, 26 Feb 2020 15:08:41 +1100 Subject: [PATCH 063/107] use storage instead of component state to store the zoom setting --- preload.js | 2 +- ts/components/session/settings/SessionSettingListItem.tsx | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/preload.js b/preload.js index bfc96b3c4..58f8c9b55 100644 --- a/preload.js +++ b/preload.js @@ -86,7 +86,7 @@ const localeMessages = ipc.sendSync('locale-data'); window.updateZoomFactor = () => { const zoomFactor = window.getSettingValue('zoom-factor-setting') || 100; window.setZoomFactor(zoomFactor / 100); -} +}; window.setZoomFactor = number => { webFrame.setZoomFactor(number); diff --git a/ts/components/session/settings/SessionSettingListItem.tsx b/ts/components/session/settings/SessionSettingListItem.tsx index ef5746c65..062da0ee3 100644 --- a/ts/components/session/settings/SessionSettingListItem.tsx +++ b/ts/components/session/settings/SessionSettingListItem.tsx @@ -142,7 +142,5 @@ export class SessionSettingListItem extends React.Component { this.setState({ sliderValue: value, }); - - } } From 8066cc0709c05bcdcdf8079ba9751dc2b6510a6b Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 26 Feb 2020 15:58:12 +1100 Subject: [PATCH 064/107] add link to explain tslint issue with sfc and props type --- ts/components/session/PillDivider.tsx | 2 ++ ts/components/session/SessionHTMLRenderer.tsx | 1 + 2 files changed, 3 insertions(+) diff --git a/ts/components/session/PillDivider.tsx b/ts/components/session/PillDivider.tsx index 508e97719..f58394832 100644 --- a/ts/components/session/PillDivider.tsx +++ b/ts/components/session/PillDivider.tsx @@ -3,6 +3,8 @@ import React from 'react'; interface ReceivedProps { text: string; } + +// Needed because of https://github.com/microsoft/tslint-microsoft-contrib/issues/339 type Props = ReceivedProps; export const PillDivider: React.SFC = props => { diff --git a/ts/components/session/SessionHTMLRenderer.tsx b/ts/components/session/SessionHTMLRenderer.tsx index 24e7c4bc8..f7f493b7b 100644 --- a/ts/components/session/SessionHTMLRenderer.tsx +++ b/ts/components/session/SessionHTMLRenderer.tsx @@ -7,6 +7,7 @@ interface ReceivedProps { key?: any; } +// Needed because of https://github.com/microsoft/tslint-microsoft-contrib/issues/339 type Props = ReceivedProps; export const SessionHtmlRenderer: React.SFC = ({ From 06aeb126c20c2d7ce57c2a1394eb8cce2e91204c Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 26 Feb 2020 17:18:41 +1100 Subject: [PATCH 065/107] enable search by username in message view #928 --- ts/components/SearchResults.tsx | 26 +++++++++++++++++++ .../session/LeftPaneMessageSection.tsx | 5 ++++ ts/state/ducks/search.ts | 15 +++-------- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/ts/components/SearchResults.tsx b/ts/components/SearchResults.tsx index fc1de4c8f..cd6f1c04b 100644 --- a/ts/components/SearchResults.tsx +++ b/ts/components/SearchResults.tsx @@ -75,6 +75,10 @@ export class SearchResults extends React.Component { ))}

) : null} + {haveFriends + ? this.renderContacts(i18n('friendsHeader'), friends, true) + : null} + {haveMessages ? (
{hideMessagesHeader ? null : ( @@ -95,4 +99,26 @@ export class SearchResults extends React.Component {
); } + private renderContacts( + header: string, + items: Array, + friends?: boolean + ) { + const { i18n, openConversation } = this.props; + + return ( +
+
{header}
+ {items.map(contact => ( + + ))} +
+ ); + } } diff --git a/ts/components/session/LeftPaneMessageSection.tsx b/ts/components/session/LeftPaneMessageSection.tsx index 9841257ea..e29b942b1 100644 --- a/ts/components/session/LeftPaneMessageSection.tsx +++ b/ts/components/session/LeftPaneMessageSection.tsx @@ -132,11 +132,16 @@ export class LeftPaneMessageSection extends React.Component { public renderList(): JSX.Element | Array { const { openConversationInternal, searchResults } = this.props; + const friends = + (searchResults && + searchResults.contacts.filter(contact => contact.isFriend)) || + []; if (searchResults) { return ( diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index f5e38b259..c92fdd814 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -172,7 +172,7 @@ async function queryConversationsAndContacts( providedQuery: string, options: SearchOptions ) { - const { ourNumber, noteToSelf, isSecondaryDevice } = options; + const { ourNumber, isSecondaryDevice } = options; const query = providedQuery.replace(/[+-.()]*/g, ''); const searchResults: Array = await searchConversations( @@ -193,8 +193,8 @@ async function queryConversationsAndContacts( ); // Split into two groups - active conversations and items just from address book - let conversations: Array = []; - let contacts: Array = []; + const conversations: Array = []; + const contacts: Array = []; const max = searchResults.length; for (let i = 0; i < max; i += 1) { const conversation = searchResults[i]; @@ -215,15 +215,6 @@ async function queryConversationsAndContacts( } } - // Inject synthetic Note to Self entry if query matches localized 'Note to Self' - if (noteToSelf.indexOf(providedQuery.toLowerCase()) !== -1) { - // ensure that we don't have duplicates in our results - contacts = contacts.filter(id => id !== ourNumber); - conversations = conversations.filter(id => id !== ourNumber); - - contacts.unshift(ourNumber); - } - return { conversations, contacts }; } From 0528a4c8ac8828b8f9d9e49da2b32b351b4cb226 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 26 Feb 2020 17:20:23 +1100 Subject: [PATCH 066/107] fix lint --- js/modules/loki_message_api.js | 7 ++++++- js/modules/loki_rpc.js | 7 +++++-- js/modules/loki_snode_api.js | 21 +++++++++++++++---- libtextsecure/message_receiver.js | 5 ++++- .../conversation/CreateGroupDialog.tsx | 6 +++++- 5 files changed, 37 insertions(+), 9 deletions(-) diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index 98411fd85..88b02b7af 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -217,7 +217,12 @@ class LokiMessageAPI { } return true; } catch (e) { - log.warn('Loki send message error:', e.code, e.message, `from ${address}`); + log.warn( + 'Loki send message error:', + e.code, + e.message, + `from ${address}` + ); if (e instanceof textsecure.WrongSwarmError) { const { newSwarm } = e; await lokiSnodeAPI.updateSwarmNodes(params.pubKey, newSwarm); diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index 61fb83926..5f29f6c38 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -70,7 +70,10 @@ const sendToProxy = async (options = {}, targetNode) => { // detect SNode is not ready (not in swarm; not done syncing) if (response.status === 503) { const ciphertext = await response.text(); - log.error(`lokiRpc sendToProxy snode ${randSnode.ip}:${randSnode.port} error`, ciphertext); + log.error( + `lokiRpc sendToProxy snode ${randSnode.ip}:${randSnode.port} error`, + ciphertext + ); // mark as bad for this round (should give it some time and improve success rates) lokiSnodeAPI.markRandomNodeUnreachable(randSnode); // retry for a new working snode @@ -104,7 +107,7 @@ const sendToProxy = async (options = {}, targetNode) => { const textDecoder = new TextDecoder(); plaintext = textDecoder.decode(plaintextBuffer); - } catch(e) { + } catch (e) { log.error( 'lokiRpc sendToProxy decode error', e.code, diff --git a/js/modules/loki_snode_api.js b/js/modules/loki_snode_api.js index 7bc2944ed..7d163a215 100644 --- a/js/modules/loki_snode_api.js +++ b/js/modules/loki_snode_api.js @@ -31,7 +31,10 @@ class LokiSnodeAPI { ]; } - async initialiseRandomPool(seedNodes = [...window.seedNodeList], consecutiveErrors = 0) { + async initialiseRandomPool( + seedNodes = [...window.seedNodeList], + consecutiveErrors = 0 + ) { const params = { limit: 20, active_only: true, @@ -71,7 +74,10 @@ class LokiSnodeAPI { if (consecutiveErrors < 3) { // retry after a possible delay setTimeout(() => { - log.info('Retrying initialising random snode pool, try #', consecutiveErrors); + log.info( + 'Retrying initialising random snode pool, try #', + consecutiveErrors + ); this.initialiseRandomPool(seedNodes, consecutiveErrors + 1); }, consecutiveErrors * consecutiveErrors * 5000); } else { @@ -181,7 +187,12 @@ class LokiSnodeAPI { const snodes = result.snodes.filter(tSnode => tSnode.ip !== '0.0.0.0'); return snodes; } catch (e) { - log.error('getSnodesForPubkey error', e.code, e.message, `for ${snode.ip}:${snode.port}`); + log.error( + 'getSnodesForPubkey error', + e.code, + e.message, + `for ${snode.ip}:${snode.port}` + ); this.markRandomNodeUnreachable(snode); return []; } @@ -197,7 +208,9 @@ class LokiSnodeAPI { const resList = await this.getSnodesForPubkey(rSnode, pubKey); // should we only activate entries that are in all results? resList.map(item => { - const hasItem = snodes.some(hItem => item.ip === hItem.ip && item.port === hItem.port); + const hasItem = snodes.some( + hItem => item.ip === hItem.ip && item.port === hItem.port + ); if (!hasItem) { snodes.push(item); } diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 0e6204016..b6b24f355 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -59,7 +59,10 @@ function MessageReceiver(username, password, signalingKey, options = {}) { lokiPublicChatAPI.removeAllListeners('publicMessage'); // we only need one MR in the system handling these // bind events - lokiPublicChatAPI.on('publicMessage', this.handleUnencryptedMessage.bind(this)); + lokiPublicChatAPI.on( + 'publicMessage', + this.handleUnencryptedMessage.bind(this) + ); openGroupBound = true; } } else { diff --git a/ts/components/conversation/CreateGroupDialog.tsx b/ts/components/conversation/CreateGroupDialog.tsx index ce853d4ce..482a35579 100644 --- a/ts/components/conversation/CreateGroupDialog.tsx +++ b/ts/components/conversation/CreateGroupDialog.tsx @@ -95,7 +95,11 @@ export class CreateGroupDialog extends React.Component { ); return ( - null}> + null} + >

{this.state.errorMessage}

From 845fc349647569e525b470b12213f348fa6142e0 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 26 Feb 2020 16:36:13 -0800 Subject: [PATCH 067/107] Fix my typo --- js/background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/background.js b/js/background.js index bd3ced3d5..5c36209fa 100644 --- a/js/background.js +++ b/js/background.js @@ -792,7 +792,7 @@ convo.updateGroup({ groupId, groupName, - nullAvatar, + avatar: nullAvatar, recipients, members, options, From 4254f15b44a850faea2d5e929252b43f3a99aef4 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 26 Feb 2020 16:53:35 -0800 Subject: [PATCH 068/107] fix message still loading issue --- js/views/conversation_view.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 0550fa63e..34b25459f 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -1189,6 +1189,11 @@ const el = this.$(`#${message.id}`); const position = el.position(); + // This message is likely not loaded yet in the DOM + if (!position) { + // should this be break? + continue; + } const { top } = position; // We're fully below the viewport, continue searching up. From c6b6ab8be61dd5463a7f6e378b2ed43751ea783a Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 26 Feb 2020 17:34:14 -0800 Subject: [PATCH 069/107] lint --- js/background.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/background.js b/js/background.js index 910c4b1bd..218acba75 100644 --- a/js/background.js +++ b/js/background.js @@ -259,6 +259,7 @@ window.initialisedAPI = true; if (storage.get('isSecondaryDevice')) { + window.log.info('Initialising as a secondary device'); window.lokiFileServerAPI.updateOurDeviceMapping(); } }; @@ -1365,6 +1366,7 @@ }); Whisper.events.on('deviceUnpairingRequested', async pubKey => { + window.log.info('unpairing device...'); await libloki.storage.removePairingAuthorisationForSecondaryPubKey( pubKey ); From 5b412f74ad8c338e245e9174a5d3b03a777a25af Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 26 Feb 2020 17:36:56 -0800 Subject: [PATCH 070/107] Revert "lint" This reverts commit c6b6ab8be61dd5463a7f6e378b2ed43751ea783a. --- js/background.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/js/background.js b/js/background.js index 218acba75..910c4b1bd 100644 --- a/js/background.js +++ b/js/background.js @@ -259,7 +259,6 @@ window.initialisedAPI = true; if (storage.get('isSecondaryDevice')) { - window.log.info('Initialising as a secondary device'); window.lokiFileServerAPI.updateOurDeviceMapping(); } }; @@ -1366,7 +1365,6 @@ }); Whisper.events.on('deviceUnpairingRequested', async pubKey => { - window.log.info('unpairing device...'); await libloki.storage.removePairingAuthorisationForSecondaryPubKey( pubKey ); From 97393ef25df7e299c4b44830e5cf57ad1bc260cb Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 26 Feb 2020 18:17:51 -0800 Subject: [PATCH 071/107] fix message order when mix multidevice messages, improve error logging --- js/modules/loki_app_dot_net_api.js | 63 ++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 8158a5bc2..d5e321cbf 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -627,6 +627,12 @@ class LokiAppDotNetServerAPI { 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, }; @@ -1476,6 +1482,14 @@ class LokiPublicChannelAPI { }); if (res.err || !res.response) { + log.error( + 'Could not get messages from', + this.serverAPI.baseServerUrl, + this.baseChannelUrl + ); + if (res.err) { + log.error('pollOnceForMessages receive error', res.err); + } return; } @@ -1663,18 +1677,31 @@ class LokiPublicChannelAPI { // filter out invalid messages pendingMessages = pendingMessages.filter(messageData => !!messageData); // separate messages coming from primary and secondary devices - const [primaryMessages, slaveMessages] = _.partition( + let [primaryMessages, slaveMessages] = _.partition( pendingMessages, message => !(message.source in slavePrimaryMap) ); - // process primary devices' message directly - primaryMessages.forEach(message => - this.chatAPI.emit('publicMessage', { - message, - }) - ); - - pendingMessages = []; // allow memory to be freed + // get minimum ID for primaryMessages and slaveMessages + const firstPrimaryId = Math.min(...primaryMessages.map(msg => msg.serverId)); + const firstSlaveId = Math.min(...slaveMessages.map(msg => msg.serverId)); + if (firstPrimaryId < firstSlaveId) { + // early send + // split off count from pendingMessages + let sendNow = []; + ([sendNow, pendingMessages] = _.partition( + pendingMessages, + message => message.serverId < firstSlaveId + )); + sendNow.forEach(message => { + // send them out now + log.info('emitting primary message', message.serverId); + this.chatAPI.emit('publicMessage', { + message, + }); + }); + sendNow = false; + } + primaryMessages = false; // free memory // get actual chat server data (mainly the name rn) of primary device const verifiedDeviceResults = await this.serverAPI.getUsers( @@ -1731,11 +1758,25 @@ class LokiPublicChannelAPI { messageData.message.profileKey = profileKey; } } - /* eslint-enable no-param-reassign */ + }); + slaveMessages = false; // free memory + + // process all messages in the order received + pendingMessages.forEach(message => { + // if slave device + if (message.source in slavePrimaryMap) { + // prevent our own device sent messages from coming back in + if (message.source === ourNumberDevice) { + // we originally sent these + return; + } + } + log.info('emitting pending message', message.serverId); this.chatAPI.emit('publicMessage', { - message: messageData, + message, }); }); + /* eslint-enable no-param-reassign */ // if we received one of our own messages From c36fd8ae6278df356a50e321ab7d9a092f6457fe Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Thu, 27 Feb 2020 01:04:41 -0800 Subject: [PATCH 072/107] handle image/ avatar paths --- js/modules/loki_app_dot_net_api.js | 71 ++++++++++++++++++------------ 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 613ee25f8..5791b7347 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -5,6 +5,7 @@ const nodeFetch = require('node-fetch'); const { URL, URLSearchParams } = require('url'); const FormData = require('form-data'); const https = require('https'); +const path = require('path'); // Can't be less than 1200 if we have unauth'd requests const PUBLICCHAT_MSG_POLL_EVERY = 1.5 * 1000; // 1.5s @@ -1254,38 +1255,50 @@ class LokiPublicChannelAPI { this.conversation.setGroupName(note.value.name); } if (note.value && note.value.avatar) { - const avatarAbsUrl = this.serverAPI.baseServerUrl + note.value.avatar; - const { - writeNewAttachmentData, - deleteAttachmentData, - } = window.Signal.Migrations; - // do we already have this image? no, then - - // download a copy and save it - const imageData = await nodeFetch(avatarAbsUrl); - // eslint-disable-next-line no-inner-declarations - function toArrayBuffer(buf) { - const ab = new ArrayBuffer(buf.length); - const view = new Uint8Array(ab); - // eslint-disable-next-line no-plusplus - for (let i = 0; i < buf.length; i++) { - view[i] = buf[i]; + if (note.value.avatar.match(/^images\//)) { + // local file avatar + const resolvedAvatar = path.normalize(note.value.avatar); + const base = path.normalize('images/'); + const re = new RegExp(`^${base}`) + // do we at least ends up inside images/ somewhere? + if (re.test(resolvedAvatar)) { + this.conversation.set('avatar', resolvedAvatar); } - return ab; - } - // eslint-enable-next-line no-inner-declarations - - const buffer = await imageData.buffer(); - const newAttributes = await window.Signal.Types.Conversation.maybeUpdateAvatar( - this.conversation.attributes, - toArrayBuffer(buffer), - { + } else { + // relative URL avatar + const avatarAbsUrl = this.serverAPI.baseServerUrl + note.value.avatar; + const { writeNewAttachmentData, deleteAttachmentData, + } = window.Signal.Migrations; + // do we already have this image? no, then + + // download a copy and save it + const imageData = await nodeFetch(avatarAbsUrl); + // eslint-disable-next-line no-inner-declarations + function toArrayBuffer(buf) { + const ab = new ArrayBuffer(buf.length); + const view = new Uint8Array(ab); + // eslint-disable-next-line no-plusplus + for (let i = 0; i < buf.length; i++) { + view[i] = buf[i]; + } + return ab; } - ); - // update group - this.conversation.set('avatar', newAttributes.avatar); + // eslint-enable-next-line no-inner-declarations + + const buffer = await imageData.buffer(); + const newAttributes = await window.Signal.Types.Conversation.maybeUpdateAvatar( + this.conversation.attributes, + toArrayBuffer(buffer), + { + writeNewAttachmentData, + deleteAttachmentData, + } + ); + // update group + this.conversation.set('avatar', newAttributes.avatar); + } } // is it mutable? // who are the moderators? @@ -1420,7 +1433,7 @@ class LokiPublicChannelAPI { } if (quote) { - // TODO: Enable quote attachments again using proper ADN style + // Disable quote attachments quote.attachments = []; } From 0daaa0459632a89fdc8e390c81f16ee9f896c008 Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Fri, 28 Feb 2020 10:20:56 +1100 Subject: [PATCH 073/107] fully working; all comments removed; put slider setting into content object --- .../settings/SessionSettingListItem.tsx | 24 ++++++++++--------- .../session/settings/SessionSettings.tsx | 12 +++++++++- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/ts/components/session/settings/SessionSettingListItem.tsx b/ts/components/session/settings/SessionSettingListItem.tsx index 062da0ee3..4820d3287 100644 --- a/ts/components/session/settings/SessionSettingListItem.tsx +++ b/ts/components/session/settings/SessionSettingListItem.tsx @@ -88,13 +88,14 @@ export class SessionSettingListItem extends React.Component { /> )} - {type === SessionSettingType.Slider && title === 'Message TTL' ? ( + {type === SessionSettingType.Slider && + content.sliderCategory === 'messageTTLSlider' ? (
{ this.handleSlider(sliderValue); @@ -105,13 +106,14 @@ export class SessionSettingListItem extends React.Component {

{`${currentSliderValue} Hours`}

- ) : type === SessionSettingType.Slider && title === 'Zoom Factor' ? ( + ) : type === SessionSettingType.Slider && + content.sliderCategory === 'zoomFactorSlider' ? (
{ this.handleSlider(sliderValue); @@ -119,7 +121,7 @@ export class SessionSettingListItem extends React.Component { />
-

{`% ${currentSliderValue} Zoom Level`}

+

{`Zoom Level: %${currentSliderValue}`}

) : null} diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 1cae11c29..38bb56b4e 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -445,6 +445,11 @@ export class SettingsView extends React.Component { comparisonValue: undefined, onClick: undefined, content: { + dotsEnabled: true, + step: 6, + min: 12, + max: 96, + sliderCategory: 'messageTTLSlider', defaultValue: 24, }, confirmationDialogParams: undefined, @@ -460,7 +465,12 @@ export class SettingsView extends React.Component { comparisonValue: undefined, onClick: undefined, content: { - defaultValue: 24, + dotsEnabled: true, + step: 20, + min: 60, + max: 200, + sliderCategory: 'zoomFactorSlider', + defaultValue: 100, }, confirmationDialogParams: undefined, }, From 75f5d5743f5a61d76e040f0d634ba13e52aef16f Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Fri, 28 Feb 2020 10:54:21 +1100 Subject: [PATCH 074/107] remove all the conditional rendering logic to make code cleaner --- .../settings/SessionSettingListItem.tsx | 25 +++---------------- .../session/settings/SessionSettings.tsx | 6 ++--- 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/ts/components/session/settings/SessionSettingListItem.tsx b/ts/components/session/settings/SessionSettingListItem.tsx index 4820d3287..517ad205e 100644 --- a/ts/components/session/settings/SessionSettingListItem.tsx +++ b/ts/components/session/settings/SessionSettingListItem.tsx @@ -88,8 +88,7 @@ export class SessionSettingListItem extends React.Component { /> )} - {type === SessionSettingType.Slider && - content.sliderCategory === 'messageTTLSlider' ? ( + {type === SessionSettingType.Slider && (
{ />
-

{`${currentSliderValue} Hours`}

+

{content.info(currentSliderValue)}

- ) : type === SessionSettingType.Slider && - content.sliderCategory === 'zoomFactorSlider' ? ( -
- { - this.handleSlider(sliderValue); - }} - /> - -
-

{`Zoom Level: %${currentSliderValue}`}

-
-
- ) : null} + )}
); diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 38bb56b4e..d5f391975 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -236,14 +236,10 @@ export class SettingsView extends React.Component { {shouldRenderPasswordLock ? ( this.renderPasswordLock() ) : ( - //
{this.renderSettingInCategory()} - {/* what gets rendered back from calling renderSettingInCategory */}
)} - - {/* session info is always shown in here */} {this.renderSessionInfo()}
@@ -451,6 +447,7 @@ export class SettingsView extends React.Component { max: 96, sliderCategory: 'messageTTLSlider', defaultValue: 24, + info: (value: number) => `${value} Hours`, }, confirmationDialogParams: undefined, }, @@ -471,6 +468,7 @@ export class SettingsView extends React.Component { max: 200, sliderCategory: 'zoomFactorSlider', defaultValue: 100, + info: (value: number) => `Zoom Factor: ${value}%` }, confirmationDialogParams: undefined, }, From fd764a2aa70cc1569a80db14174e8c357b7a5b5e Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Fri, 28 Feb 2020 10:58:55 +1100 Subject: [PATCH 075/107] match the slider value prop with currentSliderValue --- ts/components/session/settings/SessionSettingListItem.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ts/components/session/settings/SessionSettingListItem.tsx b/ts/components/session/settings/SessionSettingListItem.tsx index 517ad205e..76ee76d39 100644 --- a/ts/components/session/settings/SessionSettingListItem.tsx +++ b/ts/components/session/settings/SessionSettingListItem.tsx @@ -95,7 +95,8 @@ export class SessionSettingListItem extends React.Component { step={content.step} min={content.min} max={content.max} - defaultValue={currentSliderValue} + value={currentSliderValue} + defaultValue={content.defaultValue} onAfterChange={sliderValue => { this.handleSlider(sliderValue); }} From 70100a0ef595309575f8c4a6e6386859ee1c6685 Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Fri, 28 Feb 2020 12:07:45 +1100 Subject: [PATCH 076/107] remove value prop in slider and global var setZoomFactor --- ts/components/session/settings/SessionSettingListItem.tsx | 1 - ts/components/session/settings/SessionSettings.tsx | 2 -- 2 files changed, 3 deletions(-) diff --git a/ts/components/session/settings/SessionSettingListItem.tsx b/ts/components/session/settings/SessionSettingListItem.tsx index 76ee76d39..127a881ab 100644 --- a/ts/components/session/settings/SessionSettingListItem.tsx +++ b/ts/components/session/settings/SessionSettingListItem.tsx @@ -95,7 +95,6 @@ export class SessionSettingListItem extends React.Component { step={content.step} min={content.min} max={content.max} - value={currentSliderValue} defaultValue={content.defaultValue} onAfterChange={sliderValue => { this.handleSlider(sliderValue); diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index d5f391975..2a81b77de 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -445,7 +445,6 @@ export class SettingsView extends React.Component { step: 6, min: 12, max: 96, - sliderCategory: 'messageTTLSlider', defaultValue: 24, info: (value: number) => `${value} Hours`, }, @@ -466,7 +465,6 @@ export class SettingsView extends React.Component { step: 20, min: 60, max: 200, - sliderCategory: 'zoomFactorSlider', defaultValue: 100, info: (value: number) => `Zoom Factor: ${value}%` }, From ac8d0aa441917b5e8faf486e9e1d0e682b75a13a Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Fri, 28 Feb 2020 12:09:25 +1100 Subject: [PATCH 077/107] remove the global var setZoomFacotr --- ts/global.d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ts/global.d.ts b/ts/global.d.ts index 74c71bb6e..882a32e3f 100644 --- a/ts/global.d.ts +++ b/ts/global.d.ts @@ -11,7 +11,6 @@ interface Window { mnemonic: any; clipboard: any; attemptConnection: any; - setZoomFactor: any; passwordUtil: any; userConfig: any; From f92cb95f5c447ee1dd0b5ec625de6053db2b956a Mon Sep 17 00:00:00 2001 From: jian10au <48778096+jian10au@users.noreply.github.com> Date: Fri, 28 Feb 2020 13:55:52 +1100 Subject: [PATCH 078/107] Update ts/components/session/settings/SessionSettingListItem.tsx Accept the change Co-Authored-By: Mikunj Varsani --- ts/components/session/settings/SessionSettingListItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/components/session/settings/SessionSettingListItem.tsx b/ts/components/session/settings/SessionSettingListItem.tsx index 127a881ab..517ad205e 100644 --- a/ts/components/session/settings/SessionSettingListItem.tsx +++ b/ts/components/session/settings/SessionSettingListItem.tsx @@ -95,7 +95,7 @@ export class SessionSettingListItem extends React.Component { step={content.step} min={content.min} max={content.max} - defaultValue={content.defaultValue} + defaultValue={currentSliderValue} onAfterChange={sliderValue => { this.handleSlider(sliderValue); }} From ed5533b21f457ebf3d572eebc19b573d2d0443db Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Fri, 28 Feb 2020 14:32:45 +1100 Subject: [PATCH 079/107] lint the code again and ready for review --- ts/components/session/settings/SessionSettings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 2a81b77de..0d885f85f 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -466,7 +466,7 @@ export class SettingsView extends React.Component { min: 60, max: 200, defaultValue: 100, - info: (value: number) => `Zoom Factor: ${value}%` + info: (value: number) => `Zoom Factor: ${value}%`, }, confirmationDialogParams: undefined, }, From a09e370e1d8c0cdd29e4639e93c3587402b6a6bf Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 27 Feb 2020 12:11:08 +1100 Subject: [PATCH 080/107] Added testing on pull request Disable proxy in tests. --- .github/workflows/pull-request.yml | 67 +++ js/models/messages.js | 2 +- js/modules/loki_message_api.js | 7 +- js/modules/loki_rpc.js | 7 +- js/modules/loki_snode_api.js | 21 +- libloki/test/_test.js | 5 +- libtextsecure/message_receiver.js | 5 +- libtextsecure/test/_test.js | 5 +- package.json | 3 +- preload.js | 42 +- test/_test.js | 5 +- test/backup_test.js | 16 +- test/index.html | 517 +++++++++--------- test/spellcheck_test.js | 10 +- .../conversation/CreateGroupDialog.tsx | 6 +- ts/util/lint/linter.ts | 1 + yarn.lock | 10 +- 17 files changed, 426 insertions(+), 303 deletions(-) create mode 100644 .github/workflows/pull-request.yml diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 000000000..c500c9ca0 --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,67 @@ +# This script will run tests anytime a pull request is added +name: Session Test + +on: + pull_request: + branches: + - development + - clearnet + - github-actions + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [windows-2016, macos-latest, ubuntu-latest] + env: + SIGNAL_ENV: production + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - run: git config --global core.autocrlf false + + - name: Checkout git repo + uses: actions/checkout@v1 + + - name: Install node + uses: actions/setup-node@v1 + with: + node-version: 10.13.0 + + - name: Setup node for windows + if: runner.os == 'Windows' + run: | + npm install --global --production windows-build-tools@4.0.0 + npm install --global node-gyp@latest + npm config set python python2.7 + npm config set msvs_version 2015 + + - name: Install yarn + run: npm install yarn --no-save + + - name: Install Dependencies + run: yarn install --frozen-lockfile + + - name: Generate and concat files + run: yarn generate + + - name: Lint Files + run: | + yarn format-full --list-different + yarn eslint + yarn tslint + + - name: Make linux use en_US locale + if: runner.os == 'Linux' + run: | + sudo apt-get install -y hunspell-en-us + sudo locale-gen en_US.UTF-8 + sudo dpkg-reconfigure locales + echo ::set-env name=DISPLAY:::9.0 + echo ::set-env name=LANG::en_US.UTF-8 + + - name: Test + uses: GabrielBB/xvfb-action@v1.0 + with: + run: yarn test diff --git a/js/models/messages.js b/js/models/messages.js index 7b024886b..e6dc4f074 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -1422,7 +1422,7 @@ if (this.get('type') !== 'friend-request') { const c = this.getConversation(); // Don't bother sending sync messages to public chats - if (!c.isPublic()) { + if (c && !c.isPublic()) { this.sendSyncMessage(); } } diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index 98411fd85..88b02b7af 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -217,7 +217,12 @@ class LokiMessageAPI { } return true; } catch (e) { - log.warn('Loki send message error:', e.code, e.message, `from ${address}`); + log.warn( + 'Loki send message error:', + e.code, + e.message, + `from ${address}` + ); if (e instanceof textsecure.WrongSwarmError) { const { newSwarm } = e; await lokiSnodeAPI.updateSwarmNodes(params.pubKey, newSwarm); diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index 61fb83926..5f29f6c38 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -70,7 +70,10 @@ const sendToProxy = async (options = {}, targetNode) => { // detect SNode is not ready (not in swarm; not done syncing) if (response.status === 503) { const ciphertext = await response.text(); - log.error(`lokiRpc sendToProxy snode ${randSnode.ip}:${randSnode.port} error`, ciphertext); + log.error( + `lokiRpc sendToProxy snode ${randSnode.ip}:${randSnode.port} error`, + ciphertext + ); // mark as bad for this round (should give it some time and improve success rates) lokiSnodeAPI.markRandomNodeUnreachable(randSnode); // retry for a new working snode @@ -104,7 +107,7 @@ const sendToProxy = async (options = {}, targetNode) => { const textDecoder = new TextDecoder(); plaintext = textDecoder.decode(plaintextBuffer); - } catch(e) { + } catch (e) { log.error( 'lokiRpc sendToProxy decode error', e.code, diff --git a/js/modules/loki_snode_api.js b/js/modules/loki_snode_api.js index 7bc2944ed..7d163a215 100644 --- a/js/modules/loki_snode_api.js +++ b/js/modules/loki_snode_api.js @@ -31,7 +31,10 @@ class LokiSnodeAPI { ]; } - async initialiseRandomPool(seedNodes = [...window.seedNodeList], consecutiveErrors = 0) { + async initialiseRandomPool( + seedNodes = [...window.seedNodeList], + consecutiveErrors = 0 + ) { const params = { limit: 20, active_only: true, @@ -71,7 +74,10 @@ class LokiSnodeAPI { if (consecutiveErrors < 3) { // retry after a possible delay setTimeout(() => { - log.info('Retrying initialising random snode pool, try #', consecutiveErrors); + log.info( + 'Retrying initialising random snode pool, try #', + consecutiveErrors + ); this.initialiseRandomPool(seedNodes, consecutiveErrors + 1); }, consecutiveErrors * consecutiveErrors * 5000); } else { @@ -181,7 +187,12 @@ class LokiSnodeAPI { const snodes = result.snodes.filter(tSnode => tSnode.ip !== '0.0.0.0'); return snodes; } catch (e) { - log.error('getSnodesForPubkey error', e.code, e.message, `for ${snode.ip}:${snode.port}`); + log.error( + 'getSnodesForPubkey error', + e.code, + e.message, + `for ${snode.ip}:${snode.port}` + ); this.markRandomNodeUnreachable(snode); return []; } @@ -197,7 +208,9 @@ class LokiSnodeAPI { const resList = await this.getSnodesForPubkey(rSnode, pubKey); // should we only activate entries that are in all results? resList.map(item => { - const hasItem = snodes.some(hItem => item.ip === hItem.ip && item.port === hItem.port); + const hasItem = snodes.some( + hItem => item.ip === hItem.ip && item.port === hItem.port + ); if (!hasItem) { snodes.push(item); } diff --git a/libloki/test/_test.js b/libloki/test/_test.js index e26c8ab40..ab439a5e8 100644 --- a/libloki/test/_test.js +++ b/libloki/test/_test.js @@ -1,6 +1,9 @@ /* global window, mocha, chai, assert, Whisper */ -mocha.setup('bdd'); +mocha + .setup('bdd') + .fullTrace() + .timeout(10000); window.assert = chai.assert; window.PROTO_ROOT = '../../protos'; diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 0e6204016..b6b24f355 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -59,7 +59,10 @@ function MessageReceiver(username, password, signalingKey, options = {}) { lokiPublicChatAPI.removeAllListeners('publicMessage'); // we only need one MR in the system handling these // bind events - lokiPublicChatAPI.on('publicMessage', this.handleUnencryptedMessage.bind(this)); + lokiPublicChatAPI.on( + 'publicMessage', + this.handleUnencryptedMessage.bind(this) + ); openGroupBound = true; } } else { diff --git a/libtextsecure/test/_test.js b/libtextsecure/test/_test.js index a77e132e9..7edd32586 100644 --- a/libtextsecure/test/_test.js +++ b/libtextsecure/test/_test.js @@ -1,6 +1,9 @@ /* global mocha, chai, assert */ -mocha.setup('bdd'); +mocha + .setup('bdd') + .fullTrace() + .timeout(10000); window.assert = chai.assert; window.PROTO_ROOT = '../../protos'; diff --git a/package.json b/package.json index c5a4d1a00..206065e8d 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "test-node-coverage": "nyc --reporter=lcov --reporter=text mocha --recursive test/app test/modules ts/test libloki/test/node", "test-node-coverage-html": "nyc --reporter=lcov --reporter=html mocha --recursive test/a/* */pp test/modules ts/test libloki/test/node", "eslint": "eslint --cache .", + "eslint-fix": "eslint --fix .", "eslint-full": "eslint .", "lint": "yarn format --list-different && yarn lint-windows", "lint-full": "yarn format-full --list-different; yarn lint-windows-full", @@ -124,7 +125,7 @@ "reselect": "4.0.0", "rimraf": "2.6.2", "semver": "5.4.1", - "spellchecker": "3.5.1", + "spellchecker": "3.7.0", "tar": "4.4.8", "testcheck": "1.0.0-rc.2", "tmp": "0.0.33", diff --git a/preload.js b/preload.js index c9383e136..c7b161af3 100644 --- a/preload.js +++ b/preload.js @@ -475,23 +475,6 @@ contextMenu({ // /tmp mounted as noexec on Linux. require('./js/spell_check'); -if (config.environment === 'test') { - const isTravis = 'TRAVIS' in process.env && 'CI' in process.env; - const isWindows = process.platform === 'win32'; - /* eslint-disable global-require, import/no-extraneous-dependencies */ - window.test = { - glob: require('glob'), - fse: require('fs-extra'), - tmp: require('tmp'), - path: require('path'), - basePath: __dirname, - attachmentsPath: window.Signal.Migrations.attachmentsPath, - isTravis, - isWindows, - }; - /* eslint-enable global-require, import/no-extraneous-dependencies */ -} - window.shortenPubkey = pubkey => `(...${pubkey.substring(pubkey.length - 6)})`; window.pubkeyPattern = /@[a-fA-F0-9]{64,66}\b/g; @@ -509,3 +492,28 @@ Promise.prototype.ignore = function() { // eslint-disable-next-line more/no-then this.then(() => {}); }; + +if (config.environment.includes('test')) { + const isTravis = 'TRAVIS' in process.env && 'CI' in process.env; + const isWindows = process.platform === 'win32'; + /* eslint-disable global-require, import/no-extraneous-dependencies */ + window.test = { + glob: require('glob'), + fse: require('fs-extra'), + tmp: require('tmp'), + path: require('path'), + basePath: __dirname, + attachmentsPath: window.Signal.Migrations.attachmentsPath, + isTravis, + isWindows, + }; + /* eslint-enable global-require, import/no-extraneous-dependencies */ + window.lokiFeatureFlags = {}; + window.lokiSnodeAPI = { + refreshSwarmNodesForPubKey: () => [], + getFreshSwarmNodes: () => [], + updateSwarmNodes: () => {}, + updateLastHash: () => {}, + getSwarmNodesForPubKey: () => [], + }; +} diff --git a/test/_test.js b/test/_test.js index 6e1e03ae2..2f9a3a30b 100644 --- a/test/_test.js +++ b/test/_test.js @@ -1,6 +1,9 @@ /* global chai, Whisper, _, Backbone */ -mocha.setup('bdd'); +mocha + .setup('bdd') + .fullTrace() + .timeout(10000); window.assert = chai.assert; window.PROTO_ROOT = '../protos'; diff --git a/test/backup_test.js b/test/backup_test.js index f5146cc09..7d7ef8eea 100644 --- a/test/backup_test.js +++ b/test/backup_test.js @@ -239,20 +239,12 @@ describe('Backup', () => { it('exports then imports to produce the same data we started with', async function thisNeeded() { this.timeout(6000); - const { - attachmentsPath, - fse, - glob, - path, - tmp, - isTravis, - isWindows, - } = window.test; - - // Skip this test on travis windows + const { attachmentsPath, fse, glob, path, tmp, isWindows } = window.test; + + // Skip this test on windows // because it always fails due to lstat permission error. // Don't know how to fix it so this is a temp work around. - if (isTravis && isWindows) { + if (isWindows) { console.log( 'Skipping exports then imports to produce the same data we started' ); diff --git a/test/index.html b/test/index.html index a5bc6a046..54d1dd46f 100644 --- a/test/index.html +++ b/test/index.html @@ -1,7 +1,7 @@ - + TextSecure test runner @@ -11,90 +11,90 @@
-
+
-
+
- - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -505,50 +505,62 @@ - + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -559,11 +571,10 @@ - + - diff --git a/test/spellcheck_test.js b/test/spellcheck_test.js index 57f7b529f..6937d9d9e 100644 --- a/test/spellcheck_test.js +++ b/test/spellcheck_test.js @@ -1,6 +1,12 @@ describe('spellChecker', () => { it('should work', () => { - assert(window.spellChecker.spellCheck('correct')); - assert(!window.spellChecker.spellCheck('fhqwgads')); + assert( + window.spellChecker.spellCheck('correct'), + 'Spellchecker returned false on a correct word.' + ); + assert( + !window.spellChecker.spellCheck('fhqwgads'), + 'Spellchecker returned true on a incorrect word.' + ); }); }); diff --git a/ts/components/conversation/CreateGroupDialog.tsx b/ts/components/conversation/CreateGroupDialog.tsx index ce853d4ce..482a35579 100644 --- a/ts/components/conversation/CreateGroupDialog.tsx +++ b/ts/components/conversation/CreateGroupDialog.tsx @@ -95,7 +95,11 @@ export class CreateGroupDialog extends React.Component { ); return ( - null}> + null} + >

{this.state.errorMessage}

diff --git a/ts/util/lint/linter.ts b/ts/util/lint/linter.ts index a481a7db1..1ada85d60 100644 --- a/ts/util/lint/linter.ts +++ b/ts/util/lint/linter.ts @@ -73,6 +73,7 @@ const excludedFiles = [ '^js/Mp3LameEncoder.min.js', // Test files + '^libloki/test/*', '^libtextsecure/test/*', '^test/*', diff --git a/yarn.lock b/yarn.lock index c97426256..dc8d73cae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9704,13 +9704,13 @@ speedometer@~0.1.2: resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d" integrity sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0= -spellchecker@3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/spellchecker/-/spellchecker-3.5.1.tgz#72cc2bcbb0c610536bc2a36df1ced9b2665816cc" - integrity sha512-R1qUBsDZzio+7MFZN6/AtPUe5NGvnc0wywckuXAlp9akASaYSFqKuI5O8p3rSiA+yKP31qC7Iijjoygmzkh6xw== +spellchecker@3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/spellchecker/-/spellchecker-3.7.0.tgz#d63e6fd612352b0108e7bbf942f271665ff63c8b" + integrity sha512-saQT4BR9nivbK70s0YjyIlSbZzO6bfWRULcGL2JU7fi7wotOnWl70P0QoUwwLywNQJQ47osgCo6GmOlqzRTxbQ== dependencies: any-promise "^1.3.0" - nan "^2.10.0" + nan "^2.14.0" split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" From b7a1c5fd5a485a7dbd6124fdf8e3b66cfb89544d Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 28 Feb 2020 14:54:12 +1100 Subject: [PATCH 081/107] Remove old CI files --- .gitlab-ci.yml | 61 -------------------------------------------------- .travis.yml | 33 --------------------------- appveyor.yml | 24 -------------------- aptly.sh | 52 ------------------------------------------ preload.js | 2 -- travis.sh | 13 ----------- 6 files changed, 185 deletions(-) delete mode 100644 .gitlab-ci.yml delete mode 100644 .travis.yml delete mode 100644 appveyor.yml delete mode 100755 aptly.sh delete mode 100755 travis.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 76d3a0c03..000000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,61 +0,0 @@ -# TODO: Figure out a way to use nvm in the linux build -linux: - image: node:10.13.0 - tags: - - docker - script: - - whoami - - node -v - - yarn -v - - yarn install --frozen-lockfile - - export SIGNAL_ENV=production - - yarn generate - - $(yarn bin)/electron-builder --config.extraMetadata.environment=$SIGNAL_ENV --config.mac.bundleVersion='$CI_COMMIT_REF_SLUG' --publish=never --config.directories.output=release - cache: - paths: - - node_modules/ - artifacts: - paths: - - release/ - -osx: - tags: - - osx - script: - - nvm install - - npm install --global yarn - - yarn install --frozen-lockfile - - export SIGNAL_ENV=production - - yarn generate - - $(yarn bin)/electron-builder --config.extraMetadata.environment=$SIGNAL_ENV --config.mac.bundleVersion='$CI_COMMIT_REF_SLUG' --publish=never --config.directories.output=release - cache: - paths: - - node_modules/ - artifacts: - paths: - - release/ - -windows: - tags: - - windows-cmd - script: - # install - - set PATH=%PATH%;C:\Users\Administrator\AppData\Local\nvs\ - - set SIGNAL_ENV=production - - set /p NVMRC_VER=<.nvmrc - - call nvs add %NVMRC_VER% - - call nvs use %NVMRC_VER% - - call "C:\\PROGRA~2\\MICROS~1\\2017\\BuildTools\\Common7\\Tools\\VsDevCmd.bat" - - call yarn install --frozen-lockfile - # build - - call yarn generate - - call node build\grunt.js - - call yarn prepare-beta-build - - call node_modules\.bin\electron-builder --config.extraMetadata.environment=%SIGNAL_ENV% --publish=never --config.directories.output=release - - call node build\grunt.js test-release:win - cache: - paths: - - node_modules/ - artifacts: - paths: - - release/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5f2a7a5a6..000000000 --- a/.travis.yml +++ /dev/null @@ -1,33 +0,0 @@ -language: node_js -cache: - yarn: true - directories: - - node_modules -node_js: - - '10.13.0' -install: - - travis_wait 30 yarn install --frozen-lockfile --network-timeout 1000000 -script: - - yarn generate - - yarn lint-windows - - yarn test -env: - global: - - SIGNAL_ENV: production -sudo: false -notifications: - email: false - -matrix: - include: - - name: 'Linux' - os: linux - dist: trusty - before_install: - - sudo apt-get install -y libgtk2.0-0 libgtk-3-0 libgconf-2-4 libasound2 libxtst6 libxss1 libnss3 xvfb hunspell-en-us - before_script: - - Xvfb -ac -screen scrn 1280x2000x24 :9.0 & - - export DISPLAY=:9.0 - - export LC_ALL=en_US - - name: 'OSX' - os: osx diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 6b3167e51..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,24 +0,0 @@ -platform: - - x64 - -cache: - - '%LOCALAPPDATA%\electron\Cache' - - node_modules -> package.json - -install: - - systeminfo | findstr /C:"OS" - - set PATH=C:\Ruby23-x64\bin;%PATH% - - ps: Install-Product node 10.13.0 x64 - - yarn install --frozen-lockfile - -build_script: - - node build\grunt.js - - yarn generate - - yarn lint-windows - - yarn test-node - -test_script: - - node build\grunt.js test - -environment: - SIGNAL_ENV: production diff --git a/aptly.sh b/aptly.sh deleted file mode 100755 index a761b806f..000000000 --- a/aptly.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -# Setup - creates the local repo which will be mirrored up to S3, then back-fill it. Your -# future deploys will eliminate all old versions without these backfill steps: -# aptly repo create signal-desktop -# aptly mirror create -ignore-signatures backfill-mirror https://updates.signal.org/desktop/apt xenial -# aptly mirror update -ignore-signatures backfill-mirror -# aptly repo import backfill-mirror signal-desktop signal-desktop signal-desktop-beta -# aptly repo show -with-packages signal-desktop -# -# First run on a machine - uncomment the first set of 'aptly publish snapshot' commands, -# comment the other two. Sets up the two publish channels, one local, one to S3. -# -# Testing - comment out the lines with s3:$ENDPOINT to publish only locally. To eliminate -# effects of testing, remove package from repo, then move back to old snapshot: -# aptly repo remove signal-desktop signal-desktop_1.0.35_amd64 -# aptly publish switch -gpg-key=57F6FB06 xenial signal-desktop_v1.0.34 -# -# Pruning package set - we generally want 2-3 versions of each stream available, -# production and beta. You can remove old packages like this: -# aptly repo show -with-packages signal-desktop -# aptly repo remove signal-desktop signal-desktop_1.0.34_amd64 -# -# Release: -# NAME=signal-desktop(-beta) VERSION=X.X.X ./aptly.sh - -echo "Releasing $NAME build version $VERSION" - -REPO=signal-desktop -CURRENT=xenial -# PREVIOUS=xenial -ENDPOINT=signal-desktop-apt # Matches endpoint name in .aptly.conf -SNAPSHOT=signal-desktop_v$VERSION -GPG_KEYID=57F6FB06 - -aptly repo add $REPO release/$NAME\_$VERSION\_*.deb -aptly snapshot create $SNAPSHOT from repo $REPO - -# run these only on first release to a given repo from a given machine. the first set is -# for local testing, the second set is to set up the production server. -# https://www.aptly.info/doc/aptly/publish/snapshot/ -# aptly publish snapshot -gpg-key=$GPG_KEYID -distribution=$CURRENT $SNAPSHOT -# aptly publish snapshot -gpg-key=$GPG_KEYID -distribution=$PREVIOUS $SNAPSHOT -# aptly publish snapshot -gpg-key=$GPG_KEYID -distribution=$CURRENT -config=.aptly.conf $SNAPSHOT s3:$ENDPOINT: -# aptly publish snapshot -gpg-key=$GPG_KEYID -distribution=$PREVIOUS -config=.aptly.conf $SNAPSHOT s3:$ENDPOINT: - -# these update already-published repos, run every time after that -# https://www.aptly.info/doc/aptly/publish/switch/ -aptly publish switch -gpg-key=$GPG_KEYID $CURRENT $SNAPSHOT -# aptly publish switch -gpg-key=$GPG_KEYID $PREVIOUS $SNAPSHOT -aptly publish switch -gpg-key=$GPG_KEYID -config=.aptly.conf $CURRENT s3:$ENDPOINT: $SNAPSHOT -# aptly publish switch -gpg-key=$GPG_KEYID -config=.aptly.conf $PREVIOUS s3:$ENDPOINT: $SNAPSHOT - diff --git a/preload.js b/preload.js index c7b161af3..901dd64d2 100644 --- a/preload.js +++ b/preload.js @@ -494,7 +494,6 @@ Promise.prototype.ignore = function() { }; if (config.environment.includes('test')) { - const isTravis = 'TRAVIS' in process.env && 'CI' in process.env; const isWindows = process.platform === 'win32'; /* eslint-disable global-require, import/no-extraneous-dependencies */ window.test = { @@ -504,7 +503,6 @@ if (config.environment.includes('test')) { path: require('path'), basePath: __dirname, attachmentsPath: window.Signal.Migrations.attachmentsPath, - isTravis, isWindows, }; /* eslint-enable global-require, import/no-extraneous-dependencies */ diff --git a/travis.sh b/travis.sh deleted file mode 100755 index 8515d5851..000000000 --- a/travis.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -e - -if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start - sleep 3 -fi - -yarn test-electron - -NODE_ENV=production yarn grunt test-release:$TRAVIS_OS_NAME From d9dd1f4340a970831f8d14022abb5a084e21f72e Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Fri, 28 Feb 2020 16:26:42 +1100 Subject: [PATCH 082/107] hard dots enabled as true --- ts/components/session/settings/SessionSettingListItem.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ts/components/session/settings/SessionSettingListItem.tsx b/ts/components/session/settings/SessionSettingListItem.tsx index 517ad205e..101162c49 100644 --- a/ts/components/session/settings/SessionSettingListItem.tsx +++ b/ts/components/session/settings/SessionSettingListItem.tsx @@ -25,6 +25,7 @@ interface State { } export class SessionSettingListItem extends React.Component { + public static defaultProps = { inline: true, }; @@ -40,7 +41,7 @@ export class SessionSettingListItem extends React.Component { public render(): JSX.Element { const { title, description, type, value, content } = this.props; - + console.log(content.dotsEnable, 'dotenable?'); const inline = !!type && ![SessionSettingType.Options, SessionSettingType.Slider].includes(type); @@ -91,7 +92,7 @@ export class SessionSettingListItem extends React.Component { {type === SessionSettingType.Slider && (
Date: Fri, 28 Feb 2020 16:38:55 +1100 Subject: [PATCH 083/107] lint the code again and ready for merge --- ts/components/session/settings/SessionSettingListItem.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/ts/components/session/settings/SessionSettingListItem.tsx b/ts/components/session/settings/SessionSettingListItem.tsx index 101162c49..fd7ea27d9 100644 --- a/ts/components/session/settings/SessionSettingListItem.tsx +++ b/ts/components/session/settings/SessionSettingListItem.tsx @@ -25,7 +25,6 @@ interface State { } export class SessionSettingListItem extends React.Component { - public static defaultProps = { inline: true, }; @@ -41,7 +40,6 @@ export class SessionSettingListItem extends React.Component { public render(): JSX.Element { const { title, description, type, value, content } = this.props; - console.log(content.dotsEnable, 'dotenable?'); const inline = !!type && ![SessionSettingType.Options, SessionSettingType.Slider].includes(type); From eff2eeb119ecf1c6b4d6405dcc1bcf6c7d825c3e Mon Sep 17 00:00:00 2001 From: Brian Jian Zhao Date: Mon, 2 Mar 2020 11:35:42 +1100 Subject: [PATCH 084/107] remove zoom factor text next to % --- ts/components/session/settings/SessionSettings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 0d885f85f..233ef340e 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -466,7 +466,7 @@ export class SettingsView extends React.Component { min: 60, max: 200, defaultValue: 100, - info: (value: number) => `Zoom Factor: ${value}%`, + info: (value: number) => `${value}%`, }, confirmationDialogParams: undefined, }, From 56d5bb5b338d235afaee7e71f20b7efb22803a98 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Mon, 2 Mar 2020 16:11:58 -0800 Subject: [PATCH 085/107] fix lint --- js/views/conversation_view.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 34b25459f..14e59bd44 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -1192,6 +1192,8 @@ // This message is likely not loaded yet in the DOM if (!position) { // should this be break? + + // eslint-disable-next-line no-continue continue; } const { top } = position; From 81d43ba5abd8fa08efc33d992f0bd0c97faae942 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Mon, 2 Mar 2020 16:30:30 -0800 Subject: [PATCH 086/107] lint --- js/views/conversation_view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 14e59bd44..bebee05cf 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -1192,7 +1192,7 @@ // This message is likely not loaded yet in the DOM if (!position) { // should this be break? - + // eslint-disable-next-line no-continue continue; } From bea2930ce3345bf023acdd2b75f00fe37dbe708f Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Mon, 2 Mar 2020 16:37:02 -0800 Subject: [PATCH 087/107] try some additional linux formats --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index c5a4d1a00..b71b14c3f 100644 --- a/package.json +++ b/package.json @@ -244,7 +244,10 @@ "asarUnpack": "node_modules/spellchecker/vendor/hunspell_dictionaries", "target": [ "deb", - "AppImage" + "rpm", + "snap", + "AppImage", + "tar.xz" ], "icon": "build/icons/png" }, From 78389440c0f264d0c0e628b7271f1255d6030ea1 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 3 Mar 2020 15:17:30 +1100 Subject: [PATCH 088/107] Update sqlcipher to 4.2.0. --- app/global_errors.js | 3 +++ app/sql.js | 19 +++++++++++++++++++ package.json | 4 +++- yarn.lock | 18 +++++++++--------- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/app/global_errors.js b/app/global_errors.js index 85ddee387..16d54327f 100644 --- a/app/global_errors.js +++ b/app/global_errors.js @@ -10,6 +10,9 @@ let quitText = 'Quit'; let copyErrorAndQuitText = 'Copy error and quit'; function handleError(prefix, error) { + if (console._error) { + console._error(`${prefix}:`, Errors.toLogFormat(error)); + } console.error(`${prefix}:`, Errors.toLogFormat(error)); if (app.isReady()) { diff --git a/app/sql.js b/app/sql.js index 29f19027d..478ec44d3 100644 --- a/app/sql.js +++ b/app/sql.js @@ -231,6 +231,15 @@ async function getSQLCipherVersion(instance) { } } +async function getSQLIntegrityCheck(instance) { + const row = await instance.get('PRAGMA cipher_integrity_check;'); + if (row) { + return row.cipher_integrity_check; + } + + return null; +} + const HEX_KEY = /[^0-9A-Fa-f]/; async function setupSQLCipher(instance, { key }) { // If the key isn't hex then we need to derive a hex key from it @@ -239,6 +248,9 @@ async function setupSQLCipher(instance, { key }) { // https://www.zetetic.net/sqlcipher/sqlcipher-api/#key const value = deriveKey ? `'${key}'` : `"x'${key}'"`; await instance.run(`PRAGMA key = ${value};`); + + // https://www.zetetic.net/blog/2018/11/30/sqlcipher-400-release/#compatability-sqlcipher-4-0-0 + await instance.run('PRAGMA cipher_migrate;'); } async function setSQLPassword(password) { @@ -1071,6 +1083,13 @@ async function initialize({ configDir, key, messages, passwordAttempt }) { db = promisified; // test database + + const result = await getSQLIntegrityCheck(db); + if (result) { + console.log('Database integrity check failed:', result); + throw new Error(`Integrity check failed: ${result}`); + } + await getMessageCount(); } catch (error) { if (passwordAttempt) { diff --git a/package.json b/package.json index 206065e8d..ef1bad9b6 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "ready": "yarn clean-transpile && yarn grunt && yarn lint-full && yarn test-node && yarn test-electron && yarn lint-deps" }, "dependencies": { - "@journeyapps/sqlcipher": "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#2e28733b61640556b0272a3bfc78b0357daf71e6", + "@journeyapps/sqlcipher": "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#b10f232fac62ba7f8775c9e086bb5558fe7d948b", "@sindresorhus/is": "0.8.0", "@types/dompurify": "^2.0.0", "@types/rc-slider": "^8.6.5", @@ -316,6 +316,8 @@ "node_modules/socks/build/client/*.js", "node_modules/smart-buffer/build/*.js", "!node_modules/@journeyapps/sqlcipher/deps/*", + "!node_modules/@journeyapps/sqlcipher/build/*", + "!node_modules/@journeyapps/sqlcipher/lib/binding/node-*", "!build/*.js" ] } diff --git a/yarn.lock b/yarn.lock index dc8d73cae..b7a8a3a0e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27,12 +27,12 @@ ajv "^6.1.0" ajv-keywords "^3.1.0" -"@journeyapps/sqlcipher@https://github.com/scottnonnenberg-signal/node-sqlcipher.git#2e28733b61640556b0272a3bfc78b0357daf71e6": - version "3.2.1" - resolved "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#2e28733b61640556b0272a3bfc78b0357daf71e6" +"@journeyapps/sqlcipher@https://github.com/scottnonnenberg-signal/node-sqlcipher.git#b10f232fac62ba7f8775c9e086bb5558fe7d948b": + version "4.0.0" + resolved "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#b10f232fac62ba7f8775c9e086bb5558fe7d948b" dependencies: - nan "^2.10.0" - node-pre-gyp "^0.10.0" + nan "^2.12.1" + node-pre-gyp "^0.11.0" "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" @@ -6806,10 +6806,10 @@ node-libs-browser@^2.0.0: util "^0.11.0" vm-browserify "^1.0.1" -node-pre-gyp@^0.10.0: - version "0.10.3" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" - integrity sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A== +node-pre-gyp@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054" + integrity sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q== dependencies: detect-libc "^1.0.2" mkdirp "^0.5.1" From 9b030cfed31cffc3e2b4ded8ccd4427a4d8fc9ca Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 3 Mar 2020 18:08:49 -0800 Subject: [PATCH 089/107] Revert "try some additional linux formats" This reverts commit bea2930ce3345bf023acdd2b75f00fe37dbe708f. --- package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/package.json b/package.json index b71b14c3f..c5a4d1a00 100644 --- a/package.json +++ b/package.json @@ -244,10 +244,7 @@ "asarUnpack": "node_modules/spellchecker/vendor/hunspell_dictionaries", "target": [ "deb", - "rpm", - "snap", - "AppImage", - "tar.xz" + "AppImage" ], "icon": "build/icons/png" }, From 1792e2d952b01a493af098da4d4fbcdc03b9ae47 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 3 Mar 2020 18:39:51 -0800 Subject: [PATCH 090/107] add editGroupNameOrPicture to gear --- js/views/conversation_view.js | 4 ++++ ts/components/conversation/ConversationHeader.tsx | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 9f28d86c7..7a5762e2a 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -252,6 +252,10 @@ window.Whisper.events.trigger('inviteFriends', this.model); }, + onUpdateGroupName: () => { + window.Whisper.events.trigger('updateGroupName', this.model); + }, + onAddModerators: () => { window.Whisper.events.trigger('addModerators', this.model); }, diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index 5f90e7920..7120b5a19 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -92,6 +92,7 @@ interface Props { onRemoveModerators: () => void; onInviteFriends: () => void; onAvatarClick?: (userPubKey: string) => void; + onUpdateGroupName: () => void; i18n: LocalizerType; } @@ -292,6 +293,7 @@ export class ConversationHeader extends React.Component { onAddModerators, onRemoveModerators, onInviteFriends, + onUpdateGroupName, } = this.props; const isPrivateGroup = isGroup && !isPublic && !isRss; @@ -313,6 +315,9 @@ export class ConversationHeader extends React.Component { {i18n('removeModerators')} ) : null} + {amMod ? ( + {i18n('editGroupNameOrPicture')} + ) : null} {isPrivateGroup ? ( {i18n('leaveGroup')} ) : null} From a53fcee1bd0f9564f4caad8643baac8a96c14ba5 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 3 Mar 2020 18:42:42 -0800 Subject: [PATCH 091/107] lint --- ts/components/conversation/ConversationHeader.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index 7120b5a19..3b3d33bbb 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -316,7 +316,9 @@ export class ConversationHeader extends React.Component { ) : null} {amMod ? ( - {i18n('editGroupNameOrPicture')} + + {i18n('editGroupNameOrPicture')} + ) : null} {isPrivateGroup ? ( {i18n('leaveGroup')} From 3038a8c7d203dac834907c9cda0d23af5e4ef4c5 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 3 Mar 2020 18:48:25 -0800 Subject: [PATCH 092/107] use lodash to reduce confusion --- 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 5791b7347..c6f77a010 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -1737,8 +1737,8 @@ class LokiPublicChannelAPI { message => !(message.source in slavePrimaryMap) ); // get minimum ID for primaryMessages and slaveMessages - const firstPrimaryId = Math.min(...primaryMessages.map(msg => msg.serverId)); - const firstSlaveId = Math.min(...slaveMessages.map(msg => msg.serverId)); + const firstPrimaryId = _.min(primaryMessages.map(msg => msg.serverId)); + const firstSlaveId = _.min(slaveMessages.map(msg => msg.serverId)); if (firstPrimaryId < firstSlaveId) { // early send // split off count from pendingMessages From 2b29b76d66399998bba95613d23900346c599d4f Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 3 Mar 2020 18:54:17 -0800 Subject: [PATCH 093/107] lint --- js/modules/loki_app_dot_net_api.js | 6 +- js/modules/loki_message_api.js | 7 +- js/modules/loki_public_chat_api2.js | 209 ++++++++++++++++++ js/modules/loki_rpc.js | 7 +- js/modules/loki_snode_api.js | 21 +- libtextsecure/message_receiver.js | 5 +- .../conversation/CreateGroupDialog.tsx | 6 +- 7 files changed, 249 insertions(+), 12 deletions(-) create mode 100644 js/modules/loki_public_chat_api2.js diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index c6f77a010..c4945fd76 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -1259,7 +1259,7 @@ class LokiPublicChannelAPI { // local file avatar const resolvedAvatar = path.normalize(note.value.avatar); const base = path.normalize('images/'); - const re = new RegExp(`^${base}`) + const re = new RegExp(`^${base}`); // do we at least ends up inside images/ somewhere? if (re.test(resolvedAvatar)) { this.conversation.set('avatar', resolvedAvatar); @@ -1743,10 +1743,10 @@ class LokiPublicChannelAPI { // early send // split off count from pendingMessages let sendNow = []; - ([sendNow, pendingMessages] = _.partition( + [sendNow, pendingMessages] = _.partition( pendingMessages, message => message.serverId < firstSlaveId - )); + ); sendNow.forEach(message => { // send them out now log.info('emitting primary message', message.serverId); diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index 98411fd85..88b02b7af 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -217,7 +217,12 @@ class LokiMessageAPI { } return true; } catch (e) { - log.warn('Loki send message error:', e.code, e.message, `from ${address}`); + log.warn( + 'Loki send message error:', + e.code, + e.message, + `from ${address}` + ); if (e instanceof textsecure.WrongSwarmError) { const { newSwarm } = e; await lokiSnodeAPI.updateSwarmNodes(params.pubKey, newSwarm); diff --git a/js/modules/loki_public_chat_api2.js b/js/modules/loki_public_chat_api2.js new file mode 100644 index 000000000..b2b55c4e2 --- /dev/null +++ b/js/modules/loki_public_chat_api2.js @@ -0,0 +1,209 @@ +/* global log, textsecure */ +const EventEmitter = require('events'); +const nodeFetch = require('node-fetch'); +const { URL, URLSearchParams } = require('url'); + +const GROUPCHAT_POLL_EVERY = 1000; // 1 second + +// singleton to relay events to libtextsecure/message_receiver +class LokiPublicChatAPI extends EventEmitter { + constructor(ourKey) { + super(); + this.ourKey = ourKey; + this.lastGot = {}; + this.servers = []; + } + findOrCreateServer(hostport) { + let thisServer = null; + log.info(`LokiPublicChatAPI looking for ${hostport}`); + this.servers.some(server => { + // if we already have this hostport registered + if (server.server === hostport) { + thisServer = server; + return true; + } + return false; + }); + if (thisServer === null) { + thisServer = new LokiPublicServerAPI(this, hostport); + this.servers.push(thisServer); + } + return thisServer; + } +} + +class LokiPublicServerAPI { + constructor(chatAPI, hostport) { + this.chatAPI = chatAPI; + this.server = hostport; + this.channels = []; + } + findOrCreateChannel(channelId, conversationId) { + let thisChannel = null; + this.channels.forEach(channel => { + if ( + channel.channelId === channelId && + channel.conversationId === conversationId + ) { + thisChannel = channel; + } + }); + if (thisChannel === null) { + thisChannel = new LokiPublicChannelAPI(this, channelId, conversationId); + this.channels.push(thisChannel); + } + return thisChannel; + } + unregisterChannel(channelId) { + // find it, remove it + // if no channels left, request we deregister server + return channelId || this; // this is just to make eslint happy + } +} + +class LokiPublicChannelAPI { + constructor(serverAPI, channelId, conversationId) { + this.serverAPI = serverAPI; + this.channelId = channelId; + this.baseChannelUrl = `${serverAPI.server}/channels/${this.channelId}`; + this.groupName = 'unknown'; + this.conversationId = conversationId; + this.lastGot = 0; + log.info(`registered LokiPublicChannel ${channelId}`); + // start polling + this.pollForMessages(); + } + + async pollForChannel(source, endpoint) { + // groupName will be loaded from server + const url = new URL(this.baseChannelUrl); + /* + const params = { + include_annotations: 1, + }; + */ + let res; + let success = true; + try { + res = await nodeFetch(url); + } catch (e) { + success = false; + } + + const response = await res.json(); + if (response.meta.code !== 200) { + success = false; + } + // update this.groupId + return endpoint || success; + } + + async pollForDeletions() { + // let id = 0; + // read all messages from 0 to current + // delete local copies if server state has changed to delete + // run every minute + const url = new URL(this.baseChannelUrl); + /* + const params = { + include_annotations: 1, + }; + */ + let res; + let success = true; + try { + res = await nodeFetch(url); + } catch (e) { + success = false; + } + + const response = await res.json(); + if (response.meta.code !== 200) { + success = false; + } + return success; + } + + async pollForMessages() { + const url = new URL(`${this.baseChannelUrl}/messages`); + const params = { + include_annotations: 1, + count: -20, + }; + if (this.lastGot) { + params.since_id = this.lastGot; + } + url.search = new URLSearchParams(params); + + let res; + let success = true; + try { + res = await nodeFetch(url); + } catch (e) { + success = false; + } + + const response = await res.json(); + if (response.meta.code !== 200) { + success = false; + } + + if (success) { + let receivedAt = new Date().getTime(); + response.data.forEach(adnMessage => { + // FIXME: create proper message for this message.DataMessage.body + let timestamp = new Date(adnMessage.created_at).getTime(); + let from = adnMessage.user.username; + let source; + if (adnMessage.annotations.length) { + const noteValue = adnMessage.annotations[0].value; + ({ from, timestamp, source } = noteValue); + } + + const messageData = { + friendRequest: false, + source, + sourceDevice: 1, + timestamp, + serverTimestamp: timestamp, + receivedAt, + isPublic: true, + message: { + body: adnMessage.text, + attachments: [], + group: { + id: this.conversationId, + type: textsecure.protobuf.GroupContext.Type.DELIVER, + }, + flags: 0, + expireTimer: 0, + profileKey: null, + timestamp, + received_at: receivedAt, + sent_at: timestamp, + quote: null, + contact: [], + preview: [], + profile: { + displayName: from, + }, + }, + }; + receivedAt += 1; // Ensure different arrival times + + this.serverAPI.chatAPI.emit('publicMessage', { + message: messageData, + }); + this.lastGot = !this.lastGot + ? adnMessage.id + : Math.max(this.lastGot, adnMessage.id); + }); + } + + setTimeout(() => { + this.pollForMessages(); + }, GROUPCHAT_POLL_EVERY); + } +} + +module.exports = LokiPublicChatAPI; diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index 61fb83926..5f29f6c38 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -70,7 +70,10 @@ const sendToProxy = async (options = {}, targetNode) => { // detect SNode is not ready (not in swarm; not done syncing) if (response.status === 503) { const ciphertext = await response.text(); - log.error(`lokiRpc sendToProxy snode ${randSnode.ip}:${randSnode.port} error`, ciphertext); + log.error( + `lokiRpc sendToProxy snode ${randSnode.ip}:${randSnode.port} error`, + ciphertext + ); // mark as bad for this round (should give it some time and improve success rates) lokiSnodeAPI.markRandomNodeUnreachable(randSnode); // retry for a new working snode @@ -104,7 +107,7 @@ const sendToProxy = async (options = {}, targetNode) => { const textDecoder = new TextDecoder(); plaintext = textDecoder.decode(plaintextBuffer); - } catch(e) { + } catch (e) { log.error( 'lokiRpc sendToProxy decode error', e.code, diff --git a/js/modules/loki_snode_api.js b/js/modules/loki_snode_api.js index 7bc2944ed..7d163a215 100644 --- a/js/modules/loki_snode_api.js +++ b/js/modules/loki_snode_api.js @@ -31,7 +31,10 @@ class LokiSnodeAPI { ]; } - async initialiseRandomPool(seedNodes = [...window.seedNodeList], consecutiveErrors = 0) { + async initialiseRandomPool( + seedNodes = [...window.seedNodeList], + consecutiveErrors = 0 + ) { const params = { limit: 20, active_only: true, @@ -71,7 +74,10 @@ class LokiSnodeAPI { if (consecutiveErrors < 3) { // retry after a possible delay setTimeout(() => { - log.info('Retrying initialising random snode pool, try #', consecutiveErrors); + log.info( + 'Retrying initialising random snode pool, try #', + consecutiveErrors + ); this.initialiseRandomPool(seedNodes, consecutiveErrors + 1); }, consecutiveErrors * consecutiveErrors * 5000); } else { @@ -181,7 +187,12 @@ class LokiSnodeAPI { const snodes = result.snodes.filter(tSnode => tSnode.ip !== '0.0.0.0'); return snodes; } catch (e) { - log.error('getSnodesForPubkey error', e.code, e.message, `for ${snode.ip}:${snode.port}`); + log.error( + 'getSnodesForPubkey error', + e.code, + e.message, + `for ${snode.ip}:${snode.port}` + ); this.markRandomNodeUnreachable(snode); return []; } @@ -197,7 +208,9 @@ class LokiSnodeAPI { const resList = await this.getSnodesForPubkey(rSnode, pubKey); // should we only activate entries that are in all results? resList.map(item => { - const hasItem = snodes.some(hItem => item.ip === hItem.ip && item.port === hItem.port); + const hasItem = snodes.some( + hItem => item.ip === hItem.ip && item.port === hItem.port + ); if (!hasItem) { snodes.push(item); } diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 0e6204016..b6b24f355 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -59,7 +59,10 @@ function MessageReceiver(username, password, signalingKey, options = {}) { lokiPublicChatAPI.removeAllListeners('publicMessage'); // we only need one MR in the system handling these // bind events - lokiPublicChatAPI.on('publicMessage', this.handleUnencryptedMessage.bind(this)); + lokiPublicChatAPI.on( + 'publicMessage', + this.handleUnencryptedMessage.bind(this) + ); openGroupBound = true; } } else { diff --git a/ts/components/conversation/CreateGroupDialog.tsx b/ts/components/conversation/CreateGroupDialog.tsx index ce853d4ce..482a35579 100644 --- a/ts/components/conversation/CreateGroupDialog.tsx +++ b/ts/components/conversation/CreateGroupDialog.tsx @@ -95,7 +95,11 @@ export class CreateGroupDialog extends React.Component { ); return ( - null}> + null} + >

{this.state.errorMessage}

From ce876a8024c3526189f80b27dd129241d1ac047d Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 3 Mar 2020 19:03:15 -0800 Subject: [PATCH 094/107] not meant to include this --- js/modules/loki_public_chat_api2.js | 209 ---------------------------- 1 file changed, 209 deletions(-) delete mode 100644 js/modules/loki_public_chat_api2.js diff --git a/js/modules/loki_public_chat_api2.js b/js/modules/loki_public_chat_api2.js deleted file mode 100644 index b2b55c4e2..000000000 --- a/js/modules/loki_public_chat_api2.js +++ /dev/null @@ -1,209 +0,0 @@ -/* global log, textsecure */ -const EventEmitter = require('events'); -const nodeFetch = require('node-fetch'); -const { URL, URLSearchParams } = require('url'); - -const GROUPCHAT_POLL_EVERY = 1000; // 1 second - -// singleton to relay events to libtextsecure/message_receiver -class LokiPublicChatAPI extends EventEmitter { - constructor(ourKey) { - super(); - this.ourKey = ourKey; - this.lastGot = {}; - this.servers = []; - } - findOrCreateServer(hostport) { - let thisServer = null; - log.info(`LokiPublicChatAPI looking for ${hostport}`); - this.servers.some(server => { - // if we already have this hostport registered - if (server.server === hostport) { - thisServer = server; - return true; - } - return false; - }); - if (thisServer === null) { - thisServer = new LokiPublicServerAPI(this, hostport); - this.servers.push(thisServer); - } - return thisServer; - } -} - -class LokiPublicServerAPI { - constructor(chatAPI, hostport) { - this.chatAPI = chatAPI; - this.server = hostport; - this.channels = []; - } - findOrCreateChannel(channelId, conversationId) { - let thisChannel = null; - this.channels.forEach(channel => { - if ( - channel.channelId === channelId && - channel.conversationId === conversationId - ) { - thisChannel = channel; - } - }); - if (thisChannel === null) { - thisChannel = new LokiPublicChannelAPI(this, channelId, conversationId); - this.channels.push(thisChannel); - } - return thisChannel; - } - unregisterChannel(channelId) { - // find it, remove it - // if no channels left, request we deregister server - return channelId || this; // this is just to make eslint happy - } -} - -class LokiPublicChannelAPI { - constructor(serverAPI, channelId, conversationId) { - this.serverAPI = serverAPI; - this.channelId = channelId; - this.baseChannelUrl = `${serverAPI.server}/channels/${this.channelId}`; - this.groupName = 'unknown'; - this.conversationId = conversationId; - this.lastGot = 0; - log.info(`registered LokiPublicChannel ${channelId}`); - // start polling - this.pollForMessages(); - } - - async pollForChannel(source, endpoint) { - // groupName will be loaded from server - const url = new URL(this.baseChannelUrl); - /* - const params = { - include_annotations: 1, - }; - */ - let res; - let success = true; - try { - res = await nodeFetch(url); - } catch (e) { - success = false; - } - - const response = await res.json(); - if (response.meta.code !== 200) { - success = false; - } - // update this.groupId - return endpoint || success; - } - - async pollForDeletions() { - // let id = 0; - // read all messages from 0 to current - // delete local copies if server state has changed to delete - // run every minute - const url = new URL(this.baseChannelUrl); - /* - const params = { - include_annotations: 1, - }; - */ - let res; - let success = true; - try { - res = await nodeFetch(url); - } catch (e) { - success = false; - } - - const response = await res.json(); - if (response.meta.code !== 200) { - success = false; - } - return success; - } - - async pollForMessages() { - const url = new URL(`${this.baseChannelUrl}/messages`); - const params = { - include_annotations: 1, - count: -20, - }; - if (this.lastGot) { - params.since_id = this.lastGot; - } - url.search = new URLSearchParams(params); - - let res; - let success = true; - try { - res = await nodeFetch(url); - } catch (e) { - success = false; - } - - const response = await res.json(); - if (response.meta.code !== 200) { - success = false; - } - - if (success) { - let receivedAt = new Date().getTime(); - response.data.forEach(adnMessage => { - // FIXME: create proper message for this message.DataMessage.body - let timestamp = new Date(adnMessage.created_at).getTime(); - let from = adnMessage.user.username; - let source; - if (adnMessage.annotations.length) { - const noteValue = adnMessage.annotations[0].value; - ({ from, timestamp, source } = noteValue); - } - - const messageData = { - friendRequest: false, - source, - sourceDevice: 1, - timestamp, - serverTimestamp: timestamp, - receivedAt, - isPublic: true, - message: { - body: adnMessage.text, - attachments: [], - group: { - id: this.conversationId, - type: textsecure.protobuf.GroupContext.Type.DELIVER, - }, - flags: 0, - expireTimer: 0, - profileKey: null, - timestamp, - received_at: receivedAt, - sent_at: timestamp, - quote: null, - contact: [], - preview: [], - profile: { - displayName: from, - }, - }, - }; - receivedAt += 1; // Ensure different arrival times - - this.serverAPI.chatAPI.emit('publicMessage', { - message: messageData, - }); - this.lastGot = !this.lastGot - ? adnMessage.id - : Math.max(this.lastGot, adnMessage.id); - }); - } - - setTimeout(() => { - this.pollForMessages(); - }, GROUPCHAT_POLL_EVERY); - } -} - -module.exports = LokiPublicChatAPI; From 7a86a68c22eb134656bd03b7e7a1989891ba5346 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 4 Mar 2020 14:05:05 +1100 Subject: [PATCH 095/107] enable back note to self in search result --- ts/state/ducks/search.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index c92fdd814..194823a79 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -172,7 +172,7 @@ async function queryConversationsAndContacts( providedQuery: string, options: SearchOptions ) { - const { ourNumber, isSecondaryDevice } = options; + const { ourNumber, noteToSelf, isSecondaryDevice } = options; const query = providedQuery.replace(/[+-.()]*/g, ''); const searchResults: Array = await searchConversations( @@ -193,8 +193,8 @@ async function queryConversationsAndContacts( ); // Split into two groups - active conversations and items just from address book - const conversations: Array = []; - const contacts: Array = []; + let conversations: Array = []; + let contacts: Array = []; const max = searchResults.length; for (let i = 0; i < max; i += 1) { const conversation = searchResults[i]; @@ -214,6 +214,14 @@ async function queryConversationsAndContacts( conversations.push(conversation.id); } } + // Inject synthetic Note to Self entry if query matches localized 'Note to Self' + if (noteToSelf.indexOf(providedQuery.toLowerCase()) !== -1) { + // ensure that we don't have duplicates in our results + contacts = contacts.filter(id => id !== ourNumber); + conversations = conversations.filter(id => id !== ourNumber); + + contacts.unshift(ourNumber); + } return { conversations, contacts }; } From 1a9206fdbe9620e941310e1311bd625d25866e3d Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Wed, 4 Mar 2020 16:31:10 +0100 Subject: [PATCH 096/107] Move cleanupOrphanedAttachments before restart --- js/background.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/js/background.js b/js/background.js index 7761fbe94..1cda5ea4d 100644 --- a/js/background.js +++ b/js/background.js @@ -398,6 +398,13 @@ await storage.put('version', currentVersion); if (newVersion) { + + await window.Signal.Data.cleanupOrphanedAttachments(); + + window.log.info( + `New version detected: ${currentVersion}; previous: ${lastVersion}` + ); + if ( lastVersion && window.isBeforeVersion(lastVersion, 'v1.15.0-beta.5') @@ -405,10 +412,6 @@ await window.Signal.Logs.deleteAll(); window.restart(); } - - window.log.info( - `New version detected: ${currentVersion}; previous: ${lastVersion}` - ); } if (isIndexedDBPresent) { @@ -431,10 +434,6 @@ Views.Initialization.setMessage(window.i18n('optimizingApplication')); - if (newVersion) { - await window.Signal.Data.cleanupOrphanedAttachments(); - } - Views.Initialization.setMessage(window.i18n('loading')); idleDetector = new IdleDetector(); From f7d562eafef91dec0b3f9b4609d9fc4aa09f8cf0 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Wed, 4 Mar 2020 17:00:38 +0100 Subject: [PATCH 097/107] lint remove empty line after newVersion if --- js/background.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/background.js b/js/background.js index 1cda5ea4d..3ff691d57 100644 --- a/js/background.js +++ b/js/background.js @@ -398,7 +398,6 @@ await storage.put('version', currentVersion); if (newVersion) { - await window.Signal.Data.cleanupOrphanedAttachments(); window.log.info( From 1ea0edafa9c03c2b32bfc8fd1c1a0e4fdb0d8f50 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Wed, 4 Mar 2020 23:15:25 +0100 Subject: [PATCH 098/107] Remove redundant if statement to avoid future bugs --- js/background.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/js/background.js b/js/background.js index 3ff691d57..c9cb76bc9 100644 --- a/js/background.js +++ b/js/background.js @@ -398,19 +398,15 @@ await storage.put('version', currentVersion); if (newVersion) { - await window.Signal.Data.cleanupOrphanedAttachments(); - window.log.info( `New version detected: ${currentVersion}; previous: ${lastVersion}` ); - if ( - lastVersion && - window.isBeforeVersion(lastVersion, 'v1.15.0-beta.5') - ) { - await window.Signal.Logs.deleteAll(); - window.restart(); - } + await window.Signal.Data.cleanupOrphanedAttachments(); + + await window.Signal.Logs.deleteAll(); + + window.restart(); } if (isIndexedDBPresent) { From 9cc9d61fcd8b95ae0bf7c81afc88510d8237deb5 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Wed, 4 Mar 2020 23:29:24 +0100 Subject: [PATCH 099/107] Remove a redundant restart call --- js/background.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/js/background.js b/js/background.js index c9cb76bc9..62c505222 100644 --- a/js/background.js +++ b/js/background.js @@ -405,8 +405,6 @@ await window.Signal.Data.cleanupOrphanedAttachments(); await window.Signal.Logs.deleteAll(); - - window.restart(); } if (isIndexedDBPresent) { From 1f74803e72e5b8c3971844f400da45a509ccaa84 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 5 Mar 2020 10:46:43 +1100 Subject: [PATCH 100/107] add key to group member list --- ts/components/conversation/UpdateGroupMembersDialog.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/ts/components/conversation/UpdateGroupMembersDialog.tsx b/ts/components/conversation/UpdateGroupMembersDialog.tsx index 8acde7336..f0cf17f47 100644 --- a/ts/components/conversation/UpdateGroupMembersDialog.tsx +++ b/ts/components/conversation/UpdateGroupMembersDialog.tsx @@ -143,6 +143,7 @@ export class UpdateGroupMembersDialog extends React.Component { isSelected={!member.checkmarked} onSelect={this.onMemberClicked} onUnselect={this.onMemberClicked} + key={member.id} /> )); } From 840f280a379bcd17256046fa5680b1d879a26538 Mon Sep 17 00:00:00 2001 From: Vince Date: Thu, 5 Mar 2020 14:17:34 +1100 Subject: [PATCH 101/107] Prepare for v1.0.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ef1bad9b6..fc6ffb706 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "Session", "description": "Private messaging from your desktop", "repository": "https://github.com/loki-project/loki-messenger.git", - "version": "1.0.2", + "version": "1.0.3", "license": "GPL-3.0", "author": { "name": "Loki Project", From 06142c880a2158dd144d039d4666089ddc1430b9 Mon Sep 17 00:00:00 2001 From: Vince Date: Thu, 5 Mar 2020 15:14:56 +1100 Subject: [PATCH 102/107] Revert removal of header search icon Rendering is still disabled. --- .../conversation/ConversationHeader.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index 3b3d33bbb..1aae3bb6a 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -255,6 +255,19 @@ export class ConversationHeader extends React.Component { ); } + public renderSearch() { + return ( +
+ +
+ ); + } + public renderOptions(triggerId: string) { const { showBackButton } = this.props; @@ -385,6 +398,12 @@ export class ConversationHeader extends React.Component {
{this.renderExpirationLength()} + + {!this.props.isRss && ( + <> + {this.renderAvatar()} + + )} {!this.props.isRss && this.renderAvatar()} @@ -400,6 +419,12 @@ export class ConversationHeader extends React.Component { } } + public highlightMessageSearch() { + // This is a temporary fix. In future we want to search + // messages in the current conversation + $('.session-search-input input').focus(); + } + private renderPublicMenuItems() { const { i18n, From 178d788dcaad6e7cdcaa3c18a0cb2cdfc1bdb362 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Sun, 23 Feb 2020 18:30:27 -0800 Subject: [PATCH 103/107] Enable auto-updating using electron-updater --- config/default.json | 4 - config/production.json | 4 +- dev-app-update.yml.sample | 8 +- main.js | 37 +++- package.json | 16 +- ts/test/updater/common_test.ts | 77 ------- ts/test/updater/signature_test.ts | 206 ------------------- ts/updater/common.ts | 249 +---------------------- ts/updater/generateKeyPair.ts | 45 ----- ts/updater/generateSignature.ts | 85 -------- ts/updater/index.ts | 41 +--- ts/updater/macos.ts | 324 ------------------------------ ts/updater/signature.ts | 112 ----------- ts/updater/updater.ts | 77 +++++++ ts/updater/windows.ts | 231 --------------------- yarn.lock | 50 ++++- 16 files changed, 178 insertions(+), 1388 deletions(-) delete mode 100644 ts/test/updater/common_test.ts delete mode 100644 ts/test/updater/signature_test.ts delete mode 100644 ts/updater/generateKeyPair.ts delete mode 100644 ts/updater/generateSignature.ts delete mode 100644 ts/updater/macos.ts delete mode 100644 ts/updater/signature.ts create mode 100644 ts/updater/updater.ts delete mode 100644 ts/updater/windows.ts diff --git a/config/default.json b/config/default.json index e0b61dac0..b08784f25 100644 --- a/config/default.json +++ b/config/default.json @@ -23,10 +23,6 @@ "port": "38157" } ], - "disableAutoUpdate": true, - "updatesUrl": "TODO", - "updatesPublicKey": - "fd7dd3de7149dc0a127909fee7de0f7620ddd0de061b37a2c303e37de802a401", "updatesEnabled": false, "openDevTools": false, "buildExpiration": 0, diff --git a/config/production.json b/config/production.json index 0967ef424..5f2141001 100644 --- a/config/production.json +++ b/config/production.json @@ -1 +1,3 @@ -{} +{ + "updatesEnabled": true +} diff --git a/dev-app-update.yml.sample b/dev-app-update.yml.sample index 04af153ba..3b1ea6914 100644 --- a/dev-app-update.yml.sample +++ b/dev-app-update.yml.sample @@ -1,5 +1,3 @@ -provider: s3 -region: us-east-1 -bucket: your-test-bucket.signal.org -path: desktop -acl: public-read +owner: +repo: +provider: github diff --git a/main.js b/main.js index b739026cf..48ca42d37 100644 --- a/main.js +++ b/main.js @@ -65,9 +65,7 @@ const appInstance = config.util.getEnv('NODE_APP_INSTANCE') || 0; const attachments = require('./app/attachments'); const attachmentChannel = require('./app/attachment_channel'); -// TODO: Enable when needed -// const updater = require('./ts/updater/index'); -const updater = null; +const updater = require('./ts/updater/index'); const createTrayIcon = require('./app/tray_icon'); const ephemeralConfig = require('./app/ephemeral_config'); @@ -410,22 +408,40 @@ ipc.on('show-window', () => { showWindow(); }); -let updatesStarted = false; -ipc.on('ready-for-updates', async () => { - if (updatesStarted || !updater) { +let isReadyForUpdates = false; +async function readyForUpdates() { + if (isReadyForUpdates) { return; } - updatesStarted = true; + isReadyForUpdates = true; + + // disable for now + /* + // First, install requested sticker pack + const incomingUrl = getIncomingUrl(process.argv); + if (incomingUrl) { + handleSgnlLink(incomingUrl); + } + */ + + // Second, start checking for app updates try { await updater.start(getMainWindow, locale.messages, logger); } catch (error) { - logger.error( + const log = logger || console; + log.error( 'Error starting update checks:', error && error.stack ? error.stack : error ); } -}); +} +ipc.once('ready-for-updates', readyForUpdates); + +// Forcefully call readyForUpdates after 10 minutes. +// This ensures we start the updater. +const TEN_MINUTES = 10 * 60 * 1000; +setTimeout(readyForUpdates, TEN_MINUTES); function openReleaseNotes() { shell.openExternal( @@ -842,6 +858,9 @@ async function showMainWindow(sqlKey, passwordAttempt = false) { } setupMenu(); + + // Check updates + readyForUpdates(); } function setupMenu(options) { diff --git a/package.json b/package.json index fc6ffb706..2cbbd2f3a 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,16 @@ "name": "session-messenger-desktop", "productName": "Session", "description": "Private messaging from your desktop", - "repository": "https://github.com/loki-project/loki-messenger.git", "version": "1.0.3", "license": "GPL-3.0", "author": { "name": "Loki Project", "email": "team@loki.network" }, + "repository": { + "type": "git", + "url": "https://github.com/loki-project/session-desktop.git" + }, "main": "main.js", "scripts": { "postinstall": "electron-builder install-app-deps && rimraf node_modules/dtrace-provider", @@ -25,7 +28,6 @@ "build": "electron-builder --config.extraMetadata.environment=$SIGNAL_ENV", "build-release": "cross-env SIGNAL_ENV=production npm run build -- --config.directories.output=release", "make:linux:x64:appimage": "electron-builder build --linux appimage --x64", - "sign-release": "node ts/updater/generateSignature.js", "build-module-protobuf": "pbjs --target static-module --wrap commonjs --out ts/protobuf/compiled.js protos/*.proto && pbts --out ts/protobuf/compiled.d.ts ts/protobuf/compiled.js", "clean-module-protobuf": "rm -f ts/protobuf/compiled.d.ts ts/protobuf/compiled.js", "build-protobuf": "yarn build-module-protobuf", @@ -78,8 +80,9 @@ "dompurify": "^2.0.7", "electron-context-menu": "^0.15.0", "electron-editor-context-menu": "1.1.1", - "electron-is-dev": "0.3.0", + "electron-is-dev": "^1.1.0", "electron-localshortcut": "^3.2.1", + "electron-updater": "^4.2.2", "emoji-datasource": "4.0.0", "emoji-datasource-apple": "4.0.0", "emoji-js": "3.4.0", @@ -139,6 +142,7 @@ "@types/classnames": "2.2.3", "@types/color": "^3.0.0", "@types/config": "0.0.34", + "@types/electron-is-dev": "^1.1.1", "@types/filesize": "3.6.0", "@types/fs-extra": "5.0.5", "@types/google-libphonenumber": "7.4.14", @@ -208,7 +212,8 @@ "build": { "appId": "com.loki-project.messenger-desktop", "afterSign": "build/notarize.js", - "artifactName": "${name}-${os}-${version}.${ext}", + "artifactName": "${name}-${os}-${arch}-${version}.${ext}", + "publish": "github", "mac": { "category": "public.app-category.social-networking", "icon": "build/icons/mac/icon.icns", @@ -318,7 +323,8 @@ "!node_modules/@journeyapps/sqlcipher/deps/*", "!node_modules/@journeyapps/sqlcipher/build/*", "!node_modules/@journeyapps/sqlcipher/lib/binding/node-*", - "!build/*.js" + "!build/*.js", + "!dev-app-update.yml" ] } } diff --git a/ts/test/updater/common_test.ts b/ts/test/updater/common_test.ts deleted file mode 100644 index 349c14fc8..000000000 --- a/ts/test/updater/common_test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { assert } from 'chai'; - -import { getUpdateFileName, getVersion } from '../../updater/common'; - -describe('updater/signatures', () => { - const windows = `version: 1.23.2 -files: - - url: signal-desktop-win-1.23.2.exe - sha512: hhK+cVAb+QOK/Ln0RBcq8Rb1iPcUC0KZeT4NwLB25PMGoPmakY27XE1bXq4QlkASJN1EkYTbKf3oUJtcllziyQ== - size: 92020776 -path: signal-desktop-win-1.23.2.exe -sha512: hhK+cVAb+QOK/Ln0RBcq8Rb1iPcUC0KZeT4NwLB25PMGoPmakY27XE1bXq4QlkASJN1EkYTbKf3oUJtcllziyQ== -releaseDate: '2019-03-29T16:58:08.210Z' -`; - const mac = `version: 1.23.2 -files: - - url: signal-desktop-mac-1.23.2.zip - sha512: f4pPo3WulTVi9zBWGsJPNIlvPOTCxPibPPDmRFDoXMmFm6lqJpXZQ9DSWMJumfc4BRp4y/NTQLGYI6b4WuJwhg== - size: 105179791 - blockMapSize: 111109 -path: signal-desktop-mac-1.23.2.zip -sha512: f4pPo3WulTVi9zBWGsJPNIlvPOTCxPibPPDmRFDoXMmFm6lqJpXZQ9DSWMJumfc4BRp4y/NTQLGYI6b4WuJwhg== -releaseDate: '2019-03-29T16:57:16.997Z' -`; - const windowsBeta = `version: 1.23.2-beta.1 -files: - - url: signal-desktop-beta-win-1.23.2-beta.1.exe - sha512: ZHM1F3y/Y6ulP5NhbFuh7t2ZCpY4lD9BeBhPV+g2B/0p/66kp0MJDeVxTgjR49OakwpMAafA1d6y2QBail4hSQ== - size: 92028656 -path: signal-desktop-beta-win-1.23.2-beta.1.exe -sha512: ZHM1F3y/Y6ulP5NhbFuh7t2ZCpY4lD9BeBhPV+g2B/0p/66kp0MJDeVxTgjR49OakwpMAafA1d6y2QBail4hSQ== -releaseDate: '2019-03-29T01:56:00.544Z' -`; - const macBeta = `version: 1.23.2-beta.1 -files: - - url: signal-desktop-beta-mac-1.23.2-beta.1.zip - sha512: h/01N0DD5Jw2Q6M1n4uLGLTCrMFxcn8QOPtLR3HpABsf3w9b2jFtKb56/2cbuJXP8ol8TkTDWKnRV6mnqnLBDw== - size: 105182398 - blockMapSize: 110894 -path: signal-desktop-beta-mac-1.23.2-beta.1.zip -sha512: h/01N0DD5Jw2Q6M1n4uLGLTCrMFxcn8QOPtLR3HpABsf3w9b2jFtKb56/2cbuJXP8ol8TkTDWKnRV6mnqnLBDw== -releaseDate: '2019-03-29T01:53:23.881Z' -`; - - describe('#getVersion', () => { - it('successfully gets version', () => { - const expected = '1.23.2'; - assert.strictEqual(getVersion(windows), expected); - assert.strictEqual(getVersion(mac), expected); - - const expectedBeta = '1.23.2-beta.1'; - assert.strictEqual(getVersion(windowsBeta), expectedBeta); - assert.strictEqual(getVersion(macBeta), expectedBeta); - }); - }); - - describe('#getUpdateFileName', () => { - it('successfully gets version', () => { - assert.strictEqual( - getUpdateFileName(windows), - 'signal-desktop-win-1.23.2.exe' - ); - assert.strictEqual( - getUpdateFileName(mac), - 'signal-desktop-mac-1.23.2.zip' - ); - assert.strictEqual( - getUpdateFileName(windowsBeta), - 'signal-desktop-beta-win-1.23.2-beta.1.exe' - ); - assert.strictEqual( - getUpdateFileName(macBeta), - 'signal-desktop-beta-mac-1.23.2-beta.1.zip' - ); - }); - }); -}); diff --git a/ts/test/updater/signature_test.ts b/ts/test/updater/signature_test.ts deleted file mode 100644 index 07aff79f6..000000000 --- a/ts/test/updater/signature_test.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { existsSync } from 'fs'; -import { join } from 'path'; - -import { assert } from 'chai'; -import { copy } from 'fs-extra'; - -import { - _getFileHash, - getSignaturePath, - loadHexFromPath, - verifySignature, - writeHexToPath, - writeSignature, -} from '../../updater/signature'; -import { createTempDir, deleteTempDir } from '../../updater/common'; -import { keyPair } from '../../updater/curve'; - -describe('updater/signatures', () => { - it('_getFileHash returns correct hash', async () => { - const filePath = join(__dirname, '../../../fixtures/ghost-kitty.mp4'); - const expected = - '7bc77f27d92d00b4a1d57c480ca86dacc43d57bc318339c92119d1fbf6b557a5'; - - const hash = await _getFileHash(filePath); - - assert.strictEqual(expected, Buffer.from(hash).toString('hex')); - }); - - it('roundtrips binary file writes', async () => { - let tempDir; - - try { - tempDir = await createTempDir(); - - const path = join(tempDir, 'something.bin'); - const { publicKey } = keyPair(); - - await writeHexToPath(path, publicKey); - - const fromDisk = await loadHexFromPath(path); - - assert.strictEqual( - Buffer.from(fromDisk).compare(Buffer.from(publicKey)), - 0 - ); - } finally { - if (tempDir) { - await deleteTempDir(tempDir); - } - } - }); - - it('roundtrips signature', async () => { - let tempDir; - - try { - tempDir = await createTempDir(); - - const version = 'v1.23.2'; - const sourcePath = join(__dirname, '../../../fixtures/ghost-kitty.mp4'); - const updatePath = join(tempDir, 'ghost-kitty.mp4'); - await copy(sourcePath, updatePath); - - const privateKeyPath = join(tempDir, 'private.key'); - const { publicKey, privateKey } = keyPair(); - await writeHexToPath(privateKeyPath, privateKey); - - await writeSignature(updatePath, version, privateKeyPath); - - const signaturePath = getSignaturePath(updatePath); - assert.strictEqual(existsSync(signaturePath), true); - - const verified = await verifySignature(updatePath, version, publicKey); - assert.strictEqual(verified, true); - } finally { - if (tempDir) { - await deleteTempDir(tempDir); - } - } - }); - - it('fails signature verification if version changes', async () => { - let tempDir; - - try { - tempDir = await createTempDir(); - - const version = 'v1.23.2'; - const brokenVersion = 'v1.23.3'; - - const sourcePath = join(__dirname, '../../../fixtures/ghost-kitty.mp4'); - const updatePath = join(tempDir, 'ghost-kitty.mp4'); - await copy(sourcePath, updatePath); - - const privateKeyPath = join(tempDir, 'private.key'); - const { publicKey, privateKey } = keyPair(); - await writeHexToPath(privateKeyPath, privateKey); - - await writeSignature(updatePath, version, privateKeyPath); - - const verified = await verifySignature( - updatePath, - brokenVersion, - publicKey - ); - assert.strictEqual(verified, false); - } finally { - if (tempDir) { - await deleteTempDir(tempDir); - } - } - }); - - it('fails signature verification if signature tampered with', async () => { - let tempDir; - - try { - tempDir = await createTempDir(); - - const version = 'v1.23.2'; - - const sourcePath = join(__dirname, '../../../fixtures/ghost-kitty.mp4'); - const updatePath = join(tempDir, 'ghost-kitty.mp4'); - await copy(sourcePath, updatePath); - - const privateKeyPath = join(tempDir, 'private.key'); - const { publicKey, privateKey } = keyPair(); - await writeHexToPath(privateKeyPath, privateKey); - - await writeSignature(updatePath, version, privateKeyPath); - - const signaturePath = getSignaturePath(updatePath); - const signature = Buffer.from(await loadHexFromPath(signaturePath)); - signature[4] += 3; - await writeHexToPath(signaturePath, signature); - - const verified = await verifySignature(updatePath, version, publicKey); - assert.strictEqual(verified, false); - } finally { - if (tempDir) { - await deleteTempDir(tempDir); - } - } - }); - - it('fails signature verification if binary file tampered with', async () => { - let tempDir; - - try { - tempDir = await createTempDir(); - - const version = 'v1.23.2'; - - const sourcePath = join(__dirname, '../../../fixtures/ghost-kitty.mp4'); - const updatePath = join(tempDir, 'ghost-kitty.mp4'); - await copy(sourcePath, updatePath); - - const privateKeyPath = join(tempDir, 'private.key'); - const { publicKey, privateKey } = keyPair(); - await writeHexToPath(privateKeyPath, privateKey); - - await writeSignature(updatePath, version, privateKeyPath); - - const brokenSourcePath = join( - __dirname, - '../../../fixtures/pixabay-Soap-Bubble-7141.mp4' - ); - await copy(brokenSourcePath, updatePath); - - const verified = await verifySignature(updatePath, version, publicKey); - assert.strictEqual(verified, false); - } finally { - if (tempDir) { - await deleteTempDir(tempDir); - } - } - }); - - it('fails signature verification if signed by different key', async () => { - let tempDir; - - try { - tempDir = await createTempDir(); - - const version = 'v1.23.2'; - - const sourcePath = join(__dirname, '../../../fixtures/ghost-kitty.mp4'); - const updatePath = join(tempDir, 'ghost-kitty.mp4'); - await copy(sourcePath, updatePath); - - const privateKeyPath = join(tempDir, 'private.key'); - const { publicKey } = keyPair(); - const { privateKey } = keyPair(); - await writeHexToPath(privateKeyPath, privateKey); - - await writeSignature(updatePath, version, privateKeyPath); - - const verified = await verifySignature(updatePath, version, publicKey); - assert.strictEqual(verified, false); - } finally { - if (tempDir) { - await deleteTempDir(tempDir); - } - } - }); -}); diff --git a/ts/updater/common.ts b/ts/updater/common.ts index f6d98d661..1bb73decc 100644 --- a/ts/updater/common.ts +++ b/ts/updater/common.ts @@ -1,28 +1,4 @@ -import { - createWriteStream, - statSync, - writeFile as writeFileCallback, -} from 'fs'; -import { join } from 'path'; -import { tmpdir } from 'os'; - -// @ts-ignore -import { createParser } from 'dashdash'; -// @ts-ignore -import ProxyAgent from 'proxy-agent'; -import { FAILSAFE_SCHEMA, safeLoad } from 'js-yaml'; -import { gt } from 'semver'; -import { get as getFromConfig } from 'config'; -import { get, GotOptions, stream } from 'got'; -import { v4 as getGuid } from 'uuid'; -import pify from 'pify'; -import mkdirp from 'mkdirp'; -import rimraf from 'rimraf'; -import { app, BrowserWindow, dialog } from 'electron'; - -// @ts-ignore -import * as packageJson from '../../package.json'; -import { getSignatureFileName } from './signature'; +import { BrowserWindow, dialog } from 'electron'; export type MessagesType = { [key: string]: { @@ -42,90 +18,6 @@ export type LoggerType = { trace: LogFunction; }; -const writeFile = pify(writeFileCallback); -const mkdirpPromise = pify(mkdirp); -const rimrafPromise = pify(rimraf); -const { platform } = process; - -export async function checkForUpdates( - logger: LoggerType -): Promise<{ - fileName: string; - version: string; -} | null> { - const yaml = await getUpdateYaml(); - const version = getVersion(yaml); - - if (!version) { - logger.warn('checkForUpdates: no version extracted from downloaded yaml'); - - return null; - } - - if (isVersionNewer(version)) { - logger.info(`checkForUpdates: found newer version ${version}`); - - return { - fileName: getUpdateFileName(yaml), - version, - }; - } - - logger.info( - `checkForUpdates: ${version} is not newer; no new update available` - ); - - return null; -} - -export async function downloadUpdate( - fileName: string, - logger: LoggerType -): Promise { - const baseUrl = getUpdatesBase(); - const updateFileUrl = `${baseUrl}/${fileName}`; - - const signatureFileName = getSignatureFileName(fileName); - const signatureUrl = `${baseUrl}/${signatureFileName}`; - - let tempDir; - try { - tempDir = await createTempDir(); - const targetUpdatePath = join(tempDir, fileName); - const targetSignaturePath = join(tempDir, getSignatureFileName(fileName)); - - logger.info(`downloadUpdate: Downloading ${signatureUrl}`); - const { body } = await get(signatureUrl, getGotOptions()); - await writeFile(targetSignaturePath, body); - - logger.info(`downloadUpdate: Downloading ${updateFileUrl}`); - const downloadStream = stream(updateFileUrl, getGotOptions()); - const writeStream = createWriteStream(targetUpdatePath); - - await new Promise((resolve, reject) => { - downloadStream.on('error', error => { - reject(error); - }); - downloadStream.on('end', () => { - resolve(); - }); - - writeStream.on('error', error => { - reject(error); - }); - - downloadStream.pipe(writeStream); - }); - - return targetUpdatePath; - } catch (error) { - if (tempDir) { - await deleteTempDir(tempDir); - } - throw error; - } -} - export async function showUpdateDialog( mainWindow: BrowserWindow, messages: MessagesType @@ -179,145 +71,6 @@ export async function showCannotUpdateDialog( }); } -// Helper functions - -export function getUpdateCheckUrl(): string { - return `${getUpdatesBase()}/${getUpdatesFileName()}`; -} - -export function getUpdatesBase(): string { - return getFromConfig('updatesUrl'); -} -export function getCertificateAuthority(): string { - return getFromConfig('certificateAuthority'); -} -export function getProxyUrl(): string | undefined { - return process.env.HTTPS_PROXY || process.env.https_proxy; -} - -export function getUpdatesFileName(): string { - const prefix = isBetaChannel() ? 'beta' : 'latest'; - - if (platform === 'darwin') { - return `${prefix}-mac.yml`; - } else { - return `${prefix}.yml`; - } -} - -const hasBeta = /beta/i; -function isBetaChannel(): boolean { - return hasBeta.test(packageJson.version); -} - -function isVersionNewer(newVersion: string): boolean { - const { version } = packageJson; - - return gt(newVersion, version); -} - -export function getVersion(yaml: string): string | undefined { - const info = parseYaml(yaml); - - if (info && info.version) { - return info.version; - } - - return; -} - -export function getUpdateFileName(yaml: string) { - const info = parseYaml(yaml); - - if (info && info.path) { - return info.path; - } - - return; -} - -function parseYaml(yaml: string): any { - return safeLoad(yaml, { schema: FAILSAFE_SCHEMA, json: true }); -} - -async function getUpdateYaml(): Promise { - const targetUrl = getUpdateCheckUrl(); - const { body } = await get(targetUrl, getGotOptions()); - - if (!body) { - throw new Error('Got unexpected response back from update check'); - } - - return body.toString('utf8'); -} - -function getGotOptions(): GotOptions { - const ca = getCertificateAuthority(); - const proxyUrl = getProxyUrl(); - const agent = proxyUrl ? new ProxyAgent(proxyUrl) : undefined; - - return { - agent, - ca, - headers: { - 'Cache-Control': 'no-cache', - 'User-Agent': 'Session Desktop (+https://getsession.org)', - }, - useElectronNet: false, - }; -} - -function getBaseTempDir() { - // We only use tmpdir() when this code is run outside of an Electron app (as in: tests) - return app ? join(app.getPath('userData'), 'temp') : tmpdir(); -} - -export async function createTempDir() { - const baseTempDir = getBaseTempDir(); - const uniqueName = getGuid(); - const targetDir = join(baseTempDir, uniqueName); - await mkdirpPromise(targetDir); - - return targetDir; -} - -export async function deleteTempDir(targetDir: string) { - const pathInfo = statSync(targetDir); - if (!pathInfo.isDirectory()) { - throw new Error( - `deleteTempDir: Cannot delete path '${targetDir}' because it is not a directory` - ); - } - - const baseTempDir = getBaseTempDir(); - if (!targetDir.startsWith(baseTempDir)) { - throw new Error( - `deleteTempDir: Cannot delete path '${targetDir}' since it is not within base temp dir` - ); - } - - await rimrafPromise(targetDir); -} - export function getPrintableError(error: Error) { return error && error.stack ? error.stack : error; } - -export async function deleteBaseTempDir() { - const baseTempDir = getBaseTempDir(); - await rimrafPromise(baseTempDir); -} - -export function getCliOptions(options: any): T { - const parser = createParser({ options }); - const cliOptions = parser.parse(process.argv); - - if (cliOptions.help) { - const help = parser.help().trimRight(); - // tslint:disable-next-line:no-console - console.log(help); - process.exit(0); - } - - return cliOptions; -} diff --git a/ts/updater/generateKeyPair.ts b/ts/updater/generateKeyPair.ts deleted file mode 100644 index 8a8511e1e..000000000 --- a/ts/updater/generateKeyPair.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { getCliOptions, getPrintableError } from './common'; -import { keyPair } from './curve'; -import { writeHexToPath } from './signature'; - -/* tslint:disable:no-console */ - -const OPTIONS = [ - { - names: ['help', 'h'], - type: 'bool', - help: 'Print this help and exit.', - }, - { - names: ['key', 'k'], - type: 'string', - help: 'Path where public key will go', - default: 'public.key', - }, - { - names: ['private', 'p'], - type: 'string', - help: 'Path where private key will go', - default: 'private.key', - }, -]; - -type OptionsType = { - key: string; - private: string; -}; - -const cliOptions = getCliOptions(OPTIONS); -go(cliOptions).catch(error => { - console.error('Something went wrong!', getPrintableError(error)); -}); - -async function go(options: OptionsType) { - const { key: publicKeyPath, private: privateKeyPath } = options; - const { publicKey, privateKey } = keyPair(); - - await Promise.all([ - writeHexToPath(publicKeyPath, publicKey), - writeHexToPath(privateKeyPath, privateKey), - ]); -} diff --git a/ts/updater/generateSignature.ts b/ts/updater/generateSignature.ts deleted file mode 100644 index 68447f32f..000000000 --- a/ts/updater/generateSignature.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { join, resolve } from 'path'; -import { readdir as readdirCallback } from 'fs'; - -import pify from 'pify'; - -import { getCliOptions, getPrintableError } from './common'; -import { writeSignature } from './signature'; - -// @ts-ignore -import * as packageJson from '../../package.json'; - -const readdir = pify(readdirCallback); - -/* tslint:disable:no-console */ - -const OPTIONS = [ - { - names: ['help', 'h'], - type: 'bool', - help: 'Print this help and exit.', - }, - { - names: ['private', 'p'], - type: 'string', - help: 'Path to private key file (default: ./private.key)', - default: 'private.key', - }, - { - names: ['update', 'u'], - type: 'string', - help: 'Path to the update package (default: the .exe or .zip in ./release)', - }, - { - names: ['version', 'v'], - type: 'string', - help: `Version number of this package (default: ${packageJson.version})`, - default: packageJson.version, - }, -]; - -type OptionsType = { - private: string; - update: string; - version: string; -}; - -const cliOptions = getCliOptions(OPTIONS); -go(cliOptions).catch(error => { - console.error('Something went wrong!', getPrintableError(error)); -}); - -async function go(options: OptionsType) { - const { private: privateKeyPath, version } = options; - let { update: updatePath } = options; - - if (!updatePath) { - updatePath = await findUpdatePath(); - } - - console.log('Signing with...'); - console.log(` version: ${version}`); - console.log(` update file: ${updatePath}`); - console.log(` private key file: ${privateKeyPath}`); - - await writeSignature(updatePath, version, privateKeyPath); -} - -const IS_EXE = /\.exe$/; -const IS_ZIP = /\.zip$/; -async function findUpdatePath(): Promise { - const releaseDir = resolve('release'); - const files: Array = await readdir(releaseDir); - - const max = files.length; - for (let i = 0; i < max; i += 1) { - const file = files[i]; - const fullPath = join(releaseDir, file); - - if (IS_EXE.test(file) || IS_ZIP.test(file)) { - return fullPath; - } - } - - throw new Error("No suitable file found in 'release' folder!"); -} diff --git a/ts/updater/index.ts b/ts/updater/index.ts index 04b6bb52c..dc1ec1310 100644 --- a/ts/updater/index.ts +++ b/ts/updater/index.ts @@ -1,14 +1,7 @@ import { get as getFromConfig } from 'config'; import { BrowserWindow } from 'electron'; - -import { start as startMacOS } from './macos'; -import { start as startWindows } from './windows'; -import { - deleteBaseTempDir, - getPrintableError, - LoggerType, - MessagesType, -} from './common'; +import { start as startUpdater } from './updater'; +import { LoggerType, MessagesType } from './common'; let initialized = false; @@ -17,8 +10,6 @@ export async function start( messages?: MessagesType, logger?: LoggerType ) { - const { platform } = process; - if (initialized) { throw new Error('updater/start: Updates have already been initialized!'); } @@ -32,6 +23,13 @@ export async function start( } if (autoUpdateDisabled()) { + /* + If you really want to enable auto-updating in dev mode + You need to create a dev-app-update.yml file. + A sample can be found in dev-app-update.yml.sample. + After that you can change `updatesEnabled` to `true` in the default config. + */ + logger.info( 'updater/start: Updates disabled - not starting new version checks' ); @@ -39,28 +37,11 @@ export async function start( return; } - try { - await deleteBaseTempDir(); - } catch (error) { - logger.error( - 'updater/start: Error deleting temp dir:', - getPrintableError(error) - ); - } - - if (platform === 'win32') { - await startWindows(getMainWindow, messages, logger); - } else if (platform === 'darwin') { - await startMacOS(getMainWindow, messages, logger); - } else { - throw new Error('updater/start: Unsupported platform'); - } + await startUpdater(getMainWindow, messages, logger); } function autoUpdateDisabled() { return ( - process.platform === 'linux' || - process.mas || - !getFromConfig('updatesEnabled') + process.mas || !getFromConfig('updatesEnabled') // From Electron: Mac App Store build ); } diff --git a/ts/updater/macos.ts b/ts/updater/macos.ts deleted file mode 100644 index 0ede2fe63..000000000 --- a/ts/updater/macos.ts +++ /dev/null @@ -1,324 +0,0 @@ -import { createReadStream, statSync } from 'fs'; -import { createServer, IncomingMessage, Server, ServerResponse } from 'http'; -import { AddressInfo } from 'net'; -import { dirname } from 'path'; - -import { v4 as getGuid } from 'uuid'; -import { app, autoUpdater, BrowserWindow, dialog } from 'electron'; -import { get as getFromConfig } from 'config'; -import { gt } from 'semver'; - -import { - checkForUpdates, - deleteTempDir, - downloadUpdate, - getPrintableError, - LoggerType, - MessagesType, - showCannotUpdateDialog, - showUpdateDialog, -} from './common'; -import { hexToBinary, verifySignature } from './signature'; -import { markShouldQuit } from '../../app/window_state'; - -let isChecking = false; -const SECOND = 1000; -const MINUTE = SECOND * 60; -const INTERVAL = MINUTE * 30; - -export async function start( - getMainWindow: () => BrowserWindow, - messages: MessagesType, - logger: LoggerType -) { - logger.info('macos/start: starting checks...'); - - loggerForQuitHandler = logger; - app.once('quit', quitHandler); - - setInterval(async () => { - try { - await checkDownloadAndInstall(getMainWindow, messages, logger); - } catch (error) { - logger.error('macos/start: error:', getPrintableError(error)); - } - }, INTERVAL); - - await checkDownloadAndInstall(getMainWindow, messages, logger); -} - -let fileName: string; -let version: string; -let updateFilePath: string; -let loggerForQuitHandler: LoggerType; - -async function checkDownloadAndInstall( - getMainWindow: () => BrowserWindow, - messages: MessagesType, - logger: LoggerType -) { - if (isChecking) { - return; - } - - logger.info('checkDownloadAndInstall: checking for update...'); - try { - isChecking = true; - - const result = await checkForUpdates(logger); - if (!result) { - return; - } - - const { fileName: newFileName, version: newVersion } = result; - if (fileName !== newFileName || !version || gt(newVersion, version)) { - deleteCache(updateFilePath, logger); - fileName = newFileName; - version = newVersion; - updateFilePath = await downloadUpdate(fileName, logger); - } - - const publicKey = hexToBinary(getFromConfig('updatesPublicKey')); - const verified = verifySignature(updateFilePath, version, publicKey); - if (!verified) { - // Note: We don't delete the cache here, because we don't want to continually - // re-download the broken release. We will download it only once per launch. - throw new Error( - `checkDownloadAndInstall: Downloaded update did not pass signature verification (version: '${version}'; fileName: '${fileName}')` - ); - } - - try { - await handToAutoUpdate(updateFilePath, logger); - } catch (error) { - const readOnly = 'Cannot update while running on a read-only volume'; - const message: string = error.message || ''; - if (message.includes(readOnly)) { - logger.info('checkDownloadAndInstall: showing read-only dialog...'); - await showReadOnlyDialog(getMainWindow(), messages); - } else { - logger.info( - 'checkDownloadAndInstall: showing general update failure dialog...' - ); - await showCannotUpdateDialog(getMainWindow(), messages); - } - - throw error; - } - - // At this point, closing the app will cause the update to be installed automatically - // because Squirrel has cached the update file and will do the right thing. - - logger.info('checkDownloadAndInstall: showing update dialog...'); - const shouldUpdate = await showUpdateDialog(getMainWindow(), messages); - if (!shouldUpdate) { - return; - } - - logger.info('checkDownloadAndInstall: calling quitAndInstall...'); - markShouldQuit(); - autoUpdater.quitAndInstall(); - } catch (error) { - logger.error('checkDownloadAndInstall: error', getPrintableError(error)); - } finally { - isChecking = false; - } -} - -function quitHandler() { - deleteCache(updateFilePath, loggerForQuitHandler); -} - -// Helpers - -function deleteCache(filePath: string | null, logger: LoggerType) { - if (filePath) { - const tempDir = dirname(filePath); - deleteTempDir(tempDir).catch(error => { - logger.error( - 'quitHandler: error deleting temporary directory:', - getPrintableError(error) - ); - }); - } -} - -async function handToAutoUpdate( - filePath: string, - logger: LoggerType -): Promise { - return new Promise((resolve, reject) => { - const updateFileUrl = generateFileUrl(); - const server = createServer(); - let serverUrl: string; - - server.on('error', (error: Error) => { - logger.error( - 'handToAutoUpdate: server had error', - getPrintableError(error) - ); - shutdown(server, logger); - reject(error); - }); - - server.on( - 'request', - (request: IncomingMessage, response: ServerResponse) => { - const { url } = request; - - if (url === '/') { - const absoluteUrl = `${serverUrl}${updateFileUrl}`; - writeJSONResponse(absoluteUrl, response); - - return; - } - - if (!url || !url.startsWith(updateFileUrl)) { - write404(url, response, logger); - - return; - } - - pipeUpdateToSquirrel(filePath, server, response, logger, reject); - } - ); - - server.listen(0, '127.0.0.1', () => { - serverUrl = getServerUrl(server); - - autoUpdater.on('error', (error: Error) => { - logger.error('autoUpdater: error', getPrintableError(error)); - reject(error); - }); - autoUpdater.on('update-downloaded', () => { - logger.info('autoUpdater: update-downloaded event fired'); - shutdown(server, logger); - resolve(); - }); - - autoUpdater.setFeedURL({ - url: serverUrl, - headers: { 'Cache-Control': 'no-cache' }, - }); - autoUpdater.checkForUpdates(); - }); - }); -} - -function pipeUpdateToSquirrel( - filePath: string, - server: Server, - response: ServerResponse, - logger: LoggerType, - reject: (error: Error) => void -) { - const updateFileSize = getFileSize(filePath); - const readStream = createReadStream(filePath); - - response.on('error', (error: Error) => { - logger.error( - 'pipeUpdateToSquirrel: update file download request had an error', - getPrintableError(error) - ); - shutdown(server, logger); - reject(error); - }); - - readStream.on('error', (error: Error) => { - logger.error( - 'pipeUpdateToSquirrel: read stream error response:', - getPrintableError(error) - ); - shutdown(server, logger, response); - reject(error); - }); - - response.writeHead(200, { - 'Content-Type': 'application/zip', - 'Content-Length': updateFileSize, - }); - - readStream.pipe(response); -} - -function writeJSONResponse(url: string, response: ServerResponse) { - const data = Buffer.from( - JSON.stringify({ - url, - }) - ); - response.writeHead(200, { - 'Content-Type': 'application/json', - 'Content-Length': data.byteLength, - }); - response.end(data); -} - -function write404( - url: string | undefined, - response: ServerResponse, - logger: LoggerType -) { - logger.error(`write404: Squirrel requested unexpected url '${url}'`); - response.writeHead(404); - response.end(); -} - -function getServerUrl(server: Server) { - const address = server.address() as AddressInfo; - - // tslint:disable-next-line:no-http-string - return `http://127.0.0.1:${address.port}`; -} -function generateFileUrl(): string { - return `/${getGuid()}.zip`; -} - -function getFileSize(targetPath: string): number { - const { size } = statSync(targetPath); - - return size; -} - -function shutdown( - server: Server, - logger: LoggerType, - response?: ServerResponse -) { - try { - if (server) { - server.close(); - } - } catch (error) { - logger.error('shutdown: Error closing server', getPrintableError(error)); - } - - try { - if (response) { - response.end(); - } - } catch (endError) { - logger.error( - "shutdown: couldn't end response", - getPrintableError(endError) - ); - } -} - -export async function showReadOnlyDialog( - mainWindow: BrowserWindow, - messages: MessagesType -): Promise { - const options = { - type: 'warning', - buttons: [messages.ok.message], - title: messages.cannotUpdate.message, - message: messages.readOnlyVolume.message, - }; - - return new Promise(resolve => { - dialog.showMessageBox(mainWindow, options, () => { - resolve(); - }); - }); -} diff --git a/ts/updater/signature.ts b/ts/updater/signature.ts deleted file mode 100644 index 9bac75673..000000000 --- a/ts/updater/signature.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { createHash } from 'crypto'; -import { - createReadStream, - readFile as readFileCallback, - writeFile as writeFileCallback, -} from 'fs'; -import { basename, dirname, join, resolve as resolvePath } from 'path'; - -import pify from 'pify'; - -import { BinaryType, sign, verify } from './curve'; - -const readFile = pify(readFileCallback); -const writeFile = pify(writeFileCallback); - -export async function generateSignature( - updatePackagePath: string, - version: string, - privateKeyPath: string -) { - const privateKey = await loadHexFromPath(privateKeyPath); - const message = await generateMessage(updatePackagePath, version); - - return sign(privateKey, message); -} - -export async function verifySignature( - updatePackagePath: string, - version: string, - publicKey: BinaryType -): Promise { - const signaturePath = getSignaturePath(updatePackagePath); - const signature = await loadHexFromPath(signaturePath); - const message = await generateMessage(updatePackagePath, version); - - return verify(publicKey, message, signature); -} - -// Helper methods - -async function generateMessage( - updatePackagePath: string, - version: string -): Promise { - const hash = await _getFileHash(updatePackagePath); - const messageString = `${Buffer.from(hash).toString('hex')}-${version}`; - - return Buffer.from(messageString); -} - -export async function writeSignature( - updatePackagePath: string, - version: string, - privateKeyPath: string -) { - const signaturePath = getSignaturePath(updatePackagePath); - const signature = await generateSignature( - updatePackagePath, - version, - privateKeyPath - ); - await writeHexToPath(signaturePath, signature); -} - -export async function _getFileHash( - updatePackagePath: string -): Promise { - const hash = createHash('sha256'); - const stream = createReadStream(updatePackagePath); - - return new Promise((resolve, reject) => { - stream.on('data', data => { - hash.update(data); - }); - stream.on('close', () => { - resolve(hash.digest()); - }); - stream.on('error', error => { - reject(error); - }); - }); -} - -export function getSignatureFileName(fileName: string) { - return `${fileName}.sig`; -} - -export function getSignaturePath(updatePackagePath: string): string { - const updateFullPath = resolvePath(updatePackagePath); - const updateDir = dirname(updateFullPath); - const updateFileName = basename(updateFullPath); - - return join(updateDir, getSignatureFileName(updateFileName)); -} - -export function hexToBinary(target: string): BinaryType { - return Buffer.from(target, 'hex'); -} - -export function binaryToHex(data: BinaryType): string { - return Buffer.from(data).toString('hex'); -} - -export async function loadHexFromPath(target: string): Promise { - const hexString = await readFile(target, 'utf8'); - - return hexToBinary(hexString); -} - -export async function writeHexToPath(target: string, data: BinaryType) { - await writeFile(target, binaryToHex(data)); -} diff --git a/ts/updater/updater.ts b/ts/updater/updater.ts new file mode 100644 index 000000000..408ce2f1a --- /dev/null +++ b/ts/updater/updater.ts @@ -0,0 +1,77 @@ +import { autoUpdater } from 'electron-updater'; +import { BrowserWindow } from 'electron'; +import { markShouldQuit } from '../../app/window_state'; +import { + getPrintableError, + LoggerType, + MessagesType, + showCannotUpdateDialog, + showUpdateDialog, +} from './common'; + +let isUpdating = false; + +const SECOND = 1000; +const MINUTE = SECOND * 60; +const INTERVAL = MINUTE * 30; + +export async function start( + getMainWindow: () => BrowserWindow, + messages: MessagesType, + logger: LoggerType +) { + logger.info('auto-update: starting checks...'); + + autoUpdater.logger = logger; + + setInterval(async () => { + try { + await checkForUpdates(getMainWindow, messages, logger); + } catch (error) { + logger.error('auto-update: error:', getPrintableError(error)); + } + }, INTERVAL); + + await checkForUpdates(getMainWindow, messages, logger); +} + +async function checkForUpdates( + getMainWindow: () => BrowserWindow, + messages: MessagesType, + logger: LoggerType +) { + if (isUpdating) { + return; + } + + logger.info('auto-update: checking for update...'); + + try { + // Get the update using electron-updater + try { + const info = await autoUpdater.checkForUpdates(); + if (!info.downloadPromise) { + logger.info('auto-update: no update to download'); + + return; + } + await info.downloadPromise; + } catch (error) { + await showCannotUpdateDialog(getMainWindow(), messages); + throw error; + } + + // Update downloaded successfully, we should ask the user to update + logger.info('auto-update: showing update dialog...'); + const shouldUpdate = await showUpdateDialog(getMainWindow(), messages); + if (!shouldUpdate) { + return; + } + + logger.info('auto-update: calling quitAndInstall...'); + markShouldQuit(); + autoUpdater.quitAndInstall(); + } finally { + isUpdating = false; + } +} diff --git a/ts/updater/windows.ts b/ts/updater/windows.ts deleted file mode 100644 index 1035cf040..000000000 --- a/ts/updater/windows.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { dirname, join } from 'path'; -import { spawn as spawnEmitter, SpawnOptions } from 'child_process'; -import { readdir as readdirCallback, unlink as unlinkCallback } from 'fs'; - -import { app, BrowserWindow } from 'electron'; -import { get as getFromConfig } from 'config'; -import { gt } from 'semver'; -import pify from 'pify'; - -import { - checkForUpdates, - deleteTempDir, - downloadUpdate, - getPrintableError, - LoggerType, - MessagesType, - showCannotUpdateDialog, - showUpdateDialog, -} from './common'; -import { hexToBinary, verifySignature } from './signature'; -import { markShouldQuit } from '../../app/window_state'; - -const readdir = pify(readdirCallback); -const unlink = pify(unlinkCallback); - -let isChecking = false; -const SECOND = 1000; -const MINUTE = SECOND * 60; -const INTERVAL = MINUTE * 30; - -export async function start( - getMainWindow: () => BrowserWindow, - messages: MessagesType, - logger: LoggerType -) { - logger.info('windows/start: starting checks...'); - - loggerForQuitHandler = logger; - app.once('quit', quitHandler); - - setInterval(async () => { - try { - await checkDownloadAndInstall(getMainWindow, messages, logger); - } catch (error) { - logger.error('windows/start: error:', getPrintableError(error)); - } - }, INTERVAL); - - await deletePreviousInstallers(logger); - await checkDownloadAndInstall(getMainWindow, messages, logger); -} - -let fileName: string; -let version: string; -let updateFilePath: string; -let installing: boolean; -let loggerForQuitHandler: LoggerType; - -async function checkDownloadAndInstall( - getMainWindow: () => BrowserWindow, - messages: MessagesType, - logger: LoggerType -) { - if (isChecking) { - return; - } - - try { - isChecking = true; - - logger.info('checkDownloadAndInstall: checking for update...'); - const result = await checkForUpdates(logger); - if (!result) { - return; - } - - const { fileName: newFileName, version: newVersion } = result; - if (fileName !== newFileName || !version || gt(newVersion, version)) { - deleteCache(updateFilePath, logger); - fileName = newFileName; - version = newVersion; - updateFilePath = await downloadUpdate(fileName, logger); - } - - const publicKey = hexToBinary(getFromConfig('updatesPublicKey')); - const verified = verifySignature(updateFilePath, version, publicKey); - if (!verified) { - // Note: We don't delete the cache here, because we don't want to continually - // re-download the broken release. We will download it only once per launch. - throw new Error( - `Downloaded update did not pass signature verification (version: '${version}'; fileName: '${fileName}')` - ); - } - - logger.info('checkDownloadAndInstall: showing dialog...'); - const shouldUpdate = await showUpdateDialog(getMainWindow(), messages); - if (!shouldUpdate) { - return; - } - - try { - await verifyAndInstall(updateFilePath, version, logger); - installing = true; - } catch (error) { - logger.info( - 'checkDownloadAndInstall: showing general update failure dialog...' - ); - await showCannotUpdateDialog(getMainWindow(), messages); - - throw error; - } - - markShouldQuit(); - app.quit(); - } catch (error) { - logger.error('checkDownloadAndInstall: error', getPrintableError(error)); - } finally { - isChecking = false; - } -} - -function quitHandler() { - if (updateFilePath && !installing) { - verifyAndInstall(updateFilePath, version, loggerForQuitHandler).catch( - error => { - loggerForQuitHandler.error( - 'quitHandler: error installing:', - getPrintableError(error) - ); - } - ); - } -} - -// Helpers - -// This is fixed by out new install mechanisms... -// https://github.com/signalapp/Signal-Desktop/issues/2369 -// ...but we should also clean up those old installers. -const IS_EXE = /\.exe$/i; -async function deletePreviousInstallers(logger: LoggerType) { - const userDataPath = app.getPath('userData'); - const files: Array = await readdir(userDataPath); - await Promise.all( - files.map(async file => { - const isExe = IS_EXE.test(file); - if (!isExe) { - return; - } - - const fullPath = join(userDataPath, file); - try { - await unlink(fullPath); - } catch (error) { - logger.error(`deletePreviousInstallers: couldn't delete file ${file}`); - } - }) - ); -} - -async function verifyAndInstall( - filePath: string, - newVersion: string, - logger: LoggerType -) { - const publicKey = hexToBinary(getFromConfig('updatesPublicKey')); - const verified = verifySignature(updateFilePath, newVersion, publicKey); - if (!verified) { - throw new Error( - `Downloaded update did not pass signature verification (version: '${newVersion}'; fileName: '${fileName}')` - ); - } - - await install(filePath, logger); -} - -async function install(filePath: string, logger: LoggerType): Promise { - logger.info('windows/install: installing package...'); - const args = ['--updated']; - const options = { - detached: true, - stdio: 'ignore' as 'ignore', // TypeScript considers this a plain string without help - }; - - try { - await spawn(filePath, args, options); - } catch (error) { - if (error.code === 'UNKNOWN' || error.code === 'EACCES') { - logger.warn( - 'windows/install: Error running installer; Trying again with elevate.exe' - ); - await spawn(getElevatePath(), [filePath, ...args], options); - - return; - } - - throw error; - } -} - -function deleteCache(filePath: string | null, logger: LoggerType) { - if (filePath) { - const tempDir = dirname(filePath); - deleteTempDir(tempDir).catch(error => { - logger.error( - 'deleteCache: error deleting temporary directory', - getPrintableError(error) - ); - }); - } -} -function getElevatePath() { - const installPath = app.getAppPath(); - - return join(installPath, 'resources', 'elevate.exe'); -} - -async function spawn( - exe: string, - args: Array, - options: SpawnOptions -): Promise { - return new Promise((resolve, reject) => { - const emitter = spawnEmitter(exe, args, options); - emitter.on('error', reject); - emitter.unref(); - - // tslint:disable-next-line no-string-based-set-timeout - setTimeout(resolve, 200); - }); -} diff --git a/yarn.lock b/yarn.lock index b7a8a3a0e..e1c5b8691 100644 --- a/yarn.lock +++ b/yarn.lock @@ -191,6 +191,13 @@ dependencies: "@types/trusted-types" "*" +"@types/electron-is-dev@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/electron-is-dev/-/electron-is-dev-1.1.1.tgz#b48cb249b4615915b16477891160414b57b9a8c5" + integrity sha512-axJ7z6N/FfXHf0Q6MO75Sl7gXCqAeIJMxxYd8n80FNmGev8GPHMcva31zQQX+i4B7aBUzdyVY1UfQeFxph3xVQ== + dependencies: + electron-is-dev "*" + "@types/events@*": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" @@ -392,6 +399,13 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ== +"@types/semver@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.1.0.tgz#c8c630d4c18cd326beff77404887596f96408408" + integrity sha512-pOKLaubrAEMUItGNpgwl0HMFPrSAFic8oSVIvfu1UwcgGNmNyK9gyhBHKmBnUTwwVvpZfkzUC0GaMgnL6P86uA== + dependencies: + "@types/node" "*" + "@types/sinon@4.3.1": version "4.3.1" resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-4.3.1.tgz#32458f9b166cd44c23844eee4937814276f35199" @@ -3039,12 +3053,7 @@ electron-is-accelerator@^0.1.0: resolved "https://registry.yarnpkg.com/electron-is-accelerator/-/electron-is-accelerator-0.1.2.tgz#509e510c26a56b55e17f863a4b04e111846ab27b" integrity sha1-UJ5RDCala1Xhf4Y6SwThEYRqsns= -electron-is-dev@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-0.3.0.tgz#14e6fda5c68e9e4ecbeff9ccf037cbd7c05c5afe" - integrity sha1-FOb9pcaOnk7L7/nM8DfL18BcWv4= - -electron-is-dev@^1.0.1: +electron-is-dev@*, electron-is-dev@^1.0.1, electron-is-dev@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-1.1.0.tgz#b15a2a600bdc48a51a857d460e05f15b19a2522c" integrity sha512-Z1qA/1oHNowGtSBIcWk0pcLEqYT/j+13xUw/MYOrBUOL4X7VN0i0KCTf5SqyvMPmW5pSPKbo28wkxMxzZ20YnQ== @@ -3099,6 +3108,20 @@ electron-to-chromium@^1.2.7: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.334.tgz#0588359f4ac5c4185ebacdf5fc7e1937e2c99872" integrity sha512-RcjJhpsVaX0X6ntu/WSBlW9HE9pnCgXS9B8mTUObl1aDxaiOa0Lu+NMveIS5IDC+VELzhM32rFJDCC+AApVwcA== +electron-updater@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-4.2.2.tgz#57e106bffad16f71b1ffa3968a52a1b71c8147e6" + integrity sha512-e/OZhr5tLW0GcgmpR5wD0ImxgKMa8pPoNWRcwRyMzTL9pGej7+ORp0t9DtI5ZBHUbObIoEbrk+6EDGUGtJf+aA== + dependencies: + "@types/semver" "^7.1.0" + builder-util-runtime "8.6.0" + fs-extra "^8.1.0" + js-yaml "^3.13.1" + lazy-val "^1.0.4" + lodash.isequal "^4.5.0" + pako "^1.0.11" + semver "^7.1.3" + electron@4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/electron/-/electron-4.1.2.tgz#dc8be0f219c73d60a97675d6d3c5b040c4f50513" @@ -6080,6 +6103,11 @@ lodash.isempty@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + lodash.isfunction@^3.0.8: version "3.0.9" resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" @@ -7334,6 +7362,11 @@ package-json@^6.3.0: registry-url "^5.0.0" semver "^6.2.0" +pako@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + pako@~1.0.5: version "1.0.10" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732" @@ -9292,6 +9325,11 @@ semver@^7.1.1: resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.2.tgz#847bae5bce68c5d08889824f02667199b70e3d87" integrity sha512-BJs9T/H8sEVHbeigqzIEo57Iu/3DG6c4QoqTfbQB3BPA4zgzAomh/Fk9E7QtjWQ8mx2dgA9YCfSF4y9k9bHNpQ== +semver@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.3.tgz#e4345ce73071c53f336445cfc19efb1c311df2a6" + integrity sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA== + semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" From 8c071b2f11cd690a31caf5dcc503e640b63f03fe Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 5 Mar 2020 12:49:26 +1100 Subject: [PATCH 104/107] Fix auto updating on all platforms. Added instructions for release. --- RELEASING.md | 24 ++++++++++++++++++++++++ package.json | 5 +++-- ts/updater/updater.ts | 42 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 RELEASING.md diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 000000000..e8aed4bc6 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,24 @@ +# Releasing + +Creating a new Session Desktop release is very simple. + +1. Bump up the version in `package.json`. +2. Merge all changes required into the `master` branch. + * This will trigger github actions to start building a draft release +3. After github actions has finished building. Go to Release page in the repository. +4. Click on the draft release and change the tag target to `master`. +5. Add in release notes. +6. Generate gpg signatures. +7. Click publish release. + +## Notes + +Artifacts attached in the release shouldn't be deleted! These include the yml files (latest, latest-mac, latest-linux). These are all necessary to get auto updating to work correctly. + +### Mac + +Mac currently uses 2 formats `dmg` and `zip`. +We need the `zip` format for auto updating to work correctly. +We also need the `dmg` because on MacOS Catalina, there is a system bug where extracting the artifact `zip` using the default _Archive Utility_ will make it so the extracted application is invalid and it will fail to open. A work around for this is to extract the `zip` using an alternate program such as _The Unarchiver_. + +Once this bug is fixed we can go back to using the `zip` format by itself. diff --git a/package.json b/package.json index 2cbbd2f3a..e727cc465 100644 --- a/package.json +++ b/package.json @@ -213,12 +213,12 @@ "appId": "com.loki-project.messenger-desktop", "afterSign": "build/notarize.js", "artifactName": "${name}-${os}-${arch}-${version}.${ext}", - "publish": "github", "mac": { "category": "public.app-category.social-networking", "icon": "build/icons/mac/icon.icns", "target": [ - "dmg" + "dmg", + "zip" ], "bundleVersion": "1", "hardenedRuntime": true, @@ -232,6 +232,7 @@ "win": { "asarUnpack": "node_modules/spellchecker/vendor/hunspell_dictionaries", "publisherName": "Loki Project", + "verifyUpdateCodeSignature": false, "icon": "build/icons/win/icon.ico", "target": [ "nsis" diff --git a/ts/updater/updater.ts b/ts/updater/updater.ts index 408ce2f1a..cd2547d31 100644 --- a/ts/updater/updater.ts +++ b/ts/updater/updater.ts @@ -1,5 +1,7 @@ +import * as path from 'path'; +import * as fs from 'fs-extra'; import { autoUpdater } from 'electron-updater'; -import { BrowserWindow } from 'electron'; +import { app, BrowserWindow } from 'electron'; import { markShouldQuit } from '../../app/window_state'; import { getPrintableError, @@ -44,6 +46,13 @@ async function checkForUpdates( return; } + const canUpdate = await canAutoUpdate(); + if (!canUpdate) { + return; + } + + isUpdating = true; + logger.info('auto-update: checking for update...'); try { @@ -75,3 +84,34 @@ async function checkForUpdates( isUpdating = false; } } + +/* + Check if we have the required files to auto update. + These files won't exist inside certain formats such as a linux deb file. +*/ +async function canAutoUpdate(): Promise { + const isPackaged = app.isPackaged; + + // On a production app, we need to use resources path to check for the file + if (isPackaged && !process.resourcesPath) { + return false; + } + + // Taken from: https://github.com/electron-userland/electron-builder/blob/d4feb6d3c8b008f8b455c761d654c8088f90d8fa/packages/electron-updater/src/ElectronAppAdapter.ts#L25 + const updateFile = isPackaged ? 'app-update.yml' : 'dev-app-update.yml'; + const basePath = + isPackaged && process.resourcesPath + ? process.resourcesPath + : app.getAppPath(); + const appUpdateConfigPath = path.join(basePath, updateFile); + + return new Promise(resolve => { + try { + // tslint:disable-next-line: non-literal-fs-path + const exists = fs.existsSync(appUpdateConfigPath); + resolve(exists); + } catch (e) { + resolve(false); + } + }); +} From 4eef73c0a286c65dc32674bec1abe2d75c202a2a Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 5 Mar 2020 13:43:13 +1100 Subject: [PATCH 105/107] Converted updating into a 2 step process. First it will ask the user about downloading the new update. Then it will download the update and ask the user to restart the application. This was done so that if the company was forced to put out comprimised binaries, all our users won't be automatically updated to them. --- _locales/en/messages.json | 11 ++++++++- ts/updater/common.ts | 44 ++++++++++++++++++++++++--------- ts/updater/updater.ts | 51 +++++++++++++++++++++++++++++++++------ 3 files changed, 85 insertions(+), 21 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index eae029a62..f409716c3 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1965,7 +1965,7 @@ "message": "There is a new version of Session available." }, "autoUpdateNewVersionInstructions": { - "message": "Press Restart Session to apply the updates." + "message": "Would you like to download the update?" }, "autoUpdateRestartButtonLabel": { "message": "Restart Session" @@ -1973,6 +1973,15 @@ "autoUpdateLaterButtonLabel": { "message": "Later" }, + "autoUpdateDownloadButtonLabel": { + "message": "Download" + }, + "autoUpdateDownloadedMessage": { + "message": "The new update has been downloaded." + }, + "autoUpdateRestartInstructions": { + "message": "Press Restart Session to apply the updates." + }, "leftTheGroup": { "message": "$name$ left the group", "description": diff --git a/ts/updater/common.ts b/ts/updater/common.ts index 1bb73decc..547f01b1f 100644 --- a/ts/updater/common.ts +++ b/ts/updater/common.ts @@ -18,37 +18,57 @@ export type LoggerType = { trace: LogFunction; }; -export async function showUpdateDialog( +export async function showDownloadUpdateDialog( mainWindow: BrowserWindow, messages: MessagesType ): Promise { - const RESTART_BUTTON = 0; + const DOWNLOAD_BUTTON = 0; const LATER_BUTTON = 1; const options = { type: 'info', buttons: [ - messages.autoUpdateRestartButtonLabel.message, + messages.autoUpdateDownloadButtonLabel.message, messages.autoUpdateLaterButtonLabel.message, ], title: messages.autoUpdateNewVersionTitle.message, message: messages.autoUpdateNewVersionMessage.message, detail: messages.autoUpdateNewVersionInstructions.message, defaultId: LATER_BUTTON, - cancelId: RESTART_BUTTON, + cancelId: DOWNLOAD_BUTTON, }; return new Promise(resolve => { dialog.showMessageBox(mainWindow, options, response => { - if (response === RESTART_BUTTON) { - // It's key to delay any install calls here because they don't seem to work inside this - // callback - but only if the message box has a parent window. - // Fixes this: https://github.com/signalapp/Signal-Desktop/issues/1864 - resolve(true); + resolve(response === DOWNLOAD_BUTTON); + }); + }); +} - return; - } +export async function showUpdateDialog( + mainWindow: BrowserWindow, + messages: MessagesType +): Promise { + const RESTART_BUTTON = 0; + const LATER_BUTTON = 1; + const options = { + type: 'info', + buttons: [ + messages.autoUpdateRestartButtonLabel.message, + messages.autoUpdateLaterButtonLabel.message, + ], + title: messages.autoUpdateNewVersionTitle.message, + message: messages.autoUpdateDownloadedMessage.message, + detail: messages.autoUpdateRestartInstructions.message, + defaultId: LATER_BUTTON, + cancelId: RESTART_BUTTON, + }; - resolve(false); + return new Promise(resolve => { + dialog.showMessageBox(mainWindow, options, response => { + // It's key to delay any install calls here because they don't seem to work inside this + // callback - but only if the message box has a parent window. + // Fixes this: https://github.com/signalapp/Signal-Desktop/issues/1864 + resolve(response === RESTART_BUTTON); }); }); } diff --git a/ts/updater/updater.ts b/ts/updater/updater.ts index cd2547d31..abde36b06 100644 --- a/ts/updater/updater.ts +++ b/ts/updater/updater.ts @@ -1,6 +1,6 @@ import * as path from 'path'; import * as fs from 'fs-extra'; -import { autoUpdater } from 'electron-updater'; +import { autoUpdater, UpdateInfo } from 'electron-updater'; import { app, BrowserWindow } from 'electron'; import { markShouldQuit } from '../../app/window_state'; import { @@ -8,10 +8,13 @@ import { LoggerType, MessagesType, showCannotUpdateDialog, + showDownloadUpdateDialog, showUpdateDialog, } from './common'; +import { gt as isVersionGreaterThan, parse as parseVersion } from 'semver'; let isUpdating = false; +let downloadIgnored = false; const SECOND = 1000; const MINUTE = SECOND * 60; @@ -25,6 +28,7 @@ export async function start( logger.info('auto-update: starting checks...'); autoUpdater.logger = logger; + autoUpdater.autoDownload = false; setInterval(async () => { try { @@ -42,7 +46,7 @@ async function checkForUpdates( messages: MessagesType, logger: LoggerType ) { - if (isUpdating) { + if (isUpdating || downloadIgnored) { return; } @@ -51,20 +55,39 @@ async function checkForUpdates( return; } - isUpdating = true; - logger.info('auto-update: checking for update...'); + isUpdating = true; + try { // Get the update using electron-updater + const result = await autoUpdater.checkForUpdates(); + if (!result.updateInfo) { + logger.info('auto-update: no update info received'); + + return; + } + try { - const info = await autoUpdater.checkForUpdates(); - if (!info.downloadPromise) { - logger.info('auto-update: no update to download'); + const hasUpdate = isUpdateAvailable(result.updateInfo); + if (!hasUpdate) { + logger.info('auto-update: no update available'); return; } - await info.downloadPromise; + + logger.info('auto-update: showing download dialog...'); + const shouldDownload = await showDownloadUpdateDialog( + getMainWindow(), + messages + ); + if (!shouldDownload) { + downloadIgnored = true; + + return; + } + + await autoUpdater.downloadUpdate(); } catch (error) { await showCannotUpdateDialog(getMainWindow(), messages); throw error; @@ -85,6 +108,18 @@ async function checkForUpdates( } } +function isUpdateAvailable(updateInfo: UpdateInfo): boolean { + const latestVersion = parseVersion(updateInfo.version); + if (!latestVersion) { + return false; + } + + // We need to convert this to string because typescript won't let us use types across submodules .... + const currentVersion = autoUpdater.currentVersion.toString(); + + return isVersionGreaterThan(latestVersion, currentVersion); +} + /* Check if we have the required files to auto update. These files won't exist inside certain formats such as a linux deb file. From 9e9b2a12d6070e632b70dcefc3e6d7c5bff3c64e Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 5 Mar 2020 15:30:19 +1100 Subject: [PATCH 106/107] Fix translations --- _locales/ar/messages.json | 8 ++++---- _locales/bg/messages.json | 4 ++-- _locales/ca/messages.json | 8 ++++---- _locales/cs/messages.json | 8 ++++---- _locales/da/messages.json | 8 ++++---- _locales/de/messages.json | 8 ++++---- _locales/el/messages.json | 6 +++--- _locales/en/messages.json | 6 +++--- _locales/eo/messages.json | 8 ++++---- _locales/es/messages.json | 8 ++++---- _locales/es_419/messages.json | 8 ++++---- _locales/et/messages.json | 8 ++++---- _locales/fa/messages.json | 8 ++++---- _locales/fi/messages.json | 8 ++++---- _locales/fr/messages.json | 8 ++++---- _locales/he/messages.json | 8 ++++---- _locales/hi/messages.json | 8 ++++---- _locales/hr/messages.json | 8 ++++---- _locales/hu/messages.json | 8 ++++---- _locales/id/messages.json | 8 ++++---- _locales/it/messages.json | 8 ++++---- _locales/ja/messages.json | 8 ++++---- _locales/km/messages.json | 8 ++++---- _locales/kn/messages.json | 8 ++++---- _locales/ko/messages.json | 8 ++++---- _locales/lt/messages.json | 8 ++++---- _locales/mk/messages.json | 8 ++++---- _locales/nb/messages.json | 8 ++++---- _locales/nl/messages.json | 8 ++++---- _locales/nn/messages.json | 8 ++++---- _locales/no/messages.json | 8 ++++---- _locales/pl/messages.json | 4 ++-- _locales/pt_BR/messages.json | 8 ++++---- _locales/pt_PT/messages.json | 8 ++++---- _locales/ro/messages.json | 8 ++++---- _locales/ru/messages.json | 8 ++++---- _locales/sk/messages.json | 8 ++++---- _locales/sl/messages.json | 8 ++++---- _locales/sq/messages.json | 8 ++++---- _locales/sr/messages.json | 8 ++++---- _locales/sv/messages.json | 8 ++++---- _locales/th/messages.json | 8 ++++---- _locales/tr/messages.json | 8 ++++---- _locales/uk/messages.json | 8 ++++---- _locales/vi/messages.json | 8 ++++---- _locales/zh_CN/messages.json | 8 ++++---- _locales/zh_TW/messages.json | 8 ++++---- ts/updater/common.ts | 4 ++-- 48 files changed, 184 insertions(+), 184 deletions(-) diff --git a/_locales/ar/messages.json b/_locales/ar/messages.json index 4d8a837b5..389c7d850 100644 --- a/_locales/ar/messages.json +++ b/_locales/ar/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Signal update available", + "message": "Session update available", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "There is a new version of Signal available.", + "message": "There is a new version of Session available.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Press Restart Signal to apply the updates.", + "message": "Press Restart Session to apply the updates.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Restart Signal", + "message": "Restart Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/bg/messages.json b/_locales/bg/messages.json index 9ad74f832..26bf2808a 100644 --- a/_locales/bg/messages.json +++ b/_locales/bg/messages.json @@ -1468,11 +1468,11 @@ "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Натиснете Рестарт на Signal за да валидирате промените.", + "message": "Натиснете Рестарт на Session за да валидирате промените.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Рестарт на Signal", + "message": "Рестарт на Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/ca/messages.json b/_locales/ca/messages.json index 5a526a26c..3778704f9 100644 --- a/_locales/ca/messages.json +++ b/_locales/ca/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Disponible una actualització del Signal", + "message": "Disponible una actualització del Session", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Hi ha disponible una versió nova del Signal.", + "message": "Hi ha disponible una versió nova del Session.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Premeu Reinicia el Signal per a aplicar les actualitzacions.", + "message": "Premeu Reinicia el Session per a aplicar les actualitzacions.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Reinicia el Signal", + "message": "Reinicia el Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/cs/messages.json b/_locales/cs/messages.json index 868231021..8a4fc39f2 100644 --- a/_locales/cs/messages.json +++ b/_locales/cs/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Dostupná aktualizace Signal", + "message": "Dostupná aktualizace Session", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Je k dispozici nová verze aplikace Signal.", + "message": "Je k dispozici nová verze aplikace Session.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Stiskněte na Restartovat Signal pro aplikování změn", + "message": "Stiskněte na Restartovat Session pro aplikování změn", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Restartovat Signal", + "message": "Restartovat Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/da/messages.json b/_locales/da/messages.json index f8bbe312e..57d96b4ea 100644 --- a/_locales/da/messages.json +++ b/_locales/da/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Signalopdatering tilgængelig", + "message": "Sessionopdatering tilgængelig", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Der er en ny version af Signal tilgængelig.", + "message": "Der er en ny version af Session tilgængelig.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Genstart Signal for at anvende opdateringerne.", + "message": "Genstart Session for at anvende opdateringerne.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Genstart Signal", + "message": "Genstart Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 6df957c3a..3b5102b63 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Aktualisierung für Signal verfügbar", + "message": "Aktualisierung für Session verfügbar", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Eine neue Version von Signal ist verfügbar.", + "message": "Eine neue Version von Session ist verfügbar.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Zum Aktualisieren klicke auf »Signal neu starten«.", + "message": "Zum Aktualisieren klicke auf »Session neu starten«.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Signal neu starten", + "message": "Session neu starten", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/el/messages.json b/_locales/el/messages.json index 6acf9812d..5a504dadd 100644 --- a/_locales/el/messages.json +++ b/_locales/el/messages.json @@ -1460,11 +1460,11 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Διαθέσιμη ενημέρωση του Signal", + "message": "Διαθέσιμη ενημέρωση του Session", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Μια νέα έκδοση του Signal είναι διαθέσιμη.", + "message": "Μια νέα έκδοση του Session είναι διαθέσιμη.", "description": "" }, "autoUpdateNewVersionInstructions": { @@ -1472,7 +1472,7 @@ "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Επανεκκίνηση του Signal", + "message": "Επανεκκίνηση του Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/en/messages.json b/_locales/en/messages.json index f409716c3..3364bed1e 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1965,7 +1965,7 @@ "message": "There is a new version of Session available." }, "autoUpdateNewVersionInstructions": { - "message": "Would you like to download the update?" + "message": "Press Restart Session to apply the updates." }, "autoUpdateRestartButtonLabel": { "message": "Restart Session" @@ -1979,8 +1979,8 @@ "autoUpdateDownloadedMessage": { "message": "The new update has been downloaded." }, - "autoUpdateRestartInstructions": { - "message": "Press Restart Session to apply the updates." + "autoUpdateDownloadInstructions": { + "message": "Would you like to download the update?" }, "leftTheGroup": { "message": "$name$ left the group", diff --git a/_locales/eo/messages.json b/_locales/eo/messages.json index 5c8d79e3f..d49537ea2 100644 --- a/_locales/eo/messages.json +++ b/_locales/eo/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Ĝisdatiĝo de Signal disponeblas", + "message": "Ĝisdatiĝo de Session disponeblas", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Nova versio de Signal disponeblas.", + "message": "Nova versio de Session disponeblas.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Premu „Restartigi Signal-on“ por ĝisdatigi.", + "message": "Premu „Restartigi Session-on“ por ĝisdatigi.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Restartigi Signal-on", + "message": "Restartigi Session-on", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/es/messages.json b/_locales/es/messages.json index bf33c4d97..a9f2773e9 100644 --- a/_locales/es/messages.json +++ b/_locales/es/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Actualización de Signal Desktop disponible", + "message": "Actualización de Session Desktop disponible", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Hay una nueva versión de Signal Desktop disponible.", + "message": "Hay una nueva versión de Session Desktop disponible.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Pulsa en 'Reiniciar Signal' para aplicar cambios.", + "message": "Pulsa en 'Reiniciar Session' para aplicar cambios.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Reiniciar Signal", + "message": "Reiniciar Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/es_419/messages.json b/_locales/es_419/messages.json index fddcf20ea..f7ffb3e81 100644 --- a/_locales/es_419/messages.json +++ b/_locales/es_419/messages.json @@ -1324,19 +1324,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Actualización de Signal disponible", + "message": "Actualización de Session disponible", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Hay una nueva versión de Signal disponible.", + "message": "Hay una nueva versión de Session disponible.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Press Restart Signal to apply the updates.", + "message": "Press Restart Session to apply the updates.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Restart Signal", + "message": "Restart Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/et/messages.json b/_locales/et/messages.json index ac83ec863..486023901 100644 --- a/_locales/et/messages.json +++ b/_locales/et/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Signali uuendus on saadaval", + "message": "Session uuendus on saadaval", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Signalist on saadaval uus versioon.", + "message": "Session on saadaval uus versioon.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Uuenduste paigaldamiseks vajuta \"Taaskäivita Signal\".", + "message": "Uuenduste paigaldamiseks vajuta \"Taaskäivita Session\".", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Taaskäivita Signal", + "message": "Taaskäivita Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/fa/messages.json b/_locales/fa/messages.json index 00d691d04..67ba7791c 100644 --- a/_locales/fa/messages.json +++ b/_locales/fa/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "به‌روزرسانی Signal در دسترس است", + "message": "به‌روزرسانی Session در دسترس است", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "نسخه جدیدی از Signal در دسترس است.", + "message": "نسخه جدیدی از Session در دسترس است.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "برای اعمال آپدیت ها Signal را ری استارت کنید.", + "message": "برای اعمال آپدیت ها Session را ری استارت کنید.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "راه اندازی مجدد Signal", + "message": "راه اندازی مجدد Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/fi/messages.json b/_locales/fi/messages.json index 32bc5f796..9e68fae51 100644 --- a/_locales/fi/messages.json +++ b/_locales/fi/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Signal päivitys saatavilla", + "message": "Session päivitys saatavilla", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Uusi versio Signalista on saatavilla.", + "message": "Uusi versio Session on saatavilla.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Paina Käynnistä Signal uudelleen asentaaksesi päivitykset.", + "message": "Paina Käynnistä Session uudelleen asentaaksesi päivitykset.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Käynnistä Signal uudelleen", + "message": "Käynnistä Session uudelleen", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 7c5f03ea9..f5226da6d 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Une mise à jour de Signal est proposée", + "message": "Une mise à jour de Session est proposée", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Une nouvelle version de Signal est proposée.", + "message": "Une nouvelle version de Session est proposée.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Appuyez sur « Redémarrer Signal » pour appliquer les mises à jour.", + "message": "Appuyez sur « Redémarrer Session » pour appliquer les mises à jour.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Redémarrer Signal", + "message": "Redémarrer Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/he/messages.json b/_locales/he/messages.json index 0d510335d..41275a3a1 100644 --- a/_locales/he/messages.json +++ b/_locales/he/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "עדכון Signal זמין", + "message": "עדכון Session זמין", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "יש גרסה חדשה של Signal זמינה.", + "message": "יש גרסה חדשה של Session זמינה.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "לחץ על הפעל מחדש את Signal כדי להחיל את העדכונים.", + "message": "לחץ על הפעל מחדש את Session כדי להחיל את העדכונים.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "הפעל מחדש את Signal", + "message": "הפעל מחדש את Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/hi/messages.json b/_locales/hi/messages.json index 4cdb26f48..47b15b894 100644 --- a/_locales/hi/messages.json +++ b/_locales/hi/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Signal update available", + "message": "Session update available", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "There is a new version of Signal available.", + "message": "There is a new version of Session available.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Press Restart Signal to apply the updates.", + "message": "Press Restart Session to apply the updates.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Restart Signal", + "message": "Restart Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/hr/messages.json b/_locales/hr/messages.json index 4205980df..85e631556 100644 --- a/_locales/hr/messages.json +++ b/_locales/hr/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Dostupna nadogradnja za Signal", + "message": "Dostupna nadogradnja za Session", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Dostupna je nova inačica Signala.", + "message": "Dostupna je nova inačica Session.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Press Restart Signal to apply the updates.", + "message": "Press Restart Session to apply the updates.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Restart Signal", + "message": "Restart Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/hu/messages.json b/_locales/hu/messages.json index 3eeebb67f..816c2e8d9 100644 --- a/_locales/hu/messages.json +++ b/_locales/hu/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Signal frissítés elérhető", + "message": "Session frissítés elérhető", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "A Signal új verziója érhető el.", + "message": "A Session új verziója érhető el.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Kattints a Signal újraindítására a frissítések alkalmazásához! ", + "message": "Kattints a Session újraindítására a frissítések alkalmazásához! ", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Signal újraindítása", + "message": "Session újraindítása", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/id/messages.json b/_locales/id/messages.json index 9c4dc3b65..674e19aeb 100644 --- a/_locales/id/messages.json +++ b/_locales/id/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Tersedia Signal versi terbaru", + "message": "Tersedia Session versi terbaru", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Tersedia versi terbaru Signal.", + "message": "Tersedia versi terbaru Session.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Tekan memulai awal Signal untuk mendapatkan versi terbaru.", + "message": "Tekan memulai awal Session untuk mendapatkan versi terbaru.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Mulai ulang Signal", + "message": "Mulai ulang Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/it/messages.json b/_locales/it/messages.json index e40da8f41..4f97ab256 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Aggiornamento Signal disponibile", + "message": "Aggiornamento Session disponibile", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "È disponibile una nuova versione di Signal.", + "message": "È disponibile una nuova versione di Session.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Premi \"Riavvia Signal\" per applicare gli aggiornamenti.", + "message": "Premi \"Riavvia Session\" per applicare gli aggiornamenti.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Riavvia Signal", + "message": "Riavvia Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json index 25acf6561..1b60fd00f 100644 --- a/_locales/ja/messages.json +++ b/_locales/ja/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Signalのアップデートがあります", + "message": "Sessionのアップデートがあります", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "新しく生まれ変わったSignalがあります", + "message": "新しく生まれ変わったSessionがあります", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "アップデートを適用するにはSignalを再起動してください。", + "message": "アップデートを適用するにはSessionを再起動してください。", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Signalを再起動", + "message": "Sessionを再起動", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/km/messages.json b/_locales/km/messages.json index 9f41528e1..ce300cfda 100644 --- a/_locales/km/messages.json +++ b/_locales/km/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "មានបច្ចុប្បន្នភាព Signal", + "message": "មានបច្ចុប្បន្នភាព Session", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "មានSignalជំនាន់ថ្មី", + "message": "មានSessionជំនាន់ថ្មី", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "ចុច បើក Signalឡើងវិញ ដើម្បីដំណើការបច្ចុប្បន្នភាព។", + "message": "ចុច បើក Sessionឡើងវិញ ដើម្បីដំណើការបច្ចុប្បន្នភាព។", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "បើកSignal ឡើងវិញ", + "message": "បើកSession ឡើងវិញ", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/kn/messages.json b/_locales/kn/messages.json index 3b3af59cf..303de1ea8 100644 --- a/_locales/kn/messages.json +++ b/_locales/kn/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Signal update available", + "message": "Session update available", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "There is a new version of Signal available.", + "message": "There is a new version of Session available.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Press Restart Signal to apply the updates.", + "message": "Press Restart Session to apply the updates.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Restart Signal", + "message": "Restart Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/ko/messages.json b/_locales/ko/messages.json index 968abaec2..7738b6b41 100644 --- a/_locales/ko/messages.json +++ b/_locales/ko/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Signal update available", + "message": "Session update available", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "There is a new version of Signal available.", + "message": "There is a new version of Session available.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Press Restart Signal to apply the updates.", + "message": "Press Restart Session to apply the updates.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Restart Signal", + "message": "Restart Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/lt/messages.json b/_locales/lt/messages.json index b57915c90..6352b544e 100644 --- a/_locales/lt/messages.json +++ b/_locales/lt/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Yra prieinamas Signal atnaujinimas", + "message": "Yra prieinamas Session atnaujinimas", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Yra prieinama nauja Signal versija.", + "message": "Yra prieinama nauja Session versija.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Norėdami pritaikyti atnaujinimus, paspauskite \"Paleisti Signal iš naujo\".", + "message": "Norėdami pritaikyti atnaujinimus, paspauskite \"Paleisti Session iš naujo\".", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Paleisti Signal iš naujo", + "message": "Paleisti Session iš naujo", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/mk/messages.json b/_locales/mk/messages.json index 3987e9b00..9224fad9d 100644 --- a/_locales/mk/messages.json +++ b/_locales/mk/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Signal update available", + "message": "Session update available", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "There is a new version of Signal available.", + "message": "There is a new version of Session available.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Press Restart Signal to apply the updates.", + "message": "Press Restart Session to apply the updates.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Restart Signal", + "message": "Restart Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/nb/messages.json b/_locales/nb/messages.json index 41343bccc..262e5ee53 100644 --- a/_locales/nb/messages.json +++ b/_locales/nb/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Signal oppdatering tilgjengelig", + "message": "Session oppdatering tilgjengelig", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "En ny versjon av Signal er tilgjengelig", + "message": "En ny versjon av Session er tilgjengelig", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Trykk Restart Signal for å fullføre oppgraderingen.", + "message": "Trykk Restart Session for å fullføre oppgraderingen.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Start Signal På Nytt", + "message": "Start Session På Nytt", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/nl/messages.json b/_locales/nl/messages.json index 70739c875..17328b69d 100644 --- a/_locales/nl/messages.json +++ b/_locales/nl/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Update voor Signal beschikbaar", + "message": "Update voor Session beschikbaar", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Er is een nieuwe versie van Signal beschikbaar.", + "message": "Er is een nieuwe versie van Session beschikbaar.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Klik op ‘Signal herstarten’ om de updates toe te passen.", + "message": "Klik op Session herstarten’ om de updates toe te passen.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Signal herstarten", + "message": "Session herstarten", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/nn/messages.json b/_locales/nn/messages.json index b169d1f37..bfe13d62d 100644 --- a/_locales/nn/messages.json +++ b/_locales/nn/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Signal-oppdatering tilgjengeleg", + "message": "Session-oppdatering tilgjengeleg", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Ei ny utgåve av Signal er tilgjengeleg", + "message": "Ei ny utgåve av Session er tilgjengeleg", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Trykk «Start Signal på nytt» for å fullføra oppgraderinga.", + "message": "Trykk «Start Session på nytt» for å fullføra oppgraderinga.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Start Signal på nytt", + "message": "Start Session på nytt", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/no/messages.json b/_locales/no/messages.json index 617d1bb17..8c7cf3ff5 100644 --- a/_locales/no/messages.json +++ b/_locales/no/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Signal oppdatering tilgjengelig", + "message": "Session oppdatering tilgjengelig", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "En ny versjon av Signal er tilgjengelig", + "message": "En ny versjon av Session er tilgjengelig", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Trykk Restart Signal for å fullføre oppgraderingen.", + "message": "Trykk Restart Session for å fullføre oppgraderingen.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Start Signal På Nytt", + "message": "Start Session På Nytt", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/pl/messages.json b/_locales/pl/messages.json index d3b3d45c6..4a7165d8d 100644 --- a/_locales/pl/messages.json +++ b/_locales/pl/messages.json @@ -1460,11 +1460,11 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Dostępna aktualizacja aplikacji Signal", + "message": "Dostępna aktualizacja aplikacji Session", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Dostępna nowa wersja Signal", + "message": "Dostępna nowa wersja Session", "description": "" }, "autoUpdateNewVersionInstructions": { diff --git a/_locales/pt_BR/messages.json b/_locales/pt_BR/messages.json index f7deb63b2..84136fba0 100644 --- a/_locales/pt_BR/messages.json +++ b/_locales/pt_BR/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Atualização do Signal disponível", + "message": "Atualização do Session disponível", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Uma nova versão do Signal está disponível.", + "message": "Uma nova versão do Session está disponível.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Por favor, toque em 'reiniciar Signal' para aplicar as atualizações.", + "message": "Por favor, toque em 'reiniciar Session' para aplicar as atualizações.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Reiniciar Signal", + "message": "Reiniciar Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/pt_PT/messages.json b/_locales/pt_PT/messages.json index 1cf05c89d..cf50a6fdc 100644 --- a/_locales/pt_PT/messages.json +++ b/_locales/pt_PT/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Existe uma actualização disponível para o Signal", + "message": "Existe uma actualização disponível para o Session", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Está disponível uma nova versão do Signal.", + "message": "Está disponível uma nova versão do Session.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Pressione 'Reiniciar o Signal' para aplicar as atualizações.", + "message": "Pressione 'Reiniciar o Session' para aplicar as atualizações.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Reiniciar o Signal", + "message": "Reiniciar o Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/ro/messages.json b/_locales/ro/messages.json index dfc8ed7f1..de51cd86f 100644 --- a/_locales/ro/messages.json +++ b/_locales/ro/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Este disponibilă o actualizare de Signal ", + "message": "Este disponibilă o actualizare de Session ", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Este disponibilă o nouă versiune de Signal.", + "message": "Este disponibilă o nouă versiune de Session.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Apasă pe Repornire Signal pentru a aplica actualizările.", + "message": "Apasă pe Repornire Session pentru a aplica actualizările.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Repornește Signal", + "message": "Repornește Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json index 6e20d8595..0b67642b2 100644 --- a/_locales/ru/messages.json +++ b/_locales/ru/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Доступно обновление Signal", + "message": "Доступно обновление Session", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Доступна новая версия Signal", + "message": "Доступна новая версия Session", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Для применения обновлений перезапустите Signal.", + "message": "Для применения обновлений перезапустите Session.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Перезапустите Signal", + "message": "Перезапустите Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/sk/messages.json b/_locales/sk/messages.json index 4e79bb406..255968466 100644 --- a/_locales/sk/messages.json +++ b/_locales/sk/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Dostupná aktualizácia pre Signal", + "message": "Dostupná aktualizácia pre Session", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Je k dispozícii nová verzia Signal.", + "message": "Je k dispozícii nová verzia Session.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Reštartujte Signal pre dokončenie aktualizácie.", + "message": "Reštartujte Session pre dokončenie aktualizácie.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Reštartovať Signal", + "message": "Reštartovať Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/sl/messages.json b/_locales/sl/messages.json index 81250e0b5..a7434c0e3 100644 --- a/_locales/sl/messages.json +++ b/_locales/sl/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Na voljo je posodobitev aplikacije Signal", + "message": "Na voljo je posodobitev aplikacije Session", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Na voljo je nova različica aplikacije Signal.", + "message": "Na voljo je nova različica aplikacije Session.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Za uveljavitev nadgradenj pritisnite tipko Ponovno zaženi Signal", + "message": "Za uveljavitev nadgradenj pritisnite tipko Ponovno zaženi Session", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Ponovno zaženi Signal", + "message": "Ponovno zaženi Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/sq/messages.json b/_locales/sq/messages.json index 06ee6e4f3..89da7d48d 100644 --- a/_locales/sq/messages.json +++ b/_locales/sq/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Ka gati përditësim të Signal-it", + "message": "Ka gati përditësim të Session-it", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Ka të gatshëm një version të ri të Signal-it", + "message": "Ka të gatshëm një version të ri të Session-it", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Shtypni Rinise Signal-in që të zbatohen përditësimet.", + "message": "Shtypni Rinise Session-in që të zbatohen përditësimet.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Riniseni Signal-in", + "message": "Riniseni Session-in", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/sr/messages.json b/_locales/sr/messages.json index e58b1496e..4a6c715d7 100644 --- a/_locales/sr/messages.json +++ b/_locales/sr/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Нова верзија Signal-а је доступна", + "message": "Нова верзија Session-а је доступна", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "There is a new version of Signal available.", + "message": "There is a new version of Session available.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Press Restart Signal to apply the updates.", + "message": "Press Restart Session to apply the updates.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Restart Signal", + "message": "Restart Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/sv/messages.json b/_locales/sv/messages.json index 4af4a8295..53bf2670c 100644 --- a/_locales/sv/messages.json +++ b/_locales/sv/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Uppdatering för Signal tillgänglig", + "message": "Uppdatering för Session tillgänglig", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Det finns en ny version av Signal tillgänglig.", + "message": "Det finns en ny version av Session tillgänglig.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Vänligen starta om Signal för att uppdatera", + "message": "Vänligen starta om Session för att uppdatera", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Starta om Signal", + "message": "Starta om Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/th/messages.json b/_locales/th/messages.json index d487a0426..08d3e2af2 100644 --- a/_locales/th/messages.json +++ b/_locales/th/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "มีการอัพเดทสำหรับ Signal", + "message": "มีการอัพเดทสำหรับ Session", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "มี Signal รุ่นใหม่แล้ว", + "message": "มี Session รุ่นใหม่แล้ว", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "กด เริ่มต้น Signal ใหม่เพื่อเริ่มใช้การอัพเดต", + "message": "กด เริ่มต้น Session ใหม่เพื่อเริ่มใช้การอัพเดต", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "เริ่มต้น Signal ใหม่", + "message": "เริ่มต้น Session ใหม่", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/tr/messages.json b/_locales/tr/messages.json index a217b4a75..7ee802fd2 100644 --- a/_locales/tr/messages.json +++ b/_locales/tr/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Signal güncellemesi mevcut", + "message": "Session güncellemesi mevcut", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Signal'ın yeni bir sürümü mevcut.", + "message": "Session'ın yeni bir sürümü mevcut.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Güncellemeleri uygulamak için 'Signal'i Yeniden Başlat'a basınız.", + "message": "Güncellemeleri uygulamak için 'Session'i Yeniden Başlat'a basınız.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Signal'i Yeniden Başlat", + "message": "Session'i Yeniden Başlat", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/uk/messages.json b/_locales/uk/messages.json index 5bc5dae14..26b4629cc 100644 --- a/_locales/uk/messages.json +++ b/_locales/uk/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Доступне оновлення Signal", + "message": "Доступне оновлення Session", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "Нова версія Signal доступна.", + "message": "Нова версія Session доступна.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Press Restart Signal to apply the updates.", + "message": "Press Restart Session to apply the updates.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Restart Signal", + "message": "Restart Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/vi/messages.json b/_locales/vi/messages.json index 496213f33..9c194999d 100644 --- a/_locales/vi/messages.json +++ b/_locales/vi/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Signal update available", + "message": "Session update available", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "There is a new version of Signal available.", + "message": "There is a new version of Session available.", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Press Restart Signal to apply the updates.", + "message": "Press Restart Session to apply the updates.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Restart Signal", + "message": "Restart Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index efa5343e7..695575f2b 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Signal 有可用更新", + "message": "Session 有可用更新", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "有新版的 Signal 可用。", + "message": "有新版的 Session 可用。", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "点击“重启 Signal”来安装更新。", + "message": "点击“重启 Session", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "重启 Signal", + "message": "重启 Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/_locales/zh_TW/messages.json b/_locales/zh_TW/messages.json index 1576c8762..b0c5e69b4 100644 --- a/_locales/zh_TW/messages.json +++ b/_locales/zh_TW/messages.json @@ -1460,19 +1460,19 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Signal 可用的更新", + "message": "Session 可用的更新", "description": "" }, "autoUpdateNewVersionMessage": { - "message": "這是新版本的 Signal。", + "message": "這是新版本的 Session", "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "點選重啟 Signal 來套用更新。", + "message": "點選重啟 Session 來套用更新。", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "重啟 Signal", + "message": "重啟 Session", "description": "" }, "autoUpdateLaterButtonLabel": { diff --git a/ts/updater/common.ts b/ts/updater/common.ts index 547f01b1f..e51727af1 100644 --- a/ts/updater/common.ts +++ b/ts/updater/common.ts @@ -32,7 +32,7 @@ export async function showDownloadUpdateDialog( ], title: messages.autoUpdateNewVersionTitle.message, message: messages.autoUpdateNewVersionMessage.message, - detail: messages.autoUpdateNewVersionInstructions.message, + detail: messages.autoUpdateDownloadInstructions.message, defaultId: LATER_BUTTON, cancelId: DOWNLOAD_BUTTON, }; @@ -58,7 +58,7 @@ export async function showUpdateDialog( ], title: messages.autoUpdateNewVersionTitle.message, message: messages.autoUpdateDownloadedMessage.message, - detail: messages.autoUpdateRestartInstructions.message, + detail: messages.autoUpdateNewVersionInstructions.message, defaultId: LATER_BUTTON, cancelId: RESTART_BUTTON, }; From aeb349ea64e6a1a0b3ab413e7e3f56d2f62fc26a Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 5 Mar 2020 15:40:53 +1100 Subject: [PATCH 107/107] Unrelated lint... --- .../conversation/ConversationHeader.tsx | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index 1aae3bb6a..db3e4f56f 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -255,17 +255,17 @@ export class ConversationHeader extends React.Component { ); } - public renderSearch() { - return ( -
- -
- ); + public renderSearch() { + return ( +
+ +
+ ); } public renderOptions(triggerId: string) { @@ -398,12 +398,8 @@ export class ConversationHeader extends React.Component {
{this.renderExpirationLength()} - - {!this.props.isRss && ( - <> - {this.renderAvatar()} - - )} + + {!this.props.isRss && <>{this.renderAvatar()}} {!this.props.isRss && this.renderAvatar()} @@ -419,10 +415,10 @@ export class ConversationHeader extends React.Component { } } - public highlightMessageSearch() { - // This is a temporary fix. In future we want to search - // messages in the current conversation - $('.session-search-input input').focus(); + public highlightMessageSearch() { + // This is a temporary fix. In future we want to search + // messages in the current conversation + $('.session-search-input input').focus(); } private renderPublicMenuItems() {