@applitools/execution-grid-tunnel
Advanced tools
Comparing version 3.0.3 to 3.0.4
{ | ||
"name": "@applitools/execution-grid-tunnel", | ||
"version": "3.0.3", | ||
"version": "3.0.4", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "scripts/run-execution-grid-tunnel.js", |
@@ -241,3 +241,2 @@ 'use strict' | ||
const {frpcPath} = await prepareEnvironment({ | ||
@@ -244,0 +243,0 @@ shouldInstallFrpc, |
@@ -1,1 +0,140 @@ | ||
const{tunnelId:tunnelId,stringifyConfig:stringifyConfig,heartbeatTimeout:heartbeatTimeout=1e4,startTunnelTimeoutThreshold:startTunnelTimeoutThreshold=1e4,stopProcessTimeout:stopProcessTimeout=5e3,stringifyloggerOptions:stringifyloggerOptions}=process.env,loggerOptions=stringifyloggerOptions?JSON.parse(stringifyloggerOptions):{mode:"development",level:"info"},{createLogger:createLogger}=require("../../src/utils"),logger=createLogger({...loggerOptions,filename:tunnelId});logger.debug({action:"process-was-started",tunnelId:tunnelId,stringifyConfig:stringifyConfig,stringifyloggerOptions:stringifyloggerOptions,heartbeatTimeout:heartbeatTimeout,startTunnelTimeoutThreshold:startTunnelTimeoutThreshold,stopProcessTimeout:stopProcessTimeout});const{promises:fs}=require("fs"),nodeCleanup=require("node-cleanup"),{TunnelConnectionPool:TunnelConnectionPool}=require("./tunnel-connection-pool.js"),TUNNEL_STATUS=require("./tunnel-status.js"),tls=require("tls");let selfKillTimeoutId,initTimeoutId;const _startTunnel=async()=>{const e=JSON.parse(stringifyConfig),t=new TunnelConnectionPool({...e,logger:logger}),o=e=>{selfKillTimeoutId=setTimeout((async()=>{logger.error({action:"self-kill-timeout",error:`TunnelId ${tunnelId}: self-kill is called`,heartbeatTimeout:heartbeatTimeout}),console.log(`TunnelId ${tunnelId}: self-kill is called`);try{await t.end()}catch(e){console.log(e),t.destroy()}finally{process.exit(1)}}),e)};process.on("message",(({status:e})=>{"ok"===e&&(void 0!==selfKillTimeoutId&&clearTimeout(selfKillTimeoutId),o(heartbeatTimeout))})),nodeCleanup(((e,o)=>{logger.info({action:"tunnel-is-closing",exitCode:e,signal:o}),process.send({status:TUNNEL_STATUS.STOPPED}),selfKillTimeoutId&&clearTimeout(selfKillTimeoutId),initTimeoutId&&clearTimeout(initTimeoutId),t.end(Number(stopProcessTimeout)).catch((e=>{logger.warn({action:"tunnel-pool-timeout",error:e.message}),t.destroy()})).finally((()=>{logger.info({action:"tunnel-process-was-closed"}),setTimeout((()=>{logger.close(),process.exit()}),1e3)}))}));try{initTimeoutId=setTimeout((()=>{process.send({status:TUNNEL_STATUS.INIT_TIMEOUT_ERROR})}),startTunnelTimeoutThreshold),await t.start(),clearTimeout(initTimeoutId),initTimeoutId=void 0,o(parseInt(heartbeatTimeout)),process.send({status:TUNNEL_STATUS.RUNNING}),logger.info({action:"tunnel-started",tunnelId:tunnelId,success:!0})}catch(e){logger.error({action:"starting-tunnel-pool-failed",error:e.message}),initTimeoutId&&clearTimeout(initTimeoutId),process.send({status:TUNNEL_STATUS.INIT_ERROR})}};_startTunnel(); | ||
const { | ||
tunnelId, | ||
stringifyConfig, | ||
heartbeatTimeout = 10000, | ||
startTunnelTimeoutThreshold = 10000, | ||
stopProcessTimeout = 5000, | ||
stringifyloggerOptions | ||
} = process.env | ||
const loggerOptions = stringifyloggerOptions ? JSON.parse(stringifyloggerOptions): {mode: 'development', level: 'info'} | ||
// TODO: fix logger | ||
const {createLogger} = require('../../src/utils') | ||
const logger = createLogger({...loggerOptions, filename: tunnelId}) | ||
logger.debug({ | ||
action: 'process-was-started', | ||
tunnelId, | ||
stringifyConfig, | ||
stringifyloggerOptions, | ||
heartbeatTimeout, | ||
startTunnelTimeoutThreshold, | ||
stopProcessTimeout, | ||
}) | ||
const {promises: fs} = require('fs') | ||
const nodeCleanup = require('node-cleanup') | ||
const {TunnelConnectionPool, POOL_STATUS} = require("./tunnel-connection-pool.js") | ||
const TUNNEL_STATUS = require('./tunnel-status.js') | ||
const tls = require('tls') | ||
let selfKillTimeoutId | ||
let initTimeoutId | ||
const _startTunnel = async () => { | ||
const config = JSON.parse(stringifyConfig) | ||
const pool = new TunnelConnectionPool({...config, logger}) | ||
pool.onSetStatus((status)=> { | ||
if (status === POOL_STATUS.RECONNECT) process.send({status: TUNNEL_STATUS.RECONNECT}) | ||
}) | ||
// Closing frpc if doesn't get heart bit from parent | ||
const startSelfKillTimeout = (timeout) => { | ||
selfKillTimeoutId = setTimeout(async () => { | ||
logger.error({ | ||
action: 'self-kill-timeout', | ||
error: `TunnelId ${tunnelId}: self-kill is called`, | ||
heartbeatTimeout | ||
}) | ||
console.log(`TunnelId ${tunnelId}: self-kill is called`) | ||
try{ | ||
await pool.end() | ||
}catch (error){ | ||
console.log(error) | ||
pool.destroy() | ||
}finally{ | ||
process.exit(1) | ||
} | ||
}, timeout) | ||
} | ||
process.on('message', ({status}) => { | ||
if (status !== 'ok') { | ||
return | ||
} | ||
if (selfKillTimeoutId !== undefined) { | ||
clearTimeout(selfKillTimeoutId) | ||
} | ||
startSelfKillTimeout(heartbeatTimeout) | ||
}) | ||
// In some cases when frpc connection fails, it tries to reconnect forever. | ||
// In that line we send error to frpc controller after startTunnelTimeoutThreshold | ||
nodeCleanup((exitCode, signal) => { | ||
logger.info({ | ||
action: 'tunnel-is-closing', | ||
exitCode, | ||
signal | ||
}) | ||
process.send({status: TUNNEL_STATUS.STOPPED}) | ||
selfKillTimeoutId && clearTimeout(selfKillTimeoutId) | ||
initTimeoutId && clearTimeout(initTimeoutId) | ||
//TODO: should we check if proc.isRunning before run proc.stop | ||
pool.end(Number(stopProcessTimeout)).catch(error => { | ||
logger.warn({ | ||
action: 'tunnel-pool-timeout', | ||
error: error.message | ||
}) | ||
pool.destroy() | ||
}).finally(() => { | ||
logger.info({ | ||
action: 'tunnel-process-was-closed' | ||
}) | ||
setTimeout(() => { | ||
logger.close() | ||
process.exit() | ||
}, 1000) | ||
}) | ||
}) | ||
try{ | ||
initTimeoutId = setTimeout(() => { | ||
process.send({status: TUNNEL_STATUS.INIT_TIMEOUT_ERROR}) | ||
}, startTunnelTimeoutThreshold) | ||
await pool.start() | ||
clearTimeout(initTimeoutId) | ||
initTimeoutId = undefined | ||
startSelfKillTimeout(parseInt(heartbeatTimeout)) | ||
process.send({status: TUNNEL_STATUS.RUNNING }) | ||
logger.info({ | ||
action: 'tunnel-started', | ||
tunnelId, | ||
success: true | ||
}) | ||
// set selfkill in case execution-grid-tunnel crashes before init | ||
}catch(err){ | ||
logger.error({ | ||
action: 'starting-tunnel-pool-failed', | ||
error: err.message | ||
}) | ||
initTimeoutId && clearTimeout(initTimeoutId) | ||
process.send({status: TUNNEL_STATUS.INIT_ERROR}) | ||
} | ||
} | ||
_startTunnel() |
@@ -1,1 +0,202 @@ | ||
const{TunnelConnection:TunnelConnection,ErrorCircuitBreaker:ErrorCircuitBreaker}=require("../utils"),{v4:uuid}=require("uuid");class TunnelConnectionPool{constructor(e){this._isPoolShuttingDown=!1,this._haveConnectionSucceeded=!1,this._options=e,this._connectionMap=new Map,this._createConnectionMap=new Map,this._connectionStatusMap=new Map,this._availableConnectionMap=new Map,this._logger=this._options.logger,this._circuitBreaker=new ErrorCircuitBreaker({logger:this._options.logger})}async _createNewConnection(e){const{tunnelId:t,host:n,port:o,token:i,keepAliveMessage:c,connectedMessage:s,protocol:r,localProxyOptions:a,logger:l}=this._options,h=new(this._options.TunnelConnection||TunnelConnection)({keepAliveMessage:c,connectedMessage:s,logger:l,connectionId:e});return await h.connect({tunnelId:t,host:n,port:o,protocol:r,token:i,localProxyOptions:a}),h}async _handleConnectionLifecycle(){await this._circuitBreaker.waitForClosedState;const e=uuid();try{this._createConnectionMap.set(e,e);const t=await this._createNewConnection(e);this._createConnectionMap.delete(e),this._fireStatus("connected",e,t),t._remoteConnection?.on("error",(n=>{t._remoteConnection?.closed&&t._localConnection.closed?this._fireStatus("end",e):this._fireStatus("error",e,n)})),t._localConnection?.on("close",(()=>{this._fireStatus("end",e)}));t.waitForAttachingRequest().then((()=>this._fireStatus("occupied",e))),t.waitForRemoteConnectionClosing().then((()=>this._fireStatus("end",e)));return void this._circuitBreaker.resetErrorCounter()}catch(t){return this._createConnectionMap.delete(e),this._logger.warn({action:"handle-connection-lifecycle",error:t.message,id:e}),this._circuitBreaker.incErrorCounter(),this._fireStatus("error",e,t),t}}async _fireStatus(e,t,n=void 0){if(this._logger.debug(e,t),"connected"===e&&(this._connectionMap.set(t,n),this._connectionStatusMap.set(t,e),this._availableConnectionMap.set(t,t),!this._haveConnectionSucceeded)){this._haveConnectionSucceeded=!0;const e=this._options.preAllocation-this._connectionMap.size;for(let t=0;t<e;t++)this._handleConnectionLifecycle()}if("occupied"===e){if(!this._connectionStatusMap.has(t))return;this._connectionStatusMap.set(t,"occupied"),this._availableConnectionMap.delete(t),this._connectionStatusMap.delete(t)}"error"===e&&(this._availableConnectionMap.delete(t),this._connectionStatusMap.set(t,"error")),"end"===e&&(this._availableConnectionMap.delete(t),this._connectionStatusMap.delete(t),this._connectionMap.delete(t)),"occupied"!==e&&"end"!==e&&"error"!==e||this._haveConnectionSucceeded&&!this._isPoolShuttingDown&&this._availableConnectionMap.size+this._createConnectionMap.size<this._options.preAllocation&&this._connectionMap.size<this._options.maxConnections&&this._handleConnectionLifecycle(),this._logger.debug({action:"pool-statistics",available:this._availableConnectionMap.size,inCreation:this._createConnectionMap.size,total:this._connectionMap.size})}async start(){const e=await this._handleConnectionLifecycle();if(e)throw this._logger.error({action:"tunnel-pool-error",error:e.message}),e}async end(e=5e3){this._isPoolShuttingDown=!0;for(const e of this._connectionMap.values())e.end();return new Promise((async(t,n)=>{const o=setTimeout((()=>n(new Error("Timeout Error"))),e);for(;this._connectionMap.size>0;)await new Promise((e=>setTimeout(e,50)));clearTimeout(o),t()}))}destroy(){this._isPoolShuttingDown=!0;for(const e of this._connectionMap.values())e.destroy()}}module.exports={TunnelConnectionPool:TunnelConnectionPool}; | ||
const {TunnelConnection, ErrorCircuitBreaker} = require('../utils') | ||
const {v4: uuid} = require('uuid') | ||
const POOL_STATUS = { | ||
OFF: 'off', // start is not called yet | ||
INIT: 'init', // | ||
INIT_ERROR: 'init_error', | ||
RUNNING: 'running', // The process connected to the server | ||
RECONNECT: 'reconnect', // The process tries to reconnect to server | ||
ERROR: 'error', | ||
STOPPING: 'stoping', // User asks to stop the tunnel | ||
} | ||
class TunnelConnectionPool{ | ||
constructor(options){ | ||
this._isPoolShuttingDown = false | ||
this._haveConnectionSucceeded = false | ||
this._options = options | ||
this._connectionMap = new Map() | ||
this._createConnectionMap = new Map() | ||
this._connectionStatusMap = new Map() | ||
this._availableConnectionMap = new Map() | ||
this._logger = this._options.logger | ||
this._onSetStatusCallbacks = [] | ||
this._poolStatus = POOL_STATUS.OFF | ||
this._circuitBreaker = new ErrorCircuitBreaker({logger: this._options.logger}) | ||
this._circuitBreaker.onOpen(() => { | ||
if (this._poolStatus === POOL_STATUS.RUNNING){ | ||
this.setStatus(POOL_STATUS.RECONNECT) | ||
} | ||
}) | ||
} | ||
async _createNewConnection(connectionId){ | ||
const {tunnelId, host, port, token, keepAliveMessage, connectedMessage, protocol, localProxyOptions, logger} = this._options | ||
const TunnelConnectionClass = this._options.TunnelConnection || TunnelConnection | ||
const connection = new TunnelConnectionClass({keepAliveMessage, connectedMessage, logger, connectionId}) | ||
await connection.connect({tunnelId, host, port, protocol, token, localProxyOptions}) | ||
return connection | ||
} | ||
async _handleConnectionLifecycle(){ | ||
await this._circuitBreaker.waitForClosedState | ||
const id = uuid() | ||
try{ | ||
this._createConnectionMap.set(id, id) | ||
const connection = await this._createNewConnection(id) | ||
this._createConnectionMap.delete(id) | ||
this._fireStatus('connected', id, connection) | ||
connection._remoteConnection?.on('error', e => { | ||
if (connection._remoteConnection?.closed && connection._localConnection.closed){ | ||
this._fireStatus('end', id) | ||
}else { | ||
this._fireStatus('error', id, e) | ||
} | ||
}) | ||
connection._localConnection?.on('close', () => { | ||
this._fireStatus('end', id) | ||
}) | ||
const occupiedPromise = connection.waitForAttachingRequest() | ||
.then(()=> this._fireStatus('occupied', id)) | ||
const endPromise = connection.waitForRemoteConnectionClosing() | ||
.then(() => this._fireStatus('end', id)) | ||
this._circuitBreaker.resetErrorCounter() | ||
this.setStatus(POOL_STATUS.RUNNING) | ||
return | ||
}catch(e){ | ||
this._createConnectionMap.delete(id) | ||
this._logger.warn({ | ||
action: 'handle-connection-lifecycle', | ||
error: e.message, | ||
id | ||
}) | ||
this._circuitBreaker.incErrorCounter() | ||
this._fireStatus('error', id, e) | ||
return e | ||
} | ||
} | ||
async _fireStatus(status, id, data = undefined){ | ||
//console.log(status, id) | ||
this._logger.debug(status, id) | ||
if(status === 'connected'){ | ||
this._connectionMap.set(id, data) | ||
this._connectionStatusMap.set(id, status) | ||
this._availableConnectionMap.set(id, id) | ||
if (!this._haveConnectionSucceeded){ | ||
this._haveConnectionSucceeded = true | ||
const count = this._options.preAllocation - this._connectionMap.size | ||
for(let i=0; i< count; i++){ | ||
this._handleConnectionLifecycle() | ||
} | ||
} | ||
} | ||
if (status === 'occupied'){ | ||
if (!this._connectionStatusMap.has(id)) return | ||
this._connectionStatusMap.set(id, 'occupied') | ||
this._availableConnectionMap.delete(id) | ||
this._connectionStatusMap.delete(id) | ||
} | ||
if (status === 'error'){ | ||
this._availableConnectionMap.delete(id) | ||
this._connectionStatusMap.set(id, "error") | ||
} | ||
if (status === 'end'){ | ||
this._availableConnectionMap.delete(id) | ||
this._connectionStatusMap.delete(id) | ||
this._connectionMap.delete(id) | ||
} | ||
if(status === 'occupied' || status === 'end' || status === 'error'){ | ||
// console.log(this._haveConnectionSucceeded, this._shouldCloseConnection) | ||
if (this._haveConnectionSucceeded && !this._isPoolShuttingDown){ | ||
if ((this._availableConnectionMap.size + this._createConnectionMap.size) < this._options.preAllocation && | ||
this._connectionMap.size < this._options.maxConnections){ | ||
this._handleConnectionLifecycle() | ||
} | ||
} | ||
} | ||
this._logger.debug({ | ||
action: 'pool-statistics', | ||
available: this._availableConnectionMap.size, | ||
inCreation: this._createConnectionMap.size, | ||
total: this._connectionMap.size | ||
}) | ||
} | ||
async start(){ | ||
this.setStatus(POOL_STATUS.INIT) | ||
const error = await this._handleConnectionLifecycle() | ||
if (error){ | ||
this._logger.error({ | ||
action: 'tunnel-pool-error', | ||
error: error.message | ||
}) | ||
this.setStatus(POOL_STATUS.INIT_ERROR) | ||
throw error | ||
} | ||
} | ||
async end(timeout = 5000){ | ||
this._isPoolShuttingDown = true | ||
for(const connection of this._connectionMap.values()){ | ||
connection.end() | ||
} | ||
return new Promise(async (resolve, reject) => { | ||
const timeoutId = setTimeout(() => reject(new Error('Timeout Error')), timeout) | ||
while(this._connectionMap.size > 0){ | ||
await new Promise(r => setTimeout(r,50)) | ||
} | ||
clearTimeout(timeoutId) | ||
resolve() | ||
}) | ||
} | ||
async onSetStatus(cb){ | ||
this._onSetStatusCallbacks.push(cb) | ||
} | ||
async setStatus(status){ | ||
if (this._poolStatus === status) return | ||
this._poolStatus = status | ||
for(const cb of this._onSetStatusCallbacks){ | ||
cb(status) | ||
} | ||
} | ||
destroy(){ | ||
this._isPoolShuttingDown = true | ||
this.setStatus(POOL_STATUS.STOPPING) | ||
for(const connection of this._connectionMap.values()){ | ||
connection.destroy() | ||
} | ||
} | ||
} | ||
module.exports = {TunnelConnectionPool, POOL_STATUS} |
@@ -10,2 +10,3 @@ class ErrorCircuitBreaker{ | ||
this.waitForClosedState = Promise.resolve() | ||
this._onOpenCallbacks = [] | ||
} | ||
@@ -38,2 +39,5 @@ | ||
this._failureCount = 0 | ||
for(const cb of this._onOpenCallbacks){ | ||
cb() | ||
} | ||
} | ||
@@ -44,4 +48,8 @@ | ||
} | ||
onOpen(cb){ | ||
this._onOpenCallbacks.push(cb) | ||
} | ||
} | ||
module.exports = {ErrorCircuitBreaker} |
@@ -1,1 +0,28 @@ | ||
const{Transform:Transform}=require("stream");class KeepaliveMessageFilter extends Transform{constructor(e){super(e),this._keepAliveMessage=e.keepAliveMessage,this._waitForChunkResolver=null,this._waitForChunkPromise=new Promise((e=>this._waitForChunkResolver=e))}_transform(e,s,r){const t=e;e.length===this._keepAliveMessage.length&&t.toString()===this._keepAliveMessage||(this._waitForChunkResolver&&(this._waitForChunkResolver(),this._waitForChunkResolver=null),this.push(e)),r()}async waitForChunk(){return this._waitForChunkPromise}}module.exports={KeepaliveMessageFilter:KeepaliveMessageFilter}; | ||
const {Transform} = require('stream') | ||
class KeepaliveMessageFilter extends Transform { | ||
constructor(options) { | ||
super(options); | ||
this._keepAliveMessage = options.keepAliveMessage | ||
this._waitForChunkResolver = null | ||
this._waitForChunkPromise = new Promise(resolve => this._waitForChunkResolver = resolve) | ||
} | ||
_transform(chunk, encoding, callback){ | ||
const transformedChunk = chunk | ||
if (chunk.length !== this._keepAliveMessage.length || transformedChunk.toString() !== this._keepAliveMessage){ | ||
if(this._waitForChunkResolver){ | ||
this._waitForChunkResolver() | ||
this._waitForChunkResolver = null | ||
} | ||
this.push(chunk); | ||
} | ||
callback(); | ||
} | ||
async waitForChunk(){ | ||
return this._waitForChunkPromise | ||
} | ||
} | ||
module.exports = {KeepaliveMessageFilter} |
@@ -1,1 +0,110 @@ | ||
const tls=require("tls"),net=require("net"),{KeepaliveMessageFilter:KeepaliveMessageFilter}=require("./keepalive-filter-message"),ATTACHING_REQUEST="attaching-request",CLOSING_CONNECTION="closing-connection";class TunnelConnection{constructor({keepAliveMessage:e,connectedMessage:n,logger:o,connectionId:t}){this._logger=o,this._filter=new KeepaliveMessageFilter({keepAliveMessage:e}),this._connectedMessage=n,this._filter.on("error",(e=>o.warn({action:"filter-error",error:e.stack||e.message}))),this._connectionId=t}connect({tunnelId:e,host:n,port:o,token:t,protocol:c="tcp",localProxyOptions:i}){return new Promise(((r,s)=>{let l=!1;this._logger.debug({action:"connection-info",connectionId:this._connectionId,port:o,host:n,token:t,protocol:c});const a="tcp"==c?net.connect(o,n):tls.connect(o,n);this._remoteConnection=a,a.once("connect",(()=>{a.write(JSON.stringify({tunnelId:e,token:t})),a.once("data",(n=>{const o=n.toString();if(o!==this._connectedMessage){this._logger.warn({action:"connection-refused",connectionId:this._connectionId,error:o});const e=new Error(`Server Error: ${o}`);return s(e)}this._localConnection=net.connect(i),this._localConnection.on("error",(e=>{this._logger.warn({action:"local-connection-error",connectionId:this._connectionId,error:e.stack||e.message})})),this._localConnection.once("connect",(()=>{this._localConnection.on("end",(()=>{this._logger.debug({action:"local-connection-end",connectionId:this._connectionId,tunnelId:e}),a.end()})),a.pipe(this._filter).pipe(this._localConnection).pipe(a),l=!0,r()}))}))})),a.on("error",(e=>{this._logger.warn({action:"remote-connection-error",connectionId:this._connectionId,error:e.stack||e.message}),l||s(new Error(`Connection Closed: ${e.message||"Unexpected"}`)),a.end()}))}))}waitForAttachingRequest(){return this._filter.waitForChunk().then((()=>ATTACHING_REQUEST))}waitForRemoteConnectionClosing(){return new Promise((e=>{this._remoteConnection.once("end",(()=>{e(CLOSING_CONNECTION)}))}))}end(){this._localConnection?.closed||this._localConnection.end(),this._remoteConnection.closed||this._remoteConnection.end()}destroy(){this._localConnection?.destroyed||this._localConnection.destroy(),this._remoteConnection.destroyed||this._remoteConnection.destroy()}}module.exports={TunnelConnection:TunnelConnection,ATTACHING_REQUEST:ATTACHING_REQUEST,CLOSING_CONNECTION:CLOSING_CONNECTION}; | ||
const tls = require('tls') | ||
const net = require('net') | ||
const { KeepaliveMessageFilter } = require('./keepalive-filter-message'); | ||
const ATTACHING_REQUEST = 'attaching-request' | ||
const CLOSING_CONNECTION = 'closing-connection' | ||
class TunnelConnection { | ||
constructor({keepAliveMessage, connectedMessage, logger, connectionId}){ | ||
this._logger = logger | ||
this._filter = new KeepaliveMessageFilter({ keepAliveMessage }) | ||
this._connectedMessage = connectedMessage | ||
this._filter.on('error', (error) => logger.warn({action: 'filter-error', error: error.stack || error.message})) | ||
this._connectionId = connectionId | ||
} | ||
connect({tunnelId, host, port, token, protocol = 'tcp', localProxyOptions}){ | ||
return new Promise((resolve, reject) => { | ||
let isResolved = false | ||
this._logger.debug({action: 'connection-info', connectionId: this._connectionId, port, host, token, protocol}) | ||
const remoteConnection = protocol == 'tcp' ? net.connect(port,host): tls.connect(port, host) | ||
this._remoteConnection = remoteConnection | ||
remoteConnection.once('connect', () => { | ||
remoteConnection.write(JSON.stringify({tunnelId, token})) | ||
remoteConnection.once('data', (chunk) => { | ||
const result = chunk.toString() | ||
if (result !== this._connectedMessage){ | ||
this._logger.warn({action: 'connection-refused', connectionId: this._connectionId, error: result}) | ||
const error = new Error(`Server Error: ${result}`) | ||
return reject(error) | ||
} | ||
this._localConnection = net.connect(localProxyOptions) | ||
this._localConnection.on('error', | ||
e => { | ||
this._logger.warn({ | ||
action: 'local-connection-error', | ||
connectionId: this._connectionId, | ||
error: e.stack || e.message | ||
}) | ||
}) | ||
this._localConnection.once('connect', () => { | ||
this._localConnection.on('end', () => { | ||
this._logger.debug({ | ||
action: 'local-connection-end', | ||
connectionId: this._connectionId, | ||
tunnelId | ||
}) | ||
remoteConnection.end() | ||
}) | ||
remoteConnection.pipe(this._filter).pipe(this._localConnection).pipe(remoteConnection) | ||
isResolved = true | ||
resolve() | ||
}) | ||
}) | ||
}) | ||
remoteConnection.on('error', e => { | ||
this._logger.warn({ | ||
action: 'remote-connection-error', | ||
connectionId: this._connectionId, | ||
error: e.stack || e.message | ||
}) | ||
if (!isResolved) { | ||
reject(new Error(`Connection Closed: ${e.message || 'Unexpected'}`)) | ||
} | ||
remoteConnection.end() | ||
}) | ||
}) | ||
} | ||
waitForAttachingRequest(){ | ||
return this._filter.waitForChunk().then(() => ATTACHING_REQUEST) | ||
} | ||
waitForRemoteConnectionClosing(){ | ||
return new Promise(resolve => { | ||
this._remoteConnection.once('end', () => { | ||
resolve(CLOSING_CONNECTION) | ||
}) | ||
}) | ||
} | ||
end(){ | ||
if(!this._localConnection?.closed){ | ||
this._localConnection.end() | ||
} | ||
if(!this._remoteConnection.closed){ | ||
this._remoteConnection.end() | ||
} | ||
} | ||
destroy(){ | ||
if(!this._localConnection?.destroyed){ | ||
this._localConnection.destroy() | ||
} | ||
if(!this._remoteConnection.destroyed){ | ||
this._remoteConnection.destroy() | ||
} | ||
} | ||
} | ||
module.exports = { TunnelConnection, ATTACHING_REQUEST, CLOSING_CONNECTION} |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 2 instances in 1 package
70700
2063
1