From dcbeed42ade37f3b123d741a1efed5fdb8ae291e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 9 Feb 2025 23:36:30 +0300 Subject: [PATCH] fix regression https parsing, improve/fix http, ws parsing in all places of app. finally we parsing a single string in a decent way --- src/cameraRotationControls.ts | 2 +- src/index.ts | 6 +- src/parseServerAddress.ts | 43 +++++++++++++ src/react/AddServerOrConnect.tsx | 15 ++--- src/react/ServersListProvider.tsx | 2 +- src/utils.test.ts | 101 ++++++++++++++++++++++++++++++ src/utils.ts | 47 -------------- vitest.config.ts | 3 +- 8 files changed, 159 insertions(+), 60 deletions(-) create mode 100644 src/parseServerAddress.ts create mode 100644 src/utils.test.ts diff --git a/src/cameraRotationControls.ts b/src/cameraRotationControls.ts index 151bb841..ff7c347f 100644 --- a/src/cameraRotationControls.ts +++ b/src/cameraRotationControls.ts @@ -72,7 +72,7 @@ function pointerLockChangeCallback () { hideNotification() } if (viewer.renderer.xr.isPresenting) return // todo - if (!pointerLock.hasPointerLock && activeModalStack.length === 0) { + if (!pointerLock.hasPointerLock && activeModalStack.length === 0 && miscUiState.gameLoaded) { showModal({ reactType: 'pause-screen' }) } } diff --git a/src/index.ts b/src/index.ts index 6088735d..1f2d681c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -60,8 +60,8 @@ import { } from './globalState' import { - pointerLock, parseServerAddress -} from './utils' + pointerLock } from './utils' +import { parseServerAddress } from './parseServerAddress' import { setLoadingScreenStatus } from './appStatus' import { isCypress } from './standaloneUtils' @@ -285,7 +285,7 @@ export async function connect (connectOptions: ConnectOptions) { const https = connectOptions.proxy.startsWith('https://') || location.protocol === 'https:' connectOptions.proxy = `${connectOptions.proxy}:${https ? 443 : 80}` } - const parsedProxy = parseServerAddress(connectOptions.proxy) + const parsedProxy = parseServerAddress(connectOptions.proxy, false) const proxy = { host: parsedProxy.host, port: parsedProxy.port } let { username } = connectOptions diff --git a/src/parseServerAddress.ts b/src/parseServerAddress.ts new file mode 100644 index 00000000..dec89c00 --- /dev/null +++ b/src/parseServerAddress.ts @@ -0,0 +1,43 @@ +import { ParsedServerAddress } from './utils' + + +export const parseServerAddress = (address: string | undefined, removeHttp = true): ParsedServerAddress => { + if (!address) { + return { host: '', isWebSocket: false } + } + + const isWebSocket = address.startsWith('ws://') || address.startsWith('wss://') + if (isWebSocket) { + return { host: address, isWebSocket: true } + } + + if (removeHttp) { + address = address.replace(/^https?:\/\//, '') + } + + const parts = address.split(':') + + let version: string | null = null + let port: string | null = null + + for (let i = 0; i < parts.length; i++) { + const part = parts[i] + if (/^\d+\.\d+(\.\d+)?$/.test(part)) { + version = part + parts.splice(i, 1) + i-- + } + if (/^\d+$/.test(part)) { + port = part + parts.splice(i, 1) + i-- + } + } + + return { + host: parts.join(':'), + ...(port ? { port } : {}), + ...(version ? { version } : {}), + isWebSocket: false + } +} diff --git a/src/react/AddServerOrConnect.tsx b/src/react/AddServerOrConnect.tsx index a9415204..46964a90 100644 --- a/src/react/AddServerOrConnect.tsx +++ b/src/react/AddServerOrConnect.tsx @@ -1,6 +1,7 @@ import React, { useEffect } from 'react' import { appQueryParams } from '../appParams' import { fetchServerStatus, isServerValid } from '../api/mcStatusApi' +import { parseServerAddress } from '../parseServerAddress' import Screen from './Screen' import Input from './Input' import Button from './Button' @@ -42,12 +43,12 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ const qsParamUsername = parseQs ? appQueryParams.username : undefined const qsParamLockConnect = parseQs ? appQueryParams.lockConnect : undefined - const qsIpParts = qsParamIp?.split(':') - const ipParts = initialData?.ip ? initialData?.ip.split(':') : undefined + const parsedQsIp = parseServerAddress(qsParamIp) + const parsedInitialIp = parseServerAddress(initialData?.ip) const [serverName, setServerName] = React.useState(initialData?.name ?? qsParamName ?? '') - const [serverIp, setServerIp] = React.useState(ipParts?.[0] ?? qsIpParts?.[0] ?? '') - const [serverPort, setServerPort] = React.useState(ipParts?.[1] ?? qsIpParts?.[1] ?? '') + const [serverIp, setServerIp] = React.useState(parsedQsIp.host || parsedInitialIp.host || '') + const [serverPort, setServerPort] = React.useState(parsedQsIp.port || parsedInitialIp.port || '') const [versionOverride, setVersionOverride] = React.useState(initialData?.versionOverride ?? /* legacy */ initialData?.['version'] ?? qsParamVersion ?? '') const [proxyOverride, setProxyOverride] = React.useState(initialData?.proxyOverride ?? qsParamProxy ?? '') const [usernameOverride, setUsernameOverride] = React.useState(initialData?.usernameOverride ?? qsParamUsername ?? '') @@ -61,7 +62,7 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ const noAccountSelected = accountIndex === -1 const authenticatedAccountOverride = noAccountSelected ? undefined : freshAccount ? true : accounts?.[accountIndex] - let ipFinal = serverIp.includes(':') ? serverIp : `${serverIp}:${serverPort}` + let ipFinal = serverIp.includes(':') ? serverIp : `${serverIp}${serverPort ? `:${serverPort}` : ''}` ipFinal = ipFinal.replace(/:$/, '') const commonUseOptions: BaseServerInfo = { name: serverName, @@ -152,14 +153,14 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ required label="Server IP" value={serverIp} - disabled={lockConnect && qsIpParts?.[0] !== null} + disabled={lockConnect && parsedQsIp.host !== null} onChange={({ target: { value } }) => { setServerIp(value) setServerOnline(false) }} validateInput={serverOnline === null || fetchedServerInfoIp !== serverIp ? undefined : validateServerIp} /> - setServerPort(value)} placeholder='25565' /> + setServerPort(value)} placeholder={serverIp.startsWith('ws://') || serverIp.startsWith('wss://') ? '' : '25565'} /> {isSmallHeight ?
:
Overrides:
}
{ + it('should handle undefined input', () => { + expect(parseServerAddress(undefined)).toEqual({ + host: '', + isWebSocket: false + }) + }) + + it('should handle empty string input', () => { + expect(parseServerAddress('')).toEqual({ + host: '', + isWebSocket: false + }) + }) + + it('should parse basic hostname', () => { + expect(parseServerAddress('example.com')).toEqual({ + host: 'example.com', + isWebSocket: false + }) + }) + + it('should parse hostname with port', () => { + expect(parseServerAddress('example.com:25565')).toEqual({ + host: 'example.com', + port: '25565', + isWebSocket: false + }) + }) + + it('should parse hostname with version', () => { + expect(parseServerAddress('example.com:1.20.4')).toEqual({ + host: 'example.com', + version: '1.20.4', + isWebSocket: false + }) + }) + + it('should parse hostname with port and version', () => { + expect(parseServerAddress('example.com:25565:1.20.4')).toEqual({ + host: 'example.com', + port: '25565', + version: '1.20.4', + isWebSocket: false + }) + expect(parseServerAddress('example.com:1.20.4:25565')).toEqual({ + host: 'example.com', + version: '1.20.4', + port: '25565', + isWebSocket: false + }) + }) + + it('should handle WebSocket URLs', () => { + expect(parseServerAddress('ws://example.com')).toEqual({ + host: 'ws://example.com', + isWebSocket: true + }) + expect(parseServerAddress('wss://example.com')).toEqual({ + host: 'wss://example.com', + isWebSocket: true + }) + }) + + it('should handle http/https URLs with removeHttp=true', () => { + expect(parseServerAddress('http://example.com')).toEqual({ + host: 'example.com', + isWebSocket: false + }) + expect(parseServerAddress('https://example.com')).toEqual({ + host: 'example.com', + isWebSocket: false + }) + }) + + it('should handle http/https URLs with removeHttp=false', () => { + expect(parseServerAddress('http://example.com', false)).toEqual({ + host: 'http://example.com', + isWebSocket: false + }) + expect(parseServerAddress('https://example.com', false)).toEqual({ + host: 'https://example.com', + isWebSocket: false + }) + }) + + it('should handle IP addresses', () => { + expect(parseServerAddress('127.0.0.1')).toEqual({ + host: '127.0.0.1', + isWebSocket: false + }) + expect(parseServerAddress('127.0.0.1:25565')).toEqual({ + host: '127.0.0.1', + port: '25565', + isWebSocket: false + }) + }) +}) diff --git a/src/utils.ts b/src/utils.ts index bfdef5e0..6c0110fb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -187,50 +187,3 @@ export interface ParsedServerAddress { version?: string isWebSocket: boolean } - -export const parseServerAddress = (address: string | undefined): ParsedServerAddress => { - if (!address) { - return { host: '', isWebSocket: false } - } - - address = address.replace(/^https?:\/\//, '') - - const isWebSocket = address.startsWith('ws://') || address.startsWith('wss://') - if (isWebSocket) { - return { host: address, isWebSocket: true } - } - - // Check for version in format host:version or host:port:version - const parts = address.split(':') - if (parts.length === 2 && parts[1].includes('.')) { - return { - host: parts[0], - version: parts[1], - isWebSocket: false - } - } - - if (parts.length === 3) { - return { - host: parts[0], - port: parts[1], - version: parts[2], - isWebSocket: false - } - } - - // Standard host:port format - const hostPort = /:\d+$/.exec(address) - if (hostPort) { - return { - host: address.slice(0, -hostPort[0].length), - port: hostPort[0].slice(1), - isWebSocket: false - } - } - - return { - host: address, - isWebSocket: false - } -} diff --git a/vitest.config.ts b/vitest.config.ts index ea875183..1d1588e4 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -8,7 +8,8 @@ export default defineConfig({ '../../src/markdownToFormattedText.test.ts', '../../src/react/parseKeybindingName.test.ts', 'lib/mesher/test/tests.test.ts', - 'sign-renderer/tests.test.ts' + 'sign-renderer/tests.test.ts', + '../../src/utils.test.ts' ], }, })