Comparison

mod_cloud_notify/mod_cloud_notify.lua @ 2976:df86ce6bb0b4

Implement dummy body message to indicate high priority push This adds a dummy body sent alongside the push when the original message also contained a body to indicate a high priority push. The body can be configured and its contents are generally meaningless with most app servers because it will be stripped before pushed to the client.
author tmolitor <thilo@eightysoft.de>
date Sun, 01 Apr 2018 23:24:33 +0200
parent 2792:7622ec165cdd
child 3010:7ee59f417c16
comparison
equal deleted inserted replaced
2975:7eb6fa9b03fd 2976:df86ce6bb0b4
3 -- Copyright (C) 2017 Thilo Molitor 3 -- Copyright (C) 2017 Thilo Molitor
4 -- 4 --
5 -- This file is MIT/X11 licensed. 5 -- This file is MIT/X11 licensed.
6 6
7 local t_insert = table.insert; 7 local t_insert = table.insert;
8 local s_match = string.match;
9 local s_sub = string.sub;
8 local st = require"util.stanza"; 10 local st = require"util.stanza";
9 local jid = require"util.jid"; 11 local jid = require"util.jid";
10 local dataform = require"util.dataforms".new; 12 local dataform = require"util.dataforms".new;
11 local filters = require"util.filters"; 13 local filters = require"util.filters";
12 local hashes = require"util.hashes"; 14 local hashes = require"util.hashes";
15 17
16 -- configuration 18 -- configuration
17 local include_body = module:get_option_boolean("push_notification_with_body", false); 19 local include_body = module:get_option_boolean("push_notification_with_body", false);
18 local include_sender = module:get_option_boolean("push_notification_with_sender", false); 20 local include_sender = module:get_option_boolean("push_notification_with_sender", false);
19 local max_push_errors = module:get_option_number("push_max_errors", 50); 21 local max_push_errors = module:get_option_number("push_max_errors", 50);
22 local dummy_body = module:get_option_string("push_notification_important_body", "");
20 23
21 local host_sessions = prosody.hosts[module.host].sessions; 24 local host_sessions = prosody.hosts[module.host].sessions;
22 local push_errors = {}; 25 local push_errors = {};
23 local id2node = {}; 26 local id2node = {};
24 27
206 end 209 end
207 return true; 210 return true;
208 end 211 end
209 module:hook("iq-set/self/"..xmlns_push..":disable", push_disable); 212 module:hook("iq-set/self/"..xmlns_push..":disable", push_disable);
210 213
211 -- clone a stanza and strip it 214 -- Patched version of util.stanza:find() that supports giving stanza names
212 local function strip_stanza(stanza) 215 -- without their namespace, allowing for every namespace.
213 local tags = {}; 216 local function find(self, path)
214 local new = { name = stanza.name, attr = { xmlns = stanza.attr.xmlns, type = stanza.attr.type }, tags = tags }; 217 local pos = 1;
215 for i=1,#stanza do 218 local len = #path + 1;
216 local child = stanza[i]; 219
217 if type(child) == "table" then -- don't add raw text nodes 220 repeat
218 if child.name then 221 local xmlns, name, text;
219 child = strip_stanza(child); 222 local char = s_sub(path, pos, pos);
220 t_insert(tags, child); 223 if char == "@" then
221 end 224 return self.attr[s_sub(path, pos + 1)];
222 t_insert(new, child); 225 elseif char == "{" then
223 end 226 xmlns, pos = s_match(path, "^([^}]+)}()", pos + 1);
224 end 227 end
225 return setmetatable(new, st.stanza_mt); 228 name, text, pos = s_match(path, "^([^@/#]*)([/#]?)()", pos);
229 name = name ~= "" and name or nil;
230 if pos == len then
231 if text == "#" then
232 local child = xmlns ~= nil and self:get_child(name, xmlns) or self:child_with_name(name);
233 return child and child:get_text() or nil;
234 end
235 return xmlns ~= nil and self:get_child(name, xmlns) or self:child_with_name(name);
236 end
237 self = xmlns ~= nil and self:get_child(name, xmlns) or self:child_with_name(name);
238 until not self
239 return nil;
240 end
241
242 -- is this push a high priority one (this is needed for ios apps not using voip pushes)
243 local function is_important(stanza)
244 local st_name = stanza and stanza.name or nil;
245 if not st_name then return false; end -- nonzas are never important here
246 if st_name == "presence" then
247 return false; -- same for presences
248 elseif st_name == "message" then
249 -- unpack carbon copies
250 local stanza_direction = "in";
251 local carbon;
252 local st_type;
253 -- support carbon copied message stanzas having an arbitrary message-namespace or no message-namespace at all
254 if not carbon then carbon = find(stanza, "{urn:xmpp:carbons:2}/forwarded/message"); end
255 if not carbon then carbon = find(stanza, "{urn:xmpp:carbons:1}/forwarded/message"); end
256 stanza_direction = carbon and stanza:child_with_name("sent") and "out" or "in";
257 if carbon then stanza = carbon; end
258 st_type = stanza.attr.type;
259
260 -- headline message are always not important
261 if st_type == "headline" then return false; end
262
263 -- carbon copied outgoing messages are not important
264 if carbon and stanza_direction == "out" then return false; end
265
266 -- We can't check for body contents in encrypted messages, so let's treat them as important
267 -- Some clients don't even set a body or an empty body for encrypted messages
268
269 -- check omemo https://xmpp.org/extensions/inbox/omemo.html
270 if stanza:get_child("encrypted", "eu.siacs.conversations.axolotl") or stanza:get_child("encrypted", "urn:xmpp:omemo:0") then return true; end
271
272 -- check xep27 pgp https://xmpp.org/extensions/xep-0027.html
273 if stanza:get_child("x", "jabber:x:encrypted") then return true; end
274
275 -- check xep373 pgp (OX) https://xmpp.org/extensions/xep-0373.html
276 if stanza:get_child("openpgp", "urn:xmpp:openpgp:0") then return true; end
277
278 local body = stanza:get_child_text("body");
279 if st_type == "groupchat" and stanza:get_child_text("subject") then return false; end -- groupchat subjects are not important here
280 return body ~= nil and body ~= ""; -- empty bodies are not important
281 end
282 return false; -- this stanza wasn't one of the above cases --> it is not important, too
226 end 283 end
227 284
228 local push_form = dataform { 285 local push_form = dataform {
229 { name = "FORM_TYPE"; type = "hidden"; value = "urn:xmpp:push:summary"; }; 286 { name = "FORM_TYPE"; type = "hidden"; value = "urn:xmpp:push:summary"; };
230 { name = "message-count"; type = "text-single"; }; 287 { name = "message-count"; type = "text-single"; };
231 { name = "pending-subscription-count"; type = "text-single"; }; 288 { name = "pending-subscription-count"; type = "text-single"; };
232 { name = "last-message-sender"; type = "jid-single"; }; 289 { name = "last-message-sender"; type = "jid-single"; };
233 { name = "last-message-body"; type = "text-single"; }; 290 { name = "last-message-body"; type = "text-single"; };
234 { name = "last-message-priority"; type = "text-single"; };
235 }; 291 };
236 292
237 -- http://xmpp.org/extensions/xep-0357.html#publishing 293 -- http://xmpp.org/extensions/xep-0357.html#publishing
238 local function handle_notify_request(stanza, node, user_push_services) 294 local function handle_notify_request(stanza, node, user_push_services)
239 local pushes = 0; 295 local pushes = 0;
267 if stanza and include_sender then 323 if stanza and include_sender then
268 form_data["last-message-sender"] = stanza.attr.from; 324 form_data["last-message-sender"] = stanza.attr.from;
269 end 325 end
270 if stanza and include_body then 326 if stanza and include_body then
271 form_data["last-message-body"] = stanza:get_child_text("body"); 327 form_data["last-message-body"] = stanza:get_child_text("body");
328 elseif stanza and dummy_body ~= "" and is_important(stanza) then
329 form_data["last-message-body"] = dummy_body;
272 end 330 end
273 push_publish:add_child(push_form:form(form_data)); 331 push_publish:add_child(push_form:form(form_data));
274 if stanza and push_info.include_payload == "stripped" then
275 push_publish:tag("payload", { type = "stripped" })
276 :add_child(strip_stanza(stanza));
277 push_publish:up(); -- / payload
278 end
279 if stanza and push_info.include_payload == "full" then
280 push_publish:tag("payload", { type = "full" })
281 :add_child(st.clone(stanza));
282 push_publish:up(); -- / payload
283 end
284 push_publish:up(); -- / notification 332 push_publish:up(); -- / notification
285 push_publish:up(); -- / publish 333 push_publish:up(); -- / publish
286 push_publish:up(); -- / pubsub 334 push_publish:up(); -- / pubsub
287 if push_info.options then 335 if push_info.options then
288 push_publish:tag("publish-options"):add_child(st.deserialize(push_info.options)); 336 push_publish:tag("publish-options"):add_child(st.deserialize(push_info.options));