@venizia/ignis-docs 0.0.4 → 0.0.5
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 +2 -2
- package/wiki/best-practices/code-style-standards/function-patterns.md +31 -5
- package/wiki/best-practices/performance-optimization.md +39 -0
- package/wiki/best-practices/troubleshooting-tips.md +24 -3
- package/wiki/guides/tutorials/realtime-chat.md +724 -460
- package/wiki/references/base/filter-system/json-filtering.md +4 -2
- package/wiki/references/base/middlewares.md +24 -72
- package/wiki/references/base/repositories/advanced.md +83 -0
- package/wiki/references/base/repositories/index.md +1 -1
- package/wiki/references/components/socket-io.md +495 -78
- package/wiki/references/helpers/logger.md +222 -43
- package/wiki/references/helpers/network.md +273 -31
- package/wiki/references/helpers/socket-io.md +881 -71
- package/wiki/references/quick-reference.md +3 -5
- package/wiki/references/src-details/core.md +1 -2
- package/wiki/references/utilities/statuses.md +4 -2
|
@@ -1,122 +1,932 @@
|
|
|
1
|
-
# Socket.IO
|
|
1
|
+
# Socket.IO Helpers
|
|
2
2
|
|
|
3
|
-
Structured Socket.IO
|
|
3
|
+
Structured Socket.IO server and client management for real-time bidirectional communication. Provides authentication, room management, Redis scaling, and lifecycle management out of the box.
|
|
4
4
|
|
|
5
5
|
## Quick Reference
|
|
6
6
|
|
|
7
|
-
| Helper |
|
|
8
|
-
|
|
9
|
-
|
|
|
10
|
-
|
|
|
7
|
+
| Helper | Package | Purpose |
|
|
8
|
+
|--------|---------|---------|
|
|
9
|
+
| [`SocketIOServerHelper`](#socketioserverhelper) | `@venizia/ignis-helpers` | Server-side Socket.IO wrapper with auth, rooms, Redis adapter |
|
|
10
|
+
| [`SocketIOClientHelper`](#socketioclienthelper) | `@venizia/ignis-helpers` | Client-side Socket.IO wrapper with structured event handling |
|
|
11
11
|
|
|
12
|
-
###
|
|
12
|
+
### Import Paths
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
```typescript
|
|
15
|
+
// Server helper
|
|
16
|
+
import { SocketIOServerHelper } from '@venizia/ignis-helpers';
|
|
17
|
+
|
|
18
|
+
// Client helper
|
|
19
|
+
import { SocketIOClientHelper } from '@venizia/ignis-helpers';
|
|
20
|
+
|
|
21
|
+
// Types and constants
|
|
22
|
+
import {
|
|
23
|
+
TSocketIOServerOptions,
|
|
24
|
+
ISocketIOServerBaseOptions,
|
|
25
|
+
ISocketIOServerNodeOptions,
|
|
26
|
+
ISocketIOServerBunOptions,
|
|
27
|
+
ISocketIOClientOptions,
|
|
28
|
+
IHandshake,
|
|
29
|
+
ISocketIOClient,
|
|
30
|
+
SocketIOConstants,
|
|
31
|
+
SocketIOClientStates,
|
|
32
|
+
TSocketIOEventHandler,
|
|
33
|
+
TSocketIOAuthenticateFn,
|
|
34
|
+
TSocketIOValidateRoomFn,
|
|
35
|
+
TSocketIOClientConnectedFn,
|
|
36
|
+
} from '@venizia/ignis-helpers';
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
# SocketIOServerHelper
|
|
42
|
+
|
|
43
|
+
Wraps the Socket.IO `Server` instance with built-in authentication flow, client tracking, room management, Redis adapter/emitter, and dual-runtime support (Node.js + Bun).
|
|
44
|
+
|
|
45
|
+
## Constructor
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
new SocketIOServerHelper(opts: TSocketIOServerOptions)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Options (Discriminated Union)
|
|
52
|
+
|
|
53
|
+
`TSocketIOServerOptions` is a discriminated union on the `runtime` field:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
type TSocketIOServerOptions = ISocketIOServerNodeOptions | ISocketIOServerBunOptions;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Base options** (shared by both runtimes):
|
|
60
|
+
|
|
61
|
+
| Field | Type | Required | Default | Description |
|
|
62
|
+
|-------|------|----------|---------|-------------|
|
|
63
|
+
| `identifier` | `string` | Yes | — | Unique name for this Socket.IO server instance |
|
|
64
|
+
| `serverOptions` | `Partial<ServerOptions>` | Yes | — | Socket.IO server configuration (path, cors, etc.) |
|
|
65
|
+
| `redisConnection` | `DefaultRedisHelper` | Yes | — | Redis helper for adapter + emitter. Creates 3 duplicate connections internally |
|
|
66
|
+
| `authenticateFn` | `TSocketIOAuthenticateFn` | Yes | — | Called when client emits `authenticate`. Return `true` to accept |
|
|
67
|
+
| `clientConnectedFn` | `TSocketIOClientConnectedFn` | No | — | Called after successful authentication |
|
|
68
|
+
| `validateRoomFn` | `TSocketIOValidateRoomFn` | No | — | Called when client requests to join rooms. Return allowed room names. Joins rejected if not provided |
|
|
69
|
+
| `authenticateTimeout` | `number` | No | `10000` (10s) | Milliseconds before unauthenticated clients are disconnected |
|
|
70
|
+
| `pingInterval` | `number` | No | `30000` (30s) | Milliseconds between keep-alive pings to authenticated clients |
|
|
71
|
+
| `defaultRooms` | `string[]` | No | `['io-default', 'io-notification']` | Rooms clients auto-join after authentication |
|
|
72
|
+
|
|
73
|
+
**Node.js-specific options** (`runtime: RuntimeModules.NODE`):
|
|
20
74
|
|
|
21
|
-
|
|
75
|
+
| Field | Type | Required | Description |
|
|
76
|
+
|-------|------|----------|-------------|
|
|
77
|
+
| `runtime` | `typeof RuntimeModules.NODE` | Yes | Must be `RuntimeModules.NODE` (`'node'`) |
|
|
78
|
+
| `server` | `node:http.Server` | Yes | The HTTP server instance to attach Socket.IO to |
|
|
22
79
|
|
|
23
|
-
|
|
24
|
-
|--------|--------|---------|
|
|
25
|
-
| **Server** | `send({ destination, payload })` | Send message to room/socket |
|
|
26
|
-
| **Server** | `broadcast({ payload })` | Broadcast to all clients |
|
|
27
|
-
| **Client** | `connect()` | Connect to server |
|
|
28
|
-
| **Client** | `emit({ topic, ...data })` | Emit event |
|
|
29
|
-
| **Client** | `subscribe({ events })` | Subscribe to events |
|
|
80
|
+
**Bun-specific options** (`runtime: RuntimeModules.BUN`):
|
|
30
81
|
|
|
31
|
-
|
|
82
|
+
| Field | Type | Required | Description |
|
|
83
|
+
|-------|------|----------|-------------|
|
|
84
|
+
| `runtime` | `typeof RuntimeModules.BUN` | Yes | Must be `RuntimeModules.BUN` (`'bun'`) |
|
|
85
|
+
| `engine` | `any` | Yes | `@socket.io/bun-engine` Server instance |
|
|
32
86
|
|
|
33
|
-
|
|
87
|
+
### Constructor Examples
|
|
34
88
|
|
|
35
|
-
|
|
89
|
+
**Node.js:**
|
|
36
90
|
|
|
37
91
|
```typescript
|
|
38
|
-
import {
|
|
92
|
+
import { RuntimeModules, SocketIOServerHelper } from '@venizia/ignis-helpers';
|
|
39
93
|
|
|
40
|
-
|
|
94
|
+
const helper = new SocketIOServerHelper({
|
|
95
|
+
runtime: RuntimeModules.NODE,
|
|
96
|
+
identifier: 'my-socket-server',
|
|
97
|
+
server: httpServer, // node:http.Server
|
|
98
|
+
serverOptions: { path: '/io', cors: { origin: '*' } },
|
|
99
|
+
redisConnection: myRedisHelper,
|
|
100
|
+
authenticateFn: async (handshake) => {
|
|
101
|
+
const token = handshake.headers.authorization;
|
|
102
|
+
return verifyJWT(token);
|
|
103
|
+
},
|
|
104
|
+
clientConnectedFn: ({ socket }) => {
|
|
105
|
+
console.log('Client authenticated:', socket.id);
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Bun:**
|
|
41
111
|
|
|
42
|
-
|
|
43
|
-
|
|
112
|
+
```typescript
|
|
113
|
+
import { RuntimeModules, SocketIOServerHelper } from '@venizia/ignis-helpers';
|
|
114
|
+
|
|
115
|
+
const { Server: BunEngine } = await import('@socket.io/bun-engine');
|
|
116
|
+
const engine = new BunEngine({ path: '/io', cors: { origin: '*' } });
|
|
44
117
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
118
|
+
const helper = new SocketIOServerHelper({
|
|
119
|
+
runtime: RuntimeModules.BUN,
|
|
120
|
+
identifier: 'my-socket-server',
|
|
121
|
+
engine, // @socket.io/bun-engine instance
|
|
122
|
+
serverOptions: { path: '/io', cors: { origin: '*' } },
|
|
123
|
+
redisConnection: myRedisHelper,
|
|
124
|
+
authenticateFn: async (handshake) => {
|
|
125
|
+
const token = handshake.headers.authorization;
|
|
126
|
+
return verifyJWT(token);
|
|
127
|
+
},
|
|
128
|
+
});
|
|
54
129
|
```
|
|
55
130
|
|
|
56
|
-
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Methods
|
|
57
134
|
|
|
58
|
-
|
|
135
|
+
### `getIOServer(): IOServer`
|
|
59
136
|
|
|
60
|
-
|
|
137
|
+
Returns the raw Socket.IO `Server` instance for advanced operations.
|
|
61
138
|
|
|
62
139
|
```typescript
|
|
63
|
-
|
|
140
|
+
const io = helper.getIOServer();
|
|
141
|
+
io.of('/admin').on('connection', socket => { /* ... */ });
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### `getClients(opts?): ISocketIOClient | Map<string, ISocketIOClient> | undefined`
|
|
145
|
+
|
|
146
|
+
Returns tracked clients.
|
|
147
|
+
|
|
148
|
+
| Parameter | Type | Description |
|
|
149
|
+
|-----------|------|-------------|
|
|
150
|
+
| `opts.id` | `string` (optional) | Specific client ID. If omitted, returns all clients |
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// Get all clients
|
|
154
|
+
const allClients = helper.getClients() as Map<string, ISocketIOClient>;
|
|
155
|
+
console.log('Connected:', allClients.size);
|
|
156
|
+
|
|
157
|
+
// Get specific client
|
|
158
|
+
const client = helper.getClients({ id: socketId }) as ISocketIOClient | undefined;
|
|
159
|
+
if (client) {
|
|
160
|
+
console.log('State:', client.state); // 'authenticated' | 'authenticating' | 'unauthorized'
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### `on<HandlerArgsType, HandlerReturnType>(opts): void`
|
|
165
|
+
|
|
166
|
+
Register a listener on the IO Server (not individual sockets).
|
|
167
|
+
|
|
168
|
+
| Parameter | Type | Description |
|
|
169
|
+
|-----------|------|-------------|
|
|
170
|
+
| `opts.topic` | `string` | Event name |
|
|
171
|
+
| `opts.handler` | `(...args) => ValueOrPromise<T>` | Event handler |
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
helper.on({
|
|
175
|
+
topic: 'connection',
|
|
176
|
+
handler: (socket) => {
|
|
177
|
+
console.log('New connection:', socket.id);
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### `send(opts): void`
|
|
183
|
+
|
|
184
|
+
Send a message via the Redis emitter (works across processes/instances).
|
|
185
|
+
|
|
186
|
+
| Parameter | Type | Required | Description |
|
|
187
|
+
|-----------|------|----------|-------------|
|
|
188
|
+
| `opts.destination` | `string` | No | Socket ID or room name. If omitted, broadcasts to all |
|
|
189
|
+
| `opts.payload.topic` | `string` | Yes | Event name |
|
|
190
|
+
| `opts.payload.data` | `any` | Yes | Event payload |
|
|
191
|
+
| `opts.doLog` | `boolean` | No | Log the emission (default: `false`) |
|
|
192
|
+
| `opts.cb` | `() => void` | No | Callback executed via `setImmediate` after emit |
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
// Send to specific client
|
|
196
|
+
helper.send({
|
|
197
|
+
destination: socketId,
|
|
198
|
+
payload: { topic: 'notification', data: { message: 'Hello!' } },
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Send to room
|
|
202
|
+
helper.send({
|
|
203
|
+
destination: 'io-notification',
|
|
204
|
+
payload: { topic: 'alert', data: { level: 'warning', text: 'CPU high' } },
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Broadcast to all
|
|
208
|
+
helper.send({
|
|
209
|
+
payload: { topic: 'system:announcement', data: { text: 'Maintenance in 5 min' } },
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### `disconnect(opts): void`
|
|
214
|
+
|
|
215
|
+
Disconnect a client and clean up resources.
|
|
216
|
+
|
|
217
|
+
| Parameter | Type | Description |
|
|
218
|
+
|-----------|------|-------------|
|
|
219
|
+
| `opts.socket` | `IOSocket` | The socket to disconnect |
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
helper.disconnect({ socket: clientSocket });
|
|
223
|
+
// Clears ping interval, auth timeout, removes from tracking, calls socket.disconnect()
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### `getEngine(): any`
|
|
227
|
+
|
|
228
|
+
Returns the `@socket.io/bun-engine` instance (Bun runtime only). **Throws** if called on a non-Bun runtime.
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
const engine = helper.getEngine();
|
|
232
|
+
// Use for Bun-specific operations
|
|
233
|
+
// Throws an error if runtime is not Bun
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### `shutdown(): Promise<void>`
|
|
237
|
+
|
|
238
|
+
Full graceful shutdown — disconnects all clients, closes server, quits Redis connections.
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
await helper.shutdown();
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Shutdown sequence:**
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
shutdown()
|
|
248
|
+
│
|
|
249
|
+
├── For each tracked client:
|
|
250
|
+
│ ├── clearInterval(ping interval)
|
|
251
|
+
│ ├── clearTimeout(authenticate timeout)
|
|
252
|
+
│ └── socket.disconnect()
|
|
253
|
+
│
|
|
254
|
+
├── clients.clear()
|
|
255
|
+
│
|
|
256
|
+
├── io.close()
|
|
257
|
+
│
|
|
258
|
+
└── Quit Redis clients (parallel):
|
|
259
|
+
├── redisPub.quit()
|
|
260
|
+
├── redisSub.quit()
|
|
261
|
+
└── redisEmitter.quit()
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Authentication Flow
|
|
267
|
+
|
|
268
|
+
The server implements a challenge-response authentication pattern using Socket.IO events:
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
Client Server (SocketIOServerHelper)
|
|
272
|
+
│ │
|
|
273
|
+
│── connect ──────────────────► │ onClientConnect()
|
|
274
|
+
│ │ ├── Create client entry (state: UNAUTHORIZED)
|
|
275
|
+
│ │ ├── Start authenticateTimeout (10s)
|
|
276
|
+
│ │ └── Register disconnect handler
|
|
277
|
+
│ │
|
|
278
|
+
│── "authenticate" ───────────► │ Event handler
|
|
279
|
+
│ │ ├── Set state: AUTHENTICATING
|
|
280
|
+
│ │ └── Call authenticateFn(handshake)
|
|
281
|
+
│ │
|
|
282
|
+
│ │ ── authenticateFn returns true ──
|
|
283
|
+
│ │ onClientAuthenticated()
|
|
284
|
+
│ │ ├── Set state: AUTHENTICATED
|
|
285
|
+
│ │ ├── Send initial ping
|
|
286
|
+
│ │ ├── Join default rooms
|
|
287
|
+
│ │ ├── Register join/leave handlers
|
|
288
|
+
│ │ ├── Start ping interval (configurable via `pingInterval`)
|
|
289
|
+
│ ◄── "authenticated" ──────── │ ├── Emit "authenticated" with { id, time }
|
|
290
|
+
│ │ └── Call clientConnectedFn({ socket })
|
|
291
|
+
│ │
|
|
292
|
+
│ │ ── authenticateFn returns false ──
|
|
293
|
+
│ ◄── "unauthenticated" ────── │ ├── Emit "unauthenticated" with error message
|
|
294
|
+
│ │ └── Disconnect client (via callback)
|
|
295
|
+
│ │
|
|
296
|
+
│ │ ── authenticateTimeout expires ──
|
|
297
|
+
│ │ └── Disconnect client (if still UNAUTHORIZED)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### `IHandshake` Object
|
|
301
|
+
|
|
302
|
+
The `authenticateFn` receives the Socket.IO handshake data:
|
|
303
|
+
|
|
304
|
+
| Field | Type | Description |
|
|
305
|
+
|-------|------|-------------|
|
|
306
|
+
| `headers` | `IncomingHttpHeaders` | HTTP headers from the connection request |
|
|
307
|
+
| `time` | `string` | Handshake timestamp |
|
|
308
|
+
| `address` | `string` | Client IP address |
|
|
309
|
+
| `xdomain` | `boolean` | Whether the request is cross-domain |
|
|
310
|
+
| `secure` | `boolean` | Whether the connection is secure (HTTPS/WSS) |
|
|
311
|
+
| `issued` | `number` | When the handshake was issued (Unix timestamp) |
|
|
312
|
+
| `url` | `string` | Request URL |
|
|
313
|
+
| `query` | `ParsedUrlQuery` | URL query parameters |
|
|
314
|
+
| `auth` | `Record<string, any>` | Auth data from the client's `auth` option |
|
|
315
|
+
|
|
316
|
+
### `ISocketIOClient` Tracked State
|
|
317
|
+
|
|
318
|
+
Each connected client is tracked with:
|
|
319
|
+
|
|
320
|
+
| Field | Type | Description |
|
|
321
|
+
|-------|------|-------------|
|
|
322
|
+
| `id` | `string` | Socket ID |
|
|
323
|
+
| `socket` | `IOSocket` | Raw Socket.IO socket |
|
|
324
|
+
| `state` | `TSocketIOClientState` | Authentication state |
|
|
325
|
+
| `interval` | `NodeJS.Timeout?` | Ping interval (configurable via `pingInterval`) |
|
|
326
|
+
| `authenticateTimeout` | `NodeJS.Timeout` | Timeout to disconnect unauthenticated clients |
|
|
327
|
+
|
|
328
|
+
### Client States
|
|
329
|
+
|
|
330
|
+
```
|
|
331
|
+
┌──────────────┐ authenticate ┌────────────────┐ auth success ┌───────────────┐
|
|
332
|
+
│ UNAUTHORIZED │ ──────────────────► │ AUTHENTICATING │ ────────────────► │ AUTHENTICATED │
|
|
333
|
+
└──────────────┘ └────────────────┘ └───────────────┘
|
|
334
|
+
▲ │ │
|
|
335
|
+
│ auth failure │ │
|
|
336
|
+
└─────────────────────────────────────────┘ │
|
|
337
|
+
▲ │
|
|
338
|
+
│ disconnect │
|
|
339
|
+
└──────────────────────────────────────────────────────────────────────────────┘
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
| State | Value | Description |
|
|
343
|
+
|-------|-------|-------------|
|
|
344
|
+
| `SocketIOClientStates.UNAUTHORIZED` | `'unauthorized'` | Initial state, or after auth failure |
|
|
345
|
+
| `SocketIOClientStates.AUTHENTICATING` | `'authenticating'` | `authenticate` event received, awaiting `authenticateFn` |
|
|
346
|
+
| `SocketIOClientStates.AUTHENTICATED` | `'authenticated'` | Successfully authenticated, fully operational |
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Room Management
|
|
351
|
+
|
|
352
|
+
After authentication, clients can join and leave rooms:
|
|
353
|
+
|
|
354
|
+
### Built-in Events
|
|
355
|
+
|
|
356
|
+
| Event | Direction | Payload | Description |
|
|
357
|
+
|-------|-----------|---------|-------------|
|
|
358
|
+
| `join` | Client → Server | `{ rooms: string[] }` | Join one or more rooms |
|
|
359
|
+
| `leave` | Client → Server | `{ rooms: string[] }` | Leave one or more rooms |
|
|
360
|
+
|
|
361
|
+
These handlers are registered automatically in `onClientAuthenticated()`.
|
|
362
|
+
|
|
363
|
+
### Room Validation
|
|
364
|
+
|
|
365
|
+
Client room join requests are validated using the `validateRoomFn` callback. If no `validateRoomFn` is configured, **all join requests are rejected** for security.
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
const helper = new SocketIOServerHelper({
|
|
369
|
+
// ...
|
|
370
|
+
validateRoomFn: ({ socket, rooms }) => {
|
|
371
|
+
// Only allow rooms prefixed with 'public-' or the user's own room
|
|
372
|
+
const userId = socket.handshake.auth.userId;
|
|
373
|
+
return rooms.filter(room =>
|
|
374
|
+
room.startsWith('public-') || room === `user-${userId}`
|
|
375
|
+
);
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
The function receives the socket and requested rooms, and must return the subset of rooms the client is allowed to join.
|
|
381
|
+
|
|
382
|
+
### Default Rooms
|
|
383
|
+
|
|
384
|
+
Authenticated clients auto-join these rooms (configurable via `defaultRooms`):
|
|
385
|
+
|
|
386
|
+
| Room | Constant | Purpose |
|
|
387
|
+
|------|----------|---------|
|
|
388
|
+
| `io-default` | `SocketIOConstants.ROOM_DEFAULT` | General-purpose room for all clients |
|
|
389
|
+
| `io-notification` | `SocketIOConstants.ROOM_NOTIFICATION` | Notification delivery room |
|
|
390
|
+
|
|
391
|
+
### Programmatic Room Management
|
|
392
|
+
|
|
393
|
+
From your service code, you can manage rooms via the tracked client's socket:
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
const client = helper.getClients({ id: socketId }) as ISocketIOClient | undefined;
|
|
397
|
+
|
|
398
|
+
if (client) {
|
|
399
|
+
// Join rooms
|
|
400
|
+
client.socket.join(['room-a', 'room-b']);
|
|
401
|
+
|
|
402
|
+
// Leave a room
|
|
403
|
+
client.socket.leave('room-a');
|
|
404
|
+
|
|
405
|
+
// Get current rooms
|
|
406
|
+
const rooms = Array.from(client.socket.rooms);
|
|
407
|
+
// rooms = [socketId, 'room-b', 'io-default', 'io-notification']
|
|
408
|
+
// Note: first room is always the socket's own ID
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## Redis Integration
|
|
415
|
+
|
|
416
|
+
The helper creates **three** dedicated Redis connections (duplicated from your `redisConnection`):
|
|
417
|
+
|
|
418
|
+
| Connection | Purpose | Library |
|
|
419
|
+
|------------|---------|---------|
|
|
420
|
+
| `redisPub` | Publish adapter messages | `@socket.io/redis-adapter` |
|
|
421
|
+
| `redisSub` | Subscribe to adapter messages | `@socket.io/redis-adapter` |
|
|
422
|
+
| `redisEmitter` | Emit messages to other processes | `@socket.io/redis-emitter` |
|
|
423
|
+
|
|
424
|
+
### Why Three Connections?
|
|
425
|
+
|
|
426
|
+
```
|
|
427
|
+
Process A Redis Process B
|
|
428
|
+
┌─────────┐ ┌──────────┐ ┌─────────┐
|
|
429
|
+
│ IO Server│──redisPub────►│ │◄──redisPub────│ IO Server│
|
|
430
|
+
│ │◄──redisSub────│ Pub/Sub │──redisSub────►│ │
|
|
431
|
+
│ │ │ │ │ │
|
|
432
|
+
│ Emitter │──redisEmitter►│ Streams │◄──redisEmitter│ Emitter │
|
|
433
|
+
└─────────┘ └──────────┘ └─────────┘
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
- **Adapter** (pub/sub pair): Synchronizes Socket.IO state across multiple server instances. When server A emits to a room, the adapter broadcasts via Redis so server B's clients in that room also receive the event.
|
|
437
|
+
- **Emitter**: Allows emitting events from non-Socket.IO processes (background workers, microservices) using the same Redis connection.
|
|
438
|
+
|
|
439
|
+
### Horizontal Scaling
|
|
440
|
+
|
|
441
|
+
With Redis adapter configured, you can run multiple server instances behind a load balancer:
|
|
442
|
+
|
|
443
|
+
```
|
|
444
|
+
Client A ──► Load Balancer ──► Server 1 (Socket.IO + Redis Adapter)
|
|
445
|
+
Client B ──► │ ──► Server 2 (Socket.IO + Redis Adapter)
|
|
446
|
+
Client C ──► │ ──► Server 3 (Socket.IO + Redis Adapter)
|
|
447
|
+
│
|
|
448
|
+
All servers share state via Redis
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Events emitted via `helper.send()` use the **emitter** (not direct socket), so they propagate across all instances automatically.
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## Built-in Events Reference
|
|
456
|
+
|
|
457
|
+
| Event | Constant | Direction | When |
|
|
458
|
+
|-------|----------|-----------|------|
|
|
459
|
+
| `connection` | `SocketIOConstants.EVENT_CONNECT` | Client → Server | New WebSocket connection established |
|
|
460
|
+
| `disconnect` | `SocketIOConstants.EVENT_DISCONNECT` | Client → Server | Client disconnects (intentional or timeout) |
|
|
461
|
+
| `authenticate` | `SocketIOConstants.EVENT_AUTHENTICATE` | Client → Server | Client requests authentication |
|
|
462
|
+
| `authenticated` | `SocketIOConstants.EVENT_AUTHENTICATED` | Server → Client | Authentication succeeded |
|
|
463
|
+
| `unauthenticated` | `SocketIOConstants.EVENT_UNAUTHENTICATE` | Server → Client | Authentication failed |
|
|
464
|
+
| `ping` | `SocketIOConstants.EVENT_PING` | Server → Client | Server → Client keep-alive (interval configurable via `pingInterval`) |
|
|
465
|
+
| `join` | `SocketIOConstants.EVENT_JOIN` | Client → Server | Request to join rooms |
|
|
466
|
+
| `leave` | `SocketIOConstants.EVENT_LEAVE` | Client → Server | Request to leave rooms |
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## `configure()` Internals
|
|
471
|
+
|
|
472
|
+
The `configure()` method sets up the IO server based on runtime:
|
|
473
|
+
|
|
474
|
+
```
|
|
475
|
+
configure() [async]
|
|
476
|
+
│
|
|
477
|
+
├── Register error handlers on redisPub, redisSub, redisEmitter
|
|
478
|
+
│
|
|
479
|
+
├── Await all Redis connections ready
|
|
480
|
+
│ └── Promise.all([waitForRedisReady(pub), waitForRedisReady(sub), waitForRedisReady(emitter)])
|
|
481
|
+
│
|
|
482
|
+
├── Runtime check
|
|
483
|
+
│ ├── NODE: new IOServer(httpServer, serverOptions)
|
|
484
|
+
│ └── BUN: new IOServer() + io.bind(bunEngine)
|
|
485
|
+
│
|
|
486
|
+
├── Redis Adapter
|
|
487
|
+
│ └── io.adapter(createAdapter(redisPub, redisSub))
|
|
488
|
+
│
|
|
489
|
+
├── Redis Emitter
|
|
490
|
+
│ └── new Emitter(redisEmitter)
|
|
491
|
+
│
|
|
492
|
+
└── Connection handler
|
|
493
|
+
└── io.on('connection', socket => onClientConnect({ socket }))
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
# SocketIOClientHelper
|
|
499
|
+
|
|
500
|
+
Structured client-side Socket.IO connection management with lifecycle callbacks, event subscription, authentication, and room management.
|
|
501
|
+
|
|
502
|
+
## Constructor
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
new SocketIOClientHelper(opts: ISocketIOClientOptions)
|
|
506
|
+
```
|
|
64
507
|
|
|
65
|
-
|
|
66
|
-
|
|
508
|
+
### Options
|
|
509
|
+
|
|
510
|
+
| Field | Type | Required | Description |
|
|
511
|
+
|-------|------|----------|-------------|
|
|
512
|
+
| `identifier` | `string` | Yes | Unique name for this client |
|
|
513
|
+
| `host` | `string` | Yes | Server URL (e.g., `http://localhost:3000`) |
|
|
514
|
+
| `options` | `IOptions` | Yes | Socket.IO client options (path, extraHeaders, etc.) |
|
|
515
|
+
| `onConnected` | `() => ValueOrPromise<void>` | No | Called when WebSocket connection is established |
|
|
516
|
+
| `onDisconnected` | `(reason: string) => ValueOrPromise<void>` | No | Called on disconnect with reason |
|
|
517
|
+
| `onError` | `(error: Error) => ValueOrPromise<void>` | No | Called on connection error |
|
|
518
|
+
| `onAuthenticated` | `() => ValueOrPromise<void>` | No | Called when server sends `authenticated` event |
|
|
519
|
+
| `onUnauthenticated` | `(message: string) => ValueOrPromise<void>` | No | Called when server sends `unauthenticated` event |
|
|
520
|
+
|
|
521
|
+
### `IOptions`
|
|
522
|
+
|
|
523
|
+
Extends `SocketOptions` from `socket.io-client`:
|
|
524
|
+
|
|
525
|
+
| Field | Type | Description |
|
|
526
|
+
|-------|------|-------------|
|
|
527
|
+
| `path` | `string` | Socket.IO server path (e.g., `'/io'`) |
|
|
528
|
+
| `extraHeaders` | `Record<string, any>` | HTTP headers sent with the connection (e.g., `Authorization`) |
|
|
529
|
+
| *...inherited* | | All `socket.io-client` `SocketOptions` |
|
|
530
|
+
|
|
531
|
+
### Example
|
|
532
|
+
|
|
533
|
+
```typescript
|
|
534
|
+
import { SocketIOClientHelper } from '@venizia/ignis-helpers';
|
|
535
|
+
|
|
536
|
+
const client = new SocketIOClientHelper({
|
|
537
|
+
identifier: 'my-client',
|
|
67
538
|
host: 'http://localhost:3000',
|
|
68
539
|
options: {
|
|
69
|
-
path: '/io',
|
|
540
|
+
path: '/io',
|
|
70
541
|
extraHeaders: {
|
|
71
542
|
Authorization: 'Bearer my-jwt-token',
|
|
72
543
|
},
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
544
|
+
},
|
|
545
|
+
onConnected: () => {
|
|
546
|
+
console.log('Connected!');
|
|
547
|
+
client.authenticate(); // Trigger authentication flow
|
|
548
|
+
},
|
|
549
|
+
onAuthenticated: () => {
|
|
550
|
+
console.log('Authenticated!');
|
|
551
|
+
// Now safe to subscribe to events and emit messages
|
|
552
|
+
},
|
|
553
|
+
onDisconnected: (reason) => {
|
|
554
|
+
console.log('Disconnected:', reason);
|
|
76
555
|
},
|
|
77
556
|
});
|
|
78
557
|
|
|
79
|
-
|
|
558
|
+
// Connection is established in constructor via configure()
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
## Methods
|
|
564
|
+
|
|
565
|
+
### `connect(): void`
|
|
566
|
+
|
|
567
|
+
Explicitly connect to the server (if not already connected).
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
client.connect();
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### `disconnect(): void`
|
|
574
|
+
|
|
575
|
+
Disconnect from the server.
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
client.disconnect();
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### `authenticate(): void`
|
|
582
|
+
|
|
583
|
+
Send the `authenticate` event to the server. Only works when connected and in `UNAUTHORIZED` state.
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
client.authenticate();
|
|
587
|
+
// Server will respond with 'authenticated' or 'unauthenticated' event
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### `getState(): TSocketIOClientState`
|
|
591
|
+
|
|
592
|
+
Returns the current authentication state.
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
const state = client.getState();
|
|
596
|
+
// 'unauthorized' | 'authenticating' | 'authenticated'
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### `getSocketClient(): Socket`
|
|
600
|
+
|
|
601
|
+
Returns the raw `socket.io-client` `Socket` instance for advanced operations.
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
const socket = client.getSocketClient();
|
|
605
|
+
socket.on('custom-event', (data) => { /* ... */ });
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### `subscribe<T>(opts): void`
|
|
609
|
+
|
|
610
|
+
Subscribe to a single event with duplicate protection.
|
|
611
|
+
|
|
612
|
+
| Parameter | Type | Default | Description |
|
|
613
|
+
|-----------|------|---------|-------------|
|
|
614
|
+
| `opts.event` | `string` | — | Event name |
|
|
615
|
+
| `opts.handler` | `TSocketIOEventHandler<T>` | — | Event handler (errors are caught internally) |
|
|
616
|
+
| `opts.ignoreDuplicate` | `boolean` | `true` | Skip if handler already exists for this event |
|
|
617
|
+
|
|
618
|
+
```typescript
|
|
619
|
+
client.subscribe<{ message: string }>({
|
|
620
|
+
event: 'notification',
|
|
621
|
+
handler: (data) => {
|
|
622
|
+
console.log('Got notification:', data.message);
|
|
623
|
+
},
|
|
624
|
+
});
|
|
80
625
|
```
|
|
81
626
|
|
|
82
|
-
###
|
|
627
|
+
### `subscribeMany(opts): void`
|
|
628
|
+
|
|
629
|
+
Subscribe to multiple events at once.
|
|
83
630
|
|
|
84
631
|
```typescript
|
|
85
|
-
|
|
632
|
+
client.subscribeMany({
|
|
86
633
|
events: {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
socketClient.emit({ topic: 'authenticate' });
|
|
91
|
-
},
|
|
92
|
-
authenticated: (data) => {
|
|
93
|
-
console.log('Successfully authenticated:', data);
|
|
94
|
-
},
|
|
95
|
-
notification: (data) => {
|
|
96
|
-
console.log('Received notification:', data);
|
|
97
|
-
},
|
|
98
|
-
disconnect: () => {
|
|
99
|
-
console.log('Disconnected from server.');
|
|
100
|
-
},
|
|
634
|
+
'chat:message': (data) => console.log('Chat:', data),
|
|
635
|
+
'room:update': (data) => console.log('Room:', data),
|
|
636
|
+
'system:alert': (data) => console.log('Alert:', data),
|
|
101
637
|
},
|
|
102
638
|
});
|
|
103
639
|
```
|
|
104
640
|
|
|
641
|
+
### `unsubscribe(opts): void`
|
|
642
|
+
|
|
643
|
+
Remove event listener(s).
|
|
644
|
+
|
|
645
|
+
| Parameter | Type | Description |
|
|
646
|
+
|-----------|------|-------------|
|
|
647
|
+
| `opts.event` | `string` | Event name |
|
|
648
|
+
| `opts.handler` | `TSocketIOEventHandler` (optional) | Specific handler to remove. If omitted, removes all handlers |
|
|
649
|
+
|
|
650
|
+
```typescript
|
|
651
|
+
// Remove specific handler
|
|
652
|
+
client.unsubscribe({ event: 'notification', handler: myHandler });
|
|
653
|
+
|
|
654
|
+
// Remove all handlers for event
|
|
655
|
+
client.unsubscribe({ event: 'notification' });
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
### `unsubscribeMany(opts): void`
|
|
659
|
+
|
|
660
|
+
Remove all handlers for multiple events.
|
|
661
|
+
|
|
662
|
+
```typescript
|
|
663
|
+
client.unsubscribeMany({ events: ['chat:message', 'room:update'] });
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### `emit<T>(opts): void`
|
|
667
|
+
|
|
668
|
+
Emit an event to the server.
|
|
669
|
+
|
|
670
|
+
| Parameter | Type | Required | Description |
|
|
671
|
+
|-----------|------|----------|-------------|
|
|
672
|
+
| `opts.topic` | `string` | Yes | Event name |
|
|
673
|
+
| `opts.data` | `T` | Yes | Event payload |
|
|
674
|
+
| `opts.doLog` | `boolean` | No | Log the emission |
|
|
675
|
+
| `opts.cb` | `() => void` | No | Callback after emit |
|
|
676
|
+
|
|
677
|
+
```typescript
|
|
678
|
+
client.emit({
|
|
679
|
+
topic: 'chat:message',
|
|
680
|
+
data: { room: 'general', message: 'Hello everyone!' },
|
|
681
|
+
});
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
> [!NOTE]
|
|
685
|
+
> Throws if the client is not connected. Check `getSocketClient().connected` first if unsure.
|
|
686
|
+
|
|
687
|
+
### `joinRooms(opts): void`
|
|
688
|
+
|
|
689
|
+
Request to join rooms (emits the `join` event to server).
|
|
690
|
+
|
|
691
|
+
```typescript
|
|
692
|
+
client.joinRooms({ rooms: ['room-a', 'room-b'] });
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
### `leaveRooms(opts): void`
|
|
696
|
+
|
|
697
|
+
Request to leave rooms (emits the `leave` event to server).
|
|
698
|
+
|
|
699
|
+
```typescript
|
|
700
|
+
client.leaveRooms({ rooms: ['room-a'] });
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### `shutdown(): void`
|
|
704
|
+
|
|
705
|
+
Full cleanup — removes all listeners, disconnects, resets state.
|
|
706
|
+
|
|
707
|
+
```typescript
|
|
708
|
+
client.shutdown();
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
---
|
|
712
|
+
|
|
713
|
+
## Client Lifecycle
|
|
714
|
+
|
|
715
|
+
```
|
|
716
|
+
┌──────────┐
|
|
717
|
+
│ new │ constructor → configure()
|
|
718
|
+
│ Client() │ ├── io(host, options)
|
|
719
|
+
└─────┬─────┘ ├── Register: connect, disconnect, connect_error
|
|
720
|
+
│ ├── Register: authenticated, unauthenticated, ping
|
|
721
|
+
│ └── Connection established (if server is reachable)
|
|
722
|
+
│
|
|
723
|
+
┌─────▼─────────┐
|
|
724
|
+
│ Connected │ onConnected callback fires
|
|
725
|
+
│ (UNAUTHORIZED)│
|
|
726
|
+
└─────┬─────────┘
|
|
727
|
+
│
|
|
728
|
+
│ authenticate()
|
|
729
|
+
│
|
|
730
|
+
┌─────▼──────────┐
|
|
731
|
+
│ AUTHENTICATING │ Waiting for server response
|
|
732
|
+
└─────┬──────────┘
|
|
733
|
+
│
|
|
734
|
+
┌────┴────┐
|
|
735
|
+
│ │
|
|
736
|
+
▼ ▼
|
|
737
|
+
┌────────┐ ┌──────────────┐
|
|
738
|
+
│ AUTH'D │ │ UNAUTH'D │ onUnauthenticated callback
|
|
739
|
+
│ │ │ → disconnect │
|
|
740
|
+
└───┬────┘ └──────────────┘
|
|
741
|
+
│
|
|
742
|
+
│ onAuthenticated callback
|
|
743
|
+
│
|
|
744
|
+
▼
|
|
745
|
+
Ready to emit/subscribe
|
|
746
|
+
│
|
|
747
|
+
│ disconnect() or server disconnect
|
|
748
|
+
│
|
|
749
|
+
┌─▼───────────┐
|
|
750
|
+
│ Disconnected │ onDisconnected callback
|
|
751
|
+
│ (UNAUTHORIZED)│ State reset
|
|
752
|
+
└──────────────┘
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
## Constants Reference
|
|
758
|
+
|
|
759
|
+
### `SocketIOConstants`
|
|
760
|
+
|
|
761
|
+
| Constant | Value | Description |
|
|
762
|
+
|----------|-------|-------------|
|
|
763
|
+
| `EVENT_PING` | `'ping'` | Server → Client keep-alive (interval configurable via `pingInterval`) |
|
|
764
|
+
| `EVENT_CONNECT` | `'connection'` | Server-side connection event |
|
|
765
|
+
| `EVENT_DISCONNECT` | `'disconnect'` | Disconnection event |
|
|
766
|
+
| `EVENT_JOIN` | `'join'` | Room join request |
|
|
767
|
+
| `EVENT_LEAVE` | `'leave'` | Room leave request |
|
|
768
|
+
| `EVENT_AUTHENTICATE` | `'authenticate'` | Client → Server auth request |
|
|
769
|
+
| `EVENT_AUTHENTICATED` | `'authenticated'` | Server → Client auth success |
|
|
770
|
+
| `EVENT_UNAUTHENTICATE` | `'unauthenticated'` | Server → Client auth failure |
|
|
771
|
+
| `ROOM_DEFAULT` | `'io-default'` | Default room for all authenticated clients |
|
|
772
|
+
| `ROOM_NOTIFICATION` | `'io-notification'` | Default notification room |
|
|
773
|
+
|
|
774
|
+
### `SocketIOClientStates`
|
|
775
|
+
|
|
776
|
+
| Constant | Value |
|
|
777
|
+
|----------|-------|
|
|
778
|
+
| `UNAUTHORIZED` | `'unauthorized'` |
|
|
779
|
+
| `AUTHENTICATING` | `'authenticating'` |
|
|
780
|
+
| `AUTHENTICATED` | `'authenticated'` |
|
|
781
|
+
|
|
782
|
+
Utility methods:
|
|
783
|
+
|
|
784
|
+
```typescript
|
|
785
|
+
SocketIOClientStates.isValid('authenticated'); // true
|
|
786
|
+
SocketIOClientStates.isValid('invalid'); // false
|
|
787
|
+
SocketIOClientStates.SCHEME_SET; // Set { 'unauthorized', 'authenticating', 'authenticated' }
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
---
|
|
791
|
+
|
|
792
|
+
## Type Definitions
|
|
793
|
+
|
|
794
|
+
### `TSocketIOServerOptions`
|
|
795
|
+
|
|
796
|
+
```typescript
|
|
797
|
+
interface ISocketIOServerBaseOptions {
|
|
798
|
+
identifier: string;
|
|
799
|
+
serverOptions: Partial<ServerOptions>;
|
|
800
|
+
redisConnection: DefaultRedisHelper;
|
|
801
|
+
authenticateFn: TSocketIOAuthenticateFn;
|
|
802
|
+
clientConnectedFn?: TSocketIOClientConnectedFn;
|
|
803
|
+
validateRoomFn?: TSocketIOValidateRoomFn;
|
|
804
|
+
authenticateTimeout?: number;
|
|
805
|
+
pingInterval?: number;
|
|
806
|
+
defaultRooms?: string[];
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
interface ISocketIOServerNodeOptions extends ISocketIOServerBaseOptions {
|
|
810
|
+
runtime: typeof RuntimeModules.NODE; // 'node'
|
|
811
|
+
server: HTTPServer;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
interface ISocketIOServerBunOptions extends ISocketIOServerBaseOptions {
|
|
815
|
+
runtime: typeof RuntimeModules.BUN; // 'bun'
|
|
816
|
+
engine: any; // @socket.io/bun-engine Server instance
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
type TSocketIOServerOptions = ISocketIOServerNodeOptions | ISocketIOServerBunOptions;
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
### `ISocketIOClient`
|
|
823
|
+
|
|
824
|
+
```typescript
|
|
825
|
+
interface ISocketIOClient {
|
|
826
|
+
id: string;
|
|
827
|
+
socket: IOSocket;
|
|
828
|
+
state: TSocketIOClientState; // 'unauthorized' | 'authenticating' | 'authenticated'
|
|
829
|
+
interval?: NodeJS.Timeout; // Ping interval (set after auth)
|
|
830
|
+
authenticateTimeout: NodeJS.Timeout; // Auto-disconnect timer
|
|
831
|
+
}
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
### `IHandshake`
|
|
835
|
+
|
|
836
|
+
```typescript
|
|
837
|
+
interface IHandshake {
|
|
838
|
+
headers: IncomingHttpHeaders;
|
|
839
|
+
time: string;
|
|
840
|
+
address: string;
|
|
841
|
+
xdomain: boolean;
|
|
842
|
+
secure: boolean;
|
|
843
|
+
issued: number;
|
|
844
|
+
url: string;
|
|
845
|
+
query: ParsedUrlQuery;
|
|
846
|
+
auth: { [key: string]: any };
|
|
847
|
+
}
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
### `ISocketIOClientOptions`
|
|
851
|
+
|
|
852
|
+
```typescript
|
|
853
|
+
interface ISocketIOClientOptions {
|
|
854
|
+
identifier: string;
|
|
855
|
+
host: string;
|
|
856
|
+
options: IOptions;
|
|
857
|
+
onConnected?: () => ValueOrPromise<void>;
|
|
858
|
+
onDisconnected?: (reason: string) => ValueOrPromise<void>;
|
|
859
|
+
onError?: (error: Error) => ValueOrPromise<void>;
|
|
860
|
+
onAuthenticated?: () => ValueOrPromise<void>;
|
|
861
|
+
onUnauthenticated?: (message: string) => ValueOrPromise<void>;
|
|
862
|
+
}
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
### `TSocketIOAuthenticateFn`
|
|
866
|
+
|
|
867
|
+
```typescript
|
|
868
|
+
type TSocketIOAuthenticateFn = (args: IHandshake) => ValueOrPromise<boolean>;
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
### `TSocketIOValidateRoomFn`
|
|
872
|
+
|
|
873
|
+
```typescript
|
|
874
|
+
type TSocketIOValidateRoomFn = (opts: { socket: IOSocket; rooms: string[] }) => ValueOrPromise<string[]>;
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
### `TSocketIOClientConnectedFn`
|
|
878
|
+
|
|
879
|
+
```typescript
|
|
880
|
+
type TSocketIOClientConnectedFn = (opts: { socket: IOSocket }) => ValueOrPromise<void>;
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
### `TSocketIOEventHandler`
|
|
884
|
+
|
|
885
|
+
```typescript
|
|
886
|
+
type TSocketIOEventHandler<T = unknown> = (data: T) => ValueOrPromise<void>;
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
---
|
|
890
|
+
|
|
891
|
+
## RuntimeModules
|
|
892
|
+
|
|
893
|
+
Used for runtime detection and discriminated union typing:
|
|
894
|
+
|
|
895
|
+
```typescript
|
|
896
|
+
class RuntimeModules {
|
|
897
|
+
static readonly NODE = 'node';
|
|
898
|
+
static readonly BUN = 'bun';
|
|
899
|
+
|
|
900
|
+
static detect(): TRuntimeModule; // Returns 'bun' or 'node'
|
|
901
|
+
static isBun(): boolean;
|
|
902
|
+
static isNode(): boolean;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
type TRuntimeModule = 'node' | 'bun'; // TConstValue<typeof RuntimeModules>
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
---
|
|
909
|
+
|
|
105
910
|
## See Also
|
|
106
911
|
|
|
107
912
|
- **Related Concepts:**
|
|
108
|
-
- [Services](/guides/core-concepts/services)
|
|
913
|
+
- [Services](/guides/core-concepts/services) — Using helpers in service layer
|
|
109
914
|
|
|
110
915
|
- **Other Helpers:**
|
|
111
|
-
- [Helpers Index](./index)
|
|
112
|
-
- [Redis Helper](./redis)
|
|
916
|
+
- [Helpers Index](./index) — All available helpers
|
|
917
|
+
- [Redis Helper](./redis) — `RedisHelper` used for Socket.IO adapter
|
|
113
918
|
|
|
114
919
|
- **References:**
|
|
115
|
-
- [Socket.IO Component](/references/components/socket-io)
|
|
920
|
+
- [Socket.IO Component](/references/components/socket-io) — Component setup and lifecycle integration
|
|
116
921
|
|
|
117
922
|
- **External Resources:**
|
|
118
|
-
- [Socket.IO Documentation](https://socket.io/docs/)
|
|
119
|
-
- [Socket.IO Redis Adapter](https://socket.io/docs/v4/redis-adapter/)
|
|
923
|
+
- [Socket.IO Documentation](https://socket.io/docs/) — Official Socket.IO docs
|
|
924
|
+
- [Socket.IO Redis Adapter](https://socket.io/docs/v4/redis-adapter/) — Scaling guide
|
|
925
|
+
- [Socket.IO Client API](https://socket.io/docs/v4/client-api/) — Client reference
|
|
926
|
+
- [@socket.io/bun-engine](https://github.com/socketio/bun-engine) — Bun runtime support
|
|
120
927
|
|
|
121
928
|
- **Tutorials:**
|
|
122
|
-
- [Real-Time Chat Application](/guides/tutorials/realtime-chat)
|
|
929
|
+
- [Real-Time Chat Application](/guides/tutorials/realtime-chat) — Full Socket.IO tutorial
|
|
930
|
+
|
|
931
|
+
- **Changelog:**
|
|
932
|
+
- [2026-02-06: Socket.IO Integration Fix](/changelogs/2026-02-06-socket-io-integration-fix) — Lifecycle timing fix + Bun runtime support
|