prismcast 1.10.2 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (693) hide show
  1. package/dist/app.d.ts +6 -3
  2. package/dist/app.d.ts.map +1 -0
  3. package/dist/app.js +140 -86
  4. package/dist/app.js.map +1 -1
  5. package/dist/browser/blockedPage.d.ts +80 -0
  6. package/dist/browser/blockedPage.d.ts.map +1 -0
  7. package/dist/browser/blockedPage.js +209 -0
  8. package/dist/browser/blockedPage.js.map +1 -0
  9. package/dist/browser/browserSupervisor.d.ts +80 -0
  10. package/dist/browser/browserSupervisor.d.ts.map +1 -0
  11. package/dist/browser/browserSupervisor.js +186 -0
  12. package/dist/browser/browserSupervisor.js.map +1 -0
  13. package/dist/browser/cdp.d.ts +1 -0
  14. package/dist/browser/cdp.d.ts.map +1 -0
  15. package/dist/browser/cdp.js +6 -5
  16. package/dist/browser/cdp.js.map +1 -1
  17. package/dist/browser/channelSelection.d.ts +45 -14
  18. package/dist/browser/channelSelection.d.ts.map +1 -0
  19. package/dist/browser/channelSelection.js +81 -18
  20. package/dist/browser/channelSelection.js.map +1 -1
  21. package/dist/browser/consent.d.ts +152 -0
  22. package/dist/browser/consent.d.ts.map +1 -0
  23. package/dist/browser/consent.js +418 -0
  24. package/dist/browser/consent.js.map +1 -0
  25. package/dist/browser/display.d.ts +1 -0
  26. package/dist/browser/display.d.ts.map +1 -0
  27. package/dist/browser/hlsPlaylistObserver.d.ts +55 -0
  28. package/dist/browser/hlsPlaylistObserver.d.ts.map +1 -0
  29. package/dist/browser/hlsPlaylistObserver.js +132 -0
  30. package/dist/browser/hlsPlaylistObserver.js.map +1 -0
  31. package/dist/browser/index.d.ts +67 -40
  32. package/dist/browser/index.d.ts.map +1 -0
  33. package/dist/browser/index.js +508 -300
  34. package/dist/browser/index.js.map +1 -1
  35. package/dist/browser/launchGovernor.d.ts +89 -0
  36. package/dist/browser/launchGovernor.d.ts.map +1 -0
  37. package/dist/browser/launchGovernor.js +96 -0
  38. package/dist/browser/launchGovernor.js.map +1 -0
  39. package/dist/browser/login.d.ts +10 -2
  40. package/dist/browser/login.d.ts.map +1 -0
  41. package/dist/browser/login.js +36 -5
  42. package/dist/browser/login.js.map +1 -1
  43. package/dist/browser/manifestInterceptor.d.ts +102 -49
  44. package/dist/browser/manifestInterceptor.d.ts.map +1 -0
  45. package/dist/browser/manifestInterceptor.js +219 -237
  46. package/dist/browser/manifestInterceptor.js.map +1 -1
  47. package/dist/browser/pageStaleness.d.ts +30 -0
  48. package/dist/browser/pageStaleness.d.ts.map +1 -0
  49. package/dist/browser/pageStaleness.js +76 -0
  50. package/dist/browser/pageStaleness.js.map +1 -0
  51. package/dist/browser/precaching.d.ts +95 -3
  52. package/dist/browser/precaching.d.ts.map +1 -0
  53. package/dist/browser/precaching.js +499 -65
  54. package/dist/browser/precaching.js.map +1 -1
  55. package/dist/browser/tabNetworkObserver.d.ts +49 -0
  56. package/dist/browser/tabNetworkObserver.d.ts.map +1 -0
  57. package/dist/browser/tabNetworkObserver.js +144 -0
  58. package/dist/browser/tabNetworkObserver.js.map +1 -0
  59. package/dist/browser/tuning/cache.d.ts +61 -0
  60. package/dist/browser/tuning/cache.d.ts.map +1 -0
  61. package/dist/browser/tuning/cache.js +62 -0
  62. package/dist/browser/tuning/cache.js.map +1 -0
  63. package/dist/browser/tuning/comcastPolymer.d.ts +3 -2
  64. package/dist/browser/tuning/comcastPolymer.d.ts.map +1 -0
  65. package/dist/browser/tuning/comcastPolymer.js +79 -102
  66. package/dist/browser/tuning/comcastPolymer.js.map +1 -1
  67. package/dist/browser/tuning/cox.d.ts +1 -0
  68. package/dist/browser/tuning/cox.d.ts.map +1 -0
  69. package/dist/browser/tuning/directv.d.ts +1 -0
  70. package/dist/browser/tuning/directv.d.ts.map +1 -0
  71. package/dist/browser/tuning/directv.js +52 -46
  72. package/dist/browser/tuning/directv.js.map +1 -1
  73. package/dist/browser/tuning/fox.d.ts +1 -0
  74. package/dist/browser/tuning/fox.d.ts.map +1 -0
  75. package/dist/browser/tuning/fox.js +13 -12
  76. package/dist/browser/tuning/fox.js.map +1 -1
  77. package/dist/browser/tuning/gridSearch.d.ts +68 -0
  78. package/dist/browser/tuning/gridSearch.d.ts.map +1 -0
  79. package/dist/browser/tuning/gridSearch.js +85 -0
  80. package/dist/browser/tuning/gridSearch.js.map +1 -0
  81. package/dist/browser/tuning/hbo.d.ts +1 -0
  82. package/dist/browser/tuning/hbo.d.ts.map +1 -0
  83. package/dist/browser/tuning/hbo.js +73 -45
  84. package/dist/browser/tuning/hbo.js.map +1 -1
  85. package/dist/browser/tuning/hulu.d.ts +1 -0
  86. package/dist/browser/tuning/hulu.d.ts.map +1 -0
  87. package/dist/browser/tuning/hulu.js +128 -173
  88. package/dist/browser/tuning/hulu.js.map +1 -1
  89. package/dist/browser/tuning/shared.d.ts +75 -5
  90. package/dist/browser/tuning/shared.d.ts.map +1 -0
  91. package/dist/browser/tuning/shared.js +216 -6
  92. package/dist/browser/tuning/shared.js.map +1 -1
  93. package/dist/browser/tuning/sling.d.ts +1 -0
  94. package/dist/browser/tuning/sling.d.ts.map +1 -0
  95. package/dist/browser/tuning/sling.js +105 -126
  96. package/dist/browser/tuning/sling.js.map +1 -1
  97. package/dist/browser/tuning/spectrum.d.ts +1 -0
  98. package/dist/browser/tuning/spectrum.d.ts.map +1 -0
  99. package/dist/browser/tuning/spectrum.js +80 -125
  100. package/dist/browser/tuning/spectrum.js.map +1 -1
  101. package/dist/browser/tuning/thumbnailRow.d.ts +1 -0
  102. package/dist/browser/tuning/thumbnailRow.d.ts.map +1 -0
  103. package/dist/browser/tuning/thumbnailRow.js +8 -2
  104. package/dist/browser/tuning/thumbnailRow.js.map +1 -1
  105. package/dist/browser/tuning/tileClick.d.ts +1 -0
  106. package/dist/browser/tuning/tileClick.d.ts.map +1 -0
  107. package/dist/browser/tuning/tileClick.js +3 -2
  108. package/dist/browser/tuning/tileClick.js.map +1 -1
  109. package/dist/browser/tuning/xfinity.d.ts +1 -0
  110. package/dist/browser/tuning/xfinity.d.ts.map +1 -0
  111. package/dist/browser/tuning/youtubeTv.d.ts +1 -0
  112. package/dist/browser/tuning/youtubeTv.d.ts.map +1 -0
  113. package/dist/browser/tuning/youtubeTv.js +74 -127
  114. package/dist/browser/tuning/youtubeTv.js.map +1 -1
  115. package/dist/browser/video.d.ts +54 -14
  116. package/dist/browser/video.d.ts.map +1 -0
  117. package/dist/browser/video.js +296 -138
  118. package/dist/browser/video.js.map +1 -1
  119. package/dist/channels/index.d.ts +1 -0
  120. package/dist/channels/index.d.ts.map +1 -0
  121. package/dist/channels/index.js +12 -9
  122. package/dist/channels/index.js.map +1 -1
  123. package/dist/config/channelForm.d.ts +15 -1
  124. package/dist/config/channelForm.d.ts.map +1 -0
  125. package/dist/config/channelForm.js +28 -12
  126. package/dist/config/channelForm.js.map +1 -1
  127. package/dist/config/consistencyProbe.d.ts +1 -0
  128. package/dist/config/consistencyProbe.d.ts.map +1 -0
  129. package/dist/config/consistencyProbe.js +27 -21
  130. package/dist/config/consistencyProbe.js.map +1 -1
  131. package/dist/config/health.d.ts +41 -8
  132. package/dist/config/health.d.ts.map +1 -0
  133. package/dist/config/health.js +189 -38
  134. package/dist/config/health.js.map +1 -1
  135. package/dist/config/index.d.ts +55 -3
  136. package/dist/config/index.d.ts.map +1 -0
  137. package/dist/config/index.js +320 -69
  138. package/dist/config/index.js.map +1 -1
  139. package/dist/config/paths.d.ts +10 -9
  140. package/dist/config/paths.d.ts.map +1 -0
  141. package/dist/config/paths.js +11 -11
  142. package/dist/config/paths.js.map +1 -1
  143. package/dist/config/persistence.context.d.ts +1 -0
  144. package/dist/config/persistence.context.d.ts.map +1 -0
  145. package/dist/config/persistence.d.ts +13 -10
  146. package/dist/config/persistence.d.ts.map +1 -0
  147. package/dist/config/persistence.js +40 -21
  148. package/dist/config/persistence.js.map +1 -1
  149. package/dist/config/presets.d.ts +1 -0
  150. package/dist/config/presets.d.ts.map +1 -0
  151. package/dist/config/profiles.d.ts +22 -15
  152. package/dist/config/profiles.d.ts.map +1 -0
  153. package/dist/config/profiles.js +81 -47
  154. package/dist/config/profiles.js.map +1 -1
  155. package/dist/config/providerLineups.d.ts +47 -0
  156. package/dist/config/providerLineups.d.ts.map +1 -0
  157. package/dist/config/providerLineups.js +137 -0
  158. package/dist/config/providerLineups.js.map +1 -0
  159. package/dist/config/reactivity.d.ts +84 -0
  160. package/dist/config/reactivity.d.ts.map +1 -0
  161. package/dist/config/reactivity.js +226 -0
  162. package/dist/config/reactivity.js.map +1 -0
  163. package/dist/config/servicePacks.d.ts +18 -2
  164. package/dist/config/servicePacks.d.ts.map +1 -0
  165. package/dist/config/servicePacks.js +53 -11
  166. package/dist/config/servicePacks.js.map +1 -1
  167. package/dist/config/services.d.ts +21 -15
  168. package/dist/config/services.d.ts.map +1 -0
  169. package/dist/config/services.js +35 -24
  170. package/dist/config/services.js.map +1 -1
  171. package/dist/config/sites.d.ts +6 -4
  172. package/dist/config/sites.d.ts.map +1 -0
  173. package/dist/config/sites.js +15 -11
  174. package/dist/config/sites.js.map +1 -1
  175. package/dist/config/userChannels.d.ts +129 -30
  176. package/dist/config/userChannels.d.ts.map +1 -0
  177. package/dist/config/userChannels.js +333 -68
  178. package/dist/config/userChannels.js.map +1 -1
  179. package/dist/config/userConfig.d.ts +14 -9
  180. package/dist/config/userConfig.d.ts.map +1 -0
  181. package/dist/config/userConfig.js +87 -25
  182. package/dist/config/userConfig.js.map +1 -1
  183. package/dist/config/userProfiles.d.ts +10 -6
  184. package/dist/config/userProfiles.d.ts.map +1 -0
  185. package/dist/config/userProfiles.js +64 -39
  186. package/dist/config/userProfiles.js.map +1 -1
  187. package/dist/hdhr/channelMap.d.ts +1 -0
  188. package/dist/hdhr/channelMap.d.ts.map +1 -0
  189. package/dist/hdhr/deviceId.d.ts +1 -0
  190. package/dist/hdhr/deviceId.d.ts.map +1 -0
  191. package/dist/hdhr/discover.d.ts +1 -0
  192. package/dist/hdhr/discover.d.ts.map +1 -0
  193. package/dist/hdhr/discover.js +48 -47
  194. package/dist/hdhr/discover.js.map +1 -1
  195. package/dist/hdhr/getHandlers.d.ts +29 -0
  196. package/dist/hdhr/getHandlers.d.ts.map +1 -0
  197. package/dist/hdhr/getHandlers.js +196 -0
  198. package/dist/hdhr/getHandlers.js.map +1 -0
  199. package/dist/hdhr/identity.d.ts +36 -0
  200. package/dist/hdhr/identity.d.ts.map +1 -0
  201. package/dist/hdhr/identity.js +51 -0
  202. package/dist/hdhr/identity.js.map +1 -0
  203. package/dist/hdhr/index.d.ts +22 -4
  204. package/dist/hdhr/index.d.ts.map +1 -0
  205. package/dist/hdhr/index.js +229 -54
  206. package/dist/hdhr/index.js.map +1 -1
  207. package/dist/hdhr/protocol.d.ts +75 -0
  208. package/dist/hdhr/protocol.d.ts.map +1 -0
  209. package/dist/hdhr/protocol.js +264 -0
  210. package/dist/hdhr/protocol.js.map +1 -0
  211. package/dist/hdhr/tunerState.d.ts +24 -0
  212. package/dist/hdhr/tunerState.d.ts.map +1 -0
  213. package/dist/hdhr/tunerState.js +67 -0
  214. package/dist/hdhr/tunerState.js.map +1 -0
  215. package/dist/hdhr/udp.d.ts +43 -0
  216. package/dist/hdhr/udp.d.ts.map +1 -0
  217. package/dist/hdhr/udp.js +277 -0
  218. package/dist/hdhr/udp.js.map +1 -0
  219. package/dist/identity.d.ts +1 -0
  220. package/dist/identity.d.ts.map +1 -0
  221. package/dist/identity.js +13 -11
  222. package/dist/identity.js.map +1 -1
  223. package/dist/index.d.ts +1 -0
  224. package/dist/index.d.ts.map +1 -0
  225. package/dist/index.js +11 -12
  226. package/dist/index.js.map +1 -1
  227. package/dist/native/codecInference.d.ts +1 -0
  228. package/dist/native/codecInference.d.ts.map +1 -0
  229. package/dist/native/codecInference.js +50 -5
  230. package/dist/native/codecInference.js.map +1 -1
  231. package/dist/native/decrypt.d.ts +6 -2
  232. package/dist/native/decrypt.d.ts.map +1 -0
  233. package/dist/native/decrypt.js +15 -8
  234. package/dist/native/decrypt.js.map +1 -1
  235. package/dist/native/index.d.ts +40 -17
  236. package/dist/native/index.d.ts.map +1 -0
  237. package/dist/native/index.js +281 -120
  238. package/dist/native/index.js.map +1 -1
  239. package/dist/native/probe.d.ts +114 -18
  240. package/dist/native/probe.d.ts.map +1 -0
  241. package/dist/native/probe.js +452 -115
  242. package/dist/native/probe.js.map +1 -1
  243. package/dist/native/proxy.d.ts +57 -5
  244. package/dist/native/proxy.d.ts.map +1 -0
  245. package/dist/native/proxy.js +604 -170
  246. package/dist/native/proxy.js.map +1 -1
  247. package/dist/native/tokenExpiry.d.ts +1 -0
  248. package/dist/native/tokenExpiry.d.ts.map +1 -0
  249. package/dist/native/tokenExpiry.js +1 -1
  250. package/dist/routes/assets.d.ts +1 -0
  251. package/dist/routes/assets.d.ts.map +1 -0
  252. package/dist/routes/assets.js +4 -2
  253. package/dist/routes/assets.js.map +1 -1
  254. package/dist/routes/auth.d.ts +1 -0
  255. package/dist/routes/auth.d.ts.map +1 -0
  256. package/dist/routes/auth.js +0 -1
  257. package/dist/routes/auth.js.map +1 -1
  258. package/dist/routes/cdp.d.ts +5 -5
  259. package/dist/routes/cdp.d.ts.map +1 -0
  260. package/dist/routes/cdp.js +59 -20
  261. package/dist/routes/cdp.js.map +1 -1
  262. package/dist/routes/channels.d.ts +1 -0
  263. package/dist/routes/channels.d.ts.map +1 -0
  264. package/dist/routes/channels.js +2 -1
  265. package/dist/routes/channels.js.map +1 -1
  266. package/dist/routes/clientActions.d.ts +98 -0
  267. package/dist/routes/clientActions.d.ts.map +1 -0
  268. package/dist/routes/clientActions.js +113 -0
  269. package/dist/routes/clientActions.js.map +1 -0
  270. package/dist/routes/components.d.ts +5 -3
  271. package/dist/routes/components.d.ts.map +1 -0
  272. package/dist/routes/components.js +36 -39
  273. package/dist/routes/components.js.map +1 -1
  274. package/dist/routes/config/channels/endpoints/browse.d.ts +1 -0
  275. package/dist/routes/config/channels/endpoints/browse.d.ts.map +1 -0
  276. package/dist/routes/config/channels/endpoints/browse.js +23 -10
  277. package/dist/routes/config/channels/endpoints/browse.js.map +1 -1
  278. package/dist/routes/config/channels/endpoints/bulk.d.ts +1 -0
  279. package/dist/routes/config/channels/endpoints/bulk.d.ts.map +1 -0
  280. package/dist/routes/config/channels/endpoints/bulk.js +5 -3
  281. package/dist/routes/config/channels/endpoints/bulk.js.map +1 -1
  282. package/dist/routes/config/channels/endpoints/crud.d.ts +1 -0
  283. package/dist/routes/config/channels/endpoints/crud.d.ts.map +1 -0
  284. package/dist/routes/config/channels/endpoints/crud.js +85 -93
  285. package/dist/routes/config/channels/endpoints/crud.js.map +1 -1
  286. package/dist/routes/config/channels/endpoints/importExport.d.ts +1 -0
  287. package/dist/routes/config/channels/endpoints/importExport.d.ts.map +1 -0
  288. package/dist/routes/config/channels/endpoints/importExport.js +6 -3
  289. package/dist/routes/config/channels/endpoints/importExport.js.map +1 -1
  290. package/dist/routes/config/channels/endpoints/predefined.d.ts +1 -0
  291. package/dist/routes/config/channels/endpoints/predefined.d.ts.map +1 -0
  292. package/dist/routes/config/channels/endpoints/prefs.d.ts +1 -0
  293. package/dist/routes/config/channels/endpoints/prefs.d.ts.map +1 -0
  294. package/dist/routes/config/channels/endpoints/prefs.js +2 -1
  295. package/dist/routes/config/channels/endpoints/prefs.js.map +1 -1
  296. package/dist/routes/config/channels/endpoints/service.d.ts +2 -1
  297. package/dist/routes/config/channels/endpoints/service.d.ts.map +1 -0
  298. package/dist/routes/config/channels/endpoints/service.js +3 -1
  299. package/dist/routes/config/channels/endpoints/service.js.map +1 -1
  300. package/dist/routes/config/channels/endpoints/tags.d.ts +1 -0
  301. package/dist/routes/config/channels/endpoints/tags.d.ts.map +1 -0
  302. package/dist/routes/config/channels/endpoints/tags.js +13 -7
  303. package/dist/routes/config/channels/endpoints/tags.js.map +1 -1
  304. package/dist/routes/config/channels/healthBridge.d.ts +24 -0
  305. package/dist/routes/config/channels/healthBridge.d.ts.map +1 -0
  306. package/dist/routes/config/channels/healthBridge.js +71 -0
  307. package/dist/routes/config/channels/healthBridge.js.map +1 -0
  308. package/dist/routes/config/channels/http/handler.d.ts +1 -0
  309. package/dist/routes/config/channels/http/handler.d.ts.map +1 -0
  310. package/dist/routes/config/channels/http/playlistHint.d.ts +2 -1
  311. package/dist/routes/config/channels/http/playlistHint.d.ts.map +1 -0
  312. package/dist/routes/config/channels/http/playlistHint.js +16 -2
  313. package/dist/routes/config/channels/http/playlistHint.js.map +1 -1
  314. package/dist/routes/config/channels/http/serviceWarning.d.ts +4 -2
  315. package/dist/routes/config/channels/http/serviceWarning.d.ts.map +1 -0
  316. package/dist/routes/config/channels/http/serviceWarning.js +3 -2
  317. package/dist/routes/config/channels/http/serviceWarning.js.map +1 -1
  318. package/dist/routes/config/channels/index.d.ts +1 -0
  319. package/dist/routes/config/channels/index.d.ts.map +1 -0
  320. package/dist/routes/config/channels/setup.d.ts +1 -0
  321. package/dist/routes/config/channels/setup.d.ts.map +1 -0
  322. package/dist/routes/config/channels/table.d.ts +3 -3
  323. package/dist/routes/config/channels/table.d.ts.map +1 -0
  324. package/dist/routes/config/channels/table.js +148 -107
  325. package/dist/routes/config/channels/table.js.map +1 -1
  326. package/dist/routes/config/http/envelope.d.ts +1 -0
  327. package/dist/routes/config/http/envelope.d.ts.map +1 -0
  328. package/dist/routes/config/index.d.ts +35 -3
  329. package/dist/routes/config/index.d.ts.map +1 -0
  330. package/dist/routes/config/index.js +51 -2
  331. package/dist/routes/config/index.js.map +1 -1
  332. package/dist/routes/config/services.d.ts +5 -4
  333. package/dist/routes/config/services.d.ts.map +1 -0
  334. package/dist/routes/config/services.js +57 -43
  335. package/dist/routes/config/services.js.map +1 -1
  336. package/dist/routes/config/settings.d.ts +1 -0
  337. package/dist/routes/config/settings.d.ts.map +1 -0
  338. package/dist/routes/config/settings.js +98 -49
  339. package/dist/routes/config/settings.js.map +1 -1
  340. package/dist/routes/debug.d.ts +1 -0
  341. package/dist/routes/debug.d.ts.map +1 -0
  342. package/dist/routes/debug.js +277 -139
  343. package/dist/routes/debug.js.map +1 -1
  344. package/dist/routes/health.d.ts +39 -1
  345. package/dist/routes/health.d.ts.map +1 -0
  346. package/dist/routes/health.js +33 -27
  347. package/dist/routes/health.js.map +1 -1
  348. package/dist/routes/hls.d.ts +1 -0
  349. package/dist/routes/hls.d.ts.map +1 -0
  350. package/dist/routes/icons.d.ts +1 -0
  351. package/dist/routes/icons.d.ts.map +1 -0
  352. package/dist/routes/icons.js +1 -1
  353. package/dist/routes/index.d.ts +1 -0
  354. package/dist/routes/index.d.ts.map +1 -0
  355. package/dist/routes/logs.d.ts +1 -0
  356. package/dist/routes/logs.d.ts.map +1 -0
  357. package/dist/routes/logs.js +14 -4
  358. package/dist/routes/logs.js.map +1 -1
  359. package/dist/routes/mpegts.d.ts +1 -0
  360. package/dist/routes/mpegts.d.ts.map +1 -0
  361. package/dist/routes/play.d.ts +1 -0
  362. package/dist/routes/play.d.ts.map +1 -0
  363. package/dist/routes/playlist.d.ts +1 -0
  364. package/dist/routes/playlist.d.ts.map +1 -0
  365. package/dist/routes/playlist.js +7 -2
  366. package/dist/routes/playlist.js.map +1 -1
  367. package/dist/routes/root/content.d.ts +8 -5
  368. package/dist/routes/root/content.d.ts.map +1 -0
  369. package/dist/routes/root/content.js +29 -26
  370. package/dist/routes/root/content.js.map +1 -1
  371. package/dist/routes/root/index.d.ts +2 -1
  372. package/dist/routes/root/index.d.ts.map +1 -0
  373. package/dist/routes/root/index.js +26 -21
  374. package/dist/routes/root/index.js.map +1 -1
  375. package/dist/routes/root/scripts/channels.d.ts +6 -0
  376. package/dist/routes/root/scripts/channels.d.ts.map +1 -0
  377. package/dist/routes/root/scripts/channels.js +197 -118
  378. package/dist/routes/root/scripts/channels.js.map +1 -1
  379. package/dist/routes/root/scripts/clientEscape.d.ts +22 -0
  380. package/dist/routes/root/scripts/clientEscape.d.ts.map +1 -0
  381. package/dist/routes/root/scripts/clientEscape.js +41 -0
  382. package/dist/routes/root/scripts/clientEscape.js.map +1 -0
  383. package/dist/routes/root/scripts/clientUrl.d.ts +23 -0
  384. package/dist/routes/root/scripts/clientUrl.d.ts.map +1 -0
  385. package/dist/routes/root/scripts/clientUrl.js +44 -0
  386. package/dist/routes/root/scripts/clientUrl.js.map +1 -0
  387. package/dist/routes/root/scripts/config.d.ts +6 -0
  388. package/dist/routes/root/scripts/config.d.ts.map +1 -0
  389. package/dist/routes/root/scripts/config.js +110 -24
  390. package/dist/routes/root/scripts/config.js.map +1 -1
  391. package/dist/routes/root/scripts/index.d.ts +1 -0
  392. package/dist/routes/root/scripts/index.d.ts.map +1 -0
  393. package/dist/routes/root/scripts/shared.d.ts +1 -0
  394. package/dist/routes/root/scripts/shared.d.ts.map +1 -0
  395. package/dist/routes/root/scripts/shared.js +112 -26
  396. package/dist/routes/root/scripts/shared.js.map +1 -1
  397. package/dist/routes/root/scripts/status.d.ts +1 -0
  398. package/dist/routes/root/scripts/status.d.ts.map +1 -0
  399. package/dist/routes/root/scripts/status.handlers.d.ts +23 -33
  400. package/dist/routes/root/scripts/status.handlers.d.ts.map +1 -0
  401. package/dist/routes/root/scripts/status.handlers.js +46 -92
  402. package/dist/routes/root/scripts/status.handlers.js.map +1 -1
  403. package/dist/routes/root/scripts/status.js +21 -8
  404. package/dist/routes/root/scripts/status.js.map +1 -1
  405. package/dist/routes/root/styles.d.ts +1 -0
  406. package/dist/routes/root/styles.d.ts.map +1 -0
  407. package/dist/routes/root/styles.js +7 -4
  408. package/dist/routes/root/styles.js.map +1 -1
  409. package/dist/routes/services.d.ts +13 -1
  410. package/dist/routes/services.d.ts.map +1 -0
  411. package/dist/routes/services.js +81 -68
  412. package/dist/routes/services.js.map +1 -1
  413. package/dist/routes/sse.d.ts +1 -0
  414. package/dist/routes/sse.d.ts.map +1 -0
  415. package/dist/routes/sse.js +1 -1
  416. package/dist/routes/sse.js.map +1 -1
  417. package/dist/routes/streams.d.ts +2 -1
  418. package/dist/routes/streams.d.ts.map +1 -0
  419. package/dist/routes/streams.js +6 -4
  420. package/dist/routes/streams.js.map +1 -1
  421. package/dist/routes/theme.d.ts +3 -2
  422. package/dist/routes/theme.d.ts.map +1 -0
  423. package/dist/routes/theme.js +6 -5
  424. package/dist/routes/theme.js.map +1 -1
  425. package/dist/routes/ui.d.ts +3 -1
  426. package/dist/routes/ui.d.ts.map +1 -0
  427. package/dist/routes/ui.js +16 -4
  428. package/dist/routes/ui.js.map +1 -1
  429. package/dist/routes/upgrade.d.ts +1 -0
  430. package/dist/routes/upgrade.d.ts.map +1 -0
  431. package/dist/routes/upgrade.js +6 -2
  432. package/dist/routes/upgrade.js.map +1 -1
  433. package/dist/service/commands.context.d.ts +1 -0
  434. package/dist/service/commands.context.d.ts.map +1 -0
  435. package/dist/service/commands.context.js +4 -3
  436. package/dist/service/commands.context.js.map +1 -1
  437. package/dist/service/commands.d.ts +1 -0
  438. package/dist/service/commands.d.ts.map +1 -0
  439. package/dist/service/commands.js +10 -13
  440. package/dist/service/commands.js.map +1 -1
  441. package/dist/service/generators.context.d.ts +1 -0
  442. package/dist/service/generators.context.d.ts.map +1 -0
  443. package/dist/service/generators.context.js +1 -1
  444. package/dist/service/generators.context.js.map +1 -1
  445. package/dist/service/generators.d.ts +4 -3
  446. package/dist/service/generators.d.ts.map +1 -0
  447. package/dist/service/generators.js +111 -31
  448. package/dist/service/generators.js.map +1 -1
  449. package/dist/service/index.d.ts +1 -0
  450. package/dist/service/index.d.ts.map +1 -0
  451. package/dist/streaming/captureLock.d.ts +44 -0
  452. package/dist/streaming/captureLock.d.ts.map +1 -0
  453. package/dist/streaming/captureLock.js +95 -0
  454. package/dist/streaming/captureLock.js.map +1 -0
  455. package/dist/streaming/captureSession.d.ts +31 -0
  456. package/dist/streaming/captureSession.d.ts.map +1 -0
  457. package/dist/streaming/captureSession.js +54 -0
  458. package/dist/streaming/captureSession.js.map +1 -0
  459. package/dist/streaming/clients.d.ts +1 -0
  460. package/dist/streaming/clients.d.ts.map +1 -0
  461. package/dist/streaming/codec.d.ts +1 -0
  462. package/dist/streaming/codec.d.ts.map +1 -0
  463. package/dist/streaming/fmp4Segmenter.d.ts +29 -2
  464. package/dist/streaming/fmp4Segmenter.d.ts.map +1 -0
  465. package/dist/streaming/fmp4Segmenter.js +79 -26
  466. package/dist/streaming/fmp4Segmenter.js.map +1 -1
  467. package/dist/streaming/hls.d.ts +18 -7
  468. package/dist/streaming/hls.d.ts.map +1 -0
  469. package/dist/streaming/hls.js +220 -120
  470. package/dist/streaming/hls.js.map +1 -1
  471. package/dist/streaming/hlsResume.d.ts +1 -0
  472. package/dist/streaming/hlsResume.d.ts.map +1 -0
  473. package/dist/streaming/hlsResume.js +1 -1
  474. package/dist/streaming/hlsResume.js.map +1 -1
  475. package/dist/streaming/hlsSegments.d.ts +59 -2
  476. package/dist/streaming/hlsSegments.d.ts.map +1 -0
  477. package/dist/streaming/hlsSegments.js +147 -17
  478. package/dist/streaming/hlsSegments.js.map +1 -1
  479. package/dist/streaming/lifecycle.d.ts +4 -1
  480. package/dist/streaming/lifecycle.d.ts.map +1 -0
  481. package/dist/streaming/lifecycle.js +65 -85
  482. package/dist/streaming/lifecycle.js.map +1 -1
  483. package/dist/streaming/monitor.d.ts +6 -4
  484. package/dist/streaming/monitor.d.ts.map +1 -0
  485. package/dist/streaming/monitor.js +343 -145
  486. package/dist/streaming/monitor.js.map +1 -1
  487. package/dist/streaming/mp4Parser.d.ts +5 -4
  488. package/dist/streaming/mp4Parser.d.ts.map +1 -0
  489. package/dist/streaming/mp4Parser.js +36 -13
  490. package/dist/streaming/mp4Parser.js.map +1 -1
  491. package/dist/streaming/mpegts.d.ts +16 -0
  492. package/dist/streaming/mpegts.d.ts.map +1 -0
  493. package/dist/streaming/mpegts.js +61 -17
  494. package/dist/streaming/mpegts.js.map +1 -1
  495. package/dist/streaming/playlistBuilder.d.ts +1 -0
  496. package/dist/streaming/playlistBuilder.d.ts.map +1 -0
  497. package/dist/streaming/preroll.d.ts +32 -5
  498. package/dist/streaming/preroll.d.ts.map +1 -0
  499. package/dist/streaming/preroll.js +70 -32
  500. package/dist/streaming/preroll.js.map +1 -1
  501. package/dist/streaming/pretune.d.ts +26 -1
  502. package/dist/streaming/pretune.d.ts.map +1 -0
  503. package/dist/streaming/pretune.js +22 -24
  504. package/dist/streaming/pretune.js.map +1 -1
  505. package/dist/streaming/pretuneTimers.d.ts +24 -0
  506. package/dist/streaming/pretuneTimers.d.ts.map +1 -0
  507. package/dist/streaming/pretuneTimers.js +56 -0
  508. package/dist/streaming/pretuneTimers.js.map +1 -0
  509. package/dist/streaming/recovery.d.ts +139 -5
  510. package/dist/streaming/recovery.d.ts.map +1 -0
  511. package/dist/streaming/recovery.js +160 -16
  512. package/dist/streaming/recovery.js.map +1 -1
  513. package/dist/streaming/registry.d.ts +27 -9
  514. package/dist/streaming/registry.d.ts.map +1 -0
  515. package/dist/streaming/registry.js +21 -3
  516. package/dist/streaming/registry.js.map +1 -1
  517. package/dist/streaming/setup.d.ts +157 -29
  518. package/dist/streaming/setup.d.ts.map +1 -0
  519. package/dist/streaming/setup.js +1034 -458
  520. package/dist/streaming/setup.js.map +1 -1
  521. package/dist/streaming/showInfo.d.ts +21 -1
  522. package/dist/streaming/showInfo.d.ts.map +1 -0
  523. package/dist/streaming/showInfo.js +31 -16
  524. package/dist/streaming/showInfo.js.map +1 -1
  525. package/dist/streaming/statusEmitter.d.ts +10 -10
  526. package/dist/streaming/statusEmitter.d.ts.map +1 -0
  527. package/dist/streaming/statusEmitter.js +5 -8
  528. package/dist/streaming/statusEmitter.js.map +1 -1
  529. package/dist/types/channels.d.ts +16 -4
  530. package/dist/types/channels.d.ts.map +1 -0
  531. package/dist/types/channels.js +4 -2
  532. package/dist/types/channels.js.map +1 -1
  533. package/dist/types/config.d.ts +16 -5
  534. package/dist/types/config.d.ts.map +1 -0
  535. package/dist/types/index.d.ts +3 -2
  536. package/dist/types/index.d.ts.map +1 -0
  537. package/dist/types/profiles.d.ts +6 -4
  538. package/dist/types/profiles.d.ts.map +1 -0
  539. package/dist/types/selection.d.ts +31 -3
  540. package/dist/types/selection.d.ts.map +1 -0
  541. package/dist/types/selection.js +4 -2
  542. package/dist/types/selection.js.map +1 -1
  543. package/dist/types/shared.d.ts +1 -0
  544. package/dist/types/shared.d.ts.map +1 -0
  545. package/dist/types/streaming.d.ts +8 -0
  546. package/dist/types/streaming.d.ts.map +1 -0
  547. package/dist/upgrade/commands.context.d.ts +1 -0
  548. package/dist/upgrade/commands.context.d.ts.map +1 -0
  549. package/dist/upgrade/commands.context.js +14 -10
  550. package/dist/upgrade/commands.context.js.map +1 -1
  551. package/dist/upgrade/commands.d.ts +8 -15
  552. package/dist/upgrade/commands.d.ts.map +1 -0
  553. package/dist/upgrade/commands.js +27 -28
  554. package/dist/upgrade/commands.js.map +1 -1
  555. package/dist/upgrade/detection.context.d.ts +1 -0
  556. package/dist/upgrade/detection.context.d.ts.map +1 -0
  557. package/dist/upgrade/detection.context.js +4 -1
  558. package/dist/upgrade/detection.context.js.map +1 -1
  559. package/dist/upgrade/detection.d.ts +9 -7
  560. package/dist/upgrade/detection.d.ts.map +1 -0
  561. package/dist/upgrade/detection.js +23 -54
  562. package/dist/upgrade/detection.js.map +1 -1
  563. package/dist/upgrade/index.d.ts +2 -0
  564. package/dist/upgrade/index.d.ts.map +1 -0
  565. package/dist/upgrade/index.js +1 -0
  566. package/dist/upgrade/index.js.map +1 -1
  567. package/dist/upgrade/lifecycle.context.d.ts +20 -0
  568. package/dist/upgrade/lifecycle.context.d.ts.map +1 -0
  569. package/dist/upgrade/lifecycle.context.js +47 -0
  570. package/dist/upgrade/lifecycle.context.js.map +1 -0
  571. package/dist/upgrade/lifecycle.d.ts +72 -0
  572. package/dist/upgrade/lifecycle.d.ts.map +1 -0
  573. package/dist/upgrade/lifecycle.js +175 -0
  574. package/dist/upgrade/lifecycle.js.map +1 -0
  575. package/dist/upgrade/pathHandle.d.ts +30 -0
  576. package/dist/upgrade/pathHandle.d.ts.map +1 -0
  577. package/dist/upgrade/pathHandle.js +102 -0
  578. package/dist/upgrade/pathHandle.js.map +1 -0
  579. package/dist/utils/bootSession.context.d.ts +16 -0
  580. package/dist/utils/bootSession.context.d.ts.map +1 -0
  581. package/dist/utils/bootSession.context.js +60 -0
  582. package/dist/utils/bootSession.context.js.map +1 -0
  583. package/dist/utils/bootSession.d.ts +17 -0
  584. package/dist/utils/bootSession.d.ts.map +1 -0
  585. package/dist/utils/bootSession.js +20 -0
  586. package/dist/utils/bootSession.js.map +1 -0
  587. package/dist/utils/chromeFetch.d.ts +1 -0
  588. package/dist/utils/chromeFetch.d.ts.map +1 -0
  589. package/dist/utils/cliOutput.d.ts +1 -0
  590. package/dist/utils/cliOutput.d.ts.map +1 -0
  591. package/dist/utils/clock.d.ts +5 -4
  592. package/dist/utils/clock.d.ts.map +1 -0
  593. package/dist/utils/clock.js +19 -16
  594. package/dist/utils/clock.js.map +1 -1
  595. package/dist/utils/debugFilter.d.ts +9 -1
  596. package/dist/utils/debugFilter.d.ts.map +1 -0
  597. package/dist/utils/debugFilter.js +60 -33
  598. package/dist/utils/debugFilter.js.map +1 -1
  599. package/dist/utils/delay.d.ts +48 -15
  600. package/dist/utils/delay.d.ts.map +1 -0
  601. package/dist/utils/delay.js +73 -20
  602. package/dist/utils/delay.js.map +1 -1
  603. package/dist/utils/errors.d.ts +32 -0
  604. package/dist/utils/errors.d.ts.map +1 -0
  605. package/dist/utils/errors.js +45 -4
  606. package/dist/utils/errors.js.map +1 -1
  607. package/dist/utils/evaluate.d.ts +1 -0
  608. package/dist/utils/evaluate.d.ts.map +1 -0
  609. package/dist/utils/evaluate.js +29 -23
  610. package/dist/utils/evaluate.js.map +1 -1
  611. package/dist/utils/ffmpeg.context.d.ts +1 -0
  612. package/dist/utils/ffmpeg.context.d.ts.map +1 -0
  613. package/dist/utils/ffmpeg.context.js.map +1 -1
  614. package/dist/utils/ffmpeg.d.ts +29 -7
  615. package/dist/utils/ffmpeg.d.ts.map +1 -0
  616. package/dist/utils/ffmpeg.js +32 -14
  617. package/dist/utils/ffmpeg.js.map +1 -1
  618. package/dist/utils/fileLogger.d.ts +3 -1
  619. package/dist/utils/fileLogger.d.ts.map +1 -0
  620. package/dist/utils/fileLogger.js +64 -29
  621. package/dist/utils/fileLogger.js.map +1 -1
  622. package/dist/utils/format.d.ts +14 -1
  623. package/dist/utils/format.d.ts.map +1 -0
  624. package/dist/utils/format.js +19 -5
  625. package/dist/utils/format.js.map +1 -1
  626. package/dist/utils/index.d.ts +4 -0
  627. package/dist/utils/index.d.ts.map +1 -0
  628. package/dist/utils/index.js +3 -0
  629. package/dist/utils/index.js.map +1 -1
  630. package/dist/utils/logEmitter.d.ts +1 -0
  631. package/dist/utils/logEmitter.d.ts.map +1 -0
  632. package/dist/utils/logger.d.ts +1 -0
  633. package/dist/utils/logger.d.ts.map +1 -0
  634. package/dist/utils/logger.js +10 -6
  635. package/dist/utils/logger.js.map +1 -1
  636. package/dist/utils/m3u.d.ts +3 -2
  637. package/dist/utils/m3u.d.ts.map +1 -0
  638. package/dist/utils/m3u.js +4 -3
  639. package/dist/utils/m3u.js.map +1 -1
  640. package/dist/utils/markup.d.ts +13 -1
  641. package/dist/utils/markup.d.ts.map +1 -0
  642. package/dist/utils/markup.js +28 -4
  643. package/dist/utils/markup.js.map +1 -1
  644. package/dist/utils/memo.d.ts +3 -2
  645. package/dist/utils/memo.d.ts.map +1 -0
  646. package/dist/utils/memo.js +3 -3
  647. package/dist/utils/morganStream.d.ts +1 -0
  648. package/dist/utils/morganStream.d.ts.map +1 -0
  649. package/dist/utils/network.d.ts +1 -0
  650. package/dist/utils/network.d.ts.map +1 -0
  651. package/dist/utils/network.js +2 -2
  652. package/dist/utils/pid.d.ts +3 -25
  653. package/dist/utils/pid.d.ts.map +1 -0
  654. package/dist/utils/pid.js +10 -49
  655. package/dist/utils/pid.js.map +1 -1
  656. package/dist/utils/platform.d.ts +3 -1
  657. package/dist/utils/platform.d.ts.map +1 -0
  658. package/dist/utils/platform.js +2 -1
  659. package/dist/utils/platform.js.map +1 -1
  660. package/dist/utils/processInspector.context.d.ts +8 -0
  661. package/dist/utils/processInspector.context.d.ts.map +1 -0
  662. package/dist/utils/processInspector.context.js +106 -0
  663. package/dist/utils/processInspector.context.js.map +1 -0
  664. package/dist/utils/processInspector.d.ts +55 -0
  665. package/dist/utils/processInspector.d.ts.map +1 -0
  666. package/dist/utils/processInspector.js +127 -0
  667. package/dist/utils/processInspector.js.map +1 -0
  668. package/dist/utils/retry.d.ts +25 -2
  669. package/dist/utils/retry.d.ts.map +1 -0
  670. package/dist/utils/retry.js +33 -6
  671. package/dist/utils/retry.js.map +1 -1
  672. package/dist/utils/runtimeIdentity.context.d.ts +7 -0
  673. package/dist/utils/runtimeIdentity.context.d.ts.map +1 -0
  674. package/dist/utils/runtimeIdentity.context.js +43 -0
  675. package/dist/utils/runtimeIdentity.context.js.map +1 -0
  676. package/dist/utils/runtimeIdentity.d.ts +98 -0
  677. package/dist/utils/runtimeIdentity.d.ts.map +1 -0
  678. package/dist/utils/runtimeIdentity.js +181 -0
  679. package/dist/utils/runtimeIdentity.js.map +1 -0
  680. package/dist/utils/sanitize.d.ts +1 -0
  681. package/dist/utils/sanitize.d.ts.map +1 -0
  682. package/dist/utils/streamContext.d.ts +2 -1
  683. package/dist/utils/streamContext.d.ts.map +1 -0
  684. package/dist/utils/streamContext.js +1 -1
  685. package/dist/utils/timing.d.ts +2 -1
  686. package/dist/utils/timing.d.ts.map +1 -0
  687. package/dist/utils/timing.js +0 -5
  688. package/dist/utils/timing.js.map +1 -1
  689. package/dist/utils/version.d.ts +1 -0
  690. package/dist/utils/version.d.ts.map +1 -0
  691. package/dist/utils/version.js +47 -13
  692. package/dist/utils/version.js.map +1 -1
  693. package/package.json +26 -20
@@ -1,16 +1,22 @@
1
1
  import { LOG, delay, evaluateWithAbort, formatError } from "../../utils/index.js";
2
- import { logAvailableChannels, normalizeChannelName, scrollAndClick } from "./shared.js";
2
+ import { installOrReplaceOnNewDocument, logAvailableChannels, normalizeChannelName, scrollAndClick } from "./shared.js";
3
3
  import { CONFIG } from "../../config/index.js";
4
+ import { createProviderChannelCache } from "./cache.js";
5
+ import { searchVirtualizedGrid } from "./gridSearch.js";
4
6
  // Unified channel cache for Hulu. Maps normalized channel names to their combined entry. Aliases (e.g., "abc" -> same entry as "wls") share object references for
5
- // automatic propagation of fresh programs and EAB data. Cleared on browser disconnect via clearHuluCache().
6
- const huluChannelCache = new Map();
7
+ // automatic propagation of fresh programs and EAB data. Cleared on browser disconnect via clearHuluCache(). An affiliate projects into discovery output with its
8
+ // network name as the channelSelector; every other channel projects with its display name.
9
+ const huluCache = createProviderChannelCache((entry) => {
10
+ const result = { channelSelector: entry.affiliate ?? entry.displayName, name: entry.displayName };
11
+ if (entry.affiliate) {
12
+ result.affiliate = entry.affiliate;
13
+ }
14
+ return result;
15
+ });
7
16
  // Transient staging map for listing API response data. Maps channel UUIDs to their program schedules. Populated from listing API responses (which arrive before
8
17
  // details responses due to the interceptor's listingCapturedPromise hold). Read by populateHuluChannelCache when the details response arrives, joining UUID-keyed
9
18
  // programs with name-keyed entries. Also used to propagate fresh programs to existing cache entries when a new listing response arrives.
10
19
  const huluListingStaging = new Map();
11
- // Tracks whether a full discovery walk (with affiliate position inference) has completed. When true, buildHuluDiscoveredChannels can return comprehensive results
12
- // including proper affiliate labeling. When false, getCachedChannels returns null to force a fresh discovery walk.
13
- let huluFullyDiscovered = false;
14
20
  // Tracks pages with details API response listeners to avoid duplicate registration. Mirrors the pagesWithListeners pattern in sling.ts.
15
21
  const huluPagesWithListeners = new WeakSet();
16
22
  // The Hulu live page URL. The evaluateOnNewDocument fetch interceptor swaps channel_id and content_eab_id in the playlist API request, making the app play the
@@ -18,12 +24,11 @@ const huluPagesWithListeners = new WeakSet();
18
24
  // grid's Channels tab click triggers full API expansion, and the interceptor captures UUID+EAB from those expanded responses.
19
25
  const HULU_LIVE_URL = "https://www.hulu.com/live";
20
26
  /**
21
- * Clears all Hulu caches: the unified channel cache, listing staging map, and discovery flag. Called by clearChannelSelectionCaches() in the coordinator when the
22
- * browser restarts, since cached state may be stale in a new browser session.
27
+ * Clears all Hulu caches: the unified channel cache along with its completeness mark, and the listing staging map. Called by clearChannelSelectionCaches() in the
28
+ * coordinator when the browser restarts, since cached state may be stale in a new browser session.
23
29
  */
24
30
  function clearHuluCache() {
25
- huluChannelCache.clear();
26
- huluFullyDiscovered = false;
31
+ huluCache.clear();
27
32
  huluListingStaging.clear();
28
33
  }
29
34
  /**
@@ -34,13 +39,13 @@ function clearHuluCache() {
34
39
  * @returns The actual cache key, or null if no match found.
35
40
  */
36
41
  function resolveHuluCacheKey(normalizedName) {
37
- if (huluChannelCache.has(normalizedName)) {
42
+ if (huluCache.map.has(normalizedName)) {
38
43
  return normalizedName;
39
44
  }
40
45
  // Fuzzy fallback: strip all non-alphanumeric characters and compare. Handles formatting differences in spaces, hyphens, ampersands, and other punctuation
41
46
  // between channelSelector definitions and Hulu's Details API channel_info.name values.
42
47
  const stripped = normalizedName.replace(/[^a-z0-9]/g, "");
43
- for (const key of huluChannelCache.keys()) {
48
+ for (const key of huluCache.map.keys()) {
44
49
  if (key.replace(/[^a-z0-9]/g, "") === stripped) {
45
50
  return key;
46
51
  }
@@ -49,13 +54,13 @@ function resolveHuluCacheKey(normalizedName) {
49
54
  }
50
55
  /**
51
56
  * Looks up a channel in the unified cache by normalized name, with fuzzy fallback via resolveHuluCacheKey. Only used for lookups keyed by channelSelector -
52
- * internal cache operations that use API-derived names should use huluChannelCache.get() directly.
57
+ * internal cache operations that use API-derived names should use huluCache.map.get() directly.
53
58
  * @param normalizedName - The normalized (lowercased, whitespace-collapsed) channel name to look up.
54
59
  * @returns The matching cache entry, or null if no match found.
55
60
  */
56
61
  function findHuluChannelEntry(normalizedName) {
57
62
  const key = resolveHuluCacheKey(normalizedName);
58
- return key ? huluChannelCache.get(key) ?? null : null;
63
+ return key ? huluCache.map.get(key) ?? null : null;
59
64
  }
60
65
  /**
61
66
  * Populates the unified channel cache from a details API response. For each item with channel_info, creates or updates the entry keyed by normalized name,
@@ -69,7 +74,7 @@ function populateHuluChannelCache(items) {
69
74
  const info = item.channel_info;
70
75
  if (info?.name && info.id) {
71
76
  const normalized = normalizeChannelName(info.name);
72
- const existing = huluChannelCache.get(normalized);
77
+ const existing = huluCache.map.get(normalized);
73
78
  const programs = huluListingStaging.get(info.id) ?? existing?.programs;
74
79
  if (existing) {
75
80
  // Update in-place to preserve alias references and supplementary fields (rowNumber, affiliate).
@@ -80,12 +85,12 @@ function populateHuluChannelCache(items) {
80
85
  }
81
86
  }
82
87
  else {
83
- huluChannelCache.set(normalized, { displayName: info.name, programs, uuid: info.id });
88
+ huluCache.map.set(normalized, { displayName: info.name, programs, uuid: info.id });
84
89
  }
85
90
  channelsSeen.add(info.name);
86
91
  }
87
92
  }
88
- LOG.debug("tuning:hulu", "Details API: %s items, %s unique channels. Channel cache size: %s.", items.length, channelsSeen.size, huluChannelCache.size);
93
+ LOG.debug("tuning:hulu", "Details API: %s items, %s unique channels. Channel cache size: %s.", items.length, channelsSeen.size, huluCache.map.size);
89
94
  }
90
95
  /**
91
96
  * Finds the currently-airing EAB from a program schedule array. Searches the programs for one whose airing window brackets the current time. Returns null if the
@@ -102,28 +107,6 @@ function findCurrentEabFromPrograms(programs) {
102
107
  }
103
108
  return null;
104
109
  }
105
- /**
106
- * Derives a DiscoveredChannel array from the unified channel cache, deduplicating alias entries via Set reference equality. Affiliates produce entries with the
107
- * network name as channelSelector; non-affiliates use their display name. Used by getCachedChannels and discoverHuluChannels when returning from warm cache.
108
- * @returns Sorted array of discovered channels.
109
- */
110
- function buildHuluDiscoveredChannels() {
111
- const channels = [];
112
- const seen = new Set();
113
- for (const entry of huluChannelCache.values()) {
114
- if (seen.has(entry)) {
115
- continue;
116
- }
117
- seen.add(entry);
118
- const result = { channelSelector: entry.affiliate ?? entry.displayName, name: entry.displayName };
119
- if (entry.affiliate) {
120
- result.affiliate = entry.affiliate;
121
- }
122
- channels.push(result);
123
- }
124
- channels.sort((a, b) => a.name.localeCompare(b.name));
125
- return channels;
126
- }
127
110
  /**
128
111
  * Reads all rendered channel containers from the guide grid, extracting their names from data-testid attributes and row numbers from sr-only text. Populates the
129
112
  * unified channel cache with rowNumber and displayName as a side effect.
@@ -175,12 +158,12 @@ async function readRenderedChannels(page) {
175
158
  rendered.push({ displayName: ch.displayName, domIndex, name: ch.name, rowNumber: ch.rowNumber });
176
159
  // Cache the row number and display name for future direct-scroll lookups.
177
160
  if (ch.rowNumber >= 0) {
178
- const existing = huluChannelCache.get(ch.name);
161
+ const existing = huluCache.map.get(ch.name);
179
162
  if (existing) {
180
163
  existing.rowNumber = ch.rowNumber;
181
164
  }
182
165
  else {
183
- huluChannelCache.set(ch.name, { displayName: ch.displayName, rowNumber: ch.rowNumber });
166
+ huluCache.map.set(ch.name, { displayName: ch.displayName, rowNumber: ch.rowNumber });
184
167
  }
185
168
  }
186
169
  }
@@ -395,7 +378,7 @@ async function clickOnNowCellAndPlay(page, clickTarget, playSelector, channelNam
395
378
  // Maximum number of on-now cell click attempts. The first click may not register if React hasn't finished hydrating the guide's event handlers.
396
379
  const MAX_CLICK_ATTEMPTS = 3;
397
380
  // Shorter timeout for the play button on non-final attempts. When a click registers, the play button appears in under 10ms - this timeout only determines how
398
- // quickly we detect a missed click and retry. Keeping it low saves ~2s per failed attempt compared to the previous 3000ms value.
381
+ // quickly we detect a missed click and retry. Keeping it low lets a failed attempt fall through to the next one promptly rather than stalling on a dead wait.
399
382
  const RETRY_PLAY_TIMEOUT = 1000;
400
383
  // Delay between click retries. Gives React additional time to finish hydrating event handlers.
401
384
  const CLICK_RETRY_DELAY = 1500;
@@ -498,6 +481,9 @@ async function tryFastPathTune(page, entry, channelName) {
498
481
  const currentEab = findCurrentEabFromPrograms(entry.programs);
499
482
  if (currentEab) {
500
483
  const injected = await page.evaluate((u, e) => {
484
+ // The __prismcast* bridge functions are installed on window by the evaluateOnNewDocument fetch interceptor earlier in this module. The double cast reads
485
+ // them off window without committing to a global declaration, and the typeof === "function" guard below tolerates their absence when the interceptor was
486
+ // not installed (for example on a profile that does not use fetch interception). The same rationale applies to the other reads of these bridge functions.
501
487
  const resolver = window["__prismcastResolveDirectTune"];
502
488
  if (typeof resolver === "function") {
503
489
  return resolver(u, e);
@@ -561,8 +547,8 @@ async function releaseHeldPlaylist(page) {
561
547
  * path (discoverHuluChannels) call this immediately after navigation so they share one source of truth for the profile-selector workaround.
562
548
  *
563
549
  * Selection policy: always the first profile. We do not expose a configurable name because the typical PrismCast deployment has a single primary profile, and an
564
- * operator who wants a different one can reorder profiles in Hulu's account settings. The selected profile name is captured in the audit log so the operator
565
- * always knows which one was picked.
550
+ * operator who wants a different one can reorder profiles in Hulu's account settings. The selected profile name is logged at INFO so the operator always
551
+ * knows which one was picked.
566
552
  *
567
553
  * The probe is cheap when no modal is present (one DOM evaluate) and best-effort when it is: any failure between detection and post-click navigation is logged
568
554
  * and returns silently. The downstream #CHANNELS-wait timeout becomes the failure signal of last resort, so the existing error path stays intact.
@@ -617,10 +603,12 @@ async function handleProfileSelectorIfPresent(page) {
617
603
  * virtualized list - only ~13 of ~124 rows exist in the DOM at any time, positioned absolutely within a tall spacer div. The virtualizer renders rows based on
618
604
  * the page scroll position (`document.documentElement.scrollTop`), so we scroll to bring the target channel into the DOM, then interact with it directly.
619
605
  *
620
- * Three mechanisms handle different channel types:
606
+ * Four mechanisms handle different channel types:
621
607
  * 1. Binary search with passive row number caching - primary mechanism for most channels (~800ms first time, ~200ms on cache hit)
622
608
  * 2. Position-based inference - handles local affiliates when searching by network name (e.g., "ABC" finds the local call sign at the right sort position)
623
609
  * 3. Linear scan fallback - safety net for raw call sign searches or any channel the binary search cannot find (~2.4 seconds)
610
+ * 4. Fast-path direct tune (tryFastPathTune) - after the channel is located, attempts a direct tune via unified cache injection or interceptor
611
+ * self-resolution; a successful outcome returns success without reaching the on-now-cell click or play-button steps
624
612
  *
625
613
  * The selection process:
626
614
  * 1. Clear any "Who's Watching?" profile-selector modal that intercepts before guide interaction (see handleProfileSelectorIfPresent)
@@ -629,8 +617,9 @@ async function handleProfileSelectorIfPresent(page) {
629
617
  * 4. Check the unified cache for a row number direct-scroll shortcut
630
618
  * 5. Binary search: scroll to the midpoint row, read rendered channels (caching row numbers), check for exact match or infer local affiliate
631
619
  * 6. If binary search fails, linear scan from top to bottom as a universal fallback
632
- * 7. Click the on-now program cell (`.LiveGuideProgram--first`) in the target channel's row to open the playback overlay
633
- * 8. If playSelector is provided, wait for and click the play button to start live playback
620
+ * 7. Attempt a fast-path direct tune via tryFastPathTune; on success, return immediately, skipping steps 8 and 9
621
+ * 8. Click the on-now program cell (`.LiveGuideProgram--first`) in the target channel's row to open the playback overlay
622
+ * 9. If playSelector is provided, wait for and click the play button to start live playback
634
623
  * @param page - The Puppeteer page object.
635
624
  * @param profile - The resolved site profile with a non-null channelSelector (channel name) and channelSelection config.
636
625
  * @returns Result object with success status and optional failure reason.
@@ -684,13 +673,13 @@ async function guideGridStrategy(page, profile) {
684
673
  }
685
674
  }
686
675
  if (!rowsVisible) {
687
- return { reason: "Channel grid rows did not render.", success: false };
676
+ return { guideUnavailable: true, reason: "Channel grid rows did not render.", success: false };
688
677
  }
689
678
  // Normalize the channel name to lowercase for case-insensitive matching against data-testid suffixes.
690
679
  const normalizedName = normalizeChannelName(channelName);
691
680
  const gridMeta = await readGridMeta(page);
692
681
  if (!gridMeta) {
693
- return { reason: "Could not locate channel grid spacer element.", success: false };
682
+ return { guideUnavailable: true, reason: "Could not locate channel grid spacer element.", success: false };
694
683
  }
695
684
  const { gridDocTop, rowHeight, totalRows } = gridMeta;
696
685
  // The name of the channel to click. This starts as the normalized target name but may be replaced by a local affiliate call sign via position inference.
@@ -704,84 +693,71 @@ async function guideGridStrategy(page, profile) {
704
693
  // Read rendered channels to update the cache and confirm the channel is present.
705
694
  const rendered = await readRenderedChannels(page);
706
695
  if (rendered) {
707
- const match = rendered.find((ch) => ch.name === normalizedName);
696
+ /* The entry this shortcut trusted may be filed under a key that is not the name its row renders under - an affiliate's network-name alias, or a key the
697
+ * fuzzy lookup resolved through - so entry identity is what recognizes the row the scroll landed on. The exact-name pass runs first and the identity pass
698
+ * only picks up what it leaves, so a row rendering under the requested name always wins over one that merely shares its entry.
699
+ */
700
+ const match = rendered.find((ch) => ch.name === normalizedName) ?? rendered.find((ch) => huluCache.map.get(ch.name) === cachedEntry);
708
701
  if (match) {
709
- return await clickOnNowCellAndPlay(page, normalizedName, playSelector, channelName);
702
+ return await clickOnNowCellAndPlay(page, match.name, playSelector, channelName);
710
703
  }
711
704
  }
712
705
  // Cache hit but channel not found at expected position. The guide may have changed. Clear the row number and fall through to binary search.
713
706
  LOG.debug("tuning:hulu", "Guide cache miss for %s. Falling back to binary search.", channelName);
714
707
  cachedEntry.rowNumber = undefined;
715
708
  }
716
- // Binary search through the virtualized channel list. On each iteration we scroll to the midpoint of the current range, wait for the virtualizer to render,
717
- // then check if the target channel is among the ~13 rendered rows. If not, we compare the target name alphabetically against the first and last rendered
718
- // channel names to narrow the range. The search converges in ~3-4 iterations because the 13-row render window covers a large fraction of the remaining range.
719
- let low = 0;
720
- let high = totalRows - 1;
709
+ // The ~13-row render window covers a large fraction of whatever range is left after each step, so the search converges in ~3-4 iterations; ten is a bound on
710
+ // pathological virtualizer behavior rather than the normal exit.
721
711
  const maxIterations = 10;
722
- let found = false;
723
- for (let iteration = 0; iteration < maxIterations; iteration++) {
724
- if (low > high) {
725
- break;
726
- }
727
- const mid = Math.floor((low + high) / 2);
728
- // eslint-disable-next-line no-await-in-loop
729
- await scrollToGuideRow(page, gridDocTop, rowHeight, mid);
730
- // Read all rendered channels, populating the unified cache with row numbers as a side effect.
731
- // eslint-disable-next-line no-await-in-loop
732
- const rendered = await readRenderedChannels(page);
733
- if (!rendered || (rendered.length === 0)) {
734
- continue;
735
- }
736
- // Check for an exact match first.
737
- const exactMatch = rendered.find((ch) => ch.name === normalizedName);
738
- if (exactMatch) {
739
- found = true;
740
- break;
741
- }
742
- // Determine binary search direction by comparing the target against the first and last rendered non-call-sign channel names. Call sign channels (W*/K*
743
- // local affiliates) are excluded from direction comparison because they sort by hidden network name, not by their displayed call sign - using them for
744
- // localeCompare would send the search the wrong way.
745
- const nonCallSigns = rendered.filter((ch) => !CALL_SIGN_PATTERN.test(ch.name));
746
- if (nonCallSigns.length === 0) {
747
- // All rendered channels are call signs. Cannot determine direction. Move down and hope for better data.
748
- low = mid + 1;
749
- continue;
750
- }
751
- const first = nonCallSigns[0]?.name ?? "";
752
- const last = nonCallSigns.at(-1)?.name ?? "";
753
- if (normalizedName.localeCompare(first) < 0) {
754
- // Target sorts before the first visible non-call-sign channel. Scroll up (toward lower row indices).
755
- high = mid - 1;
756
- continue;
757
- }
758
- if (normalizedName.localeCompare(last) > 0) {
759
- // Target sorts after the last visible non-call-sign channel. Scroll down (toward higher row indices).
760
- low = mid + 1;
761
- continue;
762
- }
763
- // The target is alphabetically between the first and last rendered channels but was not found by exact data-testid match. This is the "missing" case - the
764
- // channel may be a local affiliate whose call sign doesn't match the network name we're searching for. Try position-based inference.
765
- const inferred = inferLocalAffiliate(rendered, normalizedName);
766
- if (inferred) {
712
+ const outcome = await searchVirtualizedGrid({
713
+ // Call sign channels (W*/K* local affiliates) are excluded from the direction comparison because they sort by hidden network name, not by their displayed
714
+ // call sign - using them for localeCompare would send the search the wrong way.
715
+ isDirectionAnchor: (ch) => !CALL_SIGN_PATTERN.test(ch.name),
716
+ maxIterations,
717
+ nameOf: (ch) => ch.name,
718
+ onInRangeMiss: async (rendered) => {
719
+ // The target is alphabetically between the first and last rendered channels but was not found by exact data-testid match. This is the "missing" case - the
720
+ // channel may be a local affiliate whose call sign doesn't match the network name we're searching for. Try position-based inference.
721
+ const inferred = inferLocalAffiliate(rendered, normalizedName);
722
+ if (!inferred) {
723
+ return null;
724
+ }
767
725
  LOG.debug("tuning:hulu", "Inferred local affiliate %s for network name %s.", inferred, channelName);
768
- clickTarget = inferred;
769
- found = true;
770
726
  // Cross-reference the unified cache so the network name resolves to a warm-cache direct tune on subsequent requests. The details API returns the local
771
727
  // call sign as channel_info.name, so the cache is keyed by call sign. The user's channelSelector uses the network name (e.g., "ABC"). Without this
772
728
  // cross-reference, local affiliates would always fall through to cold-cache guide grid tunes because the names never match.
773
- const inferredEntry = huluChannelCache.get(inferred);
729
+ const inferredEntry = huluCache.map.get(inferred);
774
730
  if (inferredEntry) {
775
- huluChannelCache.set(normalizedName, inferredEntry);
731
+ huluCache.map.set(normalizedName, inferredEntry);
776
732
  LOG.debug("tuning:hulu", "Cross-referenced cache: %s -> %s (from inferred affiliate %s).", channelName, inferredEntry.uuid ?? "no-uuid", inferred);
777
- // eslint-disable-next-line no-await-in-loop
778
733
  const fastPathSuccess = await tryFastPathTune(page, inferredEntry, channelName);
779
734
  if (fastPathSuccess) {
780
- return { success: true };
735
+ return { resolved: { success: true } };
781
736
  }
782
737
  }
783
- }
784
- break;
738
+ return { found: inferred };
739
+ },
740
+ probe: async (rowIndex) => {
741
+ await scrollToGuideRow(page, gridDocTop, rowHeight, rowIndex);
742
+ // Read all rendered channels, populating the unified cache with row numbers as a side effect.
743
+ const rendered = await readRenderedChannels(page);
744
+ // The rows go back in DOM order untouched, because DOM order is this guide's visual order: Hulu files most local affiliates under their hidden network
745
+ // name, so sorting by the displayed name would scramble the very positions the affiliate inference reads.
746
+ return { found: rendered?.find((ch) => ch.name === normalizedName)?.name ?? null, rows: rendered };
747
+ },
748
+ targetName: normalizedName,
749
+ totalRows
750
+ });
751
+ // The inference hook tuned the channel itself through the fast path, which leaves the post-search fast-path retry, the playlist release, and the click path
752
+ // with nothing left to do.
753
+ if (outcome.kind === "resolved") {
754
+ return outcome.result;
755
+ }
756
+ let found = false;
757
+ if (outcome.kind === "found") {
758
+ // The name to click: the target's own on an exact match, the local affiliate's call sign where position inference identified one.
759
+ clickTarget = outcome.found;
760
+ found = true;
785
761
  }
786
762
  // If binary search did not find the channel (and position inference didn't identify a local affiliate), fall back to a linear scan through all channels. This
787
763
  // handles edge cases like raw call sign searches where localeCompare gives the wrong direction, or channels like "Lakeshore PBS" that sort by hidden network
@@ -806,7 +782,7 @@ async function guideGridStrategy(page, profile) {
806
782
  if (!found) {
807
783
  // Log available channels from the unified cache to help users identify the correct channelSelector value. The cache accumulates all channel names
808
784
  // encountered during binary search and linear scan, so it contains most or all channels even though the virtualized grid only renders ~13 at a time.
809
- const availableChannels = Array.from(huluChannelCache.keys()).toSorted();
785
+ const availableChannels = Array.from(huluCache.map.keys()).toSorted();
810
786
  if (availableChannels.length > 0) {
811
787
  logAvailableChannels({
812
788
  availableChannels,
@@ -833,29 +809,6 @@ async function guideGridStrategy(page, profile) {
833
809
  // Click the on-now program cell and wait for the play button, with click retries to handle React hydration timing.
834
810
  return await clickOnNowCellAndPlay(page, clickTarget, playSelector, channelName);
835
811
  }
836
- /**
837
- * Wraps guideGridStrategy with a single retry after dismissing any stale overlay that may be covering the guide grid. After a failed click attempt on the on-now
838
- * cell, the playback overlay or entity modal can remain open, obscuring the guide and preventing subsequent channel selection attempts from locating guide rows.
839
- * Pressing Escape closes most modal overlays in React-based SPAs.
840
- * @param page - The Puppeteer page object.
841
- * @param profile - The resolved site profile with a non-null channelSelector (channel name) and channelSelection config.
842
- * @returns Result object with success status and optional failure reason.
843
- */
844
- async function guideGridWithRetry(page, profile) {
845
- let result = await guideGridStrategy(page, profile);
846
- if (!result.success) {
847
- LOG.warn("Guide grid channel selection failed: %s. Dismissing overlay and retrying.", result.reason ?? "Unknown reason");
848
- try {
849
- await page.keyboard.press("Escape");
850
- await delay(500);
851
- }
852
- catch (error) {
853
- LOG.debug("tuning:hulu", "Could not dismiss guide overlay: %s.", formatError(error));
854
- }
855
- result = await guideGridStrategy(page, profile);
856
- }
857
- return result;
858
- }
859
812
  /**
860
813
  * Sets up server-side response interception on the page to capture channel data from Hulu's guide APIs. As the live page loads, Hulu fetches program details from
861
814
  * guide.hulu.com/guide/details in batches and program schedules from guide.hulu.com/guide/listing. We intercept both responses to populate the unified channel
@@ -886,19 +839,19 @@ function setupDetailsResponseInterception(page) {
886
839
  const channelUuid = payload.slice(eqIdx + 1);
887
840
  // Find the existing entry by UUID to create an alias (shared object reference). If no entry exists yet, create a new one with programs from staging.
888
841
  let existingEntry = null;
889
- for (const e of huluChannelCache.values()) {
842
+ for (const e of huluCache.map.values()) {
890
843
  if (e.uuid === channelUuid) {
891
844
  existingEntry = e;
892
845
  break;
893
846
  }
894
847
  }
895
848
  if (existingEntry) {
896
- huluChannelCache.set(name, existingEntry);
849
+ huluCache.map.set(name, existingEntry);
897
850
  }
898
851
  else {
899
- huluChannelCache.set(name, { displayName: name, programs: huluListingStaging.get(channelUuid), uuid: channelUuid });
852
+ huluCache.map.set(name, { displayName: name, programs: huluListingStaging.get(channelUuid), uuid: channelUuid });
900
853
  }
901
- LOG.debug("tuning:hulu", "Cached affiliate UUID from playlist: %s -> %s. Channel cache size: %s.", name, channelUuid, huluChannelCache.size);
854
+ LOG.debug("tuning:hulu", "Cached affiliate UUID from playlist: %s -> %s. Channel cache size: %s.", name, channelUuid, huluCache.map.size);
902
855
  }
903
856
  }
904
857
  // Direct tune failure: the interceptor's hold period expired without resolving UUID+EAB for the target channel. The 503 response prevents Hulu from playing
@@ -941,7 +894,7 @@ function setupDetailsResponseInterception(page) {
941
894
  }
942
895
  // Propagate fresh programs to existing unified cache entries. Aliases share object references, so updating one entry automatically propagates to all
943
896
  // aliases for the same channel.
944
- for (const entry of huluChannelCache.values()) {
897
+ for (const entry of huluCache.map.values()) {
945
898
  if (entry.uuid) {
946
899
  const freshPrograms = huluListingStaging.get(entry.uuid);
947
900
  if (freshPrograms) {
@@ -999,7 +952,7 @@ async function resolveHuluDirectUrl(channelSelector, page) {
999
952
  const seenEntries = new Set();
1000
953
  const allCachedUuids = [];
1001
954
  const allCurrentEabs = [];
1002
- for (const entry of huluChannelCache.values()) {
955
+ for (const entry of huluCache.map.values()) {
1003
956
  if (!entry.uuid || seenEntries.has(entry)) {
1004
957
  continue;
1005
958
  }
@@ -1016,14 +969,21 @@ async function resolveHuluDirectUrl(channelSelector, page) {
1016
969
  * immediately. On cold tunes, the guide grid's Channels tab click triggers full API expansion, and the interceptor holds the playlist request until both
1017
970
  * UUID and EAB are captured from those responses. On all tunes, it captures listing API responses to build an in-page EAB map and expands subsequent
1018
971
  * details API requests, populating the UUID cache to ~123 channels on a single page load. The script runs via evaluateOnNewDocument - it executes before
1019
- * any page JavaScript, patching window.fetch so Hulu's module-scoped fetch reference captures the interceptor. Each stream gets its own page via
1020
- * createPageWithCapture(), so there's no persistence concern.
972
+ * any page JavaScript, patching window.fetch so Hulu's module-scoped fetch reference captures the interceptor.
973
+ *
974
+ * The install goes through installOrReplaceOnNewDocument. Every tune resolves a direct URL first, so resolveHuluDirectUrl runs again on the same page when the
975
+ * monitor re-tunes it for recovery - and the UUID/EAB cache that drives this script's arguments can warm from empty (cold first tune) to
976
+ * populated (warm re-tune) in between. The script bakes those tokens in as evaluateOnNewDocument arguments, so re-installing each time is required to carry the
977
+ * CURRENT tokens; installOrReplaceOnNewDocument removes the prior script before adding the fresh one, keeping exactly one live interceptor with up-to-date
978
+ * arguments rather than an accumulating stack of competing window.fetch patches frozen at their respective install-time values. (Gating with installOncePerPage
979
+ * instead would freeze the cold first-tune's empty tokens and stall the warm recovery re-tune - the page would hold the playlist on a fast-path that the stale
980
+ * cold-mode script cannot complete, time out, and fall back to a full guide re-tune.)
1021
981
  */
1022
982
  try {
1023
983
  // Always hold the playlist request. On warm cache, the interceptor swaps immediately. On cold cache (including affiliates), the guide grid strategy will either
1024
984
  // inject UUID+EAB via the fast path (resolving the held playlist) or release the hold before falling through to the click path.
1025
985
  const attemptDirectTune = true;
1026
- await page.evaluateOnNewDocument((initialUuid, initialEab, cachedUuids, cachedEabs, targetName, holdPlaylist) => {
986
+ await installOrReplaceOnNewDocument(page, "fetch-interceptor", async () => await page.evaluateOnNewDocument((initialUuid, initialEab, cachedUuids, cachedEabs, targetName, holdPlaylist) => {
1027
987
  // evaluateOnNewDocument runs in every frame, including ad iframes. We only want to intercept fetches in the main frame - iframes don't make Hulu API
1028
988
  // calls. The try/catch handles cross-origin iframes where accessing window.top throws a SecurityError.
1029
989
  try {
@@ -1053,8 +1013,8 @@ async function resolveHuluDirectUrl(channelSelector, page) {
1053
1013
  console.log("[HULU-DIAG] Fetch interceptor installed (" + ((uuid && eab) ? "warm" : "cold") + ")." + (uuid ? " uuid=" + uuid + " eab=" + (eab || "pending") : ""));
1054
1014
  // Promise that resolves when both UUID and EAB are available for the playlist swap. On warm cache, resolves immediately (both injected at install time).
1055
1015
  // On cold cache, resolves when the in-page listing and details API response parsers have captured both values. The playlist handler awaits this Promise
1056
- // to hold the request until the target channel's data is ready. We use Promise.withResolvers() (Chrome 119+) for the deferred case so the resolver-extraction
1057
- // pattern matches inPageTimeout below rather than the older "new Promise(executor) + outer-scope variable" pattern.
1016
+ // to hold the request until the target channel's data is ready. We use Promise.withResolvers() (Chrome 119+) for the deferred case so the resolver can be
1017
+ // invoked later by the in-page API parsers as they capture UUID and EAB, matching the resolver-extraction pattern of inPageTimeout below.
1058
1018
  let directTuneResolve = null;
1059
1019
  let directTunePromise;
1060
1020
  if (uuid && eab) {
@@ -1089,8 +1049,9 @@ async function resolveHuluDirectUrl(channelSelector, page) {
1089
1049
  }
1090
1050
  // Injection endpoint for the guide grid strategy's fast-path tune. After binary search identifies the target channel and the unified cache provides the
1091
1051
  // UUID and EAB, the strategy calls this function via page.evaluate to feed both values into the interceptor. The held playlist request then resumes with the
1092
- // swapped channel_id and content_eab_id. Returns true if the injection was accepted (directTunePromise not yet resolved), false if the Promise already
1093
- // resolved (self-resolution from API data, 8s timeout, or a previous injection).
1052
+ // swapped channel_id and content_eab_id. Returns true if the injection was accepted (the deferred resolver is still present), false once that resolver has
1053
+ // been consumed - by self-resolution from API data or a prior injection (both via tryResolveDirectTune) or by __prismcastReleasePlaylist. The 8s playlist
1054
+ // hold timeout wins the Promise.race below without clearing the resolver, so it does not by itself flip this to false.
1094
1055
  window["__prismcastResolveDirectTune"] = (u, e) => {
1095
1056
  if (!directTuneResolve) {
1096
1057
  return false;
@@ -1238,7 +1199,7 @@ async function resolveHuluDirectUrl(channelSelector, page) {
1238
1199
  }
1239
1200
  return await originalFetch(input, Object.assign({}, init ?? {}, { body }));
1240
1201
  }
1241
- // Marked async to satisfy @typescript-eslint/promise-function-async since the function returns Promise<Response>.
1202
+ // The fetch replacement is async because it awaits the underlying fetch and request-body reads while rewriting matching requests.
1242
1203
  window.fetch = async function (input, init) {
1243
1204
  const url = (typeof input === "string") ? input : (input instanceof URL) ? input.href : input.url;
1244
1205
  // Expand listing API requests to include all cached UUIDs. The mini-guide listing only sends ~10 channel UUIDs, but we have UUIDs from previous
@@ -1367,7 +1328,7 @@ async function resolveHuluDirectUrl(channelSelector, page) {
1367
1328
  }
1368
1329
  return originalFetch(input, init);
1369
1330
  };
1370
- }, cachedUuid ?? "", cachedEab ?? "", allCachedUuids, allCurrentEabs, normalizedName, attemptDirectTune);
1331
+ }, cachedUuid ?? "", cachedEab ?? "", allCachedUuids, allCurrentEabs, normalizedName, attemptDirectTune));
1371
1332
  }
1372
1333
  catch (error) {
1373
1334
  LOG.debug("tuning:hulu", "Failed to install Hulu fetch interceptor: %s.", formatError(error));
@@ -1392,21 +1353,22 @@ async function resolveHuluDirectUrl(channelSelector, page) {
1392
1353
  function invalidateHuluDirectUrl(channelSelector) {
1393
1354
  const key = resolveHuluCacheKey(normalizeChannelName(channelSelector));
1394
1355
  if (key) {
1395
- huluChannelCache.delete(key);
1356
+ huluCache.map.delete(key);
1396
1357
  }
1397
1358
  }
1398
1359
  /**
1399
1360
  * Discovers all channels from Hulu Live TV by clicking the Channels tab to trigger full API expansion and performing a complete linear scan through the
1400
- * virtualized guide grid. The route has already navigated to the Hulu live page. Detects local affiliates using the same CALL_SIGN_PATTERN and position-based
1361
+ * virtualized guide grid. This function navigates to the Hulu live page itself. Detects local affiliates using the same CALL_SIGN_PATTERN and position-based
1401
1362
  * inference logic as the tuning strategy. Affiliates get the network name as their selector; non-affiliates get their display name. Enriches unified cache
1402
1363
  * entries with affiliate metadata for subsequent getCachedChannels derivation.
1403
- * @param page - The Puppeteer page object, already on the Hulu live page (navigated by the route handler).
1364
+ * @param page - The Puppeteer page object; this function navigates it to the Hulu live page.
1404
1365
  * @returns Array of discovered channels with affiliate detection and selector mapping.
1405
1366
  */
1406
1367
  async function discoverHuluChannels(page) {
1407
1368
  // Return from the unified cache if a full discovery walk (with affiliate inference) has already completed.
1408
- if (huluFullyDiscovered && (huluChannelCache.size > 0)) {
1409
- return buildHuluDiscoveredChannels();
1369
+ const cached = huluCache.cached();
1370
+ if (cached) {
1371
+ return cached;
1410
1372
  }
1411
1373
  // Set up response interception BEFORE navigation so we capture the initial details and listing API responses during page load. These responses populate the
1412
1374
  // unified channel cache with UUID, programs, and display names for all ~130 channels - warming the tuning cache as a side effect of discovery. The same
@@ -1495,15 +1457,15 @@ async function discoverHuluChannels(page) {
1495
1457
  // Alias the network name to the affiliate's cache entry so that resolveHuluDirectUrl can look up local affiliates by their channelSelector (e.g., "PBS")
1496
1458
  // without falling through to the guide grid. Without this alias, the first tune after precaching would always be cold for local affiliates because the
1497
1459
  // cache is keyed by call sign (e.g., "wttw") and findHuluChannelEntry("pbs") would find no match.
1498
- const affiliateEntry = huluChannelCache.get(callSign);
1460
+ const affiliateEntry = huluCache.map.get(callSign);
1499
1461
  if (affiliateEntry) {
1500
- huluChannelCache.set(network, affiliateEntry);
1462
+ huluCache.map.set(network, affiliateEntry);
1501
1463
  }
1502
1464
  }
1503
1465
  }
1504
- // Enrich unified cache entries with affiliate metadata so buildHuluDiscoveredChannels can derive proper affiliate labeling on subsequent getCachedChannels calls.
1466
+ // Enrich unified cache entries with affiliate metadata so the cache's projection carries proper affiliate labeling on subsequent warm reads.
1505
1467
  for (const [callSign, network] of affiliateMap) {
1506
- const entry = huluChannelCache.get(callSign);
1468
+ const entry = huluCache.map.get(callSign);
1507
1469
  if (entry) {
1508
1470
  entry.affiliate = network;
1509
1471
  }
@@ -1520,29 +1482,22 @@ async function discoverHuluChannels(page) {
1520
1482
  // Do not cache empty results - leave the flag false so subsequent calls retry the full walk. Empty results can indicate no Hulu + Live TV subscription.
1521
1483
  if (discovered.length > 0) {
1522
1484
  discovered.sort((a, b) => a.name.localeCompare(b.name));
1523
- huluFullyDiscovered = true;
1485
+ // A full walk with affiliate position inference is what makes the cache comprehensive enough to serve warm reads. Anything short of it leaves the cache
1486
+ // unmarked, so a later discovery call runs the walk again rather than reporting a lineup missing its affiliate labeling.
1487
+ huluCache.markComplete();
1524
1488
  }
1525
1489
  return discovered;
1526
1490
  }
1527
- /**
1528
- * Returns cached discovered channels from the unified channel cache, or null if a full discovery walk (with affiliate position inference) has not yet completed.
1529
- * Derives the result on the fly from unified cache entries, deduplicating aliases via Set reference equality.
1530
- * @returns Sorted array of discovered channels or null.
1531
- */
1532
- function getHuluCachedChannels() {
1533
- if (!huluFullyDiscovered || (huluChannelCache.size === 0)) {
1534
- return null;
1535
- }
1536
- return buildHuluDiscoveredChannels();
1537
- }
1538
1491
  export const huluProvider = {
1539
1492
  discoverChannels: discoverHuluChannels,
1540
- getCachedChannels: getHuluCachedChannels,
1493
+ getCachedChannels: huluCache.cached,
1541
1494
  guideUrl: "https://www.hulu.com/live",
1542
1495
  handlesOwnNavigation: true,
1543
1496
  label: "Hulu",
1544
- // Profile for Hulu Live TV which presents a guide grid of live channels. The channel list is revealed by clicking a tab (listSelector), then the desired channel
1545
- // is found by matching img.alt text. Uses the fullscreen API (inherited from fullscreenApi) plus a dedicated fullscreen button selector for the player's native
1497
+ // Profile for Hulu Live TV which presents a guide grid of live channels. The channel list is revealed by clicking the #CHANNELS tab (listSelector), then the
1498
+ // target channel is located by matching its normalized name against the data-testid="live-guide-channel-kyber-{name}" attribute on the virtualized guide rows
1499
+ // (binary search to scroll the target into the render window, with position-based affiliate inference). Uses the fullscreen API (inherited from fullscreenApi)
1500
+ // plus a dedicated fullscreen button selector for the player's native
1546
1501
  // maximize control. Requires selectReadyVideo because the page may have multiple video elements (ads, previews, main content). Uses waitForNetworkIdle because
1547
1502
  // Hulu's SPA has heavy async initialization that often prevents the load event from firing within the retryOperation timeout; the graceful networkidle2 fallback
1548
1503
  // in navigateToPage() allows execution to continue to channel selection even when background requests are still pending.
@@ -1560,7 +1515,7 @@ export const huluProvider = {
1560
1515
  slug: "hulu",
1561
1516
  strategy: {
1562
1517
  clearCache: clearHuluCache,
1563
- execute: guideGridWithRetry,
1518
+ execute: guideGridStrategy,
1564
1519
  invalidateDirectUrl: invalidateHuluDirectUrl,
1565
1520
  resolveDirectUrl: resolveHuluDirectUrl
1566
1521
  },