@@ -160,3 +160,5 @@ import { WebSocket } from "./web.mjs"; | ||
| declare function redis(opts: { | ||
| /** Redis client used to `PUBLISH`; a subscriber is `duplicate()`d from it. */client: RedisClientLike; /** Pub/sub channel to relay over. */ | ||
| /** Redis client used to `PUBLISH`; a subscriber is `duplicate()`d from it. */ | ||
| client: RedisClientLike; | ||
| /** Pub/sub channel to relay over. */ | ||
| channel: string; | ||
@@ -252,3 +254,5 @@ /** | ||
| declare function pgsql(opts: { | ||
| /** Connected Postgres client used to both `LISTEN` and `NOTIFY`. */client: PostgresClientLike; /** Notification channel to relay over. */ | ||
| /** Connected Postgres client used to both `LISTEN` and `NOTIFY`. */ | ||
| client: PostgresClientLike; | ||
| /** Notification channel to relay over. */ | ||
| channel: string; | ||
@@ -255,0 +259,0 @@ /** |
+17
-0
@@ -62,2 +62,19 @@ import { Adapter, AdapterInstance, AdapterInternal, AdapterOptions, Hooks, Message, Peer, PeerContext, ResolveHooks, SyncAdapter, SyncDriver, SyncErrorContext, SyncMessage, WSError, WaitForDrainOptions, defineHooks, defineWebSocketAdapter } from "./_chunks/adapter.mjs"; | ||
| /** | ||
| * Milliseconds of **client→proxy** inactivity after which both the peer | ||
| * and the upstream connection are closed (peer close code `1001`). | ||
| * | ||
| * The timer is reset by every inbound frame the client sends and is | ||
| * unaffected by upstream→client traffic. This is driven by real application frames, so | ||
| * it reclaims a connection whose client vanished behind such an | ||
| * intermediary and left the upstream socket dangling. | ||
| * | ||
| * Only enable this for protocols where the client is expected to send | ||
| * traffic periodically (e.g. a heartbeat / keepalive message). For | ||
| * server-push-only protocols where the client may be legitimately silent, | ||
| * leave it disabled or it will close idle-but-live connections. | ||
| * | ||
| * @default 0 (disabled) | ||
| */ | ||
| clientIdleTimeout?: number; | ||
| /** | ||
| * Custom `WebSocket` constructor used to dial the upstream. Useful when | ||
@@ -64,0 +81,0 @@ * the runtime does not expose a global `WebSocket` (Node.js < 22) or |
+25
-1
@@ -11,2 +11,3 @@ import { defineHooks, defineWebSocketAdapter } from "./_chunks/adapter.mjs"; | ||
| const upstreams = /* @__PURE__ */ new Map(); | ||
| const clientIdleTimeoutMs = options.clientIdleTimeout ?? 0; | ||
| return { | ||
@@ -26,3 +27,5 @@ upgrade(request) { | ||
| open: false, | ||
| timeout: void 0 | ||
| timeout: void 0, | ||
| idleTimer: void 0, | ||
| lastActivity: Date.now() | ||
| }; | ||
@@ -36,2 +39,13 @@ upstreams.set(peer.id, state); | ||
| }, timeoutMs); | ||
| if (clientIdleTimeoutMs > 0) { | ||
| const checkIdle = () => { | ||
| if (upstreams.get(peer.id) !== state) return; | ||
| const remaining = clientIdleTimeoutMs - (Date.now() - state.lastActivity); | ||
| if (remaining <= 0) { | ||
| _cleanupState(upstreams, peer.id, state); | ||
| _safeClose(peer, 1001, "Client idle timeout"); | ||
| } else state.idleTimer = setTimeout(checkIdle, remaining); | ||
| }; | ||
| state.idleTimer = setTimeout(checkIdle, clientIdleTimeoutMs); | ||
| } | ||
| let resolved; | ||
@@ -55,2 +69,3 @@ try { | ||
| if (!state) return; | ||
| if (clientIdleTimeoutMs > 0) state.lastActivity = Date.now(); | ||
| const raw = typeof message.rawData === "string" ? message.rawData : message.uint8Array(); | ||
@@ -77,2 +92,3 @@ if (state.open) { | ||
| _clearTimeout(state); | ||
| _clearIdleTimer(state); | ||
| upstreams.delete(peer.id); | ||
@@ -87,2 +103,3 @@ try { | ||
| _clearTimeout(state); | ||
| _clearIdleTimer(state); | ||
| upstreams.delete(peer.id); | ||
@@ -137,2 +154,3 @@ try { | ||
| _clearTimeout(state); | ||
| _clearIdleTimer(state); | ||
| upstreams.delete(id); | ||
@@ -149,2 +167,8 @@ try { | ||
| } | ||
| function _clearIdleTimer(state) { | ||
| if (state.idleTimer !== void 0) { | ||
| clearTimeout(state.idleTimer); | ||
| state.idleTimer = void 0; | ||
| } | ||
| } | ||
| function _resolveTarget(target, peer) { | ||
@@ -151,0 +175,0 @@ const raw = typeof target === "function" ? target(peer) : target; |
+14
-15
| { | ||
| "name": "crossws", | ||
| "version": "0.4.9", | ||
| "version": "0.4.10", | ||
| "description": "Cross-platform WebSocket Servers for Node.js, Deno, Bun and Cloudflare Workers", | ||
@@ -70,3 +70,3 @@ "homepage": "https://crossws.h3.dev", | ||
| "test": "pnpm lint && pnpm typecheck && vitest run --coverage", | ||
| "typecheck": "tsgo --noEmit --skipLibCheck" | ||
| "typecheck": "tsc --noEmit --skipLibCheck" | ||
| }, | ||
@@ -77,10 +77,9 @@ "resolutions": { | ||
| "devDependencies": { | ||
| "@cloudflare/workers-types": "^4.20260701.1", | ||
| "@cloudflare/workers-types": "^5", | ||
| "@types/bun": "^1.3.14", | ||
| "@types/deno": "^2.7.0", | ||
| "@types/node": "^26.1.0", | ||
| "@types/node": "^26.1.1", | ||
| "@types/web": "^0.0.352", | ||
| "@types/ws": "^8.18.1", | ||
| "@typescript/native-preview": "^7.0.0-dev.20260701.1", | ||
| "@vitest/coverage-v8": "^4.1.9", | ||
| "@vitest/coverage-v8": "^4.1.10", | ||
| "automd": "^0.4.3", | ||
@@ -93,15 +92,15 @@ "changelogen": "^0.6.2", | ||
| "get-port-please": "^3.2.0", | ||
| "h3": "2.0.1-rc.22", | ||
| "h3": "^2.0.1-rc.24", | ||
| "jiti": "^2.7.0", | ||
| "listhen": "^1.10.0", | ||
| "obuild": "^0.4.37", | ||
| "oxfmt": "^0.57.0", | ||
| "oxlint": "^1.72.0", | ||
| "srvx": "^0.11.18", | ||
| "typescript": "^6.0.3", | ||
| "obuild": "^0.4.38", | ||
| "oxfmt": ">=0.58.0", | ||
| "oxlint": "^1.73.0", | ||
| "srvx": "^0.11.21", | ||
| "typescript": "^7.0.2", | ||
| "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.57.0", | ||
| "unbuild": "^3.6.1", | ||
| "undici": "^8.5.0", | ||
| "vitest": "^4.1.9", | ||
| "wrangler": "^4.106.0", | ||
| "undici": "^8.7.0", | ||
| "vitest": "^4.1.10", | ||
| "wrangler": "^4.110.0", | ||
| "ws": "^8.21.0" | ||
@@ -108,0 +107,0 @@ }, |
Sorry, the diff of this file is too big to display
249887
0.67%28
-3.45%4800
0.57%