14 Commits

Author SHA1 Message Date
2d7a88ba5f Fix close button styling and add disconnect for incoming chats
Close Button Improvements:
- Change pause icon (⏸) to close icon (✕) for clarity
- Change orange background to red (#dc3545) to indicate destructive action
- Update tooltip from 'Close chat' to 'End chat'
- Now visually distinct from the trash icon for removing friends

Add Disconnect for Incoming Chats:
- Add ✕ disconnect button to 'Active Chats' section (non-friends)
- Allows host to disconnect from incoming connections
- Same red styling as friend chat close button
- Shows 'Disconnected from {username}' toast

Both buttons now clearly indicate their purpose and work consistently
across friends and incoming chats.
2025-12-10 20:43:19 +01:00
55e197a5c5 Add connection request approval flow and improve friends list UX
Connection Request Approval:
- Add 'Connection Requests' section showing pending incoming connections
- Host must explicitly accept or deny incoming connection requests
- Shows username before accepting (no auto-accept)
- Accept: moves to active chats and sends acknowledgment
- Decline: closes connection and removes from pending
- Toast notification when someone wants to chat

Friends List UX Improvements:
- Add ⏸ (pause) button to close active chat without removing friend
- Change ✕ to 🗑 (trash) icon for removing friends
- Add confirmation dialog before removing a friend
- Separate 'close chat' from 'remove friend' actions
- Clearer visual distinction between actions

This prevents accidental friend removal and gives hosts control over
who they connect with before establishing the chat.
2025-12-10 20:36:27 +01:00
a08dd1dccc Add incoming chats UI and stop ICE polling when connected
UI improvements:
- Add 'Incoming Chats' section showing connections from non-friends
- Auto-select and show incoming chat when someone connects
- Toast notification when someone connects to you as host

ICE polling fix:
- Stop ICE candidate polling once connection is established
- Prevents unnecessary network requests after connection succeeds
- Only poll while in 'connecting' state

This fixes:
1. Host couldn't see incoming chats (they weren't in UI)
2. Answerer kept polling ICE candidates even after connected
2025-12-10 20:25:38 +01:00
249d1366d3 Fix ICE candidate timing issue: Buffer candidates before offerId is available
CRITICAL FIX: ICE gathering starts immediately when setLocalDescription() is
called, but we didn't have offerIds yet to send them to the server. This meant
all ICE candidates were lost before we could send them.

Solution:
- Attach temporary ICE handler BEFORE setLocalDescription()
- Buffer all ICE candidates in array until we have offerId
- After publishing and receiving offerIds, send all buffered candidates
- Replace handler with permanent one for any future candidates

This fixes the root cause of answerers not receiving any offerer candidates.
2025-12-10 20:17:02 +01:00
91b845aa1c Add detailed logging for answerer ICE candidate exchange
- Log when answerer starts polling for offerer ICE candidates
- Log count of candidates received from offerer
- Log each candidate being added with details
- Log success/failure of adding each candidate
- Log when answerer sends their own ICE candidates to server
- Helps debug ICE candidate exchange on answerer side
2025-12-10 20:06:11 +01:00
db651d4193 Filter ICE candidates by role in offerer polling
- Offerer now filters for answerer ICE candidates only
- Ignores own candidates returned from server
- Uses role field to distinguish between offerer and answerer candidates
- Improves logging to show answerer candidate count
2025-12-10 19:51:54 +01:00
a6329c8708 Implement combined polling and ICE candidate exchange for host
- Add offerId to RTCPeerConnection mapping for answer processing
- Setup ICE candidate handlers on offerer peer connections
- Replace answer-only polling with combined pollOffers() endpoint
- Process both answers and ICE candidates in single poll operation
- Track last poll timestamp to avoid reprocessing old data
- Send offerer ICE candidates to server via addOfferIceCandidates()
- Reduces HTTP requests and completes bidirectional ICE exchange
2025-12-10 19:33:31 +01:00
538315c51f Implement answer polling for offerer side
Host now polls for answered offers every 2 seconds using the new
efficient batch endpoint. When an answer is received, the host
automatically sets the remote description to complete the WebRTC
connection.

Changes:
- Store offerId to RTCPeerConnection mapping when publishing
- Poll for answered offers with timestamp filtering
- Automatically handle incoming answers
- Track last answer timestamp to avoid reprocessing

This completes the bidirectional WebRTC signaling flow.
2025-12-10 19:20:34 +01:00
6a24514e7b Fix signature validation issues in demo
- Add comprehensive logging for init and publish flows
- Verify username is claimed before publishing service
- Detect keypair mismatches and provide clear error messages
- Handle authentication errors more gracefully
- Auto-claim username if not claimed during publish
- Improved user guidance for common errors
2025-12-09 22:54:31 +01:00
46f0eb2e7a Restore full-featured chat UI with contact management, multiple chats, and RTC presets
- Restored contact management (add/remove friends)
- Restored multiple concurrent chat support
- Restored RTC configuration presets (ipv4-turn, hostname-turns, google-stun, relay-only, custom)
- Restored settings modal
- Restored dark theme styling
- Restored online status indicators
- Adapted all features to work with new unified Rondevu API
- Manual RTCPeerConnection management instead of ServiceHost/ServiceClient
- Manual offer pooling (10 concurrent connections)
- Manual ICE candidate polling
2025-12-09 22:49:11 +01:00
9e26ed3b66 Update to use local client package for development
- Install client from local path instead of npm registry
- Workaround for npm registry propagation delay

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-09 22:40:41 +01:00
7835ebd35d v2.1.0: Rewrite to use simplified Rondevu API
- Complete rewrite to use low-level Rondevu API directly
- Removed ServiceHost/ServiceClient abstractions
- Manual RTCPeerConnection and data channel setup
- Custom polling for answers and ICE candidates
- Updated to use new Rondevu class instead of RondevuService
- Direct signaling method calls instead of getAPI()
- Reduced from 926 lines to 542 lines (42% reduction)
- Demonstrates complete WebRTC flow with clear offerer/answerer roles

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-09 22:23:04 +01:00
5f223356ba feat: improve login persistence with server verification
- Properly await isUsernameClaimed() check during initialization
- Verify saved username is still valid on the server
- Show 'Welcome back' toast when restoring session
- Handle expired usernames gracefully
2025-12-08 21:39:58 +01:00
ab55a96fac fix: add missing ServiceHost and ServiceClient imports
- Import ServiceHost and ServiceClient from @xtr-dev/rondevu-client
- Fixes ReferenceError when starting hosting
2025-12-08 21:37:03 +01:00
3 changed files with 1835 additions and 842 deletions

59
package-lock.json generated
View File

@@ -8,7 +8,7 @@
"name": "rondevu-demo", "name": "rondevu-demo",
"version": "2.0.0", "version": "2.0.0",
"dependencies": { "dependencies": {
"@xtr-dev/rondevu-client": "^0.12.0", "@xtr-dev/rondevu-client": "file:../client",
"@zxing/library": "^0.21.3", "@zxing/library": "^0.21.3",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"react": "^18.2.0", "react": "^18.2.0",
@@ -22,6 +22,27 @@
"vite": "^5.4.11" "vite": "^5.4.11"
} }
}, },
"../client": {
"name": "@xtr-dev/rondevu-client",
"version": "0.13.0",
"license": "MIT",
"dependencies": {
"@noble/ed25519": "^3.0.0"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@typescript-eslint/eslint-plugin": "^8.48.1",
"@typescript-eslint/parser": "^8.48.1",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-unicorn": "^62.0.0",
"globals": "^16.5.0",
"prettier": "^3.7.4",
"typescript": "^5.9.3",
"vite": "^7.2.6"
}
},
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -745,15 +766,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@noble/ed25519": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-3.0.0.tgz",
"integrity": "sha512-QyteqMNm0GLqfa5SoYbSC3+Pvykwpn95Zgth4MFVSMKBB75ELl9tX1LAVsN4c3HXOrakHsF2gL4zWDAYCcsnzg==",
"license": "MIT",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@rolldown/pluginutils": { "node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.27", "version": "1.0.0-beta.27",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
@@ -1171,13 +1183,8 @@
} }
}, },
"node_modules/@xtr-dev/rondevu-client": { "node_modules/@xtr-dev/rondevu-client": {
"version": "0.12.0", "resolved": "../client",
"resolved": "https://registry.npmjs.org/@xtr-dev/rondevu-client/-/rondevu-client-0.12.0.tgz", "link": true
"integrity": "sha512-c1UecF29Cjck7h7b7KWyCti8YSVVkuvEzyAz7aaFwAYBEumgX1r143mTBRfAMQkFL4upkG/PL5bvBGRY9QCpug==",
"license": "MIT",
"dependencies": {
"@noble/ed25519": "^3.0.0"
}
}, },
"node_modules/@zxing/library": { "node_modules/@zxing/library": {
"version": "0.21.3", "version": "0.21.3",
@@ -1226,9 +1233,9 @@
} }
}, },
"node_modules/baseline-browser-mapping": { "node_modules/baseline-browser-mapping": {
"version": "2.9.3", "version": "2.9.6",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.3.tgz", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.6.tgz",
"integrity": "sha512-8QdH6czo+G7uBsNo0GiUfouPN1lRzKdJTGnKXwe12gkFbnnOUaUKGN55dMkfy+mnxmvjwl9zcI4VncczcVXDhA==", "integrity": "sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
@@ -1279,9 +1286,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001759", "version": "1.0.30001760",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz",
"integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==", "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -1375,9 +1382,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.266", "version": "1.5.267",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.266.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
"integrity": "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==", "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },

View File

@@ -10,7 +10,7 @@
"deploy": "npm run build && npx wrangler pages deploy dist --project-name=rondevu-demo" "deploy": "npm run build && npx wrangler pages deploy dist --project-name=rondevu-demo"
}, },
"dependencies": { "dependencies": {
"@xtr-dev/rondevu-client": "^0.12.0", "@xtr-dev/rondevu-client": "file:../client",
"@zxing/library": "^0.21.3", "@zxing/library": "^0.21.3",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"react": "^18.2.0", "react": "^18.2.0",

File diff suppressed because it is too large Load Diff