core: support contact addresses with DR keys, service requests (#7310)

* core: use double ratchet keys in contact address (#7278)

* core: use double ratchet keys in contact address

* use PQ from the first message

* query plans

* update simplexmq

* api to rotate keys, option to show full links in CLI

* shorter description

* ui: add error parameters

* disable DR in addresses

* core: parameter for create address command to configure ratchet keys

* add pqRatchet param to address-related commands

* query plan

* fix parser

* fix kotlin

---------

Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>

* core: contact request rejection and service requests (#7292)

* update simplexmq

* implement service requests and rejections

* tests

* migration

* fix migration

* add api event and response

* bot api, postgres migration

* nix shas

* bot types, rename property

* update bot type

* sign service requests

* update bots api

* query plan

* update plan

* update simplexmq

* fix test, update bot api

* fix bot api

* resolve name for service request

* refactor

---------

Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>

* update simplexmq

* update simplexmq

* test delays

---------

Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
This commit is contained in:
Evgeny
2026-08-01 12:51:03 +01:00
committed by GitHub
co-authored by Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
parent 61012d208e
commit e1a349b90f
55 changed files with 1229 additions and 248 deletions
@@ -13,10 +13,11 @@ from . import _responses as CR
# Network usage: interactive.
class APICreateMyAddress(TypedDict):
userId: int # int64
pqRatchet: NotRequired[bool]
def APICreateMyAddress_cmd_string(self: APICreateMyAddress) -> str:
return '/_address ' + str(self['userId'])
return '/_address ' + str(self['userId']) + ((' pq_ratchet=' + ('on' if self.get('pqRatchet') else 'off')) if self.get('pqRatchet') is not None else '')
APICreateMyAddress_Response = CR.UserContactLinkCreated | CR.ChatCmdError
@@ -62,11 +63,12 @@ APISetProfileAddress_Response = CR.UserProfileUpdated | CR.ChatCmdError
# Network usage: interactive.
class APISetAddressSettings(TypedDict):
userId: int # int64
pqRatchet: NotRequired[bool]
settings: "T.AddressSettings"
def APISetAddressSettings_cmd_string(self: APISetAddressSettings) -> str:
return '/_address_settings ' + str(self['userId']) + ' ' + json.dumps(self['settings'])
return '/_address_settings ' + str(self['userId']) + ((' pq_ratchet=' + ('on' if self.get('pqRatchet') else 'off')) if self.get('pqRatchet') is not None else '') + ' ' + json.dumps(self['settings'])
APISetAddressSettings_Response = CR.UserContactLinkUpdated | CR.ChatCmdError
@@ -493,6 +495,7 @@ APIAcceptContact_Response = CR.AcceptingContactRequest | CR.ChatCmdError
# Network usage: no.
class APIRejectContact(TypedDict):
contactReqId: int # int64
notify: bool
def APIRejectContact_cmd_string(self: APIRejectContact) -> str:
@@ -689,6 +692,23 @@ def APISetContactPrefs_cmd_string(self: APISetContactPrefs) -> str:
APISetContactPrefs_Response = CR.ContactPrefsUpdated | CR.ChatCmdError
# Service commands
# Bots with a double ratchet address can answer service requests.
# Send a reply to a received service request. Returns the connection ID that correlates the reply delivery event.
# Network usage: background.
class APISendServiceResponse(TypedDict):
userId: int # int64
requestId: str
responseData: dict[str, object]
def APISendServiceResponse_cmd_string(self: APISendServiceResponse) -> str:
return '/_service_response ' + str(self['userId']) + ' ' + self['requestId'] + ' ' + json.dumps(self['responseData'])
APISendServiceResponse_Response = CR.ServiceReplyAccepted | CR.ChatCmdError
# Chat management
# These commands should not be used with CLI-based bots
@@ -697,10 +717,11 @@ APISetContactPrefs_Response = CR.ContactPrefsUpdated | CR.ChatCmdError
class StartChat(TypedDict):
mainApp: bool
enableSndFiles: bool
serviceRequests: bool
def StartChat_cmd_string(self: StartChat) -> str:
return '/_start'
return '/_start' + ' main=' + ('on' if self['mainApp'] else 'off') + (' snd_files=off' if not self['enableSndFiles'] else '') + (' service_requests=on' if self['serviceRequests'] else '')
StartChat_Response = CR.ChatStarted | CR.ChatRunning
@@ -303,6 +303,17 @@ class SubscriptionStatus(TypedDict):
subscriptionStatus: "T.SubscriptionStatus"
connections: list[str]
class ServiceRequest(TypedDict):
type: Literal["serviceRequest"]
user: "T.User"
requestId: str
signerKey: NotRequired[str]
requestData: dict[str, object]
class ServiceReplySent(TypedDict):
type: Literal["serviceReplySent"]
connectionId: str
class MessageError(TypedDict):
type: Literal["messageError"]
user: "T.User"
@@ -364,12 +375,14 @@ ChatEvent = (
| HostConnected
| HostDisconnected
| SubscriptionStatus
| ServiceRequest
| ServiceReplySent
| MessageError
| ChatError
| ChatErrors
)
ChatEvent_Tag = Literal["contactConnected", "contactUpdated", "contactDeletedByContact", "receivedContactRequest", "newMemberContactReceivedInv", "contactSndReady", "newChatItems", "chatItemReaction", "chatItemsDeleted", "chatItemUpdated", "groupChatItemsDeleted", "chatItemsStatusesUpdated", "receivedGroupInvitation", "userJoinedGroup", "groupUpdated", "joinedGroupMember", "memberRole", "deletedMember", "leftMember", "deletedMemberUser", "groupDeleted", "connectedToGroupMember", "memberAcceptedByOther", "memberBlockedForAll", "groupMemberUpdated", "groupLinkDataUpdated", "groupRelayUpdated", "rcvFileDescrReady", "rcvFileComplete", "sndFileCompleteXFTP", "rcvFileStart", "rcvFileSndCancelled", "rcvFileAccepted", "rcvFileError", "rcvFileWarning", "sndFileError", "sndFileWarning", "acceptingContactRequest", "acceptingBusinessRequest", "contactConnecting", "businessLinkConnecting", "joinedGroupMemberConnecting", "groupLinkConnecting", "hostConnected", "hostDisconnected", "subscriptionStatus", "messageError", "chatError", "chatErrors"]
ChatEvent_Tag = Literal["contactConnected", "contactUpdated", "contactDeletedByContact", "receivedContactRequest", "newMemberContactReceivedInv", "contactSndReady", "newChatItems", "chatItemReaction", "chatItemsDeleted", "chatItemUpdated", "groupChatItemsDeleted", "chatItemsStatusesUpdated", "receivedGroupInvitation", "userJoinedGroup", "groupUpdated", "joinedGroupMember", "memberRole", "deletedMember", "leftMember", "deletedMemberUser", "groupDeleted", "connectedToGroupMember", "memberAcceptedByOther", "memberBlockedForAll", "groupMemberUpdated", "groupLinkDataUpdated", "groupRelayUpdated", "rcvFileDescrReady", "rcvFileComplete", "sndFileCompleteXFTP", "rcvFileStart", "rcvFileSndCancelled", "rcvFileAccepted", "rcvFileError", "rcvFileWarning", "sndFileError", "sndFileWarning", "acceptingContactRequest", "acceptingBusinessRequest", "contactConnecting", "businessLinkConnecting", "joinedGroupMemberConnecting", "groupLinkConnecting", "hostConnected", "hostDisconnected", "subscriptionStatus", "serviceRequest", "serviceReplySent", "messageError", "chatError", "chatErrors"]
class OnEventDecorator(Protocol):
@@ -656,6 +669,18 @@ class OnEventDecorator(Protocol):
Callable[["SubscriptionStatus"], Awaitable[None]],
]: ...
@overload
def __call__(self, event: Literal["serviceRequest"], /) -> Callable[
[Callable[["ServiceRequest"], Awaitable[None]]],
Callable[["ServiceRequest"], Awaitable[None]],
]: ...
@overload
def __call__(self, event: Literal["serviceReplySent"], /) -> Callable[
[Callable[["ServiceReplySent"], Awaitable[None]]],
Callable[["ServiceReplySent"], Awaitable[None]],
]: ...
@overload
def __call__(self, event: Literal["messageError"], /) -> Callable[
[Callable[["MessageError"], Awaitable[None]]],
@@ -247,6 +247,11 @@ class SentInvitation(TypedDict):
connection: "T.PendingContactConnection"
customUserProfile: NotRequired["T.Profile"]
class ServiceReplyAccepted(TypedDict):
type: Literal["serviceReplyAccepted"]
user: "T.User"
connectionId: str
class SndFileCancelled(TypedDict):
type: Literal["sndFileCancelled"]
user: "T.User"
@@ -352,6 +357,7 @@ ChatResponse = (
| SentConfirmation
| SentGroupInvitation
| SentInvitation
| ServiceReplyAccepted
| SndFileCancelled
| UserAcceptedGroupSent
| UserContactLink
@@ -365,4 +371,4 @@ ChatResponse = (
| ApiChats
)
ChatResponse_Tag = Literal["acceptingContactRequest", "activeUser", "chatItemNotChanged", "chatItemReaction", "chatItemUpdated", "chatItemsDeleted", "chatRunning", "chatStarted", "chatStopped", "cmdOk", "chatCmdError", "connectionPlan", "contactAlreadyExists", "contactConnectionDeleted", "contactDeleted", "contactPrefsUpdated", "contactRequestRejected", "contactsList", "groupDeletedUser", "groupLink", "groupLinkCreated", "groupLinkDeleted", "groupCreated", "publicGroupCreated", "publicGroupCreationFailed", "groupRelays", "groupRelaysAdded", "groupRelaysAddFailed", "relayGroupAllowed", "groupMembers", "groupUpdated", "groupsList", "invitation", "leftMemberUser", "memberAccepted", "membersBlockedForAllUser", "membersRoleUser", "newChatItems", "rcvFileAccepted", "rcvFileAcceptedSndCancelled", "rcvFileCancelled", "sentConfirmation", "sentGroupInvitation", "sentInvitation", "sndFileCancelled", "userAcceptedGroupSent", "userContactLink", "userContactLinkCreated", "userContactLinkDeleted", "userContactLinkUpdated", "userDeletedMembers", "userProfileUpdated", "userProfileNoChange", "usersList", "apiChats"]
ChatResponse_Tag = Literal["acceptingContactRequest", "activeUser", "chatItemNotChanged", "chatItemReaction", "chatItemUpdated", "chatItemsDeleted", "chatRunning", "chatStarted", "chatStopped", "cmdOk", "chatCmdError", "connectionPlan", "contactAlreadyExists", "contactConnectionDeleted", "contactDeleted", "contactPrefsUpdated", "contactRequestRejected", "contactsList", "groupDeletedUser", "groupLink", "groupLinkCreated", "groupLinkDeleted", "groupCreated", "publicGroupCreated", "publicGroupCreationFailed", "groupRelays", "groupRelaysAdded", "groupRelaysAddFailed", "relayGroupAllowed", "groupMembers", "groupUpdated", "groupsList", "invitation", "leftMemberUser", "memberAccepted", "membersBlockedForAllUser", "membersRoleUser", "newChatItems", "rcvFileAccepted", "rcvFileAcceptedSndCancelled", "rcvFileCancelled", "sentConfirmation", "sentGroupInvitation", "sentInvitation", "serviceReplyAccepted", "sndFileCancelled", "userAcceptedGroupSent", "userContactLink", "userContactLinkCreated", "userContactLinkDeleted", "userContactLinkUpdated", "userDeletedMembers", "userProfileUpdated", "userProfileNoChange", "usersList", "apiChats"]
@@ -139,6 +139,32 @@ AgentErrorType = (
AgentErrorType_Tag = Literal["CMD", "CONN", "NO_USER", "SMP", "NTF", "XFTP", "FILE", "NO_NAME_SERVERS", "PROXY", "RCP", "BROKER", "AGENT", "NOTICE", "INTERNAL", "CRITICAL", "INACTIVE"]
class AgentServiceError_rejected(TypedDict):
type: Literal["rejected"]
rejectReason: str
class AgentServiceError_timeout(TypedDict):
type: Literal["timeout"]
class AgentServiceError_noPendingRequest(TypedDict):
type: Literal["noPendingRequest"]
class AgentServiceError_notDRAddress(TypedDict):
type: Literal["notDRAddress"]
class AgentServiceError_badSignature(TypedDict):
type: Literal["badSignature"]
AgentServiceError = (
AgentServiceError_rejected
| AgentServiceError_timeout
| AgentServiceError_noPendingRequest
| AgentServiceError_notDRAddress
| AgentServiceError_badSignature
)
AgentServiceError_Tag = Literal["rejected", "timeout", "noPendingRequest", "notDRAddress", "badSignature"]
class AutoAccept(TypedDict):
acceptIncognito: bool
@@ -1420,6 +1446,7 @@ class Contact(TypedDict):
chatTs: NotRequired[str] # ISO-8601 timestamp
preparedContact: NotRequired["PreparedContact"]
contactRequestId: NotRequired[int] # int64
contactRequest: NotRequired["UserContactRequestRef"]
contactGroupMemberId: NotRequired[int] # int64
contactGrpInvSent: bool
groupDirectInv: NotRequired["GroupDirectInvitation"]
@@ -1469,7 +1496,7 @@ class ContactShortLinkData(TypedDict):
business: bool
localBadge: NotRequired["LocalBadge"]
ContactStatus = Literal["active", "deleted", "deletedByUser"]
ContactStatus = Literal["active", "deleted", "deletedByUser", "rejected"]
class ContactUserPref_contact(TypedDict):
type: Literal["contact"]
@@ -2761,6 +2788,10 @@ class SMPAgentError_A_QUEUE(TypedDict):
type: Literal["A_QUEUE"]
queueErr: str
class SMPAgentError_A_SERVICE(TypedDict):
type: Literal["A_SERVICE"]
serviceError: "AgentServiceError"
SMPAgentError = (
SMPAgentError_A_MESSAGE
| SMPAgentError_A_PROHIBITED
@@ -2769,9 +2800,10 @@ SMPAgentError = (
| SMPAgentError_A_CRYPTO
| SMPAgentError_A_DUPLICATE
| SMPAgentError_A_QUEUE
| SMPAgentError_A_SERVICE
)
SMPAgentError_Tag = Literal["A_MESSAGE", "A_PROHIBITED", "A_VERSION", "A_LINK", "A_CRYPTO", "A_DUPLICATE", "A_QUEUE"]
SMPAgentError_Tag = Literal["A_MESSAGE", "A_PROHIBITED", "A_VERSION", "A_LINK", "A_CRYPTO", "A_DUPLICATE", "A_QUEUE", "A_SERVICE"]
class SecurityCode(TypedDict):
securityCode: str
@@ -3537,6 +3569,11 @@ class UserContactRequest(TypedDict):
pqSupport: bool
welcomeSharedMsgId: NotRequired[str]
requestSharedMsgId: NotRequired[str]
rejectionSupported: bool
class UserContactRequestRef(TypedDict):
contactRequestId: int # int64
rejectionSupported: bool
class UserInfo(TypedDict):
user: "User"