Skip to content

feat: migrate sendMessage + getReadReceipts callers to REST#40675

Draft
ggazzo wants to merge 5 commits into
developfrom
chore/ddp-sendmsg-readreceipts-fix
Draft

feat: migrate sendMessage + getReadReceipts callers to REST#40675
ggazzo wants to merge 5 commits into
developfrom
chore/ddp-sendmsg-readreceipts-fix

Conversation

@ggazzo

@ggazzo ggazzo commented May 25, 2026

Copy link
Copy Markdown
Member

Summary

The two DDP methods that were reverted from #40659 because their client plumbing depends on DDP-specific semantics. This PR contains the deeper refactor each needed.

sendMessage

  • sdk.call('sendMessage', message, previewUrls)sdk.rest.post('/v1/chat.sendMessage', { message, previewUrls }).
  • Primary send flow (apps/meteor/client/lib/chats/flows/sendMessage.ts) feeds the server-rendered { message } back into Messages.state via mapMessageFromApi, replacing the optimistic temp record in the same tick the REST call resolves. Reproduces the Minimongo replication the DDP method triggered.
  • The seven fire-and-forget callsites are straight URL swaps:
    • apps/meteor/client/hooks/notification/useNotification.ts (desktop notification reply)
    • apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx
    • 5 asciiart slashcommands

getReadReceipts

  • useMethod('getReadReceipts')useEndpoint('GET', '/v1/chat.getMessageReadReceipts') in ReadReceiptsModal.
  • Dialog now takes a rid prop and subscribes to notify-room/<rid>/messagesRead; on event, invalidates the ['read-receipts', messageId] react-query so the dialog refetches when new receipts land.
  • mapReadReceiptFromApi helper revives the Date fields the REST endpoint serializes as strings.

Server-side race fix

ReadReceipt.markMessagesAsRead, markMessageAsReadBySender and storeThreadMessagesReadReceipts no longer fire-and-forget the inner storeReadReceipts(...) call — they await it. Closes the read-after-write race that the omnichannel-livechat-read-receipts e2e exposed.

Test plan

  • Quote a message with attachment in a channel; confirm the blockquote appears in the sent message and the composer's quote preview unmounts.
  • Same flow inside a thread.
  • Edit a message after sending.
  • Run the asciiart slash commands.
  • Reply from a desktop notification.
  • Livechat: visitor sends a message; agent opens the chat; agent inspects "Read receipts" — expects two entries (visitor + agent).
  • CI: `omnichannel-livechat-read-receipts.spec.ts:53` passes.
  • CI: `quote-attachment.spec.ts:29` passes.

Task: ARCH-2165

@dionisio-bot

dionisio-bot Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label
  • This PR is targeting the wrong base branch. It should target 8.6.0, but it targets 8.7.0

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot

changeset-bot Bot commented May 25, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 7859dab

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b1321b50-d13f-4ac0-baa1-bc8bf063b443

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented May 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 29.16667% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.82%. Comparing base (275a6ed) to head (7859dab).

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #40675      +/-   ##
===========================================
- Coverage    70.13%   69.82%   -0.31%     
===========================================
  Files         3368     3386      +18     
  Lines       130022   130393     +371     
  Branches     22582    22824     +242     
===========================================
- Hits         91191    91050     -141     
- Misses       35519    36030     +511     
- Partials      3312     3313       +1     
Flag Coverage Δ
unit 70.08% <29.16%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Two methods that resisted the URL-swap pass in the wider DDP -> REST
migration. This PR contains the deeper refactor each needed.

sendMessage
  Replace sdk.call('sendMessage', message, previewUrls) with
  sdk.rest.post('/v1/chat.sendMessage', { message, previewUrls }).
  In the primary send flow (apps/meteor/client/lib/chats/flows/sendMessage.ts)
  the response's server-rendered { message } is fed back into
  Messages.state via mapMessageFromApi, replacing the optimistic temp
  record in the same tick the REST call resolves. That reproduces the
  Minimongo replication the DDP method triggered — composer quote
  previews unmount, attachment renderers see attachments[] and urls[],
  message _updatedAt advances — all without waiting for the
  room-messages stream event to arrive separately.

  The seven fire-and-forget callsites do not run the optimistic
  reconcile and are straight URL swaps:
    - apps/meteor/client/hooks/notification/useNotification.ts
    - apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx
    - apps/meteor/app/slashcommand-asciiarts/client/{lenny,tableflip,unflip,gimme,shrug}.ts

getReadReceipts
  Replace useMethod('getReadReceipts') with
  useEndpoint('GET', '/v1/chat.getMessageReadReceipts') in
  ReadReceiptsModal. Add rid prop and subscribe to
  notify-room/<rid>/messagesRead; on event, invalidate the
  ['read-receipts', messageId] query so the dialog re-fetches when new
  receipts land. mapReadReceiptFromApi revives the Date fields the
  REST endpoint serializes as strings.

Server-side: ReadReceipt.markMessagesAsRead /
ReadReceipt.markMessageAsReadBySender /
ReadReceipt.storeThreadMessagesReadReceipts no longer fire-and-forget
the storeReadReceipts insertion — they await it. Closes the
read-after-write race the omnichannel-livechat-read-receipts e2e test
exposed: a fast REST GET could observe a partial set of receipts
because the visitor's self-receipt insert was still in flight when the
test opened the Read-Receipts dialog.
@ggazzo ggazzo force-pushed the chore/ddp-sendmsg-readreceipts-fix branch from 274ea6b to a8c6aa9 Compare May 27, 2026 15:02
ggazzo and others added 2 commits May 28, 2026 16:41
The optimistic record runs through onClientMessageReceived (which the
E2EE hook subscribes to) so the ciphertext is rendered as plaintext.
The post-REST replacement was bypassing that hook and storing the
server-returned ciphertext directly, leaving encrypted rooms showing
the raw cipher (or the lastUserMessageBody locator timing out in the
e2ee-encrypted-channels e2e suite).

Pipe the server response through the same hook before the state
replacement. For non-encrypted rooms the hook is a no-op
(shouldConvertReceivedMessages returns false), so the change only
affects E2EE flows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The post-REST state.update was replacing the optimistic record with
the server's response unconditionally. When the messages stream
delivered an update first (read-receipt-driven `unread: false`, async
URL/quote attachments arriving via AfterSave hooks, E2EE decrypt
results) the REST resolution would overwrite it with the stale
snapshot the server captured before those side effects landed —
breaking quote rendering in non-encrypted rooms and the 'Message
viewed' status icon used by read-receipts e2e tests.

Restore the original predicate from the DDP-era code: drop the
optimistic `temp` flag only if no other update has touched the record.
The stream (Minimongo replication) keeps doing the heavy lifting for
async-derived fields. The composer's `dismissAllQuotedMessages()`
call already runs from the outer `sendMessage` and doesn't depend on
the state.update body, so quote preview unmount keeps working.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ggazzo ggazzo added this to the 8.6.0 milestone May 28, 2026
@ggazzo

ggazzo commented May 28, 2026

Copy link
Copy Markdown
Member Author

/jira ARCH-2156

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