./ct_report/coverage/mod_presence.COVER.html

1 -module(mod_presence).
2
3 -include_lib("exml/include/exml.hrl").
4 -include("jlib.hrl").
5 -include("mongoose_logger.hrl").
6
7 -behavior(gen_mod).
8
9 -type jid_set() :: gb_sets:set(jid:jid()).
10 -type priority() :: -128..128.
11 -type maybe_priority() :: priority() | undefined.
12 -record(presences_state, {
13 %% We have _subscription to_ these users' presence status;
14 %% i.e. they send us presence updates.
15 %% This comes from the roster.
16 pres_t = gb_sets:new() :: jid_set(),
17 %% We have _subscription from_ these users,
18 %% i.e. they have subscription to us.
19 %% We send them presence updates.
20 %% This comes from the roster, and also to ourself
21 pres_f = gb_sets:new() :: jid_set(),
22 %% We're _available_ to these users,
23 %% i.e. we broadcast presence updates to them.
24 %% This may change throughout the session.
25 pres_a = gb_sets:new() :: jid_set(),
26 %% We are _invisible_ to these users.
27 %% This may change throughout the session.
28 pres_i = gb_sets:new() :: jid_set(),
29 pres_pri = 0 :: priority(),
30 pres_last :: undefined | exml:element(),
31 pres_timestamp :: undefined | integer(), % unix time in microseconds
32 pres_invis = false :: boolean() %% Are we invisible?
33 }).
34 -type presences_state() :: #presences_state{}.
35
36 -export([start/2, stop/1, supported_features/0]).
37 -export([
38 user_send_presence/3,
39 user_receive_presence/3,
40 user_terminate/3,
41 foreign_event/3
42 ]).
43 -export([
44 get/2,
45 is_subscribed_to_my_presence/3,
46 am_i_subscribed_to_presence/3,
47 presence_unavailable_stanza/0,
48 get_presence/1,
49 get_subscribed/1,
50 set_presence/2,
51 maybe_get_handler/1,
52 get_old_priority/1
53 ]).
54
55 -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
56 start(HostType, _Opts) ->
57 402 gen_hook:add_handlers(c2s_hooks(HostType)).
58
59 -spec stop(mongooseim:host_type()) -> ok.
60 stop(HostType) ->
61 402 gen_hook:delete_handlers(c2s_hooks(HostType)).
62
63 -spec supported_features() -> [atom()].
64 supported_features() ->
65 256 [dynamic_domains].
66
67 -spec get_presence(pid()) -> {jid:luser(), jid:lresource(), binary(), binary()}.
68 get_presence(Pid) ->
69 30 mongoose_c2s:call(Pid, ?MODULE, get_presence).
70
71 -spec get_subscribed(pid()) -> [jid:jid()].
72 get_subscribed(Pid) ->
73
:-(
mongoose_c2s:call(Pid, ?MODULE, get_subscribed).
74
75 -spec set_presence(pid(), exml:element()) -> ok.
76 set_presence(Pid, Message) ->
77 9 mongoose_c2s:cast(Pid, ?MODULE, {set_presence, Message}).
78
79 -spec c2s_hooks(mongooseim:host_type()) -> gen_hook:hook_list(mongoose_c2s_hooks:fn()).
80 c2s_hooks(HostType) ->
81 804 [
82 {user_send_presence, HostType, fun ?MODULE:user_send_presence/3, #{}, 50},
83 {user_receive_presence, HostType, fun ?MODULE:user_receive_presence/3, #{}, 50},
84 {user_terminate, HostType, fun ?MODULE:user_terminate/3, #{}, 90},
85 {foreign_event, HostType, fun ?MODULE:foreign_event/3, #{}, 50}
86 ].
87
88 -spec user_send_presence(mongoose_acc:t(), mongoose_c2s_hooks:params(), gen_hook:extra()) ->
89 mongoose_c2s_hooks:result().
90 user_send_presence(Acc, #{c2s_data := StateData}, _Extra) ->
91 5824 {FromJid, ToJid, Packet} = mongoose_acc:packet(Acc),
92 5824 StanzaType = mongoose_acc:stanza_type(Acc),
93 5824 case jid:are_bare_equal(FromJid, ToJid) of
94 true ->
95 4933 Acc1 = presence_update(Acc, FromJid, ToJid, Packet, StateData, StanzaType),
96 4933 {ok, Acc1};
97 _ ->
98 891 Acc1 = presence_track(Acc, FromJid, ToJid, Packet, StateData, StanzaType),
99 891 {ok, Acc1}
100 end.
101
102 -spec user_receive_presence(mongoose_acc:t(), mongoose_c2s_hooks:params(), gen_hook:extra()) ->
103 mongoose_c2s_hooks:result().
104 user_receive_presence(Acc, #{c2s_data := StateData}, _Extra) ->
105 12931 case get_mod_state(StateData) of
106 {error, not_found} ->
107
:-(
{stop, Acc};
108 Presences ->
109 12931 handle_user_received_presence(Acc, Presences, mongoose_acc:stanza_type(Acc))
110 end.
111
112 -spec user_terminate(mongoose_acc:t(), mongoose_c2s_hooks:params(), gen_hook:extra()) ->
113 gen_hook:hook_fn_ret(mongoose_acc:t()).
114 user_terminate(Acc, #{c2s_data := StateData, reason := Reason}, _Extra) ->
115 5599 case get_mod_state(StateData) of
116 834 {error, not_found} -> {ok, Acc};
117 123 #presences_state{pres_last = undefined} -> {ok, Acc};
118 4642 Presences -> handle_user_terminate(Acc, StateData, Presences, Reason)
119 end.
120
121 -spec handle_user_terminate(mongoose_acc:t(), mongoose_c2s:data(), presences_state(), term()) ->
122 gen_hook:hook_fn_ret(mongoose_acc:t()).
123 handle_user_terminate(Acc, StateData, Presences, Reason) ->
124 4642 Jid = mongoose_c2s:get_jid(StateData),
125 4642 Status = close_session_status(Reason),
126 4642 PresenceUnavailable = presence_unavailable_stanza(Status),
127 4642 ParamsAcc = #{from_jid => Jid, to_jid => jid:to_bare(Jid), element => PresenceUnavailable},
128 4642 Acc1 = mongoose_acc:update_stanza(ParamsAcc, Acc),
129 4642 presence_broadcast(Acc1, Presences#presences_state.pres_a),
130 4642 presence_broadcast(Acc1, Presences#presences_state.pres_i),
131 4642 mongoose_hooks:unset_presence_hook(Acc1, Jid, Status),
132 4642 {ok, Acc}.
133
134 -spec foreign_event(Acc, Params, Extra) -> Result when
135 Acc :: mongoose_acc:t(),
136 Params :: mongoose_c2s_hooks:params(),
137 Extra :: gen_hook:extra(),
138 Result :: mongoose_c2s_hooks:result().
139 foreign_event(Acc, #{c2s_data := StateData,
140 event_type := cast,
141 event_tag := mod_roster,
142 event_content := {item, IJID, ISubscription}}, _Extra) ->
143 780 case get_mod_state(StateData) of
144 {error, not_found} ->
145
:-(
{ok, Acc};
146 Presences ->
147 780 {stop, handle_subscription_change(Acc, StateData, IJID, ISubscription, Presences)}
148 end;
149 foreign_event(Acc, #{c2s_data := StateData,
150 event_type := {call, From},
151 event_tag := ?MODULE,
152 event_content := get_presence}, _Extra) ->
153 30 PresLast = case get_mod_state(StateData) of
154 {error, not_found} ->
155
:-(
undefined;
156 #presences_state{pres_last = Value} ->
157 30 Value
158 end,
159 30 #jid{luser = User, lresource = Resource} = mongoose_c2s:get_jid(StateData),
160 30 Reply = {User, Resource, get_showtag(PresLast), get_statustag(PresLast)},
161 30 Acc1 = mongoose_c2s_acc:to_acc(Acc, actions, [{reply, From, Reply}]),
162 30 {stop, Acc1};
163 foreign_event(Acc, #{c2s_data := StateData,
164 event_type := {call, From},
165 event_tag := ?MODULE,
166 event_content := get_subscribed}, _Extra) ->
167
:-(
Subscribed = case get_mod_state(StateData) of
168 {error, not_found} ->
169
:-(
[];
170 #presences_state{pres_f = PresF} ->
171
:-(
gb_sets:to_list(PresF)
172 end,
173
:-(
Acc1 = mongoose_c2s_acc:to_acc(Acc, actions, [{reply, From, Subscribed}]),
174
:-(
{stop, Acc1};
175 foreign_event(Acc, #{c2s_data := StateData,
176 event_type := cast,
177 event_tag := ?MODULE,
178 event_content := {set_presence, Message}}, _Extra) ->
179 9 Acc1 = mongoose_acc:update_stanza(#{element => Message}, Acc),
180 9 {FromJid, ToJid, Packet} = mongoose_acc:packet(Acc1),
181 9 StanzaType = mongoose_acc:stanza_type(Acc1),
182 9 {stop, presence_update(Acc1, FromJid, ToJid, Packet, StateData, StanzaType)};
183 foreign_event(Acc, _Params, _Extra) ->
184 86 {ok, Acc}.
185
186 -spec get_showtag(undefined | exml:element()) -> binary().
187 get_showtag(undefined) ->
188
:-(
<<"unavailable">>;
189 get_showtag(Presence) ->
190 30 case xml:get_path_s(Presence, [{elem, <<"show">>}, cdata]) of
191
:-(
<<>> -> <<"available">>;
192 30 ShowTag -> ShowTag
193 end.
194
195 -spec get_statustag(undefined | exml:element()) -> binary().
196
197 get_statustag(undefined) ->
198
:-(
<<>>;
199 get_statustag(Presence) ->
200 30 xml:get_path_s(Presence, [{elem, <<"status">>}, cdata]).
201
202 -spec handle_subscription_change(
203 mongoose_acc:t(), mongoose_c2s:data(), term(), term(), presences_state()) ->
204 mongoose_acc:t().
205 handle_subscription_change(Acc, StateData, IJID, ISubscription, Presences) ->
206 780 To = jid:make(IJID),
207 780 IsSubscribedToMe = (ISubscription == both) or (ISubscription == from),
208 780 AmISubscribedTo = (ISubscription == both) or (ISubscription == to),
209 780 WasSubscribedToMe = gb_sets:is_element(To, Presences#presences_state.pres_f),
210 780 FSet = case IsSubscribedToMe of
211 true ->
212 262 gb_sets:add_element(To, Presences#presences_state.pres_f);
213 false ->
214 518 gb_sets:del_element(To, Presences#presences_state.pres_f)
215 end,
216 780 TSet = case AmISubscribedTo of
217 true ->
218 268 gb_sets:add_element(To, Presences#presences_state.pres_t);
219 false ->
220 512 gb_sets:del_element(To, Presences#presences_state.pres_t)
221 end,
222 780 case Presences#presences_state.pres_last of
223 undefined ->
224
:-(
NewPresences = Presences#presences_state{pres_f = FSet, pres_t = TSet},
225
:-(
mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences});
226 P ->
227 780 ?LOG_DEBUG(#{what => roster_changed, roster_jid => To,
228 780 acc => Acc, c2s_state => StateData}),
229 780 From = mongoose_c2s:get_jid(StateData),
230 780 IsntInvisible = not Presences#presences_state.pres_invis,
231 780 ImAvailableTo = gb_sets:is_element(To, Presences#presences_state.pres_a),
232 780 ImInvisibleTo = gb_sets:is_element(To, Presences#presences_state.pres_i),
233 780 BecomeAvailable = IsntInvisible and IsSubscribedToMe and not WasSubscribedToMe,
234 780 BecomeUnavailable = not IsSubscribedToMe and WasSubscribedToMe
235 and (ImAvailableTo or ImInvisibleTo),
236 780 case {BecomeAvailable, BecomeUnavailable} of
237 {true, _} ->
238 164 ?LOG_DEBUG(#{what => become_available_to, roster_jid => To,
239 164 acc => Acc, c2s_state => StateData}),
240 164 ejabberd_router:route(From, To, Acc, P),
241 164 A = gb_sets:add_element(To, Presences#presences_state.pres_a),
242 164 NewPresences = Presences#presences_state{pres_a = A, pres_f = FSet, pres_t = TSet},
243 164 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences});
244 {_, true} ->
245 17 ?LOG_DEBUG(#{what => become_unavailable_to, roster_jid => To,
246 17 acc => Acc, c2s_state => StateData}),
247 17 PU = presence_unavailable_stanza(),
248 17 ejabberd_router:route(From, To, Acc, PU),
249 17 I = gb_sets:del_element(To, Presences#presences_state.pres_i),
250 17 A = gb_sets:del_element(To, Presences#presences_state.pres_a),
251 17 NewPresences = Presences#presences_state{pres_i = I, pres_a = A, pres_f = FSet, pres_t = TSet},
252 17 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences});
253 _ ->
254 599 NewPresences = Presences#presences_state{pres_f = FSet, pres_t = TSet},
255 599 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences})
256 end
257 end.
258
259 -spec handle_user_received_presence(mongoose_acc:t(), presences_state(), binary()) ->
260 mongoose_c2s_hooks:result().
261 handle_user_received_presence(Acc, Presences, <<"probe">>) ->
262 4944 {stop, handle_received_probe(Acc, Presences)};
263 handle_user_received_presence(Acc, Presences, <<"error">>) ->
264 56 {ok, handle_received_error(Acc, Presences)};
265 handle_user_received_presence(Acc, _, <<"invisible">>) ->
266 2 {ok, handle_received_invisible(Acc)};
267 handle_user_received_presence(Acc, _, <<"subscribe">>) ->
268 174 {ok, Acc};
269 handle_user_received_presence(Acc, _, <<"subscribed">>) ->
270 164 {ok, Acc};
271 handle_user_received_presence(Acc, _, <<"unsubscribe">>) ->
272 12 {ok, Acc};
273 handle_user_received_presence(Acc, _, <<"unsubscribed">>) ->
274 12 {ok, Acc};
275 handle_user_received_presence(Acc, Presences, _) ->
276 7567 {ok, handle_received_available(Acc, Presences)}.
277
278 -spec handle_received_probe(mongoose_acc:t(), presences_state()) -> mongoose_acc:t().
279 handle_received_probe(Acc, Presences) ->
280 4944 {FromJid, ToJid, _Packet} = mongoose_acc:packet(Acc),
281 4944 BareFromJid = jid:to_bare(FromJid),
282 4944 NewPresences = case am_i_available_to(FromJid, BareFromJid, Presences) of
283 4943 true -> Presences;
284 1 false -> make_available_to(FromJid, BareFromJid, Presences)
285 end,
286 4944 Acc1 = mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}),
287 4944 process_presence_probe(Acc1, NewPresences, FromJid, BareFromJid, ToJid).
288
289 -spec process_presence_probe(mongoose_acc:t(), presences_state(), jid:jid(), jid:jid(), jid:jid()) -> mongoose_acc:t().
290 process_presence_probe(Acc, #presences_state{pres_last = undefined}, _, _, _) ->
291 1 Acc;
292 process_presence_probe(Acc, Presences, FromJid, BareFromJid, ToJid) ->
293 4943 case {should_retransmit_last_presence(FromJid, BareFromJid, Presences),
294 specifically_visible_to(FromJid, Presences)} of
295 {true, _} ->
296 4943 route_probe(Acc, Presences, FromJid, ToJid);
297 {false, true} ->
298
:-(
ejabberd_router:route(ToJid, FromJid, Acc, #xmlel{name = <<"presence">>}),
299
:-(
Acc;
300 _ ->
301
:-(
Acc
302 end.
303
304 -spec route_probe(mongoose_acc:t(), presences_state(), jid:jid(), jid:jid()) -> mongoose_acc:t().
305 route_probe(Acc, Presences, FromJid, ToJid) ->
306 4943 Packet0 = Presences#presences_state.pres_last,
307 4943 TS = Presences#presences_state.pres_timestamp,
308 %% To is the one sending the presence (the target of the probe)
309 4943 Packet1 = jlib:maybe_append_delay(Packet0, ToJid, TS, <<>>),
310 4943 HostType = mongoose_acc:host_type(Acc),
311 4943 Acc2 = mongoose_hooks:presence_probe_hook(HostType, Acc, FromJid, ToJid, self()),
312 %% Don't route a presence probe to oneself
313 4943 case jid:are_equal(FromJid, ToJid) of
314 false ->
315 160 ejabberd_router:route(ToJid, FromJid, Acc2, Packet1),
316 160 Acc;
317 true ->
318 4783 Acc2
319 end.
320
321 -spec handle_received_error(mongoose_acc:t(), presences_state()) -> mongoose_acc:t().
322 handle_received_error(Acc, Presences) ->
323 56 FromJid = mongoose_acc:from_jid(Acc),
324 56 NewA = gb_sets:del_element(FromJid, Presences#presences_state.pres_a),
325 56 NewPresences = Presences#presences_state{pres_a = NewA},
326 56 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}).
327
328 -spec handle_received_invisible(mongoose_acc:t()) -> mongoose_acc:t().
329 handle_received_invisible(Acc) ->
330 2 {FromJid, ToJid, Packet} = mongoose_acc:packet(Acc),
331 2 #xmlel{attrs = Attrs} = Packet,
332 2 Attrs1 = lists:keystore(<<"type">>, 1, Attrs, {<<"type">>, <<"unavailable">>}),
333 2 NewElement = Packet#xmlel{attrs = Attrs1},
334 2 mongoose_acc:update_stanza(
335 #{element => NewElement, from_jid => FromJid, to_jid => ToJid}, Acc).
336
337 -spec handle_received_available(mongoose_acc:t(), presences_state()) -> mongoose_acc:t().
338 handle_received_available(Acc, Presences) ->
339 7567 FromJid = mongoose_acc:from_jid(Acc),
340 7567 BareJid = jid:to_bare(FromJid),
341 7567 case am_i_available_to(FromJid, BareJid, Presences) of
342 true ->
343 6221 Acc;
344 false ->
345 1346 NewPresences = make_available_to(FromJid, BareJid, Presences),
346 1346 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences})
347 end.
348
349 %% @doc User updates his presence (non-directed presence packet)
350 -spec presence_update(
351 mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), undefined | binary()) ->
352 mongoose_acc:t().
353 presence_update(Acc, FromJid, ToJid, Packet, StateData, Available)
354 when Available =:= undefined; Available =:= <<"available">> ->
355 4818 Presences = maybe_get_handler(StateData),
356 4818 presence_update_to_available(Acc, FromJid, ToJid, Packet, StateData, Presences);
357 presence_update(Acc, FromJid, ToJid, Packet, StateData, <<"unavailable">>) ->
358 123 Presences = maybe_get_handler(StateData),
359 123 presence_update_to_unavailable(Acc, FromJid, ToJid, Packet, StateData, Presences);
360 presence_update(Acc, FromJid, ToJid, Packet, StateData, <<"invisible">>) ->
361 1 Presences = maybe_get_handler(StateData),
362 1 presence_update_to_invisible(Acc, FromJid, ToJid, Packet, StateData, Presences);
363
:-(
presence_update(Acc, _, _, _, _, <<"error">>) -> Acc;
364
:-(
presence_update(Acc, _, _, _, _, <<"probe">>) -> Acc;
365
:-(
presence_update(Acc, _, _, _, _, <<"subscribe">>) -> Acc;
366
:-(
presence_update(Acc, _, _, _, _, <<"subscribed">>) -> Acc;
367
:-(
presence_update(Acc, _, _, _, _, <<"unsubscribe">>) -> Acc;
368
:-(
presence_update(Acc, _, _, _, _, <<"unsubscribed">>) -> Acc.
369
370 -spec presence_update_to_available(
371 mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), presences_state()) ->
372 mongoose_acc:t().
373 presence_update_to_available(Acc0, FromJid, ToJid, Packet, StateData, Presences) ->
374 4818 Jid = mongoose_c2s:get_jid(StateData),
375 4818 HostType = mongoose_c2s:get_host_type(StateData),
376 4818 Acc1 = mongoose_hooks:roster_get_subscription_lists(HostType, Acc0, Jid),
377 4818 {Fs0, Ts0, Pending} = mongoose_acc:get(roster, subscription_lists, {[], [], []}, Acc1),
378 4818 Fs = [jid:make(BJ) || BJ <- Fs0],
379 4818 Ts = [jid:make(BJ) || BJ <- Ts0],
380 4818 OldPriority = get_old_priority(Presences),
381 4818 NewPriority = get_priority_from_presence(Packet),
382 4818 Timestamp = erlang:system_time(microsecond),
383 4818 Acc2 = update_priority(Acc1, NewPriority, Packet, StateData),
384 4818 FromUnavail = (Presences#presences_state.pres_last == undefined) or Presences#presences_state.pres_invis,
385 4818 ?LOG_DEBUG(#{what => presence_update_to_available,
386 text => <<"Presence changes from unavailable to available">>,
387 4818 from_unavail => FromUnavail, acc => Acc2, c2s_state => StateData}),
388 4818 BareJid = jid:to_bare(Jid),
389 4818 NewPresences = Presences#presences_state{pres_f = gb_sets:from_list([BareJid | Fs]),
390 pres_t = gb_sets:from_list([BareJid | Ts]),
391 pres_pri = NewPriority,
392 pres_last = Packet,
393 pres_timestamp = Timestamp,
394 pres_invis = false},
395 4818 presence_update_to_available(
396 Acc2, FromJid, ToJid, Packet, StateData, NewPresences, Pending,
397 OldPriority, NewPriority, FromUnavail).
398
399 -spec presence_update_to_unavailable(
400 mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), presences_state()) ->
401 mongoose_acc:t().
402 presence_update_to_unavailable(Acc, _FromJid, _ToJid, Packet, StateData, Presences) ->
403 123 Status = exml_query:path(Packet, [{element, <<"status">>}, cdata], <<>>),
404 123 Sid = mongoose_c2s:get_sid(StateData),
405 123 Jid = mongoose_c2s:get_jid(StateData),
406 123 Info = mongoose_c2s:get_info(StateData),
407 123 Acc1 = ejabberd_sm:unset_presence(Acc, Sid, Jid, Status, Info),
408 123 presence_broadcast(Acc1, Presences#presences_state.pres_a),
409 123 presence_broadcast(Acc1, Presences#presences_state.pres_i),
410 123 NewPresences = Presences#presences_state{pres_last = undefined,
411 pres_timestamp = undefined,
412 pres_a = gb_sets:new(),
413 pres_i = gb_sets:new(),
414 pres_invis = false},
415 123 mongoose_c2s_acc:to_acc(Acc1, state_mod, {?MODULE, NewPresences}).
416
417 -spec presence_update_to_invisible(
418 mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), presences_state()) ->
419 mongoose_acc:t().
420 presence_update_to_invisible(Acc, FromJid, _ToJid, Packet, StateData, Presences) ->
421 1 NewPriority = get_priority_from_presence(Packet),
422 1 Acc1 = update_priority(Acc, NewPriority, Packet, StateData),
423 1 case Presences#presences_state.pres_invis of
424 false ->
425 1 presence_broadcast(Acc1, Presences#presences_state.pres_a),
426 1 presence_broadcast(Acc1, Presences#presences_state.pres_i),
427 1 NewPresences = Presences#presences_state{pres_last = undefined,
428 pres_timestamp = undefined,
429 pres_a = gb_sets:new(),
430 pres_i = gb_sets:new(),
431 pres_invis = true},
432 1 presence_broadcast_first(Acc1, FromJid, Packet, NewPresences, []);
433 true ->
434
:-(
Acc1
435 end.
436
437 %% @doc User sends a directed presence packet
438 -spec presence_track(
439 mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), undefined | binary()) ->
440 mongoose_acc:t().
441 presence_track(Acc, FromJid, ToJid, Packet, StateData, undefined) ->
442 699 Presences = maybe_get_handler(StateData),
443 699 process_presence_track_available(Acc, FromJid, ToJid, Packet, Presences);
444 presence_track(Acc, FromJid, ToJid, Packet, StateData, <<"unavailable">>) ->
445 17 Presences = maybe_get_handler(StateData),
446 17 process_presence_track_unavailable(Acc, FromJid, ToJid, Packet, Presences);
447 presence_track(Acc, FromJid, ToJid, Packet, StateData, <<"invisible">>) ->
448
:-(
Presences = maybe_get_handler(StateData),
449
:-(
process_presence_track_invisible(Acc, FromJid, ToJid, Packet, Presences);
450 presence_track(Acc, FromJid, ToJid, Packet, _, <<"subscribe">>) ->
451 85 process_presence_track_subscription_and_route(Acc, FromJid, ToJid, Packet, subscribe);
452 presence_track(Acc, FromJid, ToJid, Packet, _, <<"subscribed">>) ->
453 82 process_presence_track_subscription_and_route(Acc, FromJid, ToJid, Packet, subscribed);
454 presence_track(Acc, FromJid, ToJid, Packet, _, <<"unsubscribe">>) ->
455 3 process_presence_track_subscription_and_route(Acc, FromJid, ToJid, Packet, unsubscribe);
456 presence_track(Acc, FromJid, ToJid, Packet, _, <<"unsubscribed">>) ->
457 3 process_presence_track_subscription_and_route(Acc, FromJid, ToJid, Packet, unsubscribed);
458 presence_track(Acc, FromJid, ToJid, Packet, _, <<"error">>) ->
459 2 ejabberd_router:route(FromJid, ToJid, Acc, Packet),
460 2 Acc;
461 presence_track(Acc, FromJid, ToJid, Packet, _, <<"probe">>) ->
462
:-(
ejabberd_router:route(FromJid, ToJid, Acc, Packet),
463
:-(
Acc.
464
465 process_presence_track_available(Acc, FromJid, ToJid, Packet, Presences) ->
466 699 ejabberd_router:route(FromJid, ToJid, Acc, Packet),
467 699 I = gb_sets:del_element(ToJid, Presences#presences_state.pres_i),
468 699 A = gb_sets:add_element(ToJid, Presences#presences_state.pres_a),
469 699 NewPresences = Presences#presences_state{pres_i = I, pres_a = A},
470 699 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}).
471
472 process_presence_track_unavailable(Acc, FromJid, ToJid, Packet, Presences) ->
473 17 ejabberd_router:route(FromJid, ToJid, Acc, Packet),
474 17 I = gb_sets:del_element(ToJid, Presences#presences_state.pres_i),
475 17 A = gb_sets:del_element(ToJid, Presences#presences_state.pres_a),
476 17 NewPresences = Presences#presences_state{pres_i = I, pres_a = A},
477 17 mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}).
478
479 process_presence_track_invisible(Acc, FromJid, ToJid, Packet, Presences) ->
480
:-(
ejabberd_router:route(FromJid, ToJid, Acc, Packet),
481
:-(
I = gb_sets:add_element(ToJid, Presences#presences_state.pres_i),
482
:-(
A = gb_sets:del_element(ToJid, Presences#presences_state.pres_a),
483
:-(
NewPresences = Presences#presences_state{pres_i = I, pres_a = A},
484
:-(
mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}).
485
486 process_presence_track_subscription_and_route(Acc, FromJid, ToJid, Packet, Type) ->
487 173 Acc1 = mongoose_hooks:roster_out_subscription(Acc, FromJid, ToJid, Type),
488 173 ejabberd_router:route(jid:to_bare(FromJid), ToJid, Acc1, Packet),
489 173 Acc1.
490
491 -spec presence_broadcast(mongoose_acc:t(), jid_set()) -> mongoose_acc:t().
492 presence_broadcast(Acc, JIDSet) ->
493 9532 From = mongoose_acc:from_jid(Acc),
494 9532 gb_sets:fold(fun(JID, A) ->
495 5546 ejabberd_router:route(From, JID, A)
496 end, Acc, JIDSet).
497
498 -spec presence_update_to_available(
499 mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(),
500 presences_state(), list(), maybe_priority(), priority(), boolean()) ->
501 mongoose_acc:t().
502 presence_update_to_available(Acc, FromJid, _ToJid, Packet, StateData, Presences, SocketSend,
503 _OldPriority, NewPriority, true) ->
504 4784 Acc1 = mongoose_hooks:user_available_hook(Acc, FromJid),
505 4784 Acc2 = case NewPriority >= 0 of
506 true ->
507 4784 resend_offline_messages(Acc1, StateData);
508 false ->
509
:-(
Acc1
510 end,
511 4784 presence_broadcast_first(Acc2, FromJid, Packet, Presences, SocketSend);
512 presence_update_to_available(Acc, FromJid, _ToJid, Packet, StateData, Presences, Pending,
513 OldPriority, NewPriority, false) ->
514 34 presence_broadcast_to_trusted(
515 Acc, FromJid, Presences#presences_state.pres_f, Presences#presences_state.pres_a, Packet),
516 34 Acc1 = case (OldPriority < 0 orelse OldPriority =:= undefined) andalso NewPriority >= 0 of
517 true ->
518
:-(
resend_offline_messages(Acc, StateData);
519 false ->
520 34 Acc
521 end,
522 34 Accs = create_route_accs(Acc1, FromJid, Pending),
523 34 ToAcc = [{route, Accs}, {state_mod, {?MODULE, Presences}}],
524 34 mongoose_c2s_acc:to_acc_many(Acc1, ToAcc).
525
526 -spec presence_broadcast_to_trusted(
527 mongoose_acc:t(), jid:jid(), jid_set(), jid_set(), exml:element()) ->
528 mongoose_acc:t().
529 presence_broadcast_to_trusted(Acc, FromJid, T, A, Packet) ->
530 34 gb_sets:fold(
531 fun(JID, Ac) ->
532 36 case gb_sets:is_element(JID, T) of
533 true ->
534 36 ejabberd_router:route(FromJid, JID, Ac, Packet);
535 _ ->
536
:-(
Ac
537 end
538 end, Acc, A).
539
540 -spec resend_offline_messages(mongoose_acc:t(), mongoose_c2s:data()) -> mongoose_acc:t().
541 resend_offline_messages(Acc, StateData) ->
542 4784 ?LOG_DEBUG(#{what => resend_offline_messages, acc => Acc, c2s_state => StateData}),
543 4784 Jid = mongoose_c2s:get_jid(StateData),
544 4784 Acc1 = mongoose_hooks:resend_offline_messages_hook(Acc, Jid),
545 4784 Rs = mongoose_acc:get(offline, messages, [], Acc1),
546 4784 Acc2 = lists:foldl(fun({route, FromJid, ToJid, MsgAcc}, A) ->
547 134 resend_offline_message(A, FromJid, ToJid, MsgAcc, in);
548 ({route, MsgAcc}, A) ->
549
:-(
{FromJid, ToJid, _} = mongoose_acc:packet(Acc),
550
:-(
resend_offline_message(A, FromJid, ToJid, MsgAcc, in)
551 end, Acc1, Rs),
552 4784 mongoose_acc:delete(offline, messages, Acc2). % they are gone from db backend and sent
553
554 resend_offline_message(Acc0, FromJid, To, Acc, in) ->
555 134 Packet = mongoose_acc:element(Acc),
556 134 NewAcc = strip_c2s_fields(Acc),
557 134 ejabberd_router:route(FromJid, To, NewAcc, Packet),
558 134 Acc0.
559
560 -spec strip_c2s_fields(mongoose_acc:t()) -> mongoose_acc:t().
561 strip_c2s_fields(Acc) ->
562 %% TODO: verify if we really need to strip down these 2 fields
563 134 mongoose_acc:delete_many(c2s, [origin_jid, origin_sid], Acc).
564
565 -spec presence_broadcast_first(
566 mongoose_acc:t(), jid:jid(), exml:element(), presences_state(), [exml:element()]) ->
567 mongoose_acc:t().
568 presence_broadcast_first(Acc0, FromJid, Packet, Presences, SocketSend) ->
569 4785 Probe = presence_probe(),
570 4785 _Acc1 = gb_sets:fold(fun(JID, Accum) ->
571 4790 ejabberd_router:route(FromJid, JID, Accum, Probe)
572 end, Acc0, Presences#presences_state.pres_t),
573 4785 case Presences#presences_state.pres_invis of
574 true ->
575 1 mongoose_c2s_acc:to_acc(Acc0, state_mod, {?MODULE, Presences});
576 false ->
577 4784 {As, _Acc2} = gb_sets:fold(
578 fun(JID, {A, Accum}) ->
579 4790 Accum1 = ejabberd_router:route(FromJid, JID, Accum, Packet),
580 4790 {gb_sets:add_element(JID, A), Accum1}
581 end,
582 {Presences#presences_state.pres_a, Acc0}, Presences#presences_state.pres_f),
583 4784 NewPresences = Presences#presences_state{pres_a = As},
584 4784 Accs = create_route_accs(Acc0, FromJid, SocketSend),
585 4784 ToAcc = [{route, Accs}, {state_mod, {?MODULE, NewPresences}}],
586 4784 mongoose_c2s_acc:to_acc_many(Acc0, ToAcc)
587 end.
588
589 create_route_accs(Acc0, To, List) when is_list(List) ->
590 4818 [ mongoose_acc:update_stanza(#{to_jid => To, element => P}, Acc0)
591 4818 || P <- List ];
592 create_route_accs(Acc0, To, El) ->
593
:-(
create_route_accs(Acc0, To, [El]).
594
595 -spec presence_probe() -> exml:element().
596 presence_probe() ->
597 4785 #xmlel{name = <<"presence">>,
598 attrs = [{<<"type">>, <<"probe">>}]}.
599
600 -spec presence_unavailable_stanza() -> exml:element().
601 presence_unavailable_stanza() ->
602 43 presence_unavailable_stanza(<<>>).
603
604 -spec presence_unavailable_stanza(binary()) -> exml:element().
605 presence_unavailable_stanza(<<>>) ->
606 43 #xmlel{name = <<"presence">>,
607 attrs = [{<<"type">>, <<"unavailable">>}]};
608 presence_unavailable_stanza(Status) ->
609 4642 StatusEl = #xmlel{name = <<"status">>,
610 children = [#xmlcdata{content = Status}]},
611 4642 #xmlel{name = <<"presence">>,
612 attrs = [{<<"type">>, <<"unavailable">>}],
613 children = [StatusEl]}.
614
615 close_session_status(normal) ->
616
:-(
<<>>;
617 close_session_status({shutdown, retries}) ->
618
:-(
<<"Too many attempts">>;
619 close_session_status({shutdown, replaced}) ->
620
:-(
<<"Replaced by new connection">>;
621 close_session_status({shutdown, Reason}) when is_atom(Reason) ->
622 4609 <<"Shutdown by reason: ", (atom_to_binary(Reason))/binary>>;
623 close_session_status({shutdown, Reason}) when is_binary(Reason) ->
624 30 <<"Shutdown by reason: ", Reason/binary>>;
625 close_session_status(_) ->
626 3 <<"Unknown condition">>.
627
628 -spec maybe_get_handler(mongoose_c2s:data()) -> presences_state().
629 maybe_get_handler(StateData) ->
630 5908 case mongoose_c2s:get_mod_state(StateData, ?MODULE) of
631 1118 {ok, #presences_state{} = Presences} -> Presences;
632 4790 {error, not_found} -> #presences_state{}
633 end.
634
635 -spec get_mod_state(mongoose_c2s:data()) -> presences_state() | {error, not_found}.
636 get_mod_state(StateData) ->
637 19340 case mongoose_c2s:get_mod_state(StateData, ?MODULE) of
638 18506 {ok, Presence} -> Presence;
639 834 Error -> Error
640 end.
641
642 -spec get_priority_from_presence(exml:element()) -> priority().
643 get_priority_from_presence(PresencePacket) ->
644 4941 MaybePriority = exml_query:path(PresencePacket, [{element, <<"priority">>}, cdata], undefined),
645 4941 case catch binary_to_integer(MaybePriority) of
646 6 P when is_integer(P), -128 =< P, P =< 128 -> P;
647 4935 _ -> 0
648 end.
649
650 -spec get_old_priority(presences_state()) -> maybe_priority().
651 get_old_priority(Presences) ->
652 4912 case Presences#presences_state.pres_last of
653 4790 undefined -> undefined;
654 122 OldPresence -> get_priority_from_presence(OldPresence)
655 end.
656
657 -spec update_priority(Acc :: mongoose_acc:t(),
658 Priority :: integer(),
659 Packet :: exml:element(),
660 StateData :: mongoose_c2s:data()) -> mongoose_acc:t().
661 update_priority(Acc, Priority, Packet, StateData) ->
662 4819 Sid = mongoose_c2s:get_sid(StateData),
663 4819 Jid = mongoose_c2s:get_jid(StateData),
664 4819 Info = mongoose_c2s:get_info(StateData),
665 4819 ejabberd_sm:set_presence(Acc, Sid, Jid, Priority, Packet, Info).
666
667 am_i_subscribed_to_presence(LJID, LBareJID, S) ->
668 94 gb_sets:is_element(LJID, S#presences_state.pres_t)
669 82 orelse (LJID /= LBareJID)
670 70 andalso gb_sets:is_element(LBareJID, S#presences_state.pres_t).
671
672 -spec am_i_available_to(jid:jid(), jid:jid(), presences_state()) -> boolean().
673 am_i_available_to(FromJid, BareJid, Presences) ->
674 12511 gb_sets:is_element(FromJid, Presences#presences_state.pres_a)
675 11729 orelse (FromJid /= BareJid)
676 11729 andalso gb_sets:is_element(BareJid, Presences#presences_state.pres_a).
677
678 -spec make_available_to(jid:jid(), jid:jid(), presences_state()) -> presences_state().
679 make_available_to(FromJid, BareJid, Presences) ->
680 1347 case gb_sets:is_element(FromJid, Presences#presences_state.pres_f) of
681 true ->
682
:-(
A = gb_sets:add_element(FromJid, Presences#presences_state.pres_a),
683
:-(
Presences#presences_state{pres_a = A};
684 false ->
685 1347 case gb_sets:is_element(BareJid, Presences#presences_state.pres_f) of
686 true ->
687 1 A = gb_sets:add_element(BareJid, Presences#presences_state.pres_a),
688 1 Presences#presences_state{pres_a = A};
689 false ->
690 1346 Presences
691 end
692 end.
693
694 -spec should_retransmit_last_presence(jid:jid(), jid:jid(), presences_state()) -> boolean().
695 should_retransmit_last_presence(FromJid, BareFromJid,
696 #presences_state{pres_invis = Invisible} = Presences) ->
697 4943 not Invisible
698 4943 andalso is_subscribed_to_my_presence(FromJid, BareFromJid, Presences)
699 4943 andalso not invisible_to(FromJid, BareFromJid, Presences).
700
701 -spec is_subscribed_to_my_presence(jid:jid(), jid:jid(), presences_state()) -> boolean().
702 is_subscribed_to_my_presence(FromJid, BareFromJid, Presences) ->
703 5070 gb_sets:is_element(FromJid, Presences#presences_state.pres_f)
704 5064 orelse (FromJid /= BareFromJid)
705 5013 andalso gb_sets:is_element(BareFromJid, Presences#presences_state.pres_f).
706
707 -spec invisible_to(jid:jid(), jid:jid(), presences_state()) -> boolean().
708 invisible_to(FromJid, BareFromJid, Presences) ->
709 4943 gb_sets:is_element(FromJid, Presences#presences_state.pres_i)
710 4943 orelse (FromJid /= BareFromJid)
711 4943 andalso gb_sets:is_element(BareFromJid, Presences#presences_state.pres_i).
712
713 -spec specifically_visible_to(jid:jid(), presences_state()) -> boolean().
714 specifically_visible_to(FromJid, #presences_state{pres_invis = Invisible} = Presences) ->
715 4943 Invisible
716
:-(
andalso gb_sets:is_element(FromJid, Presences#presences_state.pres_f)
717
:-(
andalso gb_sets:is_element(FromJid, Presences#presences_state.pres_a).
718
719 -spec get(presences_state(), s_to) -> jid_set();
720 (presences_state(), s_from) -> jid_set();
721 (presences_state(), s_available) -> jid_set();
722 (presences_state(), s_invisible) -> jid_set();
723 (presences_state(), priority) -> priority();
724 (presences_state(), last) -> undefined | exml:element();
725 (presences_state(), timestamp) -> undefined | integer();
726 (presences_state(), invisible) -> boolean().
727
:-(
get(#presences_state{pres_t = Value}, s_to) -> Value;
728 123 get(#presences_state{pres_f = Value}, s_from) -> Value;
729
:-(
get(#presences_state{pres_a = Value}, s_available) -> Value;
730
:-(
get(#presences_state{pres_i = Value}, s_invisible) -> Value;
731
:-(
get(#presences_state{pres_pri = Value}, priority) -> Value;
732 7 get(#presences_state{pres_last = Value}, last) -> Value;
733
:-(
get(#presences_state{pres_timestamp = Value}, timestamp) -> Value;
734
:-(
get(#presences_state{pres_invis = Value}, invisible) -> Value.
Line Hits Source