1use std::collections::{HashMap, HashSet};
10use std::sync::{LazyLock, Mutex as StdMutex};
11use std::sync::Arc;
12use std::time::Duration;
13
14use nostr_sdk::prelude::*;
15use tokio::sync::Mutex;
16
17use crate::community::{derive, inbound, roster, service, Channel, CommunityId, Epoch};
18use crate::community::transport::LiveTransport;
19use crate::event_handler::InboundEventHandler;
20use crate::state::SessionGuard;
21use crate::stored_event::event_kind;
22
23const CHANNEL_FOLLOW_MAX_ATTEMPTS: usize = 5;
26const CHANNEL_FOLLOW_BACKOFF_MS: u64 = 700;
27
28static COMMUNITY_SUB_ID: LazyLock<Mutex<Option<SubscriptionId>>> = LazyLock::new(|| Mutex::new(None));
31
32static COMMUNITY_SUB_SET: LazyLock<Mutex<Vec<String>>> = LazyLock::new(|| Mutex::new(Vec::new()));
38
39static COMMUNITY_POOLWIDE_SUB_ID: LazyLock<Mutex<Option<SubscriptionId>>> = LazyLock::new(|| Mutex::new(None));
45
46static COMMUNITY_ROUTES: LazyLock<Mutex<HashMap<String, Channel>>> =
49 LazyLock::new(|| Mutex::new(HashMap::new()));
50
51static CONTROL_ROUTES: LazyLock<Mutex<HashMap<String, String>>> =
54 LazyLock::new(|| Mutex::new(HashMap::new()));
55
56static REFRESH_CONTROL_INFLIGHT: LazyLock<StdMutex<HashSet<String>>> =
58 LazyLock::new(|| StdMutex::new(HashSet::new()));
59
60pub async fn subscription_id() -> Option<SubscriptionId> {
63 COMMUNITY_SUB_ID.lock().await.clone()
64}
65
66pub async fn poolwide_subscription_id() -> Option<SubscriptionId> {
71 COMMUNITY_POOLWIDE_SUB_ID.lock().await.clone()
72}
73
74pub async fn clear() {
77 *COMMUNITY_SUB_ID.lock().await = None;
78 *COMMUNITY_POOLWIDE_SUB_ID.lock().await = None;
79 COMMUNITY_SUB_SET.lock().await.clear();
80 COMMUNITY_ROUTES.lock().await.clear();
81 CONTROL_ROUTES.lock().await.clear();
82 REFRESH_CONTROL_INFLIGHT.lock().unwrap_or_else(|e| e.into_inner()).clear();
83}
84
85pub async fn rebuild_routes() -> (Vec<String>, HashSet<String>) {
90 let mut routes: HashMap<String, Channel> = HashMap::new();
91 let mut control_routes: HashMap<String, String> = HashMap::new();
92 let mut pseudonyms: Vec<String> = Vec::new();
93 let mut relays: HashSet<String> = HashSet::new();
94
95 if let Ok(ids) = crate::db::community::list_community_ids() {
96 for id in ids {
97 if let Ok(Some(community)) = crate::db::community::load_community(&id) {
98 for r in &community.relays {
99 relays.insert(r.clone());
100 }
101 for ch in &community.channels {
102 for (epoch, key) in ch.read_epoch_keys() {
106 let pseudonym = derive::channel_pseudonym(&key, &ch.id, epoch).to_hex();
107 pseudonyms.push(pseudonym.clone());
108 routes.insert(pseudonym, ch.clone());
109 }
110 let next_chan = derive::rekey_pseudonym(
113 &community.server_root_key, &ch.id, Epoch(ch.epoch.0 + 1),
114 ).to_hex();
115 pseudonyms.push(next_chan.clone());
116 control_routes.insert(next_chan, community.id.to_hex());
117 }
118 let ctrl = roster::control_pseudonym(
120 &community.server_root_key, &community.id, community.server_root_epoch,
121 );
122 pseudonyms.push(ctrl.clone());
123 control_routes.insert(ctrl, community.id.to_hex());
124 let next_base = derive::base_rekey_pseudonym(
126 &community.server_root_key, &community.id,
127 Epoch(community.server_root_epoch.0 + 1),
128 ).to_hex();
129 pseudonyms.push(next_base.clone());
130 control_routes.insert(next_base, community.id.to_hex());
131 }
132 }
133 }
134
135 *COMMUNITY_ROUTES.lock().await = routes;
136 *CONTROL_ROUTES.lock().await = control_routes;
137 (pseudonyms, relays)
138}
139
140pub async fn refresh_subscription(client: &Client) {
144 let (pseudonyms, relays) = rebuild_routes().await;
145
146 let mut new_set = pseudonyms.clone();
150 new_set.sort();
151
152 let mut sub_guard = COMMUNITY_SUB_ID.lock().await;
153 let mut set_guard = COMMUNITY_SUB_SET.lock().await;
154
155 if sub_guard.is_some() && *set_guard == new_set {
159 return;
160 }
161
162 if let Some(old_id) = sub_guard.take() {
163 client.unsubscribe(&old_id).await;
164 }
165 *set_guard = new_set;
166
167 if pseudonyms.is_empty() {
168 if let Some(old_pw) = COMMUNITY_POOLWIDE_SUB_ID.lock().await.take() {
169 client.unsubscribe(&old_pw).await;
170 }
171 return;
172 }
173
174 for r in &relays {
177 let _ = client.pool().add_relay(r.as_str(), crate::community_relay_options()).await;
178 }
179 client.connect().await;
180
181 {
188 let wanted: Vec<RelayUrl> = relays.iter().filter_map(|r| RelayUrl::parse(r).ok()).collect();
189 for _ in 0..24 {
190 let pool = client.pool().all_relays().await;
191 let any_live = wanted.iter().any(|u| {
192 pool.get(u).map(|r| r.status() == RelayStatus::Connected).unwrap_or(false)
193 });
194 if any_live {
195 break;
196 }
197 tokio::time::sleep(Duration::from_millis(250)).await;
198 }
199 }
200
201 let filter = Filter::new()
202 .kinds([
203 Kind::Custom(event_kind::COMMUNITY_MESSAGE),
204 Kind::Custom(event_kind::COMMUNITY_REACTION),
205 Kind::Custom(event_kind::COMMUNITY_EDIT),
206 Kind::Custom(event_kind::COMMUNITY_DELETE),
207 Kind::Custom(event_kind::COMMUNITY_PRESENCE),
208 Kind::Custom(event_kind::COMMUNITY_KICK),
209 Kind::Custom(event_kind::COMMUNITY_TYPING),
210 Kind::Custom(event_kind::COMMUNITY_WEBXDC),
211 Kind::Custom(event_kind::COMMUNITY_CONTROL),
212 Kind::Custom(event_kind::COMMUNITY_REKEY),
213 ])
214 .custom_tags(SingleLetterTag::lowercase(Alphabet::Z), pseudonyms)
215 .limit(0);
216
217 {
219 let mut pw = COMMUNITY_POOLWIDE_SUB_ID.lock().await;
220 if let Some(old) = pw.take() {
221 client.unsubscribe(&old).await;
222 }
223 if let Ok(out) = client.subscribe(filter.clone(), None).await {
224 *pw = Some(out.val);
225 }
226 }
227 if let Ok(output) = client.subscribe_to(relays.iter().cloned(), filter, None).await {
229 *sub_guard = Some(output.val);
230 }
231}
232
233pub async fn dispatch_event(
238 session: &SessionGuard,
239 event: Event,
240 handler: Arc<dyn InboundEventHandler>,
241) {
242 let Some(my_pk) = crate::my_public_key() else { return; };
243 let Some(pseudonym) = event.tags.iter().find_map(|t| {
244 let s = t.as_slice();
245 (s.len() >= 2 && s[0] == "z").then(|| s[1].clone())
246 }) else { return; };
247
248 let kind = event.kind.as_u16();
250 if kind == event_kind::COMMUNITY_CONTROL || kind == event_kind::COMMUNITY_REKEY {
251 let community_id = CONTROL_ROUTES.lock().await.get(&pseudonym).cloned();
252 if let Some(community_id) = community_id {
253 if session.is_valid() {
254 tokio::spawn(refresh_control(community_id, handler.clone()));
257 }
258 }
259 return;
260 }
261
262 let Some(channel) = COMMUNITY_ROUTES.lock().await.get(&pseudonym).cloned() else {
263 return;
264 };
265 if !session.is_valid() {
266 return;
267 }
268
269 let outcome = {
270 let mut state = crate::state::STATE.lock().await;
271 inbound::process_incoming(&mut state, &event, &channel, &my_pk)
272 };
273 let chat_id = channel.id.to_hex();
274 match outcome {
275 Some(inbound::IncomingEvent::NewMessage(mut msg)) => {
276 if !msg.replied_to.is_empty() {
282 let _ = crate::db::events::populate_reply_context(&mut msg).await;
283 }
284 let _ = crate::db::events::save_message(&chat_id, &msg).await;
285 handler.on_community_message(&chat_id, &msg, true);
288 }
289 Some(inbound::IncomingEvent::Updated { target_id, message, edit_event }) => {
290 if let Some(ev) = edit_event {
292 let mut ev = (*ev).clone();
293 if let Ok(cid) = crate::db::id_cache::get_chat_id_by_identifier(&chat_id) {
294 ev.chat_id = cid;
295 }
296 let _ = crate::db::events::save_event(&ev).await;
297 } else {
298 let _ = crate::db::events::save_message(&chat_id, &message).await;
299 }
300 handler.on_community_update(&chat_id, &target_id, &message);
301 }
302 Some(inbound::IncomingEvent::Removed { target_id }) => {
303 let _ = crate::db::events::delete_event(&target_id).await;
304 handler.on_community_removed(&chat_id, &target_id);
305 }
306 Some(inbound::IncomingEvent::ReactionRemoved { message_id, reaction_id, message }) => {
307 let _ = crate::db::events::delete_event(&reaction_id).await;
309 handler.on_community_update(&chat_id, &message_id, &message);
310 }
311 Some(inbound::IncomingEvent::Presence { npub, joined, event_id, created_at, invited_by, invited_label }) => {
312 handler.on_community_presence(
313 &chat_id, &npub, joined, &event_id, created_at,
314 invited_by.as_deref(), invited_label.as_deref(),
315 );
316 }
317 Some(inbound::IncomingEvent::WebxdcPeer { npub, topic_id, node_addr, event_id, created_at }) => {
318 handler.on_community_webxdc(
319 &chat_id, &npub, &topic_id, node_addr.as_deref(), &event_id, created_at,
320 );
321 }
322 Some(inbound::IncomingEvent::Typing { npub, until }) => {
323 handler.on_community_typing(&chat_id, &npub, until);
324 }
325 Some(inbound::IncomingEvent::Kicked { community_id })
326 | Some(inbound::IncomingEvent::SelfLeft { community_id }) => {
327 handler.on_community_self_removed(&community_id);
330 }
331 None => {}
332 }
333}
334
335pub async fn refresh_control(community_id: String, handler: Arc<dyn InboundEventHandler>) {
340 {
342 let mut inflight = REFRESH_CONTROL_INFLIGHT.lock().unwrap_or_else(|e| e.into_inner());
343 if !inflight.insert(community_id.clone()) {
344 return;
345 }
346 }
347 struct RefreshClaim(String);
348 impl Drop for RefreshClaim {
349 fn drop(&mut self) {
350 REFRESH_CONTROL_INFLIGHT.lock().unwrap_or_else(|e| e.into_inner()).remove(&self.0);
351 }
352 }
353 let _claim = RefreshClaim(community_id.clone());
354
355 let session = SessionGuard::capture();
356 let Some(id_bytes) = hex_to_id32(&community_id) else { return; };
357 let Some(community) = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten() else { return; };
358 let bt = LiveTransport::with_timeout(Duration::from_secs(20));
359 let pre_server_epoch = community.server_root_epoch.0;
360 let pre_channel_epochs: Vec<(String, u64)> =
361 community.channels.iter().map(|c| (c.id.to_hex(), c.epoch.0)).collect();
362
363 if let Ok(c) = service::catch_up_server_root(&bt, &community).await {
367 if !session.is_valid() { return; }
368 if c.removed { handler.on_community_self_removed(&community_id); return; }
369 }
370 if !session.is_valid() { return; }
371 let community = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten().unwrap_or(community);
372 let _ = service::fetch_and_apply_control(&bt, &community).await;
373 if !session.is_valid() { return; }
374 if let Some(c) = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten() {
376 if service::am_i_banned(&c) {
377 handler.on_community_self_removed(&community_id);
378 return;
379 }
380 }
381 if !session.is_valid() { return; }
382
383 let base_delta = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten()
387 .map(|c| c.server_root_epoch.0).unwrap_or(pre_server_epoch).saturating_sub(pre_server_epoch);
388 for attempt in 0..CHANNEL_FOLLOW_MAX_ATTEMPTS {
389 if !session.is_valid() { return; }
390 let Some(cur) = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten() else { break; };
391 for ch in &cur.channels {
392 let _ = service::catch_up_channel_rekeys(&bt, &cur, &ch.id).await;
393 }
394 let caught = base_delta == 0 || crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten()
395 .map(|c| c.channels.iter().all(|ch| {
396 let pre = pre_channel_epochs.iter().find(|(id, _)| id == &ch.id.to_hex()).map(|(_, e)| *e).unwrap_or(ch.epoch.0);
397 ch.epoch.0 >= pre.saturating_add(base_delta)
398 }))
399 .unwrap_or(true);
400 if caught { break; }
401 if attempt + 1 < CHANNEL_FOLLOW_MAX_ATTEMPTS {
402 tokio::time::sleep(Duration::from_millis(CHANNEL_FOLLOW_BACKOFF_MS)).await;
403 }
404 }
405 if !session.is_valid() { return; }
406 let community = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten().unwrap_or(community);
407 let _ = service::retry_pending_read_cut(&bt, &community).await;
408 if !session.is_valid() { return; }
409 let community = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten().unwrap_or(community);
410
411 let advanced = community.server_root_epoch.0 != pre_server_epoch
414 || community.channels.iter().any(|c| {
415 pre_channel_epochs.iter().find(|(id, _)| id == &c.id.to_hex()).map(|(_, e)| *e != c.epoch.0).unwrap_or(true)
416 });
417 if advanced && session.is_valid() {
418 if let Some(client) = crate::state::nostr_client() {
419 refresh_subscription(&client).await;
420 }
421 } else {
422 let _ = rebuild_routes().await;
423 }
424 if !session.is_valid() { return; }
425 crate::community::list::refresh_membership_current(&community);
426 handler.on_community_refreshed(&community_id);
427}
428
429pub async fn teardown_local(community_id: &str) {
434 let _ = crate::db::community::delete_community_retain_keys(community_id);
435 if let Some(client) = crate::state::nostr_client() {
436 refresh_subscription(&client).await;
437 }
438}
439
440fn hex_to_id32(hex: &str) -> Option<[u8; 32]> {
442 (hex.len() == 64).then(|| crate::simd::hex::hex_to_bytes_32(hex))
443}