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::stored_event::event_kind;
21use crate::ClientRelayExt;
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 super::migration::clear_drive_inflight();
84}
85
86pub async fn rebuild_routes() -> (Vec<String>, HashSet<String>) {
91 let mut routes: HashMap<String, Channel> = HashMap::new();
92 let mut control_routes: HashMap<String, String> = HashMap::new();
93 let mut pseudonyms: Vec<String> = Vec::new();
94 let mut relays: HashSet<String> = HashSet::new();
95
96 if let Ok(ids) = crate::db::community::list_community_ids() {
97 for id in ids {
98 if let Ok(Some(community)) = crate::db::community::load_community(&id) {
99 for r in &community.relays {
100 relays.insert(r.clone());
101 }
102 for ch in &community.channels {
103 for (epoch, key) in ch.read_epoch_keys() {
107 let pseudonym = derive::channel_pseudonym(&key, &ch.id, epoch).to_hex();
108 pseudonyms.push(pseudonym.clone());
109 routes.insert(pseudonym, ch.clone());
110 }
111 let next_chan = derive::rekey_pseudonym(
114 &community.server_root_key, &ch.id, Epoch(ch.epoch.0 + 1),
115 ).to_hex();
116 pseudonyms.push(next_chan.clone());
117 control_routes.insert(next_chan, community.id.to_hex());
118 }
119 let ctrl = roster::control_pseudonym(
121 &community.server_root_key, &community.id, community.server_root_epoch,
122 );
123 pseudonyms.push(ctrl.clone());
124 control_routes.insert(ctrl, community.id.to_hex());
125 let next_base = derive::base_rekey_pseudonym(
127 &community.server_root_key, &community.id,
128 Epoch(community.server_root_epoch.0 + 1),
129 ).to_hex();
130 pseudonyms.push(next_base.clone());
131 control_routes.insert(next_base, community.id.to_hex());
132 }
133 }
134 }
135
136 *COMMUNITY_ROUTES.lock().await = routes;
137 *CONTROL_ROUTES.lock().await = control_routes;
138 (pseudonyms, relays)
139}
140
141pub async fn control_probe_coordinates() -> (Vec<String>, HashMap<String, String>, HashSet<String>) {
148 let mut coords: Vec<String> = Vec::new();
149 let mut map: HashMap<String, String> = HashMap::new();
150 let mut relays: HashSet<String> = HashSet::new();
151 let Ok(ids) = crate::db::community::list_community_ids() else {
152 return (coords, map, relays);
153 };
154 for id in ids {
155 if matches!(
158 crate::db::community::community_protocol(&id).ok().flatten(),
159 Some(crate::community::ConcordProtocol::V2)
160 ) {
161 continue;
162 }
163 let Ok(Some(community)) = crate::db::community::load_community(&id) else {
164 continue;
165 };
166 let cid = community.id.to_hex();
167 for r in &community.relays {
168 relays.insert(r.clone());
169 }
170 let mut add = |coord: String| {
171 if !map.contains_key(&coord) {
172 coords.push(coord.clone());
173 map.insert(coord, cid.clone());
174 }
175 };
176 add(roster::control_pseudonym(&community.server_root_key, &community.id, community.server_root_epoch));
177 add(derive::base_rekey_pseudonym(&community.server_root_key, &community.id, Epoch(community.server_root_epoch.0 + 1)).to_hex());
178 for ch in &community.channels {
179 add(derive::rekey_pseudonym(&community.server_root_key, &ch.id, Epoch(ch.epoch.0 + 1)).to_hex());
180 }
181 }
182 (coords, map, relays)
183}
184
185pub async fn refresh_subscription(client: &Client) {
189 let (pseudonyms, relays) = rebuild_routes().await;
190
191 let mut new_set = pseudonyms.clone();
195 new_set.sort();
196
197 let mut sub_guard = COMMUNITY_SUB_ID.lock().await;
198 let mut set_guard = COMMUNITY_SUB_SET.lock().await;
199
200 if sub_guard.is_some() && *set_guard == new_set {
204 return;
205 }
206
207 if let Some(old_id) = sub_guard.take() {
208 let _ = client.unsubscribe(&old_id).await;
209 }
210 *set_guard = new_set;
211
212 if pseudonyms.is_empty() {
213 if let Some(old_pw) = COMMUNITY_POOLWIDE_SUB_ID.lock().await.take() {
214 let _ = client.unsubscribe(&old_pw).await;
215 }
216 return;
217 }
218
219 for r in &relays {
222 let _ = client.add_managed_relay(r.as_str()).capabilities(crate::community_relay_capabilities()).await;
223 }
224 client.connect().await;
225
226 {
233 let wanted: Vec<RelayUrl> = relays.iter().filter_map(|r| RelayUrl::parse(r).ok()).collect();
234 for _ in 0..24 {
235 let pool = client.relays().all().await;
236 let any_live = wanted.iter().any(|u| {
237 pool.get(u).map(|r| r.status() == RelayStatus::Connected).unwrap_or(false)
238 });
239 if any_live {
240 break;
241 }
242 tokio::time::sleep(Duration::from_millis(250)).await;
243 }
244 }
245
246 let filter = Filter::new()
247 .kinds([
248 Kind::Custom(event_kind::COMMUNITY_MESSAGE),
249 Kind::Custom(event_kind::COMMUNITY_REACTION),
250 Kind::Custom(event_kind::COMMUNITY_EDIT),
251 Kind::Custom(event_kind::COMMUNITY_DELETE),
252 Kind::Custom(event_kind::COMMUNITY_PRESENCE),
253 Kind::Custom(event_kind::COMMUNITY_KICK),
254 Kind::Custom(event_kind::COMMUNITY_TYPING),
255 Kind::Custom(event_kind::COMMUNITY_WEBXDC),
256 Kind::Custom(event_kind::COMMUNITY_CONTROL),
257 Kind::Custom(event_kind::COMMUNITY_REKEY),
258 ])
259 .custom_tags(SingleLetterTag::LOWERCASE_Z, pseudonyms)
260 .since(Timestamp::from_secs(
264 Timestamp::now().as_secs().saturating_sub(super::REALTIME_FRESH_WINDOW_MS / 1000),
265 ))
266 .limit(0);
267
268 {
270 let mut pw = COMMUNITY_POOLWIDE_SUB_ID.lock().await;
271 if let Some(old) = pw.take() {
272 let _ = client.unsubscribe(&old).await;
273 }
274 if let Ok(out) = client.subscribe(filter.clone()).await {
275 *pw = Some(out.value);
276 }
277 }
278 if let Ok(output) = client
280 .subscribe(nostr_sdk::prelude::ReqTarget::manual(
281 relays.iter().cloned().map(|u| (u, vec![filter.clone()])),
282 ))
283 .await
284 {
285 *sub_guard = Some(output.value);
286 }
287}
288
289pub async fn dispatch_event(
294 event: Event,
295 handler: Arc<dyn InboundEventHandler>,
296) {
297 crate::db::scoped(async move {
298 let Some(my_pk) = crate::my_public_key() else { return; };
299 let Some(pseudonym) = event.tags.iter().find_map(|t| {
300 let s = t.as_slice();
301 (s.len() >= 2 && s[0] == "z").then(|| s[1].clone())
302 }) else { return; };
303
304 let kind = event.kind.as_u16();
306 if kind == event_kind::COMMUNITY_CONTROL || kind == event_kind::COMMUNITY_REKEY {
307 let community_id = CONTROL_ROUTES.lock().await.get(&pseudonym).cloned();
308 if let Some(community_id) = community_id {
309 crate::db::spawn_bound(refresh_control(community_id, handler.clone()));
312 }
313 return;
314 }
315
316 let Some(channel) = COMMUNITY_ROUTES.lock().await.get(&pseudonym).cloned() else {
317 return;
318 };
319
320 let outcome = {
321 let mut state = crate::state::STATE.lock().await;
322 inbound::process_incoming(&mut state, &event, &channel, &my_pk)
327 };
328 let chat_id = channel.id.to_hex();
329 match outcome {
330 Some(inbound::IncomingEvent::NewMessage(mut msg)) => {
331 if !msg.replied_to.is_empty() {
337 let _ = crate::db::events::populate_reply_context(&mut msg).await;
338 }
339 let _ = crate::db::events::save_message(&chat_id, &msg).await;
340 handler.on_community_message(&chat_id, &msg, super::is_realtime_fresh(msg.at));
345 }
346 Some(inbound::IncomingEvent::Updated { target_id, mut message, edit_event }) => {
347 if let Some(ev) = edit_event {
349 let mut ev = (*ev).clone();
350 if let Ok(cid) = crate::db::id_cache::get_chat_id_by_identifier(&chat_id) {
351 ev.chat_id = cid;
352 }
353 let _ = crate::db::events::save_event(&ev).await;
354 } else {
355 let _ = crate::db::events::save_message(&chat_id, &message).await;
356 }
357 let _ = crate::db::events::populate_reply_context(&mut message).await;
360 handler.on_community_update(&chat_id, &target_id, &message);
361 }
362 Some(inbound::IncomingEvent::Removed { target_id }) => {
363 let _ = crate::db::events::delete_event(&target_id).await;
364 handler.on_community_removed(&chat_id, &target_id);
365 }
366 Some(inbound::IncomingEvent::ReactionRemoved { message_id, reaction_id, mut message }) => {
367 let _ = crate::db::events::delete_event(&reaction_id).await;
369 let _ = crate::db::events::populate_reply_context(&mut message).await;
370 handler.on_community_update(&chat_id, &message_id, &message);
371 }
372 Some(inbound::IncomingEvent::Presence { npub, joined, event_id, created_at, invited_by, invited_label }) => {
373 handler.on_community_presence(
374 &chat_id, &npub, joined, &event_id, created_at,
375 invited_by.as_deref(), invited_label.as_deref(),
376 );
377 }
378 Some(inbound::IncomingEvent::WebxdcPeer { npub, topic_id, node_addr, event_id, created_at }) => {
379 handler.on_community_webxdc(
380 &chat_id, &npub, &topic_id, node_addr.as_deref(), &event_id, created_at,
381 );
382 }
383 Some(inbound::IncomingEvent::Typing { npub, until }) => {
384 handler.on_community_typing(&chat_id, &npub, until);
385 }
386 Some(inbound::IncomingEvent::Kicked { community_id })
387 | Some(inbound::IncomingEvent::SelfLeft { community_id }) => {
388 crate::log_warn!("[v1:teardown {}] INBOUND Kicked/SelfLeft on the v1 channel plane", &community_id[..8.min(community_id.len())]);
389 handler.on_community_self_removed(&community_id);
392 }
393 None => {}
394 }
395 })
396 .await
397}
398
399pub async fn refresh_control(community_id: String, handler: Arc<dyn InboundEventHandler>) {
404 crate::db::scoped(async move {
405 {
407 let mut inflight = REFRESH_CONTROL_INFLIGHT.lock().unwrap_or_else(|e| e.into_inner());
408 if !inflight.insert(community_id.clone()) {
409 return;
410 }
411 }
412 struct RefreshClaim(String);
413 impl Drop for RefreshClaim {
414 fn drop(&mut self) {
415 REFRESH_CONTROL_INFLIGHT.lock().unwrap_or_else(|e| e.into_inner()).remove(&self.0);
416 }
417 }
418 let _claim = RefreshClaim(community_id.clone());
419
420 let Some(id_bytes) = hex_to_id32(&community_id) else { return; };
421 let Some(community) = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten() else { return; };
422 let bt = LiveTransport::with_timeout(Duration::from_secs(20));
423 let pre_server_epoch = community.server_root_epoch.0;
424 let pre_channel_epochs: Vec<(String, u64)> =
425 community.channels.iter().map(|c| (c.id.to_hex(), c.epoch.0)).collect();
426
427 if let Ok(c) = service::catch_up_server_root(&bt, &community).await {
431 if c.removed {
432 crate::log_warn!("[v1:teardown {}] catch_up_server_root says REMOVED (v1 rekey walk)", &community_id[..8.min(community_id.len())]);
433 handler.on_community_self_removed(&community_id); return;
434 }
435 }
436 let community = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten().unwrap_or(community);
437 let _ = service::fetch_and_apply_control(&bt, &community).await;
438 if let Some(c) = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten() {
440 if service::am_i_banned(&c) {
441 crate::log_warn!("[v1:teardown {}] am_i_banned on the v1 refresh path", &community_id[..8.min(community_id.len())]);
442 handler.on_community_self_removed(&community_id);
443 return;
444 }
445 }
446
447 let base_delta = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten()
451 .map(|c| c.server_root_epoch.0).unwrap_or(pre_server_epoch).saturating_sub(pre_server_epoch);
452 for attempt in 0..CHANNEL_FOLLOW_MAX_ATTEMPTS {
453 let Some(cur) = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten() else { break; };
454 for ch in &cur.channels {
455 let _ = service::catch_up_channel_rekeys(&bt, &cur, &ch.id).await;
456 }
457 let caught = base_delta == 0 || crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten()
458 .map(|c| c.channels.iter().all(|ch| {
459 let pre = pre_channel_epochs.iter().find(|(id, _)| id == &ch.id.to_hex()).map(|(_, e)| *e).unwrap_or(ch.epoch.0);
460 ch.epoch.0 >= pre.saturating_add(base_delta)
461 }))
462 .unwrap_or(true);
463 if caught { break; }
464 if attempt + 1 < CHANNEL_FOLLOW_MAX_ATTEMPTS {
465 tokio::time::sleep(Duration::from_millis(CHANNEL_FOLLOW_BACKOFF_MS)).await;
466 }
467 }
468 let community = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten().unwrap_or(community);
469 let _ = service::retry_pending_read_cut(&bt, &community).await;
470 let community = crate::db::community::load_community(&CommunityId(id_bytes)).ok().flatten().unwrap_or(community);
471
472 let advanced = community.server_root_epoch.0 != pre_server_epoch
475 || community.channels.iter().any(|c| {
476 pre_channel_epochs.iter().find(|(id, _)| id == &c.id.to_hex()).map(|(_, e)| *e != c.epoch.0).unwrap_or(true)
477 });
478 if advanced {
479 if let Some(client) = crate::state::nostr_client() {
480 refresh_subscription(&client).await;
481 }
482 } else {
483 let _ = rebuild_routes().await;
484 }
485 crate::community::list::refresh_membership_current(&community);
486 handler.on_community_refreshed(&community_id);
487 })
488 .await
489}
490
491pub async fn teardown_local(community_id: &str) {
496 let _ = crate::db::community::delete_community_retain_keys(community_id);
497 if let Some(client) = crate::state::nostr_client() {
498 refresh_subscription(&client).await;
499 }
500}
501
502fn hex_to_id32(hex: &str) -> Option<[u8; 32]> {
504 (hex.len() == 64).then(|| crate::simd::hex::hex_to_bytes_32(hex))
505}