myheats

Live heats, scoring and leaderboard for sport events
git clone https://git.in0rdr.ch/myheats.git
Log | Files | Refs | Pull requests | README | LICENSE

commit 3872d32efdac5e2ff97a8f6867ec86f4056bae0a
parent 78dd7a5bc4e8cdc7025dfb93eedfbd02358e866e
Author: Andreas Gruhler <andreas.gruhler@adfinis.com>
Date:   Wed, 25 Sep 2024 23:07:38 +0200

feat: real-time scores with ws

Diffstat:
Msrc/api/db.cjs | 23++++++++++++++---------
Msrc/api/server.cjs | 22++--------------------
Msrc/frontend/Leaderboard.jsx | 33++++++++++++++-------------------
3 files changed, 30 insertions(+), 48 deletions(-)

diff --git a/src/api/db.cjs b/src/api/db.cjs @@ -17,7 +17,7 @@ require('dotenv').config({ // https://github.com/porsager/postgres?tab=readme-ov-file#environmental-variables const sql = pg({ publications: [ - 'scores', + 'myheats_realtime', ] }) @@ -392,22 +392,27 @@ async function setScore(heat, athlete, judge, score) { } } -async function getScores() { +async function watchScores(clients) { try { const { unsubscribe } = await sql.subscribe( '*:scores', (row, { command, relation, key, old }) => { - // Callback function for each row change - // tell about new event row over eg. websockets or do something else - console.log("!! SCORES CHANGED:", row) - return row + // distributed score updates to all connected clients + for(let c of clients) { + c.send(JSON.stringify({ + "message": "Received new scores", + "data": row, + })) + } }, () => { - // Callback on initial connect and potential reconnects + // callback on initial connect and potential reconnects + console.log("Watching scores") } ) + return unsubscribe } catch (error) { - console.log('Error occurred in getScores:', error); + console.log('Error occurred in watchScores:', error); throw error } } @@ -429,7 +434,7 @@ module.exports = { scoreSummaryForHeatAndAthlete, getScore, setScore, - getScores, + watchScores, addAthleteToHeat, addAthlete, removeAthlete, diff --git a/src/api/server.cjs b/src/api/server.cjs @@ -685,26 +685,8 @@ wss1.on('connection', function connection(sock) { msg = JSON.parse(m) console.log(' Uncle roger hears: %s', msg); - if (msg.method === 'getStartListForHeats') { - const startlist = await db.getStartlistForHeats(msg.data.heatIds) - sock.send(JSON.stringify({ - "data": startlist, - })); - } else if (msg.method === 'getScoreForHeatAndAthlete') { - const score = await db.getScoreForHeatAndAthlete(msg.data.heatId, msg.data.athleteId) - sock.send(JSON.stringify({ - "data": score, - })); - } else if (msg.method === 'getHeats') { - const heats = await db.getHeats() - sock.send(JSON.stringify({ - "data": heats, - })); - } else if (msg.method === 'getScores') { - const scores = await db.getScores() - sock.send(JSON.stringify({ - "data": scores, - })); + if (msg.method === 'watchScores') { + db.watchScores(clients) } } catch (error) { console.log('x Error: %s', error.message); diff --git a/src/frontend/Leaderboard.jsx b/src/frontend/Leaderboard.jsx @@ -307,25 +307,20 @@ function Leaderboard({session}) { })(); }, [heatSelection]); -// useEffect(() => { -// (async() => { -// // subscribe to scoring from judges and -// const scores = await socket.send(JSON.stringify({ -// method: "getScores", -// })) -// console.log("DEBUG: Scores", scores) -// // todo: reload only required scores -// const scoreSummary = await getScoreSummary(heatSelection.map(h => h.value)) -// setLeaderboard(scoreSummary) -// setLoading(false) -// -// // remove subscription -// return function cleanup() { -// // todo: remove subscription on API server -// //supabase.removeChannel(channel) -// } -// })(); -// }, [heatSelection]); + useEffect(() => { + (async() => { + // subscribe to scoring from judges + socket.send(JSON.stringify({ + method: "watchScores", + })) + socket.onmessage = async function(event) { + // todo: reload only required scores + const scoreSummary = await getScoreSummary(heatSelection.map(h => h.value)) + setLeaderboard(scoreSummary) + } + setLoading(false) + })(); + }, [heatSelection]); return ( <div>