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.
- package/README.md +6 -6
- package/dist/app.d.ts +1 -1
- package/dist/app.js +41 -13
- package/dist/app.js.map +1 -1
- package/dist/browser/cdp.d.ts +4 -6
- package/dist/browser/cdp.js +18 -22
- package/dist/browser/cdp.js.map +1 -1
- package/dist/browser/channelSelection.d.ts +41 -11
- package/dist/browser/channelSelection.js +75 -24
- package/dist/browser/channelSelection.js.map +1 -1
- package/dist/browser/display.d.ts +1 -1
- package/dist/browser/index.d.ts +8 -8
- package/dist/browser/index.js +44 -45
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/login.d.ts +2 -2
- package/dist/browser/login.js +1 -1
- package/dist/browser/manifestInterceptor.d.ts +77 -0
- package/dist/browser/manifestInterceptor.js +323 -0
- package/dist/browser/manifestInterceptor.js.map +1 -0
- package/dist/browser/precaching.d.ts +1 -1
- package/dist/browser/precaching.js +23 -23
- package/dist/browser/precaching.js.map +1 -1
- package/dist/browser/tuning/comcastPolymer.d.ts +1 -1
- package/dist/browser/tuning/cox.d.ts +1 -1
- package/dist/browser/tuning/directv.d.ts +1 -1
- package/dist/browser/tuning/directv.js +65 -69
- package/dist/browser/tuning/directv.js.map +1 -1
- package/dist/browser/tuning/fox.d.ts +1 -1
- package/dist/browser/tuning/fox.js +172 -15
- package/dist/browser/tuning/fox.js.map +1 -1
- package/dist/browser/tuning/hbo.d.ts +1 -1
- package/dist/browser/tuning/hbo.js +7 -7
- package/dist/browser/tuning/hbo.js.map +1 -1
- package/dist/browser/tuning/hulu.d.ts +1 -1
- package/dist/browser/tuning/hulu.js +120 -113
- package/dist/browser/tuning/hulu.js.map +1 -1
- package/dist/browser/tuning/shared.d.ts +3 -3
- package/dist/browser/tuning/shared.js +5 -5
- package/dist/browser/tuning/sling.d.ts +1 -1
- package/dist/browser/tuning/sling.js +30 -30
- package/dist/browser/tuning/sling.js.map +1 -1
- package/dist/browser/tuning/spectrum.d.ts +1 -1
- package/dist/browser/tuning/spectrum.js +21 -22
- package/dist/browser/tuning/spectrum.js.map +1 -1
- package/dist/browser/tuning/thumbnailRow.d.ts +1 -1
- package/dist/browser/tuning/thumbnailRow.js +1 -1
- package/dist/browser/tuning/tileClick.d.ts +1 -1
- package/dist/browser/tuning/tileClick.js +5 -5
- package/dist/browser/tuning/xfinity.d.ts +1 -1
- package/dist/browser/tuning/youtubeTv.d.ts +1 -1
- package/dist/browser/tuning/youtubeTv.js +17 -18
- package/dist/browser/tuning/youtubeTv.js.map +1 -1
- package/dist/browser/video.d.ts +20 -6
- package/dist/browser/video.js +30 -38
- package/dist/browser/video.js.map +1 -1
- package/dist/channels/index.d.ts +1 -2
- package/dist/channels/index.js +475 -442
- package/dist/channels/index.js.map +1 -1
- package/dist/config/channelForm.d.ts +58 -0
- package/dist/config/channelForm.js +129 -0
- package/dist/config/channelForm.js.map +1 -0
- package/dist/config/consistencyProbe.d.ts +7 -0
- package/dist/config/consistencyProbe.js +143 -0
- package/dist/config/consistencyProbe.js.map +1 -0
- package/dist/config/health.d.ts +7 -6
- package/dist/config/health.js +78 -65
- package/dist/config/health.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +21 -14
- package/dist/config/index.js.map +1 -1
- package/dist/config/paths.d.ts +9 -3
- package/dist/config/paths.js +12 -4
- package/dist/config/paths.js.map +1 -1
- package/dist/config/persistence.context.d.ts +8 -0
- package/dist/config/persistence.context.js +30 -0
- package/dist/config/persistence.context.js.map +1 -0
- package/dist/config/persistence.d.ts +157 -0
- package/dist/config/persistence.js +446 -0
- package/dist/config/persistence.js.map +1 -0
- package/dist/config/presets.d.ts +14 -4
- package/dist/config/presets.js +84 -75
- package/dist/config/presets.js.map +1 -1
- package/dist/config/profiles.d.ts +2 -2
- package/dist/config/profiles.js +7 -9
- package/dist/config/profiles.js.map +1 -1
- package/dist/config/servicePacks.d.ts +46 -0
- package/dist/config/{providerPacks.js → servicePacks.js} +50 -53
- package/dist/config/servicePacks.js.map +1 -0
- package/dist/config/services.d.ts +228 -0
- package/dist/config/services.js +772 -0
- package/dist/config/services.js.map +1 -0
- package/dist/config/sites.d.ts +3 -3
- package/dist/config/sites.js +75 -75
- package/dist/config/sites.js.map +1 -1
- package/dist/config/userChannels.d.ts +522 -54
- package/dist/config/userChannels.js +1343 -366
- package/dist/config/userChannels.js.map +1 -1
- package/dist/config/userConfig.d.ts +108 -13
- package/dist/config/userConfig.js +296 -143
- package/dist/config/userConfig.js.map +1 -1
- package/dist/config/userProfiles.d.ts +34 -16
- package/dist/config/userProfiles.js +189 -100
- package/dist/config/userProfiles.js.map +1 -1
- package/dist/hdhr/channelMap.d.ts +1 -1
- package/dist/hdhr/channelMap.js +10 -16
- package/dist/hdhr/channelMap.js.map +1 -1
- package/dist/hdhr/deviceId.js +10 -9
- package/dist/hdhr/deviceId.js.map +1 -1
- package/dist/hdhr/discover.js +4 -5
- package/dist/hdhr/discover.js.map +1 -1
- package/dist/hdhr/index.js +12 -19
- package/dist/hdhr/index.js.map +1 -1
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/native/codecInference.d.ts +44 -0
- package/dist/native/codecInference.js +327 -0
- package/dist/native/codecInference.js.map +1 -0
- package/dist/native/decrypt.d.ts +1 -1
- package/dist/native/index.d.ts +5 -5
- package/dist/native/index.js +45 -67
- package/dist/native/index.js.map +1 -1
- package/dist/native/probe.d.ts +27 -9
- package/dist/native/probe.js +159 -68
- package/dist/native/probe.js.map +1 -1
- package/dist/native/proxy.d.ts +4 -2
- package/dist/native/proxy.js +56 -53
- package/dist/native/proxy.js.map +1 -1
- package/dist/native/tokenExpiry.d.ts +22 -0
- package/dist/native/tokenExpiry.js +153 -0
- package/dist/native/tokenExpiry.js.map +1 -0
- package/dist/routes/assets.js +3 -3
- package/dist/routes/assets.js.map +1 -1
- package/dist/routes/auth.js +9 -13
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/components.js +2 -2
- package/dist/routes/components.js.map +1 -1
- package/dist/routes/config/channels/endpoints/browse.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/browse.js +195 -0
- package/dist/routes/config/channels/endpoints/browse.js.map +1 -0
- package/dist/routes/config/channels/endpoints/bulk.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/bulk.js +128 -0
- package/dist/routes/config/channels/endpoints/bulk.js.map +1 -0
- package/dist/routes/config/channels/endpoints/crud.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/crud.js +504 -0
- package/dist/routes/config/channels/endpoints/crud.js.map +1 -0
- package/dist/routes/config/channels/endpoints/importExport.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/importExport.js +131 -0
- package/dist/routes/config/channels/endpoints/importExport.js.map +1 -0
- package/dist/routes/config/channels/endpoints/predefined.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/predefined.js +78 -0
- package/dist/routes/config/channels/endpoints/predefined.js.map +1 -0
- package/dist/routes/config/channels/endpoints/prefs.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/prefs.js +58 -0
- package/dist/routes/config/channels/endpoints/prefs.js.map +1 -0
- package/dist/routes/config/channels/endpoints/service.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/service.js +149 -0
- package/dist/routes/config/channels/endpoints/service.js.map +1 -0
- package/dist/routes/config/channels/endpoints/tags.d.ts +6 -0
- package/dist/routes/config/channels/endpoints/tags.js +185 -0
- package/dist/routes/config/channels/endpoints/tags.js.map +1 -0
- package/dist/routes/config/channels/http/handler.d.ts +10 -0
- package/dist/routes/config/channels/http/handler.js +20 -0
- package/dist/routes/config/channels/http/handler.js.map +1 -0
- package/dist/routes/config/channels/http/playlistHint.d.ts +25 -0
- package/dist/routes/config/channels/http/playlistHint.js +40 -0
- package/dist/routes/config/channels/http/playlistHint.js.map +1 -0
- package/dist/routes/config/channels/http/serviceWarning.d.ts +10 -0
- package/dist/routes/config/channels/http/serviceWarning.js +28 -0
- package/dist/routes/config/channels/http/serviceWarning.js.map +1 -0
- package/dist/routes/config/channels/index.d.ts +3 -3
- package/dist/routes/config/channels/index.js +2 -2
- package/dist/routes/config/channels/index.js.map +1 -1
- package/dist/routes/config/channels/{routes.d.ts → setup.d.ts} +1 -1
- package/dist/routes/config/channels/setup.js +23 -0
- package/dist/routes/config/channels/setup.js.map +1 -0
- package/dist/routes/config/channels/table.d.ts +13 -13
- package/dist/routes/config/channels/table.js +291 -184
- package/dist/routes/config/channels/table.js.map +1 -1
- package/dist/routes/config/http/envelope.d.ts +111 -0
- package/dist/routes/config/http/envelope.js +103 -0
- package/dist/routes/config/http/envelope.js.map +1 -0
- package/dist/routes/config/index.d.ts +9 -9
- package/dist/routes/config/index.js +5 -5
- package/dist/routes/config/index.js.map +1 -1
- package/dist/routes/config/{providers.js → services.js} +105 -99
- package/dist/routes/config/services.js.map +1 -0
- package/dist/routes/config/settings.d.ts +1 -1
- package/dist/routes/config/settings.js +85 -60
- package/dist/routes/config/settings.js.map +1 -1
- package/dist/routes/debug.js +17 -18
- package/dist/routes/debug.js.map +1 -1
- package/dist/routes/health.js +1 -1
- package/dist/routes/health.js.map +1 -1
- package/dist/routes/icons.js +2 -2
- package/dist/routes/icons.js.map +1 -1
- package/dist/routes/index.js +2 -2
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/logs.js +23 -34
- package/dist/routes/logs.js.map +1 -1
- package/dist/routes/mpegts.js +1 -1
- package/dist/routes/playlist.d.ts +5 -5
- package/dist/routes/playlist.js +84 -61
- package/dist/routes/playlist.js.map +1 -1
- package/dist/routes/root/content.js +82 -61
- package/dist/routes/root/content.js.map +1 -1
- package/dist/routes/root/index.js +2 -5
- package/dist/routes/root/index.js.map +1 -1
- package/dist/routes/root/scripts/channels.js +538 -581
- package/dist/routes/root/scripts/channels.js.map +1 -1
- package/dist/routes/root/scripts/config.js +868 -1022
- package/dist/routes/root/scripts/config.js.map +1 -1
- package/dist/routes/root/scripts/index.d.ts +4 -4
- package/dist/routes/root/scripts/shared.js +445 -197
- package/dist/routes/root/scripts/shared.js.map +1 -1
- package/dist/routes/root/scripts/status.d.ts +4 -0
- package/dist/routes/root/scripts/status.handlers.d.ts +182 -0
- package/dist/routes/root/scripts/status.handlers.js +713 -0
- package/dist/routes/root/scripts/status.handlers.js.map +1 -0
- package/dist/routes/root/scripts/status.js +85 -436
- package/dist/routes/root/scripts/status.js.map +1 -1
- package/dist/routes/root/styles.js +16 -8
- package/dist/routes/root/styles.js.map +1 -1
- package/dist/routes/services.d.ts +6 -0
- package/dist/routes/{providers.js → services.js} +58 -55
- package/dist/routes/services.js.map +1 -0
- package/dist/routes/sse.d.ts +16 -0
- package/dist/routes/sse.js +29 -0
- package/dist/routes/sse.js.map +1 -0
- package/dist/routes/streams.js +10 -23
- package/dist/routes/streams.js.map +1 -1
- package/dist/routes/ui.d.ts +2 -2
- package/dist/routes/ui.js +2 -0
- package/dist/routes/ui.js.map +1 -1
- package/dist/routes/upgrade.js +6 -7
- package/dist/routes/upgrade.js.map +1 -1
- package/dist/service/commands.context.d.ts +6 -0
- package/dist/service/commands.context.js +47 -0
- package/dist/service/commands.context.js.map +1 -0
- package/dist/service/commands.d.ts +53 -8
- package/dist/service/commands.js +172 -192
- package/dist/service/commands.js.map +1 -1
- package/dist/service/generators.context.d.ts +6 -0
- package/dist/service/generators.context.js +45 -0
- package/dist/service/generators.context.js.map +1 -0
- package/dist/service/generators.d.ts +56 -16
- package/dist/service/generators.js +509 -291
- package/dist/service/generators.js.map +1 -1
- package/dist/service/index.d.ts +2 -2
- package/dist/streaming/clients.d.ts +2 -2
- package/dist/streaming/clients.js +2 -2
- package/dist/streaming/codec.d.ts +2 -2
- package/dist/streaming/codec.js +0 -1
- package/dist/streaming/codec.js.map +1 -1
- package/dist/streaming/fmp4Segmenter.d.ts +2 -2
- package/dist/streaming/fmp4Segmenter.js +12 -12
- package/dist/streaming/hls.d.ts +7 -7
- package/dist/streaming/hls.js +104 -92
- package/dist/streaming/hls.js.map +1 -1
- package/dist/streaming/hlsResume.d.ts +3 -3
- package/dist/streaming/hlsResume.js +6 -6
- package/dist/streaming/hlsResume.js.map +1 -1
- package/dist/streaming/hlsSegments.d.ts +5 -2
- package/dist/streaming/hlsSegments.js +14 -9
- package/dist/streaming/hlsSegments.js.map +1 -1
- package/dist/streaming/monitor.d.ts +12 -12
- package/dist/streaming/monitor.js +98 -66
- package/dist/streaming/monitor.js.map +1 -1
- package/dist/streaming/mp4Parser.d.ts +34 -4
- package/dist/streaming/mp4Parser.js +111 -29
- package/dist/streaming/mp4Parser.js.map +1 -1
- package/dist/streaming/mpegts.d.ts +1 -1
- package/dist/streaming/mpegts.js +13 -10
- package/dist/streaming/mpegts.js.map +1 -1
- package/dist/streaming/playlistBuilder.d.ts +1 -1
- package/dist/streaming/playlistBuilder.js +2 -2
- package/dist/streaming/preroll.d.ts +9 -9
- package/dist/streaming/preroll.js +40 -42
- package/dist/streaming/preroll.js.map +1 -1
- package/dist/streaming/pretune.js +4 -4
- package/dist/streaming/pretune.js.map +1 -1
- package/dist/streaming/recovery.d.ts +3 -3
- package/dist/streaming/recovery.js +4 -4
- package/dist/streaming/registry.d.ts +6 -6
- package/dist/streaming/registry.js +4 -8
- package/dist/streaming/registry.js.map +1 -1
- package/dist/streaming/setup.d.ts +11 -10
- package/dist/streaming/setup.js +120 -71
- package/dist/streaming/setup.js.map +1 -1
- package/dist/streaming/showInfo.d.ts +9 -3
- package/dist/streaming/showInfo.js +33 -27
- package/dist/streaming/showInfo.js.map +1 -1
- package/dist/streaming/statusEmitter.d.ts +8 -7
- package/dist/streaming/statusEmitter.js +5 -5
- package/dist/streaming/statusEmitter.js.map +1 -1
- package/dist/types/channels.d.ts +139 -36
- package/dist/types/channels.js +39 -1
- package/dist/types/channels.js.map +1 -1
- package/dist/types/config.d.ts +22 -3
- package/dist/types/index.d.ts +9 -8
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/profiles.d.ts +14 -13
- package/dist/types/selection.d.ts +49 -5
- package/dist/types/shared.d.ts +1 -1
- package/dist/types/streaming.d.ts +2 -2
- package/dist/upgrade/commands.context.d.ts +6 -0
- package/dist/upgrade/commands.context.js +29 -0
- package/dist/upgrade/commands.context.js.map +1 -0
- package/dist/upgrade/commands.d.ts +29 -2
- package/dist/upgrade/commands.js +87 -134
- package/dist/upgrade/commands.js.map +1 -1
- package/dist/upgrade/detection.context.d.ts +6 -0
- package/dist/upgrade/detection.context.js +24 -0
- package/dist/upgrade/detection.context.js.map +1 -0
- package/dist/upgrade/detection.d.ts +140 -10
- package/dist/upgrade/detection.js +153 -66
- package/dist/upgrade/detection.js.map +1 -1
- package/dist/upgrade/index.d.ts +2 -2
- package/dist/utils/chromeFetch.d.ts +1 -1
- package/dist/utils/chromeFetch.js +2 -2
- package/dist/utils/clock.d.ts +14 -0
- package/dist/utils/clock.js +28 -0
- package/dist/utils/clock.js.map +1 -0
- package/dist/utils/debugFilter.d.ts +1 -1
- package/dist/utils/debugFilter.js +8 -6
- package/dist/utils/debugFilter.js.map +1 -1
- package/dist/utils/delay.d.ts +2 -3
- package/dist/utils/delay.js +10 -16
- package/dist/utils/delay.js.map +1 -1
- package/dist/utils/evaluate.js +12 -14
- package/dist/utils/evaluate.js.map +1 -1
- package/dist/utils/ffmpeg.context.d.ts +6 -0
- package/dist/utils/ffmpeg.context.js +31 -0
- package/dist/utils/ffmpeg.context.js.map +1 -0
- package/dist/utils/ffmpeg.d.ts +85 -29
- package/dist/utils/ffmpeg.js +126 -129
- package/dist/utils/ffmpeg.js.map +1 -1
- package/dist/utils/fileLogger.d.ts +18 -2
- package/dist/utils/fileLogger.js +60 -31
- package/dist/utils/fileLogger.js.map +1 -1
- package/dist/utils/format.d.ts +7 -1
- package/dist/utils/format.js +22 -2
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/html.js +1 -3
- package/dist/utils/html.js.map +1 -1
- package/dist/utils/index.d.ts +21 -20
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.js +31 -45
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/m3u.d.ts +18 -3
- package/dist/utils/m3u.js +28 -14
- package/dist/utils/m3u.js.map +1 -1
- package/dist/utils/memo.d.ts +19 -0
- package/dist/utils/memo.js +53 -0
- package/dist/utils/memo.js.map +1 -0
- package/dist/utils/morganStream.js +4 -4
- package/dist/utils/morganStream.js.map +1 -1
- package/dist/utils/pid.d.ts +2 -2
- package/dist/utils/pid.js +2 -2
- package/dist/utils/platform.d.ts +1 -1
- package/dist/utils/platform.js +3 -2
- package/dist/utils/platform.js.map +1 -1
- package/dist/utils/retry.d.ts +2 -0
- package/dist/utils/retry.js +9 -7
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/sanitize.d.ts +2 -2
- package/dist/utils/sanitize.js +4 -4
- package/dist/utils/streamContext.d.ts +7 -0
- package/dist/utils/streamContext.js +10 -1
- package/dist/utils/streamContext.js.map +1 -1
- package/dist/utils/timing.d.ts +6 -3
- package/dist/utils/timing.js +10 -6
- package/dist/utils/timing.js.map +1 -1
- package/dist/utils/version.d.ts +3 -2
- package/dist/utils/version.js +15 -28
- package/dist/utils/version.js.map +1 -1
- package/package.json +14 -10
- package/dist/config/providerPacks.d.ts +0 -46
- package/dist/config/providerPacks.js.map +0 -1
- package/dist/config/providers.d.ts +0 -175
- package/dist/config/providers.js +0 -691
- package/dist/config/providers.js.map +0 -1
- package/dist/native/intercept.d.ts +0 -35
- package/dist/native/intercept.js +0 -184
- package/dist/native/intercept.js.map +0 -1
- package/dist/routes/config/channels/routes.js +0 -1477
- package/dist/routes/config/channels/routes.js.map +0 -1
- package/dist/routes/config/providers.js.map +0 -1
- package/dist/routes/providers.d.ts +0 -6
- package/dist/routes/providers.js.map +0 -1
- /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!"
|
|
18
|
-
* - "BBC News 24/7"
|
|
19
|
-
* - " 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
|
|
34
|
-
if (
|
|
35
|
-
return
|
|
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
|
|
40
|
-
|
|
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 (
|
|
108
|
+
for (const [i, rawLine] of lines.entries()) {
|
|
95
109
|
const lineNumber = i + 1;
|
|
96
|
-
const line =
|
|
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;
|
package/dist/utils/m3u.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"m3u.js","sourceRoot":"","sources":["../../src/utils/m3u.ts"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AAEH,6CAA6C;AAC7C,MAAM,cAAc,GAAG,EAAE,CAAC;
|
|
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
|
|
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 =
|
|
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,
|
|
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"}
|
package/dist/utils/pid.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Nullable } from "../types/index.
|
|
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
|
|
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
|
|
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
|
|
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.
|
package/dist/utils/platform.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Nullable } from "../types/index.
|
|
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";
|
package/dist/utils/platform.js
CHANGED
|
@@ -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
|
-
//
|
|
92
|
-
|
|
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":"AAKA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,GAAG,MAAM,UAAU,CAAC;AAY3B,2DAA2D;AAC3D,MAAM,iBAAiB,GAAG,qBAAqB,CAAC;AAEhD,yDAAyD;AACzD,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAE5C,4CAA4C;AAC5C,MAAM,CAAC,MAAM,UAAU,GAAG,6BAA6B,CAAC;AAExD,qCAAqC;AACrC,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAC;AAExC;;;GAGG;AACH,MAAM,UAAU,WAAW;IAEzB,QAAO,OAAO,CAAC,QAAQ,EAAE,CAAC;QAExB,KAAK,QAAQ,CAAC,CAAC,CAAC;YAEd,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,KAAK,OAAO,CAAC,CAAC,CAAC;YAEb,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO,CAAC,CAAC,CAAC;YAER,OAAO,OAAO,CAAC;QACjB,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAE/B,QAAO,WAAW,EAAE,EAAE,CAAC;QAErB,KAAK,QAAQ,CAAC,CAAC,CAAC;YAEd,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,KAAK,OAAO,CAAC,CAAC,CAAC;YAEb,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,KAAK,SAAS,CAAC,CAAC,CAAC;YAEf,OAAO,mBAAmB,CAAC;QAC7B,CAAC;QAED,OAAO,CAAC,CAAC,CAAC;YAER,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB;IAEhC,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,KAAK,GAAG,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB;IAElC,IAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,KAAK,GAAG,EAAE,CAAC;QAE1C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,wHAAwH;IACxH,IAAI,CAAC;QAEH,OAAO,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QAEP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAEhC,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;IAE7B,QAAO,WAAW,EAAE,EAAE,CAAC;QAErB,KAAK,QAAQ,CAAC,CAAC,CAAC;YAEd,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,UAAU,GAAG,QAAQ,CAAC,CAAC;QAC9E,CAAC;QAED,KAAK,OAAO,CAAC,CAAC,CAAC;YAEb,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,mBAAmB,CAAC,CAAC;QAC/E,CAAC;QAED,KAAK,SAAS,CAAC,CAAC,CAAC;YAEf,
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../../src/utils/platform.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/retry.d.ts
CHANGED
|
@@ -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;
|
package/dist/utils/retry.js
CHANGED
|
@@ -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 {
|
|
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 *
|
|
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
|
|
60
|
+
await clock.sleep(baseDelay + jitter);
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
63
|
}
|
package/dist/utils/retry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AAAA
|
|
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"}
|
package/dist/utils/sanitize.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
package/dist/utils/sanitize.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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"}
|
package/dist/utils/timing.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { type Clock } from "./clock.ts";
|
|
1
2
|
/**
|
|
2
|
-
* Creates a lightweight elapsed-time closure
|
|
3
|
-
*
|
|
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;
|
package/dist/utils/timing.js
CHANGED
|
@@ -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
|
|
7
|
-
*
|
|
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 =
|
|
11
|
-
return () => Math.round(
|
|
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
|
package/dist/utils/timing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timing.js","sourceRoot":"","sources":["../../src/utils/timing.ts"],"names":[],"mappings":"AAAA
|
|
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"}
|
package/dist/utils/version.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { Nullable } from "../types/index.
|
|
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;
|
package/dist/utils/version.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
116
|
-
//
|
|
117
|
-
const pattern = new RegExp("^## " + version.
|
|
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]
|
|
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
|
|
133
|
-
if (!force && ((now - lastCheckTime) <
|
|
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;
|