Esempio n. 1
0
def send_apple_push_notification(
    user_identity: UserPushIndentityCompat,
    devices: Sequence[DeviceToken],
    payload_data: Dict[str, Any],
    remote: Optional["RemoteZulipServer"] = None,
) -> None:
    if not devices:
        return
    # We lazily do the APNS imports as part of optimizing Zulip's base
    # import time; since these are only needed in the push
    # notification queue worker, it's best to only import them in the
    # code that needs them.
    import aioapns
    import aioapns.exceptions

    apns_context = get_apns_context()
    if apns_context is None:
        logger.debug(
            "APNs: Dropping a notification because nothing configured.  "
            "Set PUSH_NOTIFICATION_BOUNCER_URL (or APNS_CERT_FILE)."
        )
        return

    if remote:
        assert settings.ZILENCER_ENABLED
        DeviceTokenClass: Type[AbstractPushDeviceToken] = RemotePushDeviceToken
    else:
        DeviceTokenClass = PushDeviceToken

    if remote:
        logger.info(
            "APNs: Sending notification for remote user %s:%s to %d devices",
            remote.uuid,
            user_identity,
            len(devices),
        )
    else:
        logger.info(
            "APNs: Sending notification for local user %s to %d devices",
            user_identity,
            len(devices),
        )
    payload_data = modernize_apns_payload(payload_data).copy()
    message = {**payload_data.pop("custom", {}), "aps": payload_data}
    for device in devices:
        # TODO obviously this should be made to actually use the async
        request = aioapns.NotificationRequest(
            device_token=device.token, message=message, time_to_live=24 * 3600
        )

        try:
            result = apns_context.loop.run_until_complete(
                apns_context.apns.send_notification(request)
            )
        except aioapns.exceptions.ConnectionError:
            logger.error(
                "APNs: ConnectionError sending for user %s to device %s; check certificate expiration",
                user_identity,
                device.token,
            )
            continue

        if result.is_successful:
            logger.info(
                "APNs: Success sending for user %s to device %s", user_identity, device.token
            )
        elif result.description in ["Unregistered", "BadDeviceToken", "DeviceTokenNotForTopic"]:
            logger.info(
                "APNs: Removing invalid/expired token %s (%s)", device.token, result.description
            )
            # We remove all entries for this token (There
            # could be multiple for different Zulip servers).
            DeviceTokenClass.objects.filter(token=device.token, kind=DeviceTokenClass.APNS).delete()
        else:
            logger.warning(
                "APNs: Failed to send for user %s to device %s: %s",
                user_identity,
                device.token,
                result.description,
            )
def send_apple_push_notification(user_id: int,
                                 devices: List[DeviceToken],
                                 payload_data: Dict[str, Any],
                                 remote: bool = False) -> None:
    if not devices:
        return
    # We lazily do the APNS imports as part of optimizing Zulip's base
    # import time; since these are only needed in the push
    # notification queue worker, it's best to only import them in the
    # code that needs them.
    import aioapns
    import aioapns.exceptions

    apns_context = get_apns_context()
    if apns_context is None:
        logger.debug(
            "APNs: Dropping a notification because nothing configured.  "
            "Set PUSH_NOTIFICATION_BOUNCER_URL (or APNS_CERT_FILE).")
        return

    if remote:
        assert settings.ZILENCER_ENABLED
        DeviceTokenClass = RemotePushDeviceToken
    else:
        DeviceTokenClass = PushDeviceToken

    logger.info("APNs: Sending notification for user %d to %d devices",
                user_id, len(devices))
    message = {"aps": modernize_apns_payload(payload_data)}
    retries_left = APNS_MAX_RETRIES
    for device in devices:
        # TODO obviously this should be made to actually use the async
        request = aioapns.NotificationRequest(device_token=device.token,
                                              message=message,
                                              time_to_live=24 * 3600)

        async def attempt_send() -> Optional[str]:
            assert apns_context is not None
            try:
                result = await apns_context.apns.send_notification(request)
                return "Success" if result.is_successful else result.description
            except aioapns.exceptions.ConnectionClosed as e:  # nocoverage
                logger.warning(
                    "APNs: ConnectionClosed sending for user %d to device %s: %s",
                    user_id,
                    device.token,
                    e.__class__.__name__,
                )
                return None
            except aioapns.exceptions.ConnectionError as e:  # nocoverage
                logger.warning(
                    "APNs: ConnectionError sending for user %d to device %s: %s",
                    user_id,
                    device.token,
                    e.__class__.__name__,
                )
                return None

        result = apns_context.loop.run_until_complete(attempt_send())
        while result is None and retries_left > 0:
            retries_left -= 1
            result = apns_context.loop.run_until_complete(attempt_send())
        if result is None:
            result = "HTTP error, retries exhausted"

        if result == "Success":
            logger.info("APNs: Success sending for user %d to device %s",
                        user_id, device.token)
        elif result in [
                "Unregistered", "BadDeviceToken", "DeviceTokenNotForTopic"
        ]:
            logger.info("APNs: Removing invalid/expired token %s (%s)",
                        device.token, result)
            # We remove all entries for this token (There
            # could be multiple for different Zulip servers).
            DeviceTokenClass.objects.filter(
                token=device.token, kind=DeviceTokenClass.APNS).delete()
        else:
            logger.warning("APNs: Failed to send for user %d to device %s: %s",
                           user_id, device.token, result)