133 lines
4.0 KiB
JavaScript
133 lines
4.0 KiB
JavaScript
import axios from 'axios';
|
|
import debug from 'debug';
|
|
import { EventEmitter } from 'events';
|
|
import { TunnelCluster } from './TunnelCluster.js';
|
|
const log = debug('pipenet:client');
|
|
export class Tunnel extends EventEmitter {
|
|
cachedUrl;
|
|
clientId;
|
|
closed;
|
|
opts;
|
|
tunnelCluster;
|
|
url;
|
|
constructor(opts = {}) {
|
|
super();
|
|
this.opts = opts;
|
|
this.closed = false;
|
|
if (!this.opts.host) {
|
|
this.opts.host = 'https://pipenet.dev';
|
|
}
|
|
}
|
|
close() {
|
|
this.closed = true;
|
|
this.emit('close');
|
|
}
|
|
open(cb) {
|
|
this._init((err, info) => {
|
|
if (err) {
|
|
cb(err);
|
|
return;
|
|
}
|
|
this.clientId = info.name;
|
|
this.url = info.url;
|
|
if (info.cachedUrl) {
|
|
this.cachedUrl = info.cachedUrl;
|
|
}
|
|
this._establish(info);
|
|
cb();
|
|
});
|
|
}
|
|
_establish(info) {
|
|
this.setMaxListeners(info.maxConn + (EventEmitter.defaultMaxListeners || 10));
|
|
this.tunnelCluster = new TunnelCluster(info);
|
|
this.tunnelCluster.once('open', () => {
|
|
this.emit('url', info.url);
|
|
});
|
|
this.tunnelCluster.on('error', (err) => {
|
|
log('got socket error', err.message);
|
|
this.emit('error', err);
|
|
});
|
|
let tunnelCount = 0;
|
|
this.tunnelCluster.on('open', (tunnel) => {
|
|
tunnelCount++;
|
|
log('tunnel open [total: %d]', tunnelCount);
|
|
const closeHandler = () => {
|
|
tunnel.destroy();
|
|
};
|
|
if (this.closed) {
|
|
closeHandler();
|
|
return;
|
|
}
|
|
this.once('close', closeHandler);
|
|
tunnel.once('close', () => {
|
|
this.removeListener('close', closeHandler);
|
|
});
|
|
});
|
|
this.tunnelCluster.on('dead', () => {
|
|
tunnelCount--;
|
|
log('tunnel dead [total: %d]', tunnelCount);
|
|
if (this.closed) {
|
|
return;
|
|
}
|
|
this.tunnelCluster.open();
|
|
});
|
|
this.tunnelCluster.on('request', (req) => {
|
|
this.emit('request', req);
|
|
});
|
|
for (let count = 0; count < info.maxConn; ++count) {
|
|
this.tunnelCluster.open();
|
|
}
|
|
}
|
|
_getInfo(body) {
|
|
const { cachedUrl, id, ip, maxConnCount, port, sharedTunnel, url } = body;
|
|
const { host, localHost, port: localPort } = this.opts;
|
|
const { allowInvalidCert, localCa, localCert, localHttps, localKey } = this.opts;
|
|
return {
|
|
allowInvalidCert,
|
|
cachedUrl,
|
|
localCa,
|
|
localCert,
|
|
localHost,
|
|
localHttps,
|
|
localKey,
|
|
localPort,
|
|
maxConn: maxConnCount || 1,
|
|
name: id,
|
|
remoteHost: new URL(host).hostname,
|
|
remoteIp: ip,
|
|
remotePort: port,
|
|
sharedTunnel,
|
|
url,
|
|
};
|
|
}
|
|
_init(cb) {
|
|
const opt = this.opts;
|
|
const getInfo = this._getInfo.bind(this);
|
|
const params = {
|
|
headers: opt.headers || {},
|
|
responseType: 'json',
|
|
};
|
|
const baseUri = `${opt.host}/`;
|
|
const assignedDomain = opt.subdomain;
|
|
const uri = baseUri + (assignedDomain || '?new');
|
|
const getUrl = () => {
|
|
axios
|
|
.get(uri, params)
|
|
.then((res) => {
|
|
const body = res.data;
|
|
log('got tunnel information', res.data);
|
|
if (res.status !== 200) {
|
|
const err = new Error(body?.message || 'pipenet server returned an error, please try again');
|
|
return cb(err);
|
|
}
|
|
cb(null, getInfo(body));
|
|
})
|
|
.catch((err) => {
|
|
log(`tunnel server offline: ${err.message}, retry 1s`);
|
|
setTimeout(getUrl, 1000);
|
|
});
|
|
};
|
|
getUrl();
|
|
}
|
|
}
|
|
//# sourceMappingURL=Tunnel.js.map
|