about summary refs log tree commit diff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/constants.ts3
-rw-r--r--src/lib/hooks/useIntentHandler.ts38
-rw-r--r--src/lib/hooks/useTLDs.ts15
-rw-r--r--src/lib/notifications/notifications.ts98
-rw-r--r--src/lib/statsig/gates.ts1
5 files changed, 122 insertions, 33 deletions
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index a21b92de5..3f0d49989 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -202,5 +202,8 @@ export const urls = {
   },
 }
 
+export const PUBLIC_APPVIEW = 'https://api.bsky.app'
 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
diff --git a/src/lib/hooks/useIntentHandler.ts b/src/lib/hooks/useIntentHandler.ts
index 4a5653750..6b1083aa4 100644
--- a/src/lib/hooks/useIntentHandler.ts
+++ b/src/lib/hooks/useIntentHandler.ts
@@ -6,10 +6,14 @@ import {logEvent} from '#/lib/statsig/statsig'
 import {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'
 
-type IntentType = 'compose' | 'verify-email'
+type IntentType = 'compose' | 'verify-email' | 'age-assurance'
 
 const VALID_IMAGE_REGEX = /^[\w.:\-_/]+\|\d+(\.\d+)?\|\d+(\.\d+)?$/
 
@@ -20,6 +24,9 @@ export function useIntentHandler() {
   const incomingUrl = Linking.useURL()
   const composeIntent = useComposeIntent()
   const verifyEmailIntent = useVerifyEmailIntent()
+  const ageAssuranceRedirectDialogControl =
+    useAgeAssuranceRedirectDialogControl()
+  const {currentAccount} = useSession()
 
   React.useEffect(() => {
     const handleIncomingURL = (url: string) => {
@@ -65,6 +72,26 @@ export function useIntentHandler() {
           verifyEmailIntent(code)
           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)
+          }
+          return
+        }
         default: {
           return
         }
@@ -78,7 +105,13 @@ export function useIntentHandler() {
       handleIncomingURL(incomingUrl)
       previousIntentUrl = incomingUrl
     }
-  }, [incomingUrl, composeIntent, verifyEmailIntent])
+  }, [
+    incomingUrl,
+    composeIntent,
+    verifyEmailIntent,
+    ageAssuranceRedirectDialogControl,
+    currentAccount,
+  ])
 }
 
 export function useComposeIntent() {
@@ -97,7 +130,6 @@ export function useComposeIntent() {
       videoUri: string | null
     }) => {
       if (!hasSession) return
-
       closeAllActiveElements()
 
       // Whenever a video URI is present, we don't support adding images right now.
diff --git a/src/lib/hooks/useTLDs.ts b/src/lib/hooks/useTLDs.ts
new file mode 100644
index 000000000..8ed872835
--- /dev/null
+++ b/src/lib/hooks/useTLDs.ts
@@ -0,0 +1,15 @@
+import {useEffect, useState} from 'react'
+import type tldts from 'tldts'
+
+export function useTLDs() {
+  const [tlds, setTlds] = useState<typeof tldts>()
+
+  useEffect(() => {
+    // @ts-expect-error - valid path
+    import('tldts/dist/index.cjs.min.js').then(tlds => {
+      setTlds(tlds)
+    })
+  }, [])
+
+  return tlds
+}
diff --git a/src/lib/notifications/notifications.ts b/src/lib/notifications/notifications.ts
index 94b3f6de3..0d2f9ed09 100644
--- a/src/lib/notifications/notifications.ts
+++ b/src/lib/notifications/notifications.ts
@@ -2,12 +2,13 @@ import {useCallback, useEffect} from 'react'
 import {Platform} from 'react-native'
 import * as Notifications from 'expo-notifications'
 import {getBadgeCountAsync, setBadgeCountAsync} from 'expo-notifications'
-import {type AtpAgent} from '@atproto/api'
+import {type AppBskyNotificationRegisterPush, type AtpAgent} from '@atproto/api'
 import debounce from 'lodash.debounce'
 
 import {PUBLIC_APPVIEW_DID, PUBLIC_STAGING_APPVIEW_DID} from '#/lib/constants'
 import {logger as notyLogger} from '#/lib/notifications/util'
 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'
 
@@ -19,25 +20,31 @@ async function _registerPushToken({
   agent,
   currentAccount,
   token,
+  extra = {},
 }: {
   agent: AtpAgent
   currentAccount: SessionAccount
   token: Notifications.DevicePushToken
+  extra?: {
+    ageRestricted?: boolean
+  }
 }) {
   try {
-    await agent.app.bsky.notification.registerPush({
+    const payload: AppBskyNotificationRegisterPush.InputSchema = {
       serviceDid: currentAccount.service?.includes('staging')
         ? PUBLIC_STAGING_APPVIEW_DID
         : PUBLIC_APPVIEW_DID,
       platform: Platform.OS,
       token: token.data,
       appId: 'xyz.blueskyweb.app',
-    })
+      ageRestricted: extra.ageRestricted ?? false,
+    }
 
-    notyLogger.debug(`registerPushToken: success`, {
-      tokenType: token.type,
-      token: token.data,
-    })
+    notyLogger.debug(`registerPushToken: registering`, {...payload})
+
+    await agent.app.bsky.notification.registerPush(payload)
+
+    notyLogger.debug(`registerPushToken: success`)
   } catch (error) {
     notyLogger.error(`registerPushToken: failed`, {safeMessage: error})
   }
@@ -61,12 +68,21 @@ export function useRegisterPushToken() {
   const {currentAccount} = useSession()
 
   return useCallback(
-    ({token}: {token: Notifications.DevicePushToken}) => {
+    ({
+      token,
+      isAgeRestricted,
+    }: {
+      token: Notifications.DevicePushToken
+      isAgeRestricted: boolean
+    }) => {
       if (!currentAccount) return
       return _registerPushTokenDebounced({
         agent,
         currentAccount,
         token,
+        extra: {
+          ageRestricted: isAgeRestricted,
+        },
       })
     },
     [agent, currentAccount],
@@ -100,33 +116,46 @@ async function getPushToken() {
  * it fires), so there's a possibility that multiple calls will be made, but
  * that is acceptable.
  *
- * @see https://github.com/bluesky-social/social-app/pull/4467
  * @see https://github.com/expo/expo/issues/28656
  * @see https://github.com/expo/expo/issues/29909
+ * @see https://github.com/bluesky-social/social-app/pull/4467
  */
 export function useGetAndRegisterPushToken() {
+  const {isAgeRestricted} = useAgeAssuranceContext()
   const registerPushToken = useRegisterPushToken()
-  return useCallback(async () => {
-    /**
-     * This will also fire the listener added via `addPushTokenListener`. That
-     * listener also handles registration.
-     */
-    const token = await getPushToken()
-
-    notyLogger.debug(`useGetAndRegisterPushToken`, {
-      token: token ?? 'undefined',
-    })
+  return useCallback(
+    async ({
+      isAgeRestricted: isAgeRestrictedOverride,
+    }: {
+      isAgeRestricted?: boolean
+    } = {}) => {
+      if (!isNative) return
 
-    if (token) {
       /**
-       * The listener should have registered the token already, but just in
-       * case, call the debounced function again.
+       * This will also fire the listener added via `addPushTokenListener`. That
+       * listener also handles registration.
        */
-      registerPushToken({token})
-    }
+      const token = await getPushToken()
 
-    return token
-  }, [registerPushToken])
+      notyLogger.debug(`useGetAndRegisterPushToken`, {
+        token: token ?? 'undefined',
+      })
+
+      if (token) {
+        /**
+         * The listener should have registered the token already, but just in
+         * case, call the debounced function again.
+         */
+        registerPushToken({
+          token,
+          isAgeRestricted: isAgeRestrictedOverride ?? isAgeRestricted,
+        })
+      }
+
+      return token
+    },
+    [registerPushToken, isAgeRestricted],
+  )
 }
 
 /**
@@ -140,12 +169,15 @@ export function useNotificationsRegistration() {
   const {currentAccount} = useSession()
   const registerPushToken = useRegisterPushToken()
   const getAndRegisterPushToken = useGetAndRegisterPushToken()
+  const {isReady: isAgeRestrictionReady, isAgeRestricted} =
+    useAgeAssuranceContext()
 
   useEffect(() => {
     /**
-     * We want this to init right away _after_ we have a logged in user.
+     * 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) return
+    if (!currentAccount || !isAgeRestrictionReady) return
 
     notyLogger.debug(`useNotificationsRegistration`)
 
@@ -167,14 +199,20 @@ export function useNotificationsRegistration() {
      * @see https://docs.expo.dev/versions/latest/sdk/notifications/#addpushtokenlistenerlistener
      */
     const subscription = Notifications.addPushTokenListener(async token => {
-      registerPushToken({token})
+      registerPushToken({token, isAgeRestricted: isAgeRestricted})
       notyLogger.debug(`addPushTokenListener callback`, {token})
     })
 
     return () => {
       subscription.remove()
     }
-  }, [currentAccount, getAndRegisterPushToken, registerPushToken])
+  }, [
+    currentAccount,
+    getAndRegisterPushToken,
+    registerPushToken,
+    isAgeRestrictionReady,
+    isAgeRestricted,
+  ])
 }
 
 export function useRequestNotificationsPermission() {
diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts
index 3b1106480..efd7d605a 100644
--- a/src/lib/statsig/gates.ts
+++ b/src/lib/statsig/gates.ts
@@ -1,5 +1,6 @@
 export type Gate =
   // Keep this alphabetic please.
+  | 'age_assurance'
   | 'alt_share_icon'
   | 'debug_show_feedcontext'
   | 'debug_subscriptions'