112 lines
3.9 KiB
Python
112 lines
3.9 KiB
Python
import httpx
|
|
import logging
|
|
from typing import Optional, Dict, Any
|
|
|
|
from core.config import settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def send_push_notification(
|
|
push_token: str, title: str, body: str, data: Optional[Dict[str, Any]] = None
|
|
) -> bool:
|
|
"""
|
|
Sends a push notification to a specific Expo push token.
|
|
|
|
Args:
|
|
push_token: The recipient's Expo push token.
|
|
title: The title of the notification.
|
|
body: The main message content of the notification.
|
|
data: Optional dictionary containing extra data to send with the notification.
|
|
|
|
Returns:
|
|
True if the notification was sent successfully (according to Expo API), False otherwise.
|
|
"""
|
|
if not push_token:
|
|
logger.warning("Attempted to send notification but no push token provided.")
|
|
return False
|
|
|
|
message = {
|
|
"to": push_token,
|
|
"sound": "default",
|
|
"title": title,
|
|
"body": body,
|
|
"priority": "high",
|
|
"channelId": "default",
|
|
}
|
|
if data:
|
|
message["data"] = data
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
try:
|
|
response = await client.post(
|
|
settings.EXPO_PUSH_API_URL,
|
|
headers={
|
|
"Accept": "application/json",
|
|
"Accept-Encoding": "gzip, deflate",
|
|
"Content-Type": "application/json",
|
|
},
|
|
json=message,
|
|
timeout=10.0,
|
|
)
|
|
response.raise_for_status() # Raise exception for 4xx/5xx responses
|
|
|
|
response_data = response.json()
|
|
logger.debug(f"Expo push API response: {response_data}")
|
|
|
|
# Check for top-level errors first
|
|
if "errors" in response_data:
|
|
error_messages = [
|
|
err.get("message", "Unknown error")
|
|
for err in response_data["errors"]
|
|
]
|
|
logger.error(
|
|
f"Expo API returned errors for {push_token[:10]}...: {'; '.join(error_messages)}"
|
|
)
|
|
return False
|
|
|
|
# Check the status in the data field
|
|
receipt = response_data.get("data")
|
|
|
|
# if receipts is a list
|
|
if receipt:
|
|
status = receipt.get("status")
|
|
|
|
if status == "ok":
|
|
logger.info(
|
|
f"Successfully sent push notification to token: {push_token[:10]}..."
|
|
)
|
|
return True
|
|
else:
|
|
# Log details if the status is not 'ok'
|
|
error_details = receipt.get("details")
|
|
error_message = receipt.get("message")
|
|
logger.error(
|
|
f"Failed to send push notification to {push_token[:10]}... "
|
|
f"Expo status: {status}, Message: {error_message}, Details: {error_details}"
|
|
)
|
|
return False
|
|
else:
|
|
# Log if 'data' is missing, not a list, or an empty list
|
|
logger.error(
|
|
f"Unexpected Expo API response format or empty 'data' field for {push_token[:10]}... "
|
|
f"Response: {response_data}"
|
|
)
|
|
return False
|
|
|
|
except httpx.HTTPStatusError as e:
|
|
logger.error(
|
|
f"HTTP error sending push notification to {push_token[:10]}...: {e.response.status_code} - {e.response.text}"
|
|
)
|
|
return False
|
|
except httpx.RequestError as e:
|
|
logger.error(
|
|
f"Network error sending push notification to {push_token[:10]}...: {e}"
|
|
)
|
|
return False
|
|
except Exception as e:
|
|
logger.exception(
|
|
f"Unexpected error sending push notification to {push_token[:10]}...: {e}"
|
|
)
|
|
return False
|