mirror of
https://github.com/jellyfin/jellyfin-desktop.git
synced 2026-10-26 16:45:24 +00:00
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:
1 parent
dacab65460
commit
6a3cdfe10f
4 files changed
+227
-96
No files matched your search
+89
-46
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
})();
|
||||
@@ -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';
|
||||
Reference in new issue
Block a user