Files
tutabridge/src-tauri/src/main.rs
T
Anthony MandGitHub e58ee86bed Restructure into Cargo workspace with Tauri desktop GUI (#1)
Split the bridge into a tutabridge-core crate, a Tauri v2 desktop app
(src-tauri) and a React/TS UI (ui), keeping the CLI entrypoint at the
workspace root.

Add encrypted local storage (SQLCipher metadata index + encrypted .eml
files) so mail persists across launches and only the delta is fetched.

Wire the bridge to the Tuta Rust SDK via the tuta-repo submodule
(batch loading, MailDetailsBlob reading, interactive 2FA login).

Implement SMTP sending: build the draft and send it through Tuta's
DraftService/SendDraftService, mirroring the web client (body in
compressedBodyText, non-empty sender/recipient names, populated
SendDraftParameters). Add unit tests for the draft/send payload building.
2026-05-27 14:03:15 +02:00

95 lines
2.8 KiB
Rust

#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod commands;
use std::sync::Arc;
use commands::BridgeState;
use tauri::Manager;
use tokio::sync::Mutex;
use tutabridge_core::bridge::BridgeHandle;
fn main() {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug"))
.init();
tokio_rustls::rustls::crypto::ring::default_provider()
.install_default()
.expect("Failed to install TLS crypto provider");
let handle = BridgeHandle::new();
let log_rx = handle.subscribe_logs();
let shared = Arc::new(Mutex::new(handle));
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.manage(shared as BridgeState)
.invoke_handler(tauri::generate_handler![
commands::get_config,
commands::save_config,
commands::has_saved_session,
commands::start_bridge,
commands::stop_bridge,
commands::get_status,
commands::get_stats,
commands::get_bridge_password,
commands::regenerate_bridge_password,
])
.setup(|app| {
let app_handle = app.handle().clone();
tauri::async_runtime::spawn(async move {
stream_logs(app_handle, log_rx).await;
});
let state = app.state::<BridgeState>().inner().clone();
tauri::async_runtime::spawn(async move {
auto_start(state).await;
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error running TutaBridge");
}
async fn auto_start(state: Arc<Mutex<BridgeHandle>>) {
use tutabridge_core::config;
use tutabridge_core::tuta;
let mut cfg = match config::load_config() {
Ok(Some(cfg)) if !cfg.email.is_empty() => cfg,
_ => return,
};
// Ensure bridge password exists (so the UI can display it)
if let Err(e) = config::ensure_bridge_password(&mut cfg) {
log::warn!("Bridge password setup failed: {e}");
}
if !tuta::has_saved_session(&cfg.email) {
return;
}
let mut handle = state.lock().await;
if let Err(e) = handle.start(cfg, None, None).await {
log::warn!("Auto-start failed: {e}");
}
}
async fn stream_logs(
app: tauri::AppHandle,
mut rx: tokio::sync::broadcast::Receiver<String>,
) {
use tauri::Emitter;
loop {
match rx.recv().await {
Ok(line) => {
let _ = app.emit("bridge://log", &line);
}
Err(tokio::sync::broadcast::error::RecvError::Lagged(n)) => {
let _ = app.emit("bridge://log", &format!("... skipped {n} log lines"));
}
Err(tokio::sync::broadcast::error::RecvError::Closed) => break,
}
}
}