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

View file

@ -2,6 +2,7 @@
* @format
*/
// @ts-check
const Gun = require('gun')
const { performance } = require('perf_hooks')
const logger = require('winston')
const isFinite = require('lodash/isFinite')
@ -201,11 +202,15 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
logger.info(
`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} */
const orderResponse = {
response: encInvoice,
type: 'invoice'
type: 'invoice',
//@ts-expect-error
ackNode
}
const invoicePutStartTime = performance.now()
@ -231,164 +236,212 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
const invoicePutEndTime = performance.now() - invoicePutStartTime
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 => {
SchemaManager.addListenInvoice(invoice.r_hash, res)
})
const hashString = paidInvoice.r_hash.toString('hex')
const {
amt_paid_sat: amt,
add_index: addIndex,
payment_addr: paymentAddr
} = paidInvoice
const orderType = order.targetType
const { ackInfo } = order //a string representing what has been requested
switch (orderType) {
case 'tip': {
const postID = ackInfo
if (!Common.isPopulatedString(postID)) {
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 invoicePaidCb = async paidInvoice => {
console.log('INVOICE PAID')
const hashString = paidInvoice.r_hash.toString('hex')
const {
amt_paid_sat: amt,
add_index: addIndex,
payment_addr: paymentAddr
} = paidInvoice
const orderType = order.targetType
const { ackInfo } = order //a string representing what has been requested
switch (orderType) {
case 'tip': {
const postID = ackInfo
if (!Common.isPopulatedString(postID)) {
break //create the coordinate, but stop because of the invalid id
}
)
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()
.get(Key.ORDER_TO_RESPONSE)
.get(orderID)
.put(ordResponse, ack => {
if (ack.err && typeof ack.err !== 'number') {
rej(
new Error(
`Error saving encrypted orderAck to order to response usergraph: ${ack}`
)
)
} else {
res()
.get('postToTipCount')
.get(postID)
.set(null) // each item in the set is a tip
break
}
case 'spontaneousPayment': {
//no action required
break
}
case 'contentReveal': {
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
}
})
})
break
}
case 'torrentSeed': {
const seedUrl = process.env.TORRENT_SEED_URL
const seedToken = process.env.TORRENT_SEED_TOKEN
if (!seedUrl || !seedToken) {
break //service not available
}
const token = crypto.randomBytes(32).toString('hex')
const reqData = {
seed_token: seedToken,
wallet_token: token
}
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
}
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',
response: encrypted
}
console.log('RES READY')
const ackData = { seedUrl, token }
const toSend = JSON.stringify(ackData)
const encrypted = await SEA.encrypt(toSend, secret)
const serviceResponse = {
type: 'orderAck',
content: encrypted
}
await new Promise((res, rej) => {
getUser()
.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}`
await new Promise((res, rej) => {
getUser()
.get(Key.ORDER_TO_RESPONSE)
.get(ackNode)
.put(ordResponse, ack => {
if (ack.err && typeof ack.err !== 'number') {
rej(
new Error(
`Error saving encrypted orderAck to order to response usergraph: ${ack}`
)
)
)
} else {
res()
}
})
})
break
} else {
res()
}
})
})
console.log('RES SENT')
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
break
default:
return //exit because not implemented
const myGunPub = getUser()._.sea.pub
SchemaManager.AddOrder({
type: orderType,
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
SchemaManager.AddOrder({
type: orderType,
coordinateHash: hashString,
coordinateIndex: parseInt(addIndex, 10),
inbound: true,
amount: parseInt(amt, 10),
console.log('WAITING INVOICE TO BE PAID')
new Promise(res => SchemaManager.addListenInvoice(invoice.r_hash, res))
.then(invoicePaidCb)
.catch(err => {
logger.error(
`error inside onOrders, orderAddr: ${addr}, orderID: ${orderID}, order: ${JSON.stringify(
order
)}`
)
logger.error(err)
toLndPub: paymentAddr,
fromGunPub: order.from,
toGunPub: myGunPub,
invoiceMemo: memo
})
/** @type {import('shock-common').Schema.OrderResponse} */
const orderResponse = {
response: err.message,
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) {
logger.error(
`error inside onOrders, orderAddr: ${addr}, orderID: ${orderID}, order: ${JSON.stringify(

View file

@ -387,6 +387,7 @@ class SchemaManager {
.get(coordinateSHA256)
.put(encryptedOrderString, ack => {
if (ack.err && typeof ack.err !== 'number') {
console.log(ack)
rej(
new Error(
`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) => {
try {
const { type, amt, to, memo, feeLimit, postID } = req.body
if (type !== 'spont' && type !== 'post') {
const { type, amt, to, memo, feeLimit, ackInfo } = req.body
if (
type !== 'spontaneousPayment' &&
type !== 'tip' &&
type !== 'torrentSeed' &&
type !== 'contentReveal' &&
type !== 'other'
) {
return res.status(415).json({
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({
field: 'postID',
errorMessage: `Send postID`
field: 'ackInfo',
errorMessage: `Send ackInfo`
})
}
return res.status(200).json(
await GunActions.sendSpontaneousPayment(to, amt, memo, feeLimit, {
type,
postID
ackInfo
})
)
} catch (e) {
@ -2164,10 +2169,12 @@ module.exports = async (
app.post(`/api/gun/wall/`, async (req, res) => {
try {
const { tags, title, contentItems } = req.body
const SEA = require('../services/gunDB/Mediator').mySEA
return res
.status(200)
.json(await GunActions.createPostNew(tags, title, contentItems))
.json(await GunActions.createPostNew(tags, title, contentItems, SEA))
} catch (e) {
console.log(e)
return res.status(500).json({
errorMessage:
(typeof e === 'string' ? e : e.message) || 'Unknown error.'