From 3444310bb25d7c6349eda33243aea8554bd007f2 Mon Sep 17 00:00:00 2001 From: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> Date: Tue, 31 Mar 2026 03:27:30 -0700 Subject: [PATCH] feat: accept sourceUrl parameter in connectWebSocket for remote sessions (task-6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with Amplifier --- muxplex/frontend/terminal.js | 20 +++++-- muxplex/frontend/tests/test_terminal.mjs | 74 ++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/muxplex/frontend/terminal.js b/muxplex/frontend/terminal.js index 78d2159..b07a91f 100644 --- a/muxplex/frontend/terminal.js +++ b/muxplex/frontend/terminal.js @@ -11,9 +11,16 @@ let _vpHandler = null; // ─── Forward declarations ───────────────────────────────────────────────────── -function connectWebSocket(name) { - const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'; - const url = `${proto}//${location.host}/terminal/ws`; +function connectWebSocket(name, sourceUrl) { + var url; + if (sourceUrl) { + // Remote session: derive WS URL from the source's HTTP URL + url = sourceUrl.replace(/^http/, 'ws').replace(/\/+$/, '') + '/terminal/ws'; + } else { + // Local session: same origin + var proto = location.protocol === 'https:' ? 'wss:' : 'ws:'; + url = proto + '//' + location.host + '/terminal/ws'; + } const reconnectOverlay = document.getElementById('reconnect-overlay'); const encoder = typeof TextEncoder !== 'undefined' ? new TextEncoder() : null; @@ -175,8 +182,11 @@ function createTerminal() { /** * Open a terminal session inside #terminal-container. * @param {string} sessionName + * @param {string} [sourceUrl] Optional HTTP URL of the remote muxplex instance. + * When provided, the WebSocket connects to that remote host instead of the + * current page origin. */ -function openTerminal(sessionName) { +function openTerminal(sessionName, sourceUrl) { // Null _currentSession first so any in-flight close handler on the old WS won't // schedule a reconnect (it checks `if (!_currentSession) return;`). _currentSession = null; @@ -222,7 +232,7 @@ function openTerminal(sessionName) { }); } - connectWebSocket(sessionName); + connectWebSocket(sessionName, sourceUrl); initVisualViewport(); /* defined in Task 14 */ } diff --git a/muxplex/frontend/tests/test_terminal.mjs b/muxplex/frontend/tests/test_terminal.mjs index d467737..0e96a3e 100644 --- a/muxplex/frontend/tests/test_terminal.mjs +++ b/muxplex/frontend/tests/test_terminal.mjs @@ -719,6 +719,80 @@ test('terminal is auto-focused when WebSocket opens', () => { '_term.focus() should be called exactly once when the WebSocket open event fires'); }); +// --- sourceUrl / remote WebSocket tests ------------------------------------ + +test('connectWebSocket uses remote origin when sourceUrl is provided', () => { + const t = loadTerminal(); + + const orig = globalThis.setTimeout; + globalThis.setTimeout = (_fn, _ms) => 0; + + t.openTerminal('remote-session', 'http://work:8088'); + + globalThis.setTimeout = orig; + + assert.ok(t.capturedWsUrl, 'WebSocket URL should have been captured'); + assert.ok( + t.capturedWsUrl.startsWith('ws://work:8088/'), + `WebSocket URL should start with ws://work:8088/, got: ${t.capturedWsUrl}`, + ); + assert.ok( + t.capturedWsUrl.endsWith('/terminal/ws'), + `WebSocket URL should end with /terminal/ws, got: ${t.capturedWsUrl}`, + ); +}); + +test('connectWebSocket uses local origin when sourceUrl is empty string', () => { + const t = loadTerminal(); + + const orig = globalThis.setTimeout; + globalThis.setTimeout = (_fn, _ms) => 0; + + t.openTerminal('local-session', ''); + + globalThis.setTimeout = orig; + + assert.ok(t.capturedWsUrl, 'WebSocket URL should have been captured'); + assert.ok( + t.capturedWsUrl.includes('localhost'), + `WebSocket URL should include localhost for empty sourceUrl, got: ${t.capturedWsUrl}`, + ); +}); + +test('connectWebSocket uses local origin when sourceUrl is undefined', () => { + const t = loadTerminal(); + + const orig = globalThis.setTimeout; + globalThis.setTimeout = (_fn, _ms) => 0; + + t.openTerminal('local-session'); + + globalThis.setTimeout = orig; + + assert.ok(t.capturedWsUrl, 'WebSocket URL should have been captured'); + assert.ok( + t.capturedWsUrl.includes('localhost'), + `WebSocket URL should include localhost when sourceUrl is undefined, got: ${t.capturedWsUrl}`, + ); +}); + +test('connectWebSocket converts https sourceUrl to wss protocol', () => { + const t = loadTerminal(); + + const orig = globalThis.setTimeout; + globalThis.setTimeout = (_fn, _ms) => 0; + + t.openTerminal('secure-session', 'https://devserver:8088'); + + globalThis.setTimeout = orig; + + assert.ok(t.capturedWsUrl, 'WebSocket URL should have been captured'); + assert.ok( + t.capturedWsUrl.startsWith('wss://devserver:8088/'), + `WebSocket URL should start with wss://devserver:8088/, got: ${t.capturedWsUrl}`, + ); +}); + // --- Android touch scroll --------------------------------------------------- test('terminal.js Android touch scroll is UA-gated', () => {