@@ -28,6 +28,7 @@ import app.bsky.notification.ListNotificationsResponse
2828import app.bsky.notification.Preference
2929import app.bsky.notification.PutPreferencesV2Request
3030import app.bsky.notification.UpdateSeenRequest
31+ import com.atproto.server.GetServiceAuthQueryParams
3132import com.tunjid.heron.data.InternalEndpoints
3233import com.tunjid.heron.data.core.models.Block
3334import com.tunjid.heron.data.core.models.Cursor
@@ -51,6 +52,7 @@ import com.tunjid.heron.data.core.models.shouldShowNotification
5152import com.tunjid.heron.data.core.models.value
5253import com.tunjid.heron.data.core.types.MutedThreadException
5354import com.tunjid.heron.data.core.types.NotificationFilteredOutException
55+ import com.tunjid.heron.data.core.types.PostUri
5456import com.tunjid.heron.data.core.types.ProfileId
5557import com.tunjid.heron.data.core.types.RecordUri
5658import com.tunjid.heron.data.core.types.RepostUri
@@ -86,13 +88,15 @@ import dev.zacsweers.metro.Inject
8688import io.ktor.client.HttpClient
8789import io.ktor.client.plugins.DefaultRequest
8890import io.ktor.client.plugins.HttpTimeout
91+ import io.ktor.client.request.bearerAuth
8992import io.ktor.client.request.post
9093import io.ktor.client.request.setBody
9194import io.ktor.http.ContentType
9295import io.ktor.http.contentType
9396import io.ktor.http.takeFrom
9497import kotlin.time.Clock
9598import kotlin.time.Duration.Companion.milliseconds
99+ import kotlin.time.Duration.Companion.minutes
96100import kotlin.time.Duration.Companion.seconds
97101import kotlin.time.Instant
98102import kotlinx.coroutines.CoroutineDispatcher
@@ -112,6 +116,8 @@ import kotlinx.coroutines.flow.map
112116import kotlinx.coroutines.flow.stateIn
113117import kotlinx.coroutines.plus
114118import kotlinx.serialization.Serializable
119+ import sh.christian.ozone.api.Did
120+ import sh.christian.ozone.api.Nsid
115121import sh.christian.ozone.api.response.AtpResponse
116122
117123@Serializable
@@ -120,6 +126,7 @@ data class NotificationsQuery(
120126) : CursorQuery {
121127 data class Push (
122128 val senderId : ProfileId ,
129+ val targetDid : ProfileId ,
123130 val recordUri : RecordUri ,
124131 val reason : Notification .Reason ,
125132 )
@@ -302,18 +309,37 @@ internal class OfflineNotificationsRepository @Inject constructor(
302309 token : String ,
303310 ) = savedStateDataSource.inCurrentProfileSession { signedProfileId ->
304311 if (signedProfileId == null ) return @inCurrentProfileSession expiredSessionOutcome()
305- val saveNotificationTokenRequest = SaveNotificationTokenRequest (
306- did = signedProfileId.id,
307- token = token,
308- )
309- networkMonitor.runCatchingWithNetworkRetry(
310- block = {
311- notificationsClient.post(SaveNotificationTokenPath ) {
312- contentType(ContentType .Application .Json )
313- setBody(saveNotificationTokenRequest)
314- }
315- },
316- ).toOutcome()
312+
313+ networkService.runCatchingWithMonitoredNetworkRetry {
314+ getServiceAuth(
315+ GetServiceAuthQueryParams (
316+ aud = Did (signedProfileId.id),
317+ exp = Clock .System .now().epochSeconds + 5 .minutes.inWholeSeconds,
318+ lxm = Nsid (PostUri .NAMESPACE ),
319+ ),
320+ )
321+ }.mapToResult { tokenResponse ->
322+ val saveNotificationTokenRequest = SaveNotificationTokenRequest (
323+ did = signedProfileId.id,
324+ token = token,
325+ otherDids = savedStateDataSource.savedState
326+ .value.pastSessions
327+ ?.mapNotNull {
328+ if (it.profileId == signedProfileId) null
329+ else it.profileId.id
330+ }
331+ .orEmpty(),
332+ )
333+ networkMonitor.runCatchingWithNetworkRetry(
334+ block = {
335+ notificationsClient.post(SaveNotificationTokenPath ) {
336+ contentType(ContentType .Application .Json )
337+ setBody(saveNotificationTokenRequest)
338+ bearerAuth(tokenResponse.token)
339+ }
340+ },
341+ )
342+ }.toOutcome()
317343 } ? : expiredSessionOutcome()
318344
319345 override suspend fun updateNotificationPreferences (
@@ -348,7 +374,9 @@ internal class OfflineNotificationsRepository @Inject constructor(
348374 push = update.push,
349375 )
350376 when (update.reason) {
351- Notification .Reason .JoinedStarterPack -> currentPrefs.copy(starterpackJoined = simplePref)
377+ Notification .Reason .JoinedStarterPack -> currentPrefs.copy(
378+ starterpackJoined = simplePref,
379+ )
352380 Notification .Reason .SubscribedPost -> currentPrefs.copy(subscribedPost = simplePref)
353381 Notification .Reason .Unverified -> currentPrefs.copy(unverified = simplePref)
354382 Notification .Reason .Verified -> currentPrefs.copy(verified = simplePref)
@@ -405,12 +433,9 @@ internal class OfflineNotificationsRepository @Inject constructor(
405433 query : NotificationsQuery .Push ,
406434 ): Result <Notification > =
407435 // Push notifications can be received for any profile that has been signed in
408- savedStateDataSource.inPastSession(query.recordUri.profileId()) { token ->
409- val signedInProfileId = token.authProfileId
410-
436+ savedStateDataSource.inProfileSession(query.targetDid) { signedInProfileId ->
411437 recordResolver.resolve(query.recordUri)
412438 .mapCatchingUnlessCancelled { resolvedRecord ->
413-
414439 val authorEntity = profileDao.profiles(
415440 signedInProfiledId = signedInProfileId.id,
416441 ids = listOf (query.senderId),
@@ -552,7 +577,9 @@ internal class OfflineNotificationsRepository @Inject constructor(
552577 }
553578 }
554579
555- val notificationPreferences = savedStateDataSource.savedState.value.signedNotificationPreferencesOrDefault()
580+ val notificationPreferences = profileData.notifications
581+ .preferences
582+ ? : NotificationPreferences .Default
556583
557584 val isAuthorFollowed = viewerState?.isFollowing == true
558585
@@ -690,6 +717,7 @@ private fun SavedState.signedInProfileNotifications() =
690717private data class SaveNotificationTokenRequest (
691718 val did : String ,
692719 val token : String ,
720+ val otherDids : List <String >,
693721)
694722
695723private const val SaveNotificationTokenPath = " /saveNotificationToken"
0 commit comments