ws: Second connection does not always fire the open event.

  • I’ve searched for any related issues and avoided creating a duplicate issue.

Description

I have two (almost identical) connections going to the same host/port but with different tokens in the URL. When using both together, the second connection does not always fire the “open” event. I use a time-out workaround to fix the issue. The second connection does actually open, as we can see it closes if no ping event is received after 1 min of inactivity.

Reproducible in:

  • version: ^6.1.4
  • Node.js version(s): v11.0.0
  • OS version(s): OSX 10.14 (Mojave)

Steps to reproduce:

init/connect sockets
let wsPrivate = new WebSocket(websocketPrivate, { 'force new connection': true } )
let wsPublic = new WebSocket(websocketPublic, { 'force new connection': true })

await setupWebsocket(wsPrivate, 'private')
await setupWebsocket(wsPublic, 'public')
setupWebsocket
function setupWebsocket(ws, type) {
  return new Promise((resolve, reject) => {
    console.log('Setting up websocket events for', type, 'channel')

    Sockets.ws[type] = ws
    let handledResolve = false

    // This timeout recovers the connection just fine, telling me that the "open" event doesn't always fire.
    setTimeout(() => {
      if (!handledResolve && ws.readyState === ws.OPEN) {
        console.log('Recovered ws connection')

        console.log('Opened websocket connection for', type, 'channel')

        resolve()
      }
    }, 5000)

    ws.on('open', () => {
      handledResolve = true
      console.log('Opened websocket connection for', type, 'channel')

      resolve()
    })

    ws.on('error', (error) => {
      Sockets.handleSocketError(error)
      console.log(error)
      reject()
    })

    ws.on('ping', () => {

    })

    ws.on('close', () => {
      console.log(type + ' websocket closed...')
    })
  })
}

Output:

Sometimes “open” fires:

Setting up websocket events for private channel
Opened websocket connection for private channel
Setting up websocket events for public channel
Opened websocket connection for public channel

Other times, it doesn’t fire:

Setting up websocket events for private channel
Opened websocket connection for private channel
Setting up websocket events for public channel
Recovered ws connection
Opened websocket connection for public channel

Expected result:

For both “open” events to fire

Actual result:

Only the first “open” event fires, but is recoverable using a timeout.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 15 (6 by maintainers)

Most upvoted comments

What I’ve found to be the most reliable is to just use a timer and check the connection state. If it’s connected, then fire the open event. If nested inside of a promise for a setupWebsocket function, then this connection event will only fire once.

The code looks like this:

const connectionCheckTimer = setInterval(() => {
  if (ws.readyState === ws.OPEN) {
    console.log('Recovered ws connection')
    ws.emit('open')
  }
}, 1000)

ws.on('open', () => {
  clearInterval(connectionCheckTimer)

  console.log('Opened websocket connection for', type, 'channel')
  resolve()
})

Just as a PR recommendation, you could check if a user is setting up ws.on("open") event later in the lifecycle and if the connection/event has already fired, then fire it for the new event.

I had this thought exactly but if this library is trying to stick strictly to the WHATWG spec then the spec would need to be updated first. I wish there was a “connect()” api like @bugs181 suggests.

What I think ws could do is warn people in their documentation that they should handle the “open” event immediately after making the connection or they might miss that event completely. This happened to me while I was setting up some unit tests. One of my tests would hang and it would not hit the error event and there is no on('timeout') event either.

I guess one of the things you could do if you’re setting up an on('open') event later in the code is check the readyState

function doSomethingOnOpen(){   
  // ... code
}

if (client.readyState === 1) { 
 doSomethingOnOpen()
} else {
  client.on('open', doSomethingOnOpen)
}

Something like this pseudo-code:

let ws = new WebSocket(url, { dontConnect: true })

ws.on("open", ...)
ws.connect()