diff --git a/README.md b/README.md index 62945fa..0a5fb24 100644 --- a/README.md +++ b/README.md @@ -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:** diff --git a/package.json b/package.json index ab96c9b..9354cc5 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/connection.ts b/src/connection.ts index dd762b5..249430a 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -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; 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); } diff --git a/src/index.ts b/src/index.ts index ec61b94..8959432 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,7 @@ export type { ConnectionRole, RondevuConnectionParams, RondevuConnectionEvents, + WebRTCPolyfill, // Signaling types Side, Session, diff --git a/src/rondevu.ts b/src/rondevu.ts index ff437f3..b9ca6b9 100644 --- a/src/rondevu.ts +++ b/src/rondevu.ts @@ -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 { // 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); diff --git a/src/types.ts b/src/types.ts index b94659c..7c4498f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -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; } /**