1use tokio::sync::oneshot;
9
10use xmtp_common::{MaybeSend, StreamHandle};
11use xmtp_db::consent_record::ConsentState;
12use xmtp_db::group::ConversationType;
13use xmtp_db::group_message::StoredGroupMessage;
14use xmtp_proto::api_client::{XmtpMlsBidiStreams, XmtpMlsStreams};
15use xmtp_proto::types::{GroupId, InstallationId};
16
17use xmtp_common::Event;
18use xmtp_macro::log_event;
19
20use super::{
21 Result, StreamKind, SubscribeError,
22 incoming::stream_api::shared_wires,
23 watchdog::{StreamCancel, close_reason},
24};
25use crate::context::XmtpSharedContext;
26use crate::groups::MlsGroup;
27
28pub use super::incoming::stream_api::shared_wires::ApiClientIdentity;
29#[cfg(any(test, feature = "test-utils"))]
30pub use super::incoming::stream_api::shared_wires::shared_transport_count;
31
32pub async fn suspend_bidi_streams() -> Result<()> {
34 shared_wires::suspend()
35 .await
36 .map_err(SubscribeError::Transport)
37}
38
39pub async fn resume_bidi_streams() -> Result<()> {
41 shared_wires::resume();
42 Ok(())
43}
44
45struct StreamClosedGuard<F: FnOnce()> {
47 kind: StreamKind,
48 installation: InstallationId,
49 on_close: Option<F>,
50}
51
52impl<F: FnOnce()> Drop for StreamClosedGuard<F> {
53 fn drop(&mut self) {
54 log_event!(Event::StreamClosed, self.installation, kind = ?self.kind);
55 if let Some(on_close) = self.on_close.take() {
56 on_close();
57 }
58 }
59}
60
61pub(crate) struct StreamOrigin {
62 pub(crate) kind: StreamKind,
63 pub(crate) installation: InstallationId,
64 pub(crate) cancel: StreamCancel,
65}
66
67impl StreamOrigin {
68 fn new<Context: XmtpSharedContext>(kind: StreamKind, context: &Context) -> Self {
69 Self {
70 kind,
71 installation: context.installation_id(),
72 cancel: StreamCancel::new(context),
73 }
74 }
75}
76
77pub(crate) fn pump_stream<T, S, St>(
79 origin: StreamOrigin,
80 subscribe: S,
81 mut callback: impl FnMut(Result<T>) + MaybeSend + 'static,
82 on_close: impl FnOnce() + MaybeSend + 'static,
83) -> impl StreamHandle<StreamOutput = Result<()>>
84where
85 T: MaybeSend + 'static,
86 St: futures::Stream<Item = Result<T>> + MaybeSend + Unpin + 'static,
87 S: Future<Output = Result<St>> + MaybeSend + 'static,
88{
89 use futures::StreamExt;
90 let (tx, rx) = oneshot::channel();
91 let task = async move {
92 let StreamOrigin {
93 kind,
94 installation,
95 cancel,
96 } = origin;
97 log_event!(Event::StreamOpened, installation, kind = ?kind);
98 let _closed = StreamClosedGuard {
99 kind,
100 installation,
101 on_close: Some(on_close),
102 };
103 let mut stream = tokio::select! {
104 biased;
105 _ = cancel.cancelled() => return close_reason(&cancel, true, &mut callback),
106 result = subscribe => match result {
107 Ok(stream) => stream,
108 Err(error) => {
109 close_reason(&cancel, true, &mut callback)?;
110 callback(Err(error));
111 return Ok(());
112 }
113 },
114 };
115 let _ = tx.send(());
116 loop {
117 tokio::select! {
118 biased;
119 _ = cancel.cancelled() => return close_reason(&cancel, true, &mut callback),
120 next = stream.next() => match next {
121 Some(item) => {
122 close_reason(&cancel, true, &mut callback)?;
125 let terminal = item.is_err();
126 callback(item);
127 if terminal {
128 return Ok(());
129 }
130 },
131 None => return close_reason(&cancel, true, &mut callback),
132 }
133 }
134 }
135 };
136 xmtp_common::spawn(Some(rx), xmtp_common::bind_task_hub(task))
137}
138
139pub(crate) fn stream_all_messages_with_callback_dispatch<C>(
141 context: C,
142 keep_alive: impl MaybeSend + 'static,
143 conversation_type: Option<ConversationType>,
144 consent_states: Option<Vec<ConsentState>>,
145 callback: impl FnMut(Result<StoredGroupMessage>) + MaybeSend + 'static,
146 on_close: impl FnOnce() + MaybeSend + 'static,
147) -> impl StreamHandle<StreamOutput = Result<()>>
148where
149 C: XmtpSharedContext + 'static,
150 C::ApiClient:
151 XmtpMlsBidiStreams + XmtpMlsStreams + ApiClientIdentity + Clone + Send + Sync + 'static,
152 <C::ApiClient as XmtpMlsBidiStreams>::SubscribeStream: 'static,
153{
154 let origin = StreamOrigin::new(StreamKind::All, &context);
155 let subscribe = async move {
156 let _keep_alive = keep_alive;
157 super::stream_all::StreamAllMessages::new_owned(context, conversation_type, consent_states)
158 .await
159 };
160 pump_stream(origin, subscribe, callback, on_close)
161}
162
163pub(crate) fn stream_conversations_with_callback_dispatch<C>(
165 context: C,
166 keep_alive: impl MaybeSend + 'static,
167 conversation_type: Option<ConversationType>,
168 include_duplicate_dms: bool,
169 callback: impl FnMut(Result<MlsGroup<C>>) + MaybeSend + 'static,
170 on_close: impl FnOnce() + MaybeSend + 'static,
171) -> impl StreamHandle<StreamOutput = Result<()>>
172where
173 C: XmtpSharedContext + 'static,
174 C::ApiClient:
175 XmtpMlsBidiStreams + XmtpMlsStreams + ApiClientIdentity + Clone + Send + Sync + 'static,
176 <C::ApiClient as XmtpMlsBidiStreams>::SubscribeStream: 'static,
177{
178 let origin = StreamOrigin::new(StreamKind::Conversations, &context);
179 let subscribe = async move {
180 let _keep_alive = keep_alive;
181 super::stream_conversations::StreamConversations::new_owned(
182 context,
183 conversation_type,
184 include_duplicate_dms,
185 None,
186 )
187 .await
188 };
189 pump_stream(origin, subscribe, callback, on_close)
190}
191
192pub fn stream_conversation_messages_with_callback_dispatch<C>(
193 context: C,
194 group_id: GroupId,
195 callback: impl FnMut(Result<StoredGroupMessage>) + MaybeSend + 'static,
196 on_close: impl FnOnce() + MaybeSend + 'static,
197) -> impl StreamHandle<StreamOutput = Result<()>>
198where
199 C: XmtpSharedContext + 'static,
200 C::ApiClient:
201 XmtpMlsBidiStreams + XmtpMlsStreams + ApiClientIdentity + Clone + Send + Sync + 'static,
202 <C::ApiClient as XmtpMlsBidiStreams>::SubscribeStream: 'static,
203{
204 let origin = StreamOrigin::new(StreamKind::Messages, &context);
205 let subscribe = async move {
206 super::stream_messages::StreamGroupMessages::new_owned(context, vec![group_id]).await
207 };
208 pump_stream(origin, subscribe, callback, on_close)
209}