diff --git a/src/server.mjs b/src/server.mjs new file mode 100644 index 0000000..ac0c513 --- /dev/null +++ b/src/server.mjs @@ -0,0 +1,129 @@ +import http from 'node:http'; +import https from 'node:https'; +import net from 'node:net'; +import fs from 'node:fs'; +import {readFile} from 'node:fs/promises'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {WebSocketServer} from 'ws'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const NOVNC = path.join(__dirname, 'node_modules/@novnc/novnc'); +const PORT = process.env['PORT'] || 3000; +const VNC_HOST = '127.0.0.1'; +const VNC_PORT = 5900; + +const MIME_TYPES = { + '.html': 'text/html; charset=utf-8', + '.js': 'text/javascript', + '.css': 'text/css', + '.json': 'application/json', + '.svg': 'image/svg+xml', + '.png': 'image/png', + '.ico': 'image/x-icon', + '.wasm': 'application/wasm' +}; + +const server = https.createServer({ + key: fs.readFileSync('./certs/key.pem'), + cert: fs.readFileSync('./certs/cert.pem') +}, async (req, res) => { + let file; + + if (req.url === '/') file = path.join(__dirname, '../public/index.html'); + else if (req.url.startsWith('/novnc/')) file = path.join(NOVNC, req.url.slice(7)); + else { + res.writeHead(404); + res.end('Not found'); + return; + } + + try { + const data = await readFile(file); + const type = MIME_TYPES[path.extname(file)] || 'application/octet-stream'; + + res.writeHead(200, {'Content-Type': type}); + res.end(data); + } catch (error) { + console.error('HTTP error:', error.message); + res.writeHead(404); + res.end('Not found'); + } +}); + +const wss = new WebSocketServer({ + server, + path: '/websockify', + handleProtocols: protocols => { + console.log('WebSocket protocols:', [...protocols]); + + if (protocols.has('binary')) return 'binary'; + return false; + } +}); + +wss.on('connection', ws => { + console.log('VNC client connected'); + + const socket = net.createConnection(VNC_PORT, VNC_HOST); + + socket.on('connect', () => { + console.log(`Connected to VNC ${VNC_HOST}:${VNC_PORT}`); + }); + + socket.on('data', data => { + console.log(`VNC -> browser: ${data.length} bytes`); + + if (data.length <= 64) { + console.log(` hex: ${data.toString('hex')}`); + console.log(` text: ${JSON.stringify(data.toString())}`); + } + + if (ws.readyState === ws.OPEN) { + ws.send(data, {binary: true}); + } + }); + + ws.on('message', (data, isBinary) => { + const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data); + + console.log(`Browser -> VNC: ${buffer.length} bytes (${isBinary ? 'binary' : 'text'})`); + + if (buffer.length <= 64) { + console.log(` hex: ${buffer.toString('hex')}`); + console.log(` text: ${JSON.stringify(buffer.toString())}`); + } + + if (socket.writable) { + socket.write(buffer); + } + }); + + ws.on('error', error => { + console.error('WebSocket error:', error.message); + socket.destroy(); + }); + + ws.on('close', (code, reason) => { + console.log(`Browser disconnected: ${code}${reason?.length ? ` ${reason}` : ''}`); + socket.destroy(); + }); + + socket.on('error', error => { + console.error('VNC socket error:', error.message); + if (ws.readyState === ws.OPEN) ws.close(); + }); + + socket.on('close', () => { + console.log('VNC socket closed'); + if (ws.readyState === ws.OPEN) ws.close(); + }); +}); + +server.on('error', error => { + console.error('Server error:', error); +}); + +server.listen(PORT, '0.0.0.0', () => { + console.log(`https://0.0.0.0:${PORT}`); +}) \ No newline at end of file