Текстовый файл из учебного комплекта, не свидетельство установленного 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; signal?: AbortSignal; onProgress?: (message: string, status?: number) => void }, 7 ): Promise<string | null> { 8 const offer = await pc.createOffer(); 9 await pc.setLocalDescription(offer); 10 11 await waitIceGather(pc, config.webrtc.iceGatherTimeoutMs, opts?.signal); 12 opts?.signal?.throwIfAborted(); 13 14 const local = pc.localDescription; 15 if (!local) throw new Error("localDescription is missing"); 16 17 const body: Record<string, unknown> = { sdp: local.sdp, type: local.type }; 18 const sessionId = opts?.sessionId; 19 if (typeof sessionId === "string" && sessionId) body.session_id = sessionId; 20 21 opts?.onProgress?.("Sending SDP offer (body not logged)"); 22 const resp = await fetch(config.signaling.offerEndpoint, { 23 method: "POST", 24 headers: { "Content-Type": "application/json" }, 25 body: JSON.stringify(body), 26 signal: opts?.signal, 27 }); 28 opts?.onProgress?.(`Signaling HTTP response: ${resp.status}`, resp.status); 29 30 if (!resp.ok) throw new Error(`Signaling server responded with ${resp.status}`); 31 32 const answer = (await resp.json()) as any; 33 const sdp = typeof answer?.sdp === "string" ? answer.sdp : null; 34 const type = typeof answer?.type === "string" ? answer.type : null; 35 if (!sdp || !type) throw new Error("Signaling answer is missing sdp/type"); 36 await pc.setRemoteDescription({ sdp, type }); 37 38 return typeof answer?.session_id === "string" ? answer.session_id : null; 39 } 40 41 function waitIceGather(pc: RTCPeerConnection, timeoutMs: number, signal?: AbortSignal): Promise<void> { 42 signal?.throwIfAborted(); 43 if (pc.iceGatheringState === "complete") return Promise.resolve(); 44 45 return new Promise((resolve, reject) => { 46 const cleanup = () => { 47 window.clearTimeout(timer); 48 pc.removeEventListener("icegatheringstatechange", onChange); 49 signal?.removeEventListener("abort", onAbort); 50 }; 51 const timer = window.setTimeout(() => { cleanup(); resolve(); }, timeoutMs); 52 53 const onChange = () => { 54 if (pc.iceGatheringState === "complete") { 55 cleanup(); 56 resolve(); 57 } 58 }; 59 const onAbort = () => { cleanup(); reject(signal?.reason ?? new DOMException("Cancelled", "AbortError")); }; 60 61 pc.addEventListener("icegatheringstatechange", onChange); 62 signal?.addEventListener("abort", onAbort, { once: true }); 63 }); 64 }