UNPKG

p2p-message

Version:

A simple and efficient P2P messaging library for browser-to-browser communication

428 lines (365 loc) 15.1 kB
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous"> <title>P2P Messaging</title> <style> body { font-family: 'Arial', sans-serif; background-color: #f8f9fa; color: #343a40; padding: 20px; } h1 { color: #007bff; margin-bottom: 30px; } p { font-size: 1.1rem; line-height: 1.6; margin-bottom: 20px; } .input-group { margin-bottom: 20px; } .btn-outline-secondary { color: #6c757d; border-color: #6c757d; } .btn-outline-secondary:hover { color: #fff; background-color: #6c757d; border-color: #6c757d; } #connectBtn, #disconnectNetworkBtn, #peerCount { display: inline-block; margin: 0 10px 20px 0; } #peerCount { background-color: #e9ecef; padding: 8px 15px; border-radius: 5px; font-weight: bold; text-align: center; } #messageContainer, #debugContainer { border: 1px solid #ced4da; padding: 20px; margin-bottom: 20px; border-radius: 10px; background-color: #fff; } .logTitle { color: #007bff; margin-bottom: 15px; } #messageLog, #debugLog { border: 1px solid #ced4da; height: 200px; overflow-y: auto; padding: 10px; margin-bottom: 15px; background-color: #f8f9fa; font-size: 0.9rem; line-height: 1.4; white-space: pre-wrap; } #messageInput { width: 100%; margin-bottom: 15px; } .copyButton { margin-top: 10px; } .is-valid { border-color: #28a745; } .is-invalid { border-color: #dc3545; } #status { margin-top: 10px; color: #28a745; font-weight: bold; } .task-section { margin-bottom: 30px; } .peer-list { list-style: none; padding: 0; } .peer-item { display: flex; justify-content: space-between; align-items: center; padding: 5px 10px; border: 1px solid #ced4da; margin-bottom: 5px; border-radius: 5px; background-color: #fff; } .disconnect-btn { margin-left: 10px; } .block-btn { margin-left: 10px; } #task-3 > div { display: flex; align-items: center; margin-bottom: 20px; } </style> </head> <body> <div class="container"> <h1 class="mt-4 mb-4">P2P Messaging</h1> <p class="lead"> Enter the same <strong>Shared Token</strong> in two or more browsers, then click <strong>Connect</strong>. When peers discover each other, you can exchange messages. </p> <div class="alert alert-info" role="alert"> <h4>Instructions:</h4> <ol> <li>Click the <strong>Generate Token</strong> button to create a unique shared key.</li> <li>Click the <strong>Open in New Window</strong> button.</li> <li>Click the <strong>Connect</strong> button in both windows to join the P2P network.</li> <li>Once connected, you can exchange messages with other peers.</li> </ol> </div> <div id="task-1" class="task-section"> <h3>1. Generate Token</h3> <p>Click the "Generate Token" button to create a unique shared key.</p> <div class="input-group mb-3 w-25"> <input type="text" id="sharedKey" class="form-control col-3" placeholder="Generate a token"> <button class="btn btn-outline-secondary" type="button" id="refreshKeyBtn"> <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-arrow-clockwise" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2a1 1 0 0 0-1 1z" /> <path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.232 2.308a.25.25 0 0 0 0 .384l2.368 1.308A.25.25 0 0 0 8 4.466z" /> </svg> </button> </div> </div> <div id="task-2" class="task-section"> <h3>2. Open New Window</h3> <p>Click the "Open in New Window" button.</p> <button id="openNewWindowBtn" class="btn btn-info mt-2">Open in New Window</button> </div> <div id="task-3" class="task-section"> <h3>3. Connect</h3> <p>Click the "Connect" button in both windows to join the P2P network.</p> <div id="task-3" style="display: flex; align-items: center; margin-bottom: 20px;"> <button id="connectBtn" class="btn btn-primary">Connect</button> <button id="disconnectNetworkBtn" class="btn btn-danger" style="display:none;">Disconnect</button> <div id="peerCount">Connected Peers: 0</div> </div> <div id="status" class="mt-2"></div> <div id="connectionSuccess" class="alert alert-success" role="alert" style="display: none;"> Connection successful! </div> </div> <div id="peerListContainer" class="mt-4 p-3 task-section"> <h3 class="logTitle">Connected Peers</h3> <ul id="peerList" class="peer-list"> <!-- Peer list will be dynamically populated here --> </ul> </div> <div id="messageContainer" class="mt-4 p-3 task-section"> <h3 class="logTitle">Message</h3> <div id="messageLog" class="border rounded p-2 mb-2" style="height: 200px; overflow-y: auto;"></div> <div class="input-group"> <input type="text" id="messageInput" class="form-control" placeholder="Type a message..." /> <button id="sendBtn" class="btn btn-primary">Send</button> </div> </div> <div id="debugContainer" class="mt-4 p-3 task-section"> <h3 class="logTitle">Debug Log</h3> <button class="copyButton btn btn-secondary" id="copyLogBtn">Copy Debug Log</button> <div id="debugLog" class="border rounded p-2" style="height: 200px; overflow-y: auto;"></div> </div> </div> <script src="./messaging.min.js"></script> <script> // Check for token in URL parameter const urlParams = new URLSearchParams(window.location.search); const sharedKeyFromUrl = urlParams.get('token'); const sharedKeyInput = document.getElementById('sharedKey'); // If token exists in URL, use it if (sharedKeyFromUrl) { sharedKeyInput.value = sharedKeyFromUrl; sharedKeyInput.classList.add('is-valid'); } const refreshKeyBtn = document.getElementById('refreshKeyBtn'); const connectionSuccess = document.getElementById('connectionSuccess'); function generateSharedKey(length) { let result = ''; const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const charactersLength = characters.length; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; } function updateSharedTokenInput(token) { sharedKeyInput.value = token; if (token.length >= 10) { sharedKeyInput.classList.add('is-valid'); sharedKeyInput.classList.remove('is-invalid'); } else if (token.length > 0) { sharedKeyInput.classList.add('is-invalid'); sharedKeyInput.classList.remove('is-valid'); } else { sharedKeyInput.classList.remove('is-invalid'); sharedKeyInput.classList.remove('is-valid'); } } const openNewWindowBtn = document.getElementById('openNewWindowBtn'); openNewWindowBtn.addEventListener('click', () => { const currentUrl = window.location.href; const sharedToken = sharedKeyInput.value; const newUrl = `${currentUrl.split('?')[0]}?token=${sharedToken}`; window.open(newUrl, '_blank'); }); // Refresh key button handler refreshKeyBtn.addEventListener('click', () => { updateSharedTokenInput(generateSharedKey(10)); }); let peers = []; const messageLog = document.getElementById('messageLog'); const debugLog = document.getElementById('debugLog'); const connectBtn = document.getElementById('connectBtn'); const disconnectNetworkBtn = document.getElementById('disconnectNetworkBtn'); const sendBtn = document.getElementById('sendBtn'); const messageInput = document.getElementById('messageInput'); const peerCountDisplay = document.getElementById('peerCount'); const statusDisplay = document.getElementById('status'); const peerList = document.getElementById('peerList'); // Log functions function log(message, type = 'debug') { const target = type === 'message' ? messageLog : debugLog; const timestamp = new Date().toLocaleTimeString(); target.innerHTML += `[${timestamp}] ${message}\n`; target.scrollTop = target.scrollHeight; } // Event handlers for P2P communication const onMessage = (peer, message) => { log(`Peer ${peer}: ${message}`, 'message'); } const onConnect = (peer) => { log(`New peer connected: ${peer}`); peers.push(peer); updatePeerList(); peerCountDisplay.textContent = `Connected Peers: ${peers.length}`; connectionSuccess.style.display = 'block'; statusDisplay.textContent = 'Connection successful!'; connectBtn.style.display = 'none'; disconnectNetworkBtn.style.display = 'inline-block'; } function disconnectPeer(peerId) { messageClient.disconnectPeer(peerId); peers = peers.filter(peer => peer !== peerId); updatePeerList(); peerCountDisplay.textContent = `Connected Peers: ${peers.length}`; log(`Disconnected from peer ${peerId}`); } function updatePeerList() { peerList.innerHTML = ''; peers.forEach(peer => { const listItem = document.createElement('li'); listItem.className = 'peer-item'; listItem.textContent = `Peer ${peer}`; const disconnectButton = document.createElement('button'); disconnectButton.className = 'btn btn-danger disconnect-btn'; disconnectButton.textContent = 'Disconnect'; disconnectButton.addEventListener('click', () => { disconnectPeer(peer); }); listItem.appendChild(disconnectButton); peerList.appendChild(listItem); }); } // Initialize messaging configuration const trackers = ["wss://tracker.openwebtorrent.com"]; let messageClient = null; // Connect button handler connectBtn.addEventListener('click', () => { const token = sharedKeyInput.value; if (!token) { log('Please enter a Shared Token'); return; } const config = { token: token, trackers: trackers, onMessage: onMessage, onConnect: onConnect }; messageClient = new MessageClient(config); connectBtn.textContent = 'Connecting...'; messageClient.connect(); log('Connecting to P2P network...'); }); disconnectNetworkBtn.addEventListener('click', () => { messageClient.disconnect(); peers = []; updatePeerList(); peerCountDisplay.textContent = `Connected Peers: ${peers.length}`; log('Disconnected from P2P network'); connectBtn.style.display = 'inline-block'; disconnectNetworkBtn.style.display = 'none'; statusDisplay.textContent = ''; connectionSuccess.style.display = 'none'; }); // Send message handler sendBtn.addEventListener('click', () => { const message = messageInput.value.trim(); if (!message) return; if (peers.length === 0) { log('No peers connected. Wait for connections.'); return; } peers.forEach(peer => { messageClient.send(peer, message); }); log(`You: ${message}`, 'message'); messageInput.value = ''; }); // Enter token to send message messageInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { sendBtn.click(); } }); // Copy debug log button document.getElementById('copyLogBtn').addEventListener('click', () => { navigator.clipboard.writeText(debugLog.textContent) .then(() => log('Debug log copied to clipboard')) .catch(err => log('Failed to copy debug log: ' + err)); }); // Clean up peer list when peers disconnect in the background setInterval(() => { if (messageClient && messageClient.torrent) { const currentPeers = messageClient.torrent.wires.map(wire => wire.peerId); const disconnectedPeers = peers.filter(peer => !currentPeers.includes(peer)); disconnectedPeers.forEach(peer => { log(`Peer disconnected in background: ${peer}`); peers = peers.filter(p => p !== peer); updatePeerList(); peerCountDisplay.textContent = `Connected Peers: ${peers.length}`; }); } }, 2000); </script> </body> </html>