--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_cloud_notify/README.markdown Mon Aug 24 23:09:58 2015 +0200
@@ -0,0 +1,29 @@
+#summary XEP-0357: Cloud push notifications
+#labels Stage-Alpha
+
+= Introduction =
+
+This is an implementation of the server bits of
+[http://xmpp.org/extensions/xep-357.html XEP-357: Push].
+It allows clients to register an "app server" which is notified about
+new messages while the user is offline or disconnected. Implementation
+of the "app server", which is expected to forward notifications to
+something like Google Cloud Messaging or Apple Notification Service.
+
+= Details =
+
+App servers are notified about offline messages.
+
+= Installation =
+
+Same as any other module.
+
+= Configuration =
+
+Configured in-band by supporting clients.
+
+= Future =
+
+Adding support for sending notifications for users who are online but
+not currently connected, such as when `mod_smacks` is keeping their
+session alive, should be added.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_cloud_notify/mod_mobile_notify.lua Mon Aug 24 23:09:58 2015 +0200
@@ -0,0 +1,100 @@
+-- XEP-0357: Push (aka: My mobile OS vendor won't let me have persistent TCP connections)
+-- Copyright (C) 2015 Kim Alvefur
+--
+-- This file is MIT/X11 licensed.
+
+local st = require"util.stanza";
+local jid = require"util.jid";
+local dataform = require"util.dataforms".new;
+
+local xmlns_push = "urn:xmpp:push:0";
+
+module:add_feature(xmlns_push);
+
+local push_enabled = module:shared("push-enabled-users");
+
+module:hook("iq-set/self/"..xmlns_push..":enable", function (event)
+ local origin, stanza = event.origin, event.stanza;
+ local push_jid, push_node = stanza.tags[1].attr.jid, stanza.tags[1].attr.node;
+ if not push_jid or not push_node then
+ origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing jid or node"));
+ return true;
+ end
+ local publish_options = stanza.tags[1].tags[1];
+ if publish_options and ( publish_options.name ~= "x" or publish_options.attr.xmlns ~= "jabber:x:data" ) then
+ origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid publish options"));
+ return true;
+ end
+ local user_push_services = push_enabled[origin.username];
+ if not user_push_services then
+ user_push_services = {};
+ end
+ user_push_services[push_jid .. "<" .. push_node] = {
+ jid = push_jid;
+ node = push_node;
+ count = 0;
+ options = publish_options;
+ };
+ origin.send(st.reply(stanza));
+ return true;
+end);
+
+module:hook("iq-set/self/"..xmlns_push..":disable", function (event)
+ local origin, stanza = event.origin, event.stanza;
+ local push_jid, push_node = stanza.tags[1].attr.jid, stanza.tags[1].attr.node;
+ if not push_jid or not push_node then
+ origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing jid or node"));
+ return true;
+ end
+ local user_push_services = push_enabled[origin.username];
+ if user_push_services then
+ user_push_services[push_jid .. "<" .. push_node] = nil;
+ end
+ origin.send(st.reply(stanza));
+ return true;
+end);
+
+local push_form = dataform {
+ { name = "FORM_TYPE"; type = "hidden"; value = "urn:xmpp:push:summary"; };
+ { name = "message-count"; type = "text-single"; };
+ { name = "pending-subscription-count"; type = "text-single"; };
+ { name = "last-message-sender"; type = "jid-single"; };
+ { name = "last-message-body"; type = "text-single"; };
+};
+
+module:hook("message/offline/handle", function(event)
+ local origin, stanza = event.origin, event.stanza;
+ local to = stanza.attr.to;
+ local node = to and jid.split(to) or origin.username;
+ local user_push_services = push_enabled[origin.username];
+ if not user_push_services then return end
+
+ for _, push_info in pairs(push_info) do
+ push_info.count = push_info.count + 1;
+ local push_jid, push_node = push_info.jid, push_info.node;
+ local push_publish = st.iq({ to = push_jid, from = module.host, type = "set", id = "push" })
+ :tag("pubsub", { xmlns = "http://jabber.org/protocol/pubsub" })
+ :tag("publish", { node = push_node });
+ push_publish:add_child(push_form:form({
+ ["message-count"] = tostring(push_info.count);
+ ["last-message-sender"] = stanza.attr.from;
+ ["last-message-body"] = stanza:get_child_text("body");
+ }));
+ push_publish:up(); -- / publish
+ if push_info.options then
+ push_publish:tag("publish-options"):add_child(push_info.options);
+ end
+ module:send(push_publish);
+ end
+end, 1);
+
+module:hook("message/offline/broadcast", function(event)
+ local user_push_services = push_enabled[origin.username];
+ if not user_push_services then return end
+
+ for _, push_info in pairs(push_info) do
+ if push_info then
+ push_info.count = 0;
+ end
+ end
+end, 1);