@bnhf/prismcast 1.3.4-2026.2.19
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/LICENSE.md +7 -0
- package/README.md +347 -0
- package/bin/prismcast +6 -0
- package/dist/app.d.ts +6 -0
- package/dist/app.js +315 -0
- package/dist/app.js.map +1 -0
- package/dist/browser/cdp.d.ts +38 -0
- package/dist/browser/cdp.js +155 -0
- package/dist/browser/cdp.js.map +1 -0
- package/dist/browser/channelSelection.d.ts +65 -0
- package/dist/browser/channelSelection.js +202 -0
- package/dist/browser/channelSelection.js.map +1 -0
- package/dist/browser/display.d.ts +34 -0
- package/dist/browser/display.js +54 -0
- package/dist/browser/display.js.map +1 -0
- package/dist/browser/index.d.ts +205 -0
- package/dist/browser/index.js +1205 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/tuning/fox.d.ts +2 -0
- package/dist/browser/tuning/fox.js +83 -0
- package/dist/browser/tuning/fox.js.map +1 -0
- package/dist/browser/tuning/hbo.d.ts +2 -0
- package/dist/browser/tuning/hbo.js +237 -0
- package/dist/browser/tuning/hbo.js.map +1 -0
- package/dist/browser/tuning/hulu.d.ts +2 -0
- package/dist/browser/tuning/hulu.js +550 -0
- package/dist/browser/tuning/hulu.js.map +1 -0
- package/dist/browser/tuning/sling.d.ts +2 -0
- package/dist/browser/tuning/sling.js +518 -0
- package/dist/browser/tuning/sling.js.map +1 -0
- package/dist/browser/tuning/thumbnailRow.d.ts +2 -0
- package/dist/browser/tuning/thumbnailRow.js +108 -0
- package/dist/browser/tuning/thumbnailRow.js.map +1 -0
- package/dist/browser/tuning/tileClick.d.ts +2 -0
- package/dist/browser/tuning/tileClick.js +103 -0
- package/dist/browser/tuning/tileClick.js.map +1 -0
- package/dist/browser/tuning/youtubeTv.d.ts +2 -0
- package/dist/browser/tuning/youtubeTv.js +182 -0
- package/dist/browser/tuning/youtubeTv.js.map +1 -0
- package/dist/browser/video.d.ts +289 -0
- package/dist/browser/video.js +996 -0
- package/dist/browser/video.js.map +1 -0
- package/dist/channels/index.d.ts +3 -0
- package/dist/channels/index.js +392 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/config/index.d.ts +53 -0
- package/dist/config/index.js +233 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/presets.d.ts +98 -0
- package/dist/config/presets.js +241 -0
- package/dist/config/presets.js.map +1 -0
- package/dist/config/profiles.d.ts +79 -0
- package/dist/config/profiles.js +245 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/config/providers.d.ts +120 -0
- package/dist/config/providers.js +450 -0
- package/dist/config/providers.js.map +1 -0
- package/dist/config/sites.d.ts +22 -0
- package/dist/config/sites.js +377 -0
- package/dist/config/sites.js.map +1 -0
- package/dist/config/userChannels.d.ts +178 -0
- package/dist/config/userChannels.js +543 -0
- package/dist/config/userChannels.js.map +1 -0
- package/dist/config/userConfig.d.ts +235 -0
- package/dist/config/userConfig.js +913 -0
- package/dist/config/userConfig.js.map +1 -0
- package/dist/hdhr/channelMap.d.ts +21 -0
- package/dist/hdhr/channelMap.js +82 -0
- package/dist/hdhr/channelMap.js.map +1 -0
- package/dist/hdhr/deviceId.d.ts +11 -0
- package/dist/hdhr/deviceId.js +84 -0
- package/dist/hdhr/deviceId.js.map +1 -0
- package/dist/hdhr/discover.d.ts +6 -0
- package/dist/hdhr/discover.js +155 -0
- package/dist/hdhr/discover.js.map +1 -0
- package/dist/hdhr/index.d.ts +9 -0
- package/dist/hdhr/index.js +87 -0
- package/dist/hdhr/index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +144 -0
- package/dist/index.js.map +1 -0
- package/dist/routes/assets.d.ts +6 -0
- package/dist/routes/assets.js +79 -0
- package/dist/routes/assets.js.map +1 -0
- package/dist/routes/auth.d.ts +6 -0
- package/dist/routes/auth.js +77 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/channels.d.ts +6 -0
- package/dist/routes/channels.js +40 -0
- package/dist/routes/channels.js.map +1 -0
- package/dist/routes/components.d.ts +138 -0
- package/dist/routes/components.js +210 -0
- package/dist/routes/components.js.map +1 -0
- package/dist/routes/config.d.ts +72 -0
- package/dist/routes/config.js +1977 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/debug.d.ts +6 -0
- package/dist/routes/debug.js +274 -0
- package/dist/routes/debug.js.map +1 -0
- package/dist/routes/health.d.ts +6 -0
- package/dist/routes/health.js +85 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/hls.d.ts +6 -0
- package/dist/routes/hls.js +25 -0
- package/dist/routes/hls.js.map +1 -0
- package/dist/routes/index.d.ts +19 -0
- package/dist/routes/index.js +49 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/logs.d.ts +6 -0
- package/dist/routes/logs.js +164 -0
- package/dist/routes/logs.js.map +1 -0
- package/dist/routes/mpegts.d.ts +6 -0
- package/dist/routes/mpegts.js +19 -0
- package/dist/routes/mpegts.js.map +1 -0
- package/dist/routes/play.d.ts +6 -0
- package/dist/routes/play.js +18 -0
- package/dist/routes/play.js.map +1 -0
- package/dist/routes/playlist.d.ts +36 -0
- package/dist/routes/playlist.js +134 -0
- package/dist/routes/playlist.js.map +1 -0
- package/dist/routes/root.d.ts +6 -0
- package/dist/routes/root.js +2920 -0
- package/dist/routes/root.js.map +1 -0
- package/dist/routes/streams.d.ts +6 -0
- package/dist/routes/streams.js +88 -0
- package/dist/routes/streams.js.map +1 -0
- package/dist/routes/theme.d.ts +15 -0
- package/dist/routes/theme.js +275 -0
- package/dist/routes/theme.js.map +1 -0
- package/dist/routes/ui.d.ts +56 -0
- package/dist/routes/ui.js +354 -0
- package/dist/routes/ui.js.map +1 -0
- package/dist/service/commands.d.ts +41 -0
- package/dist/service/commands.js +391 -0
- package/dist/service/commands.js.map +1 -0
- package/dist/service/generators.d.ts +33 -0
- package/dist/service/generators.js +432 -0
- package/dist/service/generators.js.map +1 -0
- package/dist/service/index.d.ts +2 -0
- package/dist/service/index.js +7 -0
- package/dist/service/index.js.map +1 -0
- package/dist/streaming/clients.d.ts +48 -0
- package/dist/streaming/clients.js +114 -0
- package/dist/streaming/clients.js.map +1 -0
- package/dist/streaming/fmp4Segmenter.d.ts +61 -0
- package/dist/streaming/fmp4Segmenter.js +461 -0
- package/dist/streaming/fmp4Segmenter.js.map +1 -0
- package/dist/streaming/hls.d.ts +120 -0
- package/dist/streaming/hls.js +722 -0
- package/dist/streaming/hls.js.map +1 -0
- package/dist/streaming/hlsSegments.d.ts +54 -0
- package/dist/streaming/hlsSegments.js +162 -0
- package/dist/streaming/hlsSegments.js.map +1 -0
- package/dist/streaming/lifecycle.d.ts +33 -0
- package/dist/streaming/lifecycle.js +185 -0
- package/dist/streaming/lifecycle.js.map +1 -0
- package/dist/streaming/monitor.d.ts +74 -0
- package/dist/streaming/monitor.js +1310 -0
- package/dist/streaming/monitor.js.map +1 -0
- package/dist/streaming/mp4Parser.d.ts +74 -0
- package/dist/streaming/mp4Parser.js +566 -0
- package/dist/streaming/mp4Parser.js.map +1 -0
- package/dist/streaming/mpegts.d.ts +14 -0
- package/dist/streaming/mpegts.js +248 -0
- package/dist/streaming/mpegts.js.map +1 -0
- package/dist/streaming/registry.d.ts +119 -0
- package/dist/streaming/registry.js +127 -0
- package/dist/streaming/registry.js.map +1 -0
- package/dist/streaming/setup.d.ts +135 -0
- package/dist/streaming/setup.js +670 -0
- package/dist/streaming/setup.js.map +1 -0
- package/dist/streaming/showInfo.d.ts +30 -0
- package/dist/streaming/showInfo.js +362 -0
- package/dist/streaming/showInfo.js.map +1 -0
- package/dist/streaming/statusEmitter.d.ts +125 -0
- package/dist/streaming/statusEmitter.js +139 -0
- package/dist/streaming/statusEmitter.js.map +1 -0
- package/dist/types/index.d.ts +403 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/debugFilter.d.ts +38 -0
- package/dist/utils/debugFilter.js +157 -0
- package/dist/utils/debugFilter.js.map +1 -0
- package/dist/utils/delay.d.ts +6 -0
- package/dist/utils/delay.js +15 -0
- package/dist/utils/delay.js.map +1 -0
- package/dist/utils/errors.d.ts +15 -0
- package/dist/utils/errors.js +40 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/evaluate.d.ts +51 -0
- package/dist/utils/evaluate.js +124 -0
- package/dist/utils/evaluate.js.map +1 -0
- package/dist/utils/ffmpeg.d.ts +65 -0
- package/dist/utils/ffmpeg.js +317 -0
- package/dist/utils/ffmpeg.js.map +1 -0
- package/dist/utils/fileLogger.d.ts +25 -0
- package/dist/utils/fileLogger.js +248 -0
- package/dist/utils/fileLogger.js.map +1 -0
- package/dist/utils/format.d.ts +16 -0
- package/dist/utils/format.js +46 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/html.d.ts +6 -0
- package/dist/utils/html.js +24 -0
- package/dist/utils/html.js.map +1 -0
- package/dist/utils/index.d.ts +15 -0
- package/dist/utils/index.js +20 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logEmitter.d.ts +17 -0
- package/dist/utils/logEmitter.js +30 -0
- package/dist/utils/logEmitter.js.map +1 -0
- package/dist/utils/logger.d.ts +82 -0
- package/dist/utils/logger.js +219 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/m3u.d.ts +32 -0
- package/dist/utils/m3u.js +148 -0
- package/dist/utils/m3u.js.map +1 -0
- package/dist/utils/morganStream.d.ts +7 -0
- package/dist/utils/morganStream.js +33 -0
- package/dist/utils/morganStream.js.map +1 -0
- package/dist/utils/platform.d.ts +64 -0
- package/dist/utils/platform.js +157 -0
- package/dist/utils/platform.js.map +1 -0
- package/dist/utils/retry.d.ts +15 -0
- package/dist/utils/retry.js +82 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/streamContext.d.ts +28 -0
- package/dist/utils/streamContext.js +33 -0
- package/dist/utils/streamContext.js.map +1 -0
- package/dist/utils/version.d.ts +37 -0
- package/dist/utils/version.js +228 -0
- package/dist/utils/version.js.map +1 -0
- package/package.json +92 -0
- package/prismcast.png +0 -0
- package/prismcast.svg +74 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/* This module provides utilities for parsing M3U playlist files and extracting channel information. The parser handles extended M3U format with #EXTINF tags and extracts
|
|
2
|
+
* relevant attributes like tvg-name, tvg-id, and tvc-guide-stationid.
|
|
3
|
+
*
|
|
4
|
+
* Standard M3U format:
|
|
5
|
+
* #EXTM3U
|
|
6
|
+
* #EXTINF:-1 tvg-id="cnn.us" tvg-name="CNN" tvc-guide-stationid="12345",CNN Live
|
|
7
|
+
* https://example.com/cnn-stream
|
|
8
|
+
*/
|
|
9
|
+
// Maximum length for generated channel keys.
|
|
10
|
+
const MAX_KEY_LENGTH = 50;
|
|
11
|
+
/**
|
|
12
|
+
* Generates a URL-safe channel key from a display name. The key is used as the channel identifier in URLs and configuration.
|
|
13
|
+
* @param name - The display name to convert.
|
|
14
|
+
* @returns A lowercase, hyphen-separated key suitable for URLs.
|
|
15
|
+
*
|
|
16
|
+
* Examples:
|
|
17
|
+
* - "CNN Live!" → "cnn-live"
|
|
18
|
+
* - "BBC News 24/7" → "bbc-news-247"
|
|
19
|
+
* - " Spaces Everywhere " → "spaces-everywhere"
|
|
20
|
+
*/
|
|
21
|
+
export function generateChannelKey(name) {
|
|
22
|
+
return name
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.replace(/\s+/g, "-")
|
|
25
|
+
.replace(/[^a-z0-9-]/g, "")
|
|
26
|
+
.replace(/-+/g, "-")
|
|
27
|
+
.replace(/^-+|-+$/g, "")
|
|
28
|
+
.slice(0, MAX_KEY_LENGTH);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Extracts an attribute value from an #EXTINF line. Handles both quoted and unquoted attribute values.
|
|
32
|
+
* @param line - The #EXTINF line to parse.
|
|
33
|
+
* @param attribute - The attribute name to extract (e.g., "tvg-name").
|
|
34
|
+
* @returns The attribute value, or null if not found.
|
|
35
|
+
*/
|
|
36
|
+
function extractAttribute(line, attribute) {
|
|
37
|
+
// Match attribute="value" (quoted).
|
|
38
|
+
const quotedPattern = new RegExp(attribute + "=\"([^\"]*)\"", "i");
|
|
39
|
+
const quotedMatch = line.match(quotedPattern);
|
|
40
|
+
if (quotedMatch) {
|
|
41
|
+
return quotedMatch[1];
|
|
42
|
+
}
|
|
43
|
+
// Match attribute=value (unquoted, ends at space or comma).
|
|
44
|
+
const unquotedPattern = new RegExp(attribute + "=([^\\s,\"]+)", "i");
|
|
45
|
+
const unquotedMatch = line.match(unquotedPattern);
|
|
46
|
+
if (unquotedMatch) {
|
|
47
|
+
return unquotedMatch[1];
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Extracts the display name from an #EXTINF line. First tries tvg-name attribute, then falls back to the comma-separated suffix.
|
|
53
|
+
* @param line - The #EXTINF line to parse.
|
|
54
|
+
* @returns The display name, or null if not found.
|
|
55
|
+
*/
|
|
56
|
+
function extractName(line) {
|
|
57
|
+
// First try tvg-name attribute.
|
|
58
|
+
const tvgName = extractAttribute(line, "tvg-name");
|
|
59
|
+
if (tvgName && (tvgName.trim().length > 0)) {
|
|
60
|
+
return tvgName.trim();
|
|
61
|
+
}
|
|
62
|
+
// Fall back to comma suffix (the part after the last comma in #EXTINF line). Format: #EXTINF:-1 attributes,Display Name
|
|
63
|
+
const commaIndex = line.lastIndexOf(",");
|
|
64
|
+
if (commaIndex !== -1) {
|
|
65
|
+
const suffix = line.slice(commaIndex + 1).trim();
|
|
66
|
+
if (suffix.length > 0) {
|
|
67
|
+
return suffix;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Extracts the station ID from an #EXTINF line. Prioritizes tvc-guide-stationid (Channels DVR format), falls back to tvg-id.
|
|
74
|
+
* @param line - The #EXTINF line to parse.
|
|
75
|
+
* @returns The station ID, or undefined if not found.
|
|
76
|
+
*/
|
|
77
|
+
function extractStationId(line) {
|
|
78
|
+
// Prioritize tvc-guide-stationid for Channels DVR compatibility.
|
|
79
|
+
const tvcStationId = extractAttribute(line, "tvc-guide-stationid");
|
|
80
|
+
if (tvcStationId && (tvcStationId.trim().length > 0)) {
|
|
81
|
+
return tvcStationId.trim();
|
|
82
|
+
}
|
|
83
|
+
// Fall back to tvg-id.
|
|
84
|
+
const tvgId = extractAttribute(line, "tvg-id");
|
|
85
|
+
if (tvgId && (tvgId.trim().length > 0)) {
|
|
86
|
+
return tvgId.trim();
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Parses an M3U playlist and extracts channel information. Handles extended M3U format with #EXTINF tags.
|
|
92
|
+
* @param content - The M3U file content as a string.
|
|
93
|
+
* @returns Parse result containing channels and any errors encountered.
|
|
94
|
+
*/
|
|
95
|
+
export function parseM3U(content) {
|
|
96
|
+
const channels = [];
|
|
97
|
+
const errors = [];
|
|
98
|
+
const lines = content.split(/\r?\n/);
|
|
99
|
+
let pendingExtinf = null;
|
|
100
|
+
for (let i = 0; i < lines.length; i++) {
|
|
101
|
+
const lineNumber = i + 1;
|
|
102
|
+
const line = lines[i].trim();
|
|
103
|
+
// Skip empty lines and comments (except #EXTINF).
|
|
104
|
+
if ((line.length === 0) || ((line.startsWith("#")) && !line.startsWith("#EXTINF"))) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
// Parse #EXTINF line.
|
|
108
|
+
if (line.startsWith("#EXTINF:")) {
|
|
109
|
+
// If we have a pending #EXTINF without a URL, report an error.
|
|
110
|
+
if (pendingExtinf) {
|
|
111
|
+
errors.push("Line " + String(pendingExtinf.lineNumber) + ": Missing URL after #EXTINF.");
|
|
112
|
+
}
|
|
113
|
+
const name = extractName(line);
|
|
114
|
+
if (!name) {
|
|
115
|
+
errors.push("Line " + String(lineNumber) + ": Could not extract channel name from #EXTINF.");
|
|
116
|
+
pendingExtinf = null;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
pendingExtinf = {
|
|
120
|
+
lineNumber,
|
|
121
|
+
name,
|
|
122
|
+
stationId: extractStationId(line)
|
|
123
|
+
};
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
// Parse URL line (must follow an #EXTINF).
|
|
127
|
+
if (line.startsWith("http://") || line.startsWith("https://")) {
|
|
128
|
+
if (!pendingExtinf) {
|
|
129
|
+
// URL without preceding #EXTINF - skip silently (no name available).
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
channels.push({
|
|
133
|
+
name: pendingExtinf.name,
|
|
134
|
+
stationId: pendingExtinf.stationId,
|
|
135
|
+
url: line
|
|
136
|
+
});
|
|
137
|
+
pendingExtinf = null;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
// Unknown line format - skip silently.
|
|
141
|
+
}
|
|
142
|
+
// Check for trailing #EXTINF without URL.
|
|
143
|
+
if (pendingExtinf) {
|
|
144
|
+
errors.push("Line " + String(pendingExtinf.lineNumber) + ": Missing URL after #EXTINF.");
|
|
145
|
+
}
|
|
146
|
+
return { channels, errors };
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=m3u.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"m3u.js","sourceRoot":"","sources":["../../src/utils/m3u.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { StreamOptions } from "morgan";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a Morgan stream options object that routes log output based on the logging mode. When console logging is active, output goes to stdout with a timestamp
|
|
4
|
+
* prefix. When file logging is active, output goes to the file logger which adds its own timestamp.
|
|
5
|
+
* @returns StreamOptions object for Morgan configuration.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createMorganStream(): StreamOptions;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import df from "dateformat";
|
|
2
|
+
import { isConsoleLogging } from "./logger.js";
|
|
3
|
+
import { writeLogEntry } from "./fileLogger.js";
|
|
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
|
|
5
|
+
* console or the file logger based on the current logging mode, ensuring HTTP request logs follow the same path as application logs.
|
|
6
|
+
*
|
|
7
|
+
* Timestamps are added here rather than in the Morgan format string to ensure consistency: the file logger adds timestamps via writeLogEntry(), so we add them
|
|
8
|
+
* manually for console mode to match.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Creates a Morgan stream options object that routes log output based on the logging mode. When console logging is active, output goes to stdout with a timestamp
|
|
12
|
+
* prefix. When file logging is active, output goes to the file logger which adds its own timestamp.
|
|
13
|
+
* @returns StreamOptions object for Morgan configuration.
|
|
14
|
+
*/
|
|
15
|
+
export function createMorganStream() {
|
|
16
|
+
return {
|
|
17
|
+
write: (message) => {
|
|
18
|
+
// Remove trailing newline that Morgan adds since our loggers handle newlines.
|
|
19
|
+
const trimmedMessage = message.trim();
|
|
20
|
+
if (isConsoleLogging()) {
|
|
21
|
+
// Console logging mode - add timestamp prefix and write to stdout.
|
|
22
|
+
const timestamp = df(new Date(), "yyyy/mm/dd HH:MM:ss.l");
|
|
23
|
+
// eslint-disable-next-line no-console
|
|
24
|
+
console.log(["[", timestamp, "] ", trimmedMessage].join(""));
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
// File logging mode - route through the file logger which adds its own timestamp.
|
|
28
|
+
writeLogEntry("info", trimmedMessage);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=morganStream.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Nullable } from "../types/index.js";
|
|
2
|
+
export type Platform = "darwin" | "linux" | "windows";
|
|
3
|
+
export type ServiceManager = "launchd" | "systemd" | "windows-scheduler";
|
|
4
|
+
export declare const SERVICE_ID = "com.github.hjdhjd.prismcast";
|
|
5
|
+
export declare const SERVICE_NAME = "PrismCast";
|
|
6
|
+
/**
|
|
7
|
+
* Returns the current platform as a normalized string.
|
|
8
|
+
* @returns The platform: "darwin" for macOS, "linux" for Linux, "windows" for Windows.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getPlatform(): Platform;
|
|
11
|
+
/**
|
|
12
|
+
* Returns the appropriate service manager for the current platform.
|
|
13
|
+
* @returns The service manager type, or null if the platform is not supported for service installation.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getServiceManager(): Nullable<ServiceManager>;
|
|
16
|
+
/**
|
|
17
|
+
* Checks whether PrismCast is running as a managed service. This is determined by the presence of the PRISMCAST_SERVICE environment variable, which is set in the
|
|
18
|
+
* service definition file. This allows the application to adapt its restart behavior - when running as a service, exiting will trigger an automatic restart by the
|
|
19
|
+
* service manager, but when running standalone, the user must restart manually.
|
|
20
|
+
* @returns True if running as a managed service, false otherwise.
|
|
21
|
+
*/
|
|
22
|
+
export declare function isRunningAsService(): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Returns the path where the service file should be installed for the current platform.
|
|
25
|
+
* @returns The absolute path to the service file location.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getServiceFilePath(): string;
|
|
28
|
+
/**
|
|
29
|
+
* Returns the directory containing the service file for the current platform. This directory may need to be created before writing the service file.
|
|
30
|
+
* @returns The absolute path to the service file directory.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getServiceFileDirectory(): string;
|
|
33
|
+
/**
|
|
34
|
+
* Returns the full path to the Node.js executable. This is needed for service files which require absolute paths since the service environment may not have PATH set.
|
|
35
|
+
* We prefer symlink paths (e.g., /opt/homebrew/bin/node) over resolved paths (e.g., /opt/homebrew/Cellar/node/25.4.0/bin/node) so that Homebrew and similar package
|
|
36
|
+
* managers can upgrade Node without breaking the service.
|
|
37
|
+
* @returns The absolute path to the node binary, preferring symlinks when available.
|
|
38
|
+
*/
|
|
39
|
+
export declare function getNodeExecutablePath(): string;
|
|
40
|
+
/**
|
|
41
|
+
* Returns the full path to PrismCast's entry point (dist/index.js). This is needed for service files which require absolute paths.
|
|
42
|
+
* @returns The absolute path to the PrismCast entry point.
|
|
43
|
+
*/
|
|
44
|
+
export declare function getPrismCastEntryPoint(): string;
|
|
45
|
+
/**
|
|
46
|
+
* Returns the working directory for PrismCast. This is the parent of the dist directory.
|
|
47
|
+
* @returns The absolute path to the PrismCast working directory.
|
|
48
|
+
*/
|
|
49
|
+
export declare function getPrismCastWorkingDirectory(): string;
|
|
50
|
+
/**
|
|
51
|
+
* Returns the data directory path for PrismCast (~/.prismcast).
|
|
52
|
+
* @returns The absolute path to the data directory.
|
|
53
|
+
*/
|
|
54
|
+
export declare function getDataDirectory(): string;
|
|
55
|
+
/**
|
|
56
|
+
* Returns the directory path for service stdout/stderr output. This is the same as the data directory (~/.prismcast) to keep all PrismCast files in one place.
|
|
57
|
+
* @returns The absolute path to the service logs directory.
|
|
58
|
+
*/
|
|
59
|
+
export declare function getLogsDirectory(): string;
|
|
60
|
+
/**
|
|
61
|
+
* Checks if a service file exists at the expected location.
|
|
62
|
+
* @returns True if the service file exists.
|
|
63
|
+
*/
|
|
64
|
+
export declare function serviceFileExists(): boolean;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import url from "node:url";
|
|
5
|
+
// Environment variable name used to detect service mode.
|
|
6
|
+
const SERVICE_ENV_VAR = "PRISMCAST_SERVICE";
|
|
7
|
+
// Service identifier used in service files.
|
|
8
|
+
export const SERVICE_ID = "com.github.hjdhjd.prismcast";
|
|
9
|
+
// Service name for display purposes.
|
|
10
|
+
export const SERVICE_NAME = "PrismCast";
|
|
11
|
+
/**
|
|
12
|
+
* Returns the current platform as a normalized string.
|
|
13
|
+
* @returns The platform: "darwin" for macOS, "linux" for Linux, "windows" for Windows.
|
|
14
|
+
*/
|
|
15
|
+
export function getPlatform() {
|
|
16
|
+
switch (process.platform) {
|
|
17
|
+
case "darwin": {
|
|
18
|
+
return "darwin";
|
|
19
|
+
}
|
|
20
|
+
case "win32": {
|
|
21
|
+
return "windows";
|
|
22
|
+
}
|
|
23
|
+
default: {
|
|
24
|
+
return "linux";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Returns the appropriate service manager for the current platform.
|
|
30
|
+
* @returns The service manager type, or null if the platform is not supported for service installation.
|
|
31
|
+
*/
|
|
32
|
+
export function getServiceManager() {
|
|
33
|
+
switch (getPlatform()) {
|
|
34
|
+
case "darwin": {
|
|
35
|
+
return "launchd";
|
|
36
|
+
}
|
|
37
|
+
case "linux": {
|
|
38
|
+
return "systemd";
|
|
39
|
+
}
|
|
40
|
+
case "windows": {
|
|
41
|
+
return "windows-scheduler";
|
|
42
|
+
}
|
|
43
|
+
default: {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Checks whether PrismCast is running as a managed service. This is determined by the presence of the PRISMCAST_SERVICE environment variable, which is set in the
|
|
50
|
+
* service definition file. This allows the application to adapt its restart behavior - when running as a service, exiting will trigger an automatic restart by the
|
|
51
|
+
* service manager, but when running standalone, the user must restart manually.
|
|
52
|
+
* @returns True if running as a managed service, false otherwise.
|
|
53
|
+
*/
|
|
54
|
+
export function isRunningAsService() {
|
|
55
|
+
return process.env[SERVICE_ENV_VAR] === "1";
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Returns the path where the service file should be installed for the current platform.
|
|
59
|
+
* @returns The absolute path to the service file location.
|
|
60
|
+
*/
|
|
61
|
+
export function getServiceFilePath() {
|
|
62
|
+
const homeDir = os.homedir();
|
|
63
|
+
switch (getPlatform()) {
|
|
64
|
+
case "darwin": {
|
|
65
|
+
return path.join(homeDir, "Library", "LaunchAgents", SERVICE_ID + ".plist");
|
|
66
|
+
}
|
|
67
|
+
case "linux": {
|
|
68
|
+
return path.join(homeDir, ".config", "systemd", "user", "prismcast.service");
|
|
69
|
+
}
|
|
70
|
+
case "windows": {
|
|
71
|
+
// Windows Task Scheduler doesn't use a file path in the same way. We return a marker path for consistency.
|
|
72
|
+
return path.join(homeDir, ".prismcast", "service-installed.marker");
|
|
73
|
+
}
|
|
74
|
+
default: {
|
|
75
|
+
return "";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Returns the directory containing the service file for the current platform. This directory may need to be created before writing the service file.
|
|
81
|
+
* @returns The absolute path to the service file directory.
|
|
82
|
+
*/
|
|
83
|
+
export function getServiceFileDirectory() {
|
|
84
|
+
return path.dirname(getServiceFilePath());
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Returns the full path to the Node.js executable. This is needed for service files which require absolute paths since the service environment may not have PATH set.
|
|
88
|
+
* We prefer symlink paths (e.g., /opt/homebrew/bin/node) over resolved paths (e.g., /opt/homebrew/Cellar/node/25.4.0/bin/node) so that Homebrew and similar package
|
|
89
|
+
* managers can upgrade Node without breaking the service.
|
|
90
|
+
* @returns The absolute path to the node binary, preferring symlinks when available.
|
|
91
|
+
*/
|
|
92
|
+
export function getNodeExecutablePath() {
|
|
93
|
+
const resolvedPath = process.execPath;
|
|
94
|
+
// Common symlink locations for Node.js on various platforms.
|
|
95
|
+
const symlinkPaths = [
|
|
96
|
+
"/opt/homebrew/bin/node",
|
|
97
|
+
"/usr/local/bin/node",
|
|
98
|
+
"/usr/bin/node",
|
|
99
|
+
"/home/linuxbrew/.linuxbrew/bin/node"
|
|
100
|
+
];
|
|
101
|
+
// Check if any of the common symlink paths resolve to the same executable.
|
|
102
|
+
for (const symlinkPath of symlinkPaths) {
|
|
103
|
+
try {
|
|
104
|
+
const resolved = fs.realpathSync(symlinkPath);
|
|
105
|
+
if (resolved === resolvedPath) {
|
|
106
|
+
return symlinkPath;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Symlink doesn't exist or can't be resolved; try next.
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// No symlink found; fall back to the resolved path.
|
|
114
|
+
return resolvedPath;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Returns the full path to PrismCast's entry point (dist/index.js). This is needed for service files which require absolute paths.
|
|
118
|
+
* @returns The absolute path to the PrismCast entry point.
|
|
119
|
+
*/
|
|
120
|
+
export function getPrismCastEntryPoint() {
|
|
121
|
+
// Use import.meta.url to get the current file's path, then resolve to the entry point. This works regardless of how PrismCast was installed (npx, global, local).
|
|
122
|
+
// At runtime, this file is dist/utils/platform.js, so we go up two levels to dist/, then add index.js.
|
|
123
|
+
const currentFile = url.fileURLToPath(import.meta.url);
|
|
124
|
+
const utilsDir = path.dirname(currentFile);
|
|
125
|
+
const distDir = path.dirname(utilsDir);
|
|
126
|
+
return path.join(distDir, "index.js");
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Returns the working directory for PrismCast. This is the parent of the dist directory.
|
|
130
|
+
* @returns The absolute path to the PrismCast working directory.
|
|
131
|
+
*/
|
|
132
|
+
export function getPrismCastWorkingDirectory() {
|
|
133
|
+
const entryPoint = getPrismCastEntryPoint();
|
|
134
|
+
return path.dirname(path.dirname(entryPoint));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Returns the data directory path for PrismCast (~/.prismcast).
|
|
138
|
+
* @returns The absolute path to the data directory.
|
|
139
|
+
*/
|
|
140
|
+
export function getDataDirectory() {
|
|
141
|
+
return path.join(os.homedir(), ".prismcast");
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Returns the directory path for service stdout/stderr output. This is the same as the data directory (~/.prismcast) to keep all PrismCast files in one place.
|
|
145
|
+
* @returns The absolute path to the service logs directory.
|
|
146
|
+
*/
|
|
147
|
+
export function getLogsDirectory() {
|
|
148
|
+
return getDataDirectory();
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Checks if a service file exists at the expected location.
|
|
152
|
+
* @returns True if the service file exists.
|
|
153
|
+
*/
|
|
154
|
+
export function serviceFileExists() {
|
|
155
|
+
return fs.existsSync(getServiceFilePath());
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=platform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../../src/utils/platform.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implements a generic retry mechanism with exponential backoff and jitter. This function attempts an operation multiple times, waiting progressively longer between
|
|
3
|
+
* attempts to avoid overwhelming failing services. The exponential backoff with jitter prevents thundering herd problems where many clients retry simultaneously.
|
|
4
|
+
* @param operation - An async function to attempt. Should throw on failure.
|
|
5
|
+
* @param maxAttempts - Maximum number of attempts before giving up.
|
|
6
|
+
* @param timeoutMs - Timeout in milliseconds for each individual attempt.
|
|
7
|
+
* @param description - Human-readable description for logging purposes.
|
|
8
|
+
* @param earlySuccessCheck - Optional async function called after timeout errors. If it returns a truthy value, that value is returned as success instead of
|
|
9
|
+
* retrying. Useful for cases where the operation succeeded but took too long.
|
|
10
|
+
* @param shouldAbort - Optional function called before each attempt. If it returns true, retries are aborted immediately. Useful for checking if the page was closed
|
|
11
|
+
* during the backoff delay.
|
|
12
|
+
* @returns The result of the operation if successful.
|
|
13
|
+
* @throws The last error encountered if all attempts fail.
|
|
14
|
+
*/
|
|
15
|
+
export declare function retryOperation<T>(operation: () => Promise<T>, maxAttempts: number, timeoutMs: number, description: string, earlySuccessCheck?: () => Promise<boolean>, shouldAbort?: () => boolean): Promise<T | undefined>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* retry.ts: Retry logic with exponential backoff for PrismCast.
|
|
4
|
+
*/
|
|
5
|
+
import { formatError, isSessionClosedError } from "./errors.js";
|
|
6
|
+
import { CONFIG } from "../config/index.js";
|
|
7
|
+
import { LOG } from "./logger.js";
|
|
8
|
+
/* The retry system provides resilient operation execution with exponential backoff and jitter. When operations fail due to transient issues like network hiccups or
|
|
9
|
+
* slow page loads, the system automatically retries with increasing delays. The exponential backoff prevents overwhelming struggling services, while jitter prevents
|
|
10
|
+
* multiple clients from synchronizing their retry attempts.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Implements a generic retry mechanism with exponential backoff and jitter. This function attempts an operation multiple times, waiting progressively longer between
|
|
14
|
+
* attempts to avoid overwhelming failing services. The exponential backoff with jitter prevents thundering herd problems where many clients retry simultaneously.
|
|
15
|
+
* @param operation - An async function to attempt. Should throw on failure.
|
|
16
|
+
* @param maxAttempts - Maximum number of attempts before giving up.
|
|
17
|
+
* @param timeoutMs - Timeout in milliseconds for each individual attempt.
|
|
18
|
+
* @param description - Human-readable description for logging purposes.
|
|
19
|
+
* @param earlySuccessCheck - Optional async function called after timeout errors. If it returns a truthy value, that value is returned as success instead of
|
|
20
|
+
* retrying. Useful for cases where the operation succeeded but took too long.
|
|
21
|
+
* @param shouldAbort - Optional function called before each attempt. If it returns true, retries are aborted immediately. Useful for checking if the page was closed
|
|
22
|
+
* during the backoff delay.
|
|
23
|
+
* @returns The result of the operation if successful.
|
|
24
|
+
* @throws The last error encountered if all attempts fail.
|
|
25
|
+
*/
|
|
26
|
+
export async function retryOperation(operation, maxAttempts, timeoutMs, description, earlySuccessCheck, shouldAbort) {
|
|
27
|
+
let lastError = null;
|
|
28
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
29
|
+
// Check if we should abort before starting this attempt. This catches cases where the page was closed during the backoff delay between retries.
|
|
30
|
+
if (shouldAbort?.()) {
|
|
31
|
+
throw new Error("Operation aborted: abort condition met before retry.");
|
|
32
|
+
}
|
|
33
|
+
if (attempt > 1) {
|
|
34
|
+
LOG.debug("retry", "Retrying %s (attempt %s of %s).", description, attempt, maxAttempts);
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
// Race the operation against a timeout to prevent hanging on operations that never complete.
|
|
38
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
39
|
+
setTimeout(() => {
|
|
40
|
+
reject(new Error(["Operation timed out after ", String(timeoutMs), "ms."].join("")));
|
|
41
|
+
}, timeoutMs);
|
|
42
|
+
});
|
|
43
|
+
// eslint-disable-next-line no-await-in-loop
|
|
44
|
+
return await Promise.race([operation(), timeoutPromise]);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
lastError = error;
|
|
48
|
+
// If the page or session was closed, retrying is pointless. Abort immediately without warning since we're not going to retry.
|
|
49
|
+
if (isSessionClosedError(error)) {
|
|
50
|
+
LOG.debug("retry", "Page was closed, aborting retries for %s.", description);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
// For timeout errors, check if the operation actually succeeded despite the timeout. This handles cases where the page loaded and video started playing, but
|
|
54
|
+
// some wait condition like networkidle2 never completed. We check this before logging a warning because if early success passes, there's nothing to warn about.
|
|
55
|
+
if (earlySuccessCheck && formatError(error).includes("timed out")) {
|
|
56
|
+
try {
|
|
57
|
+
// eslint-disable-next-line no-await-in-loop
|
|
58
|
+
const successResult = await earlySuccessCheck();
|
|
59
|
+
if (successResult) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (_checkError) {
|
|
64
|
+
// Early success check failed, continue with retry logic.
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// 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.
|
|
68
|
+
LOG.warn("Attempt %s failed for %s: %s", attempt, description, formatError(error));
|
|
69
|
+
// Between retry attempts, wait with exponential backoff plus random jitter.
|
|
70
|
+
if (attempt < maxAttempts) {
|
|
71
|
+
const baseDelay = Math.min(1000 * Math.pow(2, attempt - 1), CONFIG.recovery.maxBackoffDelay);
|
|
72
|
+
const jitter = Math.random() * CONFIG.recovery.backoffJitter;
|
|
73
|
+
// eslint-disable-next-line no-await-in-loop
|
|
74
|
+
await new Promise((resolve) => {
|
|
75
|
+
setTimeout(resolve, baseDelay + jitter);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
throw lastError;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stream context containing metadata for the current stream operation. This interface is extensible - additional fields can be added as needed without changing
|
|
3
|
+
* function signatures throughout the codebase.
|
|
4
|
+
*/
|
|
5
|
+
export interface StreamContext {
|
|
6
|
+
channelName?: string;
|
|
7
|
+
streamId: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Runs a function within a stream context. All async operations called within the function will have access to the stream context via getStreamContext() and
|
|
12
|
+
* getStreamId(). This should be called at stream entry points (request handlers) and at the start of timer callbacks (setInterval, setTimeout) to re-establish
|
|
13
|
+
* context.
|
|
14
|
+
* @param context - The stream context containing streamId and optional metadata.
|
|
15
|
+
* @param fn - The async function to run within the context.
|
|
16
|
+
* @returns The result of the function.
|
|
17
|
+
*/
|
|
18
|
+
export declare function runWithStreamContext<T>(context: StreamContext, fn: () => Promise<T>): Promise<T>;
|
|
19
|
+
/**
|
|
20
|
+
* Retrieves the full stream context for the current async operation.
|
|
21
|
+
* @returns The stream context, or undefined if not running within a stream context.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getStreamContext(): StreamContext | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Convenience function to retrieve just the stream ID from the current context.
|
|
26
|
+
* @returns The stream ID, or undefined if not running within a stream context.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getStreamId(): string | undefined;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* streamContext.ts: AsyncLocalStorage-based stream context for automatic log correlation.
|
|
4
|
+
*/
|
|
5
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
6
|
+
// AsyncLocalStorage instance for stream context. This is the core mechanism that allows context to propagate through async calls.
|
|
7
|
+
const streamContextStorage = new AsyncLocalStorage();
|
|
8
|
+
/**
|
|
9
|
+
* Runs a function within a stream context. All async operations called within the function will have access to the stream context via getStreamContext() and
|
|
10
|
+
* getStreamId(). This should be called at stream entry points (request handlers) and at the start of timer callbacks (setInterval, setTimeout) to re-establish
|
|
11
|
+
* context.
|
|
12
|
+
* @param context - The stream context containing streamId and optional metadata.
|
|
13
|
+
* @param fn - The async function to run within the context.
|
|
14
|
+
* @returns The result of the function.
|
|
15
|
+
*/
|
|
16
|
+
export async function runWithStreamContext(context, fn) {
|
|
17
|
+
return streamContextStorage.run(context, fn);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Retrieves the full stream context for the current async operation.
|
|
21
|
+
* @returns The stream context, or undefined if not running within a stream context.
|
|
22
|
+
*/
|
|
23
|
+
export function getStreamContext() {
|
|
24
|
+
return streamContextStorage.getStore();
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Convenience function to retrieve just the stream ID from the current context.
|
|
28
|
+
* @returns The stream ID, or undefined if not running within a stream context.
|
|
29
|
+
*/
|
|
30
|
+
export function getStreamId() {
|
|
31
|
+
return streamContextStorage.getStore()?.streamId;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=streamContext.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Nullable } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Gets the current package version from package.json.
|
|
4
|
+
* @returns The current version string (e.g., "1.0.7").
|
|
5
|
+
*/
|
|
6
|
+
export declare function getPackageVersion(): string;
|
|
7
|
+
/**
|
|
8
|
+
* Checks for updates and caches the results.
|
|
9
|
+
* @param currentVersion - The currently running version.
|
|
10
|
+
* @param force - If true, bypasses the debounce check.
|
|
11
|
+
*/
|
|
12
|
+
export declare function checkForUpdates(currentVersion: string, force?: boolean): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Gets the cached latest version information.
|
|
15
|
+
* @param currentVersion - The currently running version.
|
|
16
|
+
* @returns Object with latest version and whether an update is available.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getVersionInfo(currentVersion: string): {
|
|
19
|
+
latestVersion: Nullable<string>;
|
|
20
|
+
updateAvailable: boolean;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Gets the changelog items for a specific version as an array of strings. Uses a two-phase lookup: first checks the cache, then fetches fresh data if the
|
|
24
|
+
* version isn't found. This handles both cold cache (no data yet) and stale cache (new version not in cached data) scenarios.
|
|
25
|
+
* @param version - The version to get changelog items for.
|
|
26
|
+
* @returns Array of changelog items (bullet points), or null if unavailable.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getChangelogItems(version: string): Promise<Nullable<string[]>>;
|
|
29
|
+
/**
|
|
30
|
+
* Starts periodic update checking.
|
|
31
|
+
* @param currentVersion - The currently running version.
|
|
32
|
+
*/
|
|
33
|
+
export declare function startUpdateChecking(currentVersion: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Stops periodic update checking.
|
|
36
|
+
*/
|
|
37
|
+
export declare function stopUpdateChecking(): void;
|