realtime-chat-room / src /hooks /useChatSocket.ts
Trae Assistant
Sync gomoku and add mini games
7d1023a
Raw
History Blame Contribute Delete
5.07 kB
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type {
ClientGameMessage,
ChatMessage,
ChatUser,
GomokuState,
ServerMessage,
} from '../../shared/chat';
type ConnectionState = 'idle' | 'connecting' | 'connected' | 'disconnected';
function getSocketUrl() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const isViteDev = window.location.port === '5173';
const host = isViteDev ? `${window.location.hostname}:3001` : window.location.host;
return `${protocol}//${host}/ws`;
}
export function useChatSocket(roomId: string | null, nickname: string | null) {
const socketRef = useRef<WebSocket | null>(null);
const reconnectTimerRef = useRef<number | null>(null);
const [connectionState, setConnectionState] = useState<ConnectionState>('idle');
const [userId, setUserId] = useState<string | null>(null);
const [users, setUsers] = useState<ChatUser[]>([]);
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [gomoku, setGomoku] = useState<GomokuState | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!roomId || !nickname) {
if (reconnectTimerRef.current) {
window.clearTimeout(reconnectTimerRef.current);
reconnectTimerRef.current = null;
}
setConnectionState('idle');
setUserId(null);
setUsers([]);
setMessages([]);
setGomoku(null);
return;
}
let disposed = false;
let reconnectAttempt = 0;
function scheduleReconnect() {
if (disposed) {
return;
}
const delay = Math.min(1000 * 2 ** reconnectAttempt, 8000);
reconnectAttempt += 1;
setConnectionState('connecting');
setError(`连接已断开,正在自动重连...`);
reconnectTimerRef.current = window.setTimeout(() => {
reconnectTimerRef.current = null;
connect();
}, delay);
}
function connect() {
const socket = new WebSocket(getSocketUrl());
socketRef.current = socket;
setConnectionState('connecting');
socket.addEventListener('open', () => {
socket.send(JSON.stringify({
type: 'join',
roomId,
nickname,
authProvider: 'nickname',
}));
});
socket.addEventListener('message', (event) => {
const payload = JSON.parse(event.data) as ServerMessage;
if (payload.type === 'welcome') {
reconnectAttempt = 0;
setConnectionState('connected');
setError(null);
setUserId(payload.userId);
setUsers(payload.users);
setMessages(payload.recentMessages);
setGomoku(payload.gomoku);
return;
}
if (payload.type === 'users') {
setUsers(payload.users);
return;
}
if (payload.type === 'chat' || payload.type === 'system') {
setMessages((current) => [...current, payload.message]);
return;
}
if (payload.type === 'error') {
setError(payload.message);
return;
}
if (payload.type === 'game' && payload.game === 'gomoku') {
setGomoku(payload.gomoku);
}
});
socket.addEventListener('close', () => {
if (socketRef.current === socket) {
socketRef.current = null;
}
if (!disposed) {
scheduleReconnect();
}
});
socket.addEventListener('error', () => {
if (!disposed) {
setError('连接聊天室失败,正在自动重试');
setConnectionState('disconnected');
}
});
}
connect();
return () => {
disposed = true;
if (reconnectTimerRef.current) {
window.clearTimeout(reconnectTimerRef.current);
reconnectTimerRef.current = null;
}
const socket = socketRef.current;
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ type: 'leave' }));
}
socket?.close();
if (socketRef.current === socket) {
socketRef.current = null;
}
};
}, [roomId, nickname]);
const sendMessage = useCallback((text: string) => {
const socket = socketRef.current;
const trimmedText = text.trim();
if (!trimmedText || !socket || socket.readyState !== WebSocket.OPEN) {
return false;
}
socket.send(JSON.stringify({
type: 'chat',
text: trimmedText,
}));
return true;
}, []);
const sendGameAction = useCallback((message: ClientGameMessage) => {
const socket = socketRef.current;
if (!socket || socket.readyState !== WebSocket.OPEN) {
return false;
}
socket.send(JSON.stringify(message));
return true;
}, []);
const value = useMemo(() => ({
connectionState,
error,
gomoku,
messages,
sendGameAction,
sendMessage,
userId,
users,
}), [connectionState, error, gomoku, messages, sendGameAction, sendMessage, userId, users]);
return value;
}