diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js index d5613cd..6d2dd03 100644 --- a/muxplex/frontend/app.js +++ b/muxplex/frontend/app.js @@ -839,10 +839,14 @@ function renderGrid(sessions) { on(tile, 'click', (e) => { // Don't navigate when clicking the delete button inside the tile if (e.target.closest && e.target.closest('.tile-delete')) return; + // Don't open error/status tiles (unreachable, auth_failed) + if (tile.classList.contains('source-tile--error') || !tile.dataset.session) return; openSession(tile.dataset.session, { remoteId: tile.dataset.remoteId || '' }); }); on(tile, 'keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { + // Don't open error/status tiles (unreachable, auth_failed) + if (tile.classList.contains('source-tile--error') || !tile.dataset.session) return; openSession(tile.dataset.session, { remoteId: tile.dataset.remoteId || '' }); } }); @@ -1154,6 +1158,7 @@ function updatePageTitle() { * @returns {Promise} */ async function openSession(name, opts = {}) { + if (!name || !name.trim()) return; hidePreview(); _viewingSession = name; _viewingRemoteId = opts.remoteId != null ? opts.remoteId : ''; diff --git a/muxplex/frontend/tests/test_app.mjs b/muxplex/frontend/tests/test_app.mjs index ead44ba..147549e 100644 --- a/muxplex/frontend/tests/test_app.mjs +++ b/muxplex/frontend/tests/test_app.mjs @@ -1240,6 +1240,52 @@ test('openSession for local session still POSTs to local /api/sessions/{name}/co globalThis.setTimeout = origSetTimeout; }); +test('openSession bails early when name is empty string', async () => { + const fetchCalls = []; + const origFetch = globalThis.fetch; + const origGetById = globalThis.document.getElementById; + const origQS = globalThis.document.querySelector; + const origSetTimeout = globalThis.setTimeout; + globalThis.fetch = async (url, opts) => { fetchCalls.push({ url, opts }); return { ok: true }; }; + globalThis.document.getElementById = () => ({ textContent: '', style: {}, classList: { remove: () => {}, add: () => {} } }); + globalThis.document.querySelector = () => null; + globalThis.setTimeout = () => {}; + globalThis.window._openTerminal = () => {}; + + await app.openSession('', {}); + + // Should NOT make any fetch calls to /connect + const connectCall = fetchCalls.find((c) => c.url && c.url.includes('/connect')); + assert.ok(!connectCall, 'should NOT call connect when name is empty string'); + globalThis.fetch = origFetch; + globalThis.document.getElementById = origGetById; + globalThis.document.querySelector = origQS; + globalThis.setTimeout = origSetTimeout; +}); + +test('openSession bails early when name is whitespace only', async () => { + const fetchCalls = []; + const origFetch = globalThis.fetch; + const origGetById = globalThis.document.getElementById; + const origQS = globalThis.document.querySelector; + const origSetTimeout = globalThis.setTimeout; + globalThis.fetch = async (url, opts) => { fetchCalls.push({ url, opts }); return { ok: true }; }; + globalThis.document.getElementById = () => ({ textContent: '', style: {}, classList: { remove: () => {}, add: () => {} } }); + globalThis.document.querySelector = () => null; + globalThis.setTimeout = () => {}; + globalThis.window._openTerminal = () => {}; + + await app.openSession(' \t\n ', {}); + + // Should NOT make any fetch calls to /connect + const connectCall = fetchCalls.find((c) => c.url && c.url.includes('/connect')); + assert.ok(!connectCall, 'should NOT call connect when name is whitespace only'); + globalThis.fetch = origFetch; + globalThis.document.getElementById = origGetById; + globalThis.document.querySelector = origQS; + globalThis.setTimeout = origSetTimeout; +}); + // --- closeSession --- test('closeSession is exported', () => {