fix: improve URL parsing in getParsedWsUrlObject (#5822)

This commit is contained in:
Sid
2025-10-17 18:15:15 +05:30
committed by GitHub
parent 4197304bf9
commit 7d8fde9180
3 changed files with 76 additions and 39 deletions

View File

@@ -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 = {};

View File

@@ -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: ''
};
}
};

View File

@@ -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');
});
});