rustrtc 0.3.49

A high-performance implementation of WebRTC
Documentation
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>RustRTC Chat</title>
    <style>
        body {
            font-family: sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }

        #chat-box {
            height: 300px;
            border: 1px solid #ccc;
            overflow-y: scroll;
            padding: 10px;
            margin-bottom: 10px;
        }

        .message {
            margin-bottom: 5px;
        }

        .sent {
            color: blue;
        }

        .received {
            color: green;
        }
    </style>
</head>

<body>
    <h1>RustRTC DataChannel Chat</h1>
    <div id="status">Status: Disconnected</div>
    <div id="chat-box"></div>
    <input type="text" id="name-input" placeholder="Enter your name">
    <input type="text" id="msg-input" placeholder="Type a message..." disabled>
    <button id="send-btn" disabled>Send</button>
    <button id="connect-btn">Connect</button>
    <button id="disconnect-btn" disabled>Disconnect</button>

    <script>
        const statusDiv = document.getElementById('status');
        const chatBox = document.getElementById('chat-box');
        const nameInput = document.getElementById('name-input');
        const msgInput = document.getElementById('msg-input');
        const sendBtn = document.getElementById('send-btn');
        const connectBtn = document.getElementById('connect-btn');
        const disconnectBtn = document.getElementById('disconnect-btn');

        let pc = null;
        let dc = null;

        function log(msg, type = 'info') {
            const div = document.createElement('div');
            div.className = 'message ' + type;
            div.textContent = msg;
            chatBox.appendChild(div);
            chatBox.scrollTop = chatBox.scrollHeight;
        }

        connectBtn.onclick = async () => {
            const name = nameInput.value.trim();
            if (!name) {
                alert("Please enter a name");
                return;
            }

            connectBtn.disabled = true;
            nameInput.disabled = true;
            statusDiv.textContent = "Status: Connecting...";

            pc = new RTCPeerConnection();

            // Use negotiated data channel (ID=0) to match server behavior
            dc = pc.createDataChannel("chat", { negotiated: true, id: 0 });
            setupDataChannel(dc, name);

            pc.onicecandidate = e => {
                if (e.candidate) {
                    console.log("New candidate", e.candidate);
                }
            };

            const offer = await pc.createOffer();
            await pc.setLocalDescription(offer);

            // Wait for ICE gathering to complete (simple approach)
            await new Promise(resolve => {
                if (pc.iceGatheringState === 'complete') {
                    resolve();
                } else {
                    const checkIce = () => {
                        if (pc.iceGatheringState === 'complete') {
                            pc.removeEventListener('icegatheringstatechange', checkIce);
                            resolve();
                        }
                    };
                    pc.addEventListener('icegatheringstatechange', checkIce);
                }
            });

            const response = await fetch('/offer', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ sdp: pc.localDescription.sdp })
            });

            const answer = await response.json();
            await pc.setRemoteDescription(new RTCSessionDescription({ type: 'answer', sdp: answer.sdp }));
        };

        disconnectBtn.onclick = () => {
            if (pc) {
                pc.close();
                pc = null;
            }
            if (dc) {
                dc.close();
                dc = null;
            }
            statusDiv.textContent = "Status: Disconnected";
            msgInput.disabled = true;
            sendBtn.disabled = true;
            connectBtn.disabled = false;
            disconnectBtn.disabled = true;
            nameInput.disabled = false;
            log("Disconnected", "info");
        };

        function setupDataChannel(channel, name) {
            channel.onopen = () => {
                statusDiv.textContent = "Status: Connected";
                msgInput.disabled = false;
                sendBtn.disabled = false;
                disconnectBtn.disabled = false;
                log("Connected as " + name, "info");

                // Send login message
                channel.send(JSON.stringify({ type: "login", name: name }));
            };
            channel.onmessage = e => {
                try {
                    const msg = JSON.parse(e.data);
                    if (msg.type === 'chat') {
                        log(msg.sender + ": " + msg.content, "received");
                    } else if (msg.type === 'system') {
                        log("[System]: " + msg.content, "info");
                    }
                } catch (err) {
                    log("Raw: " + e.data, "received");
                }
            };
            channel.onclose = () => {
                statusDiv.textContent = "Status: Disconnected";
                msgInput.disabled = true;
                sendBtn.disabled = true;
                connectBtn.disabled = false;
                disconnectBtn.disabled = true;
                nameInput.disabled = false;
                log("DataChannel Closed", "info");
            };
        }

        sendBtn.onclick = () => {
            const msg = msgInput.value;
            if (msg && dc && dc.readyState === 'open') {
                const payload = JSON.stringify({ type: "chat", content: msg });
                dc.send(payload);
                log("Me: " + msg, "sent");
                msgInput.value = '';
            }
        };

        msgInput.onkeypress = (e) => {
            if (e.key === 'Enter') sendBtn.click();
        };
    </script>
</body>

</html>