1+ const { Buffer } = require ( 'buffer' ) ;
2+ const { NATTypes, MessageTypes, StatusTypes, Config } = require ( './constants' ) ;
3+
4+ class ConnectionManager {
5+ constructor ( ) {
6+ this . sockets = [ ] ;
7+ this . udpConnectionInfo = [ ] ;
8+ this . currentConnectionPairs = { } ;
9+ this . connectionId = 1 ;
10+ }
11+
12+ addSocket ( socket , remoteAddress , remotePort , timeout ) {
13+ const socketInfo = {
14+ socket,
15+ ip : remoteAddress ,
16+ tcpPort : remotePort ,
17+ timeout,
18+ natType : NATTypes . Unknown ,
19+ localPort : 0 ,
20+ externalPortOne : 0 ,
21+ externalPortTwo : 0 ,
22+ clientID : Math . random ( ) . toString ( ) ,
23+ isServer : false // Track if this is a registered server
24+ } ;
25+ this . sockets . push ( socketInfo ) ;
26+ return socketInfo ;
27+ }
28+
29+ removeSocket ( socket , clientID = null ) {
30+ let index = - 1 ;
31+ if ( clientID ) {
32+ index = this . sockets . findIndex ( s => s . clientID === clientID ) ;
33+ } else {
34+ index = this . sockets . findIndex ( s => s . socket === socket ) ;
35+ }
36+
37+ if ( index !== - 1 ) {
38+ const socketInfo = this . sockets [ index ] ;
39+ // Clean up any pending connections for this client
40+ this . cleanupPendingConnections ( socketInfo ) ;
41+ // Remove associated UDP info
42+ this . removeUDPInfo ( socketInfo . ip ) ;
43+ this . sockets . splice ( index , 1 ) ;
44+ console . log ( `Removed client ${ socketInfo . clientID } (${ socketInfo . ip } :${ socketInfo . tcpPort } )` ) ;
45+ }
46+ }
47+
48+ cleanupPendingConnections ( socketInfo ) {
49+ // Find any connection pairs involving this socket and notify the other party
50+ Object . entries ( this . currentConnectionPairs ) . forEach ( ( [ id , pair ] ) => {
51+ if ( pair . server_info === socketInfo . ip || pair . client_info === socketInfo . ip ) {
52+ // Find the other party in the connection
53+ const otherSocket = this . sockets . find ( s =>
54+ ( pair . server_info === socketInfo . ip && s . ip === pair . client_info ) ||
55+ ( pair . client_info === socketInfo . ip && s . ip === pair . server_info )
56+ ) ;
57+
58+ if ( otherSocket ) {
59+ // Notify the other party that the connection attempt failed
60+ otherSocket . socket . write ( Buffer . from ( JSON . stringify ( {
61+ ID : MessageTypes . ConnectionTimeout ,
62+ Message : "Peer disconnected"
63+ } ) ) ) ;
64+ }
65+
66+ // Free up the UDP connection status for both peers
67+ this . udpConnectionInfo . forEach ( ( info ) => {
68+ if ( pair . server_info === info . ip || pair . client_info === info . ip ) {
69+ info . status . type = StatusTypes . Free ;
70+ }
71+ } ) ;
72+
73+ // Clean up the connection pair
74+ delete this . currentConnectionPairs [ id ] ;
75+ }
76+ } ) ;
77+ }
78+
79+ addUDPInfo ( address , port ) {
80+ const existing = this . udpConnectionInfo . find ( info => info . ip === address ) ;
81+ if ( existing ) {
82+ // Update the port to the most recent one observed
83+ existing . port = port ;
84+ } else {
85+ this . udpConnectionInfo . push ( {
86+ ip : address ,
87+ port,
88+ status : {
89+ id : this . connectionId ,
90+ type : StatusTypes . Free
91+ }
92+ } ) ;
93+ }
94+ }
95+
96+ removeUDPInfo ( ip ) {
97+ const index = this . udpConnectionInfo . findIndex ( info => info . ip === ip ) ;
98+ if ( index !== - 1 ) {
99+ this . udpConnectionInfo . splice ( index , 1 ) ;
100+ }
101+ }
102+
103+ updateTimeout ( address ) {
104+ const socket = this . sockets . find ( s => s . ip === address ) ;
105+ if ( socket ) {
106+ socket . timeout = Config . DEFAULT_TIMEOUT ;
107+ }
108+ }
109+
110+ processTimeouts ( ) {
111+ for ( let i = this . sockets . length - 1 ; i >= 0 ; i -- ) {
112+ const socket = this . sockets [ i ] ;
113+ if ( socket . natType !== NATTypes . Unknown ) {
114+ socket . timeout -- ;
115+ if ( socket . timeout <= 0 ) {
116+ console . log ( `Timeout: Removing ${ socket . isServer ? 'server' : 'client' } ${ socket . ip } :${ socket . tcpPort } (NAT type: ${ socket . natType } )` ) ;
117+ this . removeSocket ( socket . socket ) ;
118+ }
119+ }
120+ }
121+ }
122+
123+ checkNATType ( socket , localPort , externalPortOne , externalPortTwo ) {
124+ let natType = NATTypes . Unknown ;
125+
126+ if ( externalPortOne !== 0 && externalPortTwo !== 0 ) {
127+ if ( localPort === externalPortOne && localPort === externalPortTwo ) {
128+ natType = NATTypes . DirectMapping ;
129+ } else if ( externalPortOne === externalPortTwo ) {
130+ natType = NATTypes . Restricted ;
131+ } else {
132+ natType = NATTypes . Symmetric ;
133+ }
134+
135+ socket . write ( Buffer . from ( JSON . stringify ( {
136+ ID : MessageTypes . NATTypeResponse ,
137+ NATType : natType ,
138+ } ) ) ) ;
139+
140+ // Store NAT type on socket info
141+ const socketInfo = this . sockets . find ( s => s . socket === socket ) ;
142+ if ( socketInfo ) {
143+ socketInfo . natType = natType ;
144+
145+ // Check if this socket is for a specific connection (server-side per-client tunnel)
146+ if ( socketInfo . forConnectionID ) {
147+ const connectionId = socketInfo . forConnectionID ;
148+
149+ // Trigger callback to send ConnectionBegin to waiting client
150+ if ( this . onServerTunnelReady ) {
151+ this . onServerTunnelReady ( connectionId , socketInfo , natType ) ;
152+ }
153+ }
154+ }
155+ }
156+
157+ return natType ;
158+ }
159+
160+ findSocketByAddress ( address ) {
161+ return this . sockets . find ( s => s . ip === address ) ;
162+ }
163+
164+ updateNATTestPort ( clientID , port , isFirstPort = true ) {
165+ const socket = this . sockets . find ( s => s . clientID === clientID ) ;
166+ if ( socket ) {
167+ if ( isFirstPort ) {
168+ socket . externalPortOne = port ;
169+ } else {
170+ socket . externalPortTwo = port ;
171+ }
172+ return socket ;
173+ }
174+ return null ;
175+ }
176+ }
177+
178+ module . exports = ConnectionManager ;
0 commit comments