from typing import Any import aiohttp from api.clients.browser_rpc_contracts import BrowserRpcError class BrowserRpcClient: def __init__(self, rpc_url: str, session: aiohttp.ClientSession) -> None: self._run_url = rpc_url.rstrip("/") if self._run_url.endswith("/run"): self._base_url = self._run_url[: -len("/run")] else: self._base_url = self._run_url self._run_url = f"{self._base_url}/run" self._session = session async def _post_json(self, url: str, payload: dict[str, Any], timeout_sec: float | None = None) -> dict[str, Any]: timeout = aiohttp.ClientTimeout(total=timeout_sec) if timeout_sec is not None else None try: async with self._session.post(url, json=payload, timeout=timeout) as response: if response.status >= 400: body = await response.text() raise BrowserRpcError(f"RPC HTTP: {response.status}: {body}") try: data = await response.json(content_type=None) except aiohttp.ContentTypeError as exc: raise BrowserRpcError("RPC returned non-JSON response") from exc except aiohttp.ClientError as exc: raise BrowserRpcError(f"Transport error: {exc}") from exc if not isinstance(data, dict): raise BrowserRpcError("RPC returned invalid payload type") return data async def run(self, task_id: str, task: str, timeout_sec: float) -> dict[str, Any]: return await self._post_json(self._run_url, {"task_id": task_id, "task": task}, timeout_sec=timeout_sec) async def verify_captcha(self, task_id: str) -> dict[str, Any]: return await self._post_json(f"{self._base_url}/verify", {"task_id": task_id}, timeout_sec=15) async def resume(self, task_id: str, timeout_sec: float) -> dict[str, Any]: return await self._post_json(f"{self._base_url}/resume", {"task_id": task_id}, timeout_sec=timeout_sec) async def abort(self, task_id: str, reason: str | None = None) -> dict[str, Any]: return await self._post_json(f"{self._base_url}/abort", {"task_id": task_id, "reason": reason}, timeout_sec=15) async def run_browser_task(rpc_url: str, task_id: str, task: str, timeout_sec: float) -> dict[str, Any]: async with aiohttp.ClientSession() as session: client = BrowserRpcClient(rpc_url, session=session) return await client.run(task_id=task_id, task=task, timeout_sec=timeout_sec)