feat: usePresence hook for WebSocket-based page presence
Made-with: Cursor
This commit is contained in:
83
frontend/src/hooks/usePresence.js
Normal file
83
frontend/src/hooks/usePresence.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
|
||||
const WS_RECONNECT_DELAY = 3000;
|
||||
const PING_INTERVAL = 30000;
|
||||
|
||||
export function usePresence() {
|
||||
const { token, adminName, isAuthenticated } = useAuth();
|
||||
const location = useLocation();
|
||||
const [viewers, setViewers] = useState([]);
|
||||
const wsRef = useRef(null);
|
||||
const pingRef = useRef(null);
|
||||
const reconnectRef = useRef(null);
|
||||
|
||||
const getWsUrl = useCallback(() => {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
return `${protocol}//${window.location.host}/api/sessions/live`;
|
||||
}, []);
|
||||
|
||||
const connect = useCallback(() => {
|
||||
if (!isAuthenticated || !token) return;
|
||||
|
||||
const ws = new WebSocket(getWsUrl());
|
||||
wsRef.current = ws;
|
||||
|
||||
ws.onopen = () => {
|
||||
ws.send(JSON.stringify({ type: 'auth', token }));
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const msg = JSON.parse(event.data);
|
||||
|
||||
if (msg.type === 'auth_success') {
|
||||
ws.send(JSON.stringify({ type: 'page_focus', page: location.pathname }));
|
||||
|
||||
pingRef.current = setInterval(() => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: 'ping' }));
|
||||
}
|
||||
}, PING_INTERVAL);
|
||||
}
|
||||
|
||||
if (msg.type === 'presence_update') {
|
||||
const currentPage = location.pathname;
|
||||
const onSamePage = msg.admins
|
||||
.filter(a => a.page === currentPage)
|
||||
.map(a => a.name === adminName ? 'me' : a.name);
|
||||
setViewers(onSamePage);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
clearInterval(pingRef.current);
|
||||
reconnectRef.current = setTimeout(connect, WS_RECONNECT_DELAY);
|
||||
};
|
||||
|
||||
ws.onerror = () => {
|
||||
ws.close();
|
||||
};
|
||||
}, [isAuthenticated, token, adminName, location.pathname, getWsUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
connect();
|
||||
|
||||
return () => {
|
||||
clearTimeout(reconnectRef.current);
|
||||
clearInterval(pingRef.current);
|
||||
if (wsRef.current) {
|
||||
wsRef.current.onclose = null;
|
||||
wsRef.current.close();
|
||||
}
|
||||
};
|
||||
}, [connect]);
|
||||
|
||||
useEffect(() => {
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||
wsRef.current.send(JSON.stringify({ type: 'page_focus', page: location.pathname }));
|
||||
}
|
||||
}, [location.pathname]);
|
||||
|
||||
return { viewers };
|
||||
}
|
||||
Reference in New Issue
Block a user