• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

processone / ejabberd / 1296

19 Jan 2026 11:25AM UTC coverage: 33.562% (+0.09%) from 33.468%
1296

push

github

badlop
mod_conversejs: Cosmetic change: sort paths alphabetically

0 of 4 new or added lines in 1 file covered. (0.0%)

11245 existing lines in 174 files now uncovered.

15580 of 46421 relevant lines covered (33.56%)

1074.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

57.38
/src/mod_push_keepalive.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : mod_push_keepalive.erl
3
%%% Author  : Holger Weiss <holger@zedat.fu-berlin.de>
4
%%% Purpose : Keep pending XEP-0198 sessions alive with XEP-0357
5
%%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de>
6
%%%
7
%%%
8
%%% ejabberd, Copyright (C) 2017-2026 ProcessOne
9
%%%
10
%%% This program is free software; you can redistribute it and/or
11
%%% modify it under the terms of the GNU General Public License as
12
%%% published by the Free Software Foundation; either version 2 of the
13
%%% License, or (at your option) any later version.
14
%%%
15
%%% This program is distributed in the hope that it will be useful,
16
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
%%% General Public License for more details.
19
%%%
20
%%% You should have received a copy of the GNU General Public License along
21
%%% with this program; if not, write to the Free Software Foundation, Inc.,
22
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
%%%
24
%%%----------------------------------------------------------------------
25

26
-module(mod_push_keepalive).
27
-author('holger@zedat.fu-berlin.de').
28

29
-behaviour(gen_mod).
30

31
%% gen_mod callbacks.
32
-export([start/2, stop/1, reload/3, mod_opt_type/1, mod_options/1, depends/2]).
33
-export([mod_doc/0]).
34
%% ejabberd_hooks callbacks.
35
-export([ejabberd_started/0, c2s_session_pending/1, c2s_session_resumed/1,
36
         c2s_copy_session/2, c2s_handle_cast/2, c2s_handle_info/2,
37
         c2s_stanza/3]).
38

39
-include("logger.hrl").
40
-include_lib("xmpp/include/xmpp.hrl").
41
-include("translate.hrl").
42

43
-define(PUSH_BEFORE_TIMEOUT_PERIOD, 120000). % 2 minutes.
44

45
-type c2s_state() :: ejabberd_c2s:state().
46

47
%%--------------------------------------------------------------------
48
%% gen_mod callbacks.
49
%%--------------------------------------------------------------------
50
-spec start(binary(), gen_mod:opts()) -> {ok, [gen_mod:registration()]}.
51
start(_Host, _Opts) ->
UNCOV
52
    {ok,
8✔
53
     [{hook, c2s_session_pending, c2s_session_pending, 50},
54
      {hook, c2s_session_resumed, c2s_session_resumed, 50},
55
      {hook, c2s_copy_session, c2s_copy_session, 50},
56
      {hook, c2s_handle_cast, c2s_handle_cast, 40},
57
      {hook, c2s_handle_info, c2s_handle_info, 50},
58
      {hook, c2s_handle_send, c2s_stanza, 50},
59
      %% Wait for ejabberd_pkix before running our ejabberd_started/0, so that we
60
      %% don't initiate s2s connections before certificates are loaded:
61
      {hook, ejabberd_started, ejabberd_started, 90, global}]}.
62

63
-spec stop(binary()) -> ok.
64
stop(_Host) ->
UNCOV
65
    ok.
8✔
66

67
-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
68
reload(_Host, _NewOpts, _OldOpts) ->
69
    ok.
×
70

71
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
72
depends(_Host, _Opts) ->
UNCOV
73
    [{mod_push, hard},
8✔
74
     {mod_client_state, soft},
75
     {mod_stream_mgmt, soft}].
76

77
-spec mod_opt_type(atom()) -> econf:validator().
78
mod_opt_type(resume_timeout) ->
UNCOV
79
    econf:either(
8✔
80
      econf:int(0, 0),
81
      econf:timeout(second));
82
mod_opt_type(wake_on_start) ->
UNCOV
83
    econf:bool();
8✔
84
mod_opt_type(wake_on_timeout) ->
UNCOV
85
    econf:bool().
8✔
86

87
mod_options(_Host) ->
UNCOV
88
    [{resume_timeout, timer:hours(72)},
8✔
89
     {wake_on_start, false},
90
     {wake_on_timeout, true}].
91

92
mod_doc() ->
93
    #{desc =>
×
94
          [?T("This module tries to keep the stream management "
95
              "session (see _`mod_stream_mgmt`_) of a disconnected "
96
              "mobile client alive if the client enabled push "
97
              "notifications for that session. However, the normal "
98
              "session resumption timeout is restored once a push "
99
              "notification is issued, so the session will be closed "
100
              "if the client doesn't respond to push notifications."), "",
101
           ?T("The module depends on _`mod_push`_.")],
102
      opts =>
103
          [{resume_timeout,
104
            #{value => "timeout()",
105
              desc =>
106
                  ?T("This option specifies the period of time until "
107
                     "the session of a disconnected push client times out. "
108
                     "This timeout is only in effect as long as no push "
109
                     "notification is issued. Once that happened, the "
110
                     "resumption timeout configured for _`mod_stream_mgmt`_ "
111
                     "is restored. "
112
                     "The default value is '72' hours.")}},
113
           {wake_on_start,
114
            #{value => "true | false",
115
              desc =>
116
                  ?T("If this option is set to 'true', notifications "
117
                     "are generated for **all** registered push clients "
118
                     "during server startup. This option should not be "
119
                     "enabled on servers with many push clients as it "
120
                     "can generate significant load on the involved push "
121
                     "services and the server itself. "
122
                     "The default value is 'false'.")}},
123
           {wake_on_timeout,
124
            #{value => "true | false",
125
              desc =>
126
                  ?T("If this option is set to 'true', a notification "
127
                     "is generated shortly before the session would time "
128
                     "out as per the 'resume_timeout' option. "
129
                     "The default value is 'true'.")}}]}.
130

131
%%--------------------------------------------------------------------
132
%% Hook callbacks.
133
%%--------------------------------------------------------------------
134
-spec c2s_stanza(c2s_state(), xmpp_element() | xmlel(), term()) -> c2s_state().
135
c2s_stanza(#{push_enabled := true, mgmt_state := pending} = State,
136
           Pkt, _SendResult) ->
UNCOV
137
    case mod_push:is_incoming_chat_msg(Pkt) of
8✔
138
        true ->
UNCOV
139
            maybe_restore_resume_timeout(State);
8✔
140
        false ->
141
            State
×
142
    end;
143
c2s_stanza(State, _Pkt, _SendResult) ->
UNCOV
144
    State.
54,816✔
145

146
-spec c2s_session_pending(c2s_state()) -> c2s_state().
147
c2s_session_pending(#{push_enabled := true, mgmt_queue := Queue} = State) ->
UNCOV
148
    case mod_stream_mgmt:queue_find(fun mod_push:is_incoming_chat_msg/1,
8✔
149
                                    Queue) of
150
        none ->
UNCOV
151
            State1 = maybe_adjust_resume_timeout(State),
8✔
UNCOV
152
            maybe_start_wakeup_timer(State1);
8✔
153
        _Msg ->
154
            State
×
155
    end;
156
c2s_session_pending(State) ->
157
    State.
×
158

159
-spec c2s_session_resumed(c2s_state()) -> c2s_state().
160
c2s_session_resumed(#{push_enabled := true} = State) ->
161
    maybe_restore_resume_timeout(State);
×
162
c2s_session_resumed(State) ->
163
    State.
×
164

165
-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
166
c2s_copy_session(State, #{push_enabled := true,
167
                          push_resume_timeout := ResumeTimeout,
168
                          push_wake_on_timeout := WakeOnTimeout} = OldState) ->
169
    State1 = case maps:find(push_resume_timeout_orig, OldState) of
×
170
                 {ok, Val} ->
171
                     State#{push_resume_timeout_orig => Val};
×
172
                 error ->
173
                     State
×
174
             end,
175
    State1#{push_resume_timeout => ResumeTimeout,
×
176
            push_wake_on_timeout => WakeOnTimeout};
177
c2s_copy_session(State, _) ->
178
    State.
×
179

180
-spec c2s_handle_cast(c2s_state(), any()) -> c2s_state().
181
c2s_handle_cast(#{lserver := LServer} = State, {push_enable, _ID}) ->
UNCOV
182
    ResumeTimeout = mod_push_keepalive_opt:resume_timeout(LServer),
16✔
UNCOV
183
    WakeOnTimeout = mod_push_keepalive_opt:wake_on_timeout(LServer),
16✔
UNCOV
184
    State#{push_resume_timeout => ResumeTimeout,
16✔
185
           push_wake_on_timeout => WakeOnTimeout};
186
c2s_handle_cast(State, push_disable) ->
UNCOV
187
    State1 = maps:remove(push_resume_timeout, State),
8✔
UNCOV
188
    maps:remove(push_wake_on_timeout, State1);
8✔
189
c2s_handle_cast(State, _Msg) ->
190
    State.
×
191

192
-spec c2s_handle_info(c2s_state(), any()) -> c2s_state() | {stop, c2s_state()}.
193
c2s_handle_info(#{push_enabled := true, mgmt_state := pending,
194
                  jid := JID} = State, {timeout, _, push_keepalive}) ->
195
    ?INFO_MSG("Waking ~ts before session times out", [jid:encode(JID)]),
×
196
    mod_push:notify(State, none, undefined),
×
197
    {stop, State};
×
198
c2s_handle_info(State, _) ->
UNCOV
199
    State.
48,808✔
200

201
-spec ejabberd_started() -> ok.
202
ejabberd_started() ->
UNCOV
203
    Pred = fun(Host) ->
8✔
UNCOV
204
                   gen_mod:is_loaded(Host, ?MODULE) andalso
80✔
UNCOV
205
                       mod_push_keepalive_opt:wake_on_start(Host)
8✔
206
           end,
UNCOV
207
    [wake_all(Host) || Host <- ejabberd_config:get_option(hosts), Pred(Host)],
8✔
UNCOV
208
    ok.
8✔
209

210
%%--------------------------------------------------------------------
211
%% Internal functions.
212
%%--------------------------------------------------------------------
213
-spec maybe_adjust_resume_timeout(c2s_state()) -> c2s_state().
214
maybe_adjust_resume_timeout(#{push_resume_timeout := undefined} = State) ->
215
    State;
×
216
maybe_adjust_resume_timeout(#{push_resume_timeout := Timeout} = State) ->
UNCOV
217
    OrigTimeout = mod_stream_mgmt:get_resume_timeout(State),
8✔
UNCOV
218
    ?DEBUG("Adjusting resume timeout to ~B seconds", [Timeout div 1000]),
8✔
UNCOV
219
    State1 = mod_stream_mgmt:set_resume_timeout(State, Timeout),
8✔
UNCOV
220
    State1#{push_resume_timeout_orig => OrigTimeout}.
8✔
221

222
-spec maybe_restore_resume_timeout(c2s_state()) -> c2s_state().
223
maybe_restore_resume_timeout(#{push_resume_timeout_orig := Timeout} = State) ->
UNCOV
224
    ?DEBUG("Restoring resume timeout to ~B seconds", [Timeout div 1000]),
8✔
UNCOV
225
    State1 = mod_stream_mgmt:set_resume_timeout(State, Timeout),
8✔
UNCOV
226
    maps:remove(push_resume_timeout_orig, State1);
8✔
227
maybe_restore_resume_timeout(State) ->
228
    State.
×
229

230
-spec maybe_start_wakeup_timer(c2s_state()) -> c2s_state().
231
maybe_start_wakeup_timer(#{push_wake_on_timeout := true,
232
                           push_resume_timeout := ResumeTimeout} = State)
233
  when is_integer(ResumeTimeout), ResumeTimeout > ?PUSH_BEFORE_TIMEOUT_PERIOD ->
UNCOV
234
    WakeTimeout = ResumeTimeout - ?PUSH_BEFORE_TIMEOUT_PERIOD,
8✔
UNCOV
235
    ?DEBUG("Scheduling wake-up timer to fire in ~B seconds", [WakeTimeout div 1000]),
8✔
UNCOV
236
    erlang:start_timer(WakeTimeout, self(), push_keepalive),
8✔
UNCOV
237
    State;
8✔
238
maybe_start_wakeup_timer(State) ->
239
    State.
×
240

241
-spec wake_all(binary()) -> ok.
242
wake_all(LServer) ->
243
    ?INFO_MSG("Waking all push clients on ~ts", [LServer]),
×
244
    Mod = gen_mod:db_mod(LServer, mod_push),
×
245
    case Mod:lookup_sessions(LServer) of
×
246
        {ok, Sessions} ->
247
            IgnoreResponse = fun(_) -> ok end,
×
248
            lists:foreach(fun({_, PushLJID, Node, XData}) ->
×
249
                                  mod_push:notify(LServer, PushLJID, Node,
×
250
                                                  XData, none, undefined,
251
                                                  IgnoreResponse)
252
                          end, Sessions);
253
        error ->
254
            ok
×
255
    end.
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc