about summary refs log tree commit diff
path: root/src/lib/notifications
diff options
context:
space:
mode:
authorEric Bailey <git@esb.lol>2025-07-16 13:58:07 -0500
committerGitHub <noreply@github.com>2025-07-16 13:58:07 -0500
commit1dbc331314278cb7a42ded9b190dac7038ad9878 (patch)
treeb5d44e1ea75ea9d5343eec90425c8c7ac74df39f /src/lib/notifications
parent712c3ad4211e2e68d0cdbcc480967c63aeaa6c0e (diff)
downloadvoidsky-1dbc331314278cb7a42ded9b190dac7038ad9878.tar.zst
UI for age assurance compliance (#8652)
* Add geo prop

* Add prelim fetch

* Add geo debug

* Pass in assurance state to notifications registration

* Comments

* Bump git index

* Add some component utils, no design, gate chat

* Disable mod prefs buttons, does not yet edit mod prefs

* Add initial prompt component

* Refine logic for showing prompt

* Add send email dialog

* Hook up dialog to fake mutation

* Fix geo debug bug

* Move provider inside query provider

* Slightly better screen gater

* Ok decent fallback with isExempt

* Reorg

* Wrap prompt in new logic

* Override mod prefs

* Use real endpoints, optimistic state

* Add persistent card, add time-ago, warning to dialog

* Add comment

* No undefined query values

* Fix case in import

* Wait for AA to load before registering push

* Override prefs in all locations

* Small refactor of notifications registration

* Register push after aa state

* Add retries

* Update blocked screens UI

* Strengthen email validation

* Add intent dialog

* Do service auth for init

* Rug refreshJwt

* Update copy

* Some mobile styles, add dev mode option

* Fix links on native

* Clean up intent dialog on native

* Don't mutate existing session, only copy

* Handle email validation error from server

* Clarity is better

* Moar clear

* Fixes

* Tweaks

* Add country code

* Gate it

* Refresh state after redirect

* Re-check on window focus

* Remove todo

* Enable in dev

* Check for did match on redirect

* Add blocked state

* Add appeal dialog

* Copy tweaks

* Inset in blue well

* Nux the prompt

* Copy updates

* Refetch just in case

* Uppercase country code

* Align copy, add notice to chat screens

* Tweak copy

* Add test code

* Add debug code

* Refactor AccountCard

* Big refactor

* Delay post-feed queries instead

* Debug code

* Clean up state

* Reorg

* Clean up copy

* Comments

* Reorg

* UPdate URL

* Cleanup

* Remove todo

* Update debug code

* revert unneeded changes

* UPdate nux name

* Revert unneeded change

* Updaet storage schema

* Checkpoint: cleanup

* Checkpoint: almost there

* isLoaded -> isReady

* Rename useAgeAssurance

* isUnderage -> isDeclaredUnderage

* Decompose, add docblocks

* Refactor

* UPdate debug

* Apply suggestion from @surfdude29

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* Apply suggestion from @surfdude29

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* Apply suggestion from @surfdude29

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* Apply suggestion from @surfdude29

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* Apply suggestion from @surfdude29

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* Apply suggestion from @surfdude29

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* Drop including Bluesky

* Apply suggestion from @surfdude29

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* Apply suggestion from @surfdude29

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* Remove todo

* Gate debug

* Revert unneeded change

* Fail closed

* Comments

* Comment

* Comment

* fix prettier

* rm viewheader

* bump sdk

* prevent overlap in admonition

* add age assurance intent route

* Just meow

Co-authored-by: Samuel Newman <mozzius@protonmail.com>

* Nix callback

* Fix spelling of dismissible lol

* Don't compare translated string

* Better KWS link labels

* Hide DMs send options in menu

* Add button

* Fix order

* Use only supported languages

* Rm button

* best-effort language mapping

* improve typing

---------

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Diffstat (limited to 'src/lib/notifications')
-rw-r--r--src/lib/notifications/notifications.ts98
1 files changed, 68 insertions, 30 deletions
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() {