Files
LogWhispererAI/.opencode/skills/examples/ws-integration.md
Luca Sacchi Ricciardi aa489c7eb8 docs: add comprehensive frontend landing page plan and download design skills
Add detailed landing page development plan in docs/frontend_landing_plan.md:
- Complete landing page structure (Hero, Problem/Solution, Features, Demo, CTA)
- Design guidelines from downloaded skills (typography, color, motion, composition)
- Security considerations (XSS prevention, input sanitization, CSP)
- Performance targets (LCP <2.5s, bundle <150KB, Lighthouse >90)
- Responsiveness and accessibility requirements (WCAG 2.1 AA)
- Success KPIs and monitoring setup
- 3-week development timeline with daily tasks
- Definition of Done checklist

Download 10+ frontend/UI/UX skills via universal-skills-manager:
- frontend-ui-ux: UI/UX design without mockups
- frontend-design-guidelines: Production-grade interface guidelines
- frontend-developer: React best practices (40+ rules)
- frontend-engineer: Next.js 14 App Router patterns
- ui-ux-master: Comprehensive design systems and accessibility
- ui-ux-systems-designer: Information architecture and interaction
- ui-ux-design-user-experience: Platform-specific guidelines
- Plus additional reference materials and validation scripts

Configure universal-skills MCP with SkillsMP API key for curated skill access.

Safety first: All skills validated before installation, no project code modified.

Refs: Universal Skills Manager (github:jacob-bd/universal-skills-manager)
Next: Begin Sprint 3 landing page development
2026-04-03 13:13:59 +02:00

302 lines
9.3 KiB
Markdown

# WebSocket Integration Example
Реальный пример из Family Budget: интеграция WebSocket для real-time обновлений.
## Files
- **WebSocket Client**: `frontend/web/static/js/budget/budgetWSClient.js`
- **Backend Manager**: `backend/app/api/v1/endpoints/budget_sse.py`
- **Usage**: `frontend/web/templates/base.html`
## Architecture
```
┌──────────────────────────────────────────────────────────┐
│ Frontend: budgetWSClient.js │
│ │
│ new BudgetWSClient() → WebSocket("/api/v1/budget/ws") │
│ ↓ │
│ Event handlers: │
│ - budget_fact_created │
│ - budget_fact_updated │
│ - budget_fact_deleted │
│ - article_updated │
└──────────────────────────────────────────────────────────┘
↓ (WebSocket)
┌──────────────────────────────────────────────────────────┐
│ Backend: BudgetConnectionManager (in-memory) │
│ │
│ - Active connections: Map<connection_id, WebSocket> │
│ - Broadcast events to all connected clients │
│ - WORKERS=1 constraint (no Redis Pub/Sub) │
└──────────────────────────────────────────────────────────┘
↑ (Broadcast)
┌──────────────────────────────────────────────────────────┐
│ API Endpoints: /api/v1/facts │
│ │
│ POST /facts → ws_manager.broadcast("budget_fact_created", data) │
│ PUT /facts/{id} → ws_manager.broadcast("budget_fact_updated", data) │
│ DELETE /facts/{id} → ws_manager.broadcast("budget_fact_deleted", data) │
└──────────────────────────────────────────────────────────┘
```
## Frontend Implementation
### 1. Initialize WebSocket Client
```javascript
// In base.html (global scope)
<script type="module">
import { BudgetWSClient } from '/static/js/budget/budgetWSClient.js';
window.budgetWS = new BudgetWSClient();
// Register event handlers
budgetWS.on('budget_fact_created', (data) => {
console.log('New fact created:', data);
// Refresh HTMX content
htmx.trigger('#recent-transactions', 'refresh');
htmx.trigger('#quick-stats', 'refresh');
});
budgetWS.on('budget_fact_updated', (data) => {
console.log('Fact updated:', data);
htmx.trigger('#recent-transactions', 'refresh');
});
budgetWS.on('budget_fact_deleted', (data) => {
console.log('Fact deleted:', data.id);
htmx.trigger('#recent-transactions', 'refresh');
});
// Connect
budgetWS.connect();
</script>
```
### 2. WebSocket Client (Simplified)
```javascript
export class BudgetWSClient {
constructor() {
this.ws = null;
this.isConnected = false;
this.handlers = {};
}
connect() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/api/v1/budget/ws`;
this.ws = new WebSocket(wsUrl);
this.ws.onopen = () => {
console.log('[BudgetWS] Connected');
this.isConnected = true;
};
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
const { event: eventType, data } = message;
// Trigger registered handlers
if (this.handlers[eventType]) {
this.handlers[eventType].forEach(handler => handler(data));
}
};
this.ws.onclose = () => {
console.log('[BudgetWS] Disconnected');
this.isConnected = false;
// Reconnect after 3 seconds
setTimeout(() => this.connect(), 3000);
};
this.ws.onerror = (error) => {
console.error('[BudgetWS] Error:', error);
};
}
on(event, handler) {
if (!this.handlers[event]) {
this.handlers[event] = [];
}
this.handlers[event].push(handler);
}
disconnect() {
if (this.ws) {
this.ws.close();
}
}
}
```
## Backend Implementation
### 1. WebSocket Manager
```python
# backend/app/api/v1/endpoints/budget_sse.py
from fastapi import WebSocket, WebSocketDisconnect
from typing import Dict
import logging
logger = logging.getLogger(__name__)
class BudgetConnectionManager:
"""
In-memory WebSocket connection manager
CRITICAL: Works only with WORKERS=1
Multiple workers = separate managers = lost events
"""
def __init__(self):
self.active_connections: Dict[str, WebSocket] = {}
async def connect(self, websocket: WebSocket, connection_id: str):
await websocket.accept()
self.active_connections[connection_id] = websocket
logger.info(f"WebSocket connected: {connection_id}")
def disconnect(self, connection_id: str):
if connection_id in self.active_connections:
del self.active_connections[connection_id]
logger.info(f"WebSocket disconnected: {connection_id}")
async def broadcast(self, event: str, data: dict):
"""Broadcast event to all connected clients"""
message = {"event": event, "data": data}
disconnected = []
for conn_id, websocket in self.active_connections.items():
try:
await websocket.send_json(message)
except Exception as e:
logger.error(f"Broadcast error to {conn_id}: {e}")
disconnected.append(conn_id)
# Remove disconnected clients
for conn_id in disconnected:
self.disconnect(conn_id)
# Global instance
ws_manager = BudgetConnectionManager()
```
### 2. WebSocket Endpoint
```python
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
import uuid
router = APIRouter()
@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
connection_id = str(uuid.uuid4())
await ws_manager.connect(websocket, connection_id)
try:
while True:
# Keep connection alive (receive pings)
data = await websocket.receive_text()
# Optional: handle client messages
except WebSocketDisconnect:
ws_manager.disconnect(connection_id)
```
### 3. Broadcast from API Endpoints
```python
# backend/app/api/v1/endpoints/facts.py
@router.post("/", response_model=FactResponse)
async def create_fact(
fact_data: FactCreate,
session: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
# Create fact
fact = await fact_service.create_fact(session, fact_data, current_user)
# Broadcast WebSocket event
await ws_manager.broadcast(
"budget_fact_created",
{
"id": fact.id,
"amount": fact.amount,
"article_id": fact.article_id,
"financial_center_id": fact.financial_center_id,
}
)
return fact
```
## Key Features
### 1. Auto-Reconnect
```javascript
this.ws.onclose = () => {
setTimeout(() => this.connect(), 3000); // Reconnect after 3 sec
};
```
### 2. Event-Driven Architecture
```javascript
// Register handler
budgetWS.on('budget_fact_created', (data) => {
// Handle event
});
// Broadcast from backend
await ws_manager.broadcast("budget_fact_created", data);
```
### 3. HTMX Integration
```javascript
// Refresh HTMX content on WebSocket event
htmx.trigger('#recent-transactions', 'refresh');
```
### 4. Single Worker Constraint
```yaml
# docker-compose.yml
services:
backend:
command: uvicorn app.main:app --workers 1 # CRITICAL!
```
**Why WORKERS=1?**
- `BudgetConnectionManager` is in-memory
- Each worker has separate instance
- Events don't propagate between workers
- User A (worker 1) creates fact → only worker 1 clients receive event
- User B (worker 2) doesn't see update
**Future: Redis Pub/Sub**
- Use Redis for event broadcasting
- Workers subscribe to Redis channel
- Events propagate across all workers
- Can scale to multiple workers
## Performance
- **Connection time**: ~100ms
- **Event latency**: <50ms (same worker)
- **Reconnect time**: 3 seconds
- **Memory**: ~5KB per connection
## References
- **WebSocket API**: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
- **FastAPI WebSocket**: https://fastapi.tiangolo.com/advanced/websockets/
- **Real implementation**: `frontend/web/static/js/budget/budgetWSClient.js`