ttyd icon indicating copy to clipboard operation
ttyd copied to clipboard

Implement own client using ttyd only on server side

Open sreisich opened this issue 1 year ago • 5 comments

Is there a way to implement ttyd's client side in my own application? I mean, implement xterm.js in my own application that connects to the websocket provided by ttyd? What would the vanilla js code look like?

If this is technically possible, I would really like to sponsor this project. Please let me know what you think.

sreisich avatar Sep 26 '24 15:09 sreisich

Sure, as long as you implement ttyd's websocket protocol, it's pretty simple.

https://github.com/tsl0922/ttyd/blob/40e79c706be14029b391f369bee6613c31667abb/html/src/components/terminal/xterm/index.ts#L25-L36

tsl0922 avatar Oct 05 '24 02:10 tsl0922

Glad to hear that, thanks. If you like, I would like to sponsor a fully working vanilla javascript example. Would you be open to such an offer? I mean, I'm not a javascript pro and you know how to do it... I would like to sponsor an example index.html with vanilla js showing how an external page could connect to the ttyd socket. If you like, you can include this example here in the project, or anywhere, as open source code. Others might be interested in it too. If you are interested, let me know how I can contact you privately to talk about sponsoring...

sreisich avatar Oct 05 '24 10:10 sreisich

Why not just copy the code in this project?

If you really want a vanilla javascript example, I would recommend you to take a look at xterm.js's demo: https://github.com/xtermjs/xterm.js/tree/master/demo

tsl0922 avatar Oct 16 '24 10:10 tsl0922

Sure, as long as you implement ttyd's websocket protocol, it's pretty simple.

ttyd/html/src/components/terminal/xterm/index.ts

Lines 25 to 36 in 40e79c7

enum Command { // server side OUTPUT = '0', SET_WINDOW_TITLE = '1', SET_PREFERENCES = '2', // client side INPUT = '0', RESIZE_TERMINAL = '1', PAUSE = '2', RESUME = '3', }

There is a working example of that? I tried to implement ttyd's protocol but coud not get it working:


const WebSocket = require('ws');
const ws = new WebSocket("ws://127.0.0.1:8003");
ws.binaryType = "arraybuffer"; // Expect binary data

ws.onopen = () => {
    console.log("✅ Connected to ttyd!");

    setTimeout(() => {
        if (ws.readyState === WebSocket.OPEN) {
            console.log("📤 Sending command: ls");
            ws.send(new Uint8Array([0x00, ...new TextEncoder().encode("ls\n")]));
        } else {
            console.warn("⚠️ WebSocket not open. Cannot send data.");
        }
    }, 500); // Wait a little before sending
};

ws.onmessage = (event) => {
    console.log("📥 Raw Data Received:", event.data); // Debug raw data

    const data = new Uint8Array(event.data);
    if (data.length === 0) {
        console.warn("⚠️ Received empty message!");
        return;
    }

    const type = data[0]; // Extract message type
    console.log("📥 Message Type:", type);

    switch (type) {
        case 0x01: // Terminal output
            console.log("📥 Terminal Output:\n" + new TextDecoder().decode(data.slice(1)));
            break;
        case 0x05: // Window title change
            console.log("🖥️ Window Title Changed:", new TextDecoder().decode(data.slice(1)));
            break;
        default:
            console.warn("⚠️ Unknown message type:", type);
    }
};

ws.onerror = (error) => {
    console.error("❌ WebSocket Error:", error);
};

ws.onclose = (event) => {
    console.log(`🔌 Connection closed (Code: ${event.code}, Reason: ${event.reason})`);
};

gabrield avatar Apr 01 '25 13:04 gabrield

const ws = new WebSocket("ws://127.0.0.1:8003", ["tty"]);

I debugged for a long time and found that the protocol must be set to tty.

saltbo avatar Aug 07 '25 08:08 saltbo