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
use leptos::*;
use std::fmt::Display;

/// Reactive [Permissions API](https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API).
///
/// ## Demo
///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_permission)
///
/// ## Usage
///
/// ```
/// # use leptos::*;
/// # use leptos_use::use_permission;
/// #
/// # #[component]
/// # fn Demo() -> impl IntoView {
/// let microphone_access = use_permission("microphone");
/// #
/// # view! { }
/// # }
/// ```
///
/// ## Server-Side Rendering
///
/// On the server the returned signal will always be `PermissionState::Unknown`.
pub fn use_permission(permission_name: &str) -> Signal<PermissionState> {
    let (state, set_state) = create_signal(PermissionState::Unknown);

    #[cfg(not(feature = "ssr"))]
    {
        use crate::use_event_listener;
        use std::cell::RefCell;
        use std::rc::Rc;

        let permission_status = Rc::new(RefCell::new(None::<web_sys::PermissionStatus>));

        let on_change = {
            let permission_status = Rc::clone(&permission_status);

            move || {
                if let Some(permission_status) = permission_status.borrow().as_ref() {
                    set_state.set(PermissionState::from(permission_status.state()));
                }
            }
        };

        spawn_local({
            let permission_name = permission_name.to_owned();

            async move {
                if let Ok(status) = query_permission(permission_name).await {
                    let _ = use_event_listener(status.clone(), ev::change, {
                        let on_change = on_change.clone();
                        move |_| on_change()
                    });
                    permission_status.replace(Some(status));
                    on_change();
                } else {
                    set_state.set(PermissionState::Prompt);
                }
            }
        });
    }

    #[cfg(feature = "ssr")]
    {
        let _ = set_state;
        let _ = permission_name;
    }

    state.into()
}

/// Return type of [`use_permission`].
#[derive(Copy, Clone, Default, Eq, PartialEq)]
pub enum PermissionState {
    /// State hasn't been requested yet. This is the initial value.
    #[default]
    Unknown,

    /// The permission has been granted by the user.
    Granted,

    /// The user will automatically be prompted to give permission once the relevant API is called.
    Prompt,

    /// The user has denied permission.
    Denied,
}

impl Display for PermissionState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match *self {
            PermissionState::Unknown => write!(f, "unknown"),
            PermissionState::Granted => write!(f, "granted"),
            PermissionState::Prompt => write!(f, "prompt"),
            PermissionState::Denied => write!(f, "denied"),
        }
    }
}

impl From<web_sys::PermissionState> for PermissionState {
    fn from(permission_state: web_sys::PermissionState) -> Self {
        match permission_state {
            web_sys::PermissionState::Granted => PermissionState::Granted,
            web_sys::PermissionState::Prompt => PermissionState::Prompt,
            web_sys::PermissionState::Denied => PermissionState::Denied,
            _ => PermissionState::Unknown,
        }
    }
}

#[cfg(not(feature = "ssr"))]
async fn query_permission(
    permission: String,
) -> Result<web_sys::PermissionStatus, wasm_bindgen::JsValue> {
    use crate::{js, js_fut};
    use wasm_bindgen::JsCast;

    let permission_object = js_sys::Object::new();
    js!(permission_object["name"] = permission);

    let permission_state: web_sys::PermissionStatus = js_fut!(window()
        .navigator()
        .permissions()?
        .query(&permission_object)?)
    .await?
    .unchecked_into();

    Ok(permission_state)
}