Skip to content

Conversation

@zardoy
Copy link
Owner

@zardoy zardoy commented Jul 8, 2025

Summary by CodeRabbit

  • New Features

    • Introduced automatic proxy selection, which pings available proxies and selects the fastest one for improved connectivity.
    • Added a proxy management interface, allowing users to add, edit, and remove proxy URLs via a dedicated modal.
    • Enhanced proxy selection UI with an "auto-select" option and real-time ping status indicators.
    • Provided a customizable select dropdown with support for custom option rendering.
  • Improvements

    • Proxy settings now support an "auto-select" mode for seamless proxy selection.
    • Users can manage their proxy list directly within the app interface.
  • Bug Fixes

    • Ensured proxy selection logic is consistent and only occurs after a successful auto-selection process.

@coderabbitai
Copy link

coderabbitai bot commented Jul 8, 2025

Walkthrough

The changes introduce a comprehensive proxy management and auto-selection system. New modules and UI components enable users to add, edit, and remove proxy servers, visualize their latency, and select proxies manually or via automatic selection based on real-time network measurements. Core logic for measuring latency and selecting the optimal proxy is implemented, and the UI is updated to support these features.

Changes

File(s) Change Summary
src/core/pingProxy.ts Added LatencyMonitor class for measuring proxy latency via WebSocket; exported pingProxyServer and monitorLatency functions.
src/core/proxyAutoSelect.ts New module for automatic proxy selection, latency tracking, and cancellation of ongoing pings; exposes reactive state and selection logic.
src/index.ts Integrated auto-proxy selection into the connect function; imports and uses new proxy selection logic.
src/react/ProxiesList.tsx New React component and state for managing a list of proxies with add, edit, and remove capabilities via modal dialogs.
src/react/Select.tsx Added optional renderOption prop for custom rendering of select options in dropdowns.
src/react/SelectOption.tsx Added SimpleSelectOption component for modal-based select UI with optional manage button.
src/react/ServersList.tsx Updated proxy selection UI to use SimpleSelectOption, support auto-select, and show real-time ping status; added ProxyPingStatus.
src/react/appStorageProvider.ts Extended SavedProxiesData interface to include optional isAutoSelect boolean flag.
src/reactUi.tsx Imported and rendered ProxiesList component within the main app portal.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI (ServersList/ProxiesList)
    participant ProxyAutoSelect
    participant PingProxy
    participant Network

    User->>UI (ProxiesList): Add/Edit/Remove Proxy
    UI (ProxiesList)->>ProxyAutoSelect: Update proxy list state

    User->>UI (ServersList): Select "Auto-select" proxy
    UI (ServersList)->>ProxyAutoSelect: selectBestProxy(proxies)
    ProxyAutoSelect->>PingProxy: pingProxyServer(proxyUrl)
    PingProxy->>Network: WebSocket ping/pong
    Network-->>PingProxy: Latency/response
    PingProxy-->>ProxyAutoSelect: Latency result
    ProxyAutoSelect-->>UI (ServersList): Update ping status
    ProxyAutoSelect-->>UI (ServersList): Return best proxy
    UI (ServersList)-->>User: Show selected/best proxy and latency
Loading

Poem

In tunnels of code where the proxies dwell,
A rabbit now pings, selects, and can tell
Which server’s the fastest, which one’s the best—
With modals and lists, it puts them to test!
Hop, hop, hooray, for the network’s new might,
Proxy selection is now quick and light!
🐇💨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (3)
src/react/ProxiesList.tsx (2)

32-32: Improve ID generation for better uniqueness.

Using Math.random().toString(36).slice(2) for ID generation could theoretically produce duplicates, though unlikely. Consider using a more robust approach.

-      id: Math.random().toString(36).slice(2),
+      id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,

Or consider using crypto.randomUUID() if available:

-      id: Math.random().toString(36).slice(2),
+      id: crypto.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`,

21-35: Consider adding URL validation.

The addProxy function doesn't validate the entered proxy URL format. Consider adding basic validation to ensure the URL is properly formatted.

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

+   // Basic URL validation
+   try {
+     new URL(result.url)
+   } catch {
+     // Could show an error notification here
+     return
+   }

    proxiesState.proxies.push({
      id: Math.random().toString(36).slice(2),
      url: result.url
    })
  }
src/react/SelectOption.tsx (1)

200-247: LGTM! Well-implemented select component with minor accessibility suggestions.

The SimpleSelectOption component is well-structured with proper TypeScript typing and clean logic flow. Consider adding accessibility improvements:

        <div
          style={{
            cursor: 'pointer',
            color: value ? 'white' : 'gray',
            userSelect: 'none'
          }}
+         role="button"
+         tabIndex={0}
+         onKeyDown={(e) => {
+           if (e.key === 'Enter' || e.key === ' ') {
+             e.preventDefault()
+             // trigger the same logic as onClick
+           }
+         }}
          onClick={() => {

This would make the component accessible via keyboard navigation.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f93c08 and 50acc6d.

📒 Files selected for processing (9)
  • src/core/pingProxy.ts (1 hunks)
  • src/core/proxyAutoSelect.ts (1 hunks)
  • src/index.ts (2 hunks)
  • src/react/ProxiesList.tsx (1 hunks)
  • src/react/Select.tsx (3 hunks)
  • src/react/SelectOption.tsx (1 hunks)
  • src/react/ServersList.tsx (5 hunks)
  • src/react/appStorageProvider.ts (1 hunks)
  • src/reactUi.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`src/**/*.ts`: You may use the global variable `bot` directly in any file under ...

src/**/*.ts: You may use the global variable bot directly in any file under the src/ directory (e.g., src/mineflayer/playerState.ts).
In src/ code, you may use the global variable appViewer from src/appViewer.ts directly. Do not import appViewer or use window.appViewer; use the global appViewer variable as-is.

📄 Source: CodeRabbit Inference Engine (.cursor/rules/vars-usage.mdc)

List of files the instruction was applied to:

  • src/react/appStorageProvider.ts
  • src/core/pingProxy.ts
  • src/index.ts
  • src/core/proxyAutoSelect.ts
🧠 Learnings (3)
src/reactUi.tsx (1)
Learnt from: CR
PR: zardoy/minecraft-web-client#0
File: .cursor/rules/vars-usage.mdc:0-0
Timestamp: 2025-07-02T02:50:46.443Z
Learning: Some other global variables that can be used without window prefixes are listed in src/globals.d.ts.
src/react/ProxiesList.tsx (1)
Learnt from: CR
PR: zardoy/minecraft-web-client#0
File: .cursor/rules/vars-usage.mdc:0-0
Timestamp: 2025-07-02T02:50:46.443Z
Learning: Applies to renderer/**/*.ts : In renderer code (such as in `WorldRendererThree`), use the `playerState` property (e.g., `worldRenderer.playerState.gameMode`) to access player state. The implementation for `playerState` lives in `src/mineflayer/playerState.ts`.
src/index.ts (1)
Learnt from: CR
PR: zardoy/minecraft-web-client#0
File: .cursor/rules/vars-usage.mdc:0-0
Timestamp: 2025-07-02T02:50:46.443Z
Learning: Some other global variables that can be used without window prefixes are listed in src/globals.d.ts.
🧬 Code Graph Analysis (2)
src/react/ProxiesList.tsx (1)
src/react/SelectOption.tsx (1)
  • useSnapshot (101-198)
src/core/proxyAutoSelect.ts (1)
src/core/pingProxy.ts (1)
  • pingProxyServer (71-97)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-and-deploy
🔇 Additional comments (14)
src/react/appStorageProvider.ts (1)

18-18: LGTM! Clean interface extension.

The addition of the optional isAutoSelect boolean property properly extends the proxy data structure to support the auto-selection feature.

src/reactUi.tsx (2)

69-69: LGTM! Proper component import.

The import follows the established pattern for React components in this file.


254-254: LGTM! Proper component integration.

The ProxiesList component is correctly placed inside the RobustPortal alongside other modal components, following the established UI pattern.

src/react/Select.tsx (2)

22-22: LGTM! Well-typed optional prop.

The renderOption prop is properly typed as an optional function that takes an OptionStorage and returns a React node.


51-56: LGTM! Proper implementation with fallback.

The implementation correctly uses the custom renderOption function when provided, with a sensible fallback to the default option.label rendering.

src/index.ts (2)

100-101: LGTM! Clean imports for new functionality.

The imports are correctly structured and follow the existing pattern in the file.


310-311: Good refactoring of proxy parsing logic.

Moving the proxy parsing inside the conditional block after auto-selection is logical and efficient. The code maintains the same functionality while supporting the new auto-selection feature.

src/core/proxyAutoSelect.ts (2)

5-19: Well-designed state interface with clear typing.

The interface clearly defines the proxy ping state structure with appropriate types for status tracking and error handling.


86-91: Clean cancellation implementation.

The cancellation logic is properly implemented with appropriate cleanup.

src/react/ServersList.tsx (3)

1-1: Clean import organization supporting new proxy features.

All necessary dependencies are properly imported for the enhanced proxy functionality.

Also applies to: 3-3, 5-5, 10-10, 14-14


25-30: Excellent integration of auto-selection logic in getCurrentProxy.

The conditional logic properly handles auto-selection mode by returning undefined to delegate proxy choice to the connection logic.


169-188: Well-designed proxy selection UI with comprehensive options.

The UI provides excellent user experience with auto-select, manual selection, and management options. The state management properly handles the isAutoSelect flag.

src/core/pingProxy.ts (2)

71-97: Robust error handling in pingProxyServer function.

The function has good error handling and proper resource cleanup. The abort signal integration is well-implemented.


8-25: Ensure robust WebSocket URL construction and endpoint validation

  • Replace manual string-based protocol/path manipulation with the URL API and explicit error handling:
 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
-    }
+    let wsUrl: string
+    try {
+      const url = new URL(this.serverUrl)
+      url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'
+      url.pathname = '/api/vm/net/ping'
+      wsUrl = url.toString()
+    } catch {
+      return reject(new Error(`Invalid server URL: ${this.serverUrl}`))
+    }
     this.ws = new WebSocket(wsUrl)
  • No references to /api/vm/net/ping were found elsewhere in the repo. Please verify that all target proxy servers actually expose this endpoint—or adjust the path if necessary.

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

Comment on lines +292 to +308
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
}
}
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.

Comment on lines +62 to +84
// 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
}
}
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.

Comment on lines +23 to +40
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' }
}

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.

Comment on lines +42 to +60
// 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
}
})
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.

Comment on lines +45 to +77
const ProxyPingStatus = ({ proxy }: { proxy: string }) => {
const pingState = useSnapshot(proxyPingState).proxyStatus[proxy]
useEffect(() => {
if (!proxyPingState.checkStarted) {
void selectBestProxy(appStorage.proxiesData?.proxies ?? [])
}
}, [])

if (!pingState) return null

let color = 'yellow'
let text = '...'

if (pingState.status === 'success') {
color = 'limegreen'
text = `${pingState.latency}ms`
} else if (pingState.status === 'error') {
color = 'red'
text = 'err'
}

return (
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', marginLeft: '4px' }}>
<div style={{
width: '8px',
height: '8px',
borderRadius: '50%',
backgroundColor: color
}} />
<span style={{ fontSize: '12px', color: 'lightgray' }}>{text}</span>
</div>
)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

ProxyPingStatus component has potential performance and lifecycle issues.

The component has good visual design but some concerns:

  1. The useEffect runs on every render without proper dependencies
  2. Potential memory leak if component unmounts during ping operation
  3. Missing error boundary protection
 const ProxyPingStatus = ({ proxy }: { proxy: string }) => {
   const pingState = useSnapshot(proxyPingState).proxyStatus[proxy]
+  const hasStartedRef = useRef(false)
+  
   useEffect(() => {
-    if (!proxyPingState.checkStarted) {
+    if (!proxyPingState.checkStarted && !hasStartedRef.current) {
+      hasStartedRef.current = true
       void selectBestProxy(appStorage.proxiesData?.proxies ?? [])
     }
-  }, [])
+  }, []) // Dependencies are correct since we only want this to run once
 
   if (!pingState) return null

Consider adding error boundary protection and cleanup:

useEffect(() => {
  return () => {
    // Cleanup on unmount
    if (proxyPingState.checkStarted) {
      // Consider if we should cancel here or let it continue
    }
  }
}, [])
🤖 Prompt for AI Agents
In src/react/ServersList.tsx between lines 45 and 77, the ProxyPingStatus
component's useEffect lacks dependencies, causing it to run only once but
potentially missing updates; add proper dependencies to ensure it runs when
needed. Also, implement a cleanup function inside useEffect to handle component
unmounting, preventing memory leaks by canceling or ignoring ongoing ping
operations if applicable. Finally, wrap the component or its usage in an error
boundary to catch and handle runtime errors gracefully.

Comment on lines +99 to +121
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)
}
}
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.

Comment on lines +27 to +61
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)
})
}
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants