about summary refs log tree commit diff
path: root/src/lib/notifications
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/notifications')
-rw-r--r--src/lib/notifications/notifications.ts108
1 files changed, 64 insertions, 44 deletions
diff --git a/src/lib/notifications/notifications.ts b/src/lib/notifications/notifications.ts
index 38c18bf3f..66cedeaa6 100644
--- a/src/lib/notifications/notifications.ts
+++ b/src/lib/notifications/notifications.ts
@@ -1,27 +1,22 @@
+import React from 'react'
 import * as Notifications from 'expo-notifications'
 import {BskyAgent} from '@atproto/api'
 
 import {logger} from '#/logger'
-import {SessionAccount} from '#/state/session'
-import {devicePlatform} from 'platform/detection'
+import {SessionAccount, useAgent, useSession} from '#/state/session'
+import {logEvent, useGate} from 'lib/statsig/statsig'
+import {devicePlatform, isNative} from 'platform/detection'
 
 const SERVICE_DID = (serviceUrl?: string) =>
   serviceUrl?.includes('staging')
     ? 'did:web:api.staging.bsky.dev'
     : 'did:web:api.bsky.app'
 
-export async function requestPermissionsAndRegisterToken(
+async function registerPushToken(
   getAgent: () => BskyAgent,
   account: SessionAccount,
+  token: Notifications.DevicePushToken,
 ) {
-  // request notifications permission once the user has logged in
-  const perms = await Notifications.getPermissionsAsync()
-  if (!perms.granted) {
-    await Notifications.requestPermissionsAsync()
-  }
-
-  // register the push token with the server
-  const token = await Notifications.getDevicePushTokenAsync()
   try {
     await getAgent().api.app.bsky.notification.registerPush({
       serviceDid: SERVICE_DID(account.service),
@@ -42,38 +37,63 @@ export async function requestPermissionsAndRegisterToken(
   }
 }
 
-export function registerTokenChangeHandler(
-  getAgent: () => BskyAgent,
-  account: SessionAccount,
-): () => void {
-  // listens for new changes to the push token
-  // In rare situations, a push token may be changed by the push notification service while the app is running. When a token is rolled, the old one becomes invalid and sending notifications to it will fail. A push token listener will let you handle this situation gracefully by registering the new token with your backend right away.
-  const sub = Notifications.addPushTokenListener(async newToken => {
-    logger.debug(
-      'Notifications: Push token changed',
-      {tokenType: newToken.data, token: newToken.type},
-      logger.DebugContext.notifications,
-    )
-    try {
-      await getAgent().api.app.bsky.notification.registerPush({
-        serviceDid: SERVICE_DID(account.service),
-        platform: devicePlatform,
-        token: newToken.data,
-        appId: 'xyz.blueskyweb.app',
-      })
-      logger.debug(
-        'Notifications: Sent push token (event)',
-        {
-          tokenType: newToken.type,
-          token: newToken.data,
-        },
-        logger.DebugContext.notifications,
-      )
-    } catch (error) {
-      logger.error('Notifications: Failed to set push token', {message: error})
+export function useNotificationsRegistration() {
+  const [currentPermissions] = Notifications.usePermissions()
+  const {getAgent} = useAgent()
+  const {currentAccount} = useSession()
+
+  React.useEffect(() => {
+    if (!currentAccount || !currentPermissions?.granted) {
+      return
     }
-  })
-  return () => {
-    sub.remove()
-  }
+
+    // Whenever we all `getDevicePushTokenAsync()`, a change event will be fired below
+    Notifications.getDevicePushTokenAsync()
+
+    // According to the Expo docs, there is a chance that the token will change while the app is open in some rare
+    // cases. This will fire `registerPushToken` whenever that happens.
+    const subscription = Notifications.addPushTokenListener(async newToken => {
+      registerPushToken(getAgent, currentAccount, newToken)
+    })
+
+    return () => {
+      subscription.remove()
+    }
+  }, [currentAccount, currentPermissions?.granted, getAgent])
+}
+
+export function useRequestNotificationsPermission() {
+  const gate = useGate()
+  const [currentPermissions] = Notifications.usePermissions()
+
+  return React.useCallback(
+    async (context: 'StartOnboarding' | 'AfterOnboarding') => {
+      if (
+        !isNative ||
+        currentPermissions?.status === 'granted' ||
+        (currentPermissions?.status === 'denied' &&
+          !currentPermissions?.canAskAgain)
+      ) {
+        return
+      }
+      if (
+        context === 'StartOnboarding' &&
+        gate('request_notifications_permission_after_onboarding')
+      ) {
+        return
+      }
+      if (
+        context === 'AfterOnboarding' &&
+        !gate('request_notifications_permission_after_onboarding')
+      ) {
+        return
+      }
+
+      const res = await Notifications.requestPermissionsAsync()
+      logEvent('notifications:request', {
+        status: res.status,
+      })
+    },
+    [currentPermissions?.canAskAgain, currentPermissions?.status, gate],
+  )
 }