11/*
2- ** channels.js v1.3 // 7th July 2023
2+ ** channels.js v1.4 // 16th March 2026
33** Author: Adrian Salceanu and contributors // @essenciary
44** GenieFramework.com // Genie.jl
55*/
@@ -74,22 +74,30 @@ Genie.initWebChannel = function(channel = Genie.Settings.webchannels_default_rou
7474 WebChannel . processingHandlers = [ ] ;
7575
7676 const waitForOpenConnection = ( WebChannel ) => {
77- return new Promise ( ( resolve , reject ) => {
77+ // Reuse a single shared promise while a reconnection is in progress to
78+ // avoid spawning one interval per queued message (memory leak).
79+ if ( WebChannel . _openConnectionPromise ) {
80+ return WebChannel . _openConnectionPromise ;
81+ }
82+ WebChannel . _openConnectionPromise = new Promise ( ( resolve , reject ) => {
7883 const maxNumberOfAttempts = Genie . Settings . webchannels_connection_attempts ;
7984 const delay = Genie . Settings . webchannels_reconnect_delay ;
8085
8186 let currentAttempt = 0 ;
8287 const interval = setInterval ( ( ) => {
8388 if ( currentAttempt > maxNumberOfAttempts - 1 ) {
8489 clearInterval ( interval ) ;
90+ WebChannel . _openConnectionPromise = null ;
8591 reject ( new Error ( 'Maximum number of attempts exceeded: Message not sent.' ) ) ;
8692 } else if ( WebChannel . socket . readyState === 1 ) {
8793 clearInterval ( interval ) ;
94+ WebChannel . _openConnectionPromise = null ;
8895 resolve ( ) ;
8996 } ;
9097 currentAttempt ++ ;
9198 } , delay )
92- } )
99+ } ) ;
100+ return WebChannel . _openConnectionPromise ;
93101 }
94102
95103 WebChannel . socket = newSocketConnection ( WebChannel ) ;
@@ -141,7 +149,9 @@ Genie.initWebChannel = function(channel = Genie.Settings.webchannels_default_rou
141149 displayAlert ( WebChannel ) ;
142150 if ( Genie . Settings . webchannels_autosubscribe ) {
143151 if ( isDev ( ) ) console . info ( 'Attempting to reconnect! ' ) ;
144- setTimeout ( function ( ) {
152+ clearTimeout ( WebChannel . _reconnectTimer ) ;
153+ WebChannel . _reconnectTimer = setTimeout ( function ( ) {
154+ WebChannel . _openConnectionPromise = null ;
145155 WebChannel . socket = newSocketConnection ( WebChannel ) ;
146156 } , Genie . Settings . webchannels_reconnect_delay ) ;
147157 }
@@ -209,7 +219,7 @@ function displayAlert(WebChannel, content = 'Can not reach the server. Trying to
209219
210220function deleteAlert ( WebChannel ) {
211221 WebChannel . ws_disconnected = false ;
212- clearInterval ( WebChannel . alertTimeout ) ;
222+ clearTimeout ( WebChannel . alertTimeout ) ;
213223 if ( WebChannel . parent ) WebChannel . parent . ws_disconnected = false ;
214224
215225 if ( Genie . allConnected ( ) ) {
@@ -219,22 +229,35 @@ function deleteAlert(WebChannel) {
219229}
220230
221231function newSocketConnection ( WebChannel , host = Genie . Settings . websockets_exposed_host ) {
232+ // Remove listeners from the previous socket before creating a new one so
233+ // the old WebSocket object can be garbage-collected.
234+ if ( WebChannel . _socketListeners ) {
235+ const prev = WebChannel . _socketListeners . socket ;
236+ if ( prev ) {
237+ prev . removeEventListener ( 'open' , WebChannel . _socketListeners . open ) ;
238+ prev . removeEventListener ( 'message' , WebChannel . _socketListeners . message ) ;
239+ prev . removeEventListener ( 'error' , WebChannel . _socketListeners . error ) ;
240+ prev . removeEventListener ( 'close' , WebChannel . _socketListeners . close ) ;
241+ }
242+ WebChannel . _socketListeners = null ;
243+ }
244+
222245 let ws = new WebSocket ( Genie . Settings . websockets_protocol + '//' + host
223246 + ( Genie . Settings . websockets_exposed_port > 0 ? ( ':' + Genie . Settings . websockets_exposed_port ) : '' )
224247 + ( ( ( Genie . Settings . base_path . trim ( ) === '' || Genie . Settings . base_path . startsWith ( '/' ) ) ? '' : '/' ) + Genie . Settings . base_path )
225248 + ( ( ( Genie . Settings . websockets_base_path . trim ( ) === '' || Genie . Settings . websockets_base_path . startsWith ( '/' ) ) ? '' : '/' ) + Genie . Settings . websockets_base_path ) ) ;
226249
227- ws . addEventListener ( 'open' , event => {
250+ const onOpen = event => {
228251 const handlers = WebChannel . openHandlers . concat ( Genie . WebChannels . openHandlers )
229252 for ( let i = 0 ; i < handlers . length ; i ++ ) {
230253 let f = handlers [ i ] ;
231254 if ( typeof f === 'function' ) {
232255 f ( event ) ;
233256 }
234257 }
235- } ) ;
258+ } ;
236259
237- ws . addEventListener ( 'message' , event => {
260+ const onMessage = event => {
238261 const handlers = WebChannel . messageHandlers . concat ( Genie . WebChannels . messageHandlers )
239262 for ( let i = 0 ; i < handlers . length ; i ++ ) {
240263 let f = handlers [ i ] ;
@@ -243,36 +266,35 @@ function newSocketConnection(WebChannel, host = Genie.Settings.websockets_expose
243266 }
244267 }
245268 WebChannel . lastMessageAt = Date . now ( ) ;
246- } ) ;
269+ } ;
247270
248- ws . addEventListener ( 'error' , event => {
271+ const onError = event => {
249272 const handlers = WebChannel . errorHandlers . concat ( Genie . WebChannels . errorHandlers )
250273 for ( let i = 0 ; i < handlers . length ; i ++ ) {
251274 let f = handlers [ i ] ;
252275 if ( typeof f === 'function' ) {
253276 f ( event ) ;
254277 }
255278 }
256- } ) ;
279+ } ;
257280
258- ws . addEventListener ( 'close' , event => {
281+ const onClose = event => {
259282 const handlers = WebChannel . closeHandlers . concat ( Genie . WebChannels . closeHandlers )
260283 for ( let i = 0 ; i < handlers . length ; i ++ ) {
261284 let f = handlers [ i ] ;
262285 if ( typeof f === 'function' ) {
263286 f ( event ) ;
264287 }
265288 }
266- ws . onmessage = null ;
267- ws . onerror = null ;
268- ws . onclose = null ;
269- ws . onopen = null ;
270- ws = null ;
271- } ) ;
289+ } ;
272290
273- ws . addEventListener ( 'error' , _ => {
274- // WebChannel.socket = newSocketConnection();
275- } ) ;
291+ ws . addEventListener ( 'open' , onOpen ) ;
292+ ws . addEventListener ( 'message' , onMessage ) ;
293+ ws . addEventListener ( 'error' , onError ) ;
294+ ws . addEventListener ( 'close' , onClose ) ;
295+
296+ // Store named listener refs so they can be removed on the next reconnect.
297+ WebChannel . _socketListeners = { socket : ws , open : onOpen , message : onMessage , error : onError , close : onClose } ;
276298
277299 return ws
278300}
@@ -377,8 +399,8 @@ function subscribe(WebChannel, trial = 1) {
377399 WebChannel . sendMessageTo ( WebChannel . channel , window . Genie . Settings . webchannels_subscribe_channel ) ;
378400 } else if ( trial < Genie . Settings . webchannels_subscription_trials ) {
379401 if ( isDev ( ) ) console . warn ( 'Queuing subscription' ) ;
380- trial ++ ;
381- setTimeout ( subscribe . bind ( this , WebChannel , trial ) , Genie . Settings . webchannels_timeout ) ;
402+ clearTimeout ( WebChannel . _subscribeTimer ) ;
403+ WebChannel . _subscribeTimer = setTimeout ( subscribe . bind ( this , WebChannel , trial + 1 ) , Genie . Settings . webchannels_timeout ) ;
382404 } else {
383405 displayAlert ( WebChannel ) ;
384406 }
0 commit comments