Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
267 changes: 185 additions & 82 deletions frontend/components/game/session/useGameSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ export function useGameSession({
const socketInstanceId = activeSocketInstanceRef.current + 1;
activeSocketInstanceRef.current = socketInstanceId;

let reconnectTimeoutId: number | null = null;

const isCurrentSocketInstance = (socket?: WebSocket | null) =>
Boolean(
socket &&
Expand Down Expand Up @@ -267,6 +269,10 @@ export function useGameSession({

const disconnectSocket = () => {
clearPing();
if (reconnectTimeoutId !== null) {
window.clearTimeout(reconnectTimeoutId);
reconnectTimeoutId = null;
}
fpsDisplayTextRef.current = "FPS: 0";
clearUseItemQueuesRef.current();
clearTargetingModeRef.current();
Expand All @@ -280,6 +286,10 @@ export function useGameSession({
const socket = websocketRef.current;
websocketRef.current = null;
if (socket) {
socket.onopen = null;
socket.onmessage = null;
socket.onerror = null;
socket.onclose = null;
socket.close();
}
};
Expand All @@ -304,46 +314,9 @@ export function useGameSession({
disconnectSocket();
activeSessionKeyRef.current = connection.sessionKey;

const socket = new WebSocket(connection.wsUrl);
socket.binaryType = "arraybuffer";
websocketRef.current = socket;

emitStatusRef.current({ connected: false, connecting: true });

socket.onopen = () => {
if (
activeSessionKeyRef.current !== connection.sessionKey ||
!isCurrentSocketInstance(socket)
) {
socket.close();
return;
}

const sendPing = () => {
if (socket.readyState !== WebSocket.OPEN) {
return;
}

const token = nextPingTokenRef.current++;
pendingPingRef.current = {
token,
sentAt: performance.now(),
};
socket.send(createPingPacket(token));
};

socket.send(
createConnectCharacterPacket({
ticket: connection.ticket,
typeGame: connection.typeGame,
idChar: connection.idChar,
}),
);

flushPendingChatRequestRef.current();
sendPing();
pingIntervalRef.current = window.setInterval(sendPing, 10000);
};
const MAX_RECONNECT_ATTEMPTS = 6;
const BASE_RECONNECT_DELAY_MS = 1000;
const MAX_RECONNECT_DELAY_MS = 30000;

const processIncomingPacketQueue = async () => {
if (isProcessingIncomingPacketsRef.current) {
Expand Down Expand Up @@ -449,65 +422,194 @@ export function useGameSession({
return true;
};

socket.onmessage = (event) => {
if (!isCurrentSocketInstance(socket)) {
const scheduleReconnect = (attempt: number) => {
if (reconnectTimeoutId !== null) {
return;
}
if (
activeSessionKeyRef.current !== connection.sessionKey ||
!isClientReadyForConnection
) {
return;
}

const rawMessage = event.data as
| Blob
| ArrayBuffer
| ArrayBufferView;

void tryHandlePongMessage(rawMessage)
.then((wasPong) => {
if (wasPong) {
return;
}

incomingPacketQueueRef.current.push(rawMessage);
void processIncomingPacketQueue();
})
.catch(() => {
incomingPacketQueueRef.current.push(rawMessage);
void processIncomingPacketQueue();
if (attempt >= MAX_RECONNECT_ATTEMPTS) {
emitStatusRef.current({
connected: false,
connecting: false,
error: "No se pudo reconectar. Por favor, recarga la página.",
});
};

socket.onerror = () => {
if (!isCurrentSocketInstance(socket)) {
return;
}

setIsSceneReadyRef.current(false);
const delay = Math.min(
BASE_RECONNECT_DELAY_MS * Math.pow(2, attempt),
MAX_RECONNECT_DELAY_MS,
);
const remainingSec = Math.ceil(delay / 1000);

emitStatusRef.current({
connected: false,
connecting: false,
error: "No se pudo establecer la conexion websocket.",
connecting: true,
error: `Reconectando… (intento ${attempt + 1}/${MAX_RECONNECT_ATTEMPTS}, en ${remainingSec}s)`,
});

reconnectTimeoutId = window.setTimeout(() => {
reconnectTimeoutId = null;
if (
activeSessionKeyRef.current !== connection.sessionKey ||
!isClientReadyForConnection
) {
return;
}
connectSocket(attempt + 1);
Comment thread
gitar-bot[bot] marked this conversation as resolved.
}, delay);
};

socket.onclose = () => {
const connectSocket = (reconnectAttempt = 0) => {
clearPing();
if (
activeSessionKeyRef.current === connection.sessionKey &&
isCurrentSocketInstance(socket)
) {
setIsSceneReadyRef.current(false);
const previousError = latestStatusRef.current.error;
emitStatusRef.current({
connected: false,
connecting: false,
error: previousError || "Conexion cerrada.",
});
if (reconnectTimeoutId !== null) {
window.clearTimeout(reconnectTimeoutId);
reconnectTimeoutId = null;
}

const previousSocket = websocketRef.current;
if (previousSocket) {
previousSocket.onopen = null;
previousSocket.onmessage = null;
previousSocket.onerror = null;
previousSocket.onclose = null;
previousSocket.close();
}

const ws = new WebSocket(connection.wsUrl);
ws.binaryType = "arraybuffer";
websocketRef.current = ws;

if (reconnectAttempt === 0) {
emitStatusRef.current({ connected: false, connecting: true });
}

ws.onopen = () => {
if (
activeSessionKeyRef.current !== connection.sessionKey ||
!isCurrentSocketInstance(ws)
) {
ws.close();
return;
}

if (reconnectTimeoutId !== null) {
window.clearTimeout(reconnectTimeoutId);
reconnectTimeoutId = null;
}

// Reset reconnect counter on successful connection so the next
// disconnect starts back at attempt 0 instead of where we left off.
ws.onerror = () => {
if (!isCurrentSocketInstance(ws)) {
return;
}
setIsSceneReadyRef.current(false);
scheduleReconnect(0);
};

ws.onclose = () => {
clearPing();
if (
activeSessionKeyRef.current !== connection.sessionKey ||
!isCurrentSocketInstance(ws)
) {
return;
}
setIsSceneReadyRef.current(false);
scheduleReconnect(0);
};

emitStatusRef.current({ connected: true, connecting: false });

const sendPing = () => {
if (ws.readyState !== WebSocket.OPEN) {
return;
}

const token = nextPingTokenRef.current++;
pendingPingRef.current = {
token,
sentAt: performance.now(),
};
ws.send(createPingPacket(token));
};

ws.send(
createConnectCharacterPacket({
ticket: connection.ticket,
typeGame: connection.typeGame,
idChar: connection.idChar,
}),
);

flushPendingChatRequestRef.current();
sendPing();
pingIntervalRef.current = window.setInterval(sendPing, 10000);
};

ws.onmessage = (event) => {
if (!isCurrentSocketInstance(ws)) {
return;
}

const rawMessage = event.data as
| Blob
| ArrayBuffer
| ArrayBufferView;

void tryHandlePongMessage(rawMessage)
.then((wasPong) => {
if (wasPong) {
return;
}

incomingPacketQueueRef.current.push(rawMessage);
void processIncomingPacketQueue();
})
.catch(() => {
incomingPacketQueueRef.current.push(rawMessage);
void processIncomingPacketQueue();
});
};

ws.onerror = () => {
if (!isCurrentSocketInstance(ws)) {
return;
}

setIsSceneReadyRef.current(false);
scheduleReconnect(reconnectAttempt);
};

ws.onclose = () => {
clearPing();
if (
activeSessionKeyRef.current !== connection.sessionKey ||
!isCurrentSocketInstance(ws)
) {
return;
}

setIsSceneReadyRef.current(false);
scheduleReconnect(reconnectAttempt);
};
};

connectSocket(0);

return () => {
if (
activeSessionKeyRef.current === connection.sessionKey &&
isCurrentSocketInstance(socket)
) {
if (reconnectTimeoutId !== null) {
window.clearTimeout(reconnectTimeoutId);
reconnectTimeoutId = null;
}
if (activeSessionKeyRef.current === connection.sessionKey) {
activeSessionKeyRef.current = null;
}
clearAllDialogMessagesRef.current();
Expand All @@ -525,6 +627,7 @@ export function useGameSession({
characterStatsChunkTotalRef.current = 0;
disconnectSocket();
};

}, [
connection,
isClientReadyForConnection,
Expand Down
Loading