working order ack

This commit is contained in:
hatim boufnichel 2021-01-21 17:30:12 +01:00
parent 4681f22797
commit 9f4a0b05d2
4 changed files with 242 additions and 171 deletions

View file

@ -923,7 +923,7 @@ const sendHRWithInitialMsg = async (
/** /**
* @typedef {object} SpontPaymentOptions * @typedef {object} SpontPaymentOptions
* @prop {Common.Schema.OrderTargetType} type * @prop {Common.Schema.OrderTargetType} type
* @prop {string=} postID * @prop {string=} ackInfo
*/ */
/** /**
* @typedef {object} OrderRes * @typedef {object} OrderRes
@ -968,11 +968,8 @@ const sendSpontaneousPayment = async (
from: getUser()._.sea.pub, from: getUser()._.sea.pub,
memo: memo || 'no memo', memo: memo || 'no memo',
timestamp: Date.now(), timestamp: Date.now(),
targetType: opts.type targetType: opts.type,
} ackInfo: opts.ackInfo
if (opts.type === 'tip') {
order.ackInfo = opts.postID
} }
logger.info(JSON.stringify(order)) logger.info(JSON.stringify(order))
@ -1009,7 +1006,7 @@ const sendSpontaneousPayment = async (
) )
) )
} else { } else {
res(ord._.get) setTimeout(() => res(ord._.get), 0)
} }
}) })
}) })
@ -1020,7 +1017,8 @@ const sendSpontaneousPayment = async (
)}` )}`
throw new Error(msg) throw new Error(msg)
} }
console.log('ORDER ID')
console.log(orderID)
/** @type {import('shock-common').Schema.OrderResponse} */ /** @type {import('shock-common').Schema.OrderResponse} */
const encryptedOrderRes = await Utils.tryAndWait( const encryptedOrderRes = await Utils.tryAndWait(
gun => gun =>
@ -1030,12 +1028,13 @@ const sendSpontaneousPayment = async (
.get(Key.ORDER_TO_RESPONSE) .get(Key.ORDER_TO_RESPONSE)
.get(orderID) .get(orderID)
.on(orderResponse => { .on(orderResponse => {
console.log(orderResponse)
if (Schema.isOrderResponse(orderResponse)) { if (Schema.isOrderResponse(orderResponse)) {
res(orderResponse) res(orderResponse)
} }
}) })
}), }),
v => !Schema.isOrderResponse(v) v => Schema.isOrderResponse(v)
) )
if (!Schema.isOrderResponse(encryptedOrderRes)) { if (!Schema.isOrderResponse(encryptedOrderRes)) {
@ -1046,10 +1045,12 @@ const sendSpontaneousPayment = async (
throw e throw e
} }
/** @type {import('shock-common').Schema.OrderResponse} */ /** @type {import('shock-common').Schema.OrderResponse &{ackNode:string}} */
const orderResponse = { const orderResponse = {
response: await SEA.decrypt(encryptedOrderRes.response, ourSecret), response: await SEA.decrypt(encryptedOrderRes.response, ourSecret),
type: encryptedOrderRes.type type: encryptedOrderRes.type,
//@ts-expect-error
ackNode: encryptedOrderRes.ackNode
} }
logger.info('decoded orderResponse: ' + JSON.stringify(orderResponse)) logger.info('decoded orderResponse: ' + JSON.stringify(orderResponse))
@ -1080,7 +1081,10 @@ const sendSpontaneousPayment = async (
payment_request: orderResponse.response payment_request: orderResponse.response
}) })
const myLndPub = LNDHealthMananger.lndPub const myLndPub = LNDHealthMananger.lndPub
if (opts.type !== 'contentReveal' && opts.type !== 'torrentSeed') { if (
(opts.type !== 'contentReveal' && opts.type !== 'torrentSeed') ||
!orderResponse.ackNode
) {
SchemaManager.AddOrder({ SchemaManager.AddOrder({
type: opts.type, type: opts.type,
amount: parseInt(payment.value_sat, 10), amount: parseInt(payment.value_sat, 10),
@ -1094,6 +1098,8 @@ const sendSpontaneousPayment = async (
}) })
return { payment } return { payment }
} }
console.log('ACK NODE')
console.log(orderResponse.ackNode)
/** @type {import('shock-common').Schema.OrderResponse} */ /** @type {import('shock-common').Schema.OrderResponse} */
const encryptedOrderAckRes = await Utils.tryAndWait( const encryptedOrderAckRes = await Utils.tryAndWait(
gun => gun =>
@ -1101,8 +1107,11 @@ const sendSpontaneousPayment = async (
gun gun
.user(to) .user(to)
.get(Key.ORDER_TO_RESPONSE) .get(Key.ORDER_TO_RESPONSE)
.get(orderID) .get(orderResponse.ackNode)
.on(orderResponse => { .on(orderResponse => {
console.log(orderResponse)
console.log(Schema.isOrderResponse(orderResponse))
if (Schema.isOrderResponse(orderResponse)) { if (Schema.isOrderResponse(orderResponse)) {
res(orderResponse) res(orderResponse)
} }
@ -1113,7 +1122,7 @@ const sendSpontaneousPayment = async (
if (!Schema.isOrderResponse(encryptedOrderAckRes)) { if (!Schema.isOrderResponse(encryptedOrderAckRes)) {
const e = TypeError( const e = TypeError(
`Expected OrderResponse got: ${typeof encryptedOrderAckRes}` `Expected encryptedOrderAckRes got: ${typeof encryptedOrderAckRes}`
) )
logger.error(e) logger.error(e)
throw e throw e
@ -1148,6 +1157,7 @@ const sendSpontaneousPayment = async (
}) })
return { payment, orderAck } return { payment, orderAck }
} catch (e) { } catch (e) {
console.log(e)
logger.error('Error inside sendPayment()') logger.error('Error inside sendPayment()')
logger.error(e) logger.error(e)
throw e throw e

View file

@ -2,6 +2,7 @@
* @format * @format
*/ */
// @ts-check // @ts-check
const Gun = require('gun')
const { performance } = require('perf_hooks') const { performance } = require('perf_hooks')
const logger = require('winston') const logger = require('winston')
const isFinite = require('lodash/isFinite') const isFinite = require('lodash/isFinite')
@ -201,11 +202,15 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
logger.info( logger.info(
`onOrders() -> Will now place the encrypted invoice in order to response usergraph: ${addr}` `onOrders() -> Will now place the encrypted invoice in order to response usergraph: ${addr}`
) )
// @ts-expect-error
const ackNode = Gun.text.random()
/** @type {import('shock-common').Schema.OrderResponse} */ /** @type {import('shock-common').Schema.OrderResponse} */
const orderResponse = { const orderResponse = {
response: encInvoice, response: encInvoice,
type: 'invoice' type: 'invoice',
//@ts-expect-error
ackNode
} }
const invoicePutStartTime = performance.now() const invoicePutStartTime = performance.now()
@ -231,164 +236,212 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
const invoicePutEndTime = performance.now() - invoicePutStartTime const invoicePutEndTime = performance.now() - invoicePutStartTime
logger.info(`[PERF] Added invoice to GunDB in ${invoicePutEndTime}ms`) logger.info(`[PERF] Added invoice to GunDB in ${invoicePutEndTime}ms`)
/** /**
* *
* @type {Common.Schema.InvoiceWhenListed & {r_hash:Buffer,payment_addr:string}} * @param {Common.Schema.InvoiceWhenListed & {r_hash:Buffer,payment_addr:string}} paidInvoice
*/ */
const paidInvoice = await new Promise(res => { const invoicePaidCb = async paidInvoice => {
SchemaManager.addListenInvoice(invoice.r_hash, res) console.log('INVOICE PAID')
}) const hashString = paidInvoice.r_hash.toString('hex')
const hashString = paidInvoice.r_hash.toString('hex') const {
const { amt_paid_sat: amt,
amt_paid_sat: amt, add_index: addIndex,
add_index: addIndex, payment_addr: paymentAddr
payment_addr: paymentAddr } = paidInvoice
} = paidInvoice const orderType = order.targetType
const orderType = order.targetType const { ackInfo } = order //a string representing what has been requested
const { ackInfo } = order //a string representing what has been requested switch (orderType) {
switch (orderType) { case 'tip': {
case 'tip': { const postID = ackInfo
const postID = ackInfo if (!Common.isPopulatedString(postID)) {
if (!Common.isPopulatedString(postID)) { break //create the coordinate, but stop because of the invalid id
break //create the coordinate, but stop because of the invalid id
}
getUser()
.get('postToTipCount')
.get(postID)
.set(null) // each item in the set is a tip
break
}
case 'spontaneousPayment': {
//no action required
break
}
case 'contentReveal': {
//assuming digital product that only requires to be unlocked
const postID = ackInfo
if (!Common.isPopulatedString(postID)) {
break //create the coordinate, but stop because of the invalid id
}
const selectedPost = await new Promise(res => {
getUser()
.get(Key.POSTS_NEW)
.get(postID)
.load(res)
})
if (!Common.Schema.isPost(selectedPost)) {
break //create the coordinate, but stop because of the invalid post
}
/**
* @type {Record<string,string>} <contentID,decryptedRef>
*/
const contentsToSend = {}
const mySecret = require('../../Mediator').getMySecret()
await Common.Utils.asyncForEach(
Object.entries(selectedPost.contentItems),
async ([contentID, item]) => {
if (
item.type !== 'image/embedded' &&
item.type !== 'video/embedded'
) {
return //only visual content can be private
}
if (!item.isPrivate) {
return
}
const decrypted = await SEA.decrypt(item.magnetURI, mySecret)
contentsToSend[contentID] = decrypted
} }
)
const ackData = { unlockedContents: contentsToSend }
const toSend = JSON.stringify(ackData)
const encrypted = await SEA.encrypt(toSend, secret)
const ordResponse = {
type: 'orderAck',
content: encrypted
}
await new Promise((res, rej) => {
getUser() getUser()
.get(Key.ORDER_TO_RESPONSE) .get('postToTipCount')
.get(orderID) .get(postID)
.put(ordResponse, ack => { .set(null) // each item in the set is a tip
if (ack.err && typeof ack.err !== 'number') { break
rej( }
new Error( case 'spontaneousPayment': {
`Error saving encrypted orderAck to order to response usergraph: ${ack}` //no action required
) break
) }
} else { case 'contentReveal': {
res() console.log('cONTENT REVEAL')
//assuming digital product that only requires to be unlocked
const postID = ackInfo
console.log('ACK INFO')
console.log(ackInfo)
if (!Common.isPopulatedString(postID)) {
break //create the coordinate, but stop because of the invalid id
}
console.log('IS STRING')
const selectedPost = await new Promise(res => {
getUser()
.get(Key.POSTS_NEW)
.get(postID)
.load(res)
})
console.log('LOAD ok')
console.log(selectedPost)
if (
!selectedPost ||
!selectedPost.status ||
selectedPost.status !== 'publish'
) {
break //create the coordinate, but stop because of the invalid post
}
console.log('IS POST')
/**
* @type {Record<string,string>} <contentID,decryptedRef>
*/
const contentsToSend = {}
const mySecret = require('../../Mediator').getMySecret()
console.log('SECRET OK')
await Common.Utils.asyncForEach(
Object.entries(selectedPost.contentItems),
async ([contentID, item]) => {
if (
item.type !== 'image/embedded' &&
item.type !== 'video/embedded'
) {
return //only visual content can be private
} }
}) if (!item.isPrivate) {
}) return
break }
} const decrypted = await SEA.decrypt(item.magnetURI, mySecret)
case 'torrentSeed': { contentsToSend[contentID] = decrypted
const seedUrl = process.env.TORRENT_SEED_URL }
const seedToken = process.env.TORRENT_SEED_TOKEN )
if (!seedUrl || !seedToken) { const ackData = { unlockedContents: contentsToSend }
break //service not available const toSend = JSON.stringify(ackData)
} const encrypted = await SEA.encrypt(toSend, secret)
const token = crypto.randomBytes(32).toString('hex') const ordResponse = {
const reqData = { type: 'orderAck',
seed_token: seedToken, response: encrypted
wallet_token: token }
} console.log('RES READY')
const res = await fetch(`${seedUrl}/api/enroll_token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(reqData)
})
if (res.ok) {
break //request didnt work, save coordinate anyway
}
const ackData = { seedUrl, token } await new Promise((res, rej) => {
const toSend = JSON.stringify(ackData) getUser()
const encrypted = await SEA.encrypt(toSend, secret) .get(Key.ORDER_TO_RESPONSE)
const serviceResponse = { .get(ackNode)
type: 'orderAck', .put(ordResponse, ack => {
content: encrypted if (ack.err && typeof ack.err !== 'number') {
} rej(
await new Promise((res, rej) => { new Error(
getUser() `Error saving encrypted orderAck to order to response usergraph: ${ack}`
.get(Key.ORDER_TO_RESPONSE) )
.get(orderID)
.put(serviceResponse, ack => {
if (ack.err && typeof ack.err !== 'number') {
rej(
new Error(
`Error saving encrypted orderAck to order to response usergraph: ${ack}`
) )
) } else {
} else { res()
res() }
} })
}) })
}) console.log('RES SENT')
break break
}
case 'torrentSeed': {
console.log('TORRENT')
const seedUrl = process.env.TORRENT_SEED_URL
const seedToken = process.env.TORRENT_SEED_TOKEN
if (!seedUrl || !seedToken) {
break //service not available
}
console.log('SEED URL OK')
const token = crypto.randomBytes(32).toString('hex')
const reqData = {
seed_token: seedToken,
wallet_token: token
}
console.log(seedUrl)
console.log(seedToken)
const res = await fetch(`${seedUrl}/api/enroll_token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(reqData)
})
if (res.status !== 200) {
break //request didnt work, save coordinate anyway
}
console.log('RES SEED OK')
const ackData = { seedUrl, token }
const toSend = JSON.stringify(ackData)
const encrypted = await SEA.encrypt(toSend, secret)
const serviceResponse = {
type: 'orderAck',
response: encrypted
}
console.log('RES SEED SENT')
await new Promise((res, rej) => {
getUser()
.get(Key.ORDER_TO_RESPONSE)
.get(ackNode)
.put(serviceResponse, ack => {
if (ack.err && typeof ack.err !== 'number') {
rej(
new Error(
`Error saving encrypted orderAck to order to response usergraph: ${ack}`
)
)
} else {
res()
}
})
})
break
}
case 'other': //not implemented yet but save them as a coordinate anyways
break
default:
return //exit because not implemented
} }
case 'other': //not implemented yet but save them as a coordinate anyways const myGunPub = getUser()._.sea.pub
break SchemaManager.AddOrder({
default: type: orderType,
return //exit because not implemented coordinateHash: hashString,
coordinateIndex: parseInt(addIndex, 10),
inbound: true,
amount: parseInt(amt, 10),
toLndPub: paymentAddr,
fromGunPub: order.from,
toGunPub: myGunPub,
invoiceMemo: memo
})
} }
const myGunPub = getUser()._.sea.pub console.log('WAITING INVOICE TO BE PAID')
SchemaManager.AddOrder({ new Promise(res => SchemaManager.addListenInvoice(invoice.r_hash, res))
type: orderType, .then(invoicePaidCb)
coordinateHash: hashString, .catch(err => {
coordinateIndex: parseInt(addIndex, 10), logger.error(
inbound: true, `error inside onOrders, orderAddr: ${addr}, orderID: ${orderID}, order: ${JSON.stringify(
amount: parseInt(amt, 10), order
)}`
)
logger.error(err)
toLndPub: paymentAddr, /** @type {import('shock-common').Schema.OrderResponse} */
fromGunPub: order.from, const orderResponse = {
toGunPub: myGunPub, response: err.message,
invoiceMemo: memo type: 'err'
}) }
getUser()
.get(Key.ORDER_TO_RESPONSE)
.get(orderID)
// @ts-expect-error
.put(orderResponse, ack => {
if (ack.err && typeof ack.err !== 'number') {
logger.error(
`Error saving encrypted invoice to order to response usergraph: ${ack}`
)
}
})
})
} catch (err) { } catch (err) {
logger.error( logger.error(
`error inside onOrders, orderAddr: ${addr}, orderID: ${orderID}, order: ${JSON.stringify( `error inside onOrders, orderAddr: ${addr}, orderID: ${orderID}, order: ${JSON.stringify(

View file

@ -387,6 +387,7 @@ class SchemaManager {
.get(coordinateSHA256) .get(coordinateSHA256)
.put(encryptedOrderString, ack => { .put(encryptedOrderString, ack => {
if (ack.err && typeof ack.err !== 'number') { if (ack.err && typeof ack.err !== 'number') {
console.log(ack)
rej( rej(
new Error( new Error(
`Error saving coordinate order to user-graph: ${ack}` `Error saving coordinate order to user-graph: ${ack}`

View file

@ -1192,12 +1192,17 @@ module.exports = async (
app.post('/api/lnd/unifiedTrx', async (req, res) => { app.post('/api/lnd/unifiedTrx', async (req, res) => {
try { try {
const { type, amt, to, memo, feeLimit, postID } = req.body const { type, amt, to, memo, feeLimit, ackInfo } = req.body
if (
if (type !== 'spont' && type !== 'post') { type !== 'spontaneousPayment' &&
type !== 'tip' &&
type !== 'torrentSeed' &&
type !== 'contentReveal' &&
type !== 'other'
) {
return res.status(415).json({ return res.status(415).json({
field: 'type', field: 'type',
errorMessage: `Only 'spont' and 'post' payments supported via this endpoint for now.` errorMessage: `Only 'spontaneousPayment'| 'tip' | 'torrentSeed' | 'contentReveal' | 'other' payments supported via this endpoint for now.`
}) })
} }
@ -1231,17 +1236,17 @@ module.exports = async (
}) })
} }
if (type === 'post' && typeof postID !== 'string') { if (type === 'tip' && typeof ackInfo !== 'string') {
return res.status(400).json({ return res.status(400).json({
field: 'postID', field: 'ackInfo',
errorMessage: `Send postID` errorMessage: `Send ackInfo`
}) })
} }
return res.status(200).json( return res.status(200).json(
await GunActions.sendSpontaneousPayment(to, amt, memo, feeLimit, { await GunActions.sendSpontaneousPayment(to, amt, memo, feeLimit, {
type, type,
postID ackInfo
}) })
) )
} catch (e) { } catch (e) {
@ -2164,10 +2169,12 @@ module.exports = async (
app.post(`/api/gun/wall/`, async (req, res) => { app.post(`/api/gun/wall/`, async (req, res) => {
try { try {
const { tags, title, contentItems } = req.body const { tags, title, contentItems } = req.body
const SEA = require('../services/gunDB/Mediator').mySEA
return res return res
.status(200) .status(200)
.json(await GunActions.createPostNew(tags, title, contentItems)) .json(await GunActions.createPostNew(tags, title, contentItems, SEA))
} catch (e) { } catch (e) {
console.log(e)
return res.status(500).json({ return res.status(500).json({
errorMessage: errorMessage:
(typeof e === 'string' ? e : e.message) || 'Unknown error.' (typeof e === 'string' ? e : e.message) || 'Unknown error.'