Push notification & session management cleanup (#92)

* Add some temporary logging to help suss out the session drop issue

* Fix to session resumption: copy session tokens during a resumeSession attempt

* Factor out notifee display into a lib and add to storybook

* Tune the bg notifications fetch to only get what is needed

* Fix: run account update inside a mobx action

* Remove debugging logs for sessions

* Fixes to bg notifications fetch
This commit is contained in:
Paul Frazee 2023-01-25 11:31:09 -06:00 committed by GitHub
parent 079e1dbe18
commit 5f18931915
7 changed files with 148 additions and 66 deletions

View file

@ -4,6 +4,7 @@ import {RootStoreModel} from './root-store'
import {FeedModel} from './feed-view'
import {NotificationsViewModel} from './notifications-view'
import {isObj, hasProp} from '../lib/type-guards'
import {displayNotificationFromModel} from '../../view/lib/notifee'
export class MeModel {
did: string = ''
@ -125,19 +126,30 @@ export class MeModel {
this.notificationCount = res.data.count
notifee.setBadgeCount(this.notificationCount)
if (newNotifications) {
// trigger pre-emptive fetch on new notifications
let oldMostRecent = this.notifications.mostRecentNotification
this.notifications.refresh().then(() => {
// if a new most recent notification is found, trigger a notification card
const mostRecent = this.notifications.mostRecentNotification
if (mostRecent && oldMostRecent?.uri !== mostRecent?.uri) {
const notifeeOpts = mostRecent.toNotifeeOpts()
if (notifeeOpts) {
notifee.displayNotification(notifeeOpts)
}
}
})
this.notifications.refresh()
}
})
}
async bgFetchNotifications() {
const res = await this.rootStore.api.app.bsky.notification.getCount()
// NOTE we don't update this.notificationCount to avoid repaints during bg
// this means `newNotifications` may not be accurate, so we rely on
// `mostRecent` to determine if there really is a new notif to show -prf
const newNotifications = this.notificationCount !== res.data.count
notifee.setBadgeCount(res.data.count)
this.rootStore.log.debug(
`Background fetch received unread count = ${res.data.count}`,
)
if (newNotifications) {
this.rootStore.log.debug(
'Background fetch detected potentially a new notification',
)
const mostRecent = await this.notifications.getNewMostRecent()
if (mostRecent) {
this.rootStore.log.debug('Got the notification, triggering a push')
displayNotificationFromModel(mostRecent)
}
}
}
}

View file

@ -7,7 +7,6 @@ import {
AppBskyFeedVote,
AppBskyGraphAssertion,
AppBskyGraphFollow,
AppBskyEmbedImages,
} from '@atproto/api'
import {RootStoreModel} from './root-store'
import {PostThreadViewModel} from './post-thread-view'
@ -180,42 +179,6 @@ export class NotificationsViewItemModel {
})
}
}
toNotifeeOpts() {
let author = this.author.displayName || this.author.handle
let title: string
let body: string = ''
if (this.isUpvote) {
title = `${author} liked your post`
body = this.additionalPost?.thread?.postRecord?.text || ''
} else if (this.isRepost) {
title = `${author} reposted your post`
body = this.additionalPost?.thread?.postRecord?.text || ''
} else if (this.isReply) {
title = `${author} replied to your post`
body = this.additionalPost?.thread?.postRecord?.text || ''
} else if (this.isFollow) {
title = `${author} followed you`
} else {
return undefined
}
let ios
if (
AppBskyEmbedImages.isPresented(this.additionalPost?.thread?.post.embed) &&
this.additionalPost?.thread?.post.embed.images[0]?.thumb
) {
ios = {
attachments: [
{url: this.additionalPost.thread.post.embed.images[0].thumb},
],
}
}
return {
title,
body,
ios,
}
}
}
export class NotificationsViewModel {
@ -234,7 +197,7 @@ export class NotificationsViewModel {
// data
notifications: NotificationsViewItemModel[] = []
// this is used to trigger push notifications
// this is used to help trigger push notifications
mostRecentNotification: NotificationsViewItemModel | undefined
constructor(
@ -246,6 +209,7 @@ export class NotificationsViewModel {
{
rootStore: false,
params: false,
mostRecentNotification: false,
_loadPromise: false,
_loadMorePromise: false,
_updatePromise: false,
@ -333,6 +297,24 @@ export class NotificationsViewModel {
}
}
async getNewMostRecent(): Promise<NotificationsViewItemModel | undefined> {
let old = this.mostRecentNotification
const res = await this.rootStore.api.app.bsky.notification.list({limit: 1})
if (
!res.data.notifications[0] ||
old?.uri === res.data.notifications[0].uri
) {
return
}
this.mostRecentNotification = new NotificationsViewItemModel(
this.rootStore,
'mostRecent',
res.data.notifications[0],
)
await this.mostRecentNotification.fetchAdditionalData()
return this.mostRecentNotification
}
// state transitions
// =
@ -434,9 +416,6 @@ export class NotificationsViewModel {
'mostRecent',
res.data.notifications[0],
)
await this.mostRecentNotification.fetchAdditionalData()
} else {
this.mostRecentNotification = undefined
}
return this._appendAll(res, true)
}

View file

@ -136,8 +136,7 @@ export class RootStoreModel {
async onBgFetch(taskId: string) {
this.log.debug(`Background fetch fired for task ${taskId}`)
if (this.session.hasSession) {
// grab notifications
await this.me.fetchNotifications()
await this.me.bgFetchNotifications()
}
BackgroundFetch.finish(taskId)
}

View file

@ -1,4 +1,4 @@
import {makeAutoObservable} from 'mobx'
import {makeAutoObservable, runInAction} from 'mobx'
import {
sessionClient as AtpApi,
Session,
@ -298,9 +298,19 @@ export class SessionModel {
})
try {
const sess = await api.com.atproto.session.get()
if (!sess.success || sess.data.did !== account.did) {
if (
!sess.success ||
sess.data.did !== account.did ||
!api.sessionManager.session
) {
return false
}
// copy over the access tokens, as they may have refreshed during the .get() above
runInAction(() => {
account.refreshJwt = api.sessionManager.session?.refreshJwt
account.accessJwt = api.sessionManager.session?.accessJwt
})
} catch (_e) {
return false
}