Convert the libsignal-service JSON models to Kotlin.

This commit is contained in:
Greyson Parrelli
2026-09-15 18:26:03 -04:00
parent 4b9a08f2fb
commit cf386353c4
139 changed files with 1593 additions and 2741 deletions
@@ -15,7 +15,7 @@ import org.whispersystems.signalservice.internal.push.DeviceInfoList
import org.whispersystems.signalservice.internal.push.PreKeyEntity
import org.whispersystems.signalservice.internal.push.PreKeyResponse
import org.whispersystems.signalservice.internal.push.PreKeyResponseItem
import org.whispersystems.signalservice.internal.push.PushServiceSocket
import org.whispersystems.signalservice.internal.push.RegistrationLockFailure
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataJson
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
import org.whispersystems.signalservice.internal.push.WhoAmIResponse
@@ -26,18 +26,16 @@ import java.security.SecureRandom
*/
object MockProvider {
val lockedFailure = PushServiceSocket.RegistrationLockFailure().apply {
svr1Credentials = AuthCredentials.create("username", "password")
val lockedFailure = RegistrationLockFailure(
svr1Credentials = AuthCredentials.create("username", "password"),
svr2Credentials = AuthCredentials.create("username", "password")
}
)
val primaryOnlyDeviceList = DeviceInfoList().apply {
val primaryOnlyDeviceList = DeviceInfoList(
devices = listOf(
DeviceInfo().apply {
id = 1
}
DeviceInfo(id = 1)
)
}
)
val sessionMetadataJson = RegistrationSessionMetadataJson(
id = "asdfasdfasdfasdf",
@@ -50,11 +48,11 @@ object MockProvider {
)
fun createVerifyAccountResponse(aci: ServiceId, newPni: ServiceId): VerifyAccountResponse {
return VerifyAccountResponse().apply {
uuid = aci.toString()
pni = newPni.toString()
return VerifyAccountResponse(
uuid = aci.toString(),
pni = newPni.toString(),
storageCapable = false
}
)
}
fun createWhoAmIResponse(aci: ServiceId, pni: ServiceId, e164: String): WhoAmIResponse {
@@ -69,16 +67,16 @@ object MockProvider {
val signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), identity.privateKey)
val oneTimePreKey = PreKeyRecord(SecureRandom().nextInt(Medium.MAX_VALUE), ECKeyPair.generate())
val device = PreKeyResponseItem().apply {
this.deviceId = deviceId
registrationId = KeyHelper.generateRegistrationId(false)
signedPreKey = SignedPreKeyEntity(signedPreKeyRecord.id.toLong(), signedPreKeyRecord.keyPair.publicKey, signedPreKeyRecord.signature)
val device = PreKeyResponseItem(
deviceId = deviceId,
registrationId = KeyHelper.generateRegistrationId(false),
signedPreKey = SignedPreKeyEntity(signedPreKeyRecord.id.toLong(), signedPreKeyRecord.keyPair.publicKey, signedPreKeyRecord.signature),
preKey = PreKeyEntity(oneTimePreKey.id.toLong(), oneTimePreKey.keyPair.publicKey)
}
)
return PreKeyResponse().apply {
identityKey = identity.publicKey
return PreKeyResponse(
identityKey = identity.publicKey,
devices = listOf(device)
}
)
}
}
@@ -185,7 +185,7 @@ class MessageBackupsFlowViewModel(
}
activeSubscription.runIfSuccessful { subscription ->
if (subscription.willCancelAtPeriodEnd()) {
if (subscription.willCancelAtPeriodEnd) {
Log.d(TAG, "Active subscription is cancelled. Clearing tier.")
internalStateFlow.update {
it.copy(
@@ -123,7 +123,7 @@ object Badges {
uriAndDensity.first,
uriAndDensity.second,
serviceBadge.expiration?.let { getTimestamp(it) } ?: 0,
serviceBadge.isVisible,
serviceBadge.visible,
TimeUnit.SECONDS.toMillis(serviceBadge.duration)
)
}
@@ -53,7 +53,7 @@ class BadgesOverviewViewModel(
RecurringInAppPaymentRepository.getActiveSubscription(InAppPaymentSubscriberRecord.Type.DONATION),
RecurringInAppPaymentRepository.getSubscriptions()
) { active, all ->
if (!active.isActive && active.activeSubscription?.willCancelAtPeriodEnd() == true) {
if (!active.isActive && active.activeSubscription?.willCancelAtPeriodEnd == true) {
Optional.ofNullable<String>(all.firstOrNull { it.level == active.activeSubscription?.level }?.badge?.id)
} else {
Optional.empty()
@@ -297,13 +297,13 @@ class BackupStateObserver(
}
}
val signalServiceSubscriptionIsActiveAndWillRenew = activeSubscription?.isActive == true && (!activeSubscription.isCanceled || activeSubscription.willCancelAtPeriodEnd())
val signalServiceSubscriptionIsActiveAndWillRenew = activeSubscription?.isActive == true && (!activeSubscription.isCanceled || activeSubscription.willCancelAtPeriodEnd)
Log.d(TAG, "[getNetworkBackupState][subscriptionStateMismatchDetected] signalServiceSubscriptionIsActiveAndWillRenew: $signalServiceSubscriptionIsActiveAndWillRenew")
when {
signalServiceSubscriptionIsActiveAndWillRenew && !googlePlayBillingSubscriptionIsActiveAndWillRenew -> {
val type = buildPaidTypeFromSubscription(activeSubscription.activeSubscription)
val type = buildPaidTypeFromSubscription(activeSubscription.activeSubscription!!)
if (type == null) {
Log.d(TAG, "[getNetworkBackupState][subscriptionMismatchDetected] failed to load backup configuration. Likely a network error.")
@@ -313,8 +313,8 @@ class BackupStateObserver(
Log.d(TAG, "[getNetworkBackupState][subscriptionMismatchDetected] found a subscription mismatch and successfully loaded configuration.")
return BackupState.SubscriptionMismatchMissingGooglePlay(
messageBackupsType = type,
renewalTime = activeSubscription.activeSubscription.endOfCurrentPeriod.seconds,
isBilledThroughOtherStore = InAppPaymentsRepository.isBackupBilledThroughOtherStore(activeSubscription.activeSubscription)
renewalTime = activeSubscription.activeSubscription!!.endOfCurrentPeriod.seconds,
isBilledThroughOtherStore = InAppPaymentsRepository.isBackupBilledThroughOtherStore(activeSubscription.activeSubscription!!)
)
}
@@ -397,7 +397,7 @@ class BackupStateObserver(
getStateOnError()
} else {
when {
(subscription.isCanceled || subscription.willCancelAtPeriodEnd()) && subscription.isActive -> {
(subscription.isCanceled || subscription.willCancelAtPeriodEnd) && subscription.isActive -> {
Log.d(TAG, "[getPaidBackupState] Found a canceled subscription.")
InAppPaymentsRepository.updateBackupInAppPaymentWithCancelation(activeSubscription.successOrThrow())
@@ -334,9 +334,9 @@ class ChangeNumberRepository(
return ChangeNumberResult.from(
result.map { accountRegistrationResponse: VerifyAccountResponse ->
NumberChangeResult(
uuid = accountRegistrationResponse.uuid,
pni = accountRegistrationResponse.pni,
number = accountRegistrationResponse.number
uuid = accountRegistrationResponse.uuid!!,
pni = accountRegistrationResponse.pni!!,
number = accountRegistrationResponse.number!!
)
}
)
@@ -353,11 +353,11 @@ class ChangeNumberRepository(
if (whoAmI.number == newE164 && whoAmI.pni != null) {
Log.w(TAG, "Change number request did not succeed, but whoami reports the new number is already active. Treating the change as successful.")
NetworkResult.Success(
VerifyAccountResponse().apply {
uuid = whoAmI.aci
pni = whoAmI.pni
VerifyAccountResponse(
uuid = whoAmI.aci,
pni = whoAmI.pni,
number = whoAmI.number
}
)
)
} else {
Log.i(TAG, "Change number request did not succeed and whoami does not report the new number; treating as a genuine failure.")
@@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.registration.data.network.RegistrationResult
import org.whispersystems.signalservice.api.SvrNoDataException
import org.whispersystems.signalservice.api.svr.Svr3Credentials
import org.whispersystems.signalservice.internal.push.AuthCredentials
import org.whispersystems.signalservice.internal.push.PushServiceSocket.RegistrationLockFailure
import org.whispersystems.signalservice.internal.push.RegistrationLockFailure
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
/**
@@ -42,7 +42,7 @@ class InternalPendingOneTimeDonationConfigurationViewModel : ViewModel() {
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { config ->
val badge = Badges.fromServiceBadge(config.levels.values.first().badge)
val badge = Badges.fromServiceBadge(config.levels.values.first().badge!!)
state.value = state.value.copy(badge = Badges.toDatabaseBadge(badge))
}
@@ -33,7 +33,7 @@ class InternalDonorErrorConfigurationViewModel : ViewModel() {
val configuration = SignalNetwork.donationsService.getDonationsConfiguration(Locale.getDefault()).toNetworkResult().successOrNull() ?: return@launch
val giftBadges = configuration.getGiftBadges()
val boostBadges = configuration.getBoostBadges()
val subscriptionBadges = configuration.getSubscriptionLevels().values.map { Badges.fromServiceBadge(it.badge) }
val subscriptionBadges = configuration.getSubscriptionLevels().values.map { Badges.fromServiceBadge(it.badge!!) }
store.update { it.copy(badges = giftBadges + boostBadges + subscriptionBadges) }
}
@@ -5,10 +5,10 @@ import org.signal.core.util.money.PlatformCurrencyUtil
import org.thoughtcrime.securesms.badges.Badges
import org.thoughtcrime.securesms.badges.models.Badge
import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration
import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration.BOOST_LEVEL
import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration.GIFT_LEVEL
import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration.Companion.BOOST_LEVEL
import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration.Companion.GIFT_LEVEL
import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration.Companion.SUBSCRIPTION_LEVELS
import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration.LevelConfiguration
import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration.SUBSCRIPTION_LEVELS
import java.math.BigDecimal
import java.util.Currency
@@ -79,7 +79,7 @@ fun SubscriptionsConfiguration.getBoostAmounts(paymentMethodAvailability: Paymen
fun SubscriptionsConfiguration.getBadge(level: Int): Badge {
require(level == GIFT_LEVEL || level == BOOST_LEVEL || SUBSCRIPTION_LEVELS.contains(level))
return Badges.fromServiceBadge(levels[level]!!.badge)
return Badges.fromServiceBadge(levels[level]!!.badge!!)
}
fun SubscriptionsConfiguration.getSubscriptionLevels(): Map<Int, LevelConfiguration> {
@@ -98,7 +98,7 @@ object InAppPaymentsRepository {
* This operation will only be performed if we find a latest payment for the given subscriber id in the END state without cancelation data.
*/
fun updateInAppPaymentWithCancelation(activeSubscription: ActiveSubscription, subscriberType: InAppPaymentSubscriberRecord.Type) {
if (activeSubscription.isCanceled || (subscriberType == InAppPaymentSubscriberRecord.Type.BACKUP && activeSubscription.willCancelAtPeriodEnd()) || activeSubscription.isFailedPayment) {
if (activeSubscription.isCanceled || (subscriberType == InAppPaymentSubscriberRecord.Type.BACKUP && activeSubscription.willCancelAtPeriodEnd) || activeSubscription.isFailedPayment) {
writeCancelation(subscriberType, activeSubscription.chargeFailure)
}
}
@@ -145,7 +145,7 @@ object InAppPaymentsRepository {
* This operation will only be performed if we find a latest payment for the given subscriber id in the END state with cancelation data
*/
fun clearCancelation(activeSubscription: ActiveSubscription) {
if (!activeSubscription.isCanceled && !activeSubscription.willCancelAtPeriodEnd()) {
if (!activeSubscription.isCanceled && !activeSubscription.willCancelAtPeriodEnd) {
val subscriber = getSubscriber(InAppPaymentSubscriberRecord.Type.BACKUP) ?: return
val latestPayment = SignalDatabase.inAppPayments.getLatestBySubscriberId(subscriber.subscriberId) ?: return
@@ -95,7 +95,7 @@ object RecurringInAppPaymentRepository {
response.result.ifPresent { result ->
val lastEndOfPeriod = SignalDatabase.inAppPayments.getByLatestEndOfPeriod(type.inAppPaymentType)?.endOfPeriodSeconds ?: 0L
if (result.isActive && result.activeSubscription.endOfCurrentPeriod > lastEndOfPeriod) {
if (result.isActive && result.activeSubscription!!.endOfCurrentPeriod > lastEndOfPeriod) {
InAppPaymentKeepAliveJob.enqueueAndTrackTime(System.currentTimeMillis().milliseconds)
}
}
@@ -118,7 +118,7 @@ object RecurringInAppPaymentRepository {
Subscription(
id = level.toString(),
level = level,
badge = Badges.fromServiceBadge(levelConfig.badge),
badge = Badges.fromServiceBadge(levelConfig.badge!!),
prices = config.getSubscriptionAmounts(level)
)
}
@@ -22,7 +22,7 @@ class TerminalDonationRepository(
.fromCallable { donationsService.getDonationsConfiguration(Locale.getDefault()) }
.flatMap { it.flattenResult() }
.map { it.levels[terminalDonation.level.toInt()]!! }
.map { Badges.fromServiceBadge(it.badge) }
.map { Badges.fromServiceBadge(it.badge!!) }
.subscribeOn(Schedulers.io())
}
}
@@ -126,7 +126,7 @@ data class DonateToSignalState(
val isSubscriptionInProgress: Boolean = _activeSubscription?.isInProgress == true
val activeLevel: Int? = _activeSubscription?.activeSubscription?.level
val activeSubscription: ActiveSubscription.Subscription? = _activeSubscription?.activeSubscription
val isActiveSubscriptionEnding: Boolean = _activeSubscription?.isActive == true && _activeSubscription.activeSubscription.willCancelAtPeriodEnd()
val isActiveSubscriptionEnding: Boolean = _activeSubscription?.isActive == true && _activeSubscription.activeSubscription?.willCancelAtPeriodEnd == true
val renewalTimestamp = TimeUnit.SECONDS.toMillis(activeSubscription?.endOfCurrentPeriod ?: 0L)
val isSelectionValid = selectedSubscription != null && (!isSubscriptionActive || selectedSubscription.level != activeSubscription?.level)
}
@@ -384,7 +384,7 @@ class DonateToSignalViewModel(
private fun resolveSelectedSubscription(activeSubscription: ActiveSubscription, subscriptions: List<Subscription>): Subscription? {
return if (activeSubscription.isActive) {
subscriptions.firstOrNull { it.level == activeSubscription.activeSubscription.level }
subscriptions.firstOrNull { it.level == activeSubscription.activeSubscription!!.level }
} else {
subscriptions.firstOrNull()
}
@@ -31,7 +31,7 @@ object GatewaySelectorRepository {
GatewayConfiguration(
availableGateways = available,
sepaEuroMaximum = if (configuration.sepaMaximumEuros != null) FiatMoney(configuration.sepaMaximumEuros, CurrencyUtil.EURO) else null
sepaEuroMaximum = configuration.sepaMaximumEuros?.let { FiatMoney(it, CurrencyUtil.EURO) }
)
}
}
@@ -18,14 +18,15 @@ fun InAppPaymentProcessorError.toDonationError(
return when (processor) {
ActiveSubscription.Processor.STRIPE -> {
check(method is PaymentSourceType.Stripe)
val declineCode = StripeDeclineCode.getFromCode(chargeFailure.code)
val failureCode = StripeFailureCode.getFromCode(chargeFailure.code)
val chargeFailureCode = chargeFailure.code
val declineCode = StripeDeclineCode.getFromCode(chargeFailureCode)
val failureCode = StripeFailureCode.getFromCode(chargeFailureCode)
if (declineCode.isKnown()) {
DonationError.PaymentSetupError.StripeDeclinedError(source, this, declineCode, method)
} else if (failureCode.isKnown) {
DonationError.PaymentSetupError.StripeFailureCodeError(source, this, failureCode, method)
} else if (chargeFailure.code != null) {
DonationError.PaymentSetupError.StripeCodedError(source, this, chargeFailure.code)
} else if (chargeFailureCode != null) {
DonationError.PaymentSetupError.StripeCodedError(source, this, chargeFailureCode)
} else {
DonationError.PaymentSetupError.GenericError(source, this)
}
@@ -22,7 +22,7 @@ class DonationReceiptListRepository {
val subBadges = config.getSubscriptionLevels().map {
DonationReceiptBadge(
level = it.key,
badge = Badges.fromServiceBadge(it.value.badge),
badge = Badges.fromServiceBadge(it.value.badge!!),
type = InAppPaymentReceiptRecord.Type.RECURRING_DONATION
)
}
@@ -432,9 +432,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
@JvmStatic
fun maskCapabilitiesToLong(capabilities: SignalServiceProfile.Capabilities): Long {
var value: Long = 0
value = Bitmask.update(value, Capabilities.STORAGE_SERVICE_ENCRYPTION_V2, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isStorageServiceEncryptionV2).serialize().toLong())
value = Bitmask.update(value, Capabilities.USERNAME_SYNC_MESSAGES, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isUsernameSyncMessages).serialize().toLong())
value = Bitmask.update(value, Capabilities.OPTIONAL_PHONE_NUMBER, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isOptionalPhoneNumber).serialize().toLong())
value = Bitmask.update(value, Capabilities.STORAGE_SERVICE_ENCRYPTION_V2, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.storageServiceEncryptionV2).serialize().toLong())
value = Bitmask.update(value, Capabilities.USERNAME_SYNC_MESSAGES, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.usernameSyncMessages).serialize().toLong())
value = Bitmask.update(value, Capabilities.OPTIONAL_PHONE_NUMBER, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.optionalPhoneNumber).serialize().toLong())
return value
}
}
@@ -170,7 +170,7 @@ class BackupSubscriptionCheckJob private constructor(parameters: Parameters) : C
checkForFailedOrCanceledSubscriptionState(activeSubscription)
val isSignalSubscriptionFailedOrCanceled = activeSubscription?.willCancelAtPeriodEnd() == true
val isSignalSubscriptionFailedOrCanceled = activeSubscription?.willCancelAtPeriodEnd == true
if (hasActiveSignalSubscription && !isSignalSubscriptionFailedOrCanceled) {
checkAndSynchronizeZkCredentialTierWithStoredLocalTier()
}
@@ -256,7 +256,7 @@ class BackupSubscriptionCheckJob private constructor(parameters: Parameters) : C
* the "download your data" notifier sheet.
*/
private fun checkForFailedOrCanceledSubscriptionState(activeSubscription: ActiveSubscription?) {
if (activeSubscription?.willCancelAtPeriodEnd() == true && activeSubscription.activeSubscription != null) {
if (activeSubscription?.willCancelAtPeriodEnd == true && activeSubscription.activeSubscription != null) {
Log.i(TAG, "Subscription either has a payment failure or has been canceled.")
val response = SignalNetwork.accountApi.whoAmI()
@@ -300,7 +300,7 @@ class InAppPaymentKeepAliveJob private constructor(
info(type, "Failed to load subscription configuration for level ${subscription.level} for type $type")
null
} else {
Badges.toDatabaseBadge(Badges.fromServiceBadge(subscriptionConfig.badge))
Badges.toDatabaseBadge(Badges.fromServiceBadge(subscriptionConfig.badge!!))
}
} else {
warn(TAG, "Failed to load configuration while processing $type")
@@ -289,15 +289,15 @@ class IndividualSendJobV2 private constructor(parameters: Parameters, private va
is MessageService.SendError.ChallengeRequired -> {
Log.w(TAG, "${logPrefix(message.sentTimeMillis)} Challenge required (options=${error.options})", error)
val proofResponse = ProofRequiredResponse().apply {
token = error.token
val proofResponse = ProofRequiredResponse(
token = error.token,
options = error.options.map {
when (it) {
ChallengeOption.PUSH_CHALLENGE -> "pushChallenge"
ChallengeOption.CAPTCHA -> "captcha"
}
}
}
)
val proofException = ProofRequiredException(proofResponse, error.retryAfter?.inWholeSeconds ?: 0L)
val threadRecipient = SignalDatabase.threads.getRecipientForThreadId(threadId)
when (ProofRequiredExceptionHandler.handle(context, proofException, threadRecipient, threadId, messageId)) {
@@ -70,7 +70,7 @@ class RefreshDonationSubscriptionStatusJob private constructor(parameters: Param
val activeSubscription = SignalNetwork.donationsService.getSubscription(subscriber.subscriberId).resultOrThrow
if (activeSubscription.isActive) {
val endOfCurrentPeriod = activeSubscription.activeSubscription.endOfCurrentPeriod
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)
@@ -108,10 +108,10 @@ class RefreshOwnProfileJob private constructor(parameters: Parameters) : BaseJob
val profile = profileAndCredential.getProfile()
if (Util.isEmpty(profile.getName()) &&
Util.isEmpty(profile.getAvatar()) &&
Util.isEmpty(profile.getAbout()) &&
Util.isEmpty(profile.getAboutEmoji())
if (Util.isEmpty(profile.name) &&
Util.isEmpty(profile.avatar) &&
Util.isEmpty(profile.about) &&
Util.isEmpty(profile.aboutEmoji)
) {
Log.w(TAG, "The profile we retrieved was empty! Ignoring it.")
@@ -125,13 +125,13 @@ class RefreshOwnProfileJob private constructor(parameters: Parameters) : BaseJob
return
}
setProfileName(profile.getName())
setProfileAbout(profile.getAbout(), profile.getAboutEmoji())
setProfileAvatar(profile.getAvatar())
setProfileCapabilities(profile.getCapabilities())
setProfileBadges(profile.getBadges())
ensureUnidentifiedAccessCorrect(profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess())
ensurePhoneNumberSharingIsCorrect(profile.getPhoneNumberSharing())
setProfileName(profile.name)
setProfileAbout(profile.about, profile.aboutEmoji)
setProfileAvatar(profile.avatar)
setProfileCapabilities(profile.capabilities)
setProfileBadges(profile.badges)
ensureUnidentifiedAccessCorrect(profile.unidentifiedAccess, profile.unrestrictedUnidentifiedAccess)
ensurePhoneNumberSharingIsCorrect(profile.phoneNumberSharing)
profileAndCredential.getExpiringProfileKeyCredential()
.ifPresent { setExpiringProfileKeyCredential(self, ProfileKeyUtil.getSelfProfileKey(), it) }
@@ -287,8 +287,8 @@ class RefreshOwnProfileJob private constructor(parameters: Parameters) : BaseJob
.toSet()
val remoteDonorBadgeIds = badges
.filter { it.getCategory() == Badge.Category.Donor.code }
.map { it.getId() }
.filter { it.category == Badge.Category.Donor.code }
.map { it.id }
.toSet()
val remoteHasSubscriptionBadges = remoteDonorBadgeIds.any { isSubscription(it) }
@@ -320,13 +320,14 @@ class RefreshOwnProfileJob private constructor(parameters: Parameters) : BaseJob
if (response.getResult().isPresent()) {
val activeSubscription = response.getResult().get()
if (activeSubscription.isFailedPayment()) {
if (activeSubscription.isFailedPayment) {
Log.d(TAG, "Unexpected expiry due to payment failure.", true)
isDueToPaymentFailure = true
}
if (activeSubscription.getChargeFailure() != null) {
Log.d(TAG, "Active payment contains a charge failure: " + activeSubscription.getChargeFailure().getCode(), true)
val chargeFailure = activeSubscription.chargeFailure
if (chargeFailure != null) {
Log.d(TAG, "Active payment contains a charge failure: " + chargeFailure.code, true)
}
}
@@ -376,8 +377,8 @@ class RefreshOwnProfileJob private constructor(parameters: Parameters) : BaseJob
SignalStore.inAppPayments.setExpiredGiftBadge(null)
}
val userHasVisibleBadges = badges.any { it.isVisible() }
val userHasInvisibleBadges = badges.any { !it.isVisible() }
val userHasVisibleBadges = badges.any { it.visible }
val userHasInvisibleBadges = badges.any { !it.visible }
val appBadges = badges.map { Badges.fromServiceBadge(it) }
@@ -245,11 +245,12 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val
return true
}
if (localRecipientRecord.badges != remoteProfile.badges.map { Badges.fromServiceBadge(it) }) {
if (localRecipientRecord.badges != remoteProfile.badges.orEmpty().map { Badges.fromServiceBadge(it) }) {
return true
}
if (localRecipientRecord.capabilities.rawBits != maskCapabilitiesToLong(remoteProfile.capabilities)) {
val remoteCapabilities = remoteProfile.capabilities
if (remoteCapabilities != null && localRecipientRecord.capabilities.rawBits != maskCapabilitiesToLong(remoteCapabilities)) {
return true
}
@@ -257,7 +258,7 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val
val accessMode = deriveUnidentifiedAccessMode(
profileKey = profileKey,
unidentifiedAccessVerifier = remoteProfile.unidentifiedAccess,
unrestrictedUnidentifiedAccess = remoteProfile.isUnrestrictedUnidentifiedAccess
unrestrictedUnidentifiedAccess = remoteProfile.unrestrictedUnidentifiedAccess
)
if (localRecipientRecord.sealedSenderAccessMode != accessMode) {
@@ -297,7 +298,7 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val
val recipientProfileKey = ProfileKeyUtil.profileKeyOrNull(recipient.profileKey)
val badges = profile.badges?.map { Badges.fromServiceBadge(it) }
val accessMode = deriveUnidentifiedAccessMode(recipientProfileKey, profile.unidentifiedAccess, profile.isUnrestrictedUnidentifiedAccess)
val accessMode = deriveUnidentifiedAccessMode(recipientProfileKey, profile.unidentifiedAccess, profile.unrestrictedUnidentifiedAccess)
if (badges != null && badges.size != recipient.badges.size) {
Log.i(TAG, "Likely change in badges for ${recipient.id}. Going from ${recipient.badges.size} badge(s) to ${badges.size}.")
@@ -88,26 +88,26 @@ object LinkDeviceRepository {
}
fun WaitForLinkedDeviceResponse.getPlaintextDevice(): Device {
val response = this
return DeviceInfo().apply {
id = response.id
name = response.name
lastSeen = response.lastSeen
registrationId = response.registrationId
createdAtCiphertext = response.createdAtCiphertext
}.toLocalDevice()
return DeviceInfo(
id = id,
name = name,
lastSeen = lastSeen,
registrationId = registrationId,
createdAtCiphertext = createdAtCiphertext
).toLocalDevice()
}
private fun DeviceInfo.toLocalDevice(): Device {
val createdAt = this.getPlaintextCreatedAt()
val defaultDevice = Device(getId(), getName(), createdAt, getLastSeen(), getRegistrationId())
val encodedName = name
val defaultDevice = Device(id, encodedName, createdAt, lastSeen, registrationId)
try {
if (getName().isNullOrEmpty() || getName().length < 4) {
if (encodedName.isNullOrEmpty() || encodedName.length < 4) {
Log.w(TAG, "Invalid DeviceInfo name.")
return defaultDevice
}
val deviceName = DeviceName.ADAPTER.decode(Base64.decode(getName()))
val deviceName = DeviceName.ADAPTER.decode(Base64.decode(encodedName))
if (deviceName.ciphertext == null || deviceName.ephemeralPublic == null || deviceName.syntheticIv == null) {
Log.w(TAG, "Got a DeviceName that wasn't properly populated.")
return defaultDevice
@@ -119,7 +119,7 @@ object LinkDeviceRepository {
return defaultDevice
}
return Device(getId(), String(plaintext), createdAt, getLastSeen(), getRegistrationId())
return Device(id, String(plaintext), createdAt, lastSeen, registrationId)
} catch (e: Exception) {
Log.w(TAG, "Failed while reading the protobuf.", e)
}
@@ -156,9 +156,9 @@ object LinkDeviceRepository {
private fun DeviceInfo.getPlaintextCreatedAt(): Long? {
return try {
val associatedData = byteArrayOf(getId().toByte()) + this.getRegistrationId().toByteArray()
val associatedData = byteArrayOf(id.toByte()) + registrationId.toByteArray()
val createdAtPlaintext = SignalStore.account.aciIdentityKey.privateKey.open(
ciphertext = Base64.decode(this.getCreatedAtCiphertext().toByteArray()),
ciphertext = Base64.decode(createdAtCiphertext!!.toByteArray()),
info = DECRYPTION_INFO,
associatedData = associatedData
)
@@ -464,10 +464,10 @@ object RegistrationRepository {
val result: NetworkResult<AccountRegistrationResult> = api.registerAccount(sessionId, registrationData.recoveryPassword, accountAttributes, aciPreKeyCollection, pniPreKeyCollection, registrationData.fcmToken, true)
.map { accountRegistrationResponse: VerifyAccountResponse ->
AccountRegistrationResult(
uuid = accountRegistrationResponse.uuid,
pni = accountRegistrationResponse.pni,
uuid = accountRegistrationResponse.uuid!!,
pni = accountRegistrationResponse.pni!!,
storageCapable = accountRegistrationResponse.storageCapable,
number = accountRegistrationResponse.number,
number = accountRegistrationResponse.number!!,
masterKey = masterKey,
pin = pin,
aciPreKeyCollection = aciPreKeyCollection,
@@ -2041,7 +2041,7 @@ public class SignalServiceMessageSender {
try {
SendMessageResponse response = NetworkResultUtil.toMessageSendLegacy(messages.getDestination(), messageApi.sendMessage(messages, sealedSenderAccess, story));
return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || aciStore.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent());
return SendMessageResult.success(recipient, messages.getDevices(), response.getSentUnidentified(), response.getNeedsSync() || aciStore.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent());
} catch (AuthorizationFailedException |
UnregisteredUserException |
MismatchedDevicesException |
@@ -2080,7 +2080,7 @@ public class SignalServiceMessageSender {
SendMessageResponse response = socket.sendMessage(messages, sealedSenderAccess, story);
return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || aciStore.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent());
return SendMessageResult.success(recipient, messages.getDevices(), response.getSentUnidentified(), response.getNeedsSync() || aciStore.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent());
} catch (InvalidKeyException ike) {
Log.w(TAG, ike);
@@ -2302,7 +2302,7 @@ public class SignalServiceMessageSender {
SendMessageResult result = SendMessageResult.success(
recipient,
messages.getDevices(),
response.sentUnidentified(),
response.getSentUnidentified(),
response.getNeedsSync() || aciStore.isMultiDevice(),
System.currentTimeMillis() - startTime,
content.getContent()
@@ -2345,7 +2345,7 @@ public class SignalServiceMessageSender {
return SendMessageResult.success(
recipient,
messages.getDevices(),
response.sentUnidentified(),
response.getSentUnidentified(),
response.getNeedsSync() || aciStore.isMultiDevice(),
System.currentTimeMillis() - startTime,
content.getContent()
@@ -1,93 +0,0 @@
package org.whispersystems.signalservice.api.account;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.signal.libsignal.protocol.IdentityKey;
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity;
import org.whispersystems.signalservice.internal.push.KyberPreKeyEntity;
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage;
import org.signal.network.util.JsonUtil;
import java.util.List;
import java.util.Map;
public final class ChangePhoneNumberRequest {
@JsonProperty
private String sessionId;
@JsonProperty
private String recoveryPassword;
@JsonProperty
private String number;
@JsonProperty("reglock")
private String registrationLock;
@JsonProperty
@JsonSerialize(using = JsonUtil.IdentityKeySerializer.class)
@JsonDeserialize(using = JsonUtil.IdentityKeyDeserializer.class)
private IdentityKey pniIdentityKey;
@JsonProperty
private List<OutgoingPushMessage> deviceMessages;
@JsonProperty
private Map<String, SignedPreKeyEntity> devicePniSignedPrekeys;
@JsonProperty("devicePniPqLastResortPrekeys")
private Map<String, KyberPreKeyEntity> devicePniLastResortKyberPrekeys;
@JsonProperty
private Map<String, Integer> pniRegistrationIds;
@SuppressWarnings("unused")
public ChangePhoneNumberRequest() {}
public ChangePhoneNumberRequest(String sessionId,
String recoveryPassword,
String number,
String registrationLock,
IdentityKey pniIdentityKey,
List<OutgoingPushMessage> deviceMessages,
Map<String, SignedPreKeyEntity> devicePniSignedPrekeys,
Map<String, KyberPreKeyEntity> devicePniLastResortKyberPrekeys,
Map<String, Integer> pniRegistrationIds)
{
this.sessionId = sessionId;
this.recoveryPassword = recoveryPassword;
this.number = number;
this.registrationLock = registrationLock;
this.pniIdentityKey = pniIdentityKey;
this.deviceMessages = deviceMessages;
this.devicePniSignedPrekeys = devicePniSignedPrekeys;
this.devicePniLastResortKyberPrekeys = devicePniLastResortKyberPrekeys;
this.pniRegistrationIds = pniRegistrationIds;
}
public String getNumber() {
return number;
}
public String getRegistrationLock() {
return registrationLock;
}
public IdentityKey getPniIdentityKey() {
return pniIdentityKey;
}
public List<OutgoingPushMessage> getDeviceMessages() {
return deviceMessages;
}
public Map<String, SignedPreKeyEntity> getDevicePniSignedPrekeys() {
return devicePniSignedPrekeys;
}
public Map<String, Integer> getPniRegistrationIds() {
return pniRegistrationIds;
}
}
@@ -0,0 +1,29 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.account
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import org.signal.libsignal.protocol.IdentityKey
import org.signal.network.util.JsonUtil
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
import org.whispersystems.signalservice.internal.push.KyberPreKeyEntity
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage
class ChangePhoneNumberRequest(
val sessionId: String? = null,
val recoveryPassword: String? = null,
val number: String,
@JsonProperty("reglock") val registrationLock: String? = null,
@JsonSerialize(using = JsonUtil.IdentityKeySerializer::class)
@JsonDeserialize(using = JsonUtil.IdentityKeyDeserializer::class)
val pniIdentityKey: IdentityKey,
val deviceMessages: List<OutgoingPushMessage>,
val devicePniSignedPrekeys: Map<String, SignedPreKeyEntity>,
@JsonProperty("devicePniPqLastResortPrekeys") val devicePniLastResortKyberPrekeys: Map<String, KyberPreKeyEntity>,
val pniRegistrationIds: Map<String, Int>
)
@@ -1,29 +0,0 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.donations;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialRequest;
import org.signal.core.util.Base64;
import org.whispersystems.signalservice.internal.push.DonationProcessor;
class BoostReceiptCredentialRequestJson {
@JsonProperty("paymentIntentId")
private final String paymentIntentId;
@JsonProperty("receiptCredentialRequest")
private final String receiptCredentialRequest;
@JsonProperty("processor")
private final String processor;
BoostReceiptCredentialRequestJson(String paymentIntentId, ReceiptCredentialRequest receiptCredentialRequest, DonationProcessor processor) {
this.paymentIntentId = paymentIntentId;
this.receiptCredentialRequest = Base64.encodeWithPadding(receiptCredentialRequest.serialize());
this.processor = processor.getCode();
}
}
@@ -0,0 +1,22 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.donations
import org.signal.core.util.Base64
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialRequest
import org.whispersystems.signalservice.internal.push.DonationProcessor
internal class BoostReceiptCredentialRequestJson(
val paymentIntentId: String,
val receiptCredentialRequest: String,
val processor: String
) {
constructor(paymentIntentId: String, receiptCredentialRequest: ReceiptCredentialRequest, processor: DonationProcessor) : this(
paymentIntentId = paymentIntentId,
receiptCredentialRequest = Base64.encodeWithPadding(receiptCredentialRequest.serialize()),
processor = processor.code
)
}
@@ -236,7 +236,7 @@ class DonationsApi(private val authWebSocket: SignalWebSocket.AuthenticatedWebSo
val body = ReceiptCredentialRequestJson(receiptCredentialRequest)
val request = WebSocketRequestMessage.post("/v1/subscription/${subscriberId.serialize()}/receipt_credentials", body)
return NetworkResult.fromWebSocketRequest(unauthWebSocket, request, webSocketResponseConverter = NetworkResult.LongPollingWebSocketConverter(ReceiptCredentialResponseJson::class))
.map { it.receiptCredentialResponse }
.map { it.credentialResponse }
.then {
if (it != null) {
NetworkResult.Success(it)
@@ -260,7 +260,7 @@ class DonationsApi(private val authWebSocket: SignalWebSocket.AuthenticatedWebSo
val body = BoostReceiptCredentialRequestJson(paymentIntentId, receiptCredentialRequest, processor)
val request = WebSocketRequestMessage.post("/v1/subscription/boost/receipt_credentials", body)
return NetworkResult.fromWebSocketRequest(unauthWebSocket, request, webSocketResponseConverter = NetworkResult.LongPollingWebSocketConverter(ReceiptCredentialResponseJson::class))
.map { it.receiptCredentialResponse }
.map { it.credentialResponse }
.then {
if (it != null) {
NetworkResult.Success(it)
@@ -1,40 +0,0 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.donations;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Request JSON for confirming a PayPal one-time payment intent
*/
class PayPalConfirmOneTimePaymentIntentPayload {
@JsonProperty
private String amount;
@JsonProperty
private String currency;
@JsonProperty
private long level;
@JsonProperty
private String payerId;
@JsonProperty
private String paymentId;
@JsonProperty
private String paymentToken;
public PayPalConfirmOneTimePaymentIntentPayload(String amount, String currency, long level, String payerId, String paymentId, String paymentToken) {
this.amount = amount;
this.currency = currency;
this.level = level;
this.payerId = payerId;
this.paymentId = paymentId;
this.paymentToken = paymentToken;
}
}
@@ -0,0 +1,18 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.donations
/**
* Request JSON for confirming a PayPal one-time payment intent
*/
internal class PayPalConfirmOneTimePaymentIntentPayload(
val amount: String,
val currency: String,
val level: Long,
val payerId: String,
val paymentId: String,
val paymentToken: String
)
@@ -1,36 +0,0 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.donations;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Request JSON for creating a PayPal one-time payment intent
*/
class PayPalCreateOneTimePaymentIntentPayload {
@JsonProperty
private long amount;
@JsonProperty
private String currency;
@JsonProperty
private long level;
@JsonProperty
private String returnUrl;
@JsonProperty
private String cancelUrl;
public PayPalCreateOneTimePaymentIntentPayload(long amount, String currency, long level, String returnUrl, String cancelUrl) {
this.amount = amount;
this.currency = currency;
this.level = level;
this.returnUrl = returnUrl;
this.cancelUrl = cancelUrl;
}
}
@@ -0,0 +1,17 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.donations
/**
* Request JSON for creating a PayPal one-time payment intent
*/
internal class PayPalCreateOneTimePaymentIntentPayload(
val amount: Long,
val currency: String,
val level: Long,
val returnUrl: String,
val cancelUrl: String
)
@@ -1,21 +0,0 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.donations;
import com.fasterxml.jackson.annotation.JsonProperty;
class PayPalCreatePaymentMethodPayload {
@JsonProperty
private String returnUrl;
@JsonProperty
private String cancelUrl;
PayPalCreatePaymentMethodPayload(String returnUrl, String cancelUrl) {
this.returnUrl = returnUrl;
this.cancelUrl = cancelUrl;
}
}
@@ -0,0 +1,14 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.donations
/**
* Request JSON for creating a recurring PayPal payment method
*/
internal class PayPalCreatePaymentMethodPayload(
val returnUrl: String,
val cancelUrl: String
)
@@ -1,20 +0,0 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.donations;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialRequest;
import org.signal.core.util.Base64;
class ReceiptCredentialRequestJson {
@JsonProperty("receiptCredentialRequest")
private final String receiptCredentialRequest;
ReceiptCredentialRequestJson(ReceiptCredentialRequest receiptCredentialRequest) {
this.receiptCredentialRequest = Base64.encodeWithPadding(receiptCredentialRequest.serialize());
}
}
@@ -0,0 +1,15 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.donations
import org.signal.core.util.Base64
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialRequest
internal class ReceiptCredentialRequestJson(
val receiptCredentialRequest: String
) {
constructor(receiptCredentialRequest: ReceiptCredentialRequest) : this(Base64.encodeWithPadding(receiptCredentialRequest.serialize()))
}
@@ -1,36 +0,0 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.donations;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialResponse;
import org.signal.core.util.Base64;
import java.io.IOException;
import javax.annotation.Nullable;
class ReceiptCredentialResponseJson {
private final ReceiptCredentialResponse receiptCredentialResponse;
ReceiptCredentialResponseJson(@JsonProperty("receiptCredentialResponse") String receiptCredentialResponse) {
ReceiptCredentialResponse response;
try {
response = new ReceiptCredentialResponse(Base64.decode(receiptCredentialResponse));
} catch (IOException | InvalidInputException e) {
response = null;
}
this.receiptCredentialResponse = response;
}
public @Nullable ReceiptCredentialResponse getReceiptCredentialResponse() {
return receiptCredentialResponse;
}
}
@@ -0,0 +1,17 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.donations
import com.fasterxml.jackson.annotation.JsonProperty
import org.signal.core.util.Base64
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialResponse
internal class ReceiptCredentialResponseJson(
@JsonProperty("receiptCredentialResponse") receiptCredentialResponse: String
) {
/** Null if the server sent something that isn't a serialized [ReceiptCredentialResponse]. */
val credentialResponse: ReceiptCredentialResponse? = runCatching { ReceiptCredentialResponse(Base64.decode(receiptCredentialResponse)) }.getOrNull()
}
@@ -1,50 +0,0 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.donations;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation;
/**
* POST /v1/donation/redeem-receipt
*
* Request object for redeeming a receipt from a donation transaction.
*/
class RedeemDonationReceiptRequest {
private final String receiptCredentialPresentation;
private final boolean visible;
private final boolean primary;
/**
* @param receiptCredentialPresentation base64-encoded no-newlines standard-character-set with-padding of the bytes of a {@link ReceiptCredentialPresentation} object
* @param visible boolean indicating if the new badge should be visible or not on the profile
* @param primary boolean indicating if the new badge should be primary or not on the profile; is always treated as false if `visible` is false
*/
@JsonCreator RedeemDonationReceiptRequest(
@JsonProperty("receiptCredentialPresentation") String receiptCredentialPresentation,
@JsonProperty("visible") boolean visible,
@JsonProperty("primary") boolean primary) {
this.receiptCredentialPresentation = receiptCredentialPresentation;
this.visible = visible;
this.primary = primary;
}
public String getReceiptCredentialPresentation() {
return receiptCredentialPresentation;
}
public boolean isVisible() {
return visible;
}
public boolean isPrimary() {
return primary;
}
}
@@ -0,0 +1,21 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.donations
/**
* POST /v1/donation/redeem-receipt
*
* Request object for redeeming a receipt from a donation transaction.
*
* @param receiptCredentialPresentation base64-encoded no-newlines standard-character-set with-padding of the bytes of a `ReceiptCredentialPresentation`
* @param visible Whether the new badge should be visible on the profile
* @param primary Whether the new badge should be primary on the profile; always treated as false if [visible] is false
*/
internal class RedeemDonationReceiptRequest(
val receiptCredentialPresentation: String,
val visible: Boolean,
val primary: Boolean
)
@@ -1,29 +0,0 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.donations;
import com.fasterxml.jackson.annotation.JsonProperty;
class StripeOneTimePaymentIntentPayload {
@JsonProperty
private long amount;
@JsonProperty
private String currency;
@JsonProperty
private long level;
@JsonProperty
private String paymentMethod;
public StripeOneTimePaymentIntentPayload(long amount, String currency, long level, String paymentMethod) {
this.amount = amount;
this.currency = currency;
this.level = level;
this.paymentMethod = paymentMethod;
}
}
@@ -0,0 +1,16 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.donations
/**
* Request JSON for creating a Stripe one-time payment intent
*/
internal class StripeOneTimePaymentIntentPayload(
val amount: Long,
val currency: String,
val level: Long,
val paymentMethod: String
)
@@ -1,20 +0,0 @@
package org.whispersystems.signalservice.api.groupsv2;
import com.fasterxml.jackson.annotation.JsonProperty;
public class CredentialResponse {
@JsonProperty
private TemporalCredential[] credentials;
@JsonProperty
private TemporalCredential[] callLinkAuthCredentials;
public TemporalCredential[] getCredentials() {
return credentials;
}
public TemporalCredential[] getCallLinkAuthCredentials() {
return callLinkAuthCredentials;
}
}
@@ -0,0 +1,11 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.groupsv2
class CredentialResponse(
val credentials: Array<TemporalCredential> = emptyArray(),
val callLinkAuthCredentials: Array<TemporalCredential> = emptyArray()
)
@@ -1,20 +0,0 @@
package org.whispersystems.signalservice.api.groupsv2;
import com.fasterxml.jackson.annotation.JsonProperty;
public class TemporalCredential {
@JsonProperty
private byte[] credential;
@JsonProperty
private long redemptionTime;
public byte[] getCredential() {
return credential;
}
public long getRedemptionTime() {
return redemptionTime;
}
}
@@ -0,0 +1,11 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.groupsv2
class TemporalCredential(
val credential: ByteArray,
val redemptionTime: Long
)
@@ -250,7 +250,7 @@ class KeysApi(
return this.map { response ->
val bundles: MutableList<PreKeyBundle> = LinkedList()
for (device in response.getDevices()) {
for (device in response.devices) {
var preKey: ECPublicKey? = null
var signedPreKey: ECPublicKey?
var signedPreKeySignature: ByteArray?
@@ -260,39 +260,42 @@ class KeysApi(
var kyberPreKey: KEMPublicKey?
var kyberPreKeySignature: ByteArray?
if (device.getSignedPreKey() != null) {
val rawSignedPreKeyId = device.getSignedPreKey().keyId
val deviceSignedPreKey = device.signedPreKey
if (deviceSignedPreKey != null) {
val rawSignedPreKeyId = deviceSignedPreKey.keyId
if (rawSignedPreKeyId !in 0..Int.MAX_VALUE.toLong()) {
Log.w(TAG, "Signed pre-key ID for device ${device.deviceId} is out of valid range! Skipping.")
continue
}
signedPreKey = device.getSignedPreKey().publicKey
signedPreKey = deviceSignedPreKey.publicKey
signedPreKeyId = rawSignedPreKeyId.toInt()
signedPreKeySignature = device.getSignedPreKey().signature
signedPreKeySignature = deviceSignedPreKey.signature
} else {
Log.w(TAG, "No signed prekey for device ${device.deviceId}! Skipping.")
continue
}
if (device.getPreKey() != null) {
val rawPreKeyId = device.getPreKey().keyId
val devicePreKey = device.preKey
if (devicePreKey != null) {
val rawPreKeyId = devicePreKey.keyId
if (rawPreKeyId !in 0..Int.MAX_VALUE.toLong()) {
Log.w(TAG, "Pre-key ID for device ${device.deviceId} is out of valid range! Skipping.")
continue
}
preKeyId = rawPreKeyId.toInt()
preKey = device.getPreKey().publicKey
preKey = devicePreKey.publicKey
}
if (device.getKyberPreKey() != null) {
val rawKyberPreKeyId = device.getKyberPreKey().keyId
val deviceKyberPreKey = device.kyberPreKey
if (deviceKyberPreKey != null) {
val rawKyberPreKeyId = deviceKyberPreKey.keyId
if (rawKyberPreKeyId !in 0..Int.MAX_VALUE.toLong()) {
Log.w(TAG, "Kyber pre-key ID for device ${device.deviceId} is out of valid range! Skipping.")
continue
}
kyberPreKey = device.getKyberPreKey().publicKey
kyberPreKey = deviceKyberPreKey.publicKey
kyberPreKeyId = rawKyberPreKeyId.toInt()
kyberPreKeySignature = device.getKyberPreKey().signature
kyberPreKeySignature = deviceKyberPreKey.signature
} else {
Log.w(TAG, "No kyber prekey for device ${device.deviceId}! Skipping.")
continue
@@ -300,14 +303,14 @@ class KeysApi(
bundles.add(
PreKeyBundle(
device.getRegistrationId(),
device.getDeviceId(),
device.registrationId,
device.deviceId,
preKeyId,
preKey,
signedPreKeyId,
signedPreKey,
signedPreKeySignature,
response.getIdentityKey(),
response.identityKey,
kyberPreKeyId,
kyberPreKey,
kyberPreKeySignature
@@ -1,32 +0,0 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.keys;
import com.fasterxml.jackson.annotation.JsonProperty;
public class OneTimePreKeyCounts {
@JsonProperty("count")
private int ecCount;
@JsonProperty("pqCount")
private int kyberCount;
public OneTimePreKeyCounts() {}
public OneTimePreKeyCounts(int ecCount, int kyberCount) {
this.ecCount = ecCount;
this.kyberCount = kyberCount;
}
public int getEcCount() {
return ecCount;
}
public int getKyberCount() {
return kyberCount;
}
}
@@ -0,0 +1,13 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.keys
import com.fasterxml.jackson.annotation.JsonProperty
data class OneTimePreKeyCounts(
@JsonProperty("count") val ecCount: Int = 0,
@JsonProperty("pqCount") val kyberCount: Int = 0
)
@@ -33,14 +33,14 @@ class MessageApi(
companion object {
/**
* Adjust the default parsing of [SendMessageResponse] to set the non-server returned [SendMessageResponse.sentUnidentfied]
* Adjust the default parsing of [SendMessageResponse] to set the non-server returned [SendMessageResponse.sentUnidentified]
* flag on the model.
*/
private val sendMessageResponseConverter = object : NetworkResult.WebSocketResponseConverter<SendMessageResponse> {
override fun convert(response: WebsocketResponse): NetworkResult<SendMessageResponse> {
return if (response.status == 200) {
response.toSuccess(SendMessageResponse::class)
.map { it.apply { setSentUnidentfied(response.isUnidentified) } }
.map { it.apply { sentUnidentified = response.isUnidentified } }
} else {
response.toStatusCodeError()
}
@@ -1,51 +0,0 @@
package org.whispersystems.signalservice.api.messages.calls;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class TurnServerInfo {
@JsonProperty
private String username;
@JsonProperty
private String password;
@JsonProperty
private String hostname;
@JsonProperty
private List<String> urls;
@JsonProperty
private List<String> urlsWithIps;
@JsonProperty
private Long ttl;
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
// Hostname for the ips in urlsWithIps
public String getHostname() {
return hostname;
}
public List<String> getUrls() {
return urls;
}
public List<String> getUrlsWithIps() {
return urlsWithIps;
}
public Long getTtl() {
return ttl;
};
}
@@ -0,0 +1,16 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.messages.calls
data class TurnServerInfo(
val username: String? = null,
val password: String? = null,
/** Hostname for the ips in [urlsWithIps]. */
val hostname: String? = null,
val urls: List<String>? = null,
val urlsWithIps: List<String>? = null,
val ttl: Long? = null
)
@@ -1,49 +0,0 @@
/*
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.whispersystems.signalservice.api.messages.multidevice;
import com.fasterxml.jackson.annotation.JsonProperty;
public class DeviceInfo {
@JsonProperty
public int id;
@JsonProperty
public String name;
@JsonProperty
public long lastSeen;
@JsonProperty
public int registrationId;
@JsonProperty
public String createdAtCiphertext;
public DeviceInfo() {}
public int getId() {
return id;
}
public String getName() {
return name;
}
public long getLastSeen() {
return lastSeen;
}
public int getRegistrationId() {
return registrationId;
}
public String getCreatedAtCiphertext() {
return createdAtCiphertext;
}
}
@@ -0,0 +1,14 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.messages.multidevice
data class DeviceInfo(
val id: Int = 0,
val name: String? = null,
val lastSeen: Long = 0,
val registrationId: Int = 0,
val createdAtCiphertext: String? = null
)
@@ -1,36 +0,0 @@
package org.whispersystems.signalservice.api.messages.multidevice;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.UUID;
public class RegisterAsSecondaryDeviceResponse {
@JsonProperty
private UUID uuid;
@JsonProperty
private UUID pni;
@JsonProperty
private String deviceId;
public RegisterAsSecondaryDeviceResponse() {}
public RegisterAsSecondaryDeviceResponse(UUID uuid, UUID pni, String deviceId) {
this.uuid = uuid;
this.pni = pni;
this.deviceId = deviceId;
}
public UUID getUuid() {
return uuid;
}
public UUID getPni() {
return pni;
}
public String getDeviceId() {
return deviceId;
}
}
@@ -0,0 +1,14 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.messages.multidevice
import java.util.UUID
class RegisterAsSecondaryDeviceResponse(
val uuid: UUID,
val pni: UUID,
val deviceId: String
)
@@ -1,21 +0,0 @@
package org.whispersystems.signalservice.api.payments;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Map;
public final class CurrencyConversion {
@JsonProperty
private String base;
@JsonProperty
private Map<String, Double> conversions;
public String getBase() {
return base;
}
public Map<String, Double> getConversions() {
return conversions;
}
}
@@ -0,0 +1,11 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.payments
data class CurrencyConversion(
val base: String = "",
val conversions: Map<String, Double> = emptyMap()
)
@@ -1,21 +0,0 @@
package org.whispersystems.signalservice.api.payments;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public final class CurrencyConversions {
@JsonProperty
private List<CurrencyConversion> currencies;
@JsonProperty
private long timestamp;
public List<CurrencyConversion> getCurrencies() {
return currencies;
}
public long getTimestamp() {
return timestamp;
}
}
@@ -0,0 +1,11 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.payments
data class CurrencyConversions(
val currencies: List<CurrencyConversion> = emptyList(),
val timestamp: Long = 0
)
@@ -1,241 +0,0 @@
package org.whispersystems.signalservice.api.profiles;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredentialResponse;
import org.signal.core.models.ServiceId;
import org.signal.network.util.JsonUtil;
import java.math.BigDecimal;
import java.util.List;
public class SignalServiceProfile {
public enum RequestType {
PROFILE,
PROFILE_AND_CREDENTIAL
}
private static final String TAG = SignalServiceProfile.class.getSimpleName();
@JsonProperty
private String identityKey;
@JsonProperty
private String name;
@JsonProperty
private String about;
@JsonProperty
private String aboutEmoji;
@JsonProperty
private byte[] paymentAddress;
@JsonProperty
private String avatar;
@JsonProperty
private String unidentifiedAccess;
@JsonProperty
private boolean unrestrictedUnidentifiedAccess;
@JsonProperty
private Capabilities capabilities;
@JsonProperty
@JsonSerialize(using = JsonUtil.ServiceIdSerializer.class)
@JsonDeserialize(using = JsonUtil.ServiceIdDeserializer.class)
private ServiceId uuid;
@JsonProperty
private byte[] credential;
@JsonProperty
private List<Badge> badges;
@JsonProperty
private String phoneNumberSharing;
@JsonIgnore
private RequestType requestType;
public SignalServiceProfile() {}
public String getIdentityKey() {
return identityKey;
}
public String getName() {
return name;
}
public String getAbout() {
return about;
}
public String getAboutEmoji() {
return aboutEmoji;
}
public byte[] getPaymentAddress() {
return paymentAddress;
}
public String getAvatar() {
return avatar;
}
public String getUnidentifiedAccess() {
return unidentifiedAccess;
}
public String getPhoneNumberSharing() {
return phoneNumberSharing;
}
public boolean isUnrestrictedUnidentifiedAccess() {
return unrestrictedUnidentifiedAccess;
}
public Capabilities getCapabilities() {
return capabilities;
}
public List<Badge> getBadges() {
return badges;
}
public ServiceId getServiceId() {
return uuid;
}
public RequestType getRequestType() {
return requestType;
}
public void setRequestType(RequestType requestType) {
this.requestType = requestType;
}
public static class Badge {
@JsonProperty
private String id;
@JsonProperty
private String category;
@JsonProperty
private String name;
@JsonProperty
private String description;
@JsonProperty
private List<String> sprites6;
@JsonProperty
private BigDecimal expiration;
@JsonProperty
private boolean visible;
@JsonProperty
private long duration;
public String getId() {
return id;
}
public String getCategory() {
return category;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public List<String> getSprites6() {
return sprites6;
}
public BigDecimal getExpiration() {
return expiration;
}
public boolean isVisible() {
return visible;
}
/**
* @return Duration badge is valid for, in seconds.
*/
public long getDuration() {
return duration;
}
}
public static class Capabilities {
@JsonProperty
private boolean storage;
@JsonProperty("ssre2")
private boolean storageServiceEncryptionV2;
@JsonProperty("usernameChangeSyncMessage")
private boolean usernameSyncMessages;
@JsonProperty
private boolean optionalPhoneNumber;
@JsonCreator
public Capabilities() {}
public Capabilities(boolean storage, boolean storageServiceEncryptionV2, boolean usernameSyncMessages, boolean optionalPhoneNumber) {
this.storage = storage;
this.storageServiceEncryptionV2 = storageServiceEncryptionV2;
this.usernameSyncMessages = usernameSyncMessages;
this.optionalPhoneNumber = optionalPhoneNumber;
}
public boolean isStorage() {
return storage;
}
public boolean isStorageServiceEncryptionV2() {
return storageServiceEncryptionV2;
}
public boolean isUsernameSyncMessages() {
return usernameSyncMessages;
}
public boolean isOptionalPhoneNumber() {
return optionalPhoneNumber;
}
}
public ExpiringProfileKeyCredentialResponse getExpiringProfileKeyCredentialResponse() {
if (credential == null) return null;
try {
return new ExpiringProfileKeyCredentialResponse(credential);
} catch (InvalidInputException e) {
Log.w(TAG, e);
return null;
}
}
}
@@ -0,0 +1,78 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.profiles
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import org.signal.core.models.ServiceId
import org.signal.core.util.logging.Log
import org.signal.libsignal.zkgroup.InvalidInputException
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredentialResponse
import org.signal.network.util.JsonUtil
import java.math.BigDecimal
class SignalServiceProfile(
val identityKey: String? = null,
val name: String? = null,
val about: String? = null,
val aboutEmoji: String? = null,
val paymentAddress: ByteArray? = null,
val avatar: String? = null,
val unidentifiedAccess: String? = null,
val unrestrictedUnidentifiedAccess: Boolean = false,
val capabilities: Capabilities? = null,
@JsonProperty("uuid")
@JsonSerialize(using = JsonUtil.ServiceIdSerializer::class)
@JsonDeserialize(using = JsonUtil.ServiceIdDeserializer::class)
val serviceId: ServiceId? = null,
val credential: ByteArray? = null,
val badges: List<Badge>? = null,
val phoneNumberSharing: String? = null
) {
val expiringProfileKeyCredentialResponse: ExpiringProfileKeyCredentialResponse?
get() {
if (credential == null) {
return null
}
return try {
ExpiringProfileKeyCredentialResponse(credential)
} catch (e: InvalidInputException) {
Log.w(TAG, e)
null
}
}
enum class RequestType {
PROFILE,
PROFILE_AND_CREDENTIAL
}
data class Badge(
val id: String = "",
val category: String = "",
val name: String = "",
val description: String = "",
val sprites6: List<String> = emptyList(),
val expiration: BigDecimal? = null,
val visible: Boolean = false,
/** Duration the badge is valid for, in seconds. */
val duration: Long = 0
)
data class Capabilities(
val storage: Boolean = false,
@JsonProperty("ssre2") val storageServiceEncryptionV2: Boolean = false,
@JsonProperty("usernameChangeSyncMessage") val usernameSyncMessages: Boolean = false,
val optionalPhoneNumber: Boolean = false
)
companion object {
private val TAG = Log.tag(SignalServiceProfile::class)
}
}
@@ -1,14 +0,0 @@
package org.whispersystems.signalservice.api.provisioning;
import com.fasterxml.jackson.annotation.JsonProperty;
public class ProvisioningMessage {
@JsonProperty
private String body;
public ProvisioningMessage(String body) {
this.body = body;
}
}
@@ -0,0 +1,10 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.provisioning
class ProvisioningMessage(
val body: String
)
@@ -1,57 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.whispersystems.signalservice.api.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
import org.whispersystems.signalservice.internal.push.PreKeyEntity;
import org.signal.core.util.Base64;
import java.io.IOException;
public class SignedPreKeyEntity extends PreKeyEntity {
@JsonProperty
@JsonSerialize(using = ByteArraySerializer.class)
@JsonDeserialize(using = ByteArrayDeserializer.class)
private byte[] signature;
public SignedPreKeyEntity() {}
public SignedPreKeyEntity(long keyId, ECPublicKey publicKey, byte[] signature) {
super(keyId, publicKey);
this.signature = signature;
}
public byte[] getSignature() {
return signature;
}
private static class ByteArraySerializer extends JsonSerializer<byte[]> {
@Override
public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(Base64.encodeWithoutPadding(value));
}
}
private static class ByteArrayDeserializer extends JsonDeserializer<byte[]> {
@Override
public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return Base64.decode(p.getValueAsString());
}
}
}
@@ -0,0 +1,23 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.push
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import org.signal.libsignal.protocol.ecc.ECPublicKey
import org.whispersystems.signalservice.internal.push.ByteArrayDeserializerBase64
import org.whispersystems.signalservice.internal.push.ByteArraySerializerBase64NoPadding
import org.whispersystems.signalservice.internal.push.PreKeyEntity
class SignedPreKeyEntity(
val keyId: Long,
@JsonSerialize(using = PreKeyEntity.ECPublicKeySerializer::class)
@JsonDeserialize(using = PreKeyEntity.ECPublicKeyDeserializer::class)
val publicKey: ECPublicKey,
@JsonSerialize(using = ByteArraySerializerBase64NoPadding::class)
@JsonDeserialize(using = ByteArrayDeserializerBase64::class)
val signature: ByteArray
)
@@ -1,48 +0,0 @@
package org.whispersystems.signalservice.api.push.exceptions;
import org.signal.network.exceptions.MalformedResponseException;
import org.signal.network.exceptions.NonSuccessfulResponseCodeException;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.signal.network.util.JsonUtil;
import io.reactivex.rxjava3.annotations.NonNull;
/**
* Response indicating we gave the server a non-normalized phone number. The expected normalized version of the number is provided.
*/
public class NonNormalizedPhoneNumberException extends NonSuccessfulResponseCodeException {
private final String originalNumber;
private final String normalizedNumber;
public static NonNormalizedPhoneNumberException forResponse(@NonNull String responseBody) throws MalformedResponseException {
JsonResponse response = JsonUtil.fromJsonResponse(responseBody, JsonResponse.class);
return new NonNormalizedPhoneNumberException(response.originalNumber, response.normalizedNumber);
}
public NonNormalizedPhoneNumberException(String originalNumber, String normalizedNumber) {
super(400);
this.originalNumber = originalNumber;
this.normalizedNumber = normalizedNumber;
}
public String getOriginalNumber() {
return originalNumber;
}
public String getNormalizedNumber() {
return normalizedNumber;
}
private static class JsonResponse {
@JsonProperty
private String originalNumber;
@JsonProperty
private String normalizedNumber;
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.push.exceptions
import org.signal.network.exceptions.MalformedResponseException
import org.signal.network.exceptions.NonSuccessfulResponseCodeException
import org.signal.network.util.JsonUtil
/**
* Response indicating we gave the server a non-normalized phone number. The expected normalized version of the number is provided.
*/
class NonNormalizedPhoneNumberException(
val originalNumber: String,
val normalizedNumber: String
) : NonSuccessfulResponseCodeException(400) {
/** The 400 response body. Separate from the exception itself so that it can be deserialized. */
class Body(
val originalNumber: String? = null,
val normalizedNumber: String? = null
)
companion object {
@JvmStatic
@Throws(MalformedResponseException::class)
fun forResponse(responseBody: String): NonNormalizedPhoneNumberException {
val body = JsonUtil.fromJsonResponse(responseBody, Body::class.java)
if (body.originalNumber == null || body.normalizedNumber == null) {
throw MalformedResponseException("Response is missing a number")
}
return NonNormalizedPhoneNumberException(body.originalNumber, body.normalizedNumber)
}
}
}
@@ -1,27 +0,0 @@
package org.whispersystems.signalservice.api.storage;
import com.fasterxml.jackson.annotation.JsonProperty;
public class StorageAuthResponse {
@JsonProperty
private String username;
@JsonProperty
private String password;
public StorageAuthResponse() { }
public StorageAuthResponse(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
@@ -0,0 +1,11 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.storage
data class StorageAuthResponse(
val username: String,
val password: String
)
@@ -1,399 +0,0 @@
package org.whispersystems.signalservice.api.subscriptions;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
public final class ActiveSubscription {
public static final String PAYMENT_METHOD_SEPA_DEBIT = "SEPA_DEBIT";
public static final ActiveSubscription EMPTY = new ActiveSubscription(null, null);
public enum Processor {
STRIPE("STRIPE"),
BRAINTREE("BRAINTREE"),
GOOGLE_PLAY_BILLING("GOOGLE_PLAY_BILLING");
private final String code;
Processor(String code) {
this.code = code;
}
public String getCode() {
return code;
}
static Processor fromCode(String code) {
for (Processor value : Processor.values()) {
if (value.code.equals(code)) {
return value;
}
}
return STRIPE;
}
}
/**
* As per API documentation
*/
public enum PaymentMethod {
UNKNOWN("UNKNOWN"),
CARD("CARD"),
PAYPAL("PAYPAL"),
SEPA_DEBIT("SEPA_DEBIT"),
IDEAL("IDEAL"),
GOOGLE_PLAY_BILLING("GOOGLE_PLAY_BILLING"),
APPLE_APP_STORE("APPLE_APP_STORE");
private String code;
PaymentMethod(String code) {
this.code = code;
}
static PaymentMethod fromCode(String code) {
for (PaymentMethod method : PaymentMethod.values()) {
if (Objects.equals(method.code, code)) {
return method;
}
}
return PaymentMethod.UNKNOWN;
}
}
public enum Status {
/**
* The subscription is currently in a trial period and it's safe to provision your product for your customer.
* The subscription transitions automatically to active when the first payment is made.
*/
TRIALING("trialing"),
/**
* The subscription is in good standing and the most recent payment was successful. It's safe to provision your product for your customer.
*/
ACTIVE("active"),
/**
* Payment failed when you created the subscription. A successful payment needs to be made within 23 hours to activate the subscription.
*/
INCOMPLETE("incomplete"),
/**
* The initial payment on the subscription failed and no successful payment was made within 23 hours of creating the subscription.
* These subscriptions don't bill customers. This status exists so you can track customers that failed to activate their subscriptions.
*/
INCOMPLETE_EXPIRED("incomplete_expired"),
/**
* Payment on the latest invoice either failed or wasn't attempted.
*/
PAST_DUE("past_due"),
/**
* The subscription has been canceled. During cancellation, automatic collection for all unpaid invoices is disabled (auto_advance=false).
*/
CANCELED("canceled"),
/**
* The latest invoice hasn't been paid but the subscription remains in place.
* The latest invoice remains open and invoices continue to be generated but payments aren't attempted.
*/
UNPAID("unpaid");
private final String status;
private static final Set<Status> FAILURE_STATUSES = new HashSet<>(Arrays.asList(
INCOMPLETE_EXPIRED,
PAST_DUE,
UNPAID
));
Status(String status) {
this.status = status;
}
public static Status getStatus(String status) {
for (Status s : Status.values()) {
if (Objects.equals(status, s.status)) {
return s;
}
}
throw new IllegalArgumentException("Unknown status " + status);
}
static boolean isPaymentFailed(String status) {
return FAILURE_STATUSES.contains(getStatus(status));
}
}
private final Subscription activeSubscription;
private final ChargeFailure chargeFailure;
@JsonCreator
public ActiveSubscription(@JsonProperty("subscription") Subscription activeSubscription,
@JsonProperty("chargeFailure") ChargeFailure chargeFailure)
{
this.activeSubscription = activeSubscription;
this.chargeFailure = chargeFailure;
}
public Subscription getActiveSubscription() {
return activeSubscription;
}
public ChargeFailure getChargeFailure() {
return chargeFailure;
}
public boolean isActive() {
return activeSubscription != null && activeSubscription.isActive();
}
public boolean isPendingBankTransfer() {
return activeSubscription != null && Objects.equals(activeSubscription.paymentMethod, PAYMENT_METHOD_SEPA_DEBIT) && activeSubscription.paymentPending;
}
public boolean isInProgress() {
return activeSubscription != null && !isActive() && (!isFailedPayment() || isPastDue()) && !isCanceled();
}
public boolean isPastDue() {
return activeSubscription != null && activeSubscription.isPastDue();
}
public boolean isFailedPayment() {
return chargeFailure != null || (activeSubscription != null && !isActive() && activeSubscription.isFailedPayment());
}
public boolean isCanceled() {
return activeSubscription != null && activeSubscription.isCanceled();
}
/**
* Backups-specific call that gives us a value that should align with autoRenew from the GPB payment.
*/
public boolean willCancelAtPeriodEnd() {
return activeSubscription == null || activeSubscription.willCancelAtPeriodEnd;
}
public static final class Subscription {
private final int level;
private final String currency;
private final BigDecimal amount;
private final long endOfCurrentPeriod;
private final boolean isActive;
private final long billingCycleAnchor;
private final boolean willCancelAtPeriodEnd;
private final String status;
private final Processor processor;
private final PaymentMethod paymentMethod;
private final boolean paymentPending;
@JsonCreator
public Subscription(@JsonProperty("level") int level,
@JsonProperty("currency") String currency,
@JsonProperty("amount") BigDecimal amount,
@JsonProperty("endOfCurrentPeriod") long endOfCurrentPeriod,
@JsonProperty("active") boolean isActive,
@JsonProperty("billingCycleAnchor") long billingCycleAnchor,
@JsonProperty("cancelAtPeriodEnd") boolean willCancelAtPeriodEnd,
@JsonProperty("status") String status,
@JsonProperty("processor") String processor,
@JsonProperty("paymentMethod") String paymentMethod,
@JsonProperty("paymentPending") boolean paymentPending)
{
this.level = level;
this.currency = currency;
this.amount = amount;
this.endOfCurrentPeriod = endOfCurrentPeriod;
this.isActive = isActive;
this.billingCycleAnchor = billingCycleAnchor;
this.willCancelAtPeriodEnd = willCancelAtPeriodEnd;
this.status = status;
this.processor = Processor.fromCode(processor);
this.paymentMethod = PaymentMethod.fromCode(paymentMethod);
this.paymentPending = paymentPending;
}
public int getLevel() {
return level;
}
public String getCurrency() {
return currency;
}
public BigDecimal getAmount() {
return amount;
}
/**
* UNIX Epoch Timestamp in seconds, can be used to calculate next billing date per
* https://stripe.com/docs/billing/subscriptions/billing-cycle
*/
public long getBillingCycleAnchor() {
return billingCycleAnchor;
}
/**
* Whether this subscription is currently active.
*/
public boolean isActive() {
return isActive;
}
/**
* UNIX Epoch Timestamp in seconds
*/
public long getEndOfCurrentPeriod() {
return endOfCurrentPeriod;
}
/**
* Whether this subscription is set to end at the end of the current period.
*/
public boolean willCancelAtPeriodEnd() {
return willCancelAtPeriodEnd;
}
/**
* The Stripe status of this subscription (see https://stripe.com/docs/billing/subscriptions/overview#subscription-statuses)
*/
public String getStatus() {
return status;
}
public Processor getProcessor() {
return processor;
}
public PaymentMethod getPaymentMethod() {
return paymentMethod;
}
/**
* @return Whether the latest invoice for the subscription is in a non-terminal state
*/
public boolean isPaymentPending() {
return paymentPending;
}
public boolean isFailedPayment() {
return Status.isPaymentFailed(getStatus());
}
public boolean isPastDue() {
return Status.getStatus(getStatus()) == Status.PAST_DUE;
}
public boolean isCanceled() {
return Status.getStatus(getStatus()) == Status.CANCELED;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Subscription that = (Subscription) o;
return level == that.level && endOfCurrentPeriod == that.endOfCurrentPeriod && isActive == that.isActive && billingCycleAnchor == that.billingCycleAnchor && willCancelAtPeriodEnd == that.willCancelAtPeriodEnd && currency
.equals(that.currency) && amount.equals(that.amount) && status.equals(that.status) && Objects.equals(paymentMethod, that.paymentMethod) && paymentPending == that.paymentPending;
}
@Override
public int hashCode() {
return Objects.hash(level, currency, amount, endOfCurrentPeriod, isActive, billingCycleAnchor, willCancelAtPeriodEnd, status, paymentMethod, paymentPending);
}
}
public static final class ChargeFailure {
private final String code;
private final String message;
private final String outcomeNetworkStatus;
private final String outcomeNetworkReason;
private final String outcomeType;
@JsonCreator
public ChargeFailure(@JsonProperty("code") String code,
@JsonProperty("message") String message,
@JsonProperty("outcomeNetworkStatus") String outcomeNetworkStatus,
@JsonProperty("outcomeNetworkReason") String outcomeNetworkReason,
@JsonProperty("outcomeType") String outcomeType)
{
this.code = code;
this.message = message;
this.outcomeNetworkStatus = outcomeNetworkStatus;
this.outcomeNetworkReason = outcomeNetworkReason;
this.outcomeType = outcomeType;
}
/**
* Error code explaining reason for charge failure if available (see the errors section for a list of codes).
* <p>
* See: <a href="https://stripe.com/docs/api/charges/object#charge_object-failure_code">https://stripe.com/docs/api/charges/object#charge_object-failure_code</a>
*/
public String getCode() {
return code;
}
/**
* Message to user further explaining reason for charge failure if available.
* <p>
* See: <a href="https://stripe.com/docs/api/charges/object#charge_object-failure_message">https://stripe.com/docs/api/charges/object#charge_object-failure_message</a>
*/
public String getMessage() {
return message;
}
/**
* Possible values are approved_by_network, declined_by_network, not_sent_to_network, and reversed_after_approval.
* The value reversed_after_approval indicates the payment was blocked by Stripe after bank authorization,
* and may temporarily appear as "pending" on a cardholder's statement.
* <p>
* See: <a href="https://stripe.com/docs/api/charges/object#charge_object-outcome-network_status">https://stripe.com/docs/api/charges/object#charge_object-outcome-network_status</a>
*/
public String getOutcomeNetworkStatus() {
return outcomeNetworkStatus;
}
/**
* An enumerated value providing a more detailed explanation of the outcome's type. Charges blocked by Radar's default block rule have the value
* highest_risk_level. Charges placed in review by Radar's default review rule have the value elevated_risk_level. Charges authorized, blocked, or placed
* in review by custom rules have the value rule. See understanding declines for more details.
* <p>
* See: <a href="https://stripe.com/docs/api/charges/object#charge_object-outcome-reason">https://stripe.com/docs/api/charges/object#charge_object-outcome-reason</a>
*/
public @Nullable String getOutcomeNetworkReason() {
return outcomeNetworkReason;
}
/**
* Possible values are authorized, manual_review, issuer_declined, blocked, and invalid. See understanding declines and Radar reviews for details.
* <p>
* See: <a href="https://stripe.com/docs/api/charges/object#charge_object-outcome-type">https://stripe.com/docs/api/charges/object#charge_object-outcome-type</a>
*/
public String getOutcomeType() {
return outcomeType;
}
@Override public String toString() {
return "ChargeFailure{" +
"code='" + code + '\'' +
", outcomeNetworkStatus='" + outcomeNetworkStatus + '\'' +
", outcomeNetworkReason='" + outcomeNetworkReason + '\'' +
", outcomeType='" + outcomeType + '\'' +
'}';
}
}
}
@@ -0,0 +1,207 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.subscriptions
import com.fasterxml.jackson.annotation.JsonProperty
import java.math.BigDecimal
class ActiveSubscription(
@JsonProperty("subscription") val activeSubscription: Subscription? = null,
val chargeFailure: ChargeFailure? = null
) {
val isActive: Boolean
get() = activeSubscription != null && activeSubscription.isActive
val isPendingBankTransfer: Boolean
get() = activeSubscription != null && activeSubscription.paymentMethod == PaymentMethod.SEPA_DEBIT && activeSubscription.paymentPending
val isInProgress: Boolean
get() = activeSubscription != null && !isActive && (!isFailedPayment || isPastDue) && !isCanceled
val isPastDue: Boolean
get() = activeSubscription != null && activeSubscription.isPastDue
val isFailedPayment: Boolean
get() = chargeFailure != null || (activeSubscription != null && !isActive && activeSubscription.isFailedPayment)
val isCanceled: Boolean
get() = activeSubscription != null && activeSubscription.isCanceled
/**
* Backups-specific call that gives us a value that should align with autoRenew from the GPB payment.
*/
val willCancelAtPeriodEnd: Boolean
get() = activeSubscription == null || activeSubscription.willCancelAtPeriodEnd
enum class Processor(val code: String) {
STRIPE("STRIPE"),
BRAINTREE("BRAINTREE"),
GOOGLE_PLAY_BILLING("GOOGLE_PLAY_BILLING");
companion object {
fun fromCode(code: String?): Processor = entries.firstOrNull { it.code == code } ?: STRIPE
}
}
/**
* As per API documentation
*/
enum class PaymentMethod(val code: String) {
UNKNOWN("UNKNOWN"),
CARD("CARD"),
PAYPAL("PAYPAL"),
SEPA_DEBIT("SEPA_DEBIT"),
IDEAL("IDEAL"),
GOOGLE_PLAY_BILLING("GOOGLE_PLAY_BILLING"),
APPLE_APP_STORE("APPLE_APP_STORE");
companion object {
fun fromCode(code: String?): PaymentMethod = entries.firstOrNull { it.code == code } ?: UNKNOWN
}
}
enum class Status(val code: String) {
/**
* The subscription is currently in a trial period and it's safe to provision your product for your customer.
* The subscription transitions automatically to active when the first payment is made.
*/
TRIALING("trialing"),
/**
* The subscription is in good standing and the most recent payment was successful. It's safe to provision your product for your customer.
*/
ACTIVE("active"),
/**
* Payment failed when you created the subscription. A successful payment needs to be made within 23 hours to activate the subscription.
*/
INCOMPLETE("incomplete"),
/**
* The initial payment on the subscription failed and no successful payment was made within 23 hours of creating the subscription.
* These subscriptions don't bill customers. This status exists so you can track customers that failed to activate their subscriptions.
*/
INCOMPLETE_EXPIRED("incomplete_expired"),
/**
* Payment on the latest invoice either failed or wasn't attempted.
*/
PAST_DUE("past_due"),
/**
* The subscription has been canceled. During cancellation, automatic collection for all unpaid invoices is disabled (auto_advance=false).
*/
CANCELED("canceled"),
/**
* The latest invoice hasn't been paid but the subscription remains in place.
* The latest invoice remains open and invoices continue to be generated but payments aren't attempted.
*/
UNPAID("unpaid");
companion object {
private val FAILURE_STATUSES = setOf(INCOMPLETE_EXPIRED, PAST_DUE, UNPAID)
@JvmStatic
fun getStatus(status: String?): Status {
return entries.firstOrNull { it.code == status } ?: throw IllegalArgumentException("Unknown status $status")
}
internal fun isPaymentFailed(status: String?): Boolean = FAILURE_STATUSES.contains(getStatus(status))
}
}
data class Subscription(
val level: Int = 0,
val currency: String = "",
val amount: BigDecimal = BigDecimal.ZERO,
/** UNIX Epoch Timestamp in seconds */
val endOfCurrentPeriod: Long = 0,
/** Whether this subscription is currently active. */
@JsonProperty("active") val isActive: Boolean = false,
/**
* UNIX Epoch Timestamp in seconds, can be used to calculate next billing date per
* https://stripe.com/docs/billing/subscriptions/billing-cycle
*/
val billingCycleAnchor: Long = 0,
/** Whether this subscription is set to end at the end of the current period. */
@JsonProperty("cancelAtPeriodEnd") val willCancelAtPeriodEnd: Boolean = false,
/** The Stripe status of this subscription (see https://stripe.com/docs/billing/subscriptions/overview#subscription-statuses) */
val status: String = "",
@JsonProperty("processor") private val processorCode: String? = null,
@JsonProperty("paymentMethod") private val paymentMethodCode: String? = null,
/** Whether the latest invoice for the subscription is in a non-terminal state. */
val paymentPending: Boolean = false
) {
val processor: Processor
get() = Processor.fromCode(processorCode)
val paymentMethod: PaymentMethod
get() = PaymentMethod.fromCode(paymentMethodCode)
val isFailedPayment: Boolean
get() = Status.isPaymentFailed(status)
val isPastDue: Boolean
get() = Status.getStatus(status) == Status.PAST_DUE
val isCanceled: Boolean
get() = Status.getStatus(status) == Status.CANCELED
}
data class ChargeFailure(
/**
* Error code explaining reason for charge failure if available (see the errors section for a list of codes).
*
* See: [https://stripe.com/docs/api/charges/object#charge_object-failure_code]
*/
val code: String? = null,
/**
* Message to user further explaining reason for charge failure if available.
*
* See: [https://stripe.com/docs/api/charges/object#charge_object-failure_message]
*/
val message: String? = null,
/**
* Possible values are approved_by_network, declined_by_network, not_sent_to_network, and reversed_after_approval.
* The value reversed_after_approval indicates the payment was blocked by Stripe after bank authorization,
* and may temporarily appear as "pending" on a cardholder's statement.
*
* See: [https://stripe.com/docs/api/charges/object#charge_object-outcome-network_status]
*/
val outcomeNetworkStatus: String? = null,
/**
* An enumerated value providing a more detailed explanation of the outcome's type. Charges blocked by Radar's default block rule have the value
* highest_risk_level. Charges placed in review by Radar's default review rule have the value elevated_risk_level. Charges authorized, blocked, or placed
* in review by custom rules have the value rule. See understanding declines for more details.
*
* See: [https://stripe.com/docs/api/charges/object#charge_object-outcome-reason]
*/
val outcomeNetworkReason: String? = null,
/**
* Possible values are authorized, manual_review, issuer_declined, blocked, and invalid. See understanding declines and Radar reviews for details.
*
* See: [https://stripe.com/docs/api/charges/object#charge_object-outcome-type]
*/
val outcomeType: String? = null
) {
override fun toString(): String {
return "ChargeFailure{code='$code', outcomeNetworkStatus='$outcomeNetworkStatus', outcomeNetworkReason='$outcomeNetworkReason', outcomeType='$outcomeType'}"
}
}
companion object {
const val PAYMENT_METHOD_SEPA_DEBIT = "SEPA_DEBIT"
@JvmField
val EMPTY = ActiveSubscription(null, null)
}
}
@@ -1,21 +0,0 @@
package org.whispersystems.signalservice.api.subscriptions;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Response object from creating a payment intent via PayPal
*/
public class PayPalConfirmPaymentIntentResponse {
private final String paymentId;
@JsonCreator
public PayPalConfirmPaymentIntentResponse(@JsonProperty("paymentId") String paymentId) {
this.paymentId = paymentId;
}
public String getPaymentId() {
return paymentId;
}
}
@@ -0,0 +1,13 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.subscriptions
/**
* Response object from confirming a payment intent via PayPal
*/
data class PayPalConfirmPaymentIntentResponse(
val paymentId: String
)
@@ -1,27 +0,0 @@
package org.whispersystems.signalservice.api.subscriptions;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Response object from creating a payment intent via PayPal
*/
public class PayPalCreatePaymentIntentResponse {
private final String approvalUrl;
private final String paymentId;
@JsonCreator
public PayPalCreatePaymentIntentResponse(@JsonProperty("approvalUrl") String approvalUrl, @JsonProperty("paymentId") String paymentId) {
this.approvalUrl = approvalUrl;
this.paymentId = paymentId;
}
public String getApprovalUrl() {
return approvalUrl;
}
public String getPaymentId() {
return paymentId;
}
}
@@ -0,0 +1,14 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.subscriptions
/**
* Response object from creating a payment intent via PayPal
*/
data class PayPalCreatePaymentIntentResponse(
val approvalUrl: String,
val paymentId: String
)
@@ -1,23 +0,0 @@
package org.whispersystems.signalservice.api.subscriptions;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class PayPalCreatePaymentMethodResponse {
private final String approvalUrl;
private final String token;
@JsonCreator
public PayPalCreatePaymentMethodResponse(@JsonProperty("approvalUrl") String approvalUrl, @JsonProperty("token") String token) {
this.approvalUrl = approvalUrl;
this.token = token;
}
public String getApprovalUrl() {
return approvalUrl;
}
public String getToken() {
return token;
}
}
@@ -0,0 +1,14 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.subscriptions
/**
* Response object from creating a payment method via PayPal
*/
data class PayPalCreatePaymentMethodResponse(
val approvalUrl: String,
val token: String
)
@@ -1,24 +0,0 @@
package org.whispersystems.signalservice.api.subscriptions;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public final class StripeClientSecret {
private final String id;
private final String clientSecret;
@JsonCreator
public StripeClientSecret(@JsonProperty("clientSecret") String clientSecret) {
this.id = clientSecret.replaceFirst("_secret.*", "");
this.clientSecret = clientSecret;
}
public String getId() {
return id;
}
public String getClientSecret() {
return clientSecret;
}
}
@@ -0,0 +1,13 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.subscriptions
data class StripeClientSecret(
val clientSecret: String
) {
val id: String
get() = clientSecret.replaceFirst("_secret.*".toRegex(), "")
}
@@ -1,34 +0,0 @@
package org.whispersystems.signalservice.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import okhttp3.Credentials;
public class AuthCredentials {
@JsonProperty
private String username;
@JsonProperty
private String password;
public static AuthCredentials create(String username, String password) {
AuthCredentials authCredentials = new AuthCredentials();
authCredentials.username = username;
authCredentials.password = password;
return authCredentials;
}
public String asBasic() {
return Credentials.basic(username, password);
}
public String username() { return username; }
public String password() { return password; }
@Override
public String toString() {
return "AuthCredentials(xxx)";
}
}
@@ -0,0 +1,27 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.internal.push
import com.fasterxml.jackson.annotation.JsonProperty
import okhttp3.Credentials
class AuthCredentials(
@field:JsonProperty private val username: String,
@field:JsonProperty private val password: String
) {
fun asBasic(): String = Credentials.basic(username, password)
fun username(): String = username
fun password(): String = password
override fun toString(): String = "AuthCredentials(xxx)"
companion object {
@JvmStatic
fun create(username: String, password: String): AuthCredentials = AuthCredentials(username, password)
}
}
@@ -1,20 +0,0 @@
package org.whispersystems.signalservice.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class CdsiAuthResponse {
@JsonProperty
private String username;
@JsonProperty
private String password;
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
@@ -0,0 +1,11 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.internal.push
data class CdsiAuthResponse(
val username: String,
val password: String
)
@@ -1,20 +0,0 @@
package org.whispersystems.signalservice.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class ConfirmUsernameRequest {
@JsonProperty
private String usernameHash;
@JsonProperty
private String zkProof;
@JsonProperty
private String encryptedUsername;
public ConfirmUsernameRequest(String usernameHash, String zkProof, String encryptedUsername) {
this.usernameHash = usernameHash;
this.zkProof = zkProof;
this.encryptedUsername = encryptedUsername;
}
}
@@ -0,0 +1,12 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.internal.push
class ConfirmUsernameRequest(
val usernameHash: String,
val zkProof: String,
val encryptedUsername: String
)
@@ -1,19 +0,0 @@
package org.whispersystems.signalservice.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
import java.util.List;
public class DeviceInfoList {
@JsonProperty
public List<DeviceInfo> devices;
public DeviceInfoList() {}
public List<DeviceInfo> getDevices() {
return devices;
}
}
@@ -0,0 +1,12 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.internal.push
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo
class DeviceInfoList(
val devices: List<DeviceInfo> = emptyList()
)
@@ -1,20 +0,0 @@
package org.whispersystems.signalservice.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class DeviceLimit {
@JsonProperty
private int current;
@JsonProperty
private int max;
public int getCurrent() {
return current;
}
public int getMax() {
return max;
}
}
@@ -0,0 +1,11 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.internal.push
data class DeviceLimit(
val current: Int = 0,
val max: Int = 0
)
@@ -1,24 +0,0 @@
package org.whispersystems.signalservice.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class DonationIntentResult {
@JsonProperty("id")
private String id;
@JsonProperty("client_secret")
private String clientSecret;
public DonationIntentResult(@JsonProperty("id") String id, @JsonProperty("client_secret") String clientSecret) {
this.id = id;
this.clientSecret = clientSecret;
}
public String getId() {
return id;
}
public String getClientSecret() {
return clientSecret;
}
}
@@ -0,0 +1,13 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.internal.push
import com.fasterxml.jackson.annotation.JsonProperty
data class DonationIntentResult(
val id: String,
@JsonProperty("client_secret") val clientSecret: String
)
@@ -1,24 +0,0 @@
package org.whispersystems.signalservice.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Represents the body of a 409 response from the service during a sender key send.
*/
public class GroupMismatchedDevices {
@JsonProperty
private String uuid;
@JsonProperty
private MismatchedDevices devices;
public GroupMismatchedDevices() {}
public String getUuid() {
return uuid;
}
public MismatchedDevices getDevices() {
return devices;
}
}
@@ -0,0 +1,14 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.internal.push
/**
* Represents the body of a 409 response from the service during a sender key send.
*/
data class GroupMismatchedDevices(
val uuid: String,
val devices: MismatchedDevices
)
@@ -1,23 +0,0 @@
package org.whispersystems.signalservice.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Represents the body of a 410 response from the service during a sender key send.
*/
public class GroupStaleDevices {
@JsonProperty
private String uuid;
@JsonProperty
private StaleDevices devices;
public String getUuid() {
return uuid;
}
public StaleDevices getDevices() {
return devices;
}
}

Some files were not shown because too many files have changed in this diff Show More