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
512let 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+ */
719export 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