mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
150 lines
6.3 KiB
Python
150 lines
6.3 KiB
Python
import json
|
|
import logging
|
|
import os
|
|
from typing import List, Optional
|
|
from sqlalchemy.orm import Session
|
|
from pywebpush import webpush, WebPushException
|
|
from models import PushSubscription, User, Message, DMEnvelope
|
|
|
|
logger = logging.getLogger("uvicorn.error")
|
|
|
|
class PushNotificationService:
|
|
def __init__(self):
|
|
# VAPID keys - load from environment variables
|
|
self.vapid_private_key = os.getenv("VAPID_PRIVATE_KEY", "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQghg2CSKiq0KsXXXImE75Z8UAphGBjkpYjUE87zPBmGqKhRANCAATxbNBGMhNl6gLmPL0PAf2YIJCVYX_TZrSqkj7SCqsu5VNMhnDOan6Qc9hEkcTZgvwj286C24SnxfH5CghVMCI6")
|
|
self.vapid_public_key = os.getenv("VAPID_PUBLIC_KEY", "BPFs0EYyE2XqAuY8vQ8B_ZggkJVhf9NmtKqSPtIKqy7lU0yGcM5qfpBz2ESRxNmC_CPbzoLbhKfF8fkKCFUwIjo")
|
|
self.vapid_claims = {
|
|
"sub": "mailto:admin@fromchat.com",
|
|
"aud": "https://fcm.googleapis.com"
|
|
}
|
|
|
|
async def subscribe_user(self, db: Session, user_id: int, endpoint: str, p256dh_key: str, auth_key: str) -> bool:
|
|
"""Subscribe a user to push notifications"""
|
|
try:
|
|
# Check if user already has a subscription
|
|
existing_sub = db.query(PushSubscription).filter(PushSubscription.user_id == user_id).first()
|
|
|
|
if existing_sub:
|
|
# Update existing subscription
|
|
existing_sub.endpoint = endpoint
|
|
existing_sub.p256dh_key = p256dh_key
|
|
existing_sub.auth_key = auth_key
|
|
else:
|
|
# Create new subscription
|
|
new_sub = PushSubscription(
|
|
user_id=user_id,
|
|
endpoint=endpoint,
|
|
p256dh_key=p256dh_key,
|
|
auth_key=auth_key
|
|
)
|
|
db.add(new_sub)
|
|
|
|
db.commit()
|
|
logger.info(f"Push subscription saved for user {user_id}")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Failed to save push subscription for user {user_id}: {e}")
|
|
db.rollback()
|
|
return False
|
|
|
|
async def send_public_message_notification(self, db: Session, message: Message, exclude_user_id: Optional[int] = None):
|
|
"""Send push notification for a new public chat message"""
|
|
try:
|
|
# Get all users except the sender
|
|
users = db.query(User).filter(User.id != message.user_id)
|
|
if exclude_user_id:
|
|
users = users.filter(User.id != exclude_user_id)
|
|
|
|
for user in users:
|
|
# Check if user has push subscription before trying to send
|
|
subscription = db.query(PushSubscription).filter(PushSubscription.user_id == user.id).first()
|
|
if not subscription:
|
|
continue
|
|
|
|
await self._send_notification_to_user(
|
|
db, user.id,
|
|
f"New message from {message.author.username}",
|
|
message.content[:100] + ("..." if len(message.content) > 100 else ""),
|
|
message.author.profile_picture,
|
|
{
|
|
"type": "public_message",
|
|
"message_id": message.id,
|
|
"sender_id": message.user_id,
|
|
"sender_username": message.author.username
|
|
}
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Failed to send public message notifications: {e}")
|
|
|
|
async def send_dm_notification(self, db: Session, dm_envelope: DMEnvelope, sender: User):
|
|
"""Send push notification for a new DM"""
|
|
try:
|
|
await self._send_notification_to_user(
|
|
db, dm_envelope.recipient_id,
|
|
f"New message from {sender.username}",
|
|
"You have a new direct message",
|
|
sender.profile_picture,
|
|
{
|
|
"type": "dm",
|
|
"dm_id": dm_envelope.id,
|
|
"sender_id": sender.id,
|
|
"sender_username": sender.username
|
|
}
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Failed to send DM notification: {e}")
|
|
|
|
async def _send_notification_to_user(self, db: Session, user_id: int, title: str, body: str, icon: Optional[str], data: dict):
|
|
"""Send a push notification to a specific user"""
|
|
try:
|
|
subscription = db.query(PushSubscription).filter(PushSubscription.user_id == user_id).first()
|
|
if not subscription:
|
|
return
|
|
|
|
payload = {
|
|
"title": title,
|
|
"body": body,
|
|
"icon": icon or "/logo.png",
|
|
"tag": f"message_{user_id}",
|
|
"data": data
|
|
}
|
|
|
|
subscription_info = {
|
|
"endpoint": subscription.endpoint,
|
|
"keys": {
|
|
"p256dh": subscription.p256dh_key,
|
|
"auth": subscription.auth_key
|
|
}
|
|
}
|
|
|
|
webpush(
|
|
subscription_info=subscription_info,
|
|
data=json.dumps(payload),
|
|
vapid_private_key=self.vapid_private_key,
|
|
vapid_claims=self.vapid_claims
|
|
)
|
|
|
|
except WebPushException as e:
|
|
logger.error(f"WebPush error for user {user_id}: {e}")
|
|
# If the subscription is invalid, remove it
|
|
if hasattr(e, 'response') and e.response and e.response.status_code in [410, 404]:
|
|
db.query(PushSubscription).filter(PushSubscription.user_id == user_id).delete()
|
|
db.commit()
|
|
except Exception as e:
|
|
logger.error(f"Failed to send push notification to user {user_id}: {e}")
|
|
|
|
async def unsubscribe_user(self, db: Session, user_id: int) -> bool:
|
|
"""Unsubscribe a user from push notifications"""
|
|
try:
|
|
db.query(PushSubscription).filter(PushSubscription.user_id == user_id).delete()
|
|
db.commit()
|
|
logger.info(f"Push subscription removed for user {user_id}")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Failed to remove push subscription for user {user_id}: {e}")
|
|
db.rollback()
|
|
return False
|
|
|
|
# Global instance
|
|
push_service = PushNotificationService()
|