4 Commits

Author SHA1 Message Date
83831cae77 v0.18.0 - Add WebRTC polyfill support for Node.js 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 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
3 changed files with 32 additions and 6 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@xtr-dev/rondevu-client", "name": "@xtr-dev/rondevu-client",
"version": "0.17.0", "version": "0.18.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@xtr-dev/rondevu-client", "name": "@xtr-dev/rondevu-client",
"version": "0.17.0", "version": "0.18.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@noble/ed25519": "^3.0.0", "@noble/ed25519": "^3.0.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@xtr-dev/rondevu-client", "name": "@xtr-dev/rondevu-client",
"version": "0.17.0", "version": "0.18.0",
"description": "TypeScript client for Rondevu with durable WebRTC connections, automatic reconnection, and message queuing", "description": "TypeScript client for Rondevu with durable WebRTC connections, automatic reconnection, and message queuing",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",

View File

@@ -57,6 +57,9 @@ export interface RondevuOptions {
batching?: BatcherOptions | false // Optional, defaults to enabled with default options batching?: BatcherOptions | false // Optional, defaults to enabled with default options
iceServers?: IceServerPreset | RTCIceServer[] // Optional: preset name or custom STUN/TURN servers iceServers?: IceServerPreset | RTCIceServer[] // Optional: preset name or custom STUN/TURN servers
debug?: boolean // Optional: enable debug logging (default: false) debug?: boolean // Optional: enable debug logging (default: false)
// WebRTC polyfills for Node.js environments (e.g., wrtc)
rtcPeerConnection?: typeof RTCPeerConnection
rtcIceCandidate?: typeof RTCIceCandidate
} }
export interface OfferContext { export interface OfferContext {
@@ -167,6 +170,8 @@ export class Rondevu {
private batchingOptions?: BatcherOptions | false private batchingOptions?: BatcherOptions | false
private iceServers: RTCIceServer[] private iceServers: RTCIceServer[]
private debugEnabled: boolean private debugEnabled: boolean
private rtcPeerConnection?: typeof RTCPeerConnection
private rtcIceCandidate?: typeof RTCIceCandidate
// Service management // Service management
private currentService: string | null = null private currentService: string | null = null
@@ -188,7 +193,9 @@ export class Rondevu {
iceServers: RTCIceServer[], iceServers: RTCIceServer[],
cryptoAdapter?: CryptoAdapter, cryptoAdapter?: CryptoAdapter,
batchingOptions?: BatcherOptions | false, batchingOptions?: BatcherOptions | false,
debugEnabled = false debugEnabled = false,
rtcPeerConnection?: typeof RTCPeerConnection,
rtcIceCandidate?: typeof RTCIceCandidate
) { ) {
this.apiUrl = apiUrl this.apiUrl = apiUrl
this.username = username this.username = username
@@ -198,6 +205,8 @@ export class Rondevu {
this.cryptoAdapter = cryptoAdapter this.cryptoAdapter = cryptoAdapter
this.batchingOptions = batchingOptions this.batchingOptions = batchingOptions
this.debugEnabled = debugEnabled this.debugEnabled = debugEnabled
this.rtcPeerConnection = rtcPeerConnection
this.rtcIceCandidate = rtcIceCandidate
this.debug('Instance created:', { this.debug('Instance created:', {
username: this.username, username: this.username,
@@ -230,6 +239,14 @@ export class Rondevu {
static async connect(options: RondevuOptions): Promise<Rondevu> { static async connect(options: RondevuOptions): Promise<Rondevu> {
const username = options.username || Rondevu.generateAnonymousUsername() const username = options.username || Rondevu.generateAnonymousUsername()
// Apply WebRTC polyfills to global scope if provided (Node.js environments)
if (options.rtcPeerConnection) {
globalThis.RTCPeerConnection = options.rtcPeerConnection as any
}
if (options.rtcIceCandidate) {
globalThis.RTCIceCandidate = options.rtcIceCandidate as any
}
// Handle preset string or custom array // Handle preset string or custom array
let iceServers: RTCIceServer[] let iceServers: RTCIceServer[]
if (typeof options.iceServers === 'string') { if (typeof options.iceServers === 'string') {
@@ -277,7 +294,9 @@ export class Rondevu {
iceServers, iceServers,
options.cryptoAdapter, options.cryptoAdapter,
options.batching, options.batching,
options.debug || false options.debug || false,
options.rtcPeerConnection,
options.rtcIceCandidate
) )
} }
@@ -365,10 +384,17 @@ export class Rondevu {
pc.onicecandidate = async (event) => { pc.onicecandidate = async (event) => {
if (event.candidate) { if (event.candidate) {
try { try {
// Handle both browser and Node.js (wrtc) environments
// Browser: candidate.toJSON() exists
// Node.js wrtc: candidate is already a plain object
const candidateData = typeof event.candidate.toJSON === 'function'
? event.candidate.toJSON()
: event.candidate
await this.api.addOfferIceCandidates( await this.api.addOfferIceCandidates(
serviceFqn, serviceFqn,
offerId, offerId,
[event.candidate.toJSON()] [candidateData]
) )
} catch (err) { } catch (err) {
console.error('[Rondevu] Failed to send ICE candidate:', err) console.error('[Rondevu] Failed to send ICE candidate:', err)