<!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();
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);
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");
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>