Skip to content

Commit 7faf88f

Browse files
committed
Switched back to websockets instead of socket io
1 parent 5289fd3 commit 7faf88f

2 files changed

Lines changed: 100 additions & 42 deletions

File tree

src/connect/http-routes/handlers/create-command.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,18 @@ export function createCommandHandler(command: string, args?: string): Router {
1616
return res.status(400).json({ error: 'SessionId must be provided' });
1717
}
1818

19-
const server = SocketServer.get();
20-
const socket = server.getSession(sessionId);
21-
if (!socket) {
19+
const manager = SocketServer.get();
20+
const session = manager.getSession(sessionId);
21+
if (!session) {
2222
return res.status(400).json({ error: 'SessionId does not exist' });
2323
}
2424

25-
if (!socket.connected) {
26-
return res.status(400).json({ error: 'Socket not connected. Connect to socket before calling this endpoint' });
25+
const {ws, server} = session;
26+
if (!ws) {
27+
return res.status(400).json({ error: 'SessionId not open' });
2728
}
2829

29-
const pty = spawn('zsh', ['-c', `codify ${command} ${args ?? ''}`], {
30+
const pty = spawn('zsh', [], {
3031
name: 'xterm-color',
3132
cols: 80,
3233
rows: 30,
@@ -35,16 +36,17 @@ export function createCommandHandler(command: string, args?: string): Router {
3536
});
3637

3738
pty.onData((data) => {
38-
socket.emit('data', Buffer.from(data, 'utf8'));
39+
ws.send(Buffer.from(data, 'utf8'));
3940
});
4041

41-
socket.on('data', (message) => {
42+
ws.on('message', (message) => {
4243
pty.write(message.toString('utf8'));
4344
})
4445

4546
pty.onExit(({ exitCode, signal }) => {
4647
console.log('pty exit', exitCode, signal);
47-
// socket.disconnect();
48+
ws.terminate();
49+
server.close();
4850
})
4951
});
5052

src/connect/socket-server.ts

Lines changed: 89 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
1-
import { Server as HttpServer } from 'node:http';
2-
import { Server, Socket } from 'socket.io';
3-
import {config} from "../config.js";
1+
import { Server as HttpServer, IncomingMessage } from 'node:http';
2+
import { Duplex } from 'node:stream';
3+
import WebSocket, { WebSocketServer } from 'ws';
4+
5+
import { config } from '../config.js';
6+
7+
export interface Session {
8+
server: WebSocketServer;
9+
ws?: WebSocket;
10+
}
411

512
let instance: SocketServer | undefined;
613

14+
/**
15+
* Main socket server. Experimented with SocketIO but it does not work!!. xterm.js does not natively support
16+
* websckets and the arraybuffer is mangled when trying my own implementaiton. SocketIO also does not play nice
17+
* when used side by side with ws.
18+
*/
719
export class SocketServer {
820

921
private server: HttpServer;
1022
private connectionSecret: string;
11-
private io: Server
12-
13-
private handlers: Array<(io: Server, socket: Socket) => void> = [];
23+
private sessions = new Map<string, Session>()
1424

1525
static init(server: HttpServer, connectionSecret: string): SocketServer {
1626
instance = new SocketServer(server, connectionSecret);
@@ -28,42 +38,88 @@ export class SocketServer {
2838
private constructor(server: HttpServer, connectionSecret: string) {
2939
this.server = server;
3040
this.connectionSecret = connectionSecret;
31-
this.io = new Server(server, {
32-
cors: {
33-
origin: config.corsAllowedOrigins
34-
}
35-
});
36-
a this.io.on('connection', (socket) => {
37-
// Only allow clients with secret to connect
38-
if (socket.handshake.auth.token !== connectionSecret) {
39-
console.log(`Invalid auth on connection`)
40-
socket.disconnect();
41-
}
4241

43-
this.handlers.forEach(handler => handler(this.io, socket));
44-
});
42+
this.server.on('upgrade', this.onUpgrade);
4543
}
4644

47-
// These are connection handlers on the default 'ws://url.com/'
48-
registerHandler(handler: (io: Server, socket: Socket) => void): void {
49-
this.handlers.push(handler);
50-
}
45+
addSession(id: string): void {
46+
// this.io.of(`/ws/session/${id}`).on('connection', (socket) => {
47+
// console.log(`Session ${id} connected!!`);
48+
// handler?.(this.io, socket);
49+
// })
5150

52-
addSession(id: string, handler?: (io: Server, socket: Socket) => void): void {
53-
this.io.of(`/ws/session/${id}`).on('connection', (socket) => {
54-
console.log(`Session ${id} connected!!`);
55-
handler?.(this.io, socket);
56-
})
51+
this.sessions.set(
52+
id,
53+
{ server: this.createWssServer() }
54+
)
5755
}
5856

5957
// Under normal use, there should only be 1 socket (1 connection) per namespace.
60-
getSession(id: string): Socket | undefined {
61-
const sockets = [...this.io.of(`/ws/session/${id}`).sockets.values()];
62-
if (sockets.length === 0) {
63-
return undefined;
58+
getSession(id: string): Session | undefined {
59+
return this.sessions.get(id);
60+
}
61+
62+
private onUpgrade = (request: IncomingMessage, socket: Duplex, head: Buffer): void => {
63+
const { pathname } = new URL(request.url!, 'ws://localhost:51040')
64+
65+
// Ignore all socket io so it does not interfere
66+
if (pathname.includes('socket.io')) {
67+
return;
6468
}
6569

66-
return sockets[0];
70+
if (/*!this.validateOrigin(request.headers.origin ?? request.headers.referer ?? '') ||*/ !this.validateConnectionSecret(request)) {
71+
console.error('Unauthorized request. Connection code:', request.headers['sec-websocket-protocol']);
72+
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n')
73+
socket.destroy();
74+
return;
75+
}
76+
77+
if (pathname === '/ws') {
78+
console.log('Client connected!')
79+
const wss = this.createWssServer();
80+
wss.handleUpgrade(request, socket, head, (ws: WebSocket) => {});
81+
}
82+
83+
const pathSections = pathname.split('/').filter(Boolean);
84+
if (
85+
pathSections[0] === 'ws'
86+
&& pathSections[1] === 'session'
87+
&& pathSections[2]
88+
&& this.sessions.has(pathSections[2])
89+
) {
90+
const sessionId = pathSections[2];
91+
console.log('Session found, upgrading', sessionId);
92+
93+
const session = this.sessions.get(sessionId);
94+
if (!session) {
95+
return;
96+
}
97+
98+
const wss = session.server;
99+
wss.handleUpgrade(request, socket, head, (ws: WebSocket) => {
100+
console.log('New ws session!', sessionId)
101+
this.sessions.get(sessionId)!.ws = ws;
102+
});
103+
104+
wss.on('close', () => {
105+
console.log('Session closed');
106+
this.sessions.delete(sessionId);
107+
})
108+
}
109+
}
110+
111+
private validateOrigin = (origin: string): boolean =>
112+
config.corsAllowedOrigins.includes(origin)
113+
114+
private validateConnectionSecret = (request: IncomingMessage): boolean => {
115+
const connectionSecret = request.headers['sec-websocket-protocol'] as string;
116+
return connectionSecret === this.connectionSecret;
117+
}
118+
119+
private createWssServer(): WebSocketServer {
120+
return new WebSocketServer({
121+
noServer: true,
122+
})
67123
}
68124

69125
}

0 commit comments

Comments
 (0)