@hyperttp/transport-webrtc 0.3.0 → 0.4.1
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 +32 -5
- package/{dist/browser.d.ts → browser.d.ts} +3 -0
- package/browser.d.ts.map +1 -0
- package/browser.js +2 -0
- package/browser.js.map +7 -0
- package/index.d.ts +4 -0
- package/{dist/index.d.ts.map → index.d.ts.map} +1 -1
- package/index.js +2 -0
- package/index.js.map +7 -0
- package/package.json +14 -36
- package/{dist/pcm-audio.d.ts → pcm-audio.d.ts} +3 -1
- package/pcm-audio.d.ts.map +1 -0
- package/pcm-audio.js +2 -0
- package/pcm-audio.js.map +7 -0
- package/rtp-pacer.d.ts +20 -0
- package/rtp-pacer.d.ts.map +1 -0
- package/rtp-pacer.js +2 -0
- package/rtp-pacer.js.map +7 -0
- package/{dist/types → types}/types.d.ts +23 -0
- package/{dist/types → types}/types.d.ts.map +1 -1
- package/types/types.js +2 -0
- package/{dist/types → types}/types.js.map +3 -3
- package/utils/helper.js +2 -0
- package/{dist/utils → utils}/helper.js.map +2 -2
- package/{dist/webrtc.d.ts → webrtc.d.ts} +2 -0
- package/{dist/webrtc.d.ts.map → webrtc.d.ts.map} +1 -1
- package/webrtc.js +2 -0
- package/webrtc.js.map +7 -0
- package/dist/browser.d.ts.map +0 -1
- package/dist/browser.js +0 -2
- package/dist/browser.js.map +0 -7
- package/dist/index.d.ts +0 -4
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -7
- package/dist/pcm-audio.d.ts.map +0 -1
- package/dist/pcm-audio.js +0 -2
- package/dist/pcm-audio.js.map +0 -7
- package/dist/types/types.js +0 -2
- package/dist/utils/helper.js +0 -2
- package/dist/webrtc.js +0 -2
- package/dist/webrtc.js.map +0 -7
- /package/{dist/utils → utils}/helper.d.ts +0 -0
- /package/{dist/utils → utils}/helper.d.ts.map +0 -0
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.
|
|
@@ -110,7 +118,7 @@ Local browser mute/volume should not change the common source's volume.
|
|
|
110
118
|
|
|
111
119
|
## Deployment constraints
|
|
112
120
|
|
|
113
|
-
- Signaling
|
|
121
|
+
- Signaling supports opt-in trickle ICE or the default full SDP exchange over HTTP POST `/signal`; DELETE releases a session.
|
|
114
122
|
- `webrtc://` maps to HTTP, `webrtcs://` to HTTPS. Terminate TLS at a reverse proxy.
|
|
115
123
|
- The built-in signaling server has no authentication, CORS policy, or session
|
|
116
124
|
quotas. Use a same-origin authenticated gateway before exposing it publicly;
|
|
@@ -135,6 +143,16 @@ npm run build
|
|
|
135
143
|
Network integration tests use loopback and no public STUN servers. PCM adapter
|
|
136
144
|
integration tests require FFmpeg with libopus.
|
|
137
145
|
|
|
146
|
+
Run `bun run typecheck:runtimes` for separate shared, Node, Bun and Deno type
|
|
147
|
+
checks. Shared and Deno checks cover the browser-safe modules only; Node and Bun
|
|
148
|
+
checks cover all source. Bun's types include Node compatibility types.
|
|
149
|
+
Run `bun run test:node`, `bun run test:bun`, and `bun run test:deno` to execute
|
|
150
|
+
Vitest in each actual runtime; `tests/runtime.test.ts` verifies which runtime is
|
|
151
|
+
running. Deno requires `-A` for these tests, so only run trusted code. Chromium
|
|
152
|
+
integration tests require `/usr/bin/chromium` or `PLAYWRIGHT_CHROMIUM_EXECUTABLE`
|
|
153
|
+
and otherwise skip. The shutdown test launches the corresponding runtime in a
|
|
154
|
+
separate process to check that WebRTC resources do not keep it alive.
|
|
155
|
+
|
|
138
156
|
## Integration boundaries
|
|
139
157
|
|
|
140
158
|
The root entry has a `browser` export condition for bundlers that support it;
|
|
@@ -206,7 +224,16 @@ Use `closeServerSession(sessionId)` for idempotent application cleanup. The
|
|
|
206
224
|
trusted authorization context and an abort signal. `WebRtcTransport.close()`
|
|
207
225
|
closes all listener-owned sessions and emits the corresponding close events.
|
|
208
226
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
227
|
+
Without opting in, the server gathers all ICE candidates before returning its SDP
|
|
228
|
+
answer. Gathering timeouts fail the request rather than returning a partial answer.
|
|
229
|
+
|
|
230
|
+
### Opt-in server trickle ICE wire protocol
|
|
231
|
+
|
|
232
|
+
The server supports trickle ICE on the configured signaling path (default `/signal`). The existing clients and `POST` without `trickle: true` are unchanged: they exchange a fully gathered SDP answer. Browser and Werift-based clients on Node.js, Bun and Deno can opt in with `new WebRtcTransport({ trickleIce: true })`. The Werift client currently waits for the candidate exchange to finish before completing negotiation.
|
|
233
|
+
|
|
234
|
+
* `POST /signal` with JSON `{ "sessionId": "...", "sdp": { "type": "offer", "sdp": "..." }, "trickle": true }` returns `200` JSON `{ "sdp": { "type": "answer", "sdp": "..." }, "generation": "..." }` as soon as the local answer is available, potentially before gathering completes. Apply the answer before its candidates. The answer can already contain candidates.
|
|
235
|
+
* `GET /signal?sessionId=...&generation=...&cursor=0` returns `200` JSON `{ "generation": "...", "candidates": [...], "next": 0 }`. Poll with the returned `next` cursor; it is an array index, and repeated requests at the same cursor replay the same entries. Each entry is an `RTCIceCandidateInit` or `null` (end of candidates). A response without `null` is not complete. The cursor defaults to `0`.
|
|
236
|
+
* `PATCH /signal` with JSON `{ "sessionId": "...", "generation": "...", "candidate": ... }` applies one client `RTCIceCandidateInit` or `null` for end-of-candidates. A non-null candidate needs a nonempty `candidate` string and `sdpMid` or `sdpMLineIndex`. Success is `204`. Send candidates in order and await each response, including the final `null`.
|
|
237
|
+
* `DELETE /signal?sessionId=...` closes the session as before. All operations pass through the configured authorization callback; GET and PATCH must have the same owner identity and listener as the session. Anonymous/boolean authorization retains its existing anonymous ownership semantics.
|
|
238
|
+
|
|
239
|
+
Every accepted new offer invalidates the previous generation, including when switching back to legacy signaling. A new offer while negotiation or server ICE gathering is in progress returns `409`; stale or missing generations return `409`, unknown sessions `404`, owner mismatch `403`, and invalid payloads/cursors `400`. At most 256 entries (including end-of-candidates) are accepted per direction per generation; further client entries return `429`, and server overflow closes the session. Candidate strings are limited to 4096 characters, IDs to 256 characters, and request bodies to 1,000,000 bytes. Session closure, disconnection, failed gathering, and listener shutdown release generation state. Clients should stop polling after `null`, close unused sessions, and treat non-success responses as terminal or restart negotiation as appropriate.
|
|
@@ -10,6 +10,7 @@ export type WebRtcTransportRequest = TransportRequest & {
|
|
|
10
10
|
};
|
|
11
11
|
export interface WebRtcTransportOptions {
|
|
12
12
|
readonly iceServers?: RTCIceServer[];
|
|
13
|
+
readonly trickleIce?: boolean;
|
|
13
14
|
readonly iceGatheringTimeoutMs?: number;
|
|
14
15
|
readonly signalingTimeoutMs?: number;
|
|
15
16
|
readonly connectionTimeoutMs?: number;
|
|
@@ -53,6 +54,8 @@ export declare class WebRtcTransport implements HyperTransport {
|
|
|
53
54
|
private createSession;
|
|
54
55
|
private configureMedia;
|
|
55
56
|
private negotiate;
|
|
57
|
+
private negotiateTrickle;
|
|
58
|
+
private validCandidate;
|
|
56
59
|
private receive;
|
|
57
60
|
private sendRequest;
|
|
58
61
|
private dispose;
|
package/browser.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAG3F,MAAM,MAAM,sBAAsB,GAAG,gBAAgB,GAAG;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC,aAAa,EAAE,gBAAgB,KAAK,iBAAiB,CAAC;IACvF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,WAAW,CAAC;CAChD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,iBAAiB,CAAC;IAC3C,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,iBAAiB,CAAC;IAC3C,QAAQ,CAAC,WAAW,EAAE,cAAc,CAAC;IACrC,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;CACpC;AAaD,mFAAmF;AACnF,qBAAa,eAAgB,YAAW,cAAc;IASxC,OAAO,CAAC,QAAQ,CAAC,OAAO;IARpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8B;IACvD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;IAC9D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgD;IAC/E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,QAAQ,CAAK;IAErB,YAA6B,OAAO,GAAE,sBAA2B,EAAI;IAErE,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI,CAK/D;IAED,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAWzD;IAEK,OAAO,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAsDzE;IAED,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAYrB;IAED,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAEvB;IAED,OAAO,CAAC,aAAa;IA4ErB,OAAO,CAAC,cAAc;YA6BR,SAAS;YA6DT,gBAAgB;IAgM9B,OAAO,CAAC,cAAc;IAqBtB,OAAO,CAAC,OAAO;IA6Bf,OAAO,CAAC,WAAW;IAmDnB,OAAO,CAAC,OAAO;YAoCD,GAAG;IA6BjB,OAAO,CAAC,SAAS;IAuBjB,OAAO,CAAC,OAAO;CAyBhB"}
|
package/browser.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";import{abortError as p,decodeWireBody as x,encodeWireBody as I}from"./utils/helper.js";export class WebRtcTransport{options;sessions=new Map;queues=new Map;trackListeners=new Set;disposals=new Set;closed=!1;closing;sequence=0;constructor(e={}){this.options=e}onTrack(e){return this.trackListeners.add(e),()=>{this.trackListeners.delete(e)}}getSession(e){const t=this.sessions.get(e);if(!(!t||t.controller.signal.aborted))return{id:t.id,peerUrl:t.peerUrl,peerConnection:t.peerConnection,dataChannel:t.dataChannel,remoteStream:t.remoteStream,...t.localStream?{localStream:t.localStream}:{}}}async execute(e){if(this.closed)throw new Error("WebRTC transport is closed");if(e.signal?.aborted)throw p(e.signal);const t=e.peerUrl??e.url,o=(this.queues.get(t)??Promise.resolve()).catch(()=>{}).then(async()=>{if(this.closed)throw new Error("WebRTC transport is closed");if(e.signal?.aborted)throw p(e.signal);let s=this.sessions.get(t);if(s&&(s.controller.signal.aborted||s.dataChannel.readyState!=="open"||["failed","closed"].includes(s.peerConnection.connectionState))&&(await this.dispose(s),s=void 0),this.closed)throw new Error("WebRTC transport is closed");s??=this.createSession(t,e);try{const a=this.configureMedia(s,e);return(!s.signaled||a)&&await this.negotiate(s,e.signal),await this.run(s,e.signal,this.options.connectionTimeoutMs??15e3,"DataChannel open",c=>this.waitFor(s.dataChannel,["open","close","error"],c,()=>s.dataChannel.readyState==="open",()=>["closing","closed"].includes(s.dataChannel.readyState))),s}catch(a){throw this.dispose(s),a}});this.queues.set(t,o);const r=()=>{this.queues.get(t)===o&&this.queues.delete(t)};o.then(r,r);const i=await this.abortable(o,e.signal);return this.sendRequest(i,e)}close(){if(this.closing)return this.closing;this.closed=!0;const e=[...this.queues.values()],t=[...this.sessions.values()];return this.closing=(async()=>{await Promise.all(t.map(n=>this.dispose(n))),await Promise.allSettled(e),await Promise.all(this.disposals),this.trackListeners.clear()})(),this.closing}destroy(){return this.close()}createSession(e,t){const n=new URL(e.replace(/^webrtc:/i,"http:").replace(/^webrtcs:/i,"https:"));if(n.protocol!=="http:"&&n.protocol!=="https:")throw new Error(`Unsupported WebRTC signaling protocol: ${n.protocol}`);n.pathname==="/"&&(n.pathname="/signal"),n.search="",n.hash="";const o=(this.options.createPeerConnection??(d=>new RTCPeerConnection(d)))({iceServers:t.iceServers??this.options.iceServers??[{urls:"stun:stun.l.google.com:19302"}]});let r;try{r={id:`session-${crypto.randomUUID()}`,peerUrl:e,signalingUrl:n.toString(),peerConnection:o,dataChannel:o.createDataChannel(t.channelLabel??"hyperttp-webrtc-transport",{ordered:!0}),remoteStream:(this.options.createMediaStream??(()=>new MediaStream))(),controller:new AbortController,pending:new Map,cleanup:[],signaled:!1}}catch(d){throw o.close(),d}this.sessions.set(e,r);const i=d=>{r.remoteStream.getTracks().includes(d.track)||r.remoteStream.addTrack(d.track);const l={side:"client",sessionId:r.id,peerUrl:e,peerConnection:o,track:d.track,stream:r.remoteStream,streams:d.streams};for(const g of this.trackListeners)try{g(l)}catch{}},s=()=>{(o.connectionState==="failed"||o.connectionState==="closed")&&this.dispose(r)},a=()=>{this.dispose(r)},c=d=>this.receive(r,d);return o.addEventListener("track",i),o.addEventListener("connectionstatechange",s),r.dataChannel.addEventListener("message",c),r.dataChannel.addEventListener("close",a),r.dataChannel.addEventListener("error",a),r.cleanup.push(()=>{o.removeEventListener("track",i),o.removeEventListener("connectionstatechange",s),r.dataChannel.removeEventListener("message",c),r.dataChannel.removeEventListener("close",a),r.dataChannel.removeEventListener("error",a)}),r}configureMedia(e,t){const n=e.peerConnection;let o=!1;if(t.localStream){for(const r of t.localStream.getTracks())r.readyState==="live"&&!n.getSenders().some(i=>i.track===r)&&(n.addTrack(r,t.localStream),o=!0);e.localStream=t.localStream}for(const r of["audio","video"]){if(!(r==="audio"?t.receiveAudio:t.receiveVideo))continue;const i=n.getTransceivers().find(s=>s.receiver.track.kind===r);i?(i.direction==="sendonly"||i.direction==="inactive")&&(i.direction=i.direction==="sendonly"?"sendrecv":"recvonly",o=!0):(n.addTransceiver(r,{direction:"recvonly"}),o=!0)}return o}async negotiate(e,t){if(e.exchange&&await e.exchange,this.options.trickleIce)return this.negotiateTrickle(e,t);const n=e.peerConnection;await this.run(e,t,this.options.signalingTimeoutMs??1e4,"SDP offer",async o=>{const r=await n.createOffer();if(o.aborted)throw p(o);await n.setLocalDescription(r)}),await this.run(e,t,this.options.iceGatheringTimeoutMs??1e4,"ICE gathering",o=>this.waitFor(n,["icegatheringstatechange"],o,()=>n.iceGatheringState==="complete")),await this.run(e,t,this.options.signalingTimeoutMs??1e4,"Signaling",async o=>{if(!n.localDescription?.sdp)throw new Error("Local SDP offer was not generated");e.signaled=!0;const r=await fetch(e.signalingUrl,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({sessionId:e.id,sdp:n.localDescription}),signal:o});if(!r.ok)throw new Error(`Signaling HTTP error: ${r.status} ${r.statusText}`);const i=await r.json();if(!i||typeof i!="object"||!("type"in i)||i.type!=="answer"||!("sdp"in i)||typeof i.sdp!="string")throw new Error("Signaling server returned an invalid SDP answer");if(o.aborted)throw p(o);await n.setRemoteDescription({type:"answer",sdp:i.sdp})})}async negotiateTrickle(e,t){const n=e.peerConnection;let o,r;const i=new Promise((a,c)=>{o=a,r=c}),s=this.run(e,void 0,this.options.iceGatheringTimeoutMs??1e4,"Trickle ICE",async a=>{const c=[];let d,l=!1,g;const v=C=>{if(l||a.aborted)return;const b=C.candidate?C.candidate.toJSON():null;b&&b.candidate===""||(c.length>=256||!this.validCandidate(b)?g=new Error("Invalid or excessive local ICE candidates"):(c.push(b),b===null&&(l=!0)),d?.())};n.addEventListener("icecandidate",v);try{const C=await n.createOffer();if(a.aborted)throw p(a);if(await n.setLocalDescription(C),a.aborted)throw p(a);if(n.iceGatheringState==="complete"&&!l&&!g&&(c.length>=256?g=new Error("Invalid or excessive local ICE candidates"):(c.push(null),l=!0)),!n.localDescription?.sdp)throw new Error("Local SDP offer was not generated");e.signaled=!0;const f=await this.run(e,t,this.options.signalingTimeoutMs??1e4,"Signaling",async w=>{const u=await fetch(e.signalingUrl,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({sessionId:e.id,sdp:n.localDescription,trickle:!0}),signal:w});if(!u.ok)throw new Error(`Signaling HTTP error: ${u.status} ${u.statusText}`);return u.json()});if(!f||typeof f!="object"||!("generation"in f)||typeof f.generation!="string"||!f.generation||f.generation.length>256||!("sdp"in f)||!f.sdp||typeof f.sdp!="object"||!("type"in f.sdp)||f.sdp.type!=="answer"||!("sdp"in f.sdp)||typeof f.sdp.sdp!="string"||!f.sdp.sdp)throw new Error("Signaling server returned an invalid trickle answer");const S=f.generation;if(a.aborted)throw p(a);if(await n.setRemoteDescription({type:"answer",sdp:f.sdp.sdp}),a.aborted)throw p(a);o();const L=async()=>{let w=0;for(;;){if(a.aborted)throw p(a);if(g)throw g;if(w===c.length){await new Promise((h,m)=>{const E=()=>{d=void 0,m(p(a))};d=()=>{a.removeEventListener("abort",E),d=void 0,h()},a.addEventListener("abort",E,{once:!0}),a.aborted&&E()});continue}const u=c[w++],y=await fetch(e.signalingUrl,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({sessionId:e.id,generation:S,candidate:u}),signal:a});if(y.status!==204)throw new Error(`ICE candidate HTTP error: ${y.status}`);if(u===null)return}},k=async()=>{let w=0;for(;;){if(a.aborted)throw p(a);const u=new URL(e.signalingUrl);u.searchParams.set("sessionId",e.id),u.searchParams.set("generation",S),u.searchParams.set("cursor",String(w));const y=await fetch(u,{signal:a});if(!y.ok)throw new Error(`ICE polling HTTP error: ${y.status}`);const h=await y.json();if(!h||typeof h!="object"||!("generation"in h)||h.generation!==S||!("candidates"in h)||!Array.isArray(h.candidates)||!("next"in h)||!Number.isInteger(h.next)||h.next!==w+h.candidates.length||h.next>256||!h.candidates.every(m=>this.validCandidate(m))||h.candidates.slice(0,-1).includes(null))throw new Error("Signaling server returned an invalid ICE candidate page");for(const m of h.candidates){if(a.aborted)throw p(a);if(await n.addIceCandidate(m),m===null)return}w=h.next,h.candidates.length||await new Promise((m,E)=>{const P=setTimeout(()=>{a.removeEventListener("abort",T),m()},50),T=()=>{clearTimeout(P),E(p(a))};a.addEventListener("abort",T,{once:!0}),a.aborted&&T()})}};await Promise.all([L(),k()])}finally{n.removeEventListener("icecandidate",v)}});e.exchange=s,s.then(()=>{e.exchange===s&&delete e.exchange},a=>{r(a),e.exchange===s&&delete e.exchange,this.dispose(e)}),await this.abortable(i,t)}validCandidate(e){if(e===null)return!0;if(!e||typeof e!="object"||Array.isArray(e))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)}receive(e,t){if(typeof t.data!="string")return;let n;try{const r=JSON.parse(t.data);if(!r||typeof r!="object")return;n=r}catch{return}if(typeof n.requestId!="string")return;const o=e.pending.get(n.requestId);if(o)try{o(void 0,{status:typeof n.status=="number"?n.status:200,statusText:typeof n.statusText=="string"?n.statusText:"OK",headers:n.headers&&typeof n.headers=="object"?n.headers:{},body:x(n.body),url:e.peerUrl})}catch(r){o(r)}}sendRequest(e,t){const n=t.timeout&&t.timeout>0?t.timeout:this.options.requestTimeoutMs??15e3;return this.run(e,t.signal,n,"WebRTC request",o=>new Promise((r,i)=>{if(e.dataChannel.readyState!=="open"){i(new Error("WebRTC DataChannel is not open"));return}const s=`request-${++this.sequence}`,a=(d,l)=>{e.pending.delete(s),o.removeEventListener("abort",c),l?r(l):i(d??new Error("WebRTC session closed"))},c=()=>a(p(o));if(e.pending.set(s,a),o.addEventListener("abort",c,{once:!0}),o.aborted){c();return}try{e.dataChannel.send(JSON.stringify({requestId:s,method:t.method,url:t.url,headers:t.headers,body:I(t.body),protocol:t.protocol}))}catch(d){a(d)}}))}dispose(e){if(e.disposal)return e.disposal;const t=Promise.resolve().then(async()=>{e.controller.abort(new Error("WebRTC session closed")),this.sessions.get(e.peerUrl)===e&&this.sessions.delete(e.peerUrl);for(const i of e.cleanup)i();e.cleanup.length=0;for(const i of e.pending.values())i(new Error("WebRTC session closed"));try{e.dataChannel.close()}catch{}try{e.peerConnection.close()}catch{}if(!e.signaled)return;const n=new URL(e.signalingUrl);n.searchParams.set("sessionId",e.id);const o=new AbortController,r=setTimeout(()=>o.abort(),2e3);try{await this.abortable(fetch(n,{method:"DELETE",signal:o.signal}),o.signal)}catch{}finally{clearTimeout(r)}});return e.disposal=t,this.disposals.add(t),t.then(()=>this.disposals.delete(t)),t}async run(e,t,n,o,r){const i=new AbortController,s=[e.controller.signal,...t?[t]:[]],a=()=>{const d=s.find(l=>l.aborted);d&&i.abort(p(d))};for(const d of s)d.addEventListener("abort",a,{once:!0});const c=setTimeout(()=>i.abort(new Error(`${o} timed out after ${n} ms`)),n);try{if(a(),i.signal.aborted)throw p(i.signal);return await this.abortable(r(i.signal),i.signal)}finally{i.abort(),clearTimeout(c);for(const d of s)d.removeEventListener("abort",a)}}abortable(e,t){return t?new Promise((n,o)=>{const r=()=>t.removeEventListener("abort",i),i=()=>{r(),o(p(t))};t.addEventListener("abort",i,{once:!0}),t.aborted&&i(),e.then(s=>{r(),n(s)},s=>{r(),o(s)})}):e}waitFor(e,t,n,o,r=()=>!1){return new Promise((i,s)=>{const a=l=>{for(const g of t)e.removeEventListener(g,d);n.removeEventListener("abort",c),l?s(l):i()},c=()=>a(p(n)),d=l=>{n.aborted?c():r()||l?.type==="error"?a(new Error("WebRTC connection failed")):o()&&a()};for(const l of t)e.addEventListener(l,d);n.addEventListener("abort",c,{once:!0}),d()})}}
|
|
2
|
+
//# sourceMappingURL=browser.js.map
|
package/browser.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/browser.ts"],
|
|
4
|
+
"sourcesContent": ["import type { HyperTransport, TransportRequest, TransportResponse } from \"@hyperttp/types\";\nimport { abortError, decodeWireBody, encodeWireBody } from \"./utils/helper.js\";\n\nexport type WebRtcTransportRequest = TransportRequest & {\n peerUrl?: string;\n iceServers?: RTCIceServer[];\n localStream?: MediaStream;\n receiveAudio?: boolean;\n receiveVideo?: boolean;\n channelLabel?: string;\n timeout?: number;\n};\n\nexport interface WebRtcTransportOptions {\n readonly iceServers?: RTCIceServer[];\n readonly trickleIce?: boolean;\n readonly iceGatheringTimeoutMs?: number;\n readonly signalingTimeoutMs?: number;\n readonly connectionTimeoutMs?: number;\n readonly requestTimeoutMs?: number;\n readonly createPeerConnection?: (configuration: RTCConfiguration) => RTCPeerConnection;\n readonly createMediaStream?: () => MediaStream;\n}\n\nexport interface WebRtcTrackEvent {\n readonly side: \"client\";\n readonly sessionId: string;\n readonly peerUrl: string;\n readonly peerConnection: RTCPeerConnection;\n readonly track: MediaStreamTrack;\n readonly stream: MediaStream;\n readonly streams: readonly MediaStream[];\n}\n\nexport interface WebRtcSessionView {\n readonly id: string;\n readonly peerUrl: string;\n readonly peerConnection: RTCPeerConnection;\n readonly dataChannel: RTCDataChannel;\n readonly remoteStream: MediaStream;\n readonly localStream?: MediaStream;\n}\n\ninterface Session extends WebRtcSessionView {\n localStream?: MediaStream;\n signalingUrl: string;\n controller: AbortController;\n pending: Map<string, (error?: unknown, response?: TransportResponse) => void>;\n cleanup: (() => void)[];\n signaled: boolean;\n disposal?: Promise<void>;\n exchange?: Promise<void>;\n}\n\n/** Browser client transport. Caller-owned local media tracks are never stopped. */\nexport class WebRtcTransport implements HyperTransport {\n private readonly sessions = new Map<string, Session>();\n private readonly queues = new Map<string, Promise<Session>>();\n private readonly trackListeners = new Set<(event: WebRtcTrackEvent) => void>();\n private readonly disposals = new Set<Promise<void>>();\n private closed = false;\n private closing: Promise<void> | undefined;\n private sequence = 0;\n\n constructor(private readonly options: WebRtcTransportOptions = {}) {}\n\n onTrack(listener: (event: WebRtcTrackEvent) => void): () => void {\n this.trackListeners.add(listener);\n return () => {\n this.trackListeners.delete(listener);\n };\n }\n\n getSession(peerUrl: string): WebRtcSessionView | undefined {\n const session = this.sessions.get(peerUrl);\n if (!session || session.controller.signal.aborted) return undefined;\n return {\n id: session.id,\n peerUrl: session.peerUrl,\n peerConnection: session.peerConnection,\n dataChannel: session.dataChannel,\n remoteStream: session.remoteStream,\n ...(session.localStream ? { localStream: session.localStream } : {}),\n };\n }\n\n async execute(request: WebRtcTransportRequest): Promise<TransportResponse> {\n if (this.closed) throw new Error(\"WebRTC transport is closed\");\n if (request.signal?.aborted) throw abortError(request.signal);\n const peerUrl = request.peerUrl ?? request.url;\n // Queue before awaiting anything, including disposal of an unusable session.\n const previous = this.queues.get(peerUrl);\n const operation = (previous ?? Promise.resolve())\n .catch(() => undefined)\n .then(async () => {\n if (this.closed) throw new Error(\"WebRTC transport is closed\");\n if (request.signal?.aborted) throw abortError(request.signal);\n let session = this.sessions.get(peerUrl);\n if (\n session &&\n (session.controller.signal.aborted ||\n session.dataChannel.readyState !== \"open\" ||\n [\"failed\", \"closed\"].includes(session.peerConnection.connectionState))\n ) {\n await this.dispose(session);\n session = undefined;\n }\n if (this.closed) throw new Error(\"WebRTC transport is closed\");\n session ??= this.createSession(peerUrl, request);\n try {\n const changed = this.configureMedia(session, request);\n if (!session.signaled || changed) await this.negotiate(session, request.signal);\n await this.run(\n session,\n request.signal,\n this.options.connectionTimeoutMs ?? 15_000,\n \"DataChannel open\",\n (signal) =>\n this.waitFor(\n session!.dataChannel,\n [\"open\", \"close\", \"error\"],\n signal,\n () => session!.dataChannel.readyState === \"open\",\n () => [\"closing\", \"closed\"].includes(session!.dataChannel.readyState),\n ),\n );\n return session;\n } catch (error) {\n void this.dispose(session);\n throw error;\n }\n });\n this.queues.set(peerUrl, operation);\n const remove = () => {\n if (this.queues.get(peerUrl) === operation) this.queues.delete(peerUrl);\n };\n void operation.then(remove, remove);\n // A queued caller can abort immediately without interrupting another caller's setup.\n const session = await this.abortable(operation, request.signal);\n return this.sendRequest(session, request);\n }\n\n close(): Promise<void> {\n if (this.closing) return this.closing;\n this.closed = true;\n const connections = [...this.queues.values()];\n const sessions = [...this.sessions.values()];\n this.closing = (async () => {\n await Promise.all(sessions.map((session) => this.dispose(session)));\n await Promise.allSettled(connections);\n await Promise.all(this.disposals);\n this.trackListeners.clear();\n })();\n return this.closing;\n }\n\n destroy(): Promise<void> {\n return this.close();\n }\n\n private createSession(peerUrl: string, request: WebRtcTransportRequest): Session {\n const url = new URL(peerUrl.replace(/^webrtc:/i, \"http:\").replace(/^webrtcs:/i, \"https:\"));\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new Error(`Unsupported WebRTC signaling protocol: ${url.protocol}`);\n }\n if (url.pathname === \"/\") url.pathname = \"/signal\";\n url.search = \"\";\n url.hash = \"\";\n const pc = (this.options.createPeerConnection ?? ((config) => new RTCPeerConnection(config)))({\n iceServers: request.iceServers ??\n this.options.iceServers ?? [{ urls: \"stun:stun.l.google.com:19302\" }],\n });\n let session: Session;\n try {\n session = {\n id: `session-${crypto.randomUUID()}`,\n peerUrl,\n signalingUrl: url.toString(),\n peerConnection: pc,\n dataChannel: pc.createDataChannel(request.channelLabel ?? \"hyperttp-webrtc-transport\", {\n ordered: true,\n }),\n remoteStream: (this.options.createMediaStream ?? (() => new MediaStream()))(),\n controller: new AbortController(),\n pending: new Map(),\n cleanup: [],\n signaled: false,\n };\n } catch (error) {\n pc.close();\n throw error;\n }\n this.sessions.set(peerUrl, session);\n const onTrack = (event: RTCTrackEvent) => {\n if (!session.remoteStream.getTracks().includes(event.track))\n session.remoteStream.addTrack(event.track);\n const trackEvent: WebRtcTrackEvent = {\n side: \"client\",\n sessionId: session.id,\n peerUrl,\n peerConnection: pc,\n track: event.track,\n stream: session.remoteStream,\n streams: event.streams,\n };\n for (const listener of this.trackListeners) {\n try {\n listener(trackEvent);\n } catch {\n /* Consumers must not interrupt transport events. */\n }\n }\n };\n const onState = () => {\n if (pc.connectionState === \"failed\" || pc.connectionState === \"closed\")\n void this.dispose(session);\n };\n const onClose = () => {\n void this.dispose(session);\n };\n const onMessage = (event: MessageEvent) => this.receive(session, event);\n pc.addEventListener(\"track\", onTrack);\n pc.addEventListener(\"connectionstatechange\", onState);\n session.dataChannel.addEventListener(\"message\", onMessage);\n session.dataChannel.addEventListener(\"close\", onClose);\n session.dataChannel.addEventListener(\"error\", onClose);\n session.cleanup.push(() => {\n pc.removeEventListener(\"track\", onTrack);\n pc.removeEventListener(\"connectionstatechange\", onState);\n session.dataChannel.removeEventListener(\"message\", onMessage);\n session.dataChannel.removeEventListener(\"close\", onClose);\n session.dataChannel.removeEventListener(\"error\", onClose);\n });\n return session;\n }\n\n private configureMedia(session: Session, request: WebRtcTransportRequest): boolean {\n const pc = session.peerConnection;\n let changed = false;\n if (request.localStream) {\n for (const track of request.localStream.getTracks()) {\n if (\n track.readyState === \"live\" &&\n !pc.getSenders().some((sender) => sender.track === track)\n ) {\n pc.addTrack(track, request.localStream);\n changed = true;\n }\n }\n session.localStream = request.localStream;\n }\n for (const kind of [\"audio\", \"video\"] as const) {\n if (!(kind === \"audio\" ? request.receiveAudio : request.receiveVideo)) continue;\n const transceiver = pc.getTransceivers().find((item) => item.receiver.track.kind === kind);\n if (!transceiver) {\n pc.addTransceiver(kind, { direction: \"recvonly\" });\n changed = true;\n } else if (transceiver.direction === \"sendonly\" || transceiver.direction === \"inactive\") {\n transceiver.direction = transceiver.direction === \"sendonly\" ? \"sendrecv\" : \"recvonly\";\n changed = true;\n }\n }\n return changed;\n }\n\n private async negotiate(session: Session, signal?: AbortSignal): Promise<void> {\n if (session.exchange) await session.exchange;\n if (this.options.trickleIce) return this.negotiateTrickle(session, signal);\n const pc = session.peerConnection;\n await this.run(\n session,\n signal,\n this.options.signalingTimeoutMs ?? 10_000,\n \"SDP offer\",\n async (active) => {\n const offer = await pc.createOffer();\n if (active.aborted) throw abortError(active);\n await pc.setLocalDescription(offer);\n },\n );\n await this.run(\n session,\n signal,\n this.options.iceGatheringTimeoutMs ?? 10_000,\n \"ICE gathering\",\n (active) =>\n this.waitFor(\n pc,\n [\"icegatheringstatechange\"],\n active,\n () => pc.iceGatheringState === \"complete\",\n ),\n );\n await this.run(\n session,\n signal,\n this.options.signalingTimeoutMs ?? 10_000,\n \"Signaling\",\n async (active) => {\n if (!pc.localDescription?.sdp) throw new Error(\"Local SDP offer was not generated\");\n session.signaled = true;\n const response = await fetch(session.signalingUrl, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ sessionId: session.id, sdp: pc.localDescription }),\n signal: active,\n });\n if (!response.ok)\n throw new Error(`Signaling HTTP error: ${response.status} ${response.statusText}`);\n const answer: unknown = await response.json();\n if (\n !answer ||\n typeof answer !== \"object\" ||\n !(\"type\" in answer) ||\n answer.type !== \"answer\" ||\n !(\"sdp\" in answer) ||\n typeof answer.sdp !== \"string\"\n ) {\n throw new Error(\"Signaling server returned an invalid SDP answer\");\n }\n if (active.aborted) throw abortError(active);\n await pc.setRemoteDescription({ type: \"answer\", sdp: answer.sdp });\n },\n );\n }\n\n private async negotiateTrickle(session: Session, signal?: AbortSignal): Promise<void> {\n const pc = session.peerConnection;\n let ready!: () => void;\n let fail!: (error: unknown) => void;\n const established = new Promise<void>((resolve, reject) => {\n ready = resolve;\n fail = reject;\n });\n const exchange = this.run(\n session,\n undefined,\n this.options.iceGatheringTimeoutMs ?? 10_000,\n \"Trickle ICE\",\n async (active) => {\n const candidates: (RTCIceCandidateInit | null)[] = [];\n let wake: (() => void) | undefined;\n let ended = false;\n let candidateError: Error | undefined;\n const onCandidate = (event: RTCPeerConnectionIceEvent) => {\n if (ended || active.aborted) return;\n const candidate = event.candidate ? event.candidate.toJSON() : null;\n if (candidate && candidate.candidate === \"\") return;\n if (candidates.length >= 256 || !this.validCandidate(candidate)) {\n candidateError = 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 try {\n const offer = await pc.createOffer();\n if (active.aborted) throw abortError(active);\n await pc.setLocalDescription(offer);\n if (active.aborted) throw abortError(active);\n if (pc.iceGatheringState === \"complete\" && !ended && !candidateError) {\n if (candidates.length >= 256)\n candidateError = new Error(\"Invalid or excessive local ICE candidates\");\n else {\n candidates.push(null);\n ended = true;\n }\n }\n if (!pc.localDescription?.sdp) throw new Error(\"Local SDP offer was not generated\");\n session.signaled = true;\n const response = await this.run(\n session,\n signal,\n this.options.signalingTimeoutMs ?? 10_000,\n \"Signaling\",\n async (signaling) => {\n const response = await fetch(session.signalingUrl, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n sessionId: session.id,\n sdp: pc.localDescription,\n trickle: true,\n }),\n signal: signaling,\n });\n if (!response.ok)\n throw new Error(`Signaling HTTP error: ${response.status} ${response.statusText}`);\n return response.json() as Promise<unknown>;\n },\n );\n const result: unknown = response;\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 if (active.aborted) throw abortError(active);\n await pc.setRemoteDescription({ type: \"answer\", sdp: result.sdp.sdp });\n if (active.aborted) throw abortError(active);\n ready();\n const upload = async () => {\n let index = 0;\n while (true) {\n if (active.aborted) throw abortError(active);\n if (candidateError) throw candidateError;\n if (index === candidates.length) {\n await new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n wake = undefined;\n reject(abortError(active));\n };\n wake = () => {\n active.removeEventListener(\"abort\", onAbort);\n wake = undefined;\n resolve();\n };\n active.addEventListener(\"abort\", onAbort, { once: true });\n if (active.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: active,\n });\n if (patch.status !== 204)\n 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 if (active.aborted) throw abortError(active);\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 response = await fetch(url, { signal: active });\n if (!response.ok) throw new Error(`ICE polling HTTP error: ${response.status}`);\n const page: unknown = await response.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.validCandidate(candidate)) ||\n page.candidates.slice(0, -1).includes(null)\n ) {\n throw new Error(\"Signaling server returned an invalid ICE candidate page\");\n }\n for (const candidate of page.candidates as (RTCIceCandidateInit | null)[]) {\n if (active.aborted) throw abortError(active);\n await pc.addIceCandidate(candidate);\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 active.removeEventListener(\"abort\", onAbort);\n resolve();\n }, 50);\n const onAbort = () => {\n clearTimeout(timer);\n reject(abortError(active));\n };\n active.addEventListener(\"abort\", onAbort, { once: true });\n if (active.aborted) onAbort();\n });\n }\n };\n await Promise.all([upload(), download()]);\n } finally {\n pc.removeEventListener(\"icecandidate\", onCandidate);\n }\n },\n );\n session.exchange = exchange;\n void exchange.then(\n () => {\n if (session.exchange === exchange) delete session.exchange;\n },\n (error: unknown) => {\n fail(error);\n if (session.exchange === exchange) delete session.exchange;\n void this.dispose(session);\n },\n );\n await this.abortable(established, signal);\n }\n\n private validCandidate(value: unknown): value is RTCIceCandidateInit | null {\n if (value === null) return true;\n if (!value || typeof value !== \"object\" || Array.isArray(value)) 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 receive(session: Session, event: MessageEvent): void {\n if (typeof event.data !== \"string\") return;\n let message: Record<string, unknown>;\n try {\n const parsed: unknown = JSON.parse(event.data);\n if (!parsed || typeof parsed !== \"object\") return;\n message = parsed as Record<string, unknown>;\n } catch {\n return;\n }\n if (typeof message.requestId !== \"string\") return;\n const finish = session.pending.get(message.requestId);\n if (!finish) return;\n try {\n finish(undefined, {\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: decodeWireBody(message.body),\n url: session.peerUrl,\n });\n } catch (error) {\n finish(error);\n }\n }\n\n private sendRequest(\n session: Session,\n request: WebRtcTransportRequest,\n ): Promise<TransportResponse> {\n const timeout =\n request.timeout && request.timeout > 0\n ? request.timeout\n : (this.options.requestTimeoutMs ?? 15_000);\n return this.run(\n session,\n request.signal,\n timeout,\n \"WebRTC request\",\n (signal) =>\n new Promise<TransportResponse>((resolve, reject) => {\n if (session.dataChannel.readyState !== \"open\") {\n reject(new Error(\"WebRTC DataChannel is not open\"));\n return;\n }\n const requestId = `request-${++this.sequence}`;\n const finish = (error?: unknown, response?: TransportResponse) => {\n session.pending.delete(requestId);\n signal.removeEventListener(\"abort\", onAbort);\n if (response) resolve(response);\n else reject(error ?? new Error(\"WebRTC session closed\"));\n };\n const onAbort = () => finish(abortError(signal));\n session.pending.set(requestId, finish);\n signal.addEventListener(\"abort\", onAbort, { once: true });\n if (signal.aborted) {\n onAbort();\n return;\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 finish(error);\n }\n }),\n );\n }\n\n private dispose(session: Session): Promise<void> {\n if (session.disposal) return session.disposal;\n const disposal = Promise.resolve().then(async () => {\n session.controller.abort(new Error(\"WebRTC session closed\"));\n if (this.sessions.get(session.peerUrl) === session) this.sessions.delete(session.peerUrl);\n for (const cleanup of session.cleanup) cleanup();\n session.cleanup.length = 0;\n for (const finish of session.pending.values()) finish(new Error(\"WebRTC session closed\"));\n try {\n session.dataChannel.close();\n } catch {}\n try {\n session.peerConnection.close();\n } catch {}\n if (!session.signaled) return;\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 this.abortable(\n fetch(url, { method: \"DELETE\", signal: controller.signal }),\n controller.signal,\n );\n } catch {\n /* Remote cleanup is best effort. */\n } finally {\n clearTimeout(timer);\n }\n });\n session.disposal = disposal;\n this.disposals.add(disposal);\n void disposal.then(() => this.disposals.delete(disposal));\n return disposal;\n }\n\n private async run<T>(\n session: Session,\n signal: AbortSignal | undefined,\n timeout: number,\n label: string,\n task: (signal: AbortSignal) => Promise<T>,\n ): Promise<T> {\n const controller = new AbortController();\n const sources = [session.controller.signal, ...(signal ? [signal] : [])];\n const onAbort = () => {\n const source = sources.find((item) => item.aborted);\n if (source) controller.abort(abortError(source));\n };\n for (const source of sources) source.addEventListener(\"abort\", onAbort, { once: true });\n const timer = setTimeout(\n () => controller.abort(new Error(`${label} timed out after ${timeout} ms`)),\n timeout,\n );\n try {\n onAbort();\n if (controller.signal.aborted) throw abortError(controller.signal);\n return await this.abortable(task(controller.signal), controller.signal);\n } finally {\n controller.abort();\n clearTimeout(timer);\n for (const source of sources) source.removeEventListener(\"abort\", onAbort);\n }\n }\n\n private abortable<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {\n if (!signal) return promise;\n return new Promise<T>((resolve, reject) => {\n const cleanup = () => signal.removeEventListener(\"abort\", onAbort);\n const onAbort = () => {\n cleanup();\n reject(abortError(signal));\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n if (signal.aborted) onAbort();\n void promise.then(\n (value) => {\n cleanup();\n resolve(value);\n },\n (error: unknown) => {\n cleanup();\n reject(error);\n },\n );\n });\n }\n\n private waitFor(\n target: EventTarget,\n events: string[],\n signal: AbortSignal,\n ready: () => boolean,\n failed: () => boolean = () => false,\n ): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const finish = (error?: Error) => {\n for (const event of events) target.removeEventListener(event, check);\n signal.removeEventListener(\"abort\", onAbort);\n if (error) reject(error);\n else resolve();\n };\n const onAbort = () => finish(abortError(signal));\n const check = (event?: Event) => {\n if (signal.aborted) onAbort();\n else if (failed() || event?.type === \"error\") finish(new Error(\"WebRTC connection failed\"));\n else if (ready()) finish();\n };\n for (const event of events) target.addEventListener(event, check);\n signal.addEventListener(\"abort\", onAbort, { once: true });\n check();\n });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["abortError", "decodeWireBody", "encodeWireBody", "options", "listener", "peerUrl", "session", "request", "operation", "changed", "signal", "error", "remove", "connections", "sessions", "url", "pc", "config", "onTrack", "event", "trackEvent", "onState", "onClose", "onMessage", "track", "sender", "kind", "transceiver", "item", "active", "offer", "response", "answer", "ready", "fail", "established", "resolve", "reject", "exchange", "candidates", "wake", "ended", "candidateError", "onCandidate", "candidate", "result", "signaling", "generation", "upload", "index", "onAbort", "patch", "download", "cursor", "page", "timer", "value", "message", "parsed", "finish", "timeout", "requestId", "disposal", "cleanup", "controller", "label", "task", "sources", "source", "promise", "target", "events", "failed", "check"]
|
|
7
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { WebRtcTransport } from "./webrtc.js";
|
|
2
|
+
export type { WebRtcTransportOptions, WebRtcTransportServer, WebRtcServerSessionView, } from "./webrtc.js";
|
|
3
|
+
export type { WebRtcTrickleCandidate, WebRtcTrickleAnswer, WebRtcTrickleCandidates, WebRtcSessionView, WebRtcServerSessionContext, WebRtcServerSessionEvent, WebRtcTrackEvent, WebRtcMediaController, WebRtcTransportRequest, } from "./types/types.js";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EACV,sBAAsB,EACtB,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,iBAAiB,EACjB,0BAA0B,EAC1B,wBAAwB,EACxB,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EACV,sBAAsB,EACtB,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,sBAAsB,EACtB,mBAAmB,EACnB,uBAAuB,EACvB,iBAAiB,EACjB,0BAA0B,EAC1B,wBAAwB,EACxB,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,kBAAkB,CAAC"}
|
package/index.js
ADDED
package/index.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts"],
|
|
4
|
+
"sourcesContent": ["export { WebRtcTransport } from \"./webrtc.js\";\nexport type {\n WebRtcTransportOptions,\n WebRtcTransportServer,\n WebRtcServerSessionView,\n} from \"./webrtc.js\";\nexport type {\n WebRtcTrickleCandidate,\n WebRtcTrickleAnswer,\n WebRtcTrickleCandidates,\n WebRtcSessionView,\n WebRtcServerSessionContext,\n WebRtcServerSessionEvent,\n WebRtcTrackEvent,\n WebRtcMediaController,\n WebRtcTransportRequest,\n} from \"./types/types.js\";\n"],
|
|
5
|
+
"mappings": "aAAA,OAAS,oBAAuB",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperttp/transport-webrtc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "WebRTC transport for Hyperttp powered by Werift",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -16,55 +16,33 @@
|
|
|
16
16
|
"type": "git",
|
|
17
17
|
"url": "git+https://github.com/IT-IF-OR/hyperttp-transport-webrtc.git"
|
|
18
18
|
},
|
|
19
|
-
"files": [
|
|
20
|
-
"dist"
|
|
21
|
-
],
|
|
22
19
|
"type": "module",
|
|
23
20
|
"sideEffects": false,
|
|
24
|
-
"main": "./
|
|
25
|
-
"types": "./
|
|
21
|
+
"main": "./index.js",
|
|
22
|
+
"types": "./index.d.ts",
|
|
26
23
|
"exports": {
|
|
27
24
|
".": {
|
|
28
25
|
"browser": {
|
|
29
|
-
"types": "./
|
|
30
|
-
"import": "./
|
|
26
|
+
"types": "./browser.d.ts",
|
|
27
|
+
"import": "./browser.js"
|
|
31
28
|
},
|
|
32
|
-
"types": "./
|
|
33
|
-
"import": "./
|
|
29
|
+
"types": "./index.d.ts",
|
|
30
|
+
"import": "./index.js"
|
|
34
31
|
},
|
|
35
32
|
"./pcm-audio": {
|
|
36
|
-
"types": "./
|
|
37
|
-
"import": "./
|
|
33
|
+
"types": "./pcm-audio.d.ts",
|
|
34
|
+
"import": "./pcm-audio.js"
|
|
38
35
|
},
|
|
39
36
|
"./browser": {
|
|
40
|
-
"types": "./
|
|
41
|
-
"import": "./
|
|
37
|
+
"types": "./browser.d.ts",
|
|
38
|
+
"import": "./browser.js"
|
|
42
39
|
}
|
|
43
40
|
},
|
|
44
|
-
"scripts": {
|
|
45
|
-
"typecheck": "tsc --noEmit",
|
|
46
|
-
"lint": "oxlint src",
|
|
47
|
-
"lint:fix": "oxlint src --fix",
|
|
48
|
-
"format": "oxfmt src",
|
|
49
|
-
"format:check": "oxfmt src --check",
|
|
50
|
-
"test": "vitest run",
|
|
51
|
-
"dev": "tsx src/index.ts",
|
|
52
|
-
"build": "node ../../scripts/build-package.ts",
|
|
53
|
-
"clean": "node --input-type=module -e 'import { rmSync } from \"node:fs\"; rmSync(\"dist\", { recursive: true, force: true });'"
|
|
54
|
-
},
|
|
55
41
|
"dependencies": {
|
|
56
42
|
"werift": "^0.26.0"
|
|
57
43
|
},
|
|
58
|
-
"devDependencies": {
|
|
59
|
-
"@types/bun": "^1.4.4",
|
|
60
|
-
"@types/node": "^26.6.6",
|
|
61
|
-
"esbuild": "^0.28.2",
|
|
62
|
-
"oxfmt": "^0.76.0",
|
|
63
|
-
"oxlint": "^1.90.0",
|
|
64
|
-
"typescript": "^7.0.2",
|
|
65
|
-
"vitest": "^5.1.0"
|
|
66
|
-
},
|
|
67
44
|
"peerDependencies": {
|
|
68
45
|
"@hyperttp/types": "^0.5.4"
|
|
69
|
-
}
|
|
70
|
-
|
|
46
|
+
},
|
|
47
|
+
"private": false
|
|
48
|
+
}
|
|
@@ -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.
|
|
@@ -0,0 +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;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
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
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
|
+
//# sourceMappingURL=pcm-audio.js.map
|
package/pcm-audio.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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\";\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
|
+
}
|
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
|
package/rtp-pacer.js.map
ADDED
|
@@ -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
|
+
}
|
|
@@ -65,6 +65,8 @@ export interface WebRtcServerSessionEvent extends WebRtcServerSessionContext {
|
|
|
65
65
|
}
|
|
66
66
|
export interface WebRtcTransportOptions {
|
|
67
67
|
readonly iceServers?: RTCIceServer[];
|
|
68
|
+
/** Enable incremental ICE signaling with a compatible server. Default: false. */
|
|
69
|
+
readonly trickleIce?: boolean;
|
|
68
70
|
readonly iceGatheringTimeoutMs?: number;
|
|
69
71
|
readonly signalingTimeoutMs?: number;
|
|
70
72
|
readonly connectionTimeoutMs?: number;
|
|
@@ -94,10 +96,31 @@ export interface ClientSession {
|
|
|
94
96
|
localTrackIds: Set<string>;
|
|
95
97
|
pending: Map<string, PendingResponse>;
|
|
96
98
|
negotiation: Promise<void>;
|
|
99
|
+
trickleAbort?: AbortController;
|
|
97
100
|
closed: boolean;
|
|
98
101
|
mediaController: WebRtcMediaController;
|
|
99
102
|
}
|
|
103
|
+
export type WebRtcTrickleCandidate = RTCIceCandidateInit | null;
|
|
104
|
+
export interface WebRtcTrickleAnswer {
|
|
105
|
+
sdp: RTCSessionDescriptionInit;
|
|
106
|
+
generation: string;
|
|
107
|
+
}
|
|
108
|
+
export interface WebRtcTrickleCandidates {
|
|
109
|
+
generation: string;
|
|
110
|
+
candidates: WebRtcTrickleCandidate[];
|
|
111
|
+
next: number;
|
|
112
|
+
}
|
|
113
|
+
export interface ServerTrickleState {
|
|
114
|
+
generation: string;
|
|
115
|
+
candidates: WebRtcTrickleCandidate[];
|
|
116
|
+
gathering: boolean;
|
|
117
|
+
remoteCount: number;
|
|
118
|
+
remoteEnded: boolean;
|
|
119
|
+
remoteQueue: Promise<void>;
|
|
120
|
+
detach: () => void;
|
|
121
|
+
}
|
|
100
122
|
export interface ServerSession {
|
|
123
|
+
trickle?: ServerTrickleState;
|
|
101
124
|
negotiating?: boolean;
|
|
102
125
|
context: WebRtcServerSessionContext;
|
|
103
126
|
controller: AbortController;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types/types.ts"],"names":[],"mappings":"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"}
|
package/types/types.js
ADDED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/types/types.ts"],
|
|
4
|
-
"sourcesContent": ["import type { LogLevel, TransportRequest, TransportResponse } from \"@hyperttp/types\";\nimport type {\n MediaStream as WeriftMediaStream,\n RTCPeerConnection as WeriftPeerConnection,\n RTCDataChannel as WeriftDataChannel,\n MediaStreamTrack as WeriftMediaStreamTrack,\n} from \"werift\";\n\nexport const WEBRTC_BYTES_MARKER = \"__hyperttp_webrtc_bytes_v1__\";\nexport const MAX_SIGNALING_BODY_BYTES = 1_000_000;\n\nexport type EncodedWebRtcBytes = {\n [WEBRTC_BYTES_MARKER]: true;\n base64: string;\n};\n\nexport type WebRtcAction = \"connect\" | \"stream\" | \"datachannel\";\n\nexport type WebRtcTransportRequest = TransportRequest & {\n peerUrl?: string;\n action?: WebRtcAction;\n iceServers?: RTCIceServer[];\n localStream?: WeriftMediaStream;\n receiveAudio?: boolean;\n receiveVideo?: boolean;\n channelLabel?: string;\n timeout?: number;\n};\n\nexport interface WebRtcTrackEvent {\n readonly side: \"client\" | \"server\";\n readonly sessionId: string;\n readonly peerUrl?: string;\n readonly peerConnection: WeriftPeerConnection;\n readonly track: WeriftMediaStreamTrack;\n readonly stream: WeriftMediaStream;\n readonly streams: readonly WeriftMediaStream[];\n}\n\nexport interface WebRtcMediaController {\n readonly localStream: WeriftMediaStream | undefined;\n readonly remoteStream: WeriftMediaStream;\n\n addTrack(track: WeriftMediaStreamTrack, stream?: WeriftMediaStream): Promise<void>;\n replaceTrack(kind: \"audio\" | \"video\", track: WeriftMediaStreamTrack | null): Promise<void>;\n removeTrack(track: WeriftMediaStreamTrack): Promise<void>;\n close(): Promise<void>;\n\n on(\n event: \"track\",\n listener: (e: { track: WeriftMediaStreamTrack; stream: WeriftMediaStream }) => void,\n ): () => void;\n on(event: \"trackEnded\", listener: (e: { track: WeriftMediaStreamTrack }) => void): () => void;\n on(event: \"stream\", listener: (e: { stream: WeriftMediaStream }) => void): () => void;\n}\n\nexport interface WebRtcSessionView {\n readonly id: string;\n readonly peerUrl: string;\n readonly peerConnection: WeriftPeerConnection;\n readonly dataChannel: WeriftDataChannel;\n readonly remoteStream: WeriftMediaStream;\n readonly localStream?: WeriftMediaStream;\n readonly media: WebRtcMediaController;\n}\n\nexport interface WebRtcServerSessionContext {\n readonly sessionId: string;\n /** Undefined only for explicitly anonymous/legacy boolean authorization. */\n readonly ownerId: string | undefined;\n readonly authorization: unknown;\n readonly signal: AbortSignal;\n}\nexport interface WebRtcServerSessionEvent extends WebRtcServerSessionContext {\n readonly type: \"created\" | \"closed\";\n}\n\nexport interface WebRtcTransportOptions {\n readonly iceServers?: RTCIceServer[];\n readonly iceGatheringTimeoutMs?: number;\n readonly signalingTimeoutMs?: number;\n readonly connectionTimeoutMs?: number;\n readonly requestTimeoutMs?: number;\n readonly logger?: (level: LogLevel, message: string, meta?: unknown) => void;\n readonly verbose?: boolean;\n readonly createPeerConnection?: (configuration: RTCConfiguration) => WeriftPeerConnection;\n readonly createMediaStream?: () => WeriftMediaStream;\n readonly disposeLocalStream?: (sessionId: string, stream: WeriftMediaStream) => void;\n readonly localStream?:\n | WeriftMediaStream\n | ((\n sessionId: string,\n context: WebRtcServerSessionContext,\n ) => WeriftMediaStream | undefined | Promise<WeriftMediaStream | undefined>);\n}\n\nexport interface PendingResponse {\n resolve: (response: TransportResponse) => void;\n reject: (error: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n signal?: AbortSignal;\n abortListener?: () => void;\n}\n\nexport interface ClientSession {\n id: string;\n peerUrl: string;\n signalingUrl: string;\n peerConnection: WeriftPeerConnection;\n dataChannel: WeriftDataChannel;\n remoteStream: WeriftMediaStream;\n localStream?: WeriftMediaStream;\n localTrackIds: Set<string>;\n pending: Map<string, PendingResponse>;\n negotiation: Promise<void>;\n closed: boolean;\n mediaController: WebRtcMediaController;\n}\n\nexport interface ServerSession {\n negotiating?: boolean;\n context: WebRtcServerSessionContext;\n controller: AbortController;\n closing?: Promise<void>;\n release: () => void;\n id: string;\n peerConnection: WeriftPeerConnection;\n remoteStream: WeriftMediaStream;\n localTrackIds: Set<string>;\n localStream?: WeriftMediaStream;\n localStreamResolved: boolean;\n ownsLocalStream: boolean;\n closed: boolean;\n}\n\nexport interface RTCIceServer {\n credential?: string;\n urls: string | string[];\n username?: string;\n}\n"],
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": [
|
|
4
|
+
"sourcesContent": ["import type { LogLevel, TransportRequest, TransportResponse } from \"@hyperttp/types\";\nimport type {\n MediaStream as WeriftMediaStream,\n RTCPeerConnection as WeriftPeerConnection,\n RTCDataChannel as WeriftDataChannel,\n MediaStreamTrack as WeriftMediaStreamTrack,\n} from \"werift\";\n\nexport const WEBRTC_BYTES_MARKER = \"__hyperttp_webrtc_bytes_v1__\";\nexport const MAX_SIGNALING_BODY_BYTES = 1_000_000;\n\nexport type EncodedWebRtcBytes = {\n [WEBRTC_BYTES_MARKER]: true;\n base64: string;\n};\n\nexport type WebRtcAction = \"connect\" | \"stream\" | \"datachannel\";\n\nexport type WebRtcTransportRequest = TransportRequest & {\n peerUrl?: string;\n action?: WebRtcAction;\n iceServers?: RTCIceServer[];\n localStream?: WeriftMediaStream;\n receiveAudio?: boolean;\n receiveVideo?: boolean;\n channelLabel?: string;\n timeout?: number;\n};\n\nexport interface WebRtcTrackEvent {\n readonly side: \"client\" | \"server\";\n readonly sessionId: string;\n readonly peerUrl?: string;\n readonly peerConnection: WeriftPeerConnection;\n readonly track: WeriftMediaStreamTrack;\n readonly stream: WeriftMediaStream;\n readonly streams: readonly WeriftMediaStream[];\n}\n\nexport interface WebRtcMediaController {\n readonly localStream: WeriftMediaStream | undefined;\n readonly remoteStream: WeriftMediaStream;\n\n addTrack(track: WeriftMediaStreamTrack, stream?: WeriftMediaStream): Promise<void>;\n replaceTrack(kind: \"audio\" | \"video\", track: WeriftMediaStreamTrack | null): Promise<void>;\n removeTrack(track: WeriftMediaStreamTrack): Promise<void>;\n close(): Promise<void>;\n\n on(\n event: \"track\",\n listener: (e: { track: WeriftMediaStreamTrack; stream: WeriftMediaStream }) => void,\n ): () => void;\n on(event: \"trackEnded\", listener: (e: { track: WeriftMediaStreamTrack }) => void): () => void;\n on(event: \"stream\", listener: (e: { stream: WeriftMediaStream }) => void): () => void;\n}\n\nexport interface WebRtcSessionView {\n readonly id: string;\n readonly peerUrl: string;\n readonly peerConnection: WeriftPeerConnection;\n readonly dataChannel: WeriftDataChannel;\n readonly remoteStream: WeriftMediaStream;\n readonly localStream?: WeriftMediaStream;\n readonly media: WebRtcMediaController;\n}\n\nexport interface WebRtcServerSessionContext {\n readonly sessionId: string;\n /** Undefined only for explicitly anonymous/legacy boolean authorization. */\n readonly ownerId: string | undefined;\n readonly authorization: unknown;\n readonly signal: AbortSignal;\n}\nexport interface WebRtcServerSessionEvent extends WebRtcServerSessionContext {\n readonly type: \"created\" | \"closed\";\n}\n\nexport interface WebRtcTransportOptions {\n readonly iceServers?: RTCIceServer[];\n /** Enable incremental ICE signaling with a compatible server. Default: false. */\n readonly trickleIce?: boolean;\n readonly iceGatheringTimeoutMs?: number;\n readonly signalingTimeoutMs?: number;\n readonly connectionTimeoutMs?: number;\n readonly requestTimeoutMs?: number;\n readonly logger?: (level: LogLevel, message: string, meta?: unknown) => void;\n readonly verbose?: boolean;\n readonly createPeerConnection?: (configuration: RTCConfiguration) => WeriftPeerConnection;\n readonly createMediaStream?: () => WeriftMediaStream;\n readonly disposeLocalStream?: (sessionId: string, stream: WeriftMediaStream) => void;\n readonly localStream?:\n | WeriftMediaStream\n | ((\n sessionId: string,\n context: WebRtcServerSessionContext,\n ) => WeriftMediaStream | undefined | Promise<WeriftMediaStream | undefined>);\n}\n\nexport interface PendingResponse {\n resolve: (response: TransportResponse) => void;\n reject: (error: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n signal?: AbortSignal;\n abortListener?: () => void;\n}\n\nexport interface ClientSession {\n id: string;\n peerUrl: string;\n signalingUrl: string;\n peerConnection: WeriftPeerConnection;\n dataChannel: WeriftDataChannel;\n remoteStream: WeriftMediaStream;\n localStream?: WeriftMediaStream;\n localTrackIds: Set<string>;\n pending: Map<string, PendingResponse>;\n negotiation: Promise<void>;\n trickleAbort?: AbortController;\n closed: boolean;\n mediaController: WebRtcMediaController;\n}\n\nexport type WebRtcTrickleCandidate = RTCIceCandidateInit | null;\nexport interface WebRtcTrickleAnswer {\n sdp: RTCSessionDescriptionInit;\n generation: string;\n}\nexport interface WebRtcTrickleCandidates {\n generation: string;\n candidates: WebRtcTrickleCandidate[];\n next: number;\n}\nexport interface ServerTrickleState {\n generation: string;\n candidates: WebRtcTrickleCandidate[];\n gathering: boolean;\n remoteCount: number;\n remoteEnded: boolean;\n remoteQueue: Promise<void>;\n detach: () => void;\n}\nexport interface ServerSession {\n trickle?: ServerTrickleState;\n negotiating?: boolean;\n context: WebRtcServerSessionContext;\n controller: AbortController;\n closing?: Promise<void>;\n release: () => void;\n id: string;\n peerConnection: WeriftPeerConnection;\n remoteStream: WeriftMediaStream;\n localTrackIds: Set<string>;\n localStream?: WeriftMediaStream;\n localStreamResolved: boolean;\n ownsLocalStream: boolean;\n closed: boolean;\n}\n\nexport interface RTCIceServer {\n credential?: string;\n urls: string | string[];\n username?: string;\n}\n"],
|
|
5
|
+
"mappings": "aAQO,aAAM,oBAAsB,+BACtB,yBAA2B",
|
|
6
|
+
"names": []
|
|
7
7
|
}
|
package/utils/helper.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";import{WEBRTC_BYTES_MARKER as o}from"../types/types.js";export function bytesToBase64(e){let r="";for(let n=0;n<e.length;n+=32768)r+=String.fromCharCode(...e.subarray(n,n+32768));return btoa(r)}export function base64ToBytes(e){const r=atob(e),t=new Uint8Array(r.length);for(let n=0;n<r.length;n++)t[n]=r.charCodeAt(n);return t}export function encodeWireBody(e){let r;return e instanceof Uint8Array?r=e:e instanceof ArrayBuffer?r=new Uint8Array(e):ArrayBuffer.isView(e)&&(r=new Uint8Array(e.buffer,e.byteOffset,e.byteLength)),r?{[o]:!0,base64:bytesToBase64(r)}:e}export function decodeWireBody(e){if(typeof e!="object"||e===null||!(o in e))return e;const r=e;return r[o]!==!0||typeof r.base64!="string"?e:base64ToBytes(r.base64)}export function errorFromUnknown(e,r){return e instanceof Error?e:new Error(r)}export function abortError(e){return e?.reason instanceof Error?e.reason:new Error("WebRTC operation aborted")}export function countIceCandidates(e){return e?(e.match(/^a=candidate:/gm)??[]).length:0}
|
|
2
|
+
//# sourceMappingURL=helper.js.map
|