summaryrefslogtreecommitdiff
path: root/src/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/api')
-rw-r--r--src/api/configs.ts18
-rw-r--r--src/api/connections.ts12
-rw-r--r--src/api/logs.ts23
-rw-r--r--src/api/memory.ts10
-rw-r--r--src/api/proxies.ts40
-rw-r--r--src/api/rule-provider.ts6
-rw-r--r--src/api/rules.ts47
-rw-r--r--src/api/traffic.ts10
-rw-r--r--src/api/version.ts4
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;