@torrent-tv/proxy 2.6.0 → 2.6.4
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 +8 -0
- package/package.json +1 -1
- package/services/data-channel-handler.js +8 -3
- package/services/torrent-pool.js +24 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 2.6.3
|
|
2
|
+
|
|
3
|
+
- **Fix**: Data channel handler now logs **all** requests regardless of body presence — `GET /transcode/…`, `GET /api/…/progress`, `GET /api/…/stats` etc. were previously invisible in logs. Non-2xx response statuses and fetch errors are also logged, enabling diagnosis of HLS manifest load failures.
|
|
4
|
+
|
|
5
|
+
## 2.6.1
|
|
6
|
+
|
|
7
|
+
- **Fix**: `TorrentPool.getTorrent()` — eliminated a race condition where two concurrent requests for the same torrent both found the cache empty and both called `client.add()`, causing WebTorrent to throw "Cannot add duplicate torrent". In-flight promises are now cached in a private `#pending` map; subsequent requests for the same key join the existing promise instead of triggering a second `client.add()`.
|
|
8
|
+
|
|
1
9
|
## 2.5.15
|
|
2
10
|
|
|
3
11
|
- **New**: `GET /api/sources/:sourceKey/stats?fileIndex=N` — returns live torrent stats: connected peer count, download/upload speed, per-file download progress and size. Used by the browser to show meaningful feedback while waiting for file metadata.
|
package/package.json
CHANGED
|
@@ -143,9 +143,9 @@ export function createDataChannelHandler({ proxyPort, onLog }) {
|
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
146
|
+
const queryInfo = query ? `?${query}` : "";
|
|
147
|
+
const bodyInfo = body != null && typeof body === "string" && body.length > 0 ? ` body=${body.length} bytes` : "";
|
|
148
|
+
log(`[dc] ${method} ${path}${queryInfo}${bodyInfo}`);
|
|
149
149
|
|
|
150
150
|
const targetUrl = `http://127.0.0.1:${proxyPort}${path}${query ? `?${query}` : ""}`;
|
|
151
151
|
const requestHeaders = { ...(forwardedHeaders ?? {}), host: `127.0.0.1:${proxyPort}` };
|
|
@@ -159,10 +159,15 @@ export function createDataChannelHandler({ proxyPort, onLog }) {
|
|
|
159
159
|
redirect: "manual"
|
|
160
160
|
});
|
|
161
161
|
} catch (fetchError) {
|
|
162
|
+
log(`[dc] ${method} ${path}${queryInfo} → error: ${fetchError?.message ?? String(fetchError)}`);
|
|
162
163
|
send(channel, { type: "response-error", requestId, error: fetchError?.message ?? String(fetchError) });
|
|
163
164
|
return;
|
|
164
165
|
}
|
|
165
166
|
|
|
167
|
+
if (response.status !== 200 && response.status !== 206) {
|
|
168
|
+
log(`[dc] ${method} ${path}${queryInfo} → ${response.status}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
166
171
|
/** @type {Record<string, string>} */
|
|
167
172
|
const responseHeaders = {};
|
|
168
173
|
for (const [name, value] of response.headers.entries()) {
|
package/services/torrent-pool.js
CHANGED
|
@@ -36,6 +36,15 @@ function decodeTorrentSource(sourceType, source) {
|
|
|
36
36
|
* that only files with at least one active stream cause downloading.
|
|
37
37
|
*/
|
|
38
38
|
export class TorrentPool {
|
|
39
|
+
/**
|
|
40
|
+
* In-flight `client.add()` promises keyed by the same key as `torrents`.
|
|
41
|
+
* Prevents duplicate `client.add()` calls when two requests arrive
|
|
42
|
+
* concurrently for the same torrent before the first one resolves.
|
|
43
|
+
*
|
|
44
|
+
* @type {Map<string, Promise<import("webtorrent").Torrent>>}
|
|
45
|
+
*/
|
|
46
|
+
#pending = new Map();
|
|
47
|
+
|
|
39
48
|
constructor() {
|
|
40
49
|
/** @type {import("webtorrent").WebTorrent} */
|
|
41
50
|
this.client = new WebTorrent();
|
|
@@ -70,26 +79,38 @@ export class TorrentPool {
|
|
|
70
79
|
*/
|
|
71
80
|
async getTorrent(sourceType, source) {
|
|
72
81
|
const key = `${sourceType}:${crypto.createHash("sha1").update(source).digest("hex")}`;
|
|
82
|
+
|
|
83
|
+
// Already resolved — return immediately.
|
|
73
84
|
const existing = this.torrents.get(key);
|
|
74
85
|
if (existing) {
|
|
75
86
|
return existing;
|
|
76
87
|
}
|
|
77
88
|
|
|
89
|
+
// In-flight — a concurrent request already called client.add() for the
|
|
90
|
+
// same torrent; join that promise instead of calling add() again.
|
|
91
|
+
const inFlight = this.#pending.get(key);
|
|
92
|
+
if (inFlight) {
|
|
93
|
+
return inFlight;
|
|
94
|
+
}
|
|
95
|
+
|
|
78
96
|
const torrentId = decodeTorrentSource(sourceType, source);
|
|
79
|
-
const
|
|
97
|
+
const promise = new Promise((resolve, reject) => {
|
|
80
98
|
const onError = (error) => {
|
|
81
99
|
this.client.off("error", onError);
|
|
100
|
+
this.#pending.delete(key);
|
|
82
101
|
reject(error);
|
|
83
102
|
};
|
|
84
103
|
this.client.once("error", onError);
|
|
85
104
|
this.client.add(torrentId, (readyTorrent) => {
|
|
86
105
|
this.client.off("error", onError);
|
|
106
|
+
this.torrents.set(key, readyTorrent);
|
|
107
|
+
this.#pending.delete(key);
|
|
87
108
|
resolve(readyTorrent);
|
|
88
109
|
});
|
|
89
110
|
});
|
|
90
111
|
|
|
91
|
-
this.
|
|
92
|
-
return
|
|
112
|
+
this.#pending.set(key, promise);
|
|
113
|
+
return promise;
|
|
93
114
|
}
|
|
94
115
|
|
|
95
116
|
/**
|