diff --git a/changelog.d/358.feature b/changelog.d/358.feature new file mode 100644 index 00000000..c12cfd33 --- /dev/null +++ b/changelog.d/358.feature @@ -0,0 +1 @@ +Add a new APNs configuration option `push_with_badge` to suppress sending `aps.badge` in APNs messages. diff --git a/sygnal.yaml.sample b/sygnal.yaml.sample index 1b59d4c5..bbe6c425 100644 --- a/sygnal.yaml.sample +++ b/sygnal.yaml.sample @@ -202,6 +202,10 @@ apps: # # Defaults to True, set this to False if your client library provides a # # push token in hex format. # #convert_device_token_to_hex: false + # # + # # Specifies whether to send the badge key in the APNs message. + # # Defaults to True, set this to False to suppress sending the badge count. + # #push_with_badge: false # This is an example GCM/FCM push configuration. # diff --git a/sygnal/apnspushkin.py b/sygnal/apnspushkin.py index a9c2d5f1..073c6bb8 100644 --- a/sygnal/apnspushkin.py +++ b/sygnal/apnspushkin.py @@ -111,6 +111,7 @@ class ApnsPushkin(ConcurrencyLimitedPushkin): "topic", "push_type", "convert_device_token_to_hex", + "push_with_badge", } | ConcurrencyLimitedPushkin.UNDERSTOOD_CONFIG_FIELDS APNS_PUSH_TYPES = { @@ -555,7 +556,7 @@ def _get_payload_full( if loc_args: payload["aps"].setdefault("alert", {})["loc-args"] = loc_args - if badge is not None: + if self.get_config("push_with_badge", bool, True) and badge is not None: payload["aps"]["badge"] = badge if loc_key and n.room_id: @@ -563,6 +564,12 @@ def _get_payload_full( if loc_key and n.event_id: payload["event_id"] = n.event_id + if not self.get_config("push_with_badge", bool, True): + if n.counts.unread is not None: + payload["unread_count"] = n.counts.unread + if n.counts.missed_calls is not None: + payload["missed_calls"] = n.counts.missed_calls + return payload async def _send_notification( diff --git a/tests/test_apns.py b/tests/test_apns.py index 6a6653e3..5b1f9b72 100644 --- a/tests/test_apns.py +++ b/tests/test_apns.py @@ -24,6 +24,7 @@ PUSHKIN_ID = "com.example.apns" PUSHKIN_ID_WITH_PUSH_TYPE = "com.example.apns.push_type" +PUSHKIN_ID_WITH_NO_BADGE = "com.example.apns.no_badge" TEST_CERTFILE_PATH = "/path/to/my/certfile.pem" @@ -55,6 +56,12 @@ "pushkey_ts": 42, } +DEVICE_EXAMPLE_FOR_NO_BADGE_PUSHKIN = { + "app_id": "com.example.apns.no_badge", + "pushkey": "spqr", + "pushkey_ts": 42, +} + class ApnsTestCase(testutils.TestCase): def setUp(self) -> None: @@ -73,10 +80,12 @@ def setUp(self) -> None: self.apns_pushkin_snotif = MagicMock() test_pushkin = self.get_test_pushkin(PUSHKIN_ID) test_pushkin_push_type = self.get_test_pushkin(PUSHKIN_ID_WITH_PUSH_TYPE) + test_pushkin_with_no_badge = self.get_test_pushkin(PUSHKIN_ID_WITH_NO_BADGE) # type safety: using ignore here due to mypy not handling monkeypatching, # see https://github.com/python/mypy/issues/2427 test_pushkin._send_notification = self.apns_pushkin_snotif # type: ignore[assignment] # noqa: E501 test_pushkin_push_type._send_notification = self.apns_pushkin_snotif # type: ignore[assignment] # noqa: E501 + test_pushkin_with_no_badge._send_notification = self.apns_pushkin_snotif # type: ignore[assignment] # noqa: E501 def get_test_pushkin(self, name: str) -> ApnsPushkin: test_pushkin = self.sygnal.pushkins[name] @@ -91,6 +100,12 @@ def config_setup(self, config: Dict[str, Any]) -> None: "certfile": TEST_CERTFILE_PATH, "push_type": "alert", } + config["apps"][PUSHKIN_ID_WITH_NO_BADGE] = { + "type": "apns", + "certfile": TEST_CERTFILE_PATH, + "push_type": "alert", + "push_with_badge": False, + } def test_payload_truncation(self) -> None: """ @@ -391,3 +406,47 @@ def test_expected_with_push_type(self) -> None: self.assertEqual(PushType.ALERT, notification_req.push_type) self.assertEqual({"rejected": []}, resp) + + def test_expected_with_no_badge(self) -> None: + """ + Tests the expected case with no badge: a good response from APNS means + we pass on a good response to the homeserver. + """ + # Arrange + method = self.apns_pushkin_snotif + method.side_effect = testutils.make_async_magic_mock( + NotificationResult("notID", "200") + ) + + # Act + resp = self._request( + self._make_dummy_notification([DEVICE_EXAMPLE_FOR_NO_BADGE_PUSHKIN]) + ) + + # Assert + self.assertEqual(1, method.call_count) + ((notification_req,), _kwargs) = method.call_args + + self.assertEqual( + { + "room_id": "!slw48wfj34rtnrf:example.com", + "event_id": "$qTOWWTEL48yPm3uT-gdNhFcoHxfKbZuqRVnnWWSkGBs", + "missed_calls": 1, + "unread_count": 2, + "aps": { + "alert": { + "loc-key": "MSG_FROM_USER_IN_ROOM_WITH_CONTENT", + "loc-args": [ + "Major Tom", + "Mission Control", + "I'm floating in a most peculiar way.", + ], + }, + }, + }, + notification_req.message, + ) + + self.assertEqual(PushType.ALERT, notification_req.push_type) + + self.assertEqual({"rejected": []}, resp)