Handle SUBSCRIPTION_STATUS FetchLatest sync message.

This commit is contained in:
Alex Hart
2026-05-11 12:11:46 -03:00
committed by Michelle Tang
parent b4404bb5b4
commit 8ffc2e7ab8
3 changed files with 96 additions and 1 deletions
@@ -255,6 +255,7 @@ public final class JobManagerFactories {
put(ReclaimUsernameAndLinkJob.KEY, new ReclaimUsernameAndLinkJob.Factory());
put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory());
put(RefreshCallLinkDetailsJob.KEY, new RefreshCallLinkDetailsJob.Factory());
put(RefreshDonationSubscriptionStatusJob.KEY, new RefreshDonationSubscriptionStatusJob.Factory());
put(RefreshSvrCredentialsJob.KEY, new RefreshSvrCredentialsJob.Factory());
put(RefreshOwnProfileJob.KEY, new RefreshOwnProfileJob.Factory());
put(RemoteConfigRefreshJob.KEY, new RemoteConfigRefreshJob.Factory());
@@ -0,0 +1,93 @@
package org.thoughtcrime.securesms.jobs
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.net.NotPushRegisteredException
import org.thoughtcrime.securesms.recipients.Recipient
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
/**
* Refreshes the local view of the user's recurring donation subscription by querying
* the server with the locally-known subscriber id and updating
* [org.thoughtcrime.securesms.keyvalue.InAppPaymentValues.setLastEndOfPeriod] when the
* server reports an active subscription with a newer end-of-period.
*
* Used to handle incoming `FetchLatest(SUBSCRIPTION_STATUS)` sync messages on linked
* devices, where redemption never runs and `lastEndOfPeriod` would otherwise stay zero,
* causing [org.thoughtcrime.securesms.keyvalue.InAppPaymentValues.isLikelyASustainer] to
* report false even when the user is in fact subscribed.
*/
class RefreshDonationSubscriptionStatusJob private constructor(parameters: Parameters) : BaseJob(parameters) {
companion object {
const val KEY = "RefreshDonationSubscriptionStatusJob"
private val TAG = Log.tag(RefreshDonationSubscriptionStatusJob::class.java)
@JvmStatic
fun enqueue() {
val job = RefreshDonationSubscriptionStatusJob(
Parameters.Builder()
.setQueue(KEY)
.addConstraint(NetworkConstraint.KEY)
.setMaxInstancesForFactory(1)
.setMaxAttempts(10)
.build()
)
AppDependencies.jobManager.add(job)
}
}
override fun serialize(): ByteArray? = null
override fun getFactoryKey(): String = KEY
override fun onFailure() = Unit
override fun onRun() {
if (!Recipient.self().isRegistered) {
throw NotPushRegisteredException()
}
if (!SignalStore.account.isLinkedDevice) {
Log.i(TAG, "Not a linked device, skipping.")
return
}
val subscriber = InAppPaymentsRepository.getSubscriber(InAppPaymentSubscriberRecord.Type.DONATION)
if (subscriber == null) {
Log.i(TAG, "No local donation subscriber id, nothing to refresh.")
return
}
val activeSubscription = AppDependencies.donationsService.getSubscription(subscriber.subscriberId).resultOrThrow
if (activeSubscription.isActive) {
val endOfCurrentPeriod = activeSubscription.activeSubscription.endOfCurrentPeriod
if (endOfCurrentPeriod > SignalStore.inAppPayments.getLastEndOfPeriod()) {
Log.i(TAG, "Server reports active subscription with newer end-of-period. Updating local state.")
SignalStore.inAppPayments.setLastEndOfPeriod(endOfCurrentPeriod)
} else {
Log.i(TAG, "Server reports active subscription but local end-of-period is already current.")
}
} else {
Log.i(TAG, "Server reports no active subscription.")
}
}
override fun onShouldRetry(e: Exception): Boolean {
return e is PushNetworkException
}
class Factory : Job.Factory<RefreshDonationSubscriptionStatusJob> {
override fun create(parameters: Parameters, serializedData: ByteArray?): RefreshDonationSubscriptionStatusJob {
return RefreshDonationSubscriptionStatusJob(parameters)
}
}
}
@@ -68,6 +68,7 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceKeysUpdateJob
import org.thoughtcrime.securesms.jobs.MultiDeviceStickerPackSyncJob
import org.thoughtcrime.securesms.jobs.PushProcessEarlyMessagesJob
import org.thoughtcrime.securesms.jobs.RefreshCallLinkDetailsJob
import org.thoughtcrime.securesms.jobs.RefreshDonationSubscriptionStatusJob
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob
import org.thoughtcrime.securesms.jobs.StorageSyncJob
@@ -1137,7 +1138,7 @@ object SyncMessageProcessor {
when (fetchType) {
FetchLatest.Type.LOCAL_PROFILE -> AppDependencies.jobManager.add(RefreshOwnProfileJob())
FetchLatest.Type.STORAGE_MANIFEST -> AppDependencies.jobManager.add(StorageSyncJob.forRemoteChange())
FetchLatest.Type.SUBSCRIPTION_STATUS -> warn(envelopeTimestamp, "Dropping subscription status fetch message.")
FetchLatest.Type.SUBSCRIPTION_STATUS -> RefreshDonationSubscriptionStatusJob.enqueue()
else -> warn(envelopeTimestamp, "Received a fetch message for an unknown type.")
}
}