@torrent-tv/proxy 2.5.7 → 2.5.9
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 +4 -0
- package/package.json +1 -1
- package/services/webrtc-manager.js +8 -55
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
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
|
+
|
|
1
5
|
## 2.5.6
|
|
2
6
|
|
|
3
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.
|
package/package.json
CHANGED
|
@@ -93,15 +93,6 @@ export function createWebRtcManager({ sendSignal, onDataChannel, onLog }) {
|
|
|
93
93
|
/** @type {Map<string, import("node-datachannel").PeerConnection>} */
|
|
94
94
|
const peers = new Map();
|
|
95
95
|
|
|
96
|
-
/**
|
|
97
|
-
* Per-session ICE candidate state.
|
|
98
|
-
* Tracks buffered private candidates and whether a public (srflx/relay)
|
|
99
|
-
* candidate has already been forwarded to the browser.
|
|
100
|
-
*
|
|
101
|
-
* @type {Map<string, { privateCandidates: Array<{candidate: string, mid: string}>, sentPublic: boolean }>}
|
|
102
|
-
*/
|
|
103
|
-
const iceState = new Map();
|
|
104
|
-
|
|
105
96
|
/**
|
|
106
97
|
* @param {string} message
|
|
107
98
|
* @returns {void}
|
|
@@ -131,58 +122,21 @@ export function createWebRtcManager({ sendSignal, onDataChannel, onLog }) {
|
|
|
131
122
|
iceServers: ICE_SERVERS
|
|
132
123
|
});
|
|
133
124
|
|
|
134
|
-
//
|
|
135
|
-
const ice = { privateCandidates: [], sentPublic: false };
|
|
136
|
-
iceState.set(sessionId, ice);
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Release any buffered private candidates to the browser.
|
|
140
|
-
* Called as a last resort when ICE gathering completes without producing
|
|
141
|
-
* a public (srflx/relay) candidate — e.g. when STUN is unreachable or
|
|
142
|
-
* the NAT is symmetric. The browser will show a PNA permission dialog,
|
|
143
|
-
* but at least connectivity is possible.
|
|
144
|
-
*/
|
|
145
|
-
function flushPrivateCandidates() {
|
|
146
|
-
if (ice.sentPublic || ice.privateCandidates.length === 0) {
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
log(`[webrtc] Session ${sessionId.slice(0, 8)}: no public ICE candidate available — falling back to private (PNA dialog will appear)`);
|
|
150
|
-
for (const { candidate, mid } of ice.privateCandidates) {
|
|
151
|
-
sendSignal(sessionId, { type: "candidate", candidate, mid });
|
|
152
|
-
}
|
|
153
|
-
ice.privateCandidates = [];
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Forward our ICE candidates to the browser through the tunnel.
|
|
125
|
+
// Forward all ICE candidates to the browser through the tunnel.
|
|
157
126
|
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
// buffer is flushed as a fallback so the user still gets a PNA dialog
|
|
164
|
-
// rather than a silent failure.
|
|
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.
|
|
165
132
|
pc.onLocalCandidate((candidate, mid) => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
ice.privateCandidates.push({ candidate, mid });
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
// Public candidate — send immediately and discard the private buffer.
|
|
172
|
-
if (!ice.sentPublic) {
|
|
173
|
-
ice.sentPublic = true;
|
|
174
|
-
ice.privateCandidates = [];
|
|
175
|
-
log(`[webrtc] Session ${sessionId.slice(0, 8)}: sent public ICE candidate (PNA-free path)`);
|
|
176
|
-
}
|
|
133
|
+
const isPrivate = isPrivateHostCandidate(candidate);
|
|
134
|
+
log(`[webrtc] Session ${sessionId.slice(0, 8)}: sending ${isPrivate ? "private" : "public"} candidate`);
|
|
177
135
|
sendSignal(sessionId, { type: "candidate", candidate, mid });
|
|
178
136
|
});
|
|
179
137
|
|
|
180
|
-
// When gathering finishes, flush private candidates if no public one arrived.
|
|
181
138
|
pc.onGatheringStateChange((state) => {
|
|
182
139
|
log(`[webrtc] Session ${sessionId.slice(0, 8)}: gathering → ${state}`);
|
|
183
|
-
if (state === "complete") {
|
|
184
|
-
flushPrivateCandidates();
|
|
185
|
-
}
|
|
186
140
|
});
|
|
187
141
|
|
|
188
142
|
// Forward our SDP answer to the browser through the tunnel.
|
|
@@ -259,7 +213,6 @@ export function createWebRtcManager({ sendSignal, onDataChannel, onLog }) {
|
|
|
259
213
|
if (pc) {
|
|
260
214
|
try { pc.close(); } catch { /* ignore */ }
|
|
261
215
|
peers.delete(sessionId);
|
|
262
|
-
iceState.delete(sessionId);
|
|
263
216
|
log(`[webrtc] Session ${sessionId.slice(0, 8)}: closed`);
|
|
264
217
|
}
|
|
265
218
|
}
|