Rooms in Laravel
A room is a name pages join to get its events. The Laravel broadcasting names them: each channel is a room of the same name, so new PrivateChannel( 'orders.1042' ) and new PresenceChannel( 'shop' ) are the rooms orders.1042 and shop. routes/channels.php declares them, once, with what each keeps and allows.
Declaring rooms: routes/channels.php
Written as for any broadcaster, with the room options as a third argument:
use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\Broadcast;
// The updates of an order: its customer, and the staff, the last 20 kept. {order} is bound to the model, as in routes.
Broadcast::channel( 'orders.{order}', fn ( User $user, Order $order ) => $user->id === $order->customer_id || $user->staff, [ 'history' => 20 ] );
// A chat between two people, chat.34.42: those two only. They send messages and typing to it, and the messages are kept.
Broadcast::channel( 'chat.{a}.{b}', fn ( User $user, string $a, string $b ) => in_array( (string) $user->id, [ $a, $b ], true ), [ 'history' => 50, 'emit' => [ 'message', 'typing' ] ] );
// The page of a product: anyone, signed in or not, with a live count of who is looking.
Broadcast::channel( 'product.{id}', fn () => true, [ 'public' => true, 'count' => true ] );
- The user is the one signed in to the session, on the
webmiddleware of the browser routes. - The answer:
falsefor no,truefor yes, or an array: the member info, which makes the room a presence room, as the Laravel presence channels have it. - When it runs: when a page joins, and when its token is renewed (every 6 hours by default). A page asks for every room it joins in the same moment in one request, and keeps the tokens across reconnects and moves.
What a room keeps and allows
| Option | Default | |
|---|---|---|
history | none | Events kept for pages that reconnect: a number (50), up to 1,000, for 30 minutes. Or [ 'last' => 50, 'days' => 0.01 ] for less (0.01 of a day is about 15 minutes) |
emit | none | Events pages may send to the room: names, or '*' for any (events from the page) |
presence | off | Its members are listed, with their info (presence). An answer that is an array sets it too |
count | off | A live count of who is in it, at any size, without a list |
webhook | none | Which of the page events your app gets too (sending) |
public | off | Anyone may join, signed in or not, without asking the callback |
seal | on | Its events, its member info and its history are sealed with the room key (encryption) |
The options travel in the room token, signed by your app, so WordPlus Cloud enforces them and nothing is set in two places.
Rooms of your own
Beside routes/channels.php, the facade declares rooms by pattern, as the other packages do, with : in their names if you like. In the boot() of a service provider:
use WordPlus\Realtime\Realtime;
$this->callAfterResolving( Realtime::class, function ( Realtime $realtime ) {
$realtime->room( 'staff', fn ( $user ) => (bool) User::find( $user )?->staff, [ 'presence' => true ] );
} );
$useris an id here, not a model, and the pattern parts come as an array:fn ( $user, array $p ) => ….- Which decides: a room a
room()pattern owns is decided there.routes/channels.phpdecides the rest. callAfterResolvingadds it when the SDK is first used, so artisan commands run without the app credentials.
Joining
const order = rt.join( 'orders.1042' );
order.on( 'updated', ( data, meta ) => showStatus( data.status ) ); // meta: { from, id, at }
order.on( 'joined', () => showLive() );
order.on( 'error', ( e ) => console.warn( e.code ) ); // forbidden, no_rule, full …
rt.leave( 'orders.1042' );
- The event name is what the event
broadcastAs()says, or its class (App\Events\OrderUpdated). - The tabs of a user ride one connection (the worker), each with its own rooms.
- Events reach a listener opened: you never handle sealed values.
- Reserved names:
joined,join,leave,countanderrorare kept for the room itself.
History, and nothing lost
A room keeps its events for 30 minutes at most, for recovery. Your app keeps its own records, such as the messages of a chat, from the webhook.
A room that keeps history gives a page that comes back what it missed: a reconnect within the history span gets the missed events first, in order, with meta.replayed, then the new ones.
const { events, more } = await order.history( { limit: 50 } ); // the newest 50, oldest first
const older = await order.history( { before: events[ 0 ].id, limit: 50 } );
Events over 64 KB are sent but not kept. volatile ones are never kept. Your app reads and erases history too: Realtime::in( 'orders.1042' )->history() and ->forget() (sending).
More, with Node and Python side by side: rooms in depth.