UNPKG

@applitools/execution-grid-tunnel

Version:

Allows user to run tests with exection-grid and navigate to private hosts and ips

208 lines (175 loc) 5.45 kB
const path = require('path') const {fork} = require('child_process') const {generatePromise} = require('../utils/index.js') const TUNNEL_STATUS = require('./tunnel-status.js') const SEND_MESSAGE_HEARTBEAT_INTERVAL = 3000 // This line is for pkg require('./convert-frpc-output-to-tunnel-status.js') function createFrpcTunnelController({ tunnelId, tunnelConfig, configFileRootPath, binPath, socks5Proxy, portRange, logFilePath, reconnectTimeout = 30000, frpcProcessWrapperPath = path.join(__dirname, 'frpc-process-wrapper.js'), }) { const additionalEnv = { configFileRootPath, binPath, logFilePath, socks5ProxyPort: socks5Proxy && socks5Proxy.address().port, stringifyPortRange: JSON.stringify(portRange), } return new TunnelController({ type: 'frpc', tunnelId, tunnelConfig, additionalEnv, reconnectTimeout, processWrapperPath: frpcProcessWrapperPath, logFilePath, }) } function createTcpTunnelController({ tunnelId, tunnelConfig, loggerOptions, reconnectTimeout = 30000, processWrapperPath = path.join(__dirname, 'tunnel-connection-pool-wrapper.js'), }) { const additionalEnv = { stringifyloggerOptions: JSON.stringify(loggerOptions), } return new TunnelController({ type: 'tcp', tunnelId, tunnelConfig, additionalEnv, reconnectTimeout, processWrapperPath, logFilePath: path.resolve(loggerOptions.dirname, tunnelId), }) } class TunnelController { constructor({ type, tunnelId, tunnelConfig, additionalEnv = {}, processWrapperPath, reconnectTimeout, logFilePath, }) { this._type = type this._tunnelId = tunnelId this._tunnelConfig = tunnelConfig this._additionalEnv = additionalEnv this._processWrapperPath = processWrapperPath this._reconnectTimeout = reconnectTimeout this._status = TUNNEL_STATUS.OFF this._onStatusChangedCallbacks = [] this._reconnectTimeoutId = undefined this._logFilePath = logFilePath } get status() { return this._status } set status(newStatus) { if (this._status === newStatus) { return } this._status = newStatus this._onStatusChangedCallbacks.forEach((cb) => cb(this._tunnelId, newStatus)) } get tunnelId() { return this._tunnelId } async start() { this.status = TUNNEL_STATUS.INIT const [promise, resolveFn, rejectFn] = generatePromise() const tunnelProcess = fork(this._processWrapperPath, { stdio: 'inherit', detached: false, windowsHide: true, env: { ...process.env, tunnelId: this._tunnelId, stringifyConfig: JSON.stringify(this._tunnelConfig), ...this._additionalEnv, heartbeatTimeout: SEND_MESSAGE_HEARTBEAT_INTERVAL * 7, }, }) this._frpcProcess = tunnelProcess const resolveAfterConnectingSucceededOrFailed = ({status}) => { if (status === TUNNEL_STATUS.STOPPED) { this.status = TUNNEL_STATUS.ERROR rejectFn(`Frpc was stopped. look at ${this._logFilePath} to get more information`) } if (status === TUNNEL_STATUS.INIT_ERROR || status === TUNNEL_STATUS.INIT_TIMEOUT_ERROR) { this.status = status const message = status === TUNNEL_STATUS.INIT_ERROR ? 'Start error.' : 'Timeout error.' rejectFn(`${message} look at ${this._logFilePath} to get more information`) this.stop() } if (status === TUNNEL_STATUS.RUNNING) { this.status = TUNNEL_STATUS.RUNNING resolveFn() } } tunnelProcess.once('message', resolveAfterConnectingSucceededOrFailed) await promise tunnelProcess.on('message', ({status}) => { if (status === TUNNEL_STATUS.RUNNING) { this._reconnectTimeoutId && clearTimeout(this._reconnectTimeoutId) this._reconnectTimeoutId = undefined } if (!this._reconnectTimeoutId && status === TUNNEL_STATUS.RECONNECT) { this._reconnectTimeoutId = setTimeout(() => this.stop(), this._reconnectTimeout) } this.status = status }) this._heartBitIntervalId = setInterval(() => { !tunnelProcess.killed && tunnelProcess.send({status: 'ok'}) }, SEND_MESSAGE_HEARTBEAT_INTERVAL) tunnelProcess.on('error', (err) => { console.log(err) clearInterval(this._heartBitIntervalId) this.status = TUNNEL_STATUS.ERROR }) return promise } stop() { const [promise, resolveFn, _rejectFn] = generatePromise() if (this._heartBitIntervalId) { clearInterval(this._heartBitIntervalId) this._heartBitIntervalId = undefined } if (this._reconnectTimeoutId) { clearTimeout(this._reconnectTimeoutId) this._reconnectTimeoutId = undefined } if (this.status === TUNNEL_STATUS.STOPPED || this._frpcProcess.killed) { return } this.status = TUNNEL_STATUS.STOPPING if (this._frpcProcess.exitCode === null) { this._frpcProcess.once('exit', () => { // TODO: change the flow only this class set the status to TUNNEL_STATUS.STOPPED this.status = TUNNEL_STATUS.STOPPED resolveFn() }) this._frpcProcess.kill('SIGTERM') } else { this.status = TUNNEL_STATUS.STOPPED resolveFn() } return promise } onStatusChanged(cb) { this._onStatusChangedCallbacks.push(cb) } } module.exports = {createFrpcTunnelController, createTcpTunnelController}