@torrent-tv/proxy 2.89.5 → 2.89.7
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 +338 -338
- package/CHANGELOG.md +16 -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 +765 -765
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/playback-start-forecast.md +17 -2
- package/docs/socks5-egress-research.md +114 -114
- package/docs/storage-architecture.md +273 -273
- 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 +416 -416
- 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/server.js +748 -748
- 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 +1192 -1192
- package/services/encode/EncodePlan.js +1368 -1368
- package/services/encode/EncodeRun.js +32 -4
- package/services/encode/EncodeRuns.js +19 -7
- 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 +2283 -2283
- package/services/encode/RunClock.js +87 -87
- package/services/encode/RunProgress.js +79 -79
- 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 +116 -114
- package/services/encode/encode-run-state.js +551 -551
- package/services/encode/encoder-readings.js +43 -43
- 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/piece-completeness.js +60 -2
- package/services/encode/quality/EncodeCost.js +1043 -1043
- package/services/encode/quality/HostLoad.js +525 -525
- package/services/encode/quality/HostTimings.js +249 -249
- package/services/encode/quality/OutputQualityState.js +24 -24
- package/services/encode/quality/QualityController.js +758 -758
- package/services/encode/quality/QualityOffer.js +416 -416
- package/services/encode/quality/drain-threat.js +47 -47
- package/services/encode/quality/link-budget.js +337 -337
- 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-command.js +3 -1
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +53 -53
- package/services/encode/segment-formats/fmp4.js +476 -476
- package/services/encode/segment-formats/index.js +1 -0
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +8 -1
- 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 +1152 -1152
- package/services/server/ViewerRequests.js +785 -785
- 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 +1029 -1029
- 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/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 +264 -264
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- 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 -355
- package/services/viewer/playback-readiness.js +635 -635
- 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 -77
- package/test/audio-inventory.test.js +212 -212
- package/test/audio-track-bitrate.test.js +40 -40
- package/test/auto-quality-step.test.js +762 -753
- 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 +475 -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 +275 -275
- package/test/encode-exit.test.js +111 -111
- package/test/encode-orchestrator.test.js +426 -426
- package/test/encode-plan-viewers.test.js +725 -725
- package/test/encode-plan.test.js +545 -545
- package/test/encode-run-state.test.js +314 -314
- package/test/encode-run.test.js +38 -0
- 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 +68 -68
- 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/fixtures/refused-audio-20261002.json +10 -0
- package/test/fmp4-presentation-clock.test.js +4 -2
- 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 +240 -240
- package/test/helpers/manager.js +62 -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 +111 -111
- 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 +291 -291
- package/test/matroska-cues-track.test.js +192 -192
- package/test/media-time.test.js +62 -62
- 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 +207 -207
- 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 +87 -87
- 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-completeness.test.js +37 -1
- 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 -518
- package/test/playback-start-regressions.test.js +101 -101
- 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 +160 -160
- package/test/quality-variants.test.js +1270 -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 +64 -64
- package/test/same-height-switch.test.js +663 -663
- 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-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 +222 -221
- package/test/segment-serve-wiring.test.js +9 -3
- 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 +111 -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 +237 -237
- 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 +190 -190
- 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 +508 -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 +92 -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/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,219 +1,219 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file `webrtc-polyfill`'s interface, served by a pure-JavaScript WebRTC stack.
|
|
3
|
-
*
|
|
4
|
-
* **Why this exists.** node-datachannel is native, and its native side is not
|
|
5
|
-
* safe to use from two V8 isolates at once: the process aborts with
|
|
6
|
-
* `HandleScope: Entering the V8 API without proper locking in place`. Measured
|
|
7
|
-
* on win32/x64 and linux/arm64 alike — one isolate is fine (either the main
|
|
8
|
-
* thread or a worker), two at the same time is fatal, and preloading in both
|
|
9
|
-
* does not help. The upstream issue about workers (#129) was closed in 0.4.0
|
|
10
|
-
* but only covers use from a worker ALONE, which does work.
|
|
11
|
-
*
|
|
12
|
-
* We need it twice: `transport/webrtc-manager.js` runs the video channel to the browser on
|
|
13
|
-
* the main thread, and the torrent client — which is now on its own thread —
|
|
14
|
-
* creates peer connections of its own to announce on `wss://` trackers. Before
|
|
15
|
-
* the thread split both lived in one isolate and nothing was wrong; afterwards
|
|
16
|
-
* any torrent carrying a wss tracker took the whole proxy down.
|
|
17
|
-
*
|
|
18
|
-
* So the native stack stays where it earns its keep — the main thread, carrying
|
|
19
|
-
* video — and the torrent's trackers get a JavaScript implementation, where the
|
|
20
|
-
* traffic is a handful of signalling messages. `services/torrent/worker/worker.js`
|
|
21
|
-
* installs a module resolution hook that points `webrtc-polyfill` here; nothing
|
|
22
|
-
* outside the worker thread is affected, and no dependency is patched (the addon
|
|
23
|
-
* installs with `--ignore-scripts`, so a postinstall patch would never run).
|
|
24
|
-
*
|
|
25
|
-
* **What the shim has to fix.** werift's peer connection matches the interface
|
|
26
|
-
* `simple-peer` expects, but its data channel differs in two ways that matter:
|
|
27
|
-
*
|
|
28
|
-
* - it has no `binaryType`, and hands `onmessage` a `Buffer`. `simple-peer`
|
|
29
|
-
* only recognises `ArrayBuffer`; anything else goes through `text2arr`,
|
|
30
|
-
* which would corrupt every byte of torrent payload.
|
|
31
|
-
* - it has no `onbufferedamountlow`. `simple-peer` builds its backpressure on
|
|
32
|
-
* that event, so without it a send that hits the high-water mark never
|
|
33
|
-
* resumes.
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
import {
|
|
37
|
-
RTCPeerConnection as WeriftPeerConnection,
|
|
38
|
-
RTCIceCandidate
|
|
39
|
-
} from "werift";
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* A session description built the way browsers build it — from one object.
|
|
43
|
-
*
|
|
44
|
-
* werift's own class takes two positional arguments, `(sdp, type)`. Callers
|
|
45
|
-
* written against the browser pass `{ type, sdp }`, so with werift's class the
|
|
46
|
-
* type lands in the sdp slot and the description is rejected: field 2026-08-03,
|
|
47
|
-
* wss announces succeeded but every peer connection died with "Connection
|
|
48
|
-
* error: invalid sessionDescription".
|
|
49
|
-
*
|
|
50
|
-
* `RTCIceCandidate` needs no such treatment — werift already takes an object.
|
|
51
|
-
*/
|
|
52
|
-
class ShimSessionDescription {
|
|
53
|
-
/**
|
|
54
|
-
* @param {{ type?: string, sdp?: string } | string} init
|
|
55
|
-
* @param {string} [type] - Tolerated for callers using werift's own order.
|
|
56
|
-
*/
|
|
57
|
-
constructor(init, type) {
|
|
58
|
-
if (typeof init === "string") {
|
|
59
|
-
this.sdp = init;
|
|
60
|
-
this.type = type;
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
this.type = init?.type;
|
|
64
|
-
this.sdp = init?.sdp;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
toJSON() {
|
|
68
|
-
return { type: this.type, sdp: this.sdp };
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* A werift data channel wearing the browser's interface.
|
|
74
|
-
*
|
|
75
|
-
* Only what `simple-peer` touches is implemented — promising more would be
|
|
76
|
-
* pretending, since anything else has no caller and would never be exercised.
|
|
77
|
-
*/
|
|
78
|
-
class ShimDataChannel {
|
|
79
|
-
#channel;
|
|
80
|
-
/** @type {"blob" | "arraybuffer"} */
|
|
81
|
-
binaryType = "blob";
|
|
82
|
-
/** @type {((event: { data: ArrayBuffer | Buffer }) => void) | null} */
|
|
83
|
-
onmessage = null;
|
|
84
|
-
/** @type {(() => void) | null} */
|
|
85
|
-
onopen = null;
|
|
86
|
-
/** @type {(() => void) | null} */
|
|
87
|
-
onclose = null;
|
|
88
|
-
/** @type {((event: { error?: Error }) => void) | null} */
|
|
89
|
-
onerror = null;
|
|
90
|
-
/** @type {(() => void) | null} */
|
|
91
|
-
onbufferedamountlow = null;
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* @param {object} channel - werift's `RTCDataChannel`.
|
|
95
|
-
*/
|
|
96
|
-
constructor(channel) {
|
|
97
|
-
this.#channel = channel;
|
|
98
|
-
|
|
99
|
-
channel.onmessage = (event) => {
|
|
100
|
-
const data = event?.data ?? event;
|
|
101
|
-
this.onmessage?.({ data: this.#toWireFormat(data) });
|
|
102
|
-
};
|
|
103
|
-
channel.onopen = () => this.onopen?.();
|
|
104
|
-
channel.onclose = () => this.onclose?.();
|
|
105
|
-
channel.onerror = (event) => this.onerror?.(event ?? {});
|
|
106
|
-
|
|
107
|
-
// werift reports this as an observable rather than a handler property.
|
|
108
|
-
channel.bufferedAmountLow?.subscribe?.(() => this.onbufferedamountlow?.());
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Match what a browser would deliver for the requested `binaryType`.
|
|
113
|
-
*
|
|
114
|
-
* `simple-peer` checks `instanceof ArrayBuffer` and sends everything else
|
|
115
|
-
* through a text decoder, so a `Buffer` handed over unchanged arrives
|
|
116
|
-
* mangled.
|
|
117
|
-
*
|
|
118
|
-
* @param {unknown} data
|
|
119
|
-
* @returns {ArrayBuffer | unknown}
|
|
120
|
-
*/
|
|
121
|
-
#toWireFormat(data) {
|
|
122
|
-
if (this.binaryType !== "arraybuffer" || typeof data === "string") {
|
|
123
|
-
return data;
|
|
124
|
-
}
|
|
125
|
-
if (ArrayBuffer.isView(data)) {
|
|
126
|
-
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
127
|
-
}
|
|
128
|
-
return data;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
get label() {
|
|
132
|
-
return this.#channel.label;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
get readyState() {
|
|
136
|
-
return this.#channel.readyState;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
get bufferedAmount() {
|
|
140
|
-
return this.#channel.bufferedAmount;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
get bufferedAmountLowThreshold() {
|
|
144
|
-
return this.#channel.bufferedAmountLowThreshold;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
set bufferedAmountLowThreshold(value) {
|
|
148
|
-
this.#channel.bufferedAmountLowThreshold = value;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* @param {string | ArrayBuffer | ArrayBufferView} data
|
|
153
|
-
* @returns {void}
|
|
154
|
-
*/
|
|
155
|
-
send(data) {
|
|
156
|
-
if (typeof data === "string") {
|
|
157
|
-
this.#channel.send(data);
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
this.#channel.send(ArrayBuffer.isView(data) ? Buffer.from(data.buffer, data.byteOffset, data.byteLength) : Buffer.from(data));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
close() {
|
|
164
|
-
this.#channel.close();
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* werift's peer connection, handing out data channels that carry the browser's
|
|
170
|
-
* interface. Everything else is inherited unchanged — the peer connection side
|
|
171
|
-
* already matches.
|
|
172
|
-
*/
|
|
173
|
-
class ShimPeerConnection extends WeriftPeerConnection {
|
|
174
|
-
/**
|
|
175
|
-
* @param {...unknown} args - Passed through to werift.
|
|
176
|
-
*/
|
|
177
|
-
constructor(...args) {
|
|
178
|
-
super(...args);
|
|
179
|
-
|
|
180
|
-
// `ondatachannel` has to be redefined on the instance, not declared as an
|
|
181
|
-
// accessor on this class: werift assigns it as an own field in its own
|
|
182
|
-
// constructor, and an own property shadows a prototype accessor — so a
|
|
183
|
-
// subclass setter is simply never called, and the caller receives werift's
|
|
184
|
-
// bare channel instead of the wrapped one.
|
|
185
|
-
let handler = null;
|
|
186
|
-
const deliver = (event) => {
|
|
187
|
-
handler?.({ ...event, channel: new ShimDataChannel(event?.channel ?? event) });
|
|
188
|
-
};
|
|
189
|
-
Object.defineProperty(this, "ondatachannel", {
|
|
190
|
-
configurable: true,
|
|
191
|
-
// werift reads this property to dispatch, so it must hand back the
|
|
192
|
-
// wrapper rather than what the caller assigned.
|
|
193
|
-
get: () => (handler ? deliver : null),
|
|
194
|
-
set: (value) => {
|
|
195
|
-
handler = value;
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* @param {string} label
|
|
202
|
-
* @param {object} [options]
|
|
203
|
-
* @returns {ShimDataChannel}
|
|
204
|
-
*/
|
|
205
|
-
createDataChannel(label, options) {
|
|
206
|
-
return new ShimDataChannel(super.createDataChannel(label, options));
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export {
|
|
211
|
-
ShimPeerConnection as RTCPeerConnection,
|
|
212
|
-
ShimSessionDescription as RTCSessionDescription,
|
|
213
|
-
RTCIceCandidate
|
|
214
|
-
};
|
|
215
|
-
export default {
|
|
216
|
-
RTCPeerConnection: ShimPeerConnection,
|
|
217
|
-
RTCSessionDescription: ShimSessionDescription,
|
|
218
|
-
RTCIceCandidate
|
|
219
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* @file `webrtc-polyfill`'s interface, served by a pure-JavaScript WebRTC stack.
|
|
3
|
+
*
|
|
4
|
+
* **Why this exists.** node-datachannel is native, and its native side is not
|
|
5
|
+
* safe to use from two V8 isolates at once: the process aborts with
|
|
6
|
+
* `HandleScope: Entering the V8 API without proper locking in place`. Measured
|
|
7
|
+
* on win32/x64 and linux/arm64 alike — one isolate is fine (either the main
|
|
8
|
+
* thread or a worker), two at the same time is fatal, and preloading in both
|
|
9
|
+
* does not help. The upstream issue about workers (#129) was closed in 0.4.0
|
|
10
|
+
* but only covers use from a worker ALONE, which does work.
|
|
11
|
+
*
|
|
12
|
+
* We need it twice: `transport/webrtc-manager.js` runs the video channel to the browser on
|
|
13
|
+
* the main thread, and the torrent client — which is now on its own thread —
|
|
14
|
+
* creates peer connections of its own to announce on `wss://` trackers. Before
|
|
15
|
+
* the thread split both lived in one isolate and nothing was wrong; afterwards
|
|
16
|
+
* any torrent carrying a wss tracker took the whole proxy down.
|
|
17
|
+
*
|
|
18
|
+
* So the native stack stays where it earns its keep — the main thread, carrying
|
|
19
|
+
* video — and the torrent's trackers get a JavaScript implementation, where the
|
|
20
|
+
* traffic is a handful of signalling messages. `services/torrent/worker/worker.js`
|
|
21
|
+
* installs a module resolution hook that points `webrtc-polyfill` here; nothing
|
|
22
|
+
* outside the worker thread is affected, and no dependency is patched (the addon
|
|
23
|
+
* installs with `--ignore-scripts`, so a postinstall patch would never run).
|
|
24
|
+
*
|
|
25
|
+
* **What the shim has to fix.** werift's peer connection matches the interface
|
|
26
|
+
* `simple-peer` expects, but its data channel differs in two ways that matter:
|
|
27
|
+
*
|
|
28
|
+
* - it has no `binaryType`, and hands `onmessage` a `Buffer`. `simple-peer`
|
|
29
|
+
* only recognises `ArrayBuffer`; anything else goes through `text2arr`,
|
|
30
|
+
* which would corrupt every byte of torrent payload.
|
|
31
|
+
* - it has no `onbufferedamountlow`. `simple-peer` builds its backpressure on
|
|
32
|
+
* that event, so without it a send that hits the high-water mark never
|
|
33
|
+
* resumes.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import {
|
|
37
|
+
RTCPeerConnection as WeriftPeerConnection,
|
|
38
|
+
RTCIceCandidate
|
|
39
|
+
} from "werift";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A session description built the way browsers build it — from one object.
|
|
43
|
+
*
|
|
44
|
+
* werift's own class takes two positional arguments, `(sdp, type)`. Callers
|
|
45
|
+
* written against the browser pass `{ type, sdp }`, so with werift's class the
|
|
46
|
+
* type lands in the sdp slot and the description is rejected: field 2026-08-03,
|
|
47
|
+
* wss announces succeeded but every peer connection died with "Connection
|
|
48
|
+
* error: invalid sessionDescription".
|
|
49
|
+
*
|
|
50
|
+
* `RTCIceCandidate` needs no such treatment — werift already takes an object.
|
|
51
|
+
*/
|
|
52
|
+
class ShimSessionDescription {
|
|
53
|
+
/**
|
|
54
|
+
* @param {{ type?: string, sdp?: string } | string} init
|
|
55
|
+
* @param {string} [type] - Tolerated for callers using werift's own order.
|
|
56
|
+
*/
|
|
57
|
+
constructor(init, type) {
|
|
58
|
+
if (typeof init === "string") {
|
|
59
|
+
this.sdp = init;
|
|
60
|
+
this.type = type;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
this.type = init?.type;
|
|
64
|
+
this.sdp = init?.sdp;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
toJSON() {
|
|
68
|
+
return { type: this.type, sdp: this.sdp };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A werift data channel wearing the browser's interface.
|
|
74
|
+
*
|
|
75
|
+
* Only what `simple-peer` touches is implemented — promising more would be
|
|
76
|
+
* pretending, since anything else has no caller and would never be exercised.
|
|
77
|
+
*/
|
|
78
|
+
class ShimDataChannel {
|
|
79
|
+
#channel;
|
|
80
|
+
/** @type {"blob" | "arraybuffer"} */
|
|
81
|
+
binaryType = "blob";
|
|
82
|
+
/** @type {((event: { data: ArrayBuffer | Buffer }) => void) | null} */
|
|
83
|
+
onmessage = null;
|
|
84
|
+
/** @type {(() => void) | null} */
|
|
85
|
+
onopen = null;
|
|
86
|
+
/** @type {(() => void) | null} */
|
|
87
|
+
onclose = null;
|
|
88
|
+
/** @type {((event: { error?: Error }) => void) | null} */
|
|
89
|
+
onerror = null;
|
|
90
|
+
/** @type {(() => void) | null} */
|
|
91
|
+
onbufferedamountlow = null;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {object} channel - werift's `RTCDataChannel`.
|
|
95
|
+
*/
|
|
96
|
+
constructor(channel) {
|
|
97
|
+
this.#channel = channel;
|
|
98
|
+
|
|
99
|
+
channel.onmessage = (event) => {
|
|
100
|
+
const data = event?.data ?? event;
|
|
101
|
+
this.onmessage?.({ data: this.#toWireFormat(data) });
|
|
102
|
+
};
|
|
103
|
+
channel.onopen = () => this.onopen?.();
|
|
104
|
+
channel.onclose = () => this.onclose?.();
|
|
105
|
+
channel.onerror = (event) => this.onerror?.(event ?? {});
|
|
106
|
+
|
|
107
|
+
// werift reports this as an observable rather than a handler property.
|
|
108
|
+
channel.bufferedAmountLow?.subscribe?.(() => this.onbufferedamountlow?.());
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Match what a browser would deliver for the requested `binaryType`.
|
|
113
|
+
*
|
|
114
|
+
* `simple-peer` checks `instanceof ArrayBuffer` and sends everything else
|
|
115
|
+
* through a text decoder, so a `Buffer` handed over unchanged arrives
|
|
116
|
+
* mangled.
|
|
117
|
+
*
|
|
118
|
+
* @param {unknown} data
|
|
119
|
+
* @returns {ArrayBuffer | unknown}
|
|
120
|
+
*/
|
|
121
|
+
#toWireFormat(data) {
|
|
122
|
+
if (this.binaryType !== "arraybuffer" || typeof data === "string") {
|
|
123
|
+
return data;
|
|
124
|
+
}
|
|
125
|
+
if (ArrayBuffer.isView(data)) {
|
|
126
|
+
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
127
|
+
}
|
|
128
|
+
return data;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
get label() {
|
|
132
|
+
return this.#channel.label;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
get readyState() {
|
|
136
|
+
return this.#channel.readyState;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
get bufferedAmount() {
|
|
140
|
+
return this.#channel.bufferedAmount;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
get bufferedAmountLowThreshold() {
|
|
144
|
+
return this.#channel.bufferedAmountLowThreshold;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
set bufferedAmountLowThreshold(value) {
|
|
148
|
+
this.#channel.bufferedAmountLowThreshold = value;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @param {string | ArrayBuffer | ArrayBufferView} data
|
|
153
|
+
* @returns {void}
|
|
154
|
+
*/
|
|
155
|
+
send(data) {
|
|
156
|
+
if (typeof data === "string") {
|
|
157
|
+
this.#channel.send(data);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
this.#channel.send(ArrayBuffer.isView(data) ? Buffer.from(data.buffer, data.byteOffset, data.byteLength) : Buffer.from(data));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
close() {
|
|
164
|
+
this.#channel.close();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* werift's peer connection, handing out data channels that carry the browser's
|
|
170
|
+
* interface. Everything else is inherited unchanged — the peer connection side
|
|
171
|
+
* already matches.
|
|
172
|
+
*/
|
|
173
|
+
class ShimPeerConnection extends WeriftPeerConnection {
|
|
174
|
+
/**
|
|
175
|
+
* @param {...unknown} args - Passed through to werift.
|
|
176
|
+
*/
|
|
177
|
+
constructor(...args) {
|
|
178
|
+
super(...args);
|
|
179
|
+
|
|
180
|
+
// `ondatachannel` has to be redefined on the instance, not declared as an
|
|
181
|
+
// accessor on this class: werift assigns it as an own field in its own
|
|
182
|
+
// constructor, and an own property shadows a prototype accessor — so a
|
|
183
|
+
// subclass setter is simply never called, and the caller receives werift's
|
|
184
|
+
// bare channel instead of the wrapped one.
|
|
185
|
+
let handler = null;
|
|
186
|
+
const deliver = (event) => {
|
|
187
|
+
handler?.({ ...event, channel: new ShimDataChannel(event?.channel ?? event) });
|
|
188
|
+
};
|
|
189
|
+
Object.defineProperty(this, "ondatachannel", {
|
|
190
|
+
configurable: true,
|
|
191
|
+
// werift reads this property to dispatch, so it must hand back the
|
|
192
|
+
// wrapper rather than what the caller assigned.
|
|
193
|
+
get: () => (handler ? deliver : null),
|
|
194
|
+
set: (value) => {
|
|
195
|
+
handler = value;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @param {string} label
|
|
202
|
+
* @param {object} [options]
|
|
203
|
+
* @returns {ShimDataChannel}
|
|
204
|
+
*/
|
|
205
|
+
createDataChannel(label, options) {
|
|
206
|
+
return new ShimDataChannel(super.createDataChannel(label, options));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export {
|
|
211
|
+
ShimPeerConnection as RTCPeerConnection,
|
|
212
|
+
ShimSessionDescription as RTCSessionDescription,
|
|
213
|
+
RTCIceCandidate
|
|
214
|
+
};
|
|
215
|
+
export default {
|
|
216
|
+
RTCPeerConnection: ShimPeerConnection,
|
|
217
|
+
RTCSessionDescription: ShimSessionDescription,
|
|
218
|
+
RTCIceCandidate
|
|
219
|
+
};
|