index.js
2.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
const request = require('request-promise-native');
const uuid = require('uuid');
const {getProcess, killProcess, setAuthtoken, getVersion} = require('./process');
let processUrl = null;
let internalApi = null;
let tunnels = {};
async function connect (opts) {
opts = defaults(opts);
validate(opts);
if (opts.authtoken) {
await setAuthtoken(opts);
}
processUrl = await getProcess(opts);
internalApi = request.defaults({baseUrl: processUrl});
return connectRetry(opts);
}
function defaults (opts) {
opts = opts || {proto: 'http', addr: 80}
if (typeof opts === 'function') opts = {proto: 'http', addr: 80};
if (typeof opts !== 'object') opts = {proto: 'http', addr: opts};
if (!opts.proto) opts.proto = 'http';
if (!opts.addr) opts.addr = opts.port || opts.host || 80;
if (opts.httpauth) opts.auth = opts.httpauth;
return opts
}
function validate (opts) {
if (opts.web_addr === false || opts.web_addr === 'false') {
throw new Error('web_addr:false is not supported, module depends on internal ngrok api')
}
}
async function connectRetry (opts, retryCount = 0) {
opts.name = String(opts.name || uuid.v4());
try {
const response = await internalApi.post({url: 'api/tunnels', json: opts});
const publicUrl = response.public_url;
if (!publicUrl) {
throw new Error('failed to start tunnel');
}
tunnels[publicUrl] = response.uri;
if (opts.proto === 'http' && opts.bind_tls !== false) {
tunnels[publicUrl.replace('https', 'http')] = response.uri + ' (http)';
}
return publicUrl;
} catch (err) {
if (!isRetriable(err) || retryCount >= 100) {
throw err.error || err.response;
}
await new Promise((resolve) => setTimeout(resolve, 200));
return connectRetry(opts, ++retryCount);
}
}
function isRetriable (err) {
if (!err.response) return false;
const body = err.response.body;
const notReady500 = err.statusCode === 500 && /panic/.test(body)
const notReady502 = err.statusCode === 502 && body.details && body.details.err === 'tunnel session not ready yet';
const notReady503 = err.statusCode === 503 && body.details && body.details.err === 'a successful ngrok tunnel session has not yet been established';
return notReady500 || notReady502 || notReady503;
}
async function disconnect (publicUrl) {
if (!internalApi) return;
if (!publicUrl) {
const disconnectAll = Object.keys(tunnels).map(disconnect);
return Promise.all(disconnectAll);
}
const tunnelUrl = tunnels[publicUrl];
if (!tunnelUrl) {
throw new Error(`there is no tunnel with url: ${publicUrl}`)
}
await internalApi.del(tunnelUrl)
delete tunnels[publicUrl];
}
async function kill () {
if (!internalApi) return;
await killProcess();
internalApi = null;
tunnels = {}
}
function getUrl() {
return processUrl;
}
function getApi() {
return internalApi;
}
module.exports = {
connect,
disconnect,
authtoken: setAuthtoken,
kill,
getUrl,
getApi,
getVersion
};