From 760002903765a5ed66994b5b3d8dffe3ea18366d Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 24 Feb 2020 14:15:31 -0400 Subject: [PATCH 01/21] typings --- services/gunDB/Mediator/index.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index 3bf642b6..6f4cc566 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -2,6 +2,7 @@ * @format */ const Gun = require('gun') +// @ts-ignore Gun.log = () => {} // @ts-ignore require('gun/lib/open') @@ -127,10 +128,17 @@ const Event = require('../event-constants') * @prop {Record} origBody */ +/** + * @typedef {object} EncryptedEmission + * @prop {string} encryptedData + * @prop {string} encryptedKey + * @prop {string} iv + */ + // TO DO: move to common repo /** * @typedef {object} SimpleSocket - * @prop {(eventName: string, data: Emission) => void} emit + * @prop {(eventName: string, data: Emission|EncryptedEmission) => void} emit * @prop {(eventName: string, handler: (data: any) => void) => void} on * @prop {{ query: { 'x-shockwallet-device-id': string }}} handshake */ From 9cf80a655aedd2498caaca3c9c8b2e1f0681bce3 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 24 Feb 2020 14:30:49 -0400 Subject: [PATCH 02/21] tryandwait routine --- services/gunDB/contact-api/utils/index.js | 57 +++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/services/gunDB/contact-api/utils/index.js b/services/gunDB/contact-api/utils/index.js index 1294f1c5..3dc8d71a 100644 --- a/services/gunDB/contact-api/utils/index.js +++ b/services/gunDB/contact-api/utils/index.js @@ -1,6 +1,8 @@ /** * @format */ +const logger = require('winston') + const ErrorCode = require('../errorCode') const Key = require('../key') @@ -37,19 +39,68 @@ const timeout10 = promise => { ]) } +/** + * @template T + * @param {Promise} promise + * @returns {Promise} + */ +const timeout5 = promise => { + return Promise.race([ + promise, + new Promise((_, rej) => { + setTimeout(() => { + rej(new Error(ErrorCode.TIMEOUT_ERR)) + }, 5000) + }) + ]) +} + /** * @template T * @param {(gun: GUNNode, user: UserGUNNode) => Promise} promGen The function * receives the most recent gun and user instances. * @returns {Promise} */ -const tryAndWait = promGen => - timeout10( +const tryAndWait = async promGen => { + /* eslint-disable no-empty */ + + // If hang stop at 10, wait 3, retry, if hang stop at 5, reinstate, warm for + // 5, retry, stop at 10, err + + try { + return await timeout10( + promGen( + require('../../Mediator/index').getGun(), + require('../../Mediator/index').getUser() + ) + ) + } catch (_) {} + + logger.info(`\n retrying \n`) + + await delay(3000) + + try { + return await timeout5( + promGen( + require('../../Mediator/index').getGun(), + require('../../Mediator/index').getUser() + ) + ) + } catch (_) {} + + logger.info(`\n recreating gun and retrying one last time \n`) + + await require('../../Mediator/index').instantiateGun() + + return timeout10( promGen( require('../../Mediator/index').getGun(), require('../../Mediator/index').getUser() ) ) + /* eslint-enable no-empty */ +} /** * @param {string} pub @@ -74,7 +125,7 @@ const pubToEpub = async pub => { return epub } catch (err) { - console.log(err) + logger.error(err) throw new Error(`pubToEpub() -> ${err.message}`) } } From 438c5d09c7a3ce609eac66b9c672b8704c534974 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 24 Feb 2020 15:08:16 -0400 Subject: [PATCH 03/21] log errors --- services/gunDB/contact-api/utils/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/services/gunDB/contact-api/utils/index.js b/services/gunDB/contact-api/utils/index.js index 3dc8d71a..c5bb369c 100644 --- a/services/gunDB/contact-api/utils/index.js +++ b/services/gunDB/contact-api/utils/index.js @@ -74,7 +74,9 @@ const tryAndWait = async promGen => { require('../../Mediator/index').getUser() ) ) - } catch (_) {} + } catch (e) { + logger.error(e) + } logger.info(`\n retrying \n`) @@ -87,7 +89,9 @@ const tryAndWait = async promGen => { require('../../Mediator/index').getUser() ) ) - } catch (_) {} + } catch (e) { + logger.error(e) + } logger.info(`\n recreating gun and retrying one last time \n`) From 6c8c7c27edacf5877a7d8ce8440c7ece1ee5115a Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 24 Feb 2020 15:11:17 -0400 Subject: [PATCH 04/21] use tryAndWait() --- services/gunDB/contact-api/utils/index.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/services/gunDB/contact-api/utils/index.js b/services/gunDB/contact-api/utils/index.js index c5bb369c..e1fef163 100644 --- a/services/gunDB/contact-api/utils/index.js +++ b/services/gunDB/contact-api/utils/index.js @@ -206,12 +206,18 @@ const recipientToOutgoingID = async recipientPub => { * @returns {Promise} */ const currHandshakeAddress = async userPub => { - const maybeAddr = await tryAndWait(gun => - gun + const maybeAddr = await tryAndWait(async gun => { + const addr = await gun .user(userPub) .get(Key.CURRENT_HANDSHAKE_ADDRESS) .then() - ) + + if (typeof addr !== 'string' && addr !== null) { + throw new TypeError('Expected handshake address to be string or null') + } + + return addr + }) return typeof maybeAddr === 'string' ? maybeAddr : null } From ad941bcdb18cc7c74bc8892d137953450f25cefa Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 24 Feb 2020 15:12:18 -0400 Subject: [PATCH 05/21] remove unused code --- services/gunDB/contact-api/utils/index.js | 40 ----------------------- 1 file changed, 40 deletions(-) diff --git a/services/gunDB/contact-api/utils/index.js b/services/gunDB/contact-api/utils/index.js index e1fef163..c3039323 100644 --- a/services/gunDB/contact-api/utils/index.js +++ b/services/gunDB/contact-api/utils/index.js @@ -200,28 +200,6 @@ const recipientToOutgoingID = async recipientPub => { return null } -/** - * - * @param {string} userPub - * @returns {Promise} - */ -const currHandshakeAddress = async userPub => { - const maybeAddr = await tryAndWait(async gun => { - const addr = await gun - .user(userPub) - .get(Key.CURRENT_HANDSHAKE_ADDRESS) - .then() - - if (typeof addr !== 'string' && addr !== null) { - throw new TypeError('Expected handshake address to be string or null') - } - - return addr - }) - - return typeof maybeAddr === 'string' ? maybeAddr : null -} - /** * @template T * @param {T[]} arr @@ -284,22 +262,6 @@ const dataHasSoul = listenerData => */ const defaultName = pub => 'anon' + pub.slice(0, 8) -/** - * @param {string} pub - * @param {string} incomingID - * @returns {Promise} - */ -const didDisconnect = async (pub, incomingID) => { - const feed = await require('../../Mediator/index') - .getGun() - .user(pub) - .get(Key.OUTGOINGS) - .get(incomingID) - .then() - - return feed === null -} - module.exports = { asyncMap, asyncFilter, @@ -310,10 +272,8 @@ module.exports = { recipientPubToLastReqSentID, successfulHandshakeAlreadyExists, recipientToOutgoingID, - currHandshakeAddress, tryAndWait, mySecret, promisifyGunNode: require('./promisifygun'), - didDisconnect, asyncForEach } From 9a9ea5e7a2a1a1f12d03a6763e3d274fd6eeb3e9 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 24 Feb 2020 15:28:21 -0400 Subject: [PATCH 06/21] use tryandwait() --- services/gunDB/contact-api/utils/index.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/services/gunDB/contact-api/utils/index.js b/services/gunDB/contact-api/utils/index.js index c3039323..18e37bfe 100644 --- a/services/gunDB/contact-api/utils/index.js +++ b/services/gunDB/contact-api/utils/index.js @@ -182,11 +182,20 @@ const successfulHandshakeAlreadyExists = async recipientPub => { * @returns {Promise} */ const recipientToOutgoingID = async recipientPub => { - const maybeEncryptedOutgoingID = await require('../../Mediator/index') - .getUser() - .get(Key.RECIPIENT_TO_OUTGOING) - .get(recipientPub) - .then() + const maybeEncryptedOutgoingID = await tryAndWait(async (_, user) => { + const oid = await user + .get(Key.RECIPIENT_TO_OUTGOING) + .get(recipientPub) + .then() + + if (typeof oid !== 'string' && oid !== null) { + throw new Error( + 'Expected outgoing id from recipient-to-outgoing-id map to be an string or null' + ) + } + + return oid + }) if (typeof maybeEncryptedOutgoingID === 'string') { const outgoingID = await require('../../Mediator/index').mySEA.decrypt( From cdf4f8339254394b9dc8eb8b6897c708ce4486d2 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 24 Feb 2020 18:19:22 -0400 Subject: [PATCH 07/21] stupid rule --- .eslintrc.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 8f0cc2e1..29ec4835 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -77,7 +77,9 @@ "line-comment-position": "off", // if someone does this it's probably intentional - "no-useless-concat": "off" + "no-useless-concat": "off", + + "no-plusplus": "off" }, "parser": "babel-eslint", "env": { From fcc1de962e71a663561e50ff28a3bbdf1dc9b897 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 24 Feb 2020 18:20:27 -0400 Subject: [PATCH 08/21] Revert "use tryandwait()" This reverts commit 9a9ea5e7a2a1a1f12d03a6763e3d274fd6eeb3e9. --- services/gunDB/contact-api/utils/index.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/services/gunDB/contact-api/utils/index.js b/services/gunDB/contact-api/utils/index.js index 18e37bfe..c3039323 100644 --- a/services/gunDB/contact-api/utils/index.js +++ b/services/gunDB/contact-api/utils/index.js @@ -182,20 +182,11 @@ const successfulHandshakeAlreadyExists = async recipientPub => { * @returns {Promise} */ const recipientToOutgoingID = async recipientPub => { - const maybeEncryptedOutgoingID = await tryAndWait(async (_, user) => { - const oid = await user - .get(Key.RECIPIENT_TO_OUTGOING) - .get(recipientPub) - .then() - - if (typeof oid !== 'string' && oid !== null) { - throw new Error( - 'Expected outgoing id from recipient-to-outgoing-id map to be an string or null' - ) - } - - return oid - }) + const maybeEncryptedOutgoingID = await require('../../Mediator/index') + .getUser() + .get(Key.RECIPIENT_TO_OUTGOING) + .get(recipientPub) + .then() if (typeof maybeEncryptedOutgoingID === 'string') { const outgoingID = await require('../../Mediator/index').mySEA.decrypt( From 7ef3a9cb7f1cd643548b61da75d4f8cf4c426f9a Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 24 Feb 2020 18:24:42 -0400 Subject: [PATCH 09/21] optional force retrying --- services/gunDB/contact-api/utils/index.js | 24 ++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/services/gunDB/contact-api/utils/index.js b/services/gunDB/contact-api/utils/index.js index c3039323..e3a16b1c 100644 --- a/services/gunDB/contact-api/utils/index.js +++ b/services/gunDB/contact-api/utils/index.js @@ -59,21 +59,32 @@ const timeout5 = promise => { * @template T * @param {(gun: GUNNode, user: UserGUNNode) => Promise} promGen The function * receives the most recent gun and user instances. + * @param {((resolvedValue: unknown) => boolean)=} shouldRetry * @returns {Promise} */ -const tryAndWait = async promGen => { +const tryAndWait = async (promGen, shouldRetry = () => false) => { /* eslint-disable no-empty */ + /* eslint-disable init-declarations */ // If hang stop at 10, wait 3, retry, if hang stop at 5, reinstate, warm for // 5, retry, stop at 10, err + /** @type {T} */ + let resolvedValue + try { - return await timeout10( + resolvedValue = await timeout10( promGen( require('../../Mediator/index').getGun(), require('../../Mediator/index').getUser() ) ) + + if (shouldRetry(resolvedValue)) { + throw new Error('force retrying') + } + + return resolvedValue } catch (e) { logger.error(e) } @@ -83,12 +94,18 @@ const tryAndWait = async promGen => { await delay(3000) try { - return await timeout5( + resolvedValue = await timeout5( promGen( require('../../Mediator/index').getGun(), require('../../Mediator/index').getUser() ) ) + + if (shouldRetry(resolvedValue)) { + throw new Error('force retrying') + } + + return resolvedValue } catch (e) { logger.error(e) } @@ -104,6 +121,7 @@ const tryAndWait = async promGen => { ) ) /* eslint-enable no-empty */ + /* eslint-enable init-declarations */ } /** From a26c00e03a32644bb896df97e6ae376103ae6dde Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 24 Feb 2020 18:29:28 -0400 Subject: [PATCH 10/21] force retry --- services/gunDB/contact-api/utils/index.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/services/gunDB/contact-api/utils/index.js b/services/gunDB/contact-api/utils/index.js index e3a16b1c..9e1683c8 100644 --- a/services/gunDB/contact-api/utils/index.js +++ b/services/gunDB/contact-api/utils/index.js @@ -159,18 +159,20 @@ const pubToEpub = async pub => { * @returns {Promise} */ const recipientPubToLastReqSentID = async recipientPub => { - const lastReqSentID = await tryAndWait(async (_, user) => { - const userToLastReqSent = user.get(Key.USER_TO_LAST_REQUEST_SENT) - const data = await userToLastReqSent.get(recipientPub).then() + const maybeLastReqSentID = await tryAndWait( + (_, user) => { + const userToLastReqSent = user.get(Key.USER_TO_LAST_REQUEST_SENT) + return userToLastReqSent.get(recipientPub).then() + }, + // retry on undefined, in case it is a false negative + v => typeof v === 'undefined' + ) - if (typeof data !== 'string') { - return null - } + if (typeof maybeLastReqSentID !== 'string') { + return null + } - return data - }) - - return lastReqSentID + return maybeLastReqSentID } /** From 1ec1ec8b7c824b4b70fd6e04b55a98a54f6ac114 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 24 Feb 2020 18:38:12 -0400 Subject: [PATCH 11/21] use retry routine with force retry --- services/gunDB/contact-api/utils/index.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/services/gunDB/contact-api/utils/index.js b/services/gunDB/contact-api/utils/index.js index 9e1683c8..a71ef8ec 100644 --- a/services/gunDB/contact-api/utils/index.js +++ b/services/gunDB/contact-api/utils/index.js @@ -202,11 +202,15 @@ const successfulHandshakeAlreadyExists = async recipientPub => { * @returns {Promise} */ const recipientToOutgoingID = async recipientPub => { - const maybeEncryptedOutgoingID = await require('../../Mediator/index') - .getUser() - .get(Key.RECIPIENT_TO_OUTGOING) - .get(recipientPub) - .then() + const maybeEncryptedOutgoingID = await tryAndWait( + (_, user) => + user + .get(Key.RECIPIENT_TO_OUTGOING) + .get(recipientPub) + .then(), + // force retry in case undefined is a false negative + v => typeof v === 'undefined' + ) if (typeof maybeEncryptedOutgoingID === 'string') { const outgoingID = await require('../../Mediator/index').mySEA.decrypt( From 89d1f1bacf7ae53c6f4ea976665f3c921aab911a Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 24 Feb 2020 18:39:38 -0400 Subject: [PATCH 12/21] retry on undefined --- .../gunDB/contact-api/jobs/onAcceptedRequests.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/services/gunDB/contact-api/jobs/onAcceptedRequests.js b/services/gunDB/contact-api/jobs/onAcceptedRequests.js index a44b11a0..958c5401 100644 --- a/services/gunDB/contact-api/jobs/onAcceptedRequests.js +++ b/services/gunDB/contact-api/jobs/onAcceptedRequests.js @@ -104,7 +104,7 @@ const onAcceptedRequests = (user, SEA) => { throw new TypeError("typeof feedID !== 'string'") } - const feedIDExistsOnRecipientsOutgoings = await Utils.tryAndWait( + const maybeFeedOnRecipientsOutgoings = await Utils.tryAndWait( gun => new Promise(res => { gun @@ -112,11 +112,17 @@ const onAcceptedRequests = (user, SEA) => { .get(Key.OUTGOINGS) .get(feedID) .once(feed => { - res(typeof feed !== 'undefined') + res(feed) }) - }) + }), + // retry on undefined, might be a false negative + v => typeof v === 'undefined' ) + const feedIDExistsOnRecipientsOutgoings = + typeof maybeFeedOnRecipientsOutgoings === 'object' && + maybeFeedOnRecipientsOutgoings !== null + if (!feedIDExistsOnRecipientsOutgoings) { return } From 0488e322b68e705fd796808251dce2983a58986a Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 24 Feb 2020 18:40:28 -0400 Subject: [PATCH 13/21] use util --- services/gunDB/contact-api/actions.js | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index 0235b296..af3942c4 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -57,22 +57,12 @@ const __createOutgoingFeed = async (withPublicKey, user, SEA) => { user._.sea ) - const maybeEncryptedForMeOutgoingFeedID = await Utils.tryAndWait( - (_, user) => - new Promise(res => { - user - .get(Key.RECIPIENT_TO_OUTGOING) - .get(withPublicKey) - .once(data => { - res(data) - }) - }) - ) + const maybeOutgoingID = await Utils.recipientToOutgoingID(withPublicKey) let outgoingFeedID = '' // if there was no stored outgoing, create an outgoing feed - if (typeof maybeEncryptedForMeOutgoingFeedID !== 'string') { + if (typeof maybeOutgoingID !== 'string') { /** @type {PartialOutgoing} */ const newPartialOutgoingFeed = { with: encryptedForMeRecipientPub @@ -138,18 +128,7 @@ const __createOutgoingFeed = async (withPublicKey, user, SEA) => { // otherwise decrypt stored outgoing else { - const decryptedOID = await SEA.decrypt( - maybeEncryptedForMeOutgoingFeedID, - mySecret - ) - - if (typeof decryptedOID !== 'string') { - throw new TypeError( - "__createOutgoingFeed() -> typeof decryptedOID !== 'string'" - ) - } - - outgoingFeedID = decryptedOID + outgoingFeedID = maybeOutgoingID } if (typeof outgoingFeedID === 'undefined') { From 466f12de16fe7b406f0c9efe831de6f89514a0fe Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 24 Feb 2020 18:40:42 -0400 Subject: [PATCH 14/21] retry on undefined --- services/gunDB/contact-api/actions.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index af3942c4..eb8413aa 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -518,8 +518,8 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => { const lastRequestIDSentToUser = maybeLastRequestIDSentToUser console.log('sendHR() -> before alreadyContactedOnCurrHandshakeNode') - /** @type {boolean} */ - const alreadyContactedOnCurrHandshakeNode = await Utils.tryAndWait( + + const hrInHandshakeNode = await Utils.tryAndWait( gun => new Promise(res => { gun @@ -527,11 +527,16 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => { .get(currentHandshakeAddress) .get(lastRequestIDSentToUser) .once(data => { - res(typeof data !== 'undefined') + res(data) }) - }) + }), + // force retry on undefined in case the undefined was a false negative + v => typeof v === 'undefined' ) + const alreadyContactedOnCurrHandshakeNode = + typeof hrInHandshakeNode !== 'undefined' + if (alreadyContactedOnCurrHandshakeNode) { throw new Error(ErrorCode.ALREADY_REQUESTED_HANDSHAKE) } From 8d35874c567be2135ae74fc99baf7e75f545fee9 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Tue, 25 Feb 2020 15:49:36 -0400 Subject: [PATCH 15/21] better error messages --- services/gunDB/Mediator/index.js | 46 +++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index 6f4cc566..94c34da1 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -90,17 +90,52 @@ mySEA.decrypt = (encMsg, secret) => { mySEA.secret = (recipientOrSenderEpub, recipientOrSenderSEA) => { if (typeof recipientOrSenderEpub !== 'string') { - throw new TypeError('epub has to be an string') + throw new TypeError( + 'epub has to be an string, args:' + + `${JSON.stringify(recipientOrSenderEpub)} -- ${JSON.stringify( + recipientOrSenderSEA + )}` + ) + } + if (recipientOrSenderEpub.length === 0) { + throw new TypeError( + 'epub has to be populated string, args: ' + + `${JSON.stringify(recipientOrSenderEpub)} -- ${JSON.stringify( + recipientOrSenderSEA + )}` + ) } if (typeof recipientOrSenderSEA !== 'object') { - throw new TypeError('sea has to be an object') + throw new TypeError( + 'sea has to be an object, args: ' + + `${JSON.stringify(recipientOrSenderEpub)} -- ${JSON.stringify( + recipientOrSenderSEA + )}` + ) } if (recipientOrSenderEpub === recipientOrSenderSEA.pub) { - throw new Error('Do not use pub for mysecret') + throw new Error( + 'Do not use pub for mysecret, args: ' + + `${JSON.stringify(recipientOrSenderEpub)} -- ${JSON.stringify( + recipientOrSenderSEA + )}` + ) } return SEAx.secret(recipientOrSenderEpub, recipientOrSenderSEA).then(sec => { if (typeof sec !== 'string') { - throw new TypeError('Could not generate secret') + throw new TypeError( + `Could not generate secret, args: ${JSON.stringify( + recipientOrSenderEpub + )} -- ${JSON.stringify(recipientOrSenderSEA)}` + ) + } + + if (sec.length === 0) { + throw new TypeError( + `SEA.secret returned an empty string!, args: ${JSON.stringify( + recipientOrSenderEpub + )} -- ${JSON.stringify(recipientOrSenderSEA)}` + ) } return sec @@ -237,9 +272,6 @@ const instantiateGun = async () => { user._.sea )) } - if (typeof mySecret !== 'string') { - throw new TypeError("typeof mySec !== 'string'") - } const _gun = new Gun({ axe: false, From 128ff6d02a1684518cfd72445393bacd9f740355 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Tue, 25 Feb 2020 17:35:10 -0400 Subject: [PATCH 16/21] use timeout in htto polls --- services/gunDB/contact-api/utils/index.js | 3 ++- src/routes.js | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/services/gunDB/contact-api/utils/index.js b/services/gunDB/contact-api/utils/index.js index a71ef8ec..c00b68f7 100644 --- a/services/gunDB/contact-api/utils/index.js +++ b/services/gunDB/contact-api/utils/index.js @@ -299,5 +299,6 @@ module.exports = { tryAndWait, mySecret, promisifyGunNode: require('./promisifygun'), - asyncForEach + asyncForEach, + timeout5 } diff --git a/src/routes.js b/src/routes.js index 4ee69623..a7ef7b46 100644 --- a/src/routes.js +++ b/src/routes.js @@ -1570,6 +1570,7 @@ module.exports = async ( const Events = require('../services/gunDB/contact-api/events') const user = require('../services/gunDB/Mediator').getUser() const Key = require('../services/gunDB/contact-api/key') + const {timeout5} = require('../services/gunDB/contact-api/utils') app.get(`/api/gun/${GunEvent.ON_RECEIVED_REQUESTS}`, (_, res) => { try { @@ -1619,7 +1620,7 @@ module.exports = async ( app.get(`/api/gun/${GunEvent.ON_AVATAR}`, async (_, res) => { try { res.json({ - data: await user.get(Key.PROFILE).get(Key.AVATAR).then() + data: await timeout5(user.get(Key.PROFILE).get(Key.AVATAR).then()) }) } catch (err) { res.status(500).json({ @@ -1631,7 +1632,7 @@ module.exports = async ( app.get(`/api/gun/${GunEvent.ON_DISPLAY_NAME}`, async (_, res) => { try { res.json({ - data: await user.get(Key.PROFILE).get(Key.DISPLAY_NAME).then() + data: await timeout5(user.get(Key.PROFILE).get(Key.DISPLAY_NAME).then()) }) } catch (err) { res.status(500).json({ @@ -1643,7 +1644,7 @@ module.exports = async ( app.get(`/api/gun/${GunEvent.ON_HANDSHAKE_ADDRESS}`, async (_, res) => { try { res.json({ - data: await user.get(Key.CURRENT_HANDSHAKE_ADDRESS).then() + data: await timeout5(user.get(Key.CURRENT_HANDSHAKE_ADDRESS).then()) }) } catch (err) { res.status(500).json({ From 6655dbdfcaef7d06d5b6d4106f3de938951c49cd Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Tue, 25 Feb 2020 17:37:46 -0400 Subject: [PATCH 17/21] better mySea --- services/gunDB/Mediator/index.js | 63 +++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index 94c34da1..2037ecf6 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -13,6 +13,8 @@ const logger = require('winston') /** @type {import('../contact-api/SimpleGUN').ISEA} */ // @ts-ignore const SEAx = require('gun/sea') +// @ts-ignore +SEAx.throw = true /** @type {import('../contact-api/SimpleGUN').ISEA} */ const mySEA = {} @@ -37,6 +39,20 @@ mySEA.encrypt = (msg, secret) => { ) } + if (typeof secret !== 'string') { + throw new TypeError( + `mySEA.encrypt() -> expected secret to be a an string, args: |msg| -- ${JSON.stringify( + secret + )}` + ) + } + + if (secret.length < 1) { + throw new TypeError( + `mySEA.encrypt() -> expected secret to be a populated string` + ) + } + // Avoid this: https://github.com/amark/gun/issues/804 and any other issues const sanitizedMsg = $$__SHOCKWALLET__MSG__ + msg @@ -88,7 +104,7 @@ mySEA.decrypt = (encMsg, secret) => { }) } -mySEA.secret = (recipientOrSenderEpub, recipientOrSenderSEA) => { +mySEA.secret = async (recipientOrSenderEpub, recipientOrSenderSEA) => { if (typeof recipientOrSenderEpub !== 'string') { throw new TypeError( 'epub has to be an string, args:' + @@ -113,6 +129,16 @@ mySEA.secret = (recipientOrSenderEpub, recipientOrSenderSEA) => { )}` ) } + + if (recipientOrSenderSEA === null) { + throw new TypeError( + 'sea has to be nont null, args: ' + + `${JSON.stringify(recipientOrSenderEpub)} -- ${JSON.stringify( + recipientOrSenderSEA + )}` + ) + } + if (recipientOrSenderEpub === recipientOrSenderSEA.pub) { throw new Error( 'Do not use pub for mysecret, args: ' + @@ -121,25 +147,26 @@ mySEA.secret = (recipientOrSenderEpub, recipientOrSenderSEA) => { )}` ) } - return SEAx.secret(recipientOrSenderEpub, recipientOrSenderSEA).then(sec => { - if (typeof sec !== 'string') { - throw new TypeError( - `Could not generate secret, args: ${JSON.stringify( - recipientOrSenderEpub - )} -- ${JSON.stringify(recipientOrSenderSEA)}` - ) - } - if (sec.length === 0) { - throw new TypeError( - `SEA.secret returned an empty string!, args: ${JSON.stringify( - recipientOrSenderEpub - )} -- ${JSON.stringify(recipientOrSenderSEA)}` - ) - } + const sec = await SEAx.secret(recipientOrSenderEpub, recipientOrSenderSEA) - return sec - }) + if (typeof sec !== 'string') { + throw new TypeError( + `Could not generate secret, args: ${JSON.stringify( + recipientOrSenderEpub + )} -- ${JSON.stringify(recipientOrSenderSEA)}` + ) + } + + if (sec.length === 0) { + throw new TypeError( + `SEA.secret returned an empty string!, args: ${JSON.stringify( + recipientOrSenderEpub + )} -- ${JSON.stringify(recipientOrSenderSEA)}` + ) + } + + return sec } const auth = require('../../auth/auth') From 4212ea4612438a0a9fad3418da5d97a0599f5500 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Tue, 25 Feb 2020 17:42:40 -0400 Subject: [PATCH 18/21] better handling of gun instances --- services/gunDB/Mediator/index.js | 123 ++++++++++++++++++------------- 1 file changed, 72 insertions(+), 51 deletions(-) diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index 2037ecf6..34b3612b 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -216,13 +216,16 @@ let user /* eslint-enable init-declarations */ -let _currentAlias = '' -let _currentPass = '' +/** @type {string|null} */ +let _currentAlias = null +/** @type {string|null} */ +let _currentPass = null -let mySec = '' +/** @type {string|null} */ +let mySec = null /** @returns {string} */ -const getMySecret = () => mySec +const getMySecret = () => /** @type {string} */ (mySec) let _isAuthenticating = false let _isRegistering = false @@ -231,18 +234,46 @@ const isAuthenticated = () => typeof user.is === 'object' && user.is !== null const isAuthenticating = () => _isAuthenticating const isRegistering = () => _isRegistering +const getGun = () => { + return gun +} + +const getUser = () => { + return user +} + /** * Returns a promise containing the public key of the newly created user. * @param {string} alias * @param {string} pass + * @param {UserGUNNode=} user * @returns {Promise} */ -const authenticate = async (alias, pass) => { +const authenticate = async (alias, pass, user = getUser()) => { + const isFreshGun = user !== getUser() + if (isFreshGun) { + const ack = await new Promise(res => { + user.auth(alias, pass, _ack => { + res(_ack) + }) + }) + + if (typeof ack.err === 'string') { + throw new Error(ack.err) + } else if (typeof ack.sea === 'object') { + API.Jobs.onAcceptedRequests(user, mySEA) + API.Jobs.onOrders(user, gun, mySEA) + + return ack.sea.pub + } else { + throw new Error('Unknown error.') + } + } + if (isAuthenticated()) { - const currAlias = user.is && user.is.alias - if (alias !== currAlias) { + if (alias !== _currentAlias) { throw new Error( - `Tried to re-authenticate with an alias different to that of stored one, tried: ${alias} - stored: ${currAlias}, logoff first if need to change aliases.` + `Tried to re-authenticate with an alias different to that of stored one, tried: ${alias} - stored: ${_currentAlias}, logoff first if need to change aliases.` ) } // move this to a subscription; implement off() ? todo @@ -272,7 +303,7 @@ const authenticate = async (alias, pass) => { } else if (typeof ack.sea === 'object') { mySec = await mySEA.secret(user._.sea.epub, user._.sea) - _currentAlias = user.is ? user.is.alias : '' + _currentAlias = alias _currentPass = await mySEA.encrypt(pass, mySec) await new Promise(res => setTimeout(res, 5000)) @@ -290,44 +321,44 @@ const logoff = () => { user.leave() } -const instantiateGun = async () => { - let mySecret = '' - - if (user && user.is) { - mySecret = /** @type {string} */ (await mySEA.secret( - user._.sea.epub, - user._.sea - )) - } - - const _gun = new Gun({ +const instantiateGun = () => { + const _gun = /** @type {unknown} */ (new Gun({ axe: false, peers: Config.PEERS - }) + })) - // please typescript - const __gun = /** @type {unknown} */ (_gun) + gun = /** @type {GUNNode} */ (_gun) - gun = /** @type {GUNNode} */ (__gun) - - // eslint-disable-next-line require-atomic-updates user = gun.user() - - if (_currentAlias && _currentPass) { - const pass = await mySEA.decrypt(_currentPass, mySecret) - - if (typeof pass !== 'string') { - throw new Error('could not decrypt stored in memory current pass') - } - - user.leave() - - await authenticate(_currentAlias, pass) - } } instantiateGun() +const freshGun = async () => { + const _gun = /** @type {unknown} */ (new Gun({ + axe: false, + peers: Config.PEERS + })) + + const gun = /** @type {GUNNode} */ (_gun) + + const user = gun.user() + + if (!_currentAlias || !_currentPass || !mySec) { + throw new Error('Called freshGun() without alias, pass and secret cached') + } + + const pass = await mySEA.decrypt(_currentPass, mySec) + + if (typeof pass !== 'string') { + throw new Error('could not decrypt stored in memory current pass') + } + + await authenticate(_currentAlias, pass, user) + + return { gun, user } +} + /** * @param {string} token * @returns {Promise} @@ -363,14 +394,6 @@ const throwOnInvalidToken = async token => { } } -const getGun = () => { - return gun -} - -const getUser = () => { - return user -} - class Mediator { /** * @param {Readonly} socket @@ -1211,9 +1234,7 @@ const register = async (alias, pass) => { // restart instances so write to user graph work, there's an issue with gun // (at least on node) where after initial user creation, writes to user graph // don't work - await instantiateGun() - - user.leave() + instantiateGun() return authenticate(alias, pass).then(async pub => { await API.Actions.setDisplayName('anon' + pub.slice(0, 8), user) @@ -1246,9 +1267,9 @@ module.exports = { isAuthenticating, isRegistering, register, - instantiateGun, getGun, getUser, mySEA, - getMySecret + getMySecret, + freshGun } From 19f14e6d83e22aa9f78c481682500364e97bb7d1 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Tue, 25 Feb 2020 17:43:28 -0400 Subject: [PATCH 19/21] No nested tryAndWait --- .../contact-api/jobs/onAcceptedRequests.js | 179 +++++++++--------- 1 file changed, 92 insertions(+), 87 deletions(-) diff --git a/services/gunDB/contact-api/jobs/onAcceptedRequests.js b/services/gunDB/contact-api/jobs/onAcceptedRequests.js index 958c5401..44832195 100644 --- a/services/gunDB/contact-api/jobs/onAcceptedRequests.js +++ b/services/gunDB/contact-api/jobs/onAcceptedRequests.js @@ -1,6 +1,8 @@ /** * @format */ +const logger = require('winston') + const ErrorCode = require('../errorCode') const Key = require('../key') const Schema = require('../schema') @@ -12,6 +14,8 @@ const Utils = require('../utils') * @typedef {import('../SimpleGUN').UserGUNNode} UserGUNNode */ +let procid = 0 + /** * @throws {Error} NOT_AUTH * @param {UserGUNNode} user @@ -23,17 +27,16 @@ const onAcceptedRequests = (user, SEA) => { throw new Error(ErrorCode.NOT_AUTH) } - const mySecret = require('../../Mediator').getMySecret() - - if (typeof mySecret !== 'string') { - console.log("Jobs.onAcceptedRequests() -> typeof mySecret !== 'string'") - return - } + procid++ user .get(Key.STORED_REQS) .map() .once(async (storedReq, id) => { + logger.info( + `------------------------------------\nPROCID:${procid}\n---------------------------------------` + ) + const mySecret = require('../../Mediator').getMySecret() try { if (!Schema.isStoredRequest(storedReq)) { throw new TypeError( @@ -70,99 +73,101 @@ const onAcceptedRequests = (user, SEA) => { return } + const gun = require('../../Mediator').getGun() + const user = require('../../Mediator').getUser() + const recipientEpub = await Utils.pubToEpub(recipientPub) const ourSecret = await SEA.secret(recipientEpub, user._.sea) - if (typeof ourSecret !== 'string') { - throw new TypeError("typeof ourSecret !== 'string'") - } - - await Utils.tryAndWait( - (gun, user) => - new Promise((res, rej) => { - gun - .get(Key.HANDSHAKE_NODES) - .get(requestAddress) - .get(sentReqID) - .on(async sentReq => { - if (!Schema.isHandshakeRequest(sentReq)) { - rej( - new Error( - 'sent request found in handshake node not a handshake request' - ) - ) - return - } - - // The response can be decrypted with the same secret regardless of who - // wrote to it last (see HandshakeRequest definition). - // This could be our feed ID for the recipient, or the recipient's feed - // id if he accepted the request. - const feedID = await SEA.decrypt(sentReq.response, ourSecret) - - if (typeof feedID !== 'string') { - throw new TypeError("typeof feedID !== 'string'") - } - - const maybeFeedOnRecipientsOutgoings = await Utils.tryAndWait( - gun => - new Promise(res => { - gun - .user(recipientPub) - .get(Key.OUTGOINGS) - .get(feedID) - .once(feed => { - res(feed) - }) - }), - // retry on undefined, might be a false negative - v => typeof v === 'undefined' + await new Promise((res, rej) => { + gun + .get(Key.HANDSHAKE_NODES) + .get(requestAddress) + .get(sentReqID) + .on(async sentReq => { + if (!Schema.isHandshakeRequest(sentReq)) { + rej( + new Error( + 'sent request found in handshake node not a handshake request' ) + ) + return + } - const feedIDExistsOnRecipientsOutgoings = - typeof maybeFeedOnRecipientsOutgoings === 'object' && - maybeFeedOnRecipientsOutgoings !== null + // The response can be decrypted with the same secret regardless of who + // wrote to it last (see HandshakeRequest definition). + // This could be our feed ID for the recipient, or the recipient's feed + // id if he accepted the request. + const feedID = await SEA.decrypt(sentReq.response, ourSecret) - if (!feedIDExistsOnRecipientsOutgoings) { - return - } + if (typeof feedID !== 'string') { + throw new TypeError("typeof feedID !== 'string'") + } - const encryptedForMeIncomingID = await SEA.encrypt( - feedID, - mySecret - ) + logger.info(`onAcceptedRequests -> decrypted feed ID: ${feedID}`) - await new Promise((res, rej) => { - user - .get(Key.USER_TO_INCOMING) - .get(recipientPub) - .put(encryptedForMeIncomingID, ack => { - if (ack.err) { - rej(new Error(ack.err)) - } else { - res() - } + logger.info( + 'Will now try to access the other users outgoing feed' + ) + + const maybeFeedOnRecipientsOutgoings = await Utils.tryAndWait( + gun => + new Promise(res => { + gun + .user(recipientPub) + .get(Key.OUTGOINGS) + .get(feedID) + .once(feed => { + res(feed) }) - }) + }), + // retry on undefined, might be a false negative + v => typeof v === 'undefined' + ) - await new Promise((res, rej) => { - user - .get(Key.STORED_REQS) - .get(id) - .put(null, ack => { - if (ack.err) { - rej(new Error(ack.err)) - } else { - res() - } - }) - }) + const feedIDExistsOnRecipientsOutgoings = + typeof maybeFeedOnRecipientsOutgoings === 'object' && + maybeFeedOnRecipientsOutgoings !== null - // ensure this listeners gets called at least once - res() - }) + if (!feedIDExistsOnRecipientsOutgoings) { + return + } + + const encryptedForMeIncomingID = await SEA.encrypt( + feedID, + mySecret + ) + + await new Promise((res, rej) => { + user + .get(Key.USER_TO_INCOMING) + .get(recipientPub) + .put(encryptedForMeIncomingID, ack => { + if (ack.err) { + rej(new Error(ack.err)) + } else { + res() + } + }) + }) + + await new Promise((res, rej) => { + user + .get(Key.STORED_REQS) + .get(id) + .put(null, ack => { + if (ack.err) { + rej(new Error(ack.err)) + } else { + res() + } + }) + }) + + // ensure this listeners gets called at least once + res() }) - ) + }) } catch (err) { console.warn(`Jobs.onAcceptedRequests() -> ${err.message}`) console.log(err) From c6f8e37c5fdbbf889d51ed0c90906f31f7ad29fc Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Tue, 25 Feb 2020 17:44:04 -0400 Subject: [PATCH 20/21] use freshgun for tryAndWait --- services/gunDB/contact-api/utils/index.js | 39 +++++++++++++---------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/services/gunDB/contact-api/utils/index.js b/services/gunDB/contact-api/utils/index.js index c00b68f7..4a97b0d9 100644 --- a/services/gunDB/contact-api/utils/index.js +++ b/services/gunDB/contact-api/utils/index.js @@ -81,15 +81,21 @@ const tryAndWait = async (promGen, shouldRetry = () => false) => { ) if (shouldRetry(resolvedValue)) { - throw new Error('force retrying') + logger.info( + 'force retrying' + + ` args: ${promGen.toString()} -- ${shouldRetry.toString()}` + ) + } else { + return resolvedValue } - - return resolvedValue } catch (e) { logger.error(e) } - logger.info(`\n retrying \n`) + logger.info( + `\n retrying \n` + + ` args: ${promGen.toString()} -- ${shouldRetry.toString()}` + ) await delay(3000) @@ -102,24 +108,25 @@ const tryAndWait = async (promGen, shouldRetry = () => false) => { ) if (shouldRetry(resolvedValue)) { - throw new Error('force retrying') + logger.info( + 'force retrying' + + ` args: ${promGen.toString()} -- ${shouldRetry.toString()}` + ) + } else { + return resolvedValue } - - return resolvedValue } catch (e) { logger.error(e) } - logger.info(`\n recreating gun and retrying one last time \n`) - - await require('../../Mediator/index').instantiateGun() - - return timeout10( - promGen( - require('../../Mediator/index').getGun(), - require('../../Mediator/index').getUser() - ) + logger.info( + `\n recreating a fresh gun and retrying one last time \n` + + ` args: ${promGen.toString()} -- ${shouldRetry.toString()}` ) + + const { gun, user } = await require('../../Mediator/index').freshGun() + + return timeout10(promGen(gun, user)) /* eslint-enable no-empty */ /* eslint-enable init-declarations */ } From f90331fcfafe26d2353a2d3621326a7f1e4ebaef Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Tue, 25 Feb 2020 19:12:34 -0400 Subject: [PATCH 21/21] avoid a memory by not attaching jobs to a one time use gun instance --- services/gunDB/Mediator/index.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index 34b3612b..b28867f9 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -261,9 +261,6 @@ const authenticate = async (alias, pass, user = getUser()) => { if (typeof ack.err === 'string') { throw new Error(ack.err) } else if (typeof ack.sea === 'object') { - API.Jobs.onAcceptedRequests(user, mySEA) - API.Jobs.onOrders(user, gun, mySEA) - return ack.sea.pub } else { throw new Error('Unknown error.')