@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,202 @@
|
|
|
1
|
+
import { LOG, delay } from "../utils/index.js";
|
|
2
|
+
import { CHANNELS } from "../channels/index.js";
|
|
3
|
+
import { CONFIG } from "../config/index.js";
|
|
4
|
+
import { foxStrategy } from "./tuning/fox.js";
|
|
5
|
+
import { hboStrategy } from "./tuning/hbo.js";
|
|
6
|
+
import { huluStrategy } from "./tuning/hulu.js";
|
|
7
|
+
import { isChannelSelectionProfile } from "../types/index.js";
|
|
8
|
+
import { slingStrategy } from "./tuning/sling.js";
|
|
9
|
+
import { thumbnailRowStrategy } from "./tuning/thumbnailRow.js";
|
|
10
|
+
import { tileClickStrategy } from "./tuning/tileClick.js";
|
|
11
|
+
import { yttvStrategy } from "./tuning/youtubeTv.js";
|
|
12
|
+
/* Multi-channel streaming sites (like USA Network) present multiple channels on a single page, with a program guide for each channel. Users must select which
|
|
13
|
+
* channel they want to watch by clicking on a show in the guide. This module coordinates the dispatch to per-provider strategy functions in the tuning/ directory.
|
|
14
|
+
*
|
|
15
|
+
* Each strategy is a self-contained file under tuning/ that exports a single ChannelStrategyEntry object. The coordinator handles pre-dispatch concerns (image
|
|
16
|
+
* polling, no-op checks) and post-dispatch logging. Strategy files may import scrollAndClick() and normalizeChannelName() from this coordinator — the circular
|
|
17
|
+
* import is safe because all cross-module calls happen inside async functions long after module evaluation completes.
|
|
18
|
+
*/
|
|
19
|
+
/* Adding a new channel selection provider:
|
|
20
|
+
*
|
|
21
|
+
* 1. Create a new file in tuning/ implementing the strategy function with the ChannelStrategyHandler signature.
|
|
22
|
+
* 2. Export a single ChannelStrategyEntry object from the file. Set the hooks your provider needs:
|
|
23
|
+
* - execute (required): The strategy function that selects the channel in the provider's guide UI.
|
|
24
|
+
* - clearCache: If your strategy caches state (row positions, URLs), provide a function that clears it.
|
|
25
|
+
* - resolveDirectUrl / invalidateDirectUrl: If your strategy discovers stable watch URLs that can be reused across tunes, provide cache lookup and invalidation.
|
|
26
|
+
* - usesImageSlug: Set to true if channelSelector is an image URL slug requiring load polling before dispatch.
|
|
27
|
+
* 3. Import the entry here and add it to the strategies registry with the strategy name as the key.
|
|
28
|
+
* 4. Add the strategy name to the ChannelSelectionStrategy union type in types/index.ts.
|
|
29
|
+
* 5. Add a site profile entry in config/sites.ts that references the new strategy name.
|
|
30
|
+
*
|
|
31
|
+
* The coordinator handles all cross-cutting concerns (dispatch, cache clearing, direct URL resolution, image polling) through the ChannelStrategyEntry interface.
|
|
32
|
+
* Strategy files may import scrollAndClick(), normalizeChannelName(), and logAvailableChannels() from this module for shared utilities.
|
|
33
|
+
*/
|
|
34
|
+
// Strategy dispatch registry. Maps strategy names from ChannelSelectionStrategy to their implementation entry. Adding a new provider requires a single entry here
|
|
35
|
+
// — all cross-cutting concerns (cache clearing, direct URL resolution, image polling) are driven by the entry's hooks.
|
|
36
|
+
const strategies = {
|
|
37
|
+
foxGrid: foxStrategy,
|
|
38
|
+
guideGrid: huluStrategy,
|
|
39
|
+
hboGrid: hboStrategy,
|
|
40
|
+
slingGrid: slingStrategy,
|
|
41
|
+
thumbnailRow: thumbnailRowStrategy,
|
|
42
|
+
tileClick: tileClickStrategy,
|
|
43
|
+
youtubeGrid: yttvStrategy
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Returns a direct watch URL for the channel specified in the profile, if one can be resolved. Looks up the strategy entry's resolveDirectUrl hook and calls it
|
|
47
|
+
* with the channelSelector and page. Returns null if the strategy has no resolver, the profile has no channelSelector, or the resolver returns null.
|
|
48
|
+
* @param profile - The resolved site profile.
|
|
49
|
+
* @param page - The Puppeteer page object, passed through to the strategy's resolver for response interception setup or API calls.
|
|
50
|
+
* @returns The direct watch URL or null.
|
|
51
|
+
*/
|
|
52
|
+
export async function resolveDirectUrl(profile, page) {
|
|
53
|
+
const { channelSelection, channelSelector } = profile;
|
|
54
|
+
if (!channelSelector) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
58
|
+
return await strategies[channelSelection.strategy]?.resolveDirectUrl?.(channelSelector, page) ?? null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Invalidates the cached direct watch URL for the channel specified in the profile. Looks up the strategy entry's invalidateDirectUrl hook and calls it with
|
|
62
|
+
* the channelSelector. No-op if the strategy has no invalidator or the profile has no channelSelector.
|
|
63
|
+
* @param profile - The resolved site profile.
|
|
64
|
+
*/
|
|
65
|
+
export function invalidateDirectUrl(profile) {
|
|
66
|
+
const { channelSelection, channelSelector } = profile;
|
|
67
|
+
if (!channelSelector) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
71
|
+
strategies[channelSelection.strategy]?.invalidateDirectUrl?.(channelSelector);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Clears all channel selection caches. Called by handleBrowserDisconnect() in browser/index.ts when the browser restarts, since cached state (guide row positions,
|
|
75
|
+
* discovered page URLs, watch URLs) may be stale in a new browser session.
|
|
76
|
+
*/
|
|
77
|
+
export function clearChannelSelectionCaches() {
|
|
78
|
+
for (const entry of Object.values(strategies)) {
|
|
79
|
+
entry.clearCache?.();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Clicks at the specified coordinates after a brief settle delay. The delay allows scroll animations and lazy-loaded content to finish before the click fires.
|
|
84
|
+
* Callers are responsible for scrolling the target element into view (typically via scrollIntoView inside a page.evaluate call) before invoking this function.
|
|
85
|
+
* Exported for use by tuning strategy files (thumbnailRow, tileClick, hulu).
|
|
86
|
+
* @param page - The Puppeteer page object.
|
|
87
|
+
* @param target - The x/y coordinates to click.
|
|
88
|
+
* @returns True if the click was executed.
|
|
89
|
+
*/
|
|
90
|
+
export async function scrollAndClick(page, target) {
|
|
91
|
+
// Brief delay after scrolling for any animations or lazy-loaded content to settle.
|
|
92
|
+
await delay(200);
|
|
93
|
+
// Click the target coordinates to switch to the channel.
|
|
94
|
+
await page.mouse.click(target.x, target.y);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
// Normalizes a channel name for case-insensitive, whitespace-tolerant comparison. Trims leading and trailing whitespace, collapses internal whitespace sequences
|
|
98
|
+
// (including non-breaking spaces, tabs, and other Unicode whitespace matched by \s) into a single regular space, and lowercases. This handles data-testid values
|
|
99
|
+
// with trailing spaces (e.g., "WLS "), double spaces, or non-breaking space characters that would otherwise cause exact match failures.
|
|
100
|
+
// Exported for use by tuning strategy files (hulu, sling).
|
|
101
|
+
export function normalizeChannelName(name) {
|
|
102
|
+
return name.trim().replace(/\s+/g, " ").toLowerCase();
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Logs available channel names from a provider's guide grid when channel selection fails. Produces an actionable log message listing channel names that users can
|
|
106
|
+
* use as `channelSelector` values in user-defined channels. When `presetSuffix` is provided, channels already covered by built-in preset definitions are filtered
|
|
107
|
+
* out so users see only channels that require manual configuration. When omitted (small channel sets like Fox or HBO), all channels are logged unfiltered.
|
|
108
|
+
* @param options - Diagnostic dump configuration.
|
|
109
|
+
* @param options.additionalKnownNames - Extra names to exclude from the filtered list (e.g., CHANNEL_ALTERNATES values for YTTV).
|
|
110
|
+
* @param options.availableChannels - Sorted list of channel names discovered in the guide grid.
|
|
111
|
+
* @param options.channelName - The channelSelector value that failed to match, for the log message.
|
|
112
|
+
* @param options.guideUrl - The URL of the provider's guide page, included in the log message so users know what to set as the channel URL.
|
|
113
|
+
* @param options.presetSuffix - Key suffix to filter preset channels (e.g., "-yttv", "-hulu"). Omit for small unfiltered channel sets.
|
|
114
|
+
* @param options.providerName - Human-readable provider name for the log message (e.g., "YouTube TV", "Hulu").
|
|
115
|
+
*/
|
|
116
|
+
export function logAvailableChannels(options) {
|
|
117
|
+
const { additionalKnownNames, availableChannels, channelName, guideUrl, presetSuffix, providerName } = options;
|
|
118
|
+
if (availableChannels.length === 0) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
let filteredChannels;
|
|
122
|
+
let countLabel;
|
|
123
|
+
if (presetSuffix) {
|
|
124
|
+
// Collect all channelSelector values from preset channels with this suffix, lowercased for case-insensitive comparison.
|
|
125
|
+
const knownSelectors = Object.entries(CHANNELS)
|
|
126
|
+
.filter(([key]) => key.endsWith(presetSuffix))
|
|
127
|
+
.map(([, ch]) => (ch.channelSelector ?? "").toLowerCase())
|
|
128
|
+
.filter((s) => s.length > 0);
|
|
129
|
+
// Include additional known names (e.g., CHANNEL_ALTERNATES values for YTTV) so those are also filtered out.
|
|
130
|
+
if (additionalKnownNames) {
|
|
131
|
+
for (const name of additionalKnownNames) {
|
|
132
|
+
knownSelectors.push(name.toLowerCase());
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Filter to channels not matched by any known selector. A channel is "covered" if a preset would find it via exact match (with parenthetical suffix stripped)
|
|
136
|
+
// or prefix+digit match. This mirrors the strategy's own matching tiers so users see only channels that genuinely need manual configuration.
|
|
137
|
+
filteredChannels = availableChannels.filter((name) => {
|
|
138
|
+
const lower = name.toLowerCase();
|
|
139
|
+
const stripped = lower.replace(/ \(.*\)$/, "");
|
|
140
|
+
return !knownSelectors.some((sel) => {
|
|
141
|
+
return (stripped === sel) ||
|
|
142
|
+
(lower.startsWith(sel + " ") && (lower.length > sel.length + 1) && (lower.charCodeAt(sel.length + 1) >= 48) && (lower.charCodeAt(sel.length + 1) <= 57));
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
countLabel = "uncovered (" + String(filteredChannels.length) + " of " + String(availableChannels.length) + ")";
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// No preset suffix — log all available channels unfiltered. Used for small channel sets (Fox, HBO) where the full list is actionable without filtering.
|
|
149
|
+
filteredChannels = availableChannels;
|
|
150
|
+
countLabel = String(filteredChannels.length);
|
|
151
|
+
}
|
|
152
|
+
if (filteredChannels.length === 0) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
LOG.warn("Channel \"%s\" not found in %s guide. Create a user-defined channel with one of the names below as the Channel Selector and %s as the URL. " +
|
|
156
|
+
"Available channels (%s): %s.", channelName, providerName, guideUrl, countLabel, filteredChannels.join(", "));
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Selects a channel from a multi-channel player UI using the strategy specified in the profile. This is the main entry point for channel selection, called by
|
|
160
|
+
* tuneToChannel() after page navigation.
|
|
161
|
+
*
|
|
162
|
+
* The function handles:
|
|
163
|
+
* - Polling for channel slug image readiness before strategy dispatch (when entry.usesImageSlug is true)
|
|
164
|
+
* - Strategy dispatch based on profile.channelSelection.strategy
|
|
165
|
+
* - No-op for single-channel sites (strategy "none" or no channelSelector)
|
|
166
|
+
* - Logging of selection attempts and results
|
|
167
|
+
* @param page - The Puppeteer page object.
|
|
168
|
+
* @param profile - The resolved site profile containing channelSelection config and channelSelector slug.
|
|
169
|
+
* @returns Result object with success status and optional failure reason.
|
|
170
|
+
*/
|
|
171
|
+
export async function selectChannel(page, profile) {
|
|
172
|
+
const { channelSelection } = profile;
|
|
173
|
+
// No channel selection needed if strategy is "none" or no channelSelector is specified.
|
|
174
|
+
if ((channelSelection.strategy === "none") || !isChannelSelectionProfile(profile)) {
|
|
175
|
+
return { success: true };
|
|
176
|
+
}
|
|
177
|
+
// Look up the strategy entry before image polling so we can check entry.usesImageSlug.
|
|
178
|
+
const entry = strategies[channelSelection.strategy];
|
|
179
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
180
|
+
if (!entry) {
|
|
181
|
+
LOG.warn("Unknown channel selection strategy: %s.", channelSelection.strategy);
|
|
182
|
+
return { reason: "Unknown channel selection strategy.", success: false };
|
|
183
|
+
}
|
|
184
|
+
// Poll for the channel slug image to appear and fully load. We check both src match and load completion (img.complete + naturalWidth) to ensure the image is
|
|
185
|
+
// actually rendered before proceeding. This prevents race conditions where the img element exists with the correct src but the browser hasn't finished fetching
|
|
186
|
+
// and rendering it, which can cause layout instability and click failures. Only run for strategies that use an image URL slug as their channelSelector
|
|
187
|
+
// (thumbnailRow, tileClick). Strategies using channel names or station codes skip this.
|
|
188
|
+
if (entry.usesImageSlug) {
|
|
189
|
+
try {
|
|
190
|
+
await page.waitForFunction((slug) => {
|
|
191
|
+
return Array.from(document.querySelectorAll("img")).some((img) => img.src && img.src.includes(slug) && img.complete && (img.naturalWidth > 0));
|
|
192
|
+
}, { timeout: CONFIG.playback.channelSelectorDelay }, profile.channelSelector);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
// Timeout — the image hasn't loaded yet. Proceed anyway and let the strategy evaluate and report not-found naturally.
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Dispatch to the appropriate strategy via the registry.
|
|
199
|
+
const result = await entry.execute(page, profile);
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=channelSelection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channelSelection.js","sourceRoot":"","sources":["../../src/browser/channelSelection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Nullable } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sets the maximum supported viewport dimensions. Called by browser initialization after detecting the display size and accounting for browser chrome.
|
|
4
|
+
* @param width - Maximum viewport width in pixels.
|
|
5
|
+
* @param height - Maximum viewport height in pixels.
|
|
6
|
+
*/
|
|
7
|
+
export declare function setMaxSupportedViewport(width: number, height: number): void;
|
|
8
|
+
/**
|
|
9
|
+
* Returns the maximum supported viewport dimensions, or null if display detection has not yet completed. Callers should fall back to the configured preset when null
|
|
10
|
+
* is returned.
|
|
11
|
+
* @returns The maximum supported viewport dimensions, or null before detection.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getMaxSupportedViewport(): Nullable<{
|
|
14
|
+
height: number;
|
|
15
|
+
width: number;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Sets the browser chrome dimensions. Called by browser initialization after measuring the difference between outer and inner window dimensions.
|
|
19
|
+
* @param width - Chrome width in pixels (typically 0 or small for window borders).
|
|
20
|
+
* @param height - Chrome height in pixels (title bar + toolbar).
|
|
21
|
+
*/
|
|
22
|
+
export declare function setBrowserChrome(width: number, height: number): void;
|
|
23
|
+
/**
|
|
24
|
+
* Returns the cached browser chrome dimensions, or null if display detection has not yet completed.
|
|
25
|
+
* @returns The browser chrome dimensions, or null before detection.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getBrowserChrome(): Nullable<{
|
|
28
|
+
height: number;
|
|
29
|
+
width: number;
|
|
30
|
+
}>;
|
|
31
|
+
/**
|
|
32
|
+
* Clears the cached display dimensions. Called when browser restarts to force re-detection.
|
|
33
|
+
*/
|
|
34
|
+
export declare function clearDisplayCache(): void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/* This module provides a simple cache for display-related dimensions detected during browser initialization. Two sets of values are cached:
|
|
2
|
+
*
|
|
3
|
+
* 1. Maximum supported viewport: The largest viewport that fits on the display after accounting for browser chrome. Used by the preset system to determine if the
|
|
4
|
+
* configured preset needs to be degraded.
|
|
5
|
+
*
|
|
6
|
+
* 2. Browser chrome dimensions: The height and width of browser UI elements (title bar, toolbar, borders). Used when resizing the browser window to calculate the
|
|
7
|
+
* total window size needed for a given viewport size.
|
|
8
|
+
*
|
|
9
|
+
* This module is intentionally minimal with no imports from other project modules to avoid circular dependencies. The browser module detects dimensions and calls
|
|
10
|
+
* the setters. Other modules call the getters to access cached values.
|
|
11
|
+
*/
|
|
12
|
+
// Cached maximum supported viewport dimensions. Null before browser initialization completes display detection.
|
|
13
|
+
let maxSupportedViewport = null;
|
|
14
|
+
// Cached browser chrome dimensions (title bar, toolbar, borders). Null before browser initialization completes display detection.
|
|
15
|
+
let browserChrome = null;
|
|
16
|
+
/**
|
|
17
|
+
* Sets the maximum supported viewport dimensions. Called by browser initialization after detecting the display size and accounting for browser chrome.
|
|
18
|
+
* @param width - Maximum viewport width in pixels.
|
|
19
|
+
* @param height - Maximum viewport height in pixels.
|
|
20
|
+
*/
|
|
21
|
+
export function setMaxSupportedViewport(width, height) {
|
|
22
|
+
maxSupportedViewport = { height, width };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns the maximum supported viewport dimensions, or null if display detection has not yet completed. Callers should fall back to the configured preset when null
|
|
26
|
+
* is returned.
|
|
27
|
+
* @returns The maximum supported viewport dimensions, or null before detection.
|
|
28
|
+
*/
|
|
29
|
+
export function getMaxSupportedViewport() {
|
|
30
|
+
return maxSupportedViewport;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Sets the browser chrome dimensions. Called by browser initialization after measuring the difference between outer and inner window dimensions.
|
|
34
|
+
* @param width - Chrome width in pixels (typically 0 or small for window borders).
|
|
35
|
+
* @param height - Chrome height in pixels (title bar + toolbar).
|
|
36
|
+
*/
|
|
37
|
+
export function setBrowserChrome(width, height) {
|
|
38
|
+
browserChrome = { height, width };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Returns the cached browser chrome dimensions, or null if display detection has not yet completed.
|
|
42
|
+
* @returns The browser chrome dimensions, or null before detection.
|
|
43
|
+
*/
|
|
44
|
+
export function getBrowserChrome() {
|
|
45
|
+
return browserChrome;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Clears the cached display dimensions. Called when browser restarts to force re-detection.
|
|
49
|
+
*/
|
|
50
|
+
export function clearDisplayCache() {
|
|
51
|
+
browserChrome = null;
|
|
52
|
+
maxSupportedViewport = null;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=display.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display.js","sourceRoot":"","sources":["../../src/browser/display.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;GAUG;AAEH,gHAAgH;AAChH,IAAI,oBAAoB,GAAgD,IAAI,CAAC;AAE7E,kIAAkI;AAClI,IAAI,aAAa,GAAgD,IAAI,CAAC;AAEtE;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAa,EAAE,MAAc;IAEnE,oBAAoB,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC3C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB;IAErC,OAAO,oBAAoB,CAAC;AAC9B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa,EAAE,MAAc;IAE5D,aAAa,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAE9B,OAAO,aAAa,CAAC;AACvB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAE/B,aAAa,GAAG,IAAI,CAAC;IACrB,oBAAoB,GAAG,IAAI,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import type { Browser, LaunchOptions, Page } from "puppeteer-core";
|
|
2
|
+
import { getStream } from "puppeteer-stream";
|
|
3
|
+
import type { Nullable } from "../types/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Returns true if graceful shutdown is in progress.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isGracefulShutdown(): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Sets the graceful shutdown flag. Call this at the start of shutdown, before terminating streams, so that page close errors are suppressed.
|
|
10
|
+
*/
|
|
11
|
+
export declare function setGracefulShutdown(value: boolean): void;
|
|
12
|
+
/**
|
|
13
|
+
* Login status information returned by getLoginStatus().
|
|
14
|
+
*/
|
|
15
|
+
export interface LoginStatus {
|
|
16
|
+
active: boolean;
|
|
17
|
+
startTime: Nullable<number>;
|
|
18
|
+
url: Nullable<string>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Computes the current system status and emits it to SSE subscribers. Called when browser state changes significantly or when streams are added/removed.
|
|
22
|
+
*/
|
|
23
|
+
export declare function emitCurrentSystemStatus(): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Registers a page as managed by PrismCast. This should be called immediately after creating a page via browser.newPage(). Registered pages are tracked for stale
|
|
26
|
+
* page cleanup, while unregistered pages (manually opened, site popups, etc.) are left alone.
|
|
27
|
+
*
|
|
28
|
+
* Each registered page receives a unique ID that persists for the page's lifetime. This ID is used for comparison and staleness tracking, avoiding potential
|
|
29
|
+
* issues with Page object reference identity.
|
|
30
|
+
* @param page - The Puppeteer Page to register.
|
|
31
|
+
*/
|
|
32
|
+
export declare function registerManagedPage(page: Page): void;
|
|
33
|
+
/**
|
|
34
|
+
* Unregisters a page from PrismCast's management. This should be called when a page is being closed intentionally (during stream cleanup). Unregistering prevents the
|
|
35
|
+
* stale page cleanup from racing with intentional page closure.
|
|
36
|
+
* @param page - The Puppeteer Page to unregister.
|
|
37
|
+
*/
|
|
38
|
+
export declare function unregisterManagedPage(page: Page): void;
|
|
39
|
+
/**
|
|
40
|
+
* Gets the current data directory path. This is where Chrome profile data and extension files are stored.
|
|
41
|
+
* @returns The data directory path.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getDataDir(): string;
|
|
44
|
+
/**
|
|
45
|
+
* Ensures the data directory exists, creating it if necessary. This should be called during application startup before any operations that depend on the data
|
|
46
|
+
* directory (like browser launch or extension preparation).
|
|
47
|
+
*
|
|
48
|
+
* The data directory (~/.prismcast) stores:
|
|
49
|
+
* - Chrome profile data (cookies, local storage, session state)
|
|
50
|
+
* - Extension files (when running as a packaged executable)
|
|
51
|
+
*/
|
|
52
|
+
export declare function ensureDataDirectory(): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Ensures a clean slate for browser launch by terminating any stale Chrome processes and removing orphaned profile lock files. Chrome locks its profile directory
|
|
55
|
+
* while running, and if a previous instance crashed without releasing the lock, we cannot launch a new browser with the same profile. This function uses pkill to
|
|
56
|
+
* find and terminate any Chrome processes whose command line contains our profile directory path, then polls pgrep to verify the processes have actually exited.
|
|
57
|
+
* After process cleanup, it removes stale lock files (SingletonLock, SingletonCookie, SingletonSocket) and DevToolsActivePort from the profile directory.
|
|
58
|
+
*
|
|
59
|
+
* The termination strategy escalates from SIGTERM to SIGKILL. SIGTERM is sent first, giving Chrome up to 5 seconds to flush its profile databases (LevelDB,
|
|
60
|
+
* extension state, session storage) and exit cleanly. If Chrome does not exit, SIGKILL is sent as a fallback. This escalation is critical when called from the
|
|
61
|
+
* process exit handler — Chrome may be running normally (e.g., after a capture probe timeout), and an immediate SIGKILL would corrupt its profile databases,
|
|
62
|
+
* poisoning the Docker volume for subsequent container restarts.
|
|
63
|
+
*
|
|
64
|
+
* The file cleanup is essential for Docker deployments. Container restarts destroy Chrome processes without giving them a chance to release profile locks, but the
|
|
65
|
+
* lock files persist in the mounted volume. Without removing them, Chrome cannot start in the new container, causing a crash loop.
|
|
66
|
+
*
|
|
67
|
+
* This is called at startup before launching the browser and after closeBrowser() during shutdown. It's safe to call even when no stale processes or files exist.
|
|
68
|
+
*/
|
|
69
|
+
export declare function killStaleChrome(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Locates the Google Chrome executable on the system. The CHROME_BIN environment variable takes precedence, allowing operators to specify a non-standard
|
|
72
|
+
* installation. Otherwise, we search common installation paths across macOS, Linux, and Windows.
|
|
73
|
+
*
|
|
74
|
+
* @returns Path to the Chrome executable.
|
|
75
|
+
* @throws If no Chrome installation is found.
|
|
76
|
+
*/
|
|
77
|
+
export declare function getExecutablePath(): string;
|
|
78
|
+
/**
|
|
79
|
+
* Assembles the configuration options for launching Chrome with Puppeteer. These options are critical for reliable streaming:
|
|
80
|
+
*
|
|
81
|
+
* - Chrome flags configure the browser for unattended video playback without user interaction
|
|
82
|
+
* - Ignored default args prevent Puppeteer from disabling features we need (extensions, audio, component updates)
|
|
83
|
+
* - A persistent user data directory retains cookies and login state across restarts
|
|
84
|
+
* - Pipe mode provides a faster, more reliable connection than WebSocket
|
|
85
|
+
* @returns Puppeteer launch options.
|
|
86
|
+
*/
|
|
87
|
+
export declare function buildLaunchOptions(): LaunchOptions;
|
|
88
|
+
/**
|
|
89
|
+
* Provides access to the shared browser instance, launching one if needed. The browser is a shared resource used by all streaming sessions. This function handles:
|
|
90
|
+
*
|
|
91
|
+
* - Returning the existing browser if it's still connected
|
|
92
|
+
* - Launching a new browser if none exists or the previous one disconnected
|
|
93
|
+
* - Serializing concurrent callers so only one launch occurs at a time
|
|
94
|
+
* - Waiting for the puppeteer-stream extension to initialize
|
|
95
|
+
* - Setting up disconnect handlers for crash recovery
|
|
96
|
+
* @returns The browser instance.
|
|
97
|
+
* @throws If the browser cannot be launched.
|
|
98
|
+
*/
|
|
99
|
+
export declare function getCurrentBrowser(): Promise<Browser>;
|
|
100
|
+
/**
|
|
101
|
+
* Returns the Chrome version string captured when the browser launched, or null if the browser is not connected.
|
|
102
|
+
* @returns The Chrome version string (e.g., "Chrome/144.0.7559.110") or null.
|
|
103
|
+
*/
|
|
104
|
+
export declare function getChromeVersion(): Nullable<string>;
|
|
105
|
+
/**
|
|
106
|
+
* Checks if the browser is currently connected and usable. This is a synchronous check that can be used before attempting browser operations.
|
|
107
|
+
* @returns True if the browser is connected and ready for use, false otherwise.
|
|
108
|
+
*/
|
|
109
|
+
export declare function isBrowserConnected(): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Resizes the browser window to the effective viewport and minimizes it. This function combines viewport sizing with minimization to ensure the window is
|
|
112
|
+
* properly sized before being minimized. The resize uses the effective viewport from getEffectiveViewport(), which accounts for display size constraints and
|
|
113
|
+
* preset degradation.
|
|
114
|
+
*
|
|
115
|
+
* To avoid issues with creating temporary pages (which can cause the window to restore on macOS), we prefer using an existing page if one is available. Only if
|
|
116
|
+
* no pages exist do we create a temporary page.
|
|
117
|
+
*/
|
|
118
|
+
export declare function minimizeBrowserWindow(): Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* Gets all open browser pages (tabs). This is used by the health check endpoint to report page count and by stale page cleanup to find orphaned pages.
|
|
121
|
+
* @returns Array of pages, or empty array if the browser is not connected.
|
|
122
|
+
*/
|
|
123
|
+
export declare function getBrowserPages(): Promise<Page[]>;
|
|
124
|
+
/**
|
|
125
|
+
* Closes the browser and cleans up resources. This is called during graceful shutdown to ensure Chrome exits cleanly. After this call, the browser reference is
|
|
126
|
+
* cleared and any subsequent stream requests will launch a fresh browser.
|
|
127
|
+
*
|
|
128
|
+
* The function uses a two-stage approach to ensure Chrome actually exits:
|
|
129
|
+
* 1. Try browser.close() with a 5-second timeout (DevTools Protocol graceful close)
|
|
130
|
+
* 2. Run killStaleChrome() to catch anything Stage 1 missed, using SIGTERM→SIGKILL escalation to give Chrome a chance to flush its profile databases
|
|
131
|
+
*/
|
|
132
|
+
export declare function closeBrowser(): Promise<void>;
|
|
133
|
+
/**
|
|
134
|
+
* Starts login mode by opening a new browser tab with the specified URL and un-minimizing the browser window. The user can then authenticate with their TV
|
|
135
|
+
* provider in the visible browser.
|
|
136
|
+
*
|
|
137
|
+
* Login mode blocks new stream requests until it ends (via endLoginMode, tab close detection, or timeout).
|
|
138
|
+
* @param url - The URL to navigate to for authentication.
|
|
139
|
+
* @returns Object indicating success or failure with optional error message.
|
|
140
|
+
*/
|
|
141
|
+
export declare function startLoginMode(url: string): Promise<{
|
|
142
|
+
error?: string;
|
|
143
|
+
success: boolean;
|
|
144
|
+
}>;
|
|
145
|
+
/**
|
|
146
|
+
* Ends login mode by closing the login tab (if still open) and re-minimizing the browser window. This function is idempotent - it's safe to call multiple times
|
|
147
|
+
* or when login mode is not active.
|
|
148
|
+
*
|
|
149
|
+
* Called by:
|
|
150
|
+
* - User clicking "Done" in the web UI (POST /auth/done)
|
|
151
|
+
* - Tab close detection (user closes the tab manually)
|
|
152
|
+
* - 15-minute timeout
|
|
153
|
+
* - Browser disconnect handler (cleanup)
|
|
154
|
+
*/
|
|
155
|
+
export declare function endLoginMode(): Promise<void>;
|
|
156
|
+
/**
|
|
157
|
+
* Returns whether login mode is currently active. Used by the stream handler to block new stream requests during login.
|
|
158
|
+
* @returns True if login mode is active, false otherwise.
|
|
159
|
+
*/
|
|
160
|
+
export declare function isLoginModeActive(): boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Returns the current login status including whether active, the URL being used, and when login started. Used by the /auth/status API endpoint.
|
|
163
|
+
* @returns Login status object.
|
|
164
|
+
*/
|
|
165
|
+
export declare function getLoginStatus(): LoginStatus;
|
|
166
|
+
/**
|
|
167
|
+
* Cleans up browser pages that are not associated with active streams. This function runs periodically to catch any pages that were not properly closed during
|
|
168
|
+
* stream termination.
|
|
169
|
+
*
|
|
170
|
+
* The cleanup uses a multi-stage filtering process:
|
|
171
|
+
* 1. Only consider pages we created (in managedPageIds)
|
|
172
|
+
* 2. Exclude pages associated with active streams
|
|
173
|
+
* 3. Apply a grace period before closing (to handle race conditions)
|
|
174
|
+
* 4. Preserve at least one page to keep the browser alive
|
|
175
|
+
*/
|
|
176
|
+
export declare function cleanupStalePages(): Promise<void>;
|
|
177
|
+
/**
|
|
178
|
+
* Starts the periodic stale page cleanup interval. This should be called once during server startup, after the browser is initialized. The interval runs
|
|
179
|
+
* indefinitely until stopStalePageCleanup() is called (typically during graceful shutdown).
|
|
180
|
+
*/
|
|
181
|
+
export declare function startStalePageCleanup(): void;
|
|
182
|
+
/**
|
|
183
|
+
* Stops the stale page cleanup interval. This should be called during graceful shutdown to prevent the cleanup from running after we've started shutting down
|
|
184
|
+
* the browser and streams.
|
|
185
|
+
*/
|
|
186
|
+
export declare function stopStalePageCleanup(): void;
|
|
187
|
+
/**
|
|
188
|
+
* Starts the periodic browser restart eligibility check. This should be called once during server startup, after the browser is initialized. The interval runs
|
|
189
|
+
* indefinitely until stopBrowserRestartChecking() is called (typically during graceful shutdown).
|
|
190
|
+
*/
|
|
191
|
+
export declare function startBrowserRestartChecking(): void;
|
|
192
|
+
/**
|
|
193
|
+
* Stops the browser restart checking interval and cancels any pending quiet timer. This should be called during graceful shutdown to prevent a restart from
|
|
194
|
+
* racing with server shutdown.
|
|
195
|
+
*/
|
|
196
|
+
export declare function stopBrowserRestartChecking(): void;
|
|
197
|
+
/**
|
|
198
|
+
* Extracts the Puppeteer Stream extension files when running as a packaged executable. This copies the extension files from within the packaged binary to the
|
|
199
|
+
* filesystem where Chrome can load them.
|
|
200
|
+
*
|
|
201
|
+
* When running from source (not packaged), this function does nothing - puppeteer-stream can load the extension directly from node_modules.
|
|
202
|
+
* @throws If extension extraction fails.
|
|
203
|
+
*/
|
|
204
|
+
export declare function prepareExtension(): Promise<void>;
|
|
205
|
+
export { getStream };
|