REST API & Cấu hình
1. Controller
Mã nguồn: src/controllers/websocket-clients/websocket-client.controller.tsKế thừa: BaseControllerBase Path: /socket/websocket/clients (định nghĩa trong 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
Tiền tố đường dẫn đầy đủ: /v1/api/socket/websocket/clients
| Phương thức | Đường dẫn | Xác thực | Route Key | Mô tả |
|---|---|---|---|---|
GET | /status | Không | GET_STATUS | Trạng thái sẵn sàng của server và số client đang kết nối |
GET | / | JWT, Basic | GET_CLIENTS | Liệt kê tất cả client đang kết nối |
GET | /{clientId} | JWT, Basic | GET_CLIENT_BY_ID | Lấy chi tiết client cụ thể |
POST | /broadcast | JWT, Basic | BROADCAST | Phát tin nhắn đến tất cả client |
POST | /rooms/{roomName}/send | JWT, Basic | SEND_TO_ROOM | Gửi tin nhắn đến một phòng |
POST | /{clientId}/send | JWT, Basic | SEND_TO_CLIENT | Gửi tin nhắn đến một client cụ thể |
POST | /{clientId}/disconnect | JWT, Basic | DISCONNECT_CLIENT | Buộc ngắt kết nối client |
3. Định nghĩa Route (OpenAPI)
Mã nguồn: src/controllers/websocket-clients/definitions.ts
Tất cả route được định nghĩa sử dụng IAuthenticateRouteConfig với Zod schemas cho xác thực request và tài liệu OpenAPI.
Schemas dùng chung
// 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
Không yêu cầu xác thực.
Response:
{ "isReady": true, "clientCount": 5 }Response schema: z.object({ isReady: z.boolean(), clientCount: z.number() })
Logic controller:
const isReady = this._signalEventService.isReady();
const clientCount = isReady ? this._signalEventService.getClientCount() : 0;
return context.json({ isReady, clientCount }, HTTP.ResultCodes.RS_2.Ok);GET / (Danh sách Client)
Xác thực: JWT hoặc 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 /
Xác thực: JWT hoặc Basic. Trả về 404 nếu không tìm thấy client.
Response schema: WebSocketClientInfoSchema
Logic controller:
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
Xác thực: JWT hoặc 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
Xác thực: JWT hoặc Basic.
Path params: roomName (string) Request body: Giống broadcast (MessageBody) Response: { "success": true }
POST /{clientId}/send
Xác thực: JWT hoặc Basic.
Path params: clientId (string) Request body: Giống broadcast (MessageBody) Response: { "success": true }
POST /{clientId}/disconnect
Xác thực: JWT hoặc Basic. Trả về 404 nếu không tìm thấy client.
Response:
// Thành công
{ "success": true }
// Không tìm thấy
{ "error": "Client not found" }Logic controller:
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. Chính sách bảo mật
| Quy tắc | Thực thi |
|---|---|
| Mã hóa bắt buộc | requireEncryption: true — client phải hoàn thành ECDH handshake trong quá trình xác thực |
| Không public key = từ chối | Xác thực thiếu trường clientPublicKey sẽ bị từ chối handshake |
| Yêu cầu JWT | Chỉ hỗ trợ xác thực Bearer token cho WebSocket qua JWTTokenService |
| Xác thực REST | REST endpoints chấp nhận cả JWT (Authorization: Bearer <token>) và Basic (Authorization: Basic <base64>) |
| Khóa theo client | Mỗi client nhận khóa AES-256-GCM dẫn xuất từ ECDH tạm thời riêng |
| Forward secrecy | Cặp khóa ECDH mới cho mỗi kết nối; khóa phiên không bao giờ tái sử dụng |
| Dọn dẹp khóa | Khóa AES bị xóa khỏi bộ nhớ server khi client ngắt kết nối |
| Mã hóa gửi đi | outboundTransformer tự động mã hóa tất cả tin nhắn gửi đi (trừ connected và error) |
| Xác thực đầu vào | messageHandler xác thực cấu trúc { iv, ct } trước khi giải mã |
5. Biên dịch Binary độc lập
Script compile tạo binary Linux x64 độc lập cho triển khai trong Docker container debian:12-slim:
#!/bin/sh
bun build \
--compile \
--minify-whitespace \
--minify-syntax \
--sourcemap \
--target=bun-linux-x64 \
./dist/index.js \
--outfile ./dist/signalKể từ @venizia/ignis 0.0.7-7 đã loại bỏ các dependency tùy chọn nặng (socket.io, bullmq, v.v.) khỏi barrel export, script biên dịch đơn giản không cần workaround stub hay flag --external.