Chapter 12 of 15

Changes on the web, in the open windows

Freddy's colleague edits a team note in the browser. On Freddy's desktop the open list changes a moment later, without a refresh and without polling. Laravel announced the change; Elyra listened, the way Echo does in a browser.

Follow a channel

App::new().backend(
    Backend::new("https://notes.example.com")
        .broadcasting(Broadcasting::pusher("app-key"))   // REVERB_APP_KEY
        .follow("notes", "/api/notes"),                  // private-notes: re-run what read /api/notes
)

follow ties a Laravel channel to a resource. When an event arrives on private-notes, every live query that read /api/notes re-runs, coalesced, and is pushed only if the result changed, which is chapter 7's machinery fed from the network. A resource generated with --backend already follows its channel; follow_all(resources::follows()) wires them all at once.

The connection

  • The protocol is Pusher's, which Askr (with --pusher), Reverb and Pusher speak. It goes to the backend's own host, and .host(), .port() and .scheme() point it at a Reverb on its own port. It is wss only, except to loopback.
  • It opens once the user is signed in and closes on sign-out. A dropped connection reconnects, backing off from one second up to thirty, and then re-runs everything followed, because events sent in the meantime are lost.
  • Private channels are authorized by the backend, as the signed-in user, at /api/broadcasting/auth. The app secret stays on the server.
  • Laravel's ->toOthers() works: while connected, Backend sends X-Socket-ID, so Laravel can skip the app whose own write already re-ran its queries.

What each event does

  1. It re-runs the live queries that read the followed resource.
  2. It is a domain event, BackendEvent { channel, event, data }, so chapter 4's App::listen can log it or raise a notification.
  3. It reaches the windows:
import { onBackendEvent, broadcasting } from "@elyra/runtime";

onBackendEvent("notes", "NoteUpdated", (data) => toast(`${data.model.title} was updated`));
// $broadcasting.state: "connecting" | "connected" | "offline"

Freddy shows a small dot in the corner from $broadcasting.state. One caution from the guide: the data is the server's, untrusted like any input. Re-running a query does not read it at all, only the fact that something changed. Presence channels are not supported yet.

On the Laravel side

php artisan install:broadcasting --reverb   # or Askr: askr serve --pusher
// app/Models/Note.php
class Note extends Model
{
    use BroadcastsEvents;

    public function broadcastOn(string $event): array
    {
        return [new PrivateChannel('notes')];
    }
}

// routes/channels.php
Broadcast::channel('notes', fn ($user) => true);

// bootstrap/app.php: channel auth under /api, with the Sanctum token
->withBroadcasting(__DIR__.'/../routes/channels.php', [
    'prefix' => 'api', 'middleware' => ['api', 'auth:sanctum'],
])

Broadcast events are queued by default, so the Laravel app needs a queue worker, or QUEUE_CONNECTION=sync. Freddy's channel rule lets every signed-in user listen, because the whole team shares the notes; a stricter app returns a real check there. The guide is the broadcasting section of A Laravel backend, and the design is RFC 0006.

What you learned

  • .broadcasting(Broadcasting::pusher(key)).follow(channel, path) listens like Echo, over the protocol Askr, Reverb and Pusher speak.
  • The connection follows sign-in, reconnects with backoff and re-runs everything followed afterwards.
  • Each event re-runs live queries, is a BackendEvent in Rust, and reaches the page through onBackendEvent.
  • The event's data is untrusted, and presence channels are not supported yet.
Next: in Chapter 13 Freddy gets on a train with no network.