feat(chat): handle the E2EE contract additions and bump the contract packages - #870
Merged
Merged
Conversation
messaging_service.proto adds ENCRYPTION_NOT_ALLOWED to SendMessageResponse.Result and EditMessageResponse.Result (EncryptedContent sent outside a DM). Add the case to ErrorSendMessage/ErrorEditMessage after the existing cases so raw values still line up with the server, and route it through the existing failure/classification paths. model.proto's Content.type oneof adds an encrypted case; map it through the same unsupported-content path as media/system for now. Actual E2EE (X25519/HKDF/ XChaCha20) is a cross-platform parity hotspot and needs its own decision — this only keeps decoding from crashing/dropping the switch. chat/v1/model.proto's Metadata adds creator (group chats) and use_e2ee (DMs, transitional migration flag). Mirror both onto Conversation; use_e2ee is stored but not acted on yet.
push/v1/model.proto moved ChatMetadata.message into a message_ref oneof alongside a new message_id, so payload.chatMetadata.hasMessage no longer compiles. Switch on messageRef instead; the id-only case (long messages) returns nil here rather than fetching via Messaging.GetMessage. Not a regression in practice: NotificationService's transcript prefetch (cachePreview) already fetches the chat's recent messages over its own connection independently of the embedded payload, so an id-only push still gets a rendered preview, just without the no-network fast path the embedded-message case gives. Left a TODO on chatMessage(_:) naming the proto field and the gap, in case that fast path turns out to matter later.
Content.encrypted decoded to nil, so an encrypted DM message vanished from the transcript instead of being stored, unlike Android's "unsupported message" placeholder. Add ConversationMessage.Content.encrypted(scheme:nonce:ciphertext:), persist all three fields in the SQLite store (kind 3, three new nullable columns, schema version 40), and render it as the existing .deleted tombstone bubble with new copy: "This message isn't supported on this version". Decryption itself (X25519/HKDF/XChaCha20) is out of scope; the content is kept verbatim, not decoded. Wire the new case through every place that switches on Content so nothing crashes or silently drops it: chat-list preview, reply quoting, copy/link detection, and message capabilities (no copy/edit/reply capability, matching a tombstone). .media/.system are still dropped by design -- unchanged.
Every path that turns a domain ConversationMessage.Content back into a proto Content assumed .text. The only such path is ConversationController.retry(clientMessageID:in:), which pattern- matched `case .text` and silently no-opped for anything else -- including the encrypted case just added, which this client can't regenerate ciphertext for. Give ConversationMessage.Content an asProto() that switches exhaustively: .text encodes as before, .encrypted re-encodes the stored scheme/nonce/ciphertext byte for byte (a round trip of bytes this client never decrypted, not encryption), and .cash/.deleted throw ConversationMessageContentEncodingError.unsupported -- neither is ever resent this way (cash has its own send path, a tombstone is a mutation result). No fatalError, preconditionFailure, or force-unwrap on any branch. retry() now switches on pending.content: .text sends as before; .encrypted/.cash/.deleted call asProto() and log the thrown error rather than resending, since there's no outbox path for them today. Caller trace: retry(clientMessageID:in:) is the only place a stored ConversationMessage.Content is converted back to a proto Content -- send/edit/reply all build a fresh .text Content directly rather than converting an existing domain value.
chat.v1.Metadata's creator (group chats) and use_e2ee were mapped from the proto onto Conversation but never written to or read back from the SQLite store, so a conversation rebuilt from disk on cold start lost both. Add creator (nullable UUID) and useE2Ee (bool, default false) columns to ConversationTable and wire them through writeConversation/ getConversations. No separate schema-version bump: the columns land in version 40, the same rebuild already needed for the encrypted- message columns, since the version can only be bumped once per rebuild and splitting it across two commits would cost users a second resync for no benefit.
The open feat/chat-reactions branch also moves schemaVersion from 39 to 40. Identical one-line edits merge without a conflict, so whichever branch landed second would change the store with no new version, and installs already at 40 would keep a store missing its columns. Skipping 40 costs nothing: the launch check only compares the recorded version against this one.
…rotocol to 0.12.0 Neither version is published yet, so this does not resolve until both client packages are released.
This was referenced Sep 25, 2026
Merged
bmc08gt
marked this pull request as ready for review
September 25, 2026 17:31
bmc08gt
added a commit
that referenced
this pull request
Sep 26, 2026
* origin/main: feat(chat): send cash into a group chat as a cash link (#871) feat(chat): handle the E2EE contract additions and bump the contract packages (#870) docs(claude): drop think-harder lines and needless stops from agent instructions (#868) # Conflicts: # FlipcashCore/Sources/FlipcashStore/Database+Conversations.swift # FlipcashCore/Sources/FlipcashStore/Database.swift # FlipcashCore/Sources/FlipcashStore/Schema.swift
bmc08gt
added a commit
that referenced
this pull request
Sep 26, 2026
…I pins #870 moved FlipcashAPI's pins to ocp 0.6.0 and flipcash2 0.12.0 but left Package.resolved on 0.5.0 and 0.11.0, so every build rewrote it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Blocked on publishing
ocp-client-protocol0.6.0 (code-payments/ocp-client-protocol#9) andflipcash2-client-protocol0.12.0 (code-payments/flipcash2-client-protocol#18). Until both are tagged, theexact:requirements inFlipcashAPI/Package.swifthave nothing to resolve. Built locally against both client branches withFLIPCASH_PROTO_LOCAL.Handles the flipcash2 E2EE contract additions without implementing any encryption:
ENCRYPTION_NOT_ALLOWEDis appended toErrorSendMessageandErrorEditMessage, so existing raw values don't shift.Content.encryptednow maps to.encrypted(scheme:nonce:ciphertext:)instead ofnil. Before, an encrypted DM message just disappeared. It renders as the deleted-message tombstone with "This message isn't supported on this version", and all three fields are stored in SQLite.Content.asProto()switches exhaustively.ConversationController.retryis the only place stored content goes back to proto;.encryptedis written back byte for byte, while.cash/.deletedthrow and get logged.ConversationgainscreatoranduseE2Ee, persisted inConversationTable.useE2Eechanges no behaviour yet.ChatMetadata.messagemoved into themessage_refoneof, which removedhasMessage.NotificationPayloadnow switches onmessageRef. A push carrying onlymessage_idfalls back to the extension's existing fetch of recent messages; a directGetMessagefetch is a TODO.schemaVersiongoes from 39 to 41, not 40:feat/chat-reactionsalso moves it to 40, and identical one-line edits merge silently. Either version triggers the same one-time store rebuild.The ocp
fiat_values_by_currencyfields aren't wired in.BalanceServicemaps intoTokenAmount, so exposing fiat values needs a modelling decision, not a sync.Android counterpart: code-payments/code-android-app#1573