commit
266c43c9a4
6 changed files with 375 additions and 239 deletions
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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<string, any>} 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<string>}
|
||||
*/
|
||||
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<boolean>}
|
||||
|
|
@ -296,14 +391,6 @@ const throwOnInvalidToken = async token => {
|
|||
}
|
||||
}
|
||||
|
||||
const getGun = () => {
|
||||
return gun
|
||||
}
|
||||
|
||||
const getUser = () => {
|
||||
return user
|
||||
}
|
||||
|
||||
class Mediator {
|
||||
/**
|
||||
* @param {Readonly<SimpleSocket>} 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<T>} promGen The function
|
||||
* receives the most recent gun and user instances.
|
||||
* @param {Promise<T>} promise
|
||||
* @returns {Promise<T>}
|
||||
*/
|
||||
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<T>} promGen The function
|
||||
* receives the most recent gun and user instances.
|
||||
* @param {((resolvedValue: unknown) => boolean)=} shouldRetry
|
||||
* @returns {Promise<T>}
|
||||
*/
|
||||
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<string>}
|
||||
|
|
@ -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<string|null>}
|
||||
*/
|
||||
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<string|null>}
|
||||
*/
|
||||
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<string|null>}
|
||||
*/
|
||||
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<boolean>}
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue