@venizia/ignis-docs 0.0.5 → 0.0.6-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/wiki/best-practices/architecture-decisions.md +0 -8
- package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
- package/wiki/best-practices/performance-optimization.md +3 -3
- package/wiki/best-practices/security-guidelines.md +2 -2
- package/wiki/best-practices/troubleshooting-tips.md +1 -1
- package/wiki/guides/core-concepts/components-guide.md +1 -1
- package/wiki/guides/core-concepts/components.md +2 -2
- package/wiki/guides/core-concepts/dependency-injection.md +1 -1
- package/wiki/guides/core-concepts/services.md +1 -1
- package/wiki/guides/tutorials/building-a-crud-api.md +1 -1
- package/wiki/guides/tutorials/ecommerce-api.md +2 -2
- package/wiki/guides/tutorials/realtime-chat.md +6 -6
- package/wiki/guides/tutorials/testing.md +1 -1
- package/wiki/references/base/bootstrapping.md +0 -2
- package/wiki/references/base/components.md +2 -2
- package/wiki/references/base/controllers.md +0 -1
- package/wiki/references/base/datasources.md +1 -1
- package/wiki/references/base/dependency-injection.md +1 -1
- package/wiki/references/base/filter-system/quick-reference.md +0 -14
- package/wiki/references/base/middlewares.md +0 -8
- package/wiki/references/base/providers.md +0 -9
- package/wiki/references/base/services.md +0 -1
- package/wiki/references/components/authentication/api.md +444 -0
- package/wiki/references/components/authentication/errors.md +177 -0
- package/wiki/references/components/authentication/index.md +571 -0
- package/wiki/references/components/authentication/usage.md +781 -0
- package/wiki/references/components/health-check.md +292 -103
- package/wiki/references/components/index.md +14 -12
- package/wiki/references/components/mail/api.md +505 -0
- package/wiki/references/components/mail/errors.md +176 -0
- package/wiki/references/components/mail/index.md +535 -0
- package/wiki/references/components/mail/usage.md +404 -0
- package/wiki/references/components/request-tracker.md +229 -25
- package/wiki/references/components/socket-io/api.md +1051 -0
- package/wiki/references/components/socket-io/errors.md +119 -0
- package/wiki/references/components/socket-io/index.md +410 -0
- package/wiki/references/components/socket-io/usage.md +322 -0
- package/wiki/references/components/static-asset/api.md +261 -0
- package/wiki/references/components/static-asset/errors.md +89 -0
- package/wiki/references/components/static-asset/index.md +617 -0
- package/wiki/references/components/static-asset/usage.md +364 -0
- package/wiki/references/components/swagger.md +390 -110
- package/wiki/references/components/template/api-page.md +125 -0
- package/wiki/references/components/template/errors-page.md +100 -0
- package/wiki/references/components/template/index.md +104 -0
- package/wiki/references/components/template/setup-page.md +134 -0
- package/wiki/references/components/template/single-page.md +132 -0
- package/wiki/references/components/template/usage-page.md +127 -0
- package/wiki/references/components/websocket/api.md +508 -0
- package/wiki/references/components/websocket/errors.md +123 -0
- package/wiki/references/components/websocket/index.md +453 -0
- package/wiki/references/components/websocket/usage.md +475 -0
- package/wiki/references/helpers/cron/index.md +224 -0
- package/wiki/references/helpers/crypto/index.md +537 -0
- package/wiki/references/helpers/env/index.md +214 -0
- package/wiki/references/helpers/error/index.md +232 -0
- package/wiki/references/helpers/index.md +16 -15
- package/wiki/references/helpers/inversion/index.md +608 -0
- package/wiki/references/helpers/logger/index.md +600 -0
- package/wiki/references/helpers/network/api.md +986 -0
- package/wiki/references/helpers/network/index.md +620 -0
- package/wiki/references/helpers/queue/index.md +589 -0
- package/wiki/references/helpers/redis/index.md +495 -0
- package/wiki/references/helpers/socket-io/api.md +497 -0
- package/wiki/references/helpers/socket-io/index.md +513 -0
- package/wiki/references/helpers/storage/api.md +705 -0
- package/wiki/references/helpers/storage/index.md +583 -0
- package/wiki/references/helpers/template/index.md +66 -0
- package/wiki/references/helpers/template/single-page.md +126 -0
- package/wiki/references/helpers/testing/index.md +510 -0
- package/wiki/references/helpers/types/index.md +512 -0
- package/wiki/references/helpers/uid/index.md +272 -0
- package/wiki/references/helpers/websocket/api.md +736 -0
- package/wiki/references/helpers/websocket/index.md +574 -0
- package/wiki/references/helpers/worker-thread/index.md +470 -0
- package/wiki/references/quick-reference.md +3 -18
- package/wiki/references/utilities/jsx.md +1 -8
- package/wiki/references/utilities/statuses.md +0 -7
- package/wiki/references/components/authentication.md +0 -476
- package/wiki/references/components/mail.md +0 -687
- package/wiki/references/components/socket-io.md +0 -562
- package/wiki/references/components/static-asset.md +0 -1277
- package/wiki/references/helpers/cron.md +0 -108
- package/wiki/references/helpers/crypto.md +0 -132
- package/wiki/references/helpers/env.md +0 -83
- package/wiki/references/helpers/error.md +0 -97
- package/wiki/references/helpers/inversion.md +0 -176
- package/wiki/references/helpers/logger.md +0 -296
- package/wiki/references/helpers/network.md +0 -396
- package/wiki/references/helpers/queue.md +0 -150
- package/wiki/references/helpers/redis.md +0 -142
- package/wiki/references/helpers/socket-io.md +0 -932
- package/wiki/references/helpers/storage.md +0 -665
- package/wiki/references/helpers/testing.md +0 -133
- package/wiki/references/helpers/types.md +0 -167
- package/wiki/references/helpers/uid.md +0 -167
- package/wiki/references/helpers/worker-thread.md +0 -178
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
# WebSocket -- Setup & Configuration
|
|
2
|
+
|
|
3
|
+
> Bun-native real-time, bidirectional communication using pure WebSocket -- with Redis Pub/Sub for horizontal scaling, application-level heartbeat, and post-connection authentication.
|
|
4
|
+
|
|
5
|
+
> [!IMPORTANT]
|
|
6
|
+
> **Bun only.** The WebSocket component will throw an error if the runtime is Node.js. For Node.js support, use the [Socket.IO Component](../socket-io/) instead.
|
|
7
|
+
|
|
8
|
+
## Quick Reference
|
|
9
|
+
|
|
10
|
+
| Item | Value |
|
|
11
|
+
|------|-------|
|
|
12
|
+
| **Package** | `@venizia/ignis` (core component) + `@venizia/ignis-helpers` (helper classes) |
|
|
13
|
+
| **Component** | `WebSocketComponent` |
|
|
14
|
+
| **Server Helper** | [`WebSocketServerHelper`](/references/helpers/websocket/) |
|
|
15
|
+
| **Emitter Helper** | `WebSocketEmitter` (standalone Redis publisher) |
|
|
16
|
+
| **Runtimes** | Bun only (throws on Node.js) |
|
|
17
|
+
| **Scaling** | Redis Pub/Sub (ioredis -- single or Cluster) |
|
|
18
|
+
|
|
19
|
+
#### Import Paths
|
|
20
|
+
```typescript
|
|
21
|
+
// From core -- component + binding keys only
|
|
22
|
+
import {
|
|
23
|
+
WebSocketComponent,
|
|
24
|
+
WebSocketBindingKeys,
|
|
25
|
+
} from '@venizia/ignis';
|
|
26
|
+
|
|
27
|
+
// From helpers -- types, helpers, constants
|
|
28
|
+
import {
|
|
29
|
+
WebSocketServerHelper,
|
|
30
|
+
WebSocketEmitter,
|
|
31
|
+
WebSocketDefaults,
|
|
32
|
+
WebSocketEvents,
|
|
33
|
+
WebSocketChannels,
|
|
34
|
+
WebSocketClientStates,
|
|
35
|
+
WebSocketMessageTypes,
|
|
36
|
+
} from '@venizia/ignis-helpers';
|
|
37
|
+
|
|
38
|
+
import type {
|
|
39
|
+
IWebSocketServerOptions,
|
|
40
|
+
IWebSocketEmitterOptions,
|
|
41
|
+
IWebSocketClient,
|
|
42
|
+
IWebSocketMessage,
|
|
43
|
+
IRedisSocketMessage,
|
|
44
|
+
IBunWebSocketConfig,
|
|
45
|
+
TWebSocketAuthenticateFn,
|
|
46
|
+
TWebSocketValidateRoomFn,
|
|
47
|
+
TWebSocketClientConnectedFn,
|
|
48
|
+
TWebSocketClientDisconnectedFn,
|
|
49
|
+
TWebSocketMessageHandler,
|
|
50
|
+
TWebSocketOutboundTransformer,
|
|
51
|
+
TWebSocketHandshakeFn,
|
|
52
|
+
} from '@venizia/ignis-helpers';
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> [!NOTE]
|
|
56
|
+
> `IServerOptions` (the core component's subset type) is **not** exported from `@venizia/ignis`. Only `WebSocketBindingKeys` and `WebSocketComponent` are exported from the core package. All helper types, constants, and classes are imported from `@venizia/ignis-helpers`.
|
|
57
|
+
|
|
58
|
+
### Use Cases
|
|
59
|
+
|
|
60
|
+
- Live notifications and alerts
|
|
61
|
+
- Real-time chat and messaging
|
|
62
|
+
- Collaborative editing (docs, whiteboards)
|
|
63
|
+
- Live data streams (dashboards, monitoring)
|
|
64
|
+
- Multiplayer game state synchronization
|
|
65
|
+
- IoT device communication
|
|
66
|
+
- Background job progress updates (via `WebSocketEmitter`)
|
|
67
|
+
- Cross-service event broadcasting (via `WebSocketEmitter`)
|
|
68
|
+
|
|
69
|
+
## Setup
|
|
70
|
+
|
|
71
|
+
### Step 1: Install Dependencies
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Core dependency (already included via @venizia/ignis)
|
|
75
|
+
# ioredis is required for Redis Pub/Sub
|
|
76
|
+
bun add ioredis
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Step 2: Bind Required Services
|
|
80
|
+
|
|
81
|
+
In your application's `preConfigure()` method, bind the required services and register the component:
|
|
82
|
+
|
|
83
|
+
#### Full Setup Example
|
|
84
|
+
```typescript
|
|
85
|
+
import {
|
|
86
|
+
BaseApplication,
|
|
87
|
+
WebSocketComponent,
|
|
88
|
+
WebSocketBindingKeys,
|
|
89
|
+
} from '@venizia/ignis';
|
|
90
|
+
import {
|
|
91
|
+
RedisHelper,
|
|
92
|
+
} from '@venizia/ignis-helpers';
|
|
93
|
+
import type {
|
|
94
|
+
TWebSocketAuthenticateFn,
|
|
95
|
+
TWebSocketValidateRoomFn,
|
|
96
|
+
TWebSocketClientConnectedFn,
|
|
97
|
+
TWebSocketClientDisconnectedFn,
|
|
98
|
+
TWebSocketMessageHandler,
|
|
99
|
+
TWebSocketOutboundTransformer,
|
|
100
|
+
TWebSocketHandshakeFn,
|
|
101
|
+
IWebSocketServerOptions,
|
|
102
|
+
IBunWebSocketConfig,
|
|
103
|
+
ValueOrPromise,
|
|
104
|
+
} from '@venizia/ignis-helpers';
|
|
105
|
+
|
|
106
|
+
export class Application extends BaseApplication {
|
|
107
|
+
private redisHelper: RedisHelper;
|
|
108
|
+
|
|
109
|
+
preConfigure(): ValueOrPromise<void> {
|
|
110
|
+
this.setupWebSocket();
|
|
111
|
+
// ... other setup
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
setupWebSocket() {
|
|
115
|
+
// 1. Redis connection (required for cross-instance messaging)
|
|
116
|
+
this.redisHelper = new RedisHelper({
|
|
117
|
+
name: 'websocket-redis',
|
|
118
|
+
host: process.env.REDIS_HOST ?? 'localhost',
|
|
119
|
+
port: +(process.env.REDIS_PORT ?? 6379),
|
|
120
|
+
password: process.env.REDIS_PASSWORD,
|
|
121
|
+
autoConnect: false,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
this.bind<RedisHelper>({
|
|
125
|
+
key: WebSocketBindingKeys.REDIS_CONNECTION,
|
|
126
|
+
}).toValue(this.redisHelper);
|
|
127
|
+
|
|
128
|
+
// 2. Authentication handler (required)
|
|
129
|
+
const authenticateFn: TWebSocketAuthenticateFn = async (data) => {
|
|
130
|
+
const token = data.token as string;
|
|
131
|
+
if (!token) return null;
|
|
132
|
+
|
|
133
|
+
const user = await verifyJWT(token);
|
|
134
|
+
if (!user) return null;
|
|
135
|
+
|
|
136
|
+
return { userId: user.id, metadata: { role: user.role } };
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
this.bind<TWebSocketAuthenticateFn>({
|
|
140
|
+
key: WebSocketBindingKeys.AUTHENTICATE_HANDLER,
|
|
141
|
+
}).toValue(authenticateFn);
|
|
142
|
+
|
|
143
|
+
// 3. Room validation handler (optional -- joins rejected without this)
|
|
144
|
+
const validateRoomFn: TWebSocketValidateRoomFn = ({ clientId, userId, rooms }) => {
|
|
145
|
+
return rooms.filter(room => room.startsWith('public-'));
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
this.bind<TWebSocketValidateRoomFn>({
|
|
149
|
+
key: WebSocketBindingKeys.VALIDATE_ROOM_HANDLER,
|
|
150
|
+
}).toValue(validateRoomFn);
|
|
151
|
+
|
|
152
|
+
// 4. Client connected handler (optional)
|
|
153
|
+
const clientConnectedFn: TWebSocketClientConnectedFn = ({ clientId, userId }) => {
|
|
154
|
+
console.log('Client connected:', clientId, userId);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
this.bind<TWebSocketClientConnectedFn>({
|
|
158
|
+
key: WebSocketBindingKeys.CLIENT_CONNECTED_HANDLER,
|
|
159
|
+
}).toValue(clientConnectedFn);
|
|
160
|
+
|
|
161
|
+
// 5. Client disconnected handler (optional)
|
|
162
|
+
const clientDisconnectedFn: TWebSocketClientDisconnectedFn = ({ clientId, userId }) => {
|
|
163
|
+
console.log('Client disconnected:', clientId, userId);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
this.bind<TWebSocketClientDisconnectedFn>({
|
|
167
|
+
key: WebSocketBindingKeys.CLIENT_DISCONNECTED_HANDLER,
|
|
168
|
+
}).toValue(clientDisconnectedFn);
|
|
169
|
+
|
|
170
|
+
// 6. Message handler (optional -- for custom events)
|
|
171
|
+
const messageHandler: TWebSocketMessageHandler = ({ clientId, userId, message }) => {
|
|
172
|
+
console.log('Custom event:', message.event, message.data);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
this.bind<TWebSocketMessageHandler>({
|
|
176
|
+
key: WebSocketBindingKeys.MESSAGE_HANDLER,
|
|
177
|
+
}).toValue(messageHandler);
|
|
178
|
+
|
|
179
|
+
// 7. Outbound transformer (optional -- for per-client encryption)
|
|
180
|
+
const outboundTransformer: TWebSocketOutboundTransformer = async ({ client, event, data }) => {
|
|
181
|
+
if (!client.encrypted) return null;
|
|
182
|
+
// Encrypt using client's derived AES key (from ECDH handshake)
|
|
183
|
+
const encrypted = await encryptForClient(client.id, JSON.stringify({ event, data }));
|
|
184
|
+
return { event: 'encrypted', data: encrypted };
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
this.bind<TWebSocketOutboundTransformer>({
|
|
188
|
+
key: WebSocketBindingKeys.OUTBOUND_TRANSFORMER,
|
|
189
|
+
}).toValue(outboundTransformer);
|
|
190
|
+
|
|
191
|
+
// 8. Handshake handler (optional -- required when requireEncryption is true)
|
|
192
|
+
const handshakeFn: TWebSocketHandshakeFn = async ({ clientId, data }) => {
|
|
193
|
+
const clientPubKey = data.publicKey as string;
|
|
194
|
+
if (!clientPubKey) return null; // Reject -- no public key provided
|
|
195
|
+
const salt = crypto.getRandomValues(new Uint8Array(32));
|
|
196
|
+
const saltB64 = Buffer.from(salt).toString('base64');
|
|
197
|
+
const aesKey = await deriveSharedSecret(clientPubKey, salt);
|
|
198
|
+
storeClientKey(clientId, aesKey);
|
|
199
|
+
return { serverPublicKey: serverPublicKeyB64, salt: saltB64 };
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
this.bind<TWebSocketHandshakeFn>({
|
|
203
|
+
key: WebSocketBindingKeys.HANDSHAKE_HANDLER,
|
|
204
|
+
}).toValue(handshakeFn);
|
|
205
|
+
|
|
206
|
+
// 9. Server options (optional -- customize defaults)
|
|
207
|
+
this.bind<Partial<IWebSocketServerOptions>>({
|
|
208
|
+
key: WebSocketBindingKeys.SERVER_OPTIONS,
|
|
209
|
+
}).toValue({
|
|
210
|
+
identifier: 'my-app-websocket',
|
|
211
|
+
requireEncryption: true,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// 10. Register the component
|
|
215
|
+
this.component(WebSocketComponent);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Configuration
|
|
221
|
+
|
|
222
|
+
The core component's `IServerOptions` interface controls the WebSocket server setup. Default values come from `DEFAULT_SERVER_OPTIONS` and `WebSocketDefaults`:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
{
|
|
226
|
+
identifier: 'WEBSOCKET_SERVER',
|
|
227
|
+
path: '/ws', // WebSocketDefaults.PATH
|
|
228
|
+
defaultRooms: [ // Joined automatically after auth
|
|
229
|
+
'ws-default', // WebSocketDefaults.ROOM
|
|
230
|
+
'ws-notification', // WebSocketDefaults.NOTIFICATION_ROOM
|
|
231
|
+
],
|
|
232
|
+
heartbeatInterval: 30000, // 30 seconds (WebSocketDefaults.HEARTBEAT_INTERVAL)
|
|
233
|
+
heartbeatTimeout: 90000, // 90 seconds (WebSocketDefaults.HEARTBEAT_TIMEOUT)
|
|
234
|
+
requireEncryption: false,
|
|
235
|
+
serverOptions: { // Bun native WebSocket config (IBunWebSocketConfig)
|
|
236
|
+
sendPings: true, // WebSocketDefaults.SEND_PINGS
|
|
237
|
+
idleTimeout: 60, // WebSocketDefaults.IDLE_TIMEOUT (seconds)
|
|
238
|
+
maxPayloadLength: 131072, // WebSocketDefaults.MAX_PAYLOAD_LENGTH (128 KB)
|
|
239
|
+
},
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
To customize options, bind a partial options object before registering the component:
|
|
244
|
+
|
|
245
|
+
#### Custom Server Options Example
|
|
246
|
+
```typescript
|
|
247
|
+
import { WebSocketBindingKeys } from '@venizia/ignis';
|
|
248
|
+
|
|
249
|
+
this.bind({
|
|
250
|
+
key: WebSocketBindingKeys.SERVER_OPTIONS,
|
|
251
|
+
}).toValue({
|
|
252
|
+
identifier: 'my-app-websocket',
|
|
253
|
+
path: '/realtime',
|
|
254
|
+
defaultRooms: ['general', 'announcements'], // Override default rooms
|
|
255
|
+
heartbeatInterval: 20000, // More frequent heartbeats
|
|
256
|
+
heartbeatTimeout: 60000, // Shorter timeout
|
|
257
|
+
requireEncryption: true, // Require ECDH handshake
|
|
258
|
+
serverOptions: {
|
|
259
|
+
maxPayloadLength: 2097152, // 2 MB max payload
|
|
260
|
+
backpressureLimit: 2097152, // 2 MB backpressure limit
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
> [!NOTE]
|
|
266
|
+
> `authTimeout` and `encryptedBatchLimit` are properties of the helper's `IWebSocketServerOptions`, not the core component's `IServerOptions`. The component uses the helper defaults for those (`5000` ms and `10` respectively). If you need to customize them, you must set them on the helper directly (not via binding keys).
|
|
267
|
+
|
|
268
|
+
### `WebSocketDefaults` Constants
|
|
269
|
+
|
|
270
|
+
All tunable defaults are defined in the `WebSocketDefaults` class. The helper falls back to these when no explicit value is provided.
|
|
271
|
+
|
|
272
|
+
| Constant | Value | Description |
|
|
273
|
+
|----------|-------|-------------|
|
|
274
|
+
| `PATH` | `'/ws'` | Default WebSocket endpoint path |
|
|
275
|
+
| `ROOM` | `'ws-default'` | Default room name |
|
|
276
|
+
| `NOTIFICATION_ROOM` | `'ws-notification'` | Default notification room name |
|
|
277
|
+
| `BROADCAST_TOPIC` | `'ws:internal:broadcast'` | Internal Bun pub/sub broadcast topic |
|
|
278
|
+
| `MAX_PAYLOAD_LENGTH` | `131072` (128 KB) | Maximum message payload size |
|
|
279
|
+
| `IDLE_TIMEOUT` | `60` | Bun idle timeout in seconds |
|
|
280
|
+
| `BACKPRESSURE_LIMIT` | `1048576` (1 MB) | Bun backpressure limit |
|
|
281
|
+
| `SEND_PINGS` | `true` | Enable WebSocket pings |
|
|
282
|
+
| `PUBLISH_TO_SELF` | `false` | Whether server receives its own publishes |
|
|
283
|
+
| `AUTH_TIMEOUT` | `5000` (5 s) | Time to authenticate before disconnect |
|
|
284
|
+
| `HEARTBEAT_INTERVAL` | `30000` (30 s) | Interval between heartbeat sweeps |
|
|
285
|
+
| `HEARTBEAT_TIMEOUT` | `90000` (90 s) | Disconnect after 3 missed heartbeats |
|
|
286
|
+
| `ENCRYPTED_BATCH_LIMIT` | `10` | Max concurrent encryption operations |
|
|
287
|
+
|
|
288
|
+
> [!TIP]
|
|
289
|
+
> `MAX_PAYLOAD_LENGTH`, `IDLE_TIMEOUT`, `BACKPRESSURE_LIMIT`, `SEND_PINGS`, and `PUBLISH_TO_SELF` are Bun-native WebSocket settings passed via `serverOptions` inside `IServerOptions`. The rest are application-level settings on `IWebSocketServerOptions` (the helper constructor options).
|
|
290
|
+
|
|
291
|
+
#### Full `IBunWebSocketConfig` Interface
|
|
292
|
+
```typescript
|
|
293
|
+
/** Bun WebSocket native configuration options */
|
|
294
|
+
interface IBunWebSocketConfig {
|
|
295
|
+
perMessageDeflate?: boolean;
|
|
296
|
+
maxPayloadLength?: number; // Default: 128 KB (131072)
|
|
297
|
+
idleTimeout?: number; // Default: 60 s
|
|
298
|
+
backpressureLimit?: number; // Default: 1 MB (1048576)
|
|
299
|
+
closeOnBackpressureLimit?: boolean;
|
|
300
|
+
sendPings?: boolean; // Default: true
|
|
301
|
+
publishToSelf?: boolean; // Default: false
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
These options are passed directly to Bun's native WebSocket handler. Set them via `serverOptions` inside the options bound to `WebSocketBindingKeys.SERVER_OPTIONS`.
|
|
306
|
+
|
|
307
|
+
#### Full `IServerOptions` Interface (Core Component)
|
|
308
|
+
```typescript
|
|
309
|
+
interface IServerOptions {
|
|
310
|
+
identifier: string; // Default: 'WEBSOCKET_SERVER'
|
|
311
|
+
path?: string; // Default: '/ws' (from WebSocketDefaults.PATH)
|
|
312
|
+
defaultRooms?: string[]; // Default: ['ws-default', 'ws-notification']
|
|
313
|
+
serverOptions?: IBunWebSocketConfig; // Bun native WebSocket config
|
|
314
|
+
heartbeatInterval?: number; // Default: 30000 (30 s)
|
|
315
|
+
heartbeatTimeout?: number; // Default: 90000 (90 s)
|
|
316
|
+
requireEncryption?: boolean; // Default: false
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
> [!NOTE]
|
|
321
|
+
> `IServerOptions` is the **core component's** options type. It is a subset of the helper's `IWebSocketServerOptions`, which additionally includes `server`, `redisConnection`, callback functions, `authTimeout`, and `encryptedBatchLimit`. The component fills in those extra fields from the DI container before constructing the helper.
|
|
322
|
+
|
|
323
|
+
## Binding Keys
|
|
324
|
+
|
|
325
|
+
| Binding Key | Constant | Type | Required | Default |
|
|
326
|
+
|------------|----------|------|----------|---------|
|
|
327
|
+
| `@app/websocket/server-options` | `WebSocketBindingKeys.SERVER_OPTIONS` | `Partial<IServerOptions>` | No | See [Configuration](#configuration) |
|
|
328
|
+
| `@app/websocket/redis-connection` | `WebSocketBindingKeys.REDIS_CONNECTION` | `DefaultRedisHelper` | **Yes** | `null` |
|
|
329
|
+
| `@app/websocket/authenticate-handler` | `WebSocketBindingKeys.AUTHENTICATE_HANDLER` | `TWebSocketAuthenticateFn` | **Yes** | `null` |
|
|
330
|
+
| `@app/websocket/validate-room-handler` | `WebSocketBindingKeys.VALIDATE_ROOM_HANDLER` | `TWebSocketValidateRoomFn` | No | `null` |
|
|
331
|
+
| `@app/websocket/client-connected-handler` | `WebSocketBindingKeys.CLIENT_CONNECTED_HANDLER` | `TWebSocketClientConnectedFn` | No | `null` |
|
|
332
|
+
| `@app/websocket/client-disconnected-handler` | `WebSocketBindingKeys.CLIENT_DISCONNECTED_HANDLER` | `TWebSocketClientDisconnectedFn` | No | `null` |
|
|
333
|
+
| `@app/websocket/message-handler` | `WebSocketBindingKeys.MESSAGE_HANDLER` | `TWebSocketMessageHandler` | No | `null` |
|
|
334
|
+
| `@app/websocket/outbound-transformer` | `WebSocketBindingKeys.OUTBOUND_TRANSFORMER` | `TWebSocketOutboundTransformer` | No | `null` |
|
|
335
|
+
| `@app/websocket/handshake-handler` | `WebSocketBindingKeys.HANDSHAKE_HANDLER` | `TWebSocketHandshakeFn` | No* | `null` |
|
|
336
|
+
| `@app/websocket/instance` | `WebSocketBindingKeys.WEBSOCKET_INSTANCE` | `WebSocketServerHelper` | -- | *Set by component* |
|
|
337
|
+
|
|
338
|
+
> [!NOTE]
|
|
339
|
+
> `HANDSHAKE_HANDLER` is required when `IServerOptions.requireEncryption` is `true`. It performs ECDH key exchange during authentication.
|
|
340
|
+
|
|
341
|
+
> [!NOTE]
|
|
342
|
+
> `WEBSOCKET_INSTANCE` is **not** set by you -- the component creates and binds it automatically after the server starts. Inject it in services/controllers to interact with WebSocket.
|
|
343
|
+
|
|
344
|
+
### Callback Type Signatures
|
|
345
|
+
|
|
346
|
+
| Binding Key | Callback Type | Required | Description |
|
|
347
|
+
|-------------|--------------|----------|-------------|
|
|
348
|
+
| `AUTHENTICATE_HANDLER` | `TWebSocketAuthenticateFn` | **Yes** | Returns <code v-pre>{ userId, metadata }</code> or `null`/`false` to reject |
|
|
349
|
+
| `VALIDATE_ROOM_HANDLER` | `TWebSocketValidateRoomFn` | No | Filters requested rooms, returns allowed rooms |
|
|
350
|
+
| `CLIENT_CONNECTED_HANDLER` | `TWebSocketClientConnectedFn` | No | Called after successful authentication |
|
|
351
|
+
| `CLIENT_DISCONNECTED_HANDLER` | `TWebSocketClientDisconnectedFn` | No | Called on disconnect (after cleanup) |
|
|
352
|
+
| `MESSAGE_HANDLER` | `TWebSocketMessageHandler` | No | Handles non-system messages from authenticated clients |
|
|
353
|
+
| `OUTBOUND_TRANSFORMER` | `TWebSocketOutboundTransformer` | No | Transforms outbound messages (e.g., per-client encryption) |
|
|
354
|
+
| `HANDSHAKE_HANDLER` | `TWebSocketHandshakeFn` | When `requireEncryption: true` | Returns <code v-pre>{ serverPublicKey, salt }</code> or `null`/`false` to reject |
|
|
355
|
+
|
|
356
|
+
#### `TWebSocketAuthenticateFn`
|
|
357
|
+
```typescript
|
|
358
|
+
type TWebSocketAuthenticateFn<
|
|
359
|
+
AuthDataType extends Record<string, unknown> = Record<string, unknown>,
|
|
360
|
+
MetadataType extends Record<string, unknown> = Record<string, unknown>,
|
|
361
|
+
> = (
|
|
362
|
+
opts: AuthDataType,
|
|
363
|
+
) => ValueOrPromise<{ userId?: string; metadata?: MetadataType } | null | false>;
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Receives the `data` field from the client's `authenticate` event. Return <code v-pre>{ userId, metadata }</code> on success, or `null`/`false` to reject (closes with code `4003`).
|
|
367
|
+
|
|
368
|
+
#### `TWebSocketValidateRoomFn`
|
|
369
|
+
```typescript
|
|
370
|
+
type TWebSocketValidateRoomFn = (opts: {
|
|
371
|
+
clientId: string;
|
|
372
|
+
userId?: string;
|
|
373
|
+
rooms: string[];
|
|
374
|
+
}) => ValueOrPromise<string[]>;
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Called when a client sends a `join` event. Receives the sanitized room list (internal `ws:` prefix rooms are already filtered out). Return the subset of rooms the client is allowed to join.
|
|
378
|
+
|
|
379
|
+
> [!WARNING]
|
|
380
|
+
> If no `validateRoomFn` is bound, **all join requests are rejected**. You must bind this handler if you want clients to join custom rooms.
|
|
381
|
+
|
|
382
|
+
#### `TWebSocketClientConnectedFn`
|
|
383
|
+
```typescript
|
|
384
|
+
type TWebSocketClientConnectedFn<
|
|
385
|
+
MetadataType extends Record<string, unknown> = Record<string, unknown>,
|
|
386
|
+
> = (opts: {
|
|
387
|
+
clientId: string;
|
|
388
|
+
userId?: string;
|
|
389
|
+
metadata?: MetadataType;
|
|
390
|
+
}) => ValueOrPromise<void>;
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Called after a client has been fully authenticated, joined default rooms, and received the `connected` event. Errors thrown here are caught and logged -- they do not disconnect the client.
|
|
394
|
+
|
|
395
|
+
#### `TWebSocketClientDisconnectedFn`
|
|
396
|
+
```typescript
|
|
397
|
+
type TWebSocketClientDisconnectedFn = (opts: {
|
|
398
|
+
clientId: string;
|
|
399
|
+
userId?: string;
|
|
400
|
+
}) => ValueOrPromise<void>;
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Called after internal cleanup (auth timer cleared, removed from user/room indexes, removed from clients map). Errors thrown here are caught and logged.
|
|
404
|
+
|
|
405
|
+
#### `TWebSocketMessageHandler`
|
|
406
|
+
```typescript
|
|
407
|
+
type TWebSocketMessageHandler = (opts: {
|
|
408
|
+
clientId: string;
|
|
409
|
+
userId?: string;
|
|
410
|
+
message: IWebSocketMessage;
|
|
411
|
+
}) => ValueOrPromise<void>;
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Called for any message from an authenticated client whose `event` is not a system event (`authenticate`, `connected`, `disconnect`, `join`, `leave`, `error`, `heartbeat`, `encrypted`). If no handler is bound, non-system messages are silently dropped.
|
|
415
|
+
|
|
416
|
+
#### `TWebSocketOutboundTransformer`
|
|
417
|
+
```typescript
|
|
418
|
+
type TWebSocketOutboundTransformer<
|
|
419
|
+
DataType = unknown,
|
|
420
|
+
MetadataType extends Record<string, unknown> = Record<string, unknown>,
|
|
421
|
+
> = (opts: {
|
|
422
|
+
client: IWebSocketClient<MetadataType>;
|
|
423
|
+
event: string;
|
|
424
|
+
data: DataType;
|
|
425
|
+
}) => ValueOrPromise<TNullable<{ event: string; data: DataType }>>;
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Intercepts every outbound message **to encrypted clients only** before `socket.send()`. Return `null` to send the original <code v-pre>{ event, data }</code> unchanged, or return a transformed <code v-pre>{ event, data }</code> (e.g., <code v-pre>{ event: 'encrypted', data: ciphertext }</code>).
|
|
429
|
+
|
|
430
|
+
> [!NOTE]
|
|
431
|
+
> The transformer is only called for clients where `client.encrypted === true`. Non-encrypted clients bypass this entirely (zero overhead).
|
|
432
|
+
|
|
433
|
+
#### `TWebSocketHandshakeFn`
|
|
434
|
+
```typescript
|
|
435
|
+
type TWebSocketHandshakeFn<
|
|
436
|
+
AuthDataType extends Record<string, unknown> = Record<string, unknown>,
|
|
437
|
+
> = (opts: {
|
|
438
|
+
clientId: string;
|
|
439
|
+
userId?: string;
|
|
440
|
+
data: AuthDataType;
|
|
441
|
+
}) => ValueOrPromise<{ serverPublicKey: string; salt: string } | null | false>;
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Called during authentication when `requireEncryption` is `true`. Receives the same `data` payload as `authenticateFn`. Return <code v-pre>{ serverPublicKey, salt }</code> on success -- these are included in the `connected` event sent to the client. Return `null`/`false` to reject (closes with code `4004`).
|
|
445
|
+
|
|
446
|
+
## See Also
|
|
447
|
+
|
|
448
|
+
- [Usage & Examples](./usage) - Server-side usage, emitter, wire protocol, client tracking, and delivery strategy
|
|
449
|
+
- [API Reference](./api) - Architecture, WebSocketEmitter API, and internals
|
|
450
|
+
- [Error Reference](./errors) - Error conditions table and troubleshooting
|
|
451
|
+
- [WebSocketServerHelper](/references/helpers/websocket/) - Helper API documentation
|
|
452
|
+
- [Socket.IO Component](../socket-io/) - Node.js-compatible alternative with Socket.IO
|
|
453
|
+
- [Bun WebSocket Documentation](https://bun.sh/docs/api/websockets) - Official Bun WebSocket API reference
|