Software /
code /
prosody-modules
Comparison
mod_push2/mod_push2.lua @ 5682:4d1a3de56c3d
Initial work on Push 2.0
author | Stephen Paul Weber <singpolyma@singpolyma.net> |
---|---|
date | Tue, 19 Sep 2023 21:21:17 -0500 |
child | 5683:bebb10fa5787 |
comparison
equal
deleted
inserted
replaced
5681:7a4a6ded2bd6 | 5682:4d1a3de56c3d |
---|---|
1 local os_time = os.time; | |
2 local st = require"util.stanza"; | |
3 local jid = require"util.jid"; | |
4 local hashes = require"util.hashes"; | |
5 local random = require"util.random"; | |
6 local watchdog = require "util.watchdog"; | |
7 local uuid = require "util.uuid"; | |
8 local base64 = require "util.encodings".base64; | |
9 local ciphers = require "openssl.cipher"; | |
10 local pkey = require "openssl.pkey"; | |
11 local kdf = require "openssl.kdf"; | |
12 local jwt = require "util.jwt"; | |
13 | |
14 local xmlns_push = "urn:xmpp:push2:0"; | |
15 | |
16 -- configuration | |
17 local contact_uri = module:get_option_string("contact_uri", "xmpp:" .. module.host) | |
18 local extended_hibernation_timeout = module:get_option_number("push_max_hibernation_timeout", 72*3600) -- use same timeout like ejabberd | |
19 | |
20 local host_sessions = prosody.hosts[module.host].sessions | |
21 local push2_registrations = module:open_store("push2_registrations", "keyval") | |
22 | |
23 if _VERSION:match("5%.1") or _VERSION:match("5%.2") then | |
24 module:log("warn", "This module may behave incorrectly on Lua before 5.3. It is recommended to upgrade to a newer Lua version.") | |
25 end | |
26 | |
27 local function account_dico_info(event) | |
28 (event.reply or event.stanza):tag("feature", {var=xmlns_push}):up() | |
29 end | |
30 module:hook("account-disco-info", account_dico_info); | |
31 | |
32 local function parse_match(matchel) | |
33 local match = { match = matchel.attr.profile } | |
34 local send = matchel:get_child("send", "urn:xmpp:push2:send:notify-only:0") | |
35 if send then | |
36 match.send = send.attr.xmlns | |
37 return match | |
38 end | |
39 | |
40 send = matchel:get_child("send", "urn:xmpp:push2:send:sce+rfc8291+rfc8292:0") | |
41 if send then | |
42 match.send = send.attr.xmlns | |
43 match.ua_public = send:get_child_text("ua-public") | |
44 match.auth_secret = send:get_child_text("auth-secret") | |
45 match.jwt_alg = send:get_child_text("jwt-alg") | |
46 match.jwt_key = send:get_child_text("jwt-key") | |
47 match.jwt_claims = {} | |
48 for claim in send:childtags("jwt-claim") do | |
49 match.jwt_claims[claim.attr.name] = claim:get_text() | |
50 end | |
51 return match | |
52 end | |
53 | |
54 return nil | |
55 end | |
56 | |
57 local function push_enable(event) | |
58 local origin, stanza = event.origin, event.stanza; | |
59 local enable = stanza.tags[1]; | |
60 origin.log("debug", "Attempting to enable push notifications") | |
61 -- MUST contain a jid of the push service being enabled | |
62 local service_jid = enable:get_child_text("service") | |
63 -- MUST contain a string to identify the client fo the push service | |
64 local client = enable:get_child_text("client") | |
65 if not service_jid then | |
66 origin.log("debug", "Push notification enable request missing service") | |
67 origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing service")) | |
68 return true | |
69 end | |
70 if not client then | |
71 origin.log("debug", "Push notification enable request missing client") | |
72 origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing client")) | |
73 return true | |
74 end | |
75 if service_jid == stanza.attr.from then | |
76 origin.log("debug", "Push notification enable request service JID identical to our own") | |
77 origin.send(st.error_reply(stanza, "modify", "bad-request", "JID must be different from ours")) | |
78 return true | |
79 end | |
80 local matches = {} | |
81 for matchel in enable:childtags("match") do | |
82 local match = parse_match(matchel) | |
83 if match then | |
84 matches[#matches + 1] = match | |
85 end | |
86 end | |
87 -- Tie registration to client, via client_id with sasl2 or else fallback to resource | |
88 local registration_id = origin.client_id or origin.resource | |
89 local push_registration = { | |
90 service = service_jid; | |
91 client = client; | |
92 timestamp = os_time(); | |
93 matches = matches; | |
94 }; | |
95 -- TODO: can we move to keyval+ on trunk? | |
96 local registrations = push2_registrations:get(origin.username) or {} | |
97 registrations[registration_id] = push_registration | |
98 if not push2_registrations:set(origin.username, registrations) then | |
99 origin.send(st.error_reply(stanza, "wait", "internal-server-error")); | |
100 else | |
101 origin.push_registration_id = registration_id | |
102 origin.push_registration = push_registration | |
103 origin.first_hibernated_push = nil | |
104 origin.log("info", "Push notifications enabled for %s (%s)", tostring(stanza.attr.from), tostring(service_jid)) | |
105 origin.send(st.reply(stanza)) | |
106 end | |
107 return true | |
108 end | |
109 module:hook("iq-set/self/"..xmlns_push..":enable", push_enable) | |
110 | |
111 -- urgent stanzas should be delivered without delay | |
112 local function is_voip(stanza) | |
113 if stanza.name == "message" then | |
114 if stanza:get_child("propose", "urn:xmpp:jingle-message:0") then | |
115 return true, "jingle call" | |
116 end | |
117 end | |
118 end | |
119 | |
120 local function has_body(stanza) | |
121 -- We can't check for body contents in encrypted messages, so let's treat them as important | |
122 -- Some clients don't even set a body or an empty body for encrypted messages | |
123 | |
124 -- check omemo https://xmpp.org/extensions/inbox/omemo.html | |
125 if stanza:get_child("encrypted", "eu.siacs.conversations.axolotl") or stanza:get_child("encrypted", "urn:xmpp:omemo:0") then return true; end | |
126 | |
127 -- check xep27 pgp https://xmpp.org/extensions/xep-0027.html | |
128 if stanza:get_child("x", "jabber:x:encrypted") then return true; end | |
129 | |
130 -- check xep373 pgp (OX) https://xmpp.org/extensions/xep-0373.html | |
131 if stanza:get_child("openpgp", "urn:xmpp:openpgp:0") then return true; end | |
132 | |
133 local body = stanza:get_child_text("body"); | |
134 | |
135 return body ~= nil and body ~= "" | |
136 end | |
137 | |
138 -- is this push a high priority one | |
139 local function is_important(stanza) | |
140 local is_voip_stanza, urgent_reason = is_voip(stanza) | |
141 if is_voip_stanza then return true; end | |
142 | |
143 local st_name = stanza and stanza.name or nil | |
144 if not st_name then return false; end -- nonzas are never important here | |
145 if st_name == "presence" then | |
146 return false; -- same for presences | |
147 elseif st_name == "message" then | |
148 -- unpack carbon copied message stanzas | |
149 local carbon = stanza:find("{urn:xmpp:carbons:2}/{urn:xmpp:forward:0}/{jabber:client}message") | |
150 local stanza_direction = carbon and stanza:child_with_name("sent") and "out" or "in" | |
151 if carbon then stanza = carbon; end | |
152 local st_type = stanza.attr.type | |
153 | |
154 -- headline message are always not important | |
155 if st_type == "headline" then return false; end | |
156 | |
157 -- carbon copied outgoing messages are not important | |
158 if carbon and stanza_direction == "out" then return false; end | |
159 | |
160 -- groupchat subjects are not important here | |
161 if st_type == "groupchat" and stanza:get_child_text("subject") then | |
162 return false | |
163 end | |
164 | |
165 -- empty bodies are not important | |
166 return has_body(stanza) | |
167 end | |
168 return false; -- this stanza wasn't one of the above cases --> it is not important, too | |
169 end | |
170 | |
171 local function add_sce_rfc8291(match, stanza, push_notification_payload) | |
172 local max_data_size = 2847 -- https://github.com/web-push-libs/web-push-php/issues/108 | |
173 local stanza_clone = st.clone(stanza) | |
174 stanza_clone.attr.xmlns = "jabber:client" | |
175 local envelope = st.stanza("envelope", { xmlns = "urn:xmpp:sce:1" }) | |
176 :tag("content") | |
177 :tag("forwarded", { xmlns = "urn:xmpp:forward:0" }) | |
178 :add_child(stanza_clone) | |
179 :up():up():up() | |
180 local envelope_bytes = tostring(envelope) | |
181 if string.len(envelope_bytes) > max_data_size then | |
182 -- If stanza is too big, remove extra elements | |
183 stanza_clone:maptags(function(el) | |
184 if el.attr.xmlns == nil or | |
185 el.attr.xmlns == "jabber:client" or | |
186 el.attr.xmlns == "jabber:x:oob" or | |
187 (el.attr.xmlns == "urn:xmpp:sid:0" and el.name == "stanza-id") or | |
188 el.attr.xmlns == "eu.siacs.conversations.axolotl" or | |
189 el.attr.xmlns == "urn:xmpp:omemo:0" or | |
190 el.attr.xmlns == "jabber:x:encrypted" or | |
191 el.attr.xmlns == "urn:xmpp:openpgp:0" or | |
192 el.attr.xmlns == "urn:xmpp:sce:1" or | |
193 el.attr.xmlns == "urn:xmpp:jingle-message:0" or | |
194 el.attr.xmlns == "jabber:x:conference" | |
195 then | |
196 return el | |
197 else | |
198 return nil | |
199 end | |
200 end) | |
201 envelope_bytes = tostring(envelope) | |
202 end | |
203 if string.len(envelope_bytes) > max_data_size then | |
204 -- If still too big, get aggressive | |
205 stanza_clone:maptags(function(el) | |
206 if el.name == "body" or | |
207 (el.attr.xmlns == "urn:xmpp:sid:0" and el.name == "stanza-id") or | |
208 el.attr.xmlns == "urn:xmpp:jingle-message:0" or | |
209 el.attr.xmlns == "jabber:x:conference" | |
210 then | |
211 return el | |
212 else | |
213 return nil | |
214 end | |
215 end) | |
216 envelope_bytes = tostring(envelope) | |
217 end | |
218 if string.len(envelope_bytes) < max_data_size/2 then | |
219 envelope:text_tag("rpad", base64.encode(random.bytes(math.min(150, max_data_size/3 - string.len(envelope_bytes))))) | |
220 envelope_bytes = tostring(envelope) | |
221 end | |
222 | |
223 local p256dh_raw = base64.decode(match.ua_public .. "==") | |
224 local p256dh = pkey.new(p256dh_raw, "*", "public", "prime256v1") | |
225 local one_time_key = pkey.new({ type = "EC", curve = "prime256v1" }) | |
226 local one_time_key_public = one_time_key:getParameters().pub_key:toBinary() | |
227 local info = "WebPush: info\0" .. p256dh_raw .. one_time_key_public | |
228 local auth_secret = base64.decode(match.auth_secret .. "==") | |
229 local salt = random.bytes(16) | |
230 local shared_secret = one_time_key:derive(p256dh) | |
231 local ikm = kdf.derive({ | |
232 type = "HKDF", | |
233 outlen = 32, | |
234 salt = auth_secret, | |
235 key = shared_secret, | |
236 info = info, | |
237 md = "sha256" | |
238 }) | |
239 local key = kdf.derive({ | |
240 type = "HKDF", | |
241 outlen = 16, | |
242 salt = salt, | |
243 key = ikm, | |
244 info = "Content-Encoding: aes128gcm\0", | |
245 md = "sha256" | |
246 }) | |
247 local nonce = kdf.derive({ | |
248 type = "HKDF", | |
249 outlen = 12, | |
250 salt = salt, | |
251 key = ikm, | |
252 info = "Content-Encoding: nonce\0", | |
253 md = "sha256" | |
254 }) | |
255 local header = salt .. "\0\0\16\0" .. string.char(string.len(one_time_key_public)) .. one_time_key_public | |
256 local encryptor = ciphers.new("AES-128-GCM"):encrypt(key, nonce) | |
257 | |
258 push_notification_payload | |
259 :tag("encrypted", { xmlns = "urn:xmpp:sce:rfc8291:0" }) | |
260 :text_tag("payload", base64.encode(header .. encryptor:final(envelope_bytes .. "\2") .. encryptor:getTag(16))) | |
261 :up() | |
262 end | |
263 | |
264 local function add_rfc8292(match, stanza, push_notification_payload) | |
265 if not match.jwt_alg then return; end | |
266 local key = match.jwt_key | |
267 if match.jwt_alg ~= "HS256" then | |
268 -- keypairs are in PKCS#8 PEM format without header/footer | |
269 key = "-----BEGIN PRIVATE KEY-----\n"..key.."\n-----END PRIVATE KEY-----" | |
270 end | |
271 | |
272 local signer = jwt.new_signer(match.jwt_alg, key) | |
273 local payload = {} | |
274 for k, v in pairs(match.jwt_claims or {}) do | |
275 payload[k] = v | |
276 end | |
277 payload.sub = contact_uri | |
278 push_notification_payload:text_tag("jwt", signer(payload)) | |
279 end | |
280 | |
281 local function handle_notify_request(stanza, node, user_push_services, log_push_decline) | |
282 local pushes = 0; | |
283 if not #user_push_services then return pushes end | |
284 | |
285 local notify_push_services = {}; | |
286 if is_important(stanza) then | |
287 notify_push_services = user_push_services | |
288 else | |
289 for identifier, push_info in pairs(user_push_services) do | |
290 for _, match in ipairs(push_info.matches) do | |
291 if match.match == "urn:xmpp:push2:match:important" then | |
292 identifier_found.log("debug", "Not pushing because not important") | |
293 else | |
294 notify_push_services[identifier] = push_info; | |
295 end | |
296 end | |
297 end | |
298 end | |
299 | |
300 for push_registration_id, push_info in pairs(notify_push_services) do | |
301 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 | |
302 if stanza then | |
303 if not stanza._push_notify2 then stanza._push_notify2 = {}; end | |
304 if stanza._push_notify2[push_registration_id] then | |
305 if log_push_decline then | |
306 module:log("debug", "Already sent push notification for %s@%s to %s (%s)", node, module.host, push_info.jid, tostring(push_info.node)); | |
307 end | |
308 send_push = false; | |
309 end | |
310 stanza._push_notify2[push_registration_id] = true; | |
311 end | |
312 | |
313 if send_push then | |
314 local push_notification_payload = st.stanza("notification", { xmlns = xmlns_push }) | |
315 push_notification_payload:text_tag("client", push_info.client) | |
316 push_notification_payload:text_tag("priority", is_voip(stanza) and "high" or (is_important(stanza) and "normal" or "low")) | |
317 if is_voip(stanza) then | |
318 push_notification_payload:tag("voip"):up() | |
319 end | |
320 | |
321 local sends_added = {}; | |
322 for _, match in ipairs(push_info.matches) do | |
323 local does_match = false; | |
324 if match.match == "urn:xmpp:push2:match:all" then | |
325 does_match = true | |
326 elseif match.match == "urn:xmpp:push2:match:important" then | |
327 does_match = is_important(stanza) | |
328 elseif match.match == "urn:xmpp:push2:match:archived" then | |
329 does_match = stanza:get_child("stana-id", "urn:xmpp:sid:0") | |
330 elseif match.match == "urn:xmpp:push2:match:archived-with-body" then | |
331 does_match = stanza:get_child("stana-id", "urn:xmpp:sid:0") and has_body(stanza) | |
332 end | |
333 | |
334 if does_match and not sends_added[match.send] then | |
335 sends_added[match.send] = true | |
336 if match.send == "urn:xmpp:push2:send:notify-only" then | |
337 -- Nothing more to add | |
338 elseif match.send == "urn:xmpp:push2:send:sce+rfc8291+rfc8292:0" then | |
339 add_sce_rfc8291(match, stanza, push_notification_payload) | |
340 add_rfc8292(match, stanza, push_notification_payload) | |
341 else | |
342 module:log("debug", "Unkonwn send profile: " .. push_info.send) | |
343 end | |
344 end | |
345 end | |
346 | |
347 local push_publish = st.message({ to = push_info.service, from = module.host, id = uuid.generate() }) | |
348 :add_child(push_notification_payload):up() | |
349 | |
350 -- TODO: watch for message error replies and count or something | |
351 module:send(push_publish) | |
352 pushes = pushes + 1 | |
353 end | |
354 end | |
355 | |
356 return pushes | |
357 end | |
358 | |
359 -- small helper function to extract relevant push settings | |
360 local function get_push_settings(stanza, session) | |
361 local to = stanza.attr.to | |
362 local node = to and jid.split(to) or session.username | |
363 local user_push_services = push2_registrations:get(node) | |
364 return node, (user_push_services or {}) | |
365 end | |
366 | |
367 -- publish on bare groupchat | |
368 -- this picks up MUC messages when there are no devices connected | |
369 module:hook("message/bare/groupchat", function(event) | |
370 local node, user_push_services = get_push_settings(event.stanza, event.origin); | |
371 local notify_push_services = {}; | |
372 for identifier, push_info in pairs(user_push_services) do | |
373 for _, match in ipairs(push_info.matches) do | |
374 if match.match == "urn:xmpp:push2:match:archived-with-body" or match.match == "urn:xmpp:push2:match:archived" then | |
375 identifier_found.log("debug", "Not pushing because we are not archiving this stanza") | |
376 else | |
377 notify_push_services[identifier] = push_info; | |
378 end | |
379 end | |
380 end | |
381 | |
382 handle_notify_request(event.stanza, node, notify_push_services, true); | |
383 end, 1); | |
384 | |
385 local function process_stanza_queue(queue, session, queue_type) | |
386 if not session.push_registration_id then return; end | |
387 for _, match in ipairs(session.push_settings.matches) do | |
388 if match.match == "urn:xmpp:push2:match:archived-with-body" or match.match == "urn:xmpp:push2:match:archived" then | |
389 module:log("debug", "Not pushing because we are not archiving this stanza: %s", session.push_registration_id) | |
390 return | |
391 end | |
392 end | |
393 local user_push_services = {[session.push_registration_id] = session.push_settings}; | |
394 local notified = { unimportant = false; important = false } | |
395 for i=1, #queue do | |
396 local stanza = queue[i]; | |
397 -- fast ignore of already pushed stanzas | |
398 if stanza and not (stanza._push_notify2 and stanza._push_notify2[session.push_registration_id]) then | |
399 local node = get_push_settings(stanza, session); | |
400 local stanza_type = "unimportant"; | |
401 if is_important(stanza) then stanza_type = "important"; end | |
402 if not notified[stanza_type] then -- only notify if we didn't try to push for this stanza type already | |
403 if handle_notify_request(stanza, node, user_push_services, false) ~= 0 then | |
404 if session.hibernating and not session.first_hibernated_push then | |
405 -- if the message was important | |
406 -- then record the time of first push in the session for the smack module which will extend its hibernation | |
407 -- timeout based on the value of session.first_hibernated_push | |
408 if is_important(stanza) then | |
409 session.first_hibernated_push = os_time(); | |
410 -- check for prosody 0.12 mod_smacks | |
411 if session.hibernating_watchdog and session.original_smacks_callback and session.original_smacks_timeout then | |
412 -- restore old smacks watchdog (--> the start of our original timeout will be delayed until first push) | |
413 session.hibernating_watchdog:cancel(); | |
414 session.hibernating_watchdog = watchdog.new(session.original_smacks_timeout, session.original_smacks_callback); | |
415 end | |
416 end | |
417 end | |
418 notified[stanza_type] = true | |
419 end | |
420 end | |
421 end | |
422 if notified.unimportant and notified.important then break; end -- stop processing the queue if all push types are exhausted | |
423 end | |
424 end | |
425 | |
426 -- publish on unacked smacks message (use timer to send out push for all stanzas submitted in a row only once) | |
427 local function process_stanza(session, stanza) | |
428 if session.push_registration_id then | |
429 session.log("debug", "adding new stanza to push_queue"); | |
430 if not session.push_queue then session.push_queue = {}; end | |
431 local queue = session.push_queue; | |
432 queue[#queue+1] = st.clone(stanza); | |
433 if not session.awaiting_push_timer then -- timer not already running --> start new timer | |
434 session.awaiting_push_timer = module:add_timer(1.0, function () | |
435 process_stanza_queue(session.push_queue, session, "push"); | |
436 session.push_queue = {}; -- clean up queue after push | |
437 session.awaiting_push_timer = nil; | |
438 end); | |
439 end | |
440 end | |
441 return stanza; | |
442 end | |
443 | |
444 local function process_smacks_stanza(event) | |
445 local session = event.origin; | |
446 local stanza = event.stanza; | |
447 if not session.push_registration_id then | |
448 session.log("debug", "NOT invoking handle_notify_request() for newly smacks queued stanza (session.push_registration_id is not set: %s)", | |
449 session.push_registration_id | |
450 ); | |
451 else | |
452 process_stanza(session, stanza) | |
453 end | |
454 end | |
455 | |
456 -- smacks hibernation is started | |
457 local function hibernate_session(event) | |
458 local session = event.origin; | |
459 local queue = event.queue; | |
460 session.first_hibernated_push = nil; | |
461 if session.push_registration_id and session.hibernating_watchdog then -- check for prosody 0.12 mod_smacks | |
462 -- save old watchdog callback and timeout | |
463 session.original_smacks_callback = session.hibernating_watchdog.callback; | |
464 session.original_smacks_timeout = session.hibernating_watchdog.timeout; | |
465 -- cancel old watchdog and create a new watchdog with extended timeout | |
466 session.hibernating_watchdog:cancel(); | |
467 session.hibernating_watchdog = watchdog.new(extended_hibernation_timeout, function() | |
468 session.log("debug", "Push-extended smacks watchdog triggered"); | |
469 if session.original_smacks_callback then | |
470 session.log("debug", "Calling original smacks watchdog handler"); | |
471 session.original_smacks_callback(); | |
472 end | |
473 end); | |
474 end | |
475 -- process unacked stanzas | |
476 process_stanza_queue(queue, session, "smacks"); | |
477 end | |
478 | |
479 -- smacks hibernation is ended | |
480 local function restore_session(event) | |
481 local session = event.resumed; | |
482 if session then -- older smacks module versions send only the "intermediate" session in event.session and no session.resumed one | |
483 if session.awaiting_push_timer then | |
484 session.awaiting_push_timer:stop(); | |
485 session.awaiting_push_timer = nil; | |
486 end | |
487 session.first_hibernated_push = nil; | |
488 -- the extended smacks watchdog will be canceled by the smacks module, no need to anything here | |
489 end | |
490 end | |
491 | |
492 -- smacks ack is delayed | |
493 local function ack_delayed(event) | |
494 local session = event.origin; | |
495 local queue = event.queue; | |
496 local stanza = event.stanza; | |
497 if not session.push_registration_id then return; end | |
498 if stanza then process_stanza(session, stanza); return; end -- don't iterate through smacks queue if we know which stanza triggered this | |
499 for i=1, #queue do | |
500 local queued_stanza = queue[i]; | |
501 -- process unacked stanzas (handle_notify_request() will only send push requests for new stanzas) | |
502 process_stanza(session, queued_stanza); | |
503 end | |
504 end | |
505 | |
506 -- archive message added | |
507 local function archive_message_added(event) | |
508 -- event is: { origin = origin, stanza = stanza, for_user = store_user, id = id } | |
509 if not event.for_user then return; end | |
510 -- Note that the stanza in the event is a clone not the same as other hooks, so dedupe doesn't work | |
511 -- This is a problem if you wan to to also hook offline message storage for example | |
512 local stanza = st.clone(event.stanza) | |
513 stanza:tag("stanza-id", { xmlns = "urn:xmpp:sid:0", by = event.for_user.."@"..module.host, id = event.id }):up() | |
514 local user_session = host_sessions[event.for_user] and host_sessions[event.for_user].sessions or {} | |
515 local to = stanza.attr.to | |
516 to = to and jid.split(to) or event.origin.username | |
517 | |
518 -- only notify if the stanza destination is the mam user we store it for | |
519 if event.for_user == to then | |
520 local user_push_services = push2_registrations:get(to) | |
521 | |
522 -- Urgent stanzas are time-sensitive (e.g. calls) and should | |
523 -- be pushed immediately to avoid getting stuck in the smacks | |
524 -- queue in case of dead connections, for example | |
525 local is_voip_stanza, urgent_reason = is_voip(stanza); | |
526 | |
527 local notify_push_services; | |
528 if is_voip_stanza then | |
529 module:log("debug", "Urgent push for %s (%s)", to, urgent_reason); | |
530 notify_push_services = user_push_services; | |
531 else | |
532 -- only notify nodes with no active sessions (smacks is counted as active and handled separate) | |
533 notify_push_services = {}; | |
534 for identifier, push_info in pairs(user_push_services) do | |
535 local identifier_found = nil; | |
536 for _, session in pairs(user_session) do | |
537 if session.push_registration_id == identifier then | |
538 identifier_found = session; | |
539 break; | |
540 end | |
541 end | |
542 if identifier_found then | |
543 identifier_found.log("debug", "Not pushing '%s' of new MAM stanza (session still alive)", identifier) | |
544 elseif not has_body(stanza) then | |
545 for _, match in ipairs(push_info.matches) do | |
546 if match.match == "urn:xmpp:push2:match:archived-with-body" then | |
547 identifier_found.log("debug", "Not pushing '%s' of new MAM stanza (no body)", identifier) | |
548 else | |
549 notify_push_services[identifier] = push_info | |
550 end | |
551 end | |
552 else | |
553 notify_push_services[identifier] = push_info | |
554 end | |
555 end | |
556 end | |
557 | |
558 handle_notify_request(stanza, to, notify_push_services, true); | |
559 end | |
560 end | |
561 | |
562 module:hook("smacks-hibernation-start", hibernate_session); | |
563 module:hook("smacks-hibernation-end", restore_session); | |
564 module:hook("smacks-ack-delayed", ack_delayed); | |
565 module:hook("smacks-hibernation-stanza-queued", process_smacks_stanza); | |
566 module:hook("archive-message-added", archive_message_added); | |
567 | |
568 module:log("info", "Module loaded"); | |
569 function module.unload() | |
570 module:log("info", "Unloading module"); | |
571 -- cleanup some settings, reloading this module can cause process_smacks_stanza() to stop working otherwise | |
572 for user, _ in pairs(host_sessions) do | |
573 for _, session in pairs(host_sessions[user].sessions) do | |
574 if session.awaiting_push_timer then session.awaiting_push_timer:stop(); end | |
575 session.awaiting_push_timer = nil; | |
576 session.push_queue = nil; | |
577 session.first_hibernated_push = nil; | |
578 -- check for prosody 0.12 mod_smacks | |
579 if session.hibernating_watchdog and session.original_smacks_callback and session.original_smacks_timeout then | |
580 -- restore old smacks watchdog | |
581 session.hibernating_watchdog:cancel(); | |
582 session.hibernating_watchdog = watchdog.new(session.original_smacks_timeout, session.original_smacks_callback); | |
583 end | |
584 end | |
585 end | |
586 module:log("info", "Module unloaded"); | |
587 end |