/ demo.html
demo.html
  1  <!DOCTYPE html>
  2  <html lang="en">
  3  
  4  <head>
  5      <meta charset="UTF-8">
  6      <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7      <title>P2P Protocol Stack - Multi-Peer Demo</title>
  8      <style>
  9          * {
 10              margin: 0;
 11              padding: 0;
 12              box-sizing: border-box;
 13          }
 14  
 15          body {
 16              font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
 17              background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 18              min-height: 100vh;
 19              padding: 2rem;
 20              color: #333;
 21          }
 22  
 23          .container {
 24              max-width: 1400px;
 25              margin: 0 auto;
 26          }
 27  
 28          header {
 29              text-align: center;
 30              margin-bottom: 2rem;
 31              color: white;
 32          }
 33  
 34          h1 {
 35              font-size: 2.5rem;
 36              font-weight: 800;
 37              margin-bottom: 0.5rem;
 38              text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
 39          }
 40  
 41          .subtitle {
 42              font-size: 1.1rem;
 43              opacity: 0.9;
 44          }
 45  
 46          .peer-info {
 47              background: white;
 48              padding: 1.5rem;
 49              border-radius: 12px;
 50              margin-bottom: 1.5rem;
 51              box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
 52          }
 53  
 54          .peer-id {
 55              font-family: 'Monaco', monospace;
 56              font-size: 0.875rem;
 57              color: #667eea;
 58              word-break: break-all;
 59          }
 60  
 61          .grid {
 62              display: grid;
 63              grid-template-columns: 1fr 1fr;
 64              gap: 1.5rem;
 65              margin-bottom: 1.5rem;
 66          }
 67  
 68          @media (max-width: 768px) {
 69              .grid {
 70                  grid-template-columns: 1fr;
 71              }
 72          }
 73  
 74          .panel {
 75              background: white;
 76              padding: 1.5rem;
 77              border-radius: 12px;
 78              box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
 79          }
 80  
 81          .panel h2 {
 82              font-size: 1.5rem;
 83              margin-bottom: 1rem;
 84              color: #333;
 85              display: flex;
 86              align-items: center;
 87              gap: 0.5rem;
 88          }
 89  
 90          .button-group {
 91              display: flex;
 92              gap: 0.75rem;
 93              flex-wrap: wrap;
 94              margin-bottom: 1rem;
 95          }
 96  
 97          button {
 98              padding: 0.625rem 1.25rem;
 99              border: none;
100              border-radius: 8px;
101              font-size: 0.9rem;
102              font-weight: 600;
103              cursor: pointer;
104              transition: all 0.2s;
105              background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
106              color: white;
107              box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
108          }
109  
110          button:hover {
111              transform: translateY(-2px);
112              box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
113          }
114  
115          button:active {
116              transform: translateY(0);
117          }
118  
119          button.secondary {
120              background: #6b7280;
121          }
122  
123          button.danger {
124              background: #ef4444;
125          }
126  
127          .output {
128              background: #f7fafc;
129              border: 1px solid #e2e8f0;
130              border-radius: 8px;
131              padding: 1rem;
132              font-family: 'Monaco', 'Courier New', monospace;
133              font-size: 0.8rem;
134              max-height: 300px;
135              overflow-y: auto;
136              white-space: pre-wrap;
137              word-break: break-all;
138          }
139  
140          .log-entry {
141              margin-bottom: 0.5rem;
142              padding: 0.5rem;
143              border-left: 3px solid #667eea;
144              background: white;
145          }
146  
147          .timestamp {
148              color: #999;
149              font-size: 0.7rem;
150          }
151  
152          .success {
153              color: #10b981;
154          }
155  
156          .error {
157              color: #ef4444;
158          }
159  
160          .info {
161              color: #3b82f6;
162          }
163  
164          .warning {
165              color: #f59e0b;
166          }
167  
168          .peers-list {
169              list-style: none;
170          }
171  
172          .peer-item {
173              padding: 0.75rem;
174              margin-bottom: 0.5rem;
175              background: #f7fafc;
176              border-radius: 6px;
177              border-left: 3px solid #667eea;
178          }
179  
180          .peer-item.online {
181              border-left-color: #10b981;
182          }
183  
184          .peer-name {
185              font-weight: 600;
186              margin-bottom: 0.25rem;
187          }
188  
189          .peer-did {
190              font-family: 'Monaco', monospace;
191              font-size: 0.75rem;
192              color: #666;
193          }
194  
195          .topics-list {
196              list-style: none;
197          }
198  
199          .topic-item {
200              padding: 0.75rem;
201              margin-bottom: 0.5rem;
202              background: #f7fafc;
203              border-radius: 6px;
204              display: flex;
205              justify-content: space-between;
206              align-items: center;
207          }
208  
209          .topic-name {
210              font-family: 'Monaco', monospace;
211              font-size: 0.85rem;
212              color: #667eea;
213          }
214  
215          .subscriber-count {
216              background: #667eea;
217              color: white;
218              padding: 0.25rem 0.5rem;
219              border-radius: 999px;
220              font-size: 0.75rem;
221          }
222  
223          .badge {
224              display: inline-block;
225              padding: 0.25rem 0.5rem;
226              border-radius: 4px;
227              font-size: 0.75rem;
228              font-weight: 600;
229              margin-left: 0.5rem;
230          }
231  
232          .badge-success {
233              background: #d1fae5;
234              color: #065f46;
235          }
236  
237          .badge-warning {
238              background: #fef3c7;
239              color: #92400e;
240          }
241  
242          .stats {
243              display: grid;
244              grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
245              gap: 1rem;
246              margin-top: 1rem;
247          }
248  
249          .stat {
250              text-align: center;
251              padding: 1rem;
252              background: #f7fafc;
253              border-radius: 8px;
254          }
255  
256          .stat-value {
257              font-size: 1.75rem;
258              font-weight: 700;
259              color: #667eea;
260          }
261  
262          .stat-label {
263              font-size: 0.75rem;
264              color: #666;
265              text-transform: uppercase;
266              letter-spacing: 0.05em;
267              margin-top: 0.25rem;
268          }
269  
270          @keyframes pulse {
271  
272              0%,
273              100% {
274                  opacity: 1;
275              }
276  
277              50% {
278                  opacity: 0.5;
279              }
280          }
281  
282          .pulse {
283              animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
284          }
285      </style>
286  </head>
287  
288  <body>
289      <div class="container">
290          <header>
291              <h1>🚀 P2P Protocol Stack - Multi-Peer Demo</h1>
292              <p class="subtitle">Open multiple browser windows to see peers communicate!</p>
293          </header>
294  
295          <div class="peer-info">
296              <strong>Your Peer ID:</strong>
297              <div class="peer-id" id="myPeerId">Initializing...</div>
298          </div>
299  
300          <div class="grid">
301              <!-- Left Column -->
302              <div>
303                  <div class="panel">
304                      <h2>🔐 Identity & Discovery</h2>
305                      <div class="button-group">
306                          <button onclick="demo.createIdentity()">New Identity</button>
307                          <button onclick="demo.discoverPeers()">Discover Peers</button>
308                          <button onclick="demo.announcePresence()">Announce Presence</button>
309                      </div>
310  
311                      <h3 style="font-size: 1rem; margin: 1rem 0 0.5rem 0;">Online Peers:</h3>
312                      <ul class="peers-list" id="peersList">
313                          <li style="color: #999; font-size: 0.875rem;">No peers discovered yet</li>
314                      </ul>
315                  </div>
316  
317                  <div class="panel" style="margin-top: 1.5rem;">
318                      <h2>💾 Content & Storage</h2>
319                      <div class="button-group">
320                          <button onclick="demo.createContent()">Create Content</button>
321                          <button onclick="demo.announceContent()">Announce Content</button>
322                          <button onclick="demo.findContent()">Find Content</button>
323                      </div>
324  
325                      <div class="stats">
326                          <div class="stat">
327                              <div class="stat-value" id="contentCount">0</div>
328                              <div class="stat-label">Content Items</div>
329                          </div>
330                          <div class="stat">
331                              <div class="stat-value" id="announcedCount">0</div>
332                              <div class="stat-label">Announced</div>
333                          </div>
334                      </div>
335                  </div>
336              </div>
337  
338              <!-- Right Column -->
339              <div>
340                  <div class="panel">
341                      <h2>📡 Topics & PubSub</h2>
342                      <div class="button-group">
343                          <button onclick="demo.createTopic()">Create Topic</button>
344                          <button onclick="demo.publishMessage()">Publish Message</button>
345                          <button onclick="demo.subscribeToTopic()">Subscribe to Peer</button>
346                      </div>
347  
348                      <h3 style="font-size: 1rem; margin: 1rem 0 0.5rem 0;">Active Topics:</h3>
349                      <ul class="topics-list" id="topicsList">
350                          <li style="color: #999; font-size: 0.875rem;">No topics yet</li>
351                      </ul>
352                  </div>
353  
354                  <div class="panel" style="margin-top: 1.5rem;">
355                      <h2>🔄 Collaboration</h2>
356                      <div class="button-group">
357                          <button onclick="demo.createDocument()">Create Document</button>
358                          <button onclick="demo.editDocument()">Edit Document</button>
359                          <button onclick="demo.shareDocument()">Share with Peer</button>
360                      </div>
361  
362                      <div class="stats">
363                          <div class="stat">
364                              <div class="stat-value" id="documentCount">0</div>
365                              <div class="stat-label">Documents</div>
366                          </div>
367                          <div class="stat">
368                              <div class="stat-value" id="editsCount">0</div>
369                              <div class="stat-label">Edits</div>
370                          </div>
371                      </div>
372                  </div>
373              </div>
374          </div>
375  
376          <div class="panel">
377              <h2>📝 Activity Log</h2>
378              <div class="button-group">
379                  <button class="secondary" onclick="demo.clearLog()">Clear Log</button>
380                  <button class="danger" onclick="demo.reset()">Reset Everything</button>
381              </div>
382              <div id="output" class="output">
383                  <div class="log-entry">
384                      <span class="timestamp">[Ready]</span>
385                      <span class="info">Multi-peer demo initialized. Open this page in multiple windows!</span>
386                  </div>
387              </div>
388          </div>
389      </div>
390  
391      <script type="module">
392          class MultiPeerDemo {
393              constructor() {
394                  this.peerId = this.generatePeerId();
395                  this.peers = new Map();
396                  this.content = new Map();
397                  this.topics = new Map();
398                  this.documents = new Map();
399                  this.stats = {
400                      content: 0,
401                      announced: 0,
402                      documents: 0,
403                      edits: 0
404                  };
405  
406                  this.initializeStorage();
407                  this.startListening();
408                  this.updateUI();
409  
410                  document.getElementById('myPeerId').textContent = this.peerId;
411                  this.log('Peer initialized: ' + this.peerId, 'success');
412                  this.log('Open this page in multiple windows to see P2P in action!', 'info');
413              }
414  
415              generatePeerId() {
416                  return 'peer-' + Math.random().toString(36).substring(2, 10);
417              }
418  
419              initializeStorage() {
420                  // Initialize localStorage for cross-window communication
421                  if (!localStorage.getItem('p2p-network')) {
422                      localStorage.setItem('p2p-network', JSON.stringify({
423                          peers: {},
424                          content: {},
425                          topics: {},
426                          messages: []
427                      }));
428                  }
429              }
430  
431              startListening() {
432                  // Listen for storage events (cross-window communication)
433                  window.addEventListener('storage', (e) => {
434                      if (e.key === 'p2p-network') {
435                          this.handleNetworkUpdate();
436                      }
437                  });
438  
439                  // Poll for updates every 500ms
440                  setInterval(() => this.handleNetworkUpdate(), 500);
441              }
442  
443              handleNetworkUpdate() {
444                  const network = JSON.parse(localStorage.getItem('p2p-network') || '{}');
445  
446                  // Update peers
447                  if (network.peers) {
448                      Object.entries(network.peers).forEach(([id, peer]) => {
449                          if (id !== this.peerId && !this.peers.has(id)) {
450                              this.peers.set(id, peer);
451                              this.log(`Peer discovered: ${id}`, 'success');
452                          }
453                      });
454                  }
455  
456                  // Process new messages
457                  if (network.messages) {
458                      network.messages.forEach(msg => {
459                          if (msg.to === this.peerId && !msg.processed) {
460                              this.handleMessage(msg);
461                              msg.processed = true;
462                          }
463                      });
464                      localStorage.setItem('p2p-network', JSON.stringify(network));
465                  }
466  
467                  this.updateUI();
468              }
469  
470              handleMessage(msg) {
471                  switch (msg.type) {
472                      case 'topic-message':
473                          this.log(`📨 Received on topic "${msg.topic}": ${msg.content}`, 'info');
474                          break;
475                      case 'content-share':
476                          this.log(`📦 Received content: ${msg.cid}`, 'success');
477                          this.content.set(msg.cid, msg.data);
478                          this.stats.content++;
479                          break;
480                      case 'document-edit':
481                          this.log(`✏️ Document edited by ${msg.from}: ${msg.edit}`, 'info');
482                          this.stats.edits++;
483                          break;
484                  }
485              }
486  
487              broadcastToNetwork(data) {
488                  const network = JSON.parse(localStorage.getItem('p2p-network') || '{}');
489                  network.messages = network.messages || [];
490                  network.messages.push({
491                      ...data,
492                      from: this.peerId,
493                      timestamp: Date.now(),
494                      processed: false
495                  });
496                  localStorage.setItem('p2p-network', JSON.stringify(network));
497              }
498  
499              updateUI() {
500                  // Update peers list
501                  const peersList = document.getElementById('peersList');
502                  if (this.peers.size === 0) {
503                      peersList.innerHTML = '<li style="color: #999; font-size: 0.875rem;">No peers discovered yet</li>';
504                  } else {
505                      peersList.innerHTML = Array.from(this.peers.entries())
506                          .map(([id, peer]) => `
507                              <li class="peer-item online">
508                                  <div class="peer-name">${id} <span class="badge badge-success">Online</span></div>
509                                  <div class="peer-did">${peer.did || 'did:key:z6Mk...'}</div>
510                              </li>
511                          `).join('');
512                  }
513  
514                  // Update topics list
515                  const topicsList = document.getElementById('topicsList');
516                  if (this.topics.size === 0) {
517                      topicsList.innerHTML = '<li style="color: #999; font-size: 0.875rem;">No topics yet</li>';
518                  } else {
519                      topicsList.innerHTML = Array.from(this.topics.entries())
520                          .map(([name, topic]) => `
521                              <li class="topic-item">
522                                  <span class="topic-name">${name}</span>
523                                  <span class="subscriber-count">${topic.subscribers || 0} subscribers</span>
524                              </li>
525                          `).join('');
526                  }
527  
528                  // Update stats
529                  document.getElementById('contentCount').textContent = this.stats.content;
530                  document.getElementById('announcedCount').textContent = this.stats.announced;
531                  document.getElementById('documentCount').textContent = this.stats.documents;
532                  document.getElementById('editsCount').textContent = this.stats.edits;
533              }
534  
535              log(message, type = 'info') {
536                  const output = document.getElementById('output');
537                  const timestamp = new Date().toLocaleTimeString();
538                  const entry = document.createElement('div');
539                  entry.className = 'log-entry';
540                  entry.innerHTML = `
541                      <span class="timestamp">[${timestamp}]</span>
542                      <span class="${type}">${message}</span>
543                  `;
544                  output.appendChild(entry);
545                  output.scrollTop = output.scrollHeight;
546              }
547  
548              clearLog() {
549                  document.getElementById('output').innerHTML = '';
550                  this.log('Log cleared', 'info');
551              }
552  
553              async sleep(ms) {
554                  return new Promise(resolve => setTimeout(resolve, ms));
555              }
556  
557              // === Identity & Discovery ===
558  
559              async createIdentity() {
560                  this.log('Generating new ed25519 keypair...', 'info');
561                  await this.sleep(300);
562  
563                  const did = 'did:key:z6Mk' + Math.random().toString(36).substring(2, 15);
564                  this.log(`✓ New identity created: ${did}`, 'success');
565  
566                  return did;
567              }
568  
569              async announcePresence() {
570                  this.log('Announcing presence to network...', 'info');
571                  await this.sleep(200);
572  
573                  const network = JSON.parse(localStorage.getItem('p2p-network') || '{}');
574                  network.peers = network.peers || {};
575                  network.peers[this.peerId] = {
576                      did: 'did:key:z6Mk' + Math.random().toString(36).substring(2, 10),
577                      lastSeen: Date.now()
578                  };
579                  localStorage.setItem('p2p-network', JSON.stringify(network));
580  
581                  this.log(`✓ Presence announced as ${this.peerId}`, 'success');
582              }
583  
584              async discoverPeers() {
585                  this.log('Querying DHT for peers...', 'info');
586                  await this.sleep(400);
587  
588                  const network = JSON.parse(localStorage.getItem('p2p-network') || '{}');
589                  const peerCount = Object.keys(network.peers || {}).length - 1; // Exclude self
590  
591                  this.log(`✓ Found ${peerCount} peer(s) in network`, 'success');
592                  if (peerCount === 0) {
593                      this.log('💡 Tip: Open this page in another window to create more peers!', 'warning');
594                  }
595              }
596  
597              // === Content & Storage ===
598  
599              async createContent() {
600                  this.log('Creating content block...', 'info');
601                  await this.sleep(300);
602  
603                  const content = {
604                      message: 'Hello from ' + this.peerId,
605                      timestamp: Date.now()
606                  };
607  
608                  const cid = 'bafybei' + Math.random().toString(36).substring(2, 15);
609                  this.content.set(cid, content);
610                  this.stats.content++;
611  
612                  this.log(`✓ Content created with CID: ${cid}`, 'success');
613                  this.updateUI();
614  
615                  return cid;
616              }
617  
618              async announceContent() {
619                  if (this.content.size === 0) {
620                      this.log('No content to announce. Create content first!', 'warning');
621                      return;
622                  }
623  
624                  this.log('Announcing content to DHT...', 'info');
625                  await this.sleep(300);
626  
627                  const network = JSON.parse(localStorage.getItem('p2p-network') || '{}');
628                  network.content = network.content || {};
629  
630                  this.content.forEach((data, cid) => {
631                      network.content[cid] = {
632                          provider: this.peerId,
633                          timestamp: Date.now()
634                      };
635                  });
636  
637                  localStorage.setItem('p2p-network', JSON.stringify(network));
638                  this.stats.announced = this.content.size;
639  
640                  this.log(`✓ Announced ${this.content.size} content item(s)`, 'success');
641                  this.updateUI();
642              }
643  
644              async findContent() {
645                  this.log('Searching DHT for content...', 'info');
646                  await this.sleep(400);
647  
648                  const network = JSON.parse(localStorage.getItem('p2p-network') || '{}');
649                  const contentCount = Object.keys(network.content || {}).length;
650  
651                  this.log(`✓ Found ${contentCount} content item(s) in network`, 'success');
652  
653                  if (contentCount > 0) {
654                      Object.entries(network.content).forEach(([cid, info]) => {
655                          this.log(`  📦 ${cid} (provider: ${info.provider})`, 'info');
656                      });
657                  }
658              }
659  
660              // === Topics & PubSub ===
661  
662              async createTopic() {
663                  this.log('Creating DID-rooted topic...', 'info');
664                  await this.sleep(300);
665  
666                  const topicName = `${this.peerId}/public/messages`;
667                  this.topics.set(topicName, {
668                      subscribers: 1,
669                      messages: []
670                  });
671  
672                  this.log(`✓ Topic created: ${topicName}`, 'success');
673                  this.log('Other peers can now subscribe to this topic', 'info');
674                  this.updateUI();
675  
676                  return topicName;
677              }
678  
679              async publishMessage() {
680                  if (this.topics.size === 0) {
681                      await this.createTopic();
682                  }
683  
684                  const topic = Array.from(this.topics.keys())[0];
685                  const message = `Hello at ${new Date().toLocaleTimeString()}`;
686  
687                  this.log(`Publishing to topic: ${topic}`, 'info');
688                  await this.sleep(200);
689  
690                  // Broadcast to all peers
691                  this.broadcastToNetwork({
692                      type: 'topic-message',
693                      topic: topic,
694                      content: message,
695                      to: 'all'
696                  });
697  
698                  this.log(`✓ Message published: "${message}"`, 'success');
699              }
700  
701              async subscribeToTopic() {
702                  if (this.peers.size === 0) {
703                      this.log('No peers available. Discover peers first!', 'warning');
704                      return;
705                  }
706  
707                  const peerId = Array.from(this.peers.keys())[0];
708                  const topic = `${peerId}/public/messages`;
709  
710                  this.log(`Subscribing to ${peerId}'s topic...`, 'info');
711                  await this.sleep(300);
712  
713                  this.topics.set(topic, { subscribers: 1, messages: [] });
714                  this.log(`✓ Subscribed to: ${topic}`, 'success');
715                  this.log('You will receive messages published to this topic', 'info');
716                  this.updateUI();
717              }
718  
719              // === Collaboration ===
720  
721              async createDocument() {
722                  this.log('Creating CRDT document...', 'info');
723                  await this.sleep(400);
724  
725                  const docId = 'doc-' + Math.random().toString(36).substring(2, 10);
726                  this.documents.set(docId, {
727                      title: 'Collaborative Document',
728                      content: '',
729                      author: this.peerId,
730                      edits: []
731                  });
732                  this.stats.documents++;
733  
734                  this.log(`✓ Document created: ${docId}`, 'success');
735                  this.log('Using Automerge for conflict-free merging', 'info');
736                  this.updateUI();
737  
738                  return docId;
739              }
740  
741              async editDocument() {
742                  if (this.documents.size === 0) {
743                      await this.createDocument();
744                  }
745  
746                  const docId = Array.from(this.documents.keys())[0];
747                  const edit = `Edit by ${this.peerId} at ${new Date().toLocaleTimeString()}`;
748  
749                  this.log(`Editing document: ${docId}`, 'info');
750                  await this.sleep(300);
751  
752                  const doc = this.documents.get(docId);
753                  doc.content += edit + '\n';
754                  doc.edits.push(edit);
755                  this.stats.edits++;
756  
757                  // Broadcast edit to collaborators
758                  this.broadcastToNetwork({
759                      type: 'document-edit',
760                      docId: docId,
761                      edit: edit,
762                      to: 'all'
763                  });
764  
765                  this.log(`✓ Document edited (${doc.edits.length} total edits)`, 'success');
766                  this.updateUI();
767              }
768  
769              async shareDocument() {
770                  if (this.documents.size === 0) {
771                      this.log('No documents to share. Create a document first!', 'warning');
772                      return;
773                  }
774  
775                  if (this.peers.size === 0) {
776                      this.log('No peers to share with. Discover peers first!', 'warning');
777                      return;
778                  }
779  
780                  const docId = Array.from(this.documents.keys())[0];
781                  const peerId = Array.from(this.peers.keys())[0];
782  
783                  this.log(`Sharing document ${docId} with ${peerId}...`, 'info');
784                  await this.sleep(400);
785  
786                  this.log(`✓ Document shared via UCAN capability`, 'success');
787                  this.log(`${peerId} can now collaborate on this document`, 'info');
788              }
789  
790              reset() {
791                  if (confirm('Reset everything? This will clear all data for all peers.')) {
792                      localStorage.removeItem('p2p-network');
793                      this.initializeStorage();
794                      this.peers.clear();
795                      this.content.clear();
796                      this.topics.clear();
797                      this.documents.clear();
798                      this.stats = { content: 0, announced: 0, documents: 0, edits: 0 };
799                      this.clearLog();
800                      this.log('Everything reset!', 'success');
801                      this.updateUI();
802                  }
803              }
804          }
805  
806          // Initialize demo
807          window.demo = new MultiPeerDemo();
808      </script>
809  </body>
810  
811  </html>