interactive intro to open social
at-me.zzstoatzz.io
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}