diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c092a7..c076fe5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,115 @@ 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.7.0 + +Synced to [`flipcash2-protobuf-api@89b444bc`](https://github.com/code-payments/flipcash2-protobuf-api/commit/89b444bc4ae0d099dbaa90cc8b1b301398c94990), +picking up [#99](https://github.com/code-payments/flipcash2-protobuf-api/pull/99) and +[#100](https://github.com/code-payments/flipcash2-protobuf-api/pull/100). + +One field is gone: `event.v1.ChatUpdate.new_messages`, field 2, now `reserved 2`. It has carried +`[deprecated = true]` since 0.1.0, and `events` has sat beside it as the sequenced replacement for +just as long, so a client already reading `events` is unaffected and one still falling back to +`new_messages` will not compile. Everything else is additive: four RPCs that let a client create a +group chat and move in and out of it, and roster changes delivered on the event stream. + +### Removed + +- `new_messages` on `event.v1.ChatUpdate`, field 2, previously `messaging.v1.MessageBatch`. The + number is now `reserved`, so it cannot be reused and the wire format stays unambiguous for old + clients. **Source-breaking for anyone still reading it.** Read new messages from `events` + (field 6), which is contiguous, ordered, gap-detectable and catchable up through + `Messaging.GetDelta` — none of which `new_messages` offered. Delete the fallback rather than + porting it; there is nothing left for it to fall back to. + +### Added + +- Four RPCs on `service Chat`: `GetGroupChatFeed`, `StartChat`, `JoinChat` and `LeaveChat`. This is + what makes `chat/v1/chat_service.proto` import `blob/v1/model.proto` and + `moderation/v1/model.proto` for the first time, so anything compiling the chat service now needs + both alongside it. + +- `GetGroupChatFeed` reads the caller's group chats, ordered by last activity with the most recent + first. `GetGroupChatFeedRequest` carries `query_options` (field 1) and required `auth` (field 10); + `GetGroupChatFeedResponse` carries `result` (field 1, `OK`/`DENIED`/`NOT_FOUND`), up to 100 `chats` + (field 2, `chat.v1.Metadata`), `paging_token` (field 3) and `has_more` (field 4). + + It has the same read contract as `GetDmChatFeed`, and the same reason for it: chats are ordered by + a mutable key, so pagination alone cannot give a complete read. Open the event stream and start + buffering `ChatUpdate` *before* the first call, page until `has_more` is false while echoing back + the previous response's `paging_token`, then merge the buffered and ongoing updates onto the + paginated set. `query_options` controls `page_size` only — ordering is not client-selectable, and + the token is opaque and server-minted, so leave it unset on the first request and never construct + one. + + One thing the DM feed does not have: a group's membership can change mid-read. Every page is served + only for groups the caller is still a member of at the time of that page, so a group left between + pages is simply absent and its removal arrives on the stream. A client treating the paginated set + as authoritative without reconciling will show a chat it is no longer in. + + Group feed tokens are considerably larger than DM feed tokens, because the server carries the + snapshot's remaining order inside the token rather than recomputing it per page. See the + `PagingToken` change below. + +- `StartChat` creates a chat. `StartChatRequest` has a required `parameters` oneof whose only arm + today is `group` (field 1), plus required `auth` (field 10). `GroupChatParameters` carries `title` + (field 1, 1–64 characters), optional `picture` (field 2, `blob.v1.BlobId` — the original the caller + uploaded, which must be caller-owned and `READY`) and optional `rules` (field 3, the + `chat.v1.Rules` added in 0.6.0; unset means no participation requirements, and the caller must + satisfy whatever it does set). + + `StartChatResponse.result` (field 1) is `OK`, `DENIED`, `TITLE_MODERATED`, + `PICTURE_BLOB_NOT_ACCEPTED`, `INVALID_RULES` or `RULES_NOT_SATISFIED`. `chat` (field 2) is set only + on `OK` and carries the server-generated `chat_id` along with any picture renditions the server + derived, so there is nothing to refetch. `flagged_category` (field 3, + `moderation.v1.FlaggedCategory`) is the best-fit category that tripped moderation, set only on + `TITLE_MODERATED` and `NONE` otherwise; it mirrors the Moderation service's vocabulary, so a client + already rendering moderation categories can reuse that mapping. + +- `JoinChat` and `LeaveChat` move the caller in and out of a chat's roster. Both requests take + required `chat_id` (field 1) and `auth` (field 10). `JoinChatResponse.result` is + `OK`/`DENIED`/`NOT_FOUND`/`RULES_NOT_SATISFIED`, with `chat` (field 2) set only on `OK`, giving the + joined chat's metadata as the caller now sees it. `LeaveChatResponse.result` is + `OK`/`DENIED`/`NOT_FOUND` and carries nothing else. + +- `chat.v1.RosterUpdate` and `chat.v1.RosterUpdateBatch`. A `RosterUpdate` is one member joining or + leaving — a required `kind` oneof of `member_joined` (field 1) or `member_left` (field 2), plus + required `roster_summary` (field 10) holding the roster after that change. `RosterUpdateBatch` + wraps 1–100 of them. Updates go to the chat's members, including the affected user's other + devices, and are best-effort. + + They are convergent, not sequenced: they ride the event stream *outside* the gap-detected event + log, and are applied by version as described on `RosterSummary` — a greater version than the one + held means apply, lesser or equal means drop, so delivery order does not matter. A miss is not + caught up via `GetDelta`; refetch the roster on a version that cannot be reconciled. + + `MemberJoined.member` (field 1) arrives with the profile hydrated, so a cached member list updates + without a refetch. `MemberJoined.metadata` (field 2) is set **only when the joining member is the + recipient** — that is the signal to insert the chat into your own list, from that snapshot. Version + by the enclosing `RosterUpdate.roster_summary`, which is authoritative; + `metadata.roster_summary` is not compared separately. `MemberLeft.user_id` (field 1) naming the + recipient means the recipient is out and the chat should come off their list. + +- `roster_updates` on `event.v1.ChatUpdate`, field 8, typed `chat.v1.RosterUpdateBatch`. Same + convergent-overlay handling as `reaction_updates`, and the delivery path for everything above: a + `MemberLeft` naming the recipient is how a group dropped between pages of a feed read gets + reconciled. + +### Changed + +- `common.v1.PagingToken.value` raises its validation `max_len` from 128 to 16384. Same field, same + number, bytes on the wire either way, and no generated API change — this is a server-side + validation ceiling only. It is raised because `GetGroupChatFeed` packs the snapshot's remaining + order into the token. Treat the token as an opaque blob and size any storage holding it for the new + ceiling rather than the old one. + +### Unchanged + +Nothing was renumbered, and no existing result enum gained, lost or reordered a case — all four +`Result` enums in this release are on brand-new messages, so nothing positional shifted underneath +anyone. No existing field changed type, and no service, RPC or message other than `new_messages` was +removed. Apart from dropping that one fallback, the upgrade is additive. + ## 0.6.0 Synced to [`flipcash2-protobuf-api@35f99814`](https://github.com/code-payments/flipcash2-protobuf-api/commit/35f9981400947921bbe2872be63b0bb77a569e34), diff --git a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift index cb4c20e..399614f 100644 --- a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift +++ b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift @@ -44,10 +44,62 @@ public enum Flipcash_Chat_V1_Chat { method: "GetDmChatFeed" ) } + /// Namespace for "GetGroupChatFeed" metadata. + public enum GetGroupChatFeed { + /// Request type for "GetGroupChatFeed". + public typealias Input = Flipcash_Chat_V1_GetGroupChatFeedRequest + /// Response type for "GetGroupChatFeed". + public typealias Output = Flipcash_Chat_V1_GetGroupChatFeedResponse + /// Descriptor for "GetGroupChatFeed". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "GetGroupChatFeed" + ) + } + /// Namespace for "StartChat" metadata. + public enum StartChat { + /// Request type for "StartChat". + public typealias Input = Flipcash_Chat_V1_StartChatRequest + /// Response type for "StartChat". + public typealias Output = Flipcash_Chat_V1_StartChatResponse + /// Descriptor for "StartChat". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "StartChat" + ) + } + /// Namespace for "JoinChat" metadata. + public enum JoinChat { + /// Request type for "JoinChat". + public typealias Input = Flipcash_Chat_V1_JoinChatRequest + /// Response type for "JoinChat". + public typealias Output = Flipcash_Chat_V1_JoinChatResponse + /// Descriptor for "JoinChat". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "JoinChat" + ) + } + /// Namespace for "LeaveChat" metadata. + public enum LeaveChat { + /// Request type for "LeaveChat". + public typealias Input = Flipcash_Chat_V1_LeaveChatRequest + /// Response type for "LeaveChat". + public typealias Output = Flipcash_Chat_V1_LeaveChatResponse + /// Descriptor for "LeaveChat". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "LeaveChat" + ) + } /// Descriptors for all methods in the "flipcash.chat.v1.Chat" service. public static let descriptors: [GRPCCore.MethodDescriptor] = [ GetChat.descriptor, - GetDmChatFeed.descriptor + GetDmChatFeed.descriptor, + GetGroupChatFeed.descriptor, + StartChat.descriptor, + JoinChat.descriptor, + LeaveChat.descriptor ] } } @@ -133,6 +185,119 @@ extension Flipcash_Chat_V1_Chat { options: GRPCCore.CallOptions, onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result ) async throws -> Result where Result: Sendable + + /// Call the "GetGroupChatFeed" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetGroupChatFeed gets the set of group chats for an owner account using + /// > a paged API, ordered by last activity with the most recent first. + /// > + /// > It has the same read contract as GetDmChatFeed. Chats are ordered by a + /// > mutable key (last_activity), so pagination alone cannot guarantee a + /// > complete read. To get the full list, the client MUST combine this RPC + /// > with the event stream: + /// > + /// > 1. Open the event stream to receive ChatUpdate and begin buffering + /// > updates BEFORE the first GetGroupChatFeed call. + /// > 2. Page through GetGroupChatFeed to exhaustion (until has_more is + /// > false), always echoing back the paging token returned by the prior + /// > response. All pages are served against a single snapshot pinned by + /// > that token. + /// > 3. Merge the buffered and ongoing stream updates onto the paginated + /// > set. Any chat whose activity changed after the snapshot watermark + /// > is delivered via the stream rather than via pagination. + /// > + /// > Unlike the DM feed, a group's membership can change while the feed is + /// > being read. Every page is served only for groups the caller is still a + /// > member of at the time of that page; a group the caller left between + /// > pages is dropped, and its removal arrives on the stream. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetGroupChatFeedRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_GetGroupChatFeedRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_GetGroupChatFeedResponse` 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 getGroupChatFeed( + 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 "StartChat" method. + /// + /// > Source IDL Documentation: + /// > + /// > StartChat starts a new chat. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_StartChatRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_StartChatRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_StartChatResponse` 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 startChat( + 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 "JoinChat" method. + /// + /// > Source IDL Documentation: + /// > + /// > JoinChat adds the caller to a chat's roster. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_JoinChatRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_JoinChatRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_JoinChatResponse` 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 joinChat( + 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 "LeaveChat" method. + /// + /// > Source IDL Documentation: + /// > + /// > LeaveChat removes the caller from a chat's roster. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_LeaveChatRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_LeaveChatRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_LeaveChatResponse` 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 leaveChat( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result + ) async throws -> Result where Result: Sendable } /// Generated client for the "flipcash.chat.v1.Chat" service. @@ -239,6 +404,163 @@ extension Flipcash_Chat_V1_Chat { onResponse: handleResponse ) } + + /// Call the "GetGroupChatFeed" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetGroupChatFeed gets the set of group chats for an owner account using + /// > a paged API, ordered by last activity with the most recent first. + /// > + /// > It has the same read contract as GetDmChatFeed. Chats are ordered by a + /// > mutable key (last_activity), so pagination alone cannot guarantee a + /// > complete read. To get the full list, the client MUST combine this RPC + /// > with the event stream: + /// > + /// > 1. Open the event stream to receive ChatUpdate and begin buffering + /// > updates BEFORE the first GetGroupChatFeed call. + /// > 2. Page through GetGroupChatFeed to exhaustion (until has_more is + /// > false), always echoing back the paging token returned by the prior + /// > response. All pages are served against a single snapshot pinned by + /// > that token. + /// > 3. Merge the buffered and ongoing stream updates onto the paginated + /// > set. Any chat whose activity changed after the snapshot watermark + /// > is delivered via the stream rather than via pagination. + /// > + /// > Unlike the DM feed, a group's membership can change while the feed is + /// > being read. Every page is served only for groups the caller is still a + /// > member of at the time of that page; a group the caller left between + /// > pages is dropped, and its removal arrives on the stream. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetGroupChatFeedRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_GetGroupChatFeedRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_GetGroupChatFeedResponse` 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 getGroupChatFeed( + 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.GetGroupChatFeed.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "StartChat" method. + /// + /// > Source IDL Documentation: + /// > + /// > StartChat starts a new chat. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_StartChatRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_StartChatRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_StartChatResponse` 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 startChat( + 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.StartChat.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "JoinChat" method. + /// + /// > Source IDL Documentation: + /// > + /// > JoinChat adds the caller to a chat's roster. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_JoinChatRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_JoinChatRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_JoinChatResponse` 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 joinChat( + 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.JoinChat.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "LeaveChat" method. + /// + /// > Source IDL Documentation: + /// > + /// > LeaveChat removes the caller from a chat's roster. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_LeaveChatRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_LeaveChatRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_LeaveChatResponse` 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 leaveChat( + 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.LeaveChat.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } } } @@ -323,6 +645,143 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { onResponse: handleResponse ) } + + /// Call the "GetGroupChatFeed" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetGroupChatFeed gets the set of group chats for an owner account using + /// > a paged API, ordered by last activity with the most recent first. + /// > + /// > It has the same read contract as GetDmChatFeed. Chats are ordered by a + /// > mutable key (last_activity), so pagination alone cannot guarantee a + /// > complete read. To get the full list, the client MUST combine this RPC + /// > with the event stream: + /// > + /// > 1. Open the event stream to receive ChatUpdate and begin buffering + /// > updates BEFORE the first GetGroupChatFeed call. + /// > 2. Page through GetGroupChatFeed to exhaustion (until has_more is + /// > false), always echoing back the paging token returned by the prior + /// > response. All pages are served against a single snapshot pinned by + /// > that token. + /// > 3. Merge the buffered and ongoing stream updates onto the paginated + /// > set. Any chat whose activity changed after the snapshot watermark + /// > is delivered via the stream rather than via pagination. + /// > + /// > Unlike the DM feed, a group's membership can change while the feed is + /// > being read. Every page is served only for groups the caller is still a + /// > member of at the time of that page; a group the caller left between + /// > pages is dropped, and its removal arrives on the stream. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetGroupChatFeedRequest` 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 getGroupChatFeed( + 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.getGroupChatFeed( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "StartChat" method. + /// + /// > Source IDL Documentation: + /// > + /// > StartChat starts a new chat. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_StartChatRequest` 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 startChat( + 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.startChat( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "JoinChat" method. + /// + /// > Source IDL Documentation: + /// > + /// > JoinChat adds the caller to a chat's roster. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_JoinChatRequest` 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 joinChat( + 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.joinChat( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "LeaveChat" method. + /// + /// > Source IDL Documentation: + /// > + /// > LeaveChat removes the caller from a chat's roster. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_LeaveChatRequest` 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 leaveChat( + request: GRPCCore.ClientRequest, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.leaveChat( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } } // Helpers providing sugared APIs for 'ClientProtocol' methods. @@ -414,4 +873,157 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { onResponse: handleResponse ) } + + /// Call the "GetGroupChatFeed" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetGroupChatFeed gets the set of group chats for an owner account using + /// > a paged API, ordered by last activity with the most recent first. + /// > + /// > It has the same read contract as GetDmChatFeed. Chats are ordered by a + /// > mutable key (last_activity), so pagination alone cannot guarantee a + /// > complete read. To get the full list, the client MUST combine this RPC + /// > with the event stream: + /// > + /// > 1. Open the event stream to receive ChatUpdate and begin buffering + /// > updates BEFORE the first GetGroupChatFeed call. + /// > 2. Page through GetGroupChatFeed to exhaustion (until has_more is + /// > false), always echoing back the paging token returned by the prior + /// > response. All pages are served against a single snapshot pinned by + /// > that token. + /// > 3. Merge the buffered and ongoing stream updates onto the paginated + /// > set. Any chat whose activity changed after the snapshot watermark + /// > is delivered via the stream rather than via pagination. + /// > + /// > Unlike the DM feed, a group's membership can change while the feed is + /// > being read. Every page is served only for groups the caller is still a + /// > member of at the time of that page; a group the caller left between + /// > pages is dropped, and its removal arrives on the stream. + /// + /// - 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 getGroupChatFeed( + _ message: Flipcash_Chat_V1_GetGroupChatFeedRequest, + 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.getGroupChatFeed( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "StartChat" method. + /// + /// > Source IDL Documentation: + /// > + /// > StartChat starts a new chat. + /// + /// - 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 startChat( + _ message: Flipcash_Chat_V1_StartChatRequest, + 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.startChat( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "JoinChat" method. + /// + /// > Source IDL Documentation: + /// > + /// > JoinChat adds the caller to a chat's roster. + /// + /// - 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 joinChat( + _ message: Flipcash_Chat_V1_JoinChatRequest, + 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.joinChat( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "LeaveChat" method. + /// + /// > Source IDL Documentation: + /// > + /// > LeaveChat removes the caller from a chat's roster. + /// + /// - 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 leaveChat( + _ message: Flipcash_Chat_V1_LeaveChatRequest, + 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.leaveChat( + request: request, + options: options, + onResponse: handleResponse + ) + } } \ No newline at end of file diff --git a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift index 9819047..3a09fd8 100644 --- a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift +++ b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift @@ -231,13 +231,651 @@ public struct Flipcash_Chat_V1_GetDmChatFeedResponse: Sendable { fileprivate var _pagingToken: Flipcash_Common_V1_PagingToken? = nil } +public struct Flipcash_Chat_V1_GetGroupChatFeedRequest: 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. + + /// QueryOptions controls page_size. Ordering is fixed to most-recent + /// activity first and is not client-selectable. + /// + /// Leave query_options.paging_token unset on the first request: the server + /// mints a token that pins a new snapshot and returns it in the response. On + /// every subsequent request, set query_options.paging_token to the + /// paging_token from the most recent response to advance within the same + /// snapshot. The token is opaque and server-generated; do not construct it. + /// + /// Group feed tokens are larger than DM feed tokens: the server carries + /// the snapshot's remaining order in the token so later pages do not + /// recompute it. Clients must treat the token as an opaque blob of up to + /// common.v1.PagingToken's maximum size. + 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 _queryOptions: Flipcash_Common_V1_QueryOptions? = nil + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Chat_V1_GetGroupChatFeedResponse: 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_GetGroupChatFeedResponse.Result = .ok + + public var chats: [Flipcash_Chat_V1_Metadata] = [] + + /// PagingToken is the server-generated token for this paginated read. On the + /// first response it pins a new snapshot; on later responses it carries the + /// advanced cursor over (last_activity, chat_id). The client MUST send the + /// most recent value back in query_options.paging_token on the next + /// GetGroupChatFeedRequest. Set when result is OK. + 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 in this snapshot. When + /// false, the paginated set has been fully read; the complete chat list is + /// this set reconciled with the event stream (see GetGroupChatFeed). When + /// true, the client should issue another GetGroupChatFeedRequest 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 + 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_GetGroupChatFeedResponse.Result] = [ + .ok, + .denied, + .notFound, + ] + + } + + public init() {} + + fileprivate var _pagingToken: Flipcash_Common_V1_PagingToken? = nil +} + +public struct Flipcash_Chat_V1_StartChatRequest: 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. + + /// Parameters for the kind of chat being created. The variant selects the + /// chat type. + public var parameters: Flipcash_Chat_V1_StartChatRequest.OneOf_Parameters? = nil + + public var group: Flipcash_Chat_V1_StartChatRequest.GroupChatParameters { + get { + if case .group(let v)? = parameters {return v} + return Flipcash_Chat_V1_StartChatRequest.GroupChatParameters() + } + set {parameters = .group(newValue)} + } + + 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() + + /// Parameters for the kind of chat being created. The variant selects the + /// chat type. + public enum OneOf_Parameters: Equatable, Sendable { + case group(Flipcash_Chat_V1_StartChatRequest.GroupChatParameters) + + } + + /// Parameters for creating a group chat + public struct GroupChatParameters: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// Title for the chat. + public var title: String = String() + + /// The blob holding the ORIGINAL picture the caller uploaded. Optional. + /// If set, it must be owned by the caller and READY. + public var picture: Flipcash_Blob_V1_BlobId { + get {return _picture ?? Flipcash_Blob_V1_BlobId()} + set {_picture = newValue} + } + /// Returns true if `picture` has been explicitly set. + public var hasPicture: Bool {return self._picture != nil} + /// Clears the value of `picture`. Subsequent reads from it will return its default value. + public mutating func clearPicture() {self._picture = nil} + + /// Rules governing participation in the chat. Optional; if not set, the + /// chat has no participation requirements. Caller must satisfy the rules + /// in order for the chat to be started. + public var rules: Flipcash_Chat_V1_Rules { + get {return _rules ?? Flipcash_Chat_V1_Rules()} + set {_rules = newValue} + } + /// Returns true if `rules` has been explicitly set. + public var hasRules: Bool {return self._rules != nil} + /// Clears the value of `rules`. Subsequent reads from it will return its default value. + public mutating func clearRules() {self._rules = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _picture: Flipcash_Blob_V1_BlobId? = nil + fileprivate var _rules: Flipcash_Chat_V1_Rules? = nil + } + + public init() {} + + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Chat_V1_StartChatResponse: 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_StartChatResponse.Result = .ok + + /// The metadata for the newly created chat, including the server-generated + /// chat_id and any picture renditions the server derived. 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} + + /// The best-fit category that tripped moderation, mirroring the Moderation + /// service's vocabulary. Set only when result == TITLE_MODERATED; NONE + /// otherwise. + public var flaggedCategory: Flipcash_Moderation_V1_FlaggedCategory = .none + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + case denied // = 1 + case titleModerated // = 2 + case pictureBlobNotAccepted // = 3 + case invalidRules // = 4 + case rulesNotSatisfied // = 5 + case UNRECOGNIZED(Int) + + public init() { + self = .ok + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .ok + case 1: self = .denied + case 2: self = .titleModerated + case 3: self = .pictureBlobNotAccepted + case 4: self = .invalidRules + case 5: self = .rulesNotSatisfied + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .denied: return 1 + case .titleModerated: return 2 + case .pictureBlobNotAccepted: return 3 + case .invalidRules: return 4 + case .rulesNotSatisfied: 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_StartChatResponse.Result] = [ + .ok, + .denied, + .titleModerated, + .pictureBlobNotAccepted, + .invalidRules, + .rulesNotSatisfied, + ] + + } + + public init() {} + + fileprivate var _chat: Flipcash_Chat_V1_Metadata? = nil +} + +public struct Flipcash_Chat_V1_JoinChatRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var chatID: Flipcash_Common_V1_ChatId { + get {return _chatID ?? Flipcash_Common_V1_ChatId()} + set {_chatID = newValue} + } + /// Returns true if `chatID` has been explicitly set. + public var hasChatID: Bool {return self._chatID != nil} + /// Clears the value of `chatID`. Subsequent reads from it will return its default value. + public mutating func clearChatID() {self._chatID = nil} + + public var auth: Flipcash_Common_V1_Auth { + get {return _auth ?? Flipcash_Common_V1_Auth()} + set {_auth = newValue} + } + /// Returns true if `auth` has been explicitly set. + public var hasAuth: Bool {return self._auth != nil} + /// Clears the value of `auth`. Subsequent reads from it will return its default value. + public mutating func clearAuth() {self._auth = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Chat_V1_JoinChatResponse: 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_JoinChatResponse.Result = .ok + + /// The metadata for the joined chat, as seen by the caller. 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} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + case denied // = 1 + case notFound // = 2 + case rulesNotSatisfied // = 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 = .rulesNotSatisfied + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .denied: return 1 + case .notFound: return 2 + case .rulesNotSatisfied: 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_JoinChatResponse.Result] = [ + .ok, + .denied, + .notFound, + .rulesNotSatisfied, + ] + + } + + public init() {} + + fileprivate var _chat: Flipcash_Chat_V1_Metadata? = nil +} + +public struct Flipcash_Chat_V1_LeaveChatRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var chatID: Flipcash_Common_V1_ChatId { + get {return _chatID ?? Flipcash_Common_V1_ChatId()} + set {_chatID = newValue} + } + /// Returns true if `chatID` has been explicitly set. + public var hasChatID: Bool {return self._chatID != nil} + /// Clears the value of `chatID`. Subsequent reads from it will return its default value. + public mutating func clearChatID() {self._chatID = nil} + + public var auth: Flipcash_Common_V1_Auth { + get {return _auth ?? Flipcash_Common_V1_Auth()} + set {_auth = newValue} + } + /// Returns true if `auth` has been explicitly set. + public var hasAuth: Bool {return self._auth != nil} + /// Clears the value of `auth`. Subsequent reads from it will return its default value. + public mutating func clearAuth() {self._auth = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Chat_V1_LeaveChatResponse: 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_LeaveChatResponse.Result = .ok + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + 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_LeaveChatResponse.Result] = [ + .ok, + .denied, + .notFound, + ] + + } + + public init() {} +} + // MARK: - Code below here is support for the SwiftProtobuf runtime. -fileprivate let _protobuf_package = "flipcash.chat.v1" +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{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_GetChatRequest, rhs: Flipcash_Chat_V1_GetChatRequest) -> 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_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_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{2}\u{9}auth\0") +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() { @@ -245,7 +883,7 @@ extension Flipcash_Chat_V1_GetChatRequest: 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 1: try { try decoder.decodeSingularMessageField(value: &self._queryOptions) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -257,7 +895,7 @@ extension Flipcash_Chat_V1_GetChatRequest: 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._queryOptions { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() try { if let v = self._auth { @@ -266,17 +904,17 @@ extension Flipcash_Chat_V1_GetChatRequest: SwiftProtobuf.Message, SwiftProtobuf. 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} + 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_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") +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() { @@ -285,7 +923,9 @@ extension Flipcash_Chat_V1_GetChatResponse: SwiftProtobuf.Message, SwiftProtobuf // 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.chats) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._pagingToken) }() + case 4: try { try decoder.decodeSingularBoolField(value: &self.hasMore_p) }() default: break } } @@ -299,27 +939,35 @@ 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.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_GetChatResponse, rhs: Flipcash_Chat_V1_GetChatResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_GetGroupChatFeedResponse, rhs: Flipcash_Chat_V1_GetGroupChatFeedResponse) -> Bool { if lhs.result != rhs.result {return false} - if lhs._metadata != rhs._metadata {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_GetChatResponse.Result: SwiftProtobuf._ProtoNameProviding { +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_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_StartChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".StartChatRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}group\0\u{2}\u{9}auth\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -327,8 +975,19 @@ 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 { + var v: Flipcash_Chat_V1_StartChatRequest.GroupChatParameters? + var hadOneofValue = false + if let current = self.parameters { + hadOneofValue = true + if case .group(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.parameters = .group(v) + } + }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -340,30 +999,157 @@ 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 { + try { if case .group(let v)? = self.parameters { 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_StartChatRequest, rhs: Flipcash_Chat_V1_StartChatRequest) -> Bool { + if lhs.parameters != rhs.parameters {return false} + if lhs._auth != rhs._auth {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_StartChatRequest.GroupChatParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Chat_V1_StartChatRequest.protoMessageName + ".GroupChatParameters" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}title\0\u{1}picture\0\u{1}rules\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.title) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._picture) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._rules) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !self.title.isEmpty { + try visitor.visitSingularStringField(value: self.title, fieldNumber: 1) + } + try { if let v = self._picture { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = self._rules { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_StartChatRequest.GroupChatParameters, rhs: Flipcash_Chat_V1_StartChatRequest.GroupChatParameters) -> Bool { + if lhs.title != rhs.title {return false} + if lhs._picture != rhs._picture {return false} + if lhs._rules != rhs._rules {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_StartChatResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".StartChatResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}chat\0\u{3}flagged_category\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._chat) }() + case 3: try { try decoder.decodeSingularEnumField(value: &self.flaggedCategory) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) + } + try { if let v = self._chat { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + if self.flaggedCategory != .none { + try visitor.visitSingularEnumField(value: self.flaggedCategory, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_StartChatResponse, rhs: Flipcash_Chat_V1_StartChatResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs._chat != rhs._chat {return false} + if lhs.flaggedCategory != rhs.flaggedCategory {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_StartChatResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}TITLE_MODERATED\0\u{1}PICTURE_BLOB_NOT_ACCEPTED\0\u{1}INVALID_RULES\0\u{1}RULES_NOT_SATISFIED\0") +} + +extension Flipcash_Chat_V1_JoinChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".JoinChatRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{2}\u{9}auth\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() + case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() + default: break + } } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._chatID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() try { if let v = self._auth { try visitor.visitSingularMessageField(value: v, fieldNumber: 10) } }() try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_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_JoinChatRequest, rhs: Flipcash_Chat_V1_JoinChatRequest) -> Bool { + if lhs._chatID != rhs._chatID {return false} if lhs._auth != rhs._auth {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_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_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() { @@ -372,9 +1158,7 @@ extension Flipcash_Chat_V1_GetDmChatFeedResponse: SwiftProtobuf.Message, SwiftPr // 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) }() default: break } } @@ -388,28 +1172,93 @@ extension Flipcash_Chat_V1_GetDmChatFeedResponse: SwiftProtobuf.Message, SwiftPr 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._chat { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_JoinChatResponse, rhs: Flipcash_Chat_V1_JoinChatResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs._chat != rhs._chat {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_JoinChatResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0\u{1}RULES_NOT_SATISFIED\0") +} + +extension Flipcash_Chat_V1_LeaveChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LeaveChatRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{2}\u{9}auth\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() + case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() + default: break + } } - try { if let v = self._pagingToken { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._chatID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() - if self.hasMore_p != false { - try visitor.visitSingularBoolField(value: self.hasMore_p, fieldNumber: 4) + 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 { + 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_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() { + // 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) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) } try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Flipcash_Chat_V1_GetDmChatFeedResponse, rhs: Flipcash_Chat_V1_GetDmChatFeedResponse) -> Bool { + public static func ==(lhs: Flipcash_Chat_V1_LeaveChatResponse, rhs: Flipcash_Chat_V1_LeaveChatResponse) -> Bool { if lhs.result != rhs.result {return false} - if lhs.chats != rhs.chats {return false} - if lhs._pagingToken != rhs._pagingToken {return false} - if lhs.hasMore_p != rhs.hasMore_p {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Flipcash_Chat_V1_GetDmChatFeedResponse.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") } diff --git a/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift b/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift index ddad3bf..81abb7d 100644 --- a/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift +++ b/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift @@ -467,6 +467,149 @@ public struct Flipcash_Chat_V1_MetadataUpdate: Sendable { public init() {} } +/// RosterUpdate is a best-effort, real-time change to a chat's roster — a +/// member joining (e.g. via Chat.JoinChat, or as the first member via +/// Chat.StartChat) or leaving (e.g. via Chat.LeaveChat). It is delivered to +/// the chat's members, including the affected user's other devices. +/// +/// Roster changes are a convergent overlay, so these ride the event stream +/// OUTSIDE the gap-detected event log. Clients apply them by roster_summary +/// version as described on RosterSummary: a greater version than the one held +/// means the cached member list is stale and this update should be applied; a +/// lesser or equal version should be dropped, so delivery order does not +/// matter. A missed update is not caught up via GetDelta but reconciled by +/// refetching the roster when a client observes a version it cannot reconcile. +public struct Flipcash_Chat_V1_RosterUpdate: @unchecked Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var kind: OneOf_Kind? { + get {return _storage._kind} + set {_uniqueStorage()._kind = newValue} + } + + public var memberJoined: Flipcash_Chat_V1_RosterUpdate.MemberJoined { + get { + if case .memberJoined(let v)? = _storage._kind {return v} + return Flipcash_Chat_V1_RosterUpdate.MemberJoined() + } + set {_uniqueStorage()._kind = .memberJoined(newValue)} + } + + public var memberLeft: Flipcash_Chat_V1_RosterUpdate.MemberLeft { + get { + if case .memberLeft(let v)? = _storage._kind {return v} + return Flipcash_Chat_V1_RosterUpdate.MemberLeft() + } + set {_uniqueStorage()._kind = .memberLeft(newValue)} + } + + /// The chat's roster summary after this change, applied by version as + /// described above. + public var rosterSummary: Flipcash_Chat_V1_RosterSummary { + get {return _storage._rosterSummary ?? Flipcash_Chat_V1_RosterSummary()} + set {_uniqueStorage()._rosterSummary = newValue} + } + /// Returns true if `rosterSummary` has been explicitly set. + public var hasRosterSummary: Bool {return _storage._rosterSummary != nil} + /// Clears the value of `rosterSummary`. Subsequent reads from it will return its default value. + public mutating func clearRosterSummary() {_uniqueStorage()._rosterSummary = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum OneOf_Kind: Equatable, Sendable { + case memberJoined(Flipcash_Chat_V1_RosterUpdate.MemberJoined) + case memberLeft(Flipcash_Chat_V1_RosterUpdate.MemberLeft) + + } + + /// A member joined the chat. When member is the recipient, the recipient + /// has just become a member of the chat and should insert it into their + /// chat list using metadata. + public struct MemberJoined: 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 member that joined, with their profile hydrated, so a client + /// can update its cached member list without a refetch. + public var member: Flipcash_Chat_V1_Member { + get {return _member ?? Flipcash_Chat_V1_Member()} + set {_member = newValue} + } + /// Returns true if `member` has been explicitly set. + public var hasMember: Bool {return self._member != nil} + /// Clears the value of `member`. Subsequent reads from it will return its default value. + public mutating func clearMember() {self._member = nil} + + /// The full metadata for the chat, so the recipient can insert it into + /// their chat list without a refetch. Set only when member is the + /// recipient; unset for every other member of the chat. + /// + /// The recipient inserts the chat from this snapshot, then applies the + /// enclosing RosterUpdate.roster_summary by version like any other + /// roster update. RosterUpdate.roster_summary is authoritative for + /// versioning; metadata.roster_summary is not compared separately. + public var metadata: Flipcash_Chat_V1_Metadata { + get {return _metadata ?? Flipcash_Chat_V1_Metadata()} + set {_metadata = newValue} + } + /// Returns true if `metadata` has been explicitly set. + public var hasMetadata: Bool {return self._metadata != nil} + /// Clears the value of `metadata`. Subsequent reads from it will return its default value. + public mutating func clearMetadata() {self._metadata = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _member: Flipcash_Chat_V1_Member? = nil + fileprivate var _metadata: Flipcash_Chat_V1_Metadata? = nil + } + + /// A member left the chat. When user_id is the recipient, the recipient is + /// no longer a member of the chat and should remove it from their chat + /// list. + public struct MemberLeft: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// The user that left + public var userID: Flipcash_Common_V1_UserId { + get {return _userID ?? Flipcash_Common_V1_UserId()} + set {_userID = newValue} + } + /// Returns true if `userID` has been explicitly set. + public var hasUserID: Bool {return self._userID != nil} + /// Clears the value of `userID`. Subsequent reads from it will return its default value. + public mutating func clearUserID() {self._userID = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _userID: Flipcash_Common_V1_UserId? = nil + } + + public init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + +public struct Flipcash_Chat_V1_RosterUpdateBatch: 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 rosterUpdates: [Flipcash_Chat_V1_RosterUpdate] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "flipcash.chat.v1" @@ -1055,3 +1198,216 @@ extension Flipcash_Chat_V1_MetadataUpdate.LastActivityChanged: SwiftProtobuf.Mes return true } } + +extension Flipcash_Chat_V1_RosterUpdate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".RosterUpdate" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}member_joined\0\u{3}member_left\0\u{4}\u{8}roster_summary\0") + + fileprivate class _StorageClass { + var _kind: Flipcash_Chat_V1_RosterUpdate.OneOf_Kind? + var _rosterSummary: Flipcash_Chat_V1_RosterSummary? = nil + + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _kind = source._kind + _rosterSummary = source._rosterSummary + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + public mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { + var v: Flipcash_Chat_V1_RosterUpdate.MemberJoined? + var hadOneofValue = false + if let current = _storage._kind { + hadOneofValue = true + if case .memberJoined(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._kind = .memberJoined(v) + } + }() + case 2: try { + var v: Flipcash_Chat_V1_RosterUpdate.MemberLeft? + var hadOneofValue = false + if let current = _storage._kind { + hadOneofValue = true + if case .memberLeft(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._kind = .memberLeft(v) + } + }() + case 10: try { try decoder.decodeSingularMessageField(value: &_storage._rosterSummary) }() + default: break + } + } + } + } + + public func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + switch _storage._kind { + case .memberJoined?: try { + guard case .memberJoined(let v)? = _storage._kind else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + }() + case .memberLeft?: try { + guard case .memberLeft(let v)? = _storage._kind else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case nil: break + } + try { if let v = _storage._rosterSummary { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_RosterUpdate, rhs: Flipcash_Chat_V1_RosterUpdate) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._kind != rhs_storage._kind {return false} + if _storage._rosterSummary != rhs_storage._rosterSummary {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_RosterUpdate.MemberJoined: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Chat_V1_RosterUpdate.protoMessageName + ".MemberJoined" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}member\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.decodeSingularMessageField(value: &self._member) }() + 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 + try { if let v = self._member { + try visitor.visitSingularMessageField(value: v, 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_RosterUpdate.MemberJoined, rhs: Flipcash_Chat_V1_RosterUpdate.MemberJoined) -> Bool { + if lhs._member != rhs._member {return false} + if lhs._metadata != rhs._metadata {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_RosterUpdate.MemberLeft: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Chat_V1_RosterUpdate.protoMessageName + ".MemberLeft" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}user_id\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._userID) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._userID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_RosterUpdate.MemberLeft, rhs: Flipcash_Chat_V1_RosterUpdate.MemberLeft) -> Bool { + if lhs._userID != rhs._userID {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_RosterUpdateBatch: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".RosterUpdateBatch" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}roster_updates\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedMessageField(value: &self.rosterUpdates) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.rosterUpdates.isEmpty { + try visitor.visitRepeatedMessageField(value: self.rosterUpdates, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_RosterUpdateBatch, rhs: Flipcash_Chat_V1_RosterUpdateBatch) -> Bool { + if lhs.rosterUpdates != rhs.rosterUpdates {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Sources/Flipcash2ClientProtocol/event_v1_model.pb.swift b/Sources/Flipcash2ClientProtocol/event_v1_model.pb.swift index 014577f..5010e72 100644 --- a/Sources/Flipcash2ClientProtocol/event_v1_model.pb.swift +++ b/Sources/Flipcash2ClientProtocol/event_v1_model.pb.swift @@ -284,21 +284,6 @@ public struct Flipcash_Event_V1_ChatUpdate: Sendable { /// Clears the value of `chat`. Subsequent reads from it will return its default value. public mutating func clearChat() {self._chat = nil} - /// If present, new real-time messages sent on the chat. - /// - /// Deprecated: superseded by `events` (Event.message_sent), which is - /// sequenced and gap-detectable. New messages now arrive as events. - /// - /// NOTE: This field was marked as deprecated in the .proto file. - public var newMessages: Flipcash_Messaging_V1_MessageBatch { - get {return _newMessages ?? Flipcash_Messaging_V1_MessageBatch()} - set {_newMessages = newValue} - } - /// Returns true if `newMessages` has been explicitly set. - public var hasNewMessages: Bool {return self._newMessages != nil} - /// Clears the value of `newMessages`. Subsequent reads from it will return its default value. - public mutating func clearNewMessages() {self._newMessages = nil} - /// If present, message pointer updates for members in the chat. Pointers are /// convergent (monotonic, last-writer-wins), so they ride the stream as a /// best-effort overlay and are reconciled from current state on reconnect — @@ -353,16 +338,35 @@ public struct Flipcash_Event_V1_ChatUpdate: Sendable { /// Clears the value of `reactionUpdates`. Subsequent reads from it will return its default value. public mutating func clearReactionUpdates() {self._reactionUpdates = nil} + /// If present, best-effort real-time roster changes for the chat — members + /// joining or leaving. Like reaction_updates, roster changes are a + /// convergent overlay — NOT part of the gap-detected event log; clients + /// apply them by RosterSummary.version and reconcile any misses by + /// refetching the roster. + /// + /// When a RosterUpdate.MemberLeft names the recipient, the recipient is no + /// longer a member of the chat and should remove it from their chat list. + /// This is how a chat left between pages of a chat feed read is + /// reconciled. + public var rosterUpdates: Flipcash_Chat_V1_RosterUpdateBatch { + get {return _rosterUpdates ?? Flipcash_Chat_V1_RosterUpdateBatch()} + set {_rosterUpdates = newValue} + } + /// Returns true if `rosterUpdates` has been explicitly set. + public var hasRosterUpdates: Bool {return self._rosterUpdates != nil} + /// Clears the value of `rosterUpdates`. Subsequent reads from it will return its default value. + public mutating func clearRosterUpdates() {self._rosterUpdates = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} fileprivate var _chat: Flipcash_Common_V1_ChatId? = nil - fileprivate var _newMessages: Flipcash_Messaging_V1_MessageBatch? = nil fileprivate var _pointerUpdates: Flipcash_Messaging_V1_PointerBatch? = nil fileprivate var _isTypingNotifications: Flipcash_Messaging_V1_IsTypingNotificationBatch? = nil fileprivate var _events: Flipcash_Messaging_V1_EventBatch? = nil fileprivate var _reactionUpdates: Flipcash_Messaging_V1_ReactionUpdateBatch? = nil + fileprivate var _rosterUpdates: Flipcash_Chat_V1_RosterUpdateBatch? = nil } /// BlobUpdate notifies the recipient in real time that blobs they uploaded have @@ -808,7 +812,7 @@ extension Flipcash_Event_V1_ClientPong: SwiftProtobuf.Message, SwiftProtobuf._Me extension Flipcash_Event_V1_ChatUpdate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".ChatUpdate" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}chat\0\u{3}new_messages\0\u{3}pointer_updates\0\u{3}is_typing_notifications\0\u{3}metadata_updates\0\u{1}events\0\u{3}reaction_updates\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}chat\0\u{4}\u{2}pointer_updates\0\u{3}is_typing_notifications\0\u{3}metadata_updates\0\u{1}events\0\u{3}reaction_updates\0\u{3}roster_updates\0\u{c}\u{2}\u{1}") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -817,12 +821,12 @@ extension Flipcash_Event_V1_ChatUpdate: SwiftProtobuf.Message, SwiftProtobuf._Me // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularMessageField(value: &self._chat) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._newMessages) }() case 3: try { try decoder.decodeSingularMessageField(value: &self._pointerUpdates) }() case 4: try { try decoder.decodeSingularMessageField(value: &self._isTypingNotifications) }() case 5: try { try decoder.decodeRepeatedMessageField(value: &self.metadataUpdates) }() case 6: try { try decoder.decodeSingularMessageField(value: &self._events) }() case 7: try { try decoder.decodeSingularMessageField(value: &self._reactionUpdates) }() + case 8: try { try decoder.decodeSingularMessageField(value: &self._rosterUpdates) }() default: break } } @@ -836,9 +840,6 @@ extension Flipcash_Event_V1_ChatUpdate: SwiftProtobuf.Message, SwiftProtobuf._Me try { if let v = self._chat { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() - try { if let v = self._newMessages { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() try { if let v = self._pointerUpdates { try visitor.visitSingularMessageField(value: v, fieldNumber: 3) } }() @@ -854,17 +855,20 @@ extension Flipcash_Event_V1_ChatUpdate: SwiftProtobuf.Message, SwiftProtobuf._Me try { if let v = self._reactionUpdates { try visitor.visitSingularMessageField(value: v, fieldNumber: 7) } }() + try { if let v = self._rosterUpdates { + try visitor.visitSingularMessageField(value: v, fieldNumber: 8) + } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: Flipcash_Event_V1_ChatUpdate, rhs: Flipcash_Event_V1_ChatUpdate) -> Bool { if lhs._chat != rhs._chat {return false} - if lhs._newMessages != rhs._newMessages {return false} if lhs._pointerUpdates != rhs._pointerUpdates {return false} if lhs._isTypingNotifications != rhs._isTypingNotifications {return false} if lhs.metadataUpdates != rhs.metadataUpdates {return false} if lhs._events != rhs._events {return false} if lhs._reactionUpdates != rhs._reactionUpdates {return false} + if lhs._rosterUpdates != rhs._rosterUpdates {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/flipcash2.lock b/flipcash2.lock index f34d0e7..18f30e8 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: 35f9981400947921bbe2872be63b0bb77a569e34 -subject: Update chat rules to be a repeated with and introduce staff requirements (#98) +commit: 89b444bc4ae0d099dbaa90cc8b1b301398c94990 +subject: Add RPCs to start, join and leave group chats (#100) diff --git a/proto/chat/v1/chat_service.proto b/proto/chat/v1/chat_service.proto index eb8ea80..220c782 100644 --- a/proto/chat/v1/chat_service.proto +++ b/proto/chat/v1/chat_service.proto @@ -6,8 +6,10 @@ option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/g option java_package = "com.codeinc.flipcash.gen.chat.v1"; option objc_class_prefix = "FPBChatV1"; +import "blob/v1/model.proto"; import "chat/v1/model.proto"; import "common/v1/common.proto"; +import "moderation/v1/model.proto"; import "validate/validate.proto"; service Chat { @@ -37,6 +39,39 @@ service Chat { // and the stream guarantees freshness and ordering. The local last_activity // sort is maintained by the client from the stream after the initial read. rpc GetDmChatFeed(GetDmChatFeedRequest) returns (GetDmChatFeedResponse); + + // GetGroupChatFeed gets the set of group chats for an owner account using + // a paged API, ordered by last activity with the most recent first. + // + // It has the same read contract as GetDmChatFeed. Chats are ordered by a + // mutable key (last_activity), so pagination alone cannot guarantee a + // complete read. To get the full list, the client MUST combine this RPC + // with the event stream: + // + // 1. Open the event stream to receive ChatUpdate and begin buffering + // updates BEFORE the first GetGroupChatFeed call. + // 2. Page through GetGroupChatFeed to exhaustion (until has_more is + // false), always echoing back the paging token returned by the prior + // response. All pages are served against a single snapshot pinned by + // that token. + // 3. Merge the buffered and ongoing stream updates onto the paginated + // set. Any chat whose activity changed after the snapshot watermark + // is delivered via the stream rather than via pagination. + // + // Unlike the DM feed, a group's membership can change while the feed is + // being read. Every page is served only for groups the caller is still a + // member of at the time of that page; a group the caller left between + // pages is dropped, and its removal arrives on the stream. + rpc GetGroupChatFeed(GetGroupChatFeedRequest) returns (GetGroupChatFeedResponse); + + // StartChat starts a new chat. + rpc StartChat(StartChatRequest) returns (StartChatResponse); + + // JoinChat adds the caller to a chat's roster. + rpc JoinChat(JoinChatRequest) returns (JoinChatResponse); + + // LeaveChat removes the caller from a chat's roster. + rpc LeaveChat(LeaveChatRequest) returns (LeaveChatResponse); } message GetChatRequest { @@ -104,3 +139,137 @@ message GetDmChatFeedResponse { // paging_token. bool has_more = 4; } + +message GetGroupChatFeedRequest { + // QueryOptions controls page_size. Ordering is fixed to most-recent + // activity first and is not client-selectable. + // + // Leave query_options.paging_token unset on the first request: the server + // mints a token that pins a new snapshot and returns it in the response. On + // every subsequent request, set query_options.paging_token to the + // paging_token from the most recent response to advance within the same + // snapshot. The token is opaque and server-generated; do not construct it. + // + // Group feed tokens are larger than DM feed tokens: the server carries + // the snapshot's remaining order in the token so later pages do not + // recompute it. Clients must treat the token as an opaque blob of up to + // common.v1.PagingToken's maximum size. + common.v1.QueryOptions query_options = 1; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message GetGroupChatFeedResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; + NOT_FOUND = 2; + } + + repeated Metadata chats = 2 [(validate.rules).repeated = { + min_items: 0 + max_items: 100 + }]; + + // PagingToken is the server-generated token for this paginated read. On the + // first response it pins a new snapshot; on later responses it carries the + // advanced cursor over (last_activity, chat_id). The client MUST send the + // most recent value back in query_options.paging_token on the next + // GetGroupChatFeedRequest. Set when result is OK. + common.v1.PagingToken paging_token = 3; + + // HasMore indicates whether further pages remain in this snapshot. When + // false, the paginated set has been fully read; the complete chat list is + // this set reconciled with the event stream (see GetGroupChatFeed). When + // true, the client should issue another GetGroupChatFeedRequest with the + // returned paging_token. + bool has_more = 4; +} + +message StartChatRequest { + // Parameters for the kind of chat being created. The variant selects the + // chat type. + oneof parameters { + option (validate.required) = true; + + GroupChatParameters group = 1; + } + + // Parameters for creating a group chat + message GroupChatParameters { + // Title for the chat. + string title = 1 [(validate.rules).string = { + min_len: 1 + max_len: 64 + }]; + + // The blob holding the ORIGINAL picture the caller uploaded. Optional. + // If set, it must be owned by the caller and READY. + blob.v1.BlobId picture = 2; + + // Rules governing participation in the chat. Optional; if not set, the + // chat has no participation requirements. Caller must satisfy the rules + // in order for the chat to be started. + Rules rules = 3; + } + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message StartChatResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; + TITLE_MODERATED = 2; + PICTURE_BLOB_NOT_ACCEPTED = 3; + INVALID_RULES = 4; + RULES_NOT_SATISFIED = 5; + } + + // The metadata for the newly created chat, including the server-generated + // chat_id and any picture renditions the server derived. Set only when + // result == OK. + Metadata chat = 2; + + // The best-fit category that tripped moderation, mirroring the Moderation + // service's vocabulary. Set only when result == TITLE_MODERATED; NONE + // otherwise. + moderation.v1.FlaggedCategory flagged_category = 3; +} + +message JoinChatRequest { + common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message JoinChatResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; + NOT_FOUND = 2; + RULES_NOT_SATISFIED = 3; + } + + // The metadata for the joined chat, as seen by the caller. Set only when + // result == OK. + Metadata chat = 2; +} + +message LeaveChatRequest { + common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message LeaveChatResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; + NOT_FOUND = 2; + } +} diff --git a/proto/chat/v1/model.proto b/proto/chat/v1/model.proto index 6eac1bc..189d0e0 100644 --- a/proto/chat/v1/model.proto +++ b/proto/chat/v1/model.proto @@ -200,3 +200,62 @@ message MetadataUpdate { google.protobuf.Timestamp new_last_activity = 1 [(validate.rules).timestamp.required = true]; } } + +// RosterUpdate is a best-effort, real-time change to a chat's roster — a +// member joining (e.g. via Chat.JoinChat, or as the first member via +// Chat.StartChat) or leaving (e.g. via Chat.LeaveChat). It is delivered to +// the chat's members, including the affected user's other devices. +// +// Roster changes are a convergent overlay, so these ride the event stream +// OUTSIDE the gap-detected event log. Clients apply them by roster_summary +// version as described on RosterSummary: a greater version than the one held +// means the cached member list is stale and this update should be applied; a +// lesser or equal version should be dropped, so delivery order does not +// matter. A missed update is not caught up via GetDelta but reconciled by +// refetching the roster when a client observes a version it cannot reconcile. +message RosterUpdate { + oneof kind { + option (validate.required) = true; + + MemberJoined member_joined = 1; + MemberLeft member_left = 2; + } + + // The chat's roster summary after this change, applied by version as + // described above. + RosterSummary roster_summary = 10 [(validate.rules).message.required = true]; + + // A member joined the chat. When member is the recipient, the recipient + // has just become a member of the chat and should insert it into their + // chat list using metadata. + message MemberJoined { + // The member that joined, with their profile hydrated, so a client + // can update its cached member list without a refetch. + Member member = 1 [(validate.rules).message.required = true]; + + // The full metadata for the chat, so the recipient can insert it into + // their chat list without a refetch. Set only when member is the + // recipient; unset for every other member of the chat. + // + // The recipient inserts the chat from this snapshot, then applies the + // enclosing RosterUpdate.roster_summary by version like any other + // roster update. RosterUpdate.roster_summary is authoritative for + // versioning; metadata.roster_summary is not compared separately. + Metadata metadata = 2; + } + + // A member left the chat. When user_id is the recipient, the recipient is + // no longer a member of the chat and should remove it from their chat + // list. + message MemberLeft { + // The user that left + common.v1.UserId user_id = 1 [(validate.rules).message.required = true]; + } +} + +message RosterUpdateBatch { + repeated RosterUpdate roster_updates = 1 [(validate.rules).repeated = { + min_items: 1 + max_items: 100 + }]; +} diff --git a/proto/common/v1/common.proto b/proto/common/v1/common.proto index 21d46b0..51fb64e 100644 --- a/proto/common/v1/common.proto +++ b/proto/common/v1/common.proto @@ -139,7 +139,7 @@ message FiatPaymentAmount { message PagingToken { bytes value = 1 [(validate.rules).bytes = { min_len: 1 - max_len: 128 + max_len: 16384 }]; } diff --git a/proto/event/v1/model.proto b/proto/event/v1/model.proto index 9c7142c..2074d97 100644 --- a/proto/event/v1/model.proto +++ b/proto/event/v1/model.proto @@ -98,11 +98,8 @@ message ChatUpdate { // The chat that this update is for common.v1.ChatId chat = 1 [(validate.rules).message.required = true]; - // If present, new real-time messages sent on the chat. - // - // Deprecated: superseded by `events` (Event.message_sent), which is - // sequenced and gap-detectable. New messages now arrive as events. - messaging.v1.MessageBatch new_messages = 2 [deprecated = true]; + // Deprecated new_messages field + reserved 2; // If present, message pointer updates for members in the chat. Pointers are // convergent (monotonic, last-writer-wins), so they ride the stream as a @@ -131,6 +128,18 @@ message ChatUpdate { // ReactionUpdate.sequence and reconcile any misses by refreshing a message's // ReactionSummary on view. messaging.v1.ReactionUpdateBatch reaction_updates = 7; + + // If present, best-effort real-time roster changes for the chat — members + // joining or leaving. Like reaction_updates, roster changes are a + // convergent overlay — NOT part of the gap-detected event log; clients + // apply them by RosterSummary.version and reconcile any misses by + // refetching the roster. + // + // When a RosterUpdate.MemberLeft names the recipient, the recipient is no + // longer a member of the chat and should remove it from their chat list. + // This is how a chat left between pages of a chat feed read is + // reconciled. + chat.v1.RosterUpdateBatch roster_updates = 8; } // BlobUpdate notifies the recipient in real time that blobs they uploaded have