Calls in Laravel
Calls between two people that ring in every tab and device the callee has open, group calls on the WordPlus Cloud media servers, and the Better Messages call screen: the buttons, the ring and the call window, with no call code. The media of a call is encrypted end to end, with keys your app gives out.

Who may call
Your app declares calls by pattern, as it declares rooms. In the boot() of a service provider:
use App\Models\User;
use WordPlus\Realtime\Realtime;
public function boot(): void
{
// Added when the SDK is first used: artisan commands then run without the app credentials.
$this->callAfterResolving( Realtime::class, function ( Realtime $realtime ) {
$staff = fn ( $id ) => (bool) User::find( $id )?->staff;
// Calls between two people, support:34:42: the two it names, one of them staff.
$realtime->calls( 'support:{a}:{b}', function ( $user, $with, array $p ) use ( $staff ) {
$pair = [ (int) $p['a'], (int) $p['b'] ];
return in_array( $user, $pair, true ) && in_array( $with, $pair, true ) && ( $staff( $user ) || $staff( $with ) );
} );
// The daily meeting: a group call for everyone signed in.
$realtime->groupCalls( 'meeting:{id}', fn ( $user ) => null !== User::find( $user ) );
} );
}
$userand$withare ids, not models.$withis the other person: whom$usercalls, or who calls$user. Your app is asked again when the callee answers, with the callee as$user.- The answer:
false,true, or an array:info(what the others see of$user: your profile by default), and in a group callpublish,subscribe,adminandhidden. - The rooms of a group call can be encrypted end to end too:
groupCalls( 'meeting:{id}', $rule, [ 'encrypt' => true ] ). The rooms that come from a call always are. - User ids are integers for calls in this version, as they are in Laravel by default. One that is not is refused with
no_user.
The browser calls route (/wordplus/realtime/calls) comes with the package.
The call screen
Start it on every page where a call may ring, and put buttons where people call from:
// resources/js/app.js
const rt = await connect( window.wordplusRealtimeConfig );
await rt.callScreen(); // the ring and the call window, on this page
<wordplus-call to="{{ $agent->id }}" call="support:{{ min( $agent->id, auth()->id() ) }}:{{ max( $agent->id, auth()->id() ) }}" type="video" name="{{ $agent->name }}" avatar="{{ $agent->avatar }}">Call {{ $agent->name }}</wordplus-call>
<wordplus-room room="meeting:daily" type="video" title="Daily meeting">Join the meeting</wordplus-room>
- A list with call buttons:
<wordplus-online room="shop" call="support:{pair}">puts one on the row of every member (presence). - Its words come in English, German, Spanish, French, Italian, Dutch, Polish, Croatian and Hebrew, by the page
<html lang="{{ app()->getLocale() }}">.rt.callScreen( { strings: { … } } )changes any of them. - Its look is that of Better Messages, in a shadow root: your styles do not reach in, and its colours are CSS custom properties your page sets.
- Group calls need the room client in
public/(theroomsconfig), with its encryption worker beside it:wordplus-realtime copy publicputs both there.

Adding people
Once a call is up, either side may ring someone else in, and it goes on as a group call. Say whom the call screen offers:
$realtime->calls( 'support:{a}:{b}', $maySupport, [
// The staff not in the call, each still asked of $maySupport.
'people' => fn ( $user, string $call ) => User::where( 'staff', true )->whereNotIn( 'id', array_slice( explode( ':', $call ), 1 ) )->pluck( 'id' )->all(),
] );
The screen then shows an Add person button, listing them with their profile names and pictures.
Your own call interface
The call screen is built on this, and nothing else:
const calls = await rt.calls();
const call = await calls.start( 'support:34:42', { to: 34, type: 'video' } );
call.on( 'state', ( state ) => show( state ) ); // ringing, connecting, connected, reconnecting, ended
call.on( 'remote', () => call.remote.camera?.attach( video ) );
calls.on( 'incoming', async ( incoming ) => {
const answered = await incoming.accept(); // the call, or the group call it rings into. Or incoming.decline( 'busy' )
} );
const meeting = await calls.group( 'meeting:daily', { type: 'video' } );
meeting.on( 'person', ( person ) => person.camera?.attach( tileFor( person ) ) );

Every event, why a call ended, and what WordPlus Cloud sees: calls in depth.