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
63pub struct WsIoClientSession {
65 cancel_token: ArcSwap<CancellationToken>,
66 init_timeout_task: Mutex<Option<JoinHandle<()>>>,
67 message_tx: Sender<Arc<Message>>,
68 ping_task: Mutex<Option<JoinHandle<()>>>,
69 ready_timeout_task: Mutex<Option<JoinHandle<()>>>,
70 runtime: Arc<WsIoClientRuntime>,
71 state: AtomicEnumCell<SessionState>,
72}
73
74impl TaskSpawner for WsIoClientSession {
75 #[inline]
76 fn cancel_token(&self) -> Arc<CancellationToken> {
77 self.cancel_token.load_full()
78 }
79}
80
81impl WsIoClientSession {
82 #[inline]
83 pub(crate) fn new(runtime: Arc<WsIoClientRuntime>) -> (Arc<Self>, Receiver<Arc<Message>>) {
84 let channel_capacity = channel_capacity_from_websocket_config(&runtime.config.websocket_config);
85 let (message_tx, message_rx) = channel(channel_capacity);
86 (
87 Arc::new(Self {
88 cancel_token: ArcSwap::new(Arc::new(CancellationToken::new())),
89 init_timeout_task: Mutex::new(None),
90 message_tx,
91 ping_task: Mutex::new(None),
92 ready_timeout_task: Mutex::new(None),
93 runtime,
94 state: AtomicEnumCell::new(SessionState::Created),
95 }),
96 message_rx,
97 )
98 }
99
100 #[inline]
102 fn handle_disconnect_packet(&self) -> Result<()> {
103 let runtime = self.runtime.clone();
104 spawn(async move { runtime.disconnect().await });
105 Ok(())
106 }
107
108 #[inline]
109 fn handle_event_packet(self: &Arc<Self>, event: &str, packet_data: Option<Vec<u8>>) -> Result<()> {
110 self.runtime.event_registry.dispatch_event_packet(
111 self.clone(),
112 event,
113 &self.runtime.config.packet_codec,
114 packet_data,
115 &self.runtime,
116 );
117
118 Ok(())
119 }
120
121 async fn handle_init_packet(self: &Arc<Self>, packet_data: Option<&[u8]>) -> Result<()> {
122 let state = self.state.get();
124 match state {
125 SessionState::AwaitingInit => self.state.try_transition(state, SessionState::Initiating)?,
126 _ => bail!("Received init packet in invalid state: {state:?}"),
127 }
128
129 abort_locked_task(&self.init_timeout_task).await;
131
132 let response_data = if let Some(init_handler) = &self.runtime.config.init_handler {
134 timeout(
135 self.runtime.config.init_handler_timeout,
136 init_handler(self.clone(), packet_data, &self.runtime.config.packet_codec),
137 )
138 .await??
139 } else {
140 None
141 };
142
143 self.state
145 .try_transition(SessionState::Initiating, SessionState::AwaitingReady)?;
146
147 let session = self.clone();
149 *self.ready_timeout_task.lock().await = Some(spawn(async move {
150 sleep(session.runtime.config.ready_packet_timeout).await;
151 if session.state.is(SessionState::AwaitingReady) {
152 session.close();
153 }
154 }));
155
156 self.send_packet(&WsIoPacket::new_init(response_data)).await
158 }
159
160 async fn handle_ready_packet(self: &Arc<Self>) -> Result<()> {
161 let state = self.state.get();
163 match state {
164 SessionState::AwaitingReady => self.state.try_transition(state, SessionState::Ready)?,
165 _ => bail!("Received ready packet in invalid state: {state:?}"),
166 }
167
168 abort_locked_task(&self.ready_timeout_task).await;
170
171 self.runtime.wake_send_event_message_task_notify.notify_waiters();
173
174 if let Some(on_session_ready_handler) = self.runtime.config.on_session_ready_handler.clone() {
176 self.spawn_task(on_session_ready_handler(self.clone()));
178 }
179
180 Ok(())
181 }
182
183 async fn send_message(&self, message: Arc<Message>) -> Result<()> {
184 Ok(self.message_tx.send(message).await?)
185 }
186
187 async fn send_packet(&self, packet: &WsIoPacket) -> Result<()> {
188 self.send_message(self.runtime.encode_packet_to_message(packet)?).await
189 }
190
191 pub(crate) async fn cleanup(self: &Arc<Self>) {
193 self.state.store(SessionState::Closing);
195
196 abort_locked_task(&self.init_timeout_task).await;
198 abort_locked_task(&self.ping_task).await;
199 abort_locked_task(&self.ready_timeout_task).await;
200
201 self.cancel_token.load().cancel();
203
204 if let Some(on_session_close_handler) = &self.runtime.config.on_session_close_handler {
206 let _ = timeout(
207 self.runtime.config.on_session_close_handler_timeout,
208 on_session_close_handler(self.clone()),
209 )
210 .await;
211 }
212
213 self.state.store(SessionState::Closed);
215 }
216
217 #[inline]
218 pub(crate) fn close(&self) {
219 match self.state.get() {
221 SessionState::Closed | SessionState::Closing => return,
222 _ => self.state.store(SessionState::Closing),
223 }
224
225 let _ = self.message_tx.try_send(Arc::new(Message::Close(None)));
227 }
228
229 pub(crate) async fn emit_event_message(&self, message: Arc<Message>) -> Result<()> {
230 self.state.ensure(SessionState::Ready, |state| {
231 format!("Cannot emit event message in invalid state: {state:?}")
232 })?;
233
234 self.send_message(message).await
235 }
236
237 pub(crate) async fn handle_incoming_packet(self: &Arc<Self>, encoded_packet: &[u8]) -> Result<()> {
238 let packet = self.runtime.config.packet_codec.decode(encoded_packet)?;
240 match packet.r#type {
241 WsIoPacketType::Disconnect => self.handle_disconnect_packet(),
242 WsIoPacketType::Event => {
243 if self.is_ready() {
244 if let Some(event) = packet.key.as_deref() {
245 return self.handle_event_packet(event, packet.data);
246 } else {
247 bail!("Event packet missing key");
248 }
249 }
250
251 Ok(())
252 }
253 WsIoPacketType::Init => self.handle_init_packet(packet.data.as_deref()).await,
254 WsIoPacketType::Ready => self.handle_ready_packet().await,
255 }
256 }
257
258 pub(crate) async fn init(self: &Arc<Self>) {
259 self.state.store(SessionState::AwaitingInit);
260 let session = self.clone();
261
262 *self.init_timeout_task.lock().await = Some(spawn(async move {
264 sleep(session.runtime.config.init_packet_timeout).await;
265 if session.state.is(SessionState::AwaitingInit) {
266 session.close();
267 }
268 }));
269
270 let session = self.clone();
272 *self.ping_task.lock().await = Some(spawn(async move {
273 loop {
274 sleep(session.runtime.config.ping_interval).await;
275 if session.send_message(PING_MESSAGE.clone()).await.is_err() {
276 session.close();
277 }
278 }
279 }));
280 }
281
282 #[inline]
284 pub fn client(&self) -> WsIoClient {
285 WsIoClient(self.runtime.clone())
286 }
287
288 #[inline]
289 pub fn is_ready(&self) -> bool {
290 self.state.is(SessionState::Ready)
291 }
292}
293
294static PING_MESSAGE: LazyLock<Arc<Message>> = LazyLock::new(|| Arc::new(Message::Binary(vec![0x01].into())));