core: renew badges monthly and alert when support ends (#7448)

This commit is contained in:
spaced4ndy authored and GitHub committed 2026-09-09 14:31:05 +00:00
1 parent c623f4284c
commit f9bab17643
30 files changed
+2901 -220

No files matched your search

+22
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, LocalBadge)
import Simplex.Chat.Badges.Types (BadgeAlert (..), BadgeAlertKind, BadgeState (..))
import Simplex.Messaging.Crypto.BBS (BBSPublicKey)
import Simplex.Messaging.Crypto.File (CryptoFile (..))
import qualified Simplex.Messaging.Crypto.File as CF
@@ -92,6 +93,7 @@ import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), NtfTknStatus)
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, parseAll, parseString, sumTypeJSON)
import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), MsgId, NMsgMeta (..), NtfServer, ProtocolType (..), QueueId, SMPMsgMeta (..), SubscriptionMode (..), XFTPServer)
import Simplex.Messaging.Session (SessionVar)
import Simplex.Messaging.TMap (TMap)
import Simplex.Messaging.Transport (TLS, TransportPeer (..), simplexMQVersion)
import Simplex.Messaging.Transport.Client (SocksProxyWithAuth, TransportHost)
@@ -145,6 +147,10 @@ data ChatConfig = ChatConfig
badgePublicKeys :: Map Int BBSPublicKey,
-- Nothing until the badge service is deployed
badgeServiceAddress :: Maybe (ConnectTarget 'CMContact),
-- the only clock badge code reads, so tests can shift it; production arithmetic is unchanged
badgeCurrentTime :: IO UTCTime,
-- how long a badge worker waits before repeating a renewal that failed for a passing reason
badgeRetryInterval :: RetryInterval,
confirmMigrations :: MigrationConfirmation,
presetServers :: PresetServers,
shortLinkPresetServers :: NonEmpty SMPServer,
@@ -278,6 +284,12 @@ defaultInlineFilesConfig =
data ChatDatabase = ChatDatabase {chatStore :: DBStore, agentStore :: DBStore}
-- | Signalling badgeWork wakes the worker from its own wait; a full var means it has work to do.
data BadgeWorker = BadgeWorker
{ badgeWorkerAsync :: Async (),
badgeWork :: TMVar ()
}
data ChatController = ChatController
{ currentUser :: TVar (Maybe User),
randomPresetServers :: NonEmpty PresetOperator,
@@ -310,6 +322,9 @@ data ChatController = ChatController
deliveryTaskWorkers :: TMap DeliveryWorkerKey Worker,
deliveryJobWorkers :: TMap DeliveryWorkerKey Worker,
relayRequestWorkers :: TMap Int Worker, -- single global worker with key 1 is used to fit into existing worker management framework
-- one badge worker per user: badge state is per profile, and one profile must not stall another
badgeWorkers :: TMap UserId (SessionVar BadgeWorker),
badgeSeq :: TVar Int,
relayGroupLinkChecksAsync :: TVar (Maybe (Async ())),
webPreviewState :: Maybe WebPreviewState,
chatRelayTests :: TMap ConnId RelayTest,
@@ -641,6 +656,10 @@ data ChatCommand
| UpdateProfileImageFromFile FilePath -- set profile image from a .png/.jpg/.jpeg file
| AddBadge BadgeCredential -- attach an issued badge credential (testing; credential from `simplex-chat badge sign`)
| APIRedeemBadgeCode {userId :: UserId, code :: Text} -- redeem a badge code with the configured badge service
| APIGetBadgeState {userId :: UserId} -- the user's badges, their balances and any current alert
-- episode is last because it is free text: it is the value that makes one occurrence of an
-- alert distinct from the next, and the app returns whatever it was given
| APIAckBadgeAlert {userId :: UserId, badgePurchaseId :: Int64, alertKind :: BadgeAlertKind, snooze :: Bool, episode :: Text}
| ShowProfileImage
| SetUserFeature AChatFeature FeatureAllowed -- UserId (not used in UI)
| SetContactFeature AChatFeature ContactName (Maybe FeatureAllowed)
@@ -846,6 +865,7 @@ data ChatResponse
| CRServiceResponse {user :: User, responseData :: J.Object}
| CRServiceReplyAccepted {user :: User, connectionId :: AgentConnId}
| CRBadgeRedeemed {user :: User, redeemedBadge :: LocalBadge, newBadge :: Bool}
| 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}
| CRGroupsList {user :: User, groups :: [GroupInfo]}
@@ -963,6 +983,8 @@ data ChatEvent
| CEvtReceivedContactRequest {user :: User, contactRequest :: UserContactRequest, chat_ :: Maybe AChat}
| CEvtServiceRequest {user :: User, requestId :: AgentInvId, signerKey :: Maybe C.PublicKeyEd25519, requestData :: J.Object}
| CEvtServiceReplySent {connectionId :: AgentConnId}
| CEvtBadgeChanged {user :: User, badgeState :: Maybe BadgeState} -- badge state changed, including a renewal that arrived without a command
| CEvtBadgeAlert {user :: User, badgeAlert :: BadgeAlert}
| CEvtContactRequestRejected {user :: User, contact :: Contact, rejectionReason :: Maybe ContactRejectionReason}
| CEvtAcceptingContactRequest {user :: User, contact :: Contact} -- there is the same command response
| CEvtAcceptingBusinessRequest {user :: User, groupInfo :: GroupInfo}