kanidmd_core/https/
manifest.rs1use 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
10const MIME_TYPE_MANIFEST: &str = "application/manifest+json;charset=utf-8";
12
13#[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    orientation: Option<String>,
29    lang: Option<String>,
31    scope: Option<String>,
32    theme_color: String,
34    prefer_related_applications: Option<bool>,
35    icons: Vec<ManifestIcon>,
38    related_applications: Option<Vec<String>>,
41    }
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    #[serde(rename = "ltr")]
58    Ltr,
59    #[serde(rename = "rtl")]
61    Rtl,
62    #[serde(rename = "auto")]
66    Auto,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
73enum DisplayMode {
74    #[serde(rename = "full-screen")]
77    FullScreen,
78    #[serde(rename = "standalone")]
84    Standalone,
85    #[serde(rename = "minimal-ui")]
89    MinimalUi,
90    #[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
147pub(crate) async fn manifest(DomainInfo(domain_info): DomainInfo) -> impl IntoResponse {
149    let domain_display_name = domain_info.display_name().to_string();
150    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}