basic chat app

This commit is contained in:
Raven Scott
2026-02-20 02:34:13 -05:00
parent 969dbae1b2
commit 78adf1c525
+326 -376
View File
@@ -1,17 +1,25 @@
# 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.
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
Before we start, make sure you have:
### 1. Node.js Installed
```bash
@@ -24,18 +32,18 @@ node --version
### 2. Create a Project Folder
```bash
mkdir my-p2p-chat
cd my-p2p-chat
mkdir hyperswarm-chat
cd hyperswarm-chat
npm init -y
```
### 3. Install Dependencies
### 3. Install Hyperswarm
```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`:
```javascript
// Step 1: Import our tools
const Hypercore = require('hypercore')
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:
@@ -56,462 +108,360 @@ Run it:
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
const Hypercore = require('hypercore')
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')
const swarm = new Hyperswarm()
const connections = new Set()
// 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)
})
```
// Handle incoming connections
swarm.on('connection', (conn) => {
connections.add(conn)
console.log('[+] New peer connected')
Run it:
```bash
node chat.js
```
// Handle incoming messages
conn.on('data', (data) => {
console.log('Received:', data.toString())
})
You should see something like:
```
🚀 Starting P2P Chat...
📓 Feed created!
Your public key: a1b2c3d4e5f6...
Messages so far: 0
// Handle disconnection
conn.on('close', () => {
connections.delete(conn)
console.log('[-] Peer disconnected')
})
})
const discovery = swarm.join(topic, { server: true, client: true })
await discovery.flushed()
console.log('Ready to chat!')
}
start()
```
---
## Step 3: Add Networking
## Step 4: Add Chat Interface
Now let's make it discoverable:
Now let's add the ability to type and send messages:
```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()
core.ready().then(() => {
console.log('📓 Feed ready!')
console.log(' Key:', core.key.toString('hex').slice(0, 16) + '...')
// Join the swarm using our feed's discovery key
// This makes us findable by others interested in this chat
swarm.join(core.discoveryKey, { server: true, client: true })
console.log('🌐 Looking for peers...')
})
// When we find a peer
swarm.on('connection', (connection, info) => {
console.log('✅ Peer connected!', info.peer.publicKey.toString('hex').slice(0, 8))
// Replicate with them (share messages)
core.replicate(connection)
})
```
---
## Step 4: Send Messages
Now let's add the ability to send messages:
```javascript
const Hypercore = require('hypercore')
const Hyperswarm = require('hyperswarm')
const crypto = require('crypto')
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
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
swarm.on('connection', (conn) => {
connections.add(conn)
console.log('\n[+] New peer connected')
core.ready().then(() => {
console.log('📓 Feed ready!')
console.log(' Share this key with friends:', core.key.toString('hex'))
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
promptForMessage()
})
function promptForMessage() {
rl.question('You: ', (message) => {
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())
conn.on('data', (data) => {
console.log(`\nFriend: ${data.toString().trim()}`)
rl.prompt()
})
}
conn.on('close', () => {
connections.delete(conn)
console.log('\n[-] Peer disconnected')
})
})
const discovery = swarm.join(topic, { server: true, client: true })
await discovery.flushed()
console.log('\n=== P2P Chat Ready ===')
console.log('Type a message and press Enter!\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
}
// 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()
})
}
start()
```
---
## Step 5: Full Working App
## Step 5: The Complete Chat App
Here's the complete chat app:
Here's the complete, working P2P chat application:
```javascript
const Hypercore = require('hypercore')
const Hyperswarm = require('hyperswarm')
const crypto = require('crypto')
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()
async function start() {
// Fixed room — change this string only if you want a different room
const ROOM = 'basic-p2p-chat-2026'
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
core.ready().then(() => {
console.log('\n📓 Your chat feed is ready!')
console.log(' Share this key with friends to chat:')
console.log(' ' + core.key.toString('hex').slice(0, 32) + '...\n')
// 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()
})
const topic = crypto.createHash('sha256').update(ROOM).digest()
function prompt() {
rl.question('You: ', (msg) => {
if (msg.trim()) {
core.append(Buffer.from(msg))
}
prompt()
})
}
function showMessages() {
console.log('\n--- Chat History ---')
for (let i = 0; i < core.length; i++) {
core.get(i).then(msg => {
console.log(' ' + msg.toString())
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)
}
console.log('-------------------\n')
process.on('SIGINT', cleanup)
}
start().catch(err => {
console.error('Error:', err.message)
process.exit(1)
})
```
Save this as `chat.js` and run:
```bash
node chat.js
```
Save this as `chat.js` and you're done!
---
## Step 6: Testing It Works
To test with yourself:
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:**
### Terminal 1 - Start the chat:
```bash
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:**
```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
const Hypercore = require('hypercore')
const Hyperdrive = require('hyperdrive')
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()
})
}
const discovery = swarm.join(topic, { server: true, client: true })
await discovery.flushed()
```
---
- `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
One of P2P's superpowers: **offline-first**!
### Sending Messages
```javascript
// This app works OFFLINE!
// 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
conn.write(msg)
```
- 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
# Install Pear
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
```javascript
const ROOM = 'my-secret-room-123'
```
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 |
|---------|------|
| P2P Chat | Hypercore + Hyperswarm |
| Peer Discovery | Auto via DHT |
| ✅ Message Sync | core.replicate() |
| ✅ File Sharing | Hyperdrive |
| ✅ Offline-First | Built-in! |
| ✅ Distribution | Pear ready! |
| Feature | How It's Done |
|---------|---------------|
| **P2P Networking** | Hyperswarm |
| **Peer Discovery** | HyperDHT (built into Hyperswarm) |
| **Direct Connections** | NAT hole-punching |
| **Room System** | SHA-256 hash of room name |
| **Message Broadcasting** | Loop through all connections |
| **Commands** | /peers, /quit |
---
## Challenges to Try
1. **Add usernames** - Store a username with each message
2. **Add timestamps** - When was each message sent?
3. **Make it multi-room** - Different topics for different chats
4. **Add encryption** - Only decrypt messages for friends
5. **Add file download** - Save received files to disk
1. **Add usernames** - Add a username to each message
2. **Private rooms** - Use a secret key instead of a room name
3. **File sharing** - Add a command to send files
4. **Message history** - Use Hypercore to store messages
5. **Direct messages** - Send to specific peers, not everyone
---
## Summary
You just built a P2P chat app with:
- ~50 lines of code
- ~90 lines of code
- No servers
- Offline-first by default
- Encrypted by default
- Peer-to-peer discovery
- No accounts
- No registration
- 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!*