8c2e4c6fad
Co-authored-by: Eric Bailey <git@esb.lol>
131 lines
4.1 KiB
TypeScript
131 lines
4.1 KiB
TypeScript
import {useMemo} from 'react'
|
|
import {
|
|
ageAssuranceRuleIDs as ids,
|
|
type AppBskyAgeassuranceDefs,
|
|
type AtpAgent,
|
|
getAgeAssuranceRegionConfig,
|
|
type ModerationPrefs,
|
|
} from '@atproto/api'
|
|
|
|
import {getAge} from '#/lib/strings/time'
|
|
import {restrictChatSettings} from '#/state/queries/messages/restrictChatSettings'
|
|
import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation'
|
|
import {
|
|
getDidFromAgentSession,
|
|
getOtherRequiredDataFromCache,
|
|
useAgeAssuranceDataContext,
|
|
} from '#/ageAssurance/data'
|
|
import {AgeAssuranceAccess} from '#/ageAssurance/types'
|
|
import {type Geolocation, useGeolocation} from '#/geolocation'
|
|
|
|
export const MIN_ACCESS_AGE = 13
|
|
const FALLBACK_REGION_CONFIG: AppBskyAgeassuranceDefs.ConfigRegion = {
|
|
countryCode: '*',
|
|
regionCode: undefined,
|
|
minAccessAge: MIN_ACCESS_AGE,
|
|
rules: [
|
|
{
|
|
$type: ids.IfDeclaredOverAge,
|
|
age: MIN_ACCESS_AGE,
|
|
access: AgeAssuranceAccess.Full,
|
|
},
|
|
{
|
|
$type: ids.Default,
|
|
access: AgeAssuranceAccess.None,
|
|
},
|
|
],
|
|
}
|
|
|
|
/**
|
|
* Get age assurance region config based on geolocation, with fallback to
|
|
* app defaults if no region config is found.
|
|
*
|
|
* See {@link getAgeAssuranceRegionConfig} for the generic option, which can
|
|
* return undefined if the geolocation does not match any AA region.
|
|
*/
|
|
export function getAgeAssuranceRegionConfigWithFallback(
|
|
config: AppBskyAgeassuranceDefs.Config,
|
|
geolocation: Geolocation,
|
|
): AppBskyAgeassuranceDefs.ConfigRegion {
|
|
const region = getAgeAssuranceRegionConfig(config, {
|
|
countryCode: geolocation.countryCode ?? '',
|
|
regionCode: geolocation.regionCode,
|
|
})
|
|
|
|
return region || FALLBACK_REGION_CONFIG
|
|
}
|
|
|
|
/**
|
|
* Hook to get the age assurance region config based on current geolocation.
|
|
* Does not fall-back to our app defaults. If no config is found, returns
|
|
* undefined, which indicates no regional age assurance rules apply.
|
|
*/
|
|
export function useAgeAssuranceRegionConfig() {
|
|
const geolocation = useGeolocation()
|
|
const {config} = useAgeAssuranceDataContext()
|
|
return useMemo(() => {
|
|
if (!config) return
|
|
// use generic helper, we want to potentially return undefined
|
|
return getAgeAssuranceRegionConfig(config, {
|
|
countryCode: geolocation.countryCode ?? '',
|
|
regionCode: geolocation.regionCode,
|
|
})
|
|
}, [config, geolocation])
|
|
}
|
|
|
|
/**
|
|
* Hook to get the age assurance region config based on current geolocation.
|
|
* Falls back to our app defaults if no region config is found.
|
|
*/
|
|
export function useAgeAssuranceRegionConfigWithFallback() {
|
|
return useAgeAssuranceRegionConfig() || FALLBACK_REGION_CONFIG
|
|
}
|
|
|
|
/**
|
|
* Some users may have erroneously set their birth date to the current date
|
|
* if one wasn't set on their account. We previously didn't do validation on
|
|
* the bday dialog, and it defaulted to the current date. This bug _has_ been
|
|
* seen in production, so we need to check for it where possible.
|
|
*/
|
|
export function isLegacyBirthdateBug(birthDate: string) {
|
|
return ['2025', '2024', '2023'].includes((birthDate || '').slice(0, 4))
|
|
}
|
|
|
|
/**
|
|
* Returns whether the date (converted to an age as a whole integer) is under
|
|
* the provided minimum age.
|
|
*/
|
|
export function isUnderAge(birthDate: string, age: number) {
|
|
return getAge(new Date(birthDate)) < age
|
|
}
|
|
|
|
export function getBirthdateStringFromAge(age: number) {
|
|
const today = new Date()
|
|
return new Date(
|
|
today.getFullYear() - age,
|
|
today.getMonth(),
|
|
today.getDate() - 1, // set to day before to ensure age is reached
|
|
).toISOString()
|
|
}
|
|
|
|
export const makeAgeRestrictedModerationPrefs = (
|
|
prefs: ModerationPrefs,
|
|
): ModerationPrefs => ({
|
|
...prefs,
|
|
adultContentEnabled: false,
|
|
labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES,
|
|
})
|
|
|
|
/**
|
|
* Checks our cache of the actor's chat declaration record, and if it's not
|
|
* already restricted, restricts it.
|
|
*/
|
|
export function maybeRestrictChatSettings({agent}: {agent: AtpAgent}) {
|
|
const did = getDidFromAgentSession(agent)
|
|
if (!did) return
|
|
const data = getOtherRequiredDataFromCache({did})
|
|
// ...update the chat setting record if allowIncoming is not already 'none'.
|
|
if (data?.actorDeclaration?.allowIncoming === 'none') return
|
|
restrictChatSettings({agent, did})
|
|
}
|