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 cleanupzio/stable
parent
7d158f82fb
commit
de38595a7a
|
@ -6,15 +6,16 @@ import {track} from 'lib/analytics/analytics'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {RQKEY as RQKEY_NOTIFS} from '#/state/queries/notifications/feed'
|
import {RQKEY as RQKEY_NOTIFS} from '#/state/queries/notifications/feed'
|
||||||
import {truncateAndInvalidate} from '#/state/queries/util'
|
import {truncateAndInvalidate} from '#/state/queries/util'
|
||||||
import {listenSessionLoaded} from '#/state/events'
|
import {SessionAccount, getAgent} from '#/state/session'
|
||||||
|
|
||||||
const SERVICE_DID = (serviceUrl?: string) =>
|
const SERVICE_DID = (serviceUrl?: string) =>
|
||||||
serviceUrl?.includes('staging')
|
serviceUrl?.includes('staging')
|
||||||
? 'did:web:api.staging.bsky.dev'
|
? 'did:web:api.staging.bsky.dev'
|
||||||
: 'did:web:api.bsky.app'
|
: 'did:web:api.bsky.app'
|
||||||
|
|
||||||
export function init(queryClient: QueryClient) {
|
export async function requestPermissionsAndRegisterToken(
|
||||||
listenSessionLoaded(async (account, agent) => {
|
account: SessionAccount,
|
||||||
|
) {
|
||||||
// request notifications permission once the user has logged in
|
// request notifications permission once the user has logged in
|
||||||
const perms = await Notifications.getPermissionsAsync()
|
const perms = await Notifications.getPermissionsAsync()
|
||||||
if (!perms.granted) {
|
if (!perms.granted) {
|
||||||
|
@ -22,10 +23,9 @@ export function init(queryClient: QueryClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// register the push token with the server
|
// register the push token with the server
|
||||||
const token = await getPushToken()
|
const token = await Notifications.getDevicePushTokenAsync()
|
||||||
if (token) {
|
|
||||||
try {
|
try {
|
||||||
await agent.api.app.bsky.notification.registerPush({
|
await getAgent().api.app.bsky.notification.registerPush({
|
||||||
serviceDid: SERVICE_DID(account.service),
|
serviceDid: SERVICE_DID(account.service),
|
||||||
platform: devicePlatform,
|
platform: devicePlatform,
|
||||||
token: token.data,
|
token: token.data,
|
||||||
|
@ -42,39 +42,44 @@ export function init(queryClient: QueryClient) {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Notifications: Failed to set push token', {error})
|
logger.error('Notifications: Failed to set push token', {error})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function registerTokenChangeHandler(
|
||||||
|
account: SessionAccount,
|
||||||
|
): () => void {
|
||||||
// listens for new changes to the push token
|
// 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.
|
// 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}) => {
|
const sub = Notifications.addPushTokenListener(async newToken => {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'Notifications: Push token changed',
|
'Notifications: Push token changed',
|
||||||
{t, tokenType: type},
|
{tokenType: newToken.data, token: newToken.type},
|
||||||
logger.DebugContext.notifications,
|
logger.DebugContext.notifications,
|
||||||
)
|
)
|
||||||
if (t) {
|
|
||||||
try {
|
try {
|
||||||
await agent.api.app.bsky.notification.registerPush({
|
await getAgent().api.app.bsky.notification.registerPush({
|
||||||
serviceDid: SERVICE_DID(account.service),
|
serviceDid: SERVICE_DID(account.service),
|
||||||
platform: devicePlatform,
|
platform: devicePlatform,
|
||||||
token: t,
|
token: newToken.data,
|
||||||
appId: 'xyz.blueskyweb.app',
|
appId: 'xyz.blueskyweb.app',
|
||||||
})
|
})
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'Notifications: Sent push token (event)',
|
'Notifications: Sent push token (event)',
|
||||||
{
|
{
|
||||||
tokenType: type,
|
tokenType: newToken.type,
|
||||||
token: t,
|
token: newToken.data,
|
||||||
},
|
},
|
||||||
logger.DebugContext.notifications,
|
logger.DebugContext.notifications,
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Notifications: Failed to set push token', {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
|
// handle notifications that are received, both in the foreground or background
|
||||||
Notifications.addNotificationReceivedListener(event => {
|
Notifications.addNotificationReceivedListener(event => {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
@ -104,8 +109,7 @@ export function init(queryClient: QueryClient) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// handle notifications that are tapped on
|
// handle notifications that are tapped on
|
||||||
const sub = Notifications.addNotificationResponseReceivedListener(
|
Notifications.addNotificationResponseReceivedListener(response => {
|
||||||
response => {
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'Notifications: response received',
|
'Notifications: response received',
|
||||||
{
|
{
|
||||||
|
@ -113,9 +117,7 @@ export function init(queryClient: QueryClient) {
|
||||||
},
|
},
|
||||||
logger.DebugContext.notifications,
|
logger.DebugContext.notifications,
|
||||||
)
|
)
|
||||||
if (
|
if (response.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER) {
|
||||||
response.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER
|
|
||||||
) {
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'User pressed a notification, opening notifications tab',
|
'User pressed a notification, opening notifications tab',
|
||||||
{},
|
{},
|
||||||
|
@ -125,14 +127,5 @@ export function init(queryClient: QueryClient) {
|
||||||
truncateAndInvalidate(queryClient, RQKEY_NOTIFS())
|
truncateAndInvalidate(queryClient, RQKEY_NOTIFS())
|
||||||
resetToTab('NotificationsTab') // open notifications tab
|
resetToTab('NotificationsTab') // open notifications tab
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
sub.remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPushToken() {
|
|
||||||
return Notifications.getDevicePushTokenAsync()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import {
|
||||||
import {isAndroid} from 'platform/detection'
|
import {isAndroid} from 'platform/detection'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {useCloseAnyActiveElement} from '#/state/util'
|
import {useCloseAnyActiveElement} from '#/state/util'
|
||||||
|
import * as notifications from 'lib/notifications/notifications'
|
||||||
|
|
||||||
function ShellInner() {
|
function ShellInner() {
|
||||||
const isDrawerOpen = useIsDrawerOpen()
|
const isDrawerOpen = useIsDrawerOpen()
|
||||||
|
@ -52,7 +53,7 @@ function ShellInner() {
|
||||||
[setIsDrawerOpen],
|
[setIsDrawerOpen],
|
||||||
)
|
)
|
||||||
const canGoBack = useNavigationState(state => !isStateAtTabRoot(state))
|
const canGoBack = useNavigationState(state => !isStateAtTabRoot(state))
|
||||||
const {hasSession} = useSession()
|
const {hasSession, currentAccount} = useSession()
|
||||||
const closeAnyActiveElement = useCloseAnyActiveElement()
|
const closeAnyActiveElement = useCloseAnyActiveElement()
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
@ -67,6 +68,19 @@ function ShellInner() {
|
||||||
}
|
}
|
||||||
}, [closeAnyActiveElement])
|
}, [closeAnyActiveElement])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (currentAccount) {
|
||||||
|
notifications.requestPermissionsAndRegisterToken(currentAccount)
|
||||||
|
}
|
||||||
|
}, [currentAccount])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (currentAccount) {
|
||||||
|
const unsub = notifications.registerTokenChangeHandler(currentAccount)
|
||||||
|
return unsub
|
||||||
|
}
|
||||||
|
}, [currentAccount])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View style={containerPadding}>
|
<View style={containerPadding}>
|
||||||
|
|
Loading…
Reference in New Issue