Skip to main content

whatsapp_rust/types/
enc_handler.rs

1use crate::client::Client;
2use crate::types::message::MessageInfo;
3use anyhow::Result;
4use std::sync::Arc;
5use wacore_binary::Node;
6
7/// Trait for handling custom encrypted message types.
8///
9/// Mirrors the wasm-portability convention of the sibling extension points
10/// (EventHandler, SendContextResolver): the `MaybeSendSync` supertrait keeps the
11/// `Send + Sync` requirement on native (the client stores `Arc<dyn EncHandler>`
12/// across receive lanes) while dropping it on wasm32, where the client is `!Send`
13/// and a handler may hold `!Send` JS handles.
14#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
15#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
16pub trait EncHandler: wacore::sync_marker::MaybeSendSync {
17    /// Handle an encrypted node of a specific type
18    ///
19    /// # Arguments
20    /// * `client` - The client instance
21    /// * `enc_node` - The encrypted node to handle
22    /// * `info` - The message info context
23    ///
24    /// # Returns
25    /// * `Ok(())` if the message was handled successfully
26    /// * `Err(anyhow::Error)` if handling failed
27    async fn handle(&self, client: Arc<Client>, enc_node: &Node, info: &MessageInfo) -> Result<()>;
28}
29
30#[cfg(test)]
31mod tests {
32    use super::*;
33    use crate::TokioRuntime;
34    use crate::types::message::MessageInfo;
35    use anyhow::Result;
36    use async_lock::Mutex;
37    use std::sync::Arc;
38    use wacore_binary::Node;
39
40    /// Mock handler for testing custom enc types
41    #[derive(Debug)]
42    struct MockEncHandler {
43        pub calls: Arc<Mutex<Vec<String>>>,
44    }
45
46    impl MockEncHandler {
47        fn new() -> Self {
48            Self {
49                calls: Arc::new(Mutex::new(Vec::new())),
50            }
51        }
52    }
53
54    #[async_trait::async_trait]
55    impl EncHandler for MockEncHandler {
56        async fn handle(
57            &self,
58            _client: Arc<Client>,
59            enc_node: &Node,
60            _info: &MessageInfo,
61        ) -> Result<()> {
62            let enc_type = enc_node
63                .attrs()
64                .optional_string("type")
65                .as_deref()
66                .unwrap_or("unknown")
67                .to_string();
68            self.calls.lock().await.push(enc_type);
69            Ok(())
70        }
71    }
72
73    #[tokio::test]
74    async fn test_custom_enc_handler_registration() {
75        use crate::bot::Bot;
76
77        // Create a mock handler
78        let mock_handler = MockEncHandler::new();
79
80        // Build bot with custom handler and in-memory DB
81        let backend = crate::test_utils::create_test_backend().await;
82
83        let transport = whatsapp_rust_tokio_transport::TokioWebSocketTransportFactory::new();
84        let http_client = whatsapp_rust_ureq_http_client::UreqHttpClient::new();
85        let bot = Bot::builder()
86            .with_backend_arc(backend)
87            .with_transport_factory(transport)
88            .with_http_client(http_client)
89            .with_enc_handler("frskmsg", mock_handler)
90            .with_runtime(TokioRuntime)
91            .build()
92            .await
93            .expect("Failed to build bot");
94
95        // Verify handler was registered
96        assert!(
97            bot.client()
98                .custom_enc_handlers
99                .get()
100                .unwrap()
101                .contains_key("frskmsg")
102        );
103    }
104
105    #[tokio::test]
106    async fn test_multiple_custom_handlers() {
107        use crate::bot::Bot;
108
109        let handler1 = MockEncHandler::new();
110        let handler2 = MockEncHandler::new();
111
112        // Build bot with in-memory DB
113        let backend = crate::test_utils::create_test_backend().await;
114
115        let transport = whatsapp_rust_tokio_transport::TokioWebSocketTransportFactory::new();
116        let http_client = whatsapp_rust_ureq_http_client::UreqHttpClient::new();
117        let bot = Bot::builder()
118            .with_backend_arc(backend)
119            .with_transport_factory(transport)
120            .with_http_client(http_client)
121            .with_enc_handler("frskmsg", handler1)
122            .with_enc_handler("customtype", handler2)
123            .with_runtime(TokioRuntime)
124            .build()
125            .await
126            .expect("Failed to build bot");
127
128        // Verify both handlers were registered
129        let client = bot.client();
130        let handlers = client.custom_enc_handlers.get().unwrap();
131        assert!(handlers.contains_key("frskmsg"));
132        assert!(handlers.contains_key("customtype"));
133        assert_eq!(handlers.len(), 2);
134    }
135
136    #[tokio::test]
137    async fn test_builtin_handlers_still_work() {
138        use crate::bot::Bot;
139
140        // Build bot without custom handlers but with in-memory DB
141        let backend = crate::test_utils::create_test_backend().await;
142
143        let transport = whatsapp_rust_tokio_transport::TokioWebSocketTransportFactory::new();
144        let http_client = whatsapp_rust_ureq_http_client::UreqHttpClient::new();
145        let bot = Bot::builder()
146            .with_backend_arc(backend)
147            .with_transport_factory(transport)
148            .with_http_client(http_client)
149            .with_runtime(TokioRuntime)
150            .build()
151            .await
152            .expect("Failed to build bot");
153
154        // Keep the hot-path map unallocated when no extension uses it.
155        assert!(bot.client().custom_enc_handlers.get().is_none());
156    }
157}