44 Commits

Author SHA1 Message Date
217b84701f Remove STUN server, increase ICE candidate pool 2025-11-15 14:37:30 +01:00
273b6349c6 Force TCP transport for TURN (VPN blocks UDP relay) 2025-11-15 14:31:35 +01:00
37c84b7553 Use Metered TURN service 2025-11-15 14:23:52 +01:00
2d1b2e8ff4 Test with STUN-only (no TURN) to verify ICE candidate fix 2025-11-15 14:18:29 +01:00
c849f6a109 Revert to custom TURN server at 57.129.61.67
Back to using custom STUN/TURN server with IP address to avoid DNS lookup issues. Note: TURN relay still requires firewall port range to be opened.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 13:38:04 +01:00
0348ef5d8e Switch to ExpressTurn TURN server for testing
Using Google STUN servers and ExpressTurn relay server to test with the bug fix.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 13:33:38 +01:00
1ac1121793 Fix ICE candidate handling by using addEventListener
Changed from overwriting onicecandidate handlers (which broke the 'this' context) to using addEventListener. This ensures the connection manager's handlers can properly send ICE candidates without context issues.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 13:31:21 +01:00
caedff590f Switch to Metered TURN servers for testing
Temporarily using Metered.ca TURN servers to test WebRTC connectivity while diagnosing issues with custom TURN server relay ports.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 13:18:42 +01:00
d922329437 Add API-level ICE candidate exchange logging
Added logging to track when ICE candidates are sent to and received from the signaling server to help diagnose connection exchange issues.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 12:39:45 +01:00
4c58dee371 Add detailed ICE connection debugging
Added comprehensive logging for ICE gathering state, connection state, and candidate gathering to help diagnose connection issues. Properly chains event handlers to avoid breaking existing connection logic.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 12:34:51 +01:00
e4868c085b Fix DNS lookup errors by using IP address for STUN/TURN servers
Changed from domain names (stun.ronde.vu/turn.ronde.vu) to IP address (57.129.61.67) to resolve browser DNS lookup failures (error 701).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 12:31:07 +01:00
9a424b6015 Add both UDP and TCP transports for TURN server
Updated TURN server configuration to include both UDP and TCP transports for better compatibility and connection reliability.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 12:27:11 +01:00
79030adb09 Update TURN server configuration to use UDP transport
Added ?transport=udp parameter to TURN server URL for improved connectivity.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 12:20:06 +01:00
1f1502940b Update TURN server credentials for ronde.vu 2025-11-14 22:37:10 +01:00
348a732178 Switch to custom STUN/TURN servers at ronde.vu 2025-11-14 22:31:15 +01:00
9e761546e7 Fix TURN server URL - add turn: prefix 2025-11-14 21:22:25 +01:00
f9fb74de53 Switch to Google STUN + expressturn.com TURN server 2025-11-14 21:20:31 +01:00
e5e28c8264 Update metered.ca credentials and ICE server configuration 2025-11-14 21:12:30 +01:00
4021a02f6d Add TURN servers back using a.relay.metered.ca endpoint 2025-11-14 21:09:11 +01:00
73f04bc078 Restore Google STUN servers only (metered.ca DNS lookup failing) 2025-11-14 21:07:37 +01:00
257d8f264a Remove Google STUN servers, use only metered.ca ICE servers 2025-11-14 21:01:28 +01:00
678f692b64 Update client to v0.4.1 with ICE candidate fix
Critical bug fix: ICE candidates were being missed on first poll,
causing connections to fail. Updated to v0.4.1 which fixes this.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 20:47:29 +01:00
83df6aeee3 Add Google STUN servers for reliability
Added Google STUN servers alongside metered.ca for maximum
reliability. Google STUN always works and ensures basic NAT
traversal even if TURN servers fail.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 20:44:06 +01:00
c3045778eb Use metered.ca ICE servers with all transport options
- metered.ca STUN server
- TURN on port 80 (UDP and TCP)
- TURN on port 443 (UDP)
- TURNS on port 443 (TCP/TLS)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 20:37:33 +01:00
dc856b7abf Fix: Restore Google STUN servers
The connection was failing because Google STUN servers were removed.
Restored to the working configuration with Google STUN + metered.ca TURN.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 20:27:40 +01:00
ed8709c6f6 Update ICE servers to metered.ca with full transport options
- Removed Google STUN servers
- Added metered.ca STUN server
- Added TCP transport for TURN servers
- Added TURNS (secure) on port 443

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 20:23:14 +01:00
b27ab02552 Add metered.ca TURN server on port 80
Added additional TURN server endpoint for better connectivity.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 20:11:45 +01:00
b321a01d5e Update client dependency to v0.4.0
Use published npm package instead of local link.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 20:05:27 +01:00
2dc4c711e3 Update ICE servers to use metered.ca TURN
Switched to single metered.ca TURN server on port 443.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 20:00:28 +01:00
b8637ed8ad Update ICE servers to use expressturn.com
Switched from metered.ca to expressturn.com TURN server
to avoid 701 host lookup errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 19:57:09 +01:00
94b2849971 Fix: Add cleanup on component unmount
- Close all connections when component unmounts
- Prevents polling timers from running after unmount
- Better resource cleanup

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 19:48:23 +01:00
65f4aaffe0 Fix authentication check and credential validation
- Add authentication check before allowing peer discovery
- Validate stored credentials have required fields (peerId, secret)
- Remove invalid/corrupted credentials from localStorage
- Show clear error message when trying to discover without auth

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 19:05:44 +01:00
e1c8c25ea8 Add TURN servers back for restrictive NAT support
- Re-add metered.ca TURN servers on ports 80 and 443
- Keep Google STUN servers for NAT detection
- Enables connections through restrictive firewalls

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 19:01:59 +01:00
600d6308b9 Update API URL to api.ronde.vu and simplify ICE config
- Change API URL from rondevu.xtrdev.workers.dev to api.ronde.vu
- Remove TURN servers, use only Google STUN (eliminate errors)
- Update README with correct API URL

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 18:54:51 +01:00
adc363fed0 Simplify TURN server configuration
- Remove duplicate TURN entries to avoid "5+ servers" warning
- Keep only essential STUN and TURN servers
- Use standard ports (80 and 443)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 18:39:30 +01:00
d677f36eeb Add connection manager, toast notifications, and TURN server
- Replace manual RTCPeerConnection handling with RondevuConnection class
- Add react-hot-toast for better UX (replace browser alerts)
- Add TURN server configuration (relay1.expressturn.com:3480)
- Update README to reflect current demo structure
- Link local client library for latest features

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 18:30:18 +01:00
eaf474a984 Simplify demo: remove topics UI, ID-only connections
- Remove topic selection and peer discovery UI
- Remove MethodSelector and TopicsList components
- Simplify ConnectionForm to just take a connection ID
- Update to use @xtr-dev/rondevu-client@0.3.2
- Demo version: 0.3.2

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-12 23:18:05 +01:00
1efe7346f4 Update to @xtr-dev/rondevu-client@0.3.1
- Updated from 0.1.1 to 0.3.1 to support rdv.api property
- Fixes "t.api is undefined" error in TopicsList component

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-12 22:30:50 +01:00
1bbce9295d Add topics list with pagination and update to api.ronde.vu
Features:
- Added TopicsList component with pagination support
- Shows active topics with peer counts
- Pagination controls (Previous/Next)
- Refresh button to reload topics
- Modal UI with proper styling
- Floating "View Topics" button on main screens

Changes:
- Updated API URL from rondevu.xtrdev.workers.dev to api.ronde.vu
- Added TopicsList component with pagination UI
- Added modal overlay and styles
- Integrated topics modal into main App

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-12 22:20:37 +01:00
6538b5a18f Update demo to use integrated RondevuAPI
- Removed separate RondevuClient instantiation
- Updated to use rdv.api for all API calls (getVersion, listTopics, listSessions)
- Updated README example to show integrated API usage

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-12 21:52:20 +01:00
ee440b083d Update header link text to "Demo" 2025-11-08 12:02:28 +01:00
60f1068bd1 Update demo tagline
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-08 12:00:16 +01:00
55a3d0ba51 Update client to v0.1.1 with /topics endpoint support
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-08 11:56:01 +01:00
2b574526d1 1.0.2 2025-11-08 11:55:04 +01:00
8 changed files with 1253 additions and 736 deletions

268
README.md
View File

@@ -1,51 +1,52 @@
# Rondevu
# Rondevu Demo
🎯 **Simple WebRTC peer signaling and discovery**
🎯 **Interactive WebRTC peer discovery and connection demo**
Meet peers by topic, by peer ID, or by connection ID.
Experience topic-based peer discovery and WebRTC connections using the Rondevu signaling platform.
**Related repositories:**
- [rondevu-server](https://github.com/xtr-dev/rondevu-server) - HTTP signaling server
- [rondevu-server](https://github.com/xtr-dev/rondevu) - HTTP signaling server
- [rondevu-client](https://github.com/xtr-dev/rondevu-client) - TypeScript client library
---
## Rondevu Demo
## Overview
**Interactive demo showcasing three ways to connect WebRTC peers.**
This demo showcases the complete Rondevu workflow:
Experience how easy WebRTC peer discovery can be with Rondevu's three connection methods:
1. **Register** - Get peer credentials (automatically saved)
2. **Create Offers** - Advertise your WebRTC connection on topics
3. **Discover Peers** - Find other peers by topic
4. **Connect** - Establish direct P2P WebRTC connections
5. **Chat** - Send messages over WebRTC data channels
🎯 **Connect by Topic** - Auto-discover and join any available peer
👤 **Connect by Peer ID** - Filter and connect to specific peers
🔗 **Connect by Connection ID** - Share a code and connect directly
### Key Features
### Features
- **Topic-Based Discovery** - Find peers by shared topics (like torrent infohashes)
- **Real P2P Connections** - Actual WebRTC data channels (not simulated)
- **Connection Manager** - Uses high-level `RondevuConnection` API (no manual WebRTC plumbing)
- **Persistent Credentials** - Saves authentication to localStorage
- **Topics Browser** - Browse all active topics and peer counts
- **Multiple Connections** - Support multiple simultaneous peer connections
- **Real-time Chat** - Direct peer-to-peer messaging
- **Three Connection Methods** - Experience topic discovery, peer filtering, and direct connection
- **Real WebRTC** - Actual P2P connections using RTCPeerConnection (not simulated!)
- **P2P Data Channel** - Direct peer-to-peer chat without server relay
- **Peer Discovery** - Browse topics and discover available peers
- **Real-time Chat** - Send and receive messages over WebRTC data channel
- **Activity Log** - Monitor all API and WebRTC events
## Quick Start
### Quick Start
#### Installation
### Installation
```bash
npm install
```
#### Development
### Development
```bash
npm run dev
```
This will start the Vite dev server at `http://localhost:5173`
This starts the Vite dev server at `http://localhost:5173`
#### Build for Production
### Build for Production
```bash
npm run build
@@ -53,150 +54,145 @@ npm run build
The built files will be in the `dist/` directory.
#### Preview Production Build
### Preview Production Build
```bash
npm run preview
```
### Three Ways to Connect
## How to Use
This demo demonstrates all three Rondevu connection methods:
### Step 1: Register (One-time)
#### 1⃣ Join Topic (Auto-Discovery)
The demo automatically registers you when you first visit. Your credentials are saved in localStorage for future visits.
**Easiest method** - Just enter a topic and auto-connect to first available peer:
### Step 2: Create an Offer
1. Enter a topic name in the "Join Topic" section (e.g., "demo-room")
2. Click "Join Topic"
3. Rondevu finds the first available peer and connects automatically
4. Start chatting!
1. Go to the "Create Offer" tab
2. Enter one or more topics (comma-separated), e.g., `demo-room, testing`
3. Click "Create Offer"
4. Your offer is now advertised on those topics
**Best for:** Quick matching, joining any available game/chat
**Share the topic name with peers you want to connect with!**
---
### Step 3: Discover and Connect (Other Peer)
#### 2⃣ Discover Peers (Filter by Peer ID)
1. Go to the "Discover Offers" tab
2. Enter the same topic (e.g., `demo-room`)
3. Click "Discover Offers"
4. See available peers and their offers
5. Click "Answer Offer" to connect
**Connect to specific peers** - Browse and select which peer to connect to:
### Step 4: Chat
1. Enter a topic name (e.g., "demo-room")
2. Click "Discover in [topic]" to list all available peers
3. See each peer's ID in the list
4. Click "Connect" on the specific peer you want to talk to
5. Start chatting!
1. Once connected, go to the "Chat" tab
2. Select a connection from the dropdown
3. Type messages and hit Enter or click Send
4. Messages are sent **directly peer-to-peer** via WebRTC
**Best for:** Connecting to friends, teammates, or specific users
### Browse Topics
---
Click the "Topics" tab to:
- See all active topics
- View peer counts for each topic
- Quick-discover by clicking a topic
#### 3⃣ Create/Connect by ID (Direct Connection)
**Share a connection code** - Like sharing a meeting link:
**To create:**
1. Enter a topic name (e.g., "meetings")
2. Enter a custom Connection ID (e.g., "my-meeting-123") or leave blank for auto-generation
3. Click "Create Connection"
4. **Share the Connection ID** with the person you want to connect with
**To join:**
1. Get the Connection ID from your friend (e.g., "my-meeting-123")
2. Enter it in the "Connect by ID" section
3. Click "Connect to ID"
4. Start chatting!
**Best for:** Meeting rooms, QR code connections, invitation-based sessions
#### Testing Locally
## Testing Locally
The easiest way to test:
1. Open the demo in **two different browser windows** (or tabs)
2. In window 1: Create an offer with topic "test-room"
3. In window 2: Discover peers in "test-room" and click Connect
4. Watch the connection establish and start chatting!
#### Browse Topics
1. Open the demo in **two browser windows** (or tabs)
2. **Window 1**: Create an offer with topic `test-room`
3. **Window 2**: Discover offers in `test-room` and answer
4. Switch to Chat tab in both windows
5. Start chatting peer-to-peer!
- Click "Refresh Topics" to see all active topics
- Click on any topic to auto-fill the discovery form
## Technical Implementation
### Server Configuration
### Connection Manager
This demo connects to: `https://rondevu.xtrdev.workers.dev`
To use a different server, modify the `baseUrl` in `src/main.js`:
This demo uses the high-level `RondevuConnection` class which abstracts all WebRTC complexity:
```javascript
const client = new RondevuClient({
baseUrl: 'https://your-server.com'
// Create connection
const conn = client.createConnection();
// Set up event listeners
conn.on('connected', () => {
console.log('P2P connection established!');
});
conn.on('datachannel', (channel) => {
channel.onmessage = (event) => {
console.log('Message:', event.data);
};
});
// Create offer
await conn.createOffer({
topics: ['demo-room'],
ttl: 300000
});
// Or answer an offer
await conn.answer(offerId, offerSdp);
```
### Technologies
The connection manager handles:
- Offer/answer SDP generation
- ICE candidate gathering and exchange
- Automatic polling for answers and candidates
- Data channel lifecycle
- Connection state management
- Event-driven API
- **Vite** - Fast development and build tool
- **@xtr-dev/rondevu-client** - TypeScript client for Rondevu API
- **Vanilla JavaScript** - No framework dependencies
### What Happens Under the Hood
### API Examples
1. **Offerer** calls `conn.createOffer()`:
- Creates RTCPeerConnection
- Generates SDP offer
- Creates data channel
- Posts offer to Rondevu server
- Polls for answers every 2 seconds
The demo showcases all major Rondevu API endpoints:
2. **Answerer** calls `conn.answer()`:
- Creates RTCPeerConnection
- Sets remote description (offer SDP)
- Generates SDP answer
- Posts answer to server
- Polls for ICE candidates every 1 second
- `GET /` - List all topics
- `GET /:topic/sessions` - Discover peers in a topic
- `POST /:topic/offer` - Create a new offer
- `POST /answer` - Send answer to a peer
- `POST /poll` - Poll for peer data
- `GET /health` - Check server health
3. **ICE Exchange**:
- Both peers generate ICE candidates
- Candidates are automatically sent to server
- Peers poll and receive remote candidates
- ICE establishes the direct P2P path
### WebRTC Implementation Details
4. **Connection Established**:
- Data channel opens
- Chat messages flow directly between peers
- No server relay (true P2P!)
This demo implements a **complete WebRTC peer-to-peer connection** with:
### Architecture
#### Connection Flow
- **Frontend**: React + Vite
- **Signaling**: Rondevu server (Cloudflare Workers + D1)
- **Client**: @xtr-dev/rondevu-client (TypeScript library)
- **WebRTC**: RTCPeerConnection with Google STUN servers
1. **Offerer** creates an `RTCPeerConnection` and generates an SDP offer
2. Offer is sent to the Rondevu signaling server via `POST /:topic/offer`
3. **Answerer** discovers the offer via `GET /:topic/sessions`
4. Answerer creates an `RTCPeerConnection`, sets the remote offer, and generates an SDP answer
5. Answer is sent via `POST /answer`
6. Both peers generate ICE candidates and send them via `POST /answer` with `candidate` field
7. Both peers poll via `POST /poll` to receive remote ICE candidates
8. Once candidates are exchanged, the **direct P2P connection** is established
9. Data channel opens and chat messages flow **directly between peers**
## Server Configuration
#### Key Features
This demo connects to: `https://api.ronde.vu`
- **Real RTCPeerConnection** - Not simulated, actual WebRTC
- **STUN servers** - Google's public STUN servers for NAT traversal
- **Data Channel** - Named "chat" channel for text messaging
- **ICE Trickle** - Candidates are sent as they're generated
- **Automatic Polling** - Polls every 1 second for remote data
- **Connection States** - Visual indicators for connecting/connected/failed states
- **Graceful Cleanup** - Properly closes connections and stops polling
To use a different server, modify `API_URL` in `src/App.jsx`:
#### Technologies
```javascript
const API_URL = 'https://your-server.com';
```
- **RTCPeerConnection API** - Core WebRTC connection
- **RTCDataChannel API** - Unreliable but fast text messaging
- **Rondevu Signaling** - SDP and ICE candidate exchange
- **STUN Protocol** - NAT traversal (stun.l.google.com)
## Deployment
### Development Notes
- Peer IDs are auto-generated on page load
- WebRTC connections use **real** RTCPeerConnection (not simulated!)
- Sessions expire after the server's configured timeout (5 minutes default)
- The demo is completely client-side (no backend required)
- Messages are sent P2P - the server only facilitates discovery
- Works across different browsers and networks (with STUN support)
### Deployment
#### Deploy to Cloudflare Pages
The demo can be easily deployed to Cloudflare Pages (free tier):
### Deploy to Cloudflare Pages
**Quick Deploy via Wrangler:**
@@ -213,7 +209,23 @@ npx wrangler pages deploy dist --project-name=rondevu-demo
4. Set output directory: `dist`
5. Deploy automatically on every push!
### License
## Development Notes
- Credentials are stored in localStorage and persist across sessions
- Offers expire after 5 minutes by default
- The connection manager polls automatically (no manual polling needed)
- Multiple simultaneous connections are supported
- WebRTC uses Google's public STUN servers for NAT traversal
- Data channel messages are unreliable but fast (perfect for chat)
## Technologies
- **React** - UI framework
- **Vite** - Build tool and dev server
- **@xtr-dev/rondevu-client** - Rondevu client library
- **RTCPeerConnection** - WebRTC connections
- **RTCDataChannel** - P2P messaging
## License
MIT

41
package-lock.json generated
View File

@@ -1,17 +1,19 @@
{
"name": "rondevu-demo",
"version": "1.0.1",
"version": "0.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "rondevu-demo",
"version": "1.0.1",
"version": "0.4.0",
"dependencies": {
"@xtr-dev/rondevu-client": "^0.4.1",
"@zxing/library": "^0.21.3",
"qrcode": "^1.5.4",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-hot-toast": "^2.6.0"
},
"devDependencies": {
"@types/react": "^18.2.0",
@@ -1168,6 +1170,12 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
"node_modules/@xtr-dev/rondevu-client": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@xtr-dev/rondevu-client/-/rondevu-client-0.4.1.tgz",
"integrity": "sha512-8giBS48thHKoIiqD6hD2VpMer50cGg4iwVMRCaaTiC7Ci6ICHXyCorNj6lWgw7dwL56oWhzbZU+cWHlQw2dxyQ==",
"license": "MIT"
},
"node_modules/@zxing/library": {
"version": "0.21.3",
"resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.21.3.tgz",
@@ -1328,7 +1336,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true,
"license": "MIT"
},
"node_modules/debug": {
@@ -1473,6 +1480,15 @@
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/goober": {
"version": "2.1.18",
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz",
"integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==",
"license": "MIT",
"peerDependencies": {
"csstype": "^3.0.10"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -1713,6 +1729,23 @@
"react": "^18.3.1"
}
},
"node_modules/react-hot-toast": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz",
"integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==",
"license": "MIT",
"dependencies": {
"csstype": "^3.1.3",
"goober": "^2.1.16"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">=16",
"react-dom": ">=16"
}
},
"node_modules/react-refresh": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",

View File

@@ -1,7 +1,7 @@
{
"name": "rondevu-demo",
"version": "1.0.1",
"description": "Demo application for Rondevu peer signaling and discovery",
"version": "0.4.0",
"description": "Demo application for Rondevu topic-based peer discovery and signaling",
"type": "module",
"scripts": {
"dev": "vite",
@@ -10,10 +10,12 @@
"deploy": "npm run build && npx wrangler pages deploy dist --project-name=rondevu-demo"
},
"dependencies": {
"@xtr-dev/rondevu-client": "^0.4.1",
"@zxing/library": "^0.21.3",
"qrcode": "^1.5.4",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-hot-toast": "^2.6.0"
},
"devDependencies": {
"@types/react": "^18.2.0",

File diff suppressed because it is too large Load Diff

View File

@@ -2,95 +2,31 @@ import QRCodeDisplay from './QRCodeDisplay';
function ConnectionForm({
action,
method,
topic,
setTopic,
connectionId,
setConnectionId,
peerId,
setPeerId,
topics,
sessions,
connectionStatus,
qrCodeUrl,
currentConnectionId,
onConnect,
onBack,
onTopicSelect,
onDiscoverPeers
onBack
}) {
return (
<div className="step-container">
<h2>Enter Details</h2>
<h2>{action === 'create' ? 'Create Connection' : 'Join Connection'}</h2>
<div className="form-container">
{(method === 'topic' || method === 'peer-id' || (method === 'connection-id' && action === 'create')) && (
<div className="form-group">
<label>Topic</label>
<input
type="text"
value={topic}
onChange={(e) => setTopic(e.target.value)}
placeholder="e.g., game-room"
autoFocus
/>
{topics.length > 0 && (
<div className="topic-list">
{topics.map((t) => (
<button
key={t.topic}
className="topic-item"
onClick={() => {
onTopicSelect(t.topic);
if (method === 'peer-id') {
onDiscoverPeers(t.topic);
}
}}
>
{t.topic} <span className="peer-count">({t.count})</span>
</button>
))}
</div>
)}
</div>
)}
{method === 'peer-id' && (
<div className="form-group">
<label>Peer ID</label>
<input
type="text"
value={peerId}
onChange={(e) => setPeerId(e.target.value)}
placeholder="e.g., player-123"
/>
{sessions.length > 0 && (
<div className="topic-list">
{sessions.map((s) => (
<button
key={s.code}
className="topic-item"
onClick={() => setPeerId(s.peerId)}
>
{s.peerId}
</button>
))}
</div>
)}
</div>
)}
{method === 'connection-id' && (
<div className="form-group">
<label>Connection ID {action === 'create' && '(optional)'}</label>
<input
type="text"
value={connectionId}
onChange={(e) => setConnectionId(e.target.value)}
placeholder={action === 'create' ? 'Auto-generated if empty' : 'e.g., meeting-123'}
autoFocus={action === 'join'}
placeholder={action === 'create' ? 'Auto-generated if empty' : 'Enter connection ID'}
autoFocus={action === 'connect'}
/>
</div>
{action === 'create' && !connectionId && (
<p className="help-text">Leave empty to auto-generate a random ID</p>
)}
</div>
<div className="button-row">
<button className="back-button" onClick={onBack}> Back</button>
@@ -99,12 +35,10 @@ function ConnectionForm({
onClick={onConnect}
disabled={
connectionStatus === 'connecting' ||
(method === 'topic' && !topic) ||
(method === 'peer-id' && (!topic || !peerId)) ||
(method === 'connection-id' && action === 'join' && !connectionId)
(action === 'connect' && !connectionId)
}
>
{connectionStatus === 'connecting' ? 'Connecting...' : 'Connect'}
{connectionStatus === 'connecting' ? 'Connecting...' : (action === 'create' ? 'Create' : 'Connect')}
</button>
</div>

View File

@@ -3,7 +3,7 @@ function Header() {
<header className="header">
<div className="header-content">
<h1>Rondevu</h1>
<p className="tagline">Meet WebRTC peers by topic, peer ID, or connection ID</p>
<p className="tagline">Simple WebRTC peer signaling and discovery. Meet peers by topic, peer ID, or connection ID.</p>
<div className="header-links">
<a href="https://github.com/xtr-dev/rondevu-client" target="_blank" rel="noopener noreferrer">
<svg className="github-icon" viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
@@ -21,7 +21,7 @@ function Header() {
<svg className="github-icon" viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
</svg>
View source
Demo
</a>
</div>
</div>

View File

@@ -0,0 +1,120 @@
import { useState, useEffect } from 'react';
function TopicsList({ rdv, onClose }) {
const [topics, setTopics] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [page, setPage] = useState(1);
const [pagination, setPagination] = useState(null);
const [limit] = useState(20);
useEffect(() => {
loadTopics();
}, [page]);
const loadTopics = async () => {
setLoading(true);
setError(null);
try {
const response = await rdv.api.listTopics(page, limit);
setTopics(response.topics);
setPagination(response.pagination);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const handleRefresh = () => {
loadTopics();
};
const handlePrevPage = () => {
if (page > 1) {
setPage(page - 1);
}
};
const handleNextPage = () => {
if (pagination?.hasMore) {
setPage(page + 1);
}
};
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content topics-modal" onClick={(e) => e.stopPropagation()}>
<div className="modal-header">
<h2>Active Topics</h2>
<button className="close-button" onClick={onClose}>×</button>
</div>
<div className="modal-body">
{error && (
<div className="error-message" style={{ marginBottom: '1rem' }}>
Error: {error}
</div>
)}
{loading ? (
<div className="loading-message">Loading topics...</div>
) : (
<>
{topics.length === 0 ? (
<div className="empty-message">
No active topics found. Be the first to create one!
</div>
) : (
<div className="topics-list">
{topics.map((topic) => (
<div key={topic.topic} className="topic-item">
<div className="topic-name">{topic.topic}</div>
<div className="topic-count">
{topic.count} {topic.count === 1 ? 'peer' : 'peers'}
</div>
</div>
))}
</div>
)}
{pagination && (
<div className="pagination">
<button
onClick={handlePrevPage}
disabled={page === 1}
className="pagination-button"
>
Previous
</button>
<span className="pagination-info">
Page {pagination.page} of {Math.ceil(pagination.total / pagination.limit)}
{' '}({pagination.total} total)
</span>
<button
onClick={handleNextPage}
disabled={!pagination.hasMore}
className="pagination-button"
>
Next
</button>
</div>
)}
</>
)}
</div>
<div className="modal-footer">
<button onClick={handleRefresh} className="button button-secondary">
🔄 Refresh
</button>
<button onClick={onClose} className="button button-primary">
Close
</button>
</div>
</div>
</div>
);
}
export default TopicsList;

View File

@@ -162,6 +162,12 @@ body {
font-size: 0.95rem;
}
.help-text {
margin-top: 8px;
font-size: 0.85rem;
color: #6c757d;
}
input[type="text"] {
width: 100%;
padding: 12px 16px;
@@ -790,3 +796,204 @@ input[type="text"]:disabled {
width: 100%;
}
}
/* Topics List Modal */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 0.2s ease-out;
}
.modal-content {
background: white;
border-radius: 12px;
max-width: 600px;
width: 90%;
max-height: 80vh;
display: flex;
flex-direction: column;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.modal-header {
padding: 24px 32px;
border-bottom: 1px solid #e8eaf0;
display: flex;
align-items: center;
justify-content: space-between;
}
.modal-header h2 {
margin: 0;
font-size: 1.5rem;
color: #1a1a1a;
}
.close-button {
background: none;
border: none;
font-size: 2rem;
color: #6c757d;
cursor: pointer;
padding: 0;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
transition: all 0.2s;
}
.close-button:hover {
background: #f8f9fc;
color: #1a1a1a;
}
.modal-body {
padding: 24px 32px;
overflow-y: auto;
flex: 1;
}
.modal-footer {
padding: 20px 32px;
border-top: 1px solid #e8eaf0;
display: flex;
gap: 12px;
justify-content: flex-end;
}
.topics-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.topic-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
background: #f8f9fc;
border: 1px solid #e8eaf0;
border-radius: 8px;
transition: all 0.2s;
}
.topic-item:hover {
background: #f0f2f8;
border-color: #d0d5dd;
}
.topic-name {
font-weight: 500;
color: #1a1a1a;
font-size: 1rem;
word-break: break-all;
}
.topic-count {
font-size: 0.9rem;
color: #6c757d;
background: white;
padding: 4px 12px;
border-radius: 12px;
font-weight: 500;
white-space: nowrap;
}
.pagination {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 24px;
padding-top: 20px;
border-top: 1px solid #e8eaf0;
}
.pagination-button {
padding: 10px 20px;
border: 1px solid #e8eaf0;
background: white;
color: #1a1a1a;
border-radius: 8px;
font-size: 0.95rem;
cursor: pointer;
transition: all 0.2s;
font-weight: 500;
}
.pagination-button:hover:not(:disabled) {
background: #f8f9fc;
border-color: #5568d3;
color: #5568d3;
}
.pagination-button:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.pagination-info {
font-size: 0.9rem;
color: #6c757d;
}
.loading-message, .empty-message, .error-message {
text-align: center;
padding: 40px 20px;
color: #6c757d;
font-size: 1rem;
}
.error-message {
color: #dc3545;
background: #ffe8eb;
border: 1px solid #ffccd2;
border-radius: 8px;
padding: 16px;
}
.view-topics-button {
position: fixed;
bottom: 80px;
right: 20px;
padding: 14px 24px;
background: #5568d3;
color: white;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
box-shadow: 0 4px 12px rgba(85, 104, 211, 0.3);
transition: all 0.2s;
z-index: 100;
}
.view-topics-button:hover {
background: #667eea;
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(85, 104, 211, 0.4);
}