Skip to content

feat(chat): group chat RPCs and roster updates - #779

Merged
bmc08gt merged 3 commits into
mainfrom
feat/group-chat-rpcs
Sep 16, 2026
Merged

bmc08gt merged 3 commits into
mainfrom
feat/group-chat-rpcs

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Adds the client side of the flipcash2 group chat contract, and pins
flipcash2-client-protocol to 0.7.0.

Blocked on code-payments/flipcash2-client-protocol#13.
The 0.7.0 tag does not exist yet, so the exact: requirement in FlipcashAPI/Package.swift cannot
resolve until that PR merges and publishes. Draft until then. The work was written and built against
the contract checkout through FLIPCASH_PROTO_LOCAL, which CI never sees.

What this adds

Four RPCs on ChatService with matching FlipClient+Chat wrappers: getGroupChatFeed,
startChat, joinChat, leaveChat. ConversationRules gained domain→proto mapping so startChat
takes a domain ConversationRules? without leaking generated types across the FlipClient boundary.

Roster updates — ConversationStreamEvent decodes RosterUpdate/RosterUpdateBatch, and
ConversationStore applies them gated on RosterSummary.version, dropping anything not strictly
greater than the version it holds. ConversationController handles the two cases that concern the
local user: joining inserts the conversation from the metadata snapshot the update carries, and
leaving removes it, which is what the new ConversationStore.remove(_:) and
Database+Conversations.deleteConversation are for.

ChatUpdate.new_messages moving to reserved needs nothing here — this client never read it.

One note for review

ErrorStartChat is an associated-value enum carrying titleModerated(Flipcash_Moderation_V1_FlaggedCategory),
following ErrorProfile rather than the plain-Int-rawValue shape of its three sibling enums in this
file. The category is the only thing that says why a title was rejected, Android surfaces it too,
and flattening it would have left this client able to report only that the title was refused. A
ErrorStartChat.init(_:flaggedCategory:) does the mapping from the proto result, which also means
this enum no longer reconstructs itself positionally from rawValue — an upstream case insertion
can't silently renumber it.

Tested

Full FlipcashCore suite passes (950 tests, 125 suites) and ./Scripts/build.sh gives
** BUILD SUCCEEDED **, both against the local contract checkout.

Add service-layer support for the four new group-chat RPCs added to
the Chat proto service: GetGroupChatFeed, StartChat, JoinChat, and
LeaveChat. Each follows ChatService's existing plain Int-rawValue
error pattern (ErrorGetGroupChatFeed/ErrorStartChat/ErrorJoinChat/
ErrorLeaveChat), with async FlipClient+Chat wrappers and
TransportClassificationTests coverage.

ErrorStartChat can't carry the server's flaggedCategory detail on
.titleModerated since it stays a plain Int-rawValue enum for
consistency with its ChatService siblings — callers only learn the
title was rejected, not why.

StartChat's group parameters need a wire form for ConversationRules,
so add the domain-to-proto direction (ConversationRules,
ConversationListenerRule, ConversationSpeakerRule,
MinimumBalanceRequirement) alongside the existing proto-to-domain
init.

Wire the new RosterUpdate/RosterUpdateBatch messages into the event
stream: decode them into DecodedRosterUpdate/RosterChange, apply them
in ConversationStore by RosterSummary.version (drop if not greater),
and handle self-join/self-leave in ConversationController — a join
with an embedded chat snapshot inserts it into the feed directly, a
leave naming the signed-in user removes it and deletes the local
row via the new Database+Conversations.deleteConversation.

ChatUpdate.new_messages (field 2) is unused on this client already,
so its removal (now reserved) needs no changes here.
ErrorStartChat dropped the moderation category the server reports on
TITLE_MODERATED, so a rejected title could only be reported as
"rejected," not why. Android already surfaces this as
StartChatError.TitleModerated(flaggedCategory); dropping it on iOS
diverges from a contract both clients consume.

Convert ErrorStartChat to an associated-value enum modelled on
ErrorProfile, which already carries a FlaggedCategory the same way
for ProfileService's moderation cases. .titleModerated(category)
replaces the payload-less case; the plain-Int siblings
(ErrorGetGroupChatFeed/ErrorJoinChat/ErrorLeaveChat) are unchanged
since they have no payload to carry.

ChatService.startChat now switches on the response result explicitly
instead of rebuilding it via rawValue, through a new
ErrorStartChat.init(_:flaggedCategory:) that maps StartChatResponse's
proto Result to the domain error. Keeping this mapping as a pure,
synchronous init (rather than inlining the switch in the async Task
body) makes it unit-testable on its own, and means a case inserted
upstream can no longer silently renumber this enum the way rawValue
mapping could.
Group chat RPCs and roster updates need the 0.7.0 contract. The tag does not
exist yet, so the exact requirement cannot resolve until it publishes.
@bmc08gt bmc08gt self-assigned this Sep 15, 2026
@bmc08gt
bmc08gt marked this pull request as ready for review September 16, 2026 15:09
@bmc08gt
bmc08gt merged commit 567780d into main Sep 16, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant