I am developing a python-based flask app in which several users with different roles can connect to. When somebody logs in and clicks a 'ready' button he gets redirected to 'waiting' page, where I want to update info according to the number of users that get ready. When everyone is ready, all users should be simmultaneously get redirected to a countdown page to start a game. Even though I have been looking on stackoverflow I have not found an answer that helps me to solve it.
Currently, everything works fine until 'waiting' page, where info doesn't get updated and users don't get redirected to 'countdown' even though everyone is ready.
This is my code so far for app.py:
from flask import Flask, render_template, request, redirect, url_for, session
from flask_socketio import SocketIO, emit
from loguru import logger
from functools import wraps
import re
def login_required(role):
def wrapper(fn):
@wraps(fn)
def decorated_view(*args, **kwargs):
c1 = 'username' not in session
c2 = USERS_STATUS[session["username"]] != session["status"]
if c1 or c2:
return redirect(url_for('login'))
return fn(*args, **kwargs)
return decorated_view
return wrapper
def info():
print(session)
print(USERS_STATUS)
APP = Flask(__name__,
static_folder="./static/",
template_folder="./templates/")
APP.config["SESSION_PERMANENT"] = False
APP.secret_key = "admin"
SOCKETIO = SocketIO(APP)
USERS = {"sound":"pass"}
n_inst, n_publ = 1, 1
for t in ["inst","publ"]:
for ni in range(globals()[f"n_{t}"]):
USERS[f"inst{ni+1}"] = "pass"
USERS_STATUS = {k:"UNLOGGED" for k in USERS.keys()}
@APP.route("/", methods=["GET", "POST"])
def root():
"""Index page"""
return render_template("index.html")
@APP.route("/login", methods=["GET", "POST"])
def login():
info()
"""Login page"""
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if username in USERS and USERS[username] == password:
session['username'] = username
session['status'] = "LOGGED"
USERS_STATUS[username] = "LOGGED"
logger.info(f"User {username} logged in.")
return redirect(url_for("dashboard"))
else:
error = 'Invalid username or password. Please try again.'
logger.warning(f"Failed login attempt for user {username}.")
return render_template('login.html', error=error)
return render_template('login.html')
@APP.route("/logout")
def logout():
"""Logout route"""
USERS_STATUS[session["username"]] = "UNLOGGED"
return redirect(url_for('login'))
@APP.route("/dashboard")
@login_required(role='ANY')
def dashboard():
info()
"""Dashboard page"""
username = session.get("username")
return render_template("dashboard.html",username=username)
@APP.route("/ready",methods=["GET","POST"])
def ready():
info()
"""Set user status to ready"""
username = session["username"]
USERS_STATUS[username] = "READY"
session["status"] = "READY"
SOCKETIO.emit('update status','user is ready')
return redirect(url_for('waiting'))
@APP.route("/waiting",methods=["GET","POST"])
@login_required(role='ANY')
def waiting():
info()
"""Starts waiting mode"""
return render_template("waiting.html")
@APP.route("/countdown")
@login_required(role='ANY')
def countdown():
info()
"""Countdown page"""
return render_template("countdown.html")
@APP.route("/game")
@login_required(role='ANY')
def game():
info()
"""Game page"""
return render_template("game.html")
@SOCKETIO.on("update_status")
def handle_message():
c = all(s == "READY" for s in USERS_STATUS.values())
if c:
new_status = "Everybody ready. Let\'s start playing!"
else:
total_users = len(USERS)
ready_users = len([k for k,v in USERS_STATUS.items() if v=="READY"])
new_status = f"{ready_users}/{total_users} users ready! Waiting for the remaining..."
emit('update_status',
{'status':new_status},
broadcast=True)
SOCKETIO.sleep(2)
emit('redirect',
{'url':url_for('countdown')},
broadcast=True)
@SOCKETIO.on("disconnect")
def disconnect_user():
session.pop("username", None)
session.pop("status", None)
if __name__ == "__main__":
SOCKETIO.run(app=APP, host="0.0.0.0", port=1000)
Here it is the code for dashboard.html
<!DOCTYPE html>
<html>
<head>
<title>User dashboard</title>
</head>
<body>
<h2>Welcome, user {{ username }}!</h2>
<div align="center">
<a href="{{ url_for('ready') }}"><button id="readyButton">Ready</button></a>
</div>
<p><a href="{{ url_for('logout') }}">Logout</a></p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.3.2/socket.io.min.js"></script>
<script>
document.getElementById('readyButton').addEventListener('click', function() {
var socket = io();
socket.emit('update_status');
});
</script>
</body>
</html>
Here it is the code for waiting.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content=""width=device-width, initial-scale="1.0">
<title>Waiting page</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.3.2/socket.io.min.js"></script>
<script>
var socket = io();
socket.on('update_status', function(data) {
document.getElementById('status').innerText = data.status;
});
</script>
<h2>Waiting to start the game</h2>
<div id="status">Waiting for remaining users to get ready...</div>
</body>
</html>
Here it is the code for countdown.html
<!DOCTYPE html>
<html>
<head>
<title>Countdown</title>
<script>
function countdown() {
var count = 10; // Initial countdown value
var countdownElement = document.getElementById('countdown');
var countdownInterval = setInterval(function() {
countdownElement.innerHTML = count; // Display current countdown value
if (count <= 0) {
clearInterval(countdownInterval); // Stop the countdown
// Redirect the user to another page
window.location.href = "{{ url_for('game') }}";
}
count--; // Decrement the countdown value
}, 1000); // Update countdown every 1 second
}
// Start the countdown when the page loads
window.onload = function() {
countdown();
};
</script>
</head>
<body>
<h2>Redirecting in <span id="countdown">10</span> seconds...</h2>
</body>
</html>
I think I'm close to the solution, but I cannot see it... Thanks in advance!
I tried different usages of socketio.emit both from client and from server but nothing works, resulting in several different errors