basic chat app

This commit is contained in:
Raven Scott
2026-02-20 02:34:13 -05:00
parent 969dbae1b2
commit 78adf1c525
+327 -377
View File
@@ -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!*