Commit Graph

161 Commits

Author SHA1 Message Date
3e55166cea Reorganize src/ directory into feature/domain-based structure
Restructure flat src/ directory (17 files) into organized folders:

Structure:
- src/core/ - Main API (rondevu.ts, offer-pool.ts, types.ts, index.ts)
- src/connections/ - WebRTC connections (base.ts, offerer.ts, answerer.ts, config.ts, events.ts)
- src/api/ - HTTP layer (client.ts, batcher.ts)
- src/crypto/ - Crypto adapters (adapter.ts, node.ts, web.ts)
- src/utils/ - Utilities (async-lock.ts, exponential-backoff.ts, message-buffer.ts)

Changes:
- Move all 17 files to appropriate feature folders
- Update all import paths to reflect new structure
- Update package.json main/types to point to dist/core/index.js
- Preserve git history with git mv

Benefits:
- Clear separation of concerns
- Easier navigation and maintenance
- Better scalability for future features
- Logical grouping of related files

🤖 Generated with Claude Code
2025-12-16 22:51:23 +01:00
121a4d490a v0.20.0: Connection persistence with offer rotation
Implement connection persistence for offerer side through "offer rotation".
When a connection fails, the same OffererConnection object is rebound to a
new offer instead of being destroyed, preserving message buffers and event
listeners.

Features:
- Connection objects persist across disconnections
- Message buffering works seamlessly through rotations
- Event listeners remain active after rotation
- New `connection:rotated` event for tracking offer changes
- Max rotation attempts limit (default: 5) with fallback

Implementation:
- Add OffererConnection.rebindToOffer() method with AsyncLock protection
- Add rotation tracking: rotating flag, rotationAttempts counter
- Add OfferPool.createNewOfferForRotation() helper method
- Modify OfferPool failure handler to rotate instead of destroy
- Add connection:rotated event to OfferPoolEvents interface
- Forward connection:rotated event in Rondevu class
- Add edge case handling for cleanup during rotation
- Reset rotation attempts on successful connection

Documentation:
- Add "Connection Persistence" section to README with examples
- Update "New in v0.20.0" feature list
- Add v0.20.0 changelog entry
- Document rotation benefits and behavior

Benefits:
- Same connection object remains usable through disconnections
- Message buffer preserved during temporary disconnections
- Event listeners don't need to be re-registered
- Simpler user code - no need to track new connections

100% backward compatible - no breaking changes.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-16 22:36:28 +01:00
c30e554525 v0.19.0: Internal refactoring for improved maintainability
Internal improvements (100% backward compatible):

- Extract OfferPool class from Rondevu for offer lifecycle management
- Consolidate ICE polling logic into base RondevuConnection class
  (removes ~86 lines of duplicate code)
- Add AsyncLock utility for race-free concurrent operations
- Disable reconnection for offerer connections (offers are ephemeral)
- Fix compilation with abstract method implementations

Architecture improvements:
- rondevu.ts: Reduced complexity by extracting OfferPool
- connection.ts: Added consolidated pollIceCandidates() implementation
- offerer-connection.ts: Force reconnectEnabled: false in constructor
- answerer-connection.ts: Implement abstract methods from base class

New files:
- src/async-lock.ts: Mutual exclusion primitive for async operations
- src/offer-pool.ts: Manages WebRTC offer lifecycle independently

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-16 22:03:32 +01:00
7c903f7e23 v0.18.11 - Restore EventEmitter-based durable connections
- Revert the v0.18.10 revert, going back to EventEmitter API
- Durable WebRTC connections with auto-reconnect and message buffering
- AnswererConnection and OffererConnection classes
- Rich event system with 20+ events
- Updated README with v0.18.11 references and changelog
2025-12-16 21:30:59 +01:00
cb0bbe342c Update README for v0.18.9: Document durable connections and breaking changes
- Add v0.18.9 features section highlighting durable connections
- Document breaking change: connectToService() returns AnswererConnection
- Update Quick Start examples with correct event-driven API
- Add explicit warnings about waiting for 'connected' event
- Include Connection Configuration and Events documentation
- Add Migration Guide section with upgrade instructions
- Update changelog with v0.18.9 changes

This addresses user issues where messages were buffered instead of sent
due to sending before connection established.
2025-12-14 18:27:31 +01:00
919aeb7b90 Bump version to 0.18.9
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-14 16:53:45 +01:00
a480fa3ba4 Add durable WebRTC connections with auto-reconnect and message buffering (v0.18.8)
- Add connection state machine with proper lifecycle management
- Implement automatic reconnection with exponential backoff
- Add message buffering during disconnections
- Create RondevuConnection base class with state tracking
- Create OffererConnection and AnswererConnection classes
- Fix ICE polling lifecycle (now stops when connected)
- Add fillOffers() semaphore to prevent exceeding maxOffers
- Implement answer fingerprinting to prevent duplicate processing
- Add dual ICE state monitoring (iceConnectionState + connectionState)
- Fix data channel handler timing issues
- Add comprehensive event system (20+ events)
- Add connection timeouts and proper cleanup

Breaking changes:
- connectToService() now returns AnswererConnection instead of ConnectionContext
- connection:opened event signature changed: (offerId, dc) → (offerId, connection)
- Direct DataChannel access replaced with connection wrapper API

See MIGRATION.md for upgrade guide.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-14 16:52:57 +01:00
9a4fbb63f8 v0.18.7 - Revert to v0.18.3 answer processing logic v0.18.7 2025-12-14 14:39:26 +01:00
f8fb842935 Revert to v0.18.3 answer processing logic
Reverted pollInternal to exactly match v0.18.3 which was the last
working version. The changes in v0.18.5 and v0.18.6 that moved the
answered flag and timestamp updates were causing issues.

v0.18.3 working logic restored:
- Check !activeOffer.answered
- Call setRemoteDescription (no try/catch)
- Set answered = true AFTER
- Update lastPollTimestamp AFTER
- No pre-emptive timestamp updates

The only difference from v0.18.3 is the eventemitter3 import which
should not affect answer processing behavior.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-14 14:39:15 +01:00
50d49d80d3 v0.18.6 - Fix duplicate answer bug (timestamp update) v0.18.6 2025-12-14 14:29:06 +01:00
b3b1751f63 Fix duplicate answer bug - update timestamp before processing
Root cause: When setRemoteDescription failed, lastPollTimestamp was
never updated, causing the server to return the same answer repeatedly.

Solution:
1. Update lastPollTimestamp BEFORE processing answers
2. Calculate max timestamp from all received answers upfront
3. Don't throw on setRemoteDescription errors - just log and continue
4. This ensures we advance the timestamp even if processing fails

This prevents the infinite loop of:
- Poll returns answer
- Processing fails
- Timestamp not updated
- Next poll returns same answer
- Repeat

Now the timestamp advances regardless of processing success,
preventing duplicate answer fetches from the server.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-14 14:28:57 +01:00
e48b3bb17a v0.18.5 - Fix duplicate answer processing regression v0.18.5 2025-12-14 14:19:44 +01:00
8f7e15e633 Fix duplicate answer processing regression
The activeOffer.answered flag was being set AFTER the async
setRemoteDescription() call, creating a race condition window
where the same answer could be processed multiple times.

Root cause:
- Check `!activeOffer.answered` happens
- setRemoteDescription() starts (async operation)
- Before it completes, another check could happen
- Same answer gets processed twice → "stable" state error

Fix:
- Set activeOffer.answered = true BEFORE setRemoteDescription
- Add try/catch to reset flag if setRemoteDescription fails
- This prevents duplicate processing while allowing retries on error

This regression was introduced when the answered flag assignment
was not moved along with other polling logic changes.

Fixes: #6 regression

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-14 14:19:34 +01:00
fcd0f8ead0 0.18.4 v0.18.4 2025-12-14 14:06:57 +01:00
8fd4b249de Fix EventEmitter for cross-platform compatibility (v0.18.3)
Replace Node.js 'events' module with 'eventemitter3' package
to ensure compatibility in both browser and Node.js environments.

Changes:
- Replace import from 'events' to 'eventemitter3'
- Add eventemitter3 as dependency
- Remove @types/node (no longer needed)

Fixes browser bundling error where 'events' module was not available.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-14 14:05:36 +01:00
275c156c64 0.18.3 v0.18.3 2025-12-14 13:35:45 +01:00
c60a5f332a Merge remote-tracking branch 'origin/main' 2025-12-14 13:35:38 +01:00
Bas
ecd6be7f8a Merge pull request #7 from xtr-dev/claude/fix-issue-6-CTKj9
Fix issue #6
2025-12-14 13:35:19 +01:00
Claude
e652fdc130 Fix duplicate answer processing race condition (#6)
Add polling guard to prevent concurrent pollInternal() execution.
The setInterval callback doesn't await the async pollInternal(),
which could cause multiple polls to process the same answer before
lastPollTimestamp is updated, resulting in "Called in wrong state:
stable" errors from setRemoteDescription().
2025-12-14 11:59:43 +00:00
0f469e234d 0.18.2 v0.18.2 2025-12-14 12:41:22 +01:00
68c3ffb4ea Add DX improvements and EventEmitter support (v0.18.1)
This release introduces several developer experience improvements:

Breaking Changes:
- Add EventEmitter support - Rondevu now extends EventEmitter
- Consolidate discovery methods into findService() (getService, discoverService, discoverServices methods still exist but findService is the new unified API)

New Features:
- EventEmitter lifecycle events:
  - offer:created (offerId, serviceFqn)
  - offer:answered (offerId, peerUsername)
  - connection:opened (offerId, dataChannel)
  - connection:closed (offerId)
  - ice:candidate:local (offerId, candidate) - locally generated ICE
  - ice:candidate:remote (offerId, candidate, role) - remote ICE from server
  - error (error, context)

- Unified findService() method with modes:
  - 'direct' - direct lookup by FQN with username
  - 'random' - random discovery without username
  - 'paginated' - paginated results with limit/offset

- Typed error classes for better error handling:
  - RondevuError (base class with context)
  - NetworkError (network/API failures)
  - ValidationError (input validation)
  - ConnectionError (WebRTC connection issues)

- Convenience methods:
  - getOfferCount() - get active offer count
  - isConnected(offerId) - check connection status
  - disconnectAll() - close all connections
  - getServiceStatus() - get service state

Type Exports:
- Export ActiveOffer interface for getActiveOffers() typing
- Export FindServiceOptions, ServiceResult, PaginatedServiceResult
- Export all error classes

Dependencies:
- Add @types/node for EventEmitter support

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-14 12:03:41 +01:00
02e8e971be 0.18.1 2025-12-14 12:03:41 +01:00
Bas
1ca9a91056 Merge pull request #5 from xtr-dev/claude/fix-issue-2-6zYvD
Refactor OfferFactory to receive pc from Rondevu
2025-12-14 11:24:54 +01:00
claude[bot]
a0dc9ddad0 Address code review suggestions
- Update README.md example to match new OfferFactory signature
- Add error handling and RTCPeerConnection cleanup on factory failure
- Document setupIceCandidateHandler() method usage
- Use undefined instead of null for offerId variable

Co-authored-by: Bas <bvdaakster@users.noreply.github.com>
2025-12-14 10:21:43 +00:00
Claude
df231c192d Refactor OfferFactory to receive pc from Rondevu
Change the OfferFactory signature to receive the RTCPeerConnection as a
parameter instead of rtcConfig. This allows Rondevu to:

1. Create the RTCPeerConnection itself
2. Set up ICE candidate handlers BEFORE the factory runs
3. Ensure no candidates are lost when setLocalDescription() triggers
   ICE gathering

This is a cleaner fix for #2 that eliminates the race condition at the
source rather than working around it with queuing.

BREAKING CHANGE: OfferFactory signature changed from
  (rtcConfig: RTCConfiguration) => Promise<OfferContext>
to
  (pc: RTCPeerConnection) => Promise<OfferContext>

OfferContext no longer includes 'pc' since it's now provided by Rondevu.
2025-12-14 10:10:12 +00:00
Bas
4d90cce9d0 Merge pull request #3 from xtr-dev/claude/fix-issue-2-6zYvD
Fix issue #2
2025-12-14 11:03:47 +01:00
Bas
f5e202384a Merge pull request #4 from xtr-dev/add-claude-github-actions-1765706414732
Add Claude Code GitHub Workflow
2025-12-14 11:01:07 +01:00
Bas
a21fb04a6f "Claude Code Review workflow" 2025-12-14 11:00:16 +01:00
Bas
6ee1d7b5a9 "Claude PR Assistant workflow" 2025-12-14 11:00:15 +01:00
Claude
62a6cdcb99 Update package-lock.json 2025-12-14 09:57:07 +00:00
Claude
febe3b7270 Fix early ICE candidates lost due to late handler setup in createOffer()
Queue ICE candidates that are generated before we have the offerId from
the server. When the factory calls setLocalDescription(), ICE gathering
starts immediately, but we couldn't send candidates until we had the
offerId from publishService(). Now we:

1. Set up a queuing handler immediately after getting the pc from factory
2. Buffer any early candidates while publishing to get the offerId
3. Flush all queued candidates once we have the offerId
4. Continue handling future candidates normally

Fixes #2
2025-12-14 09:56:27 +00:00
83831cae77 v0.18.0 - Add WebRTC polyfill support for Node.js v0.18.0 2025-12-13 18:47:38 +01:00
e954a70aa7 Add WebRTC polyfill support for Node.js environments
Allow users to pass WebRTC polyfills (RTCPeerConnection, RTCIceCandidate) through RondevuOptions instead of manually setting global variables. The client now automatically applies these to globalThis when provided.

This simplifies Node.js integration:
- Before: Users had to manually set globalThis.RTCPeerConnection
- After: Pass rtcPeerConnection and rtcIceCandidate options

Example:
  const rondevu = await Rondevu.connect({
    apiUrl: 'https://api.example.com',
    username: 'alice',
    cryptoAdapter: new NodeCryptoAdapter(),
    rtcPeerConnection: wrtc.RTCPeerConnection,
    rtcIceCandidate: wrtc.RTCIceCandidate
  })
2025-12-13 18:47:27 +01:00
9b879522da v0.17.1 - Fix ICE candidate handling for Node.js compatibility v0.17.1 2025-12-13 15:19:49 +01:00
eb280e3826 Fix ICE candidate handling for Node.js compatibility
Handle both browser and Node.js (wrtc) environments when processing ICE candidates. Browsers provide toJSON() method on candidates, but wrtc library candidates are already plain objects. Check for toJSON() existence before calling it.

Fixes: TypeError: event.candidate.toJSON is not a function in Node.js
2025-12-13 15:19:45 +01:00
0aa9921941 Release v0.17.0: Phase 1 & 2 improvements, documentation restructuring v0.17.0 2025-12-13 13:18:43 +01:00
5fc20f1be9 Remove Node.js Host Guide references
Remove all references to NODE_HOST_GUIDE.md and test-connect.js:
- README.md: Removed Node.js Host Guide section and links
- ADVANCED.md: Simplified Node.js example, removed guide references

Keep inline Node.js example code for reference.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 23:24:56 +01:00
54c371f451 Fix broken relative links to demo repository
Change relative paths to absolute GitHub URLs:
- ../demo/NODE_HOST_GUIDE.md → github.com/xtr-dev/rondevu-demo/blob/main/NODE_HOST_GUIDE.md
- ../demo/test-connect.js → github.com/xtr-dev/rondevu-demo/blob/main/test-connect.js

Affected files:
- README.md (2 links fixed)
- ADVANCED.md (2 links fixed)

Why: Client and demo are separate repositories, so relative paths
don't work when viewing on GitHub. Now links work from anywhere.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 23:18:50 +01:00
5f4743e086 Make README more concise with ADVANCED.md
Restructure documentation for better discoverability:

Changes:
- README.md: 551 → 181 lines (67% reduction)
- ADVANCED.md: New comprehensive guide (500+ lines)

README.md now contains:
 Features overview
 Installation
 Quick start examples (offerer & answerer)
 Core API summary
 Links to advanced docs

ADVANCED.md contains:
📚 Complete API reference (all methods)
📚 Type definitions
📚 Platform support details (Browser & Node.js)
📚 Advanced usage patterns
📚 Username rules and FQN format
📚 Migration guides
📚 Examples

Benefits:
- Faster onboarding for new users
- Essential info in README
- Detailed reference still accessible
- Better documentation structure

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 23:14:54 +01:00
2ce3e98df0 Improve type safety: Replace any with proper types
Replace `candidate: any` with `candidate: RTCIceCandidateInit | null`:

Changes:
- api.ts poll() return type (line 366)
- rondevu.ts pollOffers() return type (line 827)
- IceCandidate interface (line 41)

Benefits:
- Better type safety and IntelliSense support
- Matches WebRTC spec (candidates can be null)
- Catches potential type errors at compile time
- Clearer API contracts for consumers

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 23:10:57 +01:00
800f6eaa94 Refactor connectToService() method for better maintainability
Break down 129-line method into focused helper methods:

New private methods:
- resolveServiceFqn(): Determines full FQN from various input options
  Handles direct FQN, service+username, or discovery mode

- startIcePolling(): Manages remote ICE candidate polling
  Encapsulates polling logic and interval management

Benefits:
- connectToService() reduced from 129 to ~98 lines
- Each method has single responsibility
- Easier to test and maintain
- Better code readability with clear method names
- Reusable components for future features

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 23:07:49 +01:00
d6a9876440 Extract duplicate ICE candidate handling code
Refactor ICE candidate handler setup:
- Create setupIceCandidateHandler() private method
- Remove duplicate code from createOffer() and connectToService()
- Both methods now call the shared handler setup

Benefits:
- DRY principle: 13 lines of duplicate code eliminated
- Easier to maintain: changes to ICE handling logic only needed in one place
- Consistent error handling across both code paths

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 23:06:22 +01:00
9262043e97 Add debug mode and wrap all console.log statements
Implements opt-in debug logging system:
- Add debug?: boolean option to RondevuOptions
- Add private debug() method that only logs when debug mode is enabled
- Replace all 25+ console.log statements with this.debug() calls
- Static connect() method checks options.debug before logging

Benefits:
- Clean production console output by default
- Users can enable debug logging when needed: debug: true
- All debug messages prefixed with [Rondevu]
- Error logging (console.error) preserved for important failures

Usage:
```typescript
const rondevu = await Rondevu.connect({
  apiUrl: 'https://api.ronde.vu',
  username: 'alice',
  debug: true  // Enable debug logging
})
```

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 23:05:13 +01:00
bd16798a2f Replace magic numbers with named constants in client
Refactoring: Extract magic numbers to static constants
- DEFAULT_TTL_MS = 300000 (5 minutes)
- POLLING_INTERVAL_MS = 1000 (1 second)

Replaced in:
- ttl property initialization (line 173)
- publishService() default (line 335)
- startFilling() polling interval (line 500)
- connectToService() ICE polling (line 636)

Impact: Improves code clarity and maintainability
2025-12-12 22:55:24 +01:00
c662161cd9 Add input validation to connectToService()
Validation: Add checks for empty strings in connectToService()
- Validates serviceFqn is not empty string
- Validates service is not empty string
- Validates username is not empty string
- Prevents silent failures from whitespace-only inputs

Impact: Better error messages for invalid inputs
2025-12-12 22:51:58 +01:00
a9a0127ccf Remove unused imports: Service and ServiceRequest
Cleanup: Remove Service and ServiceRequest types from imports
- Not used anywhere in rondevu.ts
- Reduces unnecessary dependencies
- Verified: Build passes with no TypeScript errors
2025-12-12 22:51:20 +01:00
4345709e9c Remove RondevuSignaler documentation and outdated files
- Remove entire RondevuSignaler class documentation section
- Remove PollingConfig interface from Types section
- Delete obsolete USAGE.md file (completely outdated)
- Update all examples to use new automatic API
- Fix migration examples to show current publishService() format
2025-12-12 22:43:48 +01:00
43dfd72c3d Remove all legacy and backward compatibility support
- Remove serviceFqn, offers array from PublishServiceOptions
- Make service and maxOffers required fields
- Simplify publishService() to only support automatic mode
- Remove RondevuSignaler class completely
- Update exports to include new types (ConnectionContext, ConnectToServiceOptions)
- Update test-connect.js to use connectToService()
- Remove all "manual mode" and "legacy" references from documentation

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 22:33:29 +01:00
ec19ce50db Add connectToService() for automatic answering side setup
- Add ConnectToServiceOptions and ConnectionContext interfaces
- Implement connectToService() method that handles entire answering flow
- Automatically discovers/gets service, creates RTCPeerConnection, exchanges answer and ICE candidates
- Supports both direct lookup (serviceFqn) and discovery (service)
- Returns connection context with pc, dc, serviceFqn, offerId, and peerUsername
- Update README with automatic mode examples

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 22:26:44 +01:00
e5c82b75b1 Add ICE server preset support and update to Rondevu.connect()
- Add 4 ICE server presets: ipv4-turn, hostname-turns, google-stun, relay-only
- Update Rondevu.connect() to accept preset string or custom RTCIceServer array
- Support both automatic (maxOffers) and manual (offers array) modes in publishService()
- Rename internal poll() to pollInternal() to avoid conflict with public API
- Update README examples to use presets and proper offer factory pattern

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 22:13:03 +01:00