prismcast 1.13.0 → 1.14.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/dist/app.d.ts +88 -6
- package/dist/app.js +287 -154
- package/dist/app.js.map +1 -1
- package/dist/browser/cdp.d.ts +6 -17
- package/dist/browser/cdp.js +6 -57
- package/dist/browser/cdp.js.map +1 -1
- package/dist/browser/index.d.ts +106 -23
- package/dist/browser/index.js +292 -225
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/precaching.d.ts +25 -11
- package/dist/browser/precaching.js +245 -97
- package/dist/browser/precaching.js.map +1 -1
- package/dist/browser/tabCapture.js +3 -10
- package/dist/browser/tabCapture.js.map +1 -1
- package/dist/browser/tuning/comcastPolymer.js +13 -14
- package/dist/browser/tuning/comcastPolymer.js.map +1 -1
- package/dist/browser/tuning/hbo.js +34 -17
- package/dist/browser/tuning/hbo.js.map +1 -1
- package/dist/browser/video.d.ts +1 -1
- package/dist/browser/video.js +3 -3
- package/dist/channels/index.js +5 -5
- package/dist/channels/index.js.map +1 -1
- package/dist/cliHelp.d.ts +11 -0
- package/dist/cliHelp.js +148 -0
- package/dist/cliHelp.js.map +1 -0
- package/dist/config/channelForm.js +5 -4
- package/dist/config/channelForm.js.map +1 -1
- package/dist/config/consistencyProbe.d.ts +2 -4
- package/dist/config/consistencyProbe.js +8 -51
- package/dist/config/consistencyProbe.js.map +1 -1
- package/dist/config/index.d.ts +107 -55
- package/dist/config/index.js +499 -267
- package/dist/config/index.js.map +1 -1
- package/dist/config/paths.d.ts +13 -8
- package/dist/config/paths.js +21 -25
- package/dist/config/paths.js.map +1 -1
- package/dist/config/persistence.d.ts +57 -11
- package/dist/config/persistence.js +127 -44
- package/dist/config/persistence.js.map +1 -1
- package/dist/config/reactivity.d.ts +60 -36
- package/dist/config/reactivity.js +89 -116
- package/dist/config/reactivity.js.map +1 -1
- package/dist/config/services.d.ts +61 -26
- package/dist/config/services.js +137 -68
- package/dist/config/services.js.map +1 -1
- package/dist/config/streamSettings.d.ts +24 -0
- package/dist/config/streamSettings.js +17 -0
- package/dist/config/streamSettings.js.map +1 -0
- package/dist/config/userChannels.d.ts +60 -52
- package/dist/config/userChannels.js +138 -188
- package/dist/config/userChannels.js.map +1 -1
- package/dist/config/userConfig.d.ts +224 -61
- package/dist/config/userConfig.js +351 -134
- package/dist/config/userConfig.js.map +1 -1
- package/dist/hdhr/discover.d.ts +7 -1
- package/dist/hdhr/discover.js +12 -6
- package/dist/hdhr/discover.js.map +1 -1
- package/dist/hdhr/index.d.ts +51 -10
- package/dist/hdhr/index.js +108 -112
- package/dist/hdhr/index.js.map +1 -1
- package/dist/hdhr/udp.d.ts +11 -4
- package/dist/hdhr/udp.js +25 -20
- package/dist/hdhr/udp.js.map +1 -1
- package/dist/index.js +16 -143
- package/dist/index.js.map +1 -1
- package/dist/native/index.d.ts +1 -1
- package/dist/native/index.js +4 -3
- package/dist/native/index.js.map +1 -1
- package/dist/native/proxy.d.ts +1 -1
- package/dist/native/proxy.js +36 -28
- package/dist/native/proxy.js.map +1 -1
- package/dist/routes/cdp.d.ts +7 -6
- package/dist/routes/cdp.js +9 -8
- package/dist/routes/cdp.js.map +1 -1
- package/dist/routes/components.d.ts +23 -5
- package/dist/routes/components.js +21 -4
- package/dist/routes/components.js.map +1 -1
- package/dist/routes/config/channels/endpoints/browse.js +39 -29
- package/dist/routes/config/channels/endpoints/browse.js.map +1 -1
- package/dist/routes/config/channels/endpoints/bulk.js +2 -6
- package/dist/routes/config/channels/endpoints/bulk.js.map +1 -1
- package/dist/routes/config/channels/endpoints/predefined.js +5 -15
- package/dist/routes/config/channels/endpoints/predefined.js.map +1 -1
- package/dist/routes/config/channels/endpoints/prefs.js +7 -6
- package/dist/routes/config/channels/endpoints/prefs.js.map +1 -1
- package/dist/routes/config/channels/endpoints/service.js +6 -5
- package/dist/routes/config/channels/endpoints/service.js.map +1 -1
- package/dist/routes/config/channels/endpoints/tags.js +4 -16
- package/dist/routes/config/channels/endpoints/tags.js.map +1 -1
- package/dist/routes/config/channels/table.d.ts +2 -1
- package/dist/routes/config/channels/table.js +11 -13
- package/dist/routes/config/channels/table.js.map +1 -1
- package/dist/routes/config/http/envelope.d.ts +6 -3
- package/dist/routes/config/http/envelope.js +18 -5
- package/dist/routes/config/http/envelope.js.map +1 -1
- package/dist/routes/config/index.d.ts +21 -13
- package/dist/routes/config/index.js +47 -34
- package/dist/routes/config/index.js.map +1 -1
- package/dist/routes/config/settings.d.ts +43 -6
- package/dist/routes/config/settings.js +225 -125
- package/dist/routes/config/settings.js.map +1 -1
- package/dist/routes/config/vocabulary.d.ts +24 -0
- package/dist/routes/config/vocabulary.js +33 -0
- package/dist/routes/config/vocabulary.js.map +1 -0
- package/dist/routes/debug.js +28 -13
- package/dist/routes/debug.js.map +1 -1
- package/dist/routes/root/content.js +33 -26
- package/dist/routes/root/content.js.map +1 -1
- package/dist/routes/root/scripts/channels.js +23 -11
- package/dist/routes/root/scripts/channels.js.map +1 -1
- package/dist/routes/root/scripts/config.d.ts +1 -1
- package/dist/routes/root/scripts/config.js +127 -79
- package/dist/routes/root/scripts/config.js.map +1 -1
- package/dist/routes/root/scripts/shared.js +34 -23
- package/dist/routes/root/scripts/shared.js.map +1 -1
- package/dist/routes/root/scripts/status.handlers.d.ts +8 -13
- package/dist/routes/root/scripts/status.handlers.js +35 -21
- package/dist/routes/root/scripts/status.handlers.js.map +1 -1
- package/dist/routes/root/styles.js +13 -16
- package/dist/routes/root/styles.js.map +1 -1
- package/dist/routes/services.js +12 -16
- package/dist/routes/services.js.map +1 -1
- package/dist/routes/theme.js +12 -12
- package/dist/routes/theme.js.map +1 -1
- package/dist/routes/ui.d.ts +4 -3
- package/dist/routes/ui.js +17 -13
- package/dist/routes/ui.js.map +1 -1
- package/dist/service/generators.js +12 -9
- package/dist/service/generators.js.map +1 -1
- package/dist/streaming/captureSession.d.ts +4 -4
- package/dist/streaming/captureSession.js +6 -7
- package/dist/streaming/captureSession.js.map +1 -1
- package/dist/streaming/codec.d.ts +12 -2
- package/dist/streaming/codec.js +40 -28
- package/dist/streaming/codec.js.map +1 -1
- package/dist/streaming/fmp4Segmenter.d.ts +14 -4
- package/dist/streaming/fmp4Segmenter.js +99 -79
- package/dist/streaming/fmp4Segmenter.js.map +1 -1
- package/dist/streaming/hls.d.ts +21 -14
- package/dist/streaming/hls.js +148 -122
- package/dist/streaming/hls.js.map +1 -1
- package/dist/streaming/hlsResume.d.ts +36 -13
- package/dist/streaming/hlsResume.js +68 -37
- package/dist/streaming/hlsResume.js.map +1 -1
- package/dist/streaming/monitor.d.ts +5 -3
- package/dist/streaming/monitor.js +47 -37
- package/dist/streaming/monitor.js.map +1 -1
- package/dist/streaming/mp4Parser.d.ts +29 -4
- package/dist/streaming/mp4Parser.js +49 -7
- package/dist/streaming/mp4Parser.js.map +1 -1
- package/dist/streaming/playlistBuilder.d.ts +1 -2
- package/dist/streaming/playlistBuilder.js +2 -3
- package/dist/streaming/playlistBuilder.js.map +1 -1
- package/dist/streaming/preroll.d.ts +43 -16
- package/dist/streaming/preroll.js +112 -62
- package/dist/streaming/preroll.js.map +1 -1
- package/dist/streaming/pretune.d.ts +2 -2
- package/dist/streaming/pretune.js +11 -7
- package/dist/streaming/pretune.js.map +1 -1
- package/dist/streaming/recovery.d.ts +29 -2
- package/dist/streaming/recovery.js +43 -4
- package/dist/streaming/recovery.js.map +1 -1
- package/dist/streaming/registry.d.ts +13 -10
- package/dist/streaming/registry.js +6 -5
- package/dist/streaming/registry.js.map +1 -1
- package/dist/streaming/setup.d.ts +29 -20
- package/dist/streaming/setup.js +181 -170
- package/dist/streaming/setup.js.map +1 -1
- package/dist/streaming/showInfo.d.ts +24 -12
- package/dist/streaming/showInfo.js +120 -113
- package/dist/streaming/showInfo.js.map +1 -1
- package/dist/streaming/statusEmitter.d.ts +15 -10
- package/dist/streaming/statusEmitter.js +24 -20
- package/dist/streaming/statusEmitter.js.map +1 -1
- package/dist/types/config.d.ts +24 -13
- package/dist/types/config.js +5 -1
- package/dist/types/config.js.map +1 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.js +2 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/profiles.d.ts +2 -2
- package/dist/types/selection.d.ts +12 -10
- package/dist/types/streaming.d.ts +12 -5
- package/dist/types/streaming.js +12 -5
- package/dist/types/streaming.js.map +1 -1
- package/dist/utils/debugFilter.d.ts +3 -2
- package/dist/utils/debugFilter.js +7 -5
- package/dist/utils/debugFilter.js.map +1 -1
- package/dist/{hdhr → utils}/deviceId.d.ts +1 -1
- package/dist/{hdhr → utils}/deviceId.js +11 -29
- package/dist/utils/deviceId.js.map +1 -0
- package/dist/utils/errors.d.ts +7 -0
- package/dist/utils/errors.js +12 -3
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/fileLogger.d.ts +23 -12
- package/dist/utils/fileLogger.js +73 -41
- package/dist/utils/fileLogger.js.map +1 -1
- package/dist/utils/httpErrors.d.ts +13 -0
- package/dist/utils/httpErrors.js +22 -0
- package/dist/utils/httpErrors.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.d.ts +9 -8
- package/dist/utils/logger.js +64 -20
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/markup.js +8 -3
- package/dist/utils/markup.js.map +1 -1
- package/dist/utils/plainObject.d.ts +6 -0
- package/dist/utils/plainObject.js +18 -0
- package/dist/utils/plainObject.js.map +1 -0
- package/dist/utils/runtimeIdentity.context.js +1 -29
- package/dist/utils/runtimeIdentity.context.js.map +1 -1
- package/dist/utils/runtimeIdentity.d.ts +17 -6
- package/dist/utils/runtimeIdentity.js +50 -17
- package/dist/utils/runtimeIdentity.js.map +1 -1
- package/package.json +5 -5
- package/dist/hdhr/deviceId.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LOG, extractDomain, formatError, startTimer, timeoutSignal } from "../utils/index.js";
|
|
2
2
|
import { clearDomainAuthRequirement, getDomainAuthState, markDomainAuth, markDomainAuthRequired } from "../config/health.js";
|
|
3
|
-
import { createDiscoveryPage, emulateLayoutSurface, getCurrentBrowser, isGracefulShutdown, registerManagedPage, syncWindowVisibility, unregisterManagedPage } from "./index.js";
|
|
3
|
+
import { createDiscoveryPage, emulateLayoutSurface, getCurrentBrowser, isBrowserConnected, isGracefulShutdown, registerManagedPage, syncWindowVisibility, unregisterManagedPage } from "./index.js";
|
|
4
4
|
import { getPersistedLineup, persistProviderLineup } from "../config/providerLineups.js";
|
|
5
5
|
import { getProviderBySlug, getProvidersForDomain } from "./channelSelection.js";
|
|
6
6
|
import { systemClock, waitWithSignal } from "homebridge-plugin-utils";
|
|
@@ -8,14 +8,17 @@ import { CONFIG } from "../config/index.js";
|
|
|
8
8
|
import { classifyBlockedPage } from "./blockedPage.js";
|
|
9
9
|
import { getProfileForUrl } from "../config/profiles.js";
|
|
10
10
|
import { isLoginModeActive } from "./login.js";
|
|
11
|
+
import { isServiceTagEnabled } from "../config/services.js";
|
|
12
|
+
import { registerConfigChangeHandler } from "../config/reactivity.js";
|
|
11
13
|
import { startOverlayHandling } from "./consent.js";
|
|
12
|
-
/* Precaching discovers channel lineups for selected services
|
|
13
|
-
* sequentially - discovery opens a browser page in a window of its own and navigates to a heavy SPA, so running all services concurrently would stress
|
|
14
|
-
* GPU on resource-constrained systems.
|
|
14
|
+
/* Precaching discovers channel lineups for selected services ahead of any tune, so that even the first tune benefits from cached lineup data. Each service is
|
|
15
|
+
* precached sequentially - discovery opens a browser page in a window of its own and navigates to a heavy SPA, so running all services concurrently would stress
|
|
16
|
+
* CPU and GPU on resource-constrained systems. A cycle runs in the background after a settle delay, never on a request's path.
|
|
15
17
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
18
|
+
* A cycle is requested with a scope. A browser launch requests one over every listed service, because the launched browser starts with every provider cache
|
|
19
|
+
* empty, and a settings save requests one over the services it adds to the list, so the save warms what it selected without clearing any other provider's
|
|
20
|
+
* cache. Precaching never launches the browser: a cycle or a deferred pass that finds no browser running stops, and the next launch requests its own cycle. Each
|
|
21
|
+
* service has its own try/catch - one failure does not stop the rest.
|
|
19
22
|
*
|
|
20
23
|
* This module also owns the discovery-outcome policy (recordDiscoveryOutcome): the single source of truth for how a completed discovery walk translates into domain
|
|
21
24
|
* auth state and a persisted channel lineup, shared by the precache cycle here and the /services/:slug/channels endpoint. The routes layer never calls a health
|
|
@@ -23,10 +26,11 @@ import { startOverlayHandling } from "./consent.js";
|
|
|
23
26
|
* primitive both the precache cycle and that endpoint walk their guides through, and the one place the empty-walk retry policy lives, so no provider carries a
|
|
24
27
|
* retry of its own.
|
|
25
28
|
*
|
|
26
|
-
* Every timer and deadline this module arms - the
|
|
27
|
-
* carry, so one injected clock drives the whole schedule.
|
|
29
|
+
* Every timer and deadline this module arms - the settle delay before a cycle, the deferred re-attempt, and each walk's ceiling - runs on the clock its
|
|
30
|
+
* dependencies carry, so one injected clock drives the whole schedule.
|
|
28
31
|
*/
|
|
29
|
-
//
|
|
32
|
+
// The settle delay in milliseconds before a requested cycle walks its first guide. It gives a browser that just launched time to settle, and every request that
|
|
33
|
+
// arrives inside it joins the pending cycle rather than starting another.
|
|
30
34
|
const PRECACHE_DELAY = 5000;
|
|
31
35
|
/* Delay in milliseconds before the services a cycle could not settle are re-attempted. Five minutes puts the second pass well past the contention a boot creates -
|
|
32
36
|
* the browser launch, the first tunes, the DVR's own channel scan - which is the likeliest reason a provider's lazy content never appeared inside its walk. A
|
|
@@ -66,41 +70,70 @@ export class DiscoveryWalkTimeoutError extends Error {
|
|
|
66
70
|
this.name = "DiscoveryWalkTimeoutError";
|
|
67
71
|
}
|
|
68
72
|
}
|
|
69
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Merges a new request's scope into the scope already requested, giving the one scope that answers each request, so a request that reaches a pending cycle or a
|
|
75
|
+
* held guard joins what is already requested rather than queueing behind it. A scope over every service absorbs any other, and slug scopes union their slugs.
|
|
76
|
+
* @param current - The scope already requested.
|
|
77
|
+
* @param added - The scope a new request carries.
|
|
78
|
+
* @returns The scope that answers each request.
|
|
79
|
+
*/
|
|
80
|
+
function mergeScopes(current, added) {
|
|
81
|
+
if (current.kind === "all") {
|
|
82
|
+
return current;
|
|
83
|
+
}
|
|
84
|
+
if (added.kind === "all") {
|
|
85
|
+
return added;
|
|
86
|
+
}
|
|
87
|
+
return { kind: "services", slugs: current.slugs.union(added.slugs) };
|
|
88
|
+
}
|
|
89
|
+
// The single-flight guard. A cycle holds it from the moment it is armed until its run ends, and a deferred re-attempt and a post-login revalidation each hold it
|
|
90
|
+
// while they run, so runs never overlap. Every holder releases it through releasePrecacheGuard, and the shutdown paths clear it directly.
|
|
70
91
|
let precacheInProgress = false;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
* provider cache, so the run holding the guard is walking guides for a browser that no longer exists and its result is worth nothing - dropping the request would
|
|
75
|
-
* leave the new browser with no lineups at all. Whoever releases the guard runs it; releasePrecacheGuard is where that happens, and it is the only place.
|
|
92
|
+
/* The scope requested while the guard was held, merged across every such request, or null when none arrived. The run holding the guard walks the scope it was
|
|
93
|
+
* armed with and cannot take on another, so a request is recorded rather than dropped: a launch's means every provider cache was just cleared, and a save's means
|
|
94
|
+
* services were added after that run read the list. Whoever releases the guard hands it on; releasePrecacheGuard is where that happens, and it is the only place.
|
|
76
95
|
*/
|
|
77
|
-
let
|
|
96
|
+
let requestedScope = null;
|
|
97
|
+
// The scheduler's pending work. updateSchedule is its only writer.
|
|
98
|
+
let schedule = { cycle: null, retry: null };
|
|
78
99
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
100
|
+
* Writes the scheduler state, and is the only code that does. It builds the next value from the current one and the change, disposes each timer the current
|
|
101
|
+
* value holds and the next value does not, compared by identity, and assigns the next value. A record replaced by one carrying the same timer keeps that
|
|
102
|
+
* timer armed, as a slug scope merged into a pending cycle does; a record cleared, or replaced by one carrying a new timer, has its timer disposed. So no path
|
|
103
|
+
* can drop a record and leave its timer armed, and none can cancel a timer the state still holds. Disposing a timer that already fired does nothing, so a fire
|
|
104
|
+
* clears its own slot through here as well.
|
|
105
|
+
* @param change - The slots to write; a slot the change leaves out keeps its record.
|
|
81
106
|
*/
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
107
|
+
function updateSchedule(change) {
|
|
108
|
+
const next = { ...schedule, ...change };
|
|
109
|
+
// The timers a schedule value holds, one per slot, so the current value's timers are compared against the next value's by identity.
|
|
110
|
+
const timersOf = (value) => [value.cycle?.timer, value.retry?.timer];
|
|
111
|
+
const kept = new Set(timersOf(next));
|
|
112
|
+
for (const timer of timersOf(schedule)) {
|
|
113
|
+
if (timer && !kept.has(timer)) {
|
|
114
|
+
timer[Symbol.dispose]();
|
|
115
|
+
}
|
|
86
116
|
}
|
|
117
|
+
schedule = next;
|
|
87
118
|
}
|
|
88
119
|
/**
|
|
89
|
-
* Releases the single-flight guard and
|
|
120
|
+
* Releases the single-flight guard and hands on any scope requested while it was held.
|
|
90
121
|
*
|
|
91
|
-
* The order is the whole point. The
|
|
92
|
-
*
|
|
93
|
-
* Every path that takes the guard - the cycle, the deferred re-attempt, and the post-login revalidation - releases it here, so the hand-off has one home
|
|
94
|
-
* than a copy at each site.
|
|
95
|
-
*
|
|
122
|
+
* The order is the whole point. The guard is freed and the recorded scope taken before the hand-off, so the request below finds a free guard and arms. Handing on
|
|
123
|
+
* first would have that request see the guard still held and record the very scope being handed on, which is how a request would go round forever without ever
|
|
124
|
+
* running. Every path that takes the guard - the cycle, the deferred re-attempt, and the post-login revalidation - releases it here, so the hand-off has one home
|
|
125
|
+
* rather than a copy at each site. The scope goes to the request function rather than to startPrecaching, so it never meets the launch's empty-list gate: the
|
|
126
|
+
* cycle it arms reads the list when it fires.
|
|
127
|
+
* @param deps - The injected dependencies, handed to the cycle this may arm.
|
|
96
128
|
*/
|
|
97
129
|
function releasePrecacheGuard(deps) {
|
|
98
130
|
precacheInProgress = false;
|
|
99
|
-
|
|
131
|
+
const scope = requestedScope;
|
|
132
|
+
if (!scope) {
|
|
100
133
|
return;
|
|
101
134
|
}
|
|
102
|
-
|
|
103
|
-
|
|
135
|
+
requestedScope = null;
|
|
136
|
+
requestPrecache(scope, deps);
|
|
104
137
|
}
|
|
105
138
|
export const defaultPrecachingDeps = {
|
|
106
139
|
clock: systemClock,
|
|
@@ -110,6 +143,7 @@ export const defaultPrecachingDeps = {
|
|
|
110
143
|
getPersistedLineup,
|
|
111
144
|
getProviderBySlug,
|
|
112
145
|
getProvidersForDomain,
|
|
146
|
+
isBrowserConnected,
|
|
113
147
|
isGracefulShutdown,
|
|
114
148
|
persistProviderLineup,
|
|
115
149
|
registerManagedPage,
|
|
@@ -118,55 +152,108 @@ export const defaultPrecachingDeps = {
|
|
|
118
152
|
unregisterManagedPage
|
|
119
153
|
};
|
|
120
154
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
155
|
+
* Arms the cycle slot for a scope on a full settle delay. The callback reads the slot's scope when it fires, never the scope armed here, so every request merged
|
|
156
|
+
* into the slot before the fire reaches the walk, and it clears the slot through updateSchedule before the cycle runs. Arming a scope over every service clears
|
|
157
|
+
* the re-attempt slot in the same write, because that cycle walks every service the re-attempt owes and a second pass over the same guides would contend with it
|
|
158
|
+
* for one browser; a slug scope leaves the re-attempt on its own delay, with its skip for a lineup that arrived meanwhile and its one pass.
|
|
159
|
+
* @param scope - The scope the cycle walks unless a later request merges into it.
|
|
160
|
+
* @param deps - The injected dependencies; the timer arms on their clock and the cycle runs with them.
|
|
125
161
|
*/
|
|
126
|
-
|
|
127
|
-
|
|
162
|
+
function armCycle(scope, deps) {
|
|
163
|
+
const timer = deps.clock.schedule(() => {
|
|
164
|
+
const armed = schedule.cycle;
|
|
165
|
+
updateSchedule({ cycle: null });
|
|
166
|
+
// A fire finds its own record in the slot, because every write that drops a cycle record disposes that record's timer first.
|
|
167
|
+
if (!armed) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
void runPrecacheCycle(armed.scope, deps);
|
|
171
|
+
}, PRECACHE_DELAY);
|
|
172
|
+
updateSchedule((scope.kind === "all") ? { cycle: { scope, timer }, retry: null } : { cycle: { scope, timer } });
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Lands a precache request, and is the one place one lands. A request carries the scope it wants walked and takes the first of these that applies: during a
|
|
176
|
+
* graceful shutdown it is dropped; with a cycle armed and not yet fired, it merges into that cycle's scope; with the guard held by a run, it merges into
|
|
177
|
+
* requestedScope, which the release hands on; otherwise it takes the guard and arms a cycle.
|
|
178
|
+
*
|
|
179
|
+
* The pending cycle is consulted ahead of the guard, which that cycle holds, because it reads the list and the browser when it fires and so serves the request
|
|
180
|
+
* whole - recording the request instead would run a second cycle after the first. A scope over every service comes from a launch, so merging one into a pending
|
|
181
|
+
* cycle re-arms that cycle for a full settle delay from the merge, the time the new browser needs, and clears the re-attempt slot as arming one does. A slug
|
|
182
|
+
* scope merged into a pending cycle keeps its timer, so a save never postpones a cycle already on its way.
|
|
183
|
+
* @param scope - The services the request wants walked.
|
|
184
|
+
* @param deps - The injected dependencies, handed to the cycle this arms.
|
|
185
|
+
*/
|
|
186
|
+
function requestPrecache(scope, deps) {
|
|
187
|
+
// Never arm during a graceful shutdown. A launch can be reached during teardown, and a cycle armed then would fire after teardown closed the browser.
|
|
188
|
+
if (deps.isGracefulShutdown()) {
|
|
128
189
|
return;
|
|
129
190
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
191
|
+
const pending = schedule.cycle;
|
|
192
|
+
if (pending) {
|
|
193
|
+
const merged = mergeScopes(pending.scope, scope);
|
|
194
|
+
if (scope.kind === "all") {
|
|
195
|
+
armCycle(merged, deps);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
updateSchedule({ cycle: { scope: merged, timer: pending.timer } });
|
|
199
|
+
}
|
|
133
200
|
return;
|
|
134
201
|
}
|
|
135
202
|
if (precacheInProgress) {
|
|
136
|
-
// Record the request rather than dropping it
|
|
137
|
-
|
|
138
|
-
fullCycleRequested = true;
|
|
203
|
+
// Record the request rather than dropping it: the run holding the guard walks the scope it was armed with, and whoever releases the guard arms this one.
|
|
204
|
+
requestedScope = requestedScope ? mergeScopes(requestedScope, scope) : scope;
|
|
139
205
|
LOG.debug("precache", "Precache deferred: already in progress.");
|
|
140
206
|
return;
|
|
141
207
|
}
|
|
142
|
-
//
|
|
208
|
+
// Take the guard as the cycle is armed, so every request until the cycle's run ends merges into it or is recorded for its release rather than arming another.
|
|
143
209
|
precacheInProgress = true;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
210
|
+
armCycle(scope, deps);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Requests a precache cycle over every listed service. This is the browser launch's request, made once its browser is ready, because a launched browser starts
|
|
214
|
+
* with every provider cache empty; a save requests only the services it adds, through applyPrecacheConfigChanges. Returns at once when no service is listed, and
|
|
215
|
+
* otherwise lands the request through requestPrecache, whose cycle waits out the settle delay on the dependencies' clock, so the launch is never blocked.
|
|
216
|
+
* @param deps - The injected browser and provider-registry dependencies; defaults to defaultPrecachingDeps.
|
|
217
|
+
*/
|
|
218
|
+
export function startPrecaching(deps = defaultPrecachingDeps) {
|
|
219
|
+
if (CONFIG.channels.precacheServices.length === 0) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
requestPrecache({ kind: "all" }, deps);
|
|
153
223
|
}
|
|
154
224
|
/**
|
|
155
|
-
* Cancels every scheduled precache - the
|
|
156
|
-
* nothing scheduled can fire after the browser has been closed. Safe to call when nothing is pending.
|
|
225
|
+
* Cancels every scheduled precache - the pending cycle and the deferred re-attempt alike - drops a recorded request, and clears the in-progress guard. Called
|
|
226
|
+
* during graceful shutdown so nothing scheduled can fire after the browser has been closed. Safe to call when nothing is pending.
|
|
157
227
|
*
|
|
158
|
-
* The
|
|
159
|
-
* which is exactly what releasing through the hand-off would do.
|
|
228
|
+
* The slots are cleared through updateSchedule, which disposes their timers. The guard is cleared directly rather than through releasePrecacheGuard, and the
|
|
229
|
+
* recorded request with it: a shutdown must not hand on a pending request, which is exactly what releasing through the hand-off would do.
|
|
160
230
|
*/
|
|
161
231
|
export function stopPrecaching() {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
scheduled = null;
|
|
165
|
-
}
|
|
166
|
-
clearDeferredRetry();
|
|
167
|
-
fullCycleRequested = false;
|
|
232
|
+
updateSchedule({ cycle: null, retry: null });
|
|
233
|
+
requestedScope = null;
|
|
168
234
|
precacheInProgress = false;
|
|
169
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Requests a walk of the services a saved precache list adds. The candidate carries the saved list and CONFIG still holds the running one, because the reconcile
|
|
238
|
+
* commits only after its handlers run, so the services the save adds are the candidate's slugs the running list lacks. They are requested as one scope while a
|
|
239
|
+
* browser is connected, so the save precaches them without clearing any other provider's cache. A save that only removes services needs nothing here, because
|
|
240
|
+
* every reader of the list reads it when it walks, and with no browser running the save requests nothing, because a browser is never launched for a save and
|
|
241
|
+
* the next launch requests every listed service. Requesting cannot fail, so the handler refuses nothing.
|
|
242
|
+
* @param _changes - The change to the precache list; the candidate carries the list, so the handler reads that instead.
|
|
243
|
+
* @param next - The candidate running configuration.
|
|
244
|
+
* @param deps - The injected dependencies; defaults to defaultPrecachingDeps.
|
|
245
|
+
* @returns No rejections.
|
|
246
|
+
*/
|
|
247
|
+
export async function applyPrecacheConfigChanges(_changes, next, deps = defaultPrecachingDeps) {
|
|
248
|
+
const added = new Set(next.channels.precacheServices).difference(new Set(CONFIG.channels.precacheServices));
|
|
249
|
+
if ((added.size > 0) && deps.isBrowserConnected()) {
|
|
250
|
+
requestPrecache({ kind: "services", slugs: added }, deps);
|
|
251
|
+
}
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
// Module-load side effect: register the handler once per process, as every config-change handler registers, so it is in place before the first save can reach
|
|
255
|
+
// the reconcile.
|
|
256
|
+
registerConfigChangeHandler("channels.precacheServices", applyPrecacheConfigChanges);
|
|
170
257
|
/**
|
|
171
258
|
* Records the consequences of a completed channel discovery: the domain auth state it proves, and the durable lineup it produced. This is the single source of
|
|
172
259
|
* truth for discovery-outcome policy, consumed by both the precache cycle (via precacheService) and the /services/:slug/channels endpoint - the routes layer never
|
|
@@ -401,8 +488,8 @@ export async function withProviderGuidePage(provider, options = {}, deps = defau
|
|
|
401
488
|
if ((channels.length === 0) && !page.isClosed() && !signal?.aborted && !deps.isGracefulShutdown()) {
|
|
402
489
|
({ channels, classification } = await retryAfterEmptyWalk({ deps, page, profile, provider }));
|
|
403
490
|
}
|
|
404
|
-
// The hook runs once, on whichever result stands - and receives the first walk's classification only when
|
|
405
|
-
// handed and the classification it records always describe the same moment.
|
|
491
|
+
// The hook runs once, on whichever result stands - and receives the first walk's classification only when no second walk ran (the page classified as blocked,
|
|
492
|
+
// or the reload before the retry failed), so the page it is handed and the classification it records always describe the same moment.
|
|
406
493
|
await afterWalk?.(page, channels, classification);
|
|
407
494
|
return channels;
|
|
408
495
|
}
|
|
@@ -431,13 +518,14 @@ export async function withProviderGuidePage(provider, options = {}, deps = defau
|
|
|
431
518
|
*/
|
|
432
519
|
export async function precacheService(provider, deps = defaultPrecachingDeps) {
|
|
433
520
|
const serviceElapsed = startTimer();
|
|
434
|
-
// Clear the service's cache before discovery to ensure a complete walk, even if a tune partially warmed the cache during the
|
|
521
|
+
// Clear the service's cache before discovery to ensure a complete walk, even if a tune partially warmed the cache during the settle delay. Only the walked
|
|
522
|
+
// service's cache is cleared, so a cycle scoped to the services a save added leaves every other provider's warm cache alone.
|
|
435
523
|
provider.strategy.clearCache?.();
|
|
436
524
|
return withProviderGuidePage(provider, {
|
|
437
525
|
afterWalk: async (page, channels, classification) => {
|
|
438
526
|
LOG.info("Precached %s: %d channels (%ss).", provider.label, channels.length, (serviceElapsed() / 1000).toFixed(1).replace(/\.0$/, ""));
|
|
439
|
-
// Record the outcome while the page is still open - an empty result classifies the page it walked, unless the session already classified it and
|
|
440
|
-
// reload, in which case that verdict travels here rather than being re-derived
|
|
527
|
+
// Record the outcome while the page is still open - an empty result classifies the page it walked, unless the session already classified it and no second
|
|
528
|
+
// walk followed (the page classified as blocked, or the reload before the retry failed), in which case that verdict travels here rather than being re-derived.
|
|
441
529
|
await recordDiscoveryOutcome(provider, channels, page, deps, classification);
|
|
442
530
|
}
|
|
443
531
|
}, deps);
|
|
@@ -448,8 +536,8 @@ export async function precacheService(provider, deps = defaultPrecachingDeps) {
|
|
|
448
536
|
*
|
|
449
537
|
* Never rejects - the observer wiring voids the returned promise, so every failure is logged and absorbed here. Revalidation deliberately ignores both the
|
|
450
538
|
* precacheServices and enabledServices filters: this is clearing-evidence collection for a domain the user just signed in to, not precaching, and the cycle's
|
|
451
|
-
* filter skip is unchanged. While it runs it holds the same single-flight guard the precache cycle holds, so a cycle
|
|
452
|
-
*
|
|
539
|
+
* filter skip is unchanged. While it runs it holds the same single-flight guard the precache cycle holds, so a cycle requested mid-revalidation, by a launch or
|
|
540
|
+
* a save, is recorded and armed once the revalidation releases the guard, instead of overlapping it.
|
|
453
541
|
* @param url - The login session's URL; its extracted domain selects the providers to revalidate.
|
|
454
542
|
* @returns A promise that resolves when revalidation completes or is skipped. It never rejects.
|
|
455
543
|
*/
|
|
@@ -469,8 +557,9 @@ export async function revalidateDomainAuth(url, deps = defaultPrecachingDeps) {
|
|
|
469
557
|
if (deps.isGracefulShutdown()) {
|
|
470
558
|
return;
|
|
471
559
|
}
|
|
472
|
-
// Defer to an in-flight precache
|
|
473
|
-
// the flag persists until the next
|
|
560
|
+
// Defer to an in-flight precache run rather than overlapping it. Limitation: when the flagged provider is outside what that run walks - a cycle whose list
|
|
561
|
+
// or whose scope leaves it out - the flag persists until the next walk that reaches the provider, the next launch's cycle over every service among them, or a
|
|
562
|
+
// successful tune for the domain, because a run in flight cannot be retargeted.
|
|
474
563
|
if (precacheInProgress) {
|
|
475
564
|
LOG.info("Deferring the post-login revalidation for %s to the precache cycle already in progress.", domain);
|
|
476
565
|
return;
|
|
@@ -509,6 +598,9 @@ export async function revalidateDomainAuth(url, deps = defaultPrecachingDeps) {
|
|
|
509
598
|
* Schedules the deferred re-attempt for the services a pass could not settle. Does nothing when every service came back with a lineup, and nothing during a
|
|
510
599
|
* shutdown, where scheduling work against the browser is precisely what teardown is closing down. This is the one place a re-attempt is scheduled, so every
|
|
511
600
|
* reason a service is still owed a walk - it ran and found nothing, it was stopped at its budget, or it never ran at all - arrives on the same schedule.
|
|
601
|
+
*
|
|
602
|
+
* A re-attempt already pending takes the new services into its own list and is re-armed for a full delay from the merge, so every service it owes waits the full
|
|
603
|
+
* delay after its last walk rather than inheriting what is left of an earlier one's wait. updateSchedule disposes the timer the merged record replaces.
|
|
512
604
|
* @param slugs - The services a pass could not settle: walked without settling, or deferred because a login session was on screen.
|
|
513
605
|
* @param deps - The injected dependencies, handed to the pass this schedules.
|
|
514
606
|
*/
|
|
@@ -516,35 +608,46 @@ function armDeferredRetry(slugs, deps) {
|
|
|
516
608
|
if ((slugs.length === 0) || deps.isGracefulShutdown()) {
|
|
517
609
|
return;
|
|
518
610
|
}
|
|
519
|
-
|
|
520
|
-
|
|
611
|
+
const owed = Array.from(new Set([...(schedule.retry?.slugs ?? []), ...slugs]));
|
|
612
|
+
LOG.debug("precache", "Scheduling one deferred discovery re-attempt for %d service%s in %d minutes.", owed.length, (owed.length === 1) ? "" : "s", PRECACHE_RETRY_DELAY / 60000);
|
|
613
|
+
updateSchedule({ retry: { slugs: owed, timer: deps.clock.schedule(() => void runDeferredRetry(deps), PRECACHE_RETRY_DELAY) } });
|
|
521
614
|
}
|
|
522
615
|
/**
|
|
523
616
|
* Runs the deferred re-attempt for the services a cycle could not settle. Never rejects - it is driven by a timer with nobody to hand a rejection to, so every
|
|
524
617
|
* per-service failure is contained the same way the cycle contains its own.
|
|
525
618
|
*
|
|
526
619
|
* A service whose lineup arrived in the interval - from a later full cycle, or from an on-demand discovery a user triggered - is skipped rather than re-walked,
|
|
527
|
-
* because the walk it would run is the expensive part and the answer is already in hand
|
|
528
|
-
*
|
|
620
|
+
* because the walk it would run is the expensive part and the answer is already in hand, and so is a service that has since left the precache list or the
|
|
621
|
+
* active service filter. A login session that begins before the pass fires stops it where it stands and re-arms everything still owed, so the walks resume once
|
|
622
|
+
* the user is done rather than opening a window over the one they are signing in through.
|
|
529
623
|
* @param deps - The injected browser and provider-registry dependencies.
|
|
530
624
|
* @returns A promise that resolves once the pass completes or is skipped.
|
|
531
625
|
*/
|
|
532
626
|
async function runDeferredRetry(deps) {
|
|
533
|
-
|
|
627
|
+
const pending = schedule.retry;
|
|
628
|
+
// Clear the slot before acting on it, through the one writer. This is the only pass there will be, so a record left standing would tell a later cancellation or
|
|
629
|
+
// merge that something is still scheduled when nothing is.
|
|
630
|
+
updateSchedule({ retry: null });
|
|
631
|
+
// A fire finds its own record in the slot, because every write that drops a re-attempt record disposes that record's timer first.
|
|
632
|
+
if (!pending) {
|
|
534
633
|
return;
|
|
535
634
|
}
|
|
536
|
-
const pending = scheduled;
|
|
537
|
-
// Drop the state before acting on it. This is the only pass there will be, so a handle left standing would tell a later cancellation that something is still
|
|
538
|
-
// scheduled when nothing is.
|
|
539
|
-
scheduled = null;
|
|
540
635
|
if (deps.isGracefulShutdown()) {
|
|
541
636
|
return;
|
|
542
637
|
}
|
|
638
|
+
// Stop rather than walk when no browser is running, as the cycle does. The pass re-arms nothing, because the next launch requests a cycle over every listed
|
|
639
|
+
// service, the ones this pass owes among them.
|
|
640
|
+
if (!deps.isBrowserConnected()) {
|
|
641
|
+
LOG.debug("precache", "Skipping the deferred discovery re-attempt: no browser is running.");
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
543
644
|
/* A cycle or a post-login revalidation holding the guard is already walking guides, quite possibly these same ones. This pass exists to try again on a settled
|
|
544
|
-
* system, not to contend with a run in flight
|
|
645
|
+
* system, not to contend with a run in flight, so it re-arms itself for its services on its own delay, as it does for a login session, rather than dropping
|
|
646
|
+
* them: the run in flight may walk a scope that leaves them out, and when the pass fires again it skips any service whose lineup arrived meanwhile.
|
|
545
647
|
*/
|
|
546
648
|
if (precacheInProgress) {
|
|
547
|
-
LOG.debug("precache", "
|
|
649
|
+
LOG.debug("precache", "Deferring the discovery re-attempt: a precache run is already in progress.");
|
|
650
|
+
armDeferredRetry(pending.slugs, deps);
|
|
548
651
|
return;
|
|
549
652
|
}
|
|
550
653
|
precacheInProgress = true;
|
|
@@ -559,10 +662,24 @@ async function runDeferredRetry(deps) {
|
|
|
559
662
|
if (deps.isGracefulShutdown()) {
|
|
560
663
|
break;
|
|
561
664
|
}
|
|
665
|
+
// Stop the same way once the browser is gone. No await lies between this check and the browser acquisition precacheService reaches through
|
|
666
|
+
// withProviderGuidePage, so a walk never acquires a browser this check found gone and the pass never launches one; an await inserted between them breaks that.
|
|
667
|
+
if (!deps.isBrowserConnected()) {
|
|
668
|
+
break;
|
|
669
|
+
}
|
|
562
670
|
const provider = deps.getProviderBySlug(slug);
|
|
563
671
|
if (!provider) {
|
|
564
672
|
continue;
|
|
565
673
|
}
|
|
674
|
+
// A service off the list or outside the running filter is owed nothing, so the pass reads the list and the filter at each service's turn, as the cycle does.
|
|
675
|
+
if (!CONFIG.channels.precacheServices.includes(slug)) {
|
|
676
|
+
LOG.debug("precache", "Skipping the deferred re-attempt for %s: it is no longer on the precache list.", provider.label);
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
if (!isServiceTagEnabled(slug)) {
|
|
680
|
+
LOG.debug("precache", "Skipping the deferred re-attempt for %s: not in active service filter.", provider.label);
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
566
683
|
if (provider.getCachedChannels()) {
|
|
567
684
|
LOG.debug("precache", "Skipping the deferred re-attempt for %s: its lineup was discovered in the meantime.", provider.label);
|
|
568
685
|
continue;
|
|
@@ -611,24 +728,41 @@ async function runDeferredRetry(deps) {
|
|
|
611
728
|
}
|
|
612
729
|
}
|
|
613
730
|
/**
|
|
614
|
-
* Executes
|
|
615
|
-
*
|
|
731
|
+
* Executes one sequential precache cycle over the listed services its scope admits, clearing each service's cache first to ensure a complete walk. The list is
|
|
732
|
+
* read when the cycle fires, so a service removed during the settle delay is never walked, and again at each service's turn, so one removed while the cycle walks
|
|
733
|
+
* an earlier service is skipped. Services the running service filter excludes are skipped as well. Services the cycle could not settle - they walked and found
|
|
616
734
|
* nothing, their walk ran past its budget, or a login session on screen kept them from walking at all - are handed to the deferred re-attempt, minutes later,
|
|
617
|
-
* once whatever
|
|
735
|
+
* once whatever contention or user session may have starved them has passed.
|
|
736
|
+
* @param scope - The services the cycle walks, read from the cycle slot when the cycle fired.
|
|
737
|
+
* @param deps - The injected browser and provider-registry dependencies.
|
|
738
|
+
* @returns A promise that resolves once the cycle completes or stops.
|
|
618
739
|
*/
|
|
619
|
-
async function runPrecacheCycle(deps) {
|
|
620
|
-
/* Bail if a graceful shutdown began while this cycle was
|
|
621
|
-
* closed it; this guard makes the cycle a no-op during teardown regardless of how the timer-cancellation race resolves. Reset the in-progress flag
|
|
622
|
-
* early return skips the finally block below - directly rather than through the hand-off, because
|
|
623
|
-
* must not do.
|
|
740
|
+
async function runPrecacheCycle(scope, deps) {
|
|
741
|
+
/* Bail if a graceful shutdown began while this cycle was pending. Discovery opens browser pages via getCurrentBrowser(), which would relaunch Chrome after
|
|
742
|
+
* shutdown closed it; this guard makes the cycle a no-op during teardown regardless of how the timer-cancellation race resolves. Reset the in-progress flag
|
|
743
|
+
* since the early return skips the finally block below - directly rather than through the hand-off, because handing on a recorded request is the one thing a
|
|
744
|
+
* teardown path must not do.
|
|
624
745
|
*/
|
|
625
746
|
if (deps.isGracefulShutdown()) {
|
|
626
747
|
precacheInProgress = false;
|
|
627
748
|
return;
|
|
628
749
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
750
|
+
/* Stop rather than walk when no browser is running. Discovery acquires its browser through getCurrentBrowser(), which would launch Chrome for a cycle, and the
|
|
751
|
+
* next launch requests its own cycle over every listed service. The guard is released through the hand-off, so a request recorded while this cycle was pending
|
|
752
|
+
* still arms, and that cycle stops here in turn when the browser is still gone.
|
|
753
|
+
*/
|
|
754
|
+
if (!deps.isBrowserConnected()) {
|
|
755
|
+
LOG.debug("precache", "Skipping a precache cycle: no browser is running.");
|
|
756
|
+
releasePrecacheGuard(deps);
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
const listed = CONFIG.channels.precacheServices;
|
|
760
|
+
const slugs = (scope.kind === "all") ? listed : listed.filter((slug) => scope.slugs.has(slug));
|
|
761
|
+
// A scope that admits no listed service - the list was emptied, or the services a save added were removed again - walks nothing and announces nothing.
|
|
762
|
+
if (slugs.length === 0) {
|
|
763
|
+
releasePrecacheGuard(deps);
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
632
766
|
const cycleElapsed = startTimer();
|
|
633
767
|
/* The services that walked without settling - they came back with nothing, or the walk ran past its budget and was stopped - named rather than counted, because
|
|
634
768
|
* the deferred re-attempt below needs to know which ones to come back to. Both readings say the same thing about the service: the guide did not answer this
|
|
@@ -649,12 +783,26 @@ async function runPrecacheCycle(deps) {
|
|
|
649
783
|
if (deps.isGracefulShutdown()) {
|
|
650
784
|
break;
|
|
651
785
|
}
|
|
786
|
+
/* Stop the same way once the browser is gone, leaving the services this cycle has not reached to the next launch's cycle. No await lies between this check
|
|
787
|
+
* and the browser acquisition precacheService reaches through withProviderGuidePage, so a walk never acquires a browser this check found gone and the
|
|
788
|
+
* scheduler never launches one; an await inserted between them breaks that guarantee.
|
|
789
|
+
*/
|
|
790
|
+
if (!deps.isBrowserConnected()) {
|
|
791
|
+
break;
|
|
792
|
+
}
|
|
652
793
|
const provider = deps.getProviderBySlug(slug);
|
|
653
794
|
if (!provider) {
|
|
654
795
|
continue;
|
|
655
796
|
}
|
|
656
|
-
// Skip
|
|
657
|
-
|
|
797
|
+
// Skip a service missing from the running list. The list is read at each turn rather than once when the cycle fired, because a save commits a new list
|
|
798
|
+
// while a cycle can be walking.
|
|
799
|
+
if (!CONFIG.channels.precacheServices.includes(slug)) {
|
|
800
|
+
LOG.debug("precache", "Skipping precache for %s: it left the precache list during the cycle.", provider.label);
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
// Skip services not in the active service filter. The running filter rather than the persisted list, through the predicate every other filter reader uses,
|
|
804
|
+
// so the cycle skips exactly the services they hide. Their stored config is preserved for when the filter changes back.
|
|
805
|
+
if (!isServiceTagEnabled(slug)) {
|
|
658
806
|
LOG.debug("precache", "Skipping precache for %s: not in active service filter.", provider.label);
|
|
659
807
|
skipped++;
|
|
660
808
|
continue;
|