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

Configure Feed

Select the types of activity you want to include in your feed.

connect to relays

phil 3727c4d5 62a1868f

+183 -29
+74
package-lock.json
··· 8 8 "name": "firehose-diff", 9 9 "version": "0.0.0", 10 10 "dependencies": { 11 + "@skyware/firehose": "^0.5.1", 11 12 "react": "^19.1.0", 12 13 "react-dom": "^19.1.0" 13 14 }, ··· 38 39 "engines": { 39 40 "node": ">=6.0.0" 40 41 } 42 + }, 43 + "node_modules/@atcute/car": { 44 + "version": "3.0.4", 45 + "resolved": "https://registry.npmjs.org/@atcute/car/-/car-3.0.4.tgz", 46 + "integrity": "sha512-T38qVn09cgaMCWka3tLWbuc/nPI5Sfjac/PwJIei2GnMldKSrZrAjeCvJAg8TWvsNab1kRZEy5uYqcBUJtzsWA==", 47 + "license": "MIT", 48 + "dependencies": { 49 + "@atcute/cbor": "^2.2.3", 50 + "@atcute/cid": "^2.2.2", 51 + "@atcute/varint": "^1.0.2" 52 + } 53 + }, 54 + "node_modules/@atcute/cbor": { 55 + "version": "2.2.3", 56 + "resolved": "https://registry.npmjs.org/@atcute/cbor/-/cbor-2.2.3.tgz", 57 + "integrity": "sha512-yvzaPI6ChNlqYN41qY8cVGCubgb6N/Y2kpNQnjC5waAZ4Xy2WH7ohuyiv0BQDvgzPT4ww1+9G+zBFxcfM2PH/Q==", 58 + "license": "MIT", 59 + "dependencies": { 60 + "@atcute/cid": "^2.2.2", 61 + "@atcute/multibase": "^1.1.3", 62 + "@atcute/uint8array": "^1.0.1" 63 + } 64 + }, 65 + "node_modules/@atcute/cid": { 66 + "version": "2.2.2", 67 + "resolved": "https://registry.npmjs.org/@atcute/cid/-/cid-2.2.2.tgz", 68 + "integrity": "sha512-deAGMqLAyplt7eIukhkjlsGubvrcMrtXkDKlUYZDo4WUdL7hSjBywtPXf6SbMK+Mjvst7l2+83OqTcY5AuuxtA==", 69 + "license": "MIT", 70 + "dependencies": { 71 + "@atcute/multibase": "^1.1.3", 72 + "@atcute/uint8array": "^1.0.1" 73 + } 74 + }, 75 + "node_modules/@atcute/multibase": { 76 + "version": "1.1.3", 77 + "resolved": "https://registry.npmjs.org/@atcute/multibase/-/multibase-1.1.3.tgz", 78 + "integrity": "sha512-vQQO0tDuQPguBvHdgV3ryn7R8U6beQ50KA/juYm+dCeT/3hOK2stMbX+IaW8JEuwkT5lJsU8wDIOicQT4mB7Ag==", 79 + "license": "MIT", 80 + "dependencies": { 81 + "@atcute/uint8array": "^1.0.1" 82 + } 83 + }, 84 + "node_modules/@atcute/uint8array": { 85 + "version": "1.0.1", 86 + "resolved": "https://registry.npmjs.org/@atcute/uint8array/-/uint8array-1.0.1.tgz", 87 + "integrity": "sha512-AAnlFKyfDRgb9GNZJbhQ6OuMhbmNPirQyapb8KnmcEhxQZ3+tt+4NcwqekEegY4MpNqSTYeeTdyxq0wGZv1JHg==", 88 + "license": "MIT" 89 + }, 90 + "node_modules/@atcute/varint": { 91 + "version": "1.0.2", 92 + "resolved": "https://registry.npmjs.org/@atcute/varint/-/varint-1.0.2.tgz", 93 + "integrity": "sha512-0O31hePzzr4O3NGWHUKKOyta6CGSL+AtN8iir8grGxu9jXyI7DBARlw6PbgKA6uTAvsXdpmRmF8MX+p0TsLnNg==", 94 + "license": "MIT" 41 95 }, 42 96 "node_modules/@babel/code-frame": { 43 97 "version": "7.27.1", ··· 1355 1409 "os": [ 1356 1410 "win32" 1357 1411 ] 1412 + }, 1413 + "node_modules/@skyware/firehose": { 1414 + "version": "0.5.1", 1415 + "resolved": "https://registry.npmjs.org/@skyware/firehose/-/firehose-0.5.1.tgz", 1416 + "integrity": "sha512-7fcTQtXCbD2t5ls/tvMeYuI8dHTiUuZvrMdD1Mq+ZyMPpjcQdv2OsmflTrldt5XY+kOgoaThImi7QEo07B3o2Q==", 1417 + "license": "MPL-2.0", 1418 + "dependencies": { 1419 + "@atcute/car": "^3.0.3", 1420 + "@atcute/cbor": "^2.2.2", 1421 + "nanoevents": "^9.1.0" 1422 + } 1358 1423 }, 1359 1424 "node_modules/@types/babel__core": { 1360 1425 "version": "7.20.5", ··· 3171 3236 "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 3172 3237 "dev": true, 3173 3238 "license": "MIT" 3239 + }, 3240 + "node_modules/nanoevents": { 3241 + "version": "9.1.0", 3242 + "resolved": "https://registry.npmjs.org/nanoevents/-/nanoevents-9.1.0.tgz", 3243 + "integrity": "sha512-Jd0fILWG44a9luj8v5kED4WI+zfkkgwKyRQKItTtlPfEsh7Lznfi1kr8/iZ+XAIss4Qq5GqRB0qtWbaz9ceO/A==", 3244 + "license": "MIT", 3245 + "engines": { 3246 + "node": "^18.0.0 || >=20.0.0" 3247 + } 3174 3248 }, 3175 3249 "node_modules/nanoid": { 3176 3250 "version": "3.3.11",
+1
package.json
··· 10 10 "preview": "vite preview" 11 11 }, 12 12 "dependencies": { 13 + "@skyware/firehose": "^0.5.1", 13 14 "react": "^19.1.0", 14 15 "react-dom": "^19.1.0" 15 16 },
-1
src/App.css
··· 1 1 #root { 2 - max-width: 1280px; 3 2 margin: 0 auto; 4 3 padding: 2rem; 5 4 text-align: center;
+35 -22
src/App.tsx
··· 1 1 import { useState } from 'react' 2 - import reactLogo from './assets/react.svg' 3 - import viteLogo from '/vite.svg' 4 2 import './App.css' 3 + import Relay from './Relay' 4 + import knownRelays from './knownRelays' 5 5 6 6 function App() { 7 - const [count, setCount] = useState(0) 7 + const [relays, setRelays] = useState([]); 8 + 9 + const recieveEvent = () => null; 8 10 9 11 return ( 10 12 <> 11 - <div> 12 - <a href="https://vite.dev" target="_blank"> 13 - <img src={viteLogo} className="logo" alt="Vite logo" /> 14 - </a> 15 - <a href="https://react.dev" target="_blank"> 16 - <img src={reactLogo} className="logo react" alt="React logo" /> 17 - </a> 18 - </div> 19 - <h1>Vite + React</h1> 20 - <div className="card"> 21 - <button onClick={() => setCount((count) => count + 1)}> 22 - count is {count} 23 - </button> 24 - <p> 25 - Edit <code>src/App.tsx</code> and save to test HMR 26 - </p> 13 + <h1>compare hoses</h1> 14 + <p><em>warning: enabling many relay connections requires a lot of bandwidth</em></p> 15 + 16 + <form style={{ display: 'block', textAlign: 'left' }}> 17 + {knownRelays.map(({ url, desc }) => ( 18 + <p key={url} style={{margin: 0}}> 19 + <label> 20 + <input 21 + type="checkbox" 22 + onInput={e => e.target.checked 23 + ? relays.includes(url) || setRelays([...relays, url]) 24 + : setRelays(relays.filter(u => u !== url)) 25 + } 26 + /> 27 + { ` ${desc} ` } 28 + (<code>{ url.slice('wss://'.length) }</code>) 29 + </label> 30 + </p> 31 + ))} 32 + </form> 33 + 34 + <div style={{ display: 'flex', flexWrap: 'wrap', gap: '2em', textAlign: 'left' }}> 35 + {relays.map(url => { 36 + const { desc } = knownRelays.find(e => e.url === url); 37 + return ( 38 + <div key={url}> 39 + <Relay url={url} desc={desc} onRecievedEvent={(type, event) => recieveEvent(url, type, event)} /> 40 + </div> 41 + ); 42 + })} 27 43 </div> 28 - <p className="read-the-docs"> 29 - Click on the Vite and React logos to learn more 30 - </p> 31 44 </> 32 45 ) 33 46 }
+10
src/Relay.css
··· 1 + .relay { 2 + } 3 + 4 + .relay h2 { 5 + margin-bottom: 0; 6 + } 7 + 8 + .relay p { 9 + margin: 0; 10 + }
+44
src/Relay.tsx
··· 1 + import { useEffect, useState } from 'react'; 2 + import { Firehose } from '@skyware/firehose'; 3 + import './Relay.css'; 4 + 5 + type firehoseState = 'connecting' | 'connected' | 'errored' | 'closed'; 6 + 7 + function Relay({ url, desc, onRecievedEvent }) { 8 + const [state, setState] = useState('connecting'); 9 + const [commits, setCommits] = useState(0); 10 + 11 + useEffect(() => { 12 + const sendIt = (type, event) => { 13 + onRecievedEvent(type, event); 14 + setCommits(n => n + 1); 15 + }; 16 + const firehose = new Firehose({ relay: url }); 17 + firehose.on('open', () => setState('connected')); 18 + firehose.on('close', () => setState('closed')); 19 + firehose.on('reconnect', (...args) => console.info('reconnect', ...args)); 20 + firehose.on('error', () => setState('errored')); 21 + firehose.on('websocketError', () => setState('errored')); 22 + firehose.on('commit', (ev) => sendIt('commit', ev)); 23 + firehose.on('sync', (ev) => sendIt('sync', ev)); 24 + firehose.on('account', (ev) => sendIt('account', ev)); 25 + firehose.on('identity', (ev) => sendIt('identity', ev)); 26 + firehose.on('info', (...args) => console.info('info event', ...args)); 27 + firehose.on('unknown', e => console.warn(`unknown event from ${url}`, e)); 28 + firehose.start(); 29 + 30 + return () => { 31 + firehose.close(); 32 + }; 33 + }, [url]); 34 + 35 + return ( 36 + <div className="relay"> 37 + <h2>{ desc }</h2> 38 + <p><code>{ url }</code></p> 39 + <p>[<code>{ state }</code>] (<code>{ commits.toLocaleString() }</code> events)</p> 40 + </div> 41 + ); 42 + } 43 + 44 + export default Relay;
+18
src/knownRelays.json
··· 1 + [ 2 + { 3 + "url": "wss://atproto.africa", 4 + "desc": "Blacksky" 5 + }, 6 + { 7 + "url": "wss://bsky.network", 8 + "desc": "Bluesky Rainbow" 9 + }, 10 + { 11 + "url": "wss://relay2.fire.hose.cam", 12 + "desc": "microcosm Montreal" 13 + }, 14 + { 15 + "url": "wss://relay3.fr.hose.cam", 16 + "desc": "microcosm France" 17 + } 18 + ]
+1 -6
src/main.tsx
··· 1 - import { StrictMode } from 'react' 2 1 import { createRoot } from 'react-dom/client' 3 2 import './index.css' 4 3 import App from './App.tsx' 5 4 6 - createRoot(document.getElementById('root')!).render( 7 - <StrictMode> 8 - <App /> 9 - </StrictMode>, 10 - ) 5 + createRoot(document.getElementById('root')!).render(<App />)