Инициализировать репозиторий «Дейл»
Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ, инструкция пользователя, техдокументация, код-стайл), бэклог, скрипты развёртывания и архив прототипа LeadRadar.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user