2023-01-11 11:42:01 +00:00
|
|
|
import logging
|
|
|
|
|
|
2023-01-13 19:28:34 +00:00
|
|
|
from celery.utils.log import get_task_logger
|
|
|
|
|
from django.conf import settings
|
2023-03-27 14:36:51 +01:00
|
|
|
from firebase_admin.messaging import AndroidConfig, APNSConfig, APNSPayload, Aps, ApsAlert, CriticalSound, Message
|
2022-12-01 15:17:01 +00:00
|
|
|
from rest_framework import status
|
2023-01-19 11:15:56 +00:00
|
|
|
from rest_framework.permissions import IsAuthenticated
|
2022-12-01 15:17:01 +00:00
|
|
|
from rest_framework.response import Response
|
|
|
|
|
from rest_framework.views import APIView
|
|
|
|
|
|
2023-01-19 11:15:56 +00:00
|
|
|
from apps.auth_token.auth import ApiTokenAuthentication
|
2023-07-05 17:14:46 +02:00
|
|
|
from apps.mobile_app.models import FCMDevice
|
2023-12-06 15:47:11 -05:00
|
|
|
from apps.mobile_app.utils import send_message_to_fcm_device
|
2024-09-18 07:16:41 +08:00
|
|
|
from common.api_helpers.custom_rate_scoped_throttler import CustomRateUserThrottler
|
2023-01-13 19:28:34 +00:00
|
|
|
from common.custom_celery_tasks import shared_dedicated_queue_retry_task
|
|
|
|
|
|
|
|
|
|
task_logger = get_task_logger(__name__)
|
|
|
|
|
task_logger.setLevel(logging.DEBUG)
|
2022-12-01 15:17:01 +00:00
|
|
|
|
|
|
|
|
|
2024-09-18 07:16:41 +08:00
|
|
|
class FCMRelayThrottler(CustomRateUserThrottler):
|
2023-01-19 11:15:56 +00:00
|
|
|
scope = "fcm_relay"
|
|
|
|
|
rate = "300/m"
|
|
|
|
|
|
|
|
|
|
|
2022-12-01 15:17:01 +00:00
|
|
|
class FCMRelayView(APIView):
|
2023-01-19 11:15:56 +00:00
|
|
|
"""
|
|
|
|
|
This view accepts push notifications from OSS instances and forwards these requests to FCM.
|
|
|
|
|
Requests to this endpoint come from OSS instances: apps.mobile_app.tasks.send_push_notification_to_fcm_relay.
|
|
|
|
|
The view uses public API authentication, so an OSS instance must be connected to cloud to use FCM relay.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
authentication_classes = [ApiTokenAuthentication]
|
|
|
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
|
throttle_classes = [FCMRelayThrottler]
|
2023-01-11 11:42:01 +00:00
|
|
|
|
2022-12-01 15:17:01 +00:00
|
|
|
def post(self, request):
|
2023-01-19 11:15:56 +00:00
|
|
|
if not settings.FCM_RELAY_ENABLED:
|
|
|
|
|
return Response(status=status.HTTP_404_NOT_FOUND)
|
2022-12-01 15:17:01 +00:00
|
|
|
|
2023-01-11 11:42:01 +00:00
|
|
|
try:
|
|
|
|
|
token = request.data["token"]
|
|
|
|
|
data = request.data["data"]
|
2023-01-13 19:28:34 +00:00
|
|
|
apns = request.data["apns"]
|
2023-03-27 14:36:51 +01:00
|
|
|
android = request.data.get("android") # optional
|
2023-01-11 11:42:01 +00:00
|
|
|
except KeyError:
|
2022-12-01 15:17:01 +00:00
|
|
|
return Response(status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
2023-03-27 14:36:51 +01:00
|
|
|
fcm_relay_async.delay(token=token, data=data, apns=apns, android=android)
|
2023-01-13 19:28:34 +00:00
|
|
|
return Response(status=status.HTTP_200_OK)
|
|
|
|
|
|
2023-01-11 11:42:01 +00:00
|
|
|
|
2023-01-13 19:28:34 +00:00
|
|
|
@shared_dedicated_queue_retry_task(
|
2023-02-01 14:45:32 +00:00
|
|
|
autoretry_for=(Exception,), retry_backoff=True, max_retries=1 if settings.DEBUG else 5
|
2023-01-13 19:28:34 +00:00
|
|
|
)
|
2023-03-27 14:36:51 +01:00
|
|
|
def fcm_relay_async(token, data, apns, android=None):
|
2023-12-06 15:47:11 -05:00
|
|
|
send_message_to_fcm_device(
|
|
|
|
|
FCMDevice(registration_id=token), _get_message_from_request_data(token, data, apns, android)
|
|
|
|
|
)
|
2023-01-11 11:42:01 +00:00
|
|
|
|
|
|
|
|
|
2023-03-27 14:36:51 +01:00
|
|
|
def _get_message_from_request_data(token, data, apns, android):
|
|
|
|
|
"""
|
|
|
|
|
Create Message object from JSON payload from OSS instance.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
return Message(
|
|
|
|
|
token=token, data=data, apns=_deserialize_apns(apns), android=AndroidConfig(**android) if android else None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _deserialize_apns(apns):
|
2023-01-11 11:42:01 +00:00
|
|
|
"""
|
|
|
|
|
Create APNSConfig object from JSON payload from OSS instance.
|
|
|
|
|
"""
|
2023-01-13 19:28:34 +00:00
|
|
|
aps = apns.get("payload", {}).get("aps", {})
|
2023-01-11 11:42:01 +00:00
|
|
|
if not aps:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
thread_id = aps.get("thread-id")
|
|
|
|
|
badge = aps.get("badge")
|
|
|
|
|
|
|
|
|
|
alert = aps.get("alert")
|
|
|
|
|
if isinstance(alert, dict):
|
|
|
|
|
alert = ApsAlert(**alert)
|
|
|
|
|
|
|
|
|
|
sound = aps.get("sound")
|
|
|
|
|
if isinstance(sound, dict):
|
|
|
|
|
sound = CriticalSound(**sound)
|
|
|
|
|
|
|
|
|
|
# remove all keys from "aps" so it can be used for custom_data
|
|
|
|
|
for key in ["thread-id", "badge", "alert", "sound"]:
|
|
|
|
|
aps.pop(key, None)
|
2022-12-01 15:17:01 +00:00
|
|
|
|
2023-01-11 11:42:01 +00:00
|
|
|
return APNSConfig(
|
|
|
|
|
payload=APNSPayload(
|
|
|
|
|
aps=Aps(
|
|
|
|
|
thread_id=thread_id,
|
|
|
|
|
badge=badge,
|
|
|
|
|
alert=alert,
|
|
|
|
|
sound=sound,
|
|
|
|
|
custom_data=aps,
|
|
|
|
|
)
|
2023-03-27 14:36:51 +01:00
|
|
|
),
|
|
|
|
|
headers=apns.get("headers"),
|
2023-01-11 11:42:01 +00:00
|
|
|
)
|