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(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(js_class = IpcClient)]
56impl JsIpcClient {
57    /// Create a new `IpcClient` instance with an in-memory session repository for saving
58    /// sessions within the SDK.
59    #[wasm_only]
60    #[wasm_bindgen(js_name = newWithSdkInMemorySessions)]
61    pub fn new_with_sdk_in_memory_sessions(
62        communication_provider: &JsCommunicationBackend,
63    ) -> JsIpcClient {
64        JsIpcClient {
65            client: Arc::new(IpcClientImpl::new(
66                NoiseCryptoProvider::new(),
67                communication_provider.clone(),
68                GenericSessionRepository::InMemory(Arc::new(InMemorySessionRepository::new(
69                    HashMap::new(),
70                ))),
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                NoiseCryptoProvider::new(),
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}