const WebSocket = require('ws'); const axios = require('axios'); require('dotenv').config(); const WS_URL = process.env.WS_URL; const WS_TOKEN = process.env.WS_TOKEN; if (!WS_URL) { console.error('❌ WS_URL is not set in .env'); process.exit(1); } const INITIAL_BACKOFF_MS = 2000; const MAX_BACKOFF_MS = 60000; const PROXY_TIMEOUT_MS = 30000; let ws = null; let reconnectAttempts = 0; let shuttingDown = false; /** * If WS_TOKEN is provided, send it as an Authorization: Bearer header so the * secret stays out of access logs. (The server still accepts the legacy * ?token=... query parameter for backward compatibility.) */ function buildClientOptions() { if (!WS_TOKEN) return undefined; return { headers: { Authorization: `Bearer ${WS_TOKEN}` } }; } function connect() { console.log(`🔄 Connecting to ${WS_URL}...`); ws = new WebSocket(WS_URL, buildClientOptions()); ws.on('open', () => { console.log('✅ Remote Agent connected to StoreHealthAnalyzer'); reconnectAttempts = 0; }); ws.on('message', async data => { let request; try { request = JSON.parse(data); if (request.action !== 'proxyRequest') return; console.log(`🔄 Proxying ${request.method || 'GET'} ${request.url}`); const response = await axios({ method: request.method || 'GET', url: request.url, headers: request.headers || {}, auth: request.auth || undefined, data: request.body || undefined, timeout: PROXY_TIMEOUT_MS, }); ws.send( JSON.stringify({ requestId: request.requestId, status: response.status, data: response.data, headers: response.headers, }) ); } catch (err) { console.error('Proxy error:', err.message); ws.send( JSON.stringify({ requestId: request ? request.requestId : null, error: err.message, status: err.response?.status || 500, data: err.response?.data || null, }) ); } }); ws.on('close', code => { console.log(`❌ Disconnected (code: ${code}).`); if (!shuttingDown) scheduleReconnect(); }); ws.on('error', err => { console.error('WebSocket error:', err.message); }); } function scheduleReconnect() { reconnectAttempts++; const backoff = Math.min( INITIAL_BACKOFF_MS * Math.pow(1.5, reconnectAttempts - 1), MAX_BACKOFF_MS ); console.log( `⏳ Reconnecting in ${Math.round(backoff / 1000)}s... (attempt ${reconnectAttempts})` ); setTimeout(connect, backoff); } connect(); function shutdownRemote(signal) { console.log(`🛑 ${signal} received. Shutting down remote agent...`); shuttingDown = true; if (ws) { try { ws.close(); } catch (_e) { // ignore close errors during shutdown } } process.exit(0); } process.on('SIGINT', () => shutdownRemote('SIGINT')); process.on('SIGTERM', () => shutdownRemote('SIGTERM'));