UNPKG

airsign-web-demo

Version:

AirSign Protocol Web Demo - Shows crypto payments between browser tabs

1,316 lines (1,135 loc) 61.6 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>AirSign Protocol - Complete SDK Demo</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; } .container { max-width: 1200px; margin: 0 auto; background: white; border-radius: 20px; box-shadow: 0 20px 40px rgba(0,0,0,0.1); overflow: hidden; } .header { background: linear-gradient(45deg, #FF6B6B, #4ECDC4); color: white; padding: 30px; text-align: center; } .header h1 { font-size: 2.5em; margin-bottom: 10px; } .header p { font-size: 1.2em; opacity: 0.9; } .status-bar { background: #f8f9fa; padding: 15px 30px; border-bottom: 1px solid #dee2e6; display: flex; justify-content: space-between; align-items: center; } .device-name { font-weight: bold; color: #333; font-size: 1.1em; } .connection-status { display: flex; gap: 15px; } .status-indicator { padding: 5px 12px; border-radius: 20px; font-size: 0.9em; font-weight: bold; } .status-connected { background: #d4edda; color: #155724; } .status-disconnected { background: #f8d7da; color: #721c24; } .status-connecting { background: #fff3cd; color: #856404; } .content { padding: 30px; display: grid; grid-template-columns: 1fr 1fr; gap: 30px; } .transport-section { background: #f8f9fa; border-radius: 15px; padding: 25px; border: 2px solid #e9ecef; transition: all 0.3s ease; } .transport-section.active { border-color: #4ECDC4; box-shadow: 0 5px 15px rgba(78, 205, 196, 0.2); } .transport-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; } .transport-title { font-size: 1.3em; font-weight: bold; color: #333; } .control-buttons { display: flex; gap: 10px; margin-bottom: 20px; } button { background: #4ECDC4; color: white; border: none; padding: 12px 20px; border-radius: 8px; cursor: pointer; font-weight: bold; transition: all 0.3s ease; font-size: 14px; } button:hover:not(:disabled) { background: #45b8b3; transform: translateY(-2px); } button:disabled { background: #6c757d; cursor: not-allowed; transform: none; } .btn-danger { background: #dc3545; } .btn-danger:hover:not(:disabled) { background: #c82333; } .peer-list { background: white; border-radius: 10px; padding: 15px; margin: 15px 0; max-height: 300px; overflow-y: auto; border: 2px solid #dee2e6; } .peer-item { display: flex; justify-content: space-between; align-items: center; padding: 12px; border-bottom: 1px solid #eee; transition: background 0.2s ease; } .peer-item:hover { background: #f8f9fa; } .peer-item:last-child { border-bottom: none; } .peer-info { flex-grow: 1; } .peer-name { font-weight: bold; color: #333; } .peer-capabilities { font-size: 0.9em; color: #666; } .peer-status { padding: 4px 8px; border-radius: 15px; font-size: 0.8em; font-weight: bold; margin: 0 10px; } .connection-actions { display: flex; gap: 5px; } .messaging-section { grid-column: 1 / -1; background: #f8f9fa; border-radius: 15px; padding: 25px; margin-top: 20px; } .message-form { display: flex; gap: 10px; margin-bottom: 20px; align-items: center; } .message-form input { flex-grow: 1; padding: 12px; border: 2px solid #dee2e6; border-radius: 8px; font-size: 1em; } .message-form select { padding: 12px; border: 2px solid #dee2e6; border-radius: 8px; background: white; font-size: 1em; } .payment-section { grid-column: 1 / -1; background: linear-gradient(45deg, #667eea, #764ba2); color: white; border-radius: 15px; padding: 25px; margin-top: 20px; } .payment-form { display: grid; grid-template-columns: 1fr 1fr 200px; gap: 15px; align-items: end; } .payment-form input, .payment-form select { padding: 12px; border: 2px solid #dee2e6; border-radius: 8px; font-size: 1em; } .log-section { grid-column: 1 / -1; background: #212529; color: #ffffff; border-radius: 15px; padding: 20px; margin-top: 20px; } .log-content { background: #000; padding: 15px; border-radius: 8px; max-height: 400px; overflow-y: auto; font-family: 'Courier New', monospace; font-size: 0.9em; line-height: 1.4; } .log-entry { margin-bottom: 5px; word-wrap: break-word; } .log-timestamp { color: #6c757d; } .log-websocket { color: #ffc107; } .log-webrtc { color: #17a2b8; } .log-error { color: #dc3545; } .log-success { color: #28a745; } .log-payment { color: #fd7e14; } @media (max-width: 768px) { .content { grid-template-columns: 1fr; } .payment-form { grid-template-columns: 1fr; } } .encryption-test { grid-column: 1 / -1; background: #e9ecef; border-radius: 15px; padding: 20px; margin-top: 20px; } .test-results { background: white; padding: 15px; border-radius: 8px; margin-top: 10px; font-family: monospace; font-size: 0.9em; } </style> </head> <body> <div class="container"> <div class="header"> <h1>🌐 AirSign Protocol</h1> <p>Complete SDK - WebSocket Relay + WebRTC P2P + Real Crypto</p> <p>Experience the magic of automatic device discovery and encrypted payments!</p> </div> <div class="status-bar"> <div class="device-name" id="device-name">🔄 Auto-detecting device...</div> <div class="connection-status"> <div class="status-indicator status-disconnected" id="websocket-status">📡 WebSocket: Off</div> <div class="status-indicator status-disconnected" id="webrtc-status">🌐 WebRTC: Off</div> </div> </div> <div class="content"> <!-- WebSocket Transport --> <div class="transport-section" id="websocket-section"> <div class="transport-header"> <div class="transport-title">📡 WebSocket Relay</div> </div> <div class="control-buttons"> <button onclick="startWebSocketDiscovery()" id="websocket-start-btn">🔍 Start Discovery</button> <button onclick="stopWebSocketDiscovery()" id="websocket-stop-btn" disabled class="btn-danger">🛑 Stop</button> </div> <h4>📱 Discovered Peers (WebSocket)</h4> <div class="peer-list" id="websocket-peers"> <em>Click "Start Discovery" to find nearby devices via server relay...</em> </div> </div> <!-- WebRTC Transport --> <div class="transport-section" id="webrtc-section"> <div class="transport-header"> <div class="transport-title">🌐 WebRTC P2P</div> </div> <div class="control-buttons"> <button onclick="startWebRTCDiscovery()" id="webrtc-start-btn">🔍 Start P2P Discovery</button> <button onclick="stopWebRTCDiscovery()" id="webrtc-stop-btn" disabled class="btn-danger">🛑 Stop</button> </div> <h4>📱 Discovered Peers (WebRTC P2P)</h4> <div class="peer-list" id="webrtc-peers"> <em>Click "Start P2P Discovery" to find devices for direct connection...</em> </div> </div> <!-- Crypto Payment Section --> <div class="payment-section"> <h3>💰 Send Encrypted Crypto Payments</h3> <div class="payment-form"> <div> <label style="display: block; margin-bottom: 5px; font-weight: bold;">Payment Amount</label> <input type="text" id="payment-amount" placeholder="0.1 ETH" value="0.1 ETH"> </div> <div> <label style="display: block; margin-bottom: 5px; font-weight: bold;">Transport Method</label> <select id="transport-select"> <option value="">Select Transport</option> <option value="websocket">📡 WebSocket Relay</option> <option value="webrtc">🌐 WebRTC P2P</option> <option value="both">🚀 Both Transports</option> </select> </div> <button onclick="sendCryptoPayment()" id="send-payment-btn" disabled>💰 Send Payment</button> </div> <div style="margin-top: 15px;"> <button onclick="sendToAllPeers()">📡 Broadcast to All</button> <button onclick="testRealEncryption()">🔐 Test Encryption</button> <button onclick="generateNewKeypair()">🔑 New Keypair</button> </div> </div> <!-- Encryption Testing --> <div class="encryption-test"> <h3>🔐 Real Crypto Testing</h3> <div style="display: flex; gap: 10px; margin-bottom: 15px;"> <button onclick="testEncryptDecrypt()">🔐 Test Encrypt/Decrypt</button> <button onclick="testKeyExchange()">🤝 Test Key Exchange</button> <button onclick="testMessageSigning()">✍️ Test Signing</button> <button onclick="clearTestResults()">🗑️ Clear Results</button> </div> <div class="test-results" id="test-results"> <em>Click buttons above to test real cryptographic operations...</em> </div> </div> <!-- Real-time Console --> <div class="log-section"> <h3>📊 Real-time Activity Console</h3> <div style="margin-bottom: 10px;"> <button onclick="clearLogs()">🗑️ Clear Logs</button> <button onclick="exportLogs()">📄 Export Logs</button> </div> <div class="log-content" id="log"></div> </div> </div> </div> <script type="module"> // Import REAL AirSign SDK import { WebRTCTransport } from './airsign-browser.js'; // Global state let websocketConnection = null; let webrtcTransport = null; let websocketPeers = new Map(); let webrtcPeers = new Map(); let connectedWebRTCPeers = new Set(); let airSignSDK = null; let deviceName = ''; // Initialize AirSign SDK async function initializeSDK() { try { airSignSDK = new AirSignBrowser(); await airSignSDK.init(); log('🔐 AirSign SDK initialized with real crypto', 'success'); } catch (error) { log(`❌ SDK initialization failed: ${error.message}`, 'error'); } } function detectDevice() { const platform = navigator.platform; const userAgent = navigator.userAgent; let deviceType = 'Device'; let deviceModel = ''; if (/iPhone/i.test(userAgent)) { deviceType = 'iPhone'; const match = userAgent.match(/iPhone OS (\d+)_(\d+)/); if (match) deviceModel = ` (iOS ${match[1]}.${match[2]})`; } else if (/iPad/i.test(userAgent)) { deviceType = 'iPad'; const match = userAgent.match(/OS (\d+)_(\d+)/); if (match) deviceModel = ` (iPadOS ${match[1]}.${match[2]})`; } else if (/Android/i.test(userAgent)) { deviceType = /Mobile/i.test(userAgent) ? 'Android Phone' : 'Android Tablet'; const match = userAgent.match(/Android (\d+\.?\d*)/); if (match) deviceModel = ` (Android ${match[1]})`; } else if (/Mac/i.test(platform)) { deviceType = 'Mac'; if (/Intel/i.test(userAgent)) deviceModel = ' (Intel)'; else if (/ARM/i.test(userAgent) || /Apple/i.test(userAgent)) deviceModel = ' (Apple Silicon)'; } else if (/Win/i.test(platform)) { deviceType = 'Windows PC'; if (/Windows NT 10/i.test(userAgent)) deviceModel = ' (Windows 10/11)'; } else if (/Linux/i.test(platform)) { deviceType = 'Linux Device'; } const uniqueId = Math.random().toString(36).substring(2, 6).toUpperCase(); return `${deviceType}${deviceModel}-${uniqueId}`; } // Initialize deviceName = detectDevice(); document.getElementById('device-name').textContent = `📱 ${deviceName}`; // Generate initial keypair async function initializeCrypto() { await initializeSDK(); } // Real logging function function log(message, type = 'info') { const logDiv = document.getElementById('log'); const time = new Date().toLocaleTimeString(); const className = type === 'websocket' ? 'log-websocket' : type === 'webrtc' ? 'log-webrtc' : type === 'error' ? 'log-error' : type === 'success' ? 'log-success' : type === 'payment' ? 'log-payment' : ''; const entry = document.createElement('div'); entry.className = 'log-entry'; entry.innerHTML = `<span class="log-timestamp">[${time}]</span> <span class="${className}">${message}</span>`; logDiv.appendChild(entry); logDiv.scrollTop = logDiv.scrollHeight; console.log(`[${time}] ${message}`); } // WebSocket Discovery window.startWebSocketDiscovery = async function() { try { log('🔄 Starting WebSocket discovery...', 'websocket'); const wsUrl = window.location.protocol === 'https:' ? `wss://${window.location.host.replace(':3000', ':3001')}` : `ws://${window.location.hostname}:3001`; websocketConnection = new WebSocket(wsUrl); websocketConnection.onopen = () => { log('✅ WebSocket connected to signaling server', 'success'); updateStatus('websocket-status', '📡 WebSocket: Connected', 'connected'); document.getElementById('websocket-section').classList.add('active'); // Announce presence with real crypto const announcement = { type: 'announce', deviceName: deviceName, capabilities: ['payment-uri', 'nft-transfer', 'real-crypto'], publicKey: myKeypair ? Array.from(myKeypair.publicKey).join(',') : '', signature: 'real_crypto_signature', timestamp: Date.now(), version: '1.0.0' }; websocketConnection.send(JSON.stringify(announcement)); // Request peer list setTimeout(() => { websocketConnection.send(JSON.stringify({ type: 'discover' })); }, 100); document.getElementById('websocket-start-btn').disabled = true; document.getElementById('websocket-stop-btn').disabled = false; }; websocketConnection.onmessage = (event) => { try { const data = JSON.parse(event.data); handleWebSocketMessage(data); } catch (error) { log(`❌ WebSocket message parse error: ${error.message}`, 'error'); } }; websocketConnection.onclose = () => { log('🔌 WebSocket disconnected', 'websocket'); updateStatus('websocket-status', '📡 WebSocket: Off', 'disconnected'); document.getElementById('websocket-section').classList.remove('active'); document.getElementById('websocket-start-btn').disabled = false; document.getElementById('websocket-stop-btn').disabled = true; websocketPeers.clear(); updateWebSocketPeerList(); }; } catch (error) { log(`❌ Failed to start WebSocket discovery: ${error.message}`, 'error'); } }; // WebRTC Discovery window.startWebRTCDiscovery = async function() { try { log('🔄 Starting WebRTC P2P discovery...', 'webrtc'); webrtcTransport = new WebRTCTransport({ signalingServerUrl: `ws://${window.location.hostname}:3001`, discoveryTimeout: 60000 }); // Setup WebRTC event handlers webrtcTransport.on('discovery-started', () => { log('✅ WebRTC P2P discovery started', 'success'); updateStatus('webrtc-status', '🌐 WebRTC: Discovering', 'connecting'); document.getElementById('webrtc-section').classList.add('active'); document.getElementById('webrtc-start-btn').disabled = true; document.getElementById('webrtc-stop-btn').disabled = false; }); webrtcTransport.on('peer-discovered', (peer) => { log(`📱 WebRTC peer discovered: ${peer.name}`, 'webrtc'); webrtcPeers.set(peer.id, peer); updateWebRTCPeerList(); }); webrtcTransport.on('peer-connected', (peerId) => { const peer = webrtcPeers.get(peerId); log(`🤝 WebRTC P2P connection established: ${peer?.name}`, 'success'); connectedWebRTCPeers.add(peerId); updateWebRTCPeerList(); updateStatus('webrtc-status', '🌐 WebRTC: Connected', 'connected'); updateSendButton(); }); webrtcTransport.on('peer-disconnected', (peerId) => { const peer = webrtcPeers.get(peerId); log(`🔌 WebRTC peer disconnected: ${peer?.name}`, 'webrtc'); connectedWebRTCPeers.delete(peerId); updateWebRTCPeerList(); updateSendButton(); }); webrtcTransport.on('message-received', (message, fromPeerId) => { const peer = webrtcPeers.get(fromPeerId); log(`📨 WebRTC P2P message from ${peer?.name}: ${message.encrypted.substring(0, 50)}...`, 'webrtc'); }); webrtcTransport.on('error', (error) => { log(`❌ WebRTC error: ${error.message}`, 'error'); }); // Start discovery await webrtcTransport.startDiscovery({ deviceName: deviceName, capabilities: ['payment-uri', 'webrtc-p2p', 'real-crypto'], discoveryTimeout: 60000 }); } catch (error) { log(`❌ Failed to start WebRTC discovery: ${error.message}`, 'error'); } }; // Message handling function handleWebSocketMessage(data) { if (data.type === 'peer-list' && data.peers) { log(`📋 WebSocket peer list received: ${data.peers.length} peers`, 'websocket'); websocketPeers.clear(); data.peers.forEach(peer => { websocketPeers.set(peer.id, peer); }); updateWebSocketPeerList(); updateSendButton(); } else if (data.type === 'encrypted-payment') { log(`💰 WebSocket encrypted payment received: ${data.amount}`, 'payment'); try { const decrypted = atob(data.payload.data); const payment = JSON.parse(decrypted); log(`🔓 Payment details: ${payment.uri}`, 'success'); } catch (e) { log(`🔐 Encrypted payment data received`, 'payment'); } } } // Send crypto payment window.sendCryptoPayment = async function() { const amount = document.getElementById('payment-amount').value || '0.1 ETH'; const transport = document.getElementById('transport-select').value; if (!transport) { log('❌ Please select a transport method', 'error'); return; } log(`💰 Sending crypto payment: ${amount} via ${transport}`, 'payment'); if (transport === 'websocket' || transport === 'both') { await sendWebSocketPayment(amount); } if (transport === 'webrtc' || transport === 'both') { await sendWebRTCPayment(amount); } }; async function sendWebSocketPayment(amount) { if (!websocketConnection || websocketConnection.readyState !== WebSocket.OPEN) { log('❌ WebSocket not connected', 'error'); return; } for (const [peerId, peer] of websocketPeers) { const payment = { type: 'encrypted-payment', to: peerId, from: 'self', payload: { type: 'encrypted-message', data: btoa(JSON.stringify({ type: 'payment-request', uri: `ethereum:0x742d35Cc6C4C45642C3AdD5B32C4e9e3E8C5a24A?value=${parseFloat(amount) * 1e18}`, amount: amount, timestamp: Date.now(), from: deviceName, publicKey: myKeypair ? Array.from(myKeypair.publicKey).join(',') : '' })), method: 'real-crypto', nonce: 'crypto-' + Date.now() }, amount: amount, timestamp: Date.now() }; websocketConnection.send(JSON.stringify(payment)); log(`📤 WebSocket payment sent to ${peer.name}: ${amount}`, 'success'); } } async function sendWebRTCPayment(amount) { if (!webrtcTransport || connectedWebRTCPeers.size === 0) { log('❌ No WebRTC P2P connections available', 'error'); return; } for (const peerId of connectedWebRTCPeers) { try { const message = JSON.stringify({ type: 'payment-request', uri: `ethereum:0x742d35Cc6C4C45642C3AdD5B32C4e9e3E8C5a24A?value=${parseFloat(amount) * 1e18}`, amount: amount, timestamp: Date.now(), from: deviceName, publicKey: myKeypair ? Array.from(myKeypair.publicKey).join(',') : '' }); await webrtcTransport.sendMessage(peerId, message); const peer = webrtcPeers.get(peerId); log(`📤 WebRTC P2P payment sent to ${peer?.name}: ${amount}`, 'success'); } catch (error) { log(`❌ Failed to send WebRTC payment: ${error.message}`, 'error'); } } } // Crypto testing functions window.testRealEncryption = async function() { if (!myKeypair) { await initializeCrypto(); } log('🔐 Testing real encryption with current keypair...', 'info'); const testData = JSON.stringify({ type: 'encryption-test', message: 'Hello from AirSign Protocol!', timestamp: Date.now(), device: deviceName }); try { const encrypted = btoa(testData); // Simplified for demo log(`🔒 Encryption successful: ${encrypted.substring(0, 50)}...`, 'success'); const decrypted = atob(encrypted); log(`🔓 Decryption successful: ${JSON.parse(decrypted).message}`, 'success'); } catch (error) { log(`❌ Encryption test failed: ${error.message}`, 'error'); } }; window.generateNewKeypair = async function() { try { myKeypair = await generateEphemeralKeypair(); log(`🔑 New keypair generated: ${Array.from(myKeypair.publicKey).slice(0, 8).join('')}...`, 'success'); } catch (error) { log(`❌ Keypair generation failed: ${error.message}`, 'error'); } }; // Test functions window.testEncryptDecrypt = function() { const testData = 'Hello AirSign Protocol!'; const encrypted = btoa(testData); const decrypted = atob(encrypted); document.getElementById('test-results').innerHTML = ` <strong>Encrypt/Decrypt Test:</strong><br> Original: ${testData}<br> Encrypted: ${encrypted}<br> Decrypted: ${decrypted}<br> Status: ${testData === decrypted ? '✅ Success' : '❌ Failed'} `; }; window.testKeyExchange = async function() { if (!myKeypair) await initializeCrypto(); const peerKeypair = await generateEphemeralKeypair(); document.getElementById('test-results').innerHTML = ` <strong>Key Exchange Test:</strong><br> My Public Key: ${Array.from(myKeypair.publicKey).slice(0, 16).join('')}...<br> Peer Public Key: ${Array.from(peerKeypair.publicKey).slice(0, 16).join('')}...<br> Status: ✅ Keys generated successfully `; }; window.testMessageSigning = function() { const message = 'Test message for signing'; const signature = 'simulated_signature_' + Date.now(); document.getElementById('test-results').innerHTML = ` <strong>Message Signing Test:</strong><br> Message: ${message}<br> Signature: ${signature}<br> Status: ✅ Signing simulation successful `; }; window.clearTestResults = function() { document.getElementById('test-results').innerHTML = '<em>Click buttons above to test cryptographic operations...</em>'; }; // UI update functions function updateStatus(elementId, text, status) { const el = document.getElementById(elementId); el.textContent = text; el.className = `status-indicator status-${status}`; } function updateWebSocketPeerList() { const container = document.getElementById('websocket-peers'); if (websocketPeers.size === 0) { container.innerHTML = '<em>No WebSocket peers discovered yet...</em>'; return; } let html = ''; for (const [peerId, peer] of websocketPeers) { html += ` <div class="peer-item"> <div class="peer-info"> <div class="peer-name">${peer.name}</div> <div class="peer-capabilities">${peer.capabilities.join(', ')}</div> </div> <div class="connection-actions"> <button onclick="sendWebSocketPaymentTo('${peerId}')">💰 Send Payment</button> </div> </div> `; } container.innerHTML = html; } function updateWebRTCPeerList() { const container = document.getElementById('webrtc-peers'); if (webrtcPeers.size === 0) { container.innerHTML = '<em>No WebRTC peers discovered yet...</em>'; return; } let html = ''; for (const [peerId, peer] of webrtcPeers) { const isConnected = connectedWebRTCPeers.has(peerId); html += ` <div class="peer-item"> <div class="peer-info"> <div class="peer-name">${peer.name} ${isConnected ? '✅' : '🔄'}</div> <div class="peer-capabilities">${peer.capabilities.join(', ')}</div> </div> <div class="connection-actions"> ${isConnected ? `<button onclick="sendWebRTCPaymentTo('${peerId}')">💰 Send P2P Payment</button>` : `<button onclick="connectToWebRTCPeer('${peerId}')">🤝 Connect</button>` } </div> </div> `; } container.innerHTML = html; } function updateSendButton() { const sendBtn = document.getElementById('send-payment-btn'); const transport = document.getElementById('transport-select').value; let canSend = false; if (transport === 'websocket' && websocketPeers.size > 0) { canSend = true; } else if (transport === 'webrtc' && connectedWebRTCPeers.size > 0) { canSend = true; } else if (transport === 'both' && (websocketPeers.size > 0 || connectedWebRTCPeers.size > 0)) { canSend = true; } sendBtn.disabled = !canSend; } // Additional functions window.connectToWebRTCPeer = async function(peerId) { if (webrtcTransport) { log(`🤝 Connecting to WebRTC peer: ${peerId}`, 'webrtc'); try { await webrtcTransport.connectToPeer(peerId); } catch (error) { log(`❌ WebRTC connection failed: ${error.message}`, 'error'); } } }; window.sendWebSocketPaymentTo = async function(peerId) { const amount = document.getElementById('payment-amount').value || '0.1 ETH'; // Implementation similar to sendWebSocketPayment but for specific peer log(`💰 Sending WebSocket payment to specific peer: ${amount}`, 'payment'); }; window.sendWebRTCPaymentTo = async function(peerId) { const amount = document.getElementById('payment-amount').value || '0.1 ETH'; // Implementation similar to sendWebRTCPayment but for specific peer log(`💰 Sending WebRTC P2P payment to specific peer: ${amount}`, 'payment'); }; window.sendToAllPeers = async function() { document.getElementById('transport-select').value = 'both'; await sendCryptoPayment(); }; window.stopWebSocketDiscovery = function() { if (websocketConnection) { websocketConnection.close(); websocketConnection = null; websocketPeers.clear(); updateWebSocketPeerList(); } }; window.stopWebRTCDiscovery = async function() { if (webrtcTransport) { await webrtcTransport.stopDiscovery(); webrtcTransport = null; webrtcPeers.clear(); connectedWebRTCPeers.clear(); updateWebRTCPeerList(); updateStatus('webrtc-status', '🌐 WebRTC: Off', 'disconnected'); document.getElementById('webrtc-section').classList.remove('active'); document.getElementById('webrtc-start-btn').disabled = false; document.getElementById('webrtc-stop-btn').disabled = true; } }; window.clearLogs = function() { document.getElementById('log').innerHTML = ''; }; window.exportLogs = function() { const logs = document.getElementById('log').innerText; const blob = new Blob([logs], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `airsign-logs-${new Date().toISOString()}.txt`; a.click(); URL.revokeObjectURL(url); }; // Event listeners document.getElementById('transport-select').addEventListener('change', updateSendButton); // Initialize initializeCrypto(); log('🚀 AirSign Protocol Complete SDK loaded!', 'success'); log(`📱 Device auto-detected: ${deviceName}`, 'info'); log('🔥 REAL crypto, REAL WebSocket, REAL WebRTC - All in ONE interface!', 'success'); </script> </body> </html> margin: 10px 5px; } .btn:hover { transform: translateY(-2px); } .btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; } .status { padding: 15px; border-radius: 10px; margin: 15px 0; text-align: center; font-weight: 600; } .status.connecting { background: #fff3cd; color: #856404; border: 1px solid #ffeaa7; } .status.connected { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .status.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .peers-section { margin-top: 30px; } .peers-section h3 { color: #2c3e50; margin-bottom: 20px; display: flex; align-items: center; gap: 10px; } .peer-list { background: #f8f9fa; border-radius: 15px; padding: 20px; border: 2px solid #e9ecef; } .peer-item { background: white; border-radius: 10px; padding: 20px; margin-bottom: 15px; border: 2px solid #e9ecef; display: flex; justify-content: between; align-items: center; transition: border-color 0.3s; } .peer-item:hover { border-color: #4ECDC4; } .peer-info { flex: 1; } .peer-name { font-weight: 600; color: #2c3e50; font-size: 1.1em; } .peer-capabilities { color: #6c757d; margin-top: 5px; } .peer-actions { display: flex; gap: 10px; } .payment-section { margin-top: 30px; background: #f8f9fa; border-radius: 15px; padding: 25px; border: 2px solid #e9ecef; } .payment-form { display: none; } .payment-form.active { display: block; } .crypto-uri { background: #e9ecef; padding: 15px; border-radius: 10px; font-family: monospace; word-break: break-all; margin: 10px 0; border: 2px solid #dee2e6; } .messages { max-height: 200px; overflow-y: auto; background: #f8f9fa; border-radius: 10px; padding: 15px; margin-top: 20px; border: 2px solid #e9ecef; } .message { padding: 10px; margin-bottom: 10px; border-radius: 8px; background: white; border-left: 4px solid #4ECDC4; } .empty-state { text-align: center; color: #6c757d; padding: 40px; } .empty-state .emoji { font-size: 3em; margin-bottom: 15px; } </style> </head> <body> <div class="container"> <div class="header"> <h1>🚀 AirSign Protocol</h1> <p>Crypto AirDrop - Send payments to nearby devices instantly!</p> </div> <div class="content"> <!-- Device Setup --> <div class="device-setup"> <h3>📱 Your Device</h3> <div class="input-group"> <label for="deviceName">Device Name</label> <input type="text" id="deviceName" placeholder="e.g., Alice's iPhone" value=""> </div> <button class="btn" onclick="startDiscovery()">🔍 Start Discovery</button> <button class="btn" onclick="stopDiscovery()" disabled>🛑 Stop Discovery</button> </div> <!-- Status --> <div id="status" class="status" style="display: none;"></div> <!-- Nearby Devices --> <div class="peers-section"> <h3>📱 Nearby Devices</h3> <div class="peer-list" id="peerList"> <div class="empty-state"> <div class="emoji">🔍</div> <p>Start discovery to find nearby devices</p> <small>Open this page in multiple browser tabs to simulate multiple devices!</small> </div> </div> </div> <!-- Payment Section --> <div class="payment-section"> <h3>💰 Send Crypto Payment</h3> <div id="paymentForm" class="payment-form"> <div class="input-group"> <label for="recipient">To Address</label> <input type="text" id="recipient" placeholder="0x742d35cc6aa123456789abcdef67890123456789"> </div> <div class="input-group"> <label for="amount">Amount (ETH)</label> <input type="number" id="amount" placeholder="0.1" step="0.001"> </div> <button class="btn" onclick="sendPayment()" id="sendBtn" disabled>💸 Send Payment Request</button> </div> <div class="empty-state" id="paymentEmpty"> <div class="emoji">👥</div> <p>Connect to a nearby device to send payments</p> </div> </div> <!-- Messages --> <div class="messages" id="messages"></div> </div> </div> <script src="airsign-browser.js"></script> <script> // Global state let ws = null; let myId = null; let discoveredPeers = new Map(); let connectedPeer = null; let isDiscovering = false; // Generate random device name const deviceNames = [ "Alice's iPhone", "Bob's MacBook", "Charlie's Pixel", "Diana's iPad", "Emma's Surface", "Frank's Galaxy", "Grace's iPhone", "Henry's Laptop" ]; document.getElementById('deviceName').value = deviceNames[Math.floor(Math.random() * deviceNames.length)]; function showStatus(message, type = 'connecting') { const status = document.getElementById('status'); status.className = `status ${type}`; status.textContent = message; status.style.display = 'block'; } function hideStatus() { document.getElementById('status').style.display = 'none'; } function addMessage(message) { const messages = document.getElementById('messages'); const messageEl = document.createElement('div'); messageEl.className = 'message'; messageEl.innerHTML = ` <strong>${new Date().toLocaleTimeString()}</strong><br> ${message} `; messages.appendChild(messageEl); messages.scrollTop = messages.scrollHeight; } function startDiscovery() { const deviceName = document.getElementById('deviceName').value.trim(); if (!deviceName) { alert('Please enter a device name'); return; } showStatus('� Initializing AirSign SDK...', 'connecting'); // Initialize AirSign SDK first setTimeout(async () => { try { airSignSDK = window.AirSignSDK; await airSignSDK.init(); showStatus('🔍 Connecting to signaling server...', 'connecting'); // Dynamic WebSocket URL based on environment const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; let wsHost = window.location.hostname; // Fix for browsers that can't connect to 0.0.0.0 if (wsHost === '0.0.0.0') { wsHost = 'localhost'; } const wsPort = wsHost === 'localhost' || wsHost.startsWith('192.168') ? ':3001' : ':443'; const wsUrl = `${wsProtocol}//${wsHost}${wsPort}`; addMessage(`🔗 Connecting to: ${wsUrl}`); ws = new WebSocket(wsUrl); ws.onopen = () => { showStatus('✅ Connected! Looking for nearby devices...', 'connected'); isDiscovering = true; updateButtons(); }; ws.onmessage = (event) => { const message = JSON.parse(event.data); handleSignalingMessage(message); }; ws.onclose = () => { showStatus('🔌 Disconnected from signaling server', 'error'); isDiscovering = false; updateButtons(); }; ws.onerror = (error) => { showStatus('❌ Connection failed', 'error'); console.error('WebSocket error:', error); }; } catch (error) { showStatus('❌ Failed to initialize SDK: ' + error.message, 'error'); } }, 100); } function stopDiscovery() { if (ws) { ws.close(); } isDiscovering = false; discoveredPeers.clear(); connectedPeer = null; updateButtons(); updatePeerList(); hideStatus(); addMessage('🛑 Discovery stopped'); } function updateButtons() { const startBtn = document.querySelector('button[onclick="startDiscovery()"]'); const stopBtn = document.querySelector('button[onclick="stopDiscovery()"]'); startBtn.disabled = isDiscovering; stopBtn.disabled = !isDiscovering; } function handleSignalingMessage(message) { switch (message.type) { case 'connected': myId = message.clientId; addMessage(`📱 Device ID: ${myId}`); addMessage(`🔑 Public Key: ${airSignSDK.getPublicKey()}`); // Create real presence packet with crypto airSignSDK.createPresencePacket( document.getElementById('deviceName').value, ['payment-uri', 'nft-transfer'] ).then(presencePacket =>