Skip to main content

fractal/login/
local_server.rs

1use gettextrs::gettext;
2use gtk::gio;
3use matrix_sdk::utils::local_server::{
4    LocalServerBuilder, LocalServerRedirectHandle, LocalServerResponse,
5};
6use tracing::error;
7use url::Url;
8
9use crate::{APP_NAME, spawn_tokio};
10
11/// The HTML template for the landing page.
12const LOCAL_SERVER_LANDING_PAGE_TEMPLATE: &str = include_str!("local_server_landing_page.html");
13
14/// Spawn a local server for listening to redirects.
15pub(super) async fn spawn_local_server() -> Result<(Url, LocalServerRedirectHandle), ()> {
16    spawn_tokio!(async move {
17        LocalServerBuilder::new()
18            .response(local_server_landing_page())
19            .spawn()
20            .await
21    })
22    .await
23    .expect("task was not aborted")
24    .map_err(|error| {
25        error!("Could not spawn local server: {error}");
26    })
27}
28
29/// The landing page, after the user performed the authentication and is
30/// redirected to the local server.
31fn local_server_landing_page() -> LocalServerResponse {
32    let mut html = LOCAL_SERVER_LANDING_PAGE_TEMPLATE.to_owned();
33
34    replace_html_variable(&mut html, "app_name", APP_NAME);
35    replace_html_variable(&mut html, "title", &gettext("Authorization Completed"));
36    replace_html_variable(
37        &mut html,
38        "message",
39        &gettext(
40            "The authorization step is complete. You can close this page and go back to Fractal.",
41        ),
42    );
43    replace_html_variable(&mut html, "icon", &svg_icon());
44
45    LocalServerResponse::Html(html)
46}
47
48/// Replace the variable with the given name by the given value in the given
49/// HTML.
50///
51/// The syntax for a variable is `@name@`. This is the same format as meson's
52/// `configure_file` function.
53///
54/// Logs an error if the variable is not found.
55fn replace_html_variable(html: &mut String, name: &str, value: &str) {
56    let pattern = format!("@{name}@");
57
58    // This is a programmer error.
59    assert!(
60        html.contains(&pattern),
61        "Variable `{pattern}` should be present in HTML template"
62    );
63
64    *html = html.replace(&pattern, value);
65}
66
67/// Get the application SVG icon, ready to be embedded in HTML code.
68///
69/// Panics if the icon is not found or is invalid in some way.
70fn svg_icon() -> String {
71    // Load the icon from the application resources.
72    let bytes = gio::resources_lookup_data(
73        "/org/gnome/Fractal/icons/scalable/apps/org.gnome.Fractal.svg",
74        gio::ResourceLookupFlags::NONE,
75    )
76    .expect("Application SVG icon should be present in GResources");
77
78    // Convert the bytes to a string, since it should be SVG.
79    let icon = String::from_utf8(bytes.to_vec())
80        .expect("Application SVG icon content should be a UTF-8 string");
81
82    // Remove the XML prologue, to inline the SVG directly into the HTML.
83    icon.trim()
84        .strip_prefix(r#"<?xml version="1.0" encoding="UTF-8"?>"#)
85        .expect("Application SVG icon should start with an XML prologue")
86        .to_owned()
87}
88
89#[cfg(test)]
90mod tests {
91    use assert_matches2::assert_matches;
92    use gtk::gio;
93    use matrix_sdk::utils::local_server::LocalServerResponse;
94
95    use super::local_server_landing_page;
96    use crate::config::tests::BUILD_DIR;
97
98    #[gtk::test]
99    fn generate_local_server_landing_page() {
100        let resources_file = format!("{BUILD_DIR}/data/resources/resources.gresource");
101        let res = gio::Resource::load(resources_file).expect("Could not load gresource file");
102        gio::resources_register(&res);
103
104        // Check that the variables were all replaced.
105        assert_matches!(local_server_landing_page(), LocalServerResponse::Html(html));
106        assert!(!html.is_empty());
107        assert!(!html.contains("@app_name@"));
108        assert!(!html.contains("@title@"));
109        assert!(!html.contains("@message@"));
110        assert!(!html.contains("@icon@"));
111    }
112}