Move request for notifications permissions to HomeReadyScreen (#3977)

* cleanup the current logic

* add statsig logs

* implement requests for permissions where needed

* oops

* let `addPushTokenListener` handle the token registration

* place new log event type with the other `notifications` type

* place registration next to handler

* more organization

* only call `gate()` if permission is not yet granted

* be more specific to prevent gate pollution

* nit

* make `token` non-optional in `registerToken`

* remove `prevDid`, move `registerPushToken` into `useEffect`

* keep it outside actually

* nit
This commit is contained in:
Hailey 2024-05-13 09:19:35 -07:00 committed by GitHub
parent 63b38b413d
commit d3406c89cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 105 additions and 67 deletions

View file

@ -1,27 +1,22 @@
import React from 'react'
import * as Notifications from 'expo-notifications'
import {BskyAgent} from '@atproto/api'
import {logger} from '#/logger'
import {SessionAccount} from '#/state/session'
import {devicePlatform} from 'platform/detection'
import {SessionAccount, useAgent, useSession} from '#/state/session'
import {logEvent, useGate} from 'lib/statsig/statsig'
import {devicePlatform, isNative} from 'platform/detection'
const SERVICE_DID = (serviceUrl?: string) =>
serviceUrl?.includes('staging')
? 'did:web:api.staging.bsky.dev'
: 'did:web:api.bsky.app'
export async function requestPermissionsAndRegisterToken(
async function registerPushToken(
getAgent: () => BskyAgent,
account: SessionAccount,
token: Notifications.DevicePushToken,
) {
// 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 Notifications.getDevicePushTokenAsync()
try {
await getAgent().api.app.bsky.notification.registerPush({
serviceDid: SERVICE_DID(account.service),
@ -42,38 +37,63 @@ export async function requestPermissionsAndRegisterToken(
}
}
export function registerTokenChangeHandler(
getAgent: () => BskyAgent,
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: Sent push token (event)',
{
tokenType: newToken.type,
token: newToken.data,
},
logger.DebugContext.notifications,
)
} catch (error) {
logger.error('Notifications: Failed to set push token', {message: error})
export function useNotificationsRegistration() {
const [currentPermissions] = Notifications.usePermissions()
const {getAgent} = useAgent()
const {currentAccount} = useSession()
React.useEffect(() => {
if (!currentAccount || !currentPermissions?.granted) {
return
}
})
return () => {
sub.remove()
}
// Whenever we all `getDevicePushTokenAsync()`, a change event will be fired below
Notifications.getDevicePushTokenAsync()
// 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(getAgent, currentAccount, newToken)
})
return () => {
subscription.remove()
}
}, [currentAccount, currentPermissions?.granted, getAgent])
}
export function useRequestNotificationsPermission() {
const gate = useGate()
const [currentPermissions] = Notifications.usePermissions()
return React.useCallback(
async (context: 'StartOnboarding' | 'AfterOnboarding') => {
if (
!isNative ||
currentPermissions?.status === 'granted' ||
(currentPermissions?.status === 'denied' &&
!currentPermissions?.canAskAgain)
) {
return
}
if (
context === 'StartOnboarding' &&
gate('request_notifications_permission_after_onboarding')
) {
return
}
if (
context === 'AfterOnboarding' &&
!gate('request_notifications_permission_after_onboarding')
) {
return
}
const res = await Notifications.requestPermissionsAsync()
logEvent('notifications:request', {
status: res.status,
})
},
[currentPermissions?.canAskAgain, currentPermissions?.status, gate],
)
}

View file

@ -16,6 +16,9 @@ export type LogEvents = {
logContext: 'SwitchAccount' | 'Settings' | 'Deactivated'
}
'notifications:openApp': {}
'notifications:request': {
status: 'granted' | 'denied' | 'undetermined'
}
'state:background': {
secondsActive: number
}

View file

@ -5,6 +5,7 @@ export type Gate =
| 'disable_poll_on_discover_v2'
| 'dms'
| 'reduced_onboarding_and_home_algo'
| 'request_notifications_permission_after_onboarding'
| 'show_follow_back_label_v2'
| 'start_session_with_following_v2'
| 'test_gate_1'