diff --git a/.env.example b/.env.example index df3456410b..93fa4c3124 100644 --- a/.env.example +++ b/.env.example @@ -46,3 +46,6 @@ GEOLOCATION_DEV_URL= # live-events web worker URL LIVE_EVENTS_DEV_URL= + +# app-config web worker URL +APP_CONFIG_DEV_URL= diff --git a/src/App.native.tsx b/src/App.native.tsx index 5de9f82b7b..7dc16ec058 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -22,6 +22,10 @@ import {ThemeProvider} from '#/lib/ThemeContext' import I18nProvider from '#/locale/i18nProvider' import {logger} from '#/logger' import {Provider as A11yProvider} from '#/state/a11y' +import { + prefetchAppConfig, + Provider as AppConfigProvider, +} from '#/state/appConfig' import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes' import {Provider as DialogStateProvider} from '#/state/dialogs' import {Provider as EmailVerificationProvider} from '#/state/email-verification' @@ -103,6 +107,7 @@ if (IS_ANDROID) { Geo.resolve() prefetchAgeAssuranceConfig() prefetchLiveEvents() +prefetchAppConfig() function InnerApp() { const [isReady, setIsReady] = React.useState(false) @@ -228,38 +233,40 @@ function App() { */ return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) } diff --git a/src/App.web.tsx b/src/App.web.tsx index abf188ea28..701167f392 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -13,6 +13,10 @@ import {ThemeProvider} from '#/lib/ThemeContext' import I18nProvider from '#/locale/i18nProvider' import {logger} from '#/logger' import {Provider as A11yProvider} from '#/state/a11y' +import { + prefetchAppConfig, + Provider as AppConfigProvider, +} from '#/state/appConfig' import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes' import {Provider as DialogStateProvider} from '#/state/dialogs' import {Provider as EmailVerificationProvider} from '#/state/email-verification' @@ -79,6 +83,7 @@ import {Provider as HideBottomBarBorderProvider} from './lib/hooks/useHideBottom Geo.resolve() prefetchAgeAssuranceConfig() prefetchLiveEvents() +prefetchAppConfig() function InnerApp() { const [isReady, setIsReady] = useState(false) @@ -202,31 +207,33 @@ function App() { */ return ( - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + ) } diff --git a/src/env/common.ts b/src/env/common.ts index 5070b35968..5d8d89aace 100644 --- a/src/env/common.ts +++ b/src/env/common.ts @@ -141,3 +141,13 @@ export const LIVE_EVENTS_PROD_URL = `https://live-events.workers.bsky.app` export const LIVE_EVENTS_URL = IS_DEV ? (LIVE_EVENTS_DEV_URL ?? LIVE_EVENTS_PROD_URL) : LIVE_EVENTS_PROD_URL + +/** + * URLs for the app-config web worker. Can be a + * locally running server, see `env.example` for more. + */ +export const APP_CONFIG_DEV_URL = process.env.APP_CONFIG_DEV_URL +export const APP_CONFIG_PROD_URL = `https://app-config.workers.bsky.app` +export const APP_CONFIG_URL = IS_DEV + ? (APP_CONFIG_DEV_URL ?? APP_CONFIG_PROD_URL) + : APP_CONFIG_PROD_URL diff --git a/src/state/appConfig.tsx b/src/state/appConfig.tsx new file mode 100644 index 0000000000..2258efe3ad --- /dev/null +++ b/src/state/appConfig.tsx @@ -0,0 +1,74 @@ +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: [], + }, +} + +async function fetchAppConfig(): Promise { + try { + const res = await fetch(`${APP_CONFIG_URL}/config`) + if (!res.ok) return null + const data = await res.json() + return data + } catch { + return null + } +} + +const Context = createContext(DEFAULT_APP_CONFIG_RESPONSE) + +export function Provider({children}: React.PropsWithChildren<{}>) { + const {data} = useQuery( + { + staleTime: Infinity, + gcTime: Infinity, + queryKey: appConfigQueryKey, + async queryFn() { + return fetchAppConfig() + }, + }, + qc, + ) + return ( + + {children} + + ) +} + +export async function prefetchAppConfig() { + const data = await fetchAppConfig() + if (data) { + qc.setQueryData(appConfigQueryKey, data) + } +} + +export function useAppConfig() { + const ctx = useContext(Context) + if (!ctx) { + throw new Error('useAppConfig must be used within a Provider') + } + return ctx +}