Quickstart

Connect, subscribe, publish, and see who else is on the channel.

The whole thing

chat.ts
import * as Blackevin from '@blackevin/client';

const realtime = new Blackevin.Realtime({
  key: process.env.BLACKEVIN_KEY,
  clientId: 'alice',
});

const channel = realtime.channels.get('chat:lobby');

channel.subscribe(message => {
  console.log(message.name, message.data);
});

await channel.publish('message', { text: 'hello' });

That is a working realtime app. The rest of this page is what each line is actually doing.

Connecting

You never call connect(). The client opens the socket on the first operation that needs one and works its way through the handshake:

initialized → connecting → connected

If you want to watch it, connection state is an event:

realtime.connection.on('state', (state, reason) => {
  console.log(state, reason?.message);
});

clientId is your presence identity. Omit it and you get a random client-a3f9…, which is fine until you want to know who is on a channel.

Channels

channels.get(name) is local — it returns a handle and touches no network. The wire work happens on the first subscribe or publish.

const channel = realtime.channels.get('chat:lobby');

Get the channel before you connect if you need listeners ready for early messages. A Message frame for a channel the client has never heard of is dropped, because there is nothing to dispatch it to.

Subscribing

channel.subscribe(message => { /* every message on this channel */ });

channel.subscribe('order.created', message => { /* just that name */ });

The name form filters client-side. The server still delivers every message on the channel and the SDK discards the ones that do not match — so it saves you a branch, not bandwidth.

Publishing

await channel.publish('order.created', { id: 7 });
await channel.publish({ name: 'order.created', data: { id: 7 } });

The promise resolves when the server acknowledges the frame, and rejects on a Nack or a timeout (realtimeRequestTimeout, 10s by default). Awaiting it is worth doing: it is the only signal that the message was accepted rather than merely written to a socket.

Publishing while the connection is down does not throw — the frame is held and sent on reconnect. See offline publishes.

Presence

const channel = realtime.channels.get('chat:lobby');

channel.presence.subscribe((member, action) => {
  console.log(member.clientId, action);
});

channel.presence.enter({ status: 'online' });

const members = await channel.presence.get();

enter announces you, get asks the server who is there. Both are covered in presence.

Replaying what you missed

A subscriber that joins late can be handed the last N persisted messages immediately:

const channel = realtime.channels.get('chat:lobby', { params: { rewind: 20 } });

channel.subscribe(message => {
  console.log(message.data);
});

The rewound messages arrive as ordinary Message frames, oldest first, before the live ones — your callback cannot tell them apart, which is the point. The server caps rewind at 100.

From a server, without a socket

A job, a cron, or an inbound webhook can publish over HTTP with no connection to hold open:

import * as Blackevin from '@blackevin/client';

const rest = new Blackevin.Rest({ key: process.env.BLACKEVIN_KEY });

await rest.channels.get('orders:new').publish('order.created', { id: 7 });

It goes through the same fanout a socket publish does, so subscribers, account queues and integrations all see it identically.

Next

Your browser must not hold the key you just used. Authentication is how it connects without one.

On this page