Merge pull request #308 from shocknet/merged-coordinates

Merged coordinates
This commit is contained in:
CapDog 2021-02-22 16:36:04 -05:00 committed by GitHub
commit 11992beb7c
8 changed files with 253 additions and 72 deletions

View file

@ -4,4 +4,6 @@ MS_TO_TOKEN_EXPIRATION=4500000
DISABLE_SHOCK_ENCRYPTION=false DISABLE_SHOCK_ENCRYPTION=false
CACHE_HEADERS_MANDATORY=true CACHE_HEADERS_MANDATORY=true
SHOCK_CACHE=true SHOCK_CACHE=true
TRUSTED_KEYS=true TRUSTED_KEYS=true
TORRENT_SEED_URL=https://webtorrent.shock.network
TORRENT_SEED_TOKEN=jibberish

View file

@ -45,6 +45,7 @@
"localtunnel": "^1.9.0", "localtunnel": "^1.9.0",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"method-override": "^2.3.7", "method-override": "^2.3.7",
"node-fetch": "^2.6.1",
"node-persist": "^3.1.0", "node-persist": "^3.1.0",
"promise": "^8.1.0", "promise": "^8.1.0",
"ramda": "^0.27.1", "ramda": "^0.27.1",
@ -68,6 +69,7 @@
"@types/jest": "^24.0.18", "@types/jest": "^24.0.18",
"@types/jsonwebtoken": "^8.3.7", "@types/jsonwebtoken": "^8.3.7",
"@types/lodash": "^4.14.141", "@types/lodash": "^4.14.141",
"@types/node-fetch": "^2.5.8",
"@types/ramda": "types/npm-ramda#dist", "@types/ramda": "types/npm-ramda#dist",
"@types/react": "16.x.x", "@types/react": "16.x.x",
"@types/socket.io": "^2.1.11", "@types/socket.io": "^2.1.11",

View file

@ -924,6 +924,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=} postID
* @prop {string=} ackInfo
*/ */
/** /**
@ -1296,6 +1297,7 @@ const setLastSeenApp = () =>
* @returns {Promise<[string, Common.Schema.RawPost]>} * @returns {Promise<[string, Common.Schema.RawPost]>}
*/ */
const createPostNew = async (tags, title, content) => { const createPostNew = async (tags, title, content) => {
const SEA = require('../Mediator').mySEA
/** @type {Common.Schema.RawPost} */ /** @type {Common.Schema.RawPost} */
const newPost = { const newPost = {
date: Date.now(), date: Date.now(),
@ -1311,6 +1313,23 @@ const createPostNew = async (tags, title, content) => {
newPost.contentItems[uuid] = c newPost.contentItems[uuid] = c
}) })
const mySecret = require('../Mediator').getMySecret()
await Common.Utils.asyncForEach(content, async c => {
// @ts-expect-error
const uuid = Gun.text.random()
newPost.contentItems[uuid] = c
if (
(c.type === 'image/embedded' || c.type === 'video/embedded') &&
c.isPrivate
) {
const encryptedMagnet = await SEA.encrypt(c.magnetURI, mySecret)
newPost.contentItems[uuid] = { ...c, magnetURI: encryptedMagnet }
} else {
newPost.contentItems[uuid] = c
}
})
/** @type {string} */ /** @type {string} */
const postID = await Common.makePromise((res, rej) => { const postID = await Common.makePromise((res, rej) => {
const _n = require('../Mediator') const _n = require('../Mediator')

View file

@ -11,6 +11,9 @@ const {
Constants: { ErrorCode }, Constants: { ErrorCode },
Schema Schema
} = Common } = Common
const { assertNever } = require('assert-never')
const crypto = require('crypto')
const fetch = require('node-fetch')
const LightningServices = require('../../../../utils/lightningServices') const LightningServices = require('../../../../utils/lightningServices')
const { const {
@ -20,6 +23,7 @@ const {
const { writeCoordinate } = require('../../../coordinates') const { writeCoordinate } = require('../../../coordinates')
const Key = require('../key') const Key = require('../key')
const Utils = require('../utils') const Utils = require('../utils')
const { gunUUID } = require('../../../../utils')
const getUser = () => require('../../Mediator').getUser() const getUser = () => require('../../Mediator').getUser()
@ -219,17 +223,178 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
/** /**
* @param {Common.InvoiceWhenListed} invoice * @param {Common.InvoiceWhenListed} invoice
*/ */
const onData = invoice => { const onData = async invoice => {
if (invoice.settled) { if (invoice.settled) {
writeCoordinate(invoice.r_hash.toString(), coord)
if (order.targetType === 'tip') { if (order.targetType === 'tip') {
getUser() getUser()
.get('postToTipCount') .get('postToTipCount')
// CAST: Checked above. // CAST: Checked above.
.get(/** @type {string} */ (order.ackInfo)) .get(/** @type {string} */ (order.ackInfo))
.set(null) // each item in the set is a tip .set(null) // each item in the set is a tip
} else if (order.targetType === 'contentReveal') {
// -----------------------------------------
logger.debug('Content Reveal')
//assuming digital product that only requires to be unlocked
const postID = order.ackInfo
if (!Common.isPopulatedString(postID)) {
logger.error(`Invalid post ID`)
logger.error(postID)
return
}
// TODO: do this reactively
const selectedPost = await new Promise(res => {
getUser()
.get(Key.POSTS_NEW)
.get(postID)
.load(res)
})
logger.debug(selectedPost)
if (Common.isPost(selectedPost)) {
logger.error('Post id provided does not correspond to a valid post')
return
}
/**
* @type {Record<string,string>} <contentID,decryptedRef>
*/
const contentsToSend = {}
const mySecret = require('../../Mediator').getMySecret()
logger.debug('SECRET OK')
let privateFound = false
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
}
privateFound = true
const decrypted = await SEA.decrypt(item.magnetURI, mySecret)
contentsToSend[contentID] = decrypted
}
)
if (!privateFound) {
logger.error(`Post provided does not contain private content`)
return
}
const ackData = { unlockedContents: contentsToSend }
const toSend = JSON.stringify(ackData)
const encrypted = await SEA.encrypt(toSend, secret)
const ordResponse = {
type: 'orderAck',
response: encrypted
}
logger.debug('RES READY')
const uuid = gunUUID()
orderResponse.ackNode = uuid
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
getUser()
.get(Key.ORDER_TO_RESPONSE)
.get(uuid)
.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()
}
})
}))
logger.debug('RES SENT CONTENT')
// ----------------------------------------------------------------------------------
} else if (order.targetType === 'spontaneousPayment') {
// no action required
} else if (order.targetType === 'torrentSeed') {
logger.debug('TORRENT')
const numberOfTokens = Number(order.ackInfo)
if (isNaN(numberOfTokens)) {
logger.error('ackInfo provided is not a valid number')
return
}
const seedUrl = process.env.TORRENT_SEED_URL
const seedToken = process.env.TORRENT_SEED_TOKEN
if (!seedUrl || !seedToken) {
logger.error('torrentSeed service not available')
return
}
logger.debug('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 TODO
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))
logger.debug('RES SEED OK')
const ackData = { seedUrl, tokens }
const toSend = JSON.stringify(ackData)
const encrypted = await SEA.encrypt(toSend, secret)
const serviceResponse = {
type: 'orderAck',
response: encrypted
}
console.log('RES SEED SENT')
const uuid = gunUUID()
orderResponse.ackNode = uuid
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
getUser()
.get(Key.ORDER_TO_RESPONSE)
.get(uuid)
.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()
}
})
}))
logger.debug('RES SENT SEED')
} else if (order.targetType === 'other') {
// TODO
} else {
assertNever(order.targetType)
} }
writeCoordinate(invoice.r_hash.toString(), coord)
stream.off() stream.off()
} }
} }

View file

@ -1192,12 +1192,35 @@ 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, postID, ackInfo } = req.body
if (type !== 'spont' && type !== 'post') { 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.`
})
}
const typesThatShouldContainAckInfo = [
'tip',
'torrentSeed',
'contentReveal'
]
const shouldContainAckInfo = typesThatShouldContainAckInfo.includes(type)
if (shouldContainAckInfo && !Common.isPopulatedString(ackInfo)) {
return res.status(400).json({
field: 'ackInfo',
errorMessage: `Transactions of type ${typesThatShouldContainAckInfo} should contain an ackInfo field.`
}) })
} }
@ -1241,7 +1264,8 @@ module.exports = async (
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 postID,
ackInfo
}) })
) )
} catch (e) { } catch (e) {

View file

@ -66,66 +66,6 @@ module.exports = (
} }
} }
const parseJSON = data => {
try {
if (typeof data === 'string') {
return JSON.parse(data)
}
return data
} catch (err) {
return data
}
}
const decryptEvent = ({ eventName, data, socket }) => {
try {
const deviceId = socket.handshake.query['x-shockwallet-device-id']
if (Encryption.isNonEncrypted(eventName)) {
return data
}
if (!data) {
return data
}
const parsedData = parseJSON(data)
if (!deviceId) {
throw {
field: 'deviceId',
message: 'Please specify a device ID'
}
}
if (!Encryption.isAuthorizedDevice({ deviceId })) {
throw {
field: 'deviceId',
message: 'Please exchange keys with the API before using the socket'
}
}
const decryptedKey = Encryption.decryptKey({
deviceId,
message: parsedData.encryptedKey
})
const decryptedMessage = Encryption.decryptMessage({
message: parsedData.encryptedData,
key: decryptedKey,
iv: parsedData.iv
})
const decryptedData = JSON.parse(decryptedMessage)
return decryptedData
} catch (err) {
logger.error(
`[SOCKET] An error has occurred while decrypting an event (${eventName}):`,
err
)
return socket.emit('encryption:error', err)
}
}
const onNewInvoice = (socket, subID) => { const onNewInvoice = (socket, subID) => {
const { lightning } = LightningServices.services const { lightning } = LightningServices.services
logger.warn('Subscribing to invoices socket...' + subID) logger.warn('Subscribing to invoices socket...' + subID)
@ -677,7 +617,7 @@ module.exports = (
} }
/** /**
* @param {Common.Schema.SimpleReceivedRequest[]} receivedReqs * @param {ReadonlyArray<Common.SimpleReceivedRequest>} receivedReqs
*/ */
const onReceivedReqs = receivedReqs => { const onReceivedReqs = receivedReqs => {
const processed = receivedReqs.map(({ id, requestorPK, timestamp }) => { const processed = receivedReqs.map(({ id, requestorPK, timestamp }) => {

View file

@ -1,9 +1,21 @@
/** /**
* @format * @format
*/ */
const Gun = require('gun')
const { asyncFilter } = require('./helpers') const { asyncFilter } = require('./helpers')
module.exports = { /**
asyncFilter * @returns {string}
*/
const gunUUID = () => {
// @ts-expect-error Not typed
const uuid = Gun.Text.random()
return uuid
}
module.exports = {
asyncFilter,
gunUUID
} }

View file

@ -722,6 +722,14 @@
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw== integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==
"@types/node-fetch@^2.5.8":
version "2.5.8"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb"
integrity sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*": "@types/node@*":
version "12.7.4" version "12.7.4"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.4.tgz#64db61e0359eb5a8d99b55e05c729f130a678b04" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.4.tgz#64db61e0359eb5a8d99b55e05c729f130a678b04"
@ -1838,7 +1846,7 @@ colour@~0.7.1:
resolved "https://registry.yarnpkg.com/colour/-/colour-0.7.1.tgz#9cb169917ec5d12c0736d3e8685746df1cadf778" resolved "https://registry.yarnpkg.com/colour/-/colour-0.7.1.tgz#9cb169917ec5d12c0736d3e8685746df1cadf778"
integrity sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g= integrity sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g=
combined-stream@^1.0.6, combined-stream@~1.0.6: combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
version "1.0.8" version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
@ -2946,6 +2954,15 @@ forever-agent@~0.6.1:
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@~2.3.2: form-data@~2.3.2:
version "2.3.3" version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
@ -4991,7 +5008,7 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
node-fetch@^2.3.0: node-fetch@^2.3.0, node-fetch@^2.6.1:
version "2.6.1" version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==