@torrent-tv/proxy 2.87.10 → 2.87.12
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 +2191 -2181
- 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/browser-proxy-contract.md +124 -101
- 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 +50 -50
- 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/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/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 +136 -129
- package/routes/api/transcode-sessions/post.js +173 -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/server.js +725 -725
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +376 -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 +758 -755
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2184 -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 +1141 -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 +315 -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/audio-inventory.js +245 -245
- 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/playback-planner.js +931 -931
- 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 +727 -522
- 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/playback-readiness.js +620 -0
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +992 -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/torrent-pool.js +2675 -2675
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/client.js +765 -765
- 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/pool-adapter.js +365 -365
- 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/torrent/worker/worker.js +1129 -1129
- package/services/transport/data-channel-handler.js +1369 -1369
- 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 +682 -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-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 +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/playback-readiness.test.js +323 -0
- 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,97 +1,97 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file How fast a calibrated encoding mode runs at a given frame size, from
|
|
3
|
-
* the readings the startup calibration took (`encode/calibration.js`).
|
|
4
|
-
*
|
|
5
|
-
* Pure arithmetic over measured figures, kept apart from the calibration so
|
|
6
|
-
* that everything which prices an encode can read it without reaching the code
|
|
7
|
-
* that runs ffmpeg.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {object} SizeReading
|
|
12
|
-
* @property {number} width
|
|
13
|
-
* @property {number} height
|
|
14
|
-
* @property {number} pixelsPerSec
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* The rate of pictures this mode would encode at this frame, in pixels per
|
|
19
|
-
* second, or null where this machine has not shown it can.
|
|
20
|
-
*
|
|
21
|
-
* An entry with no readings by size — built before sizes were read, or by a
|
|
22
|
-
* check that states one figure — answers with its one figure, as it always did.
|
|
23
|
-
*
|
|
24
|
-
* @param {{ pixelsPerSec: number, bySize?: SizeReading[], interpolationError?: number | null }} entry
|
|
25
|
-
* @param {{ width: number, height: number } | null | undefined} frame
|
|
26
|
-
* @returns {number | null}
|
|
27
|
-
*/
|
|
28
|
-
export function throughputAt(entry, frame) {
|
|
29
|
-
const bySize = Array.isArray(entry?.bySize) ? entry.bySize : [];
|
|
30
|
-
const width = Number(frame?.width) || 0;
|
|
31
|
-
const height = Number(frame?.height) || 0;
|
|
32
|
-
if (bySize.length === 0 || width <= 0 || height <= 0) {
|
|
33
|
-
const figure = Number(entry?.pixelsPerSec);
|
|
34
|
-
return Number.isFinite(figure) && figure > 0 ? figure : null;
|
|
35
|
-
}
|
|
36
|
-
const area = width * height;
|
|
37
|
-
const sorted = [...bySize].sort((left, right) => left.width * left.height - right.width * right.height);
|
|
38
|
-
const smallest = sorted[0];
|
|
39
|
-
const largest = sorted[sorted.length - 1];
|
|
40
|
-
if (area < smallest.width * smallest.height || area > largest.width * largest.height) {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
for (let index = 0; index < sorted.length; index += 1) {
|
|
44
|
-
const reading = sorted[index];
|
|
45
|
-
const readArea = reading.width * reading.height;
|
|
46
|
-
if (readArea === area) {
|
|
47
|
-
return reading.pixelsPerSec;
|
|
48
|
-
}
|
|
49
|
-
if (readArea > area) {
|
|
50
|
-
const error = entry.interpolationError;
|
|
51
|
-
if (!(Number.isFinite(error) && error >= 0 && error < 1)) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
const below = sorted[index - 1];
|
|
55
|
-
return interpolate(below, reading, area) * (1 - error);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Pixels per second at an area between two readings, straight in logarithms
|
|
63
|
-
* of both: throughput against frame area is a power law over any short
|
|
64
|
-
* stretch, and a straight line between two points of it is the least that
|
|
65
|
-
* can be said without inventing a shape.
|
|
66
|
-
*
|
|
67
|
-
* @param {SizeReading} below
|
|
68
|
-
* @param {SizeReading} above
|
|
69
|
-
* @param {number} area
|
|
70
|
-
* @returns {number}
|
|
71
|
-
*/
|
|
72
|
-
function interpolate(below, above, area) {
|
|
73
|
-
const lowArea = below.width * below.height;
|
|
74
|
-
const highArea = above.width * above.height;
|
|
75
|
-
const share = Math.log(area / lowArea) / Math.log(highArea / lowArea);
|
|
76
|
-
return Math.exp(Math.log(below.pixelsPerSec) + share * (Math.log(above.pixelsPerSec) - Math.log(below.pixelsPerSec)));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* How far an interpolation between readings of this mode was measured to be
|
|
81
|
-
* wrong: the largest relative error of a middle reading predicted from the two
|
|
82
|
-
* outside it.
|
|
83
|
-
*
|
|
84
|
-
* @param {SizeReading[]} bySize - Smallest first.
|
|
85
|
-
* @returns {number | null} Null with fewer than three readings.
|
|
86
|
-
*/
|
|
87
|
-
export function interpolationErrorOf(bySize) {
|
|
88
|
-
let largest = null;
|
|
89
|
-
for (let index = 1; index + 1 < bySize.length; index += 1) {
|
|
90
|
-
const middle = bySize[index];
|
|
91
|
-
const predicted = interpolate(bySize[index - 1], bySize[index + 1], middle.width * middle.height);
|
|
92
|
-
const error = Math.abs(predicted - middle.pixelsPerSec) / middle.pixelsPerSec;
|
|
93
|
-
largest = largest === null ? error : Math.max(largest, error);
|
|
94
|
-
}
|
|
95
|
-
return largest;
|
|
96
|
-
}
|
|
97
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file How fast a calibrated encoding mode runs at a given frame size, from
|
|
3
|
+
* the readings the startup calibration took (`encode/calibration.js`).
|
|
4
|
+
*
|
|
5
|
+
* Pure arithmetic over measured figures, kept apart from the calibration so
|
|
6
|
+
* that everything which prices an encode can read it without reaching the code
|
|
7
|
+
* that runs ffmpeg.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} SizeReading
|
|
12
|
+
* @property {number} width
|
|
13
|
+
* @property {number} height
|
|
14
|
+
* @property {number} pixelsPerSec
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The rate of pictures this mode would encode at this frame, in pixels per
|
|
19
|
+
* second, or null where this machine has not shown it can.
|
|
20
|
+
*
|
|
21
|
+
* An entry with no readings by size — built before sizes were read, or by a
|
|
22
|
+
* check that states one figure — answers with its one figure, as it always did.
|
|
23
|
+
*
|
|
24
|
+
* @param {{ pixelsPerSec: number, bySize?: SizeReading[], interpolationError?: number | null }} entry
|
|
25
|
+
* @param {{ width: number, height: number } | null | undefined} frame
|
|
26
|
+
* @returns {number | null}
|
|
27
|
+
*/
|
|
28
|
+
export function throughputAt(entry, frame) {
|
|
29
|
+
const bySize = Array.isArray(entry?.bySize) ? entry.bySize : [];
|
|
30
|
+
const width = Number(frame?.width) || 0;
|
|
31
|
+
const height = Number(frame?.height) || 0;
|
|
32
|
+
if (bySize.length === 0 || width <= 0 || height <= 0) {
|
|
33
|
+
const figure = Number(entry?.pixelsPerSec);
|
|
34
|
+
return Number.isFinite(figure) && figure > 0 ? figure : null;
|
|
35
|
+
}
|
|
36
|
+
const area = width * height;
|
|
37
|
+
const sorted = [...bySize].sort((left, right) => left.width * left.height - right.width * right.height);
|
|
38
|
+
const smallest = sorted[0];
|
|
39
|
+
const largest = sorted[sorted.length - 1];
|
|
40
|
+
if (area < smallest.width * smallest.height || area > largest.width * largest.height) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
for (let index = 0; index < sorted.length; index += 1) {
|
|
44
|
+
const reading = sorted[index];
|
|
45
|
+
const readArea = reading.width * reading.height;
|
|
46
|
+
if (readArea === area) {
|
|
47
|
+
return reading.pixelsPerSec;
|
|
48
|
+
}
|
|
49
|
+
if (readArea > area) {
|
|
50
|
+
const error = entry.interpolationError;
|
|
51
|
+
if (!(Number.isFinite(error) && error >= 0 && error < 1)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const below = sorted[index - 1];
|
|
55
|
+
return interpolate(below, reading, area) * (1 - error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Pixels per second at an area between two readings, straight in logarithms
|
|
63
|
+
* of both: throughput against frame area is a power law over any short
|
|
64
|
+
* stretch, and a straight line between two points of it is the least that
|
|
65
|
+
* can be said without inventing a shape.
|
|
66
|
+
*
|
|
67
|
+
* @param {SizeReading} below
|
|
68
|
+
* @param {SizeReading} above
|
|
69
|
+
* @param {number} area
|
|
70
|
+
* @returns {number}
|
|
71
|
+
*/
|
|
72
|
+
function interpolate(below, above, area) {
|
|
73
|
+
const lowArea = below.width * below.height;
|
|
74
|
+
const highArea = above.width * above.height;
|
|
75
|
+
const share = Math.log(area / lowArea) / Math.log(highArea / lowArea);
|
|
76
|
+
return Math.exp(Math.log(below.pixelsPerSec) + share * (Math.log(above.pixelsPerSec) - Math.log(below.pixelsPerSec)));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* How far an interpolation between readings of this mode was measured to be
|
|
81
|
+
* wrong: the largest relative error of a middle reading predicted from the two
|
|
82
|
+
* outside it.
|
|
83
|
+
*
|
|
84
|
+
* @param {SizeReading[]} bySize - Smallest first.
|
|
85
|
+
* @returns {number | null} Null with fewer than three readings.
|
|
86
|
+
*/
|
|
87
|
+
export function interpolationErrorOf(bySize) {
|
|
88
|
+
let largest = null;
|
|
89
|
+
for (let index = 1; index + 1 < bySize.length; index += 1) {
|
|
90
|
+
const middle = bySize[index];
|
|
91
|
+
const predicted = interpolate(bySize[index - 1], bySize[index + 1], middle.width * middle.height);
|
|
92
|
+
const error = Math.abs(predicted - middle.pixelsPerSec) / middle.pixelsPerSec;
|
|
93
|
+
largest = largest === null ? error : Math.max(largest, error);
|
|
94
|
+
}
|
|
95
|
+
return largest;
|
|
96
|
+
}
|
|
97
|
+
|
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file What the torrent itself costs this machine, per megabyte it moves.
|
|
3
|
-
*
|
|
4
|
-
* Downloading, verifying every piece and pushing segments down a data channel
|
|
5
|
-
* are work on the same box as the encoder, and they scale with what is moved.
|
|
6
|
-
* The reading is taken only while nothing is encoding, because that is the one
|
|
7
|
-
* moment the spending needs no arithmetic to attribute: what the process uses
|
|
8
|
-
* then is the torrent's.
|
|
9
|
-
*
|
|
10
|
-
* Except that it is not all the torrent's, and that is what this file exists
|
|
11
|
-
* for. Measured on the addon host 2026-08-18, one session reported **145.4 ms
|
|
12
|
-
* per megabyte over 8.7 MB** and **23.1 ms per megabyte over 54 MB** — the same
|
|
13
|
-
* host, the same minute, a sixfold disagreement that follows the size of the
|
|
14
|
-
* interval rather than anything about the torrent. A process with nothing to do
|
|
15
|
-
* still runs its timers, its tunnel and its session sweeps, and that draw does
|
|
16
|
-
* not shrink when fewer megabytes move: divided by a small number it swamps the
|
|
17
|
-
* answer. A minimum-megabytes threshold stood against exactly this and did not
|
|
18
|
-
* hold, because a chosen number was standing in for a measured one.
|
|
19
|
-
*
|
|
20
|
-
* The draw is measurable. An interval in which nothing encodes and the torrents
|
|
21
|
-
* move no bytes at all costs whatever this process spends anyway; subtract that
|
|
22
|
-
* from an interval which did move bytes, and what remains is the torrent's.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* The share of one core this process draws while it has nothing to do.
|
|
27
|
-
*
|
|
28
|
-
* @param {{ cpuSeconds: number, elapsedSeconds: number }} interval - CPU seconds
|
|
29
|
-
* already divided by the number of cores, so the figure is comparable with
|
|
30
|
-
* the wall seconds every other cost in the budget is stated in.
|
|
31
|
-
* @returns {number | null} Null when the interval cannot be divided.
|
|
32
|
-
*/
|
|
33
|
-
export function baseDrawFrom({ cpuSeconds, elapsedSeconds }) {
|
|
34
|
-
if (!Number.isFinite(cpuSeconds) || !Number.isFinite(elapsedSeconds) || !(elapsedSeconds > 0)) {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
if (!(cpuSeconds >= 0)) {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
return cpuSeconds / elapsedSeconds;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* What one megabyte cost, once the draw that would have been spent anyway is
|
|
45
|
-
* taken off.
|
|
46
|
-
*
|
|
47
|
-
* The draw is subtracted, so what is left is only as certain as the draw is.
|
|
48
|
-
* The draw's own readings disagree with each other by a measured amount, and
|
|
49
|
-
* over an interval that disagreement is worth `scatter × elapsed` seconds of
|
|
50
|
-
* CPU: a reading whose remainder is smaller than that measures the wobble in
|
|
51
|
-
* the subtraction rather than the torrent. This is what the removed
|
|
52
|
-
* minimum-megabytes threshold was reaching for, arrived at from the readings
|
|
53
|
-
* instead of from a chosen size — a small interval fails it because little was
|
|
54
|
-
* moved in it, and a large one passes on the same arithmetic.
|
|
55
|
-
*
|
|
56
|
-
* @param {{ cpuSeconds: number, elapsedSeconds: number, megabytes: number, baseDraw: number | null, drawScatter?: number | null }} interval
|
|
57
|
-
* @returns {number | null} Seconds of work per megabyte, or null when this
|
|
58
|
-
* interval cannot say — no base draw measured yet, nothing moved, or the
|
|
59
|
-
* interval spent no more than the draw and its own uncertainty account for.
|
|
60
|
-
*/
|
|
61
|
-
export function costPerMegabyteFrom({ cpuSeconds, elapsedSeconds, megabytes, baseDraw, drawScatter = null }) {
|
|
62
|
-
if (!Number.isFinite(cpuSeconds) || !Number.isFinite(elapsedSeconds) || !(elapsedSeconds > 0)) {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
if (!Number.isFinite(megabytes) || !(megabytes > 0)) {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
if (!Number.isFinite(baseDraw) || !(baseDraw >= 0)) {
|
|
69
|
-
// Nothing measured to subtract. Publishing the whole spending as the
|
|
70
|
-
// torrent's is what produced the 145 ms/MB reading, so this interval says
|
|
71
|
-
// nothing instead.
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
const attributable = cpuSeconds - baseDraw * elapsedSeconds;
|
|
75
|
-
if (!(attributable > 0)) {
|
|
76
|
-
// The draw accounts for everything this interval spent. Not a discovery
|
|
77
|
-
// that the torrent is free — a reading with nothing left in it.
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
const uncertainty = Number.isFinite(drawScatter) && drawScatter > 0 ? drawScatter * elapsedSeconds : 0;
|
|
81
|
-
if (!(attributable > uncertainty)) {
|
|
82
|
-
// What is left is inside the disagreement between the draw's own readings.
|
|
83
|
-
// Dividing it by a small number of megabytes is how 145 ms/MB was reported
|
|
84
|
-
// on a host that costs 23.
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
return attributable / megabytes;
|
|
88
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @file What the torrent itself costs this machine, per megabyte it moves.
|
|
3
|
+
*
|
|
4
|
+
* Downloading, verifying every piece and pushing segments down a data channel
|
|
5
|
+
* are work on the same box as the encoder, and they scale with what is moved.
|
|
6
|
+
* The reading is taken only while nothing is encoding, because that is the one
|
|
7
|
+
* moment the spending needs no arithmetic to attribute: what the process uses
|
|
8
|
+
* then is the torrent's.
|
|
9
|
+
*
|
|
10
|
+
* Except that it is not all the torrent's, and that is what this file exists
|
|
11
|
+
* for. Measured on the addon host 2026-08-18, one session reported **145.4 ms
|
|
12
|
+
* per megabyte over 8.7 MB** and **23.1 ms per megabyte over 54 MB** — the same
|
|
13
|
+
* host, the same minute, a sixfold disagreement that follows the size of the
|
|
14
|
+
* interval rather than anything about the torrent. A process with nothing to do
|
|
15
|
+
* still runs its timers, its tunnel and its session sweeps, and that draw does
|
|
16
|
+
* not shrink when fewer megabytes move: divided by a small number it swamps the
|
|
17
|
+
* answer. A minimum-megabytes threshold stood against exactly this and did not
|
|
18
|
+
* hold, because a chosen number was standing in for a measured one.
|
|
19
|
+
*
|
|
20
|
+
* The draw is measurable. An interval in which nothing encodes and the torrents
|
|
21
|
+
* move no bytes at all costs whatever this process spends anyway; subtract that
|
|
22
|
+
* from an interval which did move bytes, and what remains is the torrent's.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The share of one core this process draws while it has nothing to do.
|
|
27
|
+
*
|
|
28
|
+
* @param {{ cpuSeconds: number, elapsedSeconds: number }} interval - CPU seconds
|
|
29
|
+
* already divided by the number of cores, so the figure is comparable with
|
|
30
|
+
* the wall seconds every other cost in the budget is stated in.
|
|
31
|
+
* @returns {number | null} Null when the interval cannot be divided.
|
|
32
|
+
*/
|
|
33
|
+
export function baseDrawFrom({ cpuSeconds, elapsedSeconds }) {
|
|
34
|
+
if (!Number.isFinite(cpuSeconds) || !Number.isFinite(elapsedSeconds) || !(elapsedSeconds > 0)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
if (!(cpuSeconds >= 0)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return cpuSeconds / elapsedSeconds;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* What one megabyte cost, once the draw that would have been spent anyway is
|
|
45
|
+
* taken off.
|
|
46
|
+
*
|
|
47
|
+
* The draw is subtracted, so what is left is only as certain as the draw is.
|
|
48
|
+
* The draw's own readings disagree with each other by a measured amount, and
|
|
49
|
+
* over an interval that disagreement is worth `scatter × elapsed` seconds of
|
|
50
|
+
* CPU: a reading whose remainder is smaller than that measures the wobble in
|
|
51
|
+
* the subtraction rather than the torrent. This is what the removed
|
|
52
|
+
* minimum-megabytes threshold was reaching for, arrived at from the readings
|
|
53
|
+
* instead of from a chosen size — a small interval fails it because little was
|
|
54
|
+
* moved in it, and a large one passes on the same arithmetic.
|
|
55
|
+
*
|
|
56
|
+
* @param {{ cpuSeconds: number, elapsedSeconds: number, megabytes: number, baseDraw: number | null, drawScatter?: number | null }} interval
|
|
57
|
+
* @returns {number | null} Seconds of work per megabyte, or null when this
|
|
58
|
+
* interval cannot say — no base draw measured yet, nothing moved, or the
|
|
59
|
+
* interval spent no more than the draw and its own uncertainty account for.
|
|
60
|
+
*/
|
|
61
|
+
export function costPerMegabyteFrom({ cpuSeconds, elapsedSeconds, megabytes, baseDraw, drawScatter = null }) {
|
|
62
|
+
if (!Number.isFinite(cpuSeconds) || !Number.isFinite(elapsedSeconds) || !(elapsedSeconds > 0)) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
if (!Number.isFinite(megabytes) || !(megabytes > 0)) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
if (!Number.isFinite(baseDraw) || !(baseDraw >= 0)) {
|
|
69
|
+
// Nothing measured to subtract. Publishing the whole spending as the
|
|
70
|
+
// torrent's is what produced the 145 ms/MB reading, so this interval says
|
|
71
|
+
// nothing instead.
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const attributable = cpuSeconds - baseDraw * elapsedSeconds;
|
|
75
|
+
if (!(attributable > 0)) {
|
|
76
|
+
// The draw accounts for everything this interval spent. Not a discovery
|
|
77
|
+
// that the torrent is free — a reading with nothing left in it.
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const uncertainty = Number.isFinite(drawScatter) && drawScatter > 0 ? drawScatter * elapsedSeconds : 0;
|
|
81
|
+
if (!(attributable > uncertainty)) {
|
|
82
|
+
// What is left is inside the disagreement between the draw's own readings.
|
|
83
|
+
// Dividing it by a small number of megabytes is how 145 ms/MB was reported
|
|
84
|
+
// on a host that costs 23.
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return attributable / megabytes;
|
|
88
|
+
}
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
const ID_FILE = "proxy-identity.json";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Resolve this installation's registry id. Without a state directory the id
|
|
9
|
-
* remains ephemeral; with one, the id survives process restarts.
|
|
10
|
-
*
|
|
11
|
-
* @param {{ stateDir?: string, explicitId?: string, makeId?: () => string }} options
|
|
12
|
-
* @returns {string}
|
|
13
|
-
*/
|
|
14
|
-
export function installationId({ stateDir, explicitId, makeId = randomUUID } = {}) {
|
|
15
|
-
const requestedId = typeof explicitId === "string" ? explicitId.trim() : "";
|
|
16
|
-
if (!stateDir) {
|
|
17
|
-
return requestedId || makeId();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const file = path.join(stateDir, ID_FILE);
|
|
21
|
-
let savedId = "";
|
|
22
|
-
if (existsSync(file)) {
|
|
23
|
-
const saved = JSON.parse(readFileSync(file, "utf8"));
|
|
24
|
-
savedId = typeof saved?.id === "string" ? saved.id.trim() : "";
|
|
25
|
-
if (!savedId) {
|
|
26
|
-
throw new Error(`Invalid proxy identity file: ${file}`);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const id = requestedId || savedId || makeId();
|
|
31
|
-
if (id !== savedId) {
|
|
32
|
-
mkdirSync(stateDir, { recursive: true });
|
|
33
|
-
const temporary = `${file}.${process.pid}.tmp`;
|
|
34
|
-
writeFileSync(temporary, `${JSON.stringify({ id })}\n`, { mode: 0o600 });
|
|
35
|
-
renameSync(temporary, file);
|
|
36
|
-
}
|
|
37
|
-
return id;
|
|
38
|
-
}
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
const ID_FILE = "proxy-identity.json";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolve this installation's registry id. Without a state directory the id
|
|
9
|
+
* remains ephemeral; with one, the id survives process restarts.
|
|
10
|
+
*
|
|
11
|
+
* @param {{ stateDir?: string, explicitId?: string, makeId?: () => string }} options
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
export function installationId({ stateDir, explicitId, makeId = randomUUID } = {}) {
|
|
15
|
+
const requestedId = typeof explicitId === "string" ? explicitId.trim() : "";
|
|
16
|
+
if (!stateDir) {
|
|
17
|
+
return requestedId || makeId();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const file = path.join(stateDir, ID_FILE);
|
|
21
|
+
let savedId = "";
|
|
22
|
+
if (existsSync(file)) {
|
|
23
|
+
const saved = JSON.parse(readFileSync(file, "utf8"));
|
|
24
|
+
savedId = typeof saved?.id === "string" ? saved.id.trim() : "";
|
|
25
|
+
if (!savedId) {
|
|
26
|
+
throw new Error(`Invalid proxy identity file: ${file}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const id = requestedId || savedId || makeId();
|
|
31
|
+
if (id !== savedId) {
|
|
32
|
+
mkdirSync(stateDir, { recursive: true });
|
|
33
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
34
|
+
writeFileSync(temporary, `${JSON.stringify({ id })}\n`, { mode: 0o600 });
|
|
35
|
+
renameSync(temporary, file);
|
|
36
|
+
}
|
|
37
|
+
return id;
|
|
38
|
+
}
|