navflow-browser-server
Version:
Standalone Playwright browser server for NavFlow - enables browser automation with API key authentication, workspace device management, session sync, LLM discovery tools, and requires Node.js v22+
271 lines • 9.51 kB
JavaScript
"use strict";
/**
* Tunnel Client - Connects to proxy server and handles HTTP request forwarding
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const ws_1 = __importDefault(require("ws"));
const http_1 = __importDefault(require("http"));
const events_1 = require("events");
class TunnelClient extends events_1.EventEmitter {
constructor(localServerPort = 3002, proxyServerUrl = 'ws://localhost:8080/tunnel') {
super();
this.ws = null;
this.connected = false;
this.tunnelInfo = null;
this.pendingRequests = new Map();
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 5000; // 5 seconds
this.localServerPort = localServerPort;
this.proxyServerUrl = proxyServerUrl;
}
/**
* Connect to the proxy server
*/
async connect() {
return new Promise((resolve, reject) => {
console.log('[TunnelClient] Connecting to proxy server:', this.proxyServerUrl);
this.ws = new ws_1.default(this.proxyServerUrl);
this.ws.on('open', () => {
console.log('[TunnelClient] Connected to proxy server');
this.connected = true;
this.reconnectAttempts = 0;
resolve();
});
this.ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
this.handleMessage(message);
}
catch (error) {
console.error('[TunnelClient] Error parsing message:', error);
}
});
this.ws.on('close', () => {
console.log('[TunnelClient] Disconnected from proxy server');
this.connected = false;
this.tunnelInfo = null;
this.attemptReconnect();
});
this.ws.on('error', (error) => {
console.error('[TunnelClient] WebSocket error:', error);
if (!this.connected) {
reject(error);
}
});
// Connection timeout
setTimeout(() => {
if (!this.connected) {
reject(new Error('Connection timeout'));
}
}, 10000);
});
}
/**
* Handle incoming messages from proxy server
*/
handleMessage(message) {
switch (message.type) {
case 'tunnel_registered':
this.tunnelInfo = {
tunnelId: message.tunnelId,
password: message.password
};
this.displayTunnelInfo();
break;
case 'http_request':
this.handleHttpRequest(message);
break;
case 'ping':
// Respond to ping
if (this.ws) {
this.ws.send(JSON.stringify({ type: 'pong', timestamp: new Date().toISOString() }));
}
break;
case 'pong':
// Pong received
break;
case 'webrtc_answer':
case 'webrtc_ice_candidate':
// Forward WebRTC signaling to ScreenShareService
console.log(`[TunnelClient] Received WebRTC message: ${message.type}`);
this.emit('webrtc_message', message);
break;
case 'start_screen_share':
// Handle screen share start request
console.log(`[TunnelClient] Received screen share start request for session: ${message.sessionId}`);
this.emit('start_screen_share', message);
break;
default:
console.log('[TunnelClient] Unknown message type:', message.type);
}
}
/**
* Handle HTTP request from proxy server
*/
async handleHttpRequest(message) {
const { id, method, url, headers, body } = message;
try {
// Make request to local server
const response = await this.makeLocalRequest(method, url, headers, body);
// Send response back to proxy
if (this.ws) {
this.ws.send(JSON.stringify({
id: id,
statusCode: response.statusCode,
headers: response.headers,
body: response.body
}));
}
}
catch (error) {
console.error('[TunnelClient] Error handling HTTP request:', error);
// Send error response
if (this.ws) {
this.ws.send(JSON.stringify({
id: id,
error: error.message,
statusCode: 500
}));
}
}
}
/**
* Make HTTP request to local server
*/
async makeLocalRequest(method, url, headers, body) {
return new Promise((resolve, reject) => {
const options = {
hostname: 'localhost',
port: this.localServerPort,
path: url,
method: method,
headers: headers || {}
};
const req = http_1.default.request(options, (res) => {
let responseBody = '';
res.on('data', (chunk) => {
responseBody += chunk;
});
res.on('end', () => {
// Try to parse JSON response
let parsedBody = responseBody;
try {
if (responseBody && res.headers['content-type']?.includes('application/json')) {
parsedBody = JSON.parse(responseBody);
}
}
catch (e) {
// Keep as string if not JSON
}
resolve({
statusCode: res.statusCode,
headers: res.headers,
body: parsedBody
});
});
});
req.on('error', (error) => {
reject(error);
});
// Send request body if present
if (body) {
if (typeof body === 'object') {
req.write(JSON.stringify(body));
}
else {
req.write(body);
}
}
req.end();
});
}
/**
* Display tunnel information in console
*/
displayTunnelInfo() {
if (!this.tunnelInfo)
return;
console.log('');
console.log('🌐 ===== TUNNEL CONNECTION ESTABLISHED =====');
console.log('');
console.log(`📍 Local Server: http://localhost:${this.localServerPort}`);
console.log(`🆔 Tunnel ID: ${this.tunnelInfo.tunnelId}`);
console.log(`🔑 Password: ${this.tunnelInfo.password}`);
console.log('');
console.log('🔧 Frontend Configuration:');
console.log(` Tunnel ID: ${this.tunnelInfo.tunnelId}`);
console.log(` Password: ${this.tunnelInfo.password}`);
console.log('');
console.log('💡 Configure these credentials in your frontend settings');
console.log('===============================================');
console.log('');
}
/**
* Attempt to reconnect to proxy server
*/
attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('[TunnelClient] Max reconnection attempts reached');
return;
}
this.reconnectAttempts++;
console.log(`[TunnelClient] Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
setTimeout(() => {
this.connect().catch((error) => {
console.error('[TunnelClient] Reconnection failed:', error);
});
}, this.reconnectDelay);
}
/**
* Send ping to proxy server
*/
ping() {
if (this.ws && this.connected) {
this.ws.send(JSON.stringify({ type: 'ping', timestamp: new Date().toISOString() }));
}
}
/**
* Get current tunnel information
*/
getTunnelInfo() {
return this.tunnelInfo;
}
/**
* Check if connected to proxy
*/
isConnected() {
return this.connected && this.ws !== null && this.ws.readyState === ws_1.default.OPEN;
}
/**
* Send WebRTC signaling message through tunnel
*/
sendWebRTCMessage(message) {
if (this.ws && this.connected) {
this.ws.send(JSON.stringify(message));
}
}
/**
* Get WebSocket instance for direct access
*/
getWebSocket() {
return this.ws;
}
/**
* Disconnect from proxy server
*/
disconnect() {
if (this.ws) {
console.log('[TunnelClient] Disconnecting from proxy server');
this.ws.close();
this.ws = null;
}
this.connected = false;
this.tunnelInfo = null;
}
}
exports.default = TunnelClient;
//# sourceMappingURL=tunnelClient.js.map