Текстовый файл из учебного комплекта, не свидетельство установленного runtime. Вернуться к учебнику · Исходный файл
1 import type { AppConfig } from "../config/appConfig"; 2 3 export async function negotiate( 4 pc: RTCPeerConnection, 5 config: AppConfig, 6 opts?: { sessionId?: string | null }, 7 ): Promise<string | null> { 8 const offer = await pc.createOffer(); 9 await pc.setLocalDescription(offer); 10 11 await waitIceGather(pc, config.webrtc.iceGatherTimeoutMs); 12 13 const local = pc.localDescription; 14 if (!local) throw new Error("localDescription is missing"); 15 16 const body: Record<string, unknown> = { sdp: local.sdp, type: local.type }; 17 const sessionId = opts?.sessionId; 18 if (typeof sessionId === "string" && sessionId) body.session_id = sessionId; 19 20 const resp = await fetch(config.signaling.offerEndpoint, { 21 method: "POST", 22 headers: { "Content-Type": "application/json" }, 23 body: JSON.stringify(body), 24 }); 25 26 if (!resp.ok) throw new Error(`Signaling server responded with ${resp.status}`); 27 28 const answer = (await resp.json()) as any; 29 const sdp = typeof answer?.sdp === "string" ? answer.sdp : null; 30 const type = typeof answer?.type === "string" ? answer.type : null; 31 if (!sdp || !type) throw new Error("Signaling answer is missing sdp/type"); 32 await pc.setRemoteDescription({ sdp, type }); 33 34 return typeof answer?.session_id === "string" ? answer.session_id : null; 35 } 36 37 function waitIceGather(pc: RTCPeerConnection, timeoutMs: number): Promise<void> { 38 if (pc.iceGatheringState === "complete") return Promise.resolve(); 39 40 return new Promise((resolve) => { 41 const timer = window.setTimeout(() => { 42 pc.removeEventListener("icegatheringstatechange", onChange); 43 resolve(); 44 }, timeoutMs); 45 46 const onChange = () => { 47 if (pc.iceGatheringState === "complete") { 48 window.clearTimeout(timer); 49 pc.removeEventListener("icegatheringstatechange", onChange); 50 resolve(); 51 } 52 }; 53 54 pc.addEventListener("icegatheringstatechange", onChange); 55 }); 56 }