interactive intro to open social at-me.zzstoatzz.io
at main 131 lines 4.5 kB view raw
1// ============================================================================ 2// FIREHOSE (Jetstream WebSocket) 3// ============================================================================ 4 5import { state } from './state.js'; 6import { 7 createFirehoseParticle, 8 initFirehoseCanvas, 9 animateFirehoseParticles, 10 cleanupFirehoseCanvas 11} from './particles.js'; 12 13function connectFirehose() { 14 if (!state.did || state.jetstreamWs) return; 15 16 const watchBtn = document.getElementById('watchLiveBtn'); 17 const watchLabel = watchBtn.querySelector('.watch-label'); 18 19 // Connect to Jetstream filtering by this DID 20 const wsUrl = `wss://jetstream2.us-east.bsky.network/subscribe?wantedDids=${encodeURIComponent(state.did)}`; 21 state.jetstreamWs = new WebSocket(wsUrl); 22 23 state.jetstreamWs.onopen = () => { 24 watchLabel.textContent = 'watching...'; 25 watchBtn.classList.add('active'); 26 }; 27 28 state.jetstreamWs.onmessage = (event) => { 29 try { 30 const data = JSON.parse(event.data); 31 if (data.kind === 'commit' && data.commit) { 32 const commit = data.commit; 33 const collection = commit.collection; 34 const operation = commit.operation; 35 36 // Get namespace from collection 37 const parts = collection.split('.'); 38 const namespace = parts.length >= 2 ? `${parts[0]}.${parts[1]}` : collection; 39 40 const eventData = { 41 action: operation, 42 collection: collection, 43 namespace: namespace, 44 rkey: commit.rkey 45 }; 46 47 // Create particle animation 48 createFirehoseParticle(eventData); 49 50 // Show toast notification 51 showFirehoseToast(eventData); 52 } 53 } catch (e) { 54 console.error('Error processing Jetstream message:', e); 55 } 56 }; 57 58 state.jetstreamWs.onerror = (error) => { 59 console.error('Jetstream error:', error); 60 watchLabel.textContent = 'connection error'; 61 }; 62 63 state.jetstreamWs.onclose = () => { 64 if (state.isWatchingLive) { 65 watchLabel.textContent = 'reconnecting...'; 66 setTimeout(() => { 67 state.jetstreamWs = null; 68 if (state.isWatchingLive) connectFirehose(); 69 }, 3000); 70 } 71 }; 72} 73 74function disconnectFirehose() { 75 if (state.jetstreamWs) { 76 state.jetstreamWs.close(); 77 state.jetstreamWs = null; 78 } 79} 80 81function showFirehoseToast(event) { 82 const toast = document.getElementById('firehoseToast'); 83 const actionEl = toast.querySelector('.firehose-toast-action'); 84 const collectionEl = toast.querySelector('.firehose-toast-collection'); 85 const linkEl = document.getElementById('firehoseToastLink'); 86 87 const actionText = { 88 'create': 'created', 89 'update': 'updated', 90 'delete': 'deleted' 91 }[event.action] || event.action; 92 93 actionEl.textContent = `${actionText} record`; 94 collectionEl.innerHTML = `<code style="background: var(--bg); padding: 0.1rem 0.3rem; border-radius: 2px; font-size: 0.6rem;">${event.collection}</code>`; 95 96 if (event.action === 'delete') { 97 linkEl.style.display = 'none'; 98 } else { 99 linkEl.style.display = 'inline-block'; 100 if (state.globalPds && event.rkey) { 101 linkEl.href = `${state.globalPds}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(state.did)}&collection=${encodeURIComponent(event.collection)}&rkey=${encodeURIComponent(event.rkey)}`; 102 } 103 } 104 105 toast.classList.add('visible'); 106 setTimeout(() => { 107 toast.classList.remove('visible'); 108 }, 4000); 109} 110 111export function initFirehoseUI() { 112 // Watch live button handler 113 document.getElementById('watchLiveBtn').addEventListener('click', () => { 114 const watchBtn = document.getElementById('watchLiveBtn'); 115 const watchLabel = watchBtn.querySelector('.watch-label'); 116 117 if (state.isWatchingLive) { 118 state.isWatchingLive = false; 119 watchLabel.textContent = 'watch live'; 120 watchBtn.classList.remove('active'); 121 disconnectFirehose(); 122 cleanupFirehoseCanvas(); 123 } else { 124 state.isWatchingLive = true; 125 watchLabel.textContent = 'connecting...'; 126 initFirehoseCanvas(); 127 animateFirehoseParticles(); 128 connectFirehose(); 129 } 130 }); 131}