Can you have two separate views (on different browsers) stream to each other?

I’ve sent a lot of time the last week trying to understand turbo broadcasting/streaming. I did this using a failed project I tried years ago using, at first using jquery and coffee script and later using stimulus (just and another learning effort).

The failed concept was to create a bar game that was kind of like BINGO - just backwards. While in BINGO, you buy cards and the caller starts the game and calls out number until someone wins. In my version, the caller is the application and calls number based on how many players have purchased cards. The game starts off with around 10 number called. You can then join the game by buying cards. For each card you buy, you pick a card out of 4 options (with a time limit). You can see the call board and you try to find your best choice and pick a card. Crazy idea that failed because the bartender had to collect money and allow you to play.

The game has models

  Gamer # or Player
    has_many :cards 
    has_many :players, through: :cards
  Watcher # or Caller (admin user)
  Card 
    belongs_to :game
    belongs_to :player

There only two view

  • The Watchers view that is updated on Payers action (order card, pick card)
  • The Gamers view that allow you to play/pick cards, or order new cards.

What I’ve been attempting do is to have a two way broadcast.

# in gamer(player) view
= turbo_stream_from :gamer
# in watcher(callers) view
= turbo_stream_from :watcher

In other words,

  • any action(pick_card, order_cards) taken on the Gamers view streams changes to the Watcher view
  • any action(pay_card) taken in the Watchers view stream changes to the Gamer views

The Watchers controller only has one game based action:

def pay_cards
  @game = Game.current_game
  cards = @game.cards.where(player_id:params[:gamer_id],state:'new')
  if cards.present?
    @player = cards.first.player
    cards.update_all(state:'paid')
    @game.broadcast_replace_to "gamers",target:"gamer_#{@player.id}",partial: "gamers/control", locals: { game: @game, gamer:@player }
  end
  render turbo_stream: turbo_stream.replace("controls",partial: 'watchers/controls',locals:{game:@game})
end

The Gamers controller has four actions

  • play_card (form) - gets an empty card from the unpaid stack and has give you a choose of cards.
  • order_cards - if you have no cards to play you can order x cards. This will show up one the Watchers view as a button (Paid) on list of players actions
  • pick_card - responds to play_card picked in play_card
  • play - loop that is called (redirected-to) on any action and sends a turbo_stream to Watcher, basically a full page update (add new call, display last card picked, status of game and all players)

Through many trails and errors, I can get things to stream one way but not both.

In my current approach, any action in the Gamer side will update the Watchers view. This includes the Paid button if cards were ordered.

Click the Paid button should broadcast to the gamer, and by the log it does, but the Gamers view is not changed.

Started GET "/watchers/pay_cards?gamer_id=1" for 127.0.0.1 at 2022-11-16 17:27:18 -0600
Processing by WatchersController#pay_cards as HTML
  Parameters: {"gamer_id"=>"1"}
  ...
  Card Update All (2.2ms)  UPDATE "cards" SET "state" = ? WHERE "cards"."game_id" = ? AND "cards"."player_id" = ? AND "cards"."state" = ?  [["state", "paid"], ["game_id", 3], ["player_id", 1], ["state", "new"]]
  ↳ app/controllers/watchers_controller.rb:32:in `pay_cards'
  ...
  Rendered gamers/_control.html.slim (Duration: 3.9ms | Allocations: 1910)
[ActionCable] Broadcasting to gamers: "<turbo-stream action=\"replace\" target=\"gamer_1\"><template><turbo-frame id=\"gamer_1\"><table class=\"classic h-24 text-center vw-font-md\"><caption>Player (2022-11-16 16:50:11 -0600)</caption><tr><th>Name</th><th>Open</th><th>Ordered</th><th>Cards</th><th>Played</th><th>Action</th></tr><tr><...
Redirected to http://localhost:3000/watch
Completed 302 Found in 19ms (ActiveRecord: 3.4ms | Allocations: 5913)

I can see the broadcast and the replace template, but noting changes. If I reload the Gamers view, the view I tried to change with Turbo shows up.

In some of my many attemptes to get this to work I have seen:

Finished "/cable" [WebSocket] for 127.0.0.1 at 2022-11-16 17:59:28 -0600
Turbo::StreamsChannel stopped streaming from watcher
Started GET "/cable" for 127.0.0.1 at 2022-11-16 17:59:29 -0600
Started GET "/cable" [WebSocket] for 127.0.0.1 at 2022-11-16 17:59:29 -0600
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
Turbo::StreamsChannel is transmitting the subscription confirmation
Turbo::StreamsChannel is streaming from watcher

This comes down to my basic question

Can you have two separate views (on different browsers) stream to each other?

If so, how? What am I missing?

This in not the normal chat demo (one view with posts and comments).

EDIT: Guess not. I figured out I really didn’t need two streams. There was a physical process that had to take play - pay for cards. The watcher can take care of that.

I just added a refresh link to the gamer if cards were ordered and accomplished the same goal.

Again, this was just a learning exercise. Streaming and Broadcasting has several thing that have to be put in place, the right place, Not sure I’ll ever use it in my current apps, but at least I figured it out

The browsers can’t directly stream to each other, but they can indirectly through the server (i.e. an action on one browser sends a message to the server which then broadcasts to the other browser).