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
|
@@ -1,497 +1,95 @@
|
|
|
1
1
|
/* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
2
|
*
|
|
3
|
-
* status.ts: Client-side JavaScript generator for the PrismCast status display.
|
|
3
|
+
* status.ts: Client-side JavaScript generator for the PrismCast status display. The pure handler logic lives in status.handlers.ts as ports-and-adapters TS
|
|
4
|
+
* functions over a HandlerContext; this file is the boundary that builds the script string.
|
|
5
|
+
*
|
|
6
|
+
* The generator emits three sections in order:
|
|
7
|
+
*
|
|
8
|
+
* 1. The script-side constants (HANDLER_CONSTANTS) as `const NAME = <JSON.stringify(value)>;` lines. The handler bodies reference these by their TS-side
|
|
9
|
+
* identifiers; emitting them via JSON.stringify keeps the constants single-sourced rather than hand-mirrored as string templates.
|
|
10
|
+
*
|
|
11
|
+
* 2. The pure handler functions (HANDLER_FUNCTIONS) via Function.prototype.toString() concatenation. Each function declaration emits its full source; the
|
|
12
|
+
* browser parses them as siblings in script-tag scope, so cross-handler references resolve naturally. TypeScript type annotations strip to whitespace via
|
|
13
|
+
* Node's strip-types and to nothing via tsc compilation; the runtime body is identical between test and production.
|
|
14
|
+
*
|
|
15
|
+
* 3. A small IIFE that constructs the initial state, resolves the window.* externals, opens the EventSource, dispatches incoming events to the extracted
|
|
16
|
+
* handlers, attaches the window-bound trampolines (toggleStreamPopover, toggleStreamDetails, copyOverviewPlaylistUrl), and starts the periodic timers
|
|
17
|
+
* (1-second duration tick, 45-second staleness watchdog) plus the visibility-driven reconnect listener and the iPad tooltip initializer.
|
|
18
|
+
*
|
|
19
|
+
* Every function the IIFE references is one of: a sibling extracted via .toString() (resolves at the script-tag top scope), or a browser global (Date,
|
|
20
|
+
* EventSource, JSON, document, setInterval, window). The IIFE has no closures over server-side TS state.
|
|
21
|
+
*/
|
|
22
|
+
import { HANDLER_CONSTANTS, HANDLER_FUNCTIONS } from "./status.handlers.js";
|
|
23
|
+
/**
|
|
24
|
+
* Generates the inline <script> block that drives the status display. Returns a complete <script>...</script> string ready to embed in the page wrapper.
|
|
25
|
+
* @returns The script string.
|
|
4
26
|
*/
|
|
5
27
|
export function generateStatusScript() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"}",
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"}",
|
|
37
|
-
// Format absolute time (e.g., "6:54 AM" or "Jan 14, 6:54 AM" if different day).
|
|
38
|
-
"function formatTime(isoString) {",
|
|
39
|
-
" const date = new Date(isoString);",
|
|
40
|
-
" const now = new Date();",
|
|
41
|
-
" let hours = date.getHours();",
|
|
42
|
-
" const minutes = date.getMinutes();",
|
|
43
|
-
" const ampm = hours >= 12 ? 'PM' : 'AM';",
|
|
44
|
-
" hours = hours % 12;",
|
|
45
|
-
" hours = hours ? hours : 12;",
|
|
46
|
-
" let timeStr = hours + ':' + (minutes < 10 ? '0' : '') + minutes + ' ' + ampm;",
|
|
47
|
-
" if(date.toDateString() !== now.toDateString()) {",
|
|
48
|
-
" const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];",
|
|
49
|
-
" timeStr = months[date.getMonth()] + ' ' + date.getDate() + ', ' + timeStr;",
|
|
50
|
-
" }",
|
|
51
|
-
" return timeStr;",
|
|
52
|
-
"}",
|
|
53
|
-
// Extract concise domain from URL for display (last two hostname parts). Mirrors the server-side extractDomain() in utils/format.ts.
|
|
54
|
-
"function getDomain(url) {",
|
|
55
|
-
" const parsed = URL.parse(url);",
|
|
56
|
-
" if(!parsed) return url;",
|
|
57
|
-
" const parts = parsed.hostname.split('.');",
|
|
58
|
-
" return (parts.length > 2) ? parts.slice(-2).join('.') : parts.join('.');",
|
|
59
|
-
"}",
|
|
60
|
-
// Resolve the recovery-level label for a stream in the recovering state. Kept as a dedicated helper because the mapping is level-based, not string-based,
|
|
61
|
-
// so it cannot fold into the same lookup map as the top-level health labels. Escalation level semantics defined in monitor.ts:
|
|
62
|
-
// L1=play/unmute, L2=seek, L3=source reload, L4=page navigation.
|
|
63
|
-
"function getRecoveringLabel(level) {",
|
|
64
|
-
" switch(level) {",
|
|
65
|
-
" case 1: return 'Resuming playback';",
|
|
66
|
-
" case 2: return 'Syncing to live';",
|
|
67
|
-
" case 3: return 'Reloading player';",
|
|
68
|
-
" default: return (level >= 4) ? 'Reloading page' : 'Recovering';",
|
|
69
|
-
" }",
|
|
70
|
-
"}",
|
|
71
|
-
// Static labels for each top-level health state. The recovering state delegates to getRecoveringLabel which dispatches on the escalation level.
|
|
72
|
-
"const healthLabels = { healthy: 'Healthy', buffering: 'Buffering', stalled: 'Stalled', error: 'Error' };",
|
|
73
|
-
// Get health badge HTML using CSS variables for theme-aware colors.
|
|
74
|
-
"function getHealthBadge(health, level) {",
|
|
75
|
-
" const label = (health === 'recovering') ? getRecoveringLabel(level) : (healthLabels[health] ?? health);",
|
|
76
|
-
" return '<span class=\"status-dot\" style=\"color: ' + (healthColorVars[health] ?? 'var(--text-muted)') + ';\">●</span> ' +",
|
|
77
|
-
" '<span style=\"color: var(--text-secondary);\">' + label + '</span>';",
|
|
78
|
-
"}",
|
|
79
|
-
// Update system status display in header. Shows system health (green dot when connected, red with label when not) and stream count.
|
|
80
|
-
"function updateSystemStatus() {",
|
|
81
|
-
" if(!systemData) return;",
|
|
82
|
-
" const healthEl = document.getElementById('system-health');",
|
|
83
|
-
" const streamEl = document.getElementById('stream-count');",
|
|
84
|
-
" if(systemData.browser.connected) {",
|
|
85
|
-
" healthEl.innerHTML = '<span class=\"status-dot\" style=\"color: var(--stream-healthy);\">●</span>';",
|
|
86
|
-
" } else {",
|
|
87
|
-
" healthEl.innerHTML = '<span class=\"status-dot\" style=\"color: var(--stream-error);\">●</span> Browser offline';",
|
|
88
|
-
" }",
|
|
89
|
-
" const active = systemData.streams.active;",
|
|
90
|
-
" const limit = systemData.streams.limit;",
|
|
91
|
-
" if(active === 0) {",
|
|
92
|
-
" streamEl.textContent = '0 streams';",
|
|
93
|
-
" streamEl.classList.remove('clickable');",
|
|
94
|
-
" const popMenu = document.getElementById('stream-popover-menu');",
|
|
95
|
-
" if(popMenu) popMenu.classList.remove('show');",
|
|
96
|
-
" } else {",
|
|
97
|
-
" streamEl.textContent = active + '/' + limit + ' streams';",
|
|
98
|
-
" streamEl.classList.add('clickable');",
|
|
99
|
-
" }",
|
|
100
|
-
"}",
|
|
101
|
-
// Build the popover content from streamData. Populates the given menu element with one row per active stream.
|
|
102
|
-
"function buildStreamPopoverContent(menu) {",
|
|
103
|
-
" let html = '';",
|
|
104
|
-
" const now = Date.now();",
|
|
105
|
-
" for(const s of Object.values(streamData)) {",
|
|
106
|
-
" const color = healthColorVars[s.health] ?? 'var(--text-muted)';",
|
|
107
|
-
" const name = s.channel || s.serviceName || getDomain(s.url);",
|
|
108
|
-
" const dur = Math.floor((now - new Date(s.startTime).getTime()) / 1000);",
|
|
109
|
-
" const hwBadge = s.hardwareAccelerated ? ' <span title=\"Hardware accelerated\">\\u26A1</span>' : '';",
|
|
110
|
-
" const showSuffix = s.showName ? ' <span class=\"stream-popover-show\">' + s.showName + '</span>' : '';",
|
|
111
|
-
" html += '<div class=\"stream-popover-row\">';",
|
|
112
|
-
" html += '<span class=\"status-dot\" style=\"color: ' + color + ';\">●</span>';",
|
|
113
|
-
" html += channelDisplayHtml(s.logoUrl, name, 'stream-popover-logo', 'stream-popover-channel');",
|
|
114
|
-
" html += hwBadge + showSuffix;",
|
|
115
|
-
" html += '<span class=\"stream-popover-duration\">' + formatDuration(dur) + '</span>';",
|
|
116
|
-
" html += '</div>';",
|
|
117
|
-
" }",
|
|
118
|
-
" menu.innerHTML = html;",
|
|
119
|
-
"}",
|
|
120
|
-
// Update an already-open stream popover with current data. Called from SSE handlers and the duration interval.
|
|
121
|
-
"function updateStreamPopover() {",
|
|
122
|
-
" const menu = document.getElementById('stream-popover-menu');",
|
|
123
|
-
" if(!menu || !menu.classList.contains('show')) return;",
|
|
124
|
-
" const ids = Object.keys(streamData);",
|
|
125
|
-
" if(ids.length === 0) {",
|
|
126
|
-
" menu.classList.remove('show');",
|
|
127
|
-
" return;",
|
|
128
|
-
" }",
|
|
129
|
-
" buildStreamPopoverContent(menu);",
|
|
130
|
-
"}",
|
|
131
|
-
// Toggle the stream popover open or closed.
|
|
132
|
-
"window.toggleStreamPopover = () => {",
|
|
133
|
-
" const ids = Object.keys(streamData);",
|
|
134
|
-
" if(ids.length === 0) return;",
|
|
135
|
-
" const menu = document.getElementById('stream-popover-menu');",
|
|
136
|
-
" if(!menu) return;",
|
|
137
|
-
" const isOpen = menu.classList.contains('show');",
|
|
138
|
-
" dropdowns.close();",
|
|
139
|
-
" if(!isOpen) {",
|
|
140
|
-
" buildStreamPopoverContent(menu);",
|
|
141
|
-
" menu.classList.add('show');",
|
|
142
|
-
" }",
|
|
143
|
-
"};",
|
|
144
|
-
// Format last issue for display.
|
|
145
|
-
"function formatLastIssue(s) {",
|
|
146
|
-
" if(!s.lastIssueType || !s.lastIssueTime) { return 'None'; }",
|
|
147
|
-
" const issueLabel = s.lastIssueType.charAt(0).toUpperCase() + s.lastIssueType.slice(1);",
|
|
148
|
-
" const timeStr = formatTime(new Date(s.lastIssueTime).toISOString());",
|
|
149
|
-
" const status = (s.health === 'healthy') ? ' (recovered)' : ' (recovering)';",
|
|
150
|
-
" return issueLabel + ' at ' + timeStr + status;",
|
|
151
|
-
"}",
|
|
152
|
-
// Format auto-recovery info for display.
|
|
153
|
-
"function formatAutoRecovery(s) {",
|
|
154
|
-
" const attempts = s.recoveryAttempts;",
|
|
155
|
-
" const reloads = s.pageReloadsInWindow;",
|
|
156
|
-
" if(attempts === 0) { return 'N/A'; }",
|
|
157
|
-
" let str = attempts + (attempts === 1 ? ' attempt' : ' attempts');",
|
|
158
|
-
" if(reloads > 0) { str += ', ' + reloads + (reloads === 1 ? ' page reload' : ' page reloads'); }",
|
|
159
|
-
" return str;",
|
|
160
|
-
"}",
|
|
161
|
-
// Format client type breakdown for the detail row.
|
|
162
|
-
"function formatClients(s) {",
|
|
163
|
-
" if(s.clientCount === 0) { return 'None'; }",
|
|
164
|
-
" const parts = [];",
|
|
165
|
-
" for(const c of s.clients) {",
|
|
166
|
-
" parts.push(c.count + ' ' + (clientTypeLabels[c.type] ?? c.type));",
|
|
167
|
-
" }",
|
|
168
|
-
" return parts.join(', ');",
|
|
169
|
-
"}",
|
|
170
|
-
// Render scheduling. All DOM-writing functions funnel through requestAnimationFrame gates so multiple SSE events arriving within the same frame (e.g., two
|
|
171
|
-
// streamHealthChanged events 150ms apart) produce a single DOM write instead of redundant back-to-back rebuilds that destroy and recreate image elements.
|
|
172
|
-
"let tableRenderPending = false;",
|
|
173
|
-
"let popoverRenderPending = false;",
|
|
174
|
-
"function scheduleTableRender() {",
|
|
175
|
-
" if(!tableRenderPending) {",
|
|
176
|
-
" tableRenderPending = true;",
|
|
177
|
-
" requestAnimationFrame(() => { tableRenderPending = false; renderStreamsTable(); });",
|
|
178
|
-
" }",
|
|
179
|
-
"}",
|
|
180
|
-
"function schedulePopoverRender() {",
|
|
181
|
-
" if(!popoverRenderPending) {",
|
|
182
|
-
" popoverRenderPending = true;",
|
|
183
|
-
" requestAnimationFrame(() => { popoverRenderPending = false; updateStreamPopover(); });",
|
|
184
|
-
" }",
|
|
185
|
-
"}",
|
|
186
|
-
// Shared cell content renderers. Used by both renderStreamsTable (full rebuild) and updateStreamRow (targeted update) so that each cell's HTML is produced
|
|
187
|
-
// by a single function. This prevents the two rendering paths from diverging — if a cell format changes, the shared function is the only place to update.
|
|
188
|
-
// Health cell: client indicator dot + health badge.
|
|
189
|
-
"function renderHealthCellContent(s) {",
|
|
190
|
-
" let clientIndicator = '';",
|
|
191
|
-
" if(s.clientCount > 0) {",
|
|
192
|
-
" const title = s.clientCount + (s.clientCount !== 1 ? ' clients' : ' client');",
|
|
193
|
-
" clientIndicator = '<span class=\"client-count\" title=\"' + title + '\">◉ ' + s.clientCount + '</span> ';",
|
|
194
|
-
" }",
|
|
195
|
-
" return clientIndicator + getHealthBadge(s.health, s.escalationLevel);",
|
|
196
|
-
"}",
|
|
197
|
-
// Detail panel: codec line with mode and native quality suffix.
|
|
198
|
-
"function renderDetailCodec(s) {",
|
|
199
|
-
" const codecLabel = s.captureCodec ? (s.hardwareAccelerated ? '\\u26A1 ' + s.captureCodec : s.captureCodec) :",
|
|
200
|
-
" (s.streamingMode === 'native' ? 'Native HLS' : 'Unknown');",
|
|
201
|
-
" const modeLabel = s.streamingMode === 'native' ? 'Native HLS' : 'Capture';",
|
|
202
|
-
" let qualitySuffix = '';",
|
|
203
|
-
" if(s.streamingMode === 'native') {",
|
|
204
|
-
" const qParts = [];",
|
|
205
|
-
" if(s.nativeBandwidth > 0) { qParts.push((s.nativeBandwidth / 1000000).toFixed(1) + 'Mbps'); }",
|
|
206
|
-
" if(s.nativeResolution) {",
|
|
207
|
-
" const h = s.nativeResolution.split('x')[1];",
|
|
208
|
-
" qParts.push(nativeResolutionLabels[h] ?? s.nativeResolution);",
|
|
209
|
-
" }",
|
|
210
|
-
" if(qParts.length > 0) { qualitySuffix = ' - ' + qParts.join(' '); }",
|
|
211
|
-
" }",
|
|
212
|
-
" return '<strong>Codec:</strong> ' + codecLabel + ' (' + modeLabel + ')' + qualitySuffix;",
|
|
213
|
-
"}",
|
|
214
|
-
// Detail panel: started time with optional client count suffix.
|
|
215
|
-
"function renderDetailStarted(s) {",
|
|
216
|
-
" const clientSuffix = s.clientCount > 0 ? ' · ' + formatClients(s) : '';",
|
|
217
|
-
" return '<strong>Started:</strong> ' + formatTime(s.startTime) + clientSuffix;",
|
|
218
|
-
"}",
|
|
219
|
-
// Render the streams table.
|
|
220
|
-
"function renderStreamsTable() {",
|
|
221
|
-
" const tbody = document.getElementById('streams-tbody');",
|
|
222
|
-
" if(!tbody) return;",
|
|
223
|
-
" const entries = Object.entries(streamData);",
|
|
224
|
-
" if(entries.length === 0) {",
|
|
225
|
-
" tbody.innerHTML = '<tr class=\"empty-row\"><td colspan=\"4\">No active streams</td></tr>';",
|
|
226
|
-
" return;",
|
|
227
|
-
" }",
|
|
228
|
-
" let html = '';",
|
|
229
|
-
" for(const [ id, s ] of entries) {",
|
|
230
|
-
" const isExpanded = expandedStreams[id];",
|
|
231
|
-
" const chevron = isExpanded ? '▼' : '▶';",
|
|
232
|
-
" const rowTint = rowTints[s.health] ?? 'transparent';",
|
|
233
|
-
" const channelText = s.channel || s.serviceName || getDomain(s.url);",
|
|
234
|
-
" const channelDisplay = channelDisplayHtml(s.logoUrl, channelText, 'channel-logo', 'channel-text');",
|
|
235
|
-
" html += '<tr class=\"stream-row\" data-id=\"' + id + '\" onclick=\"toggleStreamDetails(' + id + ')\" style=\"background-color: ' + rowTint + ';\">';",
|
|
236
|
-
" html += '<td class=\"chevron\">' + chevron + '</td>';",
|
|
237
|
-
" const hwIcon = s.hardwareAccelerated ? '\\u26A1 ' : '';",
|
|
238
|
-
" let nativeBadge = '';",
|
|
239
|
-
" if(s.streamingMode === 'native') {",
|
|
240
|
-
" nativeBadge = ' <span class=\"native-badge\" title=\"Native HLS\">Native</span>';",
|
|
241
|
-
" } else if(s.hardwareAccelerated) {",
|
|
242
|
-
" nativeBadge = ' <span class=\"native-badge\" title=\"Hardware accelerated\">' + hwIcon + (s.captureCodec || '') + '</span>';",
|
|
243
|
-
" }",
|
|
244
|
-
" const durationSpan = '<span class=\"stream-duration\" id=\"duration-' + id + '\">\\u00b7 ' + formatDuration(s.duration) + '</span>';",
|
|
245
|
-
" html += '<td class=\"stream-info\">' + channelDisplay + nativeBadge + ' ' + durationSpan + '</td>';",
|
|
246
|
-
" const showDisplay = s.showName ? s.showName : '';",
|
|
247
|
-
" html += '<td class=\"stream-show\">' + showDisplay + '</td>';",
|
|
248
|
-
" html += '<td class=\"stream-health\">' + renderHealthCellContent(s) + '</td>';",
|
|
249
|
-
" html += '</tr>';",
|
|
250
|
-
" if(isExpanded) {",
|
|
251
|
-
" html += '<tr class=\"stream-details\" data-id=\"' + id + '\">';",
|
|
252
|
-
" html += '<td colspan=\"4\">';",
|
|
253
|
-
" html += '<div class=\"details-content\">';",
|
|
254
|
-
" html += '<div class=\"details-header\">';",
|
|
255
|
-
" html += '<div class=\"details-url\">' + s.url + '</div>';",
|
|
256
|
-
" html += '<div class=\"details-started\">' + renderDetailStarted(s) + '</div>';",
|
|
257
|
-
" html += '</div>';",
|
|
258
|
-
" html += '<div class=\"details-metrics\">';",
|
|
259
|
-
" html += '<div class=\"details-codec\">' + renderDetailCodec(s) + '</div>';",
|
|
260
|
-
" html += '<div class=\"details-issue\"><strong>Last issue:</strong> ' + formatLastIssue(s) + '</div>';",
|
|
261
|
-
" html += '<div class=\"details-recovery\"><strong>Recovery:</strong> ' + formatAutoRecovery(s) + '</div>';",
|
|
262
|
-
" html += '<div class=\"details-memory\"><strong>Memory:</strong> ' + formatBytes(s.memoryBytes) + '</div>';",
|
|
263
|
-
" html += '</div>';",
|
|
264
|
-
" html += '</div>';",
|
|
265
|
-
" html += '</td></tr>';",
|
|
266
|
-
" }",
|
|
267
|
-
" }",
|
|
268
|
-
" tbody.innerHTML = html;",
|
|
269
|
-
"}",
|
|
270
|
-
// Targeted update for a single stream row. Updates only the cells that change between health ticks (health badge, show name, client count, row tint, and
|
|
271
|
-
// detail panel metrics if expanded). Leaves the logo, channel name, badge, and structural elements untouched so image elements are never destroyed and
|
|
272
|
-
// recreated. Falls back to a full table render if the row doesn't exist yet (e.g., race between streamAdded and streamHealthChanged).
|
|
273
|
-
"function updateStreamRow(s) {",
|
|
274
|
-
" const row = document.querySelector('.stream-row[data-id=\"' + s.id + '\"]');",
|
|
275
|
-
" if(!row) { scheduleTableRender(); return; }",
|
|
276
|
-
// Row tint.
|
|
277
|
-
" row.style.backgroundColor = rowTints[s.health] ?? 'transparent';",
|
|
278
|
-
// Health badge cell.
|
|
279
|
-
" const healthCell = row.querySelector('.stream-health');",
|
|
280
|
-
" if(healthCell) { healthCell.innerHTML = renderHealthCellContent(s); }",
|
|
281
|
-
// Show name cell.
|
|
282
|
-
" const showCell = row.querySelector('.stream-show');",
|
|
283
|
-
" if(showCell) { showCell.textContent = s.showName || ''; }",
|
|
284
|
-
// Detail panel (if expanded). The detail row is a sibling <tr> with the same data-id.
|
|
285
|
-
" if(expandedStreams[s.id]) {",
|
|
286
|
-
" const detailRow = document.querySelector('.stream-details[data-id=\"' + s.id + '\"]');",
|
|
287
|
-
" if(detailRow) {",
|
|
288
|
-
" const issueEl = detailRow.querySelector('.details-issue');",
|
|
289
|
-
" if(issueEl) { issueEl.innerHTML = '<strong>Last issue:</strong> ' + formatLastIssue(s); }",
|
|
290
|
-
" const recoveryEl = detailRow.querySelector('.details-recovery');",
|
|
291
|
-
" if(recoveryEl) { recoveryEl.innerHTML = '<strong>Recovery:</strong> ' + formatAutoRecovery(s); }",
|
|
292
|
-
" const memoryEl = detailRow.querySelector('.details-memory');",
|
|
293
|
-
" if(memoryEl) { memoryEl.innerHTML = '<strong>Memory:</strong> ' + formatBytes(s.memoryBytes); }",
|
|
294
|
-
" const codecEl = detailRow.querySelector('.details-codec');",
|
|
295
|
-
" if(codecEl) { codecEl.innerHTML = renderDetailCodec(s); }",
|
|
296
|
-
" const startedEl = detailRow.querySelector('.details-started');",
|
|
297
|
-
" if(startedEl) { startedEl.innerHTML = renderDetailStarted(s); }",
|
|
298
|
-
" }",
|
|
299
|
-
" }",
|
|
300
|
-
"}",
|
|
301
|
-
// Toggle stream details.
|
|
302
|
-
"function toggleStreamDetails(id) {",
|
|
303
|
-
" expandedStreams[id] = !expandedStreams[id];",
|
|
304
|
-
" renderStreamsTable();",
|
|
305
|
-
"}",
|
|
306
|
-
// Update stream durations every second. We calculate duration from the immutable startTime rather than incrementing a counter, ensuring the displayed duration is
|
|
307
|
-
// always accurate regardless of any staleness in server-sent updates.
|
|
308
|
-
"function updateDurations() {",
|
|
309
|
-
" const now = Date.now();",
|
|
310
|
-
" for(const [ id, s ] of Object.entries(streamData)) {",
|
|
311
|
-
" const durationSec = Math.floor((now - new Date(s.startTime).getTime()) / 1000);",
|
|
312
|
-
" const el = document.getElementById('duration-' + id);",
|
|
313
|
-
" if(el) el.textContent = '\\u00b7 ' + formatDuration(durationSec);",
|
|
314
|
-
" }",
|
|
315
|
-
" schedulePopoverRender();",
|
|
316
|
-
"}",
|
|
317
|
-
// Track the last time any SSE event was received from the status stream. Used by the staleness checker to detect silently dead connections.
|
|
318
|
-
"let lastStatusEventTime = Date.now();",
|
|
319
|
-
"let hiddenSince = 0;",
|
|
320
|
-
// Real-time health indicator updates. These functions update channel health icons and provider login icons in the Channels tab without a page refresh.
|
|
321
|
-
"function formatTimeAgo(ts) {",
|
|
322
|
-
" const seconds = Math.floor((Date.now() - ts) / 1000);",
|
|
323
|
-
" if(seconds < 60) { return 'just now'; }",
|
|
324
|
-
" const minutes = Math.floor(seconds / 60);",
|
|
325
|
-
" if(minutes < 60) { return minutes + (minutes === 1 ? ' minute ago' : ' minutes ago'); }",
|
|
326
|
-
" const hours = Math.floor(minutes / 60);",
|
|
327
|
-
" if(hours < 24) { return hours + (hours === 1 ? ' hour ago' : ' hours ago'); }",
|
|
328
|
-
" const days = Math.floor(hours / 24);",
|
|
329
|
-
" return days + (days === 1 ? ' day ago' : ' days ago');",
|
|
330
|
-
"}",
|
|
331
|
-
"function updateChannelHealth(channelKey, status, timestamp, domain) {",
|
|
332
|
-
" const row = document.getElementById('display-row-' + channelKey);",
|
|
333
|
-
" if(!row) { return; }",
|
|
334
|
-
// If a domain was supplied (snapshot and real-time events both include it), verify it matches the currently selected domain for this channel. The login button
|
|
335
|
-
// carries a data-auth-domain attribute with the selected domain. If they differ the health entry is from a previous service and should be ignored.
|
|
336
|
-
" if(domain) {",
|
|
337
|
-
" const loginBtn = row.querySelector('.btn-icon-login');",
|
|
338
|
-
" if(loginBtn && loginBtn.getAttribute('data-auth-domain') !== domain) { return; }",
|
|
339
|
-
" }",
|
|
340
|
-
" const icon = row.querySelector('.btn-icon-health');",
|
|
341
|
-
" if(!icon) { return; }",
|
|
342
|
-
" icon.classList.remove('health-success', 'health-failed');",
|
|
343
|
-
" if(status === 'success') { icon.classList.add('health-success'); }",
|
|
344
|
-
" else if(status === 'failed') { icon.classList.add('health-failed'); }",
|
|
345
|
-
" icon.title = (status === 'success' ? 'Succeeded ' : 'Failed ') + formatTimeAgo(timestamp);",
|
|
346
|
-
"}",
|
|
347
|
-
"function updateDomainAuth(domain, timestamp) {",
|
|
348
|
-
" const buttons = document.querySelectorAll('.btn-icon-login[data-auth-domain=\"' + domain + '\"]');",
|
|
349
|
-
" for(const button of buttons) {",
|
|
350
|
-
" button.classList.add('health-success');",
|
|
351
|
-
" button.title = 'Verified ' + formatTimeAgo(timestamp);",
|
|
352
|
-
" }",
|
|
353
|
-
"}",
|
|
354
|
-
"function applyHealthSnapshot(data) {",
|
|
355
|
-
" for(const [ channelKey, entry ] of Object.entries(data.channels)) {",
|
|
356
|
-
" updateChannelHealth(channelKey, entry.status, entry.timestamp, entry.domain);",
|
|
357
|
-
" }",
|
|
358
|
-
" for(const [ domain, timestamp ] of Object.entries(data.domains)) {",
|
|
359
|
-
" updateDomainAuth(domain, timestamp);",
|
|
360
|
-
" }",
|
|
361
|
-
"}",
|
|
362
|
-
// Connect (or reconnect) to the status SSE stream. Closes any existing connection first so this is safe to call repeatedly.
|
|
363
|
-
"function connectStatusSSE() {",
|
|
364
|
-
" if(statusEventSource) { statusEventSource.close(); }",
|
|
365
|
-
" statusEventSource = new EventSource('/streams/status');",
|
|
366
|
-
" lastStatusEventTime = Date.now();",
|
|
367
|
-
// Local helper that registers an event listener and updates the staleness timestamp on every event. Handlers are optional so heartbeat can
|
|
368
|
-
// be registered with just on('heartbeat') for pure keepalive tracking. The onerror handler stays outside this wrapper because errors must
|
|
369
|
-
// not reset the staleness timer — a connection that only fires errors is still dead.
|
|
370
|
-
" function on(event, handler) {",
|
|
371
|
-
" statusEventSource.addEventListener(event, (e) => {",
|
|
372
|
-
" lastStatusEventTime = Date.now();",
|
|
373
|
-
" if(handler) { handler(e); }",
|
|
374
|
-
" });",
|
|
375
|
-
" }",
|
|
376
|
-
" on('heartbeat');",
|
|
377
|
-
" on('snapshot', (e) => {",
|
|
378
|
-
" const data = JSON.parse(e.data);",
|
|
379
|
-
" systemData = data.system;",
|
|
380
|
-
" streamData = {};",
|
|
381
|
-
" for(const stream of data.streams) {",
|
|
382
|
-
" streamData[stream.id] = stream;",
|
|
383
|
-
" }",
|
|
384
|
-
" updateSystemStatus();",
|
|
385
|
-
" renderStreamsTable();",
|
|
386
|
-
" updateStreamPopover();",
|
|
387
|
-
" if(data.health) { applyHealthSnapshot(data.health); }",
|
|
388
|
-
" });",
|
|
389
|
-
" on('streamAdded', (e) => {",
|
|
390
|
-
" const s = JSON.parse(e.data);",
|
|
391
|
-
" streamData[s.id] = s;",
|
|
392
|
-
" renderStreamsTable();",
|
|
393
|
-
" updateStreamPopover();",
|
|
394
|
-
" });",
|
|
395
|
-
" on('streamRemoved', (e) => {",
|
|
396
|
-
" const data = JSON.parse(e.data);",
|
|
397
|
-
" delete streamData[data.id];",
|
|
398
|
-
" delete expandedStreams[data.id];",
|
|
399
|
-
" renderStreamsTable();",
|
|
400
|
-
" updateStreamPopover();",
|
|
401
|
-
" if(typeof updateRestartDialogStatus === 'function') {",
|
|
402
|
-
" updateRestartDialogStatus();",
|
|
403
|
-
" }",
|
|
404
|
-
" });",
|
|
405
|
-
" on('streamHealthChanged', (e) => {",
|
|
406
|
-
" const s = JSON.parse(e.data);",
|
|
407
|
-
" const prev = streamData[s.id];",
|
|
408
|
-
" if(prev) {",
|
|
409
|
-
" const structuralChange = (prev.logoUrl !== s.logoUrl) || (prev.streamingMode !== s.streamingMode) ||",
|
|
410
|
-
" (prev.hardwareAccelerated !== s.hardwareAccelerated) || (prev.captureCodec !== s.captureCodec);",
|
|
411
|
-
" streamData[s.id] = s;",
|
|
412
|
-
" if(structuralChange) {",
|
|
413
|
-
" scheduleTableRender();",
|
|
414
|
-
" } else {",
|
|
415
|
-
" updateStreamRow(s);",
|
|
416
|
-
" }",
|
|
417
|
-
" schedulePopoverRender();",
|
|
418
|
-
" }",
|
|
419
|
-
" });",
|
|
420
|
-
" on('systemStatusChanged', (e) => {",
|
|
421
|
-
" systemData = JSON.parse(e.data);",
|
|
422
|
-
" updateSystemStatus();",
|
|
423
|
-
" });",
|
|
424
|
-
" on('healthChanged', (e) => {",
|
|
425
|
-
" const event = JSON.parse(e.data);",
|
|
426
|
-
" updateChannelHealth(event.channelKey, event.status, event.timestamp, event.domain);",
|
|
427
|
-
" if(event.status === 'success') { updateDomainAuth(event.domain, event.timestamp); }",
|
|
428
|
-
" });",
|
|
429
|
-
// Channel table updates from the server (e.g., logo population after startup). Applies the patch via the shared channelTable namespace.
|
|
430
|
-
" on('channelUpdate', (e) => {",
|
|
431
|
-
" channelTable.applyPatch(JSON.parse(e.data));",
|
|
432
|
-
" });",
|
|
433
|
-
" statusEventSource.onerror = () => {",
|
|
434
|
-
" document.getElementById('system-health').innerHTML = '<span class=\"status-dot\" style=\"color: var(--stream-stalled);\">●</span> Updates paused';",
|
|
28
|
+
// Section 1: emit each constant as a `const name = <json>;` declaration. Stable serialization (Object.entries are emitted in declaration order from
|
|
29
|
+
// status.handlers.ts) keeps generation deterministic across calls.
|
|
30
|
+
const constantDecls = HANDLER_CONSTANTS.map((c) => "const " + c.name + " = " + JSON.stringify(c.value) + ";").join("\n");
|
|
31
|
+
// Section 2: emit each handler/helper as its source via Function.prototype.toString(). Function declarations are hoisted in script-tag scope, so cross-handler
|
|
32
|
+
// references resolve regardless of declaration order; the order chosen in HANDLER_FUNCTIONS reflects the logical groups (formatters, renderers, mutators,
|
|
33
|
+
// schedulers, handlers, trampolines).
|
|
34
|
+
const handlerSources = HANDLER_FUNCTIONS.map((fn) => fn.toString()).join("\n\n");
|
|
35
|
+
// Section 3: the IIFE. Wires the runtime side - constructs the state and externals, opens the EventSource, dispatches events, attaches window.* trampolines,
|
|
36
|
+
// starts the timers. The handler-side state mutations all go through the ctx that is built here. The shared sibling-script values (channelDisplayHtml,
|
|
37
|
+
// channelTable, copyToClipboard, dropdowns) are referenced as bare identifiers, matching the existing client-side idiom - shared.ts assigns them via
|
|
38
|
+
// window.X = ... before this script loads, and bare identifiers in non-strict script-tag scope resolve through the global object. updateRestartDialogStatus
|
|
39
|
+
// is wrapped in a typeof guard inside a getter because config.ts loads after status.ts; a streamRemoved event arriving before config.ts has registered the
|
|
40
|
+
// callback must not throw.
|
|
41
|
+
const iife = [
|
|
42
|
+
"(function() {",
|
|
43
|
+
" const state = {",
|
|
44
|
+
" expandedStreams: {},",
|
|
45
|
+
" hiddenSince: 0,",
|
|
46
|
+
" lastStatusEventTime: Date.now(),",
|
|
47
|
+
" popoverRenderPending: false,",
|
|
48
|
+
" streamData: {},",
|
|
49
|
+
" systemData: null,",
|
|
50
|
+
" tableRenderPending: false",
|
|
51
|
+
" };",
|
|
52
|
+
" const externals = {",
|
|
53
|
+
" channelDisplayHtml: channelDisplayHtml,",
|
|
54
|
+
" channelTable: channelTable,",
|
|
55
|
+
" copyToClipboard: copyToClipboard,",
|
|
56
|
+
" dropdowns: dropdowns,",
|
|
57
|
+
" get updateRestartDialogStatus() { return (typeof updateRestartDialogStatus !== 'undefined') ? updateRestartDialogStatus : undefined; }",
|
|
435
58
|
" };",
|
|
436
|
-
"}",
|
|
437
|
-
|
|
438
|
-
"connectStatusSSE()
|
|
439
|
-
"
|
|
440
|
-
|
|
441
|
-
"
|
|
442
|
-
"
|
|
443
|
-
"
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
"
|
|
447
|
-
" if(document.hidden) {",
|
|
448
|
-
" hiddenSince = Date.now();",
|
|
449
|
-
" } else if((hiddenSince > 0) && ((Date.now() - hiddenSince) > 30000)) {",
|
|
450
|
-
" hiddenSince = 0;",
|
|
451
|
-
" connectStatusSSE();",
|
|
452
|
-
" const activeTab = document.querySelector('.tab-btn.active');",
|
|
453
|
-
" if(activeTab) {",
|
|
454
|
-
" document.dispatchEvent(new CustomEvent('tabactivated', { detail: { category: activeTab.getAttribute('data-category') } }));",
|
|
59
|
+
" const ctx = { document: document, externals: externals, state: state };",
|
|
60
|
+
" let statusEventSource = null;",
|
|
61
|
+
" function connectStatusSSE() {",
|
|
62
|
+
" if(statusEventSource) { statusEventSource.close(); }",
|
|
63
|
+
" statusEventSource = new EventSource('/streams/status');",
|
|
64
|
+
" state.lastStatusEventTime = Date.now();",
|
|
65
|
+
" function on(event, handler) {",
|
|
66
|
+
" statusEventSource.addEventListener(event, (e) => {",
|
|
67
|
+
" state.lastStatusEventTime = Date.now();",
|
|
68
|
+
" if(handler) { handler(e); }",
|
|
69
|
+
" });",
|
|
455
70
|
" }",
|
|
456
|
-
"
|
|
457
|
-
"
|
|
458
|
-
"
|
|
459
|
-
"
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
"
|
|
463
|
-
"
|
|
464
|
-
"
|
|
465
|
-
"}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
"(
|
|
470
|
-
"
|
|
471
|
-
"
|
|
472
|
-
"
|
|
473
|
-
"
|
|
474
|
-
" document.addEventListener('
|
|
475
|
-
"
|
|
476
|
-
"
|
|
477
|
-
" const label = btn.getAttribute('title') || btn.getAttribute('aria-label');",
|
|
478
|
-
" if(!label) return;",
|
|
479
|
-
" const rect = btn.getBoundingClientRect();",
|
|
480
|
-
" tip.textContent = label;",
|
|
481
|
-
" tip.classList.add('visible');",
|
|
482
|
-
" tip.style.top = (rect.bottom + 6 + (window.scrollY || 0)) + 'px';",
|
|
483
|
-
" tip.style.left = (rect.left + rect.width / 2 + (window.scrollX || 0)) + 'px';",
|
|
484
|
-
" tip.style.transform = 'translateX(-50%)';",
|
|
485
|
-
" }, true);",
|
|
486
|
-
" document.addEventListener('mouseleave', (e) => {",
|
|
487
|
-
" const src = e.target.closest('.btn-icon[aria-label]');",
|
|
488
|
-
" if(!src) return;",
|
|
489
|
-
" const dest = e.relatedTarget?.closest?.('.btn-icon[aria-label]');",
|
|
490
|
-
" if(dest === src) return;",
|
|
491
|
-
" tip.classList.remove('visible');",
|
|
492
|
-
" }, true);",
|
|
493
|
-
"})();",
|
|
494
|
-
"</script>"
|
|
71
|
+
" on('heartbeat');",
|
|
72
|
+
" on('snapshot', (e) => handleSnapshot(JSON.parse(e.data), ctx));",
|
|
73
|
+
" on('streamAdded', (e) => handleStreamAdded(JSON.parse(e.data), ctx));",
|
|
74
|
+
" on('streamRemoved', (e) => handleStreamRemoved(JSON.parse(e.data), ctx));",
|
|
75
|
+
" on('streamHealthChanged', (e) => handleStreamHealthChanged(JSON.parse(e.data), ctx));",
|
|
76
|
+
" on('systemStatusChanged', (e) => handleSystemStatusChanged(JSON.parse(e.data), ctx));",
|
|
77
|
+
" on('healthChanged', (e) => handleHealthChanged(JSON.parse(e.data), ctx));",
|
|
78
|
+
" on('channelUpdate', (e) => handleChannelUpdate(JSON.parse(e.data), ctx));",
|
|
79
|
+
" statusEventSource.onerror = () => handleSseError(ctx);",
|
|
80
|
+
" }",
|
|
81
|
+
" window.toggleStreamPopover = () => toggleStreamPopover(ctx);",
|
|
82
|
+
" window.toggleStreamDetails = (id) => toggleStreamDetails(id, ctx);",
|
|
83
|
+
" window.copyOverviewPlaylistUrl = () => copyOverviewPlaylistUrl(ctx);",
|
|
84
|
+
" connectStatusSSE();",
|
|
85
|
+
" setInterval(() => updateDurations(ctx), 1000);",
|
|
86
|
+
" setInterval(() => {",
|
|
87
|
+
" if((Date.now() - state.lastStatusEventTime) > 45000) { connectStatusSSE(); }",
|
|
88
|
+
" }, 45000);",
|
|
89
|
+
" document.addEventListener('visibilitychange', () => handleVisibilityChange(ctx, connectStatusSSE));",
|
|
90
|
+
" initIPadTooltips(ctx);",
|
|
91
|
+
"})();"
|
|
495
92
|
].join("\n");
|
|
93
|
+
return ["<script>", constantDecls, handlerSources, iife, "</script>"].join("\n");
|
|
496
94
|
}
|
|
497
95
|
//# sourceMappingURL=status.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../../src/routes/root/scripts/status.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../../src/routes/root/scripts/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE5E;;;GAGG;AACH,MAAM,UAAU,oBAAoB;IAElC,oJAAoJ;IACpJ,mEAAmE;IACnE,MAAM,aAAa,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,GAAG,CAAC,CAAC,IAAI,GAAG,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEzH,+JAA+J;IAC/J,0JAA0J;IAC1J,sCAAsC;IACtC,MAAM,cAAc,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEjF,6JAA6J;IAC7J,uJAAuJ;IACvJ,qJAAqJ;IACrJ,4JAA4J;IAC5J,2JAA2J;IAC3J,2BAA2B;IAC3B,MAAM,IAAI,GAAG;QAEX,eAAe;QACf,mBAAmB;QACnB,0BAA0B;QAC1B,qBAAqB;QACrB,sCAAsC;QACtC,kCAAkC;QAClC,qBAAqB;QACrB,uBAAuB;QACvB,+BAA+B;QAC/B,MAAM;QACN,uBAAuB;QACvB,6CAA6C;QAC7C,iCAAiC;QACjC,uCAAuC;QACvC,2BAA2B;QAC3B,4IAA4I;QAC5I,MAAM;QACN,2EAA2E;QAC3E,iCAAiC;QACjC,iCAAiC;QACjC,0DAA0D;QAC1D,6DAA6D;QAC7D,6CAA6C;QAC7C,mCAAmC;QACnC,0DAA0D;QAC1D,iDAAiD;QACjD,qCAAqC;QACrC,WAAW;QACX,OAAO;QACP,sBAAsB;QACtB,qEAAqE;QACrE,2EAA2E;QAC3E,+EAA+E;QAC/E,2FAA2F;QAC3F,2FAA2F;QAC3F,+EAA+E;QAC/E,+EAA+E;QAC/E,4DAA4D;QAC5D,KAAK;QACL,gEAAgE;QAChE,sEAAsE;QACtE,wEAAwE;QACxE,uBAAuB;QACvB,kDAAkD;QAClD,uBAAuB;QACvB,kFAAkF;QAClF,cAAc;QACd,uGAAuG;QACvG,0BAA0B;QAC1B,OAAO;KACR,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,OAAO,CAAE,UAAU,EAAE,aAAa,EAAE,cAAc,EAAE,IAAI,EAAE,WAAW,CAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACrF,CAAC"}
|