diff options
Diffstat (limited to 'src/api')
| -rw-r--r-- | src/api/configs.ts | 18 | ||||
| -rw-r--r-- | src/api/connections.ts | 12 | ||||
| -rw-r--r-- | src/api/logs.ts | 23 | ||||
| -rw-r--r-- | src/api/memory.ts | 10 | ||||
| -rw-r--r-- | src/api/proxies.ts | 40 | ||||
| -rw-r--r-- | src/api/rule-provider.ts | 6 | ||||
| -rw-r--r-- | src/api/rules.ts | 47 | ||||
| -rw-r--r-- | src/api/traffic.ts | 10 | ||||
| -rw-r--r-- | src/api/version.ts | 4 |
9 files changed, 102 insertions, 68 deletions
diff --git a/src/api/configs.ts b/src/api/configs.ts index 63a88c7..73d9683 100644 --- a/src/api/configs.ts +++ b/src/api/configs.ts @@ -19,17 +19,10 @@ export async function fetchConfigs(apiConfig: ClashAPIConfig, signal?: AbortSign // { Path: string } type ClashConfigPartial = TunPartial<ClashGeneralConfig>; -function configsPatchWorkaround(o: ClashConfigPartial) { - // backward compatibility for older clash using `socket-port` - if ('socks-port' in o) { - o['socket-port'] = o['socks-port']; - } - return o; -} export async function updateConfigs(apiConfig: ClashAPIConfig, o: ClashConfigPartial) { const { url, init } = getURLAndInit(apiConfig); - const body = JSON.stringify(configsPatchWorkaround(o)); + const body = JSON.stringify(o); return await fetch(url + endpoint, { ...init, body, method: 'PATCH' }); } @@ -45,10 +38,15 @@ export async function restartCore(apiConfig: ClashAPIConfig) { return await fetch(url + restartCoreEndpoint, { ...init, body, method: 'POST' }); } -export async function upgradeCore(apiConfig: ClashAPIConfig) { +// 内核更新通道,对应 mihomo `POST /upgrade?channel=` 的取值; +// 不传则由内核按当前版本自动选择 +export type UpgradeChannel = 'release' | 'alpha'; + +export async function upgradeCore(apiConfig: ClashAPIConfig, channel?: UpgradeChannel) { const { url, init } = getURLAndInit(apiConfig); const body = '{"path": "", "payload": ""}'; - return await fetch(url + upgradeCoreEndpoint, { ...init, body, method: 'POST' }); + const query = channel ? `?channel=${channel}` : ''; + return await fetch(url + upgradeCoreEndpoint + query, { ...init, body, method: 'POST' }); } export async function upgradeGeo(apiConfig: ClashAPIConfig) { diff --git a/src/api/connections.ts b/src/api/connections.ts index 8e5c1fd..0fcf2da 100644 --- a/src/api/connections.ts +++ b/src/api/connections.ts @@ -7,11 +7,11 @@ const endpoint = '/connections'; const fetched = false; interface Subscriber { - listner: unknown; // on data received, listener will be called with data + listner: ConnectionsListener; // on data received, listener will be called with data onClose: () => void; // on stream closed, onClose will be called } -const subscribers = []; +const subscribers: Subscriber[] = []; // see also https://github.com/Dreamacro/clash/blob/dev/constant/metadata.go#L41 type UUID = string; @@ -41,11 +41,12 @@ export type ConnectionItem = { rule: string; rulePayload?: string; }; -type ConnectionsData = { +export type ConnectionsData = { downloadTotal: number; uploadTotal: number; connections: Array<ConnectionItem>; }; +type ConnectionsListener = (data: ConnectionsData) => void; function appendData(s: string) { let o: ConnectionsData; @@ -60,7 +61,6 @@ function appendData(s: string) { } }); } catch (err) { - console.log('JSON.parse error', JSON.parse(s)); } subscribers.forEach((s) => s.listner(o)); @@ -71,8 +71,8 @@ type UnsubscribeFn = () => void; let wsState: number; export function fetchData( apiConfig: ClashAPIConfig, - listener: unknown, - onClose: () => void + listener: ConnectionsListener, + onClose: () => void, ): UnsubscribeFn | void { if (fetched || wsState === 1) { if (listener) diff --git a/src/api/logs.ts b/src/api/logs.ts index 53bb8ad..66989ce 100644 --- a/src/api/logs.ts +++ b/src/api/logs.ts @@ -1,6 +1,5 @@ import { pad0 } from '~/misc/utils'; -import { Log } from '~/store/types'; -import { LogsAPIConfig } from '~/types'; +import { Log, LogsAPIConfig } from '~/types'; import { buildLogsWebSocketURL, getURLAndInit } from '../misc/request-helper'; @@ -21,8 +20,8 @@ const getRandomStr = () => { let even = false; let decoded = ''; -let ws: WebSocket; -let controller: AbortController; +let ws: WebSocket | undefined; +let controller: AbortController | undefined; let usingFetchFallback = false; let currentConnStr: string; @@ -41,7 +40,6 @@ function appendData(s: string) { try { o = JSON.parse(s); } catch (err) { - console.log('JSON.parse error', s); return; } @@ -65,7 +63,7 @@ function formatDate(d: Date) { return `${YY}-${MM}-${dd} ${HH}:${mm}:${ss}`; } -function pump(reader: ReadableStreamDefaultReader) { +function pump(reader: ReadableStreamDefaultReader): Promise<void> { return reader.read().then(({ done, value }) => { const str = textDecoder.decode(value, { stream: !done }); decoded += str; @@ -82,7 +80,6 @@ function pump(reader: ReadableStreamDefaultReader) { appendData(lastSplit); decoded = ''; - console.log('GET /logs streaming done'); usingFetchFallback = false; return; @@ -97,7 +94,7 @@ function pump(reader: ReadableStreamDefaultReader) { function makeConnStr(c: LogsAPIConfig) { const keys = Object.keys(c); keys.sort(); - return keys.map((k) => c[k]).join('|'); + return keys.map((k) => c[k as keyof LogsAPIConfig]).join('|'); } function isConnectionLive() { @@ -139,7 +136,7 @@ function openConnection(apiConfig: LogsAPIConfig) { export function fetchLogs( apiConfig: LogsAPIConfig, - appendLog: AppendLogFn + appendLog: AppendLogFn, ): UnsubscribeFn | undefined { if (apiConfig.logLevel === 'uninit') return undefined; @@ -179,14 +176,16 @@ function fetchLogsWithFetch(apiConfig: LogsAPIConfig) { signal, }).then( (response) => { - const reader = response.body.getReader(); - pump(reader); + if (!response.body) { + usingFetchFallback = false; + return; + } + pump(response.body.getReader()); }, (err) => { usingFetchFallback = false; if (signal.aborted) return; - console.log('GET /logs error:', err.message); }, ); diff --git a/src/api/memory.ts b/src/api/memory.ts index 8b0c738..6f4008a 100644 --- a/src/api/memory.ts +++ b/src/api/memory.ts @@ -15,7 +15,7 @@ const memory = { oslimit: Array(Size).fill(null), size: Size, - subscribers: [], + subscribers: [] as Array<(o: { inuse: number; oslimit: number }) => void>, appendData(o: { inuse: number; oslimit: number }) { this.inuse.shift(); this.oslimit.shift(); @@ -45,7 +45,7 @@ function parseAndAppend(x: string) { memory.appendData(JSON.parse(x)); } -function pump(reader: ReadableStreamDefaultReader) { +function pump(reader: ReadableStreamDefaultReader): Promise<void> { return reader.read().then(({ done, value }) => { const str = textDecoder.decode(value, { stream: !done }); decoded += str; @@ -62,7 +62,6 @@ function pump(reader: ReadableStreamDefaultReader) { parseAndAppend(lastSplit); decoded = ''; - console.log('GET /memory streaming done'); fetched = false; return; @@ -102,7 +101,7 @@ function fetchDataWithFetch(apiConfig: ClashAPIConfig) { const { url, init } = getURLAndInit(apiConfig); fetch(url + endpoint, init).then( (response) => { - if (response.ok) { + if (response.ok && response.body) { const reader = response.body.getReader(); pump(reader); } else { @@ -110,10 +109,9 @@ function fetchDataWithFetch(apiConfig: ClashAPIConfig) { } }, (err) => { - console.log('fetch /memory error', err); fetched = false; - } + }, ); return memory; } diff --git a/src/api/proxies.ts b/src/api/proxies.ts index ca4bfd4..01a7c96 100644 --- a/src/api/proxies.ts +++ b/src/api/proxies.ts @@ -24,16 +24,20 @@ $ curl "http://127.0.0.1:8080/proxies/GLOBAL" -XPUT -d '{ "name": "Proxy" }' -i HTTP/1.1 204 No Content */ -export async function fetchProxies(config) { +export async function fetchProxies(config: ClashAPIConfig) { const { url, init } = getURLAndInit(config); const res = await fetch(url + endpoint, init); return await res.json(); } -export async function requestToSwitchProxy(apiConfig, name1, name2) { +export async function requestToSwitchProxy( + apiConfig: ClashAPIConfig, + name1: string, + name2: string, +) { const body = { name: name2 }; const { url, init } = getURLAndInit(apiConfig); - const fullURL = `${url}${endpoint}/${name1}`; + const fullURL = `${url}${endpoint}/${encodeURIComponent(name1)}`; return await fetch(fullURL, { ...init, method: 'PUT', @@ -42,11 +46,11 @@ export async function requestToSwitchProxy(apiConfig, name1, name2) { } export async function requestDelayForProxy( - apiConfig, - name, + apiConfig: ClashAPIConfig, + name: string, latencyTestUrl = DEFAULT_LATENCY_TEST_URL, timeout = 5000, - expected?: string + expected?: string, ) { const { url, init } = getURLAndInit(apiConfig); const qs = buildDelayQuery(latencyTestUrl, timeout, expected); @@ -55,11 +59,11 @@ export async function requestDelayForProxy( } export async function requestDelayForProxyGroup( - apiConfig, - name, + apiConfig: ClashAPIConfig, + name: string, latencyTestUrl = DEFAULT_LATENCY_TEST_URL, timeout = 5000, - expected?: string + expected?: string, ) { const { url, init } = getURLAndInit(apiConfig); const qs = buildDelayQuery(latencyTestUrl, timeout, expected); @@ -67,7 +71,7 @@ export async function requestDelayForProxyGroup( return await fetch(fullUrl, init); } -export async function fetchProviderProxies(config) { +export async function fetchProviderProxies(config: ClashAPIConfig) { const { url, init } = getURLAndInit(config); const res = await fetch(url + '/providers/proxies', init); if (res.status === 404) { @@ -76,18 +80,22 @@ export async function fetchProviderProxies(config) { return await res.json(); } -export async function updateProviderByName(config, name) { +export async function updateProviderByName(config: ClashAPIConfig, name: string) { const { url, init } = getURLAndInit(config); const options = { ...init, method: 'PUT' }; return await fetch(url + '/providers/proxies/' + encodeURIComponent(name), options); } -export async function healthcheckProviderByName(config, name, signal?: AbortSignal) { +export async function healthcheckProviderByName( + config: ClashAPIConfig, + name: string, + signal?: AbortSignal, +) { const { url, init } = getURLAndInit(config); const options = { ...init, method: 'GET', signal }; return await fetch( url + '/providers/proxies/' + encodeURIComponent(name) + '/healthcheck', - options + options, ); } @@ -97,15 +105,15 @@ export async function healthcheckProviderProxy( proxyName: string, latencyTestUrl = DEFAULT_LATENCY_TEST_URL, timeout = 5000, - expected?: string + expected?: string, ) { const { url, init } = getURLAndInit(config); const qs = buildDelayQuery(latencyTestUrl, timeout, expected); const options = { ...init, method: 'GET' }; return await fetch( `${url}/providers/proxies/${encodeURIComponent(providerName)}/${encodeURIComponent( - proxyName + proxyName, )}/healthcheck?${qs}`, - options + options, ); } diff --git a/src/api/rule-provider.ts b/src/api/rule-provider.ts index 43497c8..6ca45a6 100644 --- a/src/api/rule-provider.ts +++ b/src/api/rule-provider.ts @@ -42,7 +42,7 @@ export async function fetchRuleProviders(endpoint: string, apiConfig: ClashAPICo } } catch (err) { // log and ignore - + console.log('failed to GET /providers/rules', err); } return normalizeAPIResponse(data); @@ -57,14 +57,14 @@ export async function refreshRuleProviderByName({ }) { const { url, init } = getURLAndInit(apiConfig); try { - const res = await fetch(url + `/providers/rules/${name}`, { + const res = await fetch(url + `/providers/rules/${encodeURIComponent(name)}`, { method: 'PUT', ...init, }); return res.ok; } catch (err) { // log and ignore - + console.log('failed to PUT /providers/rules/:name', err); return false; } diff --git a/src/api/rules.ts b/src/api/rules.ts index 5537c31..9007041 100644 --- a/src/api/rules.ts +++ b/src/api/rules.ts @@ -5,27 +5,40 @@ import { ClashAPIConfig } from '~/types'; // const endpoint = '/rules'; -type RuleItem = RuleAPIItem & { id: number }; +export type RuleExtra = { + disabled: boolean; + hitCount: number; + hitAt: string; + missCount: number; + missAt: string; +}; + +export type RuleItem = RuleAPIItem & { id: number }; -type RuleAPIItem = { +export type RuleAPIItem = { + index?: number; type: string; payload: string; proxy: string; size: number; + extra?: RuleExtra; }; function normalizeAPIResponse(json: { rules: Array<RuleAPIItem> }): Array<RuleItem> { invariant( json.rules && json.rules.length >= 0, - 'there is no valid rules list in the rules API response' + 'there is no valid rules list in the rules API response', ); - // attach an id - return json.rules.map((r: RuleAPIItem, i: number) => ({ ...r, id: i })); + // attach an id, preferring the backend-provided index over array position + return json.rules.map((r: RuleAPIItem, i: number) => ({ + ...r, + id: typeof r.index === 'number' ? r.index : i, + })); } export async function fetchRules(endpoint: string, apiConfig: ClashAPIConfig) { - let json = { rules: [] }; + let json: { rules: Array<RuleAPIItem> } = { rules: [] }; try { const { url, init } = getURLAndInit(apiConfig); const res = await fetch(url + endpoint, init); @@ -34,8 +47,28 @@ export async function fetchRules(endpoint: string, apiConfig: ClashAPIConfig) { } } catch (err) { // log and ignore - + console.log('failed to fetch rules', err); } return normalizeAPIResponse(json); } + +export async function updateRuleDisabledStatus( + apiConfig: ClashAPIConfig, + updates: Record<number, boolean>, +) { + const { url, init } = getURLAndInit(apiConfig); + try { + const res = await fetch(url + '/rules/disable', { + method: 'PATCH', + ...init, + body: JSON.stringify(updates), + }); + return res.ok; + } catch (err) { + // log and ignore + + console.log('failed to PATCH /rules/disable', err); + return false; + } +} diff --git a/src/api/traffic.ts b/src/api/traffic.ts index a01940d..0a6bbd6 100644 --- a/src/api/traffic.ts +++ b/src/api/traffic.ts @@ -15,7 +15,7 @@ const traffic = { down: Array(Size).fill(null), size: Size, - subscribers: [], + subscribers: [] as Array<(o: { up: number; down: number }) => void>, appendData(o: { up: number; down: number }) { this.up.shift(); this.down.shift(); @@ -45,7 +45,7 @@ function parseAndAppend(x: string) { traffic.appendData(JSON.parse(x)); } -function pump(reader: ReadableStreamDefaultReader) { +function pump(reader: ReadableStreamDefaultReader): Promise<void> { return reader.read().then(({ done, value }) => { const str = textDecoder.decode(value, { stream: !done }); decoded += str; @@ -62,7 +62,6 @@ function pump(reader: ReadableStreamDefaultReader) { parseAndAppend(lastSplit); decoded = ''; - console.log('GET /traffic streaming done'); fetched = false; return; @@ -102,7 +101,7 @@ function fetchDataWithFetch(apiConfig: ClashAPIConfig) { const { url, init } = getURLAndInit(apiConfig); fetch(url + endpoint, init).then( (response) => { - if (response.ok) { + if (response.ok && response.body) { const reader = response.body.getReader(); pump(reader); } else { @@ -110,10 +109,9 @@ function fetchDataWithFetch(apiConfig: ClashAPIConfig) { } }, (err) => { - console.log('fetch /traffic error', err); fetched = false; - } + }, ); return traffic; } diff --git a/src/api/version.ts b/src/api/version.ts index aab2d22..c20aedd 100644 --- a/src/api/version.ts +++ b/src/api/version.ts @@ -9,7 +9,7 @@ type VersionData = { export async function fetchVersion( endpoint: string, - apiConfig: ClashAPIConfig + apiConfig: ClashAPIConfig, ): Promise<VersionData> { let json = {}; try { @@ -20,7 +20,7 @@ export async function fetchVersion( } } catch (err) { // log and ignore - + console.log(`failed to fetch ${endpoint}`, err); } return json; |
