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 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
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 };
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#[derive(Debug)]
217pub struct InlineClientRuntime {
218 pub client: InlineClient,
220 pub runner: ClientRunner,
222}
223
224#[derive(Debug)]
227pub struct LosslessEventReceiver {
228 receiver: mpsc::Receiver<ClientEvent>,
229}
230
231impl LosslessEventReceiver {
232 pub async fn recv(&mut self) -> Option<ClientEvent> {
235 self.receiver.recv().await
236 }
237}
238
239impl InlineClientRuntime {
240 pub fn split(self) -> (InlineClient, ClientRunner) {
242 (self.client, self.runner)
243 }
244
245 pub fn spawn(self) -> InlineClient {
247 let (client, runner) = self.split();
248 tokio::spawn(runner.run());
249 client
250 }
251}
252
253#[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 pub fn builder() -> InlineClientBuilder {
267 InlineClientBuilder::default()
268 }
269
270 pub fn status(&self) -> ClientStatus {
272 *self.status_rx.borrow()
273 }
274
275 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 pub fn subscribe(&self) -> broadcast::Receiver<ClientEvent> {
285 self.event_tx.subscribe()
286 }
287
288 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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#[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 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}