Mercurial > prosody-modules
comparison mod_cloud_notify/mod_cloud_notify.lua @ 4295:d44a8d3dd571
mod_cloud_notify: Some code cleanup, now luacheck-clean. No functionality changes.
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Fri, 11 Dec 2020 07:03:31 +0000 |
parents | 8bf83e883593 |
children | 45dcf5d4cd6c |
comparison
equal
deleted
inserted
replaced
4294:ae8191d9d533 | 4295:d44a8d3dd571 |
---|---|
2 -- Copyright (C) 2015-2016 Kim Alvefur | 2 -- Copyright (C) 2015-2016 Kim Alvefur |
3 -- Copyright (C) 2017-2019 Thilo Molitor | 3 -- Copyright (C) 2017-2019 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; | |
8 local s_match = string.match; | |
9 local s_sub = string.sub; | |
10 local os_time = os.time; | 7 local os_time = os.time; |
11 local next = next; | |
12 local st = require"util.stanza"; | 8 local st = require"util.stanza"; |
13 local jid = require"util.jid"; | 9 local jid = require"util.jid"; |
14 local dataform = require"util.dataforms".new; | 10 local dataform = require"util.dataforms".new; |
15 local filters = require"util.filters"; | |
16 local hashes = require"util.hashes"; | 11 local hashes = require"util.hashes"; |
17 local random = require"util.random"; | 12 local random = require"util.random"; |
18 local cache = require"util.cache"; | 13 local cache = require"util.cache"; |
19 | 14 |
20 local xmlns_push = "urn:xmpp:push:0"; | 15 local xmlns_push = "urn:xmpp:push:0"; |
35 -- This uses util.cache for caching the most recent devices and removing all old devices when max_push_devices is reached | 30 -- This uses util.cache for caching the most recent devices and removing all old devices when max_push_devices is reached |
36 local push_store = (function() | 31 local push_store = (function() |
37 local store = module:open_store(); | 32 local store = module:open_store(); |
38 local push_services = {}; | 33 local push_services = {}; |
39 local api = {}; | 34 local api = {}; |
35 --luacheck: ignore 212/self | |
40 function api:get(user) | 36 function api:get(user) |
41 if not push_services[user] then | 37 if not push_services[user] then |
42 local loaded, err = store:get(user); | 38 local loaded, err = store:get(user); |
43 if not loaded and err then | 39 if not loaded and err then |
44 module:log("warn", "Error reading push notification storage for user '%s': %s", user, tostring(err)); | 40 module:log("warn", "Error reading push notification storage for user '%s': %s", user, tostring(err)); |
83 local identifier = id2identifier[stanza.attr.id]; | 79 local identifier = id2identifier[stanza.attr.id]; |
84 if node == nil then return false; end -- unknown stanza? Ignore for now! | 80 if node == nil then return false; end -- unknown stanza? Ignore for now! |
85 local from = stanza.attr.from; | 81 local from = stanza.attr.from; |
86 local user_push_services = push_store:get(node); | 82 local user_push_services = push_store:get(node); |
87 local changed = false; | 83 local changed = false; |
88 | 84 |
89 for push_identifier, _ in pairs(user_push_services) do | 85 for push_identifier, _ in pairs(user_push_services) do |
90 if push_identifier == identifier then | 86 if push_identifier == identifier then |
91 if user_push_services[push_identifier] and user_push_services[push_identifier].jid == from and error_type ~= "wait" then | 87 if user_push_services[push_identifier] and user_push_services[push_identifier].jid == from and error_type ~= "wait" then |
92 push_errors[push_identifier] = push_errors[push_identifier] + 1; | 88 push_errors[push_identifier] = push_errors[push_identifier] + 1; |
93 module:log("info", "Got error of type '%s' (%s) for identifier '%s': " | 89 module:log("info", "Got error of type '%s' (%s) for identifier '%s': " |
132 local node = id2node[stanza.attr.id]; | 128 local node = id2node[stanza.attr.id]; |
133 local identifier = id2identifier[stanza.attr.id]; | 129 local identifier = id2identifier[stanza.attr.id]; |
134 if node == nil then return false; end -- unknown stanza? Ignore for now! | 130 if node == nil then return false; end -- unknown stanza? Ignore for now! |
135 local from = stanza.attr.from; | 131 local from = stanza.attr.from; |
136 local user_push_services = push_store:get(node); | 132 local user_push_services = push_store:get(node); |
137 | 133 |
138 for push_identifier, _ in pairs(user_push_services) do | 134 for push_identifier, _ in pairs(user_push_services) do |
139 if push_identifier == identifier then | 135 if push_identifier == identifier then |
140 if user_push_services[push_identifier] and user_push_services[push_identifier].jid == from and push_errors[push_identifier] > 0 then | 136 if user_push_services[push_identifier] and user_push_services[push_identifier].jid == from and push_errors[push_identifier] > 0 then |
141 push_errors[push_identifier] = 0; | 137 push_errors[push_identifier] = 0; |
142 -- unhook iq handlers for this identifier (if possible) | 138 -- unhook iq handlers for this identifier (if possible) |
143 module:unhook("iq-error/host/"..stanza.attr.id, handle_push_error); | 139 module:unhook("iq-error/host/"..stanza.attr.id, handle_push_error); |
144 module:unhook("iq-result/host/"..stanza.attr.id, handle_push_success); | 140 module:unhook("iq-result/host/"..stanza.attr.id, handle_push_success); |
145 id2node[stanza.attr.id] = nil; | 141 id2node[stanza.attr.id] = nil; |
146 id2identifier[stanza.attr.id] = nil; | 142 id2identifier[stanza.attr.id] = nil; |
147 module:log("debug", "Push succeeded, error count for identifier '%s' is now at %s again", push_identifier, tostring(push_errors[push_identifier])); | 143 module:log("debug", "Push succeeded, error count for identifier '%s' is now at %s again", |
144 push_identifier, tostring(push_errors[push_identifier]) | |
145 ); | |
148 end | 146 end |
149 end | 147 end |
150 end | 148 end |
151 return true; | 149 return true; |
152 end | 150 end |
247 module:hook("iq-set/self/"..xmlns_push..":disable", push_disable); | 245 module:hook("iq-set/self/"..xmlns_push..":disable", push_disable); |
248 | 246 |
249 -- is this push a high priority one (this is needed for ios apps not using voip pushes) | 247 -- is this push a high priority one (this is needed for ios apps not using voip pushes) |
250 local function is_important(stanza) | 248 local function is_important(stanza) |
251 local st_name = stanza and stanza.name or nil; | 249 local st_name = stanza and stanza.name or nil; |
252 if not st_name then return false; end -- nonzas are never important here | 250 if not st_name then return false; end -- nonzas are never important here |
253 if st_name == "presence" then | 251 if st_name == "presence" then |
254 return false; -- same for presences | 252 return false; -- same for presences |
255 elseif st_name == "message" then | 253 elseif st_name == "message" then |
256 -- unpack carbon copies | 254 -- unpack carbon copied message stanzas |
257 local stanza_direction = "in"; | 255 local carbon = stanza:find("{urn:xmpp:carbons:2}/{urn:xmpp:forward:0}/{jabber:client}message"); |
258 local carbon; | 256 local stanza_direction = carbon and stanza:child_with_name("sent") and "out" or "in"; |
259 local st_type; | |
260 -- support carbon copied message stanzas | |
261 if not carbon then carbon = stanza:find("{urn:xmpp:carbons:2}/{urn:xmpp:forward:0}/{jabber:client}message"); end | |
262 stanza_direction = carbon and stanza:child_with_name("sent") and "out" or "in"; | |
263 if carbon then stanza = carbon; end | 257 if carbon then stanza = carbon; end |
264 st_type = stanza.attr.type; | 258 local st_type = stanza.attr.type; |
265 | 259 |
266 -- headline message are always not important | 260 -- headline message are always not important |
267 if st_type == "headline" then return false; end | 261 if st_type == "headline" then return false; end |
268 | 262 |
269 -- carbon copied outgoing messages are not important | 263 -- carbon copied outgoing messages are not important |
270 if carbon and stanza_direction == "out" then return false; end | 264 if carbon and stanza_direction == "out" then return false; end |
271 | 265 |
272 -- We can't check for body contents in encrypted messages, so let's treat them as important | 266 -- We can't check for body contents in encrypted messages, so let's treat them as important |
273 -- Some clients don't even set a body or an empty body for encrypted messages | 267 -- Some clients don't even set a body or an empty body for encrypted messages |
274 | 268 |
275 -- check omemo https://xmpp.org/extensions/inbox/omemo.html | 269 -- check omemo https://xmpp.org/extensions/inbox/omemo.html |
276 if stanza:get_child("encrypted", "eu.siacs.conversations.axolotl") or stanza:get_child("encrypted", "urn:xmpp:omemo:0") then return true; end | 270 if stanza:get_child("encrypted", "eu.siacs.conversations.axolotl") or stanza:get_child("encrypted", "urn:xmpp:omemo:0") then return true; end |
277 | 271 |
278 -- check xep27 pgp https://xmpp.org/extensions/xep-0027.html | 272 -- check xep27 pgp https://xmpp.org/extensions/xep-0027.html |
279 if stanza:get_child("x", "jabber:x:encrypted") then return true; end | 273 if stanza:get_child("x", "jabber:x:encrypted") then return true; end |
280 | 274 |
281 -- check xep373 pgp (OX) https://xmpp.org/extensions/xep-0373.html | 275 -- check xep373 pgp (OX) https://xmpp.org/extensions/xep-0373.html |
282 if stanza:get_child("openpgp", "urn:xmpp:openpgp:0") then return true; end | 276 if stanza:get_child("openpgp", "urn:xmpp:openpgp:0") then return true; end |
283 | 277 |
284 local body = stanza:get_child_text("body"); | 278 local body = stanza:get_child_text("body"); |
285 if st_type == "groupchat" and stanza:get_child_text("subject") then return false; end -- groupchat subjects are not important here | 279 |
286 return body ~= nil and body ~= ""; -- empty bodies are not important | 280 -- groupchat subjects are not important here |
281 if st_type == "groupchat" and stanza:get_child_text("subject") then | |
282 return false; | |
283 end | |
284 | |
285 -- empty bodies are not important | |
286 return body ~= nil and body ~= ""; | |
287 end | 287 end |
288 return false; -- this stanza wasn't one of the above cases --> it is not important, too | 288 return false; -- this stanza wasn't one of the above cases --> it is not important, too |
289 end | 289 end |
290 | 290 |
291 local push_form = dataform { | 291 local push_form = dataform { |
298 | 298 |
299 -- http://xmpp.org/extensions/xep-0357.html#publishing | 299 -- http://xmpp.org/extensions/xep-0357.html#publishing |
300 local function handle_notify_request(stanza, node, user_push_services, log_push_decline) | 300 local function handle_notify_request(stanza, node, user_push_services, log_push_decline) |
301 local pushes = 0; | 301 local pushes = 0; |
302 if not #user_push_services then return pushes end | 302 if not #user_push_services then return pushes end |
303 | 303 |
304 for push_identifier, push_info in pairs(user_push_services) do | 304 for push_identifier, push_info in pairs(user_push_services) do |
305 local send_push = true; -- only send push to this node when not already done for this stanza or if no stanza is given at all | 305 local send_push = true; -- only send push to this node when not already done for this stanza or if no stanza is given at all |
306 if stanza then | 306 if stanza then |
307 if not stanza._push_notify then stanza._push_notify = {}; end | 307 if not stanza._push_notify then stanza._push_notify = {}; end |
308 if stanza._push_notify[push_identifier] then | 308 if stanza._push_notify[push_identifier] then |
311 end | 311 end |
312 send_push = false; | 312 send_push = false; |
313 end | 313 end |
314 stanza._push_notify[push_identifier] = true; | 314 stanza._push_notify[push_identifier] = true; |
315 end | 315 end |
316 | 316 |
317 if send_push then | 317 if send_push then |
318 -- construct push stanza | 318 -- construct push stanza |
319 local stanza_id = hashes.sha256(random.bytes(8), true); | 319 local stanza_id = hashes.sha256(random.bytes(8), true); |
320 local push_publish = st.iq({ to = push_info.jid, from = module.host, type = "set", id = stanza_id }) | 320 local push_publish = st.iq({ to = push_info.jid, from = module.host, type = "set", id = stanza_id }) |
321 :tag("pubsub", { xmlns = "http://jabber.org/protocol/pubsub" }) | 321 :tag("pubsub", { xmlns = "http://jabber.org/protocol/pubsub" }) |
340 push_publish:up(); -- / pubsub | 340 push_publish:up(); -- / pubsub |
341 if push_info.options then | 341 if push_info.options then |
342 push_publish:tag("publish-options"):add_child(st.deserialize(push_info.options)); | 342 push_publish:tag("publish-options"):add_child(st.deserialize(push_info.options)); |
343 end | 343 end |
344 -- send out push | 344 -- send out push |
345 module:log("debug", "Sending %s push notification for %s@%s to %s (%s)", form_data["last-message-body"] and "important" or "unimportant", node, module.host, push_info.jid, tostring(push_info.node)); | 345 module:log("debug", "Sending %s push notification for %s@%s to %s (%s)", |
346 form_data["last-message-body"] and "important" or "unimportant", | |
347 node, module.host, push_info.jid, tostring(push_info.node) | |
348 ); | |
346 -- module:log("debug", "PUSH STANZA: %s", tostring(push_publish)); | 349 -- module:log("debug", "PUSH STANZA: %s", tostring(push_publish)); |
347 -- handle push errors for this node | 350 -- handle push errors for this node |
348 if push_errors[push_identifier] == nil then | 351 if push_errors[push_identifier] == nil then |
349 push_errors[push_identifier] = 0; | 352 push_errors[push_identifier] = 0; |
350 end | 353 end |
381 for i=1, #queue do | 384 for i=1, #queue do |
382 local stanza = queue[i]; | 385 local stanza = queue[i]; |
383 -- fast ignore of already pushed stanzas | 386 -- fast ignore of already pushed stanzas |
384 if stanza and not (stanza._push_notify and stanza._push_notify[session.push_identifier]) then | 387 if stanza and not (stanza._push_notify and stanza._push_notify[session.push_identifier]) then |
385 local node = get_push_settings(stanza, session); | 388 local node = get_push_settings(stanza, session); |
386 stanza_type = "unimportant" | 389 local stanza_type = "unimportant"; |
387 if dummy_body and is_important(stanza) then stanza_type = "important"; end | 390 if dummy_body and is_important(stanza) then stanza_type = "important"; end |
388 if not notified[stanza_type] then -- only notify if we didn't try to push for this stanza type already | 391 if not notified[stanza_type] then -- only notify if we didn't try to push for this stanza type already |
389 -- session.log("debug", "Invoking cloud handle_notify_request() for smacks queued stanza: %d", i); | 392 -- session.log("debug", "Invoking cloud handle_notify_request() for smacks queued stanza: %d", i); |
390 if handle_notify_request(stanza, node, user_push_services, false) ~= 0 then | 393 if handle_notify_request(stanza, node, user_push_services, false) ~= 0 then |
391 if session.hibernating and not session.first_hibernated_push then | 394 if session.hibernating and not session.first_hibernated_push then |
422 process_stanza_queue(session.push_queue, session, "push"); | 425 process_stanza_queue(session.push_queue, session, "push"); |
423 session.push_queue = {}; -- clean up queue after push | 426 session.push_queue = {}; -- clean up queue after push |
424 end); | 427 end); |
425 end | 428 end |
426 else | 429 else |
427 session.log("debug", "NOT invoking cloud handle_notify_request() for newly smacks queued stanza (session.push_identifier is not set: %s)", session.push_identifier); | 430 session.log("debug", "NOT invoking cloud handle_notify_request() for newly smacks queued stanza (session.push_identifier is not set: %s)", |
431 session.push_identifier | |
432 ); | |
428 end | 433 end |
429 return stanza; | 434 return stanza; |
430 end | 435 end |
431 | 436 |
432 -- smacks hibernation is started | 437 -- smacks hibernation is started |
466 to = to and jid.split(to) or event.origin.username; | 471 to = to and jid.split(to) or event.origin.username; |
467 | 472 |
468 -- only notify if the stanza destination is the mam user we store it for | 473 -- only notify if the stanza destination is the mam user we store it for |
469 if event.for_user == to then | 474 if event.for_user == to then |
470 local user_push_services = push_store:get(to); | 475 local user_push_services = push_store:get(to); |
471 | 476 |
472 -- only notify nodes with no active sessions (smacks is counted as active and handled separate) | 477 -- only notify nodes with no active sessions (smacks is counted as active and handled separate) |
473 local notify_push_services = {}; | 478 local notify_push_services = {}; |
474 for identifier, push_info in pairs(user_push_services) do | 479 for identifier, push_info in pairs(user_push_services) do |
475 local identifier_found = nil; | 480 local identifier_found = nil; |
476 for _, session in pairs(user_session) do | 481 for _, session in pairs(user_session) do |
477 -- module:log("debug", "searching for '%s': identifier '%s' for session %s", tostring(identifier), tostring(session.push_identifier), tostring(session.full_jid)); | |
478 if session.push_identifier == identifier then | 482 if session.push_identifier == identifier then |
479 identifier_found = session; | 483 identifier_found = session; |
480 break; | 484 break; |
481 end | 485 end |
482 end | 486 end |
510 module:log("info", "Module loaded"); | 514 module:log("info", "Module loaded"); |
511 function module.unload() | 515 function module.unload() |
512 module:log("info", "Unloading module"); | 516 module:log("info", "Unloading module"); |
513 -- cleanup some settings, reloading this module can cause process_smacks_stanza() to stop working otherwise | 517 -- cleanup some settings, reloading this module can cause process_smacks_stanza() to stop working otherwise |
514 for user, _ in pairs(host_sessions) do | 518 for user, _ in pairs(host_sessions) do |
515 for sessionid, session in pairs(host_sessions[user].sessions) do | 519 for _, session in pairs(host_sessions[user].sessions) do |
516 if session.awaiting_push_timer then session.awaiting_push_timer:stop(); end | 520 if session.awaiting_push_timer then session.awaiting_push_timer:stop(); end |
517 session.awaiting_push_timer = nil; | 521 session.awaiting_push_timer = nil; |
518 session.first_hibernated_push = nil; | 522 session.first_hibernated_push = nil; |
519 session.push_queue = nil; | 523 session.push_queue = nil; |
520 end | 524 end |