Pub / Sub
crossws supports native pub-sub API integration. A peer can be subscribed to a set of named topics using peer.subscribe(<name>). Messages can be published to a topic using peer.publish(<name>, <message>). When publishing to a topic, the peer that does the publish is excluded from the broadcast so it won't receive its own message.
publish(), which already excludes the sender by its own documented pub/sub semantics. The observable behavior is identical either way.import { defineHooks } from "crossws";
const hooks = defineHooks({
upgrade(req) {
return {
// namespace: new URL(req.url).pathname
};
},
open(peer) {
// Send welcome to the new client
peer.send("Welcome to the server!");
// Join new client to the "chat" topic
peer.subscribe("chat");
// Notify every other connected client
peer.publish("chat", `[system] ${peer} joined!`);
},
message(peer, message) {
// The server re-broadcasts incoming messages to everyone
peer.publish("chat", `[${peer}] ${message}`);
},
close(peer) {
peer.publish("chat", `[system] ${peer} has left the chat!`);
peer.unsubscribe("chat");
},
});
By default pub/sub is in-memory and local to one instance. To span a cluster, relay messages over a shared backplane with a sync adapter.
Publishing from outside a peer
peer.publish() is convenient inside a hook, but sometimes you need to broadcast from somewhere that isn't tied to any single connection: an HTTP route, a cron job, or a webhook handler. The object returned by an adapter factory (e.g. nodeAdapter(...), or the instance backing crossws/adapters/cloudflare) exposes this same capability directly, alongside a read-only peers map of every currently connected peer:
import nodeAdapter from "crossws/adapters/node";
const ws = nodeAdapter({ hooks: { message() {} } });
// Broadcast to every peer subscribed to "chat", in every namespace
ws.publish("chat", "Server is restarting for maintenance");
// Inspect connected peers (Map<namespace, Set<Peer>>)
for (const [namespace, peers] of ws.peers) {
console.log(namespace, peers.size);
}
publish(topic, message, { compress?, namespace? })
namespace: scopes delivery to peers in that namespace only. Omit it for a global publish that reaches subscribers across every namespace.- Timing: delivery to local subscribers is fully synchronous —
publish()sends to every matching local peer before it returns, and it does not return a promise. The only asynchronous, best-effort part is relaying to other instances over an optional sync backplane; that relay is fire-and-forget and never throws out ofpublish(), so a flaky backplane can't block or crash the caller.