./ct_report/coverage/mod_event_pusher_push_plugin_defaults.COVER.html

1 %%%-------------------------------------------------------------------
2 %%% @author Rafal Slota
3 %%% @copyright (C) 2017 Erlang Solutions Ltd.
4 %%% This software is released under the Apache License, Version 2.0
5 %%% cited in 'LICENSE.txt'.
6 %%% @end
7 %%%-------------------------------------------------------------------
8 %%% @doc
9 %%% Default plugin module for mod_event_pusher_push.
10 %%% This module allows for some dynamic customizations.
11 %%% @end
12 %%%-------------------------------------------------------------------
13 -module(mod_event_pusher_push_plugin_defaults).
14 -behavior(mod_event_pusher_push_plugin).
15 -author('rafal.slota@erlang-solutions.com').
16
17 -include("jlib.hrl").
18 -include("mongoose.hrl").
19 -include("mod_event_pusher_events.hrl").
20
21 %% Callback API
22 -export([prepare_notification/2,
23 should_publish/3,
24 publish_notification/4]).
25
26 -define(PUSH_FORM_TYPE, <<"urn:xmpp:push:summary">>).
27 %%--------------------------------------------------------------------
28 %% mod_event_pusher_push_plugin callbacks
29 %%--------------------------------------------------------------------
30 -spec should_publish(Acc :: mongoose_acc:t(),
31 Event :: mod_event_pusher:event(),
32 Services :: [mod_event_pusher_push:publish_service()]) ->
33 [mod_event_pusher_push:publish_service()].
34 should_publish(Acc, #chat_event{to = To}, Services) ->
35 168 PublishedServices = mongoose_acc:get(event_pusher, published_services, [], Acc),
36 168 case should_publish(Acc, To) of
37 84 true -> Services -- PublishedServices;
38 84 false -> []
39 end;
40
:-(
should_publish(_Acc, _Event, _Services) -> [].
41
42 -spec prepare_notification(Acc :: mongoose_acc:t(),
43 Event :: mod_event_pusher:event()) ->
44 mod_event_pusher_push_plugin:push_payload() | skip.
45 prepare_notification(Acc, _) ->
46 222 {From, To, Packet} = mongoose_acc:packet(Acc),
47 222 case exml_query:subelement(Packet, <<"body">>) of
48
:-(
undefined -> skip;
49 Body ->
50 222 BodyCData = exml_query:cdata(Body),
51 222 MessageCount = get_unread_count(Acc, To),
52 222 SenderId = sender_id(From, Packet),
53 222 push_content_fields(SenderId, BodyCData, MessageCount)
54 end.
55
56 -spec publish_notification(Acc :: mongoose_acc:t(),
57 Event :: mod_event_pusher:event(),
58 Payload :: mod_event_pusher_push_plugin:push_payload(),
59 Services :: [mod_event_pusher_push:publish_service()]) ->
60 mongoose_acc:t().
61 publish_notification(Acc, _, Payload, Services) ->
62 76 To = mongoose_acc:to_jid(Acc),
63 76 HostType = mongoose_acc:host_type(Acc),
64 76 lists:foreach(fun({PubsubJID, _Node, _Form} = Service) ->
65 76 case mod_event_pusher_push:is_virtual_pubsub_host(HostType,
66 To#jid.lserver,
67 PubsubJID#jid.lserver) of
68 true ->
69 38 publish_via_hook(Acc, HostType, To, Service, Payload);
70 false ->
71 38 publish_via_pubsub(HostType, To, Service, Payload)
72 end
73 end, Services),
74
75 76 mongoose_acc:append(event_pusher, published_services, Services, Acc).
76
77 %%--------------------------------------------------------------------
78 %% local functions
79 %%--------------------------------------------------------------------
80
81 -spec should_publish(Acc :: mongoose_acc:t(), To :: jid:jid()) -> boolean().
82 should_publish(Acc, #jid{} = To) ->
83 168 HostType = mongoose_acc:host_type(Acc),
84 168 try ejabberd_auth:does_user_exist(HostType, To, stored) of
85 false ->
86 32 false;
87 true ->
88 136 ejabberd_sm:is_offline(To)
89 catch
90 _:_ ->
91
:-(
ejabberd_sm:is_offline(To)
92 end.
93
94 -spec get_unread_count(mongoose_acc:t(), jid:jid()) -> pos_integer().
95 get_unread_count(Acc, To) ->
96 222 NewAcc = mongoose_hooks:inbox_unread_count(To#jid.lserver, Acc, To),
97 222 mongoose_acc:get(inbox, unread_count, 1, NewAcc).
98
99 -spec sender_id(jid:jid(), exml:element()) -> binary().
100 sender_id(From, Packet) ->
101 222 case exml_query:attr(Packet, <<"type">>) of
102 <<"chat">> ->
103 70 jid:to_bare_binary(jid:to_lower(From));
104 <<"groupchat">> ->
105 152 jid:to_binary(jid:to_lower(From))
106 end.
107
108 -spec push_content_fields(binary(), binary(), non_neg_integer()) ->
109 mod_event_pusher_push_plugin:push_payload() | skip.
110 push_content_fields(_SenderId, <<"">>, _MessageCount) ->
111 44 skip;
112 push_content_fields(SenderId, BodyCData, MessageCount) ->
113 178 [
114 {<<"message-count">>, integer_to_binary(MessageCount)},
115 {<<"last-message-sender">>, SenderId},
116 {<<"last-message-body">>, BodyCData}
117 ].
118
119 -spec publish_via_hook(Acc :: mongoose_acc:t(),
120 HostType :: mongooseim:host_type(),
121 To :: jid:jid(),
122 Service :: mod_event_pusher_push:publish_service(),
123 PushPayload :: mod_event_pusher_push_plugin:push_payload()) ->
124 any().
125 publish_via_hook(Acc0, HostType, To, {PubsubJID, Node, Form}, PushPayload) ->
126 %% Acc is ignored by mod_push_service_mongoosepush, added here only for
127 %% traceability purposes and push_SUITE code unification
128 38 Acc = mongoose_acc:set(push_notifications, pubsub_jid, PubsubJID, Acc0),
129 38 case mongoose_hooks:push_notifications(HostType, Acc, [maps:from_list(PushPayload)], Form) of
130 {error, device_not_registered} ->
131 %% We disable the push node in case the error type is device_not_registered
132 1 mod_event_pusher_push:disable_node(HostType, To, PubsubJID, Node);
133 37 _ -> ok
134 end.
135
136 -spec publish_via_pubsub(mongooseim:host_type(), To :: jid:jid(),
137 Service :: mod_event_pusher_push:publish_service(),
138 PushPayload :: mod_event_pusher_push_plugin:push_payload()) ->
139 any().
140 publish_via_pubsub(HostType, To, {PubsubJID, Node, Form}, PushPayload) ->
141 38 Stanza = push_notification_iq(Node, Form, PushPayload),
142 38 Acc = mongoose_acc:new(#{ location => ?LOCATION,
143 host_type => HostType,
144 lserver => To#jid.lserver,
145 element => jlib:iq_to_xml(Stanza),
146 from_jid => To,
147 to_jid => PubsubJID }),
148
149 38 ResponseHandler =
150 fun(_From, _To, FAcc, Response) ->
151 38 mod_event_pusher_push:cast(HostType, fun handle_publish_response/5,
152 [HostType, To, PubsubJID, Node, Response]),
153 38 FAcc
154 end,
155 %% The IQ is routed from the recipient's server JID to pubsub JID
156 %% This is recommended in the XEP and also helps process replies to this IQ
157 38 NotificationFrom = jid:make(<<>>, To#jid.lserver, <<>>),
158 38 mod_event_pusher_push:cast(HostType, fun ejabberd_local:route_iq/5,
159 [NotificationFrom, PubsubJID, Acc, Stanza, ResponseHandler]).
160
161 -spec handle_publish_response(mongooseim:host_type(),
162 Recipient :: jid:jid(), PubsubJID :: jid:jid(),
163 Node :: mod_event_pusher_push:pubsub_node(),
164 Result :: timeout | jlib:iq()) -> ok.
165 handle_publish_response(_HostType, _Recipient, _PubsubJID, _Node, timeout) ->
166 2 ok;
167 handle_publish_response(_HostType, _Recipient, _PubsubJID, _Node, #iq{type = result}) ->
168 30 ok;
169 handle_publish_response(HostType, Recipient, PubsubJID, Node, #iq{type = error, sub_el = Els}) ->
170 4 [Error | _ ] = [Err || #xmlel{name = <<"error">>} = Err <- Els],
171 4 case exml_query:attr(Error, <<"type">>) of
172 <<"cancel">> ->
173 %% We disable the push node in case the error type is cancel
174 1 mod_event_pusher_push:disable_node(HostType, Recipient, PubsubJID, Node);
175 _ ->
176 3 ok
177 end.
178
179 -spec push_notification_iq(Node :: mod_event_pusher_push:pubsub_node(),
180 Form :: mod_event_pusher_push:form(),
181 PushPayload :: mod_event_pusher_push_plugin:push_payload()) ->
182 jlib:iq().
183 push_notification_iq(Node, Form, PushPayload) ->
184 38 #iq{type = set, sub_el = [
185 #xmlel{name = <<"pubsub">>, attrs = [{<<"xmlns">>, ?NS_PUBSUB}], children = [
186 #xmlel{name = <<"publish">>, attrs = [{<<"node">>, Node}], children = [
187 #xmlel{name = <<"item">>, children = [
188 #xmlel{name = <<"notification">>,
189 attrs = [{<<"xmlns">>, ?NS_PUSH}],
190 children = [make_form(?PUSH_FORM_TYPE, PushPayload)]}
191 ]}
192 ]}
193 ] ++ maybe_publish_options(maps:to_list(Form))}
194 ]}.
195
196 -spec make_form(binary(), mod_event_pusher_push_plugin:push_payload()) -> exml:element().
197 make_form(FormType, FieldKVs) ->
198 74 Fields = [#{var => Name, values => [Value]} || {Name, Value} <- FieldKVs],
199 74 mongoose_data_forms:form(#{ns => FormType, type => <<"submit">>, fields => Fields}).
200
201 -spec maybe_publish_options([{binary(), binary()}]) -> [exml:element()].
202 maybe_publish_options([]) ->
203 2 [];
204 maybe_publish_options(FormFields) ->
205 36 Children = [make_form(?NS_PUBSUB_PUB_OPTIONS, FormFields)],
206 36 [#xmlel{name = <<"publish-options">>, children = Children}].
Line Hits Source