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>
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
import {describe, expect, it} from '@jest/globals'
|
||||
|
||||
import {parseLinkingUrl} from '../parseLinkingUrl'
|
||||
|
||||
describe('parseLinkingUrl', () => {
|
||||
it('should correctly parse bluesky:// URLs', () => {
|
||||
const url =
|
||||
'bluesky://intent/age-assurance?result=success&actorDid=did:example:123'
|
||||
const urlp = parseLinkingUrl(url)
|
||||
expect(urlp.protocol).toBe('bluesky:')
|
||||
expect(urlp.host).toBe('')
|
||||
expect(urlp.pathname).toBe('/intent/age-assurance')
|
||||
})
|
||||
|
||||
it('should correctly parse standard URLs', () => {
|
||||
const url =
|
||||
'https://bsky.app/intent/age-assurance?result=success&actorDid=did:example:123'
|
||||
const urlp = parseLinkingUrl(url)
|
||||
expect(urlp.protocol).toBe('https:')
|
||||
expect(urlp.host).toBe('bsky.app')
|
||||
expect(urlp.pathname).toBe('/intent/age-assurance')
|
||||
})
|
||||
})
|
||||
@@ -163,6 +163,7 @@ export async function resolveLink(
|
||||
const res = await agent.resolveHandle({
|
||||
handle: urip.host,
|
||||
})
|
||||
// @ts-expect-error TODO new-sdk-migration
|
||||
urip.host = res.data.did
|
||||
}
|
||||
const res = await agent.getPosts({
|
||||
|
||||
@@ -214,6 +214,7 @@ export const PUBLIC_APPVIEW_DID = 'did:web:api.bsky.app'
|
||||
export const PUBLIC_STAGING_APPVIEW_DID = 'did:web:api.staging.bsky.dev'
|
||||
|
||||
export const DEV_ENV_APPVIEW = `http://localhost:2584` // always the same
|
||||
export const DEV_ENV_APPVIEW_DID = `did:plc:dw4kbjf5mn7nhenabiqpkyh3` // always the same
|
||||
|
||||
// temp hack for e2e - esb
|
||||
export const BLUESKY_PROXY_HEADER = {
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import React from 'react'
|
||||
|
||||
import {deviceLocales} from '#/locale/deviceLocales'
|
||||
import {useGeolocationStatus} from '#/state/geolocation'
|
||||
import {useLanguagePrefs} from '#/state/preferences'
|
||||
import {useGeolocation} from '#/geolocation'
|
||||
|
||||
/**
|
||||
* From react-native-localize
|
||||
@@ -275,7 +275,7 @@ export const countryCodeToCurrency: Record<string, string> = {
|
||||
export function useFormatCurrency(
|
||||
options?: Parameters<typeof Intl.NumberFormat>[1],
|
||||
) {
|
||||
const {location: geolocation} = useGeolocationStatus()
|
||||
const geolocation = useGeolocation()
|
||||
const {appLanguage} = useLanguagePrefs()
|
||||
return React.useMemo(() => {
|
||||
const locale = deviceLocales.at(0)
|
||||
|
||||
@@ -36,7 +36,7 @@ export function useAccountSwitcher() {
|
||||
// So we change the URL ourselves. The navigator will pick it up on remount.
|
||||
history.pushState(null, '', '/')
|
||||
}
|
||||
await resumeSession(account)
|
||||
await resumeSession(account, true)
|
||||
logEvent('account:loggedIn', {logContext, withPassword: false})
|
||||
Toast.show(_(msg`Signed in as @${account.handle}`))
|
||||
} else {
|
||||
|
||||
@@ -9,6 +9,7 @@ export const ZENDESK_SUPPORT_URL =
|
||||
|
||||
export enum SupportCode {
|
||||
AA_DID = 'AA_DID',
|
||||
AA_BIRTHDATE = 'AA_BIRTHDATE',
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,14 +4,11 @@ import * as Linking from 'expo-linking'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {parseLinkingUrl} from '#/lib/parseLinkingUrl'
|
||||
import {logger} from '#/logger'
|
||||
import {isIOS, isNative} from '#/platform/detection'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useCloseAllActiveElements} from '#/state/util'
|
||||
import {
|
||||
parseAgeAssuranceRedirectDialogState,
|
||||
useAgeAssuranceRedirectDialogControl,
|
||||
} from '#/components/ageAssurance/AgeAssuranceRedirectDialog'
|
||||
import {useIntentDialogs} from '#/components/intents/IntentDialogs'
|
||||
import {Referrer} from '../../../modules/expo-bluesky-swiss-army'
|
||||
import {useApplyPullRequestOTAUpdate} from './useOTAUpdates'
|
||||
@@ -27,8 +24,6 @@ export function useIntentHandler() {
|
||||
const incomingUrl = Linking.useLinkingURL()
|
||||
const composeIntent = useComposeIntent()
|
||||
const verifyEmailIntent = useVerifyEmailIntent()
|
||||
const ageAssuranceRedirectDialogControl =
|
||||
useAgeAssuranceRedirectDialogControl()
|
||||
const {currentAccount} = useSession()
|
||||
const {tryApplyUpdate} = useApplyPullRequestOTAUpdate()
|
||||
|
||||
@@ -47,17 +42,8 @@ export function useIntentHandler() {
|
||||
hostname: referrerInfo?.hostname,
|
||||
})
|
||||
}
|
||||
|
||||
// We want to be able to support bluesky:// deeplinks. It's unnatural for someone to use a deeplink with three
|
||||
// slashes, like bluesky:///intent/follow. However, supporting just two slashes causes us to have to take care
|
||||
// of two cases when parsing the url. If we ensure there is a third slash, we can always ensure the first
|
||||
// path parameter is in pathname rather than in hostname.
|
||||
if (url.startsWith('bluesky://') && !url.startsWith('bluesky:///')) {
|
||||
url = url.replace('bluesky://', 'bluesky:///')
|
||||
}
|
||||
|
||||
const urlp = new URL(url)
|
||||
const [__, intent, intentType] = urlp.pathname.split('/')
|
||||
const urlp = parseLinkingUrl(url)
|
||||
const [, intent, intentType] = urlp.pathname.split('/')
|
||||
|
||||
// On native, our links look like bluesky://intent/SomeIntent, so we have to check the hostname for the
|
||||
// intent check. On web, we have to check the first part of the path since we have an actual hostname
|
||||
@@ -82,23 +68,7 @@ export function useIntentHandler() {
|
||||
return
|
||||
}
|
||||
case 'age-assurance': {
|
||||
const state = parseAgeAssuranceRedirectDialogState({
|
||||
result: params.get('result') ?? undefined,
|
||||
actorDid: params.get('actorDid') ?? undefined,
|
||||
})
|
||||
|
||||
/*
|
||||
* If we don't have an account or the account doesn't match, do
|
||||
* nothing. By the time the user switches to their other account, AA
|
||||
* state should be ready for them.
|
||||
*/
|
||||
if (
|
||||
state &&
|
||||
currentAccount &&
|
||||
state.actorDid === currentAccount.did
|
||||
) {
|
||||
ageAssuranceRedirectDialogControl.open(state)
|
||||
}
|
||||
// Handled in `#/ageAssurance/components/RedirectOverlay.tsx`
|
||||
return
|
||||
}
|
||||
case 'apply-ota': {
|
||||
@@ -127,7 +97,6 @@ export function useIntentHandler() {
|
||||
incomingUrl,
|
||||
composeIntent,
|
||||
verifyEmailIntent,
|
||||
ageAssuranceRedirectDialogControl,
|
||||
currentAccount,
|
||||
tryApplyUpdate,
|
||||
])
|
||||
|
||||
@@ -9,9 +9,9 @@ import {PUBLIC_APPVIEW_DID, PUBLIC_STAGING_APPVIEW_DID} from '#/lib/constants'
|
||||
import {logger as notyLogger} from '#/lib/notifications/util'
|
||||
import {isNetworkError} from '#/lib/strings/errors'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {useAgeAssuranceContext} from '#/state/ageAssurance'
|
||||
import {type SessionAccount, useAgent, useSession} from '#/state/session'
|
||||
import BackgroundNotificationHandler from '#/../modules/expo-background-notification-handler'
|
||||
import {useAgeAssurance} from '#/ageAssurance'
|
||||
import {IS_DEV} from '#/env'
|
||||
|
||||
/**
|
||||
@@ -125,7 +125,7 @@ async function getPushToken() {
|
||||
* @see https://github.com/bluesky-social/social-app/pull/4467
|
||||
*/
|
||||
export function useGetAndRegisterPushToken() {
|
||||
const {isAgeRestricted} = useAgeAssuranceContext()
|
||||
const aa = useAgeAssurance()
|
||||
const registerPushToken = useRegisterPushToken()
|
||||
return useCallback(
|
||||
async ({
|
||||
@@ -152,13 +152,14 @@ export function useGetAndRegisterPushToken() {
|
||||
*/
|
||||
registerPushToken({
|
||||
token,
|
||||
isAgeRestricted: isAgeRestrictedOverride ?? isAgeRestricted,
|
||||
isAgeRestricted:
|
||||
isAgeRestrictedOverride ?? aa.state.access !== aa.Access.Full,
|
||||
})
|
||||
}
|
||||
|
||||
return token
|
||||
},
|
||||
[registerPushToken, isAgeRestricted],
|
||||
[registerPushToken, aa],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -173,15 +174,14 @@ export function useNotificationsRegistration() {
|
||||
const {currentAccount} = useSession()
|
||||
const registerPushToken = useRegisterPushToken()
|
||||
const getAndRegisterPushToken = useGetAndRegisterPushToken()
|
||||
const {isReady: isAgeRestrictionReady, isAgeRestricted} =
|
||||
useAgeAssuranceContext()
|
||||
const aa = useAgeAssurance()
|
||||
|
||||
useEffect(() => {
|
||||
/**
|
||||
* We want this to init right away _after_ we have a logged in user, and
|
||||
* _after_ we've loaded their age assurance state.
|
||||
*/
|
||||
if (!currentAccount || !isAgeRestrictionReady) return
|
||||
if (!currentAccount) return
|
||||
|
||||
notyLogger.debug(`useNotificationsRegistration`)
|
||||
|
||||
@@ -206,20 +206,17 @@ export function useNotificationsRegistration() {
|
||||
* @see https://docs.expo.dev/versions/latest/sdk/notifications/#addpushtokenlistenerlistener
|
||||
*/
|
||||
const subscription = Notifications.addPushTokenListener(async token => {
|
||||
registerPushToken({token, isAgeRestricted: isAgeRestricted})
|
||||
registerPushToken({
|
||||
token,
|
||||
isAgeRestricted: aa.state.access !== aa.Access.Full,
|
||||
})
|
||||
notyLogger.debug(`addPushTokenListener callback`, {token})
|
||||
})
|
||||
|
||||
return () => {
|
||||
subscription.remove()
|
||||
}
|
||||
}, [
|
||||
currentAccount,
|
||||
getAndRegisterPushToken,
|
||||
registerPushToken,
|
||||
isAgeRestrictionReady,
|
||||
isAgeRestricted,
|
||||
])
|
||||
}, [currentAccount, getAndRegisterPushToken, registerPushToken, aa])
|
||||
}
|
||||
|
||||
export function useRequestNotificationsPermission() {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export function parseLinkingUrl(url: string): URL {
|
||||
/*
|
||||
* Hack: add a third slash to bluesky:// urls so that `URL.host` is empty and
|
||||
* `URL.pathname` has the full path.
|
||||
*/
|
||||
if (url.startsWith('bluesky://') && !url.startsWith('bluesky:///')) {
|
||||
url = url.replace('bluesky://', 'bluesky:///')
|
||||
}
|
||||
return new URL(url)
|
||||
}
|
||||
@@ -41,7 +41,8 @@ export function makeRecordUri(
|
||||
collection: string,
|
||||
rkey: string,
|
||||
) {
|
||||
const urip = new AtUri('at://host/')
|
||||
const urip = new AtUri('at://placeholder.placeholder/')
|
||||
// @ts-expect-error TODO new-sdk-migration
|
||||
urip.host = didOrName
|
||||
urip.collection = collection
|
||||
urip.rkey = rkey
|
||||
|
||||
Reference in New Issue
Block a user