commit
266c43c9a4
6 changed files with 375 additions and 239 deletions
|
|
@ -77,7 +77,9 @@
|
||||||
"line-comment-position": "off",
|
"line-comment-position": "off",
|
||||||
|
|
||||||
// if someone does this it's probably intentional
|
// if someone does this it's probably intentional
|
||||||
"no-useless-concat": "off"
|
"no-useless-concat": "off",
|
||||||
|
|
||||||
|
"no-plusplus": "off"
|
||||||
},
|
},
|
||||||
"parser": "babel-eslint",
|
"parser": "babel-eslint",
|
||||||
"env": {
|
"env": {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
const Gun = require('gun')
|
const Gun = require('gun')
|
||||||
|
// @ts-ignore
|
||||||
Gun.log = () => {}
|
Gun.log = () => {}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
require('gun/lib/open')
|
require('gun/lib/open')
|
||||||
|
|
@ -12,6 +13,8 @@ const logger = require('winston')
|
||||||
/** @type {import('../contact-api/SimpleGUN').ISEA} */
|
/** @type {import('../contact-api/SimpleGUN').ISEA} */
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const SEAx = require('gun/sea')
|
const SEAx = require('gun/sea')
|
||||||
|
// @ts-ignore
|
||||||
|
SEAx.throw = true
|
||||||
|
|
||||||
/** @type {import('../contact-api/SimpleGUN').ISEA} */
|
/** @type {import('../contact-api/SimpleGUN').ISEA} */
|
||||||
const mySEA = {}
|
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
|
// Avoid this: https://github.com/amark/gun/issues/804 and any other issues
|
||||||
const sanitizedMsg = $$__SHOCKWALLET__MSG__ + msg
|
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') {
|
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') {
|
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')
|
const auth = require('../../auth/auth')
|
||||||
|
|
@ -127,10 +190,17 @@ const Event = require('../event-constants')
|
||||||
* @prop {Record<string, any>} origBody
|
* @prop {Record<string, any>} origBody
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} EncryptedEmission
|
||||||
|
* @prop {string} encryptedData
|
||||||
|
* @prop {string} encryptedKey
|
||||||
|
* @prop {string} iv
|
||||||
|
*/
|
||||||
|
|
||||||
// TO DO: move to common repo
|
// TO DO: move to common repo
|
||||||
/**
|
/**
|
||||||
* @typedef {object} SimpleSocket
|
* @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 {(eventName: string, handler: (data: any) => void) => void} on
|
||||||
* @prop {{ query: { 'x-shockwallet-device-id': string }}} handshake
|
* @prop {{ query: { 'x-shockwallet-device-id': string }}} handshake
|
||||||
*/
|
*/
|
||||||
|
|
@ -146,13 +216,16 @@ let user
|
||||||
|
|
||||||
/* eslint-enable init-declarations */
|
/* eslint-enable init-declarations */
|
||||||
|
|
||||||
let _currentAlias = ''
|
/** @type {string|null} */
|
||||||
let _currentPass = ''
|
let _currentAlias = null
|
||||||
|
/** @type {string|null} */
|
||||||
|
let _currentPass = null
|
||||||
|
|
||||||
let mySec = ''
|
/** @type {string|null} */
|
||||||
|
let mySec = null
|
||||||
|
|
||||||
/** @returns {string} */
|
/** @returns {string} */
|
||||||
const getMySecret = () => mySec
|
const getMySecret = () => /** @type {string} */ (mySec)
|
||||||
|
|
||||||
let _isAuthenticating = false
|
let _isAuthenticating = false
|
||||||
let _isRegistering = false
|
let _isRegistering = false
|
||||||
|
|
@ -161,18 +234,43 @@ const isAuthenticated = () => typeof user.is === 'object' && user.is !== null
|
||||||
const isAuthenticating = () => _isAuthenticating
|
const isAuthenticating = () => _isAuthenticating
|
||||||
const isRegistering = () => _isRegistering
|
const isRegistering = () => _isRegistering
|
||||||
|
|
||||||
|
const getGun = () => {
|
||||||
|
return gun
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUser = () => {
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a promise containing the public key of the newly created user.
|
* Returns a promise containing the public key of the newly created user.
|
||||||
* @param {string} alias
|
* @param {string} alias
|
||||||
* @param {string} pass
|
* @param {string} pass
|
||||||
|
* @param {UserGUNNode=} user
|
||||||
* @returns {Promise<string>}
|
* @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()) {
|
if (isAuthenticated()) {
|
||||||
const currAlias = user.is && user.is.alias
|
if (alias !== _currentAlias) {
|
||||||
if (alias !== currAlias) {
|
|
||||||
throw new Error(
|
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
|
// move this to a subscription; implement off() ? todo
|
||||||
|
|
@ -202,7 +300,7 @@ const authenticate = async (alias, pass) => {
|
||||||
} else if (typeof ack.sea === 'object') {
|
} else if (typeof ack.sea === 'object') {
|
||||||
mySec = await mySEA.secret(user._.sea.epub, user._.sea)
|
mySec = await mySEA.secret(user._.sea.epub, user._.sea)
|
||||||
|
|
||||||
_currentAlias = user.is ? user.is.alias : ''
|
_currentAlias = alias
|
||||||
_currentPass = await mySEA.encrypt(pass, mySec)
|
_currentPass = await mySEA.encrypt(pass, mySec)
|
||||||
|
|
||||||
await new Promise(res => setTimeout(res, 5000))
|
await new Promise(res => setTimeout(res, 5000))
|
||||||
|
|
@ -220,47 +318,44 @@ const logoff = () => {
|
||||||
user.leave()
|
user.leave()
|
||||||
}
|
}
|
||||||
|
|
||||||
const instantiateGun = async () => {
|
const instantiateGun = () => {
|
||||||
let mySecret = ''
|
const _gun = /** @type {unknown} */ (new Gun({
|
||||||
|
|
||||||
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({
|
|
||||||
axe: false,
|
axe: false,
|
||||||
peers: Config.PEERS
|
peers: Config.PEERS
|
||||||
})
|
}))
|
||||||
|
|
||||||
// please typescript
|
gun = /** @type {GUNNode} */ (_gun)
|
||||||
const __gun = /** @type {unknown} */ (_gun)
|
|
||||||
|
|
||||||
gun = /** @type {GUNNode} */ (__gun)
|
|
||||||
|
|
||||||
// eslint-disable-next-line require-atomic-updates
|
|
||||||
user = gun.user()
|
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()
|
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
|
* @param {string} token
|
||||||
* @returns {Promise<boolean>}
|
* @returns {Promise<boolean>}
|
||||||
|
|
@ -296,14 +391,6 @@ const throwOnInvalidToken = async token => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getGun = () => {
|
|
||||||
return gun
|
|
||||||
}
|
|
||||||
|
|
||||||
const getUser = () => {
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
|
|
||||||
class Mediator {
|
class Mediator {
|
||||||
/**
|
/**
|
||||||
* @param {Readonly<SimpleSocket>} socket
|
* @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
|
// 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
|
// (at least on node) where after initial user creation, writes to user graph
|
||||||
// don't work
|
// don't work
|
||||||
await instantiateGun()
|
instantiateGun()
|
||||||
|
|
||||||
user.leave()
|
|
||||||
|
|
||||||
return authenticate(alias, pass).then(async pub => {
|
return authenticate(alias, pass).then(async pub => {
|
||||||
await API.Actions.setDisplayName('anon' + pub.slice(0, 8), user)
|
await API.Actions.setDisplayName('anon' + pub.slice(0, 8), user)
|
||||||
|
|
@ -1179,9 +1264,9 @@ module.exports = {
|
||||||
isAuthenticating,
|
isAuthenticating,
|
||||||
isRegistering,
|
isRegistering,
|
||||||
register,
|
register,
|
||||||
instantiateGun,
|
|
||||||
getGun,
|
getGun,
|
||||||
getUser,
|
getUser,
|
||||||
mySEA,
|
mySEA,
|
||||||
getMySecret
|
getMySecret,
|
||||||
|
freshGun
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,22 +57,12 @@ const __createOutgoingFeed = async (withPublicKey, user, SEA) => {
|
||||||
user._.sea
|
user._.sea
|
||||||
)
|
)
|
||||||
|
|
||||||
const maybeEncryptedForMeOutgoingFeedID = await Utils.tryAndWait(
|
const maybeOutgoingID = await Utils.recipientToOutgoingID(withPublicKey)
|
||||||
(_, user) =>
|
|
||||||
new Promise(res => {
|
|
||||||
user
|
|
||||||
.get(Key.RECIPIENT_TO_OUTGOING)
|
|
||||||
.get(withPublicKey)
|
|
||||||
.once(data => {
|
|
||||||
res(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
let outgoingFeedID = ''
|
let outgoingFeedID = ''
|
||||||
|
|
||||||
// if there was no stored outgoing, create an outgoing feed
|
// if there was no stored outgoing, create an outgoing feed
|
||||||
if (typeof maybeEncryptedForMeOutgoingFeedID !== 'string') {
|
if (typeof maybeOutgoingID !== 'string') {
|
||||||
/** @type {PartialOutgoing} */
|
/** @type {PartialOutgoing} */
|
||||||
const newPartialOutgoingFeed = {
|
const newPartialOutgoingFeed = {
|
||||||
with: encryptedForMeRecipientPub
|
with: encryptedForMeRecipientPub
|
||||||
|
|
@ -138,18 +128,7 @@ const __createOutgoingFeed = async (withPublicKey, user, SEA) => {
|
||||||
|
|
||||||
// otherwise decrypt stored outgoing
|
// otherwise decrypt stored outgoing
|
||||||
else {
|
else {
|
||||||
const decryptedOID = await SEA.decrypt(
|
outgoingFeedID = maybeOutgoingID
|
||||||
maybeEncryptedForMeOutgoingFeedID,
|
|
||||||
mySecret
|
|
||||||
)
|
|
||||||
|
|
||||||
if (typeof decryptedOID !== 'string') {
|
|
||||||
throw new TypeError(
|
|
||||||
"__createOutgoingFeed() -> typeof decryptedOID !== 'string'"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
outgoingFeedID = decryptedOID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof outgoingFeedID === 'undefined') {
|
if (typeof outgoingFeedID === 'undefined') {
|
||||||
|
|
@ -539,8 +518,8 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => {
|
||||||
const lastRequestIDSentToUser = maybeLastRequestIDSentToUser
|
const lastRequestIDSentToUser = maybeLastRequestIDSentToUser
|
||||||
|
|
||||||
console.log('sendHR() -> before alreadyContactedOnCurrHandshakeNode')
|
console.log('sendHR() -> before alreadyContactedOnCurrHandshakeNode')
|
||||||
/** @type {boolean} */
|
|
||||||
const alreadyContactedOnCurrHandshakeNode = await Utils.tryAndWait(
|
const hrInHandshakeNode = await Utils.tryAndWait(
|
||||||
gun =>
|
gun =>
|
||||||
new Promise(res => {
|
new Promise(res => {
|
||||||
gun
|
gun
|
||||||
|
|
@ -548,11 +527,16 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => {
|
||||||
.get(currentHandshakeAddress)
|
.get(currentHandshakeAddress)
|
||||||
.get(lastRequestIDSentToUser)
|
.get(lastRequestIDSentToUser)
|
||||||
.once(data => {
|
.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) {
|
if (alreadyContactedOnCurrHandshakeNode) {
|
||||||
throw new Error(ErrorCode.ALREADY_REQUESTED_HANDSHAKE)
|
throw new Error(ErrorCode.ALREADY_REQUESTED_HANDSHAKE)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
/**
|
/**
|
||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
const logger = require('winston')
|
||||||
|
|
||||||
const ErrorCode = require('../errorCode')
|
const ErrorCode = require('../errorCode')
|
||||||
const Key = require('../key')
|
const Key = require('../key')
|
||||||
const Schema = require('../schema')
|
const Schema = require('../schema')
|
||||||
|
|
@ -12,6 +14,8 @@ const Utils = require('../utils')
|
||||||
* @typedef {import('../SimpleGUN').UserGUNNode} UserGUNNode
|
* @typedef {import('../SimpleGUN').UserGUNNode} UserGUNNode
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
let procid = 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws {Error} NOT_AUTH
|
* @throws {Error} NOT_AUTH
|
||||||
* @param {UserGUNNode} user
|
* @param {UserGUNNode} user
|
||||||
|
|
@ -23,17 +27,16 @@ const onAcceptedRequests = (user, SEA) => {
|
||||||
throw new Error(ErrorCode.NOT_AUTH)
|
throw new Error(ErrorCode.NOT_AUTH)
|
||||||
}
|
}
|
||||||
|
|
||||||
const mySecret = require('../../Mediator').getMySecret()
|
procid++
|
||||||
|
|
||||||
if (typeof mySecret !== 'string') {
|
|
||||||
console.log("Jobs.onAcceptedRequests() -> typeof mySecret !== 'string'")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user
|
user
|
||||||
.get(Key.STORED_REQS)
|
.get(Key.STORED_REQS)
|
||||||
.map()
|
.map()
|
||||||
.once(async (storedReq, id) => {
|
.once(async (storedReq, id) => {
|
||||||
|
logger.info(
|
||||||
|
`------------------------------------\nPROCID:${procid}\n---------------------------------------`
|
||||||
|
)
|
||||||
|
const mySecret = require('../../Mediator').getMySecret()
|
||||||
try {
|
try {
|
||||||
if (!Schema.isStoredRequest(storedReq)) {
|
if (!Schema.isStoredRequest(storedReq)) {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
|
|
@ -70,93 +73,101 @@ const onAcceptedRequests = (user, SEA) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const gun = require('../../Mediator').getGun()
|
||||||
|
const user = require('../../Mediator').getUser()
|
||||||
|
|
||||||
const recipientEpub = await Utils.pubToEpub(recipientPub)
|
const recipientEpub = await Utils.pubToEpub(recipientPub)
|
||||||
const ourSecret = await SEA.secret(recipientEpub, user._.sea)
|
const ourSecret = await SEA.secret(recipientEpub, user._.sea)
|
||||||
|
|
||||||
if (typeof ourSecret !== 'string') {
|
await new Promise((res, rej) => {
|
||||||
throw new TypeError("typeof ourSecret !== 'string'")
|
gun
|
||||||
}
|
.get(Key.HANDSHAKE_NODES)
|
||||||
|
.get(requestAddress)
|
||||||
await Utils.tryAndWait(
|
.get(sentReqID)
|
||||||
(gun, user) =>
|
.on(async sentReq => {
|
||||||
new Promise((res, rej) => {
|
if (!Schema.isHandshakeRequest(sentReq)) {
|
||||||
gun
|
rej(
|
||||||
.get(Key.HANDSHAKE_NODES)
|
new Error(
|
||||||
.get(requestAddress)
|
'sent request found in handshake node not a handshake request'
|
||||||
.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')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!feedIDExistsOnRecipientsOutgoings) {
|
// The response can be decrypted with the same secret regardless of who
|
||||||
return
|
// 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(
|
if (typeof feedID !== 'string') {
|
||||||
feedID,
|
throw new TypeError("typeof feedID !== 'string'")
|
||||||
mySecret
|
}
|
||||||
)
|
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
logger.info(`onAcceptedRequests -> decrypted feed ID: ${feedID}`)
|
||||||
user
|
|
||||||
.get(Key.USER_TO_INCOMING)
|
logger.info(
|
||||||
.get(recipientPub)
|
'Will now try to access the other users outgoing feed'
|
||||||
.put(encryptedForMeIncomingID, ack => {
|
)
|
||||||
if (ack.err) {
|
|
||||||
rej(new Error(ack.err))
|
const maybeFeedOnRecipientsOutgoings = await Utils.tryAndWait(
|
||||||
} else {
|
gun =>
|
||||||
res()
|
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) => {
|
const feedIDExistsOnRecipientsOutgoings =
|
||||||
user
|
typeof maybeFeedOnRecipientsOutgoings === 'object' &&
|
||||||
.get(Key.STORED_REQS)
|
maybeFeedOnRecipientsOutgoings !== null
|
||||||
.get(id)
|
|
||||||
.put(null, ack => {
|
|
||||||
if (ack.err) {
|
|
||||||
rej(new Error(ack.err))
|
|
||||||
} else {
|
|
||||||
res()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// ensure this listeners gets called at least once
|
if (!feedIDExistsOnRecipientsOutgoings) {
|
||||||
res()
|
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) {
|
} catch (err) {
|
||||||
console.warn(`Jobs.onAcceptedRequests() -> ${err.message}`)
|
console.warn(`Jobs.onAcceptedRequests() -> ${err.message}`)
|
||||||
console.log(err)
|
console.log(err)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
/**
|
/**
|
||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
const logger = require('winston')
|
||||||
|
|
||||||
const ErrorCode = require('../errorCode')
|
const ErrorCode = require('../errorCode')
|
||||||
const Key = require('../key')
|
const Key = require('../key')
|
||||||
|
|
||||||
|
|
@ -39,18 +41,96 @@ const timeout10 = promise => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template T
|
* @template T
|
||||||
* @param {(gun: GUNNode, user: UserGUNNode) => Promise<T>} promGen The function
|
* @param {Promise<T>} promise
|
||||||
* receives the most recent gun and user instances.
|
|
||||||
* @returns {Promise<T>}
|
* @returns {Promise<T>}
|
||||||
*/
|
*/
|
||||||
const tryAndWait = promGen =>
|
const timeout5 = promise => {
|
||||||
timeout10(
|
return Promise.race([
|
||||||
promGen(
|
promise,
|
||||||
require('../../Mediator/index').getGun(),
|
new Promise((_, rej) => {
|
||||||
require('../../Mediator/index').getUser()
|
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
|
* @param {string} pub
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
|
|
@ -74,7 +154,7 @@ const pubToEpub = async pub => {
|
||||||
|
|
||||||
return epub
|
return epub
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
logger.error(err)
|
||||||
throw new Error(`pubToEpub() -> ${err.message}`)
|
throw new Error(`pubToEpub() -> ${err.message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -86,18 +166,20 @@ const pubToEpub = async pub => {
|
||||||
* @returns {Promise<string|null>}
|
* @returns {Promise<string|null>}
|
||||||
*/
|
*/
|
||||||
const recipientPubToLastReqSentID = async recipientPub => {
|
const recipientPubToLastReqSentID = async recipientPub => {
|
||||||
const lastReqSentID = await tryAndWait(async (_, user) => {
|
const maybeLastReqSentID = await tryAndWait(
|
||||||
const userToLastReqSent = user.get(Key.USER_TO_LAST_REQUEST_SENT)
|
(_, user) => {
|
||||||
const data = await userToLastReqSent.get(recipientPub).then()
|
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') {
|
if (typeof maybeLastReqSentID !== 'string') {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return data
|
return maybeLastReqSentID
|
||||||
})
|
|
||||||
|
|
||||||
return lastReqSentID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -127,11 +209,15 @@ const successfulHandshakeAlreadyExists = async recipientPub => {
|
||||||
* @returns {Promise<string|null>}
|
* @returns {Promise<string|null>}
|
||||||
*/
|
*/
|
||||||
const recipientToOutgoingID = async recipientPub => {
|
const recipientToOutgoingID = async recipientPub => {
|
||||||
const maybeEncryptedOutgoingID = await require('../../Mediator/index')
|
const maybeEncryptedOutgoingID = await tryAndWait(
|
||||||
.getUser()
|
(_, user) =>
|
||||||
.get(Key.RECIPIENT_TO_OUTGOING)
|
user
|
||||||
.get(recipientPub)
|
.get(Key.RECIPIENT_TO_OUTGOING)
|
||||||
.then()
|
.get(recipientPub)
|
||||||
|
.then(),
|
||||||
|
// force retry in case undefined is a false negative
|
||||||
|
v => typeof v === 'undefined'
|
||||||
|
)
|
||||||
|
|
||||||
if (typeof maybeEncryptedOutgoingID === 'string') {
|
if (typeof maybeEncryptedOutgoingID === 'string') {
|
||||||
const outgoingID = await require('../../Mediator/index').mySEA.decrypt(
|
const outgoingID = await require('../../Mediator/index').mySEA.decrypt(
|
||||||
|
|
@ -145,22 +231,6 @@ const recipientToOutgoingID = async recipientPub => {
|
||||||
return null
|
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
|
* @template T
|
||||||
* @param {T[]} arr
|
* @param {T[]} arr
|
||||||
|
|
@ -223,22 +293,6 @@ const dataHasSoul = listenerData =>
|
||||||
*/
|
*/
|
||||||
const defaultName = pub => 'anon' + pub.slice(0, 8)
|
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 = {
|
module.exports = {
|
||||||
asyncMap,
|
asyncMap,
|
||||||
asyncFilter,
|
asyncFilter,
|
||||||
|
|
@ -249,10 +303,9 @@ module.exports = {
|
||||||
recipientPubToLastReqSentID,
|
recipientPubToLastReqSentID,
|
||||||
successfulHandshakeAlreadyExists,
|
successfulHandshakeAlreadyExists,
|
||||||
recipientToOutgoingID,
|
recipientToOutgoingID,
|
||||||
currHandshakeAddress,
|
|
||||||
tryAndWait,
|
tryAndWait,
|
||||||
mySecret,
|
mySecret,
|
||||||
promisifyGunNode: require('./promisifygun'),
|
promisifyGunNode: require('./promisifygun'),
|
||||||
didDisconnect,
|
asyncForEach,
|
||||||
asyncForEach
|
timeout5
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1570,6 +1570,7 @@ module.exports = async (
|
||||||
const Events = require('../services/gunDB/contact-api/events')
|
const Events = require('../services/gunDB/contact-api/events')
|
||||||
const user = require('../services/gunDB/Mediator').getUser()
|
const user = require('../services/gunDB/Mediator').getUser()
|
||||||
const Key = require('../services/gunDB/contact-api/key')
|
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) => {
|
app.get(`/api/gun/${GunEvent.ON_RECEIVED_REQUESTS}`, (_, res) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -1619,7 +1620,7 @@ module.exports = async (
|
||||||
app.get(`/api/gun/${GunEvent.ON_AVATAR}`, async (_, res) => {
|
app.get(`/api/gun/${GunEvent.ON_AVATAR}`, async (_, res) => {
|
||||||
try {
|
try {
|
||||||
res.json({
|
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) {
|
} catch (err) {
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
|
|
@ -1631,7 +1632,7 @@ module.exports = async (
|
||||||
app.get(`/api/gun/${GunEvent.ON_DISPLAY_NAME}`, async (_, res) => {
|
app.get(`/api/gun/${GunEvent.ON_DISPLAY_NAME}`, async (_, res) => {
|
||||||
try {
|
try {
|
||||||
res.json({
|
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) {
|
} catch (err) {
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
|
|
@ -1643,7 +1644,7 @@ module.exports = async (
|
||||||
app.get(`/api/gun/${GunEvent.ON_HANDSHAKE_ADDRESS}`, async (_, res) => {
|
app.get(`/api/gun/${GunEvent.ON_HANDSHAKE_ADDRESS}`, async (_, res) => {
|
||||||
try {
|
try {
|
||||||
res.json({
|
res.json({
|
||||||
data: await user.get(Key.CURRENT_HANDSHAKE_ADDRESS).then()
|
data: await timeout5(user.get(Key.CURRENT_HANDSHAKE_ADDRESS).then())
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue