mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-24 16:23:40 +01:00
Support XEP-0352: Client State Indication
This commit is contained in:
parent
f723c00762
commit
b8c98232b8
@ -66,6 +66,7 @@
|
||||
\newcommand{\module}[1]{\texttt{#1}}
|
||||
\newcommand{\modadhoc}{\module{mod\_adhoc}}
|
||||
\newcommand{\modannounce}{\module{mod\_announce}}
|
||||
\newcommand{\modclientstate}{\module{mod\_client\_state}}
|
||||
\newcommand{\modblocking}{\module{mod\_blocking}}
|
||||
\newcommand{\modcaps}{\module{mod\_caps}}
|
||||
\newcommand{\modcarboncopy}{\module{mod\_carboncopy}}
|
||||
@ -2781,6 +2782,7 @@ The following table lists all modules included in \ejabberd{}.
|
||||
\hline \modblocking{} & Simple Communications Blocking (\xepref{0191}) & \modprivacy{} \\
|
||||
\hline \modcaps{} & Entity Capabilities (\xepref{0115}) & \\
|
||||
\hline \modcarboncopy{} & Message Carbons (\xepref{0280}) & \\
|
||||
\hline \ahrefloc{modclientstate}{\modclientstate{}} & Filter stanzas for inactive clients & \\
|
||||
\hline \modconfigure{} & Server configuration using Ad-Hoc & \modadhoc{} \\
|
||||
\hline \ahrefloc{moddisco}{\moddisco{}} & Service Discovery (\xepref{0030}) & \\
|
||||
\hline \ahrefloc{modecho}{\modecho{}} & Echoes XMPP stanzas & \\
|
||||
@ -3001,6 +3003,38 @@ Note that \modannounce{} can be resource intensive on large
|
||||
deployments as it can broadcast lot of messages. This module should be
|
||||
disabled for instances of \ejabberd{} with hundreds of thousands users.
|
||||
|
||||
\makesubsection{modclientstate}{\modclientstate{}}
|
||||
\ind{modules!\modclientstate{}}\ind{Client State Indication}
|
||||
\ind{protocols!XEP-0352: Client State Indication}
|
||||
|
||||
This module allows for queueing or dropping certain types of stanzas
|
||||
when a client indicates that the user is not actively using the client
|
||||
at the moment (see \xepref{0352}). This can save bandwidth and
|
||||
resources.
|
||||
|
||||
Options:
|
||||
\begin{description}
|
||||
\titem{drop\_chat\_states: true|false} \ind{options!drop\_chat\_states}
|
||||
Drop most "standalone" Chat State Notifications (as defined in
|
||||
\xepref{0085}) while a client indicates inactivity. The default value
|
||||
is \term{false}.
|
||||
\titem{queue\_presence: true|false} \ind{options!queue\_presence}
|
||||
While a client is inactive, queue presence stanzas that indicate
|
||||
(un)availability. The latest queued stanza of each contact is
|
||||
delivered as soon as the client becomes active again. The default
|
||||
value is \term{false}.
|
||||
\end{description}
|
||||
|
||||
Example:
|
||||
\begin{verbatim}
|
||||
modules:
|
||||
...
|
||||
mod_client_state:
|
||||
drop_chat_states: true
|
||||
queue_presence: true
|
||||
...
|
||||
\end{verbatim}
|
||||
|
||||
\makesubsection{moddisco}{\moddisco{}}
|
||||
\ind{modules!\moddisco{}}
|
||||
\ind{protocols!XEP-0030: Service Discovery}
|
||||
|
@ -558,6 +558,9 @@ modules:
|
||||
mod_blocking: {} # requires mod_privacy
|
||||
mod_caps: {}
|
||||
mod_carboncopy: {}
|
||||
mod_client_state:
|
||||
drop_chat_states: true
|
||||
queue_presence: false
|
||||
mod_configure: {} # requires mod_adhoc
|
||||
mod_disco: {}
|
||||
## mod_echo: {}
|
||||
|
@ -147,5 +147,6 @@
|
||||
-define(NS_CARBONS_2, <<"urn:xmpp:carbons:2">>).
|
||||
-define(NS_CARBONS_1, <<"urn:xmpp:carbons:1">>).
|
||||
-define(NS_FORWARD, <<"urn:xmpp:forward:0">>).
|
||||
-define(NS_CLIENT_STATE, <<"urn:xmpp:csi:0">>).
|
||||
-define(NS_STREAM_MGMT_2, <<"urn:xmpp:sm:2">>).
|
||||
-define(NS_STREAM_MGMT_3, <<"urn:xmpp:sm:3">>).
|
||||
|
@ -108,6 +108,8 @@
|
||||
auth_module = unknown,
|
||||
ip,
|
||||
aux_fields = [],
|
||||
csi_state = active,
|
||||
csi_queue = [],
|
||||
mgmt_state,
|
||||
mgmt_xmlns,
|
||||
mgmt_queue,
|
||||
@ -475,6 +477,10 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
ClientStateFeature =
|
||||
[#xmlel{name = <<"csi">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}],
|
||||
children = []}],
|
||||
StreamFeatures = [#xmlel{name = <<"bind">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_BIND}],
|
||||
children = []},
|
||||
@ -484,6 +490,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
++
|
||||
RosterVersioningFeature ++
|
||||
StreamManagementFeature ++
|
||||
ClientStateFeature ++
|
||||
ejabberd_hooks:run_fold(c2s_stream_features,
|
||||
Server, [], [Server]),
|
||||
send_element(StateData,
|
||||
@ -1165,6 +1172,17 @@ wait_for_session(closed, StateData) ->
|
||||
session_established({xmlstreamelement, #xmlel{name = Name} = El}, StateData)
|
||||
when ?IS_STREAM_MGMT_TAG(Name) ->
|
||||
fsm_next_state(session_established, dispatch_stream_mgmt(El, StateData));
|
||||
session_established({xmlstreamelement,
|
||||
#xmlel{name = <<"active">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}]}},
|
||||
StateData) ->
|
||||
NewStateData = csi_queue_flush(StateData),
|
||||
fsm_next_state(session_established, NewStateData#state{csi_state = active});
|
||||
session_established({xmlstreamelement,
|
||||
#xmlel{name = <<"inactive">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}]}},
|
||||
StateData) ->
|
||||
fsm_next_state(session_established, StateData#state{csi_state = inactive});
|
||||
session_established({xmlstreamelement, El},
|
||||
StateData) ->
|
||||
FromJID = StateData#state.jid,
|
||||
@ -1855,6 +1873,8 @@ send_element(StateData, El) when StateData#state.xml_socket ->
|
||||
send_element(StateData, El) ->
|
||||
send_text(StateData, xml:element_to_binary(El)).
|
||||
|
||||
send_stanza(StateData, Stanza) when StateData#state.csi_state == inactive ->
|
||||
csi_filter_stanza(StateData, Stanza);
|
||||
send_stanza(StateData, Stanza) when StateData#state.mgmt_state == pending ->
|
||||
mgmt_queue_add(StateData, Stanza);
|
||||
send_stanza(StateData, Stanza) when StateData#state.mgmt_state == active ->
|
||||
@ -1869,18 +1889,14 @@ send_stanza(StateData, Stanza) ->
|
||||
send_element(StateData, Stanza),
|
||||
StateData.
|
||||
|
||||
send_packet(StateData, Packet) when StateData#state.mgmt_state == active;
|
||||
StateData#state.mgmt_state == pending ->
|
||||
send_packet(StateData, Packet) ->
|
||||
case is_stanza(Packet) of
|
||||
true ->
|
||||
send_stanza(StateData, Packet);
|
||||
false ->
|
||||
send_element(StateData, Packet),
|
||||
StateData
|
||||
end;
|
||||
send_packet(StateData, Stanza) ->
|
||||
send_element(StateData, Stanza),
|
||||
StateData.
|
||||
end.
|
||||
|
||||
send_header(StateData, Server, Version, Lang)
|
||||
when StateData#state.xml_socket ->
|
||||
@ -2762,9 +2778,11 @@ handle_resume(StateData, Attrs) ->
|
||||
#xmlel{name = <<"r">>,
|
||||
attrs = [{<<"xmlns">>, AttrXmlns}],
|
||||
children = []}),
|
||||
FlushedState = csi_queue_flush(NewState),
|
||||
NewStateData = FlushedState#state{csi_state = active},
|
||||
?INFO_MSG("Resumed session for ~s",
|
||||
[jlib:jid_to_string(NewState#state.jid)]),
|
||||
{ok, NewState};
|
||||
[jlib:jid_to_string(NewStateData#state.jid)]),
|
||||
{ok, NewStateData};
|
||||
{error, El, Msg} ->
|
||||
send_element(StateData, El),
|
||||
?INFO_MSG("Cannot resume session for ~s@~s: ~s",
|
||||
@ -2953,6 +2971,8 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
|
||||
pres_invis = OldStateData#state.pres_invis,
|
||||
privacy_list = OldStateData#state.privacy_list,
|
||||
aux_fields = OldStateData#state.aux_fields,
|
||||
csi_state = OldStateData#state.csi_state,
|
||||
csi_queue = OldStateData#state.csi_queue,
|
||||
mgmt_xmlns = OldStateData#state.mgmt_xmlns,
|
||||
mgmt_queue = OldStateData#state.mgmt_queue,
|
||||
mgmt_timeout = OldStateData#state.mgmt_timeout,
|
||||
@ -2976,6 +2996,71 @@ make_resume_id(StateData) ->
|
||||
{Time, _} = StateData#state.sid,
|
||||
jlib:term_to_base64({StateData#state.resource, Time}).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% XEP-0352
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
csi_filter_stanza(#state{csi_state = CsiState, jid = JID} = StateData,
|
||||
Stanza) ->
|
||||
Action = ejabberd_hooks:run_fold(csi_filter_stanza,
|
||||
StateData#state.server,
|
||||
send, [Stanza]),
|
||||
?DEBUG("Going to ~p stanza for inactive client ~p",
|
||||
[Action, jlib:jid_to_string(JID)]),
|
||||
case Action of
|
||||
queue -> csi_queue_add(StateData, Stanza);
|
||||
drop -> StateData;
|
||||
send ->
|
||||
From = xml:get_tag_attr_s(<<"from">>, Stanza),
|
||||
StateData1 = csi_queue_send(StateData, From),
|
||||
StateData2 = send_stanza(StateData1#state{csi_state = active},
|
||||
Stanza),
|
||||
StateData2#state{csi_state = CsiState}
|
||||
end.
|
||||
|
||||
csi_queue_add(#state{csi_queue = Queue, server = Host} = StateData,
|
||||
#xmlel{children = Els} = Stanza) ->
|
||||
From = xml:get_tag_attr_s(<<"from">>, Stanza),
|
||||
Time = calendar:now_to_universal_time(os:timestamp()),
|
||||
DelayTag = [jlib:timestamp_to_xml(Time, utc,
|
||||
jlib:make_jid(<<"">>, Host, <<"">>),
|
||||
<<"Client Inactive">>)],
|
||||
NewStanza = Stanza#xmlel{children = Els ++ DelayTag},
|
||||
case length(StateData#state.csi_queue) >= csi_max_queue(StateData) of
|
||||
true -> csi_queue_add(csi_queue_flush(StateData), NewStanza);
|
||||
false ->
|
||||
NewQueue = lists:keystore(From, 1, Queue, {From, NewStanza}),
|
||||
StateData#state{csi_queue = NewQueue}
|
||||
end.
|
||||
|
||||
csi_queue_send(#state{csi_queue = Queue, csi_state = CsiState} = StateData,
|
||||
From) ->
|
||||
case lists:keytake(From, 1, Queue) of
|
||||
{value, {From, Stanza}, NewQueue} ->
|
||||
NewStateData = send_stanza(StateData#state{csi_state = active},
|
||||
Stanza),
|
||||
NewStateData#state{csi_queue = NewQueue, csi_state = CsiState};
|
||||
false -> StateData
|
||||
end.
|
||||
|
||||
csi_queue_flush(#state{csi_queue = Queue, csi_state = CsiState, jid = JID} =
|
||||
StateData) ->
|
||||
?DEBUG("Flushing CSI queue for ~s", [jlib:jid_to_string(JID)]),
|
||||
NewStateData =
|
||||
lists:foldl(fun({_From, Stanza}, AccState) ->
|
||||
send_stanza(AccState, Stanza)
|
||||
end, StateData#state{csi_state = active}, Queue),
|
||||
NewStateData#state{csi_queue = [], csi_state = CsiState}.
|
||||
|
||||
%% Make sure we won't push too many messages to the XEP-0198 queue when the
|
||||
%% client becomes 'active' again. Otherwise, the client might not manage to
|
||||
%% acknowledge the message flood in time. Also, don't let the queue grow to
|
||||
%% more than 100 stanzas.
|
||||
csi_max_queue(#state{mgmt_max_queue = infinity}) -> 100;
|
||||
csi_max_queue(#state{mgmt_max_queue = Max}) when Max > 200 -> 100;
|
||||
csi_max_queue(#state{mgmt_max_queue = Max}) when Max < 2 -> 1;
|
||||
csi_max_queue(#state{mgmt_max_queue = Max}) -> Max div 2.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% JID Set memory footprint reduction code
|
||||
%%%----------------------------------------------------------------------
|
||||
|
91
src/mod_client_state.erl
Normal file
91
src/mod_client_state.erl
Normal file
@ -0,0 +1,91 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_client_state.erl
|
||||
%%% Author : Holger Weiss
|
||||
%%% Purpose : Filter stanzas sent to inactive clients (XEP-0352)
|
||||
%%% Created : 11 Sep 2014 by Holger Weiss
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2014 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_client_state).
|
||||
-author('holger@zedat.fu-berlin.de').
|
||||
|
||||
-behavior(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, filter_presence/2, filter_chat_states/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
QueuePresence = gen_mod:get_opt(queue_presence, Opts,
|
||||
fun(true) -> true end, false),
|
||||
DropChatStates = gen_mod:get_opt(drop_chat_states, Opts,
|
||||
fun(true) -> true end, false),
|
||||
if QueuePresence ->
|
||||
ejabberd_hooks:add(csi_filter_stanza, Host, ?MODULE,
|
||||
filter_presence, 50);
|
||||
true -> ok
|
||||
end,
|
||||
if DropChatStates ->
|
||||
ejabberd_hooks:add(csi_filter_stanza, Host, ?MODULE,
|
||||
filter_chat_states, 50);
|
||||
true -> ok
|
||||
end,
|
||||
ok.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
|
||||
filter_presence, 50),
|
||||
ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
|
||||
filter_chat_states, 50),
|
||||
ok.
|
||||
|
||||
filter_presence(_Action, #xmlel{name = <<"presence">>, attrs = Attrs}) ->
|
||||
case xml:get_attr(<<"type">>, Attrs) of
|
||||
{value, Type} when Type /= <<"unavailable">> ->
|
||||
?DEBUG("Got important presence stanza", []),
|
||||
{stop, send};
|
||||
_ ->
|
||||
?DEBUG("Got availability presence stanza", []),
|
||||
{stop, queue}
|
||||
end;
|
||||
filter_presence(Action, _Stanza) -> Action.
|
||||
|
||||
filter_chat_states(_Action, #xmlel{name = <<"message">>} = Stanza) ->
|
||||
%% All XEP-0085 chat states except for <gone/>:
|
||||
ChatStates = [<<"active">>, <<"inactive">>, <<"composing">>, <<"paused">>],
|
||||
Stripped =
|
||||
lists:foldl(fun(ChatState, AccStanza) ->
|
||||
xml:remove_subtags(AccStanza, ChatState,
|
||||
{<<"xmlns">>, ?NS_CHATSTATES})
|
||||
end, Stanza, ChatStates),
|
||||
case Stripped of
|
||||
#xmlel{children = [#xmlel{name = <<"thread">>}]} ->
|
||||
?DEBUG("Got standalone chat state notification", []),
|
||||
{stop, drop};
|
||||
#xmlel{children = []} ->
|
||||
?DEBUG("Got standalone chat state notification", []),
|
||||
{stop, drop};
|
||||
_ ->
|
||||
?DEBUG("Got message with chat state notification", []),
|
||||
{stop, send}
|
||||
end;
|
||||
filter_chat_states(Action, _Stanza) -> Action.
|
Loading…
Reference in New Issue
Block a user