ruby-on-railswebsocketactioncablehotwire-railsturbo-rails

Setting up Turbo stream broadcasts_refreshes, websocket error


I'm trying to configure a Rails 7.1.3/Turbo 2.0.5 app locally to try out broadcasts_refreshes for a simple model. On the view template that tries to turbo_stream_from this model, I'm getting websocket errors, indicating that it is trying to connect.

Here are the errors I am getting:

# in the browser, with or without the above config.action_cable.url
WebSocket connection to 'ws://localhost:3000/cable' failed: There was a bad response from the server.

# in the terminal window, while rails server is running
Started GET "/cable" for ::1 at 2024-04-18 01:14:20 -0700
Started GET "/cable" [WebSocket] for ::1 at 2024-04-18 01:14:20 -0700
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
WebSocket error occurred: undefined method `write_nonblock' for nil
::1 - - [18/Apr/2024:01:14:20 PDT] "GET /cable HTTP/1.1" -1 0
WebSocket error occurred: undefined method `write_nonblock' for nil

Main questions:

  1. Do the above error messages mean the authentication didn't pass?

  2. If Turbo creates its own channels, does it also create its own websocket connections and handle its own auth logic separately? Or does it use my app's ActionCable connection authentication logic in the app/channels/application_cable/connection.rb file? (My connection.rb file is currently empty, because we don't use ActionCable for anything else.)

  3. Does Rails start the redis server for ActionCable automatically with rails s?

What I tried that doesn't seem to work:

mount ActionCable.server => "/cable"
config.action_cable.url = "ws://localhost:3000/cable"
config.action_cable.allowed_request_origins = [/http:\/\/*/,/https:\/\/*/]

App configuration:

development:
  adapter: redis
  url: redis://localhost:6379/1
class FieldSlip < ApplicationRecord
  broadcasts_refreshes
<%= turbo_stream_from(:field_slip, dom_id(field_slip)) %>
<%= render(partial: "field_slips/row",
           locals: { field_slip: field_slip }) %>

Everything on the template side seems to be working, Turbo is generating turbo-cable-stream-source tags, but I notice there is no connected attribute:

<turbo-cable-stream-source channel="Turbo::StreamsChannel" signed-stream-name="ImZpZWxkX3NsaXBfam9iX3RyYWNrZXI6ZmllbGRfc2xpcF9qb2JfdHJhY2tlcl8xIg==--bcf536df9f893f646adb2ce946d4d8ffa166eb297cca1a5c7fa093c77b0da878"></turbo-cable-stream-source>

Clearly, the websocket is not working. What am I missing?


Solution

  • In my case it was the authentication issue. It is true that Turbo can connect to the websocket without authentication, but if your app requires authentication at that point, the websocket request needs authentication too. In our case, the whole view where the live updates should occur requires login.

    Adjusting app/channels/application_cable/connection.rb to use the app's authentication logic (from the cookie) made it so the Action Cable request was authenticated the same way.

    With this working, the turbo-cable-stream-source tag in the rendered page shows a connected attribute.