"""SSE-брокер событий. Сервер рассылает события подключённым браузерам: new_lead, lead_updated, toast, reminder_due, project_updated, system_status. Поток однонаправленный, по ТЗ — Server-Sent Events с автопереподключением. """ from __future__ import annotations import asyncio import json from typing import Any class Broker: def __init__(self) -> None: self._subscribers: set[asyncio.Queue] = set() self._lock = asyncio.Lock() async def subscribe(self) -> asyncio.Queue: q: asyncio.Queue = asyncio.Queue(maxsize=200) async with self._lock: self._subscribers.add(q) return q async def unsubscribe(self, q: asyncio.Queue) -> None: async with self._lock: self._subscribers.discard(q) async def publish(self, event_type: str, data: Any) -> None: payload = f"event: {event_type}\ndata: {json.dumps(data, ensure_ascii=False)}\n\n" async with self._lock: subs = list(self._subscribers) for q in subs: try: q.put_nowait(payload) except asyncio.QueueFull: # при переполнении сбрасываем очередь подписчика — браузер переподключится try: q.get_nowait() except Exception: pass try: q.put_nowait(payload) except Exception: pass async def publish_toast(self, text: str, icon: str = "check") -> None: await self.publish("toast", {"text": text, "icon": icon}) broker = Broker()