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": { diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index 3bf642b6..b28867f9 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') @@ -12,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 = {} @@ -36,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 @@ -87,23 +104,69 @@ 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') + 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') - } - return SEAx.secret(recipientOrSenderEpub, recipientOrSenderSEA).then(sec => { - if (typeof sec !== 'string') { - throw new TypeError('Could not generate secret') - } - return sec - }) + 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: ' + + `${JSON.stringify(recipientOrSenderEpub)} -- ${JSON.stringify( + recipientOrSenderSEA + )}` + ) + } + + const sec = await SEAx.secret(recipientOrSenderEpub, recipientOrSenderSEA) + + 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') @@ -127,10 +190,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 */ @@ -146,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 @@ -161,18 +234,43 @@ 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') { + 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 @@ -202,7 +300,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)) @@ -220,47 +318,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 - )) - } - if (typeof mySecret !== 'string') { - throw new TypeError("typeof mySec !== 'string'") - } - - 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} @@ -296,14 +391,6 @@ const throwOnInvalidToken = async token => { } } -const getGun = () => { - return gun -} - -const getUser = () => { - return user -} - class Mediator { /** * @param {Readonly} socket @@ -1144,9 +1231,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) @@ -1179,9 +1264,9 @@ module.exports = { isAuthenticating, isRegistering, register, - instantiateGun, getGun, getUser, mySEA, - getMySecret + getMySecret, + freshGun } diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index 0235b296..eb8413aa 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') { @@ -539,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 @@ -548,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) } diff --git a/services/gunDB/contact-api/jobs/onAcceptedRequests.js b/services/gunDB/contact-api/jobs/onAcceptedRequests.js index a44b11a0..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,93 +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 feedIDExistsOnRecipientsOutgoings = await Utils.tryAndWait( - gun => - new Promise(res => { - gun - .user(recipientPub) - .get(Key.OUTGOINGS) - .get(feedID) - .once(feed => { - res(typeof feed !== '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 + } - if (!feedIDExistsOnRecipientsOutgoings) { - 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) - const encryptedForMeIncomingID = await SEA.encrypt( - feedID, - mySecret - ) + if (typeof feedID !== 'string') { + throw new TypeError("typeof feedID !== 'string'") + } - 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(`onAcceptedRequests -> decrypted feed ID: ${feedID}`) + + 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) diff --git a/services/gunDB/contact-api/utils/index.js b/services/gunDB/contact-api/utils/index.js index 1294f1c5..4a97b0d9 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') @@ -39,18 +41,96 @@ const timeout10 = promise => { /** * @template T - * @param {(gun: GUNNode, user: UserGUNNode) => Promise} promGen The function - * receives the most recent gun and user instances. + * @param {Promise} promise * @returns {Promise} */ -const tryAndWait = promGen => - timeout10( - promGen( - require('../../Mediator/index').getGun(), - require('../../Mediator/index').getUser() +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. + * @param {((resolvedValue: unknown) => boolean)=} shouldRetry + * @returns {Promise} + */ +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 { + resolvedValue = await timeout10( + promGen( + require('../../Mediator/index').getGun(), + require('../../Mediator/index').getUser() + ) ) + + if (shouldRetry(resolvedValue)) { + logger.info( + 'force retrying' + + ` args: ${promGen.toString()} -- ${shouldRetry.toString()}` + ) + } else { + return resolvedValue + } + } catch (e) { + logger.error(e) + } + + logger.info( + `\n retrying \n` + + ` args: ${promGen.toString()} -- ${shouldRetry.toString()}` ) + await delay(3000) + + try { + resolvedValue = await timeout5( + promGen( + require('../../Mediator/index').getGun(), + require('../../Mediator/index').getUser() + ) + ) + + if (shouldRetry(resolvedValue)) { + logger.info( + 'force retrying' + + ` args: ${promGen.toString()} -- ${shouldRetry.toString()}` + ) + } else { + return resolvedValue + } + } catch (e) { + logger.error(e) + } + + 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 */ +} + /** * @param {string} pub * @returns {Promise} @@ -74,7 +154,7 @@ const pubToEpub = async pub => { return epub } catch (err) { - console.log(err) + logger.error(err) throw new Error(`pubToEpub() -> ${err.message}`) } } @@ -86,18 +166,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 } /** @@ -127,11 +209,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( @@ -145,22 +231,6 @@ const recipientToOutgoingID = async recipientPub => { return null } -/** - * - * @param {string} userPub - * @returns {Promise} - */ -const currHandshakeAddress = async userPub => { - const maybeAddr = await tryAndWait(gun => - gun - .user(userPub) - .get(Key.CURRENT_HANDSHAKE_ADDRESS) - .then() - ) - - return typeof maybeAddr === 'string' ? maybeAddr : null -} - /** * @template T * @param {T[]} arr @@ -223,22 +293,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, @@ -249,10 +303,9 @@ module.exports = { recipientPubToLastReqSentID, successfulHandshakeAlreadyExists, recipientToOutgoingID, - currHandshakeAddress, tryAndWait, mySecret, promisifyGunNode: require('./promisifygun'), - didDisconnect, - 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({