@torrent-tv/proxy 2.5.5 → 2.5.8
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/CHANGELOG.md +21 -0
- package/package.json +1 -1
- package/services/webrtc-manager.js +56 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## 2.5.7
|
|
2
|
+
|
|
3
|
+
- **Fix**: WebRTC connection failure behind symmetric NAT — all ICE candidates (private and public) are now sent to the browser immediately. The browser attempts all paths in parallel; the local LAN path succeeds when browser and proxy are on the same network. Chrome's Private Network Access dialog appears once on first connect.
|
|
4
|
+
|
|
5
|
+
## 2.5.6
|
|
6
|
+
|
|
7
|
+
- **Fix**: ICE candidate filtering — private host candidates (RFC 1918, Docker bridge IPs, IPv6 ULA/loopback) are now buffered and suppressed when a public srflx candidate is available. This eliminates the Chrome/Brave Private Network Access permission dialog when connecting from a page served over HTTPS. Falls back to private candidates if no public srflx candidate is gathered (e.g. STUN unreachable), so connectivity is preserved at the cost of the PNA dialog.
|
|
8
|
+
|
|
9
|
+
## 2.5.5
|
|
10
|
+
|
|
11
|
+
- **Fix**: Tunnel keepalive — proxy now sends a WebSocket ping to the server every 30 s to prevent Cloudflare's ~100 s idle-connection timeout from dropping the tunnel.
|
|
12
|
+
|
|
13
|
+
## 2.5.3
|
|
14
|
+
|
|
15
|
+
- Internal: improved tunnel reconnect logic and error logging.
|
|
16
|
+
|
|
17
|
+
## 2.0.0
|
|
18
|
+
|
|
19
|
+
- **New**: WebRTC P2P tunnel architecture — replaced direct HTTP streaming with a persistent WebSocket tunnel to the server. Video is delivered from the proxy to the browser over a WebRTC data channel; the server acts only as a signalling relay.
|
|
20
|
+
- **New**: `node-datachannel` dependency for server-side WebRTC.
|
|
21
|
+
- **Removed**: `public_base_url` config — no longer needed.
|
package/package.json
CHANGED
|
@@ -15,6 +15,49 @@ import nodeDataChannel from "node-datachannel";
|
|
|
15
15
|
|
|
16
16
|
const ICE_SERVERS = ["stun:stun.l.google.com:19302"];
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Return true when the ICE candidate string describes a `typ host` candidate
|
|
20
|
+
* with a private (RFC 1918 / ULA / loopback) IP address.
|
|
21
|
+
*
|
|
22
|
+
* Browsers enforce Private Network Access (PNA) and show a permission dialog
|
|
23
|
+
* when a page served from a public origin (e.g. webauth.courses) attempts a
|
|
24
|
+
* WebRTC connection to a private-network address. Filtering these candidates
|
|
25
|
+
* out before forwarding them to the browser lets the connection proceed via the
|
|
26
|
+
* server-reflexive (srflx) candidate — the proxy's public IP as seen by STUN —
|
|
27
|
+
* which does not trigger PNA.
|
|
28
|
+
*
|
|
29
|
+
* Edge case: if no srflx candidate is available (STUN unreachable, symmetric
|
|
30
|
+
* NAT that maps differently per destination, etc.) all host candidates are
|
|
31
|
+
* suppressed and the connection will fail. We accept this trade-off; STUN is
|
|
32
|
+
* a hard dependency of the WebRTC path in any case.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} candidate - Raw candidate attribute string from node-datachannel.
|
|
35
|
+
* @returns {boolean}
|
|
36
|
+
*/
|
|
37
|
+
function isPrivateHostCandidate(candidate) {
|
|
38
|
+
// Only care about "typ host" — srflx and relay are already public/relay.
|
|
39
|
+
if (!candidate.includes("typ host")) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
// RFC 1918 IPv4 private ranges.
|
|
43
|
+
if (/\b(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.)/.test(candidate)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
// Docker / typical private subnets not covered above (100.64–127 are special).
|
|
47
|
+
if (/\b127\./.test(candidate)) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
// IPv6 loopback.
|
|
51
|
+
if (/\s::1\s/.test(candidate)) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
// IPv6 Unique Local Addresses (ULA): fc00::/7 — starts with fc or fd.
|
|
55
|
+
if (/\s(?:fc|fd)[0-9a-f]{2}:/i.test(candidate)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
18
61
|
/**
|
|
19
62
|
* Configuration for the WebRTC manager.
|
|
20
63
|
*
|
|
@@ -79,11 +122,23 @@ export function createWebRtcManager({ sendSignal, onDataChannel, onLog }) {
|
|
|
79
122
|
iceServers: ICE_SERVERS
|
|
80
123
|
});
|
|
81
124
|
|
|
82
|
-
// Forward
|
|
125
|
+
// Forward all ICE candidates to the browser through the tunnel.
|
|
126
|
+
//
|
|
127
|
+
// All candidates — both private (RFC 1918) and public (srflx) — are sent
|
|
128
|
+
// immediately. The browser will attempt all paths in parallel and use
|
|
129
|
+
// whichever connects first. Private candidates trigger Chrome's Private
|
|
130
|
+
// Network Access (PNA) permission dialog once; after the user allows it
|
|
131
|
+
// the connection proceeds via the local LAN path.
|
|
83
132
|
pc.onLocalCandidate((candidate, mid) => {
|
|
133
|
+
const isPrivate = isPrivateHostCandidate(candidate);
|
|
134
|
+
log(`[webrtc] Session ${sessionId.slice(0, 8)}: sending ${isPrivate ? "private" : "public"} candidate`);
|
|
84
135
|
sendSignal(sessionId, { type: "candidate", candidate, mid });
|
|
85
136
|
});
|
|
86
137
|
|
|
138
|
+
pc.onGatheringStateChange((state) => {
|
|
139
|
+
log(`[webrtc] Session ${sessionId.slice(0, 8)}: gathering → ${state}`);
|
|
140
|
+
});
|
|
141
|
+
|
|
87
142
|
// Forward our SDP answer to the browser through the tunnel.
|
|
88
143
|
pc.onLocalDescription((sdp, type) => {
|
|
89
144
|
log(`[webrtc] Session ${sessionId.slice(0, 8)}: sending ${type}`);
|