diff --git a/CHANGELOG.fr.md b/CHANGELOG.fr.md index f29076a..b0df60d 100644 --- a/CHANGELOG.fr.md +++ b/CHANGELOG.fr.md @@ -2,6 +2,12 @@ ## [Non publié] +## [0.7.3] - 2026-04-13 + +### Corrigé +- Connexion Compte Maximus : le callback deep-link utilise maintenant l'API canonique Tauri v2 `on_open_url`, donc le code d'autorisation parvient bien à l'app en cours d'exécution au lieu de laisser l'interface bloquée en « chargement » (#51, #65) +- Les callbacks OAuth contenant un paramètre `error` remontent maintenant l'erreur à l'interface au lieu d'être ignorés silencieusement (#51) + ## [0.7.2] - 2026-04-13 ### Modifié diff --git a/CHANGELOG.md b/CHANGELOG.md index 19e94ca..02a946d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## [Unreleased] +## [0.7.3] - 2026-04-13 + +### Fixed +- Maximus Account sign-in: the deep-link callback now uses the canonical Tauri v2 `on_open_url` API, so the auth code is properly received by the running app instead of leaving the UI stuck in "loading" (#51, #65) +- OAuth callbacks containing an `error` parameter now surface the error to the UI instead of being silently ignored (#51) + ## [0.7.2] - 2026-04-13 ### Changed diff --git a/package.json b/package.json index f607f84..b109012 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "simpl_result_scaffold", "private": true, - "version": "0.7.2", + "version": "0.7.3", "license": "GPL-3.0-only", "type": "module", "scripts": { diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 518f14c..c8b4e79 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4280,7 +4280,7 @@ checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "simpl-result" -version = "0.7.2" +version = "0.7.3" dependencies = [ "aes-gcm", "argon2", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index f14e219..885eb7e 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "simpl-result" -version = "0.7.2" +version = "0.7.3" description = "Personal finance management app" license = "GPL-3.0-only" authors = ["you"] diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2d7846f..b0bad42 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -2,7 +2,8 @@ mod commands; mod database; use std::sync::Mutex; -use tauri::{Emitter, Listener, Manager}; +use tauri::{Emitter, Manager}; +use tauri_plugin_deep_link::DeepLinkExt; use tauri_plugin_sql::{Migration, MigrationKind}; #[cfg_attr(mobile, tauri::mobile_entry_point)] @@ -103,26 +104,40 @@ pub fn run() { #[cfg(desktop)] app.handle().plugin(tauri_plugin_updater::Builder::new().build())?; - // Listen for deep-link events (simpl-resultat://auth/callback?code=...) + // Register the custom scheme at runtime on Linux (the .desktop file + // handles it in prod, but register_all is a no-op there and required + // for AppImage/dev builds). + #[cfg(any(target_os = "linux", all(debug_assertions, windows)))] + { + let _ = app.deep_link().register_all(); + } + + // Canonical Tauri v2 pattern: on_open_url fires for both initial-launch + // URLs and subsequent URLs forwarded by tauri-plugin-single-instance + // (with the `deep-link` feature). let handle = app.handle().clone(); - app.listen("deep-link://new-url", move |event| { - let payload = event.payload(); - // payload is a JSON-serialized array of URL strings - if let Ok(urls) = serde_json::from_str::>(payload) { - for url in urls { - if let Some(code) = extract_auth_code(&url) { - let h = handle.clone(); - tauri::async_runtime::spawn(async move { - match commands::handle_auth_callback(h.clone(), code).await { - Ok(account) => { - let _ = h.emit("auth-callback-success", &account); - } - Err(err) => { - let _ = h.emit("auth-callback-error", &err); - } + app.deep_link().on_open_url(move |event| { + for url in event.urls() { + let url_str = url.as_str(); + let h = handle.clone(); + if let Some(code) = extract_auth_code(url_str) { + tauri::async_runtime::spawn(async move { + match commands::handle_auth_callback(h.clone(), code).await { + Ok(account) => { + let _ = h.emit("auth-callback-success", &account); } - }); - } + Err(err) => { + let _ = h.emit("auth-callback-error", &err); + } + } + }); + } else { + // No `code` param — likely an OAuth error response. Surface + // it to the frontend instead of leaving the UI stuck in + // "loading" forever. + let err_msg = extract_auth_error(url_str) + .unwrap_or_else(|| "OAuth callback did not include a code".to_string()); + let _ = h.emit("auth-callback-error", &err_msg); } } }); @@ -177,6 +192,20 @@ pub fn run() { /// Extract the `code` query parameter from a deep-link callback URL. /// e.g. "simpl-resultat://auth/callback?code=abc123&state=xyz" → Some("abc123") fn extract_auth_code(url: &str) -> Option { + extract_query_param(url, "code") +} + +/// Extract an OAuth error description from a callback URL. Returns a +/// formatted string combining `error` and `error_description` when present. +fn extract_auth_error(url: &str) -> Option { + let error = extract_query_param(url, "error")?; + match extract_query_param(url, "error_description") { + Some(desc) => Some(format!("{}: {}", error, desc)), + None => Some(error), + } +} + +fn extract_query_param(url: &str, key: &str) -> Option { let url = url.trim(); if !url.starts_with("simpl-resultat://auth/callback") { return None; @@ -184,7 +213,7 @@ fn extract_auth_code(url: &str) -> Option { let query = url.split('?').nth(1)?; for pair in query.split('&') { let mut kv = pair.splitn(2, '='); - if kv.next()? == "code" { + if kv.next()? == key { return kv.next().map(|v| { urlencoding::decode(v).map(|s| s.into_owned()).unwrap_or_else(|_| v.to_string()) }); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 534c4f4..4238cf1 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Simpl Resultat", - "version": "0.7.2", + "version": "0.7.3", "identifier": "com.simpl.resultat", "build": { "beforeDevCommand": "npm run dev",