UNPKG

rtc-link

Version:

A library for managing WebRTC connections with multiplexed streams, anti-glare handling, and streamlined data channel setup.

113 lines (101 loc) 4.28 kB
import {asPlex} from "rxprotoplex"; import {createStreamSwitcher} from "stream-switcher"; import {fromEvent, Subject} from "rxjs"; import {closeRTCListeners} from "./closeRTCListeners.js"; import {iceCandidateHandling} from "./iceCandidateHandling.js"; import {makeOffer} from "./makeOffer.js"; import {listenForOffer} from "./listenForOffer.js"; import {duplexFromRtcDataChannel} from "./duplexFromRtcDataChannel.js"; /** * Establishes a WebRTC data channel stream over a given transport stream, handling peer connection * creation, offer/answer exchange, and ICE candidate management with anti-glare protection. * * @param {boolean} isInitiator - Indicates if the current peer is the initiator of the WebRTC connection. * @param {Stream} stream - The transport stream to establish WebRTC signaling over. * @param {Object} [config={}] - Optional configuration object for the WebRTC connection. * @param {Array<Object>} [config.iceServers=[{ urls: 'stun:stun.l.google.com:19302' }]] - ICE servers used by WebRTC for NAT traversal. * * @returns {Object} - A data channel that acts as a stream switcher, enabling duplex communication * over the WebRTC data channel. * * @description * The `establishWrtcStream` function sets up a WebRTC peer-to-peer connection using the provided transport * stream for signaling. It configures the connection as an initiator or responder based on the `isInitiator` * parameter and establishes a data channel for bidirectional communication. The connection listens for * WebRTC offers, answers, and ICE candidates, allowing peers to establish and maintain a connection * even behind NATs. * * - **Anti-Glare Mechanism**: * - Prevents two initiators from conflicting when both peers attempt to initiate a connection. * - Ensures only one peer continues with the connection setup if both try to send an offer simultaneously. * * - **Event Handling**: * - Automatically regenerates the peer connection and data channel on errors or disconnects. * - Listens for incoming offers and ICE candidates from the signaling channel. * * - **Error Handling**: Emits any errors on the `dataChannel`. * * @example * const isInitiator = true; * const config = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] }; * const wrtcStream = establishWrtcStream(isInitiator, myStream, config); * * @function * @private */ const establishWrtcStream = (isInitiator, stream, config = {}) => { config.iceServers ||= [{urls: 'stun:stun.l.google.com:19302'}]; const plex = asPlex(stream); const dataChannel = createStreamSwitcher(); let close$ = new Subject(); let rtc; let closed = false; makeNewPeer(isInitiator); dataChannel.once("close", () => { if (rtc) { closeRTCListeners(rtc); rtc.close(); rtc = undefined; closed = true; } }); return dataChannel; function onError(e) { dataChannel.emit("error", e); } function makeNewPeer(isInitiator) { if (closed) { throw new Error("Cannot make new peer because stream is closed."); } if (rtc) { rtc.close(); closeRTCListeners(rtc); } close$.next?.(); close$.complete?.(); close$ = new Subject(); fromEvent(stream, "close").subscribe(o => close$.next(o)); rtc = new RTCPeerConnection(config); rtc.makingOffer = false; rtc.localFingerprint = null; // Store fingerprint on rtc createDataChannel(rtc, isInitiator); iceCandidateHandling(plex, rtc, close$); // Initiator tries to make an offer but also listens for incoming offers if (isInitiator) { makeOffer(plex, rtc, close$).catch(onError); } // Always listen for incoming offers, even if we start by making an offer listenForOffer(plex, rtc, close$, makeNewPeer).catch(onError); } function createDataChannel(rtc, isInitiator) { const stream = duplexFromRtcDataChannel( isInitiator, rtc.createDataChannel("wire", { negotiated: true, id: 0 }) ); dataChannel.switch(stream); } } export {establishWrtcStream};