Client-side
First-party Socket
@zerotal/client ships a small, dependency-free Socket that speaks the native broadcast
protocol and exposes a familiar realtime-client API — no external client library, and it works
with the lightweight ws and redis drivers (no Pusher credentials needed).
Flow apps need none of this. The client is bundled into /__flow/runtime.js and created the
first time a page declares a @on('socket:…') listener, so those listeners
are live with no script of your own. Read on only if you want a configured client — a different
host, your own auth endpoint — or if you are subscribing from code that is not a Flow component.
The package root is fine to import in browser code. It used to be a bundle error: the root also exports
ClientProvider, which reaches the CLI commands andawait import("bun"), and a browser bundler rejects that during resolution — before tree-shaking can discard the half you did not want.@zerotal/clientnow resolves to a browser-safe entry under thebrowsercondition, so a bundler getsSocket,ApiClientandCircuitBreakerand none of the server-side exports.@zerotal/client/Socketstill works and is still the leanest import ifSocketis all you need.
// in your client code
import { Socket } from "@zerotal/client";
const socket = new Socket(); // ws(s)://<host>/app/ws (matches the `path` config)
// Public channel
socket.channel("posts").listen("PostPublished", (e) => {
console.log("New post:", e.title);
});
// Private channel (the `private-` prefix is added for you; the WS connection carries the user)
socket.private(`orders.${id}`).listen("OrderUpdated", (e) => render(e.order));
// Presence channel — who's online
socket
.presence(`chat.${roomId}`)
.here((members) => setOnline(members))
.joining((m) => addOnline(m))
.leaving((m) => removeOnline(m))
.listen("Message", (e) => append(e));
// Only if you need Flow's listeners to use *this* client rather than the bundled
// one — assign before the runtime loads and it is used as-is:
window.Socket = socket;
Private and presence channels are authorized with a per-subscription HMAC signature (the same
model as Pusher): the client POSTs { socket_id, channel_name } to authEndpoint (default
/broadcasting/auth), the server runs the routes/channels.ts
rules and signs the result with the app's APP_KEY, and the client echoes the signature in its
subscribe. It's automatically re-fetched on reconnect (the signature is socket-bound). Pass CSRF
or other headers via auth.headers, or set authEndpoint: false to skip the fetch and rely on
connection-level authorization instead:
// in your client code
const socket = new Socket({
auth: { headers: { "X-CSRF-TOKEN": csrf } }, // sent on POST /broadcasting/auth
});
Connection state is observable via socket.on("connected" | "disconnected" | "reconnecting" | "error", cb); the client auto-reconnects and re-subscribes. Use socket.socketId() as the
X-Socket-ID header on your HTTP requests so server toOthers() broadcasts skip the originating
client.
Pusher-protocol clients
The server is also Pusher-protocol compatible, so the reference pusher-js client works unchanged:
// in your client code
import Pusher from "pusher-js";
const pusher = new Pusher("my-app-key", {
wsHost: window.location.hostname,
wsPort: 3000,
wssPort: 3000,
forceTLS: false,
disableStats: true,
enabledTransports: ["ws"],
// Private/presence channel auth — matches POST /broadcasting/auth
authEndpoint: "/broadcasting/auth",
auth: {
headers: {
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]')?.content ?? "",
},
},
});
// Subscribe to a public channel
const postsChannel = pusher.subscribe("posts");
postsChannel.bind("PostPublished", (data: { title: string; slug: string }) => {
console.log("New post:", data.title);
});
// Subscribe to a private channel
const ordersChannel = pusher.subscribe("private-orders.42");
ordersChannel.bind("OrderUpdated", (data) => {
console.log("Order updated:", data);
});
// Subscribe to a presence channel
const chatChannel = pusher.subscribe("presence-chat.room1");
chatChannel.bind("pusher:subscription_succeeded", (members) => {
console.log("Online members:", members);
});
Next steps
- Broadcasting overview — the guide's front page and the rest of the sections.
- Reference — the full API surface in one table.