xmpp_parsers: Add Push Notifications (XEP-0357)

Add support for Push Notification parsing according to https://xmpp.org/extensions/xep-0357.html version 0.4.1.
This commit is contained in:
Saarko 2025-02-22 09:31:02 +02:00
commit 932575c8d6
5 changed files with 184 additions and 0 deletions

View file

@ -73,6 +73,7 @@ XXXX-YY-ZZ RELEASER <admin@example.com>
- Message Displayed Synchronization (XEP-0490)
- RFC 6120 stream errors
- XEP-0045 mediated invites
- Push Notifications (XEP-0357) (!543)
* Improvements:
- Add support for `<optional/> in XEP-0198 feature advertisment
- Add support application-specific error conditions in XEP-0198

View file

@ -546,6 +546,14 @@
<xmpp:since>0.7.0</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0357.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.4.1</xmpp:version>
<xmpp:since>NEXT</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0359.html"/>

View file

@ -245,6 +245,9 @@ pub mod csi;
/// XEP-0353: Jingle Message Initiation
pub mod jingle_message;
/// XEP-0357: Push Notifications
pub mod push;
/// XEP-0359: Unique and Stable Stanza IDs
pub mod stanza_id;

View file

@ -237,6 +237,9 @@ pub const CSI: &str = "urn:xmpp:csi:0";
/// XEP-0353: Jingle Message Initiation
pub const JINGLE_MESSAGE: &str = "urn:xmpp:jingle-message:0";
/// XEP-0357: Push Notifications
pub const PUSH: &str = "urn:xmpp:push:0";
/// XEP-0359: Unique and Stable Stanza IDs
pub const SID: &str = "urn:xmpp:sid:0";

169
parsers/src/push.rs Normal file
View file

@ -0,0 +1,169 @@
// Copyright (c) 2025 saarko <saarko@tutanota.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use crate::data_forms::DataForm;
use crate::iq::IqSetPayload;
use crate::jid::BareJid;
use crate::ns;
use crate::pubsub::PubSubPayload;
use minidom::Element;
use xso::{AsXml, FromXml};
/// An enable element for push notifications
#[derive(Debug, Clone, FromXml, AsXml)]
#[xml(namespace = ns::PUSH, name = "enable")]
pub struct Enable {
/// The 'jid' attribute of the XMPP Push Service being enabled.
#[xml(attribute)]
pub jid: BareJid,
/// The 'node' attribute which is set to the provisioned node specified by the App Server.
#[xml(attribute(default))]
pub node: Option<String>,
/// Optional additional information to be provided with each published notification, such as authentication credentials.
#[xml(child(default))]
pub form: Option<DataForm>,
}
impl IqSetPayload for Enable {}
/// A disable element for push notifications
#[derive(Debug, Clone, FromXml, AsXml)]
#[xml(namespace = ns::PUSH, name = "disable")]
pub struct Disable {
/// The 'jid' attribute of the XMPP Push Service being disabled.
#[xml(attribute)]
pub jid: BareJid,
/// The 'node' attribute which was set to the provisioned node specified by the App Server.
#[xml(attribute(default))]
pub node: Option<String>,
}
impl IqSetPayload for Disable {}
/// A notification element containing push notification data
#[derive(Debug, Clone, FromXml, AsXml)]
#[xml(namespace = ns::PUSH, name = "notification")]
pub struct Notification {
/// The 'form' to provide summarized information such as the number of unread messages or number of pending subscription requests.
#[xml(child(default))]
pub form: Option<DataForm>,
/// Child elements for the notification that are not part of the summary data form.
#[xml(element(n = ..))]
pub payloads: Vec<Element>,
}
impl PubSubPayload for Notification {}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn test_enable() {
let test_enable = r#"<enable xmlns='urn:xmpp:push:0'
jid='push-5.client.example'
node='yxs32uqsflafdk3iuqo'>
<x xmlns='jabber:x:data' type='submit'>
<field var='FORM_TYPE'><value>http://jabber.org/protocol/pubsub#publish-options</value></field>
<field var='secret'><value>eruio234vzxc2kla-91</value></field>
</x>
</enable>"#;
let elem = Element::from_str(test_enable).expect("Failed to parse XML");
let enable = Enable::try_from(elem).expect("Failed to parse enable");
assert_eq!(
enable.jid,
BareJid::from_str("push-5.client.example").unwrap()
);
assert_eq!(enable.node.unwrap(), "yxs32uqsflafdk3iuqo");
assert!(enable.form.is_some());
}
#[test]
fn test_enable_only_with_required_fields() {
let test_enable = r#"<enable xmlns='urn:xmpp:push:0'
jid='push-5.client.example' />"#;
let elem = Element::from_str(test_enable).expect("Failed to parse XML");
let enable = Enable::try_from(elem).expect("Failed to parse enable");
assert_eq!(
enable.jid,
BareJid::from_str("push-5.client.example").unwrap()
);
assert!(enable.node.is_none());
assert!(enable.form.is_none());
}
#[test]
fn test_disable() {
let test_disable = r#"<disable xmlns='urn:xmpp:push:0'
jid='push-5.client.example'
node='yxs32uqsflafdk3iuqo' />"#;
let elem = Element::from_str(test_disable).expect("Failed to parse XML");
let disable = Disable::try_from(elem).expect("Failed to parse disable");
assert_eq!(
disable.jid,
BareJid::from_str("push-5.client.example").unwrap()
);
assert_eq!(disable.node.unwrap(), "yxs32uqsflafdk3iuqo");
}
#[test]
fn test_disable_only_with_required_fields() {
let test_disable = r#"<disable xmlns='urn:xmpp:push:0'
jid='push-5.client.example' />"#;
let elem = Element::from_str(test_disable).expect("Failed to parse XML");
let disable = Disable::try_from(elem).expect("Failed to parse disable");
assert_eq!(
disable.jid,
BareJid::from_str("push-5.client.example").unwrap()
);
assert!(disable.node.is_none());
}
#[test]
fn test_notification() {
let test_notification = r#"<notification xmlns='urn:xmpp:push:0'>
<x xmlns='jabber:x:data' type='result'>
<field var='FORM_TYPE'><value>urn:xmpp:push:summary</value></field>
<field var='message-count'><value>1</value></field>
<field var='last-message-sender'><value>juliet@capulet.example/balcony</value></field>
<field var='last-message-body'><value>Wherefore art thou, Romeo?</value></field>
</x>
<additional xmlns='http://example.com/custom'>Additional custom elements</additional>
</notification>"#;
let elem = Element::from_str(test_notification).expect("Failed to parse XML");
let notification = Notification::try_from(elem).expect("Failed to parse notification");
assert!(notification.form.is_some());
assert_eq!(notification.payloads.len(), 1);
}
#[test]
fn test_notification_only_with_required_fields() {
let test_notification = r#"<notification xmlns='urn:xmpp:push:0'>
<additional xmlns='http://example.com/custom'>Additional custom elements</additional>
</notification>"#;
let elem = Element::from_str(test_notification).expect("Failed to parse XML");
let notification = Notification::try_from(elem).expect("Failed to parse notification");
assert!(notification.form.is_none());
assert_eq!(notification.payloads.len(), 1);
}
}