Phase 1 (2/3) : route publique /jouer/<slug> + identité visiteur
Ajoute la vraie route hébergée multi-joueurs qui manquait totalement
(voir le constat d'exploration : /game/<slug>/play est réservé au
créateur connecté, "Publier" ne génère qu'un exécutable mono-joueur) —
un visiteur anonyme peut maintenant jouer un jeu explicitement publié en
ligne, avec sa propre partie (variables/objets per_player posés dans le
commit précédent).
core/player_identity.py : identité visiteur via un cookie NON SIGNÉ
(forge_player_id, secrets.token_urlsafe(16), 1 an) — une simple clé de
partition, jamais un jeton d'autorisation.
routes/public_play/ : 4 routes, miroirs des routes existantes de l'aperçu
créateur mais threadées avec le vrai player_id du cookie au lieu du
sentinel PLAYER_SHARED :
- GET /jouer/<slug> (game_play_public.py)
- GET /jouer/<slug>/runtime-payload
- POST /jouer/<slug>/flow/nodes/<id>/run-data
- POST /jouer/<slug>/flow/nodes/<id>/run-variable
Chacune vérifie elle-même db.is_public_played(slug) (404 sinon) — un jeu
n'est exposé publiquement que si le créateur l'a explicitement basculé
"Publier en ligne" (nouveau db/games/is_public_played.py, réutilise la
table générique _meta, comme game_meta.py pour 'name').
core/auth_guard.py : les 4 endpoints publics ajoutés à _PUBLIC_ENDPOINTS
— la garde générique de connexion les laisse passer sans session, mais
chaque vue vérifie quand même is_public_played elle-même (défense en
profondeur, pas seulement une liste d'exceptions). CSRF (core/csrf_guard.py)
n'a besoin d'AUCUN changement : le jeton est déjà lié à la session Flask,
qui existe pour n'importe quel visiteur (connecté ou non).
templates/play.html : FORGE_PLAY_URLS (posé en Phase -1) ne construit
plus ses URLs via des noms de endpoint fixes (url_for('runtime_payload',
...)) mais reçoit des URLs déjà résolues par la route elle-même
(runtime_payload_url/flow_node_run_data_url/flow_node_run_variable_url)
— nécessaire puisque ce même template sert maintenant DEUX familles de
routes (aperçu créateur ET partie publique), chacune avec ses propres
noms de endpoint. routes/play/game_play.py (aperçu créateur, INCHANGÉ
comportement) et game_play_public.py passent chacun ses propres URLs.
templates/base.html : bascule "🌐 Publier en ligne" dans la barre de
navigation du jeu, à côté de "📦 Publier" (export .zip) — deux
fonctionnalités distinctes. db/games/game_meta.py expose maintenant
is_public_played, disponible partout où `game` est dans le contexte.
Vérifié : 237 tests passent (5 nouveaux dans test_public_play.py, dont un
bout-en-bout via HTTP avec deux VRAIS clients de test anonymes — deux
cookies forge_player_id différents — qui obtiennent des valeurs de
variable indépendantes, et un qui verrouille que l'aperçu créateur reste
inchangé). Syntaxe JS validée sur les deux variantes de play.html rendu
(aperçu créateur et partie publique).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
236d6b4b46
commit
3c39f1a249
+15
-1
@@ -49,8 +49,22 @@
|
||||
jouable dans un nouvel onglet), pas une page de gestion de plus. -->
|
||||
<nav class="gameNavBar" aria-label="Navigation du jeu">
|
||||
<a href="{{ url_for('game_dashboard', slug=game.slug) }}" class="gameNavLink {{ 'active' if request.path == '/game/' ~ game.slug else '' }}" title="Écrants, objets, templates, variables">📊 Tableau de bord</a>
|
||||
<a href="{{ url_for('game_play', slug=game.slug) }}" target="_blank" class="gameNavLink" title="Aperçu jouable du jeu">▶️ Jouer</a>
|
||||
<a href="{{ url_for('game_play', slug=game.slug) }}" target="_blank" class="gameNavLink" title="Aperçu jouable du jeu (une seule partie partagée, pour la conception)">▶️ Jouer</a>
|
||||
<button type="button" class="gameNavLink" onclick="window.forgeOpenPublishModal && window.forgeOpenPublishModal()" title="Exporte le jeu en exécutable Windows autonome (zip)">📦 Publier</button>
|
||||
<!-- "Publier en ligne" (état par joueur, Phase 1) : distinct de
|
||||
"Publier" ci-dessus (export .zip mono-joueur) — active/désactive la
|
||||
route publique /jouer/<slug>, jouable par n'importe quel visiteur
|
||||
anonyme avec SA PROPRE partie (voir routes/public_play/,
|
||||
db/games/is_public_played.py). -->
|
||||
<form method="post" action="{{ url_for('game_toggle_public_played', slug=game.slug) }}" style="display:inline;">
|
||||
<input type="hidden" name="value" value="{{ '0' if game.is_public_played else '1' }}">
|
||||
<button type="submit" class="gameNavLink" title="{{ 'Rendre le jeu privé (désactive /jouer)' if game.is_public_played else 'Rendre le jeu jouable par tous, chacun avec sa propre partie' }}">
|
||||
{{ '🌐 En ligne ✓' if game.is_public_played else '🌐 Publier en ligne' }}
|
||||
</button>
|
||||
</form>
|
||||
{% if game.is_public_played %}
|
||||
<a href="{{ url_for('game_play_public', slug=game.slug) }}" target="_blank" class="gameNavLink" title="Ouvre la partie publique, comme un vrai visiteur">🔗 Lien public</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
+10
-4
@@ -79,13 +79,19 @@
|
||||
// le JS), qui ne peuvent pas contenir de code Jinja puisqu'ils ne
|
||||
// passent jamais par le moteur de templates. FORGE_PLAY_URLS porte donc
|
||||
// les URLs de routes dont ces fichiers ont besoin (actions.js,
|
||||
// bindings.js), construites ici une seule fois.
|
||||
// bindings.js), construites ici une seule fois — passées par la route
|
||||
// elle-même (runtime_payload_url/flow_node_run_data_url/
|
||||
// flow_node_run_variable_url) plutôt que construites ici avec des noms
|
||||
// de endpoint fixes : ce même template sert aussi bien l'aperçu
|
||||
// créateur (/game/<slug>/play, routes/play/game_play.py) que la partie
|
||||
// publique par joueur (/jouer/<slug>, routes/public_play/), deux
|
||||
// familles de routes distinctes (voir état par joueur, Phase 1).
|
||||
let gameData = {{ payload_json|safe }};
|
||||
let screensData = gameData.screens;
|
||||
var FORGE_PLAY_URLS = {
|
||||
runtimePayload: {{ url_for('runtime_payload', slug=game.slug)|tojson }},
|
||||
flowNodeRunData: {{ url_for('flow_node_run_data', slug=game.slug, node_id=999999)|tojson }},
|
||||
flowNodeRunVariable: {{ url_for('flow_node_run_variable', slug=game.slug, node_id=999999)|tojson }},
|
||||
runtimePayload: {{ runtime_payload_url|tojson }},
|
||||
flowNodeRunData: {{ flow_node_run_data_url|tojson }},
|
||||
flowNodeRunVariable: {{ flow_node_run_variable_url|tojson }},
|
||||
};
|
||||
</script>
|
||||
<script src="{{ url_for('static', filename='js/play/screens.js') }}"></script>
|
||||
|
||||
Reference in New Issue
Block a user