about summary refs log tree commit diff
path: root/src/lib/notifications/notifications.ts
blob: 52f984a599237f2d2896e00e9138c2574138f178 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import React from 'react'
import * as Notifications from 'expo-notifications'
import {BskyAgent} from '@atproto/api'

import {logger} from '#/logger'
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'

async function registerPushToken(
  getAgent: () => BskyAgent,
  account: SessionAccount,
  token: Notifications.DevicePushToken,
) {
  try {
    await getAgent().api.app.bsky.notification.registerPush({
      serviceDid: SERVICE_DID(account.service),
      platform: devicePlatform,
      token: token.data,
      appId: 'xyz.blueskyweb.app',
    })
    logger.debug(
      'Notifications: Sent push token (init)',
      {
        tokenType: token.type,
        token: token.data,
      },
      logger.DebugContext.notifications,
    )
  } catch (error) {
    logger.error('Notifications: Failed to set push token', {message: error})
  }
}

async function getPushToken(skipPermissionCheck = false) {
  const granted =
    skipPermissionCheck || (await Notifications.getPermissionsAsync()).granted
  if (granted) {
    Notifications.getDevicePushTokenAsync()
  }
}

export function useNotificationsRegistration() {
  const {getAgent} = useAgent()
  const {currentAccount} = useSession()

  React.useEffect(() => {
    if (!currentAccount) {
      return
    }

    getPushToken()

    // 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, getAgent])
}

export function useRequestNotificationsPermission() {
  const gate = useGate()

  return React.useCallback(
    async (context: 'StartOnboarding' | 'AfterOnboarding' | 'Login') => {
      const permissions = await Notifications.getPermissionsAsync()

      if (
        !isNative ||
        permissions?.status === 'granted' ||
        (permissions?.status === 'denied' && !permissions?.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', {
        context: context,
        status: res.status,
      })

      if (res.granted) {
        // This will fire a pushTokenEvent, which will handle registration of the token
        getPushToken(true)
      }
    },
    [gate],
  )
}