79 lines
2.8 KiB
Python
79 lines
2.8 KiB
Python
"""Invisible Web Push commands that keep browser notifications in sync."""
|
|
|
|
import json
|
|
import logging
|
|
|
|
from push_notifications.models import WebPushDevice
|
|
|
|
from notifications.models import Notification
|
|
from notifications.senders.push import PushNotificationSender
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class PushControlSender:
|
|
"""Send lifecycle commands to every active installation of one user."""
|
|
|
|
def send(
|
|
self,
|
|
*,
|
|
user_id: int,
|
|
action: str,
|
|
scope: dict,
|
|
replacement_notification_id: int | None = None,
|
|
) -> bool:
|
|
payload = {
|
|
"type": "notification_control",
|
|
"action": action,
|
|
"scope": scope,
|
|
}
|
|
if replacement_notification_id is not None:
|
|
notification = Notification.objects.filter(
|
|
id=replacement_notification_id,
|
|
recipient_id=user_id,
|
|
).first()
|
|
if notification is None:
|
|
return False
|
|
payload["notification"] = PushNotificationSender().build_message_data(
|
|
notification
|
|
)
|
|
|
|
message = json.dumps(payload)
|
|
delivered = False
|
|
push_sender = PushNotificationSender()
|
|
for device in WebPushDevice.objects.filter(user_id=user_id, active=True):
|
|
try:
|
|
result = device.send_message(message)
|
|
if not push_sender._push_result_succeeded(result):
|
|
device.refresh_from_db(fields=["active"])
|
|
if not device.active:
|
|
push_sender._deactivate_device(
|
|
device,
|
|
"provider rejected control command",
|
|
)
|
|
logger.warning(
|
|
"Push provider rejected control command",
|
|
extra={
|
|
"event": "notification.push.control_rejected",
|
|
"user_id": user_id,
|
|
"device_id": device.id,
|
|
"action": action,
|
|
},
|
|
)
|
|
continue
|
|
delivered = True
|
|
except Exception as error: # noqa: BLE001 - push clients raise vendor exceptions
|
|
logger.warning(
|
|
"Push control delivery failed",
|
|
extra={
|
|
"event": "notification.push.control_failed",
|
|
"user_id": user_id,
|
|
"device_id": device.id,
|
|
"action": action,
|
|
"error_type": type(error).__name__,
|
|
},
|
|
)
|
|
if push_sender._is_permanent_device_error(error):
|
|
push_sender._deactivate_device(device, str(error))
|
|
return delivered
|