Add retry to server connection on launch

This changes the launch behavior to retry connection when the server
cannot be reached on launch. It can be cancelled by pressing the escape
key to show the server address form.

This also changes the styling of this page.
This commit is contained in:
Andrew Rabert committed 2025-09-30 21:29:35 -04:00
1 parent dacab65460
commit 6a3cdfe10f
4 files changed
+227 -96

No files matched your search

+89 -46
View File
@@ -3,14 +3,15 @@
}
body {
background-color: #101010;
color: #fff;
font-family: sans-serif;
font-size: 1.2em;
color: #ddd;
color: rgba(255, 255, 255, 0.8);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 1em;
padding: 1em;
}
.logo {
max-width: 500px;
margin-bottom: 1em;
margin-bottom: 2em;
margin-left: auto;
margin-right: auto;
display: block;
@@ -19,44 +20,47 @@ input[type=text] {
display: block;
width: 100%;
font-size: 1em;
background-color: #292929;
color: #fff;
border: 2px solid #292929;
color: inherit;
background: #292929;
border: 0.16em solid #292929;
border-radius: 0.2em;
padding: 0.5em;
padding: 0.6em 0.8em;
font-family: inherit;
}
input[type=text]:focus {
border: 2px solid #00a4dc;
border-radius: 0.2em;
border-color: #00a4dc;
outline: none;
}
input[type=text]::placeholder {
color: #666;
color: rgba(255, 255, 255, 0.3);
opacity: 1;
}
button {
width: 100%;
color: #fff;
background-color: #00a4dc;
background: #00a4dc;
border: none;
border-radius: 0.2em;
font-size: 1em;
padding: 0.5em;
padding: 0.75em 1.5em;
cursor: pointer;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.025em;
outline: none;
font-family: inherit;
}
button:hover:not(:disabled) {
background: #0cb0e8;
}
button:disabled {
background-color: #292929;
cursor: default;
opacity: 0.6;
}
#address-label {
display: block;
margin-bottom: 0.5em;
}
#main {
max-width: 1000px;
margin-top: 3rem;
margin-left: auto;
margin-right: auto;
display: none;
}
#splash {
#container {
position: absolute;
top: 0;
left: 0;
@@ -65,31 +69,70 @@ button:disabled {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
#backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 99;
display: none;
}
#connect-fail {
text-align: center;
background-color: #101010;
max-width: 600px;
padding: 25px;
margin-top: calc(50vh - 100px);
.logo {
max-width: 500px;
margin-bottom: 2em;
margin-left: auto;
margin-right: auto;
opacity: 1;
display: block;
}
#connect-fail h1 {
#title {
margin-top: 1em;
margin-bottom: 0;
font-size: 1.5em;
margin-top: 0;
font-weight: 400;
color: #ddd;
color: rgba(255, 255, 255, 0.8);
min-height: 1.5em;
height: 1.5em;
max-width: 450px;
width: 100%;
text-align: left;
visibility: hidden;
}
#connect-form {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
position: relative;
}
#address {
margin-bottom: 1em;
max-width: 450px;
height: 2.5em;
visibility: hidden;
}
#address.connecting {
visibility: hidden;
}
#spinner {
border: 6px solid #292929;
border-top: 6px solid #00a4dc;
border-radius: 50%;
width: 80px;
height: 80px;
animation: spin 1s linear infinite;
display: none;
position: absolute;
top: calc(1em + 1.5em + 1.25em);
left: 50%;
margin-left: -40px;
}
#connect-button {
max-width: 450px;
min-height: 2.5em;
visibility: hidden;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#connect-button.cancel {
background: #cb272a;
}
#connect-button.cancel:hover:not(:disabled) {
background: #d93134;
}
#helper-text {
margin-top: 0.3em;
}
+3 -14
View File
@@ -6,26 +6,15 @@
<link rel="stylesheet" href="find-webclient.css">
</head>
<body>
<div id="splash">
<div id="container">
<img class="logo" src="logo.png">
</div>
<div id="main">
<img class="logo" src="logo.png">
<h1 id="title"></h1>
<form id="connect-form">
<label for="address" id="address-label"></label>
<h1 id="title"></h1>
<div id="spinner"></div>
<input type="text" id="address" autofocus>
<p id="helper-text"></p>
<button id="connect-button" type="submit"></button>
</form>
</div>
<div id="backdrop">
<div id="connect-fail">
<h1 id="connect-fail-title"></h1>
<p id="connect-fail-text"></p>
<button id="connect-fail-button"></button>
</div>
</div>
<script src="find-webclient.lang.js"></script>
<script src="find-webclient.js"></script>
</html>
+124 -27
View File
@@ -1,19 +1,16 @@
async function tryConnect(server) {
document.getElementById('connect-button').disabled = true;
AbortSignal.timeout = function timeout(ms) {
const ctrl = new AbortController()
setTimeout(() => ctrl.abort(), ms)
return ctrl.signal
}
try {
if (!server.startsWith("http")) {
server = "http://" + server;
}
serverBaseURL = server.replace(/\/+$/, "");
const url = serverBaseURL + "/System/Info/Public";
const response = await fetch(url, { cache: 'no-cache', signal: AbortSignal.timeout(5000) });
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 60000);
const response = await fetch(url, { cache: 'no-cache', signal: controller.signal });
clearTimeout(timeoutId);
if (response.ok && (await response.json()).Id) {
const htmlResponse = await fetch(server, { cache: 'no-cache' });
if (!htmlResponse.ok) {
@@ -21,9 +18,6 @@ async function tryConnect(server) {
}
if (response.headers.get("content-security-policy")) {
// Sigh... If we just navigate to the URL, the server's CSP will block us loading other resources.
// So we have to parse the HTML, set a new base href, and then write it back to the page.
// We also have to override the history functions to make sure they use the correct URL.
console.log("Using CSP workaround");
const webUrl = htmlResponse.url.replace(/\/[^\/]*$/, "/");
const realUrl = window.location.href;
@@ -60,36 +54,139 @@ async function tryConnect(server) {
return true;
}
return false;
} catch (e) {
console.error(e);
document.getElementById('connect-button').disabled = false;
return false;
}
}
document.getElementById('connect-form').addEventListener('submit', async (e) => {
let isConnecting = false;
const updateButtonState = () => {
const address = document.getElementById('address');
const button = document.getElementById('connect-button');
const hasValue = address.value.trim().length > 0;
if (!isConnecting) {
button.disabled = !hasValue;
}
};
const cancelOnEscape = (e) => {
if (isConnecting && e.key === 'Escape') {
cancelConnection();
}
};
const startConnecting = async () => {
const address = document.getElementById('address');
const title = document.getElementById('title');
const spinner = document.getElementById('spinner');
const button = document.getElementById('connect-button');
const server = address.value;
isConnecting = true;
title.textContent = '';
title.style.visibility = 'hidden';
address.classList.add('connecting');
address.style.visibility = 'hidden';
address.disabled = true;
spinner.style.display = 'block';
button.style.visibility = 'hidden';
document.addEventListener('keydown', cancelOnEscape);
let connected = false;
while (!connected && isConnecting) {
connected = await tryConnect(server);
if (!connected && isConnecting) {
// Wait 5 seconds before retrying
await new Promise(resolve => setTimeout(resolve, 5000));
}
}
if (!connected) {
isConnecting = false;
title.textContent = document.getElementById('title').getAttribute('data-original-text');
title.style.visibility = 'visible';
address.classList.remove('connecting');
address.style.visibility = 'visible';
address.disabled = false;
spinner.style.display = 'none';
button.style.visibility = 'visible';
document.removeEventListener('keydown', cancelOnEscape);
updateButtonState();
}
};
const cancelConnection = () => {
if (!isConnecting) return;
isConnecting = false;
const address = document.getElementById('address');
const title = document.getElementById('title');
const spinner = document.getElementById('spinner');
const button = document.getElementById('connect-button');
title.textContent = document.getElementById('title').getAttribute('data-original-text');
title.style.visibility = 'visible';
address.classList.remove('connecting');
address.style.visibility = 'visible';
address.disabled = false;
spinner.style.display = 'none';
button.style.visibility = 'visible';
document.removeEventListener('keydown', cancelOnEscape);
updateButtonState();
};
// Button click handler
document.getElementById('connect-button').addEventListener('click', (e) => {
e.preventDefault();
const server = document.getElementById('address').value;
const result = await tryConnect(server);
if (!result) {
document.getElementById('backdrop').style.display = 'block';
e.stopPropagation();
if (!e.target.disabled) {
startConnecting();
}
});
document.getElementById('connect-fail-button').addEventListener('click', () => {
document.getElementById('backdrop').style.display = 'none';
// Form submit handler
document.getElementById('connect-form').addEventListener('submit', (e) => {
e.preventDefault();
if (!isConnecting) {
startConnecting();
}
});
// Input change handler
document.getElementById('address').addEventListener('input', updateButtonState);
// load the server if we have one
// Enter key handler
document.addEventListener('keydown', (e) => {
const address = document.getElementById('address');
if (e.key === 'Enter' && !isConnecting && !address.disabled && address.value.trim()) {
e.preventDefault();
startConnecting();
}
});
// Auto-connect on load
(async() => {
const title = document.getElementById('title');
const address = document.getElementById('address');
const button = document.getElementById('connect-button');
const savedServer = window.jmpInfo.settings.main.userWebClient;
if (savedServer) {
document.getElementById('address').value = savedServer;
}
if (!savedServer || !(await tryConnect(savedServer))) {
document.getElementById('splash').style.display = 'none';
document.getElementById('main').style.display = 'block';
address.value = savedServer;
await startConnecting();
} else {
title.style.visibility = 'visible';
address.style.visibility = 'visible';
button.style.visibility = 'visible';
updateButtonState();
}
})();
+11 -9
View File
@@ -146,7 +146,7 @@ const languages = [
{
"lang": "en-gb",
"HeaderConnectToServer": "Connect to Server",
"LabelServerHost": "Host",
"LabelServerHost": "Server Address",
"LabelServerHostHelp": "192.168.1.100:8096 or https://myserver.com",
"Connect": "Connect",
"HeaderConnectionFailure": "Connection Failure",
@@ -156,7 +156,7 @@ const languages = [
{
"lang": "en-us",
"HeaderConnectToServer": "Connect to Server",
"LabelServerHost": "Host",
"LabelServerHost": "Server Address",
"LabelServerHostHelp": "192.168.1.100:8096 or https://myserver.com",
"Connect": "Connect",
"HeaderConnectionFailure": "Connection Failure",
@@ -965,10 +965,12 @@ if (!languages.find(l => l.lang === language)) {
const languageStrings = languages.find(l => l.lang === language);
const fallbackStrings = languages.find(l => l.lang === fallbackLanguage);
document.getElementById('title').innerText = languageStrings.HeaderConnectToServer || fallbackStrings.HeaderConnectToServer;
document.getElementById('address-label').innerText = languageStrings.LabelServerHost || fallbackStrings.LabelServerHost;
document.getElementById('helper-text').innerText = languageStrings.LabelServerHostHelp || fallbackStrings.LabelServerHostHelp;
document.getElementById('connect-button').innerText = languageStrings.Connect || fallbackStrings.Connect;
document.getElementById('connect-fail-title').innerText = languageStrings.HeaderConnectionFailure || fallbackStrings.HeaderConnectionFailure;
document.getElementById('connect-fail-text').innerText = languageStrings.MessageUnableToConnectToServer || fallbackStrings.MessageUnableToConnectToServer;
document.getElementById('connect-fail-button').innerText = languageStrings.ButtonGotIt || fallbackStrings.ButtonGotIt;
const titleText = languageStrings.LabelServerHost || fallbackStrings.LabelServerHost || 'Server Address';
const connectText = languageStrings.Connect || fallbackStrings.Connect;
document.getElementById('title').innerText = titleText;
document.getElementById('title').setAttribute('data-original-text', titleText);
document.getElementById('address').placeholder = languageStrings.LabelServerHostHelp || fallbackStrings.LabelServerHostHelp;
document.getElementById('connect-button').innerText = connectText;
document.getElementById('connect-button').setAttribute('data-original-text', connectText);
window.cancelButtonText = 'Cancel';