Files
sdb-back/app/services/socketio_service.py

162 lines
5.4 KiB
Python

"""SocketIO service for real-time communication."""
import logging
from flask import request
from flask_socketio import disconnect, emit, join_room, leave_room
from app import socketio
logger = logging.getLogger(__name__)
class SocketIOService:
"""Service for managing SocketIO connections and user rooms."""
@staticmethod
def get_user_room(user_id: int) -> str:
"""Get the room name for a specific user."""
return f"user_{user_id}"
@staticmethod
def emit_to_user(user_id: int, event: str, data: dict) -> None:
"""Emit an event to a specific user's room."""
room = SocketIOService.get_user_room(user_id)
socketio.emit(event, data, room=room)
logger.debug(f"Emitted {event} to user {user_id} in room {room}")
@staticmethod
def emit_to_all(event: str, data: dict) -> None:
"""Emit an event to all connected clients."""
try:
socketio.emit(event, data)
logger.info(f"Successfully emitted {event} to all clients with data keys: {list(data.keys())}")
except Exception as e:
logger.error(f"Failed to emit {event}: {e}")
@staticmethod
def emit_credits_changed(user_id: int, new_credits: int) -> None:
"""Emit credits_changed event to a user."""
SocketIOService.emit_to_user(
user_id,
"credits_changed",
{"credits": new_credits},
)
@staticmethod
def get_user_from_socketio() -> dict | None:
"""Get user from SocketIO connection using cookies."""
try:
from flask import current_app
from flask_jwt_extended import decode_token
# Check if we have the access_token cookie
access_token = request.cookies.get("access_token_cookie")
logger.debug(f"Access token from cookies: {access_token[:20] if access_token else None}...")
if not access_token:
logger.debug("No access token found in cookies")
return None
# Decode the JWT token manually
with current_app.app_context():
try:
decoded_token = decode_token(access_token)
current_user_id = decoded_token["sub"]
logger.debug(f"Decoded user ID: {current_user_id}")
if not current_user_id:
logger.debug("No user ID in token")
return None
except Exception as e:
logger.debug(f"Token decode error: {e}")
return None
# Query database for user data
from app.models.user import User
user = User.query.get(int(current_user_id))
if not user or not user.is_active:
logger.debug(f"User not found or inactive: {current_user_id}")
return None
logger.debug(f"Successfully found user: {user.email}")
return {
"id": str(user.id),
"email": user.email,
"name": user.name,
"role": user.role,
"credits": user.credits,
}
except Exception as e:
logger.debug(f"Exception in get_user_from_socketio: {e}")
return None
@socketio.on("connect")
def handle_connect(auth=None):
"""Handle client connection."""
try:
logger.info(f"SocketIO connection established from {request.remote_addr}")
logger.info(f"Session ID: {request.sid}")
except Exception:
logger.exception("Error handling SocketIO connection")
disconnect()
@socketio.on("authenticate")
def handle_authenticate(data):
"""Handle authentication after connection."""
try:
user = SocketIOService.get_user_from_socketio()
if not user:
logger.warning("SocketIO authentication failed - no user found")
emit("auth_error", {"error": "Authentication failed"})
disconnect()
return
user_id = int(user["id"])
user_room = SocketIOService.get_user_room(user_id)
# Join user-specific room
join_room(user_room)
logger.info(f"User {user_id} authenticated and joined room {user_room}")
# Send current credits on authentication
emit("auth_success", {"user": user})
emit("credits_changed", {"credits": user["credits"]})
except Exception:
logger.exception("Error handling SocketIO authentication")
emit("auth_error", {"error": "Authentication failed"})
disconnect()
@socketio.on("test_event")
def handle_test_event(data):
"""Test handler to verify SocketIO events are working."""
logger.debug(f"Test event received: {data}")
emit("test_response", {"message": "Test event received successfully"})
@socketio.on("disconnect")
def handle_disconnect():
"""Handle client disconnection."""
try:
user = SocketIOService.get_user_from_socketio()
if user:
user_id = int(user["id"])
user_room = SocketIOService.get_user_room(user_id)
leave_room(user_room)
logger.info(f"User {user_id} disconnected from SocketIO")
except Exception:
logger.exception("Error handling SocketIO disconnection")
# Export the service instance
socketio_service = SocketIOService()