upgrade to tauri 2.0 beta

This commit is contained in:
2024-06-01 20:17:50 -04:00
parent b165965289
commit 816bd7db00
28 changed files with 6323 additions and 1065 deletions

2089
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package]
name = "creddy"
version = "0.4.7"
version = "0.4.8"
description = "A friendly AWS credentials manager"
authors = ["Joseph Montanaro"]
license = ""
@ -20,13 +20,12 @@ path = "src/main.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.0.4", features = [] }
tauri-build = { version = "2.0.0-beta", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.2", features = [ "app-all", "dialog", "dialog-open", "global-shortcut", "os-all", "system-tray"] }
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" }
tauri = { version = "2.0.0-beta", features = ["tray-icon"] }
sodiumoxide = "0.2.7"
tokio = { version = ">=1.19", features = ["full"] }
sqlx = { version = "0.6.2", features = ["sqlite", "runtime-tokio-rustls"] }
@ -48,6 +47,9 @@ chacha20poly1305 = { version = "0.10.1", features = ["std"] }
which = "4.4.0"
windows = { version = "0.51.1", features = ["Win32_Foundation", "Win32_System_Pipes"] }
time = "0.3.31"
tauri-plugin-single-instance = "2.0.0-beta.9"
tauri-plugin-global-shortcut = "2.0.0-beta.6"
rfd = { version = "0.14", default-features = false, features = [ "tokio", "gtk3", "common-controls-v6" ] }
[features]
# by default Tauri runs in production mode

View File

@ -0,0 +1,17 @@
{
"identifier": "migrated",
"description": "permissions that were migrated from v1",
"local": true,
"windows": [
"main"
],
"permissions": [
"path:default",
"event:default",
"window:default",
"app:default",
"resources:default",
"menu:default",
"tray:default"
]
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default"]}}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -33,10 +33,9 @@ pub fn run() -> tauri::Result<()> {
tauri::Builder::default()
.plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| {
show_main_window(app)
.error_popup("Failed to show main window");
.blocking_error_popup("Failed to show main window")
}))
.system_tray(tray::create())
.on_system_tray_event(tray::handle_event)
.plugin(tauri_plugin_global_shortcut::Builder::default().build())
.invoke_handler(tauri::generate_handler![
ipc::unlock,
ipc::respond,
@ -77,8 +76,8 @@ pub async fn connect_db() -> Result<SqlitePool, SetupError> {
async fn setup(app: &mut App) -> Result<(), Box<dyn Error>> {
APP.set(app.handle()).unwrap();
APP.set(app.handle().clone()).unwrap();
tray::setup(app)?;
// get_or_create_db_path doesn't create the actual db file, just the directory
let is_first_launch = !config::get_or_create_db_path()?.exists();
let pool = connect_db().await?;
@ -96,7 +95,7 @@ async fn setup(app: &mut App) -> Result<(), Box<dyn Error>> {
};
let session = Session::load(&pool).await?;
Server::start(app.handle())?;
Server::start(app.handle().clone())?;
config::set_auto_launch(conf.start_on_login)?;
if let Err(_e) = config::set_auto_launch(conf.start_on_login) {
@ -123,7 +122,7 @@ async fn setup(app: &mut App) -> Result<(), Box<dyn Error>> {
app.manage(state);
// make sure we do this after managing app state, so that it doesn't panic
start_auto_locker(app.app_handle());
start_auto_locker(app.app_handle().clone());
Ok(())
}
@ -138,7 +137,7 @@ fn start_auto_locker(app: AppHandle) {
tokio::time::sleep(delay).await;
if state.should_auto_lock().await {
state.lock().await.error_popup("Failed to lock Creddy");
state.lock().await.error_popup("Failed to lock Creddy").await;
}
}
});
@ -146,27 +145,27 @@ fn start_auto_locker(app: AppHandle) {
pub fn show_main_window(app: &AppHandle) -> Result<(), WindowError> {
let w = app.get_window("main").ok_or(WindowError::NoMainWindow)?;
let w = app.get_webview_window("main").ok_or(WindowError::NoMainWindow)?;
w.show()?;
app.tray_handle()
.get_item("show_hide")
.set_title("Hide")?;
// app.tray_handle()
// .get_item("show_hide")
// .set_title("Hide")?;
Ok(())
}
pub fn hide_main_window(app: &AppHandle) -> Result<(), WindowError> {
let w = app.get_window("main").ok_or(WindowError::NoMainWindow)?;
let w = app.get_webview_window("main").ok_or(WindowError::NoMainWindow)?;
w.hide()?;
app.tray_handle()
.get_item("show_hide")
.set_title("Show")?;
// app.tray_handle()
// .get_item("show_hide")
// .set_title("Show")?;
Ok(())
}
pub fn toggle_main_window(app: &AppHandle) -> Result<(), WindowError> {
let w = app.get_window("main").ok_or(WindowError::NoMainWindow)?;
let w = app.get_webview_window("main").ok_or(WindowError::NoMainWindow)?;
if w.is_visible()? {
hide_main_window(app)
}

View File

@ -135,9 +135,12 @@ impl LockedCredentials {
}
fn default_credentials_version() -> usize { 1 }
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct BaseCredentials {
#[serde(default = "default_credentials_version")]
pub version: usize,
pub access_key_id: String,
pub secret_access_key: String,
@ -167,6 +170,7 @@ impl BaseCredentials {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SessionCredentials {
#[serde(default = "default_credentials_version")]
pub version: usize,
pub access_key_id: String,
pub secret_access_key: String,

View File

@ -1,7 +1,6 @@
use std::error::Error;
use std::convert::AsRef;
use std::ffi::OsString;
use std::sync::mpsc;
use std::string::FromUtf8Error;
use strum_macros::AsRefStr;
@ -10,14 +9,16 @@ use aws_sdk_sts::{
types::SdkError as AwsSdkError,
error::GetSessionTokenError,
};
use rfd::{
AsyncMessageDialog,
MessageDialog,
MessageLevel,
};
use sqlx::{
error::Error as SqlxError,
migrate::MigrateError,
};
use tauri::api::dialog::{
MessageDialogBuilder,
MessageDialogKind,
};
use tauri_plugin_global_shortcut::Error as ShortcutError;
use tokio::sync::oneshot::error::RecvError;
use serde::{
Serialize,
@ -27,33 +28,66 @@ use serde::{
};
pub trait ShowError {
fn error_popup(self, title: &str);
fn error_popup_nowait(self, title: &str);
#[allow(async_fn_in_trait)]
pub trait ShowError<T>
{
async fn error_popup(self, title: &str);
async fn error_popup_passthrough(self, title: &str) -> Result<T, ()>;
fn blocking_error_popup(self, title: &str);
fn error_print(self);
fn error_print_prefix(self, prefix: &str);
}
impl<E: std::fmt::Display> ShowError for Result<(), E> {
fn error_popup(self, title: &str) {
impl<T, E> ShowError<T> for Result<T, E>
where E: std::fmt::Display
{
async fn error_popup(self, title: &str) {
if let Err(e) = self {
let (tx, rx) = mpsc::channel();
MessageDialogBuilder::new(title, format!("{e}"))
.kind(MessageDialogKind::Error)
.show(move |_| tx.send(true).unwrap());
rx.recv().unwrap();
AsyncMessageDialog::new()
.set_level(MessageLevel::Error)
.set_title(title)
.set_description(format!("{e}"))
.show()
.await;
}
}
fn error_popup_nowait(self, title: &str) {
fn blocking_error_popup(self, title: &str) {
if let Err(e) = self {
MessageDialogBuilder::new(title, format!("{e}"))
.kind(MessageDialogKind::Error)
.show(|_| {})
MessageDialog::new()
.set_level(MessageLevel::Error)
.set_title(title)
.set_description(format!("{e}"))
.show();
}
}
async fn error_popup_passthrough(self, title: &str) -> Result<T, ()> {
match self {
Ok(v) => Ok(v),
Err(e) => {
AsyncMessageDialog::new()
.set_level(MessageLevel::Error)
.set_title(title)
.set_description(format!("{e}"))
.show()
.await;
Err(())
}
}
}
// fn error_popup_nowait(self, title: &str) {
// if let Err(e) = self {
// let app = app::APP.get().expect("Error popup failed, app handle not available");
// app.dialog()
// .message(format!("{e}"))
// .kind(MessageDialogKind::Error)
// .title(title)
// .show(|_| {})
// }
// }
fn error_print(self) {
if let Err(e) = self {
eprintln!("{e}");
@ -144,7 +178,7 @@ pub enum SetupError {
#[error("Failed to resolve data directory: {0}")]
DataDir(#[from] DataDirError),
#[error("Failed to register hotkeys: {0}")]
RegisterHotkeys(#[from] tauri::Error),
RegisterHotkeys(#[from] ShortcutError),
}

View File

@ -13,7 +13,7 @@ use creddy::{
fn main() {
let res = match cli::parser().get_matches().subcommand() {
None | Some(("run", _)) => {
app::run().error_popup("Creddy failed to start");
app::run().blocking_error_popup("Creddy failed to start");
Ok(())
},
Some(("get", m)) => cli::get(m),

View File

@ -126,12 +126,12 @@ async fn get_aws_credentials(
// so we bundle it all up in an async block and return a Result so we can handle errors
let proceed = async {
let notification = AwsRequestNotification {id: request_id, client, base};
app_handle.emit_all("credentials-request", &notification)?;
app_handle.emit("credentials-request", &notification)?;
let response = tokio::select! {
r = chan_recv => r?,
_ = waiter.wait_for_close() => {
app_handle.emit_all("request-cancelled", request_id)?;
app_handle.emit("request-cancelled", request_id)?;
return Err(HandlerError::Abandoned);
},
};

View File

@ -2,7 +2,6 @@ use std::io::ErrorKind;
use tokio::net::{UnixListener, UnixStream};
use tauri::{
AppHandle,
Manager,
async_runtime as rt,
};
@ -41,7 +40,7 @@ impl Server {
async fn try_serve(&self) -> Result<(), HandlerError> {
let (stream, _addr) = self.listener.accept().await?;
let new_handle = self.app_handle.app_handle();
let new_handle = self.app_handle.clone();
let client_pid = get_client_pid(&stream)?;
rt::spawn(async move {
super::handle(stream, new_handle, client_pid)

View File

@ -52,7 +52,7 @@ impl Server {
// create a new pipe instance to listen for the next client, and swap it in
let new_listener = ServerOptions::new().create(r"\\.\pipe\creddy-requests")?;
let stream = std::mem::replace(&mut self.listener, new_listener);
let new_handle = self.app_handle.app_handle();
let new_handle = self.app_handle.clone();
let client_pid = get_client_pid(&stream)?;
rt::spawn(async move {
super::handle(stream, new_handle, client_pid)

View File

@ -1,11 +1,16 @@
use serde::{Serialize, Deserialize};
use tauri::{
GlobalShortcutManager,
AppHandle,
Manager,
async_runtime as rt,
};
use tauri_plugin_global_shortcut::{
GlobalShortcutExt,
Error as ShortcutError,
};
use crate::app::APP;
use crate::config::HotkeysConfig;
use crate::errors::*;
@ -21,38 +26,62 @@ pub enum ShortcutAction {
pub fn exec_shortcut(action: ShortcutAction) {
match action {
ShortcutAction::LaunchTerminal => launch_terminal(),
ShortcutAction::ShowWindow => {
let app = APP.get().unwrap();
app.get_window("main")
.ok_or("Couldn't find application main window")
.map(|w| w.show().error_popup("Failed to show window"))
.error_popup("Failed to show window");
},
ShortcutAction::LaunchTerminal => {
rt::spawn(async {
terminal::launch(false).await.error_popup("Failed to launch terminal");
});
show_window(app);
},
}
}
pub fn register_hotkeys(hotkeys: &HotkeysConfig) -> tauri::Result<()> {
fn show_window(app: &AppHandle) {
let handle = app.clone();
rt::spawn(async move {
handle.get_webview_window("main")
.ok_or("Couldn't find application main window")
.error_popup_passthrough("Failed to show window").await?
.show()
.error_popup("Failed to show window").await;
Ok::<(), ()>(())
});
// app.get_webview_window("main") // Option<Window>
// .ok_or("Couldn't find application main window") // Result<Window, &'static str>
// .map(|w| w.show().error_popup("Failed to show window"))
// .error_popup("Failed to show window");
}
fn launch_terminal() {
rt::spawn(async {
terminal::launch(false)
.await
.error_popup("Failed to launch terminal")
.await;
});
}
pub fn register_hotkeys(hotkeys: &HotkeysConfig) -> Result<(), ShortcutError> {
let app = APP.get().unwrap();
let mut manager = app.global_shortcut_manager();
manager.unregister_all()?;
let shortcuts = app.global_shortcut();
shortcuts.unregister_all([
hotkeys.show_window.keys.as_str(),
hotkeys.launch_terminal.keys.as_str(),
])?;
if hotkeys.show_window.enabled {
manager.register(
&hotkeys.show_window.keys,
|| exec_shortcut(ShortcutAction::ShowWindow)
shortcuts.on_shortcut(
hotkeys.show_window.keys.as_str(),
|app, _shortcut, _event| show_window(app)
)?;
}
if hotkeys.launch_terminal.enabled {
manager.register(
&hotkeys.launch_terminal.keys,
|| exec_shortcut(ShortcutAction::LaunchTerminal)
shortcuts.on_shortcut(
hotkeys.launch_terminal.keys.as_str(),
|_app, _shortcut, _event| launch_terminal()
)?;
}

View File

@ -37,7 +37,7 @@ impl Visibility {
fn acquire(&mut self, delay_ms: u64) -> Result<VisibilityLease, WindowError> {
let app = crate::app::APP.get().unwrap();
let window = app.get_window("main")
let window = app.get_webview_window("main")
.ok_or(WindowError::NoMainWindow)?;
self.leases += 1;
@ -63,18 +63,17 @@ impl Visibility {
let lease = VisibilityLease { notify: tx };
let delay = Duration::from_millis(delay_ms);
let handle = app.app_handle();
rt::spawn(async move {
// We don't care if it's an error; lease being dropped should be handled identically
let _ = rx.await;
tokio::time::sleep(delay).await;
// we can't use `self` here because we would have to move it into the async block
let state = handle.state::<AppState>();
let state = app.state::<AppState>();
let mut visibility = state.visibility.write().await;
visibility.leases -= 1;
if visibility.leases == 0 {
if let Some(false) = visibility.original {
app::hide_main_window(&handle).error_print();
app::hide_main_window(app).error_print();
}
visibility.original = None;
}
@ -222,7 +221,7 @@ impl AppState {
*session = Session::load(&self.pool).await?;
let app_handle = app::APP.get().unwrap();
app_handle.emit_all("locked", None::<usize>)?;
app_handle.emit("locked", None::<usize>)?;
Ok(())
}

View File

@ -23,16 +23,16 @@ pub async fn launch(use_base: bool) -> Result<(), LaunchTerminalError> {
cmd
};
// if session is unlocked or empty, wait for credentials from frontend
// if session is locked or empty, wait for credentials from frontend
if !state.is_unlocked().await {
app.emit_all("launch-terminal-request", ())?;
app.emit("launch-terminal-request", ())?;
let lease = state.acquire_visibility_lease(0).await
.map_err(|_e| LaunchTerminalError::NoMainWindow)?; // automate conversion eventually?
let (tx, rx) = tokio::sync::oneshot::channel();
app.once_global("credentials-event", move |e| {
app.once("credentials-event", move |e| {
let success = match e.payload() {
Some("\"unlocked\"") | Some("\"entered\"") => true,
"\"unlocked\"" | "\"entered\"" => true,
_ => false,
};
let _ = tx.send(success);

View File

@ -1,45 +1,46 @@
use tauri::{
App,
AppHandle,
CustomMenuItem,
Manager,
SystemTray,
SystemTrayEvent,
SystemTrayMenu,
async_runtime as rt,
};
use tauri::menu::{
MenuBuilder,
MenuEvent,
MenuItemBuilder,
};
use crate::app;
use crate::state::AppState;
pub fn create() -> SystemTray {
let show = CustomMenuItem::new("show_hide".to_string(), "Show");
let quit = CustomMenuItem::new("exit".to_string(), "Exit");
pub fn setup(app: &App) -> tauri::Result<()> {
let show_hide = MenuItemBuilder::with_id("show_hide", "Show/Hide").build(app)?;
let exit = MenuItemBuilder::with_id("exit", "Exit").build(app)?;
let menu = SystemTrayMenu::new()
.add_item(show)
.add_item(quit);
let menu = MenuBuilder::new(app)
.items(&[&show_hide, &exit])
.build()?;
SystemTray::new().with_menu(menu)
let tray = app.tray_by_id("main").unwrap();
tray.set_menu(Some(menu))?;
tray.on_menu_event(handle_event);
Ok(())
}
pub fn handle_event(app_handle: &AppHandle, event: SystemTrayEvent) {
match event {
SystemTrayEvent::MenuItemClick{ id, .. } => {
match id.as_str() {
"exit" => app_handle.exit(0),
"show_hide" => {
let _ = app::toggle_main_window(app_handle);
let new_handle = app_handle.app_handle();
rt::spawn(async move {
let state = new_handle.state::<AppState>();
state.signal_activity().await;
});
}
_ => (),
}
}
fn handle_event(app_handle: &AppHandle, event: MenuEvent) {
match event.id.0.as_str() {
"exit" => app_handle.exit(0),
"show_hide" => {
let _ = app::toggle_main_window(app_handle);
let new_handle = app_handle.clone();
rt::spawn(async move {
let state = new_handle.state::<AppState>();
state.signal_activity().await;
});
},
_ => (),
}
}

View File

@ -3,69 +3,57 @@
"build": {
"beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm run dev",
"devPath": "http://localhost:5173",
"distDir": "../dist"
"frontendDist": "../dist",
"devUrl": "http://localhost:5173"
},
"package": {
"productName": "creddy",
"version": "0.4.7"
},
"tauri": {
"allowlist": {
"app": {
"all": true,
"show": false,
"hide": false
},
"os": {"all": true},
"dialog": {"open": true}
"bundle": {
"active": true,
"category": "DeveloperTool",
"copyright": "",
"targets": "all",
"externalBin": [],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": "",
"wix": {
"fragmentPaths": [
"conf/cli.wxs"
],
"componentRefs": [
"CliBinary",
"AddToPath"
]
}
},
"bundle": {
"active": true,
"category": "DeveloperTool",
"copyright": "",
"longDescription": "",
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"linux": {
"deb": {
"depends": []
},
"externalBin": [],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"identifier": "creddy",
"longDescription": "",
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"targets": "all",
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": "",
"wix": {
"fragmentPaths": ["conf/cli.wxs"],
"componentRefs": ["CliBinary", "AddToPath"]
}
}
},
"security": {
"csp": {
"default-src": ["'self'"],
"style-src": ["'self'", "'unsafe-inline'"]
}
},
"updater": {
"active": false
},
}
},
"productName": "creddy",
"version": "0.4.8",
"identifier": "creddy",
"plugins": {},
"app": {
"windows": [
{
"fullscreen": false,
@ -77,9 +65,24 @@
"visible": false
}
],
"systemTray": {
"trayIcon": {
"id": "main",
"iconPath": "icons/icon.png",
"iconAsTemplate": true
},
"security": {
"csp": {
"style-src": [
"'self'",
"'unsafe-inline'"
],
"default-src": [
"'self'"
],
"connect-src": [
"ipc: http://ipc.localhost"
]
}
}
}
}