1use std::sync::{
2 Arc,
3 LazyLock,
4};
5
6use anyhow::{
7 Result,
8 bail,
9};
10use arc_swap::ArcSwap;
11use kikiutils::atomic::enum_cell::AtomicEnumCell;
12use num_enum::{
13 IntoPrimitive,
14 TryFromPrimitive,
15};
16use tokio::{
17 spawn,
18 sync::{
19 Mutex,
20 mpsc::{
21 Receiver,
22 Sender,
23 channel,
24 },
25 },
26 task::JoinHandle,
27 time::{
28 sleep,
29 timeout,
30 },
31};
32use tokio_tungstenite::tungstenite::Message;
33use tokio_util::sync::CancellationToken;
34
35use crate::{
36 WsIoClient,
37 core::{
38 channel_capacity_from_websocket_config,
39 packet::{
40 WsIoPacket,
41 WsIoPacketType,
42 },
43 traits::task::spawner::TaskSpawner,
44 utils::task::abort_locked_task,
45 },
46 runtime::WsIoClientRuntime,
47};
48
49#[repr(u8)]
51#[derive(Debug, Eq, IntoPrimitive, PartialEq, TryFromPrimitive)]
52enum SessionState {
53 AwaitingInit,
54 AwaitingReady,
55 Closed,
56 Closing,
57 Created,
58 Initiating,
59 Ready,
60 Readying,
61}
62
63#[derive(Debug)]
65pub struct WsIoClientSession {
66 cancel_token: ArcSwap<CancellationToken>,
67 init_timeout_task: Mutex<Option<JoinHandle<()>>>,
68 message_tx: Sender<Arc<Message>>,
69 ping_task: Mutex<Option<JoinHandle<()>>>,
70 ready_timeout_task: Mutex<Option<JoinHandle<()>>>,
71 runtime: Arc<WsIoClientRuntime>,
72 state: AtomicEnumCell<SessionState>,
73}
74
75impl TaskSpawner for WsIoClientSession {
76 #[inline]
77 fn cancel_token(&self) -> Arc<CancellationToken> {
78 self.cancel_token.load_full()
79 }
80}
81
82impl WsIoClientSession {
83 #[inline]
84 pub(crate) fn new(runtime: Arc<WsIoClientRuntime>) -> (Arc<Self>, Receiver<Arc<Message>>) {
85 let channel_capacity = channel_capacity_from_websocket_config(&runtime.config.websocket_config);
86 let (message_tx, message_rx) = channel(channel_capacity);
87 (
88 Arc::new(Self {
89 cancel_token: ArcSwap::new(Arc::new(CancellationToken::new())),
90 init_timeout_task: Mutex::new(None),
91 message_tx,
92 ping_task: Mutex::new(None),
93 ready_timeout_task: Mutex::new(None),
94 runtime,
95 state: AtomicEnumCell::new(SessionState::Created),
96 }),
97 message_rx,
98 )
99 }
100
101 #[inline]
103 fn handle_disconnect_packet(&self) -> Result<()> {
104 let runtime = self.runtime.clone();
105 spawn(async move { runtime.disconnect().await });
106 Ok(())
107 }
108
109 #[inline]
110 fn handle_event_packet(self: &Arc<Self>, event: &str, packet_data: Option<Vec<u8>>) -> Result<()> {
111 self.runtime.event_registry.dispatch_event_packet(
112 self.clone(),
113 event,
114 &self.runtime.config.packet_codec,
115 packet_data,
116 &self.runtime,
117 );
118
119 Ok(())
120 }
121
122 async fn handle_init_packet(self: &Arc<Self>, packet_data: Option<&[u8]>) -> Result<()> {
123 let state = self.state.get();
125 match state {
126 SessionState::AwaitingInit => self.state.try_transition(state, SessionState::Initiating)?,
127 _ => bail!("Received init packet in invalid state: {state:?}"),
128 }
129
130 abort_locked_task(&self.init_timeout_task).await;
132
133 let response_data = if let Some(init_handler) = &self.runtime.config.init_handler {
135 timeout(
136 self.runtime.config.init_handler_timeout,
137 init_handler(self.clone(), packet_data, &self.runtime.config.packet_codec),
138 )
139 .await??
140 } else {
141 None
142 };
143
144 self.state
146 .try_transition(SessionState::Initiating, SessionState::AwaitingReady)?;
147
148 let session = self.clone();
150 *self.ready_timeout_task.lock().await = Some(spawn(async move {
151 sleep(session.runtime.config.ready_packet_timeout).await;
152 if session.state.is(SessionState::AwaitingReady) {
153 session.close();
154 }
155 }));
156
157 self.send_packet(&WsIoPacket::new_init(response_data)).await
159 }
160
161 async fn handle_ready_packet(self: &Arc<Self>) -> Result<()> {
162 let state = self.state.get();
164 match state {
165 SessionState::AwaitingReady => self.state.try_transition(state, SessionState::Ready)?,
166 _ => bail!("Received ready packet in invalid state: {state:?}"),
167 }
168
169 abort_locked_task(&self.ready_timeout_task).await;
171
172 self.runtime.wake_send_event_message_task_notify.notify_waiters();
174
175 if let Some(on_session_ready_handler) = self.runtime.config.on_session_ready_handler.clone() {
177 self.spawn_task(on_session_ready_handler(self.clone()));
179 }
180
181 Ok(())
182 }
183
184 async fn send_message(&self, message: Arc<Message>) -> Result<()> {
185 Ok(self.message_tx.send(message).await?)
186 }
187
188 async fn send_packet(&self, packet: &WsIoPacket) -> Result<()> {
189 self.send_message(self.runtime.encode_packet_to_message(packet)?).await
190 }
191
192 pub(crate) async fn cleanup(self: &Arc<Self>) {
194 self.state.store(SessionState::Closing);
196
197 abort_locked_task(&self.init_timeout_task).await;
199 abort_locked_task(&self.ping_task).await;
200 abort_locked_task(&self.ready_timeout_task).await;
201
202 self.cancel_token.load().cancel();
204
205 if let Some(on_session_close_handler) = &self.runtime.config.on_session_close_handler {
207 let _ = timeout(
208 self.runtime.config.on_session_close_handler_timeout,
209 on_session_close_handler(self.clone()),
210 )
211 .await;
212 }
213
214 self.state.store(SessionState::Closed);
216 }
217
218 #[inline]
219 pub(crate) fn close(&self) {
220 match self.state.get() {
222 SessionState::Closed | SessionState::Closing => return,
223 _ => self.state.store(SessionState::Closing),
224 }
225
226 let _ = self.message_tx.try_send(Arc::new(Message::Close(None)));
228 }
229
230 pub(crate) async fn emit_event_message(&self, message: Arc<Message>) -> Result<()> {
231 self.state.ensure(SessionState::Ready, |state| {
232 format!("Cannot emit event message in invalid state: {state:?}")
233 })?;
234
235 self.send_message(message).await
236 }
237
238 pub(crate) async fn handle_incoming_packet(self: &Arc<Self>, encoded_packet: &[u8]) -> Result<()> {
239 let packet = self.runtime.config.packet_codec.decode(encoded_packet)?;
241 match packet.r#type {
242 WsIoPacketType::Disconnect => self.handle_disconnect_packet(),
243 WsIoPacketType::Event => {
244 if self.is_ready() {
245 if let Some(event) = packet.key.as_deref() {
246 return self.handle_event_packet(event, packet.data);
247 } else {
248 bail!("Event packet missing key");
249 }
250 }
251
252 Ok(())
253 },
254 WsIoPacketType::Init => self.handle_init_packet(packet.data.as_deref()).await,
255 WsIoPacketType::Ready => self.handle_ready_packet().await,
256 }
257 }
258
259 pub(crate) async fn init(self: &Arc<Self>) {
260 self.state.store(SessionState::AwaitingInit);
261 let session = self.clone();
262
263 *self.init_timeout_task.lock().await = Some(spawn(async move {
265 sleep(session.runtime.config.init_packet_timeout).await;
266 if session.state.is(SessionState::AwaitingInit) {
267 session.close();
268 }
269 }));
270
271 let session = self.clone();
273 *self.ping_task.lock().await = Some(spawn(async move {
274 loop {
275 sleep(session.runtime.config.ping_interval).await;
276 if session.send_message(PING_MESSAGE.clone()).await.is_err() {
277 session.close();
278 }
279 }
280 }));
281 }
282
283 #[inline]
285 pub fn client(&self) -> WsIoClient {
286 WsIoClient(self.runtime.clone())
287 }
288
289 #[inline]
290 pub fn is_ready(&self) -> bool {
291 self.state.is(SessionState::Ready)
292 }
293}
294
295static PING_MESSAGE: LazyLock<Arc<Message>> = LazyLock::new(|| Arc::new(Message::Binary(vec![0x01].into())));