diff --git a/muxplex/main.py b/muxplex/main.py index 9ce87d6..59b84c5 100644 --- a/muxplex/main.py +++ b/muxplex/main.py @@ -365,8 +365,11 @@ async def terminal_ws_proxy(websocket: WebSocket) -> None: async def client_to_ttyd() -> None: try: while True: - data = await websocket.receive_bytes() - await ttyd_ws.send(data) + msg = await websocket.receive() + if msg.get("bytes"): + await ttyd_ws.send(msg["bytes"]) + elif msg.get("text"): + await ttyd_ws.send(msg["text"]) except Exception: pass diff --git a/muxplex/tests/test_ws_proxy.py b/muxplex/tests/test_ws_proxy.py new file mode 100644 index 0000000..00c2fee --- /dev/null +++ b/muxplex/tests/test_ws_proxy.py @@ -0,0 +1,28 @@ +""" +Regression tests for the WebSocket proxy in muxplex/main.py. +""" + +import inspect + +from muxplex.main import terminal_ws_proxy + + +def test_terminal_ws_proxy_does_not_use_receive_bytes(): + """Regression: receive_bytes() silently drops TEXT frames (like the ttyd auth token). + + terminal.js sends {"AuthToken": ""} as a TEXT WebSocket frame. The original + proxy used receive_bytes() which fails on text frames, swallowed the exception, + and exited — meaning ttyd never received the auth token, never started + streaming, resulting in a permanent black screen and reconnect loop. + + The proxy MUST use receive() and dispatch on message type to handle both + binary and text frames correctly. + """ + source = inspect.getsource(terminal_ws_proxy) + assert "receive_bytes" not in source, ( + "client_to_ttyd must not use receive_bytes() — silently drops text frames " + 'like the ttyd auth token {"AuthToken": ""}' + ) + assert ".receive()" in source, ( + "client_to_ttyd must use receive() to handle both text and binary frames" + )