Add WebRTC polyfill support for Node.js environments

Added optional polyfill parameters to RondevuOptions to support Node.js:
- RTCPeerConnection: Custom peer connection implementation
- RTCSessionDescription: Custom session description implementation
- RTCIceCandidate: Custom ICE candidate implementation

This allows users to plug in wrtc or node-webrtc packages for full
WebRTC support in Node.js environments. Updated documentation with
usage examples and environment compatibility matrix.

Version bumped to 0.7.4

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-16 20:16:42 +01:00
parent c860419e66
commit 53206d306b
6 changed files with 139 additions and 18 deletions

View File

@@ -280,6 +280,43 @@ const client = new Rondevu({
}); });
``` ```
### Node.js with WebRTC (wrtc)
For WebRTC functionality in Node.js, you need to provide WebRTC polyfills since Node.js doesn't have native WebRTC support:
```bash
npm install wrtc node-fetch
```
```typescript
import { Rondevu } from '@xtr-dev/rondevu-client';
import fetch from 'node-fetch';
import { RTCPeerConnection, RTCSessionDescription, RTCIceCandidate } from 'wrtc';
const client = new Rondevu({
baseUrl: 'https://api.ronde.vu',
fetch: fetch as any,
RTCPeerConnection,
RTCSessionDescription,
RTCIceCandidate
});
// Now you can use WebRTC features
await client.register();
const peer = client.createPeer({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' }
]
});
// Create offers, answer, etc.
const offerId = await peer.createOffer({
topics: ['my-topic']
});
```
**Note:** The `wrtc` package provides WebRTC bindings for Node.js. Alternative packages like `node-webrtc` can also be used - just pass their implementations to the Rondevu constructor.
### Deno ### Deno
```typescript ```typescript
@@ -500,28 +537,36 @@ import type {
The client library is designed to work across different JavaScript runtimes: The client library is designed to work across different JavaScript runtimes:
| Environment | Native Fetch | Custom Fetch Needed | | Environment | Native Fetch | Native WebRTC | Polyfills Needed |
|-------------|--------------|---------------------| |-------------|--------------|---------------|------------------|
| Modern Browsers | ✅ Yes | ❌ No | | Modern Browsers | ✅ Yes | ✅ Yes | ❌ None |
| Node.js 18+ | ✅ Yes | ❌ No | | Node.js 18+ | ✅ Yes | ❌ No | ✅ WebRTC (wrtc) |
| Node.js < 18 | ❌ No | ✅ Yes (node-fetch) | | Node.js < 18 | ❌ No | ❌ No | ✅ Fetch + WebRTC |
| Deno | ✅ Yes | ❌ No | | Deno | ✅ Yes | ⚠️ Partial | ❌ None (signaling only) |
| Bun | ✅ Yes | ❌ No | | Bun | ✅ Yes | ❌ No | ✅ WebRTC (wrtc) |
| Cloudflare Workers | ✅ Yes | ❌ No | | Cloudflare Workers | ✅ Yes | ❌ No | ❌ None (signaling only) |
**If your environment doesn't have native fetch:** **For signaling-only (no WebRTC peer connections):**
Use the low-level API with `client.offers` - no WebRTC polyfills needed.
**For full WebRTC support in Node.js:**
```bash ```bash
npm install node-fetch npm install wrtc node-fetch
``` ```
```typescript ```typescript
import { Rondevu } from '@xtr-dev/rondevu-client'; import { Rondevu } from '@xtr-dev/rondevu-client';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
import { RTCPeerConnection, RTCSessionDescription, RTCIceCandidate } from 'wrtc';
const client = new Rondevu({ const client = new Rondevu({
baseUrl: 'https://rondevu.xtrdev.workers.dev', baseUrl: 'https://api.ronde.vu',
fetch: fetch as any fetch: fetch as any,
RTCPeerConnection,
RTCSessionDescription,
RTCIceCandidate
}); });
``` ```

View File

@@ -1,6 +1,6 @@
{ {
"name": "@xtr-dev/rondevu-client", "name": "@xtr-dev/rondevu-client",
"version": "0.7.3", "version": "0.7.4",
"description": "TypeScript client for Rondevu topic-based peer discovery and signaling server", "description": "TypeScript client for Rondevu topic-based peer discovery and signaling server",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",

View File

@@ -43,7 +43,7 @@ export class ExchangingIceState extends PeerState {
for (const cand of candidates) { for (const cand of candidates) {
if (cand.candidate && cand.candidate.candidate && cand.candidate.candidate !== '') { if (cand.candidate && cand.candidate.candidate && cand.candidate.candidate !== '') {
try { try {
await this.peer.pc.addIceCandidate(new RTCIceCandidate(cand.candidate)); await this.peer.pc.addIceCandidate(new this.peer.RTCIceCandidate(cand.candidate));
this.lastIceTimestamp = cand.createdAt; this.lastIceTimestamp = cand.createdAt;
} catch (err) { } catch (err) {
console.warn('Failed to add ICE candidate:', err); console.warn('Failed to add ICE candidate:', err);

View File

@@ -24,6 +24,11 @@ export default class RondevuPeer extends EventEmitter<PeerEvents> {
offerId?: string; offerId?: string;
role?: 'offerer' | 'answerer'; role?: 'offerer' | 'answerer';
// WebRTC polyfills for Node.js compatibility
RTCPeerConnection: typeof RTCPeerConnection;
RTCSessionDescription: typeof RTCSessionDescription;
RTCIceCandidate: typeof RTCIceCandidate;
private _state: PeerState; private _state: PeerState;
// Event handler references for cleanup // Event handler references for cleanup
@@ -60,11 +65,34 @@ export default class RondevuPeer extends EventEmitter<PeerEvents> {
{ urls: 'stun:stun.l.google.com:19302' }, { urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' } { urls: 'stun:stun1.l.google.com:19302' }
] ]
} },
rtcPeerConnection?: typeof RTCPeerConnection,
rtcSessionDescription?: typeof RTCSessionDescription,
rtcIceCandidate?: typeof RTCIceCandidate
) { ) {
super(); super();
this.offersApi = offersApi; this.offersApi = offersApi;
this.pc = new RTCPeerConnection(rtcConfig);
// Use provided polyfills or fall back to globals
this.RTCPeerConnection = rtcPeerConnection || (typeof globalThis.RTCPeerConnection !== 'undefined'
? globalThis.RTCPeerConnection
: (() => {
throw new Error('RTCPeerConnection is not available. Please provide it in the Rondevu constructor options for Node.js environments.');
}) as any);
this.RTCSessionDescription = rtcSessionDescription || (typeof globalThis.RTCSessionDescription !== 'undefined'
? globalThis.RTCSessionDescription
: (() => {
throw new Error('RTCSessionDescription is not available. Please provide it in the Rondevu constructor options for Node.js environments.');
}) as any);
this.RTCIceCandidate = rtcIceCandidate || (typeof globalThis.RTCIceCandidate !== 'undefined'
? globalThis.RTCIceCandidate
: (() => {
throw new Error('RTCIceCandidate is not available. Please provide it in the Rondevu constructor options for Node.js environments.');
}) as any);
this.pc = new this.RTCPeerConnection(rtcConfig);
this._state = new IdleState(this); this._state = new IdleState(this);
this.setupPeerConnection(); this.setupPeerConnection();

View File

@@ -27,7 +27,7 @@ export abstract class PeerState {
async handleIceCandidate(candidate: any): Promise<void> { async handleIceCandidate(candidate: any): Promise<void> {
// ICE candidates can arrive in multiple states, so default is to add them // ICE candidates can arrive in multiple states, so default is to add them
if (this.peer.pc.remoteDescription) { if (this.peer.pc.remoteDescription) {
await this.peer.pc.addIceCandidate(new RTCIceCandidate(candidate)); await this.peer.pc.addIceCandidate(new this.peer.RTCIceCandidate(candidate));
} }
} }

View File

@@ -25,6 +25,42 @@ export interface RondevuOptions {
* ``` * ```
*/ */
fetch?: FetchFunction; fetch?: FetchFunction;
/**
* Custom RTCPeerConnection implementation for Node.js environments
* Required when using in Node.js with wrtc or similar polyfills
*
* @example Node.js with wrtc
* ```typescript
* import { RTCPeerConnection } from 'wrtc';
* const client = new Rondevu({ RTCPeerConnection });
* ```
*/
RTCPeerConnection?: typeof RTCPeerConnection;
/**
* Custom RTCSessionDescription implementation for Node.js environments
* Required when using in Node.js with wrtc or similar polyfills
*
* @example Node.js with wrtc
* ```typescript
* import { RTCSessionDescription } from 'wrtc';
* const client = new Rondevu({ RTCSessionDescription });
* ```
*/
RTCSessionDescription?: typeof RTCSessionDescription;
/**
* Custom RTCIceCandidate implementation for Node.js environments
* Required when using in Node.js with wrtc or similar polyfills
*
* @example Node.js with wrtc
* ```typescript
* import { RTCIceCandidate } from 'wrtc';
* const client = new Rondevu({ RTCIceCandidate });
* ```
*/
RTCIceCandidate?: typeof RTCIceCandidate;
} }
export class Rondevu { export class Rondevu {
@@ -33,10 +69,16 @@ export class Rondevu {
private credentials?: Credentials; private credentials?: Credentials;
private baseUrl: string; private baseUrl: string;
private fetchFn?: FetchFunction; private fetchFn?: FetchFunction;
private rtcPeerConnection?: typeof RTCPeerConnection;
private rtcSessionDescription?: typeof RTCSessionDescription;
private rtcIceCandidate?: typeof RTCIceCandidate;
constructor(options: RondevuOptions = {}) { constructor(options: RondevuOptions = {}) {
this.baseUrl = options.baseUrl || 'https://api.ronde.vu'; this.baseUrl = options.baseUrl || 'https://api.ronde.vu';
this.fetchFn = options.fetch; this.fetchFn = options.fetch;
this.rtcPeerConnection = options.RTCPeerConnection;
this.rtcSessionDescription = options.RTCSessionDescription;
this.rtcIceCandidate = options.RTCIceCandidate;
this.auth = new RondevuAuth(this.baseUrl, this.fetchFn); this.auth = new RondevuAuth(this.baseUrl, this.fetchFn);
@@ -98,6 +140,12 @@ export class Rondevu {
throw new Error('Not authenticated. Call register() first or provide credentials.'); throw new Error('Not authenticated. Call register() first or provide credentials.');
} }
return new RondevuPeer(this._offers, rtcConfig); return new RondevuPeer(
this._offers,
rtcConfig,
this.rtcPeerConnection,
this.rtcSessionDescription,
this.rtcIceCandidate
);
} }
} }