@torrent-tv/proxy 2.89.0 → 2.89.2
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/.claude/commands/opsx/apply.md +155 -155
- package/.claude/commands/opsx/archive.md +160 -160
- package/.claude/commands/opsx/explore.md +174 -174
- package/.claude/commands/opsx/propose.md +109 -109
- package/.claude/commands/opsx/sync.md +143 -143
- package/.claude/skills/openspec-apply-change/SKILL.md +159 -159
- package/.claude/skills/openspec-archive-change/SKILL.md +117 -117
- package/.claude/skills/openspec-explore/SKILL.md +289 -289
- package/.claude/skills/openspec-propose/SKILL.md +113 -113
- package/.claude/skills/openspec-sync-specs/SKILL.md +147 -147
- package/.dockerignore +5 -5
- package/AGENTS.md +6 -1
- package/CHANGELOG.md +15 -0
- package/Dockerfile +30 -30
- package/LICENSE +16 -16
- package/README.md +369 -369
- package/assets/calibration/NOTICE.md +100 -100
- package/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/docs/download-architecture.md +261 -261
- package/docs/encode-architecture.md +729 -729
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/playback-start-forecast.md +42 -8
- package/docs/socks5-egress-research.md +114 -114
- package/docs/storage-architecture.md +267 -267
- package/knip.json +14 -14
- package/openspec/changes/adaptive-bitrate/design.md +92 -92
- package/openspec/changes/adaptive-bitrate/proposal.md +56 -56
- package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +51 -51
- package/openspec/changes/adaptive-bitrate/tasks.md +40 -40
- package/openspec/changes/binary-distribution/.openspec.yaml +2 -2
- package/openspec/changes/binary-distribution/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/design.md +81 -81
- package/openspec/changes/chunked-request-bodies/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +30 -30
- package/openspec/changes/chunked-request-bodies/tasks.md +40 -40
- package/openspec/changes/cold-start/design.md +98 -98
- package/openspec/changes/cold-start/proposal.md +62 -62
- package/openspec/changes/cold-start/specs/cold-start/spec.md +47 -47
- package/openspec/changes/cold-start/tasks.md +48 -48
- package/openspec/changes/disk-cap/.openspec.yaml +2 -2
- package/openspec/changes/disk-cap/proposal.md +37 -37
- package/openspec/changes/disk-cap/specs/disk-cap/spec.md +25 -25
- package/openspec/changes/disk-cap/tasks.md +19 -19
- package/openspec/changes/proxy-observability/.openspec.yaml +2 -2
- package/openspec/changes/proxy-observability/design.md +38 -38
- package/openspec/changes/proxy-observability/proposal.md +49 -49
- package/openspec/changes/proxy-observability/specs/observability/spec.md +33 -33
- package/openspec/changes/proxy-observability/tasks.md +21 -21
- package/openspec/changes/seek-debounce/design.md +118 -118
- package/openspec/changes/seek-debounce/proposal.md +59 -59
- package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +41 -41
- package/openspec/changes/seek-debounce/tasks.md +33 -33
- package/openspec/changes/subtitle-language/.openspec.yaml +2 -2
- package/openspec/changes/subtitle-language/proposal.md +50 -50
- package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +34 -34
- package/openspec/changes/subtitle-language/tasks.md +26 -26
- package/openspec/changes/track-selection/.openspec.yaml +2 -2
- package/openspec/changes/track-selection/design.md +47 -47
- package/openspec/changes/track-selection/proposal.md +45 -45
- package/openspec/changes/track-selection/specs/track-selection/spec.md +41 -41
- package/openspec/changes/track-selection/tasks.md +20 -20
- package/openspec/changes/transcode-quality/.openspec.yaml +2 -2
- package/openspec/changes/transcode-quality/proposal.md +51 -51
- package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +139 -139
- package/openspec/changes/transcode-quality/tasks.md +63 -63
- package/openspec/config.yaml +38 -38
- package/package.json +1 -1
- package/public/.well-known/appspecific/com.chrome.devtools.json +1 -1
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
- package/research/double-spawn-2026-09-10.md +171 -171
- package/research/handover-2026-09-13.md +209 -209
- package/research/handover-reader-claims-removal-2026-09-12.md +166 -166
- package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +175 -175
- package/research/preset-benchmark-size-scaling-2026-08-22.md +55 -55
- package/research/priority-map-is-the-truth-2026-09-12.md +225 -225
- package/routes/api/client-logs/post.js +86 -86
- package/routes/api/delivery-sink/get.js +72 -72
- package/routes/api/link-probe/get.js +51 -51
- package/routes/api/playback-plan/audio-tracks/post.js +29 -29
- package/routes/api/playback-plan/post.js +53 -53
- package/routes/api/sources/post.js +36 -36
- package/routes/api/sources/stats/get.js +132 -132
- package/routes/api/sources/warm/post.js +217 -217
- package/routes/api/subtitles/get.js +230 -230
- package/routes/api/transcode-sessions/fragment-far/post.js +60 -60
- package/routes/api/transcode-sessions/net-report/post.js +138 -138
- package/routes/api/transcode-sessions/post.js +178 -178
- package/routes/api/transcode-sessions/progress/get.js +27 -27
- package/routes/api/transcode-sessions/release/post.js +41 -41
- package/routes/api/transcode-sessions/seek/post.js +63 -63
- package/routes/health/get.js +13 -13
- package/routes/healthz/get.js +13 -13
- package/routes/stream/get.js +380 -380
- package/routes/transcode/audio-file/get.js +64 -64
- package/routes/transcode/audio-warm/get.js +95 -95
- package/routes/transcode/session-file/get.js +52 -52
- package/routes/transcode/variant-file/get.js +95 -95
- package/routes/transcode/variant-warm/get.js +84 -84
- package/scripts/render-run-graph.js +173 -173
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +376 -376
- package/services/encode/EncodeOrchestrator.js +1217 -1217
- package/services/encode/EncodePlan.js +1366 -1366
- package/services/encode/EncodeRun.js +813 -813
- package/services/encode/Encoder.js +142 -142
- package/services/encode/EncoderSelection.js +38 -38
- package/services/encode/LocalObservations.js +359 -359
- package/services/encode/NvencEncoder.js +50 -50
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2282 -2282
- package/services/encode/RunProgress.js +84 -84
- package/services/encode/SegmentDemand.js +95 -95
- package/services/encode/SoftwareEncoder.js +121 -121
- package/services/encode/V4l2m2mEncoder.js +53 -53
- package/services/encode/VaapiEncoder.js +66 -66
- package/services/encode/args.js +418 -418
- package/services/encode/available-share.js +89 -89
- package/services/encode/calibration.js +361 -361
- package/services/encode/contention.js +128 -128
- package/services/encode/decode-cost-fit.js +363 -363
- package/services/encode/encode-exit.js +114 -114
- package/services/encode/encode-run-state.js +551 -551
- package/services/encode/encoder-readings.js +45 -45
- package/services/encode/fingerprint.js +322 -322
- package/services/encode/host-load.js +174 -174
- package/services/encode/index.js +9 -9
- package/services/encode/learned-median.js +90 -90
- package/services/encode/output/EncodedOutput.js +57 -57
- package/services/encode/output/Output.js +131 -131
- package/services/encode/output/OutputCatalog.js +414 -414
- package/services/encode/output/OutputSpec.js +390 -390
- package/services/encode/output/Timeline.js +308 -308
- package/services/encode/output/cut-grid.js +107 -107
- package/services/encode/output/ladder.js +32 -32
- package/services/encode/output/playlists.js +284 -284
- package/services/encode/output/rates.js +125 -125
- package/services/encode/output-key-format.js +21 -21
- package/services/encode/quality/HostLoad.js +525 -525
- package/services/encode/quality/HostTimings.js +249 -249
- package/services/encode/quality/OutputQualityState.js +28 -28
- package/services/encode/quality/QualityController.js +935 -935
- package/services/encode/quality/QualityOffer.js +416 -416
- package/services/encode/quality/drain-threat.js +47 -47
- package/services/encode/quality/link-budget.js +349 -349
- package/services/encode/quality/output-format.js +319 -319
- package/services/encode/quality/serving-output.js +216 -216
- package/services/encode/quality/visible-rung.js +57 -57
- package/services/encode/run-budget.js +97 -97
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +42 -42
- package/services/encode/segment-formats/fmp4.js +476 -458
- package/services/encode/segment-formats/index.js +103 -85
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +604 -541
- package/services/encode/segment-formats/mpegts.js +134 -134
- package/services/encode/start-stop-cost.js +178 -178
- package/services/encode/throughput.js +97 -97
- package/services/encode/torrent-cost.js +88 -88
- package/services/identity/installation-id.js +38 -38
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/ebml-reader.js +159 -159
- package/services/media/ffmpeg-banner.js +260 -260
- package/services/media/keyframe-probe.js +157 -157
- package/services/media/media-info-probe.js +93 -93
- package/services/media/playback-planner.js +931 -931
- package/services/media/tracks/AudioTrack.js +143 -143
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/VideoTrack.js +33 -33
- package/services/media/tracks/audio-bound.js +233 -233
- package/services/media/tracks/index.js +15 -15
- package/services/media/tracks/language-detect.js +174 -174
- package/services/server/OutputLifecycle.js +418 -418
- package/services/server/SegmentServing.js +1150 -1150
- package/services/server/ViewerRequests.js +790 -786
- package/services/server/WaitLedger.js +142 -142
- package/services/server/controllers/PlaybackController.js +40 -40
- package/services/server/controllers/SubtitleController.js +109 -109
- package/services/server/http-responses.js +60 -60
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +1024 -1020
- package/services/storage/Diagnostics.js +202 -202
- package/services/storage/MachineBudget.js +235 -235
- package/services/storage/allowance.js +107 -107
- package/services/storage/core-dumps.js +106 -106
- package/services/storage/files/CompletedFiles.js +391 -391
- package/services/storage/files/piece-from-whole-file.js +118 -118
- package/services/storage/free.js +71 -71
- package/services/storage/keep.js +48 -48
- package/services/storage/machine-memory.js +56 -56
- package/services/storage/memory-report.js +575 -575
- package/services/storage/piece-store/piece-disk-store.js +539 -539
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1255 -1255
- package/services/storage/wire.js +175 -175
- package/services/torrent/demand/DemandRegister.js +202 -202
- package/services/torrent/demand/Urgency.js +137 -137
- package/services/torrent/demand/Window.js +118 -118
- package/services/torrent/demand/index.js +8 -8
- package/services/torrent/demand/pieces.js +140 -140
- package/services/torrent/download/SwarmSelection.js +386 -386
- package/services/torrent/download/registry.js +102 -102
- package/services/torrent/download/withdraw-claim.js +80 -80
- package/services/torrent/files.js +455 -455
- package/services/torrent/naming.js +619 -619
- package/services/torrent/piece-store-of.js +29 -29
- package/services/torrent/supply-margin.js +262 -262
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/client.js +67 -0
- package/services/torrent/worker/fastest-wires.js +342 -342
- package/services/torrent/worker/handle-state.js +23 -23
- package/services/torrent/worker/install-webrtc-shim.js +32 -32
- package/services/torrent/worker/piece-reader.js +1078 -1078
- package/services/torrent/worker/pool-adapter.js +371 -371
- package/services/torrent/worker/protocol.js +184 -184
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/transport/delivery-probe.js +842 -842
- package/services/transport/health-collector.js +53 -53
- package/services/transport/nat-classifier.js +231 -231
- package/services/transport/packet-witness.js +808 -808
- package/services/transport/port-mapper.js +235 -235
- package/services/transport/registry-api.js +81 -81
- package/services/transport/tunnel-client.js +447 -447
- package/services/transport/usrsctp-state.js +175 -175
- package/services/transport/webrtc-manager.js +484 -484
- package/services/viewer/Assignments.js +340 -340
- package/services/viewer/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +691 -691
- package/services/viewer/Viewers.js +368 -368
- package/services/viewer/active-output.js +37 -37
- package/services/viewer/audio-start.js +46 -46
- package/services/viewer/buffer-trend.js +81 -81
- package/services/viewer/choices.js +647 -647
- package/services/viewer/media-time.js +355 -0
- package/services/{server → viewer}/playback-readiness.js +635 -516
- package/services/viewer/positions.js +126 -126
- package/services/viewer/report-intake.js +46 -46
- package/store/source-registry.js +71 -71
- package/test/asset-paths.test.js +28 -28
- package/test/audio-buffer-continuity.test.js +77 -70
- package/test/audio-inventory.test.js +212 -212
- package/test/audio-track-bitrate.test.js +40 -40
- package/test/auto-quality-step.test.js +707 -707
- package/test/available-share.test.js +76 -76
- package/test/background-fill.test.js +190 -190
- package/test/client-log-file.test.js +110 -110
- package/test/completed-files.test.js +115 -115
- package/test/concurrent-cost.test.js +148 -148
- package/test/container-media-info.test.js +228 -228
- package/test/contention.test.js +76 -76
- package/test/core-dumps.test.js +57 -57
- package/test/coverage-follows-the-disk.test.js +228 -228
- package/test/coverage-map.test.js +195 -195
- package/test/cue-push-carries-what-the-walk-found.test.js +58 -58
- package/test/cut-times-timeline.test.js +65 -65
- package/test/cuts-follow-published-grid.test.js +109 -109
- package/test/data-channel-frame.test.js +63 -63
- package/test/data-channel-viewer-lifecycle.test.js +92 -92
- package/test/dead-torrent-handle.test.js +52 -52
- package/test/declared-rates.test.js +116 -116
- package/test/declared-tracks.test.js +43 -43
- package/test/decode-cost-fit.test.js +202 -202
- package/test/decode-cost.test.js +462 -462
- package/test/delivery-probe.test.js +393 -393
- package/test/demand-register.test.js +206 -206
- package/test/dht-bootstrap.test.js +72 -72
- package/test/drain-threat.test.js +65 -65
- package/test/duplicate-add.test.js +48 -48
- package/test/encode-admission.test.js +146 -146
- package/test/encode-cost.test.js +283 -283
- package/test/encode-exit.test.js +111 -111
- package/test/encode-orchestrator.test.js +419 -419
- package/test/encode-plan-viewers.test.js +718 -718
- package/test/encode-plan.test.js +545 -545
- package/test/encode-run-state.test.js +314 -314
- package/test/encode-run.test.js +162 -162
- package/test/encode-slope.test.js +46 -46
- package/test/encoded-output.test.js +39 -39
- package/test/encoder-kinds.test.js +122 -122
- package/test/encoder-readings.test.js +46 -46
- package/test/every-claimant-is-told-its-share.test.js +152 -152
- package/test/failed-encoder-still-serves-what-exists.test.js +354 -354
- package/test/failed-start-breaker.test.js +202 -202
- package/test/fastest-wires.test.js +143 -143
- package/test/ffmpeg-stream-counts.test.js +78 -78
- package/test/file-edges.test.js +191 -191
- package/test/fmp4-presentation-clock.test.js +146 -162
- package/test/fmp4-self-contained.test.js +80 -80
- package/test/fmp4-timestamps.test.js +140 -140
- package/test/fmp4-track-completeness.test.js +41 -41
- package/test/format-keyed-outputs.test.js +78 -78
- package/test/health-metrics.test.js +38 -38
- package/test/held-request-width.test.js +157 -157
- package/test/helpers/encode-run.js +136 -136
- package/test/helpers/manager.js +57 -57
- package/test/helpers/output-spec.js +63 -63
- package/test/host-load.test.js +68 -68
- package/test/index-accuracy-is-about-the-file.test.js +128 -128
- package/test/init-compat.test.js +219 -219
- package/test/input-lost-quiets-the-plan.test.js +261 -261
- package/test/input-unavailable.test.js +48 -48
- package/test/installation-id.test.js +39 -39
- package/test/keeping-period.test.js +83 -83
- package/test/keyframe-index-accuracy.test.js +188 -188
- package/test/keyframes-belong-to-the-file.test.js +249 -249
- package/test/learned-median.test.js +37 -37
- package/test/letting-go-asks-who-is-watching.test.js +191 -191
- package/test/limit-row-by-area.test.js +60 -60
- package/test/link-budget.test.js +112 -112
- package/test/link-probe.test.js +56 -56
- package/test/local-encoding-data.test.js +273 -273
- package/test/logger-repeats.test.js +134 -134
- package/test/look-ahead-coverage.test.js +56 -56
- package/test/manager-surface.test.js +292 -292
- package/test/matroska-cues-track.test.js +192 -192
- package/test/media-time.test.js +62 -0
- package/test/memory-budget.test.js +390 -390
- package/test/move-cost.test.js +216 -216
- package/test/mp4-subtitles.test.js +173 -173
- package/test/one-authority.test.js +285 -285
- package/test/one-budget.test.js +149 -149
- package/test/one-resource-divided.test.js +185 -185
- package/test/one-serving-output-per-height.test.js +458 -458
- package/test/orchestrator-wired.test.js +206 -206
- package/test/output-catalog.test.js +39 -39
- package/test/output-shape.test.js +21 -21
- package/test/output-spec.test.js +251 -251
- package/test/output-speed.test.js +88 -88
- package/test/outputs-being-prepared.test.js +40 -40
- package/test/packet-witness-ring.test.js +236 -236
- package/test/packet-witness.test.js +148 -148
- package/test/piece-disk-store.test.js +401 -401
- package/test/piece-from-whole-file.test.js +129 -129
- package/test/piece-lru.test.js +319 -319
- package/test/piece-reader.test.js +162 -162
- package/test/piece-selection-offset.test.js +85 -85
- package/test/piece-store-eviction.test.js +595 -595
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +403 -403
- package/test/piece-store-slow-disk.test.js +159 -159
- package/test/piece-tail.test.js +97 -97
- package/test/plan-host-timings.test.js +68 -68
- package/test/plan-is-steady.test.js +225 -225
- package/test/playback-readiness.test.js +518 -483
- package/test/playback-start-regressions.test.js +101 -99
- package/test/priority-map-download.test.js +174 -174
- package/test/priority-map-emptied.test.js +204 -204
- package/test/priority-map-per-output.test.js +215 -215
- package/test/priority-map.test.js +163 -163
- package/test/probe-wedge-certainty.test.js +60 -60
- package/test/produced-copy-choice.test.js +213 -213
- package/test/published-timeline.test.js +83 -83
- package/test/quality-offer.test.js +162 -162
- package/test/quality-variants.test.js +1257 -1257
- package/test/rate-control-in-the-output.test.js +180 -180
- package/test/read-survives-withdrawal.test.js +164 -164
- package/test/read-window.test.js +365 -365
- package/test/resume-warm.test.js +39 -39
- package/test/run-graph-drift.test.js +37 -37
- package/test/run-inputs.test.js +80 -80
- package/test/run-intervals.test.js +100 -100
- package/test/run-position-follows-published-grid.test.js +89 -89
- package/test/run-progress.test.js +65 -65
- package/test/same-height-switch.test.js +664 -664
- package/test/seek-frees-the-viewers-requests.test.js +217 -217
- package/test/seek-landing.test.js +109 -109
- package/test/seek-target-not-superseded.test.js +195 -195
- package/test/segment-closed-on-both-branches.test.js +96 -96
- package/test/segment-cut-times.test.js +63 -63
- package/test/segment-demand.test.js +80 -80
- package/test/segment-hold-supersede.test.js +91 -91
- package/test/segment-judged-by-its-own-header.test.js +221 -221
- package/test/segment-store-eviction.test.js +247 -247
- package/test/segment-store.test.js +352 -352
- package/test/segment-true-start.test.js +86 -86
- package/test/segments-are-shared.test.js +176 -176
- package/test/send-chunk-bytes.test.js +86 -86
- package/test/serving-existing-output.test.js +105 -105
- package/test/serving-output.test.js +102 -102
- package/test/shared-piece-store.test.js +310 -310
- package/test/sidecar-naming.test.js +142 -142
- package/test/soundtrack-rate-bound.test.js +289 -289
- package/test/source-file.test.js +133 -133
- package/test/source-registry.test.js +18 -18
- package/test/split-a-stretch.test.js +139 -139
- package/test/stale-request-after-seek.test.js +158 -158
- package/test/startup-readings.test.js +124 -124
- package/test/stream-route.test.js +209 -209
- package/test/subtitle-cue-framing.test.js +200 -200
- package/test/subtitle-cue-source.test.js +102 -102
- package/test/subtitle-cursor.test.js +63 -63
- package/test/subtitle-defaults.test.js +118 -118
- package/test/subtitle-language.test.js +252 -252
- package/test/supply-margin.test.js +177 -177
- package/test/swarm-follows-readers.test.js +174 -174
- package/test/swarm-reach.test.js +130 -130
- package/test/swarm-selection.test.js +220 -220
- package/test/tail-duplication.test.js +167 -167
- package/test/the-channel-has-both-ends.test.js +91 -91
- package/test/the-torrent-thread-serves-bytes.test.js +107 -107
- package/test/thread-pool.test.js +53 -53
- package/test/timeline.test.js +84 -84
- package/test/torrent-contents.test.js +235 -235
- package/test/torrent-cost.test.js +71 -71
- package/test/torrent-files.test.js +221 -221
- package/test/torrent-naming.test.js +255 -255
- package/test/tracks-begin-together.test.js +209 -209
- package/test/tunnel-renewal.test.js +167 -167
- package/test/two-viewers-one-picture.test.js +504 -504
- package/test/upload-hurry.test.js +186 -186
- package/test/usrsctp-state.test.js +131 -131
- package/test/video-facts.test.js +102 -102
- package/test/viewer-assignments.test.js +143 -143
- package/test/viewer-outputs.test.js +285 -285
- package/test/viewer-presence.test.js +116 -116
- package/test/viewer-requests-admission.test.js +89 -89
- package/test/viewer-states.test.js +189 -189
- package/test/viewer-subtitle-subscription.test.js +96 -96
- package/test/viewer.test.js +139 -139
- package/test/viewing-generations.test.js +390 -390
- package/test/visible-rung.test.js +37 -37
- package/test/wait-ledger.test.js +100 -100
- package/test/webrtc-shim.test.js +149 -149
- package/test/wedge-certainty.test.js +131 -131
- package/test/whole-file-held-read.test.js +30 -0
- package/test/withdraw-piece-claim.test.js +212 -212
- package/test/worker-channel.test.js +158 -158
- package/utils/client-log-file.js +151 -151
- package/utils/logger.js +312 -312
- package/utils/parse-range.js +25 -25
- package/utils/perf.js +59 -59
- package/utils/torrent-source-key.js +38 -38
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
# SOCKS5 torrent egress — feasibility research (NOT implemented)
|
|
2
|
-
|
|
3
|
-
Research only (2026-07-10), no code. Question: can the proxy route its
|
|
4
|
-
**torrent** traffic (peers, trackers, DHT) through an owner-configured SOCKS5
|
|
5
|
-
endpoint (e.g. a VPN provider's) to hide the owner's home IP from the swarm,
|
|
6
|
-
while WebRTC to the viewer stays direct? Roadmap item 8 in the root
|
|
7
|
-
`CLAUDE.md`.
|
|
8
|
-
|
|
9
|
-
Stack: WebTorrent 2.8.5, Node. Verified against
|
|
10
|
-
`node_modules/webtorrent/lib/*.js` and `index.js`.
|
|
11
|
-
|
|
12
|
-
## What "torrent traffic" actually is (and which parts can be SOCKS5'd)
|
|
13
|
-
|
|
14
|
-
SOCKS5 forwards a connection through a relay so the destination sees the
|
|
15
|
-
relay's IP, not ours. It supports TCP (CONNECT) and, in theory, UDP (UDP
|
|
16
|
-
ASSOCIATE) — but UDP-ASSOCIATE is rarely usable from Node libraries that bind
|
|
17
|
-
their own UDP sockets. So per traffic type:
|
|
18
|
-
|
|
19
|
-
| Traffic | Transport | Where in WebTorrent | SOCKS5? |
|
|
20
|
-
|---|---|---|---|
|
|
21
|
-
| Peer wire (outgoing) | TCP | `torrent.js` `net.connect(opts)` (~L2112) | **Yes** (TCP CONNECT) |
|
|
22
|
-
| Peer wire (outgoing) | µTP (UDP) | `torrent.js` `utp.connect()` (~L2110) | No (UDP, own socket) → **disable** |
|
|
23
|
-
| HTTP/HTTPS trackers | TCP | `bittorrent-tracker` (HTTP agent) | **Yes** (agent) |
|
|
24
|
-
| WSS trackers | TCP (WebSocket) | `bittorrent-tracker` (ws agent) | **Yes** (agent) |
|
|
25
|
-
| UDP trackers (`udp://`) | UDP | `bittorrent-tracker` | No → **drop** (filter announce list) |
|
|
26
|
-
| DHT | UDP | `bittorrent-dht` | No → **disable** (`dht:false`) |
|
|
27
|
-
| LSD (local peer discovery) | UDP multicast | conn-pool | LAN-only leak → **disable** (`lsd:false`) |
|
|
28
|
-
| Incoming peers | TCP/µTP listener | conn-pool | N/A (inbound; see note) |
|
|
29
|
-
|
|
30
|
-
Conclusion: a clean, leak-free config is **TCP-only through the SOCKS5 relay**
|
|
31
|
-
with every UDP path turned OFF. This is the standard "torrent proxy" model and
|
|
32
|
-
its standard caveat: you lose µTP peers, UDP trackers, and DHT peer discovery —
|
|
33
|
-
peer counts drop, but HTTP/WSS-tracker swarms (like the rutracker trackers our
|
|
34
|
-
test torrents use) still work, and TCP peers are plentiful.
|
|
35
|
-
|
|
36
|
-
WebTorrent already exposes the needed off-switches (verified in `index.js`):
|
|
37
|
-
`utp: false` (L82), `dht: false` (L127), `lsd: false` (L76), `webSeeds`
|
|
38
|
-
(L161). Announce-list filtering to `http(s)://`/`wss://` is ours to do.
|
|
39
|
-
|
|
40
|
-
## The hard part: no dialer hook for TCP peers
|
|
41
|
-
|
|
42
|
-
WebTorrent has **no public option** to supply a custom socket/dialer for
|
|
43
|
-
outgoing peer connections — it calls `net.connect(opts)` directly inside
|
|
44
|
-
`torrent.js`. Three ways around it:
|
|
45
|
-
|
|
46
|
-
### Approach A — userland, scoped monkeypatch of `net.connect` (+ disable UDP)
|
|
47
|
-
Wrap `net.connect` so that for a **peer destination** it returns a socket
|
|
48
|
-
tunneled through SOCKS5 (via the already-present `socks` package —
|
|
49
|
-
`SocksClient.createConnection({ proxy, command:'connect', destination })`),
|
|
50
|
-
and for everything else (our loopback HTTP server, the signalling tunnel,
|
|
51
|
-
ffmpeg's `127.0.0.1` fetch, DNS) it calls the real `net.connect`.
|
|
52
|
-
- The `socks` dep is ALREADY in `node_modules` (currently transitive) — would
|
|
53
|
-
become a direct dependency.
|
|
54
|
-
- Shim detail: `net.connect` returns a socket synchronously and connects
|
|
55
|
-
async; `SocksClient.createConnection` is Promise-based. Need a small adapter
|
|
56
|
-
that returns a `net.Socket`-like duplex immediately and attaches the real
|
|
57
|
-
tunneled socket once the SOCKS handshake resolves (or emits `error`). The
|
|
58
|
-
`socks` package's event API supports this.
|
|
59
|
-
- Scoping rule: SOCKS only for non-local IPv4/IPv6 destinations; NEVER for
|
|
60
|
-
`127.0.0.1`/`::1`/the tunnel host — otherwise we'd route our own control
|
|
61
|
-
plane through the VPN.
|
|
62
|
-
- Trackers/webseeds (HTTP/WSS): pass a `socks-proxy-agent` as the HTTP/ws
|
|
63
|
-
agent to `bittorrent-tracker` / webseed fetches (need to confirm WebTorrent
|
|
64
|
-
threads an `agent` option through; may need a small patch).
|
|
65
|
-
- Pros: no fork, no build-chain changes, deployment-agnostic. Cons: global
|
|
66
|
-
monkeypatch is delicate; must be airtight on the scoping rule.
|
|
67
|
-
|
|
68
|
-
### Approach B — patch/fork WebTorrent to add a `createConnection` hook
|
|
69
|
-
Add an option so `torrent.js` uses `opts.createConnection ?? net.connect`.
|
|
70
|
-
Cleaner and explicit, but a fork to maintain (or an upstream PR — issue
|
|
71
|
-
webtorrent#807 "SOCKS Proxy Support" is open and unresolved, so upstream is
|
|
72
|
-
unlikely soon). More correct long-term if A proves too fragile.
|
|
73
|
-
|
|
74
|
-
### Approach C — network-level (VPN container / namespace), NOT in-proxy
|
|
75
|
-
Run the whole proxy process behind a VPN with a kill-switch (e.g. a `gluetun`
|
|
76
|
-
sidecar container, or a Linux network namespace bound to the VPN). Catches
|
|
77
|
-
ALL traffic — TCP, µTP, DHT, UDP trackers — leak-proof, zero proxy code.
|
|
78
|
-
Trade-off: not per-app (the whole proxy egresses via VPN, including the
|
|
79
|
-
signalling tunnel unless split), heavier ops, and awkward for the HA addon
|
|
80
|
-
(`host_network: true`). This is the ROBUST alternative to document alongside;
|
|
81
|
-
for a bare-Docker/npm operator it may actually be the better answer than
|
|
82
|
-
in-proxy SOCKS5.
|
|
83
|
-
|
|
84
|
-
## Kill-switch (fail-closed) — mandatory for the feature to mean anything
|
|
85
|
-
If the SOCKS5 relay is down, torrent traffic must NOT silently fall back to a
|
|
86
|
-
direct connection (that leaks the exact IP we set out to hide). With Approach
|
|
87
|
-
A this is natural: a failed SOCKS handshake fails the peer connection with no
|
|
88
|
-
direct retry, AND all UDP paths are already disabled, so nothing bypasses the
|
|
89
|
-
relay. Must add a startup connectivity self-test + a clear log line
|
|
90
|
-
(BitPlay's "test proxy" pattern), and surface "proxy down → torrent paused"
|
|
91
|
-
rather than leaking.
|
|
92
|
-
|
|
93
|
-
## Recommended direction (when we build it)
|
|
94
|
-
1. Primary: **Approach A** — `socks` for TCP peers via a scoped `net.connect`
|
|
95
|
-
shim + `socks-proxy-agent` for HTTP/WSS trackers + webseeds, with
|
|
96
|
-
`utp:false`, `dht:false`, `lsd:false`, and announce-list filtered to
|
|
97
|
-
TCP-based trackers. Fail-closed by construction.
|
|
98
|
-
2. Document **Approach C** (VPN container) in the addon/deployment docs as the
|
|
99
|
-
leak-proof alternative for operators who prefer it.
|
|
100
|
-
3. Approach B only if A's monkeypatch proves too fragile in practice.
|
|
101
|
-
|
|
102
|
-
## Open questions to resolve at build time (not now)
|
|
103
|
-
- Does WebTorrent 2.8.x thread an `agent`/proxy option to `bittorrent-tracker`
|
|
104
|
-
and webseed fetches, or is a small patch needed there too?
|
|
105
|
-
- `socks` socket-shim: cleanest way to present a pre-connect `net.Socket`
|
|
106
|
-
(custom Duplex vs a paused real socket) without confusing
|
|
107
|
-
`bittorrent-protocol`'s wire setup.
|
|
108
|
-
- Incoming peers: with UDP off and outbound via SOCKS, do we still accept
|
|
109
|
-
inbound TCP peers directly (that inbound listener exposes the home IP to
|
|
110
|
-
connecting peers)? For full hiding, inbound may need to be disabled too
|
|
111
|
-
(accept that peer discovery becomes outbound-only) — decide per privacy goal.
|
|
112
|
-
- Interaction with the UPnP port mapping / WebRTC UDP port (those are for the
|
|
113
|
-
viewer transport, NOT torrent egress — must stay direct; confirm the shim's
|
|
114
|
-
scoping never touches them).
|
|
1
|
+
# SOCKS5 torrent egress — feasibility research (NOT implemented)
|
|
2
|
+
|
|
3
|
+
Research only (2026-07-10), no code. Question: can the proxy route its
|
|
4
|
+
**torrent** traffic (peers, trackers, DHT) through an owner-configured SOCKS5
|
|
5
|
+
endpoint (e.g. a VPN provider's) to hide the owner's home IP from the swarm,
|
|
6
|
+
while WebRTC to the viewer stays direct? Roadmap item 8 in the root
|
|
7
|
+
`CLAUDE.md`.
|
|
8
|
+
|
|
9
|
+
Stack: WebTorrent 2.8.5, Node. Verified against
|
|
10
|
+
`node_modules/webtorrent/lib/*.js` and `index.js`.
|
|
11
|
+
|
|
12
|
+
## What "torrent traffic" actually is (and which parts can be SOCKS5'd)
|
|
13
|
+
|
|
14
|
+
SOCKS5 forwards a connection through a relay so the destination sees the
|
|
15
|
+
relay's IP, not ours. It supports TCP (CONNECT) and, in theory, UDP (UDP
|
|
16
|
+
ASSOCIATE) — but UDP-ASSOCIATE is rarely usable from Node libraries that bind
|
|
17
|
+
their own UDP sockets. So per traffic type:
|
|
18
|
+
|
|
19
|
+
| Traffic | Transport | Where in WebTorrent | SOCKS5? |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| Peer wire (outgoing) | TCP | `torrent.js` `net.connect(opts)` (~L2112) | **Yes** (TCP CONNECT) |
|
|
22
|
+
| Peer wire (outgoing) | µTP (UDP) | `torrent.js` `utp.connect()` (~L2110) | No (UDP, own socket) → **disable** |
|
|
23
|
+
| HTTP/HTTPS trackers | TCP | `bittorrent-tracker` (HTTP agent) | **Yes** (agent) |
|
|
24
|
+
| WSS trackers | TCP (WebSocket) | `bittorrent-tracker` (ws agent) | **Yes** (agent) |
|
|
25
|
+
| UDP trackers (`udp://`) | UDP | `bittorrent-tracker` | No → **drop** (filter announce list) |
|
|
26
|
+
| DHT | UDP | `bittorrent-dht` | No → **disable** (`dht:false`) |
|
|
27
|
+
| LSD (local peer discovery) | UDP multicast | conn-pool | LAN-only leak → **disable** (`lsd:false`) |
|
|
28
|
+
| Incoming peers | TCP/µTP listener | conn-pool | N/A (inbound; see note) |
|
|
29
|
+
|
|
30
|
+
Conclusion: a clean, leak-free config is **TCP-only through the SOCKS5 relay**
|
|
31
|
+
with every UDP path turned OFF. This is the standard "torrent proxy" model and
|
|
32
|
+
its standard caveat: you lose µTP peers, UDP trackers, and DHT peer discovery —
|
|
33
|
+
peer counts drop, but HTTP/WSS-tracker swarms (like the rutracker trackers our
|
|
34
|
+
test torrents use) still work, and TCP peers are plentiful.
|
|
35
|
+
|
|
36
|
+
WebTorrent already exposes the needed off-switches (verified in `index.js`):
|
|
37
|
+
`utp: false` (L82), `dht: false` (L127), `lsd: false` (L76), `webSeeds`
|
|
38
|
+
(L161). Announce-list filtering to `http(s)://`/`wss://` is ours to do.
|
|
39
|
+
|
|
40
|
+
## The hard part: no dialer hook for TCP peers
|
|
41
|
+
|
|
42
|
+
WebTorrent has **no public option** to supply a custom socket/dialer for
|
|
43
|
+
outgoing peer connections — it calls `net.connect(opts)` directly inside
|
|
44
|
+
`torrent.js`. Three ways around it:
|
|
45
|
+
|
|
46
|
+
### Approach A — userland, scoped monkeypatch of `net.connect` (+ disable UDP)
|
|
47
|
+
Wrap `net.connect` so that for a **peer destination** it returns a socket
|
|
48
|
+
tunneled through SOCKS5 (via the already-present `socks` package —
|
|
49
|
+
`SocksClient.createConnection({ proxy, command:'connect', destination })`),
|
|
50
|
+
and for everything else (our loopback HTTP server, the signalling tunnel,
|
|
51
|
+
ffmpeg's `127.0.0.1` fetch, DNS) it calls the real `net.connect`.
|
|
52
|
+
- The `socks` dep is ALREADY in `node_modules` (currently transitive) — would
|
|
53
|
+
become a direct dependency.
|
|
54
|
+
- Shim detail: `net.connect` returns a socket synchronously and connects
|
|
55
|
+
async; `SocksClient.createConnection` is Promise-based. Need a small adapter
|
|
56
|
+
that returns a `net.Socket`-like duplex immediately and attaches the real
|
|
57
|
+
tunneled socket once the SOCKS handshake resolves (or emits `error`). The
|
|
58
|
+
`socks` package's event API supports this.
|
|
59
|
+
- Scoping rule: SOCKS only for non-local IPv4/IPv6 destinations; NEVER for
|
|
60
|
+
`127.0.0.1`/`::1`/the tunnel host — otherwise we'd route our own control
|
|
61
|
+
plane through the VPN.
|
|
62
|
+
- Trackers/webseeds (HTTP/WSS): pass a `socks-proxy-agent` as the HTTP/ws
|
|
63
|
+
agent to `bittorrent-tracker` / webseed fetches (need to confirm WebTorrent
|
|
64
|
+
threads an `agent` option through; may need a small patch).
|
|
65
|
+
- Pros: no fork, no build-chain changes, deployment-agnostic. Cons: global
|
|
66
|
+
monkeypatch is delicate; must be airtight on the scoping rule.
|
|
67
|
+
|
|
68
|
+
### Approach B — patch/fork WebTorrent to add a `createConnection` hook
|
|
69
|
+
Add an option so `torrent.js` uses `opts.createConnection ?? net.connect`.
|
|
70
|
+
Cleaner and explicit, but a fork to maintain (or an upstream PR — issue
|
|
71
|
+
webtorrent#807 "SOCKS Proxy Support" is open and unresolved, so upstream is
|
|
72
|
+
unlikely soon). More correct long-term if A proves too fragile.
|
|
73
|
+
|
|
74
|
+
### Approach C — network-level (VPN container / namespace), NOT in-proxy
|
|
75
|
+
Run the whole proxy process behind a VPN with a kill-switch (e.g. a `gluetun`
|
|
76
|
+
sidecar container, or a Linux network namespace bound to the VPN). Catches
|
|
77
|
+
ALL traffic — TCP, µTP, DHT, UDP trackers — leak-proof, zero proxy code.
|
|
78
|
+
Trade-off: not per-app (the whole proxy egresses via VPN, including the
|
|
79
|
+
signalling tunnel unless split), heavier ops, and awkward for the HA addon
|
|
80
|
+
(`host_network: true`). This is the ROBUST alternative to document alongside;
|
|
81
|
+
for a bare-Docker/npm operator it may actually be the better answer than
|
|
82
|
+
in-proxy SOCKS5.
|
|
83
|
+
|
|
84
|
+
## Kill-switch (fail-closed) — mandatory for the feature to mean anything
|
|
85
|
+
If the SOCKS5 relay is down, torrent traffic must NOT silently fall back to a
|
|
86
|
+
direct connection (that leaks the exact IP we set out to hide). With Approach
|
|
87
|
+
A this is natural: a failed SOCKS handshake fails the peer connection with no
|
|
88
|
+
direct retry, AND all UDP paths are already disabled, so nothing bypasses the
|
|
89
|
+
relay. Must add a startup connectivity self-test + a clear log line
|
|
90
|
+
(BitPlay's "test proxy" pattern), and surface "proxy down → torrent paused"
|
|
91
|
+
rather than leaking.
|
|
92
|
+
|
|
93
|
+
## Recommended direction (when we build it)
|
|
94
|
+
1. Primary: **Approach A** — `socks` for TCP peers via a scoped `net.connect`
|
|
95
|
+
shim + `socks-proxy-agent` for HTTP/WSS trackers + webseeds, with
|
|
96
|
+
`utp:false`, `dht:false`, `lsd:false`, and announce-list filtered to
|
|
97
|
+
TCP-based trackers. Fail-closed by construction.
|
|
98
|
+
2. Document **Approach C** (VPN container) in the addon/deployment docs as the
|
|
99
|
+
leak-proof alternative for operators who prefer it.
|
|
100
|
+
3. Approach B only if A's monkeypatch proves too fragile in practice.
|
|
101
|
+
|
|
102
|
+
## Open questions to resolve at build time (not now)
|
|
103
|
+
- Does WebTorrent 2.8.x thread an `agent`/proxy option to `bittorrent-tracker`
|
|
104
|
+
and webseed fetches, or is a small patch needed there too?
|
|
105
|
+
- `socks` socket-shim: cleanest way to present a pre-connect `net.Socket`
|
|
106
|
+
(custom Duplex vs a paused real socket) without confusing
|
|
107
|
+
`bittorrent-protocol`'s wire setup.
|
|
108
|
+
- Incoming peers: with UDP off and outbound via SOCKS, do we still accept
|
|
109
|
+
inbound TCP peers directly (that inbound listener exposes the home IP to
|
|
110
|
+
connecting peers)? For full hiding, inbound may need to be disabled too
|
|
111
|
+
(accept that peer discovery becomes outbound-only) — decide per privacy goal.
|
|
112
|
+
- Interaction with the UPnP port mapping / WebRTC UDP port (those are for the
|
|
113
|
+
viewer transport, NOT torrent egress — must stay direct; confirm the shim's
|
|
114
|
+
scoping never touches them).
|