You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
197 lines
5.7 KiB
197 lines
5.7 KiB
4 years ago
|
/*
|
||
|
- downloads archived binary from ngrok cdn (if not found in local cache)
|
||
|
- stores it in home dir as a local cache
|
||
|
- extracts executable to module's bin folder
|
||
|
*/
|
||
|
|
||
|
function downloadNgrok(callback, options) {
|
||
|
options = options || {};
|
||
|
|
||
|
const os = require('os');
|
||
|
const fs = require('fs');
|
||
|
const path = require('path');
|
||
|
const readline = require('readline');
|
||
|
const Zip = require('decompress-zip');
|
||
|
const request = require('request');
|
||
|
const { Transform } = require('stream');
|
||
|
|
||
|
const cafilePath = options.cafilePath || process.env.NGROK_ROOT_CA_PATH;
|
||
|
const cdnUrl = getCdnUrl();
|
||
|
const cacheUrl = getCacheUrl();
|
||
|
const maxAttempts = 3;
|
||
|
let attempts = 0;
|
||
|
|
||
|
if (hasCache()) {
|
||
|
console.log('ngrok - cached download found at ' + cacheUrl);
|
||
|
extract(retry);
|
||
|
} else {
|
||
|
download(retry);
|
||
|
}
|
||
|
|
||
|
function getCdnUrl() {
|
||
|
const arch = options.arch || process.env.NGROK_ARCH || os.platform() + os.arch();
|
||
|
const cdn = options.cdnUrl || process.env.NGROK_CDN_URL || 'https://bin.equinox.io';
|
||
|
const cdnPath = options.cdnPath || process.env.NGROK_CDN_PATH || '/c/4VmDzA7iaHb/ngrok-stable-';
|
||
|
const cdnFiles = {
|
||
|
darwinia32: cdn + cdnPath + 'darwin-386.zip',
|
||
|
darwinx64: cdn + cdnPath + 'darwin-amd64.zip',
|
||
|
linuxarm: cdn + cdnPath + 'linux-arm.zip',
|
||
|
linuxarm64: cdn + cdnPath + 'linux-arm64.zip',
|
||
|
androidarm: cdn + cdnPath + 'linux-arm.zip',
|
||
|
androidarm64: cdn + cdnPath + 'linux-arm64.zip',
|
||
|
linuxia32: cdn + cdnPath + 'linux-386.zip',
|
||
|
linuxx64: cdn + cdnPath + 'linux-amd64.zip',
|
||
|
win32ia32: cdn + cdnPath + 'windows-386.zip',
|
||
|
win32x64: cdn + cdnPath + 'windows-amd64.zip',
|
||
|
freebsdia32: cdn + cdnPath + 'freebsd-386.zip',
|
||
|
freebsdx64: cdn + cdnPath + 'freebsd-amd64.zip'
|
||
|
};
|
||
|
const url = cdnFiles[arch];
|
||
|
if (!url) {
|
||
|
console.error('ngrok - platform ' + arch + ' is not supported.');
|
||
|
process.exit(1);
|
||
|
}
|
||
|
return url;
|
||
|
}
|
||
|
|
||
|
function getCacheUrl() {
|
||
|
let dir;
|
||
|
try {
|
||
|
dir = path.join(os.homedir(), '.ngrok');
|
||
|
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
|
||
|
fs.mkdirSync(dir);
|
||
|
}
|
||
|
} catch (err) {
|
||
|
dir = path.join(__dirname, 'bin');
|
||
|
}
|
||
|
const name = Buffer.from(cdnUrl).toString('base64');
|
||
|
return path.join(dir, name + '.zip');
|
||
|
}
|
||
|
|
||
|
function hasCache() {
|
||
|
if (options.ignoreCache || process.env.NGROK_IGNORE_CACHE === 'true') {
|
||
|
return false;
|
||
|
}
|
||
|
return fs.existsSync(cacheUrl) && fs.statSync(cacheUrl).size;
|
||
|
}
|
||
|
|
||
|
function download(cb) {
|
||
|
console.log('ngrok - downloading binary ' + cdnUrl);
|
||
|
|
||
|
const ca = tryToReadCaFile();
|
||
|
|
||
|
const options = {
|
||
|
url: cdnUrl,
|
||
|
ca
|
||
|
};
|
||
|
|
||
|
const downloadStream = request
|
||
|
.get(options)
|
||
|
.on('response', res => {
|
||
|
if (!/2\d\d/.test(res.statusCode)) {
|
||
|
res.pause();
|
||
|
return downloadStream.emit('error', new Error('wrong status code: ' + res.statusCode));
|
||
|
}
|
||
|
const total = res.headers['content-length'];
|
||
|
const progress = progressStream('ngrok - downloading progress: ', total);
|
||
|
res.pipe(progress).pipe(outputStream);
|
||
|
})
|
||
|
.on('error', e => {
|
||
|
console.warn('ngrok - error downloading binary', e);
|
||
|
cb(e);
|
||
|
});
|
||
|
|
||
|
const outputStream = fs
|
||
|
.createWriteStream(cacheUrl)
|
||
|
.on('error', e => {
|
||
|
console.log('ngrok - error storing binary to local file', e);
|
||
|
cb(e);
|
||
|
})
|
||
|
.on('finish', () => {
|
||
|
console.log('\nngrok - binary downloaded to ' + cacheUrl);
|
||
|
extract(cb);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function progressStream(msg, total) {
|
||
|
let downloaded = 0;
|
||
|
let shouldClearLine = false;
|
||
|
const log = () => {
|
||
|
if (shouldClearLine) {
|
||
|
readline.clearLine(process.stdout);
|
||
|
readline.cursorTo(process.stdout, 0);
|
||
|
}
|
||
|
let progress = downloaded + (total ? '/' + total : '');
|
||
|
process.stdout.write(msg + progress);
|
||
|
shouldClearLine = true;
|
||
|
};
|
||
|
if (total > 0) log();
|
||
|
return new Transform({
|
||
|
transform(data, enc, cb) {
|
||
|
downloaded += data.length;
|
||
|
log();
|
||
|
cb(null, data);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function extract(cb) {
|
||
|
console.log('ngrok - unpacking binary');
|
||
|
const moduleBinPath = path.join(__dirname, 'bin');
|
||
|
new Zip(cacheUrl)
|
||
|
.extract({ path: moduleBinPath })
|
||
|
.once('error', error)
|
||
|
.once('extract', () => {
|
||
|
const suffix = os.platform() === 'win32' ? '.exe' : '';
|
||
|
if (suffix === '.exe') {
|
||
|
fs.writeFileSync(path.join(moduleBinPath, 'ngrok.cmd'), 'ngrok.exe');
|
||
|
}
|
||
|
const target = path.join(moduleBinPath, 'ngrok' + suffix);
|
||
|
fs.chmodSync(target, 0755);
|
||
|
if (!fs.existsSync(target) || fs.statSync(target).size <= 0) {
|
||
|
return error(new Error('corrupted file ' + target));
|
||
|
}
|
||
|
console.log('ngrok - binary unpacked to ' + target);
|
||
|
cb(null);
|
||
|
});
|
||
|
|
||
|
function error(e) {
|
||
|
console.warn('ngrok - error unpacking binary', e);
|
||
|
cb(e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function retry(err) {
|
||
|
attempts++;
|
||
|
if (err && attempts === maxAttempts) {
|
||
|
console.error('ngrok - install failed', err);
|
||
|
return callback(err);
|
||
|
}
|
||
|
if (err) {
|
||
|
console.warn('ngrok - install failed, retrying');
|
||
|
return setTimeout(download, 500, retry);
|
||
|
}
|
||
|
callback();
|
||
|
}
|
||
|
|
||
|
function tryToReadCaFile() {
|
||
|
try {
|
||
|
const caString = fs.existsSync(cafilePath) && fs.readFileSync(cafilePath).toString();
|
||
|
|
||
|
const caContents =
|
||
|
caString &&
|
||
|
caString
|
||
|
.split('-----END CERTIFICATE-----')
|
||
|
.filter(c => c.trim().startsWith('-----BEGIN CERTIFICATE-----'))
|
||
|
.map(c => `${c}-----END CERTIFICATE-----`);
|
||
|
|
||
|
return caContents.length > 0 ? caContents : undefined;
|
||
|
} catch (error) {
|
||
|
console.warn(error);
|
||
|
return undefined;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = downloadNgrok;
|