1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00
Files
browser/apps/desktop/desktop_native/objc/src/lib.rs
Andreas Coroiu f16bfa4cd2 [PM-9035] desktop build logic to provide credentials to os on sync (#10181)
* feat: scaffold desktop_objc

* feat: rename fido2 to autofill

* feat: scaffold electron autofill

* feat: auto call hello world on init

* feat: scaffold call to basic objc function

* feat: simple log that checks if autofill is enabled

* feat: adding some availability guards

* feat: scaffold services and allow calls from inspector

* feat: create custom type for returning strings across rust/objc boundary

* chore: clean up comments

* feat: enable ARC

* feat: add util function `c_string_to_nsstring`

* chore: refactor and rename to `run_command`

* feat: add try-catch around command execution

* feat: properly implement command calling

Add static typing. Add proper error handling.

* feat: add autoreleasepool to avoid memory leaks

* chore: change objc names to camelCase

* fix: error returning

* feat: extract some helper functions into utils class

* feat: scaffold status command

* feat: implement status command

* feat: implement password credential mapping

* wip: implement sync command

This crashes because we are not properly handling the fact that `saveCredentialIdentities` uses callbacks, resulting in a race condition where we try to access a variable (result) that has already gotten dealloc'd.

* feat: first version of callback

* feat: make run_command async

* feat: functioning callback returns

* chore: refactor to make objc code easier to read and use

* feat: refactor everything to use new callback return method

* feat: re-implement status command with callback

* fix: warning about CommandContext not being FFI-safe

* feat: implement sync command using callbacks

* feat: implement manual password credential sync

* feat: add auto syncing

* docs: add todo

* feat: add support for passkeys

* chore: move desktop autofill service to init service

* feat: auto-add all .m files to builder

* fix: native build on unix and windows

* fix: unused compiler warnings

* fix: napi type exports

* feat: add corresponding dist command

* feat: comment signing profile until we fix signing

* fix: build breaking on non-macOS platforms

* chore: cargo lock update

* chore: revert accidental version change

* feat: put sync behind feature flag

* chore: put files in autofill folder

* fix: obj-c code not recompiling on changes

* feat: add `namespace` to commands

* fix: linting complaining about flag

* feat: add autofill as owner of their objc code

* chore: make autofill owner of run_command in core crate

* fix: re-add napi annotation

* fix: remove dev bypass
2024-12-06 16:31:30 +01:00

125 lines
3.1 KiB
Rust

#![cfg(target_os = "macos")]
use std::{
ffi::{c_char, CStr, CString},
os::raw::c_void,
};
use anyhow::{Context, Result};
#[repr(C)]
pub struct ObjCString {
value: *const c_char,
size: usize,
}
#[repr(C)]
pub struct CommandContext {
tx: Option<tokio::sync::oneshot::Sender<String>>,
}
impl CommandContext {
pub fn new() -> (Self, tokio::sync::oneshot::Receiver<String>) {
let (tx, rx) = tokio::sync::oneshot::channel::<String>();
(CommandContext { tx: Some(tx) }, rx)
}
pub fn send(&mut self, value: String) -> Result<()> {
let tx = self.tx.take().context(
"Failed to take Sender from CommandContext. Has this context already returned once?",
)?;
tx.send(value).map_err(|_| {
anyhow::anyhow!("Failed to send ObjCString from CommandContext to Rust code")
})?;
Ok(())
}
pub fn as_ptr(&mut self) -> *mut c_void {
self as *mut Self as *mut c_void
}
}
impl TryFrom<ObjCString> for String {
type Error = anyhow::Error;
fn try_from(value: ObjCString) -> Result<Self> {
let c_str = unsafe { CStr::from_ptr(value.value) };
let str = c_str
.to_str()
.context("Failed to convert ObjC output string to &str for use in Rust")?;
Ok(str.to_owned())
}
}
impl Drop for ObjCString {
fn drop(&mut self) {
unsafe {
objc::freeObjCString(self);
}
}
}
mod objc {
use std::os::raw::c_void;
use super::*;
extern "C" {
pub fn runCommand(context: *mut c_void, value: *const c_char);
pub fn freeObjCString(value: &ObjCString);
}
/// This function is called from the ObjC code to return the output of the command
#[no_mangle]
pub extern "C" fn commandReturn(context: &mut CommandContext, value: ObjCString) -> bool {
let value: String = match value.try_into() {
Ok(value) => value,
Err(e) => {
println!(
"Error: Failed to convert ObjCString to Rust string during commandReturn: {}",
e
);
return false;
}
};
match context.send(value) {
Ok(_) => 0,
Err(e) => {
println!(
"Error: Failed to return ObjCString from ObjC code to Rust code: {}",
e
);
return false;
}
};
return true;
}
}
pub async fn run_command(input: String) -> Result<String> {
// Convert input to type that can be passed to ObjC code
let c_input = CString::new(input)
.context("Failed to convert Rust input string to a CString for use in call to ObjC code")?;
let (mut context, rx) = CommandContext::new();
// Call ObjC code
unsafe { objc::runCommand(context.as_ptr(), c_input.as_ptr()) };
// Convert output from ObjC code to Rust string
let objc_output = rx.await?.try_into()?;
// Convert output from ObjC code to Rust string
// let objc_output = output.try_into()?;
Ok(objc_output)
}