Skip to content
121 linesCodeBlameRaw
1//! What the site may ask of the app: the commands behind `window.g1tDesktop`
2//! (bridge.js), and the launcher's own.
3//!
4//! Every command checks who is asking. The site's commands answer only a
5//! page of the configured server; the launcher's only the page bundled
6//! with the app. A page from anywhere else gets nothing, whatever it
7//! loads.
8
9use serde::Serialize;
10use tauri::{AppHandle, Webview};
11use tauri_plugin_notification::NotificationExt;
12use tauri_plugin_opener::OpenerExt;
13use url::Url;
14
15use crate::{badge, config, updates};
16
17const SCRIPT: &str = include_str!("bridge.js");
18
19/// The bridge, run at the start of every page the window loads.
20pub fn script() -> String {
21 SCRIPT.replace("__VERSION__", env!("CARGO_PKG_VERSION")).replace("__PLATFORM__", std::env::consts::OS)
22}
23
24/// What the site and the launcher learn about the app.
25#[derive(Serialize)]
26pub struct Info {
27 pub version: String,
28 pub platform: String,
29 pub server: String,
30 /// The version downloaded and waiting for a restart, if one is.
31 pub update_ready: Option<String>,
32}
33
34fn caller(webview: &Webview) -> Result<Url, String> {
35 webview.url().map_err(|error| error.to_string())
36}
37
38/// The page asking is one of the server's.
39fn from_site(app: &AppHandle, webview: &Webview) -> Result<(), String> {
40 let url = caller(webview)?;
41 if config::is_server(app, &url) { Ok(()) } else { Err("only g1t's own pages may ask that".into()) }
42}
43
44/// The page asking is the launcher.
45fn from_launcher(webview: &Webview) -> Result<(), String> {
46 let url = caller(webview)?;
47 if config::is_launcher(&url) { Ok(()) } else { Err("only the launcher may ask that".into()) }
48}
49
50fn from_either(app: &AppHandle, webview: &Webview) -> Result<(), String> {
51 from_site(app, webview).or_else(|_| from_launcher(webview))
52}
53
54#[tauri::command]
55pub fn desktop_info(app: AppHandle, webview: Webview) -> Result<Info, String> {
56 from_either(&app, &webview)?;
57 Ok(Info {
58 version: env!("CARGO_PKG_VERSION").into(),
59 platform: std::env::consts::OS.into(),
60 server: config::server(&app).to_string(),
61 update_ready: updates::ready(&app),
62 })
63}
64
65/// A notification from the system: the site asks for one when a message
66/// is for the person and the window is behind another.
67#[tauri::command]
68pub fn notify(app: AppHandle, webview: Webview, title: String, body: String) -> Result<(), String> {
69 from_site(&app, &webview)?;
70 app.notification().builder().title(title).body(body).show().map_err(|error| error.to_string())
71}
72
73/// What needs the person, on the icon and the tray; 0 clears it.
74#[tauri::command]
75pub fn set_badge(app: AppHandle, webview: Webview, count: i64) -> Result<(), String> {
76 from_site(&app, &webview)?;
77 badge::set(&app, count.max(0));
78 Ok(())
79}
80
81/// A link to somewhere else, opened in the browser the system uses.
82#[tauri::command]
83pub fn open_url(app: AppHandle, webview: Webview, url: String) -> Result<(), String> {
84 from_either(&app, &webview)?;
85 let parsed = Url::parse(&url).map_err(|error| error.to_string())?;
86 if !matches!(parsed.scheme(), "http" | "https" | "mailto") {
87 return Err(format!("{} links stay in the app", parsed.scheme()));
88 }
89 app.opener().open_url(parsed.as_str(), None::<&str>).map_err(|error| error.to_string())
90}
91
92/// Whether a server answers at all: the launcher asks before the window
93/// goes there, so a machine with no network sees a page of ours, not the
94/// web view's own error.
95#[tauri::command]
96pub async fn reachable(app: AppHandle, webview: Webview, url: Option<String>) -> Result<bool, String> {
97 from_launcher(&webview)?;
98 let target = match url {
99 Some(text) => config::parse(&text).ok_or("That is not a web address.")?,
100 None => config::server(&app),
101 };
102 let client = reqwest::Client::builder()
103 .timeout(std::time::Duration::from_secs(6))
104 .build()
105 .map_err(|error| error.to_string())?;
106 Ok(client.head(target).send().await.is_ok())
107}
108
109/// The launcher chose a server: it is kept, and the window goes there next.
110#[tauri::command]
111pub fn set_server(app: AppHandle, webview: Webview, url: String) -> Result<String, String> {
112 from_launcher(&webview)?;
113 config::set_server(&app, &url).map(|server| server.to_string())
114}
115
116/// Looks for an update now: `current`, `ready:<version>`, or an error.
117#[tauri::command]
118pub async fn check_for_updates(app: AppHandle, webview: Webview) -> Result<String, String> {
119 from_either(&app, &webview)?;
120 updates::check(&app, true).await
121}