Implementing a useWebRTC Hook for Simplified Peer-to-Peer Communication in React
WebRTC (Web Real-Time Communication) enables direct audio/video communication between browsers without requiring an intermediary server. This challenge asks you to create a reusable useWebRTC hook in React that simplifies the complexities of WebRTC setup, including obtaining media streams, establishing peer connections, and handling signaling. This hook will abstract away much of the boilerplate code, allowing developers to focus on the application logic of their WebRTC-powered features.
Problem Description
You are tasked with creating a useWebRTC hook in React that manages the lifecycle of a WebRTC peer connection. The hook should handle:
- Media Stream Acquisition: Requesting and managing the user's audio and/or video stream. The user should be able to specify whether to request audio, video, or both.
- Peer Connection Management: Creating and managing a
RTCPeerConnectionobject. - Signaling Channel: Providing functions for sending and receiving SDP (Session Description Protocol) offers and answers, as well as ICE (Interactive Connectivity Establishment) candidates. These functions will be used to communicate with a remote peer.
- Connection State Tracking: Tracking the state of the peer connection (e.g.,
connecting,connected,disconnected) and providing this information to the component using the hook. - Cleanup: Closing the peer connection and releasing the media stream when the component unmounts.
Key Requirements:
- The hook must be written in TypeScript.
- The hook should accept an optional
signalHandlerfunction as an argument. This function will be called with thesendSignalfunction, allowing the component to integrate with a signaling server. - The hook should return an object containing:
stream: The local media stream (ornullif not available).peerConnection: TheRTCPeerConnectionobject (ornullif not initialized).sendSignal: A function to send signaling messages (SDP offers/answers, ICE candidates) to the remote peer.connectionState: A string representing the current state of the peer connection (e.g., "connecting", "connected", "disconnected").error: An error object if an error occurred during initialization or connection.createOffer: A function to create an SDP offer.createAnswer: A function to create an SDP answer.addIceCandidate: A function to add an ICE candidate to the peer connection.closeConnection: A function to close the peer connection and release the stream.
Expected Behavior:
- When the hook is first mounted, it should request the user's media stream.
- If the media stream is successfully obtained, the
streamproperty should be updated. - The
connectionStateproperty should reflect the current state of the peer connection. - The
sendSignalfunction should be used to send signaling messages to the remote peer. - When the component is unmounted, the hook should close the peer connection and release the media stream.
- Error handling should be robust, and any errors should be reported through the
errorproperty.
Edge Cases to Consider:
- User denies media access.
- Browser does not support WebRTC.
- Network connectivity issues.
- Signaling server errors.
- Peer connection errors.
Examples
Example 1:
Input: `useWebRTC({ audio: true, video: false, signalHandler: (sendSignal) => { /* Implement signaling logic here */ } })`
Output: `{ stream: MediaStream, peerConnection: RTCPeerConnection, sendSignal: (sdp: RTCSessionDescription | RTCIceCandidate) => void, connectionState: 'connecting' | 'connected' | 'disconnected', error: null, createOffer: () => Promise<void>, createAnswer: () => Promise<void>, addIceCandidate: (candidate: RTCIceCandidate) => void, closeConnection: () => void }`
Explanation: The hook requests only audio, initializes a peer connection, and provides functions for signaling and connection management. The initial connection state is 'connecting'.
Example 2:
Input: `useWebRTC({ audio: true, video: true, signalHandler: null })`
Output: `{ stream: MediaStream, peerConnection: RTCPeerConnection, sendSignal: undefined, connectionState: 'connecting' | 'connected' | 'disconnected', error: null, createOffer: () => Promise<void>, createAnswer: () => Promise<void>, addIceCandidate: (candidate: RTCIceCandidate) => void, closeConnection: () => void }`
Explanation: The hook requests both audio and video, initializes a peer connection, and provides functions for signaling and connection management. The `signalHandler` is null, so `sendSignal` is undefined.
Example 3: (Edge Case)
Input: `useWebRTC({ audio: true, video: true, signalHandler: (sendSignal) => { /* Implement signaling logic here */ } })` (User denies media access)
Output: `{ stream: null, peerConnection: null, sendSignal: (sdp: RTCSessionDescription | RTCIceCandidate) => void, connectionState: 'disconnected', error: Error('User denied media access'), createOffer: () => Promise<void>, createAnswer: () => Promise<void>, addIceCandidate: (candidate: RTCIceCandidate) => void, closeConnection: () => void }`
Explanation: The user denies media access. The hook initializes but fails to obtain a stream, resulting in `stream: null`, `peerConnection: null`, and an error indicating the user denied media access.
Constraints
- The hook must be compatible with modern browsers that support WebRTC.
- The hook should handle errors gracefully and provide informative error messages.
- The hook should be reasonably performant and avoid unnecessary computations.
- The
sendSignalfunction should accept either anRTCSessionDescription(for SDP offers/answers) or anRTCIceCandidate. - The
createOffer,createAnswer, andaddIceCandidatefunctions should be asynchronous and return Promises.
Notes
- Consider using
async/awaitfor asynchronous operations. - Pay close attention to error handling and ensure that errors are properly reported.
- The signaling logic is not part of this challenge; the hook only provides the functions for sending and receiving signals. The component using the hook is responsible for implementing the signaling logic.
- You can use a library like
react-webcamto simplify media stream acquisition, but it's not required. The core requirement is to demonstrate understanding of WebRTC concepts and how to integrate them into a React hook. - Focus on creating a clean, reusable, and well-documented hook.