From c19c4c2fec63256a94b2bdb3bfaab90e9c7eec8b Mon Sep 17 00:00:00 2001 From: Bronwen Date: Thu, 8 Apr 2021 22:02:58 -0400 Subject: [PATCH] serviceable --- package.json | 56 ++--- relay/index.js | 3 +- {lib => src/lib}/Gateway.js | 324 +++++++++++++-------------- {lib => src/lib}/Identity.js | 0 {lib => src/lib}/Profiles.js | 0 {lib => src/lib}/STP/index.js | 374 ++++++++++++++++---------------- {lib => src/lib}/STP/packets.js | 228 +++++++++---------- {lib => src/lib}/appdata.js | 0 {lib => src/lib}/config.js | 67 +++--- {lib => src/lib}/node.js | 248 ++++++++++----------- {lib => src/lib}/title.js | 0 {lib => src/lib}/upnp.js | 132 +++++------ src/main.dev.ts | 4 + yarn.lock | 158 ++++++++++---- 14 files changed, 830 insertions(+), 764 deletions(-) rename {lib => src/lib}/Gateway.js (95%) rename {lib => src/lib}/Identity.js (100%) rename {lib => src/lib}/Profiles.js (100%) rename {lib => src/lib}/STP/index.js (95%) rename {lib => src/lib}/STP/packets.js (93%) rename {lib => src/lib}/appdata.js (100%) rename {lib => src/lib}/config.js (85%) rename {lib => src/lib}/node.js (92%) rename {lib => src/lib}/title.js (100%) rename {lib => src/lib}/upnp.js (95%) diff --git a/package.json b/package.json index a855313..6d6cecf 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,15 @@ { + "config": { + "ports": { + "relay": 5600, + "relayEnd": 5699, + "http": 5700, + "service": 5000 + }, + "endpoints": [ + "valnet.xyz:5500" + ] + }, "name": "electron-react-boilerplate", "productName": "ElectronReact", "description": "Electron application boilerplate based on React, React Router, Webpack, React Fast Refresh for rapid application development", @@ -15,20 +26,6 @@ "start:renderer": "cross-env NODE_ENV=development webpack serve --config ./.erb/configs/webpack.config.renderer.dev.babel.js", "test": "jest" }, - "lint-staged": { - "*.{js,jsx,ts,tsx}": [ - "cross-env NODE_ENV=development eslint --cache" - ], - "{*.json,.{babelrc,eslintrc,prettierrc}}": [ - "prettier --ignore-path .eslintignore --parser json --write" - ], - "*.{css,scss}": [ - "prettier --ignore-path .eslintignore --single-quote --write" - ], - "*.{html,md,yml}": [ - "prettier --ignore-path .eslintignore --single-quote --write" - ] - }, "build": { "productName": "ElectronReact", "appId": "org.erb.ElectronReact", @@ -213,13 +210,11 @@ "eslint-config-airbnb": "^18.2.0", "eslint-config-airbnb-typescript": "^12.0.0", "eslint-config-erb": "^2.0.0", - "eslint-config-prettier": "^6.11.0", "eslint-import-resolver-webpack": "^0.13.0", "eslint-plugin-compat": "^3.8.0", "eslint-plugin-import": "^2.22.0", "eslint-plugin-jest": "^24.1.3", "eslint-plugin-jsx-a11y": "6.4.1", - "eslint-plugin-prettier": "^3.1.4", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-react": "^7.20.6", "eslint-plugin-react-hooks": "^4.0.8", @@ -231,7 +226,6 @@ "mini-css-extract-plugin": "^1.3.1", "node-sass": "^5.0.0", "opencollective-postinstall": "^2.0.3", - "prettier": "^2.0.5", "react-refresh": "^0.9.0", "react-test-renderer": "^17.0.1", "rimraf": "^3.0.0", @@ -248,15 +242,23 @@ "yarn-deduplicate": "^3.1.0" }, "dependencies": { + "bonjour": "^3.5.0", "electron-debug": "^3.1.0", "electron-log": "^4.2.4", "electron-updater": "^4.3.4", "history": "^5.0.0", + "human-readable-ids": "^1.0.4", + "keyv": "^4.0.3", + "keyv-file": "^0.2.0", + "md5": "^2.3.0", "menubar": "^9.0.3", + "nat-upnp": "^1.1.1", + "node-rsa": "^1.1.1", "react": "^17.0.1", "react-dom": "^17.0.1", "react-router-dom": "^5.2.0", "regenerator-runtime": "^0.13.5", + "signale": "^1.4.0", "source-map-support": "^0.5.19" }, "devEngines": { @@ -268,21 +270,6 @@ "url": "https://opencollective.com/electron-react-boilerplate-594" }, "browserslist": [], - "prettier": { - "overrides": [ - { - "files": [ - ".prettierrc", - ".babelrc", - ".eslintrc" - ], - "options": { - "parser": "json" - } - } - ], - "singleQuote": true - }, "renovate": { "extends": [ "bliss" @@ -290,10 +277,5 @@ "baseBranches": [ "next" ] - }, - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } } } diff --git a/relay/index.js b/relay/index.js index e9f69c6..f887359 100644 --- a/relay/index.js +++ b/relay/index.js @@ -5,14 +5,13 @@ const { title } = require('../lib/title'); const log = require('signale').scope('RLAY'); const { Identity } = require('../lib/Identity'); title('relay', false); -const identity = await new Identity('relay', 'default'); const Node = require('../lib/node'); const { config } = require('../lib/config'); const { ensureDirSync } = require('fs-extra'); const appdata = require('../lib/appdata'); ensureDirSync(`${appdata}/valnet/relay`); -const node = new Node(identity); +const node = new Node(); // ==================================== [EXPRESS] const express = require('express'); diff --git a/lib/Gateway.js b/src/lib/Gateway.js similarity index 95% rename from lib/Gateway.js rename to src/lib/Gateway.js index 324bb77..a2c76cc 100644 --- a/lib/Gateway.js +++ b/src/lib/Gateway.js @@ -1,163 +1,163 @@ -const { config } = require('./config'); -const Keyv = require('keyv'); -const { KeyvFile } = require('keyv-file'); -const { Signale } = require('signale'); -const log = new Signale().scope('GTWY'); -const stp = require('./STP'); -const appdata = require('./appdata'); - -class Gateway { - constructor(identity, endpoints) { - this.identity = identity; - - this.endpoints = new Keyv({ - store: new KeyvFile({ - filename: `${appdata}/valnet/relay/${this.identity.name}-endpoints.json` - }) - }); - - this.ready = this.insertEndpoints(endpoints) - .then(this.networkTest.bind(this)); - } - - async insertEndpoints(endpoints) { - for (const endpoint of endpoints) { - const storeValue = await this.endpoints.get(endpoint); - if (storeValue) continue; - - const [host, port] = endpoint.split(':'); - const record = new EndpointRecord(host, port, null, 'unknown'); - const currentEnpoints = await this.endpoints.get('cache') || []; - - if (currentEnpoints.indexOf(endpoint) === -1) { - currentEnpoints.push(endpoint); - await this.endpoints.set('cache', currentEnpoints); - } - - await this.endpoints.set(endpoint, record); - } - - log.info('gateway endpoints:'); - for(const endpoint of (await this.endpoints.get('cache'))) { - log.info(`\t${endpoint}`); - } - } - - async networkTest() { - const endpoints = (await Promise.all( - (await this.endpoints.get('cache')) - .map(endpoint => this.endpoints.get(endpoint)) - )).map(EndpointRecord.fromJson); - - - for (const endpoint of endpoints) { - await this.testEndpoint(endpoint.host, endpoint.port); - } - } - - async testEndpoint(host, port) { - const log = new Signale({ scope: `${host}:${port}` }); - const interactive = new Signale({ interactive: true, scope: `${host}:${port}` }); - - await new Promise(async (res, rej) => { - let pings = []; - let maxPings = 1; - let connectionAttempts = 0; - let wasConnected = false; - - const done = _ => connectionAttempts === 2 || pings.length === maxPings - - log.info('Starting connection test...'); - - while (!done()) { - - await new Promise(async (res) => { - const client = stp.connect({ - identity: this.identity, - ip: host, - port: parseInt(port) - }); - - client.on('error', _ => _); - - client.on('ready', async () => { - wasConnected = true; - - while (pings.length < maxPings) { - log.info(`[${pings.length + 1}/${maxPings}] Testing connection`); - pings.push(await client.ping()); - // await new Promise(res => setTimeout(res, 1000)); - } - client.tcpSocket.destroy(); - res(); - }); - - client.on('close', () => { - connectionAttempts ++; - if(!done() && wasConnected) { - log.warn(`Lost connection, Retrying...`); - } - wasConnected = false; - res(); - }); - - }); - - } - - if (pings.length === maxPings) { - const average = Math.round(pings.reduce((a, v) => a + v, 0) / maxPings); - const pingRecord = new PingRecord(average, pings.length, new Date().getTime()); - const endpointRecord = new EndpointRecord(host, port, pingRecord, 'online'); - - await this.endpoints.set(`${host}:${port}`, endpointRecord); - - log.success(`Test complete. Average Ping: ${average}ms`); - } else { - log.error(`Could not complete connection test`) - } - - res(); - }); - } -} - -class EndpointRecord { - - /** - * @param {Object|string} json string / object representation - * @returns {EndpointRecord} - */ - static fromJson(obj) { - if (typeof obj === 'string') - return EndpointRecord.fromJson(JSON.parse(obj)); - - return new EndpointRecord( - obj.host, - obj.port, - obj.lastPing ? new PingRecord( - obj.lastPing.average, - obj.lastPing.tests, - obj.lastPing.date - ) : null, - obj.status - ); - } - - constructor(host, port, lastPing, status) { - this.host = host; - this.port = port; - this.lastPing = lastPing; - this.status = status; - } -} - -class PingRecord { - constructor(average, tests, date) { - this.average = average; - this.tests = tests; - this.date = date; - } -} - +const { config } = require('./config'); +const Keyv = require('keyv'); +const { KeyvFile } = require('keyv-file'); +const { Signale } = require('signale'); +const log = new Signale().scope('GTWY'); +const stp = require('./STP'); +const appdata = require('./appdata'); + +class Gateway { + constructor(identity, endpoints) { + this.identity = identity; + + this.endpoints = new Keyv({ + store: new KeyvFile({ + filename: `${appdata}/valnet/relay/${this.identity.name}-endpoints.json` + }) + }); + + this.ready = this.insertEndpoints(endpoints) + .then(this.networkTest.bind(this)); + } + + async insertEndpoints(endpoints) { + for (const endpoint of endpoints) { + const storeValue = await this.endpoints.get(endpoint); + if (storeValue) continue; + + const [host, port] = endpoint.split(':'); + const record = new EndpointRecord(host, port, null, 'unknown'); + const currentEnpoints = await this.endpoints.get('cache') || []; + + if (currentEnpoints.indexOf(endpoint) === -1) { + currentEnpoints.push(endpoint); + await this.endpoints.set('cache', currentEnpoints); + } + + await this.endpoints.set(endpoint, record); + } + + log.info('gateway endpoints:'); + for(const endpoint of (await this.endpoints.get('cache'))) { + log.info(`\t${endpoint}`); + } + } + + async networkTest() { + const endpoints = (await Promise.all( + (await this.endpoints.get('cache')) + .map(endpoint => this.endpoints.get(endpoint)) + )).map(EndpointRecord.fromJson); + + + for (const endpoint of endpoints) { + await this.testEndpoint(endpoint.host, endpoint.port); + } + } + + async testEndpoint(host, port) { + const log = new Signale({ scope: `${host}:${port}` }); + const interactive = new Signale({ interactive: true, scope: `${host}:${port}` }); + + await new Promise(async (res, rej) => { + let pings = []; + let maxPings = 1; + let connectionAttempts = 0; + let wasConnected = false; + + const done = _ => connectionAttempts === 2 || pings.length === maxPings + + log.info('Starting connection test...'); + + while (!done()) { + + await new Promise(async (res) => { + const client = stp.connect({ + identity: this.identity, + ip: host, + port: parseInt(port) + }); + + client.on('error', _ => _); + + client.on('ready', async () => { + wasConnected = true; + + while (pings.length < maxPings) { + log.info(`[${pings.length + 1}/${maxPings}] Testing connection`); + pings.push(await client.ping()); + // await new Promise(res => setTimeout(res, 1000)); + } + client.tcpSocket.destroy(); + res(); + }); + + client.on('close', () => { + connectionAttempts ++; + if(!done() && wasConnected) { + log.warn(`Lost connection, Retrying...`); + } + wasConnected = false; + res(); + }); + + }); + + } + + if (pings.length === maxPings) { + const average = Math.round(pings.reduce((a, v) => a + v, 0) / maxPings); + const pingRecord = new PingRecord(average, pings.length, new Date().getTime()); + const endpointRecord = new EndpointRecord(host, port, pingRecord, 'online'); + + await this.endpoints.set(`${host}:${port}`, endpointRecord); + + log.success(`Test complete. Average Ping: ${average}ms`); + } else { + log.error(`Could not complete connection test`) + } + + res(); + }); + } +} + +class EndpointRecord { + + /** + * @param {Object|string} json string / object representation + * @returns {EndpointRecord} + */ + static fromJson(obj) { + if (typeof obj === 'string') + return EndpointRecord.fromJson(JSON.parse(obj)); + + return new EndpointRecord( + obj.host, + obj.port, + obj.lastPing ? new PingRecord( + obj.lastPing.average, + obj.lastPing.tests, + obj.lastPing.date + ) : null, + obj.status + ); + } + + constructor(host, port, lastPing, status) { + this.host = host; + this.port = port; + this.lastPing = lastPing; + this.status = status; + } +} + +class PingRecord { + constructor(average, tests, date) { + this.average = average; + this.tests = tests; + this.date = date; + } +} + module.exports = Gateway \ No newline at end of file diff --git a/lib/Identity.js b/src/lib/Identity.js similarity index 100% rename from lib/Identity.js rename to src/lib/Identity.js diff --git a/lib/Profiles.js b/src/lib/Profiles.js similarity index 100% rename from lib/Profiles.js rename to src/lib/Profiles.js diff --git a/lib/STP/index.js b/src/lib/STP/index.js similarity index 95% rename from lib/STP/index.js rename to src/lib/STP/index.js index 0025f90..b29de2c 100644 --- a/lib/STP/index.js +++ b/src/lib/STP/index.js @@ -1,187 +1,187 @@ -const net = require('net'); -const EventEmitter = require('events'); -const NodeRSA = require('node-rsa'); -const log = require('signale').scope('_STP'); -const debug = require('debug')('xyz:valnet:stp'); -const { - KeyExchangePacket, - AckPacket, - PingPacket, - PongPacket -} = require('./packets'); - -module.exports.createServer = function({identity = {}, port = 5000} = {}, cb = _ => _) { - const server = new Server(identity, port); - server.on('connection', connection => { - cb(connection); - }); - // return 5; -} - -module.exports.connect = function({ - identity, - port, - ip -}) { - return new STPSocket(net.connect(port, ip), identity); -} - -class Server extends EventEmitter { - tcpServer; - identity; - port; - - constructor(identity, port) { - super(); - this.identity = identity; - this.port = port; - this.openServer(); - } - - openServer() { - // log.info(`opening STP server on ${this.port}`); - this.tcpServer = net.createServer(this.tcpConnectClient.bind(this)); - this.tcpServer.on('error', e => { - log.warn(e) - setTimeout(this.openServer.bind(this), 5000); - }) - this.tcpServer.listen(this.port); - } - - tcpConnectClient(tcpSocket) { - const socket = new STPSocket(tcpSocket, this.identity); - socket.on('ready', () => { - this.emit('connection', socket); - }) - } -} - -class STPSocket extends EventEmitter { - tcpSocket; - buffer = ''; - externalKey; - identity; - externalName; - - CONNECTING = Symbol('connecting'); - EXCHANGE = Symbol('exchange'); - SECURED = Symbol('secured'); - readyState = this.CONNECTING; - - pingCallbacks = new Map(); - - get loopback() { - return this.identity.publicKey === - this.externalKey.exportKey('pkcs8-public-pem'); - } - - get remoteAddress() { - return this.tcpSocket.remoteAddress; - } - - get remoteName() { - return this.externalName; - } - - get remoteIdentity() { - return this.externalKey.exportKey('pkcs8-public-pem'); - } - - get open() { - return this.tcpSocket.readyState === 'open'; - } - - get secured() { - return this.readyState; - } - - constructor(tcpSocket, identity) { - super(); - this.tcpSocket = tcpSocket; - this.identity = identity; - if(this.open) this.handshake(); - else this.tcpSocket.on('connect', this.handshake.bind(this)); - - this.tcpSocket.on('data', this.data.bind(this)); - this.tcpSocket.on('error', (...args) => this.emit('error', ...args)); - this.tcpSocket.on('close', (...args) => this.emit('close', ...args)); - } - - data(evt) { - this.buffer += evt.toString(); - this.processBuffer(); - } - - processBuffer() { - const parts = this.buffer.split(/(\x02[^\x02\x03]*\x03)/g); - this.buffer = ''; - - for(const message of parts) { - if(message.endsWith('\x03')) { - const obj = JSON.parse(message.substr(1, message.length - 2)); - this.processMessage(obj); - } else { - this.buffer += message; - } - } - } - - processMessage(obj) { - - if(this.readyState === this.CONNECTING && obj.cmd === 'KEY') { - debug('received remote public key...'); - this.externalKey = new NodeRSA(); - this.externalKey.importKey(obj.data.key, 'pkcs8-public-pem'); - this.externalName = obj.meta.name; - this.tcpSocket.write(new AckPacket().toBuffer()); - this.readyState = this.EXCHANGE; - debug('sent acknowledgement...'); - return; - } - - if(this.readyState === this.EXCHANGE && obj.cmd === 'ACK') { - debug('received acknowledgement...'); - this.readyState = this.SECURED; - this.emit('ready'); - return; - } - - if (this.readyState === this.SECURED && obj.cmd === 'PING') { - this.tcpSocket.write(new PongPacket(obj.data.id).toBuffer()); - return; - } - - if (this.readyState === this.SECURED && obj.cmd === 'PONG') { - if (this.pingCallbacks.has(obj.data.id)) { - this.pingCallbacks.get(obj.data.id)(); - } - return; - } - - } - - handshake() { - debug('connected') - const pk = this.identity.publicKey; - const packet = new KeyExchangePacket(pk, { - name: this.identity.name - }); - const buffer = packet.toBuffer(); - this.tcpSocket.write(buffer); - debug('sent public key...') - } - - async ping() { - const startTime = new Date().getTime(); - return await new Promise(async (res) => { - const packet = new PingPacket(); - this.pingCallbacks.set(packet.data.id, _ => { - res(new Date().getTime() - startTime); - this.pingCallbacks.delete(packet.data.id); - }); - this.tcpSocket.write(packet.toBuffer()); - debug('ping sent...'); - }) - - } -} +const net = require('net'); +const EventEmitter = require('events'); +const NodeRSA = require('node-rsa'); +const log = require('signale').scope('_STP'); +const debug = require('debug')('xyz:valnet:stp'); +const { + KeyExchangePacket, + AckPacket, + PingPacket, + PongPacket +} = require('./packets'); + +module.exports.createServer = function({identity = {}, port = 5000} = {}, cb = _ => _) { + const server = new Server(identity, port); + server.on('connection', connection => { + cb(connection); + }); + // return 5; +} + +module.exports.connect = function({ + identity, + port, + ip +}) { + return new STPSocket(net.connect(port, ip), identity); +} + +class Server extends EventEmitter { + tcpServer; + identity; + port; + + constructor(identity, port) { + super(); + this.identity = identity; + this.port = port; + this.openServer(); + } + + openServer() { + // log.info(`opening STP server on ${this.port}`); + this.tcpServer = net.createServer(this.tcpConnectClient.bind(this)); + this.tcpServer.on('error', e => { + log.warn(e) + setTimeout(this.openServer.bind(this), 5000); + }) + this.tcpServer.listen(this.port); + } + + tcpConnectClient(tcpSocket) { + const socket = new STPSocket(tcpSocket, this.identity); + socket.on('ready', () => { + this.emit('connection', socket); + }) + } +} + +class STPSocket extends EventEmitter { + tcpSocket; + buffer = ''; + externalKey; + identity; + externalName; + + CONNECTING = Symbol('connecting'); + EXCHANGE = Symbol('exchange'); + SECURED = Symbol('secured'); + readyState = this.CONNECTING; + + pingCallbacks = new Map(); + + get loopback() { + return this.identity.publicKey === + this.externalKey.exportKey('pkcs8-public-pem'); + } + + get remoteAddress() { + return this.tcpSocket.remoteAddress; + } + + get remoteName() { + return this.externalName; + } + + get remoteIdentity() { + return this.externalKey.exportKey('pkcs8-public-pem'); + } + + get open() { + return this.tcpSocket.readyState === 'open'; + } + + get secured() { + return this.readyState; + } + + constructor(tcpSocket, identity) { + super(); + this.tcpSocket = tcpSocket; + this.identity = identity; + if(this.open) this.handshake(); + else this.tcpSocket.on('connect', this.handshake.bind(this)); + + this.tcpSocket.on('data', this.data.bind(this)); + this.tcpSocket.on('error', (...args) => this.emit('error', ...args)); + this.tcpSocket.on('close', (...args) => this.emit('close', ...args)); + } + + data(evt) { + this.buffer += evt.toString(); + this.processBuffer(); + } + + processBuffer() { + const parts = this.buffer.split(/(\x02[^\x02\x03]*\x03)/g); + this.buffer = ''; + + for(const message of parts) { + if(message.endsWith('\x03')) { + const obj = JSON.parse(message.substr(1, message.length - 2)); + this.processMessage(obj); + } else { + this.buffer += message; + } + } + } + + processMessage(obj) { + + if(this.readyState === this.CONNECTING && obj.cmd === 'KEY') { + debug('received remote public key...'); + this.externalKey = new NodeRSA(); + this.externalKey.importKey(obj.data.key, 'pkcs8-public-pem'); + this.externalName = obj.meta.name; + this.tcpSocket.write(new AckPacket().toBuffer()); + this.readyState = this.EXCHANGE; + debug('sent acknowledgement...'); + return; + } + + if(this.readyState === this.EXCHANGE && obj.cmd === 'ACK') { + debug('received acknowledgement...'); + this.readyState = this.SECURED; + this.emit('ready'); + return; + } + + if (this.readyState === this.SECURED && obj.cmd === 'PING') { + this.tcpSocket.write(new PongPacket(obj.data.id).toBuffer()); + return; + } + + if (this.readyState === this.SECURED && obj.cmd === 'PONG') { + if (this.pingCallbacks.has(obj.data.id)) { + this.pingCallbacks.get(obj.data.id)(); + } + return; + } + + } + + handshake() { + debug('connected') + const pk = this.identity.publicKey; + const packet = new KeyExchangePacket(pk, { + name: this.identity.name + }); + const buffer = packet.toBuffer(); + this.tcpSocket.write(buffer); + debug('sent public key...') + } + + async ping() { + const startTime = new Date().getTime(); + return await new Promise(async (res) => { + const packet = new PingPacket(); + this.pingCallbacks.set(packet.data.id, _ => { + res(new Date().getTime() - startTime); + this.pingCallbacks.delete(packet.data.id); + }); + this.tcpSocket.write(packet.toBuffer()); + debug('ping sent...'); + }) + + } +} diff --git a/lib/STP/packets.js b/src/lib/STP/packets.js similarity index 93% rename from lib/STP/packets.js rename to src/lib/STP/packets.js index 36729c3..ce26390 100644 --- a/lib/STP/packets.js +++ b/src/lib/STP/packets.js @@ -1,115 +1,115 @@ -const md5 = require('md5'); - -// #region === [ private lib functions ] === - -class STPPacket { - cmd = 'NOOP'; - data = {}; - meta = {}; - - toBuffer() { - return Buffer.from(`\x02${JSON.stringify({ - cmd: this.cmd, - data: this.data, - meta: this.meta - })}\x03`); - } -} - -function basicPacket(commandName) { - return class extends STPPacket { - constructor() { - super(); - this.cmd = commandName; - } - } -} - -// #endregion - -// #region === [ exotic packet classes ] === - -class KeyExchangePacket extends STPPacket { - constructor(key, { - type = 'pkcs8-pem', - name = 'anonymous' - } = {}) { - super(); - this.cmd = 'KEY'; - this.data.key = key; - this.meta.name = name; - this.meta.type = type; - } -} - -class ClientsPacket extends STPPacket { - constructor(clients) { - super(); - this.cmd = 'NODES' - this.data.clients = clients; - } -} - -class PingPacket extends STPPacket { - constructor() { - super(); - this.cmd = 'PING'; - this.data.id = md5(Date()); - } -} - -class PongPacket extends STPPacket { - constructor(id) { - super(); - this.cmd = 'PONG'; - this.data.id = id; - } -} - -// #endregion - -// #region === [ ordinary packet classes ] === - -const AckPacket = basicPacket('ACK'); -const GetClientsPacket = basicPacket('QNODES'); - -// #endregion - -// #region === [ public lib functions ] === - -function reconstructPacket(packet) { - - if(packet.startsWith('\02')) - return reconstructPacket(packet.substr(1)); - if(packet.endsWith('\x03')) - return reconstructPacket(packet.substr(0, packet.length - 1)); - - const obj = JSON.parse(packet); - - switch(obj.cmd) { - case 'KEY': return new KeyExchangePacket(obj.data.key, obj.meta); - case 'NODES': return new ClientsPacket(obj.data.clients); - case 'QNODES': return new GetClientsPacket(); - case 'ACK': return new AckPacket(); - - case 'NOOP': return new STPPacket(); - default: throw new TypeError(`Unknown command ${obj.cmd}`); - } - -} - -// #endregion - -// #region === [ exports ] === - -module.exports.KeyExchangePacket = KeyExchangePacket; -module.exports.ClientsPacket = ClientsPacket; -module.exports.PingPacket = PingPacket; -module.exports.PongPacket = PongPacket; - -module.exports.AckPacket = AckPacket; -module.exports.GetClientsPacket = GetClientsPacket; - -module.exports.reconstructPacket = reconstructPacket; - +const md5 = require('md5'); + +// #region === [ private lib functions ] === + +class STPPacket { + cmd = 'NOOP'; + data = {}; + meta = {}; + + toBuffer() { + return Buffer.from(`\x02${JSON.stringify({ + cmd: this.cmd, + data: this.data, + meta: this.meta + })}\x03`); + } +} + +function basicPacket(commandName) { + return class extends STPPacket { + constructor() { + super(); + this.cmd = commandName; + } + } +} + +// #endregion + +// #region === [ exotic packet classes ] === + +class KeyExchangePacket extends STPPacket { + constructor(key, { + type = 'pkcs8-pem', + name = 'anonymous' + } = {}) { + super(); + this.cmd = 'KEY'; + this.data.key = key; + this.meta.name = name; + this.meta.type = type; + } +} + +class ClientsPacket extends STPPacket { + constructor(clients) { + super(); + this.cmd = 'NODES' + this.data.clients = clients; + } +} + +class PingPacket extends STPPacket { + constructor() { + super(); + this.cmd = 'PING'; + this.data.id = md5(Date()); + } +} + +class PongPacket extends STPPacket { + constructor(id) { + super(); + this.cmd = 'PONG'; + this.data.id = id; + } +} + +// #endregion + +// #region === [ ordinary packet classes ] === + +const AckPacket = basicPacket('ACK'); +const GetClientsPacket = basicPacket('QNODES'); + +// #endregion + +// #region === [ public lib functions ] === + +function reconstructPacket(packet) { + + if(packet.startsWith('\x02')) + return reconstructPacket(packet.substr(1)); + if(packet.endsWith('\x03')) + return reconstructPacket(packet.substr(0, packet.length - 1)); + + const obj = JSON.parse(packet); + + switch(obj.cmd) { + case 'KEY': return new KeyExchangePacket(obj.data.key, obj.meta); + case 'NODES': return new ClientsPacket(obj.data.clients); + case 'QNODES': return new GetClientsPacket(); + case 'ACK': return new AckPacket(); + + case 'NOOP': return new STPPacket(); + default: throw new TypeError(`Unknown command ${obj.cmd}`); + } + +} + +// #endregion + +// #region === [ exports ] === + +module.exports.KeyExchangePacket = KeyExchangePacket; +module.exports.ClientsPacket = ClientsPacket; +module.exports.PingPacket = PingPacket; +module.exports.PongPacket = PongPacket; + +module.exports.AckPacket = AckPacket; +module.exports.GetClientsPacket = GetClientsPacket; + +module.exports.reconstructPacket = reconstructPacket; + // #endregion \ No newline at end of file diff --git a/lib/appdata.js b/src/lib/appdata.js similarity index 100% rename from lib/appdata.js rename to src/lib/appdata.js diff --git a/lib/config.js b/src/lib/config.js similarity index 85% rename from lib/config.js rename to src/lib/config.js index 15d4133..6e3e46b 100644 --- a/lib/config.js +++ b/src/lib/config.js @@ -1,35 +1,34 @@ -const pkg = require('./../package.json'); -const { readFileSync, writeFileSync, existsSync } = require('fs'); -const { ensureDirSync } = require('fs-extra'); -const { config } = require('../package.json'); -const deepmerge = require('deepmerge'); - -const appdata = require('./appdata'); -ensureDirSync(`${appdata}/valnet/relay`); -const filepath = `${appdata}/valnet/relay/config.json`; - -const configObject = {}; - -module.exports.config = configObject; - -function loadObject(obj) { - for(const key in obj) { - configObject[key] = obj[key]; - } -} - -try { - if(!existsSync(filepath)) - writeFileSync(filepath, JSON.stringify({}, null, 2)); - - const json = readFileSync(filepath); - const data = JSON.parse(json); - - - loadObject(deepmerge(config, data, { - arrayMerge: (_, sourceArray, __) => sourceArray - })); - -} catch(e) { - +const { readFileSync, writeFileSync, existsSync } = require('fs'); +const { ensureDirSync } = require('fs-extra'); +const { config } = require('../../package.json'); +const deepmerge = require('deepmerge'); + +const appdata = require('./appdata'); +ensureDirSync(`${appdata}/valnet/relay`); +const filepath = `${appdata}/valnet/relay/config.json`; + +const configObject = {}; + +module.exports.config = configObject; + +function loadObject(obj) { + for(const key in obj) { + configObject[key] = obj[key]; + } +} + +try { + if(!existsSync(filepath)) + writeFileSync(filepath, JSON.stringify({}, null, 2)); + + const json = readFileSync(filepath); + const data = JSON.parse(json); + + + loadObject(deepmerge(config, data, { + arrayMerge: (_, sourceArray, __) => sourceArray + })); + +} catch(e) { + } \ No newline at end of file diff --git a/lib/node.js b/src/lib/node.js similarity index 92% rename from lib/node.js rename to src/lib/node.js index 6498200..bbfcaef 100644 --- a/lib/node.js +++ b/src/lib/node.js @@ -1,122 +1,128 @@ -const EventEmitter = require('events') -const stp = require('./STP'); -const upnp = require('./upnp'); -const md5 = require('md5'); -const pkg = require('./../package.json'); -const { config, write } = require('./config.js'); -const log = require('signale').scope('NODE'); -const bonjour = require('bonjour')(); -const Gateway = require('./Gateway'); - -class Node extends EventEmitter { - clients = []; - hash = null; - name = null; - readyPromise = null; - port = null; - identity; - multicastAd = null; - multicastBrowser = null; - connected = false; - multicastDevices = []; - upnpEnabled = false; - - constructor(identity) { - super(); - this.identity = identity; - this.hash = md5(identity.publicKey); - this.name = `valnet-node-${identity.name}`; - - this.readyPromise = this.negotiatePort() - .catch(this.serverStartupFailed.bind(this)) - .then(this.startServer.bind(this)) - .then(this.connectNetwork.bind(this)) - } - - async connectNetwork() { - const gateway = new Gateway(this.identity, config.endpoints); - } - - async serverStartupFailed(error) { - log.warn('port negotiation failed, using config port: ' + config.ports.relay); - log.warn('If This is meant to be a server, you\'ll'); - log.warn('need to manually forward the port.'); - log.warn('elsewise, this warning is safe to ignore.'); - } - - async startServer() { - log.info('creating Valnet Node on port ' + this.port + '...'); - - stp.createServer({ - identity: this.identity, - port: this.port - }, (connection) => { - log.info('incomming connection from ' + connection.remoteName); - }); - - log.info('advertising node on multicast...') - this.multicastAd = bonjour.publish({ - name: this.name, - type: 'stp', - port: this.port, - protocol: 'tcp' - }); - - this.multicastBrowser = bonjour.find({type: 'stp'}); - - this.multicastBrowser.on('up', this.serviceUp.bind(this)); - this.multicastBrowser.on('down', this.serviceDown.bind(this)); - - // log.success('Node successfully registered!'); - } - - async serviceUp(device) { - this.multicastDevices.push(device); - } - - async serviceDown(device) { - this.multicastDevices = this.multicastDevices.filter(testDevice => { - return testDevice.host !== device.host - || testDevice.port !== device.port - }) - } - - async negotiatePort() { - const mappings = await upnp.mappings(); - const matchingMappings = mappings.filter(mapping => { - return mapping.description === this.name - }); - const alreadyMapped = matchingMappings.length > 0; - const takenPorts = mappings.map(mapping => mapping.public.port); - - if(alreadyMapped) { - this.port = matchingMappings[0].public.port; - this.upnpEnabled = true; - log.success(`upnp port ${this.port} already registered!`); - return; - } - - for(let port = config.ports.relay; port <= config.ports.relayEnd; port ++) { - if(takenPorts.indexOf(port) === -1) { - await upnp.mapIndefinite(port, this.name); - this.port = port; - this.upnpEnabled = true; - log.success(`registered upnp port ${this.port}`); - return; - } - } - - // console.log(mappings, this.hash); - } - - static get Node() { - return Node; - } - - get ready() { - return this.readyPromise; - } -} - - +const EventEmitter = require('events') +const stp = require('./STP'); +const upnp = require('./upnp'); +const md5 = require('md5'); +const pkg = require('./../package.json'); +const { config, write } = require('./config.js'); +const log = require('signale').scope('NODE'); +const bonjour = require('bonjour')(); +const Gateway = require('./Gateway'); +const { Identity } = require('./Identity'); + +class Node extends EventEmitter { + clients = []; + hash = null; + name = null; + readyPromise = null; + port = null; + identity; + multicastAd = null; + multicastBrowser = null; + connected = false; + multicastDevices = []; + upnpEnabled = false; + + constructor() { + super(); + + this.readyPromise = this.setupIdentity() + .then(this.negotiatePort.bind(this)) + .catch(this.serverStartupFailed.bind(this)) + .then(this.startServer.bind(this)) + .then(this.connectNetwork.bind(this)) + } + + async setupIdentity() { + const identity = await new Identity('relay', 'default'); + this.identity = identity; + this.hash = md5(identity.publicKey); + this.name = `valnet-node-${identity.name}`; + } + + async connectNetwork() { + const gateway = new Gateway(this.identity, config.endpoints); + } + + async serverStartupFailed(error) { + log.warn('port negotiation failed, using config port: ' + config.ports.relay); + log.warn('If This is meant to be a server, you\'ll'); + log.warn('need to manually forward the port.'); + log.warn('elsewise, this warning is safe to ignore.'); + } + + async startServer() { + log.info('creating Valnet Node on port ' + this.port + '...'); + + stp.createServer({ + identity: this.identity, + port: this.port + }, (connection) => { + log.info('incomming connection from ' + connection.remoteName); + }); + + log.info('advertising node on multicast...') + this.multicastAd = bonjour.publish({ + name: this.name, + type: 'stp', + port: this.port, + protocol: 'tcp' + }); + + this.multicastBrowser = bonjour.find({type: 'stp'}); + + this.multicastBrowser.on('up', this.serviceUp.bind(this)); + this.multicastBrowser.on('down', this.serviceDown.bind(this)); + + // log.success('Node successfully registered!'); + } + + async serviceUp(device) { + this.multicastDevices.push(device); + } + + async serviceDown(device) { + this.multicastDevices = this.multicastDevices.filter(testDevice => { + return testDevice.host !== device.host + || testDevice.port !== device.port + }) + } + + async negotiatePort() { + const mappings = await upnp.mappings(); + const matchingMappings = mappings.filter(mapping => { + return mapping.description === this.name + }); + const alreadyMapped = matchingMappings.length > 0; + const takenPorts = mappings.map(mapping => mapping.public.port); + + if(alreadyMapped) { + this.port = matchingMappings[0].public.port; + this.upnpEnabled = true; + log.success(`upnp port ${this.port} already registered!`); + return; + } + + for(let port = config.ports.relay; port <= config.ports.relayEnd; port ++) { + if(takenPorts.indexOf(port) === -1) { + await upnp.mapIndefinite(port, this.name); + this.port = port; + this.upnpEnabled = true; + log.success(`registered upnp port ${this.port}`); + return; + } + } + + // console.log(mappings, this.hash); + } + + static get Node() { + return Node; + } + + get ready() { + return this.readyPromise; + } +} + + module.exports = Node; \ No newline at end of file diff --git a/lib/title.js b/src/lib/title.js similarity index 100% rename from lib/title.js rename to src/lib/title.js diff --git a/lib/upnp.js b/src/lib/upnp.js similarity index 95% rename from lib/upnp.js rename to src/lib/upnp.js index 918af49..463ad40 100644 --- a/lib/upnp.js +++ b/src/lib/upnp.js @@ -1,67 +1,67 @@ -const natUpnp = require('nat-upnp'); -const client = natUpnp.createClient(); - -module.exports.map = function(port, ttl = 10, name = 'upnp application') { - return new Promise((res, rej) => { - client.portMapping({ - private: port, - public: port, - ttl, - description: name - }, (err) => { - if(err) rej(err); - res(); - }); - }); -}; - -module.exports.mapIndefinite = function(port, name = 'upnp application') { - return new Promise((res, rej) => { - client.portMapping({ - private: port, - public: port, - ttl: 0, - description: name - }, (err) => { - if(err) rej(err); - res(); - }); - }); -}; - -module.exports.unmap = function(port) { - return new Promise((res, rej) => { - client.portUnmapping({ - private: port, - public: port - }, (err) => { - if(err) rej(err); - res(); - }); - }); -}; - -module.exports.mappings = function() { - return new Promise((res, rej) => { - client.getMappings((err, mappings) => { - if(err) rej(err); - res(mappings); - }); - }); -}; - - -/* -(async () => { - try { - log.debug('first upnp mapping attempt...'); - await client.map(this.port); - this.openServer(); - } catch (e) { - log.warn(`Could not open upnp port ${this.port}`); - log.warn('Check your router is configured to allow upnp.'); - log.warn('Valnet will continue to operate, but incomming') - log.warn('peer connections will not be possible.') - } -})(); +const natUpnp = require('nat-upnp'); +const client = natUpnp.createClient(); + +module.exports.map = function(port, ttl = 10, name = 'upnp application') { + return new Promise((res, rej) => { + client.portMapping({ + private: port, + public: port, + ttl, + description: name + }, (err) => { + if(err) rej(err); + res(); + }); + }); +}; + +module.exports.mapIndefinite = function(port, name = 'upnp application') { + return new Promise((res, rej) => { + client.portMapping({ + private: port, + public: port, + ttl: 0, + description: name + }, (err) => { + if(err) rej(err); + res(); + }); + }); +}; + +module.exports.unmap = function(port) { + return new Promise((res, rej) => { + client.portUnmapping({ + private: port, + public: port + }, (err) => { + if(err) rej(err); + res(); + }); + }); +}; + +module.exports.mappings = function() { + return new Promise((res, rej) => { + client.getMappings((err, mappings) => { + if(err) rej(err); + res(mappings); + }); + }); +}; + + +/* +(async () => { + try { + log.debug('first upnp mapping attempt...'); + await client.map(this.port); + this.openServer(); + } catch (e) { + log.warn(`Could not open upnp port ${this.port}`); + log.warn('Check your router is configured to allow upnp.'); + log.warn('Valnet will continue to operate, but incomming') + log.warn('peer connections will not be possible.') + } +})(); */ \ No newline at end of file diff --git a/src/main.dev.ts b/src/main.dev.ts index 4b226bc..4a17ea7 100644 --- a/src/main.dev.ts +++ b/src/main.dev.ts @@ -132,3 +132,7 @@ app.on('activate', () => { }); // === [ VALNET NODE ] === + +const { Node } = require('./lib/node'); + +const node = new Node(); diff --git a/yarn.lock b/yarn.lock index 3c73156..3913bf9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2426,7 +2426,7 @@ asn1.js@^5.2.0: minimalistic-assert "^1.0.0" safer-buffer "^2.1.0" -asn1@~0.2.3: +asn1@^0.2.4, asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== @@ -2496,7 +2496,7 @@ async@0.9.x: resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= -async@^2.6.2: +async@^2.1.5, async@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== @@ -3128,7 +3128,7 @@ chalk@^1.1.1: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -3158,6 +3158,11 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +charenc@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + cheerio@^1.0.0-rc.3: version "1.0.0-rc.3" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6" @@ -3677,6 +3682,11 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +crypt@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -4807,13 +4817,6 @@ eslint-config-erb@^2.0.0: dependencies: babel-eslint "^10.1.0" -eslint-config-prettier@^6.11.0: - version "6.15.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz#7f93f6cb7d45a92f1537a70ecc06366e1ac6fed9" - integrity sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw== - dependencies: - get-stdin "^6.0.0" - eslint-import-resolver-node@^0.3.4: version "0.3.4" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" @@ -4903,13 +4906,6 @@ eslint-plugin-jsx-a11y@6.4.1: jsx-ast-utils "^3.1.0" language-tags "^1.0.5" -eslint-plugin-prettier@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz#168ab43154e2ea57db992a2cd097c828171f75c2" - integrity sha512-jZDa8z76klRqo+TdGDTFJSavwbnWK2ZpqGKNZ+VvweMW516pDUMmQ2koXvxEE4JhzNvTv+radye/bWGBmA6jmg== - dependencies: - prettier-linter-helpers "^1.0.0" - eslint-plugin-promise@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" @@ -5236,11 +5232,6 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-diff@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" - integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== - fast-glob@^3.1.1: version "3.2.4" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" @@ -5298,6 +5289,13 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= + dependencies: + escape-string-regexp "^1.0.5" + figures@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" @@ -5482,6 +5480,15 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= +fs-extra@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" + integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -5612,11 +5619,6 @@ get-stdin@^4.0.1: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= -get-stdin@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" - integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== - get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -6119,6 +6121,13 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= +human-readable-ids@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/human-readable-ids/-/human-readable-ids-1.0.4.tgz#41b3a2a6966636e104e41e0673b984b36dfde202" + integrity sha512-h1zwThTims8A/SpqFGWyTx+jG1+WRMJaEeZgbtPGrIpj2AZjsOgy8Y+iNzJ0yAyN669Q6F02EK66WMWcst+2FA== + dependencies: + knuth-shuffle "^1.0.0" + human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -6317,7 +6326,7 @@ ip-regex@^2.1.0: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= -ip@^1.1.0, ip@^1.1.5: +ip@^1.1.0, ip@^1.1.4, ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= @@ -6378,7 +6387,7 @@ is-boolean-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== -is-buffer@^1.1.5: +is-buffer@^1.1.5, is-buffer@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -7327,6 +7336,15 @@ keyboardevents-areequal@^0.2.1: resolved "https://registry.yarnpkg.com/keyboardevents-areequal/-/keyboardevents-areequal-0.2.2.tgz#88191ec738ce9f7591c25e9056de928b40277194" integrity sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw== +keyv-file@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/keyv-file/-/keyv-file-0.2.0.tgz#3442b07a00c1d7bd0242f4a91bcf498afbd6ea6a" + integrity sha512-zUQ11eZRmilEUpV1gJSj8mBAHjyXpleQo1iCS0khb+GFRhiPfwavWgn4eDUKNlOyMZzmExnISl8HE1hNbim0gw== + dependencies: + debug "^4.1.1" + fs-extra "^4.0.1" + tslib "^1.9.3" + keyv@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" @@ -7334,7 +7352,7 @@ keyv@^3.0.0: dependencies: json-buffer "3.0.0" -keyv@^4.0.0: +keyv@^4.0.0, keyv@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== @@ -7380,6 +7398,11 @@ klona@^2.0.4: resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== +knuth-shuffle@^1.0.0: + version "1.0.8" + resolved "https://registry.yarnpkg.com/knuth-shuffle/-/knuth-shuffle-1.0.8.tgz#929a467b0efd8d297bdcf318ca988a9f1037f80d" + integrity sha512-IdC4Hpp+mx53zTt6VAGsAtbGM0g4BV9fP8tTcviCosSwocHcRDw9uG5Rnv6wLWckF4r72qeXFoK9NkvV1gUJCQ== + language-subtag-registry@~0.3.2: version "0.3.21" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a" @@ -7501,6 +7524,16 @@ load-json-file@^2.0.0: pify "^2.0.0" strip-bom "^3.0.0" +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + loader-runner@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.1.0.tgz#f70bc0c29edbabdf2043e7ee73ccc3fe1c96b42d" @@ -7715,6 +7748,15 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +md5@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" + integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== + dependencies: + charenc "0.0.2" + crypt "0.0.2" + is-buffer "~1.1.6" + mdn-browser-compat-data@^1.0.28: version "1.1.2" resolved "https://registry.yarnpkg.com/mdn-browser-compat-data/-/mdn-browser-compat-data-1.1.2.tgz#90d2a25ce731b34a14329396887dadfd657ea7b2" @@ -8050,6 +8092,16 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +nat-upnp@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/nat-upnp/-/nat-upnp-1.1.1.tgz#b18365e4faf44652549bb593c69e6b690df22043" + integrity sha512-b1Q+sf9fHGCXhlWErNgTTEto8A02MnNysw3vx3kD1657+/Ae23vPEAB6QBh+9RqLL4+xw/LmjVTiLy6A7Cx0xw== + dependencies: + async "^2.1.5" + ip "^1.1.4" + request "^2.79.0" + xml2js "~0.1.14" + native-url@^0.2.6: version "0.2.6" resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.2.6.tgz#ca1258f5ace169c716ff44eccbddb674e10399ae" @@ -8202,6 +8254,13 @@ node-releases@^1.1.66: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.67.tgz#28ebfcccd0baa6aad8e8d4d8fe4cbc49ae239c12" integrity sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg== +node-rsa@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/node-rsa/-/node-rsa-1.1.1.tgz#efd9ad382097782f506153398496f79e4464434d" + integrity sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw== + dependencies: + asn1 "^0.2.4" + node-sass@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-5.0.0.tgz#4e8f39fbef3bac8d2dc72ebe3b539711883a78d2" @@ -8871,6 +8930,14 @@ pirates@^4.0.0, pirates@^4.0.1: dependencies: node-modules-regexp "^1.0.0" +pkg-conf@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-2.1.0.tgz#2126514ca6f2abfebd168596df18ba57867f0058" + integrity sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg= + dependencies: + find-up "^2.0.0" + load-json-file "^4.0.0" + pkg-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" @@ -9251,14 +9318,7 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== - dependencies: - fast-diff "^1.1.2" - -prettier@*, prettier@^2.0.5: +prettier@*: version "2.0.5" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== @@ -9826,7 +9886,7 @@ request-promise-native@^1.0.8: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.88.0, request@^2.88.2: +request@^2.79.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -10097,7 +10157,7 @@ sass-loader@^10.1.0: schema-utils "^3.0.0" semver "^7.3.2" -sax@^1.2.4, sax@~1.2.4: +sax@>=0.1.1, sax@^1.2.4, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -10357,6 +10417,15 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== +signale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/signale/-/signale-1.4.0.tgz#c4be58302fb0262ac00fc3d886a7c113759042f1" + integrity sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w== + dependencies: + chalk "^2.3.2" + figures "^2.0.0" + pkg-conf "^2.1.0" + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -11201,7 +11270,7 @@ tsconfig-paths@^3.9.0: minimist "^1.2.0" strip-bom "^3.0.0" -tslib@^1.8.1, tslib@^1.9.0: +tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -11961,6 +12030,13 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +xml2js@~0.1.14: + version "0.1.14" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.1.14.tgz#5274e67f5a64c5f92974cd85139e0332adc6b90c" + integrity sha1-UnTmf1pkxfkpdM2FE54DMq3GuQw= + dependencies: + sax ">=0.1.1" + xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"