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,10 +24,12 @@ npm install @xtr-dev/rondevu-client
### Usage
#### Browser
```typescript
import { Rondevu } from '@xtr-dev/rondevu-client';
const rdv = new Rondevu({
const rdv = new Rondevu({
baseUrl: 'https://server.com',
rtcConfig: {
iceServers: [
@@ -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
**Main Methods:**

View File

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

View File

@@ -1,6 +1,6 @@
import { EventEmitter } from './event-emitter.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
@@ -21,6 +21,8 @@ export class RondevuConnection extends EventEmitter {
private connectionTimer?: ReturnType<typeof setTimeout>;
private isPolling: boolean = false;
private isClosed: boolean = false;
private wrtc?: WebRTCPolyfill;
private RTCIceCandidate: typeof RTCIceCandidate;
constructor(params: RondevuConnectionParams, client: RondevuAPI) {
super();
@@ -34,6 +36,10 @@ export class RondevuConnection extends EventEmitter {
this.dataChannels = new Map();
this.pollingIntervalMs = params.pollingInterval;
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.startConnectionTimeout();
@@ -187,7 +193,7 @@ export class RondevuConnection extends EventEmitter {
for (const candidateStr of offererResponse.answerCandidates) {
try {
const candidate = JSON.parse(candidateStr);
await this.pc.addIceCandidate(new RTCIceCandidate(candidate));
await this.pc.addIceCandidate(new this.RTCIceCandidate(candidate));
} catch (err) {
console.warn('Failed to add ICE candidate:', err);
}
@@ -202,7 +208,7 @@ export class RondevuConnection extends EventEmitter {
for (const candidateStr of answererResponse.offerCandidates) {
try {
const candidate = JSON.parse(candidateStr);
await this.pc.addIceCandidate(new RTCIceCandidate(candidate));
await this.pc.addIceCandidate(new this.RTCIceCandidate(candidate));
} catch (err) {
console.warn('Failed to add ICE candidate:', err);
}

View File

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

View File

@@ -1,6 +1,6 @@
import { RondevuAPI } from './client.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
@@ -14,6 +14,9 @@ export class Rondevu {
private rtcConfig?: RTCConfiguration;
private pollingInterval: number;
private connectionTimeout: number;
private wrtc?: WebRTCPolyfill;
private RTCPeerConnection: typeof RTCPeerConnection;
private RTCIceCandidate: typeof RTCIceCandidate;
/**
* Creates a new Rondevu client instance
@@ -22,6 +25,7 @@ export class Rondevu {
constructor(options: RondevuOptions = {}) {
this.baseUrl = options.baseUrl || 'https://rondevu.xtrdev.workers.dev';
this.fetchImpl = options.fetch;
this.wrtc = options.wrtc;
this.api = new RondevuAPI({
baseUrl: this.baseUrl,
@@ -33,6 +37,18 @@ export class Rondevu {
this.rtcConfig = options.rtcConfig;
this.pollingInterval = options.pollingInterval || 1000;
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> {
// 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)
pc.createDataChannel('_negotiation');
@@ -86,6 +102,7 @@ export class Rondevu {
remotePeerId: '', // Will be populated when answer is received
pollingInterval: this.pollingInterval,
connectionTimeout: this.connectionTimeout,
wrtc: this.wrtc,
};
const connection = new RondevuConnection(connectionParams, this.api);
@@ -110,7 +127,7 @@ export class Rondevu {
}
// Create peer connection
const pc = new RTCPeerConnection(this.rtcConfig);
const pc = new this.RTCPeerConnection(this.rtcConfig);
// Set remote offer
await pc.setRemoteDescription({
@@ -142,6 +159,7 @@ export class Rondevu {
remotePeerId: sessionData.peerId,
pollingInterval: this.pollingInterval,
connectionTimeout: this.connectionTimeout,
wrtc: this.wrtc,
};
const connection = new RondevuConnection(connectionParams, this.api);

View File

@@ -177,6 +177,15 @@ export interface RondevuClientOptions {
// 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
*/
@@ -193,6 +202,8 @@ export interface RondevuOptions {
pollingInterval?: number;
/** Connection timeout in milliseconds (default: 30000) */
connectionTimeout?: number;
/** WebRTC polyfill for Node.js (e.g., wrtc or @roamhq/wrtc) */
wrtc?: WebRTCPolyfill;
}
/**
@@ -222,6 +233,7 @@ export interface RondevuConnectionParams {
remotePeerId: string;
pollingInterval: number;
connectionTimeout: number;
wrtc?: WebRTCPolyfill;
}
/**