From 83e5318ba146f6c6a3cff95068a7ba5d0c6c51e2 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Tue, 31 Mar 2026 02:37:05 -0700 Subject: [PATCH] feat: add buildAuthTileHTML for auth-required federation tiles - Add buildAuthTileHTML(source) function to frontend/app.js after buildSidebarHTML - Function renders an
with device name, login button (with data-url), and hint text - HTML-escapes both name and url to prevent XSS - Export buildAuthTileHTML under '// Federation tiles' comment in module.exports - Add 5 tests covering: export, article class, device name, login button data-url, HTML escaping Task: task-2-build-auth-tile-html --- muxplex/frontend/app.js | 19 +++++++++++++++++++ muxplex/frontend/tests/test_app.mjs | 29 +++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js index 30a489a..5e2b5c3 100644 --- a/muxplex/frontend/app.js +++ b/muxplex/frontend/app.js @@ -524,6 +524,23 @@ function buildSidebarHTML(session, currentSession) { ); } +/** + * Build the HTML string for an auth-required source tile. + * @param {{ name: string, url: string }} source + * @returns {string} + */ +function buildAuthTileHTML(source) { + const escapedName = escapeHtml(source.name || ''); + const escapedUrl = escapeHtml(source.url || ''); + return ( + '
' + + '' + escapedName + '' + + '' + + 'Authenticate to see sessions' + + '
' + ); +} + /** * Returns sessions with hidden session names removed. * Only hides LOCAL sessions (those with empty/absent sourceUrl) matching the @@ -2200,6 +2217,8 @@ if (typeof module !== 'undefined' && module.exports) { mergeSources, // Filter bar renderFilterBar, + // Federation tiles + buildAuthTileHTML, // Test-only helpers _setCurrentSessions, _setViewMode, diff --git a/muxplex/frontend/tests/test_app.mjs b/muxplex/frontend/tests/test_app.mjs index cefee58..6194569 100644 --- a/muxplex/frontend/tests/test_app.mjs +++ b/muxplex/frontend/tests/test_app.mjs @@ -2835,3 +2835,32 @@ test('Phase 2 end-to-end: buildSources → tagSessions → mergeSources produces ); }); +// --- buildAuthTileHTML --- + +test('buildAuthTileHTML is exported as a function', () => { + assert.strictEqual(typeof app.buildAuthTileHTML, 'function'); +}); + +test('buildAuthTileHTML returns article with source-tile--auth class', () => { + const html = app.buildAuthTileHTML({ name: 'Dev Server', url: 'http://dev:8088' }); + assert.ok(html.startsWith(' { + const html = app.buildAuthTileHTML({ name: 'Dev Server', url: 'http://dev:8088' }); + assert.ok(html.includes('Dev Server'), 'html should include the device name'); +}); + +test('buildAuthTileHTML includes login button with data-url attribute', () => { + const html = app.buildAuthTileHTML({ name: 'Dev Server', url: 'http://dev:8088' }); + assert.ok(html.includes('source-tile__login-btn'), 'html should include source-tile__login-btn class'); + assert.ok(html.includes('data-url="http://dev:8088"'), 'html should include data-url attribute with correct value'); +}); + +test('buildAuthTileHTML escapes HTML in device name', () => { + const html = app.buildAuthTileHTML({ name: '', url: '' }); + assert.ok(!html.includes(''), 'raw script tag should not appear in html'); + assert.ok(html.includes('<script>'), 'escaped script tag should appear in html'); +}); +