Use hooks

This commit is contained in:
Eric Bailey
2025-06-24 19:21:58 -05:00
parent dba9f59a70
commit eeacf049a2
+62 -31
View File
@@ -1,4 +1,4 @@
import React from 'react' import {useCallback, useEffect} from 'react'
import {Platform} from 'react-native' import {Platform} from 'react-native'
import * as Notifications from 'expo-notifications' import * as Notifications from 'expo-notifications'
import {getBadgeCountAsync, setBadgeCountAsync} from 'expo-notifications' import {getBadgeCountAsync, setBadgeCountAsync} from 'expo-notifications'
@@ -13,16 +13,21 @@ import BackgroundNotificationHandler from '../../../modules/expo-background-noti
const logger = Logger.create(Logger.Context.Notifications) const logger = Logger.create(Logger.Context.Notifications)
/** /**
* @private
* Registers the device's push notification token with the Bluesky server. * Registers the device's push notification token with the Bluesky server.
*/ */
async function _registerPushToken( async function _registerPushToken({
agent: AtpAgent, agent,
account: SessionAccount, currentAccount,
token: Notifications.DevicePushToken, token,
) { }: {
agent: AtpAgent
currentAccount: SessionAccount
token: Notifications.DevicePushToken
}) {
try { try {
await agent.app.bsky.notification.registerPush({ await agent.app.bsky.notification.registerPush({
serviceDid: account.service?.includes('staging') serviceDid: currentAccount.service?.includes('staging')
? 'did:web:api.staging.bsky.dev' ? 'did:web:api.staging.bsky.dev'
: 'did:web:api.bsky.app', : 'did:web:api.bsky.app',
platform: Platform.select({ platform: Platform.select({
@@ -44,10 +49,34 @@ async function _registerPushToken(
} }
/** /**
* Debounced version of `_registerPushToken` to prevent multiple calls. Use this * @private
* instead of using `_registerPushToken` directly. * Debounced version of `_registerPushToken` to prevent multiple calls.
*/ */
const registerPushToken = debounce(_registerPushToken, 100) const _registerPushTokenDebounced = debounce(_registerPushToken, 100)
/**
* Hook to register the device's push notification token with the Bluesky. If
* the user is not logged in, this will do nothing.
*
* Use this instead of using `_registerPushToken` or
* `_registerPushTokenDebounced` directly.
*/
export function useRegisterPushToken() {
const agent = useAgent()
const {currentAccount} = useSession()
return useCallback(
({token}: {token: Notifications.DevicePushToken}) => {
if (!currentAccount) return
return _registerPushTokenDebounced({
agent,
currentAccount,
token,
})
},
[agent, currentAccount],
)
}
/** /**
* Retreive the device's push notification token, if permissions are granted. * Retreive the device's push notification token, if permissions are granted.
@@ -61,7 +90,7 @@ async function getPushToken() {
} }
/** /**
* Gets the device push token and registers it with the Bluesky server. * Hook to get the device push token and register it with the Bluesky server.
* *
* N.B. A previous regression in `expo-notifications` caused * N.B. A previous regression in `expo-notifications` caused
* `addPushTokenListener` to not fire on Android after calling * `addPushTokenListener` to not fire on Android after calling
@@ -69,37 +98,35 @@ async function getPushToken() {
* `registerPushToken` here. * `registerPushToken` here.
* *
* Because `registerPushToken` is debounced, we can safely call it on every * Because `registerPushToken` is debounced, we can safely call it on every
* platform and only a single call will be made to the server. * platform and only a single call will be made to the server. This does race
* the listener (if 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/bluesky-social/social-app/pull/4467
* @see https://github.com/expo/expo/issues/28656 * @see https://github.com/expo/expo/issues/28656
* @see https://github.com/expo/expo/issues/29909 * @see https://github.com/expo/expo/issues/29909
*/ */
export async function getAndRegisterPushToken( export function useGetAndRegisterPushToken() {
agent: AtpAgent, const registerPushToken = useRegisterPushToken()
currentAccount: SessionAccount, return useCallback(async () => {
) {
try {
/** /**
* This will also fire the listener added via `addPushTokenListener`. That * This will also fire the listener added via `addPushTokenListener`. That
* listener also handles registration. * listener also handles registration.
*/ */
const token = await getPushToken() const token = await getPushToken()
logger.debug(`getAndRegisterPushToken`, {token: token ?? 'undefined'}) logger.debug(`useGetAndRegisterPushToken`, {token: token ?? 'undefined'})
if (token) { if (token) {
/** /**
* The listener should have registered the token already, but just in * The listener should have registered the token already, but just in
* case, call the debounced function again. * case, call the debounced function again.
*/ */
registerPushToken(agent, currentAccount, token) registerPushToken({token})
} }
return token return token
} catch (e: any) { }, [registerPushToken])
logger.error(`getPushToken: failed`, {safeMessage: e.message})
}
} }
/** /**
@@ -110,10 +137,14 @@ export async function getAndRegisterPushToken(
* have a current account, this handling will be registered and ready to go. * have a current account, this handling will be registered and ready to go.
*/ */
export function useNotificationsRegistration() { export function useNotificationsRegistration() {
const agent = useAgent()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const registerPushToken = useRegisterPushToken()
const getAndRegisterPushToken = useGetAndRegisterPushToken()
React.useEffect(() => { useEffect(() => {
/**
* We want this to init right away _after_ we have a logged in user.
*/
if (!currentAccount) return if (!currentAccount) return
logger.debug(`useNotificationsRegistration`) logger.debug(`useNotificationsRegistration`)
@@ -123,7 +154,7 @@ export function useNotificationsRegistration() {
* they'll be requested by the `useRequestNotificationsPermission` hook * they'll be requested by the `useRequestNotificationsPermission` hook
* below. * below.
*/ */
getAndRegisterPushToken(agent, currentAccount) getAndRegisterPushToken()
/** /**
* Register the push token with the Bluesky server, whenever it changes. * Register the push token with the Bluesky server, whenever it changes.
@@ -135,20 +166,20 @@ export function useNotificationsRegistration() {
* *
* @see https://docs.expo.dev/versions/latest/sdk/notifications/#addpushtokenlistenerlistener * @see https://docs.expo.dev/versions/latest/sdk/notifications/#addpushtokenlistenerlistener
*/ */
const subscription = Notifications.addPushTokenListener(async newToken => { const subscription = Notifications.addPushTokenListener(async token => {
registerPushToken(agent, currentAccount, newToken) registerPushToken({token})
logger.debug(`addPushTokenListener callback`, {newToken}) logger.debug(`addPushTokenListener callback`, {token})
}) })
return () => { return () => {
subscription.remove() subscription.remove()
} }
}, [currentAccount, agent]) }, [currentAccount, getAndRegisterPushToken, registerPushToken])
} }
export function useRequestNotificationsPermission() { export function useRequestNotificationsPermission() {
const {currentAccount} = useSession() const {currentAccount} = useSession()
const agent = useAgent() const getAndRegisterPushToken = useGetAndRegisterPushToken()
return async ( return async (
context: 'StartOnboarding' | 'AfterOnboarding' | 'Login' | 'Home', context: 'StartOnboarding' | 'AfterOnboarding' | 'Login' | 'Home',
@@ -182,7 +213,7 @@ export function useRequestNotificationsPermission() {
* If we have an account in scope, we can safely call * If we have an account in scope, we can safely call
* `getAndRegisterPushToken`. * `getAndRegisterPushToken`.
*/ */
getAndRegisterPushToken(agent, currentAccount) getAndRegisterPushToken()
} else { } else {
/** /**
* Right after login, `currentAccount` in this scope will be undefined, * Right after login, `currentAccount` in this scope will be undefined,