mirror of
https://github.com/woodchen-ink/clash-and-dashboard.git
synced 2025-07-18 14:01:56 +08:00
Feature: use websocket api
This commit is contained in:
parent
d66cf8f9fa
commit
da255fa38c
152
package-lock.json
generated
152
package-lock.json
generated
@ -101,6 +101,12 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -956,6 +962,12 @@
|
||||
"lodash": "^4.17.13",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1250,6 +1262,12 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/semver": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.2.tgz",
|
||||
"integrity": "sha512-G1Ggy7/9Nsa1Jt2yiBR2riEuyK2DFNnqow6R7cromXPMNynackRY1vqFTLz/gwnef1LHokbXThcPhqMRjUbkpQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/unist": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz",
|
||||
@ -2232,6 +2250,15 @@
|
||||
"semver": "^5.5.0",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"execa": {
|
||||
@ -2272,6 +2299,15 @@
|
||||
"bin-version": "^3.0.0",
|
||||
"semver": "^5.6.0",
|
||||
"semver-truncate": "^1.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"bin-wrapper": {
|
||||
@ -3927,6 +3963,14 @@
|
||||
"semver": "^5.5.0",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"execa": {
|
||||
@ -5851,6 +5895,15 @@
|
||||
"semver": "^5.5.0",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"execa": {
|
||||
@ -6627,6 +6680,15 @@
|
||||
"semver": "^5.5.0",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"execa": {
|
||||
@ -6695,6 +6757,15 @@
|
||||
"semver": "^5.5.0",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"execa": {
|
||||
@ -7804,6 +7875,14 @@
|
||||
"requires": {
|
||||
"pify": "^4.0.1",
|
||||
"semver": "^5.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"mamacro": {
|
||||
@ -8404,6 +8483,14 @@
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"semver": "^5.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"normalize-package-data": {
|
||||
@ -8416,6 +8503,14 @@
|
||||
"resolve": "^1.10.0",
|
||||
"semver": "2 || 3 || 4 || 5",
|
||||
"validate-npm-package-license": "^3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"normalize-path": {
|
||||
@ -8773,6 +8868,14 @@
|
||||
"semver": "^5.5.0",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"execa": {
|
||||
@ -9222,6 +9325,15 @@
|
||||
"semver": "^5.5.0",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"execa": {
|
||||
@ -10475,10 +10587,9 @@
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
|
||||
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
|
||||
"dev": true
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
},
|
||||
"semver-regex": {
|
||||
"version": "2.0.0",
|
||||
@ -10495,6 +10606,15 @@
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"semver": "^5.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"send": {
|
||||
@ -12343,6 +12463,14 @@
|
||||
"semver": "^5.3.0",
|
||||
"tslib": "^1.8.0",
|
||||
"tsutils": "^2.29.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"tslint-config-standard": {
|
||||
@ -12393,6 +12521,14 @@
|
||||
"object-assign": "^4.1.1",
|
||||
"rimraf": "^2.4.4",
|
||||
"semver": "^5.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"tsutils": {
|
||||
@ -13096,6 +13232,14 @@
|
||||
"semver": "^5.5.0",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
|
@ -41,6 +41,7 @@
|
||||
"@types/react-router-dom": "^5.1.0",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.0",
|
||||
"@types/react-window": "^1.8.1",
|
||||
"@types/semver": "^6.0.2",
|
||||
"autoprefixer": "^9.6.1",
|
||||
"awesome-typescript-loader": "^5.2.1",
|
||||
"babel-loader": "^8.0.6",
|
||||
@ -83,6 +84,7 @@
|
||||
"react-router-dom": "^5.1.1",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
"react-window": "^1.8.5",
|
||||
"semver": "^6.3.0",
|
||||
"unstated-next": "^1.1.0",
|
||||
"use-immer": "^0.3.4"
|
||||
}
|
||||
|
@ -39,7 +39,6 @@ export default function Proxies () {
|
||||
|
||||
const [sort, setSort] = useState(sortType.None)
|
||||
const proxies = useMemo(() => {
|
||||
console.log(1)
|
||||
switch (sort) {
|
||||
case sortType.Desc:
|
||||
return data.proxy.slice().sort((a, b) => compareDesc(a, b))
|
||||
|
29
src/lib/asyncSingleton.ts
Normal file
29
src/lib/asyncSingleton.ts
Normal file
@ -0,0 +1,29 @@
|
||||
export function createAsyncSingleton<T> (fn: () => Promise<T>): () => Promise<T> {
|
||||
let promise: Promise<T> | null = null
|
||||
let instance: T | null = null
|
||||
|
||||
async function fetch () {
|
||||
if (promise) {
|
||||
return promise
|
||||
}
|
||||
|
||||
promise = fn()
|
||||
return promise
|
||||
.then(r => {
|
||||
promise = null
|
||||
return r
|
||||
})
|
||||
.catch(e => {
|
||||
promise = null
|
||||
return e
|
||||
})
|
||||
}
|
||||
|
||||
return async function () {
|
||||
if (instance) {
|
||||
return instance
|
||||
}
|
||||
|
||||
return fetch()
|
||||
}
|
||||
}
|
@ -1,12 +1,10 @@
|
||||
import axios, { AxiosInstance } from 'axios'
|
||||
import { Partial, getLocalStorageItem } from '@lib/helper'
|
||||
import axios from 'axios'
|
||||
import { Partial, getLocalStorageItem, to } from '@lib/helper'
|
||||
import { isClashX, jsBridge } from '@lib/jsBridge'
|
||||
import { createAsyncSingleton } from '@lib/asyncSingleton'
|
||||
import { Log } from '@models/Log'
|
||||
import { StreamReader } from './streamer'
|
||||
|
||||
let instance: AxiosInstance
|
||||
let logsStreamReader: StreamReader<Log> = null
|
||||
|
||||
export interface Config {
|
||||
port: number
|
||||
'socks-port': number
|
||||
@ -51,6 +49,19 @@ export interface Group {
|
||||
history: History[]
|
||||
}
|
||||
|
||||
export const getInstance = createAsyncSingleton(async () => {
|
||||
const {
|
||||
hostname,
|
||||
port,
|
||||
secret
|
||||
} = await getExternalControllerConfig()
|
||||
|
||||
return axios.create({
|
||||
baseURL: `//${hostname}:${port}`,
|
||||
headers: secret ? { Authorization: `Bearer ${secret}` } : {}
|
||||
})
|
||||
})
|
||||
|
||||
export async function getConfig () {
|
||||
const req = await getInstance()
|
||||
return req.get<Config>('configs')
|
||||
@ -81,6 +92,11 @@ export async function getProxy (name: string) {
|
||||
return req.get<Proxy>(`proxies/${name}`)
|
||||
}
|
||||
|
||||
export async function getVersion () {
|
||||
const req = await getInstance()
|
||||
return req.get<{ version: string }>('version')
|
||||
}
|
||||
|
||||
export async function getProxyDelay (name: string) {
|
||||
const req = await getInstance()
|
||||
return req.get<{ delay: number }>(`proxies/${name}/delay`, {
|
||||
@ -96,25 +112,6 @@ export async function changeProxySelected (name: string, select: string) {
|
||||
return req.put<void>(`proxies/${name}`, { name: select })
|
||||
}
|
||||
|
||||
export async function getInstance () {
|
||||
if (instance) {
|
||||
return instance
|
||||
}
|
||||
|
||||
const {
|
||||
hostname,
|
||||
port,
|
||||
secret
|
||||
} = await getExternalControllerConfig()
|
||||
|
||||
instance = axios.create({
|
||||
baseURL: `//${hostname}:${port}`,
|
||||
headers: secret ? { Authorization: `Bearer ${secret}` } : {}
|
||||
})
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
export async function getExternalControllerConfig () {
|
||||
if (isClashX()) {
|
||||
const info = await jsBridge.getAPIInfo()
|
||||
@ -137,14 +134,12 @@ export async function getExternalControllerConfig () {
|
||||
return { hostname, port, secret }
|
||||
}
|
||||
|
||||
export async function getLogsStreamReader () {
|
||||
if (logsStreamReader) {
|
||||
return logsStreamReader
|
||||
}
|
||||
export const getLogsStreamReader = createAsyncSingleton(async function getLogsStreamReader () {
|
||||
const externalController = await getExternalControllerConfig()
|
||||
const { data: config } = await getConfig()
|
||||
const logUrl = `//${externalController.hostname}:${externalController.port}/logs?level=${config['log-level']}`
|
||||
const auth = externalController.secret ? { Authorization: `Bearer ${externalController.secret}` } : {}
|
||||
logsStreamReader = new StreamReader({ url: logUrl, bufferLength: 200, headers: auth })
|
||||
return logsStreamReader
|
||||
}
|
||||
const [data, err] = await to(getVersion())
|
||||
const version = err ? 'unkonwn version' : data.data.version
|
||||
|
||||
const logUrl = `${location.protocol}//${externalController.hostname}:${externalController.port}/logs?level=${config['log-level']}`
|
||||
return new StreamReader<Log>({ url: logUrl, bufferLength: 200, token: externalController.secret, version })
|
||||
})
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { to } from '@lib/helper'
|
||||
import semver from 'semver'
|
||||
import EventEmitter from 'eventemitter3'
|
||||
|
||||
export interface Config {
|
||||
url: string
|
||||
headers?: { [key: string]: string }
|
||||
version: string
|
||||
token?: string
|
||||
bufferLength?: number
|
||||
retryInterval?: number
|
||||
}
|
||||
@ -24,15 +26,43 @@ export class StreamReader<T> {
|
||||
config
|
||||
)
|
||||
|
||||
if (semver.valid(config.version) && semver.gt(config.version, 'v0.15.0-52-gc384693')) {
|
||||
this.websocketLoop()
|
||||
return
|
||||
}
|
||||
this.loop()
|
||||
}
|
||||
|
||||
protected websocketLoop () {
|
||||
const url = new URL(this.config.url)
|
||||
url.protocol = location.protocol === 'http:' ? 'ws:' : 'wss:'
|
||||
url.searchParams.set('token', this.config.token)
|
||||
|
||||
const connection = new WebSocket(url.toJSON())
|
||||
connection.addEventListener('message', msg => {
|
||||
const data = JSON.parse(msg.data)
|
||||
this.EE.emit('data', [data])
|
||||
if (this.config.bufferLength > 0) {
|
||||
this.innerBuffer.push(data)
|
||||
if (this.innerBuffer.length > this.config.bufferLength) {
|
||||
this.innerBuffer.splice(0, this.innerBuffer.length - this.config.bufferLength)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
connection.addEventListener('close', () => setTimeout(this.websocketLoop, this.config.retryInterval))
|
||||
connection.addEventListener('error', err => {
|
||||
this.EE.emit('error', err)
|
||||
setTimeout(this.websocketLoop, this.config.retryInterval)
|
||||
})
|
||||
}
|
||||
|
||||
protected async loop () {
|
||||
const [resp, err] = await to(fetch(
|
||||
this.config.url,
|
||||
{
|
||||
mode: 'cors',
|
||||
headers: this.config.headers
|
||||
headers: this.config.token ? { Authorization: `Bearer ${this.config.token}` } : {}
|
||||
}
|
||||
))
|
||||
if (err) {
|
||||
|
@ -76,6 +76,7 @@ export interface APIInfo {
|
||||
}
|
||||
|
||||
export interface Data {
|
||||
version?: string
|
||||
|
||||
general?: {
|
||||
|
||||
|
@ -8,6 +8,7 @@ import { useI18n } from '@i18n'
|
||||
|
||||
function useData () {
|
||||
const [data, set] = useObject<Models.Data>({
|
||||
version: '',
|
||||
general: {},
|
||||
proxy: [],
|
||||
proxyGroup: [],
|
||||
@ -56,6 +57,13 @@ function useData () {
|
||||
proxyGroup: general.mode === 'Global' ? [proxyList] : groups as API.Group[],
|
||||
rules: rules.data.rules
|
||||
})
|
||||
|
||||
const [version, vErr] = await to(API.getVersion())
|
||||
if (vErr) {
|
||||
return
|
||||
}
|
||||
|
||||
set('version', version.data.version)
|
||||
}
|
||||
|
||||
function updateDelay (proxy: string, delay: number) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user