# HG changeset patch # User tmolitor # Date 1527526423 -7200 # Node ID 6fce9a935b389460cd9dfd5e9ab5b07921f9ebaa # Parent 6abee021d9dbd53297ad8cac6408ae8debab953b# Parent 2ad35f08bd57e02cb82ec00d51648ed80de4a975 Merge commit diff -r 2ad35f08bd57 -r 6fce9a935b38 mod_cloud_notify/README.markdown --- a/mod_cloud_notify/README.markdown Mon May 28 14:44:53 2018 +0200 +++ b/mod_cloud_notify/README.markdown Mon May 28 18:53:43 2018 +0200 @@ -46,12 +46,13 @@ Configuration ============= - Option Default Description - ------------------------------------ --------- --------------------------------------------------------------------------------------------------------------- - `push_notification_with_body` `false` Whether or not to send the message body to remote pubsub node. - `push_notification_with_sender` `false` Whether or not to send the message sender to remote pubsub node. - `push_max_errors` `50` How much persistent push errors are tolerated before notifications for the identifier in question are disabled - `push_notification_important_body` `` The body text to use when the stanza is important (see above), no message body is sent if this is empty + Option Default Description + ------------------------------------ ----------------- ------------------------------------------------------------------------------------------------------------------- + `push_notification_with_body` `false` Whether or not to send the message body to remote pubsub node. + `push_notification_with_sender` `false` Whether or not to send the message sender to remote pubsub node. + `push_max_errors` `16` How much persistent push errors are tolerated before notifications for the identifier in question are disabled + `push_notification_important_body` `New Message!` The body text to use when the stanza is important (see above), no message body is sent if this is empty + `push_max_devices` `5` The number of allowed devices per user (the oldest devices are automatically removed if this threshold is reached) There are privacy implications for enabling these options because plaintext content and metadata will be shared with centralized servers diff -r 2ad35f08bd57 -r 6fce9a935b38 mod_cloud_notify/mod_cloud_notify.lua --- a/mod_cloud_notify/mod_cloud_notify.lua Mon May 28 14:44:53 2018 +0200 +++ b/mod_cloud_notify/mod_cloud_notify.lua Mon May 28 18:53:43 2018 +0200 @@ -1,12 +1,13 @@ -- XEP-0357: Push (aka: My mobile OS vendor won't let me have persistent TCP connections) -- Copyright (C) 2015-2016 Kim Alvefur --- Copyright (C) 2017 Thilo Molitor +-- Copyright (C) 2017-2018 Thilo Molitor -- -- This file is MIT/X11 licensed. local t_insert = table.insert; local s_match = string.match; local s_sub = string.sub; +local os_time = os.time; local st = require"util.stanza"; local jid = require"util.jid"; local dataform = require"util.dataforms".new; @@ -18,13 +19,79 @@ -- configuration local include_body = module:get_option_boolean("push_notification_with_body", false); local include_sender = module:get_option_boolean("push_notification_with_sender", false); -local max_push_errors = module:get_option_number("push_max_errors", 50); -local dummy_body = module:get_option_string("push_notification_important_body", ""); +local max_push_errors = module:get_option_number("push_max_errors", 16); +local max_push_devices = module:get_option_number("push_max_devices", 5); +local dummy_body = module:get_option_string("push_notification_important_body", "New Message!"); local host_sessions = prosody.hosts[module.host].sessions; local push_errors = {}; local id2node = {}; +-- ordered table iterator, allow to iterate on the natural order of the keys of a table, +-- see http://lua-users.org/wiki/SortedIteration +local function __genOrderedIndex( t ) + local orderedIndex = {} + for key in pairs(t) do + table.insert( orderedIndex, key ) + end + -- sort in reverse order (newest one first) + table.sort( orderedIndex, function(a, b) + if a == nil or t[a] == nil or b == nil or t[b] == nil then return false end + -- only one timestamp given, this is the newer one + if t[a].timestamp ~= nil and t[b].timestamp == nil then return true end + if t[a].timestamp == nil and t[b].timestamp ~= nil then return false end + -- both timestamps given, sort normally + if t[a].timestamp ~= nil and t[b].timestamp ~= nil then return t[a].timestamp > t[b].timestamp end + return false -- normally not reached + end) + return orderedIndex +end +local function orderedNext(t, state) + -- Equivalent of the next function, but returns the keys in timestamp + -- order. We use a temporary ordered key table that is stored in the + -- table being iterated. + + local key = nil + --print("orderedNext: state = "..tostring(state) ) + if state == nil then + -- the first time, generate the index + t.__orderedIndex = __genOrderedIndex( t ) + key = t.__orderedIndex[1] + else + -- fetch the next value + for i = 1,table.getn(t.__orderedIndex) do + if t.__orderedIndex[i] == state then + key = t.__orderedIndex[i+1] + end + end + end + + if key then + return key, t[key] + end + + -- no more value to return, cleanup + t.__orderedIndex = nil + return +end +local function orderedPairs(t) + -- Equivalent of the pairs() function on tables. Allows to iterate + -- in order + return orderedNext, t, nil +end + +-- small helper function to return new table with only "maximum" elements containing only the newest entries +local function reduce_table(table, maximum) + local count = 0; + local result = {}; + for key, value in orderedPairs(table) do + count = count + 1; + if count > maximum then break end + result[key] = value; + end + return result; +end + -- For keeping state across reloads while caching reads local push_store = (function() local store = module:open_store(); @@ -44,7 +111,7 @@ return push_services[user], true; end function api:set(user, data) - push_services[user] = data; + push_services[user] = reduce_table(data, max_push_devices); local ok, err = store:set(user, push_services[user]); if not ok then module:log("error", "Error writing push notification storage for user '%s': %s", user, tostring(err)); @@ -160,6 +227,7 @@ node = push_node; include_payload = include_payload; options = publish_options and st.preserialize(publish_options); + timestamp = os_time(); }; local ok = push_store:set_identifier(origin.username, push_identifier, push_service); if not ok then @@ -418,7 +486,7 @@ -- archive message added local function archive_message_added(event) -- event is: { origin = origin, stanza = stanza, for_user = store_user, id = id } - -- only notify for new mam messages when at least one device is only + -- only notify for new mam messages when at least one device is online if not event.for_user or not host_sessions[event.for_user] then return; end local stanza = event.stanza; local user_session = host_sessions[event.for_user].sessions; diff -r 2ad35f08bd57 -r 6fce9a935b38 mod_delay/mod_delay.lua --- a/mod_delay/mod_delay.lua Mon May 28 14:44:53 2018 +0200 +++ b/mod_delay/mod_delay.lua Mon May 28 18:53:43 2018 +0200 @@ -1,3 +1,9 @@ +-- Copyright (C) 2016-2017 Thilo Molitor +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + local add_filter = require "util.filters".add_filter; local remove_filter = require "util.filters".remove_filter; local datetime = require "util.datetime"; @@ -23,7 +29,12 @@ module:hook("resource-bind", function(event) add_filter(event.session, "stanzas/in", add_delay, 1); end); - +module:hook("smacks-hibernation-end", function(event) + -- older smacks module versions send only the "intermediate" session in event.session and no session.resumed one + if event.resumed then + add_filter(event.resumed, "stanzas/in", add_delay, 1); + end +end); module:hook("pre-resource-unbind", function (event) remove_filter(event.session, "stanzas/in", add_delay); end);