@@ -165,7 +165,7 @@ function sendMessage(ws, message, source, target) {
165165
166166/**
167167 * Handles message injection from master to specified targets
168- * @param {{ target: string, message: string | Buffer } } data - The message data containing target and message
168+ * @param {InjectionMessage } data - The message data containing target and message
169169 */
170170function handleMasterInjection ( data ) {
171171 if ( data . target === 'all-clients' ) {
@@ -209,41 +209,150 @@ function handleMasterInjection(data) {
209209 }
210210}
211211
212+ /**
213+ * Handles connection closure requests from master
214+ * @param {CloseMessage } data - The close data containing connection ID and optional reason
215+ */
216+ function handleMasterClose ( data ) {
217+ const connectionId = data . connectionId ;
218+ const reason = data . reason || 'Closed by websocket-multiplex master control' ;
219+
220+ logger . info ( `Master requesting to close connection: ${ connectionId } ` ) ;
221+
222+ const client = connections . clients . get ( connectionId ) ;
223+ const upstream = connections . upstreams . get ( connectionId ) ;
224+
225+ if ( ! client && ! upstream ) {
226+ logger . warn ( `Connection ${ connectionId } not found for closure` ) ;
227+ return ;
228+ }
229+
230+ // Close client connection if it exists
231+ if ( client && client . ws . readyState === WebSocket . OPEN ) {
232+ logger . info ( `Closing client connection for ${ connectionId } ` ) ;
233+ client . ws . close ( 1000 , reason ) ;
234+ }
235+
236+ // Close upstream connection if it exists
237+ if ( upstream && upstream . ws . readyState === WebSocket . OPEN ) {
238+ logger . info ( `Closing upstream connection for ${ connectionId } ` ) ;
239+ upstream . ws . close ( 1000 , reason ) ;
240+ }
241+
242+ // Clean up message queue
243+ connections . messageQueues . delete ( connectionId ) ;
244+
245+ // Notify root masters about the forced closure
246+ notifyRootMasters ( 'connection' , 'connection-closed-by-master' , connectionId , {
247+ reason,
248+ timestamp : new Date ( ) . toISOString ( ) ,
249+ } ) ;
250+ }
251+
252+ /**
253+ * @typedef {Object } InjectionMessage
254+ * @property {'inject' } type - Message type
255+ * @property {string } target - Target for injection ('all-clients', 'all-upstreams', 'client:...', 'upstream:...')
256+ * @property {string | Buffer } message - Message to inject
257+ */
258+
259+ /**
260+ * @typedef {Object } CloseMessage
261+ * @property {'close' } type - Message type
262+ * @property {string } connectionId - Connection ID to close
263+ * @property {string } [reason] - Optional reason for closure
264+ */
265+
266+ /**
267+ * Validates an injection message
268+ * @param {any } data - Data to validate
269+ * @returns {data is InjectionMessage } True if valid injection message
270+ */
271+ function validateInjectionMessage ( data ) {
272+ return (
273+ data &&
274+ typeof data === 'object' &&
275+ data . type === 'inject' &&
276+ typeof data . target === 'string' &&
277+ data . target . length > 0 &&
278+ ( typeof data . message === 'string' || Buffer . isBuffer ( data . message ) )
279+ ) ;
280+ }
281+
282+ /**
283+ * Validates a close message
284+ * @param {any } data - Data to validate
285+ * @returns {data is CloseMessage } True if valid close message
286+ */
287+ function validateCloseMessage ( data ) {
288+ return (
289+ data &&
290+ typeof data === 'object' &&
291+ data . type === 'close' &&
292+ typeof data . connectionId === 'string' &&
293+ data . connectionId . length > 0
294+ ) ;
295+ }
296+
297+ /**
298+ * Handles messages from root master connections
299+ * @param {string } message - The message received
300+ * @throws {Error } When message is invalid
301+ */
302+ function handleRootMasterMessage ( message ) {
303+ const data = JSON . parse ( message ) ;
304+ logger . debug (
305+ `multiplexer <- master client: root ${ JSON . stringify ( data ) } `
306+ ) ;
307+
308+ if ( validateInjectionMessage ( data ) ) return handleMasterInjection ( data ) ;
309+ else if ( validateCloseMessage ( data ) ) return handleMasterClose ( data ) ;
310+ else throw new Error ( `Invalid master message: unsupported type '${ data ?. type } ' or missing required fields. Message: ${ JSON . stringify ( data ) } ` ) ;
311+ }
312+
313+ /**
314+ * Handles messages from master connection with type = 'client'
315+ * @param {string } targetPath - The target client path
316+ * @param {string } message - The message to forward
317+ */
318+ function handleMasterMessageForClient ( targetPath , message ) {
319+ const client = connections . clients . get ( targetPath ) ;
320+ if ( client ?. connected ) {
321+ sendMessage ( client . ws , message , 'master' , `client:${ targetPath } ` ) ;
322+ }
323+ }
324+
325+ /**
326+ * Handles messages from master connection with type = 'upstream'
327+ * @param {string } targetPath - The target upstream path
328+ * @param {string } message - The message to forward
329+ */
330+ function handleMasterMessageForUpstream ( targetPath , message ) {
331+ const upstream = connections . upstreams . get ( targetPath ) ;
332+ if ( upstream ?. connected ) {
333+ sendMessage ( upstream . ws , message , 'master' , `upstream:${ targetPath } ` ) ;
334+ }
335+ }
336+
212337/**
213338 * Handles messages received from the master connection
214339 * @param { MasterConnection } masterConnection - The master WebSocket connection
215340 * @param {string } message - The message received
216341 */
217342function handleMasterMessage ( masterConnection , message ) {
218- const { path, type, targetPath, ws } = masterConnection ;
219- if ( type === 'root' ) {
220- try {
221- /** @type {{ type: string, target: string, message: string } } */
222- const data = JSON . parse ( message ) ;
223- const target = data . target ;
224- const contents = data . message ;
225- logger . debug (
226- `multiplexer <- master client: ${ type } ${ target } ${ contents } `
227- ) ;
228-
229- if ( data . type === 'inject' ) {
230- handleMasterInjection ( data ) ;
231- }
232- } catch ( error ) {
233- logger . error ( 'Error processing master message:' , error ) ;
234- }
235- } else {
236- if ( type === 'client' ) {
237- const client = connections . clients . get ( targetPath ) ;
238- if ( client ?. connected ) {
239- sendMessage ( client . ws , message , 'master' , `client:${ targetPath } ` ) ;
240- }
241- } else if ( type === 'upstream' ) {
242- const upstream = connections . upstreams . get ( targetPath ) ;
243- if ( upstream ?. connected ) {
244- sendMessage ( upstream . ws , message , 'master' , `upstream:${ targetPath } ` ) ;
245- }
343+ const { type, targetPath } = masterConnection ;
344+
345+ try {
346+ switch ( type ) {
347+ case 'root' :
348+ return handleRootMasterMessage ( message ) ;
349+ case 'client' :
350+ return handleMasterMessageForClient ( targetPath , message ) ;
351+ case 'upstream' :
352+ return handleMasterMessageForUpstream ( targetPath , message ) ;
246353 }
354+ } catch ( error ) {
355+ logger . error ( 'Error processing master message:' , error ) ;
247356 }
248357}
249358
0 commit comments