Skip to main content

bitwarden_ipc/wasm/
ipc_client.rs

1use std::{collections::HashMap, sync::Arc};
2
3use bitwarden_threading::cancellation_token::wasm::{AbortSignal, AbortSignalExt};
4use wasm_bindgen::prelude::*;
5
6use super::communication_backend::JsCommunicationBackend;
7use crate::{
8    IpcClientImpl,
9    crypto_provider::noise::crypto_provider::NoiseCryptoProvider,
10    error::{AlreadyRunningError, ReceiveError, SubscribeError},
11    ipc_client::IpcClientSubscription,
12    ipc_client_trait::IpcClient,
13    message::{IncomingMessage, OutgoingMessage},
14    traits::InMemorySessionRepository,
15    wasm::{
16        JsSessionRepository, RawJsSessionRepository,
17        generic_session_repository::GenericSessionRepository,
18    },
19};
20
21/// JavaScript wrapper around the IPC client. For more information, see the
22/// [`IpcClient`] trait documentation.
23#[wasm_bindgen(js_name = IpcClient)]
24pub struct JsIpcClient {
25    #[wasm_bindgen(skip)]
26    /// The underlying IPC client instance. Use this to create WASM-compatible functions
27    /// that interact with the IPC client, e.g. to register RPC handlers, trigger RPC requests,
28    /// send typed messages, etc. For examples see
29    /// [wasm::ipc_register_discover_handler](crate::wasm::ipc_register_discover_handler).
30    pub client: Arc<dyn IpcClient>,
31}
32
33/// JavaScript wrapper around the IPC client subscription. For more information, see the
34/// [IpcClientSubscription](crate::IpcClientSubscription) documentation.
35#[wasm_bindgen(js_name = IpcClientSubscription)]
36pub struct JsIpcClientSubscription {
37    subscription: IpcClientSubscription,
38}
39
40#[bitwarden_ffi::wasm_export]
41#[wasm_bindgen(js_class = IpcClientSubscription)]
42impl JsIpcClientSubscription {
43    #[wasm_only(
44        note = "Use the `subscribe` method on `IpcClient` to create a subscription instance."
45    )]
46    #[allow(missing_docs)]
47    pub async fn receive(
48        &mut self,
49        abort_signal: Option<AbortSignal>,
50    ) -> Result<IncomingMessage, ReceiveError> {
51        let cancellation_token = abort_signal.map(|signal| signal.to_cancellation_token());
52        self.subscription.receive(cancellation_token).await
53    }
54}
55
56#[bitwarden_ffi::wasm_export]
57#[wasm_bindgen(js_class = IpcClient)]
58impl JsIpcClient {
59    /// Create a new `IpcClient` instance with an in-memory session repository for saving
60    /// sessions within the SDK.
61    #[wasm_only]
62    #[wasm_bindgen(js_name = newWithSdkInMemorySessions)]
63    pub fn new_with_sdk_in_memory_sessions(
64        communication_provider: &JsCommunicationBackend,
65    ) -> JsIpcClient {
66        JsIpcClient {
67            client: Arc::new(IpcClientImpl::new(
68                NoiseCryptoProvider,
69                communication_provider.clone(),
70                GenericSessionRepository::InMemory(Arc::new(InMemorySessionRepository::new(
71                    HashMap::new(),
72                ))),
73            )),
74        }
75    }
76
77    /// Create a new `IpcClient` instance with a client-managed session repository for saving
78    /// sessions using State Provider.
79    #[wasm_only]
80    #[wasm_bindgen(js_name = newWithClientManagedSessions)]
81    pub fn new_with_client_managed_sessions(
82        communication_provider: &JsCommunicationBackend,
83        session_repository: RawJsSessionRepository,
84    ) -> JsIpcClient {
85        JsIpcClient {
86            client: Arc::new(IpcClientImpl::new(
87                NoiseCryptoProvider,
88                communication_provider.clone(),
89                GenericSessionRepository::JsSessionRepository(Arc::new(JsSessionRepository::new(
90                    session_repository,
91                ))),
92            )),
93        }
94    }
95
96    #[wasm_only]
97    #[allow(missing_docs)]
98    pub async fn start(
99        &self,
100        abort_signal: Option<AbortSignal>,
101    ) -> Result<(), AlreadyRunningError> {
102        self.client
103            .start(abort_signal.map(|signal| signal.to_cancellation_token()))
104            .await
105    }
106
107    #[wasm_only]
108    #[wasm_bindgen(js_name = isRunning)]
109    #[allow(missing_docs)]
110    pub fn is_running(&self) -> bool {
111        self.client.is_running()
112    }
113
114    #[wasm_only]
115    #[allow(missing_docs)]
116    pub async fn send(&self, message: OutgoingMessage) -> Result<(), JsError> {
117        self.client
118            .send(message)
119            .await
120            .map_err(|e| JsError::new(&e.to_string()))
121    }
122
123    #[wasm_only]
124    #[allow(missing_docs)]
125    pub async fn subscribe(&self) -> Result<JsIpcClientSubscription, SubscribeError> {
126        let subscription = self.client.subscribe(None).await?;
127        Ok(JsIpcClientSubscription { subscription })
128    }
129}