prismcast 1.12.1 → 1.14.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.
Files changed (655) hide show
  1. package/dist/app.d.ts +88 -7
  2. package/dist/app.js +295 -161
  3. package/dist/app.js.map +1 -1
  4. package/dist/browser/blockedPage.d.ts +0 -1
  5. package/dist/browser/browserSupervisor.d.ts +4 -4
  6. package/dist/browser/browserSupervisor.js.map +1 -1
  7. package/dist/browser/cdp.d.ts +8 -20
  8. package/dist/browser/cdp.js +11 -61
  9. package/dist/browser/cdp.js.map +1 -1
  10. package/dist/browser/channelSelection.d.ts +16 -4
  11. package/dist/browser/channelSelection.js +42 -31
  12. package/dist/browser/channelSelection.js.map +1 -1
  13. package/dist/browser/consent.d.ts +17 -4
  14. package/dist/browser/consent.js +25 -40
  15. package/dist/browser/consent.js.map +1 -1
  16. package/dist/browser/display.d.ts +0 -1
  17. package/dist/browser/hlsPlaylistObserver.d.ts +3 -2
  18. package/dist/browser/hlsPlaylistObserver.js +9 -4
  19. package/dist/browser/hlsPlaylistObserver.js.map +1 -1
  20. package/dist/browser/index.d.ts +179 -46
  21. package/dist/browser/index.js +473 -306
  22. package/dist/browser/index.js.map +1 -1
  23. package/dist/browser/launchGovernor.d.ts +0 -1
  24. package/dist/browser/login.d.ts +8 -7
  25. package/dist/browser/login.js +25 -16
  26. package/dist/browser/login.js.map +1 -1
  27. package/dist/browser/manifestInterceptor.d.ts +14 -5
  28. package/dist/browser/manifestInterceptor.js +25 -12
  29. package/dist/browser/manifestInterceptor.js.map +1 -1
  30. package/dist/browser/navigation.d.ts +28 -0
  31. package/dist/browser/navigation.js +57 -0
  32. package/dist/browser/navigation.js.map +1 -0
  33. package/dist/browser/pageStaleness.d.ts +0 -1
  34. package/dist/browser/precaching.d.ts +27 -11
  35. package/dist/browser/precaching.js +262 -107
  36. package/dist/browser/precaching.js.map +1 -1
  37. package/dist/browser/tabCapture.d.ts +7 -10
  38. package/dist/browser/tabCapture.js +37 -50
  39. package/dist/browser/tabCapture.js.map +1 -1
  40. package/dist/browser/tabNetworkObserver.d.ts +0 -1
  41. package/dist/browser/tabSelection.d.ts +5 -5
  42. package/dist/browser/tabSelection.js +11 -10
  43. package/dist/browser/tabSelection.js.map +1 -1
  44. package/dist/browser/tuning/cache.d.ts +0 -1
  45. package/dist/browser/tuning/comcastPolymer.d.ts +0 -1
  46. package/dist/browser/tuning/comcastPolymer.js +29 -31
  47. package/dist/browser/tuning/comcastPolymer.js.map +1 -1
  48. package/dist/browser/tuning/cox.d.ts +0 -1
  49. package/dist/browser/tuning/directv.d.ts +0 -1
  50. package/dist/browser/tuning/directv.js +11 -12
  51. package/dist/browser/tuning/directv.js.map +1 -1
  52. package/dist/browser/tuning/fox.d.ts +0 -1
  53. package/dist/browser/tuning/fox.js +2 -2
  54. package/dist/browser/tuning/fox.js.map +1 -1
  55. package/dist/browser/tuning/gridSearch.d.ts +0 -1
  56. package/dist/browser/tuning/hbo.d.ts +0 -1
  57. package/dist/browser/tuning/hbo.js +34 -17
  58. package/dist/browser/tuning/hbo.js.map +1 -1
  59. package/dist/browser/tuning/hulu.d.ts +14 -2
  60. package/dist/browser/tuning/hulu.js +144 -21
  61. package/dist/browser/tuning/hulu.js.map +1 -1
  62. package/dist/browser/tuning/shared.d.ts +0 -1
  63. package/dist/browser/tuning/sling.d.ts +0 -1
  64. package/dist/browser/tuning/sling.js +17 -22
  65. package/dist/browser/tuning/sling.js.map +1 -1
  66. package/dist/browser/tuning/spectrum.d.ts +0 -1
  67. package/dist/browser/tuning/thumbnailRow.d.ts +0 -1
  68. package/dist/browser/tuning/tileClick.d.ts +0 -1
  69. package/dist/browser/tuning/xfinity.d.ts +0 -1
  70. package/dist/browser/tuning/youtubeTv.d.ts +0 -1
  71. package/dist/browser/video.d.ts +22 -20
  72. package/dist/browser/video.js +102 -119
  73. package/dist/browser/video.js.map +1 -1
  74. package/dist/browser/windowSync.d.ts +0 -1
  75. package/dist/channels/index.d.ts +0 -1
  76. package/dist/channels/index.js +5 -5
  77. package/dist/channels/index.js.map +1 -1
  78. package/dist/cliHelp.d.ts +11 -0
  79. package/dist/cliHelp.js +148 -0
  80. package/dist/cliHelp.js.map +1 -0
  81. package/dist/config/channelForm.d.ts +0 -1
  82. package/dist/config/channelForm.js +5 -4
  83. package/dist/config/channelForm.js.map +1 -1
  84. package/dist/config/channelIdentity.d.ts +27 -0
  85. package/dist/config/channelIdentity.js +72 -0
  86. package/dist/config/channelIdentity.js.map +1 -0
  87. package/dist/config/channelSort.d.ts +25 -0
  88. package/dist/config/channelSort.js +101 -0
  89. package/dist/config/channelSort.js.map +1 -0
  90. package/dist/config/consistencyProbe.d.ts +2 -5
  91. package/dist/config/consistencyProbe.js +8 -51
  92. package/dist/config/consistencyProbe.js.map +1 -1
  93. package/dist/config/health.d.ts +3 -2
  94. package/dist/config/health.js +41 -28
  95. package/dist/config/health.js.map +1 -1
  96. package/dist/config/index.d.ts +107 -56
  97. package/dist/config/index.js +499 -267
  98. package/dist/config/index.js.map +1 -1
  99. package/dist/config/paths.d.ts +13 -9
  100. package/dist/config/paths.js +21 -25
  101. package/dist/config/paths.js.map +1 -1
  102. package/dist/config/persistence.context.d.ts +0 -1
  103. package/dist/config/persistence.d.ts +57 -12
  104. package/dist/config/persistence.js +127 -44
  105. package/dist/config/persistence.js.map +1 -1
  106. package/dist/config/presets.d.ts +0 -1
  107. package/dist/config/profiles.d.ts +9 -4
  108. package/dist/config/profiles.js +37 -3
  109. package/dist/config/profiles.js.map +1 -1
  110. package/dist/config/providerLineups.d.ts +0 -1
  111. package/dist/config/reactivity.d.ts +60 -37
  112. package/dist/config/reactivity.js +89 -116
  113. package/dist/config/reactivity.js.map +1 -1
  114. package/dist/config/servicePacks.d.ts +0 -1
  115. package/dist/config/services.d.ts +62 -73
  116. package/dist/config/services.js +140 -202
  117. package/dist/config/services.js.map +1 -1
  118. package/dist/config/sites.d.ts +0 -9
  119. package/dist/config/sites.js +5 -40
  120. package/dist/config/sites.js.map +1 -1
  121. package/dist/config/streamSettings.d.ts +24 -0
  122. package/dist/config/streamSettings.js +17 -0
  123. package/dist/config/streamSettings.js.map +1 -0
  124. package/dist/config/userChannels.d.ts +82 -80
  125. package/dist/config/userChannels.js +184 -248
  126. package/dist/config/userChannels.js.map +1 -1
  127. package/dist/config/userConfig.d.ts +225 -59
  128. package/dist/config/userConfig.js +352 -131
  129. package/dist/config/userConfig.js.map +1 -1
  130. package/dist/config/userProfiles.d.ts +2 -3
  131. package/dist/config/userProfiles.js +6 -2
  132. package/dist/config/userProfiles.js.map +1 -1
  133. package/dist/hdhr/channelMap.d.ts +0 -1
  134. package/dist/hdhr/discover.d.ts +7 -2
  135. package/dist/hdhr/discover.js +12 -6
  136. package/dist/hdhr/discover.js.map +1 -1
  137. package/dist/hdhr/getHandlers.d.ts +0 -1
  138. package/dist/hdhr/identity.d.ts +0 -1
  139. package/dist/hdhr/index.d.ts +51 -11
  140. package/dist/hdhr/index.js +108 -112
  141. package/dist/hdhr/index.js.map +1 -1
  142. package/dist/hdhr/protocol.d.ts +0 -1
  143. package/dist/hdhr/tunerState.d.ts +0 -1
  144. package/dist/hdhr/udp.d.ts +11 -5
  145. package/dist/hdhr/udp.js +25 -20
  146. package/dist/hdhr/udp.js.map +1 -1
  147. package/dist/identity.d.ts +0 -1
  148. package/dist/index.d.ts +0 -1
  149. package/dist/index.js +16 -143
  150. package/dist/index.js.map +1 -1
  151. package/dist/native/codecInference.d.ts +5 -4
  152. package/dist/native/codecInference.js +12 -7
  153. package/dist/native/codecInference.js.map +1 -1
  154. package/dist/native/decrypt.d.ts +10 -3
  155. package/dist/native/decrypt.js +9 -5
  156. package/dist/native/decrypt.js.map +1 -1
  157. package/dist/native/index.d.ts +25 -23
  158. package/dist/native/index.js +27 -20
  159. package/dist/native/index.js.map +1 -1
  160. package/dist/native/probe.d.ts +7 -10
  161. package/dist/native/probe.js +40 -36
  162. package/dist/native/probe.js.map +1 -1
  163. package/dist/native/proxy.d.ts +3 -4
  164. package/dist/native/proxy.js +122 -92
  165. package/dist/native/proxy.js.map +1 -1
  166. package/dist/native/tokenExpiry.d.ts +0 -1
  167. package/dist/routes/assets.d.ts +0 -1
  168. package/dist/routes/auth.d.ts +0 -1
  169. package/dist/routes/auth.js +1 -1
  170. package/dist/routes/auth.js.map +1 -1
  171. package/dist/routes/cdp.d.ts +18 -11
  172. package/dist/routes/cdp.js +99 -70
  173. package/dist/routes/cdp.js.map +1 -1
  174. package/dist/routes/channels.d.ts +0 -1
  175. package/dist/routes/clientActions.d.ts +0 -1
  176. package/dist/routes/components.d.ts +23 -6
  177. package/dist/routes/components.js +21 -4
  178. package/dist/routes/components.js.map +1 -1
  179. package/dist/routes/config/channels/endpoints/browse.d.ts +0 -1
  180. package/dist/routes/config/channels/endpoints/browse.js +39 -29
  181. package/dist/routes/config/channels/endpoints/browse.js.map +1 -1
  182. package/dist/routes/config/channels/endpoints/bulk.d.ts +0 -1
  183. package/dist/routes/config/channels/endpoints/bulk.js +3 -7
  184. package/dist/routes/config/channels/endpoints/bulk.js.map +1 -1
  185. package/dist/routes/config/channels/endpoints/crud.d.ts +0 -1
  186. package/dist/routes/config/channels/endpoints/crud.js +3 -2
  187. package/dist/routes/config/channels/endpoints/crud.js.map +1 -1
  188. package/dist/routes/config/channels/endpoints/importExport.d.ts +0 -1
  189. package/dist/routes/config/channels/endpoints/importExport.js +7 -3
  190. package/dist/routes/config/channels/endpoints/importExport.js.map +1 -1
  191. package/dist/routes/config/channels/endpoints/predefined.d.ts +0 -1
  192. package/dist/routes/config/channels/endpoints/predefined.js +5 -15
  193. package/dist/routes/config/channels/endpoints/predefined.js.map +1 -1
  194. package/dist/routes/config/channels/endpoints/prefs.d.ts +0 -1
  195. package/dist/routes/config/channels/endpoints/prefs.js +8 -7
  196. package/dist/routes/config/channels/endpoints/prefs.js.map +1 -1
  197. package/dist/routes/config/channels/endpoints/service.d.ts +0 -1
  198. package/dist/routes/config/channels/endpoints/service.js +13 -9
  199. package/dist/routes/config/channels/endpoints/service.js.map +1 -1
  200. package/dist/routes/config/channels/endpoints/tags.d.ts +0 -1
  201. package/dist/routes/config/channels/endpoints/tags.js +4 -16
  202. package/dist/routes/config/channels/endpoints/tags.js.map +1 -1
  203. package/dist/routes/config/channels/healthBridge.d.ts +0 -1
  204. package/dist/routes/config/channels/http/handler.d.ts +0 -1
  205. package/dist/routes/config/channels/http/playlistHint.d.ts +0 -1
  206. package/dist/routes/config/channels/http/serviceWarning.d.ts +0 -1
  207. package/dist/routes/config/channels/http/serviceWarning.js +1 -1
  208. package/dist/routes/config/channels/http/serviceWarning.js.map +1 -1
  209. package/dist/routes/config/channels/index.d.ts +0 -1
  210. package/dist/routes/config/channels/setup.d.ts +0 -1
  211. package/dist/routes/config/channels/table.d.ts +2 -2
  212. package/dist/routes/config/channels/table.js +13 -14
  213. package/dist/routes/config/channels/table.js.map +1 -1
  214. package/dist/routes/config/http/envelope.d.ts +6 -4
  215. package/dist/routes/config/http/envelope.js +18 -5
  216. package/dist/routes/config/http/envelope.js.map +1 -1
  217. package/dist/routes/config/index.d.ts +21 -14
  218. package/dist/routes/config/index.js +49 -35
  219. package/dist/routes/config/index.js.map +1 -1
  220. package/dist/routes/config/services.d.ts +0 -1
  221. package/dist/routes/config/services.js +4 -3
  222. package/dist/routes/config/services.js.map +1 -1
  223. package/dist/routes/config/settings.d.ts +43 -7
  224. package/dist/routes/config/settings.js +227 -126
  225. package/dist/routes/config/settings.js.map +1 -1
  226. package/dist/routes/config/vocabulary.d.ts +24 -0
  227. package/dist/routes/config/vocabulary.js +33 -0
  228. package/dist/routes/config/vocabulary.js.map +1 -0
  229. package/dist/routes/debug.d.ts +0 -1
  230. package/dist/routes/debug.js +28 -13
  231. package/dist/routes/debug.js.map +1 -1
  232. package/dist/routes/health.d.ts +1 -2
  233. package/dist/routes/health.js +5 -2
  234. package/dist/routes/health.js.map +1 -1
  235. package/dist/routes/hls.d.ts +0 -1
  236. package/dist/routes/icons.d.ts +0 -1
  237. package/dist/routes/index.d.ts +0 -1
  238. package/dist/routes/logs.d.ts +3 -2
  239. package/dist/routes/logs.js +4 -2
  240. package/dist/routes/logs.js.map +1 -1
  241. package/dist/routes/mpegts.d.ts +0 -1
  242. package/dist/routes/play.d.ts +0 -1
  243. package/dist/routes/playlist.d.ts +0 -1
  244. package/dist/routes/playlist.js +2 -1
  245. package/dist/routes/playlist.js.map +1 -1
  246. package/dist/routes/root/content.d.ts +0 -1
  247. package/dist/routes/root/content.js +33 -26
  248. package/dist/routes/root/content.js.map +1 -1
  249. package/dist/routes/root/index.d.ts +0 -1
  250. package/dist/routes/root/scripts/channels.d.ts +0 -1
  251. package/dist/routes/root/scripts/channels.js +25 -13
  252. package/dist/routes/root/scripts/channels.js.map +1 -1
  253. package/dist/routes/root/scripts/clientEscape.d.ts +0 -1
  254. package/dist/routes/root/scripts/clientUrl.d.ts +0 -1
  255. package/dist/routes/root/scripts/config.d.ts +1 -2
  256. package/dist/routes/root/scripts/config.js +132 -105
  257. package/dist/routes/root/scripts/config.js.map +1 -1
  258. package/dist/routes/root/scripts/index.d.ts +0 -1
  259. package/dist/routes/root/scripts/shared.d.ts +0 -1
  260. package/dist/routes/root/scripts/shared.js +34 -23
  261. package/dist/routes/root/scripts/shared.js.map +1 -1
  262. package/dist/routes/root/scripts/status.d.ts +0 -1
  263. package/dist/routes/root/scripts/status.handlers.d.ts +8 -14
  264. package/dist/routes/root/scripts/status.handlers.js +38 -23
  265. package/dist/routes/root/scripts/status.handlers.js.map +1 -1
  266. package/dist/routes/root/styles.d.ts +0 -1
  267. package/dist/routes/root/styles.js +13 -16
  268. package/dist/routes/root/styles.js.map +1 -1
  269. package/dist/routes/services.d.ts +0 -1
  270. package/dist/routes/services.js +13 -17
  271. package/dist/routes/services.js.map +1 -1
  272. package/dist/routes/sse.d.ts +3 -2
  273. package/dist/routes/sse.js +7 -3
  274. package/dist/routes/sse.js.map +1 -1
  275. package/dist/routes/streams.d.ts +4 -2
  276. package/dist/routes/streams.js +8 -5
  277. package/dist/routes/streams.js.map +1 -1
  278. package/dist/routes/theme.d.ts +0 -1
  279. package/dist/routes/theme.js +12 -12
  280. package/dist/routes/theme.js.map +1 -1
  281. package/dist/routes/ui.d.ts +4 -4
  282. package/dist/routes/ui.js +17 -13
  283. package/dist/routes/ui.js.map +1 -1
  284. package/dist/routes/upgrade.d.ts +0 -1
  285. package/dist/routes/upgrade.js +2 -1
  286. package/dist/routes/upgrade.js.map +1 -1
  287. package/dist/service/commands.context.d.ts +0 -1
  288. package/dist/service/commands.context.js +7 -1
  289. package/dist/service/commands.context.js.map +1 -1
  290. package/dist/service/commands.d.ts +0 -1
  291. package/dist/service/generators.context.d.ts +0 -1
  292. package/dist/service/generators.d.ts +0 -1
  293. package/dist/service/generators.js +12 -9
  294. package/dist/service/generators.js.map +1 -1
  295. package/dist/service/index.d.ts +0 -1
  296. package/dist/streaming/captureLock.d.ts +1 -2
  297. package/dist/streaming/captureLock.js +6 -5
  298. package/dist/streaming/captureLock.js.map +1 -1
  299. package/dist/streaming/captureSession.d.ts +4 -5
  300. package/dist/streaming/captureSession.js +6 -7
  301. package/dist/streaming/captureSession.js.map +1 -1
  302. package/dist/streaming/clients.d.ts +4 -3
  303. package/dist/streaming/clients.js +5 -4
  304. package/dist/streaming/clients.js.map +1 -1
  305. package/dist/streaming/codec.d.ts +12 -3
  306. package/dist/streaming/codec.js +40 -28
  307. package/dist/streaming/codec.js.map +1 -1
  308. package/dist/streaming/fmp4Segmenter.d.ts +16 -5
  309. package/dist/streaming/fmp4Segmenter.js +110 -89
  310. package/dist/streaming/fmp4Segmenter.js.map +1 -1
  311. package/dist/streaming/hls.d.ts +55 -15
  312. package/dist/streaming/hls.js +255 -162
  313. package/dist/streaming/hls.js.map +1 -1
  314. package/dist/streaming/hlsResume.d.ts +42 -16
  315. package/dist/streaming/hlsResume.js +75 -42
  316. package/dist/streaming/hlsResume.js.map +1 -1
  317. package/dist/streaming/hlsSegments.d.ts +9 -7
  318. package/dist/streaming/hlsSegments.js +19 -14
  319. package/dist/streaming/hlsSegments.js.map +1 -1
  320. package/dist/streaming/lifecycle.d.ts +5 -4
  321. package/dist/streaming/lifecycle.js +25 -13
  322. package/dist/streaming/lifecycle.js.map +1 -1
  323. package/dist/streaming/monitor.d.ts +8 -5
  324. package/dist/streaming/monitor.js +109 -93
  325. package/dist/streaming/monitor.js.map +1 -1
  326. package/dist/streaming/mp4Parser.d.ts +29 -5
  327. package/dist/streaming/mp4Parser.js +49 -7
  328. package/dist/streaming/mp4Parser.js.map +1 -1
  329. package/dist/streaming/mpegts.d.ts +0 -1
  330. package/dist/streaming/mpegts.js +23 -7
  331. package/dist/streaming/mpegts.js.map +1 -1
  332. package/dist/streaming/playlistBuilder.d.ts +1 -3
  333. package/dist/streaming/playlistBuilder.js +2 -3
  334. package/dist/streaming/playlistBuilder.js.map +1 -1
  335. package/dist/streaming/preroll.d.ts +76 -30
  336. package/dist/streaming/preroll.js +141 -89
  337. package/dist/streaming/preroll.js.map +1 -1
  338. package/dist/streaming/pretune.d.ts +3 -4
  339. package/dist/streaming/pretune.js +50 -46
  340. package/dist/streaming/pretune.js.map +1 -1
  341. package/dist/streaming/pretuneTimers.d.ts +16 -12
  342. package/dist/streaming/pretuneTimers.js +27 -37
  343. package/dist/streaming/pretuneTimers.js.map +1 -1
  344. package/dist/streaming/recovery.d.ts +36 -7
  345. package/dist/streaming/recovery.js +53 -11
  346. package/dist/streaming/recovery.js.map +1 -1
  347. package/dist/streaming/registry.d.ts +35 -14
  348. package/dist/streaming/registry.js +23 -8
  349. package/dist/streaming/registry.js.map +1 -1
  350. package/dist/streaming/setup.d.ts +66 -25
  351. package/dist/streaming/setup.js +305 -229
  352. package/dist/streaming/setup.js.map +1 -1
  353. package/dist/streaming/showInfo.d.ts +29 -13
  354. package/dist/streaming/showInfo.js +156 -140
  355. package/dist/streaming/showInfo.js.map +1 -1
  356. package/dist/streaming/statusEmitter.d.ts +16 -12
  357. package/dist/streaming/statusEmitter.js +25 -21
  358. package/dist/streaming/statusEmitter.js.map +1 -1
  359. package/dist/types/channels.d.ts +0 -1
  360. package/dist/types/config.d.ts +24 -14
  361. package/dist/types/config.js +5 -1
  362. package/dist/types/config.js.map +1 -1
  363. package/dist/types/index.d.ts +4 -4
  364. package/dist/types/index.js +2 -1
  365. package/dist/types/index.js.map +1 -1
  366. package/dist/types/profiles.d.ts +3 -3
  367. package/dist/types/selection.d.ts +19 -8
  368. package/dist/types/shared.d.ts +0 -1
  369. package/dist/types/streaming.d.ts +12 -6
  370. package/dist/types/streaming.js +12 -5
  371. package/dist/types/streaming.js.map +1 -1
  372. package/dist/upgrade/commands.context.d.ts +0 -1
  373. package/dist/upgrade/commands.d.ts +0 -1
  374. package/dist/upgrade/detection.context.d.ts +0 -1
  375. package/dist/upgrade/detection.d.ts +0 -1
  376. package/dist/upgrade/index.d.ts +0 -1
  377. package/dist/upgrade/lifecycle.context.d.ts +2 -1
  378. package/dist/upgrade/lifecycle.context.js +9 -4
  379. package/dist/upgrade/lifecycle.context.js.map +1 -1
  380. package/dist/upgrade/lifecycle.d.ts +0 -1
  381. package/dist/upgrade/pathHandle.d.ts +0 -1
  382. package/dist/utils/bootSession.context.d.ts +0 -1
  383. package/dist/utils/bootSession.context.js +2 -1
  384. package/dist/utils/bootSession.context.js.map +1 -1
  385. package/dist/utils/bootSession.d.ts +0 -1
  386. package/dist/utils/chromeFetch.d.ts +0 -1
  387. package/dist/utils/cliOutput.d.ts +0 -1
  388. package/dist/utils/debugFilter.d.ts +3 -3
  389. package/dist/utils/debugFilter.js +7 -5
  390. package/dist/utils/debugFilter.js.map +1 -1
  391. package/dist/utils/delay.d.ts +47 -25
  392. package/dist/utils/delay.js +74 -37
  393. package/dist/utils/delay.js.map +1 -1
  394. package/dist/{hdhr → utils}/deviceId.d.ts +1 -2
  395. package/dist/{hdhr → utils}/deviceId.js +11 -29
  396. package/dist/utils/deviceId.js.map +1 -0
  397. package/dist/utils/errors.d.ts +7 -1
  398. package/dist/utils/errors.js +12 -3
  399. package/dist/utils/errors.js.map +1 -1
  400. package/dist/utils/evaluate.d.ts +11 -3
  401. package/dist/utils/evaluate.js +4 -3
  402. package/dist/utils/evaluate.js.map +1 -1
  403. package/dist/utils/ffmpeg.context.d.ts +0 -1
  404. package/dist/utils/ffmpeg.d.ts +0 -1
  405. package/dist/utils/fileLogger.d.ts +26 -14
  406. package/dist/utils/fileLogger.js +89 -49
  407. package/dist/utils/fileLogger.js.map +1 -1
  408. package/dist/utils/format.d.ts +12 -3
  409. package/dist/utils/format.js +29 -11
  410. package/dist/utils/format.js.map +1 -1
  411. package/dist/utils/httpErrors.d.ts +13 -0
  412. package/dist/utils/httpErrors.js +22 -0
  413. package/dist/utils/httpErrors.js.map +1 -0
  414. package/dist/utils/index.d.ts +3 -2
  415. package/dist/utils/index.js +3 -1
  416. package/dist/utils/index.js.map +1 -1
  417. package/dist/utils/logEmitter.d.ts +0 -1
  418. package/dist/utils/logger.d.ts +9 -9
  419. package/dist/utils/logger.js +64 -20
  420. package/dist/utils/logger.js.map +1 -1
  421. package/dist/utils/m3u.d.ts +0 -1
  422. package/dist/utils/markup.d.ts +0 -1
  423. package/dist/utils/markup.js +8 -3
  424. package/dist/utils/markup.js.map +1 -1
  425. package/dist/utils/memo.d.ts +0 -1
  426. package/dist/utils/morganStream.d.ts +0 -1
  427. package/dist/utils/network.d.ts +0 -1
  428. package/dist/utils/never.d.ts +0 -1
  429. package/dist/utils/pid.d.ts +0 -1
  430. package/dist/utils/plainObject.d.ts +6 -0
  431. package/dist/utils/plainObject.js +18 -0
  432. package/dist/utils/plainObject.js.map +1 -0
  433. package/dist/utils/platform.d.ts +0 -1
  434. package/dist/utils/processInspector.context.d.ts +32 -2
  435. package/dist/utils/processInspector.context.js +117 -1
  436. package/dist/utils/processInspector.context.js.map +1 -1
  437. package/dist/utils/processInspector.d.ts +0 -32
  438. package/dist/utils/processInspector.js +10 -117
  439. package/dist/utils/processInspector.js.map +1 -1
  440. package/dist/utils/retry.d.ts +5 -4
  441. package/dist/utils/retry.js +43 -33
  442. package/dist/utils/retry.js.map +1 -1
  443. package/dist/utils/runtimeIdentity.context.d.ts +0 -1
  444. package/dist/utils/runtimeIdentity.context.js +4 -30
  445. package/dist/utils/runtimeIdentity.context.js.map +1 -1
  446. package/dist/utils/runtimeIdentity.d.ts +18 -7
  447. package/dist/utils/runtimeIdentity.js +51 -18
  448. package/dist/utils/runtimeIdentity.js.map +1 -1
  449. package/dist/utils/sanitize.d.ts +0 -1
  450. package/dist/utils/streamContext.d.ts +0 -1
  451. package/dist/utils/timing.d.ts +3 -4
  452. package/dist/utils/timing.js +4 -4
  453. package/dist/utils/timing.js.map +1 -1
  454. package/dist/utils/version.d.ts +5 -4
  455. package/dist/utils/version.js +35 -24
  456. package/dist/utils/version.js.map +1 -1
  457. package/package.json +9 -9
  458. package/dist/app.d.ts.map +0 -1
  459. package/dist/browser/blockedPage.d.ts.map +0 -1
  460. package/dist/browser/browserSupervisor.d.ts.map +0 -1
  461. package/dist/browser/cdp.d.ts.map +0 -1
  462. package/dist/browser/channelSelection.d.ts.map +0 -1
  463. package/dist/browser/consent.d.ts.map +0 -1
  464. package/dist/browser/display.d.ts.map +0 -1
  465. package/dist/browser/hlsPlaylistObserver.d.ts.map +0 -1
  466. package/dist/browser/index.d.ts.map +0 -1
  467. package/dist/browser/launchGovernor.d.ts.map +0 -1
  468. package/dist/browser/login.d.ts.map +0 -1
  469. package/dist/browser/manifestInterceptor.d.ts.map +0 -1
  470. package/dist/browser/pageStaleness.d.ts.map +0 -1
  471. package/dist/browser/precaching.d.ts.map +0 -1
  472. package/dist/browser/tabCapture.d.ts.map +0 -1
  473. package/dist/browser/tabNetworkObserver.d.ts.map +0 -1
  474. package/dist/browser/tabSelection.d.ts.map +0 -1
  475. package/dist/browser/tuning/cache.d.ts.map +0 -1
  476. package/dist/browser/tuning/comcastPolymer.d.ts.map +0 -1
  477. package/dist/browser/tuning/cox.d.ts.map +0 -1
  478. package/dist/browser/tuning/directv.d.ts.map +0 -1
  479. package/dist/browser/tuning/fox.d.ts.map +0 -1
  480. package/dist/browser/tuning/gridSearch.d.ts.map +0 -1
  481. package/dist/browser/tuning/hbo.d.ts.map +0 -1
  482. package/dist/browser/tuning/hulu.d.ts.map +0 -1
  483. package/dist/browser/tuning/shared.d.ts.map +0 -1
  484. package/dist/browser/tuning/sling.d.ts.map +0 -1
  485. package/dist/browser/tuning/spectrum.d.ts.map +0 -1
  486. package/dist/browser/tuning/thumbnailRow.d.ts.map +0 -1
  487. package/dist/browser/tuning/tileClick.d.ts.map +0 -1
  488. package/dist/browser/tuning/xfinity.d.ts.map +0 -1
  489. package/dist/browser/tuning/youtubeTv.d.ts.map +0 -1
  490. package/dist/browser/video.d.ts.map +0 -1
  491. package/dist/browser/windowSync.d.ts.map +0 -1
  492. package/dist/channels/index.d.ts.map +0 -1
  493. package/dist/config/channelForm.d.ts.map +0 -1
  494. package/dist/config/consistencyProbe.d.ts.map +0 -1
  495. package/dist/config/health.d.ts.map +0 -1
  496. package/dist/config/index.d.ts.map +0 -1
  497. package/dist/config/paths.d.ts.map +0 -1
  498. package/dist/config/persistence.context.d.ts.map +0 -1
  499. package/dist/config/persistence.d.ts.map +0 -1
  500. package/dist/config/presets.d.ts.map +0 -1
  501. package/dist/config/profiles.d.ts.map +0 -1
  502. package/dist/config/providerLineups.d.ts.map +0 -1
  503. package/dist/config/reactivity.d.ts.map +0 -1
  504. package/dist/config/servicePacks.d.ts.map +0 -1
  505. package/dist/config/services.d.ts.map +0 -1
  506. package/dist/config/sites.d.ts.map +0 -1
  507. package/dist/config/userChannels.d.ts.map +0 -1
  508. package/dist/config/userConfig.d.ts.map +0 -1
  509. package/dist/config/userProfiles.d.ts.map +0 -1
  510. package/dist/hdhr/channelMap.d.ts.map +0 -1
  511. package/dist/hdhr/deviceId.d.ts.map +0 -1
  512. package/dist/hdhr/deviceId.js.map +0 -1
  513. package/dist/hdhr/discover.d.ts.map +0 -1
  514. package/dist/hdhr/getHandlers.d.ts.map +0 -1
  515. package/dist/hdhr/identity.d.ts.map +0 -1
  516. package/dist/hdhr/index.d.ts.map +0 -1
  517. package/dist/hdhr/protocol.d.ts.map +0 -1
  518. package/dist/hdhr/tunerState.d.ts.map +0 -1
  519. package/dist/hdhr/udp.d.ts.map +0 -1
  520. package/dist/identity.d.ts.map +0 -1
  521. package/dist/index.d.ts.map +0 -1
  522. package/dist/native/codecInference.d.ts.map +0 -1
  523. package/dist/native/decrypt.d.ts.map +0 -1
  524. package/dist/native/index.d.ts.map +0 -1
  525. package/dist/native/probe.d.ts.map +0 -1
  526. package/dist/native/proxy.d.ts.map +0 -1
  527. package/dist/native/tokenExpiry.d.ts.map +0 -1
  528. package/dist/routes/assets.d.ts.map +0 -1
  529. package/dist/routes/auth.d.ts.map +0 -1
  530. package/dist/routes/cdp.d.ts.map +0 -1
  531. package/dist/routes/channels.d.ts.map +0 -1
  532. package/dist/routes/clientActions.d.ts.map +0 -1
  533. package/dist/routes/components.d.ts.map +0 -1
  534. package/dist/routes/config/channels/endpoints/browse.d.ts.map +0 -1
  535. package/dist/routes/config/channels/endpoints/bulk.d.ts.map +0 -1
  536. package/dist/routes/config/channels/endpoints/crud.d.ts.map +0 -1
  537. package/dist/routes/config/channels/endpoints/importExport.d.ts.map +0 -1
  538. package/dist/routes/config/channels/endpoints/predefined.d.ts.map +0 -1
  539. package/dist/routes/config/channels/endpoints/prefs.d.ts.map +0 -1
  540. package/dist/routes/config/channels/endpoints/service.d.ts.map +0 -1
  541. package/dist/routes/config/channels/endpoints/tags.d.ts.map +0 -1
  542. package/dist/routes/config/channels/healthBridge.d.ts.map +0 -1
  543. package/dist/routes/config/channels/http/handler.d.ts.map +0 -1
  544. package/dist/routes/config/channels/http/playlistHint.d.ts.map +0 -1
  545. package/dist/routes/config/channels/http/serviceWarning.d.ts.map +0 -1
  546. package/dist/routes/config/channels/index.d.ts.map +0 -1
  547. package/dist/routes/config/channels/setup.d.ts.map +0 -1
  548. package/dist/routes/config/channels/table.d.ts.map +0 -1
  549. package/dist/routes/config/http/envelope.d.ts.map +0 -1
  550. package/dist/routes/config/index.d.ts.map +0 -1
  551. package/dist/routes/config/services.d.ts.map +0 -1
  552. package/dist/routes/config/settings.d.ts.map +0 -1
  553. package/dist/routes/debug.d.ts.map +0 -1
  554. package/dist/routes/health.d.ts.map +0 -1
  555. package/dist/routes/hls.d.ts.map +0 -1
  556. package/dist/routes/icons.d.ts.map +0 -1
  557. package/dist/routes/index.d.ts.map +0 -1
  558. package/dist/routes/logs.d.ts.map +0 -1
  559. package/dist/routes/mpegts.d.ts.map +0 -1
  560. package/dist/routes/play.d.ts.map +0 -1
  561. package/dist/routes/playlist.d.ts.map +0 -1
  562. package/dist/routes/root/content.d.ts.map +0 -1
  563. package/dist/routes/root/index.d.ts.map +0 -1
  564. package/dist/routes/root/scripts/channels.d.ts.map +0 -1
  565. package/dist/routes/root/scripts/clientEscape.d.ts.map +0 -1
  566. package/dist/routes/root/scripts/clientUrl.d.ts.map +0 -1
  567. package/dist/routes/root/scripts/config.d.ts.map +0 -1
  568. package/dist/routes/root/scripts/index.d.ts.map +0 -1
  569. package/dist/routes/root/scripts/shared.d.ts.map +0 -1
  570. package/dist/routes/root/scripts/status.d.ts.map +0 -1
  571. package/dist/routes/root/scripts/status.handlers.d.ts.map +0 -1
  572. package/dist/routes/root/styles.d.ts.map +0 -1
  573. package/dist/routes/services.d.ts.map +0 -1
  574. package/dist/routes/sse.d.ts.map +0 -1
  575. package/dist/routes/streams.d.ts.map +0 -1
  576. package/dist/routes/theme.d.ts.map +0 -1
  577. package/dist/routes/ui.d.ts.map +0 -1
  578. package/dist/routes/upgrade.d.ts.map +0 -1
  579. package/dist/service/commands.context.d.ts.map +0 -1
  580. package/dist/service/commands.d.ts.map +0 -1
  581. package/dist/service/generators.context.d.ts.map +0 -1
  582. package/dist/service/generators.d.ts.map +0 -1
  583. package/dist/service/index.d.ts.map +0 -1
  584. package/dist/streaming/captureLock.d.ts.map +0 -1
  585. package/dist/streaming/captureSession.d.ts.map +0 -1
  586. package/dist/streaming/clients.d.ts.map +0 -1
  587. package/dist/streaming/codec.d.ts.map +0 -1
  588. package/dist/streaming/fmp4Segmenter.d.ts.map +0 -1
  589. package/dist/streaming/hls.d.ts.map +0 -1
  590. package/dist/streaming/hlsResume.d.ts.map +0 -1
  591. package/dist/streaming/hlsSegments.d.ts.map +0 -1
  592. package/dist/streaming/lifecycle.d.ts.map +0 -1
  593. package/dist/streaming/monitor.d.ts.map +0 -1
  594. package/dist/streaming/mp4Parser.d.ts.map +0 -1
  595. package/dist/streaming/mpegts.d.ts.map +0 -1
  596. package/dist/streaming/playlistBuilder.d.ts.map +0 -1
  597. package/dist/streaming/preroll.d.ts.map +0 -1
  598. package/dist/streaming/pretune.d.ts.map +0 -1
  599. package/dist/streaming/pretuneTimers.d.ts.map +0 -1
  600. package/dist/streaming/recovery.d.ts.map +0 -1
  601. package/dist/streaming/registry.d.ts.map +0 -1
  602. package/dist/streaming/setup.d.ts.map +0 -1
  603. package/dist/streaming/showInfo.d.ts.map +0 -1
  604. package/dist/streaming/statusEmitter.d.ts.map +0 -1
  605. package/dist/types/channels.d.ts.map +0 -1
  606. package/dist/types/config.d.ts.map +0 -1
  607. package/dist/types/index.d.ts.map +0 -1
  608. package/dist/types/profiles.d.ts.map +0 -1
  609. package/dist/types/selection.d.ts.map +0 -1
  610. package/dist/types/shared.d.ts.map +0 -1
  611. package/dist/types/streaming.d.ts.map +0 -1
  612. package/dist/upgrade/commands.context.d.ts.map +0 -1
  613. package/dist/upgrade/commands.d.ts.map +0 -1
  614. package/dist/upgrade/detection.context.d.ts.map +0 -1
  615. package/dist/upgrade/detection.d.ts.map +0 -1
  616. package/dist/upgrade/index.d.ts.map +0 -1
  617. package/dist/upgrade/lifecycle.context.d.ts.map +0 -1
  618. package/dist/upgrade/lifecycle.d.ts.map +0 -1
  619. package/dist/upgrade/pathHandle.d.ts.map +0 -1
  620. package/dist/utils/bootSession.context.d.ts.map +0 -1
  621. package/dist/utils/bootSession.d.ts.map +0 -1
  622. package/dist/utils/chromeFetch.d.ts.map +0 -1
  623. package/dist/utils/cliOutput.d.ts.map +0 -1
  624. package/dist/utils/clock.d.ts +0 -15
  625. package/dist/utils/clock.d.ts.map +0 -1
  626. package/dist/utils/clock.js +0 -31
  627. package/dist/utils/clock.js.map +0 -1
  628. package/dist/utils/debugFilter.d.ts.map +0 -1
  629. package/dist/utils/delay.d.ts.map +0 -1
  630. package/dist/utils/errors.d.ts.map +0 -1
  631. package/dist/utils/evaluate.d.ts.map +0 -1
  632. package/dist/utils/ffmpeg.context.d.ts.map +0 -1
  633. package/dist/utils/ffmpeg.d.ts.map +0 -1
  634. package/dist/utils/fileLogger.d.ts.map +0 -1
  635. package/dist/utils/format.d.ts.map +0 -1
  636. package/dist/utils/index.d.ts.map +0 -1
  637. package/dist/utils/logEmitter.d.ts.map +0 -1
  638. package/dist/utils/logger.d.ts.map +0 -1
  639. package/dist/utils/m3u.d.ts.map +0 -1
  640. package/dist/utils/markup.d.ts.map +0 -1
  641. package/dist/utils/memo.d.ts.map +0 -1
  642. package/dist/utils/morganStream.d.ts.map +0 -1
  643. package/dist/utils/network.d.ts.map +0 -1
  644. package/dist/utils/never.d.ts.map +0 -1
  645. package/dist/utils/pid.d.ts.map +0 -1
  646. package/dist/utils/platform.d.ts.map +0 -1
  647. package/dist/utils/processInspector.context.d.ts.map +0 -1
  648. package/dist/utils/processInspector.d.ts.map +0 -1
  649. package/dist/utils/retry.d.ts.map +0 -1
  650. package/dist/utils/runtimeIdentity.context.d.ts.map +0 -1
  651. package/dist/utils/runtimeIdentity.d.ts.map +0 -1
  652. package/dist/utils/sanitize.d.ts.map +0 -1
  653. package/dist/utils/streamContext.d.ts.map +0 -1
  654. package/dist/utils/timing.d.ts.map +0 -1
  655. package/dist/utils/version.d.ts.map +0 -1
package/dist/app.js CHANGED
@@ -2,14 +2,14 @@
2
2
  *
3
3
  * app.ts: Express application builder for PrismCast.
4
4
  */
5
- import { CONFIG, displayConfiguration, initializeConfiguration, persistCoercedConfig, validateConfiguration } from "./config/index.js";
6
- import { LOG, boundedWait, claim, createMorganStream, formatError, formatTimestamp, getCurrentPattern, getPackageVersion, isDebugLogging, release, resolveFFmpegPath, setConsoleLogging, startUpdateChecking, stopUpdateChecking } from "./utils/index.js";
7
- import { closeBrowser, ensureDataDirectory, getCurrentBrowser, killStaleChrome, prepareExtension, setGracefulShutdown, setLoginModeEndObserver, startBrowserRestartChecking, startStalePageCleanup, stopBrowserRestartChecking, stopStalePageCleanup, syncWindowVisibility } from "./browser/index.js";
5
+ import { CONFIG, displayConfiguration, initializeConfiguration, validateConfiguration } from "./config/index.js";
6
+ import { LOG, assertNever, boundedWait, claim, createMorganStream, formatError, formatTimestamp, getCurrentPattern, getPackageVersion, handleRequestError, isDebugLogging, release, resolveFFmpegPath, setConsoleLogging, startUpdateChecking, stopUpdateChecking } from "./utils/index.js";
7
+ import { closeBrowser, ensureDataDirectory, getCurrentBrowser, isGracefulShutdown, killStaleChrome, setGracefulShutdown, setLoginModeEndObserver, startBrowserRestartChecking, startStalePageCleanup, stopBrowserRestartChecking, stopStalePageCleanup, syncWindowVisibility } from "./browser/index.js";
8
8
  import { ensureAllMigrated, snapshotAllForRelease } from "./config/persistence.js";
9
9
  import { flushHealthStateNow, loadHealthState } from "./config/health.js";
10
10
  import { getAllStreams, isCaptureIdentity } from "./streaming/registry.js";
11
11
  import { getDebugEnv, getLogFilePath, getServerPidFilePath } from "./config/paths.js";
12
- import { initializeFileLogger, shutdownFileLogger } from "./utils/fileLogger.js";
12
+ import { initializeFileLogger, setMaxLogSize, shutdownFileLogger } from "./utils/fileLogger.js";
13
13
  import { loadResumeState, saveResumeState } from "./streaming/hlsResume.js";
14
14
  import { revalidateDomainAuth, stopPrecaching } from "./browser/precaching.js";
15
15
  import { startHdhrServer, stopHdhrServer } from "./hdhr/index.js";
@@ -25,8 +25,10 @@ import { initializeUserProfiles } from "./config/userProfiles.js";
25
25
  import { installHealthBridge } from "./routes/config/channels/healthBridge.js";
26
26
  import { loadProviderLineups } from "./config/providerLineups.js";
27
27
  import morgan from "morgan";
28
+ import { registerConfigChangeHandler } from "./config/reactivity.js";
28
29
  import { runConsistencyProbeAtStartup } from "./config/consistencyProbe.js";
29
30
  import { setupRoutes } from "./routes/index.js";
31
+ import { systemClock } from "homebridge-plugin-utils";
30
32
  import { terminateStream } from "./streaming/lifecycle.js";
31
33
  import { validateProfiles } from "./config/profiles.js";
32
34
  /* The logging mode is set at startup based on the --console CLI flag. When console logging is enabled, the standard console output methods are wrapped to
@@ -42,13 +44,13 @@ let server = null;
42
44
  // shutdown, exactly as the file logger's drain bound does for a wedged filesystem operation.
43
45
  const SERVER_CLOSE_BOUND_MS = 5000;
44
46
  /* The background-service teardown stack. Each long-lived background service (stale-page sweep, browser-restart watchdog, idle cleanup, show-info/pretune polling,
45
- * update check) registers its stop here at the moment it starts in startServer(); graceful shutdown disposes the stack wholesale, so a future service cannot be
46
- * started without also being torn down. An AsyncDisposableStack is used (rather than the synchronous DisposableStack) so the teardown awaits each registered stop -
47
+ * update check) registers its stop here at the moment it starts in startBackgroundServices(); graceful shutdown disposes the stack wholesale, so a future service cannot
48
+ * be started without also being torn down. An AsyncDisposableStack is used (rather than the synchronous DisposableStack) so the teardown awaits each registered stop -
47
49
  * today's registered stops are all synchronous, but a future service whose stop must complete (e.g. flushing to disk) would be silently fire-and-forgotten by a
48
50
  * sync stack.
49
51
  */
50
52
  let backgroundServices = null;
51
- // Interval for idle stream cleanup.
53
+ // The repeating callback timer the idle stream cleanup runs on, armed through the port and disposed through its handle.
52
54
  let idleCleanupInterval = null;
53
55
  /**
54
56
  * Starts the idle cleanup interval. Runs every 10 seconds to check for idle streams and terminate them.
@@ -57,24 +59,59 @@ function startIdleCleanup() {
57
59
  if (idleCleanupInterval) {
58
60
  return;
59
61
  }
60
- // Check for idle streams every 10 seconds.
61
- idleCleanupInterval = setInterval(() => {
62
+ // Check for idle streams every 10 seconds, on the port's repeating callback timer.
63
+ idleCleanupInterval = systemClock.schedule(() => {
62
64
  cleanupIdleStreams();
63
- }, 10000);
65
+ }, 10000, { repeat: true });
64
66
  }
65
67
  /**
66
68
  * Stops the idle cleanup interval.
67
69
  */
68
70
  function stopIdleCleanup() {
69
71
  if (idleCleanupInterval) {
70
- clearInterval(idleCleanupInterval);
72
+ idleCleanupInterval[Symbol.dispose]();
71
73
  idleCleanupInterval = null;
72
74
  }
73
75
  }
74
- /* When the process receives a termination signal, we close all active streams and the browser before exiting. This ensures resources are released cleanly.
76
+ /**
77
+ * Closes the main HTTP server, graceful shutdown's step for the listener the boot's tail bound. Connections are destroyed first because the status and log SSE
78
+ * streams (routes/streams.ts and routes/logs.ts) hold their sockets open with a heartbeat, and the close waits on every one of them; the CDP proxy's WebSocket
79
+ * clients have already closed themselves with 1001 in response to the browser disconnect shutdown awaits before this step. An in-flight request ends with the
80
+ * process, because the exit follows immediately. The server reference is taken and cleared before the close, so a close that runs again finds no server.
81
+ * @param clock - The clock the close bound runs on, the system clock in production and a test clock in a row that crosses the bound.
82
+ * @returns A promise that resolves once the server closes or its bound lapses, and at once when no server is bound.
75
83
  */
84
+ export async function closeMainServer(clock = systemClock) {
85
+ const current = server;
86
+ server = null;
87
+ if (!current) {
88
+ return;
89
+ }
90
+ // The outer catch keeps a close that throws from rejecting, because shutdown runs unawaited from its signal handler and a rejection here would skip the exit
91
+ // the shutdown ends with.
92
+ try {
93
+ const { promise: closed, resolve } = Promise.withResolvers();
94
+ current.close((error) => {
95
+ if (error) {
96
+ LOG.error("The HTTP server reported an error while closing during shutdown.", { error: formatError(error) });
97
+ }
98
+ else {
99
+ LOG.info("HTTP server closed successfully.");
100
+ }
101
+ resolve(true);
102
+ });
103
+ current.closeAllConnections();
104
+ if ((await boundedWait(closed, SERVER_CLOSE_BOUND_MS, { clock })) === null) {
105
+ LOG.warn("The HTTP server did not close within its bound, so the shutdown continues.", { boundMs: SERVER_CLOSE_BOUND_MS });
106
+ }
107
+ }
108
+ catch (error) {
109
+ LOG.error("The HTTP server could not be closed during shutdown.", { error: formatError(error) });
110
+ }
111
+ }
76
112
  /**
77
- * Sets up signal handlers for graceful shutdown. When SIGINT or SIGTERM is received, we close all streams, the browser, and the HTTP server before exiting.
113
+ * Sets up signal handlers for graceful shutdown. When SIGHUP, SIGINT or SIGTERM is received, we close all active streams, the browser and the HTTP server before
114
+ * exiting, so every resource is released cleanly.
78
115
  */
79
116
  function setupGracefulShutdown() {
80
117
  let shutdownInProgress = false;
@@ -88,12 +125,14 @@ function setupGracefulShutdown() {
88
125
  // Set the graceful shutdown flag early so that page close errors are suppressed during stream termination.
89
126
  setGracefulShutdown(true);
90
127
  // Tear down the background services. HDHR is torn down first and awaited so its HTTP and UDP sockets fully release before the rest of shutdown proceeds. The
91
- // long-lived pollers are disposed wholesale via the AsyncDisposableStack they registered on at startup. The browser-launch-scoped precache cycle is cancelled
92
- // separately - its graceful-shutdown guard has already neutralized it via setGracefulShutdown above, and this clears the pending timer.
128
+ // long-lived pollers are disposed wholesale via the AsyncDisposableStack they registered on at startup. The precache scheduler, whose cycles browser launches
129
+ // and saves request rather than the boot starting them, is cancelled separately - its graceful-shutdown guard has already neutralized it via
130
+ // setGracefulShutdown above, and this clears its pending timers.
93
131
  await stopHdhrServer();
94
132
  await backgroundServices?.disposeAsync();
95
133
  stopPrecaching();
96
- // Terminate all streams. terminateStream() handles all cleanup including page closure and registry removal.
134
+ // Terminate all streams. terminateStream() runs every per-stream cleanup except the page close, which it skips during graceful shutdown because
135
+ // closeBrowser() below closes every page.
97
136
  const streams = getAllStreams();
98
137
  // Collect resume state from active streams before termination destroys the segmenters. Each entry captures sequence numbers and timestamps so the next startup
99
138
  // can seed from them, preventing HLS sequence resets that confuse Channels DVR. The segment index is decremented by one so the resumed playlist still includes
@@ -105,6 +144,7 @@ function setupGracefulShutdown() {
105
144
  if (stream.info.storeKey && segmenter) {
106
145
  resumeEntries.push({
107
146
  channelName: stream.info.storeKey,
147
+ discontinuityCount: segmenter.getDiscontinuityCount(),
108
148
  initSegment: segmenter.getInitSegment(),
109
149
  initVersion: segmenter.getInitVersion(),
110
150
  segmentIndex: Math.max(0, segmenter.getSegmentIndex() - 1),
@@ -112,7 +152,7 @@ function setupGracefulShutdown() {
112
152
  });
113
153
  }
114
154
  }
115
- saveResumeState(resumeEntries);
155
+ saveResumeState(resumeEntries, systemClock.now());
116
156
  for (const stream of streams) {
117
157
  terminateStream(stream.id, stream.info.storeKey, "server shutdown");
118
158
  }
@@ -122,32 +162,8 @@ function setupGracefulShutdown() {
122
162
  await closeBrowser();
123
163
  // Release the server instance slot so the next startup does not see a stale identity file.
124
164
  releaseInstanceSlot();
125
- /* Close the HTTP server. The close is awaited so its own line lands while the file logger is still open, which the shutdown below ends. Connections are
126
- * destroyed first because the status and log SSE streams (routes/streams.ts and routes/logs.ts) hold their sockets open with a heartbeat, and the close
127
- * waits on every one of them; the CDP proxy's WebSocket clients have already closed themselves with 1001 in response to the browser disconnect above. An
128
- * in-flight request ends with the process either way, because the exit follows immediately.
129
- */
130
- try {
131
- if (server) {
132
- const { promise: closed, resolve } = Promise.withResolvers();
133
- server.close((error) => {
134
- if (error) {
135
- LOG.error("Error closing server during shutdown: %s.", formatError(error));
136
- }
137
- else {
138
- LOG.info("HTTP server closed successfully.");
139
- }
140
- resolve(true);
141
- });
142
- server.closeAllConnections();
143
- if ((await boundedWait(closed, SERVER_CLOSE_BOUND_MS)) === null) {
144
- LOG.warn("The HTTP server did not close within %sms. Continuing the shutdown.", SERVER_CLOSE_BOUND_MS);
145
- }
146
- }
147
- }
148
- catch (error) {
149
- LOG.error("Error closing server during shutdown: %s.", formatError(error));
150
- }
165
+ // Close the HTTP server, awaited so its own line lands while the file logger is still open, which the step after it ends.
166
+ await closeMainServer();
151
167
  // Shut down file logger if in use. Awaited because shutdown drains the outstanding write chain before its final synchronous flush, and the process exit below
152
168
  // would otherwise cut that drain short.
153
169
  if (!usingConsoleLogging) {
@@ -155,6 +171,11 @@ function setupGracefulShutdown() {
155
171
  }
156
172
  process.exit(0);
157
173
  }
174
+ // A hangup asks the process to end as surely as an interrupt or a termination does, when the terminal or the session that started PrismCast goes away, and its
175
+ // default action would end the process without this teardown or the exit handler, so it runs the same shutdown.
176
+ process.on("SIGHUP", () => {
177
+ void shutdown();
178
+ });
158
179
  process.on("SIGINT", () => {
159
180
  void shutdown();
160
181
  });
@@ -182,8 +203,8 @@ const FILTERED_SKIP_PATTERNS = ["/logs", "/health", "/favicon", "/logo.png", "/l
182
203
  // Streaming and management endpoints, always logged in filtered mode because they mark what the server is actually doing.
183
204
  const FILTERED_IMPORTANT_PATTERNS = ["/stream", "/streams", "/config", "/playlist", "/debug"];
184
205
  /**
185
- * Records a request's arrival time. Registered ahead of the logger so both readers of the elapsed time measure from the moment the request reached the app rather
186
- * than from whenever the logger happened to see it.
206
+ * Records a request's arrival time. The request logger's middleware calls it as a request arrives, before handing the request to morgan, so every reader of the
207
+ * elapsed time measures from the request's arrival rather than from whenever morgan happened to see it.
187
208
  * @param req - The incoming request.
188
209
  * @param _res - The response, unused.
189
210
  * @param next - The next middleware in the chain.
@@ -260,26 +281,74 @@ export function skipInFilteredMode(request) {
260
281
  // Skip successful requests to the root landing page, and log everything else.
261
282
  return request.url === "/";
262
283
  }
284
+ /**
285
+ * Decides whether the request log skips a request at the given level: "none" skips every request, "all" logs every one, and "errors" and "filtered" apply their
286
+ * own rules. The switch is exhaustive, and no running level reaches its default arm, because every configuration the server builds corrects a level it does not
287
+ * recognize.
288
+ * @param options - The level in force, the elapsed milliseconds (null when unstamped), whether a Retry-After header is present, the response status, and the
289
+ * request URL.
290
+ * @returns True when the request should not be logged.
291
+ */
292
+ export function skipRequestLog(options) {
293
+ switch (options.level) {
294
+ case "all": {
295
+ return false;
296
+ }
297
+ case "errors": {
298
+ return skipInErrorsMode(options);
299
+ }
300
+ case "filtered": {
301
+ return skipInFilteredMode(options);
302
+ }
303
+ case "none": {
304
+ return true;
305
+ }
306
+ default: {
307
+ assertNever(options.level);
308
+ }
309
+ }
310
+ }
263
311
  /* The token is defined once for the process, at module load. morgan's token registry is global to the module, so registering inside the app builder would re-register
264
312
  * on every build - harmless today because the definition is constant, but a registration whose count tracks how often the app is assembled is the kind of coupling
265
313
  * that only shows up once a second build carries different state.
266
314
  */
267
315
  morgan.token("elapsed", elapsedMillis);
268
- /* The buildApp function creates and configures the Express application with all middleware and routes. This is separated from the server startup to allow for
269
- * testing and flexibility in deployment.
316
+ // The request log line. Its elapsed token reads the arrival stamp stampRequestStart records.
317
+ const REQUEST_LOG_FORMAT = ":method :url from :remote-addr responded :status in :elapsed ms.";
318
+ /**
319
+ * Builds the request logger: one morgan logger behind one middleware, each reading the configured HTTP log level per request, so a saved level applies from the
320
+ * next request. Whether a request is observed is decided when it arrives: under "none" the middleware hands it on at once, at the cost of one property read, and
321
+ * the request is never stamped or seen by morgan. Whether an observed request is logged is decided by the level in force when it finishes, because morgan makes
322
+ * its skip decision as the response completes. So a long-lived stream that arrived under "none" is never logged, and one that arrived under another level is
323
+ * judged by the level in force at its close.
324
+ * @param stream - The stream morgan writes each line to.
325
+ * @returns The request-logging middleware.
270
326
  */
327
+ export function createRequestLogger(stream) {
328
+ const logger = morgan(REQUEST_LOG_FORMAT, {
329
+ skip: (req, res) => skipRequestLog({
330
+ elapsedMs: elapsedMillisValue(req),
331
+ hasRetryAfter: res.getHeader("Retry-After") !== undefined,
332
+ level: CONFIG.logging.httpLogLevel,
333
+ statusCode: res.statusCode,
334
+ url: req.originalUrl || req.url
335
+ }),
336
+ stream
337
+ });
338
+ return (req, res, next) => {
339
+ if (CONFIG.logging.httpLogLevel === "none") {
340
+ next();
341
+ return;
342
+ }
343
+ stampRequestStart(req, res, () => { logger(req, res, next); });
344
+ };
345
+ }
271
346
  /**
272
- * Creates and configures the Express application with all middleware and routes.
347
+ * Creates and configures the Express application with all middleware and routes. It is a step of its own, so the boot's tail takes it as an injected step and a
348
+ * test substitutes a stub for it.
273
349
  * @returns The configured Express application.
274
350
  */
275
- async function buildApp() {
276
- try {
277
- await prepareExtension();
278
- }
279
- catch (error) {
280
- LOG.error("Cannot build app without extension: %s.", formatError(error));
281
- throw error;
282
- }
351
+ function buildApp() {
283
352
  const app = express();
284
353
  // Trust proxy headers (X-Forwarded-Proto, X-Forwarded-Host) so that req.protocol and req.hostname reflect what the client actually used when accessing through
285
354
  // a reverse proxy. This ensures playlist URLs match the client's connection.
@@ -297,55 +366,20 @@ async function buildApp() {
297
366
  // Add body parsing middleware for form submissions (configuration page).
298
367
  app.use(express.urlencoded({ extended: true }));
299
368
  app.use(express.json());
300
- // Configure Morgan for HTTP request logging based on httpLogLevel configuration. Morgan output goes through morganStream which handles timestamp formatting
301
- // consistently for both console and file logging modes.
302
- if (CONFIG.logging.httpLogLevel !== "none") {
303
- const morganFormat = ":method :url from :remote-addr responded :status in :elapsed ms.";
304
- const morganStream = createMorganStream();
305
- // Stamp each request's arrival before morgan sees it, so the elapsed token and the filtered mode's slow-request rule read the same start time.
306
- app.use(stampRequestStart);
307
- if (CONFIG.logging.httpLogLevel === "errors") {
308
- // Log requests with 4xx or 5xx status codes, but skip 404s for common browser asset requests.
309
- app.use(morgan(morganFormat, {
310
- skip: (req, res) => skipInErrorsMode({
311
- hasRetryAfter: res.getHeader("Retry-After") !== undefined,
312
- statusCode: res.statusCode,
313
- url: req.originalUrl || req.url
314
- }),
315
- stream: morganStream
316
- }));
317
- }
318
- else if (CONFIG.logging.httpLogLevel === "filtered") {
319
- // Log important requests while skipping high-frequency polling endpoints. We always log errors, slow requests, and critical endpoints.
320
- app.use(morgan(morganFormat, {
321
- skip: (req, res) => skipInFilteredMode({
322
- elapsedMs: elapsedMillisValue(req),
323
- statusCode: res.statusCode,
324
- url: req.originalUrl || req.url
325
- }),
326
- stream: morganStream
327
- }));
328
- }
329
- else {
330
- // Log all requests.
331
- app.use(morgan(morganFormat, { stream: morganStream }));
332
- }
333
- }
369
+ // Log HTTP requests at the configured level, which the logger reads per request. Its output goes through the morgan stream adapter, which formats timestamps
370
+ // the same way in console and file logging modes.
371
+ app.use(createRequestLogger(createMorganStream()));
334
372
  // Set up all HTTP endpoints.
335
373
  setupRoutes(app);
336
- // Global error handler. Express error handlers require 4 parameters even if unused.
337
- app.use((err, _req, res, _next) => {
338
- LOG.error("Unhandled error in request: %s.", formatError(err));
339
- if (!res.headersSent) {
340
- res.status(500).send("Internal server error");
341
- }
342
- });
374
+ // The request error handler goes last, so it receives what every route above raises.
375
+ app.use(handleRequestError);
343
376
  return app;
344
377
  }
345
378
  /* PrismCast instance guard. Only one server instance should run at a time - a second instance would launch a competing Chrome process, bind to the same port,
346
- * and corrupt shared state. The guard delegates to the runtime-identity primitive in utils/runtimeIdentity.ts, which composes PID liveness with the current
347
- * boot session ID to classify the on-disk identity file. Stale records from reboots, container restarts, crashes, and ungraceful shutdowns are recovered
348
- * transparently: the next claim() overwrites them. A held-live record (same boot, alive PID) is the only state that rejects a startup.
379
+ * and corrupt shared state. The guard delegates to the runtime-identity primitive in utils/runtimeIdentity.ts, which composes PID liveness, the current boot
380
+ * session ID and the writer's command-line fingerprint to classify the on-disk identity file. Stale records from reboots, container restarts, crashes, and
381
+ * ungraceful shutdowns are recovered transparently: the next claim() overwrites them. A held-live record (same boot, alive PID, and that PID not proven by its
382
+ * command-line fingerprint to be another program) is the only state that rejects a startup.
349
383
  *
350
384
  * Ownership is verified structurally inside release(): the function reads the on-disk record and only removes the file when its PID matches this process's
351
385
  * PID. The file content is itself the source of truth, so a rejected duplicate startup's exit handler cannot accidentally delete the legitimate holder's file.
@@ -416,12 +450,123 @@ async function listenMainServer(app) {
416
450
  });
417
451
  return promise;
418
452
  }
419
- /* The startServer function initializes and starts the HTTP server. It validates configuration, cleans up stale processes, warms up the browser, and starts the
420
- * Express application.
453
+ /**
454
+ * Applies a saved log size limit to the open log file. The composition root owns the handler because it wires the file logger, and the file logger, a utility
455
+ * the configuration layer builds on, cannot import that layer back. The limit comes from the candidate, because the reconcile commits CONFIG only after its
456
+ * handlers run. The size check is started rather than awaited: the save needs only the limit, which the setter assigns before its first await, so a save never
457
+ * waits on log-file I/O.
458
+ * @param _changes - The change to the log size limit; the candidate carries the limit, so the handler reads that instead.
459
+ * @param next - The candidate running configuration.
460
+ * @returns No rejections.
421
461
  */
462
+ export async function applyLogSizeChanges(_changes, next) {
463
+ void setMaxLogSize(next.logging.maxSize);
464
+ return [];
465
+ }
466
+ // Module-load side effect: register the handler once per process, as every config-change handler registers, so it is in place before the first save can reach
467
+ // the reconcile.
468
+ registerConfigChangeHandler("logging.maxSize", applyLogSizeChanges);
469
+ // The boot's tail.
422
470
  /**
423
- * Initializes and starts the HTTP server. Before accepting connections, we validate configuration, clean up stale Chrome processes, and warm up the browser
424
- * instance.
471
+ * Starts the background services, each on the teardown stack graceful shutdown disposes. The boot's tail calls it through its injected steps, so a test records
472
+ * its place in the boot's order without starting a real service.
473
+ */
474
+ function startBackgroundServices() {
475
+ // Start the background services and register each one's stop on an AsyncDisposableStack the moment it starts, so graceful shutdown can dispose them all wholesale
476
+ // and a future service cannot be started without also being torn down. These are order-independent background loops/timers, so LIFO disposal order is immaterial.
477
+ // HDHR is intentionally not a member (it is torn down first in shutdown so its sockets release before the rest), and the precache scheduler stays off the stack
478
+ // as well: browser launches and saves request its cycles, and shutdown stops it separately.
479
+ backgroundServices = new AsyncDisposableStack();
480
+ startStalePageCleanup();
481
+ backgroundServices.defer(stopStalePageCleanup);
482
+ startBrowserRestartChecking();
483
+ backgroundServices.defer(stopBrowserRestartChecking);
484
+ startIdleCleanup();
485
+ backgroundServices.defer(stopIdleCleanup);
486
+ startShowInfoPolling();
487
+ backgroundServices.defer(stopShowInfoPolling);
488
+ startPretunePolling();
489
+ backgroundServices.defer(stopPretunePolling);
490
+ startUpdateChecking(getPackageVersion());
491
+ backgroundServices.defer(stopUpdateChecking);
492
+ }
493
+ export const defaultBootServicesDeps = {
494
+ attachCdpUpgradeHandler,
495
+ buildApp,
496
+ isGracefulShutdown,
497
+ listenMainServer,
498
+ startBackgroundServices,
499
+ startHdhrServer
500
+ };
501
+ /**
502
+ * Reports whether shutdown has begun, logging the skip line when it has. It reads the graceful-shutdown state the browser module holds, which the signal handler
503
+ * sets before its first await and nothing in production clears, and keeps no flag of its own. Each caller returns when it answers true, so the line logs at most
504
+ * once per boot.
505
+ *
506
+ * Shutdown never waits on the boot, so the boot reaches a check only while shutdown is still awaiting its own steps, and where the signal lands decides what the boot has
507
+ * armed. A signal before setupGracefulShutdown(), the data directory creation included, meets no handler of ours, and the platform's default
508
+ * action ends the process with nothing armed. A signal during the release boot coordinator, the configuration load, the logger, the FFmpeg check,
509
+ * the stores or the consistency probe runs shutdown while the stack and the listener are absent, so its disposal and its close have nothing to
510
+ * act on, and with no browser to wait for, its exit ends the boot before the first check. A signal during the browser warm-up makes shutdown's
511
+ * closeBrowser() supersede the launch in flight: the launch rejects when it settles, the warm-up rethrows, and the entry's catch, finding shutdown begun, leaves the exit
512
+ * to shutdown, whose own exit usually comes first because the launch has seconds of work left, so the boot reaches no check whichever exit lands first. A signal during
513
+ * the preroll or the window sync finds a ready browser, so shutdown waits for Chrome's exit, within the browser module's BROWSER_TEARDOWN_DRAIN_BOUND_MS; the window sync
514
+ * returns at once when shutdown has begun, so a boot whose preroll finishes inside that wait reaches the first check, which stops it before the stack is armed, and a
515
+ * boot whose preroll outlasts the wait is ended by the exit. The build is synchronous and a bind on an address crosses no event-loop turn, so between arming the stack
516
+ * and starting HDHomeRun the one await a signal can land in is a hostname host's lookup during the bind: shutdown reads the listener only after its own awaits, so the
517
+ * bind completes and shutdown closes the listener, and the check after the bind stops the boot before HDHomeRun starts. A signal during the HDHomeRun start lets
518
+ * shutdown's stop run before the start finishes binding, and the sockets it binds last until the exit. A signal after the boot finished meets no check and shutdown
519
+ * tears everything down; a repeated signal is absorbed by the handler's own guard; and a process restart discards the state, so the next boot starts with it unset.
520
+ * @param deps - The boot's steps, whose shutdown reader the check calls.
521
+ * @returns True when shutdown has begun, so the caller arms nothing further.
522
+ */
523
+ function shutdownBegan(deps) {
524
+ if (!deps.isGracefulShutdown()) {
525
+ return false;
526
+ }
527
+ LOG.info("Shutdown began during startup, so the remaining startup steps are skipped.");
528
+ return true;
529
+ }
530
+ /**
531
+ * Arms what the server runs for the life of the process, in order: the background services, the Express application and its listener, the CDP upgrade handler,
532
+ * and HDHomeRun emulation. The signal handlers are installed well before the boot reaches this point, so a signal can run shutdown against a background stack, a
533
+ * listener and an HDHomeRun surface that do not exist yet, and a boot that armed them afterwards would leave them running in a process that is shutting down. The
534
+ * boot therefore reads the shutdown state before it arms the stack, and again after the bind, the await a later arming step follows, and arms nothing once
535
+ * shutdown has begun. The check after the bind decides only where that await yields to the event loop - a hostname host's lookup - and elsewhere the first check
536
+ * decides. The limit is an await already in flight when the signal lands: it completes, and the process exit at the end of shutdown ends whatever it started.
537
+ * @param deps - The boot's steps: the real ones in production, and recording stubs in a test.
538
+ * @returns A promise that resolves once the boot's tail completes or stops for a shutdown, and rejects when the build or the bind fails.
539
+ */
540
+ export async function startBootServices(deps = defaultBootServicesDeps) {
541
+ if (shutdownBegan(deps)) {
542
+ return;
543
+ }
544
+ deps.startBackgroundServices();
545
+ // Build and start Express application.
546
+ try {
547
+ const app = deps.buildApp();
548
+ // Bind the main HTTP server, detecting bind success or failure through explicit events rather than the unreliable listen callback. A bind failure throws out
549
+ // of here so the catch below surfaces it and the process exits cleanly rather than lingering with no HTTP surface.
550
+ server = await deps.listenMainServer(app);
551
+ // Attach the CDP proxy upgrade handler once the underlying http.Server exists. The handler is gated on the `cdp` debug category at request time, so it sits
552
+ // dormant until the user enables CDP via /debug. We attach unconditionally so the toggle takes effect without requiring a restart.
553
+ deps.attachCdpUpgradeHandler(server);
554
+ }
555
+ catch (error) {
556
+ // Covers both a buildApp failure and a listenMainServer bind failure; the latter already logged the precise, operator-actionable line before rejecting.
557
+ LOG.error("Failed to build or start the HTTP server: %s.", formatError(error));
558
+ throw error;
559
+ }
560
+ if (shutdownBegan(deps)) {
561
+ return;
562
+ }
563
+ // Start HDHomeRun emulation server if enabled. This runs independently of the main server and handles EADDRINUSE gracefully without affecting PrismCast's
564
+ // primary functionality.
565
+ await deps.startHdhrServer();
566
+ }
567
+ /**
568
+ * Initializes the server and starts it: we validate configuration, clean up stale Chrome processes and warm up the browser instance, then hand the rest of the
569
+ * boot to startBootServices, which arms the background services, builds the Express application and binds its listener, and starts HDHomeRun emulation.
425
570
  * @param parsedArgs - Parsed command-line arguments containing flags and override values.
426
571
  */
427
572
  export async function startServer(parsedArgs) {
@@ -455,6 +600,18 @@ export async function startServer(parsedArgs) {
455
600
  cliOverrides[configPath] = parsedArgs[argKey];
456
601
  }
457
602
  }
603
+ /* Claim the server instance slot before the boot touches anything a running instance owns: the stores the release coordinator snapshots and migrates, the
604
+ * configuration file the configuration load corrects, the Chrome profile and the port. Without the claim first, a second start against a data directory a live
605
+ * instance holds writes those stores before its claim refuses it, and a newer release started beside an older one migrates the older one's files out from
606
+ * under it. The claim depends on nothing the configuration produces, since the data directory's path is fixed before the server starts, so it can stand this
607
+ * early. The directory is created first because the identity file lives there, and creating it also removes the artifacts retired releases left behind,
608
+ * which no release since their retirement writes. The signal handlers follow the claim directly, so the boot never holds the slot without the shutdown that
609
+ * releases it on a signal; shutdown skips each surface the boot has not built yet. A failure later in the boot, the coordinator's or the configuration's
610
+ * included, exits through the exit handler, whose releaseInstanceSlot() removes the identity file only when this process wrote it.
611
+ */
612
+ await ensureDataDirectory();
613
+ claimInstanceSlot();
614
+ setupGracefulShutdown();
458
615
  // Release boot coordinator: snapshot every persistence-managed file before any reads or migrations run, then apply any pending schema migrations across all
459
616
  // stores. Both operations are safe to run more than once within a release (snapshots skip when the labeled copy already exists; ensureMigrated skips when the
460
617
  // file is at the current schema version). Running them up front guarantees a restore point exists for every file at the start of every release boot, even if a
@@ -472,10 +629,6 @@ export async function startServer(parsedArgs) {
472
629
  try {
473
630
  await initializeConfiguration(cliOverrides);
474
631
  validateConfiguration();
475
- // Write any startup capture coercion back to disk so the on-disk state matches the coerced live CONFIG. This keeps a config file that holds an unsupported
476
- // capture value (native mode) from staying divergent forever, which would otherwise make the reload-validation path reject every later save on the phantom
477
- // capture diff. A no-op unless validateConfiguration actually coerced something.
478
- await persistCoercedConfig();
479
632
  await initializeUserProfiles();
480
633
  validateProfiles();
481
634
  }
@@ -483,13 +636,6 @@ export async function startServer(parsedArgs) {
483
636
  LOG.error(formatError(error));
484
637
  process.exit(1);
485
638
  }
486
- setupGracefulShutdown();
487
- // Ensure the data directory exists before any operations that depend on it.
488
- await ensureDataDirectory();
489
- // Claim the server instance slot before launching Chrome or binding the port. This must come after ensureDataDirectory() since the identity file lives there.
490
- // claimInstanceSlot() exits with a precise diagnostic when a live holder is detected; on success the identity file holds our PID and boot session ID. If
491
- // startup subsequently fails, the exit handler calls releaseInstanceSlot() to remove the stale file.
492
- claimInstanceSlot();
493
639
  // Initialize file logger if not using console logging. Initialization follows configuration loading, which resolves the log file path and the size cap, and
494
640
  // ensureDataDirectory(), which creates the parent directory. Every line logged before this point is held by the logger and written by its first flush, so
495
641
  // the startup messages above are under no ordering constraint.
@@ -511,16 +657,15 @@ export async function startServer(parsedArgs) {
511
657
  LOG.info("Debug logging enabled for all categories.");
512
658
  }
513
659
  }
514
- // Check FFmpeg availability if using FFmpeg capture mode. This must be after file logger initialization so the log message is captured.
515
- if (CONFIG.streaming.captureMode === "ffmpeg") {
516
- const ffmpegPath = await resolveFFmpegPath();
517
- if (!ffmpegPath) {
518
- LOG.error("FFmpeg is not available. FFmpeg capture mode requires FFmpeg to be installed and in the system PATH.");
519
- LOG.error("Either install FFmpeg or change the capture mode to 'native' in the configuration.");
520
- process.exit(1);
521
- }
522
- LOG.info("Using FFmpeg at: %s.", ffmpegPath);
660
+ // Check FFmpeg availability on every boot, because FFmpeg capture is always in effect: the configuration corrects any other capture mode as it is built. This
661
+ // must be after file logger initialization so the log message is captured.
662
+ const ffmpegPath = await resolveFFmpegPath();
663
+ if (!ffmpegPath) {
664
+ LOG.error("FFmpeg is not available. FFmpeg capture mode requires FFmpeg to be installed and in the system PATH.");
665
+ LOG.error("Install FFmpeg and start PrismCast again.");
666
+ process.exit(1);
523
667
  }
668
+ LOG.info("Using FFmpeg at: %s.", ffmpegPath);
524
669
  // Load user channels from channels.json in the data directory if it exists.
525
670
  await initializeUserChannels();
526
671
  // Load persisted health state (channel health + domain auth) from health.json.
@@ -535,11 +680,11 @@ export async function startServer(parsedArgs) {
535
680
  // can clear the flag without waiting for the next precache cycle. Composition-root wiring - login.ts stays free of discovery knowledge, and revalidateDomainAuth
536
681
  // never rejects, so voiding the promise is safe.
537
682
  setLoginModeEndObserver((url) => void revalidateDomainAuth(url));
538
- // Run the cross-store consistency probe now that every store is loaded. Validates foreign-key-style rules spanning multiple stores (service selections, variant
539
- // canonicalKey targets, domain profile mappings, service tag filter) and auto-fixes warnings where safe. Errors do not block startup.
683
+ // Run the cross-store consistency probe now that every store is loaded. It logs each foreign-key-style inconsistency spanning multiple stores that an operator
684
+ // must act on, such as a variant whose canonical is missing, and changes nothing. Errors do not block startup.
540
685
  await runConsistencyProbeAtStartup();
541
686
  // Load HLS resume state from the previous shutdown. This seeds sequence numbers so streams resume forward instead of resetting to 0.
542
- loadResumeState();
687
+ loadResumeState(systemClock.now());
543
688
  killStaleChrome();
544
689
  // Warm up the browser. getCurrentBrowser() launches Chrome through the capture-readiness supervisor, whose launch gate runs the real capture probe (the
545
690
  // capability tier) at every launch - including this one. So a successful warm-up already means capture is verified. A browser that cannot capture rejects the
@@ -559,40 +704,29 @@ export async function startServer(parsedArgs) {
559
704
  // GPU probe wants an environment representative of the one capture runs in and the capture probe acquires a real capture, both of which want the window as the
560
705
  // launch left it.
561
706
  await syncWindowVisibility();
562
- // Start the background services and register each one's stop on an AsyncDisposableStack the moment it starts, so graceful shutdown can dispose them all wholesale
563
- // and a future service cannot be started without also being torn down. These are order-independent background loops/timers, so LIFO disposal order is immaterial.
564
- // HDHR is intentionally not a member (it is torn down first in shutdown so its sockets release before the rest), and the browser-launch-scoped precache cycle is
565
- // started elsewhere and stopped separately.
566
- backgroundServices = new AsyncDisposableStack();
567
- startStalePageCleanup();
568
- backgroundServices.defer(stopStalePageCleanup);
569
- startBrowserRestartChecking();
570
- backgroundServices.defer(stopBrowserRestartChecking);
571
- startIdleCleanup();
572
- backgroundServices.defer(stopIdleCleanup);
573
- startShowInfoPolling();
574
- backgroundServices.defer(stopShowInfoPolling);
575
- startPretunePolling();
576
- backgroundServices.defer(stopPretunePolling);
577
- startUpdateChecking(getPackageVersion());
578
- backgroundServices.defer(stopUpdateChecking);
579
- // Build and start Express application.
580
- try {
581
- const app = await buildApp();
582
- // Bind the main HTTP server, detecting bind success or failure through explicit events rather than the unreliable listen callback. A bind failure throws out
583
- // of here so the catch below surfaces it and the process exits cleanly rather than lingering with no HTTP surface.
584
- server = await listenMainServer(app);
585
- // Attach the CDP proxy upgrade handler once the underlying http.Server exists. The handler is gated on the `cdp` debug category at request time, so it sits
586
- // dormant until the user enables CDP via /debug. We attach unconditionally so the toggle takes effect without requiring a restart.
587
- attachCdpUpgradeHandler(server);
588
- }
589
- catch (error) {
590
- // Covers both a buildApp failure and a listenMainServer bind failure; the latter already logged the precise, operator-actionable line before rejecting.
591
- LOG.error("Failed to build or start the HTTP server: %s.", formatError(error));
592
- throw error;
707
+ // The boot's tail arms what runs for the life of the process: the background services, the HTTP server and HDHomeRun emulation.
708
+ await startBootServices();
709
+ }
710
+ export const defaultStartupFailureDeps = {
711
+ exit: (code) => {
712
+ process.exit(code);
713
+ },
714
+ isGracefulShutdown
715
+ };
716
+ /**
717
+ * Ends a startup that failed, the entry point's catch for startServer. A failure that lands once shutdown has begun is one shutdown caused, such as the browser
718
+ * warm-up's launch that shutdown superseded, so it is shutdown's to end: shutdown owns the exit, and a second exit racing it here would cut its drain short with the
719
+ * wrong code, so the handler logs the superseded startup and leaves the exit to it. The entry point's unhandled-rejection handler reads the same state for the
720
+ * same reason. Any other failure is fatal, and the process exits with a failure code so a process manager sees it.
721
+ * @param error - The error startServer rejected with.
722
+ * @param deps - The shutdown reader and the exit: the real ones in production, and recording stubs in a test.
723
+ */
724
+ export function handleStartupFailure(error, deps = defaultStartupFailureDeps) {
725
+ if (deps.isGracefulShutdown()) {
726
+ LOG.info("Startup was superseded by shutdown, so the process exits through shutdown.");
727
+ return;
593
728
  }
594
- // Start HDHomeRun emulation server if enabled. This runs independently of the main server and handles EADDRINUSE gracefully without affecting PrismCast's
595
- // primary functionality.
596
- await startHdhrServer();
729
+ LOG.error("Fatal startup error occurred.", { error: formatError(error) });
730
+ deps.exit(1);
597
731
  }
598
732
  //# sourceMappingURL=app.js.map