fix: guard openSession against empty name from unreachable federation tiles
Clicking an unreachable remote device tile passed empty session name to openSession(), producing /api/federation/2/connect/ (no session name) → 405. Fix: bail early if name is empty or whitespace. Also added guard in grid click handlers to skip error/status tiles entirely, preventing clicks on unreachable or auth_failed tiles.
This commit is contained in:
@@ -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<void>}
|
||||
*/
|
||||
async function openSession(name, opts = {}) {
|
||||
if (!name || !name.trim()) return;
|
||||
hidePreview();
|
||||
_viewingSession = name;
|
||||
_viewingRemoteId = opts.remoteId != null ? opts.remoteId : '';
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user