p2p-message
Version:
A simple and efficient P2P messaging library for browser-to-browser communication
428 lines (365 loc) • 15.1 kB
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>