fix: use on_open_url for OAuth deep-link callback
All checks were successful
Release / build-and-release (push) Successful in 27m50s
All checks were successful
Release / build-and-release (push) Successful in 27m50s
The listener `app.listen("deep-link://new-url", ...)` did not reliably
fire when tauri-plugin-single-instance (deep-link feature) forwarded a
simpl-resultat://auth/callback URL to the running instance. The user
saw the browser complete the OAuth flow, the app regain focus, and
then sit in "loading" forever because the listener never received the
URL.
Switch to the canonical Tauri v2 API — `app.deep_link().on_open_url()`
via DeepLinkExt — which is directly coupled to the deep-link plugin
and catches URLs from both initial launch and single-instance forwards.
Also surface OAuth error responses: if the callback URL contains an
`error` parameter instead of a `code`, emit `auth-callback-error` so
the UI can show the error instead of staying stuck in "loading".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f14ac3c6f8
commit
f5d74b4664
7 changed files with 65 additions and 24 deletions
|
|
@ -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é
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
|
|
@ -4280,7 +4280,7 @@ checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
|||
|
||||
[[package]]
|
||||
name = "simpl-result"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"argon2",
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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::<Vec<String>>(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<String> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
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<String> {
|
|||
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())
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue