// Recipient flow: Join room; receive 'offer' event -> createPeerConnection + createDataChannel -> send answer import BaseWebRTC, { WebRTCConfig } from "./webrtc_base"; interface AnswerPayload { answer: RTCSessionDescriptionInit; peerId: string; } export default class WebRTC_Recipient extends BaseWebRTC { constructor(config: WebRTCConfig) { super(config); this.setupRecipientSocketListeners(); } private setupRecipientSocketListeners(): void { this.socket.on("offer", ({ peerId, offer, from }) => { this.handleOffer({ peerId, offer, from }); }); this.socket.on("answer", ({ answer, peerId }) => { this.handleAnswer({ answer, peerId }); }); // Add listener for initiator re-online this.socket.on("initiator-online", ({ roomId }) => { this.log("log", `Received initiator-online for room: ${roomId}`); // Send a ready response this.log( "log", `Sending recipient-ready, my peerId: ${this.socket.id}`, this.peerId ); // Send a ready response this.socket.emit("recipient-ready", { roomId: this.roomId, peerId: this.socket.id, }); }); } // Recipient creates a connection upon receiving an offer private async handleOffer({ peerId, offer, from, }: { offer: RTCSessionDescriptionInit; peerId: string; from: string; }): Promise { this.log("log", `Handling offer from peer ${from}`); try { // 1. Clean up existing connections await this.cleanupExistingConnection(from); // 2. Create a new connection const peerConnection = await this.createPeerConnection(from); // Then create a data channel await this.createDataChannel(from); // 4. Set the remote description // console.log(`Setting remote description for peer ${from}`); await peerConnection.setRemoteDescription( new RTCSessionDescription(offer) ); // Create and set the local description (answer) // console.log(`Creating answer for peer ${from}`); const answer = await peerConnection.createAnswer(); await peerConnection.setLocalDescription(answer); // Send the answer this.log("log", `Sending answer to peer ${from}`); this.socket.emit("answer", { answer, peerId: from, from: this.socket.id, }); // Finally, process the cached ICE candidates await this.addQueuedIceCandidates(from); } catch (error) { this.fireError("Error handling offer", { error, from }); // Clean up the failed connection await this.cleanupExistingConnection(from); } } private async handleAnswer({ answer, peerId }: AnswerPayload): Promise { const peerConnection = this.peerConnections.get(peerId); if (!peerConnection) { this.fireError("No peer connection for handleAnswer", { peerId }); return; } try { await peerConnection.setRemoteDescription( new RTCSessionDescription(answer) ); } catch (error) { this.fireError("Error handling answer", { error, peerId }); } } protected async createDataChannel(peerId: string): Promise { const peerConnection = this.peerConnections.get(peerId); if (!peerConnection) { this.fireError(`No peer connection found for peer ${peerId}`, { peerId }); return; } peerConnection.ondatachannel = (event) => { this.setupDataChannel(event.channel, peerId); this.dataChannels.set(peerId, event.channel); }; } }