Skip to main content

phoenix_channel_client/client/
mod.rs

1//! Socket, channel, and connection driver implementation.
2
3mod config;
4mod endpoint;
5mod presence;
6#[cfg(feature = "tracing")]
7mod tracing_support;
8
9pub use config::{
10    ConnectContext, Connector, JoinContext, JoinPayloadLoader, Options, ReconnectAction,
11    ReconnectContext, ReconnectPolicy, Timer, static_join_payload,
12};
13pub use endpoint::{
14    ConnectionConfig, ConnectionConfigLoader, Endpoint, EndpointError, ResolvedEndpoint,
15    static_connection_config,
16};
17pub use presence::{ChannelPresence, PresenceEvent, PresenceStreamError};
18#[cfg(feature = "tracing")]
19pub use tracing_support::tracing_telemetry_hook;
20
21use std::{
22    cell::{Cell, RefCell},
23    collections::{HashMap, HashSet, VecDeque},
24    future::Future,
25    pin::Pin,
26    rc::Rc,
27    task::{Context, Poll},
28    time::Duration,
29};
30
31use futures::{
32    FutureExt, SinkExt, StreamExt,
33    channel::{mpsc, oneshot},
34    future::{LocalBoxFuture, pending},
35    stream::FuturesUnordered,
36};
37use phoenix_channel_runtime::{
38    ChannelState, Codec, EventRoute, Frame, Payload, PayloadError, PhoenixV2Codec, Protocol,
39    ProtocolEvent, ReplyStatus, Transport, TransportClose, TransportCloseRequest, TransportError,
40    TransportErrorKind, TransportEvent, WireMessage,
41};
42use serde::{Serialize, de::DeserializeOwned};
43use serde_json::{Value, json};
44use thiserror::Error;
45
46type RequestId = u64;
47
48/// Socket connection and reconnect lifecycle notification.
49#[derive(Clone, Debug, PartialEq)]
50pub enum SocketEvent {
51    /// A transport connection attempt started.
52    Connecting {
53        /// Zero-based attempt number for the connection cycle.
54        attempt: u32,
55    },
56    /// The transport connected successfully.
57    Connected,
58    /// The connection ended or was explicitly disconnected.
59    Disconnected {
60        /// Condition that ended the connection.
61        reason: DisconnectReason,
62    },
63    /// An automatic reconnect was scheduled.
64    ReconnectScheduled {
65        /// Reconnect attempt number.
66        attempt: u32,
67        /// Delay before the next connection attempt.
68        delay: Duration,
69    },
70    /// The reconnect policy chose not to retry.
71    ReconnectStopped {
72        /// Reconnect attempt number at which retries stopped.
73        attempt: u32,
74        /// Condition supplied to the reconnect policy.
75        reason: DisconnectReason,
76    },
77    /// The client driver shut down permanently.
78    Closed,
79    /// This bounded event subscriber fell behind.
80    Lagged {
81        /// Number of events dropped before the lag notification.
82        dropped: u64,
83    },
84}
85
86/// Condition that disconnected a managed socket.
87#[derive(Clone, Debug, Error, Eq, PartialEq)]
88pub enum DisconnectReason {
89    /// The application requested a disconnect.
90    #[error("connection closed by request")]
91    Requested,
92    /// Establishing the transport failed.
93    #[error("connection failed: {0}")]
94    Connect(TransportError),
95    /// An established transport operation failed.
96    #[error("transport failed: {0}")]
97    Transport(TransportError),
98    /// The transport reported a close frame or ended.
99    #[error("connection closed: {0:?}")]
100    Closed(TransportClose),
101    /// An automatic heartbeat acknowledgement did not arrive in time.
102    #[error("heartbeat acknowledgement timed out")]
103    HeartbeatTimeout,
104    /// Incoming data violated managed protocol expectations.
105    #[error("protocol failed: {0}")]
106    Protocol(String),
107    /// The driver ended before completing a connection operation.
108    #[error("client driver stopped")]
109    DriverStopped,
110}
111
112impl DisconnectReason {
113    fn should_reconnect(&self) -> bool {
114        match self {
115            Self::Closed(close) => close.should_reconnect(),
116            Self::Requested | Self::DriverStopped => false,
117            Self::Connect(_) | Self::Transport(_) | Self::HeartbeatTimeout | Self::Protocol(_) => {
118                true
119            }
120        }
121    }
122}
123
124/// Observable socket lifecycle state.
125#[derive(Clone, Copy, Debug, Eq, PartialEq)]
126pub enum SocketStatus {
127    /// No transport is active and no retry is scheduled.
128    Disconnected,
129    /// A transport connection attempt is active.
130    Connecting,
131    /// A transport is connected.
132    Connected,
133    /// The reconnect policy scheduled a future attempt.
134    WaitingToReconnect,
135    /// The driver shut down permanently.
136    Closed,
137}
138
139/// Protocol or lifecycle notification for one channel topic.
140#[derive(Clone, Debug, PartialEq)]
141pub enum ChannelEvent {
142    /// A semantic Phoenix protocol event.
143    Protocol(ProtocolEvent),
144    /// The underlying socket disconnected.
145    Disconnected,
146    /// The join payload loader failed.
147    JoinPayloadError(String),
148    /// This bounded event subscriber fell behind.
149    ///
150    /// Consumers that maintain derived state must resynchronize.
151    Lagged {
152        /// Number of events dropped before this notification.
153        dropped: u64,
154    },
155}
156
157impl ChannelEvent {
158    /// Decodes a matching application message using an [`EventRoute`].
159    pub fn route<R: EventRoute>(&self) -> Result<Option<R::Output>, PayloadError> {
160        match self {
161            Self::Protocol(ProtocolEvent::Message(frame)) => frame.route::<R>(),
162            _ => Ok(None),
163        }
164    }
165}
166
167/// Observable lifecycle state for one channel topic.
168#[derive(Clone, Copy, Debug, Eq, PartialEq)]
169pub enum ChannelStatus {
170    /// The channel is waiting for the socket to connect.
171    WaitingForSocket,
172    /// The socket is connected and the channel has not joined yet.
173    WaitingToJoin,
174    /// A join request is pending.
175    Joining,
176    /// The server accepted the join.
177    Joined,
178    /// A leave request is pending.
179    Leaving,
180    /// The channel left successfully.
181    Left,
182    /// A join was rejected or the server sent `phx_error`.
183    Errored,
184    /// The handle was dropped, the server closed it, or the driver stopped.
185    Closed,
186}
187
188/// Correlated Phoenix reply status and response payload.
189#[derive(Clone, Debug, PartialEq)]
190pub struct Reply {
191    /// Phoenix `ok` or `error` reply status.
192    pub status: ReplyStatus,
193    /// JSON or binary response payload.
194    pub response: Payload,
195}
196
197impl Reply {
198    /// Deserializes the response when it is JSON, regardless of reply status.
199    pub fn deserialize<T: serde::de::DeserializeOwned>(&self) -> Result<T, PayloadError> {
200        self.response.deserialize()
201    }
202
203    /// Converts an `ok` reply to `Ok` and an `error` reply to `Err`.
204    pub fn into_result(self) -> Result<Payload, Payload> {
205        match self.status {
206            ReplyStatus::Ok => Ok(self.response),
207            ReplyStatus::Error => Err(self.response),
208        }
209    }
210
211    /// Requires an `ok` status and deserializes its JSON response.
212    pub fn deserialize_ok<T: DeserializeOwned>(self) -> Result<T, ReplyError> {
213        self.into_result()
214            .map_err(ReplyError::Server)?
215            .deserialize()
216            .map_err(ReplyError::Decode)
217    }
218}
219
220/// Server rejection or response decoding failure.
221#[derive(Debug, Error)]
222pub enum ReplyError {
223    /// Phoenix returned an `error` reply and its payload.
224    #[error("Phoenix returned an error reply: {0:?}")]
225    Server(Payload),
226    /// The successful response was not valid for the requested type.
227    #[error("failed to decode reply payload: {0}")]
228    Decode(PayloadError),
229}
230
231/// Failure from [`Channel::call_json`].
232#[derive(Debug, Error)]
233pub enum CallJsonError {
234    /// The managed call failed.
235    #[error(transparent)]
236    Client(#[from] ClientError),
237    /// The request value could not be serialized as JSON.
238    #[error("failed to encode request payload: {0}")]
239    Encode(serde_json::Error),
240    /// Phoenix rejected the request or the response could not be decoded.
241    #[error(transparent)]
242    Reply(#[from] ReplyError),
243}
244
245/// Managed operation used in timeout and interruption errors.
246#[derive(Clone, Copy, Debug, Eq, PartialEq)]
247pub enum ClientOperation {
248    /// Joining a channel.
249    Join,
250    /// Sending a correlated channel event.
251    Call,
252    /// Sending an event without waiting for a Phoenix reply.
253    Cast,
254    /// Leaving a channel.
255    Leave,
256    /// Sending an explicit heartbeat.
257    Ping,
258}
259
260impl std::fmt::Display for ClientOperation {
261    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262        formatter.write_str(match self {
263            Self::Join => "join",
264            Self::Call => "call",
265            Self::Cast => "cast",
266            Self::Leave => "leave",
267            Self::Ping => "ping",
268        })
269    }
270}
271
272/// Failure returned by the managed socket or channel API.
273#[derive(Clone, Debug, Error, PartialEq)]
274pub enum ClientError {
275    /// The driver stopped and cannot accept more commands.
276    #[error("the managed client driver stopped")]
277    DriverStopped,
278    /// A non-blocking subscription command could not enter the bounded queue.
279    #[error("the client command queue is full")]
280    CommandQueueFull,
281    /// Too many calls are waiting for this topic to become joined.
282    #[error("the unsent push buffer is full for topic {0}")]
283    PushBufferFull(String),
284    /// A channel handle already exists for this topic.
285    #[error("a channel already exists for topic {0}")]
286    DuplicateChannel(String),
287    /// The requested channel is already joined or joining.
288    #[error("channel {0} is already joined")]
289    AlreadyJoined(String),
290    /// The channel must be joined before accepting this operation.
291    #[error("channel {0} must be joined again before sending events")]
292    ChannelNotJoined(String),
293    /// The operation requires an active socket connection.
294    #[error("the socket must be connected for this operation")]
295    SocketNotConnected,
296    /// The active transport cannot carry Phoenix binary frames.
297    #[error("the active transport does not support binary Phoenix frames")]
298    BinaryNotSupported,
299    /// The requested operation did not complete before its deadline.
300    #[error("{operation} timed out after {timeout:?}")]
301    Timeout {
302        /// Operation that timed out.
303        operation: ClientOperation,
304        /// Applied timeout duration.
305        timeout: Duration,
306    },
307    /// A transmitted operation lost its connection before receiving a reply.
308    #[error("{operation} was interrupted by connection loss")]
309    Interrupted {
310        /// Interrupted operation.
311        operation: ClientOperation,
312    },
313    /// Protocol state rejected an operation or incoming frame.
314    #[error("protocol error: {0}")]
315    Protocol(String),
316    /// Loading a fresh join payload failed.
317    #[error("join payload loader failed for {topic}: {message}")]
318    JoinPayload {
319        /// Topic whose payload could not be loaded.
320        topic: String,
321        /// Loader-provided error message.
322        message: String,
323    },
324    /// Phoenix returned an error reply to a join.
325    #[error("channel join was rejected for {topic}: {response:?}")]
326    JoinRejected {
327        /// Rejected topic.
328        topic: String,
329        /// Server rejection payload.
330        response: Payload,
331    },
332    /// No registered channel matched the topic.
333    #[error("unknown channel topic: {0}")]
334    UnknownTopic(String),
335}
336
337struct ObservableStatus<T> {
338    value: Cell<T>,
339    subscribers: RefCell<Vec<mpsc::Sender<()>>>,
340}
341
342impl<T: Copy + Eq> ObservableStatus<T> {
343    fn new(value: T) -> Self {
344        Self {
345            value: Cell::new(value),
346            subscribers: RefCell::new(Vec::new()),
347        }
348    }
349
350    fn get(&self) -> T {
351        self.value.get()
352    }
353
354    fn set(&self, value: T) {
355        if self.value.replace(value) == value {
356            return;
357        }
358        self.subscribers
359            .borrow_mut()
360            .retain_mut(|subscriber| match subscriber.try_send(()) {
361                Ok(()) => true,
362                Err(error) => error.is_full(),
363            });
364    }
365
366    fn subscribe(self: &Rc<Self>) -> StatusChanges<T> {
367        let (sender, receiver) = mpsc::channel(1);
368        self.subscribers.borrow_mut().push(sender);
369        StatusChanges {
370            receiver,
371            status: self.clone(),
372        }
373    }
374}
375
376/// Stream of changes to a copyable socket or channel status.
377pub struct StatusChanges<T> {
378    receiver: mpsc::Receiver<()>,
379    status: Rc<ObservableStatus<T>>,
380}
381
382impl<T: Copy + Eq> StatusChanges<T> {
383    /// Returns the latest status without waiting for a change.
384    pub fn current(&self) -> T {
385        self.status.get()
386    }
387
388    /// Waits for a new status, or returns `None` after the driver closes it.
389    pub async fn changed(&mut self) -> Option<T> {
390        self.receiver.next().await.map(|()| self.status.get())
391    }
392}
393
394/// Status-change stream for a [`Socket`].
395pub type SocketStatusChanges = StatusChanges<SocketStatus>;
396/// Status-change stream for a [`Channel`].
397pub type ChannelStatusChanges = StatusChanges<ChannelStatus>;
398
399/// Structured lifecycle and frame observation emitted by the driver.
400#[derive(Clone, Debug, PartialEq)]
401pub enum TelemetryEvent {
402    /// A socket lifecycle event.
403    Socket(SocketEvent),
404    /// A channel lifecycle or protocol event.
405    Channel {
406        /// Channel topic.
407        topic: String,
408        /// Event emitted for the topic.
409        event: ChannelEvent,
410    },
411    /// An encoded frame was sent.
412    FrameSent {
413        /// Frame topic.
414        topic: String,
415        /// Frame event name.
416        event: String,
417        /// Whether the encoded frame was binary.
418        binary: bool,
419        /// Encoded frame size in bytes.
420        bytes: usize,
421    },
422    /// An encoded frame was received.
423    FrameReceived {
424        /// Frame topic.
425        topic: String,
426        /// Frame event name.
427        event: String,
428        /// Whether the encoded frame was binary.
429        binary: bool,
430        /// Encoded frame size in bytes.
431        bytes: usize,
432    },
433    /// A transport connection attempt ended.
434    ConnectionAttemptFinished {
435        /// Zero-based attempt number.
436        attempt: u32,
437        /// Time spent loading configuration and connecting the transport.
438        duration: Duration,
439        /// Whether the transport connected successfully.
440        connected: bool,
441    },
442    /// A channel call reached a terminal outcome.
443    CallCompleted {
444        /// Channel topic.
445        topic: String,
446        /// Application event name.
447        event: String,
448        /// Reply or failure classification.
449        outcome: CallOutcome,
450        /// Duration from API invocation through the terminal outcome.
451        duration: Duration,
452    },
453}
454
455/// Terminal outcome recorded for call telemetry.
456#[derive(Clone, Copy, Debug, Eq, PartialEq)]
457pub enum CallOutcome {
458    /// Phoenix returned a correlated reply with this status.
459    Reply(ReplyStatus),
460    /// The caller cancelled or timed out before completion.
461    Cancelled,
462    /// Connection loss interrupted a transmitted call.
463    Interrupted,
464    /// The client rejected the call before transmission.
465    Rejected,
466}
467
468/// Callback invoked synchronously for every [`TelemetryEvent`].
469pub type TelemetryHook = Rc<dyn Fn(&TelemetryEvent)>;
470
471/// Handle used to configure channels and control the managed connection.
472///
473/// The companion [`Driver`] owns all protocol and transport state and must be
474/// polled continuously.
475#[derive(Clone)]
476pub struct Socket {
477    commands: mpsc::Sender<Command>,
478    lifecycle: mpsc::UnboundedSender<LifecycleCommand>,
479    timer: Rc<dyn Timer>,
480    options: Options,
481    request_ids: Rc<Cell<RequestId>>,
482    topics: Rc<RefCell<HashSet<String>>>,
483    status: Rc<ObservableStatus<SocketStatus>>,
484}
485
486impl Socket {
487    /// Creates a socket and driver using the bounded default Phoenix v2 codec.
488    pub fn new(
489        connector: impl Connector + 'static,
490        timer: impl Timer + 'static,
491        options: Options,
492    ) -> (Self, Driver) {
493        Self::new_with_codec(
494            connector,
495            timer,
496            options,
497            PhoenixV2Codec::limited(Default::default()),
498        )
499    }
500
501    /// Creates a socket and driver using an application-provided codec.
502    pub fn new_with_codec(
503        connector: impl Connector + 'static,
504        timer: impl Timer + 'static,
505        options: Options,
506        codec: impl Codec + 'static,
507    ) -> (Self, Driver) {
508        let (commands, command_rx) = mpsc::channel(options.command_capacity);
509        let (lifecycle, lifecycle_rx) = mpsc::unbounded();
510        let timer: Rc<dyn Timer> = Rc::new(timer);
511        let status = Rc::new(ObservableStatus::new(if options.connect_on_start {
512            SocketStatus::Connecting
513        } else {
514            SocketStatus::Disconnected
515        }));
516        let socket = Self {
517            commands,
518            lifecycle: lifecycle.clone(),
519            timer: timer.clone(),
520            options: options.clone(),
521            request_ids: Rc::new(Cell::new(0)),
522            topics: Rc::new(RefCell::new(HashSet::new())),
523            status: status.clone(),
524        };
525        let state = DriverState::new(
526            Rc::new(connector),
527            timer,
528            options,
529            Rc::new(codec),
530            command_rx,
531            lifecycle_rx,
532            status,
533        );
534        let driver = Driver {
535            inner: Box::pin(state.run()),
536        };
537        (socket, driver)
538    }
539
540    /// Registers one channel topic and its per-attempt join payload loader.
541    ///
542    /// Only one live [`Channel`] handle may exist for a topic.
543    pub fn channel(
544        &self,
545        topic: impl Into<String>,
546        payload_loader: JoinPayloadLoader,
547    ) -> Result<Channel, ClientError> {
548        let topic = topic.into();
549        if !self.topics.borrow_mut().insert(topic.clone()) {
550            return Err(ClientError::DuplicateChannel(topic));
551        }
552        let (events, event_rx) = mpsc::channel(self.options.event_capacity);
553        let status = Rc::new(ObservableStatus::new(ChannelStatus::Closed));
554        if self
555            .lifecycle
556            .unbounded_send(LifecycleCommand::Register {
557                topic: topic.clone(),
558                payload_loader,
559                events,
560                status: status.clone(),
561            })
562            .is_err()
563        {
564            self.topics.borrow_mut().remove(&topic);
565            return Err(ClientError::DriverStopped);
566        }
567        Ok(Channel {
568            topic,
569            commands: self.commands.clone(),
570            lifecycle: self.lifecycle.clone(),
571            timer: self.timer.clone(),
572            timeouts: OperationTimeouts {
573                join: self.options.join_timeout,
574                call: self.options.call_timeout,
575                leave: self.options.leave_timeout,
576            },
577            event_capacity: self.options.event_capacity,
578            request_ids: self.request_ids.clone(),
579            topics: self.topics.clone(),
580            events: event_rx,
581            status,
582        })
583    }
584
585    /// Creates an independent bounded socket event subscriber.
586    pub fn events(&self) -> Result<SocketEvents, ClientError> {
587        let (events, receiver) = mpsc::channel(self.options.event_capacity);
588        let mut commands = self.commands.clone();
589        commands
590            .try_send(Command::Subscribe { events })
591            .map_err(command_send_error)?;
592        Ok(SocketEvents { receiver })
593    }
594
595    /// Returns the latest socket status.
596    pub fn status(&self) -> SocketStatus {
597        self.status.get()
598    }
599
600    /// Creates a stream of subsequent socket status changes.
601    pub fn status_changes(&self) -> SocketStatusChanges {
602        self.status.subscribe()
603    }
604
605    /// Enables connection attempts and waits until the command is accepted.
606    pub async fn connect(&self) -> Result<(), ClientError> {
607        let (response, receiver) = oneshot::channel();
608        let mut commands = self.commands.clone();
609        commands
610            .send(Command::Connect { response })
611            .await
612            .map_err(|_| ClientError::DriverStopped)?;
613        receiver.await.map_err(|_| ClientError::DriverStopped)
614    }
615
616    /// Disables automatic reconnection and closes the active transport.
617    pub async fn disconnect(&self) -> Result<(), ClientError> {
618        self.disconnect_inner(None).await
619    }
620
621    /// Disconnects with an explicit WebSocket close code and reason.
622    pub async fn disconnect_with(
623        &self,
624        code: u16,
625        reason: impl Into<String>,
626    ) -> Result<(), ClientError> {
627        self.disconnect_inner(Some(TransportCloseRequest::new(code, reason)))
628            .await
629    }
630
631    async fn disconnect_inner(
632        &self,
633        close: Option<TransportCloseRequest>,
634    ) -> Result<(), ClientError> {
635        let (response, receiver) = oneshot::channel();
636        let mut commands = self.commands.clone();
637        commands
638            .send(Command::Disconnect { close, response })
639            .await
640            .map_err(|_| ClientError::DriverStopped)?;
641        receiver.await.map_err(|_| ClientError::DriverStopped)
642    }
643
644    /// Disconnects and immediately enables a new connection cycle.
645    pub async fn reconnect(&self) -> Result<(), ClientError> {
646        self.disconnect().await?;
647        self.connect().await
648    }
649
650    /// Sends a heartbeat using the default call timeout and returns its RTT.
651    pub async fn ping(&self) -> Result<Duration, ClientError> {
652        self.ping_with_timeout(self.options.call_timeout).await
653    }
654
655    /// Sends a heartbeat with an explicit timeout and returns its RTT.
656    pub async fn ping_with_timeout(&self, timeout: Duration) -> Result<Duration, ClientError> {
657        let id = self.next_request_id();
658        let (response, receiver) = oneshot::channel();
659        let mut commands = self.commands.clone();
660        commands
661            .send(Command::Ping { id, response })
662            .await
663            .map_err(|_| ClientError::DriverStopped)?;
664        let mut guard = RequestGuard {
665            id,
666            lifecycle: self.lifecycle.clone(),
667            armed: true,
668        };
669        let response = receiver.fuse();
670        let timeout_future = self.timer.sleep(timeout).fuse();
671        futures::pin_mut!(response, timeout_future);
672        futures::select! {
673            response = response => {
674                guard.armed = false;
675                response.map_err(|_| ClientError::DriverStopped)?
676            },
677            () = timeout_future => Err(ClientError::Timeout {
678                operation: ClientOperation::Ping,
679                timeout,
680            }),
681        }
682    }
683
684    fn next_request_id(&self) -> RequestId {
685        let mut id = self.request_ids.get().wrapping_add(1);
686        if id == 0 {
687            id = 1;
688        }
689        self.request_ids.set(id);
690        id
691    }
692
693    /// Permanently stops the driver and closes the active transport.
694    pub async fn shutdown(&self) -> Result<(), ClientError> {
695        let (response, receiver) = oneshot::channel();
696        let mut commands = self.commands.clone();
697        commands
698            .send(Command::Shutdown { response })
699            .await
700            .map_err(|_| ClientError::DriverStopped)?;
701        receiver.await.map_err(|_| ClientError::DriverStopped)
702    }
703}
704
705/// Independent bounded stream of [`SocketEvent`] values.
706pub struct SocketEvents {
707    receiver: mpsc::Receiver<SocketEvent>,
708}
709
710impl SocketEvents {
711    /// Returns the next socket event or `None` after the driver stops.
712    pub async fn next(&mut self) -> Option<SocketEvent> {
713        self.receiver.next().await
714    }
715}
716
717/// Handle for one Phoenix channel topic.
718///
719/// Dropping the handle unregisters the topic and its pending operations.
720pub struct Channel {
721    topic: String,
722    commands: mpsc::Sender<Command>,
723    lifecycle: mpsc::UnboundedSender<LifecycleCommand>,
724    timer: Rc<dyn Timer>,
725    timeouts: OperationTimeouts,
726    event_capacity: usize,
727    request_ids: Rc<Cell<RequestId>>,
728    topics: Rc<RefCell<HashSet<String>>>,
729    events: mpsc::Receiver<ChannelEvent>,
730    status: Rc<ObservableStatus<ChannelStatus>>,
731}
732
733#[derive(Clone, Copy)]
734struct OperationTimeouts {
735    join: Duration,
736    call: Duration,
737    leave: Duration,
738}
739
740impl Channel {
741    /// Returns the registered channel topic.
742    pub fn topic(&self) -> &str {
743        &self.topic
744    }
745
746    /// Returns the latest channel status.
747    pub fn status(&self) -> ChannelStatus {
748        self.status.get()
749    }
750
751    /// Creates a stream of subsequent channel status changes.
752    pub fn status_changes(&self) -> ChannelStatusChanges {
753        self.status.subscribe()
754    }
755
756    /// Joins using the default join timeout and a freshly loaded payload.
757    pub async fn join(&self) -> Result<Payload, ClientError> {
758        self.join_with_timeout(self.timeouts.join).await
759    }
760
761    /// Joins with an explicit timeout and a freshly loaded payload.
762    pub async fn join_with_timeout(&self, timeout: Duration) -> Result<Payload, ClientError> {
763        let id = self.next_request_id();
764        let (response, receiver) = oneshot::channel();
765        self.send(Command::Join {
766            id,
767            topic: self.topic.clone(),
768            timeout,
769            response,
770        })
771        .await?;
772        self.wait(id, ClientOperation::Join, timeout, receiver)
773            .await
774    }
775
776    /// Sends an event and waits for its correlated reply.
777    pub async fn call(
778        &self,
779        event: impl Into<String>,
780        payload: impl Into<Payload>,
781    ) -> Result<Reply, ClientError> {
782        self.call_with_timeout(event, payload, self.timeouts.call)
783            .await
784    }
785
786    /// Sends an event and waits up to `timeout` for its correlated reply.
787    pub async fn call_with_timeout(
788        &self,
789        event: impl Into<String>,
790        payload: impl Into<Payload>,
791        timeout: Duration,
792    ) -> Result<Reply, ClientError> {
793        let id = self.next_request_id();
794        let (response, receiver) = oneshot::channel();
795        self.send(Command::Call {
796            id,
797            topic: self.topic.clone(),
798            event: event.into(),
799            payload: payload.into(),
800            started: self.timer.now(),
801            response,
802        })
803        .await?;
804        self.wait(id, ClientOperation::Call, timeout, receiver)
805            .await
806    }
807
808    /// Serializes a JSON request, requires an `ok` reply, and decodes its response.
809    pub async fn call_json<Request, Response>(
810        &self,
811        event: impl Into<String>,
812        request: &Request,
813    ) -> Result<Response, CallJsonError>
814    where
815        Request: Serialize + ?Sized,
816        Response: DeserializeOwned,
817    {
818        let payload = serde_json::to_value(request).map_err(CallJsonError::Encode)?;
819        self.call(event, payload)
820            .await?
821            .deserialize_ok()
822            .map_err(Into::into)
823    }
824
825    /// Sends an event without asking Phoenix for a reply.
826    pub async fn cast(
827        &self,
828        event: impl Into<String>,
829        payload: impl Into<Payload>,
830    ) -> Result<(), ClientError> {
831        self.cast_with_timeout(event, payload, self.timeouts.call)
832            .await
833    }
834
835    /// Sends an event without asking Phoenix for a reply, waiting at most `timeout`
836    /// for the frame to be accepted by the transport.
837    pub async fn cast_with_timeout(
838        &self,
839        event: impl Into<String>,
840        payload: impl Into<Payload>,
841        timeout: Duration,
842    ) -> Result<(), ClientError> {
843        let id = self.next_request_id();
844        let (response, receiver) = oneshot::channel();
845        self.send(Command::Cast {
846            id,
847            topic: self.topic.clone(),
848            event: event.into(),
849            payload: payload.into(),
850            response,
851        })
852        .await?;
853        self.wait(id, ClientOperation::Cast, timeout, receiver)
854            .await
855    }
856
857    /// Leaves using the default leave timeout.
858    pub async fn leave(&self) -> Result<Payload, ClientError> {
859        self.leave_with_timeout(self.timeouts.leave).await
860    }
861
862    /// Leaves and waits up to `timeout` for the server reply.
863    pub async fn leave_with_timeout(&self, timeout: Duration) -> Result<Payload, ClientError> {
864        let id = self.next_request_id();
865        let (response, receiver) = oneshot::channel();
866        self.send(Command::Leave {
867            id,
868            topic: self.topic.clone(),
869            timeout,
870            response,
871        })
872        .await?;
873        self.wait(id, ClientOperation::Leave, timeout, receiver)
874            .await
875    }
876
877    /// Returns the next event from this handle's original event stream.
878    pub async fn next_event(&mut self) -> Option<ChannelEvent> {
879        self.events.next().await
880    }
881
882    /// Creates an independent bounded channel event subscriber.
883    pub fn events(&self) -> Result<ChannelEvents, ClientError> {
884        let (events, receiver) = mpsc::channel(self.event_capacity);
885        let mut commands = self.commands.clone();
886        commands
887            .try_send(Command::SubscribeChannel {
888                topic: self.topic.clone(),
889                events,
890            })
891            .map_err(command_send_error)?;
892        Ok(ChannelEvents { receiver })
893    }
894
895    /// Subscribes to application messages with one event name.
896    pub fn subscribe(&self, event: impl Into<String>) -> Result<EventSubscription, ClientError> {
897        Ok(EventSubscription {
898            event: event.into(),
899            events: self.events()?,
900        })
901    }
902
903    /// Creates a synchronized Phoenix Presence consumer for this channel.
904    pub fn presence(&self) -> Result<ChannelPresence<'_>, ClientError> {
905        ChannelPresence::new(self)
906    }
907
908    async fn send(&self, command: Command) -> Result<(), ClientError> {
909        let mut commands = self.commands.clone();
910        commands
911            .send(command)
912            .await
913            .map_err(|_| ClientError::DriverStopped)
914    }
915
916    fn next_request_id(&self) -> RequestId {
917        let mut id = self.request_ids.get().wrapping_add(1);
918        if id == 0 {
919            id = 1;
920        }
921        self.request_ids.set(id);
922        id
923    }
924
925    async fn wait<T>(
926        &self,
927        id: RequestId,
928        operation: ClientOperation,
929        timeout_duration: Duration,
930        receiver: oneshot::Receiver<Result<T, ClientError>>,
931    ) -> Result<T, ClientError> {
932        let mut guard = RequestGuard {
933            id,
934            lifecycle: self.lifecycle.clone(),
935            armed: true,
936        };
937        let response = receiver.fuse();
938        let timeout = self.timer.sleep(timeout_duration).fuse();
939        futures::pin_mut!(response, timeout);
940        futures::select! {
941            response = response => {
942                guard.armed = false;
943                response.map_err(|_| ClientError::DriverStopped)?
944            },
945            () = timeout => Err(ClientError::Timeout {
946                operation,
947                timeout: timeout_duration,
948            }),
949        }
950    }
951}
952
953/// Independent bounded stream of [`ChannelEvent`] values.
954pub struct ChannelEvents {
955    receiver: mpsc::Receiver<ChannelEvent>,
956}
957
958/// Event returned by an application-event subscription.
959#[derive(Clone, Debug, PartialEq)]
960pub enum SubscriptionEvent {
961    /// A matching application message.
962    Message(Payload),
963    /// The underlying socket disconnected.
964    Disconnected,
965    /// The server sent `phx_error` for the channel.
966    ChannelError(Payload),
967    /// The server sent `phx_close` for the channel.
968    ChannelClosed(Payload),
969    /// The bounded event stream dropped messages.
970    ///
971    /// `dropped` is the number discarded before this notification.
972    Lagged {
973        /// Number of events dropped before this notification.
974        dropped: u64,
975    },
976}
977
978/// Filtered bounded subscription for one application event name.
979pub struct EventSubscription {
980    event: String,
981    events: ChannelEvents,
982}
983
984impl EventSubscription {
985    /// Returns the event name matched by this subscription.
986    pub fn event(&self) -> &str {
987        &self.event
988    }
989
990    /// Returns the next matching message or channel lifecycle event.
991    pub async fn next(&mut self) -> Option<SubscriptionEvent> {
992        loop {
993            match self.events.next().await? {
994                ChannelEvent::Protocol(ProtocolEvent::Message(frame))
995                    if frame.event == self.event =>
996                {
997                    return Some(SubscriptionEvent::Message(frame.payload));
998                }
999                ChannelEvent::Protocol(ProtocolEvent::ChannelError { payload, .. }) => {
1000                    return Some(SubscriptionEvent::ChannelError(payload));
1001                }
1002                ChannelEvent::Protocol(ProtocolEvent::ChannelClosed { payload, .. }) => {
1003                    return Some(SubscriptionEvent::ChannelClosed(payload));
1004                }
1005                ChannelEvent::Disconnected => return Some(SubscriptionEvent::Disconnected),
1006                ChannelEvent::Lagged { dropped } => {
1007                    return Some(SubscriptionEvent::Lagged { dropped });
1008                }
1009                ChannelEvent::Protocol(_) | ChannelEvent::JoinPayloadError(_) => {}
1010            }
1011        }
1012    }
1013}
1014
1015impl ChannelEvents {
1016    /// Returns the next channel event or `None` after the driver stops.
1017    pub async fn next(&mut self) -> Option<ChannelEvent> {
1018        self.receiver.next().await
1019    }
1020}
1021
1022impl Drop for Channel {
1023    fn drop(&mut self) {
1024        self.status.set(ChannelStatus::Closed);
1025        self.topics.borrow_mut().remove(&self.topic);
1026        let _ = self.lifecycle.unbounded_send(LifecycleCommand::Unregister {
1027            topic: self.topic.clone(),
1028        });
1029    }
1030}
1031
1032struct RequestGuard {
1033    id: RequestId,
1034    lifecycle: mpsc::UnboundedSender<LifecycleCommand>,
1035    armed: bool,
1036}
1037
1038impl Drop for RequestGuard {
1039    fn drop(&mut self) {
1040        if self.armed {
1041            let _ = self
1042                .lifecycle
1043                .unbounded_send(LifecycleCommand::Cancel { id: self.id });
1044        }
1045    }
1046}
1047
1048fn command_send_error<T>(error: mpsc::TrySendError<T>) -> ClientError {
1049    if error.is_full() {
1050        ClientError::CommandQueueFull
1051    } else {
1052        ClientError::DriverStopped
1053    }
1054}
1055
1056/// A future that owns all connection and protocol state.
1057///
1058/// It must be polled continuously by the host executor.
1059#[must_use = "the driver must be spawned or awaited"]
1060pub struct Driver {
1061    inner: LocalBoxFuture<'static, ()>,
1062}
1063
1064impl Future for Driver {
1065    type Output = ();
1066
1067    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1068        self.inner.as_mut().poll(cx)
1069    }
1070}
1071
1072enum LifecycleCommand {
1073    Register {
1074        topic: String,
1075        payload_loader: JoinPayloadLoader,
1076        events: mpsc::Sender<ChannelEvent>,
1077        status: Rc<ObservableStatus<ChannelStatus>>,
1078    },
1079    Unregister {
1080        topic: String,
1081    },
1082    Cancel {
1083        id: RequestId,
1084    },
1085}
1086
1087enum Command {
1088    Connect {
1089        response: oneshot::Sender<()>,
1090    },
1091    Disconnect {
1092        close: Option<TransportCloseRequest>,
1093        response: oneshot::Sender<()>,
1094    },
1095    Subscribe {
1096        events: mpsc::Sender<SocketEvent>,
1097    },
1098    SubscribeChannel {
1099        topic: String,
1100        events: mpsc::Sender<ChannelEvent>,
1101    },
1102    Ping {
1103        id: RequestId,
1104        response: oneshot::Sender<Result<Duration, ClientError>>,
1105    },
1106    Join {
1107        id: RequestId,
1108        topic: String,
1109        timeout: Duration,
1110        response: oneshot::Sender<Result<Payload, ClientError>>,
1111    },
1112    Call {
1113        id: RequestId,
1114        topic: String,
1115        event: String,
1116        payload: Payload,
1117        started: Duration,
1118        response: oneshot::Sender<Result<Reply, ClientError>>,
1119    },
1120    Cast {
1121        id: RequestId,
1122        topic: String,
1123        event: String,
1124        payload: Payload,
1125        response: oneshot::Sender<Result<(), ClientError>>,
1126    },
1127    Leave {
1128        id: RequestId,
1129        topic: String,
1130        timeout: Duration,
1131        response: oneshot::Sender<Result<Payload, ClientError>>,
1132    },
1133    Shutdown {
1134        response: oneshot::Sender<()>,
1135    },
1136}
1137
1138enum QueuedPush {
1139    Call {
1140        id: RequestId,
1141        event: String,
1142        payload: Payload,
1143        started: Duration,
1144        response: oneshot::Sender<Result<Reply, ClientError>>,
1145    },
1146    Cast {
1147        id: RequestId,
1148        event: String,
1149        payload: Payload,
1150        response: oneshot::Sender<Result<(), ClientError>>,
1151    },
1152}
1153
1154impl QueuedPush {
1155    fn id(&self) -> RequestId {
1156        match self {
1157            Self::Call { id, .. } | Self::Cast { id, .. } => *id,
1158        }
1159    }
1160
1161    fn interrupt(self) {
1162        let operation = match &self {
1163            Self::Call { .. } => ClientOperation::Call,
1164            Self::Cast { .. } => ClientOperation::Cast,
1165        };
1166        self.fail(ClientError::Interrupted { operation });
1167    }
1168
1169    fn fail(self, error: ClientError) {
1170        match self {
1171            Self::Call { response, .. } => {
1172                let _ = response.send(Err(error));
1173            }
1174            Self::Cast { response, .. } => {
1175                let _ = response.send(Err(error));
1176            }
1177        }
1178    }
1179}
1180
1181struct ChannelRecord {
1182    payload_loader: JoinPayloadLoader,
1183    subscribers: Vec<EventSubscriber<ChannelEvent>>,
1184    status: Rc<ObservableStatus<ChannelStatus>>,
1185    desired: bool,
1186    ever_joined: bool,
1187    active_payload: Option<u64>,
1188    join_attempt: u32,
1189    join_timeout: Duration,
1190    rejoin_scheduled: bool,
1191    join_waiters: HashMap<RequestId, oneshot::Sender<Result<Payload, ClientError>>>,
1192    queued: VecDeque<QueuedPush>,
1193    deferred_leave: Option<PendingLeave>,
1194}
1195
1196struct EventSubscriber<T> {
1197    sender: mpsc::Sender<T>,
1198    dropped: u64,
1199}
1200
1201struct PendingCall {
1202    id: RequestId,
1203    topic: String,
1204    event: String,
1205    started: Duration,
1206    response: oneshot::Sender<Result<Reply, ClientError>>,
1207}
1208
1209struct PendingPing {
1210    id: RequestId,
1211    started: Duration,
1212    response: oneshot::Sender<Result<Duration, ClientError>>,
1213}
1214
1215struct PendingLeave {
1216    id: RequestId,
1217    timeout: Duration,
1218    response: Option<oneshot::Sender<Result<Payload, ClientError>>>,
1219}
1220
1221type PayloadResult = (String, u64, Result<Value, String>);
1222
1223enum OperationTimeout {
1224    Join {
1225        topic: String,
1226        reference: String,
1227        duration: Duration,
1228    },
1229    Leave {
1230        topic: String,
1231        reference: String,
1232        duration: Duration,
1233    },
1234}
1235
1236struct DriverState {
1237    connector: Rc<dyn Connector>,
1238    timer: Rc<dyn Timer>,
1239    options: Options,
1240    codec: Rc<dyn Codec>,
1241    commands: mpsc::Receiver<Command>,
1242    lifecycle: mpsc::UnboundedReceiver<LifecycleCommand>,
1243    protocol: Protocol,
1244    channels: HashMap<String, ChannelRecord>,
1245    socket_subscribers: Vec<EventSubscriber<SocketEvent>>,
1246    pending_joins: HashMap<String, String>,
1247    pending_calls: HashMap<String, PendingCall>,
1248    pending_pings: HashMap<String, PendingPing>,
1249    pending_leaves: HashMap<String, PendingLeave>,
1250    next_payload_id: u64,
1251    socket_status: Rc<ObservableStatus<SocketStatus>>,
1252}
1253
1254impl DriverState {
1255    fn new(
1256        connector: Rc<dyn Connector>,
1257        timer: Rc<dyn Timer>,
1258        options: Options,
1259        codec: Rc<dyn Codec>,
1260        commands: mpsc::Receiver<Command>,
1261        lifecycle: mpsc::UnboundedReceiver<LifecycleCommand>,
1262        socket_status: Rc<ObservableStatus<SocketStatus>>,
1263    ) -> Self {
1264        Self {
1265            connector,
1266            timer,
1267            options,
1268            codec,
1269            commands,
1270            lifecycle,
1271            protocol: Protocol::new(),
1272            channels: HashMap::new(),
1273            socket_subscribers: Vec::new(),
1274            pending_joins: HashMap::new(),
1275            pending_calls: HashMap::new(),
1276            pending_pings: HashMap::new(),
1277            pending_leaves: HashMap::new(),
1278            next_payload_id: 0,
1279            socket_status,
1280        }
1281    }
1282
1283    async fn run(mut self) {
1284        let mut connection_enabled = self.options.connect_on_start;
1285        let mut attempt = 0;
1286        loop {
1287            if !connection_enabled {
1288                match self.wait_idle().await {
1289                    IdleExit::Connect(response) => {
1290                        let _ = response.send(());
1291                        connection_enabled = true;
1292                        attempt = 0;
1293                    }
1294                    IdleExit::Shutdown(response) => {
1295                        let _ = response.send(());
1296                        self.emit_socket(SocketEvent::Closed);
1297                        return;
1298                    }
1299                }
1300            }
1301
1302            self.emit_socket(SocketEvent::Connecting { attempt });
1303            let connection_started = self.timer.now();
1304            let connection = self.connector.connect(ConnectContext { attempt }).fuse();
1305            let connect_timeout = self.timer.sleep(self.options.connect_timeout).fuse();
1306            futures::pin_mut!(connection, connect_timeout);
1307            let connected = loop {
1308                let lifecycle = self.lifecycle.next().fuse();
1309                let command = self.commands.next().fuse();
1310                futures::pin_mut!(lifecycle, command);
1311                futures::select_biased! {
1312                    lifecycle = lifecycle => match lifecycle {
1313                        Some(command) => self.handle_offline_lifecycle(command),
1314                        None => break None,
1315                    },
1316                    result = connection => break Some(ConnectAttemptExit::Connected(result)),
1317                    () = connect_timeout => break Some(ConnectAttemptExit::Connected(Err(TransportError::with_kind(
1318                        TransportErrorKind::Connect,
1319                        format!("connection attempt timed out after {:?}", self.options.connect_timeout),
1320                    )))),
1321                    command = command => match command {
1322                        Some(Command::Connect { response }) => {
1323                            let _ = response.send(());
1324                        }
1325                        Some(Command::Disconnect { response, .. }) => {
1326                            break Some(ConnectAttemptExit::Disconnect(response));
1327                        }
1328                        Some(Command::Shutdown { response }) => {
1329                            break Some(ConnectAttemptExit::Shutdown(response));
1330                        }
1331                        Some(command) => {
1332                            self.handle_offline_command(command);
1333                        }
1334                        None => break None,
1335                    }
1336                }
1337            };
1338            let Some(connected) = connected else {
1339                self.emit_socket(SocketEvent::Closed);
1340                return;
1341            };
1342            self.telemetry(TelemetryEvent::ConnectionAttemptFinished {
1343                attempt,
1344                duration: self.timer.now().saturating_sub(connection_started),
1345                connected: matches!(&connected, ConnectAttemptExit::Connected(Ok(_))),
1346            });
1347
1348            let retry_reason = match connected {
1349                ConnectAttemptExit::Connected(Ok(mut transport)) => {
1350                    attempt = 0;
1351                    self.emit_socket(SocketEvent::Connected);
1352                    match self.run_connected(&mut transport).await {
1353                        ConnectedExit::Shutdown(response) => {
1354                            let _ = transport.close().await;
1355                            let _ = response.send(());
1356                            self.emit_socket(SocketEvent::Closed);
1357                            return;
1358                        }
1359                        ConnectedExit::Disconnect { response, close } => {
1360                            if let Some(close) = close {
1361                                let _ = transport.close_with(close).await;
1362                            } else {
1363                                let _ = transport.close().await;
1364                            }
1365                            self.on_disconnect(&DisconnectReason::Requested);
1366                            let _ = response.send(());
1367                            connection_enabled = false;
1368                            continue;
1369                        }
1370                        ConnectedExit::Disconnected(reason) => {
1371                            let _ = transport.close().await;
1372                            self.on_disconnect(&reason);
1373                            reason
1374                        }
1375                    }
1376                }
1377                ConnectAttemptExit::Connected(Err(error)) => {
1378                    let reason = DisconnectReason::Connect(error);
1379                    self.emit_socket(SocketEvent::Disconnected {
1380                        reason: reason.clone(),
1381                    });
1382                    reason
1383                }
1384                ConnectAttemptExit::Disconnect(response) => {
1385                    self.on_disconnect(&DisconnectReason::Requested);
1386                    let _ = response.send(());
1387                    connection_enabled = false;
1388                    continue;
1389                }
1390                ConnectAttemptExit::Shutdown(response) => {
1391                    let _ = response.send(());
1392                    self.emit_socket(SocketEvent::Closed);
1393                    return;
1394                }
1395            };
1396
1397            attempt = attempt.saturating_add(1);
1398            let action = self.options.reconnect_policy.as_ref().map_or_else(
1399                || {
1400                    if retry_reason.should_reconnect() {
1401                        ReconnectAction::RetryAfter((self.options.reconnect_delay)(attempt))
1402                    } else {
1403                        ReconnectAction::Stop
1404                    }
1405                },
1406                |policy| {
1407                    policy(ReconnectContext {
1408                        attempt,
1409                        reason: retry_reason.clone(),
1410                    })
1411                },
1412            );
1413            let ReconnectAction::RetryAfter(delay) = action else {
1414                self.emit_socket(SocketEvent::ReconnectStopped {
1415                    attempt,
1416                    reason: retry_reason,
1417                });
1418                connection_enabled = false;
1419                continue;
1420            };
1421            self.emit_socket(SocketEvent::ReconnectScheduled { attempt, delay });
1422            match self.wait_offline(delay).await {
1423                OfflineExit::Retry => {}
1424                OfflineExit::Disconnect(response) => {
1425                    self.on_disconnect(&DisconnectReason::Requested);
1426                    let _ = response.send(());
1427                    connection_enabled = false;
1428                }
1429                OfflineExit::Shutdown(response) => {
1430                    let _ = response.send(());
1431                    self.emit_socket(SocketEvent::Closed);
1432                    return;
1433                }
1434            }
1435        }
1436    }
1437
1438    async fn wait_idle(&mut self) -> IdleExit {
1439        loop {
1440            let lifecycle = self.lifecycle.next().fuse();
1441            let command = self.commands.next().fuse();
1442            futures::pin_mut!(lifecycle, command);
1443            futures::select_biased! {
1444                lifecycle = lifecycle => match lifecycle {
1445                    Some(command) => self.handle_offline_lifecycle(command),
1446                    None => return IdleExit::Shutdown(closed_response()),
1447                },
1448                command = command => match command {
1449                    Some(Command::Connect { response }) => return IdleExit::Connect(response),
1450                    Some(Command::Disconnect { response, .. }) => {
1451                        let _ = response.send(());
1452                    }
1453                    Some(Command::Shutdown { response }) => return IdleExit::Shutdown(response),
1454                    Some(command) => {
1455                        self.handle_offline_command(command);
1456                    }
1457                    None => return IdleExit::Shutdown(closed_response()),
1458                }
1459            }
1460        }
1461    }
1462
1463    async fn wait_offline(&mut self, delay: Duration) -> OfflineExit {
1464        let sleep = self.timer.sleep(delay).fuse();
1465        futures::pin_mut!(sleep);
1466        loop {
1467            let lifecycle = self.lifecycle.next().fuse();
1468            let command = self.commands.next().fuse();
1469            futures::pin_mut!(lifecycle, command);
1470            futures::select_biased! {
1471                lifecycle = lifecycle => match lifecycle {
1472                    Some(command) => self.handle_offline_lifecycle(command),
1473                    None => return OfflineExit::Shutdown(closed_response()),
1474                },
1475                () = sleep => return OfflineExit::Retry,
1476                command = command => match command {
1477                    Some(Command::Connect { response }) => {
1478                        let _ = response.send(());
1479                    }
1480                    Some(Command::Disconnect { response, .. }) => {
1481                        return OfflineExit::Disconnect(response);
1482                    }
1483                    Some(Command::Shutdown { response }) => return OfflineExit::Shutdown(response),
1484                    Some(command) => {
1485                        self.handle_offline_command(command);
1486                    }
1487                    None => return OfflineExit::Shutdown(closed_response()),
1488                }
1489            }
1490        }
1491    }
1492
1493    async fn run_connected(&mut self, transport: &mut Box<dyn Transport>) -> ConnectedExit {
1494        let mut payloads: FuturesUnordered<LocalBoxFuture<'static, PayloadResult>> =
1495            FuturesUnordered::new();
1496        let mut rejoins: FuturesUnordered<LocalBoxFuture<'static, String>> =
1497            FuturesUnordered::new();
1498        let mut operation_timeouts: FuturesUnordered<LocalBoxFuture<'static, OperationTimeout>> =
1499            FuturesUnordered::new();
1500        for channel in self.channels.values_mut() {
1501            channel.rejoin_scheduled = false;
1502        }
1503        let desired = self
1504            .channels
1505            .iter()
1506            .filter(|(_, channel)| channel.desired)
1507            .map(|(topic, _)| topic.clone())
1508            .collect::<Vec<_>>();
1509        for topic in desired {
1510            if let Some(channel) = self.channels.get(&topic) {
1511                channel.status.set(ChannelStatus::WaitingToJoin);
1512            }
1513            self.load_payload(&topic, &mut payloads);
1514        }
1515
1516        let mut heartbeat = self.timer.sleep(self.options.heartbeat_interval);
1517        let mut heartbeat_reference: Option<String> = None;
1518
1519        loop {
1520            enum Action {
1521                Lifecycle(Option<LifecycleCommand>),
1522                Command(Option<Command>),
1523                Incoming(Result<TransportEvent, TransportError>),
1524                Heartbeat,
1525                Payload(Option<PayloadResult>),
1526                Rejoin(Option<String>),
1527                OperationTimeout(Option<OperationTimeout>),
1528            }
1529
1530            let action = {
1531                let lifecycle = self.lifecycle.next().fuse();
1532                let command = self.commands.next().fuse();
1533                let incoming = transport.receive().fuse();
1534                let heartbeat_wait = heartbeat.as_mut().fuse();
1535                let payload: LocalBoxFuture<'_, Option<PayloadResult>> = if payloads.is_empty() {
1536                    Box::pin(pending())
1537                } else {
1538                    Box::pin(payloads.next())
1539                };
1540                let payload = payload.fuse();
1541                let rejoin: LocalBoxFuture<'_, Option<String>> = if rejoins.is_empty() {
1542                    Box::pin(pending())
1543                } else {
1544                    Box::pin(rejoins.next())
1545                };
1546                let rejoin = rejoin.fuse();
1547                let operation_timeout: LocalBoxFuture<'_, Option<OperationTimeout>> =
1548                    if operation_timeouts.is_empty() {
1549                        Box::pin(pending())
1550                    } else {
1551                        Box::pin(operation_timeouts.next())
1552                    };
1553                let operation_timeout = operation_timeout.fuse();
1554                futures::pin_mut!(
1555                    lifecycle,
1556                    command,
1557                    incoming,
1558                    heartbeat_wait,
1559                    payload,
1560                    rejoin,
1561                    operation_timeout
1562                );
1563                futures::select_biased! {
1564                    lifecycle = lifecycle => Action::Lifecycle(lifecycle),
1565                    command = command => Action::Command(command),
1566                    incoming = incoming => Action::Incoming(incoming),
1567                    () = heartbeat_wait => Action::Heartbeat,
1568                    payload = payload => Action::Payload(payload),
1569                    topic = rejoin => Action::Rejoin(topic),
1570                    timeout = operation_timeout => Action::OperationTimeout(timeout),
1571                }
1572            };
1573
1574            let result = match action {
1575                Action::Lifecycle(Some(command)) => {
1576                    self.handle_connected_lifecycle(command, transport).await
1577                }
1578                Action::Lifecycle(None) => {
1579                    return ConnectedExit::Disconnected(DisconnectReason::DriverStopped);
1580                }
1581                Action::Command(Some(Command::Shutdown { response })) => {
1582                    return ConnectedExit::Shutdown(response);
1583                }
1584                Action::Command(Some(Command::Disconnect { close, response })) => {
1585                    return ConnectedExit::Disconnect { response, close };
1586                }
1587                Action::Command(Some(Command::Connect { response })) => {
1588                    let _ = response.send(());
1589                    Ok(())
1590                }
1591                Action::Command(Some(command)) => {
1592                    self.handle_connected_command(
1593                        command,
1594                        transport,
1595                        &mut payloads,
1596                        &mut operation_timeouts,
1597                    )
1598                    .await
1599                }
1600                Action::Command(None) => {
1601                    return ConnectedExit::Disconnected(DisconnectReason::DriverStopped);
1602                }
1603                Action::Incoming(Ok(TransportEvent::Message(message))) => {
1604                    let awaiting_heartbeat = heartbeat_reference.is_some();
1605                    let result = self
1606                        .handle_incoming(
1607                            message,
1608                            transport,
1609                            &mut payloads,
1610                            &mut rejoins,
1611                            &mut operation_timeouts,
1612                            &mut heartbeat_reference,
1613                        )
1614                        .await;
1615                    if result.is_ok() && awaiting_heartbeat && heartbeat_reference.is_none() {
1616                        heartbeat = self.timer.sleep(self.options.heartbeat_interval);
1617                    }
1618                    result
1619                }
1620                Action::Incoming(Ok(TransportEvent::Closed(close))) => {
1621                    Err(DisconnectReason::Closed(close))
1622                }
1623                Action::Incoming(Err(error)) => Err(DisconnectReason::Transport(error)),
1624                Action::Heartbeat => {
1625                    if heartbeat_reference.is_some() {
1626                        Err(DisconnectReason::HeartbeatTimeout)
1627                    } else {
1628                        let outbound = self.protocol.heartbeat();
1629                        heartbeat_reference = Some(outbound.reference);
1630                        let result = self.send_frame(transport, outbound.frame).await;
1631                        if result.is_ok() {
1632                            heartbeat = self.timer.sleep(self.options.heartbeat_timeout);
1633                        }
1634                        result
1635                    }
1636                }
1637                Action::Payload(Some((topic, payload_id, payload))) => {
1638                    self.handle_payload(
1639                        topic,
1640                        payload_id,
1641                        payload,
1642                        transport,
1643                        &mut operation_timeouts,
1644                    )
1645                    .await
1646                }
1647                Action::Payload(None) => Ok(()),
1648                Action::Rejoin(Some(topic)) => {
1649                    let desired = self.channels.get_mut(&topic).is_some_and(|channel| {
1650                        channel.rejoin_scheduled = false;
1651                        channel.desired
1652                    });
1653                    if desired {
1654                        self.load_payload(&topic, &mut payloads);
1655                    }
1656                    Ok(())
1657                }
1658                Action::Rejoin(None) => Ok(()),
1659                Action::OperationTimeout(Some(timeout)) => {
1660                    self.handle_operation_timeout(timeout, &mut payloads, &mut rejoins)
1661                }
1662                Action::OperationTimeout(None) => Ok(()),
1663            };
1664
1665            if let Err(reason) = result {
1666                return ConnectedExit::Disconnected(reason);
1667            }
1668        }
1669    }
1670
1671    fn handle_offline_lifecycle(&mut self, command: LifecycleCommand) {
1672        match command {
1673            LifecycleCommand::Register {
1674                topic,
1675                payload_loader,
1676                events,
1677                status,
1678            } => self.register(topic, payload_loader, events, status),
1679            LifecycleCommand::Unregister { topic } => self.unregister(&topic),
1680            LifecycleCommand::Cancel { id } => self.cancel(id),
1681        }
1682    }
1683
1684    fn handle_offline_command(&mut self, command: Command) -> bool {
1685        match command {
1686            Command::Connect { .. } | Command::Disconnect { .. } => {
1687                unreachable!("connection controls are handled by the driver loop")
1688            }
1689            Command::Subscribe { events } => self.socket_subscribers.push(EventSubscriber {
1690                sender: events,
1691                dropped: 0,
1692            }),
1693            Command::SubscribeChannel { topic, events } => self.subscribe_channel(&topic, events),
1694            Command::Ping { response, .. } => {
1695                let _ = response.send(Err(ClientError::SocketNotConnected));
1696            }
1697            Command::Join {
1698                id,
1699                topic,
1700                timeout,
1701                response,
1702            } => {
1703                if let Some(channel) = self.channels.get_mut(&topic) {
1704                    channel.desired = true;
1705                    channel.join_timeout = channel.join_timeout.max(timeout);
1706                    channel.status.set(ChannelStatus::WaitingForSocket);
1707                    channel.join_waiters.insert(id, response);
1708                } else {
1709                    let _ = response.send(Err(ClientError::UnknownTopic(topic)));
1710                }
1711            }
1712            Command::Call {
1713                id,
1714                topic,
1715                event,
1716                payload,
1717                started,
1718                response,
1719            } => self.queue(
1720                &topic,
1721                QueuedPush::Call {
1722                    id,
1723                    event,
1724                    payload,
1725                    started,
1726                    response,
1727                },
1728            ),
1729            Command::Cast {
1730                id,
1731                topic,
1732                event,
1733                payload,
1734                response,
1735            } => self.queue(
1736                &topic,
1737                QueuedPush::Cast {
1738                    id,
1739                    event,
1740                    payload,
1741                    response,
1742                },
1743            ),
1744            Command::Leave {
1745                topic, response, ..
1746            } => {
1747                if let Some(channel) = self.channels.get(&topic) {
1748                    channel.status.set(ChannelStatus::Left);
1749                }
1750                self.stop_channel(&topic);
1751                let _ = response.send(Ok(json!({}).into()));
1752            }
1753            Command::Shutdown { response } => {
1754                let _ = response.send(());
1755                return true;
1756            }
1757        }
1758        false
1759    }
1760
1761    async fn handle_connected_lifecycle(
1762        &mut self,
1763        command: LifecycleCommand,
1764        transport: &mut Box<dyn Transport>,
1765    ) -> Result<(), DisconnectReason> {
1766        match command {
1767            LifecycleCommand::Register {
1768                topic,
1769                payload_loader,
1770                events,
1771                status,
1772            } => self.register(topic, payload_loader, events, status),
1773            LifecycleCommand::Unregister { topic } => {
1774                let leave = if self.protocol.channel_state(&topic) == Some(ChannelState::Joined) {
1775                    self.protocol
1776                        .leave(&topic)
1777                        .ok()
1778                        .map(|outbound| outbound.frame)
1779                } else {
1780                    None
1781                };
1782                self.unregister(&topic);
1783                if let Some(frame) = leave {
1784                    self.send_frame(transport, frame).await?;
1785                }
1786            }
1787            LifecycleCommand::Cancel { id } => self.cancel(id),
1788        }
1789        Ok(())
1790    }
1791
1792    async fn handle_connected_command(
1793        &mut self,
1794        command: Command,
1795        transport: &mut Box<dyn Transport>,
1796        payloads: &mut FuturesUnordered<LocalBoxFuture<'static, PayloadResult>>,
1797        operation_timeouts: &mut FuturesUnordered<LocalBoxFuture<'static, OperationTimeout>>,
1798    ) -> Result<(), DisconnectReason> {
1799        match command {
1800            Command::Subscribe { events } => self.socket_subscribers.push(EventSubscriber {
1801                sender: events,
1802                dropped: 0,
1803            }),
1804            Command::SubscribeChannel { topic, events } => self.subscribe_channel(&topic, events),
1805            Command::Ping { id, response } => {
1806                let outbound = self.protocol.heartbeat();
1807                self.pending_pings.insert(
1808                    outbound.reference.clone(),
1809                    PendingPing {
1810                        id,
1811                        started: self.timer.now(),
1812                        response,
1813                    },
1814                );
1815                self.send_frame(transport, outbound.frame).await?;
1816            }
1817            Command::Join {
1818                id,
1819                topic,
1820                timeout,
1821                response,
1822            } => {
1823                let joined = self.protocol.channel_state(&topic) == Some(ChannelState::Joined);
1824                if let Some(channel) = self.channels.get_mut(&topic) {
1825                    channel.desired = true;
1826                    if joined {
1827                        channel.status.set(ChannelStatus::Joined);
1828                        let _ = response.send(Err(ClientError::AlreadyJoined(topic)));
1829                    } else {
1830                        channel.status.set(ChannelStatus::WaitingToJoin);
1831                        channel.join_timeout = channel.join_timeout.max(timeout);
1832                        channel.join_waiters.insert(id, response);
1833                        self.load_payload(&topic, payloads);
1834                    }
1835                } else {
1836                    let _ = response.send(Err(ClientError::UnknownTopic(topic)));
1837                }
1838            }
1839            Command::Call {
1840                id,
1841                topic,
1842                event,
1843                payload,
1844                started,
1845                response,
1846            } => {
1847                let push = QueuedPush::Call {
1848                    id,
1849                    event,
1850                    payload,
1851                    started,
1852                    response,
1853                };
1854                if self.protocol.channel_state(&topic) == Some(ChannelState::Joined) {
1855                    self.send_push(&topic, push, transport).await?;
1856                } else {
1857                    self.queue(&topic, push);
1858                }
1859            }
1860            Command::Cast {
1861                id,
1862                topic,
1863                event,
1864                payload,
1865                response,
1866            } => {
1867                let push = QueuedPush::Cast {
1868                    id,
1869                    event,
1870                    payload,
1871                    response,
1872                };
1873                if self.protocol.channel_state(&topic) == Some(ChannelState::Joined) {
1874                    self.send_push(&topic, push, transport).await?;
1875                } else {
1876                    self.queue(&topic, push);
1877                }
1878            }
1879            Command::Leave {
1880                id,
1881                topic,
1882                timeout,
1883                response,
1884            } => {
1885                let state = self.protocol.channel_state(&topic);
1886                if let Some(channel) = self.channels.get(&topic) {
1887                    let status = match state {
1888                        Some(ChannelState::Joined | ChannelState::Joining) => {
1889                            ChannelStatus::Leaving
1890                        }
1891                        _ => ChannelStatus::Left,
1892                    };
1893                    channel.status.set(status);
1894                }
1895                self.stop_channel(&topic);
1896                if state == Some(ChannelState::Joined) {
1897                    let outbound = self
1898                        .protocol
1899                        .leave(&topic)
1900                        .map_err(|error| DisconnectReason::Protocol(error.to_string()))?;
1901                    self.pending_leaves.insert(
1902                        outbound.reference.clone(),
1903                        PendingLeave {
1904                            id,
1905                            timeout,
1906                            response: Some(response),
1907                        },
1908                    );
1909                    self.schedule_operation_timeout(
1910                        OperationTimeout::Leave {
1911                            topic,
1912                            reference: outbound.reference.clone(),
1913                            duration: timeout,
1914                        },
1915                        operation_timeouts,
1916                    );
1917                    self.send_frame(transport, outbound.frame).await?;
1918                } else if state == Some(ChannelState::Joining) {
1919                    if let Some(channel) = self.channels.get_mut(&topic) {
1920                        channel.deferred_leave = Some(PendingLeave {
1921                            id,
1922                            timeout,
1923                            response: Some(response),
1924                        });
1925                    } else {
1926                        let _ = response.send(Ok(json!({}).into()));
1927                    }
1928                } else {
1929                    let _ = response.send(Ok(json!({}).into()));
1930                }
1931            }
1932            Command::Shutdown { .. } => unreachable!("handled by run_connected"),
1933            Command::Connect { .. } | Command::Disconnect { .. } => {
1934                unreachable!("connection controls are handled by run_connected")
1935            }
1936        }
1937        Ok(())
1938    }
1939
1940    fn register(
1941        &mut self,
1942        topic: String,
1943        payload_loader: JoinPayloadLoader,
1944        events: mpsc::Sender<ChannelEvent>,
1945        status: Rc<ObservableStatus<ChannelStatus>>,
1946    ) {
1947        self.channels.entry(topic).or_insert(ChannelRecord {
1948            payload_loader,
1949            subscribers: vec![EventSubscriber {
1950                sender: events,
1951                dropped: 0,
1952            }],
1953            status,
1954            desired: false,
1955            ever_joined: false,
1956            active_payload: None,
1957            join_attempt: 0,
1958            join_timeout: self.options.join_timeout,
1959            rejoin_scheduled: false,
1960            join_waiters: HashMap::new(),
1961            queued: VecDeque::new(),
1962            deferred_leave: None,
1963        });
1964    }
1965
1966    fn subscribe_channel(&mut self, topic: &str, events: mpsc::Sender<ChannelEvent>) {
1967        if let Some(channel) = self.channels.get_mut(topic) {
1968            channel.subscribers.push(EventSubscriber {
1969                sender: events,
1970                dropped: 0,
1971            });
1972        }
1973    }
1974
1975    fn unregister(&mut self, topic: &str) {
1976        self.stop_channel(topic);
1977        self.channels.remove(topic);
1978        self.protocol.discard_channel(topic);
1979    }
1980
1981    fn queue(&mut self, topic: &str, push: QueuedPush) {
1982        if let Some(channel) = self.channels.get_mut(topic) {
1983            if channel.ever_joined && !channel.desired {
1984                push.fail(ClientError::ChannelNotJoined(topic.to_owned()));
1985                return;
1986            }
1987            if channel.queued.len() < self.options.push_buffer_capacity {
1988                channel.queued.push_back(push);
1989            } else {
1990                push.fail(ClientError::PushBufferFull(topic.to_owned()));
1991            }
1992        } else {
1993            push.interrupt();
1994        }
1995    }
1996
1997    fn load_payload(
1998        &mut self,
1999        topic: &str,
2000        payloads: &mut FuturesUnordered<LocalBoxFuture<'static, PayloadResult>>,
2001    ) {
2002        let Some(channel) = self.channels.get_mut(topic) else {
2003            return;
2004        };
2005        if !channel.desired || channel.active_payload.is_some() {
2006            return;
2007        }
2008        if matches!(
2009            self.protocol.channel_state(topic),
2010            Some(ChannelState::Joining | ChannelState::Joined | ChannelState::Leaving)
2011        ) {
2012            return;
2013        }
2014        channel.status.set(ChannelStatus::WaitingToJoin);
2015        self.next_payload_id = self.next_payload_id.wrapping_add(1);
2016        if self.next_payload_id == 0 {
2017            self.next_payload_id = 1;
2018        }
2019        let payload_id = self.next_payload_id;
2020        channel.active_payload = Some(payload_id);
2021        let context = JoinContext {
2022            attempt: channel.join_attempt,
2023            is_rejoin: channel.ever_joined,
2024        };
2025        let loader = channel.payload_loader.clone();
2026        let topic = topic.to_owned();
2027        payloads.push(Box::pin(async move {
2028            let result = loader(context).await;
2029            (topic, payload_id, result)
2030        }));
2031    }
2032
2033    async fn handle_payload(
2034        &mut self,
2035        topic: String,
2036        payload_id: u64,
2037        payload: Result<Value, String>,
2038        transport: &mut Box<dyn Transport>,
2039        operation_timeouts: &mut FuturesUnordered<LocalBoxFuture<'static, OperationTimeout>>,
2040    ) -> Result<(), DisconnectReason> {
2041        let Some(channel) = self.channels.get_mut(&topic) else {
2042            return Ok(());
2043        };
2044        if channel.active_payload != Some(payload_id) {
2045            return Ok(());
2046        }
2047        channel.active_payload = None;
2048        if !channel.desired {
2049            return Ok(());
2050        }
2051        let payload = match payload {
2052            Ok(payload) => payload,
2053            Err(error) => {
2054                channel.desired = false;
2055                channel.status.set(ChannelStatus::Errored);
2056                for (_, waiter) in channel.join_waiters.drain() {
2057                    let _ = waiter.send(Err(ClientError::JoinPayload {
2058                        topic: topic.clone(),
2059                        message: error.clone(),
2060                    }));
2061                }
2062                self.emit_channel(&topic, ChannelEvent::JoinPayloadError(error));
2063                return Ok(());
2064            }
2065        };
2066        let join_timeout = channel.join_timeout;
2067        let outbound = match self.protocol.channel_state(&topic) {
2068            Some(ChannelState::Disconnected | ChannelState::Errored | ChannelState::Closed) => {
2069                self.protocol.rejoin(&topic, payload)
2070            }
2071            None => self.protocol.join(&topic, payload),
2072            Some(_) => return Ok(()),
2073        }
2074        .map_err(|error| DisconnectReason::Protocol(error.to_string()))?;
2075        if let Some(channel) = self.channels.get(&topic) {
2076            channel.status.set(ChannelStatus::Joining);
2077        }
2078        self.pending_joins
2079            .insert(outbound.reference.clone(), topic.clone());
2080        self.schedule_operation_timeout(
2081            OperationTimeout::Join {
2082                topic,
2083                reference: outbound.reference.clone(),
2084                duration: join_timeout,
2085            },
2086            operation_timeouts,
2087        );
2088        self.send_frame(transport, outbound.frame).await
2089    }
2090
2091    async fn handle_incoming(
2092        &mut self,
2093        message: WireMessage,
2094        transport: &mut Box<dyn Transport>,
2095        payloads: &mut FuturesUnordered<LocalBoxFuture<'static, PayloadResult>>,
2096        rejoins: &mut FuturesUnordered<LocalBoxFuture<'static, String>>,
2097        operation_timeouts: &mut FuturesUnordered<LocalBoxFuture<'static, OperationTimeout>>,
2098        heartbeat_reference: &mut Option<String>,
2099    ) -> Result<(), DisconnectReason> {
2100        let (binary, bytes) = wire_metadata(&message);
2101        let frame = self
2102            .codec
2103            .decode(message)
2104            .map_err(|error| DisconnectReason::Protocol(error.to_string()))?;
2105        self.telemetry(TelemetryEvent::FrameReceived {
2106            topic: frame.topic.clone(),
2107            event: frame.event.clone(),
2108            binary,
2109            bytes,
2110        });
2111        let event = self
2112            .protocol
2113            .receive(frame)
2114            .map_err(|error| DisconnectReason::Protocol(error.to_string()))?;
2115
2116        match &event {
2117            ProtocolEvent::Joined {
2118                topic,
2119                reference,
2120                response,
2121            } => {
2122                self.pending_joins.remove(reference);
2123                let deferred_leave = if let Some(channel) = self.channels.get_mut(topic) {
2124                    channel.ever_joined = true;
2125                    channel.join_attempt = 0;
2126                    channel.rejoin_scheduled = false;
2127                    let deferred_leave = channel.deferred_leave.take();
2128                    channel.status.set(if deferred_leave.is_some() {
2129                        ChannelStatus::Leaving
2130                    } else {
2131                        ChannelStatus::Joined
2132                    });
2133                    for (_, waiter) in channel.join_waiters.drain() {
2134                        let _ = waiter.send(Ok(response.clone()));
2135                    }
2136                    deferred_leave
2137                } else {
2138                    None
2139                };
2140                self.emit_channel(topic, ChannelEvent::Protocol(event.clone()));
2141                if let Some(pending) = deferred_leave {
2142                    let timeout = pending.timeout;
2143                    let outbound = self
2144                        .protocol
2145                        .leave(topic)
2146                        .map_err(|error| DisconnectReason::Protocol(error.to_string()))?;
2147                    self.pending_leaves
2148                        .insert(outbound.reference.clone(), pending);
2149                    self.schedule_operation_timeout(
2150                        OperationTimeout::Leave {
2151                            topic: topic.clone(),
2152                            reference: outbound.reference.clone(),
2153                            duration: timeout,
2154                        },
2155                        operation_timeouts,
2156                    );
2157                    self.send_frame(transport, outbound.frame).await?;
2158                } else {
2159                    self.flush(topic, transport).await?;
2160                }
2161            }
2162            ProtocolEvent::JoinError {
2163                topic,
2164                reference,
2165                response,
2166            } => {
2167                self.pending_joins.remove(reference);
2168                if let Some(channel) = self.channels.get_mut(topic) {
2169                    channel.status.set(ChannelStatus::Errored);
2170                    if let Some(pending) = channel.deferred_leave.take() {
2171                        if let Some(response) = pending.response {
2172                            let _ = response.send(Ok(json!({}).into()));
2173                        }
2174                        channel.status.set(ChannelStatus::Left);
2175                    }
2176                    for (_, waiter) in channel.join_waiters.drain() {
2177                        let _ = waiter.send(Err(ClientError::JoinRejected {
2178                            topic: topic.clone(),
2179                            response: response.clone(),
2180                        }));
2181                    }
2182                }
2183                self.emit_channel(topic, ChannelEvent::Protocol(event.clone()));
2184                self.schedule_rejoin(topic, rejoins);
2185            }
2186            ProtocolEvent::Reply {
2187                reference,
2188                status,
2189                response,
2190                topic,
2191                event: _,
2192            } => {
2193                if let Some(pending) = self.pending_calls.remove(reference) {
2194                    self.telemetry(TelemetryEvent::CallCompleted {
2195                        topic: pending.topic.clone(),
2196                        event: pending.event.clone(),
2197                        outcome: CallOutcome::Reply(*status),
2198                        duration: self.timer.now().saturating_sub(pending.started),
2199                    });
2200                    let _ = pending.response.send(Ok(Reply {
2201                        status: *status,
2202                        response: response.clone(),
2203                    }));
2204                }
2205                self.emit_channel(topic, ChannelEvent::Protocol(event.clone()));
2206            }
2207            ProtocolEvent::Left {
2208                reference,
2209                response,
2210                topic,
2211            } => {
2212                if let Some(pending) = self.pending_leaves.remove(reference) {
2213                    if let Some(response_tx) = pending.response {
2214                        let _ = response_tx.send(Ok(response.clone()));
2215                    }
2216                }
2217                let should_rejoin = self
2218                    .channels
2219                    .get(topic)
2220                    .is_some_and(|channel| channel.desired);
2221                if let Some(channel) = self.channels.get(topic) {
2222                    channel.status.set(if should_rejoin {
2223                        ChannelStatus::WaitingToJoin
2224                    } else {
2225                        ChannelStatus::Left
2226                    });
2227                }
2228                self.emit_channel(topic, ChannelEvent::Protocol(event.clone()));
2229                if should_rejoin {
2230                    self.load_payload(topic, payloads);
2231                }
2232            }
2233            ProtocolEvent::HeartbeatAck { reference, .. } => {
2234                if heartbeat_reference.as_deref() == Some(reference) {
2235                    *heartbeat_reference = None;
2236                } else if let Some(ping) = self.pending_pings.remove(reference) {
2237                    let elapsed = self.timer.now().saturating_sub(ping.started);
2238                    let _ = ping.response.send(Ok(elapsed));
2239                }
2240            }
2241            ProtocolEvent::ChannelError { topic, .. } => {
2242                self.pending_joins
2243                    .retain(|_, pending_topic| pending_topic != topic);
2244                if let Some(channel) = self.channels.get(topic) {
2245                    channel.status.set(ChannelStatus::Errored);
2246                }
2247                self.emit_channel(topic, ChannelEvent::Protocol(event.clone()));
2248                self.schedule_rejoin(topic, rejoins);
2249            }
2250            ProtocolEvent::ChannelClosed { topic, .. } => {
2251                self.pending_joins
2252                    .retain(|_, pending_topic| pending_topic != topic);
2253                if let Some(channel) = self.channels.get_mut(topic) {
2254                    channel.desired = false;
2255                    channel.status.set(ChannelStatus::Closed);
2256                }
2257                self.emit_channel(topic, ChannelEvent::Protocol(event.clone()));
2258            }
2259            ProtocolEvent::Message(frame) | ProtocolEvent::StaleMessage(frame) => {
2260                self.emit_channel(&frame.topic, ChannelEvent::Protocol(event.clone()));
2261            }
2262            ProtocolEvent::RequestInterrupted { topic, .. } => {
2263                self.emit_channel(topic, ChannelEvent::Protocol(event.clone()));
2264            }
2265            ProtocolEvent::UnmatchedReply(frame) => {
2266                self.emit_channel(&frame.topic, ChannelEvent::Protocol(event.clone()));
2267            }
2268        }
2269
2270        if let ProtocolEvent::ChannelClosed { topic, .. } = &event {
2271            self.stop_channel(topic);
2272        }
2273        Ok(())
2274    }
2275
2276    fn schedule_rejoin(
2277        &mut self,
2278        topic: &str,
2279        rejoins: &mut FuturesUnordered<LocalBoxFuture<'static, String>>,
2280    ) {
2281        let Some(channel) = self.channels.get_mut(topic) else {
2282            return;
2283        };
2284        if !channel.desired || channel.rejoin_scheduled {
2285            return;
2286        }
2287        channel.rejoin_scheduled = true;
2288        channel.join_attempt = channel.join_attempt.saturating_add(1);
2289        let delay = (self.options.rejoin_delay)(channel.join_attempt);
2290        let timer = self.timer.clone();
2291        let topic = topic.to_owned();
2292        rejoins.push(Box::pin(async move {
2293            timer.sleep(delay).await;
2294            topic
2295        }));
2296    }
2297
2298    fn schedule_operation_timeout(
2299        &self,
2300        operation: OperationTimeout,
2301        timeouts: &mut FuturesUnordered<LocalBoxFuture<'static, OperationTimeout>>,
2302    ) {
2303        let timer = self.timer.clone();
2304        let timeout = match operation {
2305            OperationTimeout::Join { duration, .. } | OperationTimeout::Leave { duration, .. } => {
2306                duration
2307            }
2308        };
2309        timeouts.push(Box::pin(async move {
2310            timer.sleep(timeout).await;
2311            operation
2312        }));
2313    }
2314
2315    fn handle_operation_timeout(
2316        &mut self,
2317        timeout: OperationTimeout,
2318        payloads: &mut FuturesUnordered<LocalBoxFuture<'static, PayloadResult>>,
2319        rejoins: &mut FuturesUnordered<LocalBoxFuture<'static, String>>,
2320    ) -> Result<(), DisconnectReason> {
2321        match timeout {
2322            OperationTimeout::Join {
2323                topic,
2324                reference,
2325                duration,
2326            } => {
2327                if self.pending_joins.remove(&reference).as_deref() != Some(topic.as_str()) {
2328                    return Ok(());
2329                }
2330                self.protocol.discard_channel(&topic);
2331                let mut should_rejoin = false;
2332                if let Some(channel) = self.channels.get_mut(&topic) {
2333                    channel.active_payload = None;
2334                    if let Some(pending) = channel.deferred_leave.take() {
2335                        if let Some(response) = pending.response {
2336                            let _ = response.send(Err(ClientError::Timeout {
2337                                operation: ClientOperation::Leave,
2338                                timeout: pending.timeout,
2339                            }));
2340                        }
2341                        channel.status.set(ChannelStatus::Left);
2342                    } else if channel.desired {
2343                        channel.status.set(ChannelStatus::Errored);
2344                        for (_, waiter) in channel.join_waiters.drain() {
2345                            let _ = waiter.send(Err(ClientError::Timeout {
2346                                operation: ClientOperation::Join,
2347                                timeout: duration,
2348                            }));
2349                        }
2350                        should_rejoin = true;
2351                    } else {
2352                        channel.status.set(ChannelStatus::Left);
2353                    }
2354                }
2355                if should_rejoin {
2356                    self.schedule_rejoin(&topic, rejoins);
2357                }
2358            }
2359            OperationTimeout::Leave {
2360                topic,
2361                reference,
2362                duration,
2363            } => {
2364                let Some(pending) = self.pending_leaves.remove(&reference) else {
2365                    return Ok(());
2366                };
2367                if let Some(response) = pending.response {
2368                    let _ = response.send(Err(ClientError::Timeout {
2369                        operation: ClientOperation::Leave,
2370                        timeout: duration,
2371                    }));
2372                }
2373                self.protocol.discard_channel(&topic);
2374                let should_rejoin = self
2375                    .channels
2376                    .get(&topic)
2377                    .is_some_and(|channel| channel.desired);
2378                if let Some(channel) = self.channels.get(&topic) {
2379                    channel.status.set(if should_rejoin {
2380                        ChannelStatus::WaitingToJoin
2381                    } else {
2382                        ChannelStatus::Left
2383                    });
2384                }
2385                if should_rejoin {
2386                    self.load_payload(&topic, payloads);
2387                }
2388            }
2389        }
2390        Ok(())
2391    }
2392
2393    async fn flush(
2394        &mut self,
2395        topic: &str,
2396        transport: &mut Box<dyn Transport>,
2397    ) -> Result<(), DisconnectReason> {
2398        loop {
2399            let push = self
2400                .channels
2401                .get_mut(topic)
2402                .and_then(|channel| channel.queued.pop_front());
2403            let Some(push) = push else {
2404                return Ok(());
2405            };
2406            self.send_push(topic, push, transport).await?;
2407        }
2408    }
2409
2410    async fn send_push(
2411        &mut self,
2412        topic: &str,
2413        push: QueuedPush,
2414        transport: &mut Box<dyn Transport>,
2415    ) -> Result<(), DisconnectReason> {
2416        match push {
2417            QueuedPush::Call {
2418                id,
2419                event,
2420                payload,
2421                started,
2422                response,
2423            } => {
2424                if matches!(&payload, Payload::Binary(_)) && !transport.supports_binary() {
2425                    self.emit_call_completed(
2426                        topic.to_owned(),
2427                        event,
2428                        started,
2429                        CallOutcome::Rejected,
2430                    );
2431                    let _ = response.send(Err(ClientError::BinaryNotSupported));
2432                    return Ok(());
2433                }
2434                let outbound = self
2435                    .protocol
2436                    .push(topic, event, payload)
2437                    .map_err(|error| DisconnectReason::Protocol(error.to_string()))?;
2438                self.pending_calls.insert(
2439                    outbound.reference.clone(),
2440                    PendingCall {
2441                        id,
2442                        topic: topic.to_owned(),
2443                        event: outbound.frame.event.clone(),
2444                        started,
2445                        response,
2446                    },
2447                );
2448                self.send_frame(transport, outbound.frame).await
2449            }
2450            QueuedPush::Cast {
2451                event,
2452                payload,
2453                response,
2454                ..
2455            } => {
2456                if matches!(&payload, Payload::Binary(_)) && !transport.supports_binary() {
2457                    let _ = response.send(Err(ClientError::BinaryNotSupported));
2458                    return Ok(());
2459                }
2460                let frame = self
2461                    .protocol
2462                    .cast(topic, event, payload)
2463                    .map_err(|error| DisconnectReason::Protocol(error.to_string()))?;
2464                match self.send_frame(transport, frame).await {
2465                    Ok(()) => {
2466                        let _ = response.send(Ok(()));
2467                        Ok(())
2468                    }
2469                    Err(error) => {
2470                        let _ = response.send(Err(ClientError::Interrupted {
2471                            operation: ClientOperation::Cast,
2472                        }));
2473                        Err(error)
2474                    }
2475                }
2476            }
2477        }
2478    }
2479
2480    async fn send_frame(
2481        &mut self,
2482        transport: &mut Box<dyn Transport>,
2483        frame: Frame,
2484    ) -> Result<(), DisconnectReason> {
2485        let topic = frame.topic.clone();
2486        let event = frame.event.clone();
2487        let message = self
2488            .codec
2489            .encode(&frame)
2490            .map_err(|error| DisconnectReason::Protocol(error.to_string()))?;
2491        let (binary, bytes) = wire_metadata(&message);
2492        self.telemetry(TelemetryEvent::FrameSent {
2493            topic,
2494            event,
2495            binary,
2496            bytes,
2497        });
2498        transport
2499            .send(message)
2500            .await
2501            .map_err(DisconnectReason::Transport)
2502    }
2503
2504    fn cancel(&mut self, id: RequestId) {
2505        let cancelled_join = self.channels.iter_mut().find_map(|(topic, channel)| {
2506            channel.join_waiters.remove(&id).map(|_| {
2507                (
2508                    topic.clone(),
2509                    channel.join_waiters.is_empty() && !channel.ever_joined,
2510                )
2511            })
2512        });
2513        if let Some((topic, stop_join)) = cancelled_join {
2514            if stop_join {
2515                if let Some(channel) = self.channels.get_mut(&topic) {
2516                    channel.desired = false;
2517                    channel.active_payload = None;
2518                    channel.status.set(ChannelStatus::Left);
2519                }
2520                self.pending_joins
2521                    .retain(|_, pending_topic| pending_topic != &topic);
2522                self.protocol.discard_channel(&topic);
2523            }
2524            return;
2525        }
2526        let mut removed_queued = false;
2527        let mut cancelled_call = None;
2528        for (topic, channel) in &mut self.channels {
2529            if let Some(pending) = channel
2530                .deferred_leave
2531                .as_mut()
2532                .filter(|pending| pending.id == id)
2533            {
2534                pending.response.take();
2535                return;
2536            }
2537            if let Some(index) = channel.queued.iter().position(|push| push.id() == id) {
2538                if let Some(QueuedPush::Call { event, started, .. }) = channel.queued.remove(index)
2539                {
2540                    cancelled_call = Some((topic.clone(), event, started));
2541                }
2542                removed_queued = true;
2543                break;
2544            }
2545        }
2546        if let Some((topic, event, started)) = cancelled_call {
2547            self.emit_call_completed(topic, event, started, CallOutcome::Cancelled);
2548        }
2549        if removed_queued {
2550            return;
2551        }
2552        let reference = self
2553            .pending_calls
2554            .iter()
2555            .find_map(|(reference, pending)| (pending.id == id).then(|| reference.clone()));
2556        if let Some(reference) = reference {
2557            if let Some(pending) = self.pending_calls.remove(&reference) {
2558                self.emit_call_completed(
2559                    pending.topic,
2560                    pending.event,
2561                    pending.started,
2562                    CallOutcome::Cancelled,
2563                );
2564            }
2565            self.protocol.forget_push(&reference);
2566        }
2567        let reference = self
2568            .pending_pings
2569            .iter()
2570            .find_map(|(reference, pending)| (pending.id == id).then(|| reference.clone()));
2571        if let Some(reference) = reference {
2572            self.pending_pings.remove(&reference);
2573            self.protocol.forget_heartbeat(&reference);
2574        }
2575        let reference = self
2576            .pending_leaves
2577            .iter()
2578            .find_map(|(reference, pending)| (pending.id == id).then(|| reference.clone()));
2579        if let Some(reference) = reference {
2580            if let Some(pending) = self.pending_leaves.get_mut(&reference) {
2581                pending.response.take();
2582            }
2583        }
2584    }
2585
2586    fn stop_channel(&mut self, topic: &str) {
2587        let mut interrupted_calls = Vec::new();
2588        if let Some(channel) = self.channels.get_mut(topic) {
2589            channel.desired = false;
2590            channel.active_payload = None;
2591            channel.rejoin_scheduled = false;
2592            if let Some(pending) = channel.deferred_leave.take() {
2593                if let Some(response) = pending.response {
2594                    let _ = response.send(Err(ClientError::Interrupted {
2595                        operation: ClientOperation::Leave,
2596                    }));
2597                }
2598            }
2599            for (_, waiter) in channel.join_waiters.drain() {
2600                let _ = waiter.send(Err(ClientError::Interrupted {
2601                    operation: ClientOperation::Join,
2602                }));
2603            }
2604            for push in channel.queued.drain(..) {
2605                match push {
2606                    QueuedPush::Call {
2607                        event,
2608                        started,
2609                        response,
2610                        ..
2611                    } => {
2612                        interrupted_calls.push((event, started));
2613                        let _ = response.send(Err(ClientError::Interrupted {
2614                            operation: ClientOperation::Call,
2615                        }));
2616                    }
2617                    push @ QueuedPush::Cast { .. } => push.interrupt(),
2618                }
2619            }
2620        }
2621        for (event, started) in interrupted_calls {
2622            self.emit_call_completed(topic.to_owned(), event, started, CallOutcome::Interrupted);
2623        }
2624    }
2625
2626    fn on_disconnect(&mut self, reason: &DisconnectReason) {
2627        let events = self.protocol.reset_connection();
2628        self.pending_joins.clear();
2629        let interrupted_calls = self
2630            .pending_calls
2631            .drain()
2632            .map(|(_, pending)| pending)
2633            .collect::<Vec<_>>();
2634        for pending in interrupted_calls {
2635            self.emit_call_completed(
2636                pending.topic,
2637                pending.event,
2638                pending.started,
2639                CallOutcome::Interrupted,
2640            );
2641            let _ = pending.response.send(Err(ClientError::Interrupted {
2642                operation: ClientOperation::Call,
2643            }));
2644        }
2645        for (_, pending) in self.pending_pings.drain() {
2646            let _ = pending.response.send(Err(ClientError::Interrupted {
2647                operation: ClientOperation::Ping,
2648            }));
2649        }
2650        for (_, pending) in self.pending_leaves.drain() {
2651            if let Some(response) = pending.response {
2652                let _ = response.send(Err(ClientError::Interrupted {
2653                    operation: ClientOperation::Leave,
2654                }));
2655            }
2656        }
2657        let topics = self.channels.keys().cloned().collect::<Vec<_>>();
2658        for channel in self.channels.values_mut() {
2659            channel.active_payload = None;
2660            channel.rejoin_scheduled = false;
2661            if let Some(pending) = channel.deferred_leave.take() {
2662                if let Some(response) = pending.response {
2663                    let _ = response.send(Err(ClientError::Interrupted {
2664                        operation: ClientOperation::Leave,
2665                    }));
2666                }
2667            }
2668            let status = if channel.desired {
2669                ChannelStatus::WaitingForSocket
2670            } else {
2671                ChannelStatus::Left
2672            };
2673            channel.status.set(status);
2674        }
2675        for topic in topics {
2676            self.emit_channel(&topic, ChannelEvent::Disconnected);
2677        }
2678        for event in events {
2679            if let ProtocolEvent::RequestInterrupted { topic, .. } = &event {
2680                self.emit_channel(topic, ChannelEvent::Protocol(event.clone()));
2681            }
2682        }
2683        self.emit_socket(SocketEvent::Disconnected {
2684            reason: reason.clone(),
2685        });
2686    }
2687
2688    fn emit_socket(&mut self, event: SocketEvent) {
2689        self.telemetry(TelemetryEvent::Socket(event.clone()));
2690        let status = match &event {
2691            SocketEvent::Connecting { .. } => SocketStatus::Connecting,
2692            SocketEvent::Connected => SocketStatus::Connected,
2693            SocketEvent::Disconnected { reason } if !reason.should_reconnect() => {
2694                SocketStatus::Disconnected
2695            }
2696            SocketEvent::ReconnectStopped { .. } => SocketStatus::Disconnected,
2697            SocketEvent::Disconnected { .. } | SocketEvent::ReconnectScheduled { .. } => {
2698                SocketStatus::WaitingToReconnect
2699            }
2700            SocketEvent::Closed => SocketStatus::Closed,
2701            SocketEvent::Lagged { .. } => self.socket_status.get(),
2702        };
2703        self.socket_status.set(status);
2704        if status == SocketStatus::Closed {
2705            for channel in self.channels.values() {
2706                channel.status.set(ChannelStatus::Closed);
2707            }
2708        }
2709        self.socket_subscribers
2710            .retain_mut(|subscriber| send_bounded(subscriber, event.clone()));
2711    }
2712
2713    fn emit_channel(&mut self, topic: &str, event: ChannelEvent) {
2714        self.telemetry(TelemetryEvent::Channel {
2715            topic: topic.to_owned(),
2716            event: event.clone(),
2717        });
2718        if let Some(channel) = self.channels.get_mut(topic) {
2719            channel
2720                .subscribers
2721                .retain_mut(|subscriber| send_bounded(subscriber, event.clone()));
2722        }
2723    }
2724
2725    fn telemetry(&self, event: TelemetryEvent) {
2726        if let Some(hook) = &self.options.telemetry {
2727            hook(&event);
2728        }
2729    }
2730
2731    fn emit_call_completed(
2732        &self,
2733        topic: String,
2734        event: String,
2735        started: Duration,
2736        outcome: CallOutcome,
2737    ) {
2738        self.telemetry(TelemetryEvent::CallCompleted {
2739            topic,
2740            event,
2741            outcome,
2742            duration: self.timer.now().saturating_sub(started),
2743        });
2744    }
2745}
2746
2747fn wire_metadata(message: &WireMessage) -> (bool, usize) {
2748    match message {
2749        WireMessage::Text(value) => (false, value.len()),
2750        WireMessage::Binary(value) => (true, value.len()),
2751    }
2752}
2753
2754trait LaggedEvent {
2755    fn lagged(dropped: u64) -> Self;
2756}
2757
2758impl LaggedEvent for SocketEvent {
2759    fn lagged(dropped: u64) -> Self {
2760        Self::Lagged { dropped }
2761    }
2762}
2763
2764impl LaggedEvent for ChannelEvent {
2765    fn lagged(dropped: u64) -> Self {
2766        Self::Lagged { dropped }
2767    }
2768}
2769
2770fn send_bounded<T: Clone + LaggedEvent>(subscriber: &mut EventSubscriber<T>, event: T) -> bool {
2771    if subscriber.dropped > 0 {
2772        match subscriber.sender.try_send(T::lagged(subscriber.dropped)) {
2773            Ok(()) => subscriber.dropped = 0,
2774            Err(error) if error.is_full() => {
2775                subscriber.dropped = subscriber.dropped.saturating_add(1);
2776                return true;
2777            }
2778            Err(_) => return false,
2779        }
2780    }
2781    match subscriber.sender.try_send(event) {
2782        Ok(()) => true,
2783        Err(error) if error.is_full() => {
2784            subscriber.dropped = 1;
2785            true
2786        }
2787        Err(_) => false,
2788    }
2789}
2790
2791enum ConnectedExit {
2792    Shutdown(oneshot::Sender<()>),
2793    Disconnect {
2794        response: oneshot::Sender<()>,
2795        close: Option<TransportCloseRequest>,
2796    },
2797    Disconnected(DisconnectReason),
2798}
2799
2800enum ConnectAttemptExit {
2801    Connected(Result<Box<dyn Transport>, TransportError>),
2802    Disconnect(oneshot::Sender<()>),
2803    Shutdown(oneshot::Sender<()>),
2804}
2805
2806enum OfflineExit {
2807    Retry,
2808    Disconnect(oneshot::Sender<()>),
2809    Shutdown(oneshot::Sender<()>),
2810}
2811
2812enum IdleExit {
2813    Connect(oneshot::Sender<()>),
2814    Shutdown(oneshot::Sender<()>),
2815}
2816
2817fn closed_response() -> oneshot::Sender<()> {
2818    let (response, receiver) = oneshot::channel();
2819    drop(receiver);
2820    response
2821}
2822
2823#[cfg(test)]
2824mod tests;