Files
bsky-social-app/src/geolocation/device.ts
T
Eric Bailey c4aef9f668 Age Assurance V2 (#9479)
* Age Assurance V2

* Tighten up test

* Add todos for sdk migration

* Align RQ versions

* Use useEffect for side effect

* Improve effects, memoize

* Standarize on birthdate

* Copy feedback

* Copilot

* Add support link

* Reove double ..

* Cleanup

* Remove redirect dialog

* Cleanup todos, add comments

* Update splash in main template too

* Mock some stuff

* Exhaustive checks

Co-authored-by: Samuel Newman <mozzius@protonmail.com>

* Exhaustive checks

Co-authored-by: Samuel Newman <mozzius@protonmail.com>

* Small fix to bday handling

* Add comment

* onboarding style tweak

sneaking this in sorry!

* rm unreachable breaks

* Put useIntentHandler back on web

* Remove misleading success set

* Align on birthdate

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2025-12-04 15:20:00 -06:00

145 lines
4.2 KiB
TypeScript

import {useCallback, useEffect, useRef} from 'react'
import * as Location from 'expo-location'
import {createPermissionHook} from 'expo-modules-core'
import {isNative} from '#/platform/detection'
import * as debug from '#/geolocation/debug'
import {logger} from '#/geolocation/logger'
import {type Geolocation} from '#/geolocation/types'
import {normalizeDeviceLocation} from '#/geolocation/util'
import {device} from '#/storage'
/**
* Location.useForegroundPermissions on web just errors if the
* navigator.permissions API is not available. We need to catch and ignore it,
* since it's effectively denied.
*
* @see https://github.com/expo/expo/blob/72f1562ed9cce5ff6dfe04aa415b71632a3d4b87/packages/expo-location/src/Location.ts#L290-L293
*/
const useForegroundPermissions = createPermissionHook({
getMethod: () =>
Location.getForegroundPermissionsAsync().catch(error => {
logger.debug(
'useForegroundPermission: error getting location permissions',
{safeMessage: error},
)
return {
status: Location.PermissionStatus.DENIED,
granted: false,
canAskAgain: false,
expires: 0,
}
}),
requestMethod: () =>
Location.requestForegroundPermissionsAsync().catch(error => {
logger.debug(
'useForegroundPermission: error requesting location permissions',
{safeMessage: error},
)
return {
status: Location.PermissionStatus.DENIED,
granted: false,
canAskAgain: false,
expires: 0,
}
}),
})
export async function getDeviceGeolocation(): Promise<Geolocation> {
if (debug.enabled) return debug.resolve(debug.deviceGeolocation)
try {
const geocode = await Location.getCurrentPositionAsync()
const locations = await Location.reverseGeocodeAsync({
latitude: geocode.coords.latitude,
longitude: geocode.coords.longitude,
})
const location = locations.at(0)
const normalized = location ? normalizeDeviceLocation(location) : undefined
return {
countryCode: normalized?.countryCode ?? undefined,
regionCode: normalized?.regionCode ?? undefined,
}
} catch (e) {
logger.error('getDeviceGeolocation: failed', {safeMessage: e})
return {
countryCode: undefined,
regionCode: undefined,
}
}
}
export function useRequestDeviceGeolocation(): () => Promise<
| {
granted: true
location: Geolocation | undefined
}
| {
granted: false
}
> {
return useCallback(async () => {
const status = await Location.requestForegroundPermissionsAsync()
if (status.granted) {
return {
granted: true,
location: await getDeviceGeolocation(),
}
} else {
return {
granted: false,
}
}
}, [])
}
/**
* Hook to get and sync the device geolocation from the device GPS and store it
* using device storage. If permissions are not granted, it will clear any cached
* storage value.
*/
export function useSyncDeviceGeolocationOnStartup(
sync: (location: Geolocation | undefined) => void,
) {
const synced = useRef(false)
const [status] = useForegroundPermissions()
useEffect(() => {
if (!isNative) return
async function get() {
// no need to set this more than once per session
if (synced.current) return
logger.debug('useSyncDeviceGeolocationOnStartup: checking perms')
if (status?.granted) {
const location = await getDeviceGeolocation()
if (location) {
logger.debug('useSyncDeviceGeolocationOnStartup: got location')
sync(location)
synced.current = true
}
} else {
const hasCachedValue = device.get(['deviceGeolocation']) !== undefined
/**
* If we have a cached value, but user has revoked permissions,
* quietly (will take effect lazily) clear this out.
*/
if (hasCachedValue) {
logger.debug(
'useSyncDeviceGeolocationOnStartup: clearing cached location, perms revoked',
)
device.set(['deviceGeolocation'], undefined)
}
}
}
get().catch(e => {
logger.error(
'useSyncDeviceGeolocationOnStartup: failed to get location',
{
safeMessage: e,
},
)
})
}, [status, sync])
}