From 9f4a0b05d21c093451b259a64da6eb677c8f71e9 Mon Sep 17 00:00:00 2001 From: hatim boufnichel Date: Thu, 21 Jan 2021 17:30:12 +0100 Subject: [PATCH] working order ack --- services/gunDB/contact-api/actions.js | 38 ++- services/gunDB/contact-api/jobs/onOrders.js | 349 +++++++++++--------- services/schema/index.js | 1 + src/routes.js | 25 +- 4 files changed, 242 insertions(+), 171 deletions(-) diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index 7d81b5ac..6f234e10 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -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 diff --git a/services/gunDB/contact-api/jobs/onOrders.js b/services/gunDB/contact-api/jobs/onOrders.js index fc3485a7..63b9914e 100644 --- a/services/gunDB/contact-api/jobs/onOrders.js +++ b/services/gunDB/contact-api/jobs/onOrders.js @@ -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} - */ - 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} + */ + 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( diff --git a/services/schema/index.js b/services/schema/index.js index 786b5ab0..ea7b78bd 100644 --- a/services/schema/index.js +++ b/services/schema/index.js @@ -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}` diff --git a/src/routes.js b/src/routes.js index f61a4857..3801a4f7 100644 --- a/src/routes.js +++ b/src/routes.js @@ -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.'