UNPKG

pigeonrtc

Version:

Pluggable cross-browser compatible WebRTC library for PeerPigeon

1,011 lines (1,001 loc) 31.1 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.js var index_exports = {}; __export(index_exports, { BrowserRTCAdapter: () => BrowserRTCAdapter, MDNSResolver: () => MDNSResolver, NodeRTCAdapter: () => NodeRTCAdapter, PeerConnection: () => PeerConnection, PigeonRTC: () => PigeonRTC, RTCAdapter: () => RTCAdapter, SignalingClient: () => SignalingClient, createPigeonRTC: () => createPigeonRTC, default: () => index_default }); module.exports = __toCommonJS(index_exports); // src/RTCAdapter.js var RTCAdapter = class { /** * Get the RTCPeerConnection class for this adapter * @returns {typeof RTCPeerConnection} The RTCPeerConnection class */ getRTCPeerConnection() { throw new Error("getRTCPeerConnection must be implemented by adapter"); } /** * Get the RTCSessionDescription class for this adapter * @returns {typeof RTCSessionDescription} The RTCSessionDescription class */ getRTCSessionDescription() { throw new Error("getRTCSessionDescription must be implemented by adapter"); } /** * Get the RTCIceCandidate class for this adapter * @returns {typeof RTCIceCandidate} The RTCIceCandidate class */ getRTCIceCandidate() { throw new Error("getRTCIceCandidate must be implemented by adapter"); } /** * Get the MediaStream class for this adapter (if supported) * @returns {typeof MediaStream|null} The MediaStream class or null if not supported */ getMediaStream() { return null; } /** * Check if this adapter supports the current environment * @returns {boolean} True if the adapter can work in the current environment */ isSupported() { throw new Error("isSupported must be implemented by adapter"); } /** * Get the name of this adapter * @returns {string} The adapter name */ getName() { throw new Error("getName must be implemented by adapter"); } /** * Initialize the adapter (for any setup that needs to happen) * @returns {Promise<void>} */ async initialize() { } /** * Get user media (if supported) * @param {MediaStreamConstraints} _constraints * @returns {Promise<MediaStream>} */ async getUserMedia(_constraints) { throw new Error("getUserMedia not supported by this adapter"); } /** * Get display media (if supported) * @param {MediaStreamConstraints} _constraints * @returns {Promise<MediaStream>} */ async getDisplayMedia(_constraints) { throw new Error("getDisplayMedia not supported by this adapter"); } }; // src/BrowserRTCAdapter.js var BrowserRTCAdapter = class extends RTCAdapter { constructor() { super(); this._checkSupport(); } _checkSupport() { if (typeof window === "undefined" || typeof navigator === "undefined") { return; } this.hasRTCPeerConnection = !!(window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection); this.hasGetUserMedia = !!(navigator.mediaDevices?.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia); this.hasGetDisplayMedia = !!navigator.mediaDevices?.getDisplayMedia; } getRTCPeerConnection() { if (typeof window === "undefined") { throw new Error("BrowserRTCAdapter requires a browser environment"); } return window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection; } getRTCSessionDescription() { if (typeof window === "undefined") { throw new Error("BrowserRTCAdapter requires a browser environment"); } return window.RTCSessionDescription || window.mozRTCSessionDescription; } getRTCIceCandidate() { if (typeof window === "undefined") { throw new Error("BrowserRTCAdapter requires a browser environment"); } return window.RTCIceCandidate || window.mozRTCIceCandidate; } getMediaStream() { if (typeof window === "undefined") { return null; } return window.MediaStream || window.webkitMediaStream; } isSupported() { return typeof window !== "undefined" && this.hasRTCPeerConnection; } getName() { return "BrowserRTCAdapter"; } async getUserMedia(constraints) { if (typeof navigator === "undefined") { throw new Error("getUserMedia requires a browser environment"); } if (navigator.mediaDevices?.getUserMedia) { return await navigator.mediaDevices.getUserMedia(constraints); } const getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; if (!getUserMedia) { throw new Error("getUserMedia is not supported in this browser"); } return new Promise((resolve, reject) => { getUserMedia.call(navigator, constraints, resolve, reject); }); } async getDisplayMedia(constraints) { if (typeof navigator === "undefined") { throw new Error("getDisplayMedia requires a browser environment"); } if (!navigator.mediaDevices?.getDisplayMedia) { throw new Error("getDisplayMedia is not supported in this browser"); } return await navigator.mediaDevices.getDisplayMedia(constraints); } }; // src/NodeRTCAdapter.js var NodeRTCAdapter = class extends RTCAdapter { constructor() { super(); this._wrtc = null; this._initialized = false; } async initialize() { if (this._initialized) { return; } try { const wrtcModule = await import("@koush/wrtc"); this._wrtc = wrtcModule.default || wrtcModule; this._initialized = true; } catch (error) { throw new Error( "NodeRTCAdapter requires @koush/wrtc to be installed. Install it with: npm install @koush/wrtc" ); } } _ensureInitialized() { if (!this._initialized || !this._wrtc) { throw new Error( "NodeRTCAdapter not initialized. Call initialize() first." ); } } getRTCPeerConnection() { this._ensureInitialized(); return this._wrtc.RTCPeerConnection; } getRTCSessionDescription() { this._ensureInitialized(); return this._wrtc.RTCSessionDescription; } getRTCIceCandidate() { this._ensureInitialized(); return this._wrtc.RTCIceCandidate; } getMediaStream() { this._ensureInitialized(); return this._wrtc.MediaStream || null; } isSupported() { return typeof process !== "undefined" && process.versions != null && process.versions.node != null && typeof window === "undefined"; } getName() { return "NodeRTCAdapter"; } async getUserMedia(_constraints) { throw new Error("getUserMedia is not supported in Node.js environment"); } async getDisplayMedia(_constraints) { throw new Error("getDisplayMedia is not supported in Node.js environment"); } }; // src/SignalingClient.js var SignalingClient = class extends EventTarget { constructor(serverUrl) { super(); this.serverUrl = serverUrl; this.ws = null; this.clientId = null; this.connected = false; this.reconnectAttempts = 0; this.maxReconnectAttempts = 5; this.reconnectDelay = 1e3; } /** * Connect to the signaling server * @returns {Promise<void>} */ connect() { return new Promise((resolve, reject) => { try { this.ws = new WebSocket(this.serverUrl); this.ws.onopen = () => { this.connected = true; this.reconnectAttempts = 0; this.dispatchEvent(new CustomEvent("connected")); resolve(); }; this.ws.onmessage = (event) => { try { const message = JSON.parse(event.data); this.handleMessage(message); } catch (err) { console.error("Error parsing message:", err); } }; this.ws.onerror = (error) => { this.dispatchEvent(new CustomEvent("error", { detail: error })); reject(error); }; this.ws.onclose = () => { this.connected = false; this.dispatchEvent(new CustomEvent("disconnected")); this.attemptReconnect(); }; } catch (err) { reject(err); } }); } /** * Handle incoming messages from signaling server * @private */ handleMessage(message) { switch (message.type) { case "id": this.clientId = message.id; this.dispatchEvent(new CustomEvent("id", { detail: { id: message.id } })); break; case "clients": this.dispatchEvent(new CustomEvent("clients", { detail: { clients: message.clients } })); break; case "offer": case "answer": case "ice-candidate": this.dispatchEvent(new CustomEvent("signal", { detail: message })); break; default: console.warn("Unknown message type:", message.type); } } /** * Send a message to the signaling server * @param {Object} message - Message to send */ send(message) { if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(message)); } else { throw new Error("WebSocket not connected"); } } /** * Send an offer to a peer * @param {string|number} peerId - Target peer ID * @param {RTCSessionDescriptionInit} offer - WebRTC offer */ sendOffer(peerId, offer) { this.send({ type: "offer", to: peerId, offer }); } /** * Send an answer to a peer * @param {string|number} peerId - Target peer ID * @param {RTCSessionDescriptionInit} answer - WebRTC answer */ sendAnswer(peerId, answer) { this.send({ type: "answer", to: peerId, answer }); } /** * Send an ICE candidate to a peer * @param {string|number} peerId - Target peer ID * @param {RTCIceCandidateInit} candidate - ICE candidate */ sendIceCandidate(peerId, candidate) { this.send({ type: "ice-candidate", to: peerId, candidate }); } /** * Attempt to reconnect to the signaling server * @private */ attemptReconnect() { if (this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectAttempts++; setTimeout(() => { console.log(`Reconnecting... attempt ${this.reconnectAttempts}`); this.connect().catch((err) => { console.error("Reconnect failed:", err); }); }, this.reconnectDelay * this.reconnectAttempts); } } /** * Disconnect from the signaling server */ disconnect() { if (this.ws) { this.ws.close(); this.ws = null; this.connected = false; this.clientId = null; } } /** * Check if connected to signaling server * @returns {boolean} */ isConnected() { return this.connected && this.ws && this.ws.readyState === WebSocket.OPEN; } /** * Get client ID * @returns {string|number|null} */ getClientId() { return this.clientId; } }; // src/MDNSResolver.js var MDNSResolver = class { constructor(options = {}) { this._resolver = null; this._initialized = false; this._cache = /* @__PURE__ */ new Map(); this._cacheTimeout = 6e4; this._mode = null; this._serverUrl = options.serverUrl || "http://localhost:5380"; } /** * Initialize the mDNS resolver * Automatically detects Node.js vs Browser environment * @returns {Promise<void>} */ async initialize() { if (this._initialized) { return; } const isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null; if (isNode) { try { const pigeonnsModule = await import("pigeonns"); const MDNSResolver2 = pigeonnsModule.default || pigeonnsModule; this._resolver = new MDNSResolver2({ timeout: 5e3, ttl: 60, // Match our cache timeout cacheSize: 1e3 }); this._resolver.start(); this._mode = "node"; this._initialized = true; console.log("\u2713 mDNS resolver initialized (Node.js mode)"); } catch (error) { console.warn("Failed to initialize Node.js mDNS resolver:", error.message); this._initialized = false; } } else { try { const response = await fetch(`${this._serverUrl}/health`, { method: "GET", signal: AbortSignal.timeout(2e3) // 2 second timeout }); if (response.ok) { this._mode = "browser"; this._initialized = true; console.log(`\u2713 mDNS resolver initialized (Browser mode) - Server: ${this._serverUrl}`); } else { console.warn(`pigeonns server not responding at ${this._serverUrl}`); this._initialized = false; } } catch (error) { console.warn(`Failed to connect to pigeonns server at ${this._serverUrl}:`, error.message); console.warn("mDNS resolution will be disabled. Start pigeonns server with: npx pigeonns serve"); this._initialized = false; } } } /** * Check if the resolver is available and initialized * @returns {boolean} */ isAvailable() { return this._initialized && this._resolver !== null; } /** * Check if an ICE candidate contains a .local hostname * @param {RTCIceCandidateInit} candidate - ICE candidate to check * @returns {boolean} */ isLocalCandidate(candidate) { if (!candidate || !candidate.candidate) { return false; } return candidate.candidate.includes(".local"); } /** * Extract hostname from ICE candidate string * @param {string} candidateString - ICE candidate string * @returns {string|null} - Extracted hostname or null * @private */ _extractHostname(candidateString) { const parts = candidateString.split(" "); for (let i = 0; i < parts.length; i++) { if (parts[i].endsWith(".local")) { return parts[i]; } } return null; } /** * Get cached IP address for hostname * @param {string} hostname - Hostname to lookup * @returns {string|null} * @private */ _getCachedIP(hostname) { const cached = this._cache.get(hostname); if (cached && Date.now() - cached.timestamp < this._cacheTimeout) { return cached.ip; } return null; } /** * Set cached IP address for hostname * @param {string} hostname - Hostname to cache * @param {string} ip - IP address to cache * @private */ _setCachedIP(hostname, ip) { this._cache.set(hostname, { ip, timestamp: Date.now() }); } /** * Resolve a .local hostname to an IP address using mDNS * Works in both Node.js (direct mDNS) and Browser (HTTP API) modes * @param {string} hostname - Hostname to resolve (e.g., "myhost.local") * @returns {Promise<string|null>} - Resolved IP address or null if resolution fails */ async resolve(hostname) { if (!this.isAvailable()) { console.warn("mDNS resolver not available"); return null; } const cachedIP = this._getCachedIP(hostname); if (cachedIP) { return cachedIP; } try { let ip = null; if (this._mode === "node") { ip = await this._resolver.resolve(hostname, "A"); } else if (this._mode === "browser") { const response = await fetch( `${this._serverUrl}/resolve?name=${encodeURIComponent(hostname)}&type=A`, { method: "GET", signal: AbortSignal.timeout(5e3) // 5 second timeout } ); if (response.ok) { const data = await response.json(); ip = data.address; } else { console.warn(`Failed to resolve ${hostname}: HTTP ${response.status}`); } } if (ip) { this._setCachedIP(hostname, ip); return ip; } return null; } catch (error) { console.warn(`Failed to resolve ${hostname}:`, error.message); return null; } } /** * Resolve an ICE candidate that contains a .local hostname * Returns a new candidate with the hostname replaced by the IP address * @param {RTCIceCandidateInit} candidate - ICE candidate to resolve * @returns {Promise<RTCIceCandidateInit|null>} - Resolved candidate or null */ async resolveCandidate(candidate) { if (!candidate || !candidate.candidate) { return null; } if (!this.isLocalCandidate(candidate)) { return candidate; } const hostname = this._extractHostname(candidate.candidate); if (!hostname) { console.warn("Could not extract hostname from candidate:", candidate.candidate); return null; } const ip = await this.resolve(hostname); if (!ip) { console.warn(`Could not resolve ${hostname} to IP address`); return null; } const resolvedCandidateString = candidate.candidate.replace(hostname, ip); return { ...candidate, candidate: resolvedCandidateString, address: ip }; } /** * Clear the resolution cache */ clearCache() { this._cache.clear(); } /** * Dispose of the resolver and clean up resources */ dispose() { this.clearCache(); if (this._mode === "node" && this._resolver) { try { this._resolver.stop(); } catch (error) { console.warn("Error stopping mDNS resolver:", error.message); } } this._resolver = null; this._initialized = false; this._mode = null; } }; // src/PeerConnection.js var PeerConnection = class extends EventTarget { constructor(rtcInstance, signalingClient, config = {}) { super(); this.rtc = rtcInstance; this.signaling = signalingClient; this.config = config; this.pc = null; this.dataChannels = /* @__PURE__ */ new Map(); this.remoteId = null; this.isInitiator = false; this.mdnsResolver = new MDNSResolver({ serverUrl: config.mdnsServerUrl || "http://localhost:5380" }); this._mdnsEnabled = config.enableMDNS !== false; } /** * Initialize peer connection * @private */ async _init() { if (this._mdnsEnabled) { await this.mdnsResolver.initialize(); } this.pc = this.rtc.createPeerConnection(this.config); this.pc.onicecandidate = async (event) => { if (event.candidate && this.remoteId) { let candidateToSend = event.candidate; if (this._mdnsEnabled && this.mdnsResolver.isAvailable() && this.mdnsResolver.isLocalCandidate(event.candidate)) { const resolvedCandidate = await this.mdnsResolver.resolveCandidate(event.candidate); if (resolvedCandidate) { candidateToSend = resolvedCandidate; console.log("Resolved .local ICE candidate:", event.candidate.candidate, "->", resolvedCandidate.candidate); } } this.signaling.sendIceCandidate(this.remoteId, candidateToSend); } }; this.pc.onconnectionstatechange = () => { this.dispatchEvent(new CustomEvent("connectionstatechange", { detail: this.pc.connectionState })); if (this.pc.connectionState === "connected") { this.dispatchEvent(new CustomEvent("connected")); } else if (this.pc.connectionState === "failed") { this.dispatchEvent(new CustomEvent("failed")); } }; this.pc.oniceconnectionstatechange = () => { this.dispatchEvent(new CustomEvent("iceconnectionstatechange", { detail: this.pc.iceConnectionState })); }; this.pc.ontrack = (event) => { this.dispatchEvent(new CustomEvent("track", { detail: { track: event.track, streams: event.streams } })); }; this.pc.ondatachannel = (event) => { const channel = event.channel; this.dataChannels.set(channel.label, channel); this._setupDataChannel(channel); this.dispatchEvent(new CustomEvent("datachannel", { detail: channel })); }; } /** * Connect to a remote peer * @param {string|number} peerId - Remote peer ID * @param {MediaStream} [localStream] - Optional local media stream * @returns {Promise<void>} */ async connect(peerId, localStream = null) { this.remoteId = peerId; this.isInitiator = true; await this._init(); if (localStream) { localStream.getTracks().forEach((track) => { this.pc.addTrack(track, localStream); }); } const offer = await this.pc.createOffer(); await this.pc.setLocalDescription(offer); this.signaling.sendOffer(peerId, offer); } /** * Handle incoming offer from remote peer * @param {string|number} peerId - Remote peer ID * @param {RTCSessionDescriptionInit} offer - WebRTC offer * @param {MediaStream} [localStream] - Optional local media stream * @returns {Promise<void>} */ async handleOffer(peerId, offer, localStream = null) { this.remoteId = peerId; this.isInitiator = false; await this._init(); if (localStream) { localStream.getTracks().forEach((track) => { this.pc.addTrack(track, localStream); }); } await this.pc.setRemoteDescription(offer); const answer = await this.pc.createAnswer(); await this.pc.setLocalDescription(answer); this.signaling.sendAnswer(peerId, answer); } /** * Handle incoming answer from remote peer * @param {RTCSessionDescriptionInit} answer - WebRTC answer * @returns {Promise<void>} */ async handleAnswer(answer) { await this.pc.setRemoteDescription(answer); } /** * Handle incoming ICE candidate * @param {RTCIceCandidateInit} candidate - ICE candidate * @returns {Promise<void>} */ async handleIceCandidate(candidate) { if (this.pc) { let candidateToAdd = candidate; if (this._mdnsEnabled && this.mdnsResolver.isAvailable() && this.mdnsResolver.isLocalCandidate(candidate)) { const resolvedCandidate = await this.mdnsResolver.resolveCandidate(candidate); if (resolvedCandidate) { candidateToAdd = resolvedCandidate; console.log("Resolved incoming .local ICE candidate:", candidate.candidate, "->", resolvedCandidate.candidate); } } await this.pc.addIceCandidate(candidateToAdd); } } /** * Create a data channel * @param {string} label - Channel label * @param {RTCDataChannelInit} [options] - Data channel options * @returns {RTCDataChannel} */ createDataChannel(label, options = {}) { if (!this.pc) { throw new Error("Peer connection not initialized"); } const channel = this.pc.createDataChannel(label, options); this.dataChannels.set(label, channel); this._setupDataChannel(channel); return channel; } /** * Setup data channel event handlers * @private */ _setupDataChannel(channel) { channel.onopen = () => { this.dispatchEvent(new CustomEvent("channelopen", { detail: channel })); }; channel.onmessage = (event) => { this.dispatchEvent(new CustomEvent("message", { detail: { channel: channel.label, data: event.data } })); }; channel.onclose = () => { this.dataChannels.delete(channel.label); this.dispatchEvent(new CustomEvent("channelclose", { detail: channel })); }; } /** * Send data on a channel * @param {string} channelLabel - Channel label * @param {string|ArrayBuffer|Blob} data - Data to send */ send(channelLabel, data) { const channel = this.dataChannels.get(channelLabel); if (channel && channel.readyState === "open") { channel.send(data); } else { throw new Error(`Channel ${channelLabel} not open`); } } /** * Get a data channel by label * @param {string} label - Channel label * @returns {RTCDataChannel|undefined} */ getDataChannel(label) { return this.dataChannels.get(label); } /** * Get the underlying RTCPeerConnection * @returns {RTCPeerConnection} */ getRTCPeerConnection() { return this.pc; } /** * Close the peer connection */ close() { if (this.pc) { this.pc.close(); this.pc = null; } this.dataChannels.clear(); this.remoteId = null; if (this.mdnsResolver) { this.mdnsResolver.dispose(); } } }; // src/PigeonRTC.js var PigeonRTC = class { constructor(options = {}) { this.adapter = options.adapter || null; this.initialized = false; } /** * Initialize PigeonRTC with automatic adapter detection or custom adapter * @param {Object} options - Configuration options * @param {RTCAdapter} options.adapter - Custom adapter to use (optional) * @param {boolean} options.preferNode - Prefer Node adapter even in browser (for testing) * @returns {Promise<void>} */ async initialize(options = {}) { if (this.initialized) { return; } if (options.adapter) { this.adapter = options.adapter; } if (!this.adapter) { this.adapter = await this._detectAdapter(options); } await this.adapter.initialize(); this.initialized = true; } /** * Automatically detect and create the appropriate adapter for the current environment * @private */ async _detectAdapter(options = {}) { if (options.preferNode || typeof window === "undefined" && typeof process !== "undefined") { const nodeAdapter = new NodeRTCAdapter(); if (nodeAdapter.isSupported()) { try { await nodeAdapter.initialize(); return nodeAdapter; } catch (error) { console.warn("Node adapter initialization failed, trying browser adapter:", error.message); } } } const browserAdapter = new BrowserRTCAdapter(); if (browserAdapter.isSupported()) { return browserAdapter; } throw new Error( "No supported WebRTC adapter found. Make sure you are running in a browser with WebRTC support or have @koush/wrtc installed for Node.js." ); } /** * Ensure PigeonRTC is initialized before use * @private */ _ensureInitialized() { if (!this.initialized || !this.adapter) { throw new Error("PigeonRTC not initialized. Call initialize() first."); } } /** * Get the RTCPeerConnection class * @returns {typeof RTCPeerConnection} */ getRTCPeerConnection() { this._ensureInitialized(); return this.adapter.getRTCPeerConnection(); } /** * Get the RTCSessionDescription class * @returns {typeof RTCSessionDescription} */ getRTCSessionDescription() { this._ensureInitialized(); return this.adapter.getRTCSessionDescription(); } /** * Get the RTCIceCandidate class * @returns {typeof RTCIceCandidate} */ getRTCIceCandidate() { this._ensureInitialized(); return this.adapter.getRTCIceCandidate(); } /** * Get the MediaStream class (if supported) * @returns {typeof MediaStream|null} */ getMediaStream() { this._ensureInitialized(); return this.adapter.getMediaStream(); } /** * Create a new RTCPeerConnection with the given configuration * @param {RTCConfiguration} config - RTCPeerConnection configuration * @returns {RTCPeerConnection} */ createPeerConnection(config) { this._ensureInitialized(); const RTCPeerConnection = this.adapter.getRTCPeerConnection(); return new RTCPeerConnection(config); } /** * Create a new RTCSessionDescription * @param {RTCSessionDescriptionInit} init - Session description initialization * @returns {RTCSessionDescription} */ createSessionDescription(init) { this._ensureInitialized(); const RTCSessionDescription = this.adapter.getRTCSessionDescription(); return new RTCSessionDescription(init); } /** * Create a new RTCIceCandidate * @param {RTCIceCandidateInit} init - ICE candidate initialization * @returns {RTCIceCandidate} */ createIceCandidate(init) { this._ensureInitialized(); const RTCIceCandidate = this.adapter.getRTCIceCandidate(); return new RTCIceCandidate(init); } /** * Get user media stream (camera/microphone) * @param {MediaStreamConstraints} constraints * @returns {Promise<MediaStream>} */ async getUserMedia(constraints) { this._ensureInitialized(); return await this.adapter.getUserMedia(constraints); } /** * Get display media stream (screen sharing) * @param {MediaStreamConstraints} constraints * @returns {Promise<MediaStream>} */ async getDisplayMedia(constraints) { this._ensureInitialized(); return await this.adapter.getDisplayMedia(constraints); } /** * Check if WebRTC is supported in the current environment * @returns {boolean} */ isSupported() { return this.adapter ? this.adapter.isSupported() : false; } /** * Get the name of the current adapter * @returns {string} */ getAdapterName() { return this.adapter ? this.adapter.getName() : "None"; } /** * Create a signaling client for peer discovery and connection management * @param {string} serverUrl - WebSocket server URL (e.g., 'ws://localhost:9090') * @returns {SignalingClient} */ createSignalingClient(serverUrl) { return new SignalingClient(serverUrl); } /** * Create a managed peer connection with built-in signaling * @param {SignalingClient} signalingClient - Signaling client instance * @param {RTCConfiguration} config - RTCPeerConnection configuration * @returns {PeerConnection} */ createManagedPeerConnection(signalingClient, config = {}) { this._ensureInitialized(); return new PeerConnection(this, signalingClient, config); } }; async function createPigeonRTC(options = {}) { const rtc = new PigeonRTC(options); await rtc.initialize(options); return rtc; } // src/index.js var index_default = createPigeonRTC; //# sourceMappingURL=index.js.map