diff --git a/docs/runtime/http/websockets.mdx b/docs/runtime/http/websockets.mdx index 216c9bd290f6..2314da6e47ed 100644 --- a/docs/runtime/http/websockets.mdx +++ b/docs/runtime/http/websockets.mdx @@ -275,7 +275,7 @@ The `.send(message)` method of `ServerWebSocket` returns a `number` indicating t ### Timeouts and limits -By default, Bun closes a WebSocket connection that has been idle for 120 seconds. Configure this with the `idleTimeout` parameter. +By default, Bun closes a WebSocket connection that has been idle for 120 seconds. Configure this with the `idleTimeout` parameter. It takes a whole number of seconds from `0` to `960`. `0` disables the timeout entirely, and values from `1` to `7` are rounded up to `8`. ```ts Bun.serve({ diff --git a/packages/bun-types/serve.d.ts b/packages/bun-types/serve.d.ts index e5121186c288..58020c5e84fa 100644 --- a/packages/bun-types/serve.d.ts +++ b/packages/bun-types/serve.d.ts @@ -494,6 +494,9 @@ declare module "bun" { * Sets the number of seconds to wait before timing out a connection * due to no messages or pings. * + * The maximum value is `960`. `0` disables the timeout. Values from + * `1` to `7` are rounded up to `8`. + * * @default 120 */ idleTimeout?: number; diff --git a/src/runtime/server/WebSocketServerContext.rs b/src/runtime/server/WebSocketServerContext.rs index ba718f89f2b8..890ec4f7f11f 100644 --- a/src/runtime/server/WebSocketServerContext.rs +++ b/src/runtime/server/WebSocketServerContext.rs @@ -331,18 +331,19 @@ pub(crate) fn on_create( ))); } - let mut idle_timeout: u16 = value.to_int64().max(0) as u16; - if idle_timeout > 960 { + let seconds = value.to_int64(); + if !(0..=960).contains(&seconds) { return Err(global_object.throw_invalid_arguments(format_args!( - "websocket expects idleTimeout to be 960 or less" + "websocket expects idleTimeout to be between 0 and 960" ))); - } else if idle_timeout > 0 { - // uws does not allow idleTimeout to be between (0, 8), - // since its timer is not that accurate, therefore round up. - idle_timeout = idle_timeout.max(8); } - server.idle_timeout = idle_timeout; + // uws::App::ws terminates on an idleTimeout in 1..8 or above 960. + server.idle_timeout = if seconds > 0 { + (seconds as u16).max(8) + } else { + 0 + }; } } if let Some(value) = object.get(global_object, "backpressureLimit")? { diff --git a/test/js/bun/http/bun-serve-args.test.ts b/test/js/bun/http/bun-serve-args.test.ts index a37874d579da..2af8219a5d45 100644 --- a/test/js/bun/http/bun-serve-args.test.ts +++ b/test/js/bun/http/bun-serve-args.test.ts @@ -201,6 +201,41 @@ describe("Bun.serve websocket options", () => { server.stop(); }); + // idleTimeout was narrowed to 16 bits before the range check, so 65537 + // became 1 second and 2 ** 31 became 0 (no timeout at all). + test("websocket idleTimeout outside 0..960 throws", () => { + for (const idleTimeout of [65537, 2 ** 31, 961, -1]) { + expect(() => + serve({ + port: 0, + websocket: { + message(ws, message) {}, + idleTimeout, + }, + fetch() { + return new Response("ok"); + }, + }), + ).toThrow("websocket expects idleTimeout to be between 0 and 960"); + } + }); + + test("websocket idleTimeout accepts 0 and 960", () => { + for (const idleTimeout of [0, 960]) { + using server = serve({ + port: 0, + websocket: { + message(ws, message) {}, + idleTimeout, + }, + fetch() { + return new Response("ok"); + }, + }); + server.stop(); + } + }); + test("websocket with compression options", () => { using server = serve({ port: 0,