From 4f4e98b3631a87e734e56c2a1e1846bdb4c3a3f6 Mon Sep 17 00:00:00 2001 From: Nick - Ngoc Pham Date: Fri, 20 Dec 2024 10:43:49 +0700 Subject: [PATCH 01/21] feat: remove old file --- Dockerfile | 15 -- bin/client | 43 ------ bin/server | 28 ---- client.js | 69 --------- examples/api.js | 9 -- lib/api.js | 22 --- nginx.conf.sample | 33 ----- package-lock.json | 353 ---------------------------------------------- package.json | 49 +++---- server.js | 200 -------------------------- 10 files changed, 23 insertions(+), 798 deletions(-) delete mode 100644 Dockerfile delete mode 100644 bin/client delete mode 100644 bin/server delete mode 100644 client.js delete mode 100644 examples/api.js delete mode 100644 lib/api.js delete mode 100644 nginx.conf.sample delete mode 100644 package-lock.json delete mode 100644 server.js diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 58ec5ae..0000000 --- a/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM node:8 - -# Create app directory -RUN mkdir -p /usr/src/app -WORKDIR /usr/src/app - -# Install app dependencies -COPY package.json /usr/src/app/ -RUN npm install - -# Bundle app source -COPY . /usr/src/app - -EXPOSE 3000 -CMD [ "node", "bin/server" ] diff --git a/bin/client b/bin/client deleted file mode 100644 index 005fa74..0000000 --- a/bin/client +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -const optimist = require('optimist'); - -let argv = optimist - .usage('Usage: $0 --server [string] --subdomain [string] --hostname [string] --port [number]') - .options('se', { - alias: 'server', - default: 'https://server.nport.link', - describe: 'Tunnel server endpoint' - }) - .options('s', { - alias: 'subdomain', - describe: '(Required) Public URL the tunnel server is forwarding to us' - }) - .options('h', { - alias: 'hostname', - default: '127.0.0.1', - describe: 'Address of local server for forwarding over socket-tunnel' - }) - .options('p', { - alias: 'port', - describe: '(Required) Port of local server for forwarding over socket-tunnel' - }) - .argv; - -if (argv.help) { - optimist.showHelp(); - process.exit(); -} - -if (!argv['server'] || !argv['subdomain'] || !argv['port']) { - for (var key in ['server', 'subdomain', 'port']) { - if (argv[key]) continue; - - console.log('Error: Required option, but nothing found'); - - optimist.showHelp(); - - process.exit(); - } -} - -require('../client.js')(argv); diff --git a/bin/server b/bin/server deleted file mode 100644 index 5b793c5..0000000 --- a/bin/server +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env node -var optimist = require('optimist'); - -var argv = optimist - .usage('Usage: $0 --hostname [string] --port [number] --subdomain [string]') - .options('h', { - alias: 'hostname', - default: '0.0.0.0', - describe: 'Accept connections on this hostname' - }) - .options('p', { - alias: 'port', - default: 3000, - describe: 'Server daemon port' - }) - .options('s', { - alias: 'subdomain', - default: '', - describe: 'Name of subdomain used. Required when server listens on a subdomain (leave blank otherwise)' - }) - .argv; - -if (argv.help) { - optimist.showHelp(); - process.exit(); -} - -require('../server.js')(argv); diff --git a/client.js b/client.js deleted file mode 100644 index 5fe2119..0000000 --- a/client.js +++ /dev/null @@ -1,69 +0,0 @@ -'use strict'; - -const IDLE_SOCKET_TIMEOUT_MILLISECONDS = 1000 * 30; - -module.exports = (options) => { - return new Promise((resolve, reject) => { - // require the things we need - const net = require('net'); - const ss = require('socket.io-stream'); - let socket = require('socket.io-client')(options['server']); - - socket.on('connect', () => { - console.log(new Date() + ': connected'); - console.log(new Date() + ': requesting subdomain ' + options['subdomain'] + ' via ' + options['server']); - - socket.emit('createTunnel', options['subdomain'], (err) => { - if (err) { - console.log(new Date() + ': [error] ' + err); - - reject(err); - } else { - console.log(new Date() + ': registered with server successfully'); - console.log(new Date() + ': your domain is: https://' + options['subdomain'] + '.nport.link'); - - // clean and concat requested url - let url; - let subdomain = options['subdomain'].toString(); - let server = options['server'].toString(); - - if (server.includes('https://')) { - url = `https://${subdomain}.${server.slice(8)}`; - } else if (server.includes('http://')) { - url = `http://${subdomain}.${server.slice(7)}`; - } else { - url = `https://${subdomain}.${server}`; - } - - // resolve promise with requested URL - resolve(url); - } - }); - }); - - socket.on('incomingClient', (clientId) => { - let client = net.connect(options['port'], options['hostname'], () => { - let s = ss.createStream(); - s.pipe(client).pipe(s); - - s.on('end', () => { - client.destroy(); - }); - - ss(socket).emit(clientId, s); - }); - - client.setTimeout(IDLE_SOCKET_TIMEOUT_MILLISECONDS); - client.on('timeout', () => { - client.end(); - }); - - client.on('error', () => { - // handle connection refusal (create a stream and immediately close it) - let s = ss.createStream(); - ss(socket).emit(clientId, s); - s.end(); - }); - }); - }); -}; diff --git a/examples/api.js b/examples/api.js deleted file mode 100644 index 2c80816..0000000 --- a/examples/api.js +++ /dev/null @@ -1,9 +0,0 @@ -const socketTunnel = require('../lib/api'); - -socketTunnel.connect('https://domain.example', 'deviceSubdomain', '2222') - .then((url) => { - console.log(url); - }) - .catch((err) => { - console.error(err); - }); diff --git a/lib/api.js b/lib/api.js deleted file mode 100644 index 470d8b4..0000000 --- a/lib/api.js +++ /dev/null @@ -1,22 +0,0 @@ -// client api -const client = require('../client'); - -let api = { - connect: (server, subdomain, port, hostname = '127.0.0.1') => { - if (!server || !subdomain || !port || !hostname) { - return Promise.reject(new Error('One or more options were not provided')); - } - - let options = { - server: server, - subdomain: subdomain.toString(), - port: port.toString(), - hostname: hostname - }; - - // client returns a promise - return client(options); - } -}; - -module.exports = api; diff --git a/nginx.conf.sample b/nginx.conf.sample deleted file mode 100644 index 68803c4..0000000 --- a/nginx.conf.sample +++ /dev/null @@ -1,33 +0,0 @@ -# Example of nginx config for proxying requests to socket-tunnel server - -server { - listen *:80; - server_name subdomain.example.com *.subdomain.example.com; - rewrite ^ https://$host$request_uri? permanent; -} - -server { - listen *:443; - - server_name subdomain.example.com *.subdomain.example.com; - - ssl on; - ssl_certificate /path/to/certificate/file.crt; - ssl_certificate_key /path/to/key/file.key; - ssl_prefer_server_ciphers on; - - location / { - proxy_pass http://127.0.0.1:3000/; - - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_http_version 1.1; - proxy_set_header X-Forwarded-Proto https; - proxy_set_header X-NginX-Proxy true; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - - proxy_redirect off; - } -} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 3535890..0000000 --- a/package-lock.json +++ /dev/null @@ -1,353 +0,0 @@ -{ - "name": "socket-tunnel", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "requires": { - "mime-types": "2.1.18", - "negotiator": "0.6.1" - } - }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" - }, - "arraybuffer.slice": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" - }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" - }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" - }, - "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" - }, - "base64id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", - "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" - }, - "better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", - "requires": { - "callsite": "1.0.0" - } - }, - "blob": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", - "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=" - }, - "callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" - }, - "component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" - }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" - }, - "component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "engine.io": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.0.tgz", - "integrity": "sha512-mRbgmAtQ4GAlKwuPnnAvXXwdPhEx+jkc0OBCLrXuD/CRvwNK3AxRSnqK4FSqmAMRRHryVJP8TopOvmEaA64fKw==", - "requires": { - "accepts": "1.3.5", - "base64id": "1.0.0", - "cookie": "0.3.1", - "debug": "3.1.0", - "engine.io-parser": "2.1.2", - "ws": "3.3.3" - } - }, - "engine.io-client": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", - "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", - "requires": { - "component-emitter": "1.2.1", - "component-inherit": "0.0.3", - "debug": "3.1.0", - "engine.io-parser": "2.1.2", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "ws": "3.3.3", - "xmlhttprequest-ssl": "1.5.5", - "yeast": "0.1.2" - } - }, - "engine.io-parser": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.2.tgz", - "integrity": "sha512-dInLFzr80RijZ1rGpx1+56/uFoH7/7InhH3kZt+Ms6hT8tNx3NGW/WNSA/f8As1WkOfkuyb3tnRyuXGxusclMw==", - "requires": { - "after": "0.8.2", - "arraybuffer.slice": "0.0.7", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.4", - "has-binary2": "1.0.2" - } - }, - "has-binary2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.2.tgz", - "integrity": "sha1-6D26SfC5vk0CbSc2U1DZ8D9Uvpg=", - "requires": { - "isarray": "2.0.1" - } - }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" - }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" - }, - "is-valid-domain": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/is-valid-domain/-/is-valid-domain-0.0.5.tgz", - "integrity": "sha1-SOcDGfy0MAkjbpazf5hDiJzntRM=" - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" - }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "requires": { - "mime-db": "1.33.0" - } - }, - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" - }, - "object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "requires": { - "minimist": "0.0.10", - "wordwrap": "0.0.3" - } - }, - "parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", - "requires": { - "better-assert": "1.0.2" - } - }, - "parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", - "requires": { - "better-assert": "1.0.2" - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - }, - "socket.io": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.0.tgz", - "integrity": "sha512-KS+3CNWWNtLbVN5j0/B+1hjxRzey+oTK6ejpAOoxMZis6aXeB8cUtfuvjHl97tuZx+t/qD/VyqFMjuzu2Js6uQ==", - "requires": { - "debug": "3.1.0", - "engine.io": "3.2.0", - "has-binary2": "1.0.2", - "socket.io-adapter": "1.1.1", - "socket.io-client": "2.1.0", - "socket.io-parser": "3.2.0" - } - }, - "socket.io-adapter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", - "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=" - }, - "socket.io-client": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.0.tgz", - "integrity": "sha512-TvKPpL0cBON5LduQfR8Rxrr+ktj70bLXGvqHCL3er5avBXruB3gpnbaud5ikFYVfANH1gCABAvo0qN8Axpg2ew==", - "requires": { - "backo2": "1.0.2", - "base64-arraybuffer": "0.1.5", - "component-bind": "1.0.0", - "component-emitter": "1.2.1", - "debug": "3.1.0", - "engine.io-client": "3.2.1", - "has-binary2": "1.0.2", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "object-component": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "socket.io-parser": "3.2.0", - "to-array": "0.1.4" - } - }, - "socket.io-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", - "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", - "requires": { - "component-emitter": "1.2.1", - "debug": "3.1.0", - "isarray": "2.0.1" - } - }, - "socket.io-stream": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/socket.io-stream/-/socket.io-stream-0.9.1.tgz", - "integrity": "sha1-QhJYMWKIuDrGk7DUPv0J1tQ6upc=", - "requires": { - "component-bind": "1.0.0", - "debug": "2.2.0" - }, - "dependencies": { - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "requires": { - "ms": "0.7.1" - } - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" - } - } - }, - "tldjs": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tldjs/-/tldjs-2.3.1.tgz", - "integrity": "sha512-W/YVH/QczLUxVjnQhFC61Iq232NWu3TqDdO0S/MtXVz4xybejBov4ud+CIwN9aYqjOecEqIy0PscGkwpG9ZyTw==", - "requires": { - "punycode": "1.4.1" - } - }, - "to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" - }, - "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" - }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" - }, - "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "requires": { - "async-limiter": "1.0.0", - "safe-buffer": "5.1.1", - "ultron": "1.1.1" - } - }, - "xmlhttprequest-ssl": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", - "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" - }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" - } - } -} diff --git a/package.json b/package.json index 2ffcee8..1a82184 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,24 @@ "name": "nport", "version": "1.0.6", "description": "Tunnel HTTP Connections via socket.io streams.", - "keywords": ["tunnel", "socket.io", "http", "streaming", "networking", "proxy"], + "keywords": [ + "nport", + "tunnel", + "socket.io", + "http", + "streaming", + "networking", + "proxy", + "ngrok alternative", + "localhost tunnel", + "local tunnel", + "HTTP tunnel", + "HTTPS tunnel", + "URL forwarding", + "web development", + "free ngrok alternative", + "open source ngrok alternative" + ], "homepage": "https://nport.link", "bugs": { "url": "https://github.com/tuanngocptn/nport/issues", @@ -17,36 +34,16 @@ "name": "Nick Pham", "email": "tuanngocptn@gmail.com" }, - "contributors": [ - { - "name": "Eric Barch", - "email": "ebarch@nooplabs.com" - } - ], - "dependencies": { - "is-valid-domain": "0.0.5", - "optimist": "^0.6.1", - "socket.io": "^2.1.0", - "socket.io-client": "^2.1.0", - "socket.io-stream": "^0.9.1", - "tldjs": "^2.3.1", - "uuid": "^3.2.1" - }, + "contributors": [], + "dependencies": {}, "bin": { "nport": "./bin/client" }, "engines": { - "node": ">=14.0.0", - "npm": ">=6.0.0" + "node": ">=18.0.0", + "npm": ">=10.0.0" }, - "files": [ - "bin/", - "client.js", - "server.js", - "lib/", - "examples/", - "nginx.conf.sample" - ], + "files": [], "funding": { "type": "individual", "url": "https://github.com/sponsors/tuanngocptn" diff --git a/server.js b/server.js deleted file mode 100644 index be006af..0000000 --- a/server.js +++ /dev/null @@ -1,200 +0,0 @@ -'use strict'; - -module.exports = (options) => { - // libs - const http = require('http'); - const tldjs = require('tldjs'); - const ss = require('socket.io-stream'); - const uuid = require('uuid/v4'); - const isValidDomain = require('is-valid-domain'); - - // association between subdomains and socket.io sockets - let socketsBySubdomain = {}; - - // bounce incoming http requests to socket.io - let server = http.createServer(async (req, res) => { - getTunnelClientStreamForReq(req).then((tunnelClientStream) => { - const reqBodyChunks = []; - - req.on('error', (err) => { - console.error(err.stack); - }); - - // collect body chunks - req.on('data', (bodyChunk) => { - reqBodyChunks.push(bodyChunk); - }); - - // proxy finalized request to tunnel stream - req.on('end', () => { - // make sure the client didn't die on us - if (req.complete) { - const reqLine = getReqLineFromReq(req); - const headers = getHeadersFromReq(req); - - let reqBody = null; - if (reqBodyChunks.length > 0) { - reqBody = Buffer.concat(reqBodyChunks); - } - - streamResponse(reqLine, headers, reqBody, tunnelClientStream); - } - }); - }).catch((subdomainErr) => { - res.statusCode = 502; - return res.end(subdomainErr.message); - }); - }); - - // HTTP upgrades (i.e. websockets) are NOT currently supported because socket.io relies on them - // server.on('upgrade', (req, socket, head) => { - // getTunnelClientStreamForReq(req).then((tunnelClientStream) => { - // tunnelClientStream.on('error', () => { - // req.destroy(); - // socket.destroy(); - // tunnelClientStream.destroy(); - // }); - - // // get the upgrade request and send it to the tunnel client - // let messageParts = getHeaderPartsForReq(req); - - // messageParts.push(''); // Push delimiter - - // let message = messageParts.join('\r\n'); - // tunnelClientStream.write(message); - - // // pipe data between ingress socket and tunnel client - // tunnelClientStream.pipe(socket).pipe(tunnelClientStream); - // }).catch((subdomainErr) => { - // // if we get an invalid subdomain, this socket is most likely being handled by the root socket.io server - // if (!subdomainErr.message.includes('Invalid subdomain')) { - // socket.end(); - // } - // }); - // }); - - function getTunnelClientStreamForReq (req) { - return new Promise((resolve, reject) => { - // without a hostname, we won't know who the request is for - let hostname = req.headers.host; - if (!hostname) { - return reject(new Error('Invalid hostname')); - } - - // make sure we received a subdomain - let subdomain = tldjs.getSubdomain(hostname).toLowerCase(); - if (!subdomain) { - return reject(new Error('Invalid subdomain: Please visit https://github.com/tuanngocptn/nport for more information')); - } - - // tldjs library return subdomain as all subdomain path from the main domain. - // Example: - // 1. super.example.com = super - // 2. my.super.example.com = my.super - // 3. If we are running the tunnel server on a subdomain, we must strip it from the provided hostname - if (options.subdomain) { - subdomain = subdomain.replace(`.${options.subdomain}`, ''); - } - - let subdomainSocket = socketsBySubdomain[subdomain]; - if (!subdomainSocket) { - return reject(new Error(`${subdomain} is currently unregistered or offline.`)); - } - - if (req.connection.tunnelClientStream !== undefined && !req.connection.tunnelClientStream.destroyed && req.connection.subdomain === subdomain) { - return resolve(req.connection.tunnelClientStream); - } - - let requestGUID = uuid(); - ss(subdomainSocket).once(requestGUID, (tunnelClientStream) => { - req.connection.subdomain = subdomain; - req.connection.tunnelClientStream = tunnelClientStream; - - // Pipe all data from tunnel stream to requesting connection - tunnelClientStream.pipe(req.connection); - - resolve(tunnelClientStream); - }); - - subdomainSocket.emit('incomingClient', requestGUID); - }); - } - - function getReqLineFromReq (req) { - return `${req.method} ${req.url} HTTP/${req.httpVersion}`; - } - - function getHeadersFromReq (req) { - const headers = []; - - for (let i = 0; i < (req.rawHeaders.length - 1); i += 2) { - headers.push(req.rawHeaders[i] + ': ' + req.rawHeaders[i + 1]); - } - - return headers; - } - - function streamResponse (reqLine, headers, reqBody, tunnelClientStream) { - tunnelClientStream.write(reqLine); - tunnelClientStream.write('\r\n'); - tunnelClientStream.write(headers.join('\r\n')); - tunnelClientStream.write('\r\n\r\n'); - if (reqBody) { - tunnelClientStream.write(reqBody); - } - } - - // socket.io instance - let io = require('socket.io')(server); - io.on('connection', (socket) => { - socket.on('createTunnel', (requestedName, responseCb) => { - if (socket.requestedName) { - // tunnel has already been created - return; - } - - // domains are case insensitive - let reqNameNormalized = requestedName.toString().toLowerCase().replace(/[^0-9a-z-.]/g, ''); - - // make sure the client is requesting a valid subdomain - if (reqNameNormalized.length === 0 || !isValidDomain(`${reqNameNormalized}.example.com`)) { - console.log(new Date() + ': ' + reqNameNormalized + ' -- bad subdomain. disconnecting client.'); - if (responseCb) { - responseCb('bad subdomain'); - } - return socket.disconnect(); - } - - // make sure someone else hasn't claimed this subdomain - if (socketsBySubdomain[reqNameNormalized]) { - console.log(new Date() + ': ' + reqNameNormalized + ' requested but already claimed. disconnecting client.'); - if (responseCb) { - responseCb('subdomain already claimed'); - } - return socket.disconnect(); - } - - // store a reference to this socket by the subdomain claimed - socketsBySubdomain[reqNameNormalized] = socket; - socket.requestedName = reqNameNormalized; - console.log(new Date() + ': ' + reqNameNormalized + ' registered successfully'); - - if (responseCb) { - responseCb(null); - } - }); - - // when a client disconnects, we need to remove their association - socket.on('disconnect', () => { - if (socket.requestedName) { - delete socketsBySubdomain[socket.requestedName]; - console.log(new Date() + ': ' + socket.requestedName + ' unregistered'); - } - }); - }); - - // http server - server.listen(options.port, options.hostname); - - console.log(`${new Date()}: socket-tunnel server started on ${options.hostname}:${options.port}`); -}; From 1b98029833630f4cad8c009fa017fbd2eb7cc58b Mon Sep 17 00:00:00 2001 From: Nick - Ngoc Pham Date: Fri, 20 Dec 2024 16:45:18 +0700 Subject: [PATCH 02/21] feat: init client and server --- .env | 1 + babel.config.json | 22 + dist/client.js | 18 + dist/server.js | 13 + dist/services/client.service.js | 16 + dist/services/server.service.js | 23 + package-lock.json | 3177 +++++++++++++++++++++++++++++++ package.json | 31 +- src/client.mjs | 24 + src/server.mjs | 18 + src/services/client.service.mjs | 11 + src/services/server.service.mjs | 23 + 12 files changed, 3372 insertions(+), 5 deletions(-) create mode 100644 .env create mode 100644 babel.config.json create mode 100644 dist/client.js create mode 100644 dist/server.js create mode 100644 dist/services/client.service.js create mode 100644 dist/services/server.service.js create mode 100644 package-lock.json create mode 100644 src/client.mjs create mode 100644 src/server.mjs create mode 100644 src/services/client.service.mjs create mode 100644 src/services/server.service.mjs diff --git a/.env b/.env new file mode 100644 index 0000000..d7cec1a --- /dev/null +++ b/.env @@ -0,0 +1 @@ +IDLE_SOCKET_TIMEOUT_MILLISECONDS=3000 diff --git a/babel.config.json b/babel.config.json new file mode 100644 index 0000000..f526f69 --- /dev/null +++ b/babel.config.json @@ -0,0 +1,22 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "node": "12" + } + } + ] + ], + "plugins": [ + [ + "replace-import-extension", + { + "extMapping": { + ".mjs": ".js" + } + } + ] + ] +} \ No newline at end of file diff --git a/dist/client.js b/dist/client.js new file mode 100644 index 0000000..074b332 --- /dev/null +++ b/dist/client.js @@ -0,0 +1,18 @@ +"use strict"; + +var _yargs = _interopRequireDefault(require("yargs")); +var _helpers = require("yargs/helpers"); +var _clientService = _interopRequireDefault(require("./services/client.service.js")); +function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } +(0, _yargs.default)((0, _helpers.hideBin)(process.argv)).command("* [port] [subdomain]", "Start the client", yargs => { + yargs.positional("port", { + type: "number", + default: 3000, + describe: "The port to start the server on" + }); + yargs.positional("subdomain", { + type: "string", + default: "myapp", + describe: "The subdomain to use" + }); +}, _clientService.default).parse(); \ No newline at end of file diff --git a/dist/server.js b/dist/server.js new file mode 100644 index 0000000..2e7c30b --- /dev/null +++ b/dist/server.js @@ -0,0 +1,13 @@ +"use strict"; + +var _yargs = _interopRequireDefault(require("yargs")); +var _helpers = require("yargs/helpers"); +var _serverService = _interopRequireDefault(require("./services/server.service.js")); +function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } +(0, _yargs.default)((0, _helpers.hideBin)(process.argv)).command("start [port]", "Start the server", yargs => { + yargs.positional("port", { + type: "number", + default: 3000, + describe: "The port to start the server on" + }); +}, _serverService.default).parse(); \ No newline at end of file diff --git a/dist/services/client.service.js b/dist/services/client.service.js new file mode 100644 index 0000000..de3bb36 --- /dev/null +++ b/dist/services/client.service.js @@ -0,0 +1,16 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; +var _socket = _interopRequireDefault(require("socket.io-client")); +function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } +const client = options => { + const socket = (0, _socket.default)("http://127.0.0.1:3000"); + socket.on("connect", () => { + console.log("Connected to server"); + }); + console.log("🚀 ~ options:", options); +}; +var _default = exports.default = client; \ No newline at end of file diff --git a/dist/services/server.service.js b/dist/services/server.service.js new file mode 100644 index 0000000..fe5a5be --- /dev/null +++ b/dist/services/server.service.js @@ -0,0 +1,23 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; +var _http = require("http"); +var _socket = require("socket.io"); +const server = options => { + const server = (0, _http.createServer)((req, res) => { + res.writeHead(200, { + "Content-Type": "text/plain" + }); + res.end("Hello World\n"); + }); + const io = new _socket.Server(server); + io.on("connection", socket => { + console.log("a user connected"); + }); + server.listen(options.port, "127.0.0.1", () => {}); + console.log(`${new Date()}: nport server started on 127.0.0.1:${options.port}`); +}; +var _default = exports.default = server; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0a8f0ab --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3177 @@ +{ + "name": "nport", + "version": "1.0.6", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nport", + "version": "1.0.6", + "license": "MIT", + "dependencies": { + "socket.io": "^4.8.1", + "socket.io-client": "^4.8.1", + "socket.io-stream": "^0.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "nport": "usr/bin/env node ./dist/client.js" + }, + "devDependencies": { + "@babel/cli": "^7.26.4", + "@babel/core": "^7.26.0", + "@babel/preset-env": "^7.26.0", + "babel-plugin-replace-import-extension": "^1.1.4", + "nodemon": "^3.1.9" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/tuanngocptn" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/cli": { + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.26.4.tgz", + "integrity": "sha512-+mORf3ezU3p3qr+82WvJSnQNE1GAYeoCfEv4fik6B5/2cvKZ75AX8oawWQdXtM9MmndooQj15Jr9kelRFWsuRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "commander": "^6.2.0", + "convert-source-map": "^2.0.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.2.0", + "make-dir": "^2.1.0", + "slash": "^2.0.0" + }, + "bin": { + "babel": "bin/babel.js", + "babel-external-helpers": "bin/babel-external-helpers.js" + }, + "engines": { + "node": ">=6.9.0" + }, + "optionalDependencies": { + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", + "chokidar": "^3.6.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", + "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", + "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", + "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz", + "integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", + "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", + "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", + "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", + "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz", + "integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", + "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", + "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", + "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz", + "integrity": "sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", + "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", + "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", + "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", + "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz", + "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.25.9", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.25.9", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.25.9", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.25.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.25.9", + "@babel/plugin-transform-typeof-symbol": "^7.25.9", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.6", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.38.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz", + "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.3", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", + "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-replace-import-extension": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/babel-plugin-replace-import-extension/-/babel-plugin-replace-import-extension-1.1.4.tgz", + "integrity": "sha512-UJi5YV31JxoaHHxhytRhQqOQN3+ZsaoXVia7e58yc35pe4tNjNr7BdznnDvIPbEG/4u5kf+lLDk9peaxsOPxGw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/core-js-compat": { + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz", + "integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debuglog": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-0.0.2.tgz", + "integrity": "sha512-R6Bl6JxzWVrquXohdoHkyGMgaL8esUXhSzB6WUfSX1N+1dFg30Yo9gshTu7ExV6ntKN4VoJFw8zVdPsVVYGmKg==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.75", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.75.tgz", + "integrity": "sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/engine.io": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz", + "integrity": "sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz", + "integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemon": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/readable-stream": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.9.tgz", + "integrity": "sha512-Xz7W+ck1vQnTauU5Uo2zYK/Ae8Ou5mRJrKkGFDZX+VF921HwsjL6lz3ga+vT8jQa6HT/0ZbamKffh9OE7xJ4vQ==", + "dependencies": { + "core-util-is": "~1.0.0" + }, + "optionalDependencies": { + "debuglog": "0.0.2" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-stream": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/socket.io-stream/-/socket.io-stream-0.5.3.tgz", + "integrity": "sha512-zMblUX2/PYWaxYVHZ5uh5tERR9nfUCx6GKX6BUbkX5v/q1xApGLU9Lnh2oZ6lwtaS7rchLfcum/QRumjbt2/5Q==", + "dependencies": { + "debug": "*", + "readable-stream": "1.1.9" + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json index 1a82184..d4ef783 100644 --- a/package.json +++ b/package.json @@ -35,17 +35,38 @@ "email": "tuanngocptn@gmail.com" }, "contributors": [], - "dependencies": {}, + "dependencies": { + "socket.io": "^4.8.1", + "socket.io-client": "^4.8.1", + "socket.io-stream": "^0.5.3", + "yargs": "^17.7.2" + }, "bin": { - "nport": "./bin/client" + "nport": "./dist/client.js" + }, + "scripts": { + "server": "node src/server.mjs start", + "client": "node src/client.mjs", + "server:dev": "nodemon src/server.mjs start", + "client:dev": "nodemon src/client.mjs", + "release": "babel src --out-dir dist", + "server:release": "npm run release --verbose && /usr/bin/env node dist/server.js start", + "client:release": "npm run release --verbose && /usr/bin/env node dist/client.js" }, "engines": { - "node": ">=18.0.0", - "npm": ">=10.0.0" + "node": ">=12", + "npm": ">=6" }, "files": [], "funding": { "type": "individual", "url": "https://github.com/sponsors/tuanngocptn" + }, + "devDependencies": { + "@babel/cli": "^7.26.4", + "@babel/core": "^7.26.0", + "@babel/preset-env": "^7.26.0", + "babel-plugin-replace-import-extension": "^1.1.4", + "nodemon": "^3.1.9" } -} \ No newline at end of file +} diff --git a/src/client.mjs b/src/client.mjs new file mode 100644 index 0000000..3c22adb --- /dev/null +++ b/src/client.mjs @@ -0,0 +1,24 @@ +#!/usr/bin/env node +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; +import client from "./services/client.service.mjs"; + +yargs(hideBin(process.argv)) + .command( + "* [port] [subdomain]", + "Start the client", + (yargs) => { + yargs.positional("port", { + type: "number", + default: 3000, + describe: "The port to start the server on", + }); + yargs.positional("subdomain", { + type: "string", + default: "myapp", + describe: "The subdomain to use", + }); + }, + client + ) + .parse(); diff --git a/src/server.mjs b/src/server.mjs new file mode 100644 index 0000000..7042e89 --- /dev/null +++ b/src/server.mjs @@ -0,0 +1,18 @@ +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; +import server from "./services/server.service.mjs"; + +yargs(hideBin(process.argv)) + .command( + "start [port]", + "Start the server", + (yargs) => { + yargs.positional("port", { + type: "number", + default: 3000, + describe: "The port to start the server on", + }); + }, + server + ) + .parse(); diff --git a/src/services/client.service.mjs b/src/services/client.service.mjs new file mode 100644 index 0000000..d919ac4 --- /dev/null +++ b/src/services/client.service.mjs @@ -0,0 +1,11 @@ +import Client from "socket.io-client"; + +const client = (options) => { + const socket = Client("http://127.0.0.1:3000"); + socket.on("connect", () => { + console.log("Connected to server"); + }); + console.log("🚀 ~ options:", options); +}; + +export default client; diff --git a/src/services/server.service.mjs b/src/services/server.service.mjs new file mode 100644 index 0000000..99180f4 --- /dev/null +++ b/src/services/server.service.mjs @@ -0,0 +1,23 @@ +import { createServer } from "http"; +import { Server } from "socket.io"; + +const server = (options) => { + const server = createServer((req, res) => { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("Hello World\n"); + }); + + const io = new Server(server); + + io.on("connection", (socket) => { + console.log("a user connected"); + }); + + server.listen(options.port, "127.0.0.1", () => {}); + + console.log( + `${new Date()}: nport server started on 127.0.0.1:${options.port}` + ); +}; + +export default server; From 6176f213feb773fe657108c741c59ecd0a20aaba Mon Sep 17 00:00:00 2001 From: Nick - Ngoc Pham Date: Fri, 20 Dec 2024 18:07:08 +0700 Subject: [PATCH 03/21] feat: finish config docker compose --- config/client.dockerfile | 13 ++++++++++ config/nport.local.conf | 22 +++++++++++++++++ config/server.dockerfile | 20 +++++++++++++++ docker-compose.yml | 44 +++++++++++++++++++++++++++++++++ src/services/server.service.mjs | 17 ++++++++----- 5 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 config/client.dockerfile create mode 100644 config/nport.local.conf create mode 100644 config/server.dockerfile create mode 100644 docker-compose.yml diff --git a/config/client.dockerfile b/config/client.dockerfile new file mode 100644 index 0000000..7b40b3c --- /dev/null +++ b/config/client.dockerfile @@ -0,0 +1,13 @@ +FROM node:22.12.0-slim + +WORKDIR /usr/src/app + +COPY package*.json ./ + +RUN npm install + +COPY . . + +EXPOSE 3000 + +CMD ["node", "src/client.mjs"] diff --git a/config/nport.local.conf b/config/nport.local.conf new file mode 100644 index 0000000..8e3ea98 --- /dev/null +++ b/config/nport.local.conf @@ -0,0 +1,22 @@ +# nginx for running nport locally you need edit host file for add +# 127.0.0.1 nport.local +# 127.0.0.1 server.nport.local +# 127.0.0.1 myapp.nport.local + +server { + listen 80; + listen [::]:80; + server_name localhost, nport.local *.nport.local; + location / { + proxy_pass http://nport-server:3000/; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_http_version 1.1; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-NginX-Proxy true; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_redirect off; + } +} \ No newline at end of file diff --git a/config/server.dockerfile b/config/server.dockerfile new file mode 100644 index 0000000..fb6498d --- /dev/null +++ b/config/server.dockerfile @@ -0,0 +1,20 @@ +# Use the Node.js v22.12.0 base image +FROM node:22.12.0-slim + +# Set the working directory +WORKDIR /usr/src/app + +# Copy package.json and package-lock.json +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy the rest of the application code +COPY . . + +# Expose the application's default port +EXPOSE 3000 + +# Default command (overwritten by docker-compose command) +CMD ["node", "src/server.mjs"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ae5d189 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,44 @@ +services: + nport-server: + build: + context: . + dockerfile: ./config/server.dockerfile + container_name: nport-server + ports: + - "3000:3000" + volumes: + - .:/usr/src/app + restart: unless-stopped + networks: + - nport-network + command: npm run server:dev + + nport-client: + build: + context: . + dockerfile: ./config/client.dockerfile + container_name: nport-client + volumes: + - .:/usr/src/app + restart: unless-stopped + networks: + - nport-network + command: npm run client:dev + depends_on: + - nport-server + + nginx: + image: nginx:1.27.3-perl + container_name: nport-nginx + volumes: + - ./config/nport.local.conf:/etc/nginx/conf.d/default.conf:ro + ports: + - "80:80" + # - "443:443" + restart: unless-stopped + networks: + - nport-network + +networks: + nport-network: + driver: bridge diff --git a/src/services/server.service.mjs b/src/services/server.service.mjs index 99180f4..2da4a50 100644 --- a/src/services/server.service.mjs +++ b/src/services/server.service.mjs @@ -7,17 +7,22 @@ const server = (options) => { res.end("Hello World\n"); }); - const io = new Server(server); + const io = new Server(server, { + cors: { + origin: "*", + methods: ["GET", "POST"], + }, + }); io.on("connection", (socket) => { console.log("a user connected"); }); - server.listen(options.port, "127.0.0.1", () => {}); - - console.log( - `${new Date()}: nport server started on 127.0.0.1:${options.port}` - ); + server.listen(options.port, "0.0.0.0", () => { + console.log( + `${new Date()}: nport server started on 0.0.0.0:${options.port}` + ); + }); }; export default server; From 86db570bd70b45f4792e0cee5b5cef60ed4c31c9 Mon Sep 17 00:00:00 2001 From: Nick - Ngoc Pham Date: Mon, 23 Dec 2024 10:52:02 +0700 Subject: [PATCH 04/21] refactor: change for running with dockercompose --- config/client.dockerfile | 8 +------- config/server.dockerfile | 15 +-------------- dist/client.js | 1 + dist/server.js | 1 + dist/services/server.service.js | 12 +++++++++--- docker-compose.yml | 4 ++-- package-lock.json | 2 +- package.json | 14 ++++++-------- src/{ => client}/client.mjs | 0 src/{ => client}/services/client.service.mjs | 0 src/{ => server}/server.mjs | 1 + src/{ => server}/services/server.service.mjs | 0 12 files changed, 23 insertions(+), 35 deletions(-) rename src/{ => client}/client.mjs (100%) rename src/{ => client}/services/client.service.mjs (100%) rename src/{ => server}/server.mjs (95%) rename src/{ => server}/services/server.service.mjs (100%) diff --git a/config/client.dockerfile b/config/client.dockerfile index 7b40b3c..21ede0a 100644 --- a/config/client.dockerfile +++ b/config/client.dockerfile @@ -1,13 +1,7 @@ FROM node:22.12.0-slim - WORKDIR /usr/src/app - COPY package*.json ./ - RUN npm install - COPY . . - EXPOSE 3000 - -CMD ["node", "src/client.mjs"] +CMD ["npm", "run", "client"] diff --git a/config/server.dockerfile b/config/server.dockerfile index fb6498d..224cf62 100644 --- a/config/server.dockerfile +++ b/config/server.dockerfile @@ -1,20 +1,7 @@ -# Use the Node.js v22.12.0 base image FROM node:22.12.0-slim - -# Set the working directory WORKDIR /usr/src/app - -# Copy package.json and package-lock.json COPY package*.json ./ - -# Install dependencies RUN npm install - -# Copy the rest of the application code COPY . . - -# Expose the application's default port EXPOSE 3000 - -# Default command (overwritten by docker-compose command) -CMD ["node", "src/server.mjs"] \ No newline at end of file +CMD ["npm", "run", "server"] \ No newline at end of file diff --git a/dist/client.js b/dist/client.js index 074b332..729aa91 100644 --- a/dist/client.js +++ b/dist/client.js @@ -1,3 +1,4 @@ +#!/usr/bin/env node "use strict"; var _yargs = _interopRequireDefault(require("yargs")); diff --git a/dist/server.js b/dist/server.js index 2e7c30b..ed89252 100644 --- a/dist/server.js +++ b/dist/server.js @@ -1,3 +1,4 @@ +#!/usr/bin/env node "use strict"; var _yargs = _interopRequireDefault(require("yargs")); diff --git a/dist/services/server.service.js b/dist/services/server.service.js index fe5a5be..06ed3c4 100644 --- a/dist/services/server.service.js +++ b/dist/services/server.service.js @@ -13,11 +13,17 @@ const server = options => { }); res.end("Hello World\n"); }); - const io = new _socket.Server(server); + const io = new _socket.Server(server, { + cors: { + origin: "*", + methods: ["GET", "POST"] + } + }); io.on("connection", socket => { console.log("a user connected"); }); - server.listen(options.port, "127.0.0.1", () => {}); - console.log(`${new Date()}: nport server started on 127.0.0.1:${options.port}`); + server.listen(options.port, "0.0.0.0", () => { + console.log(`${new Date()}: nport server started on 0.0.0.0:${options.port}`); + }); }; var _default = exports.default = server; \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index ae5d189..e4fed0f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: ports: - "3000:3000" volumes: - - .:/usr/src/app + - ./src/server:/usr/src/app/src/server restart: unless-stopped networks: - nport-network @@ -19,7 +19,7 @@ services: dockerfile: ./config/client.dockerfile container_name: nport-client volumes: - - .:/usr/src/app + - ./src/client:/usr/src/app/src/client restart: unless-stopped networks: - nport-network diff --git a/package-lock.json b/package-lock.json index 0a8f0ab..4ceb809 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "yargs": "^17.7.2" }, "bin": { - "nport": "usr/bin/env node ./dist/client.js" + "nport": "dist/client.js" }, "devDependencies": { "@babel/cli": "^7.26.4", diff --git a/package.json b/package.json index d4ef783..5b047d3 100644 --- a/package.json +++ b/package.json @@ -45,13 +45,11 @@ "nport": "./dist/client.js" }, "scripts": { - "server": "node src/server.mjs start", - "client": "node src/client.mjs", - "server:dev": "nodemon src/server.mjs start", - "client:dev": "nodemon src/client.mjs", - "release": "babel src --out-dir dist", - "server:release": "npm run release --verbose && /usr/bin/env node dist/server.js start", - "client:release": "npm run release --verbose && /usr/bin/env node dist/client.js" + "release": "rm -rf dist && babel src/server --out-dir dist && babel src/client --out-dir dist", + "server": "npm run release && node dist/server.js start", + "client": "npm run release && node dist/client.js", + "server:dev": "nodemon src/server/server.mjs start", + "client:dev": "nodemon src/client/client.mjs" }, "engines": { "node": ">=12", @@ -69,4 +67,4 @@ "babel-plugin-replace-import-extension": "^1.1.4", "nodemon": "^3.1.9" } -} +} \ No newline at end of file diff --git a/src/client.mjs b/src/client/client.mjs similarity index 100% rename from src/client.mjs rename to src/client/client.mjs diff --git a/src/services/client.service.mjs b/src/client/services/client.service.mjs similarity index 100% rename from src/services/client.service.mjs rename to src/client/services/client.service.mjs diff --git a/src/server.mjs b/src/server/server.mjs similarity index 95% rename from src/server.mjs rename to src/server/server.mjs index 7042e89..7803633 100644 --- a/src/server.mjs +++ b/src/server/server.mjs @@ -1,3 +1,4 @@ +#!/usr/bin/env node import yargs from "yargs"; import { hideBin } from "yargs/helpers"; import server from "./services/server.service.mjs"; diff --git a/src/services/server.service.mjs b/src/server/services/server.service.mjs similarity index 100% rename from src/services/server.service.mjs rename to src/server/services/server.service.mjs From c9343674b72d5fe7ce381292acd78cd37f45abb2 Mon Sep 17 00:00:00 2001 From: Nick - Ngoc Pham Date: Mon, 23 Dec 2024 15:16:52 +0700 Subject: [PATCH 05/21] feat: change network config --- .env | 2 +- .env.dev | 1 + config/client.dockerfile | 2 +- config/server.dockerfile | 2 +- docker-compose.yml | 47 ++++++++++++++++++-------- package.json | 8 ++--- src/client/services/client.service.mjs | 4 ++- 7 files changed, 44 insertions(+), 22 deletions(-) create mode 100644 .env.dev diff --git a/.env b/.env index d7cec1a..23ef8e4 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -IDLE_SOCKET_TIMEOUT_MILLISECONDS=3000 +BASE_URL=https://server.nport.link diff --git a/.env.dev b/.env.dev new file mode 100644 index 0000000..3141c31 --- /dev/null +++ b/.env.dev @@ -0,0 +1 @@ +BASE_URL=http://server.nport.local diff --git a/config/client.dockerfile b/config/client.dockerfile index 21ede0a..785a968 100644 --- a/config/client.dockerfile +++ b/config/client.dockerfile @@ -4,4 +4,4 @@ COPY package*.json ./ RUN npm install COPY . . EXPOSE 3000 -CMD ["npm", "run", "client"] +ENTRYPOINT ["npm", "run", "client"] diff --git a/config/server.dockerfile b/config/server.dockerfile index 224cf62..a639871 100644 --- a/config/server.dockerfile +++ b/config/server.dockerfile @@ -4,4 +4,4 @@ COPY package*.json ./ RUN npm install COPY . . EXPOSE 3000 -CMD ["npm", "run", "server"] \ No newline at end of file +ENTRYPOINT ["npm", "run", "server"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index e4fed0f..4e0398e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,7 @@ services: build: context: . dockerfile: ./config/server.dockerfile + image: nport-server:v1 container_name: nport-server ports: - "3000:3000" @@ -11,34 +12,52 @@ services: restart: unless-stopped networks: - nport-network - command: npm run server:dev + networks: + nport-network: + ipv4_address: 192.168.10.3 + entrypoint: npm run server:dev + + nport-nginx: + image: nginx:1.27.3-perl + container_name: nport-nginx + volumes: + - ./config/nport.local.conf:/etc/nginx/conf.d/default.conf:ro + ports: + - "80:80" + # - "443:443" + restart: unless-stopped + logging: + driver: none + networks: + nport-network: + ipv4_address: 192.168.10.2 nport-client: build: context: . dockerfile: ./config/client.dockerfile + image: nport-client:v1 container_name: nport-client volumes: - ./src/client:/usr/src/app/src/client restart: unless-stopped networks: - nport-network - command: npm run client:dev + networks: + nport-network: + ipv4_address: 192.168.10.4 + entrypoint: npm run client:dev + extra_hosts: + - "nport.local:192.168.10.2" + - "server.nport.local:192.168.10.2" + - "myapp.nport.local:192.168.10.2" depends_on: + - nport-nginx - nport-server - nginx: - image: nginx:1.27.3-perl - container_name: nport-nginx - volumes: - - ./config/nport.local.conf:/etc/nginx/conf.d/default.conf:ro - ports: - - "80:80" - # - "443:443" - restart: unless-stopped - networks: - - nport-network - networks: nport-network: driver: bridge + ipam: + config: + - subnet: 192.168.10.0/24 diff --git a/package.json b/package.json index 5b047d3..f938239 100644 --- a/package.json +++ b/package.json @@ -46,10 +46,10 @@ }, "scripts": { "release": "rm -rf dist && babel src/server --out-dir dist && babel src/client --out-dir dist", - "server": "npm run release && node dist/server.js start", - "client": "npm run release && node dist/client.js", - "server:dev": "nodemon src/server/server.mjs start", - "client:dev": "nodemon src/client/client.mjs" + "server": "npm run release && node --env-file=.env dist/server.js start", + "client": "npm run release && node --env-file=.env dist/client.js", + "server:dev": "nodemon --env-file=.env.dev src/server/server.mjs start", + "client:dev": "nodemon --env-file=.env.dev src/client/client.mjs" }, "engines": { "node": ">=12", diff --git a/src/client/services/client.service.mjs b/src/client/services/client.service.mjs index d919ac4..5e5dfcf 100644 --- a/src/client/services/client.service.mjs +++ b/src/client/services/client.service.mjs @@ -1,7 +1,9 @@ import Client from "socket.io-client"; +console.log(process.env.BASE_URL); + const client = (options) => { - const socket = Client("http://127.0.0.1:3000"); + const socket = Client(process.env.BASE_URL); socket.on("connect", () => { console.log("Connected to server"); }); From 39293cf704d114474176aa5c830841c40f1d613a Mon Sep 17 00:00:00 2001 From: Nick - Ngoc Pham Date: Mon, 23 Dec 2024 15:55:09 +0700 Subject: [PATCH 06/21] feat: disable nginx log --- docker-compose.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4e0398e..fa71233 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,8 +10,6 @@ services: volumes: - ./src/server:/usr/src/app/src/server restart: unless-stopped - networks: - - nport-network networks: nport-network: ipv4_address: 192.168.10.3 @@ -21,13 +19,14 @@ services: image: nginx:1.27.3-perl container_name: nport-nginx volumes: + - /dev/null:/var/log/nginx/access.log + - /dev/null:/var/log/nginx/error.log - ./config/nport.local.conf:/etc/nginx/conf.d/default.conf:ro ports: - "80:80" - # - "443:443" restart: unless-stopped logging: - driver: none + driver: "none" networks: nport-network: ipv4_address: 192.168.10.2 @@ -41,8 +40,6 @@ services: volumes: - ./src/client:/usr/src/app/src/client restart: unless-stopped - networks: - - nport-network networks: nport-network: ipv4_address: 192.168.10.4 @@ -51,6 +48,7 @@ services: - "nport.local:192.168.10.2" - "server.nport.local:192.168.10.2" - "myapp.nport.local:192.168.10.2" + - "other.nport.local:192.168.10.2" depends_on: - nport-nginx - nport-server From 244e5ebedbcad8c801fbbc2ca5b5bcf9b37b23ff Mon Sep 17 00:00:00 2001 From: Nick - Ngoc Pham Date: Wed, 25 Dec 2024 10:56:53 +0700 Subject: [PATCH 07/21] chore: refactor environment configuration and enhance docker setup - Removed old .env and .env.dev files; added new configuration files in config/ directory. - Updated docker-compose.yml to include a new nport-site service and adjusted volume paths. - Introduced new site.dockerfile for serving static content. - Modified client and server Dockerfiles to change working directory to /app. - Added run.sh script for simplified docker-compose commands. - Updated package.json and package-lock.json to include uuid dependency. - Created index.html for initial site structure and added socket.io client integration. - Refactored client and server services to improve socket handling and request streaming. --- .env | 1 - .env.dev | 1 - config/.env | 1 + config/.env.dev | 2 + config/client.dockerfile | 2 +- config/server.dockerfile | 2 +- config/site.dockerfile | 6 ++ dist/services/client.service.js | 1 - docker-compose.yml | 20 +++- index.html | 15 +++ package-lock.json | 15 +++ package.json | 11 +- run.sh | 3 + src/client/client.mjs | 11 +- src/client/services/client.service.mjs | 51 +++++++++- src/server/services/server.service.mjs | 136 ++++++++++++++++++++++++- 16 files changed, 254 insertions(+), 24 deletions(-) delete mode 100644 .env delete mode 100644 .env.dev create mode 100644 config/.env create mode 100644 config/.env.dev create mode 100644 config/site.dockerfile create mode 100644 index.html create mode 100644 run.sh diff --git a/.env b/.env deleted file mode 100644 index 23ef8e4..0000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -BASE_URL=https://server.nport.link diff --git a/.env.dev b/.env.dev deleted file mode 100644 index 3141c31..0000000 --- a/.env.dev +++ /dev/null @@ -1 +0,0 @@ -BASE_URL=http://server.nport.local diff --git a/config/.env b/config/.env new file mode 100644 index 0000000..1711129 --- /dev/null +++ b/config/.env @@ -0,0 +1 @@ +SERVER_URL=https://server.nport.link diff --git a/config/.env.dev b/config/.env.dev new file mode 100644 index 0000000..ae1dc78 --- /dev/null +++ b/config/.env.dev @@ -0,0 +1,2 @@ +SERVER_URL=http://server.nport.local +DOMAIN=nport.local \ No newline at end of file diff --git a/config/client.dockerfile b/config/client.dockerfile index 785a968..9f8b850 100644 --- a/config/client.dockerfile +++ b/config/client.dockerfile @@ -1,5 +1,5 @@ FROM node:22.12.0-slim -WORKDIR /usr/src/app +WORKDIR /app COPY package*.json ./ RUN npm install COPY . . diff --git a/config/server.dockerfile b/config/server.dockerfile index a639871..725ab60 100644 --- a/config/server.dockerfile +++ b/config/server.dockerfile @@ -1,5 +1,5 @@ FROM node:22.12.0-slim -WORKDIR /usr/src/app +WORKDIR /app COPY package*.json ./ RUN npm install COPY . . diff --git a/config/site.dockerfile b/config/site.dockerfile new file mode 100644 index 0000000..952f813 --- /dev/null +++ b/config/site.dockerfile @@ -0,0 +1,6 @@ +FROM node:22.12.0-slim +WORKDIR /app +RUN npm install -g http-server +COPY . . +EXPOSE 3000 +ENTRYPOINT ["http-server", "-p", "8080", "/usr/src/app"] diff --git a/dist/services/client.service.js b/dist/services/client.service.js index de3bb36..f614f57 100644 --- a/dist/services/client.service.js +++ b/dist/services/client.service.js @@ -11,6 +11,5 @@ const client = options => { socket.on("connect", () => { console.log("Connected to server"); }); - console.log("🚀 ~ options:", options); }; var _default = exports.default = client; \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index fa71233..3edfbe8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: ports: - "3000:3000" volumes: - - ./src/server:/usr/src/app/src/server + - ./src/server:/app/src/server restart: unless-stopped networks: nport-network: @@ -31,6 +31,22 @@ services: nport-network: ipv4_address: 192.168.10.2 + nport-site: + build: + context: . + dockerfile: ./config/site.dockerfile + image: nport-site:v1 + container_name: nport-site + volumes: + - ./src/site:/app/src/site + restart: unless-stopped + ports: + - "8080:8080" + networks: + nport-network: + ipv4_address: 192.168.10.5 + entrypoint: http-server -p 8080 /app + nport-client: build: context: . @@ -38,7 +54,7 @@ services: image: nport-client:v1 container_name: nport-client volumes: - - ./src/client:/usr/src/app/src/client + - ./src/client:/app/src/client restart: unless-stopped networks: nport-network: diff --git a/index.html b/index.html new file mode 100644 index 0000000..1e3461d --- /dev/null +++ b/index.html @@ -0,0 +1,15 @@ + + + + + + Document + + +

Hello World

+ + + + diff --git a/package-lock.json b/package-lock.json index 4ceb809..2be069f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "socket.io": "^4.8.1", "socket.io-client": "^4.8.1", "socket.io-stream": "^0.5.3", + "uuid": "^11.0.3", "yargs": "^17.7.2" }, "bin": { @@ -2839,6 +2840,7 @@ "version": "4.8.1", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", @@ -3074,6 +3076,19 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uuid": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", + "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index f938239..1086ab1 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "socket.io": "^4.8.1", "socket.io-client": "^4.8.1", "socket.io-stream": "^0.5.3", + "uuid": "^11.0.3", "yargs": "^17.7.2" }, "bin": { @@ -46,10 +47,10 @@ }, "scripts": { "release": "rm -rf dist && babel src/server --out-dir dist && babel src/client --out-dir dist", - "server": "npm run release && node --env-file=.env dist/server.js start", - "client": "npm run release && node --env-file=.env dist/client.js", - "server:dev": "nodemon --env-file=.env.dev src/server/server.mjs start", - "client:dev": "nodemon --env-file=.env.dev src/client/client.mjs" + "server": "npm run release && NODE_NO_WARNINGS=1 node --env-file=./config/.env dist/server.js start", + "client": "npm run release && NODE_NO_WARNINGS=1 node --env-file=./config/.env dist/client.js", + "server:dev": "NODE_NO_WARNINGS=1 nodemon --env-file=./config/.env.dev src/server/server.mjs start", + "client:dev": "NODE_NO_WARNINGS=1 nodemon --env-file=./config/.env.dev src/client/client.mjs" }, "engines": { "node": ">=12", @@ -67,4 +68,4 @@ "babel-plugin-replace-import-extension": "^1.1.4", "nodemon": "^3.1.9" } -} \ No newline at end of file +} diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..a12acf9 --- /dev/null +++ b/run.sh @@ -0,0 +1,3 @@ +docker-compose down --remove-orphans && + docker-compose build && + docker-compose up --force-recreate diff --git a/src/client/client.mjs b/src/client/client.mjs index 3c22adb..b650158 100644 --- a/src/client/client.mjs +++ b/src/client/client.mjs @@ -5,12 +5,12 @@ import client from "./services/client.service.mjs"; yargs(hideBin(process.argv)) .command( - "* [port] [subdomain]", + "* [hostname]:[port] [subdomain]", "Start the client", (yargs) => { yargs.positional("port", { type: "number", - default: 3000, + default: 8080, describe: "The port to start the server on", }); yargs.positional("subdomain", { @@ -18,7 +18,12 @@ yargs(hideBin(process.argv)) default: "myapp", describe: "The subdomain to use", }); + yargs.positional("hostname", { + type: "string", + default: "192.168.10.5", + describe: "Address of local server for forwarding over NPort", + }); }, - client + (options) => client({ ...options, server: process.env.SERVER_URL }) ) .parse(); diff --git a/src/client/services/client.service.mjs b/src/client/services/client.service.mjs index 5e5dfcf..8f0a6df 100644 --- a/src/client/services/client.service.mjs +++ b/src/client/services/client.service.mjs @@ -1,13 +1,54 @@ import Client from "socket.io-client"; +import net from "net"; +import ss from "socket.io-stream"; -console.log(process.env.BASE_URL); +const IDLE_SOCKET_TIMEOUT_MILLISECONDS = 1000 * 30; -const client = (options) => { - const socket = Client(process.env.BASE_URL); +const client = async (options) => { + const socket = Client(process.env.SERVER_URL); socket.on("connect", () => { - console.log("Connected to server"); + console.log(new Date() + `: Connected ${process.env.SERVER_URL}`); + console.log( + new Date() + + `: Requesting subdomain '${options["subdomain"]}' via '${options["server"]}'` + ); + + socket.emit( + "CreateTunnel", + { + subdomain: options["subdomain"], + server: options["server"], + }, + (response) => { + console.log(new Date() + `: Response ${response}`); + } + ); + }); + + socket.on("IncomingClient", (clientId) => { + const client = net.connect(options.port, options.hostname, () => { + const socket = ss.createStream(); + socket.pipe(client).pipe(socket); + socket.on("end", () => { + client.destroy(); + }); + ss(socket).emit(clientId, socket); + }); + + client.setTimeout(IDLE_SOCKET_TIMEOUT_MILLISECONDS); + client.on("timeout", () => { + console.log("🚀 ~ timeout:") + client.end(); + }); + + client.on("error", (err) => { + console.log("🚀 ~ error:", err.message); + // handle connection refusal (create a stream and immediately close it) + const socket = ss.createStream(); + ss(socket).emit(clientId, socket); + socket.end(); + }); }); - console.log("🚀 ~ options:", options); }; export default client; diff --git a/src/server/services/server.service.mjs b/src/server/services/server.service.mjs index 2da4a50..ea34487 100644 --- a/src/server/services/server.service.mjs +++ b/src/server/services/server.service.mjs @@ -1,11 +1,13 @@ import { createServer } from "http"; import { Server } from "socket.io"; +import { v4 as uuidv4 } from "uuid"; +import ss from "socket.io-stream"; + +// association between subdomains and socket.io sockets +let socketsBySubdomain = {}; const server = (options) => { - const server = createServer((req, res) => { - res.writeHead(200, { "Content-Type": "text/plain" }); - res.end("Hello World\n"); - }); + const server = createServer(requestListener); const io = new Server(server, { cors: { @@ -16,6 +18,31 @@ const server = (options) => { io.on("connection", (socket) => { console.log("a user connected"); + + socket.on("CreateTunnel", (data, resCallBack) => { + if (!data || !data.subdomain) { + resCallBack?.("Invalid tunnel data"); + return socket.disconnect(); + } + const { subdomain } = data; + let subdomainNormalized = subdomain + .toString() + .toLowerCase() + .replace(/[^0-9a-z-.]/g, ""); + + if (socketsBySubdomain[subdomain]) { + resCallBack?.("Subdomain already exists"); + return socket.disconnect(); + } + socketsBySubdomain[subdomain] = socket; + socket.requestedName = subdomainNormalized; + resCallBack?.("Subdomain created"); + }); + + socket.on("disconnect", () => { + console.log("a user disconnected"); + delete socketsBySubdomain[socket.requestedName]; + }); }); server.listen(options.port, "0.0.0.0", () => { @@ -25,4 +52,105 @@ const server = (options) => { }); }; +const requestListener = async (req, res) => { + try { + const tunnelClientStream = await getTunnelClientStream(req); + const reqBodyChunks = []; + + req.on("error", (err) => { + console.error(err.stack); + }); + + // collect body chunks + req.on("data", (bodyChunk) => { + reqBodyChunks.push(bodyChunk); + }); + + // proxy finalized request to tunnel stream + req.on("end", () => { + // make sure the client didn't die on us + if (req.complete) { + const reqLine = getReqLineFromReq(req); + const headers = getHeadersFromReq(req); + + let reqBody = null; + if (reqBodyChunks.length > 0) { + reqBody = Buffer.concat(reqBodyChunks); + } + + streamResponse(reqLine, headers, reqBody, tunnelClientStream); + } + }); + } catch (error) { + res.writeHead(500, { "Content-Type": "text/plain" }); + res.end(error.message); + } +}; + +const getReqLineFromReq = (req) => { + return `${req.method} ${req.url} HTTP/${req.httpVersion}`; +}; + +const getHeadersFromReq = (req) => { + const headers = []; + + for (let i = 0; i < req.rawHeaders.length - 1; i += 2) { + headers.push(req.rawHeaders[i] + ": " + req.rawHeaders[i + 1]); + } + + return headers; +}; + +const streamResponse = (reqLine, headers, reqBody, tunnelClientStream) => { + tunnelClientStream.write(reqLine); + tunnelClientStream.write("\r\n"); + tunnelClientStream.write(headers.join("\r\n")); + tunnelClientStream.write("\r\n\r\n"); + if (reqBody) { + tunnelClientStream.write(reqBody); + } +}; + +const getTunnelClientStream = async (req) => { + let hostname = req.headers.host; + if (!hostname) { + throw new Error("Invalid hostname"); + } + + const subdomain = hostname.substring( + 0, + hostname.lastIndexOf(`.${process.env.DOMAIN}`) + ); + + if (!subdomain) { + console.log("Invalid subdomain"); + throw new Error("Invalid subdomain"); + } + + let subdomainSocket = socketsBySubdomain[subdomain]; + if (!subdomainSocket) { + throw new Error(`${subdomain} is currently unregistered or offline.`); + } + + if ( + req.connection.tunnelClientStream !== undefined && + !req.connection.tunnelClientStream.destroyed && + req.connection.subdomain === subdomain + ) { + return req.connection.tunnelClientStream; + } + + const requestGUID = uuidv4(); + ss(subdomainSocket).once(requestGUID, (tunnelClientStream) => { + req.connection.subdomain = subdomain; + req.connection.tunnelClientStream = tunnelClientStream; + // Pipe all data from tunnel stream to requesting connection + tunnelClientStream.pipe(req.connection); + return tunnelClientStream; + }); + + subdomainSocket.emit("IncomingClient", requestGUID); + throw new Error(`${subdomain} has an unknown error.`); +}; + export default server; From d33726dcf454f28d3b2ca666f99129945ffeafeb Mon Sep 17 00:00:00 2001 From: Nick - Ngoc Pham Date: Wed, 25 Dec 2024 10:57:25 +0700 Subject: [PATCH 08/21] chore: draft --- src/client/services/client.service.mjs | 79 ++++++++++++++++++-------- src/server/services/server.service.mjs | 43 ++++++++++---- 2 files changed, 86 insertions(+), 36 deletions(-) diff --git a/src/client/services/client.service.mjs b/src/client/services/client.service.mjs index 8f0a6df..9774ccf 100644 --- a/src/client/services/client.service.mjs +++ b/src/client/services/client.service.mjs @@ -6,47 +6,76 @@ const IDLE_SOCKET_TIMEOUT_MILLISECONDS = 1000 * 30; const client = async (options) => { const socket = Client(process.env.SERVER_URL); + + socket.on("connect_error", (error) => { + console.error(new Date() + ": Connection error:", error); + }); + + socket.on("disconnect", (reason) => { + console.log(new Date() + ": Disconnected:", reason); + if (reason === "io server disconnect") { + socket.connect(); + } + }); + socket.on("connect", () => { console.log(new Date() + `: Connected ${process.env.SERVER_URL}`); - console.log( - new Date() + - `: Requesting subdomain '${options["subdomain"]}' via '${options["server"]}'` - ); - - socket.emit( - "CreateTunnel", - { - subdomain: options["subdomain"], - server: options["server"], - }, - (response) => { - console.log(new Date() + `: Response ${response}`); - } - ); + const createTunnel = () => { + socket.emit( + "CreateTunnel", + { + subdomain: options["subdomain"], + server: options["server"], + }, + (response) => { + console.log(new Date() + `: Response ${response}`); + if (response === "Subdomain already exists") { + setTimeout(createTunnel, 5000); + } + } + ); + }; + createTunnel(); }); socket.on("IncomingClient", (clientId) => { const client = net.connect(options.port, options.hostname, () => { - const socket = ss.createStream(); - socket.pipe(client).pipe(socket); - socket.on("end", () => { + const stream = ss.createStream(); + + stream.on("error", (err) => { + console.error("Stream error:", err); + stream.destroy(); client.destroy(); }); - ss(socket).emit(clientId, socket); + + client.on("error", (err) => { + console.error("Client error:", err); + stream.destroy(); + client.destroy(); + }); + + ss(socket).emit(clientId, stream, (error) => { + if (error) { + console.error("Stream creation error:", error); + stream.destroy(); + client.destroy(); + return; + } + client.pipe(stream).pipe(client); + }); }); client.setTimeout(IDLE_SOCKET_TIMEOUT_MILLISECONDS); client.on("timeout", () => { - console.log("🚀 ~ timeout:") + console.log("Connection timeout - closing socket"); client.end(); }); client.on("error", (err) => { - console.log("🚀 ~ error:", err.message); - // handle connection refusal (create a stream and immediately close it) - const socket = ss.createStream(); - ss(socket).emit(clientId, socket); - socket.end(); + console.error("Connection error:", err.message); + const errorStream = ss.createStream(); + ss(socket).emit(clientId, errorStream); + errorStream.end(); }); }); }; diff --git a/src/server/services/server.service.mjs b/src/server/services/server.service.mjs index ea34487..4abd41a 100644 --- a/src/server/services/server.service.mjs +++ b/src/server/services/server.service.mjs @@ -77,7 +77,6 @@ const requestListener = async (req, res) => { if (reqBodyChunks.length > 0) { reqBody = Buffer.concat(reqBodyChunks); } - streamResponse(reqLine, headers, reqBody, tunnelClientStream); } }); @@ -107,6 +106,7 @@ const streamResponse = (reqLine, headers, reqBody, tunnelClientStream) => { tunnelClientStream.write(headers.join("\r\n")); tunnelClientStream.write("\r\n\r\n"); if (reqBody) { + console.log("helloooo 6"); tunnelClientStream.write(reqBody); } }; @@ -140,17 +140,38 @@ const getTunnelClientStream = async (req) => { return req.connection.tunnelClientStream; } - const requestGUID = uuidv4(); - ss(subdomainSocket).once(requestGUID, (tunnelClientStream) => { - req.connection.subdomain = subdomain; - req.connection.tunnelClientStream = tunnelClientStream; - // Pipe all data from tunnel stream to requesting connection - tunnelClientStream.pipe(req.connection); - return tunnelClientStream; + const clientId = uuidv4(); + + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error("Tunnel stream connection timeout")); + }, 5000); // 5 second timeout + + try { + // First emit the incoming client event + subdomainSocket.emit("IncomingClient", clientId); + + // Then wait for the stream + ss(subdomainSocket).once(clientId, (tunnelClientStream) => { + clearTimeout(timeout); + req.connection.subdomain = subdomain; + req.connection.tunnelClientStream = tunnelClientStream; + + // Handle stream errors + tunnelClientStream.on('error', (err) => { + console.error('Stream error:', err); + tunnelClientStream.destroy(); + }); + + tunnelClientStream.pipe(req.connection); + resolve(tunnelClientStream); + }); + } catch (error) { + clearTimeout(timeout); + console.error("Tunnel stream error:", error); + reject(error); + } }); - - subdomainSocket.emit("IncomingClient", requestGUID); - throw new Error(`${subdomain} has an unknown error.`); }; export default server; From cde3c1682c85708089c0511f578dfbb1ee6cd447 Mon Sep 17 00:00:00 2001 From: Nick - Ngoc Pham Date: Wed, 25 Dec 2024 11:38:21 +0700 Subject: [PATCH 09/21] feat: finish editing --- package-lock.json | 39 +++ package.json | 2 + src/client/services/client.service.mjs | 272 ++++++++++++---- src/server/services/server.service.mjs | 432 +++++++++++++++++-------- 4 files changed, 550 insertions(+), 195 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2be069f..51a6b6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,11 @@ "version": "1.0.6", "license": "MIT", "dependencies": { + "is-valid-domain": "^0.1.6", "socket.io": "^4.8.1", "socket.io-client": "^4.8.1", "socket.io-stream": "^0.5.3", + "tldjs": "^2.3.1", "uuid": "^11.0.3", "yargs": "^17.7.2" }, @@ -2388,6 +2390,24 @@ "node": ">=0.12.0" } }, + "node_modules/is-valid-domain": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-valid-domain/-/is-valid-domain-0.1.6.tgz", + "integrity": "sha512-ZKtq737eFkZr71At8NxOFcP9O1K89gW3DkdrGMpp1upr/ueWjj+Weh4l9AI4rN0Gt8W2M1w7jrG2b/Yv83Ljpg==", + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + } + }, + "node_modules/is-valid-domain/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2620,6 +2640,12 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "license": "MIT" + }, "node_modules/readable-stream": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.9.tgz", @@ -2969,6 +2995,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tldjs": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tldjs/-/tldjs-2.3.1.tgz", + "integrity": "sha512-W/YVH/QczLUxVjnQhFC61Iq232NWu3TqDdO0S/MtXVz4xybejBov4ud+CIwN9aYqjOecEqIy0PscGkwpG9ZyTw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "punycode": "^1.4.1" + }, + "engines": { + "node": ">= 4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/package.json b/package.json index 1086ab1..c79e3f2 100644 --- a/package.json +++ b/package.json @@ -36,9 +36,11 @@ }, "contributors": [], "dependencies": { + "is-valid-domain": "^0.1.6", "socket.io": "^4.8.1", "socket.io-client": "^4.8.1", "socket.io-stream": "^0.5.3", + "tldjs": "^2.3.1", "uuid": "^11.0.3", "yargs": "^17.7.2" }, diff --git a/src/client/services/client.service.mjs b/src/client/services/client.service.mjs index 9774ccf..ef86468 100644 --- a/src/client/services/client.service.mjs +++ b/src/client/services/client.service.mjs @@ -1,83 +1,223 @@ -import Client from "socket.io-client"; -import net from "net"; -import ss from "socket.io-stream"; +"use strict"; +// Constants const IDLE_SOCKET_TIMEOUT_MILLISECONDS = 1000 * 30; -const client = async (options) => { - const socket = Client(process.env.SERVER_URL); +// Import dependencies +import net from "net"; +import ss from "socket.io-stream"; +import { io as clientIo } from "socket.io-client"; - socket.on("connect_error", (error) => { - console.error(new Date() + ": Connection error:", error); +// Export the main function as default +export default (options) => { + return new Promise((resolve, reject) => { + try { + const socket = initializeSocketClient(options.server); + setupSocketHandlers(socket, options, resolve, reject); + } catch (err) { + console.error(`${new Date()}: Error initializing client:`, err); + reject(err); + } }); +}; + +const initializeSocketClient = (serverUrl) => { + try { + return clientIo(serverUrl); + } catch (err) { + console.error(`${new Date()}: Error initializing socket client:`, err); + throw err; + } +}; + +const setupSocketHandlers = (socket, options, resolve, reject) => { + try { + socket.on("connect", () => handleConnect(socket, options, resolve, reject)); + socket.on("incomingClient", (clientId) => + handleIncomingClient(socket, clientId, options) + ); + socket.on("error", (err) => { + console.error(`${new Date()}: Socket error:`, err); + reject(err); + }); + } catch (err) { + console.error(`${new Date()}: Error setting up socket handlers:`, err); + reject(err); + } +}; + +const handleConnect = (socket, options, resolve, reject) => { + try { + logConnectionStatus(options); + requestTunnel(socket, options, resolve, reject); + } catch (err) { + console.error(`${new Date()}: Error handling connect:`, err); + reject(err); + } +}; - socket.on("disconnect", (reason) => { - console.log(new Date() + ": Disconnected:", reason); - if (reason === "io server disconnect") { - socket.connect(); +const logConnectionStatus = (options) => { + try { + console.log(`${new Date()}: connected`); + console.log( + `${new Date()}: requesting subdomain ${options.subdomain} via ${ + options.server + }` + ); + } catch (err) { + console.error(`${new Date()}: Error logging connection status:`, err); + throw err; + } +}; + +const requestTunnel = (socket, options, resolve, reject) => { + try { + socket.emit("createTunnel", options.subdomain, (err) => { + if (err) { + handleTunnelError(err, reject); + } else { + handleTunnelSuccess(options, resolve); + } + }); + } catch (err) { + console.error(`${new Date()}: Error requesting tunnel:`, err); + reject(err); + } +}; + +const handleTunnelError = (err, reject) => { + console.error(`${new Date()}: Tunnel error:`, err); + reject(err); +}; + +const handleTunnelSuccess = (options, resolve) => { + try { + console.log(`${new Date()}: registered with server successfully`); + console.log( + `${new Date()}: your domain is: https://${options.subdomain}.nport.link` + ); + resolve(constructUrl(options)); + } catch (err) { + console.error(`${new Date()}: Error handling tunnel success:`, err); + throw err; + } +}; + +const constructUrl = (options) => { + try { + const subdomain = options.subdomain.toString(); + const server = options.server.toString(); + + if (server.includes("https://")) { + return `https://${subdomain}.${server.slice(8)}`; + } else if (server.includes("http://")) { + return `http://${subdomain}.${server.slice(7)}`; } - }); + return `https://${subdomain}.${server}`; + } catch (err) { + console.error(`${new Date()}: Error constructing URL:`, err); + throw err; + } +}; - socket.on("connect", () => { - console.log(new Date() + `: Connected ${process.env.SERVER_URL}`); - const createTunnel = () => { - socket.emit( - "CreateTunnel", - { - subdomain: options["subdomain"], - server: options["server"], - }, - (response) => { - console.log(new Date() + `: Response ${response}`); - if (response === "Subdomain already exists") { - setTimeout(createTunnel, 5000); - } - } - ); - }; - createTunnel(); - }); +const handleIncomingClient = (socket, clientId, options) => { + try { + const client = createTcpConnection(options); + setupClientHandlers(client, socket, clientId); + } catch (err) { + console.error(`${new Date()}: Error handling incoming client:`, err); + } +}; + +const createTcpConnection = (options) => { + try { + const client = net.connect({ + port: options.port, + host: options.hostname, + timeout: IDLE_SOCKET_TIMEOUT_MILLISECONDS + }); + return client; + } catch (err) { + console.error(`${new Date()}: Error creating TCP connection:`, err); + throw err; + } +}; + +const setupClientHandlers = (client, socket, clientId) => { + try { + client.once('connect', () => { + handleClientConnect(client, socket, clientId); + }); - socket.on("IncomingClient", (clientId) => { - const client = net.connect(options.port, options.hostname, () => { - const stream = ss.createStream(); - - stream.on("error", (err) => { - console.error("Stream error:", err); - stream.destroy(); - client.destroy(); - }); - - client.on("error", (err) => { - console.error("Client error:", err); - stream.destroy(); - client.destroy(); - }); - - ss(socket).emit(clientId, stream, (error) => { - if (error) { - console.error("Stream creation error:", error); - stream.destroy(); - client.destroy(); - return; - } - client.pipe(stream).pipe(client); - }); + client.once('error', (err) => { + console.error(`${new Date()}: Client error:`, err); + handleClientError(socket, clientId); }); - client.setTimeout(IDLE_SOCKET_TIMEOUT_MILLISECONDS); - client.on("timeout", () => { - console.log("Connection timeout - closing socket"); + client.once('timeout', () => { + console.log(`${new Date()}: Client connection timed out`); client.end(); }); + } catch (err) { + console.error(`${new Date()}: Error setting up client handlers:`, err); + } +}; - client.on("error", (err) => { - console.error("Connection error:", err.message); - const errorStream = ss.createStream(); - ss(socket).emit(clientId, errorStream); - errorStream.end(); +const handleClientConnect = (client, socket, clientId) => { + try { + const stream = ss.createStream(); + + stream.once('error', (err) => { + console.error(`${new Date()}: Stream error:`, err); + cleanupConnection(client, stream); }); - }); + + client.once('error', (err) => { + console.error(`${new Date()}: Client error in stream:`, err); + cleanupConnection(client, stream); + }); + + stream.once('end', () => { + cleanupConnection(client, stream); + }); + + client.once('end', () => { + cleanupConnection(client, stream); + }); + + // Emit the stream through socket.io-stream + ss(socket).emit(clientId, stream); + + // Setup piping + client.pipe(stream).pipe(client); + + } catch (err) { + console.error(`${new Date()}: Error in handleClientConnect:`, err); + if (client && !client.destroyed) { + client.destroy(); + } + } +}; + +const cleanupConnection = (client, stream) => { + try { + if (stream && !stream.destroyed) { + stream.destroy(); + } + if (client && !client.destroyed) { + client.destroy(); + } + } catch (err) { + console.error(`${new Date()}: Error in cleanup:`, err); + } }; -export default client; +const handleClientError = (socket, clientId) => { + try { + const stream = ss.createStream(); + ss(socket).emit(clientId, stream); + stream.end(); + } catch (err) { + console.error(`${new Date()}: Error handling client error:`, err); + } +}; diff --git a/src/server/services/server.service.mjs b/src/server/services/server.service.mjs index 4abd41a..831d42a 100644 --- a/src/server/services/server.service.mjs +++ b/src/server/services/server.service.mjs @@ -1,177 +1,351 @@ -import { createServer } from "http"; -import { Server } from "socket.io"; -import { v4 as uuidv4 } from "uuid"; +// Import dependencies +import http from "http"; +import tldjs from "tldjs"; import ss from "socket.io-stream"; +import { v4 as uuid } from "uuid"; +import isValidDomain from "is-valid-domain"; +import { Server as SocketIO } from "socket.io"; +const socketsBySubdomain = {}; +let options = {}; -// association between subdomains and socket.io sockets -let socketsBySubdomain = {}; +// Export the main function as default +export default ({ port, hostname, subdomain }) => { + options = { port, hostname, subdomain }; + // Association between subdomains and socket.io sockets -const server = (options) => { - const server = createServer(requestListener); + // Create HTTP server to handle tunnel requests + const server = createTunnelServer(); - const io = new Server(server, { - cors: { - origin: "*", - methods: ["GET", "POST"], - }, + // Initialize socket.io + initializeSocketIO(server); + + // Start the HTTP server + server.listen(options.port, options.hostname); + console.log( + `${new Date()}: socket-tunnel server started on ${options.hostname}:${ + options.port + }` + ); +}; + +const createTunnelServer = () => { + return http.createServer(async (req, res) => { + try { + await handleTunnelRequest(req, res); + } catch (err) { + console.error(`${new Date()}: Error in tunnel server:`, err); + handleTunnelError(res, err); + } }); +}; - io.on("connection", (socket) => { - console.log("a user connected"); +const handleTunnelRequest = async (req, res) => { + try { + const tunnelClientStream = await getTunnelClientStreamForReq(req); + const reqBodyChunks = []; - socket.on("CreateTunnel", (data, resCallBack) => { - if (!data || !data.subdomain) { - resCallBack?.("Invalid tunnel data"); - return socket.disconnect(); - } - const { subdomain } = data; - let subdomainNormalized = subdomain - .toString() - .toLowerCase() - .replace(/[^0-9a-z-.]/g, ""); - - if (socketsBySubdomain[subdomain]) { - resCallBack?.("Subdomain already exists"); - return socket.disconnect(); + setupRequestErrorHandler(req); + setupRequestDataHandler(req, reqBodyChunks); + setupRequestEndHandler(req, reqBodyChunks, tunnelClientStream); + } catch (err) { + console.error(`${new Date()}: Error handling tunnel request:`, err); + throw err; + } +}; + +const setupRequestErrorHandler = (req) => { + req.on("error", (err) => { + console.error(`${new Date()}: Request error:`, err.stack); + }); +}; + +const setupRequestDataHandler = (req, reqBodyChunks) => { + req.on("data", (bodyChunk) => { + try { + reqBodyChunks.push(bodyChunk); + } catch (err) { + console.error(`${new Date()}: Error handling request data:`, err); + } + }); +}; + +const setupRequestEndHandler = (req, reqBodyChunks, tunnelClientStream) => { + req.on("end", () => { + try { + if (!req.complete) { + console.warn(`${new Date()}: Incomplete request received`); + return; } - socketsBySubdomain[subdomain] = socket; - socket.requestedName = subdomainNormalized; - resCallBack?.("Subdomain created"); - }); - socket.on("disconnect", () => { - console.log("a user disconnected"); - delete socketsBySubdomain[socket.requestedName]; - }); + const reqLine = getReqLineFromReq(req); + const headers = getHeadersFromReq(req); + const reqBody = + reqBodyChunks.length > 0 ? Buffer.concat(reqBodyChunks) : null; + + streamResponse(reqLine, headers, reqBody, tunnelClientStream); + } catch (err) { + console.error(`${new Date()}: Error in request end handler:`, err); + } }); +}; - server.listen(options.port, "0.0.0.0", () => { - console.log( - `${new Date()}: nport server started on 0.0.0.0:${options.port}` - ); +const handleTunnelError = (res, err) => { + console.error(`${new Date()}: Tunnel error:`, err); + res.statusCode = 502; + res.end(err.message); +}; + +const getTunnelClientStreamForReq = async (req) => { + return new Promise((resolve, reject) => { + try { + const hostname = validateHostname(req.headers.host, reject); + let subdomain = getSubdomain(hostname, options.subdomain); + + const subdomainSocket = validateSocket( + subdomain, + socketsBySubdomain, + reject + ); + + if (isExistingValidStream(req, subdomain)) { + return resolve(req.connection.tunnelClientStream); + } + + establishNewStream(req, subdomainSocket, subdomain, resolve); + } catch (err) { + console.error(`${new Date()}: Error getting tunnel client stream:`, err); + reject(err); + } }); }; -const requestListener = async (req, res) => { +const validateHostname = (hostname, reject) => { + if (!hostname) { + console.error(`${new Date()}: Invalid hostname received`); + reject(new Error("Invalid hostname")); + } + return hostname; +}; + +const getSubdomain = (hostname, optionsSubdomain) => { try { - const tunnelClientStream = await getTunnelClientStream(req); - const reqBodyChunks = []; + let subdomain = tldjs.getSubdomain(hostname).toLowerCase(); + if (!subdomain) { + const error = new Error( + "Invalid subdomain" + ); + console.error(`${new Date()}: Invalid subdomain:`, error); + throw error; + } + if (optionsSubdomain) { + subdomain = subdomain.replace(`.${optionsSubdomain}`, ""); + } + return subdomain; + } catch (err) { + console.error(`${new Date()}: Error processing subdomain:`, err); + throw err; + } +}; - req.on("error", (err) => { - console.error(err.stack); - }); +const validateSocket = (subdomain, sockets, reject) => { + const socket = sockets[subdomain]; + if (!socket) { + const error = new Error( + `${subdomain} is currently unregistered or offline.` + ); + console.error(`${new Date()}: Socket validation failed:`, error); + reject(error); + } + return socket; +}; - // collect body chunks - req.on("data", (bodyChunk) => { - reqBodyChunks.push(bodyChunk); - }); +const isExistingValidStream = (req, subdomain) => { + return ( + req.connection.tunnelClientStream !== undefined && + !req.connection.tunnelClientStream.destroyed && + req.connection.subdomain === subdomain + ); +}; - // proxy finalized request to tunnel stream - req.on("end", () => { - // make sure the client didn't die on us - if (req.complete) { - const reqLine = getReqLineFromReq(req); - const headers = getHeadersFromReq(req); - - let reqBody = null; - if (reqBodyChunks.length > 0) { - reqBody = Buffer.concat(reqBodyChunks); - } - streamResponse(reqLine, headers, reqBody, tunnelClientStream); +const establishNewStream = (req, socket, subdomain, resolve) => { + try { + const requestGUID = uuid(); + ss(socket).once(requestGUID, (tunnelClientStream) => { + try { + req.connection.subdomain = subdomain; + req.connection.tunnelClientStream = tunnelClientStream; + tunnelClientStream.pipe(req.connection); + resolve(tunnelClientStream); + } catch (err) { + console.error(`${new Date()}: Error establishing stream:`, err); } }); - } catch (error) { - res.writeHead(500, { "Content-Type": "text/plain" }); - res.end(error.message); + socket.emit("incomingClient", requestGUID); + } catch (err) { + console.error(`${new Date()}: Error setting up stream:`, err); } }; const getReqLineFromReq = (req) => { - return `${req.method} ${req.url} HTTP/${req.httpVersion}`; + try { + return `${req.method} ${req.url} HTTP/${req.httpVersion}`; + } catch (err) { + console.error(`${new Date()}: Error getting request line:`, err); + throw err; + } }; const getHeadersFromReq = (req) => { - const headers = []; - - for (let i = 0; i < req.rawHeaders.length - 1; i += 2) { - headers.push(req.rawHeaders[i] + ": " + req.rawHeaders[i + 1]); + try { + const headers = []; + for (let i = 0; i < req.rawHeaders.length - 1; i += 2) { + headers.push(`${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}`); + } + return headers; + } catch (err) { + console.error(`${new Date()}: Error processing headers:`, err); + throw err; } - - return headers; }; const streamResponse = (reqLine, headers, reqBody, tunnelClientStream) => { - tunnelClientStream.write(reqLine); - tunnelClientStream.write("\r\n"); - tunnelClientStream.write(headers.join("\r\n")); - tunnelClientStream.write("\r\n\r\n"); - if (reqBody) { - console.log("helloooo 6"); - tunnelClientStream.write(reqBody); + try { + tunnelClientStream.write(reqLine); + tunnelClientStream.write("\r\n"); + tunnelClientStream.write(headers.join("\r\n")); + tunnelClientStream.write("\r\n\r\n"); + if (reqBody) { + tunnelClientStream.write(reqBody); + } + } catch (err) { + console.error(`${new Date()}: Error streaming response:`, err); } }; -const getTunnelClientStream = async (req) => { - let hostname = req.headers.host; - if (!hostname) { - throw new Error("Invalid hostname"); +const initializeSocketIO = (server) => { + try { + const io = new SocketIO(server); + io.on("connection", handleSocketConnection); + } catch (err) { + console.error(`${new Date()}: Error initializing Socket.IO:`, err); } +}; - const subdomain = hostname.substring( - 0, - hostname.lastIndexOf(`.${process.env.DOMAIN}`) - ); +const handleSocketConnection = (socket) => { + socket.on("createTunnel", (requestedName, responseCb) => { + try { + handleTunnelCreation(socket, requestedName, responseCb); + } catch (err) { + console.error(`${new Date()}: Error in tunnel creation:`, err); + } + }); + + socket.on("disconnect", () => { + try { + handleSocketDisconnect(socket); + } catch (err) { + console.error(`${new Date()}: Error handling disconnect:`, err); + } + }); +}; + +const handleTunnelCreation = (socket, requestedName, responseCb) => { + try { + if (socket.requestedName) return; + + const reqNameNormalized = normalizeRequestedName(requestedName); + + if (!isValidTunnelName(reqNameNormalized)) { + handleInvalidTunnelName(reqNameNormalized, socket, responseCb); + return; + } + + if (isTunnelNameTaken(reqNameNormalized)) { + handleTakenTunnelName(reqNameNormalized, socket, responseCb); + return; + } - if (!subdomain) { - console.log("Invalid subdomain"); - throw new Error("Invalid subdomain"); + registerTunnel(socket, reqNameNormalized, responseCb); + } catch (err) { + console.error(`${new Date()}: Error handling tunnel creation:`, err); } +}; - let subdomainSocket = socketsBySubdomain[subdomain]; - if (!subdomainSocket) { - throw new Error(`${subdomain} is currently unregistered or offline.`); +const normalizeRequestedName = (name) => { + try { + return name + .toString() + .toLowerCase() + .replace(/[^0-9a-z-.]/g, ""); + } catch (err) { + console.error(`${new Date()}: Error normalizing name:`, err); + throw err; } +}; - if ( - req.connection.tunnelClientStream !== undefined && - !req.connection.tunnelClientStream.destroyed && - req.connection.subdomain === subdomain - ) { - return req.connection.tunnelClientStream; +const isValidTunnelName = (name) => { + try { + return name.length > 0 && isValidDomain(`${name}.example.com`); + } catch (err) { + console.error(`${new Date()}: Error validating tunnel name:`, err); + return false; } +}; - const clientId = uuidv4(); - - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - reject(new Error("Tunnel stream connection timeout")); - }, 5000); // 5 second timeout +const isTunnelNameTaken = (name) => { + try { + return !!socketsBySubdomain[name]; + } catch (err) { + console.error( + `${new Date()}: Error checking tunnel name availability:`, + err + ); + return true; + } +}; - try { - // First emit the incoming client event - subdomainSocket.emit("IncomingClient", clientId); +const handleInvalidTunnelName = (name, socket, responseCb) => { + try { + console.log( + `${new Date()}: ${name} -- bad subdomain. Disconnecting client.` + ); + if (responseCb) responseCb("bad subdomain"); + socket.disconnect(); + } catch (err) { + console.error(`${new Date()}: Error handling invalid tunnel name:`, err); + } +}; - // Then wait for the stream - ss(subdomainSocket).once(clientId, (tunnelClientStream) => { - clearTimeout(timeout); - req.connection.subdomain = subdomain; - req.connection.tunnelClientStream = tunnelClientStream; - - // Handle stream errors - tunnelClientStream.on('error', (err) => { - console.error('Stream error:', err); - tunnelClientStream.destroy(); - }); +const handleTakenTunnelName = (name, socket, responseCb) => { + try { + console.log( + `${new Date()}: ${name} requested but already claimed. Disconnecting client.` + ); + if (responseCb) responseCb("subdomain already claimed"); + socket.disconnect(); + } catch (err) { + console.error(`${new Date()}: Error handling taken tunnel name:`, err); + } +}; - tunnelClientStream.pipe(req.connection); - resolve(tunnelClientStream); - }); - } catch (error) { - clearTimeout(timeout); - console.error("Tunnel stream error:", error); - reject(error); - } - }); +const registerTunnel = (socket, name, responseCb) => { + try { + socketsBySubdomain[name] = socket; + socket.requestedName = name; + console.log(`${new Date()}: ${name} registered successfully`); + if (responseCb) responseCb(null); + } catch (err) { + console.error(`${new Date()}: Error registering tunnel:`, err); + } }; -export default server; +const handleSocketDisconnect = (socket) => { + try { + if (socket.requestedName) { + delete socketsBySubdomain[socket.requestedName]; + console.log(`${new Date()}: ${socket.requestedName} unregistered`); + } + } catch (err) { + console.error(`${new Date()}: Error handling socket disconnect:`, err); + } +}; From fa683253130c7dded8276f0fdc945c8e5651968a Mon Sep 17 00:00:00 2001 From: Nick - Ngoc Pham Date: Wed, 25 Dec 2024 12:27:10 +0700 Subject: [PATCH 10/21] refactor: streamline socket handling and request processing in client and server services - Replaced the custom socket initialization with the socket.io client directly in the client service. - Simplified error handling and request processing in the server service by removing unnecessary try-catch blocks. - Enhanced the setup of incoming client connections and tunnel requests for better readability and maintainability. - Improved logging for connection and error events to provide clearer insights during runtime. --- src/client/services/client.service.mjs | 171 ++++++----------- src/server/services/server.service.mjs | 246 +++++++++---------------- 2 files changed, 136 insertions(+), 281 deletions(-) diff --git a/src/client/services/client.service.mjs b/src/client/services/client.service.mjs index ef86468..2f4fb16 100644 --- a/src/client/services/client.service.mjs +++ b/src/client/services/client.service.mjs @@ -1,5 +1,3 @@ -"use strict"; - // Constants const IDLE_SOCKET_TIMEOUT_MILLISECONDS = 1000 * 30; @@ -12,7 +10,7 @@ import { io as clientIo } from "socket.io-client"; export default (options) => { return new Promise((resolve, reject) => { try { - const socket = initializeSocketClient(options.server); + const socket = clientIo(options.server); setupSocketHandlers(socket, options, resolve, reject); } catch (err) { console.error(`${new Date()}: Error initializing client:`, err); @@ -21,21 +19,13 @@ export default (options) => { }); }; -const initializeSocketClient = (serverUrl) => { - try { - return clientIo(serverUrl); - } catch (err) { - console.error(`${new Date()}: Error initializing socket client:`, err); - throw err; - } -}; - const setupSocketHandlers = (socket, options, resolve, reject) => { try { socket.on("connect", () => handleConnect(socket, options, resolve, reject)); - socket.on("incomingClient", (clientId) => - handleIncomingClient(socket, clientId, options) - ); + socket.on("incomingClient", (clientId) => { + const client = createTcpConnection(options); + setupClientHandlers(client, socket, clientId); + }); socket.on("error", (err) => { console.error(`${new Date()}: Socket error:`, err); reject(err); @@ -48,25 +38,15 @@ const setupSocketHandlers = (socket, options, resolve, reject) => { const handleConnect = (socket, options, resolve, reject) => { try { - logConnectionStatus(options); - requestTunnel(socket, options, resolve, reject); - } catch (err) { - console.error(`${new Date()}: Error handling connect:`, err); - reject(err); - } -}; - -const logConnectionStatus = (options) => { - try { - console.log(`${new Date()}: connected`); console.log( `${new Date()}: requesting subdomain ${options.subdomain} via ${ options.server }` ); + requestTunnel(socket, options, resolve, reject); } catch (err) { - console.error(`${new Date()}: Error logging connection status:`, err); - throw err; + console.error(`${new Date()}: Error handling connect:`, err); + reject(err); } }; @@ -74,9 +54,16 @@ const requestTunnel = (socket, options, resolve, reject) => { try { socket.emit("createTunnel", options.subdomain, (err) => { if (err) { - handleTunnelError(err, reject); + console.error(`${new Date()}: Tunnel error:`, err); + reject(err); } else { - handleTunnelSuccess(options, resolve); + console.log(`${new Date()}: registered with server successfully`); + console.log( + `${new Date()}: your domain is: https://${ + options.subdomain + }.nport.link` + ); + resolve(constructUrl(options)); } }); } catch (err) { @@ -85,103 +72,62 @@ const requestTunnel = (socket, options, resolve, reject) => { } }; -const handleTunnelError = (err, reject) => { - console.error(`${new Date()}: Tunnel error:`, err); - reject(err); -}; - -const handleTunnelSuccess = (options, resolve) => { - try { - console.log(`${new Date()}: registered with server successfully`); - console.log( - `${new Date()}: your domain is: https://${options.subdomain}.nport.link` - ); - resolve(constructUrl(options)); - } catch (err) { - console.error(`${new Date()}: Error handling tunnel success:`, err); - throw err; - } -}; - const constructUrl = (options) => { - try { - const subdomain = options.subdomain.toString(); - const server = options.server.toString(); - - if (server.includes("https://")) { - return `https://${subdomain}.${server.slice(8)}`; - } else if (server.includes("http://")) { - return `http://${subdomain}.${server.slice(7)}`; - } - return `https://${subdomain}.${server}`; - } catch (err) { - console.error(`${new Date()}: Error constructing URL:`, err); - throw err; - } -}; + const subdomain = options.subdomain.toString(); + const server = options.server.toString(); -const handleIncomingClient = (socket, clientId, options) => { - try { - const client = createTcpConnection(options); - setupClientHandlers(client, socket, clientId); - } catch (err) { - console.error(`${new Date()}: Error handling incoming client:`, err); + if (server.includes("https://")) { + return `https://${subdomain}.${server.slice(8)}`; + } else if (server.includes("http://")) { + return `http://${subdomain}.${server.slice(7)}`; } + return `https://${subdomain}.${server}`; }; const createTcpConnection = (options) => { - try { - const client = net.connect({ - port: options.port, - host: options.hostname, - timeout: IDLE_SOCKET_TIMEOUT_MILLISECONDS - }); - return client; - } catch (err) { - console.error(`${new Date()}: Error creating TCP connection:`, err); - throw err; - } + const client = net.connect({ + port: options.port, + host: options.hostname, + timeout: IDLE_SOCKET_TIMEOUT_MILLISECONDS, + }); + return client; }; const setupClientHandlers = (client, socket, clientId) => { - try { - client.once('connect', () => { - handleClientConnect(client, socket, clientId); - }); + client.once("connect", () => { + handleClientConnect(client, socket, clientId); + }); - client.once('error', (err) => { - console.error(`${new Date()}: Client error:`, err); - handleClientError(socket, clientId); - }); + client.once("error", (err) => { + console.error(`${new Date()}: Client error:`, err); + handleClientError(socket, clientId); + }); - client.once('timeout', () => { - console.log(`${new Date()}: Client connection timed out`); - client.end(); - }); - } catch (err) { - console.error(`${new Date()}: Error setting up client handlers:`, err); - } + client.once("timeout", () => { + console.log(`${new Date()}: Client connection timed out`); + client.end(); + }); }; const handleClientConnect = (client, socket, clientId) => { try { const stream = ss.createStream(); - - stream.once('error', (err) => { + + stream.once("error", (err) => { console.error(`${new Date()}: Stream error:`, err); cleanupConnection(client, stream); }); - client.once('error', (err) => { + client.once("error", (err) => { console.error(`${new Date()}: Client error in stream:`, err); cleanupConnection(client, stream); }); - stream.once('end', () => { + stream.once("end", () => { cleanupConnection(client, stream); }); - client.once('end', () => { + client.once("end", () => { cleanupConnection(client, stream); }); @@ -190,7 +136,6 @@ const handleClientConnect = (client, socket, clientId) => { // Setup piping client.pipe(stream).pipe(client); - } catch (err) { console.error(`${new Date()}: Error in handleClientConnect:`, err); if (client && !client.destroyed) { @@ -200,24 +145,16 @@ const handleClientConnect = (client, socket, clientId) => { }; const cleanupConnection = (client, stream) => { - try { - if (stream && !stream.destroyed) { - stream.destroy(); - } - if (client && !client.destroyed) { - client.destroy(); - } - } catch (err) { - console.error(`${new Date()}: Error in cleanup:`, err); + if (stream && !stream.destroyed) { + stream.destroy(); + } + if (client && !client.destroyed) { + client.destroy(); } }; const handleClientError = (socket, clientId) => { - try { - const stream = ss.createStream(); - ss(socket).emit(clientId, stream); - stream.end(); - } catch (err) { - console.error(`${new Date()}: Error handling client error:`, err); - } + const stream = ss.createStream(); + ss(socket).emit(clientId, stream); + stream.end(); }; diff --git a/src/server/services/server.service.mjs b/src/server/services/server.service.mjs index 831d42a..3c09499 100644 --- a/src/server/services/server.service.mjs +++ b/src/server/services/server.service.mjs @@ -40,17 +40,12 @@ const createTunnelServer = () => { }; const handleTunnelRequest = async (req, res) => { - try { - const tunnelClientStream = await getTunnelClientStreamForReq(req); - const reqBodyChunks = []; + const tunnelClientStream = await getTunnelClientStreamForReq(req); + const reqBodyChunks = []; - setupRequestErrorHandler(req); - setupRequestDataHandler(req, reqBodyChunks); - setupRequestEndHandler(req, reqBodyChunks, tunnelClientStream); - } catch (err) { - console.error(`${new Date()}: Error handling tunnel request:`, err); - throw err; - } + setupRequestErrorHandler(req); + setupRequestDataHandler(req, reqBodyChunks); + setupRequestEndHandler(req, reqBodyChunks, tunnelClientStream); }; const setupRequestErrorHandler = (req) => { @@ -61,31 +56,23 @@ const setupRequestErrorHandler = (req) => { const setupRequestDataHandler = (req, reqBodyChunks) => { req.on("data", (bodyChunk) => { - try { - reqBodyChunks.push(bodyChunk); - } catch (err) { - console.error(`${new Date()}: Error handling request data:`, err); - } + reqBodyChunks.push(bodyChunk); }); }; const setupRequestEndHandler = (req, reqBodyChunks, tunnelClientStream) => { req.on("end", () => { - try { - if (!req.complete) { - console.warn(`${new Date()}: Incomplete request received`); - return; - } + if (!req.complete) { + console.warn(`${new Date()}: Incomplete request received`); + return; + } - const reqLine = getReqLineFromReq(req); - const headers = getHeadersFromReq(req); - const reqBody = - reqBodyChunks.length > 0 ? Buffer.concat(reqBodyChunks) : null; + const reqLine = getReqLineFromReq(req); + const headers = getHeadersFromReq(req); + const reqBody = + reqBodyChunks.length > 0 ? Buffer.concat(reqBodyChunks) : null; - streamResponse(reqLine, headers, reqBody, tunnelClientStream); - } catch (err) { - console.error(`${new Date()}: Error in request end handler:`, err); - } + streamResponse(reqLine, headers, reqBody, tunnelClientStream); }); }; @@ -128,23 +115,16 @@ const validateHostname = (hostname, reject) => { }; const getSubdomain = (hostname, optionsSubdomain) => { - try { - let subdomain = tldjs.getSubdomain(hostname).toLowerCase(); - if (!subdomain) { - const error = new Error( - "Invalid subdomain" - ); - console.error(`${new Date()}: Invalid subdomain:`, error); - throw error; - } - if (optionsSubdomain) { - subdomain = subdomain.replace(`.${optionsSubdomain}`, ""); - } - return subdomain; - } catch (err) { - console.error(`${new Date()}: Error processing subdomain:`, err); - throw err; + let subdomain = tldjs.getSubdomain(hostname).toLowerCase(); + if (!subdomain) { + const error = new Error("Invalid subdomain"); + console.error(`${new Date()}: Invalid subdomain:`, error); + throw error; } + if (optionsSubdomain) { + subdomain = subdomain.replace(`.${optionsSubdomain}`, ""); + } + return subdomain; }; const validateSocket = (subdomain, sockets, reject) => { @@ -168,128 +148,84 @@ const isExistingValidStream = (req, subdomain) => { }; const establishNewStream = (req, socket, subdomain, resolve) => { - try { - const requestGUID = uuid(); - ss(socket).once(requestGUID, (tunnelClientStream) => { - try { - req.connection.subdomain = subdomain; - req.connection.tunnelClientStream = tunnelClientStream; - tunnelClientStream.pipe(req.connection); - resolve(tunnelClientStream); - } catch (err) { - console.error(`${new Date()}: Error establishing stream:`, err); - } - }); - socket.emit("incomingClient", requestGUID); - } catch (err) { - console.error(`${new Date()}: Error setting up stream:`, err); - } + const requestGUID = uuid(); + ss(socket).once(requestGUID, (tunnelClientStream) => { + try { + req.connection.subdomain = subdomain; + req.connection.tunnelClientStream = tunnelClientStream; + tunnelClientStream.pipe(req.connection); + resolve(tunnelClientStream); + } catch (err) { + console.error(`${new Date()}: Error establishing stream:`, err); + } + }); + socket.emit("incomingClient", requestGUID); }; const getReqLineFromReq = (req) => { - try { - return `${req.method} ${req.url} HTTP/${req.httpVersion}`; - } catch (err) { - console.error(`${new Date()}: Error getting request line:`, err); - throw err; - } + return `${req.method} ${req.url} HTTP/${req.httpVersion}`; }; const getHeadersFromReq = (req) => { - try { - const headers = []; - for (let i = 0; i < req.rawHeaders.length - 1; i += 2) { - headers.push(`${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}`); - } - return headers; - } catch (err) { - console.error(`${new Date()}: Error processing headers:`, err); - throw err; + const headers = []; + for (let i = 0; i < req.rawHeaders.length - 1; i += 2) { + headers.push(`${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}`); } + return headers; }; const streamResponse = (reqLine, headers, reqBody, tunnelClientStream) => { - try { - tunnelClientStream.write(reqLine); - tunnelClientStream.write("\r\n"); - tunnelClientStream.write(headers.join("\r\n")); - tunnelClientStream.write("\r\n\r\n"); - if (reqBody) { - tunnelClientStream.write(reqBody); - } - } catch (err) { - console.error(`${new Date()}: Error streaming response:`, err); + tunnelClientStream.write(reqLine); + tunnelClientStream.write("\r\n"); + tunnelClientStream.write(headers.join("\r\n")); + tunnelClientStream.write("\r\n\r\n"); + if (reqBody) { + tunnelClientStream.write(reqBody); } }; const initializeSocketIO = (server) => { - try { - const io = new SocketIO(server); - io.on("connection", handleSocketConnection); - } catch (err) { - console.error(`${new Date()}: Error initializing Socket.IO:`, err); - } + const io = new SocketIO(server); + io.on("connection", handleSocketConnection); }; const handleSocketConnection = (socket) => { socket.on("createTunnel", (requestedName, responseCb) => { - try { - handleTunnelCreation(socket, requestedName, responseCb); - } catch (err) { - console.error(`${new Date()}: Error in tunnel creation:`, err); - } + handleTunnelCreation(socket, requestedName, responseCb); }); socket.on("disconnect", () => { - try { - handleSocketDisconnect(socket); - } catch (err) { - console.error(`${new Date()}: Error handling disconnect:`, err); - } + handleSocketDisconnect(socket); }); }; const handleTunnelCreation = (socket, requestedName, responseCb) => { - try { - if (socket.requestedName) return; - - const reqNameNormalized = normalizeRequestedName(requestedName); + if (socket.requestedName) return; - if (!isValidTunnelName(reqNameNormalized)) { - handleInvalidTunnelName(reqNameNormalized, socket, responseCb); - return; - } + const reqNameNormalized = normalizeRequestedName(requestedName); - if (isTunnelNameTaken(reqNameNormalized)) { - handleTakenTunnelName(reqNameNormalized, socket, responseCb); - return; - } + if (!isValidTunnelName(reqNameNormalized)) { + handleInvalidTunnelName(reqNameNormalized, socket, responseCb); + return; + } - registerTunnel(socket, reqNameNormalized, responseCb); - } catch (err) { - console.error(`${new Date()}: Error handling tunnel creation:`, err); + if (isTunnelNameTaken(reqNameNormalized)) { + handleTakenTunnelName(reqNameNormalized, socket, responseCb); + return; } + + registerTunnel(socket, reqNameNormalized, responseCb); }; const normalizeRequestedName = (name) => { - try { - return name - .toString() - .toLowerCase() - .replace(/[^0-9a-z-.]/g, ""); - } catch (err) { - console.error(`${new Date()}: Error normalizing name:`, err); - throw err; - } + return name + .toString() + .toLowerCase() + .replace(/[^0-9a-z-.]/g, ""); }; const isValidTunnelName = (name) => { - try { - return name.length > 0 && isValidDomain(`${name}.example.com`); - } catch (err) { - console.error(`${new Date()}: Error validating tunnel name:`, err); - return false; - } + return name.length > 0 && isValidDomain(`${name}.example.com`); }; const isTunnelNameTaken = (name) => { @@ -305,47 +241,29 @@ const isTunnelNameTaken = (name) => { }; const handleInvalidTunnelName = (name, socket, responseCb) => { - try { - console.log( - `${new Date()}: ${name} -- bad subdomain. Disconnecting client.` - ); - if (responseCb) responseCb("bad subdomain"); - socket.disconnect(); - } catch (err) { - console.error(`${new Date()}: Error handling invalid tunnel name:`, err); - } + console.log(`${new Date()}: ${name} -- bad subdomain. Disconnecting client.`); + if (responseCb) responseCb("bad subdomain"); + socket.disconnect(); }; const handleTakenTunnelName = (name, socket, responseCb) => { - try { - console.log( - `${new Date()}: ${name} requested but already claimed. Disconnecting client.` - ); - if (responseCb) responseCb("subdomain already claimed"); - socket.disconnect(); - } catch (err) { - console.error(`${new Date()}: Error handling taken tunnel name:`, err); - } + console.log( + `${new Date()}: ${name} requested but already claimed. Disconnecting client.` + ); + if (responseCb) responseCb("subdomain already claimed"); + socket.disconnect(); }; const registerTunnel = (socket, name, responseCb) => { - try { - socketsBySubdomain[name] = socket; - socket.requestedName = name; - console.log(`${new Date()}: ${name} registered successfully`); - if (responseCb) responseCb(null); - } catch (err) { - console.error(`${new Date()}: Error registering tunnel:`, err); - } + socketsBySubdomain[name] = socket; + socket.requestedName = name; + console.log(`${new Date()}: ${name} registered successfully`); + if (responseCb) responseCb(null); }; const handleSocketDisconnect = (socket) => { - try { - if (socket.requestedName) { - delete socketsBySubdomain[socket.requestedName]; - console.log(`${new Date()}: ${socket.requestedName} unregistered`); - } - } catch (err) { - console.error(`${new Date()}: Error handling socket disconnect:`, err); + if (socket.requestedName) { + delete socketsBySubdomain[socket.requestedName]; + console.log(`${new Date()}: ${socket.requestedName} unregistered`); } }; From 89f8a888ae98334d6cfbaf62c270746f83bec0a2 Mon Sep 17 00:00:00 2001 From: Nick - Ngoc Pham Date: Wed, 25 Dec 2024 12:29:50 +0700 Subject: [PATCH 11/21] refactor: enhance socket connection handling and improve request processing in client and server services - Renamed and reorganized functions for better clarity and maintainability in the client service. - Improved error logging and streamlined socket event handling in both client and server services. - Updated HTTP request handling in the server service to enhance readability and efficiency. - Added detailed comments and documentation for key functions to facilitate future development. --- src/client/services/client.service.mjs | 175 +++++++++------ src/server/services/server.service.mjs | 282 ++++++++++--------------- 2 files changed, 226 insertions(+), 231 deletions(-) diff --git a/src/client/services/client.service.mjs b/src/client/services/client.service.mjs index 2f4fb16..db35113 100644 --- a/src/client/services/client.service.mjs +++ b/src/client/services/client.service.mjs @@ -6,73 +6,78 @@ import net from "net"; import ss from "socket.io-stream"; import { io as clientIo } from "socket.io-client"; -// Export the main function as default +/** + * Creates and initializes a socket tunnel client + * @param {Object} options Configuration options + * @returns {Promise} The constructed tunnel URL + */ export default (options) => { return new Promise((resolve, reject) => { try { const socket = clientIo(options.server); - setupSocketHandlers(socket, options, resolve, reject); + initializeSocketConnection(socket, options, resolve, reject); } catch (err) { - console.error(`${new Date()}: Error initializing client:`, err); + logError("Error initializing client", err); reject(err); } }); }; -const setupSocketHandlers = (socket, options, resolve, reject) => { +/** + * Sets up the main socket connection and event handlers + */ +const initializeSocketConnection = (socket, options, resolve, reject) => { try { - socket.on("connect", () => handleConnect(socket, options, resolve, reject)); - socket.on("incomingClient", (clientId) => { - const client = createTcpConnection(options); - setupClientHandlers(client, socket, clientId); - }); + socket.on("connect", () => onSocketConnect(socket, options, resolve, reject)); + socket.on("incomingClient", (clientId) => handleIncomingClient(socket, options, clientId)); socket.on("error", (err) => { - console.error(`${new Date()}: Socket error:`, err); + logError("Socket error", err); reject(err); }); } catch (err) { - console.error(`${new Date()}: Error setting up socket handlers:`, err); + logError("Error setting up socket handlers", err); reject(err); } }; -const handleConnect = (socket, options, resolve, reject) => { +/** + * Handles successful socket connection + */ +const onSocketConnect = (socket, options, resolve, reject) => { try { - console.log( - `${new Date()}: requesting subdomain ${options.subdomain} via ${ - options.server - }` - ); - requestTunnel(socket, options, resolve, reject); + logInfo(`Requesting subdomain ${options.subdomain} via ${options.server}`); + establishTunnel(socket, options, resolve, reject); } catch (err) { - console.error(`${new Date()}: Error handling connect:`, err); + logError("Error handling connect", err); reject(err); } }; -const requestTunnel = (socket, options, resolve, reject) => { +/** + * Requests tunnel creation from server + */ +const establishTunnel = (socket, options, resolve, reject) => { try { socket.emit("createTunnel", options.subdomain, (err) => { if (err) { - console.error(`${new Date()}: Tunnel error:`, err); + logError("Tunnel error", err); reject(err); } else { - console.log(`${new Date()}: registered with server successfully`); - console.log( - `${new Date()}: your domain is: https://${ - options.subdomain - }.nport.link` - ); - resolve(constructUrl(options)); + logInfo("Registered with server successfully"); + logInfo(`Your domain is: https://${options.subdomain}.nport.link`); + resolve(buildTunnelUrl(options)); } }); } catch (err) { - console.error(`${new Date()}: Error requesting tunnel:`, err); + logError("Error requesting tunnel", err); reject(err); } }; -const constructUrl = (options) => { +/** + * Constructs the tunnel URL based on server protocol + */ +const buildTunnelUrl = (options) => { const subdomain = options.subdomain.toString(); const server = options.server.toString(); @@ -84,77 +89,115 @@ const constructUrl = (options) => { return `https://${subdomain}.${server}`; }; +/** + * Creates a TCP connection to the target service + */ const createTcpConnection = (options) => { - const client = net.connect({ + return net.connect({ port: options.port, host: options.hostname, timeout: IDLE_SOCKET_TIMEOUT_MILLISECONDS, }); - return client; }; -const setupClientHandlers = (client, socket, clientId) => { +/** + * Handles incoming client connections + */ +const handleIncomingClient = (socket, options, clientId) => { + const client = createTcpConnection(options); + setupClientEventHandlers(client, socket, clientId); +}; + +/** + * Sets up TCP client event handlers + */ +const setupClientEventHandlers = (client, socket, clientId) => { client.once("connect", () => { - handleClientConnect(client, socket, clientId); + setupStreamConnection(client, socket, clientId); }); client.once("error", (err) => { - console.error(`${new Date()}: Client error:`, err); - handleClientError(socket, clientId); + logError("Client error", err); + handleClientFailure(socket, clientId); }); client.once("timeout", () => { - console.log(`${new Date()}: Client connection timed out`); + logInfo("Client connection timed out"); client.end(); }); }; -const handleClientConnect = (client, socket, clientId) => { +/** + * Sets up bidirectional stream between client and tunnel + */ +const setupStreamConnection = (client, socket, clientId) => { try { const stream = ss.createStream(); - - stream.once("error", (err) => { - console.error(`${new Date()}: Stream error:`, err); - cleanupConnection(client, stream); - }); - - client.once("error", (err) => { - console.error(`${new Date()}: Client error in stream:`, err); - cleanupConnection(client, stream); - }); - - stream.once("end", () => { - cleanupConnection(client, stream); - }); - - client.once("end", () => { - cleanupConnection(client, stream); - }); - - // Emit the stream through socket.io-stream + setupStreamEventHandlers(stream, client); + + // Connect stream to socket.io tunnel ss(socket).emit(clientId, stream); - - // Setup piping + + // Enable bidirectional data flow client.pipe(stream).pipe(client); } catch (err) { - console.error(`${new Date()}: Error in handleClientConnect:`, err); - if (client && !client.destroyed) { - client.destroy(); - } + logError("Error in stream setup", err); + safelyDestroyClient(client); } }; -const cleanupConnection = (client, stream) => { +/** + * Sets up stream event handlers + */ +const setupStreamEventHandlers = (stream, client) => { + stream.once("error", (err) => { + logError("Stream error", err); + cleanup(client, stream); + }); + + client.once("error", (err) => { + logError("Client error in stream", err); + cleanup(client, stream); + }); + + stream.once("end", () => cleanup(client, stream)); + client.once("end", () => cleanup(client, stream)); +}; + +/** + * Safely cleans up resources + */ +const cleanup = (client, stream) => { + safelyDestroyStream(stream); + safelyDestroyClient(client); +}; + +const safelyDestroyStream = (stream) => { if (stream && !stream.destroyed) { stream.destroy(); } +}; + +const safelyDestroyClient = (client) => { if (client && !client.destroyed) { client.destroy(); } }; -const handleClientError = (socket, clientId) => { +/** + * Handles client connection failures + */ +const handleClientFailure = (socket, clientId) => { const stream = ss.createStream(); ss(socket).emit(clientId, stream); stream.end(); }; + +// Logging helpers +const logError = (message, error) => { + console.error(`${new Date()}: ${message}:`, error); +}; + +const logInfo = (message) => { + console.log(`${new Date()}: ${message}`); +}; diff --git a/src/server/services/server.service.mjs b/src/server/services/server.service.mjs index 3c09499..3beabef 100644 --- a/src/server/services/server.service.mjs +++ b/src/server/services/server.service.mjs @@ -5,223 +5,185 @@ import ss from "socket.io-stream"; import { v4 as uuid } from "uuid"; import isValidDomain from "is-valid-domain"; import { Server as SocketIO } from "socket.io"; + +// Global state const socketsBySubdomain = {}; -let options = {}; +let serverOptions = {}; -// Export the main function as default +// Main server initialization export default ({ port, hostname, subdomain }) => { - options = { port, hostname, subdomain }; - // Association between subdomains and socket.io sockets - - // Create HTTP server to handle tunnel requests - const server = createTunnelServer(); - - // Initialize socket.io - initializeSocketIO(server); - - // Start the HTTP server - server.listen(options.port, options.hostname); - console.log( - `${new Date()}: socket-tunnel server started on ${options.hostname}:${ - options.port - }` - ); + serverOptions = { port, hostname, subdomain }; + const server = createHttpServer(); + initializeSocketServer(server); + startServer(server); }; -const createTunnelServer = () => { +// HTTP Server Setup +const createHttpServer = () => { return http.createServer(async (req, res) => { try { - await handleTunnelRequest(req, res); + await handleHttpRequest(req, res); } catch (err) { - console.error(`${new Date()}: Error in tunnel server:`, err); - handleTunnelError(res, err); + handleServerError(res, err); } }); }; -const handleTunnelRequest = async (req, res) => { - const tunnelClientStream = await getTunnelClientStreamForReq(req); - const reqBodyChunks = []; +const startServer = (server) => { + server.listen(serverOptions.port, serverOptions.hostname); + console.log( + `${new Date()}: socket-tunnel server started on ${serverOptions.hostname}:${ + serverOptions.port + }` + ); +}; - setupRequestErrorHandler(req); - setupRequestDataHandler(req, reqBodyChunks); - setupRequestEndHandler(req, reqBodyChunks, tunnelClientStream); +// HTTP Request Handling +const handleHttpRequest = async (req, res) => { + const tunnelStream = await setupTunnelStream(req); + const requestBody = await collectRequestBody(req); + forwardRequestToTunnel(req, requestBody, tunnelStream); }; -const setupRequestErrorHandler = (req) => { +const collectRequestBody = async (req) => { + const bodyChunks = []; + req.on("error", (err) => { console.error(`${new Date()}: Request error:`, err.stack); }); -}; -const setupRequestDataHandler = (req, reqBodyChunks) => { - req.on("data", (bodyChunk) => { - reqBodyChunks.push(bodyChunk); + req.on("data", (chunk) => bodyChunks.push(chunk)); + + return new Promise((resolve) => { + req.on("end", () => { + if (!req.complete) { + console.warn(`${new Date()}: Incomplete request received`); + resolve(null); + return; + } + resolve(bodyChunks.length > 0 ? Buffer.concat(bodyChunks) : null); + }); }); }; -const setupRequestEndHandler = (req, reqBodyChunks, tunnelClientStream) => { - req.on("end", () => { - if (!req.complete) { - console.warn(`${new Date()}: Incomplete request received`); - return; - } - - const reqLine = getReqLineFromReq(req); - const headers = getHeadersFromReq(req); - const reqBody = - reqBodyChunks.length > 0 ? Buffer.concat(reqBodyChunks) : null; +const forwardRequestToTunnel = (req, body, tunnelStream) => { + // Format request line and headers + const requestLine = `${req.method} ${req.url} HTTP/${req.httpVersion}`; + const headers = formatHeaders(req.rawHeaders); - streamResponse(reqLine, headers, reqBody, tunnelClientStream); - }); + // Write request to tunnel + tunnelStream.write(requestLine + "\r\n"); + tunnelStream.write(headers.join("\r\n") + "\r\n\r\n"); + if (body) { + tunnelStream.write(body); + } }; -const handleTunnelError = (res, err) => { - console.error(`${new Date()}: Tunnel error:`, err); - res.statusCode = 502; - res.end(err.message); +const formatHeaders = (rawHeaders) => { + const headers = []; + for (let i = 0; i < rawHeaders.length - 1; i += 2) { + headers.push(`${rawHeaders[i]}: ${rawHeaders[i + 1]}`); + } + return headers; }; -const getTunnelClientStreamForReq = async (req) => { +// Tunnel Stream Setup +const setupTunnelStream = async (req) => { return new Promise((resolve, reject) => { try { - const hostname = validateHostname(req.headers.host, reject); - let subdomain = getSubdomain(hostname, options.subdomain); + // Validate request + const hostname = req.headers.host || reject(new Error("Invalid hostname")); + const subdomain = extractSubdomain(hostname); + const socket = getSocketForSubdomain(subdomain); - const subdomainSocket = validateSocket( - subdomain, - socketsBySubdomain, - reject - ); - - if (isExistingValidStream(req, subdomain)) { + // Reuse existing stream if valid + if (hasValidExistingStream(req, subdomain)) { return resolve(req.connection.tunnelClientStream); } - establishNewStream(req, subdomainSocket, subdomain, resolve); + // Create new stream + createNewTunnelStream(req, socket, subdomain, resolve); } catch (err) { - console.error(`${new Date()}: Error getting tunnel client stream:`, err); + console.error(`${new Date()}: Tunnel stream setup error:`, err); reject(err); } }); }; -const validateHostname = (hostname, reject) => { - if (!hostname) { - console.error(`${new Date()}: Invalid hostname received`); - reject(new Error("Invalid hostname")); - } - return hostname; -}; - -const getSubdomain = (hostname, optionsSubdomain) => { +const extractSubdomain = (hostname) => { let subdomain = tldjs.getSubdomain(hostname).toLowerCase(); if (!subdomain) { - const error = new Error("Invalid subdomain"); - console.error(`${new Date()}: Invalid subdomain:`, error); - throw error; + throw new Error("Invalid subdomain"); } - if (optionsSubdomain) { - subdomain = subdomain.replace(`.${optionsSubdomain}`, ""); + if (serverOptions.subdomain) { + subdomain = subdomain.replace(`.${serverOptions.subdomain}`, ""); } return subdomain; }; -const validateSocket = (subdomain, sockets, reject) => { - const socket = sockets[subdomain]; +const getSocketForSubdomain = (subdomain) => { + const socket = socketsBySubdomain[subdomain]; if (!socket) { - const error = new Error( - `${subdomain} is currently unregistered or offline.` - ); - console.error(`${new Date()}: Socket validation failed:`, error); - reject(error); + throw new Error(`${subdomain} is currently unregistered or offline.`); } return socket; }; -const isExistingValidStream = (req, subdomain) => { - return ( - req.connection.tunnelClientStream !== undefined && - !req.connection.tunnelClientStream.destroyed && - req.connection.subdomain === subdomain - ); +const hasValidExistingStream = (req, subdomain) => { + const stream = req.connection.tunnelClientStream; + return stream && !stream.destroyed && req.connection.subdomain === subdomain; }; -const establishNewStream = (req, socket, subdomain, resolve) => { - const requestGUID = uuid(); - ss(socket).once(requestGUID, (tunnelClientStream) => { +const createNewTunnelStream = (req, socket, subdomain, resolve) => { + const streamId = uuid(); + ss(socket).once(streamId, (tunnelStream) => { try { req.connection.subdomain = subdomain; - req.connection.tunnelClientStream = tunnelClientStream; - tunnelClientStream.pipe(req.connection); - resolve(tunnelClientStream); + req.connection.tunnelClientStream = tunnelStream; + tunnelStream.pipe(req.connection); + resolve(tunnelStream); } catch (err) { - console.error(`${new Date()}: Error establishing stream:`, err); + console.error(`${new Date()}: Stream creation error:`, err); } }); - socket.emit("incomingClient", requestGUID); -}; - -const getReqLineFromReq = (req) => { - return `${req.method} ${req.url} HTTP/${req.httpVersion}`; + socket.emit("incomingClient", streamId); }; -const getHeadersFromReq = (req) => { - const headers = []; - for (let i = 0; i < req.rawHeaders.length - 1; i += 2) { - headers.push(`${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}`); - } - return headers; -}; - -const streamResponse = (reqLine, headers, reqBody, tunnelClientStream) => { - tunnelClientStream.write(reqLine); - tunnelClientStream.write("\r\n"); - tunnelClientStream.write(headers.join("\r\n")); - tunnelClientStream.write("\r\n\r\n"); - if (reqBody) { - tunnelClientStream.write(reqBody); - } -}; - -const initializeSocketIO = (server) => { - const io = new SocketIO(server); - io.on("connection", handleSocketConnection); -}; +// Socket.IO Server Setup +const initializeSocketServer = (httpServer) => { + const io = new SocketIO(httpServer); + io.on("connection", (socket) => { + socket.on("createTunnel", (name, callback) => { + handleTunnelRegistration(socket, name, callback); + }); -const handleSocketConnection = (socket) => { - socket.on("createTunnel", (requestedName, responseCb) => { - handleTunnelCreation(socket, requestedName, responseCb); - }); - - socket.on("disconnect", () => { - handleSocketDisconnect(socket); + socket.on("disconnect", () => { + handleTunnelDeregistration(socket); + }); }); }; -const handleTunnelCreation = (socket, requestedName, responseCb) => { +// Tunnel Registration +const handleTunnelRegistration = (socket, requestedName, callback) => { if (socket.requestedName) return; - const reqNameNormalized = normalizeRequestedName(requestedName); + const normalizedName = normalizeTunnelName(requestedName); - if (!isValidTunnelName(reqNameNormalized)) { - handleInvalidTunnelName(reqNameNormalized, socket, responseCb); + if (!isValidTunnelName(normalizedName)) { + rejectTunnel(socket, callback, "bad subdomain"); return; } - if (isTunnelNameTaken(reqNameNormalized)) { - handleTakenTunnelName(reqNameNormalized, socket, responseCb); + if (isTunnelNameTaken(normalizedName)) { + rejectTunnel(socket, callback, "subdomain already claimed"); return; } - registerTunnel(socket, reqNameNormalized, responseCb); + registerTunnel(socket, normalizedName, callback); }; -const normalizeRequestedName = (name) => { - return name - .toString() - .toLowerCase() - .replace(/[^0-9a-z-.]/g, ""); +const normalizeTunnelName = (name) => { + return name.toString().toLowerCase().replace(/[^0-9a-z-.]/g, ""); }; const isValidTunnelName = (name) => { @@ -229,41 +191,31 @@ const isValidTunnelName = (name) => { }; const isTunnelNameTaken = (name) => { - try { - return !!socketsBySubdomain[name]; - } catch (err) { - console.error( - `${new Date()}: Error checking tunnel name availability:`, - err - ); - return true; - } -}; - -const handleInvalidTunnelName = (name, socket, responseCb) => { - console.log(`${new Date()}: ${name} -- bad subdomain. Disconnecting client.`); - if (responseCb) responseCb("bad subdomain"); - socket.disconnect(); + return !!socketsBySubdomain[name]; }; -const handleTakenTunnelName = (name, socket, responseCb) => { - console.log( - `${new Date()}: ${name} requested but already claimed. Disconnecting client.` - ); - if (responseCb) responseCb("subdomain already claimed"); +const rejectTunnel = (socket, callback, reason) => { + console.log(`${new Date()}: Tunnel rejected - ${reason}`); + if (callback) callback(reason); socket.disconnect(); }; -const registerTunnel = (socket, name, responseCb) => { +const registerTunnel = (socket, name, callback) => { socketsBySubdomain[name] = socket; socket.requestedName = name; console.log(`${new Date()}: ${name} registered successfully`); - if (responseCb) responseCb(null); + if (callback) callback(null); }; -const handleSocketDisconnect = (socket) => { +const handleTunnelDeregistration = (socket) => { if (socket.requestedName) { delete socketsBySubdomain[socket.requestedName]; console.log(`${new Date()}: ${socket.requestedName} unregistered`); } }; + +const handleServerError = (res, err) => { + console.error(`${new Date()}: Server error:`, err); + res.statusCode = 502; + res.end(err.message); +}; From adb01a52781a212eb9dda67d89b85547dd0b8724 Mon Sep 17 00:00:00 2001 From: Nick - Ngoc Pham Date: Wed, 25 Dec 2024 12:57:32 +0700 Subject: [PATCH 12/21] chore: update dependencies and refactor domain validation - Added 'validator' package to validate URLs in the server service. - Replaced 'is-valid-domain' with 'validator.isURL' for improved domain validation logic. - Updated package.json and package-lock.json to include the new 'validator' dependency. - Cleaned up package-lock.json by removing outdated 'semver' entries and ensuring all dependencies are up to date. --- package-lock.json | 243 +++++++++++++++---------- package.json | 1 + src/server/services/server.service.mjs | 14 +- 3 files changed, 156 insertions(+), 102 deletions(-) diff --git a/package-lock.json b/package-lock.json index 51a6b6a..df09efd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "socket.io-stream": "^0.5.3", "tldjs": "^2.3.1", "uuid": "^11.0.3", + "validator": "^13.12.0", "yargs": "^17.7.2" }, "bin": { @@ -136,16 +137,6 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/generator": { "version": "7.26.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", @@ -193,16 +184,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", @@ -225,16 +206,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-create-regexp-features-plugin": { "version": "7.26.3", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", @@ -253,16 +224,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-define-polyfill-provider": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", @@ -1541,16 +1502,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/preset-modules": { "version": "0.1.6-no-external-plugins", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", @@ -1691,17 +1642,20 @@ "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" }, "node_modules/@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "license": "MIT" }, "node_modules/@types/cors": { "version": "2.8.17", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -1710,6 +1664,7 @@ "version": "22.10.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "license": "MIT", "dependencies": { "undici-types": "~6.20.0" } @@ -1718,6 +1673,7 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -1730,6 +1686,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -1738,6 +1695,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -1753,6 +1711,7 @@ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -1776,16 +1735,6 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/babel-plugin-polyfill-corejs3": { "version": "0.10.6", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", @@ -1824,12 +1773,14 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/base64id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", "engines": { "node": "^4.5.0 || >= 5.9" } @@ -1839,6 +1790,7 @@ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -1851,6 +1803,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1861,6 +1814,7 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -1927,6 +1881,7 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -1950,6 +1905,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -1963,6 +1919,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -1973,7 +1930,8 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" }, "node_modules/commander": { "version": "6.2.1", @@ -1989,7 +1947,8 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/convert-source-map": { "version": "2.0.0", @@ -2002,6 +1961,7 @@ "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2023,12 +1983,14 @@ "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", "dependencies": { "object-assign": "^4", "vary": "^1" @@ -2041,6 +2003,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -2058,27 +2021,30 @@ "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-0.0.2.tgz", "integrity": "sha512-R6Bl6JxzWVrquXohdoHkyGMgaL8esUXhSzB6WUfSX1N+1dFg30Yo9gshTu7ExV6ntKN4VoJFw8zVdPsVVYGmKg==", "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", "optional": true, "engines": { "node": "*" } }, "node_modules/electron-to-chromium": { - "version": "1.5.75", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.75.tgz", - "integrity": "sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==", + "version": "1.5.76", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz", + "integrity": "sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==", "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "node_modules/engine.io": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "license": "MIT", "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", @@ -2099,6 +2065,7 @@ "version": "6.6.2", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz", "integrity": "sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==", + "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", @@ -2111,6 +2078,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -2127,6 +2095,7 @@ "version": "5.2.3", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", "engines": { "node": ">=10.0.0" } @@ -2135,6 +2104,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -2151,6 +2121,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -2170,6 +2141,7 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2197,6 +2169,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2229,6 +2202,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -2260,6 +2234,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -2282,6 +2257,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -2303,7 +2279,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/inflight": { "version": "1.0.6", @@ -2329,6 +2306,7 @@ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, + "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -2337,9 +2315,9 @@ } }, "node_modules/is-core-module": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz", - "integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { @@ -2357,6 +2335,7 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2365,6 +2344,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", "engines": { "node": ">=8" } @@ -2374,6 +2354,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -2386,6 +2367,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -2399,15 +2381,6 @@ "punycode": "^2.1.1" } }, - "node_modules/is-valid-domain/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2486,6 +2459,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2494,6 +2468,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -2506,6 +2481,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2516,12 +2492,14 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2538,6 +2516,7 @@ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", "dev": true, + "license": "MIT", "dependencies": { "chokidar": "^3.5.2", "debug": "^4", @@ -2561,11 +2540,25 @@ "url": "https://opencollective.com/nodemon" } }, + "node_modules/nodemon/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2574,6 +2567,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2617,6 +2611,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -2638,18 +2633,23 @@ "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "license": "MIT" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } }, "node_modules/readable-stream": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.9.tgz", "integrity": "sha512-Xz7W+ck1vQnTauU5Uo2zYK/Ae8Ou5mRJrKkGFDZX+VF921HwsjL6lz3ga+vT8jQa6HT/0ZbamKffh9OE7xJ4vQ==", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0" }, @@ -2662,6 +2662,7 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -2761,6 +2762,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2787,15 +2789,13 @@ } }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, "node_modules/simple-update-notifier": { @@ -2803,6 +2803,7 @@ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^7.5.3" }, @@ -2810,6 +2811,19 @@ "node": ">=10" } }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", @@ -2824,6 +2838,7 @@ "version": "4.8.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "license": "MIT", "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", @@ -2841,6 +2856,7 @@ "version": "2.5.5", "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", "dependencies": { "debug": "~4.3.4", "ws": "~8.17.1" @@ -2850,6 +2866,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -2881,6 +2898,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -2897,6 +2915,7 @@ "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" @@ -2909,6 +2928,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -2934,6 +2954,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -2950,6 +2971,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -2963,6 +2985,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -2975,6 +2998,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -3008,11 +3032,18 @@ "node": ">= 4" } }, + "node_modules/tldjs/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -3025,6 +3056,7 @@ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", "dev": true, + "license": "ISC", "bin": { "nodetouch": "bin/nodetouch.js" } @@ -3033,12 +3065,14 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", @@ -3128,10 +3162,20 @@ "uuid": "dist/esm/bin/uuid" } }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -3140,6 +3184,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -3163,6 +3208,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -3191,6 +3237,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", "engines": { "node": ">=10" } @@ -3206,6 +3253,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -3223,6 +3271,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", "engines": { "node": ">=12" } diff --git a/package.json b/package.json index c79e3f2..1ae63b6 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "socket.io-stream": "^0.5.3", "tldjs": "^2.3.1", "uuid": "^11.0.3", + "validator": "^13.12.0", "yargs": "^17.7.2" }, "bin": { diff --git a/src/server/services/server.service.mjs b/src/server/services/server.service.mjs index 3beabef..01ae113 100644 --- a/src/server/services/server.service.mjs +++ b/src/server/services/server.service.mjs @@ -3,7 +3,7 @@ import http from "http"; import tldjs from "tldjs"; import ss from "socket.io-stream"; import { v4 as uuid } from "uuid"; -import isValidDomain from "is-valid-domain"; +import validator from "validator"; import { Server as SocketIO } from "socket.io"; // Global state @@ -47,7 +47,7 @@ const handleHttpRequest = async (req, res) => { const collectRequestBody = async (req) => { const bodyChunks = []; - + req.on("error", (err) => { console.error(`${new Date()}: Request error:`, err.stack); }); @@ -92,7 +92,8 @@ const setupTunnelStream = async (req) => { return new Promise((resolve, reject) => { try { // Validate request - const hostname = req.headers.host || reject(new Error("Invalid hostname")); + const hostname = + req.headers.host || reject(new Error("Invalid hostname")); const subdomain = extractSubdomain(hostname); const socket = getSocketForSubdomain(subdomain); @@ -183,11 +184,14 @@ const handleTunnelRegistration = (socket, requestedName, callback) => { }; const normalizeTunnelName = (name) => { - return name.toString().toLowerCase().replace(/[^0-9a-z-.]/g, ""); + return name + .toString() + .toLowerCase() + .replace(/[^0-9a-z-.]/g, ""); }; const isValidTunnelName = (name) => { - return name.length > 0 && isValidDomain(`${name}.example.com`); + return name.length > 0 && validator.isURL(`${name}.example.com`); }; const isTunnelNameTaken = (name) => { From 630c22c87b0abeeaaac81712cc3afa73aaaa4569 Mon Sep 17 00:00:00 2001 From: Nick - Ngoc Pham Date: Wed, 25 Dec 2024 16:06:21 +0700 Subject: [PATCH 13/21] feat: implement website structure and enhance build process - Replaced the existing entry point in docker-compose.yml to run the website using npm. - Removed the old index.html file and created a new one with a complete structure for the NPort website. - Added a webpack configuration file to manage asset bundling and optimization. - Updated package.json and package-lock.json to include new dependencies for webpack and related plugins. - Introduced a new site.dockerfile for serving the static website content. - Enhanced the client and server services to support the new website structure and improve overall functionality. - Added various assets including images, styles, and scripts for the website. - Created a robots.txt and sitemap.xml for better SEO and indexing. --- config/site.dockerfile | 5 +- dist/{ => client}/client.js | 14 +- dist/client/services/client.service.js | 201 + dist/{ => server}/server.js | 0 dist/server/services/server.service.js | 208 + dist/services/client.service.js | 15 - dist/services/server.service.js | 29 - dist/website/CNAME/CNAME | 1 + .../assets/css/main.836bf8a2e746b33e3424.css | 1 + .../favicon_io/android-chrome-192x192.png | Bin 0 -> 17059 bytes .../favicon_io/android-chrome-512x512.png | Bin 0 -> 75657 bytes .../assets/favicon_io/apple-touch-icon.png | Bin 0 -> 15473 bytes .../assets/favicon_io/favicon-16x16.png | Bin 0 -> 623 bytes .../assets/favicon_io/favicon-32x32.png | Bin 0 -> 1486 bytes dist/website/assets/favicon_io/favicon.ico | Bin 0 -> 15406 bytes .../assets/favicon_io/site.webmanifest | 1 + .../assets/imgs/buy-me-a-coffee-min.png | Bin 0 -> 1311 bytes dist/website/assets/imgs/buy-me-a-coffee.png | Bin 0 -> 12535 bytes dist/website/assets/imgs/github.png | Bin 0 -> 493 bytes dist/website/assets/imgs/npm.png | Bin 0 -> 183 bytes .../assets/imgs/nport-logo-dark-min.png | Bin 0 -> 1263 bytes dist/website/assets/imgs/nport-logo-dark.png | Bin 0 -> 10512 bytes dist/website/assets/imgs/nport-logo-light.png | Bin 0 -> 10512 bytes dist/website/assets/imgs/nport-logo.png | Bin 0 -> 60245 bytes dist/website/assets/imgs/nport-site.png | Bin 0 -> 25528 bytes .../assets/webp/buy-me-a-coffee-min.webp | Bin 0 -> 1070 bytes dist/website/assets/webp/buy-me-a-coffee.webp | Bin 0 -> 7926 bytes dist/website/assets/webp/github.webp | Bin 0 -> 394 bytes dist/website/assets/webp/npm.webp | Bin 0 -> 384 bytes .../assets/webp/nport-logo-dark-min.webp | Bin 0 -> 1210 bytes dist/website/assets/webp/nport-logo-dark.webp | Bin 0 -> 13330 bytes .../website/assets/webp/nport-logo-light.webp | Bin 0 -> 13408 bytes dist/website/assets/webp/nport-logo.webp | Bin 0 -> 26006 bytes dist/website/assets/webp/nport-site.webp | Bin 0 -> 22960 bytes dist/website/favicon.ico | Bin 0 -> 15406 bytes dist/website/index.html | 1 + dist/website/main.8a6313405f2e7990d5a7.js | 1 + dist/website/robots.txt | 6 + dist/website/sitemap.xml | 9 + docker-compose.yml | 2 +- index.html | 15 - package-lock.json | 4911 ++++++++++++++--- package.json | 25 +- src/website/CNAME | 1 + src/website/assets/css/style.css | 347 ++ .../favicon_io/android-chrome-192x192.png | Bin 0 -> 17059 bytes .../favicon_io/android-chrome-512x512.png | Bin 0 -> 75657 bytes .../assets/favicon_io/apple-touch-icon.png | Bin 0 -> 15473 bytes .../assets/favicon_io/favicon-16x16.png | Bin 0 -> 623 bytes .../assets/favicon_io/favicon-32x32.png | Bin 0 -> 1486 bytes src/website/assets/favicon_io/favicon.ico | Bin 0 -> 15406 bytes .../assets/favicon_io/site.webmanifest | 1 + .../assets/imgs/buy-me-a-coffee-min.png | Bin 0 -> 1311 bytes src/website/assets/imgs/buy-me-a-coffee.png | Bin 0 -> 12535 bytes src/website/assets/imgs/github.png | Bin 0 -> 493 bytes src/website/assets/imgs/npm.png | Bin 0 -> 183 bytes .../assets/imgs/nport-logo-dark-min.png | Bin 0 -> 1263 bytes src/website/assets/imgs/nport-logo-dark.png | Bin 0 -> 10512 bytes src/website/assets/imgs/nport-logo-light.png | Bin 0 -> 10512 bytes src/website/assets/imgs/nport-logo.png | Bin 0 -> 60245 bytes src/website/assets/imgs/nport-site.png | Bin 0 -> 25528 bytes src/website/assets/js/script.js | 31 + .../assets/webp/buy-me-a-coffee-min.webp | Bin 0 -> 1070 bytes src/website/assets/webp/buy-me-a-coffee.webp | Bin 0 -> 7926 bytes src/website/assets/webp/github.webp | Bin 0 -> 394 bytes src/website/assets/webp/npm.webp | Bin 0 -> 384 bytes .../assets/webp/nport-logo-dark-min.webp | Bin 0 -> 1210 bytes src/website/assets/webp/nport-logo-dark.webp | Bin 0 -> 13330 bytes src/website/assets/webp/nport-logo-light.webp | Bin 0 -> 13408 bytes src/website/assets/webp/nport-logo.webp | Bin 0 -> 26006 bytes src/website/assets/webp/nport-site.webp | Bin 0 -> 22960 bytes src/website/favicon.ico | Bin 0 -> 15406 bytes src/website/index.html | 161 + src/website/main.js | 15 + src/website/robots.txt | 6 + src/website/sitemap.xml | 9 + webpack.config.js | 174 + 77 files changed, 5483 insertions(+), 707 deletions(-) rename dist/{ => client}/client.js (63%) create mode 100644 dist/client/services/client.service.js rename dist/{ => server}/server.js (100%) create mode 100644 dist/server/services/server.service.js delete mode 100644 dist/services/client.service.js delete mode 100644 dist/services/server.service.js create mode 100644 dist/website/CNAME/CNAME create mode 100644 dist/website/assets/css/main.836bf8a2e746b33e3424.css create mode 100644 dist/website/assets/favicon_io/android-chrome-192x192.png create mode 100644 dist/website/assets/favicon_io/android-chrome-512x512.png create mode 100644 dist/website/assets/favicon_io/apple-touch-icon.png create mode 100644 dist/website/assets/favicon_io/favicon-16x16.png create mode 100644 dist/website/assets/favicon_io/favicon-32x32.png create mode 100644 dist/website/assets/favicon_io/favicon.ico create mode 100644 dist/website/assets/favicon_io/site.webmanifest create mode 100644 dist/website/assets/imgs/buy-me-a-coffee-min.png create mode 100644 dist/website/assets/imgs/buy-me-a-coffee.png create mode 100644 dist/website/assets/imgs/github.png create mode 100644 dist/website/assets/imgs/npm.png create mode 100644 dist/website/assets/imgs/nport-logo-dark-min.png create mode 100644 dist/website/assets/imgs/nport-logo-dark.png create mode 100644 dist/website/assets/imgs/nport-logo-light.png create mode 100644 dist/website/assets/imgs/nport-logo.png create mode 100644 dist/website/assets/imgs/nport-site.png create mode 100644 dist/website/assets/webp/buy-me-a-coffee-min.webp create mode 100644 dist/website/assets/webp/buy-me-a-coffee.webp create mode 100644 dist/website/assets/webp/github.webp create mode 100644 dist/website/assets/webp/npm.webp create mode 100644 dist/website/assets/webp/nport-logo-dark-min.webp create mode 100644 dist/website/assets/webp/nport-logo-dark.webp create mode 100644 dist/website/assets/webp/nport-logo-light.webp create mode 100644 dist/website/assets/webp/nport-logo.webp create mode 100644 dist/website/assets/webp/nport-site.webp create mode 100644 dist/website/favicon.ico create mode 100644 dist/website/index.html create mode 100644 dist/website/main.8a6313405f2e7990d5a7.js create mode 100644 dist/website/robots.txt create mode 100644 dist/website/sitemap.xml delete mode 100644 index.html create mode 100644 src/website/CNAME create mode 100644 src/website/assets/css/style.css create mode 100644 src/website/assets/favicon_io/android-chrome-192x192.png create mode 100644 src/website/assets/favicon_io/android-chrome-512x512.png create mode 100644 src/website/assets/favicon_io/apple-touch-icon.png create mode 100644 src/website/assets/favicon_io/favicon-16x16.png create mode 100644 src/website/assets/favicon_io/favicon-32x32.png create mode 100644 src/website/assets/favicon_io/favicon.ico create mode 100644 src/website/assets/favicon_io/site.webmanifest create mode 100644 src/website/assets/imgs/buy-me-a-coffee-min.png create mode 100644 src/website/assets/imgs/buy-me-a-coffee.png create mode 100644 src/website/assets/imgs/github.png create mode 100644 src/website/assets/imgs/npm.png create mode 100644 src/website/assets/imgs/nport-logo-dark-min.png create mode 100644 src/website/assets/imgs/nport-logo-dark.png create mode 100644 src/website/assets/imgs/nport-logo-light.png create mode 100644 src/website/assets/imgs/nport-logo.png create mode 100644 src/website/assets/imgs/nport-site.png create mode 100644 src/website/assets/js/script.js create mode 100644 src/website/assets/webp/buy-me-a-coffee-min.webp create mode 100644 src/website/assets/webp/buy-me-a-coffee.webp create mode 100644 src/website/assets/webp/github.webp create mode 100644 src/website/assets/webp/npm.webp create mode 100644 src/website/assets/webp/nport-logo-dark-min.webp create mode 100644 src/website/assets/webp/nport-logo-dark.webp create mode 100644 src/website/assets/webp/nport-logo-light.webp create mode 100644 src/website/assets/webp/nport-logo.webp create mode 100644 src/website/assets/webp/nport-site.webp create mode 100644 src/website/favicon.ico create mode 100644 src/website/index.html create mode 100644 src/website/main.js create mode 100644 src/website/robots.txt create mode 100644 src/website/sitemap.xml create mode 100644 webpack.config.js diff --git a/config/site.dockerfile b/config/site.dockerfile index 952f813..26b0b57 100644 --- a/config/site.dockerfile +++ b/config/site.dockerfile @@ -1,6 +1,9 @@ FROM node:22.12.0-slim WORKDIR /app +COPY package.json . +COPY package-lock.json . +RUN npm install RUN npm install -g http-server COPY . . EXPOSE 3000 -ENTRYPOINT ["http-server", "-p", "8080", "/usr/src/app"] +ENTRYPOINT ["http-server", "-p", "8080", "/app/dist/website"] diff --git a/dist/client.js b/dist/client/client.js similarity index 63% rename from dist/client.js rename to dist/client/client.js index 729aa91..4ad87bb 100644 --- a/dist/client.js +++ b/dist/client/client.js @@ -5,10 +5,10 @@ var _yargs = _interopRequireDefault(require("yargs")); var _helpers = require("yargs/helpers"); var _clientService = _interopRequireDefault(require("./services/client.service.js")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } -(0, _yargs.default)((0, _helpers.hideBin)(process.argv)).command("* [port] [subdomain]", "Start the client", yargs => { +(0, _yargs.default)((0, _helpers.hideBin)(process.argv)).command("* [hostname]:[port] [subdomain]", "Start the client", yargs => { yargs.positional("port", { type: "number", - default: 3000, + default: 8080, describe: "The port to start the server on" }); yargs.positional("subdomain", { @@ -16,4 +16,12 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e default: "myapp", describe: "The subdomain to use" }); -}, _clientService.default).parse(); \ No newline at end of file + yargs.positional("hostname", { + type: "string", + default: "192.168.10.5", + describe: "Address of local server for forwarding over NPort" + }); +}, options => (0, _clientService.default)({ + ...options, + server: process.env.SERVER_URL +})).parse(); \ No newline at end of file diff --git a/dist/client/services/client.service.js b/dist/client/services/client.service.js new file mode 100644 index 0000000..e52ae63 --- /dev/null +++ b/dist/client/services/client.service.js @@ -0,0 +1,201 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; +var _net = _interopRequireDefault(require("net")); +var _socket = _interopRequireDefault(require("socket.io-stream")); +var _socket2 = require("socket.io-client"); +function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } +// Constants +const IDLE_SOCKET_TIMEOUT_MILLISECONDS = 1000 * 30; + +// Import dependencies +/** + * Creates and initializes a socket tunnel client + * @param {Object} options Configuration options + * @returns {Promise} The constructed tunnel URL + */ +var _default = options => { + return new Promise((resolve, reject) => { + try { + const socket = (0, _socket2.io)(options.server); + initializeSocketConnection(socket, options, resolve, reject); + } catch (err) { + logError("Error initializing client", err); + reject(err); + } + }); +}; +/** + * Sets up the main socket connection and event handlers + */ +exports.default = _default; +const initializeSocketConnection = (socket, options, resolve, reject) => { + try { + socket.on("connect", () => onSocketConnect(socket, options, resolve, reject)); + socket.on("incomingClient", clientId => handleIncomingClient(socket, options, clientId)); + socket.on("error", err => { + logError("Socket error", err); + reject(err); + }); + } catch (err) { + logError("Error setting up socket handlers", err); + reject(err); + } +}; + +/** + * Handles successful socket connection + */ +const onSocketConnect = (socket, options, resolve, reject) => { + try { + logInfo(`Requesting subdomain ${options.subdomain} via ${options.server}`); + establishTunnel(socket, options, resolve, reject); + } catch (err) { + logError("Error handling connect", err); + reject(err); + } +}; + +/** + * Requests tunnel creation from server + */ +const establishTunnel = (socket, options, resolve, reject) => { + try { + socket.emit("createTunnel", options.subdomain, err => { + if (err) { + logError("Tunnel error", err); + reject(err); + } else { + logInfo("Registered with server successfully"); + logInfo(`Your domain is: https://${options.subdomain}.nport.link`); + resolve(buildTunnelUrl(options)); + } + }); + } catch (err) { + logError("Error requesting tunnel", err); + reject(err); + } +}; + +/** + * Constructs the tunnel URL based on server protocol + */ +const buildTunnelUrl = options => { + const subdomain = options.subdomain.toString(); + const server = options.server.toString(); + if (server.includes("https://")) { + return `https://${subdomain}.${server.slice(8)}`; + } else if (server.includes("http://")) { + return `http://${subdomain}.${server.slice(7)}`; + } + return `https://${subdomain}.${server}`; +}; + +/** + * Creates a TCP connection to the target service + */ +const createTcpConnection = options => { + return _net.default.connect({ + port: options.port, + host: options.hostname, + timeout: IDLE_SOCKET_TIMEOUT_MILLISECONDS + }); +}; + +/** + * Handles incoming client connections + */ +const handleIncomingClient = (socket, options, clientId) => { + const client = createTcpConnection(options); + setupClientEventHandlers(client, socket, clientId); +}; + +/** + * Sets up TCP client event handlers + */ +const setupClientEventHandlers = (client, socket, clientId) => { + client.once("connect", () => { + setupStreamConnection(client, socket, clientId); + }); + client.once("error", err => { + logError("Client error", err); + handleClientFailure(socket, clientId); + }); + client.once("timeout", () => { + logInfo("Client connection timed out"); + client.end(); + }); +}; + +/** + * Sets up bidirectional stream between client and tunnel + */ +const setupStreamConnection = (client, socket, clientId) => { + try { + const stream = _socket.default.createStream(); + setupStreamEventHandlers(stream, client); + + // Connect stream to socket.io tunnel + (0, _socket.default)(socket).emit(clientId, stream); + + // Enable bidirectional data flow + client.pipe(stream).pipe(client); + } catch (err) { + logError("Error in stream setup", err); + safelyDestroyClient(client); + } +}; + +/** + * Sets up stream event handlers + */ +const setupStreamEventHandlers = (stream, client) => { + stream.once("error", err => { + logError("Stream error", err); + cleanup(client, stream); + }); + client.once("error", err => { + logError("Client error in stream", err); + cleanup(client, stream); + }); + stream.once("end", () => cleanup(client, stream)); + client.once("end", () => cleanup(client, stream)); +}; + +/** + * Safely cleans up resources + */ +const cleanup = (client, stream) => { + safelyDestroyStream(stream); + safelyDestroyClient(client); +}; +const safelyDestroyStream = stream => { + if (stream && !stream.destroyed) { + stream.destroy(); + } +}; +const safelyDestroyClient = client => { + if (client && !client.destroyed) { + client.destroy(); + } +}; + +/** + * Handles client connection failures + */ +const handleClientFailure = (socket, clientId) => { + const stream = _socket.default.createStream(); + (0, _socket.default)(socket).emit(clientId, stream); + stream.end(); +}; + +// Logging helpers +const logError = (message, error) => { + console.error(`${new Date()}: ${message}:`, error); +}; +const logInfo = message => { + console.log(`${new Date()}: ${message}`); +}; \ No newline at end of file diff --git a/dist/server.js b/dist/server/server.js similarity index 100% rename from dist/server.js rename to dist/server/server.js diff --git a/dist/server/services/server.service.js b/dist/server/services/server.service.js new file mode 100644 index 0000000..b6d723a --- /dev/null +++ b/dist/server/services/server.service.js @@ -0,0 +1,208 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; +var _http = _interopRequireDefault(require("http")); +var _tldjs = _interopRequireDefault(require("tldjs")); +var _socket = _interopRequireDefault(require("socket.io-stream")); +var _uuid = require("uuid"); +var _validator = _interopRequireDefault(require("validator")); +var _socket2 = require("socket.io"); +function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } +// Import dependencies + +// Global state +const socketsBySubdomain = {}; +let serverOptions = {}; + +// Main server initialization +var _default = ({ + port, + hostname, + subdomain +}) => { + serverOptions = { + port, + hostname, + subdomain + }; + const server = createHttpServer(); + initializeSocketServer(server); + startServer(server); +}; // HTTP Server Setup +exports.default = _default; +const createHttpServer = () => { + return _http.default.createServer(async (req, res) => { + try { + await handleHttpRequest(req, res); + } catch (err) { + handleServerError(res, err); + } + }); +}; +const startServer = server => { + server.listen(serverOptions.port, serverOptions.hostname); + console.log(`${new Date()}: socket-tunnel server started on ${serverOptions.hostname}:${serverOptions.port}`); +}; + +// HTTP Request Handling +const handleHttpRequest = async (req, res) => { + const tunnelStream = await setupTunnelStream(req); + const requestBody = await collectRequestBody(req); + forwardRequestToTunnel(req, requestBody, tunnelStream); +}; +const collectRequestBody = async req => { + const bodyChunks = []; + req.on("error", err => { + console.error(`${new Date()}: Request error:`, err.stack); + }); + req.on("data", chunk => bodyChunks.push(chunk)); + return new Promise(resolve => { + req.on("end", () => { + if (!req.complete) { + console.warn(`${new Date()}: Incomplete request received`); + resolve(null); + return; + } + resolve(bodyChunks.length > 0 ? Buffer.concat(bodyChunks) : null); + }); + }); +}; +const forwardRequestToTunnel = (req, body, tunnelStream) => { + // Format request line and headers + const requestLine = `${req.method} ${req.url} HTTP/${req.httpVersion}`; + const headers = formatHeaders(req.rawHeaders); + + // Write request to tunnel + tunnelStream.write(requestLine + "\r\n"); + tunnelStream.write(headers.join("\r\n") + "\r\n\r\n"); + if (body) { + tunnelStream.write(body); + } +}; +const formatHeaders = rawHeaders => { + const headers = []; + for (let i = 0; i < rawHeaders.length - 1; i += 2) { + headers.push(`${rawHeaders[i]}: ${rawHeaders[i + 1]}`); + } + return headers; +}; + +// Tunnel Stream Setup +const setupTunnelStream = async req => { + return new Promise((resolve, reject) => { + try { + // Validate request + const hostname = req.headers.host || reject(new Error("Invalid hostname")); + const subdomain = extractSubdomain(hostname); + const socket = getSocketForSubdomain(subdomain); + + // Reuse existing stream if valid + if (hasValidExistingStream(req, subdomain)) { + return resolve(req.connection.tunnelClientStream); + } + + // Create new stream + createNewTunnelStream(req, socket, subdomain, resolve); + } catch (err) { + console.error(`${new Date()}: Tunnel stream setup error:`, err); + reject(err); + } + }); +}; +const extractSubdomain = hostname => { + let subdomain = _tldjs.default.getSubdomain(hostname).toLowerCase(); + if (!subdomain) { + throw new Error("Invalid subdomain"); + } + if (serverOptions.subdomain) { + subdomain = subdomain.replace(`.${serverOptions.subdomain}`, ""); + } + return subdomain; +}; +const getSocketForSubdomain = subdomain => { + const socket = socketsBySubdomain[subdomain]; + if (!socket) { + throw new Error(`${subdomain} is currently unregistered or offline.`); + } + return socket; +}; +const hasValidExistingStream = (req, subdomain) => { + const stream = req.connection.tunnelClientStream; + return stream && !stream.destroyed && req.connection.subdomain === subdomain; +}; +const createNewTunnelStream = (req, socket, subdomain, resolve) => { + const streamId = (0, _uuid.v4)(); + (0, _socket.default)(socket).once(streamId, tunnelStream => { + try { + req.connection.subdomain = subdomain; + req.connection.tunnelClientStream = tunnelStream; + tunnelStream.pipe(req.connection); + resolve(tunnelStream); + } catch (err) { + console.error(`${new Date()}: Stream creation error:`, err); + } + }); + socket.emit("incomingClient", streamId); +}; + +// Socket.IO Server Setup +const initializeSocketServer = httpServer => { + const io = new _socket2.Server(httpServer); + io.on("connection", socket => { + socket.on("createTunnel", (name, callback) => { + handleTunnelRegistration(socket, name, callback); + }); + socket.on("disconnect", () => { + handleTunnelDeregistration(socket); + }); + }); +}; + +// Tunnel Registration +const handleTunnelRegistration = (socket, requestedName, callback) => { + if (socket.requestedName) return; + const normalizedName = normalizeTunnelName(requestedName); + if (!isValidTunnelName(normalizedName)) { + rejectTunnel(socket, callback, "bad subdomain"); + return; + } + if (isTunnelNameTaken(normalizedName)) { + rejectTunnel(socket, callback, "subdomain already claimed"); + return; + } + registerTunnel(socket, normalizedName, callback); +}; +const normalizeTunnelName = name => { + return name.toString().toLowerCase().replace(/[^0-9a-z-.]/g, ""); +}; +const isValidTunnelName = name => { + return name.length > 0 && _validator.default.isURL(`${name}.example.com`); +}; +const isTunnelNameTaken = name => { + return !!socketsBySubdomain[name]; +}; +const rejectTunnel = (socket, callback, reason) => { + console.log(`${new Date()}: Tunnel rejected - ${reason}`); + if (callback) callback(reason); + socket.disconnect(); +}; +const registerTunnel = (socket, name, callback) => { + socketsBySubdomain[name] = socket; + socket.requestedName = name; + console.log(`${new Date()}: ${name} registered successfully`); + if (callback) callback(null); +}; +const handleTunnelDeregistration = socket => { + if (socket.requestedName) { + delete socketsBySubdomain[socket.requestedName]; + console.log(`${new Date()}: ${socket.requestedName} unregistered`); + } +}; +const handleServerError = (res, err) => { + console.error(`${new Date()}: Server error:`, err); + res.statusCode = 502; + res.end(err.message); +}; \ No newline at end of file diff --git a/dist/services/client.service.js b/dist/services/client.service.js deleted file mode 100644 index f614f57..0000000 --- a/dist/services/client.service.js +++ /dev/null @@ -1,15 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _socket = _interopRequireDefault(require("socket.io-client")); -function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } -const client = options => { - const socket = (0, _socket.default)("http://127.0.0.1:3000"); - socket.on("connect", () => { - console.log("Connected to server"); - }); -}; -var _default = exports.default = client; \ No newline at end of file diff --git a/dist/services/server.service.js b/dist/services/server.service.js deleted file mode 100644 index 06ed3c4..0000000 --- a/dist/services/server.service.js +++ /dev/null @@ -1,29 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _http = require("http"); -var _socket = require("socket.io"); -const server = options => { - const server = (0, _http.createServer)((req, res) => { - res.writeHead(200, { - "Content-Type": "text/plain" - }); - res.end("Hello World\n"); - }); - const io = new _socket.Server(server, { - cors: { - origin: "*", - methods: ["GET", "POST"] - } - }); - io.on("connection", socket => { - console.log("a user connected"); - }); - server.listen(options.port, "0.0.0.0", () => { - console.log(`${new Date()}: nport server started on 0.0.0.0:${options.port}`); - }); -}; -var _default = exports.default = server; \ No newline at end of file diff --git a/dist/website/CNAME/CNAME b/dist/website/CNAME/CNAME new file mode 100644 index 0000000..9efb09a --- /dev/null +++ b/dist/website/CNAME/CNAME @@ -0,0 +1 @@ +nport.link \ No newline at end of file diff --git a/dist/website/assets/css/main.836bf8a2e746b33e3424.css b/dist/website/assets/css/main.836bf8a2e746b33e3424.css new file mode 100644 index 0000000..14996e8 --- /dev/null +++ b/dist/website/assets/css/main.836bf8a2e746b33e3424.css @@ -0,0 +1 @@ +*{box-sizing:border-box;margin:0;padding:0}a{color:inherit;text-decoration:none}body{align-items:center;background:#1e1e1e;color:#fff;display:flex;font-family:sans-serif;justify-content:center;margin:0;min-height:100vh;overflow-y:auto;padding:0}.content,body{position:relative}.content{margin:100px auto 80px;max-width:1200px;padding:0 1rem;text-align:center;width:100%}.content h1{background:linear-gradient(45deg,#fff,#d4d4d4);-webkit-background-clip:text;font-size:4rem;font-weight:700;letter-spacing:5px;margin-bottom:10px;text-transform:uppercase;-webkit-text-fill-color:transparent;background-clip:text}.content h2{font-size:2rem;font-weight:400;letter-spacing:3px;margin-bottom:4rem;margin-top:4rem}.description{color:#d4d4d4;font-size:1.2rem;line-height:1.6;margin:1.5rem auto;max-width:800px;opacity:.8}.content h2 code,.feature code{background-color:#1e1e1e;border:1px solid #454545;border-radius:6px;box-shadow:0 2px 4px rgba(0,0,0,.2);color:#d4d4d4;cursor:pointer;display:inline-block;font-family:Consolas,Monaco,Andale Mono,monospace;font-size:.9em;padding:.4em .6em;position:relative;text-align:left}.content h2 code:before,.feature code:before{color:#858585;content:"$";margin-right:.8em;-webkit-user-select:none;-moz-user-select:none;user-select:none}.content h2 code:hover,.feature code:hover{background-color:#2d2d2d;border-color:#666}.social-links{display:flex;gap:1rem;justify-content:center}.button{align-items:center;border-radius:6px;color:#d4d4d4;display:flex;font-size:1rem;font-weight:500;gap:.5rem;padding:.6em 1.2em;text-decoration:none;transition:all .2s ease}.button img{height:20px;width:20px}.button:hover{background-color:#2d2d2d}.coffee-button{align-items:center;background-color:#fd0;border-radius:6px;color:#000;display:inline-flex;font-family:cursive;font-size:1.2rem;padding:.6em 1.2em;text-decoration:none;transition:all .3s ease}.coffee-button:hover{background-color:#ffcd00;transform:translateY(-1px)}.coffee-button img{height:24px;margin-right:8px}.mobile-text{display:none}@media screen and (max-width:768px){.desktop-text{display:none}.mobile-text{display:inline}.coffee-button{padding:8px 16px}}.logo{align-items:center;background:#1e1e1e;color:inherit;display:flex;gap:1rem;left:0;padding:20px 40px;position:fixed;text-decoration:none;top:0;width:100%;z-index:10}.logo img{height:60px;-o-object-fit:contain;object-fit:contain;width:60px}.logo span{color:#fff;font-size:3rem}.features{display:grid;gap:2rem;grid-template-columns:repeat(2,1fr);margin:2rem auto;padding:0 1rem;text-align:left}.feature{background:rgba(30,30,30,.6);border:1px solid #454545;border-radius:8px;padding:1.5rem}.feature h3{color:#fff;font-size:1.2rem;margin-bottom:1rem}.feature p{color:#d4d4d4;font-size:1rem;line-height:1.6;margin-bottom:1rem}.feature ul{color:#d4d4d4;list-style-position:inside}.feature li{line-height:1.4}.feature code{border-radius:4px;display:inline-block;font-family:Consolas,Monaco,Andale Mono,monospace;font-size:.9em;margin:.5rem 0;padding:.4em .6em}.feature code,.footer{background:#1e1e1e;color:#d4d4d4}.footer{bottom:0;font-size:.9rem;left:0;padding:20px;position:fixed;text-align:center;width:100%;z-index:10}.footer a{color:#fff;text-decoration:underline;text-underline-offset:2px;transition:opacity .2s ease}.footer a:hover{opacity:.8;text-decoration-thickness:2px}@media (max-width:768px){body{display:block}.content{margin:80px auto 60px}.content h1{font-size:2.5rem;letter-spacing:3px}.content h2{font-size:1.5rem}.description{font-size:1rem;margin:1rem auto;padding:0 20px}.social-links{flex-direction:row;gap:.5rem;justify-content:center;padding:0 1rem}.button{font-size:.9rem;padding:.4em .8em;width:auto}.logo{gap:.5rem;padding:10px 20px}.logo img{height:30px;width:30px}.logo span{font-size:2rem}.features{gap:1rem;grid-template-columns:1fr}.feature{padding:1rem}.footer{font-size:.8rem;padding:10px}}.copy-feedback{background-color:#2d2d2d;border:1px solid #454545;border-radius:6px;box-shadow:0 2px 4px rgba(0,0,0,.2);color:#d4d4d4;font-size:.9em;opacity:0;padding:.4em .8em;pointer-events:none;position:fixed;transition:all .2s ease;white-space:nowrap;z-index:1000}.copy-feedback.show{opacity:1} \ No newline at end of file diff --git a/dist/website/assets/favicon_io/android-chrome-192x192.png b/dist/website/assets/favicon_io/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..3f77f02d2f47b3f8ac17885b1ca21a233b5b2d3b GIT binary patch literal 17059 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 Ee+(F)EC2ui literal 0 HcmV?d00001 diff --git a/dist/website/assets/favicon_io/apple-touch-icon.png b/dist/website/assets/favicon_io/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b058ddb7fb89256e89665d865b4e660728f078a3 GIT binary patch literal 15473 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 JPDHLkV1iU7CEWl3 literal 0 HcmV?d00001 diff --git a/dist/website/assets/favicon_io/favicon-32x32.png b/dist/website/assets/favicon_io/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..aa416a6c6e420318123966a39f48d9c9df23d087 GIT binary patch literal 1486 zcmV;<1u^=GP)Px)h)G02R9HvFmuYNNRTO}~``*m7op!oVQs{zgLQyDCgis9%sBA7kY>c273_sXZ zgh&XufT5t`AN`?7Vw6~e8WIvVkzxT^B8yn8p-5Ru)0R?!E_9n|XXahKubr8;%#;Cx zzCSZ>?peNj&OK-D5p;Q^l>LAOz*Ha|i0Ud6=DTc{a{=dpJYbU$;&MlQ;d_x%>cBg| zTYxQ0CwD5=3VZpmD?;nmecWLZy)m953azuO1 zi|=9BLjkq`gB5_v$;!Lk#=q1vBTxWjhGVFzqMl#!OF8?;HLhN1z~%A~VbkfI;*di! zlE|8PpSW*eLO7;;GA9*46JYO7tGS_-9bcU0xy| zHR*F#tpE&PHH3oreBoYMd8yW1j!m+QjEQ|o8{h6RxOY97~1>KH$Nq#X6^AfZ`qHLN02m{-D~O$FGZt<2Azjvhp=3z4f^ zfQxx0Ts~YzY)TCGPaZ(xpdPo|5jV^0xv=*VHKjL6&FsyC+35)3&rnzW76s5Dmx1-N zyb_MDJ}f2DOtwIfG%|^SGX|6TL_hQhi(goJ4Xz$3r)2vDS$X^lhBW*hGhZ7+`pd&Y zmSRYN6SKEdUtCR8pIE$35AGXI6I21&9Fa14+5nN7osQv^6fVoh`B$Ugi8VeFTd!!E zimQp}Y3I?svk+R)Cy-x1OjX?y27UQ7F{64@d$^qHZRcn>S`Pk7fSBREaAc+6 ztZKw_!%5Ogva@?<9`6_0<5r?P<_s~#bCZbtY3uFa=$Za6BnRELP$ z|4Ja&EgtGdZvvZz=+)!F5k<>4g?NsYnIQ4`bJ5b`{POj4w&6KdN#wjt>?_8F574sZ z7|xIPp(V!?yX%brfXmIz2^$a|DYX$PBJ!myuv%$evjgf~(y?=i&{6^bjq|_8TTmu# zbFxI#(n-Mp%rd;C3fu=yYnKa4#sheM`<>RG4w(h1rKVyOm*Ocp z+rg+nA3hk{lnGd}MkBA?pmE_UbKU;o+gOGU3I-6m6E>|}f>BH%!6s0xd_{|7lZ9y{T3QPt$I)H zmhLl$gqDzuo;ex4U&xN;cVJXq#&cp9MolGp`dF+F&xFvexO)Jf)s~I3q;b6j%cR$Z zb?hwAt+(z-8h8tL%huf=32b(v-uxlBTxU)545-^_xBr{2jZa}*zi6szvAxiTPD4x0 z0DB@h>oBU$;w{>XQB&4-2BLcswe%oZB0`1U_j{`Xr~?AU8$?AmSL5El7USGbDUJ3& z8g(R96Er^qN?QN%*ru#U#HHQNxmvGR0KWpG!;vXUg6qWrvUl_>RNu=goSH0Ou4pexw#Q3 z)li~_lmGI0R|v80{~FD{Yah)5SEu&*aCQ5TaxaI|_knethSQF5mr@P@76Ox1jsVqj oL*6RG$e9|?&vh8jOFQcP4TyG7714gu>i_@%07*qoM6N<$f(-=DEC2ui literal 0 HcmV?d00001 diff --git a/dist/website/assets/favicon_io/favicon.ico b/dist/website/assets/favicon_io/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..392016b20619049fb5e8146ec7e06afed38b7eb4 GIT binary patch literal 15406 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 CC^G2) literal 0 HcmV?d00001 diff --git a/dist/website/assets/imgs/github.png b/dist/website/assets/imgs/github.png new file mode 100644 index 0000000000000000000000000000000000000000..143d29dfb1dfd2f0e715de50f0af1e964579273a GIT binary patch literal 493 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaMM3p^r=85p>QL70(Y)*K0-plg6nh^u>n&wq+R_!X1wKm)}}g8YIR79=>l zKR^Gzf`CDO{Qe33>l*_63+n%gWzGV+iQm)3F~s6@ZSZxbW(6LXGM5#xfBr8Q6Y5%H zrms&R%cotDClm!yG!HRk&`~cDe(_FH~#;epX_k#?9Gi! z!hTMAb>J7@u^pbeRqs=$y!204I)fp$$WB91_sZ*<%~#jg>6!FDar6A?ArQiCU}t)F z$~;-k!-nVMrpR!ezgkvc-o>~1z{$`}KPEpA{N=ph$&{o>=C6?z=i_5nTv|4tGdgoJ zn`pf9yCg{g5zT$mEA;eQciCTx=@kx5^s7DUoo&p2VD{t>pN06=X)H<8Ipw$IYdUvn ze&fvrvF455MH;tir2Quk|A#m``<-;#In%mwfD<1e#q2Dy+^5fLrSKaTrYZ+F{ WC>OD$S}FkpkHOQ`&t;ucLK6T3&FriI literal 0 HcmV?d00001 diff --git a/dist/website/assets/imgs/npm.png b/dist/website/assets/imgs/npm.png new file mode 100644 index 0000000000000000000000000000000000000000..cf290faa9dd90c09a9c495af4a64138b43642976 GIT binary patch literal 183 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#Q3?xr`X+H!~q5(c3uK)l4Kh41Kw65-UK)?+< zyUXh8&)VAdznS|Qs36wU#W5t}@Y@>)IU5W_Tn_fjdVJ7JVt3-&@PCob%wPpaN2N2n z^)4Sc_GeK;qJ$jV{OIs%(e)RvvK%(}qiFZ;6LZWlAd?4z`n$8Ihe=L9w)Ck(OX836 aA50S~-JQSRnYt7VDRebtHiQgKt>hQ@L{uurtnFYnDa_%N;3*W!b8(`FHAt}Q2ix}I+} zd!%yk_QH?4;g*}uiW}@snzwD*Ecp{!$!g}$-A!heZ+{y!FXQ>;&CG8mu6-fAru47@ z%XW_9=p$KeSDv`b$KSEzGSB<{bnlDBmZm?O7QH{E+MHM8@Q~f~r~mhYq;*VM*R;2F z|6kay-I@N@c1COF=AM{frmCntJ=gzf*38|PzMyE;q*n~bUl(pZk|VwTApe@jYnMcB zXD>7O@JrHS=C7J%dGm})E^sqADJAFXtUq<;MV$9CNd@&6YNyj!UQ4`Ed3jN4ew0(G zQ`?RCx0gQPxMV1?S^A}HX>EC5_vA@8wm#+Nl}%Uwa&K{Ie~EQuL7>z2?5&*_@BeTVxBrS{^p(+zhWrP#b%H@`lt7i6EpGf5@jr@i*f zCE?9;Z(Q?yplAQE%}evf0qvW|405lA&FOm2(RiPeL-JifKKq&zx~B8~wah!|{`>8B z^Q{X7_~d-zUZv$6_`}|xpYT$(Ve!dF(X&qdH@PbOzu{su>%N}>4*U*Gx<`|)Z<`g{ z`?7e?F1E=G{8NJs#a+)@zqx(exoU|<6PNW_rY$NFoBp*KJ#k8pv(tJ1a>l}w0US|A zx87Y{ZN1~yBp&CRQm?Ow^)4#mV@as;i0Z3(6SpDh)j@-r{^0AKw~Xb347j&;9rO*J z_+k6y%J*tNA8uH-mCH8S^ix8bN!(NQP|;Tt7JRf}mI<%f>5(kscB`@LZrg%SH*-ra zw>=3-7V-LW>UH>}*6G&{Un~BxDQ@z!WLd%51B<50oO!^)5FV4)Q4n}7@0aGwFU#-o zut{HXQg4!Vc=`T@=$Xkb7ux$QZU_Vfbe-_ec$5CUPH|4>QJx8TdoNY$D^xPQ@LBTp zlJESz{qdFSca^I>`h2M4#S)7i`{4Ah3o{=%Oqvwn{^k9`*XN5id|^;lefj(L=i5xH ztM+=|R9qGMfjLXGa?|$PJ(?_~Z;L$Nu80qwYh_pxy1Zpa_YIvryynwSp~8@vMuhUmoOIc20e^&b4>ncXEkbEe%y& zaW3B`=@M(D!DGfNDv{H+ob2b%^Yh#JlTp&Nd7Z{@-Kn-(54bIgEoYz0aptROPk5*E zYkR0h!~F+SDl~iNT1OvaJ!mz1zEx$(X_skzwP*-l_-z~JfX K=d#Wzp$P!+k+BZ| literal 0 HcmV?d00001 diff --git a/dist/website/assets/imgs/nport-logo-dark.png b/dist/website/assets/imgs/nport-logo-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..945912ca90344209b6e1642fbe9d0027b683cc0c GIT binary patch literal 10512 zcmbuFg;!Kh*!V%DOG3#d1*D{NY3Xi}PH9*r7D2i}=~$4CrAun*?huxSr4)fB7HI?* zUcT@9`wxC|&fGiC=gt#z=iGVjy=P)|wN;1-XbCVdFo@Mu74}zt;jOQ2fN_!^V%_tMX;ThVz^|M1qb@kH-qY%gvf~N^QU~B@2?her6J6hs;Mlc_NNcBBHyb~CM`JH*pwlG8y=`P@LuGULF5x}aVAb9pQQ_j zca%zbd&AUs6>hgxKOQ8^@BjXO;Eii>%dV|85ur@z`SzXn?9}$iIbAS7;Gygs_&HIy z5;cC6L&}XtYhKM=N<&p7`8QhkP6POpbw$7IwIEGAcA|IQrs`;md{J7H`?Lf9{WnQ# zBP~lPK?Qy=-gvry*1;qDLb?J6f912T?KyDJ&acVQY4*)|bQB{Y3-K!Ep1VgGhVTws zh>EL`2*;Fo@Y9HTNdG4EjzVrd9mWw_i^UXA=E3(;!XHu)&`NXj`9`J zdg|1^!N>3R*H72tKZ%xdhOf^Z6qmVN$mY^W$*JnFPwdbO4JBR(3?LP+pC$$M?}Oh# zM<^Ot0N{r(V6>(H^a9fn>zXnth`lojkqVOhnh75N5I}iZJ~ZB#0Qp0ZOI1Wfz|^@T z?iVXqL?j?xxHY}Qr>(;Qn+qq>kk;NLM+oThb2)cy`Q{{mfZ8fBK-n+fk~)?lBrNwe zYyYu0VEd|Ie%l+77s0h_8Sz6ui(dDq_j?+^X} zQdPEcR&H6tCGN$?Or{Rv=pRh_H+P z`+0mpXSrN(>GQ&_p`?$tS4x>UGum3}U*kgjz2&23eGBrwOg~3GJ!oPi(=9{Cg!mUY zIV$3mr>>IeCXyWgI{@nH;2tkOFXssNRX2oMrI3x3a}TN7`3V*Teucds>C)3lvYkzsv0Tykw}RXO?|=Ivz5LjLcWUZdA3TX2WTvN?_>IP*03BgJ#YX4SSW_{ z9i^4NQfy3J$&byX<3UOUHtO5;nhU9^FcgHBXvU7DI3bS_7g&9_lrJ`dcRRN z=jwZd_~*&^?o z_=oQ-xuh`NR{8TVN}f|_dag5EwvboevxbPjNkrG-EQ~3km3a;(juYG`MTkQOw)%RHj#t za}xDneO{GN`CWtVO;fdoLt9c7wfhI@oujh}l!{RxIBu@0X-8vDxrKQOw@fV!N(Q~) zn6;QtNmZUKlO*0Qjk%&TJA^QAsrdrjA^ZIkq|PBggC|S5Or4{_z%C9j?NR<2kU5Ft zBi;AcXyq*WVk^UEa}+Uxe(P0Bb06FH*k4*+?_XZz+%Kf}9aemj)+!6|23lUvN2@I% zHxLO+U4GK;WdR?7=u4eT@%8XEi)(v7Qe^v)`1-`&yn9dXCrFif-p01Kj>n#3f9=PX zov1$e9be^unPBymQ33(mz}o12+r}09MlxD7wTV7s~XVENCQgaiVJBMY@%4qcRD{H zkqcDuc~KQ#v3g^6OrOSe2eWc=32H%{MOgsXXut}$3QZCJ1PEbIAr zjGP7hI72ivyN+F#YVpj=aP_I6mDZxsS_Z!kZ@BNzc+u8#>L1_JbHA;6;T26COK4lQ ze^()tn{PuO=h>^sG*+cdPK19UDhbeiICY_4z{tO3a?W2~XP`4)__lsbQG6~pCTgT5 zumOOso|>HHUQ8C;J4M||BW-?ICY)q<^Gra8e^-kBHaNmpz+;Yh_WurJRh5VBu8%oG z!e7<&^lGQ7mgj7CBcIoC@i8N8TT46-xXx#%?rz)qiIGZmYEp2pb~@#a;BV1vRYZ%N ztRdc?@I1TCKb5+J4}nk;t_iy9L%Uv{>p5?fHs)xC-Uc~pidD|T6Eql0M7F0cjmpv! z_w6MTErttsJI@@l3W-NiMm@;pS4RxtvtnKR9#8&a4jeS0nSH*cu45(a$BGxwfc=Yu zUO7)aT}gDhz73`P4d(fV4#JBfEX9AHnXji@huJQjdwAB52{#=cZUSb>QwKL7ed zY7a8E^M+nvXw7DRHL&_@EHk7-wuH{JL&l%5KB4$l2iDpv4JZj9Id0CCaMg05V%}BZe!Ie4GCNIXUORe0ri?`GJ_|oV7Dvvx`oiYglOgPU3Uh=yyLGD zb9w=Hm0Jks<_=;=FozxeGtOmD6)c8ltZfs+3T;*u3q7j8S?ZXE>JF04(UwFv_GJCJ zI;hnM4Pa3P>*wc{^$tE?piT~&%c{RWuaiB;nq@|__sylPYRkv{4c$))x^tP(o7C)! zV}*_;1p(EQP1AzQ3a}A&3hi#c8b|_+LITWHdxi|MC{uOFV*Z9YCB0sb=PVN+{3rKP zHS=)D&^Jzbj8R}H^kVHjsz<7flbuak_8Bp}XC7q<(Qe?>gN;OZ7oG{{|1<~#B@!X6 zxO#7;U~icK^&=l`Z#@e2X-Hgut`AuBZDk^Z&JuhqOEfEv!)|DQXh@Ke)nHH z<6?(Tm8w>|LNmP=P{KEpJT7bkd&@gXR`-aW_vZfjZo*5(t_sU^>pue&2ogU-P4&9e z&YV4aBfUd|GA!Y4h7D*z?=gL)TPR#G9TAUiCI~^fB8p?d9M1k z-+FTb^}0tK-AC?O%Et1c*V2rC_EO$lC#w>-5TKIVq=7+Z5yo0EWC{G6k_ARtqoHG5 zFqIwOIb+ncr@9E)?ub$aiZz+15eM}Fh9*Ihv@;SaX!0>G278}Va2hW4N`G$scCQD0 z#;e24Mg|S;Ebc{Dti^{cv2*9$Xg5ja${xKk<(E0Wz`DLccv$|MdMzA5J&R{o^)}yi ziy?U}`lmzMvp}qN_%oZ0BSBE2JtT0LCVOYnsEVn7=LFyPDXOmn?HE1#7jZNBjP+BJ zz2j^(V`xCHNAQe|_es-uKm@0OlXOhKMc3LMvjzRpc@8@a;yaepuR`l#MFgJm_z|aM zHyLIv)-B63!IUuxTGofTT2M8@o+M-4RcBH6;id9f5Nt6dtToyWm3K3l1oV)-UvT0E z1t0ku33;_PT>KW4>EpT)O+{wU#261FtKhO7EvY(f3Dr*=dOKVn--bq^vUJ~CD66RSqB-i|5w7I zRnR!;TKf3^!rxaTcF4G1%YdqQu>WuV^zC{!(Dl}p5ny=4n(EHeEf5`lK44iH+Tq#y zO*(kPh{$qyY5%rXHi!@*-5$71jO@2SiK&#?MneZ|tT4f{9-GDVK8wC?|LC&`{&f?8 zz^JP!fO6{sa-z0)`3=q$6Z018qteTy*Jdxfy>pjcHUG-1R+t|`h`|+O!3M_q0k7WX zF0S5}nr&NRfRWhm#4~E4l7S087xU;9qHEHv*Wxnv+2Qpim2<&T|Il2eRNRjtuYfk) zrN0+R#%NSZP9c7XuZClZ^ag@k>liea$Zcu&K1hSVTlWS!woZ6c=StRrHtvw-yfmQA zsbgD!avAe5jNw^52^BHyoXaCC)j!-oYAeTUj~U>I~fK2DKrd}^gnc6wT8qJMXyLSa-AS#ZXQ=z4(JD(rReatrI_G=%Dq8J zHh^*_e!EW=BmsA`3i1?j?7XO7zRxd{Hp5Pn`JwB)=ZkZ1ps~lpvG0TGLt>YZyc_*e zXnA?0Da)tW^G^@O_gQ%*z})4AR}JM)FzK0mN6%PA!*r=u9lRkpagpWb$&!?ta)dF^9oJbv|B+nieJ((pLm-Nk&*&FU`Q5pz@3^_|vdCI4kWB}N(2Lj1Ce04EP zew+*Z194KL>UQflv1=#1K2sySO!pj|0XwR}NSUWTh8qz)A1gpq8K0j@99d*or7>kT zOoFc>Vgac)zV9lb#jh&U{Fu#*C2@A@uO%v>@UY@1eD**!TioVB9vYIw+7!njpL=z3 z;HXW$;OF6r6sOYv72p5%q*0hFC3kV?vq)MNSlMKL<0Sdkw&oQKD6m4E^h{dn-Y4CO z8xLixpUBG44zW1q+BPOZ;4(iq)ZJR{2R_wK|ICez(uWBZq~qO+@EYM8;~^r2!)(Il z%#8WeL=hUWB;H~xEQ%bGKl`m*BLt5k>p5P3EpD03w2>Zr&}vl7&3v&1k>)?2Dgq%S zsAr#8+YCgj92S1_LSkC?s+j~I7$zZ2_ot1ypsH*^VoK{Gg0FxV=SK6P?ex zcBQ`cnrR}z`49~NhOyTX_TJ`ic9Fx*0|X##MLlaqv`0FzfJ1pQ#NRIJq<f+r3)P z7B72qH{<(w4v4fQM2qV0NvvN$FE}B-BQ3?@lR~=-IJQ2c9gTHF^}WqN0Altrb@jsk;-rHICiWZjYP{@iV>e0Xk zitxgd>hEsZmCj1Sh*+D5(@H7`Sn)GAr%m{U?hpNK?W4gaekDnF75m%^#;Ph<#AjE)7GX(aF8JOR-scrf-*6LQKX+?=j<;%fr9-JAb_$7;M#&AJ}Ex*5=>N z_KlZPKRj5qv1W)6hMhx6br-aJOjX8JC=s6~0-xdK=*jcd35nZc3!4x12F{dT%3B>S zJzdhZ_gek{D)U)p_JF=Qmi9b+n-F1T{~#v|zyhzjra-KEx z^`Jg}w7WUweL{qYcg2<1-f_g0+A5%EIe40g<*}GiM~T)UuOaaDDRKG>^Bc);i-e1k z!oQc@{NJO$leg$tSO-!=2Zgri*=?xW!*9h}2M3`=mM5{{mUt=4%~7eO}kRy=OUd{kEue zD2$|@Yj@6_uo{V7Z}KqROzOa1dtk~^iPzFPJVpA{_uRXDqMnnCU2GtY>ObR$`pn&C zL=LXdRevd4;;Zr}n{ zIyoAd=F4^bsPx&P4rT6Imi8S~DN{eJ5v;LmHsCP6!|VPq?%!&<2#{KAfb74c^ch#* zp}U!YldEMczKacI{2;lR$j zCq7}Ko6?=TBvD%4qOiH{z_77!()t4eS2Yr;8)I^Wb1Pmd4{(6qWR<#yIGUCW5m}yp3ZR(A^i{yH4rToOhLc z6)t!Lwk?HuWX*#Zxg3D=z0=;do-@fY(y&qE(GpNyvvXIZb1pSqS+8AkAPRqb`rC(y z=8K|QI-vXVGRg8O9*m+9Pdu1z{_n`QuHPSQWxT-{2%Yqn@YWFkva6*z4IBLP81obD z!@YxUCU=9ZIF7;u;Wb^uyudL(iHWdhGw=H+Fus*789`S-3NKAanrjS|7eR@r`FU@y zgdn!jXG!z)IEi4hPgG94--E5`5Lu)aNld|?+zZ$-T@vf@2Vbnn)?=a&CL`js9M%L0 z{?dV4qWp-829kC?fo%oMU0Z#hUU5uzn3o;Ed;j%lwPM{#j^?`TmH=pPShD<|01J(n z0iP3x+)>|9I>fj?m;SJz2ANDF?%_WvAElH5S?v7}&AOc&hOd-TT*q}k<;}P*>Wv+O`NCu>PAUDWB zu0zn)fSls4^6E8SlLhExzm&w~-Kj2VtTdSc+4WWbmJIwPcBFdcWN0QUCK)+&6xM-J zooK=vmXeSVY~$cBaDvA2l0y7#Adz(S+_DRoA!A*N{}gRx=(Q{hq6+cRrQQ9(E1?Vj zKvv&GHD;UWK|)c_Ox^lj$@Au=wKMve{o0JW?w?s_2-#;3kk!Cl{R%eHIPteCU_SU@ z_W7Xv;DNS$cRChWQ+uW_SaZ?~wKbd67no`zc_!(1FN62)UW3e*4+{mExj45V&MbDfK8PD;R zKpUuMTKA7F&6x@xjITS0_CKZZ{tV>PIO>OnKF0wkaiE)xu%G9|X`E}<8aAH~bWIe? z8-i9<=ll!FD)C_9{HdIcoKrRrY*(4O^<)Epl0MHyGu=MtKWBrMgGOwjAY31&Aij-a zxI4?8p!$`a-!@6g)-{x8zHe%YT&S+k)SzYAZg0l{lW5-xEh9v*u&A*(fU5IYXSS4F zEoRhyLMioP-(ig#~B4#!z|P?qOnp?sA8_T}-)`ZJ?1U|0Xl zk7?;?2w>p_AFrQdEO&rJSm9d7_8`W_O47^G!O*UR`}QIk8yCkA;CQ6-`R;N8S8jr9 zaF^>miU>t+0m0?;gbY|RhF?(o)arIbQCc>B?`ykz$R2GMxq3$x-of)of4mTlkt(A&H;57lT_r|6Gt+$-&RKCB-yf8k z;FrN(jRT0nCO+6O_%ieh9%YTI1tc+)`i2J_7?=KIz;?rZe3$>?Q6Z!+gh`>-N)rb2M4^nbDKyNi6CRlZgxsDIbQfE z0Gm^W%Ccj82xKgvyvuF5K)9UHg#(@KX@lAo612y zLwPR;ZVZpP9V<*)iR|oG0p|@B#RkIg;sp=T-oWjT?18Sgs1jyr%VV5tt*Et$Q^=7{ z|Gsyp{Rx{@R~rsEFXER$=TG}Is4u}dKhz*#Gta18ng(HmKT`GsiZwTc-z&plQfc1P z+Gt^Gt12#Y-0u=mzPU`s0F#NrKJcG8@lQ34f@_hrOJdNMl5pOqH*ZQcVUOK|AAN*ww! z4(G1&owABOlkG?KM@7-zKt*!rp}qW&d`8@>Wa?NwmnD7Mj}0e%tjzoj|IFesI(kw# zDt^t^LW2vw;%_ADn_Uto(Yg}I-IfK`5TU?1(aq2PjOIVMFT9nL6sYQzvC5^6Gs3x^ z0`;);yb18@eP$WyKuu9)q4m&90K1;ytB0FKp@w-LBR4&Exy8-v1GE#l0*Q5S1MzKC zH>-U`*r1Ht8w#@i#~E_Dkm$Ao`^GOX%Pllwk*CsBf9@Kp8s&9oiLZse^s7yqv+ZVoN@{;juY_=j5fb!SDM#eb%PgC=+Vo7P#;@!zAC4*t==G_Yc;hOXO}0X z$i4uE2C`+(Gh~STv|vi%e_qe`I*D$m7`!$q4hR1+jJ)I@h|%rHAUA-0hP|lUB8~gN z(V#sUykJm4GrYHfjM5k}-IDqDaeZ@!k)j#Pdd6H`@s@F|$hXe^UXB_{)^+)Tm7DfY zD`%jU7;zdT-JW^-#Y(9?VauHM+HRLm%f~z3v+0Tkq zJBiwl-;gl=8_JGS%f&vln+Bc3Hyn?Z??d8z6i02g+yY7~(8h(Lxi<*!2?f+zoED)Q z=T$n1ZFo@VhPvjV``R6DhOO1FQmbLER2sz6(TFlC#|@dcEP)HJ8Kkr^WAtp;Ps2tY z)Rud+Oqtn6vlBn^rq{l!oz*XL1)0~?HOoimiwtB+=n(lU3H{AVWp1huL||6E%Kxx- z33q2vT%sngq=Qm>Iz(Ru`Xcv?V4hOVo6 z>~Nm}=`o_{;Uo{x+fW~O>&yi2nkk!D<#XXE?<{D!M74Ia{dCgng(jj*HPH_>0wsi^ z#Aqy^_Qn|+xDsyV(17$d)71ZIUw-ei6ovcrN+Un-j6K>bYi2(dB!2V-tRRjZ*+~4~ zR&zyWxzOXVIgkH6Tur;T>Cwj(k)O_T2p)C=13!0XwL?ThnA9{kECP{Y7~NecU_(RIb{)9 z5&oZ>QJ9?3K6XYz8{joHyr(u`NQA~u9jP5F@jVQE!E4Y6E;;|D*eaPu-6EpQUlWav#rhZMMZ%=@ zabe<^i%E-oLdykSC^sI^5&e(ep$HE z4=?M2=jV#c5+K9!_?dpyPJRYH>#pU5a@;3%k21j25`vLRPML~}5xu6opEZ|gAYJ7- zeNy%=!ZM;GU#OFFLM(<3{Lg0Sh)9q#%0ucU=4|6#3$~)*cEr}Eo>G13`eH*)9*Un>mD_%sGXev`3yAc0taAQF7 zBxAkncST{#?Bbw^?Sm~pZuaz?$j>sn52MmiGna|2_iDmrmHusi{lQhQ3{1}3&|w=m z%>lZSE~JyWOMJR_0{0zvsep3nH26tRe8?*4KSPH0KF12T5H<<#Av~=1%-{R#(w_&x zMQ$W~_;%YsevVp)U#yli+Mk~$Sgiqrczy($d-kbxX5F^cvBrkN*PYaLdR)W566x%E z-7Y9o#a&Zm;#w9eItM~-`{PPDd{}H*rYLN$`7U``;6*YLdNQ{TA61C+a~85>pz~=# zVL}=m7fPQI5a`9Z4)1Ow6>&Y10FK zwq$akSfA9jMyN>uE%d&HI%53Cnfqk)^|kR=VXkGVGfHy z!}E@$-0}gpm`1bLU8%dQS(3r~fYn<1dS;Da&A|f6lo~ zUf={uhPRZXaZ7 zb&TCSqI>&Zh*C<|RXvsVEz)eA^Y1V%^*n>%Pop8;PL~1P5yNSW*@N#Nxisk15ZOzESE%eGiqhL;t`|DztrF#2G z&KCuwQ`eS+rh~i7AS7Y&*Ypy&NOR1p0Qn+WE;dWTqon@XZ{@5-%-V9Vl6JFpz4L)-mjRG_o{=m<2kr{HGfWCcJj)L zDV8V@TDRJ$*U{2OUbCE3$#i}d(U6`FO*K-MHIIhaw@G-Z@-b=~8(-&G&<7v>9FqD^ zVXnQ**f3jImg0&YeArqvhh+D?EIJ>0Zy>Uve`d2im`!F-{BxHkH9p_&5I8C#6p>-^ z#mm-x?|rgaL;aHcquDEc<5^Ll}81 zNf@NMkJH`QlYN{aHNg$Fom3a#I8V_rSvy_34Mh*a^4Q-IcEesHz{R&e;tec<=l8!N z)68hy2`jPjp`DcHU*~j8#p2V{i7l40teW_a?}FVQL|?J}hEJ}pk`wj@mRJ~Yf2`pi zIpaF8@DCMLPnERkg7vW;&9NTP-Wn9}@g7!VF!>CUsOC;u;y$U^EU}|C7)QCY;SaH$CpN>bvh z37{UeJ_u>HJ1}~87tzUoX}U#@D|G2!ojZ`|mS#K<(##PuIFHmj+*fN$kxwe*zz)-t z0QrgeCCMLu_(>?uDGyCR5_-OLaIHuvn7)XtDV1~*CzEsID938Zb=Q|W)|}r?Tm57Q z(!l>;7^E!?z(RXxc9-!zi#g=x**?UjibxE|0z*?J=Z&P<*|FAhcx z6<+WxXn*s6HZAyGV?uX#W*@tB4oW^3x@D>9r}wIhsF}jSLvs$F4_jbT^_GskU*p5B zj4cY7A!CPhxT=8M$#Uk}NRH*Ekzmhs5qNjMyA3IQD`$qD%stXK%BZ-$^iw!HyI9?M z&7mO4AEmgjYguOc6+tH1d13e0W|+m>k5s63XY5M`9zdpOmwXQucFJ-TeO=MC6E?HG zCTtMbB%&B`o5 y1HJZ8c!>~(R}^wo$z9zB7oW&9pb69QdmqdTU36xRBCh{SO=?QoinR*XVgCbd#3a-J literal 0 HcmV?d00001 diff --git a/dist/website/assets/imgs/nport-logo-light.png b/dist/website/assets/imgs/nport-logo-light.png new file mode 100644 index 0000000000000000000000000000000000000000..e77fc803cbfbc7cbd48d13c5dffc715c2a551761 GIT binary patch literal 10512 zcmbuFg;x|``0zobOG3#d1*D{NY3Xi}PH9*r7D2i}=~$4CrAun*?huxSr4)fB7HI?* zetduLd)|NGopbKY^L*w$F>}wo&&-^O)zwxZCZHw2z`!6@Q&rT*z<6@_U&F(CBp?n6 z(#OM7M|mxI42*_E!hbf{k2Ws4E3IpSnpqiq* zq2KCJ?uzfP7ILK_%$KUEET#6R53(ZPt5GH`INI2hA%Pnns5kIl>T(x zQMeK{ew9PYjYex;&0R`ERV4X0TK7%^_>*--zwEUjO+0p@ciyJzXp4MNT9f;<1ONRu zNoyl5ODI7FelXs6x_{QeBl|+S0tbKPv##wqaL~@L$|8W5R>{F5CTVOCh$rx}@twUo)b>~+Pu zunEd!P5B&oQtnwVQKd4%4QG|2yB%8=;ae>`wQPB@;@0ab7j&3gh;^w- z=*v=7wsKaoRto#h@^j-YMFubYp<-uGcdVwo{~PiYjd*(AyA5#ClHTJ(sp zi~jq0d_iZqTyW|0!mgpDkG5AznK(1rTIyfpLj1kuqh@^z^1e(zM?F1gVkFZoL&t>p z7dSa8;*_VZlIbRr9RE82>gwPgFF!Bm2=`Stgj%JLjg)f_soMDo76g8Uy&vh*XHnuz zq@N8ed8L$APFboNDfp2{i4#qI!P>Kxz9mAwitKr|N~Q;BEPL-``gPW@95Fp`00CGi zhV&h!mA+DJOkK&3&7|ejHIPtIBkig*ALy8+{M2mBpFz3vV0n6dPar7m&dhCL)#S{BL=pS7vf| zx#NU?-iCa`_C%L(&dn__rP6t1<|o5%=NOY8npNBJh;;eC(gRzCHtUqxDSJaFeLFOk zqQv-z?<~2bFx^)9^Ds)DQ)qgwGhDWiSKhORh`&ih*WoOTDWR2p4o)q%K3tLwH-%2V zUihsVgD=mkyvZ@O&udHr?o|6l<@ZWq65?{K&9RH~GSDK^4z{-{c^ovjYt>QA+woMU zSC4ZN^$FmI{(0^A|{{S&0lAwYvCOSw#)qrt!~4lwOe{uz)t ziQ^;P_t$9UEc#+A!)J38F@k>URZDXp+xOUCT3+v8UgX>_r1u?Ge3I5G3-AV7Ue8CX zEg?4$2}@mm((YveAA#sgolEic@HLBTdp}ZS`;qwi#NNDnPwpp3m3iLAwzrPQo@0OQ z$CjO_KKLD9<$#%B^_5Wq0o%aZ=zZLLXmj`YnB12kq)a#wuZrUkCmYi1c^Xsz+gDwG zQ$9)^31yX+K4Ti70|&pnW$mVE?b)azClNgAv%>FwDW{uHW6wjzesnepiYR0ror(N= zg+CR;C$LmbsR_3(yDNHnYzPHg@BL97k)iMN2U-7sWEvY3rz|Q_01YhT&Rzzbz6l^1 zvNtEvjqvTR@39YQXZUlp?`vrpESE}DuCg4|6tWlUUr?(W(9B2!O5=(PX%}pwSj=}i zKOm6{RPlLH6<@J>V|GlR#&rj`^@6UMA)^q9~-_vuyt$N`VO&v>U zTeW{zA(WeMLm%hatH?A~rA$tQe<3Of(0w>{p zq$RKcfUcgJoaJ6j7Th~U-AN;Depn`)WOwsSK!<-d6cY<44`*KzT&B5YerJP)|eXQ%FN+xm%-N_A>daIkhd<&EHP(QH*j zi=3<>-kVrZ6|BTHSs^h(}bhl{?P$yt`Ns?RKqau2&RNPjb80jB`(J{upR76;Ti7Y<< z`a^0DGPm=FUSMd=W_~rW`fMySq(ioZ&a*?tpRhim_*Mtj+A9qx2_QLc&XsbtpINV& zm1bG5T0xuTva**Q{9{4?NCOlFiYUL^t+i z{kb}*)d&q>Q3dPg=auyiK3||t4w}oVzdx^&J;$15MziV& zA+5N2Z>3;wnE>@8A8l_v3iWA7Tz{?)SoCdZ3b@8R#Bdsy5QeO4CUc|yhO%FwxqEMm z6hw?+BVRAp`#hj8DOnNUgs(ir>X!_-;)qb3Oo5K|dj<+|j^aFIB_{+|{^}ZfMA4<; z7j(;gw+F23Neqyy6#*bydB`5F{+nB3od6yS3ARf9?L4N(6hkgcw({8FV5|Tkwwk1^ zJRW#YQ`a%%>NXPVIxV@|V67of7lSWaB+6p-b6#3w)RND<)L+8KPN#yF4)6MVKy~u| zlMBvc1VO3Y+x$u0R%>L{53dZ3`T!-HhpGbt+OY78z$ybSgBlPn64(lG7C(ppss=C0 z76^VNL@`+uNtOx*axyMS%H1CF779KO@A%~kV6v!u#JO)$$V;NFa`m&OBRWzaoxgtf zpPX^A!>3ABt6ia)-U}$xFRh*f|Z_5;^msUSAe@pR>r{yxZ4tYm-{o1!| z;{NZ8mSteh0R`$(Wm1`sYo+m9B(E>B3Gyyg6`5Ysu}D_6)_B@ZucE}PEZ)ETqs2T| z{n~H6Ie~iJBaZGP_bg>&`Os@=#y@*0Z?2P7iCYLz$!*fWAhQT#tr)Td{!PgOqpZ=; zF)oglu<2sRG5C%+rX2`T#?dAW7O82^BQ?7#D-R&nY+!mwKf?w|=|V zgFfTc;btR)26qV@kX*?i;)4)kOrr)A#ZI9W4{^&f19R~3o%js94^{^rWPkH=^ zQ?i>3vli=?<(Xj0m;^2B!(1(>8evb8vF@s~sQd6z`78*w7!uYRZHLOcnM?wD$lfnF zaf5=7e2s*>S{p8Y3(E9y-H7p8wbJ)m+`Yll7}s-@zM^?sjji%=vFFsO4B7uH zVbLmRoOCUH{D0x^s}Vb7T(4z7RXo`LH-GweJsaqH>&gf)JYr3C=jj%Rjz1r;tPJh& zZ2cx3ykSISIlQ!gTPqtxh>&g%TqZ{LTcE^L%50;d12$HeU|EmNVtSuNU$=ks*#!T( z2|!@f)f7Ovbpbh1TfF=R=ZcAW3-wXyWzuW2m)+jE%dVP#DFs;8T;(;`jX1IV5xs-u2L%Q$COt< zo9@!zizH(-DkY~7Kg3tVu|#?U!L4-+8cXE1w0j?p&ZKNON8q z(B{;!EkL=9c^JmJrU06*3~bAM6R8Tg8md52E{qQKm-WUA=5M0$*Z1!U4v~fNby>+ z4A}TiBZsP8m!T4VsWX<+_(%F5x~^J7Vu_+xBpSI+5HdH9t1Jif1I8#Exi`?*ZAOGw_0 zekruPJkpfqQ|$SthvNIJyb@sUa>J{J@+X+|OunOMtfFDMRI3i&5S+NkdCY5K7nPP$ zuZzLSzfZQ?Z3T!yb$8Uw9nd;IqBPdLv%q}AgYYdPbH2lGOW^= zG8-nrR}ry*)EnP-mC)i>m1%y=X2y~@JN4HRl~8zC@e@9Kpqedi^B@lmNn&k^Hn(le|yp>%$1V6IP_U0Eeot{GQV+>d}~|t3I-Hdp-y@xEp_jc z?!=9Uvei#yWoU<39CK|OlOS-JpBw6KE%yVTYNvnZ#zyJGgbLE}Zbf*F@Qv{hk-}j% zVRL52d}^Wy4OkLyu@x3Y4#}VWR<03(N0s#)ufGf)5Onkf!_7##~U?${1GltGjH+6+nJaMjhPVBCLtd zXI;Be-+Ik7k>GrY1^~m@>j-;q^EbQ5Vdnt?khY?pH6z+1omjx3JQ?C|7j@E`63E+uBxolJa9!!oP3! z#pw=;wx@$OY{>;g1kZ>q|782Rw}HSrbm3zJzB3o?N7S!gpu(W;N#JW++>G z$5mkyG^L7pU~jn#(a|?=yj`kvVM}M=_lKnPsXG#CBe!>_luPvNSSW<`$o_}LZgCW*`7D`6$GsKnVZul{6hDK{DqfOe*l&FEHisR-yBPO9==V8u(E%UlLcUbS6x#eRz2OKBbo-h?c&S;L5@Vx zf%_+LN$6~pFVkg?n#$htlL1VxGmrMBjqv-|?q7QB6?R`(iHjke!{>&ZraQh^4HVQ= z98Q9+$D3-aOT|>+zdFn0SvU~ytUL#Ha8%q)fAEKSc#dY0qH3JG>}!6ZGbyt=H9`4? z&VhPRA3xgNobo;)Ld3h`N^I{q;!155P_!I8O~mq8%&4P8>yXzF`1+JM{e}6Be`%WORbaJyNVS99&PxQxjHj3qlkVR5rKGeT|QCINyaWVkVf^N@k4#) zZZjeWSLmugnr1zkpIS#V!u3xa;t#A8I3#rz`;d?cP&f%4yu%?AJz!g*fkq)7~kP_e;D^~HC+TqEjB>*-%x5=!hvw(ixULR7IkSG(yb(p@n@2txILXiW-muhX9>KRb z#+o&J5#`lkfU1+|E`6)?GHuaE>E=4I1wh4o%ku*bmzhg|OR@{e-&v+ia9l0#t;y!f zeCl>6(>N4*B_t0VzNi0~y_OdFdFtZWX)`JLauK6uv9U*Eeuy1->n1y5{B2P_ywoJ~ z1HlUKnUU0{Z&o+;p#c_?R=?nM^@Mm7D-Sset&y;VdVy z=iL*ZFwsrv&RvoyEpJiSTz6pDSU73@0fDO;iPViTIl{RWFO>&4KyR{2-9sErONIz6 zJvchHPFFOoxdYWVJWd~r5kz`NU(?2ZSd<)0XS~UBKwQNv6*zC>7zT9r1^BL0`ZVWV zC0~UL9)WF3VIEoYAVw|+;C%12x2@+)a*Q-=)OfT6RM+g>73rKyO;^@ymmG+~-=6;V zA)@)BsFn`szPwDbe2NF7Xv7l_rknpe@~!Ll2U{6$Fa|;=y(PSL1c2;nX->li|2)S0 zMEh{>pqt6vAS;feFhO`t*Dx<|%uiw>?Agrw{t1k4WlKiT6_CP96O!f{L*+$KB5Ho# zn=2uRZS+~vJUvb#*z6OP6Yuw6YdS<0sYMb~@F({Ic1)MVdi=o`E3);ND1^y~I4y@Y zL4v<@;Fc&qqN0JMT~A>K#n7luRi~CdWX4QeKJPXObq9#9rUI`U6|mO(#sl?Ts-^P-(8t2-Y}8@=^n@p zGLY*Kv^5~7xU0N+jn`xWI@vEJad~&DOByRpW$%;uv4jqMc zU{oiX@P?%%Bm~v8erCTmqptgB))_+f*#l%Xa96*AjWkaDtqPbA zKA3$zC_i|hE#IAv1=iG_=?m7J^g?aTCiMlT+DM*B`rXUmy}Q>Sv*p7=L1r$_EeLc& zM8{05f=MDFpJ71QGkNJc;=mAQsrx$$sw<@>5Aun1tJ2o8XR5G;r z^o_3u~Y3L4V@q?U!UyB)4x;@}X}muJ`81CDp`p)lz)2kFW+Uw9IdK~2+O>ww=L206 z#qx%rRn&z2rfxmi0HCDLv(Zeq&-u^Upyi+uTPO(Ehbf3} zqZsbaawn*MW#_j|lCpIT<(coBS|S&!>oYZIS+?8Talj3qJsoWPZv z;2PZJI*%ekky}7;IXxi*mW<&S)IPPk9Z@-xA3!RxC-R(g#Th3U9PhvQj<)ZW>8xRg zckol~&O(%yjo>i^kkYo+tZnykInluZ@9x|tQbi)j7_*z5(oBvQ zJ_^9*l%cZh7#{)|3n=e$TP_eTCv@RJXM5V9c7+75Wxg*o!-_6-!&KowJNsKZ$i7W} zw#-o8%YhriV{XR^lU5=-`&GbsLq)NHFuZud1GG1A`y+dx>n*B;S=#a#=UOXjZQ>Mi zq|?9e-D!WqX4Tb(1I~;1WzhN4J`L(iFwPG(2-wUsDwn1~*x-+p{eWW44dM67Fql-D z_p~-z*xIU!%N+N+gxX3#dC_>y0c>cjH`3>}Xyl*-sd<*PvkJ+jV$Par_P}%v$B;kR z+OU)L7{WUM(La7<2nN`a$~dujgO{B&r)QKv#p8V$v5}sP4)SNEMSh$20Lc;@x~>w3 zzKp}Ut9+-dV$WpzQT@?Sv^P+Z+<9m(KO~R?lTg-}Ym}NgpdSKf^z>c#Mvo z6po5t^R>|6g0J`+$@*rO#7VTS1ah}!fi*-Za87jdvp=Kx5AF+Z}^b)|XXZY&jW>KhNp2y5hk6mtY^ZEeoM6N($9o#^C z8`aHfUlBGab6MZ zK5#T>PX;d-RL~6XZ6KpGMohP4{(W5EoMEJB#i`HtIEi?Z?^ZjN(XQ5-Yz?1iC0y90BUIkjKhdYT6tdy@O|6EE8$ zEci~M_TxJwjQ@tRqttS-5ACKw=kN{3W99phI3LAPn=QA1(h9V3p=j<6!h1piwHBvE z=*D@KPGTD#6uP0VdFZ}&hnrz*^{dosm@AbAv2-+|jLLCC<}FL$!fOU8ZOj-w8}`$% zkq5Qq9xYR5w$bdwkG$!%?`miDi(EnGb#=}1(fJ|+nG!lg{z^iBvr?Ix>H`s&Rj=|t ztX;z0SrnJ3$t&rg)SeE}SAo9BJ!5$^a%MG%*RDwR0O`WXE7&4SvpX*C_8x~Ig`xh! zb}I17JmEFB#>uQ_+pb5~8e|YoD0Z!>J#llS5iGQ4gTzBiNzg}9G6yvZLB6}L&i-#i-rd*X#!PKvzamQC+MxvhoO7^U zmGq37M743u7qx9A<|iymtUkAe=Ztd)N5=iRaE{5HYwz1PxG{UH$S2HC8Xwa<8n z_krI!Q`;KZOm?lI=_TbF-xeNMy5xuwJn&NSchf!`7P)@4_uzJve@cDwVynsHvlnA{ zM~>)h{wj8>cSPP(8!#k7|59v~OrvfQQRc6SM#p0P3-lsk z(t2{lKgC{(E85Kn!MAIEGkRg`x_A0%MOZxu4>@&Mt$z9`cL>fc3g)ZJP-}T}PG-L> zT?dlz9D(UC9I$vGhwLkIq6Gjv2G$Qk7!^%8To{4-Ekh<<;<{TDUgi*m5o<|zf= zMfn+6LgQ&U&j0%fqvb?rR1x8g-~iKa5uu?z9P1~&#I zPcqiKepeK>%q|X!*gn|u<7Q9KiTo_H`!FgUHFKHRdaov2R_WjN*B@N<%E08j4IQ?D z(;T2H=|Vc0yTqq^Cve|!mkKDCPJ^HH#D}bs{xf7~?{lnh3t^M+9>T+F&-}f=F8z5B zT;xW=hi|tH z6egt6aiR1X0fAne>+tS2QW580rOgemo`1% zXG z!ajOX(%>ti93XWzJpe0HAhY@ru0 zm~TW^a328?o1}eed)E#3ft(4&wh2~;GPML>X0aQdN*wzs)pENe&ZSr?*25{5>-IsW zR>#=QBf7Wmg(#(TUDZ=*-y+S{IsXpRQqMD3A`iJ<-5Ou{C>@t_=gMo2CbPOER(ktt zZ{l?Z`dL1ooe+fei%ERX#iVK?kG0$hCiBi_Iwcz*vY zGR=(Eov;!cAKFQI{&h~rR4hJCo!DY2%c_a*_%7J}LG%^dZ}{ZuDmh_qV2OnR_s1IU zku$CX3;$42^;AilE?6Jy(H!dm?X5upAMar`29wVqiE8eoCGL}o%@R9WgTbd!Eol+~ z08Jvg6N(OH?ku72k&4l#Du!Uy8@Xup1m;SLRpt-x&j=-;B%^$eQHdtNzopsq=W)J* zztaDGyyc<3Q=hbQ(hEhini^U^;JM-^YH`;699pHR#F|sd*1u5)7L`TJ3YVGyp(G`~ zngHri>w}PHy91+lcM+ZZm!@0fxI&ly)wu(SZfV8?AZ z36P(dUy}Usho6Mfobu2FB%$X^2iJ;(g6WIMno>z8aWXkKj&iJqTz7rBW6k;PwAD{` zKwf7N?{Yw?Pzq)gUpe4$*}_drSk;p(@J%DxUYOS0-Cmi9f$IScl&v=s;LPMX^x|OD zP~ip7g7!E6XVZf3H70a-XZEp6=b+?sp<9-ketNIEh?*%JJT&L<`LG2hRd4Co`!zo7 z%GjcS88UWAhpP(6oh)arjpSHv8VUAH7lC*8yW5b`w{m9a$=oA-ql}8{OFxCPvy0W8 z*BlCx{85Vgx|U_8UlC-Iofme0ZH8IA{YZsscgDVC-~nWccFFfpVW%ud(bpACJ7F{1 zYr+O`U7k_Xu6$eX8xNX#X9{V%Cd@mG)<8peBqQLgZ#`>KTTk}RC*2xPy}U^huc`(h zEq#R2DP~)raO#wEo!o57@TBl$IHOw|fAwSElpG_ZhQMC-B!xWPUd1f}lb;Qx1*54e zltpb`@^YAJn{|13{^t$TXX#lj*^!$NhuQ(hU)vU|{ yG|+1gg_j6ncts&cmE6^BaPf&u1DY@$zxTns&_!p~DB}9R>ZGQmtyrsI9riy?Y2%y# literal 0 HcmV?d00001 diff --git a/dist/website/assets/imgs/nport-logo.png b/dist/website/assets/imgs/nport-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f8ad9821cadf6f539b7759cb6d66bc4b55b9c19c GIT binary patch literal 60245 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

9iF(nr9*wl6P_b_MS)5G^E)rzD+FjzR&m(n=}% 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 literal 0 HcmV?d00001 diff --git a/dist/website/assets/imgs/nport-site.png b/dist/website/assets/imgs/nport-site.png new file mode 100644 index 0000000000000000000000000000000000000000..4bf051943293806ce11bcac631a6f98d304e09d3 GIT binary patch literal 25528 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