Recover presence connections after temporary database failures
This commit is contained in:
+11
-3
@@ -1,21 +1,29 @@
|
||||
import { io, type Socket } from 'socket.io-client';
|
||||
import type { GameSessionAuth } from '../shared/p2p-types.js';
|
||||
import { PRESENCE_RETRY_MS } from '../../constants/presence.js';
|
||||
|
||||
export function createPresence(counter: HTMLElement) {
|
||||
let socket: Socket | undefined, sessionId = '';
|
||||
let retry: ReturnType<typeof setTimeout> | undefined;
|
||||
const unknown = () => { counter.textContent = '접속 확인 중'; };
|
||||
window.addEventListener('pagehide', () => { socket?.disconnect(); unknown(); });
|
||||
window.addEventListener('pagehide', () => { clearTimeout(retry); socket?.disconnect(); unknown(); });
|
||||
window.addEventListener('pageshow', () => { socket?.connect(); });
|
||||
return {
|
||||
connect(auth: GameSessionAuth) {
|
||||
if (socket && sessionId === auth.gameSessionId) { socket.connect(); return; }
|
||||
socket?.removeAllListeners(); socket?.disconnect(); unknown();
|
||||
clearTimeout(retry); socket?.removeAllListeners(); socket?.disconnect(); unknown();
|
||||
sessionId = auth.gameSessionId;
|
||||
socket = io('/presence', { auth: { gameSessionId: auth.gameSessionId, token: auth.token }, forceNew: true, autoConnect: false });
|
||||
socket.on('presence:count', (count: unknown) => {
|
||||
if (Number.isSafeInteger(count) && (count as number) >= 0) counter.textContent = `접속 ${count}명`;
|
||||
});
|
||||
socket.on('disconnect', unknown); socket.on('connect_error', unknown);
|
||||
socket.on('connect', () => clearTimeout(retry));
|
||||
socket.on('disconnect', unknown);
|
||||
socket.on('connect_error', error => {
|
||||
unknown(); clearTimeout(retry);
|
||||
// Middleware errors do not trigger Socket.IO's transport reconnection loop.
|
||||
if (error.message !== 'INVALID_SESSION') retry = setTimeout(() => socket?.connect(), PRESENCE_RETRY_MS);
|
||||
});
|
||||
socket.connect();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Server } from 'socket.io';
|
||||
import type { GameSessionStore } from './game-session-store.js';
|
||||
import { ApiError, type GameSessionStore } from './game-session-store.js';
|
||||
import { PRESENCE_AUTH_REFRESH_MS } from '../../constants/presence.js';
|
||||
|
||||
// The application is deployed as one process, as is the ranked match service.
|
||||
@@ -15,7 +15,7 @@ export function registerPresence(io: Server, sessions: GameSessionStore) {
|
||||
void sessions.get({ gameSessionId, token }).then(session => {
|
||||
socket.data.playerId = session.playerId ?? session.gameSessionId;
|
||||
next();
|
||||
}, () => next(new Error('INVALID_SESSION')));
|
||||
}, error => next(new Error(error instanceof ApiError && error.statusCode === 401 ? 'INVALID_SESSION' : 'PRESENCE_UNAVAILABLE')));
|
||||
});
|
||||
namespace.on('connection', socket => {
|
||||
const before = count; publish();
|
||||
@@ -25,7 +25,10 @@ export function registerPresence(io: Server, sessions: GameSessionStore) {
|
||||
if (checking) return;
|
||||
checking = true;
|
||||
const { gameSessionId, token } = socket.handshake.auth;
|
||||
void sessions.get({ gameSessionId, token }).catch(() => socket.disconnect(true)).finally(() => { checking = false; });
|
||||
void sessions.get({ gameSessionId, token }).catch(error => {
|
||||
if (error instanceof ApiError && error.statusCode === 401) socket.disconnect(true);
|
||||
else socket.conn.close(); // Transport closure permits Socket.IO to reconnect after a transient outage.
|
||||
}).finally(() => { checking = false; });
|
||||
}, PRESENCE_AUTH_REFRESH_MS);
|
||||
timer.unref();
|
||||
socket.on('disconnect', () => { clearInterval(timer); publish(); });
|
||||
|
||||
Reference in New Issue
Block a user