diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js index 35e0c93..13615da 100644 --- a/muxplex/frontend/app.js +++ b/muxplex/frontend/app.js @@ -2039,9 +2039,11 @@ function showFabSessionInput() { * @param {string} name - The session name to create. * @returns {Promise} */ -async function createNewSession(name) { +async function createNewSession(name, remoteId) { + remoteId = remoteId || ''; try { - const res = await api('POST', '/api/sessions', { name }); + var endpoint = remoteId ? '/api/federation/' + encodeURIComponent(remoteId) + '/sessions' : '/api/sessions'; + const res = await api('POST', endpoint, { name }); const data = await res.json(); const sessionName = data.name || name; showToast('Creating session \'' + sessionName + '\'…'); @@ -2073,6 +2075,9 @@ async function createNewSession(name) { return; } + // Compute expectedKey: for remote sessions, use 'remoteId:sessionName' (sessionKey format) + var expectedKey = remoteId ? (remoteId + ':' + sessionName) : sessionName; + // Poll until the session appears in _currentSessions (max 30s, every 2s) var attempts = 0; var maxAttempts = 15; @@ -2080,13 +2085,13 @@ async function createNewSession(name) { attempts++; await pollSessions(); var found = _currentSessions && _currentSessions.find(function(s) { - return s.name === sessionName; + return (s.sessionKey || s.name) === expectedKey; }); if (found) { clearInterval(pollForSession); removeLoadingTile(); showToast('Session \'' + sessionName + '\' ready'); - openSession(sessionName); + openSession(sessionName, { remoteId: remoteId }); } else if (attempts >= maxAttempts) { clearInterval(pollForSession); removeLoadingTile(); diff --git a/muxplex/frontend/tests/test_app.mjs b/muxplex/frontend/tests/test_app.mjs index 94b647f..06ec93a 100644 --- a/muxplex/frontend/tests/test_app.mjs +++ b/muxplex/frontend/tests/test_app.mjs @@ -4231,3 +4231,63 @@ test('showFabSessionInput creates device select when multi_device_enabled with r 'showFabSessionInput must call createNewSession with name and remoteId arguments', ); }); + +// --- createNewSession federation routing (task-4) --- + +test('createNewSession accepts remoteId parameter and routes to federation endpoint', () => { + const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8'); + // Extract createNewSession function body + const fnStart = source.indexOf('async function createNewSession('); + assert.ok(fnStart !== -1, 'createNewSession function must exist'); + const fnBody = source.substring(fnStart, fnStart + 2000); + // Must accept remoteId parameter + assert.ok( + fnBody.includes('remoteId'), + 'createNewSession must accept remoteId parameter', + ); + // Must include federation endpoint path + assert.ok( + fnBody.includes('/api/federation/'), + 'createNewSession must include /api/federation/ endpoint path for remote routing', + ); + // Must still have local /api/sessions endpoint + assert.ok( + fnBody.includes('/api/sessions'), + 'createNewSession must still use /api/sessions for local sessions', + ); +}); + +test('createNewSession passes remoteId through to openSession for auto-open', () => { + const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8'); + const fnStart = source.indexOf('async function createNewSession('); + assert.ok(fnStart !== -1, 'createNewSession function must exist'); + // Use 3000 chars to cover the full function including the polling interval callback + const fnBody = source.substring(fnStart, fnStart + 3000); + // Must call openSession with remoteId option + assert.ok( + fnBody.includes('openSession') && fnBody.includes('remoteId'), + 'createNewSession must call openSession with remoteId option', + ); + // Must pass remoteId as an option object to openSession + assert.ok( + fnBody.includes('{ remoteId') || fnBody.includes('{ remoteId:'), + 'createNewSession must pass remoteId as option object to openSession', + ); +}); + +test('createNewSession matches remote sessions by sessionKey in poll loop', () => { + const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8'); + const fnStart = source.indexOf('async function createNewSession('); + assert.ok(fnStart !== -1, 'createNewSession function must exist'); + const fnBody = source.substring(fnStart, fnStart + 2000); + // Must use sessionKey in the match logic (with fallback to name) + assert.ok( + fnBody.includes('sessionKey'), + 'createNewSession poll loop must match remote sessions by sessionKey', + ); + // Must use expectedKey or similar computed key for comparison + assert.ok( + fnBody.includes('expectedKey') || (fnBody.includes('sessionKey') && fnBody.includes('remoteId')), + 'createNewSession must compute expected sessionKey for remote session matching', + ); +});