-
Notifications
You must be signed in to change notification settings - Fork 132
feat: add proxy auto-select and proxies select menu! #389
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Remove or improve the example monitorLatency function. The 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential mutation of loop variable causing incorrect proxy tracking. There's a critical bug where the 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function cancelProxyPinging () { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (currentPingAbortController) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| currentPingAbortController.abort() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| currentPingAbortController = null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 = [] | ||
|
|
@@ -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) { | ||
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify error handling and user experience for proxy selection failures. The auto-selection logic is well-implemented, but there are a few considerations:
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 🏁 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 2Length of output: 319 Improve offline detection and error messaging for proxy selection The • File: src/index.ts, lines 292–308 - 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.
🤖 Prompt for AI Agents |
||
|
|
||
| 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') ?? ''}` } }) | ||
| } | ||
|
|
||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||
|
|
||||||||||||
| 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> | ||||||||||||
| ) | ||||||||||||
| } | ||||||||||||
There was a problem hiding this comment.
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:
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