Demo

WebSocket

Define IWebsocket handlers with @Websocket, inject WebsocketServer by path, then send or brocast text and binary frames to connected clients.

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
TSXhandler + module
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
TSXwrappers
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 frame

Send 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.

TSXsendText
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).

TSXsendBinary
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
TSXbrocast from controller
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}
TSXbrocast binary
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.

TSXraw send
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
TSXreceive
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()