mirror of
https://github.com/bitwarden/browser
synced 2026-02-07 04:03:29 +00:00
Add autofill IPC client methods needed for Windows IPC
This commit is contained in:
1
apps/desktop/desktop_native/Cargo.lock
generated
1
apps/desktop/desktop_native/Cargo.lock
generated
@@ -328,6 +328,7 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
name = "autofill_provider"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"desktop_core",
|
||||
"futures",
|
||||
"serde",
|
||||
|
||||
@@ -6,7 +6,7 @@ version = { workspace = true }
|
||||
publish = { workspace = true }
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "cdylib"]
|
||||
crate-type = ["lib", "staticlib", "cdylib"]
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
@@ -14,15 +14,16 @@ name = "uniffi-bindgen"
|
||||
path = "uniffi-bindgen.rs"
|
||||
|
||||
[dependencies]
|
||||
uniffi = { workspace = true, features = ["cli"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
base64 = { workspace = true}
|
||||
desktop_core = { path = "../core" }
|
||||
futures = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
uniffi = { workspace = true, features = ["cli"] }
|
||||
tracing-subscriber = { workspace = true }
|
||||
tracing-oslog = "=0.3.0"
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Autofill Provider
|
||||
|
||||
A library for native autofill providers to interact with a host Bitwarden desktop app.
|
||||
|
||||
# Explainer: Mac OS Native Passkey Provider
|
||||
|
||||
This document describes the changes introduced in https://github.com/bitwarden/clients/pull/13963, where we introduce the MacOS Native Passkey Provider. It gives the high level explanation of the architecture and some of the quirks and additional good to know context.
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
rm -r BitwardenMacosProviderFFI.xcframework
|
||||
rm -r tmp
|
||||
if [ -d "BitwardenMacosProviderFFI.xcframework" ]; then
|
||||
rm -r "BitwardenMacosProviderFFI.xcframework"
|
||||
fi
|
||||
if [ -d "tmp" ]; then
|
||||
rm -r "tmp"
|
||||
fi
|
||||
|
||||
mkdir -p ./tmp/target/universal-darwin/release/
|
||||
|
||||
|
||||
@@ -2,44 +2,60 @@ use std::sync::Arc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
use crate::TimedCallback;
|
||||
use crate::{BitwardenError, Callback, Position, UserVerification};
|
||||
|
||||
#[derive(uniffi::Record, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(target_os = "macos", derive(uniffi::Record))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PasskeyAssertionRequest {
|
||||
rp_id: String,
|
||||
client_data_hash: Vec<u8>,
|
||||
user_verification: UserVerification,
|
||||
allowed_credentials: Vec<Vec<u8>>,
|
||||
window_xy: Position,
|
||||
//extension_input: Vec<u8>, TODO: Implement support for extensions
|
||||
pub rp_id: String,
|
||||
pub client_data_hash: Vec<u8>,
|
||||
pub user_verification: UserVerification,
|
||||
pub allowed_credentials: Vec<Vec<u8>>,
|
||||
pub window_xy: Position,
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub client_window_handle: Vec<u8>,
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub context: String,
|
||||
// pub extension_input: Vec<u8>, TODO: Implement support for extensions
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(target_os = "macos", derive(uniffi::Record))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PasskeyAssertionWithoutUserInterfaceRequest {
|
||||
rp_id: String,
|
||||
credential_id: Vec<u8>,
|
||||
user_name: String,
|
||||
user_handle: Vec<u8>,
|
||||
record_identifier: Option<String>,
|
||||
client_data_hash: Vec<u8>,
|
||||
user_verification: UserVerification,
|
||||
window_xy: Position,
|
||||
pub rp_id: String,
|
||||
pub credential_id: Vec<u8>,
|
||||
#[cfg(target_os = "macos")]
|
||||
pub user_name: String,
|
||||
#[cfg(target_os = "macos")]
|
||||
pub user_handle: Vec<u8>,
|
||||
#[cfg(target_os = "macos")]
|
||||
pub record_identifier: Option<String>,
|
||||
pub client_data_hash: Vec<u8>,
|
||||
pub user_verification: UserVerification,
|
||||
pub window_xy: Position,
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub client_window_handle: Vec<u8>,
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub context: String,
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record, Serialize, Deserialize)]
|
||||
#[cfg_attr(target_os = "macos", derive(uniffi::Record))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PasskeyAssertionResponse {
|
||||
rp_id: String,
|
||||
user_handle: Vec<u8>,
|
||||
signature: Vec<u8>,
|
||||
client_data_hash: Vec<u8>,
|
||||
authenticator_data: Vec<u8>,
|
||||
credential_id: Vec<u8>,
|
||||
pub rp_id: String,
|
||||
pub user_handle: Vec<u8>,
|
||||
pub signature: Vec<u8>,
|
||||
pub client_data_hash: Vec<u8>,
|
||||
pub authenticator_data: Vec<u8>,
|
||||
pub credential_id: Vec<u8>,
|
||||
}
|
||||
|
||||
#[uniffi::export(with_foreign)]
|
||||
#[cfg_attr(target_os = "macos", uniffi::export(with_foreign))]
|
||||
pub trait PreparePasskeyAssertionCallback: Send + Sync {
|
||||
fn on_complete(&self, credential: PasskeyAssertionResponse);
|
||||
fn on_error(&self, error: BitwardenError);
|
||||
@@ -56,3 +72,14 @@ impl Callback for Arc<dyn PreparePasskeyAssertionCallback> {
|
||||
PreparePasskeyAssertionCallback::on_error(self.as_ref(), error);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
impl PreparePasskeyAssertionCallback for TimedCallback<PasskeyAssertionResponse> {
|
||||
fn on_complete(&self, credential: PasskeyAssertionResponse) {
|
||||
self.send(Ok(credential));
|
||||
}
|
||||
|
||||
fn on_error(&self, error: BitwardenError) {
|
||||
self.send(Err(error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,58 @@
|
||||
#![cfg(target_os = "macos")]
|
||||
#![allow(clippy::disallowed_macros)] // uniffi macros trip up clippy's evaluation
|
||||
mod assertion;
|
||||
mod lock_status;
|
||||
mod registration;
|
||||
mod util;
|
||||
mod window_handle_query;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{atomic::AtomicU32, Arc, Mutex, Once},
|
||||
time::Instant,
|
||||
error::Error,
|
||||
fmt::Display,
|
||||
sync::{
|
||||
atomic::AtomicU32,
|
||||
mpsc::{self, Receiver, RecvTimeoutError, Sender},
|
||||
Arc, Mutex,
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use std::sync::Once;
|
||||
|
||||
use futures::FutureExt;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use tracing::{error, info};
|
||||
#[cfg(target_os = "macos")]
|
||||
use tracing_subscriber::{
|
||||
filter::{EnvFilter, LevelFilter},
|
||||
layer::SubscriberExt,
|
||||
util::SubscriberInitExt,
|
||||
};
|
||||
|
||||
uniffi::setup_scaffolding!();
|
||||
use crate::{
|
||||
lock_status::{GetLockStatusCallback, LockStatusRequest},
|
||||
window_handle_query::{GetWindowHandleQueryCallback, WindowHandleQueryRequest},
|
||||
};
|
||||
|
||||
mod assertion;
|
||||
mod registration;
|
||||
|
||||
use assertion::{
|
||||
PasskeyAssertionRequest, PasskeyAssertionWithoutUserInterfaceRequest,
|
||||
pub use assertion::{
|
||||
PasskeyAssertionRequest, PasskeyAssertionResponse, PasskeyAssertionWithoutUserInterfaceRequest,
|
||||
PreparePasskeyAssertionCallback,
|
||||
};
|
||||
use registration::{PasskeyRegistrationRequest, PreparePasskeyRegistrationCallback};
|
||||
pub use lock_status::LockStatusResponse;
|
||||
pub use registration::{
|
||||
PasskeyRegistrationRequest, PasskeyRegistrationResponse, PreparePasskeyRegistrationCallback,
|
||||
};
|
||||
pub use window_handle_query::WindowHandleQueryResponse;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
uniffi::setup_scaffolding!();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
#[derive(uniffi::Enum, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(target_os = "macos", derive(uniffi::Enum))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum UserVerification {
|
||||
Preferred,
|
||||
@@ -37,18 +60,30 @@ pub enum UserVerification {
|
||||
Discouraged,
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(target_os = "macos", derive(uniffi::Record))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Position {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, uniffi::Error, Serialize, Deserialize)]
|
||||
#[cfg_attr(target_os = "macos", derive(uniffi::Error))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum BitwardenError {
|
||||
Internal(String),
|
||||
}
|
||||
|
||||
impl Display for BitwardenError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Internal(msg) => write!(f, "Internal error occurred: {msg}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BitwardenError {}
|
||||
|
||||
// TODO: These have to be named differently than the actual Uniffi traits otherwise
|
||||
// the generated code will lead to ambiguous trait implementations
|
||||
// These are only used internally, so it doesn't matter that much
|
||||
@@ -57,16 +92,17 @@ trait Callback: Send + Sync {
|
||||
fn error(&self, error: BitwardenError);
|
||||
}
|
||||
|
||||
#[derive(uniffi::Enum, Debug)]
|
||||
/// Store the connection status between the macOS credential provider extension
|
||||
#[cfg_attr(target_os = "macos", derive(uniffi::Enum))]
|
||||
#[derive(Debug)]
|
||||
/// Store the connection status between the credential provider extension
|
||||
/// and the desktop application's IPC server.
|
||||
pub enum ConnectionStatus {
|
||||
Connected,
|
||||
Disconnected,
|
||||
}
|
||||
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct MacOSProviderClient {
|
||||
#[cfg_attr(target_os = "macos", derive(uniffi::Object))]
|
||||
pub struct AutofillProviderClient {
|
||||
to_server_send: tokio::sync::mpsc::Sender<String>,
|
||||
|
||||
// We need to keep track of the callbacks so we can call them when we receive a response
|
||||
@@ -81,7 +117,7 @@ pub struct MacOSProviderClient {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// Store native desktop status information to use for IPC communication
|
||||
/// between the application and the macOS credential provider.
|
||||
/// between the application and the credential provider.
|
||||
pub struct NativeStatus {
|
||||
key: String,
|
||||
value: String,
|
||||
@@ -91,12 +127,31 @@ pub struct NativeStatus {
|
||||
// have a callback.
|
||||
const NO_CALLBACK_INDICATOR: u32 = 0;
|
||||
|
||||
#[uniffi::export]
|
||||
impl MacOSProviderClient {
|
||||
// These methods are not currently needed in macOS and/or cannot be exported via FFI
|
||||
impl AutofillProviderClient {
|
||||
pub fn is_available() -> bool {
|
||||
desktop_core::ipc::path("af").exists()
|
||||
}
|
||||
|
||||
pub fn get_lock_status(&self, callback: Arc<dyn GetLockStatusCallback>) {
|
||||
self.send_message(LockStatusRequest {}, Some(Box::new(callback)));
|
||||
}
|
||||
|
||||
pub fn get_window_handle(&self, callback: Arc<dyn GetWindowHandleQueryCallback>) {
|
||||
self.send_message(
|
||||
WindowHandleQueryRequest::default(),
|
||||
Some(Box::new(callback)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_os = "macos", uniffi::export)]
|
||||
impl AutofillProviderClient {
|
||||
// FIXME: Remove unwraps! They panic and terminate the whole application.
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[uniffi::constructor]
|
||||
#[cfg_attr(target_os = "macos", uniffi::constructor)]
|
||||
pub fn connect() -> Self {
|
||||
#[cfg(target_os = "macos")]
|
||||
INIT.call_once(|| {
|
||||
let filter = EnvFilter::builder()
|
||||
// Everything logs at `INFO`
|
||||
@@ -112,10 +167,12 @@ impl MacOSProviderClient {
|
||||
.init();
|
||||
});
|
||||
|
||||
tracing::debug!("Autofill provider attempting to connect to Electron IPC...");
|
||||
|
||||
let (from_server_send, mut from_server_recv) = tokio::sync::mpsc::channel(32);
|
||||
let (to_server_send, to_server_recv) = tokio::sync::mpsc::channel(32);
|
||||
|
||||
let client = MacOSProviderClient {
|
||||
let client = AutofillProviderClient {
|
||||
to_server_send,
|
||||
response_callbacks_counter: AtomicU32::new(1), /* Start at 1 since 0 is reserved for
|
||||
* "no callback" scenarios */
|
||||
@@ -244,7 +301,7 @@ enum SerializedMessage {
|
||||
},
|
||||
}
|
||||
|
||||
impl MacOSProviderClient {
|
||||
impl AutofillProviderClient {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
fn add_callback(&self, callback: Box<dyn Callback>) -> u32 {
|
||||
let sequence_number = self
|
||||
@@ -294,3 +351,94 @@ impl MacOSProviderClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CallbackError {
|
||||
Timeout,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
impl Display for CallbackError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Timeout => f.write_str("Callback timed out"),
|
||||
Self::Cancelled => f.write_str("Callback cancelled"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::error::Error for CallbackError {}
|
||||
|
||||
pub struct TimedCallback<T> {
|
||||
tx: Arc<Mutex<Option<Sender<Result<T, BitwardenError>>>>>,
|
||||
rx: Arc<Mutex<Receiver<Result<T, BitwardenError>>>>,
|
||||
}
|
||||
|
||||
impl<T: Send + 'static> TimedCallback<T> {
|
||||
pub fn new() -> Self {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
Self {
|
||||
tx: Arc::new(Mutex::new(Some(tx))),
|
||||
rx: Arc::new(Mutex::new(rx)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait_for_response(
|
||||
&self,
|
||||
timeout: Duration,
|
||||
cancellation_token: Option<Receiver<()>>,
|
||||
) -> Result<Result<T, BitwardenError>, CallbackError> {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
if let Some(cancellation_token) = cancellation_token {
|
||||
let tx2 = tx.clone();
|
||||
let cancellation_token = Mutex::new(cancellation_token);
|
||||
std::thread::spawn(move || {
|
||||
if let Ok(()) = cancellation_token.lock().unwrap().recv_timeout(timeout) {
|
||||
tracing::debug!("Forwarding cancellation");
|
||||
_ = tx2.send(Err(CallbackError::Cancelled));
|
||||
}
|
||||
});
|
||||
}
|
||||
let response_rx = self.rx.clone();
|
||||
std::thread::spawn(move || {
|
||||
if let Ok(response) = response_rx.lock().unwrap().recv_timeout(timeout) {
|
||||
_ = tx.send(Ok(response));
|
||||
}
|
||||
});
|
||||
match rx.recv_timeout(timeout) {
|
||||
Ok(Ok(response)) => Ok(response),
|
||||
Ok(err @ Err(CallbackError::Cancelled)) => {
|
||||
tracing::debug!("Received cancellation, dropping.");
|
||||
err
|
||||
}
|
||||
Ok(err @ Err(CallbackError::Timeout)) => {
|
||||
tracing::debug!("Request timed out, dropping.");
|
||||
err
|
||||
}
|
||||
Err(RecvTimeoutError::Timeout) => Err(CallbackError::Timeout),
|
||||
Err(_) => Err(CallbackError::Cancelled),
|
||||
}
|
||||
}
|
||||
|
||||
fn send(&self, response: Result<T, BitwardenError>) {
|
||||
match self.tx.lock().unwrap().take() {
|
||||
Some(tx) => {
|
||||
if let Err(_) = tx.send(response) {
|
||||
tracing::error!("Windows provider channel closed before receiving IPC response from Electron")
|
||||
}
|
||||
}
|
||||
None => {
|
||||
tracing::error!("Callback channel used before response: multi-threading issue?");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PreparePasskeyRegistrationCallback for TimedCallback<PasskeyRegistrationResponse> {
|
||||
fn on_complete(&self, credential: PasskeyRegistrationResponse) {
|
||||
self.send(Ok(credential));
|
||||
}
|
||||
|
||||
fn on_error(&self, error: BitwardenError) {
|
||||
self.send(Err(error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{BitwardenError, Callback, TimedCallback};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub(super) struct LockStatusRequest {}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct LockStatusResponse {
|
||||
#[serde(rename = "isUnlocked")]
|
||||
pub is_unlocked: bool,
|
||||
}
|
||||
|
||||
impl Callback for Arc<dyn GetLockStatusCallback> {
|
||||
fn complete(&self, response: serde_json::Value) -> Result<(), serde_json::Error> {
|
||||
let response = serde_json::from_value(response)?;
|
||||
self.as_ref().on_complete(response);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn error(&self, error: BitwardenError) {
|
||||
self.as_ref().on_error(error);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait GetLockStatusCallback: Send + Sync {
|
||||
fn on_complete(&self, response: LockStatusResponse);
|
||||
fn on_error(&self, error: BitwardenError);
|
||||
}
|
||||
|
||||
impl GetLockStatusCallback for TimedCallback<LockStatusResponse> {
|
||||
fn on_complete(&self, response: LockStatusResponse) {
|
||||
self.send(Ok(response));
|
||||
}
|
||||
|
||||
fn on_error(&self, error: BitwardenError) {
|
||||
self.send(Err(error))
|
||||
}
|
||||
}
|
||||
@@ -4,29 +4,35 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{BitwardenError, Callback, Position, UserVerification};
|
||||
|
||||
#[derive(uniffi::Record, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(target_os = "macos", derive(uniffi::Record))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PasskeyRegistrationRequest {
|
||||
rp_id: String,
|
||||
user_name: String,
|
||||
user_handle: Vec<u8>,
|
||||
client_data_hash: Vec<u8>,
|
||||
user_verification: UserVerification,
|
||||
supported_algorithms: Vec<i32>,
|
||||
window_xy: Position,
|
||||
excluded_credentials: Vec<Vec<u8>>,
|
||||
pub rp_id: String,
|
||||
pub user_name: String,
|
||||
pub user_handle: Vec<u8>,
|
||||
pub client_data_hash: Vec<u8>,
|
||||
pub user_verification: UserVerification,
|
||||
pub supported_algorithms: Vec<i32>,
|
||||
pub window_xy: Position,
|
||||
pub excluded_credentials: Vec<Vec<u8>>,
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub client_window_handle: Vec<u8>,
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub context: String,
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record, Serialize, Deserialize)]
|
||||
#[cfg_attr(target_os = "macos", derive(uniffi::Record))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PasskeyRegistrationResponse {
|
||||
rp_id: String,
|
||||
client_data_hash: Vec<u8>,
|
||||
credential_id: Vec<u8>,
|
||||
attestation_object: Vec<u8>,
|
||||
pub rp_id: String,
|
||||
pub client_data_hash: Vec<u8>,
|
||||
pub credential_id: Vec<u8>,
|
||||
pub attestation_object: Vec<u8>,
|
||||
}
|
||||
|
||||
#[uniffi::export(with_foreign)]
|
||||
#[cfg_attr(target_os = "macos", uniffi::export(with_foreign))]
|
||||
pub trait PreparePasskeyRegistrationCallback: Send + Sync {
|
||||
fn on_complete(&self, credential: PasskeyRegistrationResponse);
|
||||
fn on_error(&self, error: BitwardenError);
|
||||
|
||||
24
apps/desktop/desktop_native/autofill_provider/src/util.rs
Normal file
24
apps/desktop/desktop_native/autofill_provider/src/util.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use serde::{de::Visitor, Deserializer};
|
||||
|
||||
pub(crate) fn deserialize_b64<'de, D: Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<Vec<u8>, D::Error> {
|
||||
deserializer.deserialize_str(Base64Visitor {})
|
||||
}
|
||||
|
||||
struct Base64Visitor;
|
||||
impl<'de> Visitor<'de> for Base64Visitor {
|
||||
type Value = Vec<u8>;
|
||||
|
||||
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.write_str("A valid base64 string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||
STANDARD.decode(v).map_err(|err| E::custom(err))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{BitwardenError, Callback, TimedCallback};
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub(super) struct WindowHandleQueryRequest {
|
||||
#[serde(rename = "windowHandle")]
|
||||
window_handle: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WindowHandleQueryResponse {
|
||||
pub is_visible: bool,
|
||||
pub is_focused: bool,
|
||||
#[serde(deserialize_with = "crate::util::deserialize_b64")]
|
||||
pub handle: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Callback for Arc<dyn GetWindowHandleQueryCallback> {
|
||||
fn complete(&self, response: serde_json::Value) -> Result<(), serde_json::Error> {
|
||||
let response = serde_json::from_value(response)?;
|
||||
self.as_ref().on_complete(response);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn error(&self, error: BitwardenError) {
|
||||
self.as_ref().on_error(error);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait GetWindowHandleQueryCallback: Send + Sync {
|
||||
fn on_complete(&self, response: WindowHandleQueryResponse);
|
||||
fn on_error(&self, error: BitwardenError);
|
||||
}
|
||||
|
||||
impl GetWindowHandleQueryCallback for TimedCallback<WindowHandleQueryResponse> {
|
||||
fn on_complete(&self, response: WindowHandleQueryResponse) {
|
||||
self.send(Ok(response));
|
||||
}
|
||||
|
||||
fn on_error(&self, error: BitwardenError) {
|
||||
self.send(Err(error))
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
fn main() {
|
||||
uniffi::uniffi_bindgen_main()
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn main() {
|
||||
unimplemented!("uniffi-bindgen is not enabled on this target.");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user