import { useEffect, useState } from 'react' import api from '../api.js' // Which shard surfaces the current viewer may reach, from // GET /public/shard/features. Admins configure this per feature (Admin → Shard // Visibility), so the nav can't be a static list any more. // // This is PRESENTATION only. The gate is server-side: a disabled feature 404s // and an out-of-rung one 403s whether or not the link is rendered. So while the // answer is still in flight we return `null` and callers show their default set // — better a link that briefly 403s than a nav that flickers in on every load. // // Cached module-level: the answer is per-viewer but stable for a session, and // every consumer would otherwise refetch it on mount. let cached = null let inFlight = null export function resetShardFeatures() { cached = null inFlight = null } export function useShardFeatures() { const [features, setFeatures] = useState(cached) useEffect(() => { if (cached) return undefined let alive = true inFlight = inFlight || api.shard .features() .then((data) => { cached = { level: data.level, set: new Set(data.features || []), // Not a visibility flag and deliberately carried alongside them: it // is the same per-viewer, once-a-session answer from the same // endpoint, and GameAccounts asking for it separately would be a // second round-trip for a field already on the wire. gameAccountSignup: Boolean(data.gameAccountSignup), } return cached }) .catch(() => { // A failed lookup must not blank the nav — fall back to "show // everything" and let the server do the gating. cached = null inFlight = null return null }) inFlight.then((result) => { if (alive) setFeatures(result) }) return () => { alive = false } }, []) return features } // Convenience: true when `name` is visible, or when we don't know yet. export function canSee(features, name) { return !features || features.set.has(name) } // The same answer in the shape core's generic feature seam takes: a Set-like of // the flags this viewer may see, or null while we do not know yet // (core's modules/featureGate.js). This module registers it as the provider for // the `uo` namespace in entry.jsx, and the nine shard-gated rows in the public // header are ours to gate as of slice 3. // // It used to be core that registered this hook, under owner id `core`, so that // the seam was exercised from the day it was built. That prediction held exactly // — this slice deleted a registration and a file rather than rewriting a header. export function useShardFlags() { const features = useShardFeatures() return features ? features.set : null } /** * Does this site offer game-account creation right now? * * `null` while unknown, which callers must treat as "not yet" rather than "no": * the form it guards would 403 anyway, and flashing it in and out is worse than * arriving a beat late. Unlike the visibility flags above this one fails CLOSED * on a lookup error — showing a create-account form on a shard that refuses them * is a dead end the player cannot tell from a bug, whereas a hidden nav row has * another way round. */ export function useGameAccountSignup() { const features = useShardFeatures() return features ? features.gameAccountSignup : null }