services wired with orderAck

This commit is contained in:
hatim boufnichel 2021-04-01 19:55:05 +02:00
parent bdf8c7206b
commit 6514018cf3
8 changed files with 190 additions and 77 deletions

View file

@ -54,7 +54,7 @@
"request-promise": "^4.2.6",
"response-time": "^2.3.2",
"shelljs": "^0.8.2",
"shock-common": "32.0.0",
"shock-common": "34.0.0",
"socket.io": "2.1.1",
"text-encoding": "^0.7.0",
"tingodb": "^0.6.1",

View file

@ -6,8 +6,6 @@ const logger = require('winston')
const Common = require('shock-common')
const { Constants, Schema } = Common
const Gun = require('gun')
const crypto = require('crypto')
const fetch = require('node-fetch')
const { ErrorCode } = Constants
@ -25,6 +23,7 @@ const Key = require('./key')
const Utils = require('./utils')
const SchemaManager = require('../../schema')
const LNDHealthMananger = require('../../../utils/lightningServices/errors')
const { enrollContentTokens, selfContentToken } = require('../../seed')
/**
* @typedef {import('./SimpleGUN').GUNNode} GUNNode
@ -961,39 +960,14 @@ const sendSpontaneousPayment = async (
!isNaN(parseInt(opts.ackInfo, 10))
) {
//user requested a seed to themselves
const numberOfTokens = Number(opts.ackInfo)
if (isNaN(numberOfTokens)) {
throw new Error('ackInfo provided is not a valid number')
}
const seedUrl = process.env.TORRENT_SEED_URL
const seedToken = process.env.TORRENT_SEED_TOKEN
if (!seedUrl || !seedToken) {
const numberOfTokens = Number(opts.ackInfo) || 1
const seedInfo = selfContentToken()
if (!seedInfo) {
throw new Error('torrentSeed service not available')
}
const { seedUrl } = seedInfo
console.log('SEED URL OK')
const tokens = Array(numberOfTokens)
for (let i = 0; i < numberOfTokens; i++) {
tokens[i] = crypto.randomBytes(32).toString('hex')
}
/**@param {string} token */
const enrollToken = async token => {
const reqData = {
seed_token: seedToken,
wallet_token: token
}
//@ts-expect-error
const res = await fetch(`${seedUrl}/api/enroll_token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(reqData)
})
if (res.status !== 200) {
throw new Error('torrentSeed service currently not available')
}
}
await Promise.all(tokens.map(enrollToken))
const tokens = await enrollContentTokens(numberOfTokens, seedInfo)
console.log('RES SEED OK')
const ackData = JSON.stringify({ seedUrl, tokens })
return {
@ -1132,7 +1106,11 @@ const sendSpontaneousPayment = async (
})
const myLndPub = LNDHealthMananger.lndPub
if (
(opts.type !== 'contentReveal' && opts.type !== 'torrentSeed') ||
(opts.type !== 'contentReveal' &&
opts.type !== 'torrentSeed' &&
opts.type !== 'service' &&
opts.type !== 'streamSeed' &&
opts.type !== 'product') ||
!orderResponse.ackNode
) {
SchemaManager.AddOrder({

View file

@ -7,8 +7,6 @@ const isFinite = require('lodash/isFinite')
const isNumber = require('lodash/isNumber')
const isNaN = require('lodash/isNaN')
const Common = require('shock-common')
const crypto = require('crypto')
const fetch = require('node-fetch')
const {
Constants: { ErrorCode },
Schema
@ -18,6 +16,7 @@ const LightningServices = require('../../../../utils/lightningServices')
const Key = require('../key')
const Utils = require('../utils')
const Gun = require('gun')
const { selfContentToken, enrollContentTokens } = require('../../../seed')
const getUser = () => require('../../Mediator').getUser()
@ -152,6 +151,54 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
`Amount was correctly decrypted, but got a non finite number, decryptedAmount: ${decryptedAmount}`
)
}
const mySecret = require('../../Mediator').getMySecret()
/**
* @type {string|null}
*/
let serviceOrderType = null //if the order refers to a service, we take the info from the service before sending the invoice
/**
* @type {{ seedUrl: string, seedToken: string }|null}
*/
let serviceOrderContentSeedInfo = null //in case the service is of type 'torrentSeed' or 'streamSeed' this is {seedUrl,seedToken}, can be omitted, in that case, it will be taken from env
if (order.targetType === 'service') {
console.log('General Service')
const { ackInfo: serviceID } = order
console.log('ACK INFO')
console.log(serviceID)
if (!Common.isPopulatedString(serviceID)) {
throw new TypeError(`no serviceID provided to orderAck`)
}
const selectedService = await new Promise(res => {
getUser()
.get(Key.OFFERED_SERVICES)
.get(serviceID)
.load(res)
})
console.log(selectedService)
if (!selectedService) {
throw new TypeError(`invalid serviceID provided to orderAck`)
}
const {
serviceType,
servicePrice,
serviceSeedUrl: encSeedUrl, //=
serviceSeedToken: encSeedToken //=
} = selectedService
if (Number(amount) !== Number(servicePrice)) {
throw new TypeError(
`service price mismatch ${amount} : ${servicePrice}`
)
}
if (serviceType === 'torrentSeed' || serviceType === 'streamSeed') {
if (encSeedUrl && encSeedToken) {
const seedUrl = await SEA.decrypt(encSeedUrl, mySecret)
const seedToken = await SEA.decrypt(encSeedToken, mySecret)
serviceOrderContentSeedInfo = { seedUrl, seedToken }
}
}
serviceOrderType = serviceType
}
const invoiceReq = {
expiry: 36000,
@ -218,7 +265,7 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
add_index: addIndex,
payment_addr: paymentAddr
} = paidInvoice
const orderType = order.targetType
const orderType = serviceOrderType || order.targetType
const { ackInfo } = order //a string representing what has been requested
switch (orderType) {
case 'tip': {
@ -269,7 +316,6 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
* @type {Record<string,string>} <contentID,decryptedRef>
*/
const contentsToSend = {}
const mySecret = require('../../Mediator').getMySecret()
console.log('SECRET OK')
let privateFound = false
await Common.Utils.asyncForEach(
@ -294,7 +340,7 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
'post provided from ackInfo does not contain private content'
break //no private content in this post
}
const ackData = { unlockedContents: contentsToSend }
const ackData = { unlockedContents: contentsToSend, ackInfo }
const toSend = JSON.stringify(ackData)
const encrypted = await SEA.encrypt(toSend, secret)
const ordResponse = {
@ -325,43 +371,75 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
}
case 'torrentSeed': {
console.log('TORRENT')
const numberOfTokens = Number(ackInfo)
if (isNaN(numberOfTokens)) {
breakError = 'ackInfo provided is not a valid number'
break
}
const seedUrl = process.env.TORRENT_SEED_URL
const seedToken = process.env.TORRENT_SEED_TOKEN
if (!seedUrl || !seedToken) {
const numberOfTokens = Number(ackInfo) || 1
const seedInfo = selfContentToken()
if (!seedInfo && !serviceOrderContentSeedInfo) {
breakError = 'torrentSeed service not available'
break //service not available
}
console.log('SEED URL OK')
const tokens = Array(numberOfTokens)
for (let i = 0; i < numberOfTokens; i++) {
tokens[i] = crypto.randomBytes(32).toString('hex')
const seedInfoReady = serviceOrderContentSeedInfo || seedInfo
if (!seedInfoReady) {
breakError = 'torrentSeed service not available'
break //service not available
}
/**@param {string} token */
const enrollToken = async token => {
const reqData = {
seed_token: seedToken,
wallet_token: token
}
//@ts-expect-error
const res = await fetch(`${seedUrl}/api/enroll_token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(reqData)
})
if (res.status !== 200) {
throw new Error('torrentSeed service currently not available')
}
}
await Promise.all(tokens.map(enrollToken))
const { seedUrl } = seedInfoReady
const tokens = await enrollContentTokens(
numberOfTokens,
seedInfoReady
)
console.log('RES SEED OK')
const ackData = { seedUrl, tokens }
const ackData = { seedUrl, tokens, ackInfo }
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(null)
}
})
})
console.log('RES SENT SEED')
orderMetadata = JSON.stringify(serviceResponse)
break
}
case 'streamSeed': {
console.log('STREAM')
const numberOfTokens = 1
const seedInfo = selfContentToken() //TODO this must change for streams
if (!seedInfo && !serviceOrderContentSeedInfo) {
breakError = 'torrentSeed service not available'
break //service not available
}
const seedInfoReady = serviceOrderContentSeedInfo || seedInfo
if (!seedInfoReady) {
breakError = 'torrentSeed service not available'
break //service not available
}
const { seedUrl } = seedInfoReady
const tokens = await enrollContentTokens(
numberOfTokens,
seedInfoReady
)
console.log('RES SEED OK')
const ackData = {
seedUrl,
tokens,
ackInfo
}
const toSend = JSON.stringify(ackData)
const encrypted = await SEA.encrypt(toSend, secret)
const serviceResponse = {
@ -392,6 +470,7 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
case 'other': //not implemented yet but save them as a coordinate anyways
break
default:
breakError = 'invalid service type provided'
return //exit because not implemented
}
const metadata = breakError ? JSON.stringify(breakError) : orderMetadata

View file

@ -71,3 +71,5 @@ exports.COORDINATE_INDEX = 'coordinateIndex'
exports.TMP_CHAIN_COORDINATE = 'tmpChainCoordinate'
exports.DATE_COORDINATE_INDEX = 'dateCoordinateIndex'
exports.OFFERED_SERVICES = 'offeredServices'

View file

@ -7,7 +7,7 @@ const Key = require('../gunDB/contact-api/key')
const lndV2 = require('../../utils/lightningServices/v2')
/**
* @typedef {import('../gunDB/contact-api/SimpleGUN').ISEA} ISEA
* @typedef { 'spontaneousPayment' | 'tip' | 'torrentSeed' | 'contentReveal' | 'other'|'invoice'|'payment'|'chainTx' } OrderType
* @typedef { 'spontaneousPayment' | 'tip' | 'torrentSeed' | 'contentReveal' | 'other'|'invoice'|'payment'|'chainTx' | 'streamSeed' |'service'|'product' } OrderType
*
* This represents a settled order only, unsettled orders have no coordinate
* @typedef {object} CoordinateOrder //everything is optional for different types

51
services/seed.js Normal file
View file

@ -0,0 +1,51 @@
const crypto = require('crypto')
const fetch = require('node-fetch')
const selfContentToken = () => {
const seedUrl = process.env.TORRENT_SEED_URL
const seedToken = process.env.TORRENT_SEED_TOKEN
if (!seedUrl || !seedToken) {
return false
}
return {seedUrl,seedToken}
}
/**
*
* @param {number} nOfTokens
* @param {{seedUrl:string,seedToken:string}} param1
* @returns
*/
const enrollContentTokens = async (nOfTokens,{seedUrl,seedToken}) => {
const tokens = Array(nOfTokens)
for (let i = 0; i < nOfTokens; i++) {
tokens[i] = crypto.randomBytes(32).toString('hex')
}
/**@param {string} token */
const enrollToken = async token => {
const reqData = {
seed_token: seedToken,
wallet_token: token
}
//@ts-expect-error
const res = await fetch(`${seedUrl}/api/enroll_token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(reqData)
})
if (res.status !== 200) {
throw new Error('torrentSeed service currently not available')
}
}
await Promise.all(tokens.map(enrollToken))
return tokens
}
module.exports = {
selfContentToken,
enrollContentTokens
}

View file

@ -1197,12 +1197,15 @@ module.exports = async (
type !== 'spontaneousPayment' &&
type !== 'tip' &&
type !== 'torrentSeed' &&
type !== 'streamSeed' &&
type !== 'contentReveal' &&
type !== 'service' &&
type !== 'product' &&
type !== 'other'
) {
return res.status(415).json({
field: 'type',
errorMessage: `Only 'spontaneousPayment'| 'tip' | 'torrentSeed' | 'contentReveal' | 'other' payments supported via this endpoint for now.`
errorMessage: `Only 'spontaneousPayment'| 'tip' | 'torrentSeed' | 'contentReveal' | 'service' | 'streamSeed' | 'product' |'other' payments supported via this endpoint for now.`
})
}

View file

@ -6212,10 +6212,10 @@ shellwords@^0.1.1:
resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==
shock-common@32.0.0:
version "32.0.0"
resolved "https://registry.yarnpkg.com/shock-common/-/shock-common-32.0.0.tgz#bb7f70d7a572783c46aeae7420179935eb5096d4"
integrity sha512-1GorUFRpkRGXdKT9PImwnj2orpoJaESU/iD+rvL8sqFMLKazkW9LfLAcRwEWCvytWdKFJ35UO2gN49N8dPiRmA==
shock-common@34.0.0:
version "34.0.0"
resolved "https://registry.yarnpkg.com/shock-common/-/shock-common-34.0.0.tgz#30ffbcb136af9bc04b936999a7eebee4e18c67f0"
integrity sha512-i+io2YBh/GLXBz4YURdxg0t//gm2H3dmpkdU8gnEVe7i/ZdabYGhyBgEnUOMy1ZTMunvv/20U8wan9W4VrOaVQ==
dependencies:
immer "^6.0.6"
lodash "^4.17.19"