push_notification: Add support to configure priority and push_type.

This commit adds support to let server configure:
* fcm_priority
* apns_priority
* apns_push_type

while sending E2EE push notifications.

The values of these fields will vary depending on whether the
send request is to send push notification for a message or
revoke an already sent notification.

Since, the bouncer receives encrypted data so it can't inspect
the payload to determine whether it is a removal request or not,
hence can't configure priority on its own.

The server needs to specify explicitly.

We're not simply sending a single 'is_removal' flag because
allowing the server to configure them separately will help in
future to support other types of notifications with a different
combination of priority and push_type, like whose aim is to notify
user about information other than a new message or removal request.

Fixes part of #35368.
This commit is contained in:
Prakhar Pratyush
2025-07-24 07:23:29 +05:30
committed by Tim Abbott
parent 945f27f099
commit 29ea8a07c2
3 changed files with 39 additions and 15 deletions

View File

@@ -8,11 +8,11 @@ from collections.abc import Iterable, Mapping, Sequence
from dataclasses import dataclass
from email.headerregistry import Address
from functools import cache
from typing import TYPE_CHECKING, Any, Literal, Optional, TypeAlias, Union
from typing import TYPE_CHECKING, Any, Final, Literal, Optional, TypeAlias, Union
import lxml.html
import orjson
from aioapns.common import NotificationResult
from aioapns.common import NotificationResult, PushType
from django.conf import settings
from django.db import transaction
from django.db.models import F, Q
@@ -1451,18 +1451,33 @@ def send_push_notifications(
assert push_device.bouncer_device_id is not None # for mypy
device_id_to_encrypted_data[str(push_device.bouncer_device_id)] = encrypted_data
# TODO: These literals will vary when implementing notification removal.
#
# Note: The "Final" qualifier serves as a shorthand
# for declaring that a variable is effectively Literal.
fcm_priority: Final = "high"
apns_priority: Final = 10
apns_push_type = PushType.ALERT
# Send push notification
try:
if settings.ZILENCER_ENABLED:
from zilencer.lib.push_notifications import send_e2ee_push_notifications
response_data: SendNotificationResponseData = send_e2ee_push_notifications(
device_id_to_encrypted_data, realm=user_profile.realm
device_id_to_encrypted_data,
fcm_priority=fcm_priority,
apns_priority=apns_priority,
apns_push_type=apns_push_type,
realm=user_profile.realm,
)
else:
post_data = {
"realm_uuid": str(user_profile.realm.uuid),
"device_id_to_encrypted_data": device_id_to_encrypted_data,
"fcm_priority": fcm_priority,
"apns_priority": apns_priority,
"apns_push_type": apns_push_type,
}
result = send_json_to_push_bouncer("POST", "push/e2ee/notify", post_data)
assert isinstance(result["android_successfully_sent_count"], int) # for mypy

View File

@@ -1,8 +1,9 @@
import asyncio
import logging
from collections.abc import Iterable
from typing import Literal, TypeAlias
from aioapns import NotificationRequest
from aioapns import NotificationRequest, PushType
from django.utils.timezone import now as timezone_now
from firebase_admin import exceptions as firebase_exceptions
from firebase_admin import messaging as firebase_messaging
@@ -19,6 +20,9 @@ from zilencer.models import RemotePushDevice, RemoteRealm
logger = logging.getLogger(__name__)
FCMPriority: TypeAlias = Literal["high", "normal"]
APNsPriority: TypeAlias = Literal[10, 5, 1]
def send_e2ee_push_notification_apple(
apns_requests: list[NotificationRequest],
@@ -117,6 +121,9 @@ def send_e2ee_push_notification_android(
def send_e2ee_push_notifications(
device_id_to_encrypted_data: dict[str, str],
*,
fcm_priority: FCMPriority,
apns_priority: APNsPriority,
apns_push_type: PushType,
realm: Realm | None = None,
remote_realm: RemoteRealm | None = None,
) -> SendNotificationResponseData:
@@ -147,18 +154,12 @@ def send_e2ee_push_notifications(
"alert": {
"title": "New notification",
},
# TODO: Should we remove `sound` and let the clients add it.
# Then we can rename it as `apns_required_message_payload`.
"sound": "default",
},
}
fcm_requests = []
fcm_remote_push_devices: list[RemotePushDevice] = []
# TODO: "normal" if remove event.
priority = "high"
for remote_push_device in remote_push_devices:
message_payload = {
"encrypted_data": device_id_to_encrypted_data[str(remote_push_device.device_id)],
@@ -174,8 +175,8 @@ def send_e2ee_push_notifications(
apns_topic=remote_push_device.ios_app_id,
device_token=remote_push_device.token,
message=apns_message_payload,
time_to_live=24 * 3600,
# TODO: priority
priority=apns_priority,
push_type=apns_push_type,
)
)
apns_remote_push_devices.append(remote_push_device)
@@ -184,7 +185,7 @@ def send_e2ee_push_notifications(
firebase_messaging.Message(
data=message_payload,
token=remote_push_device.token,
android=firebase_messaging.AndroidConfig(priority=priority),
android=firebase_messaging.AndroidConfig(priority=fcm_priority),
)
)
fcm_remote_push_devices.append(remote_push_device)

View File

@@ -8,6 +8,7 @@ from uuid import UUID
import orjson
import requests.exceptions
from aioapns import PushType
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator, validate_email
@@ -92,7 +93,7 @@ from zilencer.auth import (
generate_registration_transfer_verification_secret,
validate_registration_transfer_verification_secret,
)
from zilencer.lib.push_notifications import send_e2ee_push_notifications
from zilencer.lib.push_notifications import APNsPriority, FCMPriority, send_e2ee_push_notifications
from zilencer.lib.remote_counts import MissingDataError
from zilencer.models import (
RemoteInstallationCount,
@@ -1807,6 +1808,9 @@ def remote_server_check_analytics(request: HttpRequest, server: RemoteZulipServe
class SendE2EEPushNotificationPayload(BaseModel):
realm_uuid: str
device_id_to_encrypted_data: dict[str, str]
fcm_priority: FCMPriority
apns_priority: APNsPriority
apns_push_type: PushType
@typed_endpoint
@@ -1844,7 +1848,11 @@ def remote_server_send_e2ee_push_notification(
)
response_data = send_e2ee_push_notifications(
device_id_to_encrypted_data, remote_realm=remote_realm
device_id_to_encrypted_data,
fcm_priority=payload.fcm_priority,
apns_priority=payload.apns_priority,
apns_push_type=payload.apns_push_type,
remote_realm=remote_realm,
)
do_increment_logging_stat(