@torrent-tv/proxy 2.87.7 → 2.87.8
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 +322 -322
- package/CHANGELOG.md +4 -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/biome.json +411 -411
- package/docs/container-architecture.md +218 -218
- 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/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/files/get.js +100 -100
- package/routes/api/sources/post.js +36 -36
- package/routes/api/sources/stats/get.js +132 -132
- 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 +129 -129
- package/routes/api/transcode-sessions/post.js +160 -160
- 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 +361 -361
- package/routes/transcode/audio-file/get.js +64 -64
- package/routes/transcode/audio-warm/get.js +84 -84
- 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 +332 -332
- package/services/encode/EncodeOrchestrator.js +1217 -1217
- package/services/encode/EncodePlan.js +1366 -1366
- package/services/encode/EncodeRun.js +776 -776
- package/services/encode/EncodeRuns.js +1229 -1229
- 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/OutputOpening.js +755 -755
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2162 -2162
- 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 +416 -416
- 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/hwaccel.js +1894 -1894
- 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/index.js +20 -20
- 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/EncodeCost.js +1083 -1083
- 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 +254 -254
- package/services/encode/quality/output-format.js +266 -266
- 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 +747 -747
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +42 -42
- package/services/encode/segment-formats/fmp4.js +437 -437
- package/services/encode/segment-formats/index.js +77 -77
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +311 -311
- 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/ContainerOrchestrator.js +149 -149
- package/services/media/KeyframeTables.js +316 -316
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleCues.js +520 -520
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/container/AviContainer.js +266 -266
- package/services/media/container/Container.js +603 -603
- package/services/media/container/ContainerFactory.js +56 -56
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/MatroskaContainer.js +1494 -1494
- package/services/media/container/Mp4Container.js +1244 -1244
- package/services/media/container/SubtitleFileContainer.js +322 -322
- 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/tracks/AudioTrack.js +131 -131
- package/services/media/tracks/ContainerTrack.js +75 -75
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/TextSubtitleTrack.js +287 -287
- package/services/media/tracks/VideoTrack.js +33 -33
- 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 +1146 -1146
- package/services/server/ViewerRequests.js +522 -522
- package/services/server/WaitLedger.js +142 -142
- 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 +983 -983
- 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 +516 -516
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/piece-store/shared-piece-store.js +1880 -1880
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1196 -1196
- package/services/storage/wire.js +175 -175
- package/services/torrent/Contents.js +324 -324
- 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/fastest-wires.js +342 -342
- package/services/torrent/worker/handle-state.js +23 -23
- package/services/torrent/worker/held-bytes.js +155 -155
- package/services/torrent/worker/install-webrtc-shim.js +32 -32
- package/services/torrent/worker/piece-reader.js +1078 -1078
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/transport/data-channel-handler.js +21 -8
- 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/PriorityMap.js +390 -390
- package/services/viewer/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +657 -657
- 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/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-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 -0
- package/test/dead-torrent-handle.test.js +52 -52
- package/test/declared-rates.test.js +116 -116
- package/test/declared-tracks.test.js +35 -35
- 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 +161 -161
- 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-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/held-file.js +29 -29
- 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/keyframe-table-budget.test.js +102 -102
- 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 +292 -292
- package/test/matroska-cues-track.test.js +192 -192
- 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-output-one-session.test.js +204 -204
- 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 +381 -381
- 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 +560 -560
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +318 -318
- 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/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 +1229 -1229
- 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-serve-wiring.test.js +475 -475
- package/test/segment-store-eviction.test.js +247 -247
- package/test/segment-store.test.js +323 -323
- 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 +102 -102
- 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/source-file.test.js +133 -133
- package/test/source-files-route.test.js +103 -103
- 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 +182 -182
- package/test/subtitle-cue-framing.test.js +200 -200
- package/test/subtitle-cue-source.test.js +102 -102
- package/test/subtitle-cue-walk.test.js +370 -370
- 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/subtitle-track-numbering.test.js +386 -386
- 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 +407 -407
- 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/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,167 +1,167 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file The tunnel is replaced before anything upstream ends it, and the
|
|
3
|
-
* replacement takes over first.
|
|
4
|
-
*
|
|
5
|
-
* Something between the proxy and the server closes the socket after exactly
|
|
6
|
-
* 100 min 15 s whatever is flowing over it — measured across a day of logs on
|
|
7
|
-
* 2026-08-20, `code=1006` each time, with a 30 s keepalive running throughout,
|
|
8
|
-
* so it is a lifetime cap and not an idle timeout. Reconnecting afterwards
|
|
9
|
-
* costs five seconds in which the proxy does not exist as far as the registry
|
|
10
|
-
* is concerned, and a viewer arriving then is told there is no proxy.
|
|
11
|
-
*
|
|
12
|
-
* What is pinned here is the property that removes that window: at no instant
|
|
13
|
-
* is the server without a registered connection for this proxy.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import assert from "node:assert/strict";
|
|
17
|
-
import test from "node:test";
|
|
18
|
-
import { WebSocketServer } from "ws";
|
|
19
|
-
|
|
20
|
-
import { createTunnelClient } from "../services/transport/tunnel-client.js";
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Wait for the thing being asserted, not for a length of time.
|
|
24
|
-
*
|
|
25
|
-
* A chosen interval followed by an assertion does not test — it samples, and
|
|
26
|
-
* what it samples is how busy the machine is. The deadline here is a backstop
|
|
27
|
-
* that turns a hang into a failure; it is never the measurement.
|
|
28
|
-
*
|
|
29
|
-
* @param {() => boolean} until
|
|
30
|
-
* @param {string} what - Named in the failure, since a timeout otherwise says
|
|
31
|
-
* only that something did not happen.
|
|
32
|
-
* @param {number} [limit]
|
|
33
|
-
* @returns {Promise<void>}
|
|
34
|
-
*/
|
|
35
|
-
async function waitFor(until, what, limit = 10_000) {
|
|
36
|
-
const deadline = Date.now() + limit;
|
|
37
|
-
while (!until()) {
|
|
38
|
-
if (Date.now() > deadline) {
|
|
39
|
-
throw new Error(`${what} never happened`);
|
|
40
|
-
}
|
|
41
|
-
await new Promise((resolve) => { setTimeout(resolve, 5); });
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* A stand-in for the registry's tunnel endpoint, with the one behaviour that
|
|
47
|
-
* matters here: a new connection for a proxy REPLACES the previous one, which
|
|
48
|
-
* is what `registerConnection` does in `server/services/proxy-tunnel-server.js`.
|
|
49
|
-
*
|
|
50
|
-
* @returns {Promise<{ url: string, close: () => Promise<void>, registered: () => number, opened: () => number, everEmpty: () => boolean }>}
|
|
51
|
-
*/
|
|
52
|
-
async function startRegistry() {
|
|
53
|
-
const server = new WebSocketServer({ port: 0 });
|
|
54
|
-
await new Promise((resolve) => { server.on("listening", resolve); });
|
|
55
|
-
/** @type {import("ws").WebSocket | null} */
|
|
56
|
-
let current = null;
|
|
57
|
-
let opened = 0;
|
|
58
|
-
let everEmpty = false;
|
|
59
|
-
/** Every socket ever accepted, so the server can be shut without waiting. */
|
|
60
|
-
const accepted = new Set();
|
|
61
|
-
server.on("connection", (socket) => {
|
|
62
|
-
opened += 1;
|
|
63
|
-
accepted.add(socket);
|
|
64
|
-
socket.on("close", () => { accepted.delete(socket); });
|
|
65
|
-
const previous = current;
|
|
66
|
-
current = socket;
|
|
67
|
-
// The replacement is registered BEFORE the old one is closed, so a reader
|
|
68
|
-
// of `current` never sees nothing.
|
|
69
|
-
if (previous && previous.readyState < 2) {
|
|
70
|
-
previous.close(1000, "replaced");
|
|
71
|
-
}
|
|
72
|
-
socket.on("close", () => {
|
|
73
|
-
if (current === socket) {
|
|
74
|
-
current = null;
|
|
75
|
-
everEmpty = true;
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
const { port } = server.address();
|
|
80
|
-
return {
|
|
81
|
-
url: `http://127.0.0.1:${port}`,
|
|
82
|
-
// `close` waits for every connection to end, and a renewal can leave one
|
|
83
|
-
// still closing, so they are ended here rather than waited on.
|
|
84
|
-
close: () => new Promise((resolve) => {
|
|
85
|
-
for (const socket of accepted) {
|
|
86
|
-
socket.terminate();
|
|
87
|
-
}
|
|
88
|
-
accepted.clear();
|
|
89
|
-
server.close(() => resolve());
|
|
90
|
-
}),
|
|
91
|
-
registered: () => (current && current.readyState === 1 ? 1 : 0),
|
|
92
|
-
killCurrent: () => { current?.terminate(); },
|
|
93
|
-
opened: () => opened,
|
|
94
|
-
everEmpty: () => everEmpty
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
test("the connection is replaced before its lifetime runs out, without a gap", async (t) => {
|
|
99
|
-
const registry = await startRegistry();
|
|
100
|
-
/** @type {string[]} */
|
|
101
|
-
const lines = [];
|
|
102
|
-
const client = createTunnelClient({
|
|
103
|
-
serverUrl: registry.url,
|
|
104
|
-
proxyId: "p1",
|
|
105
|
-
token: "t",
|
|
106
|
-
proxyPort: 9090,
|
|
107
|
-
onLog: (line) => lines.push(line),
|
|
108
|
-
// The real cap is 100 min 15 s and the real renewal is at 90 min; the
|
|
109
|
-
// ratio is what matters, not the magnitude.
|
|
110
|
-
connectionLifetimeMs: 150
|
|
111
|
-
});
|
|
112
|
-
t.after(async () => {
|
|
113
|
-
client.disconnect();
|
|
114
|
-
await registry.close();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
client.connect();
|
|
118
|
-
// Until the renewals have happened, however long this machine takes over
|
|
119
|
-
// them. Waited out as 700 ms instead — about four lifetimes — this asserted
|
|
120
|
-
// how many the scheduler had got round to.
|
|
121
|
-
await waitFor(() => registry.opened() >= 3, "three connections");
|
|
122
|
-
|
|
123
|
-
assert.ok(registry.opened() >= 3, `expected several renewals, saw ${registry.opened()}`);
|
|
124
|
-
// The property this exists for: the registry was never left with nothing.
|
|
125
|
-
assert.equal(registry.everEmpty(), false, "the registry lost its connection at some point");
|
|
126
|
-
assert.equal(registry.registered(), 1);
|
|
127
|
-
// And the proxy knows the difference between a handover and going down. A
|
|
128
|
-
// "Reconnecting in" line here would mean it had treated its own renewal as a
|
|
129
|
-
// failure and waited five seconds before coming back.
|
|
130
|
-
assert.ok(lines.some((line) => line.includes("Tunnel renewing")), lines.join("\n"));
|
|
131
|
-
assert.ok(lines.some((line) => line.includes("Tunnel handed over")), lines.join("\n"));
|
|
132
|
-
assert.equal(lines.filter((line) => line.includes("Reconnecting in")).length, 0, lines.join("\n"));
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test("a connection killed from outside is still reconnected", async (t) => {
|
|
136
|
-
const registry = await startRegistry();
|
|
137
|
-
/** @type {string[]} */
|
|
138
|
-
const lines = [];
|
|
139
|
-
const client = createTunnelClient({
|
|
140
|
-
serverUrl: registry.url,
|
|
141
|
-
proxyId: "p2",
|
|
142
|
-
token: "t",
|
|
143
|
-
proxyPort: 9090,
|
|
144
|
-
onLog: (line) => lines.push(line),
|
|
145
|
-
// Far longer than this test runs, so nothing renews and the only close is
|
|
146
|
-
// the one forced below — which is what the upstream cap looks like from
|
|
147
|
-
// here: an abrupt end nobody asked for.
|
|
148
|
-
connectionLifetimeMs: 60_000
|
|
149
|
-
});
|
|
150
|
-
t.after(async () => {
|
|
151
|
-
client.disconnect();
|
|
152
|
-
await registry.close();
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
client.connect();
|
|
156
|
-
await waitFor(() => registry.opened() === 1, "the first connection");
|
|
157
|
-
// And exactly one: the lifetime above is far longer than this test runs, so
|
|
158
|
-
// nothing renews and a second would mean something else had opened it.
|
|
159
|
-
assert.equal(registry.opened(), 1);
|
|
160
|
-
|
|
161
|
-
registry.killCurrent();
|
|
162
|
-
// The renewal must not have taken the ordinary reconnect away with it.
|
|
163
|
-
await waitFor(
|
|
164
|
-
() => lines.some((line) => line.includes("Reconnecting in")),
|
|
165
|
-
"the reconnect after a connection was killed from outside"
|
|
166
|
-
);
|
|
167
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file The tunnel is replaced before anything upstream ends it, and the
|
|
3
|
+
* replacement takes over first.
|
|
4
|
+
*
|
|
5
|
+
* Something between the proxy and the server closes the socket after exactly
|
|
6
|
+
* 100 min 15 s whatever is flowing over it — measured across a day of logs on
|
|
7
|
+
* 2026-08-20, `code=1006` each time, with a 30 s keepalive running throughout,
|
|
8
|
+
* so it is a lifetime cap and not an idle timeout. Reconnecting afterwards
|
|
9
|
+
* costs five seconds in which the proxy does not exist as far as the registry
|
|
10
|
+
* is concerned, and a viewer arriving then is told there is no proxy.
|
|
11
|
+
*
|
|
12
|
+
* What is pinned here is the property that removes that window: at no instant
|
|
13
|
+
* is the server without a registered connection for this proxy.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import assert from "node:assert/strict";
|
|
17
|
+
import test from "node:test";
|
|
18
|
+
import { WebSocketServer } from "ws";
|
|
19
|
+
|
|
20
|
+
import { createTunnelClient } from "../services/transport/tunnel-client.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Wait for the thing being asserted, not for a length of time.
|
|
24
|
+
*
|
|
25
|
+
* A chosen interval followed by an assertion does not test — it samples, and
|
|
26
|
+
* what it samples is how busy the machine is. The deadline here is a backstop
|
|
27
|
+
* that turns a hang into a failure; it is never the measurement.
|
|
28
|
+
*
|
|
29
|
+
* @param {() => boolean} until
|
|
30
|
+
* @param {string} what - Named in the failure, since a timeout otherwise says
|
|
31
|
+
* only that something did not happen.
|
|
32
|
+
* @param {number} [limit]
|
|
33
|
+
* @returns {Promise<void>}
|
|
34
|
+
*/
|
|
35
|
+
async function waitFor(until, what, limit = 10_000) {
|
|
36
|
+
const deadline = Date.now() + limit;
|
|
37
|
+
while (!until()) {
|
|
38
|
+
if (Date.now() > deadline) {
|
|
39
|
+
throw new Error(`${what} never happened`);
|
|
40
|
+
}
|
|
41
|
+
await new Promise((resolve) => { setTimeout(resolve, 5); });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A stand-in for the registry's tunnel endpoint, with the one behaviour that
|
|
47
|
+
* matters here: a new connection for a proxy REPLACES the previous one, which
|
|
48
|
+
* is what `registerConnection` does in `server/services/proxy-tunnel-server.js`.
|
|
49
|
+
*
|
|
50
|
+
* @returns {Promise<{ url: string, close: () => Promise<void>, registered: () => number, opened: () => number, everEmpty: () => boolean }>}
|
|
51
|
+
*/
|
|
52
|
+
async function startRegistry() {
|
|
53
|
+
const server = new WebSocketServer({ port: 0 });
|
|
54
|
+
await new Promise((resolve) => { server.on("listening", resolve); });
|
|
55
|
+
/** @type {import("ws").WebSocket | null} */
|
|
56
|
+
let current = null;
|
|
57
|
+
let opened = 0;
|
|
58
|
+
let everEmpty = false;
|
|
59
|
+
/** Every socket ever accepted, so the server can be shut without waiting. */
|
|
60
|
+
const accepted = new Set();
|
|
61
|
+
server.on("connection", (socket) => {
|
|
62
|
+
opened += 1;
|
|
63
|
+
accepted.add(socket);
|
|
64
|
+
socket.on("close", () => { accepted.delete(socket); });
|
|
65
|
+
const previous = current;
|
|
66
|
+
current = socket;
|
|
67
|
+
// The replacement is registered BEFORE the old one is closed, so a reader
|
|
68
|
+
// of `current` never sees nothing.
|
|
69
|
+
if (previous && previous.readyState < 2) {
|
|
70
|
+
previous.close(1000, "replaced");
|
|
71
|
+
}
|
|
72
|
+
socket.on("close", () => {
|
|
73
|
+
if (current === socket) {
|
|
74
|
+
current = null;
|
|
75
|
+
everEmpty = true;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
const { port } = server.address();
|
|
80
|
+
return {
|
|
81
|
+
url: `http://127.0.0.1:${port}`,
|
|
82
|
+
// `close` waits for every connection to end, and a renewal can leave one
|
|
83
|
+
// still closing, so they are ended here rather than waited on.
|
|
84
|
+
close: () => new Promise((resolve) => {
|
|
85
|
+
for (const socket of accepted) {
|
|
86
|
+
socket.terminate();
|
|
87
|
+
}
|
|
88
|
+
accepted.clear();
|
|
89
|
+
server.close(() => resolve());
|
|
90
|
+
}),
|
|
91
|
+
registered: () => (current && current.readyState === 1 ? 1 : 0),
|
|
92
|
+
killCurrent: () => { current?.terminate(); },
|
|
93
|
+
opened: () => opened,
|
|
94
|
+
everEmpty: () => everEmpty
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
test("the connection is replaced before its lifetime runs out, without a gap", async (t) => {
|
|
99
|
+
const registry = await startRegistry();
|
|
100
|
+
/** @type {string[]} */
|
|
101
|
+
const lines = [];
|
|
102
|
+
const client = createTunnelClient({
|
|
103
|
+
serverUrl: registry.url,
|
|
104
|
+
proxyId: "p1",
|
|
105
|
+
token: "t",
|
|
106
|
+
proxyPort: 9090,
|
|
107
|
+
onLog: (line) => lines.push(line),
|
|
108
|
+
// The real cap is 100 min 15 s and the real renewal is at 90 min; the
|
|
109
|
+
// ratio is what matters, not the magnitude.
|
|
110
|
+
connectionLifetimeMs: 150
|
|
111
|
+
});
|
|
112
|
+
t.after(async () => {
|
|
113
|
+
client.disconnect();
|
|
114
|
+
await registry.close();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
client.connect();
|
|
118
|
+
// Until the renewals have happened, however long this machine takes over
|
|
119
|
+
// them. Waited out as 700 ms instead — about four lifetimes — this asserted
|
|
120
|
+
// how many the scheduler had got round to.
|
|
121
|
+
await waitFor(() => registry.opened() >= 3, "three connections");
|
|
122
|
+
|
|
123
|
+
assert.ok(registry.opened() >= 3, `expected several renewals, saw ${registry.opened()}`);
|
|
124
|
+
// The property this exists for: the registry was never left with nothing.
|
|
125
|
+
assert.equal(registry.everEmpty(), false, "the registry lost its connection at some point");
|
|
126
|
+
assert.equal(registry.registered(), 1);
|
|
127
|
+
// And the proxy knows the difference between a handover and going down. A
|
|
128
|
+
// "Reconnecting in" line here would mean it had treated its own renewal as a
|
|
129
|
+
// failure and waited five seconds before coming back.
|
|
130
|
+
assert.ok(lines.some((line) => line.includes("Tunnel renewing")), lines.join("\n"));
|
|
131
|
+
assert.ok(lines.some((line) => line.includes("Tunnel handed over")), lines.join("\n"));
|
|
132
|
+
assert.equal(lines.filter((line) => line.includes("Reconnecting in")).length, 0, lines.join("\n"));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("a connection killed from outside is still reconnected", async (t) => {
|
|
136
|
+
const registry = await startRegistry();
|
|
137
|
+
/** @type {string[]} */
|
|
138
|
+
const lines = [];
|
|
139
|
+
const client = createTunnelClient({
|
|
140
|
+
serverUrl: registry.url,
|
|
141
|
+
proxyId: "p2",
|
|
142
|
+
token: "t",
|
|
143
|
+
proxyPort: 9090,
|
|
144
|
+
onLog: (line) => lines.push(line),
|
|
145
|
+
// Far longer than this test runs, so nothing renews and the only close is
|
|
146
|
+
// the one forced below — which is what the upstream cap looks like from
|
|
147
|
+
// here: an abrupt end nobody asked for.
|
|
148
|
+
connectionLifetimeMs: 60_000
|
|
149
|
+
});
|
|
150
|
+
t.after(async () => {
|
|
151
|
+
client.disconnect();
|
|
152
|
+
await registry.close();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
client.connect();
|
|
156
|
+
await waitFor(() => registry.opened() === 1, "the first connection");
|
|
157
|
+
// And exactly one: the lifetime above is far longer than this test runs, so
|
|
158
|
+
// nothing renews and a second would mean something else had opened it.
|
|
159
|
+
assert.equal(registry.opened(), 1);
|
|
160
|
+
|
|
161
|
+
registry.killCurrent();
|
|
162
|
+
// The renewal must not have taken the ordinary reconnect away with it.
|
|
163
|
+
await waitFor(
|
|
164
|
+
() => lines.some((line) => line.includes("Reconnecting in")),
|
|
165
|
+
"the reconnect after a connection was killed from outside"
|
|
166
|
+
);
|
|
167
|
+
});
|