./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 :: mongooseim_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 136 PublishedServices = mongoose_acc:get(event_pusher, published_services, [], Acc),
36 136 case should_publish(Acc, To) of
37 84 true -> Services -- PublishedServices;
38 52 false -> []
39 end;
40
:-(
should_publish(_Acc, _Event, _Services) -> [].
41
42 -spec prepare_notification(Acc :: mongooseim_acc:t(),
43 Event :: mod_event_pusher:event()) ->
44 mod_event_pusher_push_plugin:push_payload() | skip.
45 prepare_notification(Acc, _) ->
46 190 {From, To, Packet} = mongoose_acc:packet(Acc),
47 190 case exml_query:subelement(Packet, <<"body">>) of
48
:-(
undefined -> skip;
49 Body ->
50 190 BodyCData = exml_query:cdata(Body),
51 190 MessageCount = get_unread_count(Acc, To),
52 190 SenderId = sender_id(From, Packet),
53 190 push_content_fields(SenderId, BodyCData, MessageCount)
54 end.
55
56 -spec publish_notification(Acc :: mongooseim_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 mongooseim_acc:t().
61 publish_notification(Acc, _, Payload, Services) ->
62 76 To = mongoose_acc:to_jid(Acc),
63 76 #jid{lserver = Host} = To,
64 76 lists:foreach(fun({PubsubJID, _Node, _Form} = Service) ->
65 76 case mod_event_pusher_push:is_virtual_pubsub_host(Host, Host,
66 PubsubJID#jid.lserver) of
67 true ->
68 38 publish_via_hook(Acc, Host, To, Service, Payload);
69 false ->
70 38 publish_via_pubsub(Host, To, Service, Payload)
71 end
72 end, Services),
73
74 76 mongoose_acc:append(event_pusher, published_services, Services, Acc).
75
76 %%--------------------------------------------------------------------
77 %% local functions
78 %%--------------------------------------------------------------------
79
80 -spec should_publish(Acc :: mongoose_acc:t(), To :: jid:jid()) -> boolean().
81 should_publish(Acc, #jid{} = To) ->
82 136 HostType = mongoose_acc:host_type(Acc),
83 136 try ejabberd_auth:does_user_exist(HostType, To, stored) of
84 false ->
85
:-(
false;
86 true ->
87 136 ejabberd_sm:is_offline(To)
88 catch
89 _:_ ->
90
:-(
ejabberd_sm:is_offline(To)
91 end.
92
93 -spec get_unread_count(mongoose_acc:t(), jid:jid()) -> pos_integer().
94 get_unread_count(Acc, To) ->
95 190 NewAcc = mongoose_hooks:inbox_unread_count(To#jid.lserver, Acc, To),
96 190 mongoose_acc:get(inbox, unread_count, 1, NewAcc).
97
98 -spec sender_id(jid:jid(), exml:element()) -> binary().
99 sender_id(From, Packet) ->
100 190 case exml_query:attr(Packet, <<"type">>) of
101 <<"chat">> ->
102 70 jid:to_binary(jid:to_bare(jid:to_lower(From)));
103 <<"groupchat">> ->
104 120 jid:to_binary(jid:to_lower(From))
105 end.
106
107 -spec push_content_fields(binary(), binary(), non_neg_integer()) ->
108 mod_event_pusher_push_plugin:push_payload() | skip.
109 push_content_fields(_SenderId, <<"">>, _MessageCount) ->
110 44 skip;
111 push_content_fields(SenderId, BodyCData, MessageCount) ->
112 146 [
113 {<<"message-count">>, integer_to_binary(MessageCount)},
114 {<<"last-message-sender">>, SenderId},
115 {<<"last-message-body">>, BodyCData}
116 ].
117
118 -spec publish_via_hook(Acc :: mongooseim_acc:t(),
119 Host :: jid:server(),
120 To :: jid:jid(),
121 Service :: mod_event_pusher_push:publish_service(),
122 PushPayload :: mod_event_pusher_push_plugin:push_payload()) ->
123 any().
124 publish_via_hook(Acc0, Host, To, {PubsubJID, Node, Form}, PushPayload) ->
125 38 OptionMap = maps:from_list(Form),
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(Host, Acc, [maps:from_list(PushPayload)], OptionMap) 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(Host, To, PubsubJID, Node);
133 37 _ -> ok
134 end.
135
136 -spec publish_via_pubsub(Host :: jid:server(),
137 To :: jid:jid(),
138 Service :: mod_event_pusher_push:publish_service(),
139 PushPayload :: mod_event_pusher_push_plugin:push_payload()) ->
140 any().
141 publish_via_pubsub(Host, To, {PubsubJID, Node, Form}, PushPayload) ->
142 38 Stanza = push_notification_iq(Node, Form, PushPayload),
143 %% TODO host type should be provided to this function
144 38 {ok, HostType} = mongoose_domain_api:get_domain_host_type(Host),
145 38 Acc = mongoose_acc:new(#{ location => ?LOCATION,
146 host_type => HostType,
147 lserver => Host,
148 element => jlib:iq_to_xml(Stanza),
149 from_jid => To,
150 to_jid => PubsubJID }),
151
152 38 ResponseHandler =
153 fun(_From, _To, FAcc, Response) ->
154 38 mod_event_pusher_push:cast(Host, fun handle_publish_response/5,
155 [Host, To, PubsubJID, Node, Response]),
156 38 FAcc
157 end,
158 %% The IQ is routed from the recipient's server JID to pubsub JID
159 %% This is recommended in the XEP and also helps process replies to this IQ
160 38 NotificationFrom = jid:make(<<>>, Host, <<>>),
161 38 mod_event_pusher_push:cast(Host, fun ejabberd_local:route_iq/5,
162 [NotificationFrom, PubsubJID, Acc, Stanza, ResponseHandler]).
163
164 -spec handle_publish_response(Host :: jid:server(),
165 Recipient :: jid:jid(), PubsubJID :: jid:jid(),
166 Node :: mod_event_pusher_push:pubsub_node(),
167 Result :: timeout | jlib:iq()) -> ok.
168 handle_publish_response(_Host, _Recipient, _PubsubJID, _Node, timeout) ->
169 2 ok;
170 handle_publish_response(_Host, _Recipient, _PubsubJID, _Node, #iq{type = result}) ->
171 30 ok;
172 handle_publish_response(Host, Recipient, PubsubJID, Node, #iq{type = error, sub_el = Els}) ->
173 4 [Error | _ ] = [Err || #xmlel{name = <<"error">>} = Err <- Els],
174 4 case exml_query:attr(Error, <<"type">>) of
175 <<"cancel">> ->
176 %% We disable the push node in case the error type is cancel
177 1 mod_event_pusher_push:disable_node(Host, Recipient, PubsubJID, Node);
178 _ ->
179 3 ok
180 end.
181
182 -spec push_notification_iq(Node :: mod_event_pusher_push:pubsub_node(),
183 Form :: mod_event_pusher_push:form(),
184 PushPayload :: mod_event_pusher_push_plugin:push_payload()) ->
185 jlib:iq().
186 push_notification_iq(Node, Form, PushPayload) ->
187 38 NotificationFields = [{<<"FORM_TYPE">>, ?PUSH_FORM_TYPE} | PushPayload ],
188
189 38 #iq{type = set, sub_el = [
190 #xmlel{name = <<"pubsub">>, attrs = [{<<"xmlns">>, ?NS_PUBSUB}], children = [
191 #xmlel{name = <<"publish">>, attrs = [{<<"node">>, Node}], children = [
192 #xmlel{name = <<"item">>, children = [
193 #xmlel{name = <<"notification">>,
194 attrs = [{<<"xmlns">>, ?NS_PUSH}],
195 children = [make_form(NotificationFields)]}
196 ]}
197 ]}
198 ] ++ maybe_publish_options(Form)}
199 ]}.
200
201 -spec make_form(mod_event_pusher_push:form()) -> exml:element().
202 make_form(Fields) ->
203 74 #xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"submit">>}],
204 282 children = [make_form_field(Field) || Field <- Fields]}.
205
206 -spec make_form_field(mod_event_pusher_push:form_field()) -> exml:element().
207 make_form_field({Name, Value}) ->
208 282 #xmlel{name = <<"field">>,
209 attrs = [{<<"var">>, Name}],
210 children = [#xmlel{name = <<"value">>, children = [#xmlcdata{content = Value}]}]}.
211
212 -spec maybe_publish_options(mod_event_pusher_push:form()) -> [exml:element()].
213 maybe_publish_options([]) ->
214 2 [];
215 maybe_publish_options(FormFields) ->
216 36 Children = [make_form([{<<"FORM_TYPE">>, ?NS_PUBSUB_PUB_OPTIONS}] ++ FormFields)],
217 36 [#xmlel{name = <<"publish-options">>, children = Children}].
Line Hits Source