Skip to main content

rdesktop_core/
ipc.rs

1use serde::{Deserialize, Serialize};
2use std::sync::Arc;
3
4/// A message sent from the frontend (JavaScript) to the backend (Rust).
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct IpcMessage {
7    /// Unique message ID for request-response correlation
8    pub id: String,
9
10    /// The command/method to invoke
11    pub cmd: String,
12
13    /// JSON payload
14    pub payload: serde_json::Value,
15}
16
17/// A response sent from the backend to the frontend.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct IpcResponse {
20    /// Correlation ID matching the request
21    pub id: String,
22
23    /// Whether the command succeeded
24    pub success: bool,
25
26    /// Response data (on success) or error message (on failure)
27    pub data: serde_json::Value,
28}
29
30/// A thread-safe sink used by asynchronous IPC handlers to deliver a response
31/// back to the renderer event loop. The renderer owns the queue and decides
32/// when the response is evaluated in the frontend; handlers never touch a
33/// WebView or a tao window directly.
34pub type IpcResponseSender = Arc<dyn Fn(IpcResponse) + Send + Sync + 'static>;
35
36/// Handler for IPC messages from the frontend.
37pub trait IpcHandler: Send + Sync {
38    /// Handle an IPC message and return a response.
39    fn handle(&self, message: IpcMessage) -> IpcResponse;
40
41    /// Handle an IPC message without blocking the renderer event loop.
42    ///
43    /// Renderers invoke this method from a worker thread. Existing handlers
44    /// remain synchronous by default, while handlers that already use an
45    /// async runtime can override this method and call `respond` when their
46    /// operation completes. Responses are correlated by `IpcResponse.id`, so
47    /// asynchronous replies may safely arrive out of order.
48    fn handle_async(&self, message: IpcMessage, respond: IpcResponseSender) {
49        respond(self.handle(message));
50    }
51}
52
53/// A function-based IPC handler.
54pub struct FnIpcHandler<F>
55where
56    F: Fn(IpcMessage) -> IpcResponse + Send + Sync,
57{
58    handler: F,
59}
60
61impl<F> FnIpcHandler<F>
62where
63    F: Fn(IpcMessage) -> IpcResponse + Send + Sync,
64{
65    pub fn new(handler: F) -> Self {
66        Self { handler }
67    }
68}
69
70impl<F> IpcHandler for FnIpcHandler<F>
71where
72    F: Fn(IpcMessage) -> IpcResponse + Send + Sync,
73{
74    fn handle(&self, message: IpcMessage) -> IpcResponse {
75        (self.handler)(message)
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::{FnIpcHandler, IpcHandler, IpcMessage, IpcResponse, IpcResponseSender};
82    use serde_json::json;
83    use std::sync::{Arc, Mutex};
84
85    #[test]
86    fn default_async_dispatch_preserves_sync_handler_contract() {
87        let handler = FnIpcHandler::new(|message: IpcMessage| IpcResponse {
88            id: message.id,
89            success: true,
90            data: json!({ "method": message.cmd }),
91        });
92        let received = Arc::new(Mutex::new(None));
93        let sink_target = received.clone();
94        let sink: IpcResponseSender = Arc::new(move |response| {
95            *sink_target.lock().unwrap() = Some(response);
96        });
97
98        handler.handle_async(
99            IpcMessage {
100                id: "async-1".to_string(),
101                cmd: "ping".to_string(),
102                payload: json!({}),
103            },
104            sink,
105        );
106
107        let response = received.lock().unwrap().take().unwrap();
108        assert_eq!(response.id, "async-1");
109        assert_eq!(response.data["method"], "ping");
110    }
111}