Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create uniffi package and use it from the extension #10466

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
483 changes: 482 additions & 1 deletion apps/desktop/desktop_native/Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/desktop/desktop_native/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[workspace]
resolver = "2"
members = ["napi", "core", "proxy"]
members = ["napi", "core", "proxy", "macos_provider"]
1 change: 1 addition & 0 deletions apps/desktop/desktop_native/macos_provider/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BitwardenMacosProviderFFI.xcframework
30 changes: 30 additions & 0 deletions apps/desktop/desktop_native/macos_provider/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "macos_provider"
license = "GPL-3.0"
version = "0.0.0"
edition = "2021"
publish = false

[[bin]]
name = "uniffi-bindgen"
path = "uniffi-bindgen.rs"

[lib]
crate-type = ["staticlib", "cdylib"]
bench = false

[dependencies]
desktop_core = { path = "../core" }
futures = "=0.3.31"
log = "0.4.22"
serde = { version = "1.0.205", features = ["derive"] }
serde_json = "1.0.122"
tokio = { version = "1.39.2", features = ["sync"] }
tokio-util = "0.7.11"
uniffi = { version = "0.28.0", features = ["cli"] }

[target.'cfg(target_os = "macos")'.dependencies]
oslog = "0.2.0"

[build-dependencies]
uniffi = { version = "0.28.0", features = ["build"] }
41 changes: 41 additions & 0 deletions apps/desktop/desktop_native/macos_provider/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
cd "$(dirname "$0")"

rm -r BitwardenMacosProviderFFI.xcframework
rm -r tmp

mkdir -p ./tmp/target/universal-darwin/release/


cargo build --package macos_provider --target aarch64-apple-darwin --release
cargo build --package macos_provider --target x86_64-apple-darwin --release

# Create universal libraries
lipo -create ../target/aarch64-apple-darwin/release/libmacos_provider.a \
../target/x86_64-apple-darwin/release/libmacos_provider.a \
-output ./tmp/target/universal-darwin/release/libmacos_provider.a

# Generate swift bindings
cargo run --bin uniffi-bindgen --features uniffi/cli generate \
../target/aarch64-apple-darwin/release/libmacos_provider.dylib \
--library \
--language swift \
--no-format \
--out-dir tmp/bindings

# Move generated swift bindings
mkdir -p ../../macos/autofill-extension/
mv ./tmp/bindings/*.swift ../../macos/autofill-extension/

# Massage the generated files to fit xcframework
mkdir tmp/Headers
mv ./tmp/bindings/*.h ./tmp/Headers/
cat ./tmp/bindings/*.modulemap > ./tmp/Headers/module.modulemap

# Build xcframework
xcodebuild -create-xcframework \
-library ./tmp/target/universal-darwin/release/libmacos_provider.a \
-headers ./tmp/Headers \
-output ./BitwardenMacosProviderFFI.xcframework

# Cleanup temporary files
rm -r tmp
176 changes: 176 additions & 0 deletions apps/desktop/desktop_native/macos_provider/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#![cfg(target_os = "macos")]

use std::{
collections::HashMap,
sync::{atomic::AtomicU32, Arc, Mutex},
};

use futures::FutureExt;
use log::{error, info, warn};
use registration::{PasskeyRegistrationRequest, PreparePasskeyRegistrationCallback};
use serde::{de::DeserializeOwned, Deserialize, Serialize};

uniffi::setup_scaffolding!();

mod registration;

#[derive(uniffi::Enum, Debug, Serialize, Deserialize)]
pub enum UserVerification {
Preferred,
Required,
Discouraged,
}

#[derive(uniffi::Error, Serialize, Deserialize)]
pub enum BitwardenError {
Internal(String),
}

// 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
trait Callback: Send + Sync {
fn complete(&self, credential: serde_json::Value) -> Result<(), serde_json::Error>;
fn error(&self, error: BitwardenError);
}

#[derive(uniffi::Object)]
pub struct MacOSProviderClient {
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
response_callbacks_counter: AtomicU32,
response_callbacks_queue: Arc<Mutex<HashMap<u32, Box<dyn Callback>>>>,
}

#[uniffi::export]
impl MacOSProviderClient {
#[uniffi::constructor]
pub fn connect() -> Self {
let _ = oslog::OsLogger::new("com.bitwarden.desktop.autofill-extension")
.level_filter(log::LevelFilter::Trace)
.init();

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 {
to_server_send,
response_callbacks_counter: AtomicU32::new(0),
response_callbacks_queue: Arc::new(Mutex::new(HashMap::new())),
};

let path = desktop_core::ipc::path("autofill");

let queue = client.response_callbacks_queue.clone();
std::thread::spawn(move || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Can't create runtime");

rt.spawn(
desktop_core::ipc::client::connect(path, from_server_send, to_server_recv)
.map(|r| r.map_err(|e| e.to_string())),
);

rt.block_on(async move {
while let Some(message) = from_server_recv.recv().await {
match serde_json::from_str::<SerializedMessage>(&message) {
Ok(SerializedMessage::Connected) => {
info!("Connected to server");
}
Ok(SerializedMessage::Disconnected) => {
info!("Disconnected from server");
}
Ok(SerializedMessage::Message {
sequence_number,
value,
}) => match queue.lock().unwrap().remove(&sequence_number) {
Some(cb) => match value {
Ok(value) => {
if let Err(e) = cb.complete(value) {
error!("Error deserializing message: {}", e);
}
}
Err(e) => cb.error(e),
},
None => {
error!("No callback found for sequence number: {}", sequence_number)
}
},
Err(e) => {
error!("Error deserializing message: {}", e);
}
};
}
});
});

client
}

pub fn prepare_passkey_registration(
&self,
request: PasskeyRegistrationRequest,
callback: Arc<dyn PreparePasskeyRegistrationCallback>,
) {
warn!("prepare_passkey_registration: {:?}", request);

self.send_message(request, Box::new(callback));
}
}
#[derive(Serialize, Deserialize)]
#[serde(tag = "command")]
enum SerializedMessage {
Connected,
Disconnected,
Message {
sequence_number: u32,
value: Result<serde_json::Value, BitwardenError>,
},
}

impl MacOSProviderClient {
fn add_callback(&self, callback: Box<dyn Callback>) -> u32 {
let sequence_number = self
.response_callbacks_counter
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);

self.response_callbacks_queue
.lock()
.unwrap()
.insert(sequence_number, callback);

sequence_number
}

fn send_message(
&self,
message: impl Serialize + DeserializeOwned,
callback: Box<dyn Callback>,
) {
let sequence_number = self.add_callback(callback);

let message = serde_json::to_string(&SerializedMessage::Message {
sequence_number,
value: Ok(serde_json::to_value(message).unwrap()),
})
.expect("Can't serialize message");

if let Err(e) = self.to_server_send.blocking_send(message) {
// Make sure we remove the callback from the queue if we can't send the message
if let Some(cb) = self
.response_callbacks_queue
.lock()
.unwrap()
.remove(&sequence_number)
{
cb.error(BitwardenError::Internal(format!(
"Error sending message: {}",
e
)));
}
}
}
}
41 changes: 41 additions & 0 deletions apps/desktop/desktop_native/macos_provider/src/registration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use std::sync::Arc;

use serde::{Deserialize, Serialize};

use crate::{BitwardenError, Callback, UserVerification};

#[derive(uniffi::Record, Debug, Serialize, Deserialize)]
pub struct PasskeyRegistrationRequest {
relying_party_id: String,
user_name: String,
user_handle: Vec<u8>,

client_data_hash: Vec<u8>,
user_verification: UserVerification,
}

#[derive(uniffi::Record, Serialize, Deserialize)]
pub struct PasskeyRegistrationCredential {
relying_party: String,
client_data_hash: Vec<u8>,
credential_id: Vec<u8>,
attestation_object: Vec<u8>,
}

#[uniffi::export(with_foreign)]
pub trait PreparePasskeyRegistrationCallback: Send + Sync {
fn on_complete(&self, credential: PasskeyRegistrationCredential);
fn on_error(&self, error: BitwardenError);
}

impl Callback for Arc<dyn PreparePasskeyRegistrationCallback> {
fn complete(&self, credential: serde_json::Value) -> Result<(), serde_json::Error> {
let credential = serde_json::from_value(credential)?;
PreparePasskeyRegistrationCallback::on_complete(self.as_ref(), credential);
Ok(())
}

fn error(&self, error: BitwardenError) {
PreparePasskeyRegistrationCallback::on_error(self.as_ref(), error);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
uniffi::uniffi_bindgen_main()
}
4 changes: 4 additions & 0 deletions apps/desktop/desktop_native/macos_provider/uniffi.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[bindings.swift]
ffi_module_name = "BitwardenMacosProviderFFI"
module_name = "BitwardenMacosProvider"
generate_immutable_records = true
1 change: 1 addition & 0 deletions apps/desktop/macos/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BitwardenMacosProvider.swift
Loading