@venizia/ignis-docs 0.2.0 → 0.2.1-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/README.md +14 -14
- package/content/best-practices/api-usage-examples.md +39 -19
- package/content/best-practices/architectural-patterns.md +22 -11
- package/content/best-practices/architecture-decisions.md +29 -16
- package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
- package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
- package/content/best-practices/code-style-standards/control-flow.md +33 -1
- package/content/best-practices/code-style-standards/documentation.md +28 -8
- package/content/best-practices/code-style-standards/function-patterns.md +15 -4
- package/content/best-practices/code-style-standards/index.md +7 -2
- package/content/best-practices/code-style-standards/naming-conventions.md +26 -2
- package/content/best-practices/code-style-standards/route-definitions.md +9 -5
- package/content/best-practices/code-style-standards/tooling.md +14 -1
- package/content/best-practices/code-style-standards/type-safety.md +39 -6
- package/content/best-practices/common-pitfalls.md +59 -35
- package/content/best-practices/contribution-workflow.md +6 -2
- package/content/best-practices/data-modeling.md +78 -62
- package/content/best-practices/deployment-strategies.md +56 -19
- package/content/best-practices/error-handling.md +182 -93
- package/content/best-practices/index.md +6 -0
- package/content/best-practices/performance-optimization.md +26 -13
- package/content/best-practices/security-guidelines.md +35 -9
- package/content/best-practices/testing-strategies.md +7 -2
- package/content/best-practices/troubleshooting-tips.md +27 -17
- package/content/extensions/components/api-reference.md +107 -322
- package/content/extensions/components/authentication/api.md +454 -603
- package/content/extensions/components/authentication/errors.md +121 -498
- package/content/extensions/components/authentication/index.md +88 -801
- package/content/extensions/components/authentication/usage.md +207 -956
- package/content/extensions/components/authorization/api.md +736 -656
- package/content/extensions/components/authorization/errors.md +168 -206
- package/content/extensions/components/authorization/index.md +82 -797
- package/content/extensions/components/authorization/usage.md +194 -527
- package/content/extensions/components/health-check.md +71 -243
- package/content/extensions/components/mail/api.md +504 -287
- package/content/extensions/components/mail/errors.md +73 -61
- package/content/extensions/components/mail/index.md +96 -467
- package/content/extensions/components/mail/usage.md +130 -172
- package/content/extensions/components/request-tracker.md +66 -173
- package/content/extensions/components/socket-io/api.md +195 -13
- package/content/extensions/components/socket-io/errors.md +3 -3
- package/content/extensions/components/socket-io/index.md +50 -337
- package/content/extensions/components/socket-io/usage.md +143 -26
- package/content/extensions/components/static-asset/api.md +410 -142
- package/content/extensions/components/static-asset/errors.md +110 -53
- package/content/extensions/components/static-asset/index.md +79 -608
- package/content/extensions/components/static-asset/usage.md +180 -300
- package/content/extensions/components/websocket/api.md +275 -399
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +74 -407
- package/content/extensions/components/websocket/usage.md +110 -341
- package/content/extensions/helpers/cron/index.md +51 -160
- package/content/extensions/helpers/crypto/index.md +62 -483
- package/content/extensions/helpers/crypto/reference.md +456 -0
- package/content/extensions/helpers/env/index.md +60 -178
- package/content/extensions/helpers/error/index.md +221 -207
- package/content/extensions/helpers/inversion/index.md +65 -556
- package/content/extensions/helpers/inversion/reference.md +522 -0
- package/content/extensions/helpers/kafka/admin.md +20 -1
- package/content/extensions/helpers/kafka/compile-binary.md +41 -35
- package/content/extensions/helpers/kafka/consumer.md +54 -20
- package/content/extensions/helpers/kafka/examples.md +21 -16
- package/content/extensions/helpers/kafka/index.md +80 -610
- package/content/extensions/helpers/kafka/producer.md +134 -5
- package/content/extensions/helpers/kafka/schema-registry.md +45 -70
- package/content/extensions/helpers/logger/hf-logger.md +193 -0
- package/content/extensions/helpers/logger/index.md +64 -563
- package/content/extensions/helpers/logger/pino.md +85 -0
- package/content/extensions/helpers/logger/reference.md +746 -0
- package/content/extensions/helpers/network/api.md +241 -195
- package/content/extensions/helpers/network/index.md +72 -530
- package/content/extensions/helpers/queue/index.md +72 -900
- package/content/extensions/helpers/queue/reference.md +467 -0
- package/content/extensions/helpers/redis/index.md +73 -645
- package/content/extensions/helpers/redis/reference.md +727 -0
- package/content/extensions/helpers/secrets/index.md +66 -0
- package/content/extensions/helpers/socket-io/api.md +305 -203
- package/content/extensions/helpers/socket-io/index.md +66 -432
- package/content/extensions/helpers/storage/api.md +564 -462
- package/content/extensions/helpers/storage/index.md +77 -573
- package/content/extensions/helpers/types/index.md +66 -499
- package/content/extensions/helpers/types/reference.md +650 -0
- package/content/extensions/helpers/uid/index.md +58 -227
- package/content/extensions/helpers/websocket/api.md +329 -216
- package/content/extensions/helpers/websocket/index.md +65 -503
- package/content/extensions/helpers/worker-thread/index.md +58 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/search-typesense.md +3 -3
- package/content/guides/core-concepts/persistent/transactions.md +1 -1
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/migrations/redis-helpers-migration.md +1 -1
- package/content/guides/migrations/unified-connectors-migration.md +2 -2
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/references/base/application.md +1 -1
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/datasources-reference.md +599 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +17 -39
- package/content/references/base/filter-system/application-usage.md +69 -121
- package/content/references/base/filter-system/array-operators.md +12 -0
- package/content/references/base/filter-system/comparison-operators.md +12 -0
- package/content/references/base/filter-system/default-filter.md +136 -348
- package/content/references/base/filter-system/fields-order-pagination.md +38 -16
- package/content/references/base/filter-system/index.md +106 -257
- package/content/references/base/filter-system/json-filtering.md +12 -2
- package/content/references/base/filter-system/list-operators.md +16 -2
- package/content/references/base/filter-system/logical-operators.md +13 -0
- package/content/references/base/filter-system/null-operators.md +13 -0
- package/content/references/base/filter-system/pattern-matching.md +12 -0
- package/content/references/base/filter-system/quick-reference.md +11 -2
- package/content/references/base/filter-system/range-operators.md +12 -0
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +156 -233
- package/content/references/base/middlewares.md +35 -21
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +80 -1452
- package/content/references/base/repositories/advanced.md +156 -192
- package/content/references/base/repositories/index.md +77 -650
- package/content/references/base/repositories/mixins.md +22 -18
- package/content/references/base/repositories/relations.md +123 -171
- package/content/references/base/repositories/soft-deletable.md +58 -56
- package/content/references/base/secrets.md +263 -0
- package/content/references/base/services.md +2 -2
- package/content/references/configuration/environment-variables.md +48 -4
- package/content/references/configuration/index.md +49 -31
- package/content/references/quick-reference.md +3 -16
- package/content/references/utilities/crypto.md +35 -76
- package/content/references/utilities/date.md +33 -73
- package/content/references/utilities/index.md +1 -1
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +29 -62
- package/content/references/utilities/parse.md +34 -64
- package/content/references/utilities/performance.md +33 -58
- package/content/references/utilities/promise.md +28 -62
- package/content/references/utilities/request.md +57 -218
- package/content/references/utilities/schema.md +43 -137
- package/content/references/utilities/statuses-reference.md +361 -0
- package/content/references/utilities/statuses.md +63 -667
- package/package.json +8 -8
|
@@ -1,51 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Runtime-agnostic Socket.IO server and client helpers with built-in authentication
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Socket.IO
|
|
3
|
+
description: Runtime-agnostic Socket.IO server and client helpers with a built-in authentication handshake and Redis-backed horizontal scaling
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
6
6
|
|
|
7
|
-
|
|
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/socket-io';
|
|
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/socket-io';
|
|
37
|
-
```
|
|
7
|
+
# Socket.IO
|
|
38
8
|
|
|
39
|
-
|
|
9
|
+
`SocketIOServerHelper` and `SocketIOClientHelper` wrap `socket.io` with a mandatory post-connection authentication handshake, room management, and a Redis adapter so events reach clients no matter which server instance they are connected to.
|
|
40
10
|
|
|
41
|
-
|
|
11
|
+
## In one example
|
|
42
12
|
|
|
43
|
-
|
|
13
|
+
The smallest working server: construct with a Redis connection and an `authenticateFn`, then `configure()`.
|
|
44
14
|
|
|
45
15
|
```typescript
|
|
46
|
-
import { SocketIOServerHelper } from '@venizia/ignis-helpers/socket-io';
|
|
47
|
-
import { RedisSingleHelper } from '@venizia/ignis-helpers';
|
|
48
16
|
import { createServer } from 'node:http';
|
|
17
|
+
import { RedisSingleHelper } from '@venizia/ignis-helpers';
|
|
18
|
+
import { SocketIOServerHelper } from '@venizia/ignis-helpers/socket-io';
|
|
49
19
|
|
|
50
20
|
const httpServer = createServer();
|
|
51
21
|
|
|
@@ -61,457 +31,121 @@ const socketServer = new SocketIOServerHelper({
|
|
|
61
31
|
runtime: 'node',
|
|
62
32
|
server: httpServer,
|
|
63
33
|
redisConnection: redisHelper,
|
|
64
|
-
serverOptions: {
|
|
65
|
-
cors: { origin: '*' },
|
|
66
|
-
path: '/socket.io',
|
|
67
|
-
},
|
|
68
|
-
authenticateFn: async (handshake) => {
|
|
69
|
-
const token = handshake.auth?.token;
|
|
70
|
-
return !!token; // Return true to accept, false to reject
|
|
71
|
-
},
|
|
72
|
-
validateRoomFn: async ({ socket, rooms }) => {
|
|
73
|
-
// Return only the rooms the client is allowed to join
|
|
74
|
-
return rooms.filter(r => r.startsWith('public-'));
|
|
75
|
-
},
|
|
76
|
-
clientConnectedFn: async ({ socket }) => {
|
|
77
|
-
console.log('Client authenticated:', socket.id);
|
|
78
|
-
},
|
|
79
|
-
defaultRooms: ['io-default', 'io-notification'],
|
|
80
|
-
authenticateTimeout: 10000,
|
|
81
|
-
pingInterval: 30000,
|
|
82
|
-
});
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
#### `TSocketIOServerOptions`
|
|
86
|
-
|
|
87
|
-
A discriminated union based on the `runtime` field:
|
|
88
|
-
|
|
89
|
-
| Option | Type | Default | Description |
|
|
90
|
-
|--------|------|---------|-------------|
|
|
91
|
-
| `identifier` | `string` | -- | Unique identifier for this server instance (used as logger scope) |
|
|
92
|
-
| `runtime` | `'node' \| 'bun'` | -- | Runtime environment. Determines which server field is required |
|
|
93
|
-
| `server` | `HTTPServer` | -- | Node.js HTTP server instance. **Required when `runtime` is `'node'`** |
|
|
94
|
-
| `engine` | `any` | -- | `@socket.io/bun-engine` Server instance. **Required when `runtime` is `'bun'`** |
|
|
95
|
-
| `serverOptions` | `Partial<ServerOptions>` | -- | **Required by the type.** Socket.IO `ServerOptions` (cors, path, transports, etc.). Pass `{}` when no options are needed; nullish values fall back to `{}` at runtime |
|
|
96
|
-
| `redisConnection` | `IRedisHelper` | -- | **Required.** Redis helper used to create pub, sub, and emitter clients |
|
|
97
|
-
| `authenticateFn` | `TSocketIOAuthenticateFn` | -- | **Required.** Called with the client's handshake data. Return `true` to accept, `false` to reject |
|
|
98
|
-
| `validateRoomFn` | `TSocketIOValidateRoomFn` | `undefined` | Called when a client requests to join rooms. Return the allowed subset |
|
|
99
|
-
| `clientConnectedFn` | `TSocketIOClientConnectedFn` | `undefined` | Called after a client is fully authenticated and has joined default rooms |
|
|
100
|
-
| `defaultRooms` | `string[]` | `['io-default', 'io-notification']` | Rooms that every authenticated client joins automatically |
|
|
101
|
-
| `authenticateTimeout` | `number` | `10000` (10 s) | Milliseconds before an unauthenticated client is disconnected |
|
|
102
|
-
| `pingInterval` | `number` | `30000` (30 s) | Interval in milliseconds between heartbeat pings to authenticated clients |
|
|
103
|
-
|
|
104
|
-
> [!WARNING]
|
|
105
|
-
> 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`.
|
|
106
|
-
|
|
107
|
-
### Client
|
|
108
|
-
|
|
109
|
-
`SocketIOClientHelper` connects to a Socket.IO server. Configuration is done entirely via the constructor -- `configure()` is called automatically.
|
|
110
|
-
|
|
111
|
-
```typescript
|
|
112
|
-
import { SocketIOClientHelper } from '@venizia/ignis-helpers/socket-io';
|
|
113
|
-
|
|
114
|
-
const socketClient = new SocketIOClientHelper({
|
|
115
|
-
identifier: 'my-client',
|
|
116
|
-
host: 'http://localhost:3000',
|
|
117
|
-
options: {
|
|
118
|
-
path: '/socket.io',
|
|
119
|
-
extraHeaders: {
|
|
120
|
-
Authorization: 'Bearer my-jwt-token',
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
onConnected: () => {
|
|
124
|
-
console.log('Connected to server');
|
|
125
|
-
socketClient.authenticate();
|
|
126
|
-
},
|
|
127
|
-
onDisconnected: (reason) => {
|
|
128
|
-
console.log('Disconnected:', reason);
|
|
129
|
-
},
|
|
130
|
-
onError: (error) => {
|
|
131
|
-
console.error('Connection error:', error);
|
|
132
|
-
},
|
|
133
|
-
onAuthenticated: () => {
|
|
134
|
-
console.log('Successfully authenticated');
|
|
135
|
-
},
|
|
136
|
-
onUnauthenticated: (message) => {
|
|
137
|
-
console.warn('Authentication failed:', message);
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
#### `ISocketIOClientOptions`
|
|
143
|
-
|
|
144
|
-
| Option | Type | Default | Description |
|
|
145
|
-
|--------|------|---------|-------------|
|
|
146
|
-
| `identifier` | `string` | -- | Unique identifier for this client (used as logger scope) |
|
|
147
|
-
| `host` | `string` | -- | Server URL to connect to (e.g., `'http://localhost:3000'`) |
|
|
148
|
-
| `options` | `IOptions` | -- | Socket.IO client options (extends `SocketOptions` with `path` and `extraHeaders`) |
|
|
149
|
-
| `onConnected` | `() => ValueOrPromise<void>` | `undefined` | Called when the transport connection is established |
|
|
150
|
-
| `onDisconnected` | `(reason: string) => ValueOrPromise<void>` | `undefined` | Called when disconnected. The client state resets to `'unauthorized'` |
|
|
151
|
-
| `onError` | `(error: Error) => ValueOrPromise<void>` | `undefined` | Called on connection errors |
|
|
152
|
-
| `onAuthenticated` | `() => ValueOrPromise<void>` | `undefined` | Called when the server sends an `authenticated` event |
|
|
153
|
-
| `onUnauthenticated` | `(message: string) => ValueOrPromise<void>` | `undefined` | Called when the server rejects authentication |
|
|
154
|
-
|
|
155
|
-
#### `IOptions`
|
|
156
|
-
|
|
157
|
-
Extends `SocketOptions` from `socket.io-client`:
|
|
158
|
-
|
|
159
|
-
| Option | Type | Description |
|
|
160
|
-
|--------|------|-------------|
|
|
161
|
-
| `path` | `string` | Socket.IO server path (e.g., `'/socket.io'`) |
|
|
162
|
-
| `extraHeaders` | `Record<string \| symbol \| number, any>` | Additional headers sent with the connection request |
|
|
163
|
-
|
|
164
|
-
## Usage
|
|
165
|
-
|
|
166
|
-
### Server Setup
|
|
167
|
-
|
|
168
|
-
After constructing the server helper, call `configure()` to initialize the Socket.IO server, set up the Redis adapter, and start listening for connections.
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
const socketServer = new SocketIOServerHelper({
|
|
172
|
-
identifier: 'my-server',
|
|
173
|
-
runtime: 'node',
|
|
174
|
-
server: httpServer,
|
|
175
|
-
redisConnection: redisHelper,
|
|
176
34
|
serverOptions: { cors: { origin: '*' } },
|
|
177
|
-
authenticateFn: async
|
|
178
|
-
return verifyToken(handshake.auth?.token);
|
|
179
|
-
},
|
|
35
|
+
authenticateFn: async handshake => !!handshake.auth?.token,
|
|
180
36
|
});
|
|
181
37
|
|
|
182
38
|
await socketServer.configure();
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
httpServer.listen(3000, () => {
|
|
186
|
-
console.log('HTTP + Socket.IO server running on port 3000');
|
|
187
|
-
});
|
|
39
|
+
httpServer.listen(3000);
|
|
188
40
|
```
|
|
189
41
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
For Bun, pass the `@socket.io/bun-engine` instance instead of an HTTP server:
|
|
42
|
+
A client connects at the transport level, then must explicitly authenticate before it can join rooms or exchange events:
|
|
193
43
|
|
|
194
44
|
```typescript
|
|
195
|
-
import {
|
|
196
|
-
|
|
197
|
-
const socketServer = new SocketIOServerHelper({
|
|
198
|
-
identifier: 'my-bun-server',
|
|
199
|
-
runtime: 'bun',
|
|
200
|
-
engine: bunEngineInstance,
|
|
201
|
-
redisConnection: redisHelper,
|
|
202
|
-
serverOptions: {},
|
|
203
|
-
authenticateFn: async (handshake) => {
|
|
204
|
-
return verifyToken(handshake.auth?.token);
|
|
205
|
-
},
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
await socketServer.configure();
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Client Connection
|
|
212
|
-
|
|
213
|
-
The client connects automatically on construction. Call `authenticate()` after the connection is established to trigger the server-side authentication flow.
|
|
45
|
+
import { SocketIOClientHelper } from '@venizia/ignis-helpers/socket-io';
|
|
214
46
|
|
|
215
|
-
```typescript
|
|
216
47
|
const client = new SocketIOClientHelper({
|
|
217
48
|
identifier: 'app-client',
|
|
218
49
|
host: 'http://localhost:3000',
|
|
219
50
|
options: {
|
|
220
51
|
path: '/socket.io',
|
|
221
|
-
extraHeaders: { Authorization: 'Bearer my-token' },
|
|
222
|
-
},
|
|
223
|
-
onConnected: () => {
|
|
224
|
-
// Connection established -- initiate authentication
|
|
225
|
-
client.authenticate();
|
|
226
|
-
},
|
|
227
|
-
onAuthenticated: () => {
|
|
228
|
-
// Now safe to subscribe and emit
|
|
229
|
-
client.joinRooms({ rooms: ['chat-room-1'] });
|
|
52
|
+
extraHeaders: { Authorization: 'Bearer my-jwt-token' },
|
|
230
53
|
},
|
|
54
|
+
onConnected: () => client.authenticate(),
|
|
55
|
+
onAuthenticated: () => client.emit({ topic: 'ready', data: {} }),
|
|
231
56
|
});
|
|
232
57
|
```
|
|
233
58
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
#### From the Server
|
|
59
|
+
## How it works
|
|
237
60
|
|
|
238
|
-
|
|
61
|
+
- **Two independent helpers.** `SocketIOServerHelper` wraps a `socket.io` `Server`; `SocketIOClientHelper` wraps a `socket.io-client` `Socket`. Both extend `BaseHelper`, and the client can talk to any `socket.io` server, not only this one.
|
|
62
|
+
- **Runtime-agnostic server.** Pass a Node.js `http.Server` for `runtime: 'node'`, or an `@socket.io/bun-engine` instance for `runtime: 'bun'`.
|
|
63
|
+
- **Redis is mandatory server-side.** `configure()` duplicates the parent `redisConnection` into three dedicated clients: `redisPub`/`redisSub` power `@socket.io/redis-adapter` for cross-instance room broadcast, and `redisEmitter` powers `@socket.io/redis-emitter` for `send()`.
|
|
64
|
+
- **Boot fails fast on a broken Redis connection.** `configure()` waits for all three clients to reach `ready` and rejects after 30 seconds if any never do, so a broken Redis connection fails boot instead of hanging it.
|
|
65
|
+
- **Authentication is a step separate from connecting.** A client connects at the transport level in state `UNAUTHORIZED`, then must emit `'authenticate'`. The server calls `authenticateFn(handshake)`; only `true` moves the client to `AUTHENTICATED` and joins it to `defaultRooms`.
|
|
66
|
+
- **Unauthenticated clients time out.** A client that never authenticates within `authenticateTimeout` is disconnected - including one whose `authenticateFn` is still pending when the timeout fires.
|
|
67
|
+
- **Heartbeat has no pong check.** Once authenticated, the server pings on `pingInterval` as a keep-alive; a silently dead connection is only caught when the underlying transport itself notices.
|
|
68
|
+
- **Custom rooms need `validateRoomFn`.** Room joins beyond `defaultRooms` are rejected unless you supply it - without it, every custom join request is dropped.
|
|
239
69
|
|
|
240
|
-
|
|
241
|
-
// Send to a specific client
|
|
242
|
-
socketServer.send({
|
|
243
|
-
destination: 'client-socket-id',
|
|
244
|
-
payload: {
|
|
245
|
-
topic: 'notification',
|
|
246
|
-
data: { message: 'Hello!' },
|
|
247
|
-
},
|
|
248
|
-
});
|
|
70
|
+
**Defaults**
|
|
249
71
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
data: { message: 'Server update in 5 minutes' },
|
|
255
|
-
},
|
|
256
|
-
});
|
|
72
|
+
| Option | Default | Purpose |
|
|
73
|
+
|--------|---------|---------|
|
|
74
|
+
| `authenticateTimeout` | 10s | Disconnects a client that never authenticates |
|
|
75
|
+
| `pingInterval` | 30s | Server heartbeat interval once authenticated |
|
|
257
76
|
|
|
258
|
-
|
|
259
|
-
socketServer.send({
|
|
260
|
-
destination: 'some-room',
|
|
261
|
-
payload: {
|
|
262
|
-
topic: 'room-event',
|
|
263
|
-
data: { action: 'update' },
|
|
264
|
-
},
|
|
265
|
-
doLog: true,
|
|
266
|
-
cb: () => {
|
|
267
|
-
console.log('Message queued');
|
|
268
|
-
},
|
|
269
|
-
});
|
|
270
|
-
```
|
|
77
|
+
## Common tasks
|
|
271
78
|
|
|
272
|
-
|
|
79
|
+
### Send a message from the server
|
|
273
80
|
|
|
274
|
-
|
|
81
|
+
`send()` goes through the Redis emitter, so it reaches the target on any server instance. Omit `destination` to broadcast to everyone.
|
|
275
82
|
|
|
276
83
|
```typescript
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
data: {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
// With logging enabled
|
|
283
|
-
client.emit({
|
|
284
|
-
topic: 'user-action',
|
|
285
|
-
data: { action: 'click', target: 'button-1' },
|
|
286
|
-
doLog: true,
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
// With callback
|
|
290
|
-
client.emit({
|
|
291
|
-
topic: 'upload-complete',
|
|
292
|
-
data: { fileId: '123' },
|
|
293
|
-
cb: () => {
|
|
294
|
-
console.log('Emit completed');
|
|
295
|
-
},
|
|
84
|
+
socketServer.send({
|
|
85
|
+
destination: 'some-room', // socket ID, room name, or omitted to broadcast
|
|
86
|
+
payload: { topic: 'notification', data: { message: 'Hello!' } },
|
|
87
|
+
callback: () => console.log('queued'),
|
|
296
88
|
});
|
|
297
89
|
```
|
|
298
90
|
|
|
299
|
-
###
|
|
300
|
-
|
|
301
|
-
#### Server-Side Event Binding
|
|
91
|
+
### Listen for a custom event
|
|
302
92
|
|
|
303
|
-
|
|
93
|
+
Register on the server with `on()`; subscribe on the client with `subscribe()`.
|
|
304
94
|
|
|
305
95
|
```typescript
|
|
306
96
|
socketServer.on({
|
|
307
97
|
topic: 'custom-event',
|
|
308
|
-
handler: (data) =>
|
|
309
|
-
console.log('Received:', data);
|
|
310
|
-
},
|
|
98
|
+
handler: (data: { userId: string }) => console.log('received:', data),
|
|
311
99
|
});
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
#### Client-Side Event Subscription
|
|
315
100
|
|
|
316
|
-
Use `subscribe()` for single events and `subscribeMany()` for batch registration.
|
|
317
|
-
|
|
318
|
-
```typescript
|
|
319
|
-
// Single event
|
|
320
101
|
client.subscribe({
|
|
321
102
|
event: 'notification',
|
|
322
|
-
handler:
|
|
323
|
-
console.log('Notification:', data);
|
|
324
|
-
},
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
// Prevent duplicate handlers (default behavior)
|
|
328
|
-
client.subscribe({
|
|
329
|
-
event: 'notification',
|
|
330
|
-
handler: (data) => { /* ... */ },
|
|
331
|
-
ignoreDuplicate: true, // Default: true -- skips if handler already exists
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
// Allow multiple handlers for same event
|
|
335
|
-
client.subscribe({
|
|
336
|
-
event: 'chat-message',
|
|
337
|
-
handler: handler1,
|
|
338
|
-
ignoreDuplicate: false,
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
// Batch subscribe
|
|
342
|
-
client.subscribeMany({
|
|
343
|
-
events: {
|
|
344
|
-
'user-joined': (data) => console.log('Joined:', data),
|
|
345
|
-
'user-left': (data) => console.log('Left:', data),
|
|
346
|
-
'typing': (data) => console.log('Typing:', data),
|
|
347
|
-
},
|
|
103
|
+
handler: data => console.log('notification:', data),
|
|
348
104
|
});
|
|
349
|
-
|
|
350
|
-
// Unsubscribe from a specific event (removes all handlers)
|
|
351
|
-
client.unsubscribe({ event: 'notification' });
|
|
352
|
-
|
|
353
|
-
// Unsubscribe from multiple events
|
|
354
|
-
client.unsubscribeMany({ events: ['user-joined', 'user-left'] });
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
### Rooms
|
|
358
|
-
|
|
359
|
-
#### Client-Side Room Operations
|
|
360
|
-
|
|
361
|
-
```typescript
|
|
362
|
-
// Join rooms (validated by server's validateRoomFn)
|
|
363
|
-
client.joinRooms({ rooms: ['chat-room-1', 'notifications'] });
|
|
364
|
-
|
|
365
|
-
// Leave rooms
|
|
366
|
-
client.leaveRooms({ rooms: ['chat-room-1'] });
|
|
367
105
|
```
|
|
368
106
|
|
|
369
|
-
|
|
107
|
+
### Manage rooms
|
|
370
108
|
|
|
371
|
-
|
|
109
|
+
Clients request rooms with `joinRooms()` / `leaveRooms()`; the server filters join requests through `validateRoomFn`.
|
|
372
110
|
|
|
373
111
|
```typescript
|
|
374
112
|
const socketServer = new SocketIOServerHelper({
|
|
375
113
|
// ...
|
|
376
114
|
defaultRooms: ['general', 'announcements'],
|
|
377
|
-
validateRoomFn: async ({ socket, rooms }) =>
|
|
378
|
-
// Only allow rooms the user has permission for
|
|
379
|
-
const userPermissions = await getUserPermissions(socket.id);
|
|
380
|
-
return rooms.filter(room => userPermissions.includes(room));
|
|
381
|
-
},
|
|
115
|
+
validateRoomFn: async ({ socket, rooms }) => rooms.filter(room => room.startsWith('public-')),
|
|
382
116
|
});
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
### Authentication Flow
|
|
386
|
-
|
|
387
|
-
The server enforces a post-connection authentication protocol:
|
|
388
117
|
|
|
118
|
+
client.joinRooms({ rooms: ['public-chat'] });
|
|
119
|
+
client.leaveRooms({ rooms: ['public-chat'] });
|
|
389
120
|
```
|
|
390
|
-
Client connects
|
|
391
|
-
|
|
|
392
|
-
v
|
|
393
|
-
Server creates client entry (state: UNAUTHORIZED)
|
|
394
|
-
|-- Starts authenticateTimeout timer (default: 10s)
|
|
395
|
-
|-- Registers disconnect handler
|
|
396
|
-
|
|
|
397
|
-
Client emits 'authenticate' event
|
|
398
|
-
|
|
|
399
|
-
v
|
|
400
|
-
Server calls authenticateFn(handshake)
|
|
401
|
-
|
|
|
402
|
-
+-- Returns true:
|
|
403
|
-
| |-- State -> AUTHENTICATED
|
|
404
|
-
| |-- Clear auth timeout
|
|
405
|
-
| |-- Join default rooms
|
|
406
|
-
| |-- Start ping interval
|
|
407
|
-
| |-- Emit 'authenticated' to client
|
|
408
|
-
| +-- Invoke clientConnectedFn
|
|
409
|
-
|
|
|
410
|
-
+-- Returns false:
|
|
411
|
-
| |-- State -> UNAUTHORIZED
|
|
412
|
-
| |-- Emit 'unauthenticated' to client
|
|
413
|
-
| +-- Disconnect
|
|
414
|
-
|
|
|
415
|
-
+-- Timeout (no auth within authenticateTimeout):
|
|
416
|
-
+-- Disconnect
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
### Redis Adapter
|
|
420
|
-
|
|
421
|
-
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:
|
|
422
121
|
|
|
423
|
-
|
|
424
|
-
- **redisSub** -- Subscribes to adapter messages
|
|
425
|
-
- **redisEmitter** -- Powers `send()` for cross-instance message delivery
|
|
426
|
-
|
|
427
|
-
All three connections are initialized and awaited during `configure()`. If the parent client uses `lazyConnect`, the duplicated clients will connect automatically.
|
|
428
|
-
|
|
429
|
-
> [!NOTE]
|
|
430
|
-
> `configure()` fails fast: if any of the three Redis clients never reaches the `ready` state, it rejects after **30 seconds** rather than hanging boot indefinitely.
|
|
122
|
+
### Emit from the client
|
|
431
123
|
|
|
432
124
|
```typescript
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
payload: {
|
|
438
|
-
topic: 'update',
|
|
439
|
-
data: { value: 42 },
|
|
440
|
-
},
|
|
125
|
+
client.emit({
|
|
126
|
+
topic: 'chat-message',
|
|
127
|
+
data: { text: 'Hello, world!' },
|
|
128
|
+
callback: () => console.log('emit completed'),
|
|
441
129
|
});
|
|
442
130
|
```
|
|
443
131
|
|
|
444
|
-
###
|
|
445
|
-
|
|
446
|
-
#### Server
|
|
447
|
-
|
|
448
|
-
```typescript
|
|
449
|
-
await socketServer.shutdown();
|
|
450
|
-
// 1. Disconnects all clients (clears intervals and timeouts)
|
|
451
|
-
// 2. Closes the IO server
|
|
452
|
-
// 3. Quits all three Redis connections (pub, sub, emitter)
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
#### Client
|
|
132
|
+
### Shut down cleanly
|
|
456
133
|
|
|
457
134
|
```typescript
|
|
458
|
-
|
|
459
|
-
//
|
|
460
|
-
// 2. Disconnects if connected
|
|
461
|
-
// 3. Resets state to 'unauthorized'
|
|
135
|
+
await socketServer.shutdown(); // disconnects clients, closes IO server, quits all 3 Redis clients
|
|
136
|
+
client.shutdown(); // removes listeners, disconnects, resets state
|
|
462
137
|
```
|
|
463
138
|
|
|
464
|
-
##
|
|
465
|
-
|
|
466
|
-
### `[SocketIOServerHelper] Invalid HTTP server for Node.js runtime!`
|
|
467
|
-
|
|
468
|
-
**Cause:** The `server` option is missing or falsy when `runtime` is `'node'`.
|
|
469
|
-
|
|
470
|
-
**Fix:** Pass a valid `http.Server` instance.
|
|
471
|
-
|
|
472
|
-
### `[SocketIOServerHelper] Invalid @socket.io/bun-engine instance for Bun runtime!`
|
|
473
|
-
|
|
474
|
-
**Cause:** The `engine` option is missing or falsy when `runtime` is `'bun'`.
|
|
475
|
-
|
|
476
|
-
**Fix:** Pass a valid `@socket.io/bun-engine` Server instance.
|
|
477
|
-
|
|
478
|
-
### `[SocketIOServerHelper] Unsupported runtime!`
|
|
479
|
-
|
|
480
|
-
**Cause:** The `runtime` value is neither `'node'` nor `'bun'`.
|
|
481
|
-
|
|
482
|
-
**Fix:** Use `RuntimeModules.NODE` or `RuntimeModules.BUN`.
|
|
483
|
-
|
|
484
|
-
### `Invalid redis connection to config socket.io adapter!`
|
|
485
|
-
|
|
486
|
-
**Cause:** The `redisConnection` option is missing, `null`, or `undefined`.
|
|
487
|
-
|
|
488
|
-
**Fix:** Pass a valid `IRedisHelper` instance (e.g., `RedisSingleHelper` or `RedisClusterHelper`).
|
|
489
|
-
|
|
490
|
-
### `[on] Invalid topic to start binding handler`
|
|
491
|
-
|
|
492
|
-
**Cause:** An empty or falsy `topic` was passed to `on()`.
|
|
493
|
-
|
|
494
|
-
**Fix:** Provide a non-empty string topic.
|
|
495
|
-
|
|
496
|
-
### `[on] IOServer is not initialized yet!`
|
|
497
|
-
|
|
498
|
-
**Cause:** `on()` was called before `configure()` completed.
|
|
499
|
-
|
|
500
|
-
**Fix:** Await `configure()` before registering event handlers.
|
|
501
|
-
|
|
502
|
-
### `Invalid socket client state to emit`
|
|
503
|
-
|
|
504
|
-
**Cause (client):** `emit()` was called when the client is not connected.
|
|
505
|
-
|
|
506
|
-
**Fix:** Check that the client is connected before emitting, or emit inside the `onConnected` callback.
|
|
507
|
-
|
|
508
|
-
### `Topic is required to emit`
|
|
509
|
-
|
|
510
|
-
**Cause (client):** `emit()` was called with an empty or falsy `topic`.
|
|
139
|
+
## See also
|
|
511
140
|
|
|
512
|
-
|
|
141
|
+
- [Full reference](./api) - every method signature, type, constant, and error case
|
|
142
|
+
- [Socket.IO Component](/extensions/components/socket-io/) - DI-managed lifecycle wrapper around this helper
|
|
143
|
+
- [WebSocket Helper](../websocket/) - Bun-native alternative with no `socket.io` dependency
|
|
144
|
+
- [Redis Helper](../redis/) - `RedisSingleHelper` / `RedisClusterHelper` used as `redisConnection`
|
|
513
145
|
|
|
514
|
-
|
|
146
|
+
**Files:**
|
|
515
147
|
|
|
516
|
-
- [
|
|
517
|
-
- [
|
|
148
|
+
- [`packages/helpers/src/modules/socket/socket-io/server/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/socket-io/server/helper.ts) - `SocketIOServerHelper`
|
|
149
|
+
- [`packages/helpers/src/modules/socket/socket-io/client/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/socket-io/client/helper.ts) - `SocketIOClientHelper`
|
|
150
|
+
- [`packages/helpers/src/modules/socket/socket-io/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/socket-io/common/types.ts) - option and callback types
|
|
151
|
+
- [`packages/helpers/src/modules/socket/socket-io/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/socket-io/common/constants.ts) - `SocketIOConstants`, `SocketIOClientStates`
|