216 lines
7.0 KiB
Python
216 lines
7.0 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
|
|
from app.services.decorators import require_credits
|
|
|
|
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 emit_credits_required(user_id: int, credits_needed: int) -> None:
|
|
"""Emit an event when credits are required."""
|
|
SocketIOService.emit_to_user(
|
|
user_id,
|
|
"credits_required",
|
|
{"credits_needed": credits_needed},
|
|
)
|
|
|
|
@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
|
|
SocketIOService.emit_to_user(user_id, "auth_success", {"user": user})
|
|
SocketIOService.emit_to_user(
|
|
user_id, "credits_changed", {"credits": user["credits"]}
|
|
)
|
|
|
|
except Exception:
|
|
logger.exception("Error handling SocketIO authentication")
|
|
# emit("auth_error", {"error": "Authentication failed"})
|
|
disconnect()
|
|
|
|
|
|
# @socketio.on("play_sound")
|
|
# @require_credits(1)
|
|
# def handle_play_sound(data):
|
|
# """Handle play_sound event from client."""
|
|
# try:
|
|
# user = SocketIOService.get_user_from_socketio()
|
|
|
|
# if not user:
|
|
# logger.warning("SocketIO play_sound failed - no authenticated user")
|
|
# # emit("error", {"message": "Authentication required"})
|
|
# return
|
|
|
|
# user_id = int(user["id"])
|
|
# sound_id = data.get("soundId")
|
|
# if not sound_id:
|
|
# logger.warning("SocketIO play_sound failed - no soundId provided")
|
|
# SocketIOService.emit_to_user(
|
|
# user_id, "error", {"message": "Sound ID required"}
|
|
# )
|
|
# return
|
|
|
|
# # Import and use the VLC service to play the sound
|
|
# from app.services.vlc_service import vlc_service
|
|
|
|
# logger.info(f"User {user_id} playing sound {sound_id} via SocketIO")
|
|
|
|
# # Play the sound using the VLC service
|
|
# success = vlc_service.play_sound(sound_id, user_id)
|
|
|
|
# if not success:
|
|
# SocketIOService.emit_to_user(
|
|
# user_id,
|
|
# "error",
|
|
# {"message": f"Failed to play sound {sound_id}"},
|
|
# )
|
|
|
|
# except Exception as e:
|
|
# logger.exception(f"Error handling play_sound event: {e}")
|
|
# # emit("error", {"message": "Failed to play sound"})
|
|
|
|
|
|
@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()
|