1: -module(push_integration_SUITE). 2: -compile([export_all, nowarn_export_all]). 3: 4: -include_lib("common_test/include/ct.hrl"). 5: -include_lib("eunit/include/eunit.hrl"). 6: -include_lib("exml/include/exml.hrl"). 7: -include_lib("inbox.hrl"). 8: 9: -define(RPC_SPEC, distributed_helper:mim()). 10: -define(SESSION_KEY, publish_service). 11: 12: -import(muc_light_helper, 13: [ 14: when_muc_light_message_is_sent/4, 15: then_muc_light_message_is_received_by/2, 16: when_muc_light_affiliations_are_set/3, 17: then_muc_light_affiliations_are_received_by/2 18: ]). 19: -import(push_helper, 20: [ 21: enable_stanza/3, 22: become_unavailable/1, 23: become_available/2 24: ]). 25: -import(distributed_helper, [rpc/4, subhost_pattern/1]). 26: -import(domain_helper, [domain/0]). 27: -import(config_parser_helper, [mod_config/2, config/2]). 28: 29: %%-------------------------------------------------------------------- 30: %% Suite configuration 31: %%-------------------------------------------------------------------- 32: 33: all() -> 34: [ 35: {group, pubsub_ful}, 36: {group, pubsub_less} 37: ]. 38: 39: basic_groups() -> 40: [ 41: {group, pm_msg_notifications}, 42: {group, muclight_msg_notifications}, 43: {group, pm_notifications_with_inbox}, 44: {group, groupchat_notifications_with_inbox}, 45: {group, failure_cases_v3}, 46: {group, failure_cases_v2}, 47: {group, integration_with_sm_and_offline_storage}, 48: {group, enhanced_integration_with_sm} 49: ]. 50: 51: groups() -> 52: G = [ 53: {pubsub_ful, [], basic_groups()}, 54: {pubsub_less, [], basic_groups()}, 55: {integration_with_sm_and_offline_storage,[parallel], 56: [ 57: no_duplicates_default_plugin, 58: sm_unack_messages_notified_default_plugin 59: ]}, 60: {enhanced_integration_with_sm,[], 61: [ 62: immediate_notification, 63: double_notification_with_two_sessions_in_resume 64: ]}, 65: {pm_msg_notifications, [parallel], 66: [ 67: pm_msg_notify_on_apns_w_high_priority, 68: pm_msg_notify_on_fcm_w_high_priority, 69: pm_msg_notify_on_apns_w_high_priority_silent, 70: pm_msg_notify_on_fcm_w_high_priority_silent, 71: pm_msg_notify_on_apns_no_click_action, 72: pm_msg_notify_on_fcm_no_click_action, 73: pm_msg_notify_on_apns_w_click_action, 74: pm_msg_notify_on_fcm_w_click_action, 75: pm_msg_notify_on_apns_silent, 76: pm_msg_notify_on_fcm_silent, 77: pm_msg_notify_on_apns_w_topic 78: ]}, 79: {muclight_msg_notifications, [parallel], 80: [ 81: muclight_msg_notify_on_apns_w_high_priority, 82: muclight_msg_notify_on_fcm_w_high_priority, 83: muclight_msg_notify_on_apns_w_high_priority_silent, 84: muclight_msg_notify_on_fcm_w_high_priority_silent, 85: muclight_msg_notify_on_apns_no_click_action, 86: muclight_msg_notify_on_fcm_no_click_action, 87: muclight_msg_notify_on_apns_w_click_action, 88: muclight_msg_notify_on_fcm_w_click_action, 89: muclight_msg_notify_on_apns_silent, 90: muclight_msg_notify_on_fcm_silent, 91: muclight_msg_notify_on_w_topic 92: ]}, 93: {groupchat_notifications_with_inbox, [parallel], 94: [ 95: muclight_inbox_msg_unread_count_apns, 96: muclight_inbox_msg_unread_count_fcm, 97: muclight_aff_change_fcm, 98: muclight_aff_change_apns 99: ]}, 100: {pm_notifications_with_inbox, [parallel], 101: [ 102: inbox_msg_unread_count_apns, 103: inbox_msg_unread_count_fcm, 104: inbox_msg_reset_unread_count_apns, 105: inbox_msg_reset_unread_count_fcm 106: ]}, 107: {failure_cases_v3, [parallel], failure_cases()}, 108: {failure_cases_v2, [parallel], failure_cases()} 109: ], 110: G. 111: 112: failure_cases() -> 113: [ 114: no_push_notification_for_expired_device, 115: no_push_notification_for_internal_mongoose_push_error 116: ]. 117: 118: suite() -> 119: escalus:suite(). 120: 121: %%-------------------------------------------------------------------- 122: %% Init & teardown 123: %%-------------------------------------------------------------------- 124: 125: init_per_suite(Config) -> 126: catch mongoose_push_mock:stop(), 127: mongoose_push_mock:start(Config), 128: Port = mongoose_push_mock:port(), 129: PoolOpts = #{strategy => available_worker, workers => 20}, 130: ConnOpts = #{host => "https://localhost:" ++ integer_to_list(Port), request_timeout => 2000}, 131: Pool = config([outgoing_pools, http, mongoose_push_http], #{opts => PoolOpts, conn_opts => ConnOpts}), 132: [{ok, _Pid}] = rpc(?RPC_SPEC, mongoose_wpool, start_configured_pools, [[Pool]]), 133: ConfigWithModules = dynamic_modules:save_modules(domain(), Config), 134: escalus:init_per_suite(ConfigWithModules). 135: 136: end_per_suite(Config) -> 137: escalus_fresh:clean(), 138: rpc(?RPC_SPEC, mongoose_wpool, stop, [http, global, mongoose_push_http]), 139: mongoose_push_mock:stop(), 140: escalus:end_per_suite(Config). 141: 142: init_per_group(pubsub_less, Config) -> 143: [{pubsub_host, virtual} | Config]; 144: init_per_group(pubsub_ful, Config) -> 145: [{pubsub_host, real} | Config]; 146: init_per_group(G, Config) when G =:= pm_notifications_with_inbox; 147: G =:= groupchat_notifications_with_inbox; 148: G =:= integration_with_sm_and_offline_storage -> 149: case mongoose_helper:is_rdbms_enabled(domain()) of 150: true -> 151: init_modules(G, Config); 152: _ -> 153: {skip, require_rdbms} 154: end; 155: init_per_group(G, Config) -> 156: %% Some cleaning up 157: C = init_modules(G, Config), 158: ReqMods = proplists:get_value(required_modules, C, []), 159: case lists:keymember(mod_muc_light, 1, ReqMods) of 160: true -> 161: muc_light_helper:clear_db(domain_helper:host_type()); 162: false -> 163: ct:log("Skip muc_light_helper:clear_db()", []) 164: end, 165: C. 166: 167: end_per_group(_, Config) -> 168: dynamic_modules:restore_modules(Config), 169: Config. 170: 171: init_per_testcase(CaseName, Config) -> 172: escalus:init_per_testcase(CaseName, Config). 173: 174: end_per_testcase(CaseName, Config) -> 175: escalus:end_per_testcase(CaseName, Config). 176: 177: %%------------------------------------------------------------------------------------ 178: %% GROUP integration_with_sm_and_offline_storage & enhanced_integration_with_sm 179: %%------------------------------------------------------------------------------------ 180: no_duplicates_default_plugin(Config) -> 181: ConnSteps = [start_stream, stream_features, maybe_use_ssl, 182: authenticate, bind, session], 183: 184: %% connect bob and alice 185: BobSpec = escalus_fresh:create_fresh_user(Config, bob), 186: {ok, Bob, _} = escalus_connection:start(BobSpec), 187: escalus_connection:send(Bob, escalus_stanza:presence(<<"available">>)), 188: BobJID = bare_jid(Bob), 189: 190: AliceSpec = escalus_fresh:create_fresh_user(Config, alice), 191: {ok, Alice, _} = escalus_connection:start(AliceSpec, ConnSteps), 192: escalus_connection:send(Alice, escalus_stanza:presence(<<"available">>)), 193: escalus_connection:get_stanza(Alice, presence), 194: 195: #{device_token := APNSDevice} = enable_push_for_user(Alice, <<"apns">>, [], Config), 196: 197: escalus_connection:stop(Alice), 198: push_helper:wait_for_user_offline(Alice), 199: 200: escalus_connection:send(Bob, escalus_stanza:chat_to(bare_jid(Alice), <<"msg-1">>)), 201: mongoose_helper:wait_until(fun() -> get_number_of_offline_msgs(AliceSpec) end, 1), 202: verify_notification(APNSDevice, <<"apns">>, [], BobJID, <<"msg-1">>), 203: {ok, NewAlice, _} = escalus_connection:start([{manual_ack, true} | AliceSpec], 204: ConnSteps ++ [stream_management]), 205: escalus_connection:send(NewAlice, escalus_stanza:presence(<<"available">>)), 206: Stanzas = escalus:wait_for_stanzas(NewAlice, 2), 207: escalus:assert_many([is_presence, is_chat_message], Stanzas), 208: 209: escalus_connection:stop(NewAlice), 210: mongoose_helper:wait_until(fun() -> get_number_of_offline_msgs(AliceSpec) end, 1), 211: 212: ?assertExit({test_case_failed, _}, wait_for_push_request(APNSDevice, 500)), 213: 214: escalus_connection:stop(Bob). 215: 216: get_number_of_offline_msgs(Spec) -> 217: Username = escalus_utils:jid_to_lower(proplists:get_value(username, Spec)), 218: Server = proplists:get_value(server, Spec), 219: mongoose_helper:total_offline_messages({Username, Server}). 220: 221: sm_unack_messages_notified_default_plugin(Config) -> 222: ConnSteps = [start_stream, stream_features, maybe_use_ssl, 223: authenticate, bind, session, stream_management], 224: 225: %% connect bob and alice 226: BobSpec = escalus_fresh:create_fresh_user(Config, bob), 227: {ok, Bob, _} = escalus_connection:start(BobSpec), 228: escalus_connection:send(Bob, escalus_stanza:presence(<<"available">>)), 229: escalus_connection:get_stanza(Bob, presence), 230: BobJID = bare_jid(Bob), 231: 232: AliceSpec = [{manual_ack, false}, {stream_management, true} | 233: escalus_fresh:create_fresh_user(Config, alice)], 234: {ok, Alice, _} = escalus_connection:start(AliceSpec, ConnSteps), 235: escalus_connection:send(Alice, escalus_stanza:presence(<<"available">>)), 236: escalus_connection:get_stanza(Alice, presence), 237: 238: Room = fresh_room_name(), 239: RoomJID = muc_light_helper:given_muc_light_room(Room, Alice, [{Bob, member}]), 240: 241: #{device_token := FCMDevice} = enable_push_for_user(Alice, <<"fcm">>, [], Config), 242: 243: escalus_connection:send(Bob, escalus_stanza:chat_to(bare_jid(Alice), <<"msg-0">>)), 244: escalus:assert(is_chat_message, [<<"msg-0">>], escalus_connection:get_stanza(Alice, msg)), 245: 246: H = escalus_connection:get_sm_h(Alice), 247: escalus:send(Alice, escalus_stanza:sm_ack(H)), 248: 249: escalus_connection:send(Bob, escalus_stanza:chat_to(bare_jid(Alice), <<"msg-1">>)), 250: escalus:assert(is_chat_message, [<<"msg-1">>], escalus_connection:get_stanza(Alice, msg)), 251: SenderJID = muclight_conversation(Bob, RoomJID, <<"msg-2">>), 252: escalus:assert(is_groupchat_message, [<<"msg-2">>], escalus_connection:get_stanza(Alice, msg)), 253: 254: escalus_connection:stop(Alice), 255: push_helper:wait_for_user_offline(Alice), 256: 257: verify_notification(FCMDevice, <<"fcm">>, [], [{SenderJID, <<"msg-2">>}, 258: {BobJID, <<"msg-1">>}]), 259: 260: ?assertExit({test_case_failed, _}, wait_for_push_request(FCMDevice, 500)), 261: 262: escalus_connection:stop(Bob). 263: 264: immediate_notification(Config) -> 265: ConnSteps = [start_stream, stream_features, maybe_use_ssl, 266: authenticate, bind, session, stream_resumption], 267: 268: %% connect bob and alice 269: BobSpec = escalus_fresh:create_fresh_user(Config, bob), 270: {ok, Bob, _} = escalus_connection:start(BobSpec), 271: escalus_connection:send(Bob, escalus_stanza:presence(<<"available">>)), 272: escalus_connection:get_stanza(Bob, presence), 273: BobJID = bare_jid(Bob), 274: 275: AliceSpec = [{manual_ack, false}, {stream_management, true} | 276: escalus_fresh:create_fresh_user(Config, alice)], 277: {ok, Alice, _} = escalus_connection:start(AliceSpec, ConnSteps), 278: escalus_connection:send(Alice, escalus_stanza:presence(<<"available">>)), 279: escalus_connection:get_stanza(Alice, presence), 280: 281: #{device_token := APNSDevice} = enable_push_for_user(Alice, <<"apns">>, [], Config), 282: #{device_token := FCMDevice} = enable_push_for_user(Alice, <<"fcm">>, [], Config), 283: 284: escalus_connection:send(Bob, escalus_stanza:chat_to(bare_jid(Alice), <<"msg-0">>)), 285: escalus:assert(is_chat_message, [<<"msg-0">>], escalus_connection:get_stanza(Alice, msg)), 286: 287: H = escalus_connection:get_sm_h(Alice), 288: escalus:send(Alice, escalus_stanza:sm_ack(H)), 289: 290: escalus_connection:send(Bob, escalus_stanza:chat_to(bare_jid(Alice), <<"msg-1">>)), 291: escalus:assert(is_chat_message, [<<"msg-1">>], escalus_connection:get_stanza(Alice, msg)), 292: 293: C2SPid = mongoose_helper:get_session_pid(Alice, distributed_helper:mim()), 294: escalus_connection:kill(Alice), 295: 296: verify_notification(FCMDevice, <<"fcm">>, [], BobJID, <<"msg-1">>), 297: 298: escalus_connection:send(Bob, escalus_stanza:chat_to(bare_jid(Alice), <<"msg-2">>)), 299: verify_notification(FCMDevice, <<"fcm">>, [], BobJID, <<"msg-2">>), 300: 301: ?assertExit({test_case_failed, _}, wait_for_push_request(APNSDevice, 500)), 302: 303: rpc(?RPC_SPEC, sys, terminate, [C2SPid, normal]), 304: 305: verify_notification(APNSDevice, <<"apns">>, [], [{BobJID, <<"msg-1">>}, 306: {BobJID, <<"msg-2">>}]), 307: 308: ?assertExit({test_case_failed, _}, wait_for_push_request(FCMDevice, 500)), 309: 310: escalus_connection:stop(Bob). 311: 312: double_notification_with_two_sessions_in_resume(Config) -> 313: 314: %% This test case serves as a demonstration of doubled push notifications 315: %% which occur with multiple push devices and sessions in resume state 316: 317: %% diagram presenting the test's logic 318: %% generated using https://www.sequencediagram.org/ 319: %% title double notifications in 2 sessions in resume 320: %% BobSocket -> BobC2S: msg-1 321: %% participant Alice1C2S #red 322: %% participant Alice2C2S #blue 323: %% BobC2S -#red> Alice1C2S: msg-1 324: %% BobC2S -#blue> Alice2C2S: msg-1 325: %% note over Alice1C2S: connection dies 326: %% activate Alice1C2S 327: %% note over Alice2C2S: connection dies 328: %% activate Alice2C2S 329: %% Alice1C2S -#red> PushService: msg-1 (alice1 device) 330: %% deactivate Alice1C2S 331: %% Alice2C2S -#blue> PushService: msg-1 (alice2 device) 332: %% deactivate Alice2C2S 333: %% note over Alice1C2S: resumption t/o 334: %% activate Alice1C2S 335: %% Alice1C2S -#red> Alice2C2S: msg-1 336: %% activate Alice2C2S #red 337: %% deactivate Alice1C2S 338: %% destroyafter Alice1C2S 339: %% Alice2C2S -#red> PushService: msg-1 (alice2 device) 340: %% deactivate Alice2C2S 341: %% note over Alice2C2S: resumption t/o 342: %% activate Alice2C2S 343: %% Alice2C2S -#blue> PushService: msg-1 (alice1 device) 344: %% deactivate Alice2C2S 345: %% destroyafter Alice2C2S 346: 347: ConnSteps = [start_stream, stream_features, maybe_use_ssl, 348: authenticate, bind, session, stream_resumption], 349: 350: %% connect bob 351: BobSpec = escalus_fresh:create_fresh_user(Config, bob), 352: {ok, Bob, _} = escalus_connection:start(BobSpec), 353: escalus_session:send_presence_available(Bob), 354: escalus_connection:get_stanza(Bob, presence), 355: BobJID = bare_jid(Bob), 356: 357: %% connect two resources for alice 358: AliceSpec1 = [{manual_ack, false}, {stream_resumption, true} | 359: escalus_fresh:create_fresh_user(Config, alice)], 360: AliceSpec2 = [{resource,<<"RES2">>} | AliceSpec1], 361: 362: {ok, Alice1, _} = escalus_connection:start(AliceSpec1, ConnSteps), 363: {ok, Alice2, _} = escalus_connection:start(AliceSpec2, ConnSteps), 364: 365: escalus_session:send_presence_available(Alice1), 366: escalus_connection:get_stanza(Alice1, presence), 367: 368: escalus_session:send_presence_available(Alice2), 369: escalus_connection:get_stanza(Alice2, presence), 370: 371: escalus_connection:get_stanza(Alice1, presence), 372: escalus_connection:get_stanza(Alice2, presence), 373: 374: #{device_token := APNSDevice1} = enable_push_for_user(Alice1, <<"apns">>, [], Config), 375: #{device_token := APNSDevice2} = enable_push_for_user(Alice2, <<"apns">>, [], Config), 376: 377: escalus_connection:send(Bob, escalus_stanza:chat_to(bare_jid(Alice1), <<"msg-1">>)), 378: escalus:assert(is_chat_message, [<<"msg-1">>], escalus_connection:get_stanza(Alice1, msg)), 379: escalus:assert(is_chat_message, [<<"msg-1">>], escalus_connection:get_stanza(Alice2, msg)), 380: 381: %% go into resume state, which should fire a hook which pushes notifications 382: C2SPid1 = mongoose_helper:get_session_pid(Alice1, distributed_helper:mim()), 383: escalus_connection:kill(Alice1), 384: C2SPid2 = mongoose_helper:get_session_pid(Alice2, distributed_helper:mim()), 385: escalus_connection:kill(Alice2), 386: 387: verify_notification(APNSDevice1, <<"apns">>, [], [{BobJID, <<"msg-1">>}]), 388: verify_notification(APNSDevice2, <<"apns">>, [], [{BobJID, <<"msg-1">>}]), 389: 390: ?assertExit({test_case_failed, _}, wait_for_push_request(APNSDevice1, 500)), 391: ?assertExit({test_case_failed, _}, wait_for_push_request(APNSDevice2, 1)), 392: 393: %% close xmpp stream for Alice1, which causes push notification for APNSDevice2 394: rpc(?RPC_SPEC, sys, terminate, [C2SPid1, normal]), 395: 396: verify_notification(APNSDevice2, <<"apns">>, [], [{BobJID, <<"msg-1">>}]), 397: ?assertExit({test_case_failed, _}, wait_for_push_request(APNSDevice1, 500)), 398: 399: %% close xmpp stream for Alice2, which causes push notification for APNSDevice1 400: rpc(?RPC_SPEC, sys, terminate, [C2SPid2, normal]), 401: 402: verify_notification(APNSDevice1, <<"apns">>, [], [{BobJID, <<"msg-1">>}]), 403: ?assertExit({test_case_failed, _}, wait_for_push_request(APNSDevice2, 500)), 404: 405: escalus_connection:stop(Bob). 406: 407: verify_notification(DeviceToken, Service, EnableOpts, Jid, Msg) -> 408: verify_notification(DeviceToken, Service, EnableOpts, [{Jid, Msg}]). 409: 410: verify_notification(DeviceToken, Service, EnableOpts, ParamsList) -> 411: PredGen = fun({Jid, Msg}) -> 412: fun(Notification) -> 413: try 414: Expected = [{body, Msg}, {unread_count, 1}, {badge, 1}], 415: assert_push_notification(Notification, Service, EnableOpts, 416: Jid, Expected), 417: true 418: catch 419: _:_ -> false 420: end 421: end 422: end, 423: Notifications = [begin 424: {Notification, _} = wait_for_push_request(DeviceToken), 425: Notification 426: end || _ <- ParamsList], 427: ?assertEqual(true, escalus_utils:mix_match(PredGen, ParamsList, Notifications)). 428: 429: %%-------------------------------------------------------------------- 430: %% GROUP pm_msg_notifications 431: %%-------------------------------------------------------------------- 432: 433: pm_msg_notify_on_apns(Config, EnableOpts) -> 434: escalus:fresh_story( 435: Config, [{bob, 1}, {alice, 1}], 436: fun(Bob, Alice) -> 437: {SenderJID, DeviceToken} = pm_conversation(Alice, Bob, <<"apns">>, EnableOpts, Config), 438: {Notification, _} = wait_for_push_request(DeviceToken), 439: 440: assert_push_notification(Notification, <<"apns">>, EnableOpts, SenderJID, []) 441: 442: end). 443: 444: pm_msg_notify_on_fcm(Config, EnableOpts) -> 445: escalus:fresh_story( 446: Config, [{bob, 1}, {alice, 1}], 447: fun(Bob, Alice) -> 448: {SenderJID, DeviceToken} = pm_conversation(Alice, Bob, <<"fcm">>, EnableOpts, Config), 449: {Notification, _} = wait_for_push_request(DeviceToken), 450: 451: assert_push_notification(Notification, <<"fcm">>, EnableOpts, SenderJID) 452: 453: end). 454: 455: assert_push_notification(Notification, Service, EnableOpts, SenderJID) -> 456: assert_push_notification(Notification, Service, EnableOpts, SenderJID, []). 457: 458: assert_push_notification(Notification, Service, EnableOpts, SenderJID, Expected) -> 459: 460: ?assertMatch(#{<<"service">> := Service}, Notification), 461: 462: Alert = maps:get(<<"alert">>, Notification, undefined), 463: Data = maps:get(<<"data">>, Notification, undefined), 464: 465: ExpectedBody = proplists:get_value(body, Expected, <<"OH, HAI!">>), 466: UnreadCount = proplists:get_value(unread_count, Expected, 1), 467: Badge = proplists:get_value(badge, Expected, 1), 468: 469: case proplists:get_value(<<"silent">>, EnableOpts) of 470: undefined -> 471: ?assertMatch(#{<<"body">> := ExpectedBody}, Alert), 472: ?assertMatch(#{<<"title">> := SenderJID}, Alert), 473: ?assertMatch(#{<<"badge">> := Badge}, Alert), 474: ?assertMatch(#{<<"tag">> := SenderJID}, Alert), 475: 476: case proplists:get_value(<<"click_action">>, EnableOpts) of 477: undefined -> 478: ?assertEqual(error, maps:find(<<"click_action">>, Alert)); 479: Activity -> 480: ?assertMatch(#{<<"click_action">> := Activity}, Alert) 481: end; 482: <<"true">> -> 483: ?assertMatch(#{<<"last-message-body">> := ExpectedBody}, Data), 484: ?assertMatch(#{<<"last-message-sender">> := SenderJID}, Data), 485: ?assertMatch(#{<<"message-count">> := UnreadCount}, Data) 486: end, 487: 488: case proplists:get_value(<<"priority">>, EnableOpts) of 489: undefined -> ok; 490: Priority -> 491: ?assertMatch(Priority, maps:get(<<"priority">>, Notification, undefined)) 492: end, 493: 494: case proplists:get_value(<<"topic">>, EnableOpts) of 495: undefined -> ok; 496: Topic -> 497: ?assertMatch(Topic, maps:get(<<"topic">>, Notification, undefined)) 498: end. 499: 500: 501: pm_msg_notify_on_apns_no_click_action(Config) -> 502: pm_msg_notify_on_apns(Config, []). 503: 504: pm_msg_notify_on_fcm_no_click_action(Config) -> 505: pm_msg_notify_on_fcm(Config, []). 506: 507: pm_msg_notify_on_apns_w_high_priority(Config) -> 508: pm_msg_notify_on_apns(Config, [{<<"priority">>, <<"high">>}]). 509: 510: pm_msg_notify_on_fcm_w_high_priority(Config) -> 511: pm_msg_notify_on_fcm(Config, [{<<"priority">>, <<"high">>}]). 512: 513: pm_msg_notify_on_apns_w_high_priority_silent(Config) -> 514: pm_msg_notify_on_apns(Config, [{<<"silent">>, <<"true">>}, {<<"priority">>, <<"high">>}]). 515: 516: pm_msg_notify_on_fcm_w_high_priority_silent(Config) -> 517: pm_msg_notify_on_fcm(Config, [{<<"silent">>, <<"true">>}, {<<"priority">>, <<"high">>}]). 518: 519: pm_msg_notify_on_apns_w_click_action(Config) -> 520: pm_msg_notify_on_apns(Config, [{<<"click_action">>, <<"myactivity">>}]). 521: 522: pm_msg_notify_on_fcm_w_click_action(Config) -> 523: pm_msg_notify_on_fcm(Config, [{<<"click_action">>, <<"myactivity">>}]). 524: 525: pm_msg_notify_on_fcm_silent(Config) -> 526: pm_msg_notify_on_fcm(Config, [{<<"silent">>, <<"true">>}]). 527: 528: pm_msg_notify_on_apns_silent(Config) -> 529: pm_msg_notify_on_apns(Config, [{<<"silent">>, <<"true">>}]). 530: 531: pm_msg_notify_on_apns_w_topic(Config) -> 532: pm_msg_notify_on_apns(Config, [{<<"topic">>, <<"some_topic">>}]). 533: 534: 535: %%-------------------------------------------------------------------- 536: %% GROUP inbox_msg_notifications 537: %%-------------------------------------------------------------------- 538: 539: inbox_msg_unread_count_apns(Config) -> 540: inbox_msg_unread_count(Config, <<"apns">>, [{<<"silent">>, <<"true">>}]). 541: 542: inbox_msg_unread_count_fcm(Config) -> 543: inbox_msg_unread_count(Config, <<"fcm">>, [{<<"silent">>, <<"true">>}]). 544: 545: muclight_inbox_msg_unread_count_apns(Config) -> 546: muclight_inbox_msg_unread_count(Config, <<"apns">>, [{<<"silent">>, <<"true">>}]). 547: 548: muclight_inbox_msg_unread_count_fcm(Config) -> 549: muclight_inbox_msg_unread_count(Config, <<"fcm">>, [{<<"silent">>, <<"true">>}]). 550: 551: inbox_msg_reset_unread_count_apns(Config) -> 552: inbox_msg_reset_unread_count(Config, <<"apns">>, [{<<"silent">>, <<"true">>}]). 553: 554: inbox_msg_reset_unread_count_fcm(Config) -> 555: inbox_msg_reset_unread_count(Config, <<"fcm">>, [{<<"silent">>, <<"true">>}]). 556: 557: inbox_msg_unread_count(Config, Service, EnableOpts) -> 558: escalus:fresh_story( 559: Config, [{bob, 1}, {alice, 1}, {kate, 1}], 560: fun(Bob, Alice, Kate) -> 561: % In this test Bob is the only recipient of all messages 562: #{device_token := DeviceToken} = 563: enable_push_and_become_unavailable(Bob, Service, EnableOpts, Config), 564: 565: % We're going to interleave messages from Alice and Kate to ensure 566: % that their unread counts don't leak to each other's notifications 567: 568: % We send a first message from Alice, unread counts in convs.: Alice 1, Kate 0 569: send_private_message(Alice, Bob), 570: check_notification(DeviceToken, 1), 571: 572: % We send a first message from Kate, unread counts in convs.: Alice 1, Kate 1 573: send_private_message(Kate, Bob), 574: check_notification(DeviceToken, 1), 575: 576: % Now a second message from Alice, unread counts in convs.: Alice 2, Kate 1 577: send_private_message(Alice, Bob), 578: check_notification(DeviceToken, 2), 579: 580: % And one more from Alice, unread counts in convs.: Alice 3, Kate 1 581: send_private_message(Alice, Bob), 582: check_notification(DeviceToken, 3), 583: 584: % Time for Kate again, unread counts in convs.: Alice 3, Kate 2 585: send_private_message(Kate, Bob), 586: check_notification(DeviceToken, 2) 587: end). 588: 589: inbox_msg_reset_unread_count(Config, Service, EnableOpts) -> 590: escalus:fresh_story( 591: Config, [{bob, 1}, {alice, 1}], 592: fun(Bob, Alice) -> 593: #{device_token := DeviceToken} = 594: enable_push_and_become_unavailable(Bob, Service, EnableOpts, Config), 595: send_private_message(Alice, Bob, <<"FIRST MESSAGE">>), 596: check_notification(DeviceToken, 1), 597: MsgId = send_private_message(Alice, Bob, <<"SECOND MESSAGE">>), 598: check_notification(DeviceToken, 2), 599: 600: become_available(Bob, 2), 601: inbox_helper:get_inbox(Bob, #{ count => 1 }), 602: ChatMarker = escalus_stanza:chat_marker(Alice, <<"displayed">>, MsgId), 603: escalus:send(Bob, ChatMarker), 604: escalus:wait_for_stanza(Alice), 605: 606: become_unavailable(Bob), 607: send_private_message(Alice, Bob, <<"THIRD MESSAGE">>), 608: check_notification(DeviceToken, 1) 609: end). 610: 611: 612: muclight_inbox_msg_unread_count(Config, Service, EnableOpts) -> 613: escalus:fresh_story( 614: Config, [{alice, 1}, {kate, 1}], 615: fun(Alice, Kate) -> 616: Room = fresh_room_name(), 617: RoomJID = muc_light_helper:given_muc_light_room(Room, Alice, []), 618: KateJid = inbox_helper:to_bare_lower(Kate), 619: 620: when_muc_light_affiliations_are_set(Alice, Room, [{Kate, member}]), 621: muc_light_helper:verify_aff_bcast([{Kate, member}, {Alice, owner}], [{Kate, member}]), 622: escalus:wait_for_stanza(Alice), 623: 624: #{device_token := KateToken} = 625: enable_push_and_become_unavailable(Kate, Service, EnableOpts, Config), 626: 627: SenderJID = muclight_conversation(Alice, RoomJID, <<"First!">>), 628: escalus:wait_for_stanza(Alice), 629: {Notification, _} = wait_for_push_request(KateToken), 630: assert_push_notification(Notification, Service, EnableOpts, SenderJID, 631: [{body, <<"First!">>}, {unread_count, 1}, {badge, 1}]), 632: 633: muclight_conversation(Alice, RoomJID, <<"Second!">>), 634: escalus:wait_for_stanza(Alice), 635: {Notification2, _} = wait_for_push_request(KateToken), 636: assert_push_notification(Notification2, Service, EnableOpts, SenderJID, 637: [{body, <<"Second!">>}, {unread_count, 2}, {badge, 1}]), 638: 639: {ok, true} = become_available(Kate, 0), 640: 641: muclight_conversation(Alice, RoomJID, <<"Third!">>), 642: escalus:wait_for_stanza(Kate), 643: escalus:wait_for_stanza(Alice), 644: inbox_helper:check_inbox(Kate, [#conv{unread = 3, 645: from = SenderJID, 646: to = KateJid, 647: content = <<"Third!">>}]) 648: end). 649: 650: send_private_message(Sender, Recipient) -> 651: send_private_message(Sender, Recipient, <<"Private message">>). 652: 653: send_private_message(Sender, Recipient, Body) -> 654: Id = escalus_stanza:id(), 655: Msg = escalus_stanza:set_id( escalus_stanza:chat_to(bare_jid(Recipient), Body), Id), 656: escalus:send(Sender, Msg), 657: Id. 658: 659: check_notification(DeviceToken, ExpectedCount) -> 660: {Notification, _} = wait_for_push_request(DeviceToken), 661: Data = maps:get(<<"data">>, Notification, undefined), 662: ?assertMatch(#{<<"message-count">> := ExpectedCount}, Data). 663: 664: send_message_to_room(Sender, RoomJID) -> 665: Stanza = escalus_stanza:groupchat_to(RoomJID, <<"GroupChat message">>), 666: escalus:send(Sender, Stanza). 667: 668: 669: %%-------------------------------------------------------------------- 670: %% GROUP muclight_msg_notifications 671: %%-------------------------------------------------------------------- 672: 673: muclight_msg_notify_on_apns(Config, EnableOpts) -> 674: escalus:fresh_story( 675: Config, [{alice, 1}, {bob, 1}], 676: fun(Alice, Bob) -> 677: Room = fresh_room_name(), 678: RoomJID = muc_light_helper:given_muc_light_room(Room, Alice, [{Bob, member}]), 679: #{device_token := DeviceToken} = 680: enable_push_and_become_unavailable(Bob, <<"apns">>, EnableOpts, Config), 681: 682: SenderJID = muclight_conversation(Alice, RoomJID, <<"Heyah!">>), 683: {Notification, _} = wait_for_push_request(DeviceToken), 684: assert_push_notification(Notification, <<"apns">>, EnableOpts, SenderJID, 685: [{body, <<"Heyah!">>}, {unread_count, 1}, {badge, 1}]) 686: end). 687: 688: muclight_msg_notify_on_fcm(Config, EnableOpts) -> 689: escalus:fresh_story( 690: Config, [{alice, 1}, {bob, 1}], 691: fun(Alice, Bob) -> 692: Room = fresh_room_name(), 693: RoomJID = muc_light_helper:given_muc_light_room(Room, Alice, [{Bob, member}]), 694: #{device_token := DeviceToken} = 695: enable_push_and_become_unavailable(Bob, <<"fcm">>, EnableOpts, Config), 696: 697: SenderJID = muclight_conversation(Alice, RoomJID, <<"Heyah!">>), 698: {Notification, _} = wait_for_push_request(DeviceToken), 699: assert_push_notification(Notification, <<"fcm">>, 700: EnableOpts, SenderJID, [{body, <<"Heyah!">>}, {unread_count, 1}, {badge, 1}]) 701: end). 702: 703: muclight_aff_change(Config, Service, EnableOpts) -> 704: escalus:fresh_story( 705: Config, [{alice, 1}, {kate, 1}, {bob, 1}], 706: fun(Alice, Kate, Bob) -> 707: Room = fresh_room_name(), 708: RoomJID = muc_light_helper:given_muc_light_room(Room, Alice, []), 709: 710: {_, Affiliations} = when_muc_light_affiliations_are_set(Alice, Room, [{Kate, member}]), 711: then_muc_light_affiliations_are_received_by([Alice, Kate], {Room, Affiliations}), 712: escalus:wait_for_stanza(Alice), 713: 714: #{device_token := KateToken} = 715: enable_push_and_become_unavailable(Kate, Service, EnableOpts, Config), 716: 717: Bare = bare_jid(Alice), 718: SenderJID = <<RoomJID/binary, "/", Bare/binary>>, 719: 720: {Room, Body, M1} = when_muc_light_message_is_sent(Alice, Room, <<"First!">>, <<"M1">>), 721: then_muc_light_message_is_received_by([Alice], {Room, Body, M1}), 722: 723: {Notification, _} = wait_for_push_request(KateToken), 724: assert_push_notification(Notification, Service, EnableOpts, SenderJID, 725: [{body, <<"First!">>}, {unread_count, 1}, {badge, 1}]), 726: 727: {_, Aff} = when_muc_light_affiliations_are_set(Alice, Room, [{Bob, member}]), 728: then_muc_light_affiliations_are_received_by([Alice, Bob], {Room, Aff}), 729: escalus:wait_for_stanza(Alice), 730: 731: {_, B2, M2} = when_muc_light_message_is_sent(Alice, Room, <<"Second!">>, <<"M2">>), 732: then_muc_light_message_is_received_by([Alice, Bob], {Room, B2, M2}), 733: 734: {Notification2, _} = wait_for_push_request(KateToken), 735: assert_push_notification(Notification2, Service, EnableOpts, SenderJID, 736: [{body, <<"Second!">>}, {unread_count, 2}, {badge, 1}]) 737: 738: end). 739: 740: 741: muclight_msg_notify_on_apns_no_click_action(Config) -> 742: muclight_msg_notify_on_apns(Config, []). 743: 744: muclight_msg_notify_on_fcm_no_click_action(Config) -> 745: muclight_msg_notify_on_fcm(Config, []). 746: 747: muclight_msg_notify_on_apns_w_high_priority(Config) -> 748: muclight_msg_notify_on_apns(Config, [{<<"priority">>, <<"high">>}]). 749: 750: muclight_msg_notify_on_fcm_w_high_priority(Config) -> 751: muclight_msg_notify_on_fcm(Config, [{<<"priority">>, <<"high">>}]). 752: 753: muclight_msg_notify_on_apns_w_high_priority_silent(Config) -> 754: muclight_msg_notify_on_apns(Config, [{<<"silent">>, <<"true">>}, {<<"priority">>, <<"high">>}]). 755: 756: muclight_msg_notify_on_fcm_w_high_priority_silent(Config) -> 757: muclight_msg_notify_on_fcm(Config, [{<<"silent">>, <<"true">>}, {<<"priority">>, <<"high">>}]). 758: 759: muclight_msg_notify_on_apns_w_click_action(Config) -> 760: muclight_msg_notify_on_apns(Config, [{<<"click_action">>, <<"myactivity">>}]). 761: 762: muclight_msg_notify_on_fcm_w_click_action(Config) -> 763: muclight_msg_notify_on_fcm(Config, [{<<"click_action">>, <<"myactivity">>}]). 764: 765: muclight_msg_notify_on_fcm_silent(Config) -> 766: muclight_msg_notify_on_fcm(Config, [{<<"silent">>, <<"true">>}]). 767: 768: muclight_msg_notify_on_apns_silent(Config) -> 769: muclight_msg_notify_on_apns(Config, [{<<"silent">>, <<"true">>}]). 770: 771: muclight_msg_notify_on_w_topic(Config) -> 772: muclight_msg_notify_on_apns(Config, [{<<"topic">>, <<"some_topic">>}]). 773: 774: muclight_aff_change_fcm(Config) -> 775: muclight_aff_change(Config, <<"fcm">>, [{<<"silent">>, <<"true">>}]). 776: 777: muclight_aff_change_apns(Config) -> 778: muclight_aff_change(Config, <<"apns">>, [{<<"silent">>, <<"true">>}]). 779: 780: no_push_notification_for_expired_device(Config) -> 781: escalus:fresh_story( 782: Config, [{bob, 1}, {alice, 1}], 783: fun(Bob, Alice) -> 784: Response = mongoose_push_unregistered_device_resp(Config), 785: #{device_token := DeviceToken, pubsub_node := PushNode} = 786: enable_push_and_become_unavailable(Bob, <<"fcm">>, [], Response, Config), 787: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 788: {_, Response} = wait_for_push_request(DeviceToken), 789: maybe_check_if_push_node_was_disabled(?config(api_v, Config), Bob, PushNode) 790: 791: end). 792: 793: mongoose_push_unregistered_device_resp(Config) -> 794: case ?config(api_v, Config) of 795: <<"v3">> -> 796: {410, jiffy:encode(#{<<"reason">> => <<"unregistered">>})}; 797: <<"v2">> -> 798: {500, jiffy:encode(#{<<"details">> => <<"probably_unregistered">>})} 799: end. 800: 801: maybe_check_if_push_node_was_disabled(<<"v2">>, _, _) -> 802: ok; 803: maybe_check_if_push_node_was_disabled(<<"v3">>, User, PushNode) -> 804: JID = rpc(?RPC_SPEC, jid, binary_to_bare, [escalus_utils:get_jid(User)]), 805: Host = escalus_utils:get_server(User), 806: Fun = fun() -> 807: {ok, Services} = rpc(?RPC_SPEC, mod_event_pusher_push_backend, get_publish_services, [Host, JID]), 808: lists:keymember(PushNode, 2, Services) 809: end, 810: mongoose_helper:wait_until(Fun, false), 811: 812: Fun2 = fun() -> 813: Info = mongoose_helper:get_session_info(?RPC_SPEC, User), 814: maps:get(?SESSION_KEY, Info, false) 815: end, 816: mongoose_helper:wait_until(Fun2, false). 817: 818: no_push_notification_for_internal_mongoose_push_error(Config) -> 819: escalus:fresh_story( 820: Config, [{bob, 1}, {alice, 1}], 821: fun(Bob, Alice) -> 822: Response = {503, jiffy:encode(#{<<"reason">> => <<"unspecified">>})}, 823: #{device_token := DeviceToken} = 824: enable_push_and_become_unavailable(Bob, <<"fcm">>, [], Response, Config), 825: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 826: {_, Response} = wait_for_push_request(DeviceToken) 827: 828: end). 829: 830: 831: %%-------------------------------------------------------------------- 832: %% Test helpers 833: %%-------------------------------------------------------------------- 834: 835: muclight_conversation(Sender, RoomJID, Msg) -> 836: Bare = bare_jid(Sender), 837: SenderJID = <<RoomJID/binary, "/", Bare/binary>>, 838: Stanza = escalus_stanza:groupchat_to(RoomJID, Msg), 839: escalus:send(Sender, Stanza), 840: SenderJID. 841: 842: pm_conversation(Alice, Bob, Service, EnableOpts, Config) -> 843: AliceJID = bare_jid(Alice), 844: #{device_token := DeviceToken} = 845: enable_push_and_become_unavailable(Bob, Service, EnableOpts, Config), 846: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 847: {AliceJID, DeviceToken}. 848: 849: enable_push_and_become_unavailable(User, Service, EnableOpts, Config) -> 850: enable_push_and_become_unavailable(User, Service, EnableOpts, {200, <<"OK">>}, Config). 851: 852: enable_push_and_become_unavailable(User, Service, EnableOpts, MockResponse, Config) -> 853: Ret = enable_push_for_user(User, Service, EnableOpts, MockResponse, Config), 854: become_unavailable(User), 855: Ret. 856: 857: enable_push_for_user(User, Service, EnableOpts, Config) -> 858: enable_push_for_user(User, Service, EnableOpts, {200, <<"OK">>}, Config). 859: 860: enable_push_for_user(User, Service, EnableOpts, MockResponse, Config) -> 861: Node = {PubsubJID, NodeName} = pubsub_node_from_host(Config), 862: 863: DeviceToken = gen_token(), 864: 865: case ?config(pubsub_host, Config) of 866: real -> 867: Configuration = [{<<"pubsub#access_model">>, <<"whitelist">>}, 868: {<<"pubsub#publish_model">>, <<"publishers">>}], 869: pubsub_tools:create_node(User, Node, [{type, <<"push">>}, 870: {config, Configuration}]), 871: add_user_server_to_whitelist(User, Node); 872: _ -> 873: skip 874: end, 875: 876: escalus:send(User, enable_stanza(PubsubJID, NodeName, 877: [{<<"service">>, Service}, 878: {<<"device_id">>, DeviceToken}] ++ EnableOpts)), 879: escalus:assert(is_iq_result, escalus:wait_for_stanza(User)), 880: 881: assert_push_notification_in_session(User, NodeName, Service, DeviceToken), 882: 883: mongoose_push_mock:subscribe(DeviceToken, MockResponse), 884: #{device_token => DeviceToken, 885: pubsub_node => NodeName}. 886: 887: add_user_server_to_whitelist(User, {NodeAddr, NodeName}) -> 888: AffList = [ #xmlel{ name = <<"affiliation">>, 889: attrs = [{<<"jid">>, escalus_utils:get_server(User)}, 890: {<<"affiliation">>, <<"publish-only">>}] } 891: ], 892: Affiliations = #xmlel{ name = <<"affiliations">>, attrs = [{<<"node">>, NodeName}], 893: children = AffList }, 894: Id = base64:encode(crypto:strong_rand_bytes(5)), 895: Stanza = escalus_pubsub_stanza:pubsub_owner_iq(<<"set">>, User, Id, NodeAddr, [Affiliations]), 896: escalus:send(User, Stanza), 897: escalus:assert(is_iq_result, [Stanza], escalus:wait_for_stanza(User)). 898: 899: assert_push_notification_in_session(User, NodeName, Service, DeviceToken) -> 900: Info = mongoose_helper:get_session_info(?RPC_SPEC, User), 901: {_JID, NodeName, Details} = maps:get(?SESSION_KEY, Info), 902: ?assertMatch({<<"service">>, Service}, lists:keyfind(<<"service">>, 1, Details)), 903: ?assertMatch({<<"device_id">>, DeviceToken}, lists:keyfind(<<"device_id">>, 1, Details)). 904: 905: wait_for_push_request(DeviceToken) -> 906: mongoose_push_mock:wait_for_push_request(DeviceToken, 10000). 907: 908: wait_for_push_request(DeviceToken, Timeout) -> 909: mongoose_push_mock:wait_for_push_request(DeviceToken, Timeout). 910: 911: %% ---------------------------------- 912: %% Other helpers 913: %% ---------------------------------- 914: 915: fresh_room_name(Username) -> 916: escalus_utils:jid_to_lower(<<"room-", Username/binary>>). 917: 918: fresh_room_name() -> 919: fresh_room_name(base16:encode(crypto:strong_rand_bytes(5))). 920: 921: 922: bare_jid(JIDOrClient) -> 923: ShortJID = escalus_client:short_jid(JIDOrClient), 924: escalus_utils:jid_to_lower(ShortJID). 925: 926: gen_token() -> 927: integer_to_binary(binary:decode_unsigned(crypto:strong_rand_bytes(16)), 24). 928: 929: lower(Bin) when is_binary(Bin) -> 930: list_to_binary(string:to_lower(binary_to_list(Bin))). 931: 932: pubsub_node_from_host(Config) -> 933: case ?config(pubsub_host, Config) of 934: virtual -> 935: pubsub_tools:pubsub_node_with_subdomain("virtual."); 936: real -> 937: pubsub_tools:pubsub_node() 938: end. 939: 940: getenv(VarName, Default) -> 941: case os:getenv(VarName) of 942: false -> 943: Default; 944: Value -> 945: Value 946: end. 947: 948: init_modules(G, Config) -> 949: MongoosePushAPI = mongoose_push_api_for_group(G), 950: PubSubHost = ?config(pubsub_host, Config), 951: Modules = required_modules_for_group(G, MongoosePushAPI, PubSubHost), 952: C = dynamic_modules:save_modules(domain(), Config), 953: Fun = fun() -> catch dynamic_modules:ensure_modules(domain(), Modules) end, 954: mongoose_helper:wait_until(Fun, ok), 955: [{api_v, MongoosePushAPI}, {required_modules, Modules} | C]. 956: 957: mongoose_push_api_for_group(failure_cases_v2) -> 958: <<"v2">>; 959: mongoose_push_api_for_group(_) -> 960: <<"v3">>. 961: 962: required_modules_for_group(pm_notifications_with_inbox, API, PubSubHost) -> 963: [{mod_inbox, inbox_opts()}, 964: {mod_offline, config_parser_helper:mod_config(mod_offline, #{})} | 965: required_modules(API, PubSubHost)]; 966: required_modules_for_group(groupchat_notifications_with_inbox, API, PubSubHost)-> 967: [{mod_inbox, inbox_opts()}, {mod_muc_light, muc_light_opts()} 968: | required_modules(API, PubSubHost)]; 969: required_modules_for_group(muclight_msg_notifications, API, PubSubHost) -> 970: [{mod_muc_light, muc_light_opts()} | required_modules(API, PubSubHost)]; 971: required_modules_for_group(integration_with_sm_and_offline_storage, API, PubSubHost) -> 972: [{mod_muc_light, muc_light_opts()}, 973: {mod_stream_management, config_parser_helper:mod_config(mod_stream_management, 974: #{ack_freq => never, resume_timeout => 1})}, 975: {mod_offline, config_parser_helper:mod_config(mod_offline, #{})} | 976: required_modules(API, PubSubHost)]; 977: required_modules_for_group(enhanced_integration_with_sm, API, PubSubHost) -> 978: [{mod_stream_management, config_parser_helper:mod_config(mod_stream_management, #{ack_freq => never})} | 979: required_modules(API, PubSubHost, enhanced_plugin_module_opts())]; 980: required_modules_for_group(_, API, PubSubHost) -> 981: required_modules(API, PubSubHost). 982: 983: required_modules(API, PubSubHost)-> 984: required_modules(API, PubSubHost, #{}). 985: 986: required_modules(API, PubSubHost, ExtraPushOpts) -> 987: PubSubHostOpts = virtual_pubsub_hosts_opts(PubSubHost), 988: PushOpts = maps:merge(ExtraPushOpts, PubSubHostOpts), 989: pubsub_modules(PubSubHost) ++ event_pusher_modules(API, PushOpts). 990: 991: pubsub_modules(virtual) -> 992: []; 993: pubsub_modules(real) -> 994: [{mod_pubsub, mod_config(mod_pubsub, #{plugins => [<<"dag">>, <<"push">>], 995: backend => mongoose_helper:mnesia_or_rdbms_backend(), 996: nodetree => nodetree_dag, 997: host => subhost_pattern("pubsub.@HOST@")})}]. 998: 999: event_pusher_modules(API, PushOpts) -> 1000: [{mod_push_service_mongoosepush, mod_config(mod_push_service_mongoosepush, 1001: #{pool_name => mongoose_push_http, 1002: api_version => API})}, 1003: {mod_event_pusher, #{push => push_opts(PushOpts)}}]. 1004: 1005: virtual_pubsub_hosts_opts(virtual) -> 1006: #{virtual_pubsub_hosts => [subhost_pattern("virtual.@HOST@")]}; 1007: virtual_pubsub_hosts_opts(real) -> 1008: #{}. 1009: 1010: push_opts(ExtraOpts) -> 1011: config([modules, mod_event_pusher, push], 1012: ExtraOpts#{backend => mongoose_helper:mnesia_or_rdbms_backend()}). 1013: 1014: enhanced_plugin_module_opts() -> 1015: #{plugin_module => mod_event_pusher_push_plugin_enhanced}. 1016: 1017: muc_light_opts() -> 1018: mod_config(mod_muc_light, #{backend => mongoose_helper:mnesia_or_rdbms_backend(), 1019: rooms_in_rosters => true}). 1020: 1021: inbox_opts() -> 1022: (inbox_helper:inbox_opts())#{aff_changes := false}.