bitwarden_ipc/wasm/
communication_backend.rs

1use std::sync::Arc;
2
3use bitwarden_error::bitwarden_error;
4use bitwarden_threading::ThreadBoundRunner;
5use thiserror::Error;
6use tokio::sync::RwLock;
7use wasm_bindgen::prelude::*;
8
9use crate::{
10    message::{IncomingMessage, OutgoingMessage},
11    traits::{CommunicationBackend, CommunicationBackendReceiver},
12};
13
14#[allow(missing_docs)]
15#[derive(Debug, Error)]
16#[bitwarden_error(basic)]
17#[error("Failed to deserialize incoming message: {0}")]
18pub struct DeserializeError(String);
19
20#[allow(missing_docs)]
21#[derive(Debug, Error)]
22#[bitwarden_error(basic)]
23#[error("Incoming message channel failed: {0}")]
24pub struct ChannelError(String);
25
26#[wasm_bindgen(typescript_custom_section)]
27const TS_CUSTOM_TYPES: &'static str = r#"
28export interface IpcCommunicationBackendSender {
29    send(message: OutgoingMessage): Promise<void>;
30}
31"#;
32
33#[wasm_bindgen]
34extern "C" {
35    /// JavaScript interface for handling outgoing messages from the IPC framework.
36    #[wasm_bindgen(js_name = IpcCommunicationBackendSender, typescript_type = "IpcCommunicationBackendSender")]
37    pub type JsCommunicationBackendSender;
38
39    /// Used by the IPC framework to send an outgoing message.
40    #[wasm_bindgen(catch, method, structural)]
41    pub async fn send(
42        this: &JsCommunicationBackendSender,
43        message: OutgoingMessage,
44    ) -> Result<(), JsValue>;
45
46    /// Used by JavaScript to provide an incoming message to the IPC framework.
47    #[wasm_bindgen(catch, method, structural)]
48    pub async fn receive(this: &JsCommunicationBackendSender) -> Result<JsValue, JsValue>;
49}
50
51/// JavaScript implementation of the `CommunicationBackend` trait for IPC communication.
52#[wasm_bindgen(js_name = IpcCommunicationBackend)]
53pub struct JsCommunicationBackend {
54    sender: Arc<ThreadBoundRunner<JsCommunicationBackendSender>>,
55    receive_rx: tokio::sync::broadcast::Receiver<IncomingMessage>,
56    receive_tx: tokio::sync::broadcast::Sender<IncomingMessage>,
57}
58
59impl Clone for JsCommunicationBackend {
60    fn clone(&self) -> Self {
61        Self {
62            sender: self.sender.clone(),
63            receive_rx: self.receive_rx.resubscribe(),
64            receive_tx: self.receive_tx.clone(),
65        }
66    }
67}
68
69#[wasm_bindgen(js_class = IpcCommunicationBackend)]
70impl JsCommunicationBackend {
71    /// Creates a new instance of the JavaScript communication backend.
72    #[wasm_bindgen(constructor)]
73    pub fn new(sender: JsCommunicationBackendSender) -> Self {
74        let (receive_tx, receive_rx) = tokio::sync::broadcast::channel(20);
75        Self {
76            sender: Arc::new(ThreadBoundRunner::new(sender)),
77            receive_rx,
78            receive_tx,
79        }
80    }
81
82    /// Used by JavaScript to provide an incoming message to the IPC framework.
83    pub fn receive(&self, message: IncomingMessage) -> Result<(), JsValue> {
84        self.receive_tx
85            .send(message)
86            .map_err(|e| ChannelError(e.to_string()))?;
87        Ok(())
88    }
89}
90
91impl CommunicationBackend for JsCommunicationBackend {
92    type SendError = String;
93    type Receiver = RwLock<tokio::sync::broadcast::Receiver<IncomingMessage>>;
94
95    async fn send(&self, message: OutgoingMessage) -> Result<(), Self::SendError> {
96        let result = self
97            .sender
98            .run_in_thread(|sender| async move {
99                sender.send(message).await.map_err(|e| format!("{:?}", e))
100            })
101            .await;
102
103        result.map_err(|e| e.to_string())?
104    }
105
106    async fn subscribe(&self) -> Self::Receiver {
107        RwLock::new(self.receive_rx.resubscribe())
108    }
109}
110
111impl CommunicationBackendReceiver for RwLock<tokio::sync::broadcast::Receiver<IncomingMessage>> {
112    type ReceiveError = String;
113
114    async fn receive(&self) -> Result<IncomingMessage, Self::ReceiveError> {
115        self.write().await.recv().await.map_err(|e| e.to_string())
116    }
117}