1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
import { ClashAPIConfig } from 'src/types';
import { buildWebSocketURL, getURLAndInit } from '../misc/request-helper';
const endpoint = '/connections';
const fetched = false;
const subscribers = [];
// see also https://github.com/Dreamacro/clash/blob/dev/constant/metadata.go#L41
type UUID = string;
type ConnNetwork = 'tcp' | 'udp';
type ConnType = 'HTTP' | 'HTTP Connect' | 'Socks5' | 'Redir' | 'Unknown';
export type ConnectionItem = {
id: UUID;
metadata: {
network: ConnNetwork;
type: ConnType;
sourceIP: string;
destinationIP: string;
sourcePort: string;
destinationPort: string;
host: string;
};
upload: number;
download: number;
// e.g. "2019-11-30T22:48:13.416668+08:00",
start: string;
chains: string[];
// e.g. 'Match', 'DomainKeyword'
rule: string;
rulePayload?: string;
};
type ConnectionsData = {
downloadTotal: number;
uploadTotal: number;
connections: Array<ConnectionItem>;
};
function appendData(s: string) {
let o: ConnectionsData;
try {
o = JSON.parse(s);
} catch (err) {
// eslint-disable-next-line no-console
console.log('JSON.parse error', JSON.parse(s));
}
subscribers.forEach((f) => f(o));
}
type UnsubscribeFn = () => void;
let wsState: number;
export function fetchData(
apiConfig: ClashAPIConfig,
listener: unknown
): UnsubscribeFn | void {
if (fetched || wsState === 1) {
if (listener) return subscribe(listener);
}
wsState = 1;
const url = buildWebSocketURL(apiConfig, endpoint);
const ws = new WebSocket(url);
ws.addEventListener('error', () => (wsState = 3));
ws.addEventListener('message', (event) => appendData(event.data));
if (listener) return subscribe(listener);
}
function subscribe(listener: unknown): UnsubscribeFn {
subscribers.push(listener);
return function unsubscribe() {
const idx = subscribers.indexOf(listener);
subscribers.splice(idx, 1);
};
}
export async function closeAllConnections(apiConfig: ClashAPIConfig) {
const { url, init } = getURLAndInit(apiConfig);
return await fetch(url + endpoint, { ...init, method: 'DELETE' });
}
export async function fetchConns(apiConfig: ClashAPIConfig) {
const { url, init } = getURLAndInit(apiConfig);
return await fetch(url + endpoint, { ...init });
}
export async function closeConnById(apiConfig: ClashAPIConfig, id: string) {
const { url: baseURL, init } = getURLAndInit(apiConfig);
const url = `${baseURL}${endpoint}/${id}`;
return await fetch(url, { ...init, method: 'DELETE' });
}
|