diff options
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/api/debug-appview-proxy-header.ts | 39 | ||||
-rw-r--r-- | src/lib/bg-scheduler.ts | 18 | ||||
-rw-r--r-- | src/lib/bg-scheduler.web.ts | 13 | ||||
-rw-r--r-- | src/lib/notifee.ts | 82 | ||||
-rw-r--r-- | src/lib/notifications/notifications.ts | 101 |
5 files changed, 121 insertions, 132 deletions
diff --git a/src/lib/api/debug-appview-proxy-header.ts b/src/lib/api/debug-appview-proxy-header.ts index 39890b7c3..7571bd37f 100644 --- a/src/lib/api/debug-appview-proxy-header.ts +++ b/src/lib/api/debug-appview-proxy-header.ts @@ -8,23 +8,30 @@ * version of the app. */ -import {useState, useCallback} from 'react' +import {useState, useCallback, useEffect} from 'react' import {BskyAgent} from '@atproto/api' import {isWeb} from 'platform/detection' +import * as Storage from 'lib/storage' export function useDebugHeaderSetting(agent: BskyAgent): [boolean, () => void] { - const [enabled, setEnabled] = useState<boolean>(isEnabled()) + const [enabled, setEnabled] = useState<boolean>(false) - const toggle = useCallback(() => { - if (!isWeb || typeof window === 'undefined') { - return + useEffect(() => { + async function check() { + if (await isEnabled()) { + setEnabled(true) + } } + check() + }, []) + + const toggle = useCallback(() => { if (!enabled) { - localStorage.setItem('set-header-x-appview-proxy', 'yes') + Storage.saveString('set-header-x-appview-proxy', 'yes') agent.api.xrpc.setHeader('x-appview-proxy', 'true') setEnabled(true) } else { - localStorage.removeItem('set-header-x-appview-proxy') + Storage.remove('set-header-x-appview-proxy') agent.api.xrpc.unsetHeader('x-appview-proxy') setEnabled(false) } @@ -34,30 +41,24 @@ export function useDebugHeaderSetting(agent: BskyAgent): [boolean, () => void] { } export function setDebugHeader(agent: BskyAgent, enabled: boolean) { - if (!isWeb || typeof window === 'undefined') { - return - } if (enabled) { - localStorage.setItem('set-header-x-appview-proxy', 'yes') + Storage.saveString('set-header-x-appview-proxy', 'yes') agent.api.xrpc.setHeader('x-appview-proxy', 'true') } else { - localStorage.removeItem('set-header-x-appview-proxy') + Storage.remove('set-header-x-appview-proxy') agent.api.xrpc.unsetHeader('x-appview-proxy') } } -export function applyDebugHeader(agent: BskyAgent) { +export async function applyDebugHeader(agent: BskyAgent) { if (!isWeb) { return } - if (isEnabled()) { + if (await isEnabled()) { agent.api.xrpc.setHeader('x-appview-proxy', 'true') } } -function isEnabled() { - if (!isWeb || typeof window === 'undefined') { - return false - } - return localStorage.getItem('set-header-x-appview-proxy') === 'yes' +async function isEnabled() { + return (await Storage.loadString('set-header-x-appview-proxy')) === 'yes' } diff --git a/src/lib/bg-scheduler.ts b/src/lib/bg-scheduler.ts deleted file mode 100644 index db3f2d7fd..000000000 --- a/src/lib/bg-scheduler.ts +++ /dev/null @@ -1,18 +0,0 @@ -import BackgroundFetch, { - BackgroundFetchStatus, -} from 'react-native-background-fetch' - -export function configure( - handler: (taskId: string) => Promise<void>, - timeoutHandler: (taskId: string) => void, -): Promise<BackgroundFetchStatus> { - return BackgroundFetch.configure( - {minimumFetchInterval: 15}, - handler, - timeoutHandler, - ) -} - -export function finish(taskId: string) { - return BackgroundFetch.finish(taskId) -} diff --git a/src/lib/bg-scheduler.web.ts b/src/lib/bg-scheduler.web.ts deleted file mode 100644 index 91ec9428f..000000000 --- a/src/lib/bg-scheduler.web.ts +++ /dev/null @@ -1,13 +0,0 @@ -type BackgroundFetchStatus = 0 | 1 | 2 - -export async function configure( - _handler: (taskId: string) => Promise<void>, - _timeoutHandler: (taskId: string) => Promise<void>, -): Promise<BackgroundFetchStatus> { - // TODO - return 0 -} - -export function finish(_taskId: string) { - // TODO -} diff --git a/src/lib/notifee.ts b/src/lib/notifee.ts deleted file mode 100644 index 485d79aed..000000000 --- a/src/lib/notifee.ts +++ /dev/null @@ -1,82 +0,0 @@ -import notifee, {EventType} from '@notifee/react-native' -import {AppBskyEmbedImages, AtUri} from '@atproto/api' -import {RootStoreModel} from 'state/models/root-store' -import {NotificationsFeedItemModel} from 'state/models/feeds/notifications' -import {enforceLen} from 'lib/strings/helpers' -import {sanitizeDisplayName} from './strings/display-names' -import {resetToTab} from '../Navigation' - -export function init(store: RootStoreModel) { - store.onUnreadNotifications(count => notifee.setBadgeCount(count)) - store.onPushNotification(displayNotificationFromModel) - store.onSessionLoaded(() => { - // request notifications permission once the user has logged in - notifee.requestPermission() - }) - notifee.onForegroundEvent(async ({type}: {type: EventType}) => { - store.log.debug('Notifee foreground event', {type}) - if (type === EventType.PRESS) { - store.log.debug('User pressed a notifee, opening notifications') - resetToTab('NotificationsTab') - } - }) - notifee.onBackgroundEvent(async _e => {}) // notifee requires this but we handle it with onForegroundEvent -} - -export function displayNotification( - title: string, - body?: string, - image?: string, -) { - const opts: {title: string; body?: string; ios?: any} = {title} - if (body) { - opts.body = enforceLen(body, 70, true) - } - if (image) { - opts.ios = { - attachments: [{url: image}], - } - } - return notifee.displayNotification(opts) -} - -export function displayNotificationFromModel( - notification: NotificationsFeedItemModel, -) { - let author = sanitizeDisplayName( - notification.author.displayName || notification.author.handle, - ) - let title: string - let body: string = '' - if (notification.isLike) { - title = `${author} liked your post` - body = notification.additionalPost?.thread?.postRecord?.text || '' - } else if (notification.isRepost) { - title = `${author} reposted your post` - body = notification.additionalPost?.thread?.postRecord?.text || '' - } else if (notification.isMention) { - title = `${author} mentioned you` - body = notification.additionalPost?.thread?.postRecord?.text || '' - } else if (notification.isReply) { - title = `${author} replied to your post` - body = notification.additionalPost?.thread?.postRecord?.text || '' - } else if (notification.isFollow) { - title = 'New follower!' - body = `${author} has followed you` - } else if (notification.isCustomFeedLike) { - title = `${author} liked your custom feed` - body = `${new AtUri(notification.subjectUri).rkey}` - } else { - return - } - let image - if ( - AppBskyEmbedImages.isView( - notification.additionalPost?.thread?.post.embed, - ) && - notification.additionalPost?.thread?.post.embed.images[0]?.thumb - ) { - image = notification.additionalPost.thread.post.embed.images[0].thumb - } - return displayNotification(title, body, image) -} diff --git a/src/lib/notifications/notifications.ts b/src/lib/notifications/notifications.ts new file mode 100644 index 000000000..b517b40bf --- /dev/null +++ b/src/lib/notifications/notifications.ts @@ -0,0 +1,101 @@ +import * as Notifications from 'expo-notifications' +import {RootStoreModel} from '../../state' +import {resetToTab} from '../../Navigation' +import {devicePlatform, isIOS} from 'platform/detection' + +// TODO prod did = did:web:api.bsky.app + +export function init(store: RootStoreModel) { + store.onUnreadNotifications(count => Notifications.setBadgeCountAsync(count)) + + store.onSessionLoaded(async () => { + // 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 store.agent.api.app.bsky.notification.registerPush({ + serviceDid: 'did:web:api.staging.bsky.dev', + platform: devicePlatform, + token: token.data, + appId: 'xyz.blueskyweb.app', + }) + store.log.debug('Notifications: Sent push token (init)', { + type: token.type, + token: token.data, + }) + } catch (error) { + store.log.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}) => { + store.log.debug('Notifications: Push token changed', {t, type}) + if (t) { + try { + await store.agent.api.app.bsky.notification.registerPush({ + serviceDid: 'did:web:api.staging.bsky.dev', + platform: devicePlatform, + token: t, + appId: 'xyz.blueskyweb.app', + }) + store.log.debug('Notifications: Sent push token (event)', { + type, + token: t, + }) + } catch (error) { + store.log.error('Notifications: Failed to set push token', error) + } + } + }) + }) + + // handle notifications that are tapped on, regardless of whether the app is in the foreground or background + Notifications.addNotificationReceivedListener(event => { + store.log.debug('Notifications: received', event) + if (event.request.trigger.type === 'push') { + let payload + if (isIOS) { + payload = event.request.trigger.payload + } else { + // TODO: handle android payload deeplink + } + if (payload) { + store.log.debug('Notifications: received payload', payload) + // TODO: deeplink notif here + } + } + }) + + const sub = Notifications.addNotificationResponseReceivedListener( + response => { + store.log.debug( + 'Notifications: response received', + response.actionIdentifier, + ) + if ( + response.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER + ) { + store.log.debug( + 'User pressed a notification, opening notifications tab', + ) + resetToTab('NotificationsTab') + } + }, + ) + + return () => { + sub.remove() + } +} + +export function getPushToken() { + return Notifications.getDevicePushTokenAsync() +} |