Download Game! Currently 85 players and visitors. Last logged in:AnakinElwinesMutaPanthos

Blitzer's Blog >> 72128

Back to blogs index
Posted: 06 Sep 2026 04:46 [ permalink ]
Using Termux to SSH port-forward the CDP socket directly to your phone is an
absolutely brilliant architecture. You have essentially created an encrypted
tunnel straight into your Linux devbox!
The reason it is failing with an empty {} error, despite your telnet proving
the port is open, is CORS (Cross-Origin Resource Sharing).
Because your web utility is hosted at http://Elli:33370, Android Chrome's
security model strictly prohibits it from making an HTTP fetch() request to
127.0.0.1 (localhost). The browser silently blocks the request, throwing a
TypeError: Failed to fetch. (Our REPL printed it as {} because standard
JavaScript Error objects lose their properties when passed through
JSON.stringify).
The Workaround: Bypass HTTP and use WebSockets directly
While browsers block cross-origin HTTP fetch requests to localhost, they do
not strictly block WebSocketsprovided the remote Chromium was launched with
the --remote-allow-origins="*" flag to accept the connection.
Instead of making the REPL fetch the list of tabs, we can use Termux to grab
the specific WebSocket URL for the tab, and feed it directly into our REPL.
Here is the updated class that accepts a direct WebSocket URL.
1. Update the Class in your REPL
Copy and paste this refined version into your REPL (you can overwrite the old
one):
class RemoteBrowserViewer {
  constructor() {
    this.ws = null;
    this.msgId = 1;
    this.callbacks = new Map();
    this.displayId = 'remote-linux';
    
    // Create or reuse the image element
    if (!DisplayManager.displays.has(this.displayId)) {
      this.imgEl = document.createElement('img');
      this.imgEl.style.width = '100%';
      this.imgEl.style.height = 'auto';
      this.imgEl.style.display = 'block';
      
      const container = document.createElement('div');
      container.style.backgroundColor = '#222';
      container.appendChild(this.imgEl);
      DisplayManager.add(this.displayId, container);
    } else {
      this.imgEl = DisplayManager.displays.get(this.displayId).querySelector('i
mg');
    }
  }

  // Accept the WebSocket URL directly!
  connect(wsUrl) {
    console.info(`Connecting to WS...`);
    try {
      this.ws = new WebSocket(wsUrl);
      
      this.ws.onmessage = (event) => {
        const data = JSON.parse(event.data);
        if (data.id && this.callbacks.has(data.id)) {
          this.callbacks.get(data.id)(data.result);
          this.callbacks.delete(data.id);
        }
      };

      this.ws.onopen = () => {
        console.info("CDP WebSocket Connected!");
        this.startScreenStream();
      };

      this.ws.onerror = () => {
        console.error("WebSocket Error! Did you start Linux Chromium with
--remote-allow-origins=\"*\" ?");
      };
      
    } catch (err) {
      console.error("Connection failed:", err.message);
    }
  }

  sendCommand(method, params = {}) {
    return new Promise((resolve) => {
      const id = this.msgId++;
      this.callbacks.set(id, resolve);
      this.ws.send(JSON.stringify({ id, method, params }));
    });
  }

  async startScreenStream() {
    const fetchFrame = async () => {
      if (this.ws.readyState !== WebSocket.OPEN) return;
      
      const result = await this.sendCommand('Page.captureScreenshot', { 
        format: 'jpeg', 
        quality: 40 // Lowered slightly for faster mobile tunneling
      });
      
      if (result && result.data) {
        this.imgEl.src = `data:image/jpeg;base64,${result.data}`;
      }
      
      setTimeout(fetchFrame, 150); 
    };
    fetchFrame();
  }
}

2. Get the WS URL via Termux
Keep your SSH tunnel running. Open a second Termux session/tab (or put the SSH
process in the background), and run this curl command to ask the local port
for the tabs:
curl -s http://127.0.0.1:9222/json/list | grep webSocketDebuggerUrl

It will output something like this:
"webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/page/A1B2C3D4E5F6..."
Copy that full ws://... string.
3. Connect via the REPL
Now, go back to Android Chrome and type this into your REPL:
window.remote = new RemoteBrowserViewer();

/ Paste the string you got from Termux here:
window.remote.connect('ws://127.0.0.1:9222/devtools/page/YOUR_UNIQUE_ID_HERE');

If it successfully prints [info] CDP WebSocket Connected!, you are in! Just
execute DisplayManager.switchTo('remote-linux') and you should see the live UI
of your Linux automation session tunneling straight through Termux onto your
mobile screen.