Calls in Next.js
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 server gives out.

Who may call
Your server declares calls as it declares rooms, by pattern:
// lib/realtime.ts
// Calls between two people, support:34:42: the two it names, one of them staff.
realtime.calls( 'support:{a}:{b}', async ( user, withUser, { a, b } ) => {
const pair = [ a, b ].map( Number );
return pair.includes( user ) && pair.includes( withUser ) && ( ( await isStaff( user ) ) || ( await isStaff( withUser ) ) );
} );
// The daily meeting: a group call for everyone signed in.
realtime.groupCalls( 'meeting:{id}', ( user ) => user !== null );
withUseris the other person: whomusercalls, or who callsuser. Your server is asked again when the callee answers, with the callee asuser.- The answer:
false,true, or{ info, publish, subscribe, admin, hidden }: what the others see ofuser(yourprofileby default), and in a group call whether they may send and receive, moderate, or stay unlisted. - The rooms of a group call can be encrypted end to end too:
realtime.groupCalls( 'meeting:{id}', rule, { encrypt: true } ). The rooms that come from a call always are. - User ids are integers for calls in this version. One that is not is refused with
no_user.
The calls route is already in app/api/realtime/[action]/route.ts.
The call screen
<CallScreen /> in the layout rings on every page under it. Put buttons where people call from:
'use client';
import { CallButton, GroupCallButton } from '@wordplus/realtime/react';
export function CallAnn() {
return (
<>
<CallButton to={ 34 } call="support:34:42" type="video" name="Ann" avatar="/avatars/ann.svg">Call Ann</CallButton>
<GroupCallButton name="meeting:daily" type="video" title="Daily meeting">Join the meeting</GroupCallButton>
</>
);
}
- A list with call buttons:
<OnlineList 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>.<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/(roomsincreateRealtime()), with its encryption worker beside it:postinstallcopies both.

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: async ( user, call ) => ( await staffIds() ).filter( ( id ) => ! call.split( ':' ).slice( 1 ).map( Number ).includes( id ) ),
} );
The screen then shows an Add person button, listing them with the names and pictures from your profile.
Your own call interface
The call screen is built on this, and nothing else:
'use client';
import { useCalls } from '@wordplus/realtime/react';
export function Ringing() {
const { calls, incoming } = useCalls(); // the calls of the page once loaded, and those ringing now
return incoming.map( ( call ) => (
<p key={ call.id }>
{ ( call.info as { name?: string } | null )?.name } is calling <button onClick={ () => call.accept() }>Answer</button>
</p>
) );
}
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 ) );
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.