"""Transparent WebSocket frame relay — no parse, no audio processing.""" import asyncio import logging import websockets from fastapi import WebSocket, WebSocketDisconnect from websockets.exceptions import ConnectionClosed logger = logging.getLogger(__name__) async def relay_text_websockets(left: WebSocket, right: WebSocket) -> None: stop = asyncio.Event() async def forward(source: WebSocket, destination: WebSocket) -> None: try: while not stop.is_set(): message = await source.receive_text() await destination.send_text(message) except (WebSocketDisconnect, RuntimeError): stop.set() await asyncio.gather(forward(left, right), forward(right, left)) async def relay_provider_to_upstream( provider: WebSocket, upstream_url: str, *, initial_frames: list[str] | None = None, additional_headers: dict[str, str] | None = None, ) -> None: """Raw carrier ↔ customer upstream tunnel.""" logger.info("Opening upstream tunnel to %s", upstream_url) async with websockets.connect( upstream_url, open_timeout=10, close_timeout=5, additional_headers=additional_headers, ping_interval=20, ping_timeout=20, max_queue=16, ) as upstream: for frame in initial_frames or []: await upstream.send(frame) async def provider_to_upstream() -> None: try: while True: raw = await provider.receive_text() await upstream.send(raw) except (WebSocketDisconnect, RuntimeError): return async def upstream_to_provider() -> None: try: while True: raw = await upstream.recv() if isinstance(raw, bytes): raw = raw.decode("utf-8") await provider.send_text(raw) except (ConnectionClosed, WebSocketDisconnect, RuntimeError): return await asyncio.gather(provider_to_upstream(), upstream_to_provider())