mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Implement audio calls
This commit is contained in:
+3
-2
@@ -5,7 +5,7 @@ import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
from routes import account, messaging, profile, push
|
||||
from routes import account, messaging, profile, push, webrtc
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
@@ -56,4 +56,5 @@ app.add_middleware(
|
||||
app.include_router(account.router)
|
||||
app.include_router(messaging.router)
|
||||
app.include_router(profile.router)
|
||||
app.include_router(push.router, prefix="/push")
|
||||
app.include_router(push.router, prefix="/push")
|
||||
app.include_router(webrtc.router, prefix="/webrtc")
|
||||
@@ -911,6 +911,29 @@ class MessaggingSocketManager:
|
||||
await websocket.send_json({"type": type, "data": response})
|
||||
except HTTPException as e:
|
||||
await self.send_error(websocket, type, e)
|
||||
elif type == "call_signaling":
|
||||
# Forward WebRTC signaling between peers
|
||||
try:
|
||||
self.user_by_ws[websocket] = current_user.id
|
||||
|
||||
payload = data.get("data") or {}
|
||||
to_user_id = int(payload.get("toUserId") or 0)
|
||||
if not to_user_id:
|
||||
raise HTTPException(status_code=400, detail="Missing toUserId")
|
||||
|
||||
# Ensure sender is set by the server
|
||||
payload["fromUserId"] = current_user.id
|
||||
payload["fromUsername"] = current_user.username
|
||||
|
||||
await self.send_to_user(to_user_id, {
|
||||
"type": "call_signaling",
|
||||
"data": payload
|
||||
})
|
||||
|
||||
# Optional ack
|
||||
await websocket.send_json({"type": "call_signaling", "data": {"status": "ok"}})
|
||||
except HTTPException as e:
|
||||
await self.send_error(websocket, type, e)
|
||||
else:
|
||||
await websocket.send_json({"type": type, "error": {"code": 400, "detail": "Invalid type"}})
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import logging
|
||||
import os
|
||||
import hmac
|
||||
import hashlib
|
||||
import time
|
||||
from fastapi import APIRouter, Depends
|
||||
from dependencies import get_current_user
|
||||
import traceback
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger("uvicorn.error")
|
||||
|
||||
|
||||
def generate_turn_credentials(username: str, secret: str, expiration_minutes: int = 60):
|
||||
"""Generate time-limited TURN credentials using TURN REST API format.
|
||||
|
||||
This creates temporary credentials that expire after the specified time.
|
||||
The username format is: timestamp:username
|
||||
The password is an HMAC hash of the username and secret.
|
||||
"""
|
||||
# Current timestamp (seconds since epoch)
|
||||
timestamp = int(time.time()) + (expiration_minutes * 60)
|
||||
|
||||
# Create temporary username: timestamp:original_username
|
||||
temp_username = f"{timestamp}:{username}"
|
||||
|
||||
# Generate password using HMAC-SHA1
|
||||
temp_password = hmac.new(
|
||||
secret.encode('utf-8'),
|
||||
temp_username.encode('utf-8'),
|
||||
hashlib.sha1
|
||||
).hexdigest()
|
||||
|
||||
return temp_username, temp_password
|
||||
|
||||
|
||||
@router.get("/ice")
|
||||
async def get_ice_servers(current_user = Depends(get_current_user)):
|
||||
"""Return ICE server configuration (STUN/TURN) for WebRTC clients.
|
||||
|
||||
Generates time-limited TURN credentials that expire in 1 hour.
|
||||
"""
|
||||
try:
|
||||
# Prefer using your own coturn for both STUN and TURN
|
||||
turn_domain = "fromchat.ru"
|
||||
stun_urls = [
|
||||
f"stun:{turn_domain}:3478",
|
||||
f"stuns:{turn_domain}:5349",
|
||||
]
|
||||
|
||||
turn_urls = [
|
||||
f"turn:{turn_domain}:3478",
|
||||
f"turns:{turn_domain}:5349",
|
||||
]
|
||||
|
||||
# Get TURN configuration from environment
|
||||
turn_username = os.getenv("TURN_USERNAME")
|
||||
turn_secret = os.getenv("TURN_SECRET")
|
||||
|
||||
# Check if required environment variables are set
|
||||
if not turn_username:
|
||||
logger.error("ERROR: TURN_USERNAME environment variable is not set")
|
||||
raise ValueError("TURN_USERNAME environment variable is not set")
|
||||
|
||||
if not turn_secret:
|
||||
logger.error("ERROR: TURN_SECRET environment variable is not set")
|
||||
raise ValueError("TURN_SECRET environment variable is not set")
|
||||
|
||||
ice_servers: list[dict] = [{"urls": url} for url in stun_urls]
|
||||
|
||||
temp_username, temp_password = generate_turn_credentials(
|
||||
turn_username,
|
||||
turn_secret,
|
||||
expiration_minutes=60 # Expires in 1 hour
|
||||
)
|
||||
|
||||
ice_servers.append({
|
||||
"urls": turn_urls,
|
||||
"username": temp_username,
|
||||
"credential": temp_password,
|
||||
})
|
||||
|
||||
return {"iceServers": ice_servers}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"ERROR in /api/webrtc/ice: {str(e)}")
|
||||
logger.error(f"ERROR type: {type(e).__name__}")
|
||||
traceback.print_exc()
|
||||
raise
|
||||
Reference in New Issue
Block a user