diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e0b86be788..c70f62330e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -105,7 +105,6 @@ protobuf { android { namespace = "network.loki.messenger" - useLibrary("org.apache.http.legacy") compileOptions { sourceCompatibility = JavaVersion.VERSION_21 diff --git a/app/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt b/app/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt index cdf760a7b7..410acddafc 100644 --- a/app/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt +++ b/app/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope -import network.loki.messenger.libsession_util.ConfigBase +import network.loki.messenger.libsession_util.PRIORITY_HIDDEN import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.Message @@ -144,7 +144,7 @@ class BatchMessageReceiveJob @AssistedInject constructor( message.groupPublicKey == null && // not a group message.openGroupServerMessageID == null && // not a community // not marked as hidden - configs.contacts.get(message.senderOrSync)?.priority == ConfigBase.PRIORITY_HIDDEN && + configs.contacts.get(message.senderOrSync)?.priority == PRIORITY_HIDDEN && // the message's sentTimestamp is earlier than the sentTimestamp of the last config message.sentTimestamp!! < contactConfigTimestamp } diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt index 032e061847..e2b42f3976 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt @@ -14,7 +14,6 @@ import org.session.libsession.messaging.messages.control.TypingIndicator import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.snode.SnodeAPI -import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsignal.crypto.PushTransportDetails import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.protos.SignalServiceProtos.Envelope diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index 67cae5d7b8..590dec0032 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -6,8 +6,8 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE +import network.loki.messenger.libsession_util.PRIORITY_HIDDEN +import network.loki.messenger.libsession_util.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.Namespace import network.loki.messenger.libsession_util.ReadableUserProfile import network.loki.messenger.libsession_util.protocol.SessionProtocol @@ -124,7 +124,7 @@ class MessageSender @Inject constructor( msg.toProto(builder, messageDataProvider) // Attach pro proof - proDatabase.getCurrentProProof()?.let { proof -> + configFactory.withUserConfigs { it.userProfile.getProConfig() }?.proProof?.let { proof -> builder.proMessageBuilder.proofBuilder.copyFromLibSession(proof) } diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 3c9058502d..979e83c8cb 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -10,12 +10,13 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import network.loki.messenger.R -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE +import network.loki.messenger.libsession_util.PRIORITY_HIDDEN +import network.loki.messenger.libsession_util.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.ED25519 import network.loki.messenger.libsession_util.util.BaseCommunityInfo import network.loki.messenger.libsession_util.util.BlindKeyAPI import network.loki.messenger.libsession_util.util.ExpiryMode +import network.loki.messenger.libsession_util.util.Util import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.StorageProtocol import org.session.libsession.database.userAuth @@ -394,7 +395,7 @@ class ReceivedMessageHandler @Inject constructor( // Verify the incoming message length and truncate it if needed, before saving it to the db val maxChars = proStatusManager.getIncomingMessageMaxLength(message) - val messageText = message.text?.take(maxChars) // truncate to max char limit for this message + val messageText = message.text?.let { Util.truncateCodepoints(it, maxChars) } // truncate to max char limit for this message message.text = messageText message.hasMention = listOfNotNull(userPublicKey, context.userBlindedKey) .any { key -> diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageProcessor.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageProcessor.kt index 93e3614bb7..cc29b8156e 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageProcessor.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageProcessor.kt @@ -6,7 +6,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import network.loki.messenger.R -import network.loki.messenger.libsession_util.ConfigBase +import network.loki.messenger.libsession_util.PRIORITY_HIDDEN import network.loki.messenger.libsession_util.util.BaseCommunityInfo import network.loki.messenger.libsession_util.util.BlindKeyAPI import network.loki.messenger.libsession_util.util.KeyPair @@ -493,7 +493,7 @@ class ReceivedMessageProcessor @Inject constructor( threadAddress: Address.Standard ): Boolean { val hidden = configFactory.withUserConfigs { configs -> - configs.contacts.get(threadAddress.address)?.priority == ConfigBase.PRIORITY_HIDDEN + configs.contacts.get(threadAddress.address)?.priority == PRIORITY_HIDDEN } return hidden && diff --git a/app/src/main/java/org/session/libsession/messaging/sending_receiving/VisibleMessageHandler.kt b/app/src/main/java/org/session/libsession/messaging/sending_receiving/VisibleMessageHandler.kt index 672e46a318..57ea0d6182 100644 --- a/app/src/main/java/org/session/libsession/messaging/sending_receiving/VisibleMessageHandler.kt +++ b/app/src/main/java/org/session/libsession/messaging/sending_receiving/VisibleMessageHandler.kt @@ -3,10 +3,11 @@ package org.session.libsession.messaging.sending_receiving import android.text.TextUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE +import network.loki.messenger.libsession_util.PRIORITY_HIDDEN +import network.loki.messenger.libsession_util.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.util.BaseCommunityInfo import network.loki.messenger.libsession_util.util.ExpiryMode +import network.loki.messenger.libsession_util.util.Util import org.session.libsession.database.MessageDataProvider import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.jobs.AttachmentDownloadJob @@ -147,7 +148,7 @@ class VisibleMessageHandler @Inject constructor( // Verify the incoming message length and truncate it if needed, before saving it to the db val maxChars = proStatusManager.getIncomingMessageMaxLength(message) - val messageText = message.text?.take(maxChars) // truncate to max char limit for this message + val messageText = message.text?.let { Util.truncateCodepoints(it, maxChars) } // truncate to max char limit for this message message.text = messageText message.hasMention = (sequenceOf(ctx.currentUserPublicKey) + ctx.getCurrentUserBlindedIDsByThread(threadAddress).asSequence()) .any { key -> diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt index 0f86787029..62ce248290 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/Recipient.kt @@ -1,6 +1,6 @@ package org.session.libsession.utilities.recipients -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_PINNED +import network.loki.messenger.libsession_util.PRIORITY_PINNED import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.messaging.open_groups.GroupMemberRole import org.session.libsession.utilities.Address diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientData.kt b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientData.kt index 9de714d356..26506c7ca9 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientData.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientData.kt @@ -1,6 +1,6 @@ package org.session.libsession.utilities.recipients -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE +import network.loki.messenger.libsession_util.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.GroupMember diff --git a/app/src/main/java/org/thoughtcrime/securesms/InputbarViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/InputbarViewModel.kt index f1eb2e8304..c5fdda1fb9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/InputbarViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/InputbarViewModel.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import network.loki.messenger.R +import network.loki.messenger.libsession_util.util.Util import org.session.libsession.utilities.StringSubstitutionConstants.LIMIT_KEY import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.pro.ProStatus @@ -33,7 +34,7 @@ abstract class InputbarViewModel( fun onTextChanged(text: CharSequence) { // check the character limit val maxChars = proStatusManager.getCharacterLimit(currentUser.isPro) - val charsLeft = maxChars - text.length + val charsLeft = maxChars - Util.countCodepoints(text.toString()) // update the char limit state based on characters left val charLimitState = if(charsLeft <= CHARACTER_LIMIT_THRESHOLD){ diff --git a/app/src/main/java/org/thoughtcrime/securesms/ShareViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/ShareViewModel.kt index 33b4ee7b7e..c500c40ce2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ShareViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ShareViewModel.kt @@ -23,7 +23,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import network.loki.messenger.R -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN +import network.loki.messenger.libsession_util.PRIORITY_HIDDEN import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager import org.session.libsession.utilities.Address import org.session.libsession.utilities.recipients.RecipientData @@ -32,7 +32,6 @@ import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.home.search.searchName import org.thoughtcrime.securesms.mms.PartAuthority -import org.thoughtcrime.securesms.pro.ProStatusManager import org.thoughtcrime.securesms.providers.BlobUtils import org.thoughtcrime.securesms.repository.ConversationRepository import org.thoughtcrime.securesms.util.AvatarUIData @@ -46,9 +45,8 @@ import javax.inject.Inject class ShareViewModel @Inject constructor( @param:ApplicationContext private val context: Context, private val avatarUtils: AvatarUtils, - private val proStatusManager: ProStatusManager, private val deprecationManager: LegacyGroupDeprecationManager, - private val conversationRepository: ConversationRepository, + conversationRepository: ConversationRepository, ): ViewModel(){ private val TAG = ShareViewModel::class.java.simpleName diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index fa84cb5e5d..a0321f0c19 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -44,8 +44,8 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import network.loki.messenger.R -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE +import network.loki.messenger.libsession_util.PRIORITY_HIDDEN +import network.loki.messenger.libsession_util.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.util.BlindKeyAPI import network.loki.messenger.libsession_util.util.BlindedContact import network.loki.messenger.libsession_util.util.ExpiryMode diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt index 756862d7cc..6ed520d3be 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt @@ -27,8 +27,8 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import network.loki.messenger.R -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE +import network.loki.messenger.libsession_util.PRIORITY_HIDDEN +import network.loki.messenger.libsession_util.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.GroupManagerV2 diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java index 6e29d501ac..e262f18022 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; -import android.os.Build; import androidx.annotation.NonNull; @@ -31,7 +30,6 @@ import org.session.libsignal.crypto.ecc.DjbECPublicKey; import org.session.libsignal.crypto.ecc.ECKeyPair; import org.session.libsignal.crypto.ecc.ECPrivateKey; -import org.session.libsignal.crypto.ecc.ECPublicKey; import org.session.libsignal.exceptions.InvalidKeyException; import org.session.libsignal.utilities.Base64; @@ -40,7 +38,6 @@ import kotlin.Unit; import kotlinx.coroutines.channels.BufferOverflow; import kotlinx.coroutines.flow.MutableSharedFlow; -import kotlinx.coroutines.flow.MutableStateFlow; import kotlinx.coroutines.flow.SharedFlowKt; import network.loki.messenger.libsession_util.Curve25519; import network.loki.messenger.libsession_util.util.KeyPair; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index ef55129d9e..a7bcedbf2b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -25,8 +25,9 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.withContext -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE +import network.loki.messenger.libsession_util.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.ReadableGroupInfoConfig +import network.loki.messenger.libsession_util.protocol.ProFeature import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.GroupInfo import org.session.libsession.messaging.open_groups.GroupMemberRole @@ -158,7 +159,7 @@ class RecipientRepository @Inject constructor( * multiple sub-recipients, and each of them may have their own pro data. * We then collect all the pro data and perform a final calculation at the end of [fetchRecipient]. */ - private class FetchRecipientContext { + private class ProDataContext { var proDataList: MutableList? = null fun addProData(proData: RecipientSettings.ProData?) { @@ -182,13 +183,13 @@ class RecipientRepository @Inject constructor( ): Pair?> { val now = snodeClock.get().currentTime() - val fetchRecipientContext = FetchRecipientContext() + val proDataContext = ProDataContext() // Fetch data from config first, this may contain partial information for some kind of recipient val configData = getDataFromConfig( address = address.toBlinded() ?.let { blindedIdMappingRepository.findMappings(it).firstOrNull()?.second } ?: address, - fetchRecipientContext = fetchRecipientContext + proDataContext = proDataContext ) val changeSources: MutableList>? @@ -204,7 +205,6 @@ class RecipientRepository @Inject constructor( it == TextSecurePreferences.SET_FORCE_CURRENT_USER_PRO || it == TextSecurePreferences.DEBUG_SUBSCRIPTION_STATUS }, - proDatabase.currentProProofChangesNotification, ) } else { null @@ -247,8 +247,7 @@ class RecipientRepository @Inject constructor( is RecipientData.Group -> { value = createGroupV2Recipient( address = address, - now = now, - fetchRecipientContext = fetchRecipientContext, + proDataContext = proDataContext, configData = configData, settings = settingsFetcher(address), settingsFetcher = settingsFetcher, @@ -258,7 +257,8 @@ class RecipientRepository @Inject constructor( changeSources = if (needFlow) { arrayListOf( - configFactory.userConfigsChanged(onlyConfigTypes = EnumSet.of(UserConfigType.USER_GROUPS)), + configFactory.userConfigsChanged(onlyConfigTypes = EnumSet.of(UserConfigType.USER_GROUPS, + UserConfigType.USER_PROFILE)), configFactory.configUpdateNotifications .filterIsInstance() .filter { it.groupId.hexString == address.address }, @@ -269,7 +269,6 @@ class RecipientRepository @Inject constructor( }, TextSecurePreferences.events.filter { it == TextSecurePreferences.SET_FORCE_OTHER_USERS_PRO }, proDatabase.revocationChangeNotification, - proDatabase.currentProProofChangesNotification, ) } else { null @@ -315,8 +314,7 @@ class RecipientRepository @Inject constructor( } value = group?.let { createLegacyGroupRecipient( - now = now, - fetchRecipientContext = fetchRecipientContext, + proDataContext = proDataContext, address = address, config = groupConfig, group = it, @@ -325,8 +323,7 @@ class RecipientRepository @Inject constructor( ) } ?: createGenericRecipient( address = address, - now = now, - fetchRecipientContext = fetchRecipientContext, + proDataContext = proDataContext, settings = settings ) } @@ -343,8 +340,7 @@ class RecipientRepository @Inject constructor( ) } ?: createGenericRecipient( address = address, - now = now, - fetchRecipientContext = fetchRecipientContext, + proDataContext = proDataContext, settings = settings ) @@ -379,16 +375,14 @@ class RecipientRepository @Inject constructor( .firstOrNull() ?.let { groupMember -> fetchGroupMember( - now = now, - fetchRecipientContext = fetchRecipientContext, + proDataContext = proDataContext, member = groupMember, settingsFetcher = settingsFetcher ) } ?: createGenericRecipient( address = address, - now = now, - fetchRecipientContext = fetchRecipientContext, + proDataContext = proDataContext, settings = settings ) @@ -399,7 +393,7 @@ class RecipientRepository @Inject constructor( configFactory.userConfigsChanged(), recipientSettingsDatabase.changeNotification.filter { it == address }, TextSecurePreferences.events.filter { it == TextSecurePreferences.SET_FORCE_OTHER_USERS_PRO }, - proDatabase.currentProProofChangesNotification, + configFactory.userConfigsChanged(EnumSet.of(UserConfigType.USER_PROFILE)), ) } else { null @@ -409,8 +403,7 @@ class RecipientRepository @Inject constructor( else -> { value = createGenericRecipient( address = address, - now = now, - fetchRecipientContext = fetchRecipientContext, + proDataContext = proDataContext, settings = settings ) @@ -418,7 +411,7 @@ class RecipientRepository @Inject constructor( arrayListOf( recipientSettingsDatabase.changeNotification.filter { it == address }, TextSecurePreferences.events.filter { it == TextSecurePreferences.SET_FORCE_OTHER_USERS_PRO }, - proDatabase.currentProProofChangesNotification, + configFactory.userConfigsChanged(EnumSet.of(UserConfigType.USER_PROFILE)), ) } else { null @@ -429,7 +422,7 @@ class RecipientRepository @Inject constructor( } // Calculate the ProData for this recipient - val proDataList = fetchRecipientContext.proDataList + val proDataList = proDataContext.proDataList var proData = if (!proDataList.isNullOrEmpty()) { proDataList.removeAll { it.isExpired(now) || proDatabase.isRevoked(it.genIndexHash) @@ -478,12 +471,11 @@ class RecipientRepository @Inject constructor( * for a group member purpose. */ private inline fun fetchGroupMember( - now: Instant, - fetchRecipientContext: FetchRecipientContext?, + proDataContext: ProDataContext?, member: RecipientData.GroupMemberInfo, settingsFetcher: (address: Address) -> RecipientSettings ): Recipient { - return when (val configData = getDataFromConfig(member.address, fetchRecipientContext)) { + return when (val configData = getDataFromConfig(member.address, proDataContext)) { is RecipientData.Self -> { createLocalRecipient(member.address, configData) } @@ -501,8 +493,7 @@ class RecipientRepository @Inject constructor( // with the settings fetched from the database. createGenericRecipient( address = member.address, - now = now, - fetchRecipientContext = fetchRecipientContext, + proDataContext = proDataContext, settings = settingsFetcher(member.address), groupMemberInfo = member ) @@ -512,11 +503,10 @@ class RecipientRepository @Inject constructor( private inline fun fetchLegacyGroupMember( address: Address.Standard, - now: Instant, - fetchRecipientContext: FetchRecipientContext?, + proDataContext: ProDataContext?, settingsFetcher: (address: Address) -> RecipientSettings, ): Recipient { - return when (val configData = getDataFromConfig(address, fetchRecipientContext)) { + return when (val configData = getDataFromConfig(address, proDataContext)) { is RecipientData.Self -> { createLocalRecipient(address, configData) } @@ -534,8 +524,7 @@ class RecipientRepository @Inject constructor( // with the settings fetched from the database. createGenericRecipient( address = address, - now = now, - fetchRecipientContext = fetchRecipientContext, + proDataContext = proDataContext, settings = settingsFetcher(address), ) } @@ -570,7 +559,7 @@ class RecipientRepository @Inject constructor( */ private fun getDataFromConfig( address: Address, - fetchRecipientContext: FetchRecipientContext? + proDataContext: ProDataContext? ): RecipientData? { return when (address) { is Address.Standard -> { @@ -580,19 +569,21 @@ class RecipientRepository @Inject constructor( ignoreCase = true ) ) { - //TODO: The pro data will come from config later, now we are getting it - // from local db - fetchRecipientContext?.let { ctx -> - proDatabase.getCurrentProProof()?.let { proof -> - ctx.addProData(RecipientSettings.ProData( - expiry = Instant.ofEpochMilli(proof.expiryMs), - genIndexHash = proof.genIndexHashHex, - showProBadge = true - )) + configFactory.withUserConfigs { configs -> + val pro = configs.userProfile.getProConfig() + + if (pro != null) { + proDataContext?.addProData( + RecipientSettings.ProData( + showProBadge = configs.userProfile.getProFeatures().contains( + ProFeature.PRO_BADGE + ), + expiry = Instant.ofEpochMilli(pro.proProof.expiryMs), + genIndexHash = pro.proProof.genIndexHashHex, + ) + ) } - } - configFactory.withUserConfigs { configs -> RecipientData.Self( name = configs.userProfile.getName().orEmpty(), avatar = configs.userProfile.getPic().toRemoteFile(), @@ -697,8 +688,7 @@ class RecipientRepository @Inject constructor( */ private fun createGenericRecipient( address: Address, - now: Instant, - fetchRecipientContext: FetchRecipientContext?, + proDataContext: ProDataContext?, settings: RecipientSettings, // Additional data for group members, if available. groupMemberInfo: RecipientData.GroupMemberInfo? = null, @@ -707,8 +697,8 @@ class RecipientRepository @Inject constructor( "Address must match the group member info address if provided." } - if (settings.proData != null && fetchRecipientContext != null) { - fetchRecipientContext.addProData(settings.proData) + if (settings.proData != null && proDataContext != null) { + proDataContext.addProData(settings.proData) } return Recipient( @@ -728,8 +718,7 @@ class RecipientRepository @Inject constructor( private inline fun createGroupV2Recipient( address: Address, - now: Instant, - fetchRecipientContext: FetchRecipientContext?, + proDataContext: ProDataContext?, configData: RecipientData.Group, settings: RecipientSettings?, settingsFetcher: (Address) -> RecipientSettings, @@ -738,10 +727,10 @@ class RecipientRepository @Inject constructor( address = address, data = configData.copy( firstMember = configData.members.firstOrNull()?.let { member -> - fetchGroupMember(now, fetchRecipientContext, member, settingsFetcher) + fetchGroupMember(proDataContext?.takeIf { member.isAdmin }, member, settingsFetcher) } ?: getSelf(), // Fallback to have self as first member if no members are present secondMember = configData.members.getOrNull(1)?.let { member -> - fetchGroupMember(now, fetchRecipientContext, member, settingsFetcher) + fetchGroupMember(proDataContext?.takeIf { member.isAdmin }, member, settingsFetcher) }, ), mutedUntil = settings?.muteUntil, @@ -751,8 +740,7 @@ class RecipientRepository @Inject constructor( } private inline fun createLegacyGroupRecipient( - now: Instant, - fetchRecipientContext: FetchRecipientContext?, + proDataContext: ProDataContext?, address: Address, config: GroupInfo.LegacyGroupInfo?, group: GroupRecord, // Local db data @@ -786,10 +774,10 @@ class RecipientRepository @Inject constructor( } }, firstMember = memberAddresses.firstOrNull() - ?.let { fetchLegacyGroupMember(it, now, fetchRecipientContext, settingsFetcher) } + ?.let { fetchLegacyGroupMember(it, proDataContext, settingsFetcher) } ?: getSelf(), // Fallback to have self as first member if no members are present secondMember = memberAddresses.getOrNull(1) - ?.let { fetchLegacyGroupMember(it, now, fetchRecipientContext, settingsFetcher) }, + ?.let { fetchLegacyGroupMember(it, proDataContext, settingsFetcher) }, isCurrentUserAdmin = Address.Standard(myAccountId) in group.admins ), mutedUntil = settings?.muteUntil, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 3cfbd203aa..c86eedc792 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -4,8 +4,8 @@ import android.content.Context import android.net.Uri import dagger.Lazy import dagger.hilt.android.qualifiers.ApplicationContext -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_PINNED -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE +import network.loki.messenger.libsession_util.PRIORITY_PINNED +import network.loki.messenger.libsession_util.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.MutableConversationVolatileConfig import network.loki.messenger.libsession_util.ReadableUserGroupsConfig import network.loki.messenger.libsession_util.protocol.ProFeatures diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/RecipientSettings.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/RecipientSettings.kt index 311849f493..501b5c6229 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/RecipientSettings.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/RecipientSettings.kt @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.database.model import kotlinx.serialization.Serializable -import network.loki.messenger.libsession_util.protocol.ProFeatures import network.loki.messenger.libsession_util.util.UserPic import org.session.libsession.utilities.serializable.InstantAsMillisSerializer import java.time.Instant diff --git a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt index b526e13b94..82b991b2c4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt @@ -22,8 +22,8 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE +import network.loki.messenger.libsession_util.PRIORITY_HIDDEN +import network.loki.messenger.libsession_util.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.ED25519 import network.loki.messenger.libsession_util.protocol.ProFeature import network.loki.messenger.libsession_util.protocol.ProFeatures diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt index 5e700a6db0..91a500b55c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt @@ -4,7 +4,6 @@ import android.content.Context import com.google.protobuf.ByteString import com.squareup.phrase.Phrase import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async @@ -13,7 +12,7 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import network.loki.messenger.R -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE +import network.loki.messenger.libsession_util.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.ED25519 import network.loki.messenger.libsession_util.Namespace import network.loki.messenger.libsession_util.util.Bytes.Companion.toBytes diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 21fa5e10f1..801f2e3fb4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -41,7 +41,7 @@ import kotlinx.coroutines.withContext import network.loki.messenger.BuildConfig import network.loki.messenger.R import network.loki.messenger.databinding.ActivityHomeBinding -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN +import network.loki.messenger.libsession_util.PRIORITY_HIDDEN import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager import org.session.libsession.messaging.jobs.JobQueue diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt index c444dbad40..50e059a1ec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -26,7 +26,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import network.loki.messenger.R -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN +import network.loki.messenger.libsession_util.PRIORITY_HIDDEN import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.utilities.Address diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/manager/CreateAccountManager.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/manager/CreateAccountManager.kt index de563c05e9..6e6dd857de 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/manager/CreateAccountManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/manager/CreateAccountManager.kt @@ -1,7 +1,7 @@ package org.thoughtcrime.securesms.onboarding.manager import android.app.Application -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN +import network.loki.messenger.libsession_util.PRIORITY_HIDDEN import org.session.libsession.snode.SnodeModule import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.TextSecurePreferences diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt index a709a1cb6d..0afe1c3fdc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt @@ -9,6 +9,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.NavOptionsBuilder import com.squareup.phrase.Phrase +import dagger.Lazy import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -21,6 +22,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import network.loki.messenger.R +import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.NonTranslatableStringConstants import org.session.libsession.utilities.StringSubstitutionConstants.ACTION_TYPE_KEY import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY @@ -66,6 +68,7 @@ class ProSettingsViewModel @AssistedInject constructor( private val dateUtils: DateUtils, private val prefs: TextSecurePreferences, private val proDetailsRepository: ProDetailsRepository, + private val configFactory: Lazy, ) : ViewModel() { @AssistedFactory @@ -296,7 +299,7 @@ class ProSettingsViewModel @AssistedInject constructor( RefundPlanState( proStatus = sub, isQuickRefund = isQuickRefund, - quickRefundUrl = sub.providerData.refundUrl + quickRefundUrl = sub.providerData.refundPlatformUrl ) ) } @@ -433,7 +436,9 @@ class ProSettingsViewModel @AssistedInject constructor( } is Commands.SetShowProBadge -> { - //todo PRO implement + configFactory.get().withMutableUserConfigs { configs -> + configs.userProfile.setProBadge(command.show) + } } is Commands.RefeshProDetails -> { diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ConfigExt.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ConfigExt.kt new file mode 100644 index 0000000000..85c8432bd0 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ConfigExt.kt @@ -0,0 +1,21 @@ +package org.thoughtcrime.securesms.pro + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import network.loki.messenger.libsession_util.pro.ProConfig +import org.session.libsession.utilities.ConfigFactoryProtocol +import org.session.libsession.utilities.UserConfigType +import org.session.libsession.utilities.userConfigsChanged +import org.thoughtcrime.securesms.util.castAwayType +import java.util.EnumSet + +fun ConfigFactoryProtocol.watchUserProConfig(): Flow = + userConfigsChanged(EnumSet.of(UserConfigType.USER_PROFILE)) + .castAwayType() + .onStart { emit(Unit) } + .map { + withUserConfigs { configs -> + configs.userProfile.getProConfig() + } + } diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/FetchProDetailsWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/FetchProDetailsWorker.kt index e8ccf0ae98..3df0e146d3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/FetchProDetailsWorker.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/FetchProDetailsWorker.kt @@ -19,6 +19,7 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.mapNotNull import org.session.libsession.snode.SnodeClock +import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsignal.exceptions.NonRetryableException import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.auth.LoginStateRepository @@ -46,6 +47,7 @@ class FetchProDetailsWorker @AssistedInject constructor( private val proDatabase: ProDatabase, private val loginStateRepository: LoginStateRepository, private val snodeClock: SnodeClock, + private val configFactory: ConfigFactoryProtocol, ) : CoroutineWorker(context, params) { override suspend fun doWork(): Result { val proMasterKey = @@ -64,6 +66,13 @@ class FetchProDetailsWorker @AssistedInject constructor( "Fetched pro details, status = ${details.status}, expiry = ${details.expiry}" ) + configFactory.withMutableUserConfigs { + if (details.expiry != null) { + it.userProfile.setProAccessExpiryMs(details.expiry.toEpochMilli()) + } else { + it.userProfile.removeProAccessExpiry() + } + } proDatabase.updateProDetails(proDetails = details, updatedAt = snodeClock.currentTime()) scheduleProofGenerationIfNeeded(details) @@ -88,9 +97,11 @@ class FetchProDetailsWorker @AssistedInject constructor( if (details.status != ProDetails.DETAILS_STATUS_ACTIVE) { Log.d(TAG, "Pro is not active, clearing proof") ProProofGenerationWorker.cancel(context) - proDatabase.updateCurrentProProof(null) + configFactory.withMutableUserConfigs { + it.userProfile.removeProConfig() + } } else { - val currentProof = proDatabase.getCurrentProProof() + val currentProof = configFactory.withUserConfigs { it.userProfile.getProConfig() }?.proProof if (currentProof == null || currentProof.expiryMs <= now) { Log.d( diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProDataMapper.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProDataMapper.kt index c7e16f622e..a802045349 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/ProDataMapper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProDataMapper.kt @@ -85,9 +85,9 @@ val previewAppleMetaData = PaymentProviderMetadata( platformAccount = "Apple Account", updateSubscriptionUrl = "https://www.apple.com/account/subscriptions", cancelSubscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://www.apple.com/account/subscriptions", + refundPlatformUrl = "https://www.apple.com/account/subscriptions", refundSupportUrl = "https://www.apple.com/account/subscriptions", - refundAfterPlatformDeadlineUrl = "https://www.apple.com/account/subscriptions" + refundStatusUrl = "https://www.apple.com/account/subscriptions" ) val previewAutoRenewingApple = ProStatus.Active.AutoRenewing( diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProDetailsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProDetailsRepository.kt index 7b7d654990..947a260654 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/ProDetailsRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProDetailsRepository.kt @@ -82,7 +82,7 @@ class ProDetailsRepository @Inject constructor( val currentState = loadState.value if (!force && (currentState is LoadState.Loading || currentState is LoadState.Loaded) && currentState.lastUpdated?.second?.plusSeconds(MIN_UPDATE_INTERVAL_SECONDS) - ?.isBefore(snodeClock.currentTime()) == true) { + ?.isAfter(snodeClock.currentTime()) == true) { Log.d(DebugLogGroup.PRO_DATA.label, "Pro details are fresh enough, skipping refresh") return } @@ -93,6 +93,6 @@ class ProDetailsRepository @Inject constructor( companion object { - private const val MIN_UPDATE_INTERVAL_SECONDS = 120L + private const val MIN_UPDATE_INTERVAL_SECONDS = 60L } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProProofGenerationWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProProofGenerationWorker.kt index 5743121bec..4617a46315 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/ProProofGenerationWorker.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProProofGenerationWorker.kt @@ -14,8 +14,10 @@ import androidx.work.await import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.CancellationException +import network.loki.messenger.libsession_util.ED25519 +import network.loki.messenger.libsession_util.pro.ProConfig import org.session.libsession.snode.OnionRequestAPI -import org.session.libsession.snode.SnodeClock +import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsignal.exceptions.NonRetryableException import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.auth.LoginStateRepository @@ -23,7 +25,6 @@ import org.thoughtcrime.securesms.pro.api.GenerateProProofRequest import org.thoughtcrime.securesms.pro.api.ProApiExecutor import org.thoughtcrime.securesms.pro.api.ProDetails import org.thoughtcrime.securesms.pro.api.successOrThrow -import org.thoughtcrime.securesms.pro.db.ProDatabase import org.thoughtcrime.securesms.util.getRootCause import java.time.Duration import java.time.Instant @@ -40,11 +41,10 @@ class ProProofGenerationWorker @AssistedInject constructor( @Assisted context: Context, @Assisted params: WorkerParameters, private val proApiExecutor: ProApiExecutor, - private val proDatabase: ProDatabase, private val generateProProofRequest: GenerateProProofRequest.Factory, private val proDetailsRepository: ProDetailsRepository, private val loginStateRepository: LoginStateRepository, - private val snodeClock: SnodeClock, + private val configFactory: ConfigFactoryProtocol, ) : CoroutineWorker(context, params) { override suspend fun doWork(): Result { val proMasterKey = requireNotNull(loginStateRepository.peekLoginState()?.seeded?.proMasterPrivateKey) { @@ -60,14 +60,21 @@ class ProProofGenerationWorker @AssistedInject constructor( } return try { + val rotatingPrivateKey = ED25519.generate(null).secretKey.data + val proof = proApiExecutor.executeRequest( request = generateProProofRequest.create( masterPrivateKey = proMasterKey, - rotatingPrivateKey = proDatabase.ensureValidRotatingKeys(snodeClock.currentTime()).ed25519PrivKey + rotatingPrivateKey = rotatingPrivateKey ) ).successOrThrow() - proDatabase.updateCurrentProProof(proof) + configFactory.withMutableUserConfigs { + it.userProfile.setProConfig(ProConfig( + proProof = proof, + rotatingPrivateKey = rotatingPrivateKey)) + } + Log.d(WORK_NAME, "Successfully generated a new pro proof expiring at ${Instant.ofEpochMilli(proof.expiryMs)}") Result.success() diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt index 87121d2c06..e93c05e368 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt @@ -26,9 +26,11 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.time.delay import kotlinx.coroutines.withTimeout +import network.loki.messenger.libsession_util.ED25519 import network.loki.messenger.libsession_util.pro.BackendRequests import network.loki.messenger.libsession_util.pro.BackendRequests.PAYMENT_PROVIDER_APP_STORE import network.loki.messenger.libsession_util.pro.BackendRequests.PAYMENT_PROVIDER_GOOGLE_PLAY +import network.loki.messenger.libsession_util.pro.ProConfig import network.loki.messenger.libsession_util.protocol.ProFeatures import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.snode.SnodeClock @@ -207,7 +209,13 @@ class ProStatusManager @Inject constructor( merge( configFactory.get() .userConfigsChanged(EnumSet.of(UserConfigType.USER_PROFILE)) - .map { "UserProfile changes" }, //TODO: also schedule it when the real config changes (not integrated yet) + .map { + configFactory.get().withUserConfigs { configs -> + configs.userProfile.getProAccessExpiryMs() + } + } + .distinctUntilChanged() + .map { "ProAccessExpiry in config changes" }, proDetailsRepository.loadState .mapNotNull { it.lastUpdated?.first?.expiry } @@ -229,14 +237,11 @@ class ProStatusManager @Inject constructor( "ProDetails expiry reached" }, - proDatabase.currentProProofChangesNotification - .onStart { emit(Unit) } - .mapNotNull { - proDatabase.getCurrentProProof()?.expiryMs?.let( - Instant::ofEpochMilli - ) - } - .mapLatest { expiry -> + configFactory.get() + .watchUserProConfig() + .filterNotNull() + .mapLatest { proConfig -> + val expiry = Instant.ofEpochMilli(proConfig.proProof.expiryMs) // Schedule a refresh for a random number between 10 and 60 minutes before proof expiry val now = snodeClock.currentTime() @@ -273,9 +278,9 @@ class ProStatusManager @Inject constructor( postProLaunchStatus.collectLatest { postLaunch -> if (postLaunch) { combine( - proDatabase.currentProProofChangesNotification - .onStart { emit(Unit) } - .mapNotNull { proDatabase.getCurrentProProof()?.genIndexHashHex }, + configFactory.get() + .watchUserProConfig() + .mapNotNull { it?.proProof?.genIndexHashHex }, proDatabase.revocationChangeNotification .onStart { emit(Unit) }, @@ -286,12 +291,14 @@ class ProStatusManager @Inject constructor( ) .filterNotNull() .collectLatest { revokedHash -> - if (revokedHash == proDatabase.getCurrentProProof()?.genIndexHashHex) { - Log.w( - DebugLogGroup.PRO_SUBSCRIPTION.label, - "Current Pro proof has been revoked, clearing local proof" - ) - proDatabase.updateCurrentProProof(null) + configFactory.get().withMutableUserConfigs { configs -> + if (configs.userProfile.getProConfig()?.proProof?.genIndexHashHex == revokedHash) { + Log.w( + DebugLogGroup.PRO_SUBSCRIPTION.label, + "Current Pro proof has been revoked, clearing Pro config" + ) + configs.userProfile.removeProConfig() + } } } } @@ -355,51 +362,61 @@ class ProStatusManager @Inject constructor( // no point in going further if we have no key data val keyData = loginState.loggedInState.value ?: throw Exception() + val rotatingKeyPair = ED25519.generate(null) for (attempt in 1..maxAttempts) { try { - // 5s timeout as per PRD - val paymentResponse = withTimeout(5_000L) { - apiExecutor.executeRequest( - request = AddProPaymentRequest( - googlePaymentToken = paymentId, - googleOrderId = orderId, - masterPrivateKey = keyData.seeded.proMasterPrivateKey, - rotatingPrivateKey = proDatabase.ensureValidRotatingKeys(snodeClock.currentTime()).ed25519PrivKey - ) + // 5s timeout as per PRD + val paymentResponse = withTimeout(5_000L) { + apiExecutor.executeRequest( + request = AddProPaymentRequest( + googlePaymentToken = paymentId, + googleOrderId = orderId, + masterPrivateKey = keyData.seeded.proMasterPrivateKey, + rotatingPrivateKey = rotatingKeyPair.secretKey.data ) - } + ) + } - when (paymentResponse) { - is ProApiResponse.Success -> { - Log.d(DebugLogGroup.PRO_SUBSCRIPTION.label, "Backend 'add pro payment' successful") - // Payment was successfully claimed - save it to the database - proDatabase.updateCurrentProProof(paymentResponse.data) - // refresh the pro details - proDetailsRepository.requestRefresh() + when (paymentResponse) { + is ProApiResponse.Success -> { + Log.d(DebugLogGroup.PRO_SUBSCRIPTION.label, "Backend 'add pro payment' successful") + // Payment was successfully claimed - save it + configFactory.get().withMutableUserConfigs { configs -> + configs.userProfile.setProConfig( + ProConfig( + proProof = paymentResponse.data, + rotatingPrivateKey = rotatingKeyPair.secretKey.data + ) + ) + + configs.userProfile.setProBadge(true) } + // refresh the pro details + proDetailsRepository.requestRefresh() + } - is ProApiResponse.Failure -> { - // Handle payment failure - Log.w(DebugLogGroup.PRO_SUBSCRIPTION.label, "Backend 'add pro payment' failure: $paymentResponse") - when (paymentResponse.status) { - // unknown payment is retryable - throw a generic exception here to go through our retries - AddPaymentErrorStatus.UnknownPayment -> { - throw Exception() - } + is ProApiResponse.Failure -> { + // Handle payment failure + Log.w(DebugLogGroup.PRO_SUBSCRIPTION.label, "Backend 'add pro payment' failure: $paymentResponse") + when (paymentResponse.status) { + // unknown payment is retryable - throw a generic exception here to go through our retries + AddPaymentErrorStatus.UnknownPayment -> { + throw Exception() + } - // nothing to do if already redeemed - AddPaymentErrorStatus.AlreadyRedeemed -> { - return - } + // nothing to do if already redeemed + AddPaymentErrorStatus.AlreadyRedeemed -> { + return + } - // non retryable error - throw our custom exception - AddPaymentErrorStatus.GenericError -> { - throw SubscriptionManager.PaymentServerException() - } + // non retryable error - throw our custom exception + AddPaymentErrorStatus.GenericError -> { + throw SubscriptionManager.PaymentServerException() } } } + } } catch (e: CancellationException) { throw e } catch (e: SubscriptionManager.PaymentServerException){ diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/api/GenerateProProof.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/api/GenerateProProof.kt index 399353c082..bc0c2ae364 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/api/GenerateProProof.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/api/GenerateProProof.kt @@ -13,6 +13,7 @@ class GenerateProProofRequest @AssistedInject constructor( @Assisted private val rotatingPrivateKey: ByteArray, private val snodeClock: SnodeClock, ) : ApiRequest { + override val endpoint: String get() = "generate_pro_proof" diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/db/ProDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/db/ProDatabase.kt index 7144182e8d..ef517ab69d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/db/ProDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/db/ProDatabase.kt @@ -8,13 +8,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json -import network.loki.messenger.libsession_util.ED25519 -import network.loki.messenger.libsession_util.pro.ProProof -import org.session.libsession.utilities.serializable.ByteArrayAsHexSerializer -import org.session.libsession.utilities.serializable.InstantAsMillisSerializer import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.Database import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper @@ -22,14 +16,9 @@ import org.thoughtcrime.securesms.pro.api.ProDetails import org.thoughtcrime.securesms.pro.api.ProRevocations import org.thoughtcrime.securesms.util.asSequence import java.time.Instant -import java.util.Optional -import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject import javax.inject.Provider import javax.inject.Singleton -import kotlin.jvm.optionals.getOrNull -import kotlin.time.Duration.Companion.days -import kotlin.time.toJavaDuration @Singleton class ProDatabase @Inject constructor( @@ -46,15 +35,6 @@ class ProDatabase @Inject constructor( ) val revocationChangeNotification: SharedFlow get() = mutableRevocationChangeNotification - - private val mutableCurrentProProofChangesNotification = MutableSharedFlow( - extraBufferCapacity = 10, - onBufferOverflow = BufferOverflow.DROP_OLDEST - ) - - val currentProProofChangesNotification: SharedFlow get() = mutableCurrentProProofChangesNotification - private var currentProProof = AtomicReference(Optional.empty()) - fun getLastRevocationTicket(): Long? { val cursor = readableDatabase.query("SELECT CAST(value AS INTEGER) FROM pro_state WHERE name = '$STATE_NAME_LAST_TICKET'") return cursor.use { @@ -66,41 +46,6 @@ class ProDatabase @Inject constructor( } } - fun ensureValidRotatingKeys(now: Instant): ProRotatingKeys { - writableDatabase.transaction { - // First read the database to see if we have a valid key - //language=roomsql - val existingKeys: ProRotatingKeys? = readableDatabase.rawQuery(""" - SELECT value FROM pro_state - WHERE name = ? - """, STATE_NAME_ROTATING_KEYS).use { cursor -> - if (cursor.moveToFirst()) { - json.decodeFromString(cursor.getString(0)) - } else { - null - } - } - - if (existingKeys != null && existingKeys.expiry > now) { - return existingKeys - } - - val keyPair = ED25519.generate(null) - val newKeys = ProRotatingKeys( - ed25519PubKey = keyPair.pubKey.data, - ed25519PrivKey = keyPair.secretKey.data, - expiry = now + ROTATING_KEY_VALIDITY_DAYS.days.toJavaDuration(), - ) - - execSQL(""" - INSERT OR REPLACE INTO pro_state (name, value) - VALUES ('$STATE_NAME_ROTATING_KEYS', ?) - """, arrayOf(json.encodeToString(newKeys))) - - return newKeys - } - } - fun updateRevocations( newTicket: Long, data: List @@ -183,53 +128,6 @@ class ProDatabase @Inject constructor( } } - fun getCurrentProProof(): ProProof? { - currentProProof.get()?.let { - return it.getOrNull() - } - - //language=roomsql - return readableDatabase.rawQuery(""" - SELECT value FROM pro_state - WHERE name = '?' - """, STATE_PRO_PROOF).use { cursor -> - if (cursor.moveToFirst()) { - json.decodeFromString(cursor.getString(0)) - } else { - null - } - }.also { - currentProProof.set(Optional.ofNullable(it)) - } - } - - fun updateCurrentProProof(proProof: ProProof?) { - currentProProof.set(Optional.ofNullable(proProof)) - - val changes = if (proProof != null) { - writableDatabase.compileStatement(""" - INSERT INTO pro_state(name, value) - VALUES (?, ?) - ON CONFLICT DO UPDATE SET value=excluded.value WHERE value != excluded.value - """).use { stmt -> - stmt.bindString(1, STATE_PRO_PROOF) - stmt.bindString(2, json.encodeToString(proProof)) - stmt.executeUpdateDelete() > 0 - } - } else { - writableDatabase.compileStatement(""" - DELETE FROM pro_state - WHERE name = '$STATE_PRO_PROOF' - """).use { stmt -> - stmt.executeUpdateDelete() > 0 - } - } - - if (changes) { - mutableCurrentProProofChangesNotification.tryEmit(Unit) - } - } - private val mutableProDetailsChangeNotification = MutableSharedFlow( extraBufferCapacity = 10, onBufferOverflow = BufferOverflow.DROP_OLDEST @@ -280,30 +178,12 @@ class ProDatabase @Inject constructor( } } - @Serializable - class ProRotatingKeys( - @Serializable(with = ByteArrayAsHexSerializer::class) - val ed25519PubKey: ByteArray, - - @Serializable(with = ByteArrayAsHexSerializer::class) - val ed25519PrivKey: ByteArray, - - @Serializable(with = InstantAsMillisSerializer::class) - @SerialName(JSON_NAME_EXPIRY) - val expiry: Instant, - ) { - companion object { - const val JSON_NAME_EXPIRY = "expiry_ms" - } - } companion object { private const val TAG = "ProRevocationDatabase" private const val STATE_NAME_LAST_TICKET = "last_ticket" - private const val STATE_NAME_ROTATING_KEYS = "rotating_private_key" - private const val STATE_PRO_PROOF = "pro_proof" private const val STATE_PRO_DETAILS = "pro_details" private const val STATE_PRO_DETAILS_UPDATED_AT = "pro_details_updated_at" diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/MentionViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/MentionViewModelTest.kt index 0ad8a4b0ca..ffc4c63815 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/MentionViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/MentionViewModelTest.kt @@ -7,7 +7,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE +import network.loki.messenger.libsession_util.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.util.ExpiryMode import org.junit.Before import org.junit.Rule diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 83f8de1035..0a9e2e96e0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ kotlinVersion = "2.2.21" kryoVersion = "5.6.2" kspVersion = "2.3.0" legacySupportV13Version = "1.0.0" -libsessionUtilAndroidVersion = "1.0.9-15-g01bceb9" +libsessionUtilAndroidVersion = "1.0.9-22-gde0502d" media3ExoplayerVersion = "1.8.0" mockitoCoreVersion = "5.20.0" navVersion = "2.9.5"