Add Node.js support via WebRTC polyfill injection

- Added WebRTCPolyfill interface for injecting WebRTC implementations
- Added wrtc option to RondevuOptions and RondevuConnectionParams
- Updated Rondevu and RondevuConnection to use injected APIs
- Added helpful error message when RTCPeerConnection is not available
- Updated README with Node.js usage examples
- Version bumped to 0.3.0

Fixes: RTCPeerConnection not defined error in Node.js

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-12 22:07:01 +01:00
parent 2b73e6ba44
commit 2e4d0d6a54
6 changed files with 85 additions and 8 deletions

View File

@@ -24,6 +24,8 @@ npm install @xtr-dev/rondevu-client
### Usage ### Usage
#### Browser
```typescript ```typescript
import { Rondevu } from '@xtr-dev/rondevu-client'; import { Rondevu } from '@xtr-dev/rondevu-client';
@@ -56,6 +58,44 @@ conn.on('connect', () => {
}); });
``` ```
#### Node.js
In Node.js, you need to provide a WebRTC polyfill since WebRTC APIs are not natively available:
```bash
npm install @roamhq/wrtc
# or
npm install wrtc
```
```typescript
import { Rondevu } from '@xtr-dev/rondevu-client';
import wrtc from '@roamhq/wrtc';
import fetch from 'node-fetch';
const rdv = new Rondevu({
baseUrl: 'https://server.com',
fetch: fetch as any,
wrtc: {
RTCPeerConnection: wrtc.RTCPeerConnection,
RTCSessionDescription: wrtc.RTCSessionDescription,
RTCIceCandidate: wrtc.RTCIceCandidate,
},
rtcConfig: {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' }
]
}
});
// Rest is the same as browser usage
const conn = await rdv.join('room');
conn.on('connect', () => {
const channel = conn.dataChannel('chat');
channel.send('Hello from Node.js!');
});
```
### API ### API
**Main Methods:** **Main Methods:**

View File

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

View File

@@ -1,6 +1,6 @@
import { EventEmitter } from './event-emitter.js'; import { EventEmitter } from './event-emitter.js';
import { RondevuAPI } from './client.js'; import { RondevuAPI } from './client.js';
import { RondevuConnectionParams } from './types.js'; import { RondevuConnectionParams, WebRTCPolyfill } from './types.js';
/** /**
* Represents a WebRTC connection with automatic signaling and ICE exchange * Represents a WebRTC connection with automatic signaling and ICE exchange
@@ -21,6 +21,8 @@ export class RondevuConnection extends EventEmitter {
private connectionTimer?: ReturnType<typeof setTimeout>; private connectionTimer?: ReturnType<typeof setTimeout>;
private isPolling: boolean = false; private isPolling: boolean = false;
private isClosed: boolean = false; private isClosed: boolean = false;
private wrtc?: WebRTCPolyfill;
private RTCIceCandidate: typeof RTCIceCandidate;
constructor(params: RondevuConnectionParams, client: RondevuAPI) { constructor(params: RondevuConnectionParams, client: RondevuAPI) {
super(); super();
@@ -34,6 +36,10 @@ export class RondevuConnection extends EventEmitter {
this.dataChannels = new Map(); this.dataChannels = new Map();
this.pollingIntervalMs = params.pollingInterval; this.pollingIntervalMs = params.pollingInterval;
this.connectionTimeoutMs = params.connectionTimeout; this.connectionTimeoutMs = params.connectionTimeout;
this.wrtc = params.wrtc;
// Use injected WebRTC polyfill or fall back to global
this.RTCIceCandidate = params.wrtc?.RTCIceCandidate || globalThis.RTCIceCandidate;
this.setupEventHandlers(); this.setupEventHandlers();
this.startConnectionTimeout(); this.startConnectionTimeout();
@@ -187,7 +193,7 @@ export class RondevuConnection extends EventEmitter {
for (const candidateStr of offererResponse.answerCandidates) { for (const candidateStr of offererResponse.answerCandidates) {
try { try {
const candidate = JSON.parse(candidateStr); const candidate = JSON.parse(candidateStr);
await this.pc.addIceCandidate(new RTCIceCandidate(candidate)); await this.pc.addIceCandidate(new this.RTCIceCandidate(candidate));
} catch (err) { } catch (err) {
console.warn('Failed to add ICE candidate:', err); console.warn('Failed to add ICE candidate:', err);
} }
@@ -202,7 +208,7 @@ export class RondevuConnection extends EventEmitter {
for (const candidateStr of answererResponse.offerCandidates) { for (const candidateStr of answererResponse.offerCandidates) {
try { try {
const candidate = JSON.parse(candidateStr); const candidate = JSON.parse(candidateStr);
await this.pc.addIceCandidate(new RTCIceCandidate(candidate)); await this.pc.addIceCandidate(new this.RTCIceCandidate(candidate));
} catch (err) { } catch (err) {
console.warn('Failed to add ICE candidate:', err); console.warn('Failed to add ICE candidate:', err);
} }

View File

@@ -20,6 +20,7 @@ export type {
ConnectionRole, ConnectionRole,
RondevuConnectionParams, RondevuConnectionParams,
RondevuConnectionEvents, RondevuConnectionEvents,
WebRTCPolyfill,
// Signaling types // Signaling types
Side, Side,
Session, Session,

View File

@@ -1,6 +1,6 @@
import { RondevuAPI } from './client.js'; import { RondevuAPI } from './client.js';
import { RondevuConnection } from './connection.js'; import { RondevuConnection } from './connection.js';
import { RondevuOptions, JoinOptions, RondevuConnectionParams } from './types.js'; import { RondevuOptions, JoinOptions, RondevuConnectionParams, WebRTCPolyfill } from './types.js';
/** /**
* Main Rondevu WebRTC client with automatic connection management * Main Rondevu WebRTC client with automatic connection management
@@ -14,6 +14,9 @@ export class Rondevu {
private rtcConfig?: RTCConfiguration; private rtcConfig?: RTCConfiguration;
private pollingInterval: number; private pollingInterval: number;
private connectionTimeout: number; private connectionTimeout: number;
private wrtc?: WebRTCPolyfill;
private RTCPeerConnection: typeof RTCPeerConnection;
private RTCIceCandidate: typeof RTCIceCandidate;
/** /**
* Creates a new Rondevu client instance * Creates a new Rondevu client instance
@@ -22,6 +25,7 @@ export class Rondevu {
constructor(options: RondevuOptions = {}) { constructor(options: RondevuOptions = {}) {
this.baseUrl = options.baseUrl || 'https://rondevu.xtrdev.workers.dev'; this.baseUrl = options.baseUrl || 'https://rondevu.xtrdev.workers.dev';
this.fetchImpl = options.fetch; this.fetchImpl = options.fetch;
this.wrtc = options.wrtc;
this.api = new RondevuAPI({ this.api = new RondevuAPI({
baseUrl: this.baseUrl, baseUrl: this.baseUrl,
@@ -33,6 +37,18 @@ export class Rondevu {
this.rtcConfig = options.rtcConfig; this.rtcConfig = options.rtcConfig;
this.pollingInterval = options.pollingInterval || 1000; this.pollingInterval = options.pollingInterval || 1000;
this.connectionTimeout = options.connectionTimeout || 30000; this.connectionTimeout = options.connectionTimeout || 30000;
// Use injected WebRTC polyfill or fall back to global
this.RTCPeerConnection = options.wrtc?.RTCPeerConnection || globalThis.RTCPeerConnection;
this.RTCIceCandidate = options.wrtc?.RTCIceCandidate || globalThis.RTCIceCandidate;
if (!this.RTCPeerConnection) {
throw new Error(
'RTCPeerConnection not available. ' +
'In Node.js, provide a WebRTC polyfill via the wrtc option. ' +
'Install: npm install @roamhq/wrtc or npm install wrtc'
);
}
} }
/** /**
@@ -57,7 +73,7 @@ export class Rondevu {
*/ */
async create(id: string, topic: string): Promise<RondevuConnection> { async create(id: string, topic: string): Promise<RondevuConnection> {
// Create peer connection // Create peer connection
const pc = new RTCPeerConnection(this.rtcConfig); const pc = new this.RTCPeerConnection(this.rtcConfig);
// Create initial data channel for negotiation (required for offer creation) // Create initial data channel for negotiation (required for offer creation)
pc.createDataChannel('_negotiation'); pc.createDataChannel('_negotiation');
@@ -86,6 +102,7 @@ export class Rondevu {
remotePeerId: '', // Will be populated when answer is received remotePeerId: '', // Will be populated when answer is received
pollingInterval: this.pollingInterval, pollingInterval: this.pollingInterval,
connectionTimeout: this.connectionTimeout, connectionTimeout: this.connectionTimeout,
wrtc: this.wrtc,
}; };
const connection = new RondevuConnection(connectionParams, this.api); const connection = new RondevuConnection(connectionParams, this.api);
@@ -110,7 +127,7 @@ export class Rondevu {
} }
// Create peer connection // Create peer connection
const pc = new RTCPeerConnection(this.rtcConfig); const pc = new this.RTCPeerConnection(this.rtcConfig);
// Set remote offer // Set remote offer
await pc.setRemoteDescription({ await pc.setRemoteDescription({
@@ -142,6 +159,7 @@ export class Rondevu {
remotePeerId: sessionData.peerId, remotePeerId: sessionData.peerId,
pollingInterval: this.pollingInterval, pollingInterval: this.pollingInterval,
connectionTimeout: this.connectionTimeout, connectionTimeout: this.connectionTimeout,
wrtc: this.wrtc,
}; };
const connection = new RondevuConnection(connectionParams, this.api); const connection = new RondevuConnection(connectionParams, this.api);

View File

@@ -177,6 +177,15 @@ export interface RondevuClientOptions {
// WebRTC Types // WebRTC Types
// ============================================================================ // ============================================================================
/**
* WebRTC polyfill for Node.js and other non-browser platforms
*/
export interface WebRTCPolyfill {
RTCPeerConnection: typeof RTCPeerConnection;
RTCSessionDescription: typeof RTCSessionDescription;
RTCIceCandidate: typeof RTCIceCandidate;
}
/** /**
* Configuration options for Rondevu WebRTC client * Configuration options for Rondevu WebRTC client
*/ */
@@ -193,6 +202,8 @@ export interface RondevuOptions {
pollingInterval?: number; pollingInterval?: number;
/** Connection timeout in milliseconds (default: 30000) */ /** Connection timeout in milliseconds (default: 30000) */
connectionTimeout?: number; connectionTimeout?: number;
/** WebRTC polyfill for Node.js (e.g., wrtc or @roamhq/wrtc) */
wrtc?: WebRTCPolyfill;
} }
/** /**
@@ -222,6 +233,7 @@ export interface RondevuConnectionParams {
remotePeerId: string; remotePeerId: string;
pollingInterval: number; pollingInterval: number;
connectionTimeout: number; connectionTimeout: number;
wrtc?: WebRTCPolyfill;
} }
/** /**