UNPKG

peerpigeon

Version:

WebRTC-based peer-to-peer mesh networking library with intelligent routing and signaling server

753 lines (700 loc) 41 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>PeerPigeon - WebRTC P2P Mesh</title> <link rel="stylesheet" href="styles.css"> <link rel="icon" type="image/png" sizes="32x32" href="assets/images/favicon.png"> <link rel="icon" type="image/png" sizes="16x16" href="assets/images/favicon.png"> <link rel="apple-touch-icon" href="assets/images/favicon.png"> <link rel="shortcut icon" href="assets/images/favicon.png"> <!-- Load unsea crypto library from CDN for browser environment --> <script src="https://cdn.jsdelivr.net/npm/unsea@1.3.0/dist/sea.min.js"></script> </head> <body> <div class="container"> <header> <h1><img src="assets/images/favicon.png" alt="PeerPigeon Logo" style="height: 2.5em; width: auto; vertical-align: middle; margin-right: 0.5em;"> PeerPigeon</h1> <p>WebRTC P2P mesh networking with XOR-based routing</p> <div class="usage-hint"> <strong>Tip:</strong> You can pass the API Gateway URL via query parameter:<br> <code>?api=ws://localhost:3000</code> </div> </header> <main> <div class="peer-info"> <h2>Peer Information</h2> <div class="info-group"> <label>Your Peer ID:</label> <code id="peer-id">Generating...</code> </div> <div class="info-group"> <label>Status:</label> <span id="status" class="status disconnected">Disconnected</span> </div> </div> <div class="connection-controls"> <h2>Connection</h2> <div class="button-group"> <button id="connect-btn" class="btn primary">Connect</button> <button id="disconnect-btn" class="btn secondary" disabled>Disconnect</button> <button id="cleanup-btn" class="btn tertiary" disabled>Clean Signaling Data</button> <button id="health-check-btn" class="btn quaternary" disabled>Health Check</button> <button id="refresh-btn" class="btn quaternary">🔄 Refresh Page</button> </div> <div class="system-messages-container"> <div id="system-messages" class="system-messages"></div> </div> </div> <div class="connected-peers" aria-expanded="false"> <div class="section-header"> <h2> <button id="connected-peers-toggle" class="toggle-btn" aria-expanded="false"> ▶ Connected Peers </button> <span id="connected-peers-count" class="count-badge">0</span> </h2> </div> <div id="connected-peers-content" class="collapsible-content" style="display: none;"> <div id="peers-list" class="peers-list"> <p class="empty-state">No peers connected</p> </div> </div> </div> <div class="media" aria-expanded="false"> <div class="section-header"> <h2> <button id="media-toggle" class="toggle-btn" aria-expanded="false"> ▶ Video/Audio </button> </h2> </div> <div id="media-content" class="collapsible-content" style="display: none;"> <div class="media-controls"> <div class="input-group"> <label>Media Types:</label> <div class="checkbox-group"> <label class="checkbox-label"> <input type="checkbox" id="enable-video"> <span class="checkmark"></span> Enable Video </label> <label class="checkbox-label"> <input type="checkbox" id="enable-audio"> <span class="checkmark"></span> Enable Audio </label> </div> </div> <div class="input-group"> <label for="camera-select">Camera:</label> <select id="camera-select" disabled> <option value="">Select camera...</option> </select> </div> <div class="input-group"> <label for="microphone-select">Microphone:</label> <select id="microphone-select" disabled> <option value="">Select microphone...</option> </select> </div> <div class="button-group"> <button id="start-media-btn" class="btn primary" disabled>Start Media</button> <button id="stop-media-btn" class="btn secondary" disabled>Stop Media</button> <button id="toggle-video-btn" class="btn tertiary" disabled>Toggle Video</button> <button id="toggle-audio-btn" class="btn tertiary" disabled>Toggle Audio</button> <button id="test-audio-btn" class="btn warning">🔊 Test Audio</button> </div> <div class="local-video"> <h4>Your Video</h4> <div id="local-video-container" class="video-container"> <p class="video-placeholder">No video stream</p> </div> </div> <div class="remote-videos"> <h4>Peer Videos</h4> <div id="remote-videos-container" class="videos-grid"> <p class="video-placeholder">No remote video streams</p> </div> </div> </div> </div> </div> <div class="discovered-peers" aria-expanded="false"> <div class="section-header"> <h2> <button id="discovered-peers-toggle" class="toggle-btn" aria-expanded="false"> ▶ Discovered Peers </button> <span id="discovered-peers-count" class="count-badge">0</span> </h2> </div> <div id="discovered-peers-content" class="collapsible-content" style="display: none;"> <div id="discovered-peers-list" class="discovered-peers-list"> <p class="empty-state">No peers discovered</p> </div> </div> </div> <div class="manual-connection" aria-expanded="false"> <div class="section-header"> <h2> <button id="manual-connection-toggle" class="toggle-btn" aria-expanded="false"> ▶ Manual Connection </button> </h2> </div> <div id="manual-connection-content" class="collapsible-content" style="display: none;"> <div class="input-group"> <label for="target-peer">Target Peer ID (40-character SHA-1 hash):</label> <div class="input-group horizontal"> <input type="text" id="target-peer" placeholder="Enter 40-character SHA-1 hash" maxlength="40" pattern="[a-fA-F0-9]{40}"> <button id="connect-peer-btn" class="btn secondary">Connect to Peer</button> </div> <small class="input-help"> Manually connect to a specific peer ID </small> </div> </div> </div> <div class="settings" aria-expanded="false"> <div class="section-header"> <h2> <button id="settings-toggle" class="toggle-btn" aria-expanded="false"> ▶ Settings </button> </h2> </div> <div id="settings-content" class="collapsible-content" style="display: none;"> <div class="input-group"> <label for="signaling-url">Signaling Server URL:</label> <input type="url" id="signaling-url" placeholder="ws://localhost:3000"> <small class="input-help"> Auto-populated from ?api, ?url, or ?signaling query parameters. Enter base API Gateway URL (without /signaling path). </small> </div> <div class="input-group"> <label for="min-peers">Minimum Peers:</label> <input type="number" id="min-peers" min="0" max="49" value="2" placeholder="Minimum peer connections to maintain"> <small class="input-help"> Minimum peer connections to maintain (0-49, default: 2) </small> </div> <div class="input-group"> <label for="max-peers">Maximum Peers:</label> <input type="number" id="max-peers" min="1" max="50" value="10" placeholder="Maximum number of peer connections"> <small class="input-help"> Limit concurrent peer connections (1-50, default: 10) </small> </div> <div class="auto-discovery-control"> <label class="checkbox-label"> <input type="checkbox" id="xor-routing-toggle" checked> <span class="checkmark"></span> Enable XOR-based routing </label> <small class="input-help"> Use XOR distance for peer selection and routing optimization </small> </div> <div class="auto-discovery-control"> <label class="checkbox-label"> <input type="checkbox" id="auto-discovery-toggle" checked> <span class="checkmark"></span> Enable automatic peer discovery </label> <small class="input-help"> Automatically discover and connect to nearby peers </small> </div> <div class="auto-discovery-control"> <label class="checkbox-label"> <input type="checkbox" id="eviction-strategy-toggle" checked> <span class="checkmark"></span> Enable smart eviction strategy </label> <small class="input-help"> Automatically manage peer connections when at capacity </small> </div> <div class="auto-discovery-control"> <label class="checkbox-label"> <input type="checkbox" id="webdht-toggle" checked disabled> <span class="checkmark"></span> Enable WebDHT (Distributed Hash Table) </label> <small class="input-help"> WebDHT status (configured at startup, restart required to change) </small> </div> </div> </div> <div class="dht" aria-expanded="false"> <div class="section-header"> <h2> <button id="dht-toggle" class="toggle-btn" aria-expanded="false"> ▶ WebDHT - Distributed Hash Table </button> </h2> </div> <div id="dht-content" class="collapsible-content" style="display: none;"> <div class="dht-actions"> <h3>Store Data</h3> <div class="input-group"> <label for="dht-key">Key:</label> <input type="text" id="dht-key" placeholder="Enter key (e.g., 'user:123', 'config:theme')"> </div> <div class="input-group"> <label for="dht-value">Value:</label> <textarea id="dht-value" placeholder="Enter value (JSON, text, etc.)" rows="3"></textarea> </div> <div class="input-group"> <label class="checkbox-label"> <input type="checkbox" id="dht-enable-ttl"> <span class="checkmark"></span> Enable expiration (TTL) </label> <small class="input-help"> Check to set an expiration time, unchecked = records persist forever </small> </div> <div class="input-group" id="dht-ttl-group" style="display: none;"> <label for="dht-ttl">TTL (seconds):</label> <input type="number" id="dht-ttl" placeholder="Time to live" min="1" max="2592000" value="3600"> <small class="input-help"> Time until record expires (e.g., 3600 = 1 hour, 86400 = 1 day) </small> </div> <div class="button-group"> <button id="dht-put-btn" class="btn primary">Store in DHT</button> <button id="dht-update-btn" class="btn secondary">Update in DHT</button> </div> </div> <div class="dht-actions"> <h3>Retrieve Data</h3> <div class="input-group horizontal"> <input type="text" id="dht-get-key" placeholder="Enter key to retrieve"> <button id="dht-get-btn" class="btn secondary">Get from DHT</button> </div> </div> <div class="dht-actions"> <h3>Subscribe to Changes</h3> <div class="input-group horizontal"> <input type="text" id="dht-subscribe-key" placeholder="Key to watch for changes"> <div class="button-group"> <button id="dht-subscribe-btn" class="btn tertiary">Subscribe</button> <button id="dht-clear-subscriptions-btn" class="btn quaternary">Clear All</button> </div> </div> <div id="dht-subscriptions" class="dht-subscriptions"> <h4>Active Subscriptions:</h4> <div id="dht-subscriptions-list" class="subscriptions-list"> <p class="empty-state">No active subscriptions</p> </div> </div> </div> <div class="dht-results"> <h3>DHT Operations Log</h3> <div id="dht-log" class="dht-log"></div> </div> </div> </div> <div class="crypto" aria-expanded="false"> <div class="section-header"> <h2> <button id="crypto-toggle" class="toggle-btn" aria-expanded="false"> ▶ Crypto - End-to-End Encryption </button> </h2> </div> <div id="crypto-content" class="collapsible-content" style="display: none;"> <!-- Crypto Status --> <div class="crypto-status"> <h3>Crypto Status</h3> <div class="info-group"> <label>Encryption:</label> <span id="crypto-status" class="status">Disabled</span> </div> <div class="info-group"> <label>Public Key:</label> <code id="crypto-public-key">None</code> </div> <div class="info-group"> <label>Peer Keys:</label> <span id="crypto-peer-count">0</span> </div> </div> <!-- Key Management --> <div class="crypto-keys"> <h3>Key Management</h3> <div class="button-group"> <button id="crypto-generate-btn" class="btn primary">Generate New Keypair</button> <button id="crypto-reset-btn" class="btn warning">Reset Crypto</button> <button id="crypto-self-test-btn" class="btn tertiary">Run Self Test</button> </div> <div class="crypto-user-auth"> <h4>User Authentication (Optional)</h4> <div class="input-group"> <label for="crypto-alias">Alias:</label> <input type="text" id="crypto-alias" placeholder="Your username/alias"> </div> <div class="input-group"> <label for="crypto-password">Password:</label> <input type="password" id="crypto-password" placeholder="Your password"> </div> <div class="button-group"> <button id="crypto-login-btn" class="btn secondary">Login/Create Account</button> </div> </div> </div> <!-- Message Encryption --> <div class="crypto-messaging"> <h3>Encrypted Messaging</h3> <div class="input-group"> <label class="checkbox-label"> <input type="checkbox" id="crypto-auto-encrypt"> <span class="checkmark"></span> Auto-encrypt outgoing messages </label> </div> <div class="crypto-test-messaging"> <h4>Test Encrypted Messages</h4> <div class="input-group horizontal"> <input type="text" id="crypto-test-message" placeholder="Test message to encrypt"> <input type="text" id="crypto-test-peer" placeholder="Target peer ID" maxlength="40"> <button id="crypto-send-encrypted-btn" class="btn primary">Send Encrypted</button> </div> </div> </div> <!-- Group Encryption --> <div class="crypto-groups"> <h3>Group Encryption</h3> <div class="input-group horizontal"> <input type="text" id="crypto-group-id" placeholder="Group ID (e.g., 'chat-room-1')"> <button id="crypto-create-group-btn" class="btn secondary">Create Group Key</button> </div> <div class="crypto-group-messaging"> <div class="input-group horizontal"> <input type="text" id="crypto-group-message" placeholder="Group message"> <select id="crypto-group-select"> <option value="">Select group...</option> </select> <button id="crypto-send-group-btn" class="btn primary">Send to Group</button> </div> </div> <div id="crypto-groups-list" class="crypto-groups-list"> <h4>Active Groups:</h4> <div class="empty-state">No groups created</div> </div> </div> <!-- Performance Stats --> <div class="crypto-stats"> <h3>Performance Statistics</h3> <div class="stats-grid"> <div class="stat-item"> <label>Messages Encrypted:</label> <span id="crypto-stats-encrypted">0</span> </div> <div class="stat-item"> <label>Messages Decrypted:</label> <span id="crypto-stats-decrypted">0</span> </div> <div class="stat-item"> <label>Encryption Time (avg):</label> <span id="crypto-stats-encrypt-time">0ms</span> </div> <div class="stat-item"> <label>Decryption Time (avg):</label> <span id="crypto-stats-decrypt-time">0ms</span> </div> <div class="stat-item"> <label>Key Exchanges:</label> <span id="crypto-stats-key-exchanges">0</span> </div> </div> </div> <!-- Test Results --> <div class="crypto-test-results"> <h3>Test Results</h3> <div id="crypto-test-log" class="test-log"> <p class="empty-state">No tests run yet</p> </div> </div> <!-- Advanced Operations --> <div class="crypto-advanced"> <h3>Advanced Operations</h3> <div class="button-group"> <button id="crypto-export-key-btn" class="btn quaternary">Export Public Key</button> <button id="crypto-import-peer-key-btn" class="btn quaternary">Import Peer Key</button> <button id="crypto-benchmark-btn" class="btn tertiary">Run Benchmark</button> </div> <!-- Import peer key form --> <div id="crypto-import-form" class="crypto-import-form" style="display: none;"> <div class="input-group"> <label for="crypto-import-peer-id">Peer ID:</label> <input type="text" id="crypto-import-peer-id" placeholder="40-character peer ID" maxlength="40"> </div> <div class="input-group"> <label for="crypto-import-public-key">Public Key:</label> <textarea id="crypto-import-public-key" placeholder="Paste public key here" rows="3"></textarea> </div> <div class="button-group"> <button id="crypto-import-confirm-btn" class="btn primary">Import Key</button> <button id="crypto-import-cancel-btn" class="btn secondary">Cancel</button> </div> </div> </div> </div> </div> <div class="storage" aria-expanded="false"> <div class="section-header"> <h2> <button id="storage-toggle" class="toggle-btn" aria-expanded="false"> ▶ Distributed Storage - Encrypted P2P Storage Layer </button> </h2> </div> <div id="storage-content" class="collapsible-content" style="display: none;"> <!-- Storage Status --> <div class="storage-status"> <h3>Storage Status</h3> <div class="info-group"> <label>Storage Layer:</label> <span id="storage-status">Disabled</span> </div> <div class="info-group"> <label>Stored Items:</label> <span id="storage-item-count">0</span> </div> <div class="info-group"> <label>Total Size:</label> <span id="storage-total-size">0 bytes</span> </div> </div> <!-- Storage Controls --> <div class="storage-controls"> <h3>Storage Controls</h3> <div class="button-group"> <button id="storage-enable-btn" class="btn primary">Enable Storage</button> <button id="storage-disable-btn" class="btn warning">Disable Storage</button> <button id="storage-clear-btn" class="btn quaternary">Clear All</button> </div> </div> <!-- Store Data --> <div class="storage-operations"> <h3>Store Data</h3> <div class="input-group"> <label for="storage-key">Key:</label> <input type="text" id="storage-key" placeholder="Storage key"> </div> <div class="input-group"> <label for="storage-value">Value:</label> <textarea id="storage-value" placeholder="Data to store (JSON or text)" rows="3"></textarea> </div> <!-- Storage Options --> <div class="storage-options"> <h4>Storage Options</h4> <div class="input-group"> <label class="checkbox-label"> <input type="checkbox" id="storage-encrypt" checked> <span class="checkmark"></span> Encrypt data </label> </div> <div class="input-group"> <label class="checkbox-label"> <input type="checkbox" id="storage-public"> <span class="checkmark"></span> Public access (readable by anyone) </label> </div> <div class="input-group"> <label class="checkbox-label"> <input type="checkbox" id="storage-immutable"> <span class="checkmark"></span> Immutable (cannot be updated) </label> </div> <div class="input-group"> <label class="checkbox-label"> <input type="checkbox" id="storage-crdt"> <span class="checkmark"></span> Enable CRDT (conflict-free updates) </label> </div> <div class="input-group"> <label for="storage-ttl">TTL (seconds, 0 = no expiration):</label> <input type="number" id="storage-ttl" value="0" min="0"> </div> </div> <div class="button-group"> <button id="storage-store-btn" class="btn primary">Store Data</button> <button id="storage-update-btn" class="btn secondary">Update Data</button> </div> </div> <!-- Retrieve Data --> <div class="storage-retrieve"> <h3>Retrieve Data</h3> <div class="input-group horizontal"> <input type="text" id="storage-get-key" placeholder="Key to retrieve"> <button id="storage-get-btn" class="btn secondary">Get Data</button> <button id="storage-delete-btn" class="btn warning">Delete</button> </div> </div> <!-- Access Control --> <div class="storage-access"> <h3>Access Control</h3> <div class="input-group horizontal"> <input type="text" id="storage-access-key" placeholder="Key for access control"> <input type="text" id="storage-access-peer" placeholder="Peer ID to grant access"> <select id="storage-access-level"> <option value="read">Read</option> <option value="write">Write</option> <option value="admin">Admin</option> </select> <button id="storage-grant-access-btn" class="btn tertiary">Grant Access</button> </div> <div class="input-group horizontal"> <input type="text" id="storage-revoke-key" placeholder="Key for access revocation"> <input type="text" id="storage-revoke-peer" placeholder="Peer ID to revoke access"> <button id="storage-revoke-access-btn" class="btn quaternary">Revoke Access</button> </div> </div> <!-- Bulk Operations --> <div class="storage-bulk"> <h3>Bulk Operations</h3> <div class="input-group horizontal"> <input type="text" id="storage-prefix" placeholder="Key prefix for bulk operations"> <button id="storage-list-btn" class="btn tertiary">List Keys</button> <button id="storage-bulk-delete-btn" class="btn warning">Bulk Delete</button> </div> </div> <!-- Search --> <div class="storage-search"> <h3>Search</h3> <div class="input-group horizontal"> <input type="text" id="storage-search-query" placeholder="Search query"> <select id="storage-search-type"> <option value="key">Search Keys</option> <option value="value">Search Values</option> <option value="metadata">Search Metadata</option> </select> <button id="storage-search-btn" class="btn tertiary">Search</button> </div> </div> <!-- Backup/Restore --> <div class="storage-backup"> <h3>Backup & Restore</h3> <div class="button-group"> <button id="storage-backup-btn" class="btn tertiary">Create Backup</button> <button id="storage-restore-btn" class="btn tertiary">Restore from Backup</button> </div> <input type="file" id="storage-restore-file" accept=".json" style="display: none;"> </div> <!-- Storage Log --> <div class="storage-results"> <h3>Storage Operations Log</h3> <div id="storage-log" class="storage-log"></div> </div> <!-- Lexical Storage Interface --> <div class="lexical-storage"> <h3>🔗 Lexical Storage Interface (GUN-like API)</h3> <p class="feature-description">Chain operations like: <code>storage.get('users').get('alice').put({name: 'Alice'})</code></p> <!-- Lexical Operations --> <div class="lexical-operations"> <h4>Chain Operations</h4> <div class="input-group"> <label for="lexical-path">Path (dot notation):</label> <input type="text" id="lexical-path" placeholder="users.alice" value="demo.user1"> </div> <div class="input-group"> <label for="lexical-data">Data (JSON):</label> <textarea id="lexical-data" rows="3" placeholder='{"name": "Alice", "age": 30}'></textarea> </div> <div class="input-group"> <label for="lexical-property">Property to get:</label> <input type="text" id="lexical-property" placeholder="name" value="name"> </div> <div class="button-group"> <button id="lexical-put-btn" class="btn primary">Put Data</button> <button id="lexical-get-btn" class="btn secondary">Get Property</button> <button id="lexical-val-btn" class="btn secondary">Get Full Object</button> <button id="lexical-update-btn" class="btn tertiary">Update</button> <button id="lexical-delete-btn" class="btn warning">Delete</button> </div> </div> <!-- Set Operations --> <div class="lexical-sets"> <h4>Set Operations</h4> <div class="input-group"> <label for="lexical-set-path">Set Path:</label> <input type="text" id="lexical-set-path" placeholder="users.alice.friends" value="demo.friends"> </div> <div class="input-group"> <label for="lexical-set-data">Set Data (JSON object):</label> <textarea id="lexical-set-data" rows="2" placeholder='{"bob": {"name": "Bob"}, "charlie": {"name": "Charlie"}}'></textarea> </div> <div class="button-group"> <button id="lexical-set-btn" class="btn primary">Set Data</button> <button id="lexical-map-btn" class="btn secondary">Map & Log</button> </div> </div> <!-- Property Access Demo --> <div class="lexical-proxy"> <h4>Property Access (Proxy)</h4> <div class="input-group"> <label for="lexical-proxy-path">Property Path:</label> <input type="text" id="lexical-proxy-path" placeholder="users.alice.settings.theme" value="demo.settings.theme"> </div> <div class="input-group"> <label for="lexical-proxy-value">Value:</label> <input type="text" id="lexical-proxy-value" placeholder="dark" value="dark"> </div> <div class="button-group"> <button id="lexical-proxy-set-btn" class="btn primary">Set via Proxy</button> <button id="lexical-proxy-get-btn" class="btn secondary">Get via Proxy</button> </div> </div> <!-- Utility Operations --> <div class="lexical-utils"> <h4>Utility Methods</h4> <div class="input-group"> <label for="lexical-util-path">Path:</label> <input type="text" id="lexical-util-path" placeholder="demo.user1" value="demo.user1"> </div> <div class="button-group"> <button id="lexical-exists-btn" class="btn tertiary">Check Exists</button> <button id="lexical-keys-btn" class="btn tertiary">Get Keys</button> <button id="lexical-path-btn" class="btn tertiary">Get Path</button> </div> </div> <!-- Lexical Results --> <div class="lexical-results"> <h4>Lexical Operations Results</h4> <div id="lexical-log" class="storage-log"></div> </div> </div> </div> </div> <div class="messages"> <h2>Messages</h2> <div class="input-group horizontal"> <input type="text" id="message-input" placeholder="Enter message"> <input type="text" id="dm-target-input" placeholder="Peer ID for DM (leave blank to broadcast)" maxlength="40" pattern="[a-fA-F0-9]{40}"> <button id="send-message-btn" class="btn primary">Send</button> </div> <small class="input-help">To send a direct message, enter a 40-character peer ID. Leave blank to broadcast.</small> <div id="messages-log" class="messages-log"></div> </div> </div> <script type="module" src="app.js?v=20250704"></script> </body> </html>