Use history.pushState where possible

This commit is contained in:
Andrio Celos 2022-10-11 14:01:25 +11:00
parent fe9d3c3d64
commit e3fd48b45e
5 changed files with 85 additions and 22 deletions

Binary file not shown.

View File

@ -402,7 +402,7 @@ document.addEventListener('keydown', e => {
document.getElementById('resultLeaveButton')!.addEventListener('click', e => {
e.preventDefault();
clearPreGameForm();
clearPreGameForm(true);
showSection('preGame');
newGameButton.focus();
});

View File

@ -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();

View File

@ -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.
}

View File

@ -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<Guid, Game> games = new();
internal static readonly Timer timer = new(500);
private static readonly List<Guid> 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");