Rails 7 and Turbo Streams Subscribed Only to Focused Windows

When using streams to broadcast notifications it happens what it should: notification are broadcasted! Which means all open window that subscribed to the notification channel will get the message. This is most of the time the desired behavior, but not always.

In our case, streams are used to notify the user of her last action status. Something like “Yeah, it worked! Well done” or “OMG! It failed, try differently“. It doesn’t make sense to publish this message to all open windows, just the one focused will suffice plenty, because it’s a direct notification. It might even make sense for all kind of user’s notification, even to notify the status of the last background task.

Broadcasting still make sense when targeted to update the page content for other purpose than notifications, so I wrote a little piece of code to toggle the stream connection when the user “blurs” or “focuses” windows. Something that it’s probably worth sharing.

class Focus {

  static boot() {
    this.onFocusStreams()
  }

  /** Enable streams with the class 'on-focus' only when the focus is on the window */
  static onFocusStreams() {
    let isWindowFocused = true

    window.addEventListener('focus', () => {
      isWindowFocused = true
      document.querySelectorAll("turbo-cable-stream-source.on-focus").forEach((i) => {
        if (!Turbo.session.streamObserver.sources.has(i)) {
          i.connectedCallback()
        }
      })
    })

    window.addEventListener('blur', () => {
      isWindowFocused = false
      document.querySelectorAll("turbo-cable-stream-source.on-focus").forEach((i) => {
        i.disconnectedCallback()
      })
    })

    document.addEventListener('turbo:load', () => {
      if (!isWindowFocused) {
        document.querySelectorAll("turbo-cable-stream-source.on-focus").forEach((i) => {
          i.disconnectedCallback()
        })
      }
    })
  }
}

export { Focus }

Import Focus, run Focus.boot() once and let the magic happen.


Posted

in

by

Tags: