const DEFAULT_URL = 'wss://soc-tester-9b6540e2d4ed.hosted.ghaymah.systems/ws'; class RemoteTerminal { constructor(url = DEFAULT_URL) { this.url = url; this.ws = null; this.ready = false; } async connect() { return new Promise((resolve, reject) => { this.ws = new WebSocket(this.url, ['tty']); this.ws.onopen = () => { this.ws.send(JSON.stringify({ AuthToken: '' })); this.ws.send('1' + JSON.stringify({ columns: 200, rows: 50 })); setTimeout(() => { this.ready = true; resolve(); }, 800); }; this.ws.onerror = (e) => reject(new Error('WS error: ' + (e.message || JSON.stringify(e)))); }); } async exec(command, timeoutMs = 12000) { if (!this.ready) await this.connect(); return new Promise((resolve, reject) => { const markerId = Math.random().toString(36).substring(2, 8); let rawOutput = ''; let timer = null; const messageHandler = async (event) => { let text = ''; if (event.data instanceof Blob) { text = await event.data.text(); } else if (typeof event.data === 'string') { text = event.data; } else { text = Buffer.from(event.data).toString('utf8'); } if (text.startsWith('0')) text = text.substring(1); rawOutput += text; const regex = new RegExp(`\r?\n__RET__(\\d+)_${markerId}__END__`); const match = rawOutput.match(regex); if (match) { cleanup(); const clean = rawOutput .replace(/[\x1b\x9b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '') .replace(/\r/g, ''); const exitCode = parseInt(match[1]); const lines = clean.split('\n'); const cmdIdx = lines.findIndex(l => l.includes(markerId) && l.includes('echo')); const retIdx = lines.findIndex(l => l.startsWith(`__RET__${exitCode}_${markerId}__END__`)); let outLines = []; if (cmdIdx !== -1 && retIdx !== -1 && retIdx > cmdIdx) { outLines = lines.slice(cmdIdx + 1, retIdx); } else if (retIdx !== -1) { outLines = lines.slice(0, retIdx); } else { outLines = lines; } const filtered = outLines.filter(l => !l.endsWith('~#') && !l.endsWith('$') && !l.includes('bash (')); resolve({ command, output: filtered.join('\n').trim(), exitCode, raw: rawOutput }); } }; const cleanup = () => { clearTimeout(timer); this.ws.removeEventListener('message', messageHandler); }; timer = setTimeout(() => { cleanup(); resolve({ command, output: rawOutput.replace(/[\x1b\x9b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '').trim(), exitCode: -1, timedOut: true }); }, timeoutMs); this.ws.addEventListener('message', messageHandler); const fullCmd = `${command}; echo __RET__$?_${markerId}__END__\n`; this.ws.send('0' + fullCmd); }); } close() { if (this.ws) { try { this.ws.close(); } catch (e) {} } } } module.exports = { RemoteTerminal }; if (require.main === module) { (async () => { const term = new RemoteTerminal(); await term.connect(); console.log('Connected! Testing uname -a...'); const res1 = await term.exec('uname -a'); console.log('--- OUTPUT 1 ---'); console.log(res1.output); console.log('Exit code:', res1.exitCode); console.log('\nTesting hostname & ip...'); const res2 = await term.exec('hostname -i || ip addr'); console.log('--- OUTPUT 2 ---'); console.log(res2.output); console.log('Exit code:', res2.exitCode); term.close(); })().catch(console.error); }