From e90efe1654f310feda6895cd4716111b3516dfe6 Mon Sep 17 00:00:00 2001 From: Commander AI Lab <265133236+KoalaTrapLord@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:30:55 +0000 Subject: [PATCH] fix: serve lan-client from backend at /lan on port 8080 - Mount lan-client/ as static files at /lan with html=True - Mount precon-decks/ at /precon-decks/ for direct .dck access - ai-bridge.js: use same-origin (empty URLs, auto-detect from window.location) - engine.js: simplify deck fetch to use /api/lab/precons/deck endpoint + /precon-decks/ static fallback - Eliminates cross-origin issues by serving everything from localhost:8080 Access the LAN battlefield at http://localhost:8080/lan/ --- lab_api.py | 10 ++++++++++ lan-client/ai-bridge.js | 17 ++++++++++++++--- lan-client/engine.js | 41 ++++++++++++++--------------------------- 3 files changed, 38 insertions(+), 30 deletions(-) diff --git a/lab_api.py b/lab_api.py index 35cf417..88e9e60 100644 --- a/lab_api.py +++ b/lab_api.py @@ -152,8 +152,18 @@ async def health_check(): # ── Static UI (MUST come after all @app.get routes) ────────────────────────────────────────── from pathlib import Path as _Path _legacy_ui_dir = _Path(__file__).parent / "ui" +_lan_client_dir = _Path(__file__).parent / "lan-client" +_precon_decks_dir = _Path(__file__).parent / "precon-decks" _spa_dir = _Path(__file__).parent / "frontend" / "commander-ai-lab-ui" / "dist" +# Mount LAN client at /lan (served before the root catch-all) +if _lan_client_dir.exists(): + app.mount("/lan", StaticFiles(directory=str(_lan_client_dir), html=True), name="lan-client") + +# Mount precon-decks/ so .dck files are fetchable at /precon-decks/{file}.dck +if _precon_decks_dir.exists(): + app.mount("/precon-decks", StaticFiles(directory=str(_precon_decks_dir)), name="precon-decks") + if _legacy_ui_dir.exists(): app.mount("/", StaticFiles(directory=str(_legacy_ui_dir), html=True), name="ui") elif _spa_dir.exists(): diff --git a/lan-client/ai-bridge.js b/lan-client/ai-bridge.js index 77629a2..9b889e9 100644 --- a/lan-client/ai-bridge.js +++ b/lan-client/ai-bridge.js @@ -10,9 +10,10 @@ const AI_BRIDGE_VERSION = '1.0.0'; // ============================================================ const AI_BRIDGE_CONFIG = { - // Backend URL — defaults to localhost:8080, configurable via settings - backendUrl: 'http://localhost:8080', - wsUrl: 'ws://localhost:8080', + // Backend URL — empty string = same origin (recommended when served from backend) + // Falls back to localhost:8080 if served from a different origin + backendUrl: '', + wsUrl: '', // Timeouts (ms) healthCheckTimeout: 3000, @@ -39,6 +40,16 @@ const AI_BRIDGE_CONFIG = { class BackendConnector { constructor(config = {}) { this.config = { ...AI_BRIDGE_CONFIG, ...config }; + + // Derive URLs from current page origin when empty (same-origin mode) + if (!this.config.backendUrl) { + this.config.backendUrl = window.location.origin; // e.g. http://localhost:8080 + } + if (!this.config.wsUrl) { + const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + this.config.wsUrl = `${proto}//${window.location.host}`; + } + this._connected = false; this._modelLoaded = false; this._ws = null; diff --git a/lan-client/engine.js b/lan-client/engine.js index 0995b57..24db745 100644 --- a/lan-client/engine.js +++ b/lan-client/engine.js @@ -12723,39 +12723,26 @@ async function loadPreconForPlayer(playerIdx, preconId) { var player = gameState.players[playerIdx]; if (!player) return; - // Try to get decklist text: embedded first, then fetch from backend .dck file + // Try to get decklist text: embedded first, then fetch .dck from server var decklistText = precon.decklist || ''; if (!decklistText && precon.fileName) { - // Fetch .dck file from backend if available - if (typeof AIBridge !== 'undefined' && AIBridge.connector.connected) { - try { - var deckData = await AIBridge.deckSource.fetchPreconDeck(precon.fileName); - if (deckData && deckData.decklist) { - decklistText = deckData.decklist; - } else if (deckData && typeof deckData === 'string') { - decklistText = deckData; - } - } catch (e) { - console.warn('[loadPreconForPlayer] Backend deck fetch failed:', e); + // 1. Try the API endpoint (returns JSON with decklist field) + try { + var apiResp = await fetch('/api/lab/precons/deck?fileName=' + encodeURIComponent(precon.fileName)); + if (apiResp.ok) { + var deckData = await apiResp.json(); + if (deckData && deckData.decklist) decklistText = deckData.decklist; } + } catch (e) { + console.warn('[loadPreconForPlayer] API deck fetch failed:', e); } - // Fallback: try to fetch .dck file directly from precon-decks/ (same origin) + // 2. Fallback: fetch .dck file directly from static mount if (!decklistText) { try { - var resp = await fetch('precon-decks/' + precon.fileName); - if (resp.ok) { - decklistText = await resp.text(); - } - } catch (e) { - // Fallback: try relative to backend - try { - var resp2 = await fetch('/precon-decks/' + precon.fileName); - if (resp2.ok) { - decklistText = await resp2.text(); - } - } catch (e2) { - console.warn('[loadPreconForPlayer] Could not fetch .dck file:', precon.fileName); - } + var staticResp = await fetch('/precon-decks/' + encodeURIComponent(precon.fileName)); + if (staticResp.ok) decklistText = await staticResp.text(); + } catch (e2) { + console.warn('[loadPreconForPlayer] Static .dck fetch failed:', precon.fileName); } } }