ForcePilot/backend/package/yuxi/channels/adapters/signal/sse_reconnect.py
Kris 8dc86766f1 feat(channels/signal): 新增Signal渠道适配器完整实现
新增了完整的Signal渠道适配器实现,包含RPC客户端、守护进程管理、安全策略、消息处理、安装配置工具等全套功能,支持通过signal-cli与Signal网络进行通信,包含账户管理、消息收发、反应处理、媒体分析、健康检查等能力。
2026-05-12 00:48:25 +08:00

76 lines
2.8 KiB
Python

from __future__ import annotations
import asyncio
import logging
import random
from collections.abc import Callable, Awaitable
import aiohttp
logger = logging.getLogger(__name__)
MAX_RECONNECT_DELAY = 60.0
INITIAL_RECONNECT_DELAY = 1.0
JITTER = 0.1
async def sse_event_stream(
url: str,
params: dict,
on_event: Callable[[str], Awaitable[None]],
) -> None:
reconnect_delay = INITIAL_RECONNECT_DELAY
last_event_id: str | None = None
unauth_backoff = False
while True:
try:
headers: dict[str, str] = {"Accept": "text/event-stream"}
if last_event_id:
headers["Last-Event-ID"] = last_event_id
async with aiohttp.ClientSession() as session:
async with session.get(url, params=params, headers=headers) as response:
if response.status == 401:
unauth_backoff = True
logger.error("SSE connection received 401 Unauthorized, backing off for 30s")
await asyncio.sleep(30.0)
reconnect_delay = INITIAL_RECONNECT_DELAY
continue
if response.status != 200:
logger.error(f"SSE connection failed: {response.status}")
await asyncio.sleep(reconnect_delay)
reconnect_delay = min(reconnect_delay * 2, MAX_RECONNECT_DELAY)
continue
if unauth_backoff:
logger.info("SSE reconnected successfully after 401 backoff")
unauth_backoff = False
reconnect_delay = INITIAL_RECONNECT_DELAY
async for line in response.content:
line_text = line.decode("utf-8").strip()
if line_text.startswith("id:"):
last_event_id = line_text.removeprefix("id:").strip()
continue
if line_text.startswith("data:"):
event_data = line_text.removeprefix("data:").strip()
if event_data:
try:
await on_event(event_data)
except Exception:
logger.exception("Error handling SSE event")
except (TimeoutError, aiohttp.ClientError) as e:
logger.warning(f"SSE connection lost: {e}, reconnecting in {reconnect_delay:.1f}s")
jitter_ms = reconnect_delay * JITTER * random.random()
await asyncio.sleep(reconnect_delay + jitter_ms)
reconnect_delay = min(reconnect_delay * 2, MAX_RECONNECT_DELAY)
except asyncio.CancelledError:
logger.info("SSE event stream cancelled")
break