Hide secondary windows until the login UI is painted, open external links in the default browser, and add signed Githop auto-updates

Hop-State: A_06FPJAAEMY0VXGY9S90PE90
Hop-Proposal: R_06FPJA9RY0ZAWJ1K0K760W8
Hop-Task: T_06FPJ5688NFG8VWKSFCTZEG
Hop-Attempt: AT_06FPJ5688M08V2K0PEFVQX0
This commit is contained in:
2026-07-15 21:22:22 -07:00
committed by Hop
parent ec3c8d58b0
commit 7c185c9b36
13 changed files with 677 additions and 12 deletions
+361 -6
View File
@@ -47,6 +47,15 @@ version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
[[package]]
name = "arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "async-broadcast"
version = "0.7.2"
@@ -725,6 +734,17 @@ dependencies = [
"serde_core",
]
[[package]]
name = "derive_arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "derive_more"
version = "2.1.1"
@@ -1027,6 +1047,16 @@ dependencies = [
"rustc_version",
]
[[package]]
name = "filetime"
version = "0.2.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
dependencies = [
"cfg-if",
"libc",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
@@ -1849,6 +1879,25 @@ version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
[[package]]
name = "is-docker"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
dependencies = [
"once_cell",
]
[[package]]
name = "is-wsl"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
dependencies = [
"is-docker",
"once_cell",
]
[[package]]
name = "itoa"
version = "1.0.18"
@@ -1894,6 +1943,36 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "jni"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
dependencies = [
"cfg-if",
"combine",
"jni-macros",
"jni-sys 0.4.1",
"log",
"simd_cesu8",
"thiserror 2.0.18",
"walkdir",
"windows-link 0.2.1",
]
[[package]]
name = "jni-macros"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"simd_cesu8",
"syn 2.0.119",
]
[[package]]
name = "jni-sys"
version = "0.3.1"
@@ -2109,6 +2188,12 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minisign-verify"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
@@ -2388,6 +2473,18 @@ dependencies = [
"objc2-core-foundation",
]
[[package]]
name = "objc2-osa-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
dependencies = [
"bitflags 2.13.1",
"objc2",
"objc2-app-kit",
"objc2-foundation",
]
[[package]]
name = "objc2-quartz-core"
version = "0.3.2"
@@ -2451,6 +2548,17 @@ version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "open"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0b3d059e795d52b8a72fef45658620edd4d9c359b338564aa14391ffa511ed5"
dependencies = [
"dunce",
"is-wsl",
"libc",
]
[[package]]
name = "openssl"
version = "0.10.81"
@@ -2510,6 +2618,20 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "osakit"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
dependencies = [
"objc2",
"objc2-foundation",
"objc2-osa-kit",
"serde",
"serde_json",
"thiserror 2.0.18",
]
[[package]]
name = "pango"
version = "0.18.3"
@@ -3083,15 +3205,20 @@ dependencies = [
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-util",
"js-sys",
"log",
"percent-encoding",
"pin-project-lite",
"rustls",
"rustls-pki-types",
"rustls-platform-verifier",
"serde",
"serde_json",
"sync_wrapper",
"tokio",
"tokio-rustls",
"tokio-util",
"tower",
"tower-http",
@@ -3159,6 +3286,18 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
dependencies = [
"openssl-probe",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pki-types"
version = "1.15.0"
@@ -3169,6 +3308,33 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-platform-verifier"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
dependencies = [
"core-foundation 0.10.1",
"core-foundation-sys",
"jni 0.22.4",
"log",
"once_cell",
"rustls",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki",
"security-framework",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.103.13"
@@ -3522,6 +3688,22 @@ version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
[[package]]
name = "simd_cesu8"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
dependencies = [
"rustc_version",
"simdutf8",
]
[[package]]
name = "simdutf8"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "siphasher"
version = "1.0.3"
@@ -3683,6 +3865,8 @@ dependencies = [
"tauri",
"tauri-build",
"tauri-plugin-notification",
"tauri-plugin-opener",
"tauri-plugin-updater",
]
[[package]]
@@ -3757,7 +3941,7 @@ dependencies = [
"gdkwayland-sys",
"gdkx11-sys",
"gtk",
"jni",
"jni 0.21.1",
"libc",
"log",
"ndk",
@@ -3790,6 +3974,17 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "tar"
version = "0.4.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
dependencies = [
"filetime",
"libc",
"xattr",
]
[[package]]
name = "target-lexicon"
version = "0.12.16"
@@ -3813,7 +4008,7 @@ dependencies = [
"gtk",
"heck 0.5.0",
"http",
"jni",
"jni 0.21.1",
"libc",
"log",
"mime",
@@ -3944,6 +4139,61 @@ dependencies = [
"url",
]
[[package]]
name = "tauri-plugin-opener"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17e1bea14edce6b793a04e2417e3fd924b9bc4faae83cdee7d714156cceeed29"
dependencies = [
"dunce",
"glob",
"objc2-app-kit",
"objc2-foundation",
"open",
"schemars 0.8.22",
"serde",
"serde_json",
"tauri",
"tauri-plugin",
"thiserror 2.0.18",
"url",
"windows",
"zbus",
]
[[package]]
name = "tauri-plugin-updater"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af"
dependencies = [
"base64 0.22.1",
"dirs",
"flate2",
"futures-util",
"http",
"infer",
"log",
"minisign-verify",
"osakit",
"percent-encoding",
"reqwest 0.13.4",
"rustls",
"semver",
"serde",
"serde_json",
"tar",
"tauri",
"tauri-plugin",
"tempfile",
"thiserror 2.0.18",
"time",
"tokio",
"url",
"windows-sys 0.60.2",
"zip",
]
[[package]]
name = "tauri-runtime"
version = "2.11.3"
@@ -3954,7 +4204,7 @@ dependencies = [
"dpi",
"gtk",
"http",
"jni",
"jni 0.21.1",
"objc2",
"objc2-ui-kit",
"objc2-web-kit",
@@ -3977,7 +4227,7 @@ checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f"
dependencies = [
"gtk",
"http",
"jni",
"jni 0.21.1",
"log",
"objc2",
"objc2-app-kit",
@@ -4784,6 +5034,15 @@ dependencies = [
"system-deps",
]
[[package]]
name = "webpki-root-certs"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "webpki-roots"
version = "1.0.8"
@@ -5052,6 +5311,15 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.5",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
@@ -5085,13 +5353,30 @@ dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link 0.2.1",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
"windows_i686_gnullvm 0.53.1",
"windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.1",
]
[[package]]
name = "windows-threading"
version = "0.1.0"
@@ -5122,6 +5407,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
@@ -5134,6 +5425,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
@@ -5146,12 +5443,24 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
@@ -5164,6 +5473,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
@@ -5176,6 +5491,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
@@ -5188,6 +5509,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
@@ -5200,6 +5527,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "winnow"
version = "0.5.40"
@@ -5264,7 +5597,7 @@ dependencies = [
"gtk",
"http",
"javascriptcore-rs",
"jni",
"jni 0.21.1",
"libc",
"ndk",
"objc2",
@@ -5311,6 +5644,16 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "xattr"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
dependencies = [
"libc",
"rustix",
]
[[package]]
name = "yoke"
version = "0.8.3"
@@ -5475,6 +5818,18 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "zip"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
dependencies = [
"arbitrary",
"crc32fast",
"indexmap 2.14.0",
"memchr",
]
[[package]]
name = "zmij"
version = "1.0.23"
+2
View File
@@ -23,6 +23,8 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
reqwest = { version = "0.12", features = ["cookies", "json", "rustls-tls"] }
tauri-plugin-notification = "2"
tauri-plugin-opener = "2"
tauri-plugin-updater = "2"
[dev-dependencies]
http = "1"
+2
View File
@@ -8,6 +8,8 @@ fn main() {
"has_web_session",
"open_web_app",
"open_login_window",
"open_external_url",
"reveal_window",
]),
))
.unwrap();
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-open-external-url"
description = "Enables the open_external_url command without any pre-configured scope."
commands.allow = ["open_external_url"]
[[permission]]
identifier = "deny-open-external-url"
description = "Denies the open_external_url command without any pre-configured scope."
commands.deny = ["open_external_url"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-reveal-window"
description = "Enables the reveal_window command without any pre-configured scope."
commands.allow = ["reveal_window"]
[[permission]]
identifier = "deny-reveal-window"
description = "Denies the reveal_window command without any pre-configured scope."
commands.deny = ["reveal_window"]
+2
View File
@@ -9,4 +9,6 @@ commands.allow = [
"has_web_session",
"open_web_app",
"open_login_window",
"open_external_url",
"reveal_window",
]
+195 -5
View File
@@ -6,6 +6,8 @@ use std::sync::Mutex;
use std::time::Duration;
use tauri::webview::{Cookie, PageLoadEvent};
use tauri::{Manager, State, Webview, WebviewUrl, WebviewWindow, WebviewWindowBuilder};
use tauri_plugin_opener::OpenerExt;
use tauri_plugin_updater::UpdaterExt;
struct AppState {
client: Client,
@@ -56,6 +58,85 @@ struct ErrorPayload {
error: Option<String>,
}
const UPDATER_PUBLIC_KEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEYzOEZCMTg0QzJERkUwNTcKUldSWDROL0NoTEdQODlCOW10cXAvYzNTNitFQkxUd3F2T3UybUV3REJYT1Z6OVczazFmamxhWlIK";
const LATEST_RELEASE_API: &str =
"https://githop.xyz/api/v1/repos/GnosysLabs/Synapsis-Desktop/releases/latest";
#[derive(Debug, Deserialize)]
struct ReleaseAsset {
name: String,
browser_download_url: Url,
}
#[derive(Debug, Deserialize)]
struct ReleaseInfo {
assets: Vec<ReleaseAsset>,
}
fn manifest_url(release: ReleaseInfo) -> Option<Url> {
release
.assets
.into_iter()
.find(|asset| asset.name == "latest.json")
.map(|asset| asset.browser_download_url)
}
async fn latest_manifest_url(client: &Client) -> Result<Option<Url>, String> {
let response = client
.get(LATEST_RELEASE_API)
.header("Accept", "application/json")
.send()
.await
.map_err(|error| format!("Couldnt reach the Synapsis release feed: {error}"))?;
if response.status() == StatusCode::NOT_FOUND {
return Ok(None);
}
let release = decode_response::<ReleaseInfo>(response).await?;
Ok(manifest_url(release))
}
fn start_auto_updater(app: tauri::AppHandle, client: Client) {
if cfg!(debug_assertions) {
return;
}
tauri::async_runtime::spawn(async move {
let endpoint = match latest_manifest_url(&client).await {
Ok(Some(endpoint)) => endpoint,
Ok(None) => return,
Err(error) => {
eprintln!("Couldnt discover the latest Synapsis release: {error}");
return;
}
};
let updater = match app
.updater_builder()
.pubkey(UPDATER_PUBLIC_KEY)
.endpoints(vec![endpoint])
.and_then(|builder| builder.build())
{
Ok(updater) => updater,
Err(error) => {
eprintln!("Couldnt initialize Synapsis auto-updates: {error}");
return;
}
};
match updater.check().await {
Ok(Some(update)) => {
let version = update.version.clone();
if let Err(error) = update.download_and_install(|_, _| {}, || {}).await {
eprintln!("Couldnt install Synapsis {version}: {error}");
return;
}
app.restart();
}
Ok(None) => {}
Err(error) => eprintln!("Couldnt check for a Synapsis update: {error}"),
}
});
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct RegistrationRequest<'a> {
@@ -304,7 +385,11 @@ fn remote_node_capability(origin: &str, window_label: &str, sequence: u64) -> St
"remote": {
"urls": [format!("{origin}/*")]
},
"permissions": ["notification:default", "allow-open-login-window"]
"permissions": [
"notification:default",
"allow-open-login-window",
"allow-open-external-url"
]
})
.to_string()
}
@@ -377,6 +462,48 @@ async fn install_new_window_button(client: Client, webview: Webview, page_url: U
let _ = webview.eval(new_window_button_script(accent_color.as_deref()));
}
fn external_link_script() -> &'static str {
r#"
(() => {
if (window.__SYNAPSIS_EXTERNAL_LINKS_INSTALLED__) return;
window.__SYNAPSIS_EXTERNAL_LINKS_INSTALLED__ = true;
document.addEventListener("click", (event) => {
if (event.defaultPrevented || event.button !== 0) return;
const link = event.target instanceof Element ? event.target.closest("a[href]") : null;
if (!link || link.hasAttribute("download")) return;
let destination;
try {
destination = new URL(link.href, window.location.href);
} catch {
return;
}
if (!/^https?:$/.test(destination.protocol) || destination.origin === window.location.origin) {
return;
}
event.preventDefault();
event.stopImmediatePropagation();
window.__TAURI_INTERNALS__.invoke("open_external_url", { url: destination.href })
.catch((error) => console.error("Could not open external link", error));
}, true);
})();
"#
}
#[tauri::command]
fn open_external_url(app: tauri::AppHandle, url: String) -> Result<(), String> {
let destination = Url::parse(&url).map_err(|_| "This link is not a valid URL.".to_string())?;
if !matches!(destination.scheme(), "http" | "https") {
return Err("Only web links can be opened externally.".into());
}
app.opener()
.open_url(destination.as_str(), None::<&str>)
.map_err(|error| format!("Couldnt open this link in the default browser: {error}"))
}
#[tauri::command]
async fn open_login_window(
app: tauri::AppHandle,
@@ -395,6 +522,7 @@ async fn open_login_window(
.inner_size(1280.0, 820.0)
.min_inner_size(720.0, 560.0)
.center()
.visible(false)
.hidden_title(true)
.title_bar_style(tauri::TitleBarStyle::Visible)
.theme(Some(tauri::Theme::Dark))
@@ -404,6 +532,19 @@ async fn open_login_window(
.map_err(|error| format!("Couldnt open another Synapsis window: {error}"))
}
#[tauri::command]
fn reveal_window(webview: WebviewWindow) -> Result<(), String> {
if !webview.label().starts_with("node-") {
return Ok(());
}
webview
.show()
.map_err(|error| format!("Couldnt reveal the Synapsis window: {error}"))?;
webview
.set_focus()
.map_err(|error| format!("Couldnt focus the Synapsis window: {error}"))
}
#[tauri::command]
async fn logout(state: State<'_, AppState>, node_url: String) -> Result<(), String> {
let base = normalize_node(&node_url)?;
@@ -432,19 +573,34 @@ pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_opener::init())
.plugin(
tauri_plugin_updater::Builder::new()
.pubkey(UPDATER_PUBLIC_KEY)
.build(),
)
.manage(AppState {
client,
notification_capability_sequence: AtomicU64::new(0),
window_sequence: AtomicU64::new(1),
entry_urls: Mutex::new(HashMap::new()),
})
.setup(|app| {
let client = app.state::<AppState>().client.clone();
start_auto_updater(app.handle().clone(), client);
Ok(())
})
.on_page_load(|webview, payload| {
if payload.event() != PageLoadEvent::Finished
|| !matches!(payload.url().scheme(), "http" | "https")
{
if payload.event() != PageLoadEvent::Finished {
return;
}
if !matches!(payload.url().scheme(), "http" | "https") {
return;
}
let _ = webview.eval(external_link_script());
let Ok(cookies) = webview.cookies_for_url(payload.url().clone()) else {
return;
};
@@ -493,7 +649,9 @@ pub fn run() {
logout,
has_web_session,
open_web_app,
open_login_window
open_login_window,
open_external_url,
reveal_window
])
.run(tauri::generate_context!())
.expect("error while running Synapsis");
@@ -559,6 +717,7 @@ mod tests {
assert_eq!(value["remote"]["urls"][0], "https://example.com/*");
assert_eq!(value["permissions"][0], "notification:default");
assert_eq!(value["permissions"][1], "allow-open-login-window");
assert_eq!(value["permissions"][2], "allow-open-external-url");
}
#[test]
@@ -571,6 +730,15 @@ mod tests {
assert!(script.contains("open_login_window"));
}
#[test]
fn intercepts_external_links_without_hijacking_same_origin_navigation() {
let script = external_link_script();
assert!(script.contains("destination.origin === window.location.origin"));
assert!(script.contains("open_external_url"));
assert!(script.contains("event.preventDefault()"));
}
#[test]
fn returns_to_entry_only_for_logged_out_explore() {
let explore = Url::parse("https://example.com/explore").unwrap();
@@ -581,4 +749,26 @@ mod tests {
assert!(!is_logged_out_explore(&timeline, &[]));
assert!(!is_logged_out_explore(&explore, &[active_cookie]));
}
#[test]
fn selects_latest_json_from_githop_release_assets() {
let release = ReleaseInfo {
assets: vec![
ReleaseAsset {
name: "Synapsis.app.tar.gz".into(),
browser_download_url: Url::parse("https://example.com/Synapsis.app.tar.gz")
.unwrap(),
},
ReleaseAsset {
name: "latest.json".into(),
browser_download_url: Url::parse("https://example.com/latest.json").unwrap(),
},
],
};
assert_eq!(
manifest_url(release).unwrap().as_str(),
"https://example.com/latest.json"
);
}
}
+6
View File
@@ -38,5 +38,11 @@
"icons/icon.icns",
"icons/icon.ico"
]
},
"plugins": {
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEYzOEZCMTg0QzJERkUwNTcKUldSWDROL0NoTEdQODlCOW10cXAvYzNTNitFQkxUd3F2T3UybUV3REJYT1Z6OVczazFmamxhWlIK",
"endpoints": []
}
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"bundle": {
"createUpdaterArtifacts": true
}
}