testingbot-tunnel-launcher
Version:
A wrapper around TestingBot's Tunnel
233 lines (189 loc) • 6.56 kB
JavaScript
const async = require('async')
const { exec } = require('child_process')
const fs = require('fs')
const os = require('os')
const path = require('path')
const spawn = require('child_process').spawn
const downloader = require('./downloader')
let tunnelLocation
let activeTunnel
let started = false
function logger (msg) {
console.log.apply(console, arguments)
}
function download (options, callback) {
tunnelLocation = path.normalize(path.join(__dirname, '../testingbot-tunnel.jar'))
let url = 'https://testingbot.com/tunnel/testingbot-tunnel.jar'
if (options.tunnelVersion) {
tunnelLocation = path.normalize(path.join(__dirname, `../testingbot-tunnel-${options.tunnelVersion}.jar`))
url = `https://testingbot.com/tunnel/testingbot-tunnel-${options.tunnelVersion}.jar`
}
try {
const exists = fs.existsSync(tunnelLocation)
if (exists) {
return exec(`java -jar ${tunnelLocation} -h`, (error, stdout, stderr) => {
if (error || stderr) {
logger('Found a cached testingbot-tunnel.jar file, but it might be corrupt. Redownloading.')
downloadTunnel(url, tunnelLocation, callback)
} else {
callback(null)
}
})
}
} catch (ignore) {}
return downloadTunnel(url, tunnelLocation, callback)
}
function downloadTunnel (url, tunnelLocation, callback) {
downloader.get(url, { fileName: 'testingbot-tunnel', destination: tunnelLocation }, (err) => {
if (err) {
return callback(new Error(`Could not download the tunnel from TestingBot - please check your connection. ${err.message}`))
}
return callback(null)
})
}
function createArgs (options) {
const args = []
args.push('-jar')
args.push(tunnelLocation)
const optionMapping = {
'tunnelIdentifier': 'tunnel-identifier',
'noBump': 'nobump',
'noCache': 'nocache'
}
if (options.apiKey) {
args.push(options.apiKey)
}
if (options.apiSecret) {
args.push(options.apiSecret)
}
for (const option in options) {
if ((option === 'apiKey') || (option === 'apiSecret') || (option === 'verbose') || (option === 'tunnelVersion')) {
continue
}
const optionName = optionMapping[option] || option
if (options[option] && typeof (options[option]) === 'string') {
args.push(`--${optionName}`)
args.push(options[option])
} else if (options[option]) {
args.push(`--${optionName}`)
}
}
return args
}
function run (options, callback) {
if (!fs.existsSync(tunnelLocation)) {
return callback(new Error(`Tunnel jar file is not present in ${tunnelLocation}`))
}
const checkJava = spawn('java')
checkJava.on('error', err => {
return callback(new Error(`Java might not be installed, necessary to use testingbot-tunnel ${err.message}`))
})
const onReady = function () {
started = true
logger('Tunnel is ready')
callback(null, activeTunnel)
}
const onError = function (waitCounter) {
const errorMessage = `Tunnel failed to launch in ${waitCounter} seconds.`
logger(errorMessage)
callback(new Error(errorMessage), null)
}
const readyFile = path.join(os.tmpdir(), 'testingbot.ready')
function checkReadyFile(waitCounter = 0) {
fs.access(readyFile, fs.constants.F_OK, (error) => {
if (!error) {
clearInterval(readyFileChecker)
return onReady()
}
if (waitCounter > 90) {
clearInterval(readyFileChecker)
return onError(waitCounter)
}
waitCounter += 1
})
}
const readyFileChecker = setInterval(() => checkReadyFile(), 1000)
const args = createArgs(options)
try {
if (fs.statSync(readyFile).isFile()) {
logger('Tunnel Readyfile already exists, removing')
fs.unlinkSync(readyFile)
}
} catch (ignore) {}
args.push(`-f`)
args.push(readyFile)
if (options.verbose) {
logger('Starting tunnel with options', args)
}
activeTunnel = spawn('java', args, {})
activeTunnel.stderr.on('data', data => {
data = data.toString().trim()
if (options.verbose && data !== '') {
logger(data)
}
if (data.indexOf('is available for download') > -1) {
logger(data)
}
if (data.indexOf('401 Unauthorized') > -1) {
activeTunnel.error = 'Invalid credentials. Please supply the correct key/secret obtained from TestingBot.com'
activeTunnel.close()
} else if (data.indexOf('minutes left') > -1) {
activeTunnel.error = 'You do not have any minutes left. Please upgrade your account at TestingBot.com'
activeTunnel.close()
}
})
activeTunnel.stdout.on('data', data => {
data = data.toString().trim()
if (options.verbose && data !== '') {
logger(data)
}
})
activeTunnel.close = closeCallback => {
if (closeCallback) {
if (!activeTunnel) {
closeCallback()
} else {
activeTunnel.on('close', () => {
closeCallback()
})
}
}
activeTunnel.kill('SIGINT')
}
activeTunnel.on('exit', (code, signal) => {
if (options.verbose) {
logger('Closing TestingBot Tunnel')
}
if (!started) {
callback(new Error(activeTunnel.error ? activeTunnel.error : `Could not start TestingBot Tunnel. Exit code ${code} signal: ${signal}`))
}
started = false
activeTunnel = null
})
}
function killTunnel (callback) {
if (!callback) {
callback = function () {}
}
if (!activeTunnel) {
return callback(new Error('no active tunnel'))
}
activeTunnel.kill('SIGINT')
callback(null)
}
function downloadAndRun (options, callback) {
if (!options) {
options = {}
}
if (!callback) {
callback = function () {}
}
async.waterfall([
async.apply(download, options),
async.apply(run, options)
], callback)
}
module.exports = downloadAndRun
module.exports.kill = killTunnel
module.exports.createArgs = createArgs