diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9a88bbb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,39 @@ +{ + "name": "@xtr-dev/rondevu-client", + "version": "0.5.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@xtr-dev/rondevu-client", + "version": "0.5.1", + "license": "MIT", + "dependencies": { + "@xtr-dev/rondevu-client": "^0.5.1" + }, + "devDependencies": { + "typescript": "^5.9.3" + } + }, + "node_modules/@xtr-dev/rondevu-client": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@xtr-dev/rondevu-client/-/rondevu-client-0.5.1.tgz", + "integrity": "sha512-110ejMCizPUPkHwwwNvcdCSZceLaHeFbf1LNkXvbG6pnLBqCf2uoGOOaRkArb7HNNFABFB+HXzm/AVzNdadosw==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/package.json b/package.json index 628c4eb..1395cd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xtr-dev/rondevu-client", - "version": "0.5.1", + "version": "0.6.0", "description": "TypeScript client for Rondevu topic-based peer discovery and signaling server", "type": "module", "main": "dist/index.js", @@ -25,5 +25,8 @@ "files": [ "dist", "README.md" - ] + ], + "dependencies": { + "@xtr-dev/rondevu-client": "^0.5.1" + } } diff --git a/src/index.ts b/src/index.ts index 70392b8..b1918a6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,9 +24,9 @@ export type { export { BloomFilter } from './bloom.js'; // Export peer manager -export { default as RondevuPeer } from './peer.js'; +export { default as RondevuPeer } from './peer/index.js'; export type { PeerOptions, PeerEvents, PeerTimeouts -} from './peer.js'; +} from './peer/index.js'; diff --git a/src/peer.ts b/src/peer/index.ts similarity index 77% rename from src/peer.ts rename to src/peer/index.ts index 714a711..205e128 100644 --- a/src/peer.ts +++ b/src/peer/index.ts @@ -1,5 +1,5 @@ -import { RondevuOffers } from './offers.js'; -import { EventEmitter } from './event-emitter.js'; +import { RondevuOffers } from '../offers.js'; +import { EventEmitter } from '../event-emitter.js'; /** * Timeout configurations for different connection phases @@ -94,18 +94,15 @@ class IdleState extends PeerState { } async answer(offerId: string, offerSdp: string, options: PeerOptions): Promise { - this.peer.setState(new AnsweringState(this.peer, offerId, offerSdp, options)); + this.peer.setState(new AnsweringState(this.peer)); return this.peer.state.answer(offerId, offerSdp, options); } } /** - * Creating offer and gathering ICE candidates + * Creating offer and sending to server */ class CreatingOfferState extends PeerState { - private timeout?: ReturnType; - private pendingCandidates: any[] = []; - constructor(peer: RondevuPeer, private options: PeerOptions) { super(peer); } @@ -124,25 +121,11 @@ class CreatingOfferState extends PeerState { this.peer.emitEvent('datachannel', channel); } - // Set up ICE candidate buffering - this.peer.pc.onicecandidate = (event) => { - if (event.candidate) { - const candidateData = event.candidate.toJSON(); - if (candidateData.candidate && candidateData.candidate !== '') { - this.pendingCandidates.push(candidateData); - } - } - }; - // Create WebRTC offer const offer = await this.peer.pc.createOffer(); await this.peer.pc.setLocalDescription(offer); - // Wait for ICE gathering to complete (or timeout) - const iceTimeout = options.timeouts?.iceGathering || 10000; - await this.waitForIceGathering(iceTimeout); - - // Create offer on Rondevu server (server generates hash-based ID) + // Send offer to server immediately (don't wait for ICE) const offers = await this.peer.offersApi.create([{ sdp: offer.sdp!, topics: options.topics, @@ -152,13 +135,7 @@ class CreatingOfferState extends PeerState { const offerId = offers[0].id; this.peer.offerId = offerId; - // Send buffered ICE candidates - if (this.pendingCandidates.length > 0) { - await this.peer.offersApi.addIceCandidates(offerId, this.pendingCandidates); - this.pendingCandidates = []; - } - - // Enable trickle ICE for future candidates + // Enable trickle ICE - send candidates as they arrive this.peer.pc.onicecandidate = async (event) => { if (event.candidate && offerId) { const candidateData = event.candidate.toJSON(); @@ -181,30 +158,6 @@ class CreatingOfferState extends PeerState { throw error; } } - - private async waitForIceGathering(timeout: number): Promise { - return new Promise((resolve, reject) => { - const checkState = () => { - if (this.peer.pc.iceGatheringState === 'complete') { - if (this.timeout) clearTimeout(this.timeout); - resolve(); - } - }; - - this.peer.pc.onicegatheringstatechange = checkState; - checkState(); // Check immediately in case already complete - - this.timeout = setTimeout(() => { - // Timeout is not fatal - we proceed with candidates we have - console.warn('ICE gathering timeout - proceeding with gathered candidates'); - resolve(); - }, timeout); - }); - } - - cleanup(): void { - if (this.timeout) clearTimeout(this.timeout); - } } /** @@ -279,18 +232,10 @@ class WaitingForAnswerState extends PeerState { } /** - * Answering an offer from another peer + * Answering an offer and sending to server */ class AnsweringState extends PeerState { - private timeout?: ReturnType; - private pendingCandidates: any[] = []; - - constructor( - peer: RondevuPeer, - private offerId: string, - private offerSdp: string, - private options: PeerOptions - ) { + constructor(peer: RondevuPeer) { super(peer); } @@ -301,25 +246,6 @@ class AnsweringState extends PeerState { this.peer.role = 'answerer'; this.peer.offerId = offerId; - const answerTimeout = options.timeouts?.creatingAnswer || 10000; - - this.timeout = setTimeout(() => { - this.peer.setState(new FailedState( - this.peer, - new Error('Timeout creating answer') - )); - }, answerTimeout); - - // Buffer ICE candidates during answer creation - this.peer.pc.onicecandidate = (event) => { - if (event.candidate) { - const candidateData = event.candidate.toJSON(); - if (candidateData.candidate && candidateData.candidate !== '') { - this.pendingCandidates.push(candidateData); - } - } - }; - // Set remote description await this.peer.pc.setRemoteDescription({ type: 'offer', @@ -330,26 +256,10 @@ class AnsweringState extends PeerState { const answer = await this.peer.pc.createAnswer(); await this.peer.pc.setLocalDescription(answer); - // Clear the answer creation timeout - ICE gathering has its own timeout - if (this.timeout) { - clearTimeout(this.timeout); - this.timeout = undefined; - } - - // Wait for ICE gathering - const iceTimeout = options.timeouts?.iceGathering || 10000; - await this.waitForIceGathering(iceTimeout); - - // Send answer to server FIRST + // Send answer to server immediately (don't wait for ICE) await this.peer.offersApi.answer(offerId, answer.sdp!); - // Send buffered ICE candidates - if (this.pendingCandidates.length > 0) { - await this.peer.offersApi.addIceCandidates(offerId, this.pendingCandidates); - this.pendingCandidates = []; - } - - // Enable trickle ICE + // Enable trickle ICE - send candidates as they arrive this.peer.pc.onicecandidate = async (event) => { if (event.candidate && offerId) { const candidateData = event.candidate.toJSON(); @@ -370,28 +280,6 @@ class AnsweringState extends PeerState { throw error; } } - - private async waitForIceGathering(timeout: number): Promise { - return new Promise((resolve) => { - const checkState = () => { - if (this.peer.pc.iceGatheringState === 'complete') { - resolve(); - } - }; - - this.peer.pc.onicegatheringstatechange = checkState; - checkState(); - - setTimeout(() => { - console.warn('ICE gathering timeout - proceeding with gathered candidates'); - resolve(); - }, timeout); - }); - } - - cleanup(): void { - if (this.timeout) clearTimeout(this.timeout); - } } /** diff --git a/src/rondevu.ts b/src/rondevu.ts index f9960ac..c8ed92a 100644 --- a/src/rondevu.ts +++ b/src/rondevu.ts @@ -1,6 +1,6 @@ import { RondevuAuth, Credentials, FetchFunction } from './auth.js'; import { RondevuOffers } from './offers.js'; -import RondevuPeer from './peer.js'; +import RondevuPeer from './peer/index.js'; export interface RondevuOptions { /**