buddy-tunnel
Advanced tools
Comparing version 1.0.16-dev to 1.0.17-dev
{ | ||
"name": "buddy-tunnel", | ||
"preferGlobal": false, | ||
"version": "1.0.16-dev", | ||
"version": "1.0.17-dev", | ||
"type": "module", | ||
@@ -24,2 +24,3 @@ "license": "MIT", | ||
"uuid": "9.0.1", | ||
"isbinaryfile": "5.0.2", | ||
"ws": "8.16.0" | ||
@@ -26,0 +27,0 @@ }, |
import EventEmitter from 'events'; | ||
import logger from './logger.js'; | ||
import { LOG_ERROR_WHILE_REFRESHING_AGENT } from './texts.js'; | ||
import { | ||
ERR_AGENT_REMOVED, | ||
LOG_ERROR_WHILE_REFRESHING_AGENT | ||
} from './texts.js'; | ||
import { | ||
AGENT_ACTION_DELETE, | ||
@@ -11,4 +14,5 @@ AGENT_ACTION_RESTART, | ||
} from './utils.js'; | ||
import ApiBuddy from './api/buddy.js'; | ||
import ApiBuddy, { ApiErrorAgentNotFound } from './api/buddy.js'; | ||
import Tunnel from './tunnel.js'; | ||
import Output from './output.js'; | ||
@@ -25,5 +29,7 @@ class Agent extends EventEmitter { | ||
this.manager = null; | ||
this.onRefresh = null; | ||
} | ||
startRefreshingConfiguration() { | ||
startRefreshingConfiguration(onRefresh) { | ||
this.onRefresh = onRefresh; | ||
this.refreshIn5(); | ||
@@ -91,9 +97,14 @@ } | ||
const data = await this.api.refreshAgent(this.id, this.token, this.getTunnelsUpdate(), this.started, action, resetFirstHeard); | ||
logger.info(data); | ||
if (data.agent.action) await this.makeAction(data.agent.action); | ||
this.refreshTunnels(data.tunnels || []); | ||
// todo update tunnels configuration | ||
if (this.onRefresh) this.onRefresh(true); | ||
} catch (err) { | ||
logger.error(LOG_ERROR_WHILE_REFRESHING_AGENT); | ||
logger.error(err); | ||
if (err instanceof ApiErrorAgentNotFound) { | ||
if (this.manager) this.manager.clearFileConfig(); | ||
Output.exitError(ERR_AGENT_REMOVED); | ||
} else { | ||
if (this.onRefresh) this.onRefresh(false); | ||
logger.error(LOG_ERROR_WHILE_REFRESHING_AGENT); | ||
logger.error(err); | ||
} | ||
} | ||
@@ -108,4 +119,3 @@ } | ||
}); | ||
tunnels.forEach((t) => { | ||
const data = this.convertTunnelModel(t); | ||
tunnels.forEach((data) => { | ||
const tunnel = this.tunnels.find((tunnel) => data.id === tunnel.id); | ||
@@ -121,47 +131,2 @@ if (!tunnel) { | ||
convertTunnelModel(tunnel) { | ||
const r = { | ||
id: tunnel.id, | ||
type: tunnel.type, | ||
target: tunnel.target, | ||
region: tunnel.region, | ||
timeout: tunnel.timeout, | ||
domain: tunnel.domain, | ||
subdomain: tunnel.subdomain, | ||
whitelist: tunnel.whitelist || [], | ||
useragents: [], | ||
headers: [], | ||
responseHeaders: [] | ||
}; | ||
if (tunnel.tlsSettings) { | ||
r.terminate = tunnel.tlsSettings.terminate; | ||
r.key = tunnel.tlsSettings.key; | ||
r.cert = tunnel.tlsSettings.cert; | ||
r.ca = tunnel.tlsSettings.ca; | ||
} | ||
if (tunnel.httpSettings) { | ||
r.host = tunnel.httpSettings.host; | ||
r.authLogin = tunnel.httpSettings.login; | ||
r.authPass = tunnel.httpSettings.password; | ||
r.ca = tunnel.httpSettings.ca; | ||
r.serve = tunnel.httpSettings.serve; | ||
r.useragents = tunnel.httpSettings.userAgents || []; | ||
r.headers = tunnel.httpSettings.headers || []; | ||
r.responseHeaders = tunnel.httpSettings.responseHeaders || []; | ||
r.circuitBreaker = tunnel.httpSettings.circuitBreaker; | ||
r.log = tunnel.httpSettings.log; | ||
r.verify = tunnel.httpSettings.verify; | ||
r.http2 = tunnel.httpSettings.http2; | ||
r.compression = tunnel.httpSettings.compression; | ||
} | ||
if (tunnel.sshSettings) { | ||
r.sshIp = tunnel.sshSettings.ip; | ||
r.sshPort = tunnel.sshSettings.port; | ||
r.sshUser = tunnel.sshSettings.user; | ||
r.sshPassword = tunnel.sshSettings.password; | ||
r.sshForwardPort = tunnel.sshSettings.forwardPort; | ||
} | ||
return r; | ||
} | ||
getTunnelsUpdate() { | ||
@@ -168,0 +133,0 @@ const update = []; |
@@ -22,2 +22,3 @@ import { | ||
ERR_AGENT_NOT_REGISTERED, | ||
ERR_AGENT_REMOVED, | ||
LOG_AGENT_SERVER_STARTED, | ||
@@ -29,3 +30,3 @@ LOG_AGENT_STARTED, | ||
import AgentSocket from './socket.js'; | ||
import ApiBuddy from '../api/buddy.js'; | ||
import ApiBuddy, { ApiErrorAgentNotFound } from '../api/buddy.js'; | ||
import Docker from '../docker.js'; | ||
@@ -101,6 +102,13 @@ | ||
this.agent.manager = this; | ||
this.agent.startRefreshingConfiguration(); | ||
this.agent.startRefreshingConfiguration((success) => { | ||
if (success) this.status = AGENT_STATUS_ENABLED; | ||
else this.status = AGENT_STATUS_FETCH_FAILED; | ||
}); | ||
if (this.shouldStart) await this.agent.start(); | ||
this.status = AGENT_STATUS_ENABLED; | ||
} catch (err) { | ||
if (err instanceof ApiErrorAgentNotFound) { | ||
this.clearFileConfig(); | ||
Output.exitError(ERR_AGENT_REMOVED); | ||
} | ||
Output.normal(AGENT_FETCH_RETRY); | ||
@@ -199,3 +207,3 @@ this.status = AGENT_STATUS_FETCH_FAILED; | ||
try { | ||
await ApiBuddy.removeTunnel(tunnel.id, this.agent.token); | ||
await ApiBuddy.removeTunnel(this.id, tunnel.id, this.token); | ||
} catch (err) { | ||
@@ -298,3 +306,3 @@ this.serverError(res, err.message); | ||
try { | ||
tunnel = await ApiBuddy.addTunnel(this.agent.id, this.agent.token, data); | ||
tunnel = await ApiBuddy.addTunnel(this.id, this.token, data); | ||
} catch (err) { | ||
@@ -301,0 +309,0 @@ this.serverError(res, err.message); |
@@ -79,3 +79,2 @@ import { | ||
retryHttpRequest(logRequest) { | ||
logRequest.reqBody = Buffer.from(logRequest.reqBody, 'base64'); | ||
this.tunnel.retryHttpLogRequest(logRequest); | ||
@@ -82,0 +81,0 @@ } |
@@ -55,3 +55,6 @@ import { ERR_TUNNEL_NOT_FOUND } from '../../texts.js'; | ||
this.httpLog = { | ||
requests: data.requests | ||
requests: data.requests.map((r) => { | ||
r.reqBody = Buffer.from(r.reqBody, 'base64'); | ||
return r; | ||
}) | ||
}; | ||
@@ -83,4 +86,6 @@ } | ||
const r = this.httpLog.requests.find((r) => r.id === data.id); | ||
if (!r) this.httpLog.requests.unshift(data); | ||
else { | ||
if (!r) { | ||
data.reqBody = Buffer.from(data.reqBody, 'base64'); | ||
this.httpLog.requests.unshift(data); | ||
} else { | ||
r.finished = data.finished; | ||
@@ -90,3 +95,3 @@ r.time = data.time; | ||
r.reqBodyTooLarge = data.reqBodyTooLarge; | ||
r.reqBody = data.reqBody; | ||
r.reqBody = Buffer.from(data.reqBody, 'base64'); | ||
r.headers = data.headers; | ||
@@ -93,0 +98,0 @@ r.status = data.status; |
import Output from '../output.js'; | ||
import { | ||
ERR_SWW_AGENT_ENABLING, | ||
SUC_AGENT_ENABLED | ||
TXT_AGENT_ENABLED | ||
} from '../texts.js'; | ||
@@ -18,3 +18,3 @@ import Docker from '../docker.js'; | ||
if (s.enabled) { | ||
Output.exitSuccess(SUC_AGENT_ENABLED); | ||
Output.exitSuccess(TXT_AGENT_ENABLED); | ||
} else if (timeout < 1000) { | ||
@@ -21,0 +21,0 @@ await Docker.stop(id); |
@@ -13,2 +13,3 @@ import fetch from 'node-fetch'; | ||
import { | ||
ERR_AGENT_REMOVED, | ||
ERR_FAILED_TO_CONNECT, | ||
@@ -25,5 +26,8 @@ LOG_AGENT_REGISTERED, | ||
const TIMEOUT = 10000; | ||
const TIMEOUT = 30000; | ||
const makeRequest = async (host, path, body) => { | ||
class ApiErrorAgentNotFound extends Error {} | ||
class ApiErrorFailedToConnect extends Error {} | ||
const makeRequest = async (host, path, body, method = 'POST', respAsJson = true) => { | ||
let data; | ||
@@ -37,5 +41,5 @@ try { | ||
const response = await fetch(`${host}${path}`, { | ||
body: JSON.stringify(body), | ||
headers: { 'Content-Type': 'application/json' }, | ||
method: 'POST', | ||
body: body ? JSON.stringify(body) : null, | ||
headers: body ? { 'Content-Type': 'application/json' } : null, | ||
method, | ||
signal: c.signal, | ||
@@ -46,10 +50,19 @@ agent: new HttpsAgent({ | ||
}); | ||
data = await response.json(); | ||
if (respAsJson) data = await response.json(); | ||
else data = await response.text(); | ||
} catch (err) { | ||
throw new Error(ERR_FAILED_TO_CONNECT(host)); | ||
throw new ApiErrorFailedToConnect(ERR_FAILED_TO_CONNECT(host)); | ||
} | ||
if (data.code !== 200) { | ||
throw new Error(ERR_FAILED_TO_CONNECT(host)); | ||
if (respAsJson) { | ||
if (data.code !== 200) { | ||
if (data.result.code === 234000) { | ||
throw new ApiErrorAgentNotFound(ERR_AGENT_REMOVED); | ||
} else { | ||
throw new ApiErrorFailedToConnect(ERR_FAILED_TO_CONNECT(host)); | ||
} | ||
} | ||
return data.result; | ||
} else { | ||
return data; | ||
} | ||
return data.result; | ||
}; | ||
@@ -113,3 +126,2 @@ | ||
async addTunnel(agentId, token, prepared) { | ||
// todo new url | ||
logger.info(LOG_REGISTERING_TUNNEL); | ||
@@ -126,11 +138,15 @@ const host = Cfg.getTokenHost(); | ||
async removeTunnel(id, token) { | ||
// todo new url | ||
async removeTunnel(agentId, tunnelId, token) { | ||
logger.info(LOG_REMOVING_TUNNEL); | ||
const host = Cfg.getTokenHost(); | ||
await makeRequest(host, '/tunnel/remove', { | ||
id, | ||
agentId, | ||
tunnelId, | ||
token | ||
}); | ||
} | ||
async fetchMyIp(){ | ||
return makeRequest('https://es.buddy.works', '/ip', null, 'GET', false); | ||
} | ||
} | ||
@@ -140,2 +156,7 @@ | ||
export { | ||
ApiErrorAgentNotFound, | ||
ApiErrorFailedToConnect | ||
}; | ||
export default ApiBuddy; |
@@ -22,2 +22,4 @@ import { resolve } from 'path'; | ||
} from './texts.js'; | ||
import ApiBuddy from './api/buddy.js'; | ||
import logger from './logger.js'; | ||
@@ -71,3 +73,3 @@ class Cfg { | ||
prepareTunnel(tunnelType, target, options, useDefaults) { | ||
async prepareTunnel(tunnelType, target, options, useDefaults) { | ||
const type = Input.type(tunnelType); | ||
@@ -81,3 +83,9 @@ const tunnel = { | ||
if (options.whitelist !== undefined) tunnel.whitelist = Input.whitelist(options.whitelist); | ||
else if (useDefaults) tunnel.whitelist = this.getWhitelist(); | ||
else if (useDefaults) { | ||
tunnel.whitelist = this.getWhitelist(); | ||
if (!tunnel.whitelist.length) { | ||
const myIp = await ApiBuddy.fetchMyIp(); | ||
tunnel.whitelist.push(myIp); | ||
} | ||
} | ||
if (options.timeout !== undefined) tunnel.timeout = Input.timeout(options.timeout); | ||
@@ -90,5 +98,5 @@ else if (useDefaults) tunnel.timeout = this.getTimeout(); | ||
if (options.auth !== undefined) { | ||
const { authPass, authLogin } = Input.auth(options.auth); | ||
tunnel.authLogin = authLogin; | ||
tunnel.authPass = authPass; | ||
const { password, login } = Input.auth(options.auth); | ||
tunnel.login = login; | ||
tunnel.password = password; | ||
} | ||
@@ -118,5 +126,5 @@ if (options.ca !== undefined) tunnel.ca = Input.ca(options.ca); | ||
addTunnel(name, type, target, options) { | ||
async addTunnel(name, type, target, options) { | ||
this.ensureTunnels(); | ||
this.json.tunnels[name] = this.prepareTunnel(type, target, { | ||
this.json.tunnels[name] = await this.prepareTunnel(type, target, { | ||
name, | ||
@@ -123,0 +131,0 @@ ...options |
@@ -7,4 +7,4 @@ import { Command } from 'commander'; | ||
ERR_SWW_AGENT_DISABLING, | ||
SUC_AGENT_ALREADY_DISABLED, | ||
SUC_AGENT_DISABLED | ||
TXT_AGENT_ALREADY_DISABLED, | ||
TXT_AGENT_DISABLED | ||
} from '../../texts.js'; | ||
@@ -18,3 +18,3 @@ import AgentManager from '../../agent/manager.js'; | ||
if (!commandAgentDisable.agentConfig) { | ||
Output.exitSuccess(SUC_AGENT_ALREADY_DISABLED); | ||
Output.exitSuccess(TXT_AGENT_ALREADY_DISABLED); | ||
} | ||
@@ -27,3 +27,3 @@ if (commandAgentDisable.isAgentEnabled) { | ||
if (configRemoved) { | ||
Output.exitSuccess(SUC_AGENT_DISABLED); | ||
Output.exitSuccess(TXT_AGENT_DISABLED); | ||
} else { | ||
@@ -30,0 +30,0 @@ Output.exitError(ERR_SWW_AGENT_DISABLING); |
@@ -11,3 +11,3 @@ import { Command } from 'commander'; | ||
OPTION_AGENT_TOKEN, | ||
SUC_AGENT_ALREADY_ENABLED | ||
TXT_AGENT_ALREADY_ENABLED | ||
} from '../../texts.js'; | ||
@@ -29,3 +29,3 @@ import { getVersion, } from '../../utils.js'; | ||
if (commandAgentEnable.isAgentEnabled) { | ||
Output.exitSuccess(SUC_AGENT_ALREADY_ENABLED); | ||
Output.exitSuccess(TXT_AGENT_ALREADY_ENABLED); | ||
} | ||
@@ -32,0 +32,0 @@ let id; |
@@ -6,3 +6,3 @@ import { Command } from 'commander'; | ||
ERR_AGENT_ENABLE, | ||
SUC_AGENT_RESTARTED | ||
TXT_AGENT_RESTARTED | ||
} from '../../texts.js'; | ||
@@ -19,3 +19,3 @@ import ApiAgent from '../../api/agent.js'; | ||
await ApiAgent.restartAgent(); | ||
Output.exitSuccess(SUC_AGENT_RESTARTED); | ||
Output.exitSuccess(TXT_AGENT_RESTARTED); | ||
} catch (err) { | ||
@@ -22,0 +22,0 @@ Output.exitError(err); |
@@ -6,3 +6,3 @@ import { Command } from 'commander'; | ||
ERR_AGENT_ENABLE, | ||
SUC_AGENT_STARTED | ||
TXT_AGENT_STARTED | ||
} from '../../texts.js'; | ||
@@ -19,3 +19,3 @@ import ApiAgent from '../../api/agent.js'; | ||
await ApiAgent.startAgent(); | ||
Output.exitSuccess(SUC_AGENT_STARTED); | ||
Output.exitSuccess(TXT_AGENT_STARTED); | ||
} catch (err) { | ||
@@ -22,0 +22,0 @@ Output.exitError(err); |
@@ -7,6 +7,13 @@ import { Command } from 'commander'; | ||
ERR_SWW, | ||
SUC_AGENT_IS_DISABLED, | ||
SUC_AGENT_IS_ENABLED_AND_STARTED, | ||
SUC_AGENT_IS_ENABLED_AND_STOPPED | ||
TXT_AGENT_IS_DISABLED, | ||
TXT_AGENT_IS_ENABLED_AND_HAVE_TROUBLES, | ||
TXT_AGENT_IS_ENABLED_AND_INITIALIZING, | ||
TXT_AGENT_IS_ENABLED_AND_STARTED, | ||
TXT_AGENT_IS_ENABLED_AND_STOPPED | ||
} from '../../texts.js'; | ||
import { | ||
AGENT_STATUS_ENABLED, | ||
AGENT_STATUS_FETCH_FAILED, | ||
AGENT_STATUS_INITIALIZING | ||
} from '../../utils.js'; | ||
@@ -22,11 +29,17 @@ const commandAgentStatus = new Command('status'); | ||
} | ||
if (!commandAgentStatus.agentStatus.enabled) { | ||
Output.exitError(SUC_AGENT_IS_DISABLED); | ||
} else if (commandAgentStatus.agentStatus.started) { | ||
Output.exitSuccess(SUC_AGENT_IS_ENABLED_AND_STARTED); | ||
} else { | ||
Output.exitError(SUC_AGENT_IS_ENABLED_AND_STOPPED); | ||
if (commandAgentStatus.agentStatus.status === AGENT_STATUS_INITIALIZING) { | ||
Output.exitError(TXT_AGENT_IS_ENABLED_AND_INITIALIZING); | ||
} | ||
if (commandAgentStatus.agentStatus.status === AGENT_STATUS_FETCH_FAILED) { | ||
Output.exitError(TXT_AGENT_IS_ENABLED_AND_HAVE_TROUBLES); | ||
} | ||
if (commandAgentStatus.agentStatus.status !== AGENT_STATUS_ENABLED) { | ||
Output.exitError(TXT_AGENT_IS_DISABLED); | ||
} | ||
if (commandAgentStatus.agentStatus.started) { | ||
Output.exitSuccess(TXT_AGENT_IS_ENABLED_AND_STARTED); | ||
} | ||
Output.exitError(TXT_AGENT_IS_ENABLED_AND_STOPPED); | ||
}); | ||
export default commandAgentStatus; |
@@ -6,3 +6,3 @@ import { Command } from 'commander'; | ||
ERR_AGENT_ENABLE, | ||
SUC_AGENT_STOPPED | ||
TXT_AGENT_STOPPED | ||
} from '../../texts.js'; | ||
@@ -19,3 +19,3 @@ import ApiAgent from '../../api/agent.js'; | ||
await ApiAgent.stopAgent(); | ||
Output.exitSuccess(SUC_AGENT_STOPPED); | ||
Output.exitSuccess(TXT_AGENT_STOPPED); | ||
} catch (err) { | ||
@@ -22,0 +22,0 @@ Output.exitError(err); |
@@ -9,3 +9,3 @@ import { getBasicCommandHttp } from '../../http.js'; | ||
OPTION_TARGET, | ||
SUC_TUNNEL_STARTED | ||
TXT_TUNNEL_STARTED | ||
} from '../../../texts.js'; | ||
@@ -21,6 +21,6 @@ import ApiAgent from '../../../api/agent.js'; | ||
} | ||
const prepared = Cfg.prepareTunnel(TUNNEL_HTTP, target, options, true); | ||
const prepared = await Cfg.prepareTunnel(TUNNEL_HTTP, target, options, true); | ||
try { | ||
const data = await ApiAgent.addTunnel(prepared); | ||
Output.exitSuccess(SUC_TUNNEL_STARTED(data.type)); | ||
Output.exitSuccess(TXT_TUNNEL_STARTED(data.type)); | ||
} catch (err) { | ||
@@ -27,0 +27,0 @@ Output.exitError(err); |
@@ -9,3 +9,3 @@ import { Command } from 'commander'; | ||
OPTION_NAME, | ||
SUC_TUNNEL_STARTED | ||
TXT_TUNNEL_STARTED | ||
} from '../../../texts.js'; | ||
@@ -27,3 +27,3 @@ import ApiAgent from '../../../api/agent.js'; | ||
const data = await ApiAgent.addTunnel(prepared); | ||
Output.exitSuccess(SUC_TUNNEL_STARTED(data.type)); | ||
Output.exitSuccess(TXT_TUNNEL_STARTED(data.type)); | ||
} catch (err) { | ||
@@ -30,0 +30,0 @@ Output.exitError(err); |
@@ -7,3 +7,3 @@ import { Command } from 'commander'; | ||
OPTION_ID, | ||
SUC_TUNNEL_STOPPED | ||
TXT_TUNNEL_STOPPED | ||
} from '../../../texts.js'; | ||
@@ -21,3 +21,3 @@ import ApiAgent from '../../../api/agent.js'; | ||
await ApiAgent.stopTunnel(id); | ||
Output.exitSuccess(SUC_TUNNEL_STOPPED); | ||
Output.exitSuccess(TXT_TUNNEL_STOPPED); | ||
} catch (err) { | ||
@@ -24,0 +24,0 @@ Output.exitError(err); |
@@ -9,3 +9,3 @@ import { getBasicCommandTcp } from '../../tcp.js'; | ||
OPTION_TARGET, | ||
SUC_TUNNEL_STARTED | ||
TXT_TUNNEL_STARTED | ||
} from '../../../texts.js'; | ||
@@ -21,6 +21,6 @@ import ApiAgent from '../../../api/agent.js'; | ||
} | ||
const prepared = Cfg.prepareTunnel(TUNNEL_TCP, target, options, true); | ||
const prepared = await Cfg.prepareTunnel(TUNNEL_TCP, target, options, true); | ||
try { | ||
const data = await ApiAgent.addTunnel(prepared); | ||
Output.exitSuccess(SUC_TUNNEL_STARTED(data.type)); | ||
Output.exitSuccess(TXT_TUNNEL_STARTED(data.type)); | ||
} catch (err) { | ||
@@ -27,0 +27,0 @@ Output.exitError(err); |
@@ -9,3 +9,3 @@ import { getBasicCommandTls } from '../../tls.js'; | ||
OPTION_TARGET, | ||
SUC_TUNNEL_STARTED | ||
TXT_TUNNEL_STARTED | ||
} from '../../../texts.js'; | ||
@@ -21,6 +21,6 @@ import ApiAgent from '../../../api/agent.js'; | ||
} | ||
const prepared = Cfg.prepareTunnel(TUNNEL_TLS, target, options, true); | ||
const prepared = await Cfg.prepareTunnel(TUNNEL_TLS, target, options, true); | ||
try { | ||
const data = await ApiAgent.addTunnel(prepared); | ||
Output.exitSuccess(SUC_TUNNEL_STARTED(data.type)); | ||
Output.exitSuccess(TXT_TUNNEL_STARTED(data.type)); | ||
} catch (err) { | ||
@@ -27,0 +27,0 @@ Output.exitError(err); |
@@ -14,3 +14,3 @@ import { | ||
OPTION_TARGET, | ||
SUC_TUNNEL_ADDED | ||
TXT_TUNNEL_ADDED | ||
} from '../../../texts.js'; | ||
@@ -28,6 +28,6 @@ | ||
} | ||
Cfg.addTunnel(name, TUNNEL_HTTP, target, options); | ||
Output.exitSuccess(SUC_TUNNEL_ADDED); | ||
await Cfg.addTunnel(name, TUNNEL_HTTP, target, options); | ||
Output.exitSuccess(TXT_TUNNEL_ADDED); | ||
}); | ||
export default commandConfigAddHttp; |
@@ -14,3 +14,3 @@ import { | ||
OPTION_TARGET, | ||
SUC_TUNNEL_ADDED | ||
TXT_TUNNEL_ADDED | ||
} from '../../../texts.js'; | ||
@@ -28,6 +28,6 @@ | ||
} | ||
Cfg.addTunnel(name, TUNNEL_TCP, target, options); | ||
Output.exitSuccess(SUC_TUNNEL_ADDED); | ||
await Cfg.addTunnel(name, TUNNEL_TCP, target, options); | ||
Output.exitSuccess(TXT_TUNNEL_ADDED); | ||
}); | ||
export default commandConfigAddTcp; |
@@ -14,3 +14,3 @@ import { | ||
OPTION_TARGET, | ||
SUC_TUNNEL_ADDED | ||
TXT_TUNNEL_ADDED | ||
} from '../../../texts.js'; | ||
@@ -28,6 +28,6 @@ | ||
} | ||
Cfg.addTunnel(name, TUNNEL_TLS, target, options); | ||
Output.exitSuccess(SUC_TUNNEL_ADDED); | ||
await Cfg.addTunnel(name, TUNNEL_TLS, target, options); | ||
Output.exitSuccess(TXT_TUNNEL_ADDED); | ||
}); | ||
export default commandConfigAddTls; |
@@ -8,3 +8,3 @@ import { Command } from 'commander'; | ||
OPTION_NAME, | ||
SUC_TUNNEL_REMOVED | ||
TXT_TUNNEL_REMOVED | ||
} from '../../../texts.js'; | ||
@@ -20,5 +20,5 @@ | ||
Cfg.removeTunnel(name); | ||
Output.exitSuccess(SUC_TUNNEL_REMOVED); | ||
Output.exitSuccess(TXT_TUNNEL_REMOVED); | ||
}); | ||
export default commandConfigRemoveTunnel; |
@@ -8,3 +8,3 @@ import { Command } from 'commander'; | ||
OPTION_DEFAULT_REGION, | ||
SUC_REGION_SAVED | ||
TXT_REGION_SAVED | ||
} from '../../../texts.js'; | ||
@@ -17,5 +17,5 @@ | ||
Cfg.setRegion(Input.region(region)); | ||
Output.exitSuccess(SUC_REGION_SAVED); | ||
Output.exitSuccess(TXT_REGION_SAVED); | ||
}); | ||
export default commandConfigSetRegion; |
@@ -11,3 +11,3 @@ import { Command } from 'commander'; | ||
OPTION_TIMEOUT, | ||
SUC_TIMEOUT_SAVED | ||
TXT_TIMEOUT_SAVED | ||
} from '../../../texts.js'; | ||
@@ -20,5 +20,5 @@ | ||
Cfg.setTimeout(Input.timeout(timeout)); | ||
Output.exitSuccess(SUC_TIMEOUT_SAVED); | ||
Output.exitSuccess(TXT_TIMEOUT_SAVED); | ||
}); | ||
export default commandConfigSetTimeout; |
@@ -7,3 +7,3 @@ import { Command } from 'commander'; | ||
OPTION_TOKEN, | ||
SUC_TOKEN_SAVED | ||
TXT_TOKEN_SAVED | ||
} from '../../../texts.js'; | ||
@@ -16,5 +16,5 @@ | ||
Cfg.setToken(token); | ||
Output.exitSuccess(SUC_TOKEN_SAVED); | ||
Output.exitSuccess(TXT_TOKEN_SAVED); | ||
}); | ||
export default commandConfigSetToken; |
@@ -8,3 +8,3 @@ import { Command } from 'commander'; | ||
OPTION_WHITELIST, | ||
SUC_WHITELIST_SAVED | ||
TXT_WHITELIST_SAVED | ||
} from '../../../texts.js'; | ||
@@ -17,5 +17,5 @@ | ||
Cfg.setWhitelist(Input.whitelist(whitelist)); | ||
Output.exitSuccess(SUC_WHITELIST_SAVED); | ||
Output.exitSuccess(TXT_WHITELIST_SAVED); | ||
}); | ||
export default commandConfigSetWhitelist; |
@@ -24,3 +24,5 @@ import { Command } from 'commander'; | ||
OPTION_TLS_CA, | ||
OPTION_WHITELIST | ||
OPTION_TOKEN, | ||
OPTION_WHITELIST, | ||
TXT_OPENING_TUNNEL | ||
} from '../texts.js'; | ||
@@ -54,10 +56,13 @@ import ApiBuddy from '../api/buddy.js'; | ||
commandHttp.option('-s, --serve <directory>', OPTION_SERVE); | ||
commandHttp.option('--token <token>', OPTION_TOKEN); | ||
commandHttp.argument('[protocol://host:port]', OPTION_TARGET); | ||
commandHttp.action(async (target, options) => { | ||
const prepared = Cfg.prepareTunnel(TUNNEL_HTTP, target, options, true); | ||
if (options.token) Cfg.setToken(options.token); | ||
const prepared = await Cfg.prepareTunnel(TUNNEL_HTTP, target, options, true); | ||
await Output.spinner(TXT_OPENING_TUNNEL); | ||
const agent = await ApiBuddy.register(false); | ||
await agent.start(); | ||
agent.startRefreshingConfiguration(); | ||
const tunnel = await ApiBuddy.addTunnel(agent.id, agent.token, prepared); | ||
agent.addTunnel(tunnel); | ||
await agent.start(); | ||
Output.tunnel(tunnel); | ||
@@ -64,0 +69,0 @@ }); |
@@ -7,3 +7,4 @@ import { Command } from 'commander'; | ||
ERR_TUNNEL_NOT_FOUND, | ||
OPTION_NAME | ||
OPTION_NAME, | ||
TXT_OPENING_TUNNEL | ||
} from '../texts.js'; | ||
@@ -20,7 +21,8 @@ import ApiBuddy from '../api/buddy.js'; | ||
const prepared = Cfg.getTunnel(name); | ||
await Output.spinner(TXT_OPENING_TUNNEL); | ||
const agent = await ApiBuddy.register(false); | ||
await agent.start(); | ||
agent.startRefreshingConfiguration(); | ||
const tunnel = await ApiBuddy.addTunnel(agent.id, agent.token, prepared); | ||
agent.addTunnel(tunnel); | ||
await agent.start(); | ||
Output.tunnel(tunnel); | ||
@@ -27,0 +29,0 @@ }); |
@@ -14,5 +14,8 @@ import { Command } from 'commander'; | ||
OPTION_TIMEOUT, | ||
OPTION_WHITELIST | ||
OPTION_TOKEN, | ||
OPTION_WHITELIST, | ||
TXT_OPENING_TUNNEL | ||
} from '../texts.js'; | ||
import ApiBuddy from '../api/buddy.js'; | ||
import logger from '../logger.js'; | ||
@@ -32,10 +35,13 @@ const getBasicCommandTcp = () => { | ||
commandTcp.description(DESC_COMMAND_TCP); | ||
commandTcp.option('--token <token>', OPTION_TOKEN); | ||
commandTcp.argument('[host:port]', OPTION_TARGET); | ||
commandTcp.action(async (target, options) => { | ||
const prepared = Cfg.prepareTunnel(TUNNEL_TCP, target, options, true); | ||
if (options.token) Cfg.setToken(options.token); | ||
const prepared = await Cfg.prepareTunnel(TUNNEL_TCP, target, options, true); | ||
await Output.spinner(TXT_OPENING_TUNNEL); | ||
const agent = await ApiBuddy.register(false); | ||
await agent.start(); | ||
agent.startRefreshingConfiguration(); | ||
const tunnel = await ApiBuddy.addTunnel(agent.id, agent.token, prepared); | ||
agent.addTunnel(tunnel); | ||
await agent.start(); | ||
Output.tunnel(tunnel); | ||
@@ -42,0 +48,0 @@ }); |
import { Command } from 'commander'; | ||
import { | ||
TUNNEL_TLS | ||
} from '../utils.js'; | ||
import { TUNNEL_TLS } from '../utils.js'; | ||
import Cfg from '../cfg.js'; | ||
@@ -18,3 +16,5 @@ import Output from '../output.js'; | ||
OPTION_TLS_TERMINATE, | ||
OPTION_WHITELIST | ||
OPTION_TOKEN, | ||
OPTION_WHITELIST, | ||
TXT_OPENING_TUNNEL | ||
} from '../texts.js'; | ||
@@ -40,10 +40,13 @@ import ApiBuddy from '../api/buddy.js'; | ||
commandTls.description(DESC_COMMAND_TLS); | ||
commandTls.option('--token <token>', OPTION_TOKEN); | ||
commandTls.argument('[host:port]', OPTION_TARGET); | ||
commandTls.action(async (target, options) => { | ||
const prepared = Cfg.prepareTunnel(TUNNEL_TLS, target, options, true); | ||
if (options.token) Cfg.setToken(options.token); | ||
const prepared = await Cfg.prepareTunnel(TUNNEL_TLS, target, options, true); | ||
await Output.spinner(TXT_OPENING_TUNNEL); | ||
const agent = await ApiBuddy.register(false); | ||
await agent.start(); | ||
agent.startRefreshingConfiguration(); | ||
const tunnel = await ApiBuddy.addTunnel(agent.id, agent.token, prepared); | ||
agent.addTunnel(tunnel); | ||
await agent.start(); | ||
Output.tunnel(tunnel); | ||
@@ -50,0 +53,0 @@ }); |
@@ -46,3 +46,3 @@ import { exec } from 'child_process'; | ||
let pull = '--pull="always"'; | ||
if (process.env.LOCAL) { | ||
if (process.env.DEBUG) { | ||
tag = 'local'; | ||
@@ -52,2 +52,4 @@ pull = ''; | ||
} | ||
// todo montuj katalog domowy user'a | ||
// todo dla agenta sciezki served sa wzgledem tego katalogu | ||
await this.docker(`run -d --env="BUDDY_TUNNEL_DOCKER=1" --env="BUDDY_HOSTNAME=${os.hostname()}" --name="${this.getContainerName(id)}" ${pull} --add-host="host.docker.internal:host-gateway" --restart="unless-stopped" -p "${port}:${port}" --volume="${Cfg.dir}:/root/.buddy-tunnel" "${IMAGE}:${tag}"`); | ||
@@ -54,0 +56,0 @@ return true; |
@@ -7,2 +7,3 @@ import Output from './output.js'; | ||
import { | ||
isStringRegExp, | ||
REGION_EU, | ||
@@ -79,3 +80,3 @@ REGION_US, | ||
static headers(headers) { | ||
const r = {}; | ||
const r = []; | ||
(headers || []).forEach((h) => { | ||
@@ -85,3 +86,6 @@ if (!h) return; | ||
if (s.length !== 2) return; | ||
r[s[0]] = s[1]; | ||
r.push({ | ||
name: s[0], | ||
value: s[1] | ||
}); | ||
}); | ||
@@ -123,4 +127,4 @@ return r; | ||
return { | ||
authLogin: s[0], | ||
authPass: s[1] | ||
login: s[0], | ||
password: s[1] | ||
}; | ||
@@ -132,6 +136,8 @@ } | ||
useragents.forEach((ua) => { | ||
try { | ||
new RegExp(ua); | ||
} catch (err) { | ||
Output.exitError(ERR_USER_AGENT_IS_NOT_VALID(ua)); | ||
if (isStringRegExp(ua)) { | ||
try { | ||
new RegExp(ua); | ||
} catch (err) { | ||
Output.exitError(ERR_USER_AGENT_IS_NOT_VALID(ua)); | ||
} | ||
} | ||
@@ -138,0 +144,0 @@ }); |
@@ -28,3 +28,6 @@ import TerminalKit from 'terminal-kit'; | ||
class Output { | ||
static s; | ||
static exitSuccess(msg) { | ||
this.killSpinner(); | ||
terminal.green(`${msg}\n`); | ||
@@ -35,2 +38,3 @@ process.exit(0); | ||
static exitNormal(msg) { | ||
this.killSpinner(); | ||
if (msg) terminal(`${msg}\n`); | ||
@@ -48,2 +52,16 @@ process.exit(0); | ||
static killSpinner() { | ||
if (this.s) { | ||
this.s.destroy(); | ||
this.s = null; | ||
} | ||
} | ||
static async spinner(txt) { | ||
if (!this.s) { | ||
this.s = await terminal.spinner(); | ||
if (txt) terminal(` ${txt}`); | ||
} | ||
} | ||
static formatDate() { | ||
@@ -64,2 +82,3 @@ const dt = new Date(); | ||
static tunnelInteractive(tunnel) { | ||
this.killSpinner(); | ||
const oi = new OutputInteractiveTunnel(terminal, tunnel); | ||
@@ -70,2 +89,3 @@ oi.start(); | ||
static tunnelNonInteractive(tunnel) { | ||
this.killSpinner(); | ||
const on = new OutputNoninteractiveTunnel(terminal, tunnel); | ||
@@ -142,3 +162,3 @@ on.start(); | ||
if (!headers || !headers.length) return 'Not set'; | ||
return headers.join('\n'); | ||
return headers.map((h) => `${h.name}: ${h.value}`).join('\n'); | ||
} | ||
@@ -230,4 +250,4 @@ | ||
if (latency < 0) return 'Unreachable'; | ||
if (latency < 60) return `${latency.toFixed(0)}ms`; | ||
return `${(latency / 60).toFixed(0)}s`; | ||
if (latency < 1000) return `${latency.toFixed(0)}ms`; | ||
return `${(latency / 1000).toFixed(0)}s`; | ||
} | ||
@@ -262,2 +282,6 @@ | ||
static exitError(err) { | ||
this.killSpinner(); | ||
terminal.fullscreen(false); | ||
terminal.hideCursor(false); | ||
terminal.grabInput(false); | ||
let msg; | ||
@@ -264,0 +288,0 @@ if (err instanceof Error) { |
@@ -9,3 +9,27 @@ import { | ||
import { TUNNEL_EVENT_STOPPED } from '../../tunnel.js'; | ||
import TerminalKit from 'terminal-kit'; | ||
import { isBinaryFileSync } from 'isbinaryfile'; | ||
const COLOR_CYAN = 6; | ||
const COLOR_LIGHT_GRAY = 7; | ||
const COLOR_RED = 9; | ||
const COLOR_GREEN = 10; | ||
const COLOR_WHITE = 15; | ||
const ROW_LENGTH = 200; | ||
const COLUMN_LENGTH = 30; | ||
const REQUESTS_MAX = 10; | ||
const REQUESTS_MAX_LENGTH = 80; | ||
const REQUEST_INFO_MAX_LENGTH = 100; | ||
const REQUEST_INFO_START_Y = 3; | ||
const fillString = (str, len) => { | ||
if (str.length > len) return str.substring(0, len); | ||
const need = len - str.length; | ||
for (let i = 0; i < need; i += 1) { | ||
str += ' '; | ||
} | ||
return str; | ||
}; | ||
class OutputInteractiveTunnel { | ||
@@ -15,218 +39,806 @@ constructor(terminal, tunnel) { | ||
this.tunnel = tunnel; | ||
this.viewPort = null; | ||
this.sprites = {}; | ||
this.selectedRequest = null; | ||
this.moveDown = false; | ||
this.moveUp = false; | ||
} | ||
genSpaces(count) { | ||
return new Array(count + 1).join(' '); | ||
retry() { | ||
if (this.selectedRequest && !this.selectedRequest.reqBodyTooLarge) { | ||
this.tunnel.retryHttpLogRequest(this.selectedRequest); | ||
} | ||
} | ||
renderBuddyHeader() { | ||
this.terminal(`Buddy Tunnel ${getVersion()}\n\n`); | ||
draw() { | ||
let y = 0; | ||
y += this.updateSpriteHeader(y); | ||
y += this.updateSpriteStatus(y); | ||
y += this.updateSpriteDns(y); | ||
y += this.updateSpriteType(y); | ||
y += this.updateSpriteRegion(y); | ||
y += this.updateSpriteTarget(y); | ||
y += this.updateSpriteIdentify(y); | ||
y += this.updateSpriteTerminate(y); | ||
y += this.updateSpriteEntry(y); | ||
y += this.updateSpriteLatency(y); | ||
y += this.updateSpriteConnections(y); | ||
y += this.updateSpriteRequests(y); | ||
y += this.updateSpriteRequestInfo(y); | ||
if (y < this.terminal.height) { | ||
this.viewPort.fill({ | ||
char: ' ', | ||
region: { | ||
x: 0, | ||
y, | ||
width: this.terminal.width, | ||
height: this.terminal.height - y | ||
} | ||
}); | ||
} | ||
this.viewPort.draw(); | ||
} | ||
renderTunnelLatencies() { | ||
let regionLatency = -1; | ||
let targetLatency = -1; | ||
if (this.tunnel.regionLatency) regionLatency = this.tunnel.regionLatency.latency; | ||
if (this.tunnel.serve) targetLatency = 0; | ||
else if (this.tunnel.targetLatency) targetLatency = this.tunnel.targetLatency.latency; | ||
this.terminal('\nLatencies\n'); | ||
this.terminal(' Region: '); | ||
if (regionLatency < 0) this.terminal.red('Unreachable'); | ||
else this.terminal(Output.formatLatency(regionLatency)); | ||
this.terminal('\n Target: '); | ||
if (targetLatency < 0) this.terminal.red('Unreachable'); | ||
else this.terminal(Output.formatLatency(targetLatency)); | ||
this.terminal('\n'); | ||
createSpriteConnections() { | ||
let init = fillString('', ROW_LENGTH); | ||
init += '\n'; | ||
init += fillString('Connections', ROW_LENGTH); | ||
init += '\n'; | ||
init += fillString('', ROW_LENGTH); | ||
init += '\n'; | ||
init += fillString('', ROW_LENGTH); | ||
this.sprites.connections = TerminalKit.ScreenBuffer.createFromString({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
} | ||
}, init); | ||
this.sprites.connections.put({ | ||
x: 0, | ||
y: 2, | ||
attr: { | ||
color: COLOR_LIGHT_GRAY, | ||
} | ||
}, fillString(' Current:', ROW_LENGTH)); | ||
this.sprites.connections.put({ | ||
x: 0, | ||
y: 3, | ||
attr: { | ||
color: COLOR_LIGHT_GRAY, | ||
} | ||
}, fillString(' Total:', ROW_LENGTH)); | ||
} | ||
renderTunnelConnections() { | ||
const count = Object.keys(this.tunnel.connections).length; | ||
this.terminal('\nConnections\n'); | ||
this.terminal(` Current: ${count}\n`); | ||
this.terminal(` Total: ${this.tunnel.totalConnections}\n`); | ||
createSpriteRequestInfo() { | ||
if (this.tunnel.log) { | ||
this.sprites.requestInfo = new TerminalKit.ScreenBuffer({ | ||
x: 0, | ||
y: 0, | ||
width: ROW_LENGTH, | ||
height: this.terminal.height | ||
}); | ||
this.sprites.requestInfoScroll = new TerminalKit.ScreenBuffer({ | ||
x: 0, | ||
y: REQUEST_INFO_START_Y, | ||
width: REQUEST_INFO_MAX_LENGTH, | ||
height: this.terminal.height | ||
}); | ||
} | ||
} | ||
renderTunnelRequests() { | ||
createSpriteRequests() { | ||
if (this.tunnel.log) { | ||
let header = fillString('', ROW_LENGTH); | ||
header += '\n'; | ||
header += fillString('Requests (use arrows ↑↓ to inspect & `enter` to retry)', ROW_LENGTH); | ||
header += '\n'; | ||
header += fillString('-------------------------------------------------------------------------------------------------------', ROW_LENGTH); | ||
this.sprites.requestsHeader = TerminalKit.ScreenBuffer.createFromString({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
} | ||
}, header); | ||
this.sprites.requests = new TerminalKit.ScreenBuffer({ | ||
x: 10, | ||
y: 0, | ||
width: ROW_LENGTH, | ||
height: REQUESTS_MAX | ||
}); | ||
this.sprites.requestsSelected = new TerminalKit.ScreenBuffer({ | ||
x: 0, | ||
y: 0, | ||
width: 10, | ||
height: REQUESTS_MAX | ||
}); | ||
this.sprites.requestsSelectedLast = new TerminalKit.ScreenBuffer({ | ||
x: 0, | ||
y: 0, | ||
width: ROW_LENGTH, | ||
height: 1 | ||
}); | ||
} | ||
} | ||
logRequestToText(r) { | ||
let msg = `${r.method} ${r.url}`; | ||
if (msg.length > REQUESTS_MAX_LENGTH) msg = msg.substring(0, REQUESTS_MAX_LENGTH); | ||
else msg = fillString(msg, REQUESTS_MAX_LENGTH); | ||
if (r.status === 'NEW') { | ||
msg += ' ^KNEW^'; | ||
} else if (r.status >= 500) { | ||
msg += ` ^R${r.status}^`; | ||
} else if (r.status >= 400) { | ||
msg += ` ^Y${r.status}^`; | ||
} else if (r.status >= 300) { | ||
msg += ` ^B${r.status}^`; | ||
} else { | ||
msg += ` ${r.status}`; | ||
} | ||
if (r.finished) msg += ` in ^K${r.time}ms`; | ||
return msg; | ||
} | ||
getPathAndQuery(url) { | ||
let query = {}; | ||
let path; | ||
try { | ||
const u = new URL(url, 'https://test.com'); | ||
path = u.pathname; | ||
u.searchParams.forEach((val, name) => { | ||
query[name] = val; | ||
}); | ||
} catch { | ||
path = url; | ||
} | ||
return { | ||
path, | ||
query | ||
}; | ||
} | ||
updateSpriteRequestScrollable() { | ||
if (this.selectedRequest) { | ||
this.sprites.requestInfoScroll.y = REQUEST_INFO_START_Y; | ||
this.sprites.requestInfoScroll.fill({ | ||
char: ' ', | ||
x: 0, | ||
y: 0, | ||
width: REQUEST_INFO_MAX_LENGTH, | ||
height: this.terminal.height | ||
}); | ||
const { query, path } = this.getPathAndQuery(this.selectedRequest.url); | ||
let msg = '^KHTTP version:^ '; | ||
msg += `^W${this.selectedRequest.httpVersion}^\n`; | ||
msg += '\n^KMethod:^ '; | ||
msg += `^W${this.selectedRequest.method}^\n`; | ||
msg += '\n^KPath:^ '; | ||
msg += `^W${path}^\n`; | ||
const queryKeys = Object.keys(query); | ||
if (queryKeys.length > 0) { | ||
msg += '\n^WQuery:^\n'; | ||
queryKeys.forEach((name) => { | ||
msg += `^K${name}:^ ^W${query[name]}^\n`; | ||
}); | ||
} | ||
if (this.selectedRequest.reqBody.length > 0) { | ||
msg += '\n^WBody:^\n'; | ||
if (this.selectedRequest.reqBodyTooLarge || this.selectedRequest.reqBody.length > 20 * REQUEST_INFO_MAX_LENGTH) { | ||
msg += '^K<Too large>^\n'; | ||
} else { | ||
const isBinary = isBinaryFileSync(this.selectedRequest.reqBody); | ||
if (isBinary) { | ||
msg += '^K<Binary>^\n'; | ||
} else { | ||
msg += `^K${this.selectedRequest.reqBody.toString('utf8')}^\n`; | ||
} | ||
} | ||
} | ||
msg += '\n^WHeaders:^\n'; | ||
const headers = this.selectedRequest.headers || {}; | ||
Object.keys(headers).forEach((name) => { | ||
msg += `^K${name}:^ ^W${headers[name]}^\n`; | ||
}); | ||
this.sprites.requestInfoScroll.put({ | ||
x: 0, | ||
y: 0, | ||
newLine: true, | ||
markup: true, | ||
wrap: true | ||
}, msg); | ||
} | ||
} | ||
updateSpriteRequestInfo(y) { | ||
if (this.tunnel.log) { | ||
this.sprites.requestInfo.fill({ | ||
char: ' ', | ||
region: { | ||
x: 0, | ||
y: 0, | ||
width: ROW_LENGTH, | ||
height: this.terminal.height - y | ||
} | ||
}); | ||
if (this.selectedRequest) { | ||
this.sprites.requestInfoScroll.draw({ | ||
dst: this.sprites.requestInfo, | ||
}); | ||
this.sprites.requestInfo.put({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
}, | ||
x: 0, | ||
y: 0, | ||
}, fillString('', ROW_LENGTH)); | ||
this.sprites.requestInfo.put({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
}, | ||
x: 0, | ||
y: 1, | ||
}, fillString('Request details (use `space` to scroll)', ROW_LENGTH)); | ||
this.sprites.requestInfo.put({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
}, | ||
x: 0, | ||
y: 2, | ||
}, fillString('-------------------------------------------------------------------------------------------------------', ROW_LENGTH)); | ||
} | ||
this.sprites.requestInfo.draw({ | ||
dst: this.viewPort, | ||
y | ||
}); | ||
return this.sprites.requestInfo.height; | ||
} | ||
return 0; | ||
} | ||
updateSpriteRequests(y) { | ||
if (this.tunnel.log && this.tunnel.httpLog) { | ||
this.terminal('\nRequests (use arrows ↑↓ & enter to retry)\n'); | ||
this.terminal('-----------------------------------------\n'); | ||
let maxLength = 0; | ||
const requests = this.tunnel.httpLog.requests; | ||
if (this.selectedRequest && requests.indexOf(this.selectedRequest) < 0) { | ||
this.selectedRequest = null; | ||
const max = requests.length > REQUESTS_MAX ? REQUESTS_MAX : requests.length; | ||
let col = 0; | ||
let selectedY = -1; | ||
for (let i = 0; i < max; i += 1) { | ||
const r = requests[i]; | ||
if (this.selectedRequest === r) { | ||
selectedY = i; | ||
} | ||
this.sprites.requests.put({ | ||
markup: true, | ||
x: 0, | ||
y: col | ||
}, fillString(this.logRequestToText(r), ROW_LENGTH - 10)); | ||
col += 1; | ||
} | ||
requests.forEach((logRequest, idx) => { | ||
const msg = `${logRequest.method} ${logRequest.url}`; | ||
if (maxLength < msg.length) maxLength = msg.length; | ||
if (this.moveDown) { | ||
if (!this.selectedRequest) { | ||
this.moveDown = false; | ||
this.selectedRequest = requests[0]; | ||
this.selectedRequest.selected = true; | ||
} else if (this.selectedRequest === logRequest) { | ||
this.moveDown = false; | ||
this.selectedRequest.selected = false; | ||
if (requests[idx + 1]) { | ||
this.selectedRequest = requests[idx + 1]; | ||
this.selectedRequest.selected = true; | ||
} else { | ||
this.selectedRequest = requests[0]; | ||
this.selectedRequest.selected = true; | ||
} | ||
if (col < REQUESTS_MAX) { | ||
this.sprites.requests.fill({ | ||
char: ' ', | ||
region: { | ||
x: 0, | ||
y: col, | ||
width: ROW_LENGTH, | ||
height: REQUESTS_MAX - col | ||
} | ||
} else if (this.moveUp) { | ||
if (!this.selectedRequest) { | ||
this.moveUp = false; | ||
this.selectedRequest = requests[requests.length - 1]; | ||
this.selectedRequest.selected = true; | ||
} else if (this.selectedRequest === logRequest) { | ||
this.moveUp = false; | ||
this.selectedRequest.selected = false; | ||
if (requests[idx - 1]) { | ||
this.selectedRequest = requests[idx - 1]; | ||
this.selectedRequest.selected = true; | ||
} else { | ||
this.selectedRequest = requests[requests.length - 1]; | ||
this.selectedRequest.selected = true; | ||
} | ||
} | ||
}); | ||
} | ||
this.sprites.requestsSelected.fill({ | ||
char: ' ', | ||
region: { | ||
x: 0, | ||
y: 0, | ||
width: 10, | ||
height: REQUESTS_MAX | ||
} | ||
}); | ||
if (maxLength > 80) maxLength = 80; | ||
requests.forEach((logRequest) => { | ||
let msg = `${logRequest.method} ${logRequest.url}`; | ||
if (msg.length > maxLength) msg = msg.substring(0, maxLength); | ||
else msg += this.genSpaces(maxLength - msg.length); | ||
this.terminal(msg); | ||
if (logRequest.status === 'NEW') { | ||
this.terminal.gray(` ${logRequest.status}`); | ||
} else if (logRequest.status >= 500) { | ||
this.terminal.red(` ${logRequest.status}`); | ||
} else if (logRequest.status >= 400) { | ||
this.terminal.yellow(` ${logRequest.status}`); | ||
} else if (logRequest.status >= 300) { | ||
this.terminal.blue(` ${logRequest.status}`); | ||
this.sprites.requestsSelectedLast.fill({ | ||
char: ' ', | ||
region: { | ||
x: 0, | ||
y: 0, | ||
width: ROW_LENGTH, | ||
height: 1 | ||
} | ||
}); | ||
if (this.selectedRequest) { | ||
let last; | ||
let seltxt; | ||
let selcol; | ||
if (this.selectedRequest.reqBodyTooLarge) { | ||
last = '^R[ LARGE ]^ '; | ||
seltxt = '[ LARGE ]'; | ||
selcol = COLOR_RED; | ||
} else { | ||
this.terminal(` ${logRequest.status}`); | ||
last = '^c[ RETRY ]^ '; | ||
seltxt = '[ RETRY ]'; | ||
selcol = COLOR_CYAN; | ||
} | ||
if (logRequest.finished) this.terminal.gray(` in ${logRequest.time}ms`); | ||
if (logRequest.selected) { | ||
if (logRequest.reqBodyTooLarge) this.terminal.gray(' [TOO LARGE TO RETRY]'); | ||
else this.terminal.cyan(' [RETRY]'); | ||
if (selectedY >= 0) { | ||
this.sprites.requestsSelected.put({ | ||
attr: { | ||
color: selcol | ||
}, | ||
x: 0, | ||
y: selectedY, | ||
}, fillString(seltxt, 10)); | ||
} else { | ||
last += this.logRequestToText(this.selectedRequest); | ||
this.sprites.requestsSelectedLast.put({ | ||
x: 0, | ||
y: 0, | ||
markup: true | ||
}, fillString(last, ROW_LENGTH)); | ||
} | ||
this.terminal('\n'); | ||
} | ||
this.sprites.requestsHeader.draw({ | ||
dst: this.viewPort, | ||
y | ||
}); | ||
let h = this.sprites.requestsHeader.height; | ||
this.sprites.requests.draw({ | ||
dst: this.viewPort, | ||
y: y + h | ||
}); | ||
this.sprites.requestsSelected.draw({ | ||
dst: this.viewPort, | ||
y: y + h | ||
}); | ||
h += this.sprites.requests.height; | ||
this.sprites.requestsSelectedLast.draw({ | ||
dst: this.viewPort, | ||
y: y + h | ||
}); | ||
h += this.sprites.requestsSelectedLast.height; | ||
return h; | ||
} | ||
return 0; | ||
} | ||
renderTunnelStatus() { | ||
this.terminal('Status: '); | ||
if (this.tunnel.status === TUNNEL_OPEN) this.terminal.green('OPEN'); | ||
else this.terminal.red('CLOSED'); | ||
this.terminal('\n'); | ||
moveUpDown(up = true) { | ||
if (this.tunnel.log && this.tunnel.httpLog && this.tunnel.httpLog.requests.length > 0) { | ||
const r = this.tunnel.httpLog.requests; | ||
const max = r.length > REQUESTS_MAX ? REQUESTS_MAX : r.length; | ||
if (!this.selectedRequest) this.selectedRequest = !up ? r[0] : r[max - 1]; | ||
else { | ||
let idx = -1; | ||
for (let i = 0; i < max; i += 1) { | ||
if (r[i].id === this.selectedRequest.id) { | ||
idx = !up ? i + 1 : i - 1; | ||
break; | ||
} | ||
} | ||
if (r[idx] && idx < max) this.selectedRequest = r[idx]; | ||
else if (up) this.selectedRequest = r[max - 1]; | ||
else this.selectedRequest = null; | ||
} | ||
this.updateSpriteRequestScrollable(); | ||
} | ||
} | ||
renderTunnelRegion() { | ||
this.terminal(`Region: ${Output.formatRegion(this.tunnel.region)}\n`); | ||
moveUp() { | ||
this.moveUpDown(true); | ||
} | ||
renderTunnelEntry() { | ||
this.terminal('Entry: '); | ||
this.terminal.bold(`${Output.formatEntry(this.tunnel)}\n`); | ||
moveDown() { | ||
this.moveUpDown(false); | ||
} | ||
renderType() { | ||
this.terminal(`Type: ${Output.formatType(this.tunnel.type)}\n`); | ||
space() { | ||
if (this.selectedRequest && this.sprites.requestInfoScroll) { | ||
this.sprites.requestInfoScroll.y -= 1; | ||
} | ||
} | ||
renderDns() { | ||
this.terminal('Dns: '); | ||
const { checking, valid } = this.tunnel.dns; | ||
if (valid) this.terminal.green('OK'); | ||
else if (checking) this.terminal('Checking...'); | ||
else this.terminal.red('Wrong. Clear DNS local cache'); | ||
this.terminal('\n'); | ||
createSpriteLatency() { | ||
let init = fillString('', ROW_LENGTH); | ||
init += '\n'; | ||
init += fillString('Latencies', ROW_LENGTH); | ||
init += '\n'; | ||
init += fillString('', ROW_LENGTH); | ||
init += '\n'; | ||
init += fillString('', ROW_LENGTH); | ||
this.sprites.latency = TerminalKit.ScreenBuffer.createFromString({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
} | ||
}, init); | ||
this.sprites.latency.put({ | ||
x: 0, | ||
y: 2, | ||
attr: { | ||
color: COLOR_LIGHT_GRAY, | ||
} | ||
}, fillString(' Region:', ROW_LENGTH)); | ||
this.sprites.latency.put({ | ||
x: 0, | ||
y: 3, | ||
attr: { | ||
color: COLOR_LIGHT_GRAY, | ||
} | ||
}, fillString(' Target:', ROW_LENGTH)); | ||
} | ||
renderTarget() { | ||
if (this.tunnel.serve) this.terminal(`Serve: ${Output.formatServe(this.tunnel.serve)}\n`); | ||
else this.terminal(`Target: ${Output.formatTarget(this.tunnel.type, this.tunnel.target)}\n`); | ||
createSpriteEntry() { | ||
this.sprites.entry = TerminalKit.ScreenBuffer.createFromString({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
} | ||
}, fillString('Entry:', ROW_LENGTH)); | ||
} | ||
renderHttpIdentify() { | ||
createSpriteTerminate() { | ||
if (this.tunnel.type === TUNNEL_TLS) { | ||
this.sprites.terminate = TerminalKit.ScreenBuffer.createFromString({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
} | ||
}, fillString('Terminate:', ROW_LENGTH)); | ||
} | ||
} | ||
createSpriteTarget() { | ||
this.sprites.target = TerminalKit.ScreenBuffer.createFromString({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
} | ||
}, fillString('', ROW_LENGTH)); | ||
} | ||
createSpriteHeader() { | ||
this.sprites.header = TerminalKit.ScreenBuffer.createFromString({ | ||
attr: { | ||
color: COLOR_WHITE | ||
} | ||
}, `${fillString(`Buddy Tunnel ${getVersion()}`, ROW_LENGTH)}\n${fillString('', ROW_LENGTH)}`); | ||
} | ||
createSpriteStatus() { | ||
this.sprites.status = TerminalKit.ScreenBuffer.createFromString({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
} | ||
}, fillString('Status:', ROW_LENGTH)); | ||
} | ||
createSpriteType() { | ||
this.sprites.type = TerminalKit.ScreenBuffer.createFromString({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
} | ||
}, fillString('Type:', ROW_LENGTH)); | ||
} | ||
createSpriteDns() { | ||
this.sprites.dns = TerminalKit.ScreenBuffer.createFromString({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
} | ||
}, fillString('Dns:', ROW_LENGTH)); | ||
} | ||
createSpriteRegion() { | ||
this.sprites.region = TerminalKit.ScreenBuffer.createFromString({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
} | ||
}, fillString('Region:', ROW_LENGTH)); | ||
} | ||
createSpriteIdentify() { | ||
if (this.tunnel.type === TUNNEL_HTTP) { | ||
this.terminal(`HTTP: ${Output.formatIdentify(this.tunnel.identify.type)}\n`); | ||
this.sprites.identify = TerminalKit.ScreenBuffer.createFromString({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
} | ||
}, fillString('HTTP:', ROW_LENGTH)); | ||
} | ||
} | ||
renderTlsTerminate() { | ||
updateSpriteDns(y) { | ||
let text; | ||
let color; | ||
const { | ||
checking, | ||
valid | ||
} = this.tunnel.dns; | ||
if (valid) { | ||
text = 'OK'; | ||
color = COLOR_GREEN; | ||
} else if (checking) { | ||
text = 'Checking...'; | ||
color = COLOR_WHITE; | ||
} else { | ||
text = 'Wrong. Clear DNS local cache'; | ||
color = COLOR_RED; | ||
} | ||
this.sprites.dns.put({ | ||
x: COLUMN_LENGTH, | ||
y: 0, | ||
attr: { | ||
color | ||
} | ||
}, fillString(text, ROW_LENGTH - COLUMN_LENGTH)); | ||
this.sprites.dns.draw({ | ||
dst: this.viewPort, | ||
y | ||
}); | ||
return this.sprites.dns.height; | ||
} | ||
updateSpriteHeader(y) { | ||
this.sprites.header.draw({ | ||
dst: this.viewPort, | ||
y | ||
}); | ||
return this.sprites.header.height; | ||
} | ||
updateSpriteStatus(y) { | ||
let text; | ||
let color; | ||
if (this.tunnel.status === TUNNEL_OPEN) { | ||
text = 'OPEN '; | ||
color = COLOR_GREEN; | ||
} else { | ||
text = 'CLOSED'; | ||
color = COLOR_RED; | ||
} | ||
this.sprites.status.put({ | ||
x: COLUMN_LENGTH, | ||
y: 0, | ||
attr: { | ||
color | ||
} | ||
}, fillString(text, ROW_LENGTH - COLUMN_LENGTH)); | ||
this.sprites.status.draw({ | ||
dst: this.viewPort, | ||
y | ||
}); | ||
return this.sprites.status.height; | ||
} | ||
updateSpriteType(y) { | ||
this.sprites.type.put({ | ||
x: COLUMN_LENGTH, | ||
y: 0, | ||
attr: { | ||
color: COLOR_WHITE | ||
} | ||
}, fillString(Output.formatType(this.tunnel.type), ROW_LENGTH - COLUMN_LENGTH)); | ||
this.sprites.type.draw({ | ||
dst: this.viewPort, | ||
y | ||
}); | ||
return this.sprites.type.height; | ||
} | ||
updateSpriteIdentify(y) { | ||
if (this.tunnel.type === TUNNEL_HTTP) { | ||
this.sprites.identify.put({ | ||
x: COLUMN_LENGTH, | ||
y: 0, | ||
attr: { | ||
color: COLOR_WHITE | ||
} | ||
}, fillString(Output.formatIdentify(this.tunnel.identify.type), ROW_LENGTH - COLUMN_LENGTH)); | ||
this.sprites.identify.draw({ | ||
dst: this.viewPort, | ||
y | ||
}); | ||
return this.sprites.identify.height; | ||
} | ||
return 0; | ||
} | ||
updateSpriteTarget(y) { | ||
let titleText; | ||
let valueText; | ||
if (this.tunnel.serve) { | ||
titleText = 'Server:'; | ||
valueText = Output.formatServe(this.tunnel.serve); | ||
} else { | ||
titleText = 'Target:'; | ||
valueText = Output.formatTarget(this.tunnel.type, this.tunnel.target); | ||
} | ||
this.sprites.target.put({ | ||
x: 0, | ||
y: 0, | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
} | ||
}, fillString(titleText, ROW_LENGTH)); | ||
this.sprites.target.put({ | ||
x: COLUMN_LENGTH, | ||
y: 0, | ||
attr: { | ||
color: COLOR_WHITE | ||
} | ||
}, fillString(valueText, ROW_LENGTH - COLUMN_LENGTH)); | ||
this.sprites.target.draw({ | ||
dst: this.viewPort, | ||
y | ||
}); | ||
return this.sprites.target.height; | ||
} | ||
updateSpriteRegion(y) { | ||
this.sprites.region.put({ | ||
x: COLUMN_LENGTH, | ||
y: 0, | ||
attr: { | ||
color: COLOR_WHITE | ||
} | ||
}, fillString(Output.formatRegion(this.tunnel.region), ROW_LENGTH - COLUMN_LENGTH)); | ||
this.sprites.region.draw({ | ||
dst: this.viewPort, | ||
y | ||
}); | ||
return this.sprites.region.height; | ||
} | ||
updateSpriteTerminate(y) { | ||
if (this.tunnel.type === TUNNEL_TLS) { | ||
this.terminal(`Terminate: ${Output.formatTerminate(this.tunnel.terminate)}\n`); | ||
this.sprites.terminate.put({ | ||
x: COLUMN_LENGTH, | ||
y: 0, | ||
attr: { | ||
color: COLOR_WHITE | ||
} | ||
}, fillString(Output.formatTerminate(this.tunnel.terminate), ROW_LENGTH - COLUMN_LENGTH)); | ||
this.sprites.terminate.draw({ | ||
dst: this.viewPort, | ||
y | ||
}); | ||
return this.sprites.terminate.height; | ||
} | ||
return 0; | ||
} | ||
renderTunnel() { | ||
this.renderTunnelStatus(); | ||
this.renderDns(); | ||
this.renderType(); | ||
this.renderTunnelRegion(); | ||
this.renderTarget(); | ||
this.renderHttpIdentify(); | ||
this.renderTlsTerminate(); | ||
this.renderTunnelEntry(); | ||
this.renderTunnelLatencies(); | ||
this.renderTunnelConnections(); | ||
this.renderTunnelRequests(); | ||
updateSpriteEntry(y) { | ||
this.sprites.entry.put({ | ||
x: COLUMN_LENGTH, | ||
y: 0, | ||
attr: { | ||
color: COLOR_CYAN, | ||
bold: true | ||
} | ||
}, fillString(Output.formatEntry(this.tunnel), ROW_LENGTH - COLUMN_LENGTH)); | ||
this.sprites.entry.draw({ | ||
dst: this.viewPort, | ||
y | ||
}); | ||
return this.sprites.entry.height; | ||
} | ||
refresh() { | ||
if (this.drawing) return; | ||
this.drawing = true; | ||
this.terminal.clear(); | ||
this.renderBuddyHeader(); | ||
this.renderTunnel(); | ||
this.drawing = false; | ||
updateSpriteConnections(y) { | ||
const count = Object.keys(this.tunnel.connections).length; | ||
this.sprites.connections.put({ | ||
x: COLUMN_LENGTH, | ||
y: 2, | ||
attr: { | ||
color: COLOR_WHITE, | ||
} | ||
}, fillString(String(count), ROW_LENGTH - COLUMN_LENGTH)); | ||
this.sprites.connections.put({ | ||
x: COLUMN_LENGTH, | ||
y: 3, | ||
attr: { | ||
color: COLOR_WHITE, | ||
} | ||
}, fillString(String(this.tunnel.totalConnections), ROW_LENGTH - COLUMN_LENGTH)); | ||
this.sprites.connections.draw({ | ||
dst: this.viewPort, | ||
y | ||
}); | ||
return this.sprites.connections.height; | ||
} | ||
retry() { | ||
if (this.selectedRequest && !this.selectedRequest.reqBodyTooLarge) { | ||
this.tunnel.retryHttpLogRequest(this.selectedRequest); | ||
updateSpriteLatency(y) { | ||
let regionLatency = -1; | ||
let targetLatency = -1; | ||
if (this.tunnel.regionLatency) regionLatency = this.tunnel.regionLatency.latency; | ||
if (this.tunnel.serve) targetLatency = 0; | ||
else if (this.tunnel.targetLatency) targetLatency = this.tunnel.targetLatency.latency; | ||
let region; | ||
let regionColor; | ||
let target; | ||
let targetColor; | ||
if (regionLatency < 0) { | ||
region = 'Unreachable'; | ||
regionColor = COLOR_RED; | ||
} else { | ||
region = Output.formatLatency(regionLatency); | ||
regionColor = COLOR_WHITE; | ||
} | ||
if (targetLatency < 0) { | ||
target = 'Unreachable'; | ||
targetColor = COLOR_RED; | ||
} else { | ||
target = Output.formatLatency(targetLatency); | ||
targetColor = COLOR_WHITE; | ||
} | ||
this.sprites.latency.put({ | ||
x: COLUMN_LENGTH, | ||
y: 2, | ||
attr: { | ||
color: regionColor, | ||
} | ||
}, fillString(region, ROW_LENGTH - COLUMN_LENGTH)); | ||
this.sprites.latency.put({ | ||
x: COLUMN_LENGTH, | ||
y: 3, | ||
attr: { | ||
color: targetColor, | ||
} | ||
}, fillString(target, ROW_LENGTH - COLUMN_LENGTH)); | ||
this.sprites.latency.draw({ | ||
dst: this.viewPort, | ||
y | ||
}); | ||
return this.sprites.latency.height; | ||
} | ||
start() { | ||
init() { | ||
this.viewPort = new TerminalKit.ScreenBuffer({ | ||
dst: this.terminal, | ||
width: Math.min(this.terminal.width), | ||
height: Math.min(this.terminal.height), | ||
x: 1, | ||
y: 1 | ||
}); | ||
this.createSpriteHeader(); | ||
this.createSpriteStatus(); | ||
this.createSpriteType(); | ||
this.createSpriteDns(); | ||
this.createSpriteRegion(); | ||
this.createSpriteTarget(); | ||
this.createSpriteIdentify(); | ||
this.createSpriteTerminate(); | ||
this.createSpriteEntry(); | ||
this.createSpriteLatency(); | ||
this.createSpriteConnections(); | ||
this.createSpriteRequests(); | ||
this.createSpriteRequestInfo(); | ||
this.terminal.grabInput(); | ||
this.terminal.hideCursor(); | ||
this.terminal.fullscreen(true); | ||
this.terminal.on('key', (name) => { | ||
if (name === 'CTRL_C') { | ||
this.terminal.fullscreen(false); | ||
this.terminal.grabInput(false); | ||
process.exit(); | ||
this.terminate(); | ||
} else if (name === 'ENTER') { | ||
this.retry(); | ||
} else if (!this.drawing) { | ||
if (name === 'DOWN') { | ||
this.moveDown = true; | ||
this.refresh(); | ||
} else if (name === 'UP') { | ||
this.moveUp = true; | ||
this.refresh(); | ||
} | ||
} else if (name === 'DOWN') { | ||
this.moveDown(); | ||
} else if (name === 'UP') { | ||
this.moveUp(); | ||
} else if (name === ' ') { | ||
this.space(); | ||
} | ||
}); | ||
this.terminal.on('resize', () => { | ||
this.draw(); | ||
}); | ||
this.tunnel.on(TUNNEL_EVENT_STOPPED, () => { | ||
this.terminal.fullscreen(false); | ||
this.terminal.grabInput(false); | ||
process.exit(); | ||
this.terminate(); | ||
}); | ||
setInterval(() => { | ||
this.refresh(); | ||
}, 100); | ||
this.refresh(); | ||
} | ||
terminate() { | ||
this.terminal.fullscreen(false); | ||
this.terminal.hideCursor(false); | ||
this.terminal.grabInput(false); | ||
process.exit(); | ||
} | ||
animate() { | ||
this.draw(); | ||
setTimeout(() => { | ||
this.animate(); | ||
}, 50); | ||
} | ||
start() { | ||
this.init(); | ||
this.animate(); | ||
} | ||
} | ||
export default OutputInteractiveTunnel; |
@@ -30,3 +30,3 @@ import { | ||
data.push(['Host Header', Output.formatHostHeader(this.tunnel.host)]); | ||
data.push(['Basic Auth', Output.formatBasicAuth(this.tunnel.authLogin, this.tunnel.authPass)]); | ||
data.push(['Basic Auth', Output.formatBasicAuth(this.tunnel.login, this.tunnel.password)]); | ||
data.push(['User Agents', Output.formatTunnelUserAgent(this.tunnel.useragents)]); | ||
@@ -33,0 +33,0 @@ data.push(['Request Headers', Output.formatTunnelHeaders(this.tunnel.headers)]); |
@@ -34,2 +34,3 @@ const ERR_AGENT_NOT_REGISTERED = 'Agent not registered. Exiting...'; | ||
const ERR_FAILED_TO_CONNECT = (host) => `Failed to connect to '${host}'`; | ||
const ERR_AGENT_REMOVED = 'Agent has been removed'; | ||
const ERR_FAILED_TO_CONNECT_TO_AGENT = 'Failed to connect to agent'; | ||
@@ -48,20 +49,22 @@ const ERR_NOT_FOUND = 'Not found'; | ||
const SUC_AGENT_STOPPED = 'Agent stopped'; | ||
const SUC_AGENT_STARTED = 'Agent started'; | ||
const SUC_AGENT_RESTARTED = 'Agent restarted'; | ||
const SUC_AGENT_IS_ENABLED_AND_STARTED = 'Agent is enabled & started'; | ||
const SUC_AGENT_IS_ENABLED_AND_STOPPED = 'Agent is enabled & stopped'; | ||
const SUC_AGENT_IS_DISABLED = 'Agent is disabled'; | ||
const SUC_AGENT_ENABLED = 'Agent enabled'; | ||
const SUC_AGENT_ALREADY_ENABLED = 'Agent already enabled'; | ||
const SUC_AGENT_DISABLED = 'Agent disabled'; | ||
const SUC_AGENT_ALREADY_DISABLED = 'Agent already disabled'; | ||
const SUC_TUNNEL_STARTED = (type) => `${type} tunnel started`; | ||
const SUC_TUNNEL_STOPPED = 'Tunnel stopped'; | ||
const SUC_WHITELIST_SAVED = 'Whitelist saved'; | ||
const SUC_TOKEN_SAVED = 'Token saved'; | ||
const SUC_TIMEOUT_SAVED = 'Timeout saved'; | ||
const SUC_REGION_SAVED = 'Region saved'; | ||
const SUC_TUNNEL_REMOVED = 'Tunnel removed'; | ||
const SUC_TUNNEL_ADDED = 'Tunnel added'; | ||
const TXT_AGENT_STOPPED = 'Agent stopped'; | ||
const TXT_AGENT_STARTED = 'Agent started'; | ||
const TXT_AGENT_RESTARTED = 'Agent restarted'; | ||
const TXT_AGENT_IS_ENABLED_AND_STARTED = 'Agent is enabled & started'; | ||
const TXT_AGENT_IS_ENABLED_AND_STOPPED = 'Agent is enabled & stopped'; | ||
const TXT_AGENT_IS_ENABLED_AND_INITIALIZING = 'Agent is enabled & initializing'; | ||
const TXT_AGENT_IS_ENABLED_AND_HAVE_TROUBLES = 'Agent is enabled & have connection issues'; | ||
const TXT_AGENT_IS_DISABLED = 'Agent is disabled'; | ||
const TXT_AGENT_ENABLED = 'Agent enabled'; | ||
const TXT_AGENT_ALREADY_ENABLED = 'Agent already enabled'; | ||
const TXT_AGENT_DISABLED = 'Agent disabled'; | ||
const TXT_AGENT_ALREADY_DISABLED = 'Agent already disabled'; | ||
const TXT_TUNNEL_STARTED = (type) => `${type} tunnel started`; | ||
const TXT_TUNNEL_STOPPED = 'Tunnel stopped'; | ||
const TXT_WHITELIST_SAVED = 'Whitelist saved'; | ||
const TXT_TOKEN_SAVED = 'Token saved'; | ||
const TXT_TIMEOUT_SAVED = 'Timeout saved'; | ||
const TXT_REGION_SAVED = 'Region saved'; | ||
const TXT_TUNNEL_REMOVED = 'Tunnel removed'; | ||
const TXT_TUNNEL_ADDED = 'Tunnel added'; | ||
@@ -117,2 +120,3 @@ const NO_TUNNELS_STARTED = 'No tunnels started'; | ||
const TXT_ENABLING_AGENT = 'Enabling agent...'; | ||
const TXT_OPENING_TUNNEL = 'Opening tunnel...'; | ||
@@ -124,3 +128,3 @@ const OPTION_REGION = 'override default region ("eu", "us")'; | ||
const OPTION_RESPONSE_HEADER = 'header key:value to add to the response'; | ||
const OPTION_HEADER_USER_AGENT = 'whitelist browser user-agent by regular expressions. By Default all are allowed'; | ||
const OPTION_HEADER_USER_AGENT = 'whitelist browser user-agent by contained text or regular expressions (e.g. /my-user-agent/). By Default all are allowed'; | ||
const OPTION_SERVE = 'serve files from a directory'; | ||
@@ -309,22 +313,25 @@ const OPTION_SUBDOMAIN = 'provide custom subdomain'; | ||
ERR_FAILED_TO_CONNECT, | ||
ERR_AGENT_REMOVED, | ||
AGENT_FETCH_RETRY, | ||
NO_TUNNELS_STARTED, | ||
SUC_REGION_SAVED, | ||
SUC_TUNNEL_REMOVED, | ||
SUC_TUNNEL_ADDED, | ||
SUC_TIMEOUT_SAVED, | ||
SUC_TOKEN_SAVED, | ||
SUC_WHITELIST_SAVED, | ||
SUC_TUNNEL_STOPPED, | ||
SUC_TUNNEL_STARTED, | ||
SUC_AGENT_ENABLED, | ||
SUC_AGENT_ALREADY_ENABLED, | ||
SUC_AGENT_DISABLED, | ||
SUC_AGENT_ALREADY_DISABLED, | ||
SUC_AGENT_STARTED, | ||
SUC_AGENT_RESTARTED, | ||
SUC_AGENT_IS_DISABLED, | ||
SUC_AGENT_IS_ENABLED_AND_STARTED, | ||
SUC_AGENT_IS_ENABLED_AND_STOPPED, | ||
SUC_AGENT_STOPPED, | ||
TXT_REGION_SAVED, | ||
TXT_TUNNEL_REMOVED, | ||
TXT_TUNNEL_ADDED, | ||
TXT_TIMEOUT_SAVED, | ||
TXT_TOKEN_SAVED, | ||
TXT_WHITELIST_SAVED, | ||
TXT_TUNNEL_STOPPED, | ||
TXT_TUNNEL_STARTED, | ||
TXT_AGENT_ENABLED, | ||
TXT_AGENT_ALREADY_ENABLED, | ||
TXT_AGENT_DISABLED, | ||
TXT_AGENT_ALREADY_DISABLED, | ||
TXT_AGENT_STARTED, | ||
TXT_AGENT_RESTARTED, | ||
TXT_AGENT_IS_DISABLED, | ||
TXT_AGENT_IS_ENABLED_AND_INITIALIZING, | ||
TXT_AGENT_IS_ENABLED_AND_HAVE_TROUBLES, | ||
TXT_AGENT_IS_ENABLED_AND_STARTED, | ||
TXT_AGENT_IS_ENABLED_AND_STOPPED, | ||
TXT_AGENT_STOPPED, | ||
ERR_TUNNEL_ALREADY_EXISTS, | ||
@@ -367,3 +374,4 @@ ERR_AGENT_NOT_REGISTERED, | ||
ERR_DOMAIN_IS_NOT_VALID, | ||
ERR_CERT_PATH_IS_NOT_VALID | ||
ERR_CERT_PATH_IS_NOT_VALID, | ||
TXT_OPENING_TUNNEL | ||
}; |
import basicAuth from 'basic-auth'; | ||
import { | ||
DEFAULT_TIMEOUT, | ||
isStringRegExp, | ||
TARGET_HTTP_REGEX, | ||
@@ -79,61 +80,54 @@ TARGET_ONLY_PORT_REGEX, | ||
id, | ||
sshIp, | ||
sshPort, | ||
sshUser, | ||
sshPassword, | ||
sshForwardPort, | ||
type, | ||
target, | ||
region, | ||
type, | ||
timeout, | ||
domain, | ||
subdomain, | ||
whitelist, | ||
useragents, | ||
subdomain, | ||
domain, | ||
timeout, | ||
target, | ||
serve, | ||
terminate, | ||
headers, | ||
responseHeaders, | ||
key, | ||
cert, | ||
ca, | ||
host, | ||
authPass, | ||
authLogin, | ||
log, | ||
verify, | ||
compression, | ||
circuitBreaker, | ||
http2 | ||
tlsSettings, | ||
httpSettings, | ||
sshSettings, | ||
}) { | ||
super(); | ||
this.id = id; | ||
this.sshIp = sshIp; | ||
this.sshPort = sshPort; | ||
this.sshUser = sshUser; | ||
this.sshPassword = sshPassword; | ||
this.sshForwardPort = sshForwardPort; | ||
this.type = type; | ||
this.region = region; | ||
this.type = type; | ||
this.target = target; | ||
this.whitelist = whitelist || []; | ||
this.useragents = useragents || []; | ||
this.domain = domain; | ||
this.subdomain = subdomain; | ||
this.headers = headers || []; | ||
this.responseHeaders = responseHeaders || []; | ||
this.domain = domain; | ||
this.timeout = timeout || DEFAULT_TIMEOUT; | ||
this.target = target; | ||
this.serve = serve; | ||
this.terminate = terminate; | ||
this.key = key; | ||
this.cert = cert; | ||
this.ca = ca; | ||
this.host = host; | ||
this.authLogin = authLogin; | ||
this.authPass = authPass; | ||
this.log = log; | ||
this.verify = verify; | ||
this.compression = compression; | ||
this.circuitBreaker = circuitBreaker; | ||
this.http2 = http2; | ||
this.useragents = []; | ||
this.headers = []; | ||
this.responseHeaders = []; | ||
if (tlsSettings) { | ||
this.terminate = tlsSettings.terminate; | ||
this.key = tlsSettings.key; | ||
this.cert = tlsSettings.cert; | ||
this.ca = tlsSettings.ca; | ||
} | ||
if (httpSettings) { | ||
this.host = httpSettings.host; | ||
this.login = httpSettings.login; | ||
this.password = httpSettings.password; | ||
this.ca = httpSettings.ca; | ||
this.serve = httpSettings.serve; | ||
this.useragents = httpSettings.userAgents || []; | ||
this.headers = httpSettings.headers || []; | ||
this.responseHeaders = httpSettings.responseHeaders || []; | ||
this.circuitBreaker = httpSettings.circuitBreaker; | ||
this.log = httpSettings.log; | ||
this.verify = httpSettings.verify; | ||
this.compression = httpSettings.compression; | ||
this.http2 = httpSettings.http2; | ||
} | ||
if (sshSettings) { | ||
this.sshIp = sshSettings.ip; | ||
this.sshPort = sshSettings.port; | ||
this.sshUser = sshSettings.user; | ||
this.sshPassword = sshSettings.password; | ||
this.sshForwardPort = sshSettings.forwardPort; | ||
} | ||
this.targetProto = 'http'; | ||
@@ -180,3 +174,4 @@ this.targetHost = 'localhost'; | ||
hasChanged(tunnel) { | ||
hasChanged(data) { | ||
const tunnel = new Tunnel(data); | ||
if (this.type !== tunnel.type) return true; | ||
@@ -193,4 +188,4 @@ if (this.target !== tunnel.target) return true; | ||
if (this.host !== tunnel.host) return true; | ||
if (this.authLogin !== tunnel.authLogin) return true; | ||
if (this.authPass !== tunnel.authPass) return true; | ||
if (this.login !== tunnel.login) return true; | ||
if (this.password !== tunnel.password) return true; | ||
if (this.serve !== tunnel.serve) return true; | ||
@@ -219,13 +214,20 @@ if (this.circuitBreaker !== tunnel.circuitBreaker) return true; | ||
} | ||
const sortHeaders = (a, b) => a.name.localeCompare(b.name); | ||
if (this.headers.length !== tunnel.headers.length) return true; | ||
this.headers.sort(); | ||
tunnel.headers.sort(); | ||
this.headers.sort(sortHeaders); | ||
tunnel.headers.sort(sortHeaders); | ||
for (let i=0; i < this.headers.length; i += 1) { | ||
if (this.headers[i] !== tunnel.headers[i]) return true; | ||
const thisH = this.headers[i]; | ||
const tunnelH = tunnel.headers[i]; | ||
if (thisH.name !== tunnelH.name) return true; | ||
if (thisH.value !== tunnelH.value) return true; | ||
} | ||
if (this.responseHeaders.length !== tunnel.responseHeaders.length) return true; | ||
this.responseHeaders.sort(); | ||
tunnel.responseHeaders.sort(); | ||
this.responseHeaders.sort(sortHeaders); | ||
tunnel.responseHeaders.sort(sortHeaders); | ||
for (let i=0; i < this.responseHeaders.length; i += 1) { | ||
if (this.responseHeaders[i] !== tunnel.responseHeaders[i]) return true; | ||
const thisRH = this.responseHeaders[i]; | ||
const tunnelRH = tunnel.responseHeaders[i]; | ||
if (thisRH.name !== tunnelRH.name) return true; | ||
if (thisRH.value !== tunnelRH.value) return true; | ||
} | ||
@@ -283,5 +285,5 @@ return false; | ||
httpBasicAuth(req, res) { | ||
if (this.authLogin || this.authPass) { | ||
if (this.login || this.password) { | ||
const user = basicAuth(req); | ||
if (!user || user.name !== this.authLogin || user.pass !== this.authPass) { | ||
if (!user || user.name !== this.login || user.pass !== this.password) { | ||
logger.debug(LOG_TUNNEL_HTTP_WRON_AUTH); | ||
@@ -346,7 +348,12 @@ this.httpEndFast(req, res, 401, 'Unauthorised', { | ||
for (let i=0; i < this.useragents.length; i += 1) { | ||
try { | ||
const r = new RegExp(this.useragents[i], 'i'); | ||
if (r.test(ua)) return true; | ||
} catch (err) { | ||
// do nothing | ||
const str = this.useragents[i]; | ||
if (isStringRegExp(str)) { | ||
try { | ||
const r = new RegExp(str, 'i'); | ||
if (r.test(ua)) return true; | ||
} catch (err) { | ||
// do nothing | ||
} | ||
} else if (ua.includes(str)) { | ||
return true; | ||
} | ||
@@ -353,0 +360,0 @@ } |
@@ -18,2 +18,3 @@ import { Resolver } from 'dns'; | ||
this.valid = false; | ||
this.checking = false; | ||
} else { | ||
@@ -23,4 +24,6 @@ r.resolve4(a1[0], (err, a2) => { | ||
this.valid = false; | ||
this.checking = false; | ||
} else { | ||
this.valid = a2[0] === this.sshIp; | ||
this.checking = false; | ||
} | ||
@@ -39,3 +42,3 @@ }); | ||
this.check(); | ||
}, 60000); | ||
}, 15000); | ||
} | ||
@@ -42,0 +45,0 @@ |
@@ -50,7 +50,12 @@ import EventEmitter from 'events'; | ||
this.headers = {}; | ||
this.responseHeaders = {}; | ||
this.serve = serve; | ||
this.responseHeaders = responseHeaders || {}; | ||
if (responseHeaders) { | ||
responseHeaders.forEach((header) => { | ||
this.responseHeaders[header.name.toLowerCase()] = header.value; | ||
}); | ||
} | ||
if (headers) { | ||
Object.keys(headers).forEach((key) => { | ||
this.headers[key.toLowerCase()] = headers[key]; | ||
headers.forEach((header) => { | ||
this.headers[header.name.toLowerCase()] = header.value; | ||
}); | ||
@@ -57,0 +62,0 @@ } |
@@ -49,2 +49,4 @@ import path, { resolve } from 'path'; | ||
const isStringRegExp = (str) => /^\/.*\/$/.test(str); | ||
const getHostname = () => { | ||
@@ -141,2 +143,3 @@ if (isInDocker()) return process.env.BUDDY_HOSTNAME || 'localhost'; | ||
getRealTargetHost, | ||
isStringRegExp, | ||
AGENT_STATUS_INITIALIZING, | ||
@@ -143,0 +146,0 @@ AGENT_STATUS_FETCH_FAILED, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
292013
6616
18
+ Addedisbinaryfile@5.0.2
+ Addedisbinaryfile@5.0.2(transitive)