Software / code / 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 |
| parent | 4273:8bf83e883593 |
| child | 4324: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 |