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 |
284 |
ok. |
57 |
|
|
58 |
|
-spec stop(mongooseim:host_type()) -> ok. |
59 |
|
stop(_HostType) -> |
60 |
284 |
ok. |
61 |
|
|
62 |
|
-spec hooks(mongooseim:host_type()) -> gen_hook:hook_list(mongoose_c2s_hooks:fn()). |
63 |
|
hooks(HostType) -> |
64 |
568 |
[ |
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 |
177 |
[dynamic_domains]. |
74 |
|
|
75 |
|
-spec get_presence(pid()) -> {jid:luser(), jid:lresource(), binary(), binary()}. |
76 |
|
get_presence(Pid) -> |
77 |
30 |
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 |
9 |
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 |
6966 |
{FromJid, ToJid, Packet} = mongoose_acc:packet(Acc), |
91 |
6966 |
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 |
5982 |
Acc1 = presence_update(Acc, FromJid, ToJid, Packet, StateData, Type), |
96 |
5982 |
{ok, Acc1}; |
97 |
|
{Type, false} -> |
98 |
983 |
Acc1 = presence_track(Acc, FromJid, ToJid, Packet, StateData, Type), |
99 |
983 |
{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 |
14926 |
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 |
14922 |
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 |
6896 |
case get_mod_state(StateData) of |
128 |
1199 |
{error, not_found} -> {ok, Acc}; |
129 |
208 |
#presences_state{pres_last = undefined} -> {ok, Acc}; |
130 |
5489 |
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 |
5489 |
Jid = mongoose_c2s:get_jid(StateData), |
137 |
5489 |
Status = close_session_status(Reason), |
138 |
5489 |
PresenceUnavailable = presence_unavailable_stanza(Status), |
139 |
5489 |
ParamsAcc = #{from_jid => Jid, to_jid => jid:to_bare(Jid), element => PresenceUnavailable}, |
140 |
5489 |
Acc1 = mongoose_acc:update_stanza(ParamsAcc, Acc), |
141 |
5489 |
presence_broadcast(Acc1, Presences), |
142 |
5489 |
mongoose_hooks:unset_presence_hook(Acc1, Jid, Status), |
143 |
5489 |
{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 |
840 |
case get_mod_state(StateData) of |
155 |
|
{error, not_found} -> |
156 |
:-( |
{ok, Acc}; |
157 |
|
Presences -> |
158 |
840 |
{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 |
30 |
PresLast = case get_mod_state(StateData) of |
165 |
|
{error, not_found} -> |
166 |
:-( |
undefined; |
167 |
|
#presences_state{pres_last = Value} -> |
168 |
30 |
Value |
169 |
|
end, |
170 |
30 |
#jid{luser = User, lresource = Resource} = mongoose_c2s:get_jid(StateData), |
171 |
30 |
Reply = {User, Resource, get_showtag(PresLast), get_statustag(PresLast)}, |
172 |
30 |
Acc1 = mongoose_c2s_acc:to_acc(Acc, actions, [{reply, From, Reply}]), |
173 |
30 |
{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 |
9 |
Acc1 = mongoose_acc:update_stanza(#{element => Message}, Acc), |
192 |
9 |
{FromJid, ToJid, Packet} = mongoose_acc:packet(Acc1), |
193 |
9 |
case get_presence_type(Acc) of |
194 |
|
{error, invalid_presence} -> |
195 |
:-( |
{stop, Acc}; |
196 |
|
Type -> |
197 |
9 |
{stop, presence_update(Acc1, FromJid, ToJid, Packet, StateData, Type)} |
198 |
|
end; |
199 |
|
foreign_event(Acc, _Params, _Extra) -> |
200 |
135 |
{ok, Acc}. |
201 |
|
|
202 |
|
-spec get_showtag(undefined | exml:element()) -> binary(). |
203 |
|
get_showtag(undefined) -> |
204 |
:-( |
<<"unavailable">>; |
205 |
|
get_showtag(Presence) -> |
206 |
30 |
case xml:get_path_s(Presence, [{elem, <<"show">>}, cdata]) of |
207 |
:-( |
<<>> -> <<"available">>; |
208 |
30 |
ShowTag -> ShowTag |
209 |
|
end. |
210 |
|
|
211 |
|
-spec get_statustag(undefined | exml:element()) -> binary(). |
212 |
|
|
213 |
|
get_statustag(undefined) -> |
214 |
:-( |
<<>>; |
215 |
|
get_statustag(Presence) -> |
216 |
30 |
xml:get_path_s(Presence, [{elem, <<"status">>}, cdata]). |
217 |
|
|
218 |
|
-spec handle_subscription_change(mongoose_acc:t(), mongoose_c2s:data(), term(), term(), state()) -> |
219 |
|
mongoose_acc:t(). |
220 |
|
handle_subscription_change(Acc, StateData, IJID, ISubscription, Presences) -> |
221 |
840 |
To = jid:make(IJID), |
222 |
840 |
IsSubscribedToMe = (ISubscription =:= both) or (ISubscription =:= from), |
223 |
840 |
AmISubscribedTo = (ISubscription =:= both) or (ISubscription =:= to), |
224 |
840 |
WasSubscribedToMe = is_subscribed(Presences, To, from), |
225 |
840 |
Subs = case {IsSubscribedToMe, AmISubscribedTo} of |
226 |
|
{true, true} -> |
227 |
164 |
maps:put(To, both, Presences#presences_state.subscriptions); |
228 |
|
{true, _} -> |
229 |
124 |
maps:put(To, from, Presences#presences_state.subscriptions); |
230 |
|
{_, true} -> |
231 |
130 |
maps:put(To, to, Presences#presences_state.subscriptions); |
232 |
|
{false, false} -> |
233 |
422 |
maps:remove(To, Presences#presences_state.subscriptions) |
234 |
|
end, |
235 |
840 |
case Presences#presences_state.pres_last of |
236 |
|
undefined -> |
237 |
:-( |
NewPresences = Presences#presences_state{subscriptions = Subs}, |
238 |
:-( |
mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}); |
239 |
|
P -> |
240 |
840 |
?LOG_DEBUG(#{what => roster_changed, roster_jid => To, |
241 |
840 |
acc => Acc, c2s_state => StateData}), |
242 |
840 |
From = mongoose_c2s:get_jid(StateData), |
243 |
840 |
ImAvailableTo = is_available(Presences, To), |
244 |
840 |
BecomeAvailable = IsSubscribedToMe and not WasSubscribedToMe, |
245 |
840 |
BecomeUnavailable = not IsSubscribedToMe and WasSubscribedToMe and ImAvailableTo, |
246 |
840 |
case {BecomeAvailable, BecomeUnavailable} of |
247 |
|
{true, _} -> |
248 |
181 |
?LOG_DEBUG(#{what => become_available_to, roster_jid => To, |
249 |
181 |
acc => Acc, c2s_state => StateData}), |
250 |
181 |
ejabberd_router:route(From, To, Acc, P), |
251 |
181 |
Available = sets:add_element(To, Presences#presences_state.available), |
252 |
181 |
NewPresences = Presences#presences_state{subscriptions = Subs, |
253 |
|
available = Available}, |
254 |
181 |
mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}); |
255 |
|
{_, true} -> |
256 |
19 |
?LOG_DEBUG(#{what => become_unavailable_to, roster_jid => To, |
257 |
19 |
acc => Acc, c2s_state => StateData}), |
258 |
19 |
PU = presence_unavailable_stanza(), |
259 |
19 |
ejabberd_router:route(From, To, Acc, PU), |
260 |
19 |
Available = sets:del_element(To, Presences#presences_state.available), |
261 |
19 |
NewPresences = Presences#presences_state{subscriptions = Subs, |
262 |
|
available = Available}, |
263 |
19 |
mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}); |
264 |
|
_ -> |
265 |
640 |
NewPresences = Presences#presences_state{subscriptions = Subs}, |
266 |
640 |
mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}) |
267 |
|
end |
268 |
|
end. |
269 |
|
|
270 |
|
-spec handle_user_received_presence(mongoose_acc:t(), state(), presence()) -> |
271 |
|
mongoose_c2s_hooks:result(). |
272 |
|
handle_user_received_presence(Acc, Presences, available) -> |
273 |
8088 |
{ok, handle_received_available(Acc, Presences)}; |
274 |
|
handle_user_received_presence(Acc, Presences, unavailable) -> |
275 |
450 |
{ok, handle_received_unavailable(Acc, Presences)}; |
276 |
|
handle_user_received_presence(Acc, Presences, error) -> |
277 |
57 |
{ok, handle_received_unavailable(Acc, Presences)}; |
278 |
|
handle_user_received_presence(Acc, Presences, probe) -> |
279 |
5929 |
{stop, handle_received_probe(Acc, Presences)}; |
280 |
|
handle_user_received_presence(Acc, _, _) -> |
281 |
398 |
{ok, Acc}. |
282 |
|
|
283 |
|
-spec handle_received_probe(mongoose_acc:t(), state()) -> mongoose_acc:t(). |
284 |
|
handle_received_probe(Acc, Presences) -> |
285 |
5929 |
{FromJid, ToJid, _Packet} = mongoose_acc:packet(Acc), |
286 |
5929 |
BareFromJid = jid:to_bare(FromJid), |
287 |
5929 |
NewPresences = case am_i_available_to(FromJid, BareFromJid, Presences) of |
288 |
5929 |
true -> Presences; |
289 |
:-( |
false -> make_available_to(FromJid, BareFromJid, Presences) |
290 |
|
end, |
291 |
5929 |
Acc1 = mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}), |
292 |
5929 |
process_presence_probe(Acc1, NewPresences, FromJid, BareFromJid, ToJid). |
293 |
|
|
294 |
|
-spec process_presence_probe(mongoose_acc:t(), state(), jid:jid(), jid:jid(), jid:jid()) -> mongoose_acc:t(). |
295 |
|
process_presence_probe(Acc, #presences_state{pres_last = undefined}, _, _, _) -> |
296 |
:-( |
Acc; |
297 |
|
process_presence_probe(Acc, Presences, FromJid, BareFromJid, ToJid) -> |
298 |
5929 |
case {is_subscribed_to_my_presence(FromJid, BareFromJid, Presences), |
299 |
|
specifically_visible_to(FromJid, Presences)} of |
300 |
|
{true, _} -> |
301 |
5929 |
route_probe(Acc, Presences, FromJid, ToJid); |
302 |
|
{false, true} -> |
303 |
:-( |
ejabberd_router:route(ToJid, FromJid, Acc, #xmlel{name = <<"presence">>}), |
304 |
:-( |
Acc; |
305 |
|
_ -> |
306 |
:-( |
Acc |
307 |
|
end. |
308 |
|
|
309 |
|
-spec route_probe(mongoose_acc:t(), state(), jid:jid(), jid:jid()) -> mongoose_acc:t(). |
310 |
|
route_probe(Acc, Presences, FromJid, ToJid) -> |
311 |
5929 |
Packet0 = Presences#presences_state.pres_last, |
312 |
5929 |
TS = Presences#presences_state.pres_timestamp, |
313 |
|
%% To is the one sending the presence (the target of the probe) |
314 |
5929 |
Packet1 = jlib:maybe_append_delay(Packet0, ToJid, TS, <<>>), |
315 |
5929 |
HostType = mongoose_acc:host_type(Acc), |
316 |
5929 |
Acc2 = mongoose_hooks:presence_probe_hook(HostType, Acc, FromJid, ToJid, self()), |
317 |
|
%% Don't route a presence probe to oneself |
318 |
5929 |
case jid:are_equal(FromJid, ToJid) of |
319 |
|
false -> |
320 |
197 |
ejabberd_router:route(ToJid, FromJid, Acc2, Packet1), |
321 |
197 |
Acc; |
322 |
|
true -> |
323 |
5732 |
Acc2 |
324 |
|
end. |
325 |
|
|
326 |
|
-spec handle_received_unavailable(mongoose_acc:t(), state()) -> mongoose_acc:t(). |
327 |
|
handle_received_unavailable(Acc, Presences) -> |
328 |
507 |
FromJid = mongoose_acc:from_jid(Acc), |
329 |
507 |
NewS = sets:del_element(FromJid, Presences#presences_state.available), |
330 |
507 |
NewPresences = Presences#presences_state{available = NewS}, |
331 |
507 |
mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}). |
332 |
|
|
333 |
|
-spec handle_received_available(mongoose_acc:t(), state()) -> mongoose_acc:t(). |
334 |
|
handle_received_available(Acc, Presences) -> |
335 |
8088 |
FromJid = mongoose_acc:from_jid(Acc), |
336 |
8088 |
BareJid = jid:to_bare(FromJid), |
337 |
8088 |
case am_i_available_to(FromJid, BareJid, Presences) of |
338 |
|
true -> |
339 |
7029 |
Acc; |
340 |
|
false -> |
341 |
1059 |
NewPresences = make_available_to(FromJid, BareJid, Presences), |
342 |
1059 |
mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}) |
343 |
|
end. |
344 |
|
|
345 |
|
%% @doc User updates his presence (non-directed presence packet) |
346 |
|
-spec presence_update( |
347 |
|
mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), presence()) -> |
348 |
|
mongoose_acc:t(). |
349 |
|
presence_update(Acc, FromJid, ToJid, Packet, StateData, available) -> |
350 |
5773 |
Presences = maybe_get_handler(StateData), |
351 |
5773 |
presence_update_to_available(Acc, FromJid, ToJid, Packet, StateData, Presences); |
352 |
|
presence_update(Acc, FromJid, ToJid, Packet, StateData, unavailable) -> |
353 |
218 |
Presences = maybe_get_handler(StateData), |
354 |
218 |
presence_update_to_unavailable(Acc, FromJid, ToJid, Packet, StateData, Presences); |
355 |
:-( |
presence_update(Acc, _, _, _, _, _) -> Acc. |
356 |
|
|
357 |
|
-spec presence_update_to_available( |
358 |
|
mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), state()) -> |
359 |
|
mongoose_acc:t(). |
360 |
|
presence_update_to_available(Acc0, FromJid, ToJid, Packet, StateData, Presences) -> |
361 |
5773 |
Jid = mongoose_c2s:get_jid(StateData), |
362 |
5773 |
HostType = mongoose_c2s:get_host_type(StateData), |
363 |
5773 |
{Acc1, Subs, Pending} = build_subs_and_pendings(HostType, Acc0, Jid), |
364 |
5773 |
OldPriority = get_old_priority(Presences), |
365 |
5773 |
NewPriority = get_priority_from_presence(Packet), |
366 |
5773 |
Acc2 = update_priority(Acc1, NewPriority, Packet, StateData), |
367 |
5773 |
FromUnavail = (Presences#presences_state.pres_last =:= undefined), |
368 |
5773 |
?LOG_DEBUG(#{what => presence_update_to_available, |
369 |
|
text => <<"Presence changes from unavailable to available">>, |
370 |
5773 |
from_unavail => FromUnavail, acc => Acc2, c2s_state => StateData}), |
371 |
5773 |
NewPresences = Presences#presences_state{subscriptions = Subs, |
372 |
|
pres_pri = NewPriority, |
373 |
|
pres_last = Packet, |
374 |
|
pres_timestamp = erlang:system_time(microsecond)}, |
375 |
5773 |
presence_update_to_available( |
376 |
|
Acc2, FromJid, ToJid, Packet, StateData, NewPresences, Pending, |
377 |
|
OldPriority, NewPriority, FromUnavail). |
378 |
|
|
379 |
|
-spec presence_update_to_unavailable( |
380 |
|
mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), state()) -> |
381 |
|
mongoose_acc:t(). |
382 |
|
presence_update_to_unavailable(Acc, _FromJid, _ToJid, Packet, StateData, Presences) -> |
383 |
218 |
Status = exml_query:path(Packet, [{element, <<"status">>}, cdata], <<>>), |
384 |
218 |
Sid = mongoose_c2s:get_sid(StateData), |
385 |
218 |
Jid = mongoose_c2s:get_jid(StateData), |
386 |
218 |
Info = mongoose_c2s:get_info(StateData), |
387 |
218 |
Acc1 = ejabberd_sm:unset_presence(Acc, Sid, Jid, Status, Info), |
388 |
218 |
presence_broadcast(Acc1, Presences), |
389 |
218 |
NewPresences = Presences#presences_state{pres_last = undefined, |
390 |
|
pres_timestamp = undefined}, |
391 |
218 |
mongoose_c2s_acc:to_acc(Acc1, state_mod, {?MODULE, NewPresences}). |
392 |
|
|
393 |
|
%% @doc User sends a directed presence packet |
394 |
|
-spec presence_track( |
395 |
|
mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), presence()) -> |
396 |
|
mongoose_acc:t(). |
397 |
|
presence_track(Acc, FromJid, ToJid, Packet, StateData, available) -> |
398 |
755 |
Presences = maybe_get_handler(StateData), |
399 |
755 |
process_presence_track_available(Acc, FromJid, ToJid, Packet, Presences); |
400 |
|
presence_track(Acc, FromJid, ToJid, Packet, StateData, unavailable) -> |
401 |
17 |
Presences = maybe_get_handler(StateData), |
402 |
17 |
process_presence_track_unavailable(Acc, FromJid, ToJid, Packet, Presences); |
403 |
|
presence_track(Acc, FromJid, ToJid, Packet, _, Type) when error =:= Type; probe =:= Type -> |
404 |
2 |
ejabberd_router:route(FromJid, ToJid, Acc, Packet), |
405 |
2 |
Acc; |
406 |
|
presence_track(Acc, FromJid, ToJid, Packet, _, Type) -> |
407 |
209 |
process_presence_track_subscription_and_route(Acc, FromJid, ToJid, Packet, Type). |
408 |
|
|
409 |
|
process_presence_track_available(Acc, FromJid, ToJid, Packet, Presences) -> |
410 |
755 |
ejabberd_router:route(FromJid, ToJid, Acc, Packet), |
411 |
755 |
Available = sets:add_element(ToJid, Presences#presences_state.available), |
412 |
755 |
NewPresences = Presences#presences_state{available = Available}, |
413 |
755 |
mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}). |
414 |
|
|
415 |
|
process_presence_track_unavailable(Acc, FromJid, ToJid, Packet, Presences) -> |
416 |
17 |
ejabberd_router:route(FromJid, ToJid, Acc, Packet), |
417 |
17 |
Available = sets:del_element(ToJid, Presences#presences_state.available), |
418 |
17 |
NewPresences = Presences#presences_state{available = Available}, |
419 |
17 |
mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewPresences}). |
420 |
|
|
421 |
|
process_presence_track_subscription_and_route(Acc, FromJid, ToJid, Packet, Type) -> |
422 |
209 |
Acc1 = mongoose_hooks:roster_out_subscription(Acc, FromJid, ToJid, Type), |
423 |
209 |
ejabberd_router:route(jid:to_bare(FromJid), ToJid, Acc1, Packet), |
424 |
209 |
Acc1. |
425 |
|
|
426 |
|
-spec presence_broadcast(mongoose_acc:t(), state()) -> ok. |
427 |
|
presence_broadcast(Acc, #presences_state{available = Available}) -> |
428 |
5707 |
{FromJID, _, Packet} = mongoose_acc:packet(Acc), |
429 |
5707 |
Route = fun(ToJID, _) -> ejabberd_router:route(FromJID, ToJID, Acc, Packet) end, |
430 |
5707 |
sets:fold(Route, undefined, Available). |
431 |
|
|
432 |
|
-spec presence_update_to_available( |
433 |
|
mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mongoose_c2s:data(), |
434 |
|
state(), [exml:element()], maybe_priority(), priority(), boolean()) -> |
435 |
|
mongoose_acc:t(). |
436 |
|
presence_update_to_available(Acc, FromJid, _ToJid, Packet, StateData, Presences, Pending, |
437 |
|
_OldPriority, NewPriority, true) -> |
438 |
5734 |
Acc1 = mongoose_hooks:user_available_hook(Acc, FromJid), |
439 |
5734 |
Acc2 = case NewPriority >= 0 of |
440 |
|
true -> |
441 |
5734 |
resend_offline_messages(Acc1, StateData); |
442 |
|
false -> |
443 |
:-( |
Acc1 |
444 |
|
end, |
445 |
5734 |
presence_broadcast_first(Acc2, FromJid, Packet, Presences, Pending); |
446 |
|
presence_update_to_available(Acc, FromJid, _ToJid, Packet, StateData, Presences, Pending, |
447 |
|
OldPriority, NewPriority, false) -> |
448 |
39 |
presence_broadcast_to_trusted( |
449 |
|
Acc, FromJid, Presences, Packet), |
450 |
39 |
Acc1 = case (OldPriority < 0 orelse OldPriority =:= undefined) andalso NewPriority >= 0 of |
451 |
|
true -> |
452 |
:-( |
resend_offline_messages(Acc, StateData); |
453 |
|
false -> |
454 |
39 |
Acc |
455 |
|
end, |
456 |
39 |
Accs = create_route_accs(Acc1, FromJid, Pending), |
457 |
39 |
ToAcc = [{route, Accs}, {state_mod, {?MODULE, Presences}}], |
458 |
39 |
mongoose_c2s_acc:to_acc_many(Acc1, ToAcc). |
459 |
|
|
460 |
|
-spec presence_broadcast_to_trusted(mongoose_acc:t(), jid:jid(), state(), exml:element()) -> ok. |
461 |
|
presence_broadcast_to_trusted(Acc, FromJid, Presences, Packet) -> |
462 |
39 |
sets:fold( |
463 |
|
fun(JID, _) -> |
464 |
42 |
case is_subscribed(Presences, JID, from) of |
465 |
|
true -> |
466 |
42 |
ejabberd_router:route(FromJid, JID, Acc, Packet); |
467 |
|
_ -> |
468 |
:-( |
Acc |
469 |
|
end |
470 |
|
end, undefined, Presences#presences_state.available). |
471 |
|
|
472 |
|
-spec resend_offline_messages(mongoose_acc:t(), mongoose_c2s:data()) -> mongoose_acc:t(). |
473 |
|
resend_offline_messages(Acc, StateData) -> |
474 |
5734 |
?LOG_DEBUG(#{what => resend_offline_messages, acc => Acc, c2s_state => StateData}), |
475 |
5734 |
Jid = mongoose_c2s:get_jid(StateData), |
476 |
5734 |
Acc1 = mongoose_hooks:resend_offline_messages_hook(Acc, Jid), |
477 |
5734 |
Rs = mongoose_acc:get(offline, messages, [], Acc1), |
478 |
5734 |
Acc2 = lists:foldl(fun({route, FromJid, ToJid, MsgAcc}, A) -> |
479 |
150 |
resend_offline_message(A, FromJid, ToJid, MsgAcc, in); |
480 |
|
({route, MsgAcc}, A) -> |
481 |
:-( |
{FromJid, ToJid, _} = mongoose_acc:packet(Acc), |
482 |
:-( |
resend_offline_message(A, FromJid, ToJid, MsgAcc, in) |
483 |
|
end, Acc1, Rs), |
484 |
5734 |
mongoose_acc:delete(offline, messages, Acc2). % they are gone from db backend and sent |
485 |
|
|
486 |
|
resend_offline_message(Acc0, FromJid, To, Acc, in) -> |
487 |
150 |
Packet = mongoose_acc:element(Acc), |
488 |
150 |
NewAcc = strip_c2s_fields(Acc), |
489 |
150 |
ejabberd_router:route(FromJid, To, NewAcc, Packet), |
490 |
150 |
Acc0. |
491 |
|
|
492 |
|
-spec strip_c2s_fields(mongoose_acc:t()) -> mongoose_acc:t(). |
493 |
|
strip_c2s_fields(Acc) -> |
494 |
|
%% TODO: verify if we really need to strip down these 2 fields |
495 |
150 |
mongoose_acc:delete_many(c2s, [origin_jid, origin_sid], Acc). |
496 |
|
|
497 |
|
-spec presence_broadcast_first( |
498 |
|
mongoose_acc:t(), jid:jid(), exml:element(), state(), [exml:element()]) -> |
499 |
|
mongoose_acc:t(). |
500 |
|
presence_broadcast_first(Acc0, FromJid, Packet, Presences, Pending) -> |
501 |
5734 |
broadcast_probe(Acc0, FromJid, Presences), |
502 |
5734 |
Ss = maps:fold(fun(JID, Sub, S) -> |
503 |
5739 |
notify_available_to_subscribers(Acc0, FromJid, Packet, JID, Sub, S) |
504 |
|
end, Presences#presences_state.available, Presences#presences_state.subscriptions), |
505 |
5734 |
NewPresences = Presences#presences_state{available = Ss}, |
506 |
5734 |
Accs = create_route_accs(Acc0, FromJid, Pending), |
507 |
5734 |
ToAcc = [{route, Accs}, {state_mod, {?MODULE, NewPresences}}], |
508 |
5734 |
mongoose_c2s_acc:to_acc_many(Acc0, ToAcc). |
509 |
|
|
510 |
|
-spec notify_available_to_subscribers( |
511 |
|
mongoose_acc:t(), jid:jid(), exml:element(), jid:jid(), subscriptions(), available()) -> |
512 |
|
available(). |
513 |
|
notify_available_to_subscribers(Acc0, FromJid, Packet, JID, both, S) -> |
514 |
5739 |
ejabberd_router:route(FromJid, JID, Acc0, Packet), |
515 |
5739 |
sets:add_element(JID, S); |
516 |
|
notify_available_to_subscribers(Acc0, FromJid, Packet, JID, from, S) -> |
517 |
:-( |
ejabberd_router:route(FromJid, JID, Acc0, Packet), |
518 |
:-( |
sets:add_element(JID, S); |
519 |
|
notify_available_to_subscribers(_, _, _, _, _, S) -> |
520 |
:-( |
S. |
521 |
|
|
522 |
|
-spec broadcast_probe(mongoose_acc:t(), jid:jid(), state()) -> ok. |
523 |
|
broadcast_probe(Acc, FromJid, Presences) -> |
524 |
5734 |
Probe = presence_probe(), |
525 |
5734 |
Fun = fun(ToJid, Sub) when both =:= Sub; to =:= Sub -> |
526 |
5739 |
ejabberd_router:route(FromJid, ToJid, Acc, Probe); |
527 |
|
(_, _) -> |
528 |
:-( |
ok |
529 |
|
end, |
530 |
5734 |
maps:foreach(Fun, Presences#presences_state.subscriptions). |
531 |
|
|
532 |
|
-spec create_route_accs(mongoose_acc:t(), jid:jid(), [exml:element()]) -> [mongoose_acc:t()]. |
533 |
|
create_route_accs(Acc0, To, List) when is_list(List) -> |
534 |
5773 |
[ mongoose_acc:update_stanza(#{to_jid => To, element => P}, Acc0) || P <- List ]. |
535 |
|
|
536 |
|
-spec presence_probe() -> exml:element(). |
537 |
|
presence_probe() -> |
538 |
5734 |
#xmlel{name = <<"presence">>, |
539 |
|
attrs = [{<<"type">>, <<"probe">>}]}. |
540 |
|
|
541 |
|
-spec presence_unavailable_stanza() -> exml:element(). |
542 |
|
presence_unavailable_stanza() -> |
543 |
45 |
presence_unavailable_stanza(<<>>). |
544 |
|
|
545 |
|
-spec presence_unavailable_stanza(binary()) -> exml:element(). |
546 |
|
presence_unavailable_stanza(<<>>) -> |
547 |
45 |
#xmlel{name = <<"presence">>, |
548 |
|
attrs = [{<<"type">>, <<"unavailable">>}]}; |
549 |
|
presence_unavailable_stanza(Status) -> |
550 |
5489 |
StatusEl = #xmlel{name = <<"status">>, |
551 |
|
children = [#xmlcdata{content = Status}]}, |
552 |
5489 |
#xmlel{name = <<"presence">>, |
553 |
|
attrs = [{<<"type">>, <<"unavailable">>}], |
554 |
|
children = [StatusEl]}. |
555 |
|
|
556 |
|
close_session_status(normal) -> |
557 |
:-( |
<<>>; |
558 |
|
close_session_status({shutdown, retries}) -> |
559 |
:-( |
<<"Too many attempts">>; |
560 |
|
close_session_status({shutdown, replaced}) -> |
561 |
:-( |
<<"Replaced by new connection">>; |
562 |
|
close_session_status({shutdown, Reason}) when is_atom(Reason) -> |
563 |
5448 |
<<"Shutdown by reason: ", (atom_to_binary(Reason))/binary>>; |
564 |
|
close_session_status({shutdown, Reason}) when is_binary(Reason) -> |
565 |
38 |
<<"Shutdown by reason: ", Reason/binary>>; |
566 |
|
close_session_status(_) -> |
567 |
3 |
<<"Unknown condition">>. |
568 |
|
|
569 |
|
-spec get_presence_type(mongoose_acc:t()) -> maybe_presence(). |
570 |
|
get_presence_type(Acc) -> |
571 |
21901 |
case mongoose_acc:stanza_type(Acc) of |
572 |
|
%% Note that according to https://www.rfc-editor.org/rfc/rfc6121.html#section-4.7.1 |
573 |
|
%% there is no default value nor "available" is considered a valid type. |
574 |
|
%% However, we keep accepting this for compatibility reasons. |
575 |
14616 |
undefined -> available; |
576 |
2 |
<<"available">> -> available; |
577 |
59 |
<<"error">> -> error; |
578 |
5931 |
<<"probe">> -> probe; |
579 |
293 |
<<"subscribe">> -> subscribe; |
580 |
280 |
<<"subscribed">> -> subscribed; |
581 |
15 |
<<"unsubscribe">> -> unsubscribe; |
582 |
19 |
<<"unsubscribed">> -> unsubscribed; |
583 |
685 |
<<"unavailable">> -> unavailable; |
584 |
1 |
_ -> {error, invalid_presence} |
585 |
|
end. |
586 |
|
|
587 |
|
-spec maybe_get_handler(mongoose_c2s:data()) -> state(). |
588 |
|
maybe_get_handler(StateData) -> |
589 |
7236 |
case mongoose_c2s:get_mod_state(StateData, ?MODULE) of |
590 |
1505 |
{ok, #presences_state{} = Presences} -> Presences; |
591 |
5731 |
{error, not_found} -> #presences_state{} |
592 |
|
end. |
593 |
|
|
594 |
|
-spec get_mod_state(mongoose_c2s:data()) -> state() | {error, not_found}. |
595 |
|
get_mod_state(StateData) -> |
596 |
22700 |
case mongoose_c2s:get_mod_state(StateData, ?MODULE) of |
597 |
21497 |
{ok, Presence} -> Presence; |
598 |
1203 |
Error -> Error |
599 |
|
end. |
600 |
|
|
601 |
|
-spec get_priority_from_presence(exml:element()) -> priority(). |
602 |
|
get_priority_from_presence(PresencePacket) -> |
603 |
6020 |
MaybePriority = exml_query:path(PresencePacket, [{element, <<"priority">>}, cdata], undefined), |
604 |
6020 |
case catch binary_to_integer(MaybePriority) of |
605 |
6 |
P when is_integer(P), -128 =< P, P =< 127 -> P; |
606 |
6014 |
_ -> 0 |
607 |
|
end. |
608 |
|
|
609 |
|
-spec get_old_priority(state()) -> maybe_priority(). |
610 |
|
get_old_priority(Presences) -> |
611 |
6090 |
case Presences#presences_state.pres_last of |
612 |
5843 |
undefined -> undefined; |
613 |
247 |
OldPresence -> get_priority_from_presence(OldPresence) |
614 |
|
end. |
615 |
|
|
616 |
|
-spec build_subs_and_pendings(mongooseim:host_type(), mongoose_acc:t(), jid:jid()) -> |
617 |
|
{mongoose_acc:t(), subscriptions(), [exml:element()]}. |
618 |
|
build_subs_and_pendings(HostType, Acc0, Jid) -> |
619 |
5773 |
Acc1 = mongoose_hooks:roster_get_subscription_lists(HostType, Acc0, Jid), |
620 |
5773 |
{Fs0, Ts0, Pending} = mongoose_acc:get(roster, subscription_lists, {[], [], []}, Acc1), |
621 |
5773 |
BareJid = jid:to_bare(Jid), |
622 |
5773 |
Fs = maps:from_list([{jid:make(BJ), from} || BJ <- Fs0]), |
623 |
5773 |
Ts = maps:from_list([{jid:make(BJ), to} || BJ <- Ts0]), |
624 |
5773 |
Subs = maps:merge_with(fun(_, _, _) -> both end, Fs, Ts), |
625 |
5773 |
{Acc1, Subs#{BareJid => both}, Pending}. |
626 |
|
|
627 |
|
-spec update_priority(Acc :: mongoose_acc:t(), |
628 |
|
Priority :: integer(), |
629 |
|
Packet :: exml:element(), |
630 |
|
StateData :: mongoose_c2s:data()) -> mongoose_acc:t(). |
631 |
|
update_priority(Acc, Priority, Packet, StateData) -> |
632 |
5773 |
Sid = mongoose_c2s:get_sid(StateData), |
633 |
5773 |
Jid = mongoose_c2s:get_jid(StateData), |
634 |
5773 |
Info = mongoose_c2s:get_info(StateData), |
635 |
5773 |
ejabberd_sm:set_presence(Acc, Sid, Jid, Priority, Packet, Info). |
636 |
|
|
637 |
|
-spec am_i_subscribed_to_presence(jid:jid(), jid:jid(), state()) -> boolean(). |
638 |
|
am_i_subscribed_to_presence(LJID, LBareJID, S) -> |
639 |
271 |
is_subscribed(S, LJID, to) |
640 |
241 |
orelse (LJID /= LBareJID) |
641 |
209 |
andalso is_subscribed(S, LBareJID, to). |
642 |
|
|
643 |
|
-spec am_i_available_to(jid:jid(), jid:jid(), state()) -> boolean(). |
644 |
|
am_i_available_to(FromJid, BareJid, Presences) -> |
645 |
14017 |
is_available(Presences, FromJid) |
646 |
13237 |
orelse (FromJid /= BareJid) |
647 |
13237 |
andalso is_available(Presences, BareJid). |
648 |
|
|
649 |
|
-spec make_available_to(jid:jid(), jid:jid(), state()) -> state(). |
650 |
|
make_available_to(FromJid, BareJid, Presences) -> |
651 |
1059 |
case is_subscribed(Presences, FromJid, from) of |
652 |
|
true -> |
653 |
:-( |
S = sets:add_element(FromJid, Presences#presences_state.available), |
654 |
:-( |
Presences#presences_state{available = S}; |
655 |
|
false -> |
656 |
1059 |
case is_subscribed(Presences, BareJid, from) of |
657 |
|
true -> |
658 |
:-( |
S = sets:add_element(BareJid, Presences#presences_state.available), |
659 |
:-( |
Presences#presences_state{available = S}; |
660 |
|
false -> |
661 |
1059 |
Presences |
662 |
|
end |
663 |
|
end. |
664 |
|
|
665 |
|
-spec is_subscribed_to_my_presence(jid:jid(), jid:jid(), state()) -> boolean(). |
666 |
|
is_subscribed_to_my_presence(FromJid, BareFromJid, Presences) -> |
667 |
6233 |
is_subscribed(Presences, FromJid, from) |
668 |
6218 |
orelse (FromJid /= BareFromJid) |
669 |
6138 |
andalso is_subscribed(Presences, BareFromJid, from). |
670 |
|
|
671 |
|
-spec specifically_visible_to(jid:jid(), state()) -> boolean(). |
672 |
|
specifically_visible_to(FromJid, Presences) -> |
673 |
5929 |
is_subscribed(Presences, FromJid, from) |
674 |
:-( |
andalso is_available(Presences, FromJid). |
675 |
|
|
676 |
|
-spec is_available(state(), jid:jid()) -> boolean(). |
677 |
|
is_available(#presences_state{available = Available}, Jid) -> |
678 |
28094 |
sets:is_element(Jid, Available). |
679 |
|
|
680 |
|
-spec is_subscribed(state(), jid:jid(), subscription()) -> boolean(). |
681 |
|
is_subscribed(#presences_state{subscriptions = Subs}, Jid, DesiredSub) -> |
682 |
21780 |
Sub = maps:get(Jid, Subs, '$imposible_status'), |
683 |
21780 |
both =:= Sub orelse DesiredSub =:= Sub. |
684 |
|
|
685 |
|
-spec get_by_sub(state(), subscription()) -> subscriptions(). |
686 |
|
get_by_sub(#presences_state{subscriptions = Subs}, DesiredStatus) -> |
687 |
131 |
Filter = fun(_, Status) -> both =:= Status orelse DesiredStatus =:= Status end, |
688 |
131 |
maps:filter(Filter, Subs). |
689 |
|
|
690 |
|
-spec get(state(), s_to) -> subscriptions(); |
691 |
|
(state(), s_from) -> subscriptions(); |
692 |
|
(state(), s_available) -> available(); |
693 |
|
(state(), priority) -> priority(); |
694 |
|
(state(), last) -> undefined | exml:element(); |
695 |
|
(state(), timestamp) -> undefined | integer(). |
696 |
:-( |
get(P, s_to) -> get_by_sub(P, to); |
697 |
123 |
get(P, s_from) -> get_by_sub(P, from); |
698 |
:-( |
get(#presences_state{available = Value}, s_available) -> Value; |
699 |
:-( |
get(#presences_state{pres_pri = Value}, priority) -> Value; |
700 |
7 |
get(#presences_state{pres_last = Value}, last) -> Value; |
701 |
:-( |
get(#presences_state{pres_timestamp = Value}, timestamp) -> Value. |