-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathserver.js
More file actions
114 lines (100 loc) · 4.2 KB
/
Copy pathserver.js
File metadata and controls
114 lines (100 loc) · 4.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
const { createServer } = require('http');
// url module is deprecated for parsing
const next = require('next');
const { Server } = require('socket.io');
const pty = require('node-pty');
const os = require('os');
const { jwtVerify } = require('jose');
require('dotenv').config({ path: '.env' }); // Make sure we load env vars before Next.js
const dev = process.env.NODE_ENV !== 'production';
const hostname = '0.0.0.0'; // Bind to all interfaces
const port = process.env.PORT || 3000;
const app = next({ dev, hostname, port });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = createServer(async (req, res) => {
try {
const host = req.headers.host || 'localhost';
const parsedUrl = new URL(req.url || '/', `http://${host}`);
const query = Object.fromEntries(parsedUrl.searchParams.entries());
await handle(req, res, {
pathname: parsedUrl.pathname,
query: query
});
} catch (err) {
console.error('Error occurred handling', req.url, err);
res.statusCode = 500;
res.end('Internal server error');
}
});
const io = new Server(server, {
path: '/api/socket.io',
addTrailingSlash: false,
});
// Security Verification: Only allow logged in users to access WebSocket Terminal
io.use(async (socket, next) => {
try {
const cookieHeader = socket.handshake.headers.cookie;
if (!cookieHeader) {
return next(new Error('Authentication error: No cookies provided'));
}
const cookies = {};
cookieHeader.split(';').forEach(cookie => {
const parts = cookie.split('=');
cookies[parts[0].trim()] = (parts.slice(1).join('=')).trim();
});
const sessionCookie = cookies['session'];
if (!sessionCookie) {
return next(new Error('Authentication error: Missing session token'));
}
const secretKey = process.env.JWT_SECRET || 'super-secret-key-change-it-in-production';
const encodedKey = new TextEncoder().encode(secretKey);
await jwtVerify(sessionCookie, encodedKey, {
algorithms: ['HS256'],
});
next(); // Valid!
} catch (err) {
next(new Error('Authentication error: Invalid session'));
}
});
io.on('connection', (socket) => {
console.log(`[Socket.io] Client connected to terminal: ${socket.id}`);
// ใช้อ่าน Shell เริ่มต้นจากเครื่อง โดนเฉพาะในกรณีของ MacOS ที่มักเรียกเป็น zsh โดยปริยาย
const shell = os.platform() === 'win32' ? 'powershell.exe' : (process.env.SHELL || '/bin/bash');
const ptyProcess = pty.spawn(shell, [], {
name: 'xterm-256color',
cols: 80,
rows: 24,
cwd: process.env.HOME || process.cwd(),
env: process.env
});
// 1. Backend -> Frontend (Pty output -> Browser terminal)
ptyProcess.onData((data) => {
socket.emit('terminal.out', data);
});
// 2. Frontend -> Backend (Browser typing -> Pty input)
socket.on('terminal.in', (data) => {
ptyProcess.write(data);
});
// 3. Frontend -> Backend (Resize window)
socket.on('terminal.resize', (size) => {
if (size && size.cols && size.rows) {
try {
ptyProcess.resize(size.cols, size.rows);
} catch (e) {
console.error('Resize pty error', e);
}
}
});
// 4. Cleanup when client drops connection
socket.on('disconnect', () => {
console.log(`[Socket.io] Client disconnected: ${socket.id}, terminating process.`);
try {
ptyProcess.kill();
} catch (err) { }
});
});
server.listen(port, () => {
console.log(`> App running on http://localhost:${port} using custom server.js (WebSockets enabled)`);
});
});