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