1use bitwarden_ipc::{
12 Endpoint, IpcClientExt, RequestError, RpcHandler, RpcRequest, RpcRequestInfo, Source,
13 wasm::JsIpcClient,
14};
15use bitwarden_threading::{
16 ThreadBoundRunner,
17 cancellation_token::wasm::{AbortSignal, AbortSignalExt},
18};
19use serde::{Deserialize, Serialize};
20use tsify::serde_wasm_bindgen;
21use wasm_bindgen::{JsCast, JsValue, prelude::wasm_bindgen};
22use wasm_bindgen_futures::js_sys;
23
24#[wasm_bindgen(typescript_custom_section)]
25const TS_AUTOTYPE_TYPES: &'static str = r#"
29export interface AutotypeDriver {
30 /**
31 * Enable or disable autotype. Returns if setting
32 * the enabled state was successful or not.
33 */
34 set_autotype_enabled(enabled: boolean): Promise<boolean>;
35
36 /**
37 * Set the keyboard shortcut. Returns a boolean representing
38 * if setting the shortcut was successful or not.
39 */
40 set_autotype_keyboard_shortcut(shortcut: string[]): Promise<boolean>;
41
42 /**
43 * List the applications currently running. Returns an empty
44 * array if none could be determined. Entries that do not match
45 * `AutotypeRunningApp` are skipped.
46 */
47 list_running_apps(): Promise<AutotypeRunningApp[]>;
48}
49
50/**
51 * Applies Autotype requests in the desktop render process.
52 *
53 * Implement each method as an `async` function. A method that throws synchronously or returns
54 * a non-Promise value leaves the request without a response.
55 */
56export interface AutotypeRenderDriver {
57 /**
58 * Find the credentials to autotype into the focused app, as requested
59 * by the main process when the user presses the autotype shortcut.
60 * Returns the matching vault item's username and password, or
61 * `undefined` if nothing matches.
62 */
63 get_autotype_credentials(app: AutotypeRunningApp): Promise<AutotypeCredentials | undefined>;
64}
65"#;
66
67#[bitwarden_ffi::wasm_import]
68extern "C" {
69 #[wasm_bindgen(js_name = AutotypeDriver, typescript_type = "AutotypeDriver")]
71 pub type RawJsAutotypeDriver;
72
73 #[wasm_bindgen(method, catch)]
75 async fn set_autotype_enabled(
76 this: &RawJsAutotypeDriver,
77 enabled: bool,
78 ) -> Result<JsValue, JsValue>;
79
80 #[wasm_bindgen(method, catch)]
83 async fn set_autotype_keyboard_shortcut(
84 this: &RawJsAutotypeDriver,
85 shortcut: Vec<String>,
86 ) -> Result<JsValue, JsValue>;
87
88 #[wasm_bindgen(method, catch)]
92 async fn list_running_apps(this: &RawJsAutotypeDriver) -> Result<JsValue, JsValue>;
93
94 #[wasm_bindgen(js_name = AutotypeRenderDriver, typescript_type = "AutotypeRenderDriver")]
96 pub type RawJsAutotypeRenderDriver;
97
98 #[wasm_bindgen(method, catch)]
103 async fn get_autotype_credentials(
104 this: &RawJsAutotypeRenderDriver,
105 app: AutotypeRunningApp,
106 ) -> Result<JsValue, JsValue>;
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111#[bitwarden_ffi::wasm_record]
112pub struct AutotypeRunningApp {
113 pub name: String,
115 pub path: String,
117}
118
119#[derive(Clone, Serialize, Deserialize)]
123#[bitwarden_ffi::wasm_record]
124pub struct AutotypeCredentials {
125 pub username: String,
127 pub password: String,
129}
130
131impl std::fmt::Debug for AutotypeCredentials {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 f.debug_struct("AutotypeCredentials")
134 .field("username", &"[REDACTED]")
135 .field("password", &"[REDACTED]")
136 .finish()
137 }
138}
139
140struct JsAutotypeDriver {
143 runner: ThreadBoundRunner<RawJsAutotypeDriver>,
144}
145
146impl JsAutotypeDriver {
147 fn new(runner: ThreadBoundRunner<RawJsAutotypeDriver>) -> Self {
148 Self { runner }
149 }
150
151 async fn set_autotype_enabled(&self, enabled: bool) -> bool {
154 self.runner
155 .run_in_thread(move |driver| async move {
156 match driver.set_autotype_enabled(enabled).await {
157 Ok(value) => value.as_bool().unwrap_or_else(|| {
158 tracing::error!(
159 "Autotype driver returned a non-boolean set-enabled result"
160 );
161 false
162 }),
163 Err(error) => {
164 tracing::error!(?error, "Autotype driver failed to set the enabled state");
165 false
166 }
167 }
168 })
169 .await
170 .unwrap_or_else(|error| {
171 tracing::error!(?error, "Autotype runner failed to set the enabled state");
172 false
173 })
174 }
175
176 async fn set_autotype_keyboard_shortcut(&self, shortcut: Vec<String>) -> bool {
179 self.runner
180 .run_in_thread(move |driver| async move {
181 match driver.set_autotype_keyboard_shortcut(shortcut).await {
182 Ok(value) => value.as_bool().unwrap_or_else(|| {
183 tracing::error!("Autotype driver returned a non-boolean shortcut result");
184 false
185 }),
186 Err(error) => {
187 tracing::error!(?error, "Autotype driver failed to set the shortcut");
188 false
189 }
190 }
191 })
192 .await
193 .unwrap_or_else(|error| {
194 tracing::error!(?error, "Autotype runner failed to set the shortcut");
195 false
196 })
197 }
198
199 async fn list_running_apps(&self) -> Vec<AutotypeRunningApp> {
203 self.runner
204 .run_in_thread(|driver| async move {
205 let value = match driver.list_running_apps().await {
206 Ok(value) => value,
207 Err(error) => {
208 tracing::error!(?error, "Autotype driver failed to list running apps");
209 return Vec::new();
210 }
211 };
212
213 let Ok(array) = value.dyn_into::<js_sys::Array>() else {
217 tracing::warn!("Autotype driver returned a non-array of running apps");
218 return Vec::new();
219 };
220
221 array
224 .iter()
225 .filter_map(|entry| match serde_wasm_bindgen::from_value(entry) {
226 Ok(app) => Some(app),
227 Err(error) => {
228 tracing::warn!(
229 ?error,
230 "Autotype driver returned a malformed running app; skipping it"
231 );
232 None
233 }
234 })
235 .collect()
236 })
237 .await
238 .unwrap_or_else(|error| {
239 tracing::error!(?error, "Autotype runner failed to list running apps");
240 Vec::new()
241 })
242 }
243}
244
245struct JsAutotypeRenderDriver {
248 runner: ThreadBoundRunner<RawJsAutotypeRenderDriver>,
249}
250
251impl JsAutotypeRenderDriver {
252 fn new(runner: ThreadBoundRunner<RawJsAutotypeRenderDriver>) -> Self {
253 Self { runner }
254 }
255
256 async fn get_autotype_credentials(
259 &self,
260 app: AutotypeRunningApp,
261 ) -> Option<AutotypeCredentials> {
262 self.runner
263 .run_in_thread(move |driver| async move {
264 let value = match driver.get_autotype_credentials(app).await {
265 Ok(value) => value,
266 Err(error) => {
267 tracing::error!(
268 ?error,
269 "Autotype render driver failed to get autotype credentials"
270 );
271 return None;
272 }
273 };
274
275 if value.is_undefined() || value.is_null() {
277 return None;
278 }
279
280 match serde_wasm_bindgen::from_value(value) {
283 Ok(credentials) => Some(credentials),
284 Err(_) => {
285 tracing::warn!("Autotype render driver returned malformed credentials");
286 None
287 }
288 }
289 })
290 .await
291 .unwrap_or_else(|error| {
292 tracing::error!(
293 ?error,
294 "Autotype render runner failed to get autotype credentials"
295 );
296 None
297 })
298 }
299}
300
301fn from_source(info: &RpcRequestInfo, expected: Source, request: &str) -> bool {
306 let allowed = info.source == expected;
307
308 if !allowed {
309 tracing::warn!(
310 request,
311 source = ?info.source,
312 ?expected,
313 "Autotype rejected a request from an unexpected source"
314 );
315 }
316 allowed
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct AutotypeSetEnabledRequest {
322 pub enabled: bool,
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
328#[bitwarden_ffi::wasm_record]
329pub struct AutotypeSetEnabledResponse {
330 pub success: bool,
332}
333
334impl RpcRequest for AutotypeSetEnabledRequest {
335 type Response = AutotypeSetEnabledResponse;
336
337 const NAME: &str = "AutotypeSetEnabledRequest";
338}
339
340struct AutotypeSetEnabledHandler {
345 driver: JsAutotypeDriver,
346}
347
348impl AutotypeSetEnabledHandler {
349 fn new(driver: JsAutotypeDriver) -> Self {
350 Self { driver }
351 }
352}
353
354impl RpcHandler for AutotypeSetEnabledHandler {
355 type Request = AutotypeSetEnabledRequest;
356
357 async fn handle(
358 &self,
359 request: Self::Request,
360 info: RpcRequestInfo,
361 ) -> AutotypeSetEnabledResponse {
362 if !from_source(&info, Source::DesktopRenderer, Self::Request::NAME) {
363 return AutotypeSetEnabledResponse { success: false };
364 }
365
366 AutotypeSetEnabledResponse {
367 success: self.driver.set_autotype_enabled(request.enabled).await,
368 }
369 }
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize)]
374pub struct AutotypeSetKeyboardShortcutRequest {
375 pub shortcut: Vec<String>,
377}
378
379#[derive(Debug, Clone, Serialize, Deserialize)]
381#[bitwarden_ffi::wasm_record]
382pub struct AutotypeSetKeyboardShortcutResponse {
383 pub success: bool,
385}
386
387impl RpcRequest for AutotypeSetKeyboardShortcutRequest {
388 type Response = AutotypeSetKeyboardShortcutResponse;
389
390 const NAME: &str = "AutotypeSetKeyboardShortcutRequest";
391}
392
393struct AutotypeSetKeyboardShortcutHandler {
398 driver: JsAutotypeDriver,
399}
400
401impl AutotypeSetKeyboardShortcutHandler {
402 fn new(driver: JsAutotypeDriver) -> Self {
403 Self { driver }
404 }
405}
406
407impl RpcHandler for AutotypeSetKeyboardShortcutHandler {
408 type Request = AutotypeSetKeyboardShortcutRequest;
409
410 async fn handle(
411 &self,
412 request: Self::Request,
413 info: RpcRequestInfo,
414 ) -> AutotypeSetKeyboardShortcutResponse {
415 if !from_source(&info, Source::DesktopRenderer, Self::Request::NAME) {
416 return AutotypeSetKeyboardShortcutResponse { success: false };
417 }
418
419 AutotypeSetKeyboardShortcutResponse {
420 success: self
421 .driver
422 .set_autotype_keyboard_shortcut(request.shortcut)
423 .await,
424 }
425 }
426}
427
428#[derive(Debug, Clone, Serialize, Deserialize)]
430pub struct AutotypeListRunningAppsRequest;
431
432#[derive(Debug, Clone, Serialize, Deserialize)]
434#[bitwarden_ffi::wasm_record]
435pub struct AutotypeListRunningAppsResponse {
436 pub apps: Vec<AutotypeRunningApp>,
438}
439
440impl RpcRequest for AutotypeListRunningAppsRequest {
441 type Response = AutotypeListRunningAppsResponse;
442
443 const NAME: &str = "AutotypeListRunningAppsRequest";
444}
445
446struct AutotypeListRunningAppsHandler {
451 driver: JsAutotypeDriver,
452}
453
454impl AutotypeListRunningAppsHandler {
455 fn new(driver: JsAutotypeDriver) -> Self {
456 Self { driver }
457 }
458}
459
460impl RpcHandler for AutotypeListRunningAppsHandler {
461 type Request = AutotypeListRunningAppsRequest;
462
463 async fn handle(
464 &self,
465 _: Self::Request,
466 info: RpcRequestInfo,
467 ) -> AutotypeListRunningAppsResponse {
468 if !from_source(&info, Source::DesktopRenderer, Self::Request::NAME) {
469 return AutotypeListRunningAppsResponse { apps: Vec::new() };
470 }
471
472 AutotypeListRunningAppsResponse {
473 apps: self.driver.list_running_apps().await,
474 }
475 }
476}
477
478#[derive(Debug, Clone, Serialize, Deserialize)]
481pub struct AutotypeGetCredentialsRequest {
482 pub app: AutotypeRunningApp,
484}
485
486#[derive(Debug, Clone, Serialize, Deserialize)]
488#[bitwarden_ffi::wasm_record]
489pub struct AutotypeGetCredentialsResponse {
490 #[tsify(optional)]
492 pub credentials: Option<AutotypeCredentials>,
493}
494
495impl RpcRequest for AutotypeGetCredentialsRequest {
496 type Response = AutotypeGetCredentialsResponse;
497
498 const NAME: &str = "AutotypeGetCredentialsRequest";
499}
500
501struct AutotypeGetCredentialsHandler {
508 driver: JsAutotypeRenderDriver,
509}
510
511impl AutotypeGetCredentialsHandler {
512 fn new(driver: JsAutotypeRenderDriver) -> Self {
513 Self { driver }
514 }
515}
516
517impl RpcHandler for AutotypeGetCredentialsHandler {
518 type Request = AutotypeGetCredentialsRequest;
519
520 async fn handle(
521 &self,
522 request: Self::Request,
523 info: RpcRequestInfo,
524 ) -> AutotypeGetCredentialsResponse {
525 if !from_source(&info, Source::DesktopMain, Self::Request::NAME) {
526 return AutotypeGetCredentialsResponse { credentials: None };
527 }
528
529 AutotypeGetCredentialsResponse {
530 credentials: self.driver.get_autotype_credentials(request.app).await,
531 }
532 }
533}
534
535#[bitwarden_ffi::wasm_export(js_name = registerAutotypeHandlers)]
538pub async fn register_autotype_handlers(ipc_client: &JsIpcClient, driver: RawJsAutotypeDriver) {
539 let runner = ThreadBoundRunner::new(driver);
540
541 ipc_client
542 .client
543 .register_rpc_handler(AutotypeSetEnabledHandler::new(JsAutotypeDriver::new(
544 runner.clone(),
545 )))
546 .await;
547 ipc_client
548 .client
549 .register_rpc_handler(AutotypeSetKeyboardShortcutHandler::new(
550 JsAutotypeDriver::new(runner.clone()),
551 ))
552 .await;
553 ipc_client
554 .client
555 .register_rpc_handler(AutotypeListRunningAppsHandler::new(JsAutotypeDriver::new(
556 runner,
557 )))
558 .await;
559}
560
561#[bitwarden_ffi::wasm_export(js_name = registerAutotypeRenderHandlers)]
565pub async fn register_autotype_render_handlers(
566 ipc_client: &JsIpcClient,
567 driver: RawJsAutotypeRenderDriver,
568) {
569 let runner = ThreadBoundRunner::new(driver);
570
571 ipc_client
572 .client
573 .register_rpc_handler(AutotypeGetCredentialsHandler::new(
574 JsAutotypeRenderDriver::new(runner),
575 ))
576 .await;
577}
578
579#[bitwarden_ffi::wasm_export(js_name = autotypeRequestSetEnabled)]
585pub async fn autotype_request_set_enabled(
586 ipc_client: &JsIpcClient,
587 enabled: bool,
588 abort_signal: Option<AbortSignal>,
589) -> Result<AutotypeSetEnabledResponse, RequestError> {
590 ipc_client
591 .client
592 .request(
593 AutotypeSetEnabledRequest { enabled },
594 Endpoint::DesktopMain,
595 abort_signal.map(|signal| signal.to_cancellation_token()),
596 )
597 .await
598}
599
600#[bitwarden_ffi::wasm_export(js_name = autotypeRequestSetKeyboardShortcut)]
606pub async fn autotype_request_set_keyboard_shortcut(
607 ipc_client: &JsIpcClient,
608 shortcut: Vec<String>,
609 abort_signal: Option<AbortSignal>,
610) -> Result<AutotypeSetKeyboardShortcutResponse, RequestError> {
611 ipc_client
612 .client
613 .request(
614 AutotypeSetKeyboardShortcutRequest { shortcut },
615 Endpoint::DesktopMain,
616 abort_signal.map(|signal| signal.to_cancellation_token()),
617 )
618 .await
619}
620
621#[bitwarden_ffi::wasm_export(js_name = autotypeRequestListRunningApps)]
629pub async fn autotype_request_list_running_apps(
630 ipc_client: &JsIpcClient,
631 abort_signal: Option<AbortSignal>,
632) -> Result<AutotypeListRunningAppsResponse, RequestError> {
633 ipc_client
634 .client
635 .request(
636 AutotypeListRunningAppsRequest,
637 Endpoint::DesktopMain,
638 abort_signal.map(|signal| signal.to_cancellation_token()),
639 )
640 .await
641}
642
643#[bitwarden_ffi::wasm_export(js_name = autotypeRequestGetCredentials)]
659pub async fn autotype_request_get_credentials(
660 ipc_client: &JsIpcClient,
661 app: AutotypeRunningApp,
662 abort_signal: Option<AbortSignal>,
663) -> Result<AutotypeGetCredentialsResponse, RequestError> {
664 ipc_client
665 .client
666 .request(
667 AutotypeGetCredentialsRequest { app },
668 Endpoint::DesktopRenderer,
669 abort_signal.map(|signal| signal.to_cancellation_token()),
670 )
671 .await
672}