Skip to content

Commit

Permalink
Create uniffi package and use it from the extension
Browse files Browse the repository at this point in the history
  • Loading branch information
dani-garcia committed Aug 26, 2024
1 parent 45c9202 commit e2b3738
Show file tree
Hide file tree
Showing 24 changed files with 1,604 additions and 27 deletions.
742 changes: 726 additions & 16 deletions 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"]
members = ["napi", "core", "macos_provider"]
6 changes: 6 additions & 0 deletions apps/desktop/desktop_native/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@ arboard = { version = "=3.4.0", default-features = false, features = [
] }
base64 = "=0.22.1"
cbc = { version = "=0.1.2", features = ["alloc"] }
dirs = "5.0.1"
futures = "0.3.30"
interprocess = { version = "2.2.0", features = ["tokio"] }
log = "0.4.21"
rand = "=0.8.5"
retry = "=2.0.0"
scopeguard = "=1.2.0"
sha2 = "=0.10.8"
thiserror = "=1.0.61"
tokio = { version = "1.38.0", features = ["io-util", "sync", "macros"] }
tokio-util = "0.7.11"
typenum = "=1.17.0"

[target.'cfg(windows)'.dependencies]
Expand Down
99 changes: 99 additions & 0 deletions apps/desktop/desktop_native/core/src/ipc/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use std::{
path::{Path, PathBuf},
time::Duration,
};

use interprocess::local_socket::{
tokio::{prelude::*, Stream},
GenericFilePath, ToFsName,
};
use log::{error, info};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
time::sleep,
};

use crate::ipc::NATIVE_MESSAGING_BUFFER_SIZE;

pub async fn connect(
path: PathBuf,
send: tokio::sync::mpsc::Sender<String>,
mut recv: tokio::sync::mpsc::Receiver<String>,
) {
// Keep track of connection failures to make sure we don't leave the process as a zombie
let mut connection_failures = 0;

loop {
match connect_inner(&path, &send, &mut recv).await {
Ok(()) => return,
Err(e) => {
connection_failures += 1;
if connection_failures >= 20 {
error!("Failed to connect to IPC server after 20 attempts: {e}");
return;
}

error!("Failed to connect to IPC server: {e}");
}
}

sleep(Duration::from_secs(5)).await;
}
}

async fn connect_inner(
path: &Path,
send: &tokio::sync::mpsc::Sender<String>,
recv: &mut tokio::sync::mpsc::Receiver<String>,
) -> Result<(), Box<dyn std::error::Error>> {
info!("Attempting to connect to {}", path.display());

let name = path.as_os_str().to_fs_name::<GenericFilePath>()?;
let mut conn = Stream::connect(name).await?;

info!("Connected to {}", path.display());

send.send("{\"command\":\"connected\"}".to_owned()).await?;

let mut buffer = vec![0; NATIVE_MESSAGING_BUFFER_SIZE];

// Listen to IPC messages
loop {
tokio::select! {
// Forward messages to the IPC server
msg = recv.recv() => {
match msg {
Some(msg) => {
conn.write_all(msg.as_bytes()).await?;
}
None => {
info!("Client channel closed");
break;
},
}
},

// Forward messages from the IPC server
res = conn.read(&mut buffer[..]) => {
match res {
Err(e) => {
error!("Error reading from IPC server: {e}");
send.send("{\"command\":\"disconnected\"}".to_owned()).await?;
break;
}
Ok(0) => {
info!("Connection closed");
send.send("{\"command\":\"disconnected\"}".to_owned()).await?;
break;
}
Ok(n) => {
let message = String::from_utf8_lossy(&buffer[..n]).to_string();
send.send(message).await?;
}
}
}
}
}

Ok(())
}
60 changes: 60 additions & 0 deletions apps/desktop/desktop_native/core/src/ipc/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
pub mod client;
pub mod server;

/// The maximum size of a message that can be sent over IPC.
/// According to the documentation, the maximum size sent to the browser is 1MB.
/// While the maximum size sent from the browser to the native messaging host is 4GB.
///
/// Currently we are setting the maximum both ways to be 1MB.
///
/// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging#app_side
/// https://developer.chrome.com/docs/extensions/develop/concepts/native-messaging#native-messaging-host-protocol
pub const NATIVE_MESSAGING_BUFFER_SIZE: usize = 1024 * 1024;


/// Resolve the path to the IPC socket.
pub fn path(name: &str) -> std::path::PathBuf {
#[cfg(target_os = "windows")]
{
// Use a unique IPC pipe //./pipe/xxxxxxxxxxxxxxxxx.app.bitwarden per user.
// Hashing prevents problems with reserved characters and file length limitations.
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use sha2::Digest;
let home = dirs::home_dir().unwrap();
let hash = sha2::Sha256::digest(home.as_os_str().as_encoded_bytes());
let hash_b64 = URL_SAFE_NO_PAD.encode(hash.as_slice());

format!(r"\\.\pipe\{hash_b64}.app.{name}").into()
}

#[cfg(target_os = "macos")]
{
let mut home = dirs::home_dir().unwrap();

// When running in an unsandboxed environment, path is: /Users/<user>/
// While running sandboxed, it's different: /Users/<user>/Library/Containers/com.bitwarden.desktop/Data
//
// We want to use App Groups in /Users/<user>/Library/Group Containers/LTZ2PFU5D6.com.bitwarden.desktop,
// so we need to remove all the components after the user.
// Note that we subtract 3 because the root directory is counted as a component (/, Users, <user>).
let num_components = home.components().count();
for _ in 0..num_components - 3 {
home.pop();
}

home.join(format!(
"Library/Group Containers/LTZ2PFU5D6.com.bitwarden.desktop/tmp/app.{name}"
))
}

#[cfg(target_os = "linux")]
{
// On Linux, we use the user's cache directory.
let home = dirs::cache_dir().unwrap();
let path_dir = home.join("com.bitwarden.desktop");

// The chache directory might not exist, so create it
let _ = std::fs::create_dir_all(&path_dir);
path_dir.join(format!("app.{name}"))
}
}
Loading

0 comments on commit e2b3738

Please sign in to comment.