[APP-1776] Refactor Live Now config, reorg files (#9871)

This commit is contained in:
Eric Bailey
2026-02-16 11:44:22 -06:00
committed by GitHub
parent 8ee9709fbb
commit cc2255cc49
34 changed files with 414 additions and 285 deletions
+87
View File
@@ -0,0 +1,87 @@
import {createContext, useContext} from 'react'
import {QueryClient, useQuery} from '@tanstack/react-query'
import {APP_CONFIG_URL} from '#/env'
const qc = new QueryClient()
const appConfigQueryKey = ['app-config']
/**
* Matches the types defined in our `app-config` worker
*/
type AppConfigResponse = {
liveNow: {
allow: string[]
exceptions: {
did: string
allow: string[]
}[]
}
}
export const DEFAULT_APP_CONFIG_RESPONSE: AppConfigResponse = {
liveNow: {
allow: [],
exceptions: [],
},
}
let fetchAppConfigPromise: Promise<AppConfigResponse> | undefined
async function fetchAppConfig(): Promise<AppConfigResponse | null> {
try {
if (!fetchAppConfigPromise) {
fetchAppConfigPromise = (async () => {
const r = await fetch(`${APP_CONFIG_URL}/config`)
if (!r.ok) throw new Error(await r.text())
const data = await r.json()
return data
})()
}
return await fetchAppConfigPromise
} catch (e) {
fetchAppConfigPromise = undefined
throw e
}
}
const Context = createContext<AppConfigResponse>(DEFAULT_APP_CONFIG_RESPONSE)
export function Provider({children}: React.PropsWithChildren<{}>) {
const {data} = useQuery<AppConfigResponse | null>(
{
staleTime: Infinity,
queryKey: appConfigQueryKey,
refetchInterval: query => {
// refetch regularly if fetch failed, otherwise never refetch
return query.state.status === 'error' ? 60e3 : Infinity
},
async queryFn() {
return fetchAppConfig()
},
},
qc,
)
return (
<Context.Provider value={data ?? DEFAULT_APP_CONFIG_RESPONSE}>
{children}
</Context.Provider>
)
}
export async function prefetchAppConfig() {
try {
const data = await fetchAppConfig()
if (data) {
qc.setQueryData(appConfigQueryKey, data)
}
} catch {}
}
export function useAppConfig() {
const ctx = useContext(Context)
if (!ctx) {
throw new Error('useAppConfig must be used within a Provider')
}
return ctx
}
+1 -1
View File
@@ -6,8 +6,8 @@ import {
BSKY_SERVICE_DID,
PUBLIC_BSKY_SERVICE,
} from '#/lib/constants'
import {useDebouncedValue} from '#/lib/hooks/useDebouncedValue'
import {createFullHandle} from '#/lib/strings/handles'
import {useDebouncedValue} from '#/components/live/utils'
import {useAnalytics} from '#/analytics'
import * as bsky from '#/types/bsky'
import {Agent} from '../session/agent'
+3 -67
View File
@@ -2,28 +2,17 @@ import {createContext, useContext, useMemo} from 'react'
import {useLanguagePrefs} from '#/state/preferences/languages'
import {useServiceConfigQuery} from '#/state/queries/service-config'
import {useSession} from '#/state/session'
import {useAnalytics} from '#/analytics'
import {IS_DEV} from '#/env'
import {device} from '#/storage'
type TrendingContext = {
enabled: boolean
}
type LiveNowContext = {
did: string
domains: string[]
}[]
const TrendingContext = createContext<TrendingContext>({
enabled: false,
})
TrendingContext.displayName = 'TrendingContext'
const LiveNowContext = createContext<LiveNowContext>([])
LiveNowContext.displayName = 'LiveNowContext'
const CheckEmailConfirmedContext = createContext<boolean | null>(null)
export function Provider({children}: {children: React.ReactNode}) {
@@ -60,19 +49,15 @@ export function Provider({children}: {children: React.ReactNode}) {
return {enabled}
}, [isInitialLoad, config, langPrefs.contentLanguages])
const liveNow = useMemo<LiveNowContext>(() => config?.liveNow ?? [], [config])
// probably true, so default to true when loading
// if the call fails, the query will set it to false for us
const checkEmailConfirmed = config?.checkEmailConfirmed ?? true
return (
<TrendingContext.Provider value={trending}>
<LiveNowContext.Provider value={liveNow}>
<CheckEmailConfirmedContext.Provider value={checkEmailConfirmed}>
{children}
</CheckEmailConfirmedContext.Provider>
</LiveNowContext.Provider>
<CheckEmailConfirmedContext.Provider value={checkEmailConfirmed}>
{children}
</CheckEmailConfirmedContext.Provider>
</TrendingContext.Provider>
)
}
@@ -81,55 +66,6 @@ export function useTrendingConfig() {
return useContext(TrendingContext)
}
const DEFAULT_LIVE_ALLOWED_DOMAINS = [
'twitch.tv',
'www.twitch.tv',
'stream.place',
'bluecast.app',
'www.bluecast.app',
]
export type LiveNowConfig = {
currentAccountAllowedHosts: Set<string>
defaultAllowedHosts: Set<string>
allowedHostsExceptionsByDid: Map<string, Set<string>>
}
export function useLiveNowConfig(): LiveNowConfig {
const ctx = useContext(LiveNowContext)
const canGoLive = useCanGoLive()
const {currentAccount} = useSession()
return useMemo(() => {
const defaultAllowedHosts = new Set(DEFAULT_LIVE_ALLOWED_DOMAINS)
const allowedHostsExceptionsByDid = new Map<string, Set<string>>()
for (const live of ctx) {
allowedHostsExceptionsByDid.set(
live.did,
new Set(DEFAULT_LIVE_ALLOWED_DOMAINS.concat(live.domains)),
)
}
if (!currentAccount?.did || !canGoLive)
return {
currentAccountAllowedHosts: new Set(),
defaultAllowedHosts,
allowedHostsExceptionsByDid,
}
const vip = ctx.find(live => live.did === currentAccount.did)
return {
currentAccountAllowedHosts: new Set(
DEFAULT_LIVE_ALLOWED_DOMAINS.concat(vip ? vip.domains : []),
),
defaultAllowedHosts,
allowedHostsExceptionsByDid,
}
}, [ctx, currentAccount, canGoLive])
}
export function useCanGoLive() {
const ax = useAnalytics()
const {hasSession} = useSession()
if (!hasSession) return false
return IS_DEV ? true : !ax.features.enabled(ax.features.LiveNowBetaDisable)
}
export function useCheckEmailConfirmed() {
const ctx = useContext(CheckEmailConfirmedContext)
if (ctx === null) {