From de38595a7a04e3dfe67c5251472f1817152b8116 Mon Sep 17 00:00:00 2001 From: dan Date: Wed, 6 Dec 2023 18:05:59 +0000 Subject: [PATCH] Split notification init, add cleanup (#2102) * Inline never-imported export * Remove pretense at handling cleanup * Extract per-session listener to a function * Split notifications.init() from notifications.onSessionLoaded() * Tweak forked code to be more similar * Remove unnecessary guards * Split notifications.onSessionLoaded in two * Always use getAgent() * Remove dep on listenSessionLoaded, add cleanup --- src/lib/notifications/notifications.ts | 161 ++++++++++++------------- src/view/shell/index.tsx | 16 ++- 2 files changed, 92 insertions(+), 85 deletions(-) diff --git a/src/lib/notifications/notifications.ts b/src/lib/notifications/notifications.ts index 6e79e6b9..d115dc98 100644 --- a/src/lib/notifications/notifications.ts +++ b/src/lib/notifications/notifications.ts @@ -6,75 +6,80 @@ import {track} from 'lib/analytics/analytics' import {logger} from '#/logger' import {RQKEY as RQKEY_NOTIFS} from '#/state/queries/notifications/feed' import {truncateAndInvalidate} from '#/state/queries/util' -import {listenSessionLoaded} from '#/state/events' +import {SessionAccount, getAgent} from '#/state/session' const SERVICE_DID = (serviceUrl?: string) => serviceUrl?.includes('staging') ? 'did:web:api.staging.bsky.dev' : 'did:web:api.bsky.app' -export function init(queryClient: QueryClient) { - listenSessionLoaded(async (account, agent) => { - // request notifications permission once the user has logged in - const perms = await Notifications.getPermissionsAsync() - if (!perms.granted) { - await Notifications.requestPermissionsAsync() - } +export async function requestPermissionsAndRegisterToken( + account: SessionAccount, +) { + // 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 getPushToken() - if (token) { - 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', {error}) - } - } + // 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), + 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', {error}) + } +} - // 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. - Notifications.addPushTokenListener(async ({data: t, type}) => { +export function registerTokenChangeHandler( + 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: Push token changed', - {t, tokenType: type}, + 'Notifications: Sent push token (event)', + { + tokenType: newToken.type, + token: newToken.data, + }, logger.DebugContext.notifications, ) - if (t) { - try { - await agent.api.app.bsky.notification.registerPush({ - serviceDid: SERVICE_DID(account.service), - platform: devicePlatform, - token: t, - appId: 'xyz.blueskyweb.app', - }) - logger.debug( - 'Notifications: Sent push token (event)', - { - tokenType: type, - token: t, - }, - logger.DebugContext.notifications, - ) - } catch (error) { - logger.error('Notifications: Failed to set push token', {error}) - } - } - }) + } catch (error) { + logger.error('Notifications: Failed to set push token', {error}) + } }) + return () => { + sub.remove() + } +} +export function init(queryClient: QueryClient) { // handle notifications that are received, both in the foreground or background Notifications.addNotificationReceivedListener(event => { logger.debug( @@ -104,35 +109,23 @@ export function init(queryClient: QueryClient) { }) // handle notifications that are tapped on - const sub = Notifications.addNotificationResponseReceivedListener( - response => { + Notifications.addNotificationResponseReceivedListener(response => { + logger.debug( + 'Notifications: response received', + { + actionIdentifier: response.actionIdentifier, + }, + logger.DebugContext.notifications, + ) + if (response.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER) { logger.debug( - 'Notifications: response received', - { - actionIdentifier: response.actionIdentifier, - }, + 'User pressed a notification, opening notifications tab', + {}, logger.DebugContext.notifications, ) - if ( - response.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER - ) { - logger.debug( - 'User pressed a notification, opening notifications tab', - {}, - logger.DebugContext.notifications, - ) - track('Notificatons:OpenApp') - truncateAndInvalidate(queryClient, RQKEY_NOTIFS()) - resetToTab('NotificationsTab') // open notifications tab - } - }, - ) - - return () => { - sub.remove() - } -} - -export function getPushToken() { - return Notifications.getDevicePushTokenAsync() + track('Notificatons:OpenApp') + truncateAndInvalidate(queryClient, RQKEY_NOTIFS()) + resetToTab('NotificationsTab') // open notifications tab + } + }) } diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index 5562af9a..18042c7e 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -31,6 +31,7 @@ import { import {isAndroid} from 'platform/detection' import {useSession} from '#/state/session' import {useCloseAnyActiveElement} from '#/state/util' +import * as notifications from 'lib/notifications/notifications' function ShellInner() { const isDrawerOpen = useIsDrawerOpen() @@ -52,7 +53,7 @@ function ShellInner() { [setIsDrawerOpen], ) const canGoBack = useNavigationState(state => !isStateAtTabRoot(state)) - const {hasSession} = useSession() + const {hasSession, currentAccount} = useSession() const closeAnyActiveElement = useCloseAnyActiveElement() React.useEffect(() => { @@ -67,6 +68,19 @@ function ShellInner() { } }, [closeAnyActiveElement]) + React.useEffect(() => { + if (currentAccount) { + notifications.requestPermissionsAndRegisterToken(currentAccount) + } + }, [currentAccount]) + + React.useEffect(() => { + if (currentAccount) { + const unsub = notifications.registerTokenChangeHandler(currentAccount) + return unsub + } + }, [currentAccount]) + return ( <>