Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ For WNS, you need both the ``WNS_PACKAGE_SECURITY_KEY`` and the ``WNS_SECRET_KEY
- ``APNS_TOPIC``: The topic of the remote notification, which is typically the bundle ID for your app. If you omit this header and your APNs certificate does not specify multiple topics, the APNs server uses the certificate’s Subject as the default topic.
- ``APNS_USE_ALTERNATIVE_PORT``: Use port 2197 for APNS, instead of default port 443.
- ``APNS_USE_SANDBOX``: Use 'api.development.push.apple.com', instead of default host 'api.push.apple.com'. Default value depends on ``DEBUG`` setting of your environment: if ``DEBUG`` is True and you use production certificate, you should explicitly set ``APNS_USE_SANDBOX`` to False.
- ``APNS_ERROR_TIMEOUT``: Timeout in seconds for APNS Push requests (Optional, default value is 5)

**FCM/GCM settings**

Expand Down
12 changes: 10 additions & 2 deletions push_notifications/apns_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ def apns_send_bulk_message(
mutable_content: Optional[bool] = False,
category: Optional[str] = None,
err_func: Optional[ErrFunc] = None,
timeout: Optional[int] = None,
) -> Dict[str, str]:
"""
Sends an APNS notification to one or more registration_ids.
Expand All @@ -326,7 +327,11 @@ def apns_send_bulk_message(
Notification Content Extension or UNNotificationCategory configuration.
It allows the app to display custom actions with the notification.
:param content_available: If True the `content-available` flag will be set to 1, allowing the app to be woken up in the background
:param timeout: Timeout in seconds for each notification send operation
"""
if not timeout:
timeout = get_manager().get_apns_error_timeout(application_id)

try:
topic = get_manager().get_apns_topic(application_id)
results: Dict[str, str] = {}
Expand All @@ -351,6 +356,7 @@ def apns_send_bulk_message(
mutable_content=mutable_content,
category=category,
err_func=err_func,
timeout=timeout,
)
)

Expand Down Expand Up @@ -386,6 +392,7 @@ def apns_send_bulk_message(

async def _send_bulk_request(
registration_ids: list[str],
timeout: int,
alert: Union[str, Alert],
application_id: Optional[str] = None,
creds: Optional[Credentials] = None,
Expand Down Expand Up @@ -432,16 +439,17 @@ async def _send_bulk_request(
for registration_id in registration_ids
]

send_requests = [_send_request(client, request) for request in requests]
send_requests = [_send_request(client, request, timeout) for request in requests]
return await asyncio.gather(*send_requests)


async def _send_request(
apns: APNs,
request: NotificationRequest,
timeout: int,
) -> Tuple[str, NotificationResult]:
try:
res = await asyncio.wait_for(apns.send_notification(request), timeout=1)
res = await asyncio.wait_for(apns.send_notification(request), timeout=timeout)
return request.device_token, res

except asyncio.TimeoutError:
Expand Down
4 changes: 4 additions & 0 deletions push_notifications/conf/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,10 @@ def get_apns_use_alternative_port(
def get_apns_topic(self, application_id: Optional[str] = None) -> Optional[str]:
return self._get_application_settings(application_id, "APNS", "TOPIC")


def get_apns_error_timeout(self, application_id: Optional[str] = None) -> int:
return self._get_application_settings(application_id, "APNS", "ERROR_TIMEOUT")

def get_wns_package_security_id(self, application_id: Optional[str] = None) -> str:
return self._get_application_settings(
application_id, "WNS", "PACKAGE_SECURITY_ID"
Expand Down
3 changes: 3 additions & 0 deletions push_notifications/conf/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ def get_apns_use_sandbox(self, application_id: Optional[str] = None) -> bool:
def get_apns_use_alternative_port(self, application_id: Optional[str] = None) -> bool:
raise NotImplementedError

def get_apns_error_timeout(self, application_id: Optional[str] = None) -> int:
raise NotImplementedError

def get_wns_package_security_id(self, application_id: Optional[str] = None) -> str:
raise NotImplementedError

Expand Down
1 change: 1 addition & 0 deletions push_notifications/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
PUSH_NOTIFICATIONS_SETTINGS.setdefault("APNS_USE_SANDBOX", False)
PUSH_NOTIFICATIONS_SETTINGS.setdefault("APNS_USE_ALTERNATIVE_PORT", False)
PUSH_NOTIFICATIONS_SETTINGS.setdefault("APNS_TOPIC", None)
PUSH_NOTIFICATIONS_SETTINGS.setdefault("APNS_ERROR_TIMEOUT", 5)

# WNS
PUSH_NOTIFICATIONS_SETTINGS.setdefault("WNS_PACKAGE_SECURITY_ID", None)
Expand Down
127 changes: 126 additions & 1 deletion tests/test_apns_async_push_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from unittest import mock

import pytest
from django.test import TestCase
from django.test import TestCase, override_settings


try:
Expand Down Expand Up @@ -276,3 +276,128 @@ def test_push_payload_with_content_available_not_set(self, mock_apns):
req = args[0]

assert "content-available" not in req.message["aps"]


class APNSErrorTimeoutTests(TestCase):

@mock.patch("push_notifications.apns_async.asyncio.wait_for")
@mock.patch("push_notifications.apns_async.APNS", autospec=True)
@override_settings(
PUSH_NOTIFICATION_SETTINGS={
'APNS_ERROR_TIMEOUT': 15
}
)
def test_test_timeout_value_passed_to_wait_for(self, mock_apns, mock_wait_for):
mock_wait_for.return_value = mock.AsyncMock(
return_value=NotificationResult("123", "200")
)
apns_send_message(
"123",
"Test message",
creds=TokenCredentials(
key="aaa",
key_id="bbb",
team_id="ccc",
),
)

mock_wait_for.assert_called_once()
_, kwargs = mock_wait_for.call_args
assert kwargs["timeout"] == 15

@mock.patch("push_notifications.apns_async.asyncio.wait_for")
@mock.patch("push_notifications.apns_async.APNs", autospec=True)
def test_default_timeout_is_5_seconds(self, mock_apns, mock_wait_for):
mock_wait_for.return_value = mock.AsyncMock(
return_value=NotificationResult("123", "200")
)

apns_send_message(
"123",
"Test message",
creds=TokenCredentials(
key="aaa",
key_id="bbb",
team_id="ccc",
),
)

mock_wait_for.assert_called_once()
_, kwargs = mock_wait_for.call_args
assert kwargs["timeout"] == 5

@mock.patch("push_notifications.apns_async.asyncio.wait_for")
@mock.patch("push_notifications.apns_async.APNs", autospec=True)
@override_settings(
PUSH_NOTIFICATIONS_SETTINGS={
'APNS_ERROR_TIMEOUT': 1
}
)
def test_short_timeout_value_is_respected(self, mock_apns, mock_wait_for):
mock_wait_for.return_value = mock.AsyncMock(
return_value=NotificationResult("123", "200")
)

apns_send_message(
"123",
"Test message",
creds=TokenCredentials(
key="aaa",
key_id="bbb",
team_id="ccc",
),
)

mock_wait_for.assert_called_once()
_, kwargs = mock_wait_for.call_args
assert kwargs["timeout"] == 1

@mock.patch("push_notifications.apns_async.asyncio.wait_for")
@mock.patch("push_notifications.apns_async.APNs", autospec=True)
@override_settings(
PUSH_NOTIFICATIONS_SETTINGS={
'APNS_ERROR_TIMEOUT': 30
}
)
def test_long_timeout_value_is_respected(self, mock_apns, mock_wait_for):
mock_wait_for.return_value = mock.AsyncMock(
return_value=NotificationResult("123", "200")
)

apns_send_message(
"123",
"Test message",
creds=TokenCredentials(
key="aaa",
key_id="bbb",
team_id="ccc",
),
)

mock_wait_for.assert_called_once()
_, kwargs = mock_wait_for.call_args
assert kwargs["timeout"] == 30

@mock.patch("push_notifications.apns_async.asyncio.wait_for")
@mock.patch("push_notifications.apns_async.APNs", autospec=True)
@override_settings(
PUSH_NOTIFICATIONS_SETTINGS={}
)
def test_empty_settings_uses_default_timeout(self, mock_apns, mock_wait_for):
mock_wait_for.return_value = mock.AsyncMock(
return_value=NotificationResult("123", "200")
)

apns_send_message(
"123",
"Test message",
creds=TokenCredentials(
key="aaa",
key_id="bbb",
team_id="ccc",
),
)

mock_wait_for.assert_called_once()
_, kwargs = mock_wait_for.call_args
assert kwargs["timeout"] == 5
Loading