kanidmd_core/https/
manifest.rs

1//! Builds a Progressive Web App Manifest page.
2use axum::http::header::CONTENT_TYPE;
3use axum::http::HeaderValue;
4use axum::response::{IntoResponse, Response};
5use serde::{Deserialize, Serialize};
6use serde_with::skip_serializing_none;
7
8use crate::https::extractors::DomainInfo;
9
10/// The MIME type for `.webmanifest` files.
11const MIME_TYPE_MANIFEST: &str = "application/manifest+json;charset=utf-8";
12
13/// Create a new manifest builder.
14
15#[skip_serializing_none]
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Manifest {
18    name: String,
19    short_name: String,
20    start_url: String,
21    #[serde(rename = "display")]
22    display_mode: DisplayMode,
23    background_color: String,
24    description: Option<String>,
25    #[serde(rename = "dir")]
26    direction: Direction,
27    // direction: Option<Direction>,
28    orientation: Option<String>,
29    // orientation: Option<Orientation>,
30    lang: Option<String>,
31    scope: Option<String>,
32    //
33    theme_color: String,
34    prefer_related_applications: Option<bool>,
35    // #[serde(borrow)]
36    //
37    icons: Vec<ManifestIcon>,
38    // icons: Vec<Icon<'i>>,
39    // #[serde(borrow)]
40    related_applications: Option<Vec<String>>,
41    // related_applications: Vec<Related<'r>>,
42}
43
44#[derive(Clone, Debug, Serialize, Deserialize)]
45struct ManifestIcon {
46    src: String,
47    #[serde(rename = "type")]
48    mime_type: String,
49    sizes: String,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    purpose: Option<String>,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
55enum Direction {
56    /// left-to-right
57    #[serde(rename = "ltr")]
58    Ltr,
59    /// right-to-left
60    #[serde(rename = "rtl")]
61    Rtl,
62    /// Hints to the browser to use the [Unicode bidirectional
63    /// algorithm](https://developer.mozilla.org/en-US/docs/Web/Localization/Unicode_Bidirectional_Text_Algorithm)
64    /// to make a best guess about the text's direction.
65    #[serde(rename = "auto")]
66    Auto,
67}
68
69/// Display modes from the Web app manifest definition
70///
71/// Ref: <https://developer.mozilla.org/en-US/docs/Web/Manifest/display>
72#[derive(Debug, Clone, Serialize, Deserialize)]
73enum DisplayMode {
74    /// All of the available display area is used and no user agent chrome is
75    /// shown.
76    #[serde(rename = "full-screen")]
77    FullScreen,
78    /// The application will look and feel like a standalone application. This can
79    /// include the application having a different window, its own icon in the
80    /// application launcher, etc. In this mode, the user agent will exclude UI
81    /// elements for controlling navigation, but can include other UI elements
82    /// such as a status bar.
83    #[serde(rename = "standalone")]
84    Standalone,
85    /// The application will look and feel like a standalone application, but will
86    /// have a minimal set of UI elements for controlling navigation. The elements
87    /// will vary by browser.
88    #[serde(rename = "minimal-ui")]
89    MinimalUi,
90    /// The application opens in a conventional browser tab or new window,
91    /// depending on the browser and platform. This is the default.
92    #[serde(rename = "browser")]
93    Browser,
94}
95
96pub fn manifest_data(host_req: Option<&str>, domain_display_name: String) -> Manifest {
97    let icons = vec![
98        ManifestIcon {
99            sizes: String::from("512x512"),
100            src: String::from("/pkg/img/logo-square.svg"),
101            mime_type: String::from("image/svg+xml"),
102            purpose: None,
103        },
104        ManifestIcon {
105            sizes: String::from("512x512"),
106            src: String::from("/pkg/img/logo-512.png"),
107            mime_type: String::from("image/png"),
108            purpose: Some(String::from("maskable")),
109        },
110        ManifestIcon {
111            sizes: String::from("192x192"),
112            src: String::from("/pkg/img/logo-192.png"),
113            mime_type: String::from("image/png"),
114            purpose: Some(String::from("maskable")),
115        },
116        ManifestIcon {
117            sizes: String::from("256x156"),
118            src: String::from("/pkg/img/logo-256.png"),
119            mime_type: String::from("image/png"),
120            purpose: Some(String::from("maskable")),
121        },
122    ];
123
124    let start_url = match host_req {
125        Some(value) => format!("https://{}/", value),
126        None => String::from("/"),
127    };
128
129    Manifest {
130        short_name: "Kanidm".to_string(),
131        name: domain_display_name,
132        start_url,
133        display_mode: DisplayMode::MinimalUi,
134        description: None,
135        orientation: None,
136        lang: Some("en".to_string()),
137        theme_color: "white".to_string(),
138        background_color: "white".to_string(),
139        direction: Direction::Auto,
140        scope: None,
141        prefer_related_applications: None,
142        icons,
143        related_applications: None,
144    }
145}
146
147/// Generates a manifest.json file for progressive web app usage
148pub(crate) async fn manifest(DomainInfo(domain_info): DomainInfo) -> impl IntoResponse {
149    let domain_display_name = domain_info.display_name().to_string();
150    // TODO: fix the None here to make it the request host
151    let manifest_string =
152        serde_json::to_string_pretty(&manifest_data(None, domain_display_name)).unwrap_or_default();
153    let mut res = Response::new(manifest_string);
154
155    res.headers_mut()
156        .insert(CONTENT_TYPE, HeaderValue::from_static(MIME_TYPE_MANIFEST));
157
158    res
159}