diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c5bf70a..33960ea 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,13 +1,15 @@ use reqwest::{Client, Method, StatusCode, Url}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Mutex; use std::time::Duration; -use tauri::webview::Cookie; +use tauri::webview::{Cookie, PageLoadEvent}; use tauri::{Manager, State, WebviewWindow}; struct AppState { client: Client, notification_capability_sequence: AtomicU64, + entry_url: Mutex>, } #[derive(Debug, Default, Deserialize, Serialize)] @@ -247,9 +249,17 @@ async fn has_web_session(webview: WebviewWindow, node_url: String) -> Result]) -> bool { + cookies .iter() - .any(|cookie| cookie.name() == "synapsis_sessions" && !cookie.value().trim().is_empty())) + .any(|cookie| cookie.name() == "synapsis_sessions" && !cookie.value().trim().is_empty()) +} + +fn is_logged_out_explore(url: &Url, cookies: &[Cookie<'_>]) -> bool { + url.path().trim_end_matches('/') == "/explore" && !has_synapsis_session(cookies) } #[tauri::command] @@ -259,6 +269,19 @@ async fn open_web_app( node_url: String, ) -> Result<(), String> { let base = normalize_node(&node_url)?; + { + let mut entry_url = state + .entry_url + .lock() + .map_err(|_| "Couldn’t remember the app entry screen.".to_string())?; + if entry_url.is_none() { + *entry_url = Some( + webview + .url() + .map_err(|error| format!("Couldn’t read the app entry address: {error}"))?, + ); + } + } let origin = base.origin().ascii_serialization(); let capability_id = state .notification_capability_sequence @@ -317,6 +340,49 @@ pub fn run() { .manage(AppState { client, notification_capability_sequence: AtomicU64::new(0), + entry_url: Mutex::new(None), + }) + .on_page_load(|webview, payload| { + if payload.event() != PageLoadEvent::Finished + || !matches!(payload.url().scheme(), "http" | "https") + { + return; + } + + let Ok(cookies) = webview.cookies_for_url(payload.url().clone()) else { + return; + }; + if !is_logged_out_explore(payload.url(), &cookies) { + return; + } + + let state = webview.state::(); + let mut entry_url = { + let Ok(saved_entry_url) = state.entry_url.lock() else { + return; + }; + let Some(entry_url) = saved_entry_url.clone() else { + return; + }; + entry_url + }; + + let client = state.client.clone(); + let node_url = payload.url().clone(); + tauri::async_runtime::spawn(async move { + if let Ok(base) = normalize_node(node_url.as_str()) { + if let Ok(logout_url) = endpoint(&base, "/api/auth/logout") { + let _ = client + .post(logout_url) + .json(&serde_json::json!({})) + .send() + .await; + } + } + }); + + entry_url.set_query(Some("signedOut=1")); + let _ = webview.navigate(entry_url); }) .invoke_handler(tauri::generate_handler![ connect, @@ -389,4 +455,15 @@ mod tests { assert_eq!(value["remote"]["urls"][0], "https://example.com/*"); assert_eq!(value["permissions"][0], "notification:default"); } + + #[test] + fn returns_to_entry_only_for_logged_out_explore() { + let explore = Url::parse("https://example.com/explore").unwrap(); + let timeline = Url::parse("https://example.com/").unwrap(); + let active_cookie = Cookie::new("synapsis_sessions", "active"); + + assert!(is_logged_out_explore(&explore, &[])); + assert!(!is_logged_out_explore(&timeline, &[])); + assert!(!is_logged_out_explore(&explore, &[active_cookie])); + } } diff --git a/src/App.tsx b/src/App.tsx index a78628f..e8128a3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -245,6 +245,15 @@ function App() { const [error, setError] = useState(""); useEffect(() => { + const launchParams = new URLSearchParams(window.location.search); + if (launchParams.has("signedOut")) { + localStorage.removeItem(SAVED_NODE_KEY); + window.history.replaceState({}, "", window.location.pathname); + setScreen("node"); + setBootstrapping(false); + return; + } + const savedNode = localStorage.getItem(SAVED_NODE_KEY); if (!savedNode || !isTauri()) { setBootstrapping(false);