| #!/usr/bin/env node |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const fs = require('fs'); |
| const path = require('path'); |
| const { WebSocket } = require('ws'); |
| const readline = require('readline'); |
|
|
| class QRDetectionManager { |
| constructor() { |
| this.ws = null; |
| this.isPaused = false; |
| this.qrDetected = false; |
| this.qrSourcePath = null; |
| this.scanCompleted = false; |
| this.timeout = null; |
| this.qrTimeout = 300000; |
| |
| |
| this.log = (level, message, data = {}) => { |
| const logEntry = { |
| timestamp: new Date().toISOString(), |
| level, |
| module: 'qr-detection-manager', |
| message, |
| ...data |
| }; |
| console.log(JSON.stringify(logEntry)); |
| }; |
| |
| this.log('info', 'QR Detection Manager initialized'); |
| } |
| |
| async connectWebSocket(spaceUrl) { |
| try { |
| |
| let host = spaceUrl.replace(/^https?:\/\//, '').replace(/\/$/, ''); |
| const wsUrl = `wss://${host}`; |
| const fullWsUrl = `${wsUrl}/queue/join`; |
| |
| this.log('info', 'Connecting to WebSocket', { url: fullWsUrl }); |
| |
| this.ws = new WebSocket(fullWsUrl); |
| |
| this.ws.on('open', () => { |
| this.log('info', 'WebSocket connection established'); |
| this.startMonitoring(); |
| }); |
| |
| this.ws.on('message', (data) => { |
| this.handleWebSocketMessage(data); |
| }); |
| |
| this.ws.on('error', (error) => { |
| this.log('error', 'WebSocket error', { error: error.message }); |
| }); |
| |
| this.ws.on('close', () => { |
| this.log('info', 'WebSocket connection closed'); |
| }); |
| |
| } catch (error) { |
| this.log('error', 'Failed to connect to WebSocket', { error: error.message }); |
| } |
| } |
|
|
| handleWebSocketMessage(data) { |
| |
| |
| } |
|
|
| startMonitoring() { |
| this.log('info', 'Starting QR code monitoring'); |
| |
| |
| const pingInterval = setInterval(() => { |
| if (this.ws && this.ws.readyState === WebSocket.OPEN) { |
| this.ws.ping(); |
| } else { |
| clearInterval(pingInterval); |
| } |
| }, 30000); |
| |
| |
| this.setupQRDetection(); |
| } |
| |
| setupQRDetection() { |
| this.log('info', 'Setting up QR code detection'); |
| |
| |
| this.timeout = setTimeout(() => { |
| if (!this.scanCompleted) { |
| this.log('warning', 'QR scan timeout reached'); |
| this.outputQRPrompt('❌ QR scan timeout. Please restart the process.', 'timeout'); |
| process.exit(1); |
| } |
| }, this.qrTimeout); |
| |
| |
| this.monitorForQR(); |
| } |
| |
| monitorForQR() { |
| const homeDir = process.env.HOME || '/home/node'; |
| |
| const qrCheckInterval = setInterval(() => { |
| if (this.scanCompleted) { |
| clearInterval(qrCheckInterval); |
| return; |
| } |
|
|
| |
| const qrPaths = [ |
| path.join(homeDir, '.openclaw/credentials/whatsapp/qr.png'), |
| path.join(homeDir, '.openclaw/workspace/qr.png'), |
| path.join(homeDir, 'logs/qr.png'), |
| ]; |
|
|
| for (const qrPath of qrPaths) { |
| if (fs.existsSync(qrPath)) { |
| this.qrSourcePath = qrPath; |
| this.handleQRDetected(qrPath); |
| break; |
| } |
| } |
|
|
| |
| this.checkLogsForQR(); |
| }, 2000); |
| } |
| |
| checkLogsForQR() { |
| try { |
| const homeDir = process.env.HOME || '/home/node'; |
| const logPaths = [ |
| path.join(homeDir, 'logs/app.log'), |
| path.join(homeDir, '.openclaw/workspace/startup.log'), |
| path.join(homeDir, '.openclaw/workspace/sync.log'), |
| ]; |
| |
| for (const logPath of logPaths) { |
| if (fs.existsSync(logPath)) { |
| const logContent = fs.readFileSync(logPath, 'utf8'); |
| if (this.isQRInLogContent(logContent)) { |
| this.handleQRDetected('log'); |
| break; |
| } |
| } |
| } |
| } catch (error) { |
| |
| } |
| } |
| |
| isQRInLogContent(content) { |
| |
| const qrPatterns = [ |
| /qr code/i, |
| /scan.*qr/i, |
| /please scan/i, |
| /waiting.*qr/i, |
| /login.*qr/i, |
| /whatsapp.*qr/i, |
| /authentication.*qr/i |
| ]; |
| |
| return qrPatterns.some(pattern => pattern.test(content)); |
| } |
| |
| handleQRDetected(source) { |
| if (this.qrDetected) { |
| return; |
| } |
| |
| this.qrDetected = true; |
| this.log('info', 'QR code detected', { source }); |
| |
| |
| this.isPaused = true; |
| |
| |
| this.outputQRPrompt('⏳ Waiting for WhatsApp QR code scan...', 'waiting'); |
| this.outputQRPrompt('📱 Please scan the QR code with your phone to continue.', 'qr'); |
| |
| |
| this.monitorScanCompletion(); |
| } |
| |
| outputQRPrompt(message, type) { |
| |
| process.stdout.write('\x1b[2J\x1b[0f'); |
| |
| |
| const separator = '='.repeat(60); |
| console.log(`\n${separator}`); |
| console.log(`🔐 WHATSAPP LOGIN REQUIRED`); |
| console.log(`${separator}\n`); |
| console.log(message); |
| console.log(`\n${separator}`); |
| |
| |
| if (type === 'waiting') { |
| console.log('⏳ Operation paused - waiting for QR scan...'); |
| } else if (type === 'qr') { |
| console.log('📱 Use your WhatsApp app to scan the QR code'); |
| } else if (type === 'success') { |
| console.log('✅ QR scan completed successfully!'); |
| } else if (type === 'timeout') { |
| console.log('❌ QR scan timeout - please try again'); |
| } |
| |
| console.log(`${separator}\n`); |
| |
| |
| this.log(type === 'success' ? 'info' : 'warning', 'QR prompt output', { |
| message, |
| type, |
| isPaused: this.isPaused |
| }); |
| } |
| |
| monitorScanCompletion() { |
| this.log('info', 'Monitoring for QR scan completion'); |
| |
| |
| const completionCheck = setInterval(() => { |
| if (this.checkScanCompletion()) { |
| clearInterval(completionCheck); |
| this.handleScanCompleted(); |
| } |
| }, 1000); |
| } |
| |
| checkScanCompletion() { |
| const homeDir = process.env.HOME || '/home/node'; |
|
|
| |
| if (this.qrSourcePath && !fs.existsSync(this.qrSourcePath)) { |
| return true; |
| } |
|
|
| |
| try { |
| const logPaths = [ |
| path.join(homeDir, 'logs/app.log'), |
| path.join(homeDir, '.openclaw/workspace/startup.log'), |
| path.join(homeDir, '.openclaw/workspace/sync.log'), |
| ]; |
|
|
| for (const logPath of logPaths) { |
| if (fs.existsSync(logPath)) { |
| const logContent = fs.readFileSync(logPath, 'utf8'); |
| if (this.isLoginInLogContent(logContent)) { |
| return true; |
| } |
| } |
| } |
| } catch (error) { |
| |
| } |
|
|
| |
| const sessionPaths = [ |
| path.join(homeDir, '.openclaw/credentials/whatsapp/creds.json'), |
| path.join(homeDir, '.openclaw/credentials/whatsapp/session.json'), |
| ]; |
|
|
| for (const sessionPath of sessionPaths) { |
| if (fs.existsSync(sessionPath)) { |
| return true; |
| } |
| } |
|
|
| return false; |
| } |
| |
| isLoginInLogContent(content) { |
| |
| const loginPatterns = [ |
| /login.*successful/i, |
| /authentication.*success/i, |
| /session.*established/i, |
| /connected.*whatsapp/i, |
| /qr.*scanned/i, |
| /scan.*completed/i, |
| /user.*authenticated/i |
| ]; |
| |
| return loginPatterns.some(pattern => pattern.test(content)); |
| } |
| |
| handleScanCompleted() { |
| this.scanCompleted = true; |
| this.isPaused = false; |
| |
| |
| if (this.timeout) { |
| clearTimeout(this.timeout); |
| } |
| |
| |
| this.outputQRPrompt('✅ QR code scanned successfully. Login completed.', 'success'); |
| |
| this.log('info', 'QR scan completed, resuming operations'); |
| |
| |
| setTimeout(() => { |
| |
| process.exit(0); |
| }, 3000); |
| } |
| |
| async waitForQRScan() { |
| return new Promise((resolve, reject) => { |
| const checkInterval = setInterval(() => { |
| if (this.scanCompleted) { |
| clearInterval(checkInterval); |
| resolve(); |
| } |
| }, 1000); |
| |
| |
| setTimeout(() => { |
| clearInterval(checkInterval); |
| reject(new Error('QR scan timeout')); |
| }, this.qrTimeout); |
| }); |
| } |
| |
| close() { |
| if (this.ws) { |
| this.ws.close(); |
| } |
| if (this.timeout) { |
| clearTimeout(this.timeout); |
| } |
| this.log('info', 'QR Detection Manager closed'); |
| } |
| } |
|
|
| |
| async function main() { |
| const args = process.argv.slice(2); |
| const spaceUrl = args[0] || process.env.SPACE_HOST || ''; |
| |
| const manager = new QRDetectionManager(); |
| |
| try { |
| await manager.connectWebSocket(spaceUrl); |
| |
| |
| process.on('SIGINT', () => { |
| manager.log('info', 'Received SIGINT, shutting down gracefully'); |
| manager.close(); |
| process.exit(0); |
| }); |
| |
| process.on('SIGTERM', () => { |
| manager.log('info', 'Received SIGTERM, shutting down gracefully'); |
| manager.close(); |
| process.exit(0); |
| }); |
| |
| } catch (error) { |
| manager.log('error', 'QR Detection Manager failed', { error: error.message }); |
| process.exit(1); |
| } |
| } |
|
|
| if (require.main === module) { |
| main(); |
| } |
|
|
| module.exports = QRDetectionManager; |