diff --git a/src/jfn_cef/src/resource.rs b/src/jfn_cef/src/resource.rs index 8dc4f14df..e58f0d6cc 100644 --- a/src/jfn_cef/src/resource.rs +++ b/src/jfn_cef/src/resource.rs @@ -47,6 +47,7 @@ static RESOURCES: &[(&str, Embedded)] = &[ embedded!("overlay.html", "text/html"), embedded!("overlay.js", "application/javascript"), embedded!("overlay.lang.js", "application/javascript"), + embedded!("server-url.js", "application/javascript"), ]; fn lookup(url_path: &str) -> Option<&'static Embedded> { diff --git a/src/web/overlay.css b/src/web/overlay.css index d674ce743..925d16509 100644 --- a/src/web/overlay.css +++ b/src/web/overlay.css @@ -167,6 +167,10 @@ button:disabled { padding-left: 1.5em; padding-right: 1.5em; } +.dialog-hint { + margin-top: 1em; + color: #fff; +} .dialog-button { margin-top: 2em; width: auto; diff --git a/src/web/overlay.html b/src/web/overlay.html index 41bc1a9aa..46f63da4b 100644 --- a/src/web/overlay.html +++ b/src/web/overlay.html @@ -18,6 +18,7 @@

+ \ No newline at end of file diff --git a/src/web/overlay.js b/src/web/overlay.js index 703d22eee..1f96ee5b1 100644 --- a/src/web/overlay.js +++ b/src/web/overlay.js @@ -108,7 +108,7 @@ const cancelOnEscape = (e) => { } }; -const showConnectionFailedDialog = () => { +const showConnectionFailedDialog = (server) => { const dialog = document.createElement('div'); dialog.className = 'dialog scaleIn'; @@ -119,6 +119,14 @@ const showConnectionFailedDialog = () => { message.innerText = messageUnableToConnectToServerText; message.className = 'dialog-message'; + const suggestedUrl = window.jmpDefaultServerUrlForHostOnly(server); + let hint = null; + if (suggestedUrl) { + hint = document.createElement('div'); + hint.innerText = `No scheme or port was provided. For a default local Jellyfin server, try ${suggestedUrl}.`; + hint.className = 'dialog-message dialog-hint'; + } + const button = document.createElement('button'); button.innerText = buttonGotItText; button.type = 'button'; @@ -129,6 +137,7 @@ const showConnectionFailedDialog = () => { dialog.appendChild(header); dialog.appendChild(message); + if (hint) dialog.appendChild(hint); dialog.appendChild(button); document.body.appendChild(dialog); }; @@ -166,7 +175,7 @@ const startConnecting = async () => { button.style.visibility = 'visible'; document.removeEventListener('keydown', cancelOnEscape); updateButtonState(); - showConnectionFailedDialog(); + showConnectionFailedDialog(server); } }; diff --git a/src/web/server-url.js b/src/web/server-url.js new file mode 100644 index 000000000..7e3b39a87 --- /dev/null +++ b/src/web/server-url.js @@ -0,0 +1,25 @@ +(function(global) { + function defaultServerUrlForHostOnly(address) { + const value = String(address || '').trim(); + if (!value || value.includes('://') || value.startsWith('//')) return null; + + // A bare IPv6 address needs brackets before the URL parser can + // distinguish it from a host with an explicit port. + const colonCount = (value.match(/:/g) || []).length; + const candidate = colonCount > 1 && !value.startsWith('[') + ? `http://[${value}]` + : `http://${value}`; + + try { + const url = new URL(candidate); + if (url.username || url.password || url.port || url.pathname !== '/' || url.search || url.hash) { + return null; + } + return `http://${url.hostname}:8096`; + } catch (_) { + return null; + } + } + + global.jmpDefaultServerUrlForHostOnly = defaultServerUrlForHostOnly; +})(typeof window === 'undefined' ? globalThis : window); diff --git a/src/web/server-url.test.js b/src/web/server-url.test.js new file mode 100644 index 000000000..79008367f --- /dev/null +++ b/src/web/server-url.test.js @@ -0,0 +1,17 @@ +const assert = require('node:assert/strict'); +const test = require('node:test'); + +require('./server-url.js'); + +test('suggests the Jellyfin default URL for host-only input', () => { + assert.equal(jmpDefaultServerUrlForHostOnly('jellyfin.local'), 'http://jellyfin.local:8096'); + assert.equal(jmpDefaultServerUrlForHostOnly(' 192.168.1.50 '), 'http://192.168.1.50:8096'); + assert.equal(jmpDefaultServerUrlForHostOnly('::1'), 'http://[::1]:8096'); +}); + +test('does not suggest a URL when connection details were supplied', () => { + assert.equal(jmpDefaultServerUrlForHostOnly('http://jellyfin.local'), null); + assert.equal(jmpDefaultServerUrlForHostOnly('jellyfin.local:8096'), null); + assert.equal(jmpDefaultServerUrlForHostOnly('jellyfin.local/web'), null); + assert.equal(jmpDefaultServerUrlForHostOnly(''), null); +});