Created
          August 19, 2025 17:37 
        
      - 
      
 - 
        
Save dmjio/73f8a974b37c85ef340b4beffecc17ed to your computer and use it in GitHub Desktop.  
    Web RTC example
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | <!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