fix regression https parsing, improve/fix http, ws parsing in all places of app. finally we parsing a single string in a decent way

This commit is contained in:
Vitaly Turovsky 2025-02-09 23:36:30 +03:00
commit dcbeed42ad
8 changed files with 159 additions and 60 deletions

View file

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

View file

@ -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

43
src/parseServerAddress.ts Normal file
View file

@ -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
}
}

View file

@ -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}
/>
<InputWithLabel label="Server Port" value={serverPort} disabled={lockConnect && qsIpParts?.[1] !== null} onChange={({ target: { value } }) => setServerPort(value)} placeholder='25565' />
<InputWithLabel label="Server Port" value={serverPort} disabled={lockConnect && parsedQsIp.port !== null} onChange={({ target: { value } }) => setServerPort(value)} placeholder={serverIp.startsWith('ws://') || serverIp.startsWith('wss://') ? '' : '25565'} />
{isSmallHeight ? <div style={{ gridColumn: 'span 2', marginTop: 10, }} /> : <div style={{ gridColumn: smallWidth ? '' : 'span 2' }}>Overrides:</div>}
<div style={{
display: 'flex',

View file

@ -7,7 +7,7 @@ import supportedVersions from '../supportedVersions.mjs'
import { appQueryParams } from '../appParams'
import { fetchServerStatus, isServerValid } from '../api/mcStatusApi'
import { getServerInfo } from '../mineflayer/mc-protocol'
import { parseServerAddress } from '../utils'
import { parseServerAddress } from '../parseServerAddress'
import ServersList from './ServersList'
import AddServerOrConnect, { BaseServerInfo } from './AddServerOrConnect'
import { useDidUpdateEffect } from './utils'

101
src/utils.test.ts Normal file
View file

@ -0,0 +1,101 @@
import { describe, expect, it } from 'vitest'
import { parseServerAddress } from './parseServerAddress'
describe('parseServerAddress', () => {
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
})
})
})

View file

@ -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
}
}

View file

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