forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {createContext, useContext, useMemo} from 'react'
2import {QueryClient, useQuery} from '@tanstack/react-query'
3
4import {useOnAppStateChange} from '#/lib/appState'
5import {useIsBskyTeam} from '#/lib/hooks/useIsBskyTeam'
6import {
7 convertBskyAppUrlIfNeeded,
8 isBskyCustomFeedUrl,
9 makeRecordUri,
10} from '#/lib/strings/url-helpers'
11import {LIVE_EVENTS_URL} from '#/env'
12import {useLiveEventPreferences} from '#/features/liveEvents/preferences'
13import {type LiveEventsWorkerResponse} from '#/features/liveEvents/types'
14import {useDevMode} from '#/storage/hooks/dev-mode'
15
16const qc = new QueryClient()
17const liveEventsQueryKey = ['live-events']
18
19export const DEFAULT_LIVE_EVENTS = {
20 feeds: [],
21}
22
23async function fetchLiveEvents(): Promise<LiveEventsWorkerResponse | null> {
24 try {
25 const res = await fetch(`${LIVE_EVENTS_URL}/config`)
26 if (!res.ok) return null
27 const data = await res.json()
28 return data
29 } catch {
30 return null
31 }
32}
33
34const Context = createContext<LiveEventsWorkerResponse>(DEFAULT_LIVE_EVENTS)
35
36export function Provider({children}: React.PropsWithChildren<{}>) {
37 const [isDevMode] = useDevMode()
38 const isBskyTeam = useIsBskyTeam()
39 const {data, refetch} = useQuery(
40 {
41 // keep this, prefectching handles initial load
42 staleTime: 1000 * 15,
43 queryKey: liveEventsQueryKey,
44 refetchInterval: 1000 * 60 * 5, // refetch every 5 minutes
45 async queryFn() {
46 return fetchLiveEvents()
47 },
48 },
49 qc,
50 )
51
52 useOnAppStateChange(state => {
53 if (state === 'active') refetch()
54 })
55
56 const ctx = useMemo(() => {
57 if (!data) return DEFAULT_LIVE_EVENTS
58 const feeds = data.feeds.filter(f => {
59 if (f.preview && !isBskyTeam) return false
60 return true
61 })
62 return {
63 ...data,
64 // only one at a time for now, unless bsky team and dev mode
65 feeds: isBskyTeam && isDevMode ? feeds : feeds.slice(0, 1),
66 }
67 }, [data, isBskyTeam, isDevMode])
68
69 return <Context.Provider value={ctx}>{children}</Context.Provider>
70}
71
72export async function prefetchLiveEvents() {
73 const data = await fetchLiveEvents()
74 if (data) {
75 qc.setQueryData(liveEventsQueryKey, data)
76 }
77}
78
79export function useLiveEvents() {
80 const ctx = useContext(Context)
81 if (!ctx) {
82 throw new Error('useLiveEventsContext must be used within a Provider')
83 }
84 return ctx
85}
86
87export function useUserPreferencedLiveEvents() {
88 const events = useLiveEvents()
89 const {data, isLoading} = useLiveEventPreferences()
90 if (isLoading) return DEFAULT_LIVE_EVENTS
91 const {hideAllFeeds, hiddenFeedIds} = data
92 return {
93 ...events,
94 feeds: hideAllFeeds
95 ? []
96 : events.feeds.filter(f => {
97 const hidden = f?.id ? hiddenFeedIds.includes(f?.id || '') : false
98 return !hidden
99 }),
100 }
101}
102
103export function useActiveLiveEventFeedUris() {
104 const {feeds} = useLiveEvents()
105
106 return new Set(
107 feeds
108 // insurance
109 .filter(f => isBskyCustomFeedUrl(f.url))
110 .map(f => {
111 const uri = convertBskyAppUrlIfNeeded(f.url)
112 const [_0, did, _1, rkey] = uri.split('/').filter(Boolean)
113 const urip = makeRecordUri(did, 'app.bsky.feed.generator', rkey)
114 return urip.toString()
115 }),
116 )
117}