a proof of concept realtime collaborative todo list
0

Configure Feed

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

atodo / subscribe.js
2.7 kB 92 lines
1// Subscribe to multiple Jetstream instances at once, filtered server-side to 2// the DIDs + collections we care about. Each commit comes through as JSON 3// with the record inline, so we don't need to re-fetch. 4// 5// A single instance can silently drop or lag commits for some repos, so we 6// fan out to several and dedupe: each commit arrives once per healthy 7// instance, but downstream handlers only need to see it once. 8 9const JETSTREAMS = [ 10 "wss://jetstream1.us-east.bsky.network/subscribe", 11 "wss://jetstream2.us-east.bsky.network/subscribe", 12 "wss://jetstream1.us-west.bsky.network/subscribe", 13 "wss://jetstream2.fr.hose.cam/subscribe", 14]; 15 16export function subscribe({ dids, collections, onCommit }) { 17 let closed = false; 18 const sockets = new Set(); 19 20 const seen = new Set(); 21 const seenOrder = []; 22 const once = (key) => { 23 if (seen.has(key)) return false; 24 seen.add(key); 25 seenOrder.push(key); 26 if (seenOrder.length > 1000) seen.delete(seenOrder.shift()); 27 return true; 28 }; 29 30 const connect = (endpoint) => { 31 if (closed) return; 32 let retry = 0; 33 let cursor = null; 34 35 const open = () => { 36 if (closed) return; 37 const url = new URL(endpoint); 38 for (const d of dids) url.searchParams.append("wantedDids", d); 39 for (const c of collections) url.searchParams.append("wantedCollections", c); 40 // Resume from the last seen event on reconnect, otherwise Jetstream 41 // starts a fresh live tail and commits during the downtime are lost 42 // for good. 43 if (cursor) url.searchParams.set("cursor", String(cursor)); 44 45 const ws = new WebSocket(url); 46 sockets.add(ws); 47 ws.onopen = () => (retry = 0); 48 ws.onmessage = (e) => { 49 let msg; 50 try { 51 msg = JSON.parse(e.data); 52 } catch { 53 return; 54 } 55 if (msg.time_us) cursor = msg.time_us; 56 if (msg.kind !== "commit" || !msg.commit) return; 57 58 const c = msg.commit; 59 if (!once(`${msg.did}/${c.collection}/${c.rkey}/${c.cid ?? c.operation}`)) return; 60 61 onCommit({ 62 did: msg.did, 63 collection: c.collection, 64 rkey: c.rkey, 65 cid: c.cid ?? null, 66 action: c.operation, 67 record: c.record ?? null, 68 }); 69 }; 70 ws.onclose = () => { 71 sockets.delete(ws); 72 if (closed) return; 73 retry++; 74 setTimeout(open, Math.min(1000 * 2 ** retry, 30000)); 75 }; 76 ws.onerror = () => {}; 77 }; 78 79 open(); 80 }; 81 82 for (const endpoint of JETSTREAMS) connect(endpoint); 83 84 return () => { 85 closed = true; 86 for (const ws of sockets) { 87 ws.onclose = null; 88 ws.close(); 89 } 90 sockets.clear(); 91 }; 92}