@torrent-tv/proxy 2.2.0 → 2.4.0

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.
@@ -0,0 +1,166 @@
1
+ /**
2
+ * @file WebRTC connection manager for the proxy (Node.js side).
3
+ *
4
+ * Maintains one `PeerConnection` per browser session, keyed by `sessionId`.
5
+ * Receives SDP offers and ICE candidates through the signalling tunnel
6
+ * and responds with SDP answers and its own candidates through the same channel.
7
+ *
8
+ * Data channels for actual streaming are handed off to `data-channel-handler.js`
9
+ * via the `onDataChannel` callback — this module only owns the signalling phase.
10
+ */
11
+
12
+ import nodeDataChannel from "node-datachannel";
13
+
14
+ /** @import { WebRtcSignal } from './tunnel-client.js' */
15
+
16
+ const ICE_SERVERS = ["stun:stun.l.google.com:19302"];
17
+
18
+ /**
19
+ * Configuration for the WebRTC manager.
20
+ *
21
+ * @typedef {Object} WebRtcManagerOptions
22
+ * @property {(sessionId: string, signal: WebRtcSignal) => void} sendSignal
23
+ * Send a WebRTC signal (answer or ICE candidate) back to the browser via
24
+ * the tunnel. Typically wired to `tunnelClient.sendSignal`.
25
+ * @property {(sessionId: string, channel: import("node-datachannel").DataChannel) => void} onDataChannel
26
+ * Called when the browser opens a data channel. Receives the raw
27
+ * `node-datachannel` `DataChannel` object; hand it to `createDataChannelHandler`.
28
+ * @property {(message: string) => void} [onLog]
29
+ * Optional log sink.
30
+ */
31
+
32
+ /**
33
+ * The object returned by {@link createWebRtcManager}.
34
+ *
35
+ * @typedef {Object} WebRtcManager
36
+ * @property {(sessionId: string, signal: WebRtcSignal) => void} handleSignal
37
+ * Dispatch an incoming signal (offer / ICE candidate) from the browser to
38
+ * the matching peer connection, creating it if necessary.
39
+ * @property {(sessionId: string) => void} closeSession
40
+ * Tear down and remove a peer connection by session ID.
41
+ */
42
+
43
+ /**
44
+ * Create the proxy-side WebRTC connection manager.
45
+ *
46
+ * @param {WebRtcManagerOptions} options
47
+ * @returns {WebRtcManager}
48
+ */
49
+ export function createWebRtcManager({ sendSignal, onDataChannel, onLog }) {
50
+ /** @type {Map<string, import("node-datachannel").PeerConnection>} */
51
+ const peers = new Map();
52
+
53
+ /**
54
+ * @param {string} message
55
+ * @returns {void}
56
+ */
57
+ function log(message) {
58
+ if (typeof onLog === "function") {
59
+ onLog(message);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Retrieve an existing peer connection or create a new one for the session.
65
+ *
66
+ * A new `PeerConnection` is configured with the shared ICE server list and
67
+ * wired up to forward local descriptions and candidates back to the browser.
68
+ *
69
+ * @param {string} sessionId
70
+ * @returns {import("node-datachannel").PeerConnection}
71
+ */
72
+ function getOrCreatePeer(sessionId) {
73
+ const existing = peers.get(sessionId);
74
+ if (existing) {
75
+ return existing;
76
+ }
77
+
78
+ const pc = new nodeDataChannel.PeerConnection(`proxy-${sessionId.slice(0, 8)}`, {
79
+ iceServers: ICE_SERVERS
80
+ });
81
+
82
+ // Forward our ICE candidates to the browser through the tunnel.
83
+ pc.onLocalCandidate((candidate, mid) => {
84
+ sendSignal(sessionId, { type: "candidate", candidate, mid });
85
+ });
86
+
87
+ // Forward our SDP answer to the browser through the tunnel.
88
+ pc.onLocalDescription((sdp, type) => {
89
+ log(`[webrtc] Session ${sessionId.slice(0, 8)}: sending ${type}`);
90
+ sendSignal(sessionId, { type, sdp });
91
+ });
92
+
93
+ pc.onStateChange((state) => {
94
+ log(`[webrtc] Session ${sessionId.slice(0, 8)}: state → ${state}`);
95
+ if (state === "disconnected" || state === "failed" || state === "closed") {
96
+ closeSession(sessionId);
97
+ }
98
+ });
99
+
100
+ // Browser creates the data channel — we receive it here.
101
+ pc.onDataChannel((channel) => {
102
+ log(`[webrtc] Session ${sessionId.slice(0, 8)}: data channel "${channel.getLabel()}" opened`);
103
+ onDataChannel(sessionId, channel);
104
+ });
105
+
106
+ peers.set(sessionId, pc);
107
+ return pc;
108
+ }
109
+
110
+ /**
111
+ * Dispatch an incoming WebRTC signal from the browser.
112
+ *
113
+ * - `offer`: creates (or reuses) the peer connection and sets the remote
114
+ * description; the library fires `onLocalDescription` automatically with
115
+ * the SDP answer.
116
+ * - `candidate`: adds the ICE candidate to the existing peer connection.
117
+ *
118
+ * @param {string} sessionId
119
+ * @param {WebRtcSignal} signal
120
+ * @returns {void}
121
+ */
122
+ function handleSignal(sessionId, signal) {
123
+ if (!signal || typeof signal.type !== "string") {
124
+ return;
125
+ }
126
+
127
+ if (signal.type === "offer") {
128
+ if (typeof signal.sdp !== "string") {
129
+ return;
130
+ }
131
+ log(`[webrtc] Session ${sessionId.slice(0, 8)}: received offer`);
132
+ const pc = getOrCreatePeer(sessionId);
133
+ pc.setRemoteDescription(signal.sdp, "offer");
134
+ // `onLocalDescription` fires automatically with the SDP answer.
135
+ return;
136
+ }
137
+
138
+ if (signal.type === "candidate") {
139
+ const pc = peers.get(sessionId);
140
+ if (!pc) {
141
+ return;
142
+ }
143
+ if (typeof signal.candidate === "string" && typeof signal.mid === "string") {
144
+ pc.addRemoteCandidate(signal.candidate, signal.mid);
145
+ }
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Close and remove the peer connection for a session.
151
+ * Called automatically when the connection enters a terminal state.
152
+ *
153
+ * @param {string} sessionId
154
+ * @returns {void}
155
+ */
156
+ function closeSession(sessionId) {
157
+ const pc = peers.get(sessionId);
158
+ if (pc) {
159
+ try { pc.close(); } catch { /* ignore */ }
160
+ peers.delete(sessionId);
161
+ log(`[webrtc] Session ${sessionId.slice(0, 8)}: closed`);
162
+ }
163
+ }
164
+
165
+ return { handleSignal, closeSession };
166
+ }