77 Commits

Author SHA1 Message Date
158e001055 chore: update rondevu-client to v0.12.0 2025-12-07 22:23:13 +01:00
9967e8d762 Refactor demo to use new ServiceHost and ServiceClient API
- Update to @xtr-dev/rondevu-client v0.10.1
- Replace Rondevu class with RondevuService, ServiceHost, ServiceClient
- Simplify demo implementation (removed complex features)
- Use new event-driven API for connections
- Support custom RTC configuration
- Backup old implementation as App-old.jsx

Breaking changes from v1 API:
- RondevuService for username claiming
- ServiceHost for hosting services with offer pool
- ServiceClient for connecting with auto-reconnection

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-07 19:46:07 +01:00
8c3f21f262 Update @xtr-dev/rondevu-client to v0.10.0
- ServiceHost: Manages offer pool for hosting services
- ServiceClient: Connects to hosted services with auto-reconnection
- RondevuService: High-level API for username claiming and service publishing

Note: Demo code still uses old Rondevu API and needs refactoring

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-07 19:39:54 +01:00
c511b15fbf Update to rondevu-client v0.9.2 with ICE collection fix
Updated to @xtr-dev/rondevu-client@0.9.2 which fixes critical timing
issue where ICE candidates were generated before handlers were attached.

Now includes detailed logging for service pool ICE candidate generation
with candidate types (host/srflx/relay) for debugging TURN issues.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-07 11:33:42 +01:00
71454e31d1 Add WebRTC configuration settings UI
Added a settings modal with dropdown to select from preset RTC
configurations or provide custom JSON config:

- IPv4 TURN (Recommended): Uses explicit IPv4 addresses to avoid
  DNS resolution issues and address type mismatches
- Hostname TURNS (TLS): Uses secure TURNS on port 5349 with hostname
- Google STUN Only: Minimal config for testing direct connections
- Force TURN Relay: Testing mode that forces relay usage
- Custom Configuration: Users can paste their own RTCConfiguration JSON

Settings are persisted in localStorage and applied to all new
connections and service exposures.

This allows users to test different configurations without rebuilding
and helps diagnose connection issues.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-07 11:26:44 +01:00
b2a17ce42b Update to rondevu-client v0.9.1 with ICE logging
Updated to @xtr-dev/rondevu-client@0.9.1 which includes detailed
ICE candidate exchange logging for debugging connection issues.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-07 11:15:07 +01:00
3a42f74371 Add TURNS (secure) endpoints for upgraded TURN server
Updated ICE configuration to use TURNS (TLS/DTLS) on port 5349
as the preferred relay method, with plain TURN on port 3478 as
fallback. WebRTC will try secure endpoints first for better
security and reliability.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-06 15:58:15 +01:00
2cbd46b27a Fix datachannel name to match service pool
Changed channel name from 'chat' to 'rondevu-service' to match the
channel name created by the service pool. This fixes the connection
failure where the offerer's channel and answerer's channel had
different names and couldn't connect.

The service pool creates a channel named 'rondevu-service', so clients
must use the same name to receive that channel.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-06 13:59:30 +01:00
b3dde85cd2 Force TURN relay mode to bypass NAT hairpinning
Added iceTransportPolicy: 'relay' to RTC_CONFIG to force TURN relay usage.
This bypasses NAT hairpinning issues when testing on the same network
(e.g., two browser tabs on the same machine).

This setting ensures maximum compatibility and reliability for WebRTC
connections by always using the TURN relay instead of attempting direct
or STUN-based connections.

Note: Should be commented out in production to allow direct connections
when possible for better performance.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-06 13:49:52 +01:00
66dc35c1a7 Remove rondevu-client from dependencies (using npm link)
The demo now uses npm link to use the local client development version
instead of the published package from npm registry.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-06 13:30:06 +01:00
74bf2757ff feat: v0.9.0 - durable WebRTC connections with automatic reconnection
- Replace low-level APIs with high-level durable connections
- Add automatic reconnection with exponential backoff
- Add message queuing during disconnections
- Add TTL auto-refresh for services
- Add comprehensive TypeScript types
- Update README and create MIGRATION.md guide

BREAKING CHANGES:
- Removed: client.services.*, client.discovery.*, client.createPeer()
- Added: client.exposeService(), client.connect(), client.connectByUuid()
- Handler signature changed from (channel, peer, connectionId?) to (channel, connectionId)
- Channels now use .on('message') instead of .onmessage
- Services must call service.start() to begin accepting connections
2025-12-06 12:58:30 +01:00
2550c1ac3f fix: set 5-minute TTL for chat services
Services were expiring after 60 seconds (default offer TTL).
Set TTL to 300000ms (5 minutes) so chat services stay discoverable
while users have the page open.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 19:44:36 +01:00
3d9a1c27bd chore: update to @xtr-dev/rondevu-client@0.8.3
Fix async ed25519 functions (signAsync, getPublicKeyAsync)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 19:20:19 +01:00
e26bdc308d fix: update package-lock.json to use client@0.8.2
Previous lock file had stale version 0.0.4 cached

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 19:13:08 +01:00
6650f51038 fix: improve auto-registration flow and error handling
- Fix setupStep logic to properly handle credential/username combinations
- Add console logs for debugging registration flow
- Move setupStep updates to prevent race conditions
- Always progress to claim step even if registration fails
- Reorganize initialization order

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 19:11:27 +01:00
0a975f4bcf feat: make registration automatic on first visit
- Auto-register on mount if no saved credentials exist
- Remove manual register button
- Show "Registering..." loading state instead
- Automatically progress to username claim step after registration

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 19:09:17 +01:00
c889549362 chore: update to @xtr-dev/rondevu-client@0.8.2
Includes SHA-512 hash function initialization for ed25519

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 18:44:49 +01:00
fb9830ea8c chore: update to @xtr-dev/rondevu-client@0.8.1
Includes fix for V2 API exports (usernames, services, discovery)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 18:38:21 +01:00
5714731d71 chore: update package-lock.json for client@0.8.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 18:32:34 +01:00
4ff5da0568 feat: redesign demo as dark-themed P2P chat application
- Completely rewrite App.jsx as focused chat application
- Add contact management with localStorage persistence
- Add online status detection (checks every 10s for chat.rondevu@1.0.0 service)
- Implement pooled chat service (poolSize: 10) for multiple simultaneous connections
- Add real-time P2P messaging with unique connection IDs
- Add message history per contact with auto-scrolling
- Implement identification handshake protocol for connection tracking
- Apply dark theme (#1a1a1a background, #2a2a2a cards, #4a9eff accents)
- Remove all emojis from UI elements
- Update client dependency to ^0.8.0
- Add connection status indicators

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 18:26:48 +01:00
d575022412 Update README to include live API link for rondevu-server 2025-11-17 21:44:13 +01:00
84ceae9a05 Update live demo link in README to use ronde.vu domain 2025-11-17 21:43:09 +01:00
c5f640bc62 Expand README with links to related repositories and NPM packages 2025-11-17 21:41:52 +01:00
9163e5166c Replace preset topics with dynamic topic listing from server
Changed discover page to fetch and display real topics from the API:
- Added fetchTopics() to call client.offers.getTopics()
- Display actual topic names and active peer counts
- Added loading, error, and empty states
- Added refresh button to reload topics
- Improved UX with better error handling

Updated to @xtr-dev/rondevu-client@0.7.4

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 20:17:27 +01:00
7d3b19a2b0 Update README to use client.createPeer() method
- Replaced `new RondevuPeer(client.offers)` with `client.createPeer()`
- Updated import to show Rondevu instead of RondevuPeer

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 18:03:46 +01:00
7d19557966 Update README to reflect current RondevuPeer API
- Replaced all references to removed RondevuConnection class
- Updated to use RondevuPeer with state machine lifecycle
- Documented offerer and answerer state flows
- Added detailed "What Happens Under the Hood" section
- Updated all code examples to use addEventListener
- Added trickle ICE implementation details
- Documented timeout configurations
- Updated technical implementation section

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 18:01:20 +01:00
70fd6bd16a Update rondevu-client to v0.7.3
Fixed answerer authorization for ICE candidates:
- Answer is now sent to server BEFORE setLocalDescription
- This ensures answererPeerId is registered before ICE candidates arrive
- Fixes 403 Forbidden errors when answerer sends candidates

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 17:52:12 +01:00
6dece31f2d Update rondevu-client to v0.7.2
Critical fix for ICE candidate timing bug:
- ICE handler now set up BEFORE setLocalDescription
- Ensures all ICE candidates are captured and sent to server
- Fixes connection failures where peers received 0 candidates

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 17:46:33 +01:00
b741e8f40c Update rondevu-client to v0.7.1
Updated to latest client version with:
- addEventListener/removeEventListener instead of .on* properties
- Extracted duplicate ICE handler code to base PeerState class
- Proper event listener cleanup to prevent memory leaks

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 17:37:45 +01:00
2c20af83c9 feat: Update to @xtr-dev/rondevu-client@0.6.0
Includes proper trickle ICE support:
- Offers/answers sent immediately (no waiting for ICE)
- Answering state now takes ~50-200ms instead of 15+ seconds
- ICE candidates trickle in as they're discovered
- Faster connection establishment

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 17:13:18 +01:00
78c16c95f5 fix: Handle data channel onopen event for bidirectional messaging
The offerer creates the data channel immediately, but it's not in the
'open' state until the connection is established. The answerer receives
the channel later when it's typically already open.

Now we:
- Listen for channel.onopen and update the connection state when open
- Check if channel is already open and update immediately if so
- Add logging for channel state changes
- Handle channel errors and close events

This fixes the issue where only the answerer could send messages.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 16:53:03 +01:00
953f62ce81 fix: Update to @xtr-dev/rondevu-client@0.5.1
Fixes timeout issue where answerer would fail when ICE gathering
took close to the timeout duration.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 16:49:03 +01:00
c46bfb40a9 chore: Update to @xtr-dev/rondevu-client@0.5.0
- Update client dependency to 0.5.0 (state-based peer manager)
- Bump demo version to 0.5.0

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 16:41:26 +01:00
50eeec5164 refactor: Update demo to use RondevuPeer with state management
- Replace RondevuConnection with RondevuPeer throughout
- Add state event listener for better state tracking
- Add failed event listener with error details
- Configure timeouts for offer/answer operations
- Enhanced ICE debugging with candidate pair tracking
- Add connection failure detection and logging
- Improved error handling and user feedback
- Update version to v0.5.0 (State-Based Peer Manager)
- Update TURN server configuration to ronde.vu
- Add comprehensive logging for troubleshooting

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 16:37:28 +01:00
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
10 changed files with 3029 additions and 854 deletions

91
CLAUDE.md Normal file
View File

@@ -0,0 +1,91 @@
# Rondevu Demo Development Guidelines
## WebRTC Configuration
### TURN Server Setup
When configuring TURN servers:
-**DO** use TURNS (secure) on port 5349 when available: `turns:server.com:5349`
-**DO** include TURN fallback on port 3478: `turn:server.com:3478`
-**DO** include the port number in TURN URLs (even if default)
-**DO** test TURN connectivity before deploying: `turnutils_uclient -u user -w pass server.com 3478 -y`
-**DO** provide both TCP and UDP transports for maximum compatibility
-**DON'T** omit the port number
-**DON'T** assume TURN works without testing
**Current Configuration:**
```javascript
const RTC_CONFIG = {
iceServers: [
{ urls: ["stun:stun.share.fish:3478"] },
{
urls: [
// TURNS (secure) - TLS/DTLS on port 5349 (preferred)
"turns:turn.share.fish:5349?transport=tcp",
"turns:turn.share.fish:5349?transport=udp",
// TURN (fallback) - plain on port 3478
"turn:turn.share.fish:3478?transport=tcp",
"turn:turn.share.fish:3478?transport=udp",
],
username: "webrtcuser",
credential: "supersecretpassword"
}
]
};
```
WebRTC will try TURNS (secure) endpoints first, falling back to plain TURN if needed.
### ICE Configuration
**Force Relay Mode for Testing:**
```javascript
const RTC_CONFIG = {
iceServers: [...],
iceTransportPolicy: 'relay' // Forces TURN relay, bypasses NAT issues
};
```
Use `iceTransportPolicy: 'relay'` to:
- Test if TURN server is working correctly
- Bypass NAT hairpinning issues (when both peers are on same network)
- Ensure maximum compatibility
**Remove or comment out** `iceTransportPolicy: 'relay'` for production to allow direct connections when possible.
## Debugging
### Enable Detailed ICE Logging
The demo includes detailed ICE candidate logging. Check browser console for:
- 🧊 ICE candidate gathering
- 🧊 ICE connection state changes
- 📤 Candidates sent to server
- 📥 Candidates received from server
- ✅ Successful candidate pairs
- ❌ Failed candidate pairs
### Common Issues
1. **Connection stuck in "connecting":**
- Enable relay-only mode to test TURN
- Check if both peers are behind same NAT (hairpinning issue)
- Verify TURN credentials are correct
2. **No candidates gathered:**
- Check STUN/TURN server URLs
- Verify firewall isn't blocking UDP ports
- Check TURN server is running
3. **Candidates gathered but connection fails:**
- Check if TURN relay is actually working (use `turnutils_uclient`)
- Verify server is filtering candidates by role correctly
- Enable detailed logging to see which candidate pairs are failing
## UI Guidelines
- Show clear connection status (waiting, connecting, connected, failed)
- Display peer role (offerer vs answerer) for debugging
- Provide visual feedback for all user actions
- Use toast notifications for errors and success messages

332
README.md
View File

@@ -1,51 +1,54 @@
# 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:** **Related repositories:**
- [rondevu-server](https://github.com/xtr-dev/rondevu-server) - HTTP signaling server - [@xtr-dev/rondevu-client](https://github.com/xtr-dev/rondevu-client) - TypeScript client library ([npm](https://www.npmjs.com/package/@xtr-dev/rondevu-client))
- [rondevu-client](https://github.com/xtr-dev/rondevu-client) - TypeScript client library - [@xtr-dev/rondevu-server](https://github.com/xtr-dev/rondevu-server) - HTTP signaling server ([npm](https://www.npmjs.com/package/@xtr-dev/rondevu-server), [live](https://api.ronde.vu))
- [@xtr-dev/rondevu-demo](https://github.com/xtr-dev/rondevu-demo) - Interactive demo ([live](https://ronde.vu))
--- ---
## 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 via `RondevuPeer`
5. **Chat** - Send messages over WebRTC data channels
🎯 **Connect by Topic** - Auto-discover and join any available peer ### Key Features
👤 **Connect by Peer ID** - Filter and connect to specific peers
🔗 **Connect by Connection ID** - Share a code and connect directly
### Features - **Topic-Based Discovery** - Find peers by shared topics (like torrent infohashes)
- **Real P2P Connections** - Actual WebRTC data channels (not simulated)
- **State-Based Peer Management** - Uses `RondevuPeer` with clean state machine (idle → creating-offer → waiting-for-answer → exchanging-ice → connected)
- **Trickle ICE** - Fast connection establishment by sending ICE candidates as they're discovered
- **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 ## Quick Start
- **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 ### Installation
#### Installation
```bash ```bash
npm install npm install
``` ```
#### Development ### Development
```bash ```bash
npm run dev 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 ```bash
npm run build npm run build
@@ -53,150 +56,190 @@ npm run build
The built files will be in the `dist/` directory. The built files will be in the `dist/` directory.
#### Preview Production Build ### Preview Production Build
```bash ```bash
npm run preview 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") 1. Go to the "Create Offer" tab
2. Click "Join Topic" 2. Enter one or more topics (comma-separated), e.g., `demo-room, testing`
3. Rondevu finds the first available peer and connects automatically 3. Click "Create Offer"
4. Start chatting! 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") 1. Once connected, go to the "Chat" tab
2. Click "Discover in [topic]" to list all available peers 2. Select a connection from the dropdown
3. See each peer's ID in the list 3. Type messages and hit Enter or click Send
4. Click "Connect" on the specific peer you want to talk to 4. Messages are sent **directly peer-to-peer** via WebRTC
5. Start chatting!
**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) ## Testing Locally
**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
The easiest way to test: 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 ## Technical Implementation
- Click on any topic to auto-fill the discovery form
### Server Configuration ### RondevuPeer State Machine
This demo connects to: `https://rondevu.xtrdev.workers.dev` This demo uses the `RondevuPeer` class which implements a clean state-based connection lifecycle:
To use a different server, modify the `baseUrl` in `src/main.js`:
```javascript ```javascript
const client = new RondevuClient({ import { Rondevu } from '@xtr-dev/rondevu-client';
baseUrl: 'https://your-server.com'
// Create peer
const peer = client.createPeer();
// Set up event listeners
peer.on('state', (state) => {
console.log('Peer state:', state);
// Offerer: idle → creating-offer → waiting-for-answer → exchanging-ice → connected
// Answerer: idle → answering → exchanging-ice → connected
});
peer.on('connected', () => {
console.log('✅ P2P connection established!');
});
peer.on('datachannel', (channel) => {
channel.addEventListener('message', (event) => {
console.log('📥 Message:', event.data);
});
channel.addEventListener('open', () => {
// Channel is ready, can send messages
channel.send('Hello!');
});
});
peer.on('failed', (error) => {
console.error('❌ Connection failed:', error);
});
// Create offer (offerer)
await peer.createOffer({
topics: ['demo-room'],
ttl: 300000
});
// Or answer an offer (answerer)
await peer.answer(offerId, offerSdp, {
topics: ['demo-room']
}); });
``` ```
### Technologies ### Connection States
- **Vite** - Fast development and build tool **Offerer Flow:**
- **@xtr-dev/rondevu-client** - TypeScript client for Rondevu API 1. **idle** - Initial state
- **Vanilla JavaScript** - No framework dependencies 2. **creating-offer** - Creating WebRTC offer and sending to server
3. **waiting-for-answer** - Polling for answer from peer (every 2 seconds)
4. **exchanging-ice** - Exchanging ICE candidates (polling every 1 second)
5. **connected** - Successfully connected!
6. **failed/closed** - Connection failed or was closed
### API Examples **Answerer Flow:**
1. **idle** - Initial state
2. **answering** - Creating WebRTC answer and sending to server
3. **exchanging-ice** - Exchanging ICE candidates (polling every 1 second)
4. **connected** - Successfully connected!
5. **failed/closed** - Connection failed or was closed
The demo showcases all major Rondevu API endpoints: ### What Happens Under the Hood
- `GET /` - List all topics 1. **Offerer** calls `peer.createOffer()`:
- `GET /:topic/sessions` - Discover peers in a topic - State → `creating-offer`
- `POST /:topic/offer` - Create a new offer - Creates RTCPeerConnection and data channel
- `POST /answer` - Send answer to a peer - Generates SDP offer
- `POST /poll` - Poll for peer data - Sets up ICE candidate handler (before gathering starts)
- `GET /health` - Check server health - Sets local description → ICE gathering begins
- Posts offer to Rondevu server
- State → `waiting-for-answer`
- Polls for answers every 2 seconds
- When answer received → State → `exchanging-ice`
### WebRTC Implementation Details 2. **Answerer** calls `peer.answer()`:
- State → `answering`
- Creates RTCPeerConnection
- Sets remote description (offer SDP)
- Generates SDP answer
- Sends answer to server (registers as answerer)
- Sets up ICE candidate handler (before gathering starts)
- Sets local description → ICE gathering begins
- State → `exchanging-ice`
This demo implements a **complete WebRTC peer-to-peer connection** with: 3. **ICE Exchange** (Trickle ICE):
- Both peers generate ICE candidates as they're discovered
- Candidates are automatically sent to server immediately
- Peers poll and receive remote candidates (every 1 second)
- ICE establishes the direct P2P path
- State → `connected`
#### Connection Flow 4. **Connection Established**:
- Data channel opens
- Chat messages flow directly between peers
- No server relay (true P2P!)
1. **Offerer** creates an `RTCPeerConnection` and generates an SDP offer ### Key Features of Implementation
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**
#### Key Features - **Trickle ICE**: Candidates sent immediately as discovered (no waiting)
- **Proper Authorization**: Answer sent to server before ICE gathering to authorize candidate posting
- **Event Cleanup**: All event listeners properly removed with `removeEventListener`
- **State Management**: Clean state machine with well-defined transitions
- **Error Handling**: Graceful failure states with error events
- **Real RTCPeerConnection** - Not simulated, actual WebRTC ### Architecture
- **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
#### Technologies - **Frontend**: React + Vite
- **Signaling**: Rondevu server (Cloudflare Workers + D1)
- **Client**: @xtr-dev/rondevu-client (TypeScript library)
- **WebRTC**: RTCPeerConnection with STUN/TURN servers
- **Connection Management**: RondevuPeer class with state machine
- **RTCPeerConnection API** - Core WebRTC connection ## Server Configuration
- **RTCDataChannel API** - Unreliable but fast text messaging
- **Rondevu Signaling** - SDP and ICE candidate exchange
- **STUN Protocol** - NAT traversal (stun.l.google.com)
### Development Notes This demo connects to: `https://api.ronde.vu`
- Peer IDs are auto-generated on page load To use a different server, modify `API_URL` in `src/App.jsx`:
- 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 ```javascript
const API_URL = 'https://your-server.com';
```
#### Deploy to Cloudflare Pages ## Deployment
The demo can be easily deployed to Cloudflare Pages (free tier): ### Deploy to Cloudflare Pages
**Quick Deploy via Wrangler:** **Quick Deploy via Wrangler:**
@@ -213,7 +256,46 @@ npx wrangler pages deploy dist --project-name=rondevu-demo
4. Set output directory: `dist` 4. Set output directory: `dist`
5. Deploy automatically on every push! 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 peer automatically polls for answers and ICE candidates
- Multiple simultaneous connections are supported
- WebRTC uses Google's public STUN servers + custom TURN server for NAT traversal
- Data channel messages are unreliable but fast (perfect for chat)
- Connection cleanup is automatic when peers disconnect
## Connection Timeouts
The demo uses these default timeouts:
- **ICE Gathering**: 10 seconds (not used with trickle ICE)
- **Waiting for Answer**: 30 seconds
- **Creating Answer**: 10 seconds
- **ICE Connection**: 30 seconds
These can be customized in the `PeerOptions`:
```javascript
await peer.createOffer({
topics: ['my-topic'],
timeouts: {
waitingForAnswer: 60000, // 1 minute
iceConnection: 45000 // 45 seconds
}
});
```
## Technologies
- **React** - UI framework
- **Vite** - Build tool and dev server
- **@xtr-dev/rondevu-client** - Rondevu client library with `RondevuPeer`
- **RTCPeerConnection** - WebRTC connections
- **RTCDataChannel** - P2P messaging
- **QRCode** - QR code generation for easy topic sharing
## License
MIT MIT

298
package-lock.json generated
View File

@@ -1,17 +1,19 @@
{ {
"name": "rondevu-demo", "name": "rondevu-demo",
"version": "1.0.2", "version": "2.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "rondevu-demo", "name": "rondevu-demo",
"version": "1.0.2", "version": "2.0.0",
"dependencies": { "dependencies": {
"@xtr-dev/rondevu-client": "^0.12.0",
"@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",
"react-dom": "^18.2.0" "react-dom": "^18.2.0",
"react-hot-toast": "^2.6.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.2.0", "@types/react": "^18.2.0",
@@ -20,15 +22,6 @@
"vite": "^5.4.11" "vite": "^5.4.11"
} }
}, },
"../client": {
"name": "@xtr-dev/rondevu-client",
"version": "0.0.4",
"extraneous": true,
"license": "MIT",
"devDependencies": {
"typescript": "^5.9.3"
}
},
"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",
@@ -752,6 +745,15 @@
"@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",
@@ -760,9 +762,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
"integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -774,9 +776,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
"integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -788,9 +790,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz",
"integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -802,9 +804,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
"integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -816,9 +818,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-arm64": { "node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
"integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -830,9 +832,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-x64": { "node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
"integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -844,9 +846,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
"integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -858,9 +860,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-musleabihf": { "node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
"integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -872,9 +874,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
"integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -886,9 +888,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
"integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -900,9 +902,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-loong64-gnu": { "node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
"integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
@@ -914,9 +916,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-ppc64-gnu": { "node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
"integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@@ -928,9 +930,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
"integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -942,9 +944,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-musl": { "node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
"integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -956,9 +958,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": { "node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
"integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -970,9 +972,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
"integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -984,9 +986,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
"integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -998,9 +1000,9 @@
] ]
}, },
"node_modules/@rollup/rollup-openharmony-arm64": { "node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
"integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1012,9 +1014,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
"integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1026,9 +1028,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
"integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -1040,9 +1042,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-gnu": { "node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
"integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1054,9 +1056,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
"integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1127,14 +1129,14 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "18.3.26", "version": "18.3.27",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
"integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^3.0.2" "csstype": "^3.2.2"
} }
}, },
"node_modules/@types/react-dom": { "node_modules/@types/react-dom": {
@@ -1168,6 +1170,15 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
} }
}, },
"node_modules/@xtr-dev/rondevu-client": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@xtr-dev/rondevu-client/-/rondevu-client-0.12.0.tgz",
"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",
"resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.21.3.tgz", "resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.21.3.tgz",
@@ -1215,9 +1226,9 @@
} }
}, },
"node_modules/baseline-browser-mapping": { "node_modules/baseline-browser-mapping": {
"version": "2.8.23", "version": "2.9.3",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.23.tgz", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.3.tgz",
"integrity": "sha512-616V5YX4bepJFzNyOfce5Fa8fDJMfoxzOIzDCZwaGL8MKVpFrXqfNUoIpRn9YMI5pXf/VKgzjB4htFMsFKKdiQ==", "integrity": "sha512-8QdH6czo+G7uBsNo0GiUfouPN1lRzKdJTGnKXwe12gkFbnnOUaUKGN55dMkfy+mnxmvjwl9zcI4VncczcVXDhA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
@@ -1225,9 +1236,9 @@
} }
}, },
"node_modules/browserslist": { "node_modules/browserslist": {
"version": "4.27.0", "version": "4.28.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
"integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -1245,11 +1256,11 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.8.19", "baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001751", "caniuse-lite": "^1.0.30001759",
"electron-to-chromium": "^1.5.238", "electron-to-chromium": "^1.5.263",
"node-releases": "^2.0.26", "node-releases": "^2.0.27",
"update-browserslist-db": "^1.1.4" "update-browserslist-db": "^1.2.0"
}, },
"bin": { "bin": {
"browserslist": "cli.js" "browserslist": "cli.js"
@@ -1268,9 +1279,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001753", "version": "1.0.30001759",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001753.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz",
"integrity": "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==", "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -1325,10 +1336,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.1.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/debug": { "node_modules/debug": {
@@ -1365,9 +1375,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.244", "version": "1.5.266",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.266.tgz",
"integrity": "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==", "integrity": "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@@ -1473,6 +1483,15 @@
"node": "6.* || 8.* || >= 10.*" "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": { "node_modules/is-fullwidth-code-point": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -1713,6 +1732,23 @@
"react": "^18.3.1" "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": { "node_modules/react-refresh": {
"version": "0.17.0", "version": "0.17.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
@@ -1739,9 +1775,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.52.5", "version": "4.53.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz",
"integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1755,28 +1791,28 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.52.5", "@rollup/rollup-android-arm-eabi": "4.53.3",
"@rollup/rollup-android-arm64": "4.52.5", "@rollup/rollup-android-arm64": "4.53.3",
"@rollup/rollup-darwin-arm64": "4.52.5", "@rollup/rollup-darwin-arm64": "4.53.3",
"@rollup/rollup-darwin-x64": "4.52.5", "@rollup/rollup-darwin-x64": "4.53.3",
"@rollup/rollup-freebsd-arm64": "4.52.5", "@rollup/rollup-freebsd-arm64": "4.53.3",
"@rollup/rollup-freebsd-x64": "4.52.5", "@rollup/rollup-freebsd-x64": "4.53.3",
"@rollup/rollup-linux-arm-gnueabihf": "4.52.5", "@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
"@rollup/rollup-linux-arm-musleabihf": "4.52.5", "@rollup/rollup-linux-arm-musleabihf": "4.53.3",
"@rollup/rollup-linux-arm64-gnu": "4.52.5", "@rollup/rollup-linux-arm64-gnu": "4.53.3",
"@rollup/rollup-linux-arm64-musl": "4.52.5", "@rollup/rollup-linux-arm64-musl": "4.53.3",
"@rollup/rollup-linux-loong64-gnu": "4.52.5", "@rollup/rollup-linux-loong64-gnu": "4.53.3",
"@rollup/rollup-linux-ppc64-gnu": "4.52.5", "@rollup/rollup-linux-ppc64-gnu": "4.53.3",
"@rollup/rollup-linux-riscv64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-gnu": "4.53.3",
"@rollup/rollup-linux-riscv64-musl": "4.52.5", "@rollup/rollup-linux-riscv64-musl": "4.53.3",
"@rollup/rollup-linux-s390x-gnu": "4.52.5", "@rollup/rollup-linux-s390x-gnu": "4.53.3",
"@rollup/rollup-linux-x64-gnu": "4.52.5", "@rollup/rollup-linux-x64-gnu": "4.53.3",
"@rollup/rollup-linux-x64-musl": "4.52.5", "@rollup/rollup-linux-x64-musl": "4.53.3",
"@rollup/rollup-openharmony-arm64": "4.52.5", "@rollup/rollup-openharmony-arm64": "4.53.3",
"@rollup/rollup-win32-arm64-msvc": "4.52.5", "@rollup/rollup-win32-arm64-msvc": "4.53.3",
"@rollup/rollup-win32-ia32-msvc": "4.52.5", "@rollup/rollup-win32-ia32-msvc": "4.53.3",
"@rollup/rollup-win32-x64-gnu": "4.52.5", "@rollup/rollup-win32-x64-gnu": "4.53.3",
"@rollup/rollup-win32-x64-msvc": "4.52.5", "@rollup/rollup-win32-x64-msvc": "4.53.3",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
@@ -1851,9 +1887,9 @@
} }
}, },
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.1.4", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz",
"integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {

View File

@@ -1,7 +1,7 @@
{ {
"name": "rondevu-demo", "name": "rondevu-demo",
"version": "1.0.2", "version": "2.0.0",
"description": "Demo application for Rondevu peer signaling and discovery", "description": "Demo application for Rondevu DNS-like WebRTC with username claiming and service discovery",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -10,10 +10,12 @@
"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",
"@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",
"react-dom": "^18.2.0" "react-dom": "^18.2.0",
"react-hot-toast": "^2.6.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.2.0", "@types/react": "^18.2.0",

1368
src/App-old.jsx Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,95 +2,31 @@ import QRCodeDisplay from './QRCodeDisplay';
function ConnectionForm({ function ConnectionForm({
action, action,
method,
topic,
setTopic,
connectionId, connectionId,
setConnectionId, setConnectionId,
peerId,
setPeerId,
topics,
sessions,
connectionStatus, connectionStatus,
qrCodeUrl, qrCodeUrl,
currentConnectionId, currentConnectionId,
onConnect, onConnect,
onBack, onBack
onTopicSelect,
onDiscoverPeers
}) { }) {
return ( return (
<div className="step-container"> <div className="step-container">
<h2>Enter Details</h2> <h2>{action === 'create' ? 'Create Connection' : 'Join Connection'}</h2>
<div className="form-container"> <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"> <div className="form-group">
<label>Connection ID {action === 'create' && '(optional)'}</label> <label>Connection ID {action === 'create' && '(optional)'}</label>
<input <input
type="text" type="text"
value={connectionId} value={connectionId}
onChange={(e) => setConnectionId(e.target.value)} onChange={(e) => setConnectionId(e.target.value)}
placeholder={action === 'create' ? 'Auto-generated if empty' : 'e.g., meeting-123'} placeholder={action === 'create' ? 'Auto-generated if empty' : 'Enter connection ID'}
autoFocus={action === 'join'} 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"> <div className="button-row">
<button className="back-button" onClick={onBack}> Back</button> <button className="back-button" onClick={onBack}> Back</button>
@@ -99,12 +35,10 @@ function ConnectionForm({
onClick={onConnect} onClick={onConnect}
disabled={ disabled={
connectionStatus === 'connecting' || connectionStatus === 'connecting' ||
(method === 'topic' && !topic) || (action === 'connect' && !connectionId)
(method === 'peer-id' && (!topic || !peerId)) ||
(method === 'connection-id' && action === 'join' && !connectionId)
} }
> >
{connectionStatus === 'connecting' ? 'Connecting...' : 'Connect'} {connectionStatus === 'connecting' ? 'Connecting...' : (action === 'create' ? 'Create' : 'Connect')}
</button> </button>
</div> </div>

View File

@@ -3,7 +3,7 @@ function Header() {
<header className="header"> <header className="header">
<div className="header-content"> <div className="header-content">
<h1>Rondevu</h1> <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"> <div className="header-links">
<a href="https://github.com/xtr-dev/rondevu-client" target="_blank" rel="noopener noreferrer"> <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"> <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"> <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"/> <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> </svg>
View source Demo
</a> </a>
</div> </div>
</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; font-size: 0.95rem;
} }
.help-text {
margin-top: 8px;
font-size: 0.85rem;
color: #6c757d;
}
input[type="text"] { input[type="text"] {
width: 100%; width: 100%;
padding: 12px 16px; padding: 12px 16px;
@@ -790,3 +796,223 @@ input[type="text"]:disabled {
width: 100%; 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);
}
/* Topic cards grid for discovery */
.topic-card-hover {
padding: 25px;
background: white;
border-radius: 12px;
border: 2px solid #e0e0e0;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.topic-card-hover:hover {
border-color: #667eea;
background: #fafbfc;
transform: translateY(-3px);
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.2);
}