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