hotwired / turbo-rails

Use Turbo in your Ruby on Rails app
https://turbo.hotwired.dev
MIT License
2.14k stars 330 forks source link

turbo_stream_from event on connect? #498

Open michelson opened 1 year ago

michelson commented 1 year ago

it seems there is no way to listen for an event when the turbo_stream_from is connected/disconnected.

I've managed a way to do it with an observer on the connected attribute, example:


    const element = document.querySelector("turbo-cable-stream-source");

    const observer = new MutationObserver(mutations => {
      mutations.forEach(mutation => {
        if (mutation.type === "attributes" && mutation.attributeName === "connected") {
          if (element.hasAttribute("connected")) {
            console.log("Element is connected");
            // Trigger your function here
            this.registerVisit()
          } else {
            console.log("Element is disconnected");
          }
        }
      });
    });

    observer.observe(element, {
      attributes: true
    });

while it works it would be handy to just receive an event and listen to it via docuemtn.addEventListener

what if we do something like the following in the TurboCableStreamSourceElement custom element?

subscriptionConnected() {
  this.setAttribute("connected", "");
  const event = new Event("subscription-connected");
  this.dispatchEvent(event);
}
seanpdoyle commented 1 year ago

Could you share more information on your use case? What kind of coordination are you hoping to achieve based on the connect and disconnect lifecycle hooks for the element?

michelson commented 1 year ago

I my app I have some logic in a wrapper stimulus controller, lets say app_controller.js, I want to avoid create another cable connection and rely on the stream_from tag to receive events.

1) deliver an update on the stream with a <div data-controller="events-handler" data-json="somejson*"> where somejson will be: {type: "event:type", data: more_json_here} 2) events_handler_contrloler gets the this.element.dataset.json when it appears, on connect, and handles custom events.

on that logic in app_controller.js I want to be sure that the stream_from is connected, so I can ask the server to process and deliver events only if the socket is connected.

<%= stream_from @app %>
controller: ***@***.***, target="event-div", data: data) in app controller I hope this helps to clarify the use case. Basically, I want to be sure when to ask the server to deliver events via js so the El jue, 5 oct 2023 a las 14:50, Sean Doyle ***@***.***>) escribió: > Could you share more information on your use case? What kind of > coordination are you hoping to achieve based on the connect and disconnect > lifecycle hooks for the element? > > — > Reply to this email directly, view it on GitHub > , > or unsubscribe > > . > You are receiving this because you authored the thread.Message ID: > ***@***.***> >
michelson commented 1 year ago

it would be interesting if we could publish an event on Turbo class, like turbo:stream_from:connected , or by giving an id to the element, turbo:stream_from:1234:connected so we could identify from various elements.

El jue, 5 oct 2023 a las 15:10, Miguel Michelsongs (< @.***>) escribió:

I my app I have some logic in a wrapper stimulus controller, lets say app_controller.js, I want to avoid create another cable connection and rely on the stream_from tag to receive events.

1) deliver an update on the stream with a <div data-controller="events-handler" data-json="somejson*"> where somejson will be: {type: "event:type", data: more_json_here} 2) events_handler_contrloler gets the this.element.dataset.json when it appears, on connect, and handles custom events.

on that logic in app_controller.js I want to be sure that the stream_from is connected, so I can ask the server to process and deliver events only if the socket is connected.

<%= stream_from @app %>
controller: ***@***.***, target="event-div", data: data) in app controller I hope this helps to clarify the use case. Basically, I want to be sure when to ask the server to deliver events via js so the El jue, 5 oct 2023 a las 14:50, Sean Doyle ***@***.***>) escribió: > Could you share more information on your use case? What kind of > coordination are you hoping to achieve based on the connect and disconnect > lifecycle hooks for the element? > > — > Reply to this email directly, view it on GitHub > , > or unsubscribe > > . > You are receiving this because you authored the thread.Message ID: > ***@***.***> >
ashak commented 6 months ago

I have a potential use case (unless of course what i'm doing is all wrong and there's a more correct way to do it).

It's based around broadcasting turbo streams from background tasks

I have some reporting tasks that we background. We display in spinner whilst the job is working and then a broadcast a turbo stream response containing the data when it's finished. This works fine for a job that takes any amount of time, but some jobs (especially if they run on sidekiq) finish very quickly... So quickly in fact that they have broadcast the response before the original request to the client has managed to get the client to setup the websocket and subscribe to the associated turbo stream.

As far as I can tell, the 'fix' here is to ensure that the turbo stream exists on the page before triggering the job... Which I tried by having the page load and then trigger an xhr request that creates the background job. The very quick jobs still manage to be enqueued and finish before the subscription to the turbo stream has been confirmed, so my response is basically lost. If there was an event that allowed me to confirm a successful subscription to a specific turbo stream, I could trigger the background job(s) after that had happened.

kennethgeerts commented 6 months ago

I'm running into the exact same issue as @ashak here. Could I'm missing something really big here, or this use case is not so wide spread as I'm thinking. Either way, it would be great if someone could point us to a "best practice" for the kind of scenarios.

koenhandekyn commented 6 months ago

our current 'workaround' is something like this. We have a job specific channel and use the broadcastable callback to know on the server that the client is ready. it's ready most of the time in practice, but sometimes it isn't and then we wait with the broadcast.

class JobChannel < Turbo::StreamsChannel
  def broadcastable(data)
    job = Job.find_by(id: data['job_id'])
    job&.update(broadcastable: true)
  end
end

and in the code that broadcasts something like this

    3.times do
      job.reload
      break if job.broadcastable
      Rails.logger.info "Channel for job #{job.id} is not ready yet, retrying"
      sleep 1
    end
    # do broadcast 
inspire22 commented 5 months ago

I'd find this useful as well. I'm using a messaging system & if the user loses internet for a bit (or the browser disconnects eventually if it's an inactive tab put to sleep) it's out of date and has to be refreshed.

Pepan commented 5 months ago

My situation is that jobs handles filling of days events page ... I should deliver day events in ActiveJob job by broadcast_append_to but when user switches to another page/day, it should stop streaming ...