Files
bsky-social-app/src/lib/notifications/notifications.ts
T
dan a22685c345 Sort imports (#6009)
* Mark import sort/order/style rules as error

* npm run lint -- --fix
2024-10-29 23:02:48 +00:00

154 lines
5.0 KiB
TypeScript

import React from 'react'
import * as Notifications from 'expo-notifications'
import {getBadgeCountAsync, setBadgeCountAsync} from 'expo-notifications'
import {BskyAgent} from '@atproto/api'
import {logEvent} from '#/lib/statsig/statsig'
import {logger} from '#/logger'
import {devicePlatform, isAndroid, isNative} from '#/platform/detection'
import {SessionAccount, useAgent, useSession} from '#/state/session'
import BackgroundNotificationHandler from '../../../modules/expo-background-notification-handler'
const SERVICE_DID = (serviceUrl?: string) =>
serviceUrl?.includes('staging')
? 'did:web:api.staging.bsky.dev'
: 'did:web:api.bsky.app'
async function registerPushToken(
agent: BskyAgent,
account: SessionAccount,
token: Notifications.DevicePushToken,
) {
try {
await agent.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) {
return Notifications.getDevicePushTokenAsync()
}
}
export function useNotificationsRegistration() {
const agent = useAgent()
const {currentAccount} = useSession()
React.useEffect(() => {
if (!currentAccount) {
return
}
// HACK - see https://github.com/bluesky-social/social-app/pull/4467
// An apparent regression in expo-notifications causes `addPushTokenListener` to not fire on Android whenever the
// token changes by calling `getPushToken()`. This is a workaround to ensure we register the token once it is
// generated on Android.
if (isAndroid) {
;(async () => {
const token = await getPushToken()
// Token will be undefined if we don't have notifications permission
if (token) {
registerPushToken(agent, currentAccount, token)
}
})()
} else {
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(agent, currentAccount, newToken)
})
return () => {
subscription.remove()
}
}, [currentAccount, agent])
}
export function useRequestNotificationsPermission() {
const {currentAccount} = useSession()
const agent = useAgent()
return async (
context: 'StartOnboarding' | 'AfterOnboarding' | 'Login' | 'Home',
) => {
const permissions = await Notifications.getPermissionsAsync()
if (
!isNative ||
permissions?.status === 'granted' ||
(permissions?.status === 'denied' && !permissions.canAskAgain)
) {
return
}
if (context === 'AfterOnboarding') {
return
}
if (context === 'Home' && !currentAccount) {
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
const token = await getPushToken(true)
// Same hack as above. We cannot rely on the `addPushTokenListener` to fire on Android due to an Expo bug, so we
// will manually register it here. Note that this will occur only:
// 1. right after the user signs in, leading to no `currentAccount` account being available - this will be instead
// picked up from the useEffect above on `currentAccount` change
// 2. right after onboarding. In this case, we _need_ this registration, since `currentAccount` will not change
// and we need to ensure the token is registered right after permission is granted. `currentAccount` will already
// be available in this case, so the registration will succeed.
// We should remove this once expo-notifications (and possibly FCMv1) is fixed and the `addPushTokenListener` is
// working again. See https://github.com/expo/expo/issues/28656
if (isAndroid && currentAccount && token) {
registerPushToken(agent, currentAccount, token)
}
}
}
}
export async function decrementBadgeCount(by: number) {
if (!isNative) return
let count = await getBadgeCountAsync()
count -= by
if (count < 0) {
count = 0
}
await BackgroundNotificationHandler.setBadgeCountAsync(count)
await setBadgeCountAsync(count)
}
export async function resetBadgeCount() {
await BackgroundNotificationHandler.setBadgeCountAsync(0)
await setBadgeCountAsync(0)
}