Skip to content

broadcast — Socket.IO broadcast

Imported from the facade: import { broadcast } from "@/framework/facade.js".

Emits Socket.IO events to targeted audiences. Authenticated sockets are auto-joined to "auth", "user:<id>", and "role:<role>" rooms on handshake. See Realtime.

Signature

FunctionSignatureDescription
broadcast(event, payload, options?) => voidEmit event with payload to the audiences in options

BroadcastOptions (all combinable):

OptionTypeTargets
allbooleanevery connected client
authbooleanall authenticated clients (room "auth")
users(number|string)[]user rooms ("user:42")
rolesstring[]role rooms ("role:admin")
roomsstring[]arbitrary custom rooms

Use cases

Targeted broadcasts

ts
import { broadcast } from "@/framework/facade.js";

broadcast("post.published", { postId: 1 }, { all: true });        // all connected clients
broadcast("notification.new", payload, { auth: true });           // all authenticated users
broadcast("user.message", payload, { users: [recipientId] });     // specific users
broadcast("admin.alert", payload, { roles: ["admin"] });          // specific roles
broadcast("chat.message", payload, { rooms: ["room:chat:general"] }); // custom rooms
broadcast("order.updated", payload, { users: [userId], roles: ["admin"] }); // combined

From a queue handler

ts
import { broadcast } from "@/framework/facade.js";

await broadcast("post.published", { postId, status: "live" }, { all: true });
await broadcast("user.notification", { message: "Your post is live!" }, { users: [authorId] });

Direct vs dispatchEvent

broadcast()dispatchEvent()
Broadcastsyesyes (via broadcast option)
Queues a jobnoyes (via queue option)
Best forfire-and-forget socket emitbroadcast + side effects in one call

Real world — keeping clients in sync

Server emits with the channel name embedded in the event name (<channel>.<action>); the Vue UI listens on pulse.channel(...) and registers .listen(event) — no custom channel strings to keep in sync:

ts
// modules/activity/controllers/activity.controller.ts — after a mutation
await dispatchEvent("activity.changed", { id, name }, { broadcast: { auth: true } });
vue
<!-- resources/src/pages/entity/index.vue — reactive list refresh -->
<script setup lang="ts">
import { pulse } from "@/plugins/pulse";
import { onMounted, onUnmounted } from "vue";

onMounted(() => {
  let ch = pulse.channel("entity");
  ch.listen("entity.changed", async () => { await fetchList(); });
});
onUnmounted(() => { pulse.leave("entity"); });
</script>

Private per-user channels use the user:<id> naming and are emitted with broadcast: { users: [id] } — e.g. export-notification pages listen on pulse.channel("user:${authUser.value.id}") and request reports announce report.*.ready back to that single user:

ts
// modules/report/jobs/collectionExaminerExport.ts
await dispatchEvent("report.collectionexaminerexport.ready", { downloadUrl, authId },
  { broadcast: { users: [authId] } });
vue
pulse.channel(`user:${authUser.value.id}`).listen("report.collectionexaminerexport.ready", (event) => {
  status.value = "ready";
  downloadUrl.value = event.data.downloadUrl;
});

Notes

  • Works within a single process out of the box; with REDIS=true, @socket.io/redis-adapter fans out across every server instance.
  • When the socket server is disabled (SOCKET=false) or unavailable in this process, broadcast either relays through Redis pub/sub (main process) or returns immediately — it never throws.
  • Client-side listeners use the Pulse plugin (pulse.channel("user:42").listen("post.published", cb)) — see Realtime.

Released under the MIT License.