prismcast 1.10.2 → 1.11.0
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/dist/app.d.ts +6 -3
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +140 -86
- package/dist/app.js.map +1 -1
- package/dist/browser/blockedPage.d.ts +80 -0
- package/dist/browser/blockedPage.d.ts.map +1 -0
- package/dist/browser/blockedPage.js +209 -0
- package/dist/browser/blockedPage.js.map +1 -0
- package/dist/browser/browserSupervisor.d.ts +80 -0
- package/dist/browser/browserSupervisor.d.ts.map +1 -0
- package/dist/browser/browserSupervisor.js +186 -0
- package/dist/browser/browserSupervisor.js.map +1 -0
- package/dist/browser/cdp.d.ts +1 -0
- package/dist/browser/cdp.d.ts.map +1 -0
- package/dist/browser/cdp.js +6 -5
- package/dist/browser/cdp.js.map +1 -1
- package/dist/browser/channelSelection.d.ts +45 -14
- package/dist/browser/channelSelection.d.ts.map +1 -0
- package/dist/browser/channelSelection.js +81 -18
- package/dist/browser/channelSelection.js.map +1 -1
- package/dist/browser/consent.d.ts +152 -0
- package/dist/browser/consent.d.ts.map +1 -0
- package/dist/browser/consent.js +418 -0
- package/dist/browser/consent.js.map +1 -0
- package/dist/browser/display.d.ts +1 -0
- package/dist/browser/display.d.ts.map +1 -0
- package/dist/browser/hlsPlaylistObserver.d.ts +55 -0
- package/dist/browser/hlsPlaylistObserver.d.ts.map +1 -0
- package/dist/browser/hlsPlaylistObserver.js +132 -0
- package/dist/browser/hlsPlaylistObserver.js.map +1 -0
- package/dist/browser/index.d.ts +67 -40
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +508 -300
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/launchGovernor.d.ts +89 -0
- package/dist/browser/launchGovernor.d.ts.map +1 -0
- package/dist/browser/launchGovernor.js +96 -0
- package/dist/browser/launchGovernor.js.map +1 -0
- package/dist/browser/login.d.ts +10 -2
- package/dist/browser/login.d.ts.map +1 -0
- package/dist/browser/login.js +36 -5
- package/dist/browser/login.js.map +1 -1
- package/dist/browser/manifestInterceptor.d.ts +102 -49
- package/dist/browser/manifestInterceptor.d.ts.map +1 -0
- package/dist/browser/manifestInterceptor.js +219 -237
- package/dist/browser/manifestInterceptor.js.map +1 -1
- package/dist/browser/pageStaleness.d.ts +30 -0
- package/dist/browser/pageStaleness.d.ts.map +1 -0
- package/dist/browser/pageStaleness.js +76 -0
- package/dist/browser/pageStaleness.js.map +1 -0
- package/dist/browser/precaching.d.ts +95 -3
- package/dist/browser/precaching.d.ts.map +1 -0
- package/dist/browser/precaching.js +499 -65
- package/dist/browser/precaching.js.map +1 -1
- package/dist/browser/tabNetworkObserver.d.ts +49 -0
- package/dist/browser/tabNetworkObserver.d.ts.map +1 -0
- package/dist/browser/tabNetworkObserver.js +144 -0
- package/dist/browser/tabNetworkObserver.js.map +1 -0
- package/dist/browser/tuning/cache.d.ts +61 -0
- package/dist/browser/tuning/cache.d.ts.map +1 -0
- package/dist/browser/tuning/cache.js +62 -0
- package/dist/browser/tuning/cache.js.map +1 -0
- package/dist/browser/tuning/comcastPolymer.d.ts +3 -2
- package/dist/browser/tuning/comcastPolymer.d.ts.map +1 -0
- package/dist/browser/tuning/comcastPolymer.js +79 -102
- package/dist/browser/tuning/comcastPolymer.js.map +1 -1
- package/dist/browser/tuning/cox.d.ts +1 -0
- package/dist/browser/tuning/cox.d.ts.map +1 -0
- package/dist/browser/tuning/directv.d.ts +1 -0
- package/dist/browser/tuning/directv.d.ts.map +1 -0
- package/dist/browser/tuning/directv.js +52 -46
- package/dist/browser/tuning/directv.js.map +1 -1
- package/dist/browser/tuning/fox.d.ts +1 -0
- package/dist/browser/tuning/fox.d.ts.map +1 -0
- package/dist/browser/tuning/fox.js +13 -12
- package/dist/browser/tuning/fox.js.map +1 -1
- package/dist/browser/tuning/gridSearch.d.ts +68 -0
- package/dist/browser/tuning/gridSearch.d.ts.map +1 -0
- package/dist/browser/tuning/gridSearch.js +85 -0
- package/dist/browser/tuning/gridSearch.js.map +1 -0
- package/dist/browser/tuning/hbo.d.ts +1 -0
- package/dist/browser/tuning/hbo.d.ts.map +1 -0
- package/dist/browser/tuning/hbo.js +73 -45
- package/dist/browser/tuning/hbo.js.map +1 -1
- package/dist/browser/tuning/hulu.d.ts +1 -0
- package/dist/browser/tuning/hulu.d.ts.map +1 -0
- package/dist/browser/tuning/hulu.js +128 -173
- package/dist/browser/tuning/hulu.js.map +1 -1
- package/dist/browser/tuning/shared.d.ts +75 -5
- package/dist/browser/tuning/shared.d.ts.map +1 -0
- package/dist/browser/tuning/shared.js +216 -6
- package/dist/browser/tuning/shared.js.map +1 -1
- package/dist/browser/tuning/sling.d.ts +1 -0
- package/dist/browser/tuning/sling.d.ts.map +1 -0
- package/dist/browser/tuning/sling.js +105 -126
- package/dist/browser/tuning/sling.js.map +1 -1
- package/dist/browser/tuning/spectrum.d.ts +1 -0
- package/dist/browser/tuning/spectrum.d.ts.map +1 -0
- package/dist/browser/tuning/spectrum.js +80 -125
- package/dist/browser/tuning/spectrum.js.map +1 -1
- package/dist/browser/tuning/thumbnailRow.d.ts +1 -0
- package/dist/browser/tuning/thumbnailRow.d.ts.map +1 -0
- package/dist/browser/tuning/thumbnailRow.js +8 -2
- package/dist/browser/tuning/thumbnailRow.js.map +1 -1
- package/dist/browser/tuning/tileClick.d.ts +1 -0
- package/dist/browser/tuning/tileClick.d.ts.map +1 -0
- package/dist/browser/tuning/tileClick.js +3 -2
- package/dist/browser/tuning/tileClick.js.map +1 -1
- package/dist/browser/tuning/xfinity.d.ts +1 -0
- package/dist/browser/tuning/xfinity.d.ts.map +1 -0
- package/dist/browser/tuning/youtubeTv.d.ts +1 -0
- package/dist/browser/tuning/youtubeTv.d.ts.map +1 -0
- package/dist/browser/tuning/youtubeTv.js +74 -127
- package/dist/browser/tuning/youtubeTv.js.map +1 -1
- package/dist/browser/video.d.ts +54 -14
- package/dist/browser/video.d.ts.map +1 -0
- package/dist/browser/video.js +296 -138
- package/dist/browser/video.js.map +1 -1
- package/dist/channels/index.d.ts +1 -0
- package/dist/channels/index.d.ts.map +1 -0
- package/dist/channels/index.js +12 -9
- package/dist/channels/index.js.map +1 -1
- package/dist/config/channelForm.d.ts +15 -1
- package/dist/config/channelForm.d.ts.map +1 -0
- package/dist/config/channelForm.js +28 -12
- package/dist/config/channelForm.js.map +1 -1
- package/dist/config/consistencyProbe.d.ts +1 -0
- package/dist/config/consistencyProbe.d.ts.map +1 -0
- package/dist/config/consistencyProbe.js +27 -21
- package/dist/config/consistencyProbe.js.map +1 -1
- package/dist/config/health.d.ts +41 -8
- package/dist/config/health.d.ts.map +1 -0
- package/dist/config/health.js +189 -38
- package/dist/config/health.js.map +1 -1
- package/dist/config/index.d.ts +55 -3
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +320 -69
- package/dist/config/index.js.map +1 -1
- package/dist/config/paths.d.ts +10 -9
- package/dist/config/paths.d.ts.map +1 -0
- package/dist/config/paths.js +11 -11
- package/dist/config/paths.js.map +1 -1
- package/dist/config/persistence.context.d.ts +1 -0
- package/dist/config/persistence.context.d.ts.map +1 -0
- package/dist/config/persistence.d.ts +13 -10
- package/dist/config/persistence.d.ts.map +1 -0
- package/dist/config/persistence.js +40 -21
- package/dist/config/persistence.js.map +1 -1
- package/dist/config/presets.d.ts +1 -0
- package/dist/config/presets.d.ts.map +1 -0
- package/dist/config/profiles.d.ts +22 -15
- package/dist/config/profiles.d.ts.map +1 -0
- package/dist/config/profiles.js +81 -47
- package/dist/config/profiles.js.map +1 -1
- package/dist/config/providerLineups.d.ts +47 -0
- package/dist/config/providerLineups.d.ts.map +1 -0
- package/dist/config/providerLineups.js +137 -0
- package/dist/config/providerLineups.js.map +1 -0
- package/dist/config/reactivity.d.ts +84 -0
- package/dist/config/reactivity.d.ts.map +1 -0
- package/dist/config/reactivity.js +226 -0
- package/dist/config/reactivity.js.map +1 -0
- package/dist/config/servicePacks.d.ts +18 -2
- package/dist/config/servicePacks.d.ts.map +1 -0
- package/dist/config/servicePacks.js +53 -11
- package/dist/config/servicePacks.js.map +1 -1
- package/dist/config/services.d.ts +21 -15
- package/dist/config/services.d.ts.map +1 -0
- package/dist/config/services.js +35 -24
- package/dist/config/services.js.map +1 -1
- package/dist/config/sites.d.ts +6 -4
- package/dist/config/sites.d.ts.map +1 -0
- package/dist/config/sites.js +15 -11
- package/dist/config/sites.js.map +1 -1
- package/dist/config/userChannels.d.ts +129 -30
- package/dist/config/userChannels.d.ts.map +1 -0
- package/dist/config/userChannels.js +333 -68
- package/dist/config/userChannels.js.map +1 -1
- package/dist/config/userConfig.d.ts +14 -9
- package/dist/config/userConfig.d.ts.map +1 -0
- package/dist/config/userConfig.js +87 -25
- package/dist/config/userConfig.js.map +1 -1
- package/dist/config/userProfiles.d.ts +10 -6
- package/dist/config/userProfiles.d.ts.map +1 -0
- package/dist/config/userProfiles.js +64 -39
- package/dist/config/userProfiles.js.map +1 -1
- package/dist/hdhr/channelMap.d.ts +1 -0
- package/dist/hdhr/channelMap.d.ts.map +1 -0
- package/dist/hdhr/deviceId.d.ts +1 -0
- package/dist/hdhr/deviceId.d.ts.map +1 -0
- package/dist/hdhr/discover.d.ts +1 -0
- package/dist/hdhr/discover.d.ts.map +1 -0
- package/dist/hdhr/discover.js +48 -47
- package/dist/hdhr/discover.js.map +1 -1
- package/dist/hdhr/getHandlers.d.ts +29 -0
- package/dist/hdhr/getHandlers.d.ts.map +1 -0
- package/dist/hdhr/getHandlers.js +196 -0
- package/dist/hdhr/getHandlers.js.map +1 -0
- package/dist/hdhr/identity.d.ts +36 -0
- package/dist/hdhr/identity.d.ts.map +1 -0
- package/dist/hdhr/identity.js +51 -0
- package/dist/hdhr/identity.js.map +1 -0
- package/dist/hdhr/index.d.ts +22 -4
- package/dist/hdhr/index.d.ts.map +1 -0
- package/dist/hdhr/index.js +229 -54
- package/dist/hdhr/index.js.map +1 -1
- package/dist/hdhr/protocol.d.ts +75 -0
- package/dist/hdhr/protocol.d.ts.map +1 -0
- package/dist/hdhr/protocol.js +264 -0
- package/dist/hdhr/protocol.js.map +1 -0
- package/dist/hdhr/tunerState.d.ts +24 -0
- package/dist/hdhr/tunerState.d.ts.map +1 -0
- package/dist/hdhr/tunerState.js +67 -0
- package/dist/hdhr/tunerState.js.map +1 -0
- package/dist/hdhr/udp.d.ts +43 -0
- package/dist/hdhr/udp.d.ts.map +1 -0
- package/dist/hdhr/udp.js +277 -0
- package/dist/hdhr/udp.js.map +1 -0
- package/dist/identity.d.ts +1 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +13 -11
- package/dist/identity.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -12
- package/dist/index.js.map +1 -1
- package/dist/native/codecInference.d.ts +1 -0
- package/dist/native/codecInference.d.ts.map +1 -0
- package/dist/native/codecInference.js +50 -5
- package/dist/native/codecInference.js.map +1 -1
- package/dist/native/decrypt.d.ts +6 -2
- package/dist/native/decrypt.d.ts.map +1 -0
- package/dist/native/decrypt.js +15 -8
- package/dist/native/decrypt.js.map +1 -1
- package/dist/native/index.d.ts +40 -17
- package/dist/native/index.d.ts.map +1 -0
- package/dist/native/index.js +281 -120
- package/dist/native/index.js.map +1 -1
- package/dist/native/probe.d.ts +114 -18
- package/dist/native/probe.d.ts.map +1 -0
- package/dist/native/probe.js +452 -115
- package/dist/native/probe.js.map +1 -1
- package/dist/native/proxy.d.ts +57 -5
- package/dist/native/proxy.d.ts.map +1 -0
- package/dist/native/proxy.js +604 -170
- package/dist/native/proxy.js.map +1 -1
- package/dist/native/tokenExpiry.d.ts +1 -0
- package/dist/native/tokenExpiry.d.ts.map +1 -0
- package/dist/native/tokenExpiry.js +1 -1
- package/dist/routes/assets.d.ts +1 -0
- package/dist/routes/assets.d.ts.map +1 -0
- package/dist/routes/assets.js +4 -2
- package/dist/routes/assets.js.map +1 -1
- package/dist/routes/auth.d.ts +1 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +0 -1
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/cdp.d.ts +5 -5
- package/dist/routes/cdp.d.ts.map +1 -0
- package/dist/routes/cdp.js +59 -20
- package/dist/routes/cdp.js.map +1 -1
- package/dist/routes/channels.d.ts +1 -0
- package/dist/routes/channels.d.ts.map +1 -0
- package/dist/routes/channels.js +2 -1
- package/dist/routes/channels.js.map +1 -1
- package/dist/routes/clientActions.d.ts +98 -0
- package/dist/routes/clientActions.d.ts.map +1 -0
- package/dist/routes/clientActions.js +113 -0
- package/dist/routes/clientActions.js.map +1 -0
- package/dist/routes/components.d.ts +5 -3
- package/dist/routes/components.d.ts.map +1 -0
- package/dist/routes/components.js +36 -39
- package/dist/routes/components.js.map +1 -1
- package/dist/routes/config/channels/endpoints/browse.d.ts +1 -0
- package/dist/routes/config/channels/endpoints/browse.d.ts.map +1 -0
- package/dist/routes/config/channels/endpoints/browse.js +23 -10
- package/dist/routes/config/channels/endpoints/browse.js.map +1 -1
- package/dist/routes/config/channels/endpoints/bulk.d.ts +1 -0
- package/dist/routes/config/channels/endpoints/bulk.d.ts.map +1 -0
- package/dist/routes/config/channels/endpoints/bulk.js +5 -3
- package/dist/routes/config/channels/endpoints/bulk.js.map +1 -1
- package/dist/routes/config/channels/endpoints/crud.d.ts +1 -0
- package/dist/routes/config/channels/endpoints/crud.d.ts.map +1 -0
- package/dist/routes/config/channels/endpoints/crud.js +85 -93
- package/dist/routes/config/channels/endpoints/crud.js.map +1 -1
- package/dist/routes/config/channels/endpoints/importExport.d.ts +1 -0
- package/dist/routes/config/channels/endpoints/importExport.d.ts.map +1 -0
- package/dist/routes/config/channels/endpoints/importExport.js +6 -3
- package/dist/routes/config/channels/endpoints/importExport.js.map +1 -1
- package/dist/routes/config/channels/endpoints/predefined.d.ts +1 -0
- package/dist/routes/config/channels/endpoints/predefined.d.ts.map +1 -0
- package/dist/routes/config/channels/endpoints/prefs.d.ts +1 -0
- package/dist/routes/config/channels/endpoints/prefs.d.ts.map +1 -0
- package/dist/routes/config/channels/endpoints/prefs.js +2 -1
- package/dist/routes/config/channels/endpoints/prefs.js.map +1 -1
- package/dist/routes/config/channels/endpoints/service.d.ts +2 -1
- package/dist/routes/config/channels/endpoints/service.d.ts.map +1 -0
- package/dist/routes/config/channels/endpoints/service.js +3 -1
- package/dist/routes/config/channels/endpoints/service.js.map +1 -1
- package/dist/routes/config/channels/endpoints/tags.d.ts +1 -0
- package/dist/routes/config/channels/endpoints/tags.d.ts.map +1 -0
- package/dist/routes/config/channels/endpoints/tags.js +13 -7
- package/dist/routes/config/channels/endpoints/tags.js.map +1 -1
- package/dist/routes/config/channels/healthBridge.d.ts +24 -0
- package/dist/routes/config/channels/healthBridge.d.ts.map +1 -0
- package/dist/routes/config/channels/healthBridge.js +71 -0
- package/dist/routes/config/channels/healthBridge.js.map +1 -0
- package/dist/routes/config/channels/http/handler.d.ts +1 -0
- package/dist/routes/config/channels/http/handler.d.ts.map +1 -0
- package/dist/routes/config/channels/http/playlistHint.d.ts +2 -1
- package/dist/routes/config/channels/http/playlistHint.d.ts.map +1 -0
- package/dist/routes/config/channels/http/playlistHint.js +16 -2
- package/dist/routes/config/channels/http/playlistHint.js.map +1 -1
- package/dist/routes/config/channels/http/serviceWarning.d.ts +4 -2
- package/dist/routes/config/channels/http/serviceWarning.d.ts.map +1 -0
- package/dist/routes/config/channels/http/serviceWarning.js +3 -2
- package/dist/routes/config/channels/http/serviceWarning.js.map +1 -1
- package/dist/routes/config/channels/index.d.ts +1 -0
- package/dist/routes/config/channels/index.d.ts.map +1 -0
- package/dist/routes/config/channels/setup.d.ts +1 -0
- package/dist/routes/config/channels/setup.d.ts.map +1 -0
- package/dist/routes/config/channels/table.d.ts +3 -3
- package/dist/routes/config/channels/table.d.ts.map +1 -0
- package/dist/routes/config/channels/table.js +148 -107
- package/dist/routes/config/channels/table.js.map +1 -1
- package/dist/routes/config/http/envelope.d.ts +1 -0
- package/dist/routes/config/http/envelope.d.ts.map +1 -0
- package/dist/routes/config/index.d.ts +35 -3
- package/dist/routes/config/index.d.ts.map +1 -0
- package/dist/routes/config/index.js +51 -2
- package/dist/routes/config/index.js.map +1 -1
- package/dist/routes/config/services.d.ts +5 -4
- package/dist/routes/config/services.d.ts.map +1 -0
- package/dist/routes/config/services.js +57 -43
- package/dist/routes/config/services.js.map +1 -1
- package/dist/routes/config/settings.d.ts +1 -0
- package/dist/routes/config/settings.d.ts.map +1 -0
- package/dist/routes/config/settings.js +98 -49
- package/dist/routes/config/settings.js.map +1 -1
- package/dist/routes/debug.d.ts +1 -0
- package/dist/routes/debug.d.ts.map +1 -0
- package/dist/routes/debug.js +277 -139
- package/dist/routes/debug.js.map +1 -1
- package/dist/routes/health.d.ts +39 -1
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +33 -27
- package/dist/routes/health.js.map +1 -1
- package/dist/routes/hls.d.ts +1 -0
- package/dist/routes/hls.d.ts.map +1 -0
- package/dist/routes/icons.d.ts +1 -0
- package/dist/routes/icons.d.ts.map +1 -0
- package/dist/routes/icons.js +1 -1
- package/dist/routes/index.d.ts +1 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/logs.d.ts +1 -0
- package/dist/routes/logs.d.ts.map +1 -0
- package/dist/routes/logs.js +14 -4
- package/dist/routes/logs.js.map +1 -1
- package/dist/routes/mpegts.d.ts +1 -0
- package/dist/routes/mpegts.d.ts.map +1 -0
- package/dist/routes/play.d.ts +1 -0
- package/dist/routes/play.d.ts.map +1 -0
- package/dist/routes/playlist.d.ts +1 -0
- package/dist/routes/playlist.d.ts.map +1 -0
- package/dist/routes/playlist.js +7 -2
- package/dist/routes/playlist.js.map +1 -1
- package/dist/routes/root/content.d.ts +8 -5
- package/dist/routes/root/content.d.ts.map +1 -0
- package/dist/routes/root/content.js +29 -26
- package/dist/routes/root/content.js.map +1 -1
- package/dist/routes/root/index.d.ts +2 -1
- package/dist/routes/root/index.d.ts.map +1 -0
- package/dist/routes/root/index.js +26 -21
- package/dist/routes/root/index.js.map +1 -1
- package/dist/routes/root/scripts/channels.d.ts +6 -0
- package/dist/routes/root/scripts/channels.d.ts.map +1 -0
- package/dist/routes/root/scripts/channels.js +197 -118
- package/dist/routes/root/scripts/channels.js.map +1 -1
- package/dist/routes/root/scripts/clientEscape.d.ts +22 -0
- package/dist/routes/root/scripts/clientEscape.d.ts.map +1 -0
- package/dist/routes/root/scripts/clientEscape.js +41 -0
- package/dist/routes/root/scripts/clientEscape.js.map +1 -0
- package/dist/routes/root/scripts/clientUrl.d.ts +23 -0
- package/dist/routes/root/scripts/clientUrl.d.ts.map +1 -0
- package/dist/routes/root/scripts/clientUrl.js +44 -0
- package/dist/routes/root/scripts/clientUrl.js.map +1 -0
- package/dist/routes/root/scripts/config.d.ts +6 -0
- package/dist/routes/root/scripts/config.d.ts.map +1 -0
- package/dist/routes/root/scripts/config.js +110 -24
- package/dist/routes/root/scripts/config.js.map +1 -1
- package/dist/routes/root/scripts/index.d.ts +1 -0
- package/dist/routes/root/scripts/index.d.ts.map +1 -0
- package/dist/routes/root/scripts/shared.d.ts +1 -0
- package/dist/routes/root/scripts/shared.d.ts.map +1 -0
- package/dist/routes/root/scripts/shared.js +112 -26
- package/dist/routes/root/scripts/shared.js.map +1 -1
- package/dist/routes/root/scripts/status.d.ts +1 -0
- package/dist/routes/root/scripts/status.d.ts.map +1 -0
- package/dist/routes/root/scripts/status.handlers.d.ts +23 -33
- package/dist/routes/root/scripts/status.handlers.d.ts.map +1 -0
- package/dist/routes/root/scripts/status.handlers.js +46 -92
- package/dist/routes/root/scripts/status.handlers.js.map +1 -1
- package/dist/routes/root/scripts/status.js +21 -8
- package/dist/routes/root/scripts/status.js.map +1 -1
- package/dist/routes/root/styles.d.ts +1 -0
- package/dist/routes/root/styles.d.ts.map +1 -0
- package/dist/routes/root/styles.js +7 -4
- package/dist/routes/root/styles.js.map +1 -1
- package/dist/routes/services.d.ts +13 -1
- package/dist/routes/services.d.ts.map +1 -0
- package/dist/routes/services.js +81 -68
- package/dist/routes/services.js.map +1 -1
- package/dist/routes/sse.d.ts +1 -0
- package/dist/routes/sse.d.ts.map +1 -0
- package/dist/routes/sse.js +1 -1
- package/dist/routes/sse.js.map +1 -1
- package/dist/routes/streams.d.ts +2 -1
- package/dist/routes/streams.d.ts.map +1 -0
- package/dist/routes/streams.js +6 -4
- package/dist/routes/streams.js.map +1 -1
- package/dist/routes/theme.d.ts +3 -2
- package/dist/routes/theme.d.ts.map +1 -0
- package/dist/routes/theme.js +6 -5
- package/dist/routes/theme.js.map +1 -1
- package/dist/routes/ui.d.ts +3 -1
- package/dist/routes/ui.d.ts.map +1 -0
- package/dist/routes/ui.js +16 -4
- package/dist/routes/ui.js.map +1 -1
- package/dist/routes/upgrade.d.ts +1 -0
- package/dist/routes/upgrade.d.ts.map +1 -0
- package/dist/routes/upgrade.js +6 -2
- package/dist/routes/upgrade.js.map +1 -1
- package/dist/service/commands.context.d.ts +1 -0
- package/dist/service/commands.context.d.ts.map +1 -0
- package/dist/service/commands.context.js +4 -3
- package/dist/service/commands.context.js.map +1 -1
- package/dist/service/commands.d.ts +1 -0
- package/dist/service/commands.d.ts.map +1 -0
- package/dist/service/commands.js +10 -13
- package/dist/service/commands.js.map +1 -1
- package/dist/service/generators.context.d.ts +1 -0
- package/dist/service/generators.context.d.ts.map +1 -0
- package/dist/service/generators.context.js +1 -1
- package/dist/service/generators.context.js.map +1 -1
- package/dist/service/generators.d.ts +4 -3
- package/dist/service/generators.d.ts.map +1 -0
- package/dist/service/generators.js +111 -31
- package/dist/service/generators.js.map +1 -1
- package/dist/service/index.d.ts +1 -0
- package/dist/service/index.d.ts.map +1 -0
- package/dist/streaming/captureLock.d.ts +44 -0
- package/dist/streaming/captureLock.d.ts.map +1 -0
- package/dist/streaming/captureLock.js +95 -0
- package/dist/streaming/captureLock.js.map +1 -0
- package/dist/streaming/captureSession.d.ts +31 -0
- package/dist/streaming/captureSession.d.ts.map +1 -0
- package/dist/streaming/captureSession.js +54 -0
- package/dist/streaming/captureSession.js.map +1 -0
- package/dist/streaming/clients.d.ts +1 -0
- package/dist/streaming/clients.d.ts.map +1 -0
- package/dist/streaming/codec.d.ts +1 -0
- package/dist/streaming/codec.d.ts.map +1 -0
- package/dist/streaming/fmp4Segmenter.d.ts +29 -2
- package/dist/streaming/fmp4Segmenter.d.ts.map +1 -0
- package/dist/streaming/fmp4Segmenter.js +79 -26
- package/dist/streaming/fmp4Segmenter.js.map +1 -1
- package/dist/streaming/hls.d.ts +18 -7
- package/dist/streaming/hls.d.ts.map +1 -0
- package/dist/streaming/hls.js +220 -120
- package/dist/streaming/hls.js.map +1 -1
- package/dist/streaming/hlsResume.d.ts +1 -0
- package/dist/streaming/hlsResume.d.ts.map +1 -0
- package/dist/streaming/hlsResume.js +1 -1
- package/dist/streaming/hlsResume.js.map +1 -1
- package/dist/streaming/hlsSegments.d.ts +59 -2
- package/dist/streaming/hlsSegments.d.ts.map +1 -0
- package/dist/streaming/hlsSegments.js +147 -17
- package/dist/streaming/hlsSegments.js.map +1 -1
- package/dist/streaming/lifecycle.d.ts +4 -1
- package/dist/streaming/lifecycle.d.ts.map +1 -0
- package/dist/streaming/lifecycle.js +65 -85
- package/dist/streaming/lifecycle.js.map +1 -1
- package/dist/streaming/monitor.d.ts +6 -4
- package/dist/streaming/monitor.d.ts.map +1 -0
- package/dist/streaming/monitor.js +343 -145
- package/dist/streaming/monitor.js.map +1 -1
- package/dist/streaming/mp4Parser.d.ts +5 -4
- package/dist/streaming/mp4Parser.d.ts.map +1 -0
- package/dist/streaming/mp4Parser.js +36 -13
- package/dist/streaming/mp4Parser.js.map +1 -1
- package/dist/streaming/mpegts.d.ts +16 -0
- package/dist/streaming/mpegts.d.ts.map +1 -0
- package/dist/streaming/mpegts.js +61 -17
- package/dist/streaming/mpegts.js.map +1 -1
- package/dist/streaming/playlistBuilder.d.ts +1 -0
- package/dist/streaming/playlistBuilder.d.ts.map +1 -0
- package/dist/streaming/preroll.d.ts +32 -5
- package/dist/streaming/preroll.d.ts.map +1 -0
- package/dist/streaming/preroll.js +70 -32
- package/dist/streaming/preroll.js.map +1 -1
- package/dist/streaming/pretune.d.ts +26 -1
- package/dist/streaming/pretune.d.ts.map +1 -0
- package/dist/streaming/pretune.js +22 -24
- package/dist/streaming/pretune.js.map +1 -1
- package/dist/streaming/pretuneTimers.d.ts +24 -0
- package/dist/streaming/pretuneTimers.d.ts.map +1 -0
- package/dist/streaming/pretuneTimers.js +56 -0
- package/dist/streaming/pretuneTimers.js.map +1 -0
- package/dist/streaming/recovery.d.ts +139 -5
- package/dist/streaming/recovery.d.ts.map +1 -0
- package/dist/streaming/recovery.js +160 -16
- package/dist/streaming/recovery.js.map +1 -1
- package/dist/streaming/registry.d.ts +27 -9
- package/dist/streaming/registry.d.ts.map +1 -0
- package/dist/streaming/registry.js +21 -3
- package/dist/streaming/registry.js.map +1 -1
- package/dist/streaming/setup.d.ts +157 -29
- package/dist/streaming/setup.d.ts.map +1 -0
- package/dist/streaming/setup.js +1034 -458
- package/dist/streaming/setup.js.map +1 -1
- package/dist/streaming/showInfo.d.ts +21 -1
- package/dist/streaming/showInfo.d.ts.map +1 -0
- package/dist/streaming/showInfo.js +31 -16
- package/dist/streaming/showInfo.js.map +1 -1
- package/dist/streaming/statusEmitter.d.ts +10 -10
- package/dist/streaming/statusEmitter.d.ts.map +1 -0
- package/dist/streaming/statusEmitter.js +5 -8
- package/dist/streaming/statusEmitter.js.map +1 -1
- package/dist/types/channels.d.ts +16 -4
- package/dist/types/channels.d.ts.map +1 -0
- package/dist/types/channels.js +4 -2
- package/dist/types/channels.js.map +1 -1
- package/dist/types/config.d.ts +16 -5
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/profiles.d.ts +6 -4
- package/dist/types/profiles.d.ts.map +1 -0
- package/dist/types/selection.d.ts +31 -3
- package/dist/types/selection.d.ts.map +1 -0
- package/dist/types/selection.js +4 -2
- package/dist/types/selection.js.map +1 -1
- package/dist/types/shared.d.ts +1 -0
- package/dist/types/shared.d.ts.map +1 -0
- package/dist/types/streaming.d.ts +8 -0
- package/dist/types/streaming.d.ts.map +1 -0
- package/dist/upgrade/commands.context.d.ts +1 -0
- package/dist/upgrade/commands.context.d.ts.map +1 -0
- package/dist/upgrade/commands.context.js +14 -10
- package/dist/upgrade/commands.context.js.map +1 -1
- package/dist/upgrade/commands.d.ts +8 -15
- package/dist/upgrade/commands.d.ts.map +1 -0
- package/dist/upgrade/commands.js +27 -28
- package/dist/upgrade/commands.js.map +1 -1
- package/dist/upgrade/detection.context.d.ts +1 -0
- package/dist/upgrade/detection.context.d.ts.map +1 -0
- package/dist/upgrade/detection.context.js +4 -1
- package/dist/upgrade/detection.context.js.map +1 -1
- package/dist/upgrade/detection.d.ts +9 -7
- package/dist/upgrade/detection.d.ts.map +1 -0
- package/dist/upgrade/detection.js +23 -54
- package/dist/upgrade/detection.js.map +1 -1
- package/dist/upgrade/index.d.ts +2 -0
- package/dist/upgrade/index.d.ts.map +1 -0
- package/dist/upgrade/index.js +1 -0
- package/dist/upgrade/index.js.map +1 -1
- package/dist/upgrade/lifecycle.context.d.ts +20 -0
- package/dist/upgrade/lifecycle.context.d.ts.map +1 -0
- package/dist/upgrade/lifecycle.context.js +47 -0
- package/dist/upgrade/lifecycle.context.js.map +1 -0
- package/dist/upgrade/lifecycle.d.ts +72 -0
- package/dist/upgrade/lifecycle.d.ts.map +1 -0
- package/dist/upgrade/lifecycle.js +175 -0
- package/dist/upgrade/lifecycle.js.map +1 -0
- package/dist/upgrade/pathHandle.d.ts +30 -0
- package/dist/upgrade/pathHandle.d.ts.map +1 -0
- package/dist/upgrade/pathHandle.js +102 -0
- package/dist/upgrade/pathHandle.js.map +1 -0
- package/dist/utils/bootSession.context.d.ts +16 -0
- package/dist/utils/bootSession.context.d.ts.map +1 -0
- package/dist/utils/bootSession.context.js +60 -0
- package/dist/utils/bootSession.context.js.map +1 -0
- package/dist/utils/bootSession.d.ts +17 -0
- package/dist/utils/bootSession.d.ts.map +1 -0
- package/dist/utils/bootSession.js +20 -0
- package/dist/utils/bootSession.js.map +1 -0
- package/dist/utils/chromeFetch.d.ts +1 -0
- package/dist/utils/chromeFetch.d.ts.map +1 -0
- package/dist/utils/cliOutput.d.ts +1 -0
- package/dist/utils/cliOutput.d.ts.map +1 -0
- package/dist/utils/clock.d.ts +5 -4
- package/dist/utils/clock.d.ts.map +1 -0
- package/dist/utils/clock.js +19 -16
- package/dist/utils/clock.js.map +1 -1
- package/dist/utils/debugFilter.d.ts +9 -1
- package/dist/utils/debugFilter.d.ts.map +1 -0
- package/dist/utils/debugFilter.js +60 -33
- package/dist/utils/debugFilter.js.map +1 -1
- package/dist/utils/delay.d.ts +48 -15
- package/dist/utils/delay.d.ts.map +1 -0
- package/dist/utils/delay.js +73 -20
- package/dist/utils/delay.js.map +1 -1
- package/dist/utils/errors.d.ts +32 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +45 -4
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/evaluate.d.ts +1 -0
- package/dist/utils/evaluate.d.ts.map +1 -0
- package/dist/utils/evaluate.js +29 -23
- package/dist/utils/evaluate.js.map +1 -1
- package/dist/utils/ffmpeg.context.d.ts +1 -0
- package/dist/utils/ffmpeg.context.d.ts.map +1 -0
- package/dist/utils/ffmpeg.context.js.map +1 -1
- package/dist/utils/ffmpeg.d.ts +29 -7
- package/dist/utils/ffmpeg.d.ts.map +1 -0
- package/dist/utils/ffmpeg.js +32 -14
- package/dist/utils/ffmpeg.js.map +1 -1
- package/dist/utils/fileLogger.d.ts +3 -1
- package/dist/utils/fileLogger.d.ts.map +1 -0
- package/dist/utils/fileLogger.js +64 -29
- package/dist/utils/fileLogger.js.map +1 -1
- package/dist/utils/format.d.ts +14 -1
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +19 -5
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logEmitter.d.ts +1 -0
- package/dist/utils/logEmitter.d.ts.map +1 -0
- package/dist/utils/logger.d.ts +1 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +10 -6
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/m3u.d.ts +3 -2
- package/dist/utils/m3u.d.ts.map +1 -0
- package/dist/utils/m3u.js +4 -3
- package/dist/utils/m3u.js.map +1 -1
- package/dist/utils/markup.d.ts +13 -1
- package/dist/utils/markup.d.ts.map +1 -0
- package/dist/utils/markup.js +28 -4
- package/dist/utils/markup.js.map +1 -1
- package/dist/utils/memo.d.ts +3 -2
- package/dist/utils/memo.d.ts.map +1 -0
- package/dist/utils/memo.js +3 -3
- package/dist/utils/morganStream.d.ts +1 -0
- package/dist/utils/morganStream.d.ts.map +1 -0
- package/dist/utils/network.d.ts +1 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +2 -2
- package/dist/utils/pid.d.ts +3 -25
- package/dist/utils/pid.d.ts.map +1 -0
- package/dist/utils/pid.js +10 -49
- package/dist/utils/pid.js.map +1 -1
- package/dist/utils/platform.d.ts +3 -1
- package/dist/utils/platform.d.ts.map +1 -0
- package/dist/utils/platform.js +2 -1
- package/dist/utils/platform.js.map +1 -1
- package/dist/utils/processInspector.context.d.ts +8 -0
- package/dist/utils/processInspector.context.d.ts.map +1 -0
- package/dist/utils/processInspector.context.js +106 -0
- package/dist/utils/processInspector.context.js.map +1 -0
- package/dist/utils/processInspector.d.ts +55 -0
- package/dist/utils/processInspector.d.ts.map +1 -0
- package/dist/utils/processInspector.js +127 -0
- package/dist/utils/processInspector.js.map +1 -0
- package/dist/utils/retry.d.ts +25 -2
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +33 -6
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/runtimeIdentity.context.d.ts +7 -0
- package/dist/utils/runtimeIdentity.context.d.ts.map +1 -0
- package/dist/utils/runtimeIdentity.context.js +43 -0
- package/dist/utils/runtimeIdentity.context.js.map +1 -0
- package/dist/utils/runtimeIdentity.d.ts +98 -0
- package/dist/utils/runtimeIdentity.d.ts.map +1 -0
- package/dist/utils/runtimeIdentity.js +181 -0
- package/dist/utils/runtimeIdentity.js.map +1 -0
- package/dist/utils/sanitize.d.ts +1 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/streamContext.d.ts +2 -1
- package/dist/utils/streamContext.d.ts.map +1 -0
- package/dist/utils/streamContext.js +1 -1
- package/dist/utils/timing.d.ts +2 -1
- package/dist/utils/timing.d.ts.map +1 -0
- package/dist/utils/timing.js +0 -5
- package/dist/utils/timing.js.map +1 -1
- package/dist/utils/version.d.ts +1 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +47 -13
- package/dist/utils/version.js.map +1 -1
- package/package.json +26 -20
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { CHANNEL_BINDING_KEYS, CHANNEL_IDENTITY_KEYS, DELTA_ELIGIBLE_BINDING_KEYS, DELTA_ELIGIBLE_IDENTITY_KEYS } from "../types/index.js";
|
|
6
6
|
import { FileStoreParseError, createFileStore } from "./persistence.js";
|
|
7
|
-
import { LOG, containsNonPrintable, sanitizeString } from "../utils/index.js";
|
|
7
|
+
import { LOG, containsNonPrintable, extractDomain, sanitizeString } from "../utils/index.js";
|
|
8
8
|
import { PREDEFINED_CHANNELS, PREDEFINED_TAGS } from "../channels/index.js";
|
|
9
9
|
import { buildServiceGroups, getAllServiceTags, getResolvedChannel, isChannelAvailableByService, isServiceVariant, resolveServiceKey, setEnabledServices, setServiceSelections } from "./services.js";
|
|
10
10
|
import { CONFIG } from "./index.js";
|
|
@@ -46,8 +46,9 @@ export function getChannelsParseErrorMessage() {
|
|
|
46
46
|
return userChannelsParseErrorMessage;
|
|
47
47
|
}
|
|
48
48
|
/* Transactional store for channels.json. The store uses a compound type that carries channel data alongside metadata (serviceSelections, tagRegistry). The parse
|
|
49
|
-
* function extracts
|
|
50
|
-
* type so callers work directly with StoredChannelMap. The beforeWrite hook
|
|
49
|
+
* function extracts the channel data and its metadata fields from the JSON file. The mutateChannels() wrapper normalizes predefined channel deltas before the
|
|
50
|
+
* write and adapts the compound type so callers work directly with StoredChannelMap. The beforeWrite hook (prepareChannelsForWrite) derives every field of the
|
|
51
|
+
* on-disk shape from the mutation's data envelope itself, never from module state.
|
|
51
52
|
*/
|
|
52
53
|
/* Current schema version for channels.json. Migrations are declared in channelsMigrations below; the framework runs them in order from the file's stored
|
|
53
54
|
* version up to this constant, stamps the new version after each, and records the audit trail in migrationsApplied.
|
|
@@ -58,7 +59,7 @@ export function getChannelsParseErrorMessage() {
|
|
|
58
59
|
* stamping canonicalKey on entries whose identity fields match the canonical (shape-compatible with a legitimate variant) and skipping entries whose
|
|
59
60
|
* fields differ (user standalones that happened to share a hyphenated key with a predefined canonical).
|
|
60
61
|
* 3 - Foxone-era service naming. The v2 -> v3 migration renames "foxcom" -> "foxone" in service selections and channel keys, and the special-case
|
|
61
|
-
* "fox-site" -> "fox-foxone" rename for the FoxOne variant
|
|
62
|
+
* "fox-site" -> "fox-foxone" rename for the FoxOne variant stored under the "fox-site" key. Bumping this version also flushes parser-side legacy field
|
|
62
63
|
* cleanups (provider -> service field, providerSelections -> serviceSelections key) to disk on the same write that lands the foxone rename.
|
|
63
64
|
*/
|
|
64
65
|
const CURRENT_CHANNELS_SCHEMA_VERSION = 3;
|
|
@@ -79,7 +80,7 @@ function parseChannelsFile(raw) {
|
|
|
79
80
|
let schemaVersion = 1;
|
|
80
81
|
for (const [key, value] of Object.entries(parsed)) {
|
|
81
82
|
if (key === "schemaVersion") {
|
|
82
|
-
// Tolerate bad data by falling back to version 1 - any plausibly-legacy value is fine, since migrations are
|
|
83
|
+
// Tolerate bad data by falling back to version 1 - any plausibly-legacy value is fine, since migrations are a no-op when no work is needed.
|
|
83
84
|
if ((typeof value === "number") && Number.isFinite(value) && (value >= 1)) {
|
|
84
85
|
schemaVersion = Math.floor(value);
|
|
85
86
|
}
|
|
@@ -136,18 +137,18 @@ function parseChannelsFile(raw) {
|
|
|
136
137
|
}
|
|
137
138
|
return { channels, migrationsApplied, schemaVersion, serviceSelections, tagRegistry };
|
|
138
139
|
}
|
|
139
|
-
/* Array-valued ChannelDelta fields whose equality requires canonical case-insensitive ordering before
|
|
140
|
-
* unordered-set semantics rather than order-significant lists; equality for these fields
|
|
141
|
-
* not defeat the match. Tags are the only such field today, but the property-based framing is the architectural truth:
|
|
142
|
-
* this set. The `satisfies` constraint keeps the list in sync with ChannelDelta's actual keys at compile time -
|
|
143
|
-
* to be updated.
|
|
140
|
+
/* Array-valued ChannelDelta fields whose equality requires canonical case-insensitive ordering before structural comparison via isDeepStrictEqual. The set
|
|
141
|
+
* declares which array fields have unordered-set semantics rather than order-significant lists; equality for these fields runs both sides through sortTags so
|
|
142
|
+
* authoring-order or case differences do not defeat the match. Tags are the only such field today, but the property-based framing is the architectural truth:
|
|
143
|
+
* any unordered-set array field belongs in this set. The `satisfies` constraint keeps the list in sync with ChannelDelta's actual keys at compile time -
|
|
144
|
+
* renaming or removing a field forces this tuple to be updated.
|
|
144
145
|
*/
|
|
145
146
|
const CANONICAL_SORTED_ARRAY_FIELDS = new Set(["tags"]);
|
|
146
147
|
/**
|
|
147
148
|
* Normalizes a single stored entry as a delta against a base channel. Delta fields are dropped when they are no-ops: undefined values, nulls against a base
|
|
148
|
-
* that has no such field, and values that match the base exactly. Array-valued fields use
|
|
149
|
-
* for the fields listed in CANONICAL_SORTED_ARRAY_FIELDS so authoring-order differences do not defeat the match. Non-delta fields (e.g.,
|
|
150
|
-
* through unchanged so the stored entry retains its relationship metadata.
|
|
149
|
+
* that has no such field, and values that match the base exactly. Array-valued fields use isDeepStrictEqual for structural equality, with sortTags applied to
|
|
150
|
+
* both sides for the fields listed in CANONICAL_SORTED_ARRAY_FIELDS so authoring-order differences do not defeat the match. Non-delta fields (e.g.,
|
|
151
|
+
* canonicalKey) pass through unchanged so the stored entry retains its relationship metadata.
|
|
151
152
|
* @param stored - The raw stored entry.
|
|
152
153
|
* @param base - The base Channel to diff against (predefined definition for overrides, resolved canonical for variants).
|
|
153
154
|
* @returns The normalized entry, or null when every field is a no-op and the entry would carry no information.
|
|
@@ -183,8 +184,8 @@ function normalizeEntryAgainstBase(stored, base) {
|
|
|
183
184
|
}
|
|
184
185
|
continue;
|
|
185
186
|
}
|
|
186
|
-
// Values that match the base exactly are redundant. Arrays compare
|
|
187
|
-
// first so the equality check is order-independent.
|
|
187
|
+
// Values that match the base exactly are redundant. Arrays compare structurally via isDeepStrictEqual; fields in CANONICAL_SORTED_ARRAY_FIELDS are routed
|
|
188
|
+
// through sortTags on both sides first so the equality check is order-independent.
|
|
188
189
|
const baseValue = base[field];
|
|
189
190
|
if (Array.isArray(value)) {
|
|
190
191
|
const canonical = CANONICAL_SORTED_ARRAY_FIELDS.has(field);
|
|
@@ -237,8 +238,8 @@ function classifyEntry(key, stored) {
|
|
|
237
238
|
* Computes the field-name allowlist for a stored entry's expected shape. Stored entries fall into two structural shapes determined by classification:
|
|
238
239
|
*
|
|
239
240
|
* - Canonical-shaped (kind = canonical or standalone): allowed fields are DELTA_ELIGIBLE_IDENTITY_KEYS ∪ DELTA_ELIGIBLE_BINDING_KEYS. The full delta surface.
|
|
240
|
-
* - Variant-shaped (kind = variant): allowed fields are DELTA_ELIGIBLE_BINDING_KEYS plus the structural canonicalKey
|
|
241
|
-
* canonical and must not appear on variants per the architectural principle.
|
|
241
|
+
* - Variant-shaped (kind = variant): allowed fields are DELTA_ELIGIBLE_BINDING_KEYS plus the structural canonicalKey field that marks the kind. Identity
|
|
242
|
+
* inherits from the canonical and must not appear on variants per the architectural principle.
|
|
242
243
|
*
|
|
243
244
|
* Single source of truth for the shape rule. Used by filterToDeltaSurface (normalizer pre-strip) and by the customization accessor's per-pass walks. Routes
|
|
244
245
|
* shape determination through classifyEntry so any caller automatically picks up classifier improvements - no parallel logic for "is this a variant?".
|
|
@@ -297,7 +298,7 @@ function filterToDeltaSurface(key, stored) {
|
|
|
297
298
|
function resolveVariant(canonical, predefined, stored) {
|
|
298
299
|
// Identity-only base. The url field (required on ResolvedChannel) is supplied by the predefined variant or stored delta below; the cast acknowledges that
|
|
299
300
|
// intermediate state. After the chain completes, the result has url populated when at least one of the binding-bearing layers (predefined or stored) is
|
|
300
|
-
// present - which is the documented
|
|
301
|
+
// present - which is the documented guarantee for any well-formed variant entry.
|
|
301
302
|
let resolved = pickIdentity(canonical);
|
|
302
303
|
// Variant overlays apply binding fields only - identity stays from the canonical per the architectural principle that variants are pure tuning data. Both
|
|
303
304
|
// the predefined variant and the user's stored variant entry use the same binding-only overlay.
|
|
@@ -350,7 +351,7 @@ function warnDanglingCanonical(variantKey, canonicalKey) {
|
|
|
350
351
|
*/
|
|
351
352
|
function buildResolvedCanonicals(stored) {
|
|
352
353
|
const result = {};
|
|
353
|
-
const allKeys = new Set(
|
|
354
|
+
const allKeys = new Set(Object.keys(PREDEFINED_CHANNELS)).union(new Set(Object.keys(stored)));
|
|
354
355
|
for (const key of allKeys) {
|
|
355
356
|
const classification = classifyEntry(key, stored[key]);
|
|
356
357
|
if (classification.kind === "variant") {
|
|
@@ -378,26 +379,216 @@ function buildResolvedCanonicals(stored) {
|
|
|
378
379
|
function stripNulls(stored) {
|
|
379
380
|
return Object.fromEntries(Object.entries(stored).filter(([, v]) => v !== null));
|
|
380
381
|
}
|
|
382
|
+
/* Module-private partition Sets, derived once from the type-system source of truth. The Sets back the public picker functions below; callers never reference
|
|
383
|
+
* the Sets directly. Adding or renaming a field in CHANNEL_IDENTITY_KEYS / CHANNEL_BINDING_KEYS automatically updates both Sets at runtime - the partition
|
|
384
|
+
* lives in types/channels.ts and these are the single derived runtime form.
|
|
385
|
+
*/
|
|
386
|
+
const IDENTITY_FIELDS = new Set(CHANNEL_IDENTITY_KEYS);
|
|
387
|
+
const BINDING_FIELDS = new Set(CHANNEL_BINDING_KEYS);
|
|
388
|
+
/**
|
|
389
|
+
* Internal: filters a delta to fields in the supplied allowlist. Backs pickIdentityFields and pickBindingFields. Not exported - the public surface is the
|
|
390
|
+
* named pickers, which hide the partition Sets so consumers never have to know how the partition is enumerated.
|
|
391
|
+
*/
|
|
392
|
+
function filterDeltaFields(delta, allowlist) {
|
|
393
|
+
const filtered = {};
|
|
394
|
+
for (const [field, value] of Object.entries(delta)) {
|
|
395
|
+
if (allowlist.has(field)) {
|
|
396
|
+
filtered[field] = value;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return filtered;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Returns the identity-only subset of a ChannelDelta - the fields enumerated by CHANNEL_IDENTITY_KEYS. Used by the per-field write router (PUT handler) and
|
|
403
|
+
* the storage normalizer's heal path to split a full delta into identity-only and binding-only halves so each half is routed to the correct stored entry.
|
|
404
|
+
* @param delta - The delta to project.
|
|
405
|
+
* @returns A new delta with only identity fields retained.
|
|
406
|
+
*/
|
|
407
|
+
export function pickIdentityFields(delta) {
|
|
408
|
+
return filterDeltaFields(delta, IDENTITY_FIELDS);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Returns the binding-only subset of a ChannelDelta - the fields enumerated by CHANNEL_BINDING_KEYS. Peer to pickIdentityFields; together they cover the
|
|
412
|
+
* delta surface and partition it cleanly.
|
|
413
|
+
* @param delta - The delta to project.
|
|
414
|
+
* @returns A new delta with only binding fields retained.
|
|
415
|
+
*/
|
|
416
|
+
export function pickBindingFields(delta) {
|
|
417
|
+
return filterDeltaFields(delta, BINDING_FIELDS);
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Applies a binding delta to an existing variant entry with replace semantics: any prior binding fields on the existing entry are stripped first, then the
|
|
421
|
+
* new delta's binding fields are applied. Non-binding fields (notably canonicalKey) on the existing entry are preserved. Returns null when the resulting
|
|
422
|
+
* entry would be empty.
|
|
423
|
+
*
|
|
424
|
+
* Used by the producer (handlePredefinedEdit) when the user submits a form with explicit values - the form values are the authoritative declaration of the
|
|
425
|
+
* variant's binding state, so any pre-existing binding override on the variant entry is wiped.
|
|
426
|
+
*
|
|
427
|
+
* Pair with mergeVariantBinding (preserve-existing semantics) below. The two functions name the precedence rule explicitly so future callers pick the right
|
|
428
|
+
* semantic for their context.
|
|
429
|
+
* @param existing - The existing variant entry, if any.
|
|
430
|
+
* @param delta - The new binding-only delta. The producer is asserting these are the authoritative values.
|
|
431
|
+
* @returns The merged stored entry, or null when the result would be empty.
|
|
432
|
+
*/
|
|
433
|
+
export function replaceVariantBinding(existing, delta) {
|
|
434
|
+
const next = { ...existing };
|
|
435
|
+
// Strip prior binding-field overrides; the new delta is authoritative for this category.
|
|
436
|
+
for (const field of CHANNEL_BINDING_KEYS) {
|
|
437
|
+
Reflect.deleteProperty(next, field);
|
|
438
|
+
}
|
|
439
|
+
// Apply the new binding-field overrides.
|
|
440
|
+
Object.assign(next, delta);
|
|
441
|
+
return (Object.keys(next).length > 0) ? next : null;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Merges a binding delta into an existing variant entry with preserve-existing semantics: existing variant fields win on conflicts; the delta only fills
|
|
445
|
+
* fields the variant doesn't already declare. Always returns a non-empty entry (caller is expected to invoke this only when delta has something to contribute).
|
|
446
|
+
*
|
|
447
|
+
* Used by the storage normalizer's canonical-binding overlap heal. The heal context is healing legacy data, not recording a new explicit user intent - the
|
|
448
|
+
* variant's prior customization is more recent or at least equally legitimate as the canonical-side stored binding being moved over, so the variant wins.
|
|
449
|
+
*
|
|
450
|
+
* Pair with replaceVariantBinding (replace semantics) above. The two functions name the precedence rule explicitly.
|
|
451
|
+
* @param existing - The existing variant entry, if any. Existing fields take precedence on conflicts.
|
|
452
|
+
* @param delta - The binding-only delta whose fields fill any gaps the existing entry doesn't declare.
|
|
453
|
+
* @returns The merged stored entry. Always non-empty (delta contributes at least the keys the existing didn't have).
|
|
454
|
+
*/
|
|
455
|
+
export function mergeVariantBinding(existing, delta) {
|
|
456
|
+
const merged = { ...delta };
|
|
457
|
+
if (existing) {
|
|
458
|
+
Object.assign(merged, existing);
|
|
459
|
+
}
|
|
460
|
+
return merged;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Intersects a binding delta with a criterion delta by key, returning a new binding-only delta containing fields that appear in BOTH and using values from
|
|
464
|
+
* the primary delta. Used by the URL-inferred branch of the per-field write router: when a user submits a form with no pre-selected variant but a URL that
|
|
465
|
+
* implies a sibling, the producer must avoid treating unset form fields as explicit clears against the inferred variant's predefined defaults. Restricting
|
|
466
|
+
* the variant-relative delta to fields the user actually changed (relative to the canonical) prevents that silent destruction of tuning data.
|
|
467
|
+
*
|
|
468
|
+
* Pure function - operates on two deltas independently of any storage state. Generic over the criterion: any binding field present on `criterion` makes
|
|
469
|
+
* `primary`'s value for that field eligible to land on the result.
|
|
470
|
+
* @param primary - The delta whose values populate the result.
|
|
471
|
+
* @param criterion - The delta whose key set restricts which fields propagate.
|
|
472
|
+
* @returns A new binding-only delta with primary's values for keys present in both, restricted to CHANNEL_BINDING_KEYS.
|
|
473
|
+
*/
|
|
474
|
+
export function intersectBindingDeltas(primary, criterion) {
|
|
475
|
+
const restricted = {};
|
|
476
|
+
for (const field of CHANNEL_BINDING_KEYS) {
|
|
477
|
+
if ((field in criterion) && (field in primary)) {
|
|
478
|
+
restricted[field] = primary[field];
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return restricted;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Given a canonical key and a URL, returns the sibling variant key whose effective URL extracts to the same domain. Returns undefined when no sibling matches -
|
|
485
|
+
* that is the legitimate "Custom URL" case where the user genuinely has a non-predefined URL.
|
|
486
|
+
*
|
|
487
|
+
* Used by both the producer (handlePredefinedEdit in routes/config/channels/endpoints/crud.ts) and the storage normalizer (normalizeChannelDeltas) to enforce
|
|
488
|
+
* the rule that a canonical override's binding fields must not duplicate a sibling variant's domain. When a sibling matches, the user's intent is "default this
|
|
489
|
+
* channel to the sibling's service" - architecturally expressed via serviceSelections, not via overriding the canonical URL. Both call sites share this single
|
|
490
|
+
* helper so the inference rule lives in exactly one place.
|
|
491
|
+
*
|
|
492
|
+
* Pure function. Walks predefined and user-stored siblings, sorts by key for determinism (multiple variants with the same domain pick the alphabetically-first
|
|
493
|
+
* one), and returns the first match. Effective URL is the user-stored override (if any string-valued) layered onto the predefined variant URL - same precedence
|
|
494
|
+
* as resolveVariant.
|
|
495
|
+
* @param canonicalKey - The canonical channel key whose siblings should be searched.
|
|
496
|
+
* @param submittedUrl - The URL being matched. Empty/undefined returns undefined.
|
|
497
|
+
* @param channels - The stored channels map snapshot for finding user-defined sibling variants.
|
|
498
|
+
* @returns The matching variant key, or undefined when no sibling's URL domain matches.
|
|
499
|
+
*/
|
|
500
|
+
export function inferTargetVariant(canonicalKey, submittedUrl, channels) {
|
|
501
|
+
if ((submittedUrl === undefined) || (submittedUrl === null) || (submittedUrl === "")) {
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
const userDomain = extractDomain(submittedUrl);
|
|
505
|
+
// Enumerate sibling variant keys: predefined variants and user-stored variants whose canonicalKey points at this canonical. Excludes the canonical itself.
|
|
506
|
+
// Dedupe via Set since a key may appear in both maps (user override of a predefined variant).
|
|
507
|
+
const siblingKeys = new Set();
|
|
508
|
+
for (const [predefinedKey, predefinedChannel] of Object.entries(PREDEFINED_CHANNELS)) {
|
|
509
|
+
if ((predefinedKey !== canonicalKey) && (predefinedChannel.canonicalKey === canonicalKey)) {
|
|
510
|
+
siblingKeys.add(predefinedKey);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
for (const [storedKey, storedChannel] of Object.entries(channels)) {
|
|
514
|
+
if ((storedKey !== canonicalKey) && (storedChannel.canonicalKey === canonicalKey)) {
|
|
515
|
+
siblingKeys.add(storedKey);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
// Sort for deterministic pick when multiple siblings share a domain.
|
|
519
|
+
const sortedKeys = Array.from(siblingKeys).toSorted();
|
|
520
|
+
for (const siblingKey of sortedKeys) {
|
|
521
|
+
// Effective URL: user override (when string-valued) takes precedence over the predefined variant's URL. A null delta value clears the field, falling back
|
|
522
|
+
// to the predefined; an absent field inherits, also falling back. Both collapse to "use the predefined URL."
|
|
523
|
+
const stored = channels[siblingKey];
|
|
524
|
+
const predefined = PREDEFINED_CHANNELS[siblingKey];
|
|
525
|
+
const storedUrl = stored ? stored.url : undefined;
|
|
526
|
+
const effectiveUrl = (typeof storedUrl === "string") ? storedUrl : predefined?.url;
|
|
527
|
+
if (!effectiveUrl) {
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
if (extractDomain(effectiveUrl) === userDomain) {
|
|
531
|
+
return siblingKey;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return undefined;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Detects whether any canonical override in the stored map carries binding whose URL extracts to a sibling variant's domain. Used by initializeUserChannels to
|
|
538
|
+
* conditionally trigger a one-shot startup heal pass via mutateChannels - the normalizer does the actual work, this probe just decides whether the trigger is
|
|
539
|
+
* needed so unaffected users incur no boot-time write.
|
|
540
|
+
*
|
|
541
|
+
* Cheap walk: classify, check stored URL, call inferTargetVariant. Returns on the first match - we only need to know whether at least one entry needs healing.
|
|
542
|
+
* @param channels - The stored channels map snapshot to probe.
|
|
543
|
+
* @returns True when at least one canonical override would be redirected by the normalizer.
|
|
544
|
+
*/
|
|
545
|
+
function hasCanonicalBindingOverlap(channels) {
|
|
546
|
+
for (const [key, stored] of Object.entries(channels)) {
|
|
547
|
+
const classification = classifyEntry(key, stored);
|
|
548
|
+
if (classification.kind !== "canonical") {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
const url = stored.url;
|
|
552
|
+
if (typeof url !== "string") {
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
if (inferTargetVariant(key, url, channels)) {
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
381
561
|
/**
|
|
382
562
|
* Normalizes the stored channels map to its minimal delta form. Each entry is classified, diffed against its applicable base, and any no-op fields are
|
|
383
|
-
* stripped. Standalone user channels have no base, so nulls are stripped but everything else is preserved.
|
|
563
|
+
* stripped. Standalone user channels have no base, so nulls are stripped but everything else is preserved. Mutates the data in place so the caller's reference
|
|
564
|
+
* (the file store framework's data envelope) reflects normalized state on return - matching the migration `apply` convention so the same shape is used wherever
|
|
565
|
+
* the framework runs writes.
|
|
384
566
|
*
|
|
385
567
|
* The flow is uniform across kinds:
|
|
386
568
|
*
|
|
387
|
-
* - Canonical (predefined with user override): base is the predefined channel.
|
|
569
|
+
* - Canonical (predefined with user override): base is the predefined channel. Before delta minimization, any binding fields whose URL extracts to a sibling
|
|
570
|
+
* variant's domain are healed by routing them to the matching variant entry and recording a serviceSelections redirect - the same per-field routing the
|
|
571
|
+
* producer (handlePredefinedEdit) performs at write time. This makes the rule "canonical overrides only carry canonical-service binding" structurally
|
|
572
|
+
* self-healing for any data path that misses the producer (legacy data, hand-edited files, future producers).
|
|
388
573
|
* - Variant: base is the resolved canonical layered with the predefined variant's service fields (the view the user sees in the form/table, so the delta
|
|
389
574
|
* records only what differs from that).
|
|
390
575
|
* - Standalone: no base, null values are stripped as a storage convention.
|
|
391
576
|
*
|
|
392
577
|
* Entries whose normalized delta is empty collapse to nothing and get dropped - a redundant override carries no information, and resolution falls through to
|
|
393
578
|
* the default.
|
|
394
|
-
* @param
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
579
|
+
* @param data - The full channels file data envelope. Channels and serviceSelections are both mutated in place.
|
|
580
|
+
*/
|
|
581
|
+
function normalizeChannelDeltas(data) {
|
|
582
|
+
const resolvedCanonicals = buildResolvedCanonicals(data.channels);
|
|
583
|
+
// Snapshot keys before iteration: the canonical-binding heal may write new variant entries to data.channels (heal-created variants when the matched sibling
|
|
584
|
+
// had no prior user override), and we don't want to re-process those in this pass. Heal-created entries are written in already-minimal delta shape, so
|
|
585
|
+
// skipping them is correct.
|
|
586
|
+
const initialKeys = Object.keys(data.channels);
|
|
587
|
+
for (const key of initialKeys) {
|
|
588
|
+
const stored = data.channels[key];
|
|
589
|
+
if (!stored) {
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
401
592
|
// Step 1: enforce the expected stored shape via filterToDeltaSurface. Strips orphan fields (non-delta-eligible identity, non-delta-eligible binding,
|
|
402
593
|
// identity-on-variants) before any diff computation. When fields are stripped, log a warning so operators see when their data is being cleaned. The shape
|
|
403
594
|
// rule is the same regardless of classification kind - canonicalKey-discriminated through filterToDeltaSurface internally.
|
|
@@ -409,24 +600,59 @@ function normalizeChannelDeltas(channels) {
|
|
|
409
600
|
// Step 2: classification-driven delta minimization against the appropriate base.
|
|
410
601
|
const classification = classifyEntry(key, shapeFiltered);
|
|
411
602
|
if (classification.kind === "canonical") {
|
|
603
|
+
// Step 2a: canonical-binding overlap heal. When the canonical override carries binding whose URL extracts to a sibling variant's domain, the user's
|
|
604
|
+
// intent is "default this channel to the sibling's service" - architecturally expressed via serviceSelections, not via overriding the canonical URL.
|
|
605
|
+
// Strip the binding fields from the canonical, propagate any binding that diverges from the matching variant's predefined defaults to the variant
|
|
606
|
+
// entry via mergeVariantBinding (preserve-existing semantics: the variant's prior user customization wins on field conflicts), and record the
|
|
607
|
+
// redirect. Distinct from the producer's replaceVariantBinding semantic - the heal is healing legacy data, not recording a new explicit user intent.
|
|
608
|
+
const canonicalDelta = shapeFiltered;
|
|
609
|
+
const targetVariantKey = inferTargetVariant(key, canonicalDelta.url, data.channels);
|
|
610
|
+
if (targetVariantKey) {
|
|
611
|
+
const targetPredefinedVariant = PREDEFINED_CHANNELS[targetVariantKey];
|
|
612
|
+
// For predefined siblings, diff the canonical's binding against the predefined variant's binding so only divergent fields land on the variant entry.
|
|
613
|
+
// For user-only siblings (no predefined entry), the matched variant already has its own binding from when the user created it; the canonical's
|
|
614
|
+
// binding simply represents redundant duplication and is dropped without modifying the variant entry.
|
|
615
|
+
if (targetPredefinedVariant) {
|
|
616
|
+
const bindingOnly = pickBindingFields(canonicalDelta);
|
|
617
|
+
const variantDelta = normalizeEntryAgainstBase(bindingOnly, targetPredefinedVariant);
|
|
618
|
+
if (variantDelta) {
|
|
619
|
+
data.channels[targetVariantKey] = mergeVariantBinding(data.channels[targetVariantKey], variantDelta);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
data.serviceSelections[key] = targetVariantKey;
|
|
623
|
+
LOG.info("Resolved canonical-binding overlap for '%s'; redirected default service to variant '%s'.", key, targetVariantKey);
|
|
624
|
+
// Continue normalization with the binding-stripped canonical delta. If only identity remains and matches the predefined canonical, the entry collapses.
|
|
625
|
+
const identityOnly = pickIdentityFields(canonicalDelta);
|
|
626
|
+
const normalizedIdentity = normalizeEntryAgainstBase(identityOnly, classification.predefined);
|
|
627
|
+
if (normalizedIdentity) {
|
|
628
|
+
data.channels[key] = normalizedIdentity;
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
Reflect.deleteProperty(data.channels, key);
|
|
632
|
+
}
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
412
635
|
const normalized = normalizeEntryAgainstBase(shapeFiltered, classification.predefined);
|
|
413
636
|
if (normalized) {
|
|
414
|
-
|
|
637
|
+
data.channels[key] = normalized;
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
Reflect.deleteProperty(data.channels, key);
|
|
415
641
|
}
|
|
416
642
|
continue;
|
|
417
643
|
}
|
|
418
644
|
if (classification.kind === "standalone") {
|
|
419
|
-
|
|
645
|
+
data.channels[key] = stripNulls(shapeFiltered);
|
|
420
646
|
continue;
|
|
421
647
|
}
|
|
422
648
|
// Variant. The normalization base is identity-from-canonical layered with the predefined variant's binding (if any) - the same shape the user sees in the
|
|
423
|
-
// form/table, so the delta records only what differs from that.
|
|
649
|
+
// form/table, so the delta records only what differs from that. This matches the inheritance shape used by resolveVariant: identity from
|
|
424
650
|
// canonical, binding from variant. Without the pickIdentity strip, the canonical service's binding would leak into the normalization base and stored
|
|
425
651
|
// overrides matching the canonical's binding would be incorrectly stripped as no-ops. A dangling canonical falls back to standalone-style normalization so
|
|
426
652
|
// the user does not silently lose data.
|
|
427
653
|
const canonical = resolvedCanonicals[classification.canonicalKey];
|
|
428
654
|
if (!canonical) {
|
|
429
|
-
|
|
655
|
+
data.channels[key] = stripNulls(shapeFiltered);
|
|
430
656
|
continue;
|
|
431
657
|
}
|
|
432
658
|
const identityBase = pickIdentity(canonical);
|
|
@@ -435,15 +661,18 @@ function normalizeChannelDeltas(channels) {
|
|
|
435
661
|
const base = classification.predefined ? overlayVariantBinding(identityBase, classification.predefined) : identityBase;
|
|
436
662
|
const normalized = normalizeEntryAgainstBase(shapeFiltered, base);
|
|
437
663
|
if (normalized) {
|
|
438
|
-
|
|
664
|
+
data.channels[key] = normalized;
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
Reflect.deleteProperty(data.channels, key);
|
|
439
668
|
}
|
|
440
669
|
}
|
|
441
|
-
return filtered;
|
|
442
670
|
}
|
|
443
671
|
/**
|
|
444
|
-
* Prepares channels data for writing to disk.
|
|
445
|
-
*
|
|
446
|
-
*
|
|
672
|
+
* Prepares channels data for writing to disk. This is a pure function of the passed envelope: channels, schemaVersion, migrationsApplied, serviceSelections,
|
|
673
|
+
* and tagRegistry are all sourced from `data`, never from module-state caches. The framework guarantees `data` is the source of truth at write time, so reading
|
|
674
|
+
* module state here would be a side channel that breaks correctness when the boot persist runs before module state is populated. Delta normalization is handled
|
|
675
|
+
* by mutateChannels() before the data reaches this hook.
|
|
447
676
|
* @param data - The compound channels data with already-normalized channels.
|
|
448
677
|
* @returns The serializable output with channels and current metadata.
|
|
449
678
|
*/
|
|
@@ -491,6 +720,7 @@ const channelsMigrations = {
|
|
|
491
720
|
// persist that writes the cleaned shape via prepareChannelsForWrite.
|
|
492
721
|
for (const [canonicalKey, selectedVariant] of Object.entries(data.serviceSelections)) {
|
|
493
722
|
if (selectedVariant.endsWith("-foxcom")) {
|
|
723
|
+
// Strip only "foxcom" (6 chars), not the full "-foxcom" guard (7), so the separating hyphen survives into the foxone-suffixed result.
|
|
494
724
|
data.serviceSelections[canonicalKey] = selectedVariant.slice(0, -6) + "foxone";
|
|
495
725
|
}
|
|
496
726
|
if ((canonicalKey === "fox") && (selectedVariant === "fox-site")) {
|
|
@@ -499,6 +729,7 @@ const channelsMigrations = {
|
|
|
499
729
|
}
|
|
500
730
|
for (const [key, value] of Object.entries(data.channels)) {
|
|
501
731
|
if (key.endsWith("-foxcom")) {
|
|
732
|
+
// Strip only "foxcom" (6 chars), not the full "-foxcom" guard (7), so the separating hyphen survives into the foxone-suffixed result.
|
|
502
733
|
data.channels[key.slice(0, -6) + "foxone"] = value;
|
|
503
734
|
Reflect.deleteProperty(data.channels, key);
|
|
504
735
|
}
|
|
@@ -635,7 +866,8 @@ function detectIdentityFieldLoss(before, after) {
|
|
|
635
866
|
continue;
|
|
636
867
|
}
|
|
637
868
|
// Field went value -> undefined. Check whether the canonical provides the same value, in which case this is variant inheritance / delta minimization
|
|
638
|
-
// and not data loss. Array-valued fields (notably tags) are compared via
|
|
869
|
+
// and not data loss. Array-valued fields (notably tags) are compared structurally via isDeepStrictEqual since reference equality would always fail
|
|
870
|
+
// across freshly-deserialized arrays.
|
|
639
871
|
const canonicalValue = canonical ? canonical[field] : undefined;
|
|
640
872
|
if (canonicalValue !== undefined) {
|
|
641
873
|
const sameValue = (Array.isArray(beforeValue) && Array.isArray(canonicalValue)) ?
|
|
@@ -669,8 +901,9 @@ export async function mutateChannels(fn) {
|
|
|
669
901
|
await channelsStore.mutate((data) => {
|
|
670
902
|
fn(data);
|
|
671
903
|
// Normalize channel deltas before the beforeWrite hook serializes the data. This ensures the in-memory cache and the on-disk representation are
|
|
672
|
-
// identical.
|
|
673
|
-
|
|
904
|
+
// identical. The normalizer mutates data in place (channels and serviceSelections both - the canonical-binding overlap heal touches both), matching the
|
|
905
|
+
// migration `apply` convention so all in-place writes share one shape.
|
|
906
|
+
normalizeChannelDeltas(data);
|
|
674
907
|
writtenData = data;
|
|
675
908
|
});
|
|
676
909
|
// Side effects after successful write. The store framework guarantees writtenData reflects exactly what was persisted.
|
|
@@ -683,6 +916,28 @@ export async function mutateChannels(fn) {
|
|
|
683
916
|
userChannelsParseError = false;
|
|
684
917
|
userChannelsParseErrorMessage = undefined;
|
|
685
918
|
}
|
|
919
|
+
/**
|
|
920
|
+
* Runs the startup channels-file cleanup pass. Combines two boot-time concerns into one atomic write so they share a single disk transaction:
|
|
921
|
+
*
|
|
922
|
+
* - Stale service-selections cleanup: any selection whose variant key no longer exists (deleted predefined variant, removed user channel) is removed by
|
|
923
|
+
* buildServiceGroups in module state; this pass mirrors that cleanup to disk so the file matches the runtime view across restarts.
|
|
924
|
+
* - Canonical-binding overlap heal: invoked indirectly by the post-callback normalizer (normalizeChannelDeltas) which runs against the freshly-read data
|
|
925
|
+
* inside mutateChannels. Any canonical override carrying binding whose URL extracts to a sibling variant's domain is healed in place - binding stripped
|
|
926
|
+
* from the canonical, propagated to the matching variant entry, serviceSelections updated. The normalizer always runs post-callback; the heal happening
|
|
927
|
+
* automatically is required for the cleanup to take effect, not the explicit body of this function.
|
|
928
|
+
*
|
|
929
|
+
* Existence as a named function is the architectural point: a bare `mutateChannels(() => {})` would correctly trigger the normalizer but obscure the intent
|
|
930
|
+
* at the call site. The name documents what the pass does so callers see "run startup cleanup" rather than "do nothing inside a mutation."
|
|
931
|
+
* @param staleSelections - Service-selection keys to delete from data.serviceSelections. Empty array is allowed (heal-only path).
|
|
932
|
+
* @throws FileStoreParseError if the channels file contains invalid JSON.
|
|
933
|
+
*/
|
|
934
|
+
export async function runStartupChannelsCleanup(staleSelections) {
|
|
935
|
+
await mutateChannels((data) => {
|
|
936
|
+
for (const key of staleSelections) {
|
|
937
|
+
Reflect.deleteProperty(data.serviceSelections, key);
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
}
|
|
686
941
|
/**
|
|
687
942
|
* Deletes a user channel by key.
|
|
688
943
|
* @param key - The channel key to delete.
|
|
@@ -758,7 +1013,7 @@ export async function resetUserChannels() {
|
|
|
758
1013
|
* are user-created standalones that happen to share the hyphenated key shape (e.g., "abc-kabc" as a local affiliate with its own channel number and station
|
|
759
1014
|
* ID) and must be left alone - stamping them would mark them for delta normalization and silently destroy the user's custom identity.
|
|
760
1015
|
*
|
|
761
|
-
* Array-valued identity fields (tags) compare via
|
|
1016
|
+
* Array-valued identity fields (tags) compare via isDeepStrictEqual after sortTags canonicalizes both sides, so historical write order does not defeat the match.
|
|
762
1017
|
* @param channels - The raw stored channel entries.
|
|
763
1018
|
* @returns Keys that should receive a canonicalKey stamp. Empty when nothing needs migrating.
|
|
764
1019
|
*/
|
|
@@ -845,12 +1100,15 @@ export async function initializeUserChannels() {
|
|
|
845
1100
|
// the freshly-read on-disk data; explicitly deleting the stale keys from data.serviceSelections is the data-side equivalent of the module-side cleanup
|
|
846
1101
|
// buildServiceGroups already performed.
|
|
847
1102
|
const staleSelections = buildServiceGroups(mergedChannels);
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
1103
|
+
/* Canonical-binding overlap heal at startup. Any canonical override whose binding URL extracts to a sibling variant's domain represents the wholesale-
|
|
1104
|
+
* duplication shape from before the per-field PUT routing landed - the user wanted "default this channel to the sibling's service" but the producer
|
|
1105
|
+
* dumped the full delta onto the canonical instead of routing binding to the variant and recording the redirect via serviceSelections. The normalizer
|
|
1106
|
+
* fixes this on every write; this conditional one-shot pass triggers the heal at boot for users on upgrade so they don't have to perform any manual save
|
|
1107
|
+
* action to clean up their data. Combined with the stale-selections cleanup so a single write handles both startup conditions when both apply.
|
|
1108
|
+
*/
|
|
1109
|
+
const needsOverlapHeal = hasCanonicalBindingOverlap(loadedUserChannels);
|
|
1110
|
+
if ((staleSelections.length > 0) || needsOverlapHeal) {
|
|
1111
|
+
await runStartupChannelsCleanup(staleSelections);
|
|
854
1112
|
}
|
|
855
1113
|
// Now that service groups are built, validate the configured service tags. Strip any unrecognized tags and warn.
|
|
856
1114
|
if (configuredServices.length > 0) {
|
|
@@ -892,7 +1150,7 @@ export async function initializeUserChannels() {
|
|
|
892
1150
|
const VARIANT_OVERLAY_ALLOWED_FIELDS = new Set(CHANNEL_BINDING_KEYS);
|
|
893
1151
|
// The runtime delta surface: every field that may legitimately appear in a stored ChannelDelta override. Derived from the partition arrays in types/channels.ts
|
|
894
1152
|
// so adding or removing a delta-eligible field in one place propagates everywhere. Typed as Set<string> for ergonomics at .has() call sites that take string-keyed
|
|
895
|
-
// values from Object.entries iteration; the contents are statically-known CustomizableField values via the spread
|
|
1153
|
+
// values from Object.entries iteration; the contents are statically-known CustomizableField values via the spread below.
|
|
896
1154
|
const DELTA_ALLOWED_FIELDS = new Set([...DELTA_ELIGIBLE_IDENTITY_KEYS, ...DELTA_ELIGIBLE_BINDING_KEYS]);
|
|
897
1155
|
/**
|
|
898
1156
|
* Extracts the identity-only subset of a channel. Used by resolveVariant and normalizeChannelDeltas to compute the inheritance base for variants: a variant
|
|
@@ -942,7 +1200,7 @@ function overlayDelta(base, stored) {
|
|
|
942
1200
|
* Shared overlay kernel used by overlayDelta and overlayVariantBinding. Walks the stored entry's fields and applies them to a clone of base under three rules:
|
|
943
1201
|
* fields in allowedFields apply with delta semantics (null clears, undefined skips, value overrides); non-allowed fields either pass through (delta) or are
|
|
944
1202
|
* silently dropped (variant overlay) per passThroughOthers; canonicalKey is always allowed regardless of the gate so resolved variants retain their relationship
|
|
945
|
-
* metadata. The kernel exists to keep the two overlays from drifting -
|
|
1203
|
+
* metadata. The kernel exists to keep the two overlays from drifting - both share one control-flow path rather than duplicating it in each call site.
|
|
946
1204
|
* @param base - The base channel to clone and overlay onto.
|
|
947
1205
|
* @param stored - The stored entry whose fields override the base.
|
|
948
1206
|
* @param options - allowedFields selects which fields apply with delta semantics; passThroughOthers controls whether non-allowed fields flow through or are
|
|
@@ -1026,7 +1284,7 @@ export function resolveStoredChannel(key, stored) {
|
|
|
1026
1284
|
*/
|
|
1027
1285
|
function getMergedChannelMap() {
|
|
1028
1286
|
const result = buildResolvedCanonicals(loadedUserChannels);
|
|
1029
|
-
const allKeys = new Set(
|
|
1287
|
+
const allKeys = new Set(Object.keys(PREDEFINED_CHANNELS)).union(new Set(Object.keys(loadedUserChannels)));
|
|
1030
1288
|
for (const key of allKeys) {
|
|
1031
1289
|
const stored = loadedUserChannels[key];
|
|
1032
1290
|
const classification = classifyEntry(key, stored);
|
|
@@ -1079,7 +1337,7 @@ function getMergedChannelMap() {
|
|
|
1079
1337
|
* @returns Sorted array of channel listing entries.
|
|
1080
1338
|
*/
|
|
1081
1339
|
export function getChannelListing() {
|
|
1082
|
-
const allKeys = new Set(
|
|
1340
|
+
const allKeys = new Set(Object.keys(PREDEFINED_CHANNELS)).union(new Set(Object.keys(loadedUserChannels)));
|
|
1083
1341
|
const listing = [];
|
|
1084
1342
|
for (const key of allKeys) {
|
|
1085
1343
|
// Skip service variants - they're accessed via service selection, not as separate channels.
|
|
@@ -1160,7 +1418,7 @@ export function getAllChannels() {
|
|
|
1160
1418
|
}
|
|
1161
1419
|
/**
|
|
1162
1420
|
* Returns a copy of the raw stored user channels map - the unresolved entries as they exist in channels.json. Used by the consistency probe to check
|
|
1163
|
-
* cross-store
|
|
1421
|
+
* cross-store consistency rules without applying delta resolution. Callers should not rely on the shape for anything beyond key/canonicalKey inspection.
|
|
1164
1422
|
* @returns Shallow copy of the loaded user channels.
|
|
1165
1423
|
*/
|
|
1166
1424
|
export function getStoredUserChannels() {
|
|
@@ -1513,7 +1771,8 @@ export function getChannelStationId(channelKey) {
|
|
|
1513
1771
|
return resolveStoredChannel(effectiveKey, userEntry).stationId;
|
|
1514
1772
|
}
|
|
1515
1773
|
// PREDEFINED_CHANNELS is a discriminated union; only canonicals carry stationId. The lookup here is by canonical-equivalent key (Pacific resolved to East),
|
|
1516
|
-
// so a hit is structurally a canonical. Narrow via the canonicalKey
|
|
1774
|
+
// so a hit is structurally a canonical. Narrow via the canonicalKey field that marks the kind: a defined canonicalKey marks a VariantChannel, which has no
|
|
1775
|
+
// stationId.
|
|
1517
1776
|
const predefined = PREDEFINED_CHANNELS[effectiveKey];
|
|
1518
1777
|
if (!predefined || (predefined.canonicalKey !== undefined)) {
|
|
1519
1778
|
return undefined;
|
|
@@ -1546,7 +1805,8 @@ export function isUserChannel(key) {
|
|
|
1546
1805
|
*/
|
|
1547
1806
|
/**
|
|
1548
1807
|
* Checks if a predefined channel is disabled. The disabled state is determined solely by the disabledPredefined list in config - the user's explicit visibility
|
|
1549
|
-
* intent. Property overrides (HDHR toggle, name change, tag edits) stored as deltas in channels.json are
|
|
1808
|
+
* intent. Property overrides (HDHR toggle, name change, tag edits) stored as deltas in channels.json are independent and do not affect the enabled/disabled
|
|
1809
|
+
* state.
|
|
1550
1810
|
* @param key - The channel key to check.
|
|
1551
1811
|
* @returns True if the channel is predefined and disabled.
|
|
1552
1812
|
*/
|
|
@@ -1588,14 +1848,15 @@ async function mutateDisabledPredefined(op, keys) {
|
|
|
1588
1848
|
CONFIG.channels.disabledPredefined = updatedList;
|
|
1589
1849
|
}
|
|
1590
1850
|
/**
|
|
1591
|
-
* Disables one or more predefined channels by adding their keys to the disabledPredefined list in user config.
|
|
1592
|
-
* @param keys - The predefined channel keys to disable. Duplicates within the input array and pre-existing disabled entries are handled
|
|
1851
|
+
* Disables one or more predefined channels by adding their keys to the disabledPredefined list in user config. Safe to call again with the same keys.
|
|
1852
|
+
* @param keys - The predefined channel keys to disable. Duplicates within the input array and pre-existing disabled entries are handled without producing
|
|
1853
|
+
* duplicate list entries.
|
|
1593
1854
|
*/
|
|
1594
1855
|
export async function disablePredefinedChannels(keys) {
|
|
1595
1856
|
await mutateDisabledPredefined("add", keys);
|
|
1596
1857
|
}
|
|
1597
1858
|
/**
|
|
1598
|
-
* Enables one or more predefined channels by removing their keys from the disabledPredefined list in user config.
|
|
1859
|
+
* Enables one or more predefined channels by removing their keys from the disabledPredefined list in user config. Safe to call again with the same keys.
|
|
1599
1860
|
* @param keys - The predefined channel keys to enable. Entries that aren't in the disabled list are ignored.
|
|
1600
1861
|
*/
|
|
1601
1862
|
export async function enablePredefinedChannels(keys) {
|
|
@@ -1604,7 +1865,7 @@ export async function enablePredefinedChannels(keys) {
|
|
|
1604
1865
|
/**
|
|
1605
1866
|
* Persists a partial update of channel-table display preferences (sort field, sort direction, visible columns) to config.json. Only fields present in `prefs`
|
|
1606
1867
|
* are written; absent fields are left untouched. The mutate fn copies the new values into data and the runtime CONFIG cache is updated post-write to match.
|
|
1607
|
-
*
|
|
1868
|
+
* Persists and hydrates the runtime cache in a single call, consistent with mutateServiceSelections, mutateEnabledServices, and setTagRegistry.
|
|
1608
1869
|
* @param prefs - Subset of display preferences to update.
|
|
1609
1870
|
* @throws FileStoreParseError if config.json contains invalid JSON and the .bak rotation is also unparseable.
|
|
1610
1871
|
*/
|
|
@@ -1821,27 +2082,31 @@ export function validateChannelName(name) {
|
|
|
1821
2082
|
/**
|
|
1822
2083
|
* Validates a profile name.
|
|
1823
2084
|
* @param profile - The profile name to validate (can be empty for autodetect).
|
|
1824
|
-
* @param
|
|
2085
|
+
* @param isKnownProfile - Predicate answering whether a profile name exists. Callers test each name against the single builtin lookup plus the user's own
|
|
2086
|
+
* profiles, so that a channel may reference a provider profile as well as a general or user-defined one.
|
|
1825
2087
|
* @returns Error message if invalid, undefined if valid.
|
|
1826
2088
|
*/
|
|
1827
|
-
export function validateChannelProfile(profile,
|
|
2089
|
+
export function validateChannelProfile(profile, isKnownProfile) {
|
|
1828
2090
|
// Empty profile means autodetect, which is valid.
|
|
1829
2091
|
if (!profile || (profile.trim() === "")) {
|
|
1830
2092
|
return undefined;
|
|
1831
2093
|
}
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
2094
|
+
/* Check if profile exists. The message names the rejected value alone. The set of acceptable names includes the provider profiles, which the web UI's profile
|
|
2095
|
+
* picker deliberately omits, so spelling that set out here would offer names the picker will never present. The picker is the discovery surface for a valid
|
|
2096
|
+
* profile name; this message's job is to say which value was not one.
|
|
2097
|
+
*/
|
|
2098
|
+
if (!isKnownProfile(profile)) {
|
|
2099
|
+
return "Unknown profile: " + profile + ".";
|
|
1835
2100
|
}
|
|
1836
2101
|
return undefined;
|
|
1837
2102
|
}
|
|
1838
2103
|
/**
|
|
1839
2104
|
* Validates an imported channels object for structure and content.
|
|
1840
2105
|
* @param data - The raw imported data to validate.
|
|
1841
|
-
* @param
|
|
2106
|
+
* @param isKnownProfile - Predicate answering whether a referenced profile name exists, passed through to the per-channel profile check.
|
|
1842
2107
|
* @returns Validation result with errors if invalid.
|
|
1843
2108
|
*/
|
|
1844
|
-
export function validateImportedChannels(data,
|
|
2109
|
+
export function validateImportedChannels(data, isKnownProfile) {
|
|
1845
2110
|
const errors = [];
|
|
1846
2111
|
// Check that input is an object.
|
|
1847
2112
|
if ((typeof data !== "object") || (data === null) || Array.isArray(data)) {
|
|
@@ -1886,7 +2151,7 @@ export function validateImportedChannels(data, validProfiles) {
|
|
|
1886
2151
|
}
|
|
1887
2152
|
// Validate optional profile field. Sanitize after type check.
|
|
1888
2153
|
const profile = (typeof channelData["profile"] === "string") ? sanitizeString(channelData["profile"]) : undefined;
|
|
1889
|
-
const profileError = validateChannelProfile(profile,
|
|
2154
|
+
const profileError = validateChannelProfile(profile, isKnownProfile);
|
|
1890
2155
|
if (profileError) {
|
|
1891
2156
|
errors.push("Channel '" + key + "': " + profileError);
|
|
1892
2157
|
continue;
|
|
@@ -1947,8 +2212,8 @@ export function validateImportedChannels(data, validProfiles) {
|
|
|
1947
2212
|
/**
|
|
1948
2213
|
* Sorts tags case-insensitively using locale-aware comparison. This is the single source of truth for tag ordering. Every write path (parseTagInput, PATCH
|
|
1949
2214
|
* handlers, computePredefinedDelta, transformChannelTags, setTagRegistry) routes through this so stored tag arrays share one canonical ordering. The channels
|
|
1950
|
-
* normalizer
|
|
1951
|
-
*
|
|
2215
|
+
* normalizer depends on that ordering guarantee - it uses sortTags on both sides when comparing a delta's tags against a predefined's tags, making the
|
|
2216
|
+
* isDeepStrictEqual comparison canonical regardless of how either side was originally populated.
|
|
1952
2217
|
* @param tags - Any iterable of tag strings (array, Set, generator). Not mutated.
|
|
1953
2218
|
* @returns A new array with the same elements in canonical case-insensitive order.
|
|
1954
2219
|
*/
|