Backend: - Sessions de vote : list, close, tally, threshold details, auto-expiration - Protocoles : update, simulate, meta-gouvernance, formulas CRUD - Service vote enrichi : close_session, get_threshold_details, nuanced breakdown - Schemas : ThresholdDetailOut, VoteResultOut, FormulaSimulationRequest/Result - WebSocket broadcast sur chaque vote + fermeture session - 25 nouveaux tests (threshold details, close, nuanced, simulation) Frontend: - 5 composants vote : VoteBinary, VoteNuanced, ThresholdGauge, FormulaDisplay, VoteHistory - 3 composants protocoles : ProtocolPicker, FormulaEditor, ModeParamsDisplay - Simulateur de formules interactif (page /protocols/formulas) - Page detail protocole (/protocols/[id]) - Composable useWebSocket (live updates) - Composable useVoteFormula (calcul client-side reactif) - Integration KaTeX pour rendu LaTeX des formules Documentation: - API reference : 8 nouveaux endpoints documentes - Formules : tables d'inertie, parametres detailles, simulation API - Guide vote : vote binaire/nuance, jauge, historique, simulateur, meta-gouvernance 55 tests passes (+ 1 skipped), 126 fichiers total. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
101 lines
2.3 KiB
TypeScript
101 lines
2.3 KiB
TypeScript
/**
|
|
* Composable for WebSocket connectivity to receive live vote updates.
|
|
*
|
|
* Connects to the backend WS endpoint and allows subscribing to
|
|
* individual vote session channels for real-time tally updates.
|
|
*/
|
|
export function useWebSocket() {
|
|
const config = useRuntimeConfig()
|
|
let ws: WebSocket | null = null
|
|
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
|
const connected = ref(false)
|
|
const lastMessage = ref<any>(null)
|
|
|
|
/**
|
|
* Open a WebSocket connection to the backend live endpoint.
|
|
*/
|
|
function connect() {
|
|
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
|
|
return
|
|
}
|
|
|
|
const wsUrl = config.public.apiBase
|
|
.replace(/^http/, 'ws')
|
|
.replace(/\/api\/v1$/, '/api/v1/ws/live')
|
|
|
|
ws = new WebSocket(wsUrl)
|
|
|
|
ws.onopen = () => {
|
|
connected.value = true
|
|
if (reconnectTimer) {
|
|
clearTimeout(reconnectTimer)
|
|
reconnectTimer = null
|
|
}
|
|
}
|
|
|
|
ws.onclose = () => {
|
|
connected.value = false
|
|
reconnect()
|
|
}
|
|
|
|
ws.onerror = () => {
|
|
connected.value = false
|
|
}
|
|
|
|
ws.onmessage = (event: MessageEvent) => {
|
|
try {
|
|
lastMessage.value = JSON.parse(event.data)
|
|
} catch {
|
|
lastMessage.value = event.data
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Subscribe to real-time updates for a vote session.
|
|
*/
|
|
function subscribe(sessionId: string) {
|
|
if (ws?.readyState === WebSocket.OPEN) {
|
|
ws.send(JSON.stringify({ action: 'subscribe', session_id: sessionId }))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unsubscribe from a vote session's updates.
|
|
*/
|
|
function unsubscribe(sessionId: string) {
|
|
if (ws?.readyState === WebSocket.OPEN) {
|
|
ws.send(JSON.stringify({ action: 'unsubscribe', session_id: sessionId }))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gracefully close the WebSocket connection.
|
|
*/
|
|
function disconnect() {
|
|
if (reconnectTimer) {
|
|
clearTimeout(reconnectTimer)
|
|
reconnectTimer = null
|
|
}
|
|
if (ws) {
|
|
ws.onclose = null
|
|
ws.close()
|
|
ws = null
|
|
}
|
|
connected.value = false
|
|
}
|
|
|
|
/**
|
|
* Schedule a reconnection attempt after a delay.
|
|
*/
|
|
function reconnect() {
|
|
if (reconnectTimer) return
|
|
reconnectTimer = setTimeout(() => {
|
|
reconnectTimer = null
|
|
connect()
|
|
}, 3000)
|
|
}
|
|
|
|
return { connected, lastMessage, connect, subscribe, unsubscribe, disconnect }
|
|
}
|