🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@fluojs/websockets

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fluojs/websockets - npm Package Compare versions

Comparing version
1.0.0
to
1.0.1
+12
-1
dist/node/node-service.d.ts

@@ -21,3 +21,5 @@ import type { Container } from '@fluojs/di';

private heartbeatTimer;
private isShuttingDown;
private ownedUpgradeServers;
private readonly pendingUpgradeOperations;
private pendingUpgradeReservations;

@@ -30,2 +32,3 @@ private readonly pingPending;

private readonly socketRooms;
private readonly socketStates;
private upgradeListener;

@@ -63,2 +66,4 @@ private upgradeServer;

private createConnectionHandlerState;
private settleConnectLifecycle;
private settleDisconnectLifecycle;
private getBufferedMessageCount;

@@ -91,2 +96,4 @@ private getQueuedMessageCount;

private runShutdownLifecycle;
private trackPendingUpgradeOperation;
private awaitPendingUpgradeOperations;
private stopHeartbeat;

@@ -98,3 +105,5 @@ private detachUpgradeServerListener;

private closeGatewayAttachments;
private terminateAttachmentClients;
private closeAttachmentClients;
private awaitHandlerQueueDrain;
private scheduleSocketStateCleanup;
private closeGatewayAttachment;

@@ -132,4 +141,6 @@ private clearConnectionTrackingState;

private startHeartbeat;
private unregisterSocketWithDeferredStateCleanup;
private unregisterTrackedSocketWithDeferredStateCleanup;
private unregisterSocket;
}
//# sourceMappingURL=node-service.d.ts.map
+1
-1

@@ -1,1 +0,1 @@

{"version":3,"file":"node-service.d.ts","sourceRoot":"","sources":["../../src/node/node-service.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"node-service.d.ts","sourceRoot":"","sources":["../../src/node/node-service.ts"],"names":[],"mappings":"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"}

@@ -106,2 +106,12 @@ let _initClass;

}
function createCompletionSignal() {
let resolve;
const promise = new Promise(res => {
resolve = res;
});
return {
promise,
resolve
};
}

@@ -122,3 +132,5 @@ /**

heartbeatTimer;
isShuttingDown = false;
ownedUpgradeServers = [];
pendingUpgradeOperations = new Set();
pendingUpgradeReservations = 0;

@@ -131,2 +143,3 @@ pingPending = new Set();

socketRooms = new Map();
socketStates = new Map();
upgradeListener;

@@ -278,3 +291,3 @@ upgradeServer;

socket.pause();
void this.handleUpgradeRequest(upgradeServer, attachmentsByPath, request, socket, head).catch(error => {
void this.trackPendingUpgradeOperation(this.handleUpgradeRequest(upgradeServer, attachmentsByPath, request, socket, head)).catch(error => {
this.logger.error('WebSocket upgrade admission failed.', error, 'WebSocketGatewayLifecycleService');

@@ -335,5 +348,16 @@ rejectUpgradeRequestWithStatus(socket, {

this.attachConnectionListeners(state, socket, request);
await this.resolveConnectionGateways(descriptors, state);
await this.runConnectHandlers(state, socket, request);
await this.finalizeConnectionBinding(state, socket, request);
try {
await this.resolveConnectionGateways(descriptors, state);
await this.runConnectHandlers(state, socket, request);
await this.finalizeConnectionBinding(state, socket, request);
if (this.isShuttingDown && socket.readyState === WebSocket.OPEN) {
socket.close(1001, 'Server shutting down');
await state.disconnectLifecyclePromise;
}
} finally {
if (!state.handlersReady && state.bufferedDisconnect) {
this.settleDisconnectLifecycle(state);
}
this.settleConnectLifecycle(state);
}
}

@@ -343,2 +367,3 @@ registerSocketConnection(state, socket) {

this.socketRegistry.set(state.socketId, socket);
this.socketStates.set(state.socketId, state);
}

@@ -351,2 +376,4 @@ async finalizeConnectionBinding(state, socket, request) {

createConnectionHandlerState() {
const connectLifecycle = createCompletionSignal();
const disconnectLifecycle = createCompletionSignal();
return {

@@ -356,2 +383,7 @@ bufferedDisconnect: undefined,

bufferedMessagesStartIndex: 0,
cleanupScheduled: false,
connectLifecyclePromise: connectLifecycle.promise,
connectLifecycleSettled: false,
disconnectLifecyclePromise: disconnectLifecycle.promise,
disconnectLifecycleSettled: false,
enqueuedMessageCount: 0,

@@ -363,2 +395,4 @@ handlerQueue: Promise.resolve(),

queuedMessagesStartIndex: 0,
resolveConnectLifecycle: connectLifecycle.resolve,
resolveDisconnectLifecycle: disconnectLifecycle.resolve,
resolved: [],

@@ -368,2 +402,16 @@ socketId: randomUUID()

}
settleConnectLifecycle(state) {
if (state.connectLifecycleSettled) {
return;
}
state.connectLifecycleSettled = true;
state.resolveConnectLifecycle();
}
settleDisconnectLifecycle(state) {
if (state.disconnectLifecycleSettled) {
return;
}
state.disconnectLifecycleSettled = true;
state.resolveDisconnectLifecycle();
}
getBufferedMessageCount(state) {

@@ -407,3 +455,3 @@ return state.bufferedMessages.length - (state.bufferedMessagesStartIndex ?? 0);

this.clearQueuedMessages(state);
this.unregisterSocket(state.socketId);
this.unregisterSocketWithDeferredStateCleanup(state);
this.logger.warn(`WebSocket connection ${state.socketId} exceeded ready-state message queue limit (${String(limit)}). Connection terminated.`, 'WebSocketGatewayLifecycleService');

@@ -454,2 +502,4 @@ return;

this.logger.error('WebSocket gateway disconnect dispatch failed.', error, 'WebSocketGatewayLifecycleService');
}).finally(() => {
this.settleDisconnectLifecycle(state);
});

@@ -473,7 +523,12 @@ }

socket.on('error', error => {
this.unregisterSocket(state.socketId);
this.unregisterSocket(state.socketId, {
deleteState: false
});
this.scheduleSocketStateCleanup(state);
this.logger.error('WebSocket gateway socket emitted an error.', error, 'WebSocketGatewayLifecycleService');
});
socket.on('close', (code, reason) => {
this.unregisterSocket(state.socketId);
this.unregisterSocket(state.socketId, {
deleteState: false
});
const disconnectEvent = {

@@ -485,5 +540,7 @@ code,

state.bufferedDisconnect = disconnectEvent;
this.scheduleSocketStateCleanup(state);
return;
}
this.enqueueDisconnectDispatch(state, socket, disconnectEvent);
this.scheduleSocketStateCleanup(state);
});

@@ -545,3 +602,3 @@ }

if (socket.readyState !== WebSocket.OPEN && socket.readyState !== WebSocket.CONNECTING) {
this.unregisterSocket(state.socketId);
this.unregisterSocketWithDeferredStateCleanup(state);
}

@@ -553,2 +610,8 @@ }

async resolveUpgradeRejection(request, path) {
if (this.isShuttingDown) {
return {
body: 'WebSocket server is shutting down.',
status: 503
};
}
if (!this.tryReserveUpgradeSlot()) {

@@ -569,2 +632,9 @@ return {

});
if (this.isShuttingDown) {
this.releaseUpgradeReservation();
return {
body: 'WebSocket server is shutting down.',
status: 503
};
}
if (result === false) {

@@ -671,2 +741,3 @@ this.releaseUpgradeReservation();

async runShutdownLifecycle() {
this.isShuttingDown = true;
this.stopHeartbeat();

@@ -677,2 +748,3 @@ this.detachUpgradeServerListener();

const shutdownTimeoutMs = this.resolveShutdownTimeoutMs();
await this.awaitPendingUpgradeOperations(shutdownTimeoutMs);
await this.closeGatewayAttachments(attachments, shutdownTimeoutMs);

@@ -682,2 +754,44 @@ await this.closeOwnedUpgradeServers(ownedUpgradeServers, shutdownTimeoutMs);

}
trackPendingUpgradeOperation(operation) {
let trackedOperation;
trackedOperation = operation.then(() => undefined, () => undefined).finally(() => {
if (trackedOperation) {
this.pendingUpgradeOperations.delete(trackedOperation);
}
});
this.pendingUpgradeOperations.add(trackedOperation);
return operation;
}
async awaitPendingUpgradeOperations(timeoutMs) {
if (this.pendingUpgradeOperations.size === 0) {
return;
}
await new Promise((resolve, reject) => {
let settled = false;
const timeout = setTimeout(() => {
if (settled) {
return;
}
settled = true;
reject(new Error(`Timed out while waiting for in-flight Node websocket upgrades after ${String(timeoutMs)}ms.`));
}, timeoutMs);
Promise.all([...this.pendingUpgradeOperations]).then(() => {
if (settled) {
return;
}
settled = true;
clearTimeout(timeout);
resolve();
}).catch(error => {
if (settled) {
return;
}
settled = true;
clearTimeout(timeout);
reject(error);
});
}).catch(error => {
this.logger.error(`Failed to wait for in-flight Node websocket upgrades within ${String(timeoutMs)}ms.`, error, 'WebSocketGatewayLifecycleService');
});
}
stopHeartbeat() {

@@ -748,11 +862,61 @@ if (!this.heartbeatTimer) {

await Promise.all(attachments.map(async attachment => {
this.terminateAttachmentClients(attachment);
await this.closeAttachmentClients(attachment, shutdownTimeoutMs);
await this.closeGatewayAttachment(attachment, shutdownTimeoutMs);
}));
}
terminateAttachmentClients(attachment) {
async closeAttachmentClients(attachment, timeoutMs) {
const states = [...this.socketStates.values()];
for (const client of attachment.server.clients) {
client.terminate();
if (client.readyState === WebSocket.OPEN) {
client.close(1001, 'Server shutting down');
} else if (client.readyState === WebSocket.CONNECTING) {
client.terminate();
}
}
await this.awaitHandlerQueueDrain(states, timeoutMs);
}
async awaitHandlerQueueDrain(states, timeoutMs) {
if (states.length === 0) {
return;
}
await new Promise((resolve, reject) => {
let settled = false;
const timeout = setTimeout(() => {
if (settled) {
return;
}
settled = true;
reject(new Error(`Timed out while closing Node websocket connections after ${String(timeoutMs)}ms.`));
}, timeoutMs);
Promise.all(states.map(async state => {
await state.connectLifecyclePromise;
await state.disconnectLifecyclePromise;
})).then(() => {
if (settled) {
return;
}
settled = true;
clearTimeout(timeout);
resolve();
}).catch(error => {
if (settled) {
return;
}
settled = true;
clearTimeout(timeout);
reject(error);
});
}).catch(error => {
this.logger.error(`Failed to close Node websocket connections within ${String(timeoutMs)}ms.`, error, 'WebSocketGatewayLifecycleService');
});
}
scheduleSocketStateCleanup(state) {
if (state.cleanupScheduled) {
return;
}
state.cleanupScheduled = true;
void Promise.all([state.connectLifecyclePromise, state.disconnectLifecyclePromise]).finally(() => {
this.socketStates.delete(state.socketId);
});
}
async closeGatewayAttachment(attachment, shutdownTimeoutMs) {

@@ -768,2 +932,3 @@ try {

this.socketRooms.clear();
this.socketStates.clear();
this.roomSockets.clear();

@@ -839,3 +1004,3 @@ this.pingPending.clear();

socket.terminate();
this.unregisterSocket(socketId);
this.unregisterTrackedSocketWithDeferredStateCleanup(socketId);
this.logger.warn(`WebSocket connection ${socketId} exceeded bufferedAmount threshold (${String(maxBufferedAmountBytes)} bytes). Connection terminated.`, 'WebSocketGatewayLifecycleService');

@@ -877,3 +1042,3 @@ continue;

socket.terminate();
this.unregisterSocket(socketId);
this.unregisterTrackedSocketWithDeferredStateCleanup(socketId);
}

@@ -890,4 +1055,21 @@ continue;

}
unregisterSocket(socketId) {
unregisterSocketWithDeferredStateCleanup(state) {
this.unregisterSocket(state.socketId, {
deleteState: false
});
this.scheduleSocketStateCleanup(state);
}
unregisterTrackedSocketWithDeferredStateCleanup(socketId) {
const state = this.socketStates.get(socketId);
if (!state) {
this.unregisterSocket(socketId);
return;
}
this.unregisterSocketWithDeferredStateCleanup(state);
}
unregisterSocket(socketId, options = {}) {
this.socketRegistry.delete(socketId);
if (options.deleteState !== false) {
this.socketStates.delete(socketId);
}
this.pingPending.delete(socketId);

@@ -894,0 +1076,0 @@ this.pingSentAt.delete(socketId);

@@ -12,3 +12,3 @@ {

],
"version": "1.0.0",
"version": "1.0.1",
"private": false,

@@ -73,6 +73,6 @@ "license": "MIT",

"ws": "^8.18.3",
"@fluojs/core": "^1.0.0",
"@fluojs/di": "^1.0.0",
"@fluojs/core": "^1.0.1",
"@fluojs/di": "^1.0.1",
"@fluojs/http": "^1.0.0",
"@fluojs/runtime": "^1.0.0"
"@fluojs/runtime": "^1.0.1"
},

@@ -82,5 +82,6 @@ "devDependencies": {

"vitest": "^3.2.4",
"@fluojs/platform-bun": "^1.0.0",
"@fluojs/platform-express": "^1.0.0",
"@fluojs/platform-fastify": "^1.0.0"
"@fluojs/platform-bun": "^1.0.1",
"@fluojs/platform-express": "^1.0.1",
"@fluojs/platform-fastify": "^1.0.1",
"@fluojs/testing": "^1.0.1"
},

@@ -87,0 +88,0 @@ "scripts": {

@@ -105,3 +105,3 @@ # @fluojs/websockets

옵션을 생략하면 `@fluojs/websockets`는 동시 연결 수, inbound payload 크기, pending message buffer, shutdown cleanup에 bounded default를 적용합니다. 기본값은 `maxConnections: 1000`, `maxPayloadBytes: 1 MiB`, `buffer.maxPendingMessagesPerSocket: 256`, `shutdown.timeoutMs: 5000`, Node heartbeat interval `30s`, Node backpressure `maxBufferedAmountBytes: 1 MiB`와 drop behavior입니다. 또한 server-backed Node listener는 `heartbeat.enabled`를 명시적으로 `false`로 두지 않는 한 heartbeat timer를 활성화합니다. 공식 fetch-style runtime module(`@fluojs/websockets/bun`, `@fluojs/websockets/deno`, `@fluojs/websockets/cloudflare-workers`)은 애플리케이션 shutdown 시 추적 중인 websocket 클라이언트를 닫고 `shutdown.timeoutMs` 범위 안에서 `@OnDisconnect()` cleanup이 마무리될 수 있도록 bounded 기회를 제공합니다.
옵션을 생략하면 `@fluojs/websockets`는 동시 연결 수, inbound payload 크기, pending message buffer, shutdown cleanup에 bounded default를 적용합니다. 기본값은 `maxConnections: 1000`, `maxPayloadBytes: 1 MiB`, `buffer.maxPendingMessagesPerSocket: 256`, `shutdown.timeoutMs: 5000`, Node heartbeat interval `30s`, Node backpressure `maxBufferedAmountBytes: 1 MiB`와 drop behavior입니다. 또한 server-backed Node listener는 `heartbeat.enabled`를 명시적으로 `false`로 두지 않는 한 heartbeat timer를 활성화합니다. Node shutdown은 shutdown이 시작된 뒤 in-flight async upgrade를 거절하고, 애플리케이션 shutdown 시 추적 중인 websocket 클라이언트를 닫고, `shutdown.timeoutMs` 범위 안에서 `@OnDisconnect()` cleanup이 마무리될 수 있도록 bounded 기회를 제공합니다. 공식 fetch-style runtime module(`@fluojs/websockets/bun`, `@fluojs/websockets/deno`, `@fluojs/websockets/cloudflare-workers`)도 애플리케이션 shutdown 중 동일한 bounded close와 disconnect cleanup 동작을 제공합니다.

@@ -121,2 +121,3 @@ ## 바이너리 페이로드

- `WebSocketGatewayLifecycleService`: 기본 Node.js 기반 lifecycle service token을 위한 루트 alias입니다.
- `WebSocketRoomService`: websocket room join, leave, broadcast, 조회를 위해 runtime lifecycle service가 구현하는 Room management contract입니다.
- Metadata helper와 symbol: `defineWebSocketGatewayMetadata`, `getWebSocketGatewayMetadata`, `defineWebSocketHandlerMetadata`, `getWebSocketHandlerMetadata`, `getWebSocketHandlerMetadataEntries`, `webSocketGatewayMetadataSymbol`, `webSocketHandlerMetadataSymbol`.

@@ -123,0 +124,0 @@

@@ -105,3 +105,3 @@ # @fluojs/websockets

When omitted, `@fluojs/websockets` applies bounded defaults for concurrent connections, inbound payload size, pending message buffers, and shutdown cleanup. Default settings are `maxConnections: 1000`, `maxPayloadBytes: 1 MiB`, `buffer.maxPendingMessagesPerSocket: 256`, `shutdown.timeoutMs: 5000`, Node heartbeat interval `30s`, and Node backpressure `maxBufferedAmountBytes: 1 MiB` with drop behavior. Server-backed Node listeners enable heartbeat timers unless you explicitly set `heartbeat.enabled` to `false`. The official fetch-style runtime modules (`@fluojs/websockets/bun`, `@fluojs/websockets/deno`, and `@fluojs/websockets/cloudflare-workers`) close tracked websocket clients during application shutdown and give `@OnDisconnect()` cleanup a bounded chance to finish within `shutdown.timeoutMs`.
When omitted, `@fluojs/websockets` applies bounded defaults for concurrent connections, inbound payload size, pending message buffers, and shutdown cleanup. Default settings are `maxConnections: 1000`, `maxPayloadBytes: 1 MiB`, `buffer.maxPendingMessagesPerSocket: 256`, `shutdown.timeoutMs: 5000`, Node heartbeat interval `30s`, and Node backpressure `maxBufferedAmountBytes: 1 MiB` with drop behavior. Server-backed Node listeners enable heartbeat timers unless you explicitly set `heartbeat.enabled` to `false`. Node shutdown rejects in-flight async upgrades once shutdown begins, will close tracked websocket clients during application shutdown, and gives `@OnDisconnect()` cleanup a bounded chance to finish within `shutdown.timeoutMs`. The official fetch-style runtime modules (`@fluojs/websockets/bun`, `@fluojs/websockets/deno`, and `@fluojs/websockets/cloudflare-workers`) provide the same bounded close and disconnect cleanup behavior during application shutdown.

@@ -121,2 +121,3 @@ ## Binary Payloads

- `WebSocketGatewayLifecycleService`: Root alias for the default Node.js-backed lifecycle service token.
- `WebSocketRoomService`: Room management contract implemented by runtime lifecycle services for joining, leaving, broadcasting to, and inspecting websocket rooms.
- Metadata helpers and symbols: `defineWebSocketGatewayMetadata`, `getWebSocketGatewayMetadata`, `defineWebSocketHandlerMetadata`, `getWebSocketHandlerMetadata`, `getWebSocketHandlerMetadataEntries`, `webSocketGatewayMetadataSymbol`, `webSocketHandlerMetadataSymbol`.

@@ -123,0 +124,0 @@