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