comparison mod_muc_cloud_notify/mod_muc_cloud_notify.lua @ 3883:571249f69577

mod_muc_cloud_notify: Revert last commit
author tmolitor <thilo@eightysoft.de>
date Wed, 05 Feb 2020 23:38:57 +0100
parents 3b8f4f3b1718
children 524a9103fb45
comparison
equal deleted inserted replaced
3882:3b8f4f3b1718 3883:571249f69577
22 local max_push_devices = module:get_option_number("push_max_devices", 5); 22 local max_push_devices = module:get_option_number("push_max_devices", 5);
23 local dummy_body = module:get_option_string("push_notification_important_body", "New Message!"); 23 local dummy_body = module:get_option_string("push_notification_important_body", "New Message!");
24 24
25 local host_sessions = prosody.hosts[module.host].sessions; 25 local host_sessions = prosody.hosts[module.host].sessions;
26 local push_errors = {}; 26 local push_errors = {};
27 local id2room = {} 27 local id2node = {};
28 local id2user = {};
29 28
30 module:depends("muc"); 29 module:depends("muc");
30
31 -- ordered table iterator, allow to iterate on the natural order of the keys of a table,
32 -- see http://lua-users.org/wiki/SortedIteration
33 local function __genOrderedIndex( t )
34 local orderedIndex = {}
35 for key in pairs(t) do
36 table.insert( orderedIndex, key )
37 end
38 -- sort in reverse order (newest one first)
39 table.sort( orderedIndex, function(a, b)
40 if a == nil or t[a] == nil or b == nil or t[b] == nil then return false end
41 -- only one timestamp given, this is the newer one
42 if t[a].timestamp ~= nil and t[b].timestamp == nil then return true end
43 if t[a].timestamp == nil and t[b].timestamp ~= nil then return false end
44 -- both timestamps given, sort normally
45 if t[a].timestamp ~= nil and t[b].timestamp ~= nil then return t[a].timestamp > t[b].timestamp end
46 return false -- normally not reached
47 end)
48 return orderedIndex
49 end
50 local function orderedNext(t, state)
51 -- Equivalent of the next function, but returns the keys in timestamp
52 -- order. We use a temporary ordered key table that is stored in the
53 -- table being iterated.
54
55 local key = nil
56 --print("orderedNext: state = "..tostring(state) )
57 if state == nil then
58 -- the first time, generate the index
59 t.__orderedIndex = __genOrderedIndex( t )
60 key = t.__orderedIndex[1]
61 else
62 -- fetch the next value
63 for i = 1, #t.__orderedIndex do
64 if t.__orderedIndex[i] == state then
65 key = t.__orderedIndex[i+1]
66 end
67 end
68 end
69
70 if key then
71 return key, t[key]
72 end
73
74 -- no more value to return, cleanup
75 t.__orderedIndex = nil
76 return
77 end
78 local function orderedPairs(t)
79 -- Equivalent of the pairs() function on tables. Allows to iterate
80 -- in order
81 return orderedNext, t, nil
82 end
83
84 -- small helper function to return new table with only "maximum" elements containing only the newest entries
85 local function reduce_table(table, maximum)
86 local count = 0;
87 local result = {};
88 for key, value in orderedPairs(table) do
89 count = count + 1;
90 if count > maximum then break end
91 result[key] = value;
92 end
93 return result;
94 end
31 95
32 -- For keeping state across reloads while caching reads 96 -- For keeping state across reloads while caching reads
33 local push_store = (function() 97 local push_store = (function()
34 local store = module:open_store(); 98 local store = module:open_store();
35 local push_services = {}; 99 local push_services = {};
36 local api = {}; 100 local api = {};
37 local function load_room(room) 101 function api:get(user)
38 if not push_services[room] then 102 if not push_services[user] then
39 local err; 103 local err;
40 push_services[room], err = store:get(room); 104 push_services[user], err = store:get(user);
41 if not push_services[room] and err then 105 if not push_services[user] and err then
42 module:log("warn", "Error reading push notification storage for room '%s': %s", room, tostring(err)); 106 module:log("warn", "Error reading push notification storage for user '%s': %s", user, tostring(err));
43 push_services[room] = {}; 107 push_services[user] = {};
44 return false; 108 return push_services[user], false;
45 end 109 end
110 end
111 if not push_services[user] then push_services[user] = {} end
112 return push_services[user], true;
113 end
114 function api:set(user, data)
115 push_services[user] = reduce_table(data, max_push_devices);
116 local ok, err = store:set(user, push_services[user]);
117 if not ok then
118 module:log("error", "Error writing push notification storage for user '%s': %s", user, tostring(err));
119 return false;
46 end 120 end
47 return true; 121 return true;
48 end 122 end
49 function api:get(room, user) 123 function api:set_identifier(user, push_identifier, data)
50 load_room(room); 124 local services = self:get(user);
51 if not push_services[room] then push_services[room] = {}; push_services[room][user] = {}; end 125 services[push_identifier] = data;
52 return push_services[room][user], true; 126 return self:set(user, services);
53 end
54 function api:set(room, user, data)
55 push_services[room][user] = data;
56 local ok, err = store:set(room, push_services[room]);
57 if not ok then
58 module:log("error", "Error writing push notification storage for room '%s' on behalf of user '%s': %s", room, user, tostring(err));
59 return false;
60 end
61 return true;
62 end
63 function api:get_room_users(room)
64 local users = {};
65 load_room(room);
66 for k, v in pairs(push_services[room]) do
67 table.insert(users, k);
68 end
69 return users;
70 end 127 end
71 return api; 128 return api;
72 end)(); 129 end)();
73 130
74 131
76 local handle_push_success, handle_push_error; 133 local handle_push_success, handle_push_error;
77 134
78 function handle_push_error(event) 135 function handle_push_error(event)
79 local stanza = event.stanza; 136 local stanza = event.stanza;
80 local error_type, condition = stanza:get_error(); 137 local error_type, condition = stanza:get_error();
81 local room = id2room[stanza.attr.id]; 138 local node = id2node[stanza.attr.id];
82 local user = id2user[stanza.attr.id]; 139 if node == nil then return false; end -- unknown stanza? Ignore for now!
83 if room == nil or user == nil then return false; end -- unknown stanza? Ignore for now! 140 local from = stanza.attr.from;
84 local push_service = push_store:get(room, user); 141 local user_push_services = push_store:get(node);
85 local push_identifier = room.."<"..user..">"; 142 local changed = false;
86 143
87 local stanza_id = hashes.sha256(push_identifier, true); 144 for push_identifier, _ in pairs(user_push_services) do
88 if stanza_id == stanza.attr.id then 145 local stanza_id = hashes.sha256(push_identifier, true);
89 if push_service and push_service.push_jid == stanza.attr.from and error_type ~= "wait" then 146 if stanza_id == stanza.attr.id then
90 push_errors[push_identifier] = push_errors[push_identifier] + 1; 147 if user_push_services[push_identifier] and user_push_services[push_identifier].jid == from and error_type ~= "wait" then
91 module:log("info", "Got error of type '%s' (%s) for identifier '%s': " 148 push_errors[push_identifier] = push_errors[push_identifier] + 1;
92 .."error count for this identifier is now at %s", error_type, condition, push_identifier, 149 module:log("info", "Got error of type '%s' (%s) for identifier '%s': "
93 tostring(push_errors[push_identifier])); 150 .."error count for this identifier is now at %s", error_type, condition, push_identifier,
94 if push_errors[push_identifier] >= max_push_errors then 151 tostring(push_errors[push_identifier]));
95 module:log("warn", "Disabling push notifications for identifier '%s'", push_identifier); 152 if push_errors[push_identifier] >= max_push_errors then
96 -- save changed global config 153 module:log("warn", "Disabling push notifications for identifier '%s'", push_identifier);
97 push_store:set(room, user, nil); 154 -- remove push settings from sessions
98 push_errors[push_identifier] = nil; 155 if host_sessions[node] then
99 -- unhook iq handlers for this identifier (if possible) 156 for _, session in pairs(host_sessions[node].sessions) do
100 if module.unhook then 157 if session.push_identifier == push_identifier then
101 module:unhook("iq-error/host/"..stanza_id, handle_push_error); 158 session.push_identifier = nil;
102 module:unhook("iq-result/host/"..stanza_id, handle_push_success); 159 session.push_settings = nil;
103 id2room[stanza_id] = nil; 160 session.first_hibernated_push = nil;
104 id2user[stanza_id] = nil; 161 end
162 end
163 end
164 -- save changed global config
165 changed = true;
166 user_push_services[push_identifier] = nil
167 push_errors[push_identifier] = nil;
168 -- unhook iq handlers for this identifier (if possible)
169 if module.unhook then
170 module:unhook("iq-error/host/"..stanza_id, handle_push_error);
171 module:unhook("iq-result/host/"..stanza_id, handle_push_success);
172 id2node[stanza_id] = nil;
173 end
105 end 174 end
106 end 175 elseif user_push_services[push_identifier] and user_push_services[push_identifier].jid == from and error_type == "wait" then
107 elseif push_service and push_service.push_jid == stanza.attr.from and error_type == "wait" then 176 module:log("debug", "Got error of type '%s' (%s) for identifier '%s': "
108 module:log("debug", "Got error of type '%s' (%s) for identifier '%s': " 177 .."NOT increasing error count for this identifier", error_type, condition, push_identifier);
109 .."NOT increasing error count for this identifier", error_type, condition, push_identifier); 178 end
110 end 179 end
180 end
181 if changed then
182 push_store:set(node, user_push_services);
111 end 183 end
112 return true; 184 return true;
113 end 185 end
114 186
115 function handle_push_success(event) 187 function handle_push_success(event)
116 local stanza = event.stanza; 188 local stanza = event.stanza;
117 local room = id2room[stanza.attr.id]; 189 local node = id2node[stanza.attr.id];
118 local user = id2user[stanza.attr.id]; 190 if node == nil then return false; end -- unknown stanza? Ignore for now!
119 if room == nil or user == nil then return false; end -- unknown stanza? Ignore for now! 191 local from = stanza.attr.from;
120 local push_service = push_store:get(room, user); 192 local user_push_services = push_store:get(node);
121 local push_identifier = room.."<"..user..">";
122 193
123 if hashes.sha256(push_identifier, true) == stanza.attr.id then 194 for push_identifier, _ in pairs(user_push_services) do
124 if push_service and push_service.push_jid == stanza.attr.from and push_errors[push_identifier] > 0 then 195 if hashes.sha256(push_identifier, true) == stanza.attr.id then
125 push_errors[push_identifier] = 0; 196 if user_push_services[push_identifier] and user_push_services[push_identifier].jid == from and push_errors[push_identifier] > 0 then
126 -- unhook iq handlers for this identifier (if possible) 197 push_errors[push_identifier] = 0;
127 if module.unhook then 198 module:log("debug", "Push succeeded, error count for identifier '%s' is now at %s again", push_identifier, tostring(push_errors[push_identifier]));
128 module:unhook("iq-error/host/"..stanza.attr.id, handle_push_error); 199 end
129 module:unhook("iq-result/host/"..stanza.attr.id, handle_push_success);
130 id2room[stanza.attr.id] = nil;
131 id2user[stanza.attr.id] = nil;
132 end
133 module:log("debug", "Push succeeded, error count for identifier '%s' is now at %s again", push_identifier, tostring(push_errors[push_identifier]));
134 end 200 end
135 end 201 end
136 return true; 202 return true;
137 end 203 end
138 204
139 -- http://xmpp.org/extensions/xep-xxxx.html#disco 205 -- http://xmpp.org/extensions/xep-0357.html#disco
140 module:hook("muc-disco#info", function(event) 206 local function account_dico_info(event)
141 (event.reply or event.stanza):tag("feature", {var=xmlns_push}):up(); 207 (event.reply or event.stanza):tag("feature", {var=xmlns_push}):up();
142 end); 208 end
209 module:hook("account-disco-info", account_dico_info);
143 210
144 -- http://xmpp.org/extensions/xep-0357.html#enabling 211 -- http://xmpp.org/extensions/xep-0357.html#enabling
145 local function push_enable(event) 212 local function push_enable(event)
146 local origin, stanza = event.origin, event.stanza; 213 local origin, stanza = event.origin, event.stanza;
147 local room = jid.split(stanza.attr.to);
148 local enable = stanza.tags[1]; 214 local enable = stanza.tags[1];
149 origin.log("debug", "Attempting to enable push notifications"); 215 origin.log("debug", "Attempting to enable push notifications");
150 -- MUST contain a 'jid' attribute of the XMPP Push Service being enabled 216 -- MUST contain a 'jid' attribute of the XMPP Push Service being enabled
151 local push_jid = enable.attr.jid; 217 local push_jid = enable.attr.jid;
218 -- SHOULD contain a 'node' attribute
219 local push_node = enable.attr.node;
220 -- CAN contain a 'include_payload' attribute
221 local include_payload = enable.attr.include_payload;
152 if not push_jid then 222 if not push_jid then
153 origin.log("debug", "MUC Push notification enable request missing the 'jid' field"); 223 origin.log("debug", "MUC Push notification enable request missing the 'jid' field");
154 origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing jid")); 224 origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing jid"));
155 return true; 225 return true;
156 end 226 end
157 local publish_options = enable:get_child("x", "jabber:x:data"); 227 local publish_options = enable:get_child("x", "jabber:x:data");
158 if not publish_options then 228 if not publish_options then
159 -- Could be intentional 229 -- Could be intentional
160 origin.log("debug", "No publish options in request"); 230 origin.log("debug", "No publish options in request");
161 end 231 end
232 local push_identifier = push_jid .. "<" .. (push_node or "");
162 local push_service = { 233 local push_service = {
163 push_jid = push_jid; 234 jid = push_jid;
164 device = stanza.attr.from; 235 node = push_node;
236 include_payload = include_payload;
165 options = publish_options and st.preserialize(publish_options); 237 options = publish_options and st.preserialize(publish_options);
166 timestamp = os_time(); 238 timestamp = os_time();
167 }; 239 };
168 240 local ok = push_store:set_identifier(origin.username.."@"..origin.host, push_identifier, push_service);
169 local ok = push_store:set(room, stanza.attr.from, push_service);
170 if not ok then 241 if not ok then
171 origin.send(st.error_reply(stanza, "wait", "internal-server-error")); 242 origin.send(st.error_reply(stanza, "wait", "internal-server-error"));
172 else 243 else
173 origin.log("info", "MUC Push notifications enabled for room %s by %s (%s)", 244 origin.push_identifier = push_identifier;
174 tostring(room), 245 origin.push_settings = push_service;
246 origin.first_hibernated_push = nil;
247 origin.log("info", "MUC Push notifications enabled for %s by %s (%s)",
248 tostring(stanza.attr.to),
175 tostring(stanza.attr.from), 249 tostring(stanza.attr.from),
176 tostring(push_jid) 250 tostring(origin.push_identifier)
177 ); 251 );
178 origin.send(st.reply(stanza)); 252 origin.send(st.reply(stanza));
179 end 253 end
180 return true; 254 return true;
181 end 255 end
183 257
184 258
185 -- http://xmpp.org/extensions/xep-0357.html#disabling 259 -- http://xmpp.org/extensions/xep-0357.html#disabling
186 local function push_disable(event) 260 local function push_disable(event)
187 local origin, stanza = event.origin, event.stanza; 261 local origin, stanza = event.origin, event.stanza;
188 local room = jid.split(stanza.attr.to);
189 local push_jid = stanza.tags[1].attr.jid; -- MUST include a 'jid' attribute 262 local push_jid = stanza.tags[1].attr.jid; -- MUST include a 'jid' attribute
263 local push_node = stanza.tags[1].attr.node; -- A 'node' attribute MAY be included
190 if not push_jid then 264 if not push_jid then
191 origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing jid")); 265 origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing jid"));
192 return true; 266 return true;
193 end 267 end
194 local push_identifier = room.."<"..stanza.attr.from..">"; 268 local user_push_services = push_store:get(origin.username);
195 local push_service = push_store:get(room, stanza.attr.from); 269 for key, push_info in pairs(user_push_services) do
196 local ok = true; 270 if push_info.jid == push_jid and (not push_node or push_info.node == push_node) then
197 if push_service.push_jid == push_jid then 271 origin.log("info", "Push notifications disabled (%s)", tostring(key));
198 origin.log("info", "Push notifications disabled for room %s by %s (%s)", 272 if origin.push_identifier == key then
199 tostring(room), 273 origin.push_identifier = nil;
200 sotring(stanza.attr.from), 274 origin.push_settings = nil;
201 tostring(push_jid) 275 origin.first_hibernated_push = nil;
202 ); 276 end
203 ok = push_store:set(room, stanza.attr.from, nil); 277 user_push_services[key] = nil;
204 push_errors[push_identifier] = nil; 278 push_errors[key] = nil;
205 if module.unhook then 279 if module.unhook then
206 local stanza_id = hashes.sha256(push_identifier, true); 280 module:unhook("iq-error/host/"..key, handle_push_error);
207 module:unhook("iq-error/host/"..stanza_id, handle_push_error); 281 module:unhook("iq-result/host/"..key, handle_push_success);
208 module:unhook("iq-result/host/"..stanza_id, handle_push_success); 282 id2node[key] = nil;
209 id2room[stanza_id] = nil; 283 end
210 id2user[stanza_id] = nil; 284 end
211 end 285 end
212 end 286 local ok = push_store:set(origin.username, user_push_services);
213 if not ok then 287 if not ok then
214 origin.send(st.error_reply(stanza, "wait", "internal-server-error")); 288 origin.send(st.error_reply(stanza, "wait", "internal-server-error"));
215 else 289 else
216 origin.send(st.reply(stanza)); 290 origin.send(st.reply(stanza));
217 end 291 end
289 end 363 end
290 return false; -- this stanza wasn't one of the above cases --> it is not important, too 364 return false; -- this stanza wasn't one of the above cases --> it is not important, too
291 end 365 end
292 366
293 local push_form = dataform { 367 local push_form = dataform {
294 { name = "FORM_TYPE"; type = "hidden"; value = "urn:xmpp:muc_push:summary"; }; 368 { name = "FORM_TYPE"; type = "hidden"; value = "urn:xmpp:push:summary"; };
295 --{ name = "dummy"; type = "text-single"; }; 369 { name = "message-count"; type = "text-single"; };
370 { name = "pending-subscription-count"; type = "text-single"; };
371 { name = "last-message-sender"; type = "jid-single"; };
372 { name = "last-message-body"; type = "text-single"; };
296 }; 373 };
297 374
298 -- http://xmpp.org/extensions/xep-0357.html#publishing 375 -- http://xmpp.org/extensions/xep-0357.html#publishing
299 local function handle_notify_request(stanza, user, user_push_services) 376 local function handle_notify_request(stanza, node, user_push_services, log_push_decline)
300 local pushes = 0; 377 local pushes = 0;
301 if not user_push_services or next(user_push_services) == nil then return pushes end 378 if not user_push_services or next(user_push_services) == nil then return pushes end
379
380 -- XXX: customized
381 local body = stanza:get_child_text("body");
382 if not body then
383 return pushes;
384 end
302 385
303 for push_identifier, push_info in pairs(user_push_services) do 386 for push_identifier, push_info in pairs(user_push_services) do
304 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 387 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 if stanza then 388 if stanza then
306 if not stanza._push_notify then stanza._push_notify = {}; end 389 if not stanza._push_notify then stanza._push_notify = {}; end
307 if stanza._push_notify[push_identifier] then 390 if stanza._push_notify[push_identifier] then
308 if log_push_decline then 391 if log_push_decline then
309 module:log("debug", "Already sent push notification for %s to %s (%s)", user, push_info.push_jid, tostring(push_info.node)); 392 module:log("debug", "Already sent push notification for %s@%s to %s (%s)", node, module.host, push_info.jid, tostring(push_info.node));
310 end 393 end
311 send_push = false; 394 send_push = false;
312 end 395 end
313 stanza._push_notify[push_identifier] = true; 396 stanza._push_notify[push_identifier] = true;
314 end 397 end
372 end 455 end
373 456
374 -- archive message added 457 -- archive message added
375 local function archive_message_added(event) 458 local function archive_message_added(event)
376 -- event is: { origin = origin, stanza = stanza, for_user = store_user, id = id } 459 -- event is: { origin = origin, stanza = stanza, for_user = store_user, id = id }
460 -- only notify for new mam messages when at least one device is online
377 local room = event.room; 461 local room = event.room;
378 local stanza = event.stanza; 462 local stanza = event.stanza;
379 local room_name = jid.split(room.jid); 463 local body = stanza:get_child_text('body');
380 464
381 -- extract all real ocupant jids in room
382 occupants = {};
383 for nick, occupant in room:each_occupant() do
384 for jid in occupant:each_session() do
385 occupants[jid] = true;
386 end
387 end
388
389 -- check all push registered users against occupants list
390 for _, user in pairs(push_store:get_room_users(room_name)) do
391 -- send push if not found in occupants list
392 if not occupants[user] then
393 local push_service = push_store:get(room_name, user);
394 handle_notify_request(event.stanza, user, push_service);
395 end
396 end
397
398
399
400
401 liste der registrierten push user eines raumes durchgehen
402 jeder user der NICHT im muc ist, wird gepusht
403
404
405 handle_notify_request(event.stanza, jid, user_push_services, true);
406
407
408
409 for reference in stanza:childtags("reference", "urn:xmpp:reference:0") do 465 for reference in stanza:childtags("reference", "urn:xmpp:reference:0") do
410 if reference.attr['type'] == 'mention' and reference.attr['begin'] and reference.attr['end'] then 466 if reference.attr['type'] == 'mention' and reference.attr['begin'] and reference.attr['end'] then
411 local nick = extract_reference(body, reference.attr['begin'], reference.attr['end']); 467 local nick = extract_reference(body, reference.attr['begin'], reference.attr['end']);
412 local jid = room:get_registered_jid(nick); 468 local jid = room:get_registered_jid(nick);
413 469
425 end 481 end
426 482
427 module:hook("muc-add-history", archive_message_added); 483 module:hook("muc-add-history", archive_message_added);
428 484
429 local function send_ping(event) 485 local function send_ping(event)
430 local push_services = event.push_services; 486 local user = event.user;
431 if not push_services then 487 local user_push_services = push_store:get(user);
432 local room = event.room; 488 local push_services = event.push_services or user_push_services;
433 local user = event.user;
434 push_services = push_store:get(room, user);
435 end
436 handle_notify_request(nil, user, push_services, true); 489 handle_notify_request(nil, user, push_services, true);
437 end 490 end
438 -- can be used by other modules to ping one or more (or all) push endpoints 491 -- can be used by other modules to ping one or more (or all) push endpoints
439 module:hook("muc-cloud-notify-ping", send_ping); 492 module:hook("cloud-notify-ping", send_ping);
440 493
441 module:log("info", "Module loaded"); 494 module:log("info", "Module loaded");
442 function module.unload() 495 function module.unload()
443 if module.unhook then 496 if module.unhook then
444 module:unhook("account-disco-info", account_dico_info); 497 module:unhook("account-disco-info", account_dico_info);
450 503
451 for push_identifier, _ in pairs(push_errors) do 504 for push_identifier, _ in pairs(push_errors) do
452 local stanza_id = hashes.sha256(push_identifier, true); 505 local stanza_id = hashes.sha256(push_identifier, true);
453 module:unhook("iq-error/host/"..stanza_id, handle_push_error); 506 module:unhook("iq-error/host/"..stanza_id, handle_push_error);
454 module:unhook("iq-result/host/"..stanza_id, handle_push_success); 507 module:unhook("iq-result/host/"..stanza_id, handle_push_success);
455 id2room[stanza_id] = nil; 508 id2node[stanza_id] = nil;
456 id2user[stanza_id] = nil;
457 end 509 end
458 end 510 end
459 511
460 module:log("info", "Module unloaded"); 512 module:log("info", "Module unloaded");
461 end 513 end