diff --git a/CHANGELOG.md b/CHANGELOG.md index 6516ce5..e601422 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,68 @@ 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.22.0 + +Synced to [`flipcash2-protobuf-api@b655df17`](https://github.com/code-payments/flipcash2-protobuf-api/commit/b655df1770f1d588736ee3dcb712d9be5ca2cf41), +picking up [#157](https://github.com/code-payments/flipcash2-protobuf-api/pull/157), +[#158](https://github.com/code-payments/flipcash2-protobuf-api/pull/158) and +[#159](https://github.com/code-payments/flipcash2-protobuf-api/pull/159). `chat.v1`, +`messaging.v1` and `common.v1` moved. + +This release adds invite links for private groups, plus anonymous reads of an invite and the +chat's roster. Upgrading breaks source in Swift only, not the wire. One enum gained cases, +appended at the end, so no existing value moved. + +### Breaking + +- `chat.v1.GetKeyEnvelopeResponse.key_envelope` (field 2) moved into a new `oneof envelope`, next + to `invite_key_bundle`. Swift loses `hasKeyEnvelope` and `clearKeyEnvelope`; switch on + `envelope` instead. Kotlin keeps `hasKeyEnvelope()` and gains `getEnvelopeCase()`. In either + language, a client that reads only `key_envelope` sees an empty envelope for a member who joined + with an invite. +- `common.v1.Username` now accepts 2 to 30 characters, up from 15. Nothing fails to compile, but a + client that validates, matches or encodes usernames at 15 will reject or truncate names the + server now issues. + +### Added + +- Invite RPCs on `chat.v1.Chat`: `CreateInvite`, `GetInvite`, `GetChatInvites` and + `RevokeInvite`, with `Invite`, `Invite.Status`, `InviteId`, `InviteToken`, `InviteKeyEnvelope` + and `InviteSecretEnvelope`. Only a private group's creator may create, list or revoke invites + today. `GetInvite` takes optional `auth`, so a link can be previewed without an account. +- The `Invite` docs specify the client-side crypto: how the token, wrap key and invite ID derive + from the link's secret (HKDF-SHA256 and SHA-256), the XChaCha20-Poly1305 encryption of the + chat key, and the creator's Ed25519 signature over it. iOS and Android must compute these + identically. +- `JoinChatRequest.invite` (field 2), an `InviteParameters` with the invite token and the joiner's + `InviteSecretEnvelope`. A private group can now be joined with an invite. +- `JoinChatResponse.Result` cases `INVITE_NOT_FOUND = 5`, `INVITE_EXHAUSTED = 6`, + `INVITE_EXPIRED = 7` and `INVITE_REVOKED = 8`, all appended. An exhaustive `switch` or `when` + over the result needs them before it compiles. +- `GetKeyEnvelopeResponse.invite_key_bundle` (oneof field 5), returned instead of `key_envelope` + to a member who joined with an invite and has not yet stored their own envelope with + `SetKeyEnvelope`. +- `GetKeyEnvelopeResponse.wrapped_by_public_key` (field 4). +- `GetRosterRequest.invite_token` (field 3). A link holder can read the roster with it. +- `chat.v1.ViewerState.Permissions.can_invite` (field 3). +- `messaging.v1.SystemContent` member-joined oneof gains `invited` (field 4), an `Invited` naming + `invited_by`. A client that renders join messages needs a case for it. + +### Changed + +- `GetRosterRequest.auth` is no longer required when `invite_token` is set. A request with + neither is still `DENIED`. +- `GetChat` on a private group without auth now returns its `Metadata` record instead of + `DENIED`. Its messaging state stays private. +- `JoinChat` on a private group by anyone but the creator is no longer always `DENIED`: it + succeeds with an `ACTIVE` invite. An invite join also removes the user from the lobby and is + announced as `RosterUpdate.MemberJoined`, like an admission. + +### Unchanged + +Nothing was renumbered. No service, RPC, field or enum case was removed. Apart from the oneof +move above, no field changed type or number. + ## 0.21.0 Synced to [`flipcash2-protobuf-api@b77bf78f`](https://github.com/code-payments/flipcash2-protobuf-api/commit/b77bf78ff8d9ea1bcbf9aa3afa6696ba7edd37a9), diff --git a/Sources/Flipcash2ClientProtocol/ContractInfo.swift b/Sources/Flipcash2ClientProtocol/ContractInfo.swift index 6830454..b05d09b 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.21.0-dev" - public static let protoCommit = "b77bf78ff8d9ea1bcbf9aa3afa6696ba7edd37a9" + public static let version = "0.22.0-dev" + public static let protoCommit = "b655df1770f1d588736ee3dcb712d9be5ca2cf41" public static var isLocal: Bool { protoCommit == localSentinel } diff --git a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift index 607732a..9a56988 100644 --- a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift +++ b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift @@ -248,6 +248,54 @@ public enum Flipcash_Chat_V1_Chat { method: "GetKeyEnvelope" ) } + /// Namespace for "CreateInvite" metadata. + public enum CreateInvite { + /// Request type for "CreateInvite". + public typealias Input = Flipcash_Chat_V1_CreateInviteRequest + /// Response type for "CreateInvite". + public typealias Output = Flipcash_Chat_V1_CreateInviteResponse + /// Descriptor for "CreateInvite". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "CreateInvite" + ) + } + /// Namespace for "GetInvite" metadata. + public enum GetInvite { + /// Request type for "GetInvite". + public typealias Input = Flipcash_Chat_V1_GetInviteRequest + /// Response type for "GetInvite". + public typealias Output = Flipcash_Chat_V1_GetInviteResponse + /// Descriptor for "GetInvite". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "GetInvite" + ) + } + /// Namespace for "GetChatInvites" metadata. + public enum GetChatInvites { + /// Request type for "GetChatInvites". + public typealias Input = Flipcash_Chat_V1_GetChatInvitesRequest + /// Response type for "GetChatInvites". + public typealias Output = Flipcash_Chat_V1_GetChatInvitesResponse + /// Descriptor for "GetChatInvites". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "GetChatInvites" + ) + } + /// Namespace for "RevokeInvite" metadata. + public enum RevokeInvite { + /// Request type for "RevokeInvite". + public typealias Input = Flipcash_Chat_V1_RevokeInviteRequest + /// Response type for "RevokeInvite". + public typealias Output = Flipcash_Chat_V1_RevokeInviteResponse + /// Descriptor for "RevokeInvite". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "RevokeInvite" + ) + } /// Namespace for "SetFeaturedGroups" metadata. public enum SetFeaturedGroups { /// Request type for "SetFeaturedGroups". @@ -353,6 +401,10 @@ public enum Flipcash_Chat_V1_Chat { DenyLobbyMember.descriptor, SetKeyEnvelope.descriptor, GetKeyEnvelope.descriptor, + CreateInvite.descriptor, + GetInvite.descriptor, + GetChatInvites.descriptor, + RevokeInvite.descriptor, SetFeaturedGroups.descriptor, GetFeaturedGroups.descriptor, SilenceUser.descriptor, @@ -394,8 +446,8 @@ extension Flipcash_Chat_V1_Chat { /// > 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 + /// > view_mode. Its public view is that record alone, with no messaging + /// > state. 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. /// @@ -536,9 +588,27 @@ extension Flipcash_Chat_V1_Chat { /// > carry none: group pointer advances are never broadcast, so a page of /// > them would be stale as soon as it was served. /// > - /// > Requires that the caller is a member of the chat. Anyone else is - /// > DENIED, including a non-member whom a group's listener rules admit to - /// > read its messages: who is in a chat is shown only to its members. + /// > Requires that the caller is a member of the chat, or holds a link to an + /// > invite to it (see Invite) and sends the invite's token in + /// > invite_token. Anyone else is DENIED, including a non-member whom a + /// > group's listener rules admit to read its messages: who is in a chat is + /// > shown only to its members and to those holding a link that would let + /// > them join it. + /// > + /// > The token is the credential, so auth is optional when it is sent (when + /// > set, auth must be valid). It is honored while its invite is ACTIVE and + /// > to chat_id; any other token is DENIED, as is a token sent for a public + /// > group or a DM. Whoever sends an honored token gets the page a member + /// > gets, registered or not, and a user the chat's creator banned + /// > included: a ban stops a user joining, not reading the roster through a + /// > link they hold, which the creator stops by revoking the invite. + /// > Reading the roster with a token counts no use of the invite, changes + /// > nothing, and is announced to no one. The invite is checked again on + /// > every page, so a walk ends with DENIED once it stops being ACTIVE. + /// > + /// > A member is served whether or not they send a token, which is not + /// > checked for them. A request with neither auth nor invite_token is + /// > DENIED. /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_GetRosterRequest` message. @@ -681,10 +751,41 @@ extension Flipcash_Chat_V1_Chat { /// > /// > 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. + /// > A private group (see Metadata.is_private) is joined this way only with + /// > an invite (JoinChatRequest.invite) or by its creator. Anyone else is + /// > DENIED, and enters its lobby with EnterLobby to be admitted by the + /// > creator. The creator needs neither approval nor an invite, and rejoins + /// > a private group they left with this RPC; an invite they pass is + /// > ignored. + /// > + /// > Joining with an invite (see Invite) needs no one's approval. The client + /// > previews the invite with GetInvite, which names the chat, and passes + /// > the invite's token here. The invite must be ACTIVE (INVITE_EXHAUSTED, + /// > INVITE_EXPIRED or INVITE_REVOKED otherwise), for a user who joined + /// > with it before as for anyone. + /// > + /// > The join is atomic: the caller becomes a member, the join is counted as + /// > one use of the invite (a user who left and joins again with it uses + /// > another), and the invite's copy of the chat key is stored as their key + /// > envelope, with the link's secret encrypted by the caller for themself + /// > (JoinChatRequest.InviteParameters.secret_envelope), together, or + /// > nothing happens. A caller waiting in the chat's + /// > lobby leaves it in the same step. A caller who is already a member gets + /// > OK with nothing changed: the invite is not checked, no use is counted, + /// > and their stored envelope stays as it is. Only a public group's rules + /// > or a private group's invite are checked, never both: an invite to a + /// > public group is INVITE_NOT_FOUND. + /// > + /// > Any of the joiner's devices then fetches the copy and the secret with + /// > GetKeyEnvelope (GetKeyEnvelopeResponse.invite_key_bundle), checks + /// > the copy's signature, opens the secret with the joiner's account key + /// > and the copy with the secret, wraps the chat key for the joiner and + /// > stores that envelope with SetKeyEnvelope (see KeyEnvelope). None of + /// > this needs the device the joiner joined with, or the link itself. A + /// > join with an invite is announced like an admission (see + /// > AdmitLobbyMember). A member whose copy turns out to hold the wrong key + /// > cannot fix it by joining again: they leave and are admitted from the + /// > lobby (see KeyEnvelope). /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_JoinChatRequest` message. @@ -710,9 +811,9 @@ extension Flipcash_Chat_V1_Chat { /// > 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. + /// > KeyEnvelope), so returning means being admitted from the lobby or + /// > joining with an invite 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. @@ -834,8 +935,8 @@ extension Flipcash_Chat_V1_Chat { /// > /// > 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. + /// > join a private group (see Metadata.is_private) when they hold no + /// > invite; JoinChat without one is DENIED for a private group. /// > /// > 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 @@ -850,10 +951,10 @@ extension Flipcash_Chat_V1_Chat { /// > /// > 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. + /// > creator denies them (DenyLobbyMember), when the creator admits them + /// > (AdmitLobbyMember), or when they join with an invite (JoinChat). 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 @@ -942,10 +1043,11 @@ extension Flipcash_Chat_V1_Chat { /// > 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. + /// > nobody is admitted to a group before its creator holds its key. Besides + /// > joining with an invite, which stores the invite's copy of the chat key + /// > for the joiner (see JoinChat), 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 @@ -1005,7 +1107,7 @@ extension Flipcash_Chat_V1_Chat { /// > 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): + /// > It is called in three 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 @@ -1016,6 +1118,11 @@ extension Flipcash_Chat_V1_Chat { /// > 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. + /// > - By a member who joined with an invite, after first opening the + /// > invite's copy of the chat key with the link's secret (from the + /// > secret envelope stored with it, or from the link), to replace it + /// > with one wrapped by themself. Storing it deletes the copy and that + /// > secret envelope (see InviteKeyEnvelope). /// > /// > The first envelope a caller stores for themself stands. Once the /// > stored envelope is one the caller wrapped, a call with a different @@ -1054,6 +1161,14 @@ extension Flipcash_Chat_V1_Chat { /// > client calls this once per chat per install and keeps the key it /// > unwraps. /// > + /// > For a member who joined with an invite and has not yet stored their + /// > own envelope, what is stored is the invite's copy of the chat key and + /// > the link's secret the member encrypted for themself when joining. Both + /// > are returned in invite_key_bundle in place of key_envelope. Any of + /// > the member's devices opens the secret with the member's account key, + /// > then the copy with the secret, and replaces the copy with an envelope + /// > of its own with SetKeyEnvelope (see InviteKeyEnvelope). + /// > /// > 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 @@ -1077,6 +1192,163 @@ extension Flipcash_Chat_V1_Chat { onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result ) async throws -> Result where Result: Sendable + /// Call the "CreateInvite" method. + /// + /// > Source IDL Documentation: + /// > + /// > CreateInvite creates an invite to a private group (see Invite): a link + /// > that lets users join it with JoinChat without the creator's approval. + /// > + /// > Only a member the server permits to invite may call it, as reported by + /// > ViewerState.Permissions.can_invite; anyone else is DENIED. So is any + /// > call for a group whose creator has not stored its key yet (see + /// > StartChatRequest.PrivateGroupChatParameters). + /// > + /// > The caller's client generates the link's secret and derives the + /// > invite's token, ID and wrap key from it (see Invite). It sends the + /// > token, the chat key encrypted under the wrap key and signed by the + /// > caller (InviteKeyEnvelope), and the secret encrypted for itself + /// > (InviteSecretEnvelope); the secret and the wrap key never leave it. The + /// > server computes the invite's ID from the token, which it never stores + /// > (see InviteToken), and checks the copy's signature against the + /// > caller's public key with that ID, refusing one that does not verify + /// > (INVALID_SIGNATURE), since no joiner could accept it. So a client that + /// > derived the ID wrongly is refused here, rather than sharing a link that + /// > admits no one. The server cannot check what either envelope holds: a + /// > copy that does not hold the chat key gives everyone who joins with the + /// > invite a key that reads nothing. + /// > + /// > An existing invite with the ID computed from the request's token is + /// > looked up before anything else is checked. A retry with the same token, + /// > by the same caller for the same chat, returns the invite the first + /// > attempt created, with result OK, even if the other parameters differ + /// > and whatever its status now: it may since have been used up, expired + /// > or been revoked, so the client checks Invite.status before sharing the + /// > link. An ID already held by an invite to another chat, or by another + /// > user's, is DENIED. + /// > + /// > Otherwise, the server caps how many ACTIVE invites a chat may hold + /// > (TOO_MANY_INVITES) and how far away expires_at may be + /// > (INVALID_EXPIRES_AT). + /// > + /// > Nothing is published. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_CreateInviteRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_CreateInviteRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_CreateInviteResponse` 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 createInvite( + 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 "GetInvite" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetInvite previews an invite from its link (see Invite): the chat it + /// > admits users to, whose chat_id the caller passes to JoinChat and + /// > derives the invite's wrap key with, and whether the invite still admits + /// > anyone. Anyone holding the link may call it, registered or not: the + /// > token is the credential, and auth is optional (when set, it must be + /// > valid). It counts no use, returns no key, and changes nothing. + /// > + /// > An authenticated caller gets the chat as they see it with GetChat: a + /// > non-member gets its record, with in_lobby set while they wait in its + /// > lobby. A user banned from the chat gets only BANNED. An + /// > unauthenticated caller gets the chat's public view (see GetChat), + /// > which for a private group is its record alone, and is never BANNED, + /// > since the server does not know who they are. Use counts are shown only + /// > to those who may invite (GetChatInvites). + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetInviteRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_GetInviteRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_GetInviteResponse` 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 getInvite( + 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 "GetChatInvites" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetChatInvites pages a private group's invites, most recently created + /// > first, whatever their status, each with its use count and its + /// > creator's copy of the link's secret (see InviteSecretEnvelope). + /// > + /// > Only a member the server permits to invite may call it, as reported by + /// > ViewerState.Permissions.can_invite; anyone else is DENIED. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetChatInvitesRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_GetChatInvitesRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_GetChatInvitesResponse` 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 getChatInvites( + 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 "RevokeInvite" method. + /// + /// > Source IDL Documentation: + /// > + /// > RevokeInvite stops an invite admitting anyone and, in the same step, + /// > deletes the server's copy of the chat key held for it (see + /// > InviteKeyEnvelope). Users it already admitted stay members and keep + /// > the chat key: an invite never changes it, and the copies stored for + /// > those who have not yet replaced theirs are kept, since those are their + /// > only key. So revoking a link that leaked keeps new users out, but + /// > takes the key from no one who already opened it, nor from anyone + /// > holding the link who can read those copies (see Invite). A link that + /// > leaked is replaced by revoking it and creating another. + /// > + /// > Gated like GetChatInvites. Revoking an invite already revoked is a + /// > no-op that returns OK; revoking one that has expired or been used up + /// > records it as revoked. Nothing is published. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_RevokeInviteRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_RevokeInviteRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_RevokeInviteResponse` 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 revokeInvite( + 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 "SetFeaturedGroups" method. /// /// > Source IDL Documentation: @@ -1254,6 +1526,11 @@ extension Flipcash_Chat_V1_Chat { /// > but the server cannot take back the chat key they already opened: /// > ciphertext of later messages that reached them some other way is /// > readable to them, since the key is not rotated. + /// > + /// > A ban applies to the account, not to an invite link (see Invite). A + /// > banned user who still holds a link that admits users can join with it + /// > from another account, so banning someone who joined with an invite + /// > calls for revoking that invite too (RevokeInvite). /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_BanUserRequest` message. @@ -1360,8 +1637,8 @@ extension Flipcash_Chat_V1_Chat { /// > 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 + /// > view_mode. Its public view is that record alone, with no messaging + /// > state. 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. /// @@ -1535,9 +1812,27 @@ extension Flipcash_Chat_V1_Chat { /// > carry none: group pointer advances are never broadcast, so a page of /// > them would be stale as soon as it was served. /// > - /// > Requires that the caller is a member of the chat. Anyone else is - /// > DENIED, including a non-member whom a group's listener rules admit to - /// > read its messages: who is in a chat is shown only to its members. + /// > Requires that the caller is a member of the chat, or holds a link to an + /// > invite to it (see Invite) and sends the invite's token in + /// > invite_token. Anyone else is DENIED, including a non-member whom a + /// > group's listener rules admit to read its messages: who is in a chat is + /// > shown only to its members and to those holding a link that would let + /// > them join it. + /// > + /// > The token is the credential, so auth is optional when it is sent (when + /// > set, auth must be valid). It is honored while its invite is ACTIVE and + /// > to chat_id; any other token is DENIED, as is a token sent for a public + /// > group or a DM. Whoever sends an honored token gets the page a member + /// > gets, registered or not, and a user the chat's creator banned + /// > included: a ban stops a user joining, not reading the roster through a + /// > link they hold, which the creator stops by revoking the invite. + /// > Reading the roster with a token counts no use of the invite, changes + /// > nothing, and is announced to no one. The invite is checked again on + /// > every page, so a walk ends with DENIED once it stops being ACTIVE. + /// > + /// > A member is served whether or not they send a token, which is not + /// > checked for them. A request with neither auth nor invite_token is + /// > DENIED. /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_GetRosterRequest` message. @@ -1724,10 +2019,41 @@ extension Flipcash_Chat_V1_Chat { /// > /// > 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. + /// > A private group (see Metadata.is_private) is joined this way only with + /// > an invite (JoinChatRequest.invite) or by its creator. Anyone else is + /// > DENIED, and enters its lobby with EnterLobby to be admitted by the + /// > creator. The creator needs neither approval nor an invite, and rejoins + /// > a private group they left with this RPC; an invite they pass is + /// > ignored. + /// > + /// > Joining with an invite (see Invite) needs no one's approval. The client + /// > previews the invite with GetInvite, which names the chat, and passes + /// > the invite's token here. The invite must be ACTIVE (INVITE_EXHAUSTED, + /// > INVITE_EXPIRED or INVITE_REVOKED otherwise), for a user who joined + /// > with it before as for anyone. + /// > + /// > The join is atomic: the caller becomes a member, the join is counted as + /// > one use of the invite (a user who left and joins again with it uses + /// > another), and the invite's copy of the chat key is stored as their key + /// > envelope, with the link's secret encrypted by the caller for themself + /// > (JoinChatRequest.InviteParameters.secret_envelope), together, or + /// > nothing happens. A caller waiting in the chat's + /// > lobby leaves it in the same step. A caller who is already a member gets + /// > OK with nothing changed: the invite is not checked, no use is counted, + /// > and their stored envelope stays as it is. Only a public group's rules + /// > or a private group's invite are checked, never both: an invite to a + /// > public group is INVITE_NOT_FOUND. + /// > + /// > Any of the joiner's devices then fetches the copy and the secret with + /// > GetKeyEnvelope (GetKeyEnvelopeResponse.invite_key_bundle), checks + /// > the copy's signature, opens the secret with the joiner's account key + /// > and the copy with the secret, wraps the chat key for the joiner and + /// > stores that envelope with SetKeyEnvelope (see KeyEnvelope). None of + /// > this needs the device the joiner joined with, or the link itself. A + /// > join with an invite is announced like an admission (see + /// > AdmitLobbyMember). A member whose copy turns out to hold the wrong key + /// > cannot fix it by joining again: they leave and are admitted from the + /// > lobby (see KeyEnvelope). /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_JoinChatRequest` message. @@ -1764,9 +2090,9 @@ extension Flipcash_Chat_V1_Chat { /// > 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. + /// > KeyEnvelope), so returning means being admitted from the lobby or + /// > joining with an invite 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. @@ -1932,8 +2258,8 @@ extension Flipcash_Chat_V1_Chat { /// > /// > 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. + /// > join a private group (see Metadata.is_private) when they hold no + /// > invite; JoinChat without one is DENIED for a private group. /// > /// > 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 @@ -1948,10 +2274,10 @@ extension Flipcash_Chat_V1_Chat { /// > /// > 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. + /// > creator denies them (DenyLobbyMember), when the creator admits them + /// > (AdmitLobbyMember), or when they join with an invite (JoinChat). 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 @@ -2073,10 +2399,11 @@ extension Flipcash_Chat_V1_Chat { /// > 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. + /// > nobody is admitted to a group before its creator holds its key. Besides + /// > joining with an invite, which stores the invite's copy of the chat key + /// > for the joiner (see JoinChat), 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 @@ -2158,7 +2485,7 @@ extension Flipcash_Chat_V1_Chat { /// > 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): + /// > It is called in three 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 @@ -2169,6 +2496,11 @@ extension Flipcash_Chat_V1_Chat { /// > 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. + /// > - By a member who joined with an invite, after first opening the + /// > invite's copy of the chat key with the link's secret (from the + /// > secret envelope stored with it, or from the link), to replace it + /// > with one wrapped by themself. Storing it deletes the copy and that + /// > secret envelope (see InviteKeyEnvelope). /// > /// > The first envelope a caller stores for themself stands. Once the /// > stored envelope is one the caller wrapped, a call with a different @@ -2218,6 +2550,14 @@ extension Flipcash_Chat_V1_Chat { /// > client calls this once per chat per install and keeps the key it /// > unwraps. /// > + /// > For a member who joined with an invite and has not yet stored their + /// > own envelope, what is stored is the invite's copy of the chat key and + /// > the link's secret the member encrypted for themself when joining. Both + /// > are returned in invite_key_bundle in place of key_envelope. Any of + /// > the member's devices opens the secret with the member's account key, + /// > then the copy with the secret, and replaces the copy with an envelope + /// > of its own with SetKeyEnvelope (see InviteKeyEnvelope). + /// > /// > 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 @@ -2252,6 +2592,207 @@ extension Flipcash_Chat_V1_Chat { ) } + /// Call the "CreateInvite" method. + /// + /// > Source IDL Documentation: + /// > + /// > CreateInvite creates an invite to a private group (see Invite): a link + /// > that lets users join it with JoinChat without the creator's approval. + /// > + /// > Only a member the server permits to invite may call it, as reported by + /// > ViewerState.Permissions.can_invite; anyone else is DENIED. So is any + /// > call for a group whose creator has not stored its key yet (see + /// > StartChatRequest.PrivateGroupChatParameters). + /// > + /// > The caller's client generates the link's secret and derives the + /// > invite's token, ID and wrap key from it (see Invite). It sends the + /// > token, the chat key encrypted under the wrap key and signed by the + /// > caller (InviteKeyEnvelope), and the secret encrypted for itself + /// > (InviteSecretEnvelope); the secret and the wrap key never leave it. The + /// > server computes the invite's ID from the token, which it never stores + /// > (see InviteToken), and checks the copy's signature against the + /// > caller's public key with that ID, refusing one that does not verify + /// > (INVALID_SIGNATURE), since no joiner could accept it. So a client that + /// > derived the ID wrongly is refused here, rather than sharing a link that + /// > admits no one. The server cannot check what either envelope holds: a + /// > copy that does not hold the chat key gives everyone who joins with the + /// > invite a key that reads nothing. + /// > + /// > An existing invite with the ID computed from the request's token is + /// > looked up before anything else is checked. A retry with the same token, + /// > by the same caller for the same chat, returns the invite the first + /// > attempt created, with result OK, even if the other parameters differ + /// > and whatever its status now: it may since have been used up, expired + /// > or been revoked, so the client checks Invite.status before sharing the + /// > link. An ID already held by an invite to another chat, or by another + /// > user's, is DENIED. + /// > + /// > Otherwise, the server caps how many ACTIVE invites a chat may hold + /// > (TOO_MANY_INVITES) and how far away expires_at may be + /// > (INVALID_EXPIRES_AT). + /// > + /// > Nothing is published. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_CreateInviteRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_CreateInviteRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_CreateInviteResponse` 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 createInvite( + 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.CreateInvite.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetInvite" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetInvite previews an invite from its link (see Invite): the chat it + /// > admits users to, whose chat_id the caller passes to JoinChat and + /// > derives the invite's wrap key with, and whether the invite still admits + /// > anyone. Anyone holding the link may call it, registered or not: the + /// > token is the credential, and auth is optional (when set, it must be + /// > valid). It counts no use, returns no key, and changes nothing. + /// > + /// > An authenticated caller gets the chat as they see it with GetChat: a + /// > non-member gets its record, with in_lobby set while they wait in its + /// > lobby. A user banned from the chat gets only BANNED. An + /// > unauthenticated caller gets the chat's public view (see GetChat), + /// > which for a private group is its record alone, and is never BANNED, + /// > since the server does not know who they are. Use counts are shown only + /// > to those who may invite (GetChatInvites). + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetInviteRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_GetInviteRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_GetInviteResponse` 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 getInvite( + 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.GetInvite.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetChatInvites" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetChatInvites pages a private group's invites, most recently created + /// > first, whatever their status, each with its use count and its + /// > creator's copy of the link's secret (see InviteSecretEnvelope). + /// > + /// > Only a member the server permits to invite may call it, as reported by + /// > ViewerState.Permissions.can_invite; anyone else is DENIED. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetChatInvitesRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_GetChatInvitesRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_GetChatInvitesResponse` 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 getChatInvites( + 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.GetChatInvites.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "RevokeInvite" method. + /// + /// > Source IDL Documentation: + /// > + /// > RevokeInvite stops an invite admitting anyone and, in the same step, + /// > deletes the server's copy of the chat key held for it (see + /// > InviteKeyEnvelope). Users it already admitted stay members and keep + /// > the chat key: an invite never changes it, and the copies stored for + /// > those who have not yet replaced theirs are kept, since those are their + /// > only key. So revoking a link that leaked keeps new users out, but + /// > takes the key from no one who already opened it, nor from anyone + /// > holding the link who can read those copies (see Invite). A link that + /// > leaked is replaced by revoking it and creating another. + /// > + /// > Gated like GetChatInvites. Revoking an invite already revoked is a + /// > no-op that returns OK; revoking one that has expired or been used up + /// > records it as revoked. Nothing is published. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_RevokeInviteRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_RevokeInviteRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_RevokeInviteResponse` 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 revokeInvite( + 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.RevokeInvite.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + /// Call the "SetFeaturedGroups" method. /// /// > Source IDL Documentation: @@ -2473,6 +3014,11 @@ extension Flipcash_Chat_V1_Chat { /// > but the server cannot take back the chat key they already opened: /// > ciphertext of later messages that reached them some other way is /// > readable to them, since the key is not rotated. + /// > + /// > A ban applies to the account, not to an invite link (see Invite). A + /// > banned user who still holds a link that admits users can join with it + /// > from another account, so banning someone who joined with an invite + /// > calls for revoking that invite too (RevokeInvite). /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_BanUserRequest` message. @@ -2600,8 +3146,8 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > 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 + /// > view_mode. Its public view is that record alone, with no messaging + /// > state. 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. /// @@ -2760,9 +3306,27 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > carry none: group pointer advances are never broadcast, so a page of /// > them would be stale as soon as it was served. /// > - /// > Requires that the caller is a member of the chat. Anyone else is - /// > DENIED, including a non-member whom a group's listener rules admit to - /// > read its messages: who is in a chat is shown only to its members. + /// > Requires that the caller is a member of the chat, or holds a link to an + /// > invite to it (see Invite) and sends the invite's token in + /// > invite_token. Anyone else is DENIED, including a non-member whom a + /// > group's listener rules admit to read its messages: who is in a chat is + /// > shown only to its members and to those holding a link that would let + /// > them join it. + /// > + /// > The token is the credential, so auth is optional when it is sent (when + /// > set, auth must be valid). It is honored while its invite is ACTIVE and + /// > to chat_id; any other token is DENIED, as is a token sent for a public + /// > group or a DM. Whoever sends an honored token gets the page a member + /// > gets, registered or not, and a user the chat's creator banned + /// > included: a ban stops a user joining, not reading the roster through a + /// > link they hold, which the creator stops by revoking the invite. + /// > Reading the roster with a token counts no use of the invite, changes + /// > nothing, and is announced to no one. The invite is checked again on + /// > every page, so a walk ends with DENIED once it stops being ACTIVE. + /// > + /// > A member is served whether or not they send a token, which is not + /// > checked for them. A request with neither auth nor invite_token is + /// > DENIED. /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_GetRosterRequest` message. @@ -2929,10 +3493,41 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > /// > 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. + /// > A private group (see Metadata.is_private) is joined this way only with + /// > an invite (JoinChatRequest.invite) or by its creator. Anyone else is + /// > DENIED, and enters its lobby with EnterLobby to be admitted by the + /// > creator. The creator needs neither approval nor an invite, and rejoins + /// > a private group they left with this RPC; an invite they pass is + /// > ignored. + /// > + /// > Joining with an invite (see Invite) needs no one's approval. The client + /// > previews the invite with GetInvite, which names the chat, and passes + /// > the invite's token here. The invite must be ACTIVE (INVITE_EXHAUSTED, + /// > INVITE_EXPIRED or INVITE_REVOKED otherwise), for a user who joined + /// > with it before as for anyone. + /// > + /// > The join is atomic: the caller becomes a member, the join is counted as + /// > one use of the invite (a user who left and joins again with it uses + /// > another), and the invite's copy of the chat key is stored as their key + /// > envelope, with the link's secret encrypted by the caller for themself + /// > (JoinChatRequest.InviteParameters.secret_envelope), together, or + /// > nothing happens. A caller waiting in the chat's + /// > lobby leaves it in the same step. A caller who is already a member gets + /// > OK with nothing changed: the invite is not checked, no use is counted, + /// > and their stored envelope stays as it is. Only a public group's rules + /// > or a private group's invite are checked, never both: an invite to a + /// > public group is INVITE_NOT_FOUND. + /// > + /// > Any of the joiner's devices then fetches the copy and the secret with + /// > GetKeyEnvelope (GetKeyEnvelopeResponse.invite_key_bundle), checks + /// > the copy's signature, opens the secret with the joiner's account key + /// > and the copy with the secret, wraps the chat key for the joiner and + /// > stores that envelope with SetKeyEnvelope (see KeyEnvelope). None of + /// > this needs the device the joiner joined with, or the link itself. A + /// > join with an invite is announced like an admission (see + /// > AdmitLobbyMember). A member whose copy turns out to hold the wrong key + /// > cannot fix it by joining again: they leave and are admitted from the + /// > lobby (see KeyEnvelope). /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_JoinChatRequest` message. @@ -2964,9 +3559,9 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > 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. + /// > KeyEnvelope), so returning means being admitted from the lobby or + /// > joining with an invite 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. @@ -3112,8 +3707,8 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > /// > 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. + /// > join a private group (see Metadata.is_private) when they hold no + /// > invite; JoinChat without one is DENIED for a private group. /// > /// > 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 @@ -3128,10 +3723,10 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > /// > 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. + /// > creator denies them (DenyLobbyMember), when the creator admits them + /// > (AdmitLobbyMember), or when they join with an invite (JoinChat). 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 @@ -3238,10 +3833,11 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > 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. + /// > nobody is admitted to a group before its creator holds its key. Besides + /// > joining with an invite, which stores the invite's copy of the chat key + /// > for the joiner (see JoinChat), 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 @@ -3313,7 +3909,7 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > 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): + /// > It is called in three 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 @@ -3324,6 +3920,11 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > 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. + /// > - By a member who joined with an invite, after first opening the + /// > invite's copy of the chat key with the link's secret (from the + /// > secret envelope stored with it, or from the link), to replace it + /// > with one wrapped by themself. Storing it deletes the copy and that + /// > secret envelope (see InviteKeyEnvelope). /// > /// > The first envelope a caller stores for themself stands. Once the /// > stored envelope is one the caller wrapped, a call with a different @@ -3368,6 +3969,14 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > client calls this once per chat per install and keeps the key it /// > unwraps. /// > + /// > For a member who joined with an invite and has not yet stored their + /// > own envelope, what is stored is the invite's copy of the chat key and + /// > the link's secret the member encrypted for themself when joining. Both + /// > are returned in invite_key_bundle in place of key_envelope. Any of + /// > the member's devices opens the secret with the member's account key, + /// > then the copy with the secret, and replaces the copy with an envelope + /// > of its own with SetKeyEnvelope (see InviteKeyEnvelope). + /// > /// > 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 @@ -3397,6 +4006,187 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { ) } + /// Call the "CreateInvite" method. + /// + /// > Source IDL Documentation: + /// > + /// > CreateInvite creates an invite to a private group (see Invite): a link + /// > that lets users join it with JoinChat without the creator's approval. + /// > + /// > Only a member the server permits to invite may call it, as reported by + /// > ViewerState.Permissions.can_invite; anyone else is DENIED. So is any + /// > call for a group whose creator has not stored its key yet (see + /// > StartChatRequest.PrivateGroupChatParameters). + /// > + /// > The caller's client generates the link's secret and derives the + /// > invite's token, ID and wrap key from it (see Invite). It sends the + /// > token, the chat key encrypted under the wrap key and signed by the + /// > caller (InviteKeyEnvelope), and the secret encrypted for itself + /// > (InviteSecretEnvelope); the secret and the wrap key never leave it. The + /// > server computes the invite's ID from the token, which it never stores + /// > (see InviteToken), and checks the copy's signature against the + /// > caller's public key with that ID, refusing one that does not verify + /// > (INVALID_SIGNATURE), since no joiner could accept it. So a client that + /// > derived the ID wrongly is refused here, rather than sharing a link that + /// > admits no one. The server cannot check what either envelope holds: a + /// > copy that does not hold the chat key gives everyone who joins with the + /// > invite a key that reads nothing. + /// > + /// > An existing invite with the ID computed from the request's token is + /// > looked up before anything else is checked. A retry with the same token, + /// > by the same caller for the same chat, returns the invite the first + /// > attempt created, with result OK, even if the other parameters differ + /// > and whatever its status now: it may since have been used up, expired + /// > or been revoked, so the client checks Invite.status before sharing the + /// > link. An ID already held by an invite to another chat, or by another + /// > user's, is DENIED. + /// > + /// > Otherwise, the server caps how many ACTIVE invites a chat may hold + /// > (TOO_MANY_INVITES) and how far away expires_at may be + /// > (INVALID_EXPIRES_AT). + /// > + /// > Nothing is published. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_CreateInviteRequest` 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 createInvite( + 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.createInvite( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetInvite" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetInvite previews an invite from its link (see Invite): the chat it + /// > admits users to, whose chat_id the caller passes to JoinChat and + /// > derives the invite's wrap key with, and whether the invite still admits + /// > anyone. Anyone holding the link may call it, registered or not: the + /// > token is the credential, and auth is optional (when set, it must be + /// > valid). It counts no use, returns no key, and changes nothing. + /// > + /// > An authenticated caller gets the chat as they see it with GetChat: a + /// > non-member gets its record, with in_lobby set while they wait in its + /// > lobby. A user banned from the chat gets only BANNED. An + /// > unauthenticated caller gets the chat's public view (see GetChat), + /// > which for a private group is its record alone, and is never BANNED, + /// > since the server does not know who they are. Use counts are shown only + /// > to those who may invite (GetChatInvites). + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetInviteRequest` 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 getInvite( + 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.getInvite( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetChatInvites" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetChatInvites pages a private group's invites, most recently created + /// > first, whatever their status, each with its use count and its + /// > creator's copy of the link's secret (see InviteSecretEnvelope). + /// > + /// > Only a member the server permits to invite may call it, as reported by + /// > ViewerState.Permissions.can_invite; anyone else is DENIED. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetChatInvitesRequest` 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 getChatInvites( + 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.getChatInvites( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "RevokeInvite" method. + /// + /// > Source IDL Documentation: + /// > + /// > RevokeInvite stops an invite admitting anyone and, in the same step, + /// > deletes the server's copy of the chat key held for it (see + /// > InviteKeyEnvelope). Users it already admitted stay members and keep + /// > the chat key: an invite never changes it, and the copies stored for + /// > those who have not yet replaced theirs are kept, since those are their + /// > only key. So revoking a link that leaked keeps new users out, but + /// > takes the key from no one who already opened it, nor from anyone + /// > holding the link who can read those copies (see Invite). A link that + /// > leaked is replaced by revoking it and creating another. + /// > + /// > Gated like GetChatInvites. Revoking an invite already revoked is a + /// > no-op that returns OK; revoking one that has expired or been used up + /// > records it as revoked. Nothing is published. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_RevokeInviteRequest` 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 revokeInvite( + 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.revokeInvite( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + /// Call the "SetFeaturedGroups" method. /// /// > Source IDL Documentation: @@ -3598,6 +4388,11 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > but the server cannot take back the chat key they already opened: /// > ciphertext of later messages that reached them some other way is /// > readable to them, since the key is not rotated. + /// > + /// > A ban applies to the account, not to an invite link (see Invite). A + /// > banned user who still holds a link that admits users can join with it + /// > from another account, so banning someone who joined with an invite + /// > calls for revoking that invite too (RevokeInvite). /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_BanUserRequest` message. @@ -3709,8 +4504,8 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > 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 + /// > view_mode. Its public view is that record alone, with no messaging + /// > state. 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. /// @@ -3881,9 +4676,27 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > carry none: group pointer advances are never broadcast, so a page of /// > them would be stale as soon as it was served. /// > - /// > Requires that the caller is a member of the chat. Anyone else is - /// > DENIED, including a non-member whom a group's listener rules admit to - /// > read its messages: who is in a chat is shown only to its members. + /// > Requires that the caller is a member of the chat, or holds a link to an + /// > invite to it (see Invite) and sends the invite's token in + /// > invite_token. Anyone else is DENIED, including a non-member whom a + /// > group's listener rules admit to read its messages: who is in a chat is + /// > shown only to its members and to those holding a link that would let + /// > them join it. + /// > + /// > The token is the credential, so auth is optional when it is sent (when + /// > set, auth must be valid). It is honored while its invite is ACTIVE and + /// > to chat_id; any other token is DENIED, as is a token sent for a public + /// > group or a DM. Whoever sends an honored token gets the page a member + /// > gets, registered or not, and a user the chat's creator banned + /// > included: a ban stops a user joining, not reading the roster through a + /// > link they hold, which the creator stops by revoking the invite. + /// > Reading the roster with a token counts no use of the invite, changes + /// > nothing, and is announced to no one. The invite is checked again on + /// > every page, so a walk ends with DENIED once it stops being ACTIVE. + /// > + /// > A member is served whether or not they send a token, which is not + /// > checked for them. A request with neither auth nor invite_token is + /// > DENIED. /// /// - Parameters: /// - message: request message to send. @@ -4066,10 +4879,41 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > /// > 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. + /// > A private group (see Metadata.is_private) is joined this way only with + /// > an invite (JoinChatRequest.invite) or by its creator. Anyone else is + /// > DENIED, and enters its lobby with EnterLobby to be admitted by the + /// > creator. The creator needs neither approval nor an invite, and rejoins + /// > a private group they left with this RPC; an invite they pass is + /// > ignored. + /// > + /// > Joining with an invite (see Invite) needs no one's approval. The client + /// > previews the invite with GetInvite, which names the chat, and passes + /// > the invite's token here. The invite must be ACTIVE (INVITE_EXHAUSTED, + /// > INVITE_EXPIRED or INVITE_REVOKED otherwise), for a user who joined + /// > with it before as for anyone. + /// > + /// > The join is atomic: the caller becomes a member, the join is counted as + /// > one use of the invite (a user who left and joins again with it uses + /// > another), and the invite's copy of the chat key is stored as their key + /// > envelope, with the link's secret encrypted by the caller for themself + /// > (JoinChatRequest.InviteParameters.secret_envelope), together, or + /// > nothing happens. A caller waiting in the chat's + /// > lobby leaves it in the same step. A caller who is already a member gets + /// > OK with nothing changed: the invite is not checked, no use is counted, + /// > and their stored envelope stays as it is. Only a public group's rules + /// > or a private group's invite are checked, never both: an invite to a + /// > public group is INVITE_NOT_FOUND. + /// > + /// > Any of the joiner's devices then fetches the copy and the secret with + /// > GetKeyEnvelope (GetKeyEnvelopeResponse.invite_key_bundle), checks + /// > the copy's signature, opens the secret with the joiner's account key + /// > and the copy with the secret, wraps the chat key for the joiner and + /// > stores that envelope with SetKeyEnvelope (see KeyEnvelope). None of + /// > this needs the device the joiner joined with, or the link itself. A + /// > join with an invite is announced like an admission (see + /// > AdmitLobbyMember). A member whose copy turns out to hold the wrong key + /// > cannot fix it by joining again: they leave and are admitted from the + /// > lobby (see KeyEnvelope). /// /// - Parameters: /// - message: request message to send. @@ -4105,9 +4949,9 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > 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. + /// > KeyEnvelope), so returning means being admitted from the lobby or + /// > joining with an invite again. The creator's envelope is kept, since no + /// > one else could give them another. /// /// - Parameters: /// - message: request message to send. @@ -4269,8 +5113,8 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > /// > 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. + /// > join a private group (see Metadata.is_private) when they hold no + /// > invite; JoinChat without one is DENIED for a private group. /// > /// > 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 @@ -4285,10 +5129,10 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > /// > 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. + /// > creator denies them (DenyLobbyMember), when the creator admits them + /// > (AdmitLobbyMember), or when they join with an invite (JoinChat). 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 @@ -4407,10 +5251,11 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > 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. + /// > nobody is admitted to a group before its creator holds its key. Besides + /// > joining with an invite, which stores the invite's copy of the chat key + /// > for the joiner (see JoinChat), 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 @@ -4490,7 +5335,7 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > 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): + /// > It is called in three 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 @@ -4501,6 +5346,11 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > 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. + /// > - By a member who joined with an invite, after first opening the + /// > invite's copy of the chat key with the link's secret (from the + /// > secret envelope stored with it, or from the link), to replace it + /// > with one wrapped by themself. Storing it deletes the copy and that + /// > secret envelope (see InviteKeyEnvelope). /// > /// > The first envelope a caller stores for themself stands. Once the /// > stored envelope is one the caller wrapped, a call with a different @@ -4549,6 +5399,14 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > client calls this once per chat per install and keeps the key it /// > unwraps. /// > + /// > For a member who joined with an invite and has not yet stored their + /// > own envelope, what is stored is the invite's copy of the chat key and + /// > the link's secret the member encrypted for themself when joining. Both + /// > are returned in invite_key_bundle in place of key_envelope. Any of + /// > the member's devices opens the secret with the member's account key, + /// > then the copy with the secret, and replaces the copy with an envelope + /// > of its own with SetKeyEnvelope (see InviteKeyEnvelope). + /// > /// > 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 @@ -4582,6 +5440,203 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { ) } + /// Call the "CreateInvite" method. + /// + /// > Source IDL Documentation: + /// > + /// > CreateInvite creates an invite to a private group (see Invite): a link + /// > that lets users join it with JoinChat without the creator's approval. + /// > + /// > Only a member the server permits to invite may call it, as reported by + /// > ViewerState.Permissions.can_invite; anyone else is DENIED. So is any + /// > call for a group whose creator has not stored its key yet (see + /// > StartChatRequest.PrivateGroupChatParameters). + /// > + /// > The caller's client generates the link's secret and derives the + /// > invite's token, ID and wrap key from it (see Invite). It sends the + /// > token, the chat key encrypted under the wrap key and signed by the + /// > caller (InviteKeyEnvelope), and the secret encrypted for itself + /// > (InviteSecretEnvelope); the secret and the wrap key never leave it. The + /// > server computes the invite's ID from the token, which it never stores + /// > (see InviteToken), and checks the copy's signature against the + /// > caller's public key with that ID, refusing one that does not verify + /// > (INVALID_SIGNATURE), since no joiner could accept it. So a client that + /// > derived the ID wrongly is refused here, rather than sharing a link that + /// > admits no one. The server cannot check what either envelope holds: a + /// > copy that does not hold the chat key gives everyone who joins with the + /// > invite a key that reads nothing. + /// > + /// > An existing invite with the ID computed from the request's token is + /// > looked up before anything else is checked. A retry with the same token, + /// > by the same caller for the same chat, returns the invite the first + /// > attempt created, with result OK, even if the other parameters differ + /// > and whatever its status now: it may since have been used up, expired + /// > or been revoked, so the client checks Invite.status before sharing the + /// > link. An ID already held by an invite to another chat, or by another + /// > user's, is DENIED. + /// > + /// > Otherwise, the server caps how many ACTIVE invites a chat may hold + /// > (TOO_MANY_INVITES) and how far away expires_at may be + /// > (INVALID_EXPIRES_AT). + /// > + /// > Nothing is published. + /// + /// - 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 createInvite( + _ message: Flipcash_Chat_V1_CreateInviteRequest, + 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.createInvite( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetInvite" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetInvite previews an invite from its link (see Invite): the chat it + /// > admits users to, whose chat_id the caller passes to JoinChat and + /// > derives the invite's wrap key with, and whether the invite still admits + /// > anyone. Anyone holding the link may call it, registered or not: the + /// > token is the credential, and auth is optional (when set, it must be + /// > valid). It counts no use, returns no key, and changes nothing. + /// > + /// > An authenticated caller gets the chat as they see it with GetChat: a + /// > non-member gets its record, with in_lobby set while they wait in its + /// > lobby. A user banned from the chat gets only BANNED. An + /// > unauthenticated caller gets the chat's public view (see GetChat), + /// > which for a private group is its record alone, and is never BANNED, + /// > since the server does not know who they are. Use counts are shown only + /// > to those who may invite (GetChatInvites). + /// + /// - 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 getInvite( + _ message: Flipcash_Chat_V1_GetInviteRequest, + 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.getInvite( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetChatInvites" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetChatInvites pages a private group's invites, most recently created + /// > first, whatever their status, each with its use count and its + /// > creator's copy of the link's secret (see InviteSecretEnvelope). + /// > + /// > Only a member the server permits to invite may call it, as reported by + /// > ViewerState.Permissions.can_invite; anyone else is DENIED. + /// + /// - 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 getChatInvites( + _ message: Flipcash_Chat_V1_GetChatInvitesRequest, + 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.getChatInvites( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "RevokeInvite" method. + /// + /// > Source IDL Documentation: + /// > + /// > RevokeInvite stops an invite admitting anyone and, in the same step, + /// > deletes the server's copy of the chat key held for it (see + /// > InviteKeyEnvelope). Users it already admitted stay members and keep + /// > the chat key: an invite never changes it, and the copies stored for + /// > those who have not yet replaced theirs are kept, since those are their + /// > only key. So revoking a link that leaked keeps new users out, but + /// > takes the key from no one who already opened it, nor from anyone + /// > holding the link who can read those copies (see Invite). A link that + /// > leaked is replaced by revoking it and creating another. + /// > + /// > Gated like GetChatInvites. Revoking an invite already revoked is a + /// > no-op that returns OK; revoking one that has expired or been used up + /// > records it as revoked. Nothing is published. + /// + /// - 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 revokeInvite( + _ message: Flipcash_Chat_V1_RevokeInviteRequest, + 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.revokeInvite( + request: request, + options: options, + onResponse: handleResponse + ) + } + /// Call the "SetFeaturedGroups" method. /// /// > Source IDL Documentation: @@ -4799,6 +5854,11 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > but the server cannot take back the chat key they already opened: /// > ciphertext of later messages that reached them some other way is /// > readable to them, since the key is not rotated. + /// > + /// > A ban applies to the account, not to an invite link (see Invite). A + /// > banned user who still holds a link that admits users can join with it + /// > from another account, so banning someone who joined with an invite + /// > calls for revoking that invite too (RevokeInvite). /// /// - Parameters: /// - message: request message to send. diff --git a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift index f39b381..bee2f06 100644 --- a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift +++ b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift @@ -395,6 +395,20 @@ public struct Flipcash_Chat_V1_GetRosterRequest: Sendable { /// Clears the value of `queryOptions`. Subsequent reads from it will return its default value. public mutating func clearQueryOptions() {self._queryOptions = nil} + /// Set to read the roster with an invite link (see GetRoster) rather than + /// as a member: the token derived from the link's secret (see Invite). It + /// must be a token for an invite to chat_id. Send it on every page. + public var inviteToken: Flipcash_Chat_V1_InviteToken { + get {return _inviteToken ?? Flipcash_Chat_V1_InviteToken()} + set {_inviteToken = newValue} + } + /// Returns true if `inviteToken` has been explicitly set. + public var hasInviteToken: Bool {return self._inviteToken != nil} + /// Clears the value of `inviteToken`. Subsequent reads from it will return its default value. + public mutating func clearInviteToken() {self._inviteToken = nil} + + /// Optional when invite_token is set, and required otherwise (see + /// GetRoster). When set, it must be valid. public var auth: Flipcash_Common_V1_Auth { get {return _auth ?? Flipcash_Common_V1_Auth()} set {_auth = newValue} @@ -410,6 +424,7 @@ public struct Flipcash_Chat_V1_GetRosterRequest: Sendable { fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil fileprivate var _queryOptions: Flipcash_Common_V1_QueryOptions? = nil + fileprivate var _inviteToken: Flipcash_Chat_V1_InviteToken? = nil fileprivate var _auth: Flipcash_Common_V1_Auth? = nil } @@ -810,9 +825,10 @@ public struct Flipcash_Chat_V1_StartChatRequest: Sendable { } /// 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. + /// one whose members are admitted by its creator from its lobby or join + /// with an invite, 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 @@ -825,6 +841,7 @@ public struct Flipcash_Chat_V1_StartChatRequest: Sendable { /// happen in it, and the server refuses each of these as DENIED: /// - EnterLobby, by anyone /// - AdmitLobbyMember + /// - CreateInvite /// - Messaging.SendMessage and Messaging.EditMessage /// - BlobStorage.InitiateExternalUpload of a blob encrypted for the chat /// The creator's client must keep attempting SetKeyEnvelope until it @@ -981,6 +998,16 @@ public struct Flipcash_Chat_V1_JoinChatRequest: Sendable { /// Clears the value of `chatID`. Subsequent reads from it will return its default value. public mutating func clearChatID() {self._chatID = nil} + /// Set to join a private group with an invite (see JoinChat). + public var invite: Flipcash_Chat_V1_JoinChatRequest.InviteParameters { + get {return _invite ?? Flipcash_Chat_V1_JoinChatRequest.InviteParameters()} + set {_invite = newValue} + } + /// Returns true if `invite` has been explicitly set. + public var hasInvite: Bool {return self._invite != nil} + /// Clears the value of `invite`. Subsequent reads from it will return its default value. + public mutating func clearInvite() {self._invite = nil} + public var auth: Flipcash_Common_V1_Auth { get {return _auth ?? Flipcash_Common_V1_Auth()} set {_auth = newValue} @@ -992,9 +1019,51 @@ public struct Flipcash_Chat_V1_JoinChatRequest: Sendable { public var unknownFields = SwiftProtobuf.UnknownStorage() + public struct InviteParameters: 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 token derived from the invite link's secret (see Invite). It + /// must be a token for an invite to chat_id. + public var inviteToken: Flipcash_Chat_V1_InviteToken { + get {return _inviteToken ?? Flipcash_Chat_V1_InviteToken()} + set {_inviteToken = newValue} + } + /// Returns true if `inviteToken` has been explicitly set. + public var hasInviteToken: Bool {return self._inviteToken != nil} + /// Clears the value of `inviteToken`. Subsequent reads from it will return its default value. + public mutating func clearInviteToken() {self._inviteToken = nil} + + /// The link's secret encrypted by the caller for themself (see + /// InviteSecretEnvelope). A join that stores the invite's copy of the + /// chat key for the caller stores this with it, so that any of the + /// caller's devices can open the copy until they replace it with an + /// envelope of their own. A caller who is already a member changes + /// nothing, so it is not stored. The server cannot open it or check + /// what it holds: one that does not hold the link's secret leaves the + /// copy openable only on a device that still has the link. + public var secretEnvelope: Flipcash_Chat_V1_InviteSecretEnvelope { + get {return _secretEnvelope ?? Flipcash_Chat_V1_InviteSecretEnvelope()} + set {_secretEnvelope = newValue} + } + /// Returns true if `secretEnvelope` has been explicitly set. + public var hasSecretEnvelope: Bool {return self._secretEnvelope != nil} + /// Clears the value of `secretEnvelope`. Subsequent reads from it will return its default value. + public mutating func clearSecretEnvelope() {self._secretEnvelope = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _inviteToken: Flipcash_Chat_V1_InviteToken? = nil + fileprivate var _secretEnvelope: Flipcash_Chat_V1_InviteSecretEnvelope? = nil + } + public init() {} fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil + fileprivate var _invite: Flipcash_Chat_V1_JoinChatRequest.InviteParameters? = nil fileprivate var _auth: Flipcash_Common_V1_Auth? = nil } @@ -1027,6 +1096,18 @@ public struct Flipcash_Chat_V1_JoinChatResponse: Sendable { /// The chat's creator banned the caller (see BanUser) case banned // = 4 + + /// No invite to this chat matches the token, or the chat is not a private group + case inviteNotFound // = 5 + + /// The invite has admitted max_uses joins + case inviteExhausted // = 6 + + /// The invite's expires_at has passed + case inviteExpired // = 7 + + /// The invite was revoked (see RevokeInvite) + case inviteRevoked // = 8 case UNRECOGNIZED(Int) public init() { @@ -1040,6 +1121,10 @@ public struct Flipcash_Chat_V1_JoinChatResponse: Sendable { case 2: self = .notFound case 3: self = .rulesNotSatisfied case 4: self = .banned + case 5: self = .inviteNotFound + case 6: self = .inviteExhausted + case 7: self = .inviteExpired + case 8: self = .inviteRevoked default: self = .UNRECOGNIZED(rawValue) } } @@ -1051,6 +1136,10 @@ public struct Flipcash_Chat_V1_JoinChatResponse: Sendable { case .notFound: return 2 case .rulesNotSatisfied: return 3 case .banned: return 4 + case .inviteNotFound: return 5 + case .inviteExhausted: return 6 + case .inviteExpired: return 7 + case .inviteRevoked: return 8 case .UNRECOGNIZED(let i): return i } } @@ -1062,6 +1151,10 @@ public struct Flipcash_Chat_V1_JoinChatResponse: Sendable { .notFound, .rulesNotSatisfied, .banned, + .inviteNotFound, + .inviteExhausted, + .inviteExpired, + .inviteRevoked, ] } @@ -2257,21 +2350,39 @@ public struct Flipcash_Chat_V1_GetKeyEnvelopeResponse: Sendable { public var result: Flipcash_Chat_V1_GetKeyEnvelopeResponse.Result = .ok - /// The caller's key envelope. Set only when result == OK. + /// The envelope stored for the caller. Set only when result == OK. + public var envelope: Flipcash_Chat_V1_GetKeyEnvelopeResponse.OneOf_Envelope? = nil + + /// The caller's key envelope, wrapped by themself or by the chat's + /// creator. public var keyEnvelope: Flipcash_Chat_V1_KeyEnvelope { - get {return _keyEnvelope ?? Flipcash_Chat_V1_KeyEnvelope()} - set {_keyEnvelope = newValue} + get { + if case .keyEnvelope(let v)? = envelope {return v} + return Flipcash_Chat_V1_KeyEnvelope() + } + set {envelope = .keyEnvelope(newValue)} + } + + /// An invite's copy of the chat key, stored for the caller when they + /// joined with an invite (see JoinChat) and kept until they replace it + /// with an envelope of their own (SetKeyEnvelope). It holds everything + /// the client needs to open the copy (see InviteKeyEnvelope). + public var inviteKeyBundle: Flipcash_Chat_V1_GetKeyEnvelopeResponse.InviteKeyBundle { + get { + if case .inviteKeyBundle(let v)? = envelope {return v} + return Flipcash_Chat_V1_GetKeyEnvelopeResponse.InviteKeyBundle() + } + set {envelope = .inviteKeyBundle(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. + /// the caller, or the chat's creator. For an invite's copy + /// (invite_key_bundle) it is the user who created the invite and signed + /// the copy (Invite.created_by), which must be the chat's creator (see + /// InviteKeyEnvelope). It tells the client whose public key to open or + /// verify the envelope against, and is only a hint: an envelope that does + /// not open or verify 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} @@ -2281,8 +2392,36 @@ public struct Flipcash_Chat_V1_GetKeyEnvelopeResponse: Sendable { /// Clears the value of `wrappedBy`. Subsequent reads from it will return its default value. public mutating func clearWrappedBy() {self._wrappedBy = nil} + /// The public key of the user named by wrapped_by, as the server reports + /// it: the key the client opens the envelope against, or verifies an + /// invite's copy against, when it has learned none for that user outside + /// this service (see KeyEnvelope). For a self-wrapped envelope it is the + /// caller's own key, which the client already holds. Set only when result + /// == OK. + public var wrappedByPublicKey: Flipcash_Common_V1_PublicKey { + get {return _wrappedByPublicKey ?? Flipcash_Common_V1_PublicKey()} + set {_wrappedByPublicKey = newValue} + } + /// Returns true if `wrappedByPublicKey` has been explicitly set. + public var hasWrappedByPublicKey: Bool {return self._wrappedByPublicKey != nil} + /// Clears the value of `wrappedByPublicKey`. Subsequent reads from it will return its default value. + public mutating func clearWrappedByPublicKey() {self._wrappedByPublicKey = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() + /// The envelope stored for the caller. Set only when result == OK. + public enum OneOf_Envelope: Equatable, Sendable { + /// The caller's key envelope, wrapped by themself or by the chat's + /// creator. + case keyEnvelope(Flipcash_Chat_V1_KeyEnvelope) + /// An invite's copy of the chat key, stored for the caller when they + /// joined with an invite (see JoinChat) and kept until they replace it + /// with an envelope of their own (SetKeyEnvelope). It holds everything + /// the client needs to open the copy (see InviteKeyEnvelope). + case inviteKeyBundle(Flipcash_Chat_V1_GetKeyEnvelopeResponse.InviteKeyBundle) + + } + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int case ok // = 0 @@ -2329,21 +2468,121 @@ public struct Flipcash_Chat_V1_GetKeyEnvelopeResponse: Sendable { } + public struct InviteKeyBundle: 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 invite whose copy this is. + public var inviteID: Flipcash_Chat_V1_InviteId { + get {return _inviteID ?? Flipcash_Chat_V1_InviteId()} + set {_inviteID = newValue} + } + /// Returns true if `inviteID` has been explicitly set. + public var hasInviteID: Bool {return self._inviteID != nil} + /// Clears the value of `inviteID`. Subsequent reads from it will return its default value. + public mutating func clearInviteID() {self._inviteID = nil} + + /// The invite's copy of the chat key. The client checks its + /// signature, opens it with the link's secret from secret_envelope, + /// and wraps the chat key for the caller. + public var keyEnvelope: Flipcash_Chat_V1_InviteKeyEnvelope { + get {return _keyEnvelope ?? Flipcash_Chat_V1_InviteKeyEnvelope()} + 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 link's secret, encrypted by the caller for themself when they + /// joined (JoinChatRequest.InviteParameters.secret_envelope). It opens + /// with the caller's account key on any of their devices. + public var secretEnvelope: Flipcash_Chat_V1_InviteSecretEnvelope { + get {return _secretEnvelope ?? Flipcash_Chat_V1_InviteSecretEnvelope()} + set {_secretEnvelope = newValue} + } + /// Returns true if `secretEnvelope` has been explicitly set. + public var hasSecretEnvelope: Bool {return self._secretEnvelope != nil} + /// Clears the value of `secretEnvelope`. Subsequent reads from it will return its default value. + public mutating func clearSecretEnvelope() {self._secretEnvelope = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _inviteID: Flipcash_Chat_V1_InviteId? = nil + fileprivate var _keyEnvelope: Flipcash_Chat_V1_InviteKeyEnvelope? = nil + fileprivate var _secretEnvelope: Flipcash_Chat_V1_InviteSecretEnvelope? = nil + } + public init() {} - fileprivate var _keyEnvelope: Flipcash_Chat_V1_KeyEnvelope? = nil fileprivate var _wrappedBy: Flipcash_Common_V1_UserId? = nil + fileprivate var _wrappedByPublicKey: Flipcash_Common_V1_PublicKey? = nil } -public struct Flipcash_Chat_V1_SetFeaturedGroupsRequest: Sendable { +public struct Flipcash_Chat_V1_CreateInviteRequest: 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 groups to feature, in the order to show them. Every ID must name a - /// public group chat, and none may repeat. Empty clears the featured - /// groups. - public var chatIds: [Flipcash_Common_V1_ChatId] = [] + 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 token derived from the invite link's secret (see Invite), from + /// which the server computes the invite's ID. A retry with the same token + /// returns the invite the first attempt created. + public var inviteToken: Flipcash_Chat_V1_InviteToken { + get {return _inviteToken ?? Flipcash_Chat_V1_InviteToken()} + set {_inviteToken = newValue} + } + /// Returns true if `inviteToken` has been explicitly set. + public var hasInviteToken: Bool {return self._inviteToken != nil} + /// Clears the value of `inviteToken`. Subsequent reads from it will return its default value. + public mutating func clearInviteToken() {self._inviteToken = nil} + + /// The chat key encrypted under the invite's wrap key and signed by the + /// caller. + public var keyEnvelope: Flipcash_Chat_V1_InviteKeyEnvelope { + get {return _keyEnvelope ?? Flipcash_Chat_V1_InviteKeyEnvelope()} + 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 link's secret encrypted by the caller for themself. + public var secretEnvelope: Flipcash_Chat_V1_InviteSecretEnvelope { + get {return _secretEnvelope ?? Flipcash_Chat_V1_InviteSecretEnvelope()} + set {_secretEnvelope = newValue} + } + /// Returns true if `secretEnvelope` has been explicitly set. + public var hasSecretEnvelope: Bool {return self._secretEnvelope != nil} + /// Clears the value of `secretEnvelope`. Subsequent reads from it will return its default value. + public mutating func clearSecretEnvelope() {self._secretEnvelope = nil} + + /// How many joins the invite allows (see Invite). 0 means unlimited. + public var maxUses: UInt32 = 0 + + /// When the invite stops admitting anyone. Optional; unset never expires. + /// If set, it must be in the future and no further away than the server + /// allows (INVALID_EXPIRES_AT otherwise). + public var expiresAt: SwiftProtobuf.Google_Protobuf_Timestamp { + get {return _expiresAt ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + set {_expiresAt = newValue} + } + /// Returns true if `expiresAt` has been explicitly set. + public var hasExpiresAt: Bool {return self._expiresAt != nil} + /// Clears the value of `expiresAt`. Subsequent reads from it will return its default value. + public mutating func clearExpiresAt() {self._expiresAt = nil} public var auth: Flipcash_Common_V1_Auth { get {return _auth ?? Flipcash_Common_V1_Auth()} @@ -2358,19 +2597,31 @@ public struct Flipcash_Chat_V1_SetFeaturedGroupsRequest: Sendable { public init() {} + fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil + fileprivate var _inviteToken: Flipcash_Chat_V1_InviteToken? = nil + fileprivate var _keyEnvelope: Flipcash_Chat_V1_InviteKeyEnvelope? = nil + fileprivate var _secretEnvelope: Flipcash_Chat_V1_InviteSecretEnvelope? = nil + fileprivate var _expiresAt: SwiftProtobuf.Google_Protobuf_Timestamp? = nil fileprivate var _auth: Flipcash_Common_V1_Auth? = nil } -public struct Flipcash_Chat_V1_SetFeaturedGroupsResponse: Sendable { +public struct Flipcash_Chat_V1_CreateInviteResponse: 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_SetFeaturedGroupsResponse.Result = .ok + public var result: Flipcash_Chat_V1_CreateInviteResponse.Result = .ok - /// The caller's featured groups after the write, as GetFeaturedGroups - /// returns them. Set only when result == OK. - public var featuredGroups: [Flipcash_Chat_V1_Metadata] = [] + /// The created invite, or the one a retry with the same ID created. Set + /// only when result == OK. + public var invite: Flipcash_Chat_V1_Invite { + get {return _invite ?? Flipcash_Chat_V1_Invite()} + set {_invite = newValue} + } + /// Returns true if `invite` has been explicitly set. + public var hasInvite: Bool {return self._invite != nil} + /// Clears the value of `invite`. Subsequent reads from it will return its default value. + public mutating func clearInvite() {self._invite = nil} public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -2378,11 +2629,18 @@ public struct Flipcash_Chat_V1_SetFeaturedGroupsResponse: Sendable { public typealias RawValue = Int case ok // = 0 - /// A group in the request is private; nothing was written + /// Caller may not invite to this chat (see CreateInvite) case denied // = 1 - - /// A group in the request does not exist; nothing was written case notFound // = 2 + + /// expires_at is not in the future, or further away than the server allows + case invalidExpiresAt // = 3 + + /// The chat holds as many ACTIVE invites as the server allows + case tooManyInvites // = 4 + + /// key_envelope's signature does not verify against the caller's public key + case invalidSignature // = 5 case UNRECOGNIZED(Int) public init() { @@ -2394,6 +2652,9 @@ public struct Flipcash_Chat_V1_SetFeaturedGroupsResponse: Sendable { case 0: self = .ok case 1: self = .denied case 2: self = .notFound + case 3: self = .invalidExpiresAt + case 4: self = .tooManyInvites + case 5: self = .invalidSignature default: self = .UNRECOGNIZED(rawValue) } } @@ -2403,41 +2664,46 @@ public struct Flipcash_Chat_V1_SetFeaturedGroupsResponse: Sendable { case .ok: return 0 case .denied: return 1 case .notFound: return 2 + case .invalidExpiresAt: return 3 + case .tooManyInvites: return 4 + case .invalidSignature: return 5 case .UNRECOGNIZED(let i): return i } } // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Flipcash_Chat_V1_SetFeaturedGroupsResponse.Result] = [ + public static let allCases: [Flipcash_Chat_V1_CreateInviteResponse.Result] = [ .ok, .denied, .notFound, + .invalidExpiresAt, + .tooManyInvites, + .invalidSignature, ] } public init() {} + + fileprivate var _invite: Flipcash_Chat_V1_Invite? = nil } -public struct Flipcash_Chat_V1_GetFeaturedGroupsRequest: Sendable { +public struct Flipcash_Chat_V1_GetInviteRequest: 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 whose featured groups are being fetched. Only a username is - /// supported for now; the oneof leaves room for other identifiers. - public var identifier: Flipcash_Chat_V1_GetFeaturedGroupsRequest.OneOf_Identifier? = nil - - public var username: Flipcash_Common_V1_Username { - get { - if case .username(let v)? = identifier {return v} - return Flipcash_Common_V1_Username() - } - set {identifier = .username(newValue)} + /// The token derived from the invite link's secret (see Invite). + public var inviteToken: Flipcash_Chat_V1_InviteToken { + get {return _inviteToken ?? Flipcash_Chat_V1_InviteToken()} + set {_inviteToken = newValue} } + /// Returns true if `inviteToken` has been explicitly set. + public var hasInviteToken: Bool {return self._inviteToken != nil} + /// Clears the value of `inviteToken`. Subsequent reads from it will return its default value. + public mutating func clearInviteToken() {self._inviteToken = nil} - /// Optional. When set it must be valid, but the list is the same with or - /// without it (see Chat.GetFeaturedGroups). + /// Optional (see GetInvite). When set, it must be valid. public var auth: Flipcash_Common_V1_Auth { get {return _auth ?? Flipcash_Common_V1_Auth()} set {_auth = newValue} @@ -2449,28 +2715,32 @@ public struct Flipcash_Chat_V1_GetFeaturedGroupsRequest: Sendable { public var unknownFields = SwiftProtobuf.UnknownStorage() - /// The user whose featured groups are being fetched. Only a username is - /// supported for now; the oneof leaves room for other identifiers. - public enum OneOf_Identifier: Equatable, Sendable { - case username(Flipcash_Common_V1_Username) - - } - public init() {} + fileprivate var _inviteToken: Flipcash_Chat_V1_InviteToken? = nil fileprivate var _auth: Flipcash_Common_V1_Auth? = nil } -public struct Flipcash_Chat_V1_GetFeaturedGroupsResponse: Sendable { +public struct Flipcash_Chat_V1_GetInviteResponse: 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_GetFeaturedGroupsResponse.Result = .ok + public var result: Flipcash_Chat_V1_GetInviteResponse.Result = .ok - /// The user's featured groups, in their order. Empty when they feature - /// none. Set only when result == OK. - public var featuredGroups: [Flipcash_Chat_V1_Metadata] = [] + /// The chat the invite admits users to, as the caller sees it (see + /// GetInvite). Set only when result == OK. + 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} + + /// Whether the invite still admits anyone. Set only when result == OK. + public var status: Flipcash_Chat_V1_Invite.Status = .unknown public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -2478,8 +2748,11 @@ public struct Flipcash_Chat_V1_GetFeaturedGroupsResponse: Sendable { public typealias RawValue = Int case ok // = 0 - /// No user holds the username + /// No invite matches the token case notFound // = 1 + + /// The chat's creator banned the caller (see BanUser); authenticated callers only + case banned // = 2 case UNRECOGNIZED(Int) public init() { @@ -2490,6 +2763,7 @@ public struct Flipcash_Chat_V1_GetFeaturedGroupsResponse: Sendable { switch rawValue { case 0: self = .ok case 1: self = .notFound + case 2: self = .banned default: self = .UNRECOGNIZED(rawValue) } } @@ -2498,22 +2772,26 @@ public struct Flipcash_Chat_V1_GetFeaturedGroupsResponse: Sendable { switch self { case .ok: return 0 case .notFound: return 1 + case .banned: 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_GetFeaturedGroupsResponse.Result] = [ + public static let allCases: [Flipcash_Chat_V1_GetInviteResponse.Result] = [ .ok, .notFound, + .banned, ] } public init() {} + + fileprivate var _chat: Flipcash_Chat_V1_Metadata? = nil } -public struct Flipcash_Chat_V1_SilenceUserRequest: Sendable { +public struct Flipcash_Chat_V1_GetChatInvitesRequest: 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. @@ -2527,26 +2805,21 @@ public struct Flipcash_Chat_V1_SilenceUserRequest: Sendable { /// Clears the value of `chatID`. Subsequent reads from it will return its default value. public mutating func clearChatID() {self._chatID = nil} - /// The user to silence. - 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} - - /// How long to silence them for. A timed silence must end in the future; - /// the server rejects one that does not. - public var silence: Flipcash_Chat_V1_SilenceState { - get {return _silence ?? Flipcash_Chat_V1_SilenceState()} - set {_silence = newValue} + /// QueryOptions controls page_size, capped at 100. Ordering is fixed to + /// most recently created 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 `silence` has been explicitly set. - public var hasSilence: Bool {return self._silence != nil} - /// Clears the value of `silence`. Subsequent reads from it will return its default value. - public mutating func clearSilence() {self._silence = nil} + /// 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()} @@ -2562,17 +2835,36 @@ public struct Flipcash_Chat_V1_SilenceUserRequest: Sendable { public init() {} fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil - fileprivate var _userID: Flipcash_Common_V1_UserId? = nil - fileprivate var _silence: Flipcash_Chat_V1_SilenceState? = nil + fileprivate var _queryOptions: Flipcash_Common_V1_QueryOptions? = nil fileprivate var _auth: Flipcash_Common_V1_Auth? = nil } -public struct Flipcash_Chat_V1_SilenceUserResponse: Sendable { +public struct Flipcash_Chat_V1_GetChatInvitesResponse: 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_SilenceUserResponse.Result = .ok + public var result: Flipcash_Chat_V1_GetChatInvitesResponse.Result = .ok + + /// The page, most recently created first. Set when result is OK. + public var invites: [Flipcash_Chat_V1_Invite] = [] + + /// PagingToken carries the cursor after the last invite returned. The + /// client MUST send it back in query_options.paging_token on the next + /// GetChatInvitesRequest. 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 GetChatInvitesRequest with the returned + /// paging_token. + public var hasMore_p: Bool = false public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -2580,12 +2872,9 @@ public struct Flipcash_Chat_V1_SilenceUserResponse: Sendable { public typealias RawValue = Int case ok // = 0 - /// Caller may not silence users in this chat (see SilenceUser) + /// Caller may not invite to this chat (see GetChatInvites) case denied // = 1 case notFound // = 2 - - /// The user is the chat's creator - case cannotRestrictCreator // = 3 case UNRECOGNIZED(Int) public init() { @@ -2597,7 +2886,6 @@ public struct Flipcash_Chat_V1_SilenceUserResponse: Sendable { case 0: self = .ok case 1: self = .denied case 2: self = .notFound - case 3: self = .cannotRestrictCreator default: self = .UNRECOGNIZED(rawValue) } } @@ -2607,25 +2895,25 @@ public struct Flipcash_Chat_V1_SilenceUserResponse: Sendable { case .ok: return 0 case .denied: return 1 case .notFound: return 2 - case .cannotRestrictCreator: 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_SilenceUserResponse.Result] = [ + public static let allCases: [Flipcash_Chat_V1_GetChatInvitesResponse.Result] = [ .ok, .denied, .notFound, - .cannotRestrictCreator, ] } public init() {} + + fileprivate var _pagingToken: Flipcash_Common_V1_PagingToken? = nil } -public struct Flipcash_Chat_V1_UnsilenceUserRequest: Sendable { +public struct Flipcash_Chat_V1_RevokeInviteRequest: 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. @@ -2639,15 +2927,15 @@ public struct Flipcash_Chat_V1_UnsilenceUserRequest: Sendable { /// Clears the value of `chatID`. Subsequent reads from it will return its default value. public mutating func clearChatID() {self._chatID = nil} - /// The user to unsilence. - public var userID: Flipcash_Common_V1_UserId { - get {return _userID ?? Flipcash_Common_V1_UserId()} - set {_userID = newValue} + /// The invite to revoke, which must be an invite to chat_id. + public var inviteID: Flipcash_Chat_V1_InviteId { + get {return _inviteID ?? Flipcash_Chat_V1_InviteId()} + set {_inviteID = 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} + /// Returns true if `inviteID` has been explicitly set. + public var hasInviteID: Bool {return self._inviteID != nil} + /// Clears the value of `inviteID`. Subsequent reads from it will return its default value. + public mutating func clearInviteID() {self._inviteID = nil} public var auth: Flipcash_Common_V1_Auth { get {return _auth ?? Flipcash_Common_V1_Auth()} @@ -2663,16 +2951,16 @@ public struct Flipcash_Chat_V1_UnsilenceUserRequest: Sendable { public init() {} fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil - fileprivate var _userID: Flipcash_Common_V1_UserId? = nil + fileprivate var _inviteID: Flipcash_Chat_V1_InviteId? = nil fileprivate var _auth: Flipcash_Common_V1_Auth? = nil } -public struct Flipcash_Chat_V1_UnsilenceUserResponse: Sendable { +public struct Flipcash_Chat_V1_RevokeInviteResponse: 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_UnsilenceUserResponse.Result = .ok + public var result: Flipcash_Chat_V1_RevokeInviteResponse.Result = .ok public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -2680,8 +2968,10 @@ public struct Flipcash_Chat_V1_UnsilenceUserResponse: Sendable { public typealias RawValue = Int case ok // = 0 - /// Caller may not silence users in this chat (see SilenceUser) + /// Caller may not invite to this chat (see RevokeInvite) case denied // = 1 + + /// No such chat, or no invite to it with this ID case notFound // = 2 case UNRECOGNIZED(Int) @@ -2708,7 +2998,7 @@ public struct Flipcash_Chat_V1_UnsilenceUserResponse: Sendable { } // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Flipcash_Chat_V1_UnsilenceUserResponse.Result] = [ + public static let allCases: [Flipcash_Chat_V1_RevokeInviteResponse.Result] = [ .ok, .denied, .notFound, @@ -2719,29 +3009,15 @@ public struct Flipcash_Chat_V1_UnsilenceUserResponse: Sendable { public init() {} } -public struct Flipcash_Chat_V1_BanUserRequest: Sendable { +public struct Flipcash_Chat_V1_SetFeaturedGroupsRequest: 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 ban. - 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 groups to feature, in the order to show them. Every ID must name a + /// public group chat, and none may repeat. Empty clears the featured + /// groups. + public var chatIds: [Flipcash_Common_V1_ChatId] = [] public var auth: Flipcash_Common_V1_Auth { get {return _auth ?? Flipcash_Common_V1_Auth()} @@ -2756,17 +3032,19 @@ public struct Flipcash_Chat_V1_BanUserRequest: Sendable { 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_BanUserResponse: Sendable { +public struct Flipcash_Chat_V1_SetFeaturedGroupsResponse: 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_BanUserResponse.Result = .ok + public var result: Flipcash_Chat_V1_SetFeaturedGroupsResponse.Result = .ok + + /// The caller's featured groups after the write, as GetFeaturedGroups + /// returns them. Set only when result == OK. + public var featuredGroups: [Flipcash_Chat_V1_Metadata] = [] public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -2774,12 +3052,11 @@ public struct Flipcash_Chat_V1_BanUserResponse: Sendable { public typealias RawValue = Int case ok // = 0 - /// Caller may not ban users in this chat (see BanUser) + /// A group in the request is private; nothing was written case denied // = 1 - case notFound // = 2 - /// The user is the chat's creator - case cannotRestrictCreator // = 3 + /// A group in the request does not exist; nothing was written + case notFound // = 2 case UNRECOGNIZED(Int) public init() { @@ -2791,7 +3068,6 @@ public struct Flipcash_Chat_V1_BanUserResponse: Sendable { case 0: self = .ok case 1: self = .denied case 2: self = .notFound - case 3: self = .cannotRestrictCreator default: self = .UNRECOGNIZED(rawValue) } } @@ -2801,17 +3077,15 @@ public struct Flipcash_Chat_V1_BanUserResponse: Sendable { case .ok: return 0 case .denied: return 1 case .notFound: return 2 - case .cannotRestrictCreator: 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_BanUserResponse.Result] = [ + public static let allCases: [Flipcash_Chat_V1_SetFeaturedGroupsResponse.Result] = [ .ok, .denied, .notFound, - .cannotRestrictCreator, ] } @@ -2819,13 +3093,107 @@ public struct Flipcash_Chat_V1_BanUserResponse: Sendable { public init() {} } -public struct Flipcash_Chat_V1_UnbanUserRequest: Sendable { +public struct Flipcash_Chat_V1_GetFeaturedGroupsRequest: 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()} + /// The user whose featured groups are being fetched. Only a username is + /// supported for now; the oneof leaves room for other identifiers. + public var identifier: Flipcash_Chat_V1_GetFeaturedGroupsRequest.OneOf_Identifier? = nil + + public var username: Flipcash_Common_V1_Username { + get { + if case .username(let v)? = identifier {return v} + return Flipcash_Common_V1_Username() + } + set {identifier = .username(newValue)} + } + + /// Optional. When set it must be valid, but the list is the same with or + /// without it (see Chat.GetFeaturedGroups). + 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() + + /// The user whose featured groups are being fetched. Only a username is + /// supported for now; the oneof leaves room for other identifiers. + public enum OneOf_Identifier: Equatable, Sendable { + case username(Flipcash_Common_V1_Username) + + } + + public init() {} + + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Chat_V1_GetFeaturedGroupsResponse: 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_GetFeaturedGroupsResponse.Result = .ok + + /// The user's featured groups, in their order. Empty when they feature + /// none. Set only when result == OK. + public var featuredGroups: [Flipcash_Chat_V1_Metadata] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + + /// No user holds the username + case notFound // = 1 + case UNRECOGNIZED(Int) + + public init() { + self = .ok + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .ok + case 1: self = .notFound + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .notFound: 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_GetFeaturedGroupsResponse.Result] = [ + .ok, + .notFound, + ] + + } + + public init() {} +} + +public struct Flipcash_Chat_V1_SilenceUserRequest: 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. @@ -2833,7 +3201,7 @@ public struct Flipcash_Chat_V1_UnbanUserRequest: Sendable { /// Clears the value of `chatID`. Subsequent reads from it will return its default value. public mutating func clearChatID() {self._chatID = nil} - /// The user to unban. + /// The user to silence. public var userID: Flipcash_Common_V1_UserId { get {return _userID ?? Flipcash_Common_V1_UserId()} set {_userID = newValue} @@ -2843,6 +3211,17 @@ public struct Flipcash_Chat_V1_UnbanUserRequest: Sendable { /// Clears the value of `userID`. Subsequent reads from it will return its default value. public mutating func clearUserID() {self._userID = nil} + /// How long to silence them for. A timed silence must end in the future; + /// the server rejects one that does not. + public var silence: Flipcash_Chat_V1_SilenceState { + get {return _silence ?? Flipcash_Chat_V1_SilenceState()} + set {_silence = newValue} + } + /// Returns true if `silence` has been explicitly set. + public var hasSilence: Bool {return self._silence != nil} + /// Clears the value of `silence`. Subsequent reads from it will return its default value. + public mutating func clearSilence() {self._silence = nil} + public var auth: Flipcash_Common_V1_Auth { get {return _auth ?? Flipcash_Common_V1_Auth()} set {_auth = newValue} @@ -2858,15 +3237,16 @@ public struct Flipcash_Chat_V1_UnbanUserRequest: Sendable { fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil fileprivate var _userID: Flipcash_Common_V1_UserId? = nil + fileprivate var _silence: Flipcash_Chat_V1_SilenceState? = nil fileprivate var _auth: Flipcash_Common_V1_Auth? = nil } -public struct Flipcash_Chat_V1_UnbanUserResponse: Sendable { +public struct Flipcash_Chat_V1_SilenceUserResponse: 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_UnbanUserResponse.Result = .ok + public var result: Flipcash_Chat_V1_SilenceUserResponse.Result = .ok public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -2874,9 +3254,12 @@ public struct Flipcash_Chat_V1_UnbanUserResponse: Sendable { public typealias RawValue = Int case ok // = 0 - /// Caller may not ban users in this chat (see BanUser) + /// Caller may not silence users in this chat (see SilenceUser) case denied // = 1 case notFound // = 2 + + /// The user is the chat's creator + case cannotRestrictCreator // = 3 case UNRECOGNIZED(Int) public init() { @@ -2888,6 +3271,7 @@ public struct Flipcash_Chat_V1_UnbanUserResponse: Sendable { case 0: self = .ok case 1: self = .denied case 2: self = .notFound + case 3: self = .cannotRestrictCreator default: self = .UNRECOGNIZED(rawValue) } } @@ -2897,15 +3281,17 @@ public struct Flipcash_Chat_V1_UnbanUserResponse: Sendable { case .ok: return 0 case .denied: return 1 case .notFound: return 2 + case .cannotRestrictCreator: 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_UnbanUserResponse.Result] = [ + public static let allCases: [Flipcash_Chat_V1_SilenceUserResponse.Result] = [ .ok, .denied, .notFound, + .cannotRestrictCreator, ] } @@ -2913,7 +3299,7 @@ public struct Flipcash_Chat_V1_UnbanUserResponse: Sendable { public init() {} } -public struct Flipcash_Chat_V1_GetRestrictedUsersRequest: Sendable { +public struct Flipcash_Chat_V1_UnsilenceUserRequest: 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. @@ -2927,21 +3313,15 @@ public struct Flipcash_Chat_V1_GetRestrictedUsersRequest: Sendable { /// 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 - /// most recently restricted 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} + /// The user to unsilence. + public var userID: Flipcash_Common_V1_UserId { + get {return _userID ?? Flipcash_Common_V1_UserId()} + set {_userID = 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} + /// 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()} @@ -2957,36 +3337,16 @@ public struct Flipcash_Chat_V1_GetRestrictedUsersRequest: Sendable { public init() {} fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil - fileprivate var _queryOptions: Flipcash_Common_V1_QueryOptions? = nil + fileprivate var _userID: Flipcash_Common_V1_UserId? = nil fileprivate var _auth: Flipcash_Common_V1_Auth? = nil } -public struct Flipcash_Chat_V1_GetRestrictedUsersResponse: Sendable { +public struct Flipcash_Chat_V1_UnsilenceUserResponse: 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_GetRestrictedUsersResponse.Result = .ok - - /// The page, most recently restricted first. Set when result is OK. - public var users: [Flipcash_Chat_V1_RestrictedUser] = [] - - /// PagingToken carries the cursor after the last user returned. The - /// client MUST send it back in query_options.paging_token on the next - /// GetRestrictedUsersRequest. 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 GetRestrictedUsersRequest with the returned - /// paging_token. - public var hasMore_p: Bool = false + public var result: Flipcash_Chat_V1_UnsilenceUserResponse.Result = .ok public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -2994,7 +3354,7 @@ public struct Flipcash_Chat_V1_GetRestrictedUsersResponse: Sendable { public typealias RawValue = Int case ok // = 0 - /// Caller may neither silence nor ban users in this chat (see GetRestrictedUsers) + /// Caller may not silence users in this chat (see SilenceUser) case denied // = 1 case notFound // = 2 case UNRECOGNIZED(Int) @@ -3022,7 +3382,7 @@ public struct Flipcash_Chat_V1_GetRestrictedUsersResponse: Sendable { } // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Flipcash_Chat_V1_GetRestrictedUsersResponse.Result] = [ + public static let allCases: [Flipcash_Chat_V1_UnsilenceUserResponse.Result] = [ .ok, .denied, .notFound, @@ -3031,70 +3391,668 @@ public struct Flipcash_Chat_V1_GetRestrictedUsersResponse: Sendable { } public init() {} - - fileprivate var _pagingToken: Flipcash_Common_V1_PagingToken? = 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_BanUserRequest: 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) + /// The user to ban. + 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 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 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} -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 var unknownFields = SwiftProtobuf.UnknownStorage() - 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 + 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_BanUserResponse: 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_BanUserResponse.Result = .ok + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + + /// Caller may not ban users in this chat (see BanUser) + case denied // = 1 + case notFound // = 2 + + /// The user is the chat's creator + case cannotRestrictCreator // = 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 = .cannotRestrictCreator + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .denied: return 1 + case .notFound: return 2 + case .cannotRestrictCreator: 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_BanUserResponse.Result] = [ + .ok, + .denied, + .notFound, + .cannotRestrictCreator, + ] + + } + + public init() {} +} + +public struct Flipcash_Chat_V1_UnbanUserRequest: 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 unban. + 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_UnbanUserResponse: 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_UnbanUserResponse.Result = .ok + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + + /// Caller may not ban users in this chat (see BanUser) + 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_UnbanUserResponse.Result] = [ + .ok, + .denied, + .notFound, + ] + + } + + public init() {} +} + +public struct Flipcash_Chat_V1_GetRestrictedUsersRequest: 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 + /// most recently restricted 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_GetRestrictedUsersResponse: 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_GetRestrictedUsersResponse.Result = .ok + + /// The page, most recently restricted first. Set when result is OK. + public var users: [Flipcash_Chat_V1_RestrictedUser] = [] + + /// PagingToken carries the cursor after the last user returned. The + /// client MUST send it back in query_options.paging_token on the next + /// GetRestrictedUsersRequest. 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 GetRestrictedUsersRequest 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 may neither silence nor ban users in this chat (see GetRestrictedUsers) + 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_GetRestrictedUsersResponse.Result] = [ + .ok, + .denied, + .notFound, + ] + + } + + public init() {} + + fileprivate var _pagingToken: Flipcash_Common_V1_PagingToken? = 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{3}invite_token\0\u{2}\u{7}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 3: try { try decoder.decodeSingularMessageField(value: &self._inviteToken) }() + 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._inviteToken { + 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 { + if lhs._chatID != rhs._chatID {return false} + if lhs._queryOptions != rhs._queryOptions {return false} + if lhs._inviteToken != rhs._inviteToken {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.decodeSingularMessageField(value: &self._metadata) }() + 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 } } @@ -3108,27 +4066,39 @@ extension Flipcash_Chat_V1_GetChatResponse: SwiftProtobuf.Message, SwiftProtobuf 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) + 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_GetChatResponse, rhs: Flipcash_Chat_V1_GetChatResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_GetRosterResponse, rhs: Flipcash_Chat_V1_GetRosterResponse) -> Bool { if lhs.result != rhs.result {return false} - if lhs._metadata != rhs._metadata {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_GetChatResponse.Result: SwiftProtobuf._ProtoNameProviding { +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_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") +extension Flipcash_Chat_V1_SampleChattersRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".SampleChattersRequest" + 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() { @@ -3136,8 +4106,194 @@ extension Flipcash_Chat_V1_GetDmChatFeedRequest: SwiftProtobuf.Message, SwiftPro // 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 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_SampleChattersRequest, rhs: Flipcash_Chat_V1_SampleChattersRequest) -> 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_SampleChattersResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".SampleChattersResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}chatters\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.chatters) }() + case 3: try { try decoder.decodeSingularBoolField(value: &self.hasMore_p) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) + } + if !self.chatters.isEmpty { + try visitor.visitRepeatedMessageField(value: self.chatters, fieldNumber: 2) + } + if self.hasMore_p != false { + try visitor.visitSingularBoolField(value: self.hasMore_p, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_SampleChattersResponse, rhs: Flipcash_Chat_V1_SampleChattersResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs.chatters != rhs.chatters {return false} + if lhs.hasMore_p != rhs.hasMore_p {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_SampleChattersResponse.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 } @@ -3149,30 +4305,38 @@ extension Flipcash_Chat_V1_GetDmChatFeedRequest: SwiftProtobuf.Message, SwiftPro // 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 { + switch self.parameters { + case .publicGroup?: try { + guard case .publicGroup(let v)? = self.parameters else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - if self.dmChatType != .unknown { - try visitor.visitSingularEnumField(value: self.dmChatType, fieldNumber: 2) + }() + 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_GetDmChatFeedRequest, rhs: Flipcash_Chat_V1_GetDmChatFeedRequest) -> Bool { - if lhs._queryOptions != rhs._queryOptions {return false} - if lhs.dmChatType != rhs.dmChatType {return false} + 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_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") +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{3}profile_picture\0\u{1}rules\0\u{1}description\0\u{3}cover_picture\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3180,10 +4344,11 @@ extension Flipcash_Chat_V1_GetDmChatFeedResponse: 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.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) }() + case 1: try { try decoder.decodeSingularStringField(value: &self.title) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._profilePicture) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._rules) }() + case 4: try { try decoder.decodeSingularStringField(value: &self.description_p) }() + case 5: try { try decoder.decodeSingularMessageField(value: &self._coverPicture) }() default: break } } @@ -3194,38 +4359,38 @@ extension Flipcash_Chat_V1_GetDmChatFeedResponse: SwiftProtobuf.Message, SwiftPr // 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) + if !self.title.isEmpty { + try visitor.visitSingularStringField(value: self.title, fieldNumber: 1) } - try { if let v = self._pagingToken { + try { if let v = self._profilePicture { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = self._rules { try visitor.visitSingularMessageField(value: v, fieldNumber: 3) } }() - if self.hasMore_p != false { - try visitor.visitSingularBoolField(value: self.hasMore_p, fieldNumber: 4) + if !self.description_p.isEmpty { + try visitor.visitSingularStringField(value: self.description_p, fieldNumber: 4) } + try { if let v = self._coverPicture { + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + } }() 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} + 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._profilePicture != rhs._profilePicture {return false} + if lhs._rules != rhs._rules {return false} + if lhs.description_p != rhs.description_p {return false} + if lhs._coverPicture != rhs._coverPicture {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") +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{3}profile_picture\0\u{1}description\0\u{3}cover_picture\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3233,8 +4398,10 @@ extension Flipcash_Chat_V1_GetGroupChatFeedRequest: 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._queryOptions) }() - case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() + case 1: try { try decoder.decodeSingularStringField(value: &self.title) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._profilePicture) }() + case 3: try { try decoder.decodeSingularStringField(value: &self.description_p) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._coverPicture) }() default: break } } @@ -3245,26 +4412,34 @@ 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 visitor.visitSingularMessageField(value: v, fieldNumber: 1) + if !self.title.isEmpty { + try visitor.visitSingularStringField(value: self.title, fieldNumber: 1) + } + try { if let v = self._profilePicture { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() - try { if let v = self._auth { - try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + if !self.description_p.isEmpty { + try visitor.visitSingularStringField(value: self.description_p, fieldNumber: 3) + } + try { if let v = self._coverPicture { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) } }() 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} + 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._profilePicture != rhs._profilePicture {return false} + if lhs.description_p != rhs.description_p {return false} + if lhs._coverPicture != rhs._coverPicture {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_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() { @@ -3273,9 +4448,8 @@ 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) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._chat) }() + case 3: try { try decoder.decodeSingularEnumField(value: &self.flaggedCategory) }() default: break } } @@ -3289,35 +4463,31 @@ extension Flipcash_Chat_V1_GetGroupChatFeedResponse: SwiftProtobuf.Message, Swif 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) + 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: 4) + if self.flaggedCategory != .none { + try visitor.visitSingularEnumField(value: self.flaggedCategory, fieldNumber: 3) } 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_StartChatResponse, rhs: Flipcash_Chat_V1_StartChatResponse) -> 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._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_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_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}PROFILE_PICTURE_BLOB_NOT_ACCEPTED\0\u{1}INVALID_RULES\0\u{1}RULES_NOT_SATISFIED\0\u{1}DESCRIPTION_MODERATED\0\u{1}COVER_PICTURE_BLOB_NOT_ACCEPTED\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_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{1}invite\0\u{2}\u{8}auth\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3326,7 +4496,7 @@ 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._invite) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -3341,7 +4511,7 @@ 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._invite { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() try { if let v = self._auth { @@ -3350,18 +4520,18 @@ extension Flipcash_Chat_V1_GetRosterRequest: SwiftProtobuf.Message, SwiftProtobu 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_JoinChatRequest, rhs: Flipcash_Chat_V1_JoinChatRequest) -> Bool { if lhs._chatID != rhs._chatID {return false} - if lhs._queryOptions != rhs._queryOptions {return false} + if lhs._invite != rhs._invite {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_JoinChatRequest.InviteParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Chat_V1_JoinChatRequest.protoMessageName + ".InviteParameters" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}invite_token\0\u{3}secret_envelope\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3369,11 +4539,8 @@ extension Flipcash_Chat_V1_GetRosterResponse: SwiftProtobuf.Message, SwiftProtob // 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) }() + case 1: try { try decoder.decodeSingularMessageField(value: &self._inviteToken) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._secretEnvelope) }() default: break } } @@ -3384,42 +4551,26 @@ extension Flipcash_Chat_V1_GetRosterResponse: SwiftProtobuf.Message, SwiftProtob // 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._inviteToken { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() - try { if let v = self._pagingToken { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + try { if let v = self._secretEnvelope { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() - 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} + public static func ==(lhs: Flipcash_Chat_V1_JoinChatRequest.InviteParameters, rhs: Flipcash_Chat_V1_JoinChatRequest.InviteParameters) -> Bool { + if lhs._inviteToken != rhs._inviteToken {return false} + if lhs._secretEnvelope != rhs._secretEnvelope {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_SampleChattersRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".SampleChattersRequest" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{2}\u{9}auth\0") +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() { @@ -3427,8 +4578,8 @@ extension Flipcash_Chat_V1_SampleChattersRequest: 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.decodeSingularMessageField(value: &self._chatID) }() - case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() + case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._chat) }() default: break } } @@ -3439,69 +4590,29 @@ extension Flipcash_Chat_V1_SampleChattersRequest: SwiftProtobuf.Message, SwiftPr // 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_SampleChattersRequest, rhs: Flipcash_Chat_V1_SampleChattersRequest) -> 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_SampleChattersResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".SampleChattersResponse" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}chatters\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.chatters) }() - case 3: try { try decoder.decodeSingularBoolField(value: &self.hasMore_p) }() - default: break - } - } - } - - public func traverse(visitor: inout V) throws { if self.result != .ok { try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) } - if !self.chatters.isEmpty { - try visitor.visitRepeatedMessageField(value: self.chatters, fieldNumber: 2) - } - if self.hasMore_p != false { - try visitor.visitSingularBoolField(value: self.hasMore_p, fieldNumber: 3) - } + 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_SampleChattersResponse, rhs: Flipcash_Chat_V1_SampleChattersResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_JoinChatResponse, rhs: Flipcash_Chat_V1_JoinChatResponse) -> Bool { if lhs.result != rhs.result {return false} - if lhs.chatters != rhs.chatters {return false} - if lhs.hasMore_p != rhs.hasMore_p {return false} + if lhs._chat != rhs._chat {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_SampleChattersResponse.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_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\u{1}BANNED\0\u{1}INVITE_NOT_FOUND\0\u{1}INVITE_EXHAUSTED\0\u{1}INVITE_EXPIRED\0\u{1}INVITE_REVOKED\0") } -extension Flipcash_Chat_V1_GetMentionSuggestionsRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".GetMentionSuggestionsRequest" +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 { @@ -3531,7 +4642,7 @@ extension Flipcash_Chat_V1_GetMentionSuggestionsRequest: SwiftProtobuf.Message, 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_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} @@ -3539,9 +4650,9 @@ extension Flipcash_Chat_V1_GetMentionSuggestionsRequest: SwiftProtobuf.Message, } } -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_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() { @@ -3550,7 +4661,6 @@ 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) }() default: break } } @@ -3560,27 +4670,23 @@ extension Flipcash_Chat_V1_GetMentionSuggestionsResponse: SwiftProtobuf.Message, 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 { + public static func ==(lhs: Flipcash_Chat_V1_LeaveChatResponse, rhs: Flipcash_Chat_V1_LeaveChatResponse) -> 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 { +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_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") +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{3}profile_picture\0\u{1}description\0\u{3}cover_picture\0\u{2}\u{5}auth\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3588,33 +4694,11 @@ extension Flipcash_Chat_V1_StartChatRequest: SwiftProtobuf.Message, SwiftProtobu // 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 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._profilePicture) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._description_p) }() + case 5: try { try decoder.decodeSingularMessageField(value: &self._coverPicture) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -3626,19 +4710,20 @@ 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 - switch self.parameters { - case .publicGroup?: try { - guard case .publicGroup(let v)? = self.parameters else { preconditionFailure() } + try { if let v = self._chatID { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - }() - case .privateGroup?: try { - guard case .privateGroup(let v)? = self.parameters else { preconditionFailure() } + } }() + try { if let v = self._title { 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._profilePicture { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + try { if let v = self._description_p { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + try { if let v = self._coverPicture { + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) } }() try { if let v = self._auth { try visitor.visitSingularMessageField(value: v, fieldNumber: 10) @@ -3646,18 +4731,21 @@ extension Flipcash_Chat_V1_StartChatRequest: SwiftProtobuf.Message, SwiftProtobu 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_EditChatRequest, rhs: Flipcash_Chat_V1_EditChatRequest) -> Bool { + if lhs._chatID != rhs._chatID {return false} + if lhs._title != rhs._title {return false} + if lhs._profilePicture != rhs._profilePicture {return false} + if lhs._description_p != rhs._description_p {return false} + if lhs._coverPicture != rhs._coverPicture {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{3}profile_picture\0\u{1}rules\0\u{1}description\0\u{3}cover_picture\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() { @@ -3665,53 +4753,29 @@ extension Flipcash_Chat_V1_StartChatRequest.PublicGroupChatParameters: SwiftProt // 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._profilePicture) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._rules) }() - case 4: try { try decoder.decodeSingularStringField(value: &self.description_p) }() - case 5: try { try decoder.decodeSingularMessageField(value: &self._coverPicture) }() + case 1: try { try decoder.decodeSingularStringField(value: &self.value) }() 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._profilePicture { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try { if let v = self._rules { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - if !self.description_p.isEmpty { - try visitor.visitSingularStringField(value: self.description_p, fieldNumber: 4) + if !self.value.isEmpty { + try visitor.visitSingularStringField(value: self.value, fieldNumber: 1) } - try { if let v = self._coverPicture { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } }() 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._profilePicture != rhs._profilePicture {return false} - if lhs._rules != rhs._rules {return false} - if lhs.description_p != rhs.description_p {return false} - if lhs._coverPicture != rhs._coverPicture {return false} + 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_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{3}profile_picture\0\u{1}description\0\u{3}cover_picture\0") +extension Flipcash_Chat_V1_EditChatRequest.ProfilePicture: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Chat_V1_EditChatRequest.protoMessageName + ".ProfilePicture" + 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() { @@ -3719,10 +4783,7 @@ extension Flipcash_Chat_V1_StartChatRequest.PrivateGroupChatParameters: SwiftPro // 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._profilePicture) }() - case 3: try { try decoder.decodeSingularStringField(value: &self.description_p) }() - case 4: try { try decoder.decodeSingularMessageField(value: &self._coverPicture) }() + case 1: try { try decoder.decodeSingularMessageField(value: &self._blobID) }() default: break } } @@ -3733,34 +4794,22 @@ extension Flipcash_Chat_V1_StartChatRequest.PrivateGroupChatParameters: SwiftPro // 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._profilePicture { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - if !self.description_p.isEmpty { - try visitor.visitSingularStringField(value: self.description_p, fieldNumber: 3) - } - try { if let v = self._coverPicture { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + 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_StartChatRequest.PrivateGroupChatParameters, rhs: Flipcash_Chat_V1_StartChatRequest.PrivateGroupChatParameters) -> Bool { - if lhs.title != rhs.title {return false} - if lhs._profilePicture != rhs._profilePicture {return false} - if lhs.description_p != rhs.description_p {return false} - if lhs._coverPicture != rhs._coverPicture {return false} + public static func ==(lhs: Flipcash_Chat_V1_EditChatRequest.ProfilePicture, rhs: Flipcash_Chat_V1_EditChatRequest.ProfilePicture) -> Bool { + if lhs._blobID != rhs._blobID {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_EditChatRequest.Description: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Chat_V1_EditChatRequest.protoMessageName + ".Description" + 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() { @@ -3768,47 +4817,29 @@ extension Flipcash_Chat_V1_StartChatResponse: SwiftProtobuf.Message, SwiftProtob // 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) }() + case 1: try { try decoder.decodeSingularStringField(value: &self.value) }() 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) + if !self.value.isEmpty { + try visitor.visitSingularStringField(value: self.value, fieldNumber: 1) } 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} + public static func ==(lhs: Flipcash_Chat_V1_EditChatRequest.Description, rhs: Flipcash_Chat_V1_EditChatRequest.Description) -> Bool { + if lhs.value != rhs.value {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}PROFILE_PICTURE_BLOB_NOT_ACCEPTED\0\u{1}INVALID_RULES\0\u{1}RULES_NOT_SATISFIED\0\u{1}DESCRIPTION_MODERATED\0\u{1}COVER_PICTURE_BLOB_NOT_ACCEPTED\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") +extension Flipcash_Chat_V1_EditChatRequest.CoverPicture: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Chat_V1_EditChatRequest.protoMessageName + ".CoverPicture" + 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() { @@ -3816,8 +4847,7 @@ extension Flipcash_Chat_V1_JoinChatRequest: SwiftProtobuf.Message, SwiftProtobuf // 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) }() + case 1: try { try decoder.decodeSingularMessageField(value: &self._blobID) }() default: break } } @@ -3828,26 +4858,22 @@ extension Flipcash_Chat_V1_JoinChatRequest: SwiftProtobuf.Message, SwiftProtobuf // 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 { 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_Chat_V1_JoinChatRequest, rhs: Flipcash_Chat_V1_JoinChatRequest) -> Bool { - if lhs._chatID != rhs._chatID {return false} - if lhs._auth != rhs._auth {return false} + public static func ==(lhs: Flipcash_Chat_V1_EditChatRequest.CoverPicture, rhs: Flipcash_Chat_V1_EditChatRequest.CoverPicture) -> Bool { + if lhs._blobID != rhs._blobID {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") +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() { @@ -3857,6 +4883,7 @@ extension Flipcash_Chat_V1_JoinChatResponse: SwiftProtobuf.Message, SwiftProtobu 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 } } @@ -3873,24 +4900,28 @@ extension Flipcash_Chat_V1_JoinChatResponse: SwiftProtobuf.Message, SwiftProtobu 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_JoinChatResponse, rhs: Flipcash_Chat_V1_JoinChatResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_EditChatResponse, rhs: Flipcash_Chat_V1_EditChatResponse) -> 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_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\u{1}BANNED\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}PROFILE_PICTURE_BLOB_NOT_ACCEPTED\0\u{1}COVER_PICTURE_BLOB_NOT_ACCEPTED\0\u{1}DESCRIPTION_MODERATED\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_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() { @@ -3899,6 +4930,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._mute) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -3913,23 +4945,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._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_LeaveChatRequest, rhs: Flipcash_Chat_V1_LeaveChatRequest) -> 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_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_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() { @@ -3938,32 +4974,41 @@ 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.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_LeaveChatResponse, rhs: Flipcash_Chat_V1_LeaveChatResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_MuteChatResponse, rhs: Flipcash_Chat_V1_MuteChatResponse) -> 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_LeaveChatResponse.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_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{3}profile_picture\0\u{1}description\0\u{3}cover_picture\0\u{2}\u{5}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() { @@ -3972,10 +5017,6 @@ 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._profilePicture) }() - case 4: try { try decoder.decodeSingularMessageField(value: &self._description_p) }() - case 5: try { try decoder.decodeSingularMessageField(value: &self._coverPicture) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -3990,39 +5031,23 @@ 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 visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try { if let v = self._profilePicture { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - try { if let v = self._description_p { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } }() - try { if let v = self._coverPicture { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } }() 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, rhs: Flipcash_Chat_V1_EditChatRequest) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_UnmuteChatRequest, rhs: Flipcash_Chat_V1_UnmuteChatRequest) -> Bool { if lhs._chatID != rhs._chatID {return false} - if lhs._title != rhs._title {return false} - if lhs._profilePicture != rhs._profilePicture {return false} - if lhs._description_p != rhs._description_p {return false} - if lhs._coverPicture != rhs._coverPicture {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_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() { @@ -4030,29 +5055,42 @@ 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) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._viewerState) }() default: break } } } public func traverse(visitor: inout V) throws { - if !self.value.isEmpty { - try visitor.visitSingularStringField(value: self.value, fieldNumber: 1) + // 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_EditChatRequest.Title, rhs: Flipcash_Chat_V1_EditChatRequest.Title) -> Bool { - if lhs.value != rhs.value {return false} + 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_EditChatRequest.ProfilePicture: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = Flipcash_Chat_V1_EditChatRequest.protoMessageName + ".ProfilePicture" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}blob_id\0") +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() { @@ -4060,7 +5098,8 @@ extension Flipcash_Chat_V1_EditChatRequest.ProfilePicture: SwiftProtobuf.Message // 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 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } } @@ -4071,22 +5110,26 @@ extension Flipcash_Chat_V1_EditChatRequest.ProfilePicture: SwiftProtobuf.Message // 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._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_EditChatRequest.ProfilePicture, rhs: Flipcash_Chat_V1_EditChatRequest.ProfilePicture) -> Bool { - if lhs._blobID != rhs._blobID {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_EditChatRequest.Description: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = Flipcash_Chat_V1_EditChatRequest.protoMessageName + ".Description" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}value\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() { @@ -4094,29 +5137,42 @@ extension Flipcash_Chat_V1_EditChatRequest.Description: SwiftProtobuf.Message, S // 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) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._lobby) }() default: break } } } public func traverse(visitor: inout V) throws { - if !self.value.isEmpty { - try visitor.visitSingularStringField(value: self.value, fieldNumber: 1) + // 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._lobby { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_EditChatRequest.Description, rhs: Flipcash_Chat_V1_EditChatRequest.Description) -> Bool { - if lhs.value != rhs.value {return false} + public static func ==(lhs: Flipcash_Chat_V1_EnterLobbyResponse, rhs: Flipcash_Chat_V1_EnterLobbyResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs._lobby != rhs._lobby {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_EditChatRequest.CoverPicture: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = Flipcash_Chat_V1_EditChatRequest.protoMessageName + ".CoverPicture" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}blob_id\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\u{1}BANNED\0") +} + +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 { while let fieldNumber = try decoder.nextFieldNumber() { @@ -4124,7 +5180,8 @@ extension Flipcash_Chat_V1_EditChatRequest.CoverPicture: SwiftProtobuf.Message, // 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 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } } @@ -4135,22 +5192,26 @@ extension Flipcash_Chat_V1_EditChatRequest.CoverPicture: SwiftProtobuf.Message, // 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._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_EditChatRequest.CoverPicture, rhs: Flipcash_Chat_V1_EditChatRequest.CoverPicture) -> Bool { - if lhs._blobID != rhs._blobID {return false} + 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} 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_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() { @@ -4159,46 +5220,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_LeaveLobbyResponse, rhs: Flipcash_Chat_V1_LeaveLobbyResponse) -> 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}PROFILE_PICTURE_BLOB_NOT_ACCEPTED\0\u{1}COVER_PICTURE_BLOB_NOT_ACCEPTED\0\u{1}DESCRIPTION_MODERATED\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_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_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() { @@ -4207,7 +5254,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._queryOptions) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -4222,7 +5269,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._queryOptions { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() try { if let v = self._auth { @@ -4231,18 +5278,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_GetLobbyMembersRequest, rhs: Flipcash_Chat_V1_GetLobbyMembersRequest) -> Bool { if lhs._chatID != rhs._chatID {return false} - if lhs._mute != rhs._mute {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_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_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() { @@ -4251,7 +5298,9 @@ 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) }() + 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 } } @@ -4265,27 +5314,35 @@ extension Flipcash_Chat_V1_MuteChatResponse: SwiftProtobuf.Message, SwiftProtobu 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) + 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_MuteChatResponse, rhs: Flipcash_Chat_V1_MuteChatResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_GetLobbyMembersResponse, rhs: Flipcash_Chat_V1_GetLobbyMembersResponse) -> Bool { if lhs.result != rhs.result {return false} - if lhs._viewerState != rhs._viewerState {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_MuteChatResponse.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_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") +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() { @@ -4294,6 +5351,8 @@ extension Flipcash_Chat_V1_UnmuteChatRequest: SwiftProtobuf.Message, SwiftProtob // 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._userID) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._keyEnvelope) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -4308,23 +5367,31 @@ extension Flipcash_Chat_V1_UnmuteChatRequest: SwiftProtobuf.Message, SwiftProtob 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._keyEnvelope { + 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_UnmuteChatRequest, rhs: Flipcash_Chat_V1_UnmuteChatRequest) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_AdmitLobbyMemberRequest, rhs: Flipcash_Chat_V1_AdmitLobbyMemberRequest) -> Bool { if lhs._chatID != rhs._chatID {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_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_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() { @@ -4333,41 +5400,32 @@ 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) }() 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_UnmuteChatResponse, rhs: Flipcash_Chat_V1_UnmuteChatResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_AdmitLobbyMemberResponse, rhs: Flipcash_Chat_V1_AdmitLobbyMemberResponse) -> 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_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_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") +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() { @@ -4376,6 +5434,7 @@ extension Flipcash_Chat_V1_EnterLobbyRequest: SwiftProtobuf.Message, SwiftProtob // 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._userID) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -4390,23 +5449,27 @@ extension Flipcash_Chat_V1_EnterLobbyRequest: SwiftProtobuf.Message, SwiftProtob 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_EnterLobbyRequest, rhs: Flipcash_Chat_V1_EnterLobbyRequest) -> Bool { + 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_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") +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() { @@ -4415,41 +5478,32 @@ extension Flipcash_Chat_V1_EnterLobbyResponse: 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._lobby) }() 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._lobby { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_EnterLobbyResponse, rhs: Flipcash_Chat_V1_EnterLobbyResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_DenyLobbyMemberResponse, rhs: Flipcash_Chat_V1_DenyLobbyMemberResponse) -> Bool { if lhs.result != rhs.result {return false} - if lhs._lobby != rhs._lobby {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -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\u{1}BANNED\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_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") +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() { @@ -4458,6 +5512,7 @@ extension Flipcash_Chat_V1_LeaveLobbyRequest: SwiftProtobuf.Message, SwiftProtob // 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._keyEnvelope) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -4472,22 +5527,26 @@ extension Flipcash_Chat_V1_LeaveLobbyRequest: SwiftProtobuf.Message, SwiftProtob try { if let v = self._chatID { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() + try { if let v = self._keyEnvelope { + 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_LeaveLobbyRequest, rhs: Flipcash_Chat_V1_LeaveLobbyRequest) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_SetKeyEnvelopeRequest, rhs: Flipcash_Chat_V1_SetKeyEnvelopeRequest) -> Bool { if lhs._chatID != rhs._chatID {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_LeaveLobbyResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".LeaveLobbyResponse" +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 { @@ -4509,20 +5568,20 @@ extension Flipcash_Chat_V1_LeaveLobbyResponse: SwiftProtobuf.Message, SwiftProto try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_LeaveLobbyResponse, rhs: Flipcash_Chat_V1_LeaveLobbyResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_SetKeyEnvelopeResponse, rhs: Flipcash_Chat_V1_SetKeyEnvelopeResponse) -> Bool { if lhs.result != rhs.result {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -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_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_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") +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 { while let fieldNumber = try decoder.nextFieldNumber() { @@ -4531,7 +5590,6 @@ extension Flipcash_Chat_V1_GetLobbyMembersRequest: SwiftProtobuf.Message, SwiftP // 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 } @@ -4546,27 +5604,23 @@ extension Flipcash_Chat_V1_GetLobbyMembersRequest: SwiftProtobuf.Message, SwiftP 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_GetLobbyMembersRequest, rhs: Flipcash_Chat_V1_GetLobbyMembersRequest) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_GetKeyEnvelopeRequest, rhs: Flipcash_Chat_V1_GetKeyEnvelopeRequest) -> 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_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") +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\u{3}wrapped_by_public_key\0\u{3}invite_key_bundle\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -4575,9 +5629,34 @@ extension Flipcash_Chat_V1_GetLobbyMembersResponse: SwiftProtobuf.Message, Swift // 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) }() + case 2: try { + var v: Flipcash_Chat_V1_KeyEnvelope? + var hadOneofValue = false + if let current = self.envelope { + hadOneofValue = true + if case .keyEnvelope(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.envelope = .keyEnvelope(v) + } + }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._wrappedBy) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._wrappedByPublicKey) }() + case 5: try { + var v: Flipcash_Chat_V1_GetKeyEnvelopeResponse.InviteKeyBundle? + var hadOneofValue = false + if let current = self.envelope { + hadOneofValue = true + if case .inviteKeyBundle(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.envelope = .inviteKeyBundle(v) + } + }() default: break } } @@ -4591,35 +5670,82 @@ extension Flipcash_Chat_V1_GetLobbyMembersResponse: SwiftProtobuf.Message, Swift 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 { if case .keyEnvelope(let v)? = self.envelope { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = self._wrappedBy { try visitor.visitSingularMessageField(value: v, fieldNumber: 3) } }() - if self.hasMore_p != false { - try visitor.visitSingularBoolField(value: self.hasMore_p, fieldNumber: 4) - } + try { if let v = self._wrappedByPublicKey { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + try { if case .inviteKeyBundle(let v)? = self.envelope { + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + } }() try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_GetLobbyMembersResponse, rhs: Flipcash_Chat_V1_GetLobbyMembersResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_GetKeyEnvelopeResponse, rhs: Flipcash_Chat_V1_GetKeyEnvelopeResponse) -> 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.envelope != rhs.envelope {return false} + if lhs._wrappedBy != rhs._wrappedBy {return false} + if lhs._wrappedByPublicKey != rhs._wrappedByPublicKey {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -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_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") } -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") +extension Flipcash_Chat_V1_GetKeyEnvelopeResponse.InviteKeyBundle: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Chat_V1_GetKeyEnvelopeResponse.protoMessageName + ".InviteKeyBundle" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}invite_id\0\u{3}key_envelope\0\u{3}secret_envelope\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._inviteID) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._keyEnvelope) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._secretEnvelope) }() + 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._inviteID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._keyEnvelope { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = self._secretEnvelope { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_GetKeyEnvelopeResponse.InviteKeyBundle, rhs: Flipcash_Chat_V1_GetKeyEnvelopeResponse.InviteKeyBundle) -> Bool { + if lhs._inviteID != rhs._inviteID {return false} + if lhs._keyEnvelope != rhs._keyEnvelope {return false} + if lhs._secretEnvelope != rhs._secretEnvelope {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_CreateInviteRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".CreateInviteRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{3}invite_token\0\u{3}key_envelope\0\u{3}secret_envelope\0\u{3}max_uses\0\u{3}expires_at\0\u{2}\u{4}auth\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -4628,8 +5754,11 @@ extension Flipcash_Chat_V1_AdmitLobbyMemberRequest: SwiftProtobuf.Message, Swift // 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._userID) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._inviteToken) }() case 3: try { try decoder.decodeSingularMessageField(value: &self._keyEnvelope) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._secretEnvelope) }() + case 5: try { try decoder.decodeSingularUInt32Field(value: &self.maxUses) }() + case 6: try { try decoder.decodeSingularMessageField(value: &self._expiresAt) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -4644,31 +5773,43 @@ extension Flipcash_Chat_V1_AdmitLobbyMemberRequest: SwiftProtobuf.Message, Swift try { if let v = self._chatID { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() - try { if let v = self._userID { + try { if let v = self._inviteToken { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() try { if let v = self._keyEnvelope { try visitor.visitSingularMessageField(value: v, fieldNumber: 3) } }() + try { if let v = self._secretEnvelope { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + if self.maxUses != 0 { + try visitor.visitSingularUInt32Field(value: self.maxUses, fieldNumber: 5) + } + try { if let v = self._expiresAt { + try visitor.visitSingularMessageField(value: v, fieldNumber: 6) + } }() 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_AdmitLobbyMemberRequest, rhs: Flipcash_Chat_V1_AdmitLobbyMemberRequest) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_CreateInviteRequest, rhs: Flipcash_Chat_V1_CreateInviteRequest) -> Bool { if lhs._chatID != rhs._chatID {return false} - if lhs._userID != rhs._userID {return false} + if lhs._inviteToken != rhs._inviteToken {return false} if lhs._keyEnvelope != rhs._keyEnvelope {return false} + if lhs._secretEnvelope != rhs._secretEnvelope {return false} + if lhs.maxUses != rhs.maxUses {return false} + if lhs._expiresAt != rhs._expiresAt {return false} if lhs._auth != rhs._auth {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -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") +extension Flipcash_Chat_V1_CreateInviteResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".CreateInviteResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}invite\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -4677,32 +5818,41 @@ extension Flipcash_Chat_V1_AdmitLobbyMemberResponse: 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.decodeSingularMessageField(value: &self._invite) }() 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._invite { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_AdmitLobbyMemberResponse, rhs: Flipcash_Chat_V1_AdmitLobbyMemberResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_CreateInviteResponse, rhs: Flipcash_Chat_V1_CreateInviteResponse) -> Bool { if lhs.result != rhs.result {return false} + if lhs._invite != rhs._invite {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -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_CreateInviteResponse.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}INVALID_EXPIRES_AT\0\u{1}TOO_MANY_INVITES\0\u{1}INVALID_SIGNATURE\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") +extension Flipcash_Chat_V1_GetInviteRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetInviteRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}invite_token\0\u{2}\u{9}auth\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -4710,8 +5860,7 @@ extension Flipcash_Chat_V1_DenyLobbyMemberRequest: SwiftProtobuf.Message, SwiftP // 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._userID) }() + case 1: try { try decoder.decodeSingularMessageField(value: &self._inviteToken) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -4723,30 +5872,26 @@ extension Flipcash_Chat_V1_DenyLobbyMemberRequest: SwiftProtobuf.Message, SwiftP // 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 { if let v = self._inviteToken { 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_DenyLobbyMemberRequest, rhs: Flipcash_Chat_V1_DenyLobbyMemberRequest) -> Bool { - if lhs._chatID != rhs._chatID {return false} - if lhs._userID != rhs._userID {return false} + public static func ==(lhs: Flipcash_Chat_V1_GetInviteRequest, rhs: Flipcash_Chat_V1_GetInviteRequest) -> Bool { + if lhs._inviteToken != rhs._inviteToken {return false} if lhs._auth != rhs._auth {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -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") +extension Flipcash_Chat_V1_GetInviteResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetInviteResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}chat\0\u{1}status\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -4755,32 +5900,46 @@ extension Flipcash_Chat_V1_DenyLobbyMemberResponse: SwiftProtobuf.Message, Swift // 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.status) }() 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.status != .unknown { + try visitor.visitSingularEnumField(value: self.status, fieldNumber: 3) + } try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_DenyLobbyMemberResponse, rhs: Flipcash_Chat_V1_DenyLobbyMemberResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_GetInviteResponse, rhs: Flipcash_Chat_V1_GetInviteResponse) -> Bool { if lhs.result != rhs.result {return false} + if lhs._chat != rhs._chat {return false} + if lhs.status != rhs.status {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -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_GetInviteResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}NOT_FOUND\0\u{1}BANNED\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") +extension Flipcash_Chat_V1_GetChatInvitesRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetChatInvitesRequest" + 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() { @@ -4789,7 +5948,7 @@ extension Flipcash_Chat_V1_SetKeyEnvelopeRequest: SwiftProtobuf.Message, SwiftPr // 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._keyEnvelope) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._queryOptions) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -4804,7 +5963,7 @@ extension Flipcash_Chat_V1_SetKeyEnvelopeRequest: SwiftProtobuf.Message, SwiftPr try { if let v = self._chatID { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() - try { if let v = self._keyEnvelope { + try { if let v = self._queryOptions { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() try { if let v = self._auth { @@ -4813,18 +5972,18 @@ extension Flipcash_Chat_V1_SetKeyEnvelopeRequest: SwiftProtobuf.Message, SwiftPr try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_SetKeyEnvelopeRequest, rhs: Flipcash_Chat_V1_SetKeyEnvelopeRequest) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_GetChatInvitesRequest, rhs: Flipcash_Chat_V1_GetChatInvitesRequest) -> Bool { if lhs._chatID != rhs._chatID {return false} - if lhs._keyEnvelope != rhs._keyEnvelope {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_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") +extension Flipcash_Chat_V1_GetChatInvitesResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetChatInvitesResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}invites\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() { @@ -4833,32 +5992,51 @@ extension Flipcash_Chat_V1_SetKeyEnvelopeResponse: SwiftProtobuf.Message, SwiftP // 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.invites) }() + 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.invites.isEmpty { + try visitor.visitRepeatedMessageField(value: self.invites, 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_SetKeyEnvelopeResponse, rhs: Flipcash_Chat_V1_SetKeyEnvelopeResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_GetChatInvitesResponse, rhs: Flipcash_Chat_V1_GetChatInvitesResponse) -> Bool { if lhs.result != rhs.result {return false} + if lhs.invites != rhs.invites {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_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_GetChatInvitesResponse.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_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") +extension Flipcash_Chat_V1_RevokeInviteRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".RevokeInviteRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{3}invite_id\0\u{2}\u{8}auth\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -4867,6 +6045,7 @@ extension Flipcash_Chat_V1_GetKeyEnvelopeRequest: SwiftProtobuf.Message, SwiftPr // 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._inviteID) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -4881,23 +6060,27 @@ extension Flipcash_Chat_V1_GetKeyEnvelopeRequest: SwiftProtobuf.Message, SwiftPr try { if let v = self._chatID { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() + try { if let v = self._inviteID { + 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_GetKeyEnvelopeRequest, rhs: Flipcash_Chat_V1_GetKeyEnvelopeRequest) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_RevokeInviteRequest, rhs: Flipcash_Chat_V1_RevokeInviteRequest) -> Bool { if lhs._chatID != rhs._chatID {return false} + if lhs._inviteID != rhs._inviteID {return false} if lhs._auth != rhs._auth {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -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") +extension Flipcash_Chat_V1_RevokeInviteResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".RevokeInviteResponse" + 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() { @@ -4906,41 +6089,27 @@ extension Flipcash_Chat_V1_GetKeyEnvelopeResponse: SwiftProtobuf.Message, SwiftP // 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._keyEnvelope) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._wrappedBy) }() 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._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_GetKeyEnvelopeResponse, rhs: Flipcash_Chat_V1_GetKeyEnvelopeResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_RevokeInviteResponse, rhs: Flipcash_Chat_V1_RevokeInviteResponse) -> Bool { if lhs.result != rhs.result {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_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") +extension Flipcash_Chat_V1_RevokeInviteResponse.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_SetFeaturedGroupsRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { diff --git a/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift b/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift index 19065cc..0300fef 100644 --- a/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift +++ b/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift @@ -173,7 +173,7 @@ public struct Flipcash_Chat_V1_Metadata: @unchecked Sendable { /// Rules governing participation in this chat. Only supported for group /// 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. + /// creator's approval or an invite instead. public var rules: Flipcash_Chat_V1_Rules { get {return _storage._rules ?? Flipcash_Chat_V1_Rules()} set {_uniqueStorage()._rules = newValue} @@ -208,9 +208,10 @@ public struct Flipcash_Chat_V1_Metadata: @unchecked Sendable { /// 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 + /// A private group has no rules. A user joins it in one of two ways: they + /// enter its lobby with Chat.EnterLobby and are admitted by its creator, + /// or they join with Chat.JoinChat carrying an invite the creator made + /// (see Invite). 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). @@ -1256,6 +1257,14 @@ public struct Flipcash_Chat_V1_ViewerState: Sendable { /// Clears the value of `moderation`. Subsequent reads from it will return its default value. public mutating func clearModeration() {self._moderation = nil} + /// Whether the viewer may call Chat.CreateInvite, Chat.GetChatInvites + /// and Chat.RevokeInvite on this chat. Today that is a private group's + /// creator while a member, and no one else: false for every other + /// member, and for every chat that is not a private group. Granting + /// it to anyone else would let them hand joiners a wrong chat key (see + /// Invite). + public var canInvite: Bool = false + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -1590,15 +1599,49 @@ public struct Flipcash_Chat_V1_IdempotencyKey: Sendable { /// 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. +/// over the one the server reports with the envelope (wrapped_by_public_key +/// in Chat.GetKeyEnvelope's response). 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. /// +/// A member who joins with an invite (Chat.JoinChat) is given, along with +/// their membership, the invite's copy of the chat key (see +/// InviteKeyEnvelope) in place of an envelope the creator wrapped. +/// Chat.GetKeyEnvelope returns it until they replace it: they check its +/// signature, open it with the link's secret, wrap the chat key for +/// themself and store that envelope with Chat.SetKeyEnvelope, as a member +/// admitted from the lobby does. Until they have, opening their key depends +/// on the link's secret, which they encrypted for themself when joining (see +/// InviteSecretEnvelope) and which is stored and returned with the copy, so +/// any of their devices can do this, whether or not it holds the link. +/// +/// The copy carries the creator's signature, which the member checks against +/// the creator's public key, preferring one learned outside this service as +/// above. So the copy holds as far as an envelope the creator wrapped does: +/// substituting a chat key in it takes the creator's private key, or the +/// server reporting a false public key for the creator. A member whose copy +/// fails that check, or fails to open, treats it as the wrong key. +/// +/// A member whose chat's messages do not open under their key treats it as +/// the wrong key, warns the user and does not send in the chat. That catches +/// a key made wrong by mistake, but is no defence against the server, which +/// decides what messages a member is shown and who sent them. +/// +/// A member holding the wrong key, or an invite's copy none of their devices +/// can open, has one way to repair it: leave the chat +/// (Chat.LeaveChat), which discards their envelope, then enter its lobby +/// (Chat.EnterLobby) and be admitted by the creator, who wraps the chat key +/// for them. Nothing else gives them another key: Chat.SetKeyEnvelope keeps an +/// envelope they wrapped themself (ALREADY_SET), Chat.AdmitLobbyMember leaves +/// a member's envelope as it is, and joining with the same invite again gives +/// the same copy. An invite whose copy holds the wrong key should be revoked +/// (Chat.RevokeInvite). +/// /// 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 @@ -1804,9 +1847,9 @@ public struct Flipcash_Chat_V1_LobbyUpdate: Sendable { } /// 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. + /// were denied (Chat.DenyLobbyMember), were admitted + /// (Chat.AdmitLobbyMember) or joined with an invite (Chat.JoinChat). + /// Either of the last two 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 @@ -1844,6 +1887,490 @@ public struct Flipcash_Chat_V1_LobbyUpdateBatch: Sendable { public init() {} } +/// InviteId names an invite to a private group (see Invite). Clients derive +/// it from the invite's link; it is not secret, and does not let anyone join. +public struct Flipcash_Chat_V1_InviteId: 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 value: Data = Data() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +/// InviteToken proves its sender holds an invite's link (see Invite). Clients +/// derive it from the link's secret and send it only to create the invite +/// (Chat.CreateInvite), preview it (Chat.GetInvite), read the chat's roster +/// with it (Chat.GetRoster) or join with it (Chat.JoinChat). The server never +/// stores it, in its records or its logs: anyone who reads a token can join +/// with the invite and use up its limit, and read the roster without an +/// account. +public struct Flipcash_Chat_V1_InviteToken: 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 value: Data = Data() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +/// Invite is a link that lets users join a private group (see +/// Metadata.is_private) without its creator's approval, up to a number of +/// joins or without limit. It is created with Chat.CreateInvite by a +/// member the server permits to invite (see +/// ViewerState.Permissions.can_invite), which today is the group's creator +/// alone. An invite admits whoever holds the link, including a user the +/// creator denied from the lobby (Chat.DenyLobbyMember). +/// +/// Should the server ever let members other than the creator invite, such a +/// member's invites must stop admitting anyone once they are banned, leave +/// the chat, or otherwise lose can_invite, and those permitted to ban +/// (ViewerState.Permissions.moderation.can_ban) must be able to revoke any +/// invite. That rule is for them alone: the chat's creator also loses +/// can_invite while not a member, but their invites keep working (see +/// below). +/// +/// Letting other members invite would also weaken what a joiner can trust: +/// today only the creator can hand out the chat key, but a member permitted +/// to invite could give the users who join with their invite a wrong key, +/// which the signature on the copy (see InviteKeyEnvelope) cannot catch, +/// since it proves only who made the copy. Clients would then also need a way +/// to check a key against one the creator vouches for. +/// +/// A user joins with an invite with Chat.JoinChat, which in one step makes +/// them a member, counts their use of the invite, and stores the invite's +/// copy of the chat key (see InviteKeyEnvelope) as their key envelope, with +/// the link's secret the joiner encrypted for themself (see +/// InviteSecretEnvelope). Any of their devices then fetches both with +/// Chat.GetKeyEnvelope, checks the copy's signature, opens the secret and +/// then the copy, wraps the chat key for the joiner and stores that envelope +/// with Chat.SetKeyEnvelope, as a member admitted from the lobby does (see +/// KeyEnvelope). The server gives the copy to members alone, so it reaches no +/// more users than the invite's limit allows. +/// +/// The link carries one secret: 32 random bytes the inviter's client +/// generates for this invite alone. It travels in the link's URL fragment, +/// which a browser never sends in a request, and the server never learns it. +/// A web page that a link opens can still read the fragment, so such a page +/// must never send it anywhere or log it. It is not the chat key. Every +/// client derives three values from it, with HKDF-SHA256 (RFC 5869): +/// +/// invite_token = HKDF(ikm = secret, +/// salt = "", +/// info = "flipcash-group-invite-token-v1", +/// L = 32) +/// wrap_key = HKDF(ikm = secret, +/// salt = "", +/// info = "flipcash-group-invite-wrap-v1" || chat_id, +/// L = 32) +/// invite_id = SHA-256("flipcash-group-invite-id-v1" || invite_token)[:16] +/// +/// where chat_id is the raw bytes of common.v1.ChatId.value. The link holds +/// no chat ID, so a joiner learns it from Chat.GetInvite and derives the wrap +/// key only then. +/// +/// - The invite token (InviteToken) proves the caller holds the link. The +/// server finds the invite by computing its ID from the token, so neither +/// the server's records nor anything it returns lets anyone join. +/// - The wrap key opens the invite's copy of the chat key (see +/// InviteKeyEnvelope). It is never sent anywhere, so the token does not +/// let anyone open the copy. Binding the chat ID into it makes the copy +/// open only as the key of the chat it was made for, so the server cannot +/// steer a joiner into another chat. +/// - The invite ID (InviteId) names the invite. The server computes it from +/// the token whenever one is sent, creation included, and the inviter +/// lists and revokes the invite by it. +/// +/// The limit counts joins: each join with the invite that makes someone a +/// member uses one, so a user who leaves and joins again with it uses +/// another, and leaving gives no use back. A retry of a join that succeeded +/// uses none, since the caller is a member by then (see Chat.JoinChat). The +/// server does not record who joined with which invite. An invite admits +/// anyone, those who joined with it before included, +/// only while it is ACTIVE: once it is used up, expires or is revoked, it +/// admits no one, and a user returning to the chat is admitted from the lobby +/// instead. The users it already admitted stay members. Nothing about the +/// chat's creator's own standing stops an invite they made: it keeps working +/// while they are not a member, though they cannot list or revoke it until +/// they rejoin. +/// +/// An invite never changes the chat key. Revoking a link that leaked keeps +/// new users out, but cannot take the key from anyone who already joined +/// with it. +/// +/// While an invite is ACTIVE, its link also shows who is in the chat to +/// whoever holds it, with no account needed: the token alone previews the +/// invite (Chat.GetInvite) and reads the chat's roster (Chat.GetRoster), so +/// that a web page the link opens can show both before anyone joins. A ban +/// does not stop a user who holds the link from reading the roster; revoking +/// the invite stops everyone. +/// +/// The link and the server's records together are as good as the chat key: +/// anyone holding the link who can read what the server stores can open a +/// copy of the chat key (see InviteKeyEnvelope), and with it every message +/// in the chat, without ever joining. The server deletes each copy once it +/// is no longer needed (see InviteKeyEnvelope), which bounds this but does +/// not end it. A copy that opens with the link is still stored: +/// - while the invite is ACTIVE, which is inherent, since anyone holding +/// the link could join with it anyway; +/// - for each member who joined with the invite and has not yet replaced +/// their copy, for as long as that takes, whether or not the invite has +/// since stopped admitting anyone; +/// - in the server's backups, which deleting a copy does not reach. +/// So a link is to be treated like the chat key, and shared only with those +/// who may read the chat. +/// +/// A ban (Chat.BanUser) applies to one account, not to a link. A banned user +/// who still holds a link that admits users can join with it from another +/// account, so a creator banning someone who joined with an invite +/// should revoke that invite too (Chat.RevokeInvite), and make another for +/// everyone else. +public struct Flipcash_Chat_V1_Invite: 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 inviteID: Flipcash_Chat_V1_InviteId { + get {return _inviteID ?? Flipcash_Chat_V1_InviteId()} + set {_inviteID = newValue} + } + /// Returns true if `inviteID` has been explicitly set. + public var hasInviteID: Bool {return self._inviteID != nil} + /// Clears the value of `inviteID`. Subsequent reads from it will return its default value. + public mutating func clearInviteID() {self._inviteID = nil} + + /// The private group the invite admits users to. + 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 that created the invite. + public var createdBy: Flipcash_Common_V1_UserId { + get {return _createdBy ?? Flipcash_Common_V1_UserId()} + set {_createdBy = newValue} + } + /// Returns true if `createdBy` has been explicitly set. + public var hasCreatedBy: Bool {return self._createdBy != nil} + /// Clears the value of `createdBy`. Subsequent reads from it will return its default value. + public mutating func clearCreatedBy() {self._createdBy = nil} + + /// How many joins the invite allows (see Invite). 0 means unlimited. + public var maxUses: UInt32 = 0 + + /// How many joins the invite has admitted, whether or not those users are + /// still members. Never decreases. + public var useCount: UInt32 = 0 + + /// When the invite was created. + public var createdAt: SwiftProtobuf.Google_Protobuf_Timestamp { + get {return _createdAt ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + set {_createdAt = newValue} + } + /// Returns true if `createdAt` has been explicitly set. + public var hasCreatedAt: Bool {return self._createdAt != nil} + /// Clears the value of `createdAt`. Subsequent reads from it will return its default value. + public mutating func clearCreatedAt() {self._createdAt = nil} + + /// When the invite stops admitting anyone. Unset when it never expires. + public var expiresAt: SwiftProtobuf.Google_Protobuf_Timestamp { + get {return _expiresAt ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + set {_expiresAt = newValue} + } + /// Returns true if `expiresAt` has been explicitly set. + public var hasExpiresAt: Bool {return self._expiresAt != nil} + /// Clears the value of `expiresAt`. Subsequent reads from it will return its default value. + public mutating func clearExpiresAt() {self._expiresAt = nil} + + /// What the invite still allows. When more than one reason applies, the + /// status is the first of REVOKED, EXPIRED and EXHAUSTED. + public var status: Flipcash_Chat_V1_Invite.Status = .unknown + + /// The link's secret, encrypted so that only the invite's creator can open + /// it, so that any of their devices can rebuild the link to share it again. + public var secretEnvelope: Flipcash_Chat_V1_InviteSecretEnvelope { + get {return _secretEnvelope ?? Flipcash_Chat_V1_InviteSecretEnvelope()} + set {_secretEnvelope = newValue} + } + /// Returns true if `secretEnvelope` has been explicitly set. + public var hasSecretEnvelope: Bool {return self._secretEnvelope != nil} + /// Clears the value of `secretEnvelope`. Subsequent reads from it will return its default value. + public mutating func clearSecretEnvelope() {self._secretEnvelope = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Status: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case unknown // = 0 + + /// The invite admits users + case active // = 1 + + /// It has admitted max_uses joins; it admits no one + case exhausted // = 2 + + /// expires_at has passed; it admits no one + case expired // = 3 + + /// It was revoked (see Chat.RevokeInvite); it admits no one + case revoked // = 4 + case UNRECOGNIZED(Int) + + public init() { + self = .unknown + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .active + case 2: self = .exhausted + case 3: self = .expired + case 4: self = .revoked + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .unknown: return 0 + case .active: return 1 + case .exhausted: return 2 + case .expired: return 3 + case .revoked: return 4 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Chat_V1_Invite.Status] = [ + .unknown, + .active, + .exhausted, + .expired, + .revoked, + ] + + } + + public init() {} + + fileprivate var _inviteID: Flipcash_Chat_V1_InviteId? = nil + fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil + fileprivate var _createdBy: Flipcash_Common_V1_UserId? = nil + fileprivate var _createdAt: SwiftProtobuf.Google_Protobuf_Timestamp? = nil + fileprivate var _expiresAt: SwiftProtobuf.Google_Protobuf_Timestamp? = nil + fileprivate var _secretEnvelope: Flipcash_Chat_V1_InviteSecretEnvelope? = nil +} + +/// InviteKeyEnvelope is an invite's copy of a private group's chat key, +/// encrypted under the invite's wrap key (see Invite), so that anyone holding +/// the link can open it and nobody else can, the server included, and signed +/// by the user who created the invite, so that a joiner can tell it came from +/// them. The inviter makes it when creating the invite (Chat.CreateInvite). +/// Joining with the invite (Chat.JoinChat) stores it as the joiner's key +/// envelope, which Chat.GetKeyEnvelope returns +/// (GetKeyEnvelopeResponse.invite_key_bundle), with the link's secret the +/// member encrypted for themself (see InviteSecretEnvelope), until the member +/// checks its signature, opens it, wraps the chat key for themself and stores +/// that envelope with Chat.SetKeyEnvelope (see KeyEnvelope). +/// +/// The server deletes copies it no longer needs, since anyone holding the +/// link who can read them can open them (see Invite). It deletes the +/// invite's own copy once the invite stops being ACTIVE: in the same step +/// that revokes it (Chat.RevokeInvite) or counts its last use +/// (Chat.JoinChat), and at or soon after expires_at for one that expires. +/// It deletes a member's copy, and the secret envelope stored with it, in +/// the same step that stores the envelope replacing them +/// (Chat.SetKeyEnvelope). A member who never replaces their copy keeps it, +/// whatever becomes of the invite, since it is their only key. +/// +/// For scheme XCHACHA20POLY1305, encrypt with XChaCha20-Poly1305 (the IETF +/// construction in libsodium's crypto_aead_xchacha20poly1305_ietf_encrypt): +/// key = the invite's wrap key +/// nonce = 24 fresh random bytes, set in `nonce` below +/// plaintext = the 32-byte chat key +/// aad = "flipcash-group-invite-wrap-v1" || chat_id || invite_id +/// where chat_id and invite_id are the raw bytes of common.v1.ChatId.value and +/// InviteId.value. The ciphertext, with its 16-byte Poly1305 tag appended, is +/// set in `ciphertext` below. +/// +/// The inviter then signs the copy with the Ed25519 private key of their +/// account: +/// message = "flipcash-group-invite-sig-v1" || chat_id || invite_id +/// || nonce || ciphertext +/// signature = Ed25519-Sign(inviter_private_key, message), set in +/// `signature` below +/// with chat_id and invite_id as above. The inviter is Invite.created_by, +/// reported to a member holding the copy as GetKeyEnvelopeResponse.wrapped_by. +/// Today only the chat's creator may invite (see +/// ViewerState.Permissions.can_invite), so the member first checks that +/// wrapped_by is Metadata.creator, and rejects the copy otherwise: without +/// that check, a server could name an account it controls as the inviter and +/// sign with that account's key. The member then verifies the signature +/// against the inviter's public key (wrapped_by_public_key in the same +/// response, or one learned outside this service, which it prefers), and +/// only then opens the copy. +/// +/// Only a holder of the link's secret can derive the wrap key, so a copy that +/// opens was made by one, and only the creator can sign it. Neither the +/// server, even with a link it made itself, nor anyone else holding the link +/// can substitute a chat key of its choosing, except by the server reporting +/// a false public key for the creator, the trust every KeyEnvelope rests on +/// too. A client rejects a copy whose inviter is not the creator, whose +/// signature does not verify or that fails to open, treats it as the wrong +/// key (see KeyEnvelope), and does not send in the chat. Since the copy is +/// given only on joining, a user who joined with a forged link is a member +/// by the time their client finds this out. +public struct Flipcash_Chat_V1_InviteKeyEnvelope: 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 a copy they cannot + /// open. + public var scheme: Flipcash_Chat_V1_InviteKeyEnvelope.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() + + /// The inviter's Ed25519 signature over the copy, as described above. + public var signature: Data = Data() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Scheme: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case unknown // = 0 + case xchacha20Poly1305 // = 1 + case UNRECOGNIZED(Int) + + public init() { + self = .unknown + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .xchacha20Poly1305 + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .unknown: return 0 + case .xchacha20Poly1305: 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_InviteKeyEnvelope.Scheme] = [ + .unknown, + .xchacha20Poly1305, + ] + + } + + public init() {} +} + +/// InviteSecretEnvelope is an invite link's secret (see Invite), encrypted by +/// a user for themself, so that only they can open it, on any of their +/// devices, and the server cannot. Two users make one: +/// +/// - The user who creates the invite (Invite.created_by, not necessarily +/// the chat's creator), with Chat.CreateInvite. The server returns it with +/// the invite (Chat.GetChatInvites), so that any of their devices can +/// rebuild the link to share it again. +/// - A user joining with the invite, with Chat.JoinChat. The server stores +/// it with the invite's copy of the chat key and returns it with the copy +/// (Chat.GetKeyEnvelope), so that any of their devices can open the copy +/// until they replace it with an envelope of their own. +/// +/// "Owner" below is the user who made it. For scheme +/// X25519_XCHACHA20POLY1305, it is made exactly as a KeyEnvelope a user wraps +/// for themself, with the owner as both wrapper and recipient (so the peer +/// key in step 1 is the owner's own public key), and these values in place of +/// the KeyEnvelope ones: +/// info (step 3) = "flipcash-group-invite-secret-v1" || chat_id +/// || invite_id +/// plaintext (step 4) = the link's 32-byte secret +/// aad (step 4) = "flipcash-group-invite-secret-v1" || chat_id +/// || invite_id || owner_pk || owner_pk +/// where chat_id and invite_id are the raw bytes of common.v1.ChatId.value and +/// InviteId.value, and owner_pk is the owner's 32-byte Ed25519 public key. So +/// it opens only as the secret of the invite and chat it was made for. +public struct Flipcash_Chat_V1_InviteSecretEnvelope: 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_InviteSecretEnvelope.Scheme = .unknown + + /// The random XChaCha20-Poly1305 nonce, unique per envelope. + public var nonce: Data = Data() + + /// The encrypted 32-byte secret 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_InviteSecretEnvelope.Scheme] = [ + .unknown, + .x25519Xchacha20Poly1305, + ] + + } + + public init() {} +} + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "flipcash.chat.v1" @@ -3368,7 +3895,7 @@ extension Flipcash_Chat_V1_ViewerState.Settings: SwiftProtobuf.Message, SwiftPro extension Flipcash_Chat_V1_ViewerState.Permissions: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = Flipcash_Chat_V1_ViewerState.protoMessageName + ".Permissions" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}can_edit\0\u{1}moderation\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}can_edit\0\u{1}moderation\0\u{3}can_invite\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3378,6 +3905,7 @@ extension Flipcash_Chat_V1_ViewerState.Permissions: SwiftProtobuf.Message, Swift switch fieldNumber { case 1: try { try decoder.decodeSingularBoolField(value: &self.canEdit) }() case 2: try { try decoder.decodeSingularMessageField(value: &self._moderation) }() + case 3: try { try decoder.decodeSingularBoolField(value: &self.canInvite) }() default: break } } @@ -3394,12 +3922,16 @@ extension Flipcash_Chat_V1_ViewerState.Permissions: SwiftProtobuf.Message, Swift try { if let v = self._moderation { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() + if self.canInvite != false { + try visitor.visitSingularBoolField(value: self.canInvite, fieldNumber: 3) + } try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: Flipcash_Chat_V1_ViewerState.Permissions, rhs: Flipcash_Chat_V1_ViewerState.Permissions) -> Bool { if lhs.canEdit != rhs.canEdit {return false} if lhs._moderation != rhs._moderation {return false} + if lhs.canInvite != rhs.canInvite {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -4132,3 +4664,234 @@ extension Flipcash_Chat_V1_LobbyUpdateBatch: SwiftProtobuf.Message, SwiftProtobu return true } } + +extension Flipcash_Chat_V1_InviteId: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".InviteId" + 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.decodeSingularBytesField(value: &self.value) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.value.isEmpty { + try visitor.visitSingularBytesField(value: self.value, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_InviteId, rhs: Flipcash_Chat_V1_InviteId) -> Bool { + if lhs.value != rhs.value {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_InviteToken: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".InviteToken" + 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.decodeSingularBytesField(value: &self.value) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.value.isEmpty { + try visitor.visitSingularBytesField(value: self.value, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_InviteToken, rhs: Flipcash_Chat_V1_InviteToken) -> Bool { + if lhs.value != rhs.value {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_Invite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".Invite" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}invite_id\0\u{3}chat_id\0\u{3}created_by\0\u{3}max_uses\0\u{3}use_count\0\u{3}created_at\0\u{3}expires_at\0\u{1}status\0\u{3}secret_envelope\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._inviteID) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._createdBy) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self.maxUses) }() + case 5: try { try decoder.decodeSingularUInt32Field(value: &self.useCount) }() + case 6: try { try decoder.decodeSingularMessageField(value: &self._createdAt) }() + case 7: try { try decoder.decodeSingularMessageField(value: &self._expiresAt) }() + case 8: try { try decoder.decodeSingularEnumField(value: &self.status) }() + case 9: try { try decoder.decodeSingularMessageField(value: &self._secretEnvelope) }() + 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._inviteID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._chatID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = self._createdBy { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + if self.maxUses != 0 { + try visitor.visitSingularUInt32Field(value: self.maxUses, fieldNumber: 4) + } + if self.useCount != 0 { + try visitor.visitSingularUInt32Field(value: self.useCount, fieldNumber: 5) + } + try { if let v = self._createdAt { + try visitor.visitSingularMessageField(value: v, fieldNumber: 6) + } }() + try { if let v = self._expiresAt { + try visitor.visitSingularMessageField(value: v, fieldNumber: 7) + } }() + if self.status != .unknown { + try visitor.visitSingularEnumField(value: self.status, fieldNumber: 8) + } + try { if let v = self._secretEnvelope { + try visitor.visitSingularMessageField(value: v, fieldNumber: 9) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_Invite, rhs: Flipcash_Chat_V1_Invite) -> Bool { + if lhs._inviteID != rhs._inviteID {return false} + if lhs._chatID != rhs._chatID {return false} + if lhs._createdBy != rhs._createdBy {return false} + if lhs.maxUses != rhs.maxUses {return false} + if lhs.useCount != rhs.useCount {return false} + if lhs._createdAt != rhs._createdAt {return false} + if lhs._expiresAt != rhs._expiresAt {return false} + if lhs.status != rhs.status {return false} + if lhs._secretEnvelope != rhs._secretEnvelope {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_Invite.Status: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNKNOWN\0\u{1}ACTIVE\0\u{1}EXHAUSTED\0\u{1}EXPIRED\0\u{1}REVOKED\0") +} + +extension Flipcash_Chat_V1_InviteKeyEnvelope: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".InviteKeyEnvelope" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}scheme\0\u{1}nonce\0\u{1}ciphertext\0\u{1}signature\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) }() + case 4: try { try decoder.decodeSingularBytesField(value: &self.signature) }() + 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) + } + if !self.signature.isEmpty { + try visitor.visitSingularBytesField(value: self.signature, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_InviteKeyEnvelope, rhs: Flipcash_Chat_V1_InviteKeyEnvelope) -> Bool { + if lhs.scheme != rhs.scheme {return false} + if lhs.nonce != rhs.nonce {return false} + if lhs.ciphertext != rhs.ciphertext {return false} + if lhs.signature != rhs.signature {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_InviteKeyEnvelope.Scheme: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNKNOWN\0\u{1}XCHACHA20POLY1305\0") +} + +extension Flipcash_Chat_V1_InviteSecretEnvelope: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".InviteSecretEnvelope" + 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_InviteSecretEnvelope, rhs: Flipcash_Chat_V1_InviteSecretEnvelope) -> 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_InviteSecretEnvelope.Scheme: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNKNOWN\0\u{1}X25519_XCHACHA20POLY1305\0") +} diff --git a/Sources/Flipcash2ClientProtocol/messaging_v1_model.pb.swift b/Sources/Flipcash2ClientProtocol/messaging_v1_model.pb.swift index 620882f..952575a 100644 --- a/Sources/Flipcash2ClientProtocol/messaging_v1_model.pb.swift +++ b/Sources/Flipcash2ClientProtocol/messaging_v1_model.pb.swift @@ -781,6 +781,14 @@ public struct Flipcash_Messaging_V1_SystemContent: Sendable { set {method = .admitted(newValue)} } + public var invited: Flipcash_Messaging_V1_SystemContent.UserJoined.Invited { + get { + if case .invited(let v)? = method {return v} + return Flipcash_Messaging_V1_SystemContent.UserJoined.Invited() + } + set {method = .invited(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() /// How they joined. Always set. A client that finds it unset (a way of @@ -788,6 +796,7 @@ public struct Flipcash_Messaging_V1_SystemContent: Sendable { public enum OneOf_Method: Equatable, Sendable { case direct(Flipcash_Messaging_V1_SystemContent.UserJoined.Direct) case admitted(Flipcash_Messaging_V1_SystemContent.UserJoined.Admitted) + case invited(Flipcash_Messaging_V1_SystemContent.UserJoined.Invited) } @@ -827,6 +836,30 @@ public struct Flipcash_Messaging_V1_SystemContent: Sendable { fileprivate var _admittedBy: Flipcash_Common_V1_UserId? = nil } + /// They joined with someone's invite, needing no one's approval (see + /// chat.v1.Invite). + public struct Invited: 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 created the invite + public var invitedBy: Flipcash_Common_V1_UserId { + get {return _invitedBy ?? Flipcash_Common_V1_UserId()} + set {_invitedBy = newValue} + } + /// Returns true if `invitedBy` has been explicitly set. + public var hasInvitedBy: Bool {return self._invitedBy != nil} + /// Clears the value of `invitedBy`. Subsequent reads from it will return its default value. + public mutating func clearInvitedBy() {self._invitedBy = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _invitedBy: Flipcash_Common_V1_UserId? = nil + } + public init() {} fileprivate var _userID: Flipcash_Common_V1_UserId? = nil @@ -2645,7 +2678,7 @@ extension Flipcash_Messaging_V1_SystemContent: SwiftProtobuf.Message, SwiftProto extension Flipcash_Messaging_V1_SystemContent.UserJoined: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = Flipcash_Messaging_V1_SystemContent.protoMessageName + ".UserJoined" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}user_id\0\u{1}direct\0\u{1}admitted\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}user_id\0\u{1}direct\0\u{1}admitted\0\u{1}invited\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2680,6 +2713,19 @@ extension Flipcash_Messaging_V1_SystemContent.UserJoined: SwiftProtobuf.Message, self.method = .admitted(v) } }() + case 4: try { + var v: Flipcash_Messaging_V1_SystemContent.UserJoined.Invited? + var hadOneofValue = false + if let current = self.method { + hadOneofValue = true + if case .invited(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.method = .invited(v) + } + }() default: break } } @@ -2702,6 +2748,10 @@ extension Flipcash_Messaging_V1_SystemContent.UserJoined: SwiftProtobuf.Message, guard case .admitted(let v)? = self.method else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 3) }() + case .invited?: try { + guard case .invited(let v)? = self.method else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) @@ -2768,6 +2818,40 @@ extension Flipcash_Messaging_V1_SystemContent.UserJoined.Admitted: SwiftProtobuf } } +extension Flipcash_Messaging_V1_SystemContent.UserJoined.Invited: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Messaging_V1_SystemContent.UserJoined.protoMessageName + ".Invited" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}invited_by\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._invitedBy) }() + 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._invitedBy { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Messaging_V1_SystemContent.UserJoined.Invited, rhs: Flipcash_Messaging_V1_SystemContent.UserJoined.Invited) -> Bool { + if lhs._invitedBy != rhs._invitedBy {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Flipcash_Messaging_V1_WidgetContent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".WidgetContent" public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}share_profile\0\u{1}poll\0\u{3}payment_request\0") diff --git a/flipcash2.lock b/flipcash2.lock index c961ee6..e12559e 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: b77bf78ff8d9ea1bcbf9aa3afa6696ba7edd37a9 -subject: Add chat moderation RPCs for group creators (#156) +commit: b655df1770f1d588736ee3dcb712d9be5ca2cf41 +subject: Add anonymous reads for group invite flows (#159) diff --git a/proto/chat/v1/chat_service.proto b/proto/chat/v1/chat_service.proto index 9b1e727..4f6dccb 100644 --- a/proto/chat/v1/chat_service.proto +++ b/proto/chat/v1/chat_service.proto @@ -12,6 +12,7 @@ import "common/v1/common.proto"; import "messaging/v1/model.proto"; import "moderation/v1/model.proto"; import "validate/validate.proto"; +import "google/protobuf/timestamp.proto"; service Chat { // GetChat returns the metadata for a specific chat @@ -25,8 +26,8 @@ service Chat { // 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 + // view_mode. Its public view is that record alone, with no messaging + // state. 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); @@ -107,9 +108,27 @@ service Chat { // carry none: group pointer advances are never broadcast, so a page of // them would be stale as soon as it was served. // - // Requires that the caller is a member of the chat. Anyone else is - // DENIED, including a non-member whom a group's listener rules admit to - // read its messages: who is in a chat is shown only to its members. + // Requires that the caller is a member of the chat, or holds a link to an + // invite to it (see Invite) and sends the invite's token in + // invite_token. Anyone else is DENIED, including a non-member whom a + // group's listener rules admit to read its messages: who is in a chat is + // shown only to its members and to those holding a link that would let + // them join it. + // + // The token is the credential, so auth is optional when it is sent (when + // set, auth must be valid). It is honored while its invite is ACTIVE and + // to chat_id; any other token is DENIED, as is a token sent for a public + // group or a DM. Whoever sends an honored token gets the page a member + // gets, registered or not, and a user the chat's creator banned + // included: a ban stops a user joining, not reading the roster through a + // link they hold, which the creator stops by revoking the invite. + // Reading the roster with a token counts no use of the invite, changes + // nothing, and is announced to no one. The invite is checked again on + // every page, so a walk ends with DENIED once it stops being ACTIVE. + // + // A member is served whether or not they send a token, which is not + // checked for them. A request with neither auth nor invite_token is + // DENIED. rpc GetRoster(GetRosterRequest) returns (GetRosterResponse); // SampleChatters returns a short sample of a public group's members to @@ -172,18 +191,49 @@ service Chat { // 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. + // A private group (see Metadata.is_private) is joined this way only with + // an invite (JoinChatRequest.invite) or by its creator. Anyone else is + // DENIED, and enters its lobby with EnterLobby to be admitted by the + // creator. The creator needs neither approval nor an invite, and rejoins + // a private group they left with this RPC; an invite they pass is + // ignored. + // + // Joining with an invite (see Invite) needs no one's approval. The client + // previews the invite with GetInvite, which names the chat, and passes + // the invite's token here. The invite must be ACTIVE (INVITE_EXHAUSTED, + // INVITE_EXPIRED or INVITE_REVOKED otherwise), for a user who joined + // with it before as for anyone. + // + // The join is atomic: the caller becomes a member, the join is counted as + // one use of the invite (a user who left and joins again with it uses + // another), and the invite's copy of the chat key is stored as their key + // envelope, with the link's secret encrypted by the caller for themself + // (JoinChatRequest.InviteParameters.secret_envelope), together, or + // nothing happens. A caller waiting in the chat's + // lobby leaves it in the same step. A caller who is already a member gets + // OK with nothing changed: the invite is not checked, no use is counted, + // and their stored envelope stays as it is. Only a public group's rules + // or a private group's invite are checked, never both: an invite to a + // public group is INVITE_NOT_FOUND. + // + // Any of the joiner's devices then fetches the copy and the secret with + // GetKeyEnvelope (GetKeyEnvelopeResponse.invite_key_bundle), checks + // the copy's signature, opens the secret with the joiner's account key + // and the copy with the secret, wraps the chat key for the joiner and + // stores that envelope with SetKeyEnvelope (see KeyEnvelope). None of + // this needs the device the joiner joined with, or the link itself. A + // join with an invite is announced like an admission (see + // AdmitLobbyMember). A member whose copy turns out to hold the wrong key + // cannot fix it by joining again: they leave and are admitted from the + // lobby (see KeyEnvelope). 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. + // KeyEnvelope), so returning means being admitted from the lobby or + // joining with an invite 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; @@ -225,8 +275,8 @@ service Chat { // 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. + // join a private group (see Metadata.is_private) when they hold no + // invite; JoinChat without one is DENIED for a private group. // // 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 @@ -241,10 +291,10 @@ service Chat { // // 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. + // creator denies them (DenyLobbyMember), when the creator admits them + // (AdmitLobbyMember), or when they join with an invite (JoinChat). 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 @@ -273,10 +323,11 @@ service Chat { // 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. + // nobody is admitted to a group before its creator holds its key. Besides + // joining with an invite, which stores the invite's copy of the chat key + // for the joiner (see JoinChat), 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 @@ -296,7 +347,7 @@ service Chat { // 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): + // It is called in three 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 @@ -307,6 +358,11 @@ service Chat { // 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. + // - By a member who joined with an invite, after first opening the + // invite's copy of the chat key with the link's secret (from the + // secret envelope stored with it, or from the link), to replace it + // with one wrapped by themself. Storing it deletes the copy and that + // secret envelope (see InviteKeyEnvelope). // // The first envelope a caller stores for themself stands. Once the // stored envelope is one the caller wrapped, a call with a different @@ -325,6 +381,14 @@ service Chat { // client calls this once per chat per install and keeps the key it // unwraps. // + // For a member who joined with an invite and has not yet stored their + // own envelope, what is stored is the invite's copy of the chat key and + // the link's secret the member encrypted for themself when joining. Both + // are returned in invite_key_bundle in place of key_envelope. Any of + // the member's devices opens the secret with the member's account key, + // then the copy with the secret, and replaces the copy with an envelope + // of its own with SetKeyEnvelope (see InviteKeyEnvelope). + // // 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 @@ -332,6 +396,83 @@ service Chat { // until it succeeds. rpc GetKeyEnvelope(GetKeyEnvelopeRequest) returns (GetKeyEnvelopeResponse); + // CreateInvite creates an invite to a private group (see Invite): a link + // that lets users join it with JoinChat without the creator's approval. + // + // Only a member the server permits to invite may call it, as reported by + // ViewerState.Permissions.can_invite; anyone else is DENIED. So is any + // call for a group whose creator has not stored its key yet (see + // StartChatRequest.PrivateGroupChatParameters). + // + // The caller's client generates the link's secret and derives the + // invite's token, ID and wrap key from it (see Invite). It sends the + // token, the chat key encrypted under the wrap key and signed by the + // caller (InviteKeyEnvelope), and the secret encrypted for itself + // (InviteSecretEnvelope); the secret and the wrap key never leave it. The + // server computes the invite's ID from the token, which it never stores + // (see InviteToken), and checks the copy's signature against the + // caller's public key with that ID, refusing one that does not verify + // (INVALID_SIGNATURE), since no joiner could accept it. So a client that + // derived the ID wrongly is refused here, rather than sharing a link that + // admits no one. The server cannot check what either envelope holds: a + // copy that does not hold the chat key gives everyone who joins with the + // invite a key that reads nothing. + // + // An existing invite with the ID computed from the request's token is + // looked up before anything else is checked. A retry with the same token, + // by the same caller for the same chat, returns the invite the first + // attempt created, with result OK, even if the other parameters differ + // and whatever its status now: it may since have been used up, expired + // or been revoked, so the client checks Invite.status before sharing the + // link. An ID already held by an invite to another chat, or by another + // user's, is DENIED. + // + // Otherwise, the server caps how many ACTIVE invites a chat may hold + // (TOO_MANY_INVITES) and how far away expires_at may be + // (INVALID_EXPIRES_AT). + // + // Nothing is published. + rpc CreateInvite(CreateInviteRequest) returns (CreateInviteResponse); + + // GetInvite previews an invite from its link (see Invite): the chat it + // admits users to, whose chat_id the caller passes to JoinChat and + // derives the invite's wrap key with, and whether the invite still admits + // anyone. Anyone holding the link may call it, registered or not: the + // token is the credential, and auth is optional (when set, it must be + // valid). It counts no use, returns no key, and changes nothing. + // + // An authenticated caller gets the chat as they see it with GetChat: a + // non-member gets its record, with in_lobby set while they wait in its + // lobby. A user banned from the chat gets only BANNED. An + // unauthenticated caller gets the chat's public view (see GetChat), + // which for a private group is its record alone, and is never BANNED, + // since the server does not know who they are. Use counts are shown only + // to those who may invite (GetChatInvites). + rpc GetInvite(GetInviteRequest) returns (GetInviteResponse); + + // GetChatInvites pages a private group's invites, most recently created + // first, whatever their status, each with its use count and its + // creator's copy of the link's secret (see InviteSecretEnvelope). + // + // Only a member the server permits to invite may call it, as reported by + // ViewerState.Permissions.can_invite; anyone else is DENIED. + rpc GetChatInvites(GetChatInvitesRequest) returns (GetChatInvitesResponse); + + // RevokeInvite stops an invite admitting anyone and, in the same step, + // deletes the server's copy of the chat key held for it (see + // InviteKeyEnvelope). Users it already admitted stay members and keep + // the chat key: an invite never changes it, and the copies stored for + // those who have not yet replaced theirs are kept, since those are their + // only key. So revoking a link that leaked keeps new users out, but + // takes the key from no one who already opened it, nor from anyone + // holding the link who can read those copies (see Invite). A link that + // leaked is replaced by revoking it and creating another. + // + // Gated like GetChatInvites. Revoking an invite already revoked is a + // no-op that returns OK; revoking one that has expired or been used up + // records it as revoked. Nothing is published. + rpc RevokeInvite(RevokeInviteRequest) returns (RevokeInviteResponse); + // SetFeaturedGroups replaces the caller's featured groups: an ordered // list of public group chats they show on their profile, which anyone can // fetch with GetFeaturedGroups. The list is written whole, so the request @@ -425,6 +566,11 @@ service Chat { // but the server cannot take back the chat key they already opened: // ciphertext of later messages that reached them some other way is // readable to them, since the key is not rotated. + // + // A ban applies to the account, not to an invite link (see Invite). A + // banned user who still holds a link that admits users can join with it + // from another account, so banning someone who joined with an invite + // calls for revoking that invite too (RevokeInvite). rpc BanUser(BanUserRequest) returns (BanUserResponse); // UnbanUser lifts a user's ban from a group chat. Gated as BanUser. @@ -586,7 +732,14 @@ message GetRosterRequest { // 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]; + // Set to read the roster with an invite link (see GetRoster) rather than + // as a member: the token derived from the link's secret (see Invite). It + // must be a token for an invite to chat_id. Send it on every page. + InviteToken invite_token = 3; + + // Optional when invite_token is set, and required otherwise (see + // GetRoster). When set, it must be valid. + common.v1.Auth auth = 10; } message GetRosterResponse { @@ -730,9 +883,10 @@ message StartChatRequest { } // 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. + // one whose members are admitted by its creator from its lobby or join + // with an invite, 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 @@ -745,6 +899,7 @@ message StartChatRequest { // happen in it, and the server refuses each of these as DENIED: // - EnterLobby, by anyone // - AdmitLobbyMember + // - CreateInvite // - Messaging.SendMessage and Messaging.EditMessage // - BlobStorage.InitiateExternalUpload of a blob encrypted for the chat // The creator's client must keep attempting SetKeyEnvelope until it @@ -807,6 +962,24 @@ message StartChatResponse { message JoinChatRequest { common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; + // Set to join a private group with an invite (see JoinChat). + InviteParameters invite = 2; + message InviteParameters { + // The token derived from the invite link's secret (see Invite). It + // must be a token for an invite to chat_id. + InviteToken invite_token = 1 [(validate.rules).message.required = true]; + + // The link's secret encrypted by the caller for themself (see + // InviteSecretEnvelope). A join that stores the invite's copy of the + // chat key for the caller stores this with it, so that any of the + // caller's devices can open the copy until they replace it with an + // envelope of their own. A caller who is already a member changes + // nothing, so it is not stored. The server cannot open it or check + // what it holds: one that does not hold the link's secret leaves the + // copy openable only on a device that still has the link. + InviteSecretEnvelope secret_envelope = 2 [(validate.rules).message.required = true]; + } + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; } @@ -818,6 +991,10 @@ message JoinChatResponse { NOT_FOUND = 2; RULES_NOT_SATISFIED = 3; BANNED = 4; // The chat's creator banned the caller (see BanUser) + INVITE_NOT_FOUND = 5; // No invite to this chat matches the token, or the chat is not a private group + INVITE_EXHAUSTED = 6; // The invite has admitted max_uses joins + INVITE_EXPIRED = 7; // The invite's expires_at has passed + INVITE_REVOKED = 8; // The invite was revoked (see RevokeInvite) } // The metadata for the joined chat, as seen by the caller. Set only when @@ -1105,15 +1282,174 @@ message GetKeyEnvelopeResponse { 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 envelope stored for the caller. Set only when result == OK. + oneof envelope { + // The caller's key envelope, wrapped by themself or by the chat's + // creator. + KeyEnvelope key_envelope = 2; + + // An invite's copy of the chat key, stored for the caller when they + // joined with an invite (see JoinChat) and kept until they replace it + // with an envelope of their own (SetKeyEnvelope). It holds everything + // the client needs to open the copy (see InviteKeyEnvelope). + InviteKeyBundle invite_key_bundle = 5; + } + message InviteKeyBundle { + // The invite whose copy this is. + InviteId invite_id = 1 [(validate.rules).message.required = true]; + + // The invite's copy of the chat key. The client checks its + // signature, opens it with the link's secret from secret_envelope, + // and wraps the chat key for the caller. + InviteKeyEnvelope key_envelope = 2 [(validate.rules).message.required = true]; + + // The link's secret, encrypted by the caller for themself when they + // joined (JoinChatRequest.InviteParameters.secret_envelope). It opens + // with the caller's account key on any of their devices. + InviteSecretEnvelope secret_envelope = 3 [(validate.rules).message.required = true]; + } // 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. + // the caller, or the chat's creator. For an invite's copy + // (invite_key_bundle) it is the user who created the invite and signed + // the copy (Invite.created_by), which must be the chat's creator (see + // InviteKeyEnvelope). It tells the client whose public key to open or + // verify the envelope against, and is only a hint: an envelope that does + // not open or verify against that key is rejected. Set only when result + // == OK. common.v1.UserId wrapped_by = 3; + + // The public key of the user named by wrapped_by, as the server reports + // it: the key the client opens the envelope against, or verifies an + // invite's copy against, when it has learned none for that user outside + // this service (see KeyEnvelope). For a self-wrapped envelope it is the + // caller's own key, which the client already holds. Set only when result + // == OK. + common.v1.PublicKey wrapped_by_public_key = 4; +} + +message CreateInviteRequest { + common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; + + // The token derived from the invite link's secret (see Invite), from + // which the server computes the invite's ID. A retry with the same token + // returns the invite the first attempt created. + InviteToken invite_token = 2 [(validate.rules).message.required = true]; + + // The chat key encrypted under the invite's wrap key and signed by the + // caller. + InviteKeyEnvelope key_envelope = 3 [(validate.rules).message.required = true]; + + // The link's secret encrypted by the caller for themself. + InviteSecretEnvelope secret_envelope = 4 [(validate.rules).message.required = true]; + + // How many joins the invite allows (see Invite). 0 means unlimited. + uint32 max_uses = 5; + + // When the invite stops admitting anyone. Optional; unset never expires. + // If set, it must be in the future and no further away than the server + // allows (INVALID_EXPIRES_AT otherwise). + google.protobuf.Timestamp expires_at = 6; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message CreateInviteResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; // Caller may not invite to this chat (see CreateInvite) + NOT_FOUND = 2; + INVALID_EXPIRES_AT = 3; // expires_at is not in the future, or further away than the server allows + TOO_MANY_INVITES = 4; // The chat holds as many ACTIVE invites as the server allows + INVALID_SIGNATURE = 5; // key_envelope's signature does not verify against the caller's public key + } + + // The created invite, or the one a retry with the same ID created. Set + // only when result == OK. + Invite invite = 2; +} + +message GetInviteRequest { + // The token derived from the invite link's secret (see Invite). + InviteToken invite_token = 1 [(validate.rules).message.required = true]; + + // Optional (see GetInvite). When set, it must be valid. + common.v1.Auth auth = 10; +} + +message GetInviteResponse { + Result result = 1; + enum Result { + OK = 0; + NOT_FOUND = 1; // No invite matches the token + BANNED = 2; // The chat's creator banned the caller (see BanUser); authenticated callers only + } + + // The chat the invite admits users to, as the caller sees it (see + // GetInvite). Set only when result == OK. + Metadata chat = 2; + + // Whether the invite still admits anyone. Set only when result == OK. + Invite.Status status = 3; +} + +message GetChatInvitesRequest { + common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; + + // QueryOptions controls page_size, capped at 100. Ordering is fixed to + // most recently created 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 GetChatInvitesResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; // Caller may not invite to this chat (see GetChatInvites) + NOT_FOUND = 2; + } + + // The page, most recently created first. Set when result is OK. + repeated Invite invites = 2 [(validate.rules).repeated = { + min_items: 0 + max_items: 100 + }]; + + // PagingToken carries the cursor after the last invite returned. The + // client MUST send it back in query_options.paging_token on the next + // GetChatInvitesRequest. 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 GetChatInvitesRequest with the returned + // paging_token. + bool has_more = 4; +} + +message RevokeInviteRequest { + common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; + + // The invite to revoke, which must be an invite to chat_id. + InviteId invite_id = 2 [(validate.rules).message.required = true]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message RevokeInviteResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; // Caller may not invite to this chat (see RevokeInvite) + NOT_FOUND = 2; // No such chat, or no invite to it with this ID + } } message SetFeaturedGroupsRequest { diff --git a/proto/chat/v1/model.proto b/proto/chat/v1/model.proto index 4dd457e..07c0ace 100644 --- a/proto/chat/v1/model.proto +++ b/proto/chat/v1/model.proto @@ -81,7 +81,7 @@ message Metadata { // Rules governing participation in this chat. Only supported for group // 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. + // creator's approval or an invite instead. Rules rules = 11; // Per-viewer chat state, including what the viewer may do in the chat. @@ -95,9 +95,10 @@ message Metadata { // 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 + // A private group has no rules. A user joins it in one of two ways: they + // enter its lobby with Chat.EnterLobby and are admitted by its creator, + // or they join with Chat.JoinChat carrying an invite the creator made + // (see Invite). 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). @@ -578,6 +579,14 @@ message ViewerState { // What the viewer may do to moderate the chat's users. Unset when // they may do none of it, as for every DM. ModerationPermissions moderation = 2; + + // Whether the viewer may call Chat.CreateInvite, Chat.GetChatInvites + // and Chat.RevokeInvite on this chat. Today that is a private group's + // creator while a member, and no one else: false for every other + // member, and for every chat that is not a private group. Granting + // it to anyone else would let them hand joiners a wrong chat key (see + // Invite). + bool can_invite = 3; } // ModerationPermissions are the moderation actions the viewer may take @@ -747,15 +756,49 @@ message IdempotencyKey { // 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. +// over the one the server reports with the envelope (wrapped_by_public_key +// in Chat.GetKeyEnvelope's response). 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. // +// A member who joins with an invite (Chat.JoinChat) is given, along with +// their membership, the invite's copy of the chat key (see +// InviteKeyEnvelope) in place of an envelope the creator wrapped. +// Chat.GetKeyEnvelope returns it until they replace it: they check its +// signature, open it with the link's secret, wrap the chat key for +// themself and store that envelope with Chat.SetKeyEnvelope, as a member +// admitted from the lobby does. Until they have, opening their key depends +// on the link's secret, which they encrypted for themself when joining (see +// InviteSecretEnvelope) and which is stored and returned with the copy, so +// any of their devices can do this, whether or not it holds the link. +// +// The copy carries the creator's signature, which the member checks against +// the creator's public key, preferring one learned outside this service as +// above. So the copy holds as far as an envelope the creator wrapped does: +// substituting a chat key in it takes the creator's private key, or the +// server reporting a false public key for the creator. A member whose copy +// fails that check, or fails to open, treats it as the wrong key. +// +// A member whose chat's messages do not open under their key treats it as +// the wrong key, warns the user and does not send in the chat. That catches +// a key made wrong by mistake, but is no defence against the server, which +// decides what messages a member is shown and who sent them. +// +// A member holding the wrong key, or an invite's copy none of their devices +// can open, has one way to repair it: leave the chat +// (Chat.LeaveChat), which discards their envelope, then enter its lobby +// (Chat.EnterLobby) and be admitted by the creator, who wraps the chat key +// for them. Nothing else gives them another key: Chat.SetKeyEnvelope keeps an +// envelope they wrapped themself (ALREADY_SET), Chat.AdmitLobbyMember leaves +// a member's envelope as it is, and joining with the same invite again gives +// the same copy. An invite whose copy holds the wrong key should be revoked +// (Chat.RevokeInvite). +// // 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 @@ -835,9 +878,9 @@ message LobbyUpdate { } // 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. + // were denied (Chat.DenyLobbyMember), were admitted + // (Chat.AdmitLobbyMember) or joined with an invite (Chat.JoinChat). + // Either of the last two 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]; @@ -850,3 +893,316 @@ message LobbyUpdateBatch { max_items: 100 }]; } + +// InviteId names an invite to a private group (see Invite). Clients derive +// it from the invite's link; it is not secret, and does not let anyone join. +message InviteId { + bytes value = 1 [(validate.rules).bytes = { + min_len: 16 + max_len: 16 + }]; +} + +// InviteToken proves its sender holds an invite's link (see Invite). Clients +// derive it from the link's secret and send it only to create the invite +// (Chat.CreateInvite), preview it (Chat.GetInvite), read the chat's roster +// with it (Chat.GetRoster) or join with it (Chat.JoinChat). The server never +// stores it, in its records or its logs: anyone who reads a token can join +// with the invite and use up its limit, and read the roster without an +// account. +message InviteToken { + bytes value = 1 [(validate.rules).bytes = { + min_len: 32 + max_len: 32 + }]; +} + +// Invite is a link that lets users join a private group (see +// Metadata.is_private) without its creator's approval, up to a number of +// joins or without limit. It is created with Chat.CreateInvite by a +// member the server permits to invite (see +// ViewerState.Permissions.can_invite), which today is the group's creator +// alone. An invite admits whoever holds the link, including a user the +// creator denied from the lobby (Chat.DenyLobbyMember). +// +// Should the server ever let members other than the creator invite, such a +// member's invites must stop admitting anyone once they are banned, leave +// the chat, or otherwise lose can_invite, and those permitted to ban +// (ViewerState.Permissions.moderation.can_ban) must be able to revoke any +// invite. That rule is for them alone: the chat's creator also loses +// can_invite while not a member, but their invites keep working (see +// below). +// +// Letting other members invite would also weaken what a joiner can trust: +// today only the creator can hand out the chat key, but a member permitted +// to invite could give the users who join with their invite a wrong key, +// which the signature on the copy (see InviteKeyEnvelope) cannot catch, +// since it proves only who made the copy. Clients would then also need a way +// to check a key against one the creator vouches for. +// +// A user joins with an invite with Chat.JoinChat, which in one step makes +// them a member, counts their use of the invite, and stores the invite's +// copy of the chat key (see InviteKeyEnvelope) as their key envelope, with +// the link's secret the joiner encrypted for themself (see +// InviteSecretEnvelope). Any of their devices then fetches both with +// Chat.GetKeyEnvelope, checks the copy's signature, opens the secret and +// then the copy, wraps the chat key for the joiner and stores that envelope +// with Chat.SetKeyEnvelope, as a member admitted from the lobby does (see +// KeyEnvelope). The server gives the copy to members alone, so it reaches no +// more users than the invite's limit allows. +// +// The link carries one secret: 32 random bytes the inviter's client +// generates for this invite alone. It travels in the link's URL fragment, +// which a browser never sends in a request, and the server never learns it. +// A web page that a link opens can still read the fragment, so such a page +// must never send it anywhere or log it. It is not the chat key. Every +// client derives three values from it, with HKDF-SHA256 (RFC 5869): +// +// invite_token = HKDF(ikm = secret, +// salt = "", +// info = "flipcash-group-invite-token-v1", +// L = 32) +// wrap_key = HKDF(ikm = secret, +// salt = "", +// info = "flipcash-group-invite-wrap-v1" || chat_id, +// L = 32) +// invite_id = SHA-256("flipcash-group-invite-id-v1" || invite_token)[:16] +// +// where chat_id is the raw bytes of common.v1.ChatId.value. The link holds +// no chat ID, so a joiner learns it from Chat.GetInvite and derives the wrap +// key only then. +// +// - The invite token (InviteToken) proves the caller holds the link. The +// server finds the invite by computing its ID from the token, so neither +// the server's records nor anything it returns lets anyone join. +// - The wrap key opens the invite's copy of the chat key (see +// InviteKeyEnvelope). It is never sent anywhere, so the token does not +// let anyone open the copy. Binding the chat ID into it makes the copy +// open only as the key of the chat it was made for, so the server cannot +// steer a joiner into another chat. +// - The invite ID (InviteId) names the invite. The server computes it from +// the token whenever one is sent, creation included, and the inviter +// lists and revokes the invite by it. +// +// The limit counts joins: each join with the invite that makes someone a +// member uses one, so a user who leaves and joins again with it uses +// another, and leaving gives no use back. A retry of a join that succeeded +// uses none, since the caller is a member by then (see Chat.JoinChat). The +// server does not record who joined with which invite. An invite admits +// anyone, those who joined with it before included, +// only while it is ACTIVE: once it is used up, expires or is revoked, it +// admits no one, and a user returning to the chat is admitted from the lobby +// instead. The users it already admitted stay members. Nothing about the +// chat's creator's own standing stops an invite they made: it keeps working +// while they are not a member, though they cannot list or revoke it until +// they rejoin. +// +// An invite never changes the chat key. Revoking a link that leaked keeps +// new users out, but cannot take the key from anyone who already joined +// with it. +// +// While an invite is ACTIVE, its link also shows who is in the chat to +// whoever holds it, with no account needed: the token alone previews the +// invite (Chat.GetInvite) and reads the chat's roster (Chat.GetRoster), so +// that a web page the link opens can show both before anyone joins. A ban +// does not stop a user who holds the link from reading the roster; revoking +// the invite stops everyone. +// +// The link and the server's records together are as good as the chat key: +// anyone holding the link who can read what the server stores can open a +// copy of the chat key (see InviteKeyEnvelope), and with it every message +// in the chat, without ever joining. The server deletes each copy once it +// is no longer needed (see InviteKeyEnvelope), which bounds this but does +// not end it. A copy that opens with the link is still stored: +// - while the invite is ACTIVE, which is inherent, since anyone holding +// the link could join with it anyway; +// - for each member who joined with the invite and has not yet replaced +// their copy, for as long as that takes, whether or not the invite has +// since stopped admitting anyone; +// - in the server's backups, which deleting a copy does not reach. +// So a link is to be treated like the chat key, and shared only with those +// who may read the chat. +// +// A ban (Chat.BanUser) applies to one account, not to a link. A banned user +// who still holds a link that admits users can join with it from another +// account, so a creator banning someone who joined with an invite +// should revoke that invite too (Chat.RevokeInvite), and make another for +// everyone else. +message Invite { + InviteId invite_id = 1 [(validate.rules).message.required = true]; + + // The private group the invite admits users to. + common.v1.ChatId chat_id = 2 [(validate.rules).message.required = true]; + + // The user that created the invite. + common.v1.UserId created_by = 3 [(validate.rules).message.required = true]; + + // How many joins the invite allows (see Invite). 0 means unlimited. + uint32 max_uses = 4; + + // How many joins the invite has admitted, whether or not those users are + // still members. Never decreases. + uint32 use_count = 5; + + // When the invite was created. + google.protobuf.Timestamp created_at = 6 [(validate.rules).timestamp.required = true]; + + // When the invite stops admitting anyone. Unset when it never expires. + google.protobuf.Timestamp expires_at = 7; + + // What the invite still allows. When more than one reason applies, the + // status is the first of REVOKED, EXPIRED and EXHAUSTED. + Status status = 8; + enum Status { + UNKNOWN = 0; + ACTIVE = 1; // The invite admits users + EXHAUSTED = 2; // It has admitted max_uses joins; it admits no one + EXPIRED = 3; // expires_at has passed; it admits no one + REVOKED = 4; // It was revoked (see Chat.RevokeInvite); it admits no one + } + + // The link's secret, encrypted so that only the invite's creator can open + // it, so that any of their devices can rebuild the link to share it again. + InviteSecretEnvelope secret_envelope = 9 [(validate.rules).message.required = true]; +} + +// InviteKeyEnvelope is an invite's copy of a private group's chat key, +// encrypted under the invite's wrap key (see Invite), so that anyone holding +// the link can open it and nobody else can, the server included, and signed +// by the user who created the invite, so that a joiner can tell it came from +// them. The inviter makes it when creating the invite (Chat.CreateInvite). +// Joining with the invite (Chat.JoinChat) stores it as the joiner's key +// envelope, which Chat.GetKeyEnvelope returns +// (GetKeyEnvelopeResponse.invite_key_bundle), with the link's secret the +// member encrypted for themself (see InviteSecretEnvelope), until the member +// checks its signature, opens it, wraps the chat key for themself and stores +// that envelope with Chat.SetKeyEnvelope (see KeyEnvelope). +// +// The server deletes copies it no longer needs, since anyone holding the +// link who can read them can open them (see Invite). It deletes the +// invite's own copy once the invite stops being ACTIVE: in the same step +// that revokes it (Chat.RevokeInvite) or counts its last use +// (Chat.JoinChat), and at or soon after expires_at for one that expires. +// It deletes a member's copy, and the secret envelope stored with it, in +// the same step that stores the envelope replacing them +// (Chat.SetKeyEnvelope). A member who never replaces their copy keeps it, +// whatever becomes of the invite, since it is their only key. +// +// For scheme XCHACHA20POLY1305, encrypt with XChaCha20-Poly1305 (the IETF +// construction in libsodium's crypto_aead_xchacha20poly1305_ietf_encrypt): +// key = the invite's wrap key +// nonce = 24 fresh random bytes, set in `nonce` below +// plaintext = the 32-byte chat key +// aad = "flipcash-group-invite-wrap-v1" || chat_id || invite_id +// where chat_id and invite_id are the raw bytes of common.v1.ChatId.value and +// InviteId.value. The ciphertext, with its 16-byte Poly1305 tag appended, is +// set in `ciphertext` below. +// +// The inviter then signs the copy with the Ed25519 private key of their +// account: +// message = "flipcash-group-invite-sig-v1" || chat_id || invite_id +// || nonce || ciphertext +// signature = Ed25519-Sign(inviter_private_key, message), set in +// `signature` below +// with chat_id and invite_id as above. The inviter is Invite.created_by, +// reported to a member holding the copy as GetKeyEnvelopeResponse.wrapped_by. +// Today only the chat's creator may invite (see +// ViewerState.Permissions.can_invite), so the member first checks that +// wrapped_by is Metadata.creator, and rejects the copy otherwise: without +// that check, a server could name an account it controls as the inviter and +// sign with that account's key. The member then verifies the signature +// against the inviter's public key (wrapped_by_public_key in the same +// response, or one learned outside this service, which it prefers), and +// only then opens the copy. +// +// Only a holder of the link's secret can derive the wrap key, so a copy that +// opens was made by one, and only the creator can sign it. Neither the +// server, even with a link it made itself, nor anyone else holding the link +// can substitute a chat key of its choosing, except by the server reporting +// a false public key for the creator, the trust every KeyEnvelope rests on +// too. A client rejects a copy whose inviter is not the creator, whose +// signature does not verify or that fails to open, treats it as the wrong +// key (see KeyEnvelope), and does not send in the chat. Since the copy is +// given only on joining, a user who joined with a forged link is a member +// by the time their client finds this out. +message InviteKeyEnvelope { + // The scheme used, which determines how every other field is + // interpreted. Clients treat an unrecognized scheme as a copy they cannot + // open. + Scheme scheme = 1 [(validate.rules).enum = { + in: [1] // XCHACHA20POLY1305 + }]; + enum Scheme { + UNKNOWN = 0; + 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 + }]; + + // The inviter's Ed25519 signature over the copy, as described above. + bytes signature = 4 [(validate.rules).bytes = { + min_len: 64 + max_len: 64 + }]; +} + +// InviteSecretEnvelope is an invite link's secret (see Invite), encrypted by +// a user for themself, so that only they can open it, on any of their +// devices, and the server cannot. Two users make one: +// +// - The user who creates the invite (Invite.created_by, not necessarily +// the chat's creator), with Chat.CreateInvite. The server returns it with +// the invite (Chat.GetChatInvites), so that any of their devices can +// rebuild the link to share it again. +// - A user joining with the invite, with Chat.JoinChat. The server stores +// it with the invite's copy of the chat key and returns it with the copy +// (Chat.GetKeyEnvelope), so that any of their devices can open the copy +// until they replace it with an envelope of their own. +// +// "Owner" below is the user who made it. For scheme +// X25519_XCHACHA20POLY1305, it is made exactly as a KeyEnvelope a user wraps +// for themself, with the owner as both wrapper and recipient (so the peer +// key in step 1 is the owner's own public key), and these values in place of +// the KeyEnvelope ones: +// info (step 3) = "flipcash-group-invite-secret-v1" || chat_id +// || invite_id +// plaintext (step 4) = the link's 32-byte secret +// aad (step 4) = "flipcash-group-invite-secret-v1" || chat_id +// || invite_id || owner_pk || owner_pk +// where chat_id and invite_id are the raw bytes of common.v1.ChatId.value and +// InviteId.value, and owner_pk is the owner's 32-byte Ed25519 public key. So +// it opens only as the secret of the invite and chat it was made for. +message InviteSecretEnvelope { + // 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 secret with the 16-byte Poly1305 tag appended. + bytes ciphertext = 3 [(validate.rules).bytes = { + min_len: 48 + max_len: 48 + }]; +} diff --git a/proto/common/v1/common.proto b/proto/common/v1/common.proto index 51fb64e..4eb8065 100644 --- a/proto/common/v1/common.proto +++ b/proto/common/v1/common.proto @@ -65,7 +65,7 @@ message UserId { // set as X — letters, digits and underscores — with the exception that it must // be lowercase. message Username { - string value = 1 [(validate.rules).string.pattern = "^[a-z0-9_]{2,15}$"]; + string value = 1 [(validate.rules).string.pattern = "^[a-z0-9_]{2,30}$"]; } message ChatId { diff --git a/proto/messaging/v1/model.proto b/proto/messaging/v1/model.proto index f8af210..9803972 100644 --- a/proto/messaging/v1/model.proto +++ b/proto/messaging/v1/model.proto @@ -418,6 +418,7 @@ message SystemContent { Direct direct = 2; Admitted admitted = 3; + Invited invited = 4; } // They joined on their own (e.g. a private group's creator rejoining @@ -430,6 +431,13 @@ message SystemContent { // The user that let them in common.v1.UserId admitted_by = 1 [(validate.rules).message.required = true]; } + + // They joined with someone's invite, needing no one's approval (see + // chat.v1.Invite). + message Invited { + // The user that created the invite + common.v1.UserId invited_by = 1 [(validate.rules).message.required = true]; + } } }