63 lines
2.4 KiB
JavaScript
63 lines
2.4 KiB
JavaScript
// Enabled-plugin gating for the router. Fetches the enabled plugin-name list
|
|
// ONCE (cached promise) so a disabled backend plugin's frontend routes become
|
|
// unreachable by direct URL. Fail-open: any fetch error treats every plugin as
|
|
// enabled, so an API blip can never brick navigation.
|
|
import { pluginsApi } from '../api'
|
|
|
|
let enabledPromise = null
|
|
let enabledSet = null // resolved Set<string>, or null while pending / on failure
|
|
|
|
// The router AWAITS this, so it must never outlast a navigation. An app-pool
|
|
// restart leaves requests hanging (IIS queues them while the worker starts) and
|
|
// axios has no timeout, so an unbounded wait here froze navigation outright -
|
|
// and because the promise was cached, every later navigation awaited the same
|
|
// hung request and stayed frozen after the backend recovered.
|
|
const GATE_TIMEOUT_MS = 4000
|
|
|
|
// Kick off (or reuse) the single fetch. The endpoint is jwt-optional so this
|
|
// works on unauthenticated kiosk routes (e.g. /tv) too.
|
|
export function loadEnabledPlugins() {
|
|
if (!enabledPromise) {
|
|
const fetched = pluginsApi.enabled()
|
|
.then(response => {
|
|
const names = response?.data?.data
|
|
// Only trust a real array; anything else -> fail open (null).
|
|
enabledSet = Array.isArray(names) ? new Set(names) : null
|
|
return enabledSet
|
|
})
|
|
.catch(() => {
|
|
// Fail open: leave enabledSet null so isPluginEnabled returns true.
|
|
enabledSet = null
|
|
return null
|
|
})
|
|
|
|
// Bounded wait, fail-open on expiry. A timed-out or failed attempt drops the
|
|
// cached promise so the NEXT navigation retries instead of inheriting a dead
|
|
// one; a request that lands late still populates enabledSet.
|
|
let timer
|
|
const bounded = new Promise(resolve => {
|
|
timer = setTimeout(() => resolve(null), GATE_TIMEOUT_MS)
|
|
})
|
|
enabledPromise = Promise.race([fetched, bounded]).then(result => {
|
|
clearTimeout(timer)
|
|
if (result === null) enabledPromise = null
|
|
return result
|
|
})
|
|
}
|
|
return enabledPromise
|
|
}
|
|
|
|
// True if the plugin is enabled OR the list is unknown (fail-open). A null
|
|
// enabledSet means we never got a trustworthy answer, so allow everything.
|
|
export function isPluginEnabled(name) {
|
|
if (!name) return true
|
|
if (enabledSet === null) return true
|
|
return enabledSet.has(name)
|
|
}
|
|
|
|
// Test/hot-reload hook: forget the cached result so the next call refetches.
|
|
export function resetEnabledPlugins() {
|
|
enabledPromise = null
|
|
enabledSet = null
|
|
}
|