@fluojs/socket.io 1.0.4 → 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 +3 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +100 -68
- 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/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
|
@@ -32,6 +32,7 @@ export declare class SocketIoLifecycleService implements OnApplicationBootstrap,
|
|
|
32
32
|
* @throws {Error} When the selected realtime capability cannot expose the server contract Socket.IO requires.
|
|
33
33
|
*/
|
|
34
34
|
getServer(): Server;
|
|
35
|
+
getServerAsync(): Promise<Server>;
|
|
35
36
|
/**
|
|
36
37
|
* Discovers gateway classes and binds their handlers to the resolved namespaces once per application lifecycle.
|
|
37
38
|
*/
|
|
@@ -77,6 +78,7 @@ export declare class SocketIoLifecycleService implements OnApplicationBootstrap,
|
|
|
77
78
|
*/
|
|
78
79
|
getRooms(socketId: string): ReadonlySet<string>;
|
|
79
80
|
private createServerOptions;
|
|
81
|
+
private createNodeServer;
|
|
80
82
|
private createBunEngineOptions;
|
|
81
83
|
private resolveCorsOptions;
|
|
82
84
|
private resolveMaxHttpBufferSize;
|
|
@@ -117,5 +119,6 @@ export declare class SocketIoLifecycleService implements OnApplicationBootstrap,
|
|
|
117
119
|
private shutdown;
|
|
118
120
|
private runShutdownLifecycle;
|
|
119
121
|
private closeServerWithTimeout;
|
|
122
|
+
private clearManagedState;
|
|
120
123
|
}
|
|
121
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":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,
|
|
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) {
|
|
@@ -195,15 +192,25 @@ class SocketIoLifecycleService {
|
|
|
195
192
|
}
|
|
196
193
|
const runtime = resolveSocketIoBootstrapRuntime(this.adapter);
|
|
197
194
|
if (runtime.kind === 'server-backed') {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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);
|
|
203
210
|
return this.io;
|
|
204
211
|
}
|
|
205
212
|
this.io = new Server(this.createServerOptions());
|
|
206
|
-
this.installBunSocketIoBinding(runtime, this.io);
|
|
213
|
+
await this.installBunSocketIoBinding(runtime, this.io);
|
|
207
214
|
return this.io;
|
|
208
215
|
}
|
|
209
216
|
|
|
@@ -216,11 +223,11 @@ class SocketIoLifecycleService {
|
|
|
216
223
|
}
|
|
217
224
|
const descriptors = this.discoverGatewayDescriptors();
|
|
218
225
|
if (descriptors.length === 0) {
|
|
219
|
-
this.ensureBunRealtimeBindingForRawServerAccess();
|
|
226
|
+
await this.ensureBunRealtimeBindingForRawServerAccess();
|
|
220
227
|
return;
|
|
221
228
|
}
|
|
222
229
|
this.assertNoServerBackedGatewayOptIn(descriptors);
|
|
223
|
-
const io = this.
|
|
230
|
+
const io = await this.getServerAsync();
|
|
224
231
|
const attachments = this.prepareNamespaceAttachments(io, descriptors);
|
|
225
232
|
for (const attachment of attachments) {
|
|
226
233
|
this.bindNamespaceHandlers(attachment);
|
|
@@ -317,6 +324,13 @@ class SocketIoLifecycleService {
|
|
|
317
324
|
}
|
|
318
325
|
return options;
|
|
319
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
|
+
}
|
|
320
334
|
createBunEngineOptions() {
|
|
321
335
|
const options = {
|
|
322
336
|
path: DEFAULT_SOCKETIO_ENGINE_PATH
|
|
@@ -332,16 +346,13 @@ class SocketIoLifecycleService {
|
|
|
332
346
|
};
|
|
333
347
|
}
|
|
334
348
|
resolveMaxHttpBufferSize() {
|
|
335
|
-
|
|
336
|
-
if (!isFinitePositiveInteger(configured)) {
|
|
337
|
-
return DEFAULT_SOCKETIO_MAX_HTTP_BUFFER_SIZE;
|
|
338
|
-
}
|
|
339
|
-
return configured;
|
|
349
|
+
return resolveSocketIoMaxHttpBufferSize(this.moduleOptions);
|
|
340
350
|
}
|
|
341
|
-
installBunSocketIoBinding(runtime, io) {
|
|
351
|
+
async installBunSocketIoBinding(runtime, io) {
|
|
342
352
|
if (this.bunEngine) {
|
|
343
353
|
return;
|
|
344
354
|
}
|
|
355
|
+
const BunEngineServer = await loadBunEngineServer();
|
|
345
356
|
const engine = new BunEngineServer(this.createBunEngineOptions());
|
|
346
357
|
io.bind(engine);
|
|
347
358
|
runtime.bindingHost.configureRealtimeBinding(this.createBunSocketIoBinding(engine));
|
|
@@ -378,11 +389,11 @@ class SocketIoLifecycleService {
|
|
|
378
389
|
matchesSocketIoEnginePath(pathname) {
|
|
379
390
|
return pathname === '/socket.io' || pathname === DEFAULT_SOCKETIO_ENGINE_PATH;
|
|
380
391
|
}
|
|
381
|
-
ensureBunRealtimeBindingForRawServerAccess() {
|
|
392
|
+
async ensureBunRealtimeBindingForRawServerAccess() {
|
|
382
393
|
if (!hasBunRealtimeBindingHost(this.adapter)) {
|
|
383
394
|
return;
|
|
384
395
|
}
|
|
385
|
-
this.
|
|
396
|
+
await this.getServerAsync();
|
|
386
397
|
}
|
|
387
398
|
assertNoServerBackedGatewayOptIn(descriptors) {
|
|
388
399
|
const runtime = resolveSocketIoBootstrapRuntime(this.adapter);
|
|
@@ -506,7 +517,7 @@ class SocketIoLifecycleService {
|
|
|
506
517
|
};
|
|
507
518
|
}
|
|
508
519
|
maxPendingMessagesPerSocket() {
|
|
509
|
-
return
|
|
520
|
+
return resolveSocketIoMaxPendingMessagesPerSocket(this.moduleOptions);
|
|
510
521
|
}
|
|
511
522
|
attachConnectionListeners(state, resolved, socket, request) {
|
|
512
523
|
socket.onAny((event, ...args) => {
|
|
@@ -710,13 +721,19 @@ class SocketIoLifecycleService {
|
|
|
710
721
|
createGatewayDescriptor(candidate, path) {
|
|
711
722
|
const metadata = getWebSocketGatewayMetadata(candidate.targetType);
|
|
712
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);
|
|
713
731
|
return {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
})),
|
|
732
|
+
connectHandlers: handlerIndex.connectHandlers,
|
|
733
|
+
disconnectHandlers: handlerIndex.disconnectHandlers,
|
|
734
|
+
handlers,
|
|
735
|
+
messageHandlersByEvent: handlerIndex.messageHandlersByEvent,
|
|
736
|
+
wildcardMessageHandlers: handlerIndex.wildcardMessageHandlers,
|
|
720
737
|
moduleName: candidate.moduleName,
|
|
721
738
|
path: normalizeGatewayPath(path),
|
|
722
739
|
serverBacked: metadata?.serverBacked,
|
|
@@ -758,11 +775,7 @@ class SocketIoLifecycleService {
|
|
|
758
775
|
return candidates;
|
|
759
776
|
}
|
|
760
777
|
resolveShutdownTimeoutMs() {
|
|
761
|
-
|
|
762
|
-
if (typeof configured !== 'number' || !Number.isFinite(configured) || configured <= 0) {
|
|
763
|
-
return DEFAULT_SOCKETIO_SHUTDOWN_TIMEOUT_MS;
|
|
764
|
-
}
|
|
765
|
-
return Math.floor(configured);
|
|
778
|
+
return resolveSocketIoShutdownTimeoutMs(this.moduleOptions);
|
|
766
779
|
}
|
|
767
780
|
async shutdown() {
|
|
768
781
|
if (this.shutdownPromise) {
|
|
@@ -779,51 +792,70 @@ class SocketIoLifecycleService {
|
|
|
779
792
|
}
|
|
780
793
|
async runShutdownLifecycle() {
|
|
781
794
|
const io = this.io;
|
|
782
|
-
this.wired = false;
|
|
783
795
|
if (!io) {
|
|
784
|
-
this.
|
|
785
|
-
this.socketRegistry.clear();
|
|
796
|
+
this.clearManagedState();
|
|
786
797
|
return;
|
|
787
798
|
}
|
|
799
|
+
let shouldClearManagedState = false;
|
|
800
|
+
const timeoutMs = this.resolveShutdownTimeoutMs();
|
|
788
801
|
try {
|
|
789
|
-
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
|
+
}
|
|
790
807
|
} catch (error) {
|
|
791
|
-
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');
|
|
792
809
|
} finally {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
this.attachments = [];
|
|
796
|
-
if (hasBunRealtimeBindingHost(this.adapter)) {
|
|
797
|
-
this.adapter.configureRealtimeBinding(undefined);
|
|
810
|
+
if (shouldClearManagedState) {
|
|
811
|
+
this.clearManagedState();
|
|
798
812
|
}
|
|
799
|
-
this.socketRegistry.clear();
|
|
800
813
|
}
|
|
801
814
|
}
|
|
802
815
|
closeServerWithTimeout(io, timeoutMs) {
|
|
803
|
-
return
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
io.httpServer = undefined;
|
|
815
|
-
io.close(() => {
|
|
816
|
-
if (settled) {
|
|
817
|
-
return;
|
|
818
|
-
}
|
|
819
|
-
settled = true;
|
|
820
|
-
clearTimeout(timeout);
|
|
821
|
-
resolve();
|
|
822
|
-
});
|
|
823
|
-
});
|
|
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();
|
|
824
827
|
}
|
|
825
828
|
static {
|
|
826
829
|
_initClass();
|
|
827
830
|
}
|
|
828
831
|
}
|
|
829
|
-
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/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",
|