fix(clickclack): bound websocket handshake waits at 30s (#106485)

* fix(clickclack): bound websocket handshake waits at 30s

* test(clickclack): prove WebSocket handshake deadline

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
NIO
2026-07-16 01:41:53 -07:00
committed by GitHub
co-authored by Peter Steinberger
parent 727f9e0a7e
commit 3eec404aab
2 changed files with 45 additions and 0 deletions
+5
View File
@@ -65,6 +65,10 @@ const CLICKCLACK_CORRELATION_ID_HEADER = "X-Correlation-ID";
// accepts 1 MiB request bodies, then wraps and re-encodes them as events, so a
// valid frame can exceed 1 MiB before ws hands it to the event parser.
const CLICKCLACK_INBOUND_JSON_LIMIT_BYTES = 16 * 1024 * 1024;
// Match Slack relay / Mattermost / Signal channel gateway handshake floors.
// Without this, gateway.ts waits forever for close/error when TCP accepts but
// never upgrades, pinning the monitor reconnect loop.
const CLICKCLACK_WEBSOCKET_HANDSHAKE_TIMEOUT_MS = 30_000;
class ClickClackHttpError extends Error {
constructor(
@@ -410,6 +414,7 @@ export function createClickClackClient(options: ClientOptions) {
headers: {
Authorization: `Bearer ${options.token}`,
},
handshakeTimeout: CLICKCLACK_WEBSOCKET_HANDSHAKE_TIMEOUT_MS,
maxPayload: CLICKCLACK_INBOUND_JSON_LIMIT_BYTES,
});
},
@@ -0,0 +1,40 @@
// ClickClack tests cover websocket constructor options.
import { beforeEach, describe, expect, it, vi } from "vitest";
const { webSocketCtorCalls } = vi.hoisted(() => ({
webSocketCtorCalls: [] as Array<{ url: string; options: unknown }>,
}));
vi.mock("ws", () => ({
WebSocket: function MockWebSocket(url: string | URL, options?: unknown) {
webSocketCtorCalls.push({ url: String(url), options });
},
}));
import { createClickClackClient } from "./http-client.js";
describe("createClickClackClient websocket options", () => {
beforeEach(() => {
webSocketCtorCalls.length = 0;
});
it("passes a 30-second opening handshake deadline to ws", () => {
const client = createClickClackClient({
baseUrl: "https://clickclack.example",
token: "fake",
});
client.websocket("workspace-1", "cursor-1");
expect(webSocketCtorCalls).toEqual([
{
url: "wss://clickclack.example/api/realtime/ws?workspace_id=workspace-1&after_cursor=cursor-1",
options: {
headers: { Authorization: "Bearer fake" },
handshakeTimeout: 30_000,
maxPayload: 16 * 1024 * 1024,
},
},
]);
});
});