Skip to main content
Markdown

Getting started

The realtime SDK has two halves:

  • Your server decides who may listen to a channel, call whom, and see what, and signs it. On Node, that is this package's @wordplus/realtime/server. On PHP and Laravel, it is wordplus/realtime-php, and on Python (Django, FastAPI) wordplus-realtime. All three answer the browser the same way.
  • The browser connects to WordPlus Cloud, asks your server for what it may do, and opens what arrives. That is this package's @wordplus/realtime (and /react, /native), whatever your server speaks.

Where your server's code differs by language, each example below comes in Node, PHP and Python.

1. Your app in WordPlus Cloud​

In your WordPlus Cloud account, go to Apps → Add app. Give the app a name and the addresses its pages are served from, such as https://app.example.com, plus http://localhost:3000 while you develop. Only pages on those addresses may connect.

  • Capacitor: an app in Capacitor is capacitor://localhost.
  • React Native: a React Native app sends no address, and is always let in.

You get the app's key, which is public because every page carries it, and its secret, which only your server has. Also make a local key of your own: it seals what goes through WordPlus Cloud, and it never leaves your servers.

WORDPLUS_SITE=s1500000007
WORDPLUS_SECRET=…
WORDPLUS_LOCAL_KEY=… # openssl rand -hex 32; the same on every server of the app, kept as long as the app

2. Install​

npm install @wordplus/realtime

The same packages are on WordPlus's own mirror, packages.wordplus.cloud, for a network that can't reach the registries:

  • npm: npm install https://packages.wordplus.cloud/npm/wordplus-realtime-1.0.0.tgz. Every version is listed in index.json.
  • Composer: composer config repositories.wordplus composer https://packages.wordplus.cloud/composer, then composer require as above.
  • pip and uv: add --find-links https://packages.wordplus.cloud/pypi/index.html.

Then copy the browser's two files to where your app serves its static files:

npx wordplus-realtime copy public/      # realtime-worker.js and realtime-rooms.min.js

The worker lets every tab of a browser share one connection. Without it, each tab connects by itself and counts against your connections. Copy the files again after each update of the npm package.

3. Your server​

Say who your users are, and who may listen to what:

// lib/realtime.ts
import { createRealtime } from '@wordplus/realtime/server';

export const realtime = createRealtime( {
site: process.env.WORDPLUS_SITE!,
secret: process.env.WORDPLUS_SECRET!,
localKey: process.env.WORDPLUS_LOCAL_KEY!,
// Who is asking: their id, or null for nobody.
user: async ( request ) => ( await getSession( request ) )?.userId ?? null,
// What others see of them: in presence, calls and the directory.
profile: async ( user ) => ( { name: await nameOf( user ), avatar: await avatarOf( user ) } ),
} );

// Who may listen to an order's updates: its customer.
realtime.channel( 'private-orders-', async ( user, channel ) => user !== null && ( await ownerOf( channel.slice( 15 ) ) ) === user );

The user is the signed-in user's id, or null (None) for nobody. Next, answer the browser at /api/realtime/auth, /calls and /keys:

// Next.js: app/api/realtime/[action]/route.ts
import { realtime } from '@/lib/realtime';
export const POST = realtime.route;

// Express, or any Node server
app.use( '/api/realtime', express.json(), realtime.node( ( req ) => req.user?.id ?? null ) );

// Hono, Remix, SvelteKit, Bun, Deno, workers: any fetch handler
app.post( '/api/realtime/:action', ( c ) => realtime.route( c.req.raw ) );

The routes take JSON POSTs only. A page on another site can't send JSON without asking first, and it is never allowed to.

4. The browser​

import { connect } from '@wordplus/realtime';

const rt = await connect( {
site: 's1500000007',
worker: '/realtime-worker.js',
rooms: '/realtime-rooms.min.js',
} );

const orders = rt.subscribe( 'private-orders-42' );
orders.on( 'updated', ( data ) => refresh( data ) );
orders.on( 'realtime:error', ( e ) => console.warn( e.code ) );

connect() first asks your keys route for the user's keys and identity. To skip that request, render your server's settings for the user into the page, and pass them as they are: connect( config ).

const config = await realtime.config( userId );   // in a Next.js server component, then <RealtimeProvider options={ config }>

5. Publish​

await realtime.publish( 'private-orders-42', 'updated', { status: 'paid' } );

Every browser on the channel gets { status: 'paid' }, sealed on the way with the channel's key.

Next​