1 -- XEP-0357: Push (aka: My mobile OS vendor won't let me have persistent TCP connections) |
1 -- XEP-0357: Push (aka: My mobile OS vendor won't let me have persistent TCP connections) |
2 -- Copyright (C) 2015-2016 Kim Alvefur |
2 -- Copyright (C) 2015-2016 Kim Alvefur |
3 -- Copyright (C) 2017 Thilo Molitor |
3 -- Copyright (C) 2017-2018 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; |
8 local s_match = string.match; |
9 local s_sub = string.sub; |
9 local s_sub = string.sub; |
|
10 local os_time = os.time; |
10 local st = require"util.stanza"; |
11 local st = require"util.stanza"; |
11 local jid = require"util.jid"; |
12 local jid = require"util.jid"; |
12 local dataform = require"util.dataforms".new; |
13 local dataform = require"util.dataforms".new; |
13 local filters = require"util.filters"; |
14 local filters = require"util.filters"; |
14 local hashes = require"util.hashes"; |
15 local hashes = require"util.hashes"; |
16 local xmlns_push = "urn:xmpp:push:0"; |
17 local xmlns_push = "urn:xmpp:push:0"; |
17 |
18 |
18 -- configuration |
19 -- configuration |
19 local include_body = module:get_option_boolean("push_notification_with_body", false); |
20 local include_body = module:get_option_boolean("push_notification_with_body", false); |
20 local include_sender = module:get_option_boolean("push_notification_with_sender", false); |
21 local include_sender = module:get_option_boolean("push_notification_with_sender", false); |
21 local max_push_errors = module:get_option_number("push_max_errors", 50); |
22 local max_push_errors = module:get_option_number("push_max_errors", 16); |
22 local dummy_body = module:get_option_string("push_notification_important_body", ""); |
23 local max_push_devices = module:get_option_number("push_max_devices", 5); |
|
24 local dummy_body = module:get_option_string("push_notification_important_body", "New Message!"); |
23 |
25 |
24 local host_sessions = prosody.hosts[module.host].sessions; |
26 local host_sessions = prosody.hosts[module.host].sessions; |
25 local push_errors = {}; |
27 local push_errors = {}; |
26 local id2node = {}; |
28 local id2node = {}; |
|
29 |
|
30 -- ordered table iterator, allow to iterate on the natural order of the keys of a table, |
|
31 -- see http://lua-users.org/wiki/SortedIteration |
|
32 local function __genOrderedIndex( t ) |
|
33 local orderedIndex = {} |
|
34 for key in pairs(t) do |
|
35 table.insert( orderedIndex, key ) |
|
36 end |
|
37 -- sort in reverse order (newest one first) |
|
38 table.sort( orderedIndex, function(a, b) |
|
39 if a == nil or t[a] == nil or b == nil or t[b] == nil then return false end |
|
40 -- only one timestamp given, this is the newer one |
|
41 if t[a].timestamp ~= nil and t[b].timestamp == nil then return true end |
|
42 if t[a].timestamp == nil and t[b].timestamp ~= nil then return false end |
|
43 -- both timestamps given, sort normally |
|
44 if t[a].timestamp ~= nil and t[b].timestamp ~= nil then return t[a].timestamp > t[b].timestamp end |
|
45 return false -- normally not reached |
|
46 end) |
|
47 return orderedIndex |
|
48 end |
|
49 local function orderedNext(t, state) |
|
50 -- Equivalent of the next function, but returns the keys in timestamp |
|
51 -- order. We use a temporary ordered key table that is stored in the |
|
52 -- table being iterated. |
|
53 |
|
54 local key = nil |
|
55 --print("orderedNext: state = "..tostring(state) ) |
|
56 if state == nil then |
|
57 -- the first time, generate the index |
|
58 t.__orderedIndex = __genOrderedIndex( t ) |
|
59 key = t.__orderedIndex[1] |
|
60 else |
|
61 -- fetch the next value |
|
62 for i = 1,table.getn(t.__orderedIndex) do |
|
63 if t.__orderedIndex[i] == state then |
|
64 key = t.__orderedIndex[i+1] |
|
65 end |
|
66 end |
|
67 end |
|
68 |
|
69 if key then |
|
70 return key, t[key] |
|
71 end |
|
72 |
|
73 -- no more value to return, cleanup |
|
74 t.__orderedIndex = nil |
|
75 return |
|
76 end |
|
77 local function orderedPairs(t) |
|
78 -- Equivalent of the pairs() function on tables. Allows to iterate |
|
79 -- in order |
|
80 return orderedNext, t, nil |
|
81 end |
|
82 |
|
83 -- small helper function to return new table with only "maximum" elements containing only the newest entries |
|
84 local function reduce_table(table, maximum) |
|
85 local count = 0; |
|
86 local result = {}; |
|
87 for key, value in orderedPairs(table) do |
|
88 count = count + 1; |
|
89 if count > maximum then break end |
|
90 result[key] = value; |
|
91 end |
|
92 return result; |
|
93 end |
27 |
94 |
28 -- For keeping state across reloads while caching reads |
95 -- For keeping state across reloads while caching reads |
29 local push_store = (function() |
96 local push_store = (function() |
30 local store = module:open_store(); |
97 local store = module:open_store(); |
31 local push_services = {}; |
98 local push_services = {}; |
42 end |
109 end |
43 if not push_services[user] then push_services[user] = {} end |
110 if not push_services[user] then push_services[user] = {} end |
44 return push_services[user], true; |
111 return push_services[user], true; |
45 end |
112 end |
46 function api:set(user, data) |
113 function api:set(user, data) |
47 push_services[user] = data; |
114 push_services[user] = reduce_table(data, max_push_devices); |
48 local ok, err = store:set(user, push_services[user]); |
115 local ok, err = store:set(user, push_services[user]); |
49 if not ok then |
116 if not ok then |
50 module:log("error", "Error writing push notification storage for user '%s': %s", user, tostring(err)); |
117 module:log("error", "Error writing push notification storage for user '%s': %s", user, tostring(err)); |
51 return false; |
118 return false; |
52 end |
119 end |
158 local push_service = { |
225 local push_service = { |
159 jid = push_jid; |
226 jid = push_jid; |
160 node = push_node; |
227 node = push_node; |
161 include_payload = include_payload; |
228 include_payload = include_payload; |
162 options = publish_options and st.preserialize(publish_options); |
229 options = publish_options and st.preserialize(publish_options); |
|
230 timestamp = os_time(); |
163 }; |
231 }; |
164 local ok = push_store:set_identifier(origin.username, push_identifier, push_service); |
232 local ok = push_store:set_identifier(origin.username, push_identifier, push_service); |
165 if not ok then |
233 if not ok then |
166 origin.send(st.error_reply(stanza, "wait", "internal-server-error")); |
234 origin.send(st.error_reply(stanza, "wait", "internal-server-error")); |
167 else |
235 else |
416 end |
484 end |
417 |
485 |
418 -- archive message added |
486 -- archive message added |
419 local function archive_message_added(event) |
487 local function archive_message_added(event) |
420 -- event is: { origin = origin, stanza = stanza, for_user = store_user, id = id } |
488 -- event is: { origin = origin, stanza = stanza, for_user = store_user, id = id } |
421 -- only notify for new mam messages when at least one device is only |
489 -- only notify for new mam messages when at least one device is online |
422 if not event.for_user or not host_sessions[event.for_user] then return; end |
490 if not event.for_user or not host_sessions[event.for_user] then return; end |
423 local stanza = event.stanza; |
491 local stanza = event.stanza; |
424 local user_session = host_sessions[event.for_user].sessions; |
492 local user_session = host_sessions[event.for_user].sessions; |
425 local to = stanza.attr.to; |
493 local to = stanza.attr.to; |
426 to = to and jid.split(to) or event.origin.username; |
494 to = to and jid.split(to) or event.origin.username; |