@torrent-tv/proxy 2.89.16 → 2.89.18

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.
Files changed (299) hide show
  1. package/README.md +14 -3
  2. package/package.json +10 -2
  3. package/routes/api/client-logs/post.js +9 -2
  4. package/server.js +0 -11
  5. package/.claude/commands/opsx/apply.md +0 -155
  6. package/.claude/commands/opsx/archive.md +0 -160
  7. package/.claude/commands/opsx/explore.md +0 -174
  8. package/.claude/commands/opsx/propose.md +0 -109
  9. package/.claude/commands/opsx/sync.md +0 -143
  10. package/.claude/skills/openspec-apply-change/SKILL.md +0 -159
  11. package/.claude/skills/openspec-archive-change/SKILL.md +0 -117
  12. package/.claude/skills/openspec-explore/SKILL.md +0 -289
  13. package/.claude/skills/openspec-propose/SKILL.md +0 -113
  14. package/.claude/skills/openspec-sync-specs/SKILL.md +0 -147
  15. package/.dockerignore +0 -5
  16. package/AGENTS.md +0 -363
  17. package/CHANGELOG.md +0 -2373
  18. package/Dockerfile +0 -30
  19. package/biome.json +0 -412
  20. package/corpus.tmp.json +0 -1
  21. package/docs/browser-proxy-contract.md +0 -155
  22. package/docs/container-architecture.md +0 -273
  23. package/docs/download-architecture.md +0 -261
  24. package/docs/encode-architecture.md +0 -765
  25. package/docs/encode-run-state.md +0 -96
  26. package/docs/logs.md +0 -138
  27. package/docs/playback-start-forecast.md +0 -113
  28. package/docs/socks5-egress-research.md +0 -114
  29. package/docs/storage-architecture.md +0 -273
  30. package/knip.json +0 -14
  31. package/main2.cpuprofile +0 -1
  32. package/openspec/changes/adaptive-bitrate/.openspec.yaml +0 -0
  33. package/openspec/changes/adaptive-bitrate/design.md +0 -92
  34. package/openspec/changes/adaptive-bitrate/proposal.md +0 -56
  35. package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +0 -51
  36. package/openspec/changes/adaptive-bitrate/tasks.md +0 -40
  37. package/openspec/changes/binary-distribution/.openspec.yaml +0 -2
  38. package/openspec/changes/binary-distribution/proposal.md +0 -53
  39. package/openspec/changes/chunked-request-bodies/design.md +0 -81
  40. package/openspec/changes/chunked-request-bodies/proposal.md +0 -53
  41. package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +0 -30
  42. package/openspec/changes/chunked-request-bodies/tasks.md +0 -40
  43. package/openspec/changes/cold-start/design.md +0 -98
  44. package/openspec/changes/cold-start/proposal.md +0 -62
  45. package/openspec/changes/cold-start/specs/cold-start/spec.md +0 -47
  46. package/openspec/changes/cold-start/tasks.md +0 -48
  47. package/openspec/changes/disk-cap/.openspec.yaml +0 -2
  48. package/openspec/changes/disk-cap/proposal.md +0 -37
  49. package/openspec/changes/disk-cap/specs/disk-cap/spec.md +0 -25
  50. package/openspec/changes/disk-cap/tasks.md +0 -19
  51. package/openspec/changes/proxy-observability/.openspec.yaml +0 -2
  52. package/openspec/changes/proxy-observability/design.md +0 -38
  53. package/openspec/changes/proxy-observability/proposal.md +0 -49
  54. package/openspec/changes/proxy-observability/specs/observability/spec.md +0 -33
  55. package/openspec/changes/proxy-observability/tasks.md +0 -21
  56. package/openspec/changes/seek-debounce/.openspec.yaml +0 -0
  57. package/openspec/changes/seek-debounce/design.md +0 -118
  58. package/openspec/changes/seek-debounce/proposal.md +0 -59
  59. package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +0 -41
  60. package/openspec/changes/seek-debounce/tasks.md +0 -33
  61. package/openspec/changes/subtitle-language/.openspec.yaml +0 -2
  62. package/openspec/changes/subtitle-language/proposal.md +0 -50
  63. package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +0 -34
  64. package/openspec/changes/subtitle-language/tasks.md +0 -26
  65. package/openspec/changes/track-selection/.openspec.yaml +0 -2
  66. package/openspec/changes/track-selection/design.md +0 -47
  67. package/openspec/changes/track-selection/proposal.md +0 -45
  68. package/openspec/changes/track-selection/specs/track-selection/spec.md +0 -41
  69. package/openspec/changes/track-selection/tasks.md +0 -20
  70. package/openspec/changes/transcode-quality/.openspec.yaml +0 -2
  71. package/openspec/changes/transcode-quality/proposal.md +0 -51
  72. package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +0 -139
  73. package/openspec/changes/transcode-quality/tasks.md +0 -63
  74. package/openspec/config.yaml +0 -38
  75. package/public/.well-known/appspecific/com.chrome.devtools.json +0 -1
  76. package/research/delivery-wedge-stand-ladder-2026-09-13.md +0 -329
  77. package/research/double-spawn-2026-09-10.md +0 -171
  78. package/research/handover-2026-09-13.md +0 -209
  79. package/research/handover-reader-claims-removal-2026-09-12.md +0 -166
  80. package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +0 -175
  81. package/research/preset-benchmark-size-scaling-2026-08-22.md +0 -55
  82. package/research/priority-map-is-the-truth-2026-09-12.md +0 -225
  83. package/research/valgrind-replica-2026-08-27/logs.tar.gz +0 -0
  84. package/scripts/build-audio-samples.mjs +0 -31
  85. package/scripts/render-run-graph.js +0 -173
  86. package/test/asset-paths.test.js +0 -28
  87. package/test/audio-buffer-continuity.test.js +0 -77
  88. package/test/audio-calibration.test.js +0 -23
  89. package/test/audio-inventory.test.js +0 -212
  90. package/test/audio-track-bitrate.test.js +0 -40
  91. package/test/audio-warm-recorded.test.js +0 -63
  92. package/test/auto-quality-step.test.js +0 -762
  93. package/test/available-share.test.js +0 -76
  94. package/test/background-fill.test.js +0 -190
  95. package/test/client-log-file.test.js +0 -126
  96. package/test/coalesce.test.js +0 -39
  97. package/test/completed-files.test.js +0 -115
  98. package/test/concurrent-cost.test.js +0 -148
  99. package/test/container-media-info.test.js +0 -228
  100. package/test/contention.test.js +0 -76
  101. package/test/core-dumps.test.js +0 -57
  102. package/test/coverage-follows-the-disk.test.js +0 -233
  103. package/test/coverage-map.test.js +0 -195
  104. package/test/cue-push-carries-what-the-walk-found.test.js +0 -58
  105. package/test/cut-times-timeline.test.js +0 -65
  106. package/test/cuts-follow-published-grid.test.js +0 -109
  107. package/test/data-channel-frame.test.js +0 -63
  108. package/test/data-channel-viewer-lifecycle.test.js +0 -193
  109. package/test/dead-torrent-handle.test.js +0 -52
  110. package/test/declared-rates.test.js +0 -116
  111. package/test/declared-tracks.test.js +0 -43
  112. package/test/decode-cost-fit.test.js +0 -202
  113. package/test/decode-cost.test.js +0 -475
  114. package/test/delivery-probe-field.test.js +0 -173
  115. package/test/delivery-probe.test.js +0 -382
  116. package/test/demand-register.test.js +0 -206
  117. package/test/dht-bootstrap.test.js +0 -72
  118. package/test/drain-threat.test.js +0 -65
  119. package/test/duplicate-add.test.js +0 -48
  120. package/test/encode-admission.test.js +0 -170
  121. package/test/encode-cost.test.js +0 -275
  122. package/test/encode-exit.test.js +0 -111
  123. package/test/encode-orchestrator.test.js +0 -426
  124. package/test/encode-plan-viewers.test.js +0 -725
  125. package/test/encode-plan.test.js +0 -545
  126. package/test/encode-run-state.test.js +0 -314
  127. package/test/encode-run.test.js +0 -248
  128. package/test/encode-slope.test.js +0 -46
  129. package/test/encoded-output.test.js +0 -39
  130. package/test/encoder-kinds.test.js +0 -122
  131. package/test/encoder-readings.test.js +0 -68
  132. package/test/episode-naming.test.js +0 -131
  133. package/test/every-claimant-is-told-its-share.test.js +0 -152
  134. package/test/failed-encoder-still-serves-what-exists.test.js +0 -354
  135. package/test/failed-start-breaker.test.js +0 -202
  136. package/test/fastest-wires.test.js +0 -143
  137. package/test/ffmpeg-stream-counts.test.js +0 -101
  138. package/test/file-edges.test.js +0 -191
  139. package/test/files-in-use.test.js +0 -34
  140. package/test/fixtures/audio-frame-boundary.json +0 -1
  141. package/test/fixtures/refused-audio-20261002.json +0 -10
  142. package/test/fmp4-presentation-clock.test.js +0 -148
  143. package/test/fmp4-self-contained.test.js +0 -80
  144. package/test/fmp4-timestamps.test.js +0 -140
  145. package/test/fmp4-track-completeness.test.js +0 -41
  146. package/test/format-keyed-outputs.test.js +0 -78
  147. package/test/health-metrics.test.js +0 -36
  148. package/test/held-read-ownership.test.js +0 -77
  149. package/test/held-request-width.test.js +0 -157
  150. package/test/helpers/encode-run.js +0 -248
  151. package/test/helpers/held-file.js +0 -77
  152. package/test/helpers/manager.js +0 -62
  153. package/test/helpers/matroska-file.js +0 -296
  154. package/test/helpers/output-spec.js +0 -63
  155. package/test/host-load.test.js +0 -68
  156. package/test/index-accuracy-is-about-the-file.test.js +0 -128
  157. package/test/init-compat.test.js +0 -219
  158. package/test/input-lost-quiets-the-plan.test.js +0 -261
  159. package/test/input-unavailable.test.js +0 -48
  160. package/test/installation-id.test.js +0 -39
  161. package/test/keeping-period.test.js +0 -83
  162. package/test/keyframe-index-accuracy.test.js +0 -188
  163. package/test/keyframe-table-budget.test.js +0 -123
  164. package/test/keyframes-belong-to-the-file.test.js +0 -249
  165. package/test/learned-median.test.js +0 -37
  166. package/test/letting-go-asks-who-is-watching.test.js +0 -191
  167. package/test/limit-row-by-area.test.js +0 -60
  168. package/test/link-budget.test.js +0 -111
  169. package/test/link-probe.test.js +0 -56
  170. package/test/local-encoding-data.test.js +0 -273
  171. package/test/logger-repeats.test.js +0 -134
  172. package/test/look-ahead-coverage.test.js +0 -56
  173. package/test/manager-surface.test.js +0 -291
  174. package/test/matroska-blocks.test.js +0 -0
  175. package/test/matroska-cluster-discovery.test.js +0 -323
  176. package/test/matroska-cues-track.test.js +0 -192
  177. package/test/media-time.test.js +0 -62
  178. package/test/memory-budget.test.js +0 -390
  179. package/test/move-cost.test.js +0 -216
  180. package/test/mp4-composition-times.test.js +0 -0
  181. package/test/mp4-subtitles.test.js +0 -173
  182. package/test/one-authority.test.js +0 -285
  183. package/test/one-budget.test.js +0 -149
  184. package/test/one-output-one-session.test.js +0 -273
  185. package/test/one-resource-divided.test.js +0 -185
  186. package/test/one-serving-output-per-height.test.js +0 -458
  187. package/test/orchestrator-wired.test.js +0 -207
  188. package/test/output-catalog.test.js +0 -39
  189. package/test/output-shape.test.js +0 -21
  190. package/test/output-spec.test.js +0 -251
  191. package/test/output-speed.test.js +0 -92
  192. package/test/outputs-being-prepared.test.js +0 -40
  193. package/test/packet-witness-ring.test.js +0 -236
  194. package/test/packet-witness.test.js +0 -148
  195. package/test/piece-completeness.test.js +0 -78
  196. package/test/piece-disk-store.test.js +0 -401
  197. package/test/piece-from-whole-file.test.js +0 -129
  198. package/test/piece-lru.test.js +0 -319
  199. package/test/piece-reader.test.js +0 -162
  200. package/test/piece-selection-offset.test.js +0 -85
  201. package/test/piece-store-eviction.test.js +0 -595
  202. package/test/piece-store-never-refuses.test.js +0 -153
  203. package/test/piece-store-reservations.test.js +0 -403
  204. package/test/piece-store-slow-disk.test.js +0 -159
  205. package/test/piece-tail.test.js +0 -97
  206. package/test/plan-host-timings.test.js +0 -68
  207. package/test/plan-is-steady.test.js +0 -225
  208. package/test/playback-readiness.test.js +0 -518
  209. package/test/playback-start-regressions.test.js +0 -101
  210. package/test/priority-map-download.test.js +0 -174
  211. package/test/priority-map-emptied.test.js +0 -204
  212. package/test/priority-map-per-output.test.js +0 -215
  213. package/test/priority-map.test.js +0 -163
  214. package/test/probe-wedge-certainty.test.js +0 -60
  215. package/test/produced-copy-choice.test.js +0 -213
  216. package/test/published-timeline.test.js +0 -83
  217. package/test/quality-offer.test.js +0 -160
  218. package/test/quality-variants.test.js +0 -1274
  219. package/test/rate-control-in-the-output.test.js +0 -181
  220. package/test/read-survives-withdrawal.test.js +0 -164
  221. package/test/read-window.test.js +0 -365
  222. package/test/reads-not-yet-arrived.test.js +0 -218
  223. package/test/resume-warm.test.js +0 -39
  224. package/test/run-graph-drift.test.js +0 -37
  225. package/test/run-inputs.test.js +0 -80
  226. package/test/run-intervals.test.js +0 -100
  227. package/test/run-position-follows-published-grid.test.js +0 -89
  228. package/test/run-progress.test.js +0 -64
  229. package/test/same-height-switch.test.js +0 -663
  230. package/test/seek-frees-the-viewers-requests.test.js +0 -217
  231. package/test/seek-landing.test.js +0 -109
  232. package/test/seek-target-not-superseded.test.js +0 -195
  233. package/test/segment-closed-on-both-branches.test.js +0 -114
  234. package/test/segment-cut-times.test.js +0 -63
  235. package/test/segment-demand.test.js +0 -80
  236. package/test/segment-hold-supersede.test.js +0 -91
  237. package/test/segment-judged-by-its-own-header.test.js +0 -222
  238. package/test/segment-serve-wiring.test.js +0 -481
  239. package/test/segment-store-eviction.test.js +0 -247
  240. package/test/segment-store.test.js +0 -352
  241. package/test/segment-true-start.test.js +0 -86
  242. package/test/segments-are-shared.test.js +0 -176
  243. package/test/send-chunk-bytes.test.js +0 -86
  244. package/test/serving-existing-output.test.js +0 -111
  245. package/test/serving-output.test.js +0 -102
  246. package/test/shared-piece-store.test.js +0 -310
  247. package/test/sidecar-naming.test.js +0 -142
  248. package/test/soundtrack-rate-bound.test.js +0 -289
  249. package/test/source-file.test.js +0 -133
  250. package/test/source-files-route.test.js +0 -113
  251. package/test/source-registry.test.js +0 -18
  252. package/test/split-a-stretch.test.js +0 -139
  253. package/test/stale-request-after-seek.test.js +0 -158
  254. package/test/startup-readings.test.js +0 -124
  255. package/test/stream-route.test.js +0 -237
  256. package/test/subtitle-cue-framing.test.js +0 -200
  257. package/test/subtitle-cue-source.test.js +0 -102
  258. package/test/subtitle-cue-walk.test.js +0 -385
  259. package/test/subtitle-cursor.test.js +0 -63
  260. package/test/subtitle-defaults.test.js +0 -118
  261. package/test/subtitle-language.test.js +0 -252
  262. package/test/subtitle-metadata.test.js +0 -15
  263. package/test/subtitle-plan-waits.test.js +0 -125
  264. package/test/subtitle-track-numbering.test.js +0 -391
  265. package/test/supply-margin.test.js +0 -190
  266. package/test/swarm-follows-readers.test.js +0 -174
  267. package/test/swarm-reach.test.js +0 -130
  268. package/test/swarm-selection.test.js +0 -220
  269. package/test/tail-duplication.test.js +0 -167
  270. package/test/the-channel-has-both-ends.test.js +0 -91
  271. package/test/the-torrent-thread-serves-bytes.test.js +0 -107
  272. package/test/thread-pool.test.js +0 -53
  273. package/test/timeline.test.js +0 -84
  274. package/test/torrent-contents.test.js +0 -235
  275. package/test/torrent-cost.test.js +0 -71
  276. package/test/torrent-files.test.js +0 -221
  277. package/test/torrent-naming.test.js +0 -255
  278. package/test/tracks-begin-together.test.js +0 -209
  279. package/test/tunnel-renewal.test.js +0 -167
  280. package/test/two-viewers-one-picture.test.js +0 -508
  281. package/test/upload-hurry.test.js +0 -186
  282. package/test/usrsctp-state.test.js +0 -131
  283. package/test/video-facts.test.js +0 -102
  284. package/test/viewer-assignments.test.js +0 -143
  285. package/test/viewer-outputs.test.js +0 -285
  286. package/test/viewer-presence.test.js +0 -116
  287. package/test/viewer-requests-admission.test.js +0 -92
  288. package/test/viewer-starts-on-map.test.js +0 -27
  289. package/test/viewer-states.test.js +0 -192
  290. package/test/viewer-subtitle-subscription.test.js +0 -96
  291. package/test/viewer.test.js +0 -139
  292. package/test/viewing-generations.test.js +0 -390
  293. package/test/visible-rung.test.js +0 -37
  294. package/test/wait-ledger.test.js +0 -100
  295. package/test/webrtc-shim.test.js +0 -149
  296. package/test/wedge-certainty.test.js +0 -131
  297. package/test/whole-file-held-read.test.js +0 -30
  298. package/test/withdraw-piece-claim.test.js +0 -212
  299. package/test/worker-channel.test.js +0 -158
@@ -1,174 +0,0 @@
1
- /**
2
- * @file A proxy that needs nothing from a swarm is not in that swarm.
3
- *
4
- * WebTorrent enforces `maxConns` only on peers it dials (`_drain`), while
5
- * `_addIncomingPeer` checks that the torrent is neither destroyed nor paused
6
- * and registers the peer. A proxy with its port mapped is reachable, so on a
7
- * popular torrent connections arrive and are never turned away — field
8
- * 2026-09-11: 249 connected at the start of one viewing, 596 at the end, 15 862
9
- * more queued, on a file complete for three quarters of an hour.
10
- *
11
- * The rule is not a limit on connections. While anybody is reading, every one is
12
- * worth keeping: the one that has delivered nothing yet may deliver next.
13
- *
14
- * AND IT IS ASKED OF WHAT HAS BEEN STATED, never of a count of readers. The
15
- * first version asked every five seconds whether anybody was READING, and a
16
- * torrent added three seconds earlier answered no — its edges still being read,
17
- * its plan still being built. It left the swarm with 741 connections let go,
18
- * and nothing rejoined it: rejoining waited for a reader, and the reader was
19
- * waiting for the header the swarm had been fetching. Playback did not start at
20
- * all.
21
- *
22
- * The question now is whether anything is WANTED of it, which the priority map,
23
- * the ends of an open file and a stopped read all answer — and which is true
24
- * from the moment a torrent is opened.
25
- */
26
-
27
- import test from "node:test";
28
- import assert from "node:assert/strict";
29
- import {
30
- isWanted,
31
- leaveSwarm,
32
- rejoinSwarm,
33
- stateFileEdges,
34
- swarmDecisionFor
35
- } from "../services/torrent/torrent-pool.js";
36
- import { demandFor, forgetTorrent } from "../services/torrent/download/registry.js";
37
- import { Urgency } from "../services/torrent/demand/index.js";
38
-
39
- /**
40
- * A torrent that records what was done to it. Only the surface the rule
41
- * touches: `paused` is the library's own flag, and the peers are what the pause
42
- * does not close by itself.
43
- *
44
- * @param {number} peerCount
45
- * @returns {object}
46
- */
47
- function torrentWith(peerCount) {
48
- const peers = new Map();
49
- for (let index = 0; index < peerCount; index += 1) {
50
- peers.set(String(index), {
51
- wire: { downloaded: 0 },
52
- destroy() {
53
- peers.delete(String(index));
54
- }
55
- });
56
- }
57
- return {
58
- infoHash: "f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0",
59
- files: [{ length: 1024 }, { length: 2048 }],
60
- paused: false,
61
- wires: [...peers.values()].map((peer) => peer.wire),
62
- _peers: peers,
63
- pause() {
64
- this.paused = true;
65
- },
66
- resume() {
67
- this.paused = false;
68
- }
69
- };
70
- }
71
-
72
- test("letting a swarm go pauses the torrent and closes what the pause does not", () => {
73
- const torrent = torrentWith(6);
74
-
75
- const closed = leaveSwarm(torrent);
76
-
77
- assert.equal(torrent.paused, true, "the library's own word for this was not used");
78
- assert.equal(closed, 6);
79
- assert.equal(torrent._peers.size, 0, "the connections the pause does not close were not let go");
80
- });
81
-
82
- test("a swarm already let go is not let go twice", () => {
83
- const torrent = torrentWith(4);
84
- leaveSwarm(torrent);
85
- assert.equal(leaveSwarm(torrent), 0);
86
- });
87
-
88
- test("the reader that comes back takes the swarm with it", () => {
89
- const torrent = torrentWith(3);
90
- leaveSwarm(torrent);
91
-
92
- assert.equal(rejoinSwarm(torrent), true);
93
- assert.equal(torrent.paused, false, "the next reader was left with a torrent that fetches nothing");
94
- assert.equal(rejoinSwarm(torrent), false, "a torrent already in its swarm was resumed again");
95
- });
96
-
97
- test("a torrent whose metadata has not arrived is wanted, whatever is stated", () => {
98
- // THE FIELD FAILURE OF 2026-09-11, as a question rather than as a mechanism.
99
- // A torrent being added has no list of files, so nothing can name a byte of
100
- // it — and it is being fetched precisely because somebody asked for it.
101
- const torrent = torrentWith(103);
102
- torrent.files = [];
103
- assert.equal(isWanted(torrent), true);
104
- });
105
-
106
- test("a torrent with files and nothing stated for them is wanted by nobody", () => {
107
- const torrent = torrentWith(4);
108
- try {
109
- assert.equal(isWanted(torrent), false);
110
- } finally {
111
- forgetTorrent(torrent);
112
- }
113
- });
114
-
115
- test("anything stated makes it wanted, and the last withdrawal ends that", () => {
116
- const torrent = torrentWith(4);
117
- try {
118
- // The ends of a file being opened, which is the first thing said about a
119
- // torrent anybody has picked and the reason it stays in its swarm through
120
- // the seconds when nothing else can say anything about it.
121
- stateFileEdges(torrent, 0, Urgency.TAIL);
122
- assert.equal(isWanted(torrent), true);
123
-
124
- const { register } = demandFor(torrent);
125
- register.withdraw("file-edges:0:head");
126
- assert.equal(isWanted(torrent), true, "one end of it is still wanted");
127
- register.withdraw("file-edges:0:tail");
128
- assert.equal(isWanted(torrent), false);
129
- } finally {
130
- forgetTorrent(torrent);
131
- }
132
- });
133
-
134
- test("a torrent that has been destroyed is wanted by nobody", () => {
135
- const torrent = torrentWith(1);
136
- torrent.destroyed = true;
137
- assert.equal(isWanted(torrent), false);
138
- });
139
-
140
- test("a torrent something is wanted of keeps its swarm and is off the clock", () => {
141
- assert.deepEqual(
142
- swarmDecisionFor({ wanted: true, everWanted: true }),
143
- { swarm: "take", onTheClock: false }
144
- );
145
- assert.deepEqual(
146
- swarmDecisionFor({ wanted: true, everWanted: false }),
147
- { swarm: "take", onTheClock: false }
148
- );
149
- });
150
-
151
- test("a swarm is let go on a DEPARTURE, never on a beginning", () => {
152
- // The whole of the 2.83.1 failure in one line: a torrent that has never been
153
- // wanted is one being opened — its metadata has just landed, the file list is
154
- // on its way to the person choosing, and nothing has had a chance to state
155
- // anything about it yet. Taking its swarm away there destroyed 741
156
- // connections it needed a minute later.
157
- assert.deepEqual(
158
- swarmDecisionFor({ wanted: false, everWanted: false }),
159
- { swarm: "leave alone", onTheClock: true }
160
- );
161
- assert.deepEqual(
162
- swarmDecisionFor({ wanted: false, everWanted: true }),
163
- { swarm: "let go", onTheClock: true }
164
- );
165
- });
166
-
167
- test("whatever happens to the swarm, an unwanted torrent is on the idle clock", () => {
168
- // Including the one nobody has ever wanted: a file list fetched and never
169
- // played used to be held for the life of the process, because the only thing
170
- // that started the clock was a reader letting go.
171
- for (const everWanted of [true, false]) {
172
- assert.equal(swarmDecisionFor({ wanted: false, everWanted }).onTheClock, true);
173
- }
174
- });
@@ -1,130 +0,0 @@
1
- /**
2
- * @file The line describing a swarm answers the question it is asked.
3
- *
4
- * It could not. `routes/api/sources/stats/get.js` printed `peers=N` beside
5
- * `wires=?`, which reads as two quantities of which one is unknown — while
6
- * WebTorrent's `numPeers` IS `wires.length` (`lib/torrent.js:254`, the same in
7
- * 2.8.5 and 3.0.21), so the first was the connection count and the second was a
8
- * field that has never printed anything, because the torrent lives on a worker
9
- * thread where that property does not exist.
10
- *
11
- * What was missing is the other half: how many peers the client HOLDS but is
12
- * not connected to, and what the tracker said the swarm has. Measured
13
- * 2026-08-21 on `JUFD665.mp4`, the tracker answered `seeders=5` at 13:40:30 and
14
- * the first wire arrived at 13:44:47 — 4 min 17 s in which the stats line
15
- * repeated unchanged every two seconds while the answer ("offered, not
16
- * connected") was already in the process.
17
- */
18
-
19
- import assert from "node:assert/strict";
20
- import test from "node:test";
21
-
22
- import { bestAnnounce, describeSwarmReach, secondsToFirstPeer } from "../services/torrent/torrent-pool.js";
23
-
24
- test("connected and known are separate numbers", () => {
25
- const torrent = { wires: [{}, {}], _peersLength: 7, _numQueued: 4 };
26
- assert.deepEqual(describeSwarmReach(torrent), {
27
- connectedPeers: 2,
28
- deliveringPeers: 0,
29
- knownPeers: 7,
30
- queuedPeers: 4
31
- });
32
- });
33
-
34
- test("offered but not connected is distinguishable from nobody offered", () => {
35
- // The two shapes the field produced. They need opposite investigations, and
36
- // one line has to tell them apart.
37
- const offeredNotConnected = describeSwarmReach({ wires: [], _peersLength: 5, _numQueued: 5 });
38
- const nobodyOffered = describeSwarmReach({ wires: [], _peersLength: 0, _numQueued: 0 });
39
- assert.equal(offeredNotConnected.connectedPeers, 0);
40
- assert.equal(nobodyOffered.connectedPeers, 0);
41
- assert.notEqual(offeredNotConnected.knownPeers, nobodyOffered.knownPeers);
42
- });
43
-
44
- test("internals that are gone say nothing rather than breaking the poll", () => {
45
- // `_peersLength` and `_numQueued` are not WebTorrent's published interface.
46
- // The browser polls this every two seconds, so a version that drops them must
47
- // cost the field and not the answer.
48
- assert.deepEqual(describeSwarmReach({ wires: [{}] }), {
49
- connectedPeers: 1,
50
- deliveringPeers: 0,
51
- knownPeers: null,
52
- queuedPeers: null
53
- });
54
- assert.deepEqual(describeSwarmReach({}), {
55
- connectedPeers: 0,
56
- deliveringPeers: 0,
57
- knownPeers: null,
58
- queuedPeers: null
59
- });
60
- assert.deepEqual(describeSwarmReach(null), {
61
- connectedPeers: 0,
62
- deliveringPeers: 0,
63
- knownPeers: null,
64
- queuedPeers: null
65
- });
66
- const throwing = {
67
- wires: [],
68
- get _peersLength() {
69
- throw new Error("destroyed");
70
- }
71
- };
72
- assert.deepEqual(describeSwarmReach(throwing), {
73
- connectedPeers: 0,
74
- deliveringPeers: 0,
75
- knownPeers: null,
76
- queuedPeers: null
77
- });
78
- });
79
-
80
- test("the wait for a first peer is a measured quantity", () => {
81
- // The field case: added 13:40:30.357, first wire 13:44:47.123.
82
- assert.equal(secondsToFirstPeer(1000, 258_766), 257.766);
83
- assert.equal(secondsToFirstPeer(1000, 1000), 0);
84
- });
85
-
86
- test("no peer yet is not a duration", () => {
87
- assert.equal(secondsToFirstPeer(1000, null), null);
88
- assert.equal(secondsToFirstPeer(null, 2000), null);
89
- // A clock that went backwards is not a negative wait; it is no reading.
90
- assert.equal(secondsToFirstPeer(2000, 1000), null);
91
- });
92
-
93
- test("the best tracker answer wins, not the most recent", () => {
94
- // A live tracker says 500, a dead one answers 0 two seconds later. Keeping
95
- // the last would print "nobody offered" about a swarm of five hundred, which
96
- // inverts the one distinction this figure is carried for.
97
- const answers = [
98
- { seeders: 500, leechers: 40 },
99
- { seeders: 0, leechers: 0 }
100
- ];
101
- assert.deepEqual(bestAnnounce(answers), { seeders: 500, leechers: 40, trackers: 2 });
102
- });
103
-
104
- test("trackers that answered are counted even when none knew anything", () => {
105
- assert.deepEqual(bestAnnounce([{ seeders: null, leechers: null }]), {
106
- seeders: null,
107
- leechers: null,
108
- trackers: 1
109
- });
110
- // No tracker has answered at all — a different state from "answered, knows
111
- // nobody", and the line says so.
112
- assert.deepEqual(bestAnnounce([]), { seeders: null, leechers: null, trackers: 0 });
113
- assert.deepEqual(bestAnnounce(null), { seeders: null, leechers: null, trackers: 0 });
114
- });
115
-
116
- test("leechers travel with the seeder count they were reported beside", () => {
117
- const answers = [
118
- { seeders: 2, leechers: 99 },
119
- { seeders: 7, leechers: 3 }
120
- ];
121
- assert.equal(bestAnnounce(answers).leechers, 3);
122
- // Including when the winning answer gave no leecher count: carrying the
123
- // previous tracker's figure forward would present two trackers' numbers as
124
- // one reading.
125
- assert.deepEqual(bestAnnounce([{ seeders: 2, leechers: 99 }, { seeders: 7, leechers: null }]), {
126
- seeders: 7,
127
- leechers: null,
128
- trackers: 2
129
- });
130
- });
@@ -1,220 +0,0 @@
1
- /**
2
- * @file The one place that speaks to WebTorrent.
3
- *
4
- * Driven against a stub torrent shaped like the vendored 2.8.5: a selection
5
- * list it can be asked about, a bitfield saying what has arrived, and the
6
- * private `_select`/`_deselect` the real one exposes.
7
- */
8
-
9
- import test from "node:test";
10
- import assert from "node:assert/strict";
11
-
12
- import { DemandRegister } from "../services/torrent/demand/DemandRegister.js";
13
- import { Urgency } from "../services/torrent/demand/Urgency.js";
14
- import { SwarmSelection } from "../services/torrent/download/SwarmSelection.js";
15
-
16
- const PIECE = 1024;
17
-
18
- /**
19
- * @param {object} [params]
20
- * @param {number[]} [params.have] - Pieces that have arrived.
21
- * @param {number} [params.files] - How many files, each ten pieces long.
22
- * @returns {object}
23
- */
24
- function stubTorrent({ have = [], files = 1 } = {}) {
25
- const arrived = new Set(have);
26
- const items = [];
27
- return {
28
- pieceLength: PIECE,
29
- store: null,
30
- files: Array.from({ length: files }, (unused, index) => ({
31
- offset: index * 10 * PIECE,
32
- length: 10 * PIECE
33
- })),
34
- bitfield: { get: (index) => arrived.has(index) },
35
- _critical: [],
36
- _selections: { _items: items },
37
- calls: { select: [], deselect: [], critical: [] },
38
- _select(from, to, priority, notify, isStream) {
39
- this.calls.select.push({ from, to, priority, isStream });
40
- items.push({ from, to, priority });
41
- },
42
- _deselect(from, to) {
43
- this.calls.deselect.push({ from, to });
44
- const at = items.findIndex((item) => item.from === from && item.to === to);
45
- if (at >= 0) {
46
- items.splice(at, 1);
47
- }
48
- },
49
- critical(from, to) {
50
- this.calls.critical.push({ from, to });
51
- for (let index = from; index <= to; index += 1) {
52
- this._critical[index] = true;
53
- }
54
- },
55
- /** Pretend the library dropped a satisfied selection, which it does. */
56
- forget(from, to) {
57
- const at = items.findIndex((item) => item.from === from && item.to === to);
58
- if (at >= 0) {
59
- items.splice(at, 1);
60
- }
61
- }
62
- };
63
- }
64
-
65
- test("nothing is asked of the swarm until somebody states a need", () => {
66
- const torrent = stubTorrent();
67
- const selection = new SwarmSelection({ torrent, register: new DemandRegister() });
68
-
69
- assert.deepEqual(selection.reconcile(), { stated: 0, withdrawn: 0 });
70
- assert.equal(torrent.calls.select.length, 0);
71
- });
72
-
73
- test("two viewers of one film are two instructions, and both are urgent", () => {
74
- const torrent = stubTorrent();
75
- const register = new DemandRegister();
76
- const selection = new SwarmSelection({ torrent, register });
77
-
78
- // One stopped at the start, one stopped further in. Both pictures are still.
79
- register.state({ claimant: "v1", fileIndex: 0, byteStart: 0, byteEnd: PIECE - 1, urgency: Urgency.BLOCKED });
80
- register.state({ claimant: "v2", fileIndex: 0, byteStart: 5 * PIECE, byteEnd: 6 * PIECE - 1, urgency: Urgency.BLOCKED });
81
- selection.reconcile();
82
-
83
- assert.deepEqual(torrent.calls.select, [
84
- { from: 0, to: 0, priority: 1, isStream: true },
85
- { from: 5, to: 5, priority: 1, isStream: true }
86
- ]);
87
- // Both may take a block from a slow peer, and the mark spans both.
88
- assert.deepEqual(torrent.calls.critical, [{ from: 0, to: 5 }]);
89
- });
90
-
91
- test("the same pieces wanted by two claimants are one instruction", () => {
92
- const torrent = stubTorrent();
93
- const register = new DemandRegister();
94
- const selection = new SwarmSelection({ torrent, register });
95
-
96
- // Picture and sound of one viewer read the same file and overlap.
97
- register.state({ claimant: "video", fileIndex: 0, byteStart: 0, byteEnd: PIECE - 1, urgency: Urgency.NEAR });
98
- register.state({ claimant: "audio", fileIndex: 0, byteStart: 0, byteEnd: PIECE - 1, urgency: Urgency.NEAR });
99
- const first = selection.reconcile();
100
-
101
- assert.equal(first.stated, 1, "one range, told once");
102
- assert.equal(torrent.calls.select.length, 1);
103
- });
104
-
105
- test("the speculative levels are withdrawn whole the moment something urgent is missing", () => {
106
- const torrent = stubTorrent({ have: [0] });
107
- const register = new DemandRegister();
108
- const selection = new SwarmSelection({ torrent, register });
109
-
110
- register.state({ claimant: "video", fileIndex: 0, byteStart: 0, byteEnd: PIECE - 1, urgency: Urgency.NEAR });
111
- register.state({ claimant: "fill", fileIndex: 0, byteStart: 5 * PIECE, byteEnd: 9 * PIECE - 1, urgency: Urgency.TAIL });
112
- selection.reconcile();
113
-
114
- assert.equal(selection.statedRanges().length, 2, "nothing urgent is missing, so the tail is stated");
115
- assert.ok(torrent.calls.select.some((call) => call.priority === 0), "and it is stated as zero");
116
-
117
- // The viewer moves on to a piece that has not arrived.
118
- register.state({ claimant: "video", fileIndex: 0, byteStart: PIECE, byteEnd: 2 * PIECE - 1, urgency: Urgency.NEAR });
119
- selection.reconcile();
120
-
121
- assert.deepEqual(
122
- selection.statedRanges().map((range) => range.priority),
123
- [1],
124
- "the tail is out of the download set entirely, not lowered — a peer with nothing urgent to give must not be able to fall through to it"
125
- );
126
- assert.ok(torrent.calls.deselect.length > 0);
127
- });
128
-
129
- test("a selection the library drops once satisfied is stated again", () => {
130
- const torrent = stubTorrent();
131
- const register = new DemandRegister();
132
- const selection = new SwarmSelection({ torrent, register });
133
-
134
- register.state({ claimant: "video", fileIndex: 0, byteStart: 0, byteEnd: PIECE - 1, urgency: Urgency.NEAR });
135
- selection.reconcile();
136
- assert.equal(torrent.calls.select.length, 1);
137
-
138
- // Nothing changed: no second instruction.
139
- selection.reconcile();
140
- assert.equal(torrent.calls.select.length, 1);
141
-
142
- // WebTorrent removes a selection once every piece in it has arrived, and says
143
- // nothing about having done so. The window is still wanted.
144
- torrent.forget(0, 0);
145
- selection.reconcile();
146
- assert.equal(torrent.calls.select.length, 2, "stated again, because the library had let it go");
147
- });
148
-
149
- test("a claimant that withdraws takes its instruction with it", () => {
150
- const torrent = stubTorrent();
151
- const register = new DemandRegister();
152
- const selection = new SwarmSelection({ torrent, register });
153
-
154
- register.state({ claimant: "video", fileIndex: 0, byteStart: 0, byteEnd: PIECE - 1, urgency: Urgency.BLOCKED });
155
- selection.reconcile();
156
- register.withdraw("video");
157
- const after = selection.reconcile();
158
-
159
- assert.equal(after.withdrawn, 1);
160
- assert.equal(selection.statedRanges().length, 0);
161
- // And the displacement mark goes with it: WebTorrent never clears it itself,
162
- // so a reader walking a film would leave every piece of it marked.
163
- assert.equal(torrent._critical.some((marked) => marked === true), false);
164
- });
165
-
166
- test("a window is bounded by its own file, so it cannot claim the next one", () => {
167
- const torrent = stubTorrent({ files: 3 });
168
- const register = new DemandRegister();
169
- const selection = new SwarmSelection({ torrent, register });
170
-
171
- // Asking past the end of file 1. File 1 occupies pieces 10-19.
172
- register.state({
173
- claimant: "video", fileIndex: 1, byteStart: 0, byteEnd: 100 * PIECE, urgency: Urgency.NEAR
174
- });
175
- selection.reconcile();
176
-
177
- assert.deepEqual(torrent.calls.select, [{ from: 10, to: 19, priority: 1, isStream: true }]);
178
- });
179
-
180
- test("releasing everything leaves the library holding nothing of ours", () => {
181
- const torrent = stubTorrent();
182
- const register = new DemandRegister();
183
- const selection = new SwarmSelection({ torrent, register });
184
-
185
- register.state({ claimant: "video", fileIndex: 0, byteStart: 0, byteEnd: 3 * PIECE, urgency: Urgency.BLOCKED });
186
- selection.reconcile();
187
- selection.releaseAll();
188
-
189
- assert.equal(torrent._selections._items.length, 0);
190
- assert.equal(selection.statedRanges().length, 0);
191
- assert.equal(torrent._critical.some((marked) => marked === true), false);
192
- });
193
-
194
- test("the store is told what will be read soon, from the same stated needs", () => {
195
- const torrent = stubTorrent();
196
- const protectedBy = new Map();
197
- torrent.store = {
198
- protectRange: (claimant, from, to) => protectedBy.set(claimant, `${from}-${to}`),
199
- releaseProtection: (claimant) => protectedBy.delete(claimant)
200
- };
201
- const register = new DemandRegister();
202
- const selection = new SwarmSelection({ torrent, register, findStore: () => torrent.store });
203
-
204
- register.state({ claimant: "video", fileIndex: 0, byteStart: 0, byteEnd: PIECE - 1, urgency: Urgency.NEAR });
205
- register.state({ claimant: "fill", fileIndex: 0, byteStart: 5 * PIECE, byteEnd: 9 * PIECE - 1, urgency: Urgency.TAIL });
206
- selection.reconcile();
207
-
208
- // One statement, two views of it. Until 2026-09-02 a reader said the same
209
- // thing twice — once to the store for memory, once to the torrent for
210
- // download — and a third piece of code read the first to rebuild the second.
211
- assert.equal(protectedBy.get("video"), "0-0");
212
- // But only the urgent levels: memory holds what will be READ soon, and the
213
- // tail is fetched speculatively. Protecting it would push out a piece the
214
- // decoder is about to want.
215
- assert.equal(protectedBy.has("fill"), false);
216
-
217
- register.withdraw("video");
218
- selection.reconcile();
219
- assert.equal(protectedBy.size, 0, "a reader that withdrew still held memory");
220
- });
@@ -1,167 +0,0 @@
1
- /**
2
- * @file Asking a second wire for the blocks a reader is still waiting on.
3
- *
4
- * Measured on a real swarm 2026-08-19: a blocked read's tail is 2-14 blocks of
5
- * 512, and it sits on wires at 109-886 KB/s — above the 48 KB/s gate that
6
- * WebTorrent's own `_hotswap` requires, so the library never duplicates them.
7
- * The mechanism is the library's: free the reservation with `Piece.cancel` and
8
- * leave the first request in flight, then ask a second wire through the
9
- * library's own request path.
10
- */
11
-
12
- import test from "node:test";
13
- import assert from "node:assert/strict";
14
- import { duplicateTailFor } from "../services/torrent/worker/fastest-wires.js";
15
-
16
- /** A piece with `chunks` blocks, of which `missing` at the end are absent. */
17
- function piece(chunks, missing) {
18
- const buffer = new Array(chunks);
19
- for (let index = 0; index < chunks - missing; index += 1) {
20
- buffer[index] = new Uint8Array(1);
21
- }
22
- const cancelled = [];
23
- return {
24
- _buffer: buffer,
25
- _chunks: chunks,
26
- cancel: (index) => cancelled.push(index),
27
- cancelled
28
- };
29
- }
30
-
31
- function wire({ speed = 500_000, has = true } = {}) {
32
- return {
33
- peerPieces: { get: () => has },
34
- downloadSpeed: () => speed,
35
- peerChoking: false,
36
- destroyed: false,
37
- requests: []
38
- };
39
- }
40
-
41
- test("the missing blocks are freed and asked of a second wire", () => {
42
- const target = piece(512, 3);
43
- const placed = [];
44
- const torrent = {
45
- pieces: [target],
46
- wires: [wire({ speed: 900_000 }), wire({ speed: 800_000 }), wire({ speed: 700_000 })],
47
- _request: (wireAsked, index) => {
48
- placed.push({ wireAsked, index });
49
- return true;
50
- }
51
- };
52
-
53
- const result = duplicateTailFor(torrent, 0);
54
-
55
- assert.equal(result.missing, 3, "the tail is what is still absent from the piece");
56
- assert.equal(result.duplicated, 3);
57
- assert.deepEqual(target.cancelled, [509, 510, 511], "each block's reservation is freed first");
58
- assert.equal(placed.length, 3, "and each is then asked of a wire through the library's own path");
59
- assert.equal(placed[0].wireAsked, torrent.wires[0], "fastest wire first");
60
- });
61
-
62
- test("at most one duplicate per wire, however long the tail", () => {
63
- const target = piece(512, 16);
64
- const torrent = {
65
- pieces: [target],
66
- wires: [wire(), wire()],
67
- _request: () => true
68
- };
69
-
70
- const result = duplicateTailFor(torrent, 0);
71
-
72
- assert.equal(result.duplicated, 2, "two wires, two duplicates — the rest waits for the next attempt");
73
- assert.equal(target.cancelled.length, 2, "and no reservation is freed that nobody was asked for");
74
- });
75
-
76
- test("a piece still arriving normally is not duplicated at all", () => {
77
- // Forty blocks outstanding is not a tail, it is a piece in transit. Asking
78
- // for a second copy of it would spend the shared link on bytes that are
79
- // already on their way — which is the whole difference between this and the
80
- // 2-14 blocks a blocked reader was measured waiting on.
81
- const target = piece(512, 40);
82
- const torrent = {
83
- pieces: [target],
84
- wires: [wire(), wire()],
85
- _request: () => true
86
- };
87
-
88
- const result = duplicateTailFor(torrent, 0);
89
-
90
- assert.equal(result.duplicated, 0);
91
- assert.equal(target.cancelled.length, 0, "and nothing is freed either");
92
- });
93
-
94
- test("a wire whose pipeline is full does not end the attempt", () => {
95
- // Pipelines are per wire, so one wire being full says nothing about the next.
96
- // This used to stop the whole pass on the first refusal, on the stated
97
- // reasoning that the remaining wires were "no emptier" — an assumption about
98
- // other peers' queues that nothing measures.
99
- const target = piece(512, 5);
100
- const torrent = {
101
- pieces: [target],
102
- wires: [wire(), wire(), wire()],
103
- // The library refuses when the wire already has as many requests as its
104
- // measured speed justifies.
105
- _request: () => false
106
- };
107
-
108
- const result = duplicateTailFor(torrent, 0);
109
-
110
- assert.equal(result.duplicated, 0);
111
- assert.equal(
112
- target.cancelled.length,
113
- 3,
114
- "every wire was offered a block; a freed reservation nobody took is handed to whoever asks next"
115
- );
116
- });
117
-
118
- test("a refusal by one wire does not cost the block a faster wire would have taken", () => {
119
- const target = piece(512, 3);
120
- const placed = [];
121
- const torrent = {
122
- pieces: [target],
123
- wires: [wire({ speed: 900_000 }), wire({ speed: 800_000 })],
124
- _request: (wireAsked, index) => {
125
- // The first wire is full; the second is not.
126
- if (wireAsked === torrent.wires[0]) {
127
- return false;
128
- }
129
- placed.push(index);
130
- return true;
131
- }
132
- };
133
-
134
- const result = duplicateTailFor(torrent, 0);
135
-
136
- assert.equal(result.duplicated, 1);
137
- assert.deepEqual(placed, [0], "the second wire was still asked");
138
- });
139
-
140
- test("a piece with nothing missing is left alone", () => {
141
- const target = piece(4, 0);
142
- const torrent = { pieces: [target], wires: [wire()], _request: () => true };
143
-
144
- const result = duplicateTailFor(torrent, 0);
145
-
146
- assert.equal(result.duplicated, 0);
147
- assert.equal(result.missing, 0);
148
- assert.equal(target.cancelled.length, 0);
149
- });
150
-
151
- test("with no wire holding the piece there is nothing to duplicate onto", () => {
152
- const target = piece(8, 2);
153
- const torrent = { pieces: [target], wires: [wire({ has: false })], _request: () => true };
154
-
155
- const result = duplicateTailFor(torrent, 0);
156
-
157
- assert.equal(result.wires, 0);
158
- assert.equal(result.duplicated, 0);
159
- assert.equal(target.cancelled.length, 0, "and no reservation is freed for nobody");
160
- });
161
-
162
- test("a completed piece is not touched", () => {
163
- assert.deepEqual(
164
- duplicateTailFor({ pieces: [null], wires: [wire()], _request: () => true }, 0),
165
- { duplicated: 0, missing: 0, wires: 0 }
166
- );
167
- });