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 control_probe_coordinates() -> (Vec<String>, HashMap<String, String>, HashSet<String>) {
147 let mut coords: Vec<String> = Vec::new();
148 let mut map: HashMap<String, String> = HashMap::new();
149 let mut relays: HashSet<String> = HashSet::new();
150 let Ok(ids) = crate::db::community::list_community_ids() else {
151 return (coords, map, relays);
152 };
153 for id in ids {
154 if matches!(
157 crate::db::community::community_protocol(&id).ok().flatten(),
158 Some(crate::community::ConcordProtocol::V2)
159 ) {
160 continue;
161 }
162 let Ok(Some(community)) = crate::db::community::load_community(&id) else {
163 continue;
164 };
165 let cid = community.id.to_hex();
166 for r in &community.relays {
167 relays.insert(r.clone());
168 }
169 let mut add = |coord: String| {
170 if !map.contains_key(&coord) {
171 coords.push(coord.clone());
172 map.insert(coord, cid.clone());
173 }
174 };
175 add(roster::control_pseudonym(&community.server_root_key, &community.id, community.server_root_epoch));
176 add(derive::base_rekey_pseudonym(&community.server_root_key, &community.id, Epoch(community.server_root_epoch.0 + 1)).to_hex());
177 for ch in &community.channels {
178 add(derive::rekey_pseudonym(&community.server_root_key, &ch.id, Epoch(ch.epoch.0 + 1)).to_hex());
179 }
180 }
181 (coords, map, relays)
182}
183
184pub async fn refresh_subscription(client: &Client) {
188 let (pseudonyms, relays) = rebuild_routes().await;
189
190 let mut new_set = pseudonyms.clone();
194 new_set.sort();
195
196 let mut sub_guard = COMMUNITY_SUB_ID.lock().await;
197 let mut set_guard = COMMUNITY_SUB_SET.lock().await;
198
199 if sub_guard.is_some() && *set_guard == new_set {
203 return;
204 }
205
206 if let Some(old_id) = sub_guard.take() {
207 client.unsubscribe(&old_id).await;
208 }
209 *set_guard = new_set;
210
211 if pseudonyms.is_empty() {
212 if let Some(old_pw) = COMMUNITY_POOLWIDE_SUB_ID.lock().await.take() {
213 client.unsubscribe(&old_pw).await;
214 }
215 return;
216 }
217
218 for r in &relays {
221 let _ = client.pool().add_relay(r.as_str(), crate::community_relay_options()).await;
222 }
223 client.connect().await;
224
225 {
232 let wanted: Vec<RelayUrl> = relays.iter().filter_map(|r| RelayUrl::parse(r).ok()).collect();
233 for _ in 0..24 {
234 let pool = client.pool().all_relays().await;
235 let any_live = wanted.iter().any(|u| {
236 pool.get(u).map(|r| r.status() == RelayStatus::Connected).unwrap_or(false)
237 });
238 if any_live {
239 break;
240 }
241 tokio::time::sleep(Duration::from_millis(250)).await;
242 }
243 }
244
245 let filter = Filter::new()
246 .kinds([
247 Kind::Custom(event_kind::COMMUNITY_MESSAGE),
248 Kind::Custom(event_kind::COMMUNITY_REACTION),
249 Kind::Custom(event_kind::COMMUNITY_EDIT),
250 Kind::Custom(event_kind::COMMUNITY_DELETE),
251 Kind::Custom(event_kind::COMMUNITY_PRESENCE),
252 Kind::Custom(event_kind::COMMUNITY_KICK),
253 Kind::Custom(event_kind::COMMUNITY_TYPING),
254 Kind::Custom(event_kind::COMMUNITY_WEBXDC),
255 Kind::Custom(event_kind::COMMUNITY_CONTROL),
256 Kind::Custom(event_kind::COMMUNITY_REKEY),
257 ])
258 .custom_tags(SingleLetterTag::lowercase(Alphabet::Z), pseudonyms)
259 .limit(0);
260
261 {
263 let mut pw = COMMUNITY_POOLWIDE_SUB_ID.lock().await;
264 if let Some(old) = pw.take() {
265 client.unsubscribe(&old).await;
266 }
267 if let Ok(out) = client.subscribe(filter.clone(), None).await {
268 *pw = Some(out.val);
269 }
270 }
271 if let Ok(output) = client.subscribe_to(relays.iter().cloned(), filter, None).await {
273 *sub_guard = Some(output.val);
274 }
275}
276
277pub async fn dispatch_event(
282 session: &SessionGuard,
283 event: Event,
284 handler: Arc<dyn InboundEventHandler>,
285) {
286 let Some(my_pk) = crate::my_public_key() else { return; };
287 let Some(pseudonym) = event.tags.iter().find_map(|t| {
288 let s = t.as_slice();
289 (s.len() >= 2 && s[0] == "z").then(|| s[1].clone())
290 }) else { return; };
291
292 let kind = event.kind.as_u16();
294 if kind == event_kind::COMMUNITY_CONTROL || kind == event_kind::COMMUNITY_REKEY {
295 let community_id = CONTROL_ROUTES.lock().await.get(&pseudonym).cloned();
296 if let Some(community_id) = community_id {
297 if session.is_valid() {
298 tokio::spawn(refresh_control(community_id, handler.clone()));
301 }
302 }
303 return;
304 }
305
306 let Some(channel) = COMMUNITY_ROUTES.lock().await.get(&pseudonym).cloned() else {
307 return;
308 };
309 if !session.is_valid() {
310 return;
311 }
312
313 let outcome = {
314 let mut state = crate::state::STATE.lock().await;
315 inbound::process_incoming(&mut state, &event, &channel, &my_pk)
316 };
317 let chat_id = channel.id.to_hex();
318 match outcome {
319 Some(inbound::IncomingEvent::NewMessage(mut msg)) => {
320 if !msg.replied_to.is_empty() {
326 let _ = crate::db::events::populate_reply_context(&mut msg).await;
327 }
328 let _ = crate::db::events::save_message(&chat_id, &msg).await;
329 handler.on_community_message(&chat_id, &msg, true);
332 }
333 Some(inbound::IncomingEvent::Updated { target_id, message, edit_event }) => {
334 if let Some(ev) = edit_event {
336 let mut ev = (*ev).clone();
337 if let Ok(cid) = crate::db::id_cache::get_chat_id_by_identifier(&chat_id) {
338 ev.chat_id = cid;
339 }
340 let _ = crate::db::events::save_event(&ev).await;
341 } else {
342 let _ = crate::db::events::save_message(&chat_id, &message).await;
343 }
344 handler.on_community_update(&chat_id, &target_id, &message);
345 }
346 Some(inbound::IncomingEvent::Removed { target_id }) => {
347 let _ = crate::db::events::delete_event(&target_id).await;
348 handler.on_community_removed(&chat_id, &target_id);
349 }
350 Some(inbound::IncomingEvent::ReactionRemoved { message_id, reaction_id, message }) => {
351 let _ = crate::db::events::delete_event(&reaction_id).await;
353 handler.on_community_update(&chat_id, &message_id, &message);
354 }
355 Some(inbound::IncomingEvent::Presence { npub, joined, event_id, created_at, invited_by, invited_label }) => {
356 handler.on_community_presence(
357 &chat_id, &npub, joined, &event_id, created_at,
358 invited_by.as_deref(), invited_label.as_deref(),
359 );
360 }
361 Some(inbound::IncomingEvent::WebxdcPeer { npub, topic_id, node_addr, event_id, created_at }) => {
362 handler.on_community_webxdc(
363 &chat_id, &npub, &topic_id, node_addr.as_deref(), &event_id, created_at,
364 );
365 }
366 Some(inbound::IncomingEvent::Typing { npub, until }) => {
367 handler.on_community_typing(&chat_id, &npub, until);
368 }
369 Some(inbound::IncomingEvent::Kicked { community_id })
370 | Some(inbound::IncomingEvent::SelfLeft { community_id }) => {
371 handler.on_community_self_removed(&community_id);
374 }
375 None => {}
376 }
377}
378
379pub async fn refresh_control(community_id: String, handler: Arc<dyn InboundEventHandler>) {
384 {
386 let mut inflight = REFRESH_CONTROL_INFLIGHT.lock().unwrap_or_else(|e| e.into_inner());
387 if !inflight.insert(community_id.clone()) {
388 return;
389 }
390 }
391 struct RefreshClaim(String);
392 impl Drop for RefreshClaim {
393 fn drop(&mut self) {
394 REFRESH_CONTROL_INFLIGHT.lock().unwrap_or_else(|e| e.into_inner()).remove(&self.0);
395 }
396 }
397 let _claim = RefreshClaim(community_id.clone());
398
399 let session = SessionGuard::capture();
400 let Some(id_bytes) = hex_to_id32(&community_id) else { return; };
401 let Some(community) = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten() else { return; };
402 let bt = LiveTransport::with_timeout(Duration::from_secs(20));
403 let pre_server_epoch = community.server_root_epoch.0;
404 let pre_channel_epochs: Vec<(String, u64)> =
405 community.channels.iter().map(|c| (c.id.to_hex(), c.epoch.0)).collect();
406
407 if let Ok(c) = service::catch_up_server_root(&bt, &community).await {
411 if !session.is_valid() { return; }
412 if c.removed { handler.on_community_self_removed(&community_id); return; }
413 }
414 if !session.is_valid() { return; }
415 let community = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten().unwrap_or(community);
416 let _ = service::fetch_and_apply_control(&bt, &community).await;
417 if !session.is_valid() { return; }
418 if let Some(c) = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten() {
420 if service::am_i_banned(&c) {
421 handler.on_community_self_removed(&community_id);
422 return;
423 }
424 }
425 if !session.is_valid() { return; }
426
427 let base_delta = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten()
431 .map(|c| c.server_root_epoch.0).unwrap_or(pre_server_epoch).saturating_sub(pre_server_epoch);
432 for attempt in 0..CHANNEL_FOLLOW_MAX_ATTEMPTS {
433 if !session.is_valid() { return; }
434 let Some(cur) = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten() else { break; };
435 for ch in &cur.channels {
436 let _ = service::catch_up_channel_rekeys(&bt, &cur, &ch.id).await;
437 }
438 let caught = base_delta == 0 || crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten()
439 .map(|c| c.channels.iter().all(|ch| {
440 let pre = pre_channel_epochs.iter().find(|(id, _)| id == &ch.id.to_hex()).map(|(_, e)| *e).unwrap_or(ch.epoch.0);
441 ch.epoch.0 >= pre.saturating_add(base_delta)
442 }))
443 .unwrap_or(true);
444 if caught { break; }
445 if attempt + 1 < CHANNEL_FOLLOW_MAX_ATTEMPTS {
446 tokio::time::sleep(Duration::from_millis(CHANNEL_FOLLOW_BACKOFF_MS)).await;
447 }
448 }
449 if !session.is_valid() { return; }
450 let community = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten().unwrap_or(community);
451 let _ = service::retry_pending_read_cut(&bt, &community).await;
452 if !session.is_valid() { return; }
453 let community = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten().unwrap_or(community);
454
455 let advanced = community.server_root_epoch.0 != pre_server_epoch
458 || community.channels.iter().any(|c| {
459 pre_channel_epochs.iter().find(|(id, _)| id == &c.id.to_hex()).map(|(_, e)| *e != c.epoch.0).unwrap_or(true)
460 });
461 if advanced && session.is_valid() {
462 if let Some(client) = crate::state::nostr_client() {
463 refresh_subscription(&client).await;
464 }
465 } else {
466 let _ = rebuild_routes().await;
467 }
468 if !session.is_valid() { return; }
469 crate::community::list::refresh_membership_current(&community);
470 handler.on_community_refreshed(&community_id);
471}
472
473pub async fn teardown_local(community_id: &str) {
478 let _ = crate::db::community::delete_community_retain_keys(community_id);
479 if let Some(client) = crate::state::nostr_client() {
480 refresh_subscription(&client).await;
481 }
482}
483
484fn hex_to_id32(hex: &str) -> Option<[u8; 32]> {
486 (hex.len() == 64).then(|| crate::simd::hex::hex_to_bytes_32(hex))
487}