Enable reliable native window dragging and hand authenticated sessions into the selected node's complete web experience
Hop-State: A_06FPG2TG1QGADXPK4Z3JRK8 Hop-Proposal: R_06FPG2STVE3YFFK1MMCXN7R Hop-Task: T_06FPG0V9Q74727J7JHZVD20 Hop-Attempt: AT_06FPG0V9Q4RS0GRDVR1GXVG
This commit is contained in:
Generated
+1
@@ -3338,6 +3338,7 @@ dependencies = [
|
||||
name = "synapsis-desktop"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"http",
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -22,3 +22,6 @@ tauri = { version = "2", features = [] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
reqwest = { version = "0.12", features = ["cookies", "json", "rustls-tls"] }
|
||||
|
||||
[dev-dependencies]
|
||||
http = "1"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default"
|
||||
"core:default",
|
||||
"core:window:allow-start-dragging"
|
||||
]
|
||||
}
|
||||
|
||||
+100
-7
@@ -1,7 +1,8 @@
|
||||
use reqwest::{Client, Method, StatusCode, Url};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use tauri::State;
|
||||
use tauri::webview::Cookie;
|
||||
use tauri::{State, WebviewWindow};
|
||||
|
||||
struct AppState {
|
||||
client: Client,
|
||||
@@ -116,8 +117,35 @@ async fn get<T: DeserializeOwned>(client: &Client, base: &Url, path: &str) -> Re
|
||||
decode_response(response).await
|
||||
}
|
||||
|
||||
async fn post<T: DeserializeOwned, B: Serialize>(
|
||||
fn cookies_for_webview(response: &reqwest::Response, base: &Url) -> Vec<Cookie<'static>> {
|
||||
let Some(host) = base.host_str() else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
response
|
||||
.headers()
|
||||
.get_all(reqwest::header::SET_COOKIE)
|
||||
.iter()
|
||||
.filter_map(|value| value.to_str().ok())
|
||||
.filter_map(|value| Cookie::parse(value.to_owned()).ok())
|
||||
.map(|mut cookie| {
|
||||
if cookie.domain().is_none() {
|
||||
cookie.set_domain(host.to_owned());
|
||||
}
|
||||
if cookie.path().is_none() {
|
||||
cookie.set_path("/");
|
||||
}
|
||||
if cookie.secure().is_none() {
|
||||
cookie.set_secure(base.scheme() == "https");
|
||||
}
|
||||
cookie.into_owned()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn post_and_sync_cookies<T: DeserializeOwned, B: Serialize>(
|
||||
client: &Client,
|
||||
webview: &WebviewWindow,
|
||||
base: &Url,
|
||||
path: &str,
|
||||
body: &B,
|
||||
@@ -129,7 +157,15 @@ async fn post<T: DeserializeOwned, B: Serialize>(
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| format!("Couldn’t reach this node: {error}"))?;
|
||||
decode_response(response).await
|
||||
let cookies = cookies_for_webview(&response, base);
|
||||
let payload = decode_response(response).await?;
|
||||
|
||||
for cookie in cookies {
|
||||
webview
|
||||
.set_cookie(cookie)
|
||||
.map_err(|error| format!("Couldn’t prepare the web session: {error}"))?;
|
||||
}
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -152,13 +188,15 @@ async fn connect(
|
||||
#[tauri::command]
|
||||
async fn login(
|
||||
state: State<'_, AppState>,
|
||||
webview: WebviewWindow,
|
||||
node_url: String,
|
||||
email: String,
|
||||
password: String,
|
||||
) -> Result<AuthPayload, String> {
|
||||
let base = normalize_node(&node_url)?;
|
||||
let payload = post::<SessionPayload, _>(
|
||||
let payload = post_and_sync_cookies::<SessionPayload, _>(
|
||||
&state.client,
|
||||
&webview,
|
||||
&base,
|
||||
"/api/auth/login",
|
||||
&serde_json::json!({ "email": email.trim(), "password": password }),
|
||||
@@ -173,6 +211,7 @@ async fn login(
|
||||
#[tauri::command]
|
||||
async fn register(
|
||||
state: State<'_, AppState>,
|
||||
webview: WebviewWindow,
|
||||
node_url: String,
|
||||
handle: String,
|
||||
display_name: String,
|
||||
@@ -186,14 +225,39 @@ async fn register(
|
||||
email: email.trim(),
|
||||
password: &password,
|
||||
};
|
||||
let payload =
|
||||
post::<SessionPayload, _>(&state.client, &base, "/api/auth/register", &body).await?;
|
||||
let payload = post_and_sync_cookies::<SessionPayload, _>(
|
||||
&state.client,
|
||||
&webview,
|
||||
&base,
|
||||
"/api/auth/register",
|
||||
&body,
|
||||
)
|
||||
.await?;
|
||||
payload
|
||||
.user
|
||||
.map(|user| AuthPayload { user })
|
||||
.ok_or_else(|| "The node returned an unexpected response.".into())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn has_web_session(webview: WebviewWindow, node_url: String) -> Result<bool, String> {
|
||||
let base = normalize_node(&node_url)?;
|
||||
let cookies = webview
|
||||
.cookies_for_url(base)
|
||||
.map_err(|error| format!("Couldn’t restore the web session: {error}"))?;
|
||||
Ok(cookies
|
||||
.iter()
|
||||
.any(|cookie| cookie.name() == "synapsis_sessions" && !cookie.value().trim().is_empty()))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn open_web_app(webview: WebviewWindow, node_url: String) -> Result<(), String> {
|
||||
let base = normalize_node(&node_url)?;
|
||||
webview
|
||||
.navigate(base)
|
||||
.map_err(|error| format!("Couldn’t open the Synapsis web app: {error}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn logout(state: State<'_, AppState>, node_url: String) -> Result<(), String> {
|
||||
let base = normalize_node(&node_url)?;
|
||||
@@ -222,7 +286,14 @@ pub fn run() {
|
||||
|
||||
tauri::Builder::default()
|
||||
.manage(AppState { client })
|
||||
.invoke_handler(tauri::generate_handler![connect, login, register, logout])
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
connect,
|
||||
login,
|
||||
register,
|
||||
logout,
|
||||
has_web_session,
|
||||
open_web_app
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running Synapsis");
|
||||
}
|
||||
@@ -253,4 +324,26 @@ mod tests {
|
||||
fn rejects_non_web_schemes() {
|
||||
assert!(normalize_node("file:///tmp/node").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepares_host_only_session_cookies_for_the_webview() {
|
||||
let base = normalize_node("https://example.com").unwrap();
|
||||
let response = reqwest::Response::from(
|
||||
http::Response::builder()
|
||||
.header(
|
||||
reqwest::header::SET_COOKIE,
|
||||
"synapsis_session=abc; Path=/; HttpOnly; Secure; SameSite=Lax",
|
||||
)
|
||||
.body("")
|
||||
.unwrap(),
|
||||
);
|
||||
let cookies = cookies_for_webview(&response, &base);
|
||||
|
||||
assert_eq!(cookies.len(), 1);
|
||||
assert_eq!(cookies[0].name(), "synapsis_session");
|
||||
assert_eq!(cookies[0].domain(), Some("example.com"));
|
||||
assert_eq!(cookies[0].path(), Some("/"));
|
||||
assert_eq!(cookies[0].http_only(), Some(true));
|
||||
assert_eq!(cookies[0].secure(), Some(true));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user