This commit is contained in:
lblblong
2021-10-13 15:55:10 +08:00
parent 1da3199db2
commit e503d77cf5
42 changed files with 16720 additions and 13029 deletions
+44
View File
@@ -0,0 +1,44 @@
module.exports = {
types: [
{
value: 'feat',
name: '✨ 新功能',
},
{
value: 'fix',
name: '🐛 bug修复',
},
{
value: 'refactor',
name: '🎨 样式调整',
},
{
value: 'perf',
name: '👌 性能优化',
},
{
value: 'build & ci',
name: '📦 构建与CI修改',
},
{
value: 'doc',
name: '📖 文档更新',
},
{
value: 'chore',
name: '🙈 其他修改',
},
],
scopes: [],
messages: {
type: '提交类型:',
subject: '简短说明:',
confirmCommit: '确认提交?',
},
allowCustomScopes: true,
allowBreakingChanges: ['feat', 'fix'],
skipQuestions: ['scope', 'body', 'breaking', 'footer'],
}
-1
View File
@@ -1 +0,0 @@
[{"/mnt/e/code/pro/utools/nat-utools/src/index.tsx":"1","/mnt/e/code/pro/utools/nat-utools/src/react/reportWebVitals.ts":"2","/mnt/e/code/pro/utools/nat-utools/src/pages/manager/index.tsx":"3","/mnt/e/code/pro/utools/nat-utools/src/model/nat.ts":"4","/mnt/e/code/pro/utools/nat-utools/src/store/server.ts":"5","/mnt/e/code/pro/utools/nat-utools/src/components/icon/index.tsx":"6","/mnt/e/code/pro/utools/nat-utools/src/components/alert/index.tsx":"7","/mnt/e/code/pro/utools/nat-utools/src/components/help/index.tsx":"8","/mnt/e/code/pro/utools/nat-utools/src/components/nat-edit/index.tsx":"9","/mnt/e/code/pro/utools/nat-utools/src/components/nat-log/index.tsx":"10"},{"size":697,"mtime":1627282191594,"results":"11","hashOfConfig":"12"},{"size":425,"mtime":1627282191598,"results":"13","hashOfConfig":"12"},{"size":3512,"mtime":1628044105015,"results":"14","hashOfConfig":"12"},{"size":510,"mtime":1627282191595,"results":"15","hashOfConfig":"12"},{"size":4400,"mtime":1628044184011,"results":"16","hashOfConfig":"12"},{"size":406,"mtime":1627282191591,"results":"17","hashOfConfig":"12"},{"size":842,"mtime":1627282191589,"results":"18","hashOfConfig":"12"},{"size":1442,"mtime":1628043988331,"results":"19","hashOfConfig":"12"},{"size":1626,"mtime":1627282191593,"results":"20","hashOfConfig":"12"},{"size":1109,"mtime":1627282191594,"results":"21","hashOfConfig":"12"},{"filePath":"22","messages":"23","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"olkzyp",{"filePath":"24","messages":"25","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"26","messages":"27","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"28","messages":"29","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"30","messages":"31","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"32","messages":"33","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"34","messages":"35","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"36","messages":"37","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"38","messages":"39","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"40","messages":"41","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/mnt/e/code/pro/utools/nat-utools/src/index.tsx",[],"/mnt/e/code/pro/utools/nat-utools/src/react/reportWebVitals.ts",[],"/mnt/e/code/pro/utools/nat-utools/src/pages/manager/index.tsx",[],"/mnt/e/code/pro/utools/nat-utools/src/model/nat.ts",[],"/mnt/e/code/pro/utools/nat-utools/src/store/server.ts",[],"/mnt/e/code/pro/utools/nat-utools/src/components/icon/index.tsx",[],"/mnt/e/code/pro/utools/nat-utools/src/components/alert/index.tsx",[],"/mnt/e/code/pro/utools/nat-utools/src/components/help/index.tsx",[],"/mnt/e/code/pro/utools/nat-utools/src/components/nat-edit/index.tsx",[],"/mnt/e/code/pro/utools/nat-utools/src/components/nat-log/index.tsx",[]]
-6
View File
@@ -1,6 +0,0 @@
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"printWidth": 120
}
+4 -4
View File
@@ -4,7 +4,7 @@
欢迎 start ✨
![](https://cdn.jsdelivr.net/gh/lblblong/image-bed@main/1627284934657-QQ截图20210726144102.png)
![](https://cdn.jsdelivr.net/gh/lblblong/image-bed@main/1634030014550QQ%E6%88%AA%E5%9B%BE20211012170819.png)
## 安装
@@ -16,6 +16,6 @@
## 部分界面
![](https://cdn.jsdelivr.net/gh/lblblong/image-bed@main/1627284934657-QQ截图20210726144102.png)
![](https://cdn.jsdelivr.net/gh/lblblong/image-bed@main/1627284982765-QQ截图20210726144111.png)
![](https://cdn.jsdelivr.net/gh/lblblong/image-bed@main/1627284993059-QQ截图20210726144126.png)
![](https://cdn.jsdelivr.net/gh/lblblong/image-bed@main/1634030014550QQ%E6%88%AA%E5%9B%BE20211012170819.png)
![](https://cdn.jsdelivr.net/gh/lblblong/image-bed@main/1634030036322QQ%E6%88%AA%E5%9B%BE20211012171258.png)
![](https://cdn.jsdelivr.net/gh/lblblong/image-bed@main/1634030047907QQ%E6%88%AA%E5%9B%BE20211012170837.png)
+12
View File
@@ -1,6 +1,18 @@
const CracoAlias = require('craco-alias')
module.exports = {
eslint: {
enable: false,
model: 'file',
},
plugins: [
{
plugin: CracoAlias,
options: {
source: 'tsconfig',
baseUrl: './',
tsConfigPath: './tsconfig.path.json',
},
},
],
}
+38 -41
View File
@@ -1,60 +1,57 @@
{
"name": "nat-utools",
"version": "1.2.3",
"private": true,
"name": "nat-utools-lerna",
"version": "2.0.0",
"description": "",
"homepage": ".",
"author": "lblblong",
"license": "ISC",
"scripts": {
"serve": "craco build && cra-build-watch",
"serve:preload": "cd ./preload && npm run serve",
"serve": "nodemon --watch src -e tsx,scss,ts,css --exec craco build",
"serve:preload": "cd ./packages/preload && npm run serve",
"build": "npm run build:html && npm run build:preload",
"build:html": "rm -rf ./utools/build && craco build && cp -r ./build ./utools",
"build:preload": "cd ./preload && npm run build",
"build:preload": "cd ./packages/preload && npm run build",
"commit": "git-cz"
},
"dependencies": {
"classnames": "^2.2.6",
"global": "^4.4.0",
"ajax-hook": "^2.0.3",
"classnames": "^2.3.1",
"dayjs": "^1.10.7",
"history": "^5.0.0",
"lbl-popups": "^1.0.0",
"lbtools": "^2.0.2",
"mobx": "^6.1.0",
"mobx-react": "^7.1.0",
"preload": "link:E:/code/pro/utools/preload",
"react": "^17.0.1",
"react-dom": "^17.0.1"
"mobx": "^6.3.3",
"mobx-react": "^7.2.0",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@craco/craco": "^6.0.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"@types/classnames": "^2.2.11",
"@types/jest": "^26.0.15",
"@types/js-cookie": "^2.2.6",
"@craco/craco": "^6.3.0",
"@types/localtunnel": "^2.0.1",
"@types/node": "^12.0.0",
"@types/react": "^16.9.53",
"@types/react-dom": "^16.9.8",
"@types/tcp-port-used": "^1.0.0",
"cra-build-watch": "^3.4.0",
"craco-alias": "^2.1.1",
"craco-sass-resources-loader": "^1.1.0",
"env-cmd": "^10.1.0",
"passport": "^0.4.1",
"react-scripts": "4.0.1",
"sass": "^1.30.0",
"sass-loader": "^10.1.0",
"tslib": "^2.1.0",
"typescript": "^4.0.3",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"commitizen": "^4.2.4",
"craco-alias": "^3.0.1",
"csstype": "^3.0.9",
"cz-customizable": "^6.3.0",
"react-scripts": "4.0.3",
"sass": "^1.42.1",
"typescript": "^4.1.2",
"utility-types": "^3.10.0",
"utools-helper": "^1.4.6",
"web-vitals": "^0.2.4"
"utools-helper": "^1.4.6"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
"config": {
"commitizen": {
"path": "node_modules/cz-customizable"
}
},
"prettier": {
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"printWidth": 120,
"trailingComma": "all",
"jsxSingleQuote": true
},
"browserslist": {
"production": [
+1
View File
@@ -0,0 +1 @@
/node_modules
+6
View File
@@ -0,0 +1,6 @@
dist: xenial
language: node_js
node_js:
- "8"
- "10"
- "12"
+81
View File
@@ -0,0 +1,81 @@
# 2.0.2 (2021-09-18)
- Upgrade dependencies
# 2.0.1 (2021-01-09)
- Upgrade dependencies
# 2.0.0 (2019-09-16)
- Add support for tunneling a local HTTPS server
- Add support for localtunnel server with IP-based tunnel URLs
- Node.js client API is now Promise-based, with backwards compatibility to callback
- Major refactor of entire codebase using modern ES syntax (requires Node.js v8.3.0 or above)
# 1.9.2 (2019-06-01)
- Update debug to 4.1.1
- Update axios to 0.19.0
# 1.9.1 (2018-09-08)
- Update debug to 2.6.9
# 1.9.0 (2018-04-03)
- Add _request_ event to Tunnel emitter
- Update yargs to support config via environment variables
- Add basic request logging when --print-requests argument is used
# 1.8.3 (2017-06-11)
- update request dependency
- update debug dependency
- update openurl dependency
# 1.8.2 (2016-11-17)
- fix host header transform
- update request dependency
# 1.8.1 (2016-01-20)
- fix bug w/ HostHeaderTransformer and binary data
# 1.8.0 (2015-11-04)
- pass socket errors up to top level
# 1.7.0 (2015-07-22)
- add short arg options
# 1.6.0 (2015-05-15)
- keep sockets alive after connecting
- add --open param to CLI
# 1.5.0 (2014-10-25)
- capture all errors on remote socket and restart the tunnel
# 1.4.0 (2014-08-31)
- don't emit errors for ETIMEDOUT
# 1.2.0 / 2014-04-28
- return `client` from `localtunnel` API instantiation
# 1.1.0 / 2014-02-24
- add a host header transform to change the 'Host' header in requests
# 1.0.0 / 2014-02-14
- default to localltunnel.me for host
- remove exported `connect` method (just export one function that does the same thing)
- change localtunnel signature to (port, opt, fn)
# 0.2.2 / 2014-01-09
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Roman Shtylman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+122
View File
@@ -0,0 +1,122 @@
# localtunnel
localtunnel exposes your localhost to the world for easy testing and sharing! No need to mess with DNS or deploy just to have others test out your changes.
Great for working with browser testing tools like browserling or external api callback services like twilio which require a public url for callbacks.
## Quickstart
```
npx localtunnel --port 8000
```
## Installation
### Globally
```
npm install -g localtunnel
```
### As a dependency in your project
```
yarn add localtunnel
```
## CLI usage
When localtunnel is installed globally, just use the `lt` command to start the tunnel.
```
lt --port 8000
```
Thats it! It will connect to the tunnel server, setup the tunnel, and tell you what url to use for your testing. This url will remain active for the duration of your session; so feel free to share it with others for happy fun time!
You can restart your local server all you want, `lt` is smart enough to detect this and reconnect once it is back.
### Arguments
Below are some common arguments. See `lt --help` for additional arguments
- `--subdomain` request a named subdomain on the localtunnel server (default is random characters)
- `--local-host` proxy to a hostname other than localhost
You may also specify arguments via env variables. E.x.
```
PORT=3000 lt
```
## API
The localtunnel client is also usable through an API (for test integration, automation, etc)
### localtunnel(port [,options][,callback])
Creates a new localtunnel to the specified local `port`. Will return a Promise that resolves once you have been assigned a public localtunnel url. `options` can be used to request a specific `subdomain`. A `callback` function can be passed, in which case it won't return a Promise. This exists for backwards compatibility with the old Node-style callback API. You may also pass a single options object with `port` as a property.
```js
const localtunnel = require('localtunnel');
(async () => {
const tunnel = await localtunnel({ port: 3000 });
// the assigned public url for your tunnel
// i.e. https://abcdefgjhij.localtunnel.me
tunnel.url;
tunnel.on('close', () => {
// tunnels are closed
});
})();
```
#### options
- `port` (number) [required] The local port number to expose through localtunnel.
- `subdomain` (string) Request a specific subdomain on the proxy server. **Note** You may not actually receive this name depending on availability.
- `host` (string) URL for the upstream proxy server. Defaults to `https://localtunnel.me`.
- `local_host` (string) Proxy to this hostname instead of `localhost`. This will also cause the `Host` header to be re-written to this value in proxied requests.
- `local_https` (boolean) Enable tunneling to local HTTPS server.
- `local_cert` (string) Path to certificate PEM file for local HTTPS server.
- `local_key` (string) Path to certificate key file for local HTTPS server.
- `local_ca` (string) Path to certificate authority file for self-signed certificates.
- `allow_invalid_cert` (boolean) Disable certificate checks for your local HTTPS server (ignore cert/key/ca options).
Refer to [tls.createSecureContext](https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options) for details on the certificate options.
### Tunnel
The `tunnel` instance returned to your callback emits the following events
| event | args | description |
| ------- | ---- | ------------------------------------------------------------------------------------ |
| request | info | fires when a request is processed by the tunnel, contains _method_ and _path_ fields |
| error | err | fires when an error happens on the tunnel |
| close | | fires when the tunnel has closed |
The `tunnel` instance has the following methods
| method | args | description |
| ------ | ---- | ---------------- |
| close | | close the tunnel |
## other clients
Clients in other languages
_go_ [gotunnelme](https://github.com/NoahShen/gotunnelme)
_go_ [go-localtunnel](https://github.com/localtunnel/go-localtunnel)
_C#/.NET_ [localtunnel-client](https://github.com/angelobreuer/localtunnel-client)
## server
See [localtunnel/server](//github.com/localtunnel/server) for details on the server that powers localtunnel.
## License
MIT
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env node
/* eslint-disable no-console */
const openurl = require('openurl');
const yargs = require('yargs');
const localtunnel = require('../localtunnel');
const { version } = require('../package');
const { argv } = yargs
.usage('Usage: lt --port [num] <options>')
.env(true)
.option('p', {
alias: 'port',
describe: 'Internal HTTP server port',
})
.option('h', {
alias: 'host',
describe: 'Upstream server providing forwarding',
default: 'https://localtunnel.me',
})
.option('s', {
alias: 'subdomain',
describe: 'Request this subdomain',
})
.option('l', {
alias: 'local-host',
describe: 'Tunnel traffic to this host instead of localhost, override Host header to this host',
})
.option('local-https', {
describe: 'Tunnel traffic to a local HTTPS server',
})
.option('local-cert', {
describe: 'Path to certificate PEM file for local HTTPS server',
})
.option('local-key', {
describe: 'Path to certificate key file for local HTTPS server',
})
.option('local-ca', {
describe: 'Path to certificate authority file for self-signed certificates',
})
.option('allow-invalid-cert', {
describe: 'Disable certificate checks for your local HTTPS server (ignore cert/key/ca options)',
})
.options('o', {
alias: 'open',
describe: 'Opens the tunnel URL in your browser',
})
.option('print-requests', {
describe: 'Print basic request info',
})
.require('port')
.boolean('local-https')
.boolean('allow-invalid-cert')
.boolean('print-requests')
.help('help', 'Show this help and exit')
.version(version);
if (typeof argv.port !== 'number') {
yargs.showHelp();
console.error('\nInvalid argument: `port` must be a number');
process.exit(1);
}
(async () => {
const tunnel = await localtunnel({
port: argv.port,
host: argv.host,
subdomain: argv.subdomain,
local_host: argv.localHost,
local_https: argv.localHttps,
local_cert: argv.localCert,
local_key: argv.localKey,
local_ca: argv.localCa,
allow_invalid_cert: argv.allowInvalidCert,
}).catch(err => {
throw err;
});
tunnel.on('error', err => {
throw err;
});
console.log('your url is: %s', tunnel.url);
/**
* `cachedUrl` is set when using a proxy server that support resource caching.
* This URL generally remains available after the tunnel itself has closed.
* @see https://github.com/localtunnel/localtunnel/pull/319#discussion_r319846289
*/
if (tunnel.cachedUrl) {
console.log('your cachedUrl is: %s', tunnel.cachedUrl);
}
if (argv.open) {
openurl.open(tunnel.url);
}
if (argv['print-requests']) {
tunnel.on('request', info => {
console.log(new Date().toString(), info.method, info.path);
});
}
})();
@@ -0,0 +1,23 @@
const { Transform } = require('stream');
class HeaderHostTransformer extends Transform {
constructor(opts = {}) {
super(opts);
this.host = opts.host || 'localhost';
this.replaced = false;
}
_transform(data, encoding, callback) {
callback(
null,
this.replaced // after replacing the first instance of the Host header we just become a regular passthrough
? data
: data.toString().replace(/(\r\n[Hh]ost: )\S+/, (match, $1) => {
this.replaced = true;
return $1 + this.host;
})
);
}
}
module.exports = HeaderHostTransformer;
+184
View File
@@ -0,0 +1,184 @@
/* eslint-disable consistent-return, no-underscore-dangle */
const { parse } = require('url');
const { EventEmitter } = require('events');
const axios = require('axios');
const debug = require('debug')('localtunnel:client');
axios.interceptors.request.use(function (config) {
// 解决 localtrunnel 返回 401 问题
config.headers['Bypass-Tunnel-Reminder'] = 'true'
return config
})
axios.interceptors.response.use(
function (response) {
return response
},
function (err) {
if (err.response == null) {
return Promise.reject(Error('网络连接异常'))
} else if (err.response.status === 500) {
return Promise.reject(Error('服务器异常'))
}
return err.response
},
)
const TunnelCluster = require('./TunnelCluster');
module.exports = class Tunnel extends EventEmitter {
constructor(opts = {}) {
super(opts);
this.opts = opts;
this.closed = false;
if (!this.opts.host) {
this.opts.host = 'https://localtunnel.me';
}
}
_getInfo(body) {
/* eslint-disable camelcase */
const { id, ip, port, url, cached_url, max_conn_count } = body;
const { host, port: local_port, local_host } = this.opts;
const { local_https, local_cert, local_key, local_ca, allow_invalid_cert } = this.opts;
return {
name: id,
url,
cached_url,
max_conn: max_conn_count || 1,
remote_host: parse(host).hostname,
remote_ip: ip,
remote_port: port,
local_port,
local_host,
local_https,
local_cert,
local_key,
local_ca,
allow_invalid_cert,
};
/* eslint-enable camelcase */
}
// initialize connection
// callback with connection info
_init(cb) {
const opt = this.opts;
const getInfo = this._getInfo.bind(this);
const params = {
responseType: 'json',
};
const baseUri = `${opt.host}/`;
// no subdomain at first, maybe use requested domain
const assignedDomain = opt.subdomain;
// where to quest
const uri = baseUri + (assignedDomain || '?new');
(function getUrl() {
axios
.get(uri, params)
.then(res => {
const body = res.data;
debug('got tunnel information', res.data);
if (res.status !== 200) {
const err = new Error(
(body && body.message) || 'localtunnel server returned an error, please try again'
);
return cb(err);
}
cb(null, getInfo(body));
})
.catch(err => {
debug(`tunnel server offline: ${err.message}, retry 1s`);
return setTimeout(getUrl, 1000);
});
})();
}
_establish(info) {
// increase max event listeners so that localtunnel consumers don't get
// warning messages as soon as they setup even one listener. See #71
this.setMaxListeners(info.max_conn + (EventEmitter.defaultMaxListeners || 10));
this.tunnelCluster = new TunnelCluster(info);
// only emit the url the first time
this.tunnelCluster.once('open', () => {
this.emit('url', info.url);
});
// re-emit socket error
this.tunnelCluster.on('error', err => {
debug('got socket error', err.message);
this.emit('error', err);
});
let tunnelCount = 0;
// track open count
this.tunnelCluster.on('open', tunnel => {
tunnelCount++;
debug('tunnel open [total: %d]', tunnelCount);
const closeHandler = () => {
tunnel.destroy();
};
if (this.closed) {
return closeHandler();
}
this.once('close', closeHandler);
tunnel.once('close', () => {
this.removeListener('close', closeHandler);
});
});
// when a tunnel dies, open a new one
this.tunnelCluster.on('dead', () => {
tunnelCount--;
debug('tunnel dead [total: %d]', tunnelCount);
if (this.closed) {
return;
}
this.tunnelCluster.open();
});
this.tunnelCluster.on('request', req => {
this.emit('request', req);
});
// establish as many tunnels as allowed
for (let count = 0; count < info.max_conn; ++count) {
this.tunnelCluster.open();
}
}
open(cb) {
this._init((err, info) => {
if (err) {
return cb(err);
}
this.clientId = info.name;
this.url = info.url;
// `cached_url` is only returned by proxy servers that support resource caching.
if (info.cached_url) {
this.cachedUrl = info.cached_url;
}
this._establish(info);
cb();
});
}
close() {
this.closed = true;
this.emit('close');
}
};
+152
View File
@@ -0,0 +1,152 @@
const { EventEmitter } = require('events');
const debug = require('debug')('localtunnel:client');
const fs = require('fs');
const net = require('net');
const tls = require('tls');
const HeaderHostTransformer = require('./HeaderHostTransformer');
// manages groups of tunnels
module.exports = class TunnelCluster extends EventEmitter {
constructor(opts = {}) {
super(opts);
this.opts = opts;
}
open() {
const opt = this.opts;
// Prefer IP if returned by the server
const remoteHostOrIp = opt.remote_ip || opt.remote_host;
const remotePort = opt.remote_port;
const localHost = opt.local_host || 'localhost';
const localPort = opt.local_port;
const localProtocol = opt.local_https ? 'https' : 'http';
const allowInvalidCert = opt.allow_invalid_cert;
debug(
'establishing tunnel %s://%s:%s <> %s:%s',
localProtocol,
localHost,
localPort,
remoteHostOrIp,
remotePort
);
// connection to localtunnel server
const remote = net.connect({
host: remoteHostOrIp,
port: remotePort,
});
remote.setKeepAlive(true);
remote.on('error', err => {
debug('got remote connection error', err.message);
// emit connection refused errors immediately, because they
// indicate that the tunnel can't be established.
if (err.code === 'ECONNREFUSED') {
this.emit(
'error',
new Error(
`connection refused: ${remoteHostOrIp}:${remotePort} (check your firewall settings)`
)
);
}
remote.end();
});
const connLocal = () => {
if (remote.destroyed) {
debug('remote destroyed');
this.emit('dead');
return;
}
debug('connecting locally to %s://%s:%d', localProtocol, localHost, localPort);
remote.pause();
if (allowInvalidCert) {
debug('allowing invalid certificates');
}
const getLocalCertOpts = () =>
allowInvalidCert
? { rejectUnauthorized: false }
: {
cert: fs.readFileSync(opt.local_cert),
key: fs.readFileSync(opt.local_key),
ca: opt.local_ca ? [fs.readFileSync(opt.local_ca)] : undefined,
};
// connection to local http server
const local = opt.local_https
? tls.connect({ host: localHost, port: localPort, ...getLocalCertOpts() })
: net.connect({ host: localHost, port: localPort });
const remoteClose = () => {
debug('remote close');
this.emit('dead');
local.end();
};
remote.once('close', remoteClose);
// TODO some languages have single threaded servers which makes opening up
// multiple local connections impossible. We need a smarter way to scale
// and adjust for such instances to avoid beating on the door of the server
local.once('error', err => {
debug('local error %s', err.message);
local.end();
remote.removeListener('close', remoteClose);
if (err.code !== 'ECONNREFUSED') {
return remote.end();
}
// retrying connection to local server
setTimeout(connLocal, 1000);
});
local.once('connect', () => {
debug('connected locally');
remote.resume();
let stream = remote;
// if user requested specific local host
// then we use host header transform to replace the host header
if (opt.local_host) {
debug('transform Host header to %s', opt.local_host);
stream = remote.pipe(new HeaderHostTransformer({ host: opt.local_host }));
}
stream.pipe(local).pipe(remote);
// when local closes, also get a new remote
local.once('close', hadError => {
debug('local connection closed [%s]', hadError);
});
});
};
remote.on('data', data => {
const match = data.toString().match(/^(\w+) (\S+)/);
if (match) {
this.emit('request', {
method: match[1],
path: match[2],
});
}
});
// tunnel is considered open when remote connects
remote.once('connect', () => {
this.emit('open', remote);
connLocal();
});
}
};
+14
View File
@@ -0,0 +1,14 @@
const Tunnel = require('./lib/Tunnel');
module.exports = function localtunnel(arg1, arg2, arg3) {
const options = typeof arg1 === 'object' ? arg1 : { ...arg2, port: arg1 };
const callback = typeof arg1 === 'object' ? arg2 : arg3;
const client = new Tunnel(options);
if (callback) {
client.open(err => (err ? callback(err) : callback(null, client)));
return client;
}
return new Promise((resolve, reject) =>
client.open(err => (err ? reject(err) : resolve(client)))
);
};
+162
View File
@@ -0,0 +1,162 @@
/* eslint-disable no-console */
const crypto = require('crypto');
const http = require('http');
const https = require('https');
const url = require('url');
const assert = require('assert');
const localtunnel = require('./localtunnel');
let fakePort;
before(done => {
const server = http.createServer();
server.on('request', (req, res) => {
res.write(req.headers.host);
res.end();
});
server.listen(() => {
const { port } = server.address();
fakePort = port;
done();
});
});
it('query localtunnel server w/ ident', async done => {
const tunnel = await localtunnel({ port: fakePort });
assert.ok(new RegExp('^https://.*localtunnel.me$').test(tunnel.url));
const parsed = url.parse(tunnel.url);
const opt = {
host: parsed.host,
port: 443,
headers: { host: parsed.hostname },
path: '/',
};
const req = https.request(opt, res => {
res.setEncoding('utf8');
let body = '';
res.on('data', chunk => {
body += chunk;
});
res.on('end', () => {
assert(/.*[.]localtunnel[.]me/.test(body), body);
tunnel.close();
done();
});
});
req.end();
});
it('request specific domain', async () => {
const subdomain = Math.random()
.toString(36)
.substr(2);
const tunnel = await localtunnel({ port: fakePort, subdomain });
assert.ok(new RegExp(`^https://${subdomain}.localtunnel.me$`).test(tunnel.url));
tunnel.close();
});
describe('--local-host localhost', () => {
it('override Host header with local-host', async done => {
const tunnel = await localtunnel({ port: fakePort, local_host: 'localhost' });
assert.ok(new RegExp('^https://.*localtunnel.me$').test(tunnel.url));
const parsed = url.parse(tunnel.url);
const opt = {
host: parsed.host,
port: 443,
headers: { host: parsed.hostname },
path: '/',
};
const req = https.request(opt, res => {
res.setEncoding('utf8');
let body = '';
res.on('data', chunk => {
body += chunk;
});
res.on('end', () => {
assert.strictEqual(body, 'localhost');
tunnel.close();
done();
});
});
req.end();
});
});
describe('--local-host 127.0.0.1', () => {
it('override Host header with local-host', async done => {
const tunnel = await localtunnel({ port: fakePort, local_host: '127.0.0.1' });
assert.ok(new RegExp('^https://.*localtunnel.me$').test(tunnel.url));
const parsed = url.parse(tunnel.url);
const opt = {
host: parsed.host,
port: 443,
headers: {
host: parsed.hostname,
},
path: '/',
};
const req = https.request(opt, res => {
res.setEncoding('utf8');
let body = '';
res.on('data', chunk => {
body += chunk;
});
res.on('end', () => {
assert.strictEqual(body, '127.0.0.1');
tunnel.close();
done();
});
});
req.end();
});
it('send chunked request', async done => {
const tunnel = await localtunnel({ port: fakePort, local_host: '127.0.0.1' });
assert.ok(new RegExp('^https://.*localtunnel.me$').test(tunnel.url));
const parsed = url.parse(tunnel.url);
const opt = {
host: parsed.host,
port: 443,
headers: {
host: parsed.hostname,
'Transfer-Encoding': 'chunked',
},
path: '/',
};
const req = https.request(opt, res => {
res.setEncoding('utf8');
let body = '';
res.on('data', chunk => {
body += chunk;
});
res.on('end', () => {
assert.strictEqual(body, '127.0.0.1');
tunnel.close();
done();
});
});
req.end(crypto.randomBytes(1024 * 8).toString('base64'));
});
});
+35
View File
@@ -0,0 +1,35 @@
{
"name": "localtunnel",
"description": "Expose localhost to the world",
"version": "2.0.2",
"license": "MIT",
"repository": {
"type": "git",
"url": "git://github.com/localtunnel/localtunnel.git"
},
"author": "Roman Shtylman <[email protected]>",
"contributors": [
"Roman Shtylman <[email protected]>",
"Gert Hengeveld <[email protected]>",
"Tom Coleman <[email protected]>"
],
"main": "./localtunnel.js",
"bin": {
"lt": "bin/lt.js"
},
"scripts": {
"test": "mocha --reporter list --timeout 60000 -- *.spec.js"
},
"dependencies": {
"axios": "0.21.4",
"debug": "4.3.2",
"openurl": "1.1.1",
"yargs": "17.1.1"
},
"devDependencies": {
"mocha": "~9.1.1"
},
"engines": {
"node": ">=8.3.0"
}
}
+602
View File
@@ -0,0 +1,602 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@ungap/[email protected]":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
[email protected]:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
ansi-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
ansi-regex@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"
anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
[email protected]:
version "0.21.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
dependencies:
follow-redirects "^1.14.0"
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
binary-extensions@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9"
integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
[email protected]:
version "1.3.1"
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
camelcase@^6.0.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
chalk@^4.1.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
[email protected]:
version "3.5.2"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
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"
optionalDependencies:
fsevents "~2.3.2"
cliui@^7.0.2:
version "7.0.4"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^7.0.0"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
[email protected]:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
[email protected]:
version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
dependencies:
ms "2.1.2"
[email protected]:
version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
dependencies:
ms "2.1.2"
decamelize@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==
[email protected]:
version "5.0.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
[email protected]:
version "4.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
[email protected]:
version "5.0.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
dependencies:
locate-path "^6.0.0"
path-exists "^4.0.0"
flat@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
follow-redirects@^1.14.0:
version "1.14.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e"
integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
[email protected]:
version "7.1.7"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
[email protected]:
version "1.10.5"
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
[email protected]:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
dependencies:
once "^1.3.0"
wrappy "1"
inherits@2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
is-fullwidth-code-point@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-plain-obj@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
is-unicode-supported@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
[email protected]:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
dependencies:
argparse "^2.0.1"
locate-path@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
dependencies:
p-locate "^5.0.0"
[email protected]:
version "4.1.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
dependencies:
chalk "^4.1.0"
is-unicode-supported "^0.1.0"
[email protected], minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"
mocha@~9.1.1:
version "9.1.1"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.1.1.tgz#33df2eb9c6262434630510c5f4283b36efda9b61"
integrity sha512-0wE74YMgOkCgBUj8VyIDwmLUjTsS13WV1Pg7l0SHea2qzZzlq7MDnfbPsHKcELBRk3+izEVkRofjmClpycudCA==
dependencies:
"@ungap/promise-all-settled" "1.1.2"
ansi-colors "4.1.1"
browser-stdout "1.3.1"
chokidar "3.5.2"
debug "4.3.1"
diff "5.0.0"
escape-string-regexp "4.0.0"
find-up "5.0.0"
glob "7.1.7"
growl "1.10.5"
he "1.2.0"
js-yaml "4.1.0"
log-symbols "4.1.0"
minimatch "3.0.4"
ms "2.1.3"
nanoid "3.1.23"
serialize-javascript "6.0.0"
strip-json-comments "3.1.1"
supports-color "8.1.1"
which "2.0.2"
wide-align "1.1.3"
workerpool "6.1.5"
yargs "16.2.0"
yargs-parser "20.2.4"
yargs-unparser "2.0.0"
[email protected]:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
[email protected]:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
[email protected]:
version "3.1.23"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81"
integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
[email protected]:
version "1.1.1"
resolved "https://registry.yarnpkg.com/openurl/-/openurl-1.1.1.tgz#3875b4b0ef7a52c156f0db41d4609dbb0f94b387"
integrity sha1-OHW0sO96UsFW8NtB1GCduw+Us4c=
p-limit@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
dependencies:
yocto-queue "^0.1.0"
p-locate@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
dependencies:
p-limit "^3.0.2"
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
picomatch@^2.0.4, picomatch@^2.2.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
dependencies:
safe-buffer "^5.1.0"
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
dependencies:
picomatch "^2.2.1"
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
safe-buffer@^5.1.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
[email protected]:
version "6.0.0"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==
dependencies:
randombytes "^2.1.0"
"string-width@^1.0.2 || 2":
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
dependencies:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
string-width@^4.1.0, string-width@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"
strip-ansi@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
dependencies:
ansi-regex "^3.0.0"
strip-ansi@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==
dependencies:
ansi-regex "^5.0.0"
[email protected]:
version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
[email protected]:
version "8.1.1"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
dependencies:
has-flag "^4.0.0"
supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
has-flag "^4.0.0"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
[email protected]:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
dependencies:
isexe "^2.0.0"
[email protected]:
version "1.1.3"
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
dependencies:
string-width "^1.0.2 || 2"
[email protected]:
version "6.1.5"
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.5.tgz#0f7cf076b6215fd7e1da903ff6f22ddd1886b581"
integrity sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
y18n@^5.0.5:
version "5.0.5"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.5.tgz#8769ec08d03b1ea2df2500acef561743bbb9ab18"
integrity sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==
[email protected], yargs-parser@^20.2.2:
version "20.2.4"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"
integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==
[email protected]:
version "2.0.0"
resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb"
integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==
dependencies:
camelcase "^6.0.0"
decamelize "^4.0.0"
flat "^5.0.2"
is-plain-obj "^2.1.0"
[email protected]:
version "16.2.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
dependencies:
cliui "^7.0.2"
escalade "^3.1.1"
get-caller-file "^2.0.5"
require-directory "^2.1.1"
string-width "^4.2.0"
y18n "^5.0.5"
yargs-parser "^20.2.2"
[email protected]:
version "17.1.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.1.1.tgz#c2a8091564bdb196f7c0a67c1d12e5b85b8067ba"
integrity sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ==
dependencies:
cliui "^7.0.2"
escalade "^3.1.1"
get-caller-file "^2.0.5"
require-directory "^2.1.1"
string-width "^4.2.0"
y18n "^5.0.5"
yargs-parser "^20.2.2"
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
@@ -1,9 +1,9 @@
{
"name": "preload",
"version": "0.1.0",
"version": "0.0.1",
"private": true,
"homepage": ".",
"app": "../utools/index.js",
"app": "../../utools/index.js",
"targets": {
"app": {
"source": "src/index.ts",
@@ -16,8 +16,7 @@
"build": "rm -rf ../utools/index.js && parcel build"
},
"dependencies": {
"axios": "^0.21.1",
"localtunnel": "^2.0.1",
"localtunnel": "^2.0.2",
"tslib": "^2.1.0"
},
"devDependencies": {
+9
View File
@@ -0,0 +1,9 @@
import localtunnel from 'localtunnel'
declare global {
interface Window {
localtunnel: typeof localtunnel
}
}
window.localtunnel = localtunnel
@@ -1044,7 +1044,14 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
[email protected].1, axios@^0.21.1:
[email protected].4:
version "0.21.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
dependencies:
follow-redirects "^1.14.0"
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
@@ -1726,14 +1733,7 @@ [email protected], debug@^2.2.0, debug@^2.3.3:
dependencies:
ms "2.0.0"
[email protected].1:
version "4.3.1"
resolved "https://registry.nlark.com/debug/download/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
integrity sha1-8NIpxQXgxtjEmsVT0bE9wYP2su4=
dependencies:
ms "2.1.2"
debug@^4.1.0:
[email protected].2, debug@^4.1.0:
version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
@@ -2199,6 +2199,11 @@ follow-redirects@^1.0.0, follow-redirects@^1.10.0:
resolved "https://registry.nlark.com/follow-redirects/download/follow-redirects-1.14.1.tgz?cache=0&sync_timestamp=1620555300559&other_urls=https%3A%2F%2Fregistry.nlark.com%2Ffollow-redirects%2Fdownload%2Ffollow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
integrity sha1-2RFN7Qoc/dM04WTmZirQK/2R/0M=
follow-redirects@^1.14.0:
version "1.14.4"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379"
integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -3057,15 +3062,15 @@ loader-utils@^1.1.0:
emojis-list "^3.0.0"
json5 "^1.0.1"
localtunnel@^2.0.1:
version "2.0.1"
resolved "https://registry.npm.taobao.org/localtunnel/download/localtunnel-2.0.1.tgz#8f7c593f3005647f7675e6e69af9bf746571a631"
integrity sha1-j3xZPzAFZH92debmmvm/dGVxpjE=
localtunnel@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/localtunnel/-/localtunnel-2.0.2.tgz#528d50087151c4790f89c2db374fe7b0a48501f0"
integrity sha512-n418Cn5ynvJd7m/N1d9WVJISLJF/ellZnfsLnx8WBWGzxv/ntNcFkJ1o6se5quUhCplfLGBNL5tYHiq5WF3Nug==
dependencies:
axios "0.21.1"
debug "4.3.1"
axios "0.21.4"
debug "4.3.2"
openurl "1.1.1"
yargs "16.2.0"
yargs "17.1.1"
lodash.camelcase@^4.3.0:
version "4.3.0"
@@ -5045,10 +5050,10 @@ yargs-parser@^20.2.2:
resolved "https://registry.nlark.com/yargs-parser/download/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
integrity sha1-LrfcOwKJcY/ClfNidThFxBoMlO4=
yargs@16.2.0:
version "16.2.0"
resolved "https://registry.nlark.com/yargs/download/yargs-16.2.0.tgz?cache=0&sync_timestamp=1626358476652&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fyargs%2Fdownload%2Fyargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
integrity sha1-HIK/D2tqZur85+8w43b0mhJHf2Y=
yargs@17.1.1:
version "17.1.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.1.1.tgz#c2a8091564bdb196f7c0a67c1d12e5b85b8067ba"
integrity sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ==
dependencies:
cliui "^7.0.2"
escalade "^3.1.1"
+14804
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
packages:
- 'packages/**'
-18
View File
@@ -1,18 +0,0 @@
import localtunnel from 'localtunnel'
import axios from 'axios'
declare global {
interface Window {
localtunnel: typeof localtunnel
}
}
axios.interceptors.request.use(
function (config) {
// 解决 localtrunnel 返回 401 问题
config.headers['Bypass-Tunnel-Reminder'] = 'true'
return config
}
)
window.localtunnel = localtunnel
+1 -1
View File
@@ -24,7 +24,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>指南针管理后台</title>
<title>内网穿透</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
+8
View File
@@ -25,6 +25,14 @@ const Help: FC = () => {
+
访
</p>
<h4></h4>
<p>
<ul>
<li>穿</li>
<li></li>
<li>HOSTlocalhost上时</li>
</ul>
</p>
<h4></h4>
<p>
<ul>
+13 -9
View File
@@ -1,13 +1,13 @@
import { Popups, useController } from 'lbl-popups'
import { Observer, useLocalStore } from 'mobx-react'
import { FC } from 'react'
import { Nat } from 'src/model/nat'
import styles from './index.module.scss'
type Data = Pick<Nat, 'port' | 'subdomain' | 'local_host'>
interface Props {
defaultValue?: {
port: number
subdomain?: string
}
defaultValue?: Data
}
const NatEdit: FC<Props> = (props) => {
@@ -16,6 +16,7 @@ const NatEdit: FC<Props> = (props) => {
return {
port: props.defaultValue?.port || '',
subdomian: props.defaultValue?.subdomain || '',
local_host: props.defaultValue?.local_host || '',
bindOnChange: (key: string) => {
return (e: any) => {
localStore[key] = e.target.value
@@ -40,12 +41,18 @@ const NatEdit: FC<Props> = (props) => {
value={localStore.subdomian}
onChange={localStore.bindOnChange('subdomian')}
/>
<input
placeholder="自定义HOST(可选)"
value={localStore.local_host}
onChange={localStore.bindOnChange('local_host')}
/>
<div
className={styles.btn}
onClick={() => {
ctl.close({
port: localStore.port,
subdomain: localStore.subdomian,
local_host: localStore.local_host,
})
}}
>
@@ -57,13 +64,10 @@ const NatEdit: FC<Props> = (props) => {
)
}
export function openNatEdit(props?: Props): Promise<{
port: number
subdomain?: string
}> {
export function openNatEdit(props?: Props): Promise<Data> {
return Popups.open({
el: NatEdit,
position: 'center',
props
props,
})
}
+3 -9
View File
@@ -5,20 +5,14 @@ import ReactDOM from 'react-dom'
import './assets/css/vs.css'
import './assets/icon/fonts/icon.css'
import { IndexPage } from './pages/manager'
import reportWebVitals from './react/reportWebVitals'
configure({
enforceActions: 'never',
})
ReactDOM.render(
<React.StrictMode>
<>
<Popups />
<IndexPage />
</React.StrictMode>,
document.getElementById('root')
</>,
document.getElementById('root'),
)
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals()
+90 -6
View File
@@ -1,5 +1,6 @@
import { EventEmitter } from 'events'
import { makeAutoObservable } from 'mobx'
import { openAlert } from 'src/components/alert'
export enum NatState {
on,
@@ -10,18 +11,101 @@ export enum NatState {
export class Nat {
constructor(it: Partial<Nat> = {}) {
makeAutoObservable(this)
if (it.subdomain === '') {
delete it.subdomain
}
this.id = Date.now()
Object.assign(this, it)
if (!it.port) throw Error('请传入端口号')
this.id = it.id || Date.now()
this.subdomain = it.subdomain || undefined
this.local_host = it.local_host || undefined
this.port = it.port
}
id: number
state = NatState.off
subdomain?: string
local_host?: string
port = 6600
state = NatState.off
tunnel?: EventEmitter & { url: string; close: () => void }
url?: string
logs: string[] = []
async start() {
try {
if (this.state === NatState.loading) return
this.state = NatState.loading
this.log('启动中')
this.tunnel = await window.localtunnel({
port: this.port,
subdomain: this.subdomain,
local_host: this.local_host,
})
if (this.state !== NatState.loading) throw Error('主动关闭')
this.url = this.tunnel.url
this.state = NatState.on
this.log(`启动成功:${this.url}`)
const onRequest = (req: any) => {
this.log(`${req.method} ${req.path}`)
}
const onError = (err: Error) => {
console.log(this.id, err)
this.log(err.message)
}
this.tunnel?.once('close', () => {
this.tunnel?.off('error', onError)
this.tunnel?.off('request', onRequest)
this.reset()
this.log('已关闭')
})
this.tunnel?.on('error', onError)
this.tunnel?.on('request', onRequest)
} catch (err) {
this.state = NatState.off
this.log('启动失败:' + err.message)
openAlert({
title: '启动失败',
content: err.message,
})
throw err
}
}
async stop() {
if (this.state === NatState.off) return
if (this.tunnel) {
return new Promise<void>((ok) => {
this.tunnel?.once('close', () => {
ok()
})
this.tunnel?.close()
})
} else {
this.reset()
}
}
reset() {
this.state = NatState.off
this.tunnel = undefined
this.url = ''
}
log(log: string) {
this.logs.push(log)
if (this.logs.length >= 1000) {
this.logs = this.logs.slice(100)
}
}
toJSON() {
return {
id: this.id,
subdomain: this.subdomain,
local_host: this.local_host,
port: this.port,
}
}
}
+57 -53
View File
@@ -32,36 +32,65 @@
.list {
padding-bottom: 100px;
.item,
.item > .lbox,
.item > .rbox {
display: flex;
align-items: center;
}
.item + .item {
border-top: 1px solid #efefef;
}
.item {
line-height: 1;
line-height: 1.3;
padding: 24px 16px;
color: #999;
&:hover {
background: rgba($color: var(--color-primary), $alpha: 0.09);
background: rgba($color: #5d9cec, $alpha: 0.06);
}
.lbox {
flex: 1;
> * + * {
margin-left: 16px;
$state-width: calc(14px + 16px);
.state {
width: 14px;
height: 14px;
background-color: #dfdfdf;
border-radius: 50%;
}
.top {
display: flex;
align-items: center;
> * {
display: flex;
align-items: center;
}
.state {
width: 14px;
height: 14px;
background-color: #dfdfdf;
border-radius: 50%;
.lbox {
width: $state-width;
}
.name {
font-weight: bold;
.cbox {
flex: 1;
.name {
font-weight: bold;
}
}
.rbox {
margin: -8px;
button {
border: none;
background: none;
padding: 0;
color: var(--color-primary);
margin: 8px;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
.del {
color: var(--color-third);
}
button:disabled {
color: #999;
}
}
}
.bottom {
padding-left: $state-width;
padding-top: 16px;
.url {
cursor: pointer;
overflow: hidden;
@@ -72,46 +101,21 @@
}
}
}
}
.rbox {
margin: -8px;
button {
border: none;
background: none;
padding: 0;
color: var(--color-primary);
margin: 8px;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
.del {
color: var(--color-third);
}
button:disabled {
color: #999;
}
.loading {
.state {
animation-name: blink;
animation-duration: 0.5s;
animation-iteration-count: infinite;
transition: all 0.5s;
}
}
.active {
color: #333;
.lbox {
.state {
background-color: var(--color-primary);
}
}
}
.loading {
.lbox {
.state {
animation-name: blink;
animation-duration: 0.5s;
animation-iteration-count: infinite;
transition: all 0.5s;
}
.state {
background-color: var(--color-primary);
}
}
}
+66 -45
View File
@@ -1,6 +1,7 @@
import classNames from 'classnames'
import { Observer } from 'mobx-react'
import React from 'react'
import { openAlert } from 'src/components/alert'
import { Icon } from '../../components/icon'
import { NatState } from '../../model/nat'
import { StoreServer } from '../../store/server'
@@ -16,13 +17,13 @@ export const IndexPage = () => {
className={styles.action}
onClick={() => utools.shellOpenExternal('https://github.com/lblblong/nat-utools')}
>
<Icon value="github-line" />
<Icon value='github-line' />
</div>
<div className={styles.action} onClick={() => StoreServer.openHelp()}>
<Icon value="question-mark" />
<Icon value='question-mark' />
</div>
<div className={styles.action} onClick={() => StoreServer.add()}>
<Icon value="add-line" />
<Icon value='add-line' />
</div>
</div>
<div className={styles.list}>
@@ -30,59 +31,79 @@ export const IndexPage = () => {
return (
<div
className={classNames(styles.item, {
[styles.active]: it.state === NatState.on,
[styles.loading]: it.state === NatState.loading,
[styles.active]: it.state === NatState.on,
})}
key={it.id}
>
<div className={styles.lbox}>
<span className={styles.state}></span>
<span className={styles.name}>
{it.port}
{it.subdomain ? ` - ${it.subdomain}` : ''}
</span>
<div className={styles.top}>
<div className={styles.lbox}>
<span className={styles.state}></span>
</div>
<div className={styles.cbox}>
<span className={styles.name}>
{it.local_host || 'localhost'}:{it.port}
{it.subdomain ? `${it.subdomain}` : ''}
</span>
</div>
<div className={styles.rbox}>
<button className={classNames(styles.btn, styles.del)} onClick={() => StoreServer.del(it.id)}>
</button>
<button
disabled={it.state === NatState.loading}
className={classNames(styles.btn)}
onClick={() => StoreServer.edit(it.id)}
>
</button>
<button className={classNames(styles.btn)} onClick={() => StoreServer.showLog(it.id)}>
</button>
<button
disabled={!it.url}
className={classNames(styles.btn)}
onClick={() => {
utools.copyText(it.url!)
utools.showNotification('地址已成功复制到剪切板')
}}
>
</button>
{/* {it.state === NatState.off && (
<button className={styles.btn} onClick={() => StoreServer.start(it.id)}>
启动服务
</button>
)} */}
<button
className={styles.btn}
onClick={() => {
it.state !== NatState.off ? StoreServer.stop(it.id) : StoreServer.start(it.id)
}}
>
{it.state !== NatState.off ? '关闭服务' : '启动服务'}
</button>
</div>
</div>
<div className={styles.bottom}>
<span
className={styles.url}
onClick={() => {
utools.shellOpenExternal(it.url!)
if (it.url) {
utools.shellOpenExternal(it.url)
} else {
openAlert({
title: '提示',
content: '请先启动服务',
})
}
}}
>
{it.url}
{it.url || `-`}
</span>
</div>
<div className={styles.rbox}>
<button className={classNames(styles.btn, styles.del)} onClick={() => StoreServer.del(it.id)}>
</button>
<button
disabled={it.state === NatState.loading}
className={classNames(styles.btn)}
onClick={() => StoreServer.edit(it.id)}
>
</button>
<button className={classNames(styles.btn)} onClick={() => StoreServer.showLog(it.id)}>
</button>
<button
disabled={!it.url}
className={classNames(styles.btn)}
onClick={() => {
utools.copyText(it.url!)
utools.showNotification('地址已成功复制到剪切板')
}}
>
</button>
<button
disabled={it.state === NatState.loading}
className={styles.btn}
onClick={() => StoreServer.toggle(it.id)}
>
{it.state === NatState.off ? '启动服务' : '关闭服务'}
</button>
</div>
</div>
)
})}
-15
View File
@@ -1,15 +0,0 @@
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
-5
View File
@@ -1,5 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
+21 -57
View File
@@ -1,3 +1,4 @@
import { proxy } from 'ajax-hook'
import localtunnel from 'localtunnel'
import { makeAutoObservable } from 'mobx'
import { openAlert } from 'src/components/alert'
@@ -13,6 +14,19 @@ declare global {
}
}
proxy({
onRequest: (config, handler) => {
if (config) config.headers['Bypass-Tunnel-Reminder'] = 'true'
handler.next(config)
},
onError: (err, handler) => {
handler.next(err)
},
onResponse: (response, handler) => {
handler.next(response)
},
})
function getStorageID() {
return utools.getLocalId() + '_servers'
}
@@ -47,37 +61,8 @@ class Store {
async start(id: number) {
const nat = this.getNatById(id)
if (nat.state === NatState.loading) return
nat.state = NatState.loading
nat.logs.push('启动中')
nat.tunnel = await window.localtunnel({
port: nat.port,
subdomain: nat.subdomain,
})
nat.url = nat.tunnel.url
nat.state = NatState.on
nat.logs.push('启动成功')
const onRequest = (req: any) => {
nat.logs.push(`${req.method} ${req.path}`)
}
const onError = (err: Error) => {
console.log(nat.id, err)
nat.logs.push(err.message)
}
nat.tunnel?.once('close', () => {
nat.tunnel?.off('error', onError)
nat.tunnel?.off('request', onRequest)
nat.state = NatState.off
nat.tunnel = undefined
nat.url = ''
nat.logs.push('已关闭')
})
nat.tunnel?.on('error', onError)
nat.tunnel?.on('request', onRequest)
if (nat.subdomain && nat.url.indexOf('//' + nat.subdomain) === -1) {
await nat.start()
if (nat.subdomain && nat.url!.indexOf('//' + nat.subdomain) === -1) {
openAlert({
title: '提示',
content: '子域名被占用,已为您分配其他子域名',
@@ -87,14 +72,7 @@ class Store {
async stop(id: number) {
const nat = this.getNatById(id)
if (nat.state === NatState.off) return
return new Promise<void>((ok) => {
nat.tunnel?.once('close', () => {
ok()
})
nat.tunnel?.close()
})
await nat.stop()
}
async del(id: number) {
@@ -111,13 +89,11 @@ class Store {
await this.stop(id)
}
const data = await openNatEdit({
defaultValue: {
port: nat.port,
subdomain: nat.subdomain,
},
defaultValue: nat.toJSON(),
})
nat.port = data.port
nat.subdomain = data.subdomain
nat.local_host = data.local_host
this.flushDb()
if (originState === NatState.on) {
this.start(id)
@@ -151,13 +127,7 @@ class Store {
}[]
>(getStorageID())
if (!it || !it.data) return
this.natList = it.data.map((d) => {
return new Nat({
id: d.id,
subdomain: d.subdomain,
port: d.port,
})
})
this.natList = it.data.map((d) => new Nat(d))
} catch (err) {
alert(err)
}
@@ -172,13 +142,7 @@ class Store {
}[]
>(getStorageID())
const data = this.natList.map((d) => {
return {
id: d.id,
subdomain: d.subdomain,
port: d.port,
}
})
const data = this.natList.map((d) => d.toJSON())
if (!it) {
it = {
+4 -6
View File
@@ -1,6 +1,6 @@
{
"extends": "./tsconfig.path.json",
"compilerOptions": {
"baseUrl": ".",
"target": "es5",
"lib": [
"dom",
@@ -11,18 +11,16 @@
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"noImplicitAny": false,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"downlevelIteration": true,
"experimentalDecorators": true,
"noEmit": true,
"jsx": "react-jsx",
"preserveSymlinks": true,
"types": [
"utools-helper/@types/utools",
"utools-helper/@types/electron",
@@ -31,6 +29,6 @@
},
"include": [
"src",
"global.d.ts"
"typing/global.d.ts"
]
}
+8
View File
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"src/*": ["./src/*"]
}
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
"homepage": "https://github.com/lblblong/nat-utools",
"main": "./build/index.html",
"preload": "./index.js",
"version": "1.2.1",
"version": "2.0.0",
"logo": "assets/logo.png",
"platform": ["win32", "darwin", "linux"],
"features": [
-12728
View File
File diff suppressed because it is too large Load Diff