Merge branch 'master' into feature/tunnel
This commit is contained in:
commit
d7e7689e1f
30 changed files with 1785 additions and 953 deletions
|
|
@ -6,3 +6,5 @@ CACHE_HEADERS_MANDATORY=true
|
||||||
SHOCK_CACHE=true
|
SHOCK_CACHE=true
|
||||||
TRUSTED_KEYS=true
|
TRUSTED_KEYS=true
|
||||||
LOCAL_TUNNEL_SERVER=http://tunnel.example.com
|
LOCAL_TUNNEL_SERVER=http://tunnel.example.com
|
||||||
|
TORRENT_SEED_URL=https://webtorrent.shock.network
|
||||||
|
TORRENT_SEED_TOKEN=jibberish
|
||||||
|
|
|
||||||
1
.eslintignore
Normal file
1
.eslintignore
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
*.ts
|
||||||
|
|
@ -88,7 +88,10 @@
|
||||||
// I am now convinced TODO comments closer to the relevant code are better
|
// I am now convinced TODO comments closer to the relevant code are better
|
||||||
// than GH issues. Especially when it only concerns a single function /
|
// than GH issues. Especially when it only concerns a single function /
|
||||||
// routine.
|
// routine.
|
||||||
"no-warning-comments": "off"
|
"no-warning-comments": "off",
|
||||||
|
|
||||||
|
// broken
|
||||||
|
"sort-imports": "off"
|
||||||
},
|
},
|
||||||
"parser": "babel-eslint",
|
"parser": "babel-eslint",
|
||||||
"env": {
|
"env": {
|
||||||
|
|
|
||||||
36
.github/workflows/version.yml
vendored
Normal file
36
.github/workflows/version.yml
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
name: Bump "package.json" Version
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [prereleased, released]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
version-bump:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@master
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
fetch-depth: 0 # otherwise, you will failed to push refs to dest repo
|
||||||
|
- name: Get the last API TAG and current version in package.json
|
||||||
|
run: |
|
||||||
|
export RELEASE_TAG=$(git describe --tags --abbrev=0) && \
|
||||||
|
echo "VERSION=${RELEASE_TAG}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
export API_TAG=$(cat ./package.json | jq -r '.version')
|
||||||
|
|
||||||
|
echo $(if [ "$API_TAG" = "$RELEASE_TAG" ]; then echo "UPGRADEABLE=false"; else echo "UPGRADEABLE=true"; fi) >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Update and Commit files
|
||||||
|
if: ${{ env.UPGRADEABLE == 'true' }}
|
||||||
|
run: |
|
||||||
|
cat ./package.json | jq -r --arg API_TAG "${{ env.VERSION }}" '.version = $API_TAG' | tee a.json && mv a.json package.json
|
||||||
|
git config --local user.email "actions@shock.network"
|
||||||
|
git config --local user.name "Version Update Action"
|
||||||
|
git commit -m "version upgraded to ${{ env.VERSION }}" -a
|
||||||
|
- name: Push changes
|
||||||
|
if: ${{ env.UPGRADEABLE == 'true' }}
|
||||||
|
uses: ad-m/github-push-action@master
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
branch: master
|
||||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
{
|
{
|
||||||
"eslint.enable": true,
|
"eslint.enable": true,
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"debug.node.autoAttach": "on"
|
"debug.node.autoAttach": "on",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
33
.vscode/snippets.code-snippets
vendored
Normal file
33
.vscode/snippets.code-snippets
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
// Place your api workspace snippets here. Each snippet is defined under a
|
||||||
|
// snippet name and has a scope, prefix, body and description. Add comma
|
||||||
|
// separated ids of the languages where the snippet is applicable in the scope
|
||||||
|
// field. If scope is left empty or omitted, the snippet gets applied to all
|
||||||
|
// languages. The prefix is what is used to trigger the snippet and the body
|
||||||
|
// will be expanded and inserted. Possible variables are: $1, $2 for tab
|
||||||
|
// stops, $0 for the final cursor position, and ${1:label}, ${2:another} for
|
||||||
|
// placeholders. Placeholders with the same ids are connected. Example: "Print
|
||||||
|
// to console": {"scope": "javascript,typescript", "prefix": "log", "body":
|
||||||
|
// ["console.log('$1');", "$2"
|
||||||
|
// ],
|
||||||
|
// "description": "Log output to console"
|
||||||
|
// }
|
||||||
|
|
||||||
|
"Route Body": {
|
||||||
|
"body": [
|
||||||
|
"try {",
|
||||||
|
" return res.json({",
|
||||||
|
"",
|
||||||
|
" })",
|
||||||
|
"} catch (e) {",
|
||||||
|
" console.log(e)",
|
||||||
|
" return res.status(500).json({",
|
||||||
|
" errorMessage: e.message",
|
||||||
|
" })",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Route Body",
|
||||||
|
"prefix": "rbody",
|
||||||
|
"scope": "javascript"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<h1>ShockAPI</h1>
|
<h1>ShockAPI</h1>
|
||||||
|
|
||||||

|

|
||||||
[](http://makeapullrequest.com)
|
[](http://makeapullrequest.com)
|
||||||
[](https://t.me/Shockwallet)
|
[](https://t.me/Shockwallet)
|
||||||
[](https://twitter.com/shockbtc)
|
[](https://twitter.com/shockbtc)
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ module.exports = (mainnet = false) => {
|
||||||
maxNumRoutesToQuery: 20,
|
maxNumRoutesToQuery: 20,
|
||||||
lndProto: parsePath(`${__dirname}/rpc.proto`),
|
lndProto: parsePath(`${__dirname}/rpc.proto`),
|
||||||
routerProto: parsePath(`${__dirname}/router.proto`),
|
routerProto: parsePath(`${__dirname}/router.proto`),
|
||||||
|
invoicesProto: parsePath(`${__dirname}/invoices.proto`),
|
||||||
walletUnlockerProto: parsePath(`${__dirname}/walletunlocker.proto`),
|
walletUnlockerProto: parsePath(`${__dirname}/walletunlocker.proto`),
|
||||||
lndHost: "localhost:10009",
|
lndHost: "localhost:10009",
|
||||||
lndCertPath: parsePath(`${lndDirectory}/tls.cert`),
|
lndCertPath: parsePath(`${lndDirectory}/tls.cert`),
|
||||||
|
|
|
||||||
122
config/invoices.proto
Normal file
122
config/invoices.proto
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
import "rpc.proto";
|
||||||
|
|
||||||
|
package invoicesrpc;
|
||||||
|
|
||||||
|
option go_package = "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc";
|
||||||
|
|
||||||
|
// Invoices is a service that can be used to create, accept, settle and cancel
|
||||||
|
// invoices.
|
||||||
|
service Invoices {
|
||||||
|
/*
|
||||||
|
SubscribeSingleInvoice returns a uni-directional stream (server -> client)
|
||||||
|
to notify the client of state transitions of the specified invoice.
|
||||||
|
Initially the current invoice state is always sent out.
|
||||||
|
*/
|
||||||
|
rpc SubscribeSingleInvoice (SubscribeSingleInvoiceRequest)
|
||||||
|
returns (stream lnrpc.Invoice);
|
||||||
|
|
||||||
|
/*
|
||||||
|
CancelInvoice cancels a currently open invoice. If the invoice is already
|
||||||
|
canceled, this call will succeed. If the invoice is already settled, it will
|
||||||
|
fail.
|
||||||
|
*/
|
||||||
|
rpc CancelInvoice (CancelInvoiceMsg) returns (CancelInvoiceResp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
AddHoldInvoice creates a hold invoice. It ties the invoice to the hash
|
||||||
|
supplied in the request.
|
||||||
|
*/
|
||||||
|
rpc AddHoldInvoice (AddHoldInvoiceRequest) returns (AddHoldInvoiceResp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
SettleInvoice settles an accepted invoice. If the invoice is already
|
||||||
|
settled, this call will succeed.
|
||||||
|
*/
|
||||||
|
rpc SettleInvoice (SettleInvoiceMsg) returns (SettleInvoiceResp);
|
||||||
|
}
|
||||||
|
|
||||||
|
message CancelInvoiceMsg {
|
||||||
|
// Hash corresponding to the (hold) invoice to cancel.
|
||||||
|
bytes payment_hash = 1;
|
||||||
|
}
|
||||||
|
message CancelInvoiceResp {
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddHoldInvoiceRequest {
|
||||||
|
/*
|
||||||
|
An optional memo to attach along with the invoice. Used for record keeping
|
||||||
|
purposes for the invoice's creator, and will also be set in the description
|
||||||
|
field of the encoded payment request if the description_hash field is not
|
||||||
|
being used.
|
||||||
|
*/
|
||||||
|
string memo = 1;
|
||||||
|
|
||||||
|
// The hash of the preimage
|
||||||
|
bytes hash = 2;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The value of this invoice in satoshis
|
||||||
|
|
||||||
|
The fields value and value_msat are mutually exclusive.
|
||||||
|
*/
|
||||||
|
int64 value = 3;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The value of this invoice in millisatoshis
|
||||||
|
|
||||||
|
The fields value and value_msat are mutually exclusive.
|
||||||
|
*/
|
||||||
|
int64 value_msat = 10;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Hash (SHA-256) of a description of the payment. Used if the description of
|
||||||
|
payment (memo) is too long to naturally fit within the description field
|
||||||
|
of an encoded payment request.
|
||||||
|
*/
|
||||||
|
bytes description_hash = 4;
|
||||||
|
|
||||||
|
// Payment request expiry time in seconds. Default is 3600 (1 hour).
|
||||||
|
int64 expiry = 5;
|
||||||
|
|
||||||
|
// Fallback on-chain address.
|
||||||
|
string fallback_addr = 6;
|
||||||
|
|
||||||
|
// Delta to use for the time-lock of the CLTV extended to the final hop.
|
||||||
|
uint64 cltv_expiry = 7;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Route hints that can each be individually used to assist in reaching the
|
||||||
|
invoice's destination.
|
||||||
|
*/
|
||||||
|
repeated lnrpc.RouteHint route_hints = 8;
|
||||||
|
|
||||||
|
// Whether this invoice should include routing hints for private channels.
|
||||||
|
bool private = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddHoldInvoiceResp {
|
||||||
|
/*
|
||||||
|
A bare-bones invoice for a payment within the Lightning Network. With the
|
||||||
|
details of the invoice, the sender has all the data necessary to send a
|
||||||
|
payment to the recipient.
|
||||||
|
*/
|
||||||
|
string payment_request = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SettleInvoiceMsg {
|
||||||
|
// Externally discovered pre-image that should be used to settle the hold
|
||||||
|
// invoice.
|
||||||
|
bytes preimage = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SettleInvoiceResp {
|
||||||
|
}
|
||||||
|
|
||||||
|
message SubscribeSingleInvoiceRequest {
|
||||||
|
reserved 1;
|
||||||
|
|
||||||
|
// Hash corresponding to the (hold) invoice to subscribe to.
|
||||||
|
bytes r_hash = 2;
|
||||||
|
}
|
||||||
28
package.json
28
package.json
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "shockapi",
|
"name": "shockapi",
|
||||||
"version": "1.0.0",
|
"version": "2021.1.04",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "src/server.js",
|
"main": "src/server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
@ -11,13 +11,16 @@
|
||||||
"test:watch": "jest --no-cache --watch",
|
"test:watch": "jest --no-cache --watch",
|
||||||
"typecheck": "tsc",
|
"typecheck": "tsc",
|
||||||
"lint": "eslint \"services/gunDB/**/*.js\"",
|
"lint": "eslint \"services/gunDB/**/*.js\"",
|
||||||
"format": "prettier --write \"./**/*.js\""
|
"format": "prettier --write \"./**/*.js\"",
|
||||||
|
"test:gun": "ts-node src/__gun__tests__/*.ts && rimraf -rf GUN-TEST-*"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/proto-loader": "^0.5.1",
|
"@grpc/grpc-js": "^1.2.2",
|
||||||
"axios": "^0.20.0",
|
"@grpc/proto-loader": "^0.5.5",
|
||||||
|
"assert-never": "^1.2.1",
|
||||||
|
"axios": "^0.21.1",
|
||||||
"basic-auth": "^2.0.0",
|
"basic-auth": "^2.0.0",
|
||||||
"big.js": "^5.2.2",
|
"big.js": "^5.2.2",
|
||||||
"bitcore-lib": "^0.15.0",
|
"bitcore-lib": "^0.15.0",
|
||||||
|
|
@ -31,7 +34,7 @@
|
||||||
"debug": "^3.1.0",
|
"debug": "^3.1.0",
|
||||||
"dotenv": "^8.1.0",
|
"dotenv": "^8.1.0",
|
||||||
"express": "^4.14.1",
|
"express": "^4.14.1",
|
||||||
"express-session": "^1.15.1",
|
"express-session": "^1.17.1",
|
||||||
"google-proto-files": "^1.0.3",
|
"google-proto-files": "^1.0.3",
|
||||||
"graphviz": "0.0.8",
|
"graphviz": "0.0.8",
|
||||||
"grpc": "1.24.4",
|
"grpc": "1.24.4",
|
||||||
|
|
@ -42,15 +45,16 @@
|
||||||
"localtunnel": "git://github.com/shocknet/localtunnel#40cc2c2a46b05da2217bf2e20da11a5343a5cce7",
|
"localtunnel": "git://github.com/shocknet/localtunnel#40cc2c2a46b05da2217bf2e20da11a5343a5cce7",
|
||||||
"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",
|
||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"ramda": "^0.27.1",
|
"ramda": "^0.27.1",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"request-promise": "^4.2.2",
|
"request-promise": "^4.2.6",
|
||||||
"response-time": "^2.3.2",
|
"response-time": "^2.3.2",
|
||||||
"shelljs": "^0.8.2",
|
"shelljs": "^0.8.2",
|
||||||
"shock-common": "17.x.x",
|
"shock-common": "32.0.0",
|
||||||
"socket.io": "2.1.1",
|
"socket.io": "2.1.1",
|
||||||
"text-encoding": "^0.7.0",
|
"text-encoding": "^0.7.0",
|
||||||
"tingodb": "^0.6.1",
|
"tingodb": "^0.6.1",
|
||||||
|
|
@ -58,7 +62,7 @@
|
||||||
"winston-daily-rotate-file": "^4.5.0"
|
"winston-daily-rotate-file": "^4.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-class-properties": "^7.5.5",
|
"@babel/plugin-proposal-class-properties": "^7.12.1",
|
||||||
"@types/bluebird": "^3.5.32",
|
"@types/bluebird": "^3.5.32",
|
||||||
"@types/dotenv": "^6.1.1",
|
"@types/dotenv": "^6.1.1",
|
||||||
"@types/express": "^4.17.1",
|
"@types/express": "^4.17.1",
|
||||||
|
|
@ -66,10 +70,12 @@
|
||||||
"@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/socket.io": "^2.1.11",
|
"@types/socket.io": "^2.1.11",
|
||||||
"@types/uuid": "^3.4.5",
|
"@types/uuid": "^3.4.5",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.1.0",
|
||||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
|
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
|
||||||
"eslint": "^6.6.0",
|
"eslint": "^6.6.0",
|
||||||
"eslint-config-prettier": "^6.5.0",
|
"eslint-config-prettier": "^6.5.0",
|
||||||
|
|
@ -80,8 +86,10 @@
|
||||||
"lint-staged": "^10.2.2",
|
"lint-staged": "^10.2.2",
|
||||||
"nodemon": "^1.19.3",
|
"nodemon": "^1.19.3",
|
||||||
"prettier": "^1.18.2",
|
"prettier": "^1.18.2",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"ts-node": "^9.1.1",
|
||||||
"ts-type": "^1.2.16",
|
"ts-type": "^1.2.16",
|
||||||
"typescript": "^4.0.2"
|
"typescript": "latest"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.js": [
|
"*.js": [
|
||||||
|
|
|
||||||
63
services/coordinates.js
Normal file
63
services/coordinates.js
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
/**
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Common = require('shock-common')
|
||||||
|
const mapValues = require('lodash/mapValues')
|
||||||
|
const pickBy = require('lodash/pickBy')
|
||||||
|
const Bluebird = require('bluebird')
|
||||||
|
const Logger = require('winston')
|
||||||
|
const Key = require('../services/gunDB/contact-api/key')
|
||||||
|
|
||||||
|
const { getUser, getMySecret, mySEA } = require('./gunDB/Mediator')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} coordID
|
||||||
|
* @param {Common.Coordinate} data
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export const writeCoordinate = async (coordID, data) => {
|
||||||
|
if (coordID !== data.id) {
|
||||||
|
throw new Error('CoordID must be equal to data.id')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
/**
|
||||||
|
* Because there are optional properties, typescript can also allow them
|
||||||
|
* to be specified but with a value of `undefined`. Filter out these.
|
||||||
|
* @type {Record<string, number|boolean|string>}
|
||||||
|
*/
|
||||||
|
const sanitizedData = pickBy(data, v => typeof v !== 'undefined')
|
||||||
|
|
||||||
|
const encData = await Bluebird.props(
|
||||||
|
mapValues(sanitizedData, v => {
|
||||||
|
return mySEA.encrypt(v, getMySecret())
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
getUser()
|
||||||
|
.get(Key.COORDINATES)
|
||||||
|
.get(coordID)
|
||||||
|
.put(encData, ack => {
|
||||||
|
if (ack.err && typeof ack.err !== 'number') {
|
||||||
|
Logger.info(
|
||||||
|
`Error writting corrdinate, coordinate id: ${coordID}, data: ${JSON.stringify(
|
||||||
|
data,
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
Logger.error(ack.err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
Logger.info(
|
||||||
|
`Error writing coordinate, coordinate id: ${coordID}, data: ${JSON.stringify(
|
||||||
|
data,
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
Logger.error(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -26,23 +26,13 @@ const SEAx = require('gun/sea')
|
||||||
/** @type {import('../contact-api/SimpleGUN').ISEA} */
|
/** @type {import('../contact-api/SimpleGUN').ISEA} */
|
||||||
const mySEA = {}
|
const mySEA = {}
|
||||||
|
|
||||||
const $$__SHOCKWALLET__MSG__ = '$$__SHOCKWALLET__MSG__'
|
// Avoid this: https://github.com/amark/gun/issues/804 and any other issues
|
||||||
const $$__SHOCKWALLET__ENCRYPTED__ = '$$_SHOCKWALLET__ENCRYPTED__'
|
const $$__SHOCKWALLET__ENCRYPTED__ = '$$_SHOCKWALLET__ENCRYPTED__'
|
||||||
|
const $$__SHOCKWALLET__MSG__ = '$$__SHOCKWALLET__MSG__'
|
||||||
|
const $$__SHOCKWALLET__NUMBER__ = '$$__SHOCKWALLET__NUMBER__'
|
||||||
|
const $$__SHOCKWALLET__BOOLEAN__ = '$$__SHOCKWALLET__BOOLEAN__'
|
||||||
|
|
||||||
mySEA.encrypt = (msg, secret) => {
|
mySEA.encrypt = (msg, secret) => {
|
||||||
if (typeof msg !== 'string') {
|
|
||||||
throw new TypeError(
|
|
||||||
'mySEA.encrypt() -> expected msg to be an string instead got: ' +
|
|
||||||
typeof msg
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.length === 0) {
|
|
||||||
throw new TypeError(
|
|
||||||
'mySEA.encrypt() -> expected msg to be a populated string'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof secret !== 'string') {
|
if (typeof secret !== 'string') {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
`mySEA.encrypt() -> expected secret to be a an string, args: |msg| -- ${JSON.stringify(
|
`mySEA.encrypt() -> expected secret to be a an string, args: |msg| -- ${JSON.stringify(
|
||||||
|
|
@ -57,15 +47,35 @@ mySEA.encrypt = (msg, secret) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid this: https://github.com/amark/gun/issues/804 and any other issues
|
let strToEncode = ''
|
||||||
const sanitizedMsg = $$__SHOCKWALLET__MSG__ + msg
|
|
||||||
|
|
||||||
return SEAx.encrypt(sanitizedMsg, secret).then(encMsg => {
|
if (typeof msg === 'string') {
|
||||||
|
if (msg.length === 0) {
|
||||||
|
throw new TypeError(
|
||||||
|
'mySEA.encrypt() -> expected msg to be a populated string'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
strToEncode = $$__SHOCKWALLET__MSG__ + msg
|
||||||
|
} else if (typeof msg === 'boolean') {
|
||||||
|
strToEncode = $$__SHOCKWALLET__BOOLEAN__ + msg
|
||||||
|
} else if (typeof msg === 'number') {
|
||||||
|
strToEncode = $$__SHOCKWALLET__NUMBER__ + msg
|
||||||
|
} else {
|
||||||
|
throw new TypeError('mySea.encrypt() -> Not a valid msg type.')
|
||||||
|
}
|
||||||
|
|
||||||
|
return SEAx.encrypt(strToEncode, secret).then(encMsg => {
|
||||||
return $$__SHOCKWALLET__ENCRYPTED__ + encMsg
|
return $$__SHOCKWALLET__ENCRYPTED__ + encMsg
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
mySEA.decrypt = (encMsg, secret) => {
|
/**
|
||||||
|
* @param {string} encMsg
|
||||||
|
* @param {string} secret
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
const decryptBase = (encMsg, secret) => {
|
||||||
if (typeof encMsg !== 'string') {
|
if (typeof encMsg !== 'string') {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
'mySEA.encrypt() -> expected encMsg to be an string instead got: ' +
|
'mySEA.encrypt() -> expected encMsg to be an string instead got: ' +
|
||||||
|
|
@ -104,10 +114,41 @@ mySEA.decrypt = (encMsg, secret) => {
|
||||||
throw new TypeError('Could not decrypt')
|
throw new TypeError('Could not decrypt')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (decodedMsg.startsWith($$__SHOCKWALLET__MSG__)) {
|
||||||
return decodedMsg.slice($$__SHOCKWALLET__MSG__.length)
|
return decodedMsg.slice($$__SHOCKWALLET__MSG__.length)
|
||||||
|
} else if (decodedMsg.startsWith($$__SHOCKWALLET__BOOLEAN__)) {
|
||||||
|
const dec = decodedMsg.slice($$__SHOCKWALLET__BOOLEAN__.length)
|
||||||
|
if (dec === 'true') {
|
||||||
|
return true
|
||||||
|
} else if (dec === 'false') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
throw new Error('Could not decrypt boolean value.')
|
||||||
|
} else if (decodedMsg.startsWith($$__SHOCKWALLET__NUMBER__)) {
|
||||||
|
return Number(decodedMsg.slice($$__SHOCKWALLET__NUMBER__.length))
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TypeError(
|
||||||
|
`mySea.encrypt() -> Unexpected type of prefix found inside decrypted value, first 20 characters: ${decodedMsg.slice(
|
||||||
|
0,
|
||||||
|
20
|
||||||
|
)}`
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mySEA.decrypt = (encMsg, secret) => {
|
||||||
|
return decryptBase(encMsg, secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
mySEA.decryptNumber = (encMsg, secret) => {
|
||||||
|
return decryptBase(encMsg, secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
mySEA.decryptBoolean = (encMsg, secret) => {
|
||||||
|
return decryptBase(encMsg, secret)
|
||||||
|
}
|
||||||
|
|
||||||
mySEA.secret = async (recipientOrSenderEpub, recipientOrSenderSEA) => {
|
mySEA.secret = async (recipientOrSenderEpub, recipientOrSenderSEA) => {
|
||||||
if (typeof recipientOrSenderEpub !== 'string') {
|
if (typeof recipientOrSenderEpub !== 'string') {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
|
|
@ -273,7 +314,7 @@ const authenticate = async (alias, pass, __user) => {
|
||||||
// clock skew
|
// clock skew
|
||||||
await new Promise(res => setTimeout(res, 2000))
|
await new Promise(res => setTimeout(res, 2000))
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
_user.get(Key.FOLLOWS).put(
|
_user.get(Key.FOLLOWS).put(
|
||||||
{
|
{
|
||||||
unused: null
|
unused: null
|
||||||
|
|
@ -286,7 +327,7 @@ const authenticate = async (alias, pass, __user) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
}))
|
||||||
|
|
||||||
return ack.sea.pub
|
return ack.sea.pub
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -304,7 +345,7 @@ const authenticate = async (alias, pass, __user) => {
|
||||||
// clock skew
|
// clock skew
|
||||||
await new Promise(res => setTimeout(res, 2000))
|
await new Promise(res => setTimeout(res, 2000))
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
_user.get(Key.FOLLOWS).put(
|
_user.get(Key.FOLLOWS).put(
|
||||||
{
|
{
|
||||||
unused: null
|
unused: null
|
||||||
|
|
@ -317,7 +358,7 @@ const authenticate = async (alias, pass, __user) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
}))
|
||||||
|
|
||||||
// move this to a subscription; implement off() ? todo
|
// move this to a subscription; implement off() ? todo
|
||||||
API.Jobs.onAcceptedRequests(_user, mySEA)
|
API.Jobs.onAcceptedRequests(_user, mySEA)
|
||||||
|
|
@ -363,7 +404,7 @@ const authenticate = async (alias, pass, __user) => {
|
||||||
|
|
||||||
await new Promise(res => setTimeout(res, 5000))
|
await new Promise(res => setTimeout(res, 5000))
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
_user.get(Key.FOLLOWS).put(
|
_user.get(Key.FOLLOWS).put(
|
||||||
{
|
{
|
||||||
unused: null
|
unused: null
|
||||||
|
|
@ -376,7 +417,7 @@ const authenticate = async (alias, pass, __user) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
}))
|
||||||
|
|
||||||
API.Jobs.onAcceptedRequests(_user, mySEA)
|
API.Jobs.onAcceptedRequests(_user, mySEA)
|
||||||
API.Jobs.onOrders(_user, gun, mySEA)
|
API.Jobs.onOrders(_user, gun, mySEA)
|
||||||
|
|
|
||||||
|
|
@ -120,8 +120,19 @@ export interface UserGUNNode extends GUNNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISEA {
|
export interface ISEA {
|
||||||
encrypt(message: string, senderSecret: string): Promise<string>
|
encrypt(
|
||||||
|
message: string | number | boolean,
|
||||||
|
senderSecret: string
|
||||||
|
): Promise<string>
|
||||||
decrypt(encryptedMessage: string, recipientSecret: string): Promise<string>
|
decrypt(encryptedMessage: string, recipientSecret: string): Promise<string>
|
||||||
|
decryptNumber(
|
||||||
|
encryptedMessage: string,
|
||||||
|
recipientSecret: string
|
||||||
|
): Promise<number>
|
||||||
|
decryptBoolean(
|
||||||
|
encryptedMessage: string,
|
||||||
|
recipientSecret: string
|
||||||
|
): Promise<boolean>
|
||||||
secret(
|
secret(
|
||||||
recipientOrSenderEpub: string,
|
recipientOrSenderEpub: string,
|
||||||
recipientOrSenderUserPair: UserPair
|
recipientOrSenderUserPair: UserPair
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ const { ErrorCode } = Constants
|
||||||
|
|
||||||
const {
|
const {
|
||||||
sendPaymentV2Invoice,
|
sendPaymentV2Invoice,
|
||||||
decodePayReq
|
decodePayReq,
|
||||||
|
myLNDPub
|
||||||
} = require('../../../utils/lightningServices/v2')
|
} = require('../../../utils/lightningServices/v2')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -21,6 +22,7 @@ const {
|
||||||
const Getters = require('./getters')
|
const Getters = require('./getters')
|
||||||
const Key = require('./key')
|
const Key = require('./key')
|
||||||
const Utils = require('./utils')
|
const Utils = require('./utils')
|
||||||
|
const { writeCoordinate } = require('../../coordinates')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./SimpleGUN').GUNNode} GUNNode
|
* @typedef {import('./SimpleGUN').GUNNode} GUNNode
|
||||||
|
|
@ -98,7 +100,7 @@ const __createOutgoingFeed = async (withPublicKey, user, SEA) => {
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
user
|
user
|
||||||
.get(Key.OUTGOINGS)
|
.get(Key.OUTGOINGS)
|
||||||
.get(newOutgoingFeedID)
|
.get(newOutgoingFeedID)
|
||||||
|
|
@ -111,14 +113,14 @@ const __createOutgoingFeed = async (withPublicKey, user, SEA) => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
|
|
||||||
const encryptedForMeNewOutgoingFeedID = await SEA.encrypt(
|
const encryptedForMeNewOutgoingFeedID = await SEA.encrypt(
|
||||||
newOutgoingFeedID,
|
newOutgoingFeedID,
|
||||||
mySecret
|
mySecret
|
||||||
)
|
)
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
user
|
user
|
||||||
.get(Key.RECIPIENT_TO_OUTGOING)
|
.get(Key.RECIPIENT_TO_OUTGOING)
|
||||||
.get(withPublicKey)
|
.get(withPublicKey)
|
||||||
|
|
@ -129,7 +131,7 @@ const __createOutgoingFeed = async (withPublicKey, user, SEA) => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
|
|
||||||
outgoingFeedID = newOutgoingFeedID
|
outgoingFeedID = newOutgoingFeedID
|
||||||
}
|
}
|
||||||
|
|
@ -235,7 +237,7 @@ const acceptRequest = async (
|
||||||
const mySecret = require('../Mediator').getMySecret()
|
const mySecret = require('../Mediator').getMySecret()
|
||||||
const encryptedForMeIncomingID = await SEA.encrypt(incomingID, mySecret)
|
const encryptedForMeIncomingID = await SEA.encrypt(incomingID, mySecret)
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
user
|
user
|
||||||
.get(Key.USER_TO_INCOMING)
|
.get(Key.USER_TO_INCOMING)
|
||||||
.get(senderPublicKey)
|
.get(senderPublicKey)
|
||||||
|
|
@ -246,7 +248,7 @@ const acceptRequest = async (
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
// NOTE: perform non-reversable actions before destructive actions
|
// NOTE: perform non-reversable actions before destructive actions
|
||||||
|
|
@ -259,7 +261,7 @@ const acceptRequest = async (
|
||||||
ourSecret
|
ourSecret
|
||||||
)
|
)
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
gun
|
gun
|
||||||
.get(Key.HANDSHAKE_NODES)
|
.get(Key.HANDSHAKE_NODES)
|
||||||
.get(handshakeAddress)
|
.get(handshakeAddress)
|
||||||
|
|
@ -276,7 +278,7 @@ const acceptRequest = async (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -285,7 +287,7 @@ const acceptRequest = async (
|
||||||
* @param {UserGUNNode} userNode
|
* @param {UserGUNNode} userNode
|
||||||
*/
|
*/
|
||||||
const authenticate = (user, pass, userNode) =>
|
const authenticate = (user, pass, userNode) =>
|
||||||
new Promise((resolve, reject) => {
|
/** @type {Promise<void>} */ (new Promise((resolve, reject) => {
|
||||||
if (typeof user !== 'string') {
|
if (typeof user !== 'string') {
|
||||||
throw new TypeError('expected user to be of type string')
|
throw new TypeError('expected user to be of type string')
|
||||||
}
|
}
|
||||||
|
|
@ -315,7 +317,7 @@ const authenticate = (user, pass, userNode) =>
|
||||||
resolve()
|
resolve()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} publicKey
|
* @param {string} publicKey
|
||||||
|
|
@ -347,7 +349,7 @@ const generateHandshakeAddress = async () => {
|
||||||
|
|
||||||
const address = uuidv1()
|
const address = uuidv1()
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
user.get(Key.CURRENT_HANDSHAKE_ADDRESS).put(address, ack => {
|
user.get(Key.CURRENT_HANDSHAKE_ADDRESS).put(address, ack => {
|
||||||
if (ack.err && typeof ack.err !== 'number') {
|
if (ack.err && typeof ack.err !== 'number') {
|
||||||
rej(new Error(ack.err))
|
rej(new Error(ack.err))
|
||||||
|
|
@ -355,9 +357,9 @@ const generateHandshakeAddress = async () => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
gun
|
gun
|
||||||
.get(Key.HANDSHAKE_NODES)
|
.get(Key.HANDSHAKE_NODES)
|
||||||
.get(address)
|
.get(address)
|
||||||
|
|
@ -368,7 +370,7 @@ const generateHandshakeAddress = async () => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -385,7 +387,7 @@ const cleanup = async pub => {
|
||||||
const promises = []
|
const promises = []
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
new Promise((res, rej) => {
|
/** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
user
|
user
|
||||||
.get(Key.USER_TO_INCOMING)
|
.get(Key.USER_TO_INCOMING)
|
||||||
.get(pub)
|
.get(pub)
|
||||||
|
|
@ -396,11 +398,11 @@ const cleanup = async pub => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
new Promise((res, rej) => {
|
/** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
user
|
user
|
||||||
.get(Key.RECIPIENT_TO_OUTGOING)
|
.get(Key.RECIPIENT_TO_OUTGOING)
|
||||||
.get(pub)
|
.get(pub)
|
||||||
|
|
@ -411,11 +413,11 @@ const cleanup = async pub => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
new Promise((res, rej) => {
|
/** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
user
|
user
|
||||||
.get(Key.USER_TO_LAST_REQUEST_SENT)
|
.get(Key.USER_TO_LAST_REQUEST_SENT)
|
||||||
.get(pub)
|
.get(pub)
|
||||||
|
|
@ -426,12 +428,12 @@ const cleanup = async pub => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
if (outGoingID) {
|
if (outGoingID) {
|
||||||
promises.push(
|
promises.push(
|
||||||
new Promise((res, rej) => {
|
/** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
user
|
user
|
||||||
.get(Key.OUTGOINGS)
|
.get(Key.OUTGOINGS)
|
||||||
.get(outGoingID)
|
.get(outGoingID)
|
||||||
|
|
@ -442,7 +444,7 @@ const cleanup = async pub => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -616,7 +618,7 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
user
|
user
|
||||||
.get(Key.USER_TO_LAST_REQUEST_SENT)
|
.get(Key.USER_TO_LAST_REQUEST_SENT)
|
||||||
.get(recipientPublicKey)
|
.get(recipientPublicKey)
|
||||||
|
|
@ -627,7 +629,7 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
|
|
||||||
// This needs to come before the write to sent requests. Because that write
|
// This needs to come before the write to sent requests. Because that write
|
||||||
// triggers Jobs.onAcceptedRequests and it in turn reads from request-to-user
|
// triggers Jobs.onAcceptedRequests and it in turn reads from request-to-user
|
||||||
|
|
@ -642,7 +644,7 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => {
|
||||||
timestamp
|
timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
user.get(Key.STORED_REQS).set(storedReq, ack => {
|
user.get(Key.STORED_REQS).set(storedReq, ack => {
|
||||||
if (ack.err && typeof ack.err !== 'number') {
|
if (ack.err && typeof ack.err !== 'number') {
|
||||||
|
|
@ -655,7 +657,7 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -922,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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -940,7 +943,7 @@ const sendSpontaneousPayment = async (
|
||||||
amount,
|
amount,
|
||||||
memo,
|
memo,
|
||||||
feeLimit,
|
feeLimit,
|
||||||
opts = { type: 'user' }
|
opts = { type: 'spontaneousPayment' }
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const SEA = require('../Mediator').mySEA
|
const SEA = require('../Mediator').mySEA
|
||||||
|
|
@ -965,8 +968,8 @@ const sendSpontaneousPayment = async (
|
||||||
targetType: opts.type
|
targetType: opts.type
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.type === 'post') {
|
if (opts.type === 'tip') {
|
||||||
order.postID = opts.postID
|
order.ackInfo = opts.postID
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(JSON.stringify(order))
|
logger.info(JSON.stringify(order))
|
||||||
|
|
@ -1074,6 +1077,32 @@ const sendSpontaneousPayment = async (
|
||||||
payment_request: orderResponse.response
|
payment_request: orderResponse.response
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await writeCoordinate(payment.payment_hash, {
|
||||||
|
id: payment.payment_hash,
|
||||||
|
type: (() => {
|
||||||
|
if (opts.type === 'tip') {
|
||||||
|
return 'tip'
|
||||||
|
} else if (opts.type === 'spontaneousPayment') {
|
||||||
|
return 'spontaneousPayment'
|
||||||
|
} else if (opts.type === 'contentReveal') {
|
||||||
|
return 'other' // TODO
|
||||||
|
} else if (opts.type === 'other') {
|
||||||
|
return 'other' // TODO
|
||||||
|
} else if (opts.type === 'torrentSeed') {
|
||||||
|
return 'other' // TODO
|
||||||
|
}
|
||||||
|
// ensures we handle all possible types
|
||||||
|
/** @type {never} */
|
||||||
|
const assertNever = opts.type
|
||||||
|
|
||||||
|
return assertNever && opts.type // please TS
|
||||||
|
})(),
|
||||||
|
amount: Number(payment.value_sat),
|
||||||
|
inbound: false,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
toLndPub: await myLNDPub()
|
||||||
|
})
|
||||||
|
|
||||||
return payment
|
return payment
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('Error inside sendPayment()')
|
logger.error('Error inside sendPayment()')
|
||||||
|
|
@ -1125,7 +1154,7 @@ const generateOrderAddress = user =>
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const setBio = (bio, user) =>
|
const setBio = (bio, user) =>
|
||||||
new Promise((resolve, reject) => {
|
/** @type {Promise<void>} */ (new Promise((resolve, reject) => {
|
||||||
if (!user.is) {
|
if (!user.is) {
|
||||||
throw new Error(ErrorCode.NOT_AUTH)
|
throw new Error(ErrorCode.NOT_AUTH)
|
||||||
}
|
}
|
||||||
|
|
@ -1149,7 +1178,7 @@ const setBio = (bio, user) =>
|
||||||
resolve()
|
resolve()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}).then(
|
})).then(
|
||||||
() =>
|
() =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
user
|
user
|
||||||
|
|
@ -1233,7 +1262,7 @@ const disconnect = async pub => {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const setLastSeenApp = () =>
|
const setLastSeenApp = () =>
|
||||||
new Promise((res, rej) => {
|
/** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
require('../Mediator')
|
require('../Mediator')
|
||||||
.getUser()
|
.getUser()
|
||||||
.get(Key.LAST_SEEN_APP)
|
.get(Key.LAST_SEEN_APP)
|
||||||
|
|
@ -1244,7 +1273,7 @@ const setLastSeenApp = () =>
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}).then(
|
})).then(
|
||||||
() =>
|
() =>
|
||||||
new Promise((res, rej) => {
|
new Promise((res, rej) => {
|
||||||
require('../Mediator')
|
require('../Mediator')
|
||||||
|
|
@ -1268,14 +1297,14 @@ 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(),
|
||||||
status: 'publish',
|
status: 'publish',
|
||||||
tags: tags.join('-'),
|
tags: tags.join('-'),
|
||||||
title,
|
title,
|
||||||
contentItems: {},
|
contentItems: {}
|
||||||
tipCounter: 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
content.forEach(c => {
|
content.forEach(c => {
|
||||||
|
|
@ -1284,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')
|
||||||
|
|
@ -1365,7 +1411,7 @@ const createPost = async (tags, title, content) => {
|
||||||
pageIdx = Number(pageIdx + 1).toString()
|
pageIdx = Number(pageIdx + 1).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
require('../Mediator')
|
require('../Mediator')
|
||||||
.getUser()
|
.getUser()
|
||||||
.get(Key.WALL)
|
.get(Key.WALL)
|
||||||
|
|
@ -1386,7 +1432,7 @@ const createPost = async (tags, title, content) => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
}))
|
||||||
|
|
||||||
const [postID, newPost] = await createPostNew(tags, title, content)
|
const [postID, newPost] = await createPostNew(tags, title, content)
|
||||||
|
|
||||||
|
|
@ -1412,7 +1458,7 @@ const createPost = async (tags, title, content) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (shouldBeNewPage || numOfPages === 0) {
|
if (shouldBeNewPage || numOfPages === 0) {
|
||||||
await new Promise(res => {
|
await /** @type {Promise<void>} */ (new Promise(res => {
|
||||||
require('../Mediator')
|
require('../Mediator')
|
||||||
.getUser()
|
.getUser()
|
||||||
.get(Key.WALL)
|
.get(Key.WALL)
|
||||||
|
|
@ -1424,7 +1470,7 @@ const createPost = async (tags, title, content) => {
|
||||||
|
|
||||||
res()
|
res()
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadedPost = await new Promise(res => {
|
const loadedPost = await new Promise(res => {
|
||||||
|
|
@ -1467,7 +1513,7 @@ const createPost = async (tags, title, content) => {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const deletePost = async (postId, page) => {
|
const deletePost = async (postId, page) => {
|
||||||
await new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
require('../Mediator')
|
require('../Mediator')
|
||||||
.getUser()
|
.getUser()
|
||||||
.get(Key.WALL)
|
.get(Key.WALL)
|
||||||
|
|
@ -1482,15 +1528,15 @@ const deletePost = async (postId, page) => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} publicKey
|
* @param {string} publicKey
|
||||||
* @param {boolean} isPrivate Will overwrite previous private status.
|
* @param {boolean} isPrivate Will overwrite previous private status.
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const follow = (publicKey, isPrivate) => {
|
const follow = async (publicKey, isPrivate) => {
|
||||||
/** @type {import('shock-common').Schema.Follow} */
|
/** @type {import('shock-common').Schema.Follow} */
|
||||||
const newFollow = {
|
const newFollow = {
|
||||||
private: isPrivate,
|
private: isPrivate,
|
||||||
|
|
@ -1498,7 +1544,7 @@ const follow = (publicKey, isPrivate) => {
|
||||||
user: publicKey
|
user: publicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
require('../Mediator')
|
require('../Mediator')
|
||||||
.getUser()
|
.getUser()
|
||||||
.get(Key.FOLLOWS)
|
.get(Key.FOLLOWS)
|
||||||
|
|
@ -1511,7 +1557,7 @@ const follow = (publicKey, isPrivate) => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1543,7 +1589,7 @@ const initWall = async () => {
|
||||||
const promises = []
|
const promises = []
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
new Promise((res, rej) => {
|
/** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
user
|
user
|
||||||
.get(Key.WALL)
|
.get(Key.WALL)
|
||||||
.get(Key.NUM_OF_PAGES)
|
.get(Key.NUM_OF_PAGES)
|
||||||
|
|
@ -1554,11 +1600,11 @@ const initWall = async () => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
new Promise((res, rej) => {
|
/** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
user
|
user
|
||||||
.get(Key.WALL)
|
.get(Key.WALL)
|
||||||
.get(Key.PAGES)
|
.get(Key.PAGES)
|
||||||
|
|
@ -1576,11 +1622,11 @@ const initWall = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
new Promise((res, rej) => {
|
/** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
user
|
user
|
||||||
.get(Key.WALL)
|
.get(Key.WALL)
|
||||||
.get(Key.PAGES)
|
.get(Key.PAGES)
|
||||||
|
|
@ -1593,7 +1639,7 @@ const initWall = async () => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
await Promise.all(promises)
|
await Promise.all(promises)
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ const onAcceptedRequests = (user, SEA) => {
|
||||||
const recipientEpub = await Utils.pubToEpub(recipientPub)
|
const recipientEpub = await Utils.pubToEpub(recipientPub)
|
||||||
const ourSecret = await SEA.secret(recipientEpub, user._.sea)
|
const ourSecret = await SEA.secret(recipientEpub, user._.sea)
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
gun
|
gun
|
||||||
.get(Key.HANDSHAKE_NODES)
|
.get(Key.HANDSHAKE_NODES)
|
||||||
.get(requestAddress)
|
.get(requestAddress)
|
||||||
|
|
@ -151,7 +151,7 @@ const onAcceptedRequests = (user, SEA) => {
|
||||||
mySecret
|
mySecret
|
||||||
)
|
)
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
user
|
user
|
||||||
.get(Key.USER_TO_INCOMING)
|
.get(Key.USER_TO_INCOMING)
|
||||||
.get(recipientPub)
|
.get(recipientPub)
|
||||||
|
|
@ -162,9 +162,9 @@ const onAcceptedRequests = (user, SEA) => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
user
|
user
|
||||||
.get(Key.STORED_REQS)
|
.get(Key.STORED_REQS)
|
||||||
.get(id)
|
.get(id)
|
||||||
|
|
@ -175,12 +175,12 @@ const onAcceptedRequests = (user, SEA) => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
|
|
||||||
// ensure this listeners gets called at least once
|
// ensure this listeners gets called at least once
|
||||||
res()
|
res()
|
||||||
})
|
})
|
||||||
})
|
}))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn(`Jobs.onAcceptedRequests() -> ${err.message}`)
|
logger.warn(`Jobs.onAcceptedRequests() -> ${err.message}`)
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,29 @@
|
||||||
/**
|
/**
|
||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
// @ts-check
|
||||||
const { performance } = require('perf_hooks')
|
|
||||||
const logger = require('winston')
|
const logger = require('winston')
|
||||||
const isFinite = require('lodash/isFinite')
|
const isFinite = require('lodash/isFinite')
|
||||||
const isNumber = require('lodash/isNumber')
|
const isNumber = require('lodash/isNumber')
|
||||||
const isNaN = require('lodash/isNaN')
|
const isNaN = require('lodash/isNaN')
|
||||||
|
const Common = require('shock-common')
|
||||||
const {
|
const {
|
||||||
Constants: { ErrorCode },
|
Constants: { ErrorCode },
|
||||||
Schema
|
Schema
|
||||||
} = require('shock-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 {
|
||||||
|
addInvoice,
|
||||||
|
myLNDPub
|
||||||
|
} = require('../../../../utils/lightningServices/v2')
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
@ -55,28 +63,6 @@ const ordersProcessed = new Set()
|
||||||
|
|
||||||
let currentOrderAddr = ''
|
let currentOrderAddr = ''
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {InvoiceRequest} invoiceReq
|
|
||||||
* @returns {Promise<InvoiceResponse>}
|
|
||||||
*/
|
|
||||||
const _addInvoice = invoiceReq =>
|
|
||||||
new Promise((resolve, rej) => {
|
|
||||||
const {
|
|
||||||
services: { lightning }
|
|
||||||
} = LightningServices
|
|
||||||
|
|
||||||
lightning.addInvoice(invoiceReq, (
|
|
||||||
/** @type {any} */ error,
|
|
||||||
/** @type {InvoiceResponse} */ response
|
|
||||||
) => {
|
|
||||||
if (error) {
|
|
||||||
rej(error)
|
|
||||||
} else {
|
|
||||||
resolve(response)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} addr
|
* @param {string} addr
|
||||||
* @param {ISEA} SEA
|
* @param {ISEA} SEA
|
||||||
|
|
@ -103,8 +89,6 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const listenerStartTime = performance.now()
|
|
||||||
|
|
||||||
ordersProcessed.add(orderID)
|
ordersProcessed.add(orderID)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|
@ -113,8 +97,6 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
)} -- addr: ${addr}`
|
)} -- addr: ${addr}`
|
||||||
)
|
)
|
||||||
|
|
||||||
const orderAnswerStartTime = performance.now()
|
|
||||||
|
|
||||||
const alreadyAnswered = await getUser()
|
const alreadyAnswered = await getUser()
|
||||||
.get(Key.ORDER_TO_RESPONSE)
|
.get(Key.ORDER_TO_RESPONSE)
|
||||||
.get(orderID)
|
.get(orderID)
|
||||||
|
|
@ -125,12 +107,6 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderAnswerEndTime = performance.now() - orderAnswerStartTime
|
|
||||||
|
|
||||||
logger.info(`[PERF] Order Already Answered: ${orderAnswerEndTime}ms`)
|
|
||||||
|
|
||||||
const decryptStartTime = performance.now()
|
|
||||||
|
|
||||||
const senderEpub = await Utils.pubToEpub(order.from)
|
const senderEpub = await Utils.pubToEpub(order.from)
|
||||||
const secret = await SEA.secret(senderEpub, getUser()._.sea)
|
const secret = await SEA.secret(senderEpub, getUser()._.sea)
|
||||||
|
|
||||||
|
|
@ -139,10 +115,6 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
SEA.decrypt(order.memo, secret)
|
SEA.decrypt(order.memo, secret)
|
||||||
])
|
])
|
||||||
|
|
||||||
const decryptEndTime = performance.now() - decryptStartTime
|
|
||||||
|
|
||||||
logger.info(`[PERF] Decrypt invoice info: ${decryptEndTime}ms`)
|
|
||||||
|
|
||||||
const amount = Number(decryptedAmount)
|
const amount = Number(decryptedAmount)
|
||||||
|
|
||||||
if (!isNumber(amount)) {
|
if (!isNumber(amount)) {
|
||||||
|
|
@ -174,26 +146,19 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
`onOrders() -> Will now create an invoice : ${JSON.stringify(invoiceReq)}`
|
`onOrders() -> Will now create an invoice : ${JSON.stringify(invoiceReq)}`
|
||||||
)
|
)
|
||||||
|
|
||||||
const invoiceStartTime = performance.now()
|
const invoice = await addInvoice(
|
||||||
|
invoiceReq.value,
|
||||||
const invoice = await _addInvoice(invoiceReq)
|
invoiceReq.memo,
|
||||||
|
true,
|
||||||
const invoiceEndTime = performance.now() - invoiceStartTime
|
invoiceReq.expiry
|
||||||
|
)
|
||||||
logger.info(`[PERF] LND Invoice created in ${invoiceEndTime}ms`)
|
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
'onOrders() -> Successfully created the invoice, will now encrypt it'
|
'onOrders() -> Successfully created the invoice, will now encrypt it'
|
||||||
)
|
)
|
||||||
|
|
||||||
const invoiceEncryptStartTime = performance.now()
|
|
||||||
|
|
||||||
const encInvoice = await SEA.encrypt(invoice.payment_request, secret)
|
const encInvoice = await SEA.encrypt(invoice.payment_request, secret)
|
||||||
|
|
||||||
const invoiceEncryptEndTime = performance.now() - invoiceEncryptStartTime
|
|
||||||
|
|
||||||
logger.info(`[PERF] Invoice encrypted in ${invoiceEncryptEndTime}ms`)
|
|
||||||
|
|
||||||
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}`
|
||||||
)
|
)
|
||||||
|
|
@ -204,9 +169,7 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
type: 'invoice'
|
type: 'invoice'
|
||||||
}
|
}
|
||||||
|
|
||||||
const invoicePutStartTime = performance.now()
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
|
||||||
getUser()
|
getUser()
|
||||||
.get(Key.ORDER_TO_RESPONSE)
|
.get(Key.ORDER_TO_RESPONSE)
|
||||||
.get(orderID)
|
.get(orderID)
|
||||||
|
|
@ -222,34 +185,231 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
// invoices should be settled right away so we can rely on this single
|
||||||
|
// subscription instead of life-long all invoices subscription
|
||||||
|
if (order.targetType === 'tip') {
|
||||||
|
const { ackInfo } = order
|
||||||
|
if (!Common.isPopulatedString(ackInfo)) {
|
||||||
|
throw new TypeError(`ackInfo(postID) not a a populated string`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A post tip order lifecycle is short enough that we can do it like this.
|
||||||
|
const stream = LightningServices.invoices.subscribeSingleInvoice({
|
||||||
|
r_hash: invoice.r_hash
|
||||||
})
|
})
|
||||||
|
|
||||||
const invoicePutEndTime = performance.now() - invoicePutStartTime
|
/** @type {Common.Coordinate} */
|
||||||
|
const coord = {
|
||||||
logger.info(`[PERF] Added invoice to GunDB in ${invoicePutEndTime}ms`)
|
amount,
|
||||||
|
id: invoice.r_hash.toString(),
|
||||||
const listenerEndTime = performance.now() - listenerStartTime
|
inbound: true,
|
||||||
|
timestamp: Date.now(),
|
||||||
logger.info(`[PERF] Invoice generation completed in ${listenerEndTime}ms`)
|
type: 'invoice',
|
||||||
|
invoiceMemo: memo,
|
||||||
const hash = invoice.r_hash.toString('base64')
|
fromGunPub: order.from,
|
||||||
|
toGunPub: getUser()._.sea.pub,
|
||||||
if (order.targetType === 'post') {
|
toLndPub: await myLNDPub()
|
||||||
/** @type {TipPaymentStatus} */
|
|
||||||
const paymentStatus = {
|
|
||||||
hash,
|
|
||||||
state: 'OPEN',
|
|
||||||
targetType: order.targetType,
|
|
||||||
postID: order.postID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (order.targetType === 'tip') {
|
||||||
|
coord.type = 'tip'
|
||||||
|
} else {
|
||||||
|
coord.type = 'spontaneousPayment'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Common.InvoiceWhenListed} invoice
|
||||||
|
*/
|
||||||
|
const onData = async invoice => {
|
||||||
|
if (invoice.settled) {
|
||||||
|
writeCoordinate(invoice.r_hash.toString(), coord)
|
||||||
|
|
||||||
|
if (order.targetType === 'tip') {
|
||||||
getUser()
|
getUser()
|
||||||
.get(Key.TIPS_PAYMENT_STATUS)
|
.get('postToTipCount')
|
||||||
.get(hash)
|
// CAST: Checked above.
|
||||||
// @ts-ignore
|
.get(/** @type {string} */ (order.ackInfo))
|
||||||
.put(paymentStatus, response => {
|
.set(null) // each item in the set is a tip
|
||||||
console.log(response)
|
} 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.off()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.on('data', onData)
|
||||||
|
|
||||||
|
stream.on('status', (/** @type {any} */ status) => {
|
||||||
|
logger.info(`Post tip, post: ${order.ackInfo}, invoice status:`, status)
|
||||||
|
})
|
||||||
|
stream.on('end', () => {
|
||||||
|
logger.warn(`Post tip, post: ${order.ackInfo}, invoice stream ended`)
|
||||||
|
})
|
||||||
|
stream.on('error', (/** @type {any} */ e) => {
|
||||||
|
logger.warn(`Post tip, post: ${order.ackInfo}, error:`, e)
|
||||||
|
})
|
||||||
} 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(
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,8 @@ exports.POSTS = 'posts'
|
||||||
// Tips counter for posts
|
// Tips counter for posts
|
||||||
exports.TOTAL_TIPS = 'totalTips'
|
exports.TOTAL_TIPS = 'totalTips'
|
||||||
|
|
||||||
exports.TIPS_PAYMENT_STATUS = 'tipsPaymentStatus'
|
|
||||||
|
|
||||||
exports.PROFILE_BINARY = 'profileBinary'
|
exports.PROFILE_BINARY = 'profileBinary'
|
||||||
|
|
||||||
exports.POSTS_NEW = 'posts'
|
exports.POSTS_NEW = 'posts'
|
||||||
|
|
||||||
|
exports.COORDINATES = 'coordinates'
|
||||||
|
|
|
||||||
|
|
@ -212,29 +212,19 @@ const tryAndWait = async (promGen, shouldRetry = () => false) => {
|
||||||
*/
|
*/
|
||||||
const pubToEpub = async pub => {
|
const pubToEpub = async pub => {
|
||||||
try {
|
try {
|
||||||
const epub = await tryAndWait(async gun => {
|
const epub = await timeout10(
|
||||||
const _epub = await CommonUtils.makePromise(res => {
|
CommonUtils.makePromise(res => {
|
||||||
gun
|
require('../../Mediator/index')
|
||||||
|
.getGun()
|
||||||
.user(pub)
|
.user(pub)
|
||||||
.get('epub')
|
.get('epub')
|
||||||
.once(
|
.on(data => {
|
||||||
data => {
|
if (typeof data === 'string') {
|
||||||
res(data)
|
res(data)
|
||||||
},
|
|
||||||
{
|
|
||||||
wait: 1000
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (typeof _epub !== 'string') {
|
|
||||||
throw new TypeError(
|
|
||||||
`Expected gun.user(pub).get(epub) to be an string. Instead got: ${typeof _epub}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return _epub
|
|
||||||
})
|
})
|
||||||
|
)
|
||||||
|
|
||||||
return epub
|
return epub
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const Path = require("path");
|
const Path = require("path");
|
||||||
const grpc = require("grpc");
|
const grpc = require("@grpc/grpc-js");
|
||||||
const protoLoader = require("@grpc/proto-loader");
|
const protoLoader = require("@grpc/proto-loader");
|
||||||
const logger = require("winston");
|
const logger = require("winston");
|
||||||
const fs = require("../../utils/fs");
|
const fs = require("../../utils/fs");
|
||||||
|
|
@ -10,6 +10,7 @@ const errorConstants = require("../../constants/errors");
|
||||||
* @typedef LightningConfig
|
* @typedef LightningConfig
|
||||||
* @prop {string} lnrpcProtoPath
|
* @prop {string} lnrpcProtoPath
|
||||||
* @prop {string} routerProtoPath
|
* @prop {string} routerProtoPath
|
||||||
|
* @prop {string} invoicesProtoPath
|
||||||
* @prop {string} walletUnlockerProtoPath
|
* @prop {string} walletUnlockerProtoPath
|
||||||
* @prop {string} lndHost
|
* @prop {string} lndHost
|
||||||
* @prop {string} lndCertPath
|
* @prop {string} lndCertPath
|
||||||
|
|
@ -21,6 +22,7 @@ const errorConstants = require("../../constants/errors");
|
||||||
* @prop {any} lightning
|
* @prop {any} lightning
|
||||||
* @prop {any} walletUnlocker
|
* @prop {any} walletUnlocker
|
||||||
* @prop {any} router
|
* @prop {any} router
|
||||||
|
* @prop {any} invoices
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -30,6 +32,7 @@ const errorConstants = require("../../constants/errors");
|
||||||
module.exports = async ({
|
module.exports = async ({
|
||||||
lnrpcProtoPath,
|
lnrpcProtoPath,
|
||||||
routerProtoPath,
|
routerProtoPath,
|
||||||
|
invoicesProtoPath,
|
||||||
walletUnlockerProtoPath,
|
walletUnlockerProtoPath,
|
||||||
lndHost,
|
lndHost,
|
||||||
lndCertPath,
|
lndCertPath,
|
||||||
|
|
@ -46,9 +49,15 @@ module.exports = async ({
|
||||||
includeDirs: ["node_modules/google-proto-files", "proto", Path.resolve(__dirname, "../../config")]
|
includeDirs: ["node_modules/google-proto-files", "proto", Path.resolve(__dirname, "../../config")]
|
||||||
}
|
}
|
||||||
|
|
||||||
const [lnrpcProto, routerProto, walletUnlockerProto] = await Promise.all([protoLoader.load(lnrpcProtoPath, protoLoaderConfig), protoLoader.load(routerProtoPath, protoLoaderConfig), protoLoader.load(walletUnlockerProtoPath, protoLoaderConfig)]);
|
const [lnrpcProto, routerProto, walletUnlockerProto, invoicesProto] = await Promise.all([
|
||||||
|
protoLoader.load(lnrpcProtoPath, protoLoaderConfig),
|
||||||
|
protoLoader.load(routerProtoPath, protoLoaderConfig),
|
||||||
|
protoLoader.load(walletUnlockerProtoPath, protoLoaderConfig),
|
||||||
|
protoLoader.load(invoicesProtoPath, protoLoaderConfig)
|
||||||
|
]);
|
||||||
const { lnrpc } = grpc.loadPackageDefinition(lnrpcProto);
|
const { lnrpc } = grpc.loadPackageDefinition(lnrpcProto);
|
||||||
const { routerrpc } = grpc.loadPackageDefinition(routerProto);
|
const { routerrpc } = grpc.loadPackageDefinition(routerProto);
|
||||||
|
const { invoicesrpc } = grpc.loadPackageDefinition(invoicesProto);
|
||||||
const { lnrpc: walletunlockerrpc } = grpc.loadPackageDefinition(walletUnlockerProto);
|
const { lnrpc: walletunlockerrpc } = grpc.loadPackageDefinition(walletUnlockerProto);
|
||||||
|
|
||||||
const getCredentials = async () => {
|
const getCredentials = async () => {
|
||||||
|
|
@ -93,11 +102,13 @@ module.exports = async ({
|
||||||
const walletUnlocker = new walletunlockerrpc.WalletUnlocker(lndHost, credentials);
|
const walletUnlocker = new walletunlockerrpc.WalletUnlocker(lndHost, credentials);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const router = new routerrpc.Router(lndHost, credentials);
|
const router = new routerrpc.Router(lndHost, credentials);
|
||||||
|
// @ts-ignore
|
||||||
|
const invoices = new invoicesrpc.Invoices(lndHost, credentials);
|
||||||
return {
|
return {
|
||||||
lightning,
|
lightning,
|
||||||
walletUnlocker,
|
walletUnlocker,
|
||||||
router
|
router,
|
||||||
|
invoices
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
// app/lnd.js
|
|
||||||
|
|
||||||
const logger = require("winston");
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
module.exports = function(lightning) {
|
|
||||||
const module = {};
|
|
||||||
|
|
||||||
const invoiceListeners = [];
|
|
||||||
|
|
||||||
let lndInvoicesStream = null;
|
|
||||||
|
|
||||||
const openLndInvoicesStream = function() {
|
|
||||||
if (lndInvoicesStream) {
|
|
||||||
logger.debug("Lnd invoices subscription stream already opened.");
|
|
||||||
} else {
|
|
||||||
logger.debug("Opening lnd invoices subscription stream...");
|
|
||||||
lndInvoicesStream = lightning.subscribeInvoices({});
|
|
||||||
logger.debug("Lnd invoices subscription stream opened.");
|
|
||||||
lndInvoicesStream.on("data", function(data) {
|
|
||||||
logger.debug("SubscribeInvoices Data", data);
|
|
||||||
for (let i = 0; i < invoiceListeners.length; i++) {
|
|
||||||
try {
|
|
||||||
invoiceListeners[i].dataReceived(data);
|
|
||||||
} catch (err) {
|
|
||||||
logger.warn(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
lndInvoicesStream.on("end", function() {
|
|
||||||
logger.debug("SubscribeInvoices End");
|
|
||||||
lndInvoicesStream = null;
|
|
||||||
openLndInvoicesStream(); // try opening stream again
|
|
||||||
});
|
|
||||||
lndInvoicesStream.on("error", function(err) {
|
|
||||||
logger.debug("SubscribeInvoices Error", err);
|
|
||||||
});
|
|
||||||
lndInvoicesStream.on("status", function(status) {
|
|
||||||
logger.debug("SubscribeInvoices Status", status);
|
|
||||||
if (status.code == 14) {
|
|
||||||
// Unavailable
|
|
||||||
lndInvoicesStream = null;
|
|
||||||
openLndInvoicesStream(); // try opening stream again
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// register invoice listener
|
|
||||||
module.registerInvoiceListener = function(listener) {
|
|
||||||
invoiceListeners.push(listener);
|
|
||||||
logger.debug(
|
|
||||||
"New lnd invoice listener registered, " +
|
|
||||||
invoiceListeners.length +
|
|
||||||
" listening now"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// unregister invoice listener
|
|
||||||
module.unregisterInvoiceListener = function(listener) {
|
|
||||||
invoiceListeners.splice(invoiceListeners.indexOf(listener), 1);
|
|
||||||
logger.debug(
|
|
||||||
"Lnd invoice listener unregistered, " +
|
|
||||||
invoiceListeners.length +
|
|
||||||
" still listening"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// open lnd invoices stream on start
|
|
||||||
openLndInvoicesStream();
|
|
||||||
|
|
||||||
// check every minute that lnd invoices stream is still opened
|
|
||||||
setInterval(function() {
|
|
||||||
if (!lndInvoicesStream) {
|
|
||||||
openLndInvoicesStream();
|
|
||||||
}
|
|
||||||
}, 60 * 1000);
|
|
||||||
|
|
||||||
return module;
|
|
||||||
};
|
|
||||||
109
src/__gun__tests__/mySea.ts
Normal file
109
src/__gun__tests__/mySea.ts
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
import Gun from 'gun'
|
||||||
|
import uuid from 'uuid/v1'
|
||||||
|
|
||||||
|
import { mySEA } from '../../services/gunDB/Mediator'
|
||||||
|
import { UserGUNNode } from '../../services/gunDB/contact-api/SimpleGUN'
|
||||||
|
|
||||||
|
const setupUser = async (): Promise<[UserGUNNode]> => {
|
||||||
|
const gun = Gun({
|
||||||
|
file: 'GUN-TEST-' + uuid()
|
||||||
|
})
|
||||||
|
|
||||||
|
const user = (gun.user() as unknown) as UserGUNNode
|
||||||
|
|
||||||
|
await new Promise<void>((res, rej) => {
|
||||||
|
user.create('testAlias-' + uuid(), 'testPass', ack => {
|
||||||
|
if (typeof ack.err === 'string') {
|
||||||
|
rej(new Error(ack.err))
|
||||||
|
} else {
|
||||||
|
res()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return [user]
|
||||||
|
}
|
||||||
|
|
||||||
|
const encryptsDecryptsStrings = async () => {
|
||||||
|
const [user] = await setupUser()
|
||||||
|
|
||||||
|
const stringMessage = 'Lorem ipsum dolor'
|
||||||
|
|
||||||
|
const sec = await mySEA.secret(user._.sea.epub, user._.sea)
|
||||||
|
const encrypted = await mySEA.encrypt(stringMessage, sec)
|
||||||
|
const decrypted = await mySEA.decrypt(encrypted, sec)
|
||||||
|
|
||||||
|
if (decrypted !== stringMessage) {
|
||||||
|
throw new Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const encryptsDecryptsBooleans = async () => {
|
||||||
|
const [user] = await setupUser()
|
||||||
|
|
||||||
|
const truth = true
|
||||||
|
const lie = false
|
||||||
|
|
||||||
|
const sec = await mySEA.secret(user._.sea.epub, user._.sea)
|
||||||
|
|
||||||
|
const encryptedTruth = await mySEA.encrypt(truth, sec)
|
||||||
|
const decryptedTruth = await mySEA.decryptBoolean(encryptedTruth, sec)
|
||||||
|
|
||||||
|
if (decryptedTruth !== truth) {
|
||||||
|
throw new Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
const encryptedLie = await mySEA.encrypt(lie, sec)
|
||||||
|
const decryptedLie = await mySEA.decryptBoolean(encryptedLie, sec)
|
||||||
|
|
||||||
|
if (decryptedLie !== lie) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected false got: ${decryptedLie} - ${typeof decryptedLie}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const encryptsDecryptsNumbers = async () => {
|
||||||
|
const [user] = await setupUser()
|
||||||
|
|
||||||
|
const number = Math.random() * 999999
|
||||||
|
|
||||||
|
const sec = await mySEA.secret(user._.sea.epub, user._.sea)
|
||||||
|
const encrypted = await mySEA.encrypt(number, sec)
|
||||||
|
const decrypted = await mySEA.decryptNumber(encrypted, sec)
|
||||||
|
|
||||||
|
if (decrypted !== number) {
|
||||||
|
throw new Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const encryptsDecryptsZero = async () => {
|
||||||
|
const [user] = await setupUser()
|
||||||
|
|
||||||
|
const zero = 0
|
||||||
|
|
||||||
|
const sec = await mySEA.secret(user._.sea.epub, user._.sea)
|
||||||
|
const encrypted = await mySEA.encrypt(zero, sec)
|
||||||
|
const decrypted = await mySEA.decryptNumber(encrypted, sec)
|
||||||
|
|
||||||
|
if (decrypted !== zero) {
|
||||||
|
throw new Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const runAllTests = async () => {
|
||||||
|
await encryptsDecryptsStrings()
|
||||||
|
await encryptsDecryptsBooleans()
|
||||||
|
await encryptsDecryptsNumbers()
|
||||||
|
await encryptsDecryptsZero()
|
||||||
|
|
||||||
|
console.log('\n--------------------------------')
|
||||||
|
console.log('All tests ran successfully')
|
||||||
|
console.log('--------------------------------\n')
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
runAllTests()
|
||||||
276
src/routes.js
276
src/routes.js
|
|
@ -30,12 +30,7 @@ const {
|
||||||
const GunActions = require('../services/gunDB/contact-api/actions')
|
const GunActions = require('../services/gunDB/contact-api/actions')
|
||||||
const GunGetters = require('../services/gunDB/contact-api/getters')
|
const GunGetters = require('../services/gunDB/contact-api/getters')
|
||||||
const GunKey = require('../services/gunDB/contact-api/key')
|
const GunKey = require('../services/gunDB/contact-api/key')
|
||||||
const {
|
const LV2 = require('../utils/lightningServices/v2')
|
||||||
sendPaymentV2Keysend,
|
|
||||||
sendPaymentV2Invoice,
|
|
||||||
listPayments
|
|
||||||
} = require('../utils/lightningServices/v2')
|
|
||||||
const { startTipStatusJob } = require('../utils/lndJobs')
|
|
||||||
const GunWriteRPC = require('../services/gunDB/rpc')
|
const GunWriteRPC = require('../services/gunDB/rpc')
|
||||||
|
|
||||||
const DEFAULT_MAX_NUM_ROUTES_TO_QUERY = 10
|
const DEFAULT_MAX_NUM_ROUTES_TO_QUERY = 10
|
||||||
|
|
@ -690,7 +685,6 @@ module.exports = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
onNewChannelBackup()
|
onNewChannelBackup()
|
||||||
startTipStatusJob()
|
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
authorization: token,
|
authorization: token,
|
||||||
|
|
@ -1026,30 +1020,15 @@ module.exports = async (
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
// get lnd chan info
|
// get lnd chan info
|
||||||
app.post('/api/lnd/getchaninfo', (req, res) => {
|
app.post('/api/lnd/getchaninfo', async (req, res) => {
|
||||||
const { lightning } = LightningServices.services
|
try {
|
||||||
|
return res.json(await LV2.getChanInfo(req.body.chan_id))
|
||||||
lightning.getChanInfo(
|
} catch (e) {
|
||||||
{ chan_id: req.body.chan_id },
|
console.log(e)
|
||||||
async (err, response) => {
|
return res.status(500).json({
|
||||||
if (err) {
|
errorMessage: e.message
|
||||||
logger.debug('GetChanInfo Error:', err)
|
|
||||||
const health = await checkHealth()
|
|
||||||
if (health.LNDStatus.success) {
|
|
||||||
res.status(400)
|
|
||||||
res.json({
|
|
||||||
field: 'getChanInfo',
|
|
||||||
errorMessage: sanitizeLNDError(err.message)
|
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
res.status(500)
|
|
||||||
res.json({ errorMessage: 'LND is down' })
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
logger.debug('GetChanInfo:', response)
|
|
||||||
res.json(response)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
app.get('/api/lnd/getnetworkinfo', (req, res) => {
|
app.get('/api/lnd/getnetworkinfo', (req, res) => {
|
||||||
|
|
@ -1074,47 +1053,30 @@ module.exports = async (
|
||||||
})
|
})
|
||||||
|
|
||||||
// get lnd node active channels list
|
// get lnd node active channels list
|
||||||
app.get('/api/lnd/listpeers', (req, res) => {
|
app.get('/api/lnd/listpeers', async (req, res) => {
|
||||||
const { lightning } = LightningServices.services
|
try {
|
||||||
lightning.listPeers({}, async (err, response) => {
|
return res.json({
|
||||||
if (err) {
|
peers: await LV2.listPeers(req.body.latestError)
|
||||||
logger.debug('ListPeers Error:', err)
|
|
||||||
const health = await checkHealth()
|
|
||||||
if (health.LNDStatus.success) {
|
|
||||||
res.status(400).json({
|
|
||||||
field: 'listPeers',
|
|
||||||
errorMessage: sanitizeLNDError(err.message)
|
|
||||||
})
|
})
|
||||||
} else {
|
} catch (e) {
|
||||||
res.status(500)
|
console.log(e)
|
||||||
res.json({ errorMessage: 'LND is down' })
|
return res.status(500).json({
|
||||||
}
|
errorMessage: e.message
|
||||||
}
|
|
||||||
logger.debug('ListPeers:', response)
|
|
||||||
res.json(response)
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// newaddress
|
// newaddress
|
||||||
app.post('/api/lnd/newaddress', (req, res) => {
|
app.post('/api/lnd/newaddress', async (req, res) => {
|
||||||
const { lightning } = LightningServices.services
|
try {
|
||||||
lightning.newAddress({ type: req.body.type }, async (err, response) => {
|
return res.json({
|
||||||
if (err) {
|
address: await LV2.newAddress(req.body.type)
|
||||||
logger.debug('NewAddress Error:', err)
|
|
||||||
const health = await checkHealth()
|
|
||||||
if (health.LNDStatus.success) {
|
|
||||||
res.status(400).json({
|
|
||||||
field: 'newAddress',
|
|
||||||
errorMessage: sanitizeLNDError(err.message)
|
|
||||||
})
|
})
|
||||||
} else {
|
} catch (e) {
|
||||||
res.status(500)
|
return res.status(500).json({
|
||||||
res.json({ errorMessage: 'LND is down' })
|
errorMessage: e.message
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.debug('NewAddress:', response)
|
|
||||||
res.json(response)
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// connect peer to lnd node
|
// connect peer to lnd node
|
||||||
|
|
@ -1159,47 +1121,28 @@ module.exports = async (
|
||||||
})
|
})
|
||||||
|
|
||||||
// get lnd node opened channels list
|
// get lnd node opened channels list
|
||||||
app.get('/api/lnd/listchannels', (req, res) => {
|
app.get('/api/lnd/listchannels', async (_, res) => {
|
||||||
const { lightning } = LightningServices.services
|
try {
|
||||||
lightning.listChannels({}, async (err, response) => {
|
return res.json({
|
||||||
if (err) {
|
channels: await LV2.listChannels()
|
||||||
logger.debug('ListChannels Error:', err)
|
|
||||||
const health = await checkHealth()
|
|
||||||
if (health.LNDStatus.success) {
|
|
||||||
res.status(400).json({
|
|
||||||
field: 'listChannels',
|
|
||||||
errorMessage: sanitizeLNDError(err.message)
|
|
||||||
})
|
})
|
||||||
} else {
|
} catch (e) {
|
||||||
res.status(500)
|
console.log(e)
|
||||||
res.json({ errorMessage: 'LND is down' })
|
return res.status(500).json({
|
||||||
}
|
errorMessage: e.message
|
||||||
}
|
|
||||||
logger.debug('ListChannels:', response)
|
|
||||||
res.json(response)
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// get lnd node pending channels list
|
app.get('/api/lnd/pendingchannels', async (req, res) => {
|
||||||
app.get('/api/lnd/pendingchannels', (req, res) => {
|
try {
|
||||||
const { lightning } = LightningServices.services
|
return res.json(await LV2.pendingChannels())
|
||||||
lightning.pendingChannels({}, async (err, response) => {
|
} catch (e) {
|
||||||
if (err) {
|
console.log(e)
|
||||||
logger.debug('PendingChannels Error:', err)
|
return res.status(500).json({
|
||||||
const health = await checkHealth()
|
errorMessage: e.message
|
||||||
if (health.LNDStatus.success) {
|
|
||||||
res.status(400).json({
|
|
||||||
field: 'pendingChannels',
|
|
||||||
errorMessage: sanitizeLNDError(err.message)
|
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
res.status(500)
|
|
||||||
res.json({ errorMessage: 'LND is down' })
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
logger.debug('PendingChannels:', response)
|
|
||||||
res.json(response)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
app.get('/api/lnd/unifiedTrx', (req, res) => {
|
app.get('/api/lnd/unifiedTrx', (req, res) => {
|
||||||
|
|
@ -1249,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.`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1298,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) {
|
||||||
|
|
@ -1377,7 +1344,7 @@ module.exports = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json(
|
return res.status(200).json(
|
||||||
await listPayments({
|
await LV2.listPayments({
|
||||||
include_incomplete,
|
include_incomplete,
|
||||||
index_offset,
|
index_offset,
|
||||||
max_payments,
|
max_payments,
|
||||||
|
|
@ -1664,7 +1631,7 @@ module.exports = async (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const payment = await sendPaymentV2Keysend({
|
const payment = await LV2.sendPaymentV2Keysend({
|
||||||
amt,
|
amt,
|
||||||
dest,
|
dest,
|
||||||
feeLimit,
|
feeLimit,
|
||||||
|
|
@ -1677,7 +1644,7 @@ module.exports = async (
|
||||||
}
|
}
|
||||||
const { payreq } = req.body
|
const { payreq } = req.body
|
||||||
|
|
||||||
const payment = await sendPaymentV2Invoice({
|
const payment = await LV2.sendPaymentV2Invoice({
|
||||||
feeLimit,
|
feeLimit,
|
||||||
payment_request: payreq,
|
payment_request: payreq,
|
||||||
amt: req.body.amt,
|
amt: req.body.amt,
|
||||||
|
|
@ -1766,49 +1733,12 @@ module.exports = async (
|
||||||
})
|
})
|
||||||
|
|
||||||
// addinvoice
|
// addinvoice
|
||||||
app.post('/api/lnd/addinvoice', (req, res) => {
|
app.post('/api/lnd/addinvoice', async (req, res) => {
|
||||||
const { lightning } = LightningServices.services
|
const { expiry, value, memo } = req.body
|
||||||
const invoiceRequest = { memo: req.body.memo, private: true }
|
const addInvoiceRes = await LV2.addInvoice(value, memo, true, expiry)
|
||||||
if (req.body.value) {
|
|
||||||
invoiceRequest.value = req.body.value
|
if (value) {
|
||||||
}
|
const channelsList = await LV2.listChannels({ active_only: true })
|
||||||
if (req.body.expiry) {
|
|
||||||
invoiceRequest.expiry = req.body.expiry
|
|
||||||
}
|
|
||||||
lightning.addInvoice(invoiceRequest, async (err, newInvoice) => {
|
|
||||||
if (err) {
|
|
||||||
logger.debug('AddInvoice Error:', err)
|
|
||||||
const health = await checkHealth()
|
|
||||||
if (health.LNDStatus.success) {
|
|
||||||
res.status(400).json({
|
|
||||||
field: 'addInvoice',
|
|
||||||
errorMessage: sanitizeLNDError(err.message)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
res.status(500)
|
|
||||||
res.json({ errorMessage: 'LND is down' })
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.debug('AddInvoice:', newInvoice)
|
|
||||||
if (req.body.value) {
|
|
||||||
logger.debug('AddInvoice liquidity check:')
|
|
||||||
lightning.listChannels({ active_only: true }, async (err, response) => {
|
|
||||||
if (err) {
|
|
||||||
logger.debug('ListChannels Error:', err)
|
|
||||||
const health = await checkHealth()
|
|
||||||
if (health.LNDStatus.success) {
|
|
||||||
res.status(400).json({
|
|
||||||
field: 'listChannels',
|
|
||||||
errorMessage: sanitizeLNDError(err.message)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
res.status(500)
|
|
||||||
res.json({ errorMessage: 'LND is down' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.debug('ListChannels:', response)
|
|
||||||
const channelsList = response.channels
|
|
||||||
let remoteBalance = Big(0)
|
let remoteBalance = Big(0)
|
||||||
channelsList.forEach(element => {
|
channelsList.forEach(element => {
|
||||||
const remB = Big(element.remote_balance)
|
const remB = Big(element.remote_balance)
|
||||||
|
|
@ -1816,14 +1746,19 @@ module.exports = async (
|
||||||
remoteBalance = remB
|
remoteBalance = remB
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
newInvoice.liquidityCheck = remoteBalance > req.body.value
|
|
||||||
|
addInvoiceRes.liquidityCheck = remoteBalance > value
|
||||||
//newInvoice.remoteBalance = remoteBalance
|
//newInvoice.remoteBalance = remoteBalance
|
||||||
res.json(newInvoice)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
res.json(newInvoice)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return res.json(addInvoiceRes)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
return res.status(500).json({
|
||||||
|
errorMessage: e.message
|
||||||
})
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// signmessage
|
// signmessage
|
||||||
|
|
@ -1882,7 +1817,8 @@ module.exports = async (
|
||||||
const sendCoinsRequest = {
|
const sendCoinsRequest = {
|
||||||
addr: req.body.addr,
|
addr: req.body.addr,
|
||||||
amount: req.body.amount,
|
amount: req.body.amount,
|
||||||
sat_per_byte: req.body.satPerByte
|
sat_per_byte: req.body.satPerByte,
|
||||||
|
send_all: req.body.send_all === true
|
||||||
}
|
}
|
||||||
logger.debug('SendCoins', sendCoinsRequest)
|
logger.debug('SendCoins', sendCoinsRequest)
|
||||||
lightning.sendCoins(sendCoinsRequest, async (err, response) => {
|
lightning.sendCoins(sendCoinsRequest, async (err, response) => {
|
||||||
|
|
@ -1960,23 +1896,25 @@ module.exports = async (
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.post('/api/lnd/listunspent', (req, res) => {
|
const listunspent = async (req, res) => {
|
||||||
const { lightning } = LightningServices.services
|
try {
|
||||||
const { minConfirmations = 3, maxConfirmations = 6 } = req.body
|
return res.status(200).json({
|
||||||
lightning.listUnspent(
|
utxos: await LV2.listUnspent(
|
||||||
{
|
req.body.minConfirmations,
|
||||||
min_confs: minConfirmations,
|
req.body.maxConfirmations
|
||||||
max_confs: maxConfirmations
|
|
||||||
},
|
|
||||||
(err, unspent) => {
|
|
||||||
if (err) {
|
|
||||||
return handleError(res, err)
|
|
||||||
}
|
|
||||||
logger.debug('ListUnspent:', unspent)
|
|
||||||
res.json(unspent)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
} catch (e) {
|
||||||
|
return res.status(500).json({
|
||||||
|
errorMessage: e.message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get('/api/lnd/listunspent', listunspent)
|
||||||
|
|
||||||
|
// TODO: should be GET
|
||||||
|
app.post('/api/lnd/listunspent', listunspent)
|
||||||
|
|
||||||
app.get('/api/lnd/transactions', (req, res) => {
|
app.get('/api/lnd/transactions', (req, res) => {
|
||||||
const { lightning } = LightningServices.services
|
const { lightning } = LightningServices.services
|
||||||
|
|
|
||||||
|
|
@ -167,10 +167,6 @@ const server = program => {
|
||||||
await LightningServices.init()
|
await LightningServices.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
// init lnd module =================
|
|
||||||
const lnd = require('../services/lnd/lnd')(
|
|
||||||
LightningServices.services.lightning
|
|
||||||
)
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
LightningServices.services.lightning.getInfo({}, (err, res) => {
|
LightningServices.services.lightning.getInfo({}, (err, res) => {
|
||||||
if (err && err.code !== 12) {
|
if (err && err.code !== 12) {
|
||||||
|
|
|
||||||
|
|
@ -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 }) => {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ const lnrpc = require('../../services/lnd/lightning')
|
||||||
* @prop {string} macaroonPath
|
* @prop {string} macaroonPath
|
||||||
* @prop {string} lndProto
|
* @prop {string} lndProto
|
||||||
* @prop {string} routerProto
|
* @prop {string} routerProto
|
||||||
|
* @prop {string} invoicesProto
|
||||||
* @prop {string} walletUnlockerProto
|
* @prop {string} walletUnlockerProto
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -73,6 +74,13 @@ class LightningServices {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {import('./types').Services}
|
||||||
|
*/
|
||||||
|
getServices() {
|
||||||
|
return this.services
|
||||||
|
}
|
||||||
|
|
||||||
get servicesData() {
|
get servicesData() {
|
||||||
return this.lnServicesData
|
return this.lnServicesData
|
||||||
}
|
}
|
||||||
|
|
@ -119,6 +127,7 @@ class LightningServices {
|
||||||
const lnServices = await lnrpc({
|
const lnServices = await lnrpc({
|
||||||
lnrpcProtoPath: this.defaults.lndProto,
|
lnrpcProtoPath: this.defaults.lndProto,
|
||||||
routerProtoPath: this.defaults.routerProto,
|
routerProtoPath: this.defaults.routerProto,
|
||||||
|
invoicesProtoPath: this.defaults.invoicesProto,
|
||||||
walletUnlockerProtoPath: this.defaults.walletUnlockerProto,
|
walletUnlockerProtoPath: this.defaults.walletUnlockerProto,
|
||||||
lndHost,
|
lndHost,
|
||||||
lndCertPath,
|
lndCertPath,
|
||||||
|
|
@ -127,10 +136,11 @@ class LightningServices {
|
||||||
if (!lnServices) {
|
if (!lnServices) {
|
||||||
throw new Error(`Could not init lnServices`)
|
throw new Error(`Could not init lnServices`)
|
||||||
}
|
}
|
||||||
const { lightning, walletUnlocker, router } = lnServices
|
const { lightning, walletUnlocker, router, invoices } = lnServices
|
||||||
this.lightning = lightning
|
this.lightning = lightning
|
||||||
this.walletUnlocker = walletUnlocker
|
this.walletUnlocker = walletUnlocker
|
||||||
this.router = router
|
this.router = router
|
||||||
|
this.invoices = invoices
|
||||||
this.lnServicesData = {
|
this.lnServicesData = {
|
||||||
lndProto: this.defaults.lndProto,
|
lndProto: this.defaults.lndProto,
|
||||||
lndHost,
|
lndHost,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
import * as Common from 'shock-common'
|
||||||
|
|
||||||
export interface PaymentV2 {
|
export interface PaymentV2 {
|
||||||
payment_hash: string
|
payment_hash: string
|
||||||
|
|
@ -106,3 +107,90 @@ export interface SendPaymentInvoiceParams {
|
||||||
payment_request: string
|
payment_request: string
|
||||||
timeoutSeconds?: number
|
timeoutSeconds?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StreamListener = (data: any) => void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caution: Not all methods return an stream.
|
||||||
|
*/
|
||||||
|
interface LightningStream {
|
||||||
|
on(ev: 'data' | 'end' | 'error' | 'status', listener: StreamListener): void
|
||||||
|
}
|
||||||
|
|
||||||
|
type LightningCB = (err: Error, data: Record<string, any>) => void
|
||||||
|
|
||||||
|
type LightningMethod = (
|
||||||
|
args: Record<string, any>,
|
||||||
|
cb?: LightningCB
|
||||||
|
) => LightningStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes it easier for code calling services.
|
||||||
|
*/
|
||||||
|
export interface Services {
|
||||||
|
lightning: Record<string, LightningMethod>
|
||||||
|
walletUnlocker: Record<string, LightningMethod>
|
||||||
|
router: Record<string, LightningMethod>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListChannelsReq {
|
||||||
|
active_only: boolean
|
||||||
|
inactive_only: boolean
|
||||||
|
public_only: boolean
|
||||||
|
private_only: boolean
|
||||||
|
/**
|
||||||
|
* Filters the response for channels with a target peer's pubkey. If peer is
|
||||||
|
* empty, all channels will be returned.
|
||||||
|
*/
|
||||||
|
peer: Common.Bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://api.lightning.community/#pendingchannels
|
||||||
|
*/
|
||||||
|
export interface PendingChannelsRes {
|
||||||
|
/**
|
||||||
|
* The balance in satoshis encumbered in pending channels.
|
||||||
|
*/
|
||||||
|
total_limbo_balance: string
|
||||||
|
/**
|
||||||
|
* Channels pending opening.
|
||||||
|
*/
|
||||||
|
pending_open_channels: Common.PendingOpenChannel[]
|
||||||
|
/**
|
||||||
|
* Channels pending force closing.
|
||||||
|
*/
|
||||||
|
pending_force_closing_channels: Common.ForceClosedChannel[]
|
||||||
|
/**
|
||||||
|
* Channels waiting for closing tx to confirm.
|
||||||
|
*/
|
||||||
|
waiting_close_channels: Common.WaitingCloseChannel[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://github.com/lightningnetwork/lnd/blob/daf7c8a85420fc67fffa18fa5f7d08c2040946e4/lnrpc/rpc.proto#L2948
|
||||||
|
*/
|
||||||
|
export interface AddInvoiceRes {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
r_hash: Common.Bytes
|
||||||
|
/**
|
||||||
|
* A bare-bones invoice for a payment within the Lightning Network. With the
|
||||||
|
* details of the invoice, the sender has all the data necessary to send a
|
||||||
|
* payment to the recipient.
|
||||||
|
*/
|
||||||
|
payment_request: string
|
||||||
|
/**
|
||||||
|
* The "add" index of this invoice. Each newly created invoice will increment
|
||||||
|
* this index making it monotonically increasing. Callers to the
|
||||||
|
* SubscribeInvoices call can use this to instantly get notified of all added
|
||||||
|
* invoices with an add_index greater than this one.
|
||||||
|
*/
|
||||||
|
add_index: string
|
||||||
|
/**
|
||||||
|
* The payment address of the generated invoice. This value should be used in
|
||||||
|
* all payments for this invoice as we require it for end to end security.
|
||||||
|
*/
|
||||||
|
payment_addr: Common.Bytes
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ const logger = require('winston')
|
||||||
const Common = require('shock-common')
|
const Common = require('shock-common')
|
||||||
const Ramda = require('ramda')
|
const Ramda = require('ramda')
|
||||||
|
|
||||||
|
const { writeCoordinate } = require('../../services/coordinates')
|
||||||
|
|
||||||
const lightningServices = require('./lightning-services')
|
const lightningServices = require('./lightning-services')
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./types').PaymentV2} PaymentV2
|
* @typedef {import('./types').PaymentV2} PaymentV2
|
||||||
|
|
@ -213,12 +215,50 @@ const isValidSendPaymentInvoiceParams = sendPaymentInvoiceParams => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} payReq
|
||||||
|
* @returns {Promise<Common.Schema.InvoiceWhenDecoded>}
|
||||||
|
*/
|
||||||
|
const decodePayReq = payReq =>
|
||||||
|
Common.Utils.makePromise((res, rej) => {
|
||||||
|
lightningServices.lightning.decodePayReq(
|
||||||
|
{ pay_req: payReq },
|
||||||
|
/**
|
||||||
|
* @param {{ message: any; }} err
|
||||||
|
* @param {any} paymentRequest
|
||||||
|
*/
|
||||||
|
(err, paymentRequest) => {
|
||||||
|
if (err) {
|
||||||
|
rej(new Error(err.message))
|
||||||
|
} else {
|
||||||
|
res(paymentRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
const myLNDPub = () =>
|
||||||
|
Common.makePromise((res, rej) => {
|
||||||
|
const { lightning } = lightningServices.getServices()
|
||||||
|
|
||||||
|
lightning.getInfo({}, (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
rej(new Error(err.message))
|
||||||
|
} else {
|
||||||
|
res(data.identity_pubkey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aklssjdklasd
|
* aklssjdklasd
|
||||||
* @param {SendPaymentV2Request} sendPaymentRequest
|
* @param {SendPaymentV2Request} sendPaymentRequest
|
||||||
* @returns {Promise<PaymentV2>}
|
* @returns {Promise<PaymentV2>}
|
||||||
*/
|
*/
|
||||||
const sendPaymentV2 = sendPaymentRequest => {
|
const sendPaymentV2 = async sendPaymentRequest => {
|
||||||
const {
|
const {
|
||||||
services: { router }
|
services: { router }
|
||||||
} = lightningServices
|
} = lightningServices
|
||||||
|
|
@ -229,7 +269,10 @@ const sendPaymentV2 = sendPaymentRequest => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((res, rej) => {
|
/**
|
||||||
|
* @type {import("./types").PaymentV2}
|
||||||
|
*/
|
||||||
|
const paymentV2 = await Common.makePromise((res, rej) => {
|
||||||
const stream = router.sendPaymentV2(sendPaymentRequest)
|
const stream = router.sendPaymentV2(sendPaymentRequest)
|
||||||
|
|
||||||
stream.on(
|
stream.on(
|
||||||
|
|
@ -268,6 +311,33 @@ const sendPaymentV2 = sendPaymentRequest => {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** @type {Common.Coordinate} */
|
||||||
|
const coord = {
|
||||||
|
amount: Number(paymentV2.value_sat),
|
||||||
|
id: paymentV2.payment_hash,
|
||||||
|
inbound: false,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
toLndPub: await myLNDPub(),
|
||||||
|
fromLndPub: undefined,
|
||||||
|
invoiceMemo: undefined,
|
||||||
|
type: 'payment'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendPaymentRequest.payment_request) {
|
||||||
|
const invoice = await decodePayReq(sendPaymentRequest.payment_request)
|
||||||
|
|
||||||
|
coord.invoiceMemo = invoice.description
|
||||||
|
coord.toLndPub = invoice.destination
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendPaymentRequest.dest) {
|
||||||
|
coord.toLndPub = sendPaymentRequest.dest.toString('base64')
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeCoordinate(paymentV2.payment_hash, coord)
|
||||||
|
|
||||||
|
return paymentV2
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -381,22 +451,169 @@ const listPayments = req => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} payReq
|
* @param {0|1} type
|
||||||
* @returns {Promise<Common.Schema.InvoiceWhenDecoded>}
|
* @returns {Promise<string>}
|
||||||
*/
|
*/
|
||||||
const decodePayReq = payReq =>
|
const newAddress = (type = 0) => {
|
||||||
Common.Utils.makePromise((res, rej) => {
|
const { lightning } = lightningServices.getServices()
|
||||||
lightningServices.lightning.decodePayReq(
|
|
||||||
{ pay_req: payReq },
|
return Common.Utils.makePromise((res, rej) => {
|
||||||
/**
|
lightning.newAddress({ type }, (err, response) => {
|
||||||
* @param {{ message: any; }} err
|
|
||||||
* @param {any} paymentRequest
|
|
||||||
*/
|
|
||||||
(err, paymentRequest) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
rej(new Error(err.message))
|
rej(new Error(err.message))
|
||||||
} else {
|
} else {
|
||||||
res(paymentRequest)
|
res(response.address)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} minConfs
|
||||||
|
* @param {number} maxConfs
|
||||||
|
* @returns {Promise<Common.Utxo[]>}
|
||||||
|
*/
|
||||||
|
const listUnspent = (minConfs = 3, maxConfs = 6) =>
|
||||||
|
Common.makePromise((res, rej) => {
|
||||||
|
const { lightning } = lightningServices.getServices()
|
||||||
|
|
||||||
|
lightning.listUnspent(
|
||||||
|
{
|
||||||
|
min_confs: minConfs,
|
||||||
|
max_confs: maxConfs
|
||||||
|
},
|
||||||
|
(err, unspent) => {
|
||||||
|
if (err) {
|
||||||
|
rej(new Error(err.message))
|
||||||
|
} else {
|
||||||
|
res(unspent.utxos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('./types').ListChannelsReq} ListChannelsReq
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ListChannelsReq} req
|
||||||
|
* @returns {Promise<Common.Channel[]>}
|
||||||
|
*/
|
||||||
|
const listChannels = req =>
|
||||||
|
Common.makePromise((res, rej) => {
|
||||||
|
const { lightning } = lightningServices.getServices()
|
||||||
|
|
||||||
|
lightning.listChannels(req, (err, resp) => {
|
||||||
|
if (err) {
|
||||||
|
rej(new Error(err.message))
|
||||||
|
} else {
|
||||||
|
res(resp.channels)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://api.lightning.community/#getchaninfo
|
||||||
|
* @param {string} chanID
|
||||||
|
* @returns {Promise<Common.ChannelEdge>}
|
||||||
|
*/
|
||||||
|
const getChanInfo = chanID =>
|
||||||
|
Common.makePromise((res, rej) => {
|
||||||
|
const { lightning } = lightningServices.getServices()
|
||||||
|
|
||||||
|
lightning.getChanInfo(
|
||||||
|
{
|
||||||
|
chan_id: chanID
|
||||||
|
},
|
||||||
|
(err, resp) => {
|
||||||
|
if (err) {
|
||||||
|
rej(new Error(err.message))
|
||||||
|
} else {
|
||||||
|
// Needs cast because typescript refuses to assign Record<string, any>
|
||||||
|
// to an actual object :shrugs
|
||||||
|
res(/** @type {Common.ChannelEdge} */ (resp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://api.lightning.community/#listpeers
|
||||||
|
* @param {boolean=} latestError If true, only the last error that our peer sent
|
||||||
|
* us will be returned with the peer's information, rather than the full set of
|
||||||
|
* historic errors we have stored.
|
||||||
|
* @returns {Promise<Common.Peer[]>}
|
||||||
|
*/
|
||||||
|
const listPeers = latestError =>
|
||||||
|
Common.makePromise((res, rej) => {
|
||||||
|
const { lightning } = lightningServices.getServices()
|
||||||
|
|
||||||
|
lightning.listPeers(
|
||||||
|
{
|
||||||
|
latest_error: latestError
|
||||||
|
},
|
||||||
|
(err, resp) => {
|
||||||
|
if (err) {
|
||||||
|
rej(new Error(err.message))
|
||||||
|
} else {
|
||||||
|
res(resp.peers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('./types').PendingChannelsRes} PendingChannelsRes
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<PendingChannelsRes>}
|
||||||
|
*/
|
||||||
|
const pendingChannels = () =>
|
||||||
|
Common.makePromise((res, rej) => {
|
||||||
|
const { lightning } = lightningServices.getServices()
|
||||||
|
|
||||||
|
lightning.pendingChannels({}, (err, resp) => {
|
||||||
|
if (err) {
|
||||||
|
rej(new Error(err.message))
|
||||||
|
} else {
|
||||||
|
// Needs cast because typescript refuses to assign Record<string, any>
|
||||||
|
// to an actual object :shrugs
|
||||||
|
res(/** @type {PendingChannelsRes} */ (resp))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('./types').AddInvoiceRes} AddInvoiceRes
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* https://api.lightning.community/#addinvoice
|
||||||
|
* @param {number} value
|
||||||
|
* @param {string=} memo
|
||||||
|
* @param {boolean=} confidential Alias for `private`.
|
||||||
|
* @param {number=} expiry
|
||||||
|
* @returns {Promise<AddInvoiceRes>}
|
||||||
|
*/
|
||||||
|
const addInvoice = (value, memo = '', confidential = true, expiry = 180) =>
|
||||||
|
Common.makePromise((res, rej) => {
|
||||||
|
const { lightning } = lightningServices.getServices()
|
||||||
|
|
||||||
|
lightning.addInvoice(
|
||||||
|
{
|
||||||
|
value,
|
||||||
|
memo,
|
||||||
|
private: confidential,
|
||||||
|
expiry
|
||||||
|
},
|
||||||
|
(err, resp) => {
|
||||||
|
if (err) {
|
||||||
|
rej(new Error(err.message))
|
||||||
|
} else {
|
||||||
|
// Needs cast because typescript refuses to assign Record<string, any>
|
||||||
|
// to an actual object :shrugs
|
||||||
|
res(/** @type {AddInvoiceRes} */ (resp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -406,5 +623,13 @@ module.exports = {
|
||||||
sendPaymentV2Keysend,
|
sendPaymentV2Keysend,
|
||||||
sendPaymentV2Invoice,
|
sendPaymentV2Invoice,
|
||||||
listPayments,
|
listPayments,
|
||||||
decodePayReq
|
decodePayReq,
|
||||||
|
newAddress,
|
||||||
|
listUnspent,
|
||||||
|
listChannels,
|
||||||
|
getChanInfo,
|
||||||
|
listPeers,
|
||||||
|
pendingChannels,
|
||||||
|
addInvoice,
|
||||||
|
myLNDPub
|
||||||
}
|
}
|
||||||
|
|
|
||||||
211
utils/lndJobs.js
211
utils/lndJobs.js
|
|
@ -1,211 +0,0 @@
|
||||||
/**
|
|
||||||
* @prettier
|
|
||||||
*/
|
|
||||||
const Logger = require('winston')
|
|
||||||
const { wait } = require('./helpers')
|
|
||||||
const Key = require('../services/gunDB/contact-api/key')
|
|
||||||
const { getUser } = require('../services/gunDB/Mediator')
|
|
||||||
const LightningServices = require('./lightningServices')
|
|
||||||
|
|
||||||
const ERROR_TRIES_THRESHOLD = 3
|
|
||||||
const ERROR_TRIES_DELAY = 500
|
|
||||||
const INVOICE_STATE = {
|
|
||||||
OPEN: 'OPEN',
|
|
||||||
SETTLED: 'SETTLED',
|
|
||||||
CANCELLED: 'CANCELLED',
|
|
||||||
ACCEPTED: 'ACCEPTED'
|
|
||||||
}
|
|
||||||
|
|
||||||
const _lookupInvoice = hash =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
const { lightning } = LightningServices.services
|
|
||||||
lightning.lookupInvoice({ r_hash: hash }, (err, response) => {
|
|
||||||
if (err) {
|
|
||||||
Logger.error(
|
|
||||||
'[TIP] An error has occurred while trying to lookup invoice:',
|
|
||||||
err,
|
|
||||||
'\nInvoice Hash:',
|
|
||||||
hash
|
|
||||||
)
|
|
||||||
reject(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.info('[TIP] Invoice lookup result:', response)
|
|
||||||
resolve(response)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const _getPostTipInfo = ({ postID }) =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
getUser()
|
|
||||||
.get(Key.POSTS_NEW)
|
|
||||||
.get(postID)
|
|
||||||
.once(post => {
|
|
||||||
if (post && post.date) {
|
|
||||||
const { tipCounter, tipValue } = post
|
|
||||||
resolve({
|
|
||||||
tipCounter: typeof tipCounter === 'number' ? tipCounter : 0,
|
|
||||||
tipValue: typeof tipValue === 'number' ? tipValue : 0
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(post)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const _incrementPost = ({ postID, orderAmount }) =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
const parsedAmount = parseFloat(orderAmount)
|
|
||||||
|
|
||||||
if (typeof parsedAmount !== 'number') {
|
|
||||||
reject(new Error('Invalid order amount specified'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.info('[POST TIP] Getting Post Tip Values...')
|
|
||||||
|
|
||||||
return _getPostTipInfo({ postID })
|
|
||||||
.then(({ tipValue, tipCounter }) => {
|
|
||||||
const updatedTip = {
|
|
||||||
tipCounter: tipCounter + 1,
|
|
||||||
tipValue: tipValue + parsedAmount
|
|
||||||
}
|
|
||||||
|
|
||||||
getUser()
|
|
||||||
.get(Key.POSTS_NEW)
|
|
||||||
.get(postID)
|
|
||||||
.put(updatedTip, () => {
|
|
||||||
Logger.info('[POST TIP] Successfully updated Post tip info')
|
|
||||||
resolve(updatedTip)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
Logger.error(err)
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const _updateTipData = (invoiceHash, data) =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
try {
|
|
||||||
getUser()
|
|
||||||
.get(Key.TIPS_PAYMENT_STATUS)
|
|
||||||
.get(invoiceHash)
|
|
||||||
.put(data, tip => {
|
|
||||||
if (tip === undefined) {
|
|
||||||
reject(new Error('Tip update failed'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(tip)
|
|
||||||
|
|
||||||
resolve(tip)
|
|
||||||
})
|
|
||||||
} catch (err) {
|
|
||||||
Logger.error('An error has occurred while updating tip^data')
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const _getTipData = (invoiceHash, tries = 0) =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
if (tries >= ERROR_TRIES_THRESHOLD) {
|
|
||||||
reject(new Error('Malformed data'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
getUser()
|
|
||||||
.get(Key.TIPS_PAYMENT_STATUS)
|
|
||||||
.get(invoiceHash)
|
|
||||||
.once(async tip => {
|
|
||||||
try {
|
|
||||||
if (tip === undefined) {
|
|
||||||
await wait(ERROR_TRIES_DELAY)
|
|
||||||
const tip = await _getTipData(invoiceHash, tries + 1)
|
|
||||||
|
|
||||||
if (tip) {
|
|
||||||
resolve(tip)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
reject(new Error('Malformed data'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(tip)
|
|
||||||
} catch (err) {
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const executeTipAction = (tip, invoice) => {
|
|
||||||
if (invoice.state !== INVOICE_STATE.SETTLED) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute actions once invoice is settled
|
|
||||||
Logger.info('Invoice settled!', invoice)
|
|
||||||
|
|
||||||
if (tip.targetType === 'post') {
|
|
||||||
_incrementPost({
|
|
||||||
postID: tip.postID,
|
|
||||||
orderAmount: invoice.amt_paid_sat
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateUnverifiedTips = () => {
|
|
||||||
getUser()
|
|
||||||
.get(Key.TIPS_PAYMENT_STATUS)
|
|
||||||
.map()
|
|
||||||
.once(async (tip, id) => {
|
|
||||||
try {
|
|
||||||
if (
|
|
||||||
!tip ||
|
|
||||||
tip.state !== INVOICE_STATE.OPEN ||
|
|
||||||
(tip._errorCount && tip._errorCount >= ERROR_TRIES_THRESHOLD)
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Logger.info('Unverified invoice found!', tip)
|
|
||||||
const invoice = await _lookupInvoice(tip.hash)
|
|
||||||
Logger.info('Invoice located:', invoice)
|
|
||||||
if (invoice.state !== tip.state) {
|
|
||||||
await _updateTipData(id, { state: invoice.state })
|
|
||||||
|
|
||||||
// Actions to be executed when the tip's state is updated
|
|
||||||
executeTipAction(tip, invoice)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Logger.error('[TIP] An error has occurred while updating invoice', err)
|
|
||||||
const errorCount = tip._errorCount ? tip._errorCount : 0
|
|
||||||
_updateTipData(id, {
|
|
||||||
_errorCount: errorCount + 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const startTipStatusJob = () => {
|
|
||||||
const { lightning } = LightningServices.services
|
|
||||||
const stream = lightning.subscribeInvoices({})
|
|
||||||
updateUnverifiedTips()
|
|
||||||
stream.on('data', async invoice => {
|
|
||||||
const hash = invoice.r_hash.toString('base64')
|
|
||||||
const tip = await _getTipData(hash)
|
|
||||||
if (tip.state !== invoice.state) {
|
|
||||||
await _updateTipData(hash, { state: invoice.state })
|
|
||||||
executeTipAction(tip, invoice)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
stream.on('error', err => {
|
|
||||||
Logger.error('Tip Job error' + err.details)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
startTipStatusJob
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue