@fluojs/socket.io 1.0.4 → 1.0.6
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 +102 -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/package.json +8 -8
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,12 @@ class SocketIoLifecycleService {
|
|
|
378
389
|
matchesSocketIoEnginePath(pathname) {
|
|
379
390
|
return pathname === '/socket.io' || pathname === DEFAULT_SOCKETIO_ENGINE_PATH;
|
|
380
391
|
}
|
|
381
|
-
ensureBunRealtimeBindingForRawServerAccess() {
|
|
382
|
-
|
|
392
|
+
async ensureBunRealtimeBindingForRawServerAccess() {
|
|
393
|
+
const runtime = resolveSocketIoBootstrapRuntime(this.adapter);
|
|
394
|
+
if (runtime.kind !== 'bun') {
|
|
383
395
|
return;
|
|
384
396
|
}
|
|
385
|
-
this.
|
|
397
|
+
await this.getServerAsync();
|
|
386
398
|
}
|
|
387
399
|
assertNoServerBackedGatewayOptIn(descriptors) {
|
|
388
400
|
const runtime = resolveSocketIoBootstrapRuntime(this.adapter);
|
|
@@ -506,7 +518,7 @@ class SocketIoLifecycleService {
|
|
|
506
518
|
};
|
|
507
519
|
}
|
|
508
520
|
maxPendingMessagesPerSocket() {
|
|
509
|
-
return
|
|
521
|
+
return resolveSocketIoMaxPendingMessagesPerSocket(this.moduleOptions);
|
|
510
522
|
}
|
|
511
523
|
attachConnectionListeners(state, resolved, socket, request) {
|
|
512
524
|
socket.onAny((event, ...args) => {
|
|
@@ -710,13 +722,19 @@ class SocketIoLifecycleService {
|
|
|
710
722
|
createGatewayDescriptor(candidate, path) {
|
|
711
723
|
const metadata = getWebSocketGatewayMetadata(candidate.targetType);
|
|
712
724
|
const entries = getWebSocketHandlerMetadataEntries(candidate.targetType.prototype);
|
|
725
|
+
const handlers = entries.map(entry => ({
|
|
726
|
+
event: entry.metadata.event,
|
|
727
|
+
methodKey: entry.propertyKey,
|
|
728
|
+
methodName: methodKeyToName(entry.propertyKey),
|
|
729
|
+
type: entry.metadata.type
|
|
730
|
+
}));
|
|
731
|
+
const handlerIndex = createGatewayHandlerIndex(handlers);
|
|
713
732
|
return {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
})),
|
|
733
|
+
connectHandlers: handlerIndex.connectHandlers,
|
|
734
|
+
disconnectHandlers: handlerIndex.disconnectHandlers,
|
|
735
|
+
handlers,
|
|
736
|
+
messageHandlersByEvent: handlerIndex.messageHandlersByEvent,
|
|
737
|
+
wildcardMessageHandlers: handlerIndex.wildcardMessageHandlers,
|
|
720
738
|
moduleName: candidate.moduleName,
|
|
721
739
|
path: normalizeGatewayPath(path),
|
|
722
740
|
serverBacked: metadata?.serverBacked,
|
|
@@ -758,11 +776,7 @@ class SocketIoLifecycleService {
|
|
|
758
776
|
return candidates;
|
|
759
777
|
}
|
|
760
778
|
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);
|
|
779
|
+
return resolveSocketIoShutdownTimeoutMs(this.moduleOptions);
|
|
766
780
|
}
|
|
767
781
|
async shutdown() {
|
|
768
782
|
if (this.shutdownPromise) {
|
|
@@ -779,51 +793,70 @@ class SocketIoLifecycleService {
|
|
|
779
793
|
}
|
|
780
794
|
async runShutdownLifecycle() {
|
|
781
795
|
const io = this.io;
|
|
782
|
-
this.wired = false;
|
|
783
796
|
if (!io) {
|
|
784
|
-
this.
|
|
785
|
-
this.socketRegistry.clear();
|
|
797
|
+
this.clearManagedState();
|
|
786
798
|
return;
|
|
787
799
|
}
|
|
800
|
+
let shouldClearManagedState = false;
|
|
801
|
+
const timeoutMs = this.resolveShutdownTimeoutMs();
|
|
788
802
|
try {
|
|
789
|
-
await this.closeServerWithTimeout(io,
|
|
803
|
+
const result = await this.closeServerWithTimeout(io, timeoutMs);
|
|
804
|
+
shouldClearManagedState = true;
|
|
805
|
+
if (result.kind === 'forced') {
|
|
806
|
+
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');
|
|
807
|
+
}
|
|
790
808
|
} catch (error) {
|
|
791
|
-
this.logger.error(`Failed to close Socket.IO server within ${String(
|
|
809
|
+
this.logger.error(`Failed to close Socket.IO server within ${String(timeoutMs)}ms; retaining managed Socket.IO state for shutdown retry.`, error, 'SocketIoLifecycleService');
|
|
792
810
|
} finally {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
this.attachments = [];
|
|
796
|
-
if (hasBunRealtimeBindingHost(this.adapter)) {
|
|
797
|
-
this.adapter.configureRealtimeBinding(undefined);
|
|
811
|
+
if (shouldClearManagedState) {
|
|
812
|
+
this.clearManagedState();
|
|
798
813
|
}
|
|
799
|
-
this.socketRegistry.clear();
|
|
800
814
|
}
|
|
801
815
|
}
|
|
802
816
|
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
|
-
});
|
|
817
|
+
return closeSocketIoServerWithTimeout(io, timeoutMs);
|
|
818
|
+
}
|
|
819
|
+
clearManagedState() {
|
|
820
|
+
this.wired = false;
|
|
821
|
+
this.io = undefined;
|
|
822
|
+
this.bunEngine = undefined;
|
|
823
|
+
this.attachments = [];
|
|
824
|
+
if (hasBunRealtimeBindingHost(this.adapter)) {
|
|
825
|
+
this.adapter.configureRealtimeBinding(undefined);
|
|
826
|
+
}
|
|
827
|
+
this.socketRegistry.clear();
|
|
824
828
|
}
|
|
825
829
|
static {
|
|
826
830
|
_initClass();
|
|
827
831
|
}
|
|
828
832
|
}
|
|
829
|
-
export { _SocketIoLifecycleSer as SocketIoLifecycleService };
|
|
833
|
+
export { _SocketIoLifecycleSer as SocketIoLifecycleService };
|
|
834
|
+
function createGatewayHandlerIndex(handlers) {
|
|
835
|
+
const connectHandlers = [];
|
|
836
|
+
const disconnectHandlers = [];
|
|
837
|
+
const messageHandlersByEvent = new Map();
|
|
838
|
+
const wildcardMessageHandlers = [];
|
|
839
|
+
for (const handler of handlers) {
|
|
840
|
+
if (handler.type === 'connect') {
|
|
841
|
+
connectHandlers.push(handler);
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
if (handler.type === 'disconnect') {
|
|
845
|
+
disconnectHandlers.push(handler);
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
if (handler.event === undefined) {
|
|
849
|
+
wildcardMessageHandlers.push(handler);
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
const eventHandlers = messageHandlersByEvent.get(handler.event) ?? [];
|
|
853
|
+
eventHandlers.push(handler);
|
|
854
|
+
messageHandlersByEvent.set(handler.event, eventHandlers);
|
|
855
|
+
}
|
|
856
|
+
return {
|
|
857
|
+
connectHandlers,
|
|
858
|
+
disconnectHandlers,
|
|
859
|
+
messageHandlersByEvent,
|
|
860
|
+
wildcardMessageHandlers
|
|
861
|
+
};
|
|
862
|
+
}
|
|
@@ -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.6",
|
|
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
|
|
42
|
-
"@fluojs/http": "^1.1.
|
|
43
|
-
"@fluojs/runtime": "^1.1.
|
|
44
|
-
"@fluojs/websockets": "^1.0.
|
|
41
|
+
"@fluojs/di": "^1.1.0",
|
|
42
|
+
"@fluojs/http": "^1.1.1",
|
|
43
|
+
"@fluojs/runtime": "^1.1.7",
|
|
44
|
+
"@fluojs/websockets": "^1.0.6"
|
|
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.6",
|
|
54
|
+
"@fluojs/platform-fastify": "^1.0.7",
|
|
55
|
+
"@fluojs/platform-nodejs": "^1.0.5"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"prebuild": "node ../../tooling/scripts/clean-dist.mjs",
|