Skip to content

Commit

Permalink
Add a new APNs configuration option push_with_badge to suppress sen…
Browse files Browse the repository at this point in the history
…ding `aps.badge` in APNs messages.

Signed-off-by: Chris Ennis <[email protected]>
  • Loading branch information
CEnnis91 committed Jan 26, 2024
1 parent e2eaa5c commit 64fde5d
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 1 deletion.
1 change: 1 addition & 0 deletions changelog.d/356.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a new APNs configuration option `push_with_badge` to suppress sending `aps.badge` in APNs messages.
4 changes: 4 additions & 0 deletions sygnal.yaml.sample
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand Down
9 changes: 8 additions & 1 deletion sygnal/apnspushkin.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ class ApnsPushkin(ConcurrencyLimitedPushkin):
"keyfile",
"topic",
"push_type",
"push_with_badge",
} | ConcurrencyLimitedPushkin.UNDERSTOOD_CONFIG_FIELDS

APNS_PUSH_TYPES = {
Expand Down Expand Up @@ -551,14 +552,20 @@ 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:
payload["room_id"] = n.room_id
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(
Expand Down
59 changes: 59 additions & 0 deletions tests/test_apns.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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:
Expand All @@ -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]
Expand All @@ -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:
"""
Expand Down Expand Up @@ -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)

0 comments on commit 64fde5d

Please sign in to comment.