@venizia/ignis-docs 0.2.0 → 0.2.1-1
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 +24 -13
- 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 +27 -3
- 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 +8 -4
- 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 +247 -153
- 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 +109 -323
- package/content/extensions/components/authentication/api.md +489 -602
- package/content/extensions/components/authentication/errors.md +135 -498
- package/content/extensions/components/authentication/index.md +89 -801
- package/content/extensions/components/authentication/usage.md +231 -955
- package/content/extensions/components/authorization/api.md +991 -644
- package/content/extensions/components/authorization/errors.md +204 -208
- package/content/extensions/components/authorization/getting-started.md +227 -0
- package/content/extensions/components/authorization/index.md +88 -795
- package/content/extensions/components/authorization/usage.md +219 -528
- package/content/extensions/components/health-check.md +77 -243
- package/content/extensions/components/index.md +24 -90
- package/content/extensions/components/mail/api.md +553 -285
- package/content/extensions/components/mail/errors.md +76 -62
- package/content/extensions/components/mail/index.md +111 -463
- package/content/extensions/components/mail/usage.md +139 -173
- package/content/extensions/components/request-tracker.md +70 -173
- package/content/extensions/components/socket-io/api.md +462 -785
- package/content/extensions/components/socket-io/errors.md +49 -51
- package/content/extensions/components/socket-io/index.md +69 -372
- package/content/extensions/components/socket-io/usage.md +212 -105
- package/content/extensions/components/static-asset/api.md +461 -141
- package/content/extensions/components/static-asset/errors.md +121 -53
- package/content/extensions/components/static-asset/index.md +84 -606
- package/content/extensions/components/static-asset/usage.md +184 -299
- package/content/extensions/components/template/index.md +3 -3
- package/content/extensions/components/websocket/api.md +311 -404
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +75 -407
- package/content/extensions/components/websocket/usage.md +120 -338
- package/content/extensions/helpers/cron/index.md +52 -160
- package/content/extensions/helpers/crypto/index.md +67 -480
- package/content/extensions/helpers/crypto/reference.md +528 -0
- package/content/extensions/helpers/env/index.md +64 -178
- package/content/extensions/helpers/error/index.md +286 -196
- package/content/extensions/helpers/index.md +61 -47
- package/content/extensions/helpers/inversion/index.md +76 -550
- package/content/extensions/helpers/inversion/reference.md +530 -0
- package/content/extensions/helpers/kafka/admin.md +23 -3
- package/content/extensions/helpers/kafka/compile-binary.md +83 -52
- package/content/extensions/helpers/kafka/consumer.md +65 -28
- package/content/extensions/helpers/kafka/examples.md +46 -253
- package/content/extensions/helpers/kafka/index.md +55 -617
- package/content/extensions/helpers/kafka/producer.md +156 -22
- package/content/extensions/helpers/kafka/schema-registry.md +59 -79
- package/content/extensions/helpers/logger/hf-logger.md +220 -0
- package/content/extensions/helpers/logger/index.md +78 -552
- package/content/extensions/helpers/logger/pino.md +105 -0
- package/content/extensions/helpers/logger/reference.md +937 -0
- package/content/extensions/helpers/network/api.md +276 -195
- package/content/extensions/helpers/network/index.md +87 -524
- package/content/extensions/helpers/queue/index.md +80 -897
- package/content/extensions/helpers/queue/reference.md +494 -0
- package/content/extensions/helpers/redis/index.md +86 -640
- package/content/extensions/helpers/redis/reference.md +784 -0
- package/content/extensions/helpers/secrets/index.md +136 -0
- package/content/extensions/helpers/socket-io/api.md +325 -204
- package/content/extensions/helpers/socket-io/index.md +79 -429
- package/content/extensions/helpers/storage/api.md +584 -464
- package/content/extensions/helpers/storage/index.md +80 -573
- package/content/extensions/helpers/types/index.md +75 -495
- package/content/extensions/helpers/types/reference.md +689 -0
- package/content/extensions/helpers/uid/index.md +176 -189
- package/content/extensions/helpers/websocket/api.md +366 -214
- package/content/extensions/helpers/websocket/index.md +68 -503
- package/content/extensions/helpers/worker-thread/index.md +62 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/extensions/index.md +38 -39
- package/content/extensions/src-details/mcp-server.md +96 -548
- package/content/guides/core-concepts/persistent/datasources.md +2 -2
- package/content/guides/core-concepts/persistent/index.md +5 -1
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/pglite.md +218 -0
- package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
- package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
- package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
- package/content/guides/core-concepts/persistent/sqlite.md +296 -0
- package/content/guides/core-concepts/persistent/transactions.md +12 -11
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/get-started/5-minute-quickstart.md +59 -206
- package/content/guides/get-started/philosophy.md +135 -670
- package/content/guides/get-started/setup.md +53 -74
- package/content/guides/migrations/redis-helpers-migration.md +5 -4
- package/content/guides/migrations/scoped-rbac-migration.md +5 -5
- package/content/guides/migrations/unified-connectors-migration.md +8 -7
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/guides/tutorials/realtime-chat.md +1 -1
- package/content/references/base/application.md +64 -22
- package/content/references/base/components.md +3 -3
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/controllers.md +12 -12
- package/content/references/base/datasources-reference.md +600 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +25 -46
- package/content/references/base/filter-system/application-usage.md +95 -123
- package/content/references/base/filter-system/array-operators.md +33 -43
- package/content/references/base/filter-system/comparison-operators.md +55 -63
- package/content/references/base/filter-system/default-filter.md +144 -350
- package/content/references/base/filter-system/fields-order-pagination.md +116 -148
- package/content/references/base/filter-system/index.md +114 -253
- package/content/references/base/filter-system/json-filtering.md +52 -181
- package/content/references/base/filter-system/list-operators.md +28 -44
- package/content/references/base/filter-system/logical-operators.md +69 -114
- package/content/references/base/filter-system/null-operators.md +41 -98
- package/content/references/base/filter-system/pattern-matching.md +48 -51
- package/content/references/base/filter-system/quick-reference.md +93 -196
- package/content/references/base/filter-system/range-operators.md +22 -38
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +173 -232
- package/content/references/base/grpc-controllers.md +53 -17
- package/content/references/base/index.md +5 -3
- package/content/references/base/middlewares.md +43 -28
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +81 -1452
- package/content/references/base/providers.md +8 -8
- package/content/references/base/repositories/advanced.md +281 -419
- package/content/references/base/repositories/index.md +84 -644
- package/content/references/base/repositories/mixins.md +25 -21
- package/content/references/base/repositories/relations.md +194 -375
- package/content/references/base/repositories/soft-deletable.md +67 -55
- package/content/references/base/secrets.md +267 -0
- package/content/references/base/services.md +8 -6
- package/content/references/configuration/environment-variables.md +73 -21
- package/content/references/configuration/index.md +51 -31
- package/content/references/index.md +1 -1
- 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/duration.md +85 -0
- package/content/references/utilities/index.md +6 -2
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +81 -61
- 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 +58 -218
- package/content/references/utilities/retry.md +139 -0
- 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/dist/mcp-server/index.js +0 -0
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
- package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
- package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
- package/package.json +24 -23
|
@@ -1,51 +1,23 @@
|
|
|
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
|
+
|
|
1
7
|
# Socket.IO
|
|
2
8
|
|
|
3
|
-
|
|
9
|
+
`SocketIOServerHelper` and `SocketIOClientHelper` wrap `socket.io` with a mandatory post-connection authentication handshake and room management. A Redis adapter makes sure events reach a client no matter which server instance it's connected to.
|
|
4
10
|
|
|
5
|
-
|
|
11
|
+
These are the raw classes: construct and `configure()` them yourself. Need the server helper inside an IGNIS app instead? [`SocketIOComponent`](/extensions/components/socket-io/) wires it into DI for you, on both Node.js and Bun.
|
|
6
12
|
|
|
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 |
|
|
13
|
+
## In one example
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
The smallest working server: construct with a Redis connection and an `authenticateFn`, then `configure()`.
|
|
13
16
|
|
|
14
17
|
```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
|
-
```
|
|
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/socket-io';
|
|
47
|
-
import { RedisSingleHelper } from '@venizia/ignis-helpers';
|
|
48
18
|
import { createServer } from 'node:http';
|
|
19
|
+
import { RedisSingleHelper } from '@venizia/ignis-helpers';
|
|
20
|
+
import { SocketIOServerHelper } from '@venizia/ignis-helpers/socket-io';
|
|
49
21
|
|
|
50
22
|
const httpServer = createServer();
|
|
51
23
|
|
|
@@ -61,457 +33,135 @@ const socketServer = new SocketIOServerHelper({
|
|
|
61
33
|
runtime: 'node',
|
|
62
34
|
server: httpServer,
|
|
63
35
|
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
36
|
serverOptions: { cors: { origin: '*' } },
|
|
177
|
-
authenticateFn: async
|
|
178
|
-
return verifyToken(handshake.auth?.token);
|
|
179
|
-
},
|
|
37
|
+
authenticateFn: async handshake => !!handshake.auth?.token,
|
|
180
38
|
});
|
|
181
39
|
|
|
182
40
|
await socketServer.configure();
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
httpServer.listen(3000, () => {
|
|
186
|
-
console.log('HTTP + Socket.IO server running on port 3000');
|
|
187
|
-
});
|
|
41
|
+
httpServer.listen(3000);
|
|
188
42
|
```
|
|
189
43
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
For Bun, pass the `@socket.io/bun-engine` instance instead of an HTTP server:
|
|
44
|
+
A client connects at the transport level, then must explicitly authenticate before it can join rooms or exchange events:
|
|
193
45
|
|
|
194
46
|
```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.
|
|
47
|
+
import { SocketIOClientHelper } from '@venizia/ignis-helpers/socket-io';
|
|
214
48
|
|
|
215
|
-
```typescript
|
|
216
49
|
const client = new SocketIOClientHelper({
|
|
217
50
|
identifier: 'app-client',
|
|
218
51
|
host: 'http://localhost:3000',
|
|
219
52
|
options: {
|
|
220
53
|
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'] });
|
|
54
|
+
extraHeaders: { Authorization: 'Bearer my-jwt-token' },
|
|
230
55
|
},
|
|
56
|
+
onConnected: () => client.authenticate(),
|
|
57
|
+
onAuthenticated: () => client.emit({ topic: 'ready', data: {} }),
|
|
231
58
|
});
|
|
232
59
|
```
|
|
233
60
|
|
|
234
|
-
|
|
61
|
+
## How it works
|
|
235
62
|
|
|
236
|
-
|
|
63
|
+
- **Two independent helpers, both extending `BaseHelper`:**
|
|
237
64
|
|
|
238
|
-
|
|
65
|
+
| Helper | Wraps |
|
|
66
|
+
|--------|-------|
|
|
67
|
+
| `SocketIOServerHelper` | a `socket.io` `Server` |
|
|
68
|
+
| `SocketIOClientHelper` | a `socket.io-client` `Socket` |
|
|
239
69
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
destination: 'client-socket-id',
|
|
244
|
-
payload: {
|
|
245
|
-
topic: 'notification',
|
|
246
|
-
data: { message: 'Hello!' },
|
|
247
|
-
},
|
|
248
|
-
});
|
|
70
|
+
- **The client isn't locked to this server.** It can talk to any `socket.io` server, not only this one.
|
|
71
|
+
- **Runtime-agnostic server.** Pass a Node.js `http.Server` for `runtime: 'node'`, or an `@socket.io/bun-engine` instance for `runtime: 'bun'`.
|
|
72
|
+
- **Redis is mandatory server-side.** `configure()` duplicates the parent `redisConnection` into three dedicated clients:
|
|
249
73
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
data: { message: 'Server update in 5 minutes' },
|
|
255
|
-
},
|
|
256
|
-
});
|
|
74
|
+
| Client | Powers |
|
|
75
|
+
|--------|--------|
|
|
76
|
+
| `redisPub` + `redisSub` | `@socket.io/redis-adapter` - cross-instance room broadcast |
|
|
77
|
+
| `redisEmitter` | `@socket.io/redis-emitter` - used by `send()` |
|
|
257
78
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
},
|
|
265
|
-
doLog: true,
|
|
266
|
-
cb: () => {
|
|
267
|
-
console.log('Message queued');
|
|
268
|
-
},
|
|
269
|
-
});
|
|
270
|
-
```
|
|
79
|
+
- **Boot fails fast on a broken Redis connection.** `configure()` waits for all three clients to reach `ready`. It rejects after 30 seconds if any never do, so a broken Redis connection fails boot instead of hanging it.
|
|
80
|
+
- **Authentication is a step separate from connecting.** A client connects at the transport level in state `UNAUTHORIZED`, then must emit `'authenticate'`. The server then calls `authenticateFn(handshake)`.
|
|
81
|
+
- **Only `true` authenticates.** It moves the client to `AUTHENTICATED` and joins it to `defaultRooms`.
|
|
82
|
+
- **Unauthenticated clients time out.** A client that never authenticates within `authenticateTimeout` is disconnected. That includes a client whose `authenticateFn` is still pending when the timeout fires.
|
|
83
|
+
- **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.
|
|
84
|
+
- **Custom rooms need `validateRoomFn`.** Room joins beyond `defaultRooms` are rejected unless you supply `validateRoomFn`.
|
|
271
85
|
|
|
272
|
-
|
|
86
|
+
**Defaults**
|
|
273
87
|
|
|
274
|
-
|
|
88
|
+
| Option | Default | Purpose |
|
|
89
|
+
|--------|---------|---------|
|
|
90
|
+
| `authenticateTimeout` | 10s | Disconnects a client that never authenticates |
|
|
91
|
+
| `pingInterval` | 30s | Server heartbeat interval once authenticated |
|
|
275
92
|
|
|
276
|
-
|
|
277
|
-
client.emit({
|
|
278
|
-
topic: 'chat-message',
|
|
279
|
-
data: { text: 'Hello, world!' },
|
|
280
|
-
});
|
|
93
|
+
## Common tasks
|
|
281
94
|
|
|
282
|
-
|
|
283
|
-
client.emit({
|
|
284
|
-
topic: 'user-action',
|
|
285
|
-
data: { action: 'click', target: 'button-1' },
|
|
286
|
-
doLog: true,
|
|
287
|
-
});
|
|
95
|
+
### Send a message from the server
|
|
288
96
|
|
|
289
|
-
|
|
290
|
-
client.emit({
|
|
291
|
-
topic: 'upload-complete',
|
|
292
|
-
data: { fileId: '123' },
|
|
293
|
-
cb: () => {
|
|
294
|
-
console.log('Emit completed');
|
|
295
|
-
},
|
|
296
|
-
});
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
### Listening for Events
|
|
300
|
-
|
|
301
|
-
#### Server-Side Event Binding
|
|
302
|
-
|
|
303
|
-
Use `on()` to register event handlers on the IO server instance.
|
|
97
|
+
`send()` goes through the Redis emitter, so it reaches the target on any server instance. Omit `destination` to broadcast to everyone.
|
|
304
98
|
|
|
305
99
|
```typescript
|
|
306
|
-
socketServer.
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
},
|
|
100
|
+
socketServer.send({
|
|
101
|
+
destination: 'some-room', // socket ID, room name, or omitted to broadcast
|
|
102
|
+
payload: { topic: 'notification', data: { message: 'Hello!' } },
|
|
103
|
+
callback: () => console.log('queued'),
|
|
311
104
|
});
|
|
312
105
|
```
|
|
313
106
|
|
|
314
|
-
|
|
107
|
+
### Listen for a custom event
|
|
315
108
|
|
|
316
|
-
|
|
109
|
+
Register on the server with `on()`. Subscribe on the client with `subscribe()`.
|
|
317
110
|
|
|
318
111
|
```typescript
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
handler: (data) => {
|
|
323
|
-
console.log('Notification:', data);
|
|
324
|
-
},
|
|
112
|
+
socketServer.on({
|
|
113
|
+
topic: 'custom-event',
|
|
114
|
+
handler: (data: { userId: string }) => console.log('received:', data),
|
|
325
115
|
});
|
|
326
116
|
|
|
327
|
-
// Prevent duplicate handlers (default behavior)
|
|
328
117
|
client.subscribe({
|
|
329
118
|
event: 'notification',
|
|
330
|
-
handler:
|
|
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
|
-
},
|
|
119
|
+
handler: data => console.log('notification:', data),
|
|
348
120
|
});
|
|
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
121
|
```
|
|
368
122
|
|
|
369
|
-
|
|
123
|
+
### Manage rooms
|
|
370
124
|
|
|
371
|
-
|
|
125
|
+
Clients request rooms with `joinRooms()` / `leaveRooms()`. The server filters join requests through `validateRoomFn`.
|
|
372
126
|
|
|
373
127
|
```typescript
|
|
374
128
|
const socketServer = new SocketIOServerHelper({
|
|
375
129
|
// ...
|
|
376
130
|
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
|
-
},
|
|
131
|
+
validateRoomFn: async ({ socket, rooms }) => rooms.filter(room => room.startsWith('public-')),
|
|
382
132
|
});
|
|
383
|
-
```
|
|
384
133
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
The server enforces a post-connection authentication protocol:
|
|
388
|
-
|
|
389
|
-
```
|
|
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
|
|
134
|
+
client.joinRooms({ rooms: ['public-chat'] });
|
|
135
|
+
client.leaveRooms({ rooms: ['public-chat'] });
|
|
417
136
|
```
|
|
418
137
|
|
|
419
|
-
###
|
|
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
|
-
|
|
423
|
-
- **redisPub** -- Publishes adapter messages
|
|
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.
|
|
138
|
+
### Emit from the client
|
|
431
139
|
|
|
432
140
|
```typescript
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
payload: {
|
|
438
|
-
topic: 'update',
|
|
439
|
-
data: { value: 42 },
|
|
440
|
-
},
|
|
141
|
+
client.emit({
|
|
142
|
+
topic: 'chat-message',
|
|
143
|
+
data: { text: 'Hello, world!' },
|
|
144
|
+
callback: () => console.log('emit completed'),
|
|
441
145
|
});
|
|
442
146
|
```
|
|
443
147
|
|
|
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
|
|
148
|
+
### Shut down cleanly
|
|
456
149
|
|
|
457
150
|
```typescript
|
|
458
|
-
|
|
459
|
-
//
|
|
460
|
-
// 2. Disconnects if connected
|
|
461
|
-
// 3. Resets state to 'unauthorized'
|
|
151
|
+
await socketServer.shutdown(); // disconnects clients, closes IO server, quits all 3 Redis clients
|
|
152
|
+
client.shutdown(); // removes listeners, disconnects, resets state
|
|
462
153
|
```
|
|
463
154
|
|
|
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`.
|
|
155
|
+
## See also
|
|
511
156
|
|
|
512
|
-
|
|
157
|
+
- [Full reference](./api) - every method signature, type, constant, and error case
|
|
158
|
+
- [Socket.IO Component](/extensions/components/socket-io/) - DI-managed lifecycle wrapper around this helper
|
|
159
|
+
- [WebSocket Helper](../websocket/) - Bun-native alternative with no `socket.io` dependency
|
|
160
|
+
- [Redis Helper](../redis/) - `RedisSingleHelper` / `RedisClusterHelper` used as `redisConnection`
|
|
513
161
|
|
|
514
|
-
|
|
162
|
+
**Files:**
|
|
515
163
|
|
|
516
|
-
- [
|
|
517
|
-
- [
|
|
164
|
+
- [`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`
|
|
165
|
+
- [`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`
|
|
166
|
+
- [`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
|
|
167
|
+
- [`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`
|