summaryrefslogtreecommitdiff
path: root/src/api/connections.js
blob: e385266fe246a86bb6f6aab1a5a5d1491e7cac81 (plain)
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
import { getURLAndInit } from '../misc/request-helper';

const endpoint = '/connections';

let fetched = false;
let subscribers = [];

// see also https://github.com/Dreamacro/clash/blob/dev/constant/metadata.go#L41
type UUID = string;
type ConnectionItem = {
  id: UUID,
  metadata: {
    network: 'tcp' | 'udp',
    type: 'HTTP' | 'HTTP Connect' | 'Socks5' | 'Redir' | 'Unknown',
    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: Array<string>,
  // e.g. 'Match', 'DomainKeyword'
  rule: string
};
type ConnectionsData = {
  downloadTotal: number,
  uploadTotal: number,
  connections: Array<ConnectionItem>
};

function appendData(s) {
  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));
}

function getWsUrl(apiConfig) {
  const { hostname, port, secret } = apiConfig;
  let qs = '';
  if (typeof secret === 'string' && secret !== '') {
    qs += '?token=' + secret;
  }
  return `ws://${hostname}:${port}${endpoint}${qs}`;
}

let wsState;
function fetchData(apiConfig, listener) {
  if (fetched || wsState === 1) {
    if (listener) return subscribe(listener);
  }
  wsState = 1;
  const url = getWsUrl(apiConfig);
  const ws = new WebSocket(url);
  ws.addEventListener('error', function(_ev) {
    wsState = 3;
  });
  ws.addEventListener('message', function(event) {
    appendData(event.data);
  });
  if (listener) return subscribe(listener);
}

function subscribe(listener) {
  subscribers.push(listener);
  return function unsubscribe() {
    const idx = subscribers.indexOf(listener);
    subscribers.splice(idx, 1);
  };
}

async function closeAllConnections(apiConfig) {
  const { url, init } = getURLAndInit(apiConfig);
  return await fetch(url + endpoint, { ...init, method: 'DELETE' });
}

export { fetchData, closeAllConnections };