1use std::collections::HashMap;
2use std::future::Future;
3use std::panic::AssertUnwindSafe;
4use std::sync::Arc;
5use std::sync::atomic::Ordering;
6use std::time::Duration;
7
8use bytes::Bytes;
9use futures_util::{FutureExt, SinkExt, StreamExt, future::BoxFuture, future::join_all};
10use serde::de::DeserializeOwned;
11use serde_json::{Map, Value, json};
12use tokio::net::{TcpListener, TcpStream};
13use tokio::sync::{Semaphore, mpsc};
14use tokio::time::{MissedTickBehavior, interval, timeout};
15use tokio_tungstenite::{
16 WebSocketStream, accept_async_with_config, tungstenite::Message,
17 tungstenite::protocol::WebSocketConfig,
18};
19use uuid::Uuid;
20use validator::Validate;
21use wscall_protocol::{
22 DEFAULT_MAX_FRAME_BYTES, EcdhKeypair, EncryptionKind, ErrorPayload, FileAttachment, FrameCodec,
23 PacketBody, PacketEnvelope, ProtocolError, parse_peer_public,
24};
25
26use crate::server_types::{
27 ApiContext, ApiError, ClientEntry, EventContext, ExceptionContext, ServerConnectionContext,
28 ServerDisconnectContext, ServerError, ServerHandle, ServerOutbound, ServerState,
29};
30use crate::validation;
31
32const SERVER_IDLE_TIMEOUT: Duration = Duration::from_secs(45);
33const SERVER_HEARTBEAT_INTERVAL: Duration = Duration::from_secs(15);
34const SERVER_OUTBOUND_QUEUE_CAPACITY: usize = 256;
35const SERVER_DEFAULT_MAX_IN_FLIGHT: usize = 64;
37const SERVER_CLOSE_GRACE: Duration = Duration::from_millis(500);
39
40type ApiHandler =
41 Arc<dyn Fn(ApiContext) -> BoxFuture<'static, Result<Value, ApiError>> + Send + Sync>;
42type Filter =
43 Arc<dyn Fn(ApiContext) -> BoxFuture<'static, Result<ApiContext, ApiError>> + Send + Sync>;
44type EventHandler =
45 Arc<dyn Fn(EventContext) -> BoxFuture<'static, Result<Value, ApiError>> + Send + Sync>;
46type ConnectionHandler =
47 Arc<dyn Fn(ServerConnectionContext) -> BoxFuture<'static, ()> + Send + Sync>;
48type DisconnectHandler =
49 Arc<dyn Fn(ServerDisconnectContext) -> BoxFuture<'static, ()> + Send + Sync>;
50type ExceptionHandler =
51 Arc<dyn Fn(ExceptionContext) -> BoxFuture<'static, ErrorPayload> + Send + Sync>;
52
53struct ApiRequestInput {
54 request_id: u64,
55 route: String,
56 params: Value,
57 attachments: Vec<FileAttachment>,
58 metadata: Value,
59}
60
61struct EventEmitInput {
62 event_id: u64,
63 name: String,
64 data: Map<String, Value>,
65 attachments: Vec<FileAttachment>,
66 metadata: Value,
67}
68
69impl ServerHandle {
70 pub async fn broadcast_event(
77 &self,
78 name: impl Into<String>,
79 data: Map<String, Value>,
80 attachments: Vec<FileAttachment>,
81 ) -> Result<(), ApiError> {
82 let event_id = self.state.next_event_id.fetch_add(1, Ordering::Relaxed) + 1;
83 let packet = PacketEnvelope::with_encryption(
84 PacketBody::EventEmit {
85 event_id,
86 name: name.into(),
87 data,
88 attachments,
89 metadata: json!({ "source": "server" }),
90 expect_ack: true,
91 },
92 self.default_encryption,
93 );
94
95 if self.is_ecdh {
96 for entry in self.state.clients.iter() {
99 if entry
100 .value()
101 .sender
102 .try_send(ServerOutbound::Packet(packet.clone()))
103 .is_err()
104 {
105 tracing::warn!(
106 "broadcast: outbound queue full, dropping event for a connection"
107 );
108 }
109 }
110 } else {
111 let encoded = self
113 .codec
114 .encode(&packet)
115 .map_err(|_| ApiError::internal("failed to encode broadcast event"))?;
116 let encoded = Bytes::from(encoded);
117
118 for entry in self.state.clients.iter() {
119 if entry
120 .value()
121 .sender
122 .try_send(ServerOutbound::PreEncoded(encoded.clone()))
123 .is_err()
124 {
125 tracing::warn!(
126 "broadcast: outbound queue full, dropping event for a connection"
127 );
128 }
129 }
130 }
131 Ok(())
132 }
133
134 pub async fn send_event_to(
138 &self,
139 connection_id: &str,
140 name: impl Into<String>,
141 data: Map<String, Value>,
142 attachments: Vec<FileAttachment>,
143 ) -> Result<(), ApiError> {
144 let event_id = self.state.next_event_id.fetch_add(1, Ordering::Relaxed) + 1;
145 let packet = PacketEnvelope::with_encryption(
146 PacketBody::EventEmit {
147 event_id,
148 name: name.into(),
149 data,
150 attachments,
151 metadata: json!({ "source": "server" }),
152 expect_ack: true,
153 },
154 self.default_encryption,
155 );
156
157 let entry = self
158 .state
159 .clients
160 .get(connection_id)
161 .ok_or_else(|| ApiError::not_found("target connection not found"))?;
162
163 if self.is_ecdh {
164 entry
166 .sender
167 .try_send(ServerOutbound::Packet(packet))
168 .map_err(|_| ApiError::internal("failed to queue direct event"))
169 } else {
170 let encoded = self
171 .codec
172 .encode(&packet)
173 .map_err(|_| ApiError::internal("failed to encode direct event"))?;
174 let encoded = Bytes::from(encoded);
175 entry
176 .sender
177 .try_send(ServerOutbound::PreEncoded(encoded))
178 .map_err(|_| ApiError::internal("failed to queue direct event"))
179 }
180 }
181
182 pub async fn connection_count(&self) -> usize {
184 self.state.clients.len()
185 }
186}
187
188pub struct WscallServer {
189 state: Arc<ServerState>,
190 routes: HashMap<String, ApiHandler>,
191 filters: Vec<Filter>,
192 event_handlers: HashMap<String, EventHandler>,
193 connection_handlers: Vec<ConnectionHandler>,
194 disconnect_handlers: Vec<DisconnectHandler>,
195 exception_handler: Option<ExceptionHandler>,
196 codec: FrameCodec,
197 default_encryption: EncryptionKind,
198 is_ecdh: bool,
200 max_connections: Option<usize>,
202 max_in_flight: usize,
204 max_frame_bytes: usize,
206}
207
208impl Default for WscallServer {
209 fn default() -> Self {
210 Self::new()
211 }
212}
213
214impl WscallServer {
215 pub fn new() -> Self {
216 Self {
217 state: Arc::new(ServerState::new()),
218 routes: HashMap::new(),
219 filters: Vec::new(),
220 event_handlers: HashMap::new(),
221 connection_handlers: Vec::new(),
222 disconnect_handlers: Vec::new(),
223 exception_handler: None,
224 codec: FrameCodec::plaintext(),
225 default_encryption: EncryptionKind::None,
226 is_ecdh: false,
227 max_connections: None,
228 max_in_flight: SERVER_DEFAULT_MAX_IN_FLIGHT,
229 max_frame_bytes: DEFAULT_MAX_FRAME_BYTES,
230 }
231 }
232
233 pub fn with_ecdh(mut self) -> Self {
241 self.is_ecdh = true;
242 self.default_encryption = EncryptionKind::ChaCha20;
245 self
246 }
247
248 pub fn with_chacha20_key(mut self, key: [u8; 32]) -> Self {
249 self.codec = self.codec.clone().with_chacha20_key(key);
250 self.default_encryption = EncryptionKind::ChaCha20;
251 self
252 }
253
254 pub fn with_aes256_key(mut self, key: [u8; 32]) -> Self {
255 self.codec = self.codec.clone().with_aes256_key(key);
256 self.default_encryption = EncryptionKind::Aes256;
257 self
258 }
259
260 pub fn with_max_connections(mut self, max: usize) -> Self {
266 self.max_connections = Some(max);
267 self
268 }
269
270 pub fn with_max_in_flight(mut self, max: usize) -> Self {
273 self.max_in_flight = max;
274 self
275 }
276
277 pub fn with_max_frame_bytes(mut self, max: usize) -> Self {
283 self.max_frame_bytes = max;
284 self
285 }
286
287 pub fn handle(&self) -> ServerHandle {
288 ServerHandle {
289 state: Arc::clone(&self.state),
290 codec: self.codec.clone(),
291 default_encryption: self.default_encryption,
292 is_ecdh: self.is_ecdh,
293 }
294 }
295
296 pub fn route<F, Fut>(&mut self, route: impl Into<String>, handler: F)
297 where
298 F: Fn(ApiContext) -> Fut + Send + Sync + 'static,
299 Fut: Future<Output = Result<Value, ApiError>> + Send + 'static,
300 {
301 let handler = Arc::new(move |ctx: ApiContext| {
302 Box::pin(handler(ctx)) as BoxFuture<'static, Result<Value, ApiError>>
303 });
304 self.routes.insert(route.into(), handler);
305 }
306
307 pub fn typed_route<T, F, Fut>(&mut self, route: impl Into<String>, handler: F)
308 where
309 T: DeserializeOwned + Send + 'static,
310 F: Fn(ApiContext, T) -> Fut + Send + Sync + 'static,
311 Fut: Future<Output = Result<Value, ApiError>> + Send + 'static,
312 {
313 let handler = Arc::new(handler);
314 self.route(route, move |mut ctx| {
315 let handler = Arc::clone(&handler);
316 let params = ctx.bind_take::<T>();
319 async move {
320 let params = params?;
321 handler(ctx, params).await
322 }
323 });
324 }
325
326 pub fn validated_route<T, F, Fut>(&mut self, route: impl Into<String>, handler: F)
327 where
328 T: DeserializeOwned + Validate + Send + 'static,
329 F: Fn(ApiContext, T) -> Fut + Send + Sync + 'static,
330 Fut: Future<Output = Result<Value, ApiError>> + Send + 'static,
331 {
332 let handler = Arc::new(handler);
333 self.route(route, move |mut ctx| {
334 let handler = Arc::clone(&handler);
335 let params: Result<T, ApiError> = ctx.bind_take::<T>().and_then(|params: T| {
338 params.validate().map_err(|source| {
339 ApiError::bad_request("params validation failed").with_details(json!({
340 "validation_errors": validation::errors_to_details(&source),
341 }))
342 })?;
343 Ok(params)
344 });
345 async move {
346 let params = params?;
347 handler(ctx, params).await
348 }
349 });
350 }
351
352 pub fn filter<F, Fut>(&mut self, filter: F)
353 where
354 F: Fn(ApiContext) -> Fut + Send + Sync + 'static,
355 Fut: Future<Output = Result<ApiContext, ApiError>> + Send + 'static,
356 {
357 let filter = Arc::new(move |ctx: ApiContext| {
358 Box::pin(filter(ctx)) as BoxFuture<'static, Result<ApiContext, ApiError>>
359 });
360 self.filters.push(filter);
361 }
362
363 pub fn event_handler<F, Fut>(&mut self, name: impl Into<String>, handler: F)
364 where
365 F: Fn(EventContext) -> Fut + Send + Sync + 'static,
366 Fut: Future<Output = Result<Value, ApiError>> + Send + 'static,
367 {
368 let handler = Arc::new(move |ctx: EventContext| {
369 Box::pin(handler(ctx)) as BoxFuture<'static, Result<Value, ApiError>>
370 });
371 self.event_handlers.insert(name.into(), handler);
372 }
373
374 pub fn on_connected<F, Fut>(&mut self, handler: F)
375 where
376 F: Fn(ServerConnectionContext) -> Fut + Send + Sync + 'static,
377 Fut: Future<Output = ()> + Send + 'static,
378 {
379 let handler = Arc::new(move |ctx: ServerConnectionContext| {
380 Box::pin(handler(ctx)) as BoxFuture<'static, ()>
381 });
382 self.connection_handlers.push(handler);
383 }
384
385 pub fn on_disconnected<F, Fut>(&mut self, handler: F)
386 where
387 F: Fn(ServerDisconnectContext) -> Fut + Send + Sync + 'static,
388 Fut: Future<Output = ()> + Send + 'static,
389 {
390 let handler = Arc::new(move |ctx: ServerDisconnectContext| {
391 Box::pin(handler(ctx)) as BoxFuture<'static, ()>
392 });
393 self.disconnect_handlers.push(handler);
394 }
395
396 pub fn exception_handler<F, Fut>(&mut self, handler: F)
397 where
398 F: Fn(ExceptionContext) -> Fut + Send + Sync + 'static,
399 Fut: Future<Output = ErrorPayload> + Send + 'static,
400 {
401 self.exception_handler = Some(Arc::new(move |ctx: ExceptionContext| {
402 Box::pin(handler(ctx)) as BoxFuture<'static, ErrorPayload>
403 }));
404 }
405
406 pub async fn listen(self, address: &str) -> Result<(), ServerError> {
407 let listener = TcpListener::bind(address).await?;
408 tracing::info!(%address, "wscall server listening on ws://{address}/socket");
409
410 let shared = Arc::new(self);
411 let conn_sem = shared
412 .max_connections
413 .map(|max| Arc::new(Semaphore::new(max)));
414
415 loop {
416 let permit = match &conn_sem {
420 Some(sem) => match sem.clone().acquire_owned().await {
421 Ok(permit) => Some(permit),
422 Err(_) => continue,
423 },
424 None => None,
425 };
426
427 let (stream, peer) = listener.accept().await?;
428 let _ = TcpStream::set_nodelay(&stream, true);
430
431 let server = Arc::clone(&shared);
432 tokio::spawn(async move {
433 let _permit = permit;
434 if let Err(error) = server.serve_connection(stream, peer).await {
435 tracing::warn!(?error, ?peer, "connection failed");
436 }
437 });
438 }
439 }
440
441 async fn serve_connection(
442 self: Arc<Self>,
443 stream: TcpStream,
444 peer: std::net::SocketAddr,
445 ) -> Result<(), ServerError> {
446 let mut ws_config = WebSocketConfig::default();
450 ws_config.max_message_size = Some(self.max_frame_bytes + 1024 * 1024);
451 ws_config.max_frame_size = Some(self.max_frame_bytes + 1024 * 1024);
452 let mut websocket = accept_async_with_config(stream, Some(ws_config)).await?;
453 let connection_id = Uuid::now_v7().to_string();
454 let peer_addr = Some(peer);
455
456 let session_codec = if self.is_ecdh {
459 let (codec, _encryption) = self.perform_ecdh_handshake(&mut websocket).await?;
460 codec.with_max_frame_bytes(self.max_frame_bytes)
461 } else {
462 self.codec
463 .clone()
464 .with_max_frame_bytes(self.max_frame_bytes)
465 };
466
467 let (mut sink, mut stream) = websocket.split();
468 let (tx, mut rx) = mpsc::channel::<ServerOutbound>(SERVER_OUTBOUND_QUEUE_CAPACITY);
469
470 self.state
472 .clients
473 .insert(connection_id.clone(), ClientEntry { sender: tx.clone() });
474
475 self.notify_connected(&connection_id, peer_addr).await;
476
477 let writer_codec = session_codec.clone();
480 let mut writer = tokio::spawn(async move {
481 let mut ticker = interval(SERVER_HEARTBEAT_INTERVAL);
482 ticker.set_missed_tick_behavior(MissedTickBehavior::Delay);
483 loop {
484 tokio::select! {
485 maybe_outbound = rx.recv() => {
486 let Some(outbound) = maybe_outbound else {
487 break Ok::<(), ServerError>(());
488 };
489 match outbound {
490 ServerOutbound::PreEncoded(bytes) => {
491 sink.send(Message::Binary(bytes)).await?;
496 }
497 ServerOutbound::Packet(packet) => {
498 let encoded = writer_codec.encode(&packet)?;
499 sink.send(Message::Binary(encoded.into())).await?;
500 }
501 ServerOutbound::Pong(payload) => {
502 sink.send(Message::Pong(payload.into())).await?;
503 }
504 ServerOutbound::Close => {
505 let _ = sink.send(Message::Close(None)).await;
506 break Ok(());
507 }
508 }
509 }
510 _ = ticker.tick() => {
511 if let Err(error) = sink.send(Message::Ping(Bytes::new())).await {
512 break Err(ServerError::WebSocket(error));
513 }
514 }
515 }
516 }
517 });
518
519 let reader_codec = session_codec.clone();
522
523 let in_flight = Arc::new(Semaphore::new(self.max_in_flight));
527
528 let result = async {
529 self.handle()
530 .send_event_to(
531 &connection_id,
532 "system.notice",
533 json!({ "message": "connected", "connection_id": connection_id })
534 .as_object()
535 .unwrap()
536 .clone(),
537 Vec::new(),
538 )
539 .await
540 .map_err(ServerError::Api)?;
541
542 loop {
543 let next_message = timeout(SERVER_IDLE_TIMEOUT, stream.next()).await;
544 let Some(message) =
545 next_message.map_err(|_| ServerError::IdleTimeout(connection_id.clone()))?
546 else {
547 break Ok(());
548 };
549
550 match message? {
551 Message::Binary(bytes) => {
552 if bytes.len() > self.max_frame_bytes {
555 let error_packet = PacketEnvelope::with_encryption(
556 PacketBody::ApiResponse {
557 request_id: 0,
558 ok: false,
559 status: 413,
560 data: json!({}),
561 error: Some(ErrorPayload {
562 code: "frame_too_large".to_string(),
563 message: format!(
564 "frame size {} exceeds limit {}",
565 bytes.len(),
566 self.max_frame_bytes
567 ),
568 status: 413,
569 details: None,
570 }),
571 metadata: json!({}),
572 },
573 self.default_encryption,
574 );
575 let _ = self.queue_for(&connection_id, error_packet).await;
576 continue;
577 }
578
579 let packet = reader_codec.decode(&bytes)?;
580
581 let spawn_handler = matches!(
582 &packet.body,
583 PacketBody::ApiRequest { .. } | PacketBody::EventEmit { .. }
584 );
585
586 if spawn_handler {
587 let permit = in_flight.clone().acquire_owned().await.map_err(|_| {
588 ServerError::Api(ApiError::internal("in-flight semaphore closed"))
589 })?;
590 let server = Arc::clone(&self);
591 let conn_id = connection_id.clone();
592 tokio::spawn(async move {
593 let _permit = permit;
594 if let Err(error) =
595 server.process_packet(&conn_id, peer_addr, packet).await
596 {
597 tracing::warn!(%error, "packet processing failed");
598 }
599 });
600 } else {
601 self.process_packet(&connection_id, peer_addr, packet)
604 .await?;
605 }
606 }
607 Message::Close(_) => break Ok(()),
608 Message::Ping(payload) => {
609 if tx
610 .send(ServerOutbound::Pong(payload.to_vec()))
611 .await
612 .is_err()
613 {
614 break Ok(());
615 }
616 }
617 Message::Pong(_) => {}
618 Message::Text(_) => {}
619 Message::Frame(_) => {}
620 }
621 }
622 }
623 .await;
624
625 self.state.clients.remove(&connection_id);
629 let _ = tx.try_send(ServerOutbound::Close);
630 let _ = timeout(SERVER_CLOSE_GRACE, &mut writer).await;
631 writer.abort();
632 self.notify_disconnected(&connection_id, peer_addr, Self::disconnect_reason(&result))
633 .await;
634 result
635 }
636
637 async fn perform_ecdh_handshake(
647 &self,
648 websocket: &mut WebSocketStream<TcpStream>,
649 ) -> Result<(FrameCodec, EncryptionKind), ServerError> {
650 let keypair = EcdhKeypair::generate()?;
652
653 let next = timeout(Duration::from_secs(10), websocket.next()).await;
655 let client_public = match next {
656 Ok(Some(Ok(Message::Binary(bytes)))) => parse_peer_public(&bytes).map_err(|_| {
657 ServerError::Protocol(ProtocolError::EcdhHandshake(
658 "client sent invalid public key length".to_string(),
659 ))
660 })?,
661 _ => {
662 return Err(ProtocolError::EcdhHandshake(
663 "client did not send a valid public key".to_string(),
664 )
665 .into());
666 }
667 };
668
669 websocket
671 .send(Message::Binary(keypair.public_bytes().to_vec().into()))
672 .await?;
673
674 let session_key = keypair.derive_session_key(&client_public);
676 Ok((
677 FrameCodec::plaintext().with_chacha20_key(session_key),
678 EncryptionKind::ChaCha20,
679 ))
680 }
681
682 async fn process_packet(
683 &self,
684 connection_id: &str,
685 peer_addr: Option<std::net::SocketAddr>,
686 packet: PacketEnvelope,
687 ) -> Result<(), ServerError> {
688 match packet.body {
689 PacketBody::ApiRequest {
690 request_id,
691 route,
692 params,
693 attachments,
694 metadata,
695 } => {
696 let response = self
697 .run_api_request(
698 connection_id,
699 peer_addr,
700 ApiRequestInput {
701 request_id,
702 route,
703 params,
704 attachments,
705 metadata,
706 },
707 )
708 .await;
709 self.queue_for(connection_id, response).await?;
710 }
711 PacketBody::EventEmit {
712 event_id,
713 name,
714 data,
715 attachments,
716 metadata,
717 ..
718 } => {
719 let ack = self
720 .run_event(
721 connection_id,
722 peer_addr,
723 EventEmitInput {
724 event_id,
725 name,
726 data,
727 attachments,
728 metadata,
729 },
730 )
731 .await;
732 self.queue_for(connection_id, ack).await?;
733 }
734 PacketBody::EventAck {
735 event_id,
736 ok,
737 receipt,
738 error,
739 } => {
740 tracing::debug!(
741 connection_id,
742 event_id,
743 ok,
744 ?receipt,
745 ?error,
746 "received event ack from client",
747 );
748 }
749 PacketBody::ApiResponse { .. } => {}
750 }
751 Ok(())
752 }
753
754 async fn queue_for(
760 &self,
761 connection_id: &str,
762 packet: PacketEnvelope,
763 ) -> Result<(), ServerError> {
764 let sender = match self.state.clients.get(connection_id) {
767 Some(entry) => entry.sender.clone(),
768 None => {
769 return Err(ServerError::Api(ApiError::not_found(
770 "connection is closed",
771 )));
772 }
773 };
774
775 if self.is_ecdh {
776 sender
778 .send(ServerOutbound::Packet(packet))
779 .await
780 .map_err(|_| {
781 ServerError::Api(ApiError::internal("failed to queue outbound packet"))
782 })
783 } else {
784 let encoded = self.codec.encode(&packet)?;
786 let encoded = Bytes::from(encoded);
787 sender
788 .send(ServerOutbound::PreEncoded(encoded))
789 .await
790 .map_err(|_| {
791 ServerError::Api(ApiError::internal("failed to queue outbound packet"))
792 })
793 }
794 }
795
796 async fn run_api_request(
797 &self,
798 connection_id: &str,
799 peer_addr: Option<std::net::SocketAddr>,
800 request: ApiRequestInput,
801 ) -> PacketEnvelope {
802 let ApiRequestInput {
803 request_id,
804 route,
805 params,
806 attachments,
807 metadata,
808 } = request;
809
810 let mut ctx = ApiContext {
811 connection_id: connection_id.to_string(),
812 peer_addr,
813 request_id,
814 route: route.clone(),
815 params,
816 attachments,
817 metadata,
818 server: self.handle(),
819 };
820
821 for filter in &self.filters {
822 match filter(ctx).await {
823 Ok(next_ctx) => ctx = next_ctx,
824 Err(error) => {
825 return self
826 .api_error_packet(connection_id, Some(request_id), route, error)
827 .await;
828 }
829 }
830 }
831
832 let Some(handler) = self.routes.get(&ctx.route) else {
833 return self
834 .api_error_packet(
835 connection_id,
836 Some(request_id),
837 route,
838 ApiError::not_found("route not found"),
839 )
840 .await;
841 };
842
843 match AssertUnwindSafe(handler(ctx)).catch_unwind().await {
844 Ok(Ok(data)) => PacketEnvelope::with_encryption(
845 PacketBody::ApiResponse {
846 request_id,
847 ok: true,
848 status: 200,
849 data,
850 error: None,
851 metadata: json!({}),
852 },
853 self.default_encryption,
854 ),
855 Ok(Err(error)) => {
856 self.api_error_packet(connection_id, Some(request_id), route, error)
857 .await
858 }
859 Err(_) => {
860 self.api_error_packet(
861 connection_id,
862 Some(request_id),
863 route,
864 ApiError::internal("handler panicked"),
865 )
866 .await
867 }
868 }
869 }
870
871 async fn run_event(
872 &self,
873 connection_id: &str,
874 peer_addr: Option<std::net::SocketAddr>,
875 event: EventEmitInput,
876 ) -> PacketEnvelope {
877 let EventEmitInput {
878 event_id,
879 name,
880 data,
881 attachments,
882 metadata,
883 } = event;
884
885 let ctx = EventContext {
886 connection_id: connection_id.to_string(),
887 peer_addr,
888 event_id,
889 name: name.clone(),
890 data,
891 attachments,
892 metadata,
893 server: self.handle(),
894 };
895
896 let Some(handler) = self.event_handlers.get(&name) else {
897 return PacketEnvelope::with_encryption(
898 PacketBody::EventAck {
899 event_id,
900 ok: false,
901 receipt: json!({}),
902 error: Some(ApiError::not_found("event handler not found").into_payload()),
903 },
904 self.default_encryption,
905 );
906 };
907
908 match AssertUnwindSafe(handler(ctx)).catch_unwind().await {
909 Ok(Ok(receipt)) => PacketEnvelope::with_encryption(
910 PacketBody::EventAck {
911 event_id,
912 ok: true,
913 receipt,
914 error: None,
915 },
916 self.default_encryption,
917 ),
918 Ok(Err(error)) => PacketEnvelope::with_encryption(
919 PacketBody::EventAck {
920 event_id,
921 ok: false,
922 receipt: json!({}),
923 error: Some(
924 self.map_exception(ExceptionContext {
925 connection_id: connection_id.to_string(),
926 request_id: Some(event_id),
927 target: name,
928 message_kind: "event",
929 error,
930 })
931 .await,
932 ),
933 },
934 self.default_encryption,
935 ),
936 Err(_) => PacketEnvelope::with_encryption(
937 PacketBody::EventAck {
938 event_id,
939 ok: false,
940 receipt: json!({}),
941 error: Some(
942 self.map_exception(ExceptionContext {
943 connection_id: connection_id.to_string(),
944 request_id: Some(event_id),
945 target: name,
946 message_kind: "event",
947 error: ApiError::internal("event handler panicked"),
948 })
949 .await,
950 ),
951 },
952 self.default_encryption,
953 ),
954 }
955 }
956
957 async fn api_error_packet(
958 &self,
959 connection_id: &str,
960 request_id: Option<u64>,
961 route: String,
962 error: ApiError,
963 ) -> PacketEnvelope {
964 let request_id = request_id.unwrap_or(0);
965 let status = error.status;
966 let payload = self
967 .map_exception(ExceptionContext {
968 connection_id: connection_id.to_string(),
969 request_id: Some(request_id),
970 target: route,
971 message_kind: "api",
972 error,
973 })
974 .await;
975
976 PacketEnvelope::with_encryption(
977 PacketBody::ApiResponse {
978 request_id,
979 ok: false,
980 status,
981 data: json!({}),
982 error: Some(payload),
983 metadata: json!({}),
984 },
985 self.default_encryption,
986 )
987 }
988
989 async fn notify_connected(&self, connection_id: &str, peer_addr: Option<std::net::SocketAddr>) {
990 let handlers = self.connection_handlers.clone();
991 let futures = handlers.into_iter().map(|handler| {
994 let context = ServerConnectionContext {
995 connection_id: connection_id.to_string(),
996 peer_addr,
997 server: self.handle(),
998 };
999 AssertUnwindSafe(handler(context)).catch_unwind()
1000 });
1001 for result in join_all(futures).await {
1002 if result.is_err() {
1003 tracing::error!("server connected handler panicked");
1004 }
1005 }
1006 }
1007
1008 async fn notify_disconnected(
1009 &self,
1010 connection_id: &str,
1011 peer_addr: Option<std::net::SocketAddr>,
1012 reason: String,
1013 ) {
1014 let handlers = self.disconnect_handlers.clone();
1015 let futures = handlers.into_iter().map(|handler| {
1016 let context = ServerDisconnectContext {
1017 connection_id: connection_id.to_string(),
1018 peer_addr,
1019 reason: reason.clone(),
1020 server: self.handle(),
1021 };
1022 AssertUnwindSafe(handler(context)).catch_unwind()
1023 });
1024 for result in join_all(futures).await {
1025 if result.is_err() {
1026 tracing::error!("server disconnected handler panicked");
1027 }
1028 }
1029 }
1030
1031 fn disconnect_reason(result: &Result<(), ServerError>) -> String {
1032 match result {
1033 Ok(()) => "connection closed".to_string(),
1034 Err(ServerError::IdleTimeout(_)) => "idle timeout".to_string(),
1035 Err(error) => error.to_string(),
1036 }
1037 }
1038
1039 async fn map_exception(&self, context: ExceptionContext) -> ErrorPayload {
1040 match &self.exception_handler {
1041 Some(handler) => handler(context).await,
1042 None => context.error.into_payload(),
1043 }
1044 }
1045}