REST API & Configuration
1. Controller
Source: src/controllers/websocket-clients/websocket-client.controller.tsExtends: BaseControllerBase Path: /socket/websocket/clients (defined in RestPaths.WEBSOCKET_CLIENTS) DI Dependencies: SignalEventService
// packages/signal/src/controllers/websocket-clients/websocket-client.controller.ts
@controller({ path: RestPaths.WEBSOCKET_CLIENTS })
export class WebSocketClientController extends BaseController {
constructor(
@inject({
key: BindingKeys.build({
namespace: BindingNamespaces.SERVICE,
key: SignalEventService.name,
}),
})
private readonly _signalEventService: SignalEventService,
) {
super({ scope: WebSocketClientController.name });
}
}2. REST Endpoints
Full path prefix: /v1/api/socket/websocket/clients
| Method | Path | Auth | Route Key | Description |
|---|---|---|---|---|
GET | /status | None | GET_STATUS | Server readiness and connected client count |
GET | / | JWT, Basic | GET_CLIENTS | List all connected clients |
GET | /{clientId} | JWT, Basic | GET_CLIENT_BY_ID | Get specific client details |
POST | /broadcast | JWT, Basic | BROADCAST | Broadcast message to all clients |
POST | /rooms/{roomName}/send | JWT, Basic | SEND_TO_ROOM | Send message to a room |
POST | /{clientId}/send | JWT, Basic | SEND_TO_CLIENT | Send message to a specific client |
POST | /{clientId}/disconnect | JWT, Basic | DISCONNECT_CLIENT | Force-disconnect a client |
3. Route Definitions (OpenAPI)
Source: src/controllers/websocket-clients/definitions.ts
All routes are defined using IAuthenticateRouteConfig with Zod schemas for request validation and OpenAPI documentation.
Shared Schemas
// packages/signal/src/controllers/websocket-clients/definitions.ts
const ClientIdParam = z.object({
clientId: z.string().openapi({
param: { name: 'clientId', in: 'path', description: 'WebSocket client ID' },
}),
});
const RoomNameParam = z.object({
roomName: z.string().openapi({
param: { name: 'roomName', in: 'path', description: 'WebSocket room name' },
}),
});
const MessageBody = z.object({
topic: z.string(),
data: z.any(),
});GET /status
No authentication required.
Response:
{ "isReady": true, "clientCount": 5 }Response schema: z.object({ isReady: z.boolean(), clientCount: z.number() })
Controller logic:
const isReady = this._signalEventService.isReady();
const clientCount = isReady ? this._signalEventService.getClientCount() : 0;
return context.json({ isReady, clientCount }, HTTP.ResultCodes.RS_2.Ok);GET / (List Clients)
Authentication: JWT or Basic.
Response:
{
"clients": [
{
"id": "abc123",
"state": "authenticated",
"encrypted": true,
"rooms": ["ws-default"],
"connectedAt": 1704067200000,
"lastActivity": 1704067260000,
"metadata": {}
}
]
}Response schema: z.object({ clients: z.array(WebSocketClientInfoSchema) })
GET /
Authentication: JWT or Basic. Returns 404 if client not found.
Response schema: WebSocketClientInfoSchema
Controller logic:
const client = this._signalEventService.getClientInfo({ clientId });
if (!client) {
return context.json({ error: 'Client not found' }, HTTP.ResultCodes.RS_4.NotFound);
}
return context.json(client, HTTP.ResultCodes.RS_2.Ok);POST /broadcast
Authentication: JWT or Basic.
Request body:
{ "topic": "order.updated", "data": { "orderId": "123" } }Request schema: MessageBody — z.object({ topic: z.string(), data: z.any() })
Response: { "success": true }
POST /rooms/{roomName}/send
Authentication: JWT or Basic.
Path params: roomName (string) Request body: Same as broadcast (MessageBody) Response: { "success": true }
POST /{clientId}/send
Authentication: JWT or Basic.
Path params: clientId (string) Request body: Same as broadcast (MessageBody) Response: { "success": true }
POST /{clientId}/disconnect
Authentication: JWT or Basic. Returns 404 if client not found.
Response:
// Success
{ "success": true }
// Not found
{ "error": "Client not found" }Controller logic:
const success = this._signalEventService.disconnectClient({ clientId });
if (!success) {
return context.json({ error: 'Client not found' }, HTTP.ResultCodes.RS_4.NotFound);
}
return context.json({ success: true }, HTTP.ResultCodes.RS_2.Ok);4. Security Policy
| Rule | Enforcement |
|---|---|
| Mandatory encryption | requireEncryption: true — clients must complete ECDH handshake during authentication |
| No public key = reject | Authentication without clientPublicKey field causes handshake rejection |
| JWT required | Only Bearer token authentication is supported for WebSocket via JWTTokenService |
| REST auth | REST endpoints accept both JWT (Authorization: Bearer <token>) and Basic (Authorization: Basic <base64>) strategies |
| Per-client keys | Each client gets a unique ephemeral ECDH-derived AES-256-GCM key |
| Forward secrecy | New ECDH key pair per connection; session keys are never reused |
| Key cleanup | AES key is deleted from server memory when client disconnects |
| Outbound encryption | outboundTransformer automatically encrypts all outgoing messages (except connected and error) |
| Inbound validation | messageHandler validates { iv, ct } structure before attempting decryption |
5. Standalone Binary Compilation
The compile script creates a standalone Linux x64 binary for deployment in debian:12-slim Docker containers:
#!/bin/sh
bun build \
--compile \
--minify-whitespace \
--minify-syntax \
--sourcemap \
--target=bun-linux-x64 \
./dist/index.js \
--outfile ./dist/signalSince @venizia/ignis 0.0.7-7 removed heavy optional dependencies (socket.io, bullmq, etc.) from the barrel export, the compile script is straightforward with no stub workarounds or --external flags needed.