+33
-17
src/App.tsx
+33
-17
src/App.tsx
···
3
import './App.css'
4
import '@mui/x-charts-vendor/d3-scale'
5
import Relay from './Relay'
6
-
import knownRelays from './knownRelays'
7
8
const INTERVAL = 1600;
9
const SERIES_LEN = 6;
10
const KEEPALIVE = 10 * 60 * 1000;
11
12
function App() {
13
-
const [relays, setRelays] = useState([]);
14
-
const [receiver, setReceiver] = useState(() => () => null);
15
-
const [keepalive, setKeepalive] = useState(() => () => null);
16
-
const [rateBars, setRateBars] = useState({ series: [] });
17
const [died, setDied] = useState(false);
18
19
useEffect(() => {
20
let lastChangeover = performance.now();
21
-
let currentCounts = {};
22
-
let series = [];
23
let raf = requestAnimationFrame(update);
24
let ttl = setTimeout(die, KEEPALIVE);
25
26
-
setReceiver(() => (url, type, event) => {
27
if (!currentCounts[url]) currentCounts[url] = 0;
28
currentCounts[url] += 1;
29
});
···
56
57
function update() {
58
let now = performance.now();
59
-
let dt = (now - lastChangeover) / 1000;
60
const relays = Object.keys(series.at(-1)?.counts || {}).toSorted();
61
62
setRateBars({
···
66
.concat(['now']),
67
label: 'bucket (seconds ago)',
68
}],
69
-
series: relays.map(r => ({
70
label: r,
71
data: series
72
.map(({ dt, counts }) => {
···
84
};
85
86
return () => {
87
-
setReceiver(() => () => null);
88
setKeepalive(() => () => null);
89
clearInterval(nextBlock);
90
cancelAnimationFrame(raf);
···
92
}, []);
93
94
95
-
function showRelay(url, show) {
96
setDied(false);
97
if (show) {
98
-
setRelays(rs => rs.includes(url) ? rs : [...rs, url]);
99
} else {
100
-
setRelays(rs => rs.includes(url) ? rs.filter(u => u !== url) : rs);
101
}
102
keepalive();
103
}
···
108
<p><em>warning: enabling many relay connections requires a lot of bandwidth</em></p>
109
110
<form style={{ display: 'block', textAlign: 'left' }}>
111
-
{knownRelays.map(({ url, desc }) => (
112
<p key={url} style={{margin: 0}}>
113
<label>
114
<input
···
125
126
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '2em', textAlign: 'left' }}>
127
{relays.map(url => {
128
-
const { desc } = knownRelays.find(e => e.url === url);
129
return (
130
<div key={url}>
131
<Relay
132
url={url}
133
desc={desc}
134
-
onRecieveEvent={(type, event) => receiver(url, type, event)}
135
/>
136
</div>
137
);
···
3
import './App.css'
4
import '@mui/x-charts-vendor/d3-scale'
5
import Relay from './Relay'
6
+
import knownRelays from './knownRelays.json'
7
8
const INTERVAL = 1600;
9
const SERIES_LEN = 6;
10
const KEEPALIVE = 10 * 60 * 1000;
11
12
+
interface Relay {
13
+
url: string,
14
+
desc: string,
15
+
};
16
+
17
+
interface Counts {
18
+
[k: string]: number,
19
+
};
20
+
21
+
interface CountBatch {
22
+
t: number,
23
+
dt: number,
24
+
counts: Counts,
25
+
};
26
+
27
+
const noopReceiver = (_url: string, _type: string, _event: any) => {};
28
+
29
function App() {
30
+
const [relays, setRelays] = useState([] as string[]);
31
+
const [receiver, setReceiver] = useState(() => noopReceiver);
32
+
const [keepalive, setKeepalive] = useState(() => () => {});
33
+
const [rateBars, setRateBars] = useState({ series: [] } as any);
34
const [died, setDied] = useState(false);
35
36
useEffect(() => {
37
let lastChangeover = performance.now();
38
+
let currentCounts: Counts = {};
39
+
let series: CountBatch[] = [];
40
let raf = requestAnimationFrame(update);
41
let ttl = setTimeout(die, KEEPALIVE);
42
43
+
setReceiver(() => (url: string, _type: string, _event: any) => {
44
if (!currentCounts[url]) currentCounts[url] = 0;
45
currentCounts[url] += 1;
46
});
···
73
74
function update() {
75
let now = performance.now();
76
const relays = Object.keys(series.at(-1)?.counts || {}).toSorted();
77
78
setRateBars({
···
82
.concat(['now']),
83
label: 'bucket (seconds ago)',
84
}],
85
+
series: relays.map((r: string) => ({
86
label: r,
87
data: series
88
.map(({ dt, counts }) => {
···
100
};
101
102
return () => {
103
+
setReceiver(() => noopReceiver);
104
setKeepalive(() => () => null);
105
clearInterval(nextBlock);
106
cancelAnimationFrame(raf);
···
108
}, []);
109
110
111
+
function showRelay(url: string, show: boolean) {
112
setDied(false);
113
if (show) {
114
+
setRelays((rs: string[]) => rs.includes(url) ? rs : [...rs, url]);
115
} else {
116
+
setRelays((rs: string[]) => rs.includes(url) ? rs.filter(u => u !== url) : rs);
117
}
118
keepalive();
119
}
···
124
<p><em>warning: enabling many relay connections requires a lot of bandwidth</em></p>
125
126
<form style={{ display: 'block', textAlign: 'left' }}>
127
+
{knownRelays.map(({ url, desc }: Relay) => (
128
<p key={url} style={{margin: 0}}>
129
<label>
130
<input
···
141
142
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '2em', textAlign: 'left' }}>
143
{relays.map(url => {
144
+
const { desc } = knownRelays.find((r: Relay) => r.url === url)!;
145
return (
146
<div key={url}>
147
<Relay
148
url={url}
149
desc={desc}
150
+
onRecieveEvent={(type: string, event: any) => receiver(url, type, event)}
151
/>
152
</div>
153
);
+12
-5
src/Relay.tsx
+12
-5
src/Relay.tsx
···
2
import { Firehose } from '@skyware/firehose';
3
import './Relay.css';
4
5
-
type firehoseState = 'connecting' | 'connected' | 'errored' | 'closed';
6
7
-
function Relay({ url, desc, onRecieveEvent }) {
8
-
const [state, setState] = useState('connecting');
9
const [commits, setCommits] = useState(0);
10
const [reconnects, setReconnects] = useState(0);
11
12
useEffect(() => {
13
-
const sendIt = (type, event) => {
14
onRecieveEvent(type, event);
15
setCommits(n => n + 1);
16
};
···
18
firehose.on('open', () => setState('connected'));
19
firehose.on('close', () => setState('closed'));
20
firehose.on('reconnect', () => setReconnects(n => n + 1));
21
-
firehose.on('error', e => console.error('oops', e) || setState('errored'));
22
firehose.on('websocketError', () => setState('errored'));
23
firehose.on('commit', (ev) => sendIt('commit', ev));
24
firehose.on('sync', (ev) => sendIt('sync', ev));
···
2
import { Firehose } from '@skyware/firehose';
3
import './Relay.css';
4
5
+
type HoseState = 'connecting' | 'connected' | 'errored' | 'closed';
6
7
+
function Relay({ url, desc, onRecieveEvent }: {
8
+
url: string,
9
+
desc: string,
10
+
onRecieveEvent: (type: string, event: any) => void,
11
+
}) {
12
+
const [state, setState] = useState('connecting' as HoseState);
13
const [commits, setCommits] = useState(0);
14
const [reconnects, setReconnects] = useState(0);
15
16
useEffect(() => {
17
+
const sendIt = (type: string, event: any) => {
18
onRecieveEvent(type, event);
19
setCommits(n => n + 1);
20
};
···
22
firehose.on('open', () => setState('connected'));
23
firehose.on('close', () => setState('closed'));
24
firehose.on('reconnect', () => setReconnects(n => n + 1));
25
+
firehose.on('error', e => {
26
+
console.error('oops', e);
27
+
setState('errored');
28
+
});
29
firehose.on('websocketError', () => setState('errored'));
30
firehose.on('commit', (ev) => sendIt('commit', ev));
31
firehose.on('sync', (ev) => sendIt('sync', ev));
+1
-1
tsconfig.app.json
+1
-1
tsconfig.app.json