How Clash Nyanpasu Establishes and Uses the Clash WebSocket Connection for Real-Time Data

Clash Nyanpasu establishes authenticated WebSocket connections to the Clash core by first retrieving server credentials via the REST API, then constructing secure ws:// URLs with encoded tokens to stream connections, logs, traffic, and memory data in real time.

The libnyanpasu/clash-nyanpasu frontend implements a React-based architecture that communicates with the Clash proxy core through persistent WebSocket connections. This design enables the dashboard to display live connection states, bandwidth metrics, and system logs without the overhead of polling, utilizing hooks from the frontend/interface/src/ipc/ directory to manage connection lifecycles.

How the WebSocket Connection is Established

Retrieving Core Credentials via useClashInfo

Before opening any WebSocket connection, the frontend must obtain the Clash core's server address and authentication token. According to the source code in frontend/interface/src/ipc/use-clash-info.ts, the useClashInfo hook uses SWR to fetch this data from the local REST endpoint:

import useSWR from 'swr'
import { fetcher } from '../../utils'

export const useClashInfo = () => {
  const { data, error, mutate } = useSWR('/api/clash/info', fetcher)
  return {
    data,
    error,
    mutate,
  }
}

This returns an info object containing server (hostname:port) and secret (authentication token) required for subsequent WebSocket authentication.

Building Authenticated WebSocket URLs

Located in frontend/interface/src/ipc/use-clash-web-socket.ts (lines 8-14), the useClashWebSocket hook constructs the base URL and authentication parameters using React's useMemo to prevent unnecessary recalculations:

const wsBaseUrl = useMemo(() => `ws://${info?.server}`, [info?.server])

const tokenParams = useMemo(
  () => `token=${encodeURIComponent(info?.secret || '')}`,
  [info?.secret],
)

The token is URL-encoded and forced into the query string because the Clash core rejects WebSocket connections that lack a valid token parameter.

Resolving Endpoint URLs for Data Streams

As implemented in lines 16-31 of the same file, a resolveUrl helper composes the final WebSocket endpoints for each data stream:

const resolveUrl = (path: string) => `${wsBaseUrl}/${path}?${tokenParams}`

Four distinct endpoints are resolved for real-time data: connections, logs, traffic, and memory. These URLs are memoized in a urls object and only recomputed when the info object changes, ensuring stable references across React renders.

Real-Time Data Streams and React Hooks

Creating WebSocket Connections with AHooks

The useClashWebSocket hook leverages AHooks' useWebSocket utility to establish the actual socket connections. As shown in lines 34-40 of frontend/interface/src/ipc/use-clash-web-socket.ts, empty strings are passed as URLs while credentials are loading, keeping the hooks idle until authentication data is available:

const connectionsWS = useWebSocket(urls?.connections ?? '')
const logsWS        = useWebSocket(urls?.logs ?? '')
const trafficWS     = useWebSocket(urls?.traffic ?? '')
const memoryWS      = useWebSocket(urls?.memory ?? '')

Each returned object contains the WebSocket's readyState, message (latest data payload), sendMessage method, and connection controls. The hook aggregates these into a single return object (lines 42-48):

return { connectionsWS, logsWS, trafficWS, memoryWS }

Consuming Live Data in Components

UI components import useClashWebSocket and subscribe to specific streams through the returned socket objects. For example, to display live connection lists:

import { useClashWebSocket } from '@/frontend/interface/src/ipc/use-clash-web-socket'

export const ConnectionList = () => {
  const { connectionsWS } = useClashWebSocket()

  const connections = useMemo(() => {
    try {
      return connectionsWS?.message ? JSON.parse(connectionsWS.message) : []
    } catch {
      return []
    }
  }, [connectionsWS?.message])

  return (
    <ul>
      {connections.map((c: any) => (
        <li key={c.id}>
          {c.name} – {c.status}
        </li>
      ))}
    </ul>
  )
}

For real-time traffic visualization:

import { useClashWebSocket } from '@/frontend/interface/src/ipc/use-clash-web-socket'
import { Line } from 'react-chartjs-2'

export const TrafficChart = () => {
  const { trafficWS } = useClashWebSocket()
  const [dataPoints, setDataPoints] = useState<number[]>([])

  useEffect(() => {
    if (!trafficWS?.message) return
    const { upload, download } = JSON.parse(trafficWS.message)
    setDataPoints((prev) => [...prev.slice(-19), upload + download])
  }, [trafficWS?.message])

  const chartData = {
    labels: dataPoints.map((_, i) => i),
    datasets: [{ 
      label: 'Traffic (bytes)', 
      data: dataPoints, 
      borderColor: '#42b983' 
    }],
  }

  return <Line data={chartData} />
}

Key Source Files and Architecture

The WebSocket implementation spans several critical files in the libnyanpasu/clash-nyanpasu codebase:

Summary

  • Credential Retrieval: The system uses SWR to fetch server address and authentication tokens from /api/clash/info before establishing any WebSocket connections.
  • URL Construction: The useClashWebSocket hook dynamically builds ws:// URLs with encoded token query parameters to satisfy Clash core authentication requirements.
  • Multi-Stream Architecture: Separate WebSocket connections handle connections, logs, traffic, and memory data streams, preventing head-of-line blocking between different data types.
  • React Integration: AHooks' useWebSocket manages connection lifecycles, while components consume the message property to receive JSON payloads and update UI state in real time.
  • Lazy Initialization: Empty string fallbacks prevent premature connection attempts before credentials are loaded, ensuring clean connection establishment.

Frequently Asked Questions

How does Clash Nyanpasu authenticate WebSocket connections to the Clash core?

Clash Nyanpasu authenticates WebSocket connections by URL-encoding the secret token obtained from /api/clash/info and appending it as a token query parameter to the WebSocket URL. The source code in frontend/interface/src/ipc/use-clash-web-socket.ts (lines 10-13) explicitly constructs this parameter using encodeURIComponent(info?.secret || '') because the Clash core rejects unauthenticated WebSocket upgrade requests.

Why does the frontend use four separate WebSocket connections instead of one?

The frontend maintains four distinct WebSocket connections (for connections, logs, traffic, and memory) to leverage the Clash core's separate endpoint architecture. This design isolates data streams, preventing slow or high-volume streams (like logs) from interfering with critical real-time metrics (like traffic statistics), and allows components to subscribe only to the specific data they need.

What happens if the Clash core credentials are not yet available when the component mounts?

If credentials are unavailable, the useClashWebSocket hook passes empty strings to the underlying useWebSocket calls from AHooks (lines 34-40). This keeps the WebSocket connections in a dormant state until the SWR-powered useClashInfo hook successfully retrieves the server address and secret token, at which point the URLs resolve and the connections establish automatically.

Can components send data back to the Clash core through these WebSockets?

Yes, each WebSocket object returned by useClashWebSocket includes a sendMessage function provided by AHooks. While the primary use case in Clash Nyanpasu is consuming real-time data from the core, the bidirectional nature of WebSockets allows the frontend to transmit commands or configuration updates back to the Clash API when needed.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →