| 1 | //! Where the window may go: the launcher, the server's pages, and the |
| 2 | //! pages that sign someone in there. Any other address opens in the |
| 3 | //! browser the system uses, and the window stays where it is. So do the |
| 4 | //! server's pages for reading about g1t rather than using it (`OUTSIDE`): |
| 5 | //! the window is for working in, the browser for browsing. bridge.js |
| 6 | //! sends the same pages out when a link inside the site goes to one. |
| 7 | |
| 8 | use tauri::AppHandle; |
| 9 | use tauri_plugin_opener::OpenerExt; |
| 10 | use url::Url; |
| 11 | |
| 12 | use crate::config; |
| 13 | |
| 14 | /// Signing in with GitHub goes there and comes back. |
| 15 | const SIGN_IN_HOSTS: &[&str] = &["github.com", "www.github.com"]; |
| 16 | |
| 17 | /// The server's pages that open in the browser: pricing, policies, |
| 18 | /// security, support, the desktop app's own downloads, and status. An |
| 19 | /// entry ending in `/` is every page under it. |
| 20 | pub const OUTSIDE: &[&str] = &["/pricing", "/policies", "/policies/", "/security", "/support", "/download", "/downloads/", "/status"]; |
| 21 | |
| 22 | /// Whether a path on the server is one for the browser. |
| 23 | pub fn outside(path: &str) -> bool { |
| 24 | let path = if path.len() > 1 { path.trim_end_matches('/') } else { path }; |
| 25 | OUTSIDE.iter().any(|page| if page.ends_with('/') { path.starts_with(page) } else { path == *page }) |
| 26 | } |
| 27 | |
| 28 | /// Whether a page is one of signing in with GitHub's. |
| 29 | pub fn signing_in(url: &Url) -> bool { |
| 30 | url.scheme() == "https" && url.host_str().is_some_and(|host| SIGN_IN_HOSTS.contains(&host)) |
| 31 | } |
| 32 | |
| 33 | /// Whether the window may load `url`. |
| 34 | pub fn allowed(app: &AppHandle, url: &Url) -> bool { |
| 35 | match url.scheme() { |
| 36 | "about" | "blob" | "data" => return true, |
| 37 | "http" | "https" | "tauri" => {} |
| 38 | _ => return false, |
| 39 | } |
| 40 | if config::is_launcher(url) || (config::is_server(app, url) && !outside(url.path())) { |
| 41 | return true; |
| 42 | } |
| 43 | if signing_in(url) { |
| 44 | return true; |
| 45 | } |
| 46 | let _ = app.opener().open_url(url.as_str(), None::<&str>); |
| 47 | false |
| 48 | } |
| 49 | |
| 50 | #[cfg(test)] |
| 51 | mod tests { |
| 52 | use super::outside; |
| 53 | |
| 54 | #[test] |
| 55 | fn reading_about_g1t_is_for_the_browser() { |
| 56 | assert!(outside("/pricing")); |
| 57 | assert!(outside("/pricing/")); |
| 58 | assert!(outside("/policies/terms")); |
| 59 | assert!(outside("/downloads/desktop/latest.json")); |
| 60 | assert!(!outside("/")); |
| 61 | assert!(!outside("/login")); |
| 62 | assert!(!outside("/acme/-/chat")); |
| 63 | assert!(!outside("/pricingx")); |
| 64 | assert!(!outside("/acme/security")); |
| 65 | } |
| 66 | } |