autopush_common/
notification.rs

1//! Notification protocol
2use std::collections::HashMap;
3
4use serde_derive::{Deserialize, Serialize};
5use uuid::Uuid;
6
7use crate::util::ms_since_epoch;
8
9#[derive(Serialize, Default, Deserialize, Clone, Debug)]
10/// A Publishable Notification record. This is a notification that is either
11/// received from a third party or is outbound to a UserAgent.
12///
13pub struct Notification {
14    // Required values
15    #[serde(rename = "channelID")]
16    pub channel_id: Uuid,
17    pub version: String,
18    #[serde(skip_serializing)]
19    pub timestamp: u64,
20    // Possibly stored values, provided with a default.
21    #[serde(default = "default_ttl", skip_serializing)]
22    pub ttl: u64,
23    // Optional values, which imply a "None" default.
24    #[serde(skip_serializing)]
25    pub topic: Option<String>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub data: Option<String>,
28    #[serde(skip_serializing)]
29    pub sortkey_timestamp: Option<u64>,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub headers: Option<HashMap<String, String>>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub reliability_id: Option<String>,
34    #[cfg(feature = "reliable_report")]
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub reliable_state: Option<crate::reliability::ReliabilityState>,
37}
38
39pub const TOPIC_NOTIFICATION_PREFIX: &str = "01";
40pub const STANDARD_NOTIFICATION_PREFIX: &str = "02";
41
42impl Notification {
43    /// Return an appropriate chidmessageid
44    ///
45    /// For standard messages:
46    ///     {STANDARD_NOTIFICATION_PREFIX}:{sortkey_timestamp}:{chid}
47    ///
48    /// For topic messages:
49    ///     {TOPIC_NOTIFICATION_PREFIX}:{chid}:{topic}
50    ///
51    /// Old format for non-topic messages that is no longer returned:
52    ///     {chid}:{message_id}
53    pub fn chidmessageid(&self) -> String {
54        let chid = self.channel_id.as_hyphenated();
55
56        if let Some(ref topic) = self.topic {
57            format!("{TOPIC_NOTIFICATION_PREFIX}:{chid}:{topic}")
58        } else if let Some(sortkey_timestamp) = self.sortkey_timestamp {
59            format!(
60                "{STANDARD_NOTIFICATION_PREFIX}:{}:{}",
61                if sortkey_timestamp == 0 {
62                    ms_since_epoch()
63                } else {
64                    sortkey_timestamp
65                },
66                chid
67            )
68        } else {
69            warn!("🚨 LEGACY MESSAGE!? {:?} ", self);
70            // Legacy messages which we should never get anymore
71            format!("{}:{}", chid, self.version)
72        }
73    }
74
75    pub fn expiry(&self) -> u64 {
76        self.timestamp + self.ttl
77    }
78
79    /// Convenience function to determine if the notification
80    /// has aged out.
81    pub fn expired(&self, at_sec: u64) -> bool {
82        at_sec >= self.expiry()
83    }
84
85    #[cfg(feature = "reliable_report")]
86    pub async fn record_reliability(
87        &mut self,
88        reliability: &crate::reliability::PushReliability,
89        state: crate::reliability::ReliabilityState,
90    ) {
91        self.reliable_state = reliability
92            .record(
93                &self.reliability_id,
94                state,
95                &self.reliable_state,
96                Some(self.expiry()),
97            )
98            .await
99            .inspect_err(|e| {
100                warn!("🔍⚠️ Unable to record reliability state log: {:?}", e);
101            })
102            .unwrap_or(Some(state));
103    }
104
105    #[cfg(feature = "reliable_report")]
106    pub fn clone_without_reliability_state(&self) -> Self {
107        let mut cloned = self.clone();
108        cloned.reliable_state = None;
109        cloned
110    }
111}
112
113pub(crate) fn default_ttl() -> u64 {
114    0
115}