Define & register
Implement IWebsocket on a class decorated with @Websocket. Register it on the root module under websocket. Inject WebsocketServer with the same path for send / brocast APIs.
- @Websocket({ path, ssl?, pingpongInterval? }) — path is the URL endpoint; pingpongInterval is keep-alive in ms
- @Module({ websocket: [ChatWebsocket] }) — register the handler class
- @InjectWebsocketServer("/chat-websocket") — inject the server for that path (works on the handler, services, or controllers)
- onAuthenticate — return ClientInfo ({ id, … }) to accept, or null to reject
1import {
2 InjectWebsocketServer,
3 Module,
4 Websocket,
5 WebsocketServer,
6} from "@quan-erp/shared-backend-core";
7import type {
8 ClientInfo,
9 IWebsocket,
10 WebsocketClient,
11 WebsocketEvents,
12} from "@quan-erp/shared-backend-core";
13import type { IncomingMessage } from "http";
14import type WebSocket from "ws";
15import metadata from "../../module.metadata.json" with { type: "json" };
16
17@Websocket({
18 ssl: false,
19 pingpongInterval: 1000,
20 path: "/chat-websocket",
21})
22export class ChatWebsocket implements IWebsocket {
23 @InjectWebsocketServer("/chat-websocket")
24 websocket: WebsocketServer;
25
26 onAuthenticate(ws: WebSocket, req: IncomingMessage): ClientInfo | null {
27 return { id: "user-42" }; // null rejects the connection
28 }
29
30 on(
31 event: WebsocketEvents,
32 client: WebsocketClient,
33 data: any,
34 isBinary: boolean,
35 ) {
36 // see receive section
37 }
38
39 onUpgrade() {}
40 onDestoryed() {}
41}
42
43@Module({
44 name: metadata.name,
45 providers: [],
46 controllers: [],
47 entities: [],
48 websocket: [ChatWebsocket],
49})
50export class MyPluginModule {}Message wrappers
Outbound payloads must implement WebsocketMessage (getMessage()). Use the built-in wrappers — do not pass raw strings to sendText / brocast.
- WebsocketTextEventMessage(event, payload) — JSON { event, payload }; preferred for app events
- WebsocketTextMessage(data) — plain text / raw JSON string frame
- WebsocketBinaryMessage(payload: Buffer) — binary Buffer frame
1import {
2 WebsocketBinaryMessage,
3 WebsocketTextEventMessage,
4 WebsocketTextMessage,
5} from "@quan-erp/shared-backend-core";
6
7new WebsocketTextEventMessage("kitchen", { orderId: 12 });
8// → send/brocast as JSON: {"event":"kitchen","payload":{"orderId":12}}
9
10new WebsocketTextMessage("Hello");
11// → plain text frame
12
13new WebsocketBinaryMessage(Buffer.from([0x01, 0x02]));
14// → binary frameSend text (one client)
Target a client by the id returned from onAuthenticate (ClientInfo.id). sendText accepts WebsocketTextEventMessage or WebsocketTextMessage. All sockets sharing that client id receive the frame.
1import {
2 InjectWebsocketServer,
3 Service,
4 WebsocketServer,
5 WebsocketTextEventMessage,
6 WebsocketTextMessage,
7} from "@quan-erp/shared-backend-core";
8
9@Service()
10export class ChatPushService {
11 @InjectWebsocketServer("/chat-websocket")
12 private ws: WebsocketServer;
13
14 notifyUser(userId: string) {
15 this.ws.sendText(
16 userId,
17 new WebsocketTextEventMessage("inbox", {
18 title: "New message",
19 }),
20 );
21
22 this.ws.sendText(
23 userId,
24 new WebsocketTextMessage("ping"),
25 );
26 }
27}Send binary (one client)
Use sendBinary with WebsocketBinaryMessage for Buffer payloads (files, compressed blobs, custom protocols).
1import {
2 InjectWebsocketServer,
3 Service,
4 WebsocketBinaryMessage,
5 WebsocketServer,
6} from "@quan-erp/shared-backend-core";
7
8@Service()
9export class FileStreamService {
10 @InjectWebsocketServer("/chat-websocket")
11 private ws: WebsocketServer;
12
13 pushChunk(clientId: string, chunk: Buffer) {
14 this.ws.sendBinary(
15 clientId,
16 new WebsocketBinaryMessage(chunk),
17 );
18 }
19}Brocast (all clients)
WebsocketServer.brocast (spelled brocast in the API) sends one WebsocketMessage to every connected client on that path. Works with text event, plain text, or binary wrappers.
- Method name is brocast — not broadcast
- Use from controllers/services after domain writes (e.g. kitchen board refresh)
- Payload must be a WebsocketMessage wrapper
1import {
2 Controller,
3 InjectWebsocketServer,
4 Post,
5 ResponseDto,
6 WebsocketServer,
7 WebsocketTextEventMessage,
8} from "@quan-erp/shared-backend-core";
9
10@Controller("/order")
11export class OrderController {
12 @InjectWebsocketServer("/chat-websocket")
13 private wsServer: WebsocketServer;
14
15 @Post("/")
16 async create() {
17 // … persist order …
18 this.wsServer.brocast(
19 new WebsocketTextEventMessage("kitchen", "New Order"),
20 );
21 return ResponseDto.ok({ ok: true });
22 }
23}1import {
2 WebsocketBinaryMessage,
3 WebsocketServer,
4} from "@quan-erp/shared-backend-core";
5
6declare const ws: WebsocketServer;
7ws.brocast(new WebsocketBinaryMessage(Buffer.from("raw-bytes")));Raw send on current client
Inside on(), you can also write to the active socket with client.getWebsocket().send(...). Prefer WebsocketServer helpers when targeting by client id or brocasting.
1import type { WebsocketClient } from "@quan-erp/shared-backend-core";
2import { WebsocketTextEventMessage } from "@quan-erp/shared-backend-core";
3
4function reply(client: WebsocketClient) {
5 client
6 .getWebsocket()
7 .send(new WebsocketTextEventMessage("ack", { ok: true }).getMessage());
8}Receive text & binary
on(event, client, data, isBinary) is called for connection lifecycle and frames. Use WebsocketMessageHandler to route structured text events ({ event, payload }) vs generic message / close handlers. Binary frames skip JSON event parsing.
- isBinary === false — data is a string; JSON with an event field can match onTextEvent("name")
- isBinary === true — data is binary; use .on("message", …) (text-event routing is skipped)
- onTextEvent callbacks take no args — close over data / client from on() if you need the payload
- Always call .execute() at the end of the chain
1import {
2 InjectWebsocketServer,
3 Websocket,
4 WebsocketMessageHandler,
5 WebsocketServer,
6 WebsocketTextEventMessage,
7} from "@quan-erp/shared-backend-core";
8import type {
9 IWebsocket,
10 WebsocketClient,
11 WebsocketEvents,
12} from "@quan-erp/shared-backend-core";
13
14@Websocket({ ssl: false, pingpongInterval: 1000, path: "/chat-websocket" })
15export class ChatWebsocket implements IWebsocket {
16 @InjectWebsocketServer("/chat-websocket")
17 websocket: WebsocketServer;
18
19 onAuthenticate() {
20 return { id: "user-42" };
21 }
22
23 on(
24 event: WebsocketEvents,
25 client: WebsocketClient,
26 data: any,
27 isBinary: boolean,
28 ) {
29 new WebsocketMessageHandler(event, data, isBinary)
30 .onTextEvent("chat-room", () => {
31 const parsed = JSON.parse(String(data));
32 this.websocket.brocast(
33 new WebsocketTextEventMessage("chat-room", parsed.payload),
34 );
35 })
36 .on("message", () => {
37 if (isBinary) {
38 // Buffer / ArrayBuffer-like frame in data
39 return;
40 }
41 // plain text or non-event JSON
42 })
43 .on("close", () => {
44 // client disconnected
45 })
46 .execute();
47 }
48
49 onUpgrade() {}
50 onDestoryed() {}
51}Inspect clients
- getAllClients() — every WebsocketClient on this path
- getByClientId(id) — sockets for that authenticated id (array or null)
- client.getClientId() / getClientData() — identity from onAuthenticate
WebsocketServer API
- sendText(clientId, WebsocketTextEventMessage | WebsocketTextMessage)
- sendBinary(clientId, WebsocketBinaryMessage)
- brocast(WebsocketMessage) — all clients on the path
- getByClientId(clientId) / getAllClients()