@torrent-tv/proxy 2.89.17 → 2.89.19

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 (300) hide show
  1. package/README.md +14 -3
  2. package/package.json +10 -2
  3. package/server.js +0 -11
  4. package/utils/client-log-file.js +147 -42
  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 -2377
  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 -151
  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/client-logs-route.test.js +0 -34
  97. package/test/coalesce.test.js +0 -39
  98. package/test/completed-files.test.js +0 -115
  99. package/test/concurrent-cost.test.js +0 -148
  100. package/test/container-media-info.test.js +0 -228
  101. package/test/contention.test.js +0 -76
  102. package/test/core-dumps.test.js +0 -57
  103. package/test/coverage-follows-the-disk.test.js +0 -233
  104. package/test/coverage-map.test.js +0 -195
  105. package/test/cue-push-carries-what-the-walk-found.test.js +0 -58
  106. package/test/cut-times-timeline.test.js +0 -65
  107. package/test/cuts-follow-published-grid.test.js +0 -109
  108. package/test/data-channel-frame.test.js +0 -63
  109. package/test/data-channel-viewer-lifecycle.test.js +0 -193
  110. package/test/dead-torrent-handle.test.js +0 -52
  111. package/test/declared-rates.test.js +0 -116
  112. package/test/declared-tracks.test.js +0 -43
  113. package/test/decode-cost-fit.test.js +0 -202
  114. package/test/decode-cost.test.js +0 -475
  115. package/test/delivery-probe-field.test.js +0 -173
  116. package/test/delivery-probe.test.js +0 -382
  117. package/test/demand-register.test.js +0 -206
  118. package/test/dht-bootstrap.test.js +0 -72
  119. package/test/drain-threat.test.js +0 -65
  120. package/test/duplicate-add.test.js +0 -48
  121. package/test/encode-admission.test.js +0 -170
  122. package/test/encode-cost.test.js +0 -275
  123. package/test/encode-exit.test.js +0 -111
  124. package/test/encode-orchestrator.test.js +0 -426
  125. package/test/encode-plan-viewers.test.js +0 -725
  126. package/test/encode-plan.test.js +0 -545
  127. package/test/encode-run-state.test.js +0 -314
  128. package/test/encode-run.test.js +0 -248
  129. package/test/encode-slope.test.js +0 -46
  130. package/test/encoded-output.test.js +0 -39
  131. package/test/encoder-kinds.test.js +0 -122
  132. package/test/encoder-readings.test.js +0 -68
  133. package/test/episode-naming.test.js +0 -131
  134. package/test/every-claimant-is-told-its-share.test.js +0 -152
  135. package/test/failed-encoder-still-serves-what-exists.test.js +0 -354
  136. package/test/failed-start-breaker.test.js +0 -202
  137. package/test/fastest-wires.test.js +0 -143
  138. package/test/ffmpeg-stream-counts.test.js +0 -101
  139. package/test/file-edges.test.js +0 -191
  140. package/test/files-in-use.test.js +0 -34
  141. package/test/fixtures/audio-frame-boundary.json +0 -1
  142. package/test/fixtures/refused-audio-20261002.json +0 -10
  143. package/test/fmp4-presentation-clock.test.js +0 -148
  144. package/test/fmp4-self-contained.test.js +0 -80
  145. package/test/fmp4-timestamps.test.js +0 -140
  146. package/test/fmp4-track-completeness.test.js +0 -41
  147. package/test/format-keyed-outputs.test.js +0 -78
  148. package/test/health-metrics.test.js +0 -36
  149. package/test/held-read-ownership.test.js +0 -77
  150. package/test/held-request-width.test.js +0 -157
  151. package/test/helpers/encode-run.js +0 -248
  152. package/test/helpers/held-file.js +0 -77
  153. package/test/helpers/manager.js +0 -62
  154. package/test/helpers/matroska-file.js +0 -296
  155. package/test/helpers/output-spec.js +0 -63
  156. package/test/host-load.test.js +0 -68
  157. package/test/index-accuracy-is-about-the-file.test.js +0 -128
  158. package/test/init-compat.test.js +0 -219
  159. package/test/input-lost-quiets-the-plan.test.js +0 -261
  160. package/test/input-unavailable.test.js +0 -48
  161. package/test/installation-id.test.js +0 -39
  162. package/test/keeping-period.test.js +0 -83
  163. package/test/keyframe-index-accuracy.test.js +0 -188
  164. package/test/keyframe-table-budget.test.js +0 -123
  165. package/test/keyframes-belong-to-the-file.test.js +0 -249
  166. package/test/learned-median.test.js +0 -37
  167. package/test/letting-go-asks-who-is-watching.test.js +0 -191
  168. package/test/limit-row-by-area.test.js +0 -60
  169. package/test/link-budget.test.js +0 -111
  170. package/test/link-probe.test.js +0 -56
  171. package/test/local-encoding-data.test.js +0 -273
  172. package/test/logger-repeats.test.js +0 -134
  173. package/test/look-ahead-coverage.test.js +0 -56
  174. package/test/manager-surface.test.js +0 -291
  175. package/test/matroska-blocks.test.js +0 -0
  176. package/test/matroska-cluster-discovery.test.js +0 -323
  177. package/test/matroska-cues-track.test.js +0 -192
  178. package/test/media-time.test.js +0 -62
  179. package/test/memory-budget.test.js +0 -390
  180. package/test/move-cost.test.js +0 -216
  181. package/test/mp4-composition-times.test.js +0 -0
  182. package/test/mp4-subtitles.test.js +0 -173
  183. package/test/one-authority.test.js +0 -285
  184. package/test/one-budget.test.js +0 -149
  185. package/test/one-output-one-session.test.js +0 -273
  186. package/test/one-resource-divided.test.js +0 -185
  187. package/test/one-serving-output-per-height.test.js +0 -458
  188. package/test/orchestrator-wired.test.js +0 -207
  189. package/test/output-catalog.test.js +0 -39
  190. package/test/output-shape.test.js +0 -21
  191. package/test/output-spec.test.js +0 -251
  192. package/test/output-speed.test.js +0 -92
  193. package/test/outputs-being-prepared.test.js +0 -40
  194. package/test/packet-witness-ring.test.js +0 -236
  195. package/test/packet-witness.test.js +0 -148
  196. package/test/piece-completeness.test.js +0 -78
  197. package/test/piece-disk-store.test.js +0 -401
  198. package/test/piece-from-whole-file.test.js +0 -129
  199. package/test/piece-lru.test.js +0 -319
  200. package/test/piece-reader.test.js +0 -162
  201. package/test/piece-selection-offset.test.js +0 -85
  202. package/test/piece-store-eviction.test.js +0 -595
  203. package/test/piece-store-never-refuses.test.js +0 -153
  204. package/test/piece-store-reservations.test.js +0 -403
  205. package/test/piece-store-slow-disk.test.js +0 -159
  206. package/test/piece-tail.test.js +0 -97
  207. package/test/plan-host-timings.test.js +0 -68
  208. package/test/plan-is-steady.test.js +0 -225
  209. package/test/playback-readiness.test.js +0 -518
  210. package/test/playback-start-regressions.test.js +0 -101
  211. package/test/priority-map-download.test.js +0 -174
  212. package/test/priority-map-emptied.test.js +0 -204
  213. package/test/priority-map-per-output.test.js +0 -215
  214. package/test/priority-map.test.js +0 -163
  215. package/test/probe-wedge-certainty.test.js +0 -60
  216. package/test/produced-copy-choice.test.js +0 -213
  217. package/test/published-timeline.test.js +0 -83
  218. package/test/quality-offer.test.js +0 -160
  219. package/test/quality-variants.test.js +0 -1274
  220. package/test/rate-control-in-the-output.test.js +0 -181
  221. package/test/read-survives-withdrawal.test.js +0 -164
  222. package/test/read-window.test.js +0 -365
  223. package/test/reads-not-yet-arrived.test.js +0 -218
  224. package/test/resume-warm.test.js +0 -39
  225. package/test/run-graph-drift.test.js +0 -37
  226. package/test/run-inputs.test.js +0 -80
  227. package/test/run-intervals.test.js +0 -100
  228. package/test/run-position-follows-published-grid.test.js +0 -89
  229. package/test/run-progress.test.js +0 -64
  230. package/test/same-height-switch.test.js +0 -663
  231. package/test/seek-frees-the-viewers-requests.test.js +0 -217
  232. package/test/seek-landing.test.js +0 -109
  233. package/test/seek-target-not-superseded.test.js +0 -195
  234. package/test/segment-closed-on-both-branches.test.js +0 -114
  235. package/test/segment-cut-times.test.js +0 -63
  236. package/test/segment-demand.test.js +0 -80
  237. package/test/segment-hold-supersede.test.js +0 -91
  238. package/test/segment-judged-by-its-own-header.test.js +0 -222
  239. package/test/segment-serve-wiring.test.js +0 -481
  240. package/test/segment-store-eviction.test.js +0 -247
  241. package/test/segment-store.test.js +0 -352
  242. package/test/segment-true-start.test.js +0 -86
  243. package/test/segments-are-shared.test.js +0 -176
  244. package/test/send-chunk-bytes.test.js +0 -86
  245. package/test/serving-existing-output.test.js +0 -111
  246. package/test/serving-output.test.js +0 -102
  247. package/test/shared-piece-store.test.js +0 -310
  248. package/test/sidecar-naming.test.js +0 -142
  249. package/test/soundtrack-rate-bound.test.js +0 -289
  250. package/test/source-file.test.js +0 -133
  251. package/test/source-files-route.test.js +0 -113
  252. package/test/source-registry.test.js +0 -18
  253. package/test/split-a-stretch.test.js +0 -139
  254. package/test/stale-request-after-seek.test.js +0 -158
  255. package/test/startup-readings.test.js +0 -124
  256. package/test/stream-route.test.js +0 -237
  257. package/test/subtitle-cue-framing.test.js +0 -200
  258. package/test/subtitle-cue-source.test.js +0 -102
  259. package/test/subtitle-cue-walk.test.js +0 -385
  260. package/test/subtitle-cursor.test.js +0 -63
  261. package/test/subtitle-defaults.test.js +0 -118
  262. package/test/subtitle-language.test.js +0 -252
  263. package/test/subtitle-metadata.test.js +0 -15
  264. package/test/subtitle-plan-waits.test.js +0 -125
  265. package/test/subtitle-track-numbering.test.js +0 -391
  266. package/test/supply-margin.test.js +0 -190
  267. package/test/swarm-follows-readers.test.js +0 -174
  268. package/test/swarm-reach.test.js +0 -130
  269. package/test/swarm-selection.test.js +0 -220
  270. package/test/tail-duplication.test.js +0 -167
  271. package/test/the-channel-has-both-ends.test.js +0 -91
  272. package/test/the-torrent-thread-serves-bytes.test.js +0 -107
  273. package/test/thread-pool.test.js +0 -53
  274. package/test/timeline.test.js +0 -84
  275. package/test/torrent-contents.test.js +0 -235
  276. package/test/torrent-cost.test.js +0 -71
  277. package/test/torrent-files.test.js +0 -221
  278. package/test/torrent-naming.test.js +0 -255
  279. package/test/tracks-begin-together.test.js +0 -209
  280. package/test/tunnel-renewal.test.js +0 -167
  281. package/test/two-viewers-one-picture.test.js +0 -508
  282. package/test/upload-hurry.test.js +0 -186
  283. package/test/usrsctp-state.test.js +0 -131
  284. package/test/video-facts.test.js +0 -102
  285. package/test/viewer-assignments.test.js +0 -143
  286. package/test/viewer-outputs.test.js +0 -285
  287. package/test/viewer-presence.test.js +0 -116
  288. package/test/viewer-requests-admission.test.js +0 -92
  289. package/test/viewer-starts-on-map.test.js +0 -27
  290. package/test/viewer-states.test.js +0 -192
  291. package/test/viewer-subtitle-subscription.test.js +0 -96
  292. package/test/viewer.test.js +0 -139
  293. package/test/viewing-generations.test.js +0 -390
  294. package/test/visible-rung.test.js +0 -37
  295. package/test/wait-ledger.test.js +0 -100
  296. package/test/webrtc-shim.test.js +0 -149
  297. package/test/wedge-certainty.test.js +0 -131
  298. package/test/whole-file-held-read.test.js +0 -30
  299. package/test/withdraw-piece-claim.test.js +0 -212
  300. 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
- });