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