Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

buddy-tunnel

Package Overview
Dependencies
Maintainers
2
Versions
136
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

buddy-tunnel - npm Package Compare versions

Comparing version 1.0.16-dev to 1.0.17-dev

3

package.json
{
"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,

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc