Skip to content

teslasolar/kp2p

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

250 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🌐 Konomi P2P

Peer-to-peer client for GitHub Pages - no server needed.

Transform any static website into a collaborative, real-time P2P application with just one script tag.

✨ Features

  • πŸ”— No Server Required - Pure peer-to-peer, works on GitHub Pages
  • πŸ” Auto Discovery - Peers find each other via DHT, pubsub, and bootstrap nodes
  • πŸ”„ Real-time Sync - CRDTs ensure conflict-free collaboration (Yjs)
  • πŸ’Ύ Offline First - Works offline, syncs when back online
  • πŸ” End-to-End Encrypted - All data encrypted with TweetNaCl
  • πŸ“± Works Everywhere - Browser-based, no extensions needed

πŸš€ Quick Start

Add P2P capabilities to any static page with one line:

<script type="module">
  import { injectP2P } from 'https://unpkg.com/konomi-p2p/dist/konomi-p2p.min.js'

  const p2p = await injectP2P()

  // Get shared state - syncs to all peers automatically
  const state = p2p.getSharedMap('app-state')

  // React to changes from any peer
  state.observe(e => {
    console.log('State changed:', state.toJSON())
  })

  // Make changes - automatically synced
  state.set('counter', (state.get('counter') || 0) + 1)

  // Share invite link
  console.log('Invite:', p2p.invite())
</script>

πŸ“¦ Installation

CDN

<script type="module">
  import { injectP2P } from 'https://unpkg.com/konomi-p2p'
</script>

NPM

npm install konomi-p2p
import { injectP2P } from 'konomi-p2p'

const p2p = await injectP2P()

πŸ“– API

Basic Usage

const p2p = await injectP2P({
  // Optional configuration
  user: { name: 'Alice', color: '#FF6B6B' },
  roomId: 'my-room',  // Or use URL hash
  showUI: true        // Show status overlay
})

// Shared data types
const map = p2p.getSharedMap('my-map')      // Key-value store
const array = p2p.getSharedArray('my-list') // Ordered list
const text = p2p.getSharedText('my-doc')    // Collaborative text

// Get connected peers
const peers = p2p.getPeers()

// Get users with presence info
const users = p2p.getUsers()

// Set your cursor position (for presence)
p2p.setCursor({ x: 100, y: 200 })

// Switch rooms
p2p.switchRoom('new-room-id')

// Get invite link
const link = p2p.invite()

Shared Data Types

// Map - key-value store
const map = p2p.getSharedMap('state')
map.set('key', 'value')
map.get('key')  // 'value'
map.delete('key')
map.observe(event => { /* handle changes */ })

// Array - ordered list
const arr = p2p.getSharedArray('items')
arr.push(['item1', 'item2'])
arr.insert(0, ['first'])
arr.delete(1, 1)  // delete 1 item at index 1
arr.toArray()     // get as regular array

// Text - collaborative text editing
const text = p2p.getSharedText('document')
text.insert(0, 'Hello ')
text.insert(6, 'World!')
text.toString()   // 'Hello World!'

Adapters

Easily sync existing page elements:

import { createAdapters } from 'konomi-p2p'

const adapters = createAdapters(p2p.doc)

// Sync a form with CRDT
const formAdapter = adapters.form('#my-form')
formAdapter.start()

// Sync a canvas for collaborative drawing
const canvasAdapter = adapters.canvas('#whiteboard')
canvasAdapter.start()

// Sync contenteditable element
const textAdapter = adapters.contentEditable('#editor')
textAdapter.start()

Events

// Peer connected
p2p.on('peer:connect', (event) => {
  console.log('Peer connected:', event.detail)
})

// Peer disconnected
p2p.on('peer:disconnect', (event) => {
  console.log('Peer disconnected:', event.detail)
})

// Document updated
p2p.on('update', (update) => {
  console.log('Document updated')
})

// Room switched
p2p.on('room:switch', ({ roomId }) => {
  console.log('Switched to room:', roomId)
})

RPC - Call Methods on Peers

// Register a method
p2p.registerMethod('greet', async (params, context) => {
  return `Hello from ${p2p.peerId}!`
})

// Call method on a specific peer
const result = await p2p.callPeer(peerId, 'greet', { name: 'Alice' })

// Broadcast to all peers
await p2p.broadcast('notify', { message: 'Hello everyone!' })

File Sharing

// Share a file
const hash = await p2p.shareBlob(fileData)

// Request from peer
const data = await p2p.requestBlob(peerId, hash, (progress) => {
  console.log(`${progress * 100}% complete`)
})

πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Your Static Page                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Konomi P2P API                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Shared State (Yjs CRDTs)                           β”‚   β”‚
β”‚  β”‚  Map | Array | Text | XML                           β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚  Awareness    β”‚ β”‚  Persistence β”‚ β”‚  Adapters      β”‚    β”‚
β”‚  β”‚  (presence)   β”‚ β”‚  (IndexedDB) β”‚ β”‚  (forms, etc)  β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  libp2p Network                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚ GossipSubβ”‚ β”‚ Kad-DHT   β”‚ β”‚ WebRTC   β”‚ β”‚ Relay     β”‚   β”‚
β”‚  β”‚ (pubsub) β”‚ β”‚ (routing) β”‚ β”‚ (direct) β”‚ β”‚ (fallback)β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         ↑                    ↑                    ↑
         β”‚      P2P Mesh      β”‚                    β”‚
         ↓                    ↓                    ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Peer A    │◄────►│   Peer B    │◄────►│   Peer C    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”§ Configuration

const p2p = await injectP2P({
  // Your display info
  user: {
    name: 'Your Name',
    color: '#FF6B6B',
    avatar: 'https://...'
  },

  // Room ID (defaults to URL hash)
  roomId: 'my-room',

  // Show status UI
  showUI: true,

  // Bootstrap peers
  bootstrap: [
    '/dns4/bootstrap.libp2p.io/tcp/443/wss/p2p/QmNnooDu7...'
  ],

  // Connection limits
  minConnections: 5,
  maxConnections: 50
})

πŸ” Security

  • Identity: Ed25519 keypairs stored in IndexedDB
  • Transport: Noise protocol encryption
  • Messages: Can be encrypted with shared room keys
  • Signatures: All messages can be signed and verified

πŸ“± Browser Support

  • βœ… Chrome 80+
  • βœ… Firefox 75+
  • βœ… Safari 14+
  • βœ… Edge 80+

Requires WebRTC and ES Modules support.

πŸ“š Examples

Chat Room

<div id="messages"></div>
<input id="input" placeholder="Type a message...">

<script type="module">
  import { injectP2P } from 'konomi-p2p'

  const p2p = await injectP2P()
  const messages = p2p.getSharedArray('messages')

  messages.observe(() => {
    document.getElementById('messages').innerHTML =
      messages.toArray().map(m => `<p><b>${m.user}:</b> ${m.text}</p>`).join('')
  })

  document.getElementById('input').onkeydown = (e) => {
    if (e.key === 'Enter') {
      messages.push([{
        user: p2p.getUsers().find(u => u.clientId === p2p.doc.clientID)?.name || 'Anonymous',
        text: e.target.value,
        time: Date.now()
      }])
      e.target.value = ''
    }
  }
</script>

Collaborative Whiteboard

<canvas id="canvas" width="800" height="600"></canvas>

<script type="module">
  import { injectP2P, createAdapters } from 'konomi-p2p'

  const p2p = await injectP2P()
  const adapters = createAdapters(p2p.doc)

  const canvas = adapters.canvas('#canvas', 'drawings')
  canvas.start()

  // Change color
  document.getElementById('color').onchange = (e) => {
    canvas.setColor(e.target.value)
  }
</script>

πŸ› οΈ Development

# Clone
git clone https://github.com/konomi/kp2p

# Install
npm install

# Development build with watch
npm run dev

# Production build
npm run build

πŸ“„ License

MIT

πŸ™ Credits

Built with:


Made with 🌐 for the decentralized web

About

konomi peer to peer

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages