local filters = require " prosody.util.filters " ;
local jid = require " prosody.util.jid " ;
local set = require " prosody.util.set " ;
local client_watchers = { } ;
-- active_filters[session] = {
-- filter_func = filter_func;
-- downstream = { cb1, cb2, ... };
-- }
local active_filters = { } ;
local function subscribe_session_stanzas ( session , handler , reason )
if active_filters [ session ] then
table.insert ( active_filters [ session ] . downstream , handler ) ;
if reason then
handler ( reason , nil , session ) ;
end
return ;
end
local downstream = { handler } ;
active_filters [ session ] = {
filter_in = function ( stanza )
module : log ( " debug " , " NOTIFY WATCHER %d " , # downstream ) ;
for i = 1 , # downstream do
downstream [ i ] ( " received " , stanza , session ) ;
end
return stanza ;
end ;
filter_out = function ( stanza )
module : log ( " debug " , " NOTIFY WATCHER %d " , # downstream ) ;
for i = 1 , # downstream do
downstream [ i ] ( " sent " , stanza , session ) ;
end
return stanza ;
end ;
downstream = downstream ;
} ;
filters.add_filter ( session , " stanzas/in " , active_filters [ session ] . filter_in ) ;
filters.add_filter ( session , " stanzas/out " , active_filters [ session ] . filter_out ) ;
if reason then
handler ( reason , nil , session ) ;
end
end
local function unsubscribe_session_stanzas ( session , handler , reason )
local active_filter = active_filters [ session ] ;
if not active_filter then
return ;
end
for i = # active_filter.downstream , 1 , - 1 do
if active_filter.downstream [ i ] == handler then
table.remove ( active_filter.downstream , i ) ;
if reason then
handler ( reason , nil , session ) ;
end
end
end
if # active_filter.downstream == 0 then
filters.remove_filter ( session , " stanzas/in " , active_filter.filter_in ) ;
filters.remove_filter ( session , " stanzas/out " , active_filter.filter_out ) ;
end
active_filters [ session ] = nil ;
end
local function unsubscribe_all_from_session ( session , reason )
local active_filter = active_filters [ session ] ;
if not active_filter then
return ;
end
for i = # active_filter.downstream , 1 , - 1 do
local handler = table.remove ( active_filter.downstream , i ) ;
if reason then
handler ( reason , nil , session ) ;
end
end
filters.remove_filter ( session , " stanzas/in " , active_filter.filter_in ) ;
filters.remove_filter ( session , " stanzas/out " , active_filter.filter_out ) ;
active_filters [ session ] = nil ;
end
local function unsubscribe_handler_from_all ( handler , reason )
for session in pairs ( active_filters ) do
unsubscribe_session_stanzas ( session , handler , reason ) ;
end
end
local s2s_watchers = { } ;
module : hook ( " s2sin-established " , function ( event )
for _ , watcher in ipairs ( s2s_watchers ) do
if watcher.target_spec == event.session . from_host then
subscribe_session_stanzas ( event.session , watcher.handler , " opened " ) ;
end
end
end ) ;
module : hook ( " s2sout-established " , function ( event )
for _ , watcher in ipairs ( s2s_watchers ) do
if watcher.target_spec == event.session . to_host then
subscribe_session_stanzas ( event.session , watcher.handler , " opened " ) ;
end
end
end ) ;
module : hook ( " s2s-closed " , function ( event )
unsubscribe_all_from_session ( event.session , " closed " ) ;
end ) ;
local watched_hosts = set.new ( ) ;
local handler_map = setmetatable ( { } , { __mode = " kv " } ) ;
local function add_stanza_watcher ( spec , orig_handler )
local function filtering_handler ( event_type , stanza , session )
if stanza and spec.filter_spec then
if spec.filter_spec . with_jid then
if event_type == " sent " and ( not stanza.attr . from or not jid.compare ( stanza.attr . from , spec.filter_spec . with_jid ) ) then
return ;
elseif event_type == " received " and ( not stanza.attr . to or not jid.compare ( stanza.attr . to , spec.filter_spec . with_jid ) ) then
return ;
end
end
end
return orig_handler ( event_type , stanza , session ) ;
end
handler_map [ orig_handler ] = filtering_handler ;
if spec.target_spec . jid then
local target_is_remote_host = not jid.node ( spec.target_spec . jid ) and not prosody.hosts [ spec.target_spec . jid ] ;
if target_is_remote_host then
-- Watch s2s sessions
table.insert ( s2s_watchers , {
target_spec = spec.target_spec . jid ;
handler = filtering_handler ;
orig_handler = orig_handler ;
} ) ;
-- Scan existing s2sin for matches
for session in pairs ( prosody.incoming_s2s ) do
if spec.target_spec . jid == session.from_host then
subscribe_session_stanzas ( session , filtering_handler , " attached " ) ;
end
end
-- Scan existing s2sout for matches
for local_host , local_session in pairs ( prosody.hosts ) do --luacheck: ignore 213/local_host
for remote_host , remote_session in pairs ( local_session.s2sout ) do
if spec.target_spec . jid == remote_host then
subscribe_session_stanzas ( remote_session , filtering_handler , " attached " ) ;
end
end
end
else
table.insert ( client_watchers , {
target_spec = spec.target_spec . jid ;
handler = filtering_handler ;
orig_handler = orig_handler ;
} ) ;
local host = jid.host ( spec.target_spec . jid ) ;
if not watched_hosts : contains ( host ) and prosody.hosts [ host ] then
module : context ( host ) : hook ( " resource-bind " , function ( event )
for _ , watcher in ipairs ( client_watchers ) do
module : log ( " debug " , " NEW CLIENT: %s vs %s " , event.session . full_jid , watcher.target_spec ) ;
if jid.compare ( event.session . full_jid , watcher.target_spec ) then
module : log ( " debug " , " MATCH " ) ;
subscribe_session_stanzas ( event.session , watcher.handler , " opened " ) ;
else
module : log ( " debug " , " NO MATCH " ) ;
end
end
end ) ;
module : context ( host ) : hook ( " resource-unbind " , function ( event )
unsubscribe_all_from_session ( event.session , " closed " ) ;
end ) ;
watched_hosts : add ( host ) ;
end
for full_jid , session in pairs ( prosody.full_sessions ) do
if jid.compare ( full_jid , spec.target_spec . jid ) then
subscribe_session_stanzas ( session , filtering_handler , " attached " ) ;
end
end
end
else
error ( " No recognized target selector " ) ;
end
end
local function remove_stanza_watcher ( orig_handler )
local handler = handler_map [ orig_handler ] ;
unsubscribe_handler_from_all ( handler , " detached " ) ;
handler_map [ orig_handler ] = nil ;
for i = # client_watchers , 1 , - 1 do
if client_watchers [ i ] . orig_handler == orig_handler then
table.remove ( client_watchers , i ) ;
end
end
for i = # s2s_watchers , 1 , - 1 do
if s2s_watchers [ i ] . orig_handler == orig_handler then
table.remove ( s2s_watchers , i ) ;
end
end
end
local function cleanup ( reason )
client_watchers = { } ;
s2s_watchers = { } ;
for session in pairs ( active_filters ) do
unsubscribe_all_from_session ( session , reason or " cancelled " ) ;
end
end
return {
add = add_stanza_watcher ;
remove = remove_stanza_watcher ;
cleanup = cleanup ;
} ;