@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,513 @@
|
|
|
1
|
+
# Socket.IO
|
|
2
|
+
|
|
3
|
+
Runtime-agnostic Socket.IO server and client helpers with built-in authentication flow, room management, Redis adapter for horizontal scaling, and automatic heartbeat keep-alive.
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
| Class | Extends | Role |
|
|
8
|
+
|-------|---------|------|
|
|
9
|
+
| `SocketIOServerHelper` | `BaseHelper` | Manages a Socket.IO server with authentication, rooms, ping intervals, and Redis-backed messaging |
|
|
10
|
+
| `SocketIOClientHelper` | `BaseHelper` | Manages a Socket.IO client connection with authentication, event subscriptions, and room operations |
|
|
11
|
+
|
|
12
|
+
#### Import Paths
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import {
|
|
16
|
+
SocketIOServerHelper,
|
|
17
|
+
SocketIOClientHelper,
|
|
18
|
+
SocketIOConstants,
|
|
19
|
+
SocketIOClientStates,
|
|
20
|
+
} from '@venizia/ignis-helpers';
|
|
21
|
+
|
|
22
|
+
import type {
|
|
23
|
+
TSocketIOServerOptions,
|
|
24
|
+
ISocketIOServerBaseOptions,
|
|
25
|
+
ISocketIOServerNodeOptions,
|
|
26
|
+
ISocketIOServerBunOptions,
|
|
27
|
+
ISocketIOClientOptions,
|
|
28
|
+
IOptions,
|
|
29
|
+
ISocketIOClient,
|
|
30
|
+
IHandshake,
|
|
31
|
+
TSocketIOAuthenticateFn,
|
|
32
|
+
TSocketIOValidateRoomFn,
|
|
33
|
+
TSocketIOClientConnectedFn,
|
|
34
|
+
TSocketIOEventHandler,
|
|
35
|
+
TSocketIOClientState,
|
|
36
|
+
} from '@venizia/ignis-helpers';
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Creating an Instance
|
|
40
|
+
|
|
41
|
+
### Server
|
|
42
|
+
|
|
43
|
+
`SocketIOServerHelper` requires a Redis connection for the pub/sub adapter, an HTTP server (Node.js) or Bun engine instance, and an authentication function.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { SocketIOServerHelper } from '@venizia/ignis-helpers';
|
|
47
|
+
import { DefaultRedisHelper } from '@venizia/ignis-helpers';
|
|
48
|
+
import { createServer } from 'node:http';
|
|
49
|
+
|
|
50
|
+
const httpServer = createServer();
|
|
51
|
+
|
|
52
|
+
const redisHelper = new DefaultRedisHelper({
|
|
53
|
+
name: 'socket-redis',
|
|
54
|
+
host: 'localhost',
|
|
55
|
+
port: 6379,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const socketServer = new SocketIOServerHelper({
|
|
59
|
+
identifier: 'my-socket-server',
|
|
60
|
+
runtime: 'node',
|
|
61
|
+
server: httpServer,
|
|
62
|
+
redisConnection: redisHelper,
|
|
63
|
+
serverOptions: {
|
|
64
|
+
cors: { origin: '*' },
|
|
65
|
+
path: '/socket.io',
|
|
66
|
+
},
|
|
67
|
+
authenticateFn: async (handshake) => {
|
|
68
|
+
const token = handshake.auth?.token;
|
|
69
|
+
return !!token; // Return true to accept, false to reject
|
|
70
|
+
},
|
|
71
|
+
validateRoomFn: async ({ socket, rooms }) => {
|
|
72
|
+
// Return only the rooms the client is allowed to join
|
|
73
|
+
return rooms.filter(r => r.startsWith('public-'));
|
|
74
|
+
},
|
|
75
|
+
clientConnectedFn: async ({ socket }) => {
|
|
76
|
+
console.log('Client authenticated:', socket.id);
|
|
77
|
+
},
|
|
78
|
+
defaultRooms: ['io-default', 'io-notification'],
|
|
79
|
+
authenticateTimeout: 10000,
|
|
80
|
+
pingInterval: 30000,
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### `TSocketIOServerOptions`
|
|
85
|
+
|
|
86
|
+
A discriminated union based on the `runtime` field:
|
|
87
|
+
|
|
88
|
+
| Option | Type | Default | Description |
|
|
89
|
+
|--------|------|---------|-------------|
|
|
90
|
+
| `identifier` | `string` | -- | Unique identifier for this server instance (used as logger scope) |
|
|
91
|
+
| `runtime` | `'node' \| 'bun'` | -- | Runtime environment. Determines which server field is required |
|
|
92
|
+
| `server` | `HTTPServer` | -- | Node.js HTTP server instance. **Required when `runtime` is `'node'`** |
|
|
93
|
+
| `engine` | `any` | -- | `@socket.io/bun-engine` Server instance. **Required when `runtime` is `'bun'`** |
|
|
94
|
+
| `serverOptions` | `Partial<ServerOptions>` | `{}` | Socket.IO `ServerOptions` (cors, path, transports, etc.) |
|
|
95
|
+
| `redisConnection` | `DefaultRedisHelper` | -- | **Required.** Redis helper used to create pub, sub, and emitter clients |
|
|
96
|
+
| `authenticateFn` | `TSocketIOAuthenticateFn` | -- | **Required.** Called with the client's handshake data. Return `true` to accept, `false` to reject |
|
|
97
|
+
| `validateRoomFn` | `TSocketIOValidateRoomFn` | `undefined` | Called when a client requests to join rooms. Return the allowed subset |
|
|
98
|
+
| `clientConnectedFn` | `TSocketIOClientConnectedFn` | `undefined` | Called after a client is fully authenticated and has joined default rooms |
|
|
99
|
+
| `defaultRooms` | `string[]` | `['io-default', 'io-notification']` | Rooms that every authenticated client joins automatically |
|
|
100
|
+
| `authenticateTimeout` | `number` | `10000` (10 s) | Milliseconds before an unauthenticated client is disconnected |
|
|
101
|
+
| `pingInterval` | `number` | `30000` (30 s) | Interval in milliseconds between heartbeat pings to authenticated clients |
|
|
102
|
+
|
|
103
|
+
> [!WARNING]
|
|
104
|
+
> If no `validateRoomFn` is provided, **all room join requests are rejected** with a warning log. You must provide this callback if you want clients to join custom rooms beyond the `defaultRooms`.
|
|
105
|
+
|
|
106
|
+
### Client
|
|
107
|
+
|
|
108
|
+
`SocketIOClientHelper` connects to a Socket.IO server. Configuration is done entirely via the constructor -- `configure()` is called automatically.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { SocketIOClientHelper } from '@venizia/ignis-helpers';
|
|
112
|
+
|
|
113
|
+
const socketClient = new SocketIOClientHelper({
|
|
114
|
+
identifier: 'my-client',
|
|
115
|
+
host: 'http://localhost:3000',
|
|
116
|
+
options: {
|
|
117
|
+
path: '/socket.io',
|
|
118
|
+
extraHeaders: {
|
|
119
|
+
Authorization: 'Bearer my-jwt-token',
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
onConnected: () => {
|
|
123
|
+
console.log('Connected to server');
|
|
124
|
+
socketClient.authenticate();
|
|
125
|
+
},
|
|
126
|
+
onDisconnected: (reason) => {
|
|
127
|
+
console.log('Disconnected:', reason);
|
|
128
|
+
},
|
|
129
|
+
onError: (error) => {
|
|
130
|
+
console.error('Connection error:', error);
|
|
131
|
+
},
|
|
132
|
+
onAuthenticated: () => {
|
|
133
|
+
console.log('Successfully authenticated');
|
|
134
|
+
},
|
|
135
|
+
onUnauthenticated: (message) => {
|
|
136
|
+
console.warn('Authentication failed:', message);
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### `ISocketIOClientOptions`
|
|
142
|
+
|
|
143
|
+
| Option | Type | Default | Description |
|
|
144
|
+
|--------|------|---------|-------------|
|
|
145
|
+
| `identifier` | `string` | -- | Unique identifier for this client (used as logger scope) |
|
|
146
|
+
| `host` | `string` | -- | Server URL to connect to (e.g., `'http://localhost:3000'`) |
|
|
147
|
+
| `options` | `IOptions` | -- | Socket.IO client options (extends `SocketOptions` with `path` and `extraHeaders`) |
|
|
148
|
+
| `onConnected` | `() => ValueOrPromise<void>` | `undefined` | Called when the transport connection is established |
|
|
149
|
+
| `onDisconnected` | `(reason: string) => ValueOrPromise<void>` | `undefined` | Called when disconnected. The client state resets to `'unauthorized'` |
|
|
150
|
+
| `onError` | `(error: Error) => ValueOrPromise<void>` | `undefined` | Called on connection errors |
|
|
151
|
+
| `onAuthenticated` | `() => ValueOrPromise<void>` | `undefined` | Called when the server sends an `authenticated` event |
|
|
152
|
+
| `onUnauthenticated` | `(message: string) => ValueOrPromise<void>` | `undefined` | Called when the server rejects authentication |
|
|
153
|
+
|
|
154
|
+
#### `IOptions`
|
|
155
|
+
|
|
156
|
+
Extends `SocketOptions` from `socket.io-client`:
|
|
157
|
+
|
|
158
|
+
| Option | Type | Description |
|
|
159
|
+
|--------|------|-------------|
|
|
160
|
+
| `path` | `string` | Socket.IO server path (e.g., `'/socket.io'`) |
|
|
161
|
+
| `extraHeaders` | `Record<string \| symbol \| number, any>` | Additional headers sent with the connection request |
|
|
162
|
+
|
|
163
|
+
## Usage
|
|
164
|
+
|
|
165
|
+
### Server Setup
|
|
166
|
+
|
|
167
|
+
After constructing the server helper, call `configure()` to initialize the Socket.IO server, set up the Redis adapter, and start listening for connections.
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
const socketServer = new SocketIOServerHelper({
|
|
171
|
+
identifier: 'my-server',
|
|
172
|
+
runtime: 'node',
|
|
173
|
+
server: httpServer,
|
|
174
|
+
redisConnection: redisHelper,
|
|
175
|
+
serverOptions: { cors: { origin: '*' } },
|
|
176
|
+
authenticateFn: async (handshake) => {
|
|
177
|
+
return verifyToken(handshake.auth?.token);
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
await socketServer.configure();
|
|
182
|
+
// Server is now ready and listening for connections
|
|
183
|
+
|
|
184
|
+
httpServer.listen(3000, () => {
|
|
185
|
+
console.log('HTTP + Socket.IO server running on port 3000');
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### Bun Runtime
|
|
190
|
+
|
|
191
|
+
For Bun, pass the `@socket.io/bun-engine` instance instead of an HTTP server:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { SocketIOServerHelper } from '@venizia/ignis-helpers';
|
|
195
|
+
|
|
196
|
+
const socketServer = new SocketIOServerHelper({
|
|
197
|
+
identifier: 'my-bun-server',
|
|
198
|
+
runtime: 'bun',
|
|
199
|
+
engine: bunEngineInstance,
|
|
200
|
+
redisConnection: redisHelper,
|
|
201
|
+
serverOptions: {},
|
|
202
|
+
authenticateFn: async (handshake) => {
|
|
203
|
+
return verifyToken(handshake.auth?.token);
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
await socketServer.configure();
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Client Connection
|
|
211
|
+
|
|
212
|
+
The client connects automatically on construction. Call `authenticate()` after the connection is established to trigger the server-side authentication flow.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
const client = new SocketIOClientHelper({
|
|
216
|
+
identifier: 'app-client',
|
|
217
|
+
host: 'http://localhost:3000',
|
|
218
|
+
options: {
|
|
219
|
+
path: '/socket.io',
|
|
220
|
+
extraHeaders: { Authorization: 'Bearer my-token' },
|
|
221
|
+
},
|
|
222
|
+
onConnected: () => {
|
|
223
|
+
// Connection established -- initiate authentication
|
|
224
|
+
client.authenticate();
|
|
225
|
+
},
|
|
226
|
+
onAuthenticated: () => {
|
|
227
|
+
// Now safe to subscribe and emit
|
|
228
|
+
client.joinRooms({ rooms: ['chat-room-1'] });
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Emitting Events
|
|
234
|
+
|
|
235
|
+
#### From the Server
|
|
236
|
+
|
|
237
|
+
Use `send()` to emit events through the Redis emitter. Messages can target a specific socket ID, a room, or broadcast to all clients.
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// Send to a specific client
|
|
241
|
+
socketServer.send({
|
|
242
|
+
destination: 'client-socket-id',
|
|
243
|
+
payload: {
|
|
244
|
+
topic: 'notification',
|
|
245
|
+
data: { message: 'Hello!' },
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Broadcast to all connected clients (no destination)
|
|
250
|
+
socketServer.send({
|
|
251
|
+
payload: {
|
|
252
|
+
topic: 'announcement',
|
|
253
|
+
data: { message: 'Server update in 5 minutes' },
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Send with logging and callback
|
|
258
|
+
socketServer.send({
|
|
259
|
+
destination: 'some-room',
|
|
260
|
+
payload: {
|
|
261
|
+
topic: 'room-event',
|
|
262
|
+
data: { action: 'update' },
|
|
263
|
+
},
|
|
264
|
+
doLog: true,
|
|
265
|
+
cb: () => {
|
|
266
|
+
console.log('Message queued');
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
#### From the Client
|
|
272
|
+
|
|
273
|
+
Use `emit()` to send events to the server.
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
client.emit({
|
|
277
|
+
topic: 'chat-message',
|
|
278
|
+
data: { text: 'Hello, world!' },
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// With logging enabled
|
|
282
|
+
client.emit({
|
|
283
|
+
topic: 'user-action',
|
|
284
|
+
data: { action: 'click', target: 'button-1' },
|
|
285
|
+
doLog: true,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// With callback
|
|
289
|
+
client.emit({
|
|
290
|
+
topic: 'upload-complete',
|
|
291
|
+
data: { fileId: '123' },
|
|
292
|
+
cb: () => {
|
|
293
|
+
console.log('Emit completed');
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Listening for Events
|
|
299
|
+
|
|
300
|
+
#### Server-Side Event Binding
|
|
301
|
+
|
|
302
|
+
Use `on()` to register event handlers on the IO server instance.
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
socketServer.on({
|
|
306
|
+
topic: 'custom-event',
|
|
307
|
+
handler: (data) => {
|
|
308
|
+
console.log('Received:', data);
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### Client-Side Event Subscription
|
|
314
|
+
|
|
315
|
+
Use `subscribe()` for single events and `subscribeMany()` for batch registration.
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
// Single event
|
|
319
|
+
client.subscribe({
|
|
320
|
+
event: 'notification',
|
|
321
|
+
handler: (data) => {
|
|
322
|
+
console.log('Notification:', data);
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Prevent duplicate handlers (default behavior)
|
|
327
|
+
client.subscribe({
|
|
328
|
+
event: 'notification',
|
|
329
|
+
handler: (data) => { /* ... */ },
|
|
330
|
+
ignoreDuplicate: true, // Default: true -- skips if handler already exists
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Allow multiple handlers for same event
|
|
334
|
+
client.subscribe({
|
|
335
|
+
event: 'chat-message',
|
|
336
|
+
handler: handler1,
|
|
337
|
+
ignoreDuplicate: false,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Batch subscribe
|
|
341
|
+
client.subscribeMany({
|
|
342
|
+
events: {
|
|
343
|
+
'user-joined': (data) => console.log('Joined:', data),
|
|
344
|
+
'user-left': (data) => console.log('Left:', data),
|
|
345
|
+
'typing': (data) => console.log('Typing:', data),
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Unsubscribe from a specific event (removes all handlers)
|
|
350
|
+
client.unsubscribe({ event: 'notification' });
|
|
351
|
+
|
|
352
|
+
// Unsubscribe from multiple events
|
|
353
|
+
client.unsubscribeMany({ events: ['user-joined', 'user-left'] });
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Rooms
|
|
357
|
+
|
|
358
|
+
#### Client-Side Room Operations
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
// Join rooms (validated by server's validateRoomFn)
|
|
362
|
+
client.joinRooms({ rooms: ['chat-room-1', 'notifications'] });
|
|
363
|
+
|
|
364
|
+
// Leave rooms
|
|
365
|
+
client.leaveRooms({ rooms: ['chat-room-1'] });
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
#### Server-Side Room Behavior
|
|
369
|
+
|
|
370
|
+
Authenticated clients automatically join the `defaultRooms` (by default, `'io-default'` and `'io-notification'`). Custom room join requests are validated through `validateRoomFn` before the client is allowed to join.
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
const socketServer = new SocketIOServerHelper({
|
|
374
|
+
// ...
|
|
375
|
+
defaultRooms: ['general', 'announcements'],
|
|
376
|
+
validateRoomFn: async ({ socket, rooms }) => {
|
|
377
|
+
// Only allow rooms the user has permission for
|
|
378
|
+
const userPermissions = await getUserPermissions(socket.id);
|
|
379
|
+
return rooms.filter(room => userPermissions.includes(room));
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Authentication Flow
|
|
385
|
+
|
|
386
|
+
The server enforces a post-connection authentication protocol:
|
|
387
|
+
|
|
388
|
+
```
|
|
389
|
+
Client connects
|
|
390
|
+
|
|
|
391
|
+
v
|
|
392
|
+
Server creates client entry (state: UNAUTHORIZED)
|
|
393
|
+
|-- Starts authenticateTimeout timer (default: 10s)
|
|
394
|
+
|-- Registers disconnect handler
|
|
395
|
+
|
|
|
396
|
+
Client emits 'authenticate' event
|
|
397
|
+
|
|
|
398
|
+
v
|
|
399
|
+
Server calls authenticateFn(handshake)
|
|
400
|
+
|
|
|
401
|
+
+-- Returns true:
|
|
402
|
+
| |-- State -> AUTHENTICATED
|
|
403
|
+
| |-- Clear auth timeout
|
|
404
|
+
| |-- Join default rooms
|
|
405
|
+
| |-- Start ping interval
|
|
406
|
+
| |-- Emit 'authenticated' to client
|
|
407
|
+
| +-- Invoke clientConnectedFn
|
|
408
|
+
|
|
|
409
|
+
+-- Returns false:
|
|
410
|
+
| |-- State -> UNAUTHORIZED
|
|
411
|
+
| |-- Emit 'unauthenticated' to client
|
|
412
|
+
| +-- Disconnect
|
|
413
|
+
|
|
|
414
|
+
+-- Timeout (no auth within authenticateTimeout):
|
|
415
|
+
+-- Disconnect
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Redis Adapter
|
|
419
|
+
|
|
420
|
+
The server uses `@socket.io/redis-adapter` and `@socket.io/redis-emitter` for horizontal scaling. Three Redis connections are created by duplicating the provided `redisConnection` client:
|
|
421
|
+
|
|
422
|
+
- **redisPub** -- Publishes adapter messages
|
|
423
|
+
- **redisSub** -- Subscribes to adapter messages
|
|
424
|
+
- **redisEmitter** -- Powers `send()` for cross-instance message delivery
|
|
425
|
+
|
|
426
|
+
All three connections are initialized and awaited during `configure()`. If the parent client uses `lazyConnect`, the duplicated clients will connect automatically.
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
// Messages sent via send() use the Redis emitter,
|
|
430
|
+
// so they reach clients on ANY server instance
|
|
431
|
+
socketServer.send({
|
|
432
|
+
destination: 'some-room',
|
|
433
|
+
payload: {
|
|
434
|
+
topic: 'update',
|
|
435
|
+
data: { value: 42 },
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Graceful Shutdown
|
|
441
|
+
|
|
442
|
+
#### Server
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
await socketServer.shutdown();
|
|
446
|
+
// 1. Disconnects all clients (clears intervals and timeouts)
|
|
447
|
+
// 2. Closes the IO server
|
|
448
|
+
// 3. Quits all three Redis connections (pub, sub, emitter)
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
#### Client
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
client.shutdown();
|
|
455
|
+
// 1. Removes all event listeners
|
|
456
|
+
// 2. Disconnects if connected
|
|
457
|
+
// 3. Resets state to 'unauthorized'
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
## Troubleshooting
|
|
461
|
+
|
|
462
|
+
### `[SocketIOServerHelper] Invalid HTTP server for Node.js runtime!`
|
|
463
|
+
|
|
464
|
+
**Cause:** The `server` option is missing or falsy when `runtime` is `'node'`.
|
|
465
|
+
|
|
466
|
+
**Fix:** Pass a valid `http.Server` instance.
|
|
467
|
+
|
|
468
|
+
### `[SocketIOServerHelper] Invalid @socket.io/bun-engine instance for Bun runtime!`
|
|
469
|
+
|
|
470
|
+
**Cause:** The `engine` option is missing or falsy when `runtime` is `'bun'`.
|
|
471
|
+
|
|
472
|
+
**Fix:** Pass a valid `@socket.io/bun-engine` Server instance.
|
|
473
|
+
|
|
474
|
+
### `[SocketIOServerHelper] Unsupported runtime!`
|
|
475
|
+
|
|
476
|
+
**Cause:** The `runtime` value is neither `'node'` nor `'bun'`.
|
|
477
|
+
|
|
478
|
+
**Fix:** Use `RuntimeModules.NODE` or `RuntimeModules.BUN`.
|
|
479
|
+
|
|
480
|
+
### `Invalid redis connection to config socket.io adapter!`
|
|
481
|
+
|
|
482
|
+
**Cause:** The `redisConnection` option is missing, `null`, or `undefined`.
|
|
483
|
+
|
|
484
|
+
**Fix:** Pass a valid `DefaultRedisHelper` instance.
|
|
485
|
+
|
|
486
|
+
### `[on] Invalid topic to start binding handler`
|
|
487
|
+
|
|
488
|
+
**Cause:** An empty or falsy `topic` was passed to `on()`.
|
|
489
|
+
|
|
490
|
+
**Fix:** Provide a non-empty string topic.
|
|
491
|
+
|
|
492
|
+
### `[on] IOServer is not initialized yet!`
|
|
493
|
+
|
|
494
|
+
**Cause:** `on()` was called before `configure()` completed.
|
|
495
|
+
|
|
496
|
+
**Fix:** Await `configure()` before registering event handlers.
|
|
497
|
+
|
|
498
|
+
### `Invalid socket client state to emit`
|
|
499
|
+
|
|
500
|
+
**Cause (client):** `emit()` was called when the client is not connected.
|
|
501
|
+
|
|
502
|
+
**Fix:** Check that the client is connected before emitting, or emit inside the `onConnected` callback.
|
|
503
|
+
|
|
504
|
+
### `Topic is required to emit`
|
|
505
|
+
|
|
506
|
+
**Cause (client):** `emit()` was called with an empty or falsy `topic`.
|
|
507
|
+
|
|
508
|
+
**Fix:** Provide a non-empty string topic.
|
|
509
|
+
|
|
510
|
+
## See Also
|
|
511
|
+
|
|
512
|
+
- [API Reference](./api) -- Full method signatures, types, and constants
|
|
513
|
+
- [WebSocket Helper](../websocket/) -- Bun-native WebSocket alternative
|