From e3fd48b45e69ba77d398af6832a2b415d0884cde Mon Sep 17 00:00:00 2001 From: Andrio Celos Date: Tue, 11 Oct 2022 14:01:25 +1100 Subject: [PATCH] Use history.pushState where possible --- TableturfBattleClient/index.html | Bin 7520 -> 7743 bytes TableturfBattleClient/src/Pages/GamePage.ts | 2 +- .../src/Pages/PreGamePage.ts | 78 ++++++++++++++---- TableturfBattleClient/src/app.ts | 6 +- TableturfBattleServer/Program.cs | 21 ++++- 5 files changed, 85 insertions(+), 22 deletions(-) diff --git a/TableturfBattleClient/index.html b/TableturfBattleClient/index.html index 21d12abe4c829003f355ad09f96e706c1841ca8e..abcaa573018378c1abab5221c1e29c001c65b342 100644 GIT binary patch delta 182 zcmaE0wclof8)s5tajHT_QEJ*`J|@fh;^d;tf)YC}PEO90{N&Qy)VvbC^wbj9oKz6A z*r_rkG2JgQH&sI&q)%Nl+8|aBq*=jMp*%A$CBIxRCqFr{Br`uxuOvUXq$o2lT|-l^ zD77FbF*#L3KSp1}-U_HLwL&kWBsZrf9cWaHesr9Etd^#VzK()`y1u%mHPCh&eTYjo TI?BlMq*U5*+315b*l_^>ekwbq delta 15 WcmdmQ^T29?+h!R?aam?A1}*?D4Fm@O diff --git a/TableturfBattleClient/src/Pages/GamePage.ts b/TableturfBattleClient/src/Pages/GamePage.ts index 1cff7e4..db77e4e 100644 --- a/TableturfBattleClient/src/Pages/GamePage.ts +++ b/TableturfBattleClient/src/Pages/GamePage.ts @@ -402,7 +402,7 @@ document.addEventListener('keydown', e => { document.getElementById('resultLeaveButton')!.addEventListener('click', e => { e.preventDefault(); - clearPreGameForm(); + clearPreGameForm(true); showSection('preGame'); newGameButton.focus(); }); diff --git a/TableturfBattleClient/src/Pages/PreGamePage.ts b/TableturfBattleClient/src/Pages/PreGamePage.ts index faab04a..6b1be07 100644 --- a/TableturfBattleClient/src/Pages/PreGamePage.ts +++ b/TableturfBattleClient/src/Pages/PreGamePage.ts @@ -28,6 +28,9 @@ maxPlayersBox.addEventListener('change', () => { let response = JSON.parse(request.responseText); if (!clientToken) setClientToken(response.clientToken); + + setGameUrl(response.gameID); + getGameInfo(response.gameID, 0); } }); @@ -44,18 +47,34 @@ maxPlayersBox.addEventListener('change', () => { } }); +function setGameUrl(gameID: string | null) { + if (canPushState) { + try { + history.pushState(null, '', `game/${gameID}`); + } catch { + canPushState = false; + location.hash = `#game/${gameID}`; + } + } else + location.hash = `#game/${gameID}`; +} + function tryJoinGame(name: string, idOrUrl: string, fromInitialLoad: boolean) { - const gameID = idOrUrl.substring(idOrUrl.lastIndexOf('#') + 1); - if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(gameID)) { + const m = /(?:^|[#/])([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i.exec(idOrUrl); + if (!m) { alert("Invalid game ID or link"); if (fromInitialLoad) - clearPreGameForm(); + clearPreGameForm(true); else { gameIDBox.focus(); gameIDBox.setSelectionRange(0, gameIDBox.value.length); } return; } + const gameID = m[1]; + + if (!fromInitialLoad) + setGameUrl(gameID); let request = new XMLHttpRequest(); request.open('POST', `${config.apiBaseUrl}/games/${gameID}/join`); @@ -73,7 +92,7 @@ function tryJoinGame(name: string, idOrUrl: string, fromInitialLoad: boolean) { else alert('Unable to join the room.'); if (fromInitialLoad) - clearPreGameForm(); + clearPreGameForm(true); else { gameIDBox.focus(); gameIDBox.setSelectionRange(0, gameIDBox.value.length); @@ -88,34 +107,63 @@ function tryJoinGame(name: string, idOrUrl: string, fromInitialLoad: boolean) { function getGameInfo(gameID: string, myPlayerIndex: number | null) { board.playerIndex = myPlayerIndex; - window.location.hash = `#${gameID}`; initLobbyPage(window.location.toString()); myPlayerIndex = setupWebSocket(gameID, myPlayerIndex); } -function backPreGameForm() { +function backPreGameForm(updateUrl: boolean) { document.getElementById('preGameDefaultSection')!.hidden = false; document.getElementById('preGameJoinSection')!.hidden = true; - window.location.hash = ''; + + if (updateUrl) { + if (canPushState) { + try { + history.pushState(null, '', '../..'); + } catch { + canPushState = false; + } + } + if (location.hash) + location.hash = ''; + } } -function clearPreGameForm() { - backPreGameForm(); +function clearPreGameForm(updateUrl: boolean) { + backPreGameForm(updateUrl); currentGame = null; gameIDBox.value = ''; } document.getElementById('preGameBackButton')!.addEventListener('click', e => { e.preventDefault(); - backPreGameForm(); + backPreGameForm(true); }) -const playerName = window.localStorage.getItem('name'); -(document.getElementById('nameBox') as HTMLInputElement).value = playerName || ''; -if (window.location.hash != '') { +function presetGameID(url: string) { document.getElementById('preGameDefaultSection')!.hidden = true; document.getElementById('preGameJoinSection')!.hidden = false; - (document.getElementById('gameIDBox') as HTMLInputElement).value = window.location.hash; + (document.getElementById('gameIDBox') as HTMLInputElement).value = url; if (playerName) - tryJoinGame(playerName, window.location.hash, true); + tryJoinGame(playerName, url, true); } + +const playerName = localStorage.getItem('name'); +(document.getElementById('nameBox') as HTMLInputElement).value = playerName || ''; + +function processUrl() { + const m = /^(.*)\/game\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/.exec(location.toString()); + if (m) + presetGameID(m[2]); + else if (location.hash) { + canPushState = false; + presetGameID(location.hash); + } else { + clearPreGameForm(false); + showSection('preGame'); + } +} + +window.addEventListener('popstate', () => { + processUrl(); +}); +processUrl(); diff --git a/TableturfBattleClient/src/app.ts b/TableturfBattleClient/src/app.ts index dcd8cf7..2ee82d6 100644 --- a/TableturfBattleClient/src/app.ts +++ b/TableturfBattleClient/src/app.ts @@ -1,3 +1,5 @@ +let canPushState = isSecureContext && location.protocol != 'file:'; + function delay(ms: number) { return new Promise(resolve => setTimeout(() => resolve(null), ms)); } // Sections @@ -90,7 +92,7 @@ function setupWebSocket(gameID: string, myPlayerIndex: number | null) { const webSocket = new WebSocket(`${config.apiBaseUrl.replace(/(http)(s)?\:\/\//, 'ws$2://')}/websocket?gameID=${gameID}&clientToken=${clientToken}`); webSocket.addEventListener('open', e => { const request2 = new XMLHttpRequest(); - request2.open('GET', `${config.apiBaseUrl}/games/${gameID}/playerData/${clientToken}`); + request2.open('GET', `${config.apiBaseUrl}/games/${gameID}/playerData?clientToken=${clientToken}`); request2.addEventListener('load', e => { if (request2.status == 200) { const response = JSON.parse(request2.responseText); @@ -230,8 +232,6 @@ function setupWebSocket(gameID: string, myPlayerIndex: number | null) { return myPlayerIndex; } -showSection('preGame'); - function isInternetExplorer() { return !!(window.document as any).documentMode; // This is a non-standard property implemented only by Internet Explorer. } diff --git a/TableturfBattleServer/Program.cs b/TableturfBattleServer/Program.cs index 80e18cc..da80b29 100644 --- a/TableturfBattleServer/Program.cs +++ b/TableturfBattleServer/Program.cs @@ -16,7 +16,7 @@ using Timer = System.Timers.Timer; namespace TableturfBattleServer; internal class Program { - internal static HttpServer httpServer = new(IPAddress.Loopback, 3333); + internal static HttpServer httpServer = new(IPAddress.Loopback, 3333) { DocumentRootPath = Path.Combine("..", "..", "..", "..", "TableturfBattleClient") }; internal static Dictionary games = new(); internal static readonly Timer timer = new(500); private static readonly List gameIdsToRemove = new(); @@ -45,7 +45,22 @@ internal class Program { private static void HttpServer_OnRequest(object? sender, HttpRequestEventArgs e) { e.Response.AppendHeader("Access-Control-Allow-Origin", "*"); - if (e.Request.RawUrl == "/api/games/new") { + if (!e.Request.RawUrl.StartsWith("/api/")) { + var path = e.Request.RawUrl == "/" || e.Request.RawUrl.StartsWith("/game/") ? "index.html" : e.Request.RawUrl[1..]; + if (e.TryReadFile(path, out var bytes)) + SetResponse(e.Response, 200, + Path.GetExtension(path) switch { + ".html" or ".htm" => "text/html", + ".css" => "text/css", + ".js" => "text/javascript", + ".png" => "image/png", + ".woff" or ".woff2" => "font/woff", + _ => "application/octet-stream" + }, bytes); + else + SetErrorResponse(e.Response, 404, new("FileNotFound", "File not found.")); + return; + } else if (e.Request.RawUrl == "/api/games/new") { if (e.Request.HttpMethod != "POST") { e.Response.StatusCode = (int) HttpStatusCode.MethodNotAllowed; e.Response.AddHeader("Allow", "POST"); @@ -87,7 +102,7 @@ internal class Program { } else if (e.Request.RawUrl == "/api/stages") { SetStaticResponse(e.Request, e.Response, StageDatabase.JSON, StageDatabase.Version.ToString(), StageDatabase.LastModified); } else { - var m = Regex.Match(e.Request.RawUrl, @"^/api/games/([\w-]+)(?:/(\w+)(?:/([\w-]+))?)?$", RegexOptions.Compiled); + var m = Regex.Match(e.Request.RawUrl, @"^/api/games/([\w-]+)(?:/(\w+)(?:\?clientToken=([\w-]+))?)?$", RegexOptions.Compiled); if (m.Success) { if (!Guid.TryParse(m.Groups[1].Value, out var gameID)) { SetResponse(e.Response, 400, "text/plain", "Invalid game ID");