kanidmd_core/https/
manifest.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
//! Builds a Progressive Web App Manifest page.
use axum::http::header::CONTENT_TYPE;
use axum::http::HeaderValue;
use axum::response::{IntoResponse, Response};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;

use crate::https::extractors::DomainInfo;

/// The MIME type for `.webmanifest` files.
const MIME_TYPE_MANIFEST: &str = "application/manifest+json;charset=utf-8";

/// Create a new manifest builder.

#[skip_serializing_none]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Manifest {
    name: String,
    short_name: String,
    start_url: String,
    #[serde(rename = "display")]
    display_mode: DisplayMode,
    background_color: String,
    description: Option<String>,
    #[serde(rename = "dir")]
    direction: Direction,
    // direction: Option<Direction>,
    orientation: Option<String>,
    // orientation: Option<Orientation>,
    lang: Option<String>,
    scope: Option<String>,
    //
    theme_color: String,
    prefer_related_applications: Option<bool>,
    // #[serde(borrow)]
    //
    icons: Vec<ManifestIcon>,
    // icons: Vec<Icon<'i>>,
    // #[serde(borrow)]
    related_applications: Option<Vec<String>>,
    // related_applications: Vec<Related<'r>>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
struct ManifestIcon {
    src: String,
    #[serde(rename = "type")]
    mime_type: String,
    sizes: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    purpose: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
enum Direction {
    /// left-to-right
    #[serde(rename = "ltr")]
    Ltr,
    /// right-to-left
    #[serde(rename = "rtl")]
    Rtl,
    /// Hints to the browser to use the [Unicode bidirectional
    /// algorithm](https://developer.mozilla.org/en-US/docs/Web/Localization/Unicode_Bidirectional_Text_Algorithm)
    /// to make a best guess about the text's direction.
    #[serde(rename = "auto")]
    Auto,
}

/// Display modes from the Web app manifest definition
///
/// Ref: <https://developer.mozilla.org/en-US/docs/Web/Manifest/display>
#[derive(Debug, Clone, Serialize, Deserialize)]
enum DisplayMode {
    /// All of the available display area is used and no user agent chrome is
    /// shown.
    #[serde(rename = "full-screen")]
    FullScreen,
    /// The application will look and feel like a standalone application. This can
    /// include the application having a different window, its own icon in the
    /// application launcher, etc. In this mode, the user agent will exclude UI
    /// elements for controlling navigation, but can include other UI elements
    /// such as a status bar.
    #[serde(rename = "standalone")]
    Standalone,
    /// The application will look and feel like a standalone application, but will
    /// have a minimal set of UI elements for controlling navigation. The elements
    /// will vary by browser.
    #[serde(rename = "minimal-ui")]
    MinimalUi,
    /// The application opens in a conventional browser tab or new window,
    /// depending on the browser and platform. This is the default.
    #[serde(rename = "browser")]
    Browser,
}

pub fn manifest_data(host_req: Option<&str>, domain_display_name: String) -> Manifest {
    let icons = vec![
        ManifestIcon {
            sizes: String::from("512x512"),
            src: String::from("/pkg/img/logo-square.svg"),
            mime_type: String::from("image/svg+xml"),
            purpose: None,
        },
        ManifestIcon {
            sizes: String::from("512x512"),
            src: String::from("/pkg/img/logo-512.png"),
            mime_type: String::from("image/png"),
            purpose: Some(String::from("maskable")),
        },
        ManifestIcon {
            sizes: String::from("192x192"),
            src: String::from("/pkg/img/logo-192.png"),
            mime_type: String::from("image/png"),
            purpose: Some(String::from("maskable")),
        },
        ManifestIcon {
            sizes: String::from("256x156"),
            src: String::from("/pkg/img/logo-256.png"),
            mime_type: String::from("image/png"),
            purpose: Some(String::from("maskable")),
        },
    ];

    let start_url = match host_req {
        Some(value) => format!("https://{}/", value),
        None => String::from("/"),
    };

    Manifest {
        short_name: "Kanidm".to_string(),
        name: domain_display_name,
        start_url,
        display_mode: DisplayMode::MinimalUi,
        description: None,
        orientation: None,
        lang: Some("en".to_string()),
        theme_color: "white".to_string(),
        background_color: "white".to_string(),
        direction: Direction::Auto,
        scope: None,
        prefer_related_applications: None,
        icons,
        related_applications: None,
    }
}

/// Generates a manifest.json file for progressive web app usage
pub(crate) async fn manifest(DomainInfo(domain_info): DomainInfo) -> impl IntoResponse {
    let domain_display_name = domain_info.display_name().to_string();
    // TODO: fix the None here to make it the request host
    let manifest_string =
        serde_json::to_string_pretty(&manifest_data(None, domain_display_name)).unwrap_or_default();
    let mut res = Response::new(manifest_string);

    res.headers_mut()
        .insert(CONTENT_TYPE, HeaderValue::from_static(MIME_TYPE_MANIFEST));

    res
}