diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e6e20b..e5783d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,52 @@ called out explicitly even when nothing else did. release notes, so a version with no entry here does not release. Write the entry in the same PR that syncs the contract, while the diff is still in front of you. +## 0.16.0 + +Synced to [`flipcash2-protobuf-api@ec66e6b1`](https://github.com/code-payments/flipcash2-protobuf-api/commit/ec66e6b12c59d7eae3281341e74fe8853c90a306), +picking up [#132](https://github.com/code-payments/flipcash2-protobuf-api/pull/132) and +[#133](https://github.com/code-payments/flipcash2-protobuf-api/pull/133). `chat.v1`, `event.v1`, +`messaging.v1` and `profile.v1` moved; `blob.v1` changed only in comments. + +### Changed + +- `StartChatRequest`'s `group` oneof case is now `public_group`, and its message + `GroupChatParameters` is now `PublicGroupChatParameters`. The field number is still 1, so this is + wire-compatible, but every call site that sets or reads it stops compiling on upgrade + (`setGroup`/`group` in Kotlin, `.group` in Swift). + +### Added + +- Private groups. `StartChatRequest` gains a `private_group` case (= 2) with + `PrivateGroupChatParameters` (`title`, 1–64 characters; optional `picture`). A private group is + visible once created, but nothing can happen in it until its creator stores the group's key. +- Seven `Chat` RPCs for the lobby and keys: `EnterLobby`, `LeaveLobby`, `GetLobbyMembers`, + `AdmitLobbyMember`, `DenyLobbyMember`, `SetKeyEnvelope` and `GetKeyEnvelope`, each with its own + `Result` enum. A user waits in the lobby until the creator admits or denies them; admitting is + the only way to hand another user a key envelope, and the admission arrives as an ordinary + `RosterUpdate.MemberJoined`. A denial is not announced to the denied user. +- `chat.v1.KeyEnvelope` (`scheme`, 24-byte `nonce`, 48-byte `ciphertext`): the group's 32-byte chat + key, wrapped per member. The first envelope a caller stores for themself stands; a different one + afterwards returns `ALREADY_SET`. There is no forward secrecy, and a member who leaves still knows + the chat key. +- `chat.v1.Lobby`, `LobbyMember`, `LobbyUpdate` and `LobbyUpdateBatch`, and + `event.v1.ChatUpdate.lobby_updates` (= 9) carrying members entering and leaving a lobby. +- `chat.v1.Metadata.is_private` (= 14) and `in_lobby` (= 15). `in_lobby` is per-viewer: there is no + RPC that lists the lobbies a caller is waiting in, so a client reads it from `GetChat`. +- `messaging.v1.EncryptedContent.Scheme.CHAT_KEY_XCHACHA20POLY1305` (= 2), the scheme private groups + use. Clients render a scheme they don't recognize as unsupported. +- `ENCRYPTION_REQUIRED` on `SendMessageResponse.Result` (= 3) and `EditMessageResponse.Result` + (= 6): the chat is a private group and the content is not `EncryptedContent`. +- Profile bios and cover pictures: `UserProfile.bio` (= 12, up to 160 characters) and + `cover_picture` (= 13), set through the new `Profile.SetBio` and `Profile.SetCoverPicture` RPCs. + `SetBio` can come back `FAILED_MODERATED` with a `flagged_category`; `SetCoverPicture` reports blob + state the same way `SetProfilePicture` does. + +### Upgrading + +The `public_group` rename is the one source break. No field or enum value was renumbered: every new +enum case is appended after the existing ones, so `Error*(rawValue:)` mappings keep their meaning. + ## 0.15.0 Synced to [`flipcash2-protobuf-api@993127b5`](https://github.com/code-payments/flipcash2-protobuf-api/commit/993127b50e42046ee2f290f122b05625774a0661), diff --git a/Sources/Flipcash2ClientProtocol/ContractInfo.swift b/Sources/Flipcash2ClientProtocol/ContractInfo.swift index 0a1657b..999458b 100644 --- a/Sources/Flipcash2ClientProtocol/ContractInfo.swift +++ b/Sources/Flipcash2ClientProtocol/ContractInfo.swift @@ -1,8 +1,8 @@ // Generated by scripts/generate-swift.sh. Do not edit. public enum Flipcash2ContractInfo { - public static let version = "0.15.0-dev" - public static let protoCommit = "993127b50e42046ee2f290f122b05625774a0661" + public static let version = "0.16.0-dev" + public static let protoCommit = "ec66e6b12c59d7eae3281341e74fe8853c90a306" public static var isLocal: Bool { protoCommit == localSentinel } diff --git a/Sources/Flipcash2ClientProtocol/blob_v1_blob_storage_service.pb.swift b/Sources/Flipcash2ClientProtocol/blob_v1_blob_storage_service.pb.swift index a5ce241..ee0c238 100644 --- a/Sources/Flipcash2ClientProtocol/blob_v1_blob_storage_service.pb.swift +++ b/Sources/Flipcash2ClientProtocol/blob_v1_blob_storage_service.pb.swift @@ -136,13 +136,16 @@ public struct Flipcash_Blob_V1_InitiateExternalUploadRequest: Sendable { /// profile or chat pictures). public var endToEndEncryptedFor: Flipcash_Blob_V1_InitiateExternalUploadRequest.OneOf_EndToEndEncryptedFor? = nil - /// The bytes are encrypted for this DM, as described in - /// messaging.v1.EncryptedContent: the 24-byte nonce followed by the + /// The bytes are encrypted for this DM or private group, as described + /// in messaging.v1.EncryptedContent: the 24-byte nonce followed by the /// ciphertext and its 16-byte tag. The caller must be a member of the - /// chat and the chat must be a DM, or the upload is DENIED. Once READY, - /// the blob is granted to the chat, so the other member can read it - /// through AccessContext.chat, and it can be referenced only from - /// EncryptedContent in that chat. + /// chat and the chat must be a DM or a private group + /// (chat.v1.Metadata.is_private), or the upload is DENIED. So is an + /// upload for a private group whose creator has not stored its key + /// yet (see chat.v1.StartChatRequest.PrivateGroupChatParameters). + /// Once READY, the blob is granted to the chat, so its other members + /// can read it through AccessContext.chat, and it can be referenced + /// only from EncryptedContent in that chat. public var chat: Flipcash_Common_V1_ChatId { get { if case .chat(let v)? = endToEndEncryptedFor {return v} @@ -165,13 +168,16 @@ public struct Flipcash_Blob_V1_InitiateExternalUploadRequest: Sendable { /// named here, and is rejected anywhere else (unencrypted MediaContent, /// profile or chat pictures). public enum OneOf_EndToEndEncryptedFor: Equatable, Sendable { - /// The bytes are encrypted for this DM, as described in - /// messaging.v1.EncryptedContent: the 24-byte nonce followed by the + /// The bytes are encrypted for this DM or private group, as described + /// in messaging.v1.EncryptedContent: the 24-byte nonce followed by the /// ciphertext and its 16-byte tag. The caller must be a member of the - /// chat and the chat must be a DM, or the upload is DENIED. Once READY, - /// the blob is granted to the chat, so the other member can read it - /// through AccessContext.chat, and it can be referenced only from - /// EncryptedContent in that chat. + /// chat and the chat must be a DM or a private group + /// (chat.v1.Metadata.is_private), or the upload is DENIED. So is an + /// upload for a private group whose creator has not stored its key + /// yet (see chat.v1.StartChatRequest.PrivateGroupChatParameters). + /// Once READY, the blob is granted to the chat, so its other members + /// can read it through AccessContext.chat, and it can be referenced + /// only from EncryptedContent in that chat. case chat(Flipcash_Common_V1_ChatId) } diff --git a/Sources/Flipcash2ClientProtocol/blob_v1_model.pb.swift b/Sources/Flipcash2ClientProtocol/blob_v1_model.pb.swift index 9d64457..88799d9 100644 --- a/Sources/Flipcash2ClientProtocol/blob_v1_model.pb.swift +++ b/Sources/Flipcash2ClientProtocol/blob_v1_model.pb.swift @@ -808,11 +808,11 @@ public struct Flipcash_Blob_V1_AccessContext: Sendable { /// The caller is accessing these blobs from this user's public profile. /// Authorized iff the blob is a rendition of that user's CURRENT profile - /// picture — a profile grants nothing else, and a superseded picture's - /// renditions stop resolving through it. + /// picture or cover picture — a profile grants nothing else, and a + /// superseded picture's renditions stop resolving through it. /// - /// A caller never needs this for its OWN profile picture, since it owns - /// those blobs. + /// A caller never needs this for its OWN pictures, since it owns those + /// blobs. public var userProfile: Flipcash_Common_V1_UserId { get { if case .userProfile(let v)? = scope {return v} @@ -844,11 +844,11 @@ public struct Flipcash_Blob_V1_AccessContext: Sendable { case chat(Flipcash_Common_V1_ChatId) /// The caller is accessing these blobs from this user's public profile. /// Authorized iff the blob is a rendition of that user's CURRENT profile - /// picture — a profile grants nothing else, and a superseded picture's - /// renditions stop resolving through it. + /// picture or cover picture — a profile grants nothing else, and a + /// superseded picture's renditions stop resolving through it. /// - /// A caller never needs this for its OWN profile picture, since it owns - /// those blobs. + /// A caller never needs this for its OWN pictures, since it owns those + /// blobs. case userProfile(Flipcash_Common_V1_UserId) /// The caller is accessing these blobs from this chat's public profile. /// Authorized iff the blob is a rendition of that chat's CURRENT profile diff --git a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift index 13284e4..a6eacd2 100644 --- a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift +++ b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift @@ -152,6 +152,90 @@ public enum Flipcash_Chat_V1_Chat { method: "UnmuteChat" ) } + /// Namespace for "EnterLobby" metadata. + public enum EnterLobby { + /// Request type for "EnterLobby". + public typealias Input = Flipcash_Chat_V1_EnterLobbyRequest + /// Response type for "EnterLobby". + public typealias Output = Flipcash_Chat_V1_EnterLobbyResponse + /// Descriptor for "EnterLobby". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "EnterLobby" + ) + } + /// Namespace for "LeaveLobby" metadata. + public enum LeaveLobby { + /// Request type for "LeaveLobby". + public typealias Input = Flipcash_Chat_V1_LeaveLobbyRequest + /// Response type for "LeaveLobby". + public typealias Output = Flipcash_Chat_V1_LeaveLobbyResponse + /// Descriptor for "LeaveLobby". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "LeaveLobby" + ) + } + /// Namespace for "GetLobbyMembers" metadata. + public enum GetLobbyMembers { + /// Request type for "GetLobbyMembers". + public typealias Input = Flipcash_Chat_V1_GetLobbyMembersRequest + /// Response type for "GetLobbyMembers". + public typealias Output = Flipcash_Chat_V1_GetLobbyMembersResponse + /// Descriptor for "GetLobbyMembers". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "GetLobbyMembers" + ) + } + /// Namespace for "AdmitLobbyMember" metadata. + public enum AdmitLobbyMember { + /// Request type for "AdmitLobbyMember". + public typealias Input = Flipcash_Chat_V1_AdmitLobbyMemberRequest + /// Response type for "AdmitLobbyMember". + public typealias Output = Flipcash_Chat_V1_AdmitLobbyMemberResponse + /// Descriptor for "AdmitLobbyMember". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "AdmitLobbyMember" + ) + } + /// Namespace for "DenyLobbyMember" metadata. + public enum DenyLobbyMember { + /// Request type for "DenyLobbyMember". + public typealias Input = Flipcash_Chat_V1_DenyLobbyMemberRequest + /// Response type for "DenyLobbyMember". + public typealias Output = Flipcash_Chat_V1_DenyLobbyMemberResponse + /// Descriptor for "DenyLobbyMember". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "DenyLobbyMember" + ) + } + /// Namespace for "SetKeyEnvelope" metadata. + public enum SetKeyEnvelope { + /// Request type for "SetKeyEnvelope". + public typealias Input = Flipcash_Chat_V1_SetKeyEnvelopeRequest + /// Response type for "SetKeyEnvelope". + public typealias Output = Flipcash_Chat_V1_SetKeyEnvelopeResponse + /// Descriptor for "SetKeyEnvelope". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "SetKeyEnvelope" + ) + } + /// Namespace for "GetKeyEnvelope" metadata. + public enum GetKeyEnvelope { + /// Request type for "GetKeyEnvelope". + public typealias Input = Flipcash_Chat_V1_GetKeyEnvelopeRequest + /// Response type for "GetKeyEnvelope". + public typealias Output = Flipcash_Chat_V1_GetKeyEnvelopeResponse + /// Descriptor for "GetKeyEnvelope". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "GetKeyEnvelope" + ) + } /// Descriptors for all methods in the "flipcash.chat.v1.Chat" service. public static let descriptors: [GRPCCore.MethodDescriptor] = [ GetChat.descriptor, @@ -164,7 +248,14 @@ public enum Flipcash_Chat_V1_Chat { LeaveChat.descriptor, EditChat.descriptor, MuteChat.descriptor, - UnmuteChat.descriptor + UnmuteChat.descriptor, + EnterLobby.descriptor, + LeaveLobby.descriptor, + GetLobbyMembers.descriptor, + AdmitLobbyMember.descriptor, + DenyLobbyMember.descriptor, + SetKeyEnvelope.descriptor, + GetKeyEnvelope.descriptor ] } } @@ -195,6 +286,14 @@ extension Flipcash_Chat_V1_Chat { /// > with view_mode REDACTED and none of the per-viewer fields (is_hidden, /// > viewer_state). An unauthenticated read under any other view_mode, or of /// > a DM, is DENIED. + /// > + /// > A private group (see Metadata.is_private) returns its record to any + /// > registered user, so that one who is not a member can see what they are + /// > asking to join, but its messaging state only to a member, whatever the + /// > view_mode. It has no public view: an unauthenticated read of one is + /// > DENIED. A private group whose creator has not stored its key yet (see + /// > StartChatRequest.PrivateGroupChatParameters) is returned like any + /// > other: it is visible, though nothing can happen in it. /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_GetChatRequest` message. @@ -418,6 +517,11 @@ extension Flipcash_Chat_V1_Chat { /// > Source IDL Documentation: /// > /// > JoinChat adds the caller to a chat's roster. + /// > + /// > A private group (see Metadata.is_private) cannot be joined this way and + /// > is DENIED: a user enters its lobby with EnterLobby and is admitted by + /// > its creator. The one exception is the group's creator, who needs no + /// > approval and rejoins a private group they left with this RPC. /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_JoinChatRequest` message. @@ -441,6 +545,11 @@ extension Flipcash_Chat_V1_Chat { /// > Source IDL Documentation: /// > /// > LeaveChat removes the caller from a chat's roster. + /// > + /// > Leaving a private group discards the caller's key envelope (see + /// > KeyEnvelope), so returning means entering the lobby and being admitted + /// > again. The creator's envelope is kept, since no one else could give + /// > them another. /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_LeaveChatRequest` message. @@ -553,6 +662,255 @@ extension Flipcash_Chat_V1_Chat { options: GRPCCore.CallOptions, onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result ) async throws -> Result where Result: Sendable + + /// Call the "EnterLobby" method. + /// + /// > Source IDL Documentation: + /// > + /// > EnterLobby places the caller in a private group's lobby, where they + /// > wait for the group's creator to admit them. It is how a user asks to + /// > join a private group (see Metadata.is_private); JoinChat is DENIED for + /// > one. + /// > + /// > Entering a lobby the caller is already in is a no-op that returns OK. + /// > The server caps both how many users may wait in one lobby and how many + /// > lobbies one user may wait in. A group whose creator has not stored its + /// > key yet (see StartChatRequest.PrivateGroupChatParameters) has no lobby + /// > to enter, and is DENIED until they have. + /// > + /// > A lobby is visible only to the group's creator, through + /// > GetLobbyMembers: the users waiting in it are not shown to each other or + /// > to the group's members. The creator's devices learn of the entry as a + /// > LobbyUpdate on the event stream. + /// > + /// > While the caller waits, the chat's Metadata carries in_lobby. The + /// > caller leaves the lobby when they withdraw (LeaveLobby), when the + /// > creator denies them (DenyLobbyMember), or when the creator admits them + /// > (AdmitLobbyMember). An admission reaches the caller's devices as a + /// > RosterUpdate.MemberJoined naming them; a denial is not announced to + /// > them. + /// > + /// > There is no RPC that lists the lobbies a caller is waiting in. A client + /// > keeps the chats it entered itself, and reads in_lobby from GetChat to + /// > learn whether it is still waiting in one. A paged listing may be added + /// > later. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_EnterLobbyRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_EnterLobbyRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_EnterLobbyResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + func enterLobby( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result + ) async throws -> Result where Result: Sendable + + /// Call the "LeaveLobby" method. + /// + /// > Source IDL Documentation: + /// > + /// > LeaveLobby withdraws the caller from a private group's lobby. Leaving a + /// > lobby the caller is not in is a no-op that returns OK. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_LeaveLobbyRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_LeaveLobbyRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_LeaveLobbyResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + func leaveLobby( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result + ) async throws -> Result where Result: Sendable + + /// Call the "GetLobbyMembers" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetLobbyMembers pages the users waiting in a private group's lobby, + /// > earliest entered first. + /// > + /// > Only the group's creator may call it, and only while a member of the + /// > group; anyone else is DENIED. The page is read from an index that + /// > trails writes briefly, so a user who just entered may be absent and one + /// > who just left may be present. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetLobbyMembersRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_GetLobbyMembersRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_GetLobbyMembersResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + func getLobbyMembers( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result + ) async throws -> Result where Result: Sendable + + /// Call the "AdmitLobbyMember" method. + /// + /// > Source IDL Documentation: + /// > + /// > AdmitLobbyMember admits a user waiting in a private group's lobby: + /// > it stores the chat key wrapped for them (see KeyEnvelope) and adds them + /// > to the chat's roster. The envelope is stored first, so an admitted + /// > member always has one. + /// > + /// > Only the group's creator may call it, and only while a member of the + /// > group; anyone else is DENIED. So is a creator who has not stored their + /// > own key envelope yet (see StartChatRequest.PrivateGroupChatParameters): + /// > nobody is admitted to a group before its creator holds its key. This is + /// > the only way to give another user a key envelope. Admitting a user who + /// > is already a member is a no-op that returns OK and leaves their stored + /// > envelope as it is. + /// > + /// > The admission is announced like any other join: a + /// > RosterUpdate.MemberJoined to the chat's members and to the admitted + /// > user, who then fetches their envelope with GetKeyEnvelope. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_AdmitLobbyMemberRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_AdmitLobbyMemberRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_AdmitLobbyMemberResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + func admitLobbyMember( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result + ) async throws -> Result where Result: Sendable + + /// Call the "DenyLobbyMember" method. + /// + /// > Source IDL Documentation: + /// > + /// > DenyLobbyMember removes a user from a private group's lobby without + /// > admitting them. Only the group's creator may call it, and only while a + /// > member of the group; anyone else is DENIED. Denying a user who is not + /// > in the lobby is a no-op that returns OK. + /// > + /// > The denied user is not notified. The chat's Metadata stops carrying + /// > in_lobby for them, and they may enter the lobby again. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_DenyLobbyMemberRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_DenyLobbyMemberRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_DenyLobbyMemberResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + func denyLobbyMember( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result + ) async throws -> Result where Result: Sendable + + /// Call the "SetKeyEnvelope" method. + /// + /// > Source IDL Documentation: + /// > + /// > SetKeyEnvelope stores the caller's own key envelope for a private + /// > group. Only a member may call it, and only for themself: an envelope + /// > for another user is stored by admitting them (AdmitLobbyMember). + /// > + /// > It is called in two situations (see KeyEnvelope): + /// > - By the group's creator, right after StartChat returns the chat's + /// > ID, to store the chat key they have just generated. This is the + /// > second step of creating a private group, and nothing can happen in + /// > the group until it succeeds (see + /// > StartChatRequest.PrivateGroupChatParameters). The creator's client + /// > keeps attempting it until it returns OK or ALREADY_SET: on a failed + /// > call, on its next launch, and on any device that finds the group + /// > with NO_ENVELOPE. + /// > - By an admitted member, after first opening the envelope the creator + /// > wrapped for them, to replace it with one wrapped by themself. + /// > + /// > The first envelope a caller stores for themself stands. Once the + /// > stored envelope is one the caller wrapped, a call with a different + /// > envelope changes nothing and returns ALREADY_SET, and the caller uses + /// > the stored one from GetKeyEnvelope. This keeps two of a creator's + /// > devices, each setting up the same new group, from holding different + /// > keys. Storing an envelope identical to the one stored is a no-op that + /// > returns OK. + /// > + /// > Nothing is published: the caller's other devices fetch the envelope + /// > when they need it. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_SetKeyEnvelopeRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_SetKeyEnvelopeRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_SetKeyEnvelopeResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + func setKeyEnvelope( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result + ) async throws -> Result where Result: Sendable + + /// Call the "GetKeyEnvelope" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetKeyEnvelope returns the caller's own key envelope for a private + /// > group. Only a member may call it. The chat key never changes, so a + /// > client calls this once per chat per install and keeps the key it + /// > unwraps. + /// > + /// > NO_ENVELOPE is expected only for the creator of a group whose creation + /// > stopped between StartChat and SetKeyEnvelope. A creator's client that + /// > receives it generates a chat key and stores its envelope with + /// > SetKeyEnvelope, as described on KeyEnvelope, and keeps attempting that + /// > until it succeeds. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetKeyEnvelopeRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_GetKeyEnvelopeRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_GetKeyEnvelopeResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + func getKeyEnvelope( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result + ) async throws -> Result where Result: Sendable } /// Generated client for the "flipcash.chat.v1.Chat" service. @@ -582,6 +940,14 @@ extension Flipcash_Chat_V1_Chat { /// > with view_mode REDACTED and none of the per-viewer fields (is_hidden, /// > viewer_state). An unauthenticated read under any other view_mode, or of /// > a DM, is DENIED. + /// > + /// > A private group (see Metadata.is_private) returns its record to any + /// > registered user, so that one who is not a member can see what they are + /// > asking to join, but its messaging state only to a member, whatever the + /// > view_mode. It has no public view: an unauthenticated read of one is + /// > DENIED. A private group whose creator has not stored its key yet (see + /// > StartChatRequest.PrivateGroupChatParameters) is returned like any + /// > other: it is visible, though nothing can happen in it. /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_GetChatRequest` message. @@ -871,6 +1237,11 @@ extension Flipcash_Chat_V1_Chat { /// > Source IDL Documentation: /// > /// > JoinChat adds the caller to a chat's roster. + /// > + /// > A private group (see Metadata.is_private) cannot be joined this way and + /// > is DENIED: a user enters its lobby with EnterLobby and is admitted by + /// > its creator. The one exception is the group's creator, who needs no + /// > approval and rejoins a private group they left with this RPC. /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_JoinChatRequest` message. @@ -905,6 +1276,11 @@ extension Flipcash_Chat_V1_Chat { /// > Source IDL Documentation: /// > /// > LeaveChat removes the caller from a chat's roster. + /// > + /// > Leaving a private group discards the caller's key envelope (see + /// > KeyEnvelope), so returning means entering the lobby and being admitted + /// > again. The creator's envelope is kept, since no one else could give + /// > them another. /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_LeaveChatRequest` message. @@ -1061,103 +1437,437 @@ extension Flipcash_Chat_V1_Chat { onResponse: handleResponse ) } - } -} - -// Helpers providing default arguments to 'ClientProtocol' methods. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Flipcash_Chat_V1_Chat.ClientProtocol { - /// Call the "GetChat" method. - /// - /// > Source IDL Documentation: - /// > - /// > GetChat returns the metadata for a specific chat - /// > - /// > Auth is optional. An unauthenticated caller gets the chat's public view: - /// > the same group record a registered non-member previewing it receives, - /// > with view_mode REDACTED and none of the per-viewer fields (is_hidden, - /// > viewer_state). An unauthenticated read under any other view_mode, or of - /// > a DM, is DENIED. - /// - /// - Parameters: - /// - request: A request containing a single `Flipcash_Chat_V1_GetChatRequest` message. - /// - options: Options to apply to this RPC. - /// - handleResponse: A closure which handles the response, the result of which is - /// returned to the caller. Returning from the closure will cancel the RPC if it - /// hasn't already finished. - /// - Returns: The result of `handleResponse`. - public func getChat( - request: GRPCCore.ClientRequest, - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in - try response.message - } - ) async throws -> Result where Result: Sendable { - try await self.getChat( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - onResponse: handleResponse - ) - } - /// Call the "GetDmChatFeed" method. - /// - /// > Source IDL Documentation: - /// > - /// > GetDmChatFeed gets the set of DM chats for an owner account using - /// > a paged API, ordered by last activity with the most recent first. - /// > - /// > Chats are ordered by a mutable key (last_activity), so pagination alone - /// > cannot guarantee a complete read: a chat can receive new activity and - /// > move into a region the client has already paged past. To get the full - /// > list, the client MUST combine this RPC with the event stream: - /// > - /// > 1. Open the event stream to receive ChatUpdate and begin buffering updates - /// > BEFORE the first GetDmChatFeed call. This ordering is the contract that - /// > closes the gap; subscribing after pagination starts can drop chats. - /// > 2. Page through GetDmChatFeed to exhaustion (until has_more is false), - /// > always echoing back the paging token returned by the prior response. - /// > All pages are served against a single snapshot pinned by that token, - /// > so the set is read consistently. - /// > 3. Merge the buffered and ongoing stream updates onto the paginated - /// > set. Any chat whose activity changed after the snapshot watermark - /// > is delivered via the stream rather than via pagination. - /// > - /// > Read together, pagination guarantees the set (every chat exactly once) - /// > and the stream guarantees freshness and ordering. The local last_activity - /// > sort is maintained by the client from the stream after the initial read. - /// - /// - Parameters: - /// - request: A request containing a single `Flipcash_Chat_V1_GetDmChatFeedRequest` message. - /// - options: Options to apply to this RPC. - /// - handleResponse: A closure which handles the response, the result of which is - /// returned to the caller. Returning from the closure will cancel the RPC if it - /// hasn't already finished. - /// - Returns: The result of `handleResponse`. - public func getDmChatFeed( - request: GRPCCore.ClientRequest, - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in - try response.message + /// Call the "EnterLobby" method. + /// + /// > Source IDL Documentation: + /// > + /// > EnterLobby places the caller in a private group's lobby, where they + /// > wait for the group's creator to admit them. It is how a user asks to + /// > join a private group (see Metadata.is_private); JoinChat is DENIED for + /// > one. + /// > + /// > Entering a lobby the caller is already in is a no-op that returns OK. + /// > The server caps both how many users may wait in one lobby and how many + /// > lobbies one user may wait in. A group whose creator has not stored its + /// > key yet (see StartChatRequest.PrivateGroupChatParameters) has no lobby + /// > to enter, and is DENIED until they have. + /// > + /// > A lobby is visible only to the group's creator, through + /// > GetLobbyMembers: the users waiting in it are not shown to each other or + /// > to the group's members. The creator's devices learn of the entry as a + /// > LobbyUpdate on the event stream. + /// > + /// > While the caller waits, the chat's Metadata carries in_lobby. The + /// > caller leaves the lobby when they withdraw (LeaveLobby), when the + /// > creator denies them (DenyLobbyMember), or when the creator admits them + /// > (AdmitLobbyMember). An admission reaches the caller's devices as a + /// > RosterUpdate.MemberJoined naming them; a denial is not announced to + /// > them. + /// > + /// > There is no RPC that lists the lobbies a caller is waiting in. A client + /// > keeps the chats it entered itself, and reads in_lobby from GetChat to + /// > learn whether it is still waiting in one. A paged listing may be added + /// > later. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_EnterLobbyRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_EnterLobbyRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_EnterLobbyResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func enterLobby( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.client.unary( + request: request, + descriptor: Flipcash_Chat_V1_Chat.Method.EnterLobby.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) } - ) async throws -> Result where Result: Sendable { - try await self.getDmChatFeed( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - onResponse: handleResponse - ) - } - /// Call the "GetGroupChatFeed" method. - /// - /// > Source IDL Documentation: - /// > - /// > GetGroupChatFeed gets the set of group chats for an owner account using - /// > a paged API, ordered by last activity with the most recent first. + /// Call the "LeaveLobby" method. + /// + /// > Source IDL Documentation: + /// > + /// > LeaveLobby withdraws the caller from a private group's lobby. Leaving a + /// > lobby the caller is not in is a no-op that returns OK. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_LeaveLobbyRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_LeaveLobbyRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_LeaveLobbyResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func leaveLobby( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.client.unary( + request: request, + descriptor: Flipcash_Chat_V1_Chat.Method.LeaveLobby.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetLobbyMembers" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetLobbyMembers pages the users waiting in a private group's lobby, + /// > earliest entered first. + /// > + /// > Only the group's creator may call it, and only while a member of the + /// > group; anyone else is DENIED. The page is read from an index that + /// > trails writes briefly, so a user who just entered may be absent and one + /// > who just left may be present. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetLobbyMembersRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_GetLobbyMembersRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_GetLobbyMembersResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func getLobbyMembers( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.client.unary( + request: request, + descriptor: Flipcash_Chat_V1_Chat.Method.GetLobbyMembers.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "AdmitLobbyMember" method. + /// + /// > Source IDL Documentation: + /// > + /// > AdmitLobbyMember admits a user waiting in a private group's lobby: + /// > it stores the chat key wrapped for them (see KeyEnvelope) and adds them + /// > to the chat's roster. The envelope is stored first, so an admitted + /// > member always has one. + /// > + /// > Only the group's creator may call it, and only while a member of the + /// > group; anyone else is DENIED. So is a creator who has not stored their + /// > own key envelope yet (see StartChatRequest.PrivateGroupChatParameters): + /// > nobody is admitted to a group before its creator holds its key. This is + /// > the only way to give another user a key envelope. Admitting a user who + /// > is already a member is a no-op that returns OK and leaves their stored + /// > envelope as it is. + /// > + /// > The admission is announced like any other join: a + /// > RosterUpdate.MemberJoined to the chat's members and to the admitted + /// > user, who then fetches their envelope with GetKeyEnvelope. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_AdmitLobbyMemberRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_AdmitLobbyMemberRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_AdmitLobbyMemberResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func admitLobbyMember( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.client.unary( + request: request, + descriptor: Flipcash_Chat_V1_Chat.Method.AdmitLobbyMember.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "DenyLobbyMember" method. + /// + /// > Source IDL Documentation: + /// > + /// > DenyLobbyMember removes a user from a private group's lobby without + /// > admitting them. Only the group's creator may call it, and only while a + /// > member of the group; anyone else is DENIED. Denying a user who is not + /// > in the lobby is a no-op that returns OK. + /// > + /// > The denied user is not notified. The chat's Metadata stops carrying + /// > in_lobby for them, and they may enter the lobby again. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_DenyLobbyMemberRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_DenyLobbyMemberRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_DenyLobbyMemberResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func denyLobbyMember( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.client.unary( + request: request, + descriptor: Flipcash_Chat_V1_Chat.Method.DenyLobbyMember.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "SetKeyEnvelope" method. + /// + /// > Source IDL Documentation: + /// > + /// > SetKeyEnvelope stores the caller's own key envelope for a private + /// > group. Only a member may call it, and only for themself: an envelope + /// > for another user is stored by admitting them (AdmitLobbyMember). + /// > + /// > It is called in two situations (see KeyEnvelope): + /// > - By the group's creator, right after StartChat returns the chat's + /// > ID, to store the chat key they have just generated. This is the + /// > second step of creating a private group, and nothing can happen in + /// > the group until it succeeds (see + /// > StartChatRequest.PrivateGroupChatParameters). The creator's client + /// > keeps attempting it until it returns OK or ALREADY_SET: on a failed + /// > call, on its next launch, and on any device that finds the group + /// > with NO_ENVELOPE. + /// > - By an admitted member, after first opening the envelope the creator + /// > wrapped for them, to replace it with one wrapped by themself. + /// > + /// > The first envelope a caller stores for themself stands. Once the + /// > stored envelope is one the caller wrapped, a call with a different + /// > envelope changes nothing and returns ALREADY_SET, and the caller uses + /// > the stored one from GetKeyEnvelope. This keeps two of a creator's + /// > devices, each setting up the same new group, from holding different + /// > keys. Storing an envelope identical to the one stored is a no-op that + /// > returns OK. + /// > + /// > Nothing is published: the caller's other devices fetch the envelope + /// > when they need it. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_SetKeyEnvelopeRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_SetKeyEnvelopeRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_SetKeyEnvelopeResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func setKeyEnvelope( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.client.unary( + request: request, + descriptor: Flipcash_Chat_V1_Chat.Method.SetKeyEnvelope.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetKeyEnvelope" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetKeyEnvelope returns the caller's own key envelope for a private + /// > group. Only a member may call it. The chat key never changes, so a + /// > client calls this once per chat per install and keeps the key it + /// > unwraps. + /// > + /// > NO_ENVELOPE is expected only for the creator of a group whose creation + /// > stopped between StartChat and SetKeyEnvelope. A creator's client that + /// > receives it generates a chat key and stores its envelope with + /// > SetKeyEnvelope, as described on KeyEnvelope, and keeps attempting that + /// > until it succeeds. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetKeyEnvelopeRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_GetKeyEnvelopeRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_GetKeyEnvelopeResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func getKeyEnvelope( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.client.unary( + request: request, + descriptor: Flipcash_Chat_V1_Chat.Method.GetKeyEnvelope.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + } +} + +// Helpers providing default arguments to 'ClientProtocol' methods. +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) +extension Flipcash_Chat_V1_Chat.ClientProtocol { + /// Call the "GetChat" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetChat returns the metadata for a specific chat + /// > + /// > Auth is optional. An unauthenticated caller gets the chat's public view: + /// > the same group record a registered non-member previewing it receives, + /// > with view_mode REDACTED and none of the per-viewer fields (is_hidden, + /// > viewer_state). An unauthenticated read under any other view_mode, or of + /// > a DM, is DENIED. + /// > + /// > A private group (see Metadata.is_private) returns its record to any + /// > registered user, so that one who is not a member can see what they are + /// > asking to join, but its messaging state only to a member, whatever the + /// > view_mode. It has no public view: an unauthenticated read of one is + /// > DENIED. A private group whose creator has not stored its key yet (see + /// > StartChatRequest.PrivateGroupChatParameters) is returned like any + /// > other: it is visible, though nothing can happen in it. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetChatRequest` message. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func getChat( + request: GRPCCore.ClientRequest, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.getChat( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetDmChatFeed" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetDmChatFeed gets the set of DM chats for an owner account using + /// > a paged API, ordered by last activity with the most recent first. + /// > + /// > Chats are ordered by a mutable key (last_activity), so pagination alone + /// > cannot guarantee a complete read: a chat can receive new activity and + /// > move into a region the client has already paged past. To get the full + /// > list, the client MUST combine this RPC with the event stream: + /// > + /// > 1. Open the event stream to receive ChatUpdate and begin buffering updates + /// > BEFORE the first GetDmChatFeed call. This ordering is the contract that + /// > closes the gap; subscribing after pagination starts can drop chats. + /// > 2. Page through GetDmChatFeed to exhaustion (until has_more is false), + /// > always echoing back the paging token returned by the prior response. + /// > All pages are served against a single snapshot pinned by that token, + /// > so the set is read consistently. + /// > 3. Merge the buffered and ongoing stream updates onto the paginated + /// > set. Any chat whose activity changed after the snapshot watermark + /// > is delivered via the stream rather than via pagination. + /// > + /// > Read together, pagination guarantees the set (every chat exactly once) + /// > and the stream guarantees freshness and ordering. The local last_activity + /// > sort is maintained by the client from the stream after the initial read. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetDmChatFeedRequest` message. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func getDmChatFeed( + request: GRPCCore.ClientRequest, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.getDmChatFeed( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetGroupChatFeed" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetGroupChatFeed gets the set of group chats for an owner account using + /// > a paged API, ordered by last activity with the most recent first. /// > /// > It has the same read contract as GetDmChatFeed. Chats are ordered by a /// > mutable key (last_activity), so pagination alone cannot guarantee a @@ -1337,6 +2047,11 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > Source IDL Documentation: /// > /// > JoinChat adds the caller to a chat's roster. + /// > + /// > A private group (see Metadata.is_private) cannot be joined this way and + /// > is DENIED: a user enters its lobby with EnterLobby and is admitted by + /// > its creator. The one exception is the group's creator, who needs no + /// > approval and rejoins a private group they left with this RPC. /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_JoinChatRequest` message. @@ -1366,6 +2081,11 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > Source IDL Documentation: /// > /// > LeaveChat removes the caller from a chat's roster. + /// > + /// > Leaving a private group discards the caller's key envelope (see + /// > KeyEnvelope), so returning means entering the lobby and being admitted + /// > again. The creator's envelope is kept, since no one else could give + /// > them another. /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_LeaveChatRequest` message. @@ -1377,127 +2097,418 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { public func leaveChat( request: GRPCCore.ClientRequest, options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.leaveChat( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "EditChat" method. + /// + /// > Source IDL Documentation: + /// > + /// > EditChat edits a group chat's record. Every editable field is optional; + /// > only the ones set in the request are changed, and the edit is atomic: + /// > if any part is refused, nothing is applied. + /// > + /// > Only a group chat may be edited, and only by a member the server permits + /// > to edit it, as reported by ViewerState.Permissions.can_edit; anyone else + /// > is DENIED. A new title is moderated like + /// > StartChat's. A new picture is a blob the caller has already uploaded via + /// > BlobStorage: the client uploads only the ORIGINAL and passes the + /// > resulting BlobId once the blob is READY, and the server derives the + /// > remaining renditions. Setting a field to the value the chat already has + /// > is a no-op for that field, and a request that sets nothing is a no-op + /// > that returns OK. + /// > + /// > Every real change reaches the chat's members, including the caller's + /// > other devices, on the event stream as one MetadataUpdate per field + /// > changed: TitleChanged for the title, PictureChanged for the picture. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_EditChatRequest` message. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func editChat( + request: GRPCCore.ClientRequest, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.editChat( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "MuteChat" method. + /// + /// > Source IDL Documentation: + /// > + /// > MuteChat mutes a chat for the caller, until a time or indefinitely. + /// > + /// > Only a member may mute. Muting does not affect the event stream or + /// > message delivery: pushes for a muted chat are still sent, flagged with + /// > push.v1.ChatMetadata.muted, and the client suppresses the notification. + /// > Calling it again with a different duration replaces the mute. An + /// > identical request is a no-op. A mute is cleared when the caller leaves + /// > the chat. Every real change reaches the caller's other devices as a + /// > MetadataUpdate.ViewerStateChanged on the event stream. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_MuteChatRequest` message. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func muteChat( + request: GRPCCore.ClientRequest, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.muteChat( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "UnmuteChat" method. + /// + /// > Source IDL Documentation: + /// > + /// > UnmuteChat clears the caller's mute on a chat. Unmuting a chat that is + /// > not muted is a no-op. Every real change reaches the caller's other devices + /// > as a MetadataUpdate.ViewerStateChanged on the event stream. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_UnmuteChatRequest` message. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func unmuteChat( + request: GRPCCore.ClientRequest, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.unmuteChat( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "EnterLobby" method. + /// + /// > Source IDL Documentation: + /// > + /// > EnterLobby places the caller in a private group's lobby, where they + /// > wait for the group's creator to admit them. It is how a user asks to + /// > join a private group (see Metadata.is_private); JoinChat is DENIED for + /// > one. + /// > + /// > Entering a lobby the caller is already in is a no-op that returns OK. + /// > The server caps both how many users may wait in one lobby and how many + /// > lobbies one user may wait in. A group whose creator has not stored its + /// > key yet (see StartChatRequest.PrivateGroupChatParameters) has no lobby + /// > to enter, and is DENIED until they have. + /// > + /// > A lobby is visible only to the group's creator, through + /// > GetLobbyMembers: the users waiting in it are not shown to each other or + /// > to the group's members. The creator's devices learn of the entry as a + /// > LobbyUpdate on the event stream. + /// > + /// > While the caller waits, the chat's Metadata carries in_lobby. The + /// > caller leaves the lobby when they withdraw (LeaveLobby), when the + /// > creator denies them (DenyLobbyMember), or when the creator admits them + /// > (AdmitLobbyMember). An admission reaches the caller's devices as a + /// > RosterUpdate.MemberJoined naming them; a denial is not announced to + /// > them. + /// > + /// > There is no RPC that lists the lobbies a caller is waiting in. A client + /// > keeps the chats it entered itself, and reads in_lobby from GetChat to + /// > learn whether it is still waiting in one. A paged listing may be added + /// > later. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_EnterLobbyRequest` message. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func enterLobby( + request: GRPCCore.ClientRequest, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.enterLobby( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "LeaveLobby" method. + /// + /// > Source IDL Documentation: + /// > + /// > LeaveLobby withdraws the caller from a private group's lobby. Leaving a + /// > lobby the caller is not in is a no-op that returns OK. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_LeaveLobbyRequest` message. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func leaveLobby( + request: GRPCCore.ClientRequest, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.leaveLobby( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetLobbyMembers" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetLobbyMembers pages the users waiting in a private group's lobby, + /// > earliest entered first. + /// > + /// > Only the group's creator may call it, and only while a member of the + /// > group; anyone else is DENIED. The page is read from an index that + /// > trails writes briefly, so a user who just entered may be absent and one + /// > who just left may be present. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetLobbyMembersRequest` message. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func getLobbyMembers( + request: GRPCCore.ClientRequest, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.getLobbyMembers( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "AdmitLobbyMember" method. + /// + /// > Source IDL Documentation: + /// > + /// > AdmitLobbyMember admits a user waiting in a private group's lobby: + /// > it stores the chat key wrapped for them (see KeyEnvelope) and adds them + /// > to the chat's roster. The envelope is stored first, so an admitted + /// > member always has one. + /// > + /// > Only the group's creator may call it, and only while a member of the + /// > group; anyone else is DENIED. So is a creator who has not stored their + /// > own key envelope yet (see StartChatRequest.PrivateGroupChatParameters): + /// > nobody is admitted to a group before its creator holds its key. This is + /// > the only way to give another user a key envelope. Admitting a user who + /// > is already a member is a no-op that returns OK and leaves their stored + /// > envelope as it is. + /// > + /// > The admission is announced like any other join: a + /// > RosterUpdate.MemberJoined to the chat's members and to the admitted + /// > user, who then fetches their envelope with GetKeyEnvelope. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_AdmitLobbyMemberRequest` message. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func admitLobbyMember( + request: GRPCCore.ClientRequest, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in try response.message } ) async throws -> Result where Result: Sendable { - try await self.leaveChat( + try await self.admitLobbyMember( request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), options: options, onResponse: handleResponse ) } - /// Call the "EditChat" method. + /// Call the "DenyLobbyMember" method. /// /// > Source IDL Documentation: /// > - /// > EditChat edits a group chat's record. Every editable field is optional; - /// > only the ones set in the request are changed, and the edit is atomic: - /// > if any part is refused, nothing is applied. - /// > - /// > Only a group chat may be edited, and only by a member the server permits - /// > to edit it, as reported by ViewerState.Permissions.can_edit; anyone else - /// > is DENIED. A new title is moderated like - /// > StartChat's. A new picture is a blob the caller has already uploaded via - /// > BlobStorage: the client uploads only the ORIGINAL and passes the - /// > resulting BlobId once the blob is READY, and the server derives the - /// > remaining renditions. Setting a field to the value the chat already has - /// > is a no-op for that field, and a request that sets nothing is a no-op - /// > that returns OK. + /// > DenyLobbyMember removes a user from a private group's lobby without + /// > admitting them. Only the group's creator may call it, and only while a + /// > member of the group; anyone else is DENIED. Denying a user who is not + /// > in the lobby is a no-op that returns OK. /// > - /// > Every real change reaches the chat's members, including the caller's - /// > other devices, on the event stream as one MetadataUpdate per field - /// > changed: TitleChanged for the title, PictureChanged for the picture. + /// > The denied user is not notified. The chat's Metadata stops carrying + /// > in_lobby for them, and they may enter the lobby again. /// /// - Parameters: - /// - request: A request containing a single `Flipcash_Chat_V1_EditChatRequest` message. + /// - request: A request containing a single `Flipcash_Chat_V1_DenyLobbyMemberRequest` message. /// - options: Options to apply to this RPC. /// - handleResponse: A closure which handles the response, the result of which is /// returned to the caller. Returning from the closure will cancel the RPC if it /// hasn't already finished. /// - Returns: The result of `handleResponse`. - public func editChat( - request: GRPCCore.ClientRequest, + public func denyLobbyMember( + request: GRPCCore.ClientRequest, options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in try response.message } ) async throws -> Result where Result: Sendable { - try await self.editChat( + try await self.denyLobbyMember( request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), options: options, onResponse: handleResponse ) } - /// Call the "MuteChat" method. + /// Call the "SetKeyEnvelope" method. /// /// > Source IDL Documentation: /// > - /// > MuteChat mutes a chat for the caller, until a time or indefinitely. + /// > SetKeyEnvelope stores the caller's own key envelope for a private + /// > group. Only a member may call it, and only for themself: an envelope + /// > for another user is stored by admitting them (AdmitLobbyMember). /// > - /// > Only a member may mute. Muting does not affect the event stream or - /// > message delivery: pushes for a muted chat are still sent, flagged with - /// > push.v1.ChatMetadata.muted, and the client suppresses the notification. - /// > Calling it again with a different duration replaces the mute. An - /// > identical request is a no-op. A mute is cleared when the caller leaves - /// > the chat. Every real change reaches the caller's other devices as a - /// > MetadataUpdate.ViewerStateChanged on the event stream. + /// > It is called in two situations (see KeyEnvelope): + /// > - By the group's creator, right after StartChat returns the chat's + /// > ID, to store the chat key they have just generated. This is the + /// > second step of creating a private group, and nothing can happen in + /// > the group until it succeeds (see + /// > StartChatRequest.PrivateGroupChatParameters). The creator's client + /// > keeps attempting it until it returns OK or ALREADY_SET: on a failed + /// > call, on its next launch, and on any device that finds the group + /// > with NO_ENVELOPE. + /// > - By an admitted member, after first opening the envelope the creator + /// > wrapped for them, to replace it with one wrapped by themself. + /// > + /// > The first envelope a caller stores for themself stands. Once the + /// > stored envelope is one the caller wrapped, a call with a different + /// > envelope changes nothing and returns ALREADY_SET, and the caller uses + /// > the stored one from GetKeyEnvelope. This keeps two of a creator's + /// > devices, each setting up the same new group, from holding different + /// > keys. Storing an envelope identical to the one stored is a no-op that + /// > returns OK. + /// > + /// > Nothing is published: the caller's other devices fetch the envelope + /// > when they need it. /// /// - Parameters: - /// - request: A request containing a single `Flipcash_Chat_V1_MuteChatRequest` message. + /// - request: A request containing a single `Flipcash_Chat_V1_SetKeyEnvelopeRequest` message. /// - options: Options to apply to this RPC. /// - handleResponse: A closure which handles the response, the result of which is /// returned to the caller. Returning from the closure will cancel the RPC if it /// hasn't already finished. /// - Returns: The result of `handleResponse`. - public func muteChat( - request: GRPCCore.ClientRequest, + public func setKeyEnvelope( + request: GRPCCore.ClientRequest, options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in try response.message } ) async throws -> Result where Result: Sendable { - try await self.muteChat( + try await self.setKeyEnvelope( request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), options: options, onResponse: handleResponse ) } - /// Call the "UnmuteChat" method. + /// Call the "GetKeyEnvelope" method. /// /// > Source IDL Documentation: /// > - /// > UnmuteChat clears the caller's mute on a chat. Unmuting a chat that is - /// > not muted is a no-op. Every real change reaches the caller's other devices - /// > as a MetadataUpdate.ViewerStateChanged on the event stream. + /// > GetKeyEnvelope returns the caller's own key envelope for a private + /// > group. Only a member may call it. The chat key never changes, so a + /// > client calls this once per chat per install and keeps the key it + /// > unwraps. + /// > + /// > NO_ENVELOPE is expected only for the creator of a group whose creation + /// > stopped between StartChat and SetKeyEnvelope. A creator's client that + /// > receives it generates a chat key and stores its envelope with + /// > SetKeyEnvelope, as described on KeyEnvelope, and keeps attempting that + /// > until it succeeds. /// /// - Parameters: - /// - request: A request containing a single `Flipcash_Chat_V1_UnmuteChatRequest` message. + /// - request: A request containing a single `Flipcash_Chat_V1_GetKeyEnvelopeRequest` message. /// - options: Options to apply to this RPC. /// - handleResponse: A closure which handles the response, the result of which is /// returned to the caller. Returning from the closure will cancel the RPC if it /// hasn't already finished. /// - Returns: The result of `handleResponse`. - public func unmuteChat( - request: GRPCCore.ClientRequest, + public func getKeyEnvelope( + request: GRPCCore.ClientRequest, options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in try response.message } ) async throws -> Result where Result: Sendable { - try await self.unmuteChat( + try await self.getKeyEnvelope( request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), options: options, onResponse: handleResponse ) @@ -1518,6 +2529,14 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > with view_mode REDACTED and none of the per-viewer fields (is_hidden, /// > viewer_state). An unauthenticated read under any other view_mode, or of /// > a DM, is DENIED. + /// > + /// > A private group (see Metadata.is_private) returns its record to any + /// > registered user, so that one who is not a member can see what they are + /// > asking to join, but its messaging state only to a member, whatever the + /// > view_mode. It has no public view: an unauthenticated read of one is + /// > DENIED. A private group whose creator has not stored its key yet (see + /// > StartChatRequest.PrivateGroupChatParameters) is returned like any + /// > other: it is visible, though nothing can happen in it. /// /// - Parameters: /// - message: request message to send. @@ -1801,6 +2820,11 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > Source IDL Documentation: /// > /// > JoinChat adds the caller to a chat's roster. + /// > + /// > A private group (see Metadata.is_private) cannot be joined this way and + /// > is DENIED: a user enters its lobby with EnterLobby and is admitted by + /// > its creator. The one exception is the group's creator, who needs no + /// > approval and rejoins a private group they left with this RPC. /// /// - Parameters: /// - message: request message to send. @@ -1834,6 +2858,11 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > Source IDL Documentation: /// > /// > LeaveChat removes the caller from a chat's roster. + /// > + /// > Leaving a private group discards the caller's key envelope (see + /// > KeyEnvelope), so returning means entering the lobby and being admitted + /// > again. The creator's envelope is kept, since no one else could give + /// > them another. /// /// - Parameters: /// - message: request message to send. @@ -1986,4 +3015,323 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { onResponse: handleResponse ) } + + /// Call the "EnterLobby" method. + /// + /// > Source IDL Documentation: + /// > + /// > EnterLobby places the caller in a private group's lobby, where they + /// > wait for the group's creator to admit them. It is how a user asks to + /// > join a private group (see Metadata.is_private); JoinChat is DENIED for + /// > one. + /// > + /// > Entering a lobby the caller is already in is a no-op that returns OK. + /// > The server caps both how many users may wait in one lobby and how many + /// > lobbies one user may wait in. A group whose creator has not stored its + /// > key yet (see StartChatRequest.PrivateGroupChatParameters) has no lobby + /// > to enter, and is DENIED until they have. + /// > + /// > A lobby is visible only to the group's creator, through + /// > GetLobbyMembers: the users waiting in it are not shown to each other or + /// > to the group's members. The creator's devices learn of the entry as a + /// > LobbyUpdate on the event stream. + /// > + /// > While the caller waits, the chat's Metadata carries in_lobby. The + /// > caller leaves the lobby when they withdraw (LeaveLobby), when the + /// > creator denies them (DenyLobbyMember), or when the creator admits them + /// > (AdmitLobbyMember). An admission reaches the caller's devices as a + /// > RosterUpdate.MemberJoined naming them; a denial is not announced to + /// > them. + /// > + /// > There is no RPC that lists the lobbies a caller is waiting in. A client + /// > keeps the chats it entered itself, and reads in_lobby from GetChat to + /// > learn whether it is still waiting in one. A paged listing may be added + /// > later. + /// + /// - Parameters: + /// - message: request message to send. + /// - metadata: Additional metadata to send, defaults to empty. + /// - options: Options to apply to this RPC, defaults to `.defaults`. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func enterLobby( + _ message: Flipcash_Chat_V1_EnterLobbyRequest, + metadata: GRPCCore.Metadata = [:], + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + let request = GRPCCore.ClientRequest( + message: message, + metadata: metadata + ) + return try await self.enterLobby( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "LeaveLobby" method. + /// + /// > Source IDL Documentation: + /// > + /// > LeaveLobby withdraws the caller from a private group's lobby. Leaving a + /// > lobby the caller is not in is a no-op that returns OK. + /// + /// - Parameters: + /// - message: request message to send. + /// - metadata: Additional metadata to send, defaults to empty. + /// - options: Options to apply to this RPC, defaults to `.defaults`. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func leaveLobby( + _ message: Flipcash_Chat_V1_LeaveLobbyRequest, + metadata: GRPCCore.Metadata = [:], + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + let request = GRPCCore.ClientRequest( + message: message, + metadata: metadata + ) + return try await self.leaveLobby( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetLobbyMembers" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetLobbyMembers pages the users waiting in a private group's lobby, + /// > earliest entered first. + /// > + /// > Only the group's creator may call it, and only while a member of the + /// > group; anyone else is DENIED. The page is read from an index that + /// > trails writes briefly, so a user who just entered may be absent and one + /// > who just left may be present. + /// + /// - Parameters: + /// - message: request message to send. + /// - metadata: Additional metadata to send, defaults to empty. + /// - options: Options to apply to this RPC, defaults to `.defaults`. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func getLobbyMembers( + _ message: Flipcash_Chat_V1_GetLobbyMembersRequest, + metadata: GRPCCore.Metadata = [:], + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + let request = GRPCCore.ClientRequest( + message: message, + metadata: metadata + ) + return try await self.getLobbyMembers( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "AdmitLobbyMember" method. + /// + /// > Source IDL Documentation: + /// > + /// > AdmitLobbyMember admits a user waiting in a private group's lobby: + /// > it stores the chat key wrapped for them (see KeyEnvelope) and adds them + /// > to the chat's roster. The envelope is stored first, so an admitted + /// > member always has one. + /// > + /// > Only the group's creator may call it, and only while a member of the + /// > group; anyone else is DENIED. So is a creator who has not stored their + /// > own key envelope yet (see StartChatRequest.PrivateGroupChatParameters): + /// > nobody is admitted to a group before its creator holds its key. This is + /// > the only way to give another user a key envelope. Admitting a user who + /// > is already a member is a no-op that returns OK and leaves their stored + /// > envelope as it is. + /// > + /// > The admission is announced like any other join: a + /// > RosterUpdate.MemberJoined to the chat's members and to the admitted + /// > user, who then fetches their envelope with GetKeyEnvelope. + /// + /// - Parameters: + /// - message: request message to send. + /// - metadata: Additional metadata to send, defaults to empty. + /// - options: Options to apply to this RPC, defaults to `.defaults`. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func admitLobbyMember( + _ message: Flipcash_Chat_V1_AdmitLobbyMemberRequest, + metadata: GRPCCore.Metadata = [:], + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + let request = GRPCCore.ClientRequest( + message: message, + metadata: metadata + ) + return try await self.admitLobbyMember( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "DenyLobbyMember" method. + /// + /// > Source IDL Documentation: + /// > + /// > DenyLobbyMember removes a user from a private group's lobby without + /// > admitting them. Only the group's creator may call it, and only while a + /// > member of the group; anyone else is DENIED. Denying a user who is not + /// > in the lobby is a no-op that returns OK. + /// > + /// > The denied user is not notified. The chat's Metadata stops carrying + /// > in_lobby for them, and they may enter the lobby again. + /// + /// - Parameters: + /// - message: request message to send. + /// - metadata: Additional metadata to send, defaults to empty. + /// - options: Options to apply to this RPC, defaults to `.defaults`. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func denyLobbyMember( + _ message: Flipcash_Chat_V1_DenyLobbyMemberRequest, + metadata: GRPCCore.Metadata = [:], + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + let request = GRPCCore.ClientRequest( + message: message, + metadata: metadata + ) + return try await self.denyLobbyMember( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "SetKeyEnvelope" method. + /// + /// > Source IDL Documentation: + /// > + /// > SetKeyEnvelope stores the caller's own key envelope for a private + /// > group. Only a member may call it, and only for themself: an envelope + /// > for another user is stored by admitting them (AdmitLobbyMember). + /// > + /// > It is called in two situations (see KeyEnvelope): + /// > - By the group's creator, right after StartChat returns the chat's + /// > ID, to store the chat key they have just generated. This is the + /// > second step of creating a private group, and nothing can happen in + /// > the group until it succeeds (see + /// > StartChatRequest.PrivateGroupChatParameters). The creator's client + /// > keeps attempting it until it returns OK or ALREADY_SET: on a failed + /// > call, on its next launch, and on any device that finds the group + /// > with NO_ENVELOPE. + /// > - By an admitted member, after first opening the envelope the creator + /// > wrapped for them, to replace it with one wrapped by themself. + /// > + /// > The first envelope a caller stores for themself stands. Once the + /// > stored envelope is one the caller wrapped, a call with a different + /// > envelope changes nothing and returns ALREADY_SET, and the caller uses + /// > the stored one from GetKeyEnvelope. This keeps two of a creator's + /// > devices, each setting up the same new group, from holding different + /// > keys. Storing an envelope identical to the one stored is a no-op that + /// > returns OK. + /// > + /// > Nothing is published: the caller's other devices fetch the envelope + /// > when they need it. + /// + /// - Parameters: + /// - message: request message to send. + /// - metadata: Additional metadata to send, defaults to empty. + /// - options: Options to apply to this RPC, defaults to `.defaults`. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func setKeyEnvelope( + _ message: Flipcash_Chat_V1_SetKeyEnvelopeRequest, + metadata: GRPCCore.Metadata = [:], + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + let request = GRPCCore.ClientRequest( + message: message, + metadata: metadata + ) + return try await self.setKeyEnvelope( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetKeyEnvelope" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetKeyEnvelope returns the caller's own key envelope for a private + /// > group. Only a member may call it. The chat key never changes, so a + /// > client calls this once per chat per install and keeps the key it + /// > unwraps. + /// > + /// > NO_ENVELOPE is expected only for the creator of a group whose creation + /// > stopped between StartChat and SetKeyEnvelope. A creator's client that + /// > receives it generates a chat key and stores its envelope with + /// > SetKeyEnvelope, as described on KeyEnvelope, and keeps attempting that + /// > until it succeeds. + /// + /// - Parameters: + /// - message: request message to send. + /// - metadata: Additional metadata to send, defaults to empty. + /// - options: Options to apply to this RPC, defaults to `.defaults`. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func getKeyEnvelope( + _ message: Flipcash_Chat_V1_GetKeyEnvelopeRequest, + metadata: GRPCCore.Metadata = [:], + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + let request = GRPCCore.ClientRequest( + message: message, + metadata: metadata + ) + return try await self.getKeyEnvelope( + request: request, + options: options, + onResponse: handleResponse + ) + } } \ No newline at end of file diff --git a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift index 21c3830..e28564f 100644 --- a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift +++ b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift @@ -593,15 +593,24 @@ public struct Flipcash_Chat_V1_StartChatRequest: Sendable { // methods supported on all messages. /// Parameters for the kind of chat being created. The variant selects the - /// chat type. + /// kind of chat. Both create a chat of type GROUP: one public, one + /// private. public var parameters: Flipcash_Chat_V1_StartChatRequest.OneOf_Parameters? = nil - public var group: Flipcash_Chat_V1_StartChatRequest.GroupChatParameters { + public var publicGroup: Flipcash_Chat_V1_StartChatRequest.PublicGroupChatParameters { get { - if case .group(let v)? = parameters {return v} - return Flipcash_Chat_V1_StartChatRequest.GroupChatParameters() + if case .publicGroup(let v)? = parameters {return v} + return Flipcash_Chat_V1_StartChatRequest.PublicGroupChatParameters() } - set {parameters = .group(newValue)} + set {parameters = .publicGroup(newValue)} + } + + public var privateGroup: Flipcash_Chat_V1_StartChatRequest.PrivateGroupChatParameters { + get { + if case .privateGroup(let v)? = parameters {return v} + return Flipcash_Chat_V1_StartChatRequest.PrivateGroupChatParameters() + } + set {parameters = .privateGroup(newValue)} } /// Key for retrying this request safely. A retry with the same key from the @@ -628,14 +637,17 @@ public struct Flipcash_Chat_V1_StartChatRequest: Sendable { public var unknownFields = SwiftProtobuf.UnknownStorage() /// Parameters for the kind of chat being created. The variant selects the - /// chat type. + /// kind of chat. Both create a chat of type GROUP: one public, one + /// private. public enum OneOf_Parameters: Equatable, Sendable { - case group(Flipcash_Chat_V1_StartChatRequest.GroupChatParameters) + case publicGroup(Flipcash_Chat_V1_StartChatRequest.PublicGroupChatParameters) + case privateGroup(Flipcash_Chat_V1_StartChatRequest.PrivateGroupChatParameters) } - /// Parameters for creating a group chat - public struct GroupChatParameters: Sendable { + /// Parameters for creating a public group chat: one that users join with + /// JoinChat, subject to its rules. + public struct PublicGroupChatParameters: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -674,6 +686,53 @@ public struct Flipcash_Chat_V1_StartChatRequest: Sendable { fileprivate var _rules: Flipcash_Chat_V1_Rules? = nil } + /// Parameters for creating a private group chat (see Metadata.is_private): + /// one whose creator admits each member from its lobby, and whose messages + /// are end-to-end encrypted. Whether a group is private is fixed for the + /// life of the chat. A private group has no rules. + /// + /// Creating a private group takes two steps. A key envelope is bound to + /// the chat's ID, which the caller learns only from the response, so the + /// caller generates the chat key once StartChat returns and stores its own + /// envelope with SetKeyEnvelope (see KeyEnvelope). + /// + /// Until the creator's envelope is stored, the group has no key. It + /// exists and is visible like any other private group: GetChat returns + /// it, and it appears in the creator's GetGroupChatFeed. But nothing can + /// happen in it, and the server refuses each of these as DENIED: + /// - EnterLobby, by anyone + /// - AdmitLobbyMember + /// - Messaging.SendMessage and Messaging.EditMessage + /// - BlobStorage.InitiateExternalUpload of a blob encrypted for the chat + /// The creator's client must keep attempting SetKeyEnvelope until it + /// succeeds. Storing the envelope lifts every refusal above, and it is + /// never undone: the creator's envelope is kept even if they leave. + public struct PrivateGroupChatParameters: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// Title for the chat. + public var title: String = String() + + /// The blob holding the ORIGINAL picture the caller uploaded. Optional. + /// If set, it must be owned by the caller and READY. + public var picture: Flipcash_Blob_V1_BlobId { + get {return _picture ?? Flipcash_Blob_V1_BlobId()} + set {_picture = newValue} + } + /// Returns true if `picture` has been explicitly set. + public var hasPicture: Bool {return self._picture != nil} + /// Clears the value of `picture`. Subsequent reads from it will return its default value. + public mutating func clearPicture() {self._picture = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _picture: Flipcash_Blob_V1_BlobId? = nil + } + public init() {} fileprivate var _idempotencyKey: Flipcash_Chat_V1_IdempotencyKey? = nil @@ -1307,205 +1366,1510 @@ public struct Flipcash_Chat_V1_UnmuteChatResponse: Sendable { fileprivate var _viewerState: Flipcash_Chat_V1_ViewerState? = nil } -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "flipcash.chat.v1" - -extension Flipcash_Chat_V1_GetChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".GetChatRequest" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{3}view_mode\0\u{2}\u{8}auth\0") +public struct Flipcash_Chat_V1_EnterLobbyRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() - case 2: try { try decoder.decodeSingularEnumField(value: &self.viewMode) }() - case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() - default: break - } - } + public var chatID: Flipcash_Common_V1_ChatId { + get {return _chatID ?? Flipcash_Common_V1_ChatId()} + set {_chatID = newValue} } + /// Returns true if `chatID` has been explicitly set. + public var hasChatID: Bool {return self._chatID != nil} + /// Clears the value of `chatID`. Subsequent reads from it will return its default value. + public mutating func clearChatID() {self._chatID = nil} - public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._chatID { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - if self.viewMode != .full { - try visitor.visitSingularEnumField(value: self.viewMode, fieldNumber: 2) - } - try { if let v = self._auth { - try visitor.visitSingularMessageField(value: v, fieldNumber: 10) - } }() - try unknownFields.traverse(visitor: &visitor) + public var auth: Flipcash_Common_V1_Auth { + get {return _auth ?? Flipcash_Common_V1_Auth()} + set {_auth = newValue} } + /// Returns true if `auth` has been explicitly set. + public var hasAuth: Bool {return self._auth != nil} + /// Clears the value of `auth`. Subsequent reads from it will return its default value. + public mutating func clearAuth() {self._auth = nil} - public static func ==(lhs: Flipcash_Chat_V1_GetChatRequest, rhs: Flipcash_Chat_V1_GetChatRequest) -> Bool { - if lhs._chatID != rhs._chatID {return false} - if lhs.viewMode != rhs.viewMode {return false} - if lhs._auth != rhs._auth {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil } -extension Flipcash_Chat_V1_GetChatResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".GetChatResponse" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}metadata\0") +public struct Flipcash_Chat_V1_EnterLobbyResponse: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._metadata) }() - default: break - } - } - } + public var result: Flipcash_Chat_V1_EnterLobbyResponse.Result = .ok - public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if self.result != .ok { - try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) - } - try { if let v = self._metadata { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try unknownFields.traverse(visitor: &visitor) + /// The caller's place in the lobby, so the client can record the chat it + /// is now waiting in without a refetch. Set only when result == OK. + public var lobby: Flipcash_Chat_V1_Lobby { + get {return _lobby ?? Flipcash_Chat_V1_Lobby()} + set {_lobby = newValue} } + /// Returns true if `lobby` has been explicitly set. + public var hasLobby: Bool {return self._lobby != nil} + /// Clears the value of `lobby`. Subsequent reads from it will return its default value. + public mutating func clearLobby() {self._lobby = nil} - public static func ==(lhs: Flipcash_Chat_V1_GetChatResponse, rhs: Flipcash_Chat_V1_GetChatResponse) -> Bool { - if lhs.result != rhs.result {return false} - if lhs._metadata != rhs._metadata {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} + public var unknownFields = SwiftProtobuf.UnknownStorage() -extension Flipcash_Chat_V1_GetChatResponse.Result: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") -} + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 -extension Flipcash_Chat_V1_GetDmChatFeedRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".GetDmChatFeedRequest" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}query_options\0\u{3}dm_chat_type\0\u{2}\u{8}auth\0") + /// The chat is not a private group, or has no key yet (see EnterLobby) + case denied // = 1 + case notFound // = 2 - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._queryOptions) }() - case 2: try { try decoder.decodeSingularEnumField(value: &self.dmChatType) }() - case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() - default: break - } - } - } + /// Caller is already a member of the chat + case alreadyMember // = 3 - public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._queryOptions { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - if self.dmChatType != .unknown { - try visitor.visitSingularEnumField(value: self.dmChatType, fieldNumber: 2) - } - try { if let v = self._auth { - try visitor.visitSingularMessageField(value: v, fieldNumber: 10) - } }() - try unknownFields.traverse(visitor: &visitor) - } + /// The chat's lobby holds as many users as the server allows + case lobbyFull // = 4 - public static func ==(lhs: Flipcash_Chat_V1_GetDmChatFeedRequest, rhs: Flipcash_Chat_V1_GetDmChatFeedRequest) -> Bool { - if lhs._queryOptions != rhs._queryOptions {return false} - if lhs.dmChatType != rhs.dmChatType {return false} - if lhs._auth != rhs._auth {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} + /// Caller is waiting in as many lobbies as the server allows + case tooManyLobbies // = 5 + case UNRECOGNIZED(Int) -extension Flipcash_Chat_V1_GetDmChatFeedResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".GetDmChatFeedResponse" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}chats\0\u{3}paging_token\0\u{3}has_more\0") + public init() { + self = .ok + } - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() - case 2: try { try decoder.decodeRepeatedMessageField(value: &self.chats) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._pagingToken) }() - case 4: try { try decoder.decodeSingularBoolField(value: &self.hasMore_p) }() - default: break + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .ok + case 1: self = .denied + case 2: self = .notFound + case 3: self = .alreadyMember + case 4: self = .lobbyFull + case 5: self = .tooManyLobbies + default: self = .UNRECOGNIZED(rawValue) } } - } - public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if self.result != .ok { - try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) - } - if !self.chats.isEmpty { - try visitor.visitRepeatedMessageField(value: self.chats, fieldNumber: 2) - } - try { if let v = self._pagingToken { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - if self.hasMore_p != false { - try visitor.visitSingularBoolField(value: self.hasMore_p, fieldNumber: 4) + public var rawValue: Int { + switch self { + case .ok: return 0 + case .denied: return 1 + case .notFound: return 2 + case .alreadyMember: return 3 + case .lobbyFull: return 4 + case .tooManyLobbies: return 5 + case .UNRECOGNIZED(let i): return i + } } - try unknownFields.traverse(visitor: &visitor) - } - public static func ==(lhs: Flipcash_Chat_V1_GetDmChatFeedResponse, rhs: Flipcash_Chat_V1_GetDmChatFeedResponse) -> Bool { - if lhs.result != rhs.result {return false} - if lhs.chats != rhs.chats {return false} - if lhs._pagingToken != rhs._pagingToken {return false} - if lhs.hasMore_p != rhs.hasMore_p {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Chat_V1_EnterLobbyResponse.Result] = [ + .ok, + .denied, + .notFound, + .alreadyMember, + .lobbyFull, + .tooManyLobbies, + ] + } -} -extension Flipcash_Chat_V1_GetDmChatFeedResponse.Result: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") + public init() {} + + fileprivate var _lobby: Flipcash_Chat_V1_Lobby? = nil } -extension Flipcash_Chat_V1_GetGroupChatFeedRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".GetGroupChatFeedRequest" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}query_options\0\u{2}\u{9}auth\0") +public struct Flipcash_Chat_V1_LeaveLobbyRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. - public mutating func decodeMessage(decoder: inout D) throws { + public var chatID: Flipcash_Common_V1_ChatId { + get {return _chatID ?? Flipcash_Common_V1_ChatId()} + set {_chatID = newValue} + } + /// Returns true if `chatID` has been explicitly set. + public var hasChatID: Bool {return self._chatID != nil} + /// Clears the value of `chatID`. Subsequent reads from it will return its default value. + public mutating func clearChatID() {self._chatID = nil} + + public var auth: Flipcash_Common_V1_Auth { + get {return _auth ?? Flipcash_Common_V1_Auth()} + set {_auth = newValue} + } + /// Returns true if `auth` has been explicitly set. + public var hasAuth: Bool {return self._auth != nil} + /// Clears the value of `auth`. Subsequent reads from it will return its default value. + public mutating func clearAuth() {self._auth = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Chat_V1_LeaveLobbyResponse: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var result: Flipcash_Chat_V1_LeaveLobbyResponse.Result = .ok + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + + /// The chat is not a private group + case denied // = 1 + case notFound // = 2 + case UNRECOGNIZED(Int) + + public init() { + self = .ok + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .ok + case 1: self = .denied + case 2: self = .notFound + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .denied: return 1 + case .notFound: return 2 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Chat_V1_LeaveLobbyResponse.Result] = [ + .ok, + .denied, + .notFound, + ] + + } + + public init() {} +} + +public struct Flipcash_Chat_V1_GetLobbyMembersRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var chatID: Flipcash_Common_V1_ChatId { + get {return _chatID ?? Flipcash_Common_V1_ChatId()} + set {_chatID = newValue} + } + /// Returns true if `chatID` has been explicitly set. + public var hasChatID: Bool {return self._chatID != nil} + /// Clears the value of `chatID`. Subsequent reads from it will return its default value. + public mutating func clearChatID() {self._chatID = nil} + + /// QueryOptions controls page_size, capped at 100. Ordering is fixed to + /// earliest entered first and is not client-selectable. + /// + /// Leave query_options.paging_token unset on the first request. On every + /// subsequent request, set it to the paging_token from the most recent + /// response. The token is opaque and server-generated; do not construct + /// it. It is bound to chat_id and refused with another chat. + public var queryOptions: Flipcash_Common_V1_QueryOptions { + get {return _queryOptions ?? Flipcash_Common_V1_QueryOptions()} + set {_queryOptions = newValue} + } + /// Returns true if `queryOptions` has been explicitly set. + public var hasQueryOptions: Bool {return self._queryOptions != nil} + /// Clears the value of `queryOptions`. Subsequent reads from it will return its default value. + public mutating func clearQueryOptions() {self._queryOptions = nil} + + public var auth: Flipcash_Common_V1_Auth { + get {return _auth ?? Flipcash_Common_V1_Auth()} + set {_auth = newValue} + } + /// Returns true if `auth` has been explicitly set. + public var hasAuth: Bool {return self._auth != nil} + /// Clears the value of `auth`. Subsequent reads from it will return its default value. + public mutating func clearAuth() {self._auth = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil + fileprivate var _queryOptions: Flipcash_Common_V1_QueryOptions? = nil + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Chat_V1_GetLobbyMembersResponse: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var result: Flipcash_Chat_V1_GetLobbyMembersResponse.Result = .ok + + /// The page, earliest entered first. Set when result is OK. + public var members: [Flipcash_Chat_V1_LobbyMember] = [] + + /// PagingToken carries the cursor after the last member returned. The + /// client MUST send it back in query_options.paging_token on the next + /// GetLobbyMembersRequest. Set when result is OK and has_more is true. + public var pagingToken: Flipcash_Common_V1_PagingToken { + get {return _pagingToken ?? Flipcash_Common_V1_PagingToken()} + set {_pagingToken = newValue} + } + /// Returns true if `pagingToken` has been explicitly set. + public var hasPagingToken: Bool {return self._pagingToken != nil} + /// Clears the value of `pagingToken`. Subsequent reads from it will return its default value. + public mutating func clearPagingToken() {self._pagingToken = nil} + + /// HasMore indicates whether further pages remain. When true, the client + /// should issue another GetLobbyMembersRequest with the returned + /// paging_token. + public var hasMore_p: Bool = false + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + + /// Caller is not the chat's creator, or the chat is not a private group + case denied // = 1 + case notFound // = 2 + case UNRECOGNIZED(Int) + + public init() { + self = .ok + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .ok + case 1: self = .denied + case 2: self = .notFound + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .denied: return 1 + case .notFound: return 2 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Chat_V1_GetLobbyMembersResponse.Result] = [ + .ok, + .denied, + .notFound, + ] + + } + + public init() {} + + fileprivate var _pagingToken: Flipcash_Common_V1_PagingToken? = nil +} + +public struct Flipcash_Chat_V1_AdmitLobbyMemberRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var chatID: Flipcash_Common_V1_ChatId { + get {return _chatID ?? Flipcash_Common_V1_ChatId()} + set {_chatID = newValue} + } + /// Returns true if `chatID` has been explicitly set. + public var hasChatID: Bool {return self._chatID != nil} + /// Clears the value of `chatID`. Subsequent reads from it will return its default value. + public mutating func clearChatID() {self._chatID = nil} + + /// The user to admit, who must be waiting in the chat's lobby. + public var userID: Flipcash_Common_V1_UserId { + get {return _userID ?? Flipcash_Common_V1_UserId()} + set {_userID = newValue} + } + /// Returns true if `userID` has been explicitly set. + public var hasUserID: Bool {return self._userID != nil} + /// Clears the value of `userID`. Subsequent reads from it will return its default value. + public mutating func clearUserID() {self._userID = nil} + + /// The chat key wrapped by the caller for that user's public key, as + /// given by LobbyMember.public_key (see KeyEnvelope). The server cannot + /// check its contents: an envelope the user cannot open leaves them a + /// member who cannot read the chat. + public var keyEnvelope: Flipcash_Chat_V1_KeyEnvelope { + get {return _keyEnvelope ?? Flipcash_Chat_V1_KeyEnvelope()} + set {_keyEnvelope = newValue} + } + /// Returns true if `keyEnvelope` has been explicitly set. + public var hasKeyEnvelope: Bool {return self._keyEnvelope != nil} + /// Clears the value of `keyEnvelope`. Subsequent reads from it will return its default value. + public mutating func clearKeyEnvelope() {self._keyEnvelope = nil} + + public var auth: Flipcash_Common_V1_Auth { + get {return _auth ?? Flipcash_Common_V1_Auth()} + set {_auth = newValue} + } + /// Returns true if `auth` has been explicitly set. + public var hasAuth: Bool {return self._auth != nil} + /// Clears the value of `auth`. Subsequent reads from it will return its default value. + public mutating func clearAuth() {self._auth = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil + fileprivate var _userID: Flipcash_Common_V1_UserId? = nil + fileprivate var _keyEnvelope: Flipcash_Chat_V1_KeyEnvelope? = nil + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Chat_V1_AdmitLobbyMemberResponse: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var result: Flipcash_Chat_V1_AdmitLobbyMemberResponse.Result = .ok + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + + /// Caller may not admit users to this chat (see AdmitLobbyMember) + case denied // = 1 + case notFound // = 2 + + /// The user is neither waiting in the lobby nor a member + case notInLobby // = 3 + case UNRECOGNIZED(Int) + + public init() { + self = .ok + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .ok + case 1: self = .denied + case 2: self = .notFound + case 3: self = .notInLobby + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .denied: return 1 + case .notFound: return 2 + case .notInLobby: return 3 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Chat_V1_AdmitLobbyMemberResponse.Result] = [ + .ok, + .denied, + .notFound, + .notInLobby, + ] + + } + + public init() {} +} + +public struct Flipcash_Chat_V1_DenyLobbyMemberRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var chatID: Flipcash_Common_V1_ChatId { + get {return _chatID ?? Flipcash_Common_V1_ChatId()} + set {_chatID = newValue} + } + /// Returns true if `chatID` has been explicitly set. + public var hasChatID: Bool {return self._chatID != nil} + /// Clears the value of `chatID`. Subsequent reads from it will return its default value. + public mutating func clearChatID() {self._chatID = nil} + + /// The user to remove from the chat's lobby. + public var userID: Flipcash_Common_V1_UserId { + get {return _userID ?? Flipcash_Common_V1_UserId()} + set {_userID = newValue} + } + /// Returns true if `userID` has been explicitly set. + public var hasUserID: Bool {return self._userID != nil} + /// Clears the value of `userID`. Subsequent reads from it will return its default value. + public mutating func clearUserID() {self._userID = nil} + + public var auth: Flipcash_Common_V1_Auth { + get {return _auth ?? Flipcash_Common_V1_Auth()} + set {_auth = newValue} + } + /// Returns true if `auth` has been explicitly set. + public var hasAuth: Bool {return self._auth != nil} + /// Clears the value of `auth`. Subsequent reads from it will return its default value. + public mutating func clearAuth() {self._auth = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil + fileprivate var _userID: Flipcash_Common_V1_UserId? = nil + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Chat_V1_DenyLobbyMemberResponse: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var result: Flipcash_Chat_V1_DenyLobbyMemberResponse.Result = .ok + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + + /// Caller is not the chat's creator, or the chat is not a private group + case denied // = 1 + case notFound // = 2 + case UNRECOGNIZED(Int) + + public init() { + self = .ok + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .ok + case 1: self = .denied + case 2: self = .notFound + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .denied: return 1 + case .notFound: return 2 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Chat_V1_DenyLobbyMemberResponse.Result] = [ + .ok, + .denied, + .notFound, + ] + + } + + public init() {} +} + +public struct Flipcash_Chat_V1_SetKeyEnvelopeRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var chatID: Flipcash_Common_V1_ChatId { + get {return _chatID ?? Flipcash_Common_V1_ChatId()} + set {_chatID = newValue} + } + /// Returns true if `chatID` has been explicitly set. + public var hasChatID: Bool {return self._chatID != nil} + /// Clears the value of `chatID`. Subsequent reads from it will return its default value. + public mutating func clearChatID() {self._chatID = nil} + + /// The chat key wrapped by the caller for themself (see KeyEnvelope). + public var keyEnvelope: Flipcash_Chat_V1_KeyEnvelope { + get {return _keyEnvelope ?? Flipcash_Chat_V1_KeyEnvelope()} + set {_keyEnvelope = newValue} + } + /// Returns true if `keyEnvelope` has been explicitly set. + public var hasKeyEnvelope: Bool {return self._keyEnvelope != nil} + /// Clears the value of `keyEnvelope`. Subsequent reads from it will return its default value. + public mutating func clearKeyEnvelope() {self._keyEnvelope = nil} + + public var auth: Flipcash_Common_V1_Auth { + get {return _auth ?? Flipcash_Common_V1_Auth()} + set {_auth = newValue} + } + /// Returns true if `auth` has been explicitly set. + public var hasAuth: Bool {return self._auth != nil} + /// Clears the value of `auth`. Subsequent reads from it will return its default value. + public mutating func clearAuth() {self._auth = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil + fileprivate var _keyEnvelope: Flipcash_Chat_V1_KeyEnvelope? = nil + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Chat_V1_SetKeyEnvelopeResponse: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var result: Flipcash_Chat_V1_SetKeyEnvelopeResponse.Result = .ok + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + + /// Caller is not a member, or the chat is not a private group + case denied // = 1 + case notFound // = 2 + + /// Caller already stored a different envelope of their own, which stands + case alreadySet // = 3 + case UNRECOGNIZED(Int) + + public init() { + self = .ok + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .ok + case 1: self = .denied + case 2: self = .notFound + case 3: self = .alreadySet + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .denied: return 1 + case .notFound: return 2 + case .alreadySet: return 3 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Chat_V1_SetKeyEnvelopeResponse.Result] = [ + .ok, + .denied, + .notFound, + .alreadySet, + ] + + } + + public init() {} +} + +public struct Flipcash_Chat_V1_GetKeyEnvelopeRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var chatID: Flipcash_Common_V1_ChatId { + get {return _chatID ?? Flipcash_Common_V1_ChatId()} + set {_chatID = newValue} + } + /// Returns true if `chatID` has been explicitly set. + public var hasChatID: Bool {return self._chatID != nil} + /// Clears the value of `chatID`. Subsequent reads from it will return its default value. + public mutating func clearChatID() {self._chatID = nil} + + public var auth: Flipcash_Common_V1_Auth { + get {return _auth ?? Flipcash_Common_V1_Auth()} + set {_auth = newValue} + } + /// Returns true if `auth` has been explicitly set. + public var hasAuth: Bool {return self._auth != nil} + /// Clears the value of `auth`. Subsequent reads from it will return its default value. + public mutating func clearAuth() {self._auth = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Chat_V1_GetKeyEnvelopeResponse: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var result: Flipcash_Chat_V1_GetKeyEnvelopeResponse.Result = .ok + + /// The caller's key envelope. Set only when result == OK. + public var keyEnvelope: Flipcash_Chat_V1_KeyEnvelope { + get {return _keyEnvelope ?? Flipcash_Chat_V1_KeyEnvelope()} + set {_keyEnvelope = newValue} + } + /// Returns true if `keyEnvelope` has been explicitly set. + public var hasKeyEnvelope: Bool {return self._keyEnvelope != nil} + /// Clears the value of `keyEnvelope`. Subsequent reads from it will return its default value. + public mutating func clearKeyEnvelope() {self._keyEnvelope = nil} + + /// The user who stored the envelope, as the server authenticated them: + /// the caller, or the chat's creator. It tells the client whose public + /// key to open the envelope against (see KeyEnvelope), and is only a + /// hint: an envelope that does not open against that key is rejected. Set + /// only when result == OK. + public var wrappedBy: Flipcash_Common_V1_UserId { + get {return _wrappedBy ?? Flipcash_Common_V1_UserId()} + set {_wrappedBy = newValue} + } + /// Returns true if `wrappedBy` has been explicitly set. + public var hasWrappedBy: Bool {return self._wrappedBy != nil} + /// Clears the value of `wrappedBy`. Subsequent reads from it will return its default value. + public mutating func clearWrappedBy() {self._wrappedBy = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + + /// Caller is not a member, or the chat is not a private group + case denied // = 1 + case notFound // = 2 + + /// Caller is a member with no envelope stored + case noEnvelope // = 3 + case UNRECOGNIZED(Int) + + public init() { + self = .ok + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .ok + case 1: self = .denied + case 2: self = .notFound + case 3: self = .noEnvelope + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .denied: return 1 + case .notFound: return 2 + case .noEnvelope: return 3 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Chat_V1_GetKeyEnvelopeResponse.Result] = [ + .ok, + .denied, + .notFound, + .noEnvelope, + ] + + } + + public init() {} + + fileprivate var _keyEnvelope: Flipcash_Chat_V1_KeyEnvelope? = nil + fileprivate var _wrappedBy: Flipcash_Common_V1_UserId? = nil +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "flipcash.chat.v1" + +extension Flipcash_Chat_V1_GetChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetChatRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{3}view_mode\0\u{2}\u{8}auth\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() + case 2: try { try decoder.decodeSingularEnumField(value: &self.viewMode) }() + case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._chatID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + if self.viewMode != .full { + try visitor.visitSingularEnumField(value: self.viewMode, fieldNumber: 2) + } + try { if let v = self._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_GetChatRequest, rhs: Flipcash_Chat_V1_GetChatRequest) -> Bool { + if lhs._chatID != rhs._chatID {return false} + if lhs.viewMode != rhs.viewMode {return false} + if lhs._auth != rhs._auth {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_GetChatResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetChatResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}metadata\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._metadata) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) + } + try { if let v = self._metadata { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_GetChatResponse, rhs: Flipcash_Chat_V1_GetChatResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs._metadata != rhs._metadata {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_GetChatResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") +} + +extension Flipcash_Chat_V1_GetDmChatFeedRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetDmChatFeedRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}query_options\0\u{3}dm_chat_type\0\u{2}\u{8}auth\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._queryOptions) }() + case 2: try { try decoder.decodeSingularEnumField(value: &self.dmChatType) }() + case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._queryOptions { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + if self.dmChatType != .unknown { + try visitor.visitSingularEnumField(value: self.dmChatType, fieldNumber: 2) + } + try { if let v = self._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_GetDmChatFeedRequest, rhs: Flipcash_Chat_V1_GetDmChatFeedRequest) -> Bool { + if lhs._queryOptions != rhs._queryOptions {return false} + if lhs.dmChatType != rhs.dmChatType {return false} + if lhs._auth != rhs._auth {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_GetDmChatFeedResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetDmChatFeedResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}chats\0\u{3}paging_token\0\u{3}has_more\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.chats) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._pagingToken) }() + case 4: try { try decoder.decodeSingularBoolField(value: &self.hasMore_p) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) + } + if !self.chats.isEmpty { + try visitor.visitRepeatedMessageField(value: self.chats, fieldNumber: 2) + } + try { if let v = self._pagingToken { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + if self.hasMore_p != false { + try visitor.visitSingularBoolField(value: self.hasMore_p, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_GetDmChatFeedResponse, rhs: Flipcash_Chat_V1_GetDmChatFeedResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs.chats != rhs.chats {return false} + if lhs._pagingToken != rhs._pagingToken {return false} + if lhs.hasMore_p != rhs.hasMore_p {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_GetDmChatFeedResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") +} + +extension Flipcash_Chat_V1_GetGroupChatFeedRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetGroupChatFeedRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}query_options\0\u{2}\u{9}auth\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._queryOptions) }() + case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._queryOptions { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_GetGroupChatFeedRequest, rhs: Flipcash_Chat_V1_GetGroupChatFeedRequest) -> Bool { + if lhs._queryOptions != rhs._queryOptions {return false} + if lhs._auth != rhs._auth {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_GetGroupChatFeedResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetGroupChatFeedResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}chats\0\u{3}paging_token\0\u{3}has_more\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.chats) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._pagingToken) }() + case 4: try { try decoder.decodeSingularBoolField(value: &self.hasMore_p) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) + } + if !self.chats.isEmpty { + try visitor.visitRepeatedMessageField(value: self.chats, fieldNumber: 2) + } + try { if let v = self._pagingToken { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + if self.hasMore_p != false { + try visitor.visitSingularBoolField(value: self.hasMore_p, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_GetGroupChatFeedResponse, rhs: Flipcash_Chat_V1_GetGroupChatFeedResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs.chats != rhs.chats {return false} + if lhs._pagingToken != rhs._pagingToken {return false} + if lhs.hasMore_p != rhs.hasMore_p {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_GetGroupChatFeedResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") +} + +extension Flipcash_Chat_V1_GetRosterRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetRosterRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{3}query_options\0\u{2}\u{8}auth\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._queryOptions) }() + case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._chatID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._queryOptions { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = self._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_GetRosterRequest, rhs: Flipcash_Chat_V1_GetRosterRequest) -> Bool { + if lhs._chatID != rhs._chatID {return false} + if lhs._queryOptions != rhs._queryOptions {return false} + if lhs._auth != rhs._auth {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_GetRosterResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetRosterResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}members\0\u{3}roster_summary\0\u{3}paging_token\0\u{3}has_more\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.members) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._rosterSummary) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._pagingToken) }() + case 5: try { try decoder.decodeSingularBoolField(value: &self.hasMore_p) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) + } + if !self.members.isEmpty { + try visitor.visitRepeatedMessageField(value: self.members, fieldNumber: 2) + } + try { if let v = self._rosterSummary { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + try { if let v = self._pagingToken { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + if self.hasMore_p != false { + try visitor.visitSingularBoolField(value: self.hasMore_p, fieldNumber: 5) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_GetRosterResponse, rhs: Flipcash_Chat_V1_GetRosterResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs.members != rhs.members {return false} + if lhs._rosterSummary != rhs._rosterSummary {return false} + if lhs._pagingToken != rhs._pagingToken {return false} + if lhs.hasMore_p != rhs.hasMore_p {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_GetRosterResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") +} + +extension Flipcash_Chat_V1_GetMentionSuggestionsRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetMentionSuggestionsRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{2}\u{9}auth\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() + case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._chatID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_GetMentionSuggestionsRequest, rhs: Flipcash_Chat_V1_GetMentionSuggestionsRequest) -> Bool { + if lhs._chatID != rhs._chatID {return false} + if lhs._auth != rhs._auth {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_GetMentionSuggestionsResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetMentionSuggestionsResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}suggestions\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.suggestions) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) + } + if !self.suggestions.isEmpty { + try visitor.visitRepeatedMessageField(value: self.suggestions, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_GetMentionSuggestionsResponse, rhs: Flipcash_Chat_V1_GetMentionSuggestionsResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs.suggestions != rhs.suggestions {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_GetMentionSuggestionsResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") +} + +extension Flipcash_Chat_V1_StartChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".StartChatRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}public_group\0\u{3}private_group\0\u{4}\u{7}idempotency_key\0\u{1}auth\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { + var v: Flipcash_Chat_V1_StartChatRequest.PublicGroupChatParameters? + var hadOneofValue = false + if let current = self.parameters { + hadOneofValue = true + if case .publicGroup(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.parameters = .publicGroup(v) + } + }() + case 2: try { + var v: Flipcash_Chat_V1_StartChatRequest.PrivateGroupChatParameters? + var hadOneofValue = false + if let current = self.parameters { + hadOneofValue = true + if case .privateGroup(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.parameters = .privateGroup(v) + } + }() + case 9: try { try decoder.decodeSingularMessageField(value: &self._idempotencyKey) }() + case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + switch self.parameters { + case .publicGroup?: try { + guard case .publicGroup(let v)? = self.parameters else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + }() + case .privateGroup?: try { + guard case .privateGroup(let v)? = self.parameters else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case nil: break + } + try { if let v = self._idempotencyKey { + try visitor.visitSingularMessageField(value: v, fieldNumber: 9) + } }() + try { if let v = self._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_StartChatRequest, rhs: Flipcash_Chat_V1_StartChatRequest) -> Bool { + if lhs.parameters != rhs.parameters {return false} + if lhs._idempotencyKey != rhs._idempotencyKey {return false} + if lhs._auth != rhs._auth {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_StartChatRequest.PublicGroupChatParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Chat_V1_StartChatRequest.protoMessageName + ".PublicGroupChatParameters" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}title\0\u{1}picture\0\u{1}rules\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.title) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._picture) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._rules) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !self.title.isEmpty { + try visitor.visitSingularStringField(value: self.title, fieldNumber: 1) + } + try { if let v = self._picture { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = self._rules { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_StartChatRequest.PublicGroupChatParameters, rhs: Flipcash_Chat_V1_StartChatRequest.PublicGroupChatParameters) -> Bool { + if lhs.title != rhs.title {return false} + if lhs._picture != rhs._picture {return false} + if lhs._rules != rhs._rules {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_StartChatRequest.PrivateGroupChatParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Chat_V1_StartChatRequest.protoMessageName + ".PrivateGroupChatParameters" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}title\0\u{1}picture\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.title) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._picture) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !self.title.isEmpty { + try visitor.visitSingularStringField(value: self.title, fieldNumber: 1) + } + try { if let v = self._picture { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_StartChatRequest.PrivateGroupChatParameters, rhs: Flipcash_Chat_V1_StartChatRequest.PrivateGroupChatParameters) -> Bool { + if lhs.title != rhs.title {return false} + if lhs._picture != rhs._picture {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_StartChatResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".StartChatResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}chat\0\u{3}flagged_category\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._chat) }() + case 3: try { try decoder.decodeSingularEnumField(value: &self.flaggedCategory) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) + } + try { if let v = self._chat { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + if self.flaggedCategory != .none { + try visitor.visitSingularEnumField(value: self.flaggedCategory, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_StartChatResponse, rhs: Flipcash_Chat_V1_StartChatResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs._chat != rhs._chat {return false} + if lhs.flaggedCategory != rhs.flaggedCategory {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_StartChatResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}TITLE_MODERATED\0\u{1}PICTURE_BLOB_NOT_ACCEPTED\0\u{1}INVALID_RULES\0\u{1}RULES_NOT_SATISFIED\0") +} + +extension Flipcash_Chat_V1_JoinChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".JoinChatRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{2}\u{9}auth\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() + case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._chatID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_JoinChatRequest, rhs: Flipcash_Chat_V1_JoinChatRequest) -> Bool { + if lhs._chatID != rhs._chatID {return false} + if lhs._auth != rhs._auth {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_JoinChatResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".JoinChatResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}chat\0") + + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._queryOptions) }() + case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._chat) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) + } + try { if let v = self._chat { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_JoinChatResponse, rhs: Flipcash_Chat_V1_JoinChatResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs._chat != rhs._chat {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_JoinChatResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0\u{1}RULES_NOT_SATISFIED\0") +} + +extension Flipcash_Chat_V1_LeaveChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LeaveChatRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{2}\u{9}auth\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -1517,7 +2881,7 @@ extension Flipcash_Chat_V1_GetGroupChatFeedRequest: SwiftProtobuf.Message, Swift // allocates stack space for every if/case branch local when no optimizations // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._queryOptions { + try { if let v = self._chatID { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() try { if let v = self._auth { @@ -1526,17 +2890,17 @@ extension Flipcash_Chat_V1_GetGroupChatFeedRequest: SwiftProtobuf.Message, Swift try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_GetGroupChatFeedRequest, rhs: Flipcash_Chat_V1_GetGroupChatFeedRequest) -> Bool { - if lhs._queryOptions != rhs._queryOptions {return false} + public static func ==(lhs: Flipcash_Chat_V1_LeaveChatRequest, rhs: Flipcash_Chat_V1_LeaveChatRequest) -> Bool { + if lhs._chatID != rhs._chatID {return false} if lhs._auth != rhs._auth {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_GetGroupChatFeedResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".GetGroupChatFeedResponse" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}chats\0\u{3}paging_token\0\u{3}has_more\0") +extension Flipcash_Chat_V1_LeaveChatResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LeaveChatResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1545,51 +2909,32 @@ extension Flipcash_Chat_V1_GetGroupChatFeedResponse: SwiftProtobuf.Message, Swif // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() - case 2: try { try decoder.decodeRepeatedMessageField(value: &self.chats) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._pagingToken) }() - case 4: try { try decoder.decodeSingularBoolField(value: &self.hasMore_p) }() default: break } } } public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 if self.result != .ok { try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) } - if !self.chats.isEmpty { - try visitor.visitRepeatedMessageField(value: self.chats, fieldNumber: 2) - } - try { if let v = self._pagingToken { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - if self.hasMore_p != false { - try visitor.visitSingularBoolField(value: self.hasMore_p, fieldNumber: 4) - } try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_GetGroupChatFeedResponse, rhs: Flipcash_Chat_V1_GetGroupChatFeedResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_LeaveChatResponse, rhs: Flipcash_Chat_V1_LeaveChatResponse) -> Bool { if lhs.result != rhs.result {return false} - if lhs.chats != rhs.chats {return false} - if lhs._pagingToken != rhs._pagingToken {return false} - if lhs.hasMore_p != rhs.hasMore_p {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_GetGroupChatFeedResponse.Result: SwiftProtobuf._ProtoNameProviding { +extension Flipcash_Chat_V1_LeaveChatResponse.Result: SwiftProtobuf._ProtoNameProviding { public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") } -extension Flipcash_Chat_V1_GetRosterRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".GetRosterRequest" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{3}query_options\0\u{2}\u{8}auth\0") +extension Flipcash_Chat_V1_EditChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".EditChatRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{1}title\0\u{1}picture\0\u{2}\u{7}auth\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1598,7 +2943,8 @@ extension Flipcash_Chat_V1_GetRosterRequest: SwiftProtobuf.Message, SwiftProtobu // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._queryOptions) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._title) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._picture) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -1613,27 +2959,95 @@ extension Flipcash_Chat_V1_GetRosterRequest: SwiftProtobuf.Message, SwiftProtobu try { if let v = self._chatID { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() - try { if let v = self._queryOptions { + try { if let v = self._title { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() + try { if let v = self._picture { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() try { if let v = self._auth { try visitor.visitSingularMessageField(value: v, fieldNumber: 10) } }() try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_GetRosterRequest, rhs: Flipcash_Chat_V1_GetRosterRequest) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_EditChatRequest, rhs: Flipcash_Chat_V1_EditChatRequest) -> Bool { if lhs._chatID != rhs._chatID {return false} - if lhs._queryOptions != rhs._queryOptions {return false} + if lhs._title != rhs._title {return false} + if lhs._picture != rhs._picture {return false} if lhs._auth != rhs._auth {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_GetRosterResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".GetRosterResponse" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}members\0\u{3}roster_summary\0\u{3}paging_token\0\u{3}has_more\0") +extension Flipcash_Chat_V1_EditChatRequest.Title: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Chat_V1_EditChatRequest.protoMessageName + ".Title" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}value\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.value) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.value.isEmpty { + try visitor.visitSingularStringField(value: self.value, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_EditChatRequest.Title, rhs: Flipcash_Chat_V1_EditChatRequest.Title) -> Bool { + if lhs.value != rhs.value {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_EditChatRequest.Picture: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Chat_V1_EditChatRequest.protoMessageName + ".Picture" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}blob_id\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._blobID) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._blobID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_EditChatRequest.Picture, rhs: Flipcash_Chat_V1_EditChatRequest.Picture) -> Bool { + if lhs._blobID != rhs._blobID {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_EditChatResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".EditChatResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}chat\0\u{3}flagged_category\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1642,10 +3056,8 @@ extension Flipcash_Chat_V1_GetRosterResponse: SwiftProtobuf.Message, SwiftProtob // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() - case 2: try { try decoder.decodeRepeatedMessageField(value: &self.members) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._rosterSummary) }() - case 4: try { try decoder.decodeSingularMessageField(value: &self._pagingToken) }() - case 5: try { try decoder.decodeSingularBoolField(value: &self.hasMore_p) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._chat) }() + case 3: try { try decoder.decodeSingularEnumField(value: &self.flaggedCategory) }() default: break } } @@ -1659,39 +3071,31 @@ extension Flipcash_Chat_V1_GetRosterResponse: SwiftProtobuf.Message, SwiftProtob if self.result != .ok { try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) } - if !self.members.isEmpty { - try visitor.visitRepeatedMessageField(value: self.members, fieldNumber: 2) - } - try { if let v = self._rosterSummary { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - try { if let v = self._pagingToken { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + try { if let v = self._chat { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() - if self.hasMore_p != false { - try visitor.visitSingularBoolField(value: self.hasMore_p, fieldNumber: 5) + if self.flaggedCategory != .none { + try visitor.visitSingularEnumField(value: self.flaggedCategory, fieldNumber: 3) } try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_GetRosterResponse, rhs: Flipcash_Chat_V1_GetRosterResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_EditChatResponse, rhs: Flipcash_Chat_V1_EditChatResponse) -> Bool { if lhs.result != rhs.result {return false} - if lhs.members != rhs.members {return false} - if lhs._rosterSummary != rhs._rosterSummary {return false} - if lhs._pagingToken != rhs._pagingToken {return false} - if lhs.hasMore_p != rhs.hasMore_p {return false} + if lhs._chat != rhs._chat {return false} + if lhs.flaggedCategory != rhs.flaggedCategory {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_GetRosterResponse.Result: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") +extension Flipcash_Chat_V1_EditChatResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0\u{1}TITLE_MODERATED\0\u{1}PICTURE_BLOB_NOT_ACCEPTED\0") } -extension Flipcash_Chat_V1_GetMentionSuggestionsRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".GetMentionSuggestionsRequest" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{2}\u{9}auth\0") +extension Flipcash_Chat_V1_MuteChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".MuteChatRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{1}mute\0\u{2}\u{8}auth\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1700,6 +3104,7 @@ extension Flipcash_Chat_V1_GetMentionSuggestionsRequest: SwiftProtobuf.Message, // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._mute) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -1714,23 +3119,27 @@ extension Flipcash_Chat_V1_GetMentionSuggestionsRequest: SwiftProtobuf.Message, try { if let v = self._chatID { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() + try { if let v = self._mute { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() try { if let v = self._auth { try visitor.visitSingularMessageField(value: v, fieldNumber: 10) } }() try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_GetMentionSuggestionsRequest, rhs: Flipcash_Chat_V1_GetMentionSuggestionsRequest) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_MuteChatRequest, rhs: Flipcash_Chat_V1_MuteChatRequest) -> Bool { if lhs._chatID != rhs._chatID {return false} + if lhs._mute != rhs._mute {return false} if lhs._auth != rhs._auth {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_GetMentionSuggestionsResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".GetMentionSuggestionsResponse" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}suggestions\0") +extension Flipcash_Chat_V1_MuteChatResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".MuteChatResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{3}viewer_state\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1739,58 +3148,49 @@ extension Flipcash_Chat_V1_GetMentionSuggestionsResponse: SwiftProtobuf.Message, // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() - case 2: try { try decoder.decodeRepeatedMessageField(value: &self.suggestions) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._viewerState) }() default: break } } } public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 if self.result != .ok { try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) } - if !self.suggestions.isEmpty { - try visitor.visitRepeatedMessageField(value: self.suggestions, fieldNumber: 2) - } + try { if let v = self._viewerState { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_GetMentionSuggestionsResponse, rhs: Flipcash_Chat_V1_GetMentionSuggestionsResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_MuteChatResponse, rhs: Flipcash_Chat_V1_MuteChatResponse) -> Bool { if lhs.result != rhs.result {return false} - if lhs.suggestions != rhs.suggestions {return false} + if lhs._viewerState != rhs._viewerState {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_GetMentionSuggestionsResponse.Result: SwiftProtobuf._ProtoNameProviding { +extension Flipcash_Chat_V1_MuteChatResponse.Result: SwiftProtobuf._ProtoNameProviding { public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") } -extension Flipcash_Chat_V1_StartChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".StartChatRequest" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}group\0\u{4}\u{8}idempotency_key\0\u{1}auth\0") - +extension Flipcash_Chat_V1_UnmuteChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".UnmuteChatRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{2}\u{9}auth\0") + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try { - var v: Flipcash_Chat_V1_StartChatRequest.GroupChatParameters? - var hadOneofValue = false - if let current = self.parameters { - hadOneofValue = true - if case .group(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.parameters = .group(v) - } - }() - case 9: try { try decoder.decodeSingularMessageField(value: &self._idempotencyKey) }() + case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -1802,30 +3202,26 @@ extension Flipcash_Chat_V1_StartChatRequest: SwiftProtobuf.Message, SwiftProtobu // allocates stack space for every if/case branch local when no optimizations // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and // https://github.com/apple/swift-protobuf/issues/1182 - try { if case .group(let v)? = self.parameters { + try { if let v = self._chatID { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() - try { if let v = self._idempotencyKey { - try visitor.visitSingularMessageField(value: v, fieldNumber: 9) - } }() try { if let v = self._auth { try visitor.visitSingularMessageField(value: v, fieldNumber: 10) } }() try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_StartChatRequest, rhs: Flipcash_Chat_V1_StartChatRequest) -> Bool { - if lhs.parameters != rhs.parameters {return false} - if lhs._idempotencyKey != rhs._idempotencyKey {return false} + public static func ==(lhs: Flipcash_Chat_V1_UnmuteChatRequest, rhs: Flipcash_Chat_V1_UnmuteChatRequest) -> Bool { + if lhs._chatID != rhs._chatID {return false} if lhs._auth != rhs._auth {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_StartChatRequest.GroupChatParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = Flipcash_Chat_V1_StartChatRequest.protoMessageName + ".GroupChatParameters" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}title\0\u{1}picture\0\u{1}rules\0") +extension Flipcash_Chat_V1_UnmuteChatResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".UnmuteChatResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{3}viewer_state\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1833,9 +3229,8 @@ extension Flipcash_Chat_V1_StartChatRequest.GroupChatParameters: SwiftProtobuf.M // allocates stack space for every case branch when no optimizations are // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try { try decoder.decodeSingularStringField(value: &self.title) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._picture) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._rules) }() + case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._viewerState) }() default: break } } @@ -1846,30 +3241,69 @@ extension Flipcash_Chat_V1_StartChatRequest.GroupChatParameters: SwiftProtobuf.M // allocates stack space for every if/case branch local when no optimizations // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and // https://github.com/apple/swift-protobuf/issues/1182 - if !self.title.isEmpty { - try visitor.visitSingularStringField(value: self.title, fieldNumber: 1) + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) } - try { if let v = self._picture { + try { if let v = self._viewerState { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() - try { if let v = self._rules { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_UnmuteChatResponse, rhs: Flipcash_Chat_V1_UnmuteChatResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs._viewerState != rhs._viewerState {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_UnmuteChatResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") +} + +extension Flipcash_Chat_V1_EnterLobbyRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".EnterLobbyRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{2}\u{9}auth\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() + case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._chatID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) } }() try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_StartChatRequest.GroupChatParameters, rhs: Flipcash_Chat_V1_StartChatRequest.GroupChatParameters) -> Bool { - if lhs.title != rhs.title {return false} - if lhs._picture != rhs._picture {return false} - if lhs._rules != rhs._rules {return false} + public static func ==(lhs: Flipcash_Chat_V1_EnterLobbyRequest, rhs: Flipcash_Chat_V1_EnterLobbyRequest) -> Bool { + if lhs._chatID != rhs._chatID {return false} + if lhs._auth != rhs._auth {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_StartChatResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".StartChatResponse" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}chat\0\u{3}flagged_category\0") +extension Flipcash_Chat_V1_EnterLobbyResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".EnterLobbyResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}lobby\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1878,8 +3312,7 @@ extension Flipcash_Chat_V1_StartChatResponse: SwiftProtobuf.Message, SwiftProtob // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._chat) }() - case 3: try { try decoder.decodeSingularEnumField(value: &self.flaggedCategory) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._lobby) }() default: break } } @@ -1893,30 +3326,26 @@ extension Flipcash_Chat_V1_StartChatResponse: SwiftProtobuf.Message, SwiftProtob if self.result != .ok { try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) } - try { if let v = self._chat { + try { if let v = self._lobby { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() - if self.flaggedCategory != .none { - try visitor.visitSingularEnumField(value: self.flaggedCategory, fieldNumber: 3) - } try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_StartChatResponse, rhs: Flipcash_Chat_V1_StartChatResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_EnterLobbyResponse, rhs: Flipcash_Chat_V1_EnterLobbyResponse) -> Bool { if lhs.result != rhs.result {return false} - if lhs._chat != rhs._chat {return false} - if lhs.flaggedCategory != rhs.flaggedCategory {return false} + if lhs._lobby != rhs._lobby {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_StartChatResponse.Result: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}TITLE_MODERATED\0\u{1}PICTURE_BLOB_NOT_ACCEPTED\0\u{1}INVALID_RULES\0\u{1}RULES_NOT_SATISFIED\0") +extension Flipcash_Chat_V1_EnterLobbyResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0\u{1}ALREADY_MEMBER\0\u{1}LOBBY_FULL\0\u{1}TOO_MANY_LOBBIES\0") } -extension Flipcash_Chat_V1_JoinChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".JoinChatRequest" +extension Flipcash_Chat_V1_LeaveLobbyRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LeaveLobbyRequest" public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{2}\u{9}auth\0") public mutating func decodeMessage(decoder: inout D) throws { @@ -1946,7 +3375,7 @@ extension Flipcash_Chat_V1_JoinChatRequest: SwiftProtobuf.Message, SwiftProtobuf try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_JoinChatRequest, rhs: Flipcash_Chat_V1_JoinChatRequest) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_LeaveLobbyRequest, rhs: Flipcash_Chat_V1_LeaveLobbyRequest) -> Bool { if lhs._chatID != rhs._chatID {return false} if lhs._auth != rhs._auth {return false} if lhs.unknownFields != rhs.unknownFields {return false} @@ -1954,9 +3383,9 @@ extension Flipcash_Chat_V1_JoinChatRequest: SwiftProtobuf.Message, SwiftProtobuf } } -extension Flipcash_Chat_V1_JoinChatResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".JoinChatResponse" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}chat\0") +extension Flipcash_Chat_V1_LeaveLobbyResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LeaveLobbyResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1965,41 +3394,32 @@ extension Flipcash_Chat_V1_JoinChatResponse: SwiftProtobuf.Message, SwiftProtobu // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._chat) }() default: break } } } public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 if self.result != .ok { try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) } - try { if let v = self._chat { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_JoinChatResponse, rhs: Flipcash_Chat_V1_JoinChatResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_LeaveLobbyResponse, rhs: Flipcash_Chat_V1_LeaveLobbyResponse) -> Bool { if lhs.result != rhs.result {return false} - if lhs._chat != rhs._chat {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_JoinChatResponse.Result: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0\u{1}RULES_NOT_SATISFIED\0") +extension Flipcash_Chat_V1_LeaveLobbyResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") } -extension Flipcash_Chat_V1_LeaveChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".LeaveChatRequest" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{2}\u{9}auth\0") +extension Flipcash_Chat_V1_GetLobbyMembersRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetLobbyMembersRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{3}query_options\0\u{2}\u{8}auth\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2008,6 +3428,7 @@ extension Flipcash_Chat_V1_LeaveChatRequest: SwiftProtobuf.Message, SwiftProtobu // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._queryOptions) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -2022,23 +3443,27 @@ extension Flipcash_Chat_V1_LeaveChatRequest: SwiftProtobuf.Message, SwiftProtobu try { if let v = self._chatID { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() + try { if let v = self._queryOptions { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() try { if let v = self._auth { try visitor.visitSingularMessageField(value: v, fieldNumber: 10) } }() try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_LeaveChatRequest, rhs: Flipcash_Chat_V1_LeaveChatRequest) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_GetLobbyMembersRequest, rhs: Flipcash_Chat_V1_GetLobbyMembersRequest) -> Bool { if lhs._chatID != rhs._chatID {return false} + if lhs._queryOptions != rhs._queryOptions {return false} if lhs._auth != rhs._auth {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_LeaveChatResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".LeaveChatResponse" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0") +extension Flipcash_Chat_V1_GetLobbyMembersResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetLobbyMembersResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}members\0\u{3}paging_token\0\u{3}has_more\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2047,32 +3472,51 @@ extension Flipcash_Chat_V1_LeaveChatResponse: SwiftProtobuf.Message, SwiftProtob // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.members) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._pagingToken) }() + case 4: try { try decoder.decodeSingularBoolField(value: &self.hasMore_p) }() default: break } } } public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 if self.result != .ok { try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) } + if !self.members.isEmpty { + try visitor.visitRepeatedMessageField(value: self.members, fieldNumber: 2) + } + try { if let v = self._pagingToken { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + if self.hasMore_p != false { + try visitor.visitSingularBoolField(value: self.hasMore_p, fieldNumber: 4) + } try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_LeaveChatResponse, rhs: Flipcash_Chat_V1_LeaveChatResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_GetLobbyMembersResponse, rhs: Flipcash_Chat_V1_GetLobbyMembersResponse) -> Bool { if lhs.result != rhs.result {return false} + if lhs.members != rhs.members {return false} + if lhs._pagingToken != rhs._pagingToken {return false} + if lhs.hasMore_p != rhs.hasMore_p {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_LeaveChatResponse.Result: SwiftProtobuf._ProtoNameProviding { +extension Flipcash_Chat_V1_GetLobbyMembersResponse.Result: SwiftProtobuf._ProtoNameProviding { public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") } -extension Flipcash_Chat_V1_EditChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".EditChatRequest" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{1}title\0\u{1}picture\0\u{2}\u{7}auth\0") +extension Flipcash_Chat_V1_AdmitLobbyMemberRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".AdmitLobbyMemberRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{3}user_id\0\u{3}key_envelope\0\u{2}\u{7}auth\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2081,8 +3525,8 @@ extension Flipcash_Chat_V1_EditChatRequest: SwiftProtobuf.Message, SwiftProtobuf // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._title) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._picture) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._userID) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._keyEnvelope) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -2097,10 +3541,10 @@ extension Flipcash_Chat_V1_EditChatRequest: SwiftProtobuf.Message, SwiftProtobuf try { if let v = self._chatID { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() - try { if let v = self._title { + try { if let v = self._userID { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() - try { if let v = self._picture { + try { if let v = self._keyEnvelope { try visitor.visitSingularMessageField(value: v, fieldNumber: 3) } }() try { if let v = self._auth { @@ -2109,19 +3553,19 @@ extension Flipcash_Chat_V1_EditChatRequest: SwiftProtobuf.Message, SwiftProtobuf try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_EditChatRequest, rhs: Flipcash_Chat_V1_EditChatRequest) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_AdmitLobbyMemberRequest, rhs: Flipcash_Chat_V1_AdmitLobbyMemberRequest) -> Bool { if lhs._chatID != rhs._chatID {return false} - if lhs._title != rhs._title {return false} - if lhs._picture != rhs._picture {return false} + if lhs._userID != rhs._userID {return false} + if lhs._keyEnvelope != rhs._keyEnvelope {return false} if lhs._auth != rhs._auth {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_EditChatRequest.Title: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = Flipcash_Chat_V1_EditChatRequest.protoMessageName + ".Title" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}value\0") +extension Flipcash_Chat_V1_AdmitLobbyMemberResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".AdmitLobbyMemberResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2129,29 +3573,33 @@ extension Flipcash_Chat_V1_EditChatRequest.Title: SwiftProtobuf.Message, SwiftPr // allocates stack space for every case branch when no optimizations are // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try { try decoder.decodeSingularStringField(value: &self.value) }() + case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() default: break } } } public func traverse(visitor: inout V) throws { - if !self.value.isEmpty { - try visitor.visitSingularStringField(value: self.value, fieldNumber: 1) + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) } try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_EditChatRequest.Title, rhs: Flipcash_Chat_V1_EditChatRequest.Title) -> Bool { - if lhs.value != rhs.value {return false} + public static func ==(lhs: Flipcash_Chat_V1_AdmitLobbyMemberResponse, rhs: Flipcash_Chat_V1_AdmitLobbyMemberResponse) -> Bool { + if lhs.result != rhs.result {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_EditChatRequest.Picture: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = Flipcash_Chat_V1_EditChatRequest.protoMessageName + ".Picture" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}blob_id\0") +extension Flipcash_Chat_V1_AdmitLobbyMemberResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0\u{1}NOT_IN_LOBBY\0") +} + +extension Flipcash_Chat_V1_DenyLobbyMemberRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".DenyLobbyMemberRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{3}user_id\0\u{2}\u{8}auth\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2159,7 +3607,9 @@ extension Flipcash_Chat_V1_EditChatRequest.Picture: SwiftProtobuf.Message, Swift // allocates stack space for every case branch when no optimizations are // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._blobID) }() + case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._userID) }() + case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } } @@ -2170,22 +3620,30 @@ extension Flipcash_Chat_V1_EditChatRequest.Picture: SwiftProtobuf.Message, Swift // allocates stack space for every if/case branch local when no optimizations // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._blobID { + try { if let v = self._chatID { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() + try { if let v = self._userID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = self._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_EditChatRequest.Picture, rhs: Flipcash_Chat_V1_EditChatRequest.Picture) -> Bool { - if lhs._blobID != rhs._blobID {return false} + public static func ==(lhs: Flipcash_Chat_V1_DenyLobbyMemberRequest, rhs: Flipcash_Chat_V1_DenyLobbyMemberRequest) -> Bool { + if lhs._chatID != rhs._chatID {return false} + if lhs._userID != rhs._userID {return false} + if lhs._auth != rhs._auth {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_EditChatResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".EditChatResponse" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}chat\0\u{3}flagged_category\0") +extension Flipcash_Chat_V1_DenyLobbyMemberResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".DenyLobbyMemberResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2194,46 +3652,32 @@ extension Flipcash_Chat_V1_EditChatResponse: SwiftProtobuf.Message, SwiftProtobu // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._chat) }() - case 3: try { try decoder.decodeSingularEnumField(value: &self.flaggedCategory) }() default: break } } } public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 if self.result != .ok { try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) } - try { if let v = self._chat { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - if self.flaggedCategory != .none { - try visitor.visitSingularEnumField(value: self.flaggedCategory, fieldNumber: 3) - } try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_EditChatResponse, rhs: Flipcash_Chat_V1_EditChatResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_DenyLobbyMemberResponse, rhs: Flipcash_Chat_V1_DenyLobbyMemberResponse) -> Bool { if lhs.result != rhs.result {return false} - if lhs._chat != rhs._chat {return false} - if lhs.flaggedCategory != rhs.flaggedCategory {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_EditChatResponse.Result: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0\u{1}TITLE_MODERATED\0\u{1}PICTURE_BLOB_NOT_ACCEPTED\0") +extension Flipcash_Chat_V1_DenyLobbyMemberResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") } -extension Flipcash_Chat_V1_MuteChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".MuteChatRequest" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{1}mute\0\u{2}\u{8}auth\0") +extension Flipcash_Chat_V1_SetKeyEnvelopeRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".SetKeyEnvelopeRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{3}key_envelope\0\u{2}\u{8}auth\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2242,7 +3686,7 @@ extension Flipcash_Chat_V1_MuteChatRequest: SwiftProtobuf.Message, SwiftProtobuf // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._mute) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._keyEnvelope) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -2257,7 +3701,7 @@ extension Flipcash_Chat_V1_MuteChatRequest: SwiftProtobuf.Message, SwiftProtobuf try { if let v = self._chatID { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() - try { if let v = self._mute { + try { if let v = self._keyEnvelope { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() try { if let v = self._auth { @@ -2266,18 +3710,18 @@ extension Flipcash_Chat_V1_MuteChatRequest: SwiftProtobuf.Message, SwiftProtobuf try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_MuteChatRequest, rhs: Flipcash_Chat_V1_MuteChatRequest) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_SetKeyEnvelopeRequest, rhs: Flipcash_Chat_V1_SetKeyEnvelopeRequest) -> Bool { if lhs._chatID != rhs._chatID {return false} - if lhs._mute != rhs._mute {return false} + if lhs._keyEnvelope != rhs._keyEnvelope {return false} if lhs._auth != rhs._auth {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_MuteChatResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".MuteChatResponse" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{3}viewer_state\0") +extension Flipcash_Chat_V1_SetKeyEnvelopeResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".SetKeyEnvelopeResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2286,40 +3730,31 @@ extension Flipcash_Chat_V1_MuteChatResponse: SwiftProtobuf.Message, SwiftProtobu // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._viewerState) }() default: break } } } public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 if self.result != .ok { try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) } - try { if let v = self._viewerState { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_MuteChatResponse, rhs: Flipcash_Chat_V1_MuteChatResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_SetKeyEnvelopeResponse, rhs: Flipcash_Chat_V1_SetKeyEnvelopeResponse) -> Bool { if lhs.result != rhs.result {return false} - if lhs._viewerState != rhs._viewerState {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_MuteChatResponse.Result: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") +extension Flipcash_Chat_V1_SetKeyEnvelopeResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0\u{1}ALREADY_SET\0") } -extension Flipcash_Chat_V1_UnmuteChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".UnmuteChatRequest" +extension Flipcash_Chat_V1_GetKeyEnvelopeRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetKeyEnvelopeRequest" public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{2}\u{9}auth\0") public mutating func decodeMessage(decoder: inout D) throws { @@ -2349,7 +3784,7 @@ extension Flipcash_Chat_V1_UnmuteChatRequest: SwiftProtobuf.Message, SwiftProtob try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_UnmuteChatRequest, rhs: Flipcash_Chat_V1_UnmuteChatRequest) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_GetKeyEnvelopeRequest, rhs: Flipcash_Chat_V1_GetKeyEnvelopeRequest) -> Bool { if lhs._chatID != rhs._chatID {return false} if lhs._auth != rhs._auth {return false} if lhs.unknownFields != rhs.unknownFields {return false} @@ -2357,9 +3792,9 @@ extension Flipcash_Chat_V1_UnmuteChatRequest: SwiftProtobuf.Message, SwiftProtob } } -extension Flipcash_Chat_V1_UnmuteChatResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".UnmuteChatResponse" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{3}viewer_state\0") +extension Flipcash_Chat_V1_GetKeyEnvelopeResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetKeyEnvelopeResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{3}key_envelope\0\u{3}wrapped_by\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2368,7 +3803,8 @@ extension Flipcash_Chat_V1_UnmuteChatResponse: SwiftProtobuf.Message, SwiftProto // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._viewerState) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._keyEnvelope) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._wrappedBy) }() default: break } } @@ -2382,20 +3818,24 @@ extension Flipcash_Chat_V1_UnmuteChatResponse: SwiftProtobuf.Message, SwiftProto if self.result != .ok { try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) } - try { if let v = self._viewerState { + try { if let v = self._keyEnvelope { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() + try { if let v = self._wrappedBy { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_UnmuteChatResponse, rhs: Flipcash_Chat_V1_UnmuteChatResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_GetKeyEnvelopeResponse, rhs: Flipcash_Chat_V1_GetKeyEnvelopeResponse) -> Bool { if lhs.result != rhs.result {return false} - if lhs._viewerState != rhs._viewerState {return false} + if lhs._keyEnvelope != rhs._keyEnvelope {return false} + if lhs._wrappedBy != rhs._wrappedBy {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_UnmuteChatResponse.Result: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") +extension Flipcash_Chat_V1_GetKeyEnvelopeResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0\u{1}NO_ENVELOPE\0") } diff --git a/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift b/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift index d281cc0..b8df97e 100644 --- a/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift +++ b/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift @@ -164,7 +164,9 @@ public struct Flipcash_Chat_V1_Metadata: @unchecked Sendable { public mutating func clearRosterSummary() {_uniqueStorage()._rosterSummary = nil} /// Rules governing participation in this chat. Only supported for group - /// chats. If not set, the chat has no participation requirements. + /// chats. If not set, the chat has no participation requirements. Never + /// set for a private group (see is_private), which admits a user by its + /// creator's approval instead. public var rules: Flipcash_Chat_V1_Rules { get {return _storage._rules ?? Flipcash_Chat_V1_Rules()} set {_uniqueStorage()._rules = newValue} @@ -195,9 +197,38 @@ public struct Flipcash_Chat_V1_Metadata: @unchecked Sendable { /// Clears the value of `creator`. Subsequent reads from it will return its default value. public mutating func clearCreator() {_uniqueStorage()._creator = nil} + /// Whether this chat is a private group. Only supported for group chats, + /// and fixed when the chat is created (see + /// StartChatRequest.private_group). + /// + /// A private group has no rules. Nobody joins it with Chat.JoinChat: a + /// user enters its lobby with Chat.EnterLobby and is admitted by its + /// creator. Only its members can read its messages, under any + /// messaging.v1.ViewMode, and every message a client sends in it is + /// messaging.v1.EncryptedContent, encrypted with the group's chat key + /// (see KeyEnvelope). + /// + /// A private group is visible from the moment it is created, but nothing + /// can happen in it until its creator has stored its key (see + /// StartChatRequest.PrivateGroupChatParameters). + public var isPrivate: Bool { + get {return _storage._isPrivate} + set {_uniqueStorage()._isPrivate = newValue} + } + + /// Whether the viewer is waiting in this private group's lobby (see + /// Chat.EnterLobby). Per-viewer and server-computed, like is_hidden. + /// Always false for a member, and for any chat that is not a private + /// group. + public var inLobby: Bool { + get {return _storage._inLobby} + set {_uniqueStorage()._inLobby = newValue} + } + /// Whether messages in this chat are end-to-end encrypted (see /// messaging.v1.EncryptedContent). Only supported for DMs (CONTACT_DM or DM); - /// always false for group chats. + /// always false for group chats. A private group is always end-to-end + /// encrypted and does not use this flag (see is_private). /// /// Used to migrate DMs to E2EE: when true, clients send all new content in /// the chat as EncryptedContent. When false, clients send content in the @@ -477,7 +508,7 @@ public struct Flipcash_Chat_V1_Member: @unchecked Sendable { /// MentionSuggestion is one person the caller may want to @mention in a chat, /// as returned by Chat.GetMentionSuggestions. It is not a Member: a suggestion /// may name someone who has since left the chat. -public struct Flipcash_Chat_V1_MentionSuggestion: Sendable { +public struct Flipcash_Chat_V1_MentionSuggestion: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -485,33 +516,32 @@ public struct Flipcash_Chat_V1_MentionSuggestion: Sendable { /// The suggested user's public profile. user_id and username are always /// set. public var userProfile: Flipcash_Profile_V1_UserProfile { - get {return _userProfile ?? Flipcash_Profile_V1_UserProfile()} - set {_userProfile = newValue} + get {return _storage._userProfile ?? Flipcash_Profile_V1_UserProfile()} + set {_uniqueStorage()._userProfile = newValue} } /// Returns true if `userProfile` has been explicitly set. - public var hasUserProfile: Bool {return self._userProfile != nil} + public var hasUserProfile: Bool {return _storage._userProfile != nil} /// Clears the value of `userProfile`. Subsequent reads from it will return its default value. - public mutating func clearUserProfile() {self._userProfile = nil} + public mutating func clearUserProfile() {_uniqueStorage()._userProfile = nil} /// When the user last sent a message in this chat, as the server records /// it: it may trail their latest message by up to a minute. A client /// merging new messages from the event stream into its list compares /// against it. Unset for a user suggested for another reason. public var lastSentAt: SwiftProtobuf.Google_Protobuf_Timestamp { - get {return _lastSentAt ?? SwiftProtobuf.Google_Protobuf_Timestamp()} - set {_lastSentAt = newValue} + get {return _storage._lastSentAt ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + set {_uniqueStorage()._lastSentAt = newValue} } /// Returns true if `lastSentAt` has been explicitly set. - public var hasLastSentAt: Bool {return self._lastSentAt != nil} + public var hasLastSentAt: Bool {return _storage._lastSentAt != nil} /// Clears the value of `lastSentAt`. Subsequent reads from it will return its default value. - public mutating func clearLastSentAt() {self._lastSentAt = nil} + public mutating func clearLastSentAt() {_uniqueStorage()._lastSentAt = nil} public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} - fileprivate var _userProfile: Flipcash_Profile_V1_UserProfile? = nil - fileprivate var _lastSentAt: SwiftProtobuf.Google_Protobuf_Timestamp? = nil + fileprivate var _storage = _StorageClass.defaultInstance } /// RosterSummary describes a chat's roster — its member list — without @@ -1023,6 +1053,332 @@ public struct Flipcash_Chat_V1_IdempotencyKey: Sendable { public init() {} } +/// KeyEnvelope carries a private group's chat key to one user, encrypted so +/// that only that user can open it. The server stores one envelope per member +/// and returns it with Chat.GetKeyEnvelope. It cannot open an envelope and +/// never holds the chat key. +/// +/// The chat key is 32 random bytes generated by the group's creator, and it +/// never changes. It encrypts every message and blob in the group (see +/// messaging.v1.EncryptedContent), so a member who holds it can read the +/// group's whole history. +/// +/// An envelope is bound to the chat's ID, which the creator learns only when +/// Chat.StartChat returns. So the creator generates the chat key then, and +/// stores its own envelope with Chat.SetKeyEnvelope as the second step of +/// creating the group. Until that step lands the group has no key, and the +/// server lets nothing happen in it (see +/// StartChatRequest.PrivateGroupChatParameters): in particular, nobody is +/// admitted to it. +/// +/// The creator's client keeps attempting that step until it succeeds. A +/// creator whose Chat.GetKeyEnvelope returns NO_ENVELOPE, on any device, +/// generates a chat key and stores its envelope. That is always safe: the +/// creator's envelope is never deleted, so its absence means no other user +/// was ever given a key. If two of the creator's devices do this at once, +/// the first envelope stored stands (SetKeyEnvelopeResponse.ALREADY_SET) and +/// the other device adopts it. +/// +/// An envelope is made by a wrapper for a recipient. The wrapper is either +/// the recipient themself, or the group's creator admitting the recipient +/// with Chat.AdmitLobbyMember. The server records who stored each envelope +/// and returns it as GetKeyEnvelopeResponse.wrapped_by. Both are identified +/// by the Ed25519 public keys they registered their accounts with. For scheme +/// X25519_XCHACHA20POLY1305: +/// +/// 1. Convert keys to X25519, exactly as in step 1 of +/// messaging.v1.EncryptedContent: own Ed25519 private key to an X25519 +/// private key, and the peer's Ed25519 public key to an X25519 public +/// key. The wrapper's peer is the recipient, and the recipient's peer is +/// the wrapper. When the wrapper is the recipient, the peer key is the +/// user's own public key. Reject a public key that is not a valid point, +/// or that converts to a low-order X25519 point. +/// +/// 2. Compute the shared secret: ss = X25519(own_x25519_priv, peer_x25519_pub). +/// Abort if ss is all zeros. +/// +/// 3. Derive the wrapping key with HKDF-SHA256 (RFC 5869): +/// salt = min(wrapper_pk, recipient_pk) || max(wrapper_pk, recipient_pk) +/// ikm = ss +/// info = "flipcash-group-key-wrap-v1" || chat_id +/// L = 32 +/// wrapper_pk and recipient_pk are the 32-byte Ed25519 public keys, +/// ordered bytewise, and chat_id is the raw bytes of +/// common.v1.ChatId.value. +/// +/// 4. Encrypt with XChaCha20-Poly1305 (the IETF construction in libsodium's +/// crypto_aead_xchacha20poly1305_ietf_encrypt): +/// key = the wrapping key from step 3 +/// nonce = 24 fresh random bytes, set in `nonce` below +/// plaintext = the 32-byte chat key +/// aad = "flipcash-group-key-wrap-v1" || chat_id +/// || wrapper_pk || recipient_pk +/// The ciphertext, with its 16-byte Poly1305 tag appended, is set in +/// `ciphertext` below. +/// +/// The recipient derives the same wrapping key and decrypts with the same +/// aad. Only the wrapper and the recipient can derive that key, so an +/// envelope that opens was made by one of them: neither the server nor +/// another member can substitute a chat key of its choosing. +/// +/// That holds only as far as the recipient trusts the public key it uses for +/// the wrapper. When wrapped_by is the recipient, that is the recipient's own +/// key. Otherwise wrapped_by must be Metadata.creator, and the client uses +/// the creator's public key, preferring one it learned outside this service +/// (for example, from the invitation that led it to the chat) over one the +/// server reports. A client rejects an envelope that names any other wrapper +/// or that fails to open, and does not send in the chat. +/// +/// A member who opens an envelope wrapped by the creator wraps the chat key +/// again for itself and stores that envelope with Chat.SetKeyEnvelope, so +/// that opening it later, on any of the member's devices, depends on no one +/// else's key. +/// +/// Like EncryptedContent, this has no forward secrecy: anyone who later +/// obtains a member's private key can open that member's envelope and read +/// the whole chat. A user who leaves the group still knows the chat key. The +/// server stops serving them the group's messages, and nothing else keeps +/// them out. +public struct Flipcash_Chat_V1_KeyEnvelope: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// The scheme used, which determines how every other field is + /// interpreted. Clients treat an unrecognized scheme as an envelope they + /// cannot open. + public var scheme: Flipcash_Chat_V1_KeyEnvelope.Scheme = .unknown + + /// The random XChaCha20-Poly1305 nonce, unique per envelope. + public var nonce: Data = Data() + + /// The encrypted 32-byte chat key with the 16-byte Poly1305 tag appended. + public var ciphertext: Data = Data() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Scheme: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case unknown // = 0 + case x25519Xchacha20Poly1305 // = 1 + case UNRECOGNIZED(Int) + + public init() { + self = .unknown + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .x25519Xchacha20Poly1305 + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .unknown: return 0 + case .x25519Xchacha20Poly1305: return 1 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Chat_V1_KeyEnvelope.Scheme] = [ + .unknown, + .x25519Xchacha20Poly1305, + ] + + } + + public init() {} +} + +/// LobbyMember is a user waiting in a private group's lobby for its creator +/// to admit them (see Chat.EnterLobby). It is not a Member: the user is not +/// on the chat's roster and cannot read the chat. +public struct Flipcash_Chat_V1_LobbyMember: @unchecked Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// The waiting user's public profile. user_id is always set. + public var userProfile: Flipcash_Profile_V1_UserProfile { + get {return _storage._userProfile ?? Flipcash_Profile_V1_UserProfile()} + set {_uniqueStorage()._userProfile = newValue} + } + /// Returns true if `userProfile` has been explicitly set. + public var hasUserProfile: Bool {return _storage._userProfile != nil} + /// Clears the value of `userProfile`. Subsequent reads from it will return its default value. + public mutating func clearUserProfile() {_uniqueStorage()._userProfile = nil} + + /// The Ed25519 public key the user registered their account with. It is + /// the key the creator wraps the chat key for when admitting them (see + /// KeyEnvelope). + public var publicKey: Flipcash_Common_V1_PublicKey { + get {return _storage._publicKey ?? Flipcash_Common_V1_PublicKey()} + set {_uniqueStorage()._publicKey = newValue} + } + /// Returns true if `publicKey` has been explicitly set. + public var hasPublicKey: Bool {return _storage._publicKey != nil} + /// Clears the value of `publicKey`. Subsequent reads from it will return its default value. + public mutating func clearPublicKey() {_uniqueStorage()._publicKey = nil} + + /// When the user entered the lobby. + public var enteredAt: SwiftProtobuf.Google_Protobuf_Timestamp { + get {return _storage._enteredAt ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + set {_uniqueStorage()._enteredAt = newValue} + } + /// Returns true if `enteredAt` has been explicitly set. + public var hasEnteredAt: Bool {return _storage._enteredAt != nil} + /// Clears the value of `enteredAt`. Subsequent reads from it will return its default value. + public mutating func clearEnteredAt() {_uniqueStorage()._enteredAt = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + +/// Lobby is one private group whose lobby the requesting user is waiting in, +/// as returned by Chat.EnterLobby. +public struct Flipcash_Chat_V1_Lobby: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// The chat, as a user who is not a member sees it: its record (title, + /// picture, roster summary, creator) with in_lobby set, and none of its + /// members or messaging state. + public var chat: Flipcash_Chat_V1_Metadata { + get {return _chat ?? Flipcash_Chat_V1_Metadata()} + set {_chat = newValue} + } + /// Returns true if `chat` has been explicitly set. + public var hasChat: Bool {return self._chat != nil} + /// Clears the value of `chat`. Subsequent reads from it will return its default value. + public mutating func clearChat() {self._chat = nil} + + /// When the user entered the lobby. + public var enteredAt: SwiftProtobuf.Google_Protobuf_Timestamp { + get {return _enteredAt ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + set {_enteredAt = newValue} + } + /// Returns true if `enteredAt` has been explicitly set. + public var hasEnteredAt: Bool {return self._enteredAt != nil} + /// Clears the value of `enteredAt`. Subsequent reads from it will return its default value. + public mutating func clearEnteredAt() {self._enteredAt = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _chat: Flipcash_Chat_V1_Metadata? = nil + fileprivate var _enteredAt: SwiftProtobuf.Google_Protobuf_Timestamp? = nil +} + +/// LobbyUpdate is a best-effort, real-time change to a private group's lobby: +/// a user entering it (Chat.EnterLobby) or leaving it. It is delivered only +/// to the group's creator, who is the only user that can see the lobby. +/// +/// Like roster changes, these ride the event stream OUTSIDE the gap-detected +/// event log. They carry no version and are applied as received; a client +/// that suspects a miss refetches the lobby with Chat.GetLobbyMembers. +public struct Flipcash_Chat_V1_LobbyUpdate: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var kind: Flipcash_Chat_V1_LobbyUpdate.OneOf_Kind? = nil + + public var memberEntered: Flipcash_Chat_V1_LobbyUpdate.MemberEntered { + get { + if case .memberEntered(let v)? = kind {return v} + return Flipcash_Chat_V1_LobbyUpdate.MemberEntered() + } + set {kind = .memberEntered(newValue)} + } + + public var memberLeft: Flipcash_Chat_V1_LobbyUpdate.MemberLeft { + get { + if case .memberLeft(let v)? = kind {return v} + return Flipcash_Chat_V1_LobbyUpdate.MemberLeft() + } + set {kind = .memberLeft(newValue)} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum OneOf_Kind: Equatable, Sendable { + case memberEntered(Flipcash_Chat_V1_LobbyUpdate.MemberEntered) + case memberLeft(Flipcash_Chat_V1_LobbyUpdate.MemberLeft) + + } + + /// A user entered the lobby and is waiting to be admitted. + public struct MemberEntered: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var member: Flipcash_Chat_V1_LobbyMember { + get {return _member ?? Flipcash_Chat_V1_LobbyMember()} + set {_member = newValue} + } + /// Returns true if `member` has been explicitly set. + public var hasMember: Bool {return self._member != nil} + /// Clears the value of `member`. Subsequent reads from it will return its default value. + public mutating func clearMember() {self._member = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _member: Flipcash_Chat_V1_LobbyMember? = nil + } + + /// A user is no longer in the lobby: they withdrew (Chat.LeaveLobby), + /// were denied (Chat.DenyLobbyMember) or were admitted + /// (Chat.AdmitLobbyMember). An admission also arrives as a + /// RosterUpdate.MemberJoined. + public struct MemberLeft: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// The user that left the lobby + public var userID: Flipcash_Common_V1_UserId { + get {return _userID ?? Flipcash_Common_V1_UserId()} + set {_userID = newValue} + } + /// Returns true if `userID` has been explicitly set. + public var hasUserID: Bool {return self._userID != nil} + /// Clears the value of `userID`. Subsequent reads from it will return its default value. + public mutating func clearUserID() {self._userID = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _userID: Flipcash_Common_V1_UserId? = nil + } + + public init() {} +} + +public struct Flipcash_Chat_V1_LobbyUpdateBatch: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var lobbyUpdates: [Flipcash_Chat_V1_LobbyUpdate] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "flipcash.chat.v1" @@ -1033,7 +1389,7 @@ extension Flipcash_Chat_V1_ChatType: SwiftProtobuf._ProtoNameProviding { extension Flipcash_Chat_V1_Metadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".Metadata" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{1}type\0\u{1}members\0\u{3}last_message\0\u{3}last_activity\0\u{3}latest_event_sequence\0\u{3}is_hidden\0\u{1}title\0\u{1}picture\0\u{3}roster_summary\0\u{1}rules\0\u{3}viewer_state\0\u{1}creator\0\u{4}W\u{1}use_e2ee\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{1}type\0\u{1}members\0\u{3}last_message\0\u{3}last_activity\0\u{3}latest_event_sequence\0\u{3}is_hidden\0\u{1}title\0\u{1}picture\0\u{3}roster_summary\0\u{1}rules\0\u{3}viewer_state\0\u{1}creator\0\u{3}is_private\0\u{3}in_lobby\0\u{4}U\u{1}use_e2ee\0") fileprivate class _StorageClass { var _chatID: Flipcash_Common_V1_ChatId? = nil @@ -1049,6 +1405,8 @@ extension Flipcash_Chat_V1_Metadata: SwiftProtobuf.Message, SwiftProtobuf._Messa var _rules: Flipcash_Chat_V1_Rules? = nil var _viewerState: Flipcash_Chat_V1_ViewerState? = nil var _creator: Flipcash_Common_V1_UserId? = nil + var _isPrivate: Bool = false + var _inLobby: Bool = false var _useE2Ee: Bool = false // This property is used as the initial default value for new instances of the type. @@ -1073,6 +1431,8 @@ extension Flipcash_Chat_V1_Metadata: SwiftProtobuf.Message, SwiftProtobuf._Messa _rules = source._rules _viewerState = source._viewerState _creator = source._creator + _isPrivate = source._isPrivate + _inLobby = source._inLobby _useE2Ee = source._useE2Ee } } @@ -1105,6 +1465,8 @@ extension Flipcash_Chat_V1_Metadata: SwiftProtobuf.Message, SwiftProtobuf._Messa case 11: try { try decoder.decodeSingularMessageField(value: &_storage._rules) }() case 12: try { try decoder.decodeSingularMessageField(value: &_storage._viewerState) }() case 13: try { try decoder.decodeSingularMessageField(value: &_storage._creator) }() + case 14: try { try decoder.decodeSingularBoolField(value: &_storage._isPrivate) }() + case 15: try { try decoder.decodeSingularBoolField(value: &_storage._inLobby) }() case 100: try { try decoder.decodeSingularBoolField(value: &_storage._useE2Ee) }() default: break } @@ -1157,6 +1519,12 @@ extension Flipcash_Chat_V1_Metadata: SwiftProtobuf.Message, SwiftProtobuf._Messa try { if let v = _storage._creator { try visitor.visitSingularMessageField(value: v, fieldNumber: 13) } }() + if _storage._isPrivate != false { + try visitor.visitSingularBoolField(value: _storage._isPrivate, fieldNumber: 14) + } + if _storage._inLobby != false { + try visitor.visitSingularBoolField(value: _storage._inLobby, fieldNumber: 15) + } if _storage._useE2Ee != false { try visitor.visitSingularBoolField(value: _storage._useE2Ee, fieldNumber: 100) } @@ -1182,6 +1550,8 @@ extension Flipcash_Chat_V1_Metadata: SwiftProtobuf.Message, SwiftProtobuf._Messa if _storage._rules != rhs_storage._rules {return false} if _storage._viewerState != rhs_storage._viewerState {return false} if _storage._creator != rhs_storage._creator {return false} + if _storage._isPrivate != rhs_storage._isPrivate {return false} + if _storage._inLobby != rhs_storage._inLobby {return false} if _storage._useE2Ee != rhs_storage._useE2Ee {return false} return true } @@ -1593,36 +1963,74 @@ extension Flipcash_Chat_V1_MentionSuggestion: SwiftProtobuf.Message, SwiftProtob public static let protoMessageName: String = _protobuf_package + ".MentionSuggestion" public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}user_profile\0\u{3}last_sent_at\0") + fileprivate class _StorageClass { + var _userProfile: Flipcash_Profile_V1_UserProfile? = nil + var _lastSentAt: SwiftProtobuf.Google_Protobuf_Timestamp? = nil + + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _userProfile = source._userProfile + _lastSentAt = source._lastSentAt + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._userProfile) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._lastSentAt) }() - default: break + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &_storage._userProfile) }() + case 2: try { try decoder.decodeSingularMessageField(value: &_storage._lastSentAt) }() + default: break + } } } } public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._userProfile { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try { if let v = self._lastSentAt { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = _storage._userProfile { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = _storage._lastSentAt { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + } try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: Flipcash_Chat_V1_MentionSuggestion, rhs: Flipcash_Chat_V1_MentionSuggestion) -> Bool { - if lhs._userProfile != rhs._userProfile {return false} - if lhs._lastSentAt != rhs._lastSentAt {return false} + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._userProfile != rhs_storage._userProfile {return false} + if _storage._lastSentAt != rhs_storage._lastSentAt {return false} + return true + } + if !storagesAreEqual {return false} + } if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -2345,3 +2753,335 @@ extension Flipcash_Chat_V1_IdempotencyKey: SwiftProtobuf.Message, SwiftProtobuf. return true } } + +extension Flipcash_Chat_V1_KeyEnvelope: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".KeyEnvelope" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}scheme\0\u{1}nonce\0\u{1}ciphertext\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.scheme) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.nonce) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self.ciphertext) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.scheme != .unknown { + try visitor.visitSingularEnumField(value: self.scheme, fieldNumber: 1) + } + if !self.nonce.isEmpty { + try visitor.visitSingularBytesField(value: self.nonce, fieldNumber: 2) + } + if !self.ciphertext.isEmpty { + try visitor.visitSingularBytesField(value: self.ciphertext, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_KeyEnvelope, rhs: Flipcash_Chat_V1_KeyEnvelope) -> Bool { + if lhs.scheme != rhs.scheme {return false} + if lhs.nonce != rhs.nonce {return false} + if lhs.ciphertext != rhs.ciphertext {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_KeyEnvelope.Scheme: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNKNOWN\0\u{1}X25519_XCHACHA20POLY1305\0") +} + +extension Flipcash_Chat_V1_LobbyMember: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LobbyMember" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}user_profile\0\u{3}public_key\0\u{3}entered_at\0") + + fileprivate class _StorageClass { + var _userProfile: Flipcash_Profile_V1_UserProfile? = nil + var _publicKey: Flipcash_Common_V1_PublicKey? = nil + var _enteredAt: SwiftProtobuf.Google_Protobuf_Timestamp? = nil + + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _userProfile = source._userProfile + _publicKey = source._publicKey + _enteredAt = source._enteredAt + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + public mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &_storage._userProfile) }() + case 2: try { try decoder.decodeSingularMessageField(value: &_storage._publicKey) }() + case 3: try { try decoder.decodeSingularMessageField(value: &_storage._enteredAt) }() + default: break + } + } + } + } + + public func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = _storage._userProfile { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = _storage._publicKey { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = _storage._enteredAt { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_LobbyMember, rhs: Flipcash_Chat_V1_LobbyMember) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._userProfile != rhs_storage._userProfile {return false} + if _storage._publicKey != rhs_storage._publicKey {return false} + if _storage._enteredAt != rhs_storage._enteredAt {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_Lobby: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".Lobby" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}chat\0\u{3}entered_at\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._chat) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._enteredAt) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._chat { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._enteredAt { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_Lobby, rhs: Flipcash_Chat_V1_Lobby) -> Bool { + if lhs._chat != rhs._chat {return false} + if lhs._enteredAt != rhs._enteredAt {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_LobbyUpdate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LobbyUpdate" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}member_entered\0\u{3}member_left\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { + var v: Flipcash_Chat_V1_LobbyUpdate.MemberEntered? + var hadOneofValue = false + if let current = self.kind { + hadOneofValue = true + if case .memberEntered(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.kind = .memberEntered(v) + } + }() + case 2: try { + var v: Flipcash_Chat_V1_LobbyUpdate.MemberLeft? + var hadOneofValue = false + if let current = self.kind { + hadOneofValue = true + if case .memberLeft(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.kind = .memberLeft(v) + } + }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + switch self.kind { + case .memberEntered?: try { + guard case .memberEntered(let v)? = self.kind else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + }() + case .memberLeft?: try { + guard case .memberLeft(let v)? = self.kind else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case nil: break + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_LobbyUpdate, rhs: Flipcash_Chat_V1_LobbyUpdate) -> Bool { + if lhs.kind != rhs.kind {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_LobbyUpdate.MemberEntered: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Chat_V1_LobbyUpdate.protoMessageName + ".MemberEntered" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}member\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._member) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._member { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_LobbyUpdate.MemberEntered, rhs: Flipcash_Chat_V1_LobbyUpdate.MemberEntered) -> Bool { + if lhs._member != rhs._member {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_LobbyUpdate.MemberLeft: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Chat_V1_LobbyUpdate.protoMessageName + ".MemberLeft" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}user_id\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._userID) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._userID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_LobbyUpdate.MemberLeft, rhs: Flipcash_Chat_V1_LobbyUpdate.MemberLeft) -> Bool { + if lhs._userID != rhs._userID {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_LobbyUpdateBatch: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LobbyUpdateBatch" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}lobby_updates\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedMessageField(value: &self.lobbyUpdates) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.lobbyUpdates.isEmpty { + try visitor.visitRepeatedMessageField(value: self.lobbyUpdates, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_LobbyUpdateBatch, rhs: Flipcash_Chat_V1_LobbyUpdateBatch) -> Bool { + if lhs.lobbyUpdates != rhs.lobbyUpdates {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Sources/Flipcash2ClientProtocol/event_v1_model.pb.swift b/Sources/Flipcash2ClientProtocol/event_v1_model.pb.swift index f11c86e..cc08ace 100644 --- a/Sources/Flipcash2ClientProtocol/event_v1_model.pb.swift +++ b/Sources/Flipcash2ClientProtocol/event_v1_model.pb.swift @@ -357,6 +357,20 @@ public struct Flipcash_Event_V1_ChatUpdate: Sendable { /// Clears the value of `rosterUpdates`. Subsequent reads from it will return its default value. public mutating func clearRosterUpdates() {self._rosterUpdates = nil} + /// If present, best-effort real-time changes to the chat's lobby — users + /// entering or leaving it. Sent only for a private group, and only to its + /// creator. Like roster_updates, they are NOT part of the gap-detected + /// event log; the creator reconciles any misses by refetching the lobby + /// with Chat.GetLobbyMembers. + public var lobbyUpdates: Flipcash_Chat_V1_LobbyUpdateBatch { + get {return _lobbyUpdates ?? Flipcash_Chat_V1_LobbyUpdateBatch()} + set {_lobbyUpdates = newValue} + } + /// Returns true if `lobbyUpdates` has been explicitly set. + public var hasLobbyUpdates: Bool {return self._lobbyUpdates != nil} + /// Clears the value of `lobbyUpdates`. Subsequent reads from it will return its default value. + public mutating func clearLobbyUpdates() {self._lobbyUpdates = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -367,6 +381,7 @@ public struct Flipcash_Event_V1_ChatUpdate: Sendable { fileprivate var _events: Flipcash_Messaging_V1_EventBatch? = nil fileprivate var _reactionUpdates: Flipcash_Messaging_V1_ReactionUpdateBatch? = nil fileprivate var _rosterUpdates: Flipcash_Chat_V1_RosterUpdateBatch? = nil + fileprivate var _lobbyUpdates: Flipcash_Chat_V1_LobbyUpdateBatch? = nil } /// BlobUpdate notifies the recipient in real time that blobs they uploaded have @@ -812,7 +827,7 @@ extension Flipcash_Event_V1_ClientPong: SwiftProtobuf.Message, SwiftProtobuf._Me extension Flipcash_Event_V1_ChatUpdate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".ChatUpdate" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}chat\0\u{4}\u{2}pointer_updates\0\u{3}is_typing_notifications\0\u{3}metadata_updates\0\u{1}events\0\u{3}reaction_updates\0\u{3}roster_updates\0\u{c}\u{2}\u{1}") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}chat\0\u{4}\u{2}pointer_updates\0\u{3}is_typing_notifications\0\u{3}metadata_updates\0\u{1}events\0\u{3}reaction_updates\0\u{3}roster_updates\0\u{3}lobby_updates\0\u{c}\u{2}\u{1}") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -827,6 +842,7 @@ extension Flipcash_Event_V1_ChatUpdate: SwiftProtobuf.Message, SwiftProtobuf._Me case 6: try { try decoder.decodeSingularMessageField(value: &self._events) }() case 7: try { try decoder.decodeSingularMessageField(value: &self._reactionUpdates) }() case 8: try { try decoder.decodeSingularMessageField(value: &self._rosterUpdates) }() + case 9: try { try decoder.decodeSingularMessageField(value: &self._lobbyUpdates) }() default: break } } @@ -858,6 +874,9 @@ extension Flipcash_Event_V1_ChatUpdate: SwiftProtobuf.Message, SwiftProtobuf._Me try { if let v = self._rosterUpdates { try visitor.visitSingularMessageField(value: v, fieldNumber: 8) } }() + try { if let v = self._lobbyUpdates { + try visitor.visitSingularMessageField(value: v, fieldNumber: 9) + } }() try unknownFields.traverse(visitor: &visitor) } @@ -869,6 +888,7 @@ extension Flipcash_Event_V1_ChatUpdate: SwiftProtobuf.Message, SwiftProtobuf._Me if lhs._events != rhs._events {return false} if lhs._reactionUpdates != rhs._reactionUpdates {return false} if lhs._rosterUpdates != rhs._rosterUpdates {return false} + if lhs._lobbyUpdates != rhs._lobbyUpdates {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Sources/Flipcash2ClientProtocol/messaging_v1_messaging_service.pb.swift b/Sources/Flipcash2ClientProtocol/messaging_v1_messaging_service.pb.swift index 68cacc0..a9708a3 100644 --- a/Sources/Flipcash2ClientProtocol/messaging_v1_messaging_service.pb.swift +++ b/Sources/Flipcash2ClientProtocol/messaging_v1_messaging_service.pb.swift @@ -407,7 +407,12 @@ public struct Flipcash_Messaging_V1_SendMessageRequest: Sendable { /// - TextContent /// - ReplyContent /// - MediaContent - /// - EncryptedContent, in DMs only + /// - EncryptedContent, in DMs and private groups only. A private group + /// accepts nothing else. + /// + /// A private group whose creator has not stored its key yet (see + /// chat.v1.StartChatRequest.PrivateGroupChatParameters) accepts nothing + /// at all, and the send is DENIED. public var content: [Flipcash_Messaging_V1_Content] = [] /// Client-generated idempotency token for this send. Used to dedup retried @@ -465,8 +470,12 @@ public struct Flipcash_Messaging_V1_SendMessageResponse: Sendable { case ok // = 0 case denied // = 1 - /// The content is EncryptedContent and the chat is not a DM. + /// The content is EncryptedContent and the chat is neither a DM nor a + /// private group, or its scheme is not the one the chat uses. case encryptionNotAllowed // = 2 + + /// The chat is a private group and the content is not EncryptedContent. + case encryptionRequired // = 3 case UNRECOGNIZED(Int) public init() { @@ -478,6 +487,7 @@ public struct Flipcash_Messaging_V1_SendMessageResponse: Sendable { case 0: self = .ok case 1: self = .denied case 2: self = .encryptionNotAllowed + case 3: self = .encryptionRequired default: self = .UNRECOGNIZED(rawValue) } } @@ -487,6 +497,7 @@ public struct Flipcash_Messaging_V1_SendMessageResponse: Sendable { case .ok: return 0 case .denied: return 1 case .encryptionNotAllowed: return 2 + case .encryptionRequired: return 3 case .UNRECOGNIZED(let i): return i } } @@ -496,6 +507,7 @@ public struct Flipcash_Messaging_V1_SendMessageResponse: Sendable { .ok, .denied, .encryptionNotAllowed, + .encryptionRequired, ] } @@ -532,7 +544,9 @@ public struct Flipcash_Messaging_V1_EditMessageRequest: Sendable { /// - TextContent /// - ReplyContent /// - MediaContent - /// - EncryptedContent, in DMs only + /// - EncryptedContent, in DMs and private groups only. A private group + /// accepts nothing else, and nothing at all before its creator has + /// stored its key, as for SendMessage. public var content: [Flipcash_Messaging_V1_Content] = [] /// Required optimistic-concurrency guard: the message's event_sequence as the @@ -593,8 +607,12 @@ public struct Flipcash_Messaging_V1_EditMessageResponse: Sendable { /// current state for the client to reconcile against and retry. case conflict // = 4 - /// The content is EncryptedContent and the chat is not a DM. + /// The content is EncryptedContent and the chat is neither a DM nor a + /// private group, or its scheme is not the one the chat uses. case encryptionNotAllowed // = 5 + + /// The chat is a private group and the content is not EncryptedContent. + case encryptionRequired // = 6 case UNRECOGNIZED(Int) public init() { @@ -609,6 +627,7 @@ public struct Flipcash_Messaging_V1_EditMessageResponse: Sendable { case 3: self = .cannotEdit case 4: self = .conflict case 5: self = .encryptionNotAllowed + case 6: self = .encryptionRequired default: self = .UNRECOGNIZED(rawValue) } } @@ -621,6 +640,7 @@ public struct Flipcash_Messaging_V1_EditMessageResponse: Sendable { case .cannotEdit: return 3 case .conflict: return 4 case .encryptionNotAllowed: return 5 + case .encryptionRequired: return 6 case .UNRECOGNIZED(let i): return i } } @@ -633,6 +653,7 @@ public struct Flipcash_Messaging_V1_EditMessageResponse: Sendable { .cannotEdit, .conflict, .encryptionNotAllowed, + .encryptionRequired, ] } @@ -1958,7 +1979,7 @@ extension Flipcash_Messaging_V1_SendMessageResponse: SwiftProtobuf.Message, Swif } extension Flipcash_Messaging_V1_SendMessageResponse.Result: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}ENCRYPTION_NOT_ALLOWED\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}ENCRYPTION_NOT_ALLOWED\0\u{1}ENCRYPTION_REQUIRED\0") } extension Flipcash_Messaging_V1_EditMessageRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { @@ -2055,7 +2076,7 @@ extension Flipcash_Messaging_V1_EditMessageResponse: SwiftProtobuf.Message, Swif } extension Flipcash_Messaging_V1_EditMessageResponse.Result: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}MESSAGE_NOT_FOUND\0\u{1}CANNOT_EDIT\0\u{1}CONFLICT\0\u{1}ENCRYPTION_NOT_ALLOWED\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}MESSAGE_NOT_FOUND\0\u{1}CANNOT_EDIT\0\u{1}CONFLICT\0\u{1}ENCRYPTION_NOT_ALLOWED\0\u{1}ENCRYPTION_REQUIRED\0") } extension Flipcash_Messaging_V1_DeleteMessageRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { diff --git a/Sources/Flipcash2ClientProtocol/messaging_v1_model.pb.swift b/Sources/Flipcash2ClientProtocol/messaging_v1_model.pb.swift index 5a8a8b4..eb7a6ca 100644 --- a/Sources/Flipcash2ClientProtocol/messaging_v1_model.pb.swift +++ b/Sources/Flipcash2ClientProtocol/messaging_v1_model.pb.swift @@ -614,11 +614,17 @@ public struct Flipcash_Messaging_V1_DeletedContent: Sendable { fileprivate var _deletedBy: Flipcash_Common_V1_UserId? = nil } -/// End-to-end encrypted content, sent only in DMs (chat.v1.ChatType -/// CONTACT_DM or DM). SendMessage and EditMessage reject it in a group -/// chat. The server stores and relays the ciphertext as-is and cannot read it, -/// so it cannot moderate it, render a push preview from it, or produce a -/// placeholder for it. +/// End-to-end encrypted content, sent in DMs (chat.v1.ChatType CONTACT_DM or +/// DM) and in private groups (chat.v1.Metadata.is_private). SendMessage and +/// EditMessage reject it in any other group chat, and accept nothing else from +/// a client in a private group. The server stores and relays the ciphertext +/// as-is and cannot read it, so it cannot moderate it, render a push preview +/// from it, or produce a placeholder for it. +/// +/// The scheme depends on the kind of chat: a DM uses X25519_XCHACHA20POLY1305, +/// described under "DMs" below, and a private group uses +/// CHAT_KEY_XCHACHA20POLY1305, described under "Private groups". Either is +/// rejected in the other kind of chat. /// /// The plaintext is a serialized Content message. The allowed plaintext types /// are: @@ -629,6 +635,8 @@ public struct Flipcash_Messaging_V1_DeletedContent: Sendable { /// cannot decrypt the payload at all, renders the message as unsupported. A /// decrypted Content never itself holds EncryptedContent. /// +/// DMs +/// /// Encryption is between the Ed25519 public keys the two DM members registered /// their accounts with, which each member already knows. Neither key is carried /// in the message: the sender is Message.sender_id and the recipient is the @@ -683,6 +691,38 @@ public struct Flipcash_Messaging_V1_DeletedContent: Sendable { /// future. A scheme with ephemeral keys or a ratchet can be added as a new /// Scheme value without changing this message. /// +/// Private groups +/// +/// Every member of a private group holds the same 32-byte chat key, which each +/// obtains from their own chat.v1.KeyEnvelope. No key is carried in the +/// message. For scheme CHAT_KEY_XCHACHA20POLY1305, the sender encrypts with +/// XChaCha20-Poly1305, the same construction as step 4 above: +/// key = the group's chat key +/// nonce = 24 fresh random bytes, set in `nonce` below +/// plaintext = the serialized Content +/// aad = "flipcash-group-e2ee-v1" || chat_id || sender_id +/// The ciphertext, with its 16-byte Poly1305 tag appended, is set in +/// `ciphertext` below. chat_id is the raw bytes of common.v1.ChatId.value, and +/// sender_id is the raw bytes of common.v1.UserId.value for Message.sender_id. +/// Every member encrypts under the one key, which is safe because the nonce is +/// random and 24 bytes long. Never reuse a nonce, and do not substitute a +/// 12-byte-nonce AEAD. +/// +/// A member decrypts with the chat key and the same aad. The sender is bound +/// into the aad, so the server cannot present one member's ciphertext as +/// another's, or move it to another chat. Any member can, however, produce a +/// ciphertext that names another member as its sender: who wrote a message +/// rests on the server having authenticated Message.sender_id, not on the +/// encryption. As in a DM, nothing binds the ciphertext to its MessageId, so a +/// ciphertext re-posted in the same chat by the server decrypts as a valid +/// message there. +/// +/// This scheme has no forward secrecy either, and the chat key never changes: +/// anyone who obtains it, or the private key of any member, can decrypt every +/// message in the chat, past and future. A user who has left the group still +/// holds the key, and is kept from later messages only by the server no longer +/// serving them. +/// /// Media /// /// A MediaContent plaintext references blobs the sender uploaded for this chat @@ -701,6 +741,15 @@ public struct Flipcash_Messaging_V1_DeletedContent: Sendable { /// server from serving one blob's bytes in place of another's. sender_pk is the /// key of the member who uploaded the blob, which is always Message.sender_id. /// +/// For scheme CHAT_KEY_XCHACHA20POLY1305, each blob is encrypted with the +/// group's chat key in the same way, with its own aad: +/// nonce = 24 fresh random bytes +/// aad = "flipcash-group-e2ee-blob-v1" || chat_id || blob_id || sender_id +/// blob = nonce || XChaCha20-Poly1305(chat key, nonce, image bytes, aad) +/// sender_id is the member who uploaded the blob, which is always +/// Message.sender_id. It comes last because it is the one field here that is +/// not a fixed length. +/// /// The server never sees the image, so it cannot derive renditions or /// metadata, strip privacy metadata, or moderate. Before encrypting, the sender /// downscales the image, strips privacy metadata such as EXIF and location, @@ -753,7 +802,12 @@ public struct Flipcash_Messaging_V1_EncryptedContent: Sendable { public enum Scheme: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int case unknown // = 0 + + /// DMs: a key the two members derive from each other's public keys. case x25519Xchacha20Poly1305 // = 1 + + /// Private groups: the chat key every member holds. + case chatKeyXchacha20Poly1305 // = 2 case UNRECOGNIZED(Int) public init() { @@ -764,6 +818,7 @@ public struct Flipcash_Messaging_V1_EncryptedContent: Sendable { switch rawValue { case 0: self = .unknown case 1: self = .x25519Xchacha20Poly1305 + case 2: self = .chatKeyXchacha20Poly1305 default: self = .UNRECOGNIZED(rawValue) } } @@ -772,6 +827,7 @@ public struct Flipcash_Messaging_V1_EncryptedContent: Sendable { switch self { case .unknown: return 0 case .x25519Xchacha20Poly1305: return 1 + case .chatKeyXchacha20Poly1305: return 2 case .UNRECOGNIZED(let i): return i } } @@ -780,6 +836,7 @@ public struct Flipcash_Messaging_V1_EncryptedContent: Sendable { public static let allCases: [Flipcash_Messaging_V1_EncryptedContent.Scheme] = [ .unknown, .x25519Xchacha20Poly1305, + .chatKeyXchacha20Poly1305, ] } @@ -2090,7 +2147,7 @@ extension Flipcash_Messaging_V1_EncryptedContent: SwiftProtobuf.Message, SwiftPr } extension Flipcash_Messaging_V1_EncryptedContent.Scheme: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNKNOWN\0\u{1}X25519_XCHACHA20POLY1305\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNKNOWN\0\u{1}X25519_XCHACHA20POLY1305\0\u{1}CHAT_KEY_XCHACHA20POLY1305\0") } extension Flipcash_Messaging_V1_Emoji: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { diff --git a/Sources/Flipcash2ClientProtocol/profile_v1_model.pb.swift b/Sources/Flipcash2ClientProtocol/profile_v1_model.pb.swift index 183917e..44673f3 100644 --- a/Sources/Flipcash2ClientProtocol/profile_v1_model.pb.swift +++ b/Sources/Flipcash2ClientProtocol/profile_v1_model.pb.swift @@ -136,6 +136,34 @@ public struct Flipcash_Profile_V1_UserProfile: Sendable { /// Clears the value of `minDmChatInitFee`. Subsequent reads from it will return its default value. public mutating func clearMinDmChatInitFee() {self._minDmChatInitFee = nil} + /// A short free-form description the user wrote about themself. Public, so + /// it is returned for any user, not just the caller. Empty when the user + /// has not set one. Set it with SetBio. + /// + /// It is returned on GetProfile only: a profile embedded elsewhere (a chat + /// member row, a mention suggestion) carries no bio. + public var bio: String = String() + + /// The user's cover picture — the banner shown behind the profile view — + /// as the set of renditions it is stored as, like profile_picture. Public, + /// so it is returned for any user, not just the caller. + /// + /// Unset when the user has not set one. Set it with SetCoverPicture. + /// + /// It is returned on GetProfile only: a profile embedded elsewhere carries + /// no cover picture. To fetch the bytes of ANOTHER user's cover picture, a + /// GetBlobs call must carry a blob.v1.AccessContext whose `user_profile` + /// scope names this user, exactly as for profile_picture. A caller reading + /// its own needs none. + public var coverPicture: Flipcash_Blob_V1_Media { + get {return _coverPicture ?? Flipcash_Blob_V1_Media()} + set {_coverPicture = newValue} + } + /// Returns true if `coverPicture` has been explicitly set. + public var hasCoverPicture: Bool {return self._coverPicture != nil} + /// Clears the value of `coverPicture`. Subsequent reads from it will return its default value. + public mutating func clearCoverPicture() {self._coverPicture = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -148,6 +176,7 @@ public struct Flipcash_Profile_V1_UserProfile: Sendable { fileprivate var _joinTs: SwiftProtobuf.Google_Protobuf_Timestamp? = nil fileprivate var _flipcardCustomization: Flipcash_Profile_V1_FlipcardCustomization? = nil fileprivate var _minDmChatInitFee: Flipcash_Common_V1_FiatPaymentAmount? = nil + fileprivate var _coverPicture: Flipcash_Blob_V1_Media? = nil } public struct Flipcash_Profile_V1_SocialProfile: Sendable { @@ -278,7 +307,7 @@ fileprivate let _protobuf_package = "flipcash.profile.v1" extension Flipcash_Profile_V1_UserProfile: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".UserProfile" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}display_name\0\u{3}social_profiles\0\u{3}phone_number\0\u{3}email_address\0\u{3}profile_picture\0\u{3}join_ts\0\u{3}flipcard_customization\0\u{1}username\0\u{3}user_id\0\u{3}min_dm_chat_init_fee\0\u{3}is_username_auto_assigned\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}display_name\0\u{3}social_profiles\0\u{3}phone_number\0\u{3}email_address\0\u{3}profile_picture\0\u{3}join_ts\0\u{3}flipcard_customization\0\u{1}username\0\u{3}user_id\0\u{3}min_dm_chat_init_fee\0\u{3}is_username_auto_assigned\0\u{1}bio\0\u{3}cover_picture\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -297,6 +326,8 @@ extension Flipcash_Profile_V1_UserProfile: SwiftProtobuf.Message, SwiftProtobuf. case 9: try { try decoder.decodeSingularMessageField(value: &self._userID) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._minDmChatInitFee) }() case 11: try { try decoder.decodeSingularBoolField(value: &self.isUsernameAutoAssigned) }() + case 12: try { try decoder.decodeSingularStringField(value: &self.bio) }() + case 13: try { try decoder.decodeSingularMessageField(value: &self._coverPicture) }() default: break } } @@ -340,6 +371,12 @@ extension Flipcash_Profile_V1_UserProfile: SwiftProtobuf.Message, SwiftProtobuf. if self.isUsernameAutoAssigned != false { try visitor.visitSingularBoolField(value: self.isUsernameAutoAssigned, fieldNumber: 11) } + if !self.bio.isEmpty { + try visitor.visitSingularStringField(value: self.bio, fieldNumber: 12) + } + try { if let v = self._coverPicture { + try visitor.visitSingularMessageField(value: v, fieldNumber: 13) + } }() try unknownFields.traverse(visitor: &visitor) } @@ -355,6 +392,8 @@ extension Flipcash_Profile_V1_UserProfile: SwiftProtobuf.Message, SwiftProtobuf. if lhs._joinTs != rhs._joinTs {return false} if lhs._flipcardCustomization != rhs._flipcardCustomization {return false} if lhs._minDmChatInitFee != rhs._minDmChatInitFee {return false} + if lhs.bio != rhs.bio {return false} + if lhs._coverPicture != rhs._coverPicture {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Sources/Flipcash2ClientProtocol/profile_v1_profile_service.grpc.swift b/Sources/Flipcash2ClientProtocol/profile_v1_profile_service.grpc.swift index 5ea7048..10e4f97 100644 --- a/Sources/Flipcash2ClientProtocol/profile_v1_profile_service.grpc.swift +++ b/Sources/Flipcash2ClientProtocol/profile_v1_profile_service.grpc.swift @@ -68,6 +68,30 @@ public enum Flipcash_Profile_V1_Profile { method: "SetProfilePicture" ) } + /// Namespace for "SetCoverPicture" metadata. + public enum SetCoverPicture { + /// Request type for "SetCoverPicture". + public typealias Input = Flipcash_Profile_V1_SetCoverPictureRequest + /// Response type for "SetCoverPicture". + public typealias Output = Flipcash_Profile_V1_SetCoverPictureResponse + /// Descriptor for "SetCoverPicture". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.profile.v1.Profile"), + method: "SetCoverPicture" + ) + } + /// Namespace for "SetBio" metadata. + public enum SetBio { + /// Request type for "SetBio". + public typealias Input = Flipcash_Profile_V1_SetBioRequest + /// Response type for "SetBio". + public typealias Output = Flipcash_Profile_V1_SetBioResponse + /// Descriptor for "SetBio". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.profile.v1.Profile"), + method: "SetBio" + ) + } /// Namespace for "UpdateFlipcard" metadata. public enum UpdateFlipcard { /// Request type for "UpdateFlipcard". @@ -122,6 +146,8 @@ public enum Flipcash_Profile_V1_Profile { SetDisplayName.descriptor, SetUsername.descriptor, SetProfilePicture.descriptor, + SetCoverPicture.descriptor, + SetBio.descriptor, UpdateFlipcard.descriptor, SetMinDmChatInitFee.descriptor, LinkSocialAccount.descriptor, @@ -236,6 +262,57 @@ extension Flipcash_Profile_V1_Profile { onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result ) async throws -> Result where Result: Sendable + /// Call the "SetCoverPicture" method. + /// + /// > Source IDL Documentation: + /// > + /// > SetCoverPicture sets the caller's cover picture to a blob they have + /// > already uploaded via BlobStorage, replacing any cover picture already + /// > set. The upload flow and the server-derived renditions are exactly those + /// > of SetProfilePicture; only the surface the picture is shown on differs. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Profile_V1_SetCoverPictureRequest` message. + /// - serializer: A serializer for `Flipcash_Profile_V1_SetCoverPictureRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Profile_V1_SetCoverPictureResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + func setCoverPicture( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result + ) async throws -> Result where Result: Sendable + + /// Call the "SetBio" method. + /// + /// > Source IDL Documentation: + /// > + /// > SetBio sets the caller's bio, replacing any bio already set. An empty + /// > bio clears it. The bio is moderated before it is set, like a display + /// > name. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Profile_V1_SetBioRequest` message. + /// - serializer: A serializer for `Flipcash_Profile_V1_SetBioRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Profile_V1_SetBioResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + func setBio( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result + ) async throws -> Result where Result: Sendable + /// Call the "UpdateFlipcard" method. /// /// > Source IDL Documentation: @@ -482,6 +559,79 @@ extension Flipcash_Profile_V1_Profile { ) } + /// Call the "SetCoverPicture" method. + /// + /// > Source IDL Documentation: + /// > + /// > SetCoverPicture sets the caller's cover picture to a blob they have + /// > already uploaded via BlobStorage, replacing any cover picture already + /// > set. The upload flow and the server-derived renditions are exactly those + /// > of SetProfilePicture; only the surface the picture is shown on differs. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Profile_V1_SetCoverPictureRequest` message. + /// - serializer: A serializer for `Flipcash_Profile_V1_SetCoverPictureRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Profile_V1_SetCoverPictureResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func setCoverPicture( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.client.unary( + request: request, + descriptor: Flipcash_Profile_V1_Profile.Method.SetCoverPicture.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "SetBio" method. + /// + /// > Source IDL Documentation: + /// > + /// > SetBio sets the caller's bio, replacing any bio already set. An empty + /// > bio clears it. The bio is moderated before it is set, like a display + /// > name. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Profile_V1_SetBioRequest` message. + /// - serializer: A serializer for `Flipcash_Profile_V1_SetBioRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Profile_V1_SetBioResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func setBio( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.client.unary( + request: request, + descriptor: Flipcash_Profile_V1_Profile.Method.SetBio.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + /// Call the "UpdateFlipcard" method. /// /// > Source IDL Documentation: @@ -740,6 +890,69 @@ extension Flipcash_Profile_V1_Profile.ClientProtocol { ) } + /// Call the "SetCoverPicture" method. + /// + /// > Source IDL Documentation: + /// > + /// > SetCoverPicture sets the caller's cover picture to a blob they have + /// > already uploaded via BlobStorage, replacing any cover picture already + /// > set. The upload flow and the server-derived renditions are exactly those + /// > of SetProfilePicture; only the surface the picture is shown on differs. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Profile_V1_SetCoverPictureRequest` message. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func setCoverPicture( + request: GRPCCore.ClientRequest, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.setCoverPicture( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "SetBio" method. + /// + /// > Source IDL Documentation: + /// > + /// > SetBio sets the caller's bio, replacing any bio already set. An empty + /// > bio clears it. The bio is moderated before it is set, like a display + /// > name. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Profile_V1_SetBioRequest` message. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func setBio( + request: GRPCCore.ClientRequest, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.setBio( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + /// Call the "UpdateFlipcard" method. /// /// > Source IDL Documentation: @@ -993,6 +1206,77 @@ extension Flipcash_Profile_V1_Profile.ClientProtocol { ) } + /// Call the "SetCoverPicture" method. + /// + /// > Source IDL Documentation: + /// > + /// > SetCoverPicture sets the caller's cover picture to a blob they have + /// > already uploaded via BlobStorage, replacing any cover picture already + /// > set. The upload flow and the server-derived renditions are exactly those + /// > of SetProfilePicture; only the surface the picture is shown on differs. + /// + /// - Parameters: + /// - message: request message to send. + /// - metadata: Additional metadata to send, defaults to empty. + /// - options: Options to apply to this RPC, defaults to `.defaults`. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func setCoverPicture( + _ message: Flipcash_Profile_V1_SetCoverPictureRequest, + metadata: GRPCCore.Metadata = [:], + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + let request = GRPCCore.ClientRequest( + message: message, + metadata: metadata + ) + return try await self.setCoverPicture( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "SetBio" method. + /// + /// > Source IDL Documentation: + /// > + /// > SetBio sets the caller's bio, replacing any bio already set. An empty + /// > bio clears it. The bio is moderated before it is set, like a display + /// > name. + /// + /// - Parameters: + /// - message: request message to send. + /// - metadata: Additional metadata to send, defaults to empty. + /// - options: Options to apply to this RPC, defaults to `.defaults`. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func setBio( + _ message: Flipcash_Profile_V1_SetBioRequest, + metadata: GRPCCore.Metadata = [:], + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + let request = GRPCCore.ClientRequest( + message: message, + metadata: metadata + ) + return try await self.setBio( + request: request, + options: options, + onResponse: handleResponse + ) + } + /// Call the "UpdateFlipcard" method. /// /// > Source IDL Documentation: diff --git a/Sources/Flipcash2ClientProtocol/profile_v1_profile_service.pb.swift b/Sources/Flipcash2ClientProtocol/profile_v1_profile_service.pb.swift index e82a498..ecc4d8d 100644 --- a/Sources/Flipcash2ClientProtocol/profile_v1_profile_service.pb.swift +++ b/Sources/Flipcash2ClientProtocol/profile_v1_profile_service.pb.swift @@ -449,6 +449,206 @@ public struct Flipcash_Profile_V1_SetProfilePictureResponse: Sendable { fileprivate var _profilePicture: Flipcash_Blob_V1_Media? = nil } +public struct Flipcash_Profile_V1_SetCoverPictureRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// The blob holding the ORIGINAL image the caller uploaded. It must be owned + /// by the caller and READY; the server derives the remaining renditions from + /// it, as for SetProfilePicture. + public var blobID: Flipcash_Blob_V1_BlobId { + get {return _blobID ?? Flipcash_Blob_V1_BlobId()} + set {_blobID = newValue} + } + /// Returns true if `blobID` has been explicitly set. + public var hasBlobID: Bool {return self._blobID != nil} + /// Clears the value of `blobID`. Subsequent reads from it will return its default value. + public mutating func clearBlobID() {self._blobID = nil} + + public var auth: Flipcash_Common_V1_Auth { + get {return _auth ?? Flipcash_Common_V1_Auth()} + set {_auth = newValue} + } + /// Returns true if `auth` has been explicitly set. + public var hasAuth: Bool {return self._auth != nil} + /// Clears the value of `auth`. Subsequent reads from it will return its default value. + public mutating func clearAuth() {self._auth = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _blobID: Flipcash_Blob_V1_BlobId? = nil + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Profile_V1_SetCoverPictureResponse: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var result: Flipcash_Profile_V1_SetCoverPictureResponse.Result = .ok + + /// The caller's new cover picture, including the renditions the server + /// derived. Set only when result == OK. + public var coverPicture: Flipcash_Blob_V1_Media { + get {return _coverPicture ?? Flipcash_Blob_V1_Media()} + set {_coverPicture = newValue} + } + /// Returns true if `coverPicture` has been explicitly set. + public var hasCoverPicture: Bool {return self._coverPicture != nil} + /// Clears the value of `coverPicture`. Subsequent reads from it will return its default value. + public mutating func clearCoverPicture() {self._coverPicture = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + case denied // = 1 + + /// no such blob, or it is not owned by the caller + case blobNotFound // = 2 + + /// blob is still PENDING/PROCESSING; retry once READY + case blobNotReady // = 3 + + /// blob failed validation or moderation; terminal for this id, so the client must upload again + case blobRejected // = 4 + + /// blob is READY but unusable as a picture (e.g. not an image) + case invalidBlob // = 5 + case UNRECOGNIZED(Int) + + public init() { + self = .ok + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .ok + case 1: self = .denied + case 2: self = .blobNotFound + case 3: self = .blobNotReady + case 4: self = .blobRejected + case 5: self = .invalidBlob + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .denied: return 1 + case .blobNotFound: return 2 + case .blobNotReady: return 3 + case .blobRejected: return 4 + case .invalidBlob: return 5 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Profile_V1_SetCoverPictureResponse.Result] = [ + .ok, + .denied, + .blobNotFound, + .blobNotReady, + .blobRejected, + .invalidBlob, + ] + + } + + public init() {} + + fileprivate var _coverPicture: Flipcash_Blob_V1_Media? = nil +} + +public struct Flipcash_Profile_V1_SetBioRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// The new bio to set. Empty clears the bio. + public var bio: String = String() + + public var auth: Flipcash_Common_V1_Auth { + get {return _auth ?? Flipcash_Common_V1_Auth()} + set {_auth = newValue} + } + /// Returns true if `auth` has been explicitly set. + public var hasAuth: Bool {return self._auth != nil} + /// Clears the value of `auth`. Subsequent reads from it will return its default value. + public mutating func clearAuth() {self._auth = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Profile_V1_SetBioResponse: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var result: Flipcash_Profile_V1_SetBioResponse.Result = .ok + + /// The best-fit category that tripped moderation, mirroring the Moderation + /// service's vocabulary. Set only when result == FAILED_MODERATED; NONE + /// otherwise. + public var flaggedCategory: Flipcash_Moderation_V1_FlaggedCategory = .none + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + case invalidBio // = 1 + case denied // = 2 + case failedModerated // = 3 + case UNRECOGNIZED(Int) + + public init() { + self = .ok + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .ok + case 1: self = .invalidBio + case 2: self = .denied + case 3: self = .failedModerated + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .invalidBio: return 1 + case .denied: return 2 + case .failedModerated: return 3 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Profile_V1_SetBioResponse.Result] = [ + .ok, + .invalidBio, + .denied, + .failedModerated, + ] + + } + + public init() {} +} + public struct Flipcash_Profile_V1_UpdateFlipcardRequest: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -1192,6 +1392,166 @@ extension Flipcash_Profile_V1_SetProfilePictureResponse.Result: SwiftProtobuf._P public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}BLOB_NOT_FOUND\0\u{1}BLOB_NOT_READY\0\u{1}BLOB_REJECTED\0\u{1}INVALID_BLOB\0") } +extension Flipcash_Profile_V1_SetCoverPictureRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".SetCoverPictureRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}blob_id\0\u{2}\u{9}auth\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._blobID) }() + case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._blobID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Profile_V1_SetCoverPictureRequest, rhs: Flipcash_Profile_V1_SetCoverPictureRequest) -> Bool { + if lhs._blobID != rhs._blobID {return false} + if lhs._auth != rhs._auth {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Profile_V1_SetCoverPictureResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".SetCoverPictureResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{3}cover_picture\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._coverPicture) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) + } + try { if let v = self._coverPicture { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Profile_V1_SetCoverPictureResponse, rhs: Flipcash_Profile_V1_SetCoverPictureResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs._coverPicture != rhs._coverPicture {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Profile_V1_SetCoverPictureResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}BLOB_NOT_FOUND\0\u{1}BLOB_NOT_READY\0\u{1}BLOB_REJECTED\0\u{1}INVALID_BLOB\0") +} + +extension Flipcash_Profile_V1_SetBioRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".SetBioRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}bio\0\u{2}\u{9}auth\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.bio) }() + case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !self.bio.isEmpty { + try visitor.visitSingularStringField(value: self.bio, fieldNumber: 1) + } + try { if let v = self._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Profile_V1_SetBioRequest, rhs: Flipcash_Profile_V1_SetBioRequest) -> Bool { + if lhs.bio != rhs.bio {return false} + if lhs._auth != rhs._auth {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Profile_V1_SetBioResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".SetBioResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{3}flagged_category\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() + case 2: try { try decoder.decodeSingularEnumField(value: &self.flaggedCategory) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) + } + if self.flaggedCategory != .none { + try visitor.visitSingularEnumField(value: self.flaggedCategory, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Profile_V1_SetBioResponse, rhs: Flipcash_Profile_V1_SetBioResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs.flaggedCategory != rhs.flaggedCategory {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Profile_V1_SetBioResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}INVALID_BIO\0\u{1}DENIED\0\u{1}FAILED_MODERATED\0") +} + extension Flipcash_Profile_V1_UpdateFlipcardRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".UpdateFlipcardRequest" public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}color\0\u{2}\u{9}auth\0") diff --git a/flipcash2.lock b/flipcash2.lock index fc9ef0c..c32ab57 100644 --- a/flipcash2.lock +++ b/flipcash2.lock @@ -1,4 +1,4 @@ # Pinned upstream contract. Regenerate with scripts/sync-protos.sh . upstream: code-payments/flipcash2-protobuf-api -commit: 993127b50e42046ee2f290f122b05625774a0661 -subject: Update GetMentionSuggestions comment (#131) +commit: ec66e6b12c59d7eae3281341e74fe8853c90a306 +subject: Add support for bios and cover pictures for user profiles (#133) diff --git a/proto/blob/v1/blob_storage_service.proto b/proto/blob/v1/blob_storage_service.proto index 4852db7..13f6332 100644 --- a/proto/blob/v1/blob_storage_service.proto +++ b/proto/blob/v1/blob_storage_service.proto @@ -90,13 +90,16 @@ message InitiateExternalUploadRequest { // named here, and is rejected anywhere else (unencrypted MediaContent, // profile or chat pictures). oneof end_to_end_encrypted_for { - // The bytes are encrypted for this DM, as described in - // messaging.v1.EncryptedContent: the 24-byte nonce followed by the + // The bytes are encrypted for this DM or private group, as described + // in messaging.v1.EncryptedContent: the 24-byte nonce followed by the // ciphertext and its 16-byte tag. The caller must be a member of the - // chat and the chat must be a DM, or the upload is DENIED. Once READY, - // the blob is granted to the chat, so the other member can read it - // through AccessContext.chat, and it can be referenced only from - // EncryptedContent in that chat. + // chat and the chat must be a DM or a private group + // (chat.v1.Metadata.is_private), or the upload is DENIED. So is an + // upload for a private group whose creator has not stored its key + // yet (see chat.v1.StartChatRequest.PrivateGroupChatParameters). + // Once READY, the blob is granted to the chat, so its other members + // can read it through AccessContext.chat, and it can be referenced + // only from EncryptedContent in that chat. common.v1.ChatId chat = 4; } } diff --git a/proto/blob/v1/model.proto b/proto/blob/v1/model.proto index dd8cb20..4b8a40b 100644 --- a/proto/blob/v1/model.proto +++ b/proto/blob/v1/model.proto @@ -362,11 +362,11 @@ message AccessContext { // The caller is accessing these blobs from this user's public profile. // Authorized iff the blob is a rendition of that user's CURRENT profile - // picture — a profile grants nothing else, and a superseded picture's - // renditions stop resolving through it. + // picture or cover picture — a profile grants nothing else, and a + // superseded picture's renditions stop resolving through it. // - // A caller never needs this for its OWN profile picture, since it owns - // those blobs. + // A caller never needs this for its OWN pictures, since it owns those + // blobs. common.v1.UserId user_profile = 2 [(validate.rules).message.required = true]; // The caller is accessing these blobs from this chat's public profile. diff --git a/proto/chat/v1/chat_service.proto b/proto/chat/v1/chat_service.proto index 2c7151a..3f058c3 100644 --- a/proto/chat/v1/chat_service.proto +++ b/proto/chat/v1/chat_service.proto @@ -21,6 +21,14 @@ service Chat { // with view_mode REDACTED and none of the per-viewer fields (is_hidden, // viewer_state). An unauthenticated read under any other view_mode, or of // a DM, is DENIED. + // + // A private group (see Metadata.is_private) returns its record to any + // registered user, so that one who is not a member can see what they are + // asking to join, but its messaging state only to a member, whatever the + // view_mode. It has no public view: an unauthenticated read of one is + // DENIED. A private group whose creator has not stored its key yet (see + // StartChatRequest.PrivateGroupChatParameters) is returned like any + // other: it is visible, though nothing can happen in it. rpc GetChat(GetChatRequest) returns (GetChatResponse); // GetDmChatFeed gets the set of DM chats for an owner account using @@ -124,9 +132,19 @@ service Chat { rpc StartChat(StartChatRequest) returns (StartChatResponse); // JoinChat adds the caller to a chat's roster. + // + // A private group (see Metadata.is_private) cannot be joined this way and + // is DENIED: a user enters its lobby with EnterLobby and is admitted by + // its creator. The one exception is the group's creator, who needs no + // approval and rejoins a private group they left with this RPC. rpc JoinChat(JoinChatRequest) returns (JoinChatResponse); // LeaveChat removes the caller from a chat's roster. + // + // Leaving a private group discards the caller's key envelope (see + // KeyEnvelope), so returning means entering the lobby and being admitted + // again. The creator's envelope is kept, since no one else could give + // them another. rpc LeaveChat(LeaveChatRequest) returns (LeaveChatResponse); // EditChat edits a group chat's record. Every editable field is optional; @@ -163,6 +181,115 @@ service Chat { // not muted is a no-op. Every real change reaches the caller's other devices // as a MetadataUpdate.ViewerStateChanged on the event stream. rpc UnmuteChat(UnmuteChatRequest) returns (UnmuteChatResponse); + + // EnterLobby places the caller in a private group's lobby, where they + // wait for the group's creator to admit them. It is how a user asks to + // join a private group (see Metadata.is_private); JoinChat is DENIED for + // one. + // + // Entering a lobby the caller is already in is a no-op that returns OK. + // The server caps both how many users may wait in one lobby and how many + // lobbies one user may wait in. A group whose creator has not stored its + // key yet (see StartChatRequest.PrivateGroupChatParameters) has no lobby + // to enter, and is DENIED until they have. + // + // A lobby is visible only to the group's creator, through + // GetLobbyMembers: the users waiting in it are not shown to each other or + // to the group's members. The creator's devices learn of the entry as a + // LobbyUpdate on the event stream. + // + // While the caller waits, the chat's Metadata carries in_lobby. The + // caller leaves the lobby when they withdraw (LeaveLobby), when the + // creator denies them (DenyLobbyMember), or when the creator admits them + // (AdmitLobbyMember). An admission reaches the caller's devices as a + // RosterUpdate.MemberJoined naming them; a denial is not announced to + // them. + // + // There is no RPC that lists the lobbies a caller is waiting in. A client + // keeps the chats it entered itself, and reads in_lobby from GetChat to + // learn whether it is still waiting in one. A paged listing may be added + // later. + rpc EnterLobby(EnterLobbyRequest) returns (EnterLobbyResponse); + + // LeaveLobby withdraws the caller from a private group's lobby. Leaving a + // lobby the caller is not in is a no-op that returns OK. + rpc LeaveLobby(LeaveLobbyRequest) returns (LeaveLobbyResponse); + + // GetLobbyMembers pages the users waiting in a private group's lobby, + // earliest entered first. + // + // Only the group's creator may call it, and only while a member of the + // group; anyone else is DENIED. The page is read from an index that + // trails writes briefly, so a user who just entered may be absent and one + // who just left may be present. + rpc GetLobbyMembers(GetLobbyMembersRequest) returns (GetLobbyMembersResponse); + + // AdmitLobbyMember admits a user waiting in a private group's lobby: + // it stores the chat key wrapped for them (see KeyEnvelope) and adds them + // to the chat's roster. The envelope is stored first, so an admitted + // member always has one. + // + // Only the group's creator may call it, and only while a member of the + // group; anyone else is DENIED. So is a creator who has not stored their + // own key envelope yet (see StartChatRequest.PrivateGroupChatParameters): + // nobody is admitted to a group before its creator holds its key. This is + // the only way to give another user a key envelope. Admitting a user who + // is already a member is a no-op that returns OK and leaves their stored + // envelope as it is. + // + // The admission is announced like any other join: a + // RosterUpdate.MemberJoined to the chat's members and to the admitted + // user, who then fetches their envelope with GetKeyEnvelope. + rpc AdmitLobbyMember(AdmitLobbyMemberRequest) returns (AdmitLobbyMemberResponse); + + // DenyLobbyMember removes a user from a private group's lobby without + // admitting them. Only the group's creator may call it, and only while a + // member of the group; anyone else is DENIED. Denying a user who is not + // in the lobby is a no-op that returns OK. + // + // The denied user is not notified. The chat's Metadata stops carrying + // in_lobby for them, and they may enter the lobby again. + rpc DenyLobbyMember(DenyLobbyMemberRequest) returns (DenyLobbyMemberResponse); + + // SetKeyEnvelope stores the caller's own key envelope for a private + // group. Only a member may call it, and only for themself: an envelope + // for another user is stored by admitting them (AdmitLobbyMember). + // + // It is called in two situations (see KeyEnvelope): + // - By the group's creator, right after StartChat returns the chat's + // ID, to store the chat key they have just generated. This is the + // second step of creating a private group, and nothing can happen in + // the group until it succeeds (see + // StartChatRequest.PrivateGroupChatParameters). The creator's client + // keeps attempting it until it returns OK or ALREADY_SET: on a failed + // call, on its next launch, and on any device that finds the group + // with NO_ENVELOPE. + // - By an admitted member, after first opening the envelope the creator + // wrapped for them, to replace it with one wrapped by themself. + // + // The first envelope a caller stores for themself stands. Once the + // stored envelope is one the caller wrapped, a call with a different + // envelope changes nothing and returns ALREADY_SET, and the caller uses + // the stored one from GetKeyEnvelope. This keeps two of a creator's + // devices, each setting up the same new group, from holding different + // keys. Storing an envelope identical to the one stored is a no-op that + // returns OK. + // + // Nothing is published: the caller's other devices fetch the envelope + // when they need it. + rpc SetKeyEnvelope(SetKeyEnvelopeRequest) returns (SetKeyEnvelopeResponse); + + // GetKeyEnvelope returns the caller's own key envelope for a private + // group. Only a member may call it. The chat key never changes, so a + // client calls this once per chat per install and keeps the key it + // unwraps. + // + // NO_ENVELOPE is expected only for the creator of a group whose creation + // stopped between StartChat and SetKeyEnvelope. A creator's client that + // receives it generates a chat key and stores its envelope with + // SetKeyEnvelope, as described on KeyEnvelope, and keeps attempting that + // until it succeeds. + rpc GetKeyEnvelope(GetKeyEnvelopeRequest) returns (GetKeyEnvelopeResponse); } message GetChatRequest { @@ -368,15 +495,18 @@ message GetMentionSuggestionsResponse { message StartChatRequest { // Parameters for the kind of chat being created. The variant selects the - // chat type. + // kind of chat. Both create a chat of type GROUP: one public, one + // private. oneof parameters { option (validate.required) = true; - GroupChatParameters group = 1; + PublicGroupChatParameters public_group = 1; + PrivateGroupChatParameters private_group = 2; } - // Parameters for creating a group chat - message GroupChatParameters { + // Parameters for creating a public group chat: one that users join with + // JoinChat, subject to its rules. + message PublicGroupChatParameters { // Title for the chat. string title = 1 [(validate.rules).string = { min_len: 1 @@ -393,6 +523,39 @@ message StartChatRequest { Rules rules = 3; } + // Parameters for creating a private group chat (see Metadata.is_private): + // one whose creator admits each member from its lobby, and whose messages + // are end-to-end encrypted. Whether a group is private is fixed for the + // life of the chat. A private group has no rules. + // + // Creating a private group takes two steps. A key envelope is bound to + // the chat's ID, which the caller learns only from the response, so the + // caller generates the chat key once StartChat returns and stores its own + // envelope with SetKeyEnvelope (see KeyEnvelope). + // + // Until the creator's envelope is stored, the group has no key. It + // exists and is visible like any other private group: GetChat returns + // it, and it appears in the creator's GetGroupChatFeed. But nothing can + // happen in it, and the server refuses each of these as DENIED: + // - EnterLobby, by anyone + // - AdmitLobbyMember + // - Messaging.SendMessage and Messaging.EditMessage + // - BlobStorage.InitiateExternalUpload of a blob encrypted for the chat + // The creator's client must keep attempting SetKeyEnvelope until it + // succeeds. Storing the envelope lifts every refusal above, and it is + // never undone: the creator's envelope is kept even if they leave. + message PrivateGroupChatParameters { + // Title for the chat. + string title = 1 [(validate.rules).string = { + min_len: 1 + max_len: 64 + }]; + + // The blob holding the ORIGINAL picture the caller uploaded. Optional. + // If set, it must be owned by the caller and READY. + blob.v1.BlobId picture = 2; + } + // Key for retrying this request safely. A retry with the same key from the // same caller returns the chat the first attempt created, with result OK, // even if the parameters differ. @@ -545,3 +708,168 @@ message UnmuteChatResponse { // As for MuteChatResponse.viewer_state. ViewerState viewer_state = 2; } + +message EnterLobbyRequest { + common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message EnterLobbyResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; // The chat is not a private group, or has no key yet (see EnterLobby) + NOT_FOUND = 2; + ALREADY_MEMBER = 3; // Caller is already a member of the chat + LOBBY_FULL = 4; // The chat's lobby holds as many users as the server allows + TOO_MANY_LOBBIES = 5; // Caller is waiting in as many lobbies as the server allows + } + + // The caller's place in the lobby, so the client can record the chat it + // is now waiting in without a refetch. Set only when result == OK. + Lobby lobby = 2; +} + +message LeaveLobbyRequest { + common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message LeaveLobbyResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; // The chat is not a private group + NOT_FOUND = 2; + } +} + +message GetLobbyMembersRequest { + common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; + + // QueryOptions controls page_size, capped at 100. Ordering is fixed to + // earliest entered first and is not client-selectable. + // + // Leave query_options.paging_token unset on the first request. On every + // subsequent request, set it to the paging_token from the most recent + // response. The token is opaque and server-generated; do not construct + // it. It is bound to chat_id and refused with another chat. + common.v1.QueryOptions query_options = 2; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message GetLobbyMembersResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; // Caller is not the chat's creator, or the chat is not a private group + NOT_FOUND = 2; + } + + // The page, earliest entered first. Set when result is OK. + repeated LobbyMember members = 2 [(validate.rules).repeated = { + min_items: 0 + max_items: 100 + }]; + + // PagingToken carries the cursor after the last member returned. The + // client MUST send it back in query_options.paging_token on the next + // GetLobbyMembersRequest. Set when result is OK and has_more is true. + common.v1.PagingToken paging_token = 3; + + // HasMore indicates whether further pages remain. When true, the client + // should issue another GetLobbyMembersRequest with the returned + // paging_token. + bool has_more = 4; +} + +message AdmitLobbyMemberRequest { + common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; + + // The user to admit, who must be waiting in the chat's lobby. + common.v1.UserId user_id = 2 [(validate.rules).message.required = true]; + + // The chat key wrapped by the caller for that user's public key, as + // given by LobbyMember.public_key (see KeyEnvelope). The server cannot + // check its contents: an envelope the user cannot open leaves them a + // member who cannot read the chat. + KeyEnvelope key_envelope = 3 [(validate.rules).message.required = true]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message AdmitLobbyMemberResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; // Caller may not admit users to this chat (see AdmitLobbyMember) + NOT_FOUND = 2; + NOT_IN_LOBBY = 3; // The user is neither waiting in the lobby nor a member + } +} + +message DenyLobbyMemberRequest { + common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; + + // The user to remove from the chat's lobby. + common.v1.UserId user_id = 2 [(validate.rules).message.required = true]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message DenyLobbyMemberResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; // Caller is not the chat's creator, or the chat is not a private group + NOT_FOUND = 2; + } +} + +message SetKeyEnvelopeRequest { + common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; + + // The chat key wrapped by the caller for themself (see KeyEnvelope). + KeyEnvelope key_envelope = 2 [(validate.rules).message.required = true]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message SetKeyEnvelopeResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; // Caller is not a member, or the chat is not a private group + NOT_FOUND = 2; + ALREADY_SET = 3; // Caller already stored a different envelope of their own, which stands + } +} + +message GetKeyEnvelopeRequest { + common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message GetKeyEnvelopeResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; // Caller is not a member, or the chat is not a private group + NOT_FOUND = 2; + NO_ENVELOPE = 3; // Caller is a member with no envelope stored + } + + // The caller's key envelope. Set only when result == OK. + KeyEnvelope key_envelope = 2; + + // The user who stored the envelope, as the server authenticated them: + // the caller, or the chat's creator. It tells the client whose public + // key to open the envelope against (see KeyEnvelope), and is only a + // hint: an envelope that does not open against that key is rejected. Set + // only when result == OK. + common.v1.UserId wrapped_by = 3; +} diff --git a/proto/chat/v1/model.proto b/proto/chat/v1/model.proto index ccebff5..533c4ce 100644 --- a/proto/chat/v1/model.proto +++ b/proto/chat/v1/model.proto @@ -72,7 +72,9 @@ message Metadata { RosterSummary roster_summary = 10 [(validate.rules).message.required = true]; // Rules governing participation in this chat. Only supported for group - // chats. If not set, the chat has no participation requirements. + // chats. If not set, the chat has no participation requirements. Never + // set for a private group (see is_private), which admits a user by its + // creator's approval instead. Rules rules = 11; // Per-viewer chat state, including what the viewer may do in the chat. @@ -82,9 +84,32 @@ message Metadata { // The user that created this chat. Set only for group chats. common.v1.UserId creator = 13; + // Whether this chat is a private group. Only supported for group chats, + // and fixed when the chat is created (see + // StartChatRequest.private_group). + // + // A private group has no rules. Nobody joins it with Chat.JoinChat: a + // user enters its lobby with Chat.EnterLobby and is admitted by its + // creator. Only its members can read its messages, under any + // messaging.v1.ViewMode, and every message a client sends in it is + // messaging.v1.EncryptedContent, encrypted with the group's chat key + // (see KeyEnvelope). + // + // A private group is visible from the moment it is created, but nothing + // can happen in it until its creator has stored its key (see + // StartChatRequest.PrivateGroupChatParameters). + bool is_private = 14; + + // Whether the viewer is waiting in this private group's lobby (see + // Chat.EnterLobby). Per-viewer and server-computed, like is_hidden. + // Always false for a member, and for any chat that is not a private + // group. + bool in_lobby = 15; + // Whether messages in this chat are end-to-end encrypted (see // messaging.v1.EncryptedContent). Only supported for DMs (CONTACT_DM or DM); - // always false for group chats. + // always false for group chats. A private group is always end-to-end + // encrypted and does not use this flag (see is_private). // // Used to migrate DMs to E2EE: when true, clients send all new content in // the chat as EncryptedContent. When false, clients send content in the @@ -437,3 +462,179 @@ message IdempotencyKey { max_len: 16 }]; } + +// KeyEnvelope carries a private group's chat key to one user, encrypted so +// that only that user can open it. The server stores one envelope per member +// and returns it with Chat.GetKeyEnvelope. It cannot open an envelope and +// never holds the chat key. +// +// The chat key is 32 random bytes generated by the group's creator, and it +// never changes. It encrypts every message and blob in the group (see +// messaging.v1.EncryptedContent), so a member who holds it can read the +// group's whole history. +// +// An envelope is bound to the chat's ID, which the creator learns only when +// Chat.StartChat returns. So the creator generates the chat key then, and +// stores its own envelope with Chat.SetKeyEnvelope as the second step of +// creating the group. Until that step lands the group has no key, and the +// server lets nothing happen in it (see +// StartChatRequest.PrivateGroupChatParameters): in particular, nobody is +// admitted to it. +// +// The creator's client keeps attempting that step until it succeeds. A +// creator whose Chat.GetKeyEnvelope returns NO_ENVELOPE, on any device, +// generates a chat key and stores its envelope. That is always safe: the +// creator's envelope is never deleted, so its absence means no other user +// was ever given a key. If two of the creator's devices do this at once, +// the first envelope stored stands (SetKeyEnvelopeResponse.ALREADY_SET) and +// the other device adopts it. +// +// An envelope is made by a wrapper for a recipient. The wrapper is either +// the recipient themself, or the group's creator admitting the recipient +// with Chat.AdmitLobbyMember. The server records who stored each envelope +// and returns it as GetKeyEnvelopeResponse.wrapped_by. Both are identified +// by the Ed25519 public keys they registered their accounts with. For scheme +// X25519_XCHACHA20POLY1305: +// +// 1. Convert keys to X25519, exactly as in step 1 of +// messaging.v1.EncryptedContent: own Ed25519 private key to an X25519 +// private key, and the peer's Ed25519 public key to an X25519 public +// key. The wrapper's peer is the recipient, and the recipient's peer is +// the wrapper. When the wrapper is the recipient, the peer key is the +// user's own public key. Reject a public key that is not a valid point, +// or that converts to a low-order X25519 point. +// +// 2. Compute the shared secret: ss = X25519(own_x25519_priv, peer_x25519_pub). +// Abort if ss is all zeros. +// +// 3. Derive the wrapping key with HKDF-SHA256 (RFC 5869): +// salt = min(wrapper_pk, recipient_pk) || max(wrapper_pk, recipient_pk) +// ikm = ss +// info = "flipcash-group-key-wrap-v1" || chat_id +// L = 32 +// wrapper_pk and recipient_pk are the 32-byte Ed25519 public keys, +// ordered bytewise, and chat_id is the raw bytes of +// common.v1.ChatId.value. +// +// 4. Encrypt with XChaCha20-Poly1305 (the IETF construction in libsodium's +// crypto_aead_xchacha20poly1305_ietf_encrypt): +// key = the wrapping key from step 3 +// nonce = 24 fresh random bytes, set in `nonce` below +// plaintext = the 32-byte chat key +// aad = "flipcash-group-key-wrap-v1" || chat_id +// || wrapper_pk || recipient_pk +// The ciphertext, with its 16-byte Poly1305 tag appended, is set in +// `ciphertext` below. +// +// The recipient derives the same wrapping key and decrypts with the same +// aad. Only the wrapper and the recipient can derive that key, so an +// envelope that opens was made by one of them: neither the server nor +// another member can substitute a chat key of its choosing. +// +// That holds only as far as the recipient trusts the public key it uses for +// the wrapper. When wrapped_by is the recipient, that is the recipient's own +// key. Otherwise wrapped_by must be Metadata.creator, and the client uses +// the creator's public key, preferring one it learned outside this service +// (for example, from the invitation that led it to the chat) over one the +// server reports. A client rejects an envelope that names any other wrapper +// or that fails to open, and does not send in the chat. +// +// A member who opens an envelope wrapped by the creator wraps the chat key +// again for itself and stores that envelope with Chat.SetKeyEnvelope, so +// that opening it later, on any of the member's devices, depends on no one +// else's key. +// +// Like EncryptedContent, this has no forward secrecy: anyone who later +// obtains a member's private key can open that member's envelope and read +// the whole chat. A user who leaves the group still knows the chat key. The +// server stops serving them the group's messages, and nothing else keeps +// them out. +message KeyEnvelope { + // The scheme used, which determines how every other field is + // interpreted. Clients treat an unrecognized scheme as an envelope they + // cannot open. + Scheme scheme = 1 [(validate.rules).enum = { + in: [1] // X25519_XCHACHA20POLY1305 + }]; + enum Scheme { + UNKNOWN = 0; + X25519_XCHACHA20POLY1305 = 1; + } + + // The random XChaCha20-Poly1305 nonce, unique per envelope. + bytes nonce = 2 [(validate.rules).bytes = { + min_len: 24 + max_len: 24 + }]; + + // The encrypted 32-byte chat key with the 16-byte Poly1305 tag appended. + bytes ciphertext = 3 [(validate.rules).bytes = { + min_len: 48 + max_len: 48 + }]; +} + +// LobbyMember is a user waiting in a private group's lobby for its creator +// to admit them (see Chat.EnterLobby). It is not a Member: the user is not +// on the chat's roster and cannot read the chat. +message LobbyMember { + // The waiting user's public profile. user_id is always set. + profile.v1.UserProfile user_profile = 1 [(validate.rules).message.required = true]; + + // The Ed25519 public key the user registered their account with. It is + // the key the creator wraps the chat key for when admitting them (see + // KeyEnvelope). + common.v1.PublicKey public_key = 2 [(validate.rules).message.required = true]; + + // When the user entered the lobby. + google.protobuf.Timestamp entered_at = 3 [(validate.rules).timestamp.required = true]; +} + +// Lobby is one private group whose lobby the requesting user is waiting in, +// as returned by Chat.EnterLobby. +message Lobby { + // The chat, as a user who is not a member sees it: its record (title, + // picture, roster summary, creator) with in_lobby set, and none of its + // members or messaging state. + Metadata chat = 1 [(validate.rules).message.required = true]; + + // When the user entered the lobby. + google.protobuf.Timestamp entered_at = 2 [(validate.rules).timestamp.required = true]; +} + +// LobbyUpdate is a best-effort, real-time change to a private group's lobby: +// a user entering it (Chat.EnterLobby) or leaving it. It is delivered only +// to the group's creator, who is the only user that can see the lobby. +// +// Like roster changes, these ride the event stream OUTSIDE the gap-detected +// event log. They carry no version and are applied as received; a client +// that suspects a miss refetches the lobby with Chat.GetLobbyMembers. +message LobbyUpdate { + oneof kind { + option (validate.required) = true; + + MemberEntered member_entered = 1; + MemberLeft member_left = 2; + } + + // A user entered the lobby and is waiting to be admitted. + message MemberEntered { + LobbyMember member = 1 [(validate.rules).message.required = true]; + } + + // A user is no longer in the lobby: they withdrew (Chat.LeaveLobby), + // were denied (Chat.DenyLobbyMember) or were admitted + // (Chat.AdmitLobbyMember). An admission also arrives as a + // RosterUpdate.MemberJoined. + message MemberLeft { + // The user that left the lobby + common.v1.UserId user_id = 1 [(validate.rules).message.required = true]; + } +} + +message LobbyUpdateBatch { + repeated LobbyUpdate lobby_updates = 1 [(validate.rules).repeated = { + min_items: 1 + max_items: 100 + }]; +} diff --git a/proto/event/v1/model.proto b/proto/event/v1/model.proto index 5a2d252..0ca317c 100644 --- a/proto/event/v1/model.proto +++ b/proto/event/v1/model.proto @@ -140,6 +140,13 @@ message ChatUpdate { // This is how a chat left between pages of a chat feed read is // reconciled. chat.v1.RosterUpdateBatch roster_updates = 8; + + // If present, best-effort real-time changes to the chat's lobby — users + // entering or leaving it. Sent only for a private group, and only to its + // creator. Like roster_updates, they are NOT part of the gap-detected + // event log; the creator reconciles any misses by refetching the lobby + // with Chat.GetLobbyMembers. + chat.v1.LobbyUpdateBatch lobby_updates = 9; } // BlobUpdate notifies the recipient in real time that blobs they uploaded have diff --git a/proto/messaging/v1/messaging_service.proto b/proto/messaging/v1/messaging_service.proto index bbdd99a..7c03972 100644 --- a/proto/messaging/v1/messaging_service.proto +++ b/proto/messaging/v1/messaging_service.proto @@ -237,7 +237,12 @@ message SendMessageRequest { // - TextContent // - ReplyContent // - MediaContent - // - EncryptedContent, in DMs only + // - EncryptedContent, in DMs and private groups only. A private group + // accepts nothing else. + // + // A private group whose creator has not stored its key yet (see + // chat.v1.StartChatRequest.PrivateGroupChatParameters) accepts nothing + // at all, and the send is DENIED. repeated Content content = 2 [(validate.rules).repeated = { min_items: 1 max_items: 1 @@ -256,8 +261,11 @@ message SendMessageResponse { enum Result { OK = 0; DENIED = 1; - // The content is EncryptedContent and the chat is not a DM. + // The content is EncryptedContent and the chat is neither a DM nor a + // private group, or its scheme is not the one the chat uses. ENCRYPTION_NOT_ALLOWED = 2; + // The chat is a private group and the content is not EncryptedContent. + ENCRYPTION_REQUIRED = 3; } // The chat message that was sent if the RPC was succesful, which includes @@ -274,7 +282,9 @@ message EditMessageRequest { // - TextContent // - ReplyContent // - MediaContent - // - EncryptedContent, in DMs only + // - EncryptedContent, in DMs and private groups only. A private group + // accepts nothing else, and nothing at all before its creator has + // stored its key, as for SendMessage. repeated Content content = 3 [(validate.rules).repeated = { min_items: 1 max_items: 1 @@ -302,8 +312,11 @@ message EditMessageResponse { // edit/delete won). The edit was not applied; `message` carries the // current state for the client to reconcile against and retry. CONFLICT = 4; - // The content is EncryptedContent and the chat is not a DM. + // The content is EncryptedContent and the chat is neither a DM nor a + // private group, or its scheme is not the one the chat uses. ENCRYPTION_NOT_ALLOWED = 5; + // The chat is a private group and the content is not EncryptedContent. + ENCRYPTION_REQUIRED = 6; } // On OK, the updated materialized message (advanced event_sequence, diff --git a/proto/messaging/v1/model.proto b/proto/messaging/v1/model.proto index 63b7d39..0d86b45 100644 --- a/proto/messaging/v1/model.proto +++ b/proto/messaging/v1/model.proto @@ -292,11 +292,17 @@ message DeletedContent { common.v1.UserId deleted_by = 2; } -// End-to-end encrypted content, sent only in DMs (chat.v1.ChatType -// CONTACT_DM or DM). SendMessage and EditMessage reject it in a group -// chat. The server stores and relays the ciphertext as-is and cannot read it, -// so it cannot moderate it, render a push preview from it, or produce a -// placeholder for it. +// End-to-end encrypted content, sent in DMs (chat.v1.ChatType CONTACT_DM or +// DM) and in private groups (chat.v1.Metadata.is_private). SendMessage and +// EditMessage reject it in any other group chat, and accept nothing else from +// a client in a private group. The server stores and relays the ciphertext +// as-is and cannot read it, so it cannot moderate it, render a push preview +// from it, or produce a placeholder for it. +// +// The scheme depends on the kind of chat: a DM uses X25519_XCHACHA20POLY1305, +// described under "DMs" below, and a private group uses +// CHAT_KEY_XCHACHA20POLY1305, described under "Private groups". Either is +// rejected in the other kind of chat. // // The plaintext is a serialized Content message. The allowed plaintext types // are: @@ -307,6 +313,8 @@ message DeletedContent { // cannot decrypt the payload at all, renders the message as unsupported. A // decrypted Content never itself holds EncryptedContent. // +// DMs +// // Encryption is between the Ed25519 public keys the two DM members registered // their accounts with, which each member already knows. Neither key is carried // in the message: the sender is Message.sender_id and the recipient is the @@ -361,6 +369,38 @@ message DeletedContent { // future. A scheme with ephemeral keys or a ratchet can be added as a new // Scheme value without changing this message. // +// Private groups +// +// Every member of a private group holds the same 32-byte chat key, which each +// obtains from their own chat.v1.KeyEnvelope. No key is carried in the +// message. For scheme CHAT_KEY_XCHACHA20POLY1305, the sender encrypts with +// XChaCha20-Poly1305, the same construction as step 4 above: +// key = the group's chat key +// nonce = 24 fresh random bytes, set in `nonce` below +// plaintext = the serialized Content +// aad = "flipcash-group-e2ee-v1" || chat_id || sender_id +// The ciphertext, with its 16-byte Poly1305 tag appended, is set in +// `ciphertext` below. chat_id is the raw bytes of common.v1.ChatId.value, and +// sender_id is the raw bytes of common.v1.UserId.value for Message.sender_id. +// Every member encrypts under the one key, which is safe because the nonce is +// random and 24 bytes long. Never reuse a nonce, and do not substitute a +// 12-byte-nonce AEAD. +// +// A member decrypts with the chat key and the same aad. The sender is bound +// into the aad, so the server cannot present one member's ciphertext as +// another's, or move it to another chat. Any member can, however, produce a +// ciphertext that names another member as its sender: who wrote a message +// rests on the server having authenticated Message.sender_id, not on the +// encryption. As in a DM, nothing binds the ciphertext to its MessageId, so a +// ciphertext re-posted in the same chat by the server decrypts as a valid +// message there. +// +// This scheme has no forward secrecy either, and the chat key never changes: +// anyone who obtains it, or the private key of any member, can decrypt every +// message in the chat, past and future. A user who has left the group still +// holds the key, and is kept from later messages only by the server no longer +// serving them. +// // Media // // A MediaContent plaintext references blobs the sender uploaded for this chat @@ -379,6 +419,15 @@ message DeletedContent { // server from serving one blob's bytes in place of another's. sender_pk is the // key of the member who uploaded the blob, which is always Message.sender_id. // +// For scheme CHAT_KEY_XCHACHA20POLY1305, each blob is encrypted with the +// group's chat key in the same way, with its own aad: +// nonce = 24 fresh random bytes +// aad = "flipcash-group-e2ee-blob-v1" || chat_id || blob_id || sender_id +// blob = nonce || XChaCha20-Poly1305(chat key, nonce, image bytes, aad) +// sender_id is the member who uploaded the blob, which is always +// Message.sender_id. It comes last because it is the one field here that is +// not a fixed length. +// // The server never sees the image, so it cannot derive renditions or // metadata, strip privacy metadata, or moderate. Before encrypting, the sender // downscales the image, strips privacy metadata such as EXIF and location, @@ -412,11 +461,14 @@ message EncryptedContent { // every blob the plaintext references, is interpreted. Clients render an // unrecognized scheme as unsupported. Scheme scheme = 1 [(validate.rules).enum = { - in: [1] // X25519_XCHACHA20POLY1305 + in: [1, 2] // X25519_XCHACHA20POLY1305, CHAT_KEY_XCHACHA20POLY1305 }]; enum Scheme { - UNKNOWN = 0; - X25519_XCHACHA20POLY1305 = 1; + UNKNOWN = 0; + // DMs: a key the two members derive from each other's public keys. + X25519_XCHACHA20POLY1305 = 1; + // Private groups: the chat key every member holds. + CHAT_KEY_XCHACHA20POLY1305 = 2; } // The random XChaCha20-Poly1305 nonce, unique per encryption. diff --git a/proto/profile/v1/model.proto b/proto/profile/v1/model.proto index 67c024e..ac206a6 100644 --- a/proto/profile/v1/model.proto +++ b/proto/profile/v1/model.proto @@ -73,6 +73,29 @@ message UserProfile { // when the user hasn't set one, in which case the server default applies. // Update it with SetMinDmChatInitFee. common.v1.FiatPaymentAmount min_dm_chat_init_fee = 10; + + // A short free-form description the user wrote about themself. Public, so + // it is returned for any user, not just the caller. Empty when the user + // has not set one. Set it with SetBio. + // + // It is returned on GetProfile only: a profile embedded elsewhere (a chat + // member row, a mention suggestion) carries no bio. + string bio = 12 [(validate.rules).string = { + max_len: 160 + }]; + + // The user's cover picture — the banner shown behind the profile view — + // as the set of renditions it is stored as, like profile_picture. Public, + // so it is returned for any user, not just the caller. + // + // Unset when the user has not set one. Set it with SetCoverPicture. + // + // It is returned on GetProfile only: a profile embedded elsewhere carries + // no cover picture. To fetch the bytes of ANOTHER user's cover picture, a + // GetBlobs call must carry a blob.v1.AccessContext whose `user_profile` + // scope names this user, exactly as for profile_picture. A caller reading + // its own needs none. + blob.v1.Media cover_picture = 13; } message SocialProfile { diff --git a/proto/profile/v1/profile_service.proto b/proto/profile/v1/profile_service.proto index 686d745..26e2d2d 100644 --- a/proto/profile/v1/profile_service.proto +++ b/proto/profile/v1/profile_service.proto @@ -30,6 +30,17 @@ service Profile { // DISPLAY and THUMBNAIL renditions itself and returns the full set. rpc SetProfilePicture(SetProfilePictureRequest) returns (SetProfilePictureResponse); + // SetCoverPicture sets the caller's cover picture to a blob they have + // already uploaded via BlobStorage, replacing any cover picture already + // set. The upload flow and the server-derived renditions are exactly those + // of SetProfilePicture; only the surface the picture is shown on differs. + rpc SetCoverPicture(SetCoverPictureRequest) returns (SetCoverPictureResponse); + + // SetBio sets the caller's bio, replacing any bio already set. An empty + // bio clears it. The bio is moderated before it is set, like a display + // name. + rpc SetBio(SetBioRequest) returns (SetBioResponse); + // UpdateFlipcard updates the caller's Flipcard customization. Every field is // optional; only the ones set in the request are changed. rpc UpdateFlipcard(UpdateFlipcardRequest) returns (UpdateFlipcardResponse); @@ -154,6 +165,55 @@ message SetProfilePictureResponse { blob.v1.Media profile_picture = 2; } +message SetCoverPictureRequest { + // The blob holding the ORIGINAL image the caller uploaded. It must be owned + // by the caller and READY; the server derives the remaining renditions from + // it, as for SetProfilePicture. + blob.v1.BlobId blob_id = 1 [(validate.rules).message.required = true]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message SetCoverPictureResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; + BLOB_NOT_FOUND = 2; // no such blob, or it is not owned by the caller + BLOB_NOT_READY = 3; // blob is still PENDING/PROCESSING; retry once READY + BLOB_REJECTED = 4; // blob failed validation or moderation; terminal for this id, so the client must upload again + INVALID_BLOB = 5; // blob is READY but unusable as a picture (e.g. not an image) + } + + // The caller's new cover picture, including the renditions the server + // derived. Set only when result == OK. + blob.v1.Media cover_picture = 2; +} + +message SetBioRequest { + // The new bio to set. Empty clears the bio. + string bio = 1 [(validate.rules).string = { + max_len: 160 + }]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message SetBioResponse { + Result result = 1; + enum Result { + OK = 0; + INVALID_BIO = 1; + DENIED = 2; + FAILED_MODERATED = 3; + } + + // The best-fit category that tripped moderation, mirroring the Moderation + // service's vocabulary. Set only when result == FAILED_MODERATED; NONE + // otherwise. + moderation.v1.FlaggedCategory flagged_category = 2; +} + message UpdateFlipcardRequest { // The new colour of the Flipcard. Left unchanged when unset. common.v1.Color color = 1;