@hyperttp/transport-webrtc 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -48,6 +48,14 @@ implementation. It requires an installed FFmpeg executable with `libopus` suppor
48
48
  (`ffmpegPath` can override PATH). It encodes one shared PCM input to 20 ms Opus
49
49
  frames and delivers RTP to independent Werift tracks over IPv4 loopback UDP.
50
50
  It starts consuming immediately; late subscribers join the current position.
51
+ By default FFmpeg's `-re` paces input at its nominal rate. For a live input that
52
+ is already paced, use `createPcmAudioSource(pcmStream, { realtime: false })` to
53
+ avoid this second limiter. RTP is paced using its 48 kHz timestamps, with a
54
+ bounded queue of 100 packets. An unpaced producer can overflow that queue, in
55
+ which case `audio.done` rejects; UDP itself can also drop packets before they
56
+ reach the queue. Producers remain responsible for pacing and bounded buffering.
57
+ After an input or event-loop pause, delivery resumes without a catch-up burst.
58
+ At EOF, `done` waits for queued packets to be delivered; explicit close discards them.
51
59
  Observe `audio.done` for EOF/encoder/input errors. Close the adapter when its
52
60
  playback session ends. Closing a listener stops only its track, not the source.
53
61
  It does not implement seeking, playback pause, or audio mixing.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperttp/transport-webrtc",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "WebRTC transport for Hyperttp powered by Werift",
5
5
  "keywords": [
6
6
  "bun",
package/pcm-audio.d.ts CHANGED
@@ -11,13 +11,15 @@ export interface PcmAudioSource {
11
11
  }
12
12
  export interface PcmAudioOptions {
13
13
  ffmpegPath?: string;
14
+ /** Pace input with FFmpeg's -re (default: true). RTP is additionally paced when false. */
15
+ realtime?: boolean;
14
16
  signal?: AbortSignal;
15
17
  }
16
18
  /**
17
19
  * Optional server-only helper. Requires an external FFmpeg executable with libopus
18
20
  * on PATH (or ffmpegPath). Input is raw 48 kHz, stereo, signed 16-bit little-endian
19
21
  * PCM; chunk boundaries need not align to samples. One input/encoder is shared,
20
- * paced by FFmpeg's -re, with no replay for late subscribers. RTP/RTCP stays on
22
+ * paced by FFmpeg's -re by default, with no replay for late subscribers. RTP/RTCP stays on
21
23
  * IPv4 loopback. No peer connection or native wrtc dependency is created.
22
24
  *
23
25
  * Writes are bounded to 16 KiB, with one outstanding write and one input chunk.
@@ -1 +1 @@
1
- {"version":3,"file":"pcm-audio.d.ts","sourceRoot":"","sources":["../src/pcm-audio.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAA+B,MAAM,QAAQ,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,IAAI,oBAAoB,CAAC;IAClC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,uFAAuF;IACvF,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,cAAc,CAAC,UAAU,CAAC,EAC7D,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,cAAc,CAAC,CA4PzB"}
1
+ {"version":3,"file":"pcm-audio.d.ts","sourceRoot":"","sources":["../src/pcm-audio.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAA+B,MAAM,QAAQ,CAAC;AAGlE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,IAAI,oBAAoB,CAAC;IAClC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,uFAAuF;IACvF,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,cAAc,CAAC,UAAU,CAAC,EAC7D,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,cAAc,CAAC,CA2RzB"}
package/pcm-audio.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";import{spawn as _}from"node:child_process";import{createSocket as I}from"node:dgram";import{MediaStream as x,MediaStreamTrack as A,RtpPacket as R}from"werift";export async function createPcmAudioSource(w,l={}){l.signal?.throwIfAborted();const s=I("udp4");try{await new Promise((e,r)=>{s.once("error",r),s.bind(0,"127.0.0.1",()=>{s.removeListener("error",r),e()})}),l.signal?.throwIfAborted()}catch(e){try{s.close()}catch{}throw e}const g=s.address().port,o=_(l.ffmpegPath??"ffmpeg",["-hide_banner","-loglevel","error","-nostdin","-re","-f","s16le","-ar","48000","-ac","2","-i","pipe:0","-c:a","libopus","-frame_duration","20","-ar","48000","-ac","2","-payload_type","111","-f","rtp",`rtp://127.0.0.1:${g}?rtcpport=${g}&pkt_size=1200`],{stdio:["pipe","ignore","pipe"]}),u=new Set;let n=!1,k=!1,m="",h,P,y;const v=new Promise((e,r)=>{P=e,y=r});v.catch(()=>{});const d=new Set;function S(e){return new Promise((r,i)=>{const t=()=>r(void 0);d.add(t),Promise.resolve(e).then(a=>{d.delete(t),r(a)},a=>{d.delete(t),i(a)}),n&&(d.delete(t),t())})}const C=new Promise(e=>o.once("close",()=>e()));let f;function c(e){if(f)return f;n=!0;for(const r of d)r();d.clear(),l.signal?.removeEventListener("abort",b);try{Promise.resolve(h?.()).catch(()=>{})}catch{}for(const r of u)r.stop();return u.clear(),o.stdin.destroy(),o.exitCode===null&&o.signalCode===null&&o.kill("SIGTERM"),f=(async()=>{const r=setTimeout(()=>{o.kill("SIGKILL")},1e3);try{await Promise.all([C,new Promise(i=>s.close(()=>i()))])}finally{clearTimeout(r)}})(),f.then(()=>e===void 0?P():y(e),y),f}function b(){c(l.signal?.reason??new Error("PCM audio source aborted"))}s.on("error",e=>{c(e)}),o.stdin.on("error",e=>{n||c(e)}),o.stderr.on("data",e=>{m=(m+e.toString()).slice(-8192)}),o.on("error",e=>{c(e)}),o.on("close",(e,r)=>{n||(e===0&&k?c():c(new Error(`FFmpeg exited (${r??e}): ${m.trim()}`)))}),s.on("message",(e,r)=>{if(n||r.address!=="127.0.0.1"||e.length<12||e[0]>>>6!==2||(e[1]&127)!==111)return;let i;try{i=R.deSerialize(e)}catch{return}if(i.header.payloadType===111)for(const t of u)t.stopped||t.writeRtp(i)}),l.signal?.addEventListener("abort",b,{once:!0}),l.signal?.aborted&&b();try{if(await new Promise((e,r)=>{o.once("spawn",e),o.once("error",r)}),n)throw await v,new Error("PCM audio source closed during startup")}catch(e){throw await c(e),e}async function L(){let e;try{let r;if("getReader"in w){const t=w.getReader();r={next:async()=>{const a=await t.read();return a.done?{done:!0,value:void 0}:{done:!1,value:a.value}}},h=()=>t.cancel(),e=()=>t.releaseLock()}else r=w[Symbol.asyncIterator](),h=()=>r.return?.();let i=0;for(;!n;){const t=await S(r.next());if(!t||n)return;if(t.done){if(i%4!==0)throw new Error("PCM input ends with an incomplete stereo s16le sample");k=!0,o.stdin.end();return}if(!(t.value instanceof Uint8Array))throw new TypeError("PCM chunks must be Uint8Array");const a=t.value;i=(i+a.byteLength)%4;for(let p=0;p<a.byteLength&&!n;p+=16384)await S(new Promise((M,T)=>{o.stdin.write(a.subarray(p,p+16384),E=>E?T(E):M())}))}}catch(r){n||c(r)}finally{try{e?.()}catch{}}}return L(),{done:v,subscribe(){if(n)throw new Error("PCM audio source is closed");const e=new A({kind:"audio"}),r=new x;return r.addTrack(e),u.add(e),{stream:r,close(){u.delete(e),e.stop()}}},close:()=>c()}}
1
+ "use strict";import{spawn as x}from"node:child_process";import{createSocket as A}from"node:dgram";import{MediaStream as $,MediaStreamTrack as z,RtpPacket as D}from"werift";import{RtpPacer as F}from"./rtp-pacer.js";export async function createPcmAudioSource(h,c={}){c.signal?.throwIfAborted();const d=A("udp4");try{await new Promise((e,r)=>{d.once("error",r),d.bind(0,"127.0.0.1",()=>{d.removeListener("error",r),e()})}),c.signal?.throwIfAborted()}catch(e){try{d.close()}catch{}throw e}const g=d.address().port,o=x(c.ffmpegPath??"ffmpeg",["-hide_banner","-loglevel","error","-nostdin",...c.realtime===!1?[]:["-re"],"-f","s16le","-ar","48000","-ac","2","-i","pipe:0","-c:a","libopus","-frame_duration","20","-ar","48000","-ac","2","-payload_type","111","-f","rtp",`rtp://127.0.0.1:${g}?rtcpport=${g}&pkt_size=1200`],{stdio:["pipe","ignore","pipe"]}),l=new Set;let a=!1,E=!1,y="",v,S,b;const k=new Promise((e,r)=>{S=e,b=r});k.catch(()=>{});const f=new Set;function T(e){return new Promise((r,n)=>{const t=()=>r(void 0);f.add(t),Promise.resolve(e).then(s=>{f.delete(t),r(s)},s=>{f.delete(t),n(s)}),a&&(f.delete(t),t())})}const M=new Promise(e=>o.once("close",()=>e()));let u,C=!1,p;const m=c.realtime===!1?new F(e=>{for(const r of l)r.stopped||r.writeRtp(e)},e=>{i(e)},()=>{C&&p===void 0&&i()}):void 0;function i(e){if(u)return u;a=!0,p!==void 0&&clearTimeout(p),m?.stop();for(const r of f)r();f.clear(),c.signal?.removeEventListener("abort",P);try{Promise.resolve(v?.()).catch(()=>{})}catch{}for(const r of l)r.stop();return l.clear(),o.stdin.destroy(),o.exitCode===null&&o.signalCode===null&&o.kill("SIGTERM"),u=(async()=>{const r=setTimeout(()=>{o.kill("SIGKILL")},1e3);try{await Promise.all([M,new Promise(n=>d.close(()=>n()))])}finally{clearTimeout(r)}})(),u.then(()=>e===void 0?S():b(e),b),u}function P(){i(c.signal?.reason??new Error("PCM audio source aborted"))}d.on("error",e=>{i(e)}),o.stdin.on("error",e=>{a||i(e)}),o.stderr.on("data",e=>{y=(y+e.toString()).slice(-8192)}),o.on("error",e=>{i(e)}),o.on("close",(e,r)=>{a||(e===0&&E?m?(C=!0,p=setTimeout(()=>{p=void 0,m.empty&&i()},50)):i():i(new Error(`FFmpeg exited (${r??e}): ${y.trim()}`)))}),d.on("message",(e,r)=>{if(a||r.address!=="127.0.0.1"||e.length<12||e[0]>>>6!==2||(e[1]&127)!==111)return;let n;try{n=D.deSerialize(e)}catch{return}if(n.header.payloadType===111)if(m)m.push(n,n.header.timestamp);else for(const t of l)t.stopped||t.writeRtp(n)}),c.signal?.addEventListener("abort",P,{once:!0}),c.signal?.aborted&&P();try{if(await new Promise((e,r)=>{o.once("spawn",e),o.once("error",r)}),a)throw await k,new Error("PCM audio source closed during startup")}catch(e){throw await i(e),e}async function _(){let e;try{let r;if("getReader"in h){const t=h.getReader();r={next:async()=>{const s=await t.read();return s.done?{done:!0,value:void 0}:{done:!1,value:s.value}}},v=()=>t.cancel(),e=()=>t.releaseLock()}else r=h[Symbol.asyncIterator](),v=()=>r.return?.();let n=0;for(;!a;){const t=await T(r.next());if(!t||a)return;if(t.done){if(n%4!==0)throw new Error("PCM input ends with an incomplete stereo s16le sample");E=!0,o.stdin.end();return}if(!(t.value instanceof Uint8Array))throw new TypeError("PCM chunks must be Uint8Array");const s=t.value;n=(n+s.byteLength)%4;for(let w=0;w<s.byteLength&&!a;w+=16384)await T(new Promise((I,R)=>{o.stdin.write(s.subarray(w,w+16384),L=>L?R(L):I())}))}}catch(r){a||i(r)}finally{try{e?.()}catch{}}}return _(),{done:k,subscribe(){if(a)throw new Error("PCM audio source is closed");const e=new z({kind:"audio"}),r=new $;return r.addTrack(e),l.add(e),{stream:r,close(){l.delete(e),e.stop()}}},close:()=>i()}}
2
2
  //# sourceMappingURL=pcm-audio.js.map
package/pcm-audio.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/pcm-audio.ts"],
4
- "sourcesContent": ["import { spawn } from \"node:child_process\";\nimport { createSocket } from \"node:dgram\";\nimport { MediaStream, MediaStreamTrack, RtpPacket } from \"werift\";\n\nexport interface PcmAudioSubscription {\n stream: MediaStream;\n close(): void;\n}\n\nexport interface PcmAudioSource {\n subscribe(): PcmAudioSubscription;\n close(): Promise<void>;\n /** Resolves on EOF or explicit close; rejects on input, socket, or encoder failure. */\n done: Promise<void>;\n}\n\nexport interface PcmAudioOptions {\n ffmpegPath?: string;\n signal?: AbortSignal;\n}\n\n/**\n * Optional server-only helper. Requires an external FFmpeg executable with libopus\n * on PATH (or ffmpegPath). Input is raw 48 kHz, stereo, signed 16-bit little-endian\n * PCM; chunk boundaries need not align to samples. One input/encoder is shared,\n * paced by FFmpeg's -re, with no replay for late subscribers. RTP/RTCP stays on\n * IPv4 loopback. No peer connection or native wrtc dependency is created.\n *\n * Writes are bounded to 16 KiB, with one outstanding write and one input chunk.\n * Producers must bound their own chunks/queues. Closing requests iterator.return()\n * or reader.cancel(); arbitrary iterators must cooperate to release their resources\n * (a stuck next()/return() does not prevent this helper from closing).\n */\nexport async function createPcmAudioSource(\n input: AsyncIterable<Uint8Array> | ReadableStream<Uint8Array>,\n options: PcmAudioOptions = {},\n): Promise<PcmAudioSource> {\n options.signal?.throwIfAborted();\n const socket = createSocket(\"udp4\");\n try {\n await new Promise<void>((resolve, reject) => {\n socket.once(\"error\", reject);\n socket.bind(0, \"127.0.0.1\", () => {\n socket.removeListener(\"error\", reject);\n resolve();\n });\n });\n options.signal?.throwIfAborted();\n } catch (error) {\n try {\n socket.close();\n } catch {\n /* Bind may not have succeeded. */\n }\n throw error;\n }\n\n const port = socket.address().port;\n const child = spawn(\n options.ffmpegPath ?? \"ffmpeg\",\n [\n \"-hide_banner\",\n \"-loglevel\",\n \"error\",\n \"-nostdin\",\n \"-re\",\n \"-f\",\n \"s16le\",\n \"-ar\",\n \"48000\",\n \"-ac\",\n \"2\",\n \"-i\",\n \"pipe:0\",\n \"-c:a\",\n \"libopus\",\n \"-frame_duration\",\n \"20\",\n \"-ar\",\n \"48000\",\n \"-ac\",\n \"2\",\n \"-payload_type\",\n \"111\",\n \"-f\",\n \"rtp\",\n `rtp://127.0.0.1:${port}?rtcpport=${port}&pkt_size=1200`,\n ],\n { stdio: [\"pipe\", \"ignore\", \"pipe\"] },\n );\n\n const tracks = new Set<MediaStreamTrack>();\n let stopped = false;\n let inputEnded = false;\n let stderr = \"\";\n let cancelInput: (() => unknown) | undefined;\n let resolveDone!: () => void;\n let rejectDone!: (error: unknown) => void;\n const done = new Promise<void>((resolve, reject) => {\n resolveDone = resolve;\n rejectDone = reject;\n });\n // Callers may observe done later, or only call close().\n void done.catch(() => {});\n const pending = new Set<() => void>();\n function interruptible<T>(operation: PromiseLike<T>): Promise<T | undefined> {\n return new Promise((resolve, reject) => {\n const cancel = () => resolve(undefined);\n pending.add(cancel);\n void Promise.resolve(operation).then(\n (value) => {\n pending.delete(cancel);\n resolve(value);\n },\n (error) => {\n pending.delete(cancel);\n reject(error);\n },\n );\n if (stopped) {\n pending.delete(cancel);\n cancel();\n }\n });\n }\n const childClosed = new Promise<void>((resolve) => child.once(\"close\", () => resolve()));\n let cleanup: Promise<void> | undefined;\n\n function finish(error?: unknown): Promise<void> {\n if (cleanup) return cleanup;\n stopped = true;\n for (const cancel of pending) cancel();\n pending.clear();\n options.signal?.removeEventListener(\"abort\", onAbort);\n // Do not wait for a user iterator that ignores cancellation.\n try {\n void Promise.resolve(cancelInput?.()).catch(() => {});\n } catch {\n /* Best effort. */\n }\n for (const track of tracks) track.stop();\n tracks.clear();\n child.stdin.destroy();\n if (child.exitCode === null && child.signalCode === null) child.kill(\"SIGTERM\");\n cleanup = (async () => {\n const killTimer = setTimeout(() => {\n child.kill(\"SIGKILL\");\n }, 1_000);\n try {\n await Promise.all([\n childClosed,\n new Promise<void>((resolve) => socket.close(() => resolve())),\n ]);\n } finally {\n clearTimeout(killTimer);\n }\n })();\n void cleanup.then(() => (error === undefined ? resolveDone() : rejectDone(error)), rejectDone);\n return cleanup;\n }\n\n function onAbort() {\n void finish(options.signal?.reason ?? new Error(\"PCM audio source aborted\"));\n }\n socket.on(\"error\", (error) => {\n void finish(error);\n });\n child.stdin.on(\"error\", (error) => {\n if (!stopped) void finish(error);\n });\n child.stderr.on(\"data\", (chunk: Buffer) => {\n stderr = (stderr + chunk.toString()).slice(-8_192);\n });\n child.on(\"error\", (error) => {\n void finish(error);\n });\n child.on(\"close\", (code, signal) => {\n if (stopped) return;\n if (code === 0 && inputEnded) void finish();\n else void finish(new Error(`FFmpeg exited (${signal ?? code}): ${stderr.trim()}`));\n });\n socket.on(\"message\", (message, remote) => {\n if (stopped || remote.address !== \"127.0.0.1\" || message.length < 12) return;\n // Reject RTCP and malformed/non-RTP datagrams before parsing.\n if (message[0]! >>> 6 !== 2 || (message[1]! & 0x7f) !== 111) return;\n let packet: RtpPacket;\n try {\n packet = RtpPacket.deSerialize(message);\n } catch {\n return;\n }\n if (packet.header.payloadType !== 111) return;\n for (const track of tracks) {\n if (!track.stopped) track.writeRtp(packet);\n }\n });\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n if (options.signal?.aborted) onAbort();\n\n // Surface executable startup failures from the factory, not just done.\n try {\n await new Promise<void>((resolve, reject) => {\n child.once(\"spawn\", resolve);\n child.once(\"error\", reject);\n });\n if (stopped) {\n await done;\n throw new Error(\"PCM audio source closed during startup\");\n }\n } catch (error) {\n await finish(error);\n throw error;\n }\n\n async function pump() {\n let release: (() => void) | undefined;\n try {\n let iterator: AsyncIterator<Uint8Array>;\n if (\"getReader\" in input) {\n const reader = input.getReader();\n iterator = {\n next: async () => {\n const result = await reader.read();\n return result.done ? { done: true, value: undefined } : { done: false, value: result.value };\n },\n };\n cancelInput = () => reader.cancel();\n release = () => reader.releaseLock();\n } else {\n iterator = input[Symbol.asyncIterator]();\n cancelInput = () => iterator.return?.();\n }\n let bytes = 0;\n while (!stopped) {\n const result = await interruptible(iterator.next());\n if (!result || stopped) return;\n if (result.done) {\n if (bytes % 4 !== 0)\n throw new Error(\"PCM input ends with an incomplete stereo s16le sample\");\n inputEnded = true;\n child.stdin.end();\n return;\n }\n if (!(result.value instanceof Uint8Array))\n throw new TypeError(\"PCM chunks must be Uint8Array\");\n const chunk = result.value;\n bytes = (bytes + chunk.byteLength) % 4;\n for (let offset = 0; offset < chunk.byteLength && !stopped; offset += 16_384) {\n await interruptible(\n new Promise<void>((resolve, reject) => {\n child.stdin.write(chunk.subarray(offset, offset + 16_384), (error) =>\n error ? reject(error) : resolve(),\n );\n }),\n );\n }\n }\n } catch (error) {\n if (!stopped) void finish(error);\n } finally {\n // A cancelled read may still be pending on a non-cooperative stream.\n try {\n release?.();\n } catch {\n /* Cancellation owns the pending read. */\n }\n }\n }\n void pump();\n\n return {\n done,\n subscribe() {\n if (stopped) throw new Error(\"PCM audio source is closed\");\n const track = new MediaStreamTrack({ kind: \"audio\" });\n const stream = new MediaStream();\n stream.addTrack(track);\n tracks.add(track);\n return {\n stream,\n close() {\n tracks.delete(track);\n track.stop();\n },\n };\n },\n close: () => finish(),\n };\n}\n"],
5
- "mappings": "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",
6
- "names": ["spawn", "createSocket", "MediaStream", "MediaStreamTrack", "RtpPacket", "input", "options", "socket", "resolve", "reject", "error", "port", "child", "tracks", "stopped", "inputEnded", "stderr", "cancelInput", "resolveDone", "rejectDone", "done", "pending", "interruptible", "operation", "cancel", "value", "childClosed", "cleanup", "finish", "onAbort", "track", "killTimer", "chunk", "code", "signal", "message", "remote", "packet", "pump", "release", "iterator", "reader", "result", "bytes", "offset", "stream"]
4
+ "sourcesContent": ["import { spawn } from \"node:child_process\";\nimport { createSocket } from \"node:dgram\";\nimport { MediaStream, MediaStreamTrack, RtpPacket } from \"werift\";\nimport { RtpPacer } from \"./rtp-pacer.js\";\n\nexport interface PcmAudioSubscription {\n stream: MediaStream;\n close(): void;\n}\n\nexport interface PcmAudioSource {\n subscribe(): PcmAudioSubscription;\n close(): Promise<void>;\n /** Resolves on EOF or explicit close; rejects on input, socket, or encoder failure. */\n done: Promise<void>;\n}\n\nexport interface PcmAudioOptions {\n ffmpegPath?: string;\n /** Pace input with FFmpeg's -re (default: true). RTP is additionally paced when false. */\n realtime?: boolean;\n signal?: AbortSignal;\n}\n\n/**\n * Optional server-only helper. Requires an external FFmpeg executable with libopus\n * on PATH (or ffmpegPath). Input is raw 48 kHz, stereo, signed 16-bit little-endian\n * PCM; chunk boundaries need not align to samples. One input/encoder is shared,\n * paced by FFmpeg's -re by default, with no replay for late subscribers. RTP/RTCP stays on\n * IPv4 loopback. No peer connection or native wrtc dependency is created.\n *\n * Writes are bounded to 16 KiB, with one outstanding write and one input chunk.\n * Producers must bound their own chunks/queues. Closing requests iterator.return()\n * or reader.cancel(); arbitrary iterators must cooperate to release their resources\n * (a stuck next()/return() does not prevent this helper from closing).\n */\nexport async function createPcmAudioSource(\n input: AsyncIterable<Uint8Array> | ReadableStream<Uint8Array>,\n options: PcmAudioOptions = {},\n): Promise<PcmAudioSource> {\n options.signal?.throwIfAborted();\n const socket = createSocket(\"udp4\");\n try {\n await new Promise<void>((resolve, reject) => {\n socket.once(\"error\", reject);\n socket.bind(0, \"127.0.0.1\", () => {\n socket.removeListener(\"error\", reject);\n resolve();\n });\n });\n options.signal?.throwIfAborted();\n } catch (error) {\n try {\n socket.close();\n } catch {\n /* Bind may not have succeeded. */\n }\n throw error;\n }\n\n const port = socket.address().port;\n const child = spawn(\n options.ffmpegPath ?? \"ffmpeg\",\n [\n \"-hide_banner\",\n \"-loglevel\",\n \"error\",\n \"-nostdin\",\n ...(options.realtime === false ? [] : [\"-re\"]),\n \"-f\",\n \"s16le\",\n \"-ar\",\n \"48000\",\n \"-ac\",\n \"2\",\n \"-i\",\n \"pipe:0\",\n \"-c:a\",\n \"libopus\",\n \"-frame_duration\",\n \"20\",\n \"-ar\",\n \"48000\",\n \"-ac\",\n \"2\",\n \"-payload_type\",\n \"111\",\n \"-f\",\n \"rtp\",\n `rtp://127.0.0.1:${port}?rtcpport=${port}&pkt_size=1200`,\n ],\n { stdio: [\"pipe\", \"ignore\", \"pipe\"] },\n );\n\n const tracks = new Set<MediaStreamTrack>();\n let stopped = false;\n let inputEnded = false;\n let stderr = \"\";\n let cancelInput: (() => unknown) | undefined;\n let resolveDone!: () => void;\n let rejectDone!: (error: unknown) => void;\n const done = new Promise<void>((resolve, reject) => {\n resolveDone = resolve;\n rejectDone = reject;\n });\n // Callers may observe done later, or only call close().\n void done.catch(() => {});\n const pending = new Set<() => void>();\n function interruptible<T>(operation: PromiseLike<T>): Promise<T | undefined> {\n return new Promise((resolve, reject) => {\n const cancel = () => resolve(undefined);\n pending.add(cancel);\n void Promise.resolve(operation).then(\n (value) => {\n pending.delete(cancel);\n resolve(value);\n },\n (error) => {\n pending.delete(cancel);\n reject(error);\n },\n );\n if (stopped) {\n pending.delete(cancel);\n cancel();\n }\n });\n }\n const childClosed = new Promise<void>((resolve) => child.once(\"close\", () => resolve()));\n let cleanup: Promise<void> | undefined;\n let encoderEnded = false;\n let settleTimer: ReturnType<typeof setTimeout> | undefined;\n const pacer =\n options.realtime === false\n ? new RtpPacer<RtpPacket>(\n (packet) => {\n for (const track of tracks) if (!track.stopped) track.writeRtp(packet);\n },\n (error) => {\n void finish(error);\n },\n () => {\n if (encoderEnded && settleTimer === undefined) void finish();\n },\n )\n : undefined;\n\n function finish(error?: unknown): Promise<void> {\n if (cleanup) return cleanup;\n stopped = true;\n if (settleTimer !== undefined) clearTimeout(settleTimer);\n pacer?.stop();\n for (const cancel of pending) cancel();\n pending.clear();\n options.signal?.removeEventListener(\"abort\", onAbort);\n // Do not wait for a user iterator that ignores cancellation.\n try {\n void Promise.resolve(cancelInput?.()).catch(() => {});\n } catch {\n /* Best effort. */\n }\n for (const track of tracks) track.stop();\n tracks.clear();\n child.stdin.destroy();\n if (child.exitCode === null && child.signalCode === null) child.kill(\"SIGTERM\");\n cleanup = (async () => {\n const killTimer = setTimeout(() => {\n child.kill(\"SIGKILL\");\n }, 1_000);\n try {\n await Promise.all([\n childClosed,\n new Promise<void>((resolve) => socket.close(() => resolve())),\n ]);\n } finally {\n clearTimeout(killTimer);\n }\n })();\n void cleanup.then(() => (error === undefined ? resolveDone() : rejectDone(error)), rejectDone);\n return cleanup;\n }\n\n function onAbort() {\n void finish(options.signal?.reason ?? new Error(\"PCM audio source aborted\"));\n }\n socket.on(\"error\", (error) => {\n void finish(error);\n });\n child.stdin.on(\"error\", (error) => {\n if (!stopped) void finish(error);\n });\n child.stderr.on(\"data\", (chunk: Buffer) => {\n stderr = (stderr + chunk.toString()).slice(-8_192);\n });\n child.on(\"error\", (error) => {\n void finish(error);\n });\n child.on(\"close\", (code, signal) => {\n if (stopped) return;\n if (code === 0 && inputEnded) {\n if (!pacer) void finish();\n else {\n encoderEnded = true;\n // UDP delivery can lag behind the child process close event.\n settleTimer = setTimeout(() => {\n settleTimer = undefined;\n if (pacer.empty) void finish();\n }, 50);\n }\n } else void finish(new Error(`FFmpeg exited (${signal ?? code}): ${stderr.trim()}`));\n });\n socket.on(\"message\", (message, remote) => {\n if (stopped || remote.address !== \"127.0.0.1\" || message.length < 12) return;\n // Reject RTCP and malformed/non-RTP datagrams before parsing.\n if (message[0]! >>> 6 !== 2 || (message[1]! & 0x7f) !== 111) return;\n let packet: RtpPacket;\n try {\n packet = RtpPacket.deSerialize(message);\n } catch {\n return;\n }\n if (packet.header.payloadType !== 111) return;\n if (pacer) pacer.push(packet, packet.header.timestamp);\n else\n for (const track of tracks) {\n if (!track.stopped) track.writeRtp(packet);\n }\n });\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n if (options.signal?.aborted) onAbort();\n\n // Surface executable startup failures from the factory, not just done.\n try {\n await new Promise<void>((resolve, reject) => {\n child.once(\"spawn\", resolve);\n child.once(\"error\", reject);\n });\n if (stopped) {\n await done;\n throw new Error(\"PCM audio source closed during startup\");\n }\n } catch (error) {\n await finish(error);\n throw error;\n }\n\n async function pump() {\n let release: (() => void) | undefined;\n try {\n let iterator: AsyncIterator<Uint8Array>;\n if (\"getReader\" in input) {\n const reader = input.getReader();\n iterator = {\n next: async () => {\n const result = await reader.read();\n return result.done\n ? { done: true, value: undefined }\n : { done: false, value: result.value };\n },\n };\n cancelInput = () => reader.cancel();\n release = () => reader.releaseLock();\n } else {\n iterator = input[Symbol.asyncIterator]();\n cancelInput = () => iterator.return?.();\n }\n let bytes = 0;\n while (!stopped) {\n const result = await interruptible(iterator.next());\n if (!result || stopped) return;\n if (result.done) {\n if (bytes % 4 !== 0)\n throw new Error(\"PCM input ends with an incomplete stereo s16le sample\");\n inputEnded = true;\n child.stdin.end();\n return;\n }\n if (!(result.value instanceof Uint8Array))\n throw new TypeError(\"PCM chunks must be Uint8Array\");\n const chunk = result.value;\n bytes = (bytes + chunk.byteLength) % 4;\n for (let offset = 0; offset < chunk.byteLength && !stopped; offset += 16_384) {\n await interruptible(\n new Promise<void>((resolve, reject) => {\n child.stdin.write(chunk.subarray(offset, offset + 16_384), (error) =>\n error ? reject(error) : resolve(),\n );\n }),\n );\n }\n }\n } catch (error) {\n if (!stopped) void finish(error);\n } finally {\n // A cancelled read may still be pending on a non-cooperative stream.\n try {\n release?.();\n } catch {\n /* Cancellation owns the pending read. */\n }\n }\n }\n void pump();\n\n return {\n done,\n subscribe() {\n if (stopped) throw new Error(\"PCM audio source is closed\");\n const track = new MediaStreamTrack({ kind: \"audio\" });\n const stream = new MediaStream();\n stream.addTrack(track);\n tracks.add(track);\n return {\n stream,\n close() {\n tracks.delete(track);\n track.stop();\n },\n };\n },\n close: () => finish(),\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["spawn", "createSocket", "MediaStream", "MediaStreamTrack", "RtpPacket", "RtpPacer", "input", "options", "socket", "resolve", "reject", "error", "port", "child", "tracks", "stopped", "inputEnded", "stderr", "cancelInput", "resolveDone", "rejectDone", "done", "pending", "interruptible", "operation", "cancel", "value", "childClosed", "cleanup", "encoderEnded", "settleTimer", "pacer", "packet", "track", "finish", "onAbort", "killTimer", "chunk", "code", "signal", "message", "remote", "pump", "release", "iterator", "reader", "result", "bytes", "offset", "stream"]
7
7
  }
package/rtp-pacer.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ /** Bounded, monotonic RTP playout scheduler for a single 48 kHz stream. */
2
+ export declare class RtpPacer<T> {
3
+ private readonly send;
4
+ private readonly fail;
5
+ private readonly drained;
6
+ private readonly maxPackets;
7
+ private queue;
8
+ private timer;
9
+ private lastTimestamp;
10
+ private lastDeadline;
11
+ private lastSentAt;
12
+ private closed;
13
+ private wasDrained;
14
+ constructor(send: (packet: T) => void, fail: (error: Error) => void, drained: () => void, maxPackets?: number);
15
+ push(packet: T, timestamp: number): void;
16
+ stop(): void;
17
+ get empty(): boolean;
18
+ private schedule;
19
+ }
20
+ //# sourceMappingURL=rtp-pacer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rtp-pacer.d.ts","sourceRoot":"","sources":["../src/rtp-pacer.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,qBAAa,QAAQ,CAAC,CAAC;IAUnB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAZ7B,OAAO,CAAC,KAAK,CAA0C;IACvD,OAAO,CAAC,KAAK,CAA4C;IACzD,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;IAE3B,YACmB,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,EACzB,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,EAC5B,OAAO,EAAE,MAAM,IAAI,EACnB,UAAU,SAAM,EAC/B;IAEJ,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAQvC;IAED,IAAI,IAAI,IAAI,CAKX;IAED,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,OAAO,CAAC,QAAQ;CAsCjB"}
package/rtp-pacer.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";export class RtpPacer{send;fail;drained;maxPackets;queue=[];timer;lastTimestamp;lastDeadline=0;lastSentAt=0;closed=!1;wasDrained=!1;constructor(i,e,t,s=100){this.send=i,this.fail=e,this.drained=t,this.maxPackets=s}push(i,e){if(!this.closed){if(this.queue.length>=this.maxPackets){this.fail(new Error("PCM RTP pacing queue overflow"));return}this.queue.push({packet:i,timestamp:e}),this.schedule()}}stop(){this.closed=!0,this.timer!==void 0&&clearTimeout(this.timer),this.timer=void 0,this.queue=[]}get empty(){return this.queue.length===0}schedule(){if(this.closed||this.timer!==void 0||this.queue.length===0)return;const i=this.queue[0],e=this.lastTimestamp===void 0?0:i.timestamp-this.lastTimestamp|0;if(e<0||e>48e4){this.fail(new Error("Invalid PCM RTP timestamp discontinuity"));return}const t=performance.now(),s=e/48;let a=this.lastTimestamp===void 0?t:this.lastDeadline+s;this.queue.length===1&&this.timer===void 0&&this.wasDrained&&a<t&&(a=t),this.wasDrained=!1;const h=this.lastTimestamp===void 0?t:this.lastSentAt+s*.9,r=Math.max(0,Math.ceil(Math.max(a,h)-t));this.timer=setTimeout(()=>{if(this.timer=void 0,this.closed)return;const n=this.queue.shift();this.lastTimestamp=n.timestamp,this.lastDeadline=a,this.lastSentAt=performance.now(),this.send(n.packet),!this.closed&&(this.queue.length===0?(this.wasDrained=!0,this.drained()):this.schedule())},r)}}
2
+ //# sourceMappingURL=rtp-pacer.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/rtp-pacer.ts"],
4
+ "sourcesContent": ["/** Bounded, monotonic RTP playout scheduler for a single 48 kHz stream. */\nexport class RtpPacer<T> {\n private queue: { packet: T; timestamp: number }[] = [];\n private timer: ReturnType<typeof setTimeout> | undefined;\n private lastTimestamp: number | undefined;\n private lastDeadline = 0;\n private lastSentAt = 0;\n private closed = false;\n private wasDrained = false;\n\n constructor(\n private readonly send: (packet: T) => void,\n private readonly fail: (error: Error) => void,\n private readonly drained: () => void,\n private readonly maxPackets = 100,\n ) {}\n\n push(packet: T, timestamp: number): void {\n if (this.closed) return;\n if (this.queue.length >= this.maxPackets) {\n this.fail(new Error(\"PCM RTP pacing queue overflow\"));\n return;\n }\n this.queue.push({ packet, timestamp });\n this.schedule();\n }\n\n stop(): void {\n this.closed = true;\n if (this.timer !== undefined) clearTimeout(this.timer);\n this.timer = undefined;\n this.queue = [];\n }\n\n get empty(): boolean {\n return this.queue.length === 0;\n }\n\n private schedule(): void {\n if (this.closed || this.timer !== undefined || this.queue.length === 0) return;\n const next = this.queue[0]!;\n // Signed modular difference accepts the normal 32-bit wrap, but rejects\n // backwards timestamps and discontinuities too large to schedule safely.\n const delta = this.lastTimestamp === undefined ? 0 : (next.timestamp - this.lastTimestamp) | 0;\n if (delta < 0 || delta > 480_000) {\n this.fail(new Error(\"Invalid PCM RTP timestamp discontinuity\"));\n return;\n }\n const now = performance.now();\n const interval = delta / 48;\n let deadline = this.lastTimestamp === undefined ? now : this.lastDeadline + interval;\n // An idle stream has no backlog to recover. Reset its phase, but keep the\n // minimum spacing from the previous send when packets resume quickly.\n if (this.queue.length === 1 && this.timer === undefined && this.wasDrained && deadline < now) {\n deadline = now;\n }\n this.wasDrained = false;\n // Recover lost time gradually, at no more than 1/0.9 of the media rate.\n // Keep the original media deadline so repeated stalls do not accumulate.\n const earliest = this.lastTimestamp === undefined ? now : this.lastSentAt + interval * 0.9;\n const delay = Math.max(0, Math.ceil(Math.max(deadline, earliest) - now));\n this.timer = setTimeout(() => {\n this.timer = undefined;\n if (this.closed) return;\n const item = this.queue.shift()!;\n this.lastTimestamp = item.timestamp;\n this.lastDeadline = deadline;\n this.lastSentAt = performance.now();\n this.send(item.packet);\n if (this.closed) return;\n if (this.queue.length === 0) {\n this.wasDrained = true;\n this.drained();\n } else this.schedule();\n }, delay);\n }\n}\n"],
5
+ "mappings": "aACM,aAAO,QAAQ,CAUA,KACA,KACA,QACA,WAZX,MAA4C,CAAA,EAC5C,MACA,cACA,aAAe,EACf,WAAa,EACb,OAAS,GACT,WAAa,GAErB,YACmBA,EACAC,EACAC,EACAC,EAAa,IAAG,WAHhBH,YACAC,eACAC,kBACAC,CAChB,CAEH,KAAKC,EAAWC,EAAiB,CAC/B,GAAI,MAAK,OACT,IAAI,KAAK,MAAM,QAAU,KAAK,WAAY,CACxC,KAAK,KAAK,IAAI,MAAM,+BAA+B,CAAC,EACpD,MACF,CACA,KAAK,MAAM,KAAK,CAAE,OAAAD,EAAQ,UAAAC,CAAS,CAAE,EACrC,KAAK,SAAQ,EACf,CAEA,MAAI,CACF,KAAK,OAAS,GACV,KAAK,QAAU,QAAW,aAAa,KAAK,KAAK,EACrD,KAAK,MAAQ,OACb,KAAK,MAAQ,CAAA,CACf,CAEA,IAAI,OAAK,CACP,OAAO,KAAK,MAAM,SAAW,CAC/B,CAEQ,UAAQ,CACd,GAAI,KAAK,QAAU,KAAK,QAAU,QAAa,KAAK,MAAM,SAAW,EAAG,OACxE,MAAMC,EAAO,KAAK,MAAM,CAAC,EAGnBC,EAAQ,KAAK,gBAAkB,OAAY,EAAKD,EAAK,UAAY,KAAK,cAAiB,EAC7F,GAAIC,EAAQ,GAAKA,EAAQ,KAAS,CAChC,KAAK,KAAK,IAAI,MAAM,yCAAyC,CAAC,EAC9D,MACF,CACA,MAAMC,EAAM,YAAY,IAAG,EACrBC,EAAWF,EAAQ,GACzB,IAAIG,EAAW,KAAK,gBAAkB,OAAYF,EAAM,KAAK,aAAeC,EAGxE,KAAK,MAAM,SAAW,GAAK,KAAK,QAAU,QAAa,KAAK,YAAcC,EAAWF,IACvFE,EAAWF,GAEb,KAAK,WAAa,GAGlB,MAAMG,EAAW,KAAK,gBAAkB,OAAYH,EAAM,KAAK,WAAaC,EAAW,GACjFG,EAAQ,KAAK,IAAI,EAAG,KAAK,KAAK,KAAK,IAAIF,EAAUC,CAAQ,EAAIH,CAAG,CAAC,EACvE,KAAK,MAAQ,WAAW,IAAK,CAE3B,GADA,KAAK,MAAQ,OACT,KAAK,OAAQ,OACjB,MAAMK,EAAO,KAAK,MAAM,MAAK,EAC7B,KAAK,cAAgBA,EAAK,UAC1B,KAAK,aAAeH,EACpB,KAAK,WAAa,YAAY,IAAG,EACjC,KAAK,KAAKG,EAAK,MAAM,EACjB,MAAK,SACL,KAAK,MAAM,SAAW,GACxB,KAAK,WAAa,GAClB,KAAK,QAAO,GACP,KAAK,SAAQ,EACtB,EAAGD,CAAK,CACV",
6
+ "names": ["send", "fail", "drained", "maxPackets", "packet", "timestamp", "next", "delta", "now", "interval", "deadline", "earliest", "delay", "item"]
7
+ }
package/webrtc.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"webrtc.d.ts","sourceRoot":"","sources":["../src/webrtc.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"webrtc.d.ts","sourceRoot":"","sources":["../src/webrtc.ts"],"names":[],"mappings":"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"}
package/webrtc.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";import{MAX_SIGNALING_BODY_BYTES as U}from"./types/types.js";import{abortError as k,decodeWireBody as G,encodeWireBody as $,errorFromUnknown as x}from"./utils/helper.js";import{createServer as F}from"node:http";import{MediaStream as J,RTCPeerConnection as H}from"werift";function B(P){return(P??[]).flatMap(t=>{const r=Array.isArray(t.urls)?t.urls:[t.urls];if(t.credential!==void 0&&typeof t.credential!="string")throw new TypeError("Werift currently requires ICE server credentials to be strings.");return r.map(n=>({urls:n,...t.username!==void 0?{username:t.username}:{},...t.credential!==void 0?{credential:t.credential}:{}}))})}function z(P){const e={iceServers:B(P.iceServers),...P.iceTransportPolicy!==void 0?{iceTransportPolicy:P.iceTransportPolicy}:{}};return new H(e)}function _(){return new J}export class WebRtcTransport{sessions=new Map;connecting=new Map;serverSessions=new Map;sessionListeners=new Set;closingServerSessions=new Map;trackListeners=new Set;serverClosers=new Set;requestSequence=0;closing=!1;pendingPeers=new Set;iceServers;iceGatheringTimeoutMs;signalingTimeoutMs;connectionTimeoutMs;requestTimeoutMs;createMediaStream;createPeerConnection;options;constructor(e={}){this.options=e,this.createPeerConnection=e.createPeerConnection??z,this.createMediaStream=e.createMediaStream??_,this.iceServers=e.iceServers??[{urls:"stun:stun.l.google.com:19302"}],this.iceGatheringTimeoutMs=e.iceGatheringTimeoutMs??1e4,this.signalingTimeoutMs=e.signalingTimeoutMs??1e4,this.connectionTimeoutMs=e.connectionTimeoutMs??15e3,this.requestTimeoutMs=e.requestTimeoutMs??15e3}log(e,t,r){if(this.options.logger){this.options.logger(e,t,r);return}if(!this.options.verbose&&e!=="error")return;const n=e==="debug"?console.debug:console[e];r===void 0?n(t):n(t,r)}onTrack(e){return this.trackListeners.add(e),()=>this.trackListeners.delete(e)}onServerSession(e){return this.sessionListeners.add(e),()=>this.sessionListeners.delete(e)}emitServerSession(e,t){for(const r of this.sessionListeners)try{r({...e.context,type:t})}catch(n){this.log("error","WebRTC session listener failed",n)}}getServerSession(e){const t=this.serverSessions.get(e);if(!(!t||t.closed))return{id:t.id,context:t.context,peerConnection:t.peerConnection,remoteStream:t.remoteStream,...t.localStream?{localStream:t.localStream}:{}}}getSession(e){const t=this.sessions.get(e);if(!(!t||t.closed))return{id:t.id,peerUrl:t.peerUrl,peerConnection:t.peerConnection,dataChannel:t.dataChannel,remoteStream:t.remoteStream,media:t.mediaController,...t.localStream?{localStream:t.localStream}:{}}}async execute(e){if(this.closing)throw new Error("WebRTC transport is closed");const t=e,r=t.peerUrl??e.url;if(e.signal?.aborted)throw k(e.signal);const n=await this.getOrCreateSession(r,t);if(await this.ensureMedia(n,t),e.signal?.aborted)throw k(e.signal);return this.sendRequest(n,e,t.timeout)}async listen(e){if(this.closing)throw new Error("WebRTC transport is closed");const t=new Set,r=e.port??3e3,n=e.host??"127.0.0.1",i=e.signaling?.path??"/signal";if(!i.startsWith("/")||i.startsWith("//")||/[?#\\\s]/u.test(i)||Array.from(i).some(c=>{const h=c.codePointAt(0);return h<=31||h>=127&&h<=159})||new URL(i,"http://localhost").pathname!==i)throw new TypeError("WebRTC signaling path must be an absolute literal pathname without query or fragment");if(e.signal?.aborted)throw new Error("WebRtcTransport: server startup aborted");let s=!1,a;const l=new Set,g=F((c,h)=>{(async()=>{const d=(o,p)=>{h.headersSent||h.destroyed||(h.writeHead(o,{"content-type":"application/json","cache-control":"no-store"}),h.end(JSON.stringify(p)))};if(s||this.closing){d(503,{error:"WebRTC server is closing"});return}let T;try{T=new URL(c.url??"/","http://localhost")}catch{d(400,{error:"Invalid request URL"});return}if(T.pathname!==i){d(404,{error:"Signaling endpoint not found"});return}let w,M;if(["POST","DELETE","GET","PATCH"].includes(c.method??"")&&e.signaling?.authorize){const o=new AbortController,p=()=>o.abort(),f=()=>{d(503,{error:"WebRTC signaling request aborted"})};c.once("aborted",p),h.once("close",p),o.signal.addEventListener("abort",f,{once:!0}),l.add(o);try{const y={};for(const[R,b]of Object.entries(c.headers))b!==void 0&&(y[R]=b);const C=await e.signaling.authorize({protocol:"webrtc",method:c.method,url:c.url??"/",headers:y,signal:o.signal});if(o.signal.aborted||c.aborted||h.destroyed)return;if(s||this.closing||e.signal?.aborted){d(503,{error:"WebRTC server is closing"});return}if(typeof C=="object"&&C!==null){if(typeof C.ownerId!="string"||!C.ownerId.trim()){d(403,{error:"Invalid signaling identity"});return}w=C.ownerId,M=C.context}else if(C!==!0){d(403,{error:"Signaling forbidden"});return}}catch{d(403,{error:"Signaling forbidden"});return}finally{l.delete(o),c.off("aborted",p),h.off("close",p),o.signal.removeEventListener("abort",f)}}if(c.method==="DELETE"){const o=T.searchParams.get("sessionId"),p=o?this.serverSessions.get(o):void 0;if(p&&(!t.has(o)||p.context.ownerId!==w)){d(403,{error:"Signaling forbidden"});return}o&&t.has(o)&&(await this.closeServerSession(o),t.delete(o)),h.writeHead(204).end();return}if(c.method==="GET"){const o=T.searchParams.get("sessionId"),p=o?this.serverSessions.get(o):void 0;if(!p||!o||!t.has(o)){d(404,{error:"Session not found"});return}if(p.context.ownerId!==w){d(403,{error:"Signaling forbidden"});return}const f=p.trickle;if(!f||T.searchParams.get("generation")!==f.generation){d(409,{error:"ICE generation mismatch"});return}const y=T.searchParams.get("cursor")??"0";if(!/^(0|[1-9][0-9]*)$/.test(y)||!Number.isSafeInteger(Number(y))||Number(y)>f.candidates.length){d(400,{error:"Invalid ICE cursor"});return}d(200,{generation:f.generation,candidates:f.candidates.slice(Number(y)),next:f.candidates.length});return}if(c.method!=="POST"&&c.method!=="PATCH"){d(405,{error:"Method not allowed"});return}let W="";try{for await(const o of c)if(W+=Buffer.isBuffer(o)?o.toString("utf8"):String(o),Buffer.byteLength(W,"utf8")>U){d(413,{error:"Signaling payload too large"});return}}catch(o){d(400,{error:x(o,"Failed to read body").message});return}let L,u;try{const o=JSON.parse(W);if(c.method==="PATCH"){if(!o||typeof o.sessionId!="string"||typeof o.generation!="string"||!("candidate"in o)){d(400,{error:"Expected sessionId, generation and candidate"});return}const E=this.serverSessions.get(o.sessionId);if(!E||!t.has(o.sessionId)){d(404,{error:"Session not found"});return}if(E.context.ownerId!==w){d(403,{error:"Signaling forbidden"});return}const I=E.trickle;if(!I||I.generation!==o.generation){d(409,{error:"ICE generation mismatch"});return}const v=o.candidate;if(v!==null&&(typeof v!="object"||typeof v.candidate!="string"||v.candidate.length<1||v.candidate.length>4096||v.sdpMid!=null&&(typeof v.sdpMid!="string"||v.sdpMid.length>256)||v.sdpMLineIndex!=null&&(!Number.isInteger(v.sdpMLineIndex)||v.sdpMLineIndex<0||v.sdpMLineIndex>65535)||v.sdpMid==null&&v.sdpMLineIndex==null||v.usernameFragment!=null&&(typeof v.usernameFragment!="string"||v.usernameFragment.length>256))){d(400,{error:"Invalid ICE candidate"});return}if(I.remoteEnded||I.remoteCount>=256){d(429,{error:"ICE candidate limit reached or gathering ended"});return}I.remoteCount++,v===null&&(I.remoteEnded=!0);const D=I.remoteQueue.then(async()=>{if(E.closed||E.trickle!==I)throw new Error("ICE generation expired");await E.peerConnection.addIceCandidate(v)});I.remoteQueue=D.catch(()=>{});try{await D,h.writeHead(204).end()}catch{d(400,{error:"Failed to apply ICE candidate"})}return}if(!o||o.trickle!==void 0&&typeof o.trickle!="boolean"){d(400,{error:"Invalid trickle flag"});return}if(typeof o.sessionId!="string"||o.sessionId.length<1||o.sessionId.length>200||o.sdp?.type!=="offer"||typeof o.sdp.sdp!="string"){d(400,{error:"Expected sessionId and a valid SDP offer"});return}if(s||this.closing||e.signal?.aborted||c.aborted||h.destroyed)throw new Error("WebRTC signaling request aborted");const p=o.sessionId;if(u=this.serverSessions.get(p),u&&!t.has(p)){d(409,{error:"Session belongs to another listener"});return}if(u&&u.context.ownerId!==w){d(403,{error:"Signaling forbidden"});return}if(this.closingServerSessions.has(p)){d(409,{error:"Session is closing"});return}if(u?.negotiating||u?.trickle?.gathering){d(409,{error:"Session negotiation already in progress"});return}if(!u||u.closed){L=this.createPeerConnection({iceServers:this.iceServers});const E=new AbortController;u={controller:E,context:Object.freeze({sessionId:p,ownerId:w,authorization:M,signal:E.signal}),release:()=>t.delete(p),id:p,peerConnection:L,remoteStream:this.createMediaStream(),localTrackIds:new Set,localStreamResolved:!1,ownsLocalStream:!1,closed:!1},t.add(p),this.serverSessions.set(p,u),this.attachServerPeerEvents(u,e.onRequest),this.emitServerSession(u,"created")}else L=u.peerConnection;u.trickle?.detach(),delete u.trickle,u.negotiating=!0;const f=u,y=()=>{h.writableEnded||this.closeServerSession(f.id)};h.once("close",y);const C=()=>d(503,{error:"WebRTC session closed"});if(f.controller.signal.addEventListener("abort",C,{once:!0}),h.once("finish",()=>{f.controller.signal.removeEventListener("abort",C),h.off("close",y)}),await this.setRemoteDescription(L,o.sdp),await this.attachConfiguredServerStream(u),u.closed)throw new Error("WebRTC session closed");const R=await L.createAnswer();let b;if(o.trickle===!0){const E=L,I=N=>{if(!(!b||f.closed||f.trickle!==b)){if(b.candidates.length>=256){this.closeServerSession(f.id);return}b.candidates.push(N.candidate?N.candidate.toJSON():null)}};E.addEventListener("icecandidate",I),b={generation:crypto.randomUUID(),candidates:[],gathering:!0,remoteCount:0,remoteEnded:!1,remoteQueue:Promise.resolve(),detach:()=>E.removeEventListener("icecandidate",I)},u.trickle=b;const v=this.withDeadline(E.setLocalDescription(R),this.iceGatheringTimeoutMs,u.controller.signal);let D;try{await this.withDeadline(new Promise((N,O)=>{const j=()=>{if(f.closed){O(new Error("WebRTC session closed"));return}if(E.signalingState==="stable"&&E.localDescription?.type==="answer"){N();return}D=setTimeout(j,5)};v.catch(O),j()}),this.iceGatheringTimeoutMs,u.controller.signal)}finally{clearTimeout(D)}v.then(()=>{b&&f.trickle===b&&(b.gathering=!1,b.candidates.includes(null)||b.candidates.push(null))},()=>{f.trickle===b&&this.closeServerSession(f.id)})}else if(await this.withDeadline(L.setLocalDescription(R),this.iceGatheringTimeoutMs),!await this.waitForIceGatheringComplete(L,this.iceGatheringTimeoutMs))throw new Error("Full ICE gathering timed out; trickle ICE is not supported");const A=L.localDescription;if(!A?.sdp||A.type!=="answer")throw new Error("Local SDP answer was not generated");if(s||u.closed)throw new Error("WebRTC session closed");u.negotiating=!1,d(200,b?{sdp:A,generation:b.generation}:A)}catch(o){if(this.log("error","[WebRtcTransport Server] \u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u0438\u0433\u043D\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u0438:",o),u)await this.closeServerSession(u.id);else if(L)try{await L.close()}catch{}d(400,{error:x(o,"Signaling failed").message})}})()});await new Promise((c,h)=>{const d=w=>{g.off("listening",T),h(w)},T=()=>{g.off("error",d),c()};g.once("error",d),g.once("listening",T),g.listen(r,n)}),this.log("info",`[WebRtcTransport] Listening on http://${n}:${r}${i}`);const S=()=>{m().catch(c=>{this.log("error","[WebRtcTransport] Server shutdown failed:",c)})},m=()=>{if(a)return a;s=!0;for(const c of l)c.abort();return e.signal?.removeEventListener("abort",S),a=(async()=>{const c=g.listening?new Promise((h,d)=>{g.close(T=>T?d(T):h())}):Promise.resolve();await Promise.all([...t].map(h=>this.closeServerSession(h))),t.clear(),await c,this.serverClosers.delete(m)})(),a};return e.signal?.addEventListener("abort",S,{once:!0}),this.serverClosers.add(m),e.signal?.aborted&&!s&&await m(),{close:m,webrtc:{getSession:this.getSession.bind(this),getServerSession:this.getServerSession.bind(this),onTrack:this.onTrack.bind(this),closeServerSession:c=>t.has(c)?this.closeServerSession(c):Promise.resolve(),onServerSession:this.onServerSession.bind(this)}}}async close(){this.closing=!0,await Promise.all([...this.pendingPeers].map(e=>e.close().catch(()=>{}))),await Promise.allSettled(this.connecting.values()),await Promise.all([...this.sessions.values()].map(e=>this.disposeClientSession(e,!0))),await Promise.all([...this.serverClosers].map(e=>e()))}async destroy(){await this.close()}async getOrCreateSession(e,t){const r=this.sessions.get(e);if(r&&!r.closed&&r.dataChannel.readyState==="open"&&r.peerConnection.connectionState!=="failed"&&r.peerConnection.connectionState!=="closed")return r;r&&await this.disposeClientSession(r,!0);const n=this.connecting.get(e);if(n)return n;const i=this.createClientSession(e,t);this.connecting.set(e,i);try{const s=await i;if(this.closing||s.closed)throw await this.disposeClientSession(s,!0),new Error("WebRTC transport is closed");return this.sessions.set(e,s),s}finally{this.connecting.get(e)===i&&this.connecting.delete(e)}}async createClientSession(e,t){const r=this.toSignalingUrl(e),n=this.createId("session"),i=this.createPeerConnection({iceServers:t.iceServers??this.iceServers});this.pendingPeers.add(i);const s=i.createDataChannel(t.channelLabel??"hyperttp-webrtc-transport",{ordered:!0}),a={id:n,peerUrl:e,signalingUrl:r,peerConnection:i,dataChannel:s,remoteStream:this.createMediaStream(),...t.localStream?{localStream:t.localStream}:{},localTrackIds:new Set,pending:new Map,negotiation:Promise.resolve(),closed:!1,mediaController:{}},l=new Set,g=new Set,S=new Set;i.addEventListener("track",m=>{const{track:c}=m;a.remoteStream.getTracks().includes(c)||a.remoteStream.addTrack(c);for(const h of l)h({track:c,stream:a.remoteStream});for(const h of S)h({stream:a.remoteStream});c.addEventListener("ended",()=>{for(const h of g)h({track:c})})}),a.mediaController={localStream:a.localStream,remoteStream:a.remoteStream,addTrack:(m,c)=>this.queueNegotiation(a,async()=>{i.addTrack(m,c??a.localStream??this.createMediaStream()),m.id&&a.localTrackIds.add(m.id),await this.negotiate(a)}),replaceTrack:async(m,c)=>{const h=i.getSenders().find(d=>d.track&&d.track.kind===m);h&&await h.replaceTrack(c)},removeTrack:m=>this.queueNegotiation(a,async()=>{const c=i.getSenders().find(h=>h.track===m);c&&(i.removeTrack(c),m.id&&a.localTrackIds.delete(m.id),await this.negotiate(a))}),close:async()=>{await this.disposeClientSession(a,!0)},on(m,c){return m==="track"&&l.add(c),m==="trackEnded"&&g.add(c),m==="stream"&&S.add(c),()=>{l.delete(c),g.delete(c),S.delete(c)}}};try{return this.attachClientPeerEvents(a),this.addLocalTracks(a,t.localStream),this.addReceiveTransceivers(a,t.receiveAudio,t.receiveVideo),await this.negotiate(a,t.signal),await this.waitForChannelOpen(s,this.connectionTimeoutMs,t.signal),a}catch(m){throw await this.disposeClientSession(a,!0),m}finally{this.pendingPeers.delete(i)}}attachClientPeerEvents(e){const{peerConnection:t,dataChannel:r}=e;t.addEventListener("track",n=>{const i=n.track;e.remoteStream.getTracks().some(s=>s.id===i.id)||e.remoteStream.addTrack(i),this.emitTrack({side:"client",sessionId:e.id,peerUrl:e.peerUrl,peerConnection:t,track:i,stream:e.remoteStream,streams:n.streams})}),t.addEventListener("connectionstatechange",()=>{(t.connectionState==="failed"||t.connectionState==="closed")&&this.disposeClientSession(e,!0)}),r.addEventListener("message",n=>{let i;try{i=JSON.parse(String(n.data))}catch{return}if(!i||typeof i!="object"||typeof i.requestId!="string")return;const s=e.pending.get(i.requestId);if(s)try{const a=G(i.body);this.finishPending(e,i.requestId),s.resolve({status:typeof i.status=="number"?i.status:200,statusText:typeof i.statusText=="string"?i.statusText:"OK",headers:i.headers&&typeof i.headers=="object"?i.headers:{},body:a,url:typeof i.url=="string"?i.url:e.peerUrl})}catch(a){this.finishPending(e,i.requestId),s.reject(x(a,"Invalid WebRTC response"))}}),r.addEventListener("close",()=>{this.rejectAllPending(e,new Error("WebRTC DataChannel closed")),this.disposeClientSession(e,!0)}),r.addEventListener("error",()=>{this.rejectAllPending(e,new Error("WebRTC DataChannel error"))})}queueNegotiation(e,t){const r=e.negotiation.catch(()=>{}).then(async()=>{if(e.closed)throw new Error("WebRTC session closed");try{await t()}catch(n){throw await this.disposeClientSession(e,!0),n}});return e.negotiation=r,r}async ensureMedia(e,t){!(t.localStream||t.receiveAudio||t.receiveVideo)||e.closed||await this.queueNegotiation(e,async()=>{let n=this.addLocalTracks(e,t.localStream);n=this.addReceiveTransceivers(e,t.receiveAudio,t.receiveVideo)||n,t.localStream&&(e.localStream=t.localStream,e.mediaController.localStream=t.localStream),n&&await this.negotiate(e,t.signal)})}addLocalTracks(e,t){if(!t)return!1;let r=!1;for(const n of t.getTracks())n.readyState!=="live"||e.localTrackIds.has(n.id)||(e.peerConnection.addTrack(n,t),e.localTrackIds.add(n.id),r=!0);return r}addReceiveTransceivers(e,t,r){const n=e.peerConnection;let i=!1;const s=(a,l)=>{if(!l)return;n.getTransceivers().find(S=>S.receiver.track.kind===a&&(S.direction==="recvonly"||S.direction==="sendrecv"))||(n.addTransceiver(a,{direction:"recvonly"}),i=!0)};return s("audio",t),s("video",r),i}async negotiate(e,t){if(t?.aborted)throw k(t);const r=e.peerConnection;if(r.signalingState!=="stable")throw new Error(`Cannot negotiate WebRTC session in state ${r.signalingState}`);if(this.options.trickleIce)return this.negotiateTrickle(e,t);const n=await r.createOffer();await this.withDeadline(r.setLocalDescription(n),this.iceGatheringTimeoutMs,t),await this.waitForIceGatheringComplete(r,this.iceGatheringTimeoutMs);const i=r.localDescription;if(!i?.sdp||i.type!=="offer")throw new Error("Local SDP offer was not generated");const s=new AbortController,a=setTimeout(()=>s.abort(),this.signalingTimeoutMs),l=()=>s.abort(t?.reason);t?.addEventListener("abort",l,{once:!0}),t?.aborted&&l();try{const g=await fetch(e.signalingUrl,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({sessionId:e.id,sdp:i}),signal:s.signal});if(!g.ok){const m=await g.text();throw new Error(`Signaling HTTP error: ${g.status} ${g.statusText}${m?` \u2014 ${m}`:""}`)}const S=await g.json();if(S.type!=="answer"||typeof S.sdp!="string")throw new Error("Signaling server returned an invalid SDP answer");await this.setRemoteDescription(r,S)}catch(g){throw t?.aborted?k(t):s.signal.aborted?new Error(`Signaling timed out after ${this.signalingTimeoutMs} ms`):x(g,"WebRTC signaling failed")}finally{clearTimeout(a),t?.removeEventListener("abort",l)}}async negotiateTrickle(e,t){const r=e.peerConnection,n=new AbortController;e.trickleAbort=n;const i=()=>n.abort(t?.reason);t?.addEventListener("abort",i,{once:!0}),t?.aborted&&i();const s=[];let a=!1,l,g;const S=h=>{if(a||n.signal.aborted)return;const d=h.candidate?h.candidate.toJSON():null;d?.candidate!==""&&(s.length>=256||!this.validTrickleCandidate(d)?g=new Error("Invalid or excessive local ICE candidates"):(s.push(d),d===null&&(a=!0)),l?.())};r.addEventListener("icecandidate",S);let m;const c=setTimeout(()=>n.abort(new Error("Trickle ICE timed out")),this.iceGatheringTimeoutMs+this.signalingTimeoutMs);try{const h=await this.withDeadline(r.createOffer(),this.signalingTimeoutMs,n.signal),d=this.withDeadline(r.setLocalDescription(h),this.iceGatheringTimeoutMs,n.signal);await this.withDeadline(new Promise((u,o)=>{const p=()=>{if(r.signalingState==="have-local-offer"&&r.localDescription?.sdp)return u();m=setTimeout(p,5)};d.catch(o),p()}),this.iceGatheringTimeoutMs,n.signal),clearTimeout(m);const T=await this.withDeadline(fetch(e.signalingUrl,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({sessionId:e.id,sdp:r.localDescription,trickle:!0}),signal:n.signal}),this.signalingTimeoutMs,n.signal);if(!T.ok)throw new Error(`Signaling HTTP error: ${T.status} ${T.statusText}`);const w=await this.withDeadline(T.json(),this.signalingTimeoutMs,n.signal);if(!w||typeof w!="object"||!("generation"in w)||typeof w.generation!="string"||!w.generation||w.generation.length>256||!("sdp"in w)||!w.sdp||typeof w.sdp!="object"||!("type"in w.sdp)||w.sdp.type!=="answer"||!("sdp"in w.sdp)||typeof w.sdp.sdp!="string"||!w.sdp.sdp)throw new Error("Signaling server returned an invalid trickle answer");const M=w.generation;await this.withDeadline(this.setRemoteDescription(r,{type:"answer",sdp:w.sdp.sdp}),this.signalingTimeoutMs,n.signal);const W=async()=>{let u=0;for(;;){if(n.signal.aborted)throw k(n.signal);if(g)throw g;if(u===s.length){await new Promise((f,y)=>{const C=()=>{l=void 0,y(k(n.signal))};l=()=>{n.signal.removeEventListener("abort",C),l=void 0,f()},n.signal.addEventListener("abort",C,{once:!0}),n.signal.aborted&&C()});continue}const o=s[u++],p=await fetch(e.signalingUrl,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({sessionId:e.id,generation:M,candidate:o}),signal:n.signal});if(p.status!==204)throw new Error(`ICE candidate HTTP error: ${p.status}`);if(o===null)return}},L=async()=>{let u=0;for(;;){const o=new URL(e.signalingUrl);o.searchParams.set("sessionId",e.id),o.searchParams.set("generation",M),o.searchParams.set("cursor",String(u));const p=await fetch(o,{signal:n.signal});if(!p.ok)throw new Error(`ICE polling HTTP error: ${p.status}`);const f=await p.json();if(!f||typeof f!="object"||!("generation"in f)||f.generation!==M||!("candidates"in f)||!Array.isArray(f.candidates)||!("next"in f)||!Number.isInteger(f.next)||f.next!==u+f.candidates.length||f.next>256||!f.candidates.every(y=>this.validTrickleCandidate(y))||f.candidates.slice(0,-1).includes(null))throw new Error("Signaling server returned an invalid ICE candidate page");for(const y of f.candidates)if(await this.withDeadline(r.addIceCandidate(y),this.iceGatheringTimeoutMs,n.signal),y===null)return;u=f.next,f.candidates.length||await new Promise((y,C)=>{const R=setTimeout(()=>{n.signal.removeEventListener("abort",b),y()},50),b=()=>{clearTimeout(R),C(k(n.signal))};n.signal.addEventListener("abort",b,{once:!0}),n.signal.aborted&&b()})}};await Promise.all([d.then(()=>{!a&&!g&&(s.length>=256?g=new Error("Invalid or excessive local ICE candidates"):(s.push(null),a=!0),l?.())}),W(),L()])}finally{n.abort(),clearTimeout(c),clearTimeout(m),r.removeEventListener("icecandidate",S),t?.removeEventListener("abort",i),e.trickleAbort===n&&delete e.trickleAbort}}validTrickleCandidate(e){if(e===null)return!0;if(!e||typeof e!="object")return!1;const t=e;return typeof t.candidate=="string"&&t.candidate.length>0&&t.candidate.length<=4096&&(t.sdpMid==null||typeof t.sdpMid=="string"&&t.sdpMid.length<=256)&&(t.sdpMLineIndex==null||Number.isInteger(t.sdpMLineIndex)&&t.sdpMLineIndex>=0&&t.sdpMLineIndex<=65535)&&(t.sdpMid!=null||t.sdpMLineIndex!=null)&&(t.usernameFragment==null||typeof t.usernameFragment=="string"&&t.usernameFragment.length<=256)}async setRemoteDescription(e,t){const r=new Set(e.dtlsTransports);try{await e.setRemoteDescription(t)}finally{const n=new Set(e.dtlsTransports);await Promise.all([...r].filter(i=>!n.has(i)).map(i=>i.stop()))}}async withDeadline(e,t,r){let n,i;try{return await Promise.race([e,new Promise((s,a)=>{n=setTimeout(()=>a(new Error(`WebRTC operation timed out after ${t} ms`)),t),i=()=>a(k(r)),r?.addEventListener("abort",i,{once:!0}),r?.aborted&&i()})])}finally{clearTimeout(n),i&&r?.removeEventListener("abort",i)}}async waitForIceGatheringComplete(e,t){if(e.iceGatheringState==="complete")return!0;if(!await new Promise(n=>{let i=!1,s;const a=()=>{clearTimeout(s),e.removeEventListener("icegatheringstatechange",g)},l=S=>{i||(i=!0,a(),n(S))},g=()=>{e.iceGatheringState==="complete"&&l(!0)};s=setTimeout(()=>l(!1),t),e.addEventListener("icegatheringstatechange",g),g()}))throw new Error(`ICE gathering timed out after ${t} ms`);return!0}async waitForChannelOpen(e,t,r){if(e.readyState!=="open"){if(r?.aborted)throw k(r);await new Promise((n,i)=>{let s=!1;const a=()=>{clearTimeout(h),e.removeEventListener("open",g),e.removeEventListener("error",S),e.removeEventListener("close",m),r?.removeEventListener("abort",c)},l=d=>{s||(s=!0,a(),d?i(d):n())},g=()=>l(),S=()=>l(new Error("WebRTC DataChannel failed to open")),m=()=>l(new Error("WebRTC DataChannel closed before opening")),c=()=>l(k(r)),h=setTimeout(()=>l(new Error(`DataChannel open timeout after ${t} ms`)),t);e.addEventListener("open",g),e.addEventListener("error",S),e.addEventListener("close",m),r?.addEventListener("abort",c,{once:!0}),e.readyState==="open"?l():e.readyState==="closed"&&l(new Error("WebRTC DataChannel is closed"))})}}sendRequest(e,t,r){if(t.signal?.aborted)return Promise.reject(k(t.signal));if(e.dataChannel.readyState!=="open")return Promise.reject(new Error("WebRTC DataChannel is not open"));const n=this.createId(`request${++this.requestSequence}`),i=r&&r>0?r:this.requestTimeoutMs;return new Promise((s,a)=>{const l={resolve:s,reject:a,timer:setTimeout(()=>{this.finishPending(e,n),a(new Error(`WebRTC request timed out after ${i} ms`))},i),...t.signal?{signal:t.signal}:{}};t.signal&&(l.abortListener=()=>{this.finishPending(e,n),a(k(t.signal))},t.signal.addEventListener("abort",l.abortListener,{once:!0})),e.pending.set(n,l);try{e.dataChannel.send(JSON.stringify({requestId:n,method:t.method,url:t.url,headers:t.headers,body:$(t.body),protocol:t.protocol}))}catch(g){this.finishPending(e,n),a(x(g,"Failed to send WebRTC request"))}})}finishPending(e,t){const r=e.pending.get(t);r&&(clearTimeout(r.timer),r.signal&&r.abortListener&&r.signal.removeEventListener("abort",r.abortListener),e.pending.delete(t))}rejectAllPending(e,t){for(const[r,n]of e.pending)this.finishPending(e,r),n.reject(t)}async disposeClientSession(e,t){if(!e.closed){e.closed=!0,e.trickleAbort?.abort(new Error("WebRTC session closed")),this.sessions.get(e.peerUrl)===e&&this.sessions.delete(e.peerUrl),this.rejectAllPending(e,new Error("WebRTC session closed"));try{e.dataChannel.close()}catch{}try{await e.peerConnection.close()}catch{}if(t)try{const r=new URL(e.signalingUrl);r.searchParams.set("sessionId",e.id);const n=new AbortController,i=setTimeout(()=>n.abort(),2e3);try{await fetch(r,{method:"DELETE",signal:n.signal})}finally{clearTimeout(i)}}catch{}}}attachServerPeerEvents(e,t){const r=e.peerConnection;r.addEventListener("datachannel",n=>{const i=n.channel;i.addEventListener("message",s=>{(async()=>{let a;try{const l=JSON.parse(String(s.data));if(a=typeof l.requestId=="string"?l.requestId:void 0,!a||typeof l.method!="string"||typeof l.url!="string")throw new Error("Invalid WebRTC transport request");const g={method:l.method,url:l.url,headers:l.headers&&typeof l.headers=="object"?{...l.headers,"x-webrtc-session-id":e.id}:{"x-webrtc-session-id":e.id},body:G(l.body),protocol:typeof l.protocol=="string"?l.protocol:"webrtc"};let S;if(t?S=await t(g):S={status:501,statusText:"Not Implemented",headers:{"content-type":"application/json"},body:{error:"No server request handler configured"},url:g.url},i.readyState!=="open")return;i.send(JSON.stringify({requestId:a,status:S.status,statusText:S.statusText??"",headers:S.headers,body:$(S.body),url:S.url??g.url}))}catch(l){if(this.log("error","[WebRtcTransport Server] Request failed:",l),a&&i.readyState==="open")try{i.send(JSON.stringify({requestId:a,status:500,statusText:"Internal Server Error",headers:{"content-type":"application/json"},body:{error:x(l,"Request handler failed").message}}))}catch{}}})()})}),r.addEventListener("track",n=>{const i=n.track;e.remoteStream.getTracks().some(s=>s.id===i.id)||e.remoteStream.addTrack(i),this.emitTrack({side:"server",sessionId:e.id,peerConnection:r,track:i,stream:e.remoteStream,streams:n.streams})}),r.addEventListener("connectionstatechange",()=>{(r.connectionState==="failed"||r.connectionState==="closed")&&this.closeServerSession(e.id)})}async attachConfiguredServerStream(e){if(e.closed)return;if(!e.localStreamResolved){const n=this.options.localStream,i=typeof n=="function",s=i?await n(e.id,e.context):n;if(e.closed){s&&i&&(e.localStream=s,e.ownsLocalStream=!0,this.disposeServerLocalStream(e));return}e.localStreamResolved=!0,s&&(e.localStream=s,e.ownsLocalStream=i)}const t=e.localStream;if(!t)return;const r=e.peerConnection;for(const n of t.getTracks())e.closed||n.readyState!=="live"||e.localTrackIds.has(n.id)||(r.addTrack(n,t),e.localTrackIds.add(n.id))}disposeServerLocalStream(e){const t=e.localStream,r=e.ownsLocalStream;if(delete e.localStream,e.ownsLocalStream=!1,!(!t||!r))try{this.options.disposeLocalStream?.(e.id,t)}catch{}finally{for(const n of t.getTracks())n.readyState!=="ended"&&n.stop()}}closeServerSession(e){const t=this.closingServerSessions.get(e);if(t)return t;const r=this.serverSessions.get(e);if(!r||r.closed)return Promise.resolve();r.closed=!0,r.trickle?.detach(),delete r.trickle,r.controller.abort(),r.release(),this.serverSessions.delete(e);const n=Promise.resolve().then(async()=>{try{await r.peerConnection.close()}catch(i){this.log("warn","WebRTC peer close failed",i)}finally{this.disposeServerLocalStream(r),this.emitServerSession(r,"closed")}}).finally(()=>this.closingServerSessions.delete(e));return this.closingServerSessions.set(e,n),n}emitTrack(e){for(const t of this.trackListeners)try{t(e)}catch(r){this.log("error","[WebRtcTransport] onTrack listener failed:",r)}}toSignalingUrl(e){if(e.startsWith("webrtcs://"))return e.replace(/^webrtcs:\/\//,"https://");if(e.startsWith("webrtc://"))return e.replace(/^webrtc:\/\//,"http://");throw new Error(`Unsupported WebRTC signaling URL: ${e}`)}createId(e){return`${e}-${crypto.randomUUID()}`}}
1
+ "use strict";import{MAX_SIGNALING_BODY_BYTES as U}from"./types/types.js";import{abortError as P,decodeWireBody as G,encodeWireBody as $,errorFromUnknown as N}from"./utils/helper.js";import{createServer as F}from"node:http";import{MediaStream as J,RTCPeerConnection as H}from"werift";function B(R){return(R??[]).flatMap(t=>{const r=Array.isArray(t.urls)?t.urls:[t.urls];if(t.credential!==void 0&&typeof t.credential!="string")throw new TypeError("Werift currently requires ICE server credentials to be strings.");return r.map(n=>({urls:n,...t.username!==void 0?{username:t.username}:{},...t.credential!==void 0?{credential:t.credential}:{}}))})}function z(R){const e={iceServers:B(R.iceServers),...R.iceTransportPolicy!==void 0?{iceTransportPolicy:R.iceTransportPolicy}:{}};return new H(e)}function _(){return new J}export class WebRtcTransport{sessions=new Map;connecting=new Map;serverSessions=new Map;sessionListeners=new Set;closingServerSessions=new Map;trackListeners=new Set;serverClosers=new Set;requestSequence=0;closing=!1;pendingPeers=new Set;iceServers;iceGatheringTimeoutMs;signalingTimeoutMs;connectionTimeoutMs;requestTimeoutMs;createMediaStream;createPeerConnection;options;constructor(e={}){this.options=e,this.createPeerConnection=e.createPeerConnection??z,this.createMediaStream=e.createMediaStream??_,this.iceServers=e.iceServers??[{urls:"stun:stun.l.google.com:19302"}],this.iceGatheringTimeoutMs=e.iceGatheringTimeoutMs??1e4,this.signalingTimeoutMs=e.signalingTimeoutMs??1e4,this.connectionTimeoutMs=e.connectionTimeoutMs??15e3,this.requestTimeoutMs=e.requestTimeoutMs??15e3}log(e,t,r){if(this.options.logger){this.options.logger(e,t,r);return}if(!this.options.verbose&&e!=="error")return;const n=e==="debug"?console.debug:console[e];r===void 0?n(t):n(t,r)}onTrack(e){return this.trackListeners.add(e),()=>this.trackListeners.delete(e)}onServerSession(e){return this.sessionListeners.add(e),()=>this.sessionListeners.delete(e)}emitServerSession(e,t){for(const r of this.sessionListeners)try{r({...e.context,type:t})}catch(n){this.log("error","WebRTC session listener failed",n)}}getServerSession(e){const t=this.serverSessions.get(e);if(!(!t||t.closed))return{id:t.id,context:t.context,peerConnection:t.peerConnection,remoteStream:t.remoteStream,...t.localStream?{localStream:t.localStream}:{}}}getSession(e){const t=this.sessions.get(e);if(!(!t||t.closed))return{id:t.id,peerUrl:t.peerUrl,peerConnection:t.peerConnection,dataChannel:t.dataChannel,remoteStream:t.remoteStream,media:t.mediaController,...t.localStream?{localStream:t.localStream}:{}}}async execute(e){if(this.closing)throw new Error("WebRTC transport is closed");const t=e,r=t.peerUrl??e.url;if(e.signal?.aborted)throw P(e.signal);const n=await this.getOrCreateSession(r,t);if(await this.ensureMedia(n,t),e.signal?.aborted)throw P(e.signal);return this.sendRequest(n,e,t.timeout)}async listen(e){if(this.closing)throw new Error("WebRTC transport is closed");const t=new Set,r=e.port??3e3,n=e.host??"127.0.0.1",i=e.signaling?.path??"/signal";if(!i.startsWith("/")||i.startsWith("//")||/[?#\\\s]/u.test(i)||Array.from(i).some(s=>{const g=s.codePointAt(0);return g<=31||g>=127&&g<=159})||new URL(i,"http://localhost").pathname!==i)throw new TypeError("WebRTC signaling path must be an absolute literal pathname without query or fragment");if(e.signal?.aborted)throw new Error("WebRtcTransport: server startup aborted");let a=!1,o;const d=new Set,f=F((s,g)=>{(async()=>{const c=(l,h)=>{g.headersSent||g.destroyed||(g.writeHead(l,{"content-type":"application/json","cache-control":"no-store"}),g.end(JSON.stringify(h)))};if(a||this.closing){c(503,{error:"WebRTC server is closing"});return}let C;try{C=new URL(s.url??"/","http://localhost")}catch{c(400,{error:"Invalid request URL"});return}if(C.pathname!==i){c(404,{error:"Signaling endpoint not found"});return}let S,x;if(["POST","DELETE","GET","PATCH"].includes(s.method??"")&&e.signaling?.authorize){const l=new AbortController,h=()=>l.abort(),b=()=>{c(503,{error:"WebRTC signaling request aborted"})};s.once("aborted",h),g.once("close",h),l.signal.addEventListener("abort",b,{once:!0}),d.add(l);try{const E={};for(const[T,M]of Object.entries(s.headers))M!==void 0&&(E[T]=M);const I=await e.signaling.authorize({protocol:"webrtc",method:s.method,url:s.url??"/",headers:E,signal:l.signal});if(l.signal.aborted||s.aborted||g.destroyed)return;if(a||this.closing||e.signal?.aborted){c(503,{error:"WebRTC server is closing"});return}if(typeof I=="object"&&I!==null){if(typeof I.ownerId!="string"||!I.ownerId.trim()){c(403,{error:"Invalid signaling identity"});return}S=I.ownerId,x=I.context}else if(I!==!0){c(403,{error:"Signaling forbidden"});return}}catch{c(403,{error:"Signaling forbidden"});return}finally{d.delete(l),s.off("aborted",h),g.off("close",h),l.signal.removeEventListener("abort",b)}}if(s.method==="DELETE"){const l=C.searchParams.get("sessionId"),h=l?this.serverSessions.get(l):void 0;if(h&&(!t.has(l)||h.context.ownerId!==S)){c(403,{error:"Signaling forbidden"});return}l&&t.has(l)&&(await this.closeServerSession(l),t.delete(l)),g.writeHead(204).end();return}if(s.method==="GET"){const l=C.searchParams.get("sessionId"),h=l?this.serverSessions.get(l):void 0;if(!h||!l||!t.has(l)){c(404,{error:"Session not found"});return}if(h.context.ownerId!==S){c(403,{error:"Signaling forbidden"});return}const b=h.trickle;if(!b||C.searchParams.get("generation")!==b.generation){c(409,{error:"ICE generation mismatch"});return}const E=C.searchParams.get("cursor")??"0";if(!/^(0|[1-9][0-9]*)$/.test(E)||!Number.isSafeInteger(Number(E))||Number(E)>b.candidates.length){c(400,{error:"Invalid ICE cursor"});return}c(200,{generation:b.generation,candidates:b.candidates.slice(Number(E)),next:b.candidates.length});return}if(s.method!=="POST"&&s.method!=="PATCH"){c(405,{error:"Method not allowed"});return}let W="";try{for await(const l of s)if(W+=Buffer.isBuffer(l)?l.toString("utf8"):String(l),Buffer.byteLength(W,"utf8")>U){c(413,{error:"Signaling payload too large"});return}}catch(l){this.log("error","[WebRtcTransport Server] Failed to read signaling body:",l),c(500,{error:"Signaling failed"});return}let w;try{w=JSON.parse(W)}catch{c(400,{error:"Invalid JSON"});return}let y,u;try{if(s.method==="PATCH"){if(!w||typeof w!="object"||typeof w.sessionId!="string"||typeof w.generation!="string"||!("candidate"in w)){c(400,{error:"Expected sessionId, generation and candidate"});return}const L=this.serverSessions.get(w.sessionId);if(!L||!t.has(w.sessionId)){c(404,{error:"Session not found"});return}if(L.context.ownerId!==S){c(403,{error:"Signaling forbidden"});return}const k=L.trickle;if(!k||k.generation!==w.generation){c(409,{error:"ICE generation mismatch"});return}const v=w.candidate;if(v!==null&&(typeof v!="object"||typeof v.candidate!="string"||v.candidate.length<1||v.candidate.length>4096||v.sdpMid!=null&&(typeof v.sdpMid!="string"||v.sdpMid.length>256)||v.sdpMLineIndex!=null&&(!Number.isInteger(v.sdpMLineIndex)||v.sdpMLineIndex<0||v.sdpMLineIndex>65535)||v.sdpMid==null&&v.sdpMLineIndex==null||v.usernameFragment!=null&&(typeof v.usernameFragment!="string"||v.usernameFragment.length>256))){c(400,{error:"Invalid ICE candidate"});return}if(k.remoteEnded||k.remoteCount>=256){c(429,{error:"ICE candidate limit reached or gathering ended"});return}k.remoteCount++,v===null&&(k.remoteEnded=!0);const D=k.remoteQueue.then(async()=>{if(L.closed||L.trickle!==k)throw new Error("ICE generation expired");await L.peerConnection.addIceCandidate(v)});k.remoteQueue=D.catch(()=>{});try{await D,g.writeHead(204).end()}catch{c(400,{error:"Failed to apply ICE candidate"})}return}if(!w||w.trickle!==void 0&&typeof w.trickle!="boolean"){c(400,{error:"Invalid trickle flag"});return}if(typeof w.sessionId!="string"||w.sessionId.length<1||w.sessionId.length>200||w.sdp?.type!=="offer"||typeof w.sdp.sdp!="string"){c(400,{error:"Expected sessionId and a valid SDP offer"});return}if(a||this.closing||e.signal?.aborted||s.aborted||g.destroyed)throw new Error("WebRTC signaling request aborted");const l=w.sessionId;if(u=this.serverSessions.get(l),u&&!t.has(l)){c(409,{error:"Session belongs to another listener"});return}if(u&&u.context.ownerId!==S){c(403,{error:"Signaling forbidden"});return}if(this.closingServerSessions.has(l)){c(409,{error:"Session is closing"});return}if(u?.negotiating||u?.trickle?.gathering){c(409,{error:"Session negotiation already in progress"});return}if(!u||u.closed){y=this.createPeerConnection({iceServers:this.iceServers});const L=new AbortController;u={controller:L,context:Object.freeze({sessionId:l,ownerId:S,authorization:x,signal:L.signal}),release:()=>t.delete(l),id:l,peerConnection:y,remoteStream:this.createMediaStream(),localTrackIds:new Set,localStreamResolved:!1,ownsLocalStream:!1,closed:!1},t.add(l),this.serverSessions.set(l,u),this.attachServerPeerEvents(u,e.onRequest),this.emitServerSession(u,"created")}else y=u.peerConnection;u.trickle?.detach(),delete u.trickle,u.negotiating=!0;const h=u,b=()=>{g.writableEnded||this.closeServerSession(h.id)};g.once("close",b);const E=()=>c(503,{error:"WebRTC session closed"});if(h.controller.signal.addEventListener("abort",E,{once:!0}),g.once("finish",()=>{h.controller.signal.removeEventListener("abort",E),g.off("close",b)}),await this.setRemoteDescription(y,w.sdp),await this.attachConfiguredServerStream(u),u.closed)throw new Error("WebRTC session closed");const I=await y.createAnswer();let T;if(w.trickle===!0){const L=y,k=A=>{if(!(!T||h.closed||h.trickle!==T)){if(T.candidates.length>=256){this.closeServerSession(h.id);return}T.candidates.push(A.candidate?A.candidate.toJSON():null)}};L.addEventListener("icecandidate",k),T={generation:crypto.randomUUID(),candidates:[],gathering:!0,remoteCount:0,remoteEnded:!1,remoteQueue:Promise.resolve(),detach:()=>L.removeEventListener("icecandidate",k)},u.trickle=T;const v=this.withDeadline(L.setLocalDescription(I),this.iceGatheringTimeoutMs,u.controller.signal);let D;try{await this.withDeadline(new Promise((A,O)=>{const j=()=>{if(h.closed){O(new Error("WebRTC session closed"));return}if(L.signalingState==="stable"&&L.localDescription?.type==="answer"){A();return}D=setTimeout(j,5)};v.catch(O),j()}),this.iceGatheringTimeoutMs,u.controller.signal)}finally{clearTimeout(D)}v.then(()=>{T&&h.trickle===T&&(T.gathering=!1,T.candidates.includes(null)||T.candidates.push(null))},()=>{h.trickle===T&&this.closeServerSession(h.id)})}else if(await this.withDeadline(y.setLocalDescription(I),this.iceGatheringTimeoutMs),!await this.waitForIceGatheringComplete(y,this.iceGatheringTimeoutMs))throw new Error("Full ICE gathering timed out; trickle ICE is not supported");const M=y.localDescription;if(!M?.sdp||M.type!=="answer")throw new Error("Local SDP answer was not generated");if(a||u.closed)throw new Error("WebRTC session closed");u.negotiating=!1,c(200,T?{sdp:M,generation:T.generation}:M)}catch(l){if(this.log("error","[WebRtcTransport Server] \u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u0438\u0433\u043D\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u0438:",l),c(500,{error:"Signaling failed"}),u)await this.closeServerSession(u.id);else if(y)try{await y.close()}catch{}}})()});await new Promise((s,g)=>{const c=S=>{f.off("listening",C),g(S)},C=()=>{f.off("error",c),s()};f.once("error",c),f.once("listening",C),f.listen(r,n)}),this.log("info",`[WebRtcTransport] Listening on http://${n}:${r}${i}`);const p=()=>{m().catch(s=>{this.log("error","[WebRtcTransport] Server shutdown failed:",s)})},m=()=>{if(o)return o;a=!0;for(const s of d)s.abort();return e.signal?.removeEventListener("abort",p),o=(async()=>{const s=f.listening?new Promise((g,c)=>{f.close(C=>C?c(C):g())}):Promise.resolve();await Promise.all([...t].map(g=>this.closeServerSession(g))),t.clear(),await s,this.serverClosers.delete(m)})(),o};return e.signal?.addEventListener("abort",p,{once:!0}),this.serverClosers.add(m),e.signal?.aborted&&!a&&await m(),{close:m,webrtc:{getSession:this.getSession.bind(this),getServerSession:this.getServerSession.bind(this),onTrack:this.onTrack.bind(this),closeServerSession:s=>t.has(s)?this.closeServerSession(s):Promise.resolve(),onServerSession:this.onServerSession.bind(this)}}}async close(){this.closing=!0,await Promise.all([...this.pendingPeers].map(e=>e.close().catch(()=>{}))),await Promise.allSettled(this.connecting.values()),await Promise.all([...this.sessions.values()].map(e=>this.disposeClientSession(e,!0))),await Promise.all([...this.serverClosers].map(e=>e()))}async destroy(){await this.close()}async getOrCreateSession(e,t){const r=this.sessions.get(e);if(r&&!r.closed&&r.dataChannel.readyState==="open"&&r.peerConnection.connectionState!=="failed"&&r.peerConnection.connectionState!=="closed")return r;r&&await this.disposeClientSession(r,!0);const n=this.connecting.get(e);if(n)return n;const i=this.createClientSession(e,t);this.connecting.set(e,i);try{const a=await i;if(this.closing||a.closed)throw await this.disposeClientSession(a,!0),new Error("WebRTC transport is closed");return this.sessions.set(e,a),a}finally{this.connecting.get(e)===i&&this.connecting.delete(e)}}async createClientSession(e,t){const r=this.toSignalingUrl(e),n=this.createId("session"),i=this.createPeerConnection({iceServers:t.iceServers??this.iceServers});this.pendingPeers.add(i);const a=i.createDataChannel(t.channelLabel??"hyperttp-webrtc-transport",{ordered:!0}),o={id:n,peerUrl:e,signalingUrl:r,peerConnection:i,dataChannel:a,remoteStream:this.createMediaStream(),...t.localStream?{localStream:t.localStream}:{},localTrackIds:new Set,pending:new Map,negotiation:Promise.resolve(),closed:!1,mediaController:{}},d=new Set,f=new Set,p=new Set;i.addEventListener("track",m=>{const{track:s}=m;o.remoteStream.getTracks().includes(s)||o.remoteStream.addTrack(s);for(const g of d)g({track:s,stream:o.remoteStream});for(const g of p)g({stream:o.remoteStream});s.addEventListener("ended",()=>{for(const g of f)g({track:s})})}),o.mediaController={localStream:o.localStream,remoteStream:o.remoteStream,addTrack:(m,s)=>this.queueNegotiation(o,async()=>{i.addTrack(m,s??o.localStream??this.createMediaStream()),m.id&&o.localTrackIds.add(m.id),await this.negotiate(o)}),replaceTrack:async(m,s)=>{const g=i.getSenders().find(c=>c.track&&c.track.kind===m);g&&await g.replaceTrack(s)},removeTrack:m=>this.queueNegotiation(o,async()=>{const s=i.getSenders().find(g=>g.track===m);s&&(i.removeTrack(s),m.id&&o.localTrackIds.delete(m.id),await this.negotiate(o))}),close:async()=>{await this.disposeClientSession(o,!0)},on(m,s){return m==="track"&&d.add(s),m==="trackEnded"&&f.add(s),m==="stream"&&p.add(s),()=>{d.delete(s),f.delete(s),p.delete(s)}}};try{return this.attachClientPeerEvents(o),this.addLocalTracks(o,t.localStream),this.addReceiveTransceivers(o,t.receiveAudio,t.receiveVideo),await this.negotiate(o,t.signal),await this.waitForChannelOpen(a,this.connectionTimeoutMs,t.signal),o}catch(m){throw await this.disposeClientSession(o,!0),m}finally{this.pendingPeers.delete(i)}}attachClientPeerEvents(e){const{peerConnection:t,dataChannel:r}=e;t.addEventListener("track",n=>{const i=n.track;e.remoteStream.getTracks().some(a=>a.id===i.id)||e.remoteStream.addTrack(i),this.emitTrack({side:"client",sessionId:e.id,peerUrl:e.peerUrl,peerConnection:t,track:i,stream:e.remoteStream,streams:n.streams})}),t.addEventListener("connectionstatechange",()=>{(t.connectionState==="failed"||t.connectionState==="closed")&&this.disposeClientSession(e,!0)}),r.addEventListener("message",n=>{let i;try{i=JSON.parse(String(n.data))}catch{return}if(!i||typeof i!="object"||typeof i.requestId!="string")return;const a=e.pending.get(i.requestId);if(a)try{const o=G(i.body);this.finishPending(e,i.requestId),a.resolve({status:typeof i.status=="number"?i.status:200,statusText:typeof i.statusText=="string"?i.statusText:"OK",headers:i.headers&&typeof i.headers=="object"?i.headers:{},body:o,url:typeof i.url=="string"?i.url:e.peerUrl})}catch(o){this.finishPending(e,i.requestId),a.reject(N(o,"Invalid WebRTC response"))}}),r.addEventListener("close",()=>{this.rejectAllPending(e,new Error("WebRTC DataChannel closed")),this.disposeClientSession(e,!0)}),r.addEventListener("error",()=>{this.rejectAllPending(e,new Error("WebRTC DataChannel error"))})}queueNegotiation(e,t){const r=e.negotiation.catch(()=>{}).then(async()=>{if(e.closed)throw new Error("WebRTC session closed");try{await t()}catch(n){throw await this.disposeClientSession(e,!0),n}});return e.negotiation=r,r}async ensureMedia(e,t){!(t.localStream||t.receiveAudio||t.receiveVideo)||e.closed||await this.queueNegotiation(e,async()=>{let n=this.addLocalTracks(e,t.localStream);n=this.addReceiveTransceivers(e,t.receiveAudio,t.receiveVideo)||n,t.localStream&&(e.localStream=t.localStream,e.mediaController.localStream=t.localStream),n&&await this.negotiate(e,t.signal)})}addLocalTracks(e,t){if(!t)return!1;let r=!1;for(const n of t.getTracks())n.readyState!=="live"||e.localTrackIds.has(n.id)||(e.peerConnection.addTrack(n,t),e.localTrackIds.add(n.id),r=!0);return r}addReceiveTransceivers(e,t,r){const n=e.peerConnection;let i=!1;const a=(o,d)=>{if(!d)return;n.getTransceivers().find(p=>p.receiver.track.kind===o&&(p.direction==="recvonly"||p.direction==="sendrecv"))||(n.addTransceiver(o,{direction:"recvonly"}),i=!0)};return a("audio",t),a("video",r),i}async negotiate(e,t){if(t?.aborted)throw P(t);const r=e.peerConnection;if(r.signalingState!=="stable")throw new Error(`Cannot negotiate WebRTC session in state ${r.signalingState}`);if(this.options.trickleIce)return this.negotiateTrickle(e,t);const n=await r.createOffer();await this.withDeadline(r.setLocalDescription(n),this.iceGatheringTimeoutMs,t),await this.waitForIceGatheringComplete(r,this.iceGatheringTimeoutMs);const i=r.localDescription;if(!i?.sdp||i.type!=="offer")throw new Error("Local SDP offer was not generated");const a=new AbortController,o=setTimeout(()=>a.abort(),this.signalingTimeoutMs),d=()=>a.abort(t?.reason);t?.addEventListener("abort",d,{once:!0}),t?.aborted&&d();try{const f=await fetch(e.signalingUrl,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({sessionId:e.id,sdp:i}),signal:a.signal});if(!f.ok){const m=await f.text();throw new Error(`Signaling HTTP error: ${f.status} ${f.statusText}${m?` \u2014 ${m}`:""}`)}const p=await f.json();if(p.type!=="answer"||typeof p.sdp!="string")throw new Error("Signaling server returned an invalid SDP answer");await this.setRemoteDescription(r,p)}catch(f){throw t?.aborted?P(t):a.signal.aborted?new Error(`Signaling timed out after ${this.signalingTimeoutMs} ms`):N(f,"WebRTC signaling failed")}finally{clearTimeout(o),t?.removeEventListener("abort",d)}}async negotiateTrickle(e,t){const r=e.peerConnection,n=new AbortController;e.trickleAbort=n;const i=()=>n.abort(t?.reason);t?.addEventListener("abort",i,{once:!0}),t?.aborted&&i();const a=[];let o=!1,d,f;const p=g=>{if(o||n.signal.aborted)return;const c=g.candidate?g.candidate.toJSON():null;c?.candidate!==""&&(a.length>=256||!this.validTrickleCandidate(c)?f=new Error("Invalid or excessive local ICE candidates"):(a.push(c),c===null&&(o=!0)),d?.())};r.addEventListener("icecandidate",p);let m;const s=setTimeout(()=>n.abort(new Error("Trickle ICE timed out")),this.iceGatheringTimeoutMs+this.signalingTimeoutMs);try{const g=await this.withDeadline(r.createOffer(),this.signalingTimeoutMs,n.signal),c=this.withDeadline(r.setLocalDescription(g),this.iceGatheringTimeoutMs,n.signal);await this.withDeadline(new Promise((y,u)=>{const l=()=>{if(r.signalingState==="have-local-offer"&&r.localDescription?.sdp)return y();m=setTimeout(l,5)};c.catch(u),l()}),this.iceGatheringTimeoutMs,n.signal),clearTimeout(m);const C=await this.withDeadline(fetch(e.signalingUrl,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({sessionId:e.id,sdp:r.localDescription,trickle:!0}),signal:n.signal}),this.signalingTimeoutMs,n.signal);if(!C.ok)throw new Error(`Signaling HTTP error: ${C.status} ${C.statusText}`);const S=await this.withDeadline(C.json(),this.signalingTimeoutMs,n.signal);if(!S||typeof S!="object"||!("generation"in S)||typeof S.generation!="string"||!S.generation||S.generation.length>256||!("sdp"in S)||!S.sdp||typeof S.sdp!="object"||!("type"in S.sdp)||S.sdp.type!=="answer"||!("sdp"in S.sdp)||typeof S.sdp.sdp!="string"||!S.sdp.sdp)throw new Error("Signaling server returned an invalid trickle answer");const x=S.generation;await this.withDeadline(this.setRemoteDescription(r,{type:"answer",sdp:S.sdp.sdp}),this.signalingTimeoutMs,n.signal);const W=async()=>{let y=0;for(;;){if(n.signal.aborted)throw P(n.signal);if(f)throw f;if(y===a.length){await new Promise((h,b)=>{const E=()=>{d=void 0,b(P(n.signal))};d=()=>{n.signal.removeEventListener("abort",E),d=void 0,h()},n.signal.addEventListener("abort",E,{once:!0}),n.signal.aborted&&E()});continue}const u=a[y++],l=await fetch(e.signalingUrl,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({sessionId:e.id,generation:x,candidate:u}),signal:n.signal});if(l.status!==204)throw new Error(`ICE candidate HTTP error: ${l.status}`);if(u===null)return}},w=async()=>{let y=0;for(;;){const u=new URL(e.signalingUrl);u.searchParams.set("sessionId",e.id),u.searchParams.set("generation",x),u.searchParams.set("cursor",String(y));const l=await fetch(u,{signal:n.signal});if(!l.ok)throw new Error(`ICE polling HTTP error: ${l.status}`);const h=await l.json();if(!h||typeof h!="object"||!("generation"in h)||h.generation!==x||!("candidates"in h)||!Array.isArray(h.candidates)||!("next"in h)||!Number.isInteger(h.next)||h.next!==y+h.candidates.length||h.next>256||!h.candidates.every(b=>this.validTrickleCandidate(b))||h.candidates.slice(0,-1).includes(null))throw new Error("Signaling server returned an invalid ICE candidate page");for(const b of h.candidates)if(await this.withDeadline(r.addIceCandidate(b),this.iceGatheringTimeoutMs,n.signal),b===null)return;y=h.next,h.candidates.length||await new Promise((b,E)=>{const I=setTimeout(()=>{n.signal.removeEventListener("abort",T),b()},50),T=()=>{clearTimeout(I),E(P(n.signal))};n.signal.addEventListener("abort",T,{once:!0}),n.signal.aborted&&T()})}};await Promise.all([c.then(()=>{!o&&!f&&(a.length>=256?f=new Error("Invalid or excessive local ICE candidates"):(a.push(null),o=!0),d?.())}),W(),w()])}finally{n.abort(),clearTimeout(s),clearTimeout(m),r.removeEventListener("icecandidate",p),t?.removeEventListener("abort",i),e.trickleAbort===n&&delete e.trickleAbort}}validTrickleCandidate(e){if(e===null)return!0;if(!e||typeof e!="object")return!1;const t=e;return typeof t.candidate=="string"&&t.candidate.length>0&&t.candidate.length<=4096&&(t.sdpMid==null||typeof t.sdpMid=="string"&&t.sdpMid.length<=256)&&(t.sdpMLineIndex==null||Number.isInteger(t.sdpMLineIndex)&&t.sdpMLineIndex>=0&&t.sdpMLineIndex<=65535)&&(t.sdpMid!=null||t.sdpMLineIndex!=null)&&(t.usernameFragment==null||typeof t.usernameFragment=="string"&&t.usernameFragment.length<=256)}async setRemoteDescription(e,t){const r=new Set(e.dtlsTransports);try{await e.setRemoteDescription(t)}finally{const n=new Set(e.dtlsTransports);await Promise.all([...r].filter(i=>!n.has(i)).map(i=>i.stop()))}}async withDeadline(e,t,r){let n,i;try{return await Promise.race([e,new Promise((a,o)=>{n=setTimeout(()=>o(new Error(`WebRTC operation timed out after ${t} ms`)),t),i=()=>o(P(r)),r?.addEventListener("abort",i,{once:!0}),r?.aborted&&i()})])}finally{clearTimeout(n),i&&r?.removeEventListener("abort",i)}}async waitForIceGatheringComplete(e,t){if(e.iceGatheringState==="complete")return!0;if(!await new Promise(n=>{let i=!1,a;const o=()=>{clearTimeout(a),e.removeEventListener("icegatheringstatechange",f)},d=p=>{i||(i=!0,o(),n(p))},f=()=>{e.iceGatheringState==="complete"&&d(!0)};a=setTimeout(()=>d(!1),t),e.addEventListener("icegatheringstatechange",f),f()}))throw new Error(`ICE gathering timed out after ${t} ms`);return!0}async waitForChannelOpen(e,t,r){if(e.readyState!=="open"){if(r?.aborted)throw P(r);await new Promise((n,i)=>{let a=!1;const o=()=>{clearTimeout(g),e.removeEventListener("open",f),e.removeEventListener("error",p),e.removeEventListener("close",m),r?.removeEventListener("abort",s)},d=c=>{a||(a=!0,o(),c?i(c):n())},f=()=>d(),p=()=>d(new Error("WebRTC DataChannel failed to open")),m=()=>d(new Error("WebRTC DataChannel closed before opening")),s=()=>d(P(r)),g=setTimeout(()=>d(new Error(`DataChannel open timeout after ${t} ms`)),t);e.addEventListener("open",f),e.addEventListener("error",p),e.addEventListener("close",m),r?.addEventListener("abort",s,{once:!0}),e.readyState==="open"?d():e.readyState==="closed"&&d(new Error("WebRTC DataChannel is closed"))})}}sendRequest(e,t,r){if(t.signal?.aborted)return Promise.reject(P(t.signal));if(e.dataChannel.readyState!=="open")return Promise.reject(new Error("WebRTC DataChannel is not open"));const n=this.createId(`request${++this.requestSequence}`),i=r&&r>0?r:this.requestTimeoutMs;return new Promise((a,o)=>{const d={resolve:a,reject:o,timer:setTimeout(()=>{this.finishPending(e,n),o(new Error(`WebRTC request timed out after ${i} ms`))},i),...t.signal?{signal:t.signal}:{}};t.signal&&(d.abortListener=()=>{this.finishPending(e,n),o(P(t.signal))},t.signal.addEventListener("abort",d.abortListener,{once:!0})),e.pending.set(n,d);try{e.dataChannel.send(JSON.stringify({requestId:n,method:t.method,url:t.url,headers:t.headers,body:$(t.body),protocol:t.protocol}))}catch(f){this.finishPending(e,n),o(N(f,"Failed to send WebRTC request"))}})}finishPending(e,t){const r=e.pending.get(t);r&&(clearTimeout(r.timer),r.signal&&r.abortListener&&r.signal.removeEventListener("abort",r.abortListener),e.pending.delete(t))}rejectAllPending(e,t){for(const[r,n]of e.pending)this.finishPending(e,r),n.reject(t)}async disposeClientSession(e,t){if(!e.closed){e.closed=!0,e.trickleAbort?.abort(new Error("WebRTC session closed")),this.sessions.get(e.peerUrl)===e&&this.sessions.delete(e.peerUrl),this.rejectAllPending(e,new Error("WebRTC session closed"));try{e.dataChannel.close()}catch{}try{await e.peerConnection.close()}catch{}if(t)try{const r=new URL(e.signalingUrl);r.searchParams.set("sessionId",e.id);const n=new AbortController,i=setTimeout(()=>n.abort(),2e3);try{await fetch(r,{method:"DELETE",signal:n.signal})}finally{clearTimeout(i)}}catch{}}}attachServerPeerEvents(e,t){const r=e.peerConnection;r.addEventListener("datachannel",n=>{const i=n.channel;i.addEventListener("message",a=>{(async()=>{let o;try{const d=JSON.parse(String(a.data));if(o=typeof d.requestId=="string"?d.requestId:void 0,!o||typeof d.method!="string"||typeof d.url!="string")throw new Error("Invalid WebRTC transport request");const f={method:d.method,url:d.url,headers:d.headers&&typeof d.headers=="object"?{...d.headers,"x-webrtc-session-id":e.id}:{"x-webrtc-session-id":e.id},body:G(d.body),protocol:typeof d.protocol=="string"?d.protocol:"webrtc"};let p;if(t?p=await t(f):p={status:501,statusText:"Not Implemented",headers:{"content-type":"application/json"},body:{error:"No server request handler configured"},url:f.url},i.readyState!=="open")return;i.send(JSON.stringify({requestId:o,status:p.status,statusText:p.statusText??"",headers:p.headers,body:$(p.body),url:p.url??f.url}))}catch(d){if(this.log("error","[WebRtcTransport Server] Request failed:",d),o&&i.readyState==="open")try{i.send(JSON.stringify({requestId:o,status:500,statusText:"Internal Server Error",headers:{"content-type":"application/json"},body:{error:N(d,"Request handler failed").message}}))}catch{}}})()})}),r.addEventListener("track",n=>{const i=n.track;e.remoteStream.getTracks().some(a=>a.id===i.id)||e.remoteStream.addTrack(i),this.emitTrack({side:"server",sessionId:e.id,peerConnection:r,track:i,stream:e.remoteStream,streams:n.streams})}),r.addEventListener("connectionstatechange",()=>{(r.connectionState==="failed"||r.connectionState==="closed")&&this.closeServerSession(e.id)})}async attachConfiguredServerStream(e){if(e.closed)return;if(!e.localStreamResolved){const n=this.options.localStream,i=typeof n=="function",a=i?await n(e.id,e.context):n;if(e.closed){a&&i&&(e.localStream=a,e.ownsLocalStream=!0,this.disposeServerLocalStream(e));return}e.localStreamResolved=!0,a&&(e.localStream=a,e.ownsLocalStream=i)}const t=e.localStream;if(!t)return;const r=e.peerConnection;for(const n of t.getTracks())e.closed||n.readyState!=="live"||e.localTrackIds.has(n.id)||(r.addTrack(n,t),e.localTrackIds.add(n.id))}disposeServerLocalStream(e){const t=e.localStream,r=e.ownsLocalStream;if(delete e.localStream,e.ownsLocalStream=!1,!(!t||!r))try{this.options.disposeLocalStream?.(e.id,t)}catch{}finally{for(const n of t.getTracks())n.readyState!=="ended"&&n.stop()}}closeServerSession(e){const t=this.closingServerSessions.get(e);if(t)return t;const r=this.serverSessions.get(e);if(!r||r.closed)return Promise.resolve();r.closed=!0,r.trickle?.detach(),delete r.trickle,r.controller.abort(),r.release(),this.serverSessions.delete(e);const n=Promise.resolve().then(async()=>{try{await r.peerConnection.close()}catch(i){this.log("warn","WebRTC peer close failed",i)}finally{this.disposeServerLocalStream(r),this.emitServerSession(r,"closed")}}).finally(()=>this.closingServerSessions.delete(e));return this.closingServerSessions.set(e,n),n}emitTrack(e){for(const t of this.trackListeners)try{t(e)}catch(r){this.log("error","[WebRtcTransport] onTrack listener failed:",r)}}toSignalingUrl(e){if(e.startsWith("webrtcs://"))return e.replace(/^webrtcs:\/\//,"https://");if(e.startsWith("webrtc://"))return e.replace(/^webrtc:\/\//,"http://");throw new Error(`Unsupported WebRTC signaling URL: ${e}`)}createId(e){return`${e}-${crypto.randomUUID()}`}}
2
2
  //# sourceMappingURL=webrtc.js.map
package/webrtc.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/webrtc.ts"],
4
- "sourcesContent": ["import type {\n HyperTransport,\n TransportListenOptions,\n TransportRequest,\n TransportResponse,\n TransportServer,\n} from \"@hyperttp/types\";\nimport {\n MAX_SIGNALING_BODY_BYTES,\n type ClientSession,\n type WebRtcServerSessionContext,\n type WebRtcServerSessionEvent,\n type PendingResponse,\n type ServerSession,\n type ServerTrickleState,\n type WebRtcSessionView,\n type WebRtcTrackEvent,\n type WebRtcTransportOptions as InternalWebRtcTransportOptions,\n type WebRtcTransportRequest,\n} from \"./types/types.js\";\nimport { abortError, decodeWireBody, encodeWireBody, errorFromUnknown } from \"./utils/helper.js\";\nimport { createServer, type Server as HttpServer } from \"node:http\";\nimport {\n MediaStream as WeriftMediaStream,\n RTCPeerConnection as WeriftPeerConnection,\n RTCDataChannel as WeriftDataChannel,\n type MediaStreamTrack as WeriftMediaStreamTrack,\n} from \"werift\";\n\ndeclare module \"@hyperttp/types\" {\n interface TransportServer {\n readonly webrtc?: Pick<\n WebRtcTransport,\n \"getSession\" | \"getServerSession\" | \"onTrack\" | \"closeServerSession\" | \"onServerSession\"\n >;\n }\n}\n\ntype WeriftPeerConfig = NonNullable<ConstructorParameters<typeof WeriftPeerConnection>[0]>;\ntype WeriftIceServers = WeriftPeerConfig[\"iceServers\"];\n\nfunction normalizeIceServers(servers: RTCIceServer[] | undefined): WeriftIceServers {\n const normalized = (servers ?? []).flatMap((server) => {\n const urls = Array.isArray(server.urls) ? server.urls : [server.urls];\n if (server.credential !== undefined && typeof server.credential !== \"string\") {\n throw new TypeError(\"Werift currently requires ICE server credentials to be strings.\");\n }\n return urls.map((url) => ({\n urls: url,\n ...(server.username !== undefined ? { username: server.username } : {}),\n ...(server.credential !== undefined ? { credential: server.credential } : {}),\n }));\n });\n return normalized as unknown as WeriftIceServers;\n}\n\nfunction createWeriftPeerConnection(configuration: RTCConfiguration): WeriftPeerConnection {\n const config = {\n iceServers: normalizeIceServers(configuration.iceServers),\n ...(configuration.iceTransportPolicy !== undefined\n ? { iceTransportPolicy: configuration.iceTransportPolicy }\n : {}),\n } as unknown as WeriftPeerConfig;\n return new WeriftPeerConnection(config);\n}\n\nfunction createWeriftMediaStream(): WeriftMediaStream {\n return new WeriftMediaStream();\n}\n\nexport type WebRtcTransportOptions = InternalWebRtcTransportOptions;\n\nexport interface WebRtcServerSessionView {\n readonly id: string;\n readonly context: WebRtcServerSessionContext;\n readonly peerConnection: WeriftPeerConnection;\n readonly remoteStream: WeriftMediaStream;\n readonly localStream?: WeriftMediaStream;\n}\n\nexport interface WebRtcTransportServer extends TransportServer {\n readonly webrtc: Pick<\n WebRtcTransport,\n \"getSession\" | \"getServerSession\" | \"onTrack\" | \"closeServerSession\" | \"onServerSession\"\n >;\n}\n\nexport class WebRtcTransport implements HyperTransport {\n private readonly sessions = new Map<string, ClientSession>();\n private readonly connecting = new Map<string, Promise<ClientSession>>();\n private readonly serverSessions = new Map<string, ServerSession>();\n private readonly sessionListeners = new Set<(event: WebRtcServerSessionEvent) => void>();\n private readonly closingServerSessions = new Map<string, Promise<void>>();\n private readonly trackListeners = new Set<(event: WebRtcTrackEvent) => void>();\n private readonly serverClosers = new Set<() => Promise<void>>();\n private requestSequence = 0;\n private closing = false;\n private readonly pendingPeers = new Set<WeriftPeerConnection>();\n private readonly iceServers: RTCIceServer[];\n private readonly iceGatheringTimeoutMs: number;\n private readonly signalingTimeoutMs: number;\n private readonly connectionTimeoutMs: number;\n private readonly requestTimeoutMs: number;\n readonly createMediaStream: () => WeriftMediaStream;\n private readonly createPeerConnection: (configuration: RTCConfiguration) => WeriftPeerConnection;\n private readonly options: InternalWebRtcTransportOptions;\n\n constructor(options: WebRtcTransportOptions = {}) {\n this.options = options as unknown as InternalWebRtcTransportOptions;\n this.createPeerConnection = options.createPeerConnection ?? createWeriftPeerConnection;\n this.createMediaStream =\n (options.createMediaStream as unknown as (() => WeriftMediaStream) | undefined) ??\n createWeriftMediaStream;\n this.iceServers = options.iceServers ?? [{ urls: \"stun:stun.l.google.com:19302\" }];\n this.iceGatheringTimeoutMs = options.iceGatheringTimeoutMs ?? 10_000;\n this.signalingTimeoutMs = options.signalingTimeoutMs ?? 10_000;\n this.connectionTimeoutMs = options.connectionTimeoutMs ?? 15_000;\n this.requestTimeoutMs = options.requestTimeoutMs ?? 15_000;\n }\n\n private log(level: \"debug\" | \"info\" | \"warn\" | \"error\", message: string, meta?: unknown): void {\n if (this.options.logger) {\n this.options.logger(level, message, meta);\n return;\n }\n if (!this.options.verbose && level !== \"error\") return;\n const output = level === \"debug\" ? console.debug : console[level];\n if (meta === undefined) output(message);\n else output(message, meta);\n }\n\n onTrack(listener: (event: WebRtcTrackEvent) => void): () => void {\n this.trackListeners.add(listener);\n return () => this.trackListeners.delete(listener);\n }\n\n onServerSession(listener: (event: WebRtcServerSessionEvent) => void): () => void {\n this.sessionListeners.add(listener);\n return () => this.sessionListeners.delete(listener);\n }\n\n private emitServerSession(session: ServerSession, type: \"created\" | \"closed\") {\n for (const listener of this.sessionListeners) {\n try {\n listener({ ...session.context, type });\n } catch (error) {\n this.log(\"error\", \"WebRTC session listener failed\", error);\n }\n }\n }\n\n getServerSession(sessionId: string): WebRtcServerSessionView | undefined {\n const session = this.serverSessions.get(sessionId);\n if (!session || session.closed) return undefined;\n return {\n id: session.id,\n context: session.context,\n peerConnection: session.peerConnection,\n remoteStream: session.remoteStream,\n ...(session.localStream ? { localStream: session.localStream } : {}),\n };\n }\n\n getSession(peerUrl: string): WebRtcSessionView | undefined {\n const session = this.sessions.get(peerUrl);\n if (!session || session.closed) return undefined;\n\n return {\n id: session.id,\n peerUrl: session.peerUrl,\n peerConnection: session.peerConnection,\n dataChannel: session.dataChannel,\n remoteStream: session.remoteStream,\n media: session.mediaController,\n ...(session.localStream ? { localStream: session.localStream } : {}),\n };\n }\n\n async execute(request: TransportRequest): Promise<TransportResponse> {\n if (this.closing) throw new Error(\"WebRTC transport is closed\");\n const rtcRequest = request as WebRtcTransportRequest;\n const peerUrl = rtcRequest.peerUrl ?? request.url;\n\n if (request.signal?.aborted) throw abortError(request.signal);\n\n const session = await this.getOrCreateSession(peerUrl, rtcRequest);\n await this.ensureMedia(session, rtcRequest);\n\n if (request.signal?.aborted) throw abortError(request.signal);\n\n return this.sendRequest(session, request, rtcRequest.timeout);\n }\n\n public async listen(options: TransportListenOptions): Promise<WebRtcTransportServer> {\n if (this.closing) throw new Error(\"WebRTC transport is closed\");\n const ownedSessions = new Set<string>();\n const port = options.port ?? 3000;\n const host = options.host ?? \"127.0.0.1\";\n const signalingPath = options.signaling?.path ?? \"/signal\";\n if (\n !signalingPath.startsWith(\"/\") ||\n signalingPath.startsWith(\"//\") ||\n /[?#\\\\\\s]/u.test(signalingPath) ||\n Array.from(signalingPath).some((char) => {\n const code = char.codePointAt(0)!;\n return code <= 0x1f || (code >= 0x7f && code <= 0x9f);\n }) ||\n new URL(signalingPath, \"http://localhost\").pathname !== signalingPath\n ) {\n throw new TypeError(\n \"WebRTC signaling path must be an absolute literal pathname without query or fragment\",\n );\n }\n\n if (options.signal?.aborted) {\n throw new Error(\"WebRtcTransport: server startup aborted\");\n }\n\n let closed = false;\n let closePromise: Promise<void> | undefined;\n const pendingAuthorizations = new Set<AbortController>();\n\n const httpServer: HttpServer = createServer((req, res) => {\n void (async () => {\n const sendJson = (status: number, body: unknown) => {\n if (res.headersSent || res.destroyed) return;\n res.writeHead(status, {\n \"content-type\": \"application/json\",\n \"cache-control\": \"no-store\",\n });\n res.end(JSON.stringify(body));\n };\n\n if (closed || this.closing) {\n sendJson(503, { error: \"WebRTC server is closing\" });\n return;\n }\n\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(req.url ?? \"/\", \"http://localhost\");\n } catch {\n sendJson(400, { error: \"Invalid request URL\" });\n return;\n }\n\n if (parsedUrl.pathname !== signalingPath) {\n sendJson(404, { error: \"Signaling endpoint not found\" });\n return;\n }\n\n let ownerId: string | undefined;\n let authorization: unknown;\n if (\n [\"POST\", \"DELETE\", \"GET\", \"PATCH\"].includes(req.method ?? \"\") &&\n options.signaling?.authorize\n ) {\n const controller = new AbortController();\n const onDisconnect = () => controller.abort();\n const onAuthorizationAbort = () => {\n sendJson(503, { error: \"WebRTC signaling request aborted\" });\n };\n req.once(\"aborted\", onDisconnect);\n res.once(\"close\", onDisconnect);\n controller.signal.addEventListener(\"abort\", onAuthorizationAbort, { once: true });\n pendingAuthorizations.add(controller);\n try {\n const headers: Record<string, string | string[]> = {};\n for (const [name, value] of Object.entries(req.headers)) {\n if (value !== undefined) headers[name] = value;\n }\n const allowed = await options.signaling.authorize({\n protocol: \"webrtc\",\n method: req.method!,\n url: req.url ?? \"/\",\n headers,\n signal: controller.signal,\n });\n if (controller.signal.aborted || req.aborted || res.destroyed) return;\n if (closed || this.closing || options.signal?.aborted) {\n sendJson(503, { error: \"WebRTC server is closing\" });\n return;\n }\n if (typeof allowed === \"object\" && allowed !== null) {\n if (typeof allowed.ownerId !== \"string\" || !allowed.ownerId.trim()) {\n sendJson(403, { error: \"Invalid signaling identity\" });\n return;\n }\n ownerId = allowed.ownerId;\n authorization = allowed.context;\n } else if (allowed !== true) {\n sendJson(403, { error: \"Signaling forbidden\" });\n return;\n }\n } catch {\n sendJson(403, { error: \"Signaling forbidden\" });\n return;\n } finally {\n pendingAuthorizations.delete(controller);\n req.off(\"aborted\", onDisconnect);\n res.off(\"close\", onDisconnect);\n controller.signal.removeEventListener(\"abort\", onAuthorizationAbort);\n }\n }\n\n if (req.method === \"DELETE\") {\n const sessionId = parsedUrl.searchParams.get(\"sessionId\");\n const existing = sessionId ? this.serverSessions.get(sessionId) : undefined;\n if (\n existing &&\n (!ownedSessions.has(sessionId!) || existing.context.ownerId !== ownerId)\n ) {\n sendJson(403, { error: \"Signaling forbidden\" });\n return;\n }\n if (sessionId && ownedSessions.has(sessionId)) {\n await this.closeServerSession(sessionId);\n ownedSessions.delete(sessionId);\n }\n res.writeHead(204).end();\n return;\n }\n\n if (req.method === \"GET\") {\n const id = parsedUrl.searchParams.get(\"sessionId\");\n const session = id ? this.serverSessions.get(id) : undefined;\n if (!session || !id || !ownedSessions.has(id)) {\n sendJson(404, { error: \"Session not found\" });\n return;\n }\n if (session.context.ownerId !== ownerId) {\n sendJson(403, { error: \"Signaling forbidden\" });\n return;\n }\n const state = session.trickle;\n if (!state || parsedUrl.searchParams.get(\"generation\") !== state.generation) {\n sendJson(409, { error: \"ICE generation mismatch\" });\n return;\n }\n const cursor = parsedUrl.searchParams.get(\"cursor\") ?? \"0\";\n if (\n !/^(0|[1-9][0-9]*)$/.test(cursor) ||\n !Number.isSafeInteger(Number(cursor)) ||\n Number(cursor) > state.candidates.length\n ) {\n sendJson(400, { error: \"Invalid ICE cursor\" });\n return;\n }\n sendJson(200, {\n generation: state.generation,\n candidates: state.candidates.slice(Number(cursor)),\n next: state.candidates.length,\n });\n return;\n }\n\n if (req.method !== \"POST\" && req.method !== \"PATCH\") {\n sendJson(405, { error: \"Method not allowed\" });\n return;\n }\n\n let raw = \"\";\n try {\n for await (const chunk of req) {\n raw += Buffer.isBuffer(chunk) ? chunk.toString(\"utf8\") : String(chunk);\n if (Buffer.byteLength(raw, \"utf8\") > MAX_SIGNALING_BODY_BYTES) {\n sendJson(413, { error: \"Signaling payload too large\" });\n return;\n }\n }\n } catch (error) {\n sendJson(400, { error: errorFromUnknown(error, \"Failed to read body\").message });\n return;\n }\n\n let pc: WeriftPeerConnection | undefined;\n let session: ServerSession | undefined;\n\n try {\n const payload = JSON.parse(raw) as {\n sessionId?: unknown;\n sdp?: RTCSessionDescriptionInit;\n trickle?: unknown;\n generation?: unknown;\n candidate?: unknown;\n };\n\n if (req.method === \"PATCH\") {\n if (\n !payload ||\n typeof payload.sessionId !== \"string\" ||\n typeof payload.generation !== \"string\" ||\n !(\"candidate\" in payload)\n ) {\n sendJson(400, { error: \"Expected sessionId, generation and candidate\" });\n return;\n }\n const target = this.serverSessions.get(payload.sessionId);\n if (!target || !ownedSessions.has(payload.sessionId)) {\n sendJson(404, { error: \"Session not found\" });\n return;\n }\n if (target.context.ownerId !== ownerId) {\n sendJson(403, { error: \"Signaling forbidden\" });\n return;\n }\n const state = target.trickle;\n if (!state || state.generation !== payload.generation) {\n sendJson(409, { error: \"ICE generation mismatch\" });\n return;\n }\n const candidate = payload.candidate as RTCIceCandidateInit | null;\n if (\n candidate !== null &&\n (typeof candidate !== \"object\" ||\n typeof candidate.candidate !== \"string\" ||\n candidate.candidate.length < 1 ||\n candidate.candidate.length > 4096 ||\n (candidate.sdpMid != null &&\n (typeof candidate.sdpMid !== \"string\" || candidate.sdpMid.length > 256)) ||\n (candidate.sdpMLineIndex != null &&\n (!Number.isInteger(candidate.sdpMLineIndex) ||\n candidate.sdpMLineIndex < 0 ||\n candidate.sdpMLineIndex > 65535)) ||\n (candidate.sdpMid == null && candidate.sdpMLineIndex == null) ||\n (candidate.usernameFragment != null &&\n (typeof candidate.usernameFragment !== \"string\" ||\n candidate.usernameFragment.length > 256)))\n ) {\n sendJson(400, { error: \"Invalid ICE candidate\" });\n return;\n }\n if (state.remoteEnded || state.remoteCount >= 256) {\n sendJson(429, { error: \"ICE candidate limit reached or gathering ended\" });\n return;\n }\n state.remoteCount++;\n if (candidate === null) state.remoteEnded = true;\n const apply = state.remoteQueue.then(async () => {\n if (target.closed || target.trickle !== state)\n throw new Error(\"ICE generation expired\");\n await target.peerConnection.addIceCandidate(candidate);\n });\n state.remoteQueue = apply.catch(() => {});\n try {\n await apply;\n res.writeHead(204).end();\n } catch {\n sendJson(400, { error: \"Failed to apply ICE candidate\" });\n }\n return;\n }\n if (!payload || (payload.trickle !== undefined && typeof payload.trickle !== \"boolean\")) {\n sendJson(400, { error: \"Invalid trickle flag\" });\n return;\n }\n if (\n typeof payload.sessionId !== \"string\" ||\n payload.sessionId.length < 1 ||\n payload.sessionId.length > 200 ||\n payload.sdp?.type !== \"offer\" ||\n typeof payload.sdp.sdp !== \"string\"\n ) {\n sendJson(400, { error: \"Expected sessionId and a valid SDP offer\" });\n return;\n }\n\n if (closed || this.closing || options.signal?.aborted || req.aborted || res.destroyed) {\n throw new Error(\"WebRTC signaling request aborted\");\n }\n const sessionId = payload.sessionId;\n session = this.serverSessions.get(sessionId);\n if (session && !ownedSessions.has(sessionId)) {\n sendJson(409, { error: \"Session belongs to another listener\" });\n return;\n }\n if (session && session.context.ownerId !== ownerId) {\n sendJson(403, { error: \"Signaling forbidden\" });\n return;\n }\n if (this.closingServerSessions.has(sessionId)) {\n sendJson(409, { error: \"Session is closing\" });\n return;\n }\n if (session?.negotiating || session?.trickle?.gathering) {\n sendJson(409, { error: \"Session negotiation already in progress\" });\n return;\n }\n\n if (!session || session.closed) {\n pc = this.createPeerConnection({ iceServers: this.iceServers });\n const controller = new AbortController();\n session = {\n controller,\n context: Object.freeze({\n sessionId,\n ownerId,\n authorization,\n signal: controller.signal,\n }),\n release: () => ownedSessions.delete(sessionId),\n id: sessionId,\n peerConnection: pc,\n remoteStream: this.createMediaStream(),\n localTrackIds: new Set<string>(),\n localStreamResolved: false,\n ownsLocalStream: false,\n closed: false,\n };\n ownedSessions.add(sessionId);\n this.serverSessions.set(sessionId, session);\n this.attachServerPeerEvents(session, options.onRequest);\n this.emitServerSession(session, \"created\");\n } else {\n pc = session.peerConnection;\n }\n\n session.trickle?.detach();\n delete session.trickle;\n session.negotiating = true;\n const activeSession = session;\n const disconnect = () => {\n if (!res.writableEnded) void this.closeServerSession(activeSession.id);\n };\n res.once(\"close\", disconnect);\n const onSessionAbort = () => sendJson(503, { error: \"WebRTC session closed\" });\n activeSession.controller.signal.addEventListener(\"abort\", onSessionAbort, { once: true });\n res.once(\"finish\", () => {\n activeSession.controller.signal.removeEventListener(\"abort\", onSessionAbort);\n res.off(\"close\", disconnect);\n });\n await this.setRemoteDescription(pc, payload.sdp);\n await this.attachConfiguredServerStream(session);\n if (session.closed) throw new Error(\"WebRTC session closed\");\n\n const answer = await pc.createAnswer();\n let state: ServerTrickleState | undefined;\n if (payload.trickle === true) {\n const peer = pc;\n const onCandidate = (event: { candidate?: { toJSON(): RTCIceCandidateInit } }) => {\n if (!state || activeSession.closed || activeSession.trickle !== state) return;\n if (state.candidates.length >= 256) {\n void this.closeServerSession(activeSession.id);\n return;\n }\n state.candidates.push(event.candidate ? event.candidate.toJSON() : null);\n };\n peer.addEventListener(\"icecandidate\", onCandidate);\n state = {\n generation: crypto.randomUUID(),\n candidates: [],\n gathering: true,\n remoteCount: 0,\n remoteEnded: false,\n remoteQueue: Promise.resolve(),\n detach: () => peer.removeEventListener(\"icecandidate\", onCandidate),\n };\n session.trickle = state;\n const applying = this.withDeadline(\n peer.setLocalDescription(answer),\n this.iceGatheringTimeoutMs,\n session.controller.signal,\n );\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n await this.withDeadline(\n new Promise<void>((resolve, reject) => {\n const check = () => {\n if (activeSession.closed) {\n reject(new Error(\"WebRTC session closed\"));\n return;\n }\n if (\n peer.signalingState === \"stable\" &&\n peer.localDescription?.type === \"answer\"\n ) {\n resolve();\n return;\n }\n timer = setTimeout(check, 5);\n };\n void applying.catch(reject);\n check();\n }),\n this.iceGatheringTimeoutMs,\n session.controller.signal,\n );\n } finally {\n clearTimeout(timer);\n }\n void applying.then(\n () => {\n if (state && activeSession.trickle === state) {\n state.gathering = false;\n if (!state.candidates.includes(null)) state.candidates.push(null);\n }\n },\n () => {\n if (activeSession.trickle === state) void this.closeServerSession(activeSession.id);\n },\n );\n } else {\n await this.withDeadline(pc.setLocalDescription(answer), this.iceGatheringTimeoutMs);\n if (!(await this.waitForIceGatheringComplete(pc, this.iceGatheringTimeoutMs)))\n throw new Error(\"Full ICE gathering timed out; trickle ICE is not supported\");\n }\n\n const localDescription = pc.localDescription;\n if (!localDescription?.sdp || localDescription.type !== \"answer\") {\n throw new Error(\"Local SDP answer was not generated\");\n }\n\n if (closed || session.closed) throw new Error(\"WebRTC session closed\");\n session.negotiating = false;\n sendJson(\n 200,\n state ? { sdp: localDescription, generation: state.generation } : localDescription,\n );\n } catch (error) {\n this.log(\"error\", \"[WebRtcTransport Server] \u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u0438\u0433\u043D\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u0438:\", error);\n if (session) {\n await this.closeServerSession(session.id);\n } else if (pc) {\n try {\n await pc.close();\n } catch {}\n }\n sendJson(400, { error: errorFromUnknown(error, \"Signaling failed\").message });\n }\n })();\n });\n\n await new Promise<void>((resolve, reject) => {\n const onError = (error: Error) => {\n httpServer.off(\"listening\", onListening);\n reject(error);\n };\n const onListening = () => {\n httpServer.off(\"error\", onError);\n resolve();\n };\n httpServer.once(\"error\", onError);\n httpServer.once(\"listening\", onListening);\n httpServer.listen(port, host);\n });\n\n this.log(\"info\", `[WebRtcTransport] Listening on http://${host}:${port}${signalingPath}`);\n\n const onAbort = () => {\n void closeServer().catch((error) => {\n this.log(\"error\", \"[WebRtcTransport] Server shutdown failed:\", error);\n });\n };\n\n const closeServer = (): Promise<void> => {\n if (closePromise) return closePromise;\n closed = true;\n for (const controller of pendingAuthorizations) controller.abort();\n options.signal?.removeEventListener(\"abort\", onAbort);\n closePromise = (async () => {\n const stopped = httpServer.listening\n ? new Promise<void>((resolve, reject) => {\n httpServer.close((error) => (error ? reject(error) : resolve()));\n })\n : Promise.resolve();\n await Promise.all([...ownedSessions].map((id) => this.closeServerSession(id)));\n ownedSessions.clear();\n await stopped;\n this.serverClosers.delete(closeServer);\n })();\n return closePromise;\n };\n\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n this.serverClosers.add(closeServer);\n if (options.signal?.aborted && !closed) await closeServer();\n\n return {\n close: closeServer,\n webrtc: {\n getSession: this.getSession.bind(this),\n getServerSession: this.getServerSession.bind(this),\n onTrack: this.onTrack.bind(this),\n closeServerSession: (id: string) =>\n ownedSessions.has(id) ? this.closeServerSession(id) : Promise.resolve(),\n onServerSession: this.onServerSession.bind(this),\n },\n };\n }\n\n async close(): Promise<void> {\n this.closing = true;\n await Promise.all([...this.pendingPeers].map((pc) => pc.close().catch(() => {})));\n await Promise.allSettled(this.connecting.values());\n await Promise.all(\n [...this.sessions.values()].map((session) => this.disposeClientSession(session, true)),\n );\n await Promise.all([...this.serverClosers].map((closeServer) => closeServer()));\n }\n\n async destroy(): Promise<void> {\n await this.close();\n }\n\n private async getOrCreateSession(\n peerUrl: string,\n request: WebRtcTransportRequest,\n ): Promise<ClientSession> {\n const existing = this.sessions.get(peerUrl);\n if (\n existing &&\n !existing.closed &&\n existing.dataChannel.readyState === \"open\" &&\n existing.peerConnection.connectionState !== \"failed\" &&\n existing.peerConnection.connectionState !== \"closed\"\n ) {\n return existing;\n }\n\n if (existing) {\n await this.disposeClientSession(existing, true);\n }\n\n const inFlight = this.connecting.get(peerUrl);\n if (inFlight) return inFlight;\n\n const connectionPromise = this.createClientSession(peerUrl, request);\n this.connecting.set(peerUrl, connectionPromise);\n\n try {\n const session = await connectionPromise;\n if (this.closing || session.closed) {\n await this.disposeClientSession(session, true);\n throw new Error(\"WebRTC transport is closed\");\n }\n this.sessions.set(peerUrl, session);\n return session;\n } finally {\n if (this.connecting.get(peerUrl) === connectionPromise) {\n this.connecting.delete(peerUrl);\n }\n }\n }\n\n private async createClientSession(\n peerUrl: string,\n request: WebRtcTransportRequest,\n ): Promise<ClientSession> {\n const signalingUrl = this.toSignalingUrl(peerUrl);\n const id = this.createId(\"session\");\n const pc = this.createPeerConnection({\n iceServers: request.iceServers ?? this.iceServers,\n });\n this.pendingPeers.add(pc);\n const channel = pc.createDataChannel(request.channelLabel ?? \"hyperttp-webrtc-transport\", {\n ordered: true,\n });\n\n const session: ClientSession = {\n id,\n peerUrl,\n signalingUrl,\n peerConnection: pc,\n dataChannel: channel,\n remoteStream: this.createMediaStream(),\n ...(request.localStream ? { localStream: request.localStream } : {}),\n localTrackIds: new Set<string>(),\n pending: new Map<string, PendingResponse>(),\n negotiation: Promise.resolve(),\n closed: false,\n mediaController: {} as any,\n };\n\n const trackListeners = new Set<\n (e: { track: WeriftMediaStreamTrack; stream: WeriftMediaStream }) => void\n >();\n const trackEndedListeners = new Set<(e: { track: WeriftMediaStreamTrack }) => void>();\n const streamListeners = new Set<(e: { stream: WeriftMediaStream }) => void>();\n\n pc.addEventListener(\"track\", (event: WebRtcTrackEvent) => {\n const { track } = event;\n if (!session.remoteStream.getTracks().includes(track)) session.remoteStream.addTrack(track);\n for (const listener of trackListeners) listener({ track, stream: session.remoteStream });\n for (const listener of streamListeners) listener({ stream: session.remoteStream });\n track.addEventListener(\"ended\", () => {\n for (const listener of trackEndedListeners) listener({ track });\n });\n });\n\n session.mediaController = {\n localStream: session.localStream,\n remoteStream: session.remoteStream,\n addTrack: (track, stream) =>\n this.queueNegotiation(session, async () => {\n pc.addTrack(track, stream ?? session.localStream ?? this.createMediaStream());\n if (track.id) session.localTrackIds.add(track.id);\n await this.negotiate(session);\n }),\n replaceTrack: async (kind: \"audio\" | \"video\", track: any | null) => {\n const sender = pc.getSenders().find((s: any) => s.track && s.track.kind === kind);\n if (sender) {\n await sender.replaceTrack(track);\n }\n },\n removeTrack: (track) =>\n this.queueNegotiation(session, async () => {\n const sender = pc.getSenders().find((item) => item.track === track);\n if (sender) {\n pc.removeTrack(sender);\n if (track.id) session.localTrackIds.delete(track.id);\n await this.negotiate(session);\n }\n }),\n close: async () => {\n await this.disposeClientSession(session, true);\n },\n on(event: string, listener: any) {\n if (event === \"track\") trackListeners.add(listener);\n if (event === \"trackEnded\") trackEndedListeners.add(listener);\n if (event === \"stream\") streamListeners.add(listener);\n return () => {\n trackListeners.delete(listener);\n trackEndedListeners.delete(listener);\n streamListeners.delete(listener);\n };\n },\n };\n\n try {\n this.attachClientPeerEvents(session);\n this.addLocalTracks(session, request.localStream);\n this.addReceiveTransceivers(session, request.receiveAudio, request.receiveVideo);\n await this.negotiate(session, request.signal);\n await this.waitForChannelOpen(channel, this.connectionTimeoutMs, request.signal);\n return session;\n } catch (error) {\n await this.disposeClientSession(session, true);\n throw error;\n } finally {\n this.pendingPeers.delete(pc);\n }\n }\n\n private attachClientPeerEvents(session: ClientSession): void {\n const { peerConnection: pc, dataChannel: channel } = session;\n\n pc.addEventListener(\"track\", (event: any) => {\n const track = event.track;\n if (!session.remoteStream.getTracks().some((item: any) => item.id === track.id)) {\n session.remoteStream.addTrack(track);\n }\n\n this.emitTrack({\n side: \"client\",\n sessionId: session.id,\n peerUrl: session.peerUrl,\n peerConnection: pc,\n track,\n stream: session.remoteStream,\n streams: event.streams,\n });\n });\n\n pc.addEventListener(\"connectionstatechange\", () => {\n if (pc.connectionState === \"failed\" || pc.connectionState === \"closed\") {\n void this.disposeClientSession(session, true);\n }\n });\n\n channel.addEventListener(\"message\", (event: MessageEvent) => {\n let message: Record<string, unknown>;\n try {\n message = JSON.parse(String(event.data)) as Record<string, unknown>;\n } catch {\n return;\n }\n\n if (!message || typeof message !== \"object\" || typeof message.requestId !== \"string\") return;\n const pending = session.pending.get(message.requestId);\n if (!pending) return;\n\n try {\n const body = decodeWireBody(message.body);\n this.finishPending(session, message.requestId);\n pending.resolve({\n status: typeof message.status === \"number\" ? message.status : 200,\n statusText: typeof message.statusText === \"string\" ? message.statusText : \"OK\",\n headers:\n message.headers && typeof message.headers === \"object\"\n ? (message.headers as TransportResponse[\"headers\"])\n : {},\n body,\n url: typeof message.url === \"string\" ? message.url : session.peerUrl,\n });\n } catch (error) {\n this.finishPending(session, message.requestId);\n pending.reject(errorFromUnknown(error, \"Invalid WebRTC response\"));\n }\n });\n\n channel.addEventListener(\"close\", () => {\n this.rejectAllPending(session, new Error(\"WebRTC DataChannel closed\"));\n void this.disposeClientSession(session, true);\n });\n\n channel.addEventListener(\"error\", () => {\n this.rejectAllPending(session, new Error(\"WebRTC DataChannel error\"));\n });\n }\n\n private queueNegotiation(session: ClientSession, operation: () => Promise<void>): Promise<void> {\n const pending = session.negotiation\n .catch(() => undefined)\n .then(async () => {\n if (session.closed) throw new Error(\"WebRTC session closed\");\n try {\n await operation();\n } catch (error) {\n await this.disposeClientSession(session, true);\n throw error;\n }\n });\n session.negotiation = pending;\n return pending;\n }\n\n private async ensureMedia(\n session: ClientSession,\n request: WebRtcTransportRequest,\n ): Promise<void> {\n const needsPotentialChanges =\n Boolean(request.localStream) ||\n Boolean(request.receiveAudio) ||\n Boolean(request.receiveVideo);\n if (!needsPotentialChanges || session.closed) return;\n\n await this.queueNegotiation(session, async () => {\n let changed = this.addLocalTracks(session, request.localStream);\n changed =\n this.addReceiveTransceivers(session, request.receiveAudio, request.receiveVideo) || changed;\n\n if (request.localStream) {\n session.localStream = request.localStream;\n (session.mediaController as { localStream?: WeriftMediaStream }).localStream =\n request.localStream;\n }\n if (changed) await this.negotiate(session, request.signal);\n });\n }\n\n private addLocalTracks(session: ClientSession, stream?: WeriftMediaStream): boolean {\n if (!stream) return false;\n let changed = false;\n\n for (const track of stream.getTracks()) {\n if (track.readyState !== \"live\" || session.localTrackIds.has(track.id!)) continue;\n session.peerConnection.addTrack(track, stream);\n session.localTrackIds.add(track.id!);\n changed = true;\n }\n\n return changed;\n }\n\n private addReceiveTransceivers(\n session: ClientSession,\n receiveAudio?: boolean,\n receiveVideo?: boolean,\n ): boolean {\n const pc = session.peerConnection;\n let changed = false;\n\n const ensureReceive = (kind: \"audio\" | \"video\", enabled: boolean | undefined) => {\n if (!enabled) return;\n const transceivers = pc\n .getTransceivers()\n .find(\n (item: any) =>\n item.receiver.track.kind === kind &&\n (item.direction === \"recvonly\" || item.direction === \"sendrecv\"),\n );\n if (!transceivers) {\n pc.addTransceiver(kind, { direction: \"recvonly\" });\n changed = true;\n }\n };\n\n ensureReceive(\"audio\", receiveAudio);\n ensureReceive(\"video\", receiveVideo);\n\n return changed;\n }\n\n private async negotiate(session: ClientSession, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) throw abortError(signal);\n\n const pc = session.peerConnection;\n if (pc.signalingState !== \"stable\") {\n throw new Error(`Cannot negotiate WebRTC session in state ${pc.signalingState}`);\n }\n\n if (this.options.trickleIce) return this.negotiateTrickle(session, signal);\n\n const offer = await pc.createOffer();\n await this.withDeadline(pc.setLocalDescription(offer), this.iceGatheringTimeoutMs, signal);\n await this.waitForIceGatheringComplete(pc, this.iceGatheringTimeoutMs);\n\n const localDescription = pc.localDescription;\n if (!localDescription?.sdp || localDescription.type !== \"offer\") {\n throw new Error(\"Local SDP offer was not generated\");\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.signalingTimeoutMs);\n const abortListener = () => controller.abort(signal?.reason);\n signal?.addEventListener(\"abort\", abortListener, { once: true });\n if (signal?.aborted) abortListener();\n\n try {\n const response = await fetch(session.signalingUrl, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ sessionId: session.id, sdp: localDescription }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(\n `Signaling HTTP error: ${response.status} ${response.statusText}${text ? ` \u2014 ${text}` : \"\"}`,\n );\n }\n\n const answer = (await response.json()) as RTCSessionDescriptionInit;\n if (answer.type !== \"answer\" || typeof answer.sdp !== \"string\") {\n throw new Error(\"Signaling server returned an invalid SDP answer\");\n }\n\n await this.setRemoteDescription(pc, answer);\n } catch (error) {\n if (signal?.aborted) throw abortError(signal);\n if (controller.signal.aborted) {\n throw new Error(`Signaling timed out after ${this.signalingTimeoutMs} ms`);\n }\n throw errorFromUnknown(error, \"WebRTC signaling failed\");\n } finally {\n clearTimeout(timer);\n signal?.removeEventListener(\"abort\", abortListener);\n }\n }\n\n private async negotiateTrickle(session: ClientSession, signal?: AbortSignal): Promise<void> {\n const pc = session.peerConnection;\n const controller = new AbortController();\n session.trickleAbort = controller;\n const abort = () => controller.abort(signal?.reason);\n signal?.addEventListener(\"abort\", abort, { once: true });\n if (signal?.aborted) abort();\n const candidates: (RTCIceCandidateInit | null)[] = [];\n let ended = false;\n let wake: (() => void) | undefined;\n let failure: Error | undefined;\n const onCandidate = (event: { candidate?: { toJSON(): RTCIceCandidateInit } }) => {\n if (ended || controller.signal.aborted) return;\n const candidate = event.candidate ? event.candidate.toJSON() : null;\n if (candidate?.candidate === \"\") return;\n if (candidates.length >= 256 || !this.validTrickleCandidate(candidate)) {\n failure = new Error(\"Invalid or excessive local ICE candidates\");\n } else {\n candidates.push(candidate);\n if (candidate === null) ended = true;\n }\n wake?.();\n };\n pc.addEventListener(\"icecandidate\", onCandidate);\n let pollTimer: ReturnType<typeof setTimeout> | undefined;\n const timeout = setTimeout(\n () => controller.abort(new Error(\"Trickle ICE timed out\")),\n this.iceGatheringTimeoutMs + this.signalingTimeoutMs,\n );\n try {\n const offer = await this.withDeadline(\n pc.createOffer(),\n this.signalingTimeoutMs,\n controller.signal,\n );\n const applying = this.withDeadline(\n pc.setLocalDescription(offer),\n this.iceGatheringTimeoutMs,\n controller.signal,\n );\n await this.withDeadline(\n new Promise<void>((resolve, reject) => {\n const check = () => {\n if (pc.signalingState === \"have-local-offer\" && pc.localDescription?.sdp)\n return resolve();\n pollTimer = setTimeout(check, 5);\n };\n void applying.catch(reject);\n check();\n }),\n this.iceGatheringTimeoutMs,\n controller.signal,\n );\n clearTimeout(pollTimer);\n const response = await this.withDeadline(\n fetch(session.signalingUrl, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ sessionId: session.id, sdp: pc.localDescription, trickle: true }),\n signal: controller.signal,\n }),\n this.signalingTimeoutMs,\n controller.signal,\n );\n if (!response.ok)\n throw new Error(`Signaling HTTP error: ${response.status} ${response.statusText}`);\n const result: unknown = await this.withDeadline(\n response.json(),\n this.signalingTimeoutMs,\n controller.signal,\n );\n if (\n !result ||\n typeof result !== \"object\" ||\n !(\"generation\" in result) ||\n typeof result.generation !== \"string\" ||\n !result.generation ||\n result.generation.length > 256 ||\n !(\"sdp\" in result) ||\n !result.sdp ||\n typeof result.sdp !== \"object\" ||\n !(\"type\" in result.sdp) ||\n result.sdp.type !== \"answer\" ||\n !(\"sdp\" in result.sdp) ||\n typeof result.sdp.sdp !== \"string\" ||\n !result.sdp.sdp\n ) {\n throw new Error(\"Signaling server returned an invalid trickle answer\");\n }\n const generation = result.generation;\n await this.withDeadline(\n this.setRemoteDescription(pc, { type: \"answer\", sdp: result.sdp.sdp }),\n this.signalingTimeoutMs,\n controller.signal,\n );\n const upload = async () => {\n let index = 0;\n while (true) {\n if (controller.signal.aborted) throw abortError(controller.signal);\n if (failure) throw failure;\n if (index === candidates.length) {\n await new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n wake = undefined;\n reject(abortError(controller.signal));\n };\n wake = () => {\n controller.signal.removeEventListener(\"abort\", onAbort);\n wake = undefined;\n resolve();\n };\n controller.signal.addEventListener(\"abort\", onAbort, { once: true });\n if (controller.signal.aborted) onAbort();\n });\n continue;\n }\n const candidate = candidates[index++];\n const patch = await fetch(session.signalingUrl, {\n method: \"PATCH\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ sessionId: session.id, generation, candidate }),\n signal: controller.signal,\n });\n if (patch.status !== 204) throw new Error(`ICE candidate HTTP error: ${patch.status}`);\n if (candidate === null) return;\n }\n };\n const download = async () => {\n let cursor = 0;\n while (true) {\n const url = new URL(session.signalingUrl);\n url.searchParams.set(\"sessionId\", session.id);\n url.searchParams.set(\"generation\", generation);\n url.searchParams.set(\"cursor\", String(cursor));\n const pageResponse = await fetch(url, { signal: controller.signal });\n if (!pageResponse.ok) throw new Error(`ICE polling HTTP error: ${pageResponse.status}`);\n const page: unknown = await pageResponse.json();\n if (\n !page ||\n typeof page !== \"object\" ||\n !(\"generation\" in page) ||\n page.generation !== generation ||\n !(\"candidates\" in page) ||\n !Array.isArray(page.candidates) ||\n !(\"next\" in page) ||\n !Number.isInteger(page.next) ||\n page.next !== cursor + page.candidates.length ||\n (page.next as number) > 256 ||\n !page.candidates.every((candidate: unknown) => this.validTrickleCandidate(candidate)) ||\n page.candidates.slice(0, -1).includes(null)\n )\n throw new Error(\"Signaling server returned an invalid ICE candidate page\");\n for (const candidate of page.candidates as (RTCIceCandidateInit | null)[]) {\n await this.withDeadline(\n pc.addIceCandidate(candidate),\n this.iceGatheringTimeoutMs,\n controller.signal,\n );\n if (candidate === null) return;\n }\n cursor = page.next as number;\n if (!page.candidates.length)\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n controller.signal.removeEventListener(\"abort\", onAbort);\n resolve();\n }, 50);\n const onAbort = () => {\n clearTimeout(timer);\n reject(abortError(controller.signal));\n };\n controller.signal.addEventListener(\"abort\", onAbort, { once: true });\n if (controller.signal.aborted) onAbort();\n });\n }\n };\n await Promise.all([\n applying.then(() => {\n if (!ended && !failure) {\n if (candidates.length >= 256)\n failure = new Error(\"Invalid or excessive local ICE candidates\");\n else {\n candidates.push(null);\n ended = true;\n }\n wake?.();\n }\n }),\n upload(),\n download(),\n ]);\n } finally {\n controller.abort();\n clearTimeout(timeout);\n clearTimeout(pollTimer);\n pc.removeEventListener(\"icecandidate\", onCandidate);\n signal?.removeEventListener(\"abort\", abort);\n if (session.trickleAbort === controller) delete session.trickleAbort;\n }\n }\n\n private validTrickleCandidate(value: unknown): value is RTCIceCandidateInit | null {\n if (value === null) return true;\n if (!value || typeof value !== \"object\") return false;\n const candidate = value as RTCIceCandidateInit;\n return (\n typeof candidate.candidate === \"string\" &&\n candidate.candidate.length > 0 &&\n candidate.candidate.length <= 4096 &&\n (candidate.sdpMid == null ||\n (typeof candidate.sdpMid === \"string\" && candidate.sdpMid.length <= 256)) &&\n (candidate.sdpMLineIndex == null ||\n (Number.isInteger(candidate.sdpMLineIndex) &&\n candidate.sdpMLineIndex >= 0 &&\n candidate.sdpMLineIndex <= 65535)) &&\n (candidate.sdpMid != null || candidate.sdpMLineIndex != null) &&\n (candidate.usernameFragment == null ||\n (typeof candidate.usernameFragment === \"string\" &&\n candidate.usernameFragment.length <= 256))\n );\n }\n\n private async setRemoteDescription(\n pc: WeriftPeerConnection,\n description: RTCSessionDescriptionInit,\n ): Promise<void> {\n const previous = new Set(pc.dtlsTransports);\n try {\n await pc.setRemoteDescription(description);\n } finally {\n // Werift can replace transports while applying BUNDLE without stopping the\n // displaced ICE sockets. They are no longer reachable by pc.close().\n const active = new Set(pc.dtlsTransports);\n await Promise.all(\n [...previous]\n .filter((transport) => !active.has(transport))\n .map((transport) => transport.stop()),\n );\n }\n }\n\n private async withDeadline<T>(\n operation: Promise<T>,\n timeoutMs: number,\n signal?: AbortSignal,\n ): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n let onAbort: (() => void) | undefined;\n try {\n return await Promise.race([\n operation,\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(\n () => reject(new Error(`WebRTC operation timed out after ${timeoutMs} ms`)),\n timeoutMs,\n );\n onAbort = () => reject(abortError(signal));\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n if (signal?.aborted) onAbort();\n }),\n ]);\n } finally {\n clearTimeout(timer);\n if (onAbort) signal?.removeEventListener(\"abort\", onAbort);\n }\n }\n\n private async waitForIceGatheringComplete(\n pc: WeriftPeerConnection,\n timeoutMs: number,\n ): Promise<boolean> {\n if (pc.iceGatheringState === \"complete\") return true;\n\n const completed = await new Promise<boolean>((resolve) => {\n let settled = false;\n let timer: ReturnType<typeof setTimeout>;\n\n const cleanup = () => {\n clearTimeout(timer);\n pc.removeEventListener(\"icegatheringstatechange\", onStateChange);\n };\n\n const finish = (value: boolean) => {\n if (settled) return;\n settled = true;\n cleanup();\n resolve(value);\n };\n\n const onStateChange = () => {\n if (pc.iceGatheringState === \"complete\") finish(true);\n };\n\n timer = setTimeout(() => finish(false), timeoutMs);\n pc.addEventListener(\"icegatheringstatechange\", onStateChange);\n onStateChange();\n });\n\n if (!completed) throw new Error(`ICE gathering timed out after ${timeoutMs} ms`);\n return true;\n }\n\n private async waitForChannelOpen(\n channel: WeriftDataChannel,\n timeoutMs: number,\n signal?: AbortSignal,\n ): Promise<void> {\n if (channel.readyState === \"open\") return;\n if (signal?.aborted) throw abortError(signal);\n\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n const cleanup = () => {\n clearTimeout(timer);\n channel.removeEventListener(\"open\", onOpen);\n channel.removeEventListener(\"error\", onError);\n channel.removeEventListener(\"close\", onClose);\n signal?.removeEventListener(\"abort\", onAbort);\n };\n const finish = (error?: Error) => {\n if (settled) return;\n settled = true;\n cleanup();\n if (error) reject(error);\n else resolve();\n };\n const onOpen = () => finish();\n const onError = () => finish(new Error(\"WebRTC DataChannel failed to open\"));\n const onClose = () => finish(new Error(\"WebRTC DataChannel closed before opening\"));\n const onAbort = () => finish(abortError(signal));\n const timer = setTimeout(\n () => finish(new Error(`DataChannel open timeout after ${timeoutMs} ms`)),\n timeoutMs,\n );\n\n channel.addEventListener(\"open\", onOpen);\n channel.addEventListener(\"error\", onError);\n channel.addEventListener(\"close\", onClose);\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n if (channel.readyState === \"open\") finish();\n else if (channel.readyState === \"closed\") finish(new Error(\"WebRTC DataChannel is closed\"));\n });\n }\n\n private sendRequest(\n session: ClientSession,\n request: TransportRequest,\n requestedTimeout?: number,\n ): Promise<TransportResponse> {\n if (request.signal?.aborted) return Promise.reject(abortError(request.signal));\n if (session.dataChannel.readyState !== \"open\") {\n return Promise.reject(new Error(\"WebRTC DataChannel is not open\"));\n }\n\n const requestId = this.createId(`request${++this.requestSequence}`);\n const timeoutMs =\n requestedTimeout && requestedTimeout > 0 ? requestedTimeout : this.requestTimeoutMs;\n\n return new Promise<TransportResponse>((resolve, reject) => {\n const pending: PendingResponse = {\n resolve,\n reject,\n timer: setTimeout(() => {\n this.finishPending(session, requestId);\n reject(new Error(`WebRTC request timed out after ${timeoutMs} ms`));\n }, timeoutMs),\n ...(request.signal ? { signal: request.signal } : {}),\n };\n\n if (request.signal) {\n pending.abortListener = () => {\n this.finishPending(session, requestId);\n reject(abortError(request.signal));\n };\n request.signal.addEventListener(\"abort\", pending.abortListener, { once: true });\n }\n\n session.pending.set(requestId, pending);\n\n try {\n session.dataChannel.send(\n JSON.stringify({\n requestId,\n method: request.method,\n url: request.url,\n headers: request.headers,\n body: encodeWireBody(request.body),\n protocol: request.protocol,\n }),\n );\n } catch (error) {\n this.finishPending(session, requestId);\n reject(errorFromUnknown(error, \"Failed to send WebRTC request\"));\n }\n });\n }\n\n private finishPending(session: ClientSession, requestId: string): void {\n const pending = session.pending.get(requestId);\n if (!pending) return;\n clearTimeout(pending.timer);\n if (pending.signal && pending.abortListener) {\n pending.signal.removeEventListener(\"abort\", pending.abortListener);\n }\n session.pending.delete(requestId);\n }\n\n private rejectAllPending(session: ClientSession, error: Error): void {\n for (const [requestId, pending] of session.pending) {\n this.finishPending(session, requestId);\n pending.reject(error);\n }\n }\n\n private async disposeClientSession(session: ClientSession, notifyRemote: boolean): Promise<void> {\n if (session.closed) return;\n session.closed = true;\n session.trickleAbort?.abort(new Error(\"WebRTC session closed\"));\n\n if (this.sessions.get(session.peerUrl) === session) {\n this.sessions.delete(session.peerUrl);\n }\n this.rejectAllPending(session, new Error(\"WebRTC session closed\"));\n\n try {\n session.dataChannel.close();\n } catch {}\n try {\n await session.peerConnection.close();\n } catch {}\n\n if (notifyRemote) {\n try {\n const url = new URL(session.signalingUrl);\n url.searchParams.set(\"sessionId\", session.id);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 2_000);\n try {\n await fetch(url, { method: \"DELETE\", signal: controller.signal });\n } finally {\n clearTimeout(timer);\n }\n } catch {}\n }\n }\n\n private attachServerPeerEvents(\n session: ServerSession,\n onRequest: TransportListenOptions[\"onRequest\"],\n ): void {\n const pc = session.peerConnection;\n\n pc.addEventListener(\"datachannel\", (event: any) => {\n const channel = event.channel;\n channel.addEventListener(\"message\", (messageEvent: MessageEvent) => {\n void (async () => {\n let requestId: string | undefined;\n try {\n const message = JSON.parse(String(messageEvent.data)) as {\n requestId?: unknown;\n method?: unknown;\n url?: unknown;\n headers?: unknown;\n body?: unknown;\n protocol?: unknown;\n };\n\n requestId = typeof message.requestId === \"string\" ? message.requestId : undefined;\n if (\n !requestId ||\n typeof message.method !== \"string\" ||\n typeof message.url !== \"string\"\n ) {\n throw new Error(\"Invalid WebRTC transport request\");\n }\n\n const request: TransportRequest = {\n method: message.method,\n url: message.url,\n headers:\n message.headers && typeof message.headers === \"object\"\n ? {\n ...(message.headers as TransportRequest[\"headers\"]),\n \"x-webrtc-session-id\": session.id,\n }\n : { \"x-webrtc-session-id\": session.id },\n body: decodeWireBody(message.body),\n protocol:\n typeof message.protocol === \"string\"\n ? (message.protocol as TransportRequest[\"protocol\"])\n : \"webrtc\",\n };\n\n let response: TransportResponse;\n if (!onRequest) {\n response = {\n status: 501,\n statusText: \"Not Implemented\",\n headers: { \"content-type\": \"application/json\" },\n body: { error: \"No server request handler configured\" },\n url: request.url,\n };\n } else {\n response = await onRequest(request);\n }\n\n if (channel.readyState !== \"open\") return;\n channel.send(\n JSON.stringify({\n requestId,\n status: response.status,\n statusText: response.statusText ?? \"\",\n headers: response.headers,\n body: encodeWireBody(response.body),\n url: response.url ?? request.url,\n }),\n );\n } catch (error) {\n this.log(\"error\", \"[WebRtcTransport Server] Request failed:\", error);\n if (requestId && channel.readyState === \"open\") {\n try {\n channel.send(\n JSON.stringify({\n requestId,\n status: 500,\n statusText: \"Internal Server Error\",\n headers: { \"content-type\": \"application/json\" },\n body: { error: errorFromUnknown(error, \"Request handler failed\").message },\n }),\n );\n } catch {}\n }\n }\n })();\n });\n });\n\n pc.addEventListener(\"track\", (event: any) => {\n const track = event.track;\n if (!session.remoteStream.getTracks().some((item: any) => item.id === track.id)) {\n session.remoteStream.addTrack(track);\n }\n this.emitTrack({\n side: \"server\",\n sessionId: session.id,\n peerConnection: pc,\n track,\n stream: session.remoteStream,\n streams: event.streams,\n });\n });\n\n pc.addEventListener(\"connectionstatechange\", () => {\n if (pc.connectionState === \"failed\" || pc.connectionState === \"closed\") {\n void this.closeServerSession(session.id);\n }\n });\n }\n\n private async attachConfiguredServerStream(session: ServerSession): Promise<void> {\n if (session.closed) return;\n\n if (!session.localStreamResolved) {\n const configured = this.options.localStream;\n const isFactory = typeof configured === \"function\";\n const stream = isFactory ? await configured(session.id, session.context) : configured;\n if (session.closed) {\n if (stream && isFactory) {\n session.localStream = stream;\n session.ownsLocalStream = true;\n this.disposeServerLocalStream(session);\n }\n return;\n }\n session.localStreamResolved = true;\n\n if (stream) {\n session.localStream = stream;\n session.ownsLocalStream = isFactory;\n }\n }\n\n const stream = session.localStream;\n if (!stream) return;\n\n const pc = session.peerConnection;\n for (const track of stream.getTracks()) {\n if (session.closed || track.readyState !== \"live\" || session.localTrackIds.has(track.id!)) {\n continue;\n }\n pc.addTrack(track, stream);\n session.localTrackIds.add(track.id!);\n }\n }\n\n private disposeServerLocalStream(session: ServerSession): void {\n const stream = session.localStream;\n const ownsStream = session.ownsLocalStream;\n\n delete session.localStream;\n session.ownsLocalStream = false;\n\n if (!stream || !ownsStream) return;\n\n try {\n this.options.disposeLocalStream?.(session.id, stream);\n } catch {\n } finally {\n for (const track of stream.getTracks()) {\n if (track.readyState !== \"ended\") track.stop();\n }\n }\n }\n\n /** Idempotent: concurrent closes share peer/resource cleanup. */\n public closeServerSession(sessionId: string): Promise<void> {\n const pending = this.closingServerSessions.get(sessionId);\n if (pending) return pending;\n const session = this.serverSessions.get(sessionId);\n if (!session || session.closed) return Promise.resolve();\n session.closed = true;\n session.trickle?.detach();\n delete session.trickle;\n session.controller.abort();\n session.release();\n this.serverSessions.delete(sessionId);\n const closing = Promise.resolve()\n .then(async () => {\n try {\n await session.peerConnection.close();\n } catch (error) {\n this.log(\"warn\", \"WebRTC peer close failed\", error);\n } finally {\n this.disposeServerLocalStream(session);\n this.emitServerSession(session, \"closed\");\n }\n })\n .finally(() => this.closingServerSessions.delete(sessionId));\n this.closingServerSessions.set(sessionId, closing);\n return closing;\n }\n\n private emitTrack(event: WebRtcTrackEvent): void {\n for (const listener of this.trackListeners) {\n try {\n listener(event);\n } catch (error) {\n this.log(\"error\", \"[WebRtcTransport] onTrack listener failed:\", error);\n }\n }\n }\n\n private toSignalingUrl(peerUrl: string): string {\n if (peerUrl.startsWith(\"webrtcs://\")) return peerUrl.replace(/^webrtcs:\\/\\//, \"https://\");\n if (peerUrl.startsWith(\"webrtc://\")) return peerUrl.replace(/^webrtc:\\/\\//, \"http://\");\n throw new Error(`Unsupported WebRTC signaling URL: ${peerUrl}`);\n }\n\n private createId(prefix: string): string {\n return `${prefix}-${crypto.randomUUID()}`;\n }\n}\n"],
5
- "mappings": "aAOA,OACE,4BAAAA,MAWK,mBACP,OAAS,cAAAC,EAAY,kBAAAC,EAAgB,kBAAAC,EAAgB,oBAAAC,MAAwB,oBAC7E,OAAS,gBAAAC,MAA+C,YACxD,OACE,eAAeC,EACf,qBAAqBC,MAGhB,SAcP,SAASC,EAAoBC,EAAmC,CAY9D,OAXoBA,GAAW,CAAA,GAAI,QAASC,GAAU,CACpD,MAAMC,EAAO,MAAM,QAAQD,EAAO,IAAI,EAAIA,EAAO,KAAO,CAACA,EAAO,IAAI,EACpE,GAAIA,EAAO,aAAe,QAAa,OAAOA,EAAO,YAAe,SAClE,MAAM,IAAI,UAAU,iEAAiE,EAEvF,OAAOC,EAAK,IAAKC,IAAS,CACxB,KAAMA,EACN,GAAIF,EAAO,WAAa,OAAY,CAAE,SAAUA,EAAO,QAAQ,EAAK,CAAA,EACpE,GAAIA,EAAO,aAAe,OAAY,CAAE,WAAYA,EAAO,UAAU,EAAK,CAAA,GAC1E,CACJ,CAAC,CAEH,CAEA,SAASG,EAA2BC,EAA+B,CACjE,MAAMC,EAAS,CACb,WAAYP,EAAoBM,EAAc,UAAU,EACxD,GAAIA,EAAc,qBAAuB,OACrC,CAAE,mBAAoBA,EAAc,kBAAkB,EACtD,CAAA,GAEN,OAAO,IAAIP,EAAqBQ,CAAM,CACxC,CAEA,SAASC,GAAuB,CAC9B,OAAO,IAAIV,CACb,CAmBM,aAAO,eAAe,CACT,SAAW,IAAI,IACf,WAAa,IAAI,IACjB,eAAiB,IAAI,IACrB,iBAAmB,IAAI,IACvB,sBAAwB,IAAI,IAC5B,eAAiB,IAAI,IACrB,cAAgB,IAAI,IAC7B,gBAAkB,EAClB,QAAU,GACD,aAAe,IAAI,IACnB,WACA,sBACA,mBACA,oBACA,iBACR,kBACQ,qBACA,QAEjB,YAAYW,EAAkC,CAAA,EAAE,CAC9C,KAAK,QAAUA,EACf,KAAK,qBAAuBA,EAAQ,sBAAwBJ,EAC5D,KAAK,kBACFI,EAAQ,mBACTD,EACF,KAAK,WAAaC,EAAQ,YAAc,CAAC,CAAE,KAAM,8BAA8B,CAAE,EACjF,KAAK,sBAAwBA,EAAQ,uBAAyB,IAC9D,KAAK,mBAAqBA,EAAQ,oBAAsB,IACxD,KAAK,oBAAsBA,EAAQ,qBAAuB,KAC1D,KAAK,iBAAmBA,EAAQ,kBAAoB,IACtD,CAEQ,IAAIC,EAA4CC,EAAiBC,EAAc,CACrF,GAAI,KAAK,QAAQ,OAAQ,CACvB,KAAK,QAAQ,OAAOF,EAAOC,EAASC,CAAI,EACxC,MACF,CACA,GAAI,CAAC,KAAK,QAAQ,SAAWF,IAAU,QAAS,OAChD,MAAMG,EAASH,IAAU,QAAU,QAAQ,MAAQ,QAAQA,CAAK,EAC5DE,IAAS,OAAWC,EAAOF,CAAO,EACjCE,EAAOF,EAASC,CAAI,CAC3B,CAEA,QAAQE,EAA2C,CACjD,YAAK,eAAe,IAAIA,CAAQ,EACzB,IAAM,KAAK,eAAe,OAAOA,CAAQ,CAClD,CAEA,gBAAgBA,EAAmD,CACjE,YAAK,iBAAiB,IAAIA,CAAQ,EAC3B,IAAM,KAAK,iBAAiB,OAAOA,CAAQ,CACpD,CAEQ,kBAAkBC,EAAwBC,EAA0B,CAC1E,UAAWF,KAAY,KAAK,iBAC1B,GAAI,CACFA,EAAS,CAAE,GAAGC,EAAQ,QAAS,KAAAC,CAAI,CAAE,CACvC,OAASC,EAAO,CACd,KAAK,IAAI,QAAS,iCAAkCA,CAAK,CAC3D,CAEJ,CAEA,iBAAiBC,EAAiB,CAChC,MAAMH,EAAU,KAAK,eAAe,IAAIG,CAAS,EACjD,GAAI,GAACH,GAAWA,EAAQ,QACxB,MAAO,CACL,GAAIA,EAAQ,GACZ,QAASA,EAAQ,QACjB,eAAgBA,EAAQ,eACxB,aAAcA,EAAQ,aACtB,GAAIA,EAAQ,YAAc,CAAE,YAAaA,EAAQ,WAAW,EAAK,CAAA,EAErE,CAEA,WAAWI,EAAe,CACxB,MAAMJ,EAAU,KAAK,SAAS,IAAII,CAAO,EACzC,GAAI,GAACJ,GAAWA,EAAQ,QAExB,MAAO,CACL,GAAIA,EAAQ,GACZ,QAASA,EAAQ,QACjB,eAAgBA,EAAQ,eACxB,YAAaA,EAAQ,YACrB,aAAcA,EAAQ,aACtB,MAAOA,EAAQ,gBACf,GAAIA,EAAQ,YAAc,CAAE,YAAaA,EAAQ,WAAW,EAAK,CAAA,EAErE,CAEA,MAAM,QAAQK,EAAyB,CACrC,GAAI,KAAK,QAAS,MAAM,IAAI,MAAM,4BAA4B,EAC9D,MAAMC,EAAaD,EACbD,EAAUE,EAAW,SAAWD,EAAQ,IAE9C,GAAIA,EAAQ,QAAQ,QAAS,MAAM3B,EAAW2B,EAAQ,MAAM,EAE5D,MAAML,EAAU,MAAM,KAAK,mBAAmBI,EAASE,CAAU,EAGjE,GAFA,MAAM,KAAK,YAAYN,EAASM,CAAU,EAEtCD,EAAQ,QAAQ,QAAS,MAAM3B,EAAW2B,EAAQ,MAAM,EAE5D,OAAO,KAAK,YAAYL,EAASK,EAASC,EAAW,OAAO,CAC9D,CAEO,MAAM,OAAOZ,EAA+B,CACjD,GAAI,KAAK,QAAS,MAAM,IAAI,MAAM,4BAA4B,EAC9D,MAAMa,EAAgB,IAAI,IACpBC,EAAOd,EAAQ,MAAQ,IACvBe,EAAOf,EAAQ,MAAQ,YACvBgB,EAAgBhB,EAAQ,WAAW,MAAQ,UACjD,GACE,CAACgB,EAAc,WAAW,GAAG,GAC7BA,EAAc,WAAW,IAAI,GAC7B,YAAY,KAAKA,CAAa,GAC9B,MAAM,KAAKA,CAAa,EAAE,KAAMC,GAAQ,CACtC,MAAMC,EAAOD,EAAK,YAAY,CAAC,EAC/B,OAAOC,GAAQ,IAASA,GAAQ,KAAQA,GAAQ,GAClD,CAAC,GACD,IAAI,IAAIF,EAAe,kBAAkB,EAAE,WAAaA,EAExD,MAAM,IAAI,UACR,sFAAsF,EAI1F,GAAIhB,EAAQ,QAAQ,QAClB,MAAM,IAAI,MAAM,yCAAyC,EAG3D,IAAImB,EAAS,GACTC,EACJ,MAAMC,EAAwB,IAAI,IAE5BC,EAAyBlC,EAAa,CAACmC,EAAKC,IAAO,EACjD,SAAW,CACf,MAAMC,EAAW,CAACC,EAAgBC,IAAiB,CAC7CH,EAAI,aAAeA,EAAI,YAC3BA,EAAI,UAAUE,EAAQ,CACpB,eAAgB,mBAChB,gBAAiB,WAClB,EACDF,EAAI,IAAI,KAAK,UAAUG,CAAI,CAAC,EAC9B,EAEA,GAAIR,GAAU,KAAK,QAAS,CAC1BM,EAAS,IAAK,CAAE,MAAO,0BAA0B,CAAE,EACnD,MACF,CAEA,IAAIG,EACJ,GAAI,CACFA,EAAY,IAAI,IAAIL,EAAI,KAAO,IAAK,kBAAkB,CACxD,MAAQ,CACNE,EAAS,IAAK,CAAE,MAAO,qBAAqB,CAAE,EAC9C,MACF,CAEA,GAAIG,EAAU,WAAaZ,EAAe,CACxCS,EAAS,IAAK,CAAE,MAAO,8BAA8B,CAAE,EACvD,MACF,CAEA,IAAII,EACAC,EACJ,GACE,CAAC,OAAQ,SAAU,MAAO,OAAO,EAAE,SAASP,EAAI,QAAU,EAAE,GAC5DvB,EAAQ,WAAW,UACnB,CACA,MAAM+B,EAAa,IAAI,gBACjBC,EAAe,IAAMD,EAAW,MAAK,EACrCE,EAAuB,IAAK,CAChCR,EAAS,IAAK,CAAE,MAAO,kCAAkC,CAAE,CAC7D,EACAF,EAAI,KAAK,UAAWS,CAAY,EAChCR,EAAI,KAAK,QAASQ,CAAY,EAC9BD,EAAW,OAAO,iBAAiB,QAASE,EAAsB,CAAE,KAAM,EAAI,CAAE,EAChFZ,EAAsB,IAAIU,CAAU,EACpC,GAAI,CACF,MAAMG,EAA6C,CAAA,EACnD,SAAW,CAACC,EAAMC,CAAK,IAAK,OAAO,QAAQb,EAAI,OAAO,EAChDa,IAAU,SAAWF,EAAQC,CAAI,EAAIC,GAE3C,MAAMC,EAAU,MAAMrC,EAAQ,UAAU,UAAU,CAChD,SAAU,SACV,OAAQuB,EAAI,OACZ,IAAKA,EAAI,KAAO,IAChB,QAAAW,EACA,OAAQH,EAAW,OACpB,EACD,GAAIA,EAAW,OAAO,SAAWR,EAAI,SAAWC,EAAI,UAAW,OAC/D,GAAIL,GAAU,KAAK,SAAWnB,EAAQ,QAAQ,QAAS,CACrDyB,EAAS,IAAK,CAAE,MAAO,0BAA0B,CAAE,EACnD,MACF,CACA,GAAI,OAAOY,GAAY,UAAYA,IAAY,KAAM,CACnD,GAAI,OAAOA,EAAQ,SAAY,UAAY,CAACA,EAAQ,QAAQ,KAAI,EAAI,CAClEZ,EAAS,IAAK,CAAE,MAAO,4BAA4B,CAAE,EACrD,MACF,CACAI,EAAUQ,EAAQ,QAClBP,EAAgBO,EAAQ,OAC1B,SAAWA,IAAY,GAAM,CAC3BZ,EAAS,IAAK,CAAE,MAAO,qBAAqB,CAAE,EAC9C,MACF,CACF,MAAQ,CACNA,EAAS,IAAK,CAAE,MAAO,qBAAqB,CAAE,EAC9C,MACF,SACEJ,EAAsB,OAAOU,CAAU,EACvCR,EAAI,IAAI,UAAWS,CAAY,EAC/BR,EAAI,IAAI,QAASQ,CAAY,EAC7BD,EAAW,OAAO,oBAAoB,QAASE,CAAoB,CACrE,CACF,CAEA,GAAIV,EAAI,SAAW,SAAU,CAC3B,MAAMd,EAAYmB,EAAU,aAAa,IAAI,WAAW,EAClDU,EAAW7B,EAAY,KAAK,eAAe,IAAIA,CAAS,EAAI,OAClE,GACE6B,IACC,CAACzB,EAAc,IAAIJ,CAAU,GAAK6B,EAAS,QAAQ,UAAYT,GAChE,CACAJ,EAAS,IAAK,CAAE,MAAO,qBAAqB,CAAE,EAC9C,MACF,CACIhB,GAAaI,EAAc,IAAIJ,CAAS,IAC1C,MAAM,KAAK,mBAAmBA,CAAS,EACvCI,EAAc,OAAOJ,CAAS,GAEhCe,EAAI,UAAU,GAAG,EAAE,IAAG,EACtB,MACF,CAEA,GAAID,EAAI,SAAW,MAAO,CACxB,MAAMgB,EAAKX,EAAU,aAAa,IAAI,WAAW,EAC3CtB,EAAUiC,EAAK,KAAK,eAAe,IAAIA,CAAE,EAAI,OACnD,GAAI,CAACjC,GAAW,CAACiC,GAAM,CAAC1B,EAAc,IAAI0B,CAAE,EAAG,CAC7Cd,EAAS,IAAK,CAAE,MAAO,mBAAmB,CAAE,EAC5C,MACF,CACA,GAAInB,EAAQ,QAAQ,UAAYuB,EAAS,CACvCJ,EAAS,IAAK,CAAE,MAAO,qBAAqB,CAAE,EAC9C,MACF,CACA,MAAMe,EAAQlC,EAAQ,QACtB,GAAI,CAACkC,GAASZ,EAAU,aAAa,IAAI,YAAY,IAAMY,EAAM,WAAY,CAC3Ef,EAAS,IAAK,CAAE,MAAO,yBAAyB,CAAE,EAClD,MACF,CACA,MAAMgB,EAASb,EAAU,aAAa,IAAI,QAAQ,GAAK,IACvD,GACE,CAAC,oBAAoB,KAAKa,CAAM,GAChC,CAAC,OAAO,cAAc,OAAOA,CAAM,CAAC,GACpC,OAAOA,CAAM,EAAID,EAAM,WAAW,OAClC,CACAf,EAAS,IAAK,CAAE,MAAO,oBAAoB,CAAE,EAC7C,MACF,CACAA,EAAS,IAAK,CACZ,WAAYe,EAAM,WAClB,WAAYA,EAAM,WAAW,MAAM,OAAOC,CAAM,CAAC,EACjD,KAAMD,EAAM,WAAW,OACxB,EACD,MACF,CAEA,GAAIjB,EAAI,SAAW,QAAUA,EAAI,SAAW,QAAS,CACnDE,EAAS,IAAK,CAAE,MAAO,oBAAoB,CAAE,EAC7C,MACF,CAEA,IAAIiB,EAAM,GACV,GAAI,CACF,gBAAiBC,KAASpB,EAExB,GADAmB,GAAO,OAAO,SAASC,CAAK,EAAIA,EAAM,SAAS,MAAM,EAAI,OAAOA,CAAK,EACjE,OAAO,WAAWD,EAAK,MAAM,EAAI3D,EAA0B,CAC7D0C,EAAS,IAAK,CAAE,MAAO,6BAA6B,CAAE,EACtD,MACF,CAEJ,OAASjB,EAAO,CACdiB,EAAS,IAAK,CAAE,MAAOtC,EAAiBqB,EAAO,qBAAqB,EAAE,OAAO,CAAE,EAC/E,MACF,CAEA,IAAIoC,EACAtC,EAEJ,GAAI,CACF,MAAMuC,EAAU,KAAK,MAAMH,CAAG,EAQ9B,GAAInB,EAAI,SAAW,QAAS,CAC1B,GACE,CAACsB,GACD,OAAOA,EAAQ,WAAc,UAC7B,OAAOA,EAAQ,YAAe,UAC9B,EAAE,cAAeA,GACjB,CACApB,EAAS,IAAK,CAAE,MAAO,8CAA8C,CAAE,EACvE,MACF,CACA,MAAMqB,EAAS,KAAK,eAAe,IAAID,EAAQ,SAAS,EACxD,GAAI,CAACC,GAAU,CAACjC,EAAc,IAAIgC,EAAQ,SAAS,EAAG,CACpDpB,EAAS,IAAK,CAAE,MAAO,mBAAmB,CAAE,EAC5C,MACF,CACA,GAAIqB,EAAO,QAAQ,UAAYjB,EAAS,CACtCJ,EAAS,IAAK,CAAE,MAAO,qBAAqB,CAAE,EAC9C,MACF,CACA,MAAMe,EAAQM,EAAO,QACrB,GAAI,CAACN,GAASA,EAAM,aAAeK,EAAQ,WAAY,CACrDpB,EAAS,IAAK,CAAE,MAAO,yBAAyB,CAAE,EAClD,MACF,CACA,MAAMsB,EAAYF,EAAQ,UAC1B,GACEE,IAAc,OACb,OAAOA,GAAc,UACpB,OAAOA,EAAU,WAAc,UAC/BA,EAAU,UAAU,OAAS,GAC7BA,EAAU,UAAU,OAAS,MAC5BA,EAAU,QAAU,OAClB,OAAOA,EAAU,QAAW,UAAYA,EAAU,OAAO,OAAS,MACpEA,EAAU,eAAiB,OACzB,CAAC,OAAO,UAAUA,EAAU,aAAa,GACxCA,EAAU,cAAgB,GAC1BA,EAAU,cAAgB,QAC7BA,EAAU,QAAU,MAAQA,EAAU,eAAiB,MACvDA,EAAU,kBAAoB,OAC5B,OAAOA,EAAU,kBAAqB,UACrCA,EAAU,iBAAiB,OAAS,MAC1C,CACAtB,EAAS,IAAK,CAAE,MAAO,uBAAuB,CAAE,EAChD,MACF,CACA,GAAIe,EAAM,aAAeA,EAAM,aAAe,IAAK,CACjDf,EAAS,IAAK,CAAE,MAAO,gDAAgD,CAAE,EACzE,MACF,CACAe,EAAM,cACFO,IAAc,OAAMP,EAAM,YAAc,IAC5C,MAAMQ,EAAQR,EAAM,YAAY,KAAK,SAAW,CAC9C,GAAIM,EAAO,QAAUA,EAAO,UAAYN,EACtC,MAAM,IAAI,MAAM,wBAAwB,EAC1C,MAAMM,EAAO,eAAe,gBAAgBC,CAAS,CACvD,CAAC,EACDP,EAAM,YAAcQ,EAAM,MAAM,IAAK,CAAE,CAAC,EACxC,GAAI,CACF,MAAMA,EACNxB,EAAI,UAAU,GAAG,EAAE,IAAG,CACxB,MAAQ,CACNC,EAAS,IAAK,CAAE,MAAO,+BAA+B,CAAE,CAC1D,CACA,MACF,CACA,GAAI,CAACoB,GAAYA,EAAQ,UAAY,QAAa,OAAOA,EAAQ,SAAY,UAAY,CACvFpB,EAAS,IAAK,CAAE,MAAO,sBAAsB,CAAE,EAC/C,MACF,CACA,GACE,OAAOoB,EAAQ,WAAc,UAC7BA,EAAQ,UAAU,OAAS,GAC3BA,EAAQ,UAAU,OAAS,KAC3BA,EAAQ,KAAK,OAAS,SACtB,OAAOA,EAAQ,IAAI,KAAQ,SAC3B,CACApB,EAAS,IAAK,CAAE,MAAO,0CAA0C,CAAE,EACnE,MACF,CAEA,GAAIN,GAAU,KAAK,SAAWnB,EAAQ,QAAQ,SAAWuB,EAAI,SAAWC,EAAI,UAC1E,MAAM,IAAI,MAAM,kCAAkC,EAEpD,MAAMf,EAAYoC,EAAQ,UAE1B,GADAvC,EAAU,KAAK,eAAe,IAAIG,CAAS,EACvCH,GAAW,CAACO,EAAc,IAAIJ,CAAS,EAAG,CAC5CgB,EAAS,IAAK,CAAE,MAAO,qCAAqC,CAAE,EAC9D,MACF,CACA,GAAInB,GAAWA,EAAQ,QAAQ,UAAYuB,EAAS,CAClDJ,EAAS,IAAK,CAAE,MAAO,qBAAqB,CAAE,EAC9C,MACF,CACA,GAAI,KAAK,sBAAsB,IAAIhB,CAAS,EAAG,CAC7CgB,EAAS,IAAK,CAAE,MAAO,oBAAoB,CAAE,EAC7C,MACF,CACA,GAAInB,GAAS,aAAeA,GAAS,SAAS,UAAW,CACvDmB,EAAS,IAAK,CAAE,MAAO,yCAAyC,CAAE,EAClE,MACF,CAEA,GAAI,CAACnB,GAAWA,EAAQ,OAAQ,CAC9BsC,EAAK,KAAK,qBAAqB,CAAE,WAAY,KAAK,UAAU,CAAE,EAC9D,MAAMb,EAAa,IAAI,gBACvBzB,EAAU,CACR,WAAAyB,EACA,QAAS,OAAO,OAAO,CACrB,UAAAtB,EACA,QAAAoB,EACA,cAAAC,EACA,OAAQC,EAAW,OACpB,EACD,QAAS,IAAMlB,EAAc,OAAOJ,CAAS,EAC7C,GAAIA,EACJ,eAAgBmC,EAChB,aAAc,KAAK,kBAAiB,EACpC,cAAe,IAAI,IACnB,oBAAqB,GACrB,gBAAiB,GACjB,OAAQ,IAEV/B,EAAc,IAAIJ,CAAS,EAC3B,KAAK,eAAe,IAAIA,EAAWH,CAAO,EAC1C,KAAK,uBAAuBA,EAASN,EAAQ,SAAS,EACtD,KAAK,kBAAkBM,EAAS,SAAS,CAC3C,MACEsC,EAAKtC,EAAQ,eAGfA,EAAQ,SAAS,OAAM,EACvB,OAAOA,EAAQ,QACfA,EAAQ,YAAc,GACtB,MAAM2C,EAAgB3C,EAChB4C,EAAa,IAAK,CACjB1B,EAAI,eAAoB,KAAK,mBAAmByB,EAAc,EAAE,CACvE,EACAzB,EAAI,KAAK,QAAS0B,CAAU,EAC5B,MAAMC,EAAiB,IAAM1B,EAAS,IAAK,CAAE,MAAO,uBAAuB,CAAE,EAQ7E,GAPAwB,EAAc,WAAW,OAAO,iBAAiB,QAASE,EAAgB,CAAE,KAAM,EAAI,CAAE,EACxF3B,EAAI,KAAK,SAAU,IAAK,CACtByB,EAAc,WAAW,OAAO,oBAAoB,QAASE,CAAc,EAC3E3B,EAAI,IAAI,QAAS0B,CAAU,CAC7B,CAAC,EACD,MAAM,KAAK,qBAAqBN,EAAIC,EAAQ,GAAG,EAC/C,MAAM,KAAK,6BAA6BvC,CAAO,EAC3CA,EAAQ,OAAQ,MAAM,IAAI,MAAM,uBAAuB,EAE3D,MAAM8C,EAAS,MAAMR,EAAG,aAAY,EACpC,IAAIJ,EACJ,GAAIK,EAAQ,UAAY,GAAM,CAC5B,MAAMQ,EAAOT,EACPU,EAAeC,GAA4D,CAC/E,GAAI,GAACf,GAASS,EAAc,QAAUA,EAAc,UAAYT,GAChE,IAAIA,EAAM,WAAW,QAAU,IAAK,CAC7B,KAAK,mBAAmBS,EAAc,EAAE,EAC7C,MACF,CACAT,EAAM,WAAW,KAAKe,EAAM,UAAYA,EAAM,UAAU,OAAM,EAAK,IAAI,EACzE,EACAF,EAAK,iBAAiB,eAAgBC,CAAW,EACjDd,EAAQ,CACN,WAAY,OAAO,WAAU,EAC7B,WAAY,CAAA,EACZ,UAAW,GACX,YAAa,EACb,YAAa,GACb,YAAa,QAAQ,QAAO,EAC5B,OAAQ,IAAMa,EAAK,oBAAoB,eAAgBC,CAAW,GAEpEhD,EAAQ,QAAUkC,EAClB,MAAMgB,EAAW,KAAK,aACpBH,EAAK,oBAAoBD,CAAM,EAC/B,KAAK,sBACL9C,EAAQ,WAAW,MAAM,EAE3B,IAAImD,EACJ,GAAI,CACF,MAAM,KAAK,aACT,IAAI,QAAc,CAACC,EAASC,IAAU,CACpC,MAAMC,EAAQ,IAAK,CACjB,GAAIX,EAAc,OAAQ,CACxBU,EAAO,IAAI,MAAM,uBAAuB,CAAC,EACzC,MACF,CACA,GACEN,EAAK,iBAAmB,UACxBA,EAAK,kBAAkB,OAAS,SAChC,CACAK,EAAO,EACP,MACF,CACAD,EAAQ,WAAWG,EAAO,CAAC,CAC7B,EACKJ,EAAS,MAAMG,CAAM,EAC1BC,EAAK,CACP,CAAC,EACD,KAAK,sBACLtD,EAAQ,WAAW,MAAM,CAE7B,SACE,aAAamD,CAAK,CACpB,CACKD,EAAS,KACZ,IAAK,CACChB,GAASS,EAAc,UAAYT,IACrCA,EAAM,UAAY,GACbA,EAAM,WAAW,SAAS,IAAI,GAAGA,EAAM,WAAW,KAAK,IAAI,EAEpE,EACA,IAAK,CACCS,EAAc,UAAYT,GAAY,KAAK,mBAAmBS,EAAc,EAAE,CACpF,CAAC,CAEL,SACE,MAAM,KAAK,aAAaL,EAAG,oBAAoBQ,CAAM,EAAG,KAAK,qBAAqB,EAC9E,CAAE,MAAM,KAAK,4BAA4BR,EAAI,KAAK,qBAAqB,EACzE,MAAM,IAAI,MAAM,4DAA4D,EAGhF,MAAMiB,EAAmBjB,EAAG,iBAC5B,GAAI,CAACiB,GAAkB,KAAOA,EAAiB,OAAS,SACtD,MAAM,IAAI,MAAM,oCAAoC,EAGtD,GAAI1C,GAAUb,EAAQ,OAAQ,MAAM,IAAI,MAAM,uBAAuB,EACrEA,EAAQ,YAAc,GACtBmB,EACE,IACAe,EAAQ,CAAE,IAAKqB,EAAkB,WAAYrB,EAAM,UAAU,EAAKqB,CAAgB,CAEtF,OAASrD,EAAO,CAEd,GADA,KAAK,IAAI,QAAS,0IAAiDA,CAAK,EACpEF,EACF,MAAM,KAAK,mBAAmBA,EAAQ,EAAE,UAC/BsC,EACT,GAAI,CACF,MAAMA,EAAG,MAAK,CAChB,MAAQ,CAAC,CAEXnB,EAAS,IAAK,CAAE,MAAOtC,EAAiBqB,EAAO,kBAAkB,EAAE,OAAO,CAAE,CAC9E,CACF,GAAE,CACJ,CAAC,EAED,MAAM,IAAI,QAAc,CAACkD,EAASC,IAAU,CAC1C,MAAMG,EAAWtD,GAAgB,CAC/Bc,EAAW,IAAI,YAAayC,CAAW,EACvCJ,EAAOnD,CAAK,CACd,EACMuD,EAAc,IAAK,CACvBzC,EAAW,IAAI,QAASwC,CAAO,EAC/BJ,EAAO,CACT,EACApC,EAAW,KAAK,QAASwC,CAAO,EAChCxC,EAAW,KAAK,YAAayC,CAAW,EACxCzC,EAAW,OAAOR,EAAMC,CAAI,CAC9B,CAAC,EAED,KAAK,IAAI,OAAQ,yCAAyCA,CAAI,IAAID,CAAI,GAAGE,CAAa,EAAE,EAExF,MAAMgD,EAAU,IAAK,CACdC,EAAW,EAAG,MAAOzD,GAAS,CACjC,KAAK,IAAI,QAAS,4CAA6CA,CAAK,CACtE,CAAC,CACH,EAEMyD,EAAc,IAAoB,CACtC,GAAI7C,EAAc,OAAOA,EACzBD,EAAS,GACT,UAAWY,KAAcV,EAAuBU,EAAW,MAAK,EAChE,OAAA/B,EAAQ,QAAQ,oBAAoB,QAASgE,CAAO,EACpD5C,GAAgB,SAAW,CACzB,MAAM8C,EAAU5C,EAAW,UACvB,IAAI,QAAc,CAACoC,EAASC,IAAU,CACpCrC,EAAW,MAAOd,GAAWA,EAAQmD,EAAOnD,CAAK,EAAIkD,EAAO,CAAG,CACjE,CAAC,EACD,QAAQ,QAAO,EACnB,MAAM,QAAQ,IAAI,CAAC,GAAG7C,CAAa,EAAE,IAAK0B,GAAO,KAAK,mBAAmBA,CAAE,CAAC,CAAC,EAC7E1B,EAAc,MAAK,EACnB,MAAMqD,EACN,KAAK,cAAc,OAAOD,CAAW,CACvC,GAAE,EACK7C,CACT,EAEA,OAAApB,EAAQ,QAAQ,iBAAiB,QAASgE,EAAS,CAAE,KAAM,EAAI,CAAE,EACjE,KAAK,cAAc,IAAIC,CAAW,EAC9BjE,EAAQ,QAAQ,SAAW,CAACmB,GAAQ,MAAM8C,EAAW,EAElD,CACL,MAAOA,EACP,OAAQ,CACN,WAAY,KAAK,WAAW,KAAK,IAAI,EACrC,iBAAkB,KAAK,iBAAiB,KAAK,IAAI,EACjD,QAAS,KAAK,QAAQ,KAAK,IAAI,EAC/B,mBAAqB1B,GACnB1B,EAAc,IAAI0B,CAAE,EAAI,KAAK,mBAAmBA,CAAE,EAAI,QAAQ,QAAO,EACvE,gBAAiB,KAAK,gBAAgB,KAAK,IAAI,GAGrD,CAEA,MAAM,OAAK,CACT,KAAK,QAAU,GACf,MAAM,QAAQ,IAAI,CAAC,GAAG,KAAK,YAAY,EAAE,IAAKK,GAAOA,EAAG,MAAK,EAAG,MAAM,IAAK,CAAE,CAAC,CAAC,CAAC,EAChF,MAAM,QAAQ,WAAW,KAAK,WAAW,OAAM,CAAE,EACjD,MAAM,QAAQ,IACZ,CAAC,GAAG,KAAK,SAAS,OAAM,CAAE,EAAE,IAAKtC,GAAY,KAAK,qBAAqBA,EAAS,EAAI,CAAC,CAAC,EAExF,MAAM,QAAQ,IAAI,CAAC,GAAG,KAAK,aAAa,EAAE,IAAK2D,GAAgBA,EAAW,CAAE,CAAC,CAC/E,CAEA,MAAM,SAAO,CACX,MAAM,KAAK,MAAK,CAClB,CAEQ,MAAM,mBACZvD,EACAC,EAA+B,CAE/B,MAAM2B,EAAW,KAAK,SAAS,IAAI5B,CAAO,EAC1C,GACE4B,GACA,CAACA,EAAS,QACVA,EAAS,YAAY,aAAe,QACpCA,EAAS,eAAe,kBAAoB,UAC5CA,EAAS,eAAe,kBAAoB,SAE5C,OAAOA,EAGLA,GACF,MAAM,KAAK,qBAAqBA,EAAU,EAAI,EAGhD,MAAM6B,EAAW,KAAK,WAAW,IAAIzD,CAAO,EAC5C,GAAIyD,EAAU,OAAOA,EAErB,MAAMC,EAAoB,KAAK,oBAAoB1D,EAASC,CAAO,EACnE,KAAK,WAAW,IAAID,EAAS0D,CAAiB,EAE9C,GAAI,CACF,MAAM9D,EAAU,MAAM8D,EACtB,GAAI,KAAK,SAAW9D,EAAQ,OAC1B,YAAM,KAAK,qBAAqBA,EAAS,EAAI,EACvC,IAAI,MAAM,4BAA4B,EAE9C,YAAK,SAAS,IAAII,EAASJ,CAAO,EAC3BA,CACT,SACM,KAAK,WAAW,IAAII,CAAO,IAAM0D,GACnC,KAAK,WAAW,OAAO1D,CAAO,CAElC,CACF,CAEQ,MAAM,oBACZA,EACAC,EAA+B,CAE/B,MAAM0D,EAAe,KAAK,eAAe3D,CAAO,EAC1C6B,EAAK,KAAK,SAAS,SAAS,EAC5BK,EAAK,KAAK,qBAAqB,CACnC,WAAYjC,EAAQ,YAAc,KAAK,WACxC,EACD,KAAK,aAAa,IAAIiC,CAAE,EACxB,MAAM0B,EAAU1B,EAAG,kBAAkBjC,EAAQ,cAAgB,4BAA6B,CACxF,QAAS,GACV,EAEKL,EAAyB,CAC7B,GAAAiC,EACA,QAAA7B,EACA,aAAA2D,EACA,eAAgBzB,EAChB,YAAa0B,EACb,aAAc,KAAK,kBAAiB,EACpC,GAAI3D,EAAQ,YAAc,CAAE,YAAaA,EAAQ,WAAW,EAAK,CAAA,EACjE,cAAe,IAAI,IACnB,QAAS,IAAI,IACb,YAAa,QAAQ,QAAO,EAC5B,OAAQ,GACR,gBAAiB,CAAA,GAGb4D,EAAiB,IAAI,IAGrBC,EAAsB,IAAI,IAC1BC,EAAkB,IAAI,IAE5B7B,EAAG,iBAAiB,QAAUW,GAA2B,CACvD,KAAM,CAAE,MAAAmB,CAAK,EAAKnB,EACbjD,EAAQ,aAAa,UAAS,EAAG,SAASoE,CAAK,GAAGpE,EAAQ,aAAa,SAASoE,CAAK,EAC1F,UAAWrE,KAAYkE,EAAgBlE,EAAS,CAAE,MAAAqE,EAAO,OAAQpE,EAAQ,YAAY,CAAE,EACvF,UAAWD,KAAYoE,EAAiBpE,EAAS,CAAE,OAAQC,EAAQ,YAAY,CAAE,EACjFoE,EAAM,iBAAiB,QAAS,IAAK,CACnC,UAAWrE,KAAYmE,EAAqBnE,EAAS,CAAE,MAAAqE,CAAK,CAAE,CAChE,CAAC,CACH,CAAC,EAEDpE,EAAQ,gBAAkB,CACxB,YAAaA,EAAQ,YACrB,aAAcA,EAAQ,aACtB,SAAU,CAACoE,EAAOC,IAChB,KAAK,iBAAiBrE,EAAS,SAAW,CACxCsC,EAAG,SAAS8B,EAAOC,GAAUrE,EAAQ,aAAe,KAAK,kBAAiB,CAAE,EACxEoE,EAAM,IAAIpE,EAAQ,cAAc,IAAIoE,EAAM,EAAE,EAChD,MAAM,KAAK,UAAUpE,CAAO,CAC9B,CAAC,EACH,aAAc,MAAOsE,EAAyBF,IAAqB,CACjE,MAAMG,EAASjC,EAAG,WAAU,EAAG,KAAMkC,GAAWA,EAAE,OAASA,EAAE,MAAM,OAASF,CAAI,EAC5EC,GACF,MAAMA,EAAO,aAAaH,CAAK,CAEnC,EACA,YAAcA,GACZ,KAAK,iBAAiBpE,EAAS,SAAW,CACxC,MAAMuE,EAASjC,EAAG,WAAU,EAAG,KAAMmC,GAASA,EAAK,QAAUL,CAAK,EAC9DG,IACFjC,EAAG,YAAYiC,CAAM,EACjBH,EAAM,IAAIpE,EAAQ,cAAc,OAAOoE,EAAM,EAAE,EACnD,MAAM,KAAK,UAAUpE,CAAO,EAEhC,CAAC,EACH,MAAO,SAAW,CAChB,MAAM,KAAK,qBAAqBA,EAAS,EAAI,CAC/C,EACA,GAAGiD,EAAelD,EAAa,CAC7B,OAAIkD,IAAU,SAASgB,EAAe,IAAIlE,CAAQ,EAC9CkD,IAAU,cAAciB,EAAoB,IAAInE,CAAQ,EACxDkD,IAAU,UAAUkB,EAAgB,IAAIpE,CAAQ,EAC7C,IAAK,CACVkE,EAAe,OAAOlE,CAAQ,EAC9BmE,EAAoB,OAAOnE,CAAQ,EACnCoE,EAAgB,OAAOpE,CAAQ,CACjC,CACF,GAGF,GAAI,CACF,YAAK,uBAAuBC,CAAO,EACnC,KAAK,eAAeA,EAASK,EAAQ,WAAW,EAChD,KAAK,uBAAuBL,EAASK,EAAQ,aAAcA,EAAQ,YAAY,EAC/E,MAAM,KAAK,UAAUL,EAASK,EAAQ,MAAM,EAC5C,MAAM,KAAK,mBAAmB2D,EAAS,KAAK,oBAAqB3D,EAAQ,MAAM,EACxEL,CACT,OAASE,EAAO,CACd,YAAM,KAAK,qBAAqBF,EAAS,EAAI,EACvCE,CACR,SACE,KAAK,aAAa,OAAOoC,CAAE,CAC7B,CACF,CAEQ,uBAAuBtC,EAAsB,CACnD,KAAM,CAAE,eAAgBsC,EAAI,YAAa0B,CAAO,EAAKhE,EAErDsC,EAAG,iBAAiB,QAAUW,GAAc,CAC1C,MAAMmB,EAAQnB,EAAM,MACfjD,EAAQ,aAAa,UAAS,EAAG,KAAMyE,GAAcA,EAAK,KAAOL,EAAM,EAAE,GAC5EpE,EAAQ,aAAa,SAASoE,CAAK,EAGrC,KAAK,UAAU,CACb,KAAM,SACN,UAAWpE,EAAQ,GACnB,QAASA,EAAQ,QACjB,eAAgBsC,EAChB,MAAA8B,EACA,OAAQpE,EAAQ,aAChB,QAASiD,EAAM,QAChB,CACH,CAAC,EAEDX,EAAG,iBAAiB,wBAAyB,IAAK,EAC5CA,EAAG,kBAAoB,UAAYA,EAAG,kBAAoB,WACvD,KAAK,qBAAqBtC,EAAS,EAAI,CAEhD,CAAC,EAEDgE,EAAQ,iBAAiB,UAAYf,GAAuB,CAC1D,IAAIrD,EACJ,GAAI,CACFA,EAAU,KAAK,MAAM,OAAOqD,EAAM,IAAI,CAAC,CACzC,MAAQ,CACN,MACF,CAEA,GAAI,CAACrD,GAAW,OAAOA,GAAY,UAAY,OAAOA,EAAQ,WAAc,SAAU,OACtF,MAAM8E,EAAU1E,EAAQ,QAAQ,IAAIJ,EAAQ,SAAS,EACrD,GAAK8E,EAEL,GAAI,CACF,MAAMrD,EAAO1C,EAAeiB,EAAQ,IAAI,EACxC,KAAK,cAAcI,EAASJ,EAAQ,SAAS,EAC7C8E,EAAQ,QAAQ,CACd,OAAQ,OAAO9E,EAAQ,QAAW,SAAWA,EAAQ,OAAS,IAC9D,WAAY,OAAOA,EAAQ,YAAe,SAAWA,EAAQ,WAAa,KAC1E,QACEA,EAAQ,SAAW,OAAOA,EAAQ,SAAY,SACzCA,EAAQ,QACT,CAAA,EACN,KAAAyB,EACA,IAAK,OAAOzB,EAAQ,KAAQ,SAAWA,EAAQ,IAAMI,EAAQ,QAC9D,CACH,OAASE,EAAO,CACd,KAAK,cAAcF,EAASJ,EAAQ,SAAS,EAC7C8E,EAAQ,OAAO7F,EAAiBqB,EAAO,yBAAyB,CAAC,CACnE,CACF,CAAC,EAED8D,EAAQ,iBAAiB,QAAS,IAAK,CACrC,KAAK,iBAAiBhE,EAAS,IAAI,MAAM,2BAA2B,CAAC,EAChE,KAAK,qBAAqBA,EAAS,EAAI,CAC9C,CAAC,EAEDgE,EAAQ,iBAAiB,QAAS,IAAK,CACrC,KAAK,iBAAiBhE,EAAS,IAAI,MAAM,0BAA0B,CAAC,CACtE,CAAC,CACH,CAEQ,iBAAiBA,EAAwB2E,EAA8B,CAC7E,MAAMD,EAAU1E,EAAQ,YACrB,MAAM,IAAG,EAAY,EACrB,KAAK,SAAW,CACf,GAAIA,EAAQ,OAAQ,MAAM,IAAI,MAAM,uBAAuB,EAC3D,GAAI,CACF,MAAM2E,EAAS,CACjB,OAASzE,EAAO,CACd,YAAM,KAAK,qBAAqBF,EAAS,EAAI,EACvCE,CACR,CACF,CAAC,EACH,OAAAF,EAAQ,YAAc0E,EACfA,CACT,CAEQ,MAAM,YACZ1E,EACAK,EAA+B,CAM3B,EAHMA,EAAQ,aACRA,EAAQ,cACRA,EAAQ,eACYL,EAAQ,QAEtC,MAAM,KAAK,iBAAiBA,EAAS,SAAW,CAC9C,IAAI4E,EAAU,KAAK,eAAe5E,EAASK,EAAQ,WAAW,EAC9DuE,EACE,KAAK,uBAAuB5E,EAASK,EAAQ,aAAcA,EAAQ,YAAY,GAAKuE,EAElFvE,EAAQ,cACVL,EAAQ,YAAcK,EAAQ,YAC7BL,EAAQ,gBAAwD,YAC/DK,EAAQ,aAERuE,GAAS,MAAM,KAAK,UAAU5E,EAASK,EAAQ,MAAM,CAC3D,CAAC,CACH,CAEQ,eAAeL,EAAwBqE,EAA0B,CACvE,GAAI,CAACA,EAAQ,MAAO,GACpB,IAAIO,EAAU,GAEd,UAAWR,KAASC,EAAO,UAAS,EAC9BD,EAAM,aAAe,QAAUpE,EAAQ,cAAc,IAAIoE,EAAM,EAAG,IACtEpE,EAAQ,eAAe,SAASoE,EAAOC,CAAM,EAC7CrE,EAAQ,cAAc,IAAIoE,EAAM,EAAG,EACnCQ,EAAU,IAGZ,OAAOA,CACT,CAEQ,uBACN5E,EACA6E,EACAC,EAAsB,CAEtB,MAAMxC,EAAKtC,EAAQ,eACnB,IAAI4E,EAAU,GAEd,MAAMG,EAAgB,CAACT,EAAyBU,IAAgC,CAC9E,GAAI,CAACA,EAAS,OACO1C,EAClB,gBAAe,EACf,KACEmC,GACCA,EAAK,SAAS,MAAM,OAASH,IAC5BG,EAAK,YAAc,YAAcA,EAAK,YAAc,WAAW,IAGpEnC,EAAG,eAAegC,EAAM,CAAE,UAAW,UAAU,CAAE,EACjDM,EAAU,GAEd,EAEA,OAAAG,EAAc,QAASF,CAAY,EACnCE,EAAc,QAASD,CAAY,EAE5BF,CACT,CAEQ,MAAM,UAAU5E,EAAwBiF,EAAoB,CAClE,GAAIA,GAAQ,QAAS,MAAMvG,EAAWuG,CAAM,EAE5C,MAAM3C,EAAKtC,EAAQ,eACnB,GAAIsC,EAAG,iBAAmB,SACxB,MAAM,IAAI,MAAM,4CAA4CA,EAAG,cAAc,EAAE,EAGjF,GAAI,KAAK,QAAQ,WAAY,OAAO,KAAK,iBAAiBtC,EAASiF,CAAM,EAEzE,MAAMC,EAAQ,MAAM5C,EAAG,YAAW,EAClC,MAAM,KAAK,aAAaA,EAAG,oBAAoB4C,CAAK,EAAG,KAAK,sBAAuBD,CAAM,EACzF,MAAM,KAAK,4BAA4B3C,EAAI,KAAK,qBAAqB,EAErE,MAAMiB,EAAmBjB,EAAG,iBAC5B,GAAI,CAACiB,GAAkB,KAAOA,EAAiB,OAAS,QACtD,MAAM,IAAI,MAAM,mCAAmC,EAGrD,MAAM9B,EAAa,IAAI,gBACjB0B,EAAQ,WAAW,IAAM1B,EAAW,MAAK,EAAI,KAAK,kBAAkB,EACpE0D,EAAgB,IAAM1D,EAAW,MAAMwD,GAAQ,MAAM,EAC3DA,GAAQ,iBAAiB,QAASE,EAAe,CAAE,KAAM,EAAI,CAAE,EAC3DF,GAAQ,SAASE,EAAa,EAElC,GAAI,CACF,MAAMC,EAAW,MAAM,MAAMpF,EAAQ,aAAc,CACjD,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAkB,EAC7C,KAAM,KAAK,UAAU,CAAE,UAAWA,EAAQ,GAAI,IAAKuD,CAAgB,CAAE,EACrE,OAAQ9B,EAAW,OACpB,EAED,GAAI,CAAC2D,EAAS,GAAI,CAChB,MAAMC,EAAO,MAAMD,EAAS,KAAI,EAChC,MAAM,IAAI,MACR,yBAAyBA,EAAS,MAAM,IAAIA,EAAS,UAAU,GAAGC,EAAO,WAAMA,CAAI,GAAK,EAAE,EAAE,CAEhG,CAEA,MAAMvC,EAAU,MAAMsC,EAAS,KAAI,EACnC,GAAItC,EAAO,OAAS,UAAY,OAAOA,EAAO,KAAQ,SACpD,MAAM,IAAI,MAAM,iDAAiD,EAGnE,MAAM,KAAK,qBAAqBR,EAAIQ,CAAM,CAC5C,OAAS5C,EAAO,CACd,MAAI+E,GAAQ,QAAevG,EAAWuG,CAAM,EACxCxD,EAAW,OAAO,QACd,IAAI,MAAM,6BAA6B,KAAK,kBAAkB,KAAK,EAErE5C,EAAiBqB,EAAO,yBAAyB,CACzD,SACE,aAAaiD,CAAK,EAClB8B,GAAQ,oBAAoB,QAASE,CAAa,CACpD,CACF,CAEQ,MAAM,iBAAiBnF,EAAwBiF,EAAoB,CACzE,MAAM3C,EAAKtC,EAAQ,eACbyB,EAAa,IAAI,gBACvBzB,EAAQ,aAAeyB,EACvB,MAAM6D,EAAQ,IAAM7D,EAAW,MAAMwD,GAAQ,MAAM,EACnDA,GAAQ,iBAAiB,QAASK,EAAO,CAAE,KAAM,EAAI,CAAE,EACnDL,GAAQ,SAASK,EAAK,EAC1B,MAAMC,EAA6C,CAAA,EACnD,IAAIC,EAAQ,GACRC,EACAC,EACJ,MAAM1C,EAAeC,GAA4D,CAC/E,GAAIuC,GAAS/D,EAAW,OAAO,QAAS,OACxC,MAAMgB,EAAYQ,EAAM,UAAYA,EAAM,UAAU,OAAM,EAAK,KAC3DR,GAAW,YAAc,KACzB8C,EAAW,QAAU,KAAO,CAAC,KAAK,sBAAsB9C,CAAS,EACnEiD,EAAU,IAAI,MAAM,2CAA2C,GAE/DH,EAAW,KAAK9C,CAAS,EACrBA,IAAc,OAAM+C,EAAQ,KAElCC,IAAM,EACR,EACAnD,EAAG,iBAAiB,eAAgBU,CAAW,EAC/C,IAAI2C,EACJ,MAAMC,EAAU,WACd,IAAMnE,EAAW,MAAM,IAAI,MAAM,uBAAuB,CAAC,EACzD,KAAK,sBAAwB,KAAK,kBAAkB,EAEtD,GAAI,CACF,MAAMyD,EAAQ,MAAM,KAAK,aACvB5C,EAAG,YAAW,EACd,KAAK,mBACLb,EAAW,MAAM,EAEbyB,EAAW,KAAK,aACpBZ,EAAG,oBAAoB4C,CAAK,EAC5B,KAAK,sBACLzD,EAAW,MAAM,EAEnB,MAAM,KAAK,aACT,IAAI,QAAc,CAAC2B,EAASC,IAAU,CACpC,MAAMC,EAAQ,IAAK,CACjB,GAAIhB,EAAG,iBAAmB,oBAAsBA,EAAG,kBAAkB,IACnE,OAAOc,EAAO,EAChBuC,EAAY,WAAWrC,EAAO,CAAC,CACjC,EACKJ,EAAS,MAAMG,CAAM,EAC1BC,EAAK,CACP,CAAC,EACD,KAAK,sBACL7B,EAAW,MAAM,EAEnB,aAAakE,CAAS,EACtB,MAAMP,EAAW,MAAM,KAAK,aAC1B,MAAMpF,EAAQ,aAAc,CAC1B,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAkB,EAC7C,KAAM,KAAK,UAAU,CAAE,UAAWA,EAAQ,GAAI,IAAKsC,EAAG,iBAAkB,QAAS,EAAI,CAAE,EACvF,OAAQb,EAAW,OACpB,EACD,KAAK,mBACLA,EAAW,MAAM,EAEnB,GAAI,CAAC2D,EAAS,GACZ,MAAM,IAAI,MAAM,yBAAyBA,EAAS,MAAM,IAAIA,EAAS,UAAU,EAAE,EACnF,MAAMS,EAAkB,MAAM,KAAK,aACjCT,EAAS,KAAI,EACb,KAAK,mBACL3D,EAAW,MAAM,EAEnB,GACE,CAACoE,GACD,OAAOA,GAAW,UAClB,EAAE,eAAgBA,IAClB,OAAOA,EAAO,YAAe,UAC7B,CAACA,EAAO,YACRA,EAAO,WAAW,OAAS,KAC3B,EAAE,QAASA,IACX,CAACA,EAAO,KACR,OAAOA,EAAO,KAAQ,UACtB,EAAE,SAAUA,EAAO,MACnBA,EAAO,IAAI,OAAS,UACpB,EAAE,QAASA,EAAO,MAClB,OAAOA,EAAO,IAAI,KAAQ,UAC1B,CAACA,EAAO,IAAI,IAEZ,MAAM,IAAI,MAAM,qDAAqD,EAEvE,MAAMC,EAAaD,EAAO,WAC1B,MAAM,KAAK,aACT,KAAK,qBAAqBvD,EAAI,CAAE,KAAM,SAAU,IAAKuD,EAAO,IAAI,GAAG,CAAE,EACrE,KAAK,mBACLpE,EAAW,MAAM,EAEnB,MAAMsE,EAAS,SAAW,CACxB,IAAIC,EAAQ,EACZ,OAAa,CACX,GAAIvE,EAAW,OAAO,QAAS,MAAM/C,EAAW+C,EAAW,MAAM,EACjE,GAAIiE,EAAS,MAAMA,EACnB,GAAIM,IAAUT,EAAW,OAAQ,CAC/B,MAAM,IAAI,QAAc,CAACnC,EAASC,IAAU,CAC1C,MAAMK,EAAU,IAAK,CACnB+B,EAAO,OACPpC,EAAO3E,EAAW+C,EAAW,MAAM,CAAC,CACtC,EACAgE,EAAO,IAAK,CACVhE,EAAW,OAAO,oBAAoB,QAASiC,CAAO,EACtD+B,EAAO,OACPrC,EAAO,CACT,EACA3B,EAAW,OAAO,iBAAiB,QAASiC,EAAS,CAAE,KAAM,EAAI,CAAE,EAC/DjC,EAAW,OAAO,SAASiC,EAAO,CACxC,CAAC,EACD,QACF,CACA,MAAMjB,EAAY8C,EAAWS,GAAO,EAC9BC,EAAQ,MAAM,MAAMjG,EAAQ,aAAc,CAC9C,OAAQ,QACR,QAAS,CAAE,eAAgB,kBAAkB,EAC7C,KAAM,KAAK,UAAU,CAAE,UAAWA,EAAQ,GAAI,WAAA8F,EAAY,UAAArD,CAAS,CAAE,EACrE,OAAQhB,EAAW,OACpB,EACD,GAAIwE,EAAM,SAAW,IAAK,MAAM,IAAI,MAAM,6BAA6BA,EAAM,MAAM,EAAE,EACrF,GAAIxD,IAAc,KAAM,MAC1B,CACF,EACMyD,EAAW,SAAW,CAC1B,IAAI/D,EAAS,EACb,OAAa,CACX,MAAM9C,EAAM,IAAI,IAAIW,EAAQ,YAAY,EACxCX,EAAI,aAAa,IAAI,YAAaW,EAAQ,EAAE,EAC5CX,EAAI,aAAa,IAAI,aAAcyG,CAAU,EAC7CzG,EAAI,aAAa,IAAI,SAAU,OAAO8C,CAAM,CAAC,EAC7C,MAAMgE,EAAe,MAAM,MAAM9G,EAAK,CAAE,OAAQoC,EAAW,MAAM,CAAE,EACnE,GAAI,CAAC0E,EAAa,GAAI,MAAM,IAAI,MAAM,2BAA2BA,EAAa,MAAM,EAAE,EACtF,MAAMC,EAAgB,MAAMD,EAAa,KAAI,EAC7C,GACE,CAACC,GACD,OAAOA,GAAS,UAChB,EAAE,eAAgBA,IAClBA,EAAK,aAAeN,GACpB,EAAE,eAAgBM,IAClB,CAAC,MAAM,QAAQA,EAAK,UAAU,GAC9B,EAAE,SAAUA,IACZ,CAAC,OAAO,UAAUA,EAAK,IAAI,GAC3BA,EAAK,OAASjE,EAASiE,EAAK,WAAW,QACtCA,EAAK,KAAkB,KACxB,CAACA,EAAK,WAAW,MAAO3D,GAAuB,KAAK,sBAAsBA,CAAS,CAAC,GACpF2D,EAAK,WAAW,MAAM,EAAG,EAAE,EAAE,SAAS,IAAI,EAE1C,MAAM,IAAI,MAAM,yDAAyD,EAC3E,UAAW3D,KAAa2D,EAAK,WAM3B,GALA,MAAM,KAAK,aACT9D,EAAG,gBAAgBG,CAAS,EAC5B,KAAK,sBACLhB,EAAW,MAAM,EAEfgB,IAAc,KAAM,OAE1BN,EAASiE,EAAK,KACTA,EAAK,WAAW,QACnB,MAAM,IAAI,QAAc,CAAChD,EAASC,IAAU,CAC1C,MAAMF,EAAQ,WAAW,IAAK,CAC5B1B,EAAW,OAAO,oBAAoB,QAASiC,CAAO,EACtDN,EAAO,CACT,EAAG,EAAE,EACCM,EAAU,IAAK,CACnB,aAAaP,CAAK,EAClBE,EAAO3E,EAAW+C,EAAW,MAAM,CAAC,CACtC,EACAA,EAAW,OAAO,iBAAiB,QAASiC,EAAS,CAAE,KAAM,EAAI,CAAE,EAC/DjC,EAAW,OAAO,SAASiC,EAAO,CACxC,CAAC,CACL,CACF,EACA,MAAM,QAAQ,IAAI,CAChBR,EAAS,KAAK,IAAK,CACb,CAACsC,GAAS,CAACE,IACTH,EAAW,QAAU,IACvBG,EAAU,IAAI,MAAM,2CAA2C,GAE/DH,EAAW,KAAK,IAAI,EACpBC,EAAQ,IAEVC,IAAM,EAEV,CAAC,EACDM,EAAM,EACNG,EAAQ,EACT,CACH,SACEzE,EAAW,MAAK,EAChB,aAAamE,CAAO,EACpB,aAAaD,CAAS,EACtBrD,EAAG,oBAAoB,eAAgBU,CAAW,EAClDiC,GAAQ,oBAAoB,QAASK,CAAK,EACtCtF,EAAQ,eAAiByB,GAAY,OAAOzB,EAAQ,YAC1D,CACF,CAEQ,sBAAsB8B,EAAc,CAC1C,GAAIA,IAAU,KAAM,MAAO,GAC3B,GAAI,CAACA,GAAS,OAAOA,GAAU,SAAU,MAAO,GAChD,MAAMW,EAAYX,EAClB,OACE,OAAOW,EAAU,WAAc,UAC/BA,EAAU,UAAU,OAAS,GAC7BA,EAAU,UAAU,QAAU,OAC7BA,EAAU,QAAU,MAClB,OAAOA,EAAU,QAAW,UAAYA,EAAU,OAAO,QAAU,OACrEA,EAAU,eAAiB,MACzB,OAAO,UAAUA,EAAU,aAAa,GACvCA,EAAU,eAAiB,GAC3BA,EAAU,eAAiB,SAC9BA,EAAU,QAAU,MAAQA,EAAU,eAAiB,QACvDA,EAAU,kBAAoB,MAC5B,OAAOA,EAAU,kBAAqB,UACrCA,EAAU,iBAAiB,QAAU,IAE7C,CAEQ,MAAM,qBACZH,EACA+D,EAAsC,CAEtC,MAAMC,EAAW,IAAI,IAAIhE,EAAG,cAAc,EAC1C,GAAI,CACF,MAAMA,EAAG,qBAAqB+D,CAAW,CAC3C,SAGE,MAAME,EAAS,IAAI,IAAIjE,EAAG,cAAc,EACxC,MAAM,QAAQ,IACZ,CAAC,GAAGgE,CAAQ,EACT,OAAQE,GAAc,CAACD,EAAO,IAAIC,CAAS,CAAC,EAC5C,IAAKA,GAAcA,EAAU,KAAI,CAAE,CAAC,CAE3C,CACF,CAEQ,MAAM,aACZ7B,EACA8B,EACAxB,EAAoB,CAEpB,IAAI9B,EACAO,EACJ,GAAI,CACF,OAAO,MAAM,QAAQ,KAAK,CACxBiB,EACA,IAAI,QAAe,CAAC+B,EAAUrD,IAAU,CACtCF,EAAQ,WACN,IAAME,EAAO,IAAI,MAAM,oCAAoCoD,CAAS,KAAK,CAAC,EAC1EA,CAAS,EAEX/C,EAAU,IAAML,EAAO3E,EAAWuG,CAAM,CAAC,EACzCA,GAAQ,iBAAiB,QAASvB,EAAS,CAAE,KAAM,EAAI,CAAE,EACrDuB,GAAQ,SAASvB,EAAO,CAC9B,CAAC,EACF,CACH,SACE,aAAaP,CAAK,EACdO,GAASuB,GAAQ,oBAAoB,QAASvB,CAAO,CAC3D,CACF,CAEQ,MAAM,4BACZpB,EACAmE,EAAiB,CAEjB,GAAInE,EAAG,oBAAsB,WAAY,MAAO,GA2BhD,GAAI,CAzBc,MAAM,IAAI,QAAkBc,GAAW,CACvD,IAAIuD,EAAU,GACVxD,EAEJ,MAAMyD,EAAU,IAAK,CACnB,aAAazD,CAAK,EAClBb,EAAG,oBAAoB,0BAA2BuE,CAAa,CACjE,EAEMC,EAAUhF,GAAkB,CAC5B6E,IACJA,EAAU,GACVC,EAAO,EACPxD,EAAQtB,CAAK,EACf,EAEM+E,EAAgB,IAAK,CACrBvE,EAAG,oBAAsB,YAAYwE,EAAO,EAAI,CACtD,EAEA3D,EAAQ,WAAW,IAAM2D,EAAO,EAAK,EAAGL,CAAS,EACjDnE,EAAG,iBAAiB,0BAA2BuE,CAAa,EAC5DA,EAAa,CACf,CAAC,EAEe,MAAM,IAAI,MAAM,iCAAiCJ,CAAS,KAAK,EAC/E,MAAO,EACT,CAEQ,MAAM,mBACZzC,EACAyC,EACAxB,EAAoB,CAEpB,GAAIjB,EAAQ,aAAe,OAC3B,IAAIiB,GAAQ,QAAS,MAAMvG,EAAWuG,CAAM,EAE5C,MAAM,IAAI,QAAc,CAAC7B,EAASC,IAAU,CAC1C,IAAIsD,EAAU,GACd,MAAMC,EAAU,IAAK,CACnB,aAAazD,CAAK,EAClBa,EAAQ,oBAAoB,OAAQ+C,CAAM,EAC1C/C,EAAQ,oBAAoB,QAASR,CAAO,EAC5CQ,EAAQ,oBAAoB,QAASgD,CAAO,EAC5C/B,GAAQ,oBAAoB,QAASvB,CAAO,CAC9C,EACMoD,EAAU5G,GAAiB,CAC3ByG,IACJA,EAAU,GACVC,EAAO,EACH1G,EAAOmD,EAAOnD,CAAK,EAClBkD,EAAO,EACd,EACM2D,EAAS,IAAMD,EAAM,EACrBtD,EAAU,IAAMsD,EAAO,IAAI,MAAM,mCAAmC,CAAC,EACrEE,EAAU,IAAMF,EAAO,IAAI,MAAM,0CAA0C,CAAC,EAC5EpD,EAAU,IAAMoD,EAAOpI,EAAWuG,CAAM,CAAC,EACzC9B,EAAQ,WACZ,IAAM2D,EAAO,IAAI,MAAM,kCAAkCL,CAAS,KAAK,CAAC,EACxEA,CAAS,EAGXzC,EAAQ,iBAAiB,OAAQ+C,CAAM,EACvC/C,EAAQ,iBAAiB,QAASR,CAAO,EACzCQ,EAAQ,iBAAiB,QAASgD,CAAO,EACzC/B,GAAQ,iBAAiB,QAASvB,EAAS,CAAE,KAAM,EAAI,CAAE,EAErDM,EAAQ,aAAe,OAAQ8C,EAAM,EAChC9C,EAAQ,aAAe,UAAU8C,EAAO,IAAI,MAAM,8BAA8B,CAAC,CAC5F,CAAC,EACH,CAEQ,YACN9G,EACAK,EACA4G,EAAyB,CAEzB,GAAI5G,EAAQ,QAAQ,QAAS,OAAO,QAAQ,OAAO3B,EAAW2B,EAAQ,MAAM,CAAC,EAC7E,GAAIL,EAAQ,YAAY,aAAe,OACrC,OAAO,QAAQ,OAAO,IAAI,MAAM,gCAAgC,CAAC,EAGnE,MAAMkH,EAAY,KAAK,SAAS,UAAU,EAAE,KAAK,eAAe,EAAE,EAC5DT,EACJQ,GAAoBA,EAAmB,EAAIA,EAAmB,KAAK,iBAErE,OAAO,IAAI,QAA2B,CAAC7D,EAASC,IAAU,CACxD,MAAMqB,EAA2B,CAC/B,QAAAtB,EACA,OAAAC,EACA,MAAO,WAAW,IAAK,CACrB,KAAK,cAAcrD,EAASkH,CAAS,EACrC7D,EAAO,IAAI,MAAM,kCAAkCoD,CAAS,KAAK,CAAC,CACpE,EAAGA,CAAS,EACZ,GAAIpG,EAAQ,OAAS,CAAE,OAAQA,EAAQ,MAAM,EAAK,CAAA,GAGhDA,EAAQ,SACVqE,EAAQ,cAAgB,IAAK,CAC3B,KAAK,cAAc1E,EAASkH,CAAS,EACrC7D,EAAO3E,EAAW2B,EAAQ,MAAM,CAAC,CACnC,EACAA,EAAQ,OAAO,iBAAiB,QAASqE,EAAQ,cAAe,CAAE,KAAM,EAAI,CAAE,GAGhF1E,EAAQ,QAAQ,IAAIkH,EAAWxC,CAAO,EAEtC,GAAI,CACF1E,EAAQ,YAAY,KAClB,KAAK,UAAU,CACb,UAAAkH,EACA,OAAQ7G,EAAQ,OAChB,IAAKA,EAAQ,IACb,QAASA,EAAQ,QACjB,KAAMzB,EAAeyB,EAAQ,IAAI,EACjC,SAAUA,EAAQ,SACnB,CAAC,CAEN,OAASH,EAAO,CACd,KAAK,cAAcF,EAASkH,CAAS,EACrC7D,EAAOxE,EAAiBqB,EAAO,+BAA+B,CAAC,CACjE,CACF,CAAC,CACH,CAEQ,cAAcF,EAAwBkH,EAAiB,CAC7D,MAAMxC,EAAU1E,EAAQ,QAAQ,IAAIkH,CAAS,EACxCxC,IACL,aAAaA,EAAQ,KAAK,EACtBA,EAAQ,QAAUA,EAAQ,eAC5BA,EAAQ,OAAO,oBAAoB,QAASA,EAAQ,aAAa,EAEnE1E,EAAQ,QAAQ,OAAOkH,CAAS,EAClC,CAEQ,iBAAiBlH,EAAwBE,EAAY,CAC3D,SAAW,CAACgH,EAAWxC,CAAO,IAAK1E,EAAQ,QACzC,KAAK,cAAcA,EAASkH,CAAS,EACrCxC,EAAQ,OAAOxE,CAAK,CAExB,CAEQ,MAAM,qBAAqBF,EAAwBmH,EAAqB,CAC9E,GAAI,CAAAnH,EAAQ,OACZ,CAAAA,EAAQ,OAAS,GACjBA,EAAQ,cAAc,MAAM,IAAI,MAAM,uBAAuB,CAAC,EAE1D,KAAK,SAAS,IAAIA,EAAQ,OAAO,IAAMA,GACzC,KAAK,SAAS,OAAOA,EAAQ,OAAO,EAEtC,KAAK,iBAAiBA,EAAS,IAAI,MAAM,uBAAuB,CAAC,EAEjE,GAAI,CACFA,EAAQ,YAAY,MAAK,CAC3B,MAAQ,CAAC,CACT,GAAI,CACF,MAAMA,EAAQ,eAAe,MAAK,CACpC,MAAQ,CAAC,CAET,GAAImH,EACF,GAAI,CACF,MAAM9H,EAAM,IAAI,IAAIW,EAAQ,YAAY,EACxCX,EAAI,aAAa,IAAI,YAAaW,EAAQ,EAAE,EAC5C,MAAMyB,EAAa,IAAI,gBACjB0B,EAAQ,WAAW,IAAM1B,EAAW,MAAK,EAAI,GAAK,EACxD,GAAI,CACF,MAAM,MAAMpC,EAAK,CAAE,OAAQ,SAAU,OAAQoC,EAAW,MAAM,CAAE,CAClE,SACE,aAAa0B,CAAK,CACpB,CACF,MAAQ,CAAC,EAEb,CAEQ,uBACNnD,EACAoH,EAA8C,CAE9C,MAAM9E,EAAKtC,EAAQ,eAEnBsC,EAAG,iBAAiB,cAAgBW,GAAc,CAChD,MAAMe,EAAUf,EAAM,QACtBe,EAAQ,iBAAiB,UAAYqD,GAA8B,EAC3D,SAAW,CACf,IAAIH,EACJ,GAAI,CACF,MAAMtH,EAAU,KAAK,MAAM,OAAOyH,EAAa,IAAI,CAAC,EAUpD,GADAH,EAAY,OAAOtH,EAAQ,WAAc,SAAWA,EAAQ,UAAY,OAEtE,CAACsH,GACD,OAAOtH,EAAQ,QAAW,UAC1B,OAAOA,EAAQ,KAAQ,SAEvB,MAAM,IAAI,MAAM,kCAAkC,EAGpD,MAAMS,EAA4B,CAChC,OAAQT,EAAQ,OAChB,IAAKA,EAAQ,IACb,QACEA,EAAQ,SAAW,OAAOA,EAAQ,SAAY,SAC1C,CACE,GAAIA,EAAQ,QACZ,sBAAuBI,EAAQ,IAEjC,CAAE,sBAAuBA,EAAQ,EAAE,EACzC,KAAMrB,EAAeiB,EAAQ,IAAI,EACjC,SACE,OAAOA,EAAQ,UAAa,SACvBA,EAAQ,SACT,UAGR,IAAIwF,EAaJ,GAZKgC,EASHhC,EAAW,MAAMgC,EAAU/G,CAAO,EARlC+E,EAAW,CACT,OAAQ,IACR,WAAY,kBACZ,QAAS,CAAE,eAAgB,kBAAkB,EAC7C,KAAM,CAAE,MAAO,sCAAsC,EACrD,IAAK/E,EAAQ,KAMb2D,EAAQ,aAAe,OAAQ,OACnCA,EAAQ,KACN,KAAK,UAAU,CACb,UAAAkD,EACA,OAAQ9B,EAAS,OACjB,WAAYA,EAAS,YAAc,GACnC,QAASA,EAAS,QAClB,KAAMxG,EAAewG,EAAS,IAAI,EAClC,IAAKA,EAAS,KAAO/E,EAAQ,IAC9B,CAAC,CAEN,OAASH,EAAO,CAEd,GADA,KAAK,IAAI,QAAS,2CAA4CA,CAAK,EAC/DgH,GAAalD,EAAQ,aAAe,OACtC,GAAI,CACFA,EAAQ,KACN,KAAK,UAAU,CACb,UAAAkD,EACA,OAAQ,IACR,WAAY,wBACZ,QAAS,CAAE,eAAgB,kBAAkB,EAC7C,KAAM,CAAE,MAAOrI,EAAiBqB,EAAO,wBAAwB,EAAE,OAAO,EACzE,CAAC,CAEN,MAAQ,CAAC,CAEb,CACF,GAAE,CACJ,CAAC,CACH,CAAC,EAEDoC,EAAG,iBAAiB,QAAUW,GAAc,CAC1C,MAAMmB,EAAQnB,EAAM,MACfjD,EAAQ,aAAa,UAAS,EAAG,KAAMyE,GAAcA,EAAK,KAAOL,EAAM,EAAE,GAC5EpE,EAAQ,aAAa,SAASoE,CAAK,EAErC,KAAK,UAAU,CACb,KAAM,SACN,UAAWpE,EAAQ,GACnB,eAAgBsC,EAChB,MAAA8B,EACA,OAAQpE,EAAQ,aAChB,QAASiD,EAAM,QAChB,CACH,CAAC,EAEDX,EAAG,iBAAiB,wBAAyB,IAAK,EAC5CA,EAAG,kBAAoB,UAAYA,EAAG,kBAAoB,WACvD,KAAK,mBAAmBtC,EAAQ,EAAE,CAE3C,CAAC,CACH,CAEQ,MAAM,6BAA6BA,EAAsB,CAC/D,GAAIA,EAAQ,OAAQ,OAEpB,GAAI,CAACA,EAAQ,oBAAqB,CAChC,MAAMsH,EAAa,KAAK,QAAQ,YAC1BC,EAAY,OAAOD,GAAe,WAClCjD,EAASkD,EAAY,MAAMD,EAAWtH,EAAQ,GAAIA,EAAQ,OAAO,EAAIsH,EAC3E,GAAItH,EAAQ,OAAQ,CACdqE,GAAUkD,IACZvH,EAAQ,YAAcqE,EACtBrE,EAAQ,gBAAkB,GAC1B,KAAK,yBAAyBA,CAAO,GAEvC,MACF,CACAA,EAAQ,oBAAsB,GAE1BqE,IACFrE,EAAQ,YAAcqE,EACtBrE,EAAQ,gBAAkBuH,EAE9B,CAEA,MAAMlD,EAASrE,EAAQ,YACvB,GAAI,CAACqE,EAAQ,OAEb,MAAM/B,EAAKtC,EAAQ,eACnB,UAAWoE,KAASC,EAAO,UAAS,EAC9BrE,EAAQ,QAAUoE,EAAM,aAAe,QAAUpE,EAAQ,cAAc,IAAIoE,EAAM,EAAG,IAGxF9B,EAAG,SAAS8B,EAAOC,CAAM,EACzBrE,EAAQ,cAAc,IAAIoE,EAAM,EAAG,EAEvC,CAEQ,yBAAyBpE,EAAsB,CACrD,MAAMqE,EAASrE,EAAQ,YACjBwH,EAAaxH,EAAQ,gBAK3B,GAHA,OAAOA,EAAQ,YACfA,EAAQ,gBAAkB,GAEtB,GAACqE,GAAU,CAACmD,GAEhB,GAAI,CACF,KAAK,QAAQ,qBAAqBxH,EAAQ,GAAIqE,CAAM,CACtD,MAAQ,CACR,SACE,UAAWD,KAASC,EAAO,UAAS,EAC9BD,EAAM,aAAe,SAASA,EAAM,KAAI,CAEhD,CACF,CAGO,mBAAmBjE,EAAiB,CACzC,MAAMuE,EAAU,KAAK,sBAAsB,IAAIvE,CAAS,EACxD,GAAIuE,EAAS,OAAOA,EACpB,MAAM1E,EAAU,KAAK,eAAe,IAAIG,CAAS,EACjD,GAAI,CAACH,GAAWA,EAAQ,OAAQ,OAAO,QAAQ,QAAO,EACtDA,EAAQ,OAAS,GACjBA,EAAQ,SAAS,OAAM,EACvB,OAAOA,EAAQ,QACfA,EAAQ,WAAW,MAAK,EACxBA,EAAQ,QAAO,EACf,KAAK,eAAe,OAAOG,CAAS,EACpC,MAAMsH,EAAU,QAAQ,QAAO,EAC5B,KAAK,SAAW,CACf,GAAI,CACF,MAAMzH,EAAQ,eAAe,MAAK,CACpC,OAASE,EAAO,CACd,KAAK,IAAI,OAAQ,2BAA4BA,CAAK,CACpD,SACE,KAAK,yBAAyBF,CAAO,EACrC,KAAK,kBAAkBA,EAAS,QAAQ,CAC1C,CACF,CAAC,EACA,QAAQ,IAAM,KAAK,sBAAsB,OAAOG,CAAS,CAAC,EAC7D,YAAK,sBAAsB,IAAIA,EAAWsH,CAAO,EAC1CA,CACT,CAEQ,UAAUxE,EAAuB,CACvC,UAAWlD,KAAY,KAAK,eAC1B,GAAI,CACFA,EAASkD,CAAK,CAChB,OAAS/C,EAAO,CACd,KAAK,IAAI,QAAS,6CAA8CA,CAAK,CACvE,CAEJ,CAEQ,eAAeE,EAAe,CACpC,GAAIA,EAAQ,WAAW,YAAY,EAAG,OAAOA,EAAQ,QAAQ,gBAAiB,UAAU,EACxF,GAAIA,EAAQ,WAAW,WAAW,EAAG,OAAOA,EAAQ,QAAQ,eAAgB,SAAS,EACrF,MAAM,IAAI,MAAM,qCAAqCA,CAAO,EAAE,CAChE,CAEQ,SAASsH,EAAc,CAC7B,MAAO,GAAGA,CAAM,IAAI,OAAO,WAAU,CAAE,EACzC",
6
- "names": ["MAX_SIGNALING_BODY_BYTES", "abortError", "decodeWireBody", "encodeWireBody", "errorFromUnknown", "createServer", "WeriftMediaStream", "WeriftPeerConnection", "normalizeIceServers", "servers", "server", "urls", "url", "createWeriftPeerConnection", "configuration", "config", "createWeriftMediaStream", "options", "level", "message", "meta", "output", "listener", "session", "type", "error", "sessionId", "peerUrl", "request", "rtcRequest", "ownedSessions", "port", "host", "signalingPath", "char", "code", "closed", "closePromise", "pendingAuthorizations", "httpServer", "req", "res", "sendJson", "status", "body", "parsedUrl", "ownerId", "authorization", "controller", "onDisconnect", "onAuthorizationAbort", "headers", "name", "value", "allowed", "existing", "id", "state", "cursor", "raw", "chunk", "pc", "payload", "target", "candidate", "apply", "activeSession", "disconnect", "onSessionAbort", "answer", "peer", "onCandidate", "event", "applying", "timer", "resolve", "reject", "check", "localDescription", "onError", "onListening", "onAbort", "closeServer", "stopped", "inFlight", "connectionPromise", "signalingUrl", "channel", "trackListeners", "trackEndedListeners", "streamListeners", "track", "stream", "kind", "sender", "s", "item", "pending", "operation", "changed", "receiveAudio", "receiveVideo", "ensureReceive", "enabled", "signal", "offer", "abortListener", "response", "text", "abort", "candidates", "ended", "wake", "failure", "pollTimer", "timeout", "result", "generation", "upload", "index", "patch", "download", "pageResponse", "page", "description", "previous", "active", "transport", "timeoutMs", "_resolve", "settled", "cleanup", "onStateChange", "finish", "onOpen", "onClose", "requestedTimeout", "requestId", "notifyRemote", "onRequest", "messageEvent", "configured", "isFactory", "ownsStream", "closing", "prefix"]
4
+ "sourcesContent": ["import type {\n HyperTransport,\n TransportListenOptions,\n TransportRequest,\n TransportResponse,\n TransportServer,\n} from \"@hyperttp/types\";\nimport {\n MAX_SIGNALING_BODY_BYTES,\n type ClientSession,\n type WebRtcServerSessionContext,\n type WebRtcServerSessionEvent,\n type PendingResponse,\n type ServerSession,\n type ServerTrickleState,\n type WebRtcSessionView,\n type WebRtcTrackEvent,\n type WebRtcTransportOptions as InternalWebRtcTransportOptions,\n type WebRtcTransportRequest,\n} from \"./types/types.js\";\nimport { abortError, decodeWireBody, encodeWireBody, errorFromUnknown } from \"./utils/helper.js\";\nimport { createServer, type Server as HttpServer } from \"node:http\";\nimport {\n MediaStream as WeriftMediaStream,\n RTCPeerConnection as WeriftPeerConnection,\n RTCDataChannel as WeriftDataChannel,\n type MediaStreamTrack as WeriftMediaStreamTrack,\n} from \"werift\";\n\ndeclare module \"@hyperttp/types\" {\n interface TransportServer {\n readonly webrtc?: Pick<\n WebRtcTransport,\n \"getSession\" | \"getServerSession\" | \"onTrack\" | \"closeServerSession\" | \"onServerSession\"\n >;\n }\n}\n\ntype WeriftPeerConfig = NonNullable<ConstructorParameters<typeof WeriftPeerConnection>[0]>;\ntype WeriftIceServers = WeriftPeerConfig[\"iceServers\"];\n\nfunction normalizeIceServers(servers: RTCIceServer[] | undefined): WeriftIceServers {\n const normalized = (servers ?? []).flatMap((server) => {\n const urls = Array.isArray(server.urls) ? server.urls : [server.urls];\n if (server.credential !== undefined && typeof server.credential !== \"string\") {\n throw new TypeError(\"Werift currently requires ICE server credentials to be strings.\");\n }\n return urls.map((url) => ({\n urls: url,\n ...(server.username !== undefined ? { username: server.username } : {}),\n ...(server.credential !== undefined ? { credential: server.credential } : {}),\n }));\n });\n return normalized as unknown as WeriftIceServers;\n}\n\nfunction createWeriftPeerConnection(configuration: RTCConfiguration): WeriftPeerConnection {\n const config = {\n iceServers: normalizeIceServers(configuration.iceServers),\n ...(configuration.iceTransportPolicy !== undefined\n ? { iceTransportPolicy: configuration.iceTransportPolicy }\n : {}),\n } as unknown as WeriftPeerConfig;\n return new WeriftPeerConnection(config);\n}\n\nfunction createWeriftMediaStream(): WeriftMediaStream {\n return new WeriftMediaStream();\n}\n\nexport type WebRtcTransportOptions = InternalWebRtcTransportOptions;\n\nexport interface WebRtcServerSessionView {\n readonly id: string;\n readonly context: WebRtcServerSessionContext;\n readonly peerConnection: WeriftPeerConnection;\n readonly remoteStream: WeriftMediaStream;\n readonly localStream?: WeriftMediaStream;\n}\n\nexport interface WebRtcTransportServer extends TransportServer {\n readonly webrtc: Pick<\n WebRtcTransport,\n \"getSession\" | \"getServerSession\" | \"onTrack\" | \"closeServerSession\" | \"onServerSession\"\n >;\n}\n\nexport class WebRtcTransport implements HyperTransport {\n private readonly sessions = new Map<string, ClientSession>();\n private readonly connecting = new Map<string, Promise<ClientSession>>();\n private readonly serverSessions = new Map<string, ServerSession>();\n private readonly sessionListeners = new Set<(event: WebRtcServerSessionEvent) => void>();\n private readonly closingServerSessions = new Map<string, Promise<void>>();\n private readonly trackListeners = new Set<(event: WebRtcTrackEvent) => void>();\n private readonly serverClosers = new Set<() => Promise<void>>();\n private requestSequence = 0;\n private closing = false;\n private readonly pendingPeers = new Set<WeriftPeerConnection>();\n private readonly iceServers: RTCIceServer[];\n private readonly iceGatheringTimeoutMs: number;\n private readonly signalingTimeoutMs: number;\n private readonly connectionTimeoutMs: number;\n private readonly requestTimeoutMs: number;\n readonly createMediaStream: () => WeriftMediaStream;\n private readonly createPeerConnection: (configuration: RTCConfiguration) => WeriftPeerConnection;\n private readonly options: InternalWebRtcTransportOptions;\n\n constructor(options: WebRtcTransportOptions = {}) {\n this.options = options as unknown as InternalWebRtcTransportOptions;\n this.createPeerConnection = options.createPeerConnection ?? createWeriftPeerConnection;\n this.createMediaStream =\n (options.createMediaStream as unknown as (() => WeriftMediaStream) | undefined) ??\n createWeriftMediaStream;\n this.iceServers = options.iceServers ?? [{ urls: \"stun:stun.l.google.com:19302\" }];\n this.iceGatheringTimeoutMs = options.iceGatheringTimeoutMs ?? 10_000;\n this.signalingTimeoutMs = options.signalingTimeoutMs ?? 10_000;\n this.connectionTimeoutMs = options.connectionTimeoutMs ?? 15_000;\n this.requestTimeoutMs = options.requestTimeoutMs ?? 15_000;\n }\n\n private log(level: \"debug\" | \"info\" | \"warn\" | \"error\", message: string, meta?: unknown): void {\n if (this.options.logger) {\n this.options.logger(level, message, meta);\n return;\n }\n if (!this.options.verbose && level !== \"error\") return;\n const output = level === \"debug\" ? console.debug : console[level];\n if (meta === undefined) output(message);\n else output(message, meta);\n }\n\n onTrack(listener: (event: WebRtcTrackEvent) => void): () => void {\n this.trackListeners.add(listener);\n return () => this.trackListeners.delete(listener);\n }\n\n onServerSession(listener: (event: WebRtcServerSessionEvent) => void): () => void {\n this.sessionListeners.add(listener);\n return () => this.sessionListeners.delete(listener);\n }\n\n private emitServerSession(session: ServerSession, type: \"created\" | \"closed\") {\n for (const listener of this.sessionListeners) {\n try {\n listener({ ...session.context, type });\n } catch (error) {\n this.log(\"error\", \"WebRTC session listener failed\", error);\n }\n }\n }\n\n getServerSession(sessionId: string): WebRtcServerSessionView | undefined {\n const session = this.serverSessions.get(sessionId);\n if (!session || session.closed) return undefined;\n return {\n id: session.id,\n context: session.context,\n peerConnection: session.peerConnection,\n remoteStream: session.remoteStream,\n ...(session.localStream ? { localStream: session.localStream } : {}),\n };\n }\n\n getSession(peerUrl: string): WebRtcSessionView | undefined {\n const session = this.sessions.get(peerUrl);\n if (!session || session.closed) return undefined;\n\n return {\n id: session.id,\n peerUrl: session.peerUrl,\n peerConnection: session.peerConnection,\n dataChannel: session.dataChannel,\n remoteStream: session.remoteStream,\n media: session.mediaController,\n ...(session.localStream ? { localStream: session.localStream } : {}),\n };\n }\n\n async execute(request: TransportRequest): Promise<TransportResponse> {\n if (this.closing) throw new Error(\"WebRTC transport is closed\");\n const rtcRequest = request as WebRtcTransportRequest;\n const peerUrl = rtcRequest.peerUrl ?? request.url;\n\n if (request.signal?.aborted) throw abortError(request.signal);\n\n const session = await this.getOrCreateSession(peerUrl, rtcRequest);\n await this.ensureMedia(session, rtcRequest);\n\n if (request.signal?.aborted) throw abortError(request.signal);\n\n return this.sendRequest(session, request, rtcRequest.timeout);\n }\n\n public async listen(options: TransportListenOptions): Promise<WebRtcTransportServer> {\n if (this.closing) throw new Error(\"WebRTC transport is closed\");\n const ownedSessions = new Set<string>();\n const port = options.port ?? 3000;\n const host = options.host ?? \"127.0.0.1\";\n const signalingPath = options.signaling?.path ?? \"/signal\";\n if (\n !signalingPath.startsWith(\"/\") ||\n signalingPath.startsWith(\"//\") ||\n /[?#\\\\\\s]/u.test(signalingPath) ||\n Array.from(signalingPath).some((char) => {\n const code = char.codePointAt(0)!;\n return code <= 0x1f || (code >= 0x7f && code <= 0x9f);\n }) ||\n new URL(signalingPath, \"http://localhost\").pathname !== signalingPath\n ) {\n throw new TypeError(\n \"WebRTC signaling path must be an absolute literal pathname without query or fragment\",\n );\n }\n\n if (options.signal?.aborted) {\n throw new Error(\"WebRtcTransport: server startup aborted\");\n }\n\n let closed = false;\n let closePromise: Promise<void> | undefined;\n const pendingAuthorizations = new Set<AbortController>();\n\n const httpServer: HttpServer = createServer((req, res) => {\n void (async () => {\n const sendJson = (status: number, body: unknown) => {\n if (res.headersSent || res.destroyed) return;\n res.writeHead(status, {\n \"content-type\": \"application/json\",\n \"cache-control\": \"no-store\",\n });\n res.end(JSON.stringify(body));\n };\n\n if (closed || this.closing) {\n sendJson(503, { error: \"WebRTC server is closing\" });\n return;\n }\n\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(req.url ?? \"/\", \"http://localhost\");\n } catch {\n sendJson(400, { error: \"Invalid request URL\" });\n return;\n }\n\n if (parsedUrl.pathname !== signalingPath) {\n sendJson(404, { error: \"Signaling endpoint not found\" });\n return;\n }\n\n let ownerId: string | undefined;\n let authorization: unknown;\n if (\n [\"POST\", \"DELETE\", \"GET\", \"PATCH\"].includes(req.method ?? \"\") &&\n options.signaling?.authorize\n ) {\n const controller = new AbortController();\n const onDisconnect = () => controller.abort();\n const onAuthorizationAbort = () => {\n sendJson(503, { error: \"WebRTC signaling request aborted\" });\n };\n req.once(\"aborted\", onDisconnect);\n res.once(\"close\", onDisconnect);\n controller.signal.addEventListener(\"abort\", onAuthorizationAbort, { once: true });\n pendingAuthorizations.add(controller);\n try {\n const headers: Record<string, string | string[]> = {};\n for (const [name, value] of Object.entries(req.headers)) {\n if (value !== undefined) headers[name] = value;\n }\n const allowed = await options.signaling.authorize({\n protocol: \"webrtc\",\n method: req.method!,\n url: req.url ?? \"/\",\n headers,\n signal: controller.signal,\n });\n if (controller.signal.aborted || req.aborted || res.destroyed) return;\n if (closed || this.closing || options.signal?.aborted) {\n sendJson(503, { error: \"WebRTC server is closing\" });\n return;\n }\n if (typeof allowed === \"object\" && allowed !== null) {\n if (typeof allowed.ownerId !== \"string\" || !allowed.ownerId.trim()) {\n sendJson(403, { error: \"Invalid signaling identity\" });\n return;\n }\n ownerId = allowed.ownerId;\n authorization = allowed.context;\n } else if (allowed !== true) {\n sendJson(403, { error: \"Signaling forbidden\" });\n return;\n }\n } catch {\n sendJson(403, { error: \"Signaling forbidden\" });\n return;\n } finally {\n pendingAuthorizations.delete(controller);\n req.off(\"aborted\", onDisconnect);\n res.off(\"close\", onDisconnect);\n controller.signal.removeEventListener(\"abort\", onAuthorizationAbort);\n }\n }\n\n if (req.method === \"DELETE\") {\n const sessionId = parsedUrl.searchParams.get(\"sessionId\");\n const existing = sessionId ? this.serverSessions.get(sessionId) : undefined;\n if (\n existing &&\n (!ownedSessions.has(sessionId!) || existing.context.ownerId !== ownerId)\n ) {\n sendJson(403, { error: \"Signaling forbidden\" });\n return;\n }\n if (sessionId && ownedSessions.has(sessionId)) {\n await this.closeServerSession(sessionId);\n ownedSessions.delete(sessionId);\n }\n res.writeHead(204).end();\n return;\n }\n\n if (req.method === \"GET\") {\n const id = parsedUrl.searchParams.get(\"sessionId\");\n const session = id ? this.serverSessions.get(id) : undefined;\n if (!session || !id || !ownedSessions.has(id)) {\n sendJson(404, { error: \"Session not found\" });\n return;\n }\n if (session.context.ownerId !== ownerId) {\n sendJson(403, { error: \"Signaling forbidden\" });\n return;\n }\n const state = session.trickle;\n if (!state || parsedUrl.searchParams.get(\"generation\") !== state.generation) {\n sendJson(409, { error: \"ICE generation mismatch\" });\n return;\n }\n const cursor = parsedUrl.searchParams.get(\"cursor\") ?? \"0\";\n if (\n !/^(0|[1-9][0-9]*)$/.test(cursor) ||\n !Number.isSafeInteger(Number(cursor)) ||\n Number(cursor) > state.candidates.length\n ) {\n sendJson(400, { error: \"Invalid ICE cursor\" });\n return;\n }\n sendJson(200, {\n generation: state.generation,\n candidates: state.candidates.slice(Number(cursor)),\n next: state.candidates.length,\n });\n return;\n }\n\n if (req.method !== \"POST\" && req.method !== \"PATCH\") {\n sendJson(405, { error: \"Method not allowed\" });\n return;\n }\n\n let raw = \"\";\n try {\n for await (const chunk of req) {\n raw += Buffer.isBuffer(chunk) ? chunk.toString(\"utf8\") : String(chunk);\n if (Buffer.byteLength(raw, \"utf8\") > MAX_SIGNALING_BODY_BYTES) {\n sendJson(413, { error: \"Signaling payload too large\" });\n return;\n }\n }\n } catch (error) {\n this.log(\"error\", \"[WebRtcTransport Server] Failed to read signaling body:\", error);\n sendJson(500, { error: \"Signaling failed\" });\n return;\n }\n\n let payload: {\n sessionId?: unknown;\n sdp?: RTCSessionDescriptionInit;\n trickle?: unknown;\n generation?: unknown;\n candidate?: unknown;\n };\n try {\n payload = JSON.parse(raw);\n } catch {\n sendJson(400, { error: \"Invalid JSON\" });\n return;\n }\n\n let pc: WeriftPeerConnection | undefined;\n let session: ServerSession | undefined;\n\n try {\n if (req.method === \"PATCH\") {\n if (\n !payload ||\n typeof payload !== \"object\" ||\n typeof payload.sessionId !== \"string\" ||\n typeof payload.generation !== \"string\" ||\n !(\"candidate\" in payload)\n ) {\n sendJson(400, { error: \"Expected sessionId, generation and candidate\" });\n return;\n }\n const target = this.serverSessions.get(payload.sessionId);\n if (!target || !ownedSessions.has(payload.sessionId)) {\n sendJson(404, { error: \"Session not found\" });\n return;\n }\n if (target.context.ownerId !== ownerId) {\n sendJson(403, { error: \"Signaling forbidden\" });\n return;\n }\n const state = target.trickle;\n if (!state || state.generation !== payload.generation) {\n sendJson(409, { error: \"ICE generation mismatch\" });\n return;\n }\n const candidate = payload.candidate as RTCIceCandidateInit | null;\n if (\n candidate !== null &&\n (typeof candidate !== \"object\" ||\n typeof candidate.candidate !== \"string\" ||\n candidate.candidate.length < 1 ||\n candidate.candidate.length > 4096 ||\n (candidate.sdpMid != null &&\n (typeof candidate.sdpMid !== \"string\" || candidate.sdpMid.length > 256)) ||\n (candidate.sdpMLineIndex != null &&\n (!Number.isInteger(candidate.sdpMLineIndex) ||\n candidate.sdpMLineIndex < 0 ||\n candidate.sdpMLineIndex > 65535)) ||\n (candidate.sdpMid == null && candidate.sdpMLineIndex == null) ||\n (candidate.usernameFragment != null &&\n (typeof candidate.usernameFragment !== \"string\" ||\n candidate.usernameFragment.length > 256)))\n ) {\n sendJson(400, { error: \"Invalid ICE candidate\" });\n return;\n }\n if (state.remoteEnded || state.remoteCount >= 256) {\n sendJson(429, { error: \"ICE candidate limit reached or gathering ended\" });\n return;\n }\n state.remoteCount++;\n if (candidate === null) state.remoteEnded = true;\n const apply = state.remoteQueue.then(async () => {\n if (target.closed || target.trickle !== state)\n throw new Error(\"ICE generation expired\");\n await target.peerConnection.addIceCandidate(candidate);\n });\n state.remoteQueue = apply.catch(() => {});\n try {\n await apply;\n res.writeHead(204).end();\n } catch {\n sendJson(400, { error: \"Failed to apply ICE candidate\" });\n }\n return;\n }\n if (!payload || (payload.trickle !== undefined && typeof payload.trickle !== \"boolean\")) {\n sendJson(400, { error: \"Invalid trickle flag\" });\n return;\n }\n if (\n typeof payload.sessionId !== \"string\" ||\n payload.sessionId.length < 1 ||\n payload.sessionId.length > 200 ||\n payload.sdp?.type !== \"offer\" ||\n typeof payload.sdp.sdp !== \"string\"\n ) {\n sendJson(400, { error: \"Expected sessionId and a valid SDP offer\" });\n return;\n }\n\n if (closed || this.closing || options.signal?.aborted || req.aborted || res.destroyed) {\n throw new Error(\"WebRTC signaling request aborted\");\n }\n const sessionId = payload.sessionId;\n session = this.serverSessions.get(sessionId);\n if (session && !ownedSessions.has(sessionId)) {\n sendJson(409, { error: \"Session belongs to another listener\" });\n return;\n }\n if (session && session.context.ownerId !== ownerId) {\n sendJson(403, { error: \"Signaling forbidden\" });\n return;\n }\n if (this.closingServerSessions.has(sessionId)) {\n sendJson(409, { error: \"Session is closing\" });\n return;\n }\n if (session?.negotiating || session?.trickle?.gathering) {\n sendJson(409, { error: \"Session negotiation already in progress\" });\n return;\n }\n\n if (!session || session.closed) {\n pc = this.createPeerConnection({ iceServers: this.iceServers });\n const controller = new AbortController();\n session = {\n controller,\n context: Object.freeze({\n sessionId,\n ownerId,\n authorization,\n signal: controller.signal,\n }),\n release: () => ownedSessions.delete(sessionId),\n id: sessionId,\n peerConnection: pc,\n remoteStream: this.createMediaStream(),\n localTrackIds: new Set<string>(),\n localStreamResolved: false,\n ownsLocalStream: false,\n closed: false,\n };\n ownedSessions.add(sessionId);\n this.serverSessions.set(sessionId, session);\n this.attachServerPeerEvents(session, options.onRequest);\n this.emitServerSession(session, \"created\");\n } else {\n pc = session.peerConnection;\n }\n\n session.trickle?.detach();\n delete session.trickle;\n session.negotiating = true;\n const activeSession = session;\n const disconnect = () => {\n if (!res.writableEnded) void this.closeServerSession(activeSession.id);\n };\n res.once(\"close\", disconnect);\n const onSessionAbort = () => sendJson(503, { error: \"WebRTC session closed\" });\n activeSession.controller.signal.addEventListener(\"abort\", onSessionAbort, { once: true });\n res.once(\"finish\", () => {\n activeSession.controller.signal.removeEventListener(\"abort\", onSessionAbort);\n res.off(\"close\", disconnect);\n });\n await this.setRemoteDescription(pc, payload.sdp);\n await this.attachConfiguredServerStream(session);\n if (session.closed) throw new Error(\"WebRTC session closed\");\n\n const answer = await pc.createAnswer();\n let state: ServerTrickleState | undefined;\n if (payload.trickle === true) {\n const peer = pc;\n const onCandidate = (event: { candidate?: { toJSON(): RTCIceCandidateInit } }) => {\n if (!state || activeSession.closed || activeSession.trickle !== state) return;\n if (state.candidates.length >= 256) {\n void this.closeServerSession(activeSession.id);\n return;\n }\n state.candidates.push(event.candidate ? event.candidate.toJSON() : null);\n };\n peer.addEventListener(\"icecandidate\", onCandidate);\n state = {\n generation: crypto.randomUUID(),\n candidates: [],\n gathering: true,\n remoteCount: 0,\n remoteEnded: false,\n remoteQueue: Promise.resolve(),\n detach: () => peer.removeEventListener(\"icecandidate\", onCandidate),\n };\n session.trickle = state;\n const applying = this.withDeadline(\n peer.setLocalDescription(answer),\n this.iceGatheringTimeoutMs,\n session.controller.signal,\n );\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n await this.withDeadline(\n new Promise<void>((resolve, reject) => {\n const check = () => {\n if (activeSession.closed) {\n reject(new Error(\"WebRTC session closed\"));\n return;\n }\n if (\n peer.signalingState === \"stable\" &&\n peer.localDescription?.type === \"answer\"\n ) {\n resolve();\n return;\n }\n timer = setTimeout(check, 5);\n };\n void applying.catch(reject);\n check();\n }),\n this.iceGatheringTimeoutMs,\n session.controller.signal,\n );\n } finally {\n clearTimeout(timer);\n }\n void applying.then(\n () => {\n if (state && activeSession.trickle === state) {\n state.gathering = false;\n if (!state.candidates.includes(null)) state.candidates.push(null);\n }\n },\n () => {\n if (activeSession.trickle === state) void this.closeServerSession(activeSession.id);\n },\n );\n } else {\n await this.withDeadline(pc.setLocalDescription(answer), this.iceGatheringTimeoutMs);\n if (!(await this.waitForIceGatheringComplete(pc, this.iceGatheringTimeoutMs)))\n throw new Error(\"Full ICE gathering timed out; trickle ICE is not supported\");\n }\n\n const localDescription = pc.localDescription;\n if (!localDescription?.sdp || localDescription.type !== \"answer\") {\n throw new Error(\"Local SDP answer was not generated\");\n }\n\n if (closed || session.closed) throw new Error(\"WebRTC session closed\");\n session.negotiating = false;\n sendJson(\n 200,\n state ? { sdp: localDescription, generation: state.generation } : localDescription,\n );\n } catch (error) {\n this.log(\"error\", \"[WebRtcTransport Server] \u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u0438\u0433\u043D\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u0438:\", error);\n sendJson(500, { error: \"Signaling failed\" });\n if (session) {\n await this.closeServerSession(session.id);\n } else if (pc) {\n try {\n await pc.close();\n } catch {}\n }\n }\n })();\n });\n\n await new Promise<void>((resolve, reject) => {\n const onError = (error: Error) => {\n httpServer.off(\"listening\", onListening);\n reject(error);\n };\n const onListening = () => {\n httpServer.off(\"error\", onError);\n resolve();\n };\n httpServer.once(\"error\", onError);\n httpServer.once(\"listening\", onListening);\n httpServer.listen(port, host);\n });\n\n this.log(\"info\", `[WebRtcTransport] Listening on http://${host}:${port}${signalingPath}`);\n\n const onAbort = () => {\n void closeServer().catch((error) => {\n this.log(\"error\", \"[WebRtcTransport] Server shutdown failed:\", error);\n });\n };\n\n const closeServer = (): Promise<void> => {\n if (closePromise) return closePromise;\n closed = true;\n for (const controller of pendingAuthorizations) controller.abort();\n options.signal?.removeEventListener(\"abort\", onAbort);\n closePromise = (async () => {\n const stopped = httpServer.listening\n ? new Promise<void>((resolve, reject) => {\n httpServer.close((error) => (error ? reject(error) : resolve()));\n })\n : Promise.resolve();\n await Promise.all([...ownedSessions].map((id) => this.closeServerSession(id)));\n ownedSessions.clear();\n await stopped;\n this.serverClosers.delete(closeServer);\n })();\n return closePromise;\n };\n\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n this.serverClosers.add(closeServer);\n if (options.signal?.aborted && !closed) await closeServer();\n\n return {\n close: closeServer,\n webrtc: {\n getSession: this.getSession.bind(this),\n getServerSession: this.getServerSession.bind(this),\n onTrack: this.onTrack.bind(this),\n closeServerSession: (id: string) =>\n ownedSessions.has(id) ? this.closeServerSession(id) : Promise.resolve(),\n onServerSession: this.onServerSession.bind(this),\n },\n };\n }\n\n async close(): Promise<void> {\n this.closing = true;\n await Promise.all([...this.pendingPeers].map((pc) => pc.close().catch(() => {})));\n await Promise.allSettled(this.connecting.values());\n await Promise.all(\n [...this.sessions.values()].map((session) => this.disposeClientSession(session, true)),\n );\n await Promise.all([...this.serverClosers].map((closeServer) => closeServer()));\n }\n\n async destroy(): Promise<void> {\n await this.close();\n }\n\n private async getOrCreateSession(\n peerUrl: string,\n request: WebRtcTransportRequest,\n ): Promise<ClientSession> {\n const existing = this.sessions.get(peerUrl);\n if (\n existing &&\n !existing.closed &&\n existing.dataChannel.readyState === \"open\" &&\n existing.peerConnection.connectionState !== \"failed\" &&\n existing.peerConnection.connectionState !== \"closed\"\n ) {\n return existing;\n }\n\n if (existing) {\n await this.disposeClientSession(existing, true);\n }\n\n const inFlight = this.connecting.get(peerUrl);\n if (inFlight) return inFlight;\n\n const connectionPromise = this.createClientSession(peerUrl, request);\n this.connecting.set(peerUrl, connectionPromise);\n\n try {\n const session = await connectionPromise;\n if (this.closing || session.closed) {\n await this.disposeClientSession(session, true);\n throw new Error(\"WebRTC transport is closed\");\n }\n this.sessions.set(peerUrl, session);\n return session;\n } finally {\n if (this.connecting.get(peerUrl) === connectionPromise) {\n this.connecting.delete(peerUrl);\n }\n }\n }\n\n private async createClientSession(\n peerUrl: string,\n request: WebRtcTransportRequest,\n ): Promise<ClientSession> {\n const signalingUrl = this.toSignalingUrl(peerUrl);\n const id = this.createId(\"session\");\n const pc = this.createPeerConnection({\n iceServers: request.iceServers ?? this.iceServers,\n });\n this.pendingPeers.add(pc);\n const channel = pc.createDataChannel(request.channelLabel ?? \"hyperttp-webrtc-transport\", {\n ordered: true,\n });\n\n const session: ClientSession = {\n id,\n peerUrl,\n signalingUrl,\n peerConnection: pc,\n dataChannel: channel,\n remoteStream: this.createMediaStream(),\n ...(request.localStream ? { localStream: request.localStream } : {}),\n localTrackIds: new Set<string>(),\n pending: new Map<string, PendingResponse>(),\n negotiation: Promise.resolve(),\n closed: false,\n mediaController: {} as any,\n };\n\n const trackListeners = new Set<\n (e: { track: WeriftMediaStreamTrack; stream: WeriftMediaStream }) => void\n >();\n const trackEndedListeners = new Set<(e: { track: WeriftMediaStreamTrack }) => void>();\n const streamListeners = new Set<(e: { stream: WeriftMediaStream }) => void>();\n\n pc.addEventListener(\"track\", (event: WebRtcTrackEvent) => {\n const { track } = event;\n if (!session.remoteStream.getTracks().includes(track)) session.remoteStream.addTrack(track);\n for (const listener of trackListeners) listener({ track, stream: session.remoteStream });\n for (const listener of streamListeners) listener({ stream: session.remoteStream });\n track.addEventListener(\"ended\", () => {\n for (const listener of trackEndedListeners) listener({ track });\n });\n });\n\n session.mediaController = {\n localStream: session.localStream,\n remoteStream: session.remoteStream,\n addTrack: (track, stream) =>\n this.queueNegotiation(session, async () => {\n pc.addTrack(track, stream ?? session.localStream ?? this.createMediaStream());\n if (track.id) session.localTrackIds.add(track.id);\n await this.negotiate(session);\n }),\n replaceTrack: async (kind: \"audio\" | \"video\", track: any | null) => {\n const sender = pc.getSenders().find((s: any) => s.track && s.track.kind === kind);\n if (sender) {\n await sender.replaceTrack(track);\n }\n },\n removeTrack: (track) =>\n this.queueNegotiation(session, async () => {\n const sender = pc.getSenders().find((item) => item.track === track);\n if (sender) {\n pc.removeTrack(sender);\n if (track.id) session.localTrackIds.delete(track.id);\n await this.negotiate(session);\n }\n }),\n close: async () => {\n await this.disposeClientSession(session, true);\n },\n on(event: string, listener: any) {\n if (event === \"track\") trackListeners.add(listener);\n if (event === \"trackEnded\") trackEndedListeners.add(listener);\n if (event === \"stream\") streamListeners.add(listener);\n return () => {\n trackListeners.delete(listener);\n trackEndedListeners.delete(listener);\n streamListeners.delete(listener);\n };\n },\n };\n\n try {\n this.attachClientPeerEvents(session);\n this.addLocalTracks(session, request.localStream);\n this.addReceiveTransceivers(session, request.receiveAudio, request.receiveVideo);\n await this.negotiate(session, request.signal);\n await this.waitForChannelOpen(channel, this.connectionTimeoutMs, request.signal);\n return session;\n } catch (error) {\n await this.disposeClientSession(session, true);\n throw error;\n } finally {\n this.pendingPeers.delete(pc);\n }\n }\n\n private attachClientPeerEvents(session: ClientSession): void {\n const { peerConnection: pc, dataChannel: channel } = session;\n\n pc.addEventListener(\"track\", (event: any) => {\n const track = event.track;\n if (!session.remoteStream.getTracks().some((item: any) => item.id === track.id)) {\n session.remoteStream.addTrack(track);\n }\n\n this.emitTrack({\n side: \"client\",\n sessionId: session.id,\n peerUrl: session.peerUrl,\n peerConnection: pc,\n track,\n stream: session.remoteStream,\n streams: event.streams,\n });\n });\n\n pc.addEventListener(\"connectionstatechange\", () => {\n if (pc.connectionState === \"failed\" || pc.connectionState === \"closed\") {\n void this.disposeClientSession(session, true);\n }\n });\n\n channel.addEventListener(\"message\", (event: MessageEvent) => {\n let message: Record<string, unknown>;\n try {\n message = JSON.parse(String(event.data)) as Record<string, unknown>;\n } catch {\n return;\n }\n\n if (!message || typeof message !== \"object\" || typeof message.requestId !== \"string\") return;\n const pending = session.pending.get(message.requestId);\n if (!pending) return;\n\n try {\n const body = decodeWireBody(message.body);\n this.finishPending(session, message.requestId);\n pending.resolve({\n status: typeof message.status === \"number\" ? message.status : 200,\n statusText: typeof message.statusText === \"string\" ? message.statusText : \"OK\",\n headers:\n message.headers && typeof message.headers === \"object\"\n ? (message.headers as TransportResponse[\"headers\"])\n : {},\n body,\n url: typeof message.url === \"string\" ? message.url : session.peerUrl,\n });\n } catch (error) {\n this.finishPending(session, message.requestId);\n pending.reject(errorFromUnknown(error, \"Invalid WebRTC response\"));\n }\n });\n\n channel.addEventListener(\"close\", () => {\n this.rejectAllPending(session, new Error(\"WebRTC DataChannel closed\"));\n void this.disposeClientSession(session, true);\n });\n\n channel.addEventListener(\"error\", () => {\n this.rejectAllPending(session, new Error(\"WebRTC DataChannel error\"));\n });\n }\n\n private queueNegotiation(session: ClientSession, operation: () => Promise<void>): Promise<void> {\n const pending = session.negotiation\n .catch(() => undefined)\n .then(async () => {\n if (session.closed) throw new Error(\"WebRTC session closed\");\n try {\n await operation();\n } catch (error) {\n await this.disposeClientSession(session, true);\n throw error;\n }\n });\n session.negotiation = pending;\n return pending;\n }\n\n private async ensureMedia(\n session: ClientSession,\n request: WebRtcTransportRequest,\n ): Promise<void> {\n const needsPotentialChanges =\n Boolean(request.localStream) ||\n Boolean(request.receiveAudio) ||\n Boolean(request.receiveVideo);\n if (!needsPotentialChanges || session.closed) return;\n\n await this.queueNegotiation(session, async () => {\n let changed = this.addLocalTracks(session, request.localStream);\n changed =\n this.addReceiveTransceivers(session, request.receiveAudio, request.receiveVideo) || changed;\n\n if (request.localStream) {\n session.localStream = request.localStream;\n (session.mediaController as { localStream?: WeriftMediaStream }).localStream =\n request.localStream;\n }\n if (changed) await this.negotiate(session, request.signal);\n });\n }\n\n private addLocalTracks(session: ClientSession, stream?: WeriftMediaStream): boolean {\n if (!stream) return false;\n let changed = false;\n\n for (const track of stream.getTracks()) {\n if (track.readyState !== \"live\" || session.localTrackIds.has(track.id!)) continue;\n session.peerConnection.addTrack(track, stream);\n session.localTrackIds.add(track.id!);\n changed = true;\n }\n\n return changed;\n }\n\n private addReceiveTransceivers(\n session: ClientSession,\n receiveAudio?: boolean,\n receiveVideo?: boolean,\n ): boolean {\n const pc = session.peerConnection;\n let changed = false;\n\n const ensureReceive = (kind: \"audio\" | \"video\", enabled: boolean | undefined) => {\n if (!enabled) return;\n const transceivers = pc\n .getTransceivers()\n .find(\n (item: any) =>\n item.receiver.track.kind === kind &&\n (item.direction === \"recvonly\" || item.direction === \"sendrecv\"),\n );\n if (!transceivers) {\n pc.addTransceiver(kind, { direction: \"recvonly\" });\n changed = true;\n }\n };\n\n ensureReceive(\"audio\", receiveAudio);\n ensureReceive(\"video\", receiveVideo);\n\n return changed;\n }\n\n private async negotiate(session: ClientSession, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) throw abortError(signal);\n\n const pc = session.peerConnection;\n if (pc.signalingState !== \"stable\") {\n throw new Error(`Cannot negotiate WebRTC session in state ${pc.signalingState}`);\n }\n\n if (this.options.trickleIce) return this.negotiateTrickle(session, signal);\n\n const offer = await pc.createOffer();\n await this.withDeadline(pc.setLocalDescription(offer), this.iceGatheringTimeoutMs, signal);\n await this.waitForIceGatheringComplete(pc, this.iceGatheringTimeoutMs);\n\n const localDescription = pc.localDescription;\n if (!localDescription?.sdp || localDescription.type !== \"offer\") {\n throw new Error(\"Local SDP offer was not generated\");\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.signalingTimeoutMs);\n const abortListener = () => controller.abort(signal?.reason);\n signal?.addEventListener(\"abort\", abortListener, { once: true });\n if (signal?.aborted) abortListener();\n\n try {\n const response = await fetch(session.signalingUrl, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ sessionId: session.id, sdp: localDescription }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(\n `Signaling HTTP error: ${response.status} ${response.statusText}${text ? ` \u2014 ${text}` : \"\"}`,\n );\n }\n\n const answer = (await response.json()) as RTCSessionDescriptionInit;\n if (answer.type !== \"answer\" || typeof answer.sdp !== \"string\") {\n throw new Error(\"Signaling server returned an invalid SDP answer\");\n }\n\n await this.setRemoteDescription(pc, answer);\n } catch (error) {\n if (signal?.aborted) throw abortError(signal);\n if (controller.signal.aborted) {\n throw new Error(`Signaling timed out after ${this.signalingTimeoutMs} ms`);\n }\n throw errorFromUnknown(error, \"WebRTC signaling failed\");\n } finally {\n clearTimeout(timer);\n signal?.removeEventListener(\"abort\", abortListener);\n }\n }\n\n private async negotiateTrickle(session: ClientSession, signal?: AbortSignal): Promise<void> {\n const pc = session.peerConnection;\n const controller = new AbortController();\n session.trickleAbort = controller;\n const abort = () => controller.abort(signal?.reason);\n signal?.addEventListener(\"abort\", abort, { once: true });\n if (signal?.aborted) abort();\n const candidates: (RTCIceCandidateInit | null)[] = [];\n let ended = false;\n let wake: (() => void) | undefined;\n let failure: Error | undefined;\n const onCandidate = (event: { candidate?: { toJSON(): RTCIceCandidateInit } }) => {\n if (ended || controller.signal.aborted) return;\n const candidate = event.candidate ? event.candidate.toJSON() : null;\n if (candidate?.candidate === \"\") return;\n if (candidates.length >= 256 || !this.validTrickleCandidate(candidate)) {\n failure = new Error(\"Invalid or excessive local ICE candidates\");\n } else {\n candidates.push(candidate);\n if (candidate === null) ended = true;\n }\n wake?.();\n };\n pc.addEventListener(\"icecandidate\", onCandidate);\n let pollTimer: ReturnType<typeof setTimeout> | undefined;\n const timeout = setTimeout(\n () => controller.abort(new Error(\"Trickle ICE timed out\")),\n this.iceGatheringTimeoutMs + this.signalingTimeoutMs,\n );\n try {\n const offer = await this.withDeadline(\n pc.createOffer(),\n this.signalingTimeoutMs,\n controller.signal,\n );\n const applying = this.withDeadline(\n pc.setLocalDescription(offer),\n this.iceGatheringTimeoutMs,\n controller.signal,\n );\n await this.withDeadline(\n new Promise<void>((resolve, reject) => {\n const check = () => {\n if (pc.signalingState === \"have-local-offer\" && pc.localDescription?.sdp)\n return resolve();\n pollTimer = setTimeout(check, 5);\n };\n void applying.catch(reject);\n check();\n }),\n this.iceGatheringTimeoutMs,\n controller.signal,\n );\n clearTimeout(pollTimer);\n const response = await this.withDeadline(\n fetch(session.signalingUrl, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ sessionId: session.id, sdp: pc.localDescription, trickle: true }),\n signal: controller.signal,\n }),\n this.signalingTimeoutMs,\n controller.signal,\n );\n if (!response.ok)\n throw new Error(`Signaling HTTP error: ${response.status} ${response.statusText}`);\n const result: unknown = await this.withDeadline(\n response.json(),\n this.signalingTimeoutMs,\n controller.signal,\n );\n if (\n !result ||\n typeof result !== \"object\" ||\n !(\"generation\" in result) ||\n typeof result.generation !== \"string\" ||\n !result.generation ||\n result.generation.length > 256 ||\n !(\"sdp\" in result) ||\n !result.sdp ||\n typeof result.sdp !== \"object\" ||\n !(\"type\" in result.sdp) ||\n result.sdp.type !== \"answer\" ||\n !(\"sdp\" in result.sdp) ||\n typeof result.sdp.sdp !== \"string\" ||\n !result.sdp.sdp\n ) {\n throw new Error(\"Signaling server returned an invalid trickle answer\");\n }\n const generation = result.generation;\n await this.withDeadline(\n this.setRemoteDescription(pc, { type: \"answer\", sdp: result.sdp.sdp }),\n this.signalingTimeoutMs,\n controller.signal,\n );\n const upload = async () => {\n let index = 0;\n while (true) {\n if (controller.signal.aborted) throw abortError(controller.signal);\n if (failure) throw failure;\n if (index === candidates.length) {\n await new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n wake = undefined;\n reject(abortError(controller.signal));\n };\n wake = () => {\n controller.signal.removeEventListener(\"abort\", onAbort);\n wake = undefined;\n resolve();\n };\n controller.signal.addEventListener(\"abort\", onAbort, { once: true });\n if (controller.signal.aborted) onAbort();\n });\n continue;\n }\n const candidate = candidates[index++];\n const patch = await fetch(session.signalingUrl, {\n method: \"PATCH\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ sessionId: session.id, generation, candidate }),\n signal: controller.signal,\n });\n if (patch.status !== 204) throw new Error(`ICE candidate HTTP error: ${patch.status}`);\n if (candidate === null) return;\n }\n };\n const download = async () => {\n let cursor = 0;\n while (true) {\n const url = new URL(session.signalingUrl);\n url.searchParams.set(\"sessionId\", session.id);\n url.searchParams.set(\"generation\", generation);\n url.searchParams.set(\"cursor\", String(cursor));\n const pageResponse = await fetch(url, { signal: controller.signal });\n if (!pageResponse.ok) throw new Error(`ICE polling HTTP error: ${pageResponse.status}`);\n const page: unknown = await pageResponse.json();\n if (\n !page ||\n typeof page !== \"object\" ||\n !(\"generation\" in page) ||\n page.generation !== generation ||\n !(\"candidates\" in page) ||\n !Array.isArray(page.candidates) ||\n !(\"next\" in page) ||\n !Number.isInteger(page.next) ||\n page.next !== cursor + page.candidates.length ||\n (page.next as number) > 256 ||\n !page.candidates.every((candidate: unknown) => this.validTrickleCandidate(candidate)) ||\n page.candidates.slice(0, -1).includes(null)\n )\n throw new Error(\"Signaling server returned an invalid ICE candidate page\");\n for (const candidate of page.candidates as (RTCIceCandidateInit | null)[]) {\n await this.withDeadline(\n pc.addIceCandidate(candidate),\n this.iceGatheringTimeoutMs,\n controller.signal,\n );\n if (candidate === null) return;\n }\n cursor = page.next as number;\n if (!page.candidates.length)\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n controller.signal.removeEventListener(\"abort\", onAbort);\n resolve();\n }, 50);\n const onAbort = () => {\n clearTimeout(timer);\n reject(abortError(controller.signal));\n };\n controller.signal.addEventListener(\"abort\", onAbort, { once: true });\n if (controller.signal.aborted) onAbort();\n });\n }\n };\n await Promise.all([\n applying.then(() => {\n if (!ended && !failure) {\n if (candidates.length >= 256)\n failure = new Error(\"Invalid or excessive local ICE candidates\");\n else {\n candidates.push(null);\n ended = true;\n }\n wake?.();\n }\n }),\n upload(),\n download(),\n ]);\n } finally {\n controller.abort();\n clearTimeout(timeout);\n clearTimeout(pollTimer);\n pc.removeEventListener(\"icecandidate\", onCandidate);\n signal?.removeEventListener(\"abort\", abort);\n if (session.trickleAbort === controller) delete session.trickleAbort;\n }\n }\n\n private validTrickleCandidate(value: unknown): value is RTCIceCandidateInit | null {\n if (value === null) return true;\n if (!value || typeof value !== \"object\") return false;\n const candidate = value as RTCIceCandidateInit;\n return (\n typeof candidate.candidate === \"string\" &&\n candidate.candidate.length > 0 &&\n candidate.candidate.length <= 4096 &&\n (candidate.sdpMid == null ||\n (typeof candidate.sdpMid === \"string\" && candidate.sdpMid.length <= 256)) &&\n (candidate.sdpMLineIndex == null ||\n (Number.isInteger(candidate.sdpMLineIndex) &&\n candidate.sdpMLineIndex >= 0 &&\n candidate.sdpMLineIndex <= 65535)) &&\n (candidate.sdpMid != null || candidate.sdpMLineIndex != null) &&\n (candidate.usernameFragment == null ||\n (typeof candidate.usernameFragment === \"string\" &&\n candidate.usernameFragment.length <= 256))\n );\n }\n\n private async setRemoteDescription(\n pc: WeriftPeerConnection,\n description: RTCSessionDescriptionInit,\n ): Promise<void> {\n const previous = new Set(pc.dtlsTransports);\n try {\n await pc.setRemoteDescription(description);\n } finally {\n // Werift can replace transports while applying BUNDLE without stopping the\n // displaced ICE sockets. They are no longer reachable by pc.close().\n const active = new Set(pc.dtlsTransports);\n await Promise.all(\n [...previous]\n .filter((transport) => !active.has(transport))\n .map((transport) => transport.stop()),\n );\n }\n }\n\n private async withDeadline<T>(\n operation: Promise<T>,\n timeoutMs: number,\n signal?: AbortSignal,\n ): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n let onAbort: (() => void) | undefined;\n try {\n return await Promise.race([\n operation,\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(\n () => reject(new Error(`WebRTC operation timed out after ${timeoutMs} ms`)),\n timeoutMs,\n );\n onAbort = () => reject(abortError(signal));\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n if (signal?.aborted) onAbort();\n }),\n ]);\n } finally {\n clearTimeout(timer);\n if (onAbort) signal?.removeEventListener(\"abort\", onAbort);\n }\n }\n\n private async waitForIceGatheringComplete(\n pc: WeriftPeerConnection,\n timeoutMs: number,\n ): Promise<boolean> {\n if (pc.iceGatheringState === \"complete\") return true;\n\n const completed = await new Promise<boolean>((resolve) => {\n let settled = false;\n let timer: ReturnType<typeof setTimeout>;\n\n const cleanup = () => {\n clearTimeout(timer);\n pc.removeEventListener(\"icegatheringstatechange\", onStateChange);\n };\n\n const finish = (value: boolean) => {\n if (settled) return;\n settled = true;\n cleanup();\n resolve(value);\n };\n\n const onStateChange = () => {\n if (pc.iceGatheringState === \"complete\") finish(true);\n };\n\n timer = setTimeout(() => finish(false), timeoutMs);\n pc.addEventListener(\"icegatheringstatechange\", onStateChange);\n onStateChange();\n });\n\n if (!completed) throw new Error(`ICE gathering timed out after ${timeoutMs} ms`);\n return true;\n }\n\n private async waitForChannelOpen(\n channel: WeriftDataChannel,\n timeoutMs: number,\n signal?: AbortSignal,\n ): Promise<void> {\n if (channel.readyState === \"open\") return;\n if (signal?.aborted) throw abortError(signal);\n\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n const cleanup = () => {\n clearTimeout(timer);\n channel.removeEventListener(\"open\", onOpen);\n channel.removeEventListener(\"error\", onError);\n channel.removeEventListener(\"close\", onClose);\n signal?.removeEventListener(\"abort\", onAbort);\n };\n const finish = (error?: Error) => {\n if (settled) return;\n settled = true;\n cleanup();\n if (error) reject(error);\n else resolve();\n };\n const onOpen = () => finish();\n const onError = () => finish(new Error(\"WebRTC DataChannel failed to open\"));\n const onClose = () => finish(new Error(\"WebRTC DataChannel closed before opening\"));\n const onAbort = () => finish(abortError(signal));\n const timer = setTimeout(\n () => finish(new Error(`DataChannel open timeout after ${timeoutMs} ms`)),\n timeoutMs,\n );\n\n channel.addEventListener(\"open\", onOpen);\n channel.addEventListener(\"error\", onError);\n channel.addEventListener(\"close\", onClose);\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n if (channel.readyState === \"open\") finish();\n else if (channel.readyState === \"closed\") finish(new Error(\"WebRTC DataChannel is closed\"));\n });\n }\n\n private sendRequest(\n session: ClientSession,\n request: TransportRequest,\n requestedTimeout?: number,\n ): Promise<TransportResponse> {\n if (request.signal?.aborted) return Promise.reject(abortError(request.signal));\n if (session.dataChannel.readyState !== \"open\") {\n return Promise.reject(new Error(\"WebRTC DataChannel is not open\"));\n }\n\n const requestId = this.createId(`request${++this.requestSequence}`);\n const timeoutMs =\n requestedTimeout && requestedTimeout > 0 ? requestedTimeout : this.requestTimeoutMs;\n\n return new Promise<TransportResponse>((resolve, reject) => {\n const pending: PendingResponse = {\n resolve,\n reject,\n timer: setTimeout(() => {\n this.finishPending(session, requestId);\n reject(new Error(`WebRTC request timed out after ${timeoutMs} ms`));\n }, timeoutMs),\n ...(request.signal ? { signal: request.signal } : {}),\n };\n\n if (request.signal) {\n pending.abortListener = () => {\n this.finishPending(session, requestId);\n reject(abortError(request.signal));\n };\n request.signal.addEventListener(\"abort\", pending.abortListener, { once: true });\n }\n\n session.pending.set(requestId, pending);\n\n try {\n session.dataChannel.send(\n JSON.stringify({\n requestId,\n method: request.method,\n url: request.url,\n headers: request.headers,\n body: encodeWireBody(request.body),\n protocol: request.protocol,\n }),\n );\n } catch (error) {\n this.finishPending(session, requestId);\n reject(errorFromUnknown(error, \"Failed to send WebRTC request\"));\n }\n });\n }\n\n private finishPending(session: ClientSession, requestId: string): void {\n const pending = session.pending.get(requestId);\n if (!pending) return;\n clearTimeout(pending.timer);\n if (pending.signal && pending.abortListener) {\n pending.signal.removeEventListener(\"abort\", pending.abortListener);\n }\n session.pending.delete(requestId);\n }\n\n private rejectAllPending(session: ClientSession, error: Error): void {\n for (const [requestId, pending] of session.pending) {\n this.finishPending(session, requestId);\n pending.reject(error);\n }\n }\n\n private async disposeClientSession(session: ClientSession, notifyRemote: boolean): Promise<void> {\n if (session.closed) return;\n session.closed = true;\n session.trickleAbort?.abort(new Error(\"WebRTC session closed\"));\n\n if (this.sessions.get(session.peerUrl) === session) {\n this.sessions.delete(session.peerUrl);\n }\n this.rejectAllPending(session, new Error(\"WebRTC session closed\"));\n\n try {\n session.dataChannel.close();\n } catch {}\n try {\n await session.peerConnection.close();\n } catch {}\n\n if (notifyRemote) {\n try {\n const url = new URL(session.signalingUrl);\n url.searchParams.set(\"sessionId\", session.id);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 2_000);\n try {\n await fetch(url, { method: \"DELETE\", signal: controller.signal });\n } finally {\n clearTimeout(timer);\n }\n } catch {}\n }\n }\n\n private attachServerPeerEvents(\n session: ServerSession,\n onRequest: TransportListenOptions[\"onRequest\"],\n ): void {\n const pc = session.peerConnection;\n\n pc.addEventListener(\"datachannel\", (event: any) => {\n const channel = event.channel;\n channel.addEventListener(\"message\", (messageEvent: MessageEvent) => {\n void (async () => {\n let requestId: string | undefined;\n try {\n const message = JSON.parse(String(messageEvent.data)) as {\n requestId?: unknown;\n method?: unknown;\n url?: unknown;\n headers?: unknown;\n body?: unknown;\n protocol?: unknown;\n };\n\n requestId = typeof message.requestId === \"string\" ? message.requestId : undefined;\n if (\n !requestId ||\n typeof message.method !== \"string\" ||\n typeof message.url !== \"string\"\n ) {\n throw new Error(\"Invalid WebRTC transport request\");\n }\n\n const request: TransportRequest = {\n method: message.method,\n url: message.url,\n headers:\n message.headers && typeof message.headers === \"object\"\n ? {\n ...(message.headers as TransportRequest[\"headers\"]),\n \"x-webrtc-session-id\": session.id,\n }\n : { \"x-webrtc-session-id\": session.id },\n body: decodeWireBody(message.body),\n protocol:\n typeof message.protocol === \"string\"\n ? (message.protocol as TransportRequest[\"protocol\"])\n : \"webrtc\",\n };\n\n let response: TransportResponse;\n if (!onRequest) {\n response = {\n status: 501,\n statusText: \"Not Implemented\",\n headers: { \"content-type\": \"application/json\" },\n body: { error: \"No server request handler configured\" },\n url: request.url,\n };\n } else {\n response = await onRequest(request);\n }\n\n if (channel.readyState !== \"open\") return;\n channel.send(\n JSON.stringify({\n requestId,\n status: response.status,\n statusText: response.statusText ?? \"\",\n headers: response.headers,\n body: encodeWireBody(response.body),\n url: response.url ?? request.url,\n }),\n );\n } catch (error) {\n this.log(\"error\", \"[WebRtcTransport Server] Request failed:\", error);\n if (requestId && channel.readyState === \"open\") {\n try {\n channel.send(\n JSON.stringify({\n requestId,\n status: 500,\n statusText: \"Internal Server Error\",\n headers: { \"content-type\": \"application/json\" },\n body: { error: errorFromUnknown(error, \"Request handler failed\").message },\n }),\n );\n } catch {}\n }\n }\n })();\n });\n });\n\n pc.addEventListener(\"track\", (event: any) => {\n const track = event.track;\n if (!session.remoteStream.getTracks().some((item: any) => item.id === track.id)) {\n session.remoteStream.addTrack(track);\n }\n this.emitTrack({\n side: \"server\",\n sessionId: session.id,\n peerConnection: pc,\n track,\n stream: session.remoteStream,\n streams: event.streams,\n });\n });\n\n pc.addEventListener(\"connectionstatechange\", () => {\n if (pc.connectionState === \"failed\" || pc.connectionState === \"closed\") {\n void this.closeServerSession(session.id);\n }\n });\n }\n\n private async attachConfiguredServerStream(session: ServerSession): Promise<void> {\n if (session.closed) return;\n\n if (!session.localStreamResolved) {\n const configured = this.options.localStream;\n const isFactory = typeof configured === \"function\";\n const stream = isFactory ? await configured(session.id, session.context) : configured;\n if (session.closed) {\n if (stream && isFactory) {\n session.localStream = stream;\n session.ownsLocalStream = true;\n this.disposeServerLocalStream(session);\n }\n return;\n }\n session.localStreamResolved = true;\n\n if (stream) {\n session.localStream = stream;\n session.ownsLocalStream = isFactory;\n }\n }\n\n const stream = session.localStream;\n if (!stream) return;\n\n const pc = session.peerConnection;\n for (const track of stream.getTracks()) {\n if (session.closed || track.readyState !== \"live\" || session.localTrackIds.has(track.id!)) {\n continue;\n }\n pc.addTrack(track, stream);\n session.localTrackIds.add(track.id!);\n }\n }\n\n private disposeServerLocalStream(session: ServerSession): void {\n const stream = session.localStream;\n const ownsStream = session.ownsLocalStream;\n\n delete session.localStream;\n session.ownsLocalStream = false;\n\n if (!stream || !ownsStream) return;\n\n try {\n this.options.disposeLocalStream?.(session.id, stream);\n } catch {\n } finally {\n for (const track of stream.getTracks()) {\n if (track.readyState !== \"ended\") track.stop();\n }\n }\n }\n\n /** Idempotent: concurrent closes share peer/resource cleanup. */\n public closeServerSession(sessionId: string): Promise<void> {\n const pending = this.closingServerSessions.get(sessionId);\n if (pending) return pending;\n const session = this.serverSessions.get(sessionId);\n if (!session || session.closed) return Promise.resolve();\n session.closed = true;\n session.trickle?.detach();\n delete session.trickle;\n session.controller.abort();\n session.release();\n this.serverSessions.delete(sessionId);\n const closing = Promise.resolve()\n .then(async () => {\n try {\n await session.peerConnection.close();\n } catch (error) {\n this.log(\"warn\", \"WebRTC peer close failed\", error);\n } finally {\n this.disposeServerLocalStream(session);\n this.emitServerSession(session, \"closed\");\n }\n })\n .finally(() => this.closingServerSessions.delete(sessionId));\n this.closingServerSessions.set(sessionId, closing);\n return closing;\n }\n\n private emitTrack(event: WebRtcTrackEvent): void {\n for (const listener of this.trackListeners) {\n try {\n listener(event);\n } catch (error) {\n this.log(\"error\", \"[WebRtcTransport] onTrack listener failed:\", error);\n }\n }\n }\n\n private toSignalingUrl(peerUrl: string): string {\n if (peerUrl.startsWith(\"webrtcs://\")) return peerUrl.replace(/^webrtcs:\\/\\//, \"https://\");\n if (peerUrl.startsWith(\"webrtc://\")) return peerUrl.replace(/^webrtc:\\/\\//, \"http://\");\n throw new Error(`Unsupported WebRTC signaling URL: ${peerUrl}`);\n }\n\n private createId(prefix: string): string {\n return `${prefix}-${crypto.randomUUID()}`;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["MAX_SIGNALING_BODY_BYTES", "abortError", "decodeWireBody", "encodeWireBody", "errorFromUnknown", "createServer", "WeriftMediaStream", "WeriftPeerConnection", "normalizeIceServers", "servers", "server", "urls", "url", "createWeriftPeerConnection", "configuration", "config", "createWeriftMediaStream", "options", "level", "message", "meta", "output", "listener", "session", "type", "error", "sessionId", "peerUrl", "request", "rtcRequest", "ownedSessions", "port", "host", "signalingPath", "char", "code", "closed", "closePromise", "pendingAuthorizations", "httpServer", "req", "res", "sendJson", "status", "body", "parsedUrl", "ownerId", "authorization", "controller", "onDisconnect", "onAuthorizationAbort", "headers", "name", "value", "allowed", "existing", "id", "state", "cursor", "raw", "chunk", "payload", "pc", "target", "candidate", "apply", "activeSession", "disconnect", "onSessionAbort", "answer", "peer", "onCandidate", "event", "applying", "timer", "resolve", "reject", "check", "localDescription", "onError", "onListening", "onAbort", "closeServer", "stopped", "inFlight", "connectionPromise", "signalingUrl", "channel", "trackListeners", "trackEndedListeners", "streamListeners", "track", "stream", "kind", "sender", "s", "item", "pending", "operation", "changed", "receiveAudio", "receiveVideo", "ensureReceive", "enabled", "signal", "offer", "abortListener", "response", "text", "abort", "candidates", "ended", "wake", "failure", "pollTimer", "timeout", "result", "generation", "upload", "index", "patch", "download", "pageResponse", "page", "description", "previous", "active", "transport", "timeoutMs", "_resolve", "settled", "cleanup", "onStateChange", "finish", "onOpen", "onClose", "requestedTimeout", "requestId", "notifyRemote", "onRequest", "messageEvent", "configured", "isFactory", "ownsStream", "closing", "prefix"]
7
7
  }