@fluojs/socket.io
v1.0.7
Published
Socket.IO v4 gateway adapter for Fluo applications, built on the shared runtime and websocket decorators.
Maintainers
Readme
@fluojs/socket.io
Socket.IO v4 gateway adapter for the fluo runtime.
Table of Contents
- Installation
- When to Use
- Quick Start
- Common Patterns
- Public API Overview
- Supported Platforms
- Example Sources
Installation
npm install @fluojs/core @fluojs/socket.io @fluojs/websockets socket.ioWhen to Use
Use this package when you need advanced real-time features like rooms, namespaces, broadcasting, and automatic reconnection provided by Socket.IO. This adapter integrates Socket.IO v4 into fluo's decorator-based architecture, sharing the same @WebSocketGateway core as raw websockets.
Quick Start
import { SocketIoModule, SOCKETIO_ROOM_SERVICE, type SocketIoRoomService } from '@fluojs/socket.io';
import { WebSocketGateway, OnMessage } from '@fluojs/websockets';
import { Inject, Module } from '@fluojs/core';
@Inject(SOCKETIO_ROOM_SERVICE)
@WebSocketGateway({ path: '/chat' })
class ChatGateway {
constructor(private readonly rooms: SocketIoRoomService) {}
@OnMessage('ping')
handlePing(payload: unknown) {
this.rooms.broadcastToRoom('chat:lobby', 'pong', payload);
}
}
@Module({
imports: [SocketIoModule.forRoot()],
providers: [ChatGateway],
})
export class AppModule {}Common Patterns
Room Management
The SocketIoRoomService provides a high-level API for managing client rooms and broadcasting.
this.rooms.joinRoom(socket.id, 'room:123');
this.rooms.broadcastToRoom('room:123', 'event', data);Room helpers follow the shared WebSocketRoomService contract and add Socket.IO namespace awareness. Inside a gateway handler the current @WebSocketGateway({ path }) namespace is inferred automatically. When room helpers run outside gateway handler context, pass the namespace path explicitly so room operations target the intended Socket.IO namespace instead of a same-named room elsewhere.
this.rooms.broadcastToRoom('room:123', 'event', data, '/chat');
this.rooms.joinRoom(socketId, 'room:123', '/chat');Accessing the Raw Server
You can inject the underlying Socket.IO Server instance for low-level control.
import { SOCKETIO_SERVER } from '@fluojs/socket.io';
import type { Server } from 'socket.io';
@Inject(SOCKETIO_SERVER)
class MyService {
constructor(private readonly io: Server) {}
}Keep raw server access narrow and use it for Socket.IO-specific semantics that the shared room contract intentionally does not wrap, such as multi-room native emits or volatile delivery:
@Inject(SOCKETIO_SERVER)
class SupportBroadcasts {
constructor(private readonly io: Server) {}
broadcastUrgent(message: string) {
this.io.of('/support').to(['ticket:active', 'staff:updates']).emit('announcement', { message });
}
sendTyping(ticketId: string, userId: string) {
this.io.of('/support').volatile.to(`ticket:${ticketId}`).emit('typing', { userId });
}
}Handler return values and ACK replies
Socket.IO gateway handlers use the shared @fluojs/websockets positional handler model: (payload, socket, request, acknowledgement). Return values are awaited for error containment and ordering, but they are ignored; fluo does not convert a returned value into an implicit Socket.IO emit or ACK reply. When a migrated NestJS @SubscribeMessage() handler previously returned an ACK payload, rewrite it to call the acknowledgement callback explicitly or inject SOCKETIO_SERVER and emit through the raw server boundary.
@OnMessage('ping')
handlePing(payload: unknown, _socket: Socket, _request: SocketIoHandshakeRequest, ack?: (response: unknown) => void) {
ack?.({ event: 'pong', payload });
}Auth guards, safe CORS defaults, and bounded payloads
Use SocketIoModule.forRoot(...) to require explicit namespace/message auth, keep CORS in a deny-by-default posture, and cap inbound Engine.IO payload size.
SocketIoModule.forRoot({
auth: {
connection({ socket }) {
return socket.handshake.auth.token === 'demo-token'
? true
: { message: 'Authentication required.' };
},
message({ payload }) {
return payload === 'allowed'
? true
: { message: 'Forbidden event.' };
},
},
cors: {
origin: ['https://app.example.com'],
},
engine: {
maxHttpBufferSize: 65_536,
},
});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.
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.
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.
Guard contracts
auth.connection receives SocketIoConnectionGuardContext before namespace connect handlers run. auth.message receives SocketIoMessageGuardContext before message handlers run. Guards can return true, false, or a SocketIoGuardRejection with message, optional data, and optional disconnect; message rejections use ACK payloads shaped as { error, data }.
Bun-specific notes
The Bun path supports Socket.IO through @socket.io/bun-engine, but it requires static CORS shapes: no CORS delegate functions and no boolean entries inside cors.origin arrays. @WebSocketGateway({ serverBacked }) is not supported on Bun. Bun's HTTP request body limit (maxRequestBodySize) and WebSocket frame limit (websocket.maxPayloadLength) are separate host contracts; the adapter maps both from engine.maxHttpBufferSize so polling requests and websocket frames share the configured inbound payload bound.
Module registration
Register Socket.IO with SocketIoModule.forRoot(...).
Register Socket.IO through module imports in the owning module so namespace/message guards, CORS, and Engine.IO options stay configured in one place.
Public API Overview
SocketIoModule.forRoot(options): Main module for Socket.IO integration.SocketIoModule.forRoot({ global, auth, cors, engine, ... }): Configures provider visibility, namespace/message guards, explicit CORS, and Engine.IO payload bounds.SOCKETIO_SERVER: Token to inject the raw Socket.IOServer.SOCKETIO_ROOM_SERVICE: Token to inject theSocketIoRoomService.SocketIoRoomService: Shared room contract plus Socket.IO namespace-awarejoinRoom,leaveRoom,broadcastToRoom, andgetRoomshelpers.SocketIoLifecycleService: Lifecycle-backed implementation behind the server and room-service tokens; application code should usually injectSOCKETIO_SERVERorSOCKETIO_ROOM_SERVICEinstead.- Types:
SocketIoModuleOptions,SocketIoHandshakeRequest,SocketIoConnectionGuardContext,SocketIoConnectionGuard,SocketIoMessageGuardContext,SocketIoMessageGuard,SocketIoGuardRejection.
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 Node.js server-backed runtime adapter or the official Bun engine host is required; unsupported/noop adapters fail fast during bootstrap.
Supported Platforms
| Platform | Support | Note |
| --- | --- | --- |
| Node.js (Raw/Express/Fastify) | ✅ Full | Server-backed mode |
| Bun | ✅ Full | Via @socket.io/bun-engine |
| Deno | ❌ None | Not currently supported |
| Workers | ❌ None | Not currently supported |
Example Sources
packages/socket.io/src/config.internal.test.tspackages/socket.io/src/module.test.tspackages/socket.io/src/public-surface.test.ts
