WIP
This commit is contained in:
@@ -126,6 +126,7 @@ module.exports = function (_config) {
|
|||||||
'com.apple.security.application-groups': 'group.app.bsky',
|
'com.apple.security.application-groups': 'group.app.bsky',
|
||||||
'com.apple.developer.usernotifications.communication': true,
|
'com.apple.developer.usernotifications.communication': true,
|
||||||
// 'com.apple.developer.device-information.user-assigned-device-name': true,
|
// 'com.apple.developer.device-information.user-assigned-device-name': true,
|
||||||
|
'com.apple.developer.declared-age-range': true,
|
||||||
},
|
},
|
||||||
privacyManifests: {
|
privacyManifests: {
|
||||||
NSPrivacyCollectedDataTypes: [
|
NSPrivacyCollectedDataTypes: [
|
||||||
|
|||||||
@@ -157,6 +157,7 @@
|
|||||||
"emoji-regex": "^10.4.0",
|
"emoji-regex": "^10.4.0",
|
||||||
"eventemitter3": "^5.0.1",
|
"eventemitter3": "^5.0.1",
|
||||||
"expo": "54.0.34",
|
"expo": "54.0.34",
|
||||||
|
"expo-age-range": "^0.2.0",
|
||||||
"expo-application": "~7.0.8",
|
"expo-application": "~7.0.8",
|
||||||
"expo-blur": "~15.0.8",
|
"expo-blur": "~15.0.8",
|
||||||
"expo-build-properties": "~1.0.10",
|
"expo-build-properties": "~1.0.10",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
|||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
|
import * as AgeRange from 'expo-age-range'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SupportCode,
|
SupportCode,
|
||||||
@@ -348,7 +349,18 @@ function AccessSection() {
|
|||||||
label={_(msg`Verify now`)}
|
label={_(msg`Verify now`)}
|
||||||
size="large"
|
size="large"
|
||||||
color={hasInitiated ? 'secondary' : 'primary'}
|
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()
|
control.open()
|
||||||
ax.metric('ageAssurance:initDialogOpen', {
|
ax.metric('ageAssurance:initDialogOpen', {
|
||||||
hasInitiatedPreviously: hasInitiated,
|
hasInitiatedPreviously: hasInitiated,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persist
|
|||||||
import {focusManager, QueryClient, useQuery} from '@tanstack/react-query'
|
import {focusManager, QueryClient, useQuery} from '@tanstack/react-query'
|
||||||
import {persistQueryClient} from '@tanstack/react-query-persist-client'
|
import {persistQueryClient} from '@tanstack/react-query-persist-client'
|
||||||
import debounce from 'lodash.debounce'
|
import debounce from 'lodash.debounce'
|
||||||
|
import * as AgeRange from 'expo-age-range';
|
||||||
|
|
||||||
import {networkRetry} from '#/lib/async/retry'
|
import {networkRetry} from '#/lib/async/retry'
|
||||||
import {PUBLIC_BSKY_SERVICE} from '#/lib/constants'
|
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.
|
* Helper to prefetch all age assurance data from the server.
|
||||||
*/
|
*/
|
||||||
@@ -494,6 +588,7 @@ export function prefetchAgeAssuranceServerData({agent}: {agent: AtpAgent}) {
|
|||||||
configPrefetchPromise,
|
configPrefetchPromise,
|
||||||
prefetchServerState({agent}),
|
prefetchServerState({agent}),
|
||||||
prefetchOtherRequiredData({agent}),
|
prefetchOtherRequiredData({agent}),
|
||||||
|
prefetchDeviceSignals({agent}),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ import {
|
|||||||
type AppBskyAgeassuranceDefs,
|
type AppBskyAgeassuranceDefs,
|
||||||
type AppBskyAgeassuranceGetState,
|
type AppBskyAgeassuranceGetState,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import * as AgeRange from 'expo-age-range'
|
||||||
|
|
||||||
import {type OtherRequiredData} from '#/ageAssurance/data'
|
import {type OtherRequiredData} from '#/ageAssurance/data'
|
||||||
import {IS_DEV, IS_E2E} from '#/env'
|
import {IS_DEV, IS_E2E} from '#/env'
|
||||||
import {type Geolocation} from '#/geolocation'
|
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
|
export const geolocation: Geolocation | undefined = enabled
|
||||||
? {
|
? {
|
||||||
@@ -35,9 +36,9 @@ export const serverState: AppBskyAgeassuranceGetState.OutputSchema | undefined =
|
|||||||
serverStateEnabled
|
serverStateEnabled
|
||||||
? {
|
? {
|
||||||
state: {
|
state: {
|
||||||
lastInitiatedAt: new Date(2025, 1, 1).toISOString(),
|
lastInitiatedAt: undefined, // new Date(2025, 1, 1).toISOString(),
|
||||||
status: 'assured',
|
status: 'unknown',
|
||||||
access: 'full',
|
access: 'unknown',
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
accountCreatedAt: new Date(2023, 1, 1).toISOString(),
|
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) {
|
export async function resolve<T>(data: T) {
|
||||||
await new Promise(y => setTimeout(y, 500)) // simulate network
|
await new Promise(y => setTimeout(y, 500)) // simulate network
|
||||||
return data
|
return data
|
||||||
|
|||||||
Reference in New Issue
Block a user