@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,64 +1,90 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Socket.IO - Full Reference
|
|
3
|
+
description: Complete reference for SocketIOServerHelper and SocketIOClientHelper - every option, method signature, type, constant, and error case
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
# Socket.IO - Full Reference
|
|
8
|
+
|
|
9
|
+
Exhaustive reference for `SocketIOServerHelper` and `SocketIOClientHelper`. For a readable introduction and the common tasks, start with the [Socket.IO overview](/extensions/helpers/socket-io/).
|
|
10
|
+
|
|
11
|
+
**Files:**
|
|
12
|
+
|
|
13
|
+
- [`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`
|
|
14
|
+
- [`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`
|
|
15
|
+
- [`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
|
|
16
|
+
- [`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`
|
|
17
|
+
- [`packages/helpers/src/modules/socket/socket-io/index.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/socket-io/index.ts) - barrel export (`@venizia/ignis-helpers/socket-io`)
|
|
4
18
|
|
|
5
19
|
## Architecture
|
|
6
20
|
|
|
7
21
|
```
|
|
8
22
|
SocketIOServerHelper (extends BaseHelper)
|
|
9
23
|
|
|
|
10
|
-
|--
|
|
11
|
-
| |--
|
|
12
|
-
|
|
|
24
|
+
|-- constructor(opts)
|
|
25
|
+
| |-- setRuntime(opts) # validates `server` (Node) or `engine` (Bun)
|
|
26
|
+
| +-- initRedisClients(opts.redisConnection)
|
|
27
|
+
| |-- redisPub = duplicateClient()
|
|
28
|
+
| |-- redisSub = duplicateClient()
|
|
29
|
+
| +-- redisEmitter = duplicateClient()
|
|
30
|
+
|
|
|
31
|
+
|-- configure() [async]
|
|
32
|
+
| |-- ensureRedisClientsConnecting([redisPub, redisSub, redisEmitter])
|
|
33
|
+
| |-- await Promise.all(waitForRedisReady x3) # 30s timeout per client
|
|
34
|
+
| |-- initIOServer() # new IOServer(server, serverOptions) | new IOServer() + io.bind(engine)
|
|
13
35
|
| |-- io.adapter(createAdapter(redisPub, redisSub))
|
|
14
|
-
| |-- new Emitter(redisEmitter)
|
|
15
|
-
| +-- io.on('connection', onClientConnect)
|
|
36
|
+
| |-- emitter = new Emitter(redisEmitter)
|
|
37
|
+
| +-- io.on('connection', socket => onClientConnect({ socket }))
|
|
16
38
|
|
|
|
17
39
|
|-- onClientConnect({ socket })
|
|
18
|
-
| |-- Create ISocketIOClient (state: UNAUTHORIZED)
|
|
40
|
+
| |-- Create ISocketIOClient entry (state: UNAUTHORIZED)
|
|
19
41
|
| |-- Start authenticateTimeout timer
|
|
20
42
|
| |-- Register 'disconnect' handler
|
|
21
|
-
| +--
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
| |--
|
|
25
|
-
| |--
|
|
26
|
-
|
|
|
27
|
-
| |-- Start ping interval
|
|
28
|
-
| |-- Emit 'authenticated' to client
|
|
29
|
-
| +-- Invoke clientConnectedFn
|
|
43
|
+
| +-- registerAuthHandler({ socket, handshake, clientId })
|
|
44
|
+
| +-- socket.on('authenticate', ...)
|
|
45
|
+
| |-- state -> AUTHENTICATING
|
|
46
|
+
| |-- authenticateFn(handshake)
|
|
47
|
+
| |-- true -> onClientAuthenticated({ socket })
|
|
48
|
+
| +-- false or throw -> emit 'unauthenticated', disconnect
|
|
30
49
|
|
|
|
31
|
-
|--
|
|
32
|
-
|
|
|
50
|
+
|-- onClientAuthenticated({ socket })
|
|
51
|
+
| |-- state -> AUTHENTICATED, clear authenticateTimeout
|
|
52
|
+
| |-- ping({ socket, doIgnoreAuth: true }) # immediate first ping
|
|
53
|
+
| |-- Join all defaultRooms
|
|
54
|
+
| |-- registerRoomHandlers({ socket, clientId }) # 'join' / 'leave'
|
|
55
|
+
| |-- interval = setInterval(ping, pingInterval)
|
|
56
|
+
| |-- send 'authenticated' to socket.id
|
|
57
|
+
| +-- invokeHook(clientConnectedFn)
|
|
33
58
|
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
59
|
+
|-- send({ destination?, payload, doLog?, callback? })
|
|
60
|
+
| +-- emitter.compress(true).to(destination)?.emit(topic, data)
|
|
61
|
+
|
|
|
62
|
+
+-- shutdown() [async]
|
|
63
|
+
|-- Disconnect all tracked clients (clears their timers)
|
|
64
|
+
|-- close() # io.close()
|
|
65
|
+
+-- Promise.all([redisPub.quit(), redisSub.quit(), redisEmitter.quit()])
|
|
38
66
|
|
|
39
67
|
|
|
40
68
|
SocketIOClientHelper (extends BaseHelper)
|
|
41
69
|
|
|
|
42
70
|
|-- constructor -> configure()
|
|
43
|
-
| |-- io(host, options)
|
|
44
|
-
| |-- Register 'connect'
|
|
45
|
-
| |-- Register 'disconnect'
|
|
46
|
-
| |-- Register 'connect_error'
|
|
47
|
-
| |-- Register 'authenticated'
|
|
48
|
-
| |-- Register 'unauthenticated'
|
|
49
|
-
| +-- Register 'ping'
|
|
71
|
+
| |-- client = io(host, options)
|
|
72
|
+
| |-- Register 'connect' -> onConnected
|
|
73
|
+
| |-- Register 'disconnect' -> state UNAUTHORIZED, onDisconnected
|
|
74
|
+
| |-- Register 'connect_error' -> onError
|
|
75
|
+
| |-- Register 'authenticated' -> state AUTHENTICATED, onAuthenticated
|
|
76
|
+
| |-- Register 'unauthenticated' -> state UNAUTHORIZED, onUnauthenticated
|
|
77
|
+
| +-- Register 'ping' -> debug log
|
|
50
78
|
|
|
|
51
|
-
|-- authenticate()
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|--
|
|
55
|
-
|-- emit({ topic, data, doLog?, cb? })
|
|
56
|
-
|-- joinRooms({ rooms }) -> client.emit('join', { rooms })
|
|
79
|
+
|-- authenticate() -> state AUTHENTICATING, client.emit('authenticate')
|
|
80
|
+
|-- subscribe({ event, handler, ignoreDuplicate? }) # wraps handler, tracked in wrappedHandlers
|
|
81
|
+
|-- emit({ topic, data, doLog?, callback? })
|
|
82
|
+
|-- joinRooms({ rooms }) -> client.emit('join', { rooms })
|
|
57
83
|
|-- leaveRooms({ rooms }) -> client.emit('leave', { rooms })
|
|
58
84
|
|
|
|
59
85
|
+-- shutdown()
|
|
60
86
|
|-- client.removeAllListeners()
|
|
61
|
-
|--
|
|
87
|
+
|-- disconnect() if connected
|
|
62
88
|
+-- state -> UNAUTHORIZED
|
|
63
89
|
```
|
|
64
90
|
|
|
@@ -66,25 +92,33 @@ SocketIOClientHelper (extends BaseHelper)
|
|
|
66
92
|
|
|
67
93
|
### `SocketIOServerHelper`
|
|
68
94
|
|
|
69
|
-
Extends `BaseHelper`. Manages a
|
|
95
|
+
Extends `BaseHelper`. Manages a `socket.io` server with a Redis adapter, authentication, room management, and heartbeat pings.
|
|
70
96
|
|
|
71
97
|
#### `constructor(opts: TSocketIOServerOptions)`
|
|
72
98
|
|
|
73
|
-
|
|
99
|
+
Validates the runtime-specific `server` (Node) or `engine` (Bun) field and duplicates the provided `redisConnection` into three independent clients (`redisPub`, `redisSub`, `redisEmitter`). Does **not** start the IO server - call `configure()` to complete initialization.
|
|
74
100
|
|
|
75
|
-
|
|
101
|
+
**Throws:**
|
|
102
|
+
|
|
103
|
+
- `'[SocketIOServerHelper] Invalid HTTP server for Node.js runtime!'` - `runtime: 'node'` and `server` is falsy
|
|
104
|
+
- `'[SocketIOServerHelper] Invalid @socket.io/bun-engine instance for Bun runtime!'` - `runtime: 'bun'` and `engine` is falsy
|
|
105
|
+
- `'[SocketIOServerHelper] Unsupported runtime!'` - `runtime` is neither `'node'` nor `'bun'`
|
|
106
|
+
- `'Invalid redis connection to config socket.io adapter!'` - `redisConnection` is falsy
|
|
76
107
|
|
|
77
108
|
#### `configure(): Promise<void>`
|
|
78
109
|
|
|
79
|
-
|
|
110
|
+
1. Registers `error` listeners on all three duplicated Redis clients (logged, not thrown)
|
|
111
|
+
2. Kicks off `connect()` on any client still in `'wait'` status (duplicated clients inherit `lazyConnect` from the parent and do not dial on their own)
|
|
112
|
+
3. `await Promise.all([waitForRedisReady(redisPub), waitForRedisReady(redisSub), waitForRedisReady(redisEmitter)])`
|
|
113
|
+
4. Creates the `IOServer` - `new IOServer(server, serverOptions)` for Node.js, or `new IOServer()` followed by `io.bind(engine)` for Bun
|
|
114
|
+
5. Attaches the Redis adapter via `@socket.io/redis-adapter`
|
|
115
|
+
6. Creates the Redis emitter via `@socket.io/redis-emitter`
|
|
116
|
+
7. Registers the `'connection'` handler
|
|
80
117
|
|
|
81
|
-
|
|
82
|
-
2. Creates the `IOServer` based on runtime (`new IOServer(httpServer, serverOptions)` for Node.js, or `new IOServer()` with `io.bind(engine)` for Bun)
|
|
83
|
-
3. Attaches the Redis adapter via `@socket.io/redis-adapter`
|
|
84
|
-
4. Creates a Redis emitter via `@socket.io/redis-emitter`
|
|
85
|
-
5. Registers the `'connection'` event handler
|
|
118
|
+
Must be called before `on()`, `send()`, or any server operation.
|
|
86
119
|
|
|
87
|
-
|
|
120
|
+
> [!NOTE]
|
|
121
|
+
> `waitForRedisReady` rejects after **30 seconds** if a client never reaches `ready` (or immediately on that client's `error` event), so a broken Redis connection fails `configure()` instead of hanging boot indefinitely.
|
|
88
122
|
|
|
89
123
|
#### `getIOServer(): IOServer`
|
|
90
124
|
|
|
@@ -92,144 +126,151 @@ Returns the underlying `socket.io` `Server` instance for direct access.
|
|
|
92
126
|
|
|
93
127
|
```typescript
|
|
94
128
|
const io = socketServer.getIOServer();
|
|
95
|
-
io.of('/admin').on('connection',
|
|
129
|
+
io.of('/admin').on('connection', socket => {
|
|
130
|
+
/* ... */
|
|
131
|
+
});
|
|
96
132
|
```
|
|
97
133
|
|
|
98
134
|
#### `getEngine(): any`
|
|
99
135
|
|
|
100
|
-
Returns the Bun engine instance.
|
|
136
|
+
Returns the Bun engine instance.
|
|
101
137
|
|
|
102
|
-
|
|
103
|
-
// Error: '[getEngine] Engine is only available for Bun runtime!'
|
|
104
|
-
```
|
|
138
|
+
**Throws:** `'[getEngine] Engine is only available for Bun runtime!'` - `runtime` is not `'bun'`
|
|
105
139
|
|
|
106
140
|
#### `getClients(opts?: { id?: string }): ISocketIOClient | Map<string, ISocketIOClient> | undefined`
|
|
107
141
|
|
|
108
|
-
|
|
142
|
+
Without `id`, returns the full `Map<string, ISocketIOClient>`. With `{ id }`, returns that client's entry or `undefined`.
|
|
109
143
|
|
|
110
144
|
```typescript
|
|
111
|
-
// Get all clients
|
|
112
145
|
const allClients = socketServer.getClients() as Map<string, ISocketIOClient>;
|
|
113
|
-
|
|
114
|
-
// Get a specific client by socket ID
|
|
115
146
|
const client = socketServer.getClients({ id: 'socket-id' }) as ISocketIOClient | undefined;
|
|
116
147
|
```
|
|
117
148
|
|
|
118
|
-
#### `on<HandlerArgsType, HandlerReturnType>(opts: { topic: string; handler: (...args: HandlerArgsType) => ValueOrPromise<HandlerReturnType> }): void`
|
|
149
|
+
#### `on<HandlerArgsType extends unknown[] = unknown[], HandlerReturnType = void>(opts: { topic: string; handler: (...args: HandlerArgsType) => ValueOrPromise<HandlerReturnType> }): void`
|
|
119
150
|
|
|
120
|
-
Registers an event handler on the IO server instance.
|
|
151
|
+
Registers an event handler directly on the IO server instance (`io.on(topic, handler)`).
|
|
121
152
|
|
|
122
153
|
```typescript
|
|
123
154
|
socketServer.on({
|
|
124
155
|
topic: 'custom-event',
|
|
125
|
-
handler: (data: { userId: string }) =>
|
|
126
|
-
console.log('Received:', data);
|
|
127
|
-
},
|
|
156
|
+
handler: (data: { userId: string }) => console.log('received:', data),
|
|
128
157
|
});
|
|
129
158
|
```
|
|
130
159
|
|
|
131
160
|
**Throws:**
|
|
132
|
-
|
|
133
|
-
- `'[on] Invalid
|
|
134
|
-
- `'[on]
|
|
161
|
+
|
|
162
|
+
- `'[on] Invalid topic to start binding handler'` - `topic` is empty/falsy
|
|
163
|
+
- `'[on] Invalid event handler | topic: {topic}'` - `handler` is missing
|
|
164
|
+
- `'[on] IOServer is not initialized yet!'` - called before `configure()` completes
|
|
135
165
|
|
|
136
166
|
#### `onClientConnect(opts: { socket: IOSocket }): void`
|
|
137
167
|
|
|
138
|
-
Handles a new socket connection.
|
|
168
|
+
Handles a new socket connection. Invoked automatically by the `'connection'` event; can also be called manually.
|
|
139
169
|
|
|
140
170
|
1. Creates an `ISocketIOClient` entry with state `UNAUTHORIZED`
|
|
141
171
|
2. Starts the `authenticateTimeout` timer
|
|
142
172
|
3. Registers `'disconnect'` and `'authenticate'` handlers on the socket
|
|
143
173
|
|
|
144
|
-
|
|
174
|
+
No-op if `socket` is falsy or the client ID already exists in the registry.
|
|
145
175
|
|
|
146
176
|
#### `onClientAuthenticated(opts: { socket: IOSocket }): void`
|
|
147
177
|
|
|
148
|
-
|
|
178
|
+
Runs after successful authentication. Can also be called manually to authenticate a client programmatically.
|
|
179
|
+
|
|
180
|
+
1. Sets state to `AUTHENTICATED`
|
|
181
|
+
2. Clears and unsets the `authenticateTimeout` timer
|
|
182
|
+
3. Sends an immediate ping (`doIgnoreAuth: true`)
|
|
183
|
+
4. Joins all `defaultRooms`
|
|
184
|
+
5. Registers room handlers (`'join'`, `'leave'`)
|
|
185
|
+
6. Starts the periodic ping `interval`
|
|
186
|
+
7. Emits `'authenticated'` to the client with `{ id, time }`
|
|
187
|
+
8. Invokes `clientConnectedFn` - errors are caught and logged, never surfaced to the caller
|
|
149
188
|
|
|
150
|
-
|
|
151
|
-
2. Sends an initial ping
|
|
152
|
-
3. Joins all `defaultRooms`
|
|
153
|
-
4. Registers room handlers (`join`, `leave`)
|
|
154
|
-
5. Starts the periodic ping interval
|
|
155
|
-
6. Emits `'authenticated'` event to the client with `{ id, time }`
|
|
156
|
-
7. Invokes `clientConnectedFn` callback (errors caught and logged)
|
|
189
|
+
If the client entry no longer exists (disconnected mid-flow), logs and calls `disconnect({ socket })` instead.
|
|
157
190
|
|
|
158
191
|
#### `ping(opts: { socket: IOSocket; doIgnoreAuth: boolean }): void`
|
|
159
192
|
|
|
160
|
-
|
|
193
|
+
Emits `'ping'` to the client with `{ time: ISO string }`.
|
|
161
194
|
|
|
162
|
-
- If `doIgnoreAuth` is `false` and the client is not
|
|
163
|
-
-
|
|
195
|
+
- If `doIgnoreAuth` is `false` and the client is not `AUTHENTICATED`, the client is disconnected instead of pinged
|
|
196
|
+
- No-op if the socket or the tracked client is not found
|
|
164
197
|
|
|
165
198
|
#### `disconnect(opts: { socket: IOSocket }): void`
|
|
166
199
|
|
|
167
|
-
Disconnects a client and cleans up internal state:
|
|
168
|
-
|
|
169
200
|
1. Clears the ping `interval` timer
|
|
170
201
|
2. Clears the `authenticateTimeout` timer
|
|
171
|
-
3. Removes the client from the
|
|
202
|
+
3. Removes the client from the registry
|
|
172
203
|
4. Calls `socket.disconnect()`
|
|
173
204
|
|
|
174
|
-
|
|
205
|
+
No-op if `socket` is falsy.
|
|
206
|
+
|
|
207
|
+
#### `send(opts: { destination?: string; payload: { topic: string; data: any }; doLog?: boolean; callback?: () => void }): void`
|
|
175
208
|
|
|
176
|
-
Emits
|
|
209
|
+
Emits through the Redis emitter with compression enabled (`emitter.compress(true)`), reaching clients on **any** server instance.
|
|
177
210
|
|
|
178
211
|
| Parameter | Type | Description |
|
|
179
212
|
|-----------|------|-------------|
|
|
180
|
-
| `destination` | `string \| undefined` | Socket ID or room name.
|
|
213
|
+
| `destination` | `string \| undefined` | Socket ID or room name. Omit to broadcast to all clients |
|
|
181
214
|
| `payload.topic` | `string` | Event name |
|
|
182
215
|
| `payload.data` | `any` | Event payload |
|
|
183
|
-
| `doLog` | `boolean` |
|
|
184
|
-
| `
|
|
216
|
+
| `doLog` | `boolean` | Logs the message details. Default: `false` |
|
|
217
|
+
| `callback` | `() => void` | Invoked via `setImmediate` after the emit call returns |
|
|
185
218
|
|
|
186
|
-
|
|
219
|
+
No-op if `payload`, `payload.topic`, or `payload.data` is falsy. Logs an error and returns if called before `configure()` (the Redis emitter does not exist yet).
|
|
187
220
|
|
|
188
|
-
|
|
221
|
+
```typescript
|
|
222
|
+
socketServer.send({
|
|
223
|
+
destination: 'some-room',
|
|
224
|
+
payload: { topic: 'update', data: { value: 42 } },
|
|
225
|
+
callback: () => console.log('queued'),
|
|
226
|
+
});
|
|
227
|
+
```
|
|
189
228
|
|
|
190
|
-
|
|
229
|
+
#### `shutdown(): Promise<void>`
|
|
191
230
|
|
|
192
|
-
1. Disconnects
|
|
231
|
+
1. Disconnects every tracked client, clearing its `interval` and `authenticateTimeout`
|
|
193
232
|
2. Clears the `clients` map
|
|
194
|
-
3. Closes the IO server
|
|
195
|
-
4.
|
|
233
|
+
3. Closes the IO server (no-op if `configure()` never ran)
|
|
234
|
+
4. `await Promise.all([redisPub.quit(), redisSub.quit(), redisEmitter.quit()])` - runs even if closing the IO server rejects, so the three Redis sockets never leak
|
|
196
235
|
|
|
197
236
|
## Client API
|
|
198
237
|
|
|
199
238
|
### `SocketIOClientHelper`
|
|
200
239
|
|
|
201
|
-
Extends `BaseHelper`. Manages a
|
|
240
|
+
Extends `BaseHelper`. Manages a `socket.io-client` connection with authentication, event subscriptions, and room operations.
|
|
202
241
|
|
|
203
242
|
#### `constructor(opts: ISocketIOClientOptions)`
|
|
204
243
|
|
|
205
|
-
|
|
244
|
+
Stores the callbacks and calls `configure()` internally - the client starts connecting immediately.
|
|
206
245
|
|
|
207
246
|
#### `configure(): void`
|
|
208
247
|
|
|
209
|
-
|
|
248
|
+
Creates the `socket.io-client` connection (`io(host, options)`) and registers the internal lifecycle handlers below. Called automatically by the constructor; a second call is a no-op if a client instance already exists.
|
|
210
249
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
250
|
+
| Event | Behavior |
|
|
251
|
+
|-------|----------|
|
|
252
|
+
| `'connect'` | Invokes `onConnected` |
|
|
253
|
+
| `'disconnect'` | State -> `UNAUTHORIZED`, invokes `onDisconnected(reason)` |
|
|
254
|
+
| `'connect_error'` | Invokes `onError(error)` |
|
|
255
|
+
| `'authenticated'` | State -> `AUTHENTICATED`, invokes `onAuthenticated` |
|
|
256
|
+
| `'unauthenticated'` | State -> `UNAUTHORIZED`, invokes `onUnauthenticated(message)` |
|
|
257
|
+
| `'ping'` | Logs a debug message only |
|
|
258
|
+
|
|
259
|
+
Every callback invocation is wrapped so a rejected promise is caught and logged, never thrown.
|
|
218
260
|
|
|
219
261
|
#### `getState(): TSocketIOClientState`
|
|
220
262
|
|
|
221
|
-
Returns
|
|
263
|
+
Returns `'unauthorized'`, `'authenticating'`, or `'authenticated'`.
|
|
222
264
|
|
|
223
265
|
```typescript
|
|
224
|
-
|
|
225
|
-
if (state === 'authenticated') {
|
|
266
|
+
if (client.getState() === 'authenticated') {
|
|
226
267
|
client.emit({ topic: 'message', data: { text: 'hello' } });
|
|
227
268
|
}
|
|
228
269
|
```
|
|
229
270
|
|
|
230
271
|
#### `getSocketClient(): Socket`
|
|
231
272
|
|
|
232
|
-
Returns the underlying `socket.io-client` `Socket` instance
|
|
273
|
+
Returns the underlying `socket.io-client` `Socket` instance.
|
|
233
274
|
|
|
234
275
|
```typescript
|
|
235
276
|
const rawSocket = client.getSocketClient();
|
|
@@ -238,143 +279,175 @@ rawSocket.io.opts.reconnection = false;
|
|
|
238
279
|
|
|
239
280
|
#### `authenticate(): void`
|
|
240
281
|
|
|
241
|
-
|
|
282
|
+
Emits `'authenticate'` to the server to start the handshake.
|
|
242
283
|
|
|
243
|
-
-
|
|
244
|
-
-
|
|
284
|
+
- No-op if the client is not connected
|
|
285
|
+
- No-op if the current state is not `'unauthorized'`
|
|
245
286
|
- Sets state to `AUTHENTICATING` before emitting
|
|
246
287
|
|
|
247
288
|
```typescript
|
|
248
|
-
// Typically called inside the onConnected callback
|
|
249
289
|
const client = new SocketIOClientHelper({
|
|
250
290
|
// ...
|
|
251
|
-
onConnected: () =>
|
|
252
|
-
client.authenticate();
|
|
253
|
-
},
|
|
291
|
+
onConnected: () => client.authenticate(),
|
|
254
292
|
});
|
|
255
293
|
```
|
|
256
294
|
|
|
257
295
|
#### `subscribe<T>(opts: { event: string; handler: TSocketIOEventHandler<T>; ignoreDuplicate?: boolean }): void`
|
|
258
296
|
|
|
259
|
-
Registers
|
|
297
|
+
Registers a handler on the client socket. The handler is wrapped with error handling that catches both sync throws and async rejections, and the wrapper is tracked in an internal `wrappedHandlers` map so `unsubscribe()` can find and remove it.
|
|
260
298
|
|
|
261
299
|
| Parameter | Type | Default | Description |
|
|
262
300
|
|-----------|------|---------|-------------|
|
|
263
|
-
| `event` | `string` |
|
|
264
|
-
| `handler` | `TSocketIOEventHandler<T>` |
|
|
265
|
-
| `ignoreDuplicate` | `boolean` | `true` |
|
|
301
|
+
| `event` | `string` | - | Event name to listen for |
|
|
302
|
+
| `handler` | `TSocketIOEventHandler<T>` | - | Callback receiving the event data |
|
|
303
|
+
| `ignoreDuplicate` | `boolean` | `true` | Skip registration if a handler already exists for this event |
|
|
266
304
|
|
|
267
305
|
#### `subscribeMany(opts: { events: Record<string, TSocketIOEventHandler>; ignoreDuplicate?: boolean }): void`
|
|
268
306
|
|
|
269
|
-
|
|
307
|
+
Calls `subscribe()` for each entry in `events`.
|
|
270
308
|
|
|
271
309
|
```typescript
|
|
272
310
|
client.subscribeMany({
|
|
273
311
|
events: {
|
|
274
|
-
'
|
|
275
|
-
'
|
|
312
|
+
'user-joined': data => console.log('joined:', data),
|
|
313
|
+
'user-left': data => console.log('left:', data),
|
|
276
314
|
},
|
|
277
|
-
ignoreDuplicate: false,
|
|
278
315
|
});
|
|
279
316
|
```
|
|
280
317
|
|
|
281
318
|
#### `unsubscribe(opts: { event: string; handler?: TSocketIOEventHandler }): void`
|
|
282
319
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
-
|
|
286
|
-
- If `handler` is omitted, removes **all** handlers for the event
|
|
287
|
-
- If no listeners exist for the event, returns early (no-op)
|
|
320
|
+
- With `handler`, removes only that specific handler (looked up in `wrappedHandlers`; no-op if it was not registered through this helper)
|
|
321
|
+
- Without `handler`, removes **all** handlers for the event
|
|
322
|
+
- No-op if the event has no listeners
|
|
288
323
|
|
|
289
324
|
#### `unsubscribeMany(opts: { events: string[] }): void`
|
|
290
325
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
```typescript
|
|
294
|
-
client.unsubscribeMany({ events: ['event-a', 'event-b'] });
|
|
295
|
-
```
|
|
326
|
+
Calls `unsubscribe()` for each event, removing all handlers for each.
|
|
296
327
|
|
|
297
328
|
#### `connect(): void`
|
|
298
329
|
|
|
299
|
-
Manually
|
|
300
|
-
|
|
301
|
-
Returns early if the client instance does not exist.
|
|
330
|
+
Manually reconnects the client socket. No-op if the client instance does not exist (i.e. `configure()` never ran).
|
|
302
331
|
|
|
303
332
|
#### `disconnect(): void`
|
|
304
333
|
|
|
305
|
-
Manually disconnects the client socket without
|
|
306
|
-
|
|
307
|
-
Returns early if the client instance does not exist.
|
|
334
|
+
Manually disconnects the client socket without removing listeners or resetting state. No-op if the client instance does not exist.
|
|
308
335
|
|
|
309
336
|
> [!TIP]
|
|
310
|
-
> Use `shutdown()`
|
|
337
|
+
> Use `shutdown()` for a full cleanup that also removes listeners and resets the authentication state.
|
|
311
338
|
|
|
312
|
-
#### `emit<T>(opts: { topic: string; data: T; doLog?: boolean;
|
|
339
|
+
#### `emit<T>(opts: { topic: string; data: T; doLog?: boolean; callback?: () => void }): void`
|
|
313
340
|
|
|
314
341
|
Emits an event to the server.
|
|
315
342
|
|
|
316
343
|
| Parameter | Type | Default | Description |
|
|
317
344
|
|-----------|------|---------|-------------|
|
|
318
|
-
| `topic` | `string` |
|
|
319
|
-
| `data` | `T` |
|
|
320
|
-
| `doLog` | `boolean` | `false` |
|
|
321
|
-
| `
|
|
345
|
+
| `topic` | `string` | - | Event name |
|
|
346
|
+
| `data` | `T` | - | Event payload |
|
|
347
|
+
| `doLog` | `boolean` | `false` | Logs the emission details |
|
|
348
|
+
| `callback` | `() => void` | `undefined` | Invoked via `setImmediate` after the emit call |
|
|
322
349
|
|
|
323
350
|
**Throws:**
|
|
324
|
-
- `'Invalid socket client state to emit'` (status 400) -- if the client is not connected
|
|
325
|
-
- `'Topic is required to emit'` (status 400) -- if `topic` is empty/falsy
|
|
326
351
|
|
|
327
|
-
|
|
352
|
+
- `'Invalid socket client state to emit'` (status 400) - the client is not connected
|
|
353
|
+
- `'Topic is required to emit'` (status 400) - `topic` is empty/falsy
|
|
328
354
|
|
|
329
|
-
|
|
355
|
+
#### `joinRooms(opts: { rooms: string[] }): void`
|
|
330
356
|
|
|
331
|
-
Logs a warning and
|
|
357
|
+
Emits `'join'` with `{ rooms }`; the server validates the request through its `validateRoomFn`. Logs a warning and no-ops if the client is not connected.
|
|
332
358
|
|
|
333
359
|
#### `leaveRooms(opts: { rooms: string[] }): void`
|
|
334
360
|
|
|
335
|
-
|
|
361
|
+
Emits `'leave'` with `{ rooms }`. Logs a warning and no-ops if the client is not connected.
|
|
336
362
|
|
|
337
|
-
|
|
363
|
+
> [!NOTE]
|
|
364
|
+
> Unlike `joinRooms()`, leave requests are **not** validated server-side - the client can request to leave any room name, including ones it never joined (`socket.leave()` on a room the socket isn't in is a no-op).
|
|
338
365
|
|
|
339
366
|
#### `shutdown(): void`
|
|
340
367
|
|
|
341
|
-
Fully shuts down the client:
|
|
342
|
-
|
|
343
368
|
1. Removes all event listeners (`removeAllListeners()`)
|
|
344
369
|
2. Disconnects if currently connected
|
|
345
370
|
3. Resets state to `UNAUTHORIZED`
|
|
346
371
|
|
|
372
|
+
## Authentication Protocol
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
Client connects (transport only)
|
|
376
|
+
|
|
|
377
|
+
v
|
|
378
|
+
Server creates client entry (state: UNAUTHORIZED)
|
|
379
|
+
|-- Starts authenticateTimeout timer (default: 10s)
|
|
380
|
+
|-- Registers 'disconnect' handler
|
|
381
|
+
|
|
|
382
|
+
Client emits 'authenticate'
|
|
383
|
+
|
|
|
384
|
+
v
|
|
385
|
+
State -> AUTHENTICATING
|
|
386
|
+
Server calls authenticateFn(handshake)
|
|
387
|
+
|
|
|
388
|
+
+-- Resolves true:
|
|
389
|
+
| |-- State -> AUTHENTICATED, authenticateTimeout cleared
|
|
390
|
+
| |-- Immediate ping, join defaultRooms, start ping interval
|
|
391
|
+
| |-- Emit 'authenticated' { id, time } to client
|
|
392
|
+
| +-- Invoke clientConnectedFn (errors caught and logged)
|
|
393
|
+
|
|
|
394
|
+
+-- Resolves false:
|
|
395
|
+
| |-- State -> UNAUTHORIZED
|
|
396
|
+
| |-- Emit 'unauthenticated' { message: 'Invalid token to authenticate! Please login again!', time }
|
|
397
|
+
| +-- Disconnect (via the emit callback)
|
|
398
|
+
|
|
|
399
|
+
+-- Throws / rejects:
|
|
400
|
+
| |-- State -> UNAUTHORIZED
|
|
401
|
+
| |-- Emit 'unauthenticated' { message: 'Failed to authenticate connection! Please login again!', time }
|
|
402
|
+
| +-- Disconnect (via the emit callback), logged with doLog: true
|
|
403
|
+
|
|
|
404
|
+
+-- authenticateTimeout elapses first:
|
|
405
|
+
+-- Disconnect - including a client whose authenticateFn is still pending;
|
|
406
|
+
its eventual resolution finds the client already removed and is a no-op
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
A client that re-emits `'authenticate'` while already `AUTHENTICATING` or `AUTHENTICATED` is ignored (logged as a warning, `authenticateFn` is not called again).
|
|
410
|
+
|
|
411
|
+
## Redis Adapter
|
|
412
|
+
|
|
413
|
+
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`:
|
|
414
|
+
|
|
415
|
+
| Client | Purpose |
|
|
416
|
+
|--------|---------|
|
|
417
|
+
| `redisPub` | Publishes adapter messages (room membership, cross-instance socket.io internals) |
|
|
418
|
+
| `redisSub` | Subscribes to adapter messages |
|
|
419
|
+
| `redisEmitter` | Powers `send()` for cross-instance message delivery |
|
|
420
|
+
|
|
421
|
+
- **Initialized during `configure()`.** All three clients are created and awaited before the IO server starts.
|
|
422
|
+
- **Lazy connect handled explicitly.** If the parent `redisConnection` uses `lazyConnect` (`autoConnect: false`), the duplicated clients are kicked into `connect()` explicitly, since duplicated clients inherit `lazyConnect` but never dial on their own.
|
|
423
|
+
|
|
347
424
|
## Types Reference
|
|
348
425
|
|
|
349
426
|
### `IHandshake`
|
|
350
427
|
|
|
351
|
-
Represents the client handshake data available during authentication.
|
|
352
|
-
|
|
353
428
|
```typescript
|
|
354
429
|
interface IHandshake {
|
|
355
|
-
headers: IncomingHttpHeaders;
|
|
356
|
-
time: string;
|
|
357
|
-
address: string;
|
|
358
|
-
xdomain: boolean;
|
|
359
|
-
secure: boolean;
|
|
360
|
-
issued: number;
|
|
361
|
-
url: string;
|
|
362
|
-
query: ParsedUrlQuery;
|
|
363
|
-
auth: { [key: string]: any };
|
|
430
|
+
headers: IncomingHttpHeaders;
|
|
431
|
+
time: string;
|
|
432
|
+
address: string;
|
|
433
|
+
xdomain: boolean;
|
|
434
|
+
secure: boolean;
|
|
435
|
+
issued: number;
|
|
436
|
+
url: string;
|
|
437
|
+
query: ParsedUrlQuery;
|
|
438
|
+
auth: { [key: string]: any };
|
|
364
439
|
}
|
|
365
440
|
```
|
|
366
441
|
|
|
367
442
|
### `ISocketIOClient`
|
|
368
443
|
|
|
369
|
-
Internal representation of a connected client tracked by the server.
|
|
370
|
-
|
|
371
444
|
```typescript
|
|
372
445
|
interface ISocketIOClient {
|
|
373
|
-
id: string;
|
|
374
|
-
socket: IOSocket;
|
|
375
|
-
state: TSocketIOClientState;
|
|
376
|
-
interval?: NodeJS.Timeout;
|
|
377
|
-
authenticateTimeout
|
|
446
|
+
id: string;
|
|
447
|
+
socket: IOSocket;
|
|
448
|
+
state: TSocketIOClientState;
|
|
449
|
+
interval?: NodeJS.Timeout; // Ping interval timer, set after authentication
|
|
450
|
+
authenticateTimeout?: NodeJS.Timeout; // Cleared (set to undefined) after authentication
|
|
378
451
|
}
|
|
379
452
|
```
|
|
380
453
|
|
|
@@ -386,44 +459,31 @@ type TSocketIOClientState = 'unauthorized' | 'authenticating' | 'authenticated';
|
|
|
386
459
|
|
|
387
460
|
### `TSocketIOAuthenticateFn`
|
|
388
461
|
|
|
389
|
-
Server-side authentication callback. Receives the handshake data and returns a boolean indicating whether to accept or reject the connection.
|
|
390
|
-
|
|
391
462
|
```typescript
|
|
392
463
|
type TSocketIOAuthenticateFn = (args: IHandshake) => ValueOrPromise<boolean>;
|
|
393
464
|
```
|
|
394
465
|
|
|
395
466
|
### `TSocketIOValidateRoomFn`
|
|
396
467
|
|
|
397
|
-
Server-side room validation callback. Receives the socket and requested rooms, returns the subset of rooms the client is allowed to join.
|
|
398
|
-
|
|
399
468
|
```typescript
|
|
400
|
-
type TSocketIOValidateRoomFn = (opts: {
|
|
401
|
-
socket: IOSocket;
|
|
402
|
-
rooms: string[];
|
|
403
|
-
}) => ValueOrPromise<string[]>;
|
|
469
|
+
type TSocketIOValidateRoomFn = (opts: { socket: IOSocket; rooms: string[] }) => ValueOrPromise<string[]>;
|
|
404
470
|
```
|
|
405
471
|
|
|
406
472
|
### `TSocketIOClientConnectedFn`
|
|
407
473
|
|
|
408
|
-
Server-side callback invoked after a client is fully authenticated and has joined default rooms.
|
|
409
|
-
|
|
410
474
|
```typescript
|
|
411
|
-
type TSocketIOClientConnectedFn = (opts: {
|
|
412
|
-
socket: IOSocket;
|
|
413
|
-
}) => ValueOrPromise<void>;
|
|
475
|
+
type TSocketIOClientConnectedFn = (opts: { socket: IOSocket }) => ValueOrPromise<void>;
|
|
414
476
|
```
|
|
415
477
|
|
|
416
478
|
### `TSocketIOEventHandler<T>`
|
|
417
479
|
|
|
418
|
-
Client-side event handler type.
|
|
419
|
-
|
|
420
480
|
```typescript
|
|
421
481
|
type TSocketIOEventHandler<T = unknown> = (data: T) => ValueOrPromise<void>;
|
|
422
482
|
```
|
|
423
483
|
|
|
424
484
|
### `IOptions`
|
|
425
485
|
|
|
426
|
-
Client connection options. Extends `SocketOptions` from `socket.io-client
|
|
486
|
+
Client connection options. Extends `SocketOptions` from `socket.io-client`; `path` and `extraHeaders` are required by the type.
|
|
427
487
|
|
|
428
488
|
```typescript
|
|
429
489
|
interface IOptions extends SocketOptions {
|
|
@@ -432,24 +492,53 @@ interface IOptions extends SocketOptions {
|
|
|
432
492
|
}
|
|
433
493
|
```
|
|
434
494
|
|
|
495
|
+
### `ISocketIOClientOptions`
|
|
496
|
+
|
|
497
|
+
| Option | Type | Default | Description |
|
|
498
|
+
|--------|------|---------|-------------|
|
|
499
|
+
| `identifier` | `string` | - | Unique identifier for this client (used as logger scope) |
|
|
500
|
+
| `host` | `string` | - | Server URL to connect to (e.g. `'http://localhost:3000'`) |
|
|
501
|
+
| `options` | `IOptions` | - | Socket.IO client options |
|
|
502
|
+
| `onConnected` | `() => ValueOrPromise<void>` | `undefined` | Called when the transport connection is established |
|
|
503
|
+
| `onDisconnected` | `(reason: string) => ValueOrPromise<void>` | `undefined` | Called on disconnect; state resets to `'unauthorized'` first |
|
|
504
|
+
| `onError` | `(error: Error) => ValueOrPromise<void>` | `undefined` | Called on `'connect_error'` |
|
|
505
|
+
| `onAuthenticated` | `() => ValueOrPromise<void>` | `undefined` | Called when the server confirms authentication |
|
|
506
|
+
| `onUnauthenticated` | `(message: string) => ValueOrPromise<void>` | `undefined` | Called when the server rejects authentication |
|
|
507
|
+
|
|
435
508
|
### `TSocketIOServerOptions`
|
|
436
509
|
|
|
437
|
-
Discriminated union
|
|
510
|
+
Discriminated union on `runtime`.
|
|
438
511
|
|
|
439
512
|
```typescript
|
|
440
513
|
type TSocketIOServerOptions = ISocketIOServerNodeOptions | ISocketIOServerBunOptions;
|
|
441
514
|
|
|
515
|
+
interface ISocketIOServerBaseOptions {
|
|
516
|
+
identifier: string;
|
|
517
|
+
serverOptions: Partial<ServerOptions>; // socket.io ServerOptions - required by the type, pass {} if unused
|
|
518
|
+
redisConnection: IRedisHelper;
|
|
519
|
+
defaultRooms?: string[]; // Default: ['io-default', 'io-notification']
|
|
520
|
+
authenticateTimeout?: number; // Default: 10000 (10s)
|
|
521
|
+
pingInterval?: number; // Default: 30000 (30s)
|
|
522
|
+
|
|
523
|
+
authenticateFn: TSocketIOAuthenticateFn;
|
|
524
|
+
validateRoomFn?: TSocketIOValidateRoomFn;
|
|
525
|
+
clientConnectedFn?: TSocketIOClientConnectedFn;
|
|
526
|
+
}
|
|
527
|
+
|
|
442
528
|
interface ISocketIOServerNodeOptions extends ISocketIOServerBaseOptions {
|
|
443
529
|
runtime: 'node';
|
|
444
|
-
server: HTTPServer;
|
|
530
|
+
server: HTTPServer; // node:http Server
|
|
445
531
|
}
|
|
446
532
|
|
|
447
533
|
interface ISocketIOServerBunOptions extends ISocketIOServerBaseOptions {
|
|
448
534
|
runtime: 'bun';
|
|
449
|
-
engine: any;
|
|
535
|
+
engine: any; // @socket.io/bun-engine Server instance - typed `any` since it's an optional peer dep
|
|
450
536
|
}
|
|
451
537
|
```
|
|
452
538
|
|
|
539
|
+
> [!WARNING]
|
|
540
|
+
> If no `validateRoomFn` is provided, **all** custom room join requests are rejected with a warning log. Clients still get `defaultRooms` automatically.
|
|
541
|
+
|
|
453
542
|
## Constants
|
|
454
543
|
|
|
455
544
|
### `SocketIOConstants`
|
|
@@ -458,12 +547,12 @@ interface ISocketIOServerBunOptions extends ISocketIOServerBaseOptions {
|
|
|
458
547
|
|----------|-------|-------------|
|
|
459
548
|
| `EVENT_PING` | `'ping'` | Heartbeat event emitted by the server at `pingInterval` |
|
|
460
549
|
| `EVENT_CONNECT` | `'connection'` | Server-side connection event |
|
|
461
|
-
| `EVENT_DISCONNECT` | `'disconnect'` | Disconnect event (
|
|
550
|
+
| `EVENT_DISCONNECT` | `'disconnect'` | Disconnect event (server and client) |
|
|
462
551
|
| `EVENT_JOIN` | `'join'` | Room join request event |
|
|
463
552
|
| `EVENT_LEAVE` | `'leave'` | Room leave request event |
|
|
464
|
-
| `EVENT_AUTHENTICATE` | `'authenticate'` | Client
|
|
465
|
-
| `EVENT_AUTHENTICATED` | `'authenticated'` | Server
|
|
466
|
-
| `EVENT_UNAUTHENTICATE` | `'unauthenticated'` | Server
|
|
553
|
+
| `EVENT_AUTHENTICATE` | `'authenticate'` | Client -> server authentication request |
|
|
554
|
+
| `EVENT_AUTHENTICATED` | `'authenticated'` | Server -> client authentication success |
|
|
555
|
+
| `EVENT_UNAUTHENTICATE` | `'unauthenticated'` | Server -> client authentication failure |
|
|
467
556
|
| `ROOM_DEFAULT` | `'io-default'` | Default room name |
|
|
468
557
|
| `ROOM_NOTIFICATION` | `'io-notification'` | Default notification room name |
|
|
469
558
|
|
|
@@ -475,23 +564,36 @@ interface ISocketIOServerBunOptions extends ISocketIOServerBaseOptions {
|
|
|
475
564
|
| `AUTHENTICATING` | `'authenticating'` | Authentication in progress |
|
|
476
565
|
| `AUTHENTICATED` | `'authenticated'` | Successfully authenticated |
|
|
477
566
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
Static method that checks whether a string is a valid client state value.
|
|
567
|
+
`SocketIOClientStates.isValid(input: string): input is TConstValue<typeof SocketIOClientStates>` - checks membership against the backing `SCHEME_SET`.
|
|
481
568
|
|
|
482
569
|
```typescript
|
|
483
570
|
SocketIOClientStates.isValid('authenticated'); // true
|
|
484
571
|
SocketIOClientStates.isValid('invalid'); // false
|
|
485
572
|
```
|
|
486
573
|
|
|
487
|
-
### Internal
|
|
574
|
+
### Internal defaults
|
|
488
575
|
|
|
489
576
|
| Constant | Value | Description |
|
|
490
577
|
|----------|-------|-------------|
|
|
491
|
-
| `CLIENT_AUTHENTICATE_TIMEOUT` | `
|
|
492
|
-
| `CLIENT_PING_INTERVAL` | `
|
|
493
|
-
|
|
494
|
-
##
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
578
|
+
| `CLIENT_AUTHENTICATE_TIMEOUT` | `10_000` (10s) | Default `authenticateTimeout` |
|
|
579
|
+
| `CLIENT_PING_INTERVAL` | `30_000` (30s) | Default `pingInterval` |
|
|
580
|
+
|
|
581
|
+
## Common Errors
|
|
582
|
+
|
|
583
|
+
| Error | Cause | Fix |
|
|
584
|
+
|-------|-------|-----|
|
|
585
|
+
| `[SocketIOServerHelper] Invalid HTTP server for Node.js runtime!` | `server` missing/falsy with `runtime: 'node'` | Pass a valid `http.Server` instance |
|
|
586
|
+
| `[SocketIOServerHelper] Invalid @socket.io/bun-engine instance for Bun runtime!` | `engine` missing/falsy with `runtime: 'bun'` | Pass a valid `@socket.io/bun-engine` `Server` instance |
|
|
587
|
+
| `[SocketIOServerHelper] Unsupported runtime!` | `runtime` is neither `'node'` nor `'bun'` | Use `RuntimeModules.NODE`, `RuntimeModules.BUN`, or `RuntimeModules.detect()` |
|
|
588
|
+
| `Invalid redis connection to config socket.io adapter!` | `redisConnection` missing, `null`, or `undefined` | Pass a valid `IRedisHelper` (e.g. `RedisSingleHelper`, `RedisClusterHelper`) |
|
|
589
|
+
| `[on] Invalid topic to start binding handler` | Empty/falsy `topic` passed to `on()` | Provide a non-empty string topic |
|
|
590
|
+
| `[on] IOServer is not initialized yet!` | `on()` called before `configure()` completed | `await configure()` before registering handlers |
|
|
591
|
+
| `Invalid socket client state to emit` (client) | `emit()` called while not connected | Check `getState()` / connection status, or emit inside `onConnected` |
|
|
592
|
+
| `Topic is required to emit` (client) | `emit()` called with an empty/falsy `topic` | Provide a non-empty string topic |
|
|
593
|
+
|
|
594
|
+
## See also
|
|
595
|
+
|
|
596
|
+
- [Socket.IO overview](/extensions/helpers/socket-io/) - getting started, constructor options, and common tasks
|
|
597
|
+
- [Socket.IO Component](/extensions/components/socket-io/) - DI-managed lifecycle wrapper around this helper
|
|
598
|
+
- [WebSocket Helper](../websocket/) - Bun-native alternative with no `socket.io` dependency
|
|
599
|
+
- [Redis Helper](/extensions/helpers/redis/reference) - `duplicateClient()` and the topology classes used as `redisConnection`
|