Skip to content

chore: migrate loadNextMessages and loadSurroundingMessages methods to rooms.history rest - #7793

Merged
diegolmello merged 23 commits into
developfrom
chore/migrate-load-next-messages-rest
Oct 16, 2026
Merged

diegolmello merged 23 commits into
developfrom
chore/migrate-load-next-messages-rest

Conversation

@Rohit3523

@Rohit3523 Rohit3523 commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Proposed changes

The loadNextMessages and loadSurroundingMessages DDP methods are deprecated and the server removes them in 9.0.0, which would break Jump to Message and the Newer Loader. On servers 8.9.0 and above both now use GET /v1/rooms.history (next and aroundId), and older servers keep the DDP calls. A Newer Loader whose next page comes back empty is now removed instead of staying stuck, on both transports.

Issue(s)

https://rocketchat.atlassian.net/browse/NATIVE-1726
https://rocketchat.atlassian.net/browse/NATIVE-1752

How to test or reproduce

  1. On a server 8.9.0 or above, create a channel with 120 messages (m001 to m120) while the app is closed, and post a link to m003 from another room using the server's Site_Url
  2. Tap the link in the app
  3. Tap "Load newer" on the Newer Loader after m027
  4. Repeat on a server below 8.9.0
  5. Observe on both that the window opens as m001 to m027, then rejoins the live tail with no loader row left

Screenshots

N/A

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

On 8.9.0 and above the endpoint is requested with showThreadMessages: true, so Thread Messages are stored exactly as the DDP methods stored them, and the room list shows the same messages either way because it hides Thread Messages that weren't also sent to the channel (loadMessagesForRoom keeps false, which is out of scope here). It skips the server's hidden system message types, so those system messages are no longer stored by these two paths. If the message to jump to can't be loaded, the jump is cancelled on every server version instead of falling back to the message's timestamp: rooms.history answers 404 (not visible) or 403 (no preview-c-room) on 8.9.0 and above, and the DDP method returns false below it. A DDP reply without messages now rejects in both loaders instead of resolving empty; the Newer Loader row stays as before and the error is logged. Tested on the Android emulator against a local 8.10 server (REST) and mobile.qa 8.4 (DDP). The empty page was produced by stubbing the response, and iOS was not tested.

Summary by CodeRabbit

  • New Features
    • Added paginated room message history, including continuation cursors and messages surrounding a selected message.
    • Message history retrieval works across newer and older server versions.
  • Bug Fixes
    • Empty results now remove loading indicators, and additional indicators appear only when more messages are available.
    • Loading errors are logged and surfaced; failed message jumps are cancelled and loading indicators dismissed.
    • Messages loaded around a selected message are returned in timestamp order.

The loadNextMessages DDP method is removed in server 9.0.0. On servers
>= 8.9.0 the Newer Loader now loads through GET /v1/rooms.history; older
servers keep the DDP call. An empty response removes the Newer Loader
row instead of leaving it stuck.
@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 19190ced-7f54-4abb-b903-a2282fb35799
📥 Commits

Reviewing files that changed from the base of the PR and between f33ff41 and 460c2f2.

📒 Files selected for processing: 9
  • app/lib/methods/__tests__/loadNextMessages.test.ts
  • app/lib/methods/__tests__/loadSurroundingMessages.test.ts
  • app/lib/methods/loadNextMessages.ts
  • app/lib/services/__tests__/restApi.getNextMessages.test.ts
  • app/lib/services/__tests__/restApi.getSurroundingMessages.test.ts
  • app/lib/services/restApi.ts
  • app/views/RoomView/definitions.ts
  • app/views/RoomView/hooks/__tests__/useJumpToMessage.lifecycle.test.tsx
  • app/views/RoomView/services/resolveJumpAnchor.ts
🚧 Files skipped from review as they are similar to previous changes: 1
  • app/lib/methods/tests/loadSurroundingMessages.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

📜 Recent review details
⏰ Context from checks skipped due to timeout: 2
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: E2E Shard Preflight
🔇 Additional comments: 8
app/lib/services/restApi.ts: 1

942-949: LGTM!

Also applies to: 954-956, 969-970, 974-974

app/lib/services/__tests__/restApi.getNextMessages.test.ts: 1

60-60: LGTM!

Also applies to: 65-65, 67-67, 98-98, 111-111

app/lib/services/__tests__/restApi.getSurroundingMessages.test.ts: 1

64-64: LGTM!

Also applies to: 71-71, 79-79, 106-106, 120-120, 136-136, 139-141

app/lib/methods/loadNextMessages.ts: 1

22-22: LGTM!

app/lib/methods/__tests__/loadNextMessages.test.ts: 1

48-48: LGTM!

Also applies to: 53-53

app/views/RoomView/definitions.ts: 1

159-159: LGTM!

app/views/RoomView/services/resolveJumpAnchor.ts: 1

16-17: LGTM!

app/views/RoomView/hooks/__tests__/useJumpToMessage.lifecycle.test.tsx: 1

168-182: LGTM!

Walkthrough

The change adds the rooms.history endpoint and retrieval services that use REST on servers version 8.9.0 or later and DDP on older servers. Both message loaders use these services. Jump-anchor loading now declares its message result type and has a rejection test.

Changes

Room message history

Layer / File(s) Summary
History endpoint and version-aware retrieval
app/definitions/rest/v1/rooms.ts, app/lib/services/restApi.ts, app/lib/services/__tests__/restApi.*.test.ts
The rooms.history contract defines the request and response. Retrieval services select REST or DDP based on server version, convert message timestamps, and return pagination data. Tests cover both paths and failure cases.
Next-message loader pagination
app/lib/methods/loadNextMessages.ts, app/lib/methods/loadNextMessages.test.ts, app/lib/methods/__tests__/loadNextMessages.test.ts
loadNextMessages uses getNextMessages, removes the loader for an empty response, and adds a next-chunk loader when more messages exist and the newest message is not stored. Tests cover request arguments, pagination, ordering, and errors.
Surrounding-message loader and jump anchor
app/lib/methods/loadSurroundingMessages.ts, app/lib/methods/__tests__/loadSurroundingMessages.test.ts, app/views/RoomView/definitions.ts, app/views/RoomView/services/resolveJumpAnchor.ts, app/views/RoomView/hooks/__tests__/useJumpToMessage.lifecycle.test.tsx
loadSurroundingMessages uses getSurroundingMessages, sorts fetched messages, and retains conditional boundary loader updates. The jump-anchor dependency now returns Promise<IMessage[]>. A test covers a rejected server-chunk load.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Other

Sequence Diagram(s)

sequenceDiagram
  participant Loader as Message loader
  participant Retrieval as History retrieval service
  participant REST as rooms.history
  participant DDP as DDP methods
  Loader->>Retrieval: Request messages with room and pagination parameters
  alt Server version 8.9.0 or later
    Retrieval->>REST: Request room history
    REST-->>Retrieval: Messages and cursors
  else Older server
    Retrieval->>DDP: Call legacy history method
    DDP-->>Retrieval: EJSON response
  end
  Retrieval-->>Loader: Messages and pagination data
Loading

Suggested labels: type: bug

Suggested reviewers: diegolmello

Merge Risk: 🟡 Moderate · up to 460c2

An empty history page could remove the only way to load newer messages if the server supplies a continuation. Confirm the server’s empty-page behavior or preserve the continuation before merging.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 13 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: migrating both message-loading methods to the rooms.history REST endpoint.
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Errors were encountered while retrieving linked issues.

Errors: 1
  • JIRA integration encountered authorization issues. Please disconnect and reconnect the integration in the CodeRabbit UI.

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.

…dating

updateMessages compares stored thread rows against the incoming _updatedAt with <, which is always false for an ISO string. The DDP reply carried Dates, so the REST path stopped updating existing threads and thread_messages rows from the Newer Loader.
updateMessages compares stored thread rows against the incoming _updatedAt with <, which is always false for an ISO string. Convert it where the message is normalized instead of in getNextMessages, so every REST loader feeding updateMessages gets the comparison right. Dates, numbers and a missing value are left as they were.
…real buildMessage

The existing thread tests mock buildMessage as identity and use numeric _updatedAt, so they pass with or without the Date conversion. Run the thread and thread message update paths through the real buildMessage with a stored Date and an incoming ISO string.
…lizeMessage

The rooms.history response carries ISO strings, which never compare as newer
in the thread branches of updateMessages. Converting in the REST branch of
the wrapper keeps normalizeMessage, loadMessagesForRoom and loadMissedMessages
unchanged, so those loaders do not start refreshing stored thread rows and
overwriting their downloaded file:// title_link.
@Rohit3523

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@diegolmello diegolmello left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintainability review. I found no release blockers: I checked the rooms.history contract against the server source. The 8.9.0 gate matches the release that shipped aroundId. The next cursor is an exclusive millisecond timestamp, so loaderTs - 1 neither skips nor duplicates messages. cursor.next/cursor.previous map to newer/older on both the next and aroundId paths. The jump target appears only once.

The comments below are structural. Most are one change seen from different angles. Return one result shape from the history adapters and build loader rows in one factory, and both loaders shrink to fetch → sort → add loaders → store.

messages.push(loadMoreItem);
}
}
if (moreBefore) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loader-row literal { _id: generateLoadMoreId(anchor._id), rid, ts: dayjs(anchor.ts) ±1ms .toDate(), t, msg } as IMessage now appears 5 times: twice here, once in loadNextMessages, twice in loadMessagesForRoom. The copies have already drifted (the loadNextMessages one leaves out msg). Each type implies its offset: MORE/PREVIOUS_CHUNK are −1ms and NEXT_CHUNK is +1ms. Could we add a buildLoaderRow(anchor, type) next to generateLoadMoreId? That also removes the five as IMessage casts.

Once getNextMessages returns the same shape as this adapter (see the comment on restApi.ts), the two more* && !(await getMessageById(...)) blocks can become a single shared withChunkLoaders(messages, { moreBefore, moreAfter }) that both loaders call.

});
const lastMessage = messages[messages.length - 1];
if (hasMore && !(await getMessageById(lastMessage._id))) {
messages.push({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same loader literal as in loadSurroundingMessages, minus msg. One factory would stop this drift. Separately, ILoadMoreMessage in app/definitions/IMessage.ts declares ts: string, which is wrong now that every loader stores a Date. Either have the factory return that type (with ts: Date) or delete it.

try {
const { messages: fetched, hasMore } = await getNextMessages({ rid: args.rid, after: args.ts, count: COUNT });
const messages = orderBy(fetched, 'ts');
if (!messages.length) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch only exists because updateMessages returns early when update and remove are both empty, even when a loaderItem was passed. If we add loaderItem to that guard, the branch goes away: updateMessages({ rid, update: messages, loaderItem }) already handles the empty case, and every caller gets the "remove an empty loader" behavior, not just this one.

return { messages: messages as IMessage[], cursor: history.cursor };
};

export const getNextMessages = async ({ rid, after, count }: { rid: string; after: Date; count: number }) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getNextMessages and getSurroundingMessages duplicate each other: same version gate, same DDP fallback (methodCallWrapper → null check → throw → EJSON.fromJSONValue), but different return shapes ({ messages, hasMore } vs { messages, moreBefore, moreAfter }). That shape mismatch is what keeps the two loaders from sharing code. Suggestion: have both return { messages, moreBefore, moreAfter }. On the DDP next path that's moreBefore: false, moreAfter: messages.length === count.

On placement: only getRoomHistory is a REST wrapper. The transport switch, DDP calls, EJSON decoding and cursor interpretation are chunk-loading policy, and restApi.ts is already over 1.3k lines. A small roomHistory module with one supportsRoomsHistory() gate and one fetchHistoryChunk(rid, position) would keep the whole feature in one file.

export const getNextMessages = async ({ rid, after, count }: { rid: string; after: Date; count: number }) => {
const serverVersion = reduxStore.getState().server.version;
if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '8.9.0')) {
const newestLoadedTs = after.getTime() - 1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This - 1 undoes the + 1ms that loadNextMessages applies when it stamps the Newer Loader. That's correct, since the server's next cursor is exclusive. But the two halves are in different files with nothing linking them, and each file's tests only check its own half. If someone changes either offset alone, Load more silently skips or duplicates a message. The parameter name after is also misleading, because the value is one ms past the newest loaded message. Could the offset and its inverse live together in the loader-row helper, or could this take { newestLoadedTs } directly?

throw new Error('Unable to load room history');
}
const messages = history.messages.map(message => ({ ...message, _updatedAt: new Date(message._updatedAt) }));
return { messages: messages as IMessage[], cursor: history.cursor };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only _updatedAt is converted here: ts stays an ISO string, and then everything is cast to IMessage[]. The DDP path returns Date for both fields, so the same return type carries different runtime types depending on the transport. It works today because dayjs and orderBy accept ISO strings. Could we either normalize ts too and drop the cast, or name the mapper after what it guarantees (for example, thread rows need a Date _updatedAt)?

})
);

it('stamps the trailing Older Loader one millisecond before the oldest Message of the Chunk', async () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: these new tests go into a file outside __tests__, but this PR already moved loadNextMessages.test.ts into app/lib/methods/__tests__/. Could this file move too?

@diegolmello
diegolmello merged commit 479ddf9 into develop Oct 16, 2026
2 of 4 checks passed
@diegolmello
diegolmello deleted the chore/migrate-load-next-messages-rest branch October 16, 2026 16:42
diegolmello added a commit that referenced this pull request Oct 16, 2026
* chore: migrate getThreadMessages ddp method to chat.getThreadMessages rest api (#7773)

* chore: migrate loadNextMessages and loadSurroundingMessages methods to rooms.history rest (#7793)

* chore: Bump version to 4.77.2

* chore: migrate DDP methods to REST endpoints (#7783)

---------

Co-authored-by: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com>
@diegolmello diegolmello mentioned this pull request Oct 16, 2026
3 of 10 tasks
diegolmello added a commit that referenced this pull request Oct 16, 2026
* chore: migrate getThreadMessages ddp method to chat.getThreadMessages rest api (#7773)

* chore: migrate loadNextMessages and loadSurroundingMessages methods to rooms.history rest (#7793)

* chore: Bump version to 4.77.2

* chore: migrate DDP methods to REST endpoints (#7783)

---------

Co-authored-by: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com>

This branch is waiting to be deployed

3 waiting deployments
ios_build — 3424df64 Waiting Oct 16, 2026 by diegolmello via Build iOS / Hold #7351
android_build — 3424df64 Waiting Oct 16, 2026 by diegolmello via Build Android / Hold #7351
approve_e2e_testing — 3424df64 Waiting Oct 16, 2026 by diegolmello via E2E Hold #7351
upload_android — f87815eb Deployed Oct 13, 2026 by Rohit3523 via Build Android / Upload Hold #7224
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants