bitwarden_ipc/wasm/
ipc_client.rs1use std::{collections::HashMap, sync::Arc};
2
3use bitwarden_threading::cancellation_token::wasm::{AbortSignal, AbortSignalExt};
4use serde::{Deserialize, Serialize};
5use wasm_bindgen::prelude::*;
6
7use super::communication_backend::JsCommunicationBackend;
8use crate::{
9 IpcClientImpl,
10 broadcast::{BroadcastGroup, EndpointSendResult},
11 crypto_provider::noise::crypto_provider::NoiseCryptoProvider,
12 endpoint::Endpoint,
13 error::{AlreadyRunningError, BroadcastError, ReceiveError, SendError, SubscribeError},
14 ipc_client::IpcClientSubscription,
15 ipc_client_trait::IpcClient,
16 message::{IncomingMessage, OutgoingMessage},
17 traits::InMemorySessionRepository,
18 wasm::{
19 JsSessionRepository, RawJsSessionRepository,
20 generic_session_repository::GenericSessionRepository,
21 },
22};
23
24#[bitwarden_ffi::wasm_object(js_name = IpcClient)]
27pub struct JsIpcClient {
28 #[wasm_bindgen(skip)]
29 pub client: Arc<dyn IpcClient>,
34}
35
36#[derive(Serialize, Deserialize, Debug, Clone)]
39#[bitwarden_ffi::wasm_record]
40pub struct IpcEndpointSendResult {
41 pub endpoint: Endpoint,
43 pub error: Option<SendError>,
45}
46
47impl From<EndpointSendResult> for IpcEndpointSendResult {
48 fn from(value: EndpointSendResult) -> Self {
49 Self {
50 endpoint: value.endpoint,
51 error: value.result.err(),
52 }
53 }
54}
55
56#[bitwarden_ffi::wasm_object(js_name = IpcClientSubscription)]
59pub struct JsIpcClientSubscription {
60 subscription: IpcClientSubscription,
61}
62
63#[bitwarden_ffi::wasm_export(js_class = IpcClientSubscription)]
64impl JsIpcClientSubscription {
65 #[wasm_only(
66 note = "Use the `subscribe` method on `IpcClient` to create a subscription instance."
67 )]
68 #[allow(missing_docs)]
69 pub async fn receive(
70 &mut self,
71 abort_signal: Option<AbortSignal>,
72 ) -> Result<IncomingMessage, ReceiveError> {
73 let cancellation_token = abort_signal.map(|signal| signal.to_cancellation_token());
74 self.subscription.receive(cancellation_token).await
75 }
76}
77
78#[bitwarden_ffi::wasm_export(js_class = IpcClient)]
79impl JsIpcClient {
80 #[wasm_only]
83 #[wasm_bindgen(js_name = newWithSdkInMemorySessions)]
84 pub fn new_with_sdk_in_memory_sessions(
85 communication_provider: &JsCommunicationBackend,
86 ) -> JsIpcClient {
87 JsIpcClient {
88 client: Arc::new(IpcClientImpl::new(
89 NoiseCryptoProvider::new(),
90 communication_provider.clone(),
91 GenericSessionRepository::InMemory(Arc::new(InMemorySessionRepository::new(
92 HashMap::new(),
93 ))),
94 )),
95 }
96 }
97
98 #[wasm_only]
101 #[wasm_bindgen(js_name = newWithClientManagedSessions)]
102 pub fn new_with_client_managed_sessions(
103 communication_provider: &JsCommunicationBackend,
104 session_repository: RawJsSessionRepository,
105 ) -> JsIpcClient {
106 JsIpcClient {
107 client: Arc::new(IpcClientImpl::new(
108 NoiseCryptoProvider::new(),
109 communication_provider.clone(),
110 GenericSessionRepository::JsSessionRepository(Arc::new(JsSessionRepository::new(
111 session_repository,
112 ))),
113 )),
114 }
115 }
116
117 #[wasm_only]
118 #[allow(missing_docs)]
119 pub async fn start(
120 &self,
121 abort_signal: Option<AbortSignal>,
122 ) -> Result<(), AlreadyRunningError> {
123 self.client
124 .start(abort_signal.map(|signal| signal.to_cancellation_token()))
125 .await
126 }
127
128 #[wasm_only]
129 #[wasm_bindgen(js_name = isRunning)]
130 #[allow(missing_docs)]
131 pub fn is_running(&self) -> bool {
132 self.client.is_running()
133 }
134
135 #[wasm_only]
136 #[allow(missing_docs)]
137 pub async fn send(&self, message: OutgoingMessage) -> Result<(), JsError> {
138 self.client
139 .send(message)
140 .await
141 .map_err(|e| JsError::new(&e.to_string()))
142 }
143
144 #[wasm_only]
145 #[allow(missing_docs)]
146 pub async fn broadcast(
147 &self,
148 group: BroadcastGroup,
149 payload: Vec<u8>,
150 topic: Option<String>,
151 ) -> Result<Vec<IpcEndpointSendResult>, BroadcastError> {
152 let results = self.client.broadcast(group, payload, topic).await?;
153 Ok(results.into_iter().map(Into::into).collect())
154 }
155
156 #[wasm_only]
157 #[allow(missing_docs)]
158 pub async fn subscribe(&self) -> Result<JsIpcClientSubscription, SubscribeError> {
159 let subscription = self.client.subscribe(None).await?;
160 Ok(JsIpcClientSubscription { subscription })
161 }
162}