Relay firehose browser tools: https://compare.hose.cam

fix types

at least one bug probably

Changed files
+46 -23
src
+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
··· 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
··· 3 "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 "target": "ES2020", 5 "useDefineForClassFields": true, 6 - "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 "module": "ESNext", 8 "skipLibCheck": true, 9
··· 3 "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 "target": "ES2020", 5 "useDefineForClassFields": true, 6 + "lib": ["ES2023", "DOM", "DOM.Iterable"], 7 "module": "ESNext", 8 "skipLibCheck": true, 9