@fluojs/socket.io 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +3 -3
- package/README.md +3 -3
- package/dist/adapter.d.ts +4 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +110 -69
- package/dist/config.internal.d.ts +37 -0
- package/dist/config.internal.d.ts.map +1 -0
- package/dist/config.internal.js +61 -0
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +3 -1
- package/dist/shutdown.internal.d.ts +25 -0
- package/dist/shutdown.internal.d.ts.map +1 -0
- package/dist/shutdown.internal.js +66 -0
- package/dist/types.d.ts +6 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
package/README.ko.md
CHANGED
|
@@ -103,11 +103,11 @@ SocketIoModule.forRoot({
|
|
|
103
103
|
});
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
`cors`를 생략하면 `@fluojs/socket.io`는 `{ credentials: false, origin: false }`를 기본값으로 사용하므로 cross-origin 노출은 명시적 opt-in이 필요합니다. `engine.maxHttpBufferSize`를 생략하면 어댑터가 1 MiB Engine.IO payload 상한을 적용합니다. 기본값에는 `buffer.maxPendingMessagesPerSocket: 128`, `buffer.overflowPolicy: 'drop-oldest'`, `shutdown.timeoutMs: 5000`도 포함됩니다.
|
|
106
|
+
`cors`를 생략하면 `@fluojs/socket.io`는 `{ credentials: false, origin: false }`를 기본값으로 사용하므로 cross-origin 노출은 명시적 opt-in이 필요합니다. `engine.maxHttpBufferSize`를 생략하면 어댑터가 1 MiB Engine.IO payload 상한을 적용합니다. 기본값에는 `buffer.maxPendingMessagesPerSocket: 128`, `buffer.overflowPolicy: 'drop-oldest'`, `shutdown.timeoutMs: 5000`도 포함됩니다. 명시적인 `engine.maxHttpBufferSize`, `buffer.maxPendingMessagesPerSocket`, `shutdown.timeoutMs` 값은 양의 정수여야 하며, 잘못된 명시 값은 기본값으로 fallback하지 않고 모듈 등록 중 실패합니다.
|
|
107
107
|
|
|
108
108
|
정적 `@WebSocketGateway({ path })` namespace는 fluo gateway discovery가 소유하며 Socket.IO dynamic child namespace로 취급하지 않습니다. 어댑터는 이러한 정적 namespace에 대해 Socket.IO의 `cleanupEmptyChildNamespaces` 동작을 비활성화합니다. 애플리케이션 코드가 raw `SOCKETIO_SERVER` 접근으로 dynamic child namespace를 만들면 해당 소유권과 cleanup 정책은 애플리케이션 수준 Socket.IO 통합이 담당합니다.
|
|
109
109
|
|
|
110
|
-
애플리케이션 종료 중 Socket.IO client 정리는 Socket.IO가 소유하지만 underlying HTTP server는 이를 제공한 platform adapter 또는 shared HTTP server 통합이 계속 소유합니다. 어댑터는 `io.close(...)` 전에 해당 HTTP server 참조를 분리하므로 client cleanup은 실행되지만 Socket.IO가 adapter-owned/shared HTTP listener를 닫지는 않습니다. 동일한 managed Socket.IO instance 주변에 별도의 manual socket-disconnect 경로를 추가하지 마세요.
|
|
110
|
+
애플리케이션 종료 중 Socket.IO client 정리는 Socket.IO가 소유하지만 underlying HTTP server는 이를 제공한 platform adapter 또는 shared HTTP server 통합이 계속 소유합니다. 어댑터는 `io.close(...)` 전에 해당 HTTP server 참조를 분리하므로 client cleanup은 실행되지만 Socket.IO가 adapter-owned/shared HTTP listener를 닫지는 않습니다. Graceful Socket.IO close가 `shutdown.timeoutMs`를 넘으면 어댑터는 lifecycle state를 정리하기 전에 managed Socket.IO client를 force-disconnect합니다. 해당 force cleanup도 실패하면 shutdown retry를 위해 managed server reference와 registry를 보존합니다. 동일한 managed Socket.IO instance 주변에 별도의 manual socket-disconnect 경로를 추가하지 마세요.
|
|
111
111
|
|
|
112
112
|
### Guard 계약
|
|
113
113
|
|
|
@@ -130,7 +130,7 @@ Socket.IO 등록은 소유 모듈의 import 경로에서 구성하여 namespace/
|
|
|
130
130
|
- `SOCKETIO_ROOM_SERVICE`
|
|
131
131
|
- `SocketIoRoomService`: 공유 room 계약에 Socket.IO namespace-aware `joinRoom`, `leaveRoom`, `broadcastToRoom`, `getRooms` helper를 더한 타입입니다.
|
|
132
132
|
- `SocketIoLifecycleService`: server와 room-service token 뒤에서 동작하는 lifecycle 기반 구현입니다. 애플리케이션 코드는 일반적으로 `SOCKETIO_SERVER` 또는 `SOCKETIO_ROOM_SERVICE`를 주입하세요.
|
|
133
|
-
- 타입: `SocketIoModuleOptions`, `SocketIoConnectionGuardContext`, `SocketIoConnectionGuard`, `SocketIoMessageGuardContext`, `SocketIoMessageGuard`, `SocketIoGuardRejection`.
|
|
133
|
+
- 타입: `SocketIoModuleOptions`, `SocketIoHandshakeRequest`, `SocketIoConnectionGuardContext`, `SocketIoConnectionGuard`, `SocketIoMessageGuardContext`, `SocketIoMessageGuard`, `SocketIoGuardRejection`.
|
|
134
134
|
|
|
135
135
|
`SocketIoModuleOptions`는 `global`, `auth`, `buffer`, `cors`, `engine`, `shutdown`, `transports`를 포함합니다. `global`의 기본값은 `true`이므로 `SOCKETIO_SERVER`와 `SOCKETIO_ROOM_SERVICE`가 앱 전체에서 보입니다. module-local provider visibility가 필요하면 `false`로 설정하세요. 지원되는 server-backed runtime adapter가 필요하며, unsupported/noop adapter는 bootstrap 중 빠르게 실패합니다.
|
|
136
136
|
|
package/README.md
CHANGED
|
@@ -105,11 +105,11 @@ SocketIoModule.forRoot({
|
|
|
105
105
|
});
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
When `cors` is omitted, `@fluojs/socket.io` defaults to `{ credentials: false, origin: false }` so cross-origin exposure stays opt-in. When `engine.maxHttpBufferSize` is omitted, the adapter applies a bounded 1 MiB Engine.IO payload limit. Defaults also include `buffer.maxPendingMessagesPerSocket: 128`, `buffer.overflowPolicy: 'drop-oldest'`, and `shutdown.timeoutMs: 5000`.
|
|
108
|
+
When `cors` is omitted, `@fluojs/socket.io` defaults to `{ credentials: false, origin: false }` so cross-origin exposure stays opt-in. When `engine.maxHttpBufferSize` is omitted, the adapter applies a bounded 1 MiB Engine.IO payload limit. Defaults also include `buffer.maxPendingMessagesPerSocket: 128`, `buffer.overflowPolicy: 'drop-oldest'`, and `shutdown.timeoutMs: 5000`. Explicit `engine.maxHttpBufferSize`, `buffer.maxPendingMessagesPerSocket`, and `shutdown.timeoutMs` values must be positive integers; invalid explicit values fail during module registration instead of falling back to defaults.
|
|
109
109
|
|
|
110
110
|
Static `@WebSocketGateway({ path })` namespaces are owned by fluo's gateway discovery and are not treated as Socket.IO dynamic child namespaces. The adapter keeps Socket.IO's `cleanupEmptyChildNamespaces` behavior disabled for those static namespaces. If application code creates dynamic child namespaces through raw `SOCKETIO_SERVER` access, that ownership and cleanup policy stays with the application-level Socket.IO integration.
|
|
111
111
|
|
|
112
|
-
During application shutdown, Socket.IO owns cleanup for connected Socket.IO clients, but the underlying HTTP server remains owned by the platform adapter or shared HTTP server integration that supplied it. The adapter detaches that HTTP server reference before `io.close(...)`, so client cleanup still runs while Socket.IO does not close adapter-owned/shared HTTP listeners. Do not add a second manual socket-disconnect path around the same managed Socket.IO instance.
|
|
112
|
+
During application shutdown, Socket.IO owns cleanup for connected Socket.IO clients, but the underlying HTTP server remains owned by the platform adapter or shared HTTP server integration that supplied it. The adapter detaches that HTTP server reference before `io.close(...)`, so client cleanup still runs while Socket.IO does not close adapter-owned/shared HTTP listeners. If graceful Socket.IO close exceeds `shutdown.timeoutMs`, the adapter force-disconnects managed Socket.IO clients before clearing lifecycle state; if that force cleanup fails, the managed server reference and registries are retained for shutdown retry. Do not add a second manual socket-disconnect path around the same managed Socket.IO instance.
|
|
113
113
|
|
|
114
114
|
### Guard contracts
|
|
115
115
|
|
|
@@ -132,7 +132,7 @@ Register Socket.IO through module imports in the owning module so namespace/mess
|
|
|
132
132
|
- `SOCKETIO_ROOM_SERVICE`: Token to inject the `SocketIoRoomService`.
|
|
133
133
|
- `SocketIoRoomService`: Shared room contract plus Socket.IO namespace-aware `joinRoom`, `leaveRoom`, `broadcastToRoom`, and `getRooms` helpers.
|
|
134
134
|
- `SocketIoLifecycleService`: Lifecycle-backed implementation behind the server and room-service tokens; application code should usually inject `SOCKETIO_SERVER` or `SOCKETIO_ROOM_SERVICE` instead.
|
|
135
|
-
- Types: `SocketIoModuleOptions`, `SocketIoConnectionGuardContext`, `SocketIoConnectionGuard`, `SocketIoMessageGuardContext`, `SocketIoMessageGuard`, `SocketIoGuardRejection`.
|
|
135
|
+
- Types: `SocketIoModuleOptions`, `SocketIoHandshakeRequest`, `SocketIoConnectionGuardContext`, `SocketIoConnectionGuard`, `SocketIoMessageGuardContext`, `SocketIoMessageGuard`, `SocketIoGuardRejection`.
|
|
136
136
|
|
|
137
137
|
`SocketIoModuleOptions` covers `global`, `auth`, `buffer`, `cors`, `engine`, `shutdown`, and `transports`. `global` defaults to `true`, which keeps `SOCKETIO_SERVER` and `SOCKETIO_ROOM_SERVICE` visible across the app; set it to `false` when you want module-local provider visibility. A supported server-backed runtime adapter is required; unsupported/noop adapters fail fast during bootstrap.
|
|
138
138
|
|
package/dist/adapter.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export declare class SocketIoLifecycleService implements OnApplicationBootstrap,
|
|
|
22
22
|
private readonly namespaceContext;
|
|
23
23
|
private readonly socketRegistry;
|
|
24
24
|
private shutdownPromise;
|
|
25
|
+
private shutdownStarted;
|
|
25
26
|
private wired;
|
|
26
27
|
constructor(runtimeContainer: Container, compiledModules: readonly CompiledModule[], logger: ApplicationLogger, adapter: HttpApplicationAdapter, moduleOptions: SocketIoModuleOptions);
|
|
27
28
|
/**
|
|
@@ -31,6 +32,7 @@ export declare class SocketIoLifecycleService implements OnApplicationBootstrap,
|
|
|
31
32
|
* @throws {Error} When the selected realtime capability cannot expose the server contract Socket.IO requires.
|
|
32
33
|
*/
|
|
33
34
|
getServer(): Server;
|
|
35
|
+
getServerAsync(): Promise<Server>;
|
|
34
36
|
/**
|
|
35
37
|
* Discovers gateway classes and binds their handlers to the resolved namespaces once per application lifecycle.
|
|
36
38
|
*/
|
|
@@ -76,6 +78,7 @@ export declare class SocketIoLifecycleService implements OnApplicationBootstrap,
|
|
|
76
78
|
*/
|
|
77
79
|
getRooms(socketId: string): ReadonlySet<string>;
|
|
78
80
|
private createServerOptions;
|
|
81
|
+
private createNodeServer;
|
|
79
82
|
private createBunEngineOptions;
|
|
80
83
|
private resolveCorsOptions;
|
|
81
84
|
private resolveMaxHttpBufferSize;
|
|
@@ -116,5 +119,6 @@ export declare class SocketIoLifecycleService implements OnApplicationBootstrap,
|
|
|
116
119
|
private shutdown;
|
|
117
120
|
private runShutdownLifecycle;
|
|
118
121
|
private closeServerWithTimeout;
|
|
122
|
+
private clearManagedState;
|
|
119
123
|
}
|
|
120
124
|
//# sourceMappingURL=adapter.d.ts.map
|
package/dist/adapter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/adapter.js
CHANGED
|
@@ -7,17 +7,14 @@ function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side
|
|
|
7
7
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
8
8
|
import { Inject } from '@fluojs/core';
|
|
9
9
|
import { getClassDiMetadata } from '@fluojs/core/internal';
|
|
10
|
-
import { Server as BunEngineServer } from '@socket.io/bun-engine';
|
|
11
10
|
import { APPLICATION_LOGGER, COMPILED_MODULES, HTTP_APPLICATION_ADAPTER, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
12
11
|
import { getWebSocketGatewayMetadata, getWebSocketHandlerMetadataEntries } from '@fluojs/websockets';
|
|
13
12
|
import { Server } from 'socket.io';
|
|
14
13
|
import { SOCKETIO_OPTIONS_INTERNAL } from './options-token.internal.js';
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
function isFinitePositiveInteger(value) {
|
|
20
|
-
return typeof value === 'number' && Number.isInteger(value) && Number.isFinite(value) && value > 0;
|
|
14
|
+
import { DEFAULT_SOCKETIO_ENGINE_PATH, resolveSocketIoMaxHttpBufferSize, resolveSocketIoMaxPendingMessagesPerSocket, resolveSocketIoShutdownTimeoutMs } from './config.internal.js';
|
|
15
|
+
import { closeSocketIoServerWithTimeout } from './shutdown.internal.js';
|
|
16
|
+
async function loadBunEngineServer() {
|
|
17
|
+
return (await import('@socket.io/bun-engine')).Server;
|
|
21
18
|
}
|
|
22
19
|
function normalizeGuardRejection(result, defaultMessage) {
|
|
23
20
|
if (result === undefined || result === true) {
|
|
@@ -170,6 +167,7 @@ class SocketIoLifecycleService {
|
|
|
170
167
|
namespaceContext = new AsyncLocalStorage();
|
|
171
168
|
socketRegistry = new Map();
|
|
172
169
|
shutdownPromise;
|
|
170
|
+
shutdownStarted = false;
|
|
173
171
|
wired = false;
|
|
174
172
|
constructor(runtimeContainer, compiledModules, logger, adapter, moduleOptions) {
|
|
175
173
|
this.runtimeContainer = runtimeContainer;
|
|
@@ -186,20 +184,33 @@ class SocketIoLifecycleService {
|
|
|
186
184
|
* @throws {Error} When the selected realtime capability cannot expose the server contract Socket.IO requires.
|
|
187
185
|
*/
|
|
188
186
|
getServer() {
|
|
187
|
+
if (this.shutdownStarted) {
|
|
188
|
+
throw new Error('Socket.IO server access is unavailable after application shutdown has started.');
|
|
189
|
+
}
|
|
189
190
|
if (this.io) {
|
|
190
191
|
return this.io;
|
|
191
192
|
}
|
|
192
193
|
const runtime = resolveSocketIoBootstrapRuntime(this.adapter);
|
|
193
194
|
if (runtime.kind === 'server-backed') {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
195
|
+
this.io = this.createNodeServer(runtime);
|
|
196
|
+
return this.io;
|
|
197
|
+
}
|
|
198
|
+
throw new Error('Socket.IO Bun engine bootstrap is asynchronous. Use getServerAsync() for Bun-style adapters.');
|
|
199
|
+
}
|
|
200
|
+
async getServerAsync() {
|
|
201
|
+
if (this.shutdownStarted) {
|
|
202
|
+
throw new Error('Socket.IO server access is unavailable after application shutdown has started.');
|
|
203
|
+
}
|
|
204
|
+
if (this.io) {
|
|
205
|
+
return this.io;
|
|
206
|
+
}
|
|
207
|
+
const runtime = resolveSocketIoBootstrapRuntime(this.adapter);
|
|
208
|
+
if (runtime.kind === 'server-backed') {
|
|
209
|
+
this.io = this.createNodeServer(runtime);
|
|
199
210
|
return this.io;
|
|
200
211
|
}
|
|
201
212
|
this.io = new Server(this.createServerOptions());
|
|
202
|
-
this.installBunSocketIoBinding(runtime, this.io);
|
|
213
|
+
await this.installBunSocketIoBinding(runtime, this.io);
|
|
203
214
|
return this.io;
|
|
204
215
|
}
|
|
205
216
|
|
|
@@ -212,11 +223,11 @@ class SocketIoLifecycleService {
|
|
|
212
223
|
}
|
|
213
224
|
const descriptors = this.discoverGatewayDescriptors();
|
|
214
225
|
if (descriptors.length === 0) {
|
|
215
|
-
this.ensureBunRealtimeBindingForRawServerAccess();
|
|
226
|
+
await this.ensureBunRealtimeBindingForRawServerAccess();
|
|
216
227
|
return;
|
|
217
228
|
}
|
|
218
229
|
this.assertNoServerBackedGatewayOptIn(descriptors);
|
|
219
|
-
const io = this.
|
|
230
|
+
const io = await this.getServerAsync();
|
|
220
231
|
const attachments = this.prepareNamespaceAttachments(io, descriptors);
|
|
221
232
|
for (const attachment of attachments) {
|
|
222
233
|
this.bindNamespaceHandlers(attachment);
|
|
@@ -313,6 +324,13 @@ class SocketIoLifecycleService {
|
|
|
313
324
|
}
|
|
314
325
|
return options;
|
|
315
326
|
}
|
|
327
|
+
createNodeServer(runtime) {
|
|
328
|
+
const httpServer = runtime.capability.server;
|
|
329
|
+
if (!isNodeHttpServerLike(httpServer)) {
|
|
330
|
+
throw new Error('Socket.IO bootstrap requires the selected realtime capability to expose a Node HTTP/S server instance.');
|
|
331
|
+
}
|
|
332
|
+
return new Server(httpServer, this.createServerOptions());
|
|
333
|
+
}
|
|
316
334
|
createBunEngineOptions() {
|
|
317
335
|
const options = {
|
|
318
336
|
path: DEFAULT_SOCKETIO_ENGINE_PATH
|
|
@@ -328,16 +346,13 @@ class SocketIoLifecycleService {
|
|
|
328
346
|
};
|
|
329
347
|
}
|
|
330
348
|
resolveMaxHttpBufferSize() {
|
|
331
|
-
|
|
332
|
-
if (!isFinitePositiveInteger(configured)) {
|
|
333
|
-
return DEFAULT_SOCKETIO_MAX_HTTP_BUFFER_SIZE;
|
|
334
|
-
}
|
|
335
|
-
return configured;
|
|
349
|
+
return resolveSocketIoMaxHttpBufferSize(this.moduleOptions);
|
|
336
350
|
}
|
|
337
|
-
installBunSocketIoBinding(runtime, io) {
|
|
351
|
+
async installBunSocketIoBinding(runtime, io) {
|
|
338
352
|
if (this.bunEngine) {
|
|
339
353
|
return;
|
|
340
354
|
}
|
|
355
|
+
const BunEngineServer = await loadBunEngineServer();
|
|
341
356
|
const engine = new BunEngineServer(this.createBunEngineOptions());
|
|
342
357
|
io.bind(engine);
|
|
343
358
|
runtime.bindingHost.configureRealtimeBinding(this.createBunSocketIoBinding(engine));
|
|
@@ -374,11 +389,11 @@ class SocketIoLifecycleService {
|
|
|
374
389
|
matchesSocketIoEnginePath(pathname) {
|
|
375
390
|
return pathname === '/socket.io' || pathname === DEFAULT_SOCKETIO_ENGINE_PATH;
|
|
376
391
|
}
|
|
377
|
-
ensureBunRealtimeBindingForRawServerAccess() {
|
|
392
|
+
async ensureBunRealtimeBindingForRawServerAccess() {
|
|
378
393
|
if (!hasBunRealtimeBindingHost(this.adapter)) {
|
|
379
394
|
return;
|
|
380
395
|
}
|
|
381
|
-
this.
|
|
396
|
+
await this.getServerAsync();
|
|
382
397
|
}
|
|
383
398
|
assertNoServerBackedGatewayOptIn(descriptors) {
|
|
384
399
|
const runtime = resolveSocketIoBootstrapRuntime(this.adapter);
|
|
@@ -502,7 +517,7 @@ class SocketIoLifecycleService {
|
|
|
502
517
|
};
|
|
503
518
|
}
|
|
504
519
|
maxPendingMessagesPerSocket() {
|
|
505
|
-
return
|
|
520
|
+
return resolveSocketIoMaxPendingMessagesPerSocket(this.moduleOptions);
|
|
506
521
|
}
|
|
507
522
|
attachConnectionListeners(state, resolved, socket, request) {
|
|
508
523
|
socket.onAny((event, ...args) => {
|
|
@@ -706,13 +721,19 @@ class SocketIoLifecycleService {
|
|
|
706
721
|
createGatewayDescriptor(candidate, path) {
|
|
707
722
|
const metadata = getWebSocketGatewayMetadata(candidate.targetType);
|
|
708
723
|
const entries = getWebSocketHandlerMetadataEntries(candidate.targetType.prototype);
|
|
724
|
+
const handlers = entries.map(entry => ({
|
|
725
|
+
event: entry.metadata.event,
|
|
726
|
+
methodKey: entry.propertyKey,
|
|
727
|
+
methodName: methodKeyToName(entry.propertyKey),
|
|
728
|
+
type: entry.metadata.type
|
|
729
|
+
}));
|
|
730
|
+
const handlerIndex = createGatewayHandlerIndex(handlers);
|
|
709
731
|
return {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
})),
|
|
732
|
+
connectHandlers: handlerIndex.connectHandlers,
|
|
733
|
+
disconnectHandlers: handlerIndex.disconnectHandlers,
|
|
734
|
+
handlers,
|
|
735
|
+
messageHandlersByEvent: handlerIndex.messageHandlersByEvent,
|
|
736
|
+
wildcardMessageHandlers: handlerIndex.wildcardMessageHandlers,
|
|
716
737
|
moduleName: candidate.moduleName,
|
|
717
738
|
path: normalizeGatewayPath(path),
|
|
718
739
|
serverBacked: metadata?.serverBacked,
|
|
@@ -754,67 +775,87 @@ class SocketIoLifecycleService {
|
|
|
754
775
|
return candidates;
|
|
755
776
|
}
|
|
756
777
|
resolveShutdownTimeoutMs() {
|
|
757
|
-
|
|
758
|
-
if (typeof configured !== 'number' || !Number.isFinite(configured) || configured <= 0) {
|
|
759
|
-
return DEFAULT_SOCKETIO_SHUTDOWN_TIMEOUT_MS;
|
|
760
|
-
}
|
|
761
|
-
return Math.floor(configured);
|
|
778
|
+
return resolveSocketIoShutdownTimeoutMs(this.moduleOptions);
|
|
762
779
|
}
|
|
763
780
|
async shutdown() {
|
|
764
781
|
if (this.shutdownPromise) {
|
|
765
782
|
await this.shutdownPromise;
|
|
766
783
|
return;
|
|
767
784
|
}
|
|
785
|
+
this.shutdownStarted = true;
|
|
768
786
|
this.shutdownPromise = this.runShutdownLifecycle();
|
|
769
|
-
|
|
787
|
+
try {
|
|
788
|
+
await this.shutdownPromise;
|
|
789
|
+
} finally {
|
|
790
|
+
this.shutdownPromise = undefined;
|
|
791
|
+
}
|
|
770
792
|
}
|
|
771
793
|
async runShutdownLifecycle() {
|
|
772
794
|
const io = this.io;
|
|
773
|
-
this.wired = false;
|
|
774
795
|
if (!io) {
|
|
775
|
-
this.
|
|
776
|
-
this.socketRegistry.clear();
|
|
796
|
+
this.clearManagedState();
|
|
777
797
|
return;
|
|
778
798
|
}
|
|
799
|
+
let shouldClearManagedState = false;
|
|
800
|
+
const timeoutMs = this.resolveShutdownTimeoutMs();
|
|
779
801
|
try {
|
|
780
|
-
await this.closeServerWithTimeout(io,
|
|
802
|
+
const result = await this.closeServerWithTimeout(io, timeoutMs);
|
|
803
|
+
shouldClearManagedState = true;
|
|
804
|
+
if (result.kind === 'forced') {
|
|
805
|
+
this.logger.error(`Failed to close Socket.IO server within ${String(timeoutMs)}ms; force-disconnected managed Socket.IO clients before clearing lifecycle state.`, result.timeoutError, 'SocketIoLifecycleService');
|
|
806
|
+
}
|
|
781
807
|
} catch (error) {
|
|
782
|
-
this.logger.error(`Failed to close Socket.IO server within ${String(
|
|
808
|
+
this.logger.error(`Failed to close Socket.IO server within ${String(timeoutMs)}ms; retaining managed Socket.IO state for shutdown retry.`, error, 'SocketIoLifecycleService');
|
|
783
809
|
} finally {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
this.attachments = [];
|
|
787
|
-
if (hasBunRealtimeBindingHost(this.adapter)) {
|
|
788
|
-
this.adapter.configureRealtimeBinding(undefined);
|
|
810
|
+
if (shouldClearManagedState) {
|
|
811
|
+
this.clearManagedState();
|
|
789
812
|
}
|
|
790
|
-
this.socketRegistry.clear();
|
|
791
813
|
}
|
|
792
814
|
}
|
|
793
815
|
closeServerWithTimeout(io, timeoutMs) {
|
|
794
|
-
return
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
io.httpServer = undefined;
|
|
806
|
-
io.close(() => {
|
|
807
|
-
if (settled) {
|
|
808
|
-
return;
|
|
809
|
-
}
|
|
810
|
-
settled = true;
|
|
811
|
-
clearTimeout(timeout);
|
|
812
|
-
resolve();
|
|
813
|
-
});
|
|
814
|
-
});
|
|
816
|
+
return closeSocketIoServerWithTimeout(io, timeoutMs);
|
|
817
|
+
}
|
|
818
|
+
clearManagedState() {
|
|
819
|
+
this.wired = false;
|
|
820
|
+
this.io = undefined;
|
|
821
|
+
this.bunEngine = undefined;
|
|
822
|
+
this.attachments = [];
|
|
823
|
+
if (hasBunRealtimeBindingHost(this.adapter)) {
|
|
824
|
+
this.adapter.configureRealtimeBinding(undefined);
|
|
825
|
+
}
|
|
826
|
+
this.socketRegistry.clear();
|
|
815
827
|
}
|
|
816
828
|
static {
|
|
817
829
|
_initClass();
|
|
818
830
|
}
|
|
819
831
|
}
|
|
820
|
-
export { _SocketIoLifecycleSer as SocketIoLifecycleService };
|
|
832
|
+
export { _SocketIoLifecycleSer as SocketIoLifecycleService };
|
|
833
|
+
function createGatewayHandlerIndex(handlers) {
|
|
834
|
+
const connectHandlers = [];
|
|
835
|
+
const disconnectHandlers = [];
|
|
836
|
+
const messageHandlersByEvent = new Map();
|
|
837
|
+
const wildcardMessageHandlers = [];
|
|
838
|
+
for (const handler of handlers) {
|
|
839
|
+
if (handler.type === 'connect') {
|
|
840
|
+
connectHandlers.push(handler);
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
if (handler.type === 'disconnect') {
|
|
844
|
+
disconnectHandlers.push(handler);
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
if (handler.event === undefined) {
|
|
848
|
+
wildcardMessageHandlers.push(handler);
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
const eventHandlers = messageHandlersByEvent.get(handler.event) ?? [];
|
|
852
|
+
eventHandlers.push(handler);
|
|
853
|
+
messageHandlersByEvent.set(handler.event, eventHandlers);
|
|
854
|
+
}
|
|
855
|
+
return {
|
|
856
|
+
connectHandlers,
|
|
857
|
+
disconnectHandlers,
|
|
858
|
+
messageHandlersByEvent,
|
|
859
|
+
wildcardMessageHandlers
|
|
860
|
+
};
|
|
861
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SocketIoModuleOptions } from './types.js';
|
|
2
|
+
/** Default number of pre-connect Socket.IO messages buffered per socket. */
|
|
3
|
+
export declare const DEFAULT_MAX_PENDING_MESSAGES_PER_SOCKET = 128;
|
|
4
|
+
/** Default Engine.IO request path used by Socket.IO transports. */
|
|
5
|
+
export declare const DEFAULT_SOCKETIO_ENGINE_PATH = "/socket.io/";
|
|
6
|
+
/** Default maximum inbound Engine.IO payload size in bytes. */
|
|
7
|
+
export declare const DEFAULT_SOCKETIO_MAX_HTTP_BUFFER_SIZE = 1048576;
|
|
8
|
+
/** Default graceful Socket.IO shutdown timeout in milliseconds. */
|
|
9
|
+
export declare const DEFAULT_SOCKETIO_SHUTDOWN_TIMEOUT_MS = 5000;
|
|
10
|
+
/**
|
|
11
|
+
* Assert that explicit Socket.IO numeric options are positive integers.
|
|
12
|
+
*
|
|
13
|
+
* @param options Module options supplied to `SocketIoModule.forRoot(...)`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function assertValidSocketIoModuleOptions(options: SocketIoModuleOptions): void;
|
|
16
|
+
/**
|
|
17
|
+
* Resolve the effective Socket.IO Engine.IO payload bound.
|
|
18
|
+
*
|
|
19
|
+
* @param options Module options supplied to `SocketIoModule.forRoot(...)`.
|
|
20
|
+
* @returns The explicit payload bound or the package default.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveSocketIoMaxHttpBufferSize(options: SocketIoModuleOptions): number;
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the effective pending-message buffer limit.
|
|
25
|
+
*
|
|
26
|
+
* @param options Module options supplied to `SocketIoModule.forRoot(...)`.
|
|
27
|
+
* @returns The explicit per-socket buffer limit or the package default.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveSocketIoMaxPendingMessagesPerSocket(options: SocketIoModuleOptions): number;
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the effective Socket.IO shutdown timeout.
|
|
32
|
+
*
|
|
33
|
+
* @param options Module options supplied to `SocketIoModule.forRoot(...)`.
|
|
34
|
+
* @returns The explicit shutdown timeout or the package default.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveSocketIoShutdownTimeoutMs(options: SocketIoModuleOptions): number;
|
|
37
|
+
//# sourceMappingURL=config.internal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.internal.d.ts","sourceRoot":"","sources":["../src/config.internal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,4EAA4E;AAC5E,eAAO,MAAM,uCAAuC,MAAM,CAAC;AAE3D,mEAAmE;AACnE,eAAO,MAAM,4BAA4B,gBAAgB,CAAC;AAE1D,+DAA+D;AAC/D,eAAO,MAAM,qCAAqC,UAAY,CAAC;AAE/D,mEAAmE;AACnE,eAAO,MAAM,oCAAoC,OAAQ,CAAC;AAmB1D;;;;GAIG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAIrF;AAED;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAEvF;AAED;;;;;GAKG;AACH,wBAAgB,0CAA0C,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAEjG;AAED;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAEvF"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** Default number of pre-connect Socket.IO messages buffered per socket. */
|
|
2
|
+
export const DEFAULT_MAX_PENDING_MESSAGES_PER_SOCKET = 128;
|
|
3
|
+
|
|
4
|
+
/** Default Engine.IO request path used by Socket.IO transports. */
|
|
5
|
+
export const DEFAULT_SOCKETIO_ENGINE_PATH = '/socket.io/';
|
|
6
|
+
|
|
7
|
+
/** Default maximum inbound Engine.IO payload size in bytes. */
|
|
8
|
+
export const DEFAULT_SOCKETIO_MAX_HTTP_BUFFER_SIZE = 1_048_576;
|
|
9
|
+
|
|
10
|
+
/** Default graceful Socket.IO shutdown timeout in milliseconds. */
|
|
11
|
+
export const DEFAULT_SOCKETIO_SHUTDOWN_TIMEOUT_MS = 5_000;
|
|
12
|
+
function isPositiveInteger(value) {
|
|
13
|
+
return typeof value === 'number' && Number.isInteger(value) && Number.isFinite(value) && value > 0;
|
|
14
|
+
}
|
|
15
|
+
function assertOptionalPositiveInteger(value, path) {
|
|
16
|
+
if (value === undefined || isPositiveInteger(value)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
throw new Error(`Socket.IO configuration ${path} must be a positive integer when provided.`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Assert that explicit Socket.IO numeric options are positive integers.
|
|
24
|
+
*
|
|
25
|
+
* @param options Module options supplied to `SocketIoModule.forRoot(...)`.
|
|
26
|
+
*/
|
|
27
|
+
export function assertValidSocketIoModuleOptions(options) {
|
|
28
|
+
assertOptionalPositiveInteger(options.engine?.maxHttpBufferSize, 'engine.maxHttpBufferSize');
|
|
29
|
+
assertOptionalPositiveInteger(options.buffer?.maxPendingMessagesPerSocket, 'buffer.maxPendingMessagesPerSocket');
|
|
30
|
+
assertOptionalPositiveInteger(options.shutdown?.timeoutMs, 'shutdown.timeoutMs');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the effective Socket.IO Engine.IO payload bound.
|
|
35
|
+
*
|
|
36
|
+
* @param options Module options supplied to `SocketIoModule.forRoot(...)`.
|
|
37
|
+
* @returns The explicit payload bound or the package default.
|
|
38
|
+
*/
|
|
39
|
+
export function resolveSocketIoMaxHttpBufferSize(options) {
|
|
40
|
+
return options.engine?.maxHttpBufferSize ?? DEFAULT_SOCKETIO_MAX_HTTP_BUFFER_SIZE;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the effective pending-message buffer limit.
|
|
45
|
+
*
|
|
46
|
+
* @param options Module options supplied to `SocketIoModule.forRoot(...)`.
|
|
47
|
+
* @returns The explicit per-socket buffer limit or the package default.
|
|
48
|
+
*/
|
|
49
|
+
export function resolveSocketIoMaxPendingMessagesPerSocket(options) {
|
|
50
|
+
return options.buffer?.maxPendingMessagesPerSocket ?? DEFAULT_MAX_PENDING_MESSAGES_PER_SOCKET;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the effective Socket.IO shutdown timeout.
|
|
55
|
+
*
|
|
56
|
+
* @param options Module options supplied to `SocketIoModule.forRoot(...)`.
|
|
57
|
+
* @returns The explicit shutdown timeout or the package default.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveSocketIoShutdownTimeoutMs(options) {
|
|
60
|
+
return options.shutdown?.timeoutMs ?? DEFAULT_SOCKETIO_SHUTDOWN_TIMEOUT_MS;
|
|
61
|
+
}
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAShE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAwBxD;;GAEG;AACH,qBAAa,cAAc;IACzB;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,qBAA0B,GAAG,UAAU;CAWhE"}
|
package/dist/module.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineModule } from '@fluojs/runtime';
|
|
2
|
+
import { assertValidSocketIoModuleOptions } from './config.internal.js';
|
|
2
3
|
import { SOCKETIO_OPTIONS_INTERNAL } from './options-token.internal.js';
|
|
3
4
|
import { SocketIoLifecycleService } from './adapter.js';
|
|
4
5
|
import { SOCKETIO_ROOM_SERVICE, SOCKETIO_SERVER } from './tokens.js';
|
|
@@ -11,7 +12,7 @@ function createSocketIoProviderSet(options = {}) {
|
|
|
11
12
|
useClass: SocketIoLifecycleService
|
|
12
13
|
}, {
|
|
13
14
|
provide: SOCKETIO_SERVER,
|
|
14
|
-
useFactory: service => service.
|
|
15
|
+
useFactory: async service => await service.getServerAsync(),
|
|
15
16
|
inject: [SocketIoLifecycleService]
|
|
16
17
|
}, {
|
|
17
18
|
provide: SOCKETIO_ROOM_SERVICE,
|
|
@@ -41,6 +42,7 @@ export class SocketIoModule {
|
|
|
41
42
|
* ```
|
|
42
43
|
*/
|
|
43
44
|
static forRoot(options = {}) {
|
|
45
|
+
assertValidSocketIoModuleOptions(options);
|
|
44
46
|
class SocketIoRuntimeModule extends SocketIoModule {}
|
|
45
47
|
return defineModule(SocketIoRuntimeModule, {
|
|
46
48
|
exports: [SOCKETIO_ROOM_SERVICE, SOCKETIO_SERVER],
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Result of a bounded Socket.IO close attempt. */
|
|
2
|
+
export interface SocketIoCloseResult {
|
|
3
|
+
readonly kind: 'closed' | 'forced';
|
|
4
|
+
readonly timeoutError?: SocketIoShutdownTimeoutError;
|
|
5
|
+
}
|
|
6
|
+
/** Minimal Socket.IO server surface required for lifecycle shutdown. */
|
|
7
|
+
export interface SocketIoCloseTarget {
|
|
8
|
+
close(callback?: () => void): unknown;
|
|
9
|
+
disconnectSockets?: (close?: boolean) => unknown;
|
|
10
|
+
httpServer?: unknown;
|
|
11
|
+
}
|
|
12
|
+
/** Error retained when Socket.IO shutdown exceeds the configured timeout. */
|
|
13
|
+
export declare class SocketIoShutdownTimeoutError extends Error {
|
|
14
|
+
readonly timeoutMs: number;
|
|
15
|
+
constructor(timeoutMs: number);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Close a Socket.IO server while bounding graceful cleanup time.
|
|
19
|
+
*
|
|
20
|
+
* @param io Socket.IO close target managed by the lifecycle service.
|
|
21
|
+
* @param timeoutMs Maximum graceful close duration before managed clients are force-disconnected.
|
|
22
|
+
* @returns A close result that identifies graceful completion or forced client cleanup.
|
|
23
|
+
*/
|
|
24
|
+
export declare function closeSocketIoServerWithTimeout(io: SocketIoCloseTarget, timeoutMs: number): Promise<SocketIoCloseResult>;
|
|
25
|
+
//# sourceMappingURL=shutdown.internal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shutdown.internal.d.ts","sourceRoot":"","sources":["../src/shutdown.internal.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACnC,QAAQ,CAAC,YAAY,CAAC,EAAE,4BAA4B,CAAC;CACtD;AAED,wEAAwE;AACxE,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;IACtC,iBAAiB,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;IACjD,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,6EAA6E;AAC7E,qBAAa,4BAA6B,SAAQ,KAAK;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM;gBAAjB,SAAS,EAAE,MAAM;CAIvC;AAUD;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAAC,EAAE,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAwCvH"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Result of a bounded Socket.IO close attempt. */
|
|
2
|
+
|
|
3
|
+
/** Minimal Socket.IO server surface required for lifecycle shutdown. */
|
|
4
|
+
|
|
5
|
+
/** Error retained when Socket.IO shutdown exceeds the configured timeout. */
|
|
6
|
+
export class SocketIoShutdownTimeoutError extends Error {
|
|
7
|
+
constructor(timeoutMs) {
|
|
8
|
+
super(`Timed out while closing Socket.IO server after ${String(timeoutMs)}ms.`);
|
|
9
|
+
this.timeoutMs = timeoutMs;
|
|
10
|
+
this.name = 'SocketIoShutdownTimeoutError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function detachAdapterOwnedHttpServer(io) {
|
|
14
|
+
io.httpServer = undefined;
|
|
15
|
+
}
|
|
16
|
+
function forceDisconnectManagedClients(io) {
|
|
17
|
+
io.disconnectSockets?.(true);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Close a Socket.IO server while bounding graceful cleanup time.
|
|
22
|
+
*
|
|
23
|
+
* @param io Socket.IO close target managed by the lifecycle service.
|
|
24
|
+
* @param timeoutMs Maximum graceful close duration before managed clients are force-disconnected.
|
|
25
|
+
* @returns A close result that identifies graceful completion or forced client cleanup.
|
|
26
|
+
*/
|
|
27
|
+
export function closeSocketIoServerWithTimeout(io, timeoutMs) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
let settled = false;
|
|
30
|
+
const timeout = setTimeout(() => {
|
|
31
|
+
if (settled) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const timeoutError = new SocketIoShutdownTimeoutError(timeoutMs);
|
|
35
|
+
try {
|
|
36
|
+
forceDisconnectManagedClients(io);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
settled = true;
|
|
39
|
+
reject(error);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
settled = true;
|
|
43
|
+
resolve({
|
|
44
|
+
kind: 'forced',
|
|
45
|
+
timeoutError
|
|
46
|
+
});
|
|
47
|
+
}, timeoutMs);
|
|
48
|
+
detachAdapterOwnedHttpServer(io);
|
|
49
|
+
try {
|
|
50
|
+
io.close(() => {
|
|
51
|
+
if (settled) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
settled = true;
|
|
55
|
+
clearTimeout(timeout);
|
|
56
|
+
resolve({
|
|
57
|
+
kind: 'closed'
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
} catch (error) {
|
|
61
|
+
settled = true;
|
|
62
|
+
clearTimeout(timeout);
|
|
63
|
+
reject(error);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { IncomingMessage } from 'node:http';
|
|
2
2
|
import type { ServerOptions, Socket } from 'socket.io';
|
|
3
3
|
import type { WebSocketRoomService } from '@fluojs/websockets';
|
|
4
|
+
/** Runtime-specific Socket.IO handshake request surfaced to guard callbacks. */
|
|
5
|
+
export type SocketIoHandshakeRequest = IncomingMessage | Request;
|
|
4
6
|
/**
|
|
5
7
|
* Room management contract exposed by `@fluojs/socket.io` gateways.
|
|
6
8
|
*
|
|
@@ -52,8 +54,8 @@ export interface SocketIoConnectionGuardContext {
|
|
|
52
54
|
activeConnectionCount: number;
|
|
53
55
|
/** Namespace path currently being connected, normalized to the Socket.IO gateway path contract. */
|
|
54
56
|
namespacePath: string;
|
|
55
|
-
/**
|
|
56
|
-
request:
|
|
57
|
+
/** Runtime-specific handshake request exposed by the selected Socket.IO runtime. */
|
|
58
|
+
request: SocketIoHandshakeRequest;
|
|
57
59
|
/** Socket.IO socket instance under evaluation. */
|
|
58
60
|
socket: Socket;
|
|
59
61
|
}
|
|
@@ -73,8 +75,8 @@ export interface SocketIoMessageGuardContext {
|
|
|
73
75
|
namespacePath: string;
|
|
74
76
|
/** Event payload extracted from the Socket.IO argument list. */
|
|
75
77
|
payload: unknown;
|
|
76
|
-
/**
|
|
77
|
-
request:
|
|
78
|
+
/** Runtime-specific handshake request associated with the current socket. */
|
|
79
|
+
request: SocketIoHandshakeRequest;
|
|
78
80
|
/** Socket.IO socket instance emitting the event. */
|
|
79
81
|
socket: Socket;
|
|
80
82
|
}
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE/D;;;;GAIG;AACH,MAAM,WAAW,mBAAoB,SAAQ,oBAAoB;IAC/D;;;;;;;OAOG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE1F;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEvE;;;;;;OAMG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzE;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,6FAA6F;IAC7F,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf,0GAA0G;IAC1G,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC7C,wFAAwF;IACxF,qBAAqB,EAAE,MAAM,CAAC;IAE9B,mGAAmG;IACnG,aAAa,EAAE,MAAM,CAAC;IAEtB,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"realtime",
|
|
10
10
|
"platform"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.0.
|
|
12
|
+
"version": "1.0.5",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@socket.io/bun-engine": "^0.1.0",
|
|
40
40
|
"@fluojs/core": "^1.0.3",
|
|
41
|
-
"@fluojs/di": "^1.0
|
|
41
|
+
"@fluojs/di": "^1.1.0",
|
|
42
42
|
"@fluojs/http": "^1.1.0",
|
|
43
|
-
"@fluojs/runtime": "^1.1.
|
|
44
|
-
"@fluojs/websockets": "^1.0.
|
|
43
|
+
"@fluojs/runtime": "^1.1.6",
|
|
44
|
+
"@fluojs/websockets": "^1.0.5"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"socket.io": "^4.0.0"
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"socket.io": "^4.8.1",
|
|
51
51
|
"socket.io-client": "^4.8.1",
|
|
52
52
|
"vitest": "^3.2.4",
|
|
53
|
-
"@fluojs/platform-express": "^1.0.
|
|
54
|
-
"@fluojs/platform-fastify": "^1.0.
|
|
55
|
-
"@fluojs/platform-nodejs": "^1.0.
|
|
53
|
+
"@fluojs/platform-express": "^1.0.5",
|
|
54
|
+
"@fluojs/platform-fastify": "^1.0.6",
|
|
55
|
+
"@fluojs/platform-nodejs": "^1.0.5"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"prebuild": "node ../../tooling/scripts/clean-dist.mjs",
|