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