From 4a6d0ee091cbd3deadcb98577bc4ffd7bbbe03ef Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Fri, 5 Dec 2025 19:51:09 +0100 Subject: [PATCH] Fix WebRTC state machine error in pooled services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When handling answered offers in pooled services, we were creating fresh peer connections in "stable" state and trying to set the remote answer, which caused "Cannot set remote answer in state stable" error. Fixed by: - Adding offerSdp to AnsweredOffer interface - Passing original offer SDP through the offer pool - Setting local description (offer) before remote description (answer) This ensures the peer connection is in "have-local-offer" state before applying the answer, satisfying WebRTC's state machine requirements. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/offer-pool.ts | 12 ++++++++++-- src/service-pool.ts | 8 +++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/offer-pool.ts b/src/offer-pool.ts index 3e5c1c1..da9f5e7 100644 --- a/src/offer-pool.ts +++ b/src/offer-pool.ts @@ -6,7 +6,8 @@ import { RondevuOffers, Offer } from './offers.js'; export interface AnsweredOffer { offerId: string; answererId: string; - sdp: string; + sdp: string; // Answer SDP + offerSdp: string; // Original offer SDP answeredAt: number; } @@ -110,11 +111,18 @@ export class OfferPool { // Process each answer for (const answer of myAnswers) { - // Notify ServicePool + // Get the original offer + const offer = this.offers.get(answer.offerId); + if (!offer) { + continue; // Offer already consumed, skip + } + + // Notify ServicePool with both answer and original offer SDP await this.options.onAnswered({ offerId: answer.offerId, answererId: answer.answererId, sdp: answer.sdp, + offerSdp: offer.sdp, answeredAt: answer.answeredAt }); diff --git a/src/service-pool.ts b/src/service-pool.ts index 7c3c136..d2e9ed6 100644 --- a/src/service-pool.ts +++ b/src/service-pool.ts @@ -241,7 +241,13 @@ export class ServicePool { peer.role = 'offerer'; peer.offerId = answer.offerId; - // Set remote description (the answer) + // Set local description (the original offer) first + await peer.pc.setLocalDescription({ + type: 'offer', + sdp: answer.offerSdp + }); + + // Now set remote description (the answer) await peer.pc.setRemoteDescription({ type: 'answer', sdp: answer.sdp