StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | use std::sync::OnceLock; |
| 2 | |
| 3 | use woothee::parser::{Parser, WootheeResult}; |
| 4 | |
| 5 | use crate::platform::OperatingSystem; |
| 6 | |
| 7 | static PARSED_USER_AGENT: OnceLock<Option<ParsedUserAgent>> = OnceLock::new(); |
| 8 | static PLATFORM: OnceLock<OperatingSystem> = OnceLock::new(); |
| 9 | |
| 10 | #[derive(Debug)] |
| 11 | struct ParsedUserAgent { |
| 12 | os: String, |
| 13 | /// For macOS, the version number is probably incorrect as it is currently |
| 14 | /// capped at 10.15. See: https://bugs.webkit.org/show_bug.cgi?id=216593. |
| 15 | /// It's possible to get the correct version using the Client Hints API, but |
| 16 | /// this is currently only supported by Chrome: https://developer.mozilla.org/en-US/docs/Web/API/User-Agent_Client_Hints_API. |
| 17 | os_version: String, |
| 18 | browser: String, |
| 19 | browser_version: String, |
| 20 | } |
| 21 | |
| 22 | impl ParsedUserAgent { |
| 23 | /// Converts the result we get from parsing the user agent into a struct |
| 24 | /// with owned values. |
| 25 | fn from_woothee_result(result: &WootheeResult) -> Self { |
| 26 | ParsedUserAgent { |
| 27 | os: result.os.to_string(), |
| 28 | os_version: result.os_version.to_string(), |
| 29 | browser: result.name.to_string(), |
| 30 | browser_version: result.version.to_string(), |
| 31 | } |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | fn parsed_user_agent() -> Option<&'static ParsedUserAgent> { |
| 36 | PARSED_USER_AGENT |
| 37 | .get_or_init(|| { |
| 38 | let Ok(user_agent) = gloo::utils::window().navigator().user_agent() else { |
| 39 | return None; |
| 40 | }; |
| 41 | |
| 42 | let parser = Parser::new(); |
| 43 | parser |
| 44 | .parse(user_agent.as_str()) |
| 45 | .map(|result| ParsedUserAgent::from_woothee_result(&result)) |
| 46 | }) |
| 47 | .as_ref() |
| 48 | } |
| 49 | |
| 50 | /// Returns the current operating system by reading the user agent. If the user agent was not able |
| 51 | /// to be read, [`OperatingSystem::Other`] is returned. |
| 52 | /// |
| 53 | /// # Panics |
| 54 | /// Panics if called before the app was attached to the DOM. |
| 55 | pub(super) fn current_platform() -> OperatingSystem { |
| 56 | *PLATFORM.get_or_init(|| { |
| 57 | let Some(parsed_user_agent) = parsed_user_agent() else { |
| 58 | return OperatingSystem::Other(None); |
| 59 | }; |
| 60 | |
| 61 | // Try to parse the user agent to determine the OS. _heavily_ inspired by |
| 62 | // https://github.com/mozilla-services/contile/blob/61da2719fa4586fc0b15fe7f47ebbc1586f28a47/src/web/user_agent.rs#L95-L105. |
| 63 | let os = parsed_user_agent.os.to_lowercase(); |
| 64 | match os.as_str() { |
| 65 | _ if os.starts_with("windows") => OperatingSystem::Windows, |
| 66 | "mac osx" => OperatingSystem::Mac, |
| 67 | "linux" => OperatingSystem::Linux, |
| 68 | _ => OperatingSystem::Other(Some(&parsed_user_agent.os)), |
| 69 | } |
| 70 | }) |
| 71 | } |
| 72 | |
| 73 | /// Returns the user agent provided by the browser. If the user agent was |
| 74 | /// unable to be read, returns None. |
| 75 | pub fn user_agent() -> Option<String> { |
| 76 | gloo::utils::window().navigator().user_agent().ok() |
| 77 | } |
| 78 | |
| 79 | /// Returns the version of the current operating system, parsed from the user |
| 80 | /// agent. If the user agent was not able to be read, returns None. |
| 81 | /// |
| 82 | /// Also returns None if the current operating system is macOS. The version |
| 83 | /// reported to the user agent is capped at 10.15, meaning it is probably |
| 84 | /// incorrect in most cases: https://bugs.webkit.org/show_bug.cgi?id=216593. |
| 85 | pub fn current_os_version() -> Option<&'static str> { |
| 86 | if matches!(current_platform(), OperatingSystem::Mac) { |
| 87 | return None; |
| 88 | }; |
| 89 | |
| 90 | parsed_user_agent().map(|ua| ua.os_version.as_str()) |
| 91 | } |
| 92 | |
| 93 | /// Returns the name of the browser, parsed from the user agent. If the user |
| 94 | /// agent was not able to be read, returns None. |
| 95 | pub fn current_browser() -> Option<&'static str> { |
| 96 | parsed_user_agent().map(|ua| ua.browser.as_str()) |
| 97 | } |
| 98 | |
| 99 | /// Returns the version of the current browser, parsed from the user agent. |
| 100 | /// If the user agent was not able to be read, returns None. |
| 101 | pub fn current_browser_version() -> Option<&'static str> { |
| 102 | parsed_user_agent().map(|ua| ua.browser_version.as_str()) |
| 103 | } |
| 104 |