UNPKG

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+

210 lines 7.86 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.HeartbeatService = void 0; const axios_1 = __importDefault(require("axios")); const UpdateService_1 = require("./UpdateService"); class HeartbeatService { constructor(browserManager, apiKey, version, proxyServerUrl) { this.intervalId = null; this.deviceId = null; this.isShuttingDown = false; this.browserManager = browserManager; this.apiKey = apiKey; this.version = version; this.proxyServerUrl = proxyServerUrl || 'https://navflow-proxy-858493283701.us-central1.run.app'; this.startTime = new Date(); this.updateService = new UpdateService_1.UpdateService('navflow-browser-server', version, async () => { console.log('💤 Preparing for auto-update shutdown...'); this.stop(); // Give time for offline heartbeat to send await new Promise(resolve => setTimeout(resolve, 1000)); }); } /** * Start the heartbeat service */ start() { if (this.intervalId) { console.warn('Heartbeat service is already running'); return; } console.log('🔄 Starting heartbeat service...'); // Send initial heartbeat immediately this.sendHeartbeat(); // Set up periodic heartbeat every 30 seconds this.intervalId = setInterval(() => { if (!this.isShuttingDown) { this.sendHeartbeat(); } }, 30000); console.log('✅ Heartbeat service started (30s interval)'); } /** * Stop the heartbeat service */ stop() { this.isShuttingDown = true; if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; console.log('🛑 Heartbeat service stopped'); } // Send final offline heartbeat this.sendOfflineHeartbeat(); } /** * Get system metrics for heartbeat */ getSystemMetrics() { const memoryUsage = process.memoryUsage(); const uptime = Date.now() - this.startTime.getTime(); const activeSessions = this.browserManager.getActiveSessions().length; return { activeSessions, memoryUsage: `${Math.round(memoryUsage.heapUsed / 1024 / 1024)}MB`, uptime: this.formatUptime(uptime) }; } /** * Format uptime in human readable format */ formatUptime(ms) { const seconds = Math.floor(ms / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); if (days > 0) return `${days}d ${hours % 24}h`; if (hours > 0) return `${hours}h ${minutes % 60}m`; if (minutes > 0) return `${minutes}m ${seconds % 60}s`; return `${seconds}s`; } /** * Send heartbeat to proxy server */ async sendHeartbeat() { try { const payload = { deviceId: this.deviceId || undefined, apiKey: this.apiKey, status: 'online', metrics: this.getSystemMetrics(), version: this.version, timestamp: new Date().toISOString() }; const response = await axios_1.default.post(`${this.proxyServerUrl}/api/devices/heartbeat`, payload, { timeout: 10000, headers: { 'Content-Type': 'application/json' } }); if (response.data.success) { // Store device ID if returned if (response.data.message && !this.deviceId) { // Extract device ID from response if available // Heartbeat successful but don't log to reduce console noise } // Check for updates if (response.data.updateAvailable && response.data.latestVersion) { console.log(`🆙 Update available: ${this.version} → ${response.data.latestVersion}`); await this.handleUpdateAvailable(response.data.latestVersion); } } else { console.warn('⚠️ Heartbeat failed:', response.data.message); } } catch (error) { if (axios_1.default.isAxiosError(error)) { if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') { console.warn('🔗 Proxy server unreachable, will retry...'); } else if (error.response?.status === 401) { console.error('🔑 Invalid API key for heartbeat'); } else { console.warn('⚠️ Heartbeat failed:', error.message); } } else { console.warn('⚠️ Heartbeat error:', error.message); } } } /** * Send offline heartbeat when shutting down */ async sendOfflineHeartbeat() { try { const payload = { deviceId: this.deviceId || undefined, apiKey: this.apiKey, status: 'offline', metrics: this.getSystemMetrics(), version: this.version, timestamp: new Date().toISOString() }; await axios_1.default.post(`${this.proxyServerUrl}/api/devices/heartbeat`, payload, { timeout: 5000, headers: { 'Content-Type': 'application/json' } }); console.log('📤 Offline heartbeat sent'); } catch (error) { // Ignore errors during shutdown console.warn('⚠️ Failed to send offline heartbeat:', error); } } /** * Handle update available notification */ async handleUpdateAvailable(latestVersion) { // Check if any flows are currently executing const activeSessions = this.browserManager.getActiveSessions(); let hasActiveFlows = false; // Check each session to see if it's actively being used for (const sessionId of activeSessions) { const session = await this.browserManager.getSession(sessionId); if (session) { // Consider a session active if it was used in the last 5 minutes const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000); if (session.lastUsed > fiveMinutesAgo) { hasActiveFlows = true; break; } } } if (hasActiveFlows) { console.log('🔄 Update available but flows are running, will check again later'); return; } console.log('🚀 No active flows detected, applying update...'); // Perform the auto-update const updateSuccess = await this.updateService.performUpdate(); if (!updateSuccess) { console.warn('⚠️ Auto-update failed, will try again later'); } } /** * Set device ID (called after device registration) */ setDeviceId(deviceId) { this.deviceId = deviceId; console.log(`📱 Device ID set for heartbeat: ${deviceId}`); } /** * Check if heartbeat service is running */ isRunning() { return this.intervalId !== null && !this.isShuttingDown; } } exports.HeartbeatService = HeartbeatService; //# sourceMappingURL=HeartbeatService.js.map