11import { IncomingMessage , Server , createServer } from 'node:http' ;
2+ import { Duplex } from 'node:stream' ;
23import { v4 as uuid } from 'uuid' ;
34import { WebSocket , WebSocketServer } from 'ws' ;
5+ import { config } from '../config.js' ;
46
5- const DEFAULT_PORT = 51_040 ;
7+ let instance : WsServerManager | undefined ;
68
79export class WsServerManager {
810
@@ -14,19 +16,27 @@ export class WsServerManager {
1416
1517 private connectionSecret ;
1618
17- constructor ( connectionSecret ?: string ) {
18- this . server = createServer ( ) ;
19+ static init ( server : Server , connectionSecret ?: string ) : WsServerManager {
20+ instance = new WsServerManager ( server , connectionSecret ) ;
21+ return instance ;
22+ }
23+
24+ static get ( ) : WsServerManager {
25+ if ( ! instance ) {
26+ throw new Error ( 'You must call WsServerManager.init before using it' ) ;
27+ }
28+
29+ return instance ;
30+ }
31+
32+ private constructor ( server : Server , connectionSecret ?: string ) {
33+ this . server = server
1934 this . connectionSecret = connectionSecret ;
2035 this . wsServerMap . set ( 'default' , this . createWssServer ( ) ) ;
2136
22- this . initServer ( ) ;
37+ this . server . on ( 'upgrade' , this . onUpgrade )
2338 }
2439
25- listen ( cb ?: ( ) => void , port ?: number , ) {
26- this . port = port ?? DEFAULT_PORT
27- this . server . listen ( this . port , 'localhost' , cb ) ;
28- }
29-
3040 setDefaultHandler ( handler : ( ws : WebSocket , manager : WsServerManager ) => void ) : WsServerManager {
3141 const wss = this . createWssServer ( ) ;
3242 this . wsServerMap . set ( 'default' , wss ) ;
@@ -35,73 +45,50 @@ export class WsServerManager {
3545 return this ;
3646 }
3747
38- addAdditionalHandlers ( path : string , handler : ( ws : WebSocket ) => void ) : WsServerManager {
39- this . handlerMap . set ( path , ( ) => {
40- const wss = this . addWebsocketServer ( ) ;
41-
42- } ) ;
43-
44- return this ;
45- }
46-
47- startAdhocWsServer ( sessionId : string , handler : ( ws : WebSocket , manager : WsServerManager ) => void ) {
48+ addAdhocWsServer ( sessionId : string , handler : ( ws : WebSocket , manager : WsServerManager ) => void ) {
4849 this . wsServerMap . set ( sessionId , this . createWssServer ( ) ) ;
4950 this . handlerMap . set ( sessionId , handler ) ;
5051 }
5152
52- private addWebsocketServer ( ) : string {
53- const key = uuid ( ) ;
54-
55- const wss = new WebSocketServer ( {
56- noServer : true
57- } )
58- this . wsServerMap . set ( key , wss ) ;
59-
60- wss . on ( 'close' , ( ) => {
61- this . wsServerMap . delete ( key ) ;
62- } )
63-
64- return key ;
53+ private onUpgrade = ( request : IncomingMessage , socket : Duplex , head : Buffer ) : void => {
54+ const { pathname } = new URL ( request . url ! , 'ws://localhost:51040' )
55+
56+ if ( ! this . validateOrigin ( request . headers . origin ! )
57+ || this . validateConnectionSecret ( request ) ) {
58+ console . error ( 'Unauthorized request from' , request . headers . origin ) ;
59+ socket . write ( 'HTTP/1.1 401 Unauthorized\r\n\r\n' )
60+ socket . destroy ( ) ;
61+ return ;
62+ }
63+
64+ if ( pathname === '/ws' && this . handlerMap . has ( 'default' ) ) {
65+ const wss = this . wsServerMap . get ( 'default' ) ;
66+ wss ?. handleUpgrade ( request , socket , head , ( ws , request ) => this . handlerMap . get ( 'default' ) ! ( ws , this , request ) ) ;
67+ return ;
68+ }
69+
70+ const pathSections = pathname . split ( '/' ) . filter ( Boolean ) ;
71+ if (
72+ pathSections [ 0 ] === 'ws'
73+ && pathSections [ 1 ] === 'session'
74+ && pathSections [ 2 ]
75+ && this . handlerMap . has ( pathSections [ 2 ] )
76+ ) {
77+ const sessionId = pathSections [ 2 ] ;
78+ console . log ( 'session found, upgrading' , sessionId ) ;
79+
80+ const wss = this . wsServerMap . get ( sessionId ) ! ;
81+
82+ wss . handleUpgrade ( request , socket , head , ( ws , request ) => this . handlerMap . get ( sessionId ) ! ( ws , this , request ) ) ;
83+ }
6584 }
6685
67- private initServer ( ) {
68- this . server . on ( 'upgrade' , ( request , socket , head ) => {
69- console . log ( 'upgrade' )
70-
71- const { pathname } = new URL ( request . url ! , 'ws://localhost:51040' )
72- console . log ( 'Pathname:' , pathname )
73-
74- const code = request . headers [ 'sec-websocket-protocol' ]
75- if ( this . connectionSecret && code !== this . connectionSecret ) {
76- console . log ( 'Auth failed' ) ;
77- socket . write ( 'HTTP/1.1 401 Unauthorized\r\n\r\n' )
78- socket . destroy ( )
79- return ;
80- }
81-
82- if ( pathname === '/' && this . handlerMap . has ( 'default' ) ) {
83- const wss = this . wsServerMap . get ( 'default' ) ;
84- wss ?. handleUpgrade ( request , socket , head , ( ws , request ) => this . handlerMap . get ( 'default' ) ! ( ws , this , request ) ) ;
85- return ;
86- }
87-
88- const pathSections = pathname . split ( '/' ) . filter ( Boolean ) ;
89- console . log ( pathSections ) ;
90- console . log ( 'available sessions' , this . handlerMap )
91-
92- if ( pathSections [ 0 ] === 'session'
93- && pathSections [ 1 ]
94- && this . handlerMap . has ( pathSections [ 1 ] )
95- ) {
96- const sessionId = pathSections [ 1 ] ;
97- console . log ( 'session found, upgrading' , sessionId ) ;
98-
99- const wss = this . wsServerMap . get ( sessionId ) ! ;
100-
101- wss . handleUpgrade ( request , socket , head , ( ws , request ) => this . handlerMap . get ( sessionId ) ! ( ws , this , request ) ) ;
102- return ;
103- }
104- } )
86+ private validateOrigin = ( origin : string ) : boolean =>
87+ config . corsAllowedOrigins . includes ( origin )
88+
89+ private validateConnectionSecret = ( request : IncomingMessage ) : boolean => {
90+ const connectionSecret = request . headers [ 'connection-secret' ] as string ;
91+ return connectionSecret === this . connectionSecret ;
10592 }
10693
10794 private createWssServer ( ) : WebSocketServer {
0 commit comments