feat: Enable telephony for OSS (#21)

* fix: fix tooltip bug

* feat: add Twilio with CloudFlare configuration

* chore: update Tella Video
This commit is contained in:
Abhishek 2025-10-04 12:22:50 +05:30 committed by GitHub
parent d39a8111a6
commit 8e2e5c9327
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 891 additions and 191 deletions

104
api/utils/tunnel.py Normal file
View file

@ -0,0 +1,104 @@
"""Utility for getting the cloudflared tunnel URL at runtime."""
import asyncio
import os
import re
from typing import Optional
import aiohttp
from loguru import logger
class TunnelURLProvider:
"""Provider for getting the tunnel URL from cloudflared or environment."""
@classmethod
async def get_tunnel_url(cls) -> str:
"""
Get the tunnel URL for external access.
Priority:
1. BACKEND_API_ENDPOINT environment variable (if set)
2. Query cloudflared metrics endpoint
3. Raise error if neither available
Returns:
str: The tunnel domain (without protocol)
Raises:
ValueError: If no tunnel URL can be determined
"""
# First priority: Check environment variable
env_endpoint = os.getenv("BACKEND_API_ENDPOINT")
if env_endpoint:
logger.debug(f"Using BACKEND_API_ENDPOINT from environment: {env_endpoint}")
return env_endpoint
# Second priority: Query cloudflared
try:
# Try to get URL from cloudflared metrics
url = await cls._get_cloudflared_url()
if url:
logger.info(f"Retrieved tunnel URL from cloudflared: {url}")
return url
except Exception as e:
logger.warning(f"Failed to get tunnel URL from cloudflared: {e}")
raise ValueError(
"No tunnel URL available. Please set BACKEND_API_ENDPOINT environment "
"variable or ensure cloudflared service is running."
)
@classmethod
async def _get_cloudflared_url(cls) -> Optional[str]:
"""
Query cloudflared metrics endpoint to get the tunnel URL.
Returns:
Optional[str]: The tunnel domain (without protocol), or None if not found
"""
try:
# Try to connect to cloudflared metrics endpoint
# The service name in docker-compose is 'cloudflared'
metrics_url = "http://cloudflared:2000/metrics"
async with aiohttp.ClientSession() as session:
async with session.get(
metrics_url, timeout=aiohttp.ClientTimeout(total=5)
) as response:
if response.status != 200:
logger.warning(
f"Cloudflared metrics returned status {response.status}"
)
return None
text = await response.text()
# Look for the tunnel URL in metrics
# Cloudflared exposes this in the userHostname metric
match = re.search(r'userHostname="([^"]+)"', text)
if match:
hostname = match.group(1)
# Remove https:// or wss:// if present
hostname = hostname.replace("https://", "").replace(
"wss://", ""
)
return hostname
# Alternative: Look for trycloudflare.com domain
match = re.search(r"([a-z0-9-]+\.trycloudflare\.com)", text)
if match:
return match.group(1)
logger.warning("Could not find tunnel URL in cloudflared metrics")
return None
except asyncio.TimeoutError:
logger.warning("Timeout connecting to cloudflared metrics endpoint")
return None
except aiohttp.ClientError as e:
logger.warning(f"Error connecting to cloudflared: {e}")
return None
except Exception as e:
logger.error(f"Unexpected error getting cloudflared URL: {e}")
return None