From 12f73e23f26999b4827dead588327a77cfe1dde1 Mon Sep 17 00:00:00 2001 From: Maxim Shishmarev Date: Tue, 7 Jul 2020 09:57:52 +1000 Subject: [PATCH 1/3] Move onion requests to typescript --- js/background.js | 2 +- js/modules/data.d.ts | 2 +- js/modules/loki_app_dot_net_api.js | 6 +- js/modules/loki_snode_api.js | 316 +------------------------ preload.js | 8 +- ts/session/onions/index.ts | 310 ++++++++++++++++++++++++ ts/session/snode_api/onions.ts | 11 +- ts/session/snode_api/serviceNodeAPI.ts | 2 +- 8 files changed, 329 insertions(+), 328 deletions(-) create mode 100644 ts/session/onions/index.ts diff --git a/js/background.js b/js/background.js index 89f5d242f..ce8aaae34 100644 --- a/js/background.js +++ b/js/background.js @@ -292,7 +292,7 @@ window.lokiFeatureFlags.useFileOnionRequests ) { // Initialize paths for onion requests - window.lokiSnodeAPI.buildNewOnionPaths(); + window.OnionAPI.buildNewOnionPaths(); } const currentPoWDifficulty = storage.get('PoWDifficulty', null); diff --git a/js/modules/data.d.ts b/js/modules/data.d.ts index 151beca16..bc48adc40 100644 --- a/js/modules/data.d.ts +++ b/js/modules/data.d.ts @@ -159,7 +159,7 @@ export function getPairingAuthorisationsFor( export function removePairingAuthorisationsFor(pubKey: string): Promise; // Guard Nodes -export function getGuardNodes(): Promise; +export function getGuardNodes(): Promise>; export function updateGuardNodes(nodes: Array): Promise; // Storage Items diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 19735c007..da97bfdb8 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -1,6 +1,6 @@ /* global log, textsecure, libloki, Signal, Whisper, ConversationController, clearTimeout, MessageController, libsignal, StringView, window, _, -dcodeIO, Buffer, lokiSnodeAPI, TextDecoder, process */ +dcodeIO, Buffer, TextDecoder, process */ const nodeFetch = require('node-fetch'); const { URL, URLSearchParams } = require('url'); const FormData = require('form-data'); @@ -59,7 +59,7 @@ const sendViaOnion = async (srvPubKey, url, fetchOptions, options = {}) => { // eslint-disable-next-line no-param-reassign options.retry = 0; // eslint-disable-next-line no-param-reassign - options.requestNumber = window.lokiSnodeAPI.assignOnionRequestNumber(); + options.requestNumber = window.OnionAPI.assignOnionRequestNumber(); } const payloadObj = { @@ -92,7 +92,7 @@ const sendViaOnion = async (srvPubKey, url, fetchOptions, options = {}) => { let pathNodes = []; try { - pathNodes = await lokiSnodeAPI.getOnionPath(); + pathNodes = await window.OnionAPI.getOnionPath(); } catch (e) { log.error( `loki_app_dot_net:::sendViaOnion #${options.requestNumber} - getOnionPath Error ${e.code} ${e.message}` diff --git a/js/modules/loki_snode_api.js b/js/modules/loki_snode_api.js index e5e8d5710..984b8941e 100644 --- a/js/modules/loki_snode_api.js +++ b/js/modules/loki_snode_api.js @@ -1,317 +1,11 @@ /* eslint-disable class-methods-use-this */ -/* global window, textsecure, log, process, Buffer, StringView, dcodeIO */ - -// not sure I like this name but it's been than util -const primitives = require('./loki_primitives'); - -const is = require('@sindresorhus/is'); -const nodeFetch = require('node-fetch'); - -const MIN_GUARD_COUNT = 2; +/* global window, Buffer, StringView, dcodeIO */ class LokiSnodeAPI { - constructor({ serverUrl, localUrl }) { - if (!is.string(serverUrl)) { - throw new Error('LokiSnodeAPI.initialize: Invalid server url'); - } - this.serverUrl = serverUrl; // random.snode - this.localUrl = localUrl; // localhost.loki - this.swarmsPendingReplenish = {}; - this.stopGetAllVersionPromiseControl = false; - - this.onionPaths = []; - this.guardNodes = []; - this.onionRequestCounter = 0; // Request index for debugging - } - - assignOnionRequestNumber() { - this.onionRequestCounter += 1; - return this.onionRequestCounter; - } - - async testGuardNode(snode) { - log.info('Testing a candidate guard node ', snode); - - // Send a post request and make sure it is OK - const endpoint = '/storage_rpc/v1'; - - const url = `https://${snode.ip}:${snode.port}${endpoint}`; - - const ourPK = textsecure.storage.user.getNumber(); - const pubKey = window.getStoragePubKey(ourPK); // truncate if testnet - - const method = 'get_snodes_for_pubkey'; - const params = { pubKey }; - const body = { - jsonrpc: '2.0', - id: '0', - method, - params, - }; - - const fetchOptions = { - method: 'POST', - body: JSON.stringify(body), - headers: { 'Content-Type': 'application/json' }, - timeout: 10000, // 10s, we want a smaller timeout for testing - }; - - process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; - - let response; - - try { - // Log this line for testing - // curl -k -X POST -H 'Content-Type: application/json' -d '"+fetchOptions.body.replace(/"/g, "\\'")+"'", url - response = await nodeFetch(url, fetchOptions); - } catch (e) { - if (e.type === 'request-timeout') { - log.warn(`test timeout for node,`, snode); - } - return false; - } finally { - process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'; - } - - if (!response.ok) { - log.info(`Node failed the guard test:`, snode); - } - - return response.ok; - } - - async selectGuardNodes() { - const _ = window.Lodash; - - // FIXME: handle rejections - let nodePool = await window.SnodePool.getRandomSnodePool(); - if (nodePool.length === 0) { - log.error(`Could not select guard nodes: node pool is empty`); - return []; - } - - let shuffled = _.shuffle(nodePool); - - let guardNodes = []; - - const DESIRED_GUARD_COUNT = 3; - - if (shuffled.length < DESIRED_GUARD_COUNT) { - log.error( - `Could not select guard nodes: node pool is not big enough, pool size ${shuffled.length}, need ${DESIRED_GUARD_COUNT}, attempting to refresh randomPool` - ); - await window.SnodePool.refreshRandomPool(); - nodePool = await window.SnodePool.getRandomSnodePool(); - shuffled = _.shuffle(nodePool); - if (shuffled.length < DESIRED_GUARD_COUNT) { - log.error( - `Could not select guard nodes: node pool is not big enough, pool size ${shuffled.length}, need ${DESIRED_GUARD_COUNT}, failing...` - ); - return []; - } - } - // The use of await inside while is intentional: - // we only want to repeat if the await fails - // eslint-disable-next-line-no-await-in-loop - while (guardNodes.length < 3) { - if (shuffled.length < DESIRED_GUARD_COUNT) { - log.error(`Not enought nodes in the pool`); - break; - } - - const candidateNodes = shuffled.splice(0, DESIRED_GUARD_COUNT); - - // Test all three nodes at once - // eslint-disable-next-line no-await-in-loop - const idxOk = await Promise.all( - candidateNodes.map(n => this.testGuardNode(n)) - ); - - const goodNodes = _.zip(idxOk, candidateNodes) - .filter(x => x[0]) - .map(x => x[1]); - - guardNodes = _.concat(guardNodes, goodNodes); - } - - if (guardNodes.length < DESIRED_GUARD_COUNT) { - log.error( - `COULD NOT get enough guard nodes, only have: ${guardNodes.length}` - ); - } - - log.info('new guard nodes: ', guardNodes); - - const edKeys = guardNodes.map(n => n.pubkey_ed25519); - - await window.libloki.storage.updateGuardNodes(edKeys); - - return guardNodes; - } - - async getOnionPath(toExclude = null) { - const _ = window.Lodash; - - let goodPaths = this.onionPaths.filter(x => !x.bad); - - let attemptNumber = 0; - while (goodPaths.length < MIN_GUARD_COUNT) { - log.error( - `Must have at least 2 good onion paths, actual: ${goodPaths.length}, attempt #${attemptNumber} fetching more...` - ); - // eslint-disable-next-line no-await-in-loop - await this.buildNewOnionPaths(); - // should we add a delay? buildNewOnionPaths should act as one - - // reload goodPaths now - attemptNumber += 1; - goodPaths = this.onionPaths.filter(x => !x.bad); - } - - const paths = _.shuffle(goodPaths); - - if (!toExclude) { - if (!paths[0]) { - log.error('LokiSnodeAPI::getOnionPath - no path in', paths); - return []; - } - if (!paths[0].path) { - log.error('LokiSnodeAPI::getOnionPath - no path in', paths[0]); - } - return paths[0].path; - } - - // Select a path that doesn't contain `toExclude` - const otherPaths = paths.filter( - path => - !_.some(path, node => node.pubkey_ed25519 === toExclude.pubkey_ed25519) - ); - - if (otherPaths.length === 0) { - // This should never happen! - // well it did happen, should we - // await this.buildNewOnionPaths(); - // and restart call? - log.error( - `LokiSnodeAPI::getOnionPath - no paths without`, - toExclude.pubkey_ed25519, - 'path count', - paths.length, - 'goodPath count', - goodPaths.length, - 'paths', - paths - ); - throw new Error('No onion paths available after filtering'); - } - - if (!otherPaths[0].path) { - log.error( - 'LokiSnodeAPI::getOnionPath - otherPaths no path in', - otherPaths[0] - ); - } - - return otherPaths[0].path; - } - - markPathAsBad(path) { - this.onionPaths.forEach(p => { - if (!p.path) { - log.error('LokiSnodeAPI::markPathAsBad - no path in', p); - } - if (p.path === path) { - // eslint-disable-next-line no-param-reassign - p.bad = true; - } - }); - } - - async buildNewOnionPathsWorker() { - const _ = window.Lodash; - - log.info('LokiSnodeAPI::buildNewOnionPaths - building new onion paths'); - - const allNodes = await window.SnodePool.getRandomSnodePool(); - - if (this.guardNodes.length === 0) { - // Not cached, load from DB - const nodes = await window.libloki.storage.getGuardNodes(); - - if (nodes.length === 0) { - log.warn( - 'LokiSnodeAPI::buildNewOnionPaths - no guard nodes in DB. Will be selecting new guards nodes...' - ); - } else { - // We only store the nodes' keys, need to find full entries: - const edKeys = nodes.map(x => x.ed25519PubKey); - this.guardNodes = allNodes.filter( - x => edKeys.indexOf(x.pubkey_ed25519) !== -1 - ); - - if (this.guardNodes.length < edKeys.length) { - log.warn( - `LokiSnodeAPI::buildNewOnionPaths - could not find some guard nodes: ${this.guardNodes.length}/${edKeys.length} left` - ); - } - } - - // If guard nodes is still empty (the old nodes are now invalid), select new ones: - if (this.guardNodes.length < MIN_GUARD_COUNT) { - // TODO: don't throw away potentially good guard nodes - this.guardNodes = await this.selectGuardNodes(); - } - } - - // TODO: select one guard node and 2 other nodes randomly - let otherNodes = _.difference(allNodes, this.guardNodes); - - if (otherNodes.length < 2) { - log.warn( - 'LokiSnodeAPI::buildNewOnionPaths - Too few nodes to build an onion path! Refreshing pool and retrying' - ); - await window.SnodePool.refreshRandomPool(); - await this.buildNewOnionPaths(); - return; - } - - otherNodes = _.shuffle(otherNodes); - const guards = _.shuffle(this.guardNodes); - - // Create path for every guard node: - const nodesNeededPerPaths = window.lokiFeatureFlags.onionRequestHops - 1; - - // Each path needs X (nodesNeededPerPaths) nodes in addition to the guard node: - const maxPath = Math.floor( - Math.min( - guards.length, - nodesNeededPerPaths - ? otherNodes.length / nodesNeededPerPaths - : otherNodes.length - ) - ); - - // TODO: might want to keep some of the existing paths - this.onionPaths = []; - - for (let i = 0; i < maxPath; i += 1) { - const path = [guards[i]]; - for (let j = 0; j < nodesNeededPerPaths; j += 1) { - path.push(otherNodes[i * nodesNeededPerPaths + j]); - } - this.onionPaths.push({ path, bad: false }); - } - - log.info(`Built ${this.onionPaths.length} onion paths`, this.onionPaths); - } - - async buildNewOnionPaths() { - // this function may be called concurrently make sure we only have one inflight - return primitives.allowOnlyOneAtATime('buildNewOnionPaths', async () => { - await this.buildNewOnionPathsWorker(); - }); - } - + // ************** NOTE *************** + // This is not used by anything yet, + // but should be. Do not remove!!! + // *********************************** async getLnsMapping(lnsName, timeout) { // Returns { pubkey, error } // pubkey is diff --git a/preload.js b/preload.js index efafb66fa..377b757e2 100644 --- a/preload.js +++ b/preload.js @@ -332,14 +332,12 @@ const { initialize: initializeWebAPI } = require('./js/modules/web_api'); window.WebAPI = initializeWebAPI(); window.seedNodeList = JSON.parse(config.seedNodeList); -const LokiSnodeAPI = require('./js/modules/loki_snode_api'); window.SenderKeyAPI = require('./js/modules/loki_sender_key_api'); -window.lokiSnodeAPI = new LokiSnodeAPI({ - serverUrl: config.serverUrl, - localUrl: config.localUrl, -}); +const { OnionAPI } = require('./ts/session/onions'); + +window.OnionAPI = OnionAPI; if (process.env.USE_STUBBED_NETWORK) { const StubMessageAPI = require('./integration_test/stubs/stub_message_api'); diff --git a/ts/session/onions/index.ts b/ts/session/onions/index.ts new file mode 100644 index 000000000..9fb8568e3 --- /dev/null +++ b/ts/session/onions/index.ts @@ -0,0 +1,310 @@ +import { allowOnlyOneAtATime } from '../../../js/modules/loki_primitives'; +import * as Data from '../../../js/modules/data'; +import * as SnodePool from '../snode_api/snodePool'; +import _ from 'lodash'; +import fetch from 'node-fetch'; + +type Snode = SnodePool.Snode; + +const MIN_GUARD_COUNT = 2; + +interface SnodePath { + path: Array; + bad: boolean; +} + +class OnionPaths { + private onionPaths: Array = []; + + // This array is meant to store nodes will full info, + // so using GuardNode would not be correct (there is + // some naming issue here it seems) + private guardNodes: Array = []; + private onionRequestCounter = 0; // Request index for debugging + + public async buildNewOnionPaths() { + // this function may be called concurrently make sure we only have one inflight + return allowOnlyOneAtATime('buildNewOnionPaths', async () => { + await this.buildNewOnionPathsWorker(); + }); + } + + public async getOnionPath(toExclude?: { + pubkey_ed25519: string; + }): Promise> { + const { log } = window; + + let goodPaths = this.onionPaths.filter(x => !x.bad); + + let attemptNumber = 0; + while (goodPaths.length < MIN_GUARD_COUNT) { + log.error( + `Must have at least 2 good onion paths, actual: ${goodPaths.length}, attempt #${attemptNumber} fetching more...` + ); + // eslint-disable-next-line no-await-in-loop + await this.buildNewOnionPaths(); + // should we add a delay? buildNewOnionPaths should act as one + + // reload goodPaths now + attemptNumber += 1; + goodPaths = this.onionPaths.filter(x => !x.bad); + } + + const paths = _.shuffle(goodPaths); + + if (!toExclude) { + if (!paths[0]) { + log.error('LokiSnodeAPI::getOnionPath - no path in', paths); + return []; + } + if (!paths[0].path) { + log.error('LokiSnodeAPI::getOnionPath - no path in', paths[0]); + } + return paths[0].path; + } + + // Select a path that doesn't contain `toExclude` + const otherPaths = paths.filter( + path => + !_.some( + path.path, + node => node.pubkey_ed25519 === toExclude.pubkey_ed25519 + ) + ); + + if (otherPaths.length === 0) { + // This should never happen! + // well it did happen, should we + // await this.buildNewOnionPaths(); + // and restart call? + log.error( + 'LokiSnodeAPI::getOnionPath - no paths without', + toExclude.pubkey_ed25519, + 'path count', + paths.length, + 'goodPath count', + goodPaths.length, + 'paths', + paths + ); + throw new Error('No onion paths available after filtering'); + } + + if (!otherPaths[0].path) { + log.error( + 'LokiSnodeAPI::getOnionPath - otherPaths no path in', + otherPaths[0] + ); + } + + return otherPaths[0].path; + } + + public markPathAsBad(path: Array) { + // TODO: we might want to remove the nodes from the + // node pool (but we don't know which node on the path + // is causing issues) + + this.onionPaths.forEach(p => { + if (_.isEqual(p.path, path)) { + // eslint-disable-next-line no-param-reassign + p.bad = true; + } + }); + } + + public assignOnionRequestNumber() { + this.onionRequestCounter += 1; + return this.onionRequestCounter; + } + + private async testGuardNode(snode: Snode) { + const { log } = window; + + log.info('Testing a candidate guard node ', snode); + + // Send a post request and make sure it is OK + const endpoint = '/storage_rpc/v1'; + + const url = `https://${snode.ip}:${snode.port}${endpoint}`; + + const ourPK = window.textsecure.storage.user.getNumber(); + const pubKey = window.getStoragePubKey(ourPK); // truncate if testnet + + const method = 'get_snodes_for_pubkey'; + const params = { pubKey }; + const body = { + jsonrpc: '2.0', + id: '0', + method, + params, + }; + + const fetchOptions = { + method: 'POST', + body: JSON.stringify(body), + headers: { 'Content-Type': 'application/json' }, + timeout: 10000, // 10s, we want a smaller timeout for testing + }; + + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + + let response; + + try { + // Log this line for testing + // curl -k -X POST -H 'Content-Type: application/json' -d '"+fetchOptions.body.replace(/"/g, "\\'")+"'", url + response = await fetch(url, fetchOptions); + } catch (e) { + if (e.type === 'request-timeout') { + log.warn('test timeout for node,', snode); + } + return false; + } finally { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'; + } + + if (!response.ok) { + log.info('Node failed the guard test:', snode); + } + + return response.ok; + } + + private async selectGuardNodes(): Promise> { + const { log } = window; + + const DESIRED_GUARD_COUNT = 3; + // `getRandomSnodePool` is expected to refresh itself on low nodes + const nodePool = await SnodePool.getRandomSnodePool(); + if (nodePool.length < DESIRED_GUARD_COUNT) { + log.error( + 'Could not select guard nodes. Not enough nodes in the pool: ', + nodePool.length + ); + return []; + } + + const shuffled = _.shuffle(nodePool); + + let guardNodes: Array = []; + + // The use of await inside while is intentional: + // we only want to repeat if the await fails + // eslint-disable-next-line-no-await-in-loop + while (guardNodes.length < 3) { + if (shuffled.length < DESIRED_GUARD_COUNT) { + log.error('Not enought nodes in the pool'); + break; + } + + const candidateNodes = shuffled.splice(0, DESIRED_GUARD_COUNT); + + // Test all three nodes at once + // eslint-disable-next-line no-await-in-loop + const idxOk = await Promise.all( + candidateNodes.map(n => this.testGuardNode(n)) + ); + + const goodNodes = _.zip(idxOk, candidateNodes) + .filter(x => x[0]) + .map(x => x[1]) as Array; + + guardNodes = _.concat(guardNodes, goodNodes); + } + + if (guardNodes.length < DESIRED_GUARD_COUNT) { + log.error( + `COULD NOT get enough guard nodes, only have: ${guardNodes.length}` + ); + } + + log.info('new guard nodes: ', guardNodes); + + const edKeys = guardNodes.map(n => _.pick(n, 'pubkey_ed25519')); + + await window.libloki.storage.updateGuardNodes(edKeys); + + return guardNodes; + } + + private async buildNewOnionPathsWorker() { + const { log } = window; + + log.info('LokiSnodeAPI::buildNewOnionPaths - building new onion paths'); + + const allNodes = await SnodePool.getRandomSnodePool(); + + if (this.guardNodes.length === 0) { + // Not cached, load from DB + const nodes = await Data.getGuardNodes(); + + if (nodes.length === 0) { + log.warn( + 'LokiSnodeAPI::buildNewOnionPaths - no guard nodes in DB. Will be selecting new guards nodes...' + ); + } else { + // We only store the nodes' keys, need to find full entries: + const edKeys = nodes.map(x => x.ed25519PubKey); + this.guardNodes = allNodes.filter( + x => edKeys.indexOf(x.pubkey_ed25519) !== -1 + ); + + if (this.guardNodes.length < edKeys.length) { + log.warn( + `LokiSnodeAPI::buildNewOnionPaths - could not find some guard nodes: ${this.guardNodes.length}/${edKeys.length} left` + ); + } + } + + // If guard nodes is still empty (the old nodes are now invalid), select new ones: + if (this.guardNodes.length < MIN_GUARD_COUNT) { + // TODO: don't throw away potentially good guard nodes + this.guardNodes = await this.selectGuardNodes(); + } + } + + // TODO: select one guard node and 2 other nodes randomly + let otherNodes = _.difference(allNodes, this.guardNodes); + + if (otherNodes.length < 2) { + log.warn( + 'LokiSnodeAPI::buildNewOnionPaths - Too few nodes to build an onion path! Refreshing pool and retrying' + ); + await SnodePool.refreshRandomPool(); + await this.buildNewOnionPaths(); + return; + } + + otherNodes = _.shuffle(otherNodes); + const guards = _.shuffle(this.guardNodes); + + // Create path for every guard node: + const nodesNeededPerPaths = window.lokiFeatureFlags.onionRequestHops - 1; + + // Each path needs X (nodesNeededPerPaths) nodes in addition to the guard node: + const maxPath = Math.floor( + Math.min( + guards.length, + nodesNeededPerPaths + ? otherNodes.length / nodesNeededPerPaths + : otherNodes.length + ) + ); + + // TODO: might want to keep some of the existing paths + this.onionPaths = []; + + for (let i = 0; i < maxPath; i += 1) { + const path = [guards[i]]; + for (let j = 0; j < nodesNeededPerPaths; j += 1) { + path.push(otherNodes[i * nodesNeededPerPaths + j]); + } + this.onionPaths.push({ path, bad: false }); + } + + log.info(`Built ${this.onionPaths.length} onion paths`, this.onionPaths); + } +} + +export const OnionAPI = new OnionPaths(); diff --git a/ts/session/snode_api/onions.ts b/ts/session/snode_api/onions.ts index cfe7ca7ac..176f4c23b 100644 --- a/ts/session/snode_api/onions.ts +++ b/ts/session/snode_api/onions.ts @@ -4,8 +4,7 @@ import https from 'https'; import { Snode } from './snodePool'; import ByteBuffer from 'bytebuffer'; import { StringUtils } from '../utils'; - -const BAD_PATH = 'bad_path'; +import { OnionAPI } from '../onions'; enum RequestError { BAD_PATH, @@ -401,15 +400,15 @@ export async function lokiOnionFetch( body: any, targetNode: Snode ): Promise { - const { lokiSnodeAPI, log } = window; + const { log } = window; // Loop until the result is not BAD_PATH // tslint:disable-next-line no-constant-condition while (true) { // Get a path excluding `targetNode`: // eslint-disable-next-line no-await-in-loop - const path = await lokiSnodeAPI.getOnionPath(targetNode); - const thisIdx = lokiSnodeAPI.assignOnionRequestNumber(); + const path = await OnionAPI.getOnionPath(targetNode); + const thisIdx = OnionAPI.assignOnionRequestNumber(); // At this point I only care about BAD_PATH @@ -427,7 +426,7 @@ export async function lokiOnionFetch( targetNode.port }` ); - lokiSnodeAPI.markPathAsBad(path); + OnionAPI.markPathAsBad(path); return false; } else if (result === RequestError.OTHER) { // could mean, fail to parse results diff --git a/ts/session/snode_api/serviceNodeAPI.ts b/ts/session/snode_api/serviceNodeAPI.ts index 528fd5ca1..0cb783058 100644 --- a/ts/session/snode_api/serviceNodeAPI.ts +++ b/ts/session/snode_api/serviceNodeAPI.ts @@ -261,7 +261,7 @@ export async function storeOnNode( targetNode: Snode, params: SendParams ): Promise { - const { log, textsecure, lokiSnodeAPI } = window; + const { log, textsecure } = window; let successiveFailures = 0; while (successiveFailures < MAX_ACCEPTABLE_FAILURES) { From 6919f53a45ea954b56b83a3c0164846bc7f19ab1 Mon Sep 17 00:00:00 2001 From: Maxim Shishmarev Date: Tue, 7 Jul 2020 11:05:04 +1000 Subject: [PATCH 2/3] Move constants to preload.js --- preload.js | 2 ++ ts/session/onions/index.ts | 21 +++++++++------------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/preload.js b/preload.js index 377b757e2..a1c1a2bda 100644 --- a/preload.js +++ b/preload.js @@ -99,6 +99,8 @@ window.CONSTANTS = new (function() { // https://loki.network/2020/03/25/loki-name-system-the-facts/ this.LNS_REGEX = `^[a-zA-Z0-9_]([a-zA-Z0-9_-]{0,${this.LNS_MAX_LENGTH - 2}}[a-zA-Z0-9_]){0,1}$`; + this.MIN_GUARD_COUNT = 2; + this.DESIRED_GUARD_COUNT = 3; })(); window.versionInfo = { diff --git a/ts/session/onions/index.ts b/ts/session/onions/index.ts index 9fb8568e3..5f875fae7 100644 --- a/ts/session/onions/index.ts +++ b/ts/session/onions/index.ts @@ -6,8 +6,6 @@ import fetch from 'node-fetch'; type Snode = SnodePool.Snode; -const MIN_GUARD_COUNT = 2; - interface SnodePath { path: Array; bad: boolean; @@ -32,12 +30,12 @@ class OnionPaths { public async getOnionPath(toExclude?: { pubkey_ed25519: string; }): Promise> { - const { log } = window; + const { log, CONSTANTS } = window; let goodPaths = this.onionPaths.filter(x => !x.bad); let attemptNumber = 0; - while (goodPaths.length < MIN_GUARD_COUNT) { + while (goodPaths.length < CONSTANTS.MIN_GUARD_COUNT) { log.error( `Must have at least 2 good onion paths, actual: ${goodPaths.length}, attempt #${attemptNumber} fetching more...` ); @@ -172,12 +170,11 @@ class OnionPaths { } private async selectGuardNodes(): Promise> { - const { log } = window; + const { CONSTANTS, log } = window; - const DESIRED_GUARD_COUNT = 3; // `getRandomSnodePool` is expected to refresh itself on low nodes const nodePool = await SnodePool.getRandomSnodePool(); - if (nodePool.length < DESIRED_GUARD_COUNT) { + if (nodePool.length < CONSTANTS.DESIRED_GUARD_COUNT) { log.error( 'Could not select guard nodes. Not enough nodes in the pool: ', nodePool.length @@ -193,12 +190,12 @@ class OnionPaths { // we only want to repeat if the await fails // eslint-disable-next-line-no-await-in-loop while (guardNodes.length < 3) { - if (shuffled.length < DESIRED_GUARD_COUNT) { + if (shuffled.length < CONSTANTS.DESIRED_GUARD_COUNT) { log.error('Not enought nodes in the pool'); break; } - const candidateNodes = shuffled.splice(0, DESIRED_GUARD_COUNT); + const candidateNodes = shuffled.splice(0, CONSTANTS.DESIRED_GUARD_COUNT); // Test all three nodes at once // eslint-disable-next-line no-await-in-loop @@ -213,7 +210,7 @@ class OnionPaths { guardNodes = _.concat(guardNodes, goodNodes); } - if (guardNodes.length < DESIRED_GUARD_COUNT) { + if (guardNodes.length < CONSTANTS.DESIRED_GUARD_COUNT) { log.error( `COULD NOT get enough guard nodes, only have: ${guardNodes.length}` ); @@ -229,7 +226,7 @@ class OnionPaths { } private async buildNewOnionPathsWorker() { - const { log } = window; + const { CONSTANTS, log } = window; log.info('LokiSnodeAPI::buildNewOnionPaths - building new onion paths'); @@ -258,7 +255,7 @@ class OnionPaths { } // If guard nodes is still empty (the old nodes are now invalid), select new ones: - if (this.guardNodes.length < MIN_GUARD_COUNT) { + if (this.guardNodes.length < CONSTANTS.MIN_GUARD_COUNT) { // TODO: don't throw away potentially good guard nodes this.guardNodes = await this.selectGuardNodes(); } From ccc487528d7c32275521b2bb16370f7f2d7b0ae8 Mon Sep 17 00:00:00 2001 From: Maxim Shishmarev Date: Tue, 7 Jul 2020 15:34:56 +1000 Subject: [PATCH 3/3] Fix edkey extraction --- ts/receiver/dataMessage.ts | 8 +++++++- ts/receiver/receiver.ts | 3 +++ ts/session/onions/index.ts | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/ts/receiver/dataMessage.ts b/ts/receiver/dataMessage.ts index 653075b37..7589855cc 100644 --- a/ts/receiver/dataMessage.ts +++ b/ts/receiver/dataMessage.ts @@ -521,8 +521,14 @@ function sendDeliveryReceipt(source: string, timestamp: any) { // await getMessageQueue().sendUsingMultiDevice(device, receiptMessage); } +interface MessageEvent { + data: any; + type: string; + confirm: () => void; +} + // tslint:disable:cyclomatic-complexity max-func-body-length */ -export async function handleMessageEvent(event: any): Promise { +export async function handleMessageEvent(event: MessageEvent): Promise { const { data, confirm } = event; const isIncoming = event.type === 'message'; diff --git a/ts/receiver/receiver.ts b/ts/receiver/receiver.ts index 3b50cdb93..f5fe2c92f 100644 --- a/ts/receiver/receiver.ts +++ b/ts/receiver/receiver.ts @@ -325,6 +325,9 @@ export async function handleUnencryptedMessage({ message: outerMessage }: any) { // Public chat messages from ourselves should be outgoing type: isPublicChatMessage && isOurDevice ? 'sent' : 'message', data: outerMessage, + confirm: () => { + /* do nothing */ + }, }; await handleMessageEvent(ev); diff --git a/ts/session/onions/index.ts b/ts/session/onions/index.ts index 5f875fae7..53a2c1cd4 100644 --- a/ts/session/onions/index.ts +++ b/ts/session/onions/index.ts @@ -218,7 +218,7 @@ class OnionPaths { log.info('new guard nodes: ', guardNodes); - const edKeys = guardNodes.map(n => _.pick(n, 'pubkey_ed25519')); + const edKeys = guardNodes.map(n => n.pubkey_ed25519); await window.libloki.storage.updateGuardNodes(edKeys);