mirror of
https://github.com/zulip/zulip.git
synced 2025-11-05 22:43:42 +00:00
This replaces the AppleDeviceToken table with a generic PushDeviceToken with a `kind` field to make it easier to add functionality like per-device/per-stream settings that share code between Android and iOS devices. The schema must continue to work on prod with the old table name, so we add the new table in parallel and can drop the old table once this code hits prod and any necessary data is copied. (imported from commit 0209a7013f2850ac6311f23c3d6f92c65ffd19e3)
77 lines
3.0 KiB
Python
77 lines
3.0 KiB
Python
from __future__ import absolute_import
|
|
|
|
from zerver.models import PushDeviceToken
|
|
from zerver.lib.timestamp import timestamp_to_datetime
|
|
from zerver.decorator import statsd_increment
|
|
|
|
from apnsclient import Session, Message, APNs
|
|
|
|
from django.conf import settings
|
|
|
|
import base64, binascii, logging, os
|
|
|
|
# Maintain a long-lived Session object to avoid having to re-SSL-handshake
|
|
# for each request
|
|
session = Session()
|
|
connection = None
|
|
if settings.APNS_CERT_FILE is not None and os.path.exists(settings.APNS_CERT_FILE):
|
|
connection = session.get_connection(settings.APNS_SANDBOX, cert_file=settings.APNS_CERT_FILE)
|
|
|
|
def num_push_devices_for_user(user_profile, kind = None):
|
|
if kind is None:
|
|
return PushDeviceToken.objects.filter(user=user_profile).count()
|
|
else:
|
|
return PushDeviceToken.objects.filter(user=user_profile, kind=kind).count()
|
|
|
|
# We store the token as b64, but apns-client wants hex strings
|
|
def b64_to_hex(data):
|
|
return binascii.hexlify(base64.b64decode(data))
|
|
|
|
def hex_to_b64(data):
|
|
return base64.b64encode(binascii.unhexlify(data))
|
|
|
|
# Send a push notification to the desired clients
|
|
# extra_data is a dict that will be passed to the
|
|
# mobile app
|
|
@statsd_increment("apple_push_notification")
|
|
def send_apple_push_notification(user, alert, **extra_data):
|
|
if not connection:
|
|
logging.error("Attempting to send push notification, but no connection was found. This may be because we could not find the APNS Certificate file.")
|
|
|
|
tokens = [b64_to_hex(device.token) for device in
|
|
PushDeviceToken.objects.filter(user=user, kind=PushDeviceToken.APNS)]
|
|
|
|
logging.info("Sending apple push notification to devices: %s" % (tokens,))
|
|
message = Message(tokens, alert=alert, **extra_data)
|
|
|
|
apns_client = APNs(connection)
|
|
ret = apns_client.send(message)
|
|
if not ret:
|
|
logging.warning("Failed to send push notification for clients %s" % (tokens,))
|
|
return
|
|
|
|
for token, reason in ret.failed.items():
|
|
code, errmsg = reason
|
|
logging.warning("Failed to deliver APNS notification to %s, reason: %s" % (token, errmsg))
|
|
|
|
# Check failures not related to devices.
|
|
for code, errmsg in ret.errors:
|
|
logging.warning("Unknown error when delivering APNS: %s" % (errmsg,))
|
|
|
|
if ret.needs_retry():
|
|
# TODO handle retrying by potentially scheduling a background job
|
|
# or re-queueing
|
|
logging.warning("APNS delivery needs a retry but ignoring")
|
|
|
|
# NOTE: This is used by the check_apns_tokens manage.py command. Do not call it otherwise, as the
|
|
# feedback() call can take up to 15s
|
|
def check_apns_feedback():
|
|
apns_client = APNs(connection, tail_timeout=20)
|
|
|
|
for token, since in apns_client.feedback():
|
|
since_date = timestamp_to_datetime(since)
|
|
logging.info("Found unavailable token %s, unavailable since %s" % (token, since_date))
|
|
|
|
PushDeviceToken.objects.filter(token=hex_to_b64(token), last_updates__lt=since_date, type=PushDeviceToken.APNS).delete()
|
|
logging.info("Finished checking feedback for stale tokens")
|