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.

Revisions

  1. dmjio created this gist Aug 19, 2025.
    554 changes: 554 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,554 @@
    <!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>