atagen

macltok

public
Star0
Watch0
3dab4a4e73f48986ea8e34215bdeb7d5d3870773
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
push-gateway/src/lib.rsRust109 lines3.7 KB
1
//! the homeserver side of `POST /_matrix/push/v1/notify`, for a gateway that holds no session
2
3
use ruma_common::{OwnedEventId, OwnedRoomAliasId, OwnedRoomId, OwnedUserId};
4
use serde::{Deserialize, Serialize};
5
use serde_json::{Map, Value, value::RawValue};
6
7
#[derive(Clone, Debug, Deserialize)]
8
#[non_exhaustive]
9
pub struct GatewayNotification {
10
    pub event_id: Option<OwnedEventId>,
11
    pub room_id: Option<OwnedRoomId>,
12
    #[serde(rename = "type")]
13
    pub event_type: Option<String>,
14
    pub sender: Option<OwnedUserId>,
15
    pub sender_display_name: Option<String>,
16
    pub room_name: Option<String>,
17
    pub room_alias: Option<OwnedRoomAliasId>,
18
    #[serde(default)]
19
    pub user_is_target: bool,
20
    #[serde(default)]
21
    pub prio: NotifyPrio,
22
    /// only in the full format, and still encrypted for an encrypted room
23
    pub content: Option<Box<RawValue>>,
24
    #[serde(default)]
25
    pub counts: NotifyCounts,
26
    #[serde(default)]
27
    pub devices: Vec<GatewayDevice>,
28
}
29
30
impl GatewayNotification {
31
    /// a count-only push carries no event, and iOS punishes a web push that shows nothing
32
    pub fn is_event(&self) -> bool {
33
        self.event_id.is_some()
34
    }
35
}
36
37
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
38
#[serde(rename_all = "lowercase")]
39
pub enum NotifyPrio {
40
    #[default]
41
    High,
42
    Low,
43
}
44
45
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
46
#[non_exhaustive]
47
pub struct NotifyCounts {
48
    #[serde(default)]
49
    pub unread: u64,
50
    #[serde(default)]
51
    pub missed_calls: u64,
52
}
53
54
#[derive(Clone, Debug, Deserialize)]
55
#[non_exhaustive]
56
pub struct GatewayDevice {
57
    pub app_id: String,
58
    pub pushkey: String,
59
    pub pushkey_ts: Option<u64>,
60
    /// the pusher's data as it was set, `format` included
61
    #[serde(default)]
62
    pub data: Map<String, Value>,
63
    #[serde(default)]
64
    pub tweaks: Map<String, Value>,
65
}
66
67
#[derive(Deserialize)]
68
struct NotifyBody {
69
    notification: GatewayNotification,
70
}
71
72
pub fn parse_notify(body: &[u8]) -> Result<GatewayNotification, serde_json::Error> {
73
    serde_json::from_slice::<NotifyBody>(body).map(|body| body.notification)
74
}
75
76
/// the pushkeys the homeserver should drop, such as one whose web push endpoint answered 404 or 410
77
pub fn notify_response(rejected: &[String]) -> Vec<u8> {
78
    #[derive(Serialize)]
79
    struct Response<'a> {
80
        rejected: &'a [String],
81
    }
82
    serde_json::to_vec(&Response { rejected }).expect("a list of strings serialises")
83
}
84
85
#[cfg(test)]
86
mod tests {
87
    use super::*;
88
89
    #[test]
90
    fn a_count_only_push_is_no_event_and_devices_keep_their_data() {
91
        let body = br#"{"notification":{"counts":{"unread":2},"devices":[{"app_id":"me.malice","pushkey":"https://push.example/abc","data":{"format":"event_id_only","endpoint":"https://push.example/abc","auth":"secret"},"tweaks":{"sound":"default"}}]}}"#;
92
        let notification = parse_notify(body).unwrap();
93
        assert!(!notification.is_event());
94
        assert_eq!(notification.counts.unread, 2);
95
        assert_eq!(notification.prio, NotifyPrio::High);
96
        assert_eq!(notification.devices[0].data["auth"], "secret");
97
        assert_eq!(notification.devices[0].tweaks["sound"], "default");
98
99
        let body = br#"{"notification":{"event_id":"$e","room_id":"!r:example.org","type":"m.room.message","sender":"@a:example.org","prio":"low","content":{"body":"hi"},"devices":[]}}"#;
100
        let notification = parse_notify(body).unwrap();
101
        assert!(notification.is_event());
102
        assert_eq!(notification.prio, NotifyPrio::Low);
103
        assert_eq!(notification.content.unwrap().get(), r#"{"body":"hi"}"#);
104
        assert_eq!(
105
            notify_response(&["gone".to_owned()]),
106
            br#"{"rejected":["gone"]}"#
107
        );
108
    }
109
}