mod_cloud_notify/mod_cloud_notify.lua
changeset 3059 6abee021d9db
parent 3014 7ee59f417c16
child 3082 6b860de18a53
equal deleted inserted replaced
3058:5e94061c1aa7 3059:6abee021d9db
     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;