Skip to content

Instantly share code, notes, and snippets.

@dmjio
Created August 19, 2025 17:37
Show Gist options
  • Save dmjio/73f8a974b37c85ef340b4beffecc17ed to your computer and use it in GitHub Desktop.
Save dmjio/73f8a974b37c85ef340b4beffecc17ed to your computer and use it in GitHub Desktop.
Web RTC example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket API Example</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
color: #fff;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
@media (max-width: 768px) {
.container {
grid-template-columns: 1fr;
}
}
.panel {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.18);
}
h1, h2 {
text-align: center;
margin-bottom: 20px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.connection-status {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
padding: 10px;
border-radius: 8px;
font-weight: bold;
}
.status-connected {
background: rgba(76, 175, 80, 0.3);
border: 1px solid #4CAF50;
}
.status-disconnected {
background: rgba(244, 67, 54, 0.3);
border: 1px solid #F44336;
}
.status-connecting {
background: rgba(255, 193, 7, 0.3);
border: 1px solid #FFC107;
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 10px;
}
.connected {
background: #4CAF50;
box-shadow: 0 0 8px #4CAF50;
}
.disconnected {
background: #F44336;
box-shadow: 0 0 8px #F44336;
}
.connecting {
background: #FFC107;
box-shadow: 0 0 8px #FFC107;
}
.controls {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
button {
padding: 10px 15px;
border: none;
border-radius: 6px;
background: rgba(41, 128, 185, 0.8);
color: white;
cursor: pointer;
font-weight: bold;
transition: all 0.3s ease;
}
button:hover {
background: rgba(52, 152, 219, 1);
transform: translateY(-2px);
}
button:disabled {
background: rgba(149, 165, 166, 0.5);
cursor: not-allowed;
transform: none;
}
.data-type-selector {
margin-bottom: 20px;
}
select, input {
width: 100%;
padding: 10px;
margin-bottom: 10px;
border-radius: 6px;
border: 1px solid rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.1);
color: white;
}
select option {
background: rgba(0, 0, 0, 0.8);
}
.log {
height: 300px;
overflow-y: auto;
background: rgba(0, 0, 0, 0.3);
padding: 15px;
border-radius: 8px;
font-family: monospace;
font-size: 14px;
}
.log-entry {
margin-bottom: 8px;
padding: 8px;
border-radius: 4px;
border-left: 4px solid;
}
.log-sent {
background: rgba(41, 128, 185, 0.2);
border-color: #2980b9;
}
.log-received {
background: rgba(46, 204, 113, 0.2);
border-color: #2ecc71;
}
.log-system {
background: rgba(241, 196, 15, 0.2);
border-color: #f1c40f;
}
.log-error {
background: rgba(231, 76, 60, 0.2);
border-color: #e74c3c;
}
.message-form {
display: flex;
flex-direction: column;
gap: 10px;
}
.binary-controls {
display: flex;
gap: 10px;
margin-top: 10px;
}
.binary-controls button {
flex: 1;
}
.ping-info {
display: flex;
justify-content: space-between;
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid rgba(255, 255, 255, 0.2);
}
.blink {
animation: blink 1s infinite;
}
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
</style>
</head>
<body>
<div class="container">
<div class="panel">
<h1>WebSocket API Example</h1>
<div id="connectionStatus" class="connection-status status-disconnected">
<div class="status-indicator disconnected"></div>
<span>Disconnected</span>
</div>
<div class="controls">
<button id="connectBtn">Connect</button>
<button id="disconnectBtn" disabled>Disconnect</button>
<button id="reconnectBtn" disabled>Reconnect</button>
</div>
<div class="data-type-selector">
<h2>Send Data</h2>
<select id="dataType">
<option value="text">Text</option>
<option value="json">JSON</option>
<option value="arraybuffer">ArrayBuffer</option>
<option value="blob">Blob</option>
</select>
<div id="textInput">
<input type="text" id="messageInput" placeholder="Enter text message">
<button id="sendBtn" disabled>Send Message</button>
</div>
<div id="jsonInput" style="display: none;">
<input type="text" id="jsonKey" placeholder="Key">
<input type="text" id="jsonValue" placeholder="Value">
<button id="sendJsonBtn" disabled>Send JSON</button>
</div>
<div id="binaryInput" style="display: none;">
<div class="binary-controls">
<button id="sendArrayBufferBtn" disabled>Send ArrayBuffer</button>
<button id="sendBlobBtn" disabled>Send Blob</button>
</div>
</div>
</div>
<div class="ping-info">
<div>Ping: <span id="pingValue">-</span> ms</div>
<button id="pingBtn" disabled>Test Ping</button>
</div>
</div>
<div class="panel">
<h2>Communication Log</h2>
<div id="log" class="log"></div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Elements
const connectBtn = document.getElementById('connectBtn');
const disconnectBtn = document.getElementById('disconnectBtn');
const reconnectBtn = document.getElementById('reconnectBtn');
const sendBtn = document.getElementById('sendBtn');
const sendJsonBtn = document.getElementById('sendJsonBtn');
const sendArrayBufferBtn = document.getElementById('sendArrayBufferBtn');
const sendBlobBtn = document.getElementById('sendBlobBtn');
const pingBtn = document.getElementById('pingBtn');
const connectionStatus = document.getElementById('connectionStatus');
const statusIndicator = connectionStatus.querySelector('.status-indicator');
const statusText = connectionStatus.querySelector('span');
const log = document.getElementById('log');
const dataType = document.getElementById('dataType');
const messageInput = document.getElementById('messageInput');
const jsonKey = document.getElementById('jsonKey');
const jsonValue = document.getElementById('jsonValue');
const pingValue = document.getElementById('pingValue');
// Input containers
const textInput = document.getElementById('textInput');
const jsonInput = document.getElementById('jsonInput');
const binaryInput = document.getElementById('binaryInput');
// WebSocket variables
let socket = null;
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
let pingInterval = null;
let lastPingTime = 0;
// WebSocket server URL (using a public test server)
const serverUrl = 'wss://echo.websocket.org';
// Update UI based on connection state
function updateUIState(connected) {
connectBtn.disabled = connected;
disconnectBtn.disabled = !connected;
reconnectBtn.disabled = !connected;
sendBtn.disabled = !connected;
sendJsonBtn.disabled = !connected;
sendArrayBufferBtn.disabled = !connected;
sendBlobBtn.disabled = !connected;
pingBtn.disabled = !connected;
if (connected) {
connectionStatus.className = 'connection-status status-connected';
statusIndicator.className = 'status-indicator connected';
statusText.textContent = 'Connected';
} else {
connectionStatus.className = 'connection-status status-disconnected';
statusIndicator.className = 'status-indicator disconnected';
statusText.textContent = 'Disconnected';
pingValue.textContent = '-';
}
}
// Show connection in progress
function showConnecting() {
connectionStatus.className = 'connection-status status-connecting';
statusIndicator.className = 'status-indicator connecting blink';
statusText.textContent = 'Connecting...';
connectBtn.disabled = true;
}
// Add entry to log
function addLogEntry(message, type) {
const entry = document.createElement('div');
entry.className = `log-entry log-${type}`;
entry.innerHTML = `<strong>${new Date().toLocaleTimeString()}</strong>: ${message}`;
log.appendChild(entry);
log.scrollTop = log.scrollHeight;
}
// Connect to WebSocket server
function connect() {
if (socket && socket.readyState === WebSocket.OPEN) {
addLogEntry('Already connected to server', 'system');
return;
}
showConnecting();
addLogEntry(`Connecting to ${serverUrl}`, 'system');
try {
socket = new WebSocket(serverUrl);
socket.addEventListener('open', function(event) {
reconnectAttempts = 0;
updateUIState(true);
addLogEntry('Connection established successfully', 'system');
// Start ping interval
pingInterval = setInterval(() => {
if (socket.readyState === WebSocket.OPEN) {
lastPingTime = Date.now();
socket.send(JSON.stringify({ type: 'ping', timestamp: lastPingTime }));
}
}, 30000);
});
socket.addEventListener('message', function(event) {
// Handle different data types
if (event.data instanceof ArrayBuffer) {
addLogEntry(`Received ArrayBuffer: ${new Uint8Array(event.data).join(',')}`, 'received');
} else if (event.data instanceof Blob) {
// Convert Blob to text for display
const reader = new FileReader();
reader.onload = function() {
addLogEntry(`Received Blob: ${reader.result}`, 'received');
};
reader.readAsText(event.data);
} else {
// Try to parse as JSON, otherwise display as text
try {
const data = JSON.parse(event.data);
if (data.type === 'pong') {
const ping = Date.now() - data.timestamp;
pingValue.textContent = ping;
return;
}
addLogEntry(`Received JSON: ${event.data}`, 'received');
} catch (e) {
addLogEntry(`Received text: ${event.data}`, 'received');
}
}
});
socket.addEventListener('close', function(event) {
updateUIState(false);
clearInterval(pingInterval);
if (event.wasClean) {
addLogEntry(`Connection closed cleanly, code=${event.code}, reason=${event.reason}`, 'system');
} else {
addLogEntry('Connection abruptly closed', 'error');
// Attempt to reconnect
attemptReconnect();
}
});
socket.addEventListener('error', function(error) {
addLogEntry('WebSocket error occurred', 'error');
console.error('WebSocket error:', error);
});
} catch (error) {
addLogEntry(`Failed to connect: ${error.message}`, 'error');
updateUIState(false);
}
}
// Attempt to reconnect with exponential backoff
function attemptReconnect() {
if (reconnectAttempts >= maxReconnectAttempts) {
addLogEntry('Max reconnection attempts reached', 'error');
return;
}
const delay = Math.pow(2, reconnectAttempts) * 1000;
reconnectAttempts++;
addLogEntry(`Attempting to reconnect in ${delay/1000} seconds (attempt ${reconnectAttempts}/${maxReconnectAttempts})`, 'system');
setTimeout(() => {
if (socket && socket.readyState !== WebSocket.OPEN) {
connect();
}
}, delay);
}
// Disconnect from server
function disconnect() {
if (socket) {
socket.close(1000, 'User disconnected');
addLogEntry('Disconnected from server', 'system');
}
}
// Send message based on selected data type
function sendMessage() {
if (!socket || socket.readyState !== WebSocket.OPEN) {
addLogEntry('Not connected to server', 'error');
return;
}
const type = dataType.value;
try {
switch (type) {
case 'text':
const text = messageInput.value;
if (text) {
socket.send(text);
addLogEntry(`Sent text: ${text}`, 'sent');
messageInput.value = '';
}
break;
case 'json':
const key = jsonKey.value;
const value = jsonValue.value;
if (key && value) {
const json = JSON.stringify({ [key]: value });
socket.send(json);
addLogEntry(`Sent JSON: ${json}`, 'sent');
jsonKey.value = '';
jsonValue.value = '';
}
break;
case 'arraybuffer':
// Create a simple array buffer with sample data
const buffer = new ArrayBuffer(8);
const view = new Int8Array(buffer);
for (let i = 0; i < 8; i++) view[i] = i;
socket.send(buffer);
addLogEntry(`Sent ArrayBuffer: ${view.join(',')}`, 'sent');
break;
case 'blob':
// Create a simple blob with sample data
const blob = new Blob(['Hello from WebSocket Blob!'], { type: 'text/plain' });
socket.send(blob);
addLogEntry('Sent Blob with text content', 'sent');
break;
}
} catch (error) {
addLogEntry(`Error sending data: ${error.message}`, 'error');
}
}
// Test ping to server
function testPing() {
if (!socket || socket.readyState !== WebSocket.OPEN) {
addLogEntry('Not connected to server', 'error');
return;
}
lastPingTime = Date.now();
socket.send(JSON.stringify({ type: 'ping', timestamp: lastPingTime }));
pingValue.textContent = 'testing...';
}
// Handle data type selection change
dataType.addEventListener('change', function() {
textInput.style.display = this.value === 'text' ? 'block' : 'none';
jsonInput.style.display = this.value === 'json' ? 'block' : 'none';
binaryInput.style.display = (this.value === 'arraybuffer' || this.value === 'blob') ? 'block' : 'none';
});
// Event listeners
connectBtn.addEventListener('click', connect);
disconnectBtn.addEventListener('click', disconnect);
reconnectBtn.addEventListener('click', connect);
sendBtn.addEventListener('click', sendMessage);
sendJsonBtn.addEventListener('click', sendMessage);
sendArrayBufferBtn.addEventListener('click', sendMessage);
sendBlobBtn.addEventListener('click', sendMessage);
pingBtn.addEventListener('click', testPing);
// Allow sending message with Enter key
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') sendMessage();
});
jsonKey.addEventListener('keypress', function(e) {
if (e.key === 'Enter') sendMessage();
});
jsonValue.addEventListener('keypress', function(e) {
if (e.key === 'Enter') sendMessage();
});
// Initial UI state
updateUIState(false);
addLogEntry('WebSocket client ready. Click Connect to start.', 'system');
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment