From 6905b9d7682e85b3c56616dc49db74105df2d827 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 5 May 2025 18:35:20 +0200 Subject: [PATCH] lib/client: add human-readable messages for WebSocket error codes --- lib/client.js | 50 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/lib/client.js b/lib/client.js index 1cd5e28..d959630 100644 --- a/lib/client.js +++ b/lib/client.js @@ -32,9 +32,33 @@ const RECONNECT_MAX_DELAY_MSEC = 10 * 60 * 1000; // 10min // WebSocket status codes // https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1 -const NORMAL_CLOSURE = 1000; -const GOING_AWAY = 1001; -const UNSUPPORTED_DATA = 1003; +const WEBSOCKET_CLOSE_CODES = { + NORMAL_CLOSURE: 1000, + GOING_AWAY: 1001, + PROTOCOL_ERROR: 1002, + UNSUPPORTED_DATA: 1003, + NO_STATUS_CODE: 1005, + ABNORMAL_CLOSURE: 1006, + INVALID_FRAME_PAYLOAD_DATA: 1007, + POLICY_VIOLATION: 1008, + MESSAGE_TOO_BIG: 1009, + MISSING_MANDATORY_EXT: 1010, + INTERNAL_SERVER_ERROR: 1011, + TLS_HANDSHAKE_FAILED: 1015, +}; +const WEBSOCKET_CLOSE_CODE_NAMES = { + [WEBSOCKET_CLOSE_CODES.GOING_AWAY]: "going away", + [WEBSOCKET_CLOSE_CODES.PROTOCOL_ERROR]: "protocol error", + [WEBSOCKET_CLOSE_CODES.UNSUPPORTED_DATA]: "unsupported data", + [WEBSOCKET_CLOSE_CODES.NO_STATUS_CODE]: "no status code received", + [WEBSOCKET_CLOSE_CODES.ABNORMAL_CLOSURE]: "abnormal closure", + [WEBSOCKET_CLOSE_CODES.INVALID_FRAME_PAYLOAD_DATA]: "invalid frame payload data", + [WEBSOCKET_CLOSE_CODES.POLICY_VIOLATION]: "policy violation", + [WEBSOCKET_CLOSE_CODES.MESSAGE_TOO_BIG]: "message too big", + [WEBSOCKET_CLOSE_CODES.MISSING_MANDATORY_EXT]: "missing mandatory extension", + [WEBSOCKET_CLOSE_CODES.INTERNAL_SERVER_ERROR]: "internal server error", + [WEBSOCKET_CLOSE_CODES.TLS_HANDSHAKE_FAILED]: "TLS handshake failed", +}; // See https://github.com/quakenet/snircd/blob/master/doc/readme.who // Sorted by order of appearance in RPL_WHOSPCRPL @@ -69,6 +93,18 @@ class IRCError extends Error { } } +class WebSocketError extends Error { + constructor(code) { + let text = "Connection error"; + let name = WEBSOCKET_CLOSE_CODE_NAMES[code]; + if (name) { + text += " (" + name + ")"; + } + + super(text); + } +} + /** * Implements a simple exponential backoff. */ @@ -189,8 +225,8 @@ export default class Client extends EventTarget { this.ws.addEventListener("close", (event) => { console.log("Connection closed (code: " + event.code + ")"); - if (event.code !== NORMAL_CLOSURE && event.code !== GOING_AWAY) { - this.dispatchError(new Error("Connection error")); + if (event.code !== WEBSOCKET_CLOSE_CODES.NORMAL_CLOSURE && event.code !== WEBSOCKET_CLOSE_CODES.GOING_AWAY) { + this.dispatchError(new WebSocketError(event.code)); } this.ws = null; @@ -237,7 +273,7 @@ export default class Client extends EventTarget { this.setPingInterval(0); if (this.ws) { - this.ws.close(NORMAL_CLOSURE); + this.ws.close(WEBSOCKET_CLOSE_CODES.NORMAL_CLOSURE); } } @@ -297,7 +333,7 @@ export default class Client extends EventTarget { handleMessage(event) { if (typeof event.data !== "string") { console.error("Received unsupported data type:", event.data); - this.ws.close(UNSUPPORTED_DATA); + this.ws.close(WEBSOCKET_CLOSE_CODES.UNSUPPORTED_DATA); return; }