Add liveEvents context

This commit is contained in:
Eric Bailey
2026-01-13 15:34:29 -06:00
parent 9a20dd0ddd
commit fd29569ee7
6 changed files with 189 additions and 3 deletions
+8 -1
View File
@@ -69,6 +69,10 @@ import {Provider as VideoVolumeProvider} from '#/components/Post/Embed/VideoEmbe
import {ToastOutlet} from '#/components/Toast'
import {Provider as AgeAssuranceV2Provider} from '#/ageAssurance'
import {prefetchAgeAssuranceConfig} from '#/ageAssurance'
import {
prefetchLiveEvents,
Provider as LiveEventsProvider,
} from '#/features/liveEvents/context'
import * as Geo from '#/geolocation'
import {Splash} from '#/Splash'
import {BottomSheetProvider} from '../modules/bottom-sheet'
@@ -92,6 +96,7 @@ if (isAndroid) {
*/
Geo.resolve()
prefetchAgeAssuranceConfig()
prefetchLiveEvents()
function InnerApp() {
const [isReady, setIsReady] = React.useState(false)
@@ -230,7 +235,9 @@ function App() {
<StarterPackProvider>
<SafeAreaProvider
initialMetrics={initialWindowMetrics}>
<InnerApp />
<LiveEventsProvider>
<InnerApp />
</LiveEventsProvider>
</SafeAreaProvider>
</StarterPackProvider>
</BottomSheetProvider>
+8 -1
View File
@@ -57,6 +57,10 @@ import {Provider as VideoVolumeProvider} from '#/components/Post/Embed/VideoEmbe
import {ToastOutlet} from '#/components/Toast'
import {Provider as AgeAssuranceV2Provider} from '#/ageAssurance'
import {prefetchAgeAssuranceConfig} from '#/ageAssurance'
import {
prefetchLiveEvents,
Provider as LiveEventsProvider,
} from '#/features/liveEvents/context'
import * as Geo from '#/geolocation'
import {Splash} from '#/Splash'
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
@@ -67,6 +71,7 @@ import {Provider as HideBottomBarBorderProvider} from './lib/hooks/useHideBottom
*/
Geo.resolve()
prefetchAgeAssuranceConfig()
prefetchLiveEvents()
function InnerApp() {
const [isReady, setIsReady] = React.useState(false)
@@ -198,7 +203,9 @@ function App() {
<LightboxStateProvider>
<PortalProvider>
<StarterPackProvider>
<InnerApp />
<LiveEventsProvider>
<InnerApp />
</LiveEventsProvider>
</StarterPackProvider>
</PortalProvider>
</LightboxStateProvider>
+6 -1
View File
@@ -11,7 +11,12 @@ import {
import {themes} from '#/alf/themes'
import {type Device} from '#/storage'
export {type TextStyleProp, type Theme, type ViewStyleProp} from '@bsky.app/alf'
export {
type TextStyleProp,
type Theme,
utils,
type ViewStyleProp,
} from '@bsky.app/alf'
export {atoms} from '#/alf/atoms'
export * from '#/alf/breakpoints'
export * from '#/alf/fonts'
+55
View File
@@ -0,0 +1,55 @@
import {createContext, useContext} from 'react'
import {QueryClient, useQuery} from '@tanstack/react-query'
import {IS_DEV, LIVE_EVENTS_URL} from '#/env'
import {type LiveEventsWorkerResponse} from '#/features/liveEvents/types'
const qc = new QueryClient()
const liveEventsQueryKey = ['live-events']
async function fetchLiveEvents(): Promise<LiveEventsWorkerResponse | null> {
try {
const res = await fetch(`${LIVE_EVENTS_URL}/config`)
if (!res.ok) return null
const data = await res.json()
return data
} catch {
return null
}
}
const Context = createContext<LiveEventsWorkerResponse>({
feeds: [],
})
export function Provider({children}: React.PropsWithChildren<{}>) {
const {data} = useQuery(
{
staleTime: IS_DEV ? 5e3 : 1000 * 60,
queryKey: liveEventsQueryKey,
async queryFn() {
return fetchLiveEvents()
},
},
qc,
)
return (
<Context.Provider value={data || {feeds: []}}>{children}</Context.Provider>
)
}
export async function prefetchLiveEvents() {
const data = await fetchLiveEvents()
if (data) {
qc.setQueryData(liveEventsQueryKey, data)
}
}
export function useLiveEvents() {
const ctx = useContext(Context)
if (!ctx) {
throw new Error('useLiveEventsContext must be used within a Provider')
}
return ctx
}
+94
View File
@@ -0,0 +1,94 @@
import {createContext, useContext} from 'react'
import {QueryClient, useQuery} from '@tanstack/react-query'
import {IS_DEV, LIVE_EVENTS_URL} from '#/env'
type LiveEventFeedImageLayout = 'wide' // maybe more in the future
type LiveEventFeedImage = {
alt: string
overlayColor: string
url: string
blurhash: string
}
type LiveEventFeed = {
title: string
url: string
images: Partial<Record<LiveEventFeedImageLayout, LiveEventFeedImage>>
}
type LiveEventsConfig = {
feeds: LiveEventFeed[]
}
const qc = new QueryClient({
defaultOptions: {
queries: {
/**
* We clear this manually, so disable automatic garbage collection.
* @see https://tanstack.com/query/latest/docs/framework/react/plugins/persistQueryClient#how-it-works
*/
gcTime: Infinity,
},
},
})
const liveEventsQueryKey = ['live-events']
async function getLiveEvents(): Promise<LiveEventsConfig | null> {
const res = await fetch(`${LIVE_EVENTS_URL}/config`)
if (!res.ok) return null
return res.json()
}
export function getLiveEventsCache() {
return qc.getQueryData<LiveEventsConfig>(liveEventsQueryKey)
}
export async function prefetchLiveEvents() {
const data = await getLiveEvents()
if (data) {
qc.setQueryData(liveEventsQueryKey, data)
}
}
export const Context = createContext<LiveEventsConfig>({
feeds: [],
})
export function useLiveEventsContext() {
const ctx = useContext(Context)
if (!ctx) {
throw new Error('useLiveEventsContext must be used within a Provider')
}
return ctx
}
export function Provider({children}: React.PropsWithChildren<{}>) {
const {data} = useQuery(
{
/**
* Will re-fetch when stale, at most every minute (or 5s in dev for easier
* testing).
*
* @see https://tanstack.com/query/latest/docs/framework/react/guides/initial-query-data#initial-data-from-the-cache-with-initialdataupdatedat
*/
staleTime: IS_DEV ? 5e3 : 1000 * 60,
/**
* N.B. if prefetch failed above, we'll have no `initialData`, and this
* query will run on startup.
*/
initialData: getLiveEventsCache(),
initialDataUpdatedAt: () =>
qc.getQueryState(liveEventsQueryKey)?.dataUpdatedAt,
queryKey: liveEventsQueryKey,
async queryFn() {
console.debug(`live-events: fetching config`)
return getLiveEvents()
},
},
qc,
)
return (
<Context.Provider value={data || {feeds: []}}>{children}</Context.Provider>
)
}
+18
View File
@@ -0,0 +1,18 @@
export type LiveEventFeedImageLayout = 'wide' // maybe more in the future
export type LiveEventFeedImage = {
alt: string
overlayColor: string
url: string
blurhash: string
}
export type LiveEventFeed = {
title: string
url: string
images: Record<LiveEventFeedImageLayout, LiveEventFeedImage>
}
export type LiveEventsWorkerResponse = {
feeds: LiveEventFeed[]
}