core, ui: badge redeem errors and fixes (#7515)

- redeem errors are typed (CEBadgeRedeemError) instead of matched by text in the apps
- service timeout (A_SERVICE) decodes in the apps and offers Retry via the existing retry alert
- unexpected redeem errors show the error itself instead of a generic message
- service error codes are a typed enum in the apps (BadgeServiceErrorCode)
- CRBadgeRedeemed returns badge state, so the apps skip a second round-trip after redeem
- setBadgeAlertAcked is scoped to user_id
- badgeChanged updates non-active profiles, so other profiles' badges don't go stale
- pitch banner is not shown to a profile that already has a badge
- one badgeTypeName per platform, used by the badge screen and the badge info alert
- BadgeAlertKind and BadgeAlertPrice decode via standard JSON, no custom decoders
- dead "Support ended" title branch removed from Your Badge view
- kotlin: users from badge responses carry remoteHostId
- kotlin: redeem code field keeps the IME's cursor and composition state
- kotlin: "Get your code" shown in all flavours
- kotlin: "Don't show again" -> "Dismiss", matching iOS
- kotlin: parseBadgeCode moved next to its FFI in platform/Core.kt
- kotlin: BadgesView no longer cross-fades on badge state updates
- kotlin: section title not uppercased
- ios: redeem code field parses once per change
- ios: A_SERVICE rejected reason is not decoded
- CLI: "cannot redeem badge code: ..." with the source of the error
- comments clarified
This commit is contained in:
spaced4ndy
2026-09-16 14:15:11 +00:00
committed by GitHub
parent 9af6fb00e6
commit f38c7e0f2b
26 changed files with 482 additions and 434 deletions
+15 -1
View File
@@ -84,6 +84,7 @@ import qualified Simplex.Messaging.Agent.Store.DB as DB
import Simplex.Messaging.Client (HostMode (..), SMPProxyFallback (..), SMPProxyMode (..), SMPWebPortServers (..), SocksMode (..))
import qualified Simplex.Messaging.Crypto as C
import Simplex.Chat.Badges (BadgeCredential, FileSizeLimits, LocalBadge)
import Simplex.Chat.Badges.Service (BadgeServiceErrorCode)
import Simplex.Chat.Badges.Types (BadgeAlert (..), BadgeAlertKind, BadgeState (..))
import Simplex.Messaging.Crypto.BBS (BBSPublicKey)
import Simplex.Messaging.Crypto.File (CryptoFile (..))
@@ -866,7 +867,7 @@ data ChatResponse
| CRContactRequestRejected {user :: User, contactRequest :: UserContactRequest, contact_ :: Maybe Contact}
| CRServiceResponse {user :: User, responseData :: J.Object}
| CRServiceReplyAccepted {user :: User, connectionId :: AgentConnId}
| CRBadgeRedeemed {user :: User, redeemedBadge :: LocalBadge, newBadge :: Bool}
| CRBadgeRedeemed {user :: User, redeemedBadge :: LocalBadge, newBadge :: Bool, badgeState :: Maybe BadgeState}
| CRBadgeState {user :: User, badgeState :: Maybe BadgeState}
| CRUserAcceptedGroupSent {user :: User, groupInfo :: GroupInfo, hostContact :: Maybe Contact}
| CRUserDeletedMembers {user :: User, groupInfo :: GroupInfo, members :: [GroupMember], withMessages :: Bool, msgSigned :: Bool}
@@ -1476,6 +1477,16 @@ data SimplexDomainError
| SDEUnknownDomain -- the resolved link's profile has no name, or a different name
deriving (Eq, Show)
data BadgeRedeemError
= BREInvalidCode -- format or check character
| BREServiceNotConfigured
| BREBadgeActive
| BREServiceError {serviceError :: BadgeServiceErrorCode}
| BREInvalidResponse {message :: String}
| BREUnknownKeyIndex
| BRECredentialNotVerified
deriving (Eq, Show)
data ChatErrorType
= CENoActiveUser
| CENoConnectionUser {agentConnId :: AgentConnId}
@@ -1548,6 +1559,7 @@ data ChatErrorType
| CEAgentVersion
| CEAgentNoSubResult {agentConnId :: AgentConnId}
| CECommandError {message :: String}
| CEBadgeRedeemError {badgeRedeemError :: BadgeRedeemError}
| CEServerProtocol {serverProtocol :: AProtocolType}
| CEAgentCommandError {message :: String}
| CEInvalidFileDescription {message :: String}
@@ -1832,6 +1844,8 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "FC") ''ForwardConfirmation)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "SDE") ''SimplexDomainError)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "BRE") ''BadgeRedeemError)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CE") ''ChatErrorType)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "RHE") ''RemoteHostError)