Skip to main content
Markdown

Sending from Laravel

Your app sends events to rooms and to users: broadcast events, or the facade straight. Everyone in the room gets them, sealed on the way, and a room that keeps history keeps them.

Broadcast events​

namespace App\Events;

use App\Models\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;

class OrderUpdated implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets;

public function __construct( public Order $order ) {}

public function broadcastOn(): array
{
return [ new PrivateChannel( 'orders.' . $this->order->id ) ]; // the room orders.1042
}

public function broadcastAs(): string
{
return 'updated';
}

public function broadcastWith(): array
{
return [ 'id' => $this->order->id, 'status' => $this->order->status ];
}
}
public function status( Request $request, Order $order )
{
$order->update( $request->validate( [ 'status' => [ 'required', Rule::in( Order::STATUSES ) ] ] ) );

// Everyone in the room but the tab whose request this is, which shows the change already.
broadcast( new OrderUpdated( $order ) )->toOthers();

return response()->json( [ 'ok' => true ] );
}
  • The event name is the one broadcastAs() returns, or else its class.
  • Its data is what broadcastWith() returns, or else its public properties: up to 1 MB, sealed with the room key.
  • Timing: ShouldBroadcast goes through your queue (run a worker), ShouldBroadcastNow during the request.
  • Failures: a refusal from WordPlus Cloud throws the Laravel BroadcastException, with the code in its message. Broadcast after your change is saved: a failed broadcast is a missed live update, not a lost change.

toOthers()​

It skips the tab whose request caused the event, when the page sends its tab as X-Realtime-Tab:

await fetch( `/orders/${ id }/status`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': window.wordplusRealtimeConfig.auth.headers[ 'X-CSRF-TOKEN' ],
'X-Realtime-Tab': rt.tab,
},
body: JSON.stringify( { status: 'shipped' } ),
} );

// With axios, once: axios.defaults.headers.common[ 'X-Realtime-Tab' ] = rt.tab

With the facade​

The same chain as in every package:

use WordPlus\Realtime\Laravel\Facades\Realtime;

Realtime::to( 'orders.1042' )->emit( 'updated', [ 'status' => 'shipped' ] );
Realtime::to( [ 'orders.1042', 'staff' ] )->except( $request->header( 'X-Realtime-Tab' ) )->emit( 'updated', [ 'status' => 'shipped' ] );

// Every tab and device a user has open, sealed with their own key:
Realtime::toUser( $order->customer_id )->emit( 'notice', [ 'text' => 'Your order shipped' ] );

Realtime::batch( [
[ 'rooms' => 'orders.1042', 'event' => 'updated', 'data' => [ 'status' => 'shipped' ] ],
[ 'users' => 42, 'event' => 'notice', 'data' => [ 'text' => 'Your order shipped' ] ],
] ); // 100 events a request
rt.on( 'notice', ( data ) => toast( data.text ) );   // on the page, for its signed-in user

A refusal throws RealtimeError, with its errorCode: too_large, too_many, reserved, bad_room.

Asking about a room​

Realtime::in( 'shop' )->count();
Realtime::in( 'shop' )->members(); // of a presence room, their info opened
Realtime::in( 'chat.34.42' )->history( [ 'limit' => 50 ] );
Realtime::in( 'chat.34.42' )->forget( $id ); // one kept event, forget() for all
Realtime::in( 'chat.34.42' )->kick( 43 ); // out now, refused until their token ends, and the key moves on

Moving a key on needs a cache store that does not evict, for the key generations: WORDPLUS_REALTIME_GENERATIONS=redis (encryption).

Webhooks​

Your app can hear what happens in rooms. Set the address on your app page in your WordPlus Cloud account, https://shop.example.com/wordplus/realtime/webhook, which the package answers, outside the web middleware:

// routes/channels.php: a room says which events of the pages come here too
Broadcast::channel( 'chat.{a}.{b}', $isOneOf, [ 'history' => 50, 'emit' => [ 'message', 'typing' ], 'webhook' => [ 'message' ] ] );
// the boot() of a service provider
$this->callAfterResolving( Realtime::class, function ( Realtime $realtime ) {
$realtime->on( 'message', fn ( array $e ) => Message::firstOrCreate( [ 'event_id' => $e['event_id'] ], [ 'room' => $e['room'], 'from' => $e['from'], 'text' => $e['data']['text'] ] ) );
$realtime->on( 'user.offline', fn ( array $e ) => User::whereKey( $e['user'] )->update( [ 'away' => true ] ) );
} );
  • What comes: the page events a room names in webhook, by their name. room.occupied and room.vacated. member.joined and member.left. user.online and user.offline. '*' gets them all.
  • Checked and opened: each request is signed, and checked before your handler sees it. Sealed data comes opened.
  • At least once: each event has its id, so a repeat can be dropped. A request that fails is tried again for 24 hours.

More, with Node and Python side by side: sending in depth.