/
kan64
/
spreadsheet-lab
Обзор
Документация
Войти
/
kan64
/
spreadsheet-lab
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
develop
client/src/hooks/useSheetEvents.ts
110 строк
3 KB
Anton Kravchenkov
feat: fix
30 мар 2026, 23:14
30 мар 2026, 23:14
b5f409e
Код
Авторство
О чём код?
import { useEffect, useRef } from 'react'; import { handleUnauthorized } from '../utils/api'; const API_BASE = '/api'; function getToken(): string | null { return localStorage.getItem('auth_token'); } type SheetEventType = | 'cell' | 'layout' | 'insertRow' | 'deleteRow' | 'insertColumn' | 'deleteColumn' | 'pasteRows'; export interface SheetEvent { type: SheetEventType; data: unknown; } type OnEvent = (event: SheetEvent) => void; /** * Subscribes to Server-Sent Events for a sheet. Reconnects on disconnect. * Uses fetch (not EventSource) to send Authorization header. */ export function useSheetEvents(sheetId: string | null, onEvent: OnEvent): void { const onEventRef = useRef(onEvent); onEventRef.current = onEvent; useEffect(() => { if (!sheetId) return; let cancelled = false; let retryTimeout: ReturnType<typeof setTimeout>; function connect() { if (cancelled) return; const token = getToken(); if (!token) return; fetch(`${API_BASE}/sheets/${sheetId}/events`, { headers: { Authorization: `Bearer ${token}` }, }) .then((res) => { if (res.status === 401) { handleUnauthorized(); return; } if (!res.ok || !res.body) { throw new Error(`SSE failed: ${res.status}`); } const reader = res.body!.getReader(); const decoder = new TextDecoder(); let buffer = ''; let eventType = ''; function processLine(line: string) { if (line.startsWith('event:')) { eventType = line.slice(6).trim(); } else if (line.startsWith('data:') && eventType) { const dataStr = line.slice(5).trim(); if (dataStr) { try { const data = JSON.parse(dataStr); onEventRef.current({ type: eventType as SheetEventType, data }); } catch { // ignore parse errors } } eventType = ''; } } function read() { reader.read().then(({ done, value }) => { if (cancelled || done) { if (!cancelled) { retryTimeout = setTimeout(connect, 2000); } return; } buffer += decoder.decode(value, { stream: true }); const lines = buffer.split(/\r?\n/); buffer = lines.pop() ?? ''; for (const line of lines) { processLine(line); } read(); }); } read(); }) .catch((err) => { if (!cancelled) { retryTimeout = setTimeout(connect, 2000); } }); } connect(); return () => { cancelled = true; clearTimeout(retryTimeout); }; }, [sheetId]); }