basic chat app
This commit is contained in:
@@ -1,17 +1,25 @@
|
|||||||
# Phase 7: Building Your First P2P App
|
# Phase 7: Building Your First P2P App
|
||||||
|
|
||||||
## Let's Build Something!
|
## Let's Build Something Real!
|
||||||
|
|
||||||
Now that you understand the concepts, let's build an actual P2P application together.
|
Now that you understand the concepts, let's build an actual P2P application together.
|
||||||
|
|
||||||
We'll build a **P2P Chat App** - step by step.
|
We'll build a **P2P Chat App** - step by step. By the end, you'll have a working chat app where multiple people can talk directly to each other without any server!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What We're Building
|
||||||
|
|
||||||
|
A simple anonymous chat app where:
|
||||||
|
- Everyone joins the same chat room automatically
|
||||||
|
- Messages go directly from one peer to another
|
||||||
|
- No server needed - just run the code
|
||||||
|
- Works between any two computers on the internet
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
Before we start, make sure you have:
|
|
||||||
|
|
||||||
### 1. Node.js Installed
|
### 1. Node.js Installed
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -24,18 +32,18 @@ node --version
|
|||||||
### 2. Create a Project Folder
|
### 2. Create a Project Folder
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir my-p2p-chat
|
mkdir hyperswarm-chat
|
||||||
cd my-p2p-chat
|
cd hyperswarm-chat
|
||||||
npm init -y
|
npm init -y
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Install Dependencies
|
### 3. Install Hyperswarm
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install hypercore hyperswarm
|
npm install hyperswarm
|
||||||
```
|
```
|
||||||
|
|
||||||
That's it! Just two packages to build a P2P chat app.
|
That's it! One package - that's all you need for P2P networking.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -44,11 +52,55 @@ That's it! Just two packages to build a P2P chat app.
|
|||||||
Create a file called `chat.js`:
|
Create a file called `chat.js`:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Step 1: Import our tools
|
|
||||||
const Hypercore = require('hypercore')
|
|
||||||
const Hyperswarm = require('hyperswarm')
|
const Hyperswarm = require('hyperswarm')
|
||||||
|
const crypto = require('crypto')
|
||||||
|
|
||||||
console.log('🚀 Starting P2P Chat...')
|
console.log('Starting P2P Chat...')
|
||||||
|
|
||||||
|
// Create a topic (this determines our chat room)
|
||||||
|
const ROOM = 'basic-p2p-chat-2026'
|
||||||
|
const topic = crypto.createHash('sha256').update(ROOM).digest()
|
||||||
|
|
||||||
|
console.log('Topic:', topic.toString('hex'))
|
||||||
|
```
|
||||||
|
|
||||||
|
Run it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node chat.js
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see a hex string - that's your topic key.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Create the Swarm
|
||||||
|
|
||||||
|
Now let's create the P2P networking layer:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const Hyperswarm = require('hyperswarm')
|
||||||
|
const crypto = require('crypto')
|
||||||
|
|
||||||
|
async function start() {
|
||||||
|
const ROOM = 'basic-p2p-chat-2026'
|
||||||
|
const topic = crypto.createHash('sha256').update(ROOM).digest()
|
||||||
|
|
||||||
|
// Create the swarm
|
||||||
|
const swarm = new Hyperswarm()
|
||||||
|
|
||||||
|
console.log('Swarm created, joining room...')
|
||||||
|
|
||||||
|
// Join the chat room - both as server and client
|
||||||
|
const discovery = swarm.join(topic, { server: true, client: true })
|
||||||
|
|
||||||
|
// Wait for discovery to flush (find peers)
|
||||||
|
await discovery.flushed()
|
||||||
|
|
||||||
|
console.log('Connected to chat room!')
|
||||||
|
}
|
||||||
|
|
||||||
|
start()
|
||||||
```
|
```
|
||||||
|
|
||||||
Run it:
|
Run it:
|
||||||
@@ -56,462 +108,360 @@ Run it:
|
|||||||
node chat.js
|
node chat.js
|
||||||
```
|
```
|
||||||
|
|
||||||
You should see: `🚀 Starting P2P Chat...`
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Step 2: Create a Feed
|
## Step 3: Handle Connections
|
||||||
|
|
||||||
Add the Hypercore setup:
|
Now let's handle when peers connect:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const Hypercore = require('hypercore')
|
|
||||||
const Hyperswarm = require('hyperswarm')
|
const Hyperswarm = require('hyperswarm')
|
||||||
|
const crypto = require('crypto')
|
||||||
|
|
||||||
console.log('🚀 Starting P2P Chat...')
|
async function start() {
|
||||||
|
const ROOM = 'basic-p2p-chat-2026'
|
||||||
|
const topic = crypto.createHash('sha256').update(ROOM).digest()
|
||||||
|
|
||||||
// Create a feed (your personal message log)
|
|
||||||
// This is like your personal notebook
|
|
||||||
const core = new Hypercore('./chat-storage')
|
|
||||||
|
|
||||||
// Wait for it to be ready
|
|
||||||
core.ready().then(() => {
|
|
||||||
console.log('📓 Feed created!')
|
|
||||||
console.log(' Your public key:', core.key.toString('hex').slice(0, 16) + '...')
|
|
||||||
console.log(' Messages so far:', core.length)
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Run it:
|
|
||||||
```bash
|
|
||||||
node chat.js
|
|
||||||
```
|
|
||||||
|
|
||||||
You should see something like:
|
|
||||||
```
|
|
||||||
🚀 Starting P2P Chat...
|
|
||||||
📓 Feed created!
|
|
||||||
Your public key: a1b2c3d4e5f6...
|
|
||||||
Messages so far: 0
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 3: Add Networking
|
|
||||||
|
|
||||||
Now let's make it discoverable:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const Hypercore = require('hypercore')
|
|
||||||
const Hyperswarm = require('hyperswarm')
|
|
||||||
|
|
||||||
console.log('🚀 Starting P2P Chat...')
|
|
||||||
|
|
||||||
// Create feed
|
|
||||||
const core = new Hypercore('./chat-storage')
|
|
||||||
|
|
||||||
// Create swarm (networking)
|
|
||||||
const swarm = new Hyperswarm()
|
const swarm = new Hyperswarm()
|
||||||
|
const connections = new Set()
|
||||||
|
|
||||||
core.ready().then(() => {
|
// Handle incoming connections
|
||||||
console.log('📓 Feed ready!')
|
swarm.on('connection', (conn) => {
|
||||||
console.log(' Key:', core.key.toString('hex').slice(0, 16) + '...')
|
connections.add(conn)
|
||||||
|
console.log('[+] New peer connected')
|
||||||
|
|
||||||
// Join the swarm using our feed's discovery key
|
// Handle incoming messages
|
||||||
// This makes us findable by others interested in this chat
|
conn.on('data', (data) => {
|
||||||
swarm.join(core.discoveryKey, { server: true, client: true })
|
console.log('Received:', data.toString())
|
||||||
|
|
||||||
console.log('🌐 Looking for peers...')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// When we find a peer
|
// Handle disconnection
|
||||||
swarm.on('connection', (connection, info) => {
|
conn.on('close', () => {
|
||||||
console.log('✅ Peer connected!', info.peer.publicKey.toString('hex').slice(0, 8))
|
connections.delete(conn)
|
||||||
|
console.log('[-] Peer disconnected')
|
||||||
// Replicate with them (share messages)
|
|
||||||
core.replicate(connection)
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const discovery = swarm.join(topic, { server: true, client: true })
|
||||||
|
await discovery.flushed()
|
||||||
|
|
||||||
|
console.log('Ready to chat!')
|
||||||
|
}
|
||||||
|
|
||||||
|
start()
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Step 4: Send Messages
|
## Step 4: Add Chat Interface
|
||||||
|
|
||||||
Now let's add the ability to send messages:
|
Now let's add the ability to type and send messages:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const Hypercore = require('hypercore')
|
|
||||||
const Hyperswarm = require('hyperswarm')
|
const Hyperswarm = require('hyperswarm')
|
||||||
|
const crypto = require('crypto')
|
||||||
const readline = require('readline')
|
const readline = require('readline')
|
||||||
|
|
||||||
console.log('🚀 Starting P2P Chat...')
|
async function start() {
|
||||||
|
const ROOM = 'basic-p2p-chat-2026'
|
||||||
|
const topic = crypto.createHash('sha256').update(ROOM).digest()
|
||||||
|
|
||||||
const core = new Hypercore('./chat-storage')
|
|
||||||
const swarm = new Hyperswarm()
|
const swarm = new Hyperswarm()
|
||||||
|
const connections = new Set()
|
||||||
|
|
||||||
// Create interface for typing
|
swarm.on('connection', (conn) => {
|
||||||
const rl = readline.createInterface({
|
connections.add(conn)
|
||||||
input: process.stdin,
|
console.log('\n[+] New peer connected')
|
||||||
output: process.stdout
|
|
||||||
|
conn.on('data', (data) => {
|
||||||
|
console.log(`\nFriend: ${data.toString().trim()}`)
|
||||||
|
rl.prompt()
|
||||||
})
|
})
|
||||||
|
|
||||||
core.ready().then(() => {
|
conn.on('close', () => {
|
||||||
console.log('📓 Feed ready!')
|
connections.delete(conn)
|
||||||
console.log(' Share this key with friends:', core.key.toString('hex'))
|
console.log('\n[-] Peer disconnected')
|
||||||
console.log()
|
})
|
||||||
|
|
||||||
swarm.join(core.discoveryKey, { server: true, client: true })
|
|
||||||
|
|
||||||
// Listen for new messages
|
|
||||||
core.on('append', () => {
|
|
||||||
// New message arrived! Show it.
|
|
||||||
showLatestMessages()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Start chat
|
const discovery = swarm.join(topic, { server: true, client: true })
|
||||||
promptForMessage()
|
await discovery.flushed()
|
||||||
})
|
|
||||||
|
|
||||||
function promptForMessage() {
|
console.log('\n=== P2P Chat Ready ===')
|
||||||
rl.question('You: ', (message) => {
|
console.log('Type a message and press Enter!\n')
|
||||||
if (message.trim()) {
|
|
||||||
// Send message to feed
|
|
||||||
core.append(Buffer.from(message))
|
|
||||||
}
|
|
||||||
promptForMessage() // Keep asking
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function showLatestMessages() {
|
|
||||||
// Show last 5 messages
|
|
||||||
const start = Math.max(0, core.length - 5)
|
|
||||||
for (let i = start; i < core.length; i++) {
|
|
||||||
core.get(i).then(data => {
|
|
||||||
console.log('📨 Friend:', data.toString())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 5: Full Working App
|
|
||||||
|
|
||||||
Here's the complete chat app:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const Hypercore = require('hypercore')
|
|
||||||
const Hyperswarm = require('hyperswarm')
|
|
||||||
const readline = require('readline')
|
|
||||||
|
|
||||||
console.log('╔═══════════════════════════════════╗')
|
|
||||||
console.log('║ 🚀 P2P Chat App v1.0 ║')
|
|
||||||
console.log('╚═══════════════════════════════════╝')
|
|
||||||
|
|
||||||
const core = new Hypercore('./my-chat')
|
|
||||||
const swarm = new Hyperswarm()
|
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout
|
output: process.stdout,
|
||||||
|
prompt: '> '
|
||||||
})
|
})
|
||||||
|
rl.prompt()
|
||||||
|
|
||||||
core.ready().then(() => {
|
rl.on('line', (input) => {
|
||||||
console.log('\n📓 Your chat feed is ready!')
|
const msg = input.trim()
|
||||||
console.log(' Share this key with friends to chat:')
|
if (!msg) {
|
||||||
console.log(' ' + core.key.toString('hex').slice(0, 32) + '...\n')
|
rl.prompt()
|
||||||
|
return
|
||||||
// Join swarm
|
|
||||||
swarm.join(core.discoveryKey, { server: true, client: true })
|
|
||||||
|
|
||||||
// Handle connections
|
|
||||||
swarm.on('connection', (conn, info) => {
|
|
||||||
console.log('✅ Peer connected!')
|
|
||||||
core.replicate(conn)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Show messages when they arrive
|
|
||||||
core.on('append', showMessages)
|
|
||||||
|
|
||||||
// Start the chat
|
|
||||||
console.log('💬 Type a message and press Enter!\n')
|
|
||||||
prompt()
|
|
||||||
})
|
|
||||||
|
|
||||||
function prompt() {
|
|
||||||
rl.question('You: ', (msg) => {
|
|
||||||
if (msg.trim()) {
|
|
||||||
core.append(Buffer.from(msg))
|
|
||||||
}
|
}
|
||||||
prompt()
|
|
||||||
|
// Show our own message
|
||||||
|
console.log(`\nYou: ${msg}`)
|
||||||
|
|
||||||
|
// Send to all connected peers
|
||||||
|
for (const conn of connections) {
|
||||||
|
if (!conn.destroyed) {
|
||||||
|
conn.write(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.prompt()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMessages() {
|
start()
|
||||||
console.log('\n--- Chat History ---')
|
|
||||||
for (let i = 0; i < core.length; i++) {
|
|
||||||
core.get(i).then(msg => {
|
|
||||||
console.log(' ' + msg.toString())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
console.log('-------------------\n')
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Save this as `chat.js` and run:
|
---
|
||||||
|
|
||||||
```bash
|
## Step 5: The Complete Chat App
|
||||||
node chat.js
|
|
||||||
|
Here's the complete, working P2P chat application:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const Hyperswarm = require('hyperswarm')
|
||||||
|
const crypto = require('crypto')
|
||||||
|
const readline = require('readline')
|
||||||
|
|
||||||
|
|
||||||
|
async function start() {
|
||||||
|
// Fixed room — change this string only if you want a different room
|
||||||
|
const ROOM = 'basic-p2p-chat-2026'
|
||||||
|
|
||||||
|
|
||||||
|
const topic = crypto.createHash('sha256').update(ROOM).digest()
|
||||||
|
|
||||||
|
|
||||||
|
const swarm = new Hyperswarm()
|
||||||
|
const connections = new Set()
|
||||||
|
|
||||||
|
|
||||||
|
swarm.on('connection', (conn) => {
|
||||||
|
connections.add(conn)
|
||||||
|
console.log('\n[+] New peer connected')
|
||||||
|
|
||||||
|
|
||||||
|
conn.on('data', (data) => {
|
||||||
|
process.stdout.write(`\n${data.toString().trim()}\n> `)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
conn.on('close', () => {
|
||||||
|
connections.delete(conn)
|
||||||
|
console.log('\n[-] Peer disconnected')
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
conn.on('error', () => {}) // ignore common network errors
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// Join the room (both server + client)
|
||||||
|
const discovery = swarm.join(topic, { server: true, client: true })
|
||||||
|
await discovery.flushed()
|
||||||
|
|
||||||
|
|
||||||
|
console.log(`\n=== Anonymous Hyperswarm Chat ===`)
|
||||||
|
console.log(`Room: ${ROOM}`)
|
||||||
|
console.log(`Just type and press Enter to chat.\nCommands: /peers /quit\n`)
|
||||||
|
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
prompt: '> '
|
||||||
|
})
|
||||||
|
rl.prompt()
|
||||||
|
|
||||||
|
|
||||||
|
rl.on('line', (input) => {
|
||||||
|
const msg = input.trim()
|
||||||
|
if (!msg) {
|
||||||
|
rl.prompt()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (msg === '/quit' || msg === '/exit') {
|
||||||
|
cleanup()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (msg === '/peers') {
|
||||||
|
console.log(`Connected peers: ${connections.size}`)
|
||||||
|
rl.prompt()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Local echo + broadcast to everyone
|
||||||
|
process.stdout.write(`\n${msg}\n> `)
|
||||||
|
|
||||||
|
|
||||||
|
for (const conn of connections) {
|
||||||
|
if (!conn.destroyed) {
|
||||||
|
conn.write(msg + '\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
rl.prompt()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
async function cleanup() {
|
||||||
|
rl.close()
|
||||||
|
for (const conn of connections) conn.destroy()
|
||||||
|
await swarm.destroy()
|
||||||
|
console.log('\nDisconnected. Goodbye!')
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
process.on('SIGINT', cleanup)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
start().catch(err => {
|
||||||
|
console.error('Error:', err.message)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Save this as `chat.js` and you're done!
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Step 6: Testing It Works
|
## Step 6: Testing It Works
|
||||||
|
|
||||||
To test with yourself:
|
### Terminal 1 - Start the chat:
|
||||||
|
|
||||||
1. Open two terminal windows
|
|
||||||
2. Run `node chat.js` in both
|
|
||||||
3. Copy the key from one terminal to the other
|
|
||||||
4. Type messages!
|
|
||||||
|
|
||||||
Actually, let me fix that - there's a bug. Let me make it cleaner:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Simpler version - just run in two terminals
|
|
||||||
// They will find each other automatically!
|
|
||||||
|
|
||||||
const Hypercore = require('hypercore')
|
|
||||||
const Hyperswarm = require('hyperswarm')
|
|
||||||
const readline = require('readline')
|
|
||||||
|
|
||||||
const core = new Hypercore('./chat-' + Date.now())
|
|
||||||
const swarm = new Hyperswarm()
|
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
|
||||||
input: process.stdin,
|
|
||||||
output: process.stdout
|
|
||||||
})
|
|
||||||
|
|
||||||
core.ready().then(() => {
|
|
||||||
console.log('\n📓 Feed ready!')
|
|
||||||
console.log(' Key:', core.key.toString('hex'))
|
|
||||||
console.log(' (Run this in another terminal and paste the key!)\n')
|
|
||||||
|
|
||||||
swarm.join(core.discoveryKey, { server: true, client: true })
|
|
||||||
|
|
||||||
swarm.on('connection', (conn, info) => {
|
|
||||||
console.log('\n✅ PEER CONNECTED!\n')
|
|
||||||
core.replicate(conn)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Listen for messages
|
|
||||||
core.on('append', () => {
|
|
||||||
const len = core.length
|
|
||||||
core.get(len - 1).then(msg => {
|
|
||||||
console.log('\n📨 Received:', msg.toString())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
prompt()
|
|
||||||
})
|
|
||||||
|
|
||||||
function prompt() {
|
|
||||||
rl.question('You: ', (msg) => {
|
|
||||||
if (msg.trim()) {
|
|
||||||
core.append(Buffer.from(msg))
|
|
||||||
}
|
|
||||||
prompt()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### To Test:
|
|
||||||
|
|
||||||
**Terminal 1:**
|
|
||||||
```bash
|
```bash
|
||||||
node chat.js
|
node chat.js
|
||||||
# Copy the key it shows
|
```
|
||||||
|
|
||||||
|
You'll see:
|
||||||
|
```
|
||||||
|
=== Anonymous Hyperswarm Chat ===
|
||||||
|
Room: basic-p2p-chat-2026
|
||||||
|
Just type and press Enter to chat.
|
||||||
|
Commands: /peers /quit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Terminal 2 - Join the same chat:
|
||||||
|
```bash
|
||||||
|
node chat.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### Watch for connection!
|
||||||
|
|
||||||
|
After a few seconds (first time can take 5-60 seconds for DHT discovery and NAT hole-punching), you'll see:
|
||||||
|
|
||||||
|
**Terminal 1:**
|
||||||
|
```
|
||||||
|
[+] New peer connected
|
||||||
```
|
```
|
||||||
|
|
||||||
**Terminal 2:**
|
**Terminal 2:**
|
||||||
```bash
|
|
||||||
node chat.js
|
|
||||||
# It will find Terminal 1 automatically!
|
|
||||||
# (They share the same discovery key)
|
|
||||||
```
|
```
|
||||||
|
[+] New peer connected
|
||||||
|
```
|
||||||
|
|
||||||
|
Now type in either terminal and press Enter - the message will appear in both!
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Step 7: Adding File Sharing
|
## How It Works
|
||||||
|
|
||||||
Now let's add file sharing to our chat!
|
### The Room Concept
|
||||||
|
|
||||||
|
```
|
||||||
|
const ROOM = 'basic-p2p-chat-2026'
|
||||||
|
const topic = crypto.createHash('sha256').update(ROOM).digest()
|
||||||
|
```
|
||||||
|
|
||||||
|
- We pick a room name (any string)
|
||||||
|
- We hash it to get a 32-byte topic
|
||||||
|
- Everyone who uses the same room name gets the same topic
|
||||||
|
- The DHT uses this topic to help peers find each other
|
||||||
|
|
||||||
|
### Joining the Swarm
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const Hypercore = require('hypercore')
|
const discovery = swarm.join(topic, { server: true, client: true })
|
||||||
const Hyperdrive = require('hyperdrive')
|
await discovery.flushed()
|
||||||
const Hyperswarm = require('hyperswarm')
|
|
||||||
const fs = require('fs')
|
|
||||||
const readline = require('readline')
|
|
||||||
|
|
||||||
console.log('📎 P2P Chat + File Sharing!')
|
|
||||||
|
|
||||||
const core = new Hypercore('./chat-storage')
|
|
||||||
const drive = new Hyperdrive(core)
|
|
||||||
const swarm = new Hyperswarm()
|
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
|
||||||
input: process.stdin,
|
|
||||||
output: process.stdout
|
|
||||||
})
|
|
||||||
|
|
||||||
core.ready().then(() => {
|
|
||||||
drive.ready().then(() => {
|
|
||||||
console.log('📓 Ready! Key:', core.key.toString('hex').slice(0, 16) + '...')
|
|
||||||
|
|
||||||
swarm.join(drive.discoveryKey, { server: true, client: true })
|
|
||||||
swarm.join(core.discoveryKey, { server: true, client: true })
|
|
||||||
|
|
||||||
swarm.on('connection', (conn) => {
|
|
||||||
console.log('🔗 Peer connected!')
|
|
||||||
core.replicate(conn)
|
|
||||||
drive.replicate(conn)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Handle commands
|
|
||||||
console.log('\nCommands:')
|
|
||||||
console.log(' msg <text> - Send a message')
|
|
||||||
console.log(' file <filepath> - Send a file\n')
|
|
||||||
|
|
||||||
prompt()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
function prompt() {
|
|
||||||
rl.question('> ', async (input) => {
|
|
||||||
const parts = input.split(' ')
|
|
||||||
const cmd = parts[0]
|
|
||||||
const arg = parts.slice(1).join(' ')
|
|
||||||
|
|
||||||
if (cmd === 'msg' && arg) {
|
|
||||||
await core.append(Buffer.from(arg))
|
|
||||||
console.log('✅ Message sent!')
|
|
||||||
} else if (cmd === 'file' && arg) {
|
|
||||||
try {
|
|
||||||
const content = fs.readFileSync(arg)
|
|
||||||
await drive.put('/' + arg, content)
|
|
||||||
console.log('✅ File sent! Key: /' + arg)
|
|
||||||
} catch (e) {
|
|
||||||
console.log('❌ Error:', e.message)
|
|
||||||
}
|
|
||||||
} else if (cmd === 'list') {
|
|
||||||
for await (const file of drive.readdir('/')) {
|
|
||||||
console.log(' 📄', file)
|
|
||||||
}
|
|
||||||
} else if (cmd === 'get' && arg) {
|
|
||||||
const content = await drive.get('/' + arg)
|
|
||||||
console.log('📄 File content:', content ? content.toString() : 'not found')
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
- `server: true` - Accept connections from other peers
|
||||||
|
- `client: true` - Connect to other peers we discover
|
||||||
|
- `discovery.flushed()` - Wait until we're fully connected to the network
|
||||||
|
|
||||||
## Step 8: Offline-First
|
### Sending Messages
|
||||||
|
|
||||||
One of P2P's superpowers: **offline-first**!
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// This app works OFFLINE!
|
conn.write(msg)
|
||||||
// Here's how:
|
|
||||||
|
|
||||||
const core = new Hypercore('./offline-chat')
|
|
||||||
const swarm = new Hyperswarm()
|
|
||||||
|
|
||||||
// 1. APPEND WORKS OFFLINE
|
|
||||||
// You can always add to your feed
|
|
||||||
await core.append(Buffer.from('Message'))
|
|
||||||
|
|
||||||
// Even without internet!
|
|
||||||
// It just waits in your local storage
|
|
||||||
|
|
||||||
// 2. WHEN ONLINE, IT SYNCS
|
|
||||||
core.ready().then(() => {
|
|
||||||
swarm.join(core.discoveryKey)
|
|
||||||
|
|
||||||
swarm.on('connection', conn => {
|
|
||||||
// Automatically syncs when peer found
|
|
||||||
core.replicate(conn)
|
|
||||||
console.log('🔄 Synced!')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// 3. YOU DON'T NEED TO CHANGE YOUR CODE!
|
|
||||||
// Just append, and it syncs when possible
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- Each connection is a simple stream
|
||||||
|
- We just write raw data to it
|
||||||
|
- The receiving end gets it via the 'data' event
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Step 9: Making It Distributable
|
## Customizing the Chat Room
|
||||||
|
|
||||||
Want to share your app? Use Pear!
|
Want a different chat room? Just change the `ROOM` variable:
|
||||||
|
|
||||||
```bash
|
```javascript
|
||||||
# Install Pear
|
const ROOM = 'my-secret-room-123'
|
||||||
npm install -g pear
|
|
||||||
|
|
||||||
# Initialize Pear project
|
|
||||||
pear init my-chat-app
|
|
||||||
|
|
||||||
# Put your code in app.js
|
|
||||||
|
|
||||||
# Run locally
|
|
||||||
pear run
|
|
||||||
|
|
||||||
# Build for distribution
|
|
||||||
pear build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Now others can install your app directly from you!
|
Everyone who uses the same room name will find each other!
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## What We've Built
|
## Features Included
|
||||||
|
|
||||||
| Feature | Code |
|
| Feature | How It's Done |
|
||||||
|---------|------|
|
|---------|---------------|
|
||||||
| ✅ P2P Chat | Hypercore + Hyperswarm |
|
| **P2P Networking** | Hyperswarm |
|
||||||
| ✅ Peer Discovery | Auto via DHT |
|
| **Peer Discovery** | HyperDHT (built into Hyperswarm) |
|
||||||
| ✅ Message Sync | core.replicate() |
|
| **Direct Connections** | NAT hole-punching |
|
||||||
| ✅ File Sharing | Hyperdrive |
|
| **Room System** | SHA-256 hash of room name |
|
||||||
| ✅ Offline-First | Built-in! |
|
| **Message Broadcasting** | Loop through all connections |
|
||||||
| ✅ Distribution | Pear ready! |
|
| **Commands** | /peers, /quit |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Challenges to Try
|
## Challenges to Try
|
||||||
|
|
||||||
1. **Add usernames** - Store a username with each message
|
1. **Add usernames** - Add a username to each message
|
||||||
2. **Add timestamps** - When was each message sent?
|
2. **Private rooms** - Use a secret key instead of a room name
|
||||||
3. **Make it multi-room** - Different topics for different chats
|
3. **File sharing** - Add a command to send files
|
||||||
4. **Add encryption** - Only decrypt messages for friends
|
4. **Message history** - Use Hypercore to store messages
|
||||||
5. **Add file download** - Save received files to disk
|
5. **Direct messages** - Send to specific peers, not everyone
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
You just built a P2P chat app with:
|
You just built a P2P chat app with:
|
||||||
- ~50 lines of code
|
- ~90 lines of code
|
||||||
- No servers
|
- No servers
|
||||||
- Offline-first by default
|
- No accounts
|
||||||
- Encrypted by default
|
- No registration
|
||||||
- Peer-to-peer discovery
|
- Direct peer-to-peer communication
|
||||||
|
|
||||||
This is the power of Holepunch!
|
This is the power of Hyperswarm - real P2P networking with just a few lines of code!
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -531,4 +481,4 @@ Now that you've built your first app, continue your journey:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*🎉 Congratulations! You just built a P2P app!*
|
*🎉 Congratulations! You just built a real P2P app!*
|
||||||
|
|||||||
Reference in New Issue
Block a user