Update Flix-Streams preset provider configuration#902
Conversation
Refreshes the Flix-Streams marketplace preset to match the current Flix configuration flow. Changes include: replaces individual provider toggles with multi-select provider selection removes outdated Signal Vault server selection and uses the current unified Signal Vault config updates movie/series/anime and live TV provider options maps selected providers back to Flix-compatible enable_* config flags adds dynamic provider metadata loading from the Flix configure page with a static fallback keeps compatibility with older saved toggle-based configs Tested with: pnpm -F core -F server run build
WalkthroughThe PR introduces dynamic metadata support to the preset system, allowing presets to asynchronously fetch runtime-derived configuration from a backend service. The FlixStreams preset is significantly expanded with a larger schema, updated UI model for provider management, and new provider configuration logic whilst maintaining backward compatibility with legacy flags. Changes
Sequence DiagramsequenceDiagram
participant Client
participant PresetManager
participant FlixStreams
participant Backend as Flix Backend
participant Cache
Client->>PresetManager: getPresetListDynamic()
activate PresetManager
PresetManager->>FlixStreams: getDynamicMetadata()
activate FlixStreams
FlixStreams->>Cache: Check cached metadata
alt Cache Hit
Cache-->>FlixStreams: Return cached metadata
else Cache Miss
FlixStreams->>Backend: Fetch /configure HTML
Backend-->>FlixStreams: HTML with provider defaults
FlixStreams->>FlixStreams: Parse provider order & labels
FlixStreams->>Cache: Store metadata
Cache-->>FlixStreams: Cached
end
FlixStreams-->>PresetManager: PresetMetadata with dynamic options
deactivate FlixStreams
PresetManager->>PresetManager: Convert to MinimalMetadata
PresetManager-->>Client: PresetMinimalMetadata[]
deactivate PresetManager
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
packages/core/src/presets/presetManager.ts (1)
171-186: Type the optionalgetDynamicMetadatahook instead ofas any.Casting to
anyto callgetDynamicMetadataworks but leaks type-checking. Declaring it as an optional static method on thePresetbase (or a side interface) would let TS verify the signature/return type while still allowing presets to opt in.♻️ Sketch
- PRESET_LIST.map(async (presetId) => { - const preset = this.fromId(presetId); - let metadata = preset.METADATA; - if (typeof (preset as any).getDynamicMetadata === 'function') { - try { - metadata = await (preset as any).getDynamicMetadata(); - } catch { - metadata = preset.METADATA; - } - } - return this.toMinimalMetadata(metadata); + PRESET_LIST.map(async (presetId) => { + const preset = this.fromId(presetId) as typeof Preset & { + getDynamicMetadata?: () => Promise<PresetMetadata>; + }; + let metadata = preset.METADATA; + if (typeof preset.getDynamicMetadata === 'function') { + try { + metadata = await preset.getDynamicMetadata(); + } catch { + metadata = preset.METADATA; + } + } + return this.toMinimalMetadata(metadata);A cleaner long-term option is to add
static getDynamicMetadata?(): Promise<PresetMetadata>to thePresetbase class so all presets share a typed contract.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/core/src/presets/presetManager.ts` around lines 171 - 186, The code in getPresetListDynamic casts presets to any to call getDynamicMetadata, losing type safety; add an optional static method signature on the Preset base (e.g., declare static getDynamicMetadata?: () => Promise<PresetMetadata> or add it to a side interface implemented by concrete preset classes) so TypeScript can type-check the hook, then replace the any cast with a typed access (e.g., treat preset as typeof Preset or the new interface) and call getDynamicMetadata with proper typing; keep using this.fromId, PRESET_LIST, this.toMinimalMetadata and fallback to preset.METADATA on error as before.packages/core/src/presets/flixStreams.ts (1)
779-783: Avoid shadowing the file-scopeproviderIds/liveTvProviderIds.These locals shadow the module-level constants of the same name (lines 395-396) used elsewhere in
buildConfig. Consider renaming, e.g.remoteProviderIds/remoteLiveTvProviderIds, to keep future edits unambiguous.♻️ Suggested rename
- const providerIds = providerOrder.slice(0, firstLiveIndex); - const liveTvProviderIds = providerOrder.slice(firstLiveIndex); + const remoteProviderIds = providerOrder.slice(0, firstLiveIndex); + const remoteLiveTvProviderIds = providerOrder.slice(firstLiveIndex); @@ - return { - providerOptions: providerIds.map(toOption), - liveTvProviderOptions: liveTvProviderIds.map(toOption), - defaultProviderSelection: providerIds.filter( - (id) => defaultConfig[id] === true - ), - defaultLiveTvProviderSelection: liveTvProviderIds.filter( - (id) => defaultConfig[id] === true - ), - }; + return { + providerOptions: remoteProviderIds.map(toOption), + liveTvProviderOptions: remoteLiveTvProviderIds.map(toOption), + defaultProviderSelection: remoteProviderIds.filter( + (id) => defaultConfig[id] === true + ), + defaultLiveTvProviderSelection: remoteLiveTvProviderIds.filter( + (id) => defaultConfig[id] === true + ), + };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/core/src/presets/flixStreams.ts` around lines 779 - 783, The local variables providerIds and liveTvProviderIds in the block computing liveStartIndex/firstLiveIndex are shadowing file-scoped constants used elsewhere in buildConfig; rename the locals (for example to remoteProviderIds and remoteLiveTvProviderIds) and update all references in that scope (the slice results produced from providerOrder using liveStartIndex/firstLiveIndex) so the module-level providerIds/liveTvProviderIds remain unshadowed and unambiguous.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/core/src/presets/flixStreams.ts`:
- Around line 779-783: The current logic uses liveStartIndex >= 0 ?
liveStartIndex : providerOrder.length which silently treats all providers as
non-live when the 'enable_box13_emby_live_tv' divider is missing; update the
code around providerOrder, liveStartIndex, firstLiveIndex, providerIds and
liveTvProviderIds to detect the missing divider and instead fall back to the
static METADATA (or its provider list) for providerIds and set liveTvProviderIds
to an explicit empty list (or another safe static value); ensure you reference
providerOrder and 'enable_box13_emby_live_tv' to decide the branch so the UI
shows the static metadata rather than an empty dynamic live list.
- Around line 725-753: getRemoteProviderMetadata currently blocks callers on a
new fetch (using AbortSignal.timeout(3000)) each cache miss and has no in-flight
dedup, causing /api/status latency; change it so the method returns an existing
cached.value immediately if present even when expired (stale-while-revalidate)
and only blocks when there is no cache at all, and add an in-flight promise
dedup (e.g., store a Promise in a new static remoteProviderMetadataPromise
field) so concurrent callers share the same fetch: when cache is absent await
the fetch, set remoteProviderMetadataCache and clear the promise on completion;
when cache exists but expired, return cache.value immediately and if no
in-flight promise start the fetch in background to refresh
remoteProviderMetadataCache (or set short expiry/null on error), referencing
getRemoteProviderMetadata, remoteProviderMetadataCache,
remoteProviderMetadataPromise and METADATA.URL to locate where to implement
these changes.
---
Nitpick comments:
In `@packages/core/src/presets/flixStreams.ts`:
- Around line 779-783: The local variables providerIds and liveTvProviderIds in
the block computing liveStartIndex/firstLiveIndex are shadowing file-scoped
constants used elsewhere in buildConfig; rename the locals (for example to
remoteProviderIds and remoteLiveTvProviderIds) and update all references in that
scope (the slice results produced from providerOrder using
liveStartIndex/firstLiveIndex) so the module-level providerIds/liveTvProviderIds
remain unshadowed and unambiguous.
In `@packages/core/src/presets/presetManager.ts`:
- Around line 171-186: The code in getPresetListDynamic casts presets to any to
call getDynamicMetadata, losing type safety; add an optional static method
signature on the Preset base (e.g., declare static getDynamicMetadata?: () =>
Promise<PresetMetadata> or add it to a side interface implemented by concrete
preset classes) so TypeScript can type-check the hook, then replace the any cast
with a typed access (e.g., treat preset as typeof Preset or the new interface)
and call getDynamicMetadata with proper typing; keep using this.fromId,
PRESET_LIST, this.toMinimalMetadata and fallback to preset.METADATA on error as
before.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: b4368791-9fe4-4514-824a-e07a771b26c3
📒 Files selected for processing (3)
packages/core/src/presets/flixStreams.tspackages/core/src/presets/presetManager.tspackages/server/src/routes/api/status.ts
| private static async getRemoteProviderMetadata(): Promise<FlixProviderMetadata | null> { | ||
| const cached = this.remoteProviderMetadataCache; | ||
| if (cached && cached.expiresAt > Date.now()) { | ||
| return cached.value; | ||
| } | ||
|
|
||
| try { | ||
| const baseUrl = String(this.METADATA.URL).replace(/\/$/, ''); | ||
| const response = await fetch(`${baseUrl}/configure`, { | ||
| signal: AbortSignal.timeout(3000), | ||
| }); | ||
| if (!response.ok) { | ||
| throw new Error(`Flix configure fetch failed: ${response.status}`); | ||
| } | ||
| const html = await response.text(); | ||
| const metadata = this.parseRemoteProviderMetadata(html); | ||
| this.remoteProviderMetadataCache = { | ||
| expiresAt: Date.now() + 10 * 60 * 1000, | ||
| value: metadata, | ||
| }; | ||
| return metadata; | ||
| } catch { | ||
| this.remoteProviderMetadataCache = { | ||
| expiresAt: Date.now() + 60 * 1000, | ||
| value: null, | ||
| }; | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
/api/status now blocks on a remote /configure fetch every cache cycle.
Because getDynamicMetadata is awaited inside PresetManager.getPresetListDynamic() (called from the status route), any cold or expired cache cycle here adds up to 3 s of latency to /api/status. Concurrent status requests during a cache miss will each trigger a separate fetch (no in-flight dedup), which can amplify load on the Flix backend during transient outages.
Consider one of:
- Stale-while-revalidate: keep returning the previous (possibly stale)
valueimmediately and revalidate in the background once expired. Only block when there is no cache entry at all. - Promise dedup: cache the in-flight
Promise<FlixProviderMetadata | null>so concurrent callers share a single fetch. - Background warmer: kick off the refresh on a timer/startup hook and let
/api/statusalways read from the in-memory snapshot.
♻️ Stale-while-revalidate + in-flight dedup sketch
private static remoteProviderMetadataCache:
| {
expiresAt: number;
value: FlixProviderMetadata | null;
}
| undefined;
+ private static remoteProviderMetadataInflight:
+ | Promise<FlixProviderMetadata | null>
+ | undefined;
@@
private static async getRemoteProviderMetadata(): Promise<FlixProviderMetadata | null> {
const cached = this.remoteProviderMetadataCache;
- if (cached && cached.expiresAt > Date.now()) {
+ const fresh = cached && cached.expiresAt > Date.now();
+ if (fresh) {
return cached.value;
}
+ // Serve stale immediately; revalidate in the background.
+ if (cached && !this.remoteProviderMetadataInflight) {
+ this.remoteProviderMetadataInflight = this.fetchRemoteProviderMetadata()
+ .finally(() => {
+ this.remoteProviderMetadataInflight = undefined;
+ });
+ return cached.value;
+ }
+ // No cache yet — share the in-flight promise across concurrent callers.
+ this.remoteProviderMetadataInflight ??= this.fetchRemoteProviderMetadata()
+ .finally(() => {
+ this.remoteProviderMetadataInflight = undefined;
+ });
+ return this.remoteProviderMetadataInflight;
+ }
+ private static async fetchRemoteProviderMetadata(): Promise<FlixProviderMetadata | null> {
try {
const baseUrl = String(this.METADATA.URL).replace(/\/$/, '');
/* …unchanged fetch + parse + cache write… */🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/presets/flixStreams.ts` around lines 725 - 753,
getRemoteProviderMetadata currently blocks callers on a new fetch (using
AbortSignal.timeout(3000)) each cache miss and has no in-flight dedup, causing
/api/status latency; change it so the method returns an existing cached.value
immediately if present even when expired (stale-while-revalidate) and only
blocks when there is no cache at all, and add an in-flight promise dedup (e.g.,
store a Promise in a new static remoteProviderMetadataPromise field) so
concurrent callers share the same fetch: when cache is absent await the fetch,
set remoteProviderMetadataCache and clear the promise on completion; when cache
exists but expired, return cache.value immediately and if no in-flight promise
start the fetch in background to refresh remoteProviderMetadataCache (or set
short expiry/null on error), referencing getRemoteProviderMetadata,
remoteProviderMetadataCache, remoteProviderMetadataPromise and METADATA.URL to
locate where to implement these changes.
| const liveStartIndex = providerOrder.indexOf('enable_box13_emby_live_tv'); | ||
| const firstLiveIndex = | ||
| liveStartIndex >= 0 ? liveStartIndex : providerOrder.length; | ||
| const providerIds = providerOrder.slice(0, firstLiveIndex); | ||
| const liveTvProviderIds = providerOrder.slice(firstLiveIndex); |
There was a problem hiding this comment.
Fall back to static metadata when the live-TV divider isn't found.
liveStartIndex >= 0 ? liveStartIndex : providerOrder.length silently classifies every provider as a non-live one when enable_box13_emby_live_tv is missing from upstream provider_order. That ships a UI with an empty "Enabled live TV sources" list while still claiming to be dynamic — worse than just falling back to the static METADATA.
🛡️ Defensive fallback
- const liveStartIndex = providerOrder.indexOf('enable_box13_emby_live_tv');
- const firstLiveIndex =
- liveStartIndex >= 0 ? liveStartIndex : providerOrder.length;
- const providerIds = providerOrder.slice(0, firstLiveIndex);
- const liveTvProviderIds = providerOrder.slice(firstLiveIndex);
+ const liveStartIndex = providerOrder.indexOf('enable_box13_emby_live_tv');
+ if (liveStartIndex < 0) {
+ // Upstream changed the live-TV divider; fall back to static metadata.
+ return null;
+ }
+ const providerIds = providerOrder.slice(0, liveStartIndex);
+ const liveTvProviderIds = providerOrder.slice(liveStartIndex);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/presets/flixStreams.ts` around lines 779 - 783, The current
logic uses liveStartIndex >= 0 ? liveStartIndex : providerOrder.length which
silently treats all providers as non-live when the 'enable_box13_emby_live_tv'
divider is missing; update the code around providerOrder, liveStartIndex,
firstLiveIndex, providerIds and liveTvProviderIds to detect the missing divider
and instead fall back to the static METADATA (or its provider list) for
providerIds and set liveTvProviderIds to an explicit empty list (or another safe
static value); ensure you reference providerOrder and
'enable_box13_emby_live_tv' to decide the branch so the UI shows the static
metadata rather than an empty dynamic live list.
Refreshes the Flix-Streams marketplace preset to match the current Flix configuration flow.
Changes include:
replaces individual provider toggles with multi-select provider selection removes outdated Signal Vault server selection and uses the current unified Signal Vault config updates movie/series/anime and live TV provider options maps selected providers back to Flix-compatible enable_* config flags adds dynamic provider metadata loading from the Flix configure page with a static fallback keeps compatibility with older saved toggle-based configs Tested with:
pnpm -F core -F server run build
Summary by CodeRabbit
Release Notes
New Features