Skip to main content

inline_client/
runtime.rs

1//! Async client facade and runner.
2//!
3//! This module establishes the TDLib-style host shape: cheap cloneable handle,
4//! single owner runner, bounded command queue, and broadcast committed events.
5//! Transport, sync, store, and transaction managers plug into this runner
6//! instead of being spread across bridges or agents.
7
8use std::{
9    sync::{
10        Arc, Mutex as StdMutex,
11        atomic::{AtomicBool, Ordering},
12    },
13    time::{Duration, SystemTime, UNIX_EPOCH},
14};
15
16use tokio::sync::{broadcast, mpsc, oneshot, watch};
17use tokio::task::{JoinHandle, JoinSet};
18
19use crate::backend::retry_after_seconds_from_message;
20use crate::{
21    AccountStateSnapshot, AddChatParticipantRequest, AuthStartRequest, AuthStartResult,
22    AuthVerifyRequest, AuthVerifyResult, BackendError, BackendResult, ChatParticipantsPage,
23    ChatParticipantsRequest, ChatStateSnapshot, ClientBackend, ClientErrorCategory, ClientEvent,
24    ClientFailure, ClientStatus, ClientStatusSnapshot, ConnectRequest, CreateDmRequest,
25    CreateReplyThreadRequest, CreateThreadRequest, CreatedChat, DeleteChatRequest,
26    DeleteMessageRequest, DialogsPage, DialogsRequest, EditMessageRequest, HistoryPage,
27    HistoryRequest, InMemoryBackend, InlineId, MessageMutation, OperationOutcome, ReactRequest,
28    ReadRequest, RemoveChatParticipantRequest, SendTextOutcome, SendTextRequest,
29    SetMarkedUnreadRequest, TypingRequest, UpdateChatInfoRequest, UpdateDialogNotificationsRequest,
30    UploadRequest,
31};
32
33/// Default bounded command queue capacity.
34pub const DEFAULT_COMMAND_QUEUE_CAPACITY: usize = 128;
35/// Default maximum number of concurrent backend requests.
36pub const DEFAULT_MAX_CONCURRENT_REQUESTS: usize = 32;
37
38/// Default broadcast event queue capacity.
39pub const DEFAULT_EVENT_QUEUE_CAPACITY: usize = 1024;
40/// Default bounded queue capacity for the optional single lossless consumer.
41pub const DEFAULT_LOSSLESS_EVENT_QUEUE_CAPACITY: usize = 4096;
42
43/// Reconnect/backoff policy for the long-lived backend event receiver.
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub struct ReconnectPolicy {
46    /// Delay before the first network/timeout retry.
47    pub initial_delay: Duration,
48    /// Maximum network/timeout retry delay.
49    pub max_delay: Duration,
50    /// Delay before the first rate-limit retry when no server hint is present.
51    pub rate_limit_initial_delay: Duration,
52    /// Maximum rate-limit retry delay.
53    pub rate_limit_max_delay: Duration,
54    /// Symmetric percentage of jitter applied to retry delays.
55    pub jitter_percent: u8,
56}
57
58impl Default for ReconnectPolicy {
59    fn default() -> Self {
60        Self {
61            initial_delay: Duration::from_secs(1),
62            max_delay: Duration::from_secs(60),
63            rate_limit_initial_delay: Duration::from_secs(30),
64            rate_limit_max_delay: Duration::from_secs(5 * 60),
65            jitter_percent: 20,
66        }
67    }
68}
69
70/// Errors returned by the async client handle before an operation reaches the backend.
71#[derive(Debug, thiserror::Error)]
72#[non_exhaustive]
73pub enum ClientCommandError {
74    /// The client runner has stopped accepting commands.
75    #[error("inline client runner is closed")]
76    Closed,
77
78    /// The client runner dropped a command response before completing it.
79    #[error("inline client runner dropped command response")]
80    ResponseDropped,
81}
82
83/// Errors returned by typed client operations.
84#[derive(Debug, thiserror::Error)]
85#[non_exhaustive]
86pub enum ClientRequestError {
87    /// The operation could not be delivered to the runner.
88    #[error(transparent)]
89    Command(#[from] ClientCommandError),
90
91    /// The backend rejected or failed the operation.
92    #[error(transparent)]
93    Backend(#[from] BackendError),
94}
95
96/// Builder for an [`InlineClient`] runtime.
97#[derive(Clone, Debug)]
98pub struct InlineClientBuilder {
99    command_queue_capacity: usize,
100    max_concurrent_requests: usize,
101    event_queue_capacity: usize,
102    lossless_event_queue_capacity: usize,
103    reconnect_policy: ReconnectPolicy,
104    initial_status: ClientStatus,
105    backend: Arc<dyn ClientBackend>,
106}
107
108impl InlineClientBuilder {
109    /// Sets the backend used by the runner.
110    pub fn backend(mut self, backend: impl ClientBackend) -> Self {
111        self.backend = Arc::new(backend);
112        self
113    }
114
115    /// Sets the shared backend used by the runner.
116    pub fn shared_backend(mut self, backend: Arc<dyn ClientBackend>) -> Self {
117        self.backend = backend;
118        self
119    }
120
121    /// Sets the bounded command queue capacity.
122    pub fn command_queue_capacity(mut self, capacity: usize) -> Self {
123        self.command_queue_capacity = capacity.max(1);
124        self
125    }
126
127    /// Sets the maximum number of backend requests allowed in flight.
128    pub fn max_concurrent_requests(mut self, maximum: usize) -> Self {
129        self.max_concurrent_requests = maximum.max(1);
130        self
131    }
132
133    /// Sets the broadcast event queue capacity.
134    pub fn event_queue_capacity(mut self, capacity: usize) -> Self {
135        self.event_queue_capacity = capacity.max(1);
136        self
137    }
138
139    /// Sets the bounded queue capacity for the optional lossless event consumer.
140    pub fn lossless_event_queue_capacity(mut self, capacity: usize) -> Self {
141        self.lossless_event_queue_capacity = capacity.max(1);
142        self
143    }
144
145    /// Sets event-stream reconnect and rate-limit backoff policy.
146    pub fn reconnect_policy(mut self, policy: ReconnectPolicy) -> Self {
147        self.reconnect_policy = policy;
148        self
149    }
150
151    /// Sets the initial client status.
152    pub fn initial_status(mut self, status: ClientStatus) -> Self {
153        self.initial_status = status;
154        self
155    }
156
157    /// Builds a client handle and runner pair.
158    pub fn build(self) -> InlineClientRuntime {
159        let (command_tx, command_rx) = mpsc::channel(self.command_queue_capacity);
160        let (event_tx, _) = broadcast::channel(self.event_queue_capacity);
161        let (lossless_event_tx, lossless_event_rx) =
162            mpsc::channel(self.lossless_event_queue_capacity);
163        let lossless_event_active = Arc::new(AtomicBool::new(false));
164        let (status_tx, status_rx) = watch::channel(self.initial_status);
165        let (failure_tx, failure_rx) = watch::channel(None);
166        let (signal_tx, signal_rx) = mpsc::channel(self.command_queue_capacity);
167
168        let client = InlineClient {
169            command_tx,
170            event_tx: event_tx.clone(),
171            lossless_event_rx: Arc::new(StdMutex::new(Some(lossless_event_rx))),
172            lossless_event_active: lossless_event_active.clone(),
173            status_rx,
174            failure_rx,
175        };
176        let event_emitter = ClientEventEmitter {
177            broadcast: event_tx,
178            lossless: lossless_event_tx,
179            lossless_active: lossless_event_active,
180        };
181        let runner = ClientRunner {
182            command_rx,
183            event_emitter,
184            status_tx,
185            failure_tx,
186            status: self.initial_status,
187            failure: None,
188            backend: self.backend,
189            signal_tx,
190            signal_rx,
191            event_task: None,
192            request_tasks: JoinSet::new(),
193            max_concurrent_requests: self.max_concurrent_requests,
194            reconnect_policy: self.reconnect_policy,
195        };
196
197        InlineClientRuntime { client, runner }
198    }
199}
200
201impl Default for InlineClientBuilder {
202    fn default() -> Self {
203        Self {
204            command_queue_capacity: DEFAULT_COMMAND_QUEUE_CAPACITY,
205            max_concurrent_requests: DEFAULT_MAX_CONCURRENT_REQUESTS,
206            event_queue_capacity: DEFAULT_EVENT_QUEUE_CAPACITY,
207            lossless_event_queue_capacity: DEFAULT_LOSSLESS_EVENT_QUEUE_CAPACITY,
208            reconnect_policy: ReconnectPolicy::default(),
209            initial_status: ClientStatus::Disconnected,
210            backend: Arc::new(InMemoryBackend::default()),
211        }
212    }
213}
214
215/// Built client runtime before the runner is hosted.
216#[derive(Debug)]
217pub struct InlineClientRuntime {
218    /// Cloneable client handle.
219    pub client: InlineClient,
220    /// Single-owner client runner.
221    pub runner: ClientRunner,
222}
223
224/// Single-consumer bounded stream for hosts that must not silently lose
225/// committed lossless client events.
226#[derive(Debug)]
227pub struct LosslessEventReceiver {
228    receiver: mpsc::Receiver<ClientEvent>,
229}
230
231impl LosslessEventReceiver {
232    /// Receives the next lossless event, or `None` after the client runner
233    /// closes.
234    pub async fn recv(&mut self) -> Option<ClientEvent> {
235        self.receiver.recv().await
236    }
237}
238
239impl InlineClientRuntime {
240    /// Splits the runtime into handle and runner.
241    pub fn split(self) -> (InlineClient, ClientRunner) {
242        (self.client, self.runner)
243    }
244
245    /// Spawns the runner on the current Tokio runtime and returns the handle.
246    pub fn spawn(self) -> InlineClient {
247        let (client, runner) = self.split();
248        tokio::spawn(runner.run());
249        client
250    }
251}
252
253/// Cheap cloneable handle for apps, bridges, agents, and tests.
254#[derive(Clone, Debug)]
255pub struct InlineClient {
256    command_tx: mpsc::Sender<ClientCommand>,
257    event_tx: broadcast::Sender<ClientEvent>,
258    lossless_event_rx: Arc<StdMutex<Option<mpsc::Receiver<ClientEvent>>>>,
259    lossless_event_active: Arc<AtomicBool>,
260    status_rx: watch::Receiver<ClientStatus>,
261    failure_rx: watch::Receiver<Option<ClientFailure>>,
262}
263
264impl InlineClient {
265    /// Creates a default client runtime builder.
266    pub fn builder() -> InlineClientBuilder {
267        InlineClientBuilder::default()
268    }
269
270    /// Returns the latest observed client status.
271    pub fn status(&self) -> ClientStatus {
272        *self.status_rx.borrow()
273    }
274
275    /// Returns the latest observed status snapshot.
276    pub fn status_snapshot(&self) -> ClientStatusSnapshot {
277        ClientStatusSnapshot {
278            status: *self.status_rx.borrow(),
279            failure: self.failure_rx.borrow().clone(),
280        }
281    }
282
283    /// Subscribes to committed client events.
284    pub fn subscribe(&self) -> broadcast::Receiver<ClientEvent> {
285        self.event_tx.subscribe()
286    }
287
288    /// Claims the single bounded lossless event stream.
289    ///
290    /// Once claimed, the runner applies backpressure before accepting more
291    /// work rather than dropping lossless events. Broadcast subscribers remain
292    /// independent and may still observe lag errors. Returns `None` after the
293    /// lossless stream has already been claimed.
294    pub fn take_lossless_events(&self) -> Option<LosslessEventReceiver> {
295        let receiver = self
296            .lossless_event_rx
297            .lock()
298            .expect("lossless event receiver poisoned")
299            .take()?;
300        self.lossless_event_active.store(true, Ordering::Release);
301        Some(LosslessEventReceiver { receiver })
302    }
303
304    /// Sends an Inline login code.
305    pub async fn auth_start(
306        &self,
307        request: AuthStartRequest,
308    ) -> Result<AuthStartResult, ClientRequestError> {
309        match self.request(ClientRequest::AuthStart(request)).await? {
310            ClientResponse::AuthStart(result) => Ok(result),
311            other => unreachable!("auth_start returned {other:?}"),
312        }
313    }
314
315    /// Verifies an Inline login code and persists the resulting session.
316    pub async fn auth_verify(
317        &self,
318        request: AuthVerifyRequest,
319    ) -> Result<AuthVerifyResult, ClientRequestError> {
320        match self.request(ClientRequest::AuthVerify(request)).await? {
321            ClientResponse::AuthVerify(result) => Ok(result),
322            other => unreachable!("auth_verify returned {other:?}"),
323        }
324    }
325
326    /// Resumes a previously stored session, if available.
327    pub async fn resume_session(&self) -> Result<ClientStatusSnapshot, ClientRequestError> {
328        match self.request(ClientRequest::Resume).await? {
329            ClientResponse::Status(status) => Ok(status),
330            other => unreachable!("resume_session returned {other:?}"),
331        }
332    }
333
334    /// Connects or reconnects the client.
335    pub async fn connect(
336        &self,
337        request: ConnectRequest,
338    ) -> Result<ClientStatusSnapshot, ClientRequestError> {
339        match self.request(ClientRequest::Connect(request)).await? {
340            ClientResponse::Status(status) => Ok(status),
341            other => unreachable!("connect returned {other:?}"),
342        }
343    }
344
345    /// Logs out the current account.
346    pub async fn logout(&self) -> Result<(), ClientRequestError> {
347        match self.request(ClientRequest::Logout).await? {
348            ClientResponse::Empty => Ok(()),
349            other => unreachable!("logout returned {other:?}"),
350        }
351    }
352
353    /// Lists dialogs.
354    pub async fn dialogs(
355        &self,
356        request: DialogsRequest,
357    ) -> Result<DialogsPage, ClientRequestError> {
358        match self.request(ClientRequest::Dialogs(request)).await? {
359            ClientResponse::Dialogs(page) => Ok(page),
360            other => unreachable!("dialogs returned {other:?}"),
361        }
362    }
363
364    /// Lists only durable cached dialogs without making a network request.
365    pub async fn cached_dialogs(
366        &self,
367        request: DialogsRequest,
368    ) -> Result<DialogsPage, ClientRequestError> {
369        match self.request(ClientRequest::CachedDialogs(request)).await? {
370            ClientResponse::Dialogs(page) => Ok(page),
371            other => unreachable!("cached_dialogs returned {other:?}"),
372        }
373    }
374
375    /// Loads account-level durable state for recovery after a consumer event
376    /// stream retention gap.
377    pub async fn account_state(&self) -> Result<AccountStateSnapshot, ClientRequestError> {
378        match self.request(ClientRequest::AccountState).await? {
379            ClientResponse::AccountState(snapshot) => Ok(snapshot),
380            other => unreachable!("account_state returned {other:?}"),
381        }
382    }
383
384    /// Loads durable state for one chat. Current messages remain paged through
385    /// [`Self::history`] so large accounts do not require an unbounded snapshot.
386    pub async fn chat_state(
387        &self,
388        chat_id: InlineId,
389    ) -> Result<ChatStateSnapshot, ClientRequestError> {
390        match self.request(ClientRequest::ChatState(chat_id)).await? {
391            ClientResponse::ChatState(snapshot) => Ok(*snapshot),
392            other => unreachable!("chat_state returned {other:?}"),
393        }
394    }
395
396    /// Fetches chat history.
397    pub async fn history(
398        &self,
399        request: HistoryRequest,
400    ) -> Result<HistoryPage, ClientRequestError> {
401        match self.request(ClientRequest::History(request)).await? {
402            ClientResponse::History(page) => Ok(page),
403            other => unreachable!("history returned {other:?}"),
404        }
405    }
406
407    /// Fetches only durable locally cached history without a network request.
408    pub async fn cached_history(
409        &self,
410        request: HistoryRequest,
411    ) -> Result<HistoryPage, ClientRequestError> {
412        match self.request(ClientRequest::CachedHistory(request)).await? {
413            ClientResponse::History(page) => Ok(page),
414            other => unreachable!("cached_history returned {other:?}"),
415        }
416    }
417
418    /// Fetches chat participants.
419    pub async fn chat_participants(
420        &self,
421        request: ChatParticipantsRequest,
422    ) -> Result<ChatParticipantsPage, ClientRequestError> {
423        match self
424            .request(ClientRequest::ChatParticipants(request))
425            .await?
426        {
427            ClientResponse::ChatParticipants(page) => Ok(page),
428            other => unreachable!("chat_participants returned {other:?}"),
429        }
430    }
431
432    /// Adds a user to an Inline chat.
433    pub async fn add_chat_participant(
434        &self,
435        request: AddChatParticipantRequest,
436    ) -> Result<(), ClientRequestError> {
437        match self
438            .request(ClientRequest::AddChatParticipant(request))
439            .await?
440        {
441            ClientResponse::Empty => Ok(()),
442            other => unreachable!("add_chat_participant returned {other:?}"),
443        }
444    }
445
446    /// Removes a user from an Inline chat.
447    pub async fn remove_chat_participant(
448        &self,
449        request: RemoveChatParticipantRequest,
450    ) -> Result<(), ClientRequestError> {
451        match self
452            .request(ClientRequest::RemoveChatParticipant(request))
453            .await?
454        {
455            ClientResponse::Empty => Ok(()),
456            other => unreachable!("remove_chat_participant returned {other:?}"),
457        }
458    }
459
460    /// Updates mutable Inline chat metadata.
461    pub async fn update_chat_info(
462        &self,
463        request: UpdateChatInfoRequest,
464    ) -> Result<(), ClientRequestError> {
465        match self.request(ClientRequest::UpdateChatInfo(request)).await? {
466            ClientResponse::Empty => Ok(()),
467            other => unreachable!("update_chat_info returned {other:?}"),
468        }
469    }
470
471    /// Deletes an Inline chat when permitted by the service.
472    pub async fn delete_chat(&self, request: DeleteChatRequest) -> Result<(), ClientRequestError> {
473        match self.request(ClientRequest::DeleteChat(request)).await? {
474            ClientResponse::Empty => Ok(()),
475            other => unreachable!("delete_chat returned {other:?}"),
476        }
477    }
478
479    /// Creates or opens a direct message chat.
480    pub async fn create_dm(
481        &self,
482        request: CreateDmRequest,
483    ) -> Result<CreatedChat, ClientRequestError> {
484        match self.request(ClientRequest::CreateDm(request)).await? {
485            ClientResponse::CreatedChat(chat) => Ok(chat),
486            other => unreachable!("create_dm returned {other:?}"),
487        }
488    }
489
490    /// Creates a regular Inline thread chat.
491    pub async fn create_thread(
492        &self,
493        request: CreateThreadRequest,
494    ) -> Result<CreatedChat, ClientRequestError> {
495        match self.request(ClientRequest::CreateThread(request)).await? {
496            ClientResponse::CreatedChat(chat) => Ok(chat),
497            other => unreachable!("create_thread returned {other:?}"),
498        }
499    }
500
501    /// Creates a child/reply Inline thread chat.
502    pub async fn create_reply_thread(
503        &self,
504        request: CreateReplyThreadRequest,
505    ) -> Result<CreatedChat, ClientRequestError> {
506        match self
507            .request(ClientRequest::CreateReplyThread(request))
508            .await?
509        {
510            ClientResponse::CreatedChat(chat) => Ok(chat),
511            other => unreachable!("create_reply_thread returned {other:?}"),
512        }
513    }
514
515    /// Sends a text message.
516    pub async fn send_text(
517        &self,
518        request: SendTextRequest,
519    ) -> Result<MessageMutation, ClientRequestError> {
520        match self.request(ClientRequest::SendText(request)).await? {
521            ClientResponse::Message(mutation) => Ok(mutation),
522            other => unreachable!("send_text returned {other:?}"),
523        }
524    }
525
526    /// Uploads and sends a media message.
527    pub async fn send_media(
528        &self,
529        request: UploadRequest,
530        bytes: Vec<u8>,
531    ) -> Result<MessageMutation, ClientRequestError> {
532        match self
533            .request(ClientRequest::SendMedia { request, bytes })
534            .await?
535        {
536            ClientResponse::Message(mutation) => Ok(mutation),
537            other => unreachable!("send_media returned {other:?}"),
538        }
539    }
540
541    /// Edits a text message.
542    pub async fn edit_message(
543        &self,
544        request: EditMessageRequest,
545    ) -> Result<(), ClientRequestError> {
546        match self.request(ClientRequest::EditMessage(request)).await? {
547            ClientResponse::Empty => Ok(()),
548            other => unreachable!("edit_message returned {other:?}"),
549        }
550    }
551
552    /// Deletes or unsends a message.
553    pub async fn delete_message(
554        &self,
555        request: DeleteMessageRequest,
556    ) -> Result<(), ClientRequestError> {
557        match self.request(ClientRequest::DeleteMessage(request)).await? {
558            ClientResponse::Empty => Ok(()),
559            other => unreachable!("delete_message returned {other:?}"),
560        }
561    }
562
563    /// Adds or removes a reaction.
564    pub async fn react(&self, request: ReactRequest) -> Result<(), ClientRequestError> {
565        match self.request(ClientRequest::React(request)).await? {
566            ClientResponse::Empty => Ok(()),
567            other => unreachable!("react returned {other:?}"),
568        }
569    }
570
571    /// Marks messages read.
572    pub async fn read(&self, request: ReadRequest) -> Result<(), ClientRequestError> {
573        match self.request(ClientRequest::Read(request)).await? {
574            ClientResponse::Empty => Ok(()),
575            other => unreachable!("read returned {other:?}"),
576        }
577    }
578
579    /// Sets the explicit marked-unread state for a chat.
580    pub async fn set_marked_unread(
581        &self,
582        request: SetMarkedUnreadRequest,
583    ) -> Result<(), ClientRequestError> {
584        match self
585            .request(ClientRequest::SetMarkedUnread(request))
586            .await?
587        {
588            ClientResponse::Empty => Ok(()),
589            other => unreachable!("set_marked_unread returned {other:?}"),
590        }
591    }
592
593    /// Sets or clears a per-dialog notification override.
594    pub async fn update_dialog_notifications(
595        &self,
596        request: UpdateDialogNotificationsRequest,
597    ) -> Result<(), ClientRequestError> {
598        match self
599            .request(ClientRequest::UpdateDialogNotifications(request))
600            .await?
601        {
602            ClientResponse::Empty => Ok(()),
603            other => unreachable!("update_dialog_notifications returned {other:?}"),
604        }
605    }
606
607    /// Sends a typing state.
608    pub async fn typing(&self, request: TypingRequest) -> Result<(), ClientRequestError> {
609        match self.request(ClientRequest::Typing(request)).await? {
610            ClientResponse::Empty => Ok(()),
611            other => unreachable!("typing returned {other:?}"),
612        }
613    }
614
615    /// Updates status through the runner.
616    ///
617    /// This is public so early hosts and tests can exercise the event/status
618    /// pipeline before the real transport manager is wired in. Future transport
619    /// code should call the same internal command path.
620    pub async fn set_status(
621        &self,
622        status: ClientStatus,
623        failure: Option<ClientFailure>,
624    ) -> Result<(), ClientCommandError> {
625        let (respond_to, response) = oneshot::channel();
626        self.command_tx
627            .send(ClientCommand::SetStatus {
628                status,
629                failure,
630                respond_to,
631            })
632            .await
633            .map_err(|_| ClientCommandError::Closed)?;
634        response
635            .await
636            .map_err(|_| ClientCommandError::ResponseDropped)
637    }
638
639    /// Requests runner shutdown.
640    pub async fn shutdown(&self) -> Result<(), ClientCommandError> {
641        let (respond_to, response) = oneshot::channel();
642        self.command_tx
643            .send(ClientCommand::Shutdown { respond_to })
644            .await
645            .map_err(|_| ClientCommandError::Closed)?;
646        response
647            .await
648            .map_err(|_| ClientCommandError::ResponseDropped)
649    }
650
651    async fn request(&self, request: ClientRequest) -> Result<ClientResponse, ClientRequestError> {
652        let (respond_to, response) = oneshot::channel();
653        self.command_tx
654            .send(ClientCommand::Request {
655                request: Box::new(request),
656                respond_to,
657            })
658            .await
659            .map_err(|_| ClientCommandError::Closed)?;
660        response
661            .await
662            .map_err(|_| ClientCommandError::ResponseDropped)?
663            .map_err(ClientRequestError::Backend)
664    }
665}
666
667/// Single-owner async client runner.
668#[derive(Debug)]
669pub struct ClientRunner {
670    command_rx: mpsc::Receiver<ClientCommand>,
671    event_emitter: ClientEventEmitter,
672    status_tx: watch::Sender<ClientStatus>,
673    failure_tx: watch::Sender<Option<ClientFailure>>,
674    status: ClientStatus,
675    failure: Option<ClientFailure>,
676    backend: Arc<dyn ClientBackend>,
677    signal_tx: mpsc::Sender<RunnerSignal>,
678    signal_rx: mpsc::Receiver<RunnerSignal>,
679    event_task: Option<JoinHandle<()>>,
680    request_tasks: JoinSet<()>,
681    max_concurrent_requests: usize,
682    reconnect_policy: ReconnectPolicy,
683}
684
685#[derive(Clone, Debug)]
686struct ClientEventEmitter {
687    broadcast: broadcast::Sender<ClientEvent>,
688    lossless: mpsc::Sender<ClientEvent>,
689    lossless_active: Arc<AtomicBool>,
690}
691
692impl ClientEventEmitter {
693    async fn emit(&self, event: ClientEvent) {
694        let _ = self.broadcast.send(event.clone());
695        if event.reliability() == crate::EventReliability::Lossless
696            && self.lossless_active.load(Ordering::Acquire)
697            && self.lossless.send(event).await.is_err()
698        {
699            self.lossless_active.store(false, Ordering::Release);
700        }
701    }
702
703    async fn emit_operation_events(&self, outcome: OperationOutcome) {
704        for event in outcome.events {
705            self.emit(event).await;
706        }
707    }
708
709    async fn emit_send_outcome(&self, outcome: SendTextOutcome) -> MessageMutation {
710        let transaction = outcome.transaction_event();
711        let message_id = outcome.message_id;
712        let chat_id = outcome.chat_id;
713        let message = outcome.message;
714        let mut mutation = outcome.mutation;
715        mutation.state = Some(outcome.state);
716        mutation.failure = outcome.failure.clone();
717        self.emit(ClientEvent::TransactionChanged(transaction))
718            .await;
719        if let Some(message_id) = message_id {
720            self.emit(ClientEvent::MessageUpserted {
721                chat_id,
722                message_id,
723            })
724            .await;
725        }
726        if let Some(message) = message {
727            self.emit(ClientEvent::MessageStored { message }).await;
728        }
729        mutation
730    }
731}
732
733impl ClientRunner {
734    /// Runs the client command loop until shutdown or all handles are dropped.
735    pub async fn run(mut self) {
736        log::debug!("inline client runner started");
737        loop {
738            tokio::select! {
739                command = self.command_rx.recv(), if self.request_tasks.len() < self.max_concurrent_requests => {
740                    if !self.handle_optional_command(command).await {
741                        break;
742                    }
743                }
744                signal = self.signal_rx.recv() => {
745                    if let Some(signal) = signal {
746                        self.handle_signal(signal).await;
747                    }
748                }
749                completed = self.request_tasks.join_next(), if !self.request_tasks.is_empty() => {
750                    if let Some(Err(error)) = completed {
751                        log::error!("inline client request task failed: {error}");
752                    }
753                }
754            }
755        }
756        self.stop_event_receiver();
757        log::debug!("inline client runner stopped");
758    }
759
760    async fn handle_optional_command(&mut self, command: Option<ClientCommand>) -> bool {
761        let Some(command) = command else {
762            return false;
763        };
764        self.handle_command(command).await
765    }
766
767    async fn handle_command(&mut self, command: ClientCommand) -> bool {
768        match command {
769            ClientCommand::Request {
770                request,
771                respond_to,
772            } => {
773                if request.can_run_concurrently() {
774                    self.spawn_concurrent_request(*request, respond_to);
775                    return true;
776                }
777                self.finish_concurrent_requests().await;
778                let response = self.handle_request(*request).await;
779                let _ = respond_to.send(response);
780                true
781            }
782            ClientCommand::SetStatus {
783                status,
784                failure,
785                respond_to,
786            } => {
787                self.update_status(status, failure).await;
788                let _ = respond_to.send(());
789                true
790            }
791            ClientCommand::Shutdown { respond_to } => {
792                log::debug!("inline client runner shutdown requested");
793                self.request_tasks.abort_all();
794                self.update_status(ClientStatus::ShuttingDown, None).await;
795                let _ = respond_to.send(());
796                false
797            }
798        }
799    }
800
801    const fn should_receive_events(&self) -> bool {
802        matches!(
803            self.status,
804            ClientStatus::Connected | ClientStatus::Reconnecting
805        )
806    }
807
808    async fn emit_received_events(&self, events: Vec<ClientEvent>) {
809        for event in events {
810            self.event_emitter.emit(event).await;
811        }
812    }
813
814    async fn handle_signal(&mut self, signal: RunnerSignal) {
815        match signal {
816            RunnerSignal::Events(events) => {
817                if self.status == ClientStatus::Reconnecting {
818                    self.update_status(ClientStatus::Connected, None).await;
819                }
820                self.emit_received_events(events).await;
821            }
822            RunnerSignal::ReceiveError(error) => {
823                self.update_status_for_backend_error(&error).await;
824            }
825        }
826    }
827
828    async fn handle_request(&mut self, request: ClientRequest) -> BackendResult<ClientResponse> {
829        log::debug!("handling inline client request: {}", request.kind());
830        match request {
831            ClientRequest::AuthStart(auth) => self
832                .backend
833                .auth_start(auth)
834                .await
835                .map(ClientResponse::AuthStart),
836            ClientRequest::AuthVerify(auth) => {
837                self.update_status(ClientStatus::Connecting, None).await;
838                match self.backend.auth_verify(auth).await {
839                    Ok(result) => {
840                        let status = result.status.clone();
841                        self.update_status(status.status, status.failure.clone())
842                            .await;
843                        Ok(ClientResponse::AuthVerify(result))
844                    }
845                    Err(error) => {
846                        self.update_status_for_backend_error(&error).await;
847                        Err(error)
848                    }
849                }
850            }
851            ClientRequest::Resume => {
852                self.update_status(ClientStatus::Connecting, None).await;
853                match self.backend.resume_session().await {
854                    Ok(status) => {
855                        self.update_status(status.status, status.failure.clone())
856                            .await;
857                        Ok(ClientResponse::Status(status))
858                    }
859                    Err(error) => {
860                        self.update_status_for_backend_error(&error).await;
861                        Err(error)
862                    }
863                }
864            }
865            ClientRequest::Connect(connect) => {
866                self.update_status(ClientStatus::Connecting, None).await;
867                match self.backend.connect(connect).await {
868                    Ok(status) => {
869                        self.update_status(status.status, status.failure.clone())
870                            .await;
871                        Ok(ClientResponse::Status(status))
872                    }
873                    Err(error) => {
874                        self.update_status_for_backend_error(&error).await;
875                        Err(error)
876                    }
877                }
878            }
879            ClientRequest::Logout => {
880                self.backend.logout().await?;
881                self.update_status(ClientStatus::LoggedOut, None).await;
882                Ok(ClientResponse::Empty)
883            }
884            ClientRequest::Dialogs(dialogs) => self
885                .backend
886                .dialogs(dialogs)
887                .await
888                .map(ClientResponse::Dialogs),
889            ClientRequest::CachedDialogs(dialogs) => self
890                .backend
891                .cached_dialogs(dialogs)
892                .await
893                .map(ClientResponse::Dialogs),
894            ClientRequest::AccountState => self
895                .backend
896                .account_state()
897                .await
898                .map(ClientResponse::AccountState),
899            ClientRequest::ChatState(chat_id) => self
900                .backend
901                .chat_state(chat_id)
902                .await
903                .map(Box::new)
904                .map(ClientResponse::ChatState),
905            ClientRequest::History(history) => self
906                .backend
907                .history(history)
908                .await
909                .map(ClientResponse::History),
910            ClientRequest::CachedHistory(history) => self
911                .backend
912                .cached_history(history)
913                .await
914                .map(ClientResponse::History),
915            ClientRequest::ChatParticipants(participants) => self
916                .backend
917                .chat_participants(participants)
918                .await
919                .map(ClientResponse::ChatParticipants),
920            ClientRequest::AddChatParticipant(request) => {
921                let outcome = self.backend.add_chat_participant(request).await?;
922                self.event_emitter.emit_operation_events(outcome).await;
923                Ok(ClientResponse::Empty)
924            }
925            ClientRequest::RemoveChatParticipant(request) => {
926                let outcome = self.backend.remove_chat_participant(request).await?;
927                self.event_emitter.emit_operation_events(outcome).await;
928                Ok(ClientResponse::Empty)
929            }
930            ClientRequest::UpdateChatInfo(request) => {
931                let outcome = self.backend.update_chat_info(request).await?;
932                self.event_emitter.emit_operation_events(outcome).await;
933                Ok(ClientResponse::Empty)
934            }
935            ClientRequest::DeleteChat(request) => {
936                let outcome = self.backend.delete_chat(request).await?;
937                self.event_emitter.emit_operation_events(outcome).await;
938                Ok(ClientResponse::Empty)
939            }
940            ClientRequest::CreateDm(request) => self
941                .backend
942                .create_dm(request)
943                .await
944                .map(ClientResponse::CreatedChat),
945            ClientRequest::CreateThread(request) => self
946                .backend
947                .create_thread(request)
948                .await
949                .map(ClientResponse::CreatedChat),
950            ClientRequest::CreateReplyThread(request) => self
951                .backend
952                .create_reply_thread(request)
953                .await
954                .map(ClientResponse::CreatedChat),
955            ClientRequest::SendText(send) => {
956                let outcome = self.backend.send_text(send).await?;
957                Ok(ClientResponse::Message(
958                    self.event_emitter.emit_send_outcome(outcome).await,
959                ))
960            }
961            ClientRequest::SendMedia { request, bytes } => {
962                let outcome = self.backend.send_media(request, bytes).await?;
963                Ok(ClientResponse::Message(
964                    self.event_emitter.emit_send_outcome(outcome).await,
965                ))
966            }
967            ClientRequest::EditMessage(edit) => {
968                let outcome = self.backend.edit_message(edit).await?;
969                self.event_emitter.emit_operation_events(outcome).await;
970                Ok(ClientResponse::Empty)
971            }
972            ClientRequest::DeleteMessage(delete) => {
973                let outcome = self.backend.delete_message(delete).await?;
974                self.event_emitter.emit_operation_events(outcome).await;
975                Ok(ClientResponse::Empty)
976            }
977            ClientRequest::React(react) => {
978                let outcome = self.backend.react(react).await?;
979                self.event_emitter.emit_operation_events(outcome).await;
980                Ok(ClientResponse::Empty)
981            }
982            ClientRequest::Read(read) => {
983                let outcome = self.backend.read(read).await?;
984                self.event_emitter.emit_operation_events(outcome).await;
985                Ok(ClientResponse::Empty)
986            }
987            ClientRequest::SetMarkedUnread(request) => {
988                let outcome = self.backend.set_marked_unread(request).await?;
989                self.event_emitter.emit_operation_events(outcome).await;
990                Ok(ClientResponse::Empty)
991            }
992            ClientRequest::UpdateDialogNotifications(request) => {
993                let outcome = self.backend.update_dialog_notifications(request).await?;
994                self.event_emitter.emit_operation_events(outcome).await;
995                Ok(ClientResponse::Empty)
996            }
997            ClientRequest::Typing(typing) => {
998                let outcome = self.backend.typing(typing).await?;
999                self.event_emitter.emit_operation_events(outcome).await;
1000                Ok(ClientResponse::Empty)
1001            }
1002        }
1003    }
1004
1005    async fn update_status_for_backend_error(&mut self, error: &BackendError) {
1006        let status = match error.category {
1007            ClientErrorCategory::AuthRequired => ClientStatus::AuthRequired,
1008            ClientErrorCategory::AuthExpired => ClientStatus::AuthExpired,
1009            ClientErrorCategory::Network
1010            | ClientErrorCategory::Timeout
1011            | ClientErrorCategory::RateLimited => ClientStatus::Reconnecting,
1012            _ => ClientStatus::Disconnected,
1013        };
1014        self.update_status(
1015            status,
1016            Some(ClientFailure::new(error.category, error.message.clone())),
1017        )
1018        .await;
1019    }
1020
1021    async fn update_status(&mut self, status: ClientStatus, failure: Option<ClientFailure>) {
1022        log::debug!("inline client status changed: {status:?}");
1023        self.status = status;
1024        self.failure = failure.clone();
1025        let _ = self.status_tx.send(status);
1026        let _ = self.failure_tx.send(failure.clone());
1027        self.event_emitter
1028            .emit(ClientEvent::StatusChanged { status, failure })
1029            .await;
1030        self.sync_event_receiver_to_status();
1031    }
1032
1033    fn spawn_concurrent_request(
1034        &mut self,
1035        request: ClientRequest,
1036        respond_to: oneshot::Sender<BackendResult<ClientResponse>>,
1037    ) {
1038        let backend = self.backend.clone();
1039        let event_emitter = self.event_emitter.clone();
1040        self.request_tasks.spawn(async move {
1041            let response = handle_concurrent_request(backend, event_emitter, request).await;
1042            let _ = respond_to.send(response);
1043        });
1044    }
1045
1046    async fn finish_concurrent_requests(&mut self) {
1047        while let Some(completed) = self.request_tasks.join_next().await {
1048            if let Err(error) = completed {
1049                log::error!("inline client request task failed: {error}");
1050            }
1051        }
1052    }
1053
1054    fn sync_event_receiver_to_status(&mut self) {
1055        if self.should_receive_events() {
1056            self.start_event_receiver();
1057        } else {
1058            self.stop_event_receiver();
1059        }
1060    }
1061
1062    fn start_event_receiver(&mut self) {
1063        if self.event_task.is_some() {
1064            return;
1065        }
1066        let backend = self.backend.clone();
1067        let signal_tx = self.signal_tx.clone();
1068        let reconnect_policy = self.reconnect_policy;
1069        self.event_task = Some(tokio::spawn(run_backend_event_receiver(
1070            backend,
1071            signal_tx,
1072            reconnect_policy,
1073        )));
1074    }
1075
1076    fn stop_event_receiver(&mut self) {
1077        if let Some(task) = self.event_task.take() {
1078            task.abort();
1079        }
1080    }
1081}
1082
1083async fn handle_concurrent_request(
1084    backend: Arc<dyn ClientBackend>,
1085    events: ClientEventEmitter,
1086    request: ClientRequest,
1087) -> BackendResult<ClientResponse> {
1088    log::debug!(
1089        "handling concurrent inline client request: {}",
1090        request.kind()
1091    );
1092    match request {
1093        ClientRequest::Dialogs(request) => {
1094            backend.dialogs(request).await.map(ClientResponse::Dialogs)
1095        }
1096        ClientRequest::CachedDialogs(request) => backend
1097            .cached_dialogs(request)
1098            .await
1099            .map(ClientResponse::Dialogs),
1100        ClientRequest::AccountState => backend
1101            .account_state()
1102            .await
1103            .map(ClientResponse::AccountState),
1104        ClientRequest::ChatState(chat_id) => backend
1105            .chat_state(chat_id)
1106            .await
1107            .map(Box::new)
1108            .map(ClientResponse::ChatState),
1109        ClientRequest::History(request) => {
1110            backend.history(request).await.map(ClientResponse::History)
1111        }
1112        ClientRequest::CachedHistory(request) => backend
1113            .cached_history(request)
1114            .await
1115            .map(ClientResponse::History),
1116        ClientRequest::ChatParticipants(request) => backend
1117            .chat_participants(request)
1118            .await
1119            .map(ClientResponse::ChatParticipants),
1120        ClientRequest::AddChatParticipant(request) => {
1121            events
1122                .emit_operation_events(backend.add_chat_participant(request).await?)
1123                .await;
1124            Ok(ClientResponse::Empty)
1125        }
1126        ClientRequest::RemoveChatParticipant(request) => {
1127            events
1128                .emit_operation_events(backend.remove_chat_participant(request).await?)
1129                .await;
1130            Ok(ClientResponse::Empty)
1131        }
1132        ClientRequest::UpdateChatInfo(request) => {
1133            events
1134                .emit_operation_events(backend.update_chat_info(request).await?)
1135                .await;
1136            Ok(ClientResponse::Empty)
1137        }
1138        ClientRequest::DeleteChat(request) => {
1139            events
1140                .emit_operation_events(backend.delete_chat(request).await?)
1141                .await;
1142            Ok(ClientResponse::Empty)
1143        }
1144        ClientRequest::CreateDm(request) => backend
1145            .create_dm(request)
1146            .await
1147            .map(ClientResponse::CreatedChat),
1148        ClientRequest::CreateThread(request) => backend
1149            .create_thread(request)
1150            .await
1151            .map(ClientResponse::CreatedChat),
1152        ClientRequest::CreateReplyThread(request) => backend
1153            .create_reply_thread(request)
1154            .await
1155            .map(ClientResponse::CreatedChat),
1156        ClientRequest::SendText(request) => {
1157            let outcome = backend.send_text(request).await?;
1158            Ok(ClientResponse::Message(
1159                events.emit_send_outcome(outcome).await,
1160            ))
1161        }
1162        ClientRequest::SendMedia { request, bytes } => {
1163            let outcome = backend.send_media(request, bytes).await?;
1164            Ok(ClientResponse::Message(
1165                events.emit_send_outcome(outcome).await,
1166            ))
1167        }
1168        ClientRequest::EditMessage(request) => {
1169            events
1170                .emit_operation_events(backend.edit_message(request).await?)
1171                .await;
1172            Ok(ClientResponse::Empty)
1173        }
1174        ClientRequest::DeleteMessage(request) => {
1175            events
1176                .emit_operation_events(backend.delete_message(request).await?)
1177                .await;
1178            Ok(ClientResponse::Empty)
1179        }
1180        ClientRequest::React(request) => {
1181            events
1182                .emit_operation_events(backend.react(request).await?)
1183                .await;
1184            Ok(ClientResponse::Empty)
1185        }
1186        ClientRequest::Read(request) => {
1187            events
1188                .emit_operation_events(backend.read(request).await?)
1189                .await;
1190            Ok(ClientResponse::Empty)
1191        }
1192        ClientRequest::SetMarkedUnread(request) => {
1193            events
1194                .emit_operation_events(backend.set_marked_unread(request).await?)
1195                .await;
1196            Ok(ClientResponse::Empty)
1197        }
1198        ClientRequest::UpdateDialogNotifications(request) => {
1199            events
1200                .emit_operation_events(backend.update_dialog_notifications(request).await?)
1201                .await;
1202            Ok(ClientResponse::Empty)
1203        }
1204        ClientRequest::Typing(request) => {
1205            events
1206                .emit_operation_events(backend.typing(request).await?)
1207                .await;
1208            Ok(ClientResponse::Empty)
1209        }
1210        ClientRequest::AuthStart(_)
1211        | ClientRequest::AuthVerify(_)
1212        | ClientRequest::Resume
1213        | ClientRequest::Connect(_)
1214        | ClientRequest::Logout => unreachable!("session request was spawned concurrently"),
1215    }
1216}
1217
1218async fn run_backend_event_receiver(
1219    backend: Arc<dyn ClientBackend>,
1220    signal_tx: mpsc::Sender<RunnerSignal>,
1221    reconnect_policy: ReconnectPolicy,
1222) {
1223    let mut retry_attempt = 0_u32;
1224    loop {
1225        match backend.receive_events().await {
1226            Ok(events) => {
1227                retry_attempt = 0;
1228                if signal_tx.send(RunnerSignal::Events(events)).await.is_err() {
1229                    break;
1230                }
1231            }
1232            Err(error) => {
1233                let retry = should_retry_event_receive(&error);
1234                let delay = event_retry_delay(&error, retry_attempt, reconnect_policy);
1235                if signal_tx
1236                    .send(RunnerSignal::ReceiveError(error))
1237                    .await
1238                    .is_err()
1239                {
1240                    break;
1241                }
1242                if !retry {
1243                    break;
1244                }
1245                retry_attempt = retry_attempt.saturating_add(1);
1246                tokio::time::sleep(delay).await;
1247            }
1248        }
1249    }
1250}
1251
1252enum ClientCommand {
1253    Request {
1254        request: Box<ClientRequest>,
1255        respond_to: oneshot::Sender<BackendResult<ClientResponse>>,
1256    },
1257    SetStatus {
1258        status: ClientStatus,
1259        failure: Option<ClientFailure>,
1260        respond_to: oneshot::Sender<()>,
1261    },
1262    Shutdown {
1263        respond_to: oneshot::Sender<()>,
1264    },
1265}
1266
1267#[derive(Debug)]
1268enum RunnerSignal {
1269    Events(Vec<ClientEvent>),
1270    ReceiveError(BackendError),
1271}
1272
1273impl std::fmt::Debug for ClientCommand {
1274    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1275        match self {
1276            Self::Request { request, .. } => f
1277                .debug_struct("Request")
1278                .field("request", request)
1279                .finish_non_exhaustive(),
1280            Self::SetStatus {
1281                status, failure, ..
1282            } => f
1283                .debug_struct("SetStatus")
1284                .field("status", status)
1285                .field("failure", failure)
1286                .finish_non_exhaustive(),
1287            Self::Shutdown { .. } => f.debug_struct("Shutdown").finish_non_exhaustive(),
1288        }
1289    }
1290}
1291
1292#[derive(Debug)]
1293enum ClientRequest {
1294    AuthStart(AuthStartRequest),
1295    AuthVerify(AuthVerifyRequest),
1296    Resume,
1297    Connect(ConnectRequest),
1298    Logout,
1299    Dialogs(DialogsRequest),
1300    CachedDialogs(DialogsRequest),
1301    AccountState,
1302    ChatState(InlineId),
1303    History(HistoryRequest),
1304    CachedHistory(HistoryRequest),
1305    ChatParticipants(ChatParticipantsRequest),
1306    AddChatParticipant(AddChatParticipantRequest),
1307    RemoveChatParticipant(RemoveChatParticipantRequest),
1308    UpdateChatInfo(UpdateChatInfoRequest),
1309    DeleteChat(DeleteChatRequest),
1310    CreateDm(CreateDmRequest),
1311    CreateThread(CreateThreadRequest),
1312    CreateReplyThread(CreateReplyThreadRequest),
1313    SendText(SendTextRequest),
1314    SendMedia {
1315        request: UploadRequest,
1316        bytes: Vec<u8>,
1317    },
1318    EditMessage(EditMessageRequest),
1319    DeleteMessage(DeleteMessageRequest),
1320    React(ReactRequest),
1321    Read(ReadRequest),
1322    SetMarkedUnread(SetMarkedUnreadRequest),
1323    UpdateDialogNotifications(UpdateDialogNotificationsRequest),
1324    Typing(TypingRequest),
1325}
1326
1327impl ClientRequest {
1328    const fn can_run_concurrently(&self) -> bool {
1329        !matches!(
1330            self,
1331            Self::AuthStart(_)
1332                | Self::AuthVerify(_)
1333                | Self::Resume
1334                | Self::Connect(_)
1335                | Self::Logout
1336        )
1337    }
1338
1339    const fn kind(&self) -> &'static str {
1340        match self {
1341            Self::AuthStart(_) => "auth_start",
1342            Self::AuthVerify(_) => "auth_verify",
1343            Self::Resume => "resume",
1344            Self::Connect(_) => "connect",
1345            Self::Logout => "logout",
1346            Self::Dialogs(_) => "dialogs",
1347            Self::CachedDialogs(_) => "cached_dialogs",
1348            Self::AccountState => "account_state",
1349            Self::ChatState(_) => "chat_state",
1350            Self::History(_) => "history",
1351            Self::CachedHistory(_) => "cached_history",
1352            Self::ChatParticipants(_) => "chat_participants",
1353            Self::AddChatParticipant(_) => "add_chat_participant",
1354            Self::RemoveChatParticipant(_) => "remove_chat_participant",
1355            Self::UpdateChatInfo(_) => "update_chat_info",
1356            Self::DeleteChat(_) => "delete_chat",
1357            Self::CreateDm(_) => "create_dm",
1358            Self::CreateThread(_) => "create_thread",
1359            Self::CreateReplyThread(_) => "create_reply_thread",
1360            Self::SendText(_) => "send_text",
1361            Self::SendMedia { .. } => "send_media",
1362            Self::EditMessage(_) => "edit_message",
1363            Self::DeleteMessage(_) => "delete_message",
1364            Self::React(_) => "react",
1365            Self::Read(_) => "read",
1366            Self::SetMarkedUnread(_) => "set_marked_unread",
1367            Self::UpdateDialogNotifications(_) => "update_dialog_notifications",
1368            Self::Typing(_) => "typing",
1369        }
1370    }
1371}
1372
1373fn event_retry_delay(error: &BackendError, attempt: u32, policy: ReconnectPolicy) -> Duration {
1374    if error.category == ClientErrorCategory::RateLimited
1375        && let Some(hint) = error
1376            .retry_after_seconds
1377            .map(Duration::from_secs)
1378            .or_else(|| retry_after_hint(&error.message))
1379    {
1380        return apply_retry_jitter(hint.min(policy.rate_limit_max_delay), policy.jitter_percent);
1381    }
1382    let (initial, maximum) = match error.category {
1383        ClientErrorCategory::RateLimited => {
1384            (policy.rate_limit_initial_delay, policy.rate_limit_max_delay)
1385        }
1386        _ => (policy.initial_delay, policy.max_delay),
1387    };
1388    let multiplier = 1_u32.checked_shl(attempt.min(16)).unwrap_or(u32::MAX);
1389    apply_retry_jitter(
1390        initial.saturating_mul(multiplier).min(maximum),
1391        policy.jitter_percent,
1392    )
1393}
1394
1395fn retry_after_hint(message: &str) -> Option<Duration> {
1396    retry_after_seconds_from_message(message).map(Duration::from_secs)
1397}
1398
1399fn apply_retry_jitter(delay: Duration, jitter_percent: u8) -> Duration {
1400    let jitter = u64::from(jitter_percent.min(100));
1401    if jitter == 0 || delay.is_zero() {
1402        return delay;
1403    }
1404    let seed = SystemTime::now()
1405        .duration_since(UNIX_EPOCH)
1406        .unwrap_or_default()
1407        .subsec_nanos() as u64;
1408    let spread = jitter * 2 + 1;
1409    let factor = 100_u64.saturating_sub(jitter) + seed % spread;
1410    Duration::from_millis(
1411        u64::try_from(delay.as_millis().saturating_mul(u128::from(factor)) / 100)
1412            .unwrap_or(u64::MAX),
1413    )
1414}
1415
1416const fn should_retry_event_receive(error: &BackendError) -> bool {
1417    matches!(
1418        error.category,
1419        ClientErrorCategory::Network
1420            | ClientErrorCategory::Timeout
1421            | ClientErrorCategory::RateLimited
1422    )
1423}
1424
1425#[derive(Debug)]
1426enum ClientResponse {
1427    Empty,
1428    Status(ClientStatusSnapshot),
1429    AuthStart(AuthStartResult),
1430    AuthVerify(AuthVerifyResult),
1431    Dialogs(DialogsPage),
1432    AccountState(AccountStateSnapshot),
1433    ChatState(Box<ChatStateSnapshot>),
1434    History(HistoryPage),
1435    ChatParticipants(ChatParticipantsPage),
1436    CreatedChat(CreatedChat),
1437    Message(MessageMutation),
1438}
1439
1440#[cfg(test)]
1441mod tests {
1442    use crate::{
1443        AuthContactKind, AuthCredential, AuthToken, DialogRecord, HistoryRequest, InlineId,
1444        MediaKind, MessageContent, PeerRef,
1445    };
1446
1447    #[tokio::test]
1448    async fn lossless_subscriber_receives_events_that_overflow_broadcast_history() {
1449        let client = InlineClient::builder()
1450            .event_queue_capacity(1)
1451            .lossless_event_queue_capacity(3)
1452            .build()
1453            .spawn();
1454        let mut broadcast = client.subscribe();
1455        let mut lossless = client.take_lossless_events().unwrap();
1456        assert!(client.take_lossless_events().is_none());
1457
1458        for status in [
1459            ClientStatus::AuthRequired,
1460            ClientStatus::AuthExpired,
1461            ClientStatus::LoggedOut,
1462        ] {
1463            client.set_status(status, None).await.unwrap();
1464        }
1465
1466        let mut received = Vec::new();
1467        for _ in 0..3 {
1468            received.push(lossless.recv().await.unwrap());
1469        }
1470        let statuses = received
1471            .into_iter()
1472            .map(|event| match event {
1473                ClientEvent::StatusChanged { status, .. } => status,
1474                other => panic!("expected status event, got {other:?}"),
1475            })
1476            .collect::<Vec<_>>();
1477        assert_eq!(
1478            statuses,
1479            vec![
1480                ClientStatus::AuthRequired,
1481                ClientStatus::AuthExpired,
1482                ClientStatus::LoggedOut
1483            ]
1484        );
1485        assert!(matches!(
1486            broadcast.recv().await,
1487            Err(broadcast::error::RecvError::Lagged(2))
1488        ));
1489    }
1490
1491    #[test]
1492    fn reconnect_backoff_is_bounded_and_uses_rate_limit_hints() {
1493        let policy = ReconnectPolicy {
1494            initial_delay: Duration::from_secs(1),
1495            max_delay: Duration::from_secs(8),
1496            rate_limit_initial_delay: Duration::from_secs(30),
1497            rate_limit_max_delay: Duration::from_secs(120),
1498            jitter_percent: 0,
1499        };
1500        let network = BackendError::new(ClientErrorCategory::Network, "offline");
1501        assert_eq!(
1502            event_retry_delay(&network, 0, policy),
1503            Duration::from_secs(1)
1504        );
1505        assert_eq!(
1506            event_retry_delay(&network, 1, policy),
1507            Duration::from_secs(2)
1508        );
1509        assert_eq!(
1510            event_retry_delay(&network, 20, policy),
1511            Duration::from_secs(8)
1512        );
1513
1514        let limited = BackendError::new(
1515            ClientErrorCategory::RateLimited,
1516            "FLOOD_WAIT_45: retry later",
1517        );
1518        assert_eq!(
1519            event_retry_delay(&limited, 0, policy),
1520            Duration::from_secs(45)
1521        );
1522        let typed_limited = BackendError::new(ClientErrorCategory::RateLimited, "slow down")
1523            .with_retry_after_seconds(75);
1524        assert_eq!(
1525            event_retry_delay(&typed_limited, 0, policy),
1526            Duration::from_secs(75)
1527        );
1528        assert_eq!(
1529            retry_after_hint("please retry after 12 seconds"),
1530            Some(Duration::from_secs(12))
1531        );
1532        assert_eq!(
1533            retry_after_hint("retry_after=19"),
1534            Some(Duration::from_secs(19))
1535        );
1536    }
1537
1538    use super::*;
1539
1540    fn token_connect() -> ConnectRequest {
1541        ConnectRequest::new(AuthCredential::AccessToken {
1542            token: AuthToken::try_new("token").unwrap(),
1543        })
1544    }
1545
1546    fn auth_verify_request() -> AuthVerifyRequest {
1547        AuthVerifyRequest {
1548            contact: "mo@example.com".to_owned(),
1549            kind: AuthContactKind::Email,
1550            code: "123456".to_owned(),
1551            challenge_token: None,
1552            device_name: Some("inline-client test".to_owned()),
1553            account_namespace: None,
1554        }
1555    }
1556
1557    #[tokio::test]
1558    async fn status_snapshot_returns_current_status() {
1559        let client = InlineClient::builder()
1560            .initial_status(ClientStatus::Connected)
1561            .build()
1562            .spawn();
1563
1564        let status = client.status_snapshot();
1565
1566        assert_eq!(status.status, ClientStatus::Connected);
1567        assert_eq!(status.failure, None);
1568    }
1569
1570    #[tokio::test]
1571    async fn set_status_emits_lossless_event() {
1572        let client = InlineClient::builder().build().spawn();
1573        let mut events = client.subscribe();
1574
1575        client
1576            .set_status(
1577                ClientStatus::AuthExpired,
1578                Some(ClientFailure::new(
1579                    ClientErrorCategory::AuthExpired,
1580                    "relogin required",
1581                )),
1582            )
1583            .await
1584            .unwrap();
1585
1586        let event = recv_until_event(&mut events, |event| {
1587            matches!(
1588                event,
1589                ClientEvent::StatusChanged {
1590                    status: ClientStatus::AuthExpired,
1591                    ..
1592                }
1593            )
1594        })
1595        .await;
1596        assert_eq!(event.reliability(), crate::EventReliability::Lossless);
1597        assert!(matches!(
1598            event,
1599            ClientEvent::StatusChanged {
1600                status: ClientStatus::AuthExpired,
1601                ..
1602            }
1603        ));
1604        assert_eq!(client.status(), ClientStatus::AuthExpired);
1605        assert_eq!(
1606            client.status_snapshot().failure.unwrap().category,
1607            ClientErrorCategory::AuthExpired
1608        );
1609    }
1610
1611    #[tokio::test]
1612    async fn connect_updates_status_and_emits_event() {
1613        let client = InlineClient::builder().build().spawn();
1614        let mut events = client.subscribe();
1615
1616        let status = client.connect(token_connect()).await.unwrap();
1617
1618        assert_eq!(status.status, ClientStatus::Connected);
1619        assert_eq!(client.status(), ClientStatus::Connected);
1620
1621        let event = recv_until_event(&mut events, |event| {
1622            matches!(
1623                event,
1624                ClientEvent::StatusChanged {
1625                    status: ClientStatus::Connected,
1626                    ..
1627                }
1628            )
1629        })
1630        .await;
1631        assert!(matches!(
1632            event,
1633            ClientEvent::StatusChanged {
1634                status: ClientStatus::Connected,
1635                ..
1636            }
1637        ));
1638    }
1639
1640    #[tokio::test]
1641    async fn runner_emits_backend_pushed_events_while_connected() {
1642        let backend = InMemoryBackend::new();
1643        let client = InlineClient::builder()
1644            .backend(backend.clone())
1645            .build()
1646            .spawn();
1647        let mut events = client.subscribe();
1648
1649        client.connect(token_connect()).await.unwrap();
1650        backend.push_event_batch(vec![ClientEvent::MessageDeleted {
1651            chat_id: InlineId::new(7),
1652            message_id: InlineId::new(99),
1653        }]);
1654
1655        let event = recv_until_event(&mut events, |event| {
1656            matches!(
1657                event,
1658                ClientEvent::MessageDeleted {
1659                    chat_id,
1660                    message_id,
1661                } if *chat_id == InlineId::new(7) && *message_id == InlineId::new(99)
1662            )
1663        })
1664        .await;
1665        assert_eq!(
1666            event,
1667            ClientEvent::MessageDeleted {
1668                chat_id: InlineId::new(7),
1669                message_id: InlineId::new(99),
1670            }
1671        );
1672    }
1673
1674    #[tokio::test]
1675    async fn runner_marks_reconnecting_when_event_receive_is_rate_limited() {
1676        let backend = InMemoryBackend::new();
1677        let client = InlineClient::builder()
1678            .backend(backend.clone())
1679            .build()
1680            .spawn();
1681        let mut events = client.subscribe();
1682
1683        client.connect(token_connect()).await.unwrap();
1684        backend.push_event_error(BackendError::new(
1685            ClientErrorCategory::RateLimited,
1686            "rate limited",
1687        ));
1688
1689        let event = recv_until_event(&mut events, |event| {
1690            matches!(
1691                event,
1692                ClientEvent::StatusChanged {
1693                    status: ClientStatus::Reconnecting,
1694                    ..
1695                }
1696            )
1697        })
1698        .await;
1699        assert!(matches!(
1700            event,
1701            ClientEvent::StatusChanged {
1702                status: ClientStatus::Reconnecting,
1703                ..
1704            }
1705        ));
1706        assert_eq!(client.status(), ClientStatus::Reconnecting);
1707    }
1708
1709    #[tokio::test]
1710    async fn auth_start_and_verify_flow_through_backend() {
1711        let client = InlineClient::builder().build().spawn();
1712        let mut events = client.subscribe();
1713
1714        let started = client
1715            .auth_start(AuthStartRequest {
1716                contact: "mo@example.com".to_owned(),
1717                kind: AuthContactKind::Email,
1718                device_name: Some("inline-client test".to_owned()),
1719            })
1720            .await
1721            .unwrap();
1722        assert!(started.existing_user);
1723        assert!(!started.needs_invite_code);
1724
1725        let verified = client.auth_verify(auth_verify_request()).await.unwrap();
1726        assert_eq!(verified.user_id, InlineId::new(1));
1727        assert_eq!(verified.account_namespace, "1");
1728        assert_eq!(verified.status.status, ClientStatus::Connected);
1729        assert_eq!(client.status(), ClientStatus::Connected);
1730
1731        let event = recv_until_event(&mut events, |event| {
1732            matches!(
1733                event,
1734                ClientEvent::StatusChanged {
1735                    status: ClientStatus::Connected,
1736                    ..
1737                }
1738            )
1739        })
1740        .await;
1741        assert!(matches!(
1742            event,
1743            ClientEvent::StatusChanged {
1744                status: ClientStatus::Connected,
1745                ..
1746            }
1747        ));
1748    }
1749
1750    #[tokio::test]
1751    async fn resume_without_session_reports_auth_required() {
1752        let client = InlineClient::builder().build().spawn();
1753        let mut events = client.subscribe();
1754
1755        let status = client.resume_session().await.unwrap();
1756
1757        assert_eq!(status.status, ClientStatus::AuthRequired);
1758        assert_eq!(client.status(), ClientStatus::AuthRequired);
1759
1760        let event = recv_until_event(&mut events, |event| {
1761            matches!(
1762                event,
1763                ClientEvent::StatusChanged {
1764                    status: ClientStatus::AuthRequired,
1765                    ..
1766                }
1767            )
1768        })
1769        .await;
1770        assert!(matches!(
1771            event,
1772            ClientEvent::StatusChanged {
1773                status: ClientStatus::AuthRequired,
1774                ..
1775            }
1776        ));
1777    }
1778
1779    #[tokio::test]
1780    async fn dialogs_and_history_flow_through_backend() {
1781        let backend = InMemoryBackend::new();
1782        backend.upsert_dialog(DialogRecord {
1783            chat_id: InlineId::new(7),
1784            peer_user_id: None,
1785            title: Some("general".to_owned()),
1786            last_message_id: None,
1787            synced_through_message_id: None,
1788            unread_count: Some(0),
1789            ..DialogRecord::new(InlineId::new(7))
1790        });
1791        backend.insert_message(crate::MessageRecord {
1792            chat_id: InlineId::new(7),
1793            message_id: InlineId::new(1),
1794            sender_id: InlineId::new(2),
1795            timestamp: 1,
1796            is_outgoing: false,
1797            content: MessageContent::Text {
1798                text: "hello".to_owned(),
1799            },
1800            reply_to_message_id: None,
1801            transaction: None,
1802        });
1803        let client = InlineClient::builder().backend(backend).build().spawn();
1804        client.connect(token_connect()).await.unwrap();
1805
1806        let dialogs = client.dialogs(DialogsRequest::default()).await.unwrap();
1807        assert_eq!(dialogs.dialogs.len(), 1);
1808        assert_eq!(dialogs.dialogs[0].chat_id, InlineId::new(7));
1809
1810        let history = client
1811            .history(HistoryRequest {
1812                chat_id: InlineId::new(7),
1813                limit: Some(10),
1814                before_message_id: None,
1815                after_message_id: None,
1816            })
1817            .await
1818            .unwrap();
1819        assert_eq!(history.messages.len(), 1);
1820        assert_eq!(history.messages[0].message_id, InlineId::new(1));
1821    }
1822
1823    #[tokio::test]
1824    async fn send_text_returns_mutation_and_emits_events() {
1825        let client = InlineClient::builder().build().spawn();
1826        client.connect(token_connect()).await.unwrap();
1827        let mut events = client.subscribe();
1828
1829        let mutation = client
1830            .send_text(SendTextRequest::new(
1831                PeerRef::Chat {
1832                    chat_id: InlineId::new(7),
1833                },
1834                "hello",
1835            ))
1836            .await
1837            .unwrap();
1838
1839        assert_eq!(mutation.message_id, Some(InlineId::new(1)));
1840        assert_eq!(mutation.state, Some(crate::TransactionState::Completed));
1841        assert!(mutation.failure.is_none());
1842        assert_eq!(
1843            mutation.transaction.final_message_id,
1844            Some(InlineId::new(1))
1845        );
1846
1847        let events = [
1848            events.recv().await.unwrap(),
1849            events.recv().await.unwrap(),
1850            events.recv().await.unwrap(),
1851        ];
1852        assert!(
1853            events
1854                .iter()
1855                .any(|event| matches!(event, ClientEvent::TransactionChanged(_)))
1856        );
1857        assert!(events.iter().any(|event| matches!(
1858            event,
1859            ClientEvent::MessageUpserted {
1860                chat_id: InlineId(7),
1861                message_id: InlineId(1)
1862            }
1863        )));
1864        assert!(events.iter().any(|event| matches!(
1865            event,
1866            ClientEvent::MessageStored { message }
1867                if message.chat_id == InlineId::new(7)
1868                    && message.message_id == InlineId::new(1)
1869        )));
1870    }
1871
1872    #[tokio::test]
1873    async fn edit_message_emits_stored_upsert_event() {
1874        let backend = InMemoryBackend::new();
1875        backend.insert_message(crate::MessageRecord {
1876            chat_id: InlineId::new(7),
1877            message_id: InlineId::new(1),
1878            sender_id: InlineId::new(2),
1879            timestamp: 1,
1880            is_outgoing: false,
1881            content: MessageContent::Text {
1882                text: "old".to_owned(),
1883            },
1884            reply_to_message_id: None,
1885            transaction: None,
1886        });
1887        let client = InlineClient::builder().backend(backend).build().spawn();
1888        client.connect(token_connect()).await.unwrap();
1889        let mut events = client.subscribe();
1890
1891        client
1892            .edit_message(EditMessageRequest {
1893                chat_id: InlineId::new(7),
1894                message_id: InlineId::new(1),
1895                text: "edited".to_owned(),
1896                external_id: None,
1897            })
1898            .await
1899            .unwrap();
1900
1901        match events.recv().await.unwrap() {
1902            ClientEvent::MessageStored { message } => {
1903                assert_eq!(message.chat_id, InlineId::new(7));
1904                assert_eq!(message.message_id, InlineId::new(1));
1905                assert_eq!(
1906                    message.content,
1907                    MessageContent::Text {
1908                        text: "edited".to_owned()
1909                    }
1910                );
1911            }
1912            other => panic!("unexpected event: {other:?}"),
1913        }
1914    }
1915
1916    #[tokio::test]
1917    async fn send_media_emits_message_events() {
1918        let client = InlineClient::builder().build().spawn();
1919        client.connect(token_connect()).await.unwrap();
1920        let mut events = client.subscribe();
1921
1922        let mutation = client
1923            .send_media(
1924                UploadRequest {
1925                    peer: PeerRef::Chat {
1926                        chat_id: InlineId::new(7),
1927                    },
1928                    kind: MediaKind::Photo,
1929                    file_name: Some("image.png".to_owned()),
1930                    mime_type: Some("image/png".to_owned()),
1931                    size_bytes: Some(4),
1932                    caption: Some("caption".to_owned()),
1933                    width: Some(10),
1934                    height: Some(10),
1935                    duration_ms: None,
1936                    external_id: None,
1937                    random_id: None,
1938                    reply_to_message_id: None,
1939                },
1940                vec![1, 2, 3, 4],
1941            )
1942            .await
1943            .unwrap();
1944
1945        assert_eq!(mutation.message_id, Some(InlineId::new(1)));
1946
1947        let _transaction = events.recv().await.unwrap();
1948        let _upsert = events.recv().await.unwrap();
1949        match events.recv().await.unwrap() {
1950            ClientEvent::MessageStored { message } => {
1951                assert_eq!(message.chat_id, InlineId::new(7));
1952                assert_eq!(message.message_id, InlineId::new(1));
1953                match message.content {
1954                    MessageContent::Media {
1955                        kind,
1956                        file_name,
1957                        caption,
1958                        ..
1959                    } => {
1960                        assert_eq!(kind, MediaKind::Photo);
1961                        assert_eq!(file_name.as_deref(), Some("image.png"));
1962                        assert_eq!(caption.as_deref(), Some("caption"));
1963                    }
1964                    other => panic!("unexpected content: {other:?}"),
1965                }
1966            }
1967            other => panic!("unexpected event: {other:?}"),
1968        }
1969    }
1970
1971    #[tokio::test]
1972    async fn shutdown_stops_runner() {
1973        let client = InlineClient::builder().build().spawn();
1974
1975        client.shutdown().await.unwrap();
1976        let err = client
1977            .dialogs(DialogsRequest::default())
1978            .await
1979            .expect_err("runner should reject commands after shutdown");
1980
1981        assert!(matches!(
1982            err,
1983            ClientRequestError::Command(
1984                ClientCommandError::Closed | ClientCommandError::ResponseDropped
1985            )
1986        ));
1987    }
1988
1989    async fn recv_until_event(
1990        events: &mut broadcast::Receiver<ClientEvent>,
1991        matches: impl Fn(&ClientEvent) -> bool,
1992    ) -> ClientEvent {
1993        tokio::time::timeout(Duration::from_secs(3), async {
1994            loop {
1995                let event = events.recv().await.unwrap();
1996                if matches(&event) {
1997                    return event;
1998                }
1999            }
2000        })
2001        .await
2002        .expect("expected matching client event")
2003    }
2004}