This commit is contained in:
Eric Bailey
2025-12-16 12:33:45 -06:00
parent 495d1fe9a7
commit e06c95e2f0
5 changed files with 119 additions and 5 deletions
+1
View File
@@ -126,6 +126,7 @@ module.exports = function (_config) {
'com.apple.security.application-groups': 'group.app.bsky',
'com.apple.developer.usernotifications.communication': true,
// 'com.apple.developer.device-information.user-assigned-device-name': true,
'com.apple.developer.declared-age-range': true,
},
privacyManifests: {
NSPrivacyCollectedDataTypes: [
+1
View File
@@ -157,6 +157,7 @@
"emoji-regex": "^10.4.0",
"eventemitter3": "^5.0.1",
"expo": "54.0.34",
"expo-age-range": "^0.2.0",
"expo-application": "~7.0.8",
"expo-blur": "~15.0.8",
"expo-build-properties": "~1.0.10",
+13 -1
View File
@@ -4,6 +4,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import * as AgeRange from 'expo-age-range'
import {
SupportCode,
@@ -348,7 +349,18 @@ function AccessSection() {
label={_(msg`Verify now`)}
size="large"
color={hasInitiated ? 'secondary' : 'primary'}
onPress={() => {
onPress={async () => {
try {
const ageRange = await AgeRange.requestAgeRangeAsync({
threshold1: 10,
threshold2: 13,
threshold3: 18,
});
console.log(ageRange)
} catch (e) {
console.error(e)
}
return
control.open()
ax.metric('ageAssurance:initDialogOpen', {
hasInitiatedPreviously: hasInitiated,
+95
View File
@@ -11,6 +11,7 @@ import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persist
import {focusManager, QueryClient, useQuery} from '@tanstack/react-query'
import {persistQueryClient} from '@tanstack/react-query-persist-client'
import debounce from 'lodash.debounce'
import * as AgeRange from 'expo-age-range';
import {networkRetry} from '#/lib/async/retry'
import {PUBLIC_BSKY_SERVICE} from '#/lib/constants'
@@ -485,6 +486,99 @@ export function useOtherRequiredDataQuery() {
)
}
export function createDeviceSignalsQueryKey({did}: {did: string}) {
return ['device-signals', did]
}
export async function getDeviceSignals(): Promise<AgeRange.AgeRangeResponse | undefined> {
if (debug.enabled) return debug.resolve(debug.deviceSignals)
try {
return AgeRange.requestAgeRangeAsync({
threshold1: 13,
threshold2: 16,
threshold3: 18,
});
} catch (e: any) {
logger.error(`getDeviceSignals: failed to get device signals`, {
safeMessage: e.message,
})
return undefined
}
}
export function getDeviceSignalsFromCache({
did,
}: {
did: string
}):
| AgeRange.AgeRangeResponse
| undefined {
return qc.getQueryData<AgeRange.AgeRangeResponse>(
createDeviceSignalsQueryKey({did}),
)
}
let deviceSignalsPrefetchPromise: Promise<void> | undefined
export async function prefetchDeviceSignals({
agent,
}: {
agent: AtpAgent
}) {
const did = getDidFromAgentSession(agent)
if (!did) return
/**
* If we don't have a cache, it's possible the user hasn't granted access.
* We don't want to do this during the prefetch phase, so just exit early,
* the user can potentially enable it later.
*/
const cached = getDeviceSignalsFromCache({did})
if (!cached) return
if (deviceSignalsPrefetchPromise) {
logger.debug(`prefetchDeviceSignals: already in progress`)
return
}
deviceSignalsPrefetchPromise = new Promise(async resolve => {
await cacheHydrationPromise
const cached = getDeviceSignalsFromCache({did})
if (cached) {
logger.debug(`prefetchDeviceSignals: using cache`)
resolve()
} else {
try {
logger.debug(`prefetchDeviceSignals: resolving...`)
const res = await getDeviceSignals()
qc.setQueryData<AgeRange.AgeRangeResponse>(
createDeviceSignalsQueryKey({did}),
res,
)
} catch (e: any) {
logger.warn(`prefetchDeviceSignals: failed`, {
safeMessage: e.message,
})
} finally {
resolve()
}
}
})
}
export function useDeviceSignalsQuery() {
const agent = useAgent()
const did = getDidFromAgentSession(agent)
return useQuery(
{
enabled: !!did,
initialData: getDeviceSignalsFromCache({did: did!}),
queryKey: createDeviceSignalsQueryKey({did: did!}),
async queryFn() {
logger.debug(`useDeviceSignalsQuery: fetching device signals`)
return getDeviceSignals()
},
},
qc,
)
}
/**
* Helper to prefetch all age assurance data from the server.
*/
@@ -494,6 +588,7 @@ export function prefetchAgeAssuranceServerData({agent}: {agent: AtpAgent}) {
configPrefetchPromise,
prefetchServerState({agent}),
prefetchOtherRequiredData({agent}),
prefetchDeviceSignals({agent}),
])
}
+9 -4
View File
@@ -3,12 +3,13 @@ import {
type AppBskyAgeassuranceDefs,
type AppBskyAgeassuranceGetState,
} from '@atproto/api'
import * as AgeRange from 'expo-age-range'
import {type OtherRequiredData} from '#/ageAssurance/data'
import {IS_DEV, IS_E2E} from '#/env'
import {type Geolocation} from '#/geolocation'
export const enabled = (IS_DEV && false) || IS_E2E
export const enabled = (IS_DEV && true) || IS_E2E
export const geolocation: Geolocation | undefined = enabled
? {
@@ -35,9 +36,9 @@ export const serverState: AppBskyAgeassuranceGetState.OutputSchema | undefined =
serverStateEnabled
? {
state: {
lastInitiatedAt: new Date(2025, 1, 1).toISOString(),
status: 'assured',
access: 'full',
lastInitiatedAt: undefined, // new Date(2025, 1, 1).toISOString(),
status: 'unknown',
access: 'unknown',
},
metadata: {
accountCreatedAt: new Date(2023, 1, 1).toISOString(),
@@ -257,6 +258,10 @@ export const config: AppBskyAgeassuranceDefs.Config = {
],
}
const deviceSignalsEnabled = true
export const deviceSignals: AgeRange.AgeRangeResponse | undefined =
deviceSignalsEnabled ? {} : undefined
export async function resolve<T>(data: T) {
await new Promise(y => setTimeout(y, 500)) // simulate network
return data