Return to the native entry screen after the final website account logs out

Hop-State: A_06FPG94PH3WVGZM2V10TNC0
Hop-Proposal: R_06FPG943E08MTNDQMWHA0E8
Hop-Task: T_06FPG80D8VVAEPFXY3F8WGR
Hop-Attempt: AT_06FPG80D8VE7RMJ24BEY3SR
This commit is contained in:
2026-07-15 16:37:36 -07:00
committed by Hop
parent 4a408edce8
commit 5ab1ac1a28
2 changed files with 89 additions and 3 deletions
+80 -3
View File
@@ -1,13 +1,15 @@
use reqwest::{Client, Method, StatusCode, Url}; use reqwest::{Client, Method, StatusCode, Url};
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;
use std::time::Duration; use std::time::Duration;
use tauri::webview::Cookie; use tauri::webview::{Cookie, PageLoadEvent};
use tauri::{Manager, State, WebviewWindow}; use tauri::{Manager, State, WebviewWindow};
struct AppState { struct AppState {
client: Client, client: Client,
notification_capability_sequence: AtomicU64, notification_capability_sequence: AtomicU64,
entry_url: Mutex<Option<Url>>,
} }
#[derive(Debug, Default, Deserialize, Serialize)] #[derive(Debug, Default, Deserialize, Serialize)]
@@ -247,9 +249,17 @@ async fn has_web_session(webview: WebviewWindow, node_url: String) -> Result<boo
let cookies = webview let cookies = webview
.cookies_for_url(base) .cookies_for_url(base)
.map_err(|error| format!("Couldnt restore the web session: {error}"))?; .map_err(|error| format!("Couldnt restore the web session: {error}"))?;
Ok(cookies Ok(has_synapsis_session(&cookies))
}
fn has_synapsis_session(cookies: &[Cookie<'_>]) -> bool {
cookies
.iter() .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] #[tauri::command]
@@ -259,6 +269,19 @@ async fn open_web_app(
node_url: String, node_url: String,
) -> Result<(), String> { ) -> Result<(), String> {
let base = normalize_node(&node_url)?; let base = normalize_node(&node_url)?;
{
let mut entry_url = state
.entry_url
.lock()
.map_err(|_| "Couldnt remember the app entry screen.".to_string())?;
if entry_url.is_none() {
*entry_url = Some(
webview
.url()
.map_err(|error| format!("Couldnt read the app entry address: {error}"))?,
);
}
}
let origin = base.origin().ascii_serialization(); let origin = base.origin().ascii_serialization();
let capability_id = state let capability_id = state
.notification_capability_sequence .notification_capability_sequence
@@ -317,6 +340,49 @@ pub fn run() {
.manage(AppState { .manage(AppState {
client, client,
notification_capability_sequence: AtomicU64::new(0), 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::<AppState>();
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![ .invoke_handler(tauri::generate_handler![
connect, connect,
@@ -389,4 +455,15 @@ mod tests {
assert_eq!(value["remote"]["urls"][0], "https://example.com/*"); assert_eq!(value["remote"]["urls"][0], "https://example.com/*");
assert_eq!(value["permissions"][0], "notification:default"); 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]));
}
} }
+9
View File
@@ -245,6 +245,15 @@ function App() {
const [error, setError] = useState(""); const [error, setError] = useState("");
useEffect(() => { 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); const savedNode = localStorage.getItem(SAVED_NODE_KEY);
if (!savedNode || !isTauri()) { if (!savedNode || !isTauri()) {
setBootstrapping(false); setBootstrapping(false);