Relay firehose browser tools: https://compare.hose.cam
at main 196 lines 6.8 kB view raw
1import { useState, useCallback, useEffect } from 'react'; 2import { MiniDoc, Collection, Did } from './types'; 3import { listRecords, resolveMiniDoc, getRepoStatus } from './microcosm'; 4import LilUser from './LilUser'; 5import knownRelays from '../knownRelays.json'; 6 7const INCLUDED_RELAYS = [ 8 // most relays don't send cors headers rn 9 'wss://relay.xero.systems', 10 'wss://relay1.us-east.bsky.network', 11 'wss://relay1.us-west.bsky.network', 12 'wss://relay.fire.hose.cam', 13 'wss://relay3.fr.hose.cam', 14 15 // these relays are ineligible for other reasons: 16 // 'wss://atproto.africa', // rsky-relay does not implement getRepoStatus (and doesn't have this bug) 17 // 'wss://bsky.network', // old bgs codes does not have getRepoStatus 18]; 19 20 21async function checkRelayStatuses(repo: Did) { 22 const deactivateds = []; 23 const missings = []; 24 const fails = []; 25 for (const url of INCLUDED_RELAYS) { 26 const u = new URL(url); 27 u.protocol = u.protocol.replace('ws', 'http'); 28 let repoStatus; 29 try { 30 repoStatus = await getRepoStatus(u, repo); 31 } catch (e) {} 32 if (repoStatus === 'notfound') { 33 missings.push(u.hostname); 34 continue; 35 } 36 if (!repoStatus) { 37 fails.push(u.hostname); 38 continue; 39 } 40 if (!repoStatus.active) { 41 console.log('rs', repoStatus); 42 deactivateds.push(u.hostname); 43 } 44 } 45 return { deactivateds, missings, fails }; 46} 47 48function FailSummary({ oof, children }) { 49 const badRelays = {}; 50 const { deactivateds, missings, fails } = oof; 51 52 deactivateds.forEach(u => badRelays[u] = 'deactivated'); 53 missings.forEach(u => badRelays[u] = 'not crawling'); 54 fails.forEach(u => badRelays[u] = 'check failed'); 55 56 return ( 57 <p style={{ fontSize: '0.8em', textAlign: 'right', margin: '0' }}> 58 {Object.keys(badRelays).map(k => (<> 59 <code>{k}</code>: <span style={{ color: "#f64" }}>{badRelays[k]}</span><br /> 60 </>))} 61 <strong>pds:</strong> <code>{oof.doc.pds.hostname}</code> (<span style={{ color: "#7f6"}}>active</span>)<br/> 62 </p> 63 ) 64} 65 66function Results({ actives }) { 67 const hasFails = []; 68 let oks = 0; 69 actives.forEach(a => { 70 if (a.deactivateds.length > 0 || a.missings.length > 0 || a.fails.length > 0) { 71 hasFails.push(a); 72 } else { 73 oks += 1; 74 } 75 }) 76 return ( 77 <> 78 <p>{oks} account{oks !== 1 && 's'} on alternative PDSs checked out ok.</p> 79 {hasFails.length > 0 && 80 <> 81 <h3>{hasFails.length} account{hasFails.length !== 1 && 's'} found with relay problems</h3> 82 {hasFails.map(f => ( 83 <div key={f.doc.did.val} style={{ margin: "0.5rem 0" }}> 84 <LilUser doc={f.doc}> 85 <FailSummary oof={f} /> 86 </LilUser> 87 </div> 88 ))} 89 </> 90 } 91 </> 92 ); 93} 94 95function CheckFollowers({ doc }: { 96 doc: MiniDoc, 97}) { 98 const [seenDids, setSeenDids] = useState({}); 99 const [actives, setActives] = useState([]); 100 const [actuallyDeactivated, setActuallyDeactivated] = useState([]); 101 const [mushrooms, setMushrooms] = useState([]); 102 const [failures, setFailures] = useState([]); 103 104 const checkFollowing = useCallback(async subject => { 105 if (seenDids[subject]) return; 106 else setSeenDids(s => ({ ...s, [subject]: true })); 107 108 let doc; 109 try { 110 doc = await resolveMiniDoc(subject); 111 } catch {} 112 if (!doc) { 113 setFailures(fs => [...fs, { subject, reason: 'resolution' }]); 114 return; 115 } 116 if (doc.pds.hostname.endsWith(".host.bsky.network")) { 117 setMushrooms(ms => [...ms, doc]); 118 return; 119 } 120 let repoStatus; 121 try { 122 repoStatus = await getRepoStatus(doc.pds, doc.did); 123 } catch (e) {} 124 if (repoStatus === 'notfound') { 125 setFailures(fs => [...fs, { subject, reason: 'notfound' } ]); 126 return; 127 } 128 if (!repoStatus) { 129 setFailures(fs => [...fs, { subject, reason: 'pds getRepoStatus' } ]); 130 return; 131 } 132 if (!repoStatus.active) { 133 setActuallyDeactivated(ads => [...ads, doc]); 134 return; 135 } 136 const { deactivateds, missings, fails } = await checkRelayStatuses(doc.did); 137 setActives(as => [...as, { doc, deactivateds, missings, fails }]); 138 }, []); 139 140 useEffect(() => { 141 let cancel = false; 142 (async() => { 143 // check ourselves first 144 checkFollowing(doc.did.val); 145 146 const gen = listRecords(doc.pds, doc.did, new Collection('app.bsky.graph.follow')); 147 148 for await (const record of gen) { 149 if (cancel) break; 150 checkFollowing(record.subject); 151 } 152 })(); 153 return () => cancel = true; 154 }, [doc.did.val, doc.pds]); 155 156 return ( 157 <div style={{ marginBottom: "4em" }}> 158 <h2>Checking following ({Object.keys(seenDids).length})&hellip;</h2> 159 <p>Of your follows, {failures.length} failed resolution, {mushrooms.length} are on bsky mushroom PDSs, and {actuallyDeactivated.length} are actually deactivated.</p> 160 <Results actives={actives} /> 161 162 <div style={{ textAlign: "left" }}> 163 <h3 style={{ margin: "3em 0 0" }}>What these results mean</h3> 164 165 <h4 style={{ marginBottom: "0", color: "#f64" }}>Deactivated</h4> 166 <p>The relay has become desynchronized with this account, incorrectly marking it as not <code>active</code>. All commits from this account will be blocked by the relay; none will be broadcast to relay consumers.</p> 167 168 <h4 style={{ marginBottom: "0", color: "#f64" }}>Not crawling</h4> 169 <p>The relay doesn't know about this account—perhaps it as never crawled its PDS. No content from this account will be discovered by the relay, so relay consumers won't see it.</p> 170 171 <h4 style={{ marginBottom: "0", color: "#f64" }}>Check failed</h4> 172 <p>This account seems active, but something went wrong when checking its status with the relay. It might be fine!</p> 173 174 <h3 style={{ margin: "3em 0 0" }}>Which relays are checked?</h3> 175 176 <ul> 177 {INCLUDED_RELAYS.map(u => ( 178 <li key={u}><code>{new URL(u).hostname}</code></li> 179 ))} 180 </ul> 181 182 <h4 style={{ marginBottom: "0" }}>Excluded relays</h4> 183 184 <ul> 185 <li><code>atproto.africa</code> does not store repo status, so it can't get desynchronized, and won't drop commits.</li> 186 <li><code>bsky.network</code>, running the old BGS code, does not implement <code>com.atproto.sync.getRepoStatus</code>.</li> 187 <li>All other known relays do not allow CORS XRPC requests, so we can't check from your browser.</li> 188 </ul> 189 190 <p>Accounts on Bluesky's mushroom PDSs are not checked because accounts seem to mainly desynchronize when migrating PDSs. Since accounts can now be migrated into the mushrooms, perhaps they should be checked too?</p> 191 </div> 192 </div> 193 ); 194} 195 196export default CheckFollowers;