From 7d8fde9180c49bf21678501d5a09f71f7c54f751 Mon Sep 17 00:00:00 2001 From: Sid Date: Fri, 17 Oct 2025 18:15:15 +0530 Subject: [PATCH] fix: improve URL parsing in getParsedWsUrlObject (#5822) --- packages/bruno-requests/src/ws/ws-client.js | 40 +------------------ packages/bruno-requests/src/ws/ws-url.js | 39 ++++++++++++++++++ packages/bruno-requests/src/ws/ws-url.spec.ts | 36 +++++++++++++++++ 3 files changed, 76 insertions(+), 39 deletions(-) create mode 100644 packages/bruno-requests/src/ws/ws-url.js create mode 100644 packages/bruno-requests/src/ws/ws-url.spec.ts diff --git a/packages/bruno-requests/src/ws/ws-client.js b/packages/bruno-requests/src/ws/ws-client.js index 90bb8a0bc..a22d40318 100644 --- a/packages/bruno-requests/src/ws/ws-client.js +++ b/packages/bruno-requests/src/ws/ws-client.js @@ -1,5 +1,6 @@ import ws from 'ws'; import { hexy as hexdump } from 'hexy'; +import { getParsedWsUrlObject } from './ws-url'; /** * Safely parse JSON string with error handling @@ -21,45 +22,6 @@ const safeParseJSON = (jsonString, context = 'JSON string') => { } }; -/** - * Get parsed WebSocket URL object - * @param {string} url - The WebSocket URL - * @returns {Object} Parsed URL object with protocol, host, path - */ -const getParsedWsUrlObject = (url) => { - const addProtocolIfMissing = (str) => { - if (str.includes('://')) return str; - - // For localhost, default to insecure (grpc://) for local development - if (str.includes('localhost') || str.includes('127.0.0.1')) { - return `ws://${str}`; - } - - // For other hosts, default to secure - return `wss://${str}`; - }; - - const removeTrailingSlash = (str) => (str.endsWith('/') ? str.slice(0, -1) : str); - - if (!url) return { host: '', path: '' }; - - try { - const urlObj = new URL(addProtocolIfMissing(url.toLowerCase())); - return { - protocol: urlObj.protocol, - host: urlObj.host, - path: removeTrailingSlash(urlObj.pathname), - search: urlObj.search, - fullUrl: urlObj.href - }; - } catch (err) { - console.error({ err }); - return { - host: '', - path: '' - }; - } -}; class WsClient { messageQueues = {}; diff --git a/packages/bruno-requests/src/ws/ws-url.js b/packages/bruno-requests/src/ws/ws-url.js new file mode 100644 index 000000000..10ff4d54a --- /dev/null +++ b/packages/bruno-requests/src/ws/ws-url.js @@ -0,0 +1,39 @@ +/** + * Get parsed WebSocket URL object + * @param {string} url - The WebSocket URL + * @returns {Object} Parsed URL object with protocol, host, path + */ +export const getParsedWsUrlObject = (url) => { + const addProtocolIfMissing = (str) => { + if (str.includes('://')) return str; + + // For localhost, default to insecure (grpc://) for local development + if (str.includes('localhost') || str.includes('127.0.0.1')) { + return `ws://${str}`; + } + + // For other hosts, default to secure + return `wss://${str}`; + }; + + const removeTrailingSlash = (str) => (str.endsWith('/') ? str.slice(0, -1) : str); + + if (!url) return { host: '', path: '' }; + + try { + const urlObj = new URL(addProtocolIfMissing(url)); + return { + protocol: urlObj.protocol, + host: urlObj.host, + path: removeTrailingSlash(urlObj.pathname), + search: urlObj.search, + fullUrl: urlObj.href + }; + } catch (err) { + console.error({ err }); + return { + host: '', + path: '' + }; + } +}; diff --git a/packages/bruno-requests/src/ws/ws-url.spec.ts b/packages/bruno-requests/src/ws/ws-url.spec.ts new file mode 100644 index 000000000..dd2eeab7c --- /dev/null +++ b/packages/bruno-requests/src/ws/ws-url.spec.ts @@ -0,0 +1,36 @@ +import { getParsedWsUrlObject } from './ws-url'; + +describe('getParsedWsUrlObject', () => { + it('returns empty host and path for empty input', () => { + expect(getParsedWsUrlObject('')).toEqual({ host: '', path: '' }); + }); + + it('defaults to ws:// for localhost without protocol', () => { + const parsed: any = getParsedWsUrlObject('localhost:8080/some/path'); + expect(parsed.protocol).toBe('ws:'); + expect(parsed.host).toBe('localhost:8080'); + expect(parsed.path).toBe('/some/path'); + expect(parsed.fullUrl.startsWith('ws://')).toBe(true); + }); + + it('defaults to wss:// for external hosts without protocol', () => { + const parsed: any = getParsedWsUrlObject('example.com/s'); + expect(parsed.protocol).toBe('wss:'); + expect(parsed.host).toBe('example.com'); + expect(parsed.path).toBe('/s'); + expect(parsed.fullUrl.startsWith('wss://')).toBe(true); + }); + + it('preserves provided protocol and parses query/search', () => { + const parsed: any = getParsedWsUrlObject('wss://example.com/path/With/cAses/?a=1&b=2'); + expect(parsed.protocol).toBe('wss:'); + expect(parsed.host).toBe('example.com'); + expect(parsed.path).toBe('/path/With/cAses'); + expect(parsed.search).toBe('?a=1&b=2'); + }); + + it('removes trailing slash from path', () => { + const parsed: any = getParsedWsUrlObject('ws://127.0.0.1:9000/endpoint/'); + expect(parsed.path).toBe('/endpoint'); + }); +});