1use bitwarden_ipc::{
8 Endpoint, IpcClientExt, RequestError, RpcHandler, RpcRequest, RpcRequestInfo, Source,
9 wasm::JsIpcClient,
10};
11use bitwarden_threading::{
12 ThreadBoundRunner,
13 cancellation_token::wasm::{AbortSignal, AbortSignalExt},
14};
15use serde::{Deserialize, Serialize};
16use tsify::serde_wasm_bindgen;
17use wasm_bindgen::{JsCast, JsValue, prelude::wasm_bindgen};
18use wasm_bindgen_futures::js_sys;
19
20#[wasm_bindgen(typescript_custom_section)]
21const TS_AUTOTYPE_TYPES: &'static str = r#"
25export interface AutotypeDriver {
26 /**
27 * Enable or disable autotype. Returns if setting
28 * the enabled state was successful or not.
29 */
30 set_autotype_enabled(enabled: boolean): Promise<boolean>;
31
32 /**
33 * Set the keyboard shortcut. Returns a boolean representing
34 * if setting the shortcut was successful or not.
35 */
36 set_autotype_keyboard_shortcut(shortcut: string[]): Promise<boolean>;
37
38 /**
39 * List the applications currently running. Returns an empty
40 * array if none could be determined. Entries that do not match
41 * `AutotypeRunningApp` are skipped.
42 */
43 list_running_apps(): Promise<AutotypeRunningApp[]>;
44}
45"#;
46
47#[wasm_bindgen]
48extern "C" {
49 #[wasm_bindgen(js_name = AutotypeDriver, typescript_type = "AutotypeDriver")]
51 pub type RawJsAutotypeDriver;
52
53 #[wasm_bindgen(method, catch)]
55 async fn set_autotype_enabled(
56 this: &RawJsAutotypeDriver,
57 enabled: bool,
58 ) -> Result<JsValue, JsValue>;
59
60 #[wasm_bindgen(method, catch)]
63 async fn set_autotype_keyboard_shortcut(
64 this: &RawJsAutotypeDriver,
65 shortcut: Vec<String>,
66 ) -> Result<JsValue, JsValue>;
67
68 #[wasm_bindgen(method, catch)]
72 async fn list_running_apps(this: &RawJsAutotypeDriver) -> Result<JsValue, JsValue>;
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77#[bitwarden_ffi::wasm_record]
78pub struct AutotypeRunningApp {
79 pub name: String,
81 pub path: String,
83}
84
85struct JsAutotypeDriver {
88 runner: ThreadBoundRunner<RawJsAutotypeDriver>,
89}
90
91impl JsAutotypeDriver {
92 fn new(runner: ThreadBoundRunner<RawJsAutotypeDriver>) -> Self {
93 Self { runner }
94 }
95
96 async fn set_autotype_enabled(&self, enabled: bool) -> bool {
99 self.runner
100 .run_in_thread(move |driver| async move {
101 match driver.set_autotype_enabled(enabled).await {
102 Ok(value) => value.as_bool().unwrap_or_else(|| {
103 tracing::error!(
104 "Autotype driver returned a non-boolean set-enabled result"
105 );
106 false
107 }),
108 Err(error) => {
109 tracing::error!(?error, "Autotype driver failed to set the enabled state");
110 false
111 }
112 }
113 })
114 .await
115 .unwrap_or_else(|error| {
116 tracing::error!(?error, "Autotype runner failed to set the enabled state");
117 false
118 })
119 }
120
121 async fn set_autotype_keyboard_shortcut(&self, shortcut: Vec<String>) -> bool {
124 self.runner
125 .run_in_thread(move |driver| async move {
126 match driver.set_autotype_keyboard_shortcut(shortcut).await {
127 Ok(value) => value.as_bool().unwrap_or_else(|| {
128 tracing::error!("Autotype driver returned a non-boolean shortcut result");
129 false
130 }),
131 Err(error) => {
132 tracing::error!(?error, "Autotype driver failed to set the shortcut");
133 false
134 }
135 }
136 })
137 .await
138 .unwrap_or_else(|error| {
139 tracing::error!(?error, "Autotype runner failed to set the shortcut");
140 false
141 })
142 }
143
144 async fn list_running_apps(&self) -> Vec<AutotypeRunningApp> {
148 self.runner
149 .run_in_thread(|driver| async move {
150 let value = match driver.list_running_apps().await {
151 Ok(value) => value,
152 Err(error) => {
153 tracing::error!(?error, "Autotype driver failed to list running apps");
154 return Vec::new();
155 }
156 };
157
158 let Ok(array) = value.dyn_into::<js_sys::Array>() else {
162 tracing::warn!("Autotype driver returned a non-array of running apps");
163 return Vec::new();
164 };
165
166 array
169 .iter()
170 .filter_map(|entry| match serde_wasm_bindgen::from_value(entry) {
171 Ok(app) => Some(app),
172 Err(error) => {
173 tracing::warn!(
174 ?error,
175 "Autotype driver returned a malformed running app; skipping it"
176 );
177 None
178 }
179 })
180 .collect()
181 })
182 .await
183 .unwrap_or_else(|error| {
184 tracing::error!(?error, "Autotype runner failed to list running apps");
185 Vec::new()
186 })
187 }
188}
189
190fn from_renderer(info: &RpcRequestInfo, request: &str) -> bool {
195 let allowed = matches!(info.source, Source::DesktopRenderer);
196
197 if !allowed {
198 tracing::warn!(
199 request,
200 source = ?info.source,
201 "Autotype rejected a request from a non-renderer source"
202 );
203 }
204 allowed
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct AutotypeSetEnabledRequest {
210 pub enabled: bool,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
216#[bitwarden_ffi::wasm_record]
217pub struct AutotypeSetEnabledResponse {
218 pub success: bool,
220}
221
222impl RpcRequest for AutotypeSetEnabledRequest {
223 type Response = AutotypeSetEnabledResponse;
224
225 const NAME: &str = "AutotypeSetEnabledRequest";
226}
227
228struct AutotypeSetEnabledHandler {
233 driver: JsAutotypeDriver,
234}
235
236impl AutotypeSetEnabledHandler {
237 fn new(driver: JsAutotypeDriver) -> Self {
238 Self { driver }
239 }
240}
241
242impl RpcHandler for AutotypeSetEnabledHandler {
243 type Request = AutotypeSetEnabledRequest;
244
245 async fn handle(
246 &self,
247 request: Self::Request,
248 info: RpcRequestInfo,
249 ) -> AutotypeSetEnabledResponse {
250 if !from_renderer(&info, Self::Request::NAME) {
251 return AutotypeSetEnabledResponse { success: false };
252 }
253
254 AutotypeSetEnabledResponse {
255 success: self.driver.set_autotype_enabled(request.enabled).await,
256 }
257 }
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct AutotypeSetKeyboardShortcutRequest {
263 pub shortcut: Vec<String>,
265}
266
267#[derive(Debug, Clone, Serialize, Deserialize)]
269#[bitwarden_ffi::wasm_record]
270pub struct AutotypeSetKeyboardShortcutResponse {
271 pub success: bool,
273}
274
275impl RpcRequest for AutotypeSetKeyboardShortcutRequest {
276 type Response = AutotypeSetKeyboardShortcutResponse;
277
278 const NAME: &str = "AutotypeSetKeyboardShortcutRequest";
279}
280
281struct AutotypeSetKeyboardShortcutHandler {
286 driver: JsAutotypeDriver,
287}
288
289impl AutotypeSetKeyboardShortcutHandler {
290 fn new(driver: JsAutotypeDriver) -> Self {
291 Self { driver }
292 }
293}
294
295impl RpcHandler for AutotypeSetKeyboardShortcutHandler {
296 type Request = AutotypeSetKeyboardShortcutRequest;
297
298 async fn handle(
299 &self,
300 request: Self::Request,
301 info: RpcRequestInfo,
302 ) -> AutotypeSetKeyboardShortcutResponse {
303 if !from_renderer(&info, Self::Request::NAME) {
304 return AutotypeSetKeyboardShortcutResponse { success: false };
305 }
306
307 AutotypeSetKeyboardShortcutResponse {
308 success: self
309 .driver
310 .set_autotype_keyboard_shortcut(request.shortcut)
311 .await,
312 }
313 }
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct AutotypeListRunningAppsRequest;
319
320#[derive(Debug, Clone, Serialize, Deserialize)]
322#[bitwarden_ffi::wasm_record]
323pub struct AutotypeListRunningAppsResponse {
324 pub apps: Vec<AutotypeRunningApp>,
326}
327
328impl RpcRequest for AutotypeListRunningAppsRequest {
329 type Response = AutotypeListRunningAppsResponse;
330
331 const NAME: &str = "AutotypeListRunningAppsRequest";
332}
333
334struct AutotypeListRunningAppsHandler {
339 driver: JsAutotypeDriver,
340}
341
342impl AutotypeListRunningAppsHandler {
343 fn new(driver: JsAutotypeDriver) -> Self {
344 Self { driver }
345 }
346}
347
348impl RpcHandler for AutotypeListRunningAppsHandler {
349 type Request = AutotypeListRunningAppsRequest;
350
351 async fn handle(
352 &self,
353 _: Self::Request,
354 info: RpcRequestInfo,
355 ) -> AutotypeListRunningAppsResponse {
356 if !from_renderer(&info, Self::Request::NAME) {
357 return AutotypeListRunningAppsResponse { apps: Vec::new() };
358 }
359
360 AutotypeListRunningAppsResponse {
361 apps: self.driver.list_running_apps().await,
362 }
363 }
364}
365
366#[bitwarden_ffi::wasm_export(js_name = registerAutotypeHandlers)]
369pub async fn register_autotype_handlers(ipc_client: &JsIpcClient, driver: RawJsAutotypeDriver) {
370 let runner = ThreadBoundRunner::new(driver);
371
372 ipc_client
373 .client
374 .register_rpc_handler(AutotypeSetEnabledHandler::new(JsAutotypeDriver::new(
375 runner.clone(),
376 )))
377 .await;
378 ipc_client
379 .client
380 .register_rpc_handler(AutotypeSetKeyboardShortcutHandler::new(
381 JsAutotypeDriver::new(runner.clone()),
382 ))
383 .await;
384 ipc_client
385 .client
386 .register_rpc_handler(AutotypeListRunningAppsHandler::new(JsAutotypeDriver::new(
387 runner,
388 )))
389 .await;
390}
391
392#[bitwarden_ffi::wasm_export(js_name = autotypeRequestSetEnabled)]
398pub async fn autotype_request_set_enabled(
399 ipc_client: &JsIpcClient,
400 enabled: bool,
401 abort_signal: Option<AbortSignal>,
402) -> Result<AutotypeSetEnabledResponse, RequestError> {
403 ipc_client
404 .client
405 .request(
406 AutotypeSetEnabledRequest { enabled },
407 Endpoint::DesktopMain,
408 abort_signal.map(|signal| signal.to_cancellation_token()),
409 )
410 .await
411}
412
413#[bitwarden_ffi::wasm_export(js_name = autotypeRequestSetKeyboardShortcut)]
419pub async fn autotype_request_set_keyboard_shortcut(
420 ipc_client: &JsIpcClient,
421 shortcut: Vec<String>,
422 abort_signal: Option<AbortSignal>,
423) -> Result<AutotypeSetKeyboardShortcutResponse, RequestError> {
424 ipc_client
425 .client
426 .request(
427 AutotypeSetKeyboardShortcutRequest { shortcut },
428 Endpoint::DesktopMain,
429 abort_signal.map(|signal| signal.to_cancellation_token()),
430 )
431 .await
432}
433
434#[bitwarden_ffi::wasm_export(js_name = autotypeRequestListRunningApps)]
442pub async fn autotype_request_list_running_apps(
443 ipc_client: &JsIpcClient,
444 abort_signal: Option<AbortSignal>,
445) -> Result<AutotypeListRunningAppsResponse, RequestError> {
446 ipc_client
447 .client
448 .request(
449 AutotypeListRunningAppsRequest,
450 Endpoint::DesktopMain,
451 abort_signal.map(|signal| signal.to_cancellation_token()),
452 )
453 .await
454}