112 lines
3.5 KiB
TypeScript
112 lines
3.5 KiB
TypeScript
// 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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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);
|
|
};
|
|
}
|
|
}
|