Fix region key symmetry
This commit is contained in:
+25
-29
@@ -35,6 +35,7 @@ import {
|
|||||||
isLegacyBirthdateBug,
|
isLegacyBirthdateBug,
|
||||||
} from '#/ageAssurance/util'
|
} from '#/ageAssurance/util'
|
||||||
import {IS_DEV, IS_NATIVE} from '#/env'
|
import {IS_DEV, IS_NATIVE} from '#/env'
|
||||||
|
import {useGeolocation} from '#/geolocation'
|
||||||
import {device} from '#/storage'
|
import {device} from '#/storage'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -523,7 +524,7 @@ export async function getDeviceSignals(): Promise<
|
|||||||
/**
|
/**
|
||||||
* The raw region-keyed map of device signals (all regions). Used internally by
|
* The raw region-keyed map of device signals (all regions). Used internally by
|
||||||
* the query + writer, which operate on the full map. Most consumers want
|
* the query + writer, which operate on the full map. Most consumers want
|
||||||
* {@link getDeviceSignalsFromCacheForCurrentRegion}, which resolves to the
|
* {@link getDeviceSignalsFromCacheForRegion}, which resolves to the
|
||||||
* current region.
|
* current region.
|
||||||
*/
|
*/
|
||||||
export function getDeviceSignalsMapFromCache({
|
export function getDeviceSignalsMapFromCache({
|
||||||
@@ -535,39 +536,19 @@ export function getDeviceSignalsMapFromCache({
|
|||||||
createDeviceSignalsQueryKey({did}),
|
createDeviceSignalsQueryKey({did}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Resolves a region-keyed signals map down to the signals for the region the
|
|
||||||
* user is currently in (per `mergedGeolocation`). Returns undefined when we
|
|
||||||
* have no map, no geolocation, or no stored signals for that region.
|
|
||||||
*
|
|
||||||
* Device assurance is region-bound, so we only ever surface the signals
|
|
||||||
* captured in the user's current region.
|
|
||||||
*/
|
|
||||||
function selectDeviceSignalsForCurrentRegion(
|
|
||||||
map: AgeAssuranceDeviceSignals | undefined,
|
|
||||||
): AgeRange.AgeRangeResponse | undefined {
|
|
||||||
if (!map) return undefined
|
|
||||||
const geolocation = device.get(['mergedGeolocation'])
|
|
||||||
if (!geolocation?.countryCode) return undefined
|
|
||||||
return map[
|
|
||||||
createRegionKey({
|
|
||||||
countryCode: geolocation.countryCode,
|
|
||||||
regionCode: geolocation.regionCode,
|
|
||||||
})
|
|
||||||
]
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Returns the device signals for the region the user is currently in, or
|
* Returns the device signals for the region the user is currently in, or
|
||||||
* undefined. See {@link selectDeviceSignalsForCurrentRegion}.
|
* undefined.
|
||||||
*/
|
*/
|
||||||
export function getDeviceSignalsFromCacheForCurrentRegion({
|
export function getDeviceSignalsFromCacheForRegion({
|
||||||
did,
|
did,
|
||||||
|
region,
|
||||||
}: {
|
}: {
|
||||||
did: string
|
did: string
|
||||||
|
region: AppBskyAgeassuranceDefs.ConfigRegion
|
||||||
}): AgeRange.AgeRangeResponse | undefined {
|
}): AgeRange.AgeRangeResponse | undefined {
|
||||||
return selectDeviceSignalsForCurrentRegion(
|
const regionKey = createRegionKey(region)
|
||||||
getDeviceSignalsMapFromCache({did}),
|
return getDeviceSignalsMapFromCache({did})?.[regionKey]
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Stores freshly granted device signals into the (persisted) cache under the
|
* Stores freshly granted device signals into the (persisted) cache under the
|
||||||
@@ -632,6 +613,21 @@ export async function prefetchDeviceSignals({agent}: {agent: AtpAgent}) {
|
|||||||
export function useDeviceSignalsQuery() {
|
export function useDeviceSignalsQuery() {
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
const did = getDidFromAgentSession(agent)
|
const did = getDidFromAgentSession(agent)
|
||||||
|
const {data: config} = useConfigQuery()
|
||||||
|
const geolocation = useGeolocation()
|
||||||
|
/*
|
||||||
|
* Resolve the matched config region (no fallback) and key off it, so the read
|
||||||
|
* stays symmetric with the write (see `setDeviceSignalsForRegion`). When
|
||||||
|
* geolocation matches no AA region there's no device grant to surface.
|
||||||
|
*/
|
||||||
|
const regionConfig = config
|
||||||
|
? getAgeAssuranceRegionConfig(config, {
|
||||||
|
countryCode: geolocation.countryCode ?? '',
|
||||||
|
regionCode: geolocation.regionCode,
|
||||||
|
})
|
||||||
|
: undefined
|
||||||
|
const regionKey = regionConfig ? createRegionKey(regionConfig) : undefined
|
||||||
|
|
||||||
return useQuery(
|
return useQuery(
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -655,7 +651,7 @@ export function useDeviceSignalsQuery() {
|
|||||||
// The cache holds the full region-keyed map (the writer merges into it);
|
// The cache holds the full region-keyed map (the writer merges into it);
|
||||||
// `select` resolves it to the current region for consumers without
|
// `select` resolves it to the current region for consumers without
|
||||||
// mutating the cached value.
|
// mutating the cached value.
|
||||||
select: selectDeviceSignalsForCurrentRegion,
|
select: map => (map && regionKey ? map[regionKey] : undefined),
|
||||||
},
|
},
|
||||||
qc,
|
qc,
|
||||||
)
|
)
|
||||||
@@ -705,7 +701,7 @@ export type AgeAssuranceServerData = {
|
|||||||
/**
|
/**
|
||||||
* The native on-device age signals for the region the user is currently in,
|
* The native on-device age signals for the region the user is currently in,
|
||||||
* if they've granted access there. Already resolved from the region-keyed
|
* if they've granted access there. Already resolved from the region-keyed
|
||||||
* cache (see `getDeviceSignalsFromCacheForCurrentRegion`), so a grant from
|
* cache (see `getDeviceSignalsFromCacheForRegion`), so a grant from
|
||||||
* another region won't appear here. Only consumed for regions that permit
|
* another region won't appear here. Only consumed for regions that permit
|
||||||
* device verification.
|
* device verification.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ import type * as AgeRange from 'expo-age-range'
|
|||||||
import {
|
import {
|
||||||
type AppBskyAgeassuranceDefs,
|
type AppBskyAgeassuranceDefs,
|
||||||
computeAgeAssuranceRegionAccess,
|
computeAgeAssuranceRegionAccess,
|
||||||
|
getAgeAssuranceRegionConfig,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
|
||||||
import {getAge} from '#/lib/strings/time'
|
import {getAge} from '#/lib/strings/time'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {
|
import {
|
||||||
getConfigFromCache,
|
getConfigFromCache,
|
||||||
getDeviceSignalsFromCacheForCurrentRegion,
|
getDeviceSignalsFromCacheForRegion,
|
||||||
getOtherRequiredDataFromCache,
|
getOtherRequiredDataFromCache,
|
||||||
getServerStateFromCache,
|
getServerStateFromCache,
|
||||||
useAgeAssuranceServerDataContext,
|
useAgeAssuranceServerDataContext,
|
||||||
@@ -140,7 +141,6 @@ export function unsafeGetAndComputeAgeAssurance({did}: {did: string}) {
|
|||||||
const config = getConfigFromCache()
|
const config = getConfigFromCache()
|
||||||
const state = getServerStateFromCache({did})
|
const state = getServerStateFromCache({did})
|
||||||
const requiredData = getOtherRequiredDataFromCache({did})
|
const requiredData = getOtherRequiredDataFromCache({did})
|
||||||
const deviceSignals = getDeviceSignalsFromCacheForCurrentRegion({did})
|
|
||||||
const geolocation = device.get(['mergedGeolocation'])
|
const geolocation = device.get(['mergedGeolocation'])
|
||||||
|
|
||||||
if (!geolocation || !config || !state || !requiredData) {
|
if (!geolocation || !config || !state || !requiredData) {
|
||||||
@@ -153,6 +153,19 @@ export function unsafeGetAndComputeAgeAssurance({did}: {did: string}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const region = getAgeAssuranceRegionConfigWithFallback(config, geolocation)
|
const region = getAgeAssuranceRegionConfigWithFallback(config, geolocation)
|
||||||
|
/*
|
||||||
|
* Device signals are keyed off the matched config region (no fallback): if
|
||||||
|
* geolocation matches no AA region there's no device grant to read, so we
|
||||||
|
* skip the lookup rather than keying off FALLBACK_REGION_CONFIG. This keeps
|
||||||
|
* the read key symmetric with the write (see `setDeviceSignalsForRegion`).
|
||||||
|
*/
|
||||||
|
const deviceRegion = getAgeAssuranceRegionConfig(config, {
|
||||||
|
countryCode: geolocation.countryCode ?? '',
|
||||||
|
regionCode: geolocation.regionCode,
|
||||||
|
})
|
||||||
|
const deviceSignals = deviceRegion
|
||||||
|
? getDeviceSignalsFromCacheForRegion({did, region: deviceRegion})
|
||||||
|
: undefined
|
||||||
const metadata: AgeAssuranceMetadata = {
|
const metadata: AgeAssuranceMetadata = {
|
||||||
accountCreatedAt: state.metadata?.accountCreatedAt,
|
accountCreatedAt: state.metadata?.accountCreatedAt,
|
||||||
declaredAge: requiredData?.birthdate
|
declaredAge: requiredData?.birthdate
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ export function createRegionKey(region: {
|
|||||||
* Derives age assurance data from native device signals for the given region,
|
* Derives age assurance data from native device signals for the given region,
|
||||||
* but only when the region permits device verification. The signals are
|
* but only when the region permits device verification. The signals are
|
||||||
* expected to already be resolved to the user's current region (see
|
* expected to already be resolved to the user's current region (see
|
||||||
* `getDeviceSignalsFromCacheForCurrentRegion`), so a grant captured in another
|
* `getDeviceSignalsFromCacheForRegion`), so a grant captured in another region
|
||||||
* region won't reach here.
|
* won't reach here.
|
||||||
*
|
*
|
||||||
* The OS-provided `lowerBound` is the minimum age the platform will attest to,
|
* The OS-provided `lowerBound` is the minimum age the platform will attest to,
|
||||||
* which maps onto the `assuredAge` input of the rule engine (i.e.
|
* which maps onto the `assuredAge` input of the rule engine (i.e.
|
||||||
|
|||||||
Reference in New Issue
Block a user