prismcast 1.9.0 → 1.10.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 +1 -1
- package/dist/app.js +27 -9
- package/dist/app.js.map +1 -1
- package/dist/browser/cdp.js +7 -7
- package/dist/browser/cdp.js.map +1 -1
- package/dist/browser/channelSelection.d.ts +41 -11
- package/dist/browser/channelSelection.js +75 -24
- package/dist/browser/channelSelection.js.map +1 -1
- package/dist/browser/display.d.ts +1 -1
- package/dist/browser/index.d.ts +8 -8
- package/dist/browser/index.js +42 -43
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/login.d.ts +2 -2
- package/dist/browser/login.js +1 -1
- package/dist/browser/manifestInterceptor.d.ts +77 -0
- package/dist/browser/manifestInterceptor.js +323 -0
- package/dist/browser/manifestInterceptor.js.map +1 -0
- package/dist/browser/precaching.js +4 -4
- package/dist/browser/tuning/comcastPolymer.d.ts +1 -1
- package/dist/browser/tuning/cox.d.ts +1 -1
- package/dist/browser/tuning/directv.d.ts +1 -1
- package/dist/browser/tuning/directv.js +61 -62
- package/dist/browser/tuning/directv.js.map +1 -1
- package/dist/browser/tuning/fox.d.ts +1 -1
- package/dist/browser/tuning/fox.js +171 -14
- package/dist/browser/tuning/fox.js.map +1 -1
- package/dist/browser/tuning/hbo.d.ts +1 -1
- package/dist/browser/tuning/hbo.js +6 -6
- package/dist/browser/tuning/hbo.js.map +1 -1
- package/dist/browser/tuning/hulu.d.ts +1 -1
- package/dist/browser/tuning/hulu.js +117 -110
- package/dist/browser/tuning/hulu.js.map +1 -1
- package/dist/browser/tuning/shared.d.ts +3 -3
- package/dist/browser/tuning/shared.js +5 -5
- package/dist/browser/tuning/sling.d.ts +1 -1
- package/dist/browser/tuning/sling.js +28 -28
- package/dist/browser/tuning/sling.js.map +1 -1
- package/dist/browser/tuning/spectrum.d.ts +1 -1
- package/dist/browser/tuning/spectrum.js +21 -22
- package/dist/browser/tuning/spectrum.js.map +1 -1
- package/dist/browser/tuning/thumbnailRow.d.ts +1 -1
- package/dist/browser/tuning/thumbnailRow.js +1 -1
- package/dist/browser/tuning/tileClick.d.ts +1 -1
- package/dist/browser/tuning/tileClick.js +5 -5
- package/dist/browser/tuning/xfinity.d.ts +1 -1
- package/dist/browser/tuning/youtubeTv.d.ts +1 -1
- package/dist/browser/tuning/youtubeTv.js +17 -18
- package/dist/browser/tuning/youtubeTv.js.map +1 -1
- package/dist/browser/video.d.ts +20 -6
- package/dist/browser/video.js +30 -38
- package/dist/browser/video.js.map +1 -1
- package/dist/channels/index.d.ts +1 -2
- package/dist/channels/index.js +73 -40
- package/dist/channels/index.js.map +1 -1
- package/dist/config/channelForm.d.ts +58 -0
- package/dist/config/channelForm.js +129 -0
- package/dist/config/channelForm.js.map +1 -0
- package/dist/config/consistencyProbe.d.ts +7 -0
- package/dist/config/consistencyProbe.js +143 -0
- package/dist/config/consistencyProbe.js.map +1 -0
- package/dist/config/health.d.ts +4 -3
- package/dist/config/health.js +75 -62
- package/dist/config/health.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +19 -12
- package/dist/config/index.js.map +1 -1
- package/dist/config/paths.d.ts +9 -3
- package/dist/config/paths.js +12 -4
- package/dist/config/paths.js.map +1 -1
- package/dist/config/persistence.context.d.ts +8 -0
- package/dist/config/persistence.context.js +30 -0
- package/dist/config/persistence.context.js.map +1 -0
- package/dist/config/persistence.d.ts +110 -12
- package/dist/config/persistence.js +351 -32
- package/dist/config/persistence.js.map +1 -1
- package/dist/config/presets.d.ts +14 -4
- package/dist/config/presets.js +84 -75
- package/dist/config/presets.js.map +1 -1
- package/dist/config/profiles.d.ts +2 -2
- package/dist/config/profiles.js +7 -9
- package/dist/config/profiles.js.map +1 -1
- package/dist/config/servicePacks.d.ts +2 -2
- package/dist/config/servicePacks.js +28 -28
- package/dist/config/servicePacks.js.map +1 -1
- package/dist/config/services.d.ts +62 -36
- package/dist/config/services.js +150 -136
- package/dist/config/services.js.map +1 -1
- package/dist/config/sites.d.ts +3 -3
- package/dist/config/sites.js +16 -16
- package/dist/config/sites.js.map +1 -1
- package/dist/config/userChannels.d.ts +500 -41
- package/dist/config/userChannels.js +1201 -312
- package/dist/config/userChannels.js.map +1 -1
- package/dist/config/userConfig.d.ts +103 -10
- package/dist/config/userConfig.js +277 -117
- package/dist/config/userConfig.js.map +1 -1
- package/dist/config/userProfiles.d.ts +4 -2
- package/dist/config/userProfiles.js +72 -26
- package/dist/config/userProfiles.js.map +1 -1
- package/dist/hdhr/channelMap.js +8 -11
- package/dist/hdhr/channelMap.js.map +1 -1
- package/dist/hdhr/deviceId.js +10 -9
- package/dist/hdhr/deviceId.js.map +1 -1
- package/dist/hdhr/discover.js +4 -5
- package/dist/hdhr/discover.js.map +1 -1
- package/dist/hdhr/index.js +6 -10
- package/dist/hdhr/index.js.map +1 -1
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/native/codecInference.d.ts +44 -0
- package/dist/native/codecInference.js +327 -0
- package/dist/native/codecInference.js.map +1 -0
- package/dist/native/decrypt.d.ts +1 -1
- package/dist/native/index.d.ts +5 -5
- package/dist/native/index.js +42 -64
- package/dist/native/index.js.map +1 -1
- package/dist/native/probe.d.ts +27 -9
- package/dist/native/probe.js +158 -67
- package/dist/native/probe.js.map +1 -1
- package/dist/native/proxy.d.ts +4 -2
- package/dist/native/proxy.js +52 -49
- package/dist/native/proxy.js.map +1 -1
- package/dist/native/tokenExpiry.d.ts +22 -0
- package/dist/native/tokenExpiry.js +153 -0
- package/dist/native/tokenExpiry.js.map +1 -0
- package/dist/routes/auth.js +6 -10
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/components.js +2 -2
- package/dist/routes/components.js.map +1 -1
- package/dist/routes/config/channels/endpoints/browse.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/browse.js +195 -0
- package/dist/routes/config/channels/endpoints/browse.js.map +1 -0
- package/dist/routes/config/channels/endpoints/bulk.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/bulk.js +128 -0
- package/dist/routes/config/channels/endpoints/bulk.js.map +1 -0
- package/dist/routes/config/channels/endpoints/crud.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/crud.js +504 -0
- package/dist/routes/config/channels/endpoints/crud.js.map +1 -0
- package/dist/routes/config/channels/endpoints/importExport.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/importExport.js +131 -0
- package/dist/routes/config/channels/endpoints/importExport.js.map +1 -0
- package/dist/routes/config/channels/endpoints/predefined.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/predefined.js +78 -0
- package/dist/routes/config/channels/endpoints/predefined.js.map +1 -0
- package/dist/routes/config/channels/endpoints/prefs.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/prefs.js +58 -0
- package/dist/routes/config/channels/endpoints/prefs.js.map +1 -0
- package/dist/routes/config/channels/endpoints/service.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/service.js +149 -0
- package/dist/routes/config/channels/endpoints/service.js.map +1 -0
- package/dist/routes/config/channels/endpoints/tags.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/tags.js +185 -0
- package/dist/routes/config/channels/endpoints/tags.js.map +1 -0
- package/dist/routes/config/channels/http/handler.d.ts +10 -0
- package/dist/routes/config/channels/http/handler.js +20 -0
- package/dist/routes/config/channels/http/handler.js.map +1 -0
- package/dist/routes/config/channels/http/playlistHint.d.ts +25 -0
- package/dist/routes/config/channels/http/playlistHint.js +40 -0
- package/dist/routes/config/channels/http/playlistHint.js.map +1 -0
- package/dist/routes/config/channels/http/serviceWarning.d.ts +10 -0
- package/dist/routes/config/channels/http/serviceWarning.js +28 -0
- package/dist/routes/config/channels/http/serviceWarning.js.map +1 -0
- package/dist/routes/config/channels/index.d.ts +3 -3
- package/dist/routes/config/channels/index.js +1 -1
- package/dist/routes/config/channels/index.js.map +1 -1
- package/dist/routes/config/channels/{routes.d.ts → setup.d.ts} +1 -1
- package/dist/routes/config/channels/setup.js +23 -0
- package/dist/routes/config/channels/setup.js.map +1 -0
- package/dist/routes/config/channels/table.d.ts +9 -9
- package/dist/routes/config/channels/table.js +124 -76
- package/dist/routes/config/channels/table.js.map +1 -1
- package/dist/routes/config/http/envelope.d.ts +111 -0
- package/dist/routes/config/http/envelope.js +103 -0
- package/dist/routes/config/http/envelope.js.map +1 -0
- package/dist/routes/config/index.d.ts +9 -9
- package/dist/routes/config/index.js +2 -2
- package/dist/routes/config/index.js.map +1 -1
- package/dist/routes/config/services.js +69 -68
- package/dist/routes/config/services.js.map +1 -1
- package/dist/routes/config/settings.d.ts +1 -1
- package/dist/routes/config/settings.js +45 -38
- package/dist/routes/config/settings.js.map +1 -1
- package/dist/routes/debug.js +12 -11
- package/dist/routes/debug.js.map +1 -1
- package/dist/routes/icons.js +2 -2
- package/dist/routes/icons.js.map +1 -1
- package/dist/routes/logs.js +19 -30
- package/dist/routes/logs.js.map +1 -1
- package/dist/routes/mpegts.js +1 -1
- package/dist/routes/playlist.d.ts +1 -1
- package/dist/routes/playlist.js +37 -26
- package/dist/routes/playlist.js.map +1 -1
- package/dist/routes/root/content.js +27 -6
- package/dist/routes/root/content.js.map +1 -1
- package/dist/routes/root/index.js +2 -5
- package/dist/routes/root/index.js.map +1 -1
- package/dist/routes/root/scripts/channels.js +16 -16
- package/dist/routes/root/scripts/channels.js.map +1 -1
- package/dist/routes/root/scripts/config.js +59 -59
- package/dist/routes/root/scripts/config.js.map +1 -1
- package/dist/routes/root/scripts/index.d.ts +4 -4
- package/dist/routes/root/scripts/shared.js +24 -10
- package/dist/routes/root/scripts/shared.js.map +1 -1
- package/dist/routes/root/scripts/status.d.ts +4 -0
- package/dist/routes/root/scripts/status.handlers.d.ts +182 -0
- package/dist/routes/root/scripts/status.handlers.js +713 -0
- package/dist/routes/root/scripts/status.handlers.js.map +1 -0
- package/dist/routes/root/scripts/status.js +86 -488
- package/dist/routes/root/scripts/status.js.map +1 -1
- package/dist/routes/root/styles.js +5 -5
- package/dist/routes/services.js +18 -15
- package/dist/routes/services.js.map +1 -1
- package/dist/routes/sse.d.ts +16 -0
- package/dist/routes/sse.js +29 -0
- package/dist/routes/sse.js.map +1 -0
- package/dist/routes/streams.js +10 -23
- package/dist/routes/streams.js.map +1 -1
- package/dist/routes/ui.d.ts +2 -2
- package/dist/routes/upgrade.js +6 -7
- package/dist/routes/upgrade.js.map +1 -1
- package/dist/service/commands.context.d.ts +6 -0
- package/dist/service/commands.context.js +47 -0
- package/dist/service/commands.context.js.map +1 -0
- package/dist/service/commands.d.ts +53 -8
- package/dist/service/commands.js +171 -188
- package/dist/service/commands.js.map +1 -1
- package/dist/service/generators.context.d.ts +6 -0
- package/dist/service/generators.context.js +45 -0
- package/dist/service/generators.context.js.map +1 -0
- package/dist/service/generators.d.ts +56 -16
- package/dist/service/generators.js +499 -383
- package/dist/service/generators.js.map +1 -1
- package/dist/service/index.d.ts +2 -2
- package/dist/streaming/clients.d.ts +2 -2
- package/dist/streaming/clients.js +2 -2
- package/dist/streaming/codec.d.ts +2 -2
- package/dist/streaming/codec.js +0 -1
- package/dist/streaming/codec.js.map +1 -1
- package/dist/streaming/fmp4Segmenter.d.ts +2 -2
- package/dist/streaming/fmp4Segmenter.js +12 -12
- package/dist/streaming/hls.d.ts +3 -3
- package/dist/streaming/hls.js +81 -70
- package/dist/streaming/hls.js.map +1 -1
- package/dist/streaming/hlsResume.d.ts +3 -3
- package/dist/streaming/hlsResume.js +5 -5
- package/dist/streaming/hlsSegments.d.ts +5 -2
- package/dist/streaming/hlsSegments.js +14 -9
- package/dist/streaming/hlsSegments.js.map +1 -1
- package/dist/streaming/monitor.d.ts +12 -12
- package/dist/streaming/monitor.js +80 -54
- package/dist/streaming/monitor.js.map +1 -1
- package/dist/streaming/mp4Parser.d.ts +34 -4
- package/dist/streaming/mp4Parser.js +111 -29
- package/dist/streaming/mp4Parser.js.map +1 -1
- package/dist/streaming/mpegts.d.ts +1 -1
- package/dist/streaming/mpegts.js +13 -10
- package/dist/streaming/mpegts.js.map +1 -1
- package/dist/streaming/playlistBuilder.d.ts +1 -1
- package/dist/streaming/playlistBuilder.js +2 -2
- package/dist/streaming/preroll.d.ts +9 -9
- package/dist/streaming/preroll.js +39 -41
- package/dist/streaming/preroll.js.map +1 -1
- package/dist/streaming/pretune.js +4 -4
- package/dist/streaming/pretune.js.map +1 -1
- package/dist/streaming/recovery.d.ts +3 -3
- package/dist/streaming/recovery.js +4 -4
- package/dist/streaming/registry.d.ts +6 -6
- package/dist/streaming/setup.d.ts +9 -8
- package/dist/streaming/setup.js +97 -49
- package/dist/streaming/setup.js.map +1 -1
- package/dist/streaming/showInfo.d.ts +9 -3
- package/dist/streaming/showInfo.js +26 -18
- package/dist/streaming/showInfo.js.map +1 -1
- package/dist/streaming/statusEmitter.d.ts +4 -4
- package/dist/streaming/statusEmitter.js +2 -2
- package/dist/types/channels.d.ts +133 -34
- package/dist/types/channels.js +39 -1
- package/dist/types/channels.js.map +1 -1
- package/dist/types/config.d.ts +20 -1
- package/dist/types/index.d.ts +9 -8
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/profiles.d.ts +5 -4
- package/dist/types/selection.d.ts +48 -4
- package/dist/types/streaming.d.ts +1 -1
- package/dist/upgrade/commands.context.d.ts +6 -0
- package/dist/upgrade/commands.context.js +29 -0
- package/dist/upgrade/commands.context.js.map +1 -0
- package/dist/upgrade/commands.d.ts +29 -2
- package/dist/upgrade/commands.js +87 -134
- package/dist/upgrade/commands.js.map +1 -1
- package/dist/upgrade/detection.context.d.ts +6 -0
- package/dist/upgrade/detection.context.js +24 -0
- package/dist/upgrade/detection.context.js.map +1 -0
- package/dist/upgrade/detection.d.ts +140 -10
- package/dist/upgrade/detection.js +153 -66
- package/dist/upgrade/detection.js.map +1 -1
- package/dist/upgrade/index.d.ts +2 -2
- package/dist/utils/chromeFetch.d.ts +1 -1
- package/dist/utils/chromeFetch.js +1 -1
- package/dist/utils/clock.d.ts +14 -0
- package/dist/utils/clock.js +28 -0
- package/dist/utils/clock.js.map +1 -0
- package/dist/utils/debugFilter.d.ts +1 -1
- package/dist/utils/debugFilter.js +6 -4
- package/dist/utils/debugFilter.js.map +1 -1
- package/dist/utils/delay.d.ts +2 -3
- package/dist/utils/delay.js +10 -16
- package/dist/utils/delay.js.map +1 -1
- package/dist/utils/evaluate.js +12 -14
- package/dist/utils/evaluate.js.map +1 -1
- package/dist/utils/ffmpeg.context.d.ts +6 -0
- package/dist/utils/ffmpeg.context.js +31 -0
- package/dist/utils/ffmpeg.context.js.map +1 -0
- package/dist/utils/ffmpeg.d.ts +85 -29
- package/dist/utils/ffmpeg.js +126 -129
- package/dist/utils/ffmpeg.js.map +1 -1
- package/dist/utils/fileLogger.d.ts +18 -2
- package/dist/utils/fileLogger.js +58 -30
- package/dist/utils/fileLogger.js.map +1 -1
- package/dist/utils/format.d.ts +1 -1
- package/dist/utils/format.js +2 -2
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/html.js +1 -3
- package/dist/utils/html.js.map +1 -1
- package/dist/utils/index.d.ts +21 -20
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.js +16 -20
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/m3u.d.ts +18 -3
- package/dist/utils/m3u.js +28 -14
- package/dist/utils/m3u.js.map +1 -1
- package/dist/utils/memo.d.ts +19 -0
- package/dist/utils/memo.js +53 -0
- package/dist/utils/memo.js.map +1 -0
- package/dist/utils/morganStream.js +2 -2
- package/dist/utils/morganStream.js.map +1 -1
- package/dist/utils/pid.d.ts +2 -2
- package/dist/utils/pid.js +2 -2
- package/dist/utils/platform.d.ts +1 -1
- package/dist/utils/platform.js +3 -3
- package/dist/utils/platform.js.map +1 -1
- package/dist/utils/retry.d.ts +2 -0
- package/dist/utils/retry.js +9 -7
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/sanitize.d.ts +2 -2
- package/dist/utils/sanitize.js +4 -4
- package/dist/utils/timing.d.ts +6 -3
- package/dist/utils/timing.js +10 -6
- package/dist/utils/timing.js.map +1 -1
- package/dist/utils/version.d.ts +3 -2
- package/dist/utils/version.js +14 -27
- package/dist/utils/version.js.map +1 -1
- package/package.json +13 -7
- package/dist/native/intercept.d.ts +0 -35
- package/dist/native/intercept.js +0 -181
- package/dist/native/intercept.js.map +0 -1
- package/dist/routes/config/channels/routes.js +0 -1505
- package/dist/routes/config/channels/routes.js.map +0 -1
|
@@ -258,7 +258,7 @@ export function generateLandingPageStyles() {
|
|
|
258
258
|
".channel-table tr.user-channel:hover { background: var(--user-channel-tint-hover); }",
|
|
259
259
|
// Predefined channel override indicator. Left-border accent + blue dot on the first cell matches the settings form's .form-group.modified treatment
|
|
260
260
|
// (border + dot = "modified from defaults"). The border targets td:first-child because border-collapse: separate prevents borders on <tr> elements. The
|
|
261
|
-
// dot is a ::before pseudo-element on the same cell so it's always the first visual element in the row, right next to the border. No background tint
|
|
261
|
+
// dot is a ::before pseudo-element on the same cell so it's always the first visual element in the row, right next to the border. No background tint - the
|
|
262
262
|
// border and dot are the sole differentiators, exactly like modified settings.
|
|
263
263
|
".channel-table tr.channel-override td:first-child { border-left: 3px solid var(--interactive-primary); }",
|
|
264
264
|
".channel-table tr.channel-override td:first-child::before { content: ''; display: inline-block; width: 8px; height: 8px; " +
|
|
@@ -273,7 +273,7 @@ export function generateLandingPageStyles() {
|
|
|
273
273
|
".channel-table tr.channel-unavailable td { color: var(--text-tertiary); }",
|
|
274
274
|
".channel-table tr.channel-unavailable.channel-disabled { opacity: 0.5; }",
|
|
275
275
|
".channel-table.hide-disabled tr.channel-unavailable { display: none; }",
|
|
276
|
-
// Tag pills
|
|
276
|
+
// Tag pills - rounded labels for tag display in the channel table, tag manager modal, and edit form. The pill shape provides clear visual identity and
|
|
277
277
|
// scannability across all surfaces where tags appear.
|
|
278
278
|
".tag-badge { display: inline-block; padding: 1px 7px; margin: 1px 2px; font-size: 11px; border-radius: 10px; " +
|
|
279
279
|
"background: var(--surface-elevated); border: 1px solid var(--border-default); color: var(--text-secondary); font-weight: 500; }",
|
|
@@ -283,7 +283,7 @@ export function generateLandingPageStyles() {
|
|
|
283
283
|
".tag-checkbox-label { cursor: pointer; display: inline-flex; align-items: center; gap: 3px; }",
|
|
284
284
|
".tag-checkbox-label input { margin: 0; }",
|
|
285
285
|
".tag-checkbox-label .tag-badge { cursor: pointer; }",
|
|
286
|
-
// Inline tag edit portal
|
|
286
|
+
// Inline tag edit portal - shared dropdown portaled to <body> via JS. The td has the dropdown class so the document click handler recognizes it as a dropdown
|
|
287
287
|
// context. The display override preserves table-cell layout (the .dropdown class defaults to display: inline-block for div-based dropdowns).
|
|
288
288
|
"td.dropdown { display: table-cell; }",
|
|
289
289
|
".inline-tag-menu { min-width: 140px; }",
|
|
@@ -291,7 +291,7 @@ export function generateLandingPageStyles() {
|
|
|
291
291
|
".tag-editable:hover { background: var(--surface-hover); }",
|
|
292
292
|
".tag-rename-input { font-size: 11px; padding: 1px 4px; border: 1px solid var(--interactive-primary); border-radius: 10px; " +
|
|
293
293
|
"outline: none; width: 120px; }",
|
|
294
|
-
// Tag manager modal
|
|
294
|
+
// Tag manager modal - list of tags with add/delete/restore actions. Hint and error styling use shared wizard classes.
|
|
295
295
|
".tag-manager-add { display: flex; gap: 8px; margin-bottom: 8px; }",
|
|
296
296
|
".tag-manager-add input { flex: 1; padding: 4px 8px; font-size: 13px; border: 1px solid var(--form-input-border); border-radius: 4px; }",
|
|
297
297
|
".tag-manager-list { display: flex; flex-direction: column; gap: 4px; }",
|
|
@@ -363,7 +363,7 @@ export function generateLandingPageStyles() {
|
|
|
363
363
|
// Health indicator colors. The login icon only uses health-success (provider verified). The health icon uses both (channel last-tune result).
|
|
364
364
|
".health-success { color: var(--interactive-success); }",
|
|
365
365
|
".health-failed { color: var(--interactive-delete); }",
|
|
366
|
-
// Health icon is non-interactive
|
|
366
|
+
// Health icon is non-interactive - no hover effect, default cursor. The .user-channel override needs matching specificity (0-3-0) to prevent the
|
|
367
367
|
// tinted hover background from making the health icon look clickable on user-defined channel rows.
|
|
368
368
|
".btn-icon-health { cursor: default; }",
|
|
369
369
|
".btn-icon-health:hover { background: transparent; color: var(--text-secondary); }",
|
package/dist/routes/services.js
CHANGED
|
@@ -2,11 +2,12 @@ import { getChannelListing, getChannelLogo, isPredefinedChannel } from "../confi
|
|
|
2
2
|
import { getChannelServiceLabel, getResolvedChannel, getServiceGroup, getServiceTagForChannel, isServiceTagEnabled, resolveServiceKey } from "../config/services.js";
|
|
3
3
|
import { getCurrentBrowser, minimizeBrowserWindow, registerManagedPage, unregisterManagedPage } from "../browser/index.js";
|
|
4
4
|
import { getProviderBySlug, normalizeChannelName } from "../browser/channelSelection.js";
|
|
5
|
+
import { sendError, sendNotFoundError } from "./config/http/envelope.js";
|
|
5
6
|
import { CONFIG } from "../config/index.js";
|
|
6
7
|
import { LOG } from "../utils/index.js";
|
|
7
8
|
/* The services endpoint exposes channel discovery for each registered service. A GET request to /services/:slug/channels creates a temporary browser page,
|
|
8
9
|
* navigates to the service's guide, runs the service's discoverChannels implementation, and returns a sorted JSON array of discovered channels. The temporary
|
|
9
|
-
* page is always closed in a finally block to prevent resource leaks. Concurrent requests for the same service are coalesced
|
|
10
|
+
* page is always closed in a finally block to prevent resource leaks. Concurrent requests for the same service are coalesced - only one discovery walk runs at a
|
|
10
11
|
* time, and subsequent requests piggyback on the in-flight result. A refresh=true request aborts any in-flight discovery and starts fresh.
|
|
11
12
|
*/
|
|
12
13
|
// Sentinel error used to identify aborted discoveries in the retry loop. Distinguishes abort rejections from genuine discovery failures so the loop only retries
|
|
@@ -26,8 +27,10 @@ const inflight = new Map();
|
|
|
26
27
|
*/
|
|
27
28
|
function sendDiscoveryError(res, label, error) {
|
|
28
29
|
const message = (error instanceof Error) ? error.message : String(error);
|
|
30
|
+
// Channel discovery failures are warnings rather than errors because they typically reflect provider DOM drift, network blips, or transient site outages
|
|
31
|
+
// (recoverable by retry) rather than server-side faults. The envelope helper normalizes the response shape; the LOG.warn level is preserved deliberately.
|
|
29
32
|
LOG.warn("Channel discovery failed for %s: %s.", label, message);
|
|
30
|
-
res
|
|
33
|
+
sendError(res, 500, { error: "Channel discovery failed: " + message + "." });
|
|
31
34
|
}
|
|
32
35
|
/**
|
|
33
36
|
* Runs service channel discovery in a temporary browser page. Opens a new page, navigates to the service's guide URL, runs the discovery function, and returns
|
|
@@ -40,7 +43,7 @@ function sendDiscoveryError(res, label, error) {
|
|
|
40
43
|
async function runDiscovery(provider, signal) {
|
|
41
44
|
let page = null;
|
|
42
45
|
// Close the page when the abort signal fires. This causes any in-progress Puppeteer operations to throw, propagating the cancellation through the discovery
|
|
43
|
-
// function without requiring explicit signal checking in each provider's implementation. The finally block also closes the page unconditionally
|
|
46
|
+
// function without requiring explicit signal checking in each provider's implementation. The finally block also closes the page unconditionally - the
|
|
44
47
|
// redundant close is idempotent (caught by try/catch).
|
|
45
48
|
const onAbort = () => {
|
|
46
49
|
if (page) {
|
|
@@ -99,16 +102,16 @@ async function runDiscovery(provider, signal) {
|
|
|
99
102
|
* (predefined keys are hand-crafted and may not match generateChannelKey output).
|
|
100
103
|
*
|
|
101
104
|
* For each canonical in the listing, we check for a variant matching the browsed service (via getServiceGroup) and extract its channelSelector via
|
|
102
|
-
* getResolvedChannel. This builds a channelSelector
|
|
105
|
+
* getResolvedChannel. This builds a channelSelector -> lineup state map that the discovered channels are matched against.
|
|
103
106
|
*
|
|
104
107
|
* @param channels - The raw discovered channels from the service.
|
|
105
108
|
* @param serviceSlug - The slug of the service being browsed (e.g., "spectrum", "hulu").
|
|
106
109
|
* @returns Annotated channels with lineup state where applicable.
|
|
107
110
|
*/
|
|
108
111
|
function annotateWithLineupState(channels, serviceSlug) {
|
|
109
|
-
// Build a channelSelector
|
|
112
|
+
// Build a channelSelector -> lineup state mapping for the browsed service. For each canonical entry in the listing, we find the variant that corresponds
|
|
110
113
|
// to this service (if one exists) and index by that variant's channelSelector. This ensures matching works even when predefined keys don't match the
|
|
111
|
-
// normalized channel name (e.g., "axstv" vs generateChannelKey("AXS TV")
|
|
114
|
+
// normalized channel name (e.g., "axstv" vs generateChannelKey("AXS TV") -> "axs-tv").
|
|
112
115
|
//
|
|
113
116
|
// When two canonicals share the same channelSelector for a service (East/Pacific pairs like "Disney Channel"), the map stores an array of states. The
|
|
114
117
|
// annotation step then uses the discovery entry's stationId to disambiguate which canonical to assign.
|
|
@@ -184,11 +187,11 @@ function annotateWithLineupState(channels, serviceSlug) {
|
|
|
184
187
|
if (!states || (states.length === 0)) {
|
|
185
188
|
return ch;
|
|
186
189
|
}
|
|
187
|
-
// Single match
|
|
190
|
+
// Single match - no disambiguation needed.
|
|
188
191
|
if (states.length === 1) {
|
|
189
192
|
return { ...ch, lineup: states[0] };
|
|
190
193
|
}
|
|
191
|
-
// Multiple matches
|
|
194
|
+
// Multiple matches - use stationId to pick the right canonical. Fall back to the first match if stationId doesn't disambiguate.
|
|
192
195
|
const match = states.find((s) => s.stationId === ch.stationId) ?? states[0];
|
|
193
196
|
return { ...ch, lineup: match };
|
|
194
197
|
});
|
|
@@ -199,18 +202,18 @@ function annotateWithLineupState(channels, serviceSlug) {
|
|
|
199
202
|
*/
|
|
200
203
|
export function setupServicesEndpoint(app) {
|
|
201
204
|
app.get("/services/:slug/channels", async (req, res) => {
|
|
202
|
-
const slug = req.params
|
|
205
|
+
const slug = req.params["slug"];
|
|
203
206
|
const provider = getProviderBySlug(slug);
|
|
204
207
|
if (!provider) {
|
|
205
|
-
res
|
|
208
|
+
sendNotFoundError(res, "Unknown service: " + slug + ".");
|
|
206
209
|
return;
|
|
207
210
|
}
|
|
208
211
|
// When refresh=true is requested, clear the service's caches (unified channel cache, row caches, fully-enumerated flags, etc.) so the discovery walk runs
|
|
209
|
-
// against fresh data. This also resets warm tuning state (watch URLs, GUIDs), but the discovery walk repopulates the unified cache before returning
|
|
210
|
-
// subsequent tune resolves from the freshly populated cache as normal. If a discovery is already in flight, abort it first
|
|
212
|
+
// against fresh data. This also resets warm tuning state (watch URLs, GUIDs), but the discovery walk repopulates the unified cache before returning - any
|
|
213
|
+
// subsequent tune resolves from the freshly populated cache as normal. If a discovery is already in flight, abort it first - clearing the cache while a
|
|
211
214
|
// discovery is progressively populating it would corrupt its state.
|
|
212
|
-
const lineup = req.query
|
|
213
|
-
const refresh = req.query
|
|
215
|
+
const lineup = req.query["lineup"] === "true";
|
|
216
|
+
const refresh = req.query["refresh"] === "true";
|
|
214
217
|
if (refresh) {
|
|
215
218
|
const existing = inflight.get(slug);
|
|
216
219
|
if (existing) {
|
|
@@ -243,7 +246,7 @@ export function setupServicesEndpoint(app) {
|
|
|
243
246
|
entry = { controller, promise };
|
|
244
247
|
inflight.set(slug, entry);
|
|
245
248
|
}
|
|
246
|
-
// Await the in-flight discovery. If it was aborted by a refresh=true request, a new discovery should now be in the map
|
|
249
|
+
// Await the in-flight discovery. If it was aborted by a refresh=true request, a new discovery should now be in the map - retry against that one. The caller
|
|
247
250
|
// doesn't know or care about the abort; they just want channels. Only DiscoveryAbortError triggers a retry; genuine failures are reported immediately.
|
|
248
251
|
for (;;) {
|
|
249
252
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"services.js","sourceRoot":"","sources":["../../src/routes/services.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACnG,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,eAAe,EAAE,uBAAuB,EAAE,mBAAmB,EAChH,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC3H,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AACzF,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAGxC;;;;GAIG;AAEH,iKAAiK;AACjK,sFAAsF;AACtF,MAAM,mBAAoB,SAAQ,KAAK;IAErC;QAEE,KAAK,CAAC,oBAAoB,CAAC,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAWD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAC;AAElD;;;;;GAKG;AACH,SAAS,kBAAkB,CAAC,GAAa,EAAE,KAAa,EAAE,KAAc;IAEtE,MAAM,OAAO,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEzE,GAAG,CAAC,IAAI,CAAC,sCAAsC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACjE,
|
|
1
|
+
{"version":3,"file":"services.js","sourceRoot":"","sources":["../../src/routes/services.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Response } from "express";
|
|
2
|
+
/**
|
|
3
|
+
* SSE transport handle returned by installSseStream. Callers use sendEvent to push events with JSON payloads (eventType=null for an unnamed data event), and
|
|
4
|
+
* call close() during request teardown to clear the heartbeat timer.
|
|
5
|
+
*/
|
|
6
|
+
export interface SseStream {
|
|
7
|
+
close: () => void;
|
|
8
|
+
sendEvent: (eventType: string | null, data: unknown) => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Sets the SSE response headers, flushes them so the connection opens immediately, and starts the heartbeat. Returns helpers the caller uses to push events
|
|
12
|
+
* and to clean up the heartbeat timer when the connection closes.
|
|
13
|
+
* @param res - The Express response object.
|
|
14
|
+
* @returns An SseStream handle for sending events and tearing down.
|
|
15
|
+
*/
|
|
16
|
+
export declare function installSseStream(res: Response): SseStream;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Heartbeat cadence in milliseconds. Sent as a named heartbeat event to keep the connection alive through proxies and let clients detect staleness.
|
|
2
|
+
const HEARTBEAT_INTERVAL_MS = 30_000;
|
|
3
|
+
/**
|
|
4
|
+
* Sets the SSE response headers, flushes them so the connection opens immediately, and starts the heartbeat. Returns helpers the caller uses to push events
|
|
5
|
+
* and to clean up the heartbeat timer when the connection closes.
|
|
6
|
+
* @param res - The Express response object.
|
|
7
|
+
* @returns An SseStream handle for sending events and tearing down.
|
|
8
|
+
*/
|
|
9
|
+
export function installSseStream(res) {
|
|
10
|
+
// Cache-Control prevents proxies from buffering the stream; Connection: keep-alive ensures the connection stays open; Content-Type: text/event-stream is
|
|
11
|
+
// the SSE protocol marker the browser EventSource needs to recognize the response.
|
|
12
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
13
|
+
res.setHeader("Connection", "keep-alive");
|
|
14
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
15
|
+
// Flush headers immediately so the EventSource handshake completes before the first event arrives.
|
|
16
|
+
res.flushHeaders();
|
|
17
|
+
// Send a named heartbeat event every HEARTBEAT_INTERVAL_MS to keep the connection alive through proxies and allow clients to detect staleness.
|
|
18
|
+
const heartbeat = setInterval(() => res.write("event: heartbeat\ndata: \n\n"), HEARTBEAT_INTERVAL_MS);
|
|
19
|
+
return {
|
|
20
|
+
close: () => { clearInterval(heartbeat); },
|
|
21
|
+
sendEvent: (eventType, data) => {
|
|
22
|
+
if (eventType !== null) {
|
|
23
|
+
res.write("event: " + eventType + "\n");
|
|
24
|
+
}
|
|
25
|
+
res.write("data: " + JSON.stringify(data) + "\n\n");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=sse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/routes/sse.ts"],"names":[],"mappings":"AAOA,oJAAoJ;AACpJ,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAYrC;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAa;IAE5C,yJAAyJ;IACzJ,mFAAmF;IACnF,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;IAC3C,GAAG,CAAC,SAAS,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;IAC1C,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,mBAAmB,CAAC,CAAC;IAEnD,mGAAmG;IACnG,GAAG,CAAC,YAAY,EAAE,CAAC;IAEnB,+IAA+I;IAC/I,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,8BAA8B,CAAC,EAAE,qBAAqB,CAAC,CAAC;IAEtG,OAAO;QAEL,KAAK,EAAE,GAAS,EAAE,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAChD,SAAS,EAAE,CAAC,SAAwB,EAAE,IAAa,EAAQ,EAAE;YAE3D,IAAG,SAAS,KAAK,IAAI,EAAE,CAAC;gBAEtB,GAAG,CAAC,KAAK,CAAC,SAAS,GAAG,SAAS,GAAG,IAAI,CAAC,CAAC;YAC1C,CAAC;YAED,GAAG,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;QACtD,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/routes/streams.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { getAllStreams, getStream, getStreamCount, getStreamMemoryUsage } from "../streaming/registry.js";
|
|
2
2
|
import { getStatusSnapshot, getStreamStatus, subscribeToStatus } from "../streaming/statusEmitter.js";
|
|
3
|
+
import { sendNotFoundError, sendSuccess, sendValidationError } from "./config/http/envelope.js";
|
|
3
4
|
import { CONFIG } from "../config/index.js";
|
|
4
5
|
import { emitCurrentSystemStatus } from "../browser/index.js";
|
|
6
|
+
import { installSseStream } from "./sse.js";
|
|
5
7
|
import { terminateStream } from "../streaming/lifecycle.js";
|
|
6
8
|
/* The streams endpoint provides visibility into active streams and allows operators to terminate streams via the API. This is useful for debugging and for
|
|
7
9
|
* integrations that need to manage stream lifecycle.
|
|
@@ -43,44 +45,29 @@ export function setupStreamsEndpoint(app) {
|
|
|
43
45
|
app.delete("/streams/:id", (req, res) => {
|
|
44
46
|
const streamIdParam = parseInt(req.params.id);
|
|
45
47
|
if (isNaN(streamIdParam)) {
|
|
46
|
-
res
|
|
48
|
+
sendValidationError(res, "Invalid stream ID.");
|
|
47
49
|
return;
|
|
48
50
|
}
|
|
49
51
|
const streamInfo = getStream(streamIdParam);
|
|
50
52
|
if (!streamInfo) {
|
|
51
|
-
res
|
|
53
|
+
sendNotFoundError(res, "Stream not found.");
|
|
52
54
|
return;
|
|
53
55
|
}
|
|
54
56
|
terminateStream(streamIdParam, streamInfo.info.storeKey, "API request");
|
|
55
57
|
void emitCurrentSystemStatus();
|
|
56
|
-
res
|
|
58
|
+
sendSuccess(res, { data: { streamId: streamIdParam }, message: "Stream terminated." });
|
|
57
59
|
});
|
|
58
60
|
/* The /streams/status endpoint provides real-time stream and system status via Server-Sent Events. Connected clients receive an initial snapshot of all streams
|
|
59
61
|
* and the system state, then receive updates as streams are added, removed, or their health changes.
|
|
60
62
|
*/
|
|
61
63
|
app.get("/streams/status", (req, res) => {
|
|
62
|
-
|
|
63
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
64
|
-
res.setHeader("Connection", "keep-alive");
|
|
65
|
-
res.setHeader("Content-Type", "text/event-stream");
|
|
66
|
-
// Disable response buffering to ensure events are sent immediately.
|
|
67
|
-
res.flushHeaders();
|
|
64
|
+
const sse = installSseStream(res);
|
|
68
65
|
// Send the initial snapshot so clients have current state.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
// Subscribe to status events and forward them to the client.
|
|
73
|
-
const unsubscribe = subscribeToStatus((eventType, data) => {
|
|
74
|
-
res.write("event: " + eventType + "\n");
|
|
75
|
-
res.write("data: " + JSON.stringify(data) + "\n\n");
|
|
76
|
-
});
|
|
77
|
-
// Send a named heartbeat event every 30 seconds to keep the connection alive through proxies and allow clients to detect staleness.
|
|
78
|
-
const heartbeatInterval = setInterval(() => {
|
|
79
|
-
res.write("event: heartbeat\ndata: \n\n");
|
|
80
|
-
}, 30000);
|
|
81
|
-
// Clean up when the client disconnects.
|
|
66
|
+
sse.sendEvent("snapshot", getStatusSnapshot());
|
|
67
|
+
// Subscribe to status events and forward them to the client; the heartbeat is owned by installSseStream.
|
|
68
|
+
const unsubscribe = subscribeToStatus((eventType, data) => { sse.sendEvent(eventType, data); });
|
|
82
69
|
req.on("close", () => {
|
|
83
|
-
|
|
70
|
+
sse.close();
|
|
84
71
|
unsubscribe();
|
|
85
72
|
});
|
|
86
73
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streams.js","sourceRoot":"","sources":["../../src/routes/streams.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAC1G,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACtG,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAI5C,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D;;GAEG;AAEH;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAE/C,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,IAAa,EAAE,GAAa,EAAQ,EAAE;QAEzD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,MAAM,OAAO,GAcP,EAAE,CAAC;QAET,KAAI,MAAM,UAAU,IAAI,aAAa,EAAE,EAAE,CAAC;YAExC,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;YAE9C,OAAO,CAAC,IAAI,CAAC;gBAEX,OAAO,EAAE,UAAU,CAAC,WAAW;gBAC/B,WAAW,EAAE,MAAM,EAAE,WAAW,IAAI,CAAC;gBACrC,OAAO,EAAE,MAAM,EAAE,OAAO,IAAI,EAAE;gBAC9B,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,UAAU,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC;gBACnE,eAAe,EAAE,MAAM,EAAE,eAAe,IAAI,CAAC;gBAC7C,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,SAAS;gBACnC,EAAE,EAAE,UAAU,CAAC,EAAE;gBACjB,OAAO,EAAE,MAAM,EAAE,OAAO,IAAI,EAAE;gBAC9B,MAAM,EAAE,oBAAoB,CAAC,UAAU,CAAC;gBACxC,gBAAgB,EAAE,MAAM,EAAE,gBAAgB,IAAI,CAAC;gBAC/C,QAAQ,EAAE,MAAM,EAAE,QAAQ,IAAI,EAAE;gBAChC,SAAS,EAAE,UAAU,CAAC,SAAS,CAAC,WAAW,EAAE;gBAC7C,GAAG,EAAE,UAAU,CAAC,GAAG;aACpB,CAAC,CAAC;QACL,CAAC;QAED,GAAG,CAAC,IAAI,CAAC;YAEP,KAAK,EAAE,cAAc,EAAE;YACvB,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC,oBAAoB;YAC5C,OAAO,EAAE,OAAO;SACjB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,6JAA6J;IAC7J,iDAAiD;IACjD,GAAG,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,GAAY,EAAE,GAAa,EAAQ,EAAE;QAE/D,MAAM,aAAa,GAAG,QAAQ,CAAE,GAAG,CAAC,MAAyB,CAAC,EAAE,CAAC,CAAC;QAElE,IAAG,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;YAExB,
|
|
1
|
+
{"version":3,"file":"streams.js","sourceRoot":"","sources":["../../src/routes/streams.ts"],"names":[],"mappings":"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"}
|
package/dist/routes/ui.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { generateAlert, generateBadge, generateButton, generatePanelHeader, generateSection, generateSelect, generateSimpleAlert, generateStatusIndicator, generateTextInput } from "./components.
|
|
2
|
-
export type { AlertType, BadgeVariant, ButtonOptions, ButtonSize, ButtonVariant, SelectOption, SelectOptions, TextInputOptions } from "./components.
|
|
1
|
+
export { generateAlert, generateBadge, generateButton, generatePanelHeader, generateSection, generateSelect, generateSimpleAlert, generateStatusIndicator, generateTextInput } from "./components.ts";
|
|
2
|
+
export type { AlertType, BadgeVariant, ButtonOptions, ButtonSize, ButtonVariant, SelectOption, SelectOptions, TextInputOptions } from "./components.ts";
|
|
3
3
|
/**
|
|
4
4
|
* Options for configuring the tab script behavior.
|
|
5
5
|
*/
|
package/dist/routes/upgrade.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { LOG, fetchLatestVersion,
|
|
1
|
+
import { LOG, fetchLatestVersion, getPackageVersion, isRunningAsService, isVersionLessThan, normalizeVersion } from "../utils/index.js";
|
|
2
|
+
import { sendErrorResponse, sendSuccess, sendValidationError } from "./config/http/envelope.js";
|
|
2
3
|
import { closeBrowser } from "../browser/index.js";
|
|
3
4
|
import { detectInstallMethod } from "../upgrade/detection.js";
|
|
4
5
|
import { exec as execCallback } from "node:child_process";
|
|
@@ -29,8 +30,7 @@ export function setupUpgradeEndpoint(app) {
|
|
|
29
30
|
});
|
|
30
31
|
}
|
|
31
32
|
catch (error) {
|
|
32
|
-
|
|
33
|
-
res.status(500).json({ message: "Failed to get upgrade info: " + formatError(error), success: false });
|
|
33
|
+
sendErrorResponse(res, error, "get upgrade info");
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
36
|
// POST /upgrade - Executes the upgrade command for the detected installation method. Uses async exec so the event loop stays free during the upgrade command,
|
|
@@ -39,7 +39,7 @@ export function setupUpgradeEndpoint(app) {
|
|
|
39
39
|
try {
|
|
40
40
|
const info = detectInstallMethod();
|
|
41
41
|
if (!info.upgradeable) {
|
|
42
|
-
res
|
|
42
|
+
sendValidationError(res, "This installation method does not support in-place upgrades.");
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
45
|
// Execute the upgrade command asynchronously to avoid blocking the event loop.
|
|
@@ -50,7 +50,7 @@ export function setupUpgradeEndpoint(app) {
|
|
|
50
50
|
LOG.info("Executing upgrade via web UI: %s.", info.upgradeCommand);
|
|
51
51
|
await exec(info.upgradeCommand, options);
|
|
52
52
|
const willRestart = isRunningAsService();
|
|
53
|
-
res
|
|
53
|
+
sendSuccess(res, { data: { willRestart }, message: "Upgrade complete." });
|
|
54
54
|
// If running as a service, exit after a short delay so the service manager restarts us with the new version.
|
|
55
55
|
if (willRestart) {
|
|
56
56
|
setTimeout(() => {
|
|
@@ -60,8 +60,7 @@ export function setupUpgradeEndpoint(app) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
catch (error) {
|
|
63
|
-
|
|
64
|
-
res.status(500).json({ message: "Upgrade failed: " + formatError(error), success: false, willRestart: false });
|
|
63
|
+
sendErrorResponse(res, error, "upgrade");
|
|
65
64
|
}
|
|
66
65
|
});
|
|
67
66
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../src/routes/upgrade.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,GAAG,EAAE,kBAAkB,EAAE,
|
|
1
|
+
{"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../src/routes/upgrade.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,GAAG,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACxI,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChG,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,IAAI,GAAG,SAAS,CAAC,YAAY,CAAC,CAAC;AAErC;;GAEG;AAEH;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAE/C,qHAAqH;IACrH,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,KAAK,EAAE,IAAa,EAAE,GAAa,EAAiB,EAAE;QAE7E,IAAI,CAAC;YAEH,MAAM,IAAI,GAAG,mBAAmB,EAAE,CAAC;YACnC,MAAM,cAAc,GAAG,gBAAgB,CAAC,iBAAiB,EAAE,CAAC,CAAC;YAC7D,MAAM,aAAa,GAAG,MAAM,kBAAkB,EAAE,CAAC;YACjD,MAAM,eAAe,GAAG,CAAC,aAAa,KAAK,IAAI,CAAC,IAAI,iBAAiB,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;YAErG,GAAG,CAAC,IAAI,CAAC;gBAEP,cAAc;gBACd,aAAa;gBACb,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,eAAe;gBACf,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,WAAW,EAAE,IAAI,CAAC,WAAW;aAC9B,CAAC,CAAC;QACL,CAAC;QAAC,OAAM,KAAK,EAAE,CAAC;YAEd,iBAAiB,CAAC,GAAG,EAAE,KAAK,EAAE,kBAAkB,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,8JAA8J;IAC9J,sEAAsE;IACtE,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,IAAa,EAAE,GAAa,EAAiB,EAAE;QAEzE,IAAI,CAAC;YAEH,MAAM,IAAI,GAAG,mBAAmB,EAAE,CAAC;YAEnC,IAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;gBAErB,mBAAmB,CAAC,GAAG,EAAE,8DAA8D,CAAC,CAAC;gBAEzF,OAAO;YACT,CAAC;YAED,+EAA+E;YAC/E,MAAM,OAAO,GAAsC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;YAEvE,IAAG,CAAC,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;gBAEpD,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC;YAChC,CAAC;YAED,GAAG,CAAC,IAAI,CAAC,mCAAmC,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;YAEnE,MAAM,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;YAEzC,MAAM,WAAW,GAAG,kBAAkB,EAAE,CAAC;YAEzC,WAAW,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC,CAAC;YAE1E,6GAA6G;YAC7G,IAAG,WAAW,EAAE,CAAC;gBAEf,UAAU,CAAC,GAAG,EAAE;oBAEd,GAAG,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;oBAE/D,KAAK,YAAY,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBACzF,CAAC,EAAE,GAAG,CAAC,CAAC;YACV,CAAC;QACH,CAAC;QAAC,OAAM,KAAK,EAAE,CAAC;YAEd,iBAAiB,CAAC,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* commands.context.ts: The default adapter that produces a ServiceContext from real runtime I/O. The handlers in commands.ts are pure orchestrators over a
|
|
4
|
+
* ServiceContext; this file is the only place in the service-commands module that calls execFile, fetches HTTP, reads the filesystem, or queries process.platform.
|
|
5
|
+
* Tests bypass this file entirely by constructing ServiceContext literals inline.
|
|
6
|
+
*/
|
|
7
|
+
import { DEFAULTS, readConfig } from "../config/userConfig.js";
|
|
8
|
+
import { getNodeExecutablePath, getPlatform, getPrismCastEntryPoint, getServiceFilePath } from "../utils/platform.js";
|
|
9
|
+
import { buildServiceDefinition, detectStalePaths, getServiceGenerator, getServicePaths } from "./generators.js";
|
|
10
|
+
import { print, printError } from "../utils/cliOutput.js";
|
|
11
|
+
import { getDataDir } from "../config/paths.js";
|
|
12
|
+
/**
|
|
13
|
+
* Builds the default ServiceContext from real runtime I/O.
|
|
14
|
+
* @returns A ServiceContext populated from the live process.
|
|
15
|
+
*/
|
|
16
|
+
export function createDefaultServiceContext() {
|
|
17
|
+
return {
|
|
18
|
+
buildServiceDefinition,
|
|
19
|
+
detectStalePaths,
|
|
20
|
+
fetchActiveStreams: async (port) => {
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetch("http://127.0.0.1:" + String(port) + "/streams", { signal: AbortSignal.timeout(3000) });
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return await response.json();
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
getDataDir,
|
|
33
|
+
getEntryPoint: getPrismCastEntryPoint,
|
|
34
|
+
getGenerator: getServiceGenerator,
|
|
35
|
+
getNodePath: getNodeExecutablePath,
|
|
36
|
+
getPlatform: () => getPlatform(),
|
|
37
|
+
getServerPort: async () => {
|
|
38
|
+
const result = await readConfig();
|
|
39
|
+
return result.config.server?.port ?? DEFAULTS.server.port;
|
|
40
|
+
},
|
|
41
|
+
getServiceFilePath,
|
|
42
|
+
getServicePaths,
|
|
43
|
+
stderr: printError,
|
|
44
|
+
stdout: print
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=commands.context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.context.js","sourceRoot":"","sources":["../../src/service/commands.context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAiB,qBAAqB,EAAE,WAAW,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAErI,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACjH,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD;;;GAGG;AACH,MAAM,UAAU,2BAA2B;IAEzC,OAAO;QAEL,sBAAsB;QACtB,gBAAgB;QAChB,kBAAkB,EAAE,KAAK,EAAE,IAAY,EAAmC,EAAE;YAE1E,IAAI,CAAC;gBAEH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,mBAAmB,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,UAAU,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBAErH,IAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;oBAEhB,OAAO,IAAI,CAAC;gBACd,CAAC;gBAED,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAqB,CAAC;YAClD,CAAC;YAAC,MAAM,CAAC;gBAEP,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QACD,UAAU;QACV,aAAa,EAAE,sBAAsB;QACrC,YAAY,EAAE,mBAAmB;QACjC,WAAW,EAAE,qBAAqB;QAClC,WAAW,EAAE,GAAa,EAAE,CAAC,WAAW,EAAE;QAC1C,aAAa,EAAE,KAAK,IAAqB,EAAE;YAEzC,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;YAElC,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,IAAI,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC;QAC5D,CAAC;QACD,kBAAkB;QAClB,eAAe;QACf,MAAM,EAAE,UAAU;QAClB,MAAM,EAAE,KAAK;KACd,CAAC;AACJ,CAAC"}
|
|
@@ -1,43 +1,88 @@
|
|
|
1
|
+
import { type Platform } from "../utils/platform.ts";
|
|
2
|
+
import type { ServiceDefinition, ServiceGenerator, ServicePaths, StalePathResult } from "./generators.ts";
|
|
3
|
+
import type { Nullable } from "../types/index.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Response from the /streams endpoint.
|
|
6
|
+
*/
|
|
7
|
+
export interface StreamsResponse {
|
|
8
|
+
count: number;
|
|
9
|
+
limit: number;
|
|
10
|
+
streams: {
|
|
11
|
+
channel: Nullable<string>;
|
|
12
|
+
duration: number;
|
|
13
|
+
id: number;
|
|
14
|
+
showName: string;
|
|
15
|
+
url: string;
|
|
16
|
+
}[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The runtime context the service handlers consume. Each field models one capability the handlers need - generator selection, path/platform queries, server probe,
|
|
20
|
+
* stale-path detection, service-definition build, and stdout/stderr output. Decision logic is a pure function of this shape; production wires it through
|
|
21
|
+
* createDefaultServiceContext (in commands.context.ts), tests pass a context literal.
|
|
22
|
+
*/
|
|
23
|
+
export interface ServiceContext {
|
|
24
|
+
readonly buildServiceDefinition: () => ServiceDefinition;
|
|
25
|
+
readonly detectStalePaths: () => Nullable<StalePathResult>;
|
|
26
|
+
readonly fetchActiveStreams: (port: number) => Promise<Nullable<StreamsResponse>>;
|
|
27
|
+
readonly getDataDir: () => string;
|
|
28
|
+
readonly getEntryPoint: () => string;
|
|
29
|
+
readonly getGenerator: () => Nullable<ServiceGenerator>;
|
|
30
|
+
readonly getNodePath: () => string;
|
|
31
|
+
readonly getPlatform: () => Platform;
|
|
32
|
+
readonly getServerPort: () => Promise<number>;
|
|
33
|
+
readonly getServiceFilePath: () => string;
|
|
34
|
+
readonly getServicePaths: () => Nullable<ServicePaths>;
|
|
35
|
+
readonly stderr: (line: string) => void;
|
|
36
|
+
readonly stdout: (line: string) => void;
|
|
37
|
+
}
|
|
1
38
|
/**
|
|
2
39
|
* Prints usage information for the service subcommand.
|
|
40
|
+
* @param ctx - The service context. Defaults to createDefaultServiceContext().
|
|
3
41
|
*/
|
|
4
|
-
export declare function printServiceUsage(): void;
|
|
42
|
+
export declare function printServiceUsage(ctx?: ServiceContext): void;
|
|
5
43
|
/**
|
|
6
44
|
* Handles the `service install` command. Generates and installs a service file for the current platform, then starts the service.
|
|
7
45
|
* @param force - If true, reinstall even if already installed.
|
|
46
|
+
* @param ctx - The service context. Defaults to createDefaultServiceContext().
|
|
8
47
|
* @returns Exit code (0 for success, 1 for error).
|
|
9
48
|
*/
|
|
10
|
-
export declare function handleInstall(force: boolean): Promise<number>;
|
|
49
|
+
export declare function handleInstall(force: boolean, ctx?: ServiceContext): Promise<number>;
|
|
11
50
|
/**
|
|
12
51
|
* Handles the `service uninstall` command. Stops and removes the service.
|
|
52
|
+
* @param ctx - The service context. Defaults to createDefaultServiceContext().
|
|
13
53
|
* @returns Exit code (0 for success, 1 for error).
|
|
14
54
|
*/
|
|
15
|
-
export declare function handleUninstall(): Promise<number>;
|
|
55
|
+
export declare function handleUninstall(ctx?: ServiceContext): Promise<number>;
|
|
16
56
|
/**
|
|
17
57
|
* Handles the `service start` command. Starts the service if it is installed but not running. If the service file contains stale paths (e.g., after a Homebrew
|
|
18
58
|
* upgrade), they are fixed automatically before starting.
|
|
59
|
+
* @param ctx - The service context. Defaults to createDefaultServiceContext().
|
|
19
60
|
* @returns Exit code (0 for success, 1 for error).
|
|
20
61
|
*/
|
|
21
|
-
export declare function handleStart(): Promise<number>;
|
|
62
|
+
export declare function handleStart(ctx?: ServiceContext): Promise<number>;
|
|
22
63
|
/**
|
|
23
64
|
* Handles the `service stop` command. Always attempts to stop the service regardless of whether it appears to be running, ensuring that loaded-but-not-running
|
|
24
65
|
* state (common on launchd after a crash) is properly cleared.
|
|
66
|
+
* @param ctx - The service context. Defaults to createDefaultServiceContext().
|
|
25
67
|
* @returns Exit code (0 for success, 1 for error).
|
|
26
68
|
*/
|
|
27
|
-
export declare function handleStop(): Promise<number>;
|
|
69
|
+
export declare function handleStop(ctx?: ServiceContext): Promise<number>;
|
|
28
70
|
/**
|
|
29
71
|
* Handles the `service restart` command. Stops the service, then compares paths and regenerates the service file if needed before starting.
|
|
72
|
+
* @param ctx - The service context. Defaults to createDefaultServiceContext().
|
|
30
73
|
* @returns Exit code (0 for success, 1 for error).
|
|
31
74
|
*/
|
|
32
|
-
export declare function handleRestart(): Promise<number>;
|
|
75
|
+
export declare function handleRestart(ctx?: ServiceContext): Promise<number>;
|
|
33
76
|
/**
|
|
34
77
|
* Handles the `service status` command. Shows the current service installation and running status.
|
|
78
|
+
* @param ctx - The service context. Defaults to createDefaultServiceContext().
|
|
35
79
|
* @returns Exit code (0 for success).
|
|
36
80
|
*/
|
|
37
|
-
export declare function handleStatus(): Promise<number>;
|
|
81
|
+
export declare function handleStatus(ctx?: ServiceContext): Promise<number>;
|
|
38
82
|
/**
|
|
39
83
|
* Main handler for the `service` subcommand. Parses arguments and delegates to the appropriate handler.
|
|
40
84
|
* @param args - Arguments after 'service' (e.g., ['install', '--force']).
|
|
85
|
+
* @param ctx - The service context. Defaults to createDefaultServiceContext().
|
|
41
86
|
* @returns Exit code (0 for success, 1 for error).
|
|
42
87
|
*/
|
|
43
|
-
export declare function handleServiceCommand(args: string[]): Promise<number>;
|
|
88
|
+
export declare function handleServiceCommand(args: string[], ctx?: ServiceContext): Promise<number>;
|