prismcast 1.8.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. package/README.md +6 -6
  2. package/dist/app.d.ts +1 -1
  3. package/dist/app.js +41 -13
  4. package/dist/app.js.map +1 -1
  5. package/dist/browser/cdp.d.ts +4 -6
  6. package/dist/browser/cdp.js +18 -22
  7. package/dist/browser/cdp.js.map +1 -1
  8. package/dist/browser/channelSelection.d.ts +41 -11
  9. package/dist/browser/channelSelection.js +75 -24
  10. package/dist/browser/channelSelection.js.map +1 -1
  11. package/dist/browser/display.d.ts +1 -1
  12. package/dist/browser/index.d.ts +8 -8
  13. package/dist/browser/index.js +44 -45
  14. package/dist/browser/index.js.map +1 -1
  15. package/dist/browser/login.d.ts +2 -2
  16. package/dist/browser/login.js +1 -1
  17. package/dist/browser/manifestInterceptor.d.ts +77 -0
  18. package/dist/browser/manifestInterceptor.js +323 -0
  19. package/dist/browser/manifestInterceptor.js.map +1 -0
  20. package/dist/browser/precaching.d.ts +1 -1
  21. package/dist/browser/precaching.js +23 -23
  22. package/dist/browser/precaching.js.map +1 -1
  23. package/dist/browser/tuning/comcastPolymer.d.ts +1 -1
  24. package/dist/browser/tuning/cox.d.ts +1 -1
  25. package/dist/browser/tuning/directv.d.ts +1 -1
  26. package/dist/browser/tuning/directv.js +65 -69
  27. package/dist/browser/tuning/directv.js.map +1 -1
  28. package/dist/browser/tuning/fox.d.ts +1 -1
  29. package/dist/browser/tuning/fox.js +172 -15
  30. package/dist/browser/tuning/fox.js.map +1 -1
  31. package/dist/browser/tuning/hbo.d.ts +1 -1
  32. package/dist/browser/tuning/hbo.js +7 -7
  33. package/dist/browser/tuning/hbo.js.map +1 -1
  34. package/dist/browser/tuning/hulu.d.ts +1 -1
  35. package/dist/browser/tuning/hulu.js +120 -113
  36. package/dist/browser/tuning/hulu.js.map +1 -1
  37. package/dist/browser/tuning/shared.d.ts +3 -3
  38. package/dist/browser/tuning/shared.js +5 -5
  39. package/dist/browser/tuning/sling.d.ts +1 -1
  40. package/dist/browser/tuning/sling.js +30 -30
  41. package/dist/browser/tuning/sling.js.map +1 -1
  42. package/dist/browser/tuning/spectrum.d.ts +1 -1
  43. package/dist/browser/tuning/spectrum.js +21 -22
  44. package/dist/browser/tuning/spectrum.js.map +1 -1
  45. package/dist/browser/tuning/thumbnailRow.d.ts +1 -1
  46. package/dist/browser/tuning/thumbnailRow.js +1 -1
  47. package/dist/browser/tuning/tileClick.d.ts +1 -1
  48. package/dist/browser/tuning/tileClick.js +5 -5
  49. package/dist/browser/tuning/xfinity.d.ts +1 -1
  50. package/dist/browser/tuning/youtubeTv.d.ts +1 -1
  51. package/dist/browser/tuning/youtubeTv.js +17 -18
  52. package/dist/browser/tuning/youtubeTv.js.map +1 -1
  53. package/dist/browser/video.d.ts +20 -6
  54. package/dist/browser/video.js +30 -38
  55. package/dist/browser/video.js.map +1 -1
  56. package/dist/channels/index.d.ts +1 -2
  57. package/dist/channels/index.js +475 -442
  58. package/dist/channels/index.js.map +1 -1
  59. package/dist/config/channelForm.d.ts +58 -0
  60. package/dist/config/channelForm.js +129 -0
  61. package/dist/config/channelForm.js.map +1 -0
  62. package/dist/config/consistencyProbe.d.ts +7 -0
  63. package/dist/config/consistencyProbe.js +143 -0
  64. package/dist/config/consistencyProbe.js.map +1 -0
  65. package/dist/config/health.d.ts +7 -6
  66. package/dist/config/health.js +78 -65
  67. package/dist/config/health.js.map +1 -1
  68. package/dist/config/index.d.ts +1 -1
  69. package/dist/config/index.js +21 -14
  70. package/dist/config/index.js.map +1 -1
  71. package/dist/config/paths.d.ts +9 -3
  72. package/dist/config/paths.js +12 -4
  73. package/dist/config/paths.js.map +1 -1
  74. package/dist/config/persistence.context.d.ts +8 -0
  75. package/dist/config/persistence.context.js +30 -0
  76. package/dist/config/persistence.context.js.map +1 -0
  77. package/dist/config/persistence.d.ts +157 -0
  78. package/dist/config/persistence.js +446 -0
  79. package/dist/config/persistence.js.map +1 -0
  80. package/dist/config/presets.d.ts +14 -4
  81. package/dist/config/presets.js +84 -75
  82. package/dist/config/presets.js.map +1 -1
  83. package/dist/config/profiles.d.ts +2 -2
  84. package/dist/config/profiles.js +7 -9
  85. package/dist/config/profiles.js.map +1 -1
  86. package/dist/config/servicePacks.d.ts +46 -0
  87. package/dist/config/{providerPacks.js → servicePacks.js} +50 -53
  88. package/dist/config/servicePacks.js.map +1 -0
  89. package/dist/config/services.d.ts +228 -0
  90. package/dist/config/services.js +772 -0
  91. package/dist/config/services.js.map +1 -0
  92. package/dist/config/sites.d.ts +3 -3
  93. package/dist/config/sites.js +75 -75
  94. package/dist/config/sites.js.map +1 -1
  95. package/dist/config/userChannels.d.ts +522 -54
  96. package/dist/config/userChannels.js +1343 -366
  97. package/dist/config/userChannels.js.map +1 -1
  98. package/dist/config/userConfig.d.ts +108 -13
  99. package/dist/config/userConfig.js +296 -143
  100. package/dist/config/userConfig.js.map +1 -1
  101. package/dist/config/userProfiles.d.ts +34 -16
  102. package/dist/config/userProfiles.js +189 -100
  103. package/dist/config/userProfiles.js.map +1 -1
  104. package/dist/hdhr/channelMap.d.ts +1 -1
  105. package/dist/hdhr/channelMap.js +10 -16
  106. package/dist/hdhr/channelMap.js.map +1 -1
  107. package/dist/hdhr/deviceId.js +10 -9
  108. package/dist/hdhr/deviceId.js.map +1 -1
  109. package/dist/hdhr/discover.js +4 -5
  110. package/dist/hdhr/discover.js.map +1 -1
  111. package/dist/hdhr/index.js +12 -19
  112. package/dist/hdhr/index.js.map +1 -1
  113. package/dist/index.js +12 -12
  114. package/dist/index.js.map +1 -1
  115. package/dist/native/codecInference.d.ts +44 -0
  116. package/dist/native/codecInference.js +327 -0
  117. package/dist/native/codecInference.js.map +1 -0
  118. package/dist/native/decrypt.d.ts +1 -1
  119. package/dist/native/index.d.ts +5 -5
  120. package/dist/native/index.js +45 -67
  121. package/dist/native/index.js.map +1 -1
  122. package/dist/native/probe.d.ts +27 -9
  123. package/dist/native/probe.js +159 -68
  124. package/dist/native/probe.js.map +1 -1
  125. package/dist/native/proxy.d.ts +4 -2
  126. package/dist/native/proxy.js +56 -53
  127. package/dist/native/proxy.js.map +1 -1
  128. package/dist/native/tokenExpiry.d.ts +22 -0
  129. package/dist/native/tokenExpiry.js +153 -0
  130. package/dist/native/tokenExpiry.js.map +1 -0
  131. package/dist/routes/assets.js +3 -3
  132. package/dist/routes/assets.js.map +1 -1
  133. package/dist/routes/auth.js +9 -13
  134. package/dist/routes/auth.js.map +1 -1
  135. package/dist/routes/components.js +2 -2
  136. package/dist/routes/components.js.map +1 -1
  137. package/dist/routes/config/channels/endpoints/browse.d.ts +6 -0
  138. package/dist/routes/config/channels/endpoints/browse.js +195 -0
  139. package/dist/routes/config/channels/endpoints/browse.js.map +1 -0
  140. package/dist/routes/config/channels/endpoints/bulk.d.ts +6 -0
  141. package/dist/routes/config/channels/endpoints/bulk.js +128 -0
  142. package/dist/routes/config/channels/endpoints/bulk.js.map +1 -0
  143. package/dist/routes/config/channels/endpoints/crud.d.ts +6 -0
  144. package/dist/routes/config/channels/endpoints/crud.js +504 -0
  145. package/dist/routes/config/channels/endpoints/crud.js.map +1 -0
  146. package/dist/routes/config/channels/endpoints/importExport.d.ts +6 -0
  147. package/dist/routes/config/channels/endpoints/importExport.js +131 -0
  148. package/dist/routes/config/channels/endpoints/importExport.js.map +1 -0
  149. package/dist/routes/config/channels/endpoints/predefined.d.ts +6 -0
  150. package/dist/routes/config/channels/endpoints/predefined.js +78 -0
  151. package/dist/routes/config/channels/endpoints/predefined.js.map +1 -0
  152. package/dist/routes/config/channels/endpoints/prefs.d.ts +6 -0
  153. package/dist/routes/config/channels/endpoints/prefs.js +58 -0
  154. package/dist/routes/config/channels/endpoints/prefs.js.map +1 -0
  155. package/dist/routes/config/channels/endpoints/service.d.ts +6 -0
  156. package/dist/routes/config/channels/endpoints/service.js +149 -0
  157. package/dist/routes/config/channels/endpoints/service.js.map +1 -0
  158. package/dist/routes/config/channels/endpoints/tags.d.ts +6 -0
  159. package/dist/routes/config/channels/endpoints/tags.js +185 -0
  160. package/dist/routes/config/channels/endpoints/tags.js.map +1 -0
  161. package/dist/routes/config/channels/http/handler.d.ts +10 -0
  162. package/dist/routes/config/channels/http/handler.js +20 -0
  163. package/dist/routes/config/channels/http/handler.js.map +1 -0
  164. package/dist/routes/config/channels/http/playlistHint.d.ts +25 -0
  165. package/dist/routes/config/channels/http/playlistHint.js +40 -0
  166. package/dist/routes/config/channels/http/playlistHint.js.map +1 -0
  167. package/dist/routes/config/channels/http/serviceWarning.d.ts +10 -0
  168. package/dist/routes/config/channels/http/serviceWarning.js +28 -0
  169. package/dist/routes/config/channels/http/serviceWarning.js.map +1 -0
  170. package/dist/routes/config/channels/index.d.ts +3 -3
  171. package/dist/routes/config/channels/index.js +2 -2
  172. package/dist/routes/config/channels/index.js.map +1 -1
  173. package/dist/routes/config/channels/{routes.d.ts → setup.d.ts} +1 -1
  174. package/dist/routes/config/channels/setup.js +23 -0
  175. package/dist/routes/config/channels/setup.js.map +1 -0
  176. package/dist/routes/config/channels/table.d.ts +13 -13
  177. package/dist/routes/config/channels/table.js +291 -184
  178. package/dist/routes/config/channels/table.js.map +1 -1
  179. package/dist/routes/config/http/envelope.d.ts +111 -0
  180. package/dist/routes/config/http/envelope.js +103 -0
  181. package/dist/routes/config/http/envelope.js.map +1 -0
  182. package/dist/routes/config/index.d.ts +9 -9
  183. package/dist/routes/config/index.js +5 -5
  184. package/dist/routes/config/index.js.map +1 -1
  185. package/dist/routes/config/{providers.js → services.js} +105 -99
  186. package/dist/routes/config/services.js.map +1 -0
  187. package/dist/routes/config/settings.d.ts +1 -1
  188. package/dist/routes/config/settings.js +85 -60
  189. package/dist/routes/config/settings.js.map +1 -1
  190. package/dist/routes/debug.js +17 -18
  191. package/dist/routes/debug.js.map +1 -1
  192. package/dist/routes/health.js +1 -1
  193. package/dist/routes/health.js.map +1 -1
  194. package/dist/routes/icons.js +2 -2
  195. package/dist/routes/icons.js.map +1 -1
  196. package/dist/routes/index.js +2 -2
  197. package/dist/routes/index.js.map +1 -1
  198. package/dist/routes/logs.js +23 -34
  199. package/dist/routes/logs.js.map +1 -1
  200. package/dist/routes/mpegts.js +1 -1
  201. package/dist/routes/playlist.d.ts +5 -5
  202. package/dist/routes/playlist.js +84 -61
  203. package/dist/routes/playlist.js.map +1 -1
  204. package/dist/routes/root/content.js +82 -61
  205. package/dist/routes/root/content.js.map +1 -1
  206. package/dist/routes/root/index.js +2 -5
  207. package/dist/routes/root/index.js.map +1 -1
  208. package/dist/routes/root/scripts/channels.js +538 -581
  209. package/dist/routes/root/scripts/channels.js.map +1 -1
  210. package/dist/routes/root/scripts/config.js +868 -1022
  211. package/dist/routes/root/scripts/config.js.map +1 -1
  212. package/dist/routes/root/scripts/index.d.ts +4 -4
  213. package/dist/routes/root/scripts/shared.js +445 -197
  214. package/dist/routes/root/scripts/shared.js.map +1 -1
  215. package/dist/routes/root/scripts/status.d.ts +4 -0
  216. package/dist/routes/root/scripts/status.handlers.d.ts +182 -0
  217. package/dist/routes/root/scripts/status.handlers.js +713 -0
  218. package/dist/routes/root/scripts/status.handlers.js.map +1 -0
  219. package/dist/routes/root/scripts/status.js +85 -436
  220. package/dist/routes/root/scripts/status.js.map +1 -1
  221. package/dist/routes/root/styles.js +16 -8
  222. package/dist/routes/root/styles.js.map +1 -1
  223. package/dist/routes/services.d.ts +6 -0
  224. package/dist/routes/{providers.js → services.js} +58 -55
  225. package/dist/routes/services.js.map +1 -0
  226. package/dist/routes/sse.d.ts +16 -0
  227. package/dist/routes/sse.js +29 -0
  228. package/dist/routes/sse.js.map +1 -0
  229. package/dist/routes/streams.js +10 -23
  230. package/dist/routes/streams.js.map +1 -1
  231. package/dist/routes/ui.d.ts +2 -2
  232. package/dist/routes/ui.js +2 -0
  233. package/dist/routes/ui.js.map +1 -1
  234. package/dist/routes/upgrade.js +6 -7
  235. package/dist/routes/upgrade.js.map +1 -1
  236. package/dist/service/commands.context.d.ts +6 -0
  237. package/dist/service/commands.context.js +47 -0
  238. package/dist/service/commands.context.js.map +1 -0
  239. package/dist/service/commands.d.ts +53 -8
  240. package/dist/service/commands.js +172 -192
  241. package/dist/service/commands.js.map +1 -1
  242. package/dist/service/generators.context.d.ts +6 -0
  243. package/dist/service/generators.context.js +45 -0
  244. package/dist/service/generators.context.js.map +1 -0
  245. package/dist/service/generators.d.ts +56 -16
  246. package/dist/service/generators.js +509 -291
  247. package/dist/service/generators.js.map +1 -1
  248. package/dist/service/index.d.ts +2 -2
  249. package/dist/streaming/clients.d.ts +2 -2
  250. package/dist/streaming/clients.js +2 -2
  251. package/dist/streaming/codec.d.ts +2 -2
  252. package/dist/streaming/codec.js +0 -1
  253. package/dist/streaming/codec.js.map +1 -1
  254. package/dist/streaming/fmp4Segmenter.d.ts +2 -2
  255. package/dist/streaming/fmp4Segmenter.js +12 -12
  256. package/dist/streaming/hls.d.ts +7 -7
  257. package/dist/streaming/hls.js +104 -92
  258. package/dist/streaming/hls.js.map +1 -1
  259. package/dist/streaming/hlsResume.d.ts +3 -3
  260. package/dist/streaming/hlsResume.js +6 -6
  261. package/dist/streaming/hlsResume.js.map +1 -1
  262. package/dist/streaming/hlsSegments.d.ts +5 -2
  263. package/dist/streaming/hlsSegments.js +14 -9
  264. package/dist/streaming/hlsSegments.js.map +1 -1
  265. package/dist/streaming/monitor.d.ts +12 -12
  266. package/dist/streaming/monitor.js +98 -66
  267. package/dist/streaming/monitor.js.map +1 -1
  268. package/dist/streaming/mp4Parser.d.ts +34 -4
  269. package/dist/streaming/mp4Parser.js +111 -29
  270. package/dist/streaming/mp4Parser.js.map +1 -1
  271. package/dist/streaming/mpegts.d.ts +1 -1
  272. package/dist/streaming/mpegts.js +13 -10
  273. package/dist/streaming/mpegts.js.map +1 -1
  274. package/dist/streaming/playlistBuilder.d.ts +1 -1
  275. package/dist/streaming/playlistBuilder.js +2 -2
  276. package/dist/streaming/preroll.d.ts +9 -9
  277. package/dist/streaming/preroll.js +40 -42
  278. package/dist/streaming/preroll.js.map +1 -1
  279. package/dist/streaming/pretune.js +4 -4
  280. package/dist/streaming/pretune.js.map +1 -1
  281. package/dist/streaming/recovery.d.ts +3 -3
  282. package/dist/streaming/recovery.js +4 -4
  283. package/dist/streaming/registry.d.ts +6 -6
  284. package/dist/streaming/registry.js +4 -8
  285. package/dist/streaming/registry.js.map +1 -1
  286. package/dist/streaming/setup.d.ts +11 -10
  287. package/dist/streaming/setup.js +120 -71
  288. package/dist/streaming/setup.js.map +1 -1
  289. package/dist/streaming/showInfo.d.ts +9 -3
  290. package/dist/streaming/showInfo.js +33 -27
  291. package/dist/streaming/showInfo.js.map +1 -1
  292. package/dist/streaming/statusEmitter.d.ts +8 -7
  293. package/dist/streaming/statusEmitter.js +5 -5
  294. package/dist/streaming/statusEmitter.js.map +1 -1
  295. package/dist/types/channels.d.ts +139 -36
  296. package/dist/types/channels.js +39 -1
  297. package/dist/types/channels.js.map +1 -1
  298. package/dist/types/config.d.ts +22 -3
  299. package/dist/types/index.d.ts +9 -8
  300. package/dist/types/index.js +1 -0
  301. package/dist/types/index.js.map +1 -1
  302. package/dist/types/profiles.d.ts +14 -13
  303. package/dist/types/selection.d.ts +49 -5
  304. package/dist/types/shared.d.ts +1 -1
  305. package/dist/types/streaming.d.ts +2 -2
  306. package/dist/upgrade/commands.context.d.ts +6 -0
  307. package/dist/upgrade/commands.context.js +29 -0
  308. package/dist/upgrade/commands.context.js.map +1 -0
  309. package/dist/upgrade/commands.d.ts +29 -2
  310. package/dist/upgrade/commands.js +87 -134
  311. package/dist/upgrade/commands.js.map +1 -1
  312. package/dist/upgrade/detection.context.d.ts +6 -0
  313. package/dist/upgrade/detection.context.js +24 -0
  314. package/dist/upgrade/detection.context.js.map +1 -0
  315. package/dist/upgrade/detection.d.ts +140 -10
  316. package/dist/upgrade/detection.js +153 -66
  317. package/dist/upgrade/detection.js.map +1 -1
  318. package/dist/upgrade/index.d.ts +2 -2
  319. package/dist/utils/chromeFetch.d.ts +1 -1
  320. package/dist/utils/chromeFetch.js +2 -2
  321. package/dist/utils/clock.d.ts +14 -0
  322. package/dist/utils/clock.js +28 -0
  323. package/dist/utils/clock.js.map +1 -0
  324. package/dist/utils/debugFilter.d.ts +1 -1
  325. package/dist/utils/debugFilter.js +8 -6
  326. package/dist/utils/debugFilter.js.map +1 -1
  327. package/dist/utils/delay.d.ts +2 -3
  328. package/dist/utils/delay.js +10 -16
  329. package/dist/utils/delay.js.map +1 -1
  330. package/dist/utils/evaluate.js +12 -14
  331. package/dist/utils/evaluate.js.map +1 -1
  332. package/dist/utils/ffmpeg.context.d.ts +6 -0
  333. package/dist/utils/ffmpeg.context.js +31 -0
  334. package/dist/utils/ffmpeg.context.js.map +1 -0
  335. package/dist/utils/ffmpeg.d.ts +85 -29
  336. package/dist/utils/ffmpeg.js +126 -129
  337. package/dist/utils/ffmpeg.js.map +1 -1
  338. package/dist/utils/fileLogger.d.ts +18 -2
  339. package/dist/utils/fileLogger.js +60 -31
  340. package/dist/utils/fileLogger.js.map +1 -1
  341. package/dist/utils/format.d.ts +7 -1
  342. package/dist/utils/format.js +22 -2
  343. package/dist/utils/format.js.map +1 -1
  344. package/dist/utils/html.js +1 -3
  345. package/dist/utils/html.js.map +1 -1
  346. package/dist/utils/index.d.ts +21 -20
  347. package/dist/utils/index.js +1 -0
  348. package/dist/utils/index.js.map +1 -1
  349. package/dist/utils/logger.js +31 -45
  350. package/dist/utils/logger.js.map +1 -1
  351. package/dist/utils/m3u.d.ts +18 -3
  352. package/dist/utils/m3u.js +28 -14
  353. package/dist/utils/m3u.js.map +1 -1
  354. package/dist/utils/memo.d.ts +19 -0
  355. package/dist/utils/memo.js +53 -0
  356. package/dist/utils/memo.js.map +1 -0
  357. package/dist/utils/morganStream.js +4 -4
  358. package/dist/utils/morganStream.js.map +1 -1
  359. package/dist/utils/pid.d.ts +2 -2
  360. package/dist/utils/pid.js +2 -2
  361. package/dist/utils/platform.d.ts +1 -1
  362. package/dist/utils/platform.js +3 -2
  363. package/dist/utils/platform.js.map +1 -1
  364. package/dist/utils/retry.d.ts +2 -0
  365. package/dist/utils/retry.js +9 -7
  366. package/dist/utils/retry.js.map +1 -1
  367. package/dist/utils/sanitize.d.ts +2 -2
  368. package/dist/utils/sanitize.js +4 -4
  369. package/dist/utils/streamContext.d.ts +7 -0
  370. package/dist/utils/streamContext.js +10 -1
  371. package/dist/utils/streamContext.js.map +1 -1
  372. package/dist/utils/timing.d.ts +6 -3
  373. package/dist/utils/timing.js +10 -6
  374. package/dist/utils/timing.js.map +1 -1
  375. package/dist/utils/version.d.ts +3 -2
  376. package/dist/utils/version.js +15 -28
  377. package/dist/utils/version.js.map +1 -1
  378. package/package.json +14 -10
  379. package/dist/config/providerPacks.d.ts +0 -46
  380. package/dist/config/providerPacks.js.map +0 -1
  381. package/dist/config/providers.d.ts +0 -175
  382. package/dist/config/providers.js +0 -691
  383. package/dist/config/providers.js.map +0 -1
  384. package/dist/native/intercept.d.ts +0 -35
  385. package/dist/native/intercept.js +0 -184
  386. package/dist/native/intercept.js.map +0 -1
  387. package/dist/routes/config/channels/routes.js +0 -1477
  388. package/dist/routes/config/channels/routes.js.map +0 -1
  389. package/dist/routes/config/providers.js.map +0 -1
  390. package/dist/routes/providers.d.ts +0 -6
  391. package/dist/routes/providers.js.map +0 -1
  392. /package/dist/routes/config/{providers.d.ts → services.d.ts} +0 -0
package/dist/utils/m3u.js CHANGED
@@ -8,15 +8,32 @@
8
8
  */
9
9
  // Maximum length for generated channel keys.
10
10
  const MAX_KEY_LENGTH = 50;
11
+ /**
12
+ * Escapes a string for safe inclusion as an M3U quoted attribute value (the `value` portion of `attribute="value"`). RFC 8216 section 4.2 forbids the
13
+ * double-quote character (the attribute terminator) and bare line feed / carriage return inside a quoted-string and defines no escape sequence; the de-facto
14
+ * extended-M3U convention used by VLC, IPTV-Org tooling, and downstream consumers is backslash-escaping. We backslash-escape the structural characters and
15
+ * replace literal CR/LF with a single space - there is no portable escape sequence for line breaks inside a quoted-string, and dropping the line break entirely
16
+ * would silently glue adjacent words together while leaving the attribute syntactically valid. Apply this helper at every M3U attribute write site that flows
17
+ * user-controlled content; structurally server-controlled values (validated channel keys, numeric fields, fixed literals) do not need it but pass through
18
+ * unchanged when wrapped, so prefer wrapping for uniformity.
19
+ *
20
+ * Order matters: the backslash replacement runs first so the backslashes we introduce when escaping double-quote do not get re-escaped by a second pass.
21
+ *
22
+ * @param value - The raw string to escape.
23
+ * @returns A string safe to embed between the surrounding double-quotes of an M3U attribute value.
24
+ */
25
+ export function escapeM3uAttribute(value) {
26
+ return value.replace(/[\r\n]+/g, " ").replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
27
+ }
11
28
  /**
12
29
  * Generates a URL-safe channel key from a display name. The key is used as the channel identifier in URLs and configuration.
13
30
  * @param name - The display name to convert.
14
31
  * @returns A lowercase, hyphen-separated key suitable for URLs.
15
32
  *
16
33
  * Examples:
17
- * - "CNN Live!" → "cnn-live"
18
- * - "BBC News 24/7" → "bbc-news-247"
19
- * - " Spaces Everywhere " → "spaces-everywhere"
34
+ * - "CNN Live!" -> "cnn-live"
35
+ * - "BBC News 24/7" -> "bbc-news-247"
36
+ * - " Spaces Everywhere " -> "spaces-everywhere"
20
37
  */
21
38
  export function generateChannelKey(name) {
22
39
  return name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_KEY_LENGTH);
@@ -30,17 +47,14 @@ export function generateChannelKey(name) {
30
47
  function extractAttribute(line, attribute) {
31
48
  // Match attribute="value" (quoted).
32
49
  const quotedPattern = new RegExp(attribute + "=\"([^\"]*)\"", "i");
33
- const quotedMatch = line.match(quotedPattern);
34
- if (quotedMatch) {
35
- return quotedMatch[1];
50
+ const quotedValue = line.match(quotedPattern)?.[1];
51
+ if (quotedValue !== undefined) {
52
+ return quotedValue;
36
53
  }
37
54
  // Match attribute=value (unquoted, ends at space or comma).
38
55
  const unquotedPattern = new RegExp(attribute + "=([^\\s,\"]+)", "i");
39
- const unquotedMatch = line.match(unquotedPattern);
40
- if (unquotedMatch) {
41
- return unquotedMatch[1];
42
- }
43
- return null;
56
+ const unquotedValue = line.match(unquotedPattern)?.[1];
57
+ return unquotedValue ?? null;
44
58
  }
45
59
  /**
46
60
  * Extracts the display name from an #EXTINF line. First tries tvg-name attribute, then falls back to the comma-separated suffix.
@@ -53,7 +67,7 @@ function extractName(line) {
53
67
  if (tvgName && (tvgName.trim().length > 0)) {
54
68
  return tvgName.trim();
55
69
  }
56
- // Fall back to comma suffix (the part after the last comma in #EXTINF line). Format: #EXTINF:-1 attributes,Display Name
70
+ // Fall back to comma suffix (the part after the last comma in #EXTINF line). Format: "#EXTINF:-1 attributes,Display Name".
57
71
  const commaIndex = line.lastIndexOf(",");
58
72
  if (commaIndex !== -1) {
59
73
  const suffix = line.slice(commaIndex + 1).trim();
@@ -91,9 +105,9 @@ export function parseM3U(content) {
91
105
  const errors = [];
92
106
  const lines = content.split(/\r?\n/);
93
107
  let pendingExtinf = null;
94
- for (let i = 0; i < lines.length; i++) {
108
+ for (const [i, rawLine] of lines.entries()) {
95
109
  const lineNumber = i + 1;
96
- const line = lines[i].trim();
110
+ const line = rawLine.trim();
97
111
  // Skip empty lines and comments (except #EXTINF).
98
112
  if ((line.length === 0) || ((line.startsWith("#")) && !line.startsWith("#EXTINF"))) {
99
113
  continue;
@@ -1 +1 @@
1
- {"version":3,"file":"m3u.js","sourceRoot":"","sources":["../../src/utils/m3u.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"m3u.js","sourceRoot":"","sources":["../../src/utils/m3u.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Wraps an async probe in a one-shot memoization closure. The returned function:
3
+ *
4
+ * - On first call: invokes probe(), caches the resolved value, returns it.
5
+ * - On concurrent first-calls (multiple callers arriving before the first probe resolves): all callers share the single in-flight promise, so probe is
6
+ * invoked exactly once regardless of arrival order.
7
+ * - On subsequent calls after first success: returns the cached value directly without invoking probe.
8
+ * - On probe rejection: the rejection is sticky. Every subsequent call returns the same rejected promise without re-probing. This is by design - retrying
9
+ * a probe that has demonstrated it can fail risks a retry storm; if the caller wants retry semantics, they should compose memoizeAsync with a retry helper
10
+ * rather than have memoizeAsync silently retry. (Production-cached resolveFFmpegPath has no rejection path - probeFFmpegPath returns undefined for "not
11
+ * found" rather than throwing - so this branch never fires in the ffmpeg use case.)
12
+ *
13
+ * The cache and pending state are closure-scoped: there is no test seam, no reset method, and no way for callers to invalidate the cache. Callers that need
14
+ * invalidation are doing a different thing (TTL cache, refresh-on-event cache) and should not use this primitive.
15
+ *
16
+ * @param probe - The async function to memoize. Invoked at most once on success, or once per process on rejection.
17
+ * @returns A function that returns the same Promise<T> on every call.
18
+ */
19
+ export declare function memoizeAsync<T>(probe: () => Promise<T>): () => Promise<T>;
@@ -0,0 +1,53 @@
1
+ /* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
+ *
3
+ * memo.ts: Async memoization primitives. The single primitive here is memoizeAsync - a one-shot async cache that runs its probe at most once across the
4
+ * lifetime of the returned function and shares the in-flight promise across concurrent first-callers. Used to lazily compute values that are expensive on
5
+ * first call and cheap-to-cached on subsequent calls (e.g., resolveFFmpegPath probing the filesystem).
6
+ */
7
+ /**
8
+ * Wraps an async probe in a one-shot memoization closure. The returned function:
9
+ *
10
+ * - On first call: invokes probe(), caches the resolved value, returns it.
11
+ * - On concurrent first-calls (multiple callers arriving before the first probe resolves): all callers share the single in-flight promise, so probe is
12
+ * invoked exactly once regardless of arrival order.
13
+ * - On subsequent calls after first success: returns the cached value directly without invoking probe.
14
+ * - On probe rejection: the rejection is sticky. Every subsequent call returns the same rejected promise without re-probing. This is by design - retrying
15
+ * a probe that has demonstrated it can fail risks a retry storm; if the caller wants retry semantics, they should compose memoizeAsync with a retry helper
16
+ * rather than have memoizeAsync silently retry. (Production-cached resolveFFmpegPath has no rejection path - probeFFmpegPath returns undefined for "not
17
+ * found" rather than throwing - so this branch never fires in the ffmpeg use case.)
18
+ *
19
+ * The cache and pending state are closure-scoped: there is no test seam, no reset method, and no way for callers to invalidate the cache. Callers that need
20
+ * invalidation are doing a different thing (TTL cache, refresh-on-event cache) and should not use this primitive.
21
+ *
22
+ * @param probe - The async function to memoize. Invoked at most once on success, or once per process on rejection.
23
+ * @returns A function that returns the same Promise<T> on every call.
24
+ */
25
+ export function memoizeAsync(probe) {
26
+ let state = { kind: "unset" };
27
+ return async () => {
28
+ switch (state.kind) {
29
+ case "resolved": {
30
+ // Fast path: return the cached value directly. TypeScript narrows state.value to T on this branch.
31
+ return state.value;
32
+ }
33
+ case "pending": {
34
+ // Concurrent caller (or a follow-up caller during a sticky-rejection state). Share the single in-flight promise.
35
+ return state.promise;
36
+ }
37
+ case "unset": {
38
+ /* First call. Kick off the probe and chain a success handler that flips state to "resolved". The chained promise is what callers receive: when probe
39
+ * succeeds, the .then() callback runs, state becomes "resolved", and the chained promise resolves with the value. When probe rejects, the .then()
40
+ * callback never runs (no onRejected handler) and the chained promise rejects, so state stays "pending" carrying the rejected promise - all future
41
+ * callers receive the same rejection.
42
+ */
43
+ const promise = probe().then((value) => {
44
+ state = { kind: "resolved", value };
45
+ return value;
46
+ });
47
+ state = { kind: "pending", promise };
48
+ return promise;
49
+ }
50
+ }
51
+ };
52
+ }
53
+ //# sourceMappingURL=memo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memo.js","sourceRoot":"","sources":["../../src/utils/memo.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAeH;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,YAAY,CAAI,KAAuB;IAErD,IAAI,KAAK,GAAiB,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAE5C,OAAO,KAAK,IAAgB,EAAE;QAE5B,QAAO,KAAK,CAAC,IAAI,EAAE,CAAC;YAElB,KAAK,UAAU,CAAC,CAAC,CAAC;gBAEhB,mGAAmG;gBACnG,OAAO,KAAK,CAAC,KAAK,CAAC;YACrB,CAAC;YAED,KAAK,SAAS,CAAC,CAAC,CAAC;gBAEf,iHAAiH;gBACjH,OAAO,KAAK,CAAC,OAAO,CAAC;YACvB,CAAC;YAED,KAAK,OAAO,CAAC,CAAC,CAAC;gBAEb;;;;mBAIG;gBACH,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;oBAErC,KAAK,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;oBAEpC,OAAO,KAAK,CAAC;gBACf,CAAC,CAAC,CAAC;gBAEH,KAAK,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;gBAErC,OAAO,OAAO,CAAC;YACjB,CAAC;QACH,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -1,4 +1,4 @@
1
- import df from "dateformat";
1
+ import { formatTimestamp } from "./format.js";
2
2
  import { isConsoleLogging } from "./logger.js";
3
3
  import { writeLogEntry } from "./fileLogger.js";
4
4
  /* Morgan HTTP request logger needs a writable stream to output log entries. By default, Morgan writes to stdout. This adapter routes Morgan output to either the
@@ -19,13 +19,13 @@ export function createMorganStream() {
19
19
  const trimmedMessage = message.trim();
20
20
  if (isConsoleLogging()) {
21
21
  // Console logging mode - add timestamp prefix and write to stdout.
22
- const timestamp = df(new Date(), "yyyy/mm/dd HH:MM:ss.l");
22
+ const timestamp = formatTimestamp();
23
23
  // eslint-disable-next-line no-console
24
24
  console.log(["[", timestamp, "] ", trimmedMessage].join(""));
25
25
  }
26
26
  else {
27
- // File logging mode - route through the file logger which adds its own timestamp.
28
- writeLogEntry("info", trimmedMessage);
27
+ // File logging mode - route through the file logger which adds its own timestamp. HTTP request logs use the default terminal color, matching info-level output.
28
+ writeLogEntry("info", trimmedMessage, null);
29
29
  }
30
30
  }
31
31
  };
@@ -1 +1 @@
1
- {"version":3,"file":"morganStream.js","sourceRoot":"","sources":["../../src/utils/morganStream.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD;;;;;GAKG;AAEH;;;;GAIG;AACH,MAAM,UAAU,kBAAkB;IAEhC,OAAO;QAEL,KAAK,EAAE,CAAC,OAAe,EAAQ,EAAE;YAE/B,8EAA8E;YAC9E,MAAM,cAAc,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;YAEtC,IAAG,gBAAgB,EAAE,EAAE,CAAC;gBAEtB,mEAAmE;gBACnE,MAAM,SAAS,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,uBAAuB,CAAC,CAAC;gBAE1D,sCAAsC;gBACtC,OAAO,CAAC,GAAG,CAAC,CAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,CAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACjE,CAAC;iBAAM,CAAC;gBAEN,kFAAkF;gBAClF,aAAa,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"morganStream.js","sourceRoot":"","sources":["../../src/utils/morganStream.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD;;;;;GAKG;AAEH;;;;GAIG;AACH,MAAM,UAAU,kBAAkB;IAEhC,OAAO;QAEL,KAAK,EAAE,CAAC,OAAe,EAAQ,EAAE;YAE/B,8EAA8E;YAC9E,MAAM,cAAc,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;YAEtC,IAAG,gBAAgB,EAAE,EAAE,CAAC;gBAEtB,mEAAmE;gBACnE,MAAM,SAAS,GAAG,eAAe,EAAE,CAAC;gBAEpC,sCAAsC;gBACtC,OAAO,CAAC,GAAG,CAAC,CAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,CAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACjE,CAAC;iBAAM,CAAC;gBAEN,gKAAgK;gBAChK,aAAa,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC;YAC9C,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -1,7 +1,7 @@
1
- import type { Nullable } from "../types/index.js";
1
+ import type { Nullable } from "../types/index.ts";
2
2
  /**
3
3
  * Checks whether a process with the given PID is still running. Uses the signal 0 technique: process.kill(pid, 0) throws ESRCH if the process does not exist,
4
- * returns successfully if it does. EPERM (permission denied) means the process exists but belongs to another user — treated as "still running" since we cannot
4
+ * returns successfully if it does. EPERM (permission denied) means the process exists but belongs to another user - treated as "still running" since we cannot
5
5
  * kill it anyway. This is cross-platform and works on macOS, Linux, and Windows without external tools.
6
6
  * @param pid - The process ID to check.
7
7
  * @returns True if the process is running, false if it has exited.
package/dist/utils/pid.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import fs from "node:fs";
2
2
  /* PID files provide a lightweight mechanism for tracking running processes across restarts and crashes. Each PID file stores a single process ID as a plain text
3
3
  * integer. On startup, the stored PID is checked via signal 0 to determine whether the process is still alive. Stale PID files from crashed or terminated processes
4
- * are handled gracefully — ENOENT on read and ESRCH on signal check are both expected and silently ignored.
4
+ * are handled gracefully - ENOENT on read and ESRCH on signal check are both expected and silently ignored.
5
5
  *
6
6
  * This module is intentionally synchronous. PID file operations run in contexts where the event loop may not be available (process.on("exit") handlers), so all
7
7
  * I/O uses the synchronous fs API and process.kill() for signaling.
8
8
  */
9
9
  /**
10
10
  * Checks whether a process with the given PID is still running. Uses the signal 0 technique: process.kill(pid, 0) throws ESRCH if the process does not exist,
11
- * returns successfully if it does. EPERM (permission denied) means the process exists but belongs to another user — treated as "still running" since we cannot
11
+ * returns successfully if it does. EPERM (permission denied) means the process exists but belongs to another user - treated as "still running" since we cannot
12
12
  * kill it anyway. This is cross-platform and works on macOS, Linux, and Windows without external tools.
13
13
  * @param pid - The process ID to check.
14
14
  * @returns True if the process is running, false if it has exited.
@@ -1,4 +1,4 @@
1
- import type { Nullable } from "../types/index.js";
1
+ import type { Nullable } from "../types/index.ts";
2
2
  export type Platform = "darwin" | "linux" | "windows";
3
3
  export type ServiceManager = "launchd" | "systemd" | "windows-scheduler";
4
4
  export declare const SERVICE_ID = "com.github.hjdhjd.prismcast";
@@ -88,8 +88,9 @@ export function getServiceFilePath() {
88
88
  return path.join(homeDir, ".config", "systemd", "user", "prismcast.service");
89
89
  }
90
90
  case "windows": {
91
- // Windows Task Scheduler doesn't use a file path in the same way. We return a marker path inside the data directory for consistency.
92
- return path.join(getDataDir(), "service-installed.marker");
91
+ // The PowerShell launcher script is the primary service file on Windows. It contains the runtime configuration (working directory, environment variables,
92
+ // node and entry point paths) and serves as the single source of truth for stale path detection. Task Scheduler invokes this launcher at user logon.
93
+ return path.join(getDataDir(), "prismcast-service.ps1");
93
94
  }
94
95
  default: {
95
96
  return "";
@@ -1 +1 @@
1
- {"version":3,"file":"platform.js","sourceRoot":"","sources":["../../src/utils/platform.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../../src/utils/platform.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,10 @@
1
+ import { type Clock } from "./clock.ts";
1
2
  /**
2
3
  * Options for retryOperation. Groups all parameters into a single object to avoid positional parameter sprawl and make the function extensible.
3
4
  */
4
5
  export interface RetryOptions<T> {
5
6
  backoffJitter?: number;
7
+ clock?: Clock;
6
8
  description: string;
7
9
  earlySuccessCheck?: () => Promise<boolean>;
8
10
  maxAttempts: number;
@@ -1,8 +1,10 @@
1
1
  /* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
2
  *
3
- * retry.ts: Retry logic with exponential backoff for PrismCast.
3
+ * retry.ts: Retry logic with exponential backoff for PrismCast. Time-dependent operations route through a Clock (see clock.ts) so tests can deterministically
4
+ * control sleeps and timeout races without depending on real-time delays - the function's nested raceWithTimeout/Promise.race/finally/await delay chain is
5
+ * exactly the shape Node's synchronous mock.timers.tick cannot drain.
4
6
  */
5
- import { delay, raceWithTimeout } from "./delay.js";
7
+ import { realClock } from "./clock.js";
6
8
  import { formatError, isSessionClosedError } from "./errors.js";
7
9
  import { LOG } from "./logger.js";
8
10
  /**
@@ -13,7 +15,7 @@ import { LOG } from "./logger.js";
13
15
  * @throws The last error encountered if all attempts fail.
14
16
  */
15
17
  export async function retryOperation(options) {
16
- const { backoffJitter = 1000, description, earlySuccessCheck, maxAttempts, maxBackoffDelay = 3000, operation, shouldAbort, timeoutMs } = options;
18
+ const { backoffJitter = 1000, clock = realClock, description, earlySuccessCheck, maxAttempts, maxBackoffDelay = 3000, operation, shouldAbort, timeoutMs } = options;
17
19
  let lastError = null;
18
20
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
19
21
  // Check if we should abort before starting this attempt. This catches cases where the page was closed during the backoff delay between retries.
@@ -25,7 +27,7 @@ export async function retryOperation(options) {
25
27
  }
26
28
  try {
27
29
  // eslint-disable-next-line no-await-in-loop
28
- return await raceWithTimeout(operation(), timeoutMs);
30
+ return await clock.raceWithTimeout(operation(), timeoutMs);
29
31
  }
30
32
  catch (error) {
31
33
  lastError = error;
@@ -49,13 +51,13 @@ export async function retryOperation(options) {
49
51
  }
50
52
  }
51
53
  // If we reach here, we're going to retry (or fail after max attempts). Now log the warning since there's an actual issue to report.
52
- LOG.warn("Attempt %s failed for %s: %s", attempt, description, formatError(error));
54
+ LOG.warn("Attempt %s failed for %s: %s.", attempt, description, formatError(error));
53
55
  // Between retry attempts, wait with exponential backoff plus random jitter.
54
56
  if (attempt < maxAttempts) {
55
- const baseDelay = Math.min(1000 * Math.pow(2, attempt - 1), maxBackoffDelay);
57
+ const baseDelay = Math.min(1000 * (2 ** (attempt - 1)), maxBackoffDelay);
56
58
  const jitter = Math.random() * backoffJitter;
57
59
  // eslint-disable-next-line no-await-in-loop
58
- await delay(baseDelay + jitter);
60
+ await clock.sleep(baseDelay + jitter);
59
61
  }
60
62
  }
61
63
  }
@@ -1 +1 @@
1
- {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAuClC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAI,OAAwB;IAE9D,MAAM,EAAE,aAAa,GAAG,IAAI,EAAE,WAAW,EAAE,iBAAiB,EAAE,WAAW,EAAE,eAAe,GAAG,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IAEjJ,IAAI,SAAS,GAAY,IAAI,CAAC;IAE9B,KAAI,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;QAEvD,gJAAgJ;QAChJ,IAAG,WAAW,EAAE,EAAE,EAAE,CAAC;YAEnB,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;QAC1E,CAAC;QAED,IAAG,OAAO,GAAG,CAAC,EAAE,CAAC;YAEf,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,iCAAiC,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;QAC3F,CAAC;QAED,IAAI,CAAC;YAEH,4CAA4C;YAC5C,OAAO,MAAM,eAAe,CAAC,SAAS,EAAE,EAAE,SAAS,CAAC,CAAC;QACvD,CAAC;QAAC,OAAM,KAAK,EAAE,CAAC;YAEd,SAAS,GAAG,KAAK,CAAC;YAElB,8HAA8H;YAC9H,IAAG,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAE/B,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,2CAA2C,EAAE,WAAW,CAAC,CAAC;gBAE7E,MAAM,KAAK,CAAC;YACd,CAAC;YAED,6JAA6J;YAC7J,gKAAgK;YAChK,IAAG,iBAAiB,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gBAEjE,IAAI,CAAC;oBAEH,4CAA4C;oBAC5C,MAAM,aAAa,GAAG,MAAM,iBAAiB,EAAE,CAAC;oBAEhD,IAAG,aAAa,EAAE,CAAC;wBAEjB,OAAO;oBACT,CAAC;gBACH,CAAC;gBAAC,OAAM,WAAW,EAAE,CAAC;oBAEpB,yDAAyD;gBAC3D,CAAC;YACH,CAAC;YAED,oIAAoI;YACpI,GAAG,CAAC,IAAI,CAAC,8BAA8B,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;YAEnF,4EAA4E;YAC5E,IAAG,OAAO,GAAG,WAAW,EAAE,CAAC;gBAEzB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;gBAC7E,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,aAAa,CAAC;gBAE7C,4CAA4C;gBAC5C,MAAM,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,SAAS,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAc,SAAS,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AA4ClC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAI,OAAwB;IAE9D,MAAM,EAAE,aAAa,GAAG,IAAI,EAAE,KAAK,GAAG,SAAS,EAAE,WAAW,EAAE,iBAAiB,EAAE,WAAW,EAAE,eAAe,GAAG,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IAEpK,IAAI,SAAS,GAAY,IAAI,CAAC;IAE9B,KAAI,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;QAEvD,gJAAgJ;QAChJ,IAAG,WAAW,EAAE,EAAE,EAAE,CAAC;YAEnB,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;QAC1E,CAAC;QAED,IAAG,OAAO,GAAG,CAAC,EAAE,CAAC;YAEf,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,iCAAiC,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;QAC3F,CAAC;QAED,IAAI,CAAC;YAEH,4CAA4C;YAC5C,OAAO,MAAM,KAAK,CAAC,eAAe,CAAC,SAAS,EAAE,EAAE,SAAS,CAAC,CAAC;QAC7D,CAAC;QAAC,OAAM,KAAK,EAAE,CAAC;YAEd,SAAS,GAAG,KAAK,CAAC;YAElB,8HAA8H;YAC9H,IAAG,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAE/B,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,2CAA2C,EAAE,WAAW,CAAC,CAAC;gBAE7E,MAAM,KAAK,CAAC;YACd,CAAC;YAED,6JAA6J;YAC7J,gKAAgK;YAChK,IAAG,iBAAiB,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gBAEjE,IAAI,CAAC;oBAEH,4CAA4C;oBAC5C,MAAM,aAAa,GAAG,MAAM,iBAAiB,EAAE,CAAC;oBAEhD,IAAG,aAAa,EAAE,CAAC;wBAEjB,OAAO;oBACT,CAAC;gBACH,CAAC;gBAAC,OAAM,WAAW,EAAE,CAAC;oBAEpB,yDAAyD;gBAC3D,CAAC;YACH,CAAC;YAED,oIAAoI;YACpI,GAAG,CAAC,IAAI,CAAC,+BAA+B,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;YAEpF,4EAA4E;YAC5E,IAAG,OAAO,GAAG,WAAW,EAAE,CAAC;gBAEzB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;gBACzE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,aAAa,CAAC;gBAE7C,4CAA4C;gBAC5C,MAAM,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,SAAS,CAAC;AAClB,CAAC"}
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Strips non-printable characters from a string and trims whitespace. Replaces `.trim()` at data-collection points to ensure string values contain only visible
3
- * content. Safe to call on already-clean strings — the regex replacement is a no-op when no non-printable characters are present.
3
+ * content. Safe to call on already-clean strings - the regex replacement is a no-op when no non-printable characters are present.
4
4
  * @param value - The string to sanitize.
5
5
  * @returns The sanitized string with non-printable characters removed and whitespace trimmed.
6
6
  */
7
7
  export declare function sanitizeString(value: string): string;
8
8
  /**
9
- * Tests whether a string contains any non-printable characters. Used for startup warnings when loading persisted data — the warning alerts users to corruption
9
+ * Tests whether a string contains any non-printable characters. Used for startup warnings when loading persisted data - the warning alerts users to corruption
10
10
  * without modifying the loaded values.
11
11
  * @param value - The string to test.
12
12
  * @returns True if the string contains at least one non-printable character.
@@ -10,12 +10,12 @@
10
10
  * spaces, and Unicode line/paragraph separators. TAB (\x09), LF (\x0A), and CR (\x0D) are preserved because they appear legitimately in multi-line text fields.
11
11
  */
12
12
  // Pattern matching non-printable characters. Covers: C0 controls except TAB/LF/CR, DEL, BOM, zero-width spaces, directional markers, and line/paragraph separators.
13
- // The no-control-regex rule is suppressed because this regex genuinely needs to match control characters — that is its entire purpose.
13
+ // The no-control-regex rule is suppressed because this regex genuinely needs to match control characters - that is its entire purpose.
14
14
  // eslint-disable-next-line no-control-regex
15
15
  const NON_PRINTABLE_PATTERN = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\uFEFF\u200B-\u200F\u2028-\u2029]/g;
16
16
  /**
17
17
  * Strips non-printable characters from a string and trims whitespace. Replaces `.trim()` at data-collection points to ensure string values contain only visible
18
- * content. Safe to call on already-clean strings — the regex replacement is a no-op when no non-printable characters are present.
18
+ * content. Safe to call on already-clean strings - the regex replacement is a no-op when no non-printable characters are present.
19
19
  * @param value - The string to sanitize.
20
20
  * @returns The sanitized string with non-printable characters removed and whitespace trimmed.
21
21
  */
@@ -23,13 +23,13 @@ export function sanitizeString(value) {
23
23
  return value.replace(NON_PRINTABLE_PATTERN, "").trim();
24
24
  }
25
25
  /**
26
- * Tests whether a string contains any non-printable characters. Used for startup warnings when loading persisted data — the warning alerts users to corruption
26
+ * Tests whether a string contains any non-printable characters. Used for startup warnings when loading persisted data - the warning alerts users to corruption
27
27
  * without modifying the loaded values.
28
28
  * @param value - The string to test.
29
29
  * @returns True if the string contains at least one non-printable character.
30
30
  */
31
31
  export function containsNonPrintable(value) {
32
- // Reset lastIndex before testing — the global flag on NON_PRINTABLE_PATTERN causes .test() to advance lastIndex on each call, which would produce alternating
32
+ // Reset lastIndex before testing - the global flag on NON_PRINTABLE_PATTERN causes .test() to advance lastIndex on each call, which would produce alternating
33
33
  // true/false results without this reset.
34
34
  NON_PRINTABLE_PATTERN.lastIndex = 0;
35
35
  return NON_PRINTABLE_PATTERN.test(value);
@@ -4,6 +4,7 @@
4
4
  */
5
5
  export interface StreamContext {
6
6
  channelName?: string;
7
+ showNameResolver?: () => string;
7
8
  streamId: string;
8
9
  url?: string;
9
10
  }
@@ -26,3 +27,9 @@ export declare function getStreamContext(): StreamContext | undefined;
26
27
  * @returns The stream ID, or undefined if not running within a stream context.
27
28
  */
28
29
  export declare function getStreamId(): string | undefined;
30
+ /**
31
+ * Resolves the current show name from the stream context's lazy resolver. Returns an empty string if no resolver is set or no show name is available. Named
32
+ * distinctly from the stream-ID-based show name lookup in the streaming module to avoid ambiguity via the barrel export in utils/index.ts.
33
+ * @returns The current show name, or an empty string.
34
+ */
35
+ export declare function resolveContextShowName(): string;
@@ -2,7 +2,7 @@
2
2
  *
3
3
  * streamContext.ts: AsyncLocalStorage-based stream context for automatic log correlation.
4
4
  */
5
- import { AsyncLocalStorage } from "async_hooks";
5
+ import { AsyncLocalStorage } from "node:async_hooks";
6
6
  // AsyncLocalStorage instance for stream context. This is the core mechanism that allows context to propagate through async calls.
7
7
  const streamContextStorage = new AsyncLocalStorage();
8
8
  /**
@@ -30,4 +30,13 @@ export function getStreamContext() {
30
30
  export function getStreamId() {
31
31
  return streamContextStorage.getStore()?.streamId;
32
32
  }
33
+ /**
34
+ * Resolves the current show name from the stream context's lazy resolver. Returns an empty string if no resolver is set or no show name is available. Named
35
+ * distinctly from the stream-ID-based show name lookup in the streaming module to avoid ambiguity via the barrel export in utils/index.ts.
36
+ * @returns The current show name, or an empty string.
37
+ */
38
+ export function resolveContextShowName() {
39
+ const resolver = streamContextStorage.getStore()?.showNameResolver;
40
+ return resolver ? resolver() : "";
41
+ }
33
42
  //# sourceMappingURL=streamContext.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"streamContext.js","sourceRoot":"","sources":["../../src/utils/streamContext.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AA4BhD,kIAAkI;AAClI,MAAM,oBAAoB,GAAG,IAAI,iBAAiB,EAAiB,CAAC;AAEpE;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAI,OAAsB,EAAE,EAAoB;IAExF,OAAO,oBAAoB,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAE9B,OAAO,oBAAoB,CAAC,QAAQ,EAAE,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW;IAEzB,OAAO,oBAAoB,CAAC,QAAQ,EAAE,EAAE,QAAQ,CAAC;AACnD,CAAC"}
1
+ {"version":3,"file":"streamContext.js","sourceRoot":"","sources":["../../src/utils/streamContext.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAgCrD,kIAAkI;AAClI,MAAM,oBAAoB,GAAG,IAAI,iBAAiB,EAAiB,CAAC;AAEpE;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAI,OAAsB,EAAE,EAAoB;IAExF,OAAO,oBAAoB,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAE9B,OAAO,oBAAoB,CAAC,QAAQ,EAAE,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW;IAEzB,OAAO,oBAAoB,CAAC,QAAQ,EAAE,EAAE,QAAQ,CAAC;AACnD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB;IAEpC,MAAM,QAAQ,GAAG,oBAAoB,CAAC,QAAQ,EAAE,EAAE,gBAAgB,CAAC;IAEnE,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACpC,CAAC"}
@@ -1,5 +1,8 @@
1
+ import { type Clock } from "./clock.ts";
1
2
  /**
2
- * Creates a lightweight elapsed-time closure using performance.now(). Call the returned function to get the elapsed milliseconds since creation.
3
- * @returns A closure that returns elapsed milliseconds as a number.
3
+ * Creates a lightweight elapsed-time closure. Captures the clock's current reading at creation and rounds the elapsed delta on each call. Production callers
4
+ * pass no argument and consume the realClock default; tests inject a fake clock for deterministic time control.
5
+ * @param clock - The clock to read time from. Defaults to realClock (which wraps performance.now()).
6
+ * @returns A closure that returns elapsed milliseconds as a rounded integer.
4
7
  */
5
- export declare function startTimer(): () => number;
8
+ export declare function startTimer(clock?: Clock): () => number;
@@ -1,13 +1,17 @@
1
1
  /* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
2
  *
3
- * timing.ts: Timing measurement utilities for PrismCast.
3
+ * timing.ts: Timing measurement utilities for PrismCast. Reads the clock through the Clock port (see clock.ts) so tests can inject a deterministic time source;
4
+ * production callers omit the argument and the default-argument wires through to realClock which delegates to performance.now().
4
5
  */
6
+ import { realClock } from "./clock.js";
5
7
  /**
6
- * Creates a lightweight elapsed-time closure using performance.now(). Call the returned function to get the elapsed milliseconds since creation.
7
- * @returns A closure that returns elapsed milliseconds as a number.
8
+ * Creates a lightweight elapsed-time closure. Captures the clock's current reading at creation and rounds the elapsed delta on each call. Production callers
9
+ * pass no argument and consume the realClock default; tests inject a fake clock for deterministic time control.
10
+ * @param clock - The clock to read time from. Defaults to realClock (which wraps performance.now()).
11
+ * @returns A closure that returns elapsed milliseconds as a rounded integer.
8
12
  */
9
- export function startTimer() {
10
- const start = performance.now();
11
- return () => Math.round(performance.now() - start);
13
+ export function startTimer(clock = realClock) {
14
+ const start = clock.now();
15
+ return () => Math.round(clock.now() - start);
12
16
  }
13
17
  //# sourceMappingURL=timing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"timing.js","sourceRoot":"","sources":["../../src/utils/timing.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,MAAM,UAAU,UAAU;IAExB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAEhC,OAAO,GAAW,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC;AAC7D,CAAC"}
1
+ {"version":3,"file":"timing.js","sourceRoot":"","sources":["../../src/utils/timing.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAc,SAAS,EAAE,MAAM,YAAY,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,QAAe,SAAS;IAEjD,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;IAE1B,OAAO,GAAW,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC;AACvD,CAAC"}
@@ -1,6 +1,7 @@
1
- import type { Nullable } from "../types/index.js";
1
+ import type { Nullable } from "../types/index.ts";
2
2
  /**
3
- * Gets the current package version from package.json.
3
+ * Gets the current package version from package.json. Resolved at module load via the JSON import attribute, so the value is structurally guaranteed and the
4
+ * lookup is a constant-time field read.
4
5
  * @returns The current version string (e.g., "1.0.7").
5
6
  */
6
7
  export declare function getPackageVersion(): string;
@@ -3,38 +3,25 @@
3
3
  * version.ts: Version checking and update notification utilities.
4
4
  */
5
5
  import { LOG } from "./logger.js";
6
- import { fileURLToPath } from "url";
7
6
  import { formatError } from "./errors.js";
8
- import { readFileSync } from "fs";
9
- import { resolve } from "path";
7
+ import packageJson from "../../package.json" with { type: "json" };
10
8
  // Package name for npm registry lookups.
11
9
  const NPM_PACKAGE_NAME = "prismcast";
12
- // Cached package version.
13
- let cachedPackageVersion = null;
14
10
  /**
15
- * Gets the current package version from package.json.
11
+ * Gets the current package version from package.json. Resolved at module load via the JSON import attribute, so the value is structurally guaranteed and the
12
+ * lookup is a constant-time field read.
16
13
  * @returns The current version string (e.g., "1.0.7").
17
14
  */
18
15
  export function getPackageVersion() {
19
- if (cachedPackageVersion) {
20
- return cachedPackageVersion;
21
- }
22
- try {
23
- // Resolve the path to package.json relative to this file. This file is in src/utils/ or dist/utils/, and package.json is in the project root.
24
- const currentDir = fileURLToPath(new URL(".", import.meta.url));
25
- const packagePath = resolve(currentDir, "../../package.json");
26
- const packageJson = JSON.parse(readFileSync(packagePath, "utf-8"));
27
- cachedPackageVersion = packageJson.version;
28
- return cachedPackageVersion;
29
- }
30
- catch {
31
- return "0.0.0";
32
- }
16
+ return packageJson.version;
33
17
  }
34
18
  // GitHub raw URL for fetching changelog.
35
19
  const CHANGELOG_URL = "https://raw.githubusercontent.com/hjdhjd/prismcast/main/Changelog.md";
36
20
  // How often to check for updates (2 hours in milliseconds).
37
21
  const UPDATE_CHECK_INTERVAL = 2 * 60 * 60 * 1000;
22
+ // Minimum interval between consecutive checkForUpdates() calls. Multiple invocations within this window collapse into a single check, preventing duplicate
23
+ // network traffic when both the startup notification path and the periodic interval timer fire close together. Bypassed when the caller passes force=true.
24
+ const UPDATE_CHECK_DEBOUNCE = 60 * 1000;
38
25
  // Cached version information.
39
26
  let cachedLatestVersion = null;
40
27
  let cachedChangelog = null;
@@ -75,7 +62,7 @@ export function isVersionLessThan(a, b) {
75
62
  */
76
63
  export async function fetchLatestVersion() {
77
64
  try {
78
- const response = await fetch("https://registry.npmjs.org/" + NPM_PACKAGE_NAME);
65
+ const response = await fetch("https://registry.npmjs.org/" + NPM_PACKAGE_NAME, { signal: AbortSignal.timeout(5000) });
79
66
  if (!response.ok) {
80
67
  return null;
81
68
  }
@@ -94,7 +81,7 @@ export async function fetchLatestVersion() {
94
81
  */
95
82
  async function fetchChangelogContent() {
96
83
  try {
97
- const response = await fetch(CHANGELOG_URL);
84
+ const response = await fetch(CHANGELOG_URL, { signal: AbortSignal.timeout(5000) });
98
85
  if (!response.ok) {
99
86
  return null;
100
87
  }
@@ -112,15 +99,15 @@ async function fetchChangelogContent() {
112
99
  * @returns The changelog entry for the version, or null if not found.
113
100
  */
114
101
  function extractVersionChangelog(changelog, version) {
115
- // Match the version header and capture everything until the next version header or end of file. The changelog format is: ## 1.0.8 (date)
116
- // Note: We use (?![^]) instead of $ for end-of-string because the m flag makes $ match end-of-line, which would stop the non-greedy *? at the first line.
117
- const pattern = new RegExp("^## " + version.replace(/\./g, "\\.") + "\\s+\\([^)]+\\)\\s*\\n([\\s\\S]*?)(?=^## \\d|(?![^]))", "m");
102
+ // Match the version header and capture everything until the next version header or end of file. The changelog format is "## 1.0.8 (date)". The end-of-string
103
+ // sentinel uses (?![^]) instead of $ because the m flag makes $ match end-of-line, which would stop the non-greedy *? at the first line.
104
+ const pattern = new RegExp("^## " + version.replaceAll(".", "\\.") + "\\s+\\([^)]+\\)\\s*\\n([\\s\\S]*?)(?=^## \\d|(?![^]))", "m");
118
105
  const match = changelog.match(pattern);
119
106
  if (!match) {
120
107
  return null;
121
108
  }
122
109
  // Clean up the entry: trim whitespace and remove leading/trailing blank lines.
123
- return match[1].trim();
110
+ return match[1]?.trim() ?? null;
124
111
  }
125
112
  /**
126
113
  * Checks for updates and caches the results.
@@ -129,8 +116,8 @@ function extractVersionChangelog(changelog, version) {
129
116
  */
130
117
  export async function checkForUpdates(currentVersion, force = false) {
131
118
  const now = Date.now();
132
- // Skip if we checked recently (within 1 minute) to avoid duplicate checks on startup, unless forced.
133
- if (!force && ((now - lastCheckTime) < 60000)) {
119
+ // Skip when a check fired within the debounce window so duplicate startup paths collapse to one network request, unless force=true bypasses the guard.
120
+ if (!force && ((now - lastCheckTime) < UPDATE_CHECK_DEBOUNCE)) {
134
121
  return;
135
122
  }
136
123
  lastCheckTime = now;