diff --git a/.env.example b/.env.example index 638679f8..96eb2f48 100644 --- a/.env.example +++ b/.env.example @@ -5,4 +5,6 @@ DISABLE_SHOCK_ENCRYPTION=false CACHE_HEADERS_MANDATORY=true SHOCK_CACHE=true TRUSTED_KEYS=true -LOCAL_TUNNEL_SERVER=http://tunnel.example.com \ No newline at end of file +LOCAL_TUNNEL_SERVER=http://tunnel.example.com +TORRENT_SEED_URL=https://webtorrent.shock.network +TORRENT_SEED_TOKEN=jibberish diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..6461deec --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +*.ts diff --git a/.eslintrc.json b/.eslintrc.json index dd2485bf..252f73b1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -88,7 +88,10 @@ // I am now convinced TODO comments closer to the relevant code are better // than GH issues. Especially when it only concerns a single function / // routine. - "no-warning-comments": "off" + "no-warning-comments": "off", + + // broken + "sort-imports": "off" }, "parser": "babel-eslint", "env": { diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml new file mode 100644 index 00000000..26e292c4 --- /dev/null +++ b/.github/workflows/version.yml @@ -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 diff --git a/.vscode/settings.json b/.vscode/settings.json index 5171a933..38a20598 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,7 @@ { "eslint.enable": true, "typescript.tsdk": "node_modules/typescript/lib", - "debug.node.autoAttach": "on" + "debug.node.autoAttach": "on", + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode" } diff --git a/.vscode/snippets.code-snippets b/.vscode/snippets.code-snippets new file mode 100644 index 00000000..53bd01c5 --- /dev/null +++ b/.vscode/snippets.code-snippets @@ -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" + } +} \ No newline at end of file diff --git a/README.md b/README.md index c7dcc9c1..c3cde0d8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

ShockAPI

-![GitHub last commit](https://img.shields.io/github/last-commit/shocknet/wallet?style=flat-square) +![GitHub last commit](https://img.shields.io/github/last-commit/shocknet/api?style=flat-square) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![Chat](https://img.shields.io/badge/chat-on%20Telegram-blue?style=flat-square)](https://t.me/Shockwallet) [![Twitter Follow](https://img.shields.io/twitter/follow/ShockBTC?style=flat-square)](https://twitter.com/shockbtc) diff --git a/config/defaults.js b/config/defaults.js index 7341dd50..a44376f4 100644 --- a/config/defaults.js +++ b/config/defaults.js @@ -35,6 +35,7 @@ module.exports = (mainnet = false) => { maxNumRoutesToQuery: 20, lndProto: parsePath(`${__dirname}/rpc.proto`), routerProto: parsePath(`${__dirname}/router.proto`), + invoicesProto: parsePath(`${__dirname}/invoices.proto`), walletUnlockerProto: parsePath(`${__dirname}/walletunlocker.proto`), lndHost: "localhost:10009", lndCertPath: parsePath(`${lndDirectory}/tls.cert`), diff --git a/config/invoices.proto b/config/invoices.proto new file mode 100644 index 00000000..df52b8c8 --- /dev/null +++ b/config/invoices.proto @@ -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; +} diff --git a/package.json b/package.json index 24409ce2..608bb8d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "shockapi", - "version": "1.0.0", + "version": "2021.1.04", "description": "", "main": "src/server.js", "scripts": { @@ -11,13 +11,16 @@ "test:watch": "jest --no-cache --watch", "typecheck": "tsc", "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": "", "license": "ISC", "dependencies": { - "@grpc/proto-loader": "^0.5.1", - "axios": "^0.20.0", + "@grpc/grpc-js": "^1.2.2", + "@grpc/proto-loader": "^0.5.5", + "assert-never": "^1.2.1", + "axios": "^0.21.1", "basic-auth": "^2.0.0", "big.js": "^5.2.2", "bitcore-lib": "^0.15.0", @@ -31,7 +34,7 @@ "debug": "^3.1.0", "dotenv": "^8.1.0", "express": "^4.14.1", - "express-session": "^1.15.1", + "express-session": "^1.17.1", "google-proto-files": "^1.0.3", "graphviz": "0.0.8", "grpc": "1.24.4", @@ -42,15 +45,16 @@ "localtunnel": "git://github.com/shocknet/localtunnel#40cc2c2a46b05da2217bf2e20da11a5343a5cce7", "lodash": "^4.17.20", "method-override": "^2.3.7", + "node-fetch": "^2.6.1", "node-persist": "^3.1.0", "promise": "^8.1.0", "qrcode-terminal": "^0.12.0", "ramda": "^0.27.1", "request": "^2.88.2", - "request-promise": "^4.2.2", + "request-promise": "^4.2.6", "response-time": "^2.3.2", "shelljs": "^0.8.2", - "shock-common": "17.x.x", + "shock-common": "32.0.0", "socket.io": "2.1.1", "text-encoding": "^0.7.0", "tingodb": "^0.6.1", @@ -58,7 +62,7 @@ "winston-daily-rotate-file": "^4.5.0" }, "devDependencies": { - "@babel/plugin-proposal-class-properties": "^7.5.5", + "@babel/plugin-proposal-class-properties": "^7.12.1", "@types/bluebird": "^3.5.32", "@types/dotenv": "^6.1.1", "@types/express": "^4.17.1", @@ -66,10 +70,12 @@ "@types/jest": "^24.0.18", "@types/jsonwebtoken": "^8.3.7", "@types/lodash": "^4.14.141", + "@types/node-fetch": "^2.5.8", "@types/ramda": "types/npm-ramda#dist", + "@types/react": "16.x.x", "@types/socket.io": "^2.1.11", "@types/uuid": "^3.4.5", - "babel-eslint": "^10.0.3", + "babel-eslint": "^10.1.0", "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", "eslint": "^6.6.0", "eslint-config-prettier": "^6.5.0", @@ -80,8 +86,10 @@ "lint-staged": "^10.2.2", "nodemon": "^1.19.3", "prettier": "^1.18.2", + "rimraf": "^3.0.2", + "ts-node": "^9.1.1", "ts-type": "^1.2.16", - "typescript": "^4.0.2" + "typescript": "latest" }, "lint-staged": { "*.js": [ diff --git a/services/coordinates.js b/services/coordinates.js new file mode 100644 index 00000000..ac212256 --- /dev/null +++ b/services/coordinates.js @@ -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} + */ +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} + */ + 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) + } +} diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index e4aba797..eddb2466 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -26,23 +26,13 @@ const SEAx = require('gun/sea') /** @type {import('../contact-api/SimpleGUN').ISEA} */ 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__MSG__ = '$$__SHOCKWALLET__MSG__' +const $$__SHOCKWALLET__NUMBER__ = '$$__SHOCKWALLET__NUMBER__' +const $$__SHOCKWALLET__BOOLEAN__ = '$$__SHOCKWALLET__BOOLEAN__' 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') { throw new TypeError( `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 - const sanitizedMsg = $$__SHOCKWALLET__MSG__ + msg + let strToEncode = '' - 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 }) } -mySEA.decrypt = (encMsg, secret) => { +/** + * @param {string} encMsg + * @param {string} secret + * @returns {Promise} + */ +const decryptBase = (encMsg, secret) => { if (typeof encMsg !== 'string') { throw new TypeError( 'mySEA.encrypt() -> expected encMsg to be an string instead got: ' + @@ -104,10 +114,41 @@ mySEA.decrypt = (encMsg, secret) => { throw new TypeError('Could not decrypt') } - return decodedMsg.slice($$__SHOCKWALLET__MSG__.length) + if (decodedMsg.startsWith($$__SHOCKWALLET__MSG__)) { + 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) => { if (typeof recipientOrSenderEpub !== 'string') { throw new TypeError( @@ -273,7 +314,7 @@ const authenticate = async (alias, pass, __user) => { // clock skew await new Promise(res => setTimeout(res, 2000)) - await new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { _user.get(Key.FOLLOWS).put( { unused: null @@ -286,7 +327,7 @@ const authenticate = async (alias, pass, __user) => { } } ) - }) + })) return ack.sea.pub } else { @@ -304,7 +345,7 @@ const authenticate = async (alias, pass, __user) => { // clock skew await new Promise(res => setTimeout(res, 2000)) - await new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { _user.get(Key.FOLLOWS).put( { unused: null @@ -317,7 +358,7 @@ const authenticate = async (alias, pass, __user) => { } } ) - }) + })) // move this to a subscription; implement off() ? todo 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, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { _user.get(Key.FOLLOWS).put( { unused: null @@ -376,7 +417,7 @@ const authenticate = async (alias, pass, __user) => { } } ) - }) + })) API.Jobs.onAcceptedRequests(_user, mySEA) API.Jobs.onOrders(_user, gun, mySEA) diff --git a/services/gunDB/contact-api/SimpleGUN.ts b/services/gunDB/contact-api/SimpleGUN.ts index 6d04fdf7..fee7b196 100644 --- a/services/gunDB/contact-api/SimpleGUN.ts +++ b/services/gunDB/contact-api/SimpleGUN.ts @@ -120,8 +120,19 @@ export interface UserGUNNode extends GUNNode { } export interface ISEA { - encrypt(message: string, senderSecret: string): Promise + encrypt( + message: string | number | boolean, + senderSecret: string + ): Promise decrypt(encryptedMessage: string, recipientSecret: string): Promise + decryptNumber( + encryptedMessage: string, + recipientSecret: string + ): Promise + decryptBoolean( + encryptedMessage: string, + recipientSecret: string + ): Promise secret( recipientOrSenderEpub: string, recipientOrSenderUserPair: UserPair diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index f784cfe5..e1fd403b 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -11,7 +11,8 @@ const { ErrorCode } = Constants const { sendPaymentV2Invoice, - decodePayReq + decodePayReq, + myLNDPub } = require('../../../utils/lightningServices/v2') /** @@ -21,6 +22,7 @@ const { const Getters = require('./getters') const Key = require('./key') const Utils = require('./utils') +const { writeCoordinate } = require('../../coordinates') /** * @typedef {import('./SimpleGUN').GUNNode} GUNNode @@ -98,7 +100,7 @@ const __createOutgoingFeed = async (withPublicKey, user, SEA) => { timestamp: Date.now() } - await new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { user .get(Key.OUTGOINGS) .get(newOutgoingFeedID) @@ -111,14 +113,14 @@ const __createOutgoingFeed = async (withPublicKey, user, SEA) => { res() } }) - }) + })) const encryptedForMeNewOutgoingFeedID = await SEA.encrypt( newOutgoingFeedID, mySecret ) - await new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { user .get(Key.RECIPIENT_TO_OUTGOING) .get(withPublicKey) @@ -129,7 +131,7 @@ const __createOutgoingFeed = async (withPublicKey, user, SEA) => { res() } }) - }) + })) outgoingFeedID = newOutgoingFeedID } @@ -235,7 +237,7 @@ const acceptRequest = async ( const mySecret = require('../Mediator').getMySecret() const encryptedForMeIncomingID = await SEA.encrypt(incomingID, mySecret) - await new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { user .get(Key.USER_TO_INCOMING) .get(senderPublicKey) @@ -246,7 +248,7 @@ const acceptRequest = async ( res() } }) - }) + })) //////////////////////////////////////////////////////////////////////////// // NOTE: perform non-reversable actions before destructive actions @@ -259,7 +261,7 @@ const acceptRequest = async ( ourSecret ) - await new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { gun .get(Key.HANDSHAKE_NODES) .get(handshakeAddress) @@ -276,7 +278,7 @@ const acceptRequest = async ( } } ) - }) + })) } /** @@ -285,7 +287,7 @@ const acceptRequest = async ( * @param {UserGUNNode} userNode */ const authenticate = (user, pass, userNode) => - new Promise((resolve, reject) => { + /** @type {Promise} */ (new Promise((resolve, reject) => { if (typeof user !== 'string') { throw new TypeError('expected user to be of type string') } @@ -315,7 +317,7 @@ const authenticate = (user, pass, userNode) => resolve() } }) - }) + })) /** * @param {string} publicKey @@ -347,7 +349,7 @@ const generateHandshakeAddress = async () => { const address = uuidv1() - await new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { user.get(Key.CURRENT_HANDSHAKE_ADDRESS).put(address, ack => { if (ack.err && typeof ack.err !== 'number') { rej(new Error(ack.err)) @@ -355,9 +357,9 @@ const generateHandshakeAddress = async () => { res() } }) - }) + })) - await new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { gun .get(Key.HANDSHAKE_NODES) .get(address) @@ -368,7 +370,7 @@ const generateHandshakeAddress = async () => { res() } }) - }) + })) } /** @@ -385,7 +387,7 @@ const cleanup = async pub => { const promises = [] promises.push( - new Promise((res, rej) => { + /** @type {Promise} */ (new Promise((res, rej) => { user .get(Key.USER_TO_INCOMING) .get(pub) @@ -396,11 +398,11 @@ const cleanup = async pub => { res() } }) - }) + })) ) promises.push( - new Promise((res, rej) => { + /** @type {Promise} */ (new Promise((res, rej) => { user .get(Key.RECIPIENT_TO_OUTGOING) .get(pub) @@ -411,11 +413,11 @@ const cleanup = async pub => { res() } }) - }) + })) ) promises.push( - new Promise((res, rej) => { + /** @type {Promise} */ (new Promise((res, rej) => { user .get(Key.USER_TO_LAST_REQUEST_SENT) .get(pub) @@ -426,12 +428,12 @@ const cleanup = async pub => { res() } }) - }) + })) ) if (outGoingID) { promises.push( - new Promise((res, rej) => { + /** @type {Promise} */ (new Promise((res, rej) => { user .get(Key.OUTGOINGS) .get(outGoingID) @@ -442,7 +444,7 @@ const cleanup = async pub => { res() } }) - }) + })) ) } @@ -616,7 +618,7 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => { }) }) - await new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { user .get(Key.USER_TO_LAST_REQUEST_SENT) .get(recipientPublicKey) @@ -627,7 +629,7 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => { res() } }) - }) + })) // 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 @@ -642,7 +644,7 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => { timestamp } - await new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { //@ts-ignore user.get(Key.STORED_REQS).set(storedReq, ack => { if (ack.err && typeof ack.err !== 'number') { @@ -655,7 +657,7 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => { res() } }) - }) + })) } /** @@ -922,6 +924,7 @@ const sendHRWithInitialMsg = async ( * @typedef {object} SpontPaymentOptions * @prop {Common.Schema.OrderTargetType} type * @prop {string=} postID + * @prop {string=} ackInfo */ /** @@ -940,7 +943,7 @@ const sendSpontaneousPayment = async ( amount, memo, feeLimit, - opts = { type: 'user' } + opts = { type: 'spontaneousPayment' } ) => { try { const SEA = require('../Mediator').mySEA @@ -965,8 +968,8 @@ const sendSpontaneousPayment = async ( targetType: opts.type } - if (opts.type === 'post') { - order.postID = opts.postID + if (opts.type === 'tip') { + order.ackInfo = opts.postID } logger.info(JSON.stringify(order)) @@ -1074,6 +1077,32 @@ const sendSpontaneousPayment = async ( 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 } catch (e) { logger.error('Error inside sendPayment()') @@ -1125,7 +1154,7 @@ const generateOrderAddress = user => * @returns {Promise} */ const setBio = (bio, user) => - new Promise((resolve, reject) => { + /** @type {Promise} */ (new Promise((resolve, reject) => { if (!user.is) { throw new Error(ErrorCode.NOT_AUTH) } @@ -1149,7 +1178,7 @@ const setBio = (bio, user) => resolve() } }) - }).then( + })).then( () => new Promise((resolve, reject) => { user @@ -1233,7 +1262,7 @@ const disconnect = async pub => { * @returns {Promise} */ const setLastSeenApp = () => - new Promise((res, rej) => { + /** @type {Promise} */ (new Promise((res, rej) => { require('../Mediator') .getUser() .get(Key.LAST_SEEN_APP) @@ -1244,7 +1273,7 @@ const setLastSeenApp = () => res() } }) - }).then( + })).then( () => new Promise((res, rej) => { require('../Mediator') @@ -1268,14 +1297,14 @@ const setLastSeenApp = () => * @returns {Promise<[string, Common.Schema.RawPost]>} */ const createPostNew = async (tags, title, content) => { + const SEA = require('../Mediator').mySEA /** @type {Common.Schema.RawPost} */ const newPost = { date: Date.now(), status: 'publish', tags: tags.join('-'), title, - contentItems: {}, - tipCounter: 0 + contentItems: {} } content.forEach(c => { @@ -1284,6 +1313,23 @@ const createPostNew = async (tags, title, content) => { 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} */ const postID = await Common.makePromise((res, rej) => { const _n = require('../Mediator') @@ -1365,7 +1411,7 @@ const createPost = async (tags, title, content) => { pageIdx = Number(pageIdx + 1).toString() } - await new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { require('../Mediator') .getUser() .get(Key.WALL) @@ -1386,7 +1432,7 @@ const createPost = async (tags, title, content) => { res() } ) - }) + })) const [postID, newPost] = await createPostNew(tags, title, content) @@ -1412,7 +1458,7 @@ const createPost = async (tags, title, content) => { }) if (shouldBeNewPage || numOfPages === 0) { - await new Promise(res => { + await /** @type {Promise} */ (new Promise(res => { require('../Mediator') .getUser() .get(Key.WALL) @@ -1424,7 +1470,7 @@ const createPost = async (tags, title, content) => { res() }) - }) + })) } const loadedPost = await new Promise(res => { @@ -1467,7 +1513,7 @@ const createPost = async (tags, title, content) => { * @returns {Promise} */ const deletePost = async (postId, page) => { - await new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { require('../Mediator') .getUser() .get(Key.WALL) @@ -1482,15 +1528,15 @@ const deletePost = async (postId, page) => { res() } }) - }) + })) } /** * @param {string} publicKey * @param {boolean} isPrivate Will overwrite previous private status. - * @returns {Promise} + * @returns {Promise} */ -const follow = (publicKey, isPrivate) => { +const follow = async (publicKey, isPrivate) => { /** @type {import('shock-common').Schema.Follow} */ const newFollow = { private: isPrivate, @@ -1498,7 +1544,7 @@ const follow = (publicKey, isPrivate) => { user: publicKey } - return new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { require('../Mediator') .getUser() .get(Key.FOLLOWS) @@ -1511,7 +1557,7 @@ const follow = (publicKey, isPrivate) => { res() } }) - }) + })) } /** @@ -1543,7 +1589,7 @@ const initWall = async () => { const promises = [] promises.push( - new Promise((res, rej) => { + /** @type {Promise} */ (new Promise((res, rej) => { user .get(Key.WALL) .get(Key.NUM_OF_PAGES) @@ -1554,11 +1600,11 @@ const initWall = async () => { res() } }) - }) + })) ) promises.push( - new Promise((res, rej) => { + /** @type {Promise} */ (new Promise((res, rej) => { user .get(Key.WALL) .get(Key.PAGES) @@ -1576,11 +1622,11 @@ const initWall = async () => { } } ) - }) + })) ) promises.push( - new Promise((res, rej) => { + /** @type {Promise} */ (new Promise((res, rej) => { user .get(Key.WALL) .get(Key.PAGES) @@ -1593,7 +1639,7 @@ const initWall = async () => { res() } }) - }) + })) ) await Promise.all(promises) diff --git a/services/gunDB/contact-api/jobs/onAcceptedRequests.js b/services/gunDB/contact-api/jobs/onAcceptedRequests.js index ba9cad5a..255cc107 100644 --- a/services/gunDB/contact-api/jobs/onAcceptedRequests.js +++ b/services/gunDB/contact-api/jobs/onAcceptedRequests.js @@ -92,7 +92,7 @@ const onAcceptedRequests = (user, SEA) => { const recipientEpub = await Utils.pubToEpub(recipientPub) const ourSecret = await SEA.secret(recipientEpub, user._.sea) - await new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { gun .get(Key.HANDSHAKE_NODES) .get(requestAddress) @@ -151,7 +151,7 @@ const onAcceptedRequests = (user, SEA) => { mySecret ) - await new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { user .get(Key.USER_TO_INCOMING) .get(recipientPub) @@ -162,9 +162,9 @@ const onAcceptedRequests = (user, SEA) => { res() } }) - }) + })) - await new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { user .get(Key.STORED_REQS) .get(id) @@ -175,12 +175,12 @@ const onAcceptedRequests = (user, SEA) => { res() } }) - }) + })) // ensure this listeners gets called at least once res() }) - }) + })) } catch (err) { logger.warn(`Jobs.onAcceptedRequests() -> ${err.message}`) logger.error(err) diff --git a/services/gunDB/contact-api/jobs/onOrders.js b/services/gunDB/contact-api/jobs/onOrders.js index 3a73dd73..ad757ccc 100644 --- a/services/gunDB/contact-api/jobs/onOrders.js +++ b/services/gunDB/contact-api/jobs/onOrders.js @@ -1,21 +1,29 @@ /** * @format */ - -const { performance } = require('perf_hooks') +// @ts-check const logger = require('winston') const isFinite = require('lodash/isFinite') const isNumber = require('lodash/isNumber') const isNaN = require('lodash/isNaN') +const Common = require('shock-common') const { Constants: { ErrorCode }, 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 { + addInvoice, + myLNDPub +} = require('../../../../utils/lightningServices/v2') +const { writeCoordinate } = require('../../../coordinates') const Key = require('../key') const Utils = require('../utils') +const { gunUUID } = require('../../../../utils') const getUser = () => require('../../Mediator').getUser() @@ -55,28 +63,6 @@ const ordersProcessed = new Set() let currentOrderAddr = '' -/** - * @param {InvoiceRequest} invoiceReq - * @returns {Promise} - */ -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 {ISEA} SEA @@ -103,8 +89,6 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => { return } - const listenerStartTime = performance.now() - ordersProcessed.add(orderID) logger.info( @@ -113,8 +97,6 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => { )} -- addr: ${addr}` ) - const orderAnswerStartTime = performance.now() - const alreadyAnswered = await getUser() .get(Key.ORDER_TO_RESPONSE) .get(orderID) @@ -125,12 +107,6 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => { 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 secret = await SEA.secret(senderEpub, getUser()._.sea) @@ -139,10 +115,6 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => { SEA.decrypt(order.memo, secret) ]) - const decryptEndTime = performance.now() - decryptStartTime - - logger.info(`[PERF] Decrypt invoice info: ${decryptEndTime}ms`) - const amount = Number(decryptedAmount) if (!isNumber(amount)) { @@ -174,26 +146,19 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => { `onOrders() -> Will now create an invoice : ${JSON.stringify(invoiceReq)}` ) - const invoiceStartTime = performance.now() - - const invoice = await _addInvoice(invoiceReq) - - const invoiceEndTime = performance.now() - invoiceStartTime - - logger.info(`[PERF] LND Invoice created in ${invoiceEndTime}ms`) + const invoice = await addInvoice( + invoiceReq.value, + invoiceReq.memo, + true, + invoiceReq.expiry + ) logger.info( 'onOrders() -> Successfully created the invoice, will now encrypt it' ) - const invoiceEncryptStartTime = performance.now() - const encInvoice = await SEA.encrypt(invoice.payment_request, secret) - const invoiceEncryptEndTime = performance.now() - invoiceEncryptStartTime - - logger.info(`[PERF] Invoice encrypted in ${invoiceEncryptEndTime}ms`) - logger.info( `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' } - const invoicePutStartTime = performance.now() - - await new Promise((res, rej) => { + await /** @type {Promise} */ (new Promise((res, rej) => { getUser() .get(Key.ORDER_TO_RESPONSE) .get(orderID) @@ -222,34 +185,231 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => { 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 - - logger.info(`[PERF] Added invoice to GunDB in ${invoicePutEndTime}ms`) - - const listenerEndTime = performance.now() - listenerStartTime - - logger.info(`[PERF] Invoice generation completed in ${listenerEndTime}ms`) - - const hash = invoice.r_hash.toString('base64') - - if (order.targetType === 'post') { - /** @type {TipPaymentStatus} */ - const paymentStatus = { - hash, - state: 'OPEN', - targetType: order.targetType, - postID: order.postID - } - getUser() - .get(Key.TIPS_PAYMENT_STATUS) - .get(hash) - // @ts-ignore - .put(paymentStatus, response => { - console.log(response) - }) + /** @type {Common.Coordinate} */ + const coord = { + amount, + id: invoice.r_hash.toString(), + inbound: true, + timestamp: Date.now(), + type: 'invoice', + invoiceMemo: memo, + fromGunPub: order.from, + toGunPub: getUser()._.sea.pub, + toLndPub: await myLNDPub() } + + 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() + .get('postToTipCount') + // CAST: Checked above. + .get(/** @type {string} */ (order.ackInfo)) + .set(null) // each item in the set is a tip + } else if (order.targetType === 'contentReveal') { + // ----------------------------------------- + logger.debug('Content Reveal') + + //assuming digital product that only requires to be unlocked + const postID = order.ackInfo + + if (!Common.isPopulatedString(postID)) { + logger.error(`Invalid post ID`) + logger.error(postID) + return + } + + // TODO: do this reactively + const selectedPost = await new Promise(res => { + getUser() + .get(Key.POSTS_NEW) + .get(postID) + .load(res) + }) + + logger.debug(selectedPost) + + if (Common.isPost(selectedPost)) { + logger.error('Post id provided does not correspond to a valid post') + return + } + + /** + * @type {Record} + */ + 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} */ (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} */ (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) { logger.error( `error inside onOrders, orderAddr: ${addr}, orderID: ${orderID}, order: ${JSON.stringify( diff --git a/services/gunDB/contact-api/key.js b/services/gunDB/contact-api/key.js index ece0c1ed..210fae06 100644 --- a/services/gunDB/contact-api/key.js +++ b/services/gunDB/contact-api/key.js @@ -59,8 +59,8 @@ exports.POSTS = 'posts' // Tips counter for posts exports.TOTAL_TIPS = 'totalTips' -exports.TIPS_PAYMENT_STATUS = 'tipsPaymentStatus' - exports.PROFILE_BINARY = 'profileBinary' exports.POSTS_NEW = 'posts' + +exports.COORDINATES = 'coordinates' diff --git a/services/gunDB/contact-api/utils/index.js b/services/gunDB/contact-api/utils/index.js index bcab3105..3af89742 100644 --- a/services/gunDB/contact-api/utils/index.js +++ b/services/gunDB/contact-api/utils/index.js @@ -212,29 +212,19 @@ const tryAndWait = async (promGen, shouldRetry = () => false) => { */ const pubToEpub = async pub => { try { - const epub = await tryAndWait(async gun => { - const _epub = await CommonUtils.makePromise(res => { - gun + const epub = await timeout10( + CommonUtils.makePromise(res => { + require('../../Mediator/index') + .getGun() .user(pub) .get('epub') - .once( - data => { + .on(data => { + if (typeof data === 'string') { 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 } catch (err) { diff --git a/services/lnd/lightning.js b/services/lnd/lightning.js index caf232f2..18237d44 100644 --- a/services/lnd/lightning.js +++ b/services/lnd/lightning.js @@ -1,5 +1,5 @@ const Path = require("path"); -const grpc = require("grpc"); +const grpc = require("@grpc/grpc-js"); const protoLoader = require("@grpc/proto-loader"); const logger = require("winston"); const fs = require("../../utils/fs"); @@ -10,6 +10,7 @@ const errorConstants = require("../../constants/errors"); * @typedef LightningConfig * @prop {string} lnrpcProtoPath * @prop {string} routerProtoPath + * @prop {string} invoicesProtoPath * @prop {string} walletUnlockerProtoPath * @prop {string} lndHost * @prop {string} lndCertPath @@ -21,6 +22,7 @@ const errorConstants = require("../../constants/errors"); * @prop {any} lightning * @prop {any} walletUnlocker * @prop {any} router + * @prop {any} invoices */ /** @@ -30,10 +32,11 @@ const errorConstants = require("../../constants/errors"); module.exports = async ({ lnrpcProtoPath, routerProtoPath, + invoicesProtoPath, walletUnlockerProtoPath, - lndHost, - lndCertPath, - macaroonPath + lndHost, + lndCertPath, + macaroonPath }) => { try { process.env.GRPC_SSL_CIPHER_SUITES = "HIGH+ECDSA"; @@ -46,9 +49,15 @@ module.exports = async ({ 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 { routerrpc } = grpc.loadPackageDefinition(routerProto); + const { invoicesrpc } = grpc.loadPackageDefinition(invoicesProto); const { lnrpc: walletunlockerrpc } = grpc.loadPackageDefinition(walletUnlockerProto); const getCredentials = async () => { @@ -93,11 +102,13 @@ module.exports = async ({ const walletUnlocker = new walletunlockerrpc.WalletUnlocker(lndHost, credentials); // @ts-ignore const router = new routerrpc.Router(lndHost, credentials); - + // @ts-ignore + const invoices = new invoicesrpc.Invoices(lndHost, credentials); return { lightning, walletUnlocker, - router + router, + invoices }; } diff --git a/services/lnd/lnd.js b/services/lnd/lnd.js deleted file mode 100644 index da5ffe51..00000000 --- a/services/lnd/lnd.js +++ /dev/null @@ -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; -}; diff --git a/src/__gun__tests__/mySea.ts b/src/__gun__tests__/mySea.ts new file mode 100644 index 00000000..f2f2af21 --- /dev/null +++ b/src/__gun__tests__/mySea.ts @@ -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((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() diff --git a/src/routes.js b/src/routes.js index 2fd43091..a79b5eb7 100644 --- a/src/routes.js +++ b/src/routes.js @@ -30,12 +30,7 @@ const { const GunActions = require('../services/gunDB/contact-api/actions') const GunGetters = require('../services/gunDB/contact-api/getters') const GunKey = require('../services/gunDB/contact-api/key') -const { - sendPaymentV2Keysend, - sendPaymentV2Invoice, - listPayments -} = require('../utils/lightningServices/v2') -const { startTipStatusJob } = require('../utils/lndJobs') +const LV2 = require('../utils/lightningServices/v2') const GunWriteRPC = require('../services/gunDB/rpc') const DEFAULT_MAX_NUM_ROUTES_TO_QUERY = 10 @@ -690,7 +685,6 @@ module.exports = async ( } onNewChannelBackup() - startTipStatusJob() res.json({ authorization: token, @@ -1026,30 +1020,15 @@ module.exports = async ( ) }) // get lnd chan info - app.post('/api/lnd/getchaninfo', (req, res) => { - const { lightning } = LightningServices.services - - lightning.getChanInfo( - { chan_id: req.body.chan_id }, - async (err, response) => { - if (err) { - 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.post('/api/lnd/getchaninfo', async (req, res) => { + try { + return res.json(await LV2.getChanInfo(req.body.chan_id)) + } catch (e) { + console.log(e) + return res.status(500).json({ + errorMessage: e.message + }) + } }) app.get('/api/lnd/getnetworkinfo', (req, res) => { @@ -1074,47 +1053,30 @@ module.exports = async ( }) // get lnd node active channels list - app.get('/api/lnd/listpeers', (req, res) => { - const { lightning } = LightningServices.services - lightning.listPeers({}, async (err, response) => { - if (err) { - logger.debug('ListPeers Error:', err) - const health = await checkHealth() - if (health.LNDStatus.success) { - res.status(400).json({ - field: 'listPeers', - errorMessage: sanitizeLNDError(err.message) - }) - } else { - res.status(500) - res.json({ errorMessage: 'LND is down' }) - } - } - logger.debug('ListPeers:', response) - res.json(response) - }) + app.get('/api/lnd/listpeers', async (req, res) => { + try { + return res.json({ + peers: await LV2.listPeers(req.body.latestError) + }) + } catch (e) { + console.log(e) + return res.status(500).json({ + errorMessage: e.message + }) + } }) // newaddress - app.post('/api/lnd/newaddress', (req, res) => { - const { lightning } = LightningServices.services - lightning.newAddress({ type: req.body.type }, async (err, response) => { - if (err) { - logger.debug('NewAddress Error:', err) - const health = await checkHealth() - if (health.LNDStatus.success) { - res.status(400).json({ - field: 'newAddress', - errorMessage: sanitizeLNDError(err.message) - }) - } else { - res.status(500) - res.json({ errorMessage: 'LND is down' }) - } - } - logger.debug('NewAddress:', response) - res.json(response) - }) + app.post('/api/lnd/newaddress', async (req, res) => { + try { + return res.json({ + address: await LV2.newAddress(req.body.type) + }) + } catch (e) { + return res.status(500).json({ + errorMessage: e.message + }) + } }) // connect peer to lnd node @@ -1159,47 +1121,28 @@ module.exports = async ( }) // get lnd node opened channels list - app.get('/api/lnd/listchannels', (req, res) => { - const { lightning } = LightningServices.services - lightning.listChannels({}, 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) - res.json(response) - }) + app.get('/api/lnd/listchannels', async (_, res) => { + try { + return res.json({ + channels: await LV2.listChannels() + }) + } catch (e) { + console.log(e) + return res.status(500).json({ + errorMessage: e.message + }) + } }) - // get lnd node pending channels list - app.get('/api/lnd/pendingchannels', (req, res) => { - const { lightning } = LightningServices.services - lightning.pendingChannels({}, async (err, response) => { - if (err) { - logger.debug('PendingChannels Error:', err) - const health = await checkHealth() - 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/pendingchannels', async (req, res) => { + try { + return res.json(await LV2.pendingChannels()) + } catch (e) { + console.log(e) + return res.status(500).json({ + errorMessage: e.message + }) + } }) app.get('/api/lnd/unifiedTrx', (req, res) => { @@ -1249,12 +1192,35 @@ module.exports = async ( app.post('/api/lnd/unifiedTrx', async (req, res) => { 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({ 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( await GunActions.sendSpontaneousPayment(to, amt, memo, feeLimit, { type, - postID + postID, + ackInfo }) ) } catch (e) { @@ -1377,7 +1344,7 @@ module.exports = async ( } return res.status(200).json( - await listPayments({ + await LV2.listPayments({ include_incomplete, index_offset, max_payments, @@ -1664,7 +1631,7 @@ module.exports = async ( }) } - const payment = await sendPaymentV2Keysend({ + const payment = await LV2.sendPaymentV2Keysend({ amt, dest, feeLimit, @@ -1677,7 +1644,7 @@ module.exports = async ( } const { payreq } = req.body - const payment = await sendPaymentV2Invoice({ + const payment = await LV2.sendPaymentV2Invoice({ feeLimit, payment_request: payreq, amt: req.body.amt, @@ -1766,64 +1733,32 @@ module.exports = async ( }) // addinvoice - app.post('/api/lnd/addinvoice', (req, res) => { - const { lightning } = LightningServices.services - const invoiceRequest = { memo: req.body.memo, private: true } - if (req.body.value) { - invoiceRequest.value = req.body.value - } - 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' }) + app.post('/api/lnd/addinvoice', async (req, res) => { + const { expiry, value, memo } = req.body + const addInvoiceRes = await LV2.addInvoice(value, memo, true, expiry) + + if (value) { + const channelsList = await LV2.listChannels({ active_only: true }) + let remoteBalance = Big(0) + channelsList.forEach(element => { + const remB = Big(element.remote_balance) + if (remB.gt(remoteBalance)) { + remoteBalance = remB } - 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) - channelsList.forEach(element => { - const remB = Big(element.remote_balance) - if (remB.gt(remoteBalance)) { - remoteBalance = remB - } - }) - newInvoice.liquidityCheck = remoteBalance > req.body.value - //newInvoice.remoteBalance = remoteBalance - res.json(newInvoice) - }) - } else { - res.json(newInvoice) - } - }) + }) + + addInvoiceRes.liquidityCheck = remoteBalance > value + //newInvoice.remoteBalance = remoteBalance + } + + try { + return res.json(addInvoiceRes) + } catch (e) { + console.log(e) + return res.status(500).json({ + errorMessage: e.message + }) + } }) // signmessage @@ -1882,7 +1817,8 @@ module.exports = async ( const sendCoinsRequest = { addr: req.body.addr, 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) lightning.sendCoins(sendCoinsRequest, async (err, response) => { @@ -1960,23 +1896,25 @@ module.exports = async ( ) }) - app.post('/api/lnd/listunspent', (req, res) => { - const { lightning } = LightningServices.services - const { minConfirmations = 3, maxConfirmations = 6 } = req.body - lightning.listUnspent( - { - min_confs: minConfirmations, - max_confs: maxConfirmations - }, - (err, unspent) => { - if (err) { - return handleError(res, err) - } - logger.debug('ListUnspent:', unspent) - res.json(unspent) - } - ) - }) + const listunspent = async (req, res) => { + try { + return res.status(200).json({ + utxos: await LV2.listUnspent( + req.body.minConfirmations, + req.body.maxConfirmations + ) + }) + } 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) => { const { lightning } = LightningServices.services diff --git a/src/server.js b/src/server.js index 6e514a98..4917bcd1 100644 --- a/src/server.js +++ b/src/server.js @@ -167,10 +167,6 @@ const server = program => { await LightningServices.init() } - // init lnd module ================= - const lnd = require('../services/lnd/lnd')( - LightningServices.services.lightning - ) await new Promise((resolve, reject) => { LightningServices.services.lightning.getInfo({}, (err, res) => { if (err && err.code !== 12) { diff --git a/src/sockets.js b/src/sockets.js index 54537af8..f1a90196 100644 --- a/src/sockets.js +++ b/src/sockets.js @@ -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 { lightning } = LightningServices.services logger.warn('Subscribing to invoices socket...' + subID) @@ -677,7 +617,7 @@ module.exports = ( } /** - * @param {Common.Schema.SimpleReceivedRequest[]} receivedReqs + * @param {ReadonlyArray} receivedReqs */ const onReceivedReqs = receivedReqs => { const processed = receivedReqs.map(({ id, requestorPK, timestamp }) => { diff --git a/utils/index.js b/utils/index.js index 90e80eee..516d762a 100644 --- a/utils/index.js +++ b/utils/index.js @@ -1,9 +1,21 @@ /** * @format */ +const Gun = require('gun') 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 } diff --git a/utils/lightningServices/lightning-services.js b/utils/lightningServices/lightning-services.js index a0631f1b..71ef98d9 100644 --- a/utils/lightningServices/lightning-services.js +++ b/utils/lightningServices/lightning-services.js @@ -18,6 +18,7 @@ const lnrpc = require('../../services/lnd/lightning') * @prop {string} macaroonPath * @prop {string} lndProto * @prop {string} routerProto + * @prop {string} invoicesProto * @prop {string} walletUnlockerProto */ @@ -73,6 +74,13 @@ class LightningServices { } } + /** + * @returns {import('./types').Services} + */ + getServices() { + return this.services + } + get servicesData() { return this.lnServicesData } @@ -119,6 +127,7 @@ class LightningServices { const lnServices = await lnrpc({ lnrpcProtoPath: this.defaults.lndProto, routerProtoPath: this.defaults.routerProto, + invoicesProtoPath: this.defaults.invoicesProto, walletUnlockerProtoPath: this.defaults.walletUnlockerProto, lndHost, lndCertPath, @@ -127,10 +136,11 @@ class LightningServices { if (!lnServices) { throw new Error(`Could not init lnServices`) } - const { lightning, walletUnlocker, router } = lnServices + const { lightning, walletUnlocker, router, invoices } = lnServices this.lightning = lightning this.walletUnlocker = walletUnlocker this.router = router + this.invoices = invoices this.lnServicesData = { lndProto: this.defaults.lndProto, lndHost, diff --git a/utils/lightningServices/types.ts b/utils/lightningServices/types.ts index f8e77d81..ca0d034f 100644 --- a/utils/lightningServices/types.ts +++ b/utils/lightningServices/types.ts @@ -1,6 +1,7 @@ /** * @format */ +import * as Common from 'shock-common' export interface PaymentV2 { payment_hash: string @@ -106,3 +107,90 @@ export interface SendPaymentInvoiceParams { payment_request: string 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) => void + +type LightningMethod = ( + args: Record, + cb?: LightningCB +) => LightningStream + +/** + * Makes it easier for code calling services. + */ +export interface Services { + lightning: Record + walletUnlocker: Record + router: Record +} + +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 +} diff --git a/utils/lightningServices/v2.js b/utils/lightningServices/v2.js index 34f42f44..45a03a52 100644 --- a/utils/lightningServices/v2.js +++ b/utils/lightningServices/v2.js @@ -6,6 +6,8 @@ const logger = require('winston') const Common = require('shock-common') const Ramda = require('ramda') +const { writeCoordinate } = require('../../services/coordinates') + const lightningServices = require('./lightning-services') /** * @typedef {import('./types').PaymentV2} PaymentV2 @@ -213,12 +215,50 @@ const isValidSendPaymentInvoiceParams = sendPaymentInvoiceParams => { return true } +/** + * @param {string} payReq + * @returns {Promise} + */ +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} + */ +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 * @param {SendPaymentV2Request} sendPaymentRequest * @returns {Promise} */ -const sendPaymentV2 = sendPaymentRequest => { +const sendPaymentV2 = async sendPaymentRequest => { const { services: { router } } = 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) 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 - * @returns {Promise} + * @param {0|1} type + * @returns {Promise} */ -const decodePayReq = payReq => - Common.Utils.makePromise((res, rej) => { - lightningServices.lightning.decodePayReq( - { pay_req: payReq }, - /** - * @param {{ message: any; }} err - * @param {any} paymentRequest - */ - (err, paymentRequest) => { +const newAddress = (type = 0) => { + const { lightning } = lightningServices.getServices() + + return Common.Utils.makePromise((res, rej) => { + lightning.newAddress({ type }, (err, response) => { + if (err) { + rej(new Error(err.message)) + } else { + res(response.address) + } + }) + }) +} + +/** + * @param {number} minConfs + * @param {number} maxConfs + * @returns {Promise} + */ +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(paymentRequest) + res(unspent.utxos) + } + } + ) + }) + +/** + * @typedef {import('./types').ListChannelsReq} ListChannelsReq + */ + +/** + * @param {ListChannelsReq} req + * @returns {Promise} + */ +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} + */ +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 + // 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} + */ +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} + */ +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 + // 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} + */ +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 + // to an actual object :shrugs + res(/** @type {AddInvoiceRes} */ (resp)) } } ) @@ -406,5 +623,13 @@ module.exports = { sendPaymentV2Keysend, sendPaymentV2Invoice, listPayments, - decodePayReq + decodePayReq, + newAddress, + listUnspent, + listChannels, + getChanInfo, + listPeers, + pendingChannels, + addInvoice, + myLNDPub } diff --git a/utils/lndJobs.js b/utils/lndJobs.js deleted file mode 100644 index 0c5791f2..00000000 --- a/utils/lndJobs.js +++ /dev/null @@ -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 -} diff --git a/yarn.lock b/yarn.lock index 5e7e8649..a66b0444 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,12 +2,12 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" - integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.11", "@babel/code-frame@^7.5.5": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== dependencies: - "@babel/highlight" "^7.0.0" + "@babel/highlight" "^7.10.4" "@babel/core@^7.1.0": version "7.6.2" @@ -29,6 +29,15 @@ semver "^5.4.1" source-map "^0.5.0" +"@babel/generator@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.11.tgz#98a7df7b8c358c9a37ab07a24056853016aba3af" + integrity sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA== + dependencies: + "@babel/types" "^7.12.11" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/generator@^7.4.0", "@babel/generator@^7.6.2": version "7.6.2" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.2.tgz#dac8a3c2df118334c2a29ff3446da1636a8f8c03" @@ -39,79 +48,101 @@ lodash "^4.17.13" source-map "^0.5.0" -"@babel/generator@^7.6.3": - version "7.6.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.4.tgz#a4f8437287bf9671b07f483b76e3bb731bc97671" - integrity sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w== +"@babel/helper-create-class-features-plugin@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz#3c45998f431edd4a9214c5f1d3ad1448a6137f6e" + integrity sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w== dependencies: - "@babel/types" "^7.6.3" - jsesc "^2.5.1" - lodash "^4.17.13" - source-map "^0.5.0" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-member-expression-to-functions" "^7.12.1" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/helper-replace-supers" "^7.12.1" + "@babel/helper-split-export-declaration" "^7.10.4" -"@babel/helper-create-class-features-plugin@^7.5.5": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.6.0.tgz#769711acca889be371e9bc2eb68641d55218021f" - integrity sha512-O1QWBko4fzGju6VoVvrZg0RROCVifcLxiApnGP3OWfWzvxRZFCoBD81K5ur5e3bVY2Vf/5rIJm8cqPKn8HUJng== +"@babel/helper-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" + integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== dependencies: - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-member-expression-to-functions" "^7.5.5" - "@babel/helper-optimise-call-expression" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-replace-supers" "^7.5.5" - "@babel/helper-split-export-declaration" "^7.4.4" + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" -"@babel/helper-function-name@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" - integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw== +"@babel/helper-function-name@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz#1fd7738aee5dcf53c3ecff24f1da9c511ec47b42" + integrity sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA== dependencies: - "@babel/helper-get-function-arity" "^7.0.0" - "@babel/template" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/helper-get-function-arity" "^7.12.10" + "@babel/template" "^7.12.7" + "@babel/types" "^7.12.11" -"@babel/helper-get-function-arity@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" - integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ== +"@babel/helper-get-function-arity@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" + integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.10.4" -"@babel/helper-member-expression-to-functions@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz#1fb5b8ec4453a93c439ee9fe3aeea4a84b76b590" - integrity sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA== +"@babel/helper-get-function-arity@^7.12.10": + version "7.12.10" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz#b158817a3165b5faa2047825dfa61970ddcc16cf" + integrity sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag== dependencies: - "@babel/types" "^7.5.5" + "@babel/types" "^7.12.10" -"@babel/helper-optimise-call-expression@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz#a2920c5702b073c15de51106200aa8cad20497d5" - integrity sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g== +"@babel/helper-member-expression-to-functions@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.1.tgz#fba0f2fcff3fba00e6ecb664bb5e6e26e2d6165c" + integrity sha512-k0CIe3tXUKTRSoEx1LQEPFU9vRQfqHtl+kf8eNnDqb4AUJEy5pz6aIiog+YWtVm2jpggjS1laH68bPsR+KWWPQ== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.12.1" -"@babel/helper-plugin-utils@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" - integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA== - -"@babel/helper-replace-supers@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz#f84ce43df031222d2bad068d2626cb5799c34bc2" - integrity sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg== +"@babel/helper-optimise-call-expression@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" + integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== dependencies: - "@babel/helper-member-expression-to-functions" "^7.5.5" - "@babel/helper-optimise-call-expression" "^7.0.0" - "@babel/traverse" "^7.5.5" - "@babel/types" "^7.5.5" + "@babel/types" "^7.10.4" -"@babel/helper-split-export-declaration@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" - integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" + integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== + +"@babel/helper-replace-supers@^7.12.1": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.12.5.tgz#f009a17543bbbbce16b06206ae73b63d3fca68d9" + integrity sha512-5YILoed0ZyIpF4gKcpZitEnXEJ9UoDRki1Ey6xz46rxOzfNMAhVIJMoune1hmPVxh40LRv1+oafz7UsWX+vyWA== dependencies: - "@babel/types" "^7.4.4" + "@babel/helper-member-expression-to-functions" "^7.12.1" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/traverse" "^7.12.5" + "@babel/types" "^7.12.5" + +"@babel/helper-split-export-declaration@^7.10.4": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" + integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== + dependencies: + "@babel/types" "^7.11.0" + +"@babel/helper-split-export-declaration@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz#1b4cc424458643c47d37022223da33d76ea4603a" + integrity sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g== + dependencies: + "@babel/types" "^7.12.11" + +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" + integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== + +"@babel/helper-validator-identifier@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" + integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== "@babel/helpers@^7.6.2": version "7.6.2" @@ -122,32 +153,27 @@ "@babel/traverse" "^7.6.2" "@babel/types" "^7.6.0" -"@babel/highlight@^7.0.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" - integrity sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ== +"@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== dependencies: + "@babel/helper-validator-identifier" "^7.10.4" chalk "^2.0.0" - esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.6.3": - version "7.6.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.4.tgz#cb9b36a7482110282d5cb6dd424ec9262b473d81" - integrity sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A== +"@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.4.3", "@babel/parser@^7.6.0", "@babel/parser@^7.6.2", "@babel/parser@^7.7.0": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.11.tgz#9ce3595bcd74bc5c466905e86c535b8b25011e79" + integrity sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg== -"@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.6.0", "@babel/parser@^7.6.2": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.2.tgz#205e9c95e16ba3b8b96090677a67c9d6075b70a1" - integrity sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg== - -"@babel/plugin-proposal-class-properties@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.5.5.tgz#a974cfae1e37c3110e71f3c6a2e48b8e71958cd4" - integrity sha512-AF79FsnWFxjlaosgdi421vmYG6/jg79bVD0dpD44QdgobzHKuLZ6S3vl8la9qIeSwGi8i1fS0O1mfuDAAdo1/A== +"@babel/plugin-proposal-class-properties@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz#a082ff541f2a29a4821065b8add9346c0c16e5de" + integrity sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w== dependencies: - "@babel/helper-create-class-features-plugin" "^7.5.5" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-create-class-features-plugin" "^7.12.1" + "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-object-rest-spread@^7.0.0": version "7.2.0" @@ -157,13 +183,31 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/runtime@^7.6.3": - version "7.11.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736" - integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw== + version "7.12.18" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.18.tgz#af137bd7e7d9705a412b3caaf991fe6aaa97831b" + integrity sha512-BogPQ7ciE6SYAUPtlm9tWbgI9+2AgqSam6QivMgXgAT+fKbgppaj4ZX15MHeLC1PVF5sNk70huBu20XxWOs8Cg== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.6.0": +"@babel/template@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" + integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/parser" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/template@^7.12.7": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc" + integrity sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/parser" "^7.12.7" + "@babel/types" "^7.12.7" + +"@babel/template@^7.4.0", "@babel/template@^7.6.0": version "7.6.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6" integrity sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ== @@ -172,52 +216,28 @@ "@babel/parser" "^7.6.0" "@babel/types" "^7.6.0" -"@babel/traverse@^7.0.0": - version "7.6.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.3.tgz#66d7dba146b086703c0fb10dd588b7364cec47f9" - integrity sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw== +"@babel/traverse@^7.1.0", "@babel/traverse@^7.12.5", "@babel/traverse@^7.4.3", "@babel/traverse@^7.6.2", "@babel/traverse@^7.7.0": + version "7.12.12" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.12.tgz#d0cd87892704edd8da002d674bc811ce64743376" + integrity sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w== dependencies: - "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.6.3" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.4.4" - "@babel/parser" "^7.6.3" - "@babel/types" "^7.6.3" + "@babel/code-frame" "^7.12.11" + "@babel/generator" "^7.12.11" + "@babel/helper-function-name" "^7.12.11" + "@babel/helper-split-export-declaration" "^7.12.11" + "@babel/parser" "^7.12.11" + "@babel/types" "^7.12.12" debug "^4.1.0" globals "^11.1.0" - lodash "^4.17.13" + lodash "^4.17.19" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.5.5", "@babel/traverse@^7.6.2": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.2.tgz#b0e2bfd401d339ce0e6c05690206d1e11502ce2c" - integrity sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ== +"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.11.0", "@babel/types@^7.12.1", "@babel/types@^7.12.10", "@babel/types@^7.12.11", "@babel/types@^7.12.12", "@babel/types@^7.12.5", "@babel/types@^7.12.7", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.6.0", "@babel/types@^7.7.0": + version "7.12.12" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.12.tgz#4608a6ec313abbd87afa55004d373ad04a96c299" + integrity sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ== dependencies: - "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.6.2" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.4.4" - "@babel/parser" "^7.6.2" - "@babel/types" "^7.6.0" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.13" - -"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5", "@babel/types@^7.6.0": - version "7.6.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.1.tgz#53abf3308add3ac2a2884d539151c57c4b3ac648" - integrity sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g== - dependencies: - esutils "^2.0.2" - lodash "^4.17.13" - to-fast-properties "^2.0.0" - -"@babel/types@^7.6.3": - version "7.6.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.3.tgz#3f07d96f854f98e2fbd45c64b0cb942d11e8ba09" - integrity sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA== - dependencies: - esutils "^2.0.2" - lodash "^4.17.13" + "@babel/helper-validator-identifier" "^7.12.11" + lodash "^4.17.19" to-fast-properties "^2.0.0" "@cnakazawa/watch@^1.0.3": @@ -237,10 +257,19 @@ enabled "2.0.x" kuler "^2.0.0" -"@grpc/proto-loader@^0.5.1": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.5.2.tgz#c84f83be962f518bc303ca2d5e6ef2239439786c" - integrity sha512-eBKD/FPxQoY1x6QONW2nBd54QUEyzcFP9FenujmoeDPy1rutVSHki1s/wR68F6O1QfCNDx+ayBH1O2CVNMzyyw== +"@grpc/grpc-js@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.2.2.tgz#ee4a7417fe15a686a8e369c36ec4c80678445c67" + integrity sha512-iK/T984Ni6VnmlQK/LJdUk+VsXSaYIWkgzJ0LyOcxN2SowAmoRjG28kS7B1ui/q/MAv42iM3051WBt5QorFxmg== + dependencies: + "@types/node" "^12.12.47" + google-auth-library "^6.1.1" + semver "^6.2.0" + +"@grpc/proto-loader@^0.5.5": + version "0.5.5" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.5.5.tgz#6725e7a1827bdf8e92e29fbf4e9ef0203c0906a9" + integrity sha512-WwN9jVNdHRQoOBo9FDH7qU+mgfjPc8GygPYms3M+y3fbQLfnCe/Kv/E01t7JRgnrsOHH8euvSbed3mIalXhwqQ== dependencies: lodash.camelcase "^4.3.0" protobufjs "^6.8.6" @@ -693,6 +722,14 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw== +"@types/node-fetch@^2.5.8": + version "2.5.8" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb" + integrity sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw== + dependencies: + "@types/node" "*" + form-data "^3.0.0" + "@types/node@*": version "12.7.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.4.tgz#64db61e0359eb5a8d99b55e05c729f130a678b04" @@ -703,11 +740,21 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.17.tgz#b96d4dd3e427382482848948041d3754d40fd5ce" integrity sha512-p/sGgiPaathCfOtqu2fx5Mu1bcjuP8ALFg4xpGgNkcin7LwRyzUKniEHBKdcE1RPsenq5JVPIpMTJSygLboygQ== +"@types/node@^12.12.47": + version "12.19.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.8.tgz#efd6d1a90525519fc608c9db16c8a78f7693a978" + integrity sha512-D4k2kNi0URNBxIRCb1khTnkWNHv8KSL1owPmS/K5e5t8B2GzMReY7AsJIY1BnP5KdlgC4rj9jk2IkDMasIE7xg== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/prop-types@*": + version "15.7.3" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" + integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== + "@types/pvutils@*": version "0.0.2" resolved "https://registry.npmjs.org/@types/pvutils/-/pvutils-0.0.2.tgz#e21684962cfa58ac920fd576d90556032dc86009" @@ -722,6 +769,14 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== +"@types/react@16.x.x": + version "16.14.2" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.2.tgz#85dcc0947d0645349923c04ccef6018a1ab7538c" + integrity sha512-BzzcAlyDxXl2nANlabtT4thtvbbnhee8hMmH/CcJrISDBVcJS1iOsP1f0OAgSdGE0MsY9tqcrb9YoZcOFv9dbQ== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + "@types/serve-static@*": version "1.13.3" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1" @@ -789,6 +844,13 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -840,6 +902,13 @@ after@0.8.2: resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + aggregate-error@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" @@ -965,6 +1034,11 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -1007,6 +1081,11 @@ arraybuffer.slice@~0.0.7: resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog== +arrify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" + integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== + asap@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -1034,6 +1113,11 @@ asn1js@^2.0.26: dependencies: pvutils latest +assert-never@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/assert-never/-/assert-never-1.2.1.tgz#11f0e363bf146205fb08193b5c7b90f4d1cf44fe" + integrity sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw== + assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" @@ -1097,10 +1181,10 @@ axios@0.19.0: follow-redirects "1.5.10" is-buffer "^2.0.2" -axios@^0.20.0: - version "0.20.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.20.0.tgz#057ba30f04884694993a8cd07fa394cff11c50bd" - integrity sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA== +axios@^0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== dependencies: follow-redirects "^1.10.0" @@ -1113,15 +1197,15 @@ babel-code-frame@^6.26.0: esutils "^2.0.2" js-tokens "^3.0.2" -babel-eslint@^10.0.3: - version "10.0.3" - resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.3.tgz#81a2c669be0f205e19462fed2482d33e4687a88a" - integrity sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA== +babel-eslint@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" + integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.0.0" - "@babel/traverse" "^7.0.0" - "@babel/types" "^7.0.0" + "@babel/parser" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" eslint-visitor-keys "^1.0.0" resolve "^1.12.0" @@ -1264,6 +1348,11 @@ base64-js@^1.0.2: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== +base64-js@^1.3.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + base64id@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" @@ -1308,6 +1397,11 @@ big.js@^5.2.2: resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== +bignumber.js@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" + integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA== + binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" @@ -1330,12 +1424,7 @@ blob@0.0.5: resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig== -bluebird@^3.5.0: - version "3.5.5" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" - integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== - -bluebird@^3.7.2: +bluebird@^3.5.0, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -1752,7 +1841,7 @@ colour@~0.7.1: resolved "https://registry.yarnpkg.com/colour/-/colour-0.7.1.tgz#9cb169917ec5d12c0736d3e8685746df1cadf778" integrity sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g= -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -1916,6 +2005,11 @@ create-error-class@^3.0.0: dependencies: capture-stack-trace "^1.0.0" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-spawn@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -1962,6 +2056,11 @@ cssstyle@^1.0.0: dependencies: cssom "0.3.x" +csstype@^3.0.2: + version "3.0.6" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.6.tgz#865d0b5833d7d8d40f4e5b8a6d76aea3de4725ef" + integrity sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw== + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -1985,7 +2084,14 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: dependencies: ms "2.0.0" -debug@4.1.1, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +debug@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== @@ -2107,6 +2213,11 @@ diff-sequences@^24.9.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" @@ -2146,7 +2257,7 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -ecdsa-sig-formatter@1.0.11: +ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== @@ -2478,6 +2589,11 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + exec-sh@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.2.tgz#6738de2eb7c8e671d0366aea0b0db8c6f7d7391b" @@ -2554,18 +2670,18 @@ expect@^24.9.0: jest-message-util "^24.9.0" jest-regex-util "^24.9.0" -express-session@^1.15.1: - version "1.16.2" - resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.16.2.tgz#59f36d7770e94872d19b163b6708a2d16aa6848c" - integrity sha512-oy0sRsdw6n93E9wpCNWKRnSsxYnSDX9Dnr9mhZgqUEEorzcq5nshGYSZ4ZReHFhKQ80WI5iVUUSPW7u3GaKauw== +express-session@^1.17.1: + version "1.17.1" + resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.1.tgz#36ecbc7034566d38c8509885c044d461c11bf357" + integrity sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q== dependencies: - cookie "0.3.1" + cookie "0.4.0" cookie-signature "1.0.6" debug "2.6.9" depd "~2.0.0" on-headers "~1.0.2" parseurl "~1.3.3" - safe-buffer "5.1.2" + safe-buffer "5.2.0" uid-safe "~2.1.5" express@^4.14.1: @@ -2619,7 +2735,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@~3.0.2: +extend@^3.0.2, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -2687,6 +2803,11 @@ fast-safe-stringify@^2.0.4: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== +fast-text-encoding@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" + integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== + fb-watchman@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" @@ -2820,6 +2941,15 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -2909,6 +3039,11 @@ gcp-metadata@^4.2.0: gaxios "^4.0.0" json-bigint "^1.0.0" +get-caller-file@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" + integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== + get-caller-file@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -2999,6 +3134,28 @@ globals@^9.18.0: resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== +google-auth-library@^6.1.1: + version "6.1.3" + resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-6.1.3.tgz#39d868140b70d0c4b32c6f6d8f4ccc1400d84dca" + integrity sha512-m9mwvY3GWbr7ZYEbl61isWmk+fvTmOt0YNUfPOUY2VH8K5pZlAIWJjxEi0PqR3OjMretyiQLI6GURMrPSwHQ2g== + dependencies: + arrify "^2.0.0" + base64-js "^1.3.0" + ecdsa-sig-formatter "^1.0.11" + fast-text-encoding "^1.0.0" + gaxios "^4.0.0" + gcp-metadata "^4.2.0" + gtoken "^5.0.4" + jws "^4.0.0" + lru-cache "^6.0.0" + +google-p12-pem@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-3.0.3.tgz#673ac3a75d3903a87f05878f3c75e06fc151669e" + integrity sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA== + dependencies: + node-forge "^0.10.0" + google-proto-files@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/google-proto-files/-/google-proto-files-1.1.0.tgz#3d96148ddd07dd3facd016e5191966776c3f5897" @@ -3053,6 +3210,16 @@ grpc@1.24.4: node-pre-gyp "^0.16.0" protobufjs "^5.0.3" +gtoken@^5.0.4: + version "5.1.0" + resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-5.1.0.tgz#4ba8d2fc9a8459098f76e7e8fd7beaa39fda9fe4" + integrity sha512-4d8N6Lk8TEAHl9vVoRVMh9BNOKWVgl2DdNtr3428O75r3QFrF/a5MMu851VmK0AA8+iSvbwRv69k5XnMLURGhg== + dependencies: + gaxios "^4.0.0" + google-p12-pem "^3.0.3" + jws "^4.0.0" + mime "^2.2.0" + "gun@git://github.com/amark/gun#97aa976c97e6219a9f93095d32c220dcd371ca62": version "0.2020.520" resolved "git://github.com/amark/gun#97aa976c97e6219a9f93095d32c220dcd371ca62" @@ -3226,6 +3393,14 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -3349,9 +3524,9 @@ inherits@=2.0.1: integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= ini@^1.3.4, ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== inquirer@^7.0.0: version "7.0.0" @@ -4138,6 +4313,13 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-bigint@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" + integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== + dependencies: + bignumber.js "^9.0.0" + json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -4212,6 +4394,15 @@ jwa@^1.4.1: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" +jwa@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc" + integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + jws@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" @@ -4220,6 +4411,14 @@ jws@^3.2.2: jwa "^1.4.1" safe-buffer "^5.0.1" +jws@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4" + integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg== + dependencies: + jwa "^2.0.0" + safe-buffer "^5.0.1" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -4487,6 +4686,13 @@ lru-cache@^4.0.1: pseudomap "^1.0.2" yallist "^2.1.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + make-dir@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" @@ -4502,6 +4708,11 @@ make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + makeerror@1.0.x: version "1.0.11" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" @@ -4600,6 +4811,11 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@^2.2.0: + version "2.4.6" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" + integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -4692,7 +4908,7 @@ ms@2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -ms@^2.1.1: +ms@2.1.2, ms@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== @@ -4762,6 +4978,16 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +node-fetch@^2.3.0, node-fetch@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== + +node-forge@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" + integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -4874,9 +5100,9 @@ normalize-path@^3.0.0: integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== normalizr@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/normalizr/-/normalizr-3.6.0.tgz#b8bbc4546ffe43c1c2200503041642915fcd3e1c" - integrity sha512-25cd8DiDu+pL46KIaxtVVvvEPjGacJgv0yUg950evr62dQ/ks2JO1kf7+Vi5/rMFjaSTSTls7aCnmRlUSljtiA== + version "3.6.1" + resolved "https://registry.yarnpkg.com/normalizr/-/normalizr-3.6.1.tgz#d367ab840e031ff382141b8d81ce279292ff69fe" + integrity sha512-8iEmqXmPtll8PwbEFrbPoDxVw7MKnNvt3PZzR2Xvq9nggEEOgBlNICPXYzyZ4w4AkHUzCU998mdatER3n2VaMA== npm-bundled@^1.0.1: version "1.0.6" @@ -5349,10 +5575,10 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -promise@^8.0.1: - version "8.0.3" - resolved "https://registry.yarnpkg.com/promise/-/promise-8.0.3.tgz#f592e099c6cddc000d538ee7283bb190452b0bf6" - integrity sha512-HeRDUL1RJiLhyA0/grn+PTShlBAcLuh/1BJGtrvjwbvRDCTLLMEz9rOGCV+R3vHY4MixIuoMEd9Yq/XvsTPcjw== +promise@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" + integrity sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q== dependencies: asap "~2.0.6" @@ -5656,6 +5882,13 @@ request-promise-core@1.1.2: dependencies: lodash "^4.17.11" +request-promise-core@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" + integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== + dependencies: + lodash "^4.17.19" + request-promise-native@^1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.7.tgz#a49868a624bdea5069f1251d0a836e0d89aa2c59" @@ -5665,13 +5898,13 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request-promise@^4.2.2: - version "4.2.4" - resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.4.tgz#1c5ed0d71441e38ad58c7ce4ea4ea5b06d54b310" - integrity sha512-8wgMrvE546PzbR5WbYxUQogUnUDfM0S7QIFZMID+J73vdFARkFy+HElj4T+MWYhpXwlLp0EQ8Zoj8xUA0he4Vg== +request-promise@^4.2.6: + version "4.2.6" + resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.6.tgz#7e7e5b9578630e6f598e3813c0f8eb342a27f0a2" + integrity sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ== dependencies: bluebird "^3.5.0" - request-promise-core "1.1.2" + request-promise-core "1.1.4" stealthy-require "^1.1.1" tough-cookie "^2.3.3" @@ -5780,6 +6013,13 @@ rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: dependencies: glob "^7.1.3" +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + rsvp@^4.8.4: version "4.8.5" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" @@ -5811,6 +6051,11 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-buffer@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -5972,10 +6217,10 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== -shock-common@17.x.x: - version "17.0.0" - resolved "https://registry.yarnpkg.com/shock-common/-/shock-common-17.0.0.tgz#9005e35338d58f5aab396ed2dbe6b241c60d5995" - integrity sha512-OW0KsWaFf/8dq4W8z9047SZRXLi7OggGWrJeRhvw4b79d50uI3/7fXGmquoNXoEnVC8iDAFRjaC35hDSKg+mGA== +shock-common@32.0.0: + version "32.0.0" + resolved "https://registry.yarnpkg.com/shock-common/-/shock-common-32.0.0.tgz#bb7f70d7a572783c46aeae7420179935eb5096d4" + integrity sha512-1GorUFRpkRGXdKT9PImwnj2orpoJaESU/iD+rvL8sqFMLKazkW9LfLAcRwEWCvytWdKFJ35UO2gN49N8dPiRmA== dependencies: immer "^6.0.6" lodash "^4.17.19" @@ -6126,6 +6371,14 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" +source-map-support@^0.5.17: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map-support@^0.5.6: version "0.5.13" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" @@ -6605,6 +6858,18 @@ triple-beam@^1.2.0, triple-beam@^1.3.0: resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== +ts-node@^9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" + integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg== + dependencies: + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + ts-toolbelt@^6.6.2: version "6.7.7" resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.7.7.tgz#4d7f34cafd519bf0f1d6da5a4b07889db6ba0184" @@ -6694,10 +6959,10 @@ typescript-tuple@^2.2.1: dependencies: typescript-compare "^0.0.2" -typescript@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.2.tgz#7ea7c88777c723c681e33bf7988be5d008d05ac2" - integrity sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ== +typescript@latest: + version "4.1.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.5.tgz#123a3b214aaff3be32926f0d8f1f6e704eb89a72" + integrity sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA== uglify-js@^3.1.4: version "3.10.2" @@ -7123,6 +7388,11 @@ yallist@^3.0.0, yallist@^3.0.3: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yaml@^1.7.2: version "1.10.0" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" @@ -7169,3 +7439,8 @@ yeast@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==