iris_chat_protocol/protocol_engine/
engine_group_wakes.rs1use super::*;
2
3pub(super) const MAX_READY_GROUP_PUBLICATIONS: usize = 4_096;
4
5impl ProtocolEngine {
6 fn ready_group_publication_is_allowed(&self, pending: &ProtocolReadyGroupPublish) -> bool {
7 pending.publish.is_publishable()
10 && !pending
11 .publish
12 .chat_id
13 .strip_prefix("group:")
14 .is_some_and(|group| self.local_owner_is_inactive_for_group(group))
15 && !pending
16 .wake_recipient
17 .is_some_and(|owner| self.held_direct_chats.contains(&owner.to_hex()))
18 }
19
20 pub(super) fn retire_ineligible_group_publications(&mut self) -> anyhow::Result<()> {
21 let rejected = self
22 .ready_group_publications
23 .iter()
24 .filter(|(_, pending)| !self.ready_group_publication_is_allowed(pending))
25 .map(|(id, _)| id.clone())
26 .collect::<Vec<_>>();
27 if rejected.is_empty() {
28 return Ok(());
29 }
30 self.with_state_checkpoint(|engine| {
31 for id in rejected {
32 engine.ready_group_publications.remove(&id);
33 }
34 engine.persist_now()
35 })
36 }
37
38 pub(super) fn retain_group_publications(
39 &mut self,
40 effects: &[ProtocolEffect],
41 wake_recipient: Option<NdrOwnerPubkey>,
42 ) -> anyhow::Result<()> {
43 let added = effects
44 .iter()
45 .filter(|effect| {
46 let ProtocolEffect::Publish(publish) = effect;
47 !self
48 .ready_group_publications
49 .contains_key(&publish.event.id.to_hex())
50 })
51 .count();
52 anyhow::ensure!(
53 self.ready_group_publications.len().saturating_add(added)
54 <= MAX_READY_GROUP_PUBLICATIONS,
55 "Outgoing group queue is full"
56 );
57 for effect in effects {
58 let ProtocolEffect::Publish(publish) = effect;
59 self.ready_group_publications
60 .entry(publish.event.id.to_hex())
61 .or_insert_with(|| ProtocolReadyGroupPublish {
62 publish: publish.clone(),
63 wake_recipient,
64 });
65 }
66 Ok(())
67 }
68
69 pub fn pending_group_publish_effects(&self) -> Vec<ProtocolEffect> {
72 self.ready_group_publications
73 .values()
74 .filter(|pending| self.ready_group_publication_is_allowed(pending))
75 .map(|pending| ProtocolEffect::Publish(pending.publish.clone()))
76 .collect()
77 }
78
79 pub fn has_pending_group_publish(&self, event_id: &str) -> bool {
80 self.ready_group_publications.contains_key(event_id)
81 }
82
83 pub fn ack_group_publish_effects(&mut self, effects: &[ProtocolEffect]) -> anyhow::Result<()> {
86 if !effects.iter().any(|effect| {
87 let ProtocolEffect::Publish(publish) = effect;
88 self.has_pending_group_publish(&publish.event.id.to_hex())
89 }) {
90 return Ok(());
91 }
92 self.with_state_checkpoint(|engine| {
93 for effect in effects {
94 let ProtocolEffect::Publish(publish) = effect;
95 engine
96 .ready_group_publications
97 .remove(&publish.event.id.to_hex());
98 }
99 engine.persist_now()
100 })
101 }
102
103 pub(super) fn prepare_private_group_wakes(
106 &mut self,
107 group_id: &str,
108 output: &mut ProtocolGroupSendResult,
109 inner_event_id: Option<String>,
110 ) -> anyhow::Result<()> {
111 let group = self
112 .group_manager
113 .group(group_id)
114 .ok_or_else(|| anyhow::anyhow!("group is unavailable"))?;
115 let own_authors = self.group_sender_event_pubkeys_for_owner(self.owner_pubkey);
116 let broadcasts = output
117 .effects
118 .iter()
119 .filter_map(|effect| {
120 let ProtocolEffect::Publish(publish) = effect;
121 (own_authors.contains(&publish.event.pubkey)
122 && publish.carrier == ProtocolCarrier::SharedGroup)
123 .then_some(publish.event.clone())
124 })
125 .collect::<Vec<_>>();
126 let chat_id = group_chat_id(group_id);
127 for event in broadcasts {
128 let content = serde_json::json!({
129 "v": 1, "type": "group-wake", "event": event,
130 })
131 .to_string();
132 let mut rumor = UnsignedEvent::new(
133 self.owner_pubkey,
134 event.created_at,
135 nostr::Kind::Custom(21_113),
136 [],
137 content,
138 );
139 rumor.ensure_id();
140 let delivery_id = inner_event_id
143 .clone()
144 .or_else(|| rumor.id.map(|id| id.to_hex()));
145 let payload = serde_json::to_vec(&rumor)?;
146 let mut rng = OsRng;
147 let mut ctx = ProtocolContext::new(NdrUnixSeconds(unix_now().get()), &mut rng);
148 for recipient_owner in group.members.iter().copied().collect::<BTreeSet<_>>() {
149 if recipient_owner == self.local_owner
150 || self
151 .held_direct_chats
152 .contains(&public_owner(recipient_owner)?.to_hex())
153 {
154 continue;
155 }
156 let prepared = self.session_manager.prepare_remote_send(
157 &mut ctx,
158 recipient_owner,
159 payload.clone(),
160 )?;
161 let prepared = group_publish_from_prepared_send(
162 prepared,
163 GroupPendingFanout::Remote {
164 recipient_owner,
165 payload: payload.clone(),
166 },
167 );
168 self.queue_group_pending_fanouts(group_id, &prepared, delivery_id.clone());
169 let ready = protocol_effects_from_group_prepared_publish(
170 &prepared,
171 self,
172 delivery_id.clone(),
173 chat_id.clone(),
174 &mut output.event_ids,
175 )?;
176 self.retain_group_publications(&ready, Some(recipient_owner))?;
177 output.effects.extend(ready);
178 }
179 let payload = local_sibling_payload(self.owner_pubkey, &payload)?;
180 let prepared = self
181 .session_manager
182 .prepare_local_sibling_send_reusing_sessions(&mut ctx, payload.clone())?;
183 let prepared = group_publish_from_prepared_send(
184 prepared,
185 GroupPendingFanout::LocalSiblings { payload },
186 );
187 self.queue_group_pending_fanouts(group_id, &prepared, delivery_id.clone());
188 output
189 .effects
190 .extend(protocol_effects_from_group_prepared_publish(
191 &prepared,
192 self,
193 delivery_id.clone(),
194 chat_id.clone(),
195 &mut output.event_ids,
196 )?);
197 }
198 Ok(())
199 }
200}