Fix: voting flow bugs na code review
- acceptingMatch/finalizingMatches als const gedeclareerd (ipv implicit global) - 30-min voting timeout met cleanup interval - _created timestamp bij acceptingMatch init - finalizingMatches semaphore tegen race conditions - try/catch + logError in finalizeMatch() - Speler check in vote callbacks (m.players.includes) - finalizingMatches.delete bij cleanup na finalize
This commit is contained in:
@@ -155,6 +155,21 @@ function setUserState(chatId, data) {
|
|||||||
userState.set(chatId, data);
|
userState.set(chatId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Match voting state (per match pending ranked/friendly vote) ───
|
||||||
|
const acceptingMatch = {};
|
||||||
|
const finalizingMatches = new Set(); // semaphore tegen race condition
|
||||||
|
|
||||||
|
// Voting timeout: clean up matches older than 30 minutes
|
||||||
|
setInterval(() => {
|
||||||
|
const now = Date.now();
|
||||||
|
for (const [matchId, m] of Object.entries(acceptingMatch)) {
|
||||||
|
if (m._created && now - m._created > 30 * 60 * 1000) {
|
||||||
|
delete acceptingMatch[matchId];
|
||||||
|
log('Voting timeout: match ' + matchId + ' cleaned up');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 60 * 1000); // check elke minuut
|
||||||
|
|
||||||
// ─── Safe sendMessage wrapper (met rate limiting) ───
|
// ─── Safe sendMessage wrapper (met rate limiting) ───
|
||||||
const sendMsg = throttledSend;
|
const sendMsg = throttledSend;
|
||||||
|
|
||||||
@@ -1549,7 +1564,8 @@ bot.on('callback_query', async (query) => {
|
|||||||
location: matchInfo.location,
|
location: matchInfo.location,
|
||||||
proposed_teams: matchInfo.proposed_teams,
|
proposed_teams: matchInfo.proposed_teams,
|
||||||
ranked: new Set(),
|
ranked: new Set(),
|
||||||
friendly: new Set()
|
friendly: new Set(),
|
||||||
|
_created: Date.now()
|
||||||
};
|
};
|
||||||
|
|
||||||
bot.answerCallbackQuery(query.id, { text: 'Aanwezig!' }).catch(() => {});
|
bot.answerCallbackQuery(query.id, { text: 'Aanwezig!' }).catch(() => {});
|
||||||
@@ -1593,6 +1609,12 @@ bot.on('callback_query', async (query) => {
|
|||||||
const player = players.find(p => p.telegram_id == chatId);
|
const player = players.find(p => p.telegram_id == chatId);
|
||||||
if (!player) return;
|
if (!player) return;
|
||||||
|
|
||||||
|
// Security: only match players can vote
|
||||||
|
if (!m.players.includes(player.id)) {
|
||||||
|
bot.answerCallbackQuery(query.id, { text: 'Je zit niet in deze match' }).catch(() => {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (m.ranked.has(player.id) || m.friendly.has(player.id)) {
|
if (m.ranked.has(player.id) || m.friendly.has(player.id)) {
|
||||||
bot.answerCallbackQuery(query.id, { text: 'Je hebt al gestemd!' }).catch(() => {});
|
bot.answerCallbackQuery(query.id, { text: 'Je hebt al gestemd!' }).catch(() => {});
|
||||||
return;
|
return;
|
||||||
@@ -1637,11 +1659,20 @@ bot.on('callback_query', async (query) => {
|
|||||||
|
|
||||||
// ─── Finaliseer match ───
|
// ─── Finaliseer match ───
|
||||||
async function finalizeMatch(matchId, finalType) {
|
async function finalizeMatch(matchId, finalType) {
|
||||||
|
if (finalizingMatches.has(matchId)) return; // prevent race
|
||||||
|
finalizingMatches.add(matchId);
|
||||||
|
|
||||||
const players = await api('/players');
|
const players = await api('/players');
|
||||||
const m = acceptingMatch[matchId];
|
const m = acceptingMatch[matchId];
|
||||||
if (!m) return;
|
if (!m) { finalizingMatches.delete(matchId); return; }
|
||||||
|
|
||||||
try { await api('/matches/' + matchId + '/type', 'POST', { match_type: finalType }); } catch(e) {}
|
try {
|
||||||
|
await api('/matches/' + matchId + '/type', 'POST', { match_type: finalType });
|
||||||
|
} catch(e) {
|
||||||
|
logError('finalizeMatch: failed to set match type:', e.message);
|
||||||
|
finalizingMatches.delete(matchId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const getName = id => { const p = players.find(x => x.id == id); return p ? p.name : '?'; };
|
const getName = id => { const p = players.find(x => x.id == id); return p ? p.name : '?'; };
|
||||||
const t1names = (m.proposed_teams?.team1 || []).map(getName).join(' + ');
|
const t1names = (m.proposed_teams?.team1 || []).map(getName).join(' + ');
|
||||||
@@ -1659,6 +1690,7 @@ bot.on('callback_query', async (query) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete acceptingMatch[matchId];
|
delete acceptingMatch[matchId];
|
||||||
|
finalizingMatches.delete(matchId);
|
||||||
log('Match ' + matchId + ' finalized as ' + finalType);
|
log('Match ' + matchId + ' finalized as ' + finalType);
|
||||||
}
|
}
|
||||||
} else if (data.startsWith('team_swap_')) {
|
} else if (data.startsWith('team_swap_')) {
|
||||||
|
|||||||
Reference in New Issue
Block a user