@@ -14,7 +14,7 @@ const Auth = require('./Auth')
14
14
class SocketAuthority {
15
15
constructor ( ) {
16
16
this . Server = null
17
- this . io = null
17
+ this . socketIoServers = [ ]
18
18
19
19
/** @type {Object.<string, SocketClient> } */
20
20
this . clients = { }
@@ -89,82 +89,104 @@ class SocketAuthority {
89
89
*
90
90
* @param {Function } callback
91
91
*/
92
- close ( callback ) {
93
- Logger . info ( '[SocketAuthority] Shutting down' )
94
- // This will close all open socket connections, and also close the underlying http server
95
- if ( this . io ) this . io . close ( callback )
96
- else callback ( )
92
+ async close ( ) {
93
+ Logger . info ( '[SocketAuthority] closing...' )
94
+ const closePromises = this . socketIoServers . map ( ( io ) => {
95
+ return new Promise ( ( resolve ) => {
96
+ Logger . info ( `[SocketAuthority] Closing Socket.IO server: ${ io . path } ` )
97
+ io . close ( ( ) => {
98
+ Logger . info ( `[SocketAuthority] Socket.IO server closed: ${ io . path } ` )
99
+ resolve ( )
100
+ } )
101
+ } )
102
+ } )
103
+ await Promise . all ( closePromises )
104
+ Logger . info ( '[SocketAuthority] closed' )
105
+ this . socketIoServers = [ ]
97
106
}
98
107
99
108
initialize ( Server ) {
100
109
this . Server = Server
101
110
102
- this . io = new SocketIO . Server ( this . Server . server , {
111
+ const socketIoOptions = {
103
112
cors : {
104
113
origin : '*' ,
105
114
methods : [ 'GET' , 'POST' ]
106
- } ,
107
- path : `${ global . RouterBasePath } /socket.io`
108
- } )
109
-
110
- this . io . on ( 'connection' , ( socket ) => {
111
- this . clients [ socket . id ] = {
112
- id : socket . id ,
113
- socket,
114
- connected_at : Date . now ( )
115
115
}
116
- socket . sheepClient = this . clients [ socket . id ]
116
+ }
117
117
118
- Logger . info ( '[SocketAuthority] Socket Connected' , socket . id )
118
+ const ioServer = new SocketIO . Server ( Server . server , socketIoOptions )
119
+ ioServer . path = '/socket.io'
120
+ this . socketIoServers . push ( ioServer )
119
121
120
- // Required for associating a User with a socket
121
- socket . on ( 'auth' , ( token ) => this . authenticateSocket ( socket , token ) )
122
+ if ( global . RouterBasePath ) {
123
+ // open a separate socket.io server for the router base path, keeping the original server open for legacy clients
124
+ const ioBasePath = `${ global . RouterBasePath } /socket.io`
125
+ const ioBasePathServer = new SocketIO . Server ( Server . server , { ...socketIoOptions , path : ioBasePath } )
126
+ ioBasePathServer . path = ioBasePath
127
+ this . socketIoServers . push ( ioBasePathServer )
128
+ }
122
129
123
- // Scanning
124
- socket . on ( 'cancel_scan' , ( libraryId ) => this . cancelScan ( libraryId ) )
130
+ this . socketIoServers . forEach ( ( io ) => {
131
+ io . on ( 'connection' , ( socket ) => {
132
+ this . clients [ socket . id ] = {
133
+ id : socket . id ,
134
+ socket,
135
+ connected_at : Date . now ( )
136
+ }
137
+ socket . sheepClient = this . clients [ socket . id ]
125
138
126
- // Logs
127
- socket . on ( 'set_log_listener' , ( level ) => Logger . addSocketListener ( socket , level ) )
128
- socket . on ( 'remove_log_listener' , ( ) => Logger . removeSocketListener ( socket . id ) )
139
+ Logger . info ( `[SocketAuthority] Socket Connected to ${ io . path } ` , socket . id )
129
140
130
- // Sent automatically from socket.io clients
131
- socket . on ( 'disconnect' , ( reason ) => {
132
- Logger . removeSocketListener ( socket . id )
141
+ // Required for associating a User with a socket
142
+ socket . on ( 'auth' , ( token ) => this . authenticateSocket ( socket , token ) )
133
143
134
- const _client = this . clients [ socket . id ]
135
- if ( ! _client ) {
136
- Logger . warn ( `[SocketAuthority] Socket ${ socket . id } disconnect, no client (Reason: ${ reason } )` )
137
- } else if ( ! _client . user ) {
138
- Logger . info ( `[SocketAuthority] Unauth socket ${ socket . id } disconnected (Reason: ${ reason } )` )
139
- delete this . clients [ socket . id ]
140
- } else {
141
- Logger . debug ( '[SocketAuthority] User Offline ' + _client . user . username )
142
- this . adminEmitter ( 'user_offline' , _client . user . toJSONForPublic ( this . Server . playbackSessionManager . sessions ) )
144
+ // Scanning
145
+ socket . on ( 'cancel_scan' , ( libraryId ) => this . cancelScan ( libraryId ) )
143
146
144
- const disconnectTime = Date . now ( ) - _client . connected_at
145
- Logger . info ( `[SocketAuthority] Socket ${ socket . id } disconnected from client "${ _client . user . username } " after ${ disconnectTime } ms (Reason: ${ reason } )` )
146
- delete this . clients [ socket . id ]
147
- }
148
- } )
147
+ // Logs
148
+ socket . on ( 'set_log_listener' , ( level ) => Logger . addSocketListener ( socket , level ) )
149
+ socket . on ( 'remove_log_listener' , ( ) => Logger . removeSocketListener ( socket . id ) )
149
150
150
- //
151
- // Events for testing
152
- //
153
- socket . on ( 'message_all_users' , ( payload ) => {
154
- // admin user can send a message to all authenticated users
155
- // displays on the web app as a toast
156
- const client = this . clients [ socket . id ] || { }
157
- if ( client . user ?. isAdminOrUp ) {
158
- this . emitter ( 'admin_message' , payload . message || '' )
159
- } else {
160
- Logger . error ( `[SocketAuthority] Non-admin user sent the message_all_users event` )
161
- }
162
- } )
163
- socket . on ( 'ping' , ( ) => {
164
- const client = this . clients [ socket . id ] || { }
165
- const user = client . user || { }
166
- Logger . debug ( `[SocketAuthority] Received ping from socket ${ user . username || 'No User' } ` )
167
- socket . emit ( 'pong' )
151
+ // Sent automatically from socket.io clients
152
+ socket . on ( 'disconnect' , ( reason ) => {
153
+ Logger . removeSocketListener ( socket . id )
154
+
155
+ const _client = this . clients [ socket . id ]
156
+ if ( ! _client ) {
157
+ Logger . warn ( `[SocketAuthority] Socket ${ socket . id } disconnect, no client (Reason: ${ reason } )` )
158
+ } else if ( ! _client . user ) {
159
+ Logger . info ( `[SocketAuthority] Unauth socket ${ socket . id } disconnected (Reason: ${ reason } )` )
160
+ delete this . clients [ socket . id ]
161
+ } else {
162
+ Logger . debug ( '[SocketAuthority] User Offline ' + _client . user . username )
163
+ this . adminEmitter ( 'user_offline' , _client . user . toJSONForPublic ( this . Server . playbackSessionManager . sessions ) )
164
+
165
+ const disconnectTime = Date . now ( ) - _client . connected_at
166
+ Logger . info ( `[SocketAuthority] Socket ${ socket . id } disconnected from client "${ _client . user . username } " after ${ disconnectTime } ms (Reason: ${ reason } )` )
167
+ delete this . clients [ socket . id ]
168
+ }
169
+ } )
170
+
171
+ //
172
+ // Events for testing
173
+ //
174
+ socket . on ( 'message_all_users' , ( payload ) => {
175
+ // admin user can send a message to all authenticated users
176
+ // displays on the web app as a toast
177
+ const client = this . clients [ socket . id ] || { }
178
+ if ( client . user ?. isAdminOrUp ) {
179
+ this . emitter ( 'admin_message' , payload . message || '' )
180
+ } else {
181
+ Logger . error ( `[SocketAuthority] Non-admin user sent the message_all_users event` )
182
+ }
183
+ } )
184
+ socket . on ( 'ping' , ( ) => {
185
+ const client = this . clients [ socket . id ] || { }
186
+ const user = client . user || { }
187
+ Logger . debug ( `[SocketAuthority] Received ping from socket ${ user . username || 'No User' } ` )
188
+ socket . emit ( 'pong' )
189
+ } )
168
190
} )
169
191
} )
170
192
}
0 commit comments