forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useEffect, useMemo, useState} from 'react'
2import {computeAgeAssuranceRegionAccess} from '@atproto/api'
3
4import {useSession} from '#/state/session'
5import {useAgeAssuranceDataContext} from '#/ageAssurance/data'
6import {logger} from '#/ageAssurance/logger'
7import {
8 AgeAssuranceAccess,
9 type AgeAssuranceState,
10 AgeAssuranceStatus,
11 parseAccessFromString,
12 parseStatusFromString,
13} from '#/ageAssurance/types'
14import {getAgeAssuranceRegionConfigWithFallback} from '#/ageAssurance/util'
15import {useGeolocation} from '#/geolocation'
16
17export function useAgeAssuranceState(): AgeAssuranceState {
18 const {hasSession} = useSession()
19 const geolocation = useGeolocation()
20 const {config, state, data} = useAgeAssuranceDataContext()
21
22 return useMemo(() => {
23 /**
24 * This is where we control logged-out moderation prefs. It's all
25 * downstream of AA now.
26 */
27 if (!hasSession)
28 return {
29 status: AgeAssuranceStatus.Unknown,
30 access: AgeAssuranceAccess.Safe,
31 }
32
33 /**
34 * This can happen if the prefetch fails (such as due to network issues).
35 * The query handler will try it again, but if it continues to fail, of
36 * course we won't have config.
37 *
38 * In this case, fail open to avoid blocking users.
39 */
40 if (!config) {
41 logger.warn('useAgeAssuranceState: missing config')
42 return {
43 status: AgeAssuranceStatus.Unknown,
44 access: AgeAssuranceAccess.Safe,
45 error: 'config',
46 }
47 }
48
49 const region = getAgeAssuranceRegionConfigWithFallback(config, geolocation)
50 const isAARequired = region.countryCode !== '*'
51 const isTerminalState =
52 state?.status === 'assured' || state?.status === 'blocked'
53
54 /*
55 * If we are in a terminal state and AA is required for this region,
56 * we can trust the server state completely and avoid recomputing.
57 */
58 if (isTerminalState && isAARequired) {
59 return {
60 lastInitiatedAt: state.lastInitiatedAt,
61 status: parseStatusFromString(state.status),
62 access: parseAccessFromString(state.access),
63 }
64 }
65
66 /*
67 * Otherwise, we need to compute the access based on the latest data. For
68 * accounts with an accurate birthdate, our default fallback rules should
69 * ensure correct access.
70 */
71 const result = computeAgeAssuranceRegionAccess(region, data)
72 const computed = {
73 lastInitiatedAt: state?.lastInitiatedAt,
74 // prefer server state
75 status: state?.status
76 ? parseStatusFromString(state?.status)
77 : AgeAssuranceStatus.Unknown,
78 // prefer server state
79 access: result
80 ? parseAccessFromString(result.access)
81 : AgeAssuranceAccess.Full,
82 }
83 logger.debug('debug useAgeAssuranceState', {
84 region,
85 state,
86 data,
87 computed,
88 })
89 return computed
90 }, [hasSession, geolocation, config, state, data])
91}
92
93export function useOnAgeAssuranceAccessUpdate(
94 cb: (state: AgeAssuranceState) => void,
95) {
96 const state = useAgeAssuranceState()
97 // start with null to ensure callback is called on first render
98 const [prevAccess, setPrevAccess] = useState<AgeAssuranceAccess | null>(null)
99
100 useEffect(() => {
101 if (prevAccess !== state.access) {
102 setPrevAccess(state.access)
103 cb(state)
104 logger.debug(`useOnAgeAssuranceAccessUpdate`, {state})
105 }
106 }, [cb, state, prevAccess])
107}