Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions src/core/pingProxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
class LatencyMonitor {
private ws: WebSocket | null = null
private isConnected = false

constructor (public serverUrl: string) {
}

async connect () {
return new Promise<void>((resolve, reject) => {
// Convert http(s):// to ws(s)://
let wsUrl = this.serverUrl.replace(/^http/, 'ws') + '/api/vm/net/ping'
if (!wsUrl.startsWith('ws')) {
wsUrl = 'wss://' + wsUrl
}
this.ws = new WebSocket(wsUrl)

this.ws.onopen = () => {
this.isConnected = true
resolve()
}
this.ws.onerror = (error) => {
reject(error)
}
})
}

async measureLatency (): Promise<{
roundTripTime: number;
serverProcessingTime: number;
networkLatency: number;
}> {
return new Promise((resolve, reject) => {
if (!this.isConnected) {
reject(new Error('Not connected'))
return
}

const pingId = Date.now().toString()
const startTime = performance.now()

const handler = (event: MessageEvent) => {
if (typeof event.data === 'string' && event.data.startsWith('pong:')) {
const [_, receivedPingId, serverProcessingTime] = event.data.split(':')

if (receivedPingId === pingId) {
this.ws?.removeEventListener('message', handler)
const roundTripTime = performance.now() - startTime

resolve({
roundTripTime,
serverProcessingTime: parseFloat(serverProcessingTime),
networkLatency: roundTripTime - parseFloat(serverProcessingTime)
})
}
}
}

this.ws?.addEventListener('message', handler)
this.ws?.send('ping:' + pingId)
})
}
Comment on lines +27 to +61
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve latency measurement robustness and cleanup.

The latency measurement has good logic but needs better error handling and cleanup:

  1. No timeout protection for ping responses
  2. Event listener not removed on error paths
  3. Potential memory leak with orphaned event listeners
 async measureLatency (): Promise<{
   roundTripTime: number;
   serverProcessingTime: number;
   networkLatency: number;
 }> {
-  return new Promise((resolve, reject) => {
+  return new Promise((resolve, reject) => {
+    const PING_TIMEOUT = 5000 // 5 seconds
+    let timeoutId: NodeJS.Timeout
     if (!this.isConnected) {
       reject(new Error('Not connected'))
       return
     }

     const pingId = Date.now().toString()
     const startTime = performance.now()

+    const cleanup = () => {
+      if (timeoutId) clearTimeout(timeoutId)
+      this.ws?.removeEventListener('message', handler)
+    }
+
     const handler = (event: MessageEvent) => {
       if (typeof event.data === 'string' && event.data.startsWith('pong:')) {
         const [_, receivedPingId, serverProcessingTime] = event.data.split(':')

         if (receivedPingId === pingId) {
-          this.ws?.removeEventListener('message', handler)
+          cleanup()
           const roundTripTime = performance.now() - startTime

           resolve({
             roundTripTime,
             serverProcessingTime: parseFloat(serverProcessingTime),
             networkLatency: roundTripTime - parseFloat(serverProcessingTime)
           })
         }
       }
     }
+
+    timeoutId = setTimeout(() => {
+      cleanup()
+      reject(new Error('Ping timeout'))
+    }, PING_TIMEOUT)

     this.ws?.addEventListener('message', handler)
     this.ws?.send('ping:' + pingId)
   })
 }
🤖 Prompt for AI Agents
In src/core/pingProxy.ts between lines 27 and 61, the measureLatency method
lacks timeout handling and proper cleanup of event listeners on error or
timeout. To fix this, add a timeout mechanism that rejects the promise if no
pong response is received within a reasonable time, and ensure the message event
listener is removed both on successful response and on timeout or error to
prevent memory leaks.


disconnect () {
if (this.ws) {
this.ws.close()
this.isConnected = false
}
}
}

export async function pingProxyServer (serverUrl: string, abortSignal?: AbortSignal) {
try {
const monitor = new LatencyMonitor(serverUrl)
if (abortSignal) {
abortSignal.addEventListener('abort', () => {
monitor.disconnect()
})
}

await monitor.connect()
const latency = await monitor.measureLatency()
monitor.disconnect()
return {
success: true,
latency: Math.round(latency.networkLatency)
}
} catch (err) {
let msg = String(err)
if (err instanceof Event && err.type === 'error') {
msg = 'Connection error'
}
return {
success: false,
error: msg
}
}
}

export async function monitorLatency () {
const monitor = new LatencyMonitor('https://your-server.com')

try {
await monitor.connect()

// Single measurement
const latency = await monitor.measureLatency()

// Or continuous monitoring
setInterval(async () => {
try {
const latency = await monitor.measureLatency()
console.log('Current latency:', latency)
} catch (error) {
console.error('Error measuring latency:', error)
}
}, 5000) // Check every 5 seconds

} catch (error) {
console.error('Failed to connect:', error)
}
}
Comment on lines +99 to +121
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove or improve the example monitorLatency function.

The monitorLatency function appears to be example code with a hardcoded URL and should not be in production code.

Either remove this function entirely if it's just an example, or improve it for actual use:

-export async function monitorLatency () {
-  const monitor = new LatencyMonitor('https://your-server.com')
+export async function monitorLatency (serverUrl: string, options: {
+  interval?: number
+  onLatency?: (latency: any) => void
+  onError?: (error: any) => void
+} = {}) {
+  const { interval = 5000, onLatency = console.log, onError = console.error } = options
+  const monitor = new LatencyMonitor(serverUrl)
 
   try {
     await monitor.connect()
 
-    // Single measurement
-    const latency = await monitor.measureLatency()
-
-    // Or continuous monitoring
     setInterval(async () => {
       try {
         const latency = await monitor.measureLatency()
-        console.log('Current latency:', latency)
+        onLatency(latency)
       } catch (error) {
-        console.error('Error measuring latency:', error)
+        onError(error)
       }
-    }, 5000) // Check every 5 seconds
+    }, interval)
 
   } catch (error) {
-    console.error('Failed to connect:', error)
+    onError(error)
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function monitorLatency () {
const monitor = new LatencyMonitor('https://your-server.com')
try {
await monitor.connect()
// Single measurement
const latency = await monitor.measureLatency()
// Or continuous monitoring
setInterval(async () => {
try {
const latency = await monitor.measureLatency()
console.log('Current latency:', latency)
} catch (error) {
console.error('Error measuring latency:', error)
}
}, 5000) // Check every 5 seconds
} catch (error) {
console.error('Failed to connect:', error)
}
}
export async function monitorLatency (
serverUrl: string,
options: {
interval?: number
onLatency?: (latency: any) => void
onError?: (error: any) => void
} = {}
) {
const { interval = 5000, onLatency = console.log, onError = console.error } = options
const monitor = new LatencyMonitor(serverUrl)
try {
await monitor.connect()
setInterval(async () => {
try {
const latency = await monitor.measureLatency()
onLatency(latency)
} catch (error) {
onError(error)
}
}, interval)
} catch (error) {
onError(error)
}
}
🤖 Prompt for AI Agents
In src/core/pingProxy.ts between lines 99 and 121, the monitorLatency function
uses a hardcoded URL and serves as example code, which is not suitable for
production. Remove this function entirely if it is only for demonstration, or
refactor it to accept the server URL as a parameter and handle errors
appropriately to make it reusable and production-ready.

91 changes: 91 additions & 0 deletions src/core/proxyAutoSelect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { proxy } from 'valtio'
import { appStorage } from '../react/appStorageProvider'
import { pingProxyServer } from './pingProxy'

export interface ProxyPingState {
selectedProxy: string | null
proxyStatus: Record<string, {
status: 'checking' | 'success' | 'error'
latency?: number
error?: string
}>
checkStarted: boolean
}

export const proxyPingState = proxy<ProxyPingState>({
selectedProxy: null,
proxyStatus: {},
checkStarted: false
})

let currentPingAbortController: AbortController | null = null

export async function selectBestProxy (proxies: string[]): Promise<string | null> {
if (proxyPingState.checkStarted) {
cancelProxyPinging()
}
proxyPingState.checkStarted = true

// Cancel any ongoing pings
if (currentPingAbortController) {
currentPingAbortController.abort()
}
currentPingAbortController = new AbortController()
const abortController = currentPingAbortController // Store in local const to satisfy TypeScript

// Reset ping states
for (const proxy of proxies) {
proxyPingState.proxyStatus[proxy] = { status: 'checking' }
}

Comment on lines +23 to +40
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Address potential race condition in checkStarted flag.

The current implementation has a potential race condition where checkStarted is set to true before checking for existing operations.

 export async function selectBestProxy (proxies: string[]): Promise<string | null> {
-  if (proxyPingState.checkStarted) {
-    cancelProxyPinging()
-  }
-  proxyPingState.checkStarted = true
+  // Cancel any ongoing pings first
+  if (proxyPingState.checkStarted) {
+    cancelProxyPinging()
+  }
 
-  // Cancel any ongoing pings
   if (currentPingAbortController) {
     currentPingAbortController.abort()
   }
   currentPingAbortController = new AbortController()
   const abortController = currentPingAbortController // Store in local const to satisfy TypeScript
+  
+  proxyPingState.checkStarted = true
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function selectBestProxy (proxies: string[]): Promise<string | null> {
if (proxyPingState.checkStarted) {
cancelProxyPinging()
}
proxyPingState.checkStarted = true
// Cancel any ongoing pings
if (currentPingAbortController) {
currentPingAbortController.abort()
}
currentPingAbortController = new AbortController()
const abortController = currentPingAbortController // Store in local const to satisfy TypeScript
// Reset ping states
for (const proxy of proxies) {
proxyPingState.proxyStatus[proxy] = { status: 'checking' }
}
export async function selectBestProxy (proxies: string[]): Promise<string | null> {
// Cancel any ongoing pings first
if (proxyPingState.checkStarted) {
cancelProxyPinging()
}
if (currentPingAbortController) {
currentPingAbortController.abort()
}
currentPingAbortController = new AbortController()
const abortController = currentPingAbortController // Store in local const to satisfy TypeScript
proxyPingState.checkStarted = true
// Reset ping states
for (const proxy of proxies) {
proxyPingState.proxyStatus[proxy] = { status: 'checking' }
}
// …rest of function…
}
🤖 Prompt for AI Agents
In src/core/proxyAutoSelect.ts around lines 23 to 40, the checkStarted flag is
set to true before confirming no existing operations are running, causing a
potential race condition. To fix this, move the assignment of checkStarted =
true to after cancelProxyPinging() completes and any ongoing pings are aborted,
ensuring no concurrent operations proceed simultaneously.

try {
// Create a promise for each proxy
const pingPromises = proxies.map(async (proxy) => {
if (proxy.startsWith(':')) {
proxy = `${location.protocol}//${location.hostname}${proxy}`
}
try {
const result = await pingProxyServer(proxy, abortController.signal)
if (result.success) {
proxyPingState.proxyStatus[proxy] = { status: 'success', latency: result.latency }
return { proxy, latency: result.latency }
} else {
proxyPingState.proxyStatus[proxy] = { status: 'error', error: result.error }
return null
}
} catch (err) {
proxyPingState.proxyStatus[proxy] = { status: 'error', error: String(err) }
return null
}
})
Comment on lines +42 to +60
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential mutation of loop variable causing incorrect proxy tracking.

There's a critical bug where the proxy variable is mutated within the loop, which will cause incorrect tracking in proxyPingState.proxyStatus.

   const pingPromises = proxies.map(async (proxy) => {
+    let normalizedProxy = proxy
     if (proxy.startsWith(':')) {
-      proxy = `${location.protocol}//${location.hostname}${proxy}`
+      normalizedProxy = `${location.protocol}//${location.hostname}${proxy}`
     }
     try {
-      const result = await pingProxyServer(proxy, abortController.signal)
+      const result = await pingProxyServer(normalizedProxy, abortController.signal)
       if (result.success) {
-        proxyPingState.proxyStatus[proxy] = { status: 'success', latency: result.latency }
-        return { proxy, latency: result.latency }
+        proxyPingState.proxyStatus[proxy] = { status: 'success', latency: result.latency }
+        return { proxy: normalizedProxy, latency: result.latency }
       } else {
         proxyPingState.proxyStatus[proxy] = { status: 'error', error: result.error }
         return null
       }
     } catch (err) {
       proxyPingState.proxyStatus[proxy] = { status: 'error', error: String(err) }
       return null
     }
   })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Create a promise for each proxy
const pingPromises = proxies.map(async (proxy) => {
if (proxy.startsWith(':')) {
proxy = `${location.protocol}//${location.hostname}${proxy}`
}
try {
const result = await pingProxyServer(proxy, abortController.signal)
if (result.success) {
proxyPingState.proxyStatus[proxy] = { status: 'success', latency: result.latency }
return { proxy, latency: result.latency }
} else {
proxyPingState.proxyStatus[proxy] = { status: 'error', error: result.error }
return null
}
} catch (err) {
proxyPingState.proxyStatus[proxy] = { status: 'error', error: String(err) }
return null
}
})
// Create a promise for each proxy
const pingPromises = proxies.map(async (proxy) => {
let normalizedProxy = proxy
if (proxy.startsWith(':')) {
normalizedProxy = `${location.protocol}//${location.hostname}${proxy}`
}
try {
const result = await pingProxyServer(normalizedProxy, abortController.signal)
if (result.success) {
proxyPingState.proxyStatus[proxy] = { status: 'success', latency: result.latency }
return { proxy: normalizedProxy, latency: result.latency }
} else {
proxyPingState.proxyStatus[proxy] = { status: 'error', error: result.error }
return null
}
} catch (err) {
proxyPingState.proxyStatus[proxy] = { status: 'error', error: String(err) }
return null
}
})
🤖 Prompt for AI Agents
In src/core/proxyAutoSelect.ts between lines 42 and 60, the loop variable
'proxy' is mutated inside the map callback, which causes incorrect keys in
proxyPingState.proxyStatus. To fix this, create a new variable to hold the
potentially modified proxy URL instead of reassigning 'proxy'. Use this new
variable for all subsequent operations and assignments to ensure the original
loop variable remains unchanged.


// Use Promise.race to get the first successful response
const results = await Promise.race([
// Wait for first successful ping
Promise.any(pingPromises.map(async p => p.then(r => r && { type: 'success' as const, data: r }))),
// Or wait for all to fail
Promise.all(pingPromises).then(results => {
if (results.every(r => r === null)) {
return { type: 'all-failed' as const }
}
return null
})
])

if (!results || results.type === 'all-failed') {
return null
}

return results.type === 'success' ? results.data.proxy : null
} finally {
currentPingAbortController = null
proxyPingState.checkStarted = false
}
}
Comment on lines +62 to +84
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve Promise.race logic and add timeout protection.

The current Promise.race logic is complex and could be simplified. Also, there's no timeout protection for the ping operations.

-    // Use Promise.race to get the first successful response
-    const results = await Promise.race([
-      // Wait for first successful ping
-      Promise.any(pingPromises.map(async p => p.then(r => r && { type: 'success' as const, data: r }))),
-      // Or wait for all to fail
-      Promise.all(pingPromises).then(results => {
-        if (results.every(r => r === null)) {
-          return { type: 'all-failed' as const }
-        }
-        return null
-      })
-    ])
-
-    if (!results || results.type === 'all-failed') {
-      return null
-    }
-
-    return results.type === 'success' ? results.data.proxy : null
+    // Wait for first successful ping or all to complete
+    try {
+      const firstSuccess = await Promise.any(
+        pingPromises.map(async p => {
+          const result = await p
+          if (result) return result
+          throw new Error('Ping failed')
+        })
+      )
+      return firstSuccess.proxy
+    } catch {
+      // All pings failed
+      return null
+    }

Also consider adding a timeout wrapper:

const PING_TIMEOUT = 10000 // 10 seconds

const pingWithTimeout = (promise: Promise<any>) => 
  Promise.race([
    promise,
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('Ping timeout')), PING_TIMEOUT)
    )
  ])
🤖 Prompt for AI Agents
In src/core/proxyAutoSelect.ts around lines 62 to 84, simplify the Promise.race
logic by removing the nested Promise.any and Promise.all combination and instead
use a single Promise.race with ping promises wrapped in a timeout. Implement a
pingWithTimeout helper that wraps each ping promise with a timeout rejection
after 10 seconds, then race these wrapped promises to get the first successful
ping or timeout. This will both simplify the logic and add timeout protection to
the ping operations.


export function cancelProxyPinging () {
if (currentPingAbortController) {
currentPingAbortController.abort()
currentPingAbortController = null
}
}
24 changes: 22 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ import { registerOpenBenchmarkListener } from './benchmark'
import { tryHandleBuiltinCommand } from './builtinCommands'
import { loadingTimerState } from './react/LoadingTimer'
import { loadPluginsIntoWorld } from './react/CreateWorldProvider'
import { appStorage } from './react/appStorageProvider'
import { selectBestProxy } from './core/proxyAutoSelect'

window.debug = debug
window.beforeRenderFrame = []
Expand Down Expand Up @@ -193,8 +195,6 @@ 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, false)
const proxy = { host: parsedProxy.host, port: parsedProxy.port }
let { username } = connectOptions

if (connectOptions.server) {
Expand Down Expand Up @@ -289,6 +289,26 @@ export async function connect (connectOptions: ConnectOptions) {
let clientDataStream: Duplex | undefined

if (connectOptions.server && !connectOptions.viewerWsConnect && !parsedServer.isWebSocket) {
if (appStorage.proxiesData?.isAutoSelect && appStorage.proxiesData.proxies.length > 0) {
setLoadingScreenStatus('Selecting best proxy...')
const bestProxy = await selectBestProxy(appStorage.proxiesData.proxies)
if (bestProxy) {
connectOptions.proxy = bestProxy
} else {
let message = 'Failed to find a working proxy.'
if (navigator.onLine) {
message += '\n\nPlease check your internet connection and try again.'
} else {
message += '\nWe tried these proxies but none of them worked, try opening any of these urls in your browser:'
message += `\n${appStorage.proxiesData.proxies.join(', ')}`
}
setLoadingScreenStatus(message, true)
return
}
}
Comment on lines +292 to +308
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify error handling and user experience for proxy selection failures.

The auto-selection logic is well-implemented, but there are a few considerations:

  1. The error message construction could be improved for better user guidance
  2. The navigator.onLine check may not be reliable in all environments

Consider this improvement for better error messaging:

-        let message = 'Failed to find a working proxy.'
-        if (navigator.onLine) {
-          message += '\n\nPlease check your internet connection and try again.'
-        } else {
-          message += '\nWe tried these proxies but none of them worked, try opening any of these urls in your browser:'
-          message += `\n${appStorage.proxiesData.proxies.join(', ')}`
-        }
+        let message = 'Failed to find a working proxy.'
+        if (!navigator.onLine) {
+          message += '\n\nYour device appears to be offline. Please check your internet connection.'
+        } else {
+          message += '\n\nTry manually testing these proxy URLs in your browser:'
+          message += `\n${appStorage.proxiesData.proxies.join('\n')}`
+        }

Could you verify if navigator.onLine works reliably across all target browsers and environments?


🏁 Script executed:

#!/bin/bash
# Search for other usages of navigator.onLine to understand browser support requirements
rg "navigator\.onLine" --type ts --type js -A 2 -B 2

Length of output: 319


Improve offline detection and error messaging for proxy selection

The navigator.onLine API is known to be unreliable (e.g. some browsers always report “online”). Instead of relying solely on it, add an active connectivity check before choosing which error message to show, and update the messaging accordingly.

• File: src/index.ts, lines 292–308
• Replace the navigator.onLine branch with something like:

-        let message = 'Failed to find a working proxy.'
-        if (!navigator.onLine) {
-          message += '\n\nYour device appears to be offline. Please check your internet connection.'
-        } else {
-          message += '\n\nTry manually testing these proxy URLs in your browser:'
-          message += `\n${appStorage.proxiesData.proxies.join('\n')}`
-        }
+        let message = 'Failed to find a working proxy.'
+        // Perform a lightweight network check rather than relying on navigator.onLine
+        const isConnected = await fetch('https://example.com/healthcheck', {
+          method: 'HEAD',
+          cache: 'no-store',
+        })
+          .then(() => true)
+          .catch(() => false)
+
+        if (!isConnected) {
+          message += '\n\nUnable to reach the internet. Please check your network connection and try again.'
+        } else {
+          message += '\n\nAll tested proxies failed. You can try opening these URLs directly in your browser:'
+          message += `\n${appStorage.proxiesData.proxies.join('\n')}`
+        }

This approach gives more accurate connectivity detection across browsers. You may want to extract the health-check URL or logic into a reusable helper.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/index.ts around lines 292 to 308, the use of navigator.onLine for offline
detection is unreliable. Replace the navigator.onLine check with an active
connectivity test by performing a lightweight fetch request to a known reliable
URL to confirm internet access. Based on the fetch result, update the error
message accordingly. Extract this connectivity check into a reusable helper
function for clarity and reuse.


const parsedProxy = parseServerAddress(connectOptions.proxy, false)
const proxy = { host: parsedProxy.host, port: parsedProxy.port }
console.log(`using proxy ${proxy.host}:${proxy.port || location.port}`)
net['setProxy']({ hostname: proxy.host, port: proxy.port, headers: { Authorization: `Bearer ${new URLSearchParams(location.search).get('token') ?? ''}` } })
}
Expand Down
98 changes: 98 additions & 0 deletions src/react/ProxiesList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { useSnapshot, proxy } from 'valtio'
import { openURL } from 'renderer/viewer/lib/simpleUtils'
import { hideCurrentModal } from '../globalState'
import { showInputsModal } from './SelectOption'
import Screen from './Screen'
import Button from './Button'
import { pixelartIcons } from './PixelartIcon'
import { useIsModalActive } from './utilsApp'

// This would be in a separate state file in a real implementation
export const proxiesState = proxy({
proxies: [] as Array<{ id: string, url: string }>
})
Comment on lines +10 to +13
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider integrating with existing proxy storage system.

This component creates its own separate proxiesState instead of integrating with the existing appStorage.proxiesData from src/react/appStorageProvider.ts. This could lead to data synchronization issues between the proxy management UI and the actual proxy selection logic.

Consider using the existing storage system:

-// This would be in a separate state file in a real implementation
-export const proxiesState = proxy({
-  proxies: [] as Array<{ id: string, url: string }>
-})
+import { appStorage } from '../appStorageProvider'

Then use appStorage.proxiesData throughout the component to ensure data consistency.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// This would be in a separate state file in a real implementation
export const proxiesState = proxy({
proxies: [] as Array<{ id: string, url: string }>
})
import { appStorage } from '../appStorageProvider'
🤖 Prompt for AI Agents
In src/react/ProxiesList.tsx around lines 10 to 13, the code defines a new
proxiesState which duplicates proxy data management. Instead, import and use the
existing appStorage.proxiesData from src/react/appStorageProvider.ts to maintain
a single source of truth. Replace proxiesState references with
appStorage.proxiesData throughout the component to ensure data consistency and
avoid synchronization issues.


export default () => {
const { proxies } = useSnapshot(proxiesState)
const isActive = useIsModalActive('proxies')

if (!isActive) return null

const addProxy = async () => {
const result = await showInputsModal('Add Proxy', {
url: {
type: 'text',
label: 'Proxy URL',
placeholder: 'wss://your-proxy.com'
}
})
if (!result) return

proxiesState.proxies.push({
id: Math.random().toString(36).slice(2),
url: result.url
})
}

const editProxy = async (proxy: { id: string, url: string }) => {
const result = await showInputsModal('Edit Proxy', {
url: {
type: 'text',
label: 'Proxy URL',
placeholder: 'wss://your-proxy.com',
defaultValue: proxy.url
}
})
if (!result) return

const index = proxiesState.proxies.findIndex(p => p.id === proxy.id)
if (index !== -1) {
proxiesState.proxies[index].url = result.url
}
}

const removeProxy = (id: string) => {
proxiesState.proxies = proxiesState.proxies.filter(p => p.id !== id)
}

return (
<Screen title="Proxies">
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, padding: 10 }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
{proxies.map(proxy => (
<div key={proxy.id} style={{ display: 'flex', alignItems: 'center', gap: 5, backgroundColor: 'rgba(0,0,0,0.5)', padding: 5 }}>
<span style={{ flex: 1 }}>{proxy.url}</span>
<Button
icon={pixelartIcons.edit}
style={{ width: 24, height: 24, padding: 0 }}
onClick={async () => editProxy(proxy)}
/>
<Button
icon={pixelartIcons.close}
style={{ width: 24, height: 24, padding: 0, color: '#ff4444' }}
onClick={() => removeProxy(proxy.id)}
/>
</div>
))}
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
<Button
style={{ flex: 1 }}
onClick={addProxy}
>
Add Proxy
</Button>
<span style={{ fontSize: 6, color: '#aaa' }}>
Note: You can self-host your own proxy in less than a minute with the script from
</span>
<Button
style={{ fontSize: 6, padding: '2px 4px' }}
onClick={() => openURL('https://github.com/zardoy/minecraft-everywhere')}
>
github.com/zardoy/minecraft-everywhere
</Button>
</div>
</div>
</Screen>
)
}
10 changes: 9 additions & 1 deletion src/react/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface Props {
placeholder?: string
containerStyle?: CSSProperties
disabled?: boolean
renderOption?: (option: OptionStorage) => React.ReactNode
}

export default ({
Expand All @@ -29,7 +30,8 @@ export default ({
defaultValue,
containerStyle,
placeholder,
disabled
disabled,
renderOption
}: Props) => {
const [inputValue, setInputValue] = useState<string | undefined>(defaultValue?.label ?? '')
const [currValue, setCurrValue] = useState<string | undefined>(defaultValue?.label ?? '')
Expand All @@ -46,6 +48,12 @@ export default ({
formatCreateLabel={(value) => {
return 'Use "' + value + '"'
}}
formatOptionLabel={(option) => {
if (renderOption) {
return renderOption(option)
}
return option.label
}}
isDisabled={disabled}
placeholder={placeholder ?? ''}
onChange={(e, action) => {
Expand Down
Loading
Loading