mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import * as Notifications from 'expo-notifications'
3import {getBadgeCountAsync, setBadgeCountAsync} from 'expo-notifications'
4import {BskyAgent} from '@atproto/api'
5
6import {logEvent} from '#/lib/statsig/statsig'
7import {logger} from '#/logger'
8import {devicePlatform, isAndroid, isNative} from '#/platform/detection'
9import {SessionAccount, useAgent, useSession} from '#/state/session'
10import BackgroundNotificationHandler from '../../../modules/expo-background-notification-handler'
11
12const SERVICE_DID = (serviceUrl?: string) =>
13 serviceUrl?.includes('staging')
14 ? 'did:web:api.staging.bsky.dev'
15 : 'did:web:api.bsky.app'
16
17async function registerPushToken(
18 agent: BskyAgent,
19 account: SessionAccount,
20 token: Notifications.DevicePushToken,
21) {
22 try {
23 await agent.api.app.bsky.notification.registerPush({
24 serviceDid: SERVICE_DID(account.service),
25 platform: devicePlatform,
26 token: token.data,
27 appId: 'xyz.blueskyweb.app',
28 })
29 logger.debug(
30 'Notifications: Sent push token (init)',
31 {
32 tokenType: token.type,
33 token: token.data,
34 },
35 logger.DebugContext.notifications,
36 )
37 } catch (error) {
38 logger.error('Notifications: Failed to set push token', {message: error})
39 }
40}
41
42async function getPushToken(skipPermissionCheck = false) {
43 const granted =
44 skipPermissionCheck || (await Notifications.getPermissionsAsync()).granted
45 if (granted) {
46 return Notifications.getDevicePushTokenAsync()
47 }
48}
49
50export function useNotificationsRegistration() {
51 const agent = useAgent()
52 const {currentAccount} = useSession()
53
54 React.useEffect(() => {
55 if (!currentAccount) {
56 return
57 }
58
59 // HACK - see https://github.com/bluesky-social/social-app/pull/4467
60 // An apparent regression in expo-notifications causes `addPushTokenListener` to not fire on Android whenever the
61 // token changes by calling `getPushToken()`. This is a workaround to ensure we register the token once it is
62 // generated on Android.
63 if (isAndroid) {
64 ;(async () => {
65 const token = await getPushToken()
66
67 // Token will be undefined if we don't have notifications permission
68 if (token) {
69 registerPushToken(agent, currentAccount, token)
70 }
71 })()
72 } else {
73 getPushToken()
74 }
75
76 // According to the Expo docs, there is a chance that the token will change while the app is open in some rare
77 // cases. This will fire `registerPushToken` whenever that happens.
78 const subscription = Notifications.addPushTokenListener(async newToken => {
79 registerPushToken(agent, currentAccount, newToken)
80 })
81
82 return () => {
83 subscription.remove()
84 }
85 }, [currentAccount, agent])
86}
87
88export function useRequestNotificationsPermission() {
89 const {currentAccount} = useSession()
90 const agent = useAgent()
91
92 return async (
93 context: 'StartOnboarding' | 'AfterOnboarding' | 'Login' | 'Home',
94 ) => {
95 const permissions = await Notifications.getPermissionsAsync()
96
97 if (
98 !isNative ||
99 permissions?.status === 'granted' ||
100 (permissions?.status === 'denied' && !permissions.canAskAgain)
101 ) {
102 return
103 }
104 if (context === 'AfterOnboarding') {
105 return
106 }
107 if (context === 'Home' && !currentAccount) {
108 return
109 }
110
111 const res = await Notifications.requestPermissionsAsync()
112 logEvent('notifications:request', {
113 context: context,
114 status: res.status,
115 })
116
117 if (res.granted) {
118 // This will fire a pushTokenEvent, which will handle registration of the token
119 const token = await getPushToken(true)
120
121 // Same hack as above. We cannot rely on the `addPushTokenListener` to fire on Android due to an Expo bug, so we
122 // will manually register it here. Note that this will occur only:
123 // 1. right after the user signs in, leading to no `currentAccount` account being available - this will be instead
124 // picked up from the useEffect above on `currentAccount` change
125 // 2. right after onboarding. In this case, we _need_ this registration, since `currentAccount` will not change
126 // and we need to ensure the token is registered right after permission is granted. `currentAccount` will already
127 // be available in this case, so the registration will succeed.
128 // We should remove this once expo-notifications (and possibly FCMv1) is fixed and the `addPushTokenListener` is
129 // working again. See https://github.com/expo/expo/issues/28656
130 if (isAndroid && currentAccount && token) {
131 registerPushToken(agent, currentAccount, token)
132 }
133 }
134 }
135}
136
137export async function decrementBadgeCount(by: number) {
138 if (!isNative) return
139
140 let count = await getBadgeCountAsync()
141 count -= by
142 if (count < 0) {
143 count = 0
144 }
145
146 await BackgroundNotificationHandler.setBadgeCountAsync(count)
147 await setBadgeCountAsync(count)
148}
149
150export async function resetBadgeCount() {
151 await BackgroundNotificationHandler.setBadgeCountAsync(0)
152 await setBadgeCountAsync(0)
153}